Skip to content

Commit

Permalink
Use custom point light preparation
Browse files Browse the repository at this point in the history
While GpuArrayBuffer does a lot of this for us, it also has a quirk
where it won't write to the GPU if the array is empty, which messes
with our pipeline.
  • Loading branch information
jgayfer committed Jul 17, 2024
1 parent c06faed commit bf7593a
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 20 deletions.
29 changes: 17 additions & 12 deletions src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use bevy::{
prelude::*,
render::{
extract_component::UniformComponentPlugin,
gpu_component_array_buffer::GpuComponentArrayBufferPlugin,
render_graph::{RenderGraphApp, ViewNodeRunner},
render_resource::SpecializedRenderPipelines,
renderer::RenderDevice,
view::{check_visibility, VisibilitySystems},
Render, RenderApp, RenderSet,
},
Expand All @@ -19,12 +19,12 @@ use crate::{
render::{
extract::{
extract_ambient_lights, extract_point_lights, ExtractedAmbientLight2d,
ExtractedPointLight2d,
},
lighting::{
prepare_lighting_pipelines, LightingNode, LightingPass, LightingPipeline,
LIGHTING_SHADER,
},
prepare::{prepare_point_lights, GpuPointLights},
},
};

Expand All @@ -40,16 +40,13 @@ impl Plugin for Light2dPlugin {
Shader::from_wgsl
);

app.add_plugins((
UniformComponentPlugin::<ExtractedAmbientLight2d>::default(),
GpuComponentArrayBufferPlugin::<ExtractedPointLight2d>::default(),
))
.register_type::<AmbientLight2d>()
.register_type::<PointLight2d>()
.add_systems(
PostUpdate,
check_visibility::<With<PointLight2d>>.in_set(VisibilitySystems::CheckVisibility),
);
app.add_plugins(UniformComponentPlugin::<ExtractedAmbientLight2d>::default())
.register_type::<AmbientLight2d>()
.register_type::<PointLight2d>()
.add_systems(
PostUpdate,
check_visibility::<With<PointLight2d>>.in_set(VisibilitySystems::CheckVisibility),
);

let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
Expand All @@ -65,6 +62,10 @@ impl Plugin for Light2dPlugin {
Render,
prepare_lighting_pipelines.in_set(RenderSet::Prepare),
)
.add_systems(
Render,
prepare_point_lights.in_set(RenderSet::PrepareResources),
)
.add_render_graph_node::<ViewNodeRunner<LightingNode>>(Core2d, LightingPass)
.add_render_graph_edge(Core2d, Node2d::EndMainPass, LightingPass);
}
Expand All @@ -75,5 +76,9 @@ impl Plugin for Light2dPlugin {
};

render_app.init_resource::<LightingPipeline>();

render_app.insert_resource(GpuPointLights::new(
render_app.world().resource::<RenderDevice>(),
));
}
}
2 changes: 1 addition & 1 deletion src/render/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bevy::{

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

#[derive(Component, Default, Clone, ShaderType)]
#[derive(Component, Default, Clone, ShaderType, Copy)]
pub struct ExtractedPointLight2d {
pub transform: Vec2,
pub radius: f32,
Expand Down
11 changes: 4 additions & 7 deletions src/render/lighting/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ use bevy::render::extract_component::{ComponentUniforms, DynamicUniformIndex};
use bevy::render::render_graph::ViewNode;

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

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

use super::{LightingPipeline, LightingPipelineId};

Expand Down Expand Up @@ -58,10 +58,7 @@ impl ViewNode for LightingNode {
return Ok(());
};

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

Expand Down
1 change: 1 addition & 0 deletions src/render/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod extract;
pub mod lighting;
pub mod prepare;
94 changes: 94 additions & 0 deletions src/render/prepare.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use super::extract::ExtractedPointLight2d;
use bevy::{
prelude::*,
render::{
render_resource::{BindingResource, ShaderType, StorageBuffer, UniformBuffer},
renderer::{RenderDevice, RenderQueue},
},
};

const MAX_UNIFORM_POINT_LIGHTS: usize = 256;

#[derive(Resource)]
pub enum GpuPointLights {
Uniform(UniformBuffer<GpuPointLightsUniform>),
Storage(StorageBuffer<GpuPointLightsStorage>),
}

impl GpuPointLights {
pub fn new(device: &RenderDevice) -> Self {
let limits = device.limits();
if limits.max_storage_buffers_per_shader_stage == 0 {
GpuPointLights::Uniform(UniformBuffer::from(GpuPointLightsUniform::default()))
} else {
GpuPointLights::Storage(StorageBuffer::from(GpuPointLightsStorage::default()))
}
}

fn set(&mut self, mut point_lights: Vec<ExtractedPointLight2d>) {
match self {
GpuPointLights::Uniform(buffer) => {
let len = point_lights.len().min(MAX_UNIFORM_POINT_LIGHTS);
let src = &point_lights[..len];
let dst = &mut buffer.get_mut().data[..len];
dst.copy_from_slice(src);
}
GpuPointLights::Storage(buffer) => {
buffer.get_mut().data.clear();
buffer.get_mut().data.append(&mut point_lights);
}
}
}

fn write_buffer(&mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
match self {
GpuPointLights::Uniform(buffer) => {
buffer.write_buffer(render_device, render_queue);
}
GpuPointLights::Storage(buffer) => {
buffer.write_buffer(render_device, render_queue);
}
}
}

pub fn binding(&self) -> Option<BindingResource> {
match self {
GpuPointLights::Uniform(buffer) => buffer.binding(),
GpuPointLights::Storage(buffer) => buffer.binding(),
}
}
}

#[derive(ShaderType)]
pub struct GpuPointLightsUniform {
data: Box<[ExtractedPointLight2d; MAX_UNIFORM_POINT_LIGHTS]>,
}

impl Default for GpuPointLightsUniform {
fn default() -> Self {
Self {
data: Box::new([ExtractedPointLight2d::default(); MAX_UNIFORM_POINT_LIGHTS]),
}
}
}

#[derive(Default, ShaderType)]
pub struct GpuPointLightsStorage {
#[size(runtime)]
pub data: Vec<ExtractedPointLight2d>,
}

pub fn prepare_point_lights(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
point_light_query: Query<&ExtractedPointLight2d>,
mut gpu_point_lights: ResMut<GpuPointLights>,
) {
let point_lights = point_light_query
.iter()
.cloned()
.collect::<Vec<ExtractedPointLight2d>>();

gpu_point_lights.set(point_lights.clone());
gpu_point_lights.write_buffer(&render_device, &render_queue);
}

0 comments on commit bf7593a

Please sign in to comment.