From 5a55eb11c670c2c43d8267677c9f2c62c14f3319 Mon Sep 17 00:00:00 2001 From: James Gayfer <10660608+jgayfer@users.noreply.github.com> Date: Wed, 17 Jul 2024 10:25:38 -0700 Subject: [PATCH 1/2] Use custom point light preparation 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. --- src/plugin.rs | 33 +++++++------ src/render/extract.rs | 2 +- src/render/lighting/node.rs | 11 ++--- src/render/mod.rs | 1 + src/render/prepare.rs | 94 +++++++++++++++++++++++++++++++++++++ 5 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 src/render/prepare.rs diff --git a/src/plugin.rs b/src/plugin.rs index d7fe516..0ce2017 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -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, }, @@ -17,14 +17,12 @@ use bevy::{ use crate::{ light::{AmbientLight2d, PointLight2d}, render::{ - extract::{ - extract_ambient_lights, extract_point_lights, ExtractedAmbientLight2d, - ExtractedPointLight2d, - }, + extract::{extract_ambient_lights, extract_point_lights, ExtractedAmbientLight2d}, lighting::{ prepare_lighting_pipelines, LightingNode, LightingPass, LightingPipeline, LIGHTING_SHADER, }, + prepare::{prepare_point_lights, GpuPointLights}, }, }; @@ -40,16 +38,13 @@ impl Plugin for Light2dPlugin { Shader::from_wgsl ); - app.add_plugins(( - UniformComponentPlugin::::default(), - GpuComponentArrayBufferPlugin::::default(), - )) - .register_type::() - .register_type::() - .add_systems( - PostUpdate, - check_visibility::>.in_set(VisibilitySystems::CheckVisibility), - ); + app.add_plugins(UniformComponentPlugin::::default()) + .register_type::() + .register_type::() + .add_systems( + PostUpdate, + check_visibility::>.in_set(VisibilitySystems::CheckVisibility), + ); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; @@ -65,6 +60,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::>(Core2d, LightingPass) .add_render_graph_edge(Core2d, Node2d::EndMainPass, LightingPass); } @@ -75,5 +74,9 @@ impl Plugin for Light2dPlugin { }; render_app.init_resource::(); + + render_app.insert_resource(GpuPointLights::new( + render_app.world().resource::(), + )); } } diff --git a/src/render/extract.rs b/src/render/extract.rs index 86538d3..a3871f4 100644 --- a/src/render/extract.rs +++ b/src/render/extract.rs @@ -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, diff --git a/src/render/lighting/node.rs b/src/render/lighting/node.rs index 6a11303..a8fe28e 100644 --- a/src/render/lighting/node.rs +++ b/src/render/lighting/node.rs @@ -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}; @@ -58,10 +58,7 @@ impl ViewNode for LightingNode { return Ok(()); }; - let Some(point_light_binding) = world - .resource::>() - .binding() - else { + let Some(point_light_binding) = world.resource::().binding() else { return Ok(()); }; diff --git a/src/render/mod.rs b/src/render/mod.rs index aec3230..5934d04 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,2 +1,3 @@ pub mod extract; pub mod lighting; +pub mod prepare; diff --git a/src/render/prepare.rs b/src/render/prepare.rs new file mode 100644 index 0000000..51e4489 --- /dev/null +++ b/src/render/prepare.rs @@ -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), + Storage(StorageBuffer), +} + +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) { + 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 { + 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, +} + +pub fn prepare_point_lights( + render_device: Res, + render_queue: Res, + point_light_query: Query<&ExtractedPointLight2d>, + mut gpu_point_lights: ResMut, +) { + let point_lights = point_light_query + .iter() + .cloned() + .collect::>(); + + gpu_point_lights.set(point_lights.clone()); + gpu_point_lights.write_buffer(&render_device, &render_queue); +} From 0a3d3e19987d7deaeda1197f493ee1da755a21ec Mon Sep 17 00:00:00 2001 From: James Gayfer <10660608+jgayfer@users.noreply.github.com> Date: Fri, 19 Jul 2024 09:36:41 -0700 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c2f659..c237934 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Ambient light not working when there are no point lights (#17). + + ## [0.2.0] - 2024-07-04 ### Added