From f23311e2ed205ac0e4712210049ba3078b10a7d7 Mon Sep 17 00:00:00 2001 From: James Gayfer <10660608+jgayfer@users.noreply.github.com> Date: Wed, 31 Jul 2024 21:24:53 -0700 Subject: [PATCH] Use separate node for SDF passo Not strictly necessary, but I do like having an explicit render graph, especially if it's something someone else ever wanted to tweak. --- src/plugin.rs | 8 ++-- src/render/lighting/mod.rs | 2 - src/render/lighting/node.rs | 56 +--------------------- src/render/lighting/pipeline.rs | 57 +--------------------- src/render/mod.rs | 1 + src/render/sdf/mod.rs | 16 +++++++ src/render/sdf/node.rs | 85 +++++++++++++++++++++++++++++++++ src/render/sdf/pipeline.rs | 66 +++++++++++++++++++++++++ 8 files changed, 177 insertions(+), 114 deletions(-) create mode 100644 src/render/sdf/mod.rs create mode 100644 src/render/sdf/node.rs create mode 100644 src/render/sdf/pipeline.rs diff --git a/src/plugin.rs b/src/plugin.rs index c7e5d91..a569f50 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -23,9 +23,10 @@ use crate::{ }, lighting::{ prepare_lighting_auxiliary_textures, prepare_lighting_pipelines, LightMapPipeline, - LightingNode, LightingPass, LightingPipeline, SdfPipeline, LIGHTING_SHADER, - LIGHT_MAP_SHADER, SDF_SHADER, TYPES_SHADER, + LightingNode, LightingPass, LightingPipeline, LIGHTING_SHADER, LIGHT_MAP_SHADER, + TYPES_SHADER, }, + sdf::{SdfNode, SdfPass, SdfPipeline, SDF_SHADER}, }, }; @@ -99,7 +100,8 @@ impl Plugin for Light2dPlugin { ), ) .add_render_graph_node::>(Core2d, LightingPass) - .add_render_graph_edge(Core2d, Node2d::EndMainPass, LightingPass); + .add_render_graph_node::>(Core2d, SdfPass) + .add_render_graph_edges(Core2d, (Node2d::EndMainPass, SdfPass, LightingPass)); } fn finish(&self, app: &mut App) { diff --git a/src/render/lighting/mod.rs b/src/render/lighting/mod.rs index baed3b6..011ab62 100644 --- a/src/render/lighting/mod.rs +++ b/src/render/lighting/mod.rs @@ -17,8 +17,6 @@ pub use prepare::*; pub const TYPES_SHADER: Handle = Handle::weak_from_u128(134542958402584092759402858489640143033); -pub const SDF_SHADER: Handle = - Handle::weak_from_u128(231804371047309214783091483091843019281); pub const LIGHTING_SHADER: Handle = Handle::weak_from_u128(111120241052143214281687226997564407636); pub const LIGHT_MAP_SHADER: Handle = diff --git a/src/render/lighting/node.rs b/src/render/lighting/node.rs index df90728..5ca6184 100644 --- a/src/render/lighting/node.rs +++ b/src/render/lighting/node.rs @@ -11,17 +11,10 @@ use bevy::render::renderer::RenderDevice; use bevy::render::view::{ViewTarget, ViewUniformOffset, ViewUniforms}; use smallvec::{smallvec, SmallVec}; -use crate::render::extract::{ - ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d, -}; +use crate::render::extract::{ExtractedAmbientLight2d, ExtractedPointLight2d}; -use super::{ - LightMapPipeline, Lighting2dAuxiliaryTextures, LightingPipeline, LightingPipelineId, - SdfPipeline, -}; +use super::{LightMapPipeline, Lighting2dAuxiliaryTextures, LightingPipeline, LightingPipelineId}; -const SDF_PASS: &str = "sdf_pass"; -const SDF_BIND_GROUP: &str = "sdf_bind_group"; const LIGHT_MAP_PASS: &str = "light_map_pass"; const LIGHT_MAP_BIND_GROUP: &str = "light_map_bind_group"; const LIGHTING_PASS: &str = "lighting_pass"; @@ -49,21 +42,17 @@ impl ViewNode for LightingNode { >, world: &'w World, ) -> Result<(), bevy::render::render_graph::NodeRunError> { - let sdf_pipeline_resource = world.resource::(); let light_map_pipeline_resource = world.resource::(); let pipeline = world.resource::(); let pipeline_cache = world.resource::(); let ( - Some(sdf_pipeline), Some(lighting_pipeline), Some(light_map_pipeline), Some(view_uniform_binding), Some(ambient_light_uniform), Some(point_light_binding), - Some(light_occluders_binding), ) = ( - pipeline_cache.get_render_pipeline(sdf_pipeline_resource.pipeline_id), pipeline_cache.get_render_pipeline(pipeline_id.0), pipeline_cache.get_render_pipeline(light_map_pipeline_resource.pipeline_id), world.resource::().uniforms.binding(), @@ -74,52 +63,11 @@ impl ViewNode for LightingNode { world .resource::>() .binding(), - world - .resource::>() - .binding(), ) else { return Ok(()); }; - // SDF - let bind_group = render_context.render_device().create_bind_group( - SDF_BIND_GROUP, - &sdf_pipeline_resource.layout, - &BindGroupEntries::sequential((view_uniform_binding.clone(), light_occluders_binding)), - ); - - let mut sdf_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { - label: Some(SDF_PASS), - color_attachments: &[Some(RenderPassColorAttachment { - view: &aux_textures.sdf.default_view, - resolve_target: None, - ops: Operations::default(), - })], - ..default() - }); - - let mut dynamic_offsets: SmallVec<[u32; 3]> = smallvec![view_offset.offset]; - - // 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::() - .limits() - .max_storage_buffers_per_shader_stage - == 0 - { - dynamic_offsets.push(0); - } - - sdf_pass.set_render_pipeline(sdf_pipeline); - sdf_pass.set_bind_group(0, &bind_group, &dynamic_offsets); - sdf_pass.draw(0..3, 0..1); - - drop(sdf_pass); - // Light map let light_map_bind_group = render_context.render_device().create_bind_group( LIGHT_MAP_BIND_GROUP, diff --git a/src/render/lighting/pipeline.rs b/src/render/lighting/pipeline.rs index 10c3040..da0461c 100644 --- a/src/render/lighting/pipeline.rs +++ b/src/render/lighting/pipeline.rs @@ -11,68 +11,15 @@ use bevy::render::renderer::RenderDevice; use bevy::render::texture::BevyDefault; use bevy::render::view::{ViewTarget, ViewUniform}; -use crate::render::extract::{ - ExtractedAmbientLight2d, ExtractedLightOccluder2d, ExtractedPointLight2d, -}; +use crate::render::extract::{ExtractedAmbientLight2d, ExtractedPointLight2d}; -use super::{LightingPipelineKey, LIGHTING_SHADER, LIGHT_MAP_SHADER, SDF_SHADER}; +use super::{LightingPipelineKey, LIGHTING_SHADER, LIGHT_MAP_SHADER}; -const SDF_PIPELINE: &str = "sdf_pipeline"; -const SDF_BIND_GROUP_LAYOUT: &str = "sdf_bind_group_layout"; const LIGHTING_PIPELINE: &str = "lighting_pipeline"; const LIGHTING_BIND_GROUP_LAYOUT: &str = "lighting_bind_group_layout"; const LIGHT_MAP_BIND_GROUP_LAYOUT: &str = "light_map_group_layout"; const LIGHT_MAP_PIPELINE: &str = "light_map_pipeline"; -#[derive(Resource)] -pub struct SdfPipeline { - pub layout: BindGroupLayout, - pub pipeline_id: CachedRenderPipelineId, -} - -impl FromWorld for SdfPipeline { - fn from_world(world: &mut World) -> Self { - let render_device = world.resource::(); - let pipeline_cache = world.resource::(); - - let layout = render_device.create_bind_group_layout( - SDF_BIND_GROUP_LAYOUT, - &BindGroupLayoutEntries::sequential( - ShaderStages::FRAGMENT, - ( - uniform_buffer::(true), - GpuArrayBuffer::::binding_layout(render_device), - ), - ), - ); - - let pipeline_id = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { - label: Some(SDF_PIPELINE.into()), - layout: vec![layout.clone()], - vertex: fullscreen_shader_vertex_state(), - fragment: Some(FragmentState { - shader: SDF_SHADER, - shader_defs: vec![], - entry_point: "fragment".into(), - targets: vec![Some(ColorTargetState { - format: TextureFormat::Rgba16Float, - blend: None, - write_mask: ColorWrites::ALL, - })], - }), - primitive: PrimitiveState::default(), - depth_stencil: None, - multisample: MultisampleState::default(), - push_constant_ranges: vec![], - }); - - Self { - layout, - pipeline_id, - } - } -} - #[derive(Resource)] pub struct LightMapPipeline { pub layout: BindGroupLayout, diff --git a/src/render/mod.rs b/src/render/mod.rs index aec3230..c4004ed 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,2 +1,3 @@ pub mod extract; pub mod lighting; +pub mod sdf; diff --git a/src/render/sdf/mod.rs b/src/render/sdf/mod.rs new file mode 100644 index 0000000..14de977 --- /dev/null +++ b/src/render/sdf/mod.rs @@ -0,0 +1,16 @@ +mod node; +mod pipeline; + +use bevy::{ + asset::Handle, + render::{render_graph::RenderLabel, render_resource::Shader}, +}; + +pub use node::SdfNode; +pub use pipeline::SdfPipeline; + +pub const SDF_SHADER: Handle = + Handle::weak_from_u128(231804371047309214783091483091843019281); + +#[derive(Debug, Hash, PartialEq, Eq, Clone, RenderLabel)] +pub struct SdfPass; diff --git a/src/render/sdf/node.rs b/src/render/sdf/node.rs new file mode 100644 index 0000000..cab7baf --- /dev/null +++ b/src/render/sdf/node.rs @@ -0,0 +1,85 @@ +use bevy::ecs::system::lifetimeless::Read; +use bevy::prelude::*; +use bevy::render::render_graph::ViewNode; + +use bevy::render::render_resource::{ + BindGroupEntries, GpuArrayBuffer, Operations, PipelineCache, RenderPassColorAttachment, + RenderPassDescriptor, +}; +use bevy::render::renderer::RenderDevice; +use bevy::render::view::{ViewUniformOffset, ViewUniforms}; +use smallvec::{smallvec, SmallVec}; + +use crate::render::extract::ExtractedLightOccluder2d; + +use crate::render::lighting::Lighting2dAuxiliaryTextures; + +use super::pipeline::SdfPipeline; + +const SDF_PASS: &str = "sdf_pass"; +const SDF_BIND_GROUP: &str = "sdf_bind_group"; + +#[derive(Default)] +pub struct SdfNode; + +impl ViewNode for SdfNode { + type ViewQuery = (Read, Read); + + fn run<'w>( + &self, + _graph: &mut bevy::render::render_graph::RenderGraphContext, + render_context: &mut bevy::render::renderer::RenderContext<'w>, + (view_offset, aux_textures): bevy::ecs::query::QueryItem<'w, Self::ViewQuery>, + world: &'w World, + ) -> Result<(), bevy::render::render_graph::NodeRunError> { + let sdf_pipeline = world.resource::(); + let pipeline_cache = world.resource::(); + + let (Some(pipeline), Some(view_uniform_binding), Some(light_occluders_binding)) = ( + pipeline_cache.get_render_pipeline(sdf_pipeline.pipeline_id), + world.resource::().uniforms.binding(), + world + .resource::>() + .binding(), + ) else { + return Ok(()); + }; + + let bind_group = render_context.render_device().create_bind_group( + SDF_BIND_GROUP, + &sdf_pipeline.layout, + &BindGroupEntries::sequential((view_uniform_binding.clone(), light_occluders_binding)), + ); + + let mut sdf_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor { + label: Some(SDF_PASS), + color_attachments: &[Some(RenderPassColorAttachment { + view: &aux_textures.sdf.default_view, + resolve_target: None, + ops: Operations::default(), + })], + ..default() + }); + + let mut dynamic_offsets: SmallVec<[u32; 3]> = smallvec![view_offset.offset]; + + // 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::() + .limits() + .max_storage_buffers_per_shader_stage + == 0 + { + dynamic_offsets.push(0); + } + + sdf_pass.set_render_pipeline(pipeline); + sdf_pass.set_bind_group(0, &bind_group, &dynamic_offsets); + sdf_pass.draw(0..3, 0..1); + + Ok(()) + } +} diff --git a/src/render/sdf/pipeline.rs b/src/render/sdf/pipeline.rs new file mode 100644 index 0000000..253c340 --- /dev/null +++ b/src/render/sdf/pipeline.rs @@ -0,0 +1,66 @@ +use bevy::core_pipeline::fullscreen_vertex_shader::fullscreen_shader_vertex_state; +use bevy::prelude::*; +use bevy::render::render_resource::binding_types::uniform_buffer; +use bevy::render::render_resource::{ + BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId, ColorTargetState, ColorWrites, + FragmentState, GpuArrayBuffer, MultisampleState, PipelineCache, PrimitiveState, + RenderPipelineDescriptor, ShaderStages, TextureFormat, +}; +use bevy::render::renderer::RenderDevice; +use bevy::render::view::ViewUniform; + +use crate::render::extract::ExtractedLightOccluder2d; + +use super::SDF_SHADER; + +const SDF_PIPELINE: &str = "sdf_pipeline"; +const SDF_BIND_GROUP_LAYOUT: &str = "sdf_bind_group_layout"; + +#[derive(Resource)] +pub struct SdfPipeline { + pub layout: BindGroupLayout, + pub pipeline_id: CachedRenderPipelineId, +} + +impl FromWorld for SdfPipeline { + fn from_world(world: &mut World) -> Self { + let render_device = world.resource::(); + let pipeline_cache = world.resource::(); + + let layout = render_device.create_bind_group_layout( + SDF_BIND_GROUP_LAYOUT, + &BindGroupLayoutEntries::sequential( + ShaderStages::FRAGMENT, + ( + uniform_buffer::(true), + GpuArrayBuffer::::binding_layout(render_device), + ), + ), + ); + + let pipeline_id = pipeline_cache.queue_render_pipeline(RenderPipelineDescriptor { + label: Some(SDF_PIPELINE.into()), + layout: vec![layout.clone()], + vertex: fullscreen_shader_vertex_state(), + fragment: Some(FragmentState { + shader: SDF_SHADER, + shader_defs: vec![], + entry_point: "fragment".into(), + targets: vec![Some(ColorTargetState { + format: TextureFormat::Rgba16Float, + blend: None, + write_mask: ColorWrites::ALL, + })], + }), + primitive: PrimitiveState::default(), + depth_stencil: None, + multisample: MultisampleState::default(), + push_constant_ranges: vec![], + }); + + Self { + layout, + pipeline_id, + } + } +}