Skip to content

Commit

Permalink
Add support for WebGL2 (#7)
Browse files Browse the repository at this point in the history
Adds limited support for wasm builds against WebGL2.

The max number of point lights is fixed at a modest amount of lights as
we're only using a single binding of a fixed length array, but we can
increase this if necessary with more uniform bindings.
  • Loading branch information
jgayfer authored Jun 10, 2024
1 parent 284bc5b commit e054874
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 93 deletions.
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;

0 comments on commit e054874

Please sign in to comment.