Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for WebGL2 #7

Merged
merged 1 commit into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ use bevy::{
prelude::*,
render::{
extract_component::UniformComponentPlugin,
gpu_component_array_buffer::GpuComponentArrayBufferPlugin,
render_graph::{RenderGraphApp, ViewNodeRunner},
Render, RenderApp, RenderSet,
RenderApp,
},
};

use crate::{
light::{AmbientLight2d, PointLight2d},
render::{
extract::{extract_ambient_lights, extract_point_lights, ExtractedAmbientLight2d},
gpu::{prepare_point_lights, GpuPointLights},
extract::{
extract_ambient_lights, extract_point_lights, ExtractedAmbientLight2d,
ExtractedPointLight2d,
},
lighting::{LightingNode, LightingPass, LightingPipeline, LIGHTING_SHADER},
},
};
Expand All @@ -32,9 +35,12 @@ impl Plugin for Light2dPlugin {
Shader::from_wgsl
);

app.add_plugins(UniformComponentPlugin::<ExtractedAmbientLight2d>::default())
.register_type::<AmbientLight2d>()
.register_type::<PointLight2d>();
app.add_plugins((
UniformComponentPlugin::<ExtractedAmbientLight2d>::default(),
GpuComponentArrayBufferPlugin::<ExtractedPointLight2d>::default(),
))
.register_type::<AmbientLight2d>()
.register_type::<PointLight2d>();

let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
Expand All @@ -45,7 +51,6 @@ impl Plugin for Light2dPlugin {
ExtractSchedule,
(extract_point_lights, extract_ambient_lights),
)
.add_systems(Render, (prepare_point_lights).in_set(RenderSet::Prepare))
.add_render_graph_node::<ViewNodeRunner<LightingNode>>(Core2d, LightingPass)
.add_render_graph_edge(Core2d, Node2d::MainPass, LightingPass);
}
Expand All @@ -55,8 +60,6 @@ impl Plugin for Light2dPlugin {
return;
};

render_app
.init_resource::<LightingPipeline>()
.init_resource::<GpuPointLights>();
render_app.init_resource::<LightingPipeline>();
}
}
18 changes: 5 additions & 13 deletions src/render/extract.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
use bevy::{
core_pipeline::core_2d::Camera2d,
ecs::{
component::Component,
entity::Entity,
query::{With, Without},
system::{Commands, Query},
},
math::Vec4,
render::{render_resource::ShaderType, view::ViewVisibility, Extract},
transform::components::GlobalTransform,
prelude::*,
render::{render_resource::ShaderType, Extract},
};

use crate::light::{AmbientLight2d, PointLight2d};

