From e0a2b2dfa216318993089e789ee43d4bacd0c389 Mon Sep 17 00:00:00 2001 From: James Gayfer <10660608+jgayfer@users.noreply.github.com> Date: Mon, 26 Aug 2024 08:16:38 -0700 Subject: [PATCH] Use empty buffer instead of populating junk data (#27) * Add an "empty" buffer When our GpuArrayBuffer has no data in it, we don't have a binding to use to send to the GPU, in which case the node generally won't run. We can use this "empty" buffer to supply our shaders with a "junk" binding, allowing the shaders to still run (assuming we don't read from this). * Use empty buffer as stand in for empty array buffers With a fallback "empty" binding, we no longer have to send junk point light and occluder data to the GPU (which I believe has been causing some issues). * Update CHANGELOG.md --- CHANGELOG.md | 1 + src/plugin.rs | 3 +++ src/render/empty_buffer.rs | 42 ++++++++++++++++++++++++++++++++++++ src/render/extract.rs | 15 ------------- src/render/light_map/node.rs | 4 +++- src/render/mod.rs | 1 + src/render/sdf/node.rs | 4 +++- 7 files changed, 53 insertions(+), 17 deletions(-) create mode 100644 src/render/empty_buffer.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 85afbe3..337944e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Point lights rendering despite being despawned (#25). +- Shadow sometimes appearing when no occluders were present (#27). ### Migration guide diff --git a/src/plugin.rs b/src/plugin.rs index 1415b7b..7f40c9d 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -18,6 +18,7 @@ use crate::{ light::{AmbientLight2d, PointLight2d}, occluder::LightOccluder2d, render::{ + empty_buffer::{prepare_empty_buffer, EmptyBuffer}, extract::{ extract_ambient_lights, extract_light_occluders, extract_point_lights, ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d, @@ -84,6 +85,7 @@ impl Plugin for Light2dPlugin { render_app .init_resource::>() .init_resource::() + .init_resource::() .add_systems( ExtractSchedule, ( @@ -97,6 +99,7 @@ impl Plugin for Light2dPlugin { ( prepare_lighting_pipelines.in_set(RenderSet::Prepare), prepare_point_light_count.in_set(RenderSet::Prepare), + prepare_empty_buffer.in_set(RenderSet::Prepare), prepare_sdf_texture .after(prepare_view_targets) .in_set(RenderSet::ManageViews), diff --git a/src/render/empty_buffer.rs b/src/render/empty_buffer.rs new file mode 100644 index 0000000..4683214 --- /dev/null +++ b/src/render/empty_buffer.rs @@ -0,0 +1,42 @@ +use bevy::{ + ecs::system::{Res, ResMut, Resource}, + render::{ + render_resource::{BindingResource, Buffer, BufferDescriptor, BufferUsages}, + renderer::RenderDevice, + }, +}; + +/// A resource serving as a general purpose "empty" buffer, allowing us to use it as a +/// stand in for times we don't have a usable binding (such as an empty array). +#[derive(Resource, Default)] +pub struct EmptyBuffer { + pub buffer: Option, +} + +impl EmptyBuffer { + pub fn binding(&self) -> Option { + self.buffer + .as_ref() + .map(|buffer| BindingResource::Buffer(buffer.as_entire_buffer_binding())) + } + + pub fn fill_buffer(&mut self, render_device: &RenderDevice) { + if self.buffer.is_none() { + self.buffer = Some(render_device.create_buffer(&BufferDescriptor { + label: "empty-buffer".into(), + // This needs to be at least as big as the items we're storing in our + // GPUArrayBuffer. + size: 64, + usage: BufferUsages::COPY_DST | BufferUsages::STORAGE | BufferUsages::UNIFORM, + mapped_at_creation: false, + })); + } + } +} + +pub fn prepare_empty_buffer( + mut empty_buffer: ResMut, + render_device: Res, +) { + empty_buffer.fill_buffer(&render_device); +} diff --git a/src/render/extract.rs b/src/render/extract.rs index 72f7942..30deb74 100644 --- a/src/render/extract.rs +++ b/src/render/extract.rs @@ -46,17 +46,6 @@ pub fn extract_point_lights( cast_shadows: if point_light.cast_shadows { 1 } else { 0 }, }); } - - // BufferVec won't write to the GPU if there aren't any point lights. - // For now we can spawn an empty point light to get around this. - commands.spawn(ExtractedPointLight2d { - transform: Vec2::ZERO, - intensity: 0.0, - radius: 0.0, - falloff: 0.0, - color: LinearRgba::BLACK, - cast_shadows: 0, - }); } pub fn extract_light_occluders( @@ -79,10 +68,6 @@ pub fn extract_light_occluders( commands.get_or_spawn(entity).insert(extracted_occluder); } - - // BufferVec won't write to the GPU if there aren't any point lights. - // For now we can spawn an empty occluder to get around this. - commands.spawn(ExtractedLightOccluder2d::default()); } pub fn extract_ambient_lights( diff --git a/src/render/light_map/node.rs b/src/render/light_map/node.rs index 931e527..8f58d12 100644 --- a/src/render/light_map/node.rs +++ b/src/render/light_map/node.rs @@ -11,6 +11,7 @@ use bevy::render::renderer::RenderDevice; use bevy::render::view::{ViewUniformOffset, ViewUniforms}; use smallvec::{smallvec, SmallVec}; +use crate::render::empty_buffer::EmptyBuffer; use crate::render::extract::{ExtractedAmbientLight2d, ExtractedPointLight2d}; use crate::render::sdf::SdfTexture; @@ -58,7 +59,8 @@ impl ViewNode for LightMapNode { .binding(), world .resource::>() - .binding(), + .binding() + .or(world.resource::().binding()), world.resource::().buffer.binding(), ) else { diff --git a/src/render/mod.rs b/src/render/mod.rs index 5e64c32..88979b5 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,5 +1,6 @@ use bevy::{asset::Handle, render::render_resource::Shader}; +pub mod empty_buffer; pub mod extract; pub mod light_map; pub mod lighting; diff --git a/src/render/sdf/node.rs b/src/render/sdf/node.rs index 8c79cdc..496b7b0 100644 --- a/src/render/sdf/node.rs +++ b/src/render/sdf/node.rs @@ -10,6 +10,7 @@ use bevy::render::renderer::RenderDevice; use bevy::render::view::{ViewUniformOffset, ViewUniforms}; use smallvec::{smallvec, SmallVec}; +use crate::render::empty_buffer::EmptyBuffer; use crate::render::extract::ExtractedLightOccluder2d; use super::pipeline::SdfPipeline; @@ -39,7 +40,8 @@ impl ViewNode for SdfNode { world.resource::().uniforms.binding(), world .resource::>() - .binding(), + .binding() + .or(world.resource::().binding()), ) else { return Ok(()); };