#[derive(Component, Default, Clone)]
#[derive(Component, Default, Clone, ShaderType)]
pub struct ExtractedPointLight2d {
pub transform: GlobalTransform,
pub transform: Vec2,
pub radius: f32,
pub color: Vec4,
pub intensity: f32,
Expand All @@ -37,7 +29,7 @@ pub fn extract_point_lights(
}
commands.get_or_spawn(entity).insert(ExtractedPointLight2d {
color: point_light.color.rgba_linear_to_vec4(),
transform: *global_transform,
transform: global_transform.translation().xy(),
radius: point_light.radius,
intensity: point_light.intensity,
falloff: point_light.falloff,
Expand Down
48 changes: 0 additions & 48 deletions src/render/gpu.rs

This file was deleted.

28 changes: 24 additions & 4 deletions src/render/lighting/lighting.wgsl
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
#import bevy_render::view::View

// We're currently only using a single uniform binding for point lights in
// WebGL2, which is limited to 4kb in BatchedUniformBuffer, so we need to
// ensure our point lights can fit in 4kb.
const MAX_POINT_LIGHTS: u32 = 82u;

struct PointLight2d {
center: vec2f,
radius: f32,
Expand Down Expand Up @@ -46,18 +51,33 @@ var texture_sampler: sampler;
var<uniform> view: View;

@group(0) @binding(3)
var<storage> point_lights: array<PointLight2d>;

@group(0) @binding(4)
var<uniform> ambient_light: AmbientLight2d;

// WebGL2 does not support storage buffers, so we fall back to a fixed length
// array in a uniform buffer.
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
@group(0) @binding(4)
var<storage> point_lights: array<PointLight2d>;
#else
@group(0) @binding(4)
var<uniform> point_lights: array<PointLight2d, MAX_POINT_LIGHTS>;
#endif

@fragment
fn fragment(vo: FullscreenVertexOutput) -> @location(0) vec4<f32> {
// Setup aggregate color from light sources to multiply the main texture by.
var light_color = vec3(1.0);

// WebGL2 does not support storage buffers (or runtime sized arrays), so we
// need to use a fixed number of point lights.
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 6
let point_light_count = arrayLength(&point_lights);
#else
let point_light_count = MAX_POINT_LIGHTS;
#endif

// For each light, determine its illumination if we're within range of it.
for (var i = 0u; i < arrayLength(&point_lights); i++) {
for (var i = 0u; i < point_light_count; i++) {

let point_light = point_lights[i];

Expand Down
39 changes: 30 additions & 9 deletions src/render/lighting/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ use bevy::render::extract_component::{ComponentUniforms, DynamicUniformIndex};
use bevy::render::render_graph::ViewNode;

use bevy::render::render_resource::{
BindGroupEntries, Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
BindGroupEntries, GpuArrayBuffer, Operations, PipelineCache, RenderPassColorAttachment,
RenderPassDescriptor,
};
use bevy::render::renderer::RenderDevice;
use bevy::render::view::{ViewTarget, ViewUniformOffset, ViewUniforms};
use bevy::utils::smallvec::{smallvec, SmallVec};

use crate::render::extract::ExtractedAmbientLight2d;
use crate::render::gpu::GpuPointLights;
use crate::render::extract::{ExtractedAmbientLight2d, ExtractedPointLight2d};

use super::LightingPipeline;

Expand Down Expand Up @@ -45,10 +47,6 @@ impl ViewNode for LightingNode {
return Ok(());
};

let Some(point_light_buffer) = world.resource::<GpuPointLights>().buffer.binding() else {
return Ok(());
};

let Some(ambient_light_uniform) = world
.resource::<ComponentUniforms<ExtractedAmbientLight2d>>()
.uniforms()
Expand All @@ -57,6 +55,13 @@ impl ViewNode for LightingNode {
return Ok(());
};

let Some(point_light_binding) = world
.resource::<GpuArrayBuffer<ExtractedPointLight2d>>()
.binding()
else {
return Ok(());
};

let post_process = view_target.post_process_write();

let bind_group = render_context.render_device().create_bind_group(
Expand All @@ -66,8 +71,8 @@ impl ViewNode for LightingNode {
post_process.source,
&lighting_pipeline.sampler,
view_uniform_binding,
point_light_buffer,
ambient_light_uniform,
point_light_binding,
)),
);

Expand All @@ -83,8 +88,24 @@ impl ViewNode for LightingNode {
occlusion_query_set: None,
});

let mut dynamic_offsets: SmallVec<[u32; 3]> =
smallvec![view_offset.offset, ambient_index.index()];

// Storage buffers aren't available in WebGL2. We fall back to a
// dynamic uniform buffer, and therefore need to provide the offset.
// We're providing a value of 0 here as we're limiting the number of
// point lights to only those that can reasonably fit in a single binding.
if world
.resource::<RenderDevice>()
.limits()
.max_storage_buffers_per_shader_stage
== 0
{
dynamic_offsets.push(0);
}

render_pass.set_render_pipeline(pipeline);
render_pass.set_bind_group(0, &bind_group, &[view_offset.offset, ambient_index.index()]);
render_pass.set_bind_group(0, &bind_group, &dynamic_offsets);
render_pass.draw(0..3, 0..1);

Ok(())
Expand Down
14 changes: 6 additions & 8 deletions src/render/lighting/pipeline.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
use bevy::core_pipeline::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
use bevy::prelude::*;
use bevy::render::render_resource::binding_types::{
sampler, storage_buffer_read_only, texture_2d, uniform_buffer,
};
use bevy::render::render_resource::binding_types::{sampler, texture_2d, uniform_buffer};
use bevy::render::render_resource::{
BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites,
FragmentState, MultisampleState, PipelineCache, PrimitiveState, RenderPipelineDescriptor,
Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages, TextureFormat, TextureSampleType,
FragmentState, GpuArrayBuffer, MultisampleState, PipelineCache, PrimitiveState,
RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
TextureFormat, TextureSampleType,
};
use bevy::render::renderer::RenderDevice;
use bevy::render::texture::BevyDefault;
use bevy::render::view::ViewUniform;

use crate::render::extract::ExtractedAmbientLight2d;
use crate::render::gpu::GpuPointLight2d;
use crate::render::extract::{ExtractedAmbientLight2d, ExtractedPointLight2d};

use super::LIGHTING_SHADER;

Expand All @@ -39,8 +37,8 @@ impl FromWorld for LightingPipeline {
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
uniform_buffer::<ViewUniform>(true),
storage_buffer_read_only::<Vec<GpuPointLight2d>>(false),
uniform_buffer::<ExtractedAmbientLight2d>(true),
GpuArrayBuffer::<ExtractedPointLight2d>::binding_layout(render_device),
),
),
);
Expand Down
1 change: 0 additions & 1 deletion src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
pub mod extract;
pub mod gpu;
pub mod lighting;
Loading