From 4c2ed6ca802edd974abe66e644046d32f8605463 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 28 Apr 2024 17:51:35 -0400 Subject: [PATCH 01/20] render: create foundation of render graph, also add super simple topological sort for pass execution path --- lyra-game/src/render/graph/execution_path.rs | 86 ++++++++++++ lyra-game/src/render/graph/mod.rs | 114 ++++++++++++++++ lyra-game/src/render/graph/pass.rs | 125 ++++++++++++++++++ lyra-game/src/render/graph/passes/base.rs | 37 ++++++ .../render/graph/passes/light_cull_compute.rs | 66 +++++++++ lyra-game/src/render/graph/passes/mod.rs | 5 + lyra-game/src/render/mod.rs | 3 +- 7 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 lyra-game/src/render/graph/execution_path.rs create mode 100644 lyra-game/src/render/graph/mod.rs create mode 100644 lyra-game/src/render/graph/pass.rs create mode 100644 lyra-game/src/render/graph/passes/base.rs create mode 100644 lyra-game/src/render/graph/passes/light_cull_compute.rs create mode 100644 lyra-game/src/render/graph/passes/mod.rs diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs new file mode 100644 index 0000000..cdbbc6e --- /dev/null +++ b/lyra-game/src/render/graph/execution_path.rs @@ -0,0 +1,86 @@ +use std::collections::{HashMap, VecDeque}; + +use rustc_hash::{FxHashMap, FxHashSet}; + +use super::RenderGraphPassDesc; + +pub struct GraphExecutionPath { + /// Queue of the path, top is the first to be executed. + /// Each element is the handle of a pass. + pub queue: VecDeque, +} + +impl GraphExecutionPath { + pub fn new(pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { + // collect all the output slots + let mut total_outputs = HashMap::new(); + for desc in pass_descriptions.iter() { + for slot in desc.output_slots() { + total_outputs.insert(slot.name.clone(), SlotOwnerPair { + pass: desc.id, + slot: slot.id, + }); + } + } + + let mut nodes = FxHashMap::::default(); + for desc in pass_descriptions.iter() { + // find the node inputs + let mut inputs = vec![]; + for slot in desc.input_slots() { + let inp = total_outputs.get(&slot.name) + .expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name)); + inputs.push(*inp); + } + + let node = Node { + id: desc.id, + desc: (*desc).clone(), + slot_inputs: inputs + }; + nodes.insert(node.id, node); + } + + // sort the graph + let mut stack = VecDeque::new(); + let mut visited = FxHashSet::default(); + for (_, no) in nodes.iter() { + Self::topological_sort(&nodes, &mut stack, &mut visited, no); + } + + Self { + queue: stack, + } + } + + fn topological_sort(graph: &FxHashMap, stack: &mut VecDeque, visited: &mut FxHashSet, node: &Node) { + if !visited.contains(&node.id) { + visited.insert(node.id); + + for depend in &node.slot_inputs { + let depend_node = graph.get(&depend.pass) + .expect("could not find dependent node"); + + if !visited.contains(&depend.pass) { + Self::topological_sort(graph, stack, visited, depend_node); + } + } + + stack.push_back(node.id); + } + + } +} + +#[derive(Debug, Clone, Copy)] +struct SlotOwnerPair { + pass: u64, + slot: u64, +} + +#[derive(Clone)] +struct Node { + id: u64, + desc: RenderGraphPassDesc, + slot_inputs: Vec, +} diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs new file mode 100644 index 0000000..c6b824c --- /dev/null +++ b/lyra-game/src/render/graph/mod.rs @@ -0,0 +1,114 @@ +mod pass; +use std::collections::HashMap; + +pub use pass::*; + +mod passes; +pub use passes::*; + +mod execution_path; + +use rustc_hash::FxHashMap; +use wgpu::RenderPass; + +use super::renderer::{BasicRenderer, Renderer}; + +struct PassEntry { + inner: Box, + desc: RenderGraphPassDesc, +} + +struct ResourcedSlot { + slot: RenderPassSlot, + value: SlotValue, + bindgroup: wgpu::BindGroup, +} + +#[derive(Default)] +pub struct RenderGraph { + slots: FxHashMap, + slot_names: HashMap, + passes: FxHashMap, + current_id: u64, +} + +impl RenderGraph { + pub fn new() -> Self { + Self::default() + } + + pub fn slot_id(&self, name: &str) -> Option { + self.slot_names.get(name).cloned() + } + + pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { + self.passes.get(&id) + .map(|s| &s.desc) + } + + pub fn bindgroup(&self, id: u64) -> Option<&wgpu::BindGroup> { + self.slots.get(&id) + .map(|s| &s.bindgroup) + } + + pub fn add_pass(&mut self, pass: P) { + let desc = pass.desc(&mut self.current_id); + self.passes.insert(desc.id, PassEntry { + inner: Box::new(pass), + desc, + }); + } + + /// Creates all buffers required for the passes, also creates an internal execution path. + pub fn setup(&mut self) { + todo!() + } + + pub fn prepare(&mut self) { + todo!() + } + + pub fn render(&mut self, renderer: &mut BasicRenderer) { + todo!() + } +} + +pub struct RenderGraphContext { + +} + +impl RenderGraphContext { + pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass { + todo!() + } + + pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass { + todo!() + } +} + +#[cfg(test)] +mod tests { + use winit::dpi::PhysicalSize; + + use super::{execution_path::GraphExecutionPath, BasePass, LightCullComputePass, RenderGraph}; + + #[test] + fn full_test() { + let mut graph = RenderGraph::new(); + + graph.add_pass(BasePass::new()); + graph.add_pass(LightCullComputePass::new(PhysicalSize::new(800, 600))); + + let descs = graph.passes.values().map(|pass| &pass.desc).collect(); + let mut path = GraphExecutionPath::new(descs); + + println!("Pass execution order:"); + let mut num = 1; + while let Some(pass_id) = path.queue.pop_front() { + let pass = graph.pass(pass_id).unwrap(); + println!(" {}: {}", num, pass.name); + num += 1; + } + } +} \ No newline at end of file diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs new file mode 100644 index 0000000..7809d11 --- /dev/null +++ b/lyra-game/src/render/graph/pass.rs @@ -0,0 +1,125 @@ +use std::collections::HashMap; + +use lyra_ecs::World; + +use super::{RenderGraph, RenderGraphContext}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] +pub enum RenderPassType { + Compute, + #[default] + Render +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SlotType { + TextureView, + Sampler, + Buffer, +} + +#[derive(Debug)] +pub enum SlotValue { + TextureView(wgpu::TextureView), + Sampler(wgpu::Sampler), + Buffer(wgpu::Buffer), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum SlotAttribute { + Input, + Output, +} + +#[derive(Clone)] +pub struct RenderPassSlot { + pub ty: SlotType, + pub attribute: SlotAttribute, + pub id: u64, + pub name: String, + + // buffer desc, texture desc +} + +#[derive(Clone)] +pub struct RenderGraphPassDesc { + pub id: u64, + pub name: String, + pub pass_type: RenderPassType, + pub slots: Vec, + slot_names: HashMap, +} + +impl RenderGraphPassDesc { + pub fn new(id: u64, name: &str, pass_type: RenderPassType) -> Self { + Self { + id, + name: name.to_string(), + pass_type, + slots: vec![], + slot_names: HashMap::default(), + } + } + + pub fn add_slot(&mut self, slot: RenderPassSlot) { + self.slot_names.insert(slot.name.clone(), slot.id); + self.slots.push(slot); + } + + #[inline(always)] + pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) { + let slot = RenderPassSlot { + id, + name: name.to_string(), + ty: SlotType::Buffer, + attribute, + }; + self.add_slot(slot); + } + + #[inline(always)] + pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) { + let slot = RenderPassSlot { + id, + name: name.to_string(), + ty: SlotType::TextureView, + attribute, + }; + self.add_slot(slot); + } + + #[inline(always)] + pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) { + let slot = RenderPassSlot { + id, + name: name.to_string(), + ty: SlotType::Sampler, + attribute, + }; + self.add_slot(slot); + } + + pub fn input_slots(&self) -> Vec<&RenderPassSlot> { + self.slots + .iter() + .filter(|s| s.attribute == SlotAttribute::Input) + .collect() + } + + pub fn output_slots(&self) -> Vec<&RenderPassSlot> { + self.slots + .iter() + .filter(|s| s.attribute == SlotAttribute::Output) + .collect() + } +} + +pub trait RenderGraphPass: 'static { + /// Create a render pass describer. + /// + /// The `id` argument is passed as mutable so you can increment it as you use it for new slots. + fn desc(&self, id: &mut u64) -> RenderGraphPassDesc; + + fn prepare(&mut self, world: &mut World); + fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext); +} \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs new file mode 100644 index 0000000..a51be89 --- /dev/null +++ b/lyra-game/src/render/graph/passes/base.rs @@ -0,0 +1,37 @@ +use crate::render::graph::{RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute}; + +/// Supplies some basic things other passes needs. +/// +/// screen size buffer, camera buffer, +#[derive(Default)] +pub struct BasePass; + +impl BasePass { + pub fn new() -> Self { + Self::default() + } +} + +impl RenderGraphPass for BasePass { + fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc { + let mut desc = RenderGraphPassDesc::new(*id, "BasePass", RenderPassType::Compute); + *id += 1; + + desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output); + *id += 1; + desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output); + *id += 1; + + desc + } + + fn prepare(&mut self, world: &mut lyra_ecs::World) { + let _ = world; + todo!() + } + + fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, desc: &crate::render::graph::RenderGraphPassDesc, context: &mut crate::render::graph::RenderGraphContext) { + let _ = (graph, desc, context); + todo!() + } +} \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs new file mode 100644 index 0000000..43318ff --- /dev/null +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -0,0 +1,66 @@ +use lyra_ecs::World; + +use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute}; + +pub struct LightCullComputePass { + workgroup_size: glam::UVec2, +} + +impl LightCullComputePass { + pub fn new(screen_size: winit::dpi::PhysicalSize) -> Self { + Self { + workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height) + } + } +} + +impl RenderGraphPass for LightCullComputePass { + fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc { + let mut desc = RenderGraphPassDesc::new(*id, "LightCullCompute", RenderPassType::Compute); + *id += 1; + + desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input); + *id += 1; + desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input); + *id += 1; + + desc.add_buffer_slot(*id, "light_indices", SlotAttribute::Output); + *id += 1; + /* desc.add_buffer_slot(*id, "indices_buffer", SlotAttribute::Output); + *id += 1; */ + desc.add_texture_view_slot(*id, "grid_texture", SlotAttribute::Output); + *id += 1; + + + desc + } + + fn prepare(&mut self, world: &mut World) { + let _ = world; + todo!() + } + + fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _: &crate::render::graph::RenderGraphPassDesc, context: &mut RenderGraphContext) { + let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("Pass_lightCull") + }); + + let depth_tex = graph.bindgroup(graph.slot_id("depth_texture").expect("Could not find depth texture slot")).unwrap(); + let camera_bg = graph.bindgroup(graph.slot_id("camera_buffer").expect("Could not find camera buffers")).unwrap(); + let screen_size_bg = graph.bindgroup(graph.slot_id("screen_size_buffer").expect("Could not find screen size buffer slot")).unwrap(); + let indices_bg = graph.bindgroup(graph.slot_id("light_indices").expect("Could not find light index buffer slot")).unwrap(); + let light_grid_bg = graph.bindgroup(graph.slot_id("grid_texture").expect("Could not find light grid buffer slot")).unwrap(); + + //pass.set_pipeline(pipeline) + + pass.set_bind_group(0, depth_tex, &[]); + pass.set_bind_group(1, camera_bg, &[]); + pass.set_bind_group(2, indices_bg, &[]); + pass.set_bind_group(3, light_grid_bg, &[]); + pass.set_bind_group(4, screen_size_bg, &[]); + + pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1); + + + } +} \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs new file mode 100644 index 0000000..0871d73 --- /dev/null +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -0,0 +1,5 @@ +mod light_cull_compute; +pub use light_cull_compute::*; + +mod base; +pub use base::*; \ No newline at end of file diff --git a/lyra-game/src/render/mod.rs b/lyra-game/src/render/mod.rs index d7985d5..2d4c650 100755 --- a/lyra-game/src/render/mod.rs +++ b/lyra-game/src/render/mod.rs @@ -13,4 +13,5 @@ pub mod window; pub mod transform_buffer_storage; pub mod light; pub mod light_cull_compute; -pub mod avec; \ No newline at end of file +pub mod avec; +pub mod graph; \ No newline at end of file From a4e80d4fec4cb515f45be86426bff199eb06bffa Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 4 May 2024 10:02:50 -0400 Subject: [PATCH 02/20] render: continue work of render graph, expanding RenderGraphPass and removing things from the renderer --- lyra-game/src/render/compute_pipeline.rs | 35 ++ lyra-game/src/render/graph/mod.rs | 236 +++++++-- lyra-game/src/render/graph/pass.rs | 57 ++- lyra-game/src/render/graph/passes/base.rs | 18 +- .../src/render/graph/passes/depth_prepass.rs | 94 ++++ .../render/graph/passes/light_cull_compute.rs | 123 ++++- lyra-game/src/render/graph/passes/mod.rs | 5 +- lyra-game/src/render/graph/slot_desc.rs | 166 ++++++ lyra-game/src/render/mod.rs | 2 + lyra-game/src/render/pipeline.rs | 65 +++ lyra-game/src/render/render_pipeline.rs | 37 +- lyra-game/src/render/renderer.rs | 473 ++---------------- 12 files changed, 787 insertions(+), 524 deletions(-) create mode 100644 lyra-game/src/render/compute_pipeline.rs create mode 100644 lyra-game/src/render/graph/passes/depth_prepass.rs create mode 100644 lyra-game/src/render/graph/slot_desc.rs create mode 100644 lyra-game/src/render/pipeline.rs diff --git a/lyra-game/src/render/compute_pipeline.rs b/lyra-game/src/render/compute_pipeline.rs new file mode 100644 index 0000000..d44bde4 --- /dev/null +++ b/lyra-game/src/render/compute_pipeline.rs @@ -0,0 +1,35 @@ +use std::{collections::HashMap, ops::Deref}; + +use wgpu::PipelineLayout; + +pub struct ComputePipeline { + layout: PipelineLayout, + wgpu_pipeline: wgpu::ComputePipeline, +} + +impl Deref for ComputePipeline { + type Target = wgpu::ComputePipeline; + + fn deref(&self) -> &Self::Target { + &self.wgpu_pipeline + } +} + +impl ComputePipeline { + pub fn new(layout: PipelineLayout, pipeline: wgpu::ComputePipeline) -> Self { + Self { + layout, + wgpu_pipeline: pipeline, + } + } + + #[inline(always)] + pub fn layout(&self) -> &PipelineLayout { + &self.layout + } + + #[inline(always)] + pub fn wgpu_pipeline(&self) -> &wgpu::ComputePipeline { + &self.wgpu_pipeline + } +} \ No newline at end of file diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index c6b824c..fd2c072 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -6,12 +6,16 @@ pub use pass::*; mod passes; pub use passes::*; +mod slot_desc; +pub use slot_desc::*; + mod execution_path; use rustc_hash::FxHashMap; -use wgpu::RenderPass; +use tracing::debug; +use wgpu::{util::DeviceExt, RenderPass}; -use super::renderer::{BasicRenderer, Renderer}; +use super::{compute_pipeline::ComputePipeline, pipeline::Pipeline, render_pipeline::RenderPipeline, renderer::{BasicRenderer, Renderer}}; struct PassEntry { inner: Box, @@ -19,22 +23,54 @@ struct PassEntry { } struct ResourcedSlot { - slot: RenderPassSlot, + name: String, + //slot: RenderPassSlot, + ty: SlotType, value: SlotValue, - bindgroup: wgpu::BindGroup, + // will when the bind group is created + /// The id of the bind group for this slot. Becomes `Some` when the render graph creates the + /// bind group + bind_group_id: Option, + create_desc: Option, +} + +/// Stores the pipeline and other resources it uses. +/// +/// This stores the bind groups that have been created for it +pub struct PipelineResource { + pub pipeline: Pipeline, + /// Lookup map for bind groups using names + pub bg_layout_name_lookup: HashMap, } -#[derive(Default)] pub struct RenderGraph { slots: FxHashMap, slot_names: HashMap, + // slots with same name + slot_mutlikey: FxHashMap, passes: FxHashMap, + // TODO: Use a SlotMap + bind_groups: FxHashMap, + // TODO: make pipelines a `type` parameter in RenderPasses, + // then the pipelines can be retrieved via TypeId to the pass. + pipelines: HashMap, current_id: u64, + + pub(crate) surface_config: wgpu::SurfaceConfiguration, } impl RenderGraph { - pub fn new() -> Self { - Self::default() + pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self { + Self { + slots: Default::default(), + slot_names: Default::default(), + slot_mutlikey: Default::default(), + passes: Default::default(), + bind_groups: Default::default(), + pipelines: Default::default(), + current_id: 0, + surface_config, + } } pub fn slot_id(&self, name: &str) -> Option { @@ -46,13 +82,32 @@ impl RenderGraph { .map(|s| &s.desc) } - pub fn bindgroup(&self, id: u64) -> Option<&wgpu::BindGroup> { - self.slots.get(&id) - .map(|s| &s.bindgroup) - } - pub fn add_pass(&mut self, pass: P) { - let desc = pass.desc(&mut self.current_id); + let mut desc = pass.desc(self, &mut self.current_id); + + for slot in &mut desc.slots { + if let Some((id, other)) = self.slot_names.get(&slot.name) + .and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) + { + slot.id = *id; + + if slot.desc.is_some() && other.create_desc.is_none() { + other.create_desc = slot.desc; + } + } else { + let res_slot = ResourcedSlot { + name: slot.name, + ty: slot.ty, + value: SlotValue::None, + bind_group_id: None, + create_desc: slot.desc, + }; + + self.slots.insert(slot.id, res_slot); + self.slot_names.insert(slot.name, slot.id); + } + } + self.passes.insert(desc.id, PassEntry { inner: Box::new(pass), desc, @@ -60,7 +115,39 @@ impl RenderGraph { } /// Creates all buffers required for the passes, also creates an internal execution path. - pub fn setup(&mut self) { + pub fn setup(&mut self, device: &wgpu::Device) { + + for slot in self.slots.values_mut() { + if slot.bind_group_id.is_none() { + match slot.create_desc { + Some(SlotDescriptor::BufferInit(bi)) => { + let label = format!("B_{}", slot.name); + let wb = bi.as_wgpu(Some(&label)); + + let buf = device.create_buffer_init(&wb); + slot.value = SlotValue::Buffer(buf); + + debug!(slot=slot.name, "Created and initialized buffer for slot"); + }, + Some(SlotDescriptor::Buffer(b)) => { + let label = format!("B_{}", slot.name); + let wb = b.as_wgpu(Some(&label)); + + let buf = device.create_buffer(&wb); + slot.value = SlotValue::Buffer(buf); + + debug!(slot=slot.name, "Created buffer"); + } + //Some(SlotDescriptor::Sampler(b)) => {}, + //Some(SlotDescriptor::Texture(b)) => {}, + //Some(SlotDescriptor::TextureView(b)) => {}, + Some(SlotDescriptor::None) => {}, + None => {}, + _ => todo!(), + } + } + } + todo!() } @@ -71,13 +158,102 @@ impl RenderGraph { pub fn render(&mut self, renderer: &mut BasicRenderer) { todo!() } + + /// Get a pipeline by name from the graph. + /// + /// # Panics + /// Panics if the pipeline was not found by name. + #[inline(always)] + pub fn pipeline(&self, name: &str) -> &Pipeline { + &self.pipelines.get(name) + .unwrap() + .pipeline + } + + /// Attempt to get a pipeline by name from the graph. + /// + /// Returns `None` if the pipeline was not found by name, + #[inline(always)] + pub fn try_pipeline(&self, name: &str) -> Option<&Pipeline> { + self.pipelines.get(name) + .map(|p| &p.pipeline) + } + + /// Get a [`RenderPipeline`] by name from the graph. + /// + /// # Panics + /// Panics if the pipeline was not found by name, or if the pipeline is not a render pipeline. + #[inline(always)] + pub fn render_pipeline(&self, name: &str) -> &RenderPipeline { + self.pipelines.get(name) + .unwrap() + .pipeline + .as_render() + } + + /// Attempt to get a [`RenderPipeline`] by name from the graph. + /// + /// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline. + #[inline(always)] + pub fn try_render_pipeline(&self, name: &str) -> Option<&RenderPipeline> { + self.pipelines.get(name) + .and_then(|p| p.pipeline.try_as_render()) + } + + /// Get a [`ComputePipeline`] by name from the graph. + /// + /// # Panics + /// Panics if the pipeline was not found by name, or if the pipeline is not a compute pipeline. + #[inline(always)] + pub fn compute_pipeline(&self, name: &str) -> &ComputePipeline { + &self.pipelines.get(name) + .unwrap() + .pipeline + .as_compute() + } + + /// Attempt to get a [`ComputePipeline`] by name from the graph. + /// + /// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline. + #[inline(always)] + pub fn try_compute_pipeline(&self, name: &str) -> Option<&ComputePipeline> { + self.pipelines.get(name) + .and_then(|p| p.pipeline.try_as_compute()) + } + + #[inline(always)] + pub fn try_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { + self.bind_groups.get(&id) + } + + #[inline(always)] + pub fn bind_group(&self, id: u64) -> &wgpu::BindGroup { + self.try_bind_group(id) + .expect("Unknown id for bind group") + } + + #[inline(always)] + pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { + self.slots.get(&id) + .and_then(|s| self.bind_groups.get(&s.bind_group_id.expect("Slot bind group has not been created yet"))) + } + + #[inline(always)] + pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup { + let bg_id = self.slots.get(&id) + .expect("unknown slot id") + .bind_group_id + .expect("Slot bind group has not been created yet"); + self.bind_group(id) + } } -pub struct RenderGraphContext { - +pub struct RenderGraphContext<'a> { + encoder: wgpu::CommandEncoder, + queue: &'a wgpu::Queue, } -impl RenderGraphContext { +impl<'a> RenderGraphContext<'a> { pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass { todo!() } @@ -85,30 +261,4 @@ impl RenderGraphContext { pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass { todo!() } -} - -#[cfg(test)] -mod tests { - use winit::dpi::PhysicalSize; - - use super::{execution_path::GraphExecutionPath, BasePass, LightCullComputePass, RenderGraph}; - - #[test] - fn full_test() { - let mut graph = RenderGraph::new(); - - graph.add_pass(BasePass::new()); - graph.add_pass(LightCullComputePass::new(PhysicalSize::new(800, 600))); - - let descs = graph.passes.values().map(|pass| &pass.desc).collect(); - let mut path = GraphExecutionPath::new(descs); - - println!("Pass execution order:"); - let mut num = 1; - while let Some(pass_id) = path.queue.pop_front() { - let pass = graph.pass(pass_id).unwrap(); - println!(" {}: {}", num, pass.name); - num += 1; - } - } } \ No newline at end of file diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 7809d11..6fbd328 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use lyra_ecs::World; -use super::{RenderGraph, RenderGraphContext}; +use super::{BufferDescriptor, BufferInitDescriptor, RenderGraph, RenderGraphContext, SamplerDescriptor, TextureDescriptor, TextureViewDescriptor}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RenderPassType { @@ -15,16 +15,31 @@ pub enum RenderPassType { pub enum SlotType { TextureView, Sampler, + Texture, Buffer, } #[derive(Debug)] pub enum SlotValue { + None, TextureView(wgpu::TextureView), Sampler(wgpu::Sampler), + Texture(wgpu::Texture), Buffer(wgpu::Buffer), } +#[derive(Clone, Debug)] +pub enum SlotDescriptor { + /// Most likely this slot is an input, so it doesn't need to specify the descriptor. + None, + + TextureView(TextureViewDescriptor), + Sampler(SamplerDescriptor), + Texture(TextureDescriptor), + Buffer(BufferDescriptor), + BufferInit(BufferInitDescriptor), +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SlotAttribute { Input, @@ -37,8 +52,9 @@ pub struct RenderPassSlot { pub attribute: SlotAttribute, pub id: u64, pub name: String, - - // buffer desc, texture desc + /// The descriptor of the slot value. + /// This will be `None` if this slot is an input. + pub desc: Option, } #[derive(Clone)] @@ -67,34 +83,61 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) { + pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { + debug_assert!(matches!(desc, Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), + "slot descriptor does not match the type of slot"); + let slot = RenderPassSlot { id, name: name.to_string(), ty: SlotType::Buffer, attribute, + desc, }; self.add_slot(slot); } #[inline(always)] - pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) { + pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { + debug_assert!(matches!(desc, Some(SlotDescriptor::Texture(_))), + "slot descriptor does not match the type of slot"); + + let slot = RenderPassSlot { + id, + name: name.to_string(), + ty: SlotType::Texture, + attribute, + desc, + }; + self.add_slot(slot); + } + + #[inline(always)] + pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { + debug_assert!(matches!(desc, Some(SlotDescriptor::TextureView(_))), + "slot descriptor does not match the type of slot"); + let slot = RenderPassSlot { id, name: name.to_string(), ty: SlotType::TextureView, attribute, + desc, }; self.add_slot(slot); } #[inline(always)] - pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) { + pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { + debug_assert!(matches!(desc, Some(SlotDescriptor::Sampler(_))), + "slot descriptor does not match the type of slot"); + let slot = RenderPassSlot { id, name: name.to_string(), ty: SlotType::Sampler, attribute, + desc, }; self.add_slot(slot); } @@ -118,7 +161,7 @@ pub trait RenderGraphPass: 'static { /// Create a render pass describer. /// /// The `id` argument is passed as mutable so you can increment it as you use it for new slots. - fn desc(&self, id: &mut u64) -> RenderGraphPassDesc; + fn desc(&self, graph: &mut RenderGraph, id: &mut u64) -> RenderGraphPassDesc; fn prepare(&mut self, world: &mut World); fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext); diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index a51be89..feaee62 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -1,4 +1,6 @@ -use crate::render::graph::{RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute}; +use glam::UVec2; + +use crate::render::{camera::CameraUniform, graph::{BufferInitDescriptor, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, SlotDescriptor}}; /// Supplies some basic things other passes needs. /// @@ -13,13 +15,21 @@ impl BasePass { } impl RenderGraphPass for BasePass { - fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc { + fn desc(&self, graph: &mut crate::render::graph::RenderGraph, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc { let mut desc = RenderGraphPassDesc::new(*id, "BasePass", RenderPassType::Compute); *id += 1; - desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output); + desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + label: Some("B_ScreenSize".to_string()), + contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }))); *id += 1; - desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output); + desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + label: Some("B_Camera".to_string()), + contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }))); *id += 1; desc diff --git a/lyra-game/src/render/graph/passes/depth_prepass.rs b/lyra-game/src/render/graph/passes/depth_prepass.rs new file mode 100644 index 0000000..85f654c --- /dev/null +++ b/lyra-game/src/render/graph/passes/depth_prepass.rs @@ -0,0 +1,94 @@ +use glam::UVec2; + +use crate::render::{ + camera::CameraUniform, + graph::{ + BufferInitDescriptor, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SamplerDescriptor, SlotAttribute, SlotDescriptor, TextureDescriptor, TextureViewDescriptor + }, +}; + +/// Supplies some basic things other passes needs. +/// +/// screen size buffer, camera buffer, +#[derive(Default)] +pub struct DepthPrePass; + +impl DepthPrePass { + pub fn new() -> Self { + Self::default() + } +} + +impl RenderGraphPass for DepthPrePass { + fn desc( + &self, + graph: &mut crate::render::graph::RenderGraph, + id: &mut u64, + ) -> crate::render::graph::RenderGraphPassDesc { + let mut desc = RenderGraphPassDesc::new(*id, "DepthPrePass", RenderPassType::Compute); + *id += 1; + + let size = wgpu::Extent3d { + width: graph.surface_config.width, + height: graph.surface_config.height, + depth_or_array_layers: 1, + }; + + desc.add_texture_slot( + *id, + "depth_texture", + SlotAttribute::Output, + Some(SlotDescriptor::Texture(TextureDescriptor { + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING, + view_formats: vec![], + })), + ); + *id += 1; + + desc.add_texture_view_slot( + *id, + "depth_texture_view", + SlotAttribute::Output, + Some(SlotDescriptor::TextureView(TextureViewDescriptor::default_view("depth_texture"))), + ); + *id += 1; + + desc.add_texture_view_slot( + *id, + "depth_texture_sampler", + SlotAttribute::Output, + Some(SlotDescriptor::Sampler(SamplerDescriptor { + mag_filter: wgpu::FilterMode::Linear, + min_filter: wgpu::FilterMode::Linear, + compare: Some(wgpu::CompareFunction::LessEqual), + lod_min_clamp: 0.0, + lod_max_clamp: 100.0, + ..SamplerDescriptor::default_sampler("depth_texture") + })), + ); + *id += 1; + + desc + } + + fn prepare(&mut self, world: &mut lyra_ecs::World) { + let _ = world; + todo!() + } + + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + desc: &crate::render::graph::RenderGraphPassDesc, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let _ = (graph, desc, context); + todo!() + } +} diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index 43318ff..8d8ddb0 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -1,6 +1,11 @@ +use std::mem; + use lyra_ecs::World; -use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute}; +use crate::render::graph::{ + BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, + SlotAttribute, SlotDescriptor, TextureDescriptor, TextureViewDescriptor, +}; pub struct LightCullComputePass { workgroup_size: glam::UVec2, @@ -9,28 +14,78 @@ pub struct LightCullComputePass { impl LightCullComputePass { pub fn new(screen_size: winit::dpi::PhysicalSize) -> Self { Self { - workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height) + workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height), } } } impl RenderGraphPass for LightCullComputePass { - fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc { + fn desc( + &self, + graph: &mut crate::render::graph::RenderGraph, + id: &mut u64, + ) -> crate::render::graph::RenderGraphPassDesc { let mut desc = RenderGraphPassDesc::new(*id, "LightCullCompute", RenderPassType::Compute); *id += 1; - desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input); + desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input, None); *id += 1; - desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input); + desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input, None); *id += 1; - - desc.add_buffer_slot(*id, "light_indices", SlotAttribute::Output); + + let mut contents = Vec::::new(); + let contents_len = + self.workgroup_size.x * self.workgroup_size.y * 200 * mem::size_of::() as u32; + contents.resize(contents_len as _, 0); + desc.add_buffer_slot( + *id, + "light_indices", + SlotAttribute::Output, + Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + label: Some("B_LightIndices".to_string()), + contents, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + })), + ); *id += 1; - /* desc.add_buffer_slot(*id, "indices_buffer", SlotAttribute::Output); - *id += 1; */ - desc.add_texture_view_slot(*id, "grid_texture", SlotAttribute::Output); + + let size = wgpu::Extent3d { + width: self.workgroup_size.x, + height: self.workgroup_size.y, + depth_or_array_layers: 1, + }; + desc.add_texture_slot( + *id, + "lightgrid_texture", + SlotAttribute::Output, + Some(SlotDescriptor::Texture(TextureDescriptor { + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg32Uint, // vec2 + usage: wgpu::TextureUsages::STORAGE_BINDING, + view_formats: vec![], + })), + ); + *id += 1; + + desc.add_texture_view_slot( + *id, + "lightgrid_texture_view", + SlotAttribute::Output, + Some(SlotDescriptor::TextureView(TextureViewDescriptor { + texture_label: "lightgrid_texture".to_string(), + format: Some(wgpu::TextureFormat::Rg32Uint), // vec2 + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + })), + ); *id += 1; - desc } @@ -40,18 +95,44 @@ impl RenderGraphPass for LightCullComputePass { todo!() } - fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _: &crate::render::graph::RenderGraphPassDesc, context: &mut RenderGraphContext) { + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + _: &crate::render::graph::RenderGraphPassDesc, + context: &mut RenderGraphContext, + ) { let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("Pass_lightCull") + label: Some("Pass_lightCull"), }); - let depth_tex = graph.bindgroup(graph.slot_id("depth_texture").expect("Could not find depth texture slot")).unwrap(); - let camera_bg = graph.bindgroup(graph.slot_id("camera_buffer").expect("Could not find camera buffers")).unwrap(); - let screen_size_bg = graph.bindgroup(graph.slot_id("screen_size_buffer").expect("Could not find screen size buffer slot")).unwrap(); - let indices_bg = graph.bindgroup(graph.slot_id("light_indices").expect("Could not find light index buffer slot")).unwrap(); - let light_grid_bg = graph.bindgroup(graph.slot_id("grid_texture").expect("Could not find light grid buffer slot")).unwrap(); + let pipeline = graph.compute_pipeline("main"); + pass.set_pipeline(pipeline); - //pass.set_pipeline(pipeline) + let depth_tex = graph.slot_bind_group( + graph + .slot_id("depth_texture") + .expect("Could not find depth texture slot"), + ); + let camera_bg = graph.slot_bind_group( + graph + .slot_id("camera_buffer") + .expect("Could not find camera buffers"), + ); + let screen_size_bg = graph.slot_bind_group( + graph + .slot_id("screen_size_buffer") + .expect("Could not find screen size buffer slot"), + ); + let indices_bg = graph.slot_bind_group( + graph + .slot_id("light_indices") + .expect("Could not find light index buffer slot"), + ); + let light_grid_bg = graph.slot_bind_group( + graph + .slot_id("grid_texture") + .expect("Could not find light grid buffer slot"), + ); pass.set_bind_group(0, depth_tex, &[]); pass.set_bind_group(1, camera_bg, &[]); @@ -60,7 +141,5 @@ impl RenderGraphPass for LightCullComputePass { pass.set_bind_group(4, screen_size_bg, &[]); pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1); - - } -} \ No newline at end of file +} diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 0871d73..5309f48 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -2,4 +2,7 @@ mod light_cull_compute; pub use light_cull_compute::*; mod base; -pub use base::*; \ No newline at end of file +pub use base::*; + +mod depth_prepass; +pub use depth_prepass::*; \ No newline at end of file diff --git a/lyra-game/src/render/graph/slot_desc.rs b/lyra-game/src/render/graph/slot_desc.rs new file mode 100644 index 0000000..0022c1f --- /dev/null +++ b/lyra-game/src/render/graph/slot_desc.rs @@ -0,0 +1,166 @@ +use std::num::{NonZeroU32, NonZeroU8}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TextureViewDescriptor { + /// The label of the texture that this view will be created from. + pub texture_label: String, + /// Format of the texture view. At this time, it must be the same as the underlying format of the texture. + pub format: Option, + /// The dimension of the texture view. For 1D textures, this must be `D1`. For 2D textures it must be one of + /// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `D3` + pub dimension: Option, + /// Aspect of the texture. Color textures must be [`TextureAspect::All`]. + pub aspect: wgpu::TextureAspect, + /// Base mip level. + pub base_mip_level: u32, + /// Mip level count. + /// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count. + /// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total. + pub mip_level_count: Option, + /// Base array layer. + pub base_array_layer: u32, + /// Layer count. + /// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count. + /// If `None`, considered to include the rest of the array layers, but at least 1 in total. + pub array_layer_count: Option, +} + +impl TextureViewDescriptor { + pub fn default_view(texture_label: &str) -> Self { + let d = wgpu::TextureViewDescriptor::default(); + + Self { + texture_label: texture_label.to_string(), + format: d.format, + dimension: d.dimension, + aspect: d.aspect, + base_array_layer: d.base_array_layer, + base_mip_level: d.base_mip_level, + mip_level_count: d.mip_level_count, + array_layer_count: d.array_layer_count, + } + } +} + + +#[derive(Clone, Debug, PartialEq)] +pub struct SamplerDescriptor { + /// The label of the texture that this view will be created from. + pub texture_label: String, + /// How to deal with out of bounds accesses in the u (i.e. x) direction + pub address_mode_u: wgpu::AddressMode, + /// How to deal with out of bounds accesses in the v (i.e. y) direction + pub address_mode_v: wgpu::AddressMode, + /// How to deal with out of bounds accesses in the w (i.e. z) direction + pub address_mode_w: wgpu::AddressMode, + /// How to filter the texture when it needs to be magnified (made larger) + pub mag_filter: wgpu::FilterMode, + /// How to filter the texture when it needs to be minified (made smaller) + pub min_filter: wgpu::FilterMode, + /// How to filter between mip map levels + pub mipmap_filter: wgpu::FilterMode, + /// Minimum level of detail (i.e. mip level) to use + pub lod_min_clamp: f32, + /// Maximum level of detail (i.e. mip level) to use + pub lod_max_clamp: f32, + /// If this is enabled, this is a comparison sampler using the given comparison function. + pub compare: Option, + /// Valid values: 1, 2, 4, 8, and 16. + pub anisotropy_clamp: Option, + /// Border color to use when address_mode is [`AddressMode::ClampToBorder`] + pub border_color: Option, +} + +impl SamplerDescriptor { + pub fn default_sampler(texture_label: &str) -> Self { + let d = wgpu::SamplerDescriptor::default(); + + Self { + texture_label: texture_label.to_string(), + address_mode_u: d.address_mode_u, + address_mode_v: d.address_mode_v, + address_mode_w: d.address_mode_w, + mag_filter: d.mag_filter, + min_filter: d.min_filter, + mipmap_filter: d.mipmap_filter, + lod_min_clamp: d.lod_min_clamp, + lod_max_clamp: d.lod_max_clamp, + compare: d.compare, + anisotropy_clamp: d.anisotropy_clamp, + border_color: d.border_color, + } + } +} + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TextureDescriptor { + /// Size of the texture. All components must be greater than zero. For a + /// regular 1D/2D texture, the unused sizes will be 1. For 2DArray textures, + /// Z is the number of 2D textures in that array. + pub size: wgpu::Extent3d, + /// Mip count of texture. For a texture with no extra mips, this must be 1. + pub mip_level_count: u32, + /// Sample count of texture. If this is not 1, texture must have [`BindingType::Texture::multisampled`] set to true. + pub sample_count: u32, + /// Dimensions of the texture. + pub dimension: wgpu::TextureDimension, + /// Format of the texture. + pub format: wgpu::TextureFormat, + /// Allowed usages of the texture. If used in other ways, the operation will panic. + pub usage: wgpu::TextureUsages, + /// Specifies what view formats will be allowed when calling create_view() on this texture. + /// + /// View formats of the same format as the texture are always allowed. + /// + /// Note: currently, only the srgb-ness is allowed to change. (ex: Rgba8Unorm texture + Rgba8UnormSrgb view) + pub view_formats: Vec, +} + +#[repr(C)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct BufferDescriptor { + /// Size of a buffer. + pub size: wgpu::BufferAddress, + /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation + /// will panic. + pub usage: wgpu::BufferUsages, + /// Allows a buffer to be mapped immediately after they are made. It does not have to be [`BufferUsages::MAP_READ`] or + /// [`BufferUsages::MAP_WRITE`], all buffers are allowed to be mapped at creation. + /// + /// If this is `true`, [`size`](#structfield.size) must be a multiple of + /// [`COPY_BUFFER_ALIGNMENT`]. + pub mapped_at_creation: bool, +} + +impl BufferDescriptor { + pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::BufferDescriptor { + wgpu::BufferDescriptor { + label, + size: self.size, + usage: self.usage, + mapped_at_creation: self.mapped_at_creation, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct BufferInitDescriptor { + /// Debug label of a buffer. This will show up in graphics debuggers for easy identification. + pub label: Option, + /// Contents of a buffer on creation. + pub contents: Vec, + /// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation + /// will panic. + pub usage: wgpu::BufferUsages, +} + +impl BufferInitDescriptor { + pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::util::BufferInitDescriptor { + wgpu::util::BufferInitDescriptor { + label, + contents: &self.contents, + usage: self.usage, + } + } +} \ No newline at end of file diff --git a/lyra-game/src/render/mod.rs b/lyra-game/src/render/mod.rs index 2d4c650..62e587e 100755 --- a/lyra-game/src/render/mod.rs +++ b/lyra-game/src/render/mod.rs @@ -1,5 +1,7 @@ pub mod renderer; pub mod render_pipeline; +pub mod compute_pipeline; +pub mod pipeline; pub mod vertex; pub mod desc_buf_lay; pub mod render_buffer; diff --git a/lyra-game/src/render/pipeline.rs b/lyra-game/src/render/pipeline.rs new file mode 100644 index 0000000..db23149 --- /dev/null +++ b/lyra-game/src/render/pipeline.rs @@ -0,0 +1,65 @@ +use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline}; + +pub enum Pipeline { + Render(RenderPipeline), + Compute(ComputePipeline), +} + +impl Into for Pipeline { + fn into(self) -> RenderPipeline { + match self { + Self::Render(r) => r, + _ => panic!("Pipeline is not a RenderPipeline"), + } + } +} + +impl Into for Pipeline { + fn into(self) -> ComputePipeline { + match self { + Self::Compute(c) => c, + _ => panic!("Pipeline is not a RenderPipeline"), + } + } +} + +impl Pipeline { + pub fn bind_group_layout(&self, index: u32) -> wgpu::BindGroupLayout { + match self { + Pipeline::Render(r) => r.get_bind_group_layout(index), + Pipeline::Compute(c) => c.get_bind_group_layout(index), + } + } + + /// Gets self as a render pipeline, panics if self is not a render pipeline. + pub fn as_render(&self) -> &RenderPipeline { + match self { + Self::Render(r) => r, + _ => panic!("Pipeline is not a RenderPipeline") + } + } + + /// Gets self as a render pipeline, returns `None` if self is not a render pipeline. + pub fn try_as_render(&self) -> Option<&RenderPipeline> { + match self { + Self::Render(r) => Some(r), + _ => None, + } + } + + /// Gets self as a compute pipeline, panics if self is not a compute pipeline. + pub fn as_compute(&self) -> &ComputePipeline { + match self { + Self::Compute(r) => r, + _ => panic!("Pipeline is not a ComputePipeline") + } + } + + /// Gets self as a compute pipeline, returns `None` if self is not a compute pipeline. + pub fn try_as_compute(&self) -> Option<&ComputePipeline> { + match self { + Self::Compute(c) => Some(c), + _ => None, + } + } +} \ No newline at end of file diff --git a/lyra-game/src/render/render_pipeline.rs b/lyra-game/src/render/render_pipeline.rs index cf073e9..19a1ecc 100755 --- a/lyra-game/src/render/render_pipeline.rs +++ b/lyra-game/src/render/render_pipeline.rs @@ -1,26 +1,25 @@ -use wgpu::{PipelineLayout, RenderPipeline, VertexBufferLayout, BindGroupLayout}; +use std::{collections::HashMap, ops::Deref}; + +use wgpu::{PipelineLayout, VertexBufferLayout, BindGroupLayout}; use super::texture::RenderTexture; -pub struct FullRenderPipeline { +pub struct RenderPipeline { layout: PipelineLayout, - wgpu_pipeline: RenderPipeline, + wgpu_pipeline: wgpu::RenderPipeline, } -impl FullRenderPipeline { - pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec, bind_group_layouts: Vec<&BindGroupLayout>) -> FullRenderPipeline { - // Extract the layouts from all the jobs - /* let mut buffer_layouts = vec![]; - let mut bind_group_layouts = vec![]; - for job in jobs.iter() { - // Push layout for the vertex buffer, index buffer doesn't need one - buffer_layouts.push(Vertex::desc()); +impl Deref for RenderPipeline { + type Target = wgpu::RenderPipeline; - if let Some(layout) = job.mesh().texture_layout.as_ref() { - bind_group_layouts.push(layout); - } - } */ + fn deref(&self) -> &Self::Target { + &self.wgpu_pipeline + } +} +impl RenderPipeline { + /// Creates the default render pipeline + pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec, bind_group_layouts: Vec<&BindGroupLayout>) -> RenderPipeline { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), @@ -78,13 +77,13 @@ impl FullRenderPipeline { } } - #[allow(dead_code)] - pub fn get_layout(&self) -> &PipelineLayout { + #[inline(always)] + pub fn layout(&self) -> &PipelineLayout { &self.layout } - #[allow(dead_code)] - pub fn get_wgpu_pipeline(&self) -> &RenderPipeline { + #[inline(always)] + pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline { &self.wgpu_pipeline } } \ No newline at end of file diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index cd7f127..75e7f0f 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -18,6 +18,7 @@ use wgpu::util::DeviceExt; use winit::window::Window; use crate::math::Transform; +use crate::render::graph::{BasePass, DepthPrePass, LightCullComputePass}; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; use crate::scene::CameraComponent; @@ -25,6 +26,7 @@ use crate::DeltaTime; use super::camera::{RenderCamera, CameraUniform}; use super::desc_buf_lay::DescVertexBufferLayout; +use super::graph::RenderGraph; use super::light::LightUniformBuffers; use super::light_cull_compute::LightCullCompute; use super::material::Material; @@ -32,20 +34,23 @@ use super::render_buffer::BufferWrapper; use super::texture::RenderTexture; use super::transform_buffer_storage::{TransformBuffers, TransformGroup}; use super::vertex::Vertex; -use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; +use super::{render_pipeline::RenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; use lyra_resource::{gltf::Mesh, ResHandle}; type MeshHandle = ResHandle; type SceneHandle = ResHandle; +#[derive(Clone, Copy, Debug)] +pub struct ScreenSize(glam::UVec2); + pub trait Renderer { fn prepare(&mut self, main_world: &mut World); fn render(&mut self) -> Result<(), wgpu::SurfaceError>; - fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize); + fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize); fn surface_size(&self) -> winit::dpi::PhysicalSize; - fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc); + fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc); } pub trait RenderPass { @@ -83,36 +88,35 @@ pub struct BasicRenderer { pub clear_color: wgpu::Color, - pub render_pipelines: rustc_hash::FxHashMap>, + pub render_pipelines: rustc_hash::FxHashMap>, pub render_jobs: VecDeque, - mesh_buffers: rustc_hash::FxHashMap, // TODO: clean up left over buffers from deleted entities/components - material_buffers: rustc_hash::FxHashMap>, - entity_meshes: rustc_hash::FxHashMap, + //mesh_buffers: rustc_hash::FxHashMap, // TODO: clean up left over buffers from deleted entities/components + //material_buffers: rustc_hash::FxHashMap>, + //entity_meshes: rustc_hash::FxHashMap, - transform_buffers: TransformBuffers, + //transform_buffers: TransformBuffers, render_limits: Limits, - inuse_camera: RenderCamera, - camera_buffer: BufferWrapper, - //camera_bind_group: wgpu::BindGroup, + //inuse_camera: RenderCamera, + //camera_buffer: BufferWrapper, - bgl_texture: Rc, - default_texture: RenderTexture, - depth_buffer_texture: RenderTexture, + //bgl_texture: Rc, + //default_texture: RenderTexture, + //depth_buffer_texture: RenderTexture, + //material_buffer: BufferWrapper, + //light_buffers: LightUniformBuffers, + //light_cull_compute: LightCullCompute, - material_buffer: BufferWrapper, - - light_buffers: LightUniformBuffers, - - light_cull_compute: LightCullCompute, + graph: RenderGraph, } impl BasicRenderer { - #[instrument(skip(window))] - pub async fn create_with_window(window: Arc) -> BasicRenderer { + #[instrument(skip(world, window))] + pub async fn create_with_window(world: &mut World, window: Arc) -> BasicRenderer { let size = window.inner_size(); + world.add_resource(ScreenSize(glam::UVec2::new(size.width, size.height))); // Get a GPU handle let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { @@ -205,7 +209,13 @@ impl BasicRenderer { let device = Rc::new(device); let queue = Rc::new(queue); - let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); + //let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); + + let mut g = RenderGraph::new(config.clone()); + g.add_pass(BasePass::new()); + g.add_pass(DepthPrePass::new()); + g.add_pass(LightCullComputePass::new(size)); + g.setup(&device); let mut s = Self { window, @@ -222,28 +232,14 @@ impl BasicRenderer { }, render_pipelines: Default::default(), render_jobs: Default::default(), - mesh_buffers: Default::default(), - material_buffers: Default::default(), - entity_meshes: Default::default(), render_limits, - transform_buffers, - - inuse_camera: RenderCamera::new(size), - camera_buffer, - - bgl_texture, - default_texture, - depth_buffer_texture: depth_texture, - - light_buffers: light_uniform_buffers, - material_buffer: mat_buffer, - light_cull_compute, + graph: g, }; // create the default pipelines - let mut pipelines = rustc_hash::FxHashMap::default(); - pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader, + /* let mut pipelines = rustc_hash::FxHashMap::default(); + pipelines.insert(0, Arc::new(RenderPipeline::new(&s.device, &s.config, &shader, vec![super::vertex::Vertex::desc(),], vec![&s.bgl_texture, &s.transform_buffers.bindgroup_layout, s.camera_buffer.bindgroup_layout().unwrap(), @@ -251,401 +247,27 @@ impl BasicRenderer { &s.bgl_texture, &s.light_cull_compute.light_indices_grid.bg_pair.layout, ]))); - s.render_pipelines = pipelines; + s.render_pipelines = pipelines; */ s } - - /// Checks if the mesh buffers in the GPU need to be updated. - #[instrument(skip(self, _entity, meshh))] - fn check_mesh_buffers(&mut self, _entity: Entity, meshh: &ResHandle) { - let mesh_uuid = meshh.uuid(); - - if let (Some(mesh), Some(buffers)) = (meshh.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) { - // check if the buffer sizes dont match. If they dont, completely remake the buffers - let vertices = mesh.position().unwrap(); - if buffers.buffer_vertex.count() != vertices.len() { - debug!("Recreating buffers for mesh {}", mesh_uuid.to_string()); - let (vert, idx) = self.create_vertex_index_buffers(&mesh); - - // have to re-get buffers because of borrow checker - let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap(); - buffers.buffer_indices = idx; - buffers.buffer_vertex = vert; - - return; - } - - // update vertices - let vertex_buffer = buffers.buffer_vertex.buffer(); - let vertices = vertices.as_slice(); - // align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT) - let (_, vertices, _) = bytemuck::pod_align_to::(vertices); - self.queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices)); - - // update the indices if they're given - if let Some(index_buffer) = buffers.buffer_indices.as_ref() { - let aligned_indices = match mesh.indices.as_ref().unwrap() { - // U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size. - lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::(v).1, - lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::(v).1, - }; - - let index_buffer = index_buffer.1.buffer(); - self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices)); - } - } - } - - #[instrument(skip(self, mesh))] - fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) { - let positions = mesh.position().unwrap(); - let tex_coords: Vec = mesh.tex_coords().cloned() - .unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]); - let normals = mesh.normals().unwrap(); - - assert!(positions.len() == tex_coords.len() && positions.len() == normals.len()); - - let mut vertex_inputs = vec![]; - for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) { - vertex_inputs.push(Vertex::new(*v, *t, *n)); - } - - let vertex_buffer = self.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(), - usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST, - } - ); - let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len()); - - let indices = match mesh.indices.as_ref() { - Some(indices) => { - let (idx_type, len, contents) = match indices { - lyra_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)), - lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)), - }; - - let index_buffer = self.device.create_buffer_init( - &wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents, - usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST, - } - ); - - let buffer_indices = BufferStorage::new(index_buffer, 0, len); - - Some((idx_type, buffer_indices)) - }, - None => { - None - } - }; - - ( vertex_buffer, indices ) - } - - #[instrument(skip(self, mesh))] - fn create_mesh_buffers(&mut self, mesh: &Mesh) -> MeshBufferStorage { - let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh); - - let material = mesh.material.as_ref() - .expect("Material resource not loaded yet"); - let material_ref = material.data_ref() - .unwrap(); - - let material = self.material_buffers.entry(material.uuid()) - .or_insert_with(|| { - debug!(uuid=material.uuid().to_string(), "Sending material to gpu"); - Rc::new(Material::from_resource(&self.device, &self.queue, self.bgl_texture.clone(), &material_ref)) - }); - - // TODO: support material uniforms from multiple uniforms - let uni = MaterialUniform::from(&**material); - self.queue.write_buffer(&self.material_buffer.inner_buf, 0, bytemuck::bytes_of(&uni)); - - MeshBufferStorage { - buffer_vertex: vertex_buffer, - buffer_indices, - material: Some(material.clone()), - } - } - - /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. - #[instrument(skip(self, transform, mesh, entity))] - fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool { - let _ = transform; - /* if self.transform_buffers.should_expand() { - self.transform_buffers.expand_buffers(&self.device); - } - - self.transform_buffers.update_or_insert(&self.queue, &self.render_limits, - entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); */ - - #[allow(clippy::map_entry)] - if !self.mesh_buffers.contains_key(&mesh_uuid) { - // create the mesh's buffers - let buffers = self.create_mesh_buffers(mesh); - self.mesh_buffers.insert(mesh_uuid, buffers); - self.entity_meshes.insert(entity, mesh_uuid); - - true - } else { false } - } } impl Renderer for BasicRenderer { #[instrument(skip(self, main_world))] fn prepare(&mut self, main_world: &mut World) { - let last_epoch = main_world.current_tick(); - let mut alive_entities = HashSet::new(); - - let view = main_world.view_iter::<( - Entities, - &Transform, - TickOf, - Or< - (&MeshHandle, TickOf), - (&SceneHandle, TickOf) - >, - Option<&mut InterpTransform>, - Res, - )>(); - - // used to store InterpTransform components to add to entities later - let mut component_queue: Vec<(Entity, InterpTransform)> = vec![]; - - for ( - entity, - transform, - _transform_epoch, - ( - mesh_pair, - scene_pair - ), - interp_tran, - delta_time, - ) in view - { - alive_entities.insert(entity); - - let interp_transform = match interp_tran { - Some(mut interp_transform) => { - // found in https://youtu.be/YJB1QnEmlTs?t=472 - interp_transform.alpha = 1.0 - interp_transform.alpha.powf(**delta_time); - - interp_transform.last_transform = interp_transform.last_transform.lerp(*transform, interp_transform.alpha); - interp_transform.last_transform - }, - None => { - let interp = InterpTransform { - last_transform: *transform, - alpha: 0.5, - }; - component_queue.push((entity, interp)); - - *transform - } - }; - - if let Some((mesh_han, mesh_epoch)) = mesh_pair { - if let Some(mesh) = mesh_han.data_ref() { - // if process mesh did not just create a new mesh, and the epoch - // shows that the scene has changed, verify that the mesh buffers - // dont need to be resent to the gpu. - if !self.process_mesh(entity, interp_transform, &*mesh, mesh_han.uuid()) - && mesh_epoch == last_epoch { - self.check_mesh_buffers(entity, &mesh_han); - } - - if self.transform_buffers.needs_expand() { - self.transform_buffers.expand_buffers(&self.device); - } - - let group = TransformGroup::EntityRes(entity, mesh_han.uuid()); - let transform_id = self.transform_buffers.update_or_push(&self.device, &self.queue, &self.render_limits, - group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation)); - - let material = mesh.material.as_ref().unwrap() - .data_ref().unwrap(); - let shader = material.shader_uuid.unwrap_or(0); - let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); - self.render_jobs.push_back(job); - } - } - - if let Some((scene_han, scene_epoch)) = scene_pair { - if let Some(scene) = scene_han.data_ref() { - if scene_epoch == last_epoch { - let view = scene.world().view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); - lyra_scene::system_update_world_transforms(scene.world(), view).unwrap(); - } - - for (mesh_han, pos) in scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() { - if let Some(mesh) = mesh_han.data_ref() { - let mesh_interpo = interp_transform + **pos; - - // if process mesh did not just create a new mesh, and the epoch - // shows that the scene has changed, verify that the mesh buffers - // dont need to be resent to the gpu. - if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid()) - && scene_epoch == last_epoch { - self.check_mesh_buffers(entity, &mesh_han); - } - - if self.transform_buffers.needs_expand() { - self.transform_buffers.expand_buffers(&self.device); - } - - let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); - let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); - let transform_id = self.transform_buffers.update_or_push(&self.device, &self.queue, &self.render_limits, - group, mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) ); - - let material = mesh.material.as_ref().unwrap() - .data_ref().unwrap(); - let shader = material.shader_uuid.unwrap_or(0); - let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); - self.render_jobs.push_back(job); - } - } - } - } - } - - for (en, interp) in component_queue { - main_world.insert(en, interp); - } - - // collect dead entities - self.transform_buffers.send_to_gpu(&self.queue); - - // when buffer storage length does not match the amount of iterated entities, - // remove all dead entities, and their buffers, if they weren't iterated over - if self.mesh_buffers.len() != alive_entities.len() { - let removed_entities: Vec = self.entity_meshes - .extract_if(|e, _| !alive_entities.contains(e)) - .map(|(_, v)| v) - .collect(); - self.mesh_buffers.retain(|u, _| !removed_entities.contains(u)); - } - - // update camera uniform - if let Some(camera) = main_world.view_iter::<&mut CameraComponent>().next() { - let uniform = self.inuse_camera.calc_view_projection(&camera); - self.camera_buffer.write_buffer(&self.queue, 0, &[uniform]); - } else { - warn!("Missing camera!"); - } - - self.light_buffers.update_lights(&self.queue, last_epoch, main_world); + self.graph.prepare(); } #[instrument(skip(self))] fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - let output = self.surface.get_current_texture()?; - let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default()); - - self.light_cull_compute.compute(&self.camera_buffer, &self.light_buffers, &self.depth_buffer_texture); - - let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Basic Renderer's Encoder") - }); - - // Create a new variable scope for the render pass - { - // There's only one render pass currently - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(self.clear_color), - store: true, - }, - })], - // enable depth buffer - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: &self.depth_buffer_texture.view, - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }), - }); - - // Pop off jobs from the queue as they're being processed - while let Some(job) = self.render_jobs.pop_front() { - if let Some(pipeline) = self.render_pipelines.get(&job.shader_id) { - // specify to use this pipeline - render_pass.set_pipeline(pipeline.get_wgpu_pipeline()); - - // get the mesh (containing vertices) and the buffers from storage - let buffers = self.mesh_buffers.get(&job.mesh_uuid); - if buffers.is_none() { - warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid); - continue; - } - let buffers = buffers.unwrap(); - /* let buffers = self.mesh_buffers.get(&job.mesh_uuid) - .expect("missing render job mesh"); */ - - // Bind the optional texture - if let Some(tex) = buffers.material.as_ref() - .and_then(|m| m.diffuse_texture.as_ref()) { - render_pass.set_bind_group(0, tex.bind_group(), &[]); - } else { - render_pass.set_bind_group(0, self.default_texture.bind_group(), &[]); - } - - if let Some(tex) = buffers.material.as_ref() - .and_then(|m| m.specular.as_ref()) - .and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) { - render_pass.set_bind_group(5, tex.bind_group(), &[]); - } else { - render_pass.set_bind_group(5, self.default_texture.bind_group(), &[]); - } - - // Get the bindgroup for job's transform and bind to it using an offset. - let bindgroup = self.transform_buffers.bind_group(job.transform_id); - let offset = self.transform_buffers.buffer_offset(job.transform_id); - render_pass.set_bind_group(1, bindgroup, &[ offset, ]); - - render_pass.set_bind_group(2, &self.camera_buffer.bindgroup(), &[]); - render_pass.set_bind_group(3, &self.light_buffers.bind_group_pair.bindgroup, &[]); - render_pass.set_bind_group(4, &self.material_buffer.bindgroup_pair.as_ref().unwrap().bindgroup, &[]); - - render_pass.set_bind_group(6, &self.light_cull_compute.light_indices_grid.bg_pair.bindgroup, &[]); - - // if this mesh uses indices, use them to draw the mesh - if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() { - let indices_len = indices.count() as u32; - - render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - render_pass.set_index_buffer(indices.buffer().slice(..), *idx_type); - render_pass.draw_indexed(0..indices_len, 0, 0..1); - } else { - let vertex_count = buffers.buffer_vertex.count(); - - render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); - render_pass.draw(0..vertex_count as u32, 0..1); - } - } - } - } - - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); + self.graph.render(self); Ok(()) } - #[instrument(skip(self))] - fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize) { + #[instrument(skip(world, self))] + fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; self.config.width = new_size.width; @@ -653,15 +275,10 @@ impl Renderer for BasicRenderer { // tell other things of updated resize self.surface.configure(&self.device, &self.config); - - let create_bindgroup = self.depth_buffer_texture.bindgroup_pair.is_some(); - self.depth_buffer_texture = RenderTexture::create_depth_texture(&self.device, &self.config, "Depth Buffer Texture"); - if create_bindgroup { - self.depth_buffer_texture.create_bind_group(&self.device); - } - - self.inuse_camera.update_aspect_ratio(self.size); - self.light_cull_compute.update_screen_size(new_size); + + let mut world_ss = world.get_resource::(); + world_ss.0 = glam::UVec2::new(new_size.width, new_size.height); + self.graph.surface_config = self.config.clone(); } } @@ -669,7 +286,7 @@ impl Renderer for BasicRenderer { self.size } - fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc) { + fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc) { self.render_pipelines.insert(shader_id, pipeline); } } \ No newline at end of file From daa6fc3d4b5b45b268f9a0cbaec46d6f1c6e1738 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 8 May 2024 18:25:12 -0400 Subject: [PATCH 03/20] move profiles to root workspace Cargo.toml so they aren't ignored --- Cargo.toml | 9 ++++++++- examples/fixed-timestep-rotating-model/Cargo.toml | 12 +----------- examples/lua-scripting/Cargo.toml | 10 ---------- examples/many-lights/Cargo.toml | 12 +----------- 4 files changed, 10 insertions(+), 33 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8c361d6..cb59c15 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,8 @@ members = [ "examples/many-lights", "examples/fixed-timestep-rotating-model", - "examples/lua-scripting" + "examples/lua-scripting", + "examples/simple_scene" ] [features] @@ -27,3 +28,9 @@ tracy = ["lyra-game/tracy"] [dependencies] lyra-game = { path = "lyra-game" } lyra-scripting = { path = "lyra-scripting", optional = true } + +[profile.dev] +opt-level = 1 + +[profile.release] +debug = true \ No newline at end of file diff --git a/examples/fixed-timestep-rotating-model/Cargo.toml b/examples/fixed-timestep-rotating-model/Cargo.toml index 8d7fa61..e6e20ee 100644 --- a/examples/fixed-timestep-rotating-model/Cargo.toml +++ b/examples/fixed-timestep-rotating-model/Cargo.toml @@ -9,14 +9,4 @@ anyhow = "1.0.75" async-std = "1.12.0" tracing = "0.1.37" rand = "0.8.5" -fps_counter = "3.0.0" - -[target.x86_64-unknown-linux-gnu] -linker = "/usr/bin/clang" -rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"] - -[profile.dev] -opt-level = 1 - -[profile.release] -debug = true \ No newline at end of file +fps_counter = "3.0.0" \ No newline at end of file diff --git a/examples/lua-scripting/Cargo.toml b/examples/lua-scripting/Cargo.toml index 41cedba..1cb7cc3 100644 --- a/examples/lua-scripting/Cargo.toml +++ b/examples/lua-scripting/Cargo.toml @@ -10,13 +10,3 @@ async-std = "1.12.0" tracing = "0.1.37" rand = "0.8.5" fps_counter = "3.0.0" - -[target.x86_64-unknown-linux-gnu] -linker = "/usr/bin/clang" -rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"] - -[profile.dev] -opt-level = 1 - -[profile.release] -debug = true \ No newline at end of file diff --git a/examples/many-lights/Cargo.toml b/examples/many-lights/Cargo.toml index f552f46..cb5b996 100644 --- a/examples/many-lights/Cargo.toml +++ b/examples/many-lights/Cargo.toml @@ -9,14 +9,4 @@ anyhow = "1.0.75" async-std = "1.12.0" tracing = "0.1.37" rand = "0.8.5" -fps_counter = "3.0.0" - -[target.x86_64-unknown-linux-gnu] -linker = "/usr/bin/clang" -rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"] - -[profile.dev] -opt-level = 1 - -[profile.release] -debug = true \ No newline at end of file +fps_counter = "3.0.0" \ No newline at end of file From bccf6287c09d5c770589ee6a5b7253b3f2d7ace3 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 8 May 2024 18:27:10 -0400 Subject: [PATCH 04/20] render: get first image from RenderGraph, just a simple hard coded triangle --- .lapce/run.toml | 29 ++ Cargo.lock | 10 + examples/simple_scene/Cargo.toml | 10 + examples/simple_scene/scripts/test.lua | 59 +++ examples/simple_scene/src/main.rs | 149 ++++++++ lyra-game/src/game.rs | 8 +- lyra-game/src/lib.rs | 1 + lyra-game/src/render/graph/execution_path.rs | 5 +- lyra-game/src/render/graph/mod.rs | 357 ++++++++++++------ lyra-game/src/render/graph/pass.rs | 50 ++- lyra-game/src/render/graph/passes/mod.rs | 10 +- lyra-game/src/render/graph/passes/triangle.rs | 95 +++++ lyra-game/src/render/graph/slot_desc.rs | 32 +- lyra-game/src/render/render_pipeline.rs | 5 +- lyra-game/src/render/renderer.rs | 31 +- lyra-game/src/render/shaders/triangle.wgsl | 19 + 16 files changed, 736 insertions(+), 134 deletions(-) create mode 100644 .lapce/run.toml create mode 100644 examples/simple_scene/Cargo.toml create mode 100644 examples/simple_scene/scripts/test.lua create mode 100644 examples/simple_scene/src/main.rs create mode 100644 lyra-game/src/render/graph/passes/triangle.rs create mode 100644 lyra-game/src/render/shaders/triangle.wgsl diff --git a/.lapce/run.toml b/.lapce/run.toml new file mode 100644 index 0000000..eccecf1 --- /dev/null +++ b/.lapce/run.toml @@ -0,0 +1,29 @@ +# The run config is used for both run mode and debug mode + +[[configs]] +# the name of this task +name = "Example 'simple_scene'" + +# the type of the debugger. If not set, it can't be debugged but can still be run +type = "lldb" + +# the program to run +program = "../../target/debug/simple_scene" + +# the program arguments, e.g. args = ["arg1", "arg2"], optional +# args = [] + +# current working directory, optional +cwd = "${workspace}/examples/simple_scene" + +# environment variables, optional +# [configs.env] +# VAR1 = "VAL1" +# VAR2 = "VAL2" + +# task to run before the run/debug session is started, optional +[configs.prelaunch] +program = "cargo" +args = [ + "build", +] diff --git a/Cargo.lock b/Cargo.lock index eb90419..b1ccf40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3065,6 +3065,16 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "simple_scene" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-std", + "lyra-engine", + "tracing", +] + [[package]] name = "slab" version = "0.4.9" diff --git a/examples/simple_scene/Cargo.toml b/examples/simple_scene/Cargo.toml new file mode 100644 index 0000000..0e157fc --- /dev/null +++ b/examples/simple_scene/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "simple_scene" +version = "0.1.0" +edition = "2021" + +[dependencies] +lyra-engine = { path = "../../", features = ["tracy"] } +anyhow = "1.0.75" +async-std = "1.12.0" +tracing = "0.1.37" \ No newline at end of file diff --git a/examples/simple_scene/scripts/test.lua b/examples/simple_scene/scripts/test.lua new file mode 100644 index 0000000..bac6e90 --- /dev/null +++ b/examples/simple_scene/scripts/test.lua @@ -0,0 +1,59 @@ +---Return the userdata's name from its metatable +---@param val userdata +---@return string +function udname(val) + return getmetatable(val).__name +end + +function on_init() + local cube = world:request_res("../assets/cube-texture-embedded.gltf") + print("Loaded textured cube (" .. udname(cube) .. ")") + + cube:wait_until_loaded() + local scenes = cube:scenes() + local cube_scene = scenes[1] + + local pos = Transform.from_translation(Vec3.new(0, 0, -8.0)) + + local e = world:spawn(pos, cube_scene) + print("spawned entity " .. tostring(e)) +end + +--[[ function on_first() + print("Lua's first function was called") +end + +function on_pre_update() + print("Lua's pre-update function was called") +end ]] + +function on_update() + --[[ ---@type number + local dt = world:resource(DeltaTime) + local act = world:resource(ActionHandler) + ---@type number + local move_objs = act:get_axis("ObjectsMoveUpDown") + + world:view(function (t) + if move_objs ~= nil then + t:translate(0, move_objs * 0.35 * dt, 0) + return t + end + end, Transform) ]] + + ---@type number + local dt = world:resource(DeltaTime) + + world:view(function (t) + t:translate(0, 0.15 * dt, 0) + return t + end, Transform) +end + +--[[ function on_post_update() + print("Lua's post-update function was called") +end + +function on_last() + print("Lua's last function was called") +end ]] \ No newline at end of file diff --git a/examples/simple_scene/src/main.rs b/examples/simple_scene/src/main.rs new file mode 100644 index 0000000..5faefb0 --- /dev/null +++ b/examples/simple_scene/src/main.rs @@ -0,0 +1,149 @@ +use lyra_engine::{ + assets::{gltf::Gltf, ResourceManager}, + game::Game, + input::{ + Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, + InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput, + }, + math::{self, Transform, Vec3}, + render::light::directional::DirectionalLight, + scene::{ + CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform, + ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN, + ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN, + }, +}; + +#[async_std::main] +async fn main() { + let action_handler_plugin = |game: &mut Game| { + let action_handler = ActionHandler::builder() + .add_layout(LayoutId::from(0)) + .add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis)) + .add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis)) + .add_action("Debug", Action::new(ActionKind::Button)) + .add_mapping( + ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0)) + .bind( + ACTLBL_MOVE_FORWARD_BACKWARD, + &[ + ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_MOVE_LEFT_RIGHT, + &[ + ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_MOVE_UP_DOWN, + &[ + ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0), + ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0), + ], + ) + .bind( + ACTLBL_LOOK_LEFT_RIGHT, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(), + ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_LOOK_UP_DOWN, + &[ + ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(), + ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0), + ], + ) + .bind( + ACTLBL_LOOK_ROLL, + &[ + ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0), + ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0), + ], + ) + .bind( + "Debug", + &[ActionSource::Keyboard(KeyCode::B).into_binding()], + ) + .finish(), + ) + .finish(); + + let world = game.world_mut(); + world.add_resource(action_handler); + game.with_plugin(InputActionPlugin); + }; + + Game::initialize() + .await + .with_plugin(lyra_engine::DefaultPlugins) + .with_plugin(setup_scene_plugin) + .with_plugin(action_handler_plugin) + //.with_plugin(camera_debug_plugin) + .with_plugin(FreeFlyCameraPlugin) + .run() + .await; +} + +fn setup_scene_plugin(game: &mut Game) { + let world = game.world_mut(); + let resman = world.get_resource_mut::(); + + /* let camera_gltf = resman + .request::("../assets/AntiqueCamera.glb") + .unwrap(); + + camera_gltf.wait_recurse_dependencies_load(); + let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0]; + drop(resman); + + world.spawn(( + camera_mesh.clone(), + WorldTransform::default(), + Transform::from_xyz(0.0, -5.0, -2.0), + )); */ + + let cube_gltf = resman + .request::("../assets/cube-texture-embedded.gltf") + .unwrap(); + + cube_gltf.wait_recurse_dependencies_load(); + let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0]; + drop(resman); + + world.spawn(( + cube_mesh.clone(), + WorldTransform::default(), + Transform::from_xyz(0.0, -5.0, -2.0), + )); + + { + let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0); + light_tran.scale = Vec3::new(0.5, 0.5, 0.5); + light_tran.rotate_x(math::Angle::Degrees(-45.0)); + light_tran.rotate_y(math::Angle::Degrees(25.0)); + world.spawn(( + DirectionalLight { + enabled: true, + color: Vec3::ONE, + intensity: 0.15, //..Default::default() + }, + light_tran, + )); + } + + let mut camera = CameraComponent::new_3d(); + camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5); + world.spawn((camera, FreeFlyCamera::default())); +} \ No newline at end of file diff --git a/lyra-game/src/game.rs b/lyra-game/src/game.rs index 5e624d7..8ccf6c3 100755 --- a/lyra-game/src/game.rs +++ b/lyra-game/src/game.rs @@ -57,10 +57,12 @@ struct GameLoop { } impl GameLoop { - pub async fn new(window: Arc, world: World, staged_exec: StagedExecutor) -> GameLoop { + pub async fn new(window: Arc, mut world: World, staged_exec: StagedExecutor) -> Self { + let renderer = BasicRenderer::create_with_window(&mut world, window.clone()).await; + Self { window: Arc::clone(&window), - renderer: Box::new(BasicRenderer::create_with_window(window).await), + renderer: Box::new(renderer), world, staged_exec, @@ -68,7 +70,7 @@ impl GameLoop { } pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize) { - self.renderer.on_resize(new_size); + self.renderer.on_resize(&mut self.world, new_size); } pub async fn on_init(&mut self) { diff --git a/lyra-game/src/lib.rs b/lyra-game/src/lib.rs index 47de0fb..83d7615 100644 --- a/lyra-game/src/lib.rs +++ b/lyra-game/src/lib.rs @@ -1,6 +1,7 @@ #![feature(hash_extract_if)] #![feature(lint_reasons)] #![feature(trait_alias)] +#![feature(map_many_mut)] extern crate self as lyra_engine; diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs index cdbbc6e..5001046 100644 --- a/lyra-game/src/render/graph/execution_path.rs +++ b/lyra-game/src/render/graph/execution_path.rs @@ -11,7 +11,7 @@ pub struct GraphExecutionPath { } impl GraphExecutionPath { - pub fn new(pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { + pub fn new(built_in_slots: FxHashSet, pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { // collect all the output slots let mut total_outputs = HashMap::new(); for desc in pass_descriptions.iter() { @@ -28,6 +28,9 @@ impl GraphExecutionPath { // find the node inputs let mut inputs = vec![]; for slot in desc.input_slots() { + // If the slot is built in to the graph, no need to care about the sorting. + if built_in_slots.contains(&slot.id) { continue; } + let inp = total_outputs.get(&slot.name) .expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name)); inputs.push(*inp); diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index fd2c072..056968e 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -1,5 +1,10 @@ mod pass; -use std::collections::HashMap; +use std::{ + cell::RefCell, + collections::{HashMap, VecDeque}, + ptr::NonNull, + sync::Arc, +}; pub use pass::*; @@ -11,14 +16,22 @@ pub use slot_desc::*; mod execution_path; -use rustc_hash::FxHashMap; -use tracing::debug; +use rustc_hash::{FxHashMap, FxHashSet}; +use tracing::{debug, debug_span, instrument}; use wgpu::{util::DeviceExt, RenderPass}; -use super::{compute_pipeline::ComputePipeline, pipeline::Pipeline, render_pipeline::RenderPipeline, renderer::{BasicRenderer, Renderer}}; +use self::execution_path::GraphExecutionPath; +use super::{ + compute_pipeline::ComputePipeline, + pipeline::Pipeline, + render_pipeline::RenderPipeline, + renderer::{BasicRenderer, Renderer}, +}; + +#[derive(Clone)] struct PassEntry { - inner: Box, + inner: Arc>, desc: RenderGraphPassDesc, } @@ -35,7 +48,7 @@ struct ResourcedSlot { } /// Stores the pipeline and other resources it uses. -/// +/// /// This stores the bind groups that have been created for it pub struct PipelineResource { pub pipeline: Pipeline, @@ -46,80 +59,137 @@ pub struct PipelineResource { pub struct RenderGraph { slots: FxHashMap, slot_names: HashMap, - // slots with same name - slot_mutlikey: FxHashMap, passes: FxHashMap, // TODO: Use a SlotMap bind_groups: FxHashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. - pipelines: HashMap, + /// + pipelines: FxHashMap, current_id: u64, + exec_path: Option, pub(crate) surface_config: wgpu::SurfaceConfiguration, } impl RenderGraph { pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self { + let mut slots = FxHashMap::default(); + let mut slot_names = HashMap::default(); + + slots.insert( + 0, + ResourcedSlot { + name: "window_texture_view".to_string(), + ty: SlotType::TextureView, + value: SlotValue::None, + bind_group_id: None, + create_desc: None, + }, + ); + slot_names.insert("window_texture_view".to_string(), 0u64); + Self { - slots: Default::default(), - slot_names: Default::default(), - slot_mutlikey: Default::default(), + slots, + slot_names, passes: Default::default(), bind_groups: Default::default(), pipelines: Default::default(), - current_id: 0, + current_id: 1, + exec_path: None, surface_config, } } + pub fn next_id(&mut self) -> u64 { + self.current_id += 1; + self.current_id + } + pub fn slot_id(&self, name: &str) -> Option { self.slot_names.get(name).cloned() } + pub fn slot_value(&self, id: u64) -> Option<&SlotValue> { + self.slots.get(&id).map(|s| &s.value) + } + pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { - self.passes.get(&id) - .map(|s| &s.desc) + self.passes.get(&id).map(|s| &s.desc) } pub fn add_pass(&mut self, pass: P) { - let mut desc = pass.desc(self, &mut self.current_id); - + let mut desc = pass.desc(self); + for slot in &mut desc.slots { - if let Some((id, other)) = self.slot_names.get(&slot.name) + if let Some((id, other)) = self + .slot_names + .get(&slot.name) .and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) { slot.id = *id; if slot.desc.is_some() && other.create_desc.is_none() { - other.create_desc = slot.desc; + other.create_desc = slot.desc.clone(); } } else { let res_slot = ResourcedSlot { - name: slot.name, + name: slot.name.clone(), ty: slot.ty, value: SlotValue::None, bind_group_id: None, - create_desc: slot.desc, + create_desc: slot.desc.clone(), }; self.slots.insert(slot.id, res_slot); - self.slot_names.insert(slot.name, slot.id); + self.slot_names.insert(slot.name.clone(), slot.id); } } - - self.passes.insert(desc.id, PassEntry { - inner: Box::new(pass), - desc, - }); + + self.passes.insert( + desc.id, + PassEntry { + inner: Arc::new(RefCell::new(pass)), + desc, + }, + ); } /// Creates all buffers required for the passes, also creates an internal execution path. + #[instrument(skip(self, device))] pub fn setup(&mut self, device: &wgpu::Device) { + let mut later_slots = VecDeque::new(); - for slot in self.slots.values_mut() { - if slot.bind_group_id.is_none() { - match slot.create_desc { + // For all passes, create their pipelines + for pass in self.passes.values() { + if let Some(pipei) = &pass.desc.pipeline_info { + let pipeline = match pass.desc.pass_type { + RenderPassType::Render => { + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some(pass.desc.name.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&pipei.source)), + }); + Pipeline::Render(RenderPipeline::new(device, &self.surface_config, &shader, vec![], vec![])) + } + _ => todo!(), + }; + + let res = PipelineResource { + pipeline, + bg_layout_name_lookup: Default::default(), + }; + self.pipelines.insert(pass.desc.id, res); + } + } + + for (slot_id, slot) in &mut self.slots { + if slot.bind_group_id.is_none() && slot.create_desc.is_some() { + let s = debug_span!("res_creation", slot = slot.name); + let _e = s.enter(); + + debug!("Creating bind group for slot"); + + match &slot.create_desc { Some(SlotDescriptor::BufferInit(bi)) => { let label = format!("B_{}", slot.name); let wb = bi.as_wgpu(Some(&label)); @@ -127,98 +197,125 @@ impl RenderGraph { let buf = device.create_buffer_init(&wb); slot.value = SlotValue::Buffer(buf); - debug!(slot=slot.name, "Created and initialized buffer for slot"); - }, + debug!("Created and initialized buffer for slot"); + } Some(SlotDescriptor::Buffer(b)) => { let label = format!("B_{}", slot.name); let wb = b.as_wgpu(Some(&label)); - + let buf = device.create_buffer(&wb); slot.value = SlotValue::Buffer(buf); - debug!(slot=slot.name, "Created buffer"); + debug!("Created buffer"); } + Some(SlotDescriptor::Texture(t)) => { + let label = format!("Tex_{}", slot.name); + let wt = t.as_wgpu(Some(&label)); + + let tex = device.create_texture(&wt); + slot.value = SlotValue::Texture(tex); + + debug!("Created texture"); + } + Some(SlotDescriptor::TextureView(tv)) => { + // texture views cannot be done in this step. Not only would we run into a + // borrow checker error when trying to get the texture for the view, we + // can also not guarantee that the texture was actually created. + let tex_slot = self + .slot_names + .get(&tv.texture_label) + .expect("Failed to find texture for texture view slot"); + later_slots.push_back((*slot_id, *tex_slot)); + + debug!("Queuing slot resources for later creation"); + } + //Some(SlotDescriptor::Sampler(b)) => {}, //Some(SlotDescriptor::Texture(b)) => {}, - //Some(SlotDescriptor::TextureView(b)) => {}, - Some(SlotDescriptor::None) => {}, - None => {}, + Some(SlotDescriptor::None) => {} + None => {} _ => todo!(), } } } - todo!() + // create buffers for the queued slots + while let Some((lower_id, upper_id)) = later_slots.pop_front() { + let many = self.slots.get_many_mut([&lower_id, &upper_id]).unwrap(); + let (lower_slot, upper_slot) = match many { + [a, b] => (a, b), + }; + + let s = debug_span!("deferred_res_creation", slot = lower_slot.name); + let _e = s.enter(); + + match &lower_slot.create_desc { + Some(SlotDescriptor::TextureView(tv)) => { + let label = format!("TexView_{}", lower_slot.name); + + let wt = tv.as_wgpu(Some(&label)); + let tex = upper_slot.value.as_texture(); + + let texview = tex.create_view(&wt); + lower_slot.value = SlotValue::TextureView(texview); + + debug!(slot = lower_slot.name, "Created texture view"); + } + Some(SlotDescriptor::None) => {} + None => {} + _ => unreachable!("this slot should not have been put into the do later list"), + } + } } pub fn prepare(&mut self) { - todo!() - } - - pub fn render(&mut self, renderer: &mut BasicRenderer) { - todo!() + let builtin = { + let mut h = FxHashSet::default(); + h.insert(0u64); + h + }; + let descs = self.passes.values().map(|p| &p.desc).collect(); + self.exec_path = Some(GraphExecutionPath::new(builtin, descs)); } - /// Get a pipeline by name from the graph. - /// - /// # Panics - /// Panics if the pipeline was not found by name. - #[inline(always)] - pub fn pipeline(&self, name: &str) -> &Pipeline { - &self.pipelines.get(name) - .unwrap() - .pipeline + pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, surface: &wgpu::Surface) { + let mut path = self.exec_path.take().unwrap(); + + let output = surface.get_current_texture().unwrap(); + let view = output + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + // update the window texture view slot. + let window_tv_slot = self.slots.get_mut(&0).unwrap(); // 0 is window_texture_view + debug_assert_eq!( + window_tv_slot.name.as_str(), + "window_texture_view", + "unexpected slot where 'window_texture_view' should be" + ); + window_tv_slot.value = SlotValue::TextureView(view); + + while let Some(pass_id) = path.queue.pop_front() { + let pass = self.passes.get(&pass_id).unwrap().clone(); + let label = format!("{} Encoder", pass.desc.name); + + let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some(&label), + }); + let mut context = RenderGraphContext::new(encoder, queue); + + let mut inner = pass.inner.borrow_mut(); + inner.execute(self, &pass.desc, &mut context); + + queue.submit(std::iter::once(context.encoder.finish())); + } + + output.present(); } - /// Attempt to get a pipeline by name from the graph. - /// - /// Returns `None` if the pipeline was not found by name, #[inline(always)] - pub fn try_pipeline(&self, name: &str) -> Option<&Pipeline> { - self.pipelines.get(name) - .map(|p| &p.pipeline) - } - - /// Get a [`RenderPipeline`] by name from the graph. - /// - /// # Panics - /// Panics if the pipeline was not found by name, or if the pipeline is not a render pipeline. - #[inline(always)] - pub fn render_pipeline(&self, name: &str) -> &RenderPipeline { - self.pipelines.get(name) - .unwrap() - .pipeline - .as_render() - } - - /// Attempt to get a [`RenderPipeline`] by name from the graph. - /// - /// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline. - #[inline(always)] - pub fn try_render_pipeline(&self, name: &str) -> Option<&RenderPipeline> { - self.pipelines.get(name) - .and_then(|p| p.pipeline.try_as_render()) - } - - /// Get a [`ComputePipeline`] by name from the graph. - /// - /// # Panics - /// Panics if the pipeline was not found by name, or if the pipeline is not a compute pipeline. - #[inline(always)] - pub fn compute_pipeline(&self, name: &str) -> &ComputePipeline { - &self.pipelines.get(name) - .unwrap() - .pipeline - .as_compute() - } - - /// Attempt to get a [`ComputePipeline`] by name from the graph. - /// - /// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline. - #[inline(always)] - pub fn try_compute_pipeline(&self, name: &str) -> Option<&ComputePipeline> { - self.pipelines.get(name) - .and_then(|p| p.pipeline.try_as_compute()) + pub fn pipeline(&self, id: u64) -> &Pipeline { + &self.pipelines.get(&id).unwrap().pipeline } #[inline(always)] @@ -228,19 +325,24 @@ impl RenderGraph { #[inline(always)] pub fn bind_group(&self, id: u64) -> &wgpu::BindGroup { - self.try_bind_group(id) - .expect("Unknown id for bind group") + self.try_bind_group(id).expect("Unknown id for bind group") } #[inline(always)] pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { - self.slots.get(&id) - .and_then(|s| self.bind_groups.get(&s.bind_group_id.expect("Slot bind group has not been created yet"))) + self.slots.get(&id).and_then(|s| { + self.bind_groups.get( + &s.bind_group_id + .expect("Slot bind group has not been created yet"), + ) + }) } #[inline(always)] pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup { - let bg_id = self.slots.get(&id) + let bg_id = self + .slots + .get(&id) .expect("unknown slot id") .bind_group_id .expect("Slot bind group has not been created yet"); @@ -248,17 +350,50 @@ impl RenderGraph { } } +pub(crate) struct BufferWrite { + /// The name of the slot that has the resource that will be written + target_slot: String, + bytes: Vec, +} + pub struct RenderGraphContext<'a> { - encoder: wgpu::CommandEncoder, - queue: &'a wgpu::Queue, + /// Becomes None when the encoder is submitted + pub(crate) encoder: wgpu::CommandEncoder, + pub(crate) queue: &'a wgpu::Queue, + pub(crate) buffer_writes: Vec, + renderpass_desc: Vec>, } impl<'a> RenderGraphContext<'a> { - pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass { - todo!() + pub fn new(encoder: wgpu::CommandEncoder, queue: &'a wgpu::Queue) -> Self { + Self { + encoder, + queue, + buffer_writes: vec![], + renderpass_desc: vec![], + } + } + + pub fn begin_render_pass( + &'a mut self, + desc: wgpu::RenderPassDescriptor<'a, 'a>, + ) -> wgpu::RenderPass { + self.encoder.begin_render_pass(&desc) } pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass { - todo!() + self.encoder.begin_compute_pass(desc) } -} \ No newline at end of file + + pub fn write_buffer(&mut self, target_slot: &str, bytes: &[u8]) { + //self.queue.write_buffer(buffer, offset, data) + self.buffer_writes.push(BufferWrite { + target_slot: target_slot.to_string(), + bytes: bytes.to_vec(), + }) + } + + pub fn write_buffer_muck(&mut self, target_slot: &str, bytes: T) { + self.write_buffer(target_slot, bytemuck::bytes_of(&bytes)); + } +} diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 6fbd328..ad87be1 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -28,6 +28,23 @@ pub enum SlotValue { Buffer(wgpu::Buffer), } +impl SlotValue { + /// Gets `self` as a texture, panics if self is not a instance of [`SlotValue::Texture`]. + pub fn as_texture(&self) -> &wgpu::Texture { + match self { + Self::Texture(t) => t, + _ => panic!("self is not an instance of SlotValue::Texture"), + } + } + + pub fn as_texture_view(&self) -> &wgpu::TextureView { + match self { + Self::TextureView(t) => t, + _ => panic!("self is not an instance of SlotValue::TextureView"), + } + } +} + #[derive(Clone, Debug)] pub enum SlotDescriptor { /// Most likely this slot is an input, so it doesn't need to specify the descriptor. @@ -57,6 +74,21 @@ pub struct RenderPassSlot { pub desc: Option, } +#[derive(Clone)] +pub struct RenderGraphPipelineInfo { + pub label: String, + pub source: String, +} + +impl RenderGraphPipelineInfo { + pub fn new(label: &str, source: &str) -> Self { + Self { + label: label.to_string(), + source: source.to_string(), + } + } +} + #[derive(Clone)] pub struct RenderGraphPassDesc { pub id: u64, @@ -64,16 +96,18 @@ pub struct RenderGraphPassDesc { pub pass_type: RenderPassType, pub slots: Vec, slot_names: HashMap, + pub pipeline_info: Option, } impl RenderGraphPassDesc { - pub fn new(id: u64, name: &str, pass_type: RenderPassType) -> Self { + pub fn new(id: u64, name: &str, pass_type: RenderPassType, pipeline_info: Option) -> Self { Self { id, name: name.to_string(), pass_type, slots: vec![], slot_names: HashMap::default(), + pipeline_info } } @@ -84,7 +118,7 @@ impl RenderGraphPassDesc { #[inline(always)] pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), + debug_assert!(matches!(desc, None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -99,7 +133,7 @@ impl RenderGraphPassDesc { #[inline(always)] pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::Texture(_))), + debug_assert!(matches!(desc, None | Some(SlotDescriptor::Texture(_))), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -114,7 +148,7 @@ impl RenderGraphPassDesc { #[inline(always)] pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::TextureView(_))), + debug_assert!(matches!(desc, None | Some(SlotDescriptor::TextureView(_))), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -128,8 +162,8 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, Some(SlotDescriptor::Sampler(_))), + pub fn add_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { + debug_assert!(matches!(desc, None | Some(SlotDescriptor::Sampler(_))), "slot descriptor does not match the type of slot"); let slot = RenderPassSlot { @@ -161,8 +195,8 @@ pub trait RenderGraphPass: 'static { /// Create a render pass describer. /// /// The `id` argument is passed as mutable so you can increment it as you use it for new slots. - fn desc(&self, graph: &mut RenderGraph, id: &mut u64) -> RenderGraphPassDesc; + fn desc<'a, 'b>(&'a self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc; - fn prepare(&mut self, world: &mut World); + fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext); fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext); } \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 5309f48..7f7a170 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -1,8 +1,14 @@ -mod light_cull_compute; +/* mod light_cull_compute; pub use light_cull_compute::*; mod base; pub use base::*; mod depth_prepass; -pub use depth_prepass::*; \ No newline at end of file +pub use depth_prepass::*; + +mod simple_phong; +pub use simple_phong::*; */ + +mod triangle; +pub use triangle::*; \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs new file mode 100644 index 0000000..0f724a6 --- /dev/null +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -0,0 +1,95 @@ +use glam::UVec2; +use tracing::warn; + +use crate::{ + render::{ + camera::{CameraUniform, RenderCamera}, + graph::{ + BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, + RenderGraphPipelineInfo, RenderPassType, SlotAttribute, SlotDescriptor, + }, + renderer::ScreenSize, + }, + scene::CameraComponent, +}; + +/// Supplies some basic things other passes needs. +/// +/// screen size buffer, camera buffer, +#[derive(Default)] +pub struct TrianglePass; + +impl TrianglePass { + pub fn new() -> Self { + Self::default() + } +} + +impl RenderGraphPass for TrianglePass { + fn desc( + &self, + graph: &mut crate::render::graph::RenderGraph, + ) -> crate::render::graph::RenderGraphPassDesc { + let mut desc = RenderGraphPassDesc::new( + graph.next_id(), + "TrianglePass", + RenderPassType::Render, + Some(RenderGraphPipelineInfo::new( + "TriangleShader", + include_str!("../../shaders/triangle.wgsl"), + )), + ); + + desc.add_texture_view_slot(graph.next_id(), "window_texture_view", SlotAttribute::Input, None); + + /* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + label: Some("B_ScreenSize".to_string()), + contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }))); + desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + label: Some("B_Camera".to_string()), + contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }))); */ + + desc + } + + fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {} + + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + desc: &crate::render::graph::RenderGraphPassDesc, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let view = graph.slot_value(graph.slot_id("window_texture_view").unwrap()).unwrap().as_texture_view(); + let encoder = &mut context.encoder; + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("TrianglePass"), + color_attachments: &[ + // This is what @location(0) in the fragment shader targets + Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: true, + }, + }), + ], + depth_stencil_attachment: None, + }); + + let pipeline = graph.pipeline(desc.id); + pass.set_pipeline(&pipeline.as_render()); + pass.draw(0..3, 0..1); + } +} diff --git a/lyra-game/src/render/graph/slot_desc.rs b/lyra-game/src/render/graph/slot_desc.rs index 0022c1f..51ac703 100644 --- a/lyra-game/src/render/graph/slot_desc.rs +++ b/lyra-game/src/render/graph/slot_desc.rs @@ -40,6 +40,19 @@ impl TextureViewDescriptor { array_layer_count: d.array_layer_count, } } + + pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::TextureViewDescriptor<'a> { + wgpu::TextureViewDescriptor { + label, + format: self.format, + dimension: self.dimension, + aspect: self.aspect, + base_mip_level: self.base_mip_level, + mip_level_count: self.mip_level_count, + base_array_layer: self.base_array_layer, + array_layer_count: self.array_layer_count, + } + } } @@ -117,6 +130,21 @@ pub struct TextureDescriptor { pub view_formats: Vec, } +impl TextureDescriptor { + pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::TextureDescriptor<'a> { + wgpu::TextureDescriptor { + label, + size: self.size, + mip_level_count: self.mip_level_count, + sample_count: self.sample_count, + dimension: self.dimension, + format: self.format, + usage: self.usage, + view_formats: &self.view_formats, + } + } +} + #[repr(C)] #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct BufferDescriptor { @@ -134,7 +162,7 @@ pub struct BufferDescriptor { } impl BufferDescriptor { - pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::BufferDescriptor { + pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::BufferDescriptor<'a> { wgpu::BufferDescriptor { label, size: self.size, @@ -156,7 +184,7 @@ pub struct BufferInitDescriptor { } impl BufferInitDescriptor { - pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::util::BufferInitDescriptor { + pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::util::BufferInitDescriptor<'a> { wgpu::util::BufferInitDescriptor { label, contents: &self.contents, diff --git a/lyra-game/src/render/render_pipeline.rs b/lyra-game/src/render/render_pipeline.rs index 19a1ecc..8a2f8c3 100755 --- a/lyra-game/src/render/render_pipeline.rs +++ b/lyra-game/src/render/render_pipeline.rs @@ -56,13 +56,14 @@ impl RenderPipeline { // Requires Features::CONSERVATIVE_RASTERIZATION conservative: false, }, - depth_stencil: Some(wgpu::DepthStencilState { + /*depth_stencil: Some(wgpu::DepthStencilState { format: RenderTexture::DEPTH_FORMAT, depth_write_enabled: true, depth_compare: wgpu::CompareFunction::Less, stencil: wgpu::StencilState::default(), // TODO: stencil buffer bias: wgpu::DepthBiasState::default(), - }), + }),*/ + depth_stencil: None, multisample: wgpu::MultisampleState { count: 1, mask: !0, diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 75e7f0f..56bc1c9 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -1,4 +1,5 @@ use std::collections::{VecDeque, HashSet}; +use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; use std::borrow::Cow; @@ -18,7 +19,7 @@ use wgpu::util::DeviceExt; use winit::window::Window; use crate::math::Transform; -use crate::render::graph::{BasePass, DepthPrePass, LightCullComputePass}; +use crate::render::graph::TrianglePass; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; use crate::scene::CameraComponent; @@ -44,6 +45,20 @@ type SceneHandle = ResHandle; #[derive(Clone, Copy, Debug)] pub struct ScreenSize(glam::UVec2); +impl Deref for ScreenSize { + type Target = glam::UVec2; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ScreenSize { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + pub trait Renderer { fn prepare(&mut self, main_world: &mut World); fn render(&mut self) -> Result<(), wgpu::SurfaceError>; @@ -212,9 +227,15 @@ impl BasicRenderer { //let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); let mut g = RenderGraph::new(config.clone()); - g.add_pass(BasePass::new()); + /* debug!("Adding base pass"); + g.add_pass(TrianglePass::new()); + debug!("Adding depth pre-pass"); g.add_pass(DepthPrePass::new()); - g.add_pass(LightCullComputePass::new(size)); + debug!("Adding light cull compute pass"); + g.add_pass(LightCullComputePass::new(size)); */ + + debug!("Adding triangle pass"); + g.add_pass(TrianglePass::new()); g.setup(&device); let mut s = Self { @@ -261,7 +282,7 @@ impl Renderer for BasicRenderer { #[instrument(skip(self))] fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.graph.render(self); + self.graph.render(&self.device, &self.queue, &self.surface); Ok(()) } @@ -276,7 +297,7 @@ impl Renderer for BasicRenderer { // tell other things of updated resize self.surface.configure(&self.device, &self.config); - let mut world_ss = world.get_resource::(); + let mut world_ss = world.get_resource_mut::(); world_ss.0 = glam::UVec2::new(new_size.width, new_size.height); self.graph.surface_config = self.config.clone(); } diff --git a/lyra-game/src/render/shaders/triangle.wgsl b/lyra-game/src/render/shaders/triangle.wgsl new file mode 100644 index 0000000..f5a0eae --- /dev/null +++ b/lyra-game/src/render/shaders/triangle.wgsl @@ -0,0 +1,19 @@ +struct VertexOutput { + @builtin(position) clip_position: vec4, +}; + +@vertex +fn vs_main( + @builtin(vertex_index) in_vertex_index: u32, +) -> VertexOutput { + var out: VertexOutput; + let x = f32(1 - i32(in_vertex_index)) * 0.5; + let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5; + out.clip_position = vec4(x, y, 0.0, 1.0); + return out; +} + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return vec4(0.3, 0.2, 0.1, 1.0); +} \ No newline at end of file From b94a8e3cd3df2aa15e661c6b5b5e04e63506365b Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 11 May 2024 09:19:58 -0400 Subject: [PATCH 05/20] Make it possible to create a complete pipeline descriptor for a pass --- lyra-game/src/render/graph/execution_path.rs | 5 +- lyra-game/src/render/graph/mod.rs | 37 ++- lyra-game/src/render/graph/pass.rs | 151 +++++++++--- lyra-game/src/render/graph/passes/mod.rs | 4 +- lyra-game/src/render/graph/passes/triangle.rs | 53 +++- lyra-game/src/render/mod.rs | 4 +- lyra-game/src/render/render_pipeline.rs | 90 ------- lyra-game/src/render/renderer.rs | 26 +- .../render/{ => resource}/compute_pipeline.rs | 0 lyra-game/src/render/resource/mod.rs | 8 + .../src/render/{ => resource}/pipeline.rs | 0 .../src/render/resource/render_pipeline.rs | 228 ++++++++++++++++++ .../src/render/shaders/simple_phong.wgsl | 0 13 files changed, 430 insertions(+), 176 deletions(-) delete mode 100755 lyra-game/src/render/render_pipeline.rs rename lyra-game/src/render/{ => resource}/compute_pipeline.rs (100%) create mode 100644 lyra-game/src/render/resource/mod.rs rename lyra-game/src/render/{ => resource}/pipeline.rs (100%) create mode 100755 lyra-game/src/render/resource/render_pipeline.rs create mode 100644 lyra-game/src/render/shaders/simple_phong.wgsl diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs index 5001046..049b7af 100644 --- a/lyra-game/src/render/graph/execution_path.rs +++ b/lyra-game/src/render/graph/execution_path.rs @@ -81,9 +81,8 @@ struct SlotOwnerPair { slot: u64, } -#[derive(Clone)] -struct Node { +struct Node<'a> { id: u64, - desc: RenderGraphPassDesc, + desc: &'a RenderGraphPassDesc, slot_inputs: Vec, } diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 056968e..d975370 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -23,16 +23,13 @@ use wgpu::{util::DeviceExt, RenderPass}; use self::execution_path::GraphExecutionPath; use super::{ - compute_pipeline::ComputePipeline, - pipeline::Pipeline, - render_pipeline::RenderPipeline, - renderer::{BasicRenderer, Renderer}, + renderer::{BasicRenderer, Renderer}, resource::{Pipeline, RenderPipeline}, }; -#[derive(Clone)] +//#[derive(Clone)] struct PassEntry { inner: Arc>, - desc: RenderGraphPassDesc, + desc: Arc, } struct ResourcedSlot { @@ -115,7 +112,7 @@ impl RenderGraph { } pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { - self.passes.get(&id).map(|s| &s.desc) + self.passes.get(&id).map(|s| &*s.desc) } pub fn add_pass(&mut self, pass: P) { @@ -150,7 +147,7 @@ impl RenderGraph { desc.id, PassEntry { inner: Arc::new(RefCell::new(pass)), - desc, + desc: Arc::new(desc), }, ); } @@ -162,14 +159,10 @@ impl RenderGraph { // For all passes, create their pipelines for pass in self.passes.values() { - if let Some(pipei) = &pass.desc.pipeline_info { + if let Some(pipei) = &pass.desc.pipeline_desc { let pipeline = match pass.desc.pass_type { RenderPassType::Render => { - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some(pass.desc.name.as_str()), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&pipei.source)), - }); - Pipeline::Render(RenderPipeline::new(device, &self.surface_config, &shader, vec![], vec![])) + Pipeline::Render(RenderPipeline::create(device, pipei)) } _ => todo!(), }; @@ -274,7 +267,7 @@ impl RenderGraph { h.insert(0u64); h }; - let descs = self.passes.values().map(|p| &p.desc).collect(); + let descs = self.passes.values().map(|p| &*p.desc).collect(); self.exec_path = Some(GraphExecutionPath::new(builtin, descs)); } @@ -295,21 +288,25 @@ impl RenderGraph { ); window_tv_slot.value = SlotValue::TextureView(view); + let mut encoders = vec![]; while let Some(pass_id) = path.queue.pop_front() { - let pass = self.passes.get(&pass_id).unwrap().clone(); - let label = format!("{} Encoder", pass.desc.name); + let pass = self.passes.get(&pass_id).unwrap(); + let pass_inn = pass.inner.clone(); + let pass_desc = pass.desc.clone(); + let label = format!("{} Encoder", pass_desc.name); let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(&label), }); let mut context = RenderGraphContext::new(encoder, queue); - let mut inner = pass.inner.borrow_mut(); - inner.execute(self, &pass.desc, &mut context); + let mut inner = pass_inn.borrow_mut(); + inner.execute(self, &*pass_desc, &mut context); - queue.submit(std::iter::once(context.encoder.finish())); + encoders.push(context.encoder.finish()); } + queue.submit(encoders.into_iter()); output.present(); } diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index ad87be1..0ae51b1 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -1,14 +1,19 @@ -use std::collections::HashMap; +use std::{collections::HashMap, num::NonZeroU32, rc::Rc}; use lyra_ecs::World; -use super::{BufferDescriptor, BufferInitDescriptor, RenderGraph, RenderGraphContext, SamplerDescriptor, TextureDescriptor, TextureViewDescriptor}; +use crate::render::resource::RenderPipelineDescriptor; + +use super::{ + BufferDescriptor, BufferInitDescriptor, RenderGraph, RenderGraphContext, SamplerDescriptor, + TextureDescriptor, TextureViewDescriptor, +}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RenderPassType { Compute, #[default] - Render + Render, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -49,7 +54,7 @@ impl SlotValue { pub enum SlotDescriptor { /// Most likely this slot is an input, so it doesn't need to specify the descriptor. None, - + TextureView(TextureViewDescriptor), Sampler(SamplerDescriptor), Texture(TextureDescriptor), @@ -75,39 +80,83 @@ pub struct RenderPassSlot { } #[derive(Clone)] -pub struct RenderGraphPipelineInfo { - pub label: String, +pub struct PipelineShaderDesc { + pub label: Option, pub source: String, + pub entry_point: String, +} + +#[derive(Clone)] +pub struct RenderGraphPipelineInfo { + /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. + pub label: Option, + /// The layout of bind groups for this pipeline. + pub bind_group_layouts: Vec>, + /// The descriptor of the vertex shader. + pub vertex: PipelineShaderDesc, + /// The properties of the pipeline at the primitive assembly and rasterization level. + pub primitive: wgpu::PrimitiveState, + /// The effect of draw calls on the depth and stencil aspects of the output target, if any. + pub depth_stencil: Option, + /// The multi-sampling properties of the pipeline. + pub multisample: wgpu::MultisampleState, + /// The compiled fragment stage, its entry point, and the color targets. + pub fragment: Option, + /// If the pipeline will be used with a multiview render pass, this indicates how many array + /// layers the attachments will have. + pub multiview: Option, } impl RenderGraphPipelineInfo { - pub fn new(label: &str, source: &str) -> Self { + pub fn new( + label: &str, + bind_group_layouts: Vec, + vertex: PipelineShaderDesc, + primitive: wgpu::PrimitiveState, + depth_stencil: Option, + multisample: wgpu::MultisampleState, + fragment: Option, + multiview: Option, + ) -> Self { Self { - label: label.to_string(), - source: source.to_string(), + label: Some(label.to_string()), + bind_group_layouts: bind_group_layouts + .into_iter() + .map(|bgl| Rc::new(bgl)) + .collect(), + vertex, + primitive, + depth_stencil, + multisample, + fragment, + multiview, } } } -#[derive(Clone)] pub struct RenderGraphPassDesc { pub id: u64, pub name: String, pub pass_type: RenderPassType, pub slots: Vec, slot_names: HashMap, - pub pipeline_info: Option, + pub pipeline_desc: Option, } impl RenderGraphPassDesc { - pub fn new(id: u64, name: &str, pass_type: RenderPassType, pipeline_info: Option) -> Self { + pub fn new( + id: u64, + name: &str, + pass_type: RenderPassType, + pipeline_desc: Option, + ) -> Self { Self { id, name: name.to_string(), pass_type, slots: vec![], slot_names: HashMap::default(), - pipeline_info + pipeline_desc, } } @@ -117,9 +166,20 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), - "slot descriptor does not match the type of slot"); + pub fn add_buffer_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!( + desc, + None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) + ), + "slot descriptor does not match the type of slot" + ); let slot = RenderPassSlot { id, @@ -132,10 +192,18 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::Texture(_))), - "slot descriptor does not match the type of slot"); - + pub fn add_texture_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!(desc, None | Some(SlotDescriptor::Texture(_))), + "slot descriptor does not match the type of slot" + ); + let slot = RenderPassSlot { id, name: name.to_string(), @@ -147,10 +215,18 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::TextureView(_))), - "slot descriptor does not match the type of slot"); - + pub fn add_texture_view_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!(desc, None | Some(SlotDescriptor::TextureView(_))), + "slot descriptor does not match the type of slot" + ); + let slot = RenderPassSlot { id, name: name.to_string(), @@ -162,10 +238,18 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option) { - debug_assert!(matches!(desc, None | Some(SlotDescriptor::Sampler(_))), - "slot descriptor does not match the type of slot"); - + pub fn add_sampler_slot( + &mut self, + id: u64, + name: &str, + attribute: SlotAttribute, + desc: Option, + ) { + debug_assert!( + matches!(desc, None | Some(SlotDescriptor::Sampler(_))), + "slot descriptor does not match the type of slot" + ); + let slot = RenderPassSlot { id, name: name.to_string(), @@ -193,10 +277,15 @@ impl RenderGraphPassDesc { pub trait RenderGraphPass: 'static { /// Create a render pass describer. - /// + /// /// The `id` argument is passed as mutable so you can increment it as you use it for new slots. fn desc<'a, 'b>(&'a self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc; fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext); - fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext); -} \ No newline at end of file + fn execute( + &mut self, + graph: &mut RenderGraph, + desc: &RenderGraphPassDesc, + context: &mut RenderGraphContext, + ); +} diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 7f7a170..0b8c295 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -5,9 +5,9 @@ mod base; pub use base::*; mod depth_prepass; -pub use depth_prepass::*; +pub use depth_prepass::*; */ -mod simple_phong; +/* mod simple_phong; pub use simple_phong::*; */ mod triangle; diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 0f724a6..1ab2a62 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -1,14 +1,19 @@ +use std::rc::Rc; + use glam::UVec2; use tracing::warn; +use wgpu::include_wgsl; use crate::{ render::{ camera::{CameraUniform, RenderCamera}, graph::{ - BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, - RenderGraphPipelineInfo, RenderPassType, SlotAttribute, SlotDescriptor, + BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext, RenderGraphPass, + RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType, SlotAttribute, + SlotDescriptor, }, renderer::ScreenSize, + resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState}, }, scene::CameraComponent, }; @@ -30,17 +35,46 @@ impl RenderGraphPass for TrianglePass { &self, graph: &mut crate::render::graph::RenderGraph, ) -> crate::render::graph::RenderGraphPassDesc { + let shader = Rc::new(Shader { + label: Some("triangle_shader".into()), + source: include_str!("../../shaders/triangle.wgsl").to_string(), + }); + let mut desc = RenderGraphPassDesc::new( graph.next_id(), "TrianglePass", RenderPassType::Render, - Some(RenderGraphPipelineInfo::new( - "TriangleShader", - include_str!("../../shaders/triangle.wgsl"), - )), + Some(RenderPipelineDescriptor { + label: Some("triangle_pipeline".into()), + layouts: vec![], + push_constant_ranges: vec![], + vertex: VertexState { + module: shader.clone(), + entry_point: "vs_main".into(), + buffers: vec![], + }, + fragment: Some(FragmentState { + module: shader, + entry_point: "fs_main".into(), + targets: vec![Some(wgpu::ColorTargetState { + format: graph.surface_config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + depth_stencil: None, + primitive: wgpu::PrimitiveState::default(), + multisample: wgpu::MultisampleState::default(), + multiview: None, + }), ); - desc.add_texture_view_slot(graph.next_id(), "window_texture_view", SlotAttribute::Input, None); + desc.add_texture_view_slot( + graph.next_id(), + "window_texture_view", + SlotAttribute::Input, + None, + ); /* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { label: Some("B_ScreenSize".to_string()), @@ -64,7 +98,10 @@ impl RenderGraphPass for TrianglePass { desc: &crate::render::graph::RenderGraphPassDesc, context: &mut crate::render::graph::RenderGraphContext, ) { - let view = graph.slot_value(graph.slot_id("window_texture_view").unwrap()).unwrap().as_texture_view(); + let view = graph + .slot_value(graph.slot_id("window_texture_view").unwrap()) + .unwrap() + .as_texture_view(); let encoder = &mut context.encoder; let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { diff --git a/lyra-game/src/render/mod.rs b/lyra-game/src/render/mod.rs index 62e587e..7e29e21 100755 --- a/lyra-game/src/render/mod.rs +++ b/lyra-game/src/render/mod.rs @@ -1,7 +1,5 @@ pub mod renderer; -pub mod render_pipeline; -pub mod compute_pipeline; -pub mod pipeline; +pub mod resource; pub mod vertex; pub mod desc_buf_lay; pub mod render_buffer; diff --git a/lyra-game/src/render/render_pipeline.rs b/lyra-game/src/render/render_pipeline.rs deleted file mode 100755 index 8a2f8c3..0000000 --- a/lyra-game/src/render/render_pipeline.rs +++ /dev/null @@ -1,90 +0,0 @@ -use std::{collections::HashMap, ops::Deref}; - -use wgpu::{PipelineLayout, VertexBufferLayout, BindGroupLayout}; - -use super::texture::RenderTexture; - -pub struct RenderPipeline { - layout: PipelineLayout, - wgpu_pipeline: wgpu::RenderPipeline, -} - -impl Deref for RenderPipeline { - type Target = wgpu::RenderPipeline; - - fn deref(&self) -> &Self::Target { - &self.wgpu_pipeline - } -} - -impl RenderPipeline { - /// Creates the default render pipeline - pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec, bind_group_layouts: Vec<&BindGroupLayout>) -> RenderPipeline { - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &bind_group_layouts, - push_constant_ranges: &[], - }); - - let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Render Pipeline"), - layout: Some(&render_pipeline_layout), - vertex: wgpu::VertexState { - module: shader, - entry_point: "vs_main", - buffers: &buffer_layouts, - }, - fragment: Some(wgpu::FragmentState { - module: shader, - entry_point: "fs_main", - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - /*depth_stencil: Some(wgpu::DepthStencilState { - format: RenderTexture::DEPTH_FORMAT, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Less, - stencil: wgpu::StencilState::default(), // TODO: stencil buffer - bias: wgpu::DepthBiasState::default(), - }),*/ - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - }); - - Self { - layout: render_pipeline_layout, - wgpu_pipeline: render_pipeline, - } - } - - #[inline(always)] - pub fn layout(&self) -> &PipelineLayout { - &self.layout - } - - #[inline(always)] - pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline { - &self.wgpu_pipeline - } -} \ No newline at end of file diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 56bc1c9..4567bfe 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -1,41 +1,29 @@ -use std::collections::{VecDeque, HashSet}; +use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; use std::borrow::Cow; -use glam::Vec3; -use itertools::izip; -use lyra_ecs::query::filter::{Has, Not, Or}; -use lyra_ecs::relation::{ChildOf, RelationOriginComponent}; -use lyra_ecs::{Component, Entity}; -use lyra_ecs::query::{Entities, Res, TickOf}; +use lyra_ecs::Component; use lyra_ecs::World; -use lyra_scene::{SceneGraph, WorldTransform}; +use lyra_scene::SceneGraph; use tracing::{debug, instrument, warn}; -use uuid::Uuid; -use wgpu::{BindGroupLayout, Limits}; -use wgpu::util::DeviceExt; +use wgpu::Limits; use winit::window::Window; use crate::math::Transform; use crate::render::graph::TrianglePass; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; -use crate::scene::CameraComponent; -use crate::DeltaTime; -use super::camera::{RenderCamera, CameraUniform}; -use super::desc_buf_lay::DescVertexBufferLayout; +use super::camera::CameraUniform; use super::graph::RenderGraph; use super::light::LightUniformBuffers; -use super::light_cull_compute::LightCullCompute; use super::material::Material; use super::render_buffer::BufferWrapper; use super::texture::RenderTexture; -use super::transform_buffer_storage::{TransformBuffers, TransformGroup}; -use super::vertex::Vertex; -use super::{render_pipeline::RenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; +use super::transform_buffer_storage::TransformBuffers; +use super::{resource::RenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; use lyra_resource::{gltf::Mesh, ResHandle}; diff --git a/lyra-game/src/render/compute_pipeline.rs b/lyra-game/src/render/resource/compute_pipeline.rs similarity index 100% rename from lyra-game/src/render/compute_pipeline.rs rename to lyra-game/src/render/resource/compute_pipeline.rs diff --git a/lyra-game/src/render/resource/mod.rs b/lyra-game/src/render/resource/mod.rs new file mode 100644 index 0000000..984fcaa --- /dev/null +++ b/lyra-game/src/render/resource/mod.rs @@ -0,0 +1,8 @@ +mod pipeline; +pub use pipeline::*; + +mod compute_pipeline; +pub use compute_pipeline::*; + +mod render_pipeline; +pub use render_pipeline::*; \ No newline at end of file diff --git a/lyra-game/src/render/pipeline.rs b/lyra-game/src/render/resource/pipeline.rs similarity index 100% rename from lyra-game/src/render/pipeline.rs rename to lyra-game/src/render/resource/pipeline.rs diff --git a/lyra-game/src/render/resource/render_pipeline.rs b/lyra-game/src/render/resource/render_pipeline.rs new file mode 100755 index 0000000..20629b3 --- /dev/null +++ b/lyra-game/src/render/resource/render_pipeline.rs @@ -0,0 +1,228 @@ +use std::{num::NonZeroU32, ops::Deref, rc::Rc}; + +use wgpu::{BindGroupLayout, PipelineLayout}; + +#[derive(Debug, Default, Clone)] +pub struct VertexBufferLayout { + pub array_stride: wgpu::BufferAddress, + pub step_mode: wgpu::VertexStepMode, + pub attributes: Vec, +} + +/// Describes the vertex stage in a render pipeline. +#[derive(Debug, Clone)] +pub struct VertexState { + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub module: Rc, + /// The entry point in the compiled shader. + /// There must be a function in the shader with the same name. + pub entry_point: String, + /// The format of the vertex buffers used with this pipeline. + pub buffers: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Shader { + pub label: Option, + pub source: String, +} + +/// Describes the fragment stage in the render pipeline. +#[derive(Debug, Clone)] +pub struct FragmentState { + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub module: Rc, + /// The entry point in the compiled shader. + /// There must be a function in the shader with the same name. + pub entry_point: String, + /// The color state of the render targets. + pub targets: Vec>, +} + +//#[derive(Debug, Clone)] +pub struct RenderPipelineDescriptor { + pub label: Option, + pub layouts: Vec, + pub push_constant_ranges: Vec, + pub vertex: VertexState, + pub fragment: Option, + pub primitive: wgpu::PrimitiveState, + pub depth_stencil: Option, + pub multisample: wgpu::MultisampleState, + pub multiview: Option, +} + +impl RenderPipelineDescriptor { + /// Create the [`wgpu::PipelineLayout`] for this pipeline + pub(crate) fn create_layout(&self, device: &wgpu::Device) -> wgpu::PipelineLayout { + let bgs = self.layouts.iter().collect::>(); + + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)), + bind_group_layouts: &bgs, + push_constant_ranges: &self.push_constant_ranges, + }); + + todo!() + } + + /* fn as_wgpu<'a>(&'a self, device: &wgpu::Device, layout: Option<&'a wgpu::PipelineLayout>) -> wgpu::RenderPipelineDescriptor<'a> { + let vbuffers = self.vertex.buffers.iter().map(|vbl| { + wgpu::VertexBufferLayout { + array_stride: vbl.array_stride, + step_mode: vbl.step_mode, + attributes: &vbl.attributes + } + }).collect::>(); + let vmodule = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: self.vertex.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&self.vertex.module.source)), + }); + let vstate = wgpu::VertexState { + module: &vmodule, + entry_point: &self.vertex.entry_point, + buffers: &vbuffers, + }; + + let fmodule = self.fragment.as_ref().map(|f| { + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: f.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), + }) + }); + + let fstate = self.fragment.as_ref().map(move |f| { + wgpu::FragmentState { + module: fmodule.as_ref().unwrap(), + entry_point: &f.entry_point, + targets: &f.targets, + } + }); + + wgpu::RenderPipelineDescriptor { + label: self.label.as_deref(), + layout, + vertex: vstate, + primitive: self.primitive, + depth_stencil: self.depth_stencil, + multisample: self.multisample, + fragment: fstate, + multiview: self.multiview, + } + } */ +} + +pub struct RenderPipeline { + layout: Option, + wgpu_pipeline: wgpu::RenderPipeline, +} + +impl Deref for RenderPipeline { + type Target = wgpu::RenderPipeline; + + fn deref(&self) -> &Self::Target { + &self.wgpu_pipeline + } +} + +impl From for RenderPipeline { + fn from(value: wgpu::RenderPipeline) -> Self { + Self { + layout: None, + wgpu_pipeline: value, + } + } +} + +impl RenderPipeline { + /// Creates the default render pipeline + /// + /// Parameters: + /// * `device` - The device to create the pipeline on. + /// * `config` - The surface config to use to create the pipeline. + /// * `label` - The label of the pipeline. + /// * `shader` - The compiled shader of the pipeline. + /// * `vertex_entry_point` - The entry point name of the vertex shader + pub fn create(device: &wgpu::Device, desc: &RenderPipelineDescriptor) -> RenderPipeline { + // create the layout only if bind groups layouts were specified + let layout = if !desc.layouts.is_empty() { + Some(desc.create_layout(device)) + } else { + None + }; + + let vrtx_buffs = desc + .vertex + .buffers + .iter() + .map(|vbl| wgpu::VertexBufferLayout { + array_stride: vbl.array_stride, + step_mode: vbl.step_mode, + attributes: &vbl.attributes, + }) + .collect::>(); + + // an Rc was used here so that this shader could be reused by the fragment stage if + // they share the same shader. I tried to do it without an Rc but couldn't get past + // the borrow checker + let vrtx_shad = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: desc.vertex.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + &desc.vertex.module.source, + )), + })); + let vrtx_state = wgpu::VertexState { + module: &*vrtx_shad, + entry_point: &desc.vertex.entry_point, + buffers: &vrtx_buffs, + }; + + let frag_module = desc.fragment.as_ref().map(|f| { + if f.module == desc.vertex.module { + vrtx_shad.clone() + } else { + Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: f.module.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), + })) + } + }); + + let fm = frag_module.as_ref(); + let fstate = desc.fragment.as_ref().map(move |f| wgpu::FragmentState { + module: fm.unwrap(), + entry_point: &f.entry_point, + targets: &f.targets, + }); + + let render_desc = wgpu::RenderPipelineDescriptor { + label: desc.label.as_deref(), + layout: layout.as_ref(), + vertex: vrtx_state, + primitive: desc.primitive, + depth_stencil: desc.depth_stencil.clone(), + multisample: desc.multisample, + fragment: fstate, + multiview: desc.multiview, + }; + + let render_pipeline = device.create_render_pipeline(&render_desc); + + Self { + layout, + wgpu_pipeline: render_pipeline, + } + } + + #[inline(always)] + pub fn layout(&self) -> Option<&PipelineLayout> { + self.layout.as_ref() + } + + #[inline(always)] + pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline { + &self.wgpu_pipeline + } +} diff --git a/lyra-game/src/render/shaders/simple_phong.wgsl b/lyra-game/src/render/shaders/simple_phong.wgsl new file mode 100644 index 0000000..e69de29 From cee6e44d6122dd68f9a0b0bfe100fecd94e79282 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Tue, 14 May 2024 18:46:35 -0400 Subject: [PATCH 06/20] render: support creating bindgroups for passes and updating buffers every frame --- lyra-game/src/render/graph/mod.rs | 175 +++++------------- lyra-game/src/render/graph/pass.rs | 62 +++---- lyra-game/src/render/graph/passes/triangle.rs | 68 +++++-- lyra-game/src/render/graph/slot_desc.rs | 18 +- lyra-game/src/render/render_buffer.rs | 28 ++- lyra-game/src/render/renderer.rs | 4 +- .../src/render/resource/render_pipeline.rs | 4 +- .../src/render/shaders/simple_phong.wgsl | 87 +++++++++ lyra-game/src/render/shaders/triangle.wgsl | 5 +- 9 files changed, 266 insertions(+), 185 deletions(-) diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index d975370..379e2a9 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -3,9 +3,11 @@ use std::{ cell::RefCell, collections::{HashMap, VecDeque}, ptr::NonNull, + rc::Rc, sync::Arc, }; +use lyra_ecs::World; pub use pass::*; mod passes; @@ -17,13 +19,14 @@ pub use slot_desc::*; mod execution_path; use rustc_hash::{FxHashMap, FxHashSet}; -use tracing::{debug, debug_span, instrument}; +use tracing::{debug, debug_span, instrument, trace}; use wgpu::{util::DeviceExt, RenderPass}; use self::execution_path::GraphExecutionPath; use super::{ - renderer::{BasicRenderer, Renderer}, resource::{Pipeline, RenderPipeline}, + renderer::{BasicRenderer, Renderer}, + resource::{Pipeline, RenderPipeline}, }; //#[derive(Clone)] @@ -37,11 +40,6 @@ struct ResourcedSlot { //slot: RenderPassSlot, ty: SlotType, value: SlotValue, - // will when the bind group is created - /// The id of the bind group for this slot. Becomes `Some` when the render graph creates the - /// bind group - bind_group_id: Option, - create_desc: Option, } /// Stores the pipeline and other resources it uses. @@ -54,6 +52,8 @@ pub struct PipelineResource { } pub struct RenderGraph { + device: Rc, + queue: Rc, slots: FxHashMap, slot_names: HashMap, passes: FxHashMap, @@ -70,7 +70,7 @@ pub struct RenderGraph { } impl RenderGraph { - pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self { + pub fn new(device: Rc, queue: Rc, surface_config: wgpu::SurfaceConfiguration) -> Self { let mut slots = FxHashMap::default(); let mut slot_names = HashMap::default(); @@ -79,14 +79,15 @@ impl RenderGraph { ResourcedSlot { name: "window_texture_view".to_string(), ty: SlotType::TextureView, + // this will get set in prepare stage. value: SlotValue::None, - bind_group_id: None, - create_desc: None, }, ); slot_names.insert("window_texture_view".to_string(), 0u64); Self { + device, + queue, slots, slot_names, passes: Default::default(), @@ -98,6 +99,10 @@ impl RenderGraph { } } + pub fn device(&self) -> &wgpu::Device { + &*self.device + } + pub fn next_id(&mut self) -> u64 { self.current_id += 1; self.current_id @@ -115,7 +120,7 @@ impl RenderGraph { self.passes.get(&id).map(|s| &*s.desc) } - pub fn add_pass(&mut self, pass: P) { + pub fn add_pass(&mut self, mut pass: P) { let mut desc = pass.desc(self); for slot in &mut desc.slots { @@ -124,18 +129,19 @@ impl RenderGraph { .get(&slot.name) .and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) { + // if there is a slot of the same name slot.id = *id; - if slot.desc.is_some() && other.create_desc.is_none() { - other.create_desc = slot.desc.clone(); - } + debug_assert_eq!( + slot.ty, other.ty, + "slot {} in pass {} does not match existing slot of same name", + slot.name, desc.name + ); } else { let res_slot = ResourcedSlot { name: slot.name.clone(), ty: slot.ty, value: SlotValue::None, - bind_group_id: None, - create_desc: slot.desc.clone(), }; self.slots.insert(slot.id, res_slot); @@ -155,8 +161,6 @@ impl RenderGraph { /// Creates all buffers required for the passes, also creates an internal execution path. #[instrument(skip(self, device))] pub fn setup(&mut self, device: &wgpu::Device) { - let mut later_slots = VecDeque::new(); - // For all passes, create their pipelines for pass in self.passes.values() { if let Some(pipei) = &pass.desc.pipeline_desc { @@ -174,101 +178,27 @@ impl RenderGraph { self.pipelines.insert(pass.desc.id, res); } } - - for (slot_id, slot) in &mut self.slots { - if slot.bind_group_id.is_none() && slot.create_desc.is_some() { - let s = debug_span!("res_creation", slot = slot.name); - let _e = s.enter(); - - debug!("Creating bind group for slot"); - - match &slot.create_desc { - Some(SlotDescriptor::BufferInit(bi)) => { - let label = format!("B_{}", slot.name); - let wb = bi.as_wgpu(Some(&label)); - - let buf = device.create_buffer_init(&wb); - slot.value = SlotValue::Buffer(buf); - - debug!("Created and initialized buffer for slot"); - } - Some(SlotDescriptor::Buffer(b)) => { - let label = format!("B_{}", slot.name); - let wb = b.as_wgpu(Some(&label)); - - let buf = device.create_buffer(&wb); - slot.value = SlotValue::Buffer(buf); - - debug!("Created buffer"); - } - Some(SlotDescriptor::Texture(t)) => { - let label = format!("Tex_{}", slot.name); - let wt = t.as_wgpu(Some(&label)); - - let tex = device.create_texture(&wt); - slot.value = SlotValue::Texture(tex); - - debug!("Created texture"); - } - Some(SlotDescriptor::TextureView(tv)) => { - // texture views cannot be done in this step. Not only would we run into a - // borrow checker error when trying to get the texture for the view, we - // can also not guarantee that the texture was actually created. - let tex_slot = self - .slot_names - .get(&tv.texture_label) - .expect("Failed to find texture for texture view slot"); - later_slots.push_back((*slot_id, *tex_slot)); - - debug!("Queuing slot resources for later creation"); - } - - //Some(SlotDescriptor::Sampler(b)) => {}, - //Some(SlotDescriptor::Texture(b)) => {}, - Some(SlotDescriptor::None) => {} - None => {} - _ => todo!(), - } - } - } - - // create buffers for the queued slots - while let Some((lower_id, upper_id)) = later_slots.pop_front() { - let many = self.slots.get_many_mut([&lower_id, &upper_id]).unwrap(); - let (lower_slot, upper_slot) = match many { - [a, b] => (a, b), - }; - - let s = debug_span!("deferred_res_creation", slot = lower_slot.name); - let _e = s.enter(); - - match &lower_slot.create_desc { - Some(SlotDescriptor::TextureView(tv)) => { - let label = format!("TexView_{}", lower_slot.name); - - let wt = tv.as_wgpu(Some(&label)); - let tex = upper_slot.value.as_texture(); - - let texview = tex.create_view(&wt); - lower_slot.value = SlotValue::TextureView(texview); - - debug!(slot = lower_slot.name, "Created texture view"); - } - Some(SlotDescriptor::None) => {} - None => {} - _ => unreachable!("this slot should not have been put into the do later list"), - } - } } - pub fn prepare(&mut self) { + #[instrument(skip(self, world))] + pub fn prepare(&mut self, world: &mut World) { + // prepare all passes + let mut context = RenderGraphContext::new(&self.queue, None); + for (_, pass) in &mut self.passes { + let mut inner = pass.inner.borrow_mut(); + inner.prepare(world, &mut context); + } + + // create the execution path for the graph. This will be executed in `RenderGraph::render` let builtin = { let mut h = FxHashSet::default(); h.insert(0u64); h }; let descs = self.passes.values().map(|p| &*p.desc).collect(); - self.exec_path = Some(GraphExecutionPath::new(builtin, descs)); + let path = GraphExecutionPath::new(builtin, descs); + trace!("Found {} steps in the rendergraph to execute", path.queue.len()); + self.exec_path = Some(path); } pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, surface: &wgpu::Surface) { @@ -298,12 +228,12 @@ impl RenderGraph { let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(&label), }); - let mut context = RenderGraphContext::new(encoder, queue); + let mut context = RenderGraphContext::new(queue, Some(encoder)); let mut inner = pass_inn.borrow_mut(); inner.execute(self, &*pass_desc, &mut context); - encoders.push(context.encoder.finish()); + encoders.push(context.encoder.unwrap().finish()); } queue.submit(encoders.into_iter()); @@ -327,23 +257,12 @@ impl RenderGraph { #[inline(always)] pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { - self.slots.get(&id).and_then(|s| { - self.bind_groups.get( - &s.bind_group_id - .expect("Slot bind group has not been created yet"), - ) - }) + todo!() } #[inline(always)] pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup { - let bg_id = self - .slots - .get(&id) - .expect("unknown slot id") - .bind_group_id - .expect("Slot bind group has not been created yet"); - self.bind_group(id) + todo!() } } @@ -355,14 +274,14 @@ pub(crate) struct BufferWrite { pub struct RenderGraphContext<'a> { /// Becomes None when the encoder is submitted - pub(crate) encoder: wgpu::CommandEncoder, + pub(crate) encoder: Option, pub(crate) queue: &'a wgpu::Queue, pub(crate) buffer_writes: Vec, renderpass_desc: Vec>, } impl<'a> RenderGraphContext<'a> { - pub fn new(encoder: wgpu::CommandEncoder, queue: &'a wgpu::Queue) -> Self { + pub fn new(queue: &'a wgpu::Queue, encoder: Option) -> Self { Self { encoder, queue, @@ -375,11 +294,19 @@ impl<'a> RenderGraphContext<'a> { &'a mut self, desc: wgpu::RenderPassDescriptor<'a, 'a>, ) -> wgpu::RenderPass { - self.encoder.begin_render_pass(&desc) + self.encoder + .as_mut() + .expect("RenderGraphContext is missing a command encoder. This is likely \ + because you are trying to run render commands in the prepare stage.") + .begin_render_pass(&desc) } pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass { - self.encoder.begin_compute_pass(desc) + self.encoder + .as_mut() + .expect("RenderGraphContext is missing a command encoder. This is likely \ + because you are trying to run render commands in the prepare stage.") + .begin_compute_pass(desc) } pub fn write_buffer(&mut self, target_slot: &str, bytes: &[u8]) { diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 0ae51b1..5aa7e83 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -30,7 +30,7 @@ pub enum SlotValue { TextureView(wgpu::TextureView), Sampler(wgpu::Sampler), Texture(wgpu::Texture), - Buffer(wgpu::Buffer), + Buffer(Rc), } impl SlotValue { @@ -49,19 +49,6 @@ impl SlotValue { } } } - -#[derive(Clone, Debug)] -pub enum SlotDescriptor { - /// Most likely this slot is an input, so it doesn't need to specify the descriptor. - None, - - TextureView(TextureViewDescriptor), - Sampler(SamplerDescriptor), - Texture(TextureDescriptor), - Buffer(BufferDescriptor), - BufferInit(BufferInitDescriptor), -} - #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SlotAttribute { Input, @@ -76,7 +63,7 @@ pub struct RenderPassSlot { pub name: String, /// The descriptor of the slot value. /// This will be `None` if this slot is an input. - pub desc: Option, + pub value: Option>, } #[derive(Clone)] @@ -171,14 +158,14 @@ impl RenderGraphPassDesc { id: u64, name: &str, attribute: SlotAttribute, - desc: Option, + value: Option, ) { debug_assert!( matches!( - desc, - None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) + value, + None | Some(SlotValue::Buffer(_)) ), - "slot descriptor does not match the type of slot" + "slot value is not a buffer" ); let slot = RenderPassSlot { @@ -186,7 +173,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::Buffer, attribute, - desc, + value: value.map(|v| Rc::new(v)), }; self.add_slot(slot); } @@ -197,11 +184,14 @@ impl RenderGraphPassDesc { id: u64, name: &str, attribute: SlotAttribute, - desc: Option, + value: Option, ) { debug_assert!( - matches!(desc, None | Some(SlotDescriptor::Texture(_))), - "slot descriptor does not match the type of slot" + matches!( + value, + None | Some(SlotValue::Texture(_)) + ), + "slot value is not a texture" ); let slot = RenderPassSlot { @@ -209,7 +199,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::Texture, attribute, - desc, + value: value.map(|v| Rc::new(v)), }; self.add_slot(slot); } @@ -220,11 +210,14 @@ impl RenderGraphPassDesc { id: u64, name: &str, attribute: SlotAttribute, - desc: Option, + value: Option, ) { debug_assert!( - matches!(desc, None | Some(SlotDescriptor::TextureView(_))), - "slot descriptor does not match the type of slot" + matches!( + value, + None | Some(SlotValue::TextureView(_)) + ), + "slot value is not a texture view" ); let slot = RenderPassSlot { @@ -232,7 +225,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::TextureView, attribute, - desc, + value: value.map(|v| Rc::new(v)), }; self.add_slot(slot); } @@ -243,11 +236,14 @@ impl RenderGraphPassDesc { id: u64, name: &str, attribute: SlotAttribute, - desc: Option, + value: Option, ) { debug_assert!( - matches!(desc, None | Some(SlotDescriptor::Sampler(_))), - "slot descriptor does not match the type of slot" + matches!( + value, + None | Some(SlotValue::Sampler(_)) + ), + "slot value is not a sampler" ); let slot = RenderPassSlot { @@ -255,7 +251,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::Sampler, attribute, - desc, + value: value.map(|v| Rc::new(v)), }; self.add_slot(slot); } @@ -279,7 +275,7 @@ pub trait RenderGraphPass: 'static { /// Create a render pass describer. /// /// The `id` argument is passed as mutable so you can increment it as you use it for new slots. - fn desc<'a, 'b>(&'a self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc; + fn desc<'a, 'b>(&'a mut self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc; fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext); fn execute( diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 1ab2a62..6958536 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -8,21 +8,26 @@ use crate::{ render::{ camera::{CameraUniform, RenderCamera}, graph::{ - BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext, RenderGraphPass, - RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType, SlotAttribute, - SlotDescriptor, + BufferDescriptor, BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext, + RenderGraphPass, RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType, + SlotAttribute, SlotValue, }, + render_buffer::BufferWrapper, renderer::ScreenSize, resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState}, }, - scene::CameraComponent, + scene::CameraComponent, DeltaTime, }; /// Supplies some basic things other passes needs. /// /// screen size buffer, camera buffer, #[derive(Default)] -pub struct TrianglePass; +pub struct TrianglePass { + color_bg: Option, + color_buf: Option>, + acc: f32, +} impl TrianglePass { pub fn new() -> Self { @@ -32,7 +37,7 @@ impl TrianglePass { impl RenderGraphPass for TrianglePass { fn desc( - &self, + &mut self, graph: &mut crate::render::graph::RenderGraph, ) -> crate::render::graph::RenderGraphPassDesc { let shader = Rc::new(Shader { @@ -40,13 +45,25 @@ impl RenderGraphPass for TrianglePass { source: include_str!("../../shaders/triangle.wgsl").to_string(), }); + let device = graph.device(); + + let (color_bgl, color_bg, color_buf, _) = BufferWrapper::builder() + .label_prefix("color") + .visibility(wgpu::ShaderStages::FRAGMENT) + .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) + .contents(&[glam::Vec4::new(0.902, 0.639, 0.451, 1.0)]) + .finish_parts(device); + let color_buf = Rc::new(color_buf); + self.color_bg = Some(color_bg); + self.color_buf = Some(color_buf.clone()); + let mut desc = RenderGraphPassDesc::new( graph.next_id(), "TrianglePass", RenderPassType::Render, Some(RenderPipelineDescriptor { label: Some("triangle_pipeline".into()), - layouts: vec![], + layouts: vec![color_bgl], push_constant_ranges: vec![], vertex: VertexState { module: shader.clone(), @@ -76,21 +93,28 @@ impl RenderGraphPass for TrianglePass { None, ); - /* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { - label: Some("B_ScreenSize".to_string()), - contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }))); - desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { - label: Some("B_Camera".to_string()), - contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }))); */ + desc.add_buffer_slot( + graph.next_id(), + "color_buffer", + SlotAttribute::Output, + Some(SlotValue::Buffer(color_buf)), + ); desc } - fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {} + fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) { + const SPEED: f32 = 1.5; + + let dt = **world.get_resource::(); + self.acc += dt; + let x = (self.acc * SPEED).sin(); + let y = ((self.acc + 2.15) * SPEED).sin(); + let z = ((self.acc + 5.35) * SPEED).sin(); + + let color = glam::Vec4::new(x, y, z, 1.0); + context.queue.write_buffer(self.color_buf.as_ref().unwrap(), 0, bytemuck::bytes_of(&color)); + } fn execute( &mut self, @@ -102,7 +126,12 @@ impl RenderGraphPass for TrianglePass { .slot_value(graph.slot_id("window_texture_view").unwrap()) .unwrap() .as_texture_view(); - let encoder = &mut context.encoder; + let encoder = context.encoder.as_mut().unwrap(); + + //context.queue.write_buffer(buffer, offset, data) + + //let color_bg = graph.bind_group(graph.slot_id("color_buffer").unwrap()); + let color_bg = self.color_bg.as_ref().unwrap(); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("TrianglePass"), @@ -127,6 +156,7 @@ impl RenderGraphPass for TrianglePass { let pipeline = graph.pipeline(desc.id); pass.set_pipeline(&pipeline.as_render()); + pass.set_bind_group(0, color_bg, &[]); pass.draw(0..3, 0..1); } } diff --git a/lyra-game/src/render/graph/slot_desc.rs b/lyra-game/src/render/graph/slot_desc.rs index 51ac703..5c56eab 100644 --- a/lyra-game/src/render/graph/slot_desc.rs +++ b/lyra-game/src/render/graph/slot_desc.rs @@ -1,4 +1,4 @@ -use std::num::{NonZeroU32, NonZeroU8}; +use std::{mem, num::{NonZeroU32, NonZeroU8}}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct TextureViewDescriptor { @@ -162,6 +162,14 @@ pub struct BufferDescriptor { } impl BufferDescriptor { + pub fn new(usage: wgpu::BufferUsages, mapped_at_creation: bool) -> Self { + Self { + size: mem::size_of::() as _, + usage, + mapped_at_creation, + } + } + pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::BufferDescriptor<'a> { wgpu::BufferDescriptor { label, @@ -184,6 +192,14 @@ pub struct BufferInitDescriptor { } impl BufferInitDescriptor { + pub fn new(label: Option<&str>, data: &T, usage: wgpu::BufferUsages) -> Self { + Self { + label: label.map(|s| s.to_string()), + contents: bytemuck::bytes_of(data).to_vec(), + usage, + } + } + pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::util::BufferInitDescriptor<'a> { wgpu::util::BufferInitDescriptor { label, diff --git a/lyra-game/src/render/render_buffer.rs b/lyra-game/src/render/render_buffer.rs index 794e09e..ed40980 100755 --- a/lyra-game/src/render/render_buffer.rs +++ b/lyra-game/src/render/render_buffer.rs @@ -134,6 +134,15 @@ impl BufferWrapper { "BufferWrapper is missing bindgroup pair! Cannot set bind group on RenderPass!", ).bindgroup } + + /// Take the bind group layout, the bind group, and the buffer out of the wrapper. + pub fn parts(self) -> (Option>, Option, wgpu::Buffer) { + if let Some(pair) = self.bindgroup_pair { + (Some(pair.layout), Some(pair.bindgroup), self.inner_buf) + } else { + (None, None, self.inner_buf) + } + } } /// Struct used for building a BufferWrapper @@ -221,7 +230,7 @@ impl BufferWrapperBuilder { /// * `contents` - The contents to initialize the buffer with. /// /// If a field is missing, a panic will occur. - pub fn finish(self, device: &wgpu::Device) -> BufferWrapper { + pub fn finish_parts(self, device: &wgpu::Device) -> (wgpu::BindGroupLayout, wgpu::BindGroup, wgpu::Buffer, usize) { let buf_usage = self.buffer_usage.expect("Buffer usage was not set"); let buffer = if let Some(contents) = self.contents.as_ref() { device.create_buffer_init( @@ -293,10 +302,25 @@ impl BufferWrapperBuilder { } }; - BufferWrapper { + /* BufferWrapper { bindgroup_pair: Some(bg_pair), inner_buf: buffer, len: Some(self.count.unwrap_or_default() as usize), + } */ + + (Rc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize) + } + + pub fn finish(self, device: &wgpu::Device) -> BufferWrapper { + let (bgl, bg, buff, len) = self.finish_parts(device); + + BufferWrapper { + bindgroup_pair: Some(BindGroupPair { + layout: Rc::new(bgl), + bindgroup: bg + }), + inner_buf: buff, + len: Some(len), } } } diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 4567bfe..290bd42 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -214,7 +214,7 @@ impl BasicRenderer { let queue = Rc::new(queue); //let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); - let mut g = RenderGraph::new(config.clone()); + let mut g = RenderGraph::new(device.clone(), queue.clone(), config.clone()); /* debug!("Adding base pass"); g.add_pass(TrianglePass::new()); debug!("Adding depth pre-pass"); @@ -265,7 +265,7 @@ impl BasicRenderer { impl Renderer for BasicRenderer { #[instrument(skip(self, main_world))] fn prepare(&mut self, main_world: &mut World) { - self.graph.prepare(); + self.graph.prepare(main_world); } #[instrument(skip(self))] diff --git a/lyra-game/src/render/resource/render_pipeline.rs b/lyra-game/src/render/resource/render_pipeline.rs index 20629b3..e3ab1d5 100755 --- a/lyra-game/src/render/resource/render_pipeline.rs +++ b/lyra-game/src/render/resource/render_pipeline.rs @@ -63,9 +63,7 @@ impl RenderPipelineDescriptor { label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)), bind_group_layouts: &bgs, push_constant_ranges: &self.push_constant_ranges, - }); - - todo!() + }) } /* fn as_wgpu<'a>(&'a self, device: &wgpu::Device, layout: Option<&'a wgpu::PipelineLayout>) -> wgpu::RenderPipelineDescriptor<'a> { diff --git a/lyra-game/src/render/shaders/simple_phong.wgsl b/lyra-game/src/render/shaders/simple_phong.wgsl index e69de29..aa9f8a9 100644 --- a/lyra-game/src/render/shaders/simple_phong.wgsl +++ b/lyra-game/src/render/shaders/simple_phong.wgsl @@ -0,0 +1,87 @@ +// Vertex shader + +const max_light_count: u32 = 16u; + +const LIGHT_TY_DIRECTIONAL = 0u; +const LIGHT_TY_POINT = 1u; +const LIGHT_TY_SPOT = 2u; + +const ALPHA_CUTOFF = 0.1; + +struct VertexInput { + @location(0) position: vec3, + @location(1) tex_coords: vec2, + @location(2) normal: vec3, +} + +struct VertexOutput { + @builtin(position) clip_position: vec4, + @location(0) tex_coords: vec2, + @location(1) world_position: vec3, + @location(2) world_normal: vec3, +} + +struct TransformData { + transform: mat4x4, + normal_matrix: mat4x4, +} + +struct CameraUniform { + view: mat4x4, + inverse_projection: mat4x4, + view_projection: mat4x4, + projection: mat4x4, + position: vec3, + tile_debug: u32, +}; + +@group(1) @binding(0) +var u_model_transform_data: TransformData; + +@group(2) @binding(0) +var u_camera: CameraUniform; + +@vertex +fn vs_main( + model: VertexInput, +) -> VertexOutput { + var out: VertexOutput; + + out.tex_coords = model.tex_coords; + out.clip_position = u_camera.view_projection * u_model_transform_data.transform * vec4(model.position, 1.0); + + // the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36 + let normal_mat4 = u_model_transform_data.normal_matrix; + let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz); + out.world_normal = normalize(normal_mat * model.normal, ); + + var world_position: vec4 = u_model_transform_data.transform * vec4(model.position, 1.0); + out.world_position = world_position.xyz; + + return out; +} + +// Fragment shader + +struct Material { + ambient: vec4, + diffuse: vec4, + specular: vec4, + shininess: f32, +} + +@group(0) @binding(0) +var t_diffuse: texture_2d; +@group(0) @binding(1) +var s_diffuse: sampler; + +@fragment +fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); + + if (object_color.a < ALPHA_CUTOFF) { + discard; + } + + return object_color; +} \ No newline at end of file diff --git a/lyra-game/src/render/shaders/triangle.wgsl b/lyra-game/src/render/shaders/triangle.wgsl index f5a0eae..171ddc0 100644 --- a/lyra-game/src/render/shaders/triangle.wgsl +++ b/lyra-game/src/render/shaders/triangle.wgsl @@ -2,6 +2,9 @@ struct VertexOutput { @builtin(position) clip_position: vec4, }; +@group(0) @binding(0) +var u_triangle_color: vec4; + @vertex fn vs_main( @builtin(vertex_index) in_vertex_index: u32, @@ -15,5 +18,5 @@ fn vs_main( @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return vec4(0.3, 0.2, 0.1, 1.0); + return vec4(u_triangle_color.xyz, 1.0); } \ No newline at end of file From 64e6e4a942c0fedec9a730587d9eead6c9c71a3e Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 17 May 2024 17:43:46 -0400 Subject: [PATCH 07/20] render: make it easier to share bind groups and bind group layouts between passes --- lyra-game/src/render/graph/mod.rs | 136 +++++++++++++----- lyra-game/src/render/graph/pass.rs | 64 +++++---- lyra-game/src/render/graph/passes/triangle.rs | 35 ++--- .../src/render/resource/render_pipeline.rs | 55 +------ 4 files changed, 158 insertions(+), 132 deletions(-) diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 379e2a9..bd9b08d 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -2,7 +2,6 @@ mod pass; use std::{ cell::RefCell, collections::{HashMap, VecDeque}, - ptr::NonNull, rc::Rc, sync::Arc, }; @@ -19,15 +18,11 @@ pub use slot_desc::*; mod execution_path; use rustc_hash::{FxHashMap, FxHashSet}; -use tracing::{debug, debug_span, instrument, trace}; -use wgpu::{util::DeviceExt, RenderPass}; +use tracing::{debug_span, instrument, trace}; use self::execution_path::GraphExecutionPath; -use super::{ - renderer::{BasicRenderer, Renderer}, - resource::{Pipeline, RenderPipeline}, -}; +use super::resource::{Pipeline, RenderPipeline}; //#[derive(Clone)] struct PassEntry { @@ -35,6 +30,14 @@ struct PassEntry { desc: Arc, } +pub struct BindGroupEntry { + pub name: String, + /// BindGroup + pub bg: Rc, + /// BindGroupLayout + pub layout: Option>, +} + struct ResourcedSlot { name: String, //slot: RenderPassSlot, @@ -58,7 +61,8 @@ pub struct RenderGraph { slot_names: HashMap, passes: FxHashMap, // TODO: Use a SlotMap - bind_groups: FxHashMap, + bind_groups: FxHashMap, + bind_group_names: FxHashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. /// @@ -70,7 +74,11 @@ pub struct RenderGraph { } impl RenderGraph { - pub fn new(device: Rc, queue: Rc, surface_config: wgpu::SurfaceConfiguration) -> Self { + pub fn new( + device: Rc, + queue: Rc, + surface_config: wgpu::SurfaceConfiguration, + ) -> Self { let mut slots = FxHashMap::default(); let mut slot_names = HashMap::default(); @@ -92,6 +100,7 @@ impl RenderGraph { slot_names, passes: Default::default(), bind_groups: Default::default(), + bind_group_names: Default::default(), pipelines: Default::default(), current_id: 1, exec_path: None, @@ -129,19 +138,19 @@ impl RenderGraph { .get(&slot.name) .and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) { - // if there is a slot of the same name - slot.id = *id; - debug_assert_eq!( slot.ty, other.ty, "slot {} in pass {} does not match existing slot of same name", slot.name, desc.name ); + + // if there is a slot of the same name + slot.id = *id; } else { let res_slot = ResourcedSlot { name: slot.name.clone(), ty: slot.ty, - value: SlotValue::None, + value: slot.value.clone().unwrap_or(SlotValue::None), }; self.slots.insert(slot.id, res_slot); @@ -149,6 +158,19 @@ impl RenderGraph { } } + for (name, bg, bgl) in &desc.bind_groups { + let bg_id = self.next_id(); + self.bind_groups.insert( + bg_id, + BindGroupEntry { + name: name.clone(), + bg: bg.clone(), + layout: bgl.clone(), + }, + ); + self.bind_group_names.insert(name.clone(), bg_id); + } + self.passes.insert( desc.id, PassEntry { @@ -189,6 +211,28 @@ impl RenderGraph { inner.prepare(world, &mut context); } + // Queue all buffer writes to the gpu + { + let s = debug_span!("queue_buffer_writes"); + let _e = s.enter(); + + while let Some(bufwr) = context.buffer_writes.pop_front() { + let slot = self + .slots + .get(&self.slot_id(&bufwr.target_slot).unwrap()) + .expect(&format!( + "Failed to find slot '{}' for buffer write", + bufwr.target_slot + )); + let buf = slot + .value + .as_buffer() + .expect(&format!("Slot '{}' is not a buffer", bufwr.target_slot)); + + self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes); + } + } + // create the execution path for the graph. This will be executed in `RenderGraph::render` let builtin = { let mut h = FxHashSet::default(); @@ -197,7 +241,10 @@ impl RenderGraph { }; let descs = self.passes.values().map(|p| &*p.desc).collect(); let path = GraphExecutionPath::new(builtin, descs); - trace!("Found {} steps in the rendergraph to execute", path.queue.len()); + trace!( + "Found {} steps in the rendergraph to execute", + path.queue.len() + ); self.exec_path = Some(path); } @@ -216,7 +263,7 @@ impl RenderGraph { "window_texture_view", "unexpected slot where 'window_texture_view' should be" ); - window_tv_slot.value = SlotValue::TextureView(view); + window_tv_slot.value = SlotValue::TextureView(Rc::new(view)); let mut encoders = vec![]; while let Some(pass_id) = path.queue.pop_front() { @@ -246,37 +293,45 @@ impl RenderGraph { } #[inline(always)] - pub fn try_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { - self.bind_groups.get(&id) + pub fn try_bind_group(&self, id: u64) -> Option<&Rc> { + self.bind_groups.get(&id).map(|e| &e.bg) } #[inline(always)] - pub fn bind_group(&self, id: u64) -> &wgpu::BindGroup { + pub fn bind_group(&self, id: u64) -> &Rc { self.try_bind_group(id).expect("Unknown id for bind group") } #[inline(always)] - pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { - todo!() + pub fn try_bind_group_layout(&self, id: u64) -> Option<&Rc> { + self.bind_groups.get(&id).and_then(|e| e.layout.as_ref()) } #[inline(always)] - pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup { - todo!() + pub fn bind_group_layout(&self, id: u64) -> &Rc { + self.try_bind_group_layout(id) + .expect("Unknown id for bind group layout") + } + + #[inline(always)] + pub fn bind_group_id(&self, name: &str) -> Option { + self.bind_group_names.get(name).copied() } } pub(crate) struct BufferWrite { /// The name of the slot that has the resource that will be written target_slot: String, + offset: u64, bytes: Vec, } +#[allow(dead_code)] pub struct RenderGraphContext<'a> { /// Becomes None when the encoder is submitted pub(crate) encoder: Option, pub(crate) queue: &'a wgpu::Queue, - pub(crate) buffer_writes: Vec, + pub(crate) buffer_writes: VecDeque, renderpass_desc: Vec>, } @@ -285,7 +340,7 @@ impl<'a> RenderGraphContext<'a> { Self { encoder, queue, - buffer_writes: vec![], + buffer_writes: Default::default(), renderpass_desc: vec![], } } @@ -296,28 +351,43 @@ impl<'a> RenderGraphContext<'a> { ) -> wgpu::RenderPass { self.encoder .as_mut() - .expect("RenderGraphContext is missing a command encoder. This is likely \ - because you are trying to run render commands in the prepare stage.") + .expect( + "RenderGraphContext is missing a command encoder. This is likely \ + because you are trying to run render commands in the prepare stage.", + ) .begin_render_pass(&desc) } pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass { self.encoder .as_mut() - .expect("RenderGraphContext is missing a command encoder. This is likely \ - because you are trying to run render commands in the prepare stage.") + .expect( + "RenderGraphContext is missing a command encoder. This is likely \ + because you are trying to run render commands in the prepare stage.", + ) .begin_compute_pass(desc) } - pub fn write_buffer(&mut self, target_slot: &str, bytes: &[u8]) { - //self.queue.write_buffer(buffer, offset, data) - self.buffer_writes.push(BufferWrite { + /// Queue a data write to a buffer at that is contained in `target_slot`. + /// + /// This does not submit the data to the GPU immediately, or add it to the `wgpu::Queue`. The + /// data will be submitted to the GPU queue right after the prepare stage for all passes + /// is ran. + pub fn queue_buffer_write(&mut self, target_slot: &str, offset: u64, bytes: &[u8]) { + self.buffer_writes.push_back(BufferWrite { target_slot: target_slot.to_string(), + offset, bytes: bytes.to_vec(), }) } - pub fn write_buffer_muck(&mut self, target_slot: &str, bytes: T) { - self.write_buffer(target_slot, bytemuck::bytes_of(&bytes)); + /// Write + pub fn queue_buffer_write_with( + &mut self, + target_slot: &str, + offset: u64, + bytes: T, + ) { + self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes)); } } diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 5aa7e83..6c134f3 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -4,10 +4,7 @@ use lyra_ecs::World; use crate::render::resource::RenderPipelineDescriptor; -use super::{ - BufferDescriptor, BufferInitDescriptor, RenderGraph, RenderGraphContext, SamplerDescriptor, - TextureDescriptor, TextureViewDescriptor, -}; +use super::{RenderGraph, RenderGraphContext}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RenderPassType { @@ -24,12 +21,12 @@ pub enum SlotType { Buffer, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum SlotValue { None, - TextureView(wgpu::TextureView), - Sampler(wgpu::Sampler), - Texture(wgpu::Texture), + TextureView(Rc), + Sampler(Rc), + Texture(Rc), Buffer(Rc), } @@ -37,17 +34,24 @@ impl SlotValue { /// Gets `self` as a texture, panics if self is not a instance of [`SlotValue::Texture`]. pub fn as_texture(&self) -> &wgpu::Texture { match self { - Self::Texture(t) => t, + Self::Texture(v) => v, _ => panic!("self is not an instance of SlotValue::Texture"), } } pub fn as_texture_view(&self) -> &wgpu::TextureView { match self { - Self::TextureView(t) => t, + Self::TextureView(v) => v, _ => panic!("self is not an instance of SlotValue::TextureView"), } } + + pub fn as_buffer(&self) -> Option<&wgpu::Buffer> { + match self { + Self::Buffer(v) => Some(v), + _ => None, + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SlotAttribute { @@ -63,7 +67,7 @@ pub struct RenderPassSlot { pub name: String, /// The descriptor of the slot value. /// This will be `None` if this slot is an input. - pub value: Option>, + pub value: Option, } #[derive(Clone)] @@ -128,6 +132,11 @@ pub struct RenderGraphPassDesc { pub slots: Vec, slot_names: HashMap, pub pipeline_desc: Option, + pub bind_groups: Vec<( + String, + Rc, + Option>, + )>, } impl RenderGraphPassDesc { @@ -136,6 +145,7 @@ impl RenderGraphPassDesc { name: &str, pass_type: RenderPassType, pipeline_desc: Option, + bind_groups: Vec<(&str, Rc, Option>)>, ) -> Self { Self { id, @@ -144,6 +154,10 @@ impl RenderGraphPassDesc { slots: vec![], slot_names: HashMap::default(), pipeline_desc, + bind_groups: bind_groups + .into_iter() + .map(|bg| (bg.0.to_string(), bg.1, bg.2)) + .collect(), } } @@ -161,10 +175,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!( - value, - None | Some(SlotValue::Buffer(_)) - ), + matches!(value, None | Some(SlotValue::Buffer(_))), "slot value is not a buffer" ); @@ -173,7 +184,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::Buffer, attribute, - value: value.map(|v| Rc::new(v)), + value, }; self.add_slot(slot); } @@ -187,10 +198,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!( - value, - None | Some(SlotValue::Texture(_)) - ), + matches!(value, None | Some(SlotValue::Texture(_))), "slot value is not a texture" ); @@ -199,7 +207,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::Texture, attribute, - value: value.map(|v| Rc::new(v)), + value, }; self.add_slot(slot); } @@ -213,10 +221,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!( - value, - None | Some(SlotValue::TextureView(_)) - ), + matches!(value, None | Some(SlotValue::TextureView(_))), "slot value is not a texture view" ); @@ -225,7 +230,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::TextureView, attribute, - value: value.map(|v| Rc::new(v)), + value, }; self.add_slot(slot); } @@ -239,10 +244,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!( - value, - None | Some(SlotValue::Sampler(_)) - ), + matches!(value, None | Some(SlotValue::Sampler(_))), "slot value is not a sampler" ); @@ -251,7 +253,7 @@ impl RenderGraphPassDesc { name: name.to_string(), ty: SlotType::Sampler, attribute, - value: value.map(|v| Rc::new(v)), + value, }; self.add_slot(slot); } diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 6958536..05d4a83 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -1,22 +1,15 @@ use std::rc::Rc; -use glam::UVec2; -use tracing::warn; -use wgpu::include_wgsl; - use crate::{ render::{ - camera::{CameraUniform, RenderCamera}, graph::{ - BufferDescriptor, BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext, - RenderGraphPass, RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType, + RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, SlotValue, }, render_buffer::BufferWrapper, - renderer::ScreenSize, resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState}, }, - scene::CameraComponent, DeltaTime, + DeltaTime, }; /// Supplies some basic things other passes needs. @@ -24,8 +17,8 @@ use crate::{ /// screen size buffer, camera buffer, #[derive(Default)] pub struct TrianglePass { - color_bg: Option, - color_buf: Option>, + //color_bg: Option>, + //color_buf: Option>, acc: f32, } @@ -46,16 +39,16 @@ impl RenderGraphPass for TrianglePass { }); let device = graph.device(); - let (color_bgl, color_bg, color_buf, _) = BufferWrapper::builder() .label_prefix("color") .visibility(wgpu::ShaderStages::FRAGMENT) .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) .contents(&[glam::Vec4::new(0.902, 0.639, 0.451, 1.0)]) .finish_parts(device); - let color_buf = Rc::new(color_buf); - self.color_bg = Some(color_bg); - self.color_buf = Some(color_buf.clone()); + let color_bgl = Rc::new(color_bgl); + let color_bg = Rc::new(color_bg); + + //let color_buf = Rc::new(color_buf); let mut desc = RenderGraphPassDesc::new( graph.next_id(), @@ -63,7 +56,7 @@ impl RenderGraphPass for TrianglePass { RenderPassType::Render, Some(RenderPipelineDescriptor { label: Some("triangle_pipeline".into()), - layouts: vec![color_bgl], + layouts: vec![color_bgl.clone()], push_constant_ranges: vec![], vertex: VertexState { module: shader.clone(), @@ -84,6 +77,7 @@ impl RenderGraphPass for TrianglePass { multisample: wgpu::MultisampleState::default(), multiview: None, }), + vec![("color_bg", color_bg, Some(color_bgl))], ); desc.add_texture_view_slot( @@ -97,7 +91,7 @@ impl RenderGraphPass for TrianglePass { graph.next_id(), "color_buffer", SlotAttribute::Output, - Some(SlotValue::Buffer(color_buf)), + Some(SlotValue::Buffer(Rc::new(color_buf))), ); desc @@ -105,7 +99,7 @@ impl RenderGraphPass for TrianglePass { fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) { const SPEED: f32 = 1.5; - + let dt = **world.get_resource::(); self.acc += dt; let x = (self.acc * SPEED).sin(); @@ -113,7 +107,7 @@ impl RenderGraphPass for TrianglePass { let z = ((self.acc + 5.35) * SPEED).sin(); let color = glam::Vec4::new(x, y, z, 1.0); - context.queue.write_buffer(self.color_buf.as_ref().unwrap(), 0, bytemuck::bytes_of(&color)); + context.queue_buffer_write_with("color_buffer", 0, color); } fn execute( @@ -131,7 +125,8 @@ impl RenderGraphPass for TrianglePass { //context.queue.write_buffer(buffer, offset, data) //let color_bg = graph.bind_group(graph.slot_id("color_buffer").unwrap()); - let color_bg = self.color_bg.as_ref().unwrap(); + //let color_bg = self.color_bg.as_ref().unwrap(); + let color_bg = graph.bind_group(graph.bind_group_id("color_bg").unwrap()); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("TrianglePass"), diff --git a/lyra-game/src/render/resource/render_pipeline.rs b/lyra-game/src/render/resource/render_pipeline.rs index e3ab1d5..defb332 100755 --- a/lyra-game/src/render/resource/render_pipeline.rs +++ b/lyra-game/src/render/resource/render_pipeline.rs @@ -1,6 +1,6 @@ use std::{num::NonZeroU32, ops::Deref, rc::Rc}; -use wgpu::{BindGroupLayout, PipelineLayout}; +use wgpu::PipelineLayout; #[derive(Debug, Default, Clone)] pub struct VertexBufferLayout { @@ -44,7 +44,7 @@ pub struct FragmentState { //#[derive(Debug, Clone)] pub struct RenderPipelineDescriptor { pub label: Option, - pub layouts: Vec, + pub layouts: Vec>, pub push_constant_ranges: Vec, pub vertex: VertexState, pub fragment: Option, @@ -57,7 +57,11 @@ pub struct RenderPipelineDescriptor { impl RenderPipelineDescriptor { /// Create the [`wgpu::PipelineLayout`] for this pipeline pub(crate) fn create_layout(&self, device: &wgpu::Device) -> wgpu::PipelineLayout { - let bgs = self.layouts.iter().collect::>(); + let bgs = self + .layouts + .iter() + .map(|bg| bg.as_ref()) + .collect::>(); device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)), @@ -65,51 +69,6 @@ impl RenderPipelineDescriptor { push_constant_ranges: &self.push_constant_ranges, }) } - - /* fn as_wgpu<'a>(&'a self, device: &wgpu::Device, layout: Option<&'a wgpu::PipelineLayout>) -> wgpu::RenderPipelineDescriptor<'a> { - let vbuffers = self.vertex.buffers.iter().map(|vbl| { - wgpu::VertexBufferLayout { - array_stride: vbl.array_stride, - step_mode: vbl.step_mode, - attributes: &vbl.attributes - } - }).collect::>(); - let vmodule = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: self.vertex.module.label.as_ref().map(|s| s.as_str()), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&self.vertex.module.source)), - }); - let vstate = wgpu::VertexState { - module: &vmodule, - entry_point: &self.vertex.entry_point, - buffers: &vbuffers, - }; - - let fmodule = self.fragment.as_ref().map(|f| { - device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: f.module.label.as_ref().map(|s| s.as_str()), - source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), - }) - }); - - let fstate = self.fragment.as_ref().map(move |f| { - wgpu::FragmentState { - module: fmodule.as_ref().unwrap(), - entry_point: &f.entry_point, - targets: &f.targets, - } - }); - - wgpu::RenderPipelineDescriptor { - label: self.label.as_deref(), - layout, - vertex: vstate, - primitive: self.primitive, - depth_stencil: self.depth_stencil, - multisample: self.multisample, - fragment: fstate, - multiview: self.multiview, - } - } */ } pub struct RenderPipeline { From 8c3446389cf6293cc4104ed6979e16501a6c1c0b Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 18 May 2024 11:02:07 -0400 Subject: [PATCH 08/20] render: code cleanup --- lyra-game/src/render/graph/execution_path.rs | 4 +- lyra-game/src/render/graph/mod.rs | 54 ++++++++++++------- lyra-game/src/render/graph/passes/triangle.rs | 17 ++---- lyra-game/src/render/renderer.rs | 2 +- .../src/render/resource/compute_pipeline.rs | 2 +- 5 files changed, 42 insertions(+), 37 deletions(-) diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs index 049b7af..ab080ad 100644 --- a/lyra-game/src/render/graph/execution_path.rs +++ b/lyra-game/src/render/graph/execution_path.rs @@ -38,7 +38,7 @@ impl GraphExecutionPath { let node = Node { id: desc.id, - desc: (*desc).clone(), + desc: (*desc), slot_inputs: inputs }; nodes.insert(node.id, node); @@ -75,12 +75,14 @@ impl GraphExecutionPath { } } +#[allow(dead_code)] #[derive(Debug, Clone, Copy)] struct SlotOwnerPair { pass: u64, slot: u64, } +#[allow(dead_code)] struct Node<'a> { id: u64, desc: &'a RenderGraphPassDesc, diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index bd9b08d..043b742 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -121,17 +121,11 @@ impl RenderGraph { self.slot_names.get(name).cloned() } - pub fn slot_value(&self, id: u64) -> Option<&SlotValue> { - self.slots.get(&id).map(|s| &s.value) - } - - pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { - self.passes.get(&id).map(|s| &*s.desc) - } - + #[instrument(skip(self, pass), level = "debug")] pub fn add_pass(&mut self, mut pass: P) { let mut desc = pass.desc(self); + // collect all the slots of the pass for slot in &mut desc.slots { if let Some((id, other)) = self .slot_names @@ -158,6 +152,7 @@ impl RenderGraph { } } + // get clones of the bind groups and layouts for (name, bg, bgl) in &desc.bind_groups { let bg_id = self.next_id(); self.bind_groups.insert( @@ -211,8 +206,8 @@ impl RenderGraph { inner.prepare(world, &mut context); } - // Queue all buffer writes to the gpu { + // Queue all buffer writes to the gpu let s = debug_span!("queue_buffer_writes"); let _e = s.enter(); @@ -236,7 +231,7 @@ impl RenderGraph { // create the execution path for the graph. This will be executed in `RenderGraph::render` let builtin = { let mut h = FxHashSet::default(); - h.insert(0u64); + h.insert(0u64); // include the base pass h }; let descs = self.passes.values().map(|p| &*p.desc).collect(); @@ -248,7 +243,8 @@ impl RenderGraph { self.exec_path = Some(path); } - pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, surface: &wgpu::Surface) { + #[instrument(skip(self, surface))] + pub fn render(&mut self, surface: &wgpu::Surface) { let mut path = self.exec_path.take().unwrap(); let output = surface.get_current_texture().unwrap(); @@ -272,10 +268,13 @@ impl RenderGraph { let pass_desc = pass.desc.clone(); let label = format!("{} Encoder", pass_desc.name); - let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some(&label), - }); - let mut context = RenderGraphContext::new(queue, Some(encoder)); + let encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some(&label), + }); + let queue = self.queue.clone(); // clone is required to appease the borrow checker + let mut context = RenderGraphContext::new(&queue, Some(encoder)); let mut inner = pass_inn.borrow_mut(); inner.execute(self, &*pass_desc, &mut context); @@ -283,10 +282,22 @@ impl RenderGraph { encoders.push(context.encoder.unwrap().finish()); } - queue.submit(encoders.into_iter()); + self.queue.submit(encoders.into_iter()); output.present(); } + pub fn slot_value(&self, id: u64) -> Option<&SlotValue> { + self.slots.get(&id).map(|s| &s.value) + } + + pub fn slot_value_mut(&mut self, id: u64) -> Option<&mut SlotValue> { + self.slots.get_mut(&id).map(|s| &mut s.value) + } + + pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { + self.passes.get(&id).map(|s| &*s.desc) + } + #[inline(always)] pub fn pipeline(&self, id: u64) -> &Pipeline { &self.pipelines.get(&id).unwrap().pipeline @@ -319,7 +330,8 @@ impl RenderGraph { } } -pub(crate) struct BufferWrite { +/// A queued write to a GPU buffer targeting a graph slot. +pub(crate) struct GraphBufferWrite { /// The name of the slot that has the resource that will be written target_slot: String, offset: u64, @@ -331,7 +343,7 @@ pub struct RenderGraphContext<'a> { /// Becomes None when the encoder is submitted pub(crate) encoder: Option, pub(crate) queue: &'a wgpu::Queue, - pub(crate) buffer_writes: VecDeque, + pub(crate) buffer_writes: VecDeque, renderpass_desc: Vec>, } @@ -373,15 +385,17 @@ impl<'a> RenderGraphContext<'a> { /// This does not submit the data to the GPU immediately, or add it to the `wgpu::Queue`. The /// data will be submitted to the GPU queue right after the prepare stage for all passes /// is ran. + #[instrument(skip(self, bytes), level="trace", fields(size = bytes.len()))] pub fn queue_buffer_write(&mut self, target_slot: &str, offset: u64, bytes: &[u8]) { - self.buffer_writes.push_back(BufferWrite { + self.buffer_writes.push_back(GraphBufferWrite { target_slot: target_slot.to_string(), offset, bytes: bytes.to_vec(), }) } - /// Write + /// Queue a data write of a type that to a buffer at that is contained in `target_slot`. + #[instrument(skip(self, bytes), level="trace", fields(size = std::mem::size_of::()))] pub fn queue_buffer_write_with( &mut self, target_slot: &str, diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 05d4a83..937f03b 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -12,13 +12,9 @@ use crate::{ DeltaTime, }; -/// Supplies some basic things other passes needs. -/// -/// screen size buffer, camera buffer, +/// A demo pass that renders a triangle that changes colors every frame. #[derive(Default)] pub struct TrianglePass { - //color_bg: Option>, - //color_buf: Option>, acc: f32, } @@ -48,8 +44,6 @@ impl RenderGraphPass for TrianglePass { let color_bgl = Rc::new(color_bgl); let color_bg = Rc::new(color_bg); - //let color_buf = Rc::new(color_buf); - let mut desc = RenderGraphPassDesc::new( graph.next_id(), "TrianglePass", @@ -120,14 +114,9 @@ impl RenderGraphPass for TrianglePass { .slot_value(graph.slot_id("window_texture_view").unwrap()) .unwrap() .as_texture_view(); - let encoder = context.encoder.as_mut().unwrap(); - - //context.queue.write_buffer(buffer, offset, data) - - //let color_bg = graph.bind_group(graph.slot_id("color_buffer").unwrap()); - //let color_bg = self.color_bg.as_ref().unwrap(); let color_bg = graph.bind_group(graph.bind_group_id("color_bg").unwrap()); - + + let encoder = context.encoder.as_mut().unwrap(); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("TrianglePass"), color_attachments: &[ diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 290bd42..23f4cb6 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -270,7 +270,7 @@ impl Renderer for BasicRenderer { #[instrument(skip(self))] fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.graph.render(&self.device, &self.queue, &self.surface); + self.graph.render(&self.surface); Ok(()) } diff --git a/lyra-game/src/render/resource/compute_pipeline.rs b/lyra-game/src/render/resource/compute_pipeline.rs index d44bde4..43d37d2 100644 --- a/lyra-game/src/render/resource/compute_pipeline.rs +++ b/lyra-game/src/render/resource/compute_pipeline.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ops::Deref}; +use std::ops::Deref; use wgpu::PipelineLayout; From fc57777a456c98891f0ab0504c1035fbc890a365 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 19 May 2024 12:56:03 -0400 Subject: [PATCH 09/20] render: move render targets to be graph slots, create present passes and base passes Since the render graph no longer has default slots, base passes must be created that supply things like render targets. This also makes it easier to render offscreen to some other surface that is not the window, or just some other texture --- .vscode/launch.json | 18 +++ lyra-game/src/render/graph/execution_path.rs | 7 +- lyra-game/src/render/graph/mod.rs | 108 ++++++++---------- lyra-game/src/render/graph/pass.rs | 36 +++++- lyra-game/src/render/graph/passes/base.rs | 87 +++++++++++--- lyra-game/src/render/graph/passes/mod.rs | 9 +- .../src/render/graph/passes/present_pass.rs | 52 +++++++++ lyra-game/src/render/graph/passes/triangle.rs | 9 +- lyra-game/src/render/renderer.rs | 47 +++----- 9 files changed, 256 insertions(+), 117 deletions(-) create mode 100644 lyra-game/src/render/graph/passes/present_pass.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index 26d7b96..a69d883 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,24 @@ "args": [], "cwd": "${workspaceFolder}/examples/testbed" }, + { + "type": "lldb", + "request": "launch", + "name": "Debug example simple_scene", + "cargo": { + "args": [ + "build", + "--manifest-path", "${workspaceFolder}/examples/simple_scene/Cargo.toml" + //"--bin=testbed", + ], + "filter": { + "name": "simple_scene", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}/examples/simple_scene" + }, { "type": "lldb", "request": "launch", diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs index ab080ad..bb16ed8 100644 --- a/lyra-game/src/render/graph/execution_path.rs +++ b/lyra-game/src/render/graph/execution_path.rs @@ -11,9 +11,11 @@ pub struct GraphExecutionPath { } impl GraphExecutionPath { - pub fn new(built_in_slots: FxHashSet, pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { + pub fn new(pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { // collect all the output slots let mut total_outputs = HashMap::new(); + total_outputs.reserve(pass_descriptions.len()); + for desc in pass_descriptions.iter() { for slot in desc.output_slots() { total_outputs.insert(slot.name.clone(), SlotOwnerPair { @@ -28,9 +30,6 @@ impl GraphExecutionPath { // find the node inputs let mut inputs = vec![]; for slot in desc.input_slots() { - // If the slot is built in to the graph, no need to care about the sorting. - if built_in_slots.contains(&slot.id) { continue; } - let inp = total_outputs.get(&slot.name) .expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name)); inputs.push(*inp); diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 043b742..e485576 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -17,8 +17,8 @@ pub use slot_desc::*; mod execution_path; -use rustc_hash::{FxHashMap, FxHashSet}; -use tracing::{debug_span, instrument, trace}; +use rustc_hash::FxHashMap; +use tracing::{debug_span, instrument, trace, warn}; use self::execution_path::GraphExecutionPath; @@ -38,6 +38,7 @@ pub struct BindGroupEntry { pub layout: Option>, } +#[allow(dead_code)] struct ResourcedSlot { name: String, //slot: RenderPassSlot, @@ -54,6 +55,13 @@ pub struct PipelineResource { pub bg_layout_name_lookup: HashMap, } +#[derive(Debug)] +pub struct RenderTarget { + pub surface: wgpu::Surface, + pub surface_config: wgpu::SurfaceConfiguration, + pub current_texture: Option, +} + pub struct RenderGraph { device: Rc, queue: Rc, @@ -69,42 +77,21 @@ pub struct RenderGraph { pipelines: FxHashMap, current_id: u64, exec_path: Option, - - pub(crate) surface_config: wgpu::SurfaceConfiguration, } impl RenderGraph { - pub fn new( - device: Rc, - queue: Rc, - surface_config: wgpu::SurfaceConfiguration, - ) -> Self { - let mut slots = FxHashMap::default(); - let mut slot_names = HashMap::default(); - - slots.insert( - 0, - ResourcedSlot { - name: "window_texture_view".to_string(), - ty: SlotType::TextureView, - // this will get set in prepare stage. - value: SlotValue::None, - }, - ); - slot_names.insert("window_texture_view".to_string(), 0u64); - + pub fn new(device: Rc, queue: Rc) -> Self { Self { device, queue, - slots, - slot_names, + slots: Default::default(), + slot_names: Default::default(), passes: Default::default(), bind_groups: Default::default(), bind_group_names: Default::default(), pipelines: Default::default(), current_id: 1, exec_path: None, - surface_config, } } @@ -138,6 +125,11 @@ impl RenderGraph { slot.name, desc.name ); + trace!( + "Found existing slot for {}, changing id to {}", + slot.name, id + ); + // if there is a slot of the same name slot.id = *id; } else { @@ -229,61 +221,61 @@ impl RenderGraph { } // create the execution path for the graph. This will be executed in `RenderGraph::render` - let builtin = { - let mut h = FxHashSet::default(); - h.insert(0u64); // include the base pass - h - }; let descs = self.passes.values().map(|p| &*p.desc).collect(); - let path = GraphExecutionPath::new(builtin, descs); + let path = GraphExecutionPath::new(descs); trace!( "Found {} steps in the rendergraph to execute", path.queue.len() ); + self.exec_path = Some(path); } - #[instrument(skip(self, surface))] - pub fn render(&mut self, surface: &wgpu::Surface) { + #[instrument(skip(self))] + pub fn render(&mut self) { let mut path = self.exec_path.take().unwrap(); - let output = surface.get_current_texture().unwrap(); - let view = output - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - // update the window texture view slot. - let window_tv_slot = self.slots.get_mut(&0).unwrap(); // 0 is window_texture_view - debug_assert_eq!( - window_tv_slot.name.as_str(), - "window_texture_view", - "unexpected slot where 'window_texture_view' should be" - ); - window_tv_slot.value = SlotValue::TextureView(Rc::new(view)); - - let mut encoders = vec![]; + let mut encoders = Vec::with_capacity(self.passes.len() / 2); while let Some(pass_id) = path.queue.pop_front() { let pass = self.passes.get(&pass_id).unwrap(); let pass_inn = pass.inner.clone(); let pass_desc = pass.desc.clone(); let label = format!("{} Encoder", pass_desc.name); - let encoder = self - .device - .create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some(&label), - }); + // encoders are not needed for presenter nodes. + let encoder = if pass_desc.pass_type == RenderPassType::Presenter { + None + } else { + Some( + self.device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some(&label), + }), + ) + }; + let queue = self.queue.clone(); // clone is required to appease the borrow checker - let mut context = RenderGraphContext::new(&queue, Some(encoder)); + let mut context = RenderGraphContext::new(&queue, encoder); + + // all encoders need to be submitted before a presenter node is executed. + if pass_desc.pass_type == RenderPassType::Presenter { + self.queue.submit(encoders.drain(..)); + } let mut inner = pass_inn.borrow_mut(); inner.execute(self, &*pass_desc, &mut context); - encoders.push(context.encoder.unwrap().finish()); + if let Some(encoder) = context.encoder { + encoders.push(encoder.finish()); + } } - self.queue.submit(encoders.into_iter()); - output.present(); + if !encoders.is_empty() { + warn!("{} encoders were not submitted in the same render cycle they were created. \ + Make sure there is a presenting pass at the end. You may still see something, \ + however it will be delayed a render cycle.", encoders.len()); + self.queue.submit(encoders.into_iter()); + } } pub fn slot_value(&self, id: u64) -> Option<&SlotValue> { diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 6c134f3..8a92ce4 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -1,16 +1,17 @@ -use std::{collections::HashMap, num::NonZeroU32, rc::Rc}; +use std::{cell::{Ref, RefCell, RefMut}, collections::HashMap, num::NonZeroU32, rc::Rc}; use lyra_ecs::World; use crate::render::resource::RenderPipelineDescriptor; -use super::{RenderGraph, RenderGraphContext}; +use super::{RenderGraph, RenderGraphContext, RenderTarget}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RenderPassType { Compute, #[default] Render, + Presenter, } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -19,15 +20,19 @@ pub enum SlotType { Sampler, Texture, Buffer, + RenderTarget, } #[derive(Debug, Clone)] pub enum SlotValue { None, + /// The value will be set during a later phase of the render graph. + Lazy, TextureView(Rc), Sampler(Rc), Texture(Rc), Buffer(Rc), + RenderTarget(Rc>), } impl SlotValue { @@ -52,6 +57,20 @@ impl SlotValue { _ => None, } } + + pub fn as_render_target(&self) -> Option> { + match self { + Self::RenderTarget(v) => Some(v.borrow()), + _ => None, + } + } + + pub fn as_render_target_mut(&mut self) -> Option> { + match self { + Self::RenderTarget(v) => Some(v.borrow_mut()), + _ => None, + } + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SlotAttribute { @@ -162,6 +181,11 @@ impl RenderGraphPassDesc { } pub fn add_slot(&mut self, slot: RenderPassSlot) { + debug_assert!( + !(slot.attribute == SlotAttribute::Input && slot.value.is_some()), + "input slots should not have values" + ); + self.slot_names.insert(slot.name.clone(), slot.id); self.slots.push(slot); } @@ -175,7 +199,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!(value, None | Some(SlotValue::Buffer(_))), + matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Buffer(_))), "slot value is not a buffer" ); @@ -198,7 +222,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!(value, None | Some(SlotValue::Texture(_))), + matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Texture(_))), "slot value is not a texture" ); @@ -221,7 +245,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!(value, None | Some(SlotValue::TextureView(_))), + matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::TextureView(_))), "slot value is not a texture view" ); @@ -244,7 +268,7 @@ impl RenderGraphPassDesc { value: Option, ) { debug_assert!( - matches!(value, None | Some(SlotValue::Sampler(_))), + matches!(value, None | Some(SlotValue::Lazy) | Some(SlotValue::Sampler(_))), "slot value is not a sampler" ); diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index feaee62..ed23fc7 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -1,47 +1,100 @@ -use glam::UVec2; +use std::{cell::RefCell, rc::Rc}; -use crate::render::{camera::CameraUniform, graph::{BufferInitDescriptor, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, SlotDescriptor}}; +use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, RenderTarget, SlotAttribute, SlotType, SlotValue}; /// Supplies some basic things other passes needs. /// /// screen size buffer, camera buffer, #[derive(Default)] -pub struct BasePass; +pub struct BasePass { + /// Temporary storage for the main render target + /// + /// This should be Some when the pass is first created then after its added to + /// the render graph it will be None and stay None. + temp_render_target: Option, + main_rt_id: u64, + window_tv_id: u64, +} impl BasePass { - pub fn new() -> Self { - Self::default() + pub fn new(surface: wgpu::Surface, surface_config: wgpu::SurfaceConfiguration) -> Self { + Self { + temp_render_target: Some(RenderTarget { + surface, + surface_config, + current_texture: None, + }), + main_rt_id: 0, + window_tv_id: 0, + } } } impl RenderGraphPass for BasePass { - fn desc(&self, graph: &mut crate::render::graph::RenderGraph, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc { - let mut desc = RenderGraphPassDesc::new(*id, "BasePass", RenderPassType::Compute); - *id += 1; + fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::RenderGraphPassDesc { + let mut desc = RenderGraphPassDesc::new( + graph.next_id(), + "base", + RenderPassType::Render, + None, + vec![], + ); - desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { + /* desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { label: Some("B_ScreenSize".to_string()), contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }))); - *id += 1; desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { label: Some("B_Camera".to_string()), contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(), usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, }))); - *id += 1; + *id += 1; */ + + self.main_rt_id = graph.next_id(); + let render_target = self.temp_render_target.take().unwrap(); + desc.add_slot( + RenderPassSlot { + ty: SlotType::RenderTarget, + attribute: SlotAttribute::Output, + id: self.main_rt_id, + name: "main_render_target".into(), + value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(render_target)))), + } + ); + self.window_tv_id = graph.next_id(); + desc.add_texture_view_slot( + self.window_tv_id, + "window_texture_view", + SlotAttribute::Output, + Some(SlotValue::Lazy), + ); desc } - fn prepare(&mut self, world: &mut lyra_ecs::World) { - let _ = world; - todo!() + fn prepare(&mut self, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) { + } - fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, desc: &crate::render::graph::RenderGraphPassDesc, context: &mut crate::render::graph::RenderGraphContext) { - let _ = (graph, desc, context); - todo!() + fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::RenderGraphPassDesc, _context: &mut crate::render::graph::RenderGraphContext) { + let tv_slot = graph.slot_value_mut(self.main_rt_id) + .expect("somehow the main render target slot is missing"); + let mut rt = tv_slot.as_render_target_mut().unwrap(); + debug_assert!(!rt.current_texture.is_some(), "main render target surface was not presented!"); + + let surface_tex = rt.surface.get_current_texture().unwrap(); + let view = surface_tex.texture.create_view(&wgpu::TextureViewDescriptor::default()); + + rt.current_texture = Some(surface_tex); + drop(rt); // must be manually dropped for borrow checker when getting texture view slot + + // store the surface texture to the slot + let tv_slot = graph.slot_value_mut(self.window_tv_id) + .expect("somehow the window texture view slot is missing"); + *tv_slot = SlotValue::TextureView(Rc::new(view)); + + } } \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 0b8c295..a8dbd06 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -1,8 +1,7 @@ /* mod light_cull_compute; pub use light_cull_compute::*; -mod base; -pub use base::*; + mod depth_prepass; pub use depth_prepass::*; */ @@ -10,5 +9,11 @@ pub use depth_prepass::*; */ /* mod simple_phong; pub use simple_phong::*; */ +mod base; +pub use base::*; + +mod present_pass; +pub use present_pass::*; + mod triangle; pub use triangle::*; \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/present_pass.rs b/lyra-game/src/render/graph/passes/present_pass.rs new file mode 100644 index 0000000..221837a --- /dev/null +++ b/lyra-game/src/render/graph/passes/present_pass.rs @@ -0,0 +1,52 @@ +use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, SlotAttribute, SlotType}; + +/// Supplies some basic things other passes needs. +/// +/// screen size buffer, camera buffer, +pub struct PresentPass { + render_target_slot: String, +} + +impl PresentPass { + pub fn new(render_target_slot: &str) -> Self { + Self { + render_target_slot: render_target_slot.into(), + } + } +} + +impl RenderGraphPass for PresentPass { + fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::RenderGraphPassDesc { + let mut desc = RenderGraphPassDesc::new( + graph.next_id(), + &format!("present_{}", self.render_target_slot), + RenderPassType::Presenter, + None, + vec![], + ); + + desc.add_slot( + RenderPassSlot { + ty: SlotType::RenderTarget, + attribute: SlotAttribute::Input, + id: graph.next_id(), + name: self.render_target_slot.clone(), + value: None, + } + ); + + desc + } + + fn prepare(&mut self, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) { + + } + + fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::RenderGraphPassDesc, _context: &mut crate::render::graph::RenderGraphContext) { + let id = graph.slot_id(&self.render_target_slot) + .expect(&format!("render target slot '{}' for PresentPass is missing", self.render_target_slot)); + let mut slot = graph.slot_value_mut(id).unwrap().as_render_target_mut().unwrap(); + let surf_tex = slot.current_texture.take().unwrap(); + surf_tex.present(); + } +} \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 937f03b..5464dd2 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -44,6 +44,13 @@ impl RenderGraphPass for TrianglePass { let color_bgl = Rc::new(color_bgl); let color_bg = Rc::new(color_bg); + let main_rt = graph.slot_id("main_render_target") + .and_then(|s| graph.slot_value(s)) + .and_then(|s| s.as_render_target()) + .expect("missing main render target"); + let surface_config_format = main_rt.surface_config.format; + drop(main_rt); + let mut desc = RenderGraphPassDesc::new( graph.next_id(), "TrianglePass", @@ -61,7 +68,7 @@ impl RenderGraphPass for TrianglePass { module: shader, entry_point: "fs_main".into(), targets: vec![Some(wgpu::ColorTargetState { - format: graph.surface_config.format, + format: surface_config_format, blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 23f4cb6..6a9ed8a 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; use std::rc::Rc; @@ -12,7 +13,7 @@ use wgpu::Limits; use winit::window::Window; use crate::math::Transform; -use crate::render::graph::TrianglePass; +use crate::render::graph::{BasePass, PresentPass, TrianglePass}; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; @@ -82,10 +83,8 @@ pub struct InterpTransform { } pub struct BasicRenderer { - pub surface: wgpu::Surface, pub device: Rc, // device does not need to be mutable, no need for refcell pub queue: Rc, - pub config: wgpu::SurfaceConfiguration, pub size: winit::dpi::PhysicalSize, pub window: Arc, @@ -214,7 +213,7 @@ impl BasicRenderer { let queue = Rc::new(queue); //let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); - let mut g = RenderGraph::new(device.clone(), queue.clone(), config.clone()); + let mut g = RenderGraph::new(device.clone(), queue.clone()); /* debug!("Adding base pass"); g.add_pass(TrianglePass::new()); debug!("Adding depth pre-pass"); @@ -222,16 +221,18 @@ impl BasicRenderer { debug!("Adding light cull compute pass"); g.add_pass(LightCullComputePass::new(size)); */ + debug!("Adding base pass"); + g.add_pass(BasePass::new(surface, config)); debug!("Adding triangle pass"); g.add_pass(TrianglePass::new()); + debug!("Adding present pass"); + g.add_pass(PresentPass::new("main_render_target")); g.setup(&device); - let mut s = Self { + Self { window, - surface, device, queue, - config, size, clear_color: wgpu::Color { r: 0.1, @@ -244,21 +245,7 @@ impl BasicRenderer { render_limits, graph: g, - }; - - // create the default pipelines - /* let mut pipelines = rustc_hash::FxHashMap::default(); - pipelines.insert(0, Arc::new(RenderPipeline::new(&s.device, &s.config, &shader, - vec![super::vertex::Vertex::desc(),], - vec![&s.bgl_texture, &s.transform_buffers.bindgroup_layout, - s.camera_buffer.bindgroup_layout().unwrap(), - &s.light_buffers.bind_group_pair.layout, &s.material_buffer.bindgroup_pair.as_ref().unwrap().layout, - &s.bgl_texture, - &s.light_cull_compute.light_indices_grid.bg_pair.layout, - ]))); - s.render_pipelines = pipelines; */ - - s + } } } @@ -270,7 +257,7 @@ impl Renderer for BasicRenderer { #[instrument(skip(self))] fn render(&mut self) -> Result<(), wgpu::SurfaceError> { - self.graph.render(&self.surface); + self.graph.render(); Ok(()) } @@ -279,15 +266,17 @@ impl Renderer for BasicRenderer { fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize) { if new_size.width > 0 && new_size.height > 0 { self.size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - - // tell other things of updated resize - self.surface.configure(&self.device, &self.config); + + // update surface config and the surface + let mut rt = self.graph.slot_value_mut(self.graph.slot_id("main_render_target").unwrap()) + .unwrap().as_render_target_mut().unwrap(); + rt.surface_config.width = new_size.width; + rt.surface_config.height = new_size.height; + rt.surface.configure(&self.device, &rt.surface_config); + // update screen size resource in ecs let mut world_ss = world.get_resource_mut::(); world_ss.0 = glam::UVec2::new(new_size.width, new_size.height); - self.graph.surface_config = self.config.clone(); } } From 9a48075f07d5786eaad3c6713e35fdf69467e6a4 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 25 May 2024 19:27:36 -0400 Subject: [PATCH 10/20] render: change to manual creation of render graph exeuction path, rewrite light cull compute pass into the render graph --- Cargo.lock | 17 ++ lyra-game/Cargo.toml | 1 + lyra-game/src/render/graph/mod.rs | 69 ++++- lyra-game/src/render/graph/pass.rs | 19 +- lyra-game/src/render/graph/passes/base.rs | 172 ++++++++--- .../src/render/graph/passes/light_base.rs | 67 +++++ .../render/graph/passes/light_cull_compute.rs | 282 ++++++++++++------ lyra-game/src/render/graph/passes/mod.rs | 9 +- lyra-game/src/render/graph/passes/triangle.rs | 10 +- lyra-game/src/render/light/mod.rs | 13 +- lyra-game/src/render/mod.rs | 2 +- lyra-game/src/render/renderer.rs | 19 +- .../src/render/resource/compute_pipeline.rs | 84 +++++- lyra-game/src/render/resource/mod.rs | 3 + lyra-game/src/render/resource/pipeline.rs | 23 +- .../src/render/resource/render_pipeline.rs | 39 +-- lyra-game/src/render/resource/shader.rs | 40 +++ .../src/render/shaders/simple_phong.wgsl | 87 ------ 18 files changed, 660 insertions(+), 296 deletions(-) create mode 100644 lyra-game/src/render/graph/passes/light_base.rs create mode 100644 lyra-game/src/render/resource/shader.rs delete mode 100644 lyra-game/src/render/shaders/simple_phong.wgsl diff --git a/Cargo.lock b/Cargo.lock index b1ccf40..ebb7297 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -959,6 +959,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.0.28" @@ -1865,6 +1871,7 @@ dependencies = [ "lyra-reflect", "lyra-resource", "lyra-scene", + "petgraph", "quote", "rustc-hash", "syn 2.0.51", @@ -2507,6 +2514,16 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.1.0", +] + [[package]] name = "pin-project-lite" version = "0.2.13" diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index 0ea0b05..197f2f8 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -35,6 +35,7 @@ itertools = "0.11.0" thiserror = "1.0.56" unique = "0.9.1" rustc-hash = "1.1.0" +petgraph = { version = "0.6.5", features = ["matrix_graph"] } [features] tracy = ["dep:tracing-tracy"] diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index e485576..84c82b1 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -6,6 +6,7 @@ use std::{ sync::Arc, }; +use itertools::Itertools; use lyra_ecs::World; pub use pass::*; @@ -19,15 +20,18 @@ mod execution_path; use rustc_hash::FxHashMap; use tracing::{debug_span, instrument, trace, warn}; +use wgpu::ComputePass; use self::execution_path::GraphExecutionPath; -use super::resource::{Pipeline, RenderPipeline}; +use super::resource::{ComputePipeline, Pipeline, RenderPipeline}; //#[derive(Clone)] struct PassEntry { inner: Arc>, desc: Arc, + /// The index of the pass in the execution graph + graph_index: petgraph::matrix_graph::NodeIndex, } pub struct BindGroupEntry { @@ -73,10 +77,10 @@ pub struct RenderGraph { bind_group_names: FxHashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. - /// pipelines: FxHashMap, current_id: u64, exec_path: Option, + new_path: petgraph::matrix_graph::DiMatrix, usize>, } impl RenderGraph { @@ -92,6 +96,7 @@ impl RenderGraph { pipelines: Default::default(), current_id: 1, exec_path: None, + new_path: Default::default(), } } @@ -158,11 +163,14 @@ impl RenderGraph { self.bind_group_names.insert(name.clone(), bg_id); } + let index = self.new_path.add_node(desc.id); + self.passes.insert( desc.id, PassEntry { inner: Arc::new(RefCell::new(pass)), desc: Arc::new(desc), + graph_index: index, }, ); } @@ -172,12 +180,19 @@ impl RenderGraph { pub fn setup(&mut self, device: &wgpu::Device) { // For all passes, create their pipelines for pass in self.passes.values() { - if let Some(pipei) = &pass.desc.pipeline_desc { + if let Some(pipeline_desc) = &pass.desc.pipeline_desc { let pipeline = match pass.desc.pass_type { RenderPassType::Render => { - Pipeline::Render(RenderPipeline::create(device, pipei)) + Pipeline::Render(RenderPipeline::create(device, pipeline_desc.as_render_pipeline_descriptor() + .expect("got compute pipeline descriptor in a render pass"))) + }, + RenderPassType::Compute => { + Pipeline::Compute(ComputePipeline::create(device, pipeline_desc.as_compute_pipeline_descriptor() + .expect("got render pipeline descriptor in a compute pass"))) + }, + RenderPassType::Presenter | RenderPassType::Node => { + panic!("Present or Node RenderGraph passes should not have a pipeline descriptor!"); } - _ => todo!(), }; let res = PipelineResource { @@ -233,25 +248,30 @@ impl RenderGraph { #[instrument(skip(self))] pub fn render(&mut self) { - let mut path = self.exec_path.take().unwrap(); + let mut sorted: VecDeque = petgraph::algo::toposort(&self.new_path, None) + .expect("RenderGraph had cycled!") + .iter().map(|i| self.new_path[i.clone()]) + .collect(); + let path_names = sorted.iter().map(|i| self.pass(*i).unwrap().name.clone()).collect_vec(); + trace!("Render graph execution order: {:?}", path_names); let mut encoders = Vec::with_capacity(self.passes.len() / 2); - while let Some(pass_id) = path.queue.pop_front() { + while let Some(pass_id) = sorted.pop_front() { let pass = self.passes.get(&pass_id).unwrap(); let pass_inn = pass.inner.clone(); let pass_desc = pass.desc.clone(); let label = format!("{} Encoder", pass_desc.name); // encoders are not needed for presenter nodes. - let encoder = if pass_desc.pass_type == RenderPassType::Presenter { - None - } else { + let encoder = if pass_desc.pass_type.should_have_pipeline() { Some( self.device .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: Some(&label), }), ) + } else { + None }; let queue = self.queue.clone(); // clone is required to appease the borrow checker @@ -259,9 +279,11 @@ impl RenderGraph { // all encoders need to be submitted before a presenter node is executed. if pass_desc.pass_type == RenderPassType::Presenter { + trace!("Submitting {} encoderd before presenting", encoders.len()); self.queue.submit(encoders.drain(..)); } + trace!("Executing {}", pass_desc.name); let mut inner = pass_inn.borrow_mut(); inner.execute(self, &*pass_desc, &mut context); @@ -320,6 +342,16 @@ impl RenderGraph { pub fn bind_group_id(&self, name: &str) -> Option { self.bind_group_names.get(name).copied() } + + pub fn add_edge(&mut self, from: &str, to: &str) { + let from_idx = self.passes.iter().find(|p| p.1.desc.name == from).map(|p| p.1.graph_index) + .expect("Failed to find from pass"); + let to_idx = self.passes.iter().find(|p| p.1.desc.name == to).map(|p| p.1.graph_index) + .expect("Failed to find to pass"); + + self.new_path.add_edge(from_idx, to_idx, ()); + //self.new_path.add_edge(NodeIndex::new(from_id as usize), NodeIndex::new(to_id as usize), ()); + } } /// A queued write to a GPU buffer targeting a graph slot. @@ -396,4 +428,21 @@ impl<'a> RenderGraphContext<'a> { ) { self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes)); } + + pub fn get_bind_groups<'b>(&self, graph: &'b RenderGraph, bind_group_names: &[&str]) -> Vec>> { + bind_group_names + .iter() + .map(|name| graph.bind_group_id(name)) + .map(|bgi| bgi.map(|bgi| graph.bind_group(bgi))) + .collect() + } + + pub fn set_bind_groups<'b, 'c>(graph: &'b RenderGraph, pass: &'c mut ComputePass<'b>, bind_groups: &[(&str, u32)]) { + for (name, index) in bind_groups { + let bg = graph.bind_group_id(name).map(|bgi| graph.bind_group(bgi)) + .expect(&format!("Could not find bind group '{}'", name)); + + pass.set_bind_group(*index, bg, &[]); + } + } } diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index 8a92ce4..c995290 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -2,18 +2,31 @@ use std::{cell::{Ref, RefCell, RefMut}, collections::HashMap, num::NonZeroU32, r use lyra_ecs::World; -use crate::render::resource::RenderPipelineDescriptor; +use crate::render::resource::PipelineDescriptor; use super::{RenderGraph, RenderGraphContext, RenderTarget}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RenderPassType { + /// A node doesn't render, compute, or present anything. This likely means it injects data into the graph. + Node, Compute, #[default] Render, Presenter, } +impl RenderPassType { + pub fn should_have_pipeline(&self) -> bool { + match self { + RenderPassType::Node => false, + RenderPassType::Compute => true, + RenderPassType::Render => true, + RenderPassType::Presenter => false, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SlotType { TextureView, @@ -150,7 +163,7 @@ pub struct RenderGraphPassDesc { pub pass_type: RenderPassType, pub slots: Vec, slot_names: HashMap, - pub pipeline_desc: Option, + pub pipeline_desc: Option, pub bind_groups: Vec<( String, Rc, @@ -163,7 +176,7 @@ impl RenderGraphPassDesc { id: u64, name: &str, pass_type: RenderPassType, - pipeline_desc: Option, + pipeline_desc: Option, bind_groups: Vec<(&str, Rc, Option>)>, ) -> Self { Self { diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index ed23fc7..9700f0a 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -1,68 +1,114 @@ use std::{cell::RefCell, rc::Rc}; -use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, RenderTarget, SlotAttribute, SlotType, SlotValue}; +use glam::UVec2; +use tracing::warn; +use winit::dpi::PhysicalSize; + +use crate::{ + render::{ + camera::{CameraUniform, RenderCamera}, + graph::{ + RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, + RenderPassType, RenderTarget, SlotAttribute, SlotType, SlotValue, + }, + render_buffer::BufferWrapper, texture::RenderTexture, + }, + scene::CameraComponent, +}; /// Supplies some basic things other passes needs. -/// +/// /// screen size buffer, camera buffer, #[derive(Default)] pub struct BasePass { /// Temporary storage for the main render target - /// + /// /// This should be Some when the pass is first created then after its added to /// the render graph it will be None and stay None. temp_render_target: Option, main_rt_id: u64, window_tv_id: u64, + screen_size: glam::UVec2, } impl BasePass { pub fn new(surface: wgpu::Surface, surface_config: wgpu::SurfaceConfiguration) -> Self { + let size = glam::UVec2::new(surface_config.width, surface_config.height); + Self { temp_render_target: Some(RenderTarget { surface, surface_config, current_texture: None, }), - main_rt_id: 0, - window_tv_id: 0, + screen_size: size, + ..Default::default() } } } impl RenderGraphPass for BasePass { - fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::RenderGraphPassDesc { + fn desc( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + ) -> crate::render::graph::RenderGraphPassDesc { + let render_target = self.temp_render_target.take().unwrap(); + self.screen_size = UVec2::new( + render_target.surface_config.width, + render_target.surface_config.height, + ); + + let (screen_size_bgl, screen_size_bg, screen_size_buf, _) = BufferWrapper::builder() + .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) + .label_prefix("ScreenSize") + .visibility(wgpu::ShaderStages::COMPUTE) + .buffer_dynamic_offset(false) + .contents(&[self.screen_size]) + .finish_parts(&graph.device()); + let screen_size_bgl = Rc::new(screen_size_bgl); + let screen_size_bg = Rc::new(screen_size_bg); + + let (camera_bgl, camera_bg, camera_buf, _) = BufferWrapper::builder() + .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) + .label_prefix("camera") + .visibility(wgpu::ShaderStages::all()) + .buffer_dynamic_offset(false) + .contents(&[CameraUniform::default()]) + .finish_parts(&graph.device()); + let camera_bgl = Rc::new(camera_bgl); + let camera_bg = Rc::new(camera_bg); + + // create the depth texture using the utility struct, then take all the required fields + let mut depth_texture = RenderTexture::create_depth_texture(&graph.device(), &render_target.surface_config, "depth_texture"); + depth_texture.create_bind_group(&graph.device); + + let dt_bg_pair = depth_texture.bindgroup_pair.unwrap(); + let depth_texture_bg = Rc::new(dt_bg_pair.bindgroup); + let depth_texture_bgl = dt_bg_pair.layout; + let depth_texture_view = Rc::new(depth_texture.view); + let mut desc = RenderGraphPassDesc::new( graph.next_id(), "base", RenderPassType::Render, None, - vec![], + vec![ + ("depth_texture", depth_texture_bg, Some(depth_texture_bgl)), + ("screen_size", screen_size_bg, Some(screen_size_bgl)), + ("camera", camera_bg, Some(camera_bgl)), + ], ); - /* desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { - label: Some("B_ScreenSize".to_string()), - contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }))); - desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor { - label: Some("B_Camera".to_string()), - contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }))); - *id += 1; */ - self.main_rt_id = graph.next_id(); - let render_target = self.temp_render_target.take().unwrap(); - desc.add_slot( - RenderPassSlot { - ty: SlotType::RenderTarget, - attribute: SlotAttribute::Output, - id: self.main_rt_id, - name: "main_render_target".into(), - value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(render_target)))), - } - ); + desc.add_slot(RenderPassSlot { + ty: SlotType::RenderTarget, + attribute: SlotAttribute::Output, + id: self.main_rt_id, + name: "main_render_target".into(), + value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new( + render_target, + )))), + }); self.window_tv_id = graph.next_id(); desc.add_texture_view_slot( self.window_tv_id, @@ -70,31 +116,75 @@ impl RenderGraphPass for BasePass { SlotAttribute::Output, Some(SlotValue::Lazy), ); + desc.add_texture_view_slot( + self.window_tv_id, + "depth_texture_view", + SlotAttribute::Output, + Some(SlotValue::TextureView(depth_texture_view)), + ); + desc.add_buffer_slot( + graph.next_id(), + "screen_size_buffer", + SlotAttribute::Output, + Some(SlotValue::Buffer(Rc::new(screen_size_buf))), + ); + desc.add_buffer_slot( + graph.next_id(), + "camera_buffer", + SlotAttribute::Output, + Some(SlotValue::Buffer(Rc::new(camera_buf))), + ); desc } - fn prepare(&mut self, _world: &mut lyra_ecs::World, _context: &mut RenderGraphContext) { - + fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) { + if let Some(camera) = world.view_iter::<&mut CameraComponent>().next() { + let mut render_cam = + RenderCamera::new(PhysicalSize::new(self.screen_size.x, self.screen_size.y)); + let uniform = render_cam.calc_view_projection(&camera); + + context.queue_buffer_write_with("camera_buffer", 0, uniform) + } else { + warn!("Missing camera!"); + } } - fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::RenderGraphPassDesc, _context: &mut crate::render::graph::RenderGraphContext) { - let tv_slot = graph.slot_value_mut(self.main_rt_id) + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + _desc: &crate::render::graph::RenderGraphPassDesc, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let tv_slot = graph + .slot_value_mut(self.main_rt_id) .expect("somehow the main render target slot is missing"); let mut rt = tv_slot.as_render_target_mut().unwrap(); - debug_assert!(!rt.current_texture.is_some(), "main render target surface was not presented!"); - + debug_assert!( + !rt.current_texture.is_some(), + "main render target surface was not presented!" + ); + + // update the screen size buffer if the size changed. + if rt.surface_config.width != self.screen_size.x + || rt.surface_config.height != self.screen_size.y + { + self.screen_size = UVec2::new(rt.surface_config.width, rt.surface_config.height); + context.queue_buffer_write_with("screen_size_buffer", 0, self.screen_size) + } + let surface_tex = rt.surface.get_current_texture().unwrap(); - let view = surface_tex.texture.create_view(&wgpu::TextureViewDescriptor::default()); - + let view = surface_tex + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + rt.current_texture = Some(surface_tex); drop(rt); // must be manually dropped for borrow checker when getting texture view slot // store the surface texture to the slot - let tv_slot = graph.slot_value_mut(self.window_tv_id) + let tv_slot = graph + .slot_value_mut(self.window_tv_id) .expect("somehow the window texture view slot is missing"); *tv_slot = SlotValue::TextureView(Rc::new(view)); - - } -} \ No newline at end of file +} diff --git a/lyra-game/src/render/graph/passes/light_base.rs b/lyra-game/src/render/graph/passes/light_base.rs new file mode 100644 index 0000000..e26c30d --- /dev/null +++ b/lyra-game/src/render/graph/passes/light_base.rs @@ -0,0 +1,67 @@ +use crate::render::{ + graph::{ + RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, + SlotValue, + }, + light::LightUniformBuffers, +}; + +/// Supplies some basic things other passes needs. +/// +/// screen size buffer, camera buffer, +#[derive(Default)] +pub struct LightBasePass { + light_buffers: Option, +} + +impl LightBasePass { + pub fn new() -> Self { + Self::default() + } +} + +impl RenderGraphPass for LightBasePass { + fn desc( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + ) -> crate::render::graph::RenderGraphPassDesc { + let device = &graph.device; + self.light_buffers = Some(LightUniformBuffers::new(device)); + let light_buffers = self.light_buffers.as_ref().unwrap(); + + let mut desc = RenderGraphPassDesc::new( + graph.next_id(), + "light_base", + RenderPassType::Node, + None, + vec![( + "light_buffers", + light_buffers.bind_group.clone(), + Some(light_buffers.bind_group_layout.clone()), + )], + ); + + desc.add_buffer_slot( + graph.next_id(), + "light_buffers", + SlotAttribute::Output, + Some(SlotValue::Buffer(light_buffers.buffer.clone())), + ); + + desc + } + + fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) { + let tick = world.current_tick(); + let lights = self.light_buffers.as_mut().unwrap(); + lights.update_lights(context.queue, tick, world); + } + + fn execute( + &mut self, + _graph: &mut crate::render::graph::RenderGraph, + _desc: &crate::render::graph::RenderGraphPassDesc, + _context: &mut crate::render::graph::RenderGraphContext, + ) { + } +} diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index 8d8ddb0..10ec948 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -1,10 +1,14 @@ -use std::mem; +use std::{mem, rc::Rc}; use lyra_ecs::World; +use wgpu::util::DeviceExt; -use crate::render::graph::{ - BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, - SlotAttribute, SlotDescriptor, TextureDescriptor, TextureViewDescriptor, +use crate::render::{ + graph::{ + RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, + SlotValue, + }, + resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader}, }; pub struct LightCullComputePass { @@ -21,124 +25,228 @@ impl LightCullComputePass { impl RenderGraphPass for LightCullComputePass { fn desc( - &self, + &mut self, graph: &mut crate::render::graph::RenderGraph, - id: &mut u64, ) -> crate::render::graph::RenderGraphPassDesc { - let mut desc = RenderGraphPassDesc::new(*id, "LightCullCompute", RenderPassType::Compute); - *id += 1; + let shader = Rc::new(Shader { + label: Some("light_cull_comp_shader".into()), + source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(), + }); - desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input, None); - *id += 1; - desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input, None); - *id += 1; + // get the size of the work group for the grid + let main_rt = graph + .slot_id("main_render_target") + .and_then(|s| graph.slot_value(s)) + .and_then(|s| s.as_render_target()) + .expect("missing main render target"); + self.workgroup_size = + glam::UVec2::new(main_rt.surface_config.width, main_rt.surface_config.height); + // initialize some buffers with empty data let mut contents = Vec::::new(); let contents_len = - self.workgroup_size.x * self.workgroup_size.y * 200 * mem::size_of::() as u32; + self.workgroup_size.x * self.workgroup_size.y * mem::size_of::() as u32; contents.resize(contents_len as _, 0); - desc.add_buffer_slot( - *id, - "light_indices", - SlotAttribute::Output, - Some(SlotDescriptor::BufferInit(BufferInitDescriptor { - label: Some("B_LightIndices".to_string()), - contents, + + let device = graph.device(); + let light_indices_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("light_indices_buffer"), + contents: &contents, + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, + }); + + let light_index_counter_buffer = + device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("light_index_counter_buffer"), + contents: &bytemuck::cast_slice(&[0]), usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST, - })), - ); - *id += 1; + }); + + let light_indices_bg_layout = Rc::new(device.create_bind_group_layout( + &wgpu::BindGroupLayoutDescriptor { + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::COMPUTE | wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::ReadWrite, + format: wgpu::TextureFormat::Rg32Uint, // vec2 + view_dimension: wgpu::TextureViewDimension::D2, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 2, + visibility: wgpu::ShaderStages::COMPUTE, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Storage { read_only: false }, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }, + ], + label: Some("light_indices_grid_bgl"), + }, + )); let size = wgpu::Extent3d { width: self.workgroup_size.x, height: self.workgroup_size.y, depth_or_array_layers: 1, }; - desc.add_texture_slot( - *id, - "lightgrid_texture", - SlotAttribute::Output, - Some(SlotDescriptor::Texture(TextureDescriptor { - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Rg32Uint, // vec2 - usage: wgpu::TextureUsages::STORAGE_BINDING, - view_formats: vec![], + let grid_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("light_grid_tex"), + size, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rg32Uint, // vec2 + usage: wgpu::TextureUsages::STORAGE_BINDING, + view_formats: &[], + }); + + let grid_texture_view = grid_texture.create_view(&wgpu::TextureViewDescriptor { + label: Some("light_grid_texview"), + format: Some(wgpu::TextureFormat::Rg32Uint), // vec2 + dimension: Some(wgpu::TextureViewDimension::D2), + aspect: wgpu::TextureAspect::All, + base_mip_level: 0, + mip_level_count: None, + base_array_layer: 0, + array_layer_count: None, + }); + + let light_indices_bg = Rc::new(device.create_bind_group(&wgpu::BindGroupDescriptor { + layout: &light_indices_bg_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &light_indices_buffer, + offset: 0, + size: None, // the entire light buffer is needed + }), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::TextureView(&grid_texture_view), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &light_index_counter_buffer, + offset: 0, + size: None, // the entire light buffer is needed + }), + }, + ], + label: Some("light_indices_grid_bind_group"), + })); + + drop(main_rt); + let pass_id = graph.next_id(); + + let depth_tex_bgl = graph.bind_group_layout(graph.bind_group_id("depth_texture").unwrap()); + let camera_bgl = graph.bind_group_layout(graph.bind_group_id("camera").unwrap()); + let lights_bgl = graph.bind_group_layout(graph.bind_group_id("light_buffers").unwrap()); + let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id("screen_size").unwrap()); + + let mut desc = RenderGraphPassDesc::new( + pass_id, + "light_cull_compute", + RenderPassType::Compute, + Some(PipelineDescriptor::Compute(ComputePipelineDescriptor { + label: Some("light_cull_pipeline".into()), + push_constant_ranges: vec![], + layouts: vec![ + depth_tex_bgl.clone(), + camera_bgl.clone(), + lights_bgl.clone(), + light_indices_bg_layout.clone(), + screen_size_bgl.clone(), + ], + shader, + shader_entry_point: "cs_main".into(), })), + vec![( + "light_indices_grid", + light_indices_bg, + Some(light_indices_bg_layout), + )], ); - *id += 1; desc.add_texture_view_slot( - *id, - "lightgrid_texture_view", - SlotAttribute::Output, - Some(SlotDescriptor::TextureView(TextureViewDescriptor { - texture_label: "lightgrid_texture".to_string(), - format: Some(wgpu::TextureFormat::Rg32Uint), // vec2 - dimension: Some(wgpu::TextureViewDimension::D2), - aspect: wgpu::TextureAspect::All, - base_mip_level: 0, - mip_level_count: None, - base_array_layer: 0, - array_layer_count: None, - })), + graph.next_id(), + "window_texture_view", + SlotAttribute::Input, + None, + ); + desc.add_buffer_slot( + graph.next_id(), + "screen_size_buffer", + SlotAttribute::Input, + None, + ); + desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Input, None); + desc.add_buffer_slot( + graph.next_id(), + "index_counter_buffer", + SlotAttribute::Output, + Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))), ); - *id += 1; desc } - fn prepare(&mut self, world: &mut World) { - let _ = world; - todo!() - } + fn prepare(&mut self, _world: &mut World, _context: &mut RenderGraphContext) {} fn execute( &mut self, graph: &mut crate::render::graph::RenderGraph, - _: &crate::render::graph::RenderGraphPassDesc, + desc: &crate::render::graph::RenderGraphPassDesc, context: &mut RenderGraphContext, ) { let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor { - label: Some("Pass_lightCull"), + label: Some("light_cull_pass"), }); - let pipeline = graph.compute_pipeline("main"); - pass.set_pipeline(pipeline); + let pipeline = graph.pipeline(desc.id); + pass.set_pipeline(pipeline.as_compute()); - let depth_tex = graph.slot_bind_group( - graph - .slot_id("depth_texture") - .expect("Could not find depth texture slot"), - ); - let camera_bg = graph.slot_bind_group( - graph - .slot_id("camera_buffer") - .expect("Could not find camera buffers"), - ); - let screen_size_bg = graph.slot_bind_group( - graph - .slot_id("screen_size_buffer") - .expect("Could not find screen size buffer slot"), - ); - let indices_bg = graph.slot_bind_group( - graph - .slot_id("light_indices") - .expect("Could not find light index buffer slot"), - ); - let light_grid_bg = graph.slot_bind_group( - graph - .slot_id("grid_texture") - .expect("Could not find light grid buffer slot"), - ); + /* let depth_tex_bg = graph.bind_group(graph.bind_group_id("depth_texture").unwrap()); + let camera_bg = graph.bind_group(graph.bind_group_id("camera").unwrap()); + let lights_bg = graph.bind_group(graph.bind_group_id("light_buffers").unwrap()); + let grid_bg = graph.bind_group(graph.bind_group_id("light_indices_grid").unwrap()); + let screen_size_bg = graph.bind_group(graph.bind_group_id("screen_size").unwrap()); - pass.set_bind_group(0, depth_tex, &[]); + pass.set_bind_group(0, depth_tex_bg, &[]); pass.set_bind_group(1, camera_bg, &[]); - pass.set_bind_group(2, indices_bg, &[]); - pass.set_bind_group(3, light_grid_bg, &[]); - pass.set_bind_group(4, screen_size_bg, &[]); + pass.set_bind_group(2, lights_bg, &[]); + pass.set_bind_group(3, grid_bg, &[]); + pass.set_bind_group(4, screen_size_bg, &[]); */ + + RenderGraphContext::set_bind_groups( + graph, + &mut pass, + &[ + ("depth_texture", 0), + ("camera", 1), + ("light_buffers", 2), + ("light_indices_grid", 3), + ("screen_size", 4), + ], + ); pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1); } diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index a8dbd06..bd590fc 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -1,9 +1,7 @@ -/* mod light_cull_compute; +mod light_cull_compute; pub use light_cull_compute::*; - - -mod depth_prepass; +/*mod depth_prepass; pub use depth_prepass::*; */ /* mod simple_phong; @@ -12,6 +10,9 @@ pub use simple_phong::*; */ mod base; pub use base::*; +mod light_base; +pub use light_base::*; + mod present_pass; pub use present_pass::*; diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs index 5464dd2..b6417b0 100644 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ b/lyra-game/src/render/graph/passes/triangle.rs @@ -7,7 +7,7 @@ use crate::{ SlotAttribute, SlotValue, }, render_buffer::BufferWrapper, - resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState}, + resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, }, DeltaTime, }; @@ -53,9 +53,9 @@ impl RenderGraphPass for TrianglePass { let mut desc = RenderGraphPassDesc::new( graph.next_id(), - "TrianglePass", + "triangle", RenderPassType::Render, - Some(RenderPipelineDescriptor { + Some(PipelineDescriptor::Render(RenderPipelineDescriptor { label: Some("triangle_pipeline".into()), layouts: vec![color_bgl.clone()], push_constant_ranges: vec![], @@ -77,7 +77,7 @@ impl RenderGraphPass for TrianglePass { primitive: wgpu::PrimitiveState::default(), multisample: wgpu::MultisampleState::default(), multiview: None, - }), + })), vec![("color_bg", color_bg, Some(color_bgl))], ); @@ -125,7 +125,7 @@ impl RenderGraphPass for TrianglePass { let encoder = context.encoder.as_mut().unwrap(); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("TrianglePass"), + label: Some("triangle_pass"), color_attachments: &[ // This is what @location(0) in the fragment shader targets Some(wgpu::RenderPassColorAttachment { diff --git a/lyra-game/src/render/light/mod.rs b/lyra-game/src/render/light/mod.rs index ca37007..9135632 100644 --- a/lyra-game/src/render/light/mod.rs +++ b/lyra-game/src/render/light/mod.rs @@ -6,7 +6,7 @@ use lyra_ecs::{Entity, Tick, World, query::{Entities, TickOf}}; pub use point::*; pub use spotlight::*; -use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem}; +use std::{collections::{HashMap, VecDeque}, marker::PhantomData, mem, rc::Rc}; use crate::math::Transform; @@ -100,8 +100,10 @@ impl LightBuffer { } pub(crate) struct LightUniformBuffers { - pub buffer: wgpu::Buffer, - pub bind_group_pair: BindGroupPair, + pub buffer: Rc, + //pub bind_group_pair: BindGroupPair, + pub bind_group: Rc, + pub bind_group_layout: Rc, pub light_indexes: HashMap, dead_indices: VecDeque, pub current_light_idx: u32, @@ -158,8 +160,9 @@ impl LightUniformBuffers { }); Self { - buffer, - bind_group_pair: BindGroupPair::new(bindgroup, bindgroup_layout), + buffer: Rc::new(buffer), + bind_group: Rc::new(bindgroup), + bind_group_layout: Rc::new(bindgroup_layout), light_indexes: Default::default(), current_light_idx: 0, dead_indices: VecDeque::new(), diff --git a/lyra-game/src/render/mod.rs b/lyra-game/src/render/mod.rs index 7e29e21..d1e39d5 100755 --- a/lyra-game/src/render/mod.rs +++ b/lyra-game/src/render/mod.rs @@ -12,6 +12,6 @@ pub mod camera; pub mod window; pub mod transform_buffer_storage; pub mod light; -pub mod light_cull_compute; +//pub mod light_cull_compute; pub mod avec; pub mod graph; \ No newline at end of file diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 6a9ed8a..56fbc9e 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -13,7 +13,7 @@ use wgpu::Limits; use winit::window::Window; use crate::math::Transform; -use crate::render::graph::{BasePass, PresentPass, TrianglePass}; +use crate::render::graph::{BasePass, LightBasePass, LightCullComputePass, PresentPass, TrianglePass}; use crate::render::material::MaterialUniform; use crate::render::render_buffer::BufferWrapperBuilder; @@ -211,22 +211,25 @@ impl BasicRenderer { let device = Rc::new(device); let queue = Rc::new(queue); - //let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); let mut g = RenderGraph::new(device.clone(), queue.clone()); - /* debug!("Adding base pass"); - g.add_pass(TrianglePass::new()); - debug!("Adding depth pre-pass"); - g.add_pass(DepthPrePass::new()); - debug!("Adding light cull compute pass"); - g.add_pass(LightCullComputePass::new(size)); */ debug!("Adding base pass"); g.add_pass(BasePass::new(surface, config)); + debug!("Adding light base pass"); + g.add_pass(LightBasePass::new()); + debug!("Adding light cull compute pass"); + g.add_pass(LightCullComputePass::new(size)); debug!("Adding triangle pass"); g.add_pass(TrianglePass::new()); debug!("Adding present pass"); g.add_pass(PresentPass::new("main_render_target")); + + g.add_edge("base", "light_base"); + g.add_edge("light_base", "light_cull_compute"); + g.add_edge("base", "triangle"); + g.add_edge("base", "present_main_render_target"); + g.setup(&device); Self { diff --git a/lyra-game/src/render/resource/compute_pipeline.rs b/lyra-game/src/render/resource/compute_pipeline.rs index 43d37d2..71b48a5 100644 --- a/lyra-game/src/render/resource/compute_pipeline.rs +++ b/lyra-game/src/render/resource/compute_pipeline.rs @@ -1,9 +1,41 @@ -use std::ops::Deref; +use std::{ops::Deref, rc::Rc}; use wgpu::PipelineLayout; +use super::Shader; + +//#[derive(Debug, Clone)] +pub struct ComputePipelineDescriptor { + pub label: Option, + pub layouts: Vec>, + pub push_constant_ranges: Vec, + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub shader: Rc, + /// The entry point in the compiled shader. + /// There must be a function in the shader with the same name. + pub shader_entry_point: String, +} + +impl ComputePipelineDescriptor { + /// Create the [`wgpu::PipelineLayout`] for this pipeline + pub(crate) fn create_layout(&self, device: &wgpu::Device) -> wgpu::PipelineLayout { + let bgs = self + .layouts + .iter() + .map(|bg| bg.as_ref()) + .collect::>(); + + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)), + bind_group_layouts: &bgs, + push_constant_ranges: &self.push_constant_ranges, + }) + } +} + pub struct ComputePipeline { - layout: PipelineLayout, + layout: Option, wgpu_pipeline: wgpu::ComputePipeline, } @@ -15,8 +47,48 @@ impl Deref for ComputePipeline { } } +impl From for ComputePipeline { + fn from(value: wgpu::ComputePipeline) -> Self { + Self { + layout: None, + wgpu_pipeline: value, + } + } +} + impl ComputePipeline { - pub fn new(layout: PipelineLayout, pipeline: wgpu::ComputePipeline) -> Self { + /// Creates a new compute pipeline on the `device`. + /// + /// Parameters: + /// * `device` - The device to create the pipeline on. + /// * `desc` - The discriptor of the compute pipeline + pub fn create(device: &wgpu::Device, desc: &ComputePipelineDescriptor) -> ComputePipeline { + // create the layout only if bind groups layouts were specified + let layout = if !desc.layouts.is_empty() { + Some(desc.create_layout(device)) + } else { + None + }; + + // an Rc was used here so that this shader could be reused by the fragment stage if + // they share the same shader. I tried to do it without an Rc but couldn't get past + // the borrow checker + let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: desc.shader.label.as_ref().map(|s| s.as_str()), + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( + &desc.shader.source, + )), + })); + + let desc = wgpu::ComputePipelineDescriptor { + label: desc.label.as_deref(), + layout: layout.as_ref(), + module: &compiled_shader, + entry_point: &desc.shader_entry_point, + }; + + let pipeline = device.create_compute_pipeline(&desc); + Self { layout, wgpu_pipeline: pipeline, @@ -24,12 +96,12 @@ impl ComputePipeline { } #[inline(always)] - pub fn layout(&self) -> &PipelineLayout { - &self.layout + pub fn layout(&self) -> Option<&PipelineLayout> { + self.layout.as_ref() } #[inline(always)] pub fn wgpu_pipeline(&self) -> &wgpu::ComputePipeline { &self.wgpu_pipeline } -} \ No newline at end of file +} diff --git a/lyra-game/src/render/resource/mod.rs b/lyra-game/src/render/resource/mod.rs index 984fcaa..34ccb1d 100644 --- a/lyra-game/src/render/resource/mod.rs +++ b/lyra-game/src/render/resource/mod.rs @@ -1,3 +1,6 @@ +mod shader; +pub use shader::*; + mod pipeline; pub use pipeline::*; diff --git a/lyra-game/src/render/resource/pipeline.rs b/lyra-game/src/render/resource/pipeline.rs index db23149..d66eb45 100644 --- a/lyra-game/src/render/resource/pipeline.rs +++ b/lyra-game/src/render/resource/pipeline.rs @@ -1,4 +1,25 @@ -use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline}; +use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline, ComputePipelineDescriptor, RenderPipelineDescriptor}; + +pub enum PipelineDescriptor { + Render(RenderPipelineDescriptor), + Compute(ComputePipelineDescriptor), +} + +impl PipelineDescriptor { + pub fn as_render_pipeline_descriptor(&self) -> Option<&RenderPipelineDescriptor> { + match self { + Self::Render(r) => Some(r), + _ => None, + } + } + + pub fn as_compute_pipeline_descriptor(&self) -> Option<&ComputePipelineDescriptor> { + match self { + Self::Compute(c) => Some(c), + _ => None, + } + } +} pub enum Pipeline { Render(RenderPipeline), diff --git a/lyra-game/src/render/resource/render_pipeline.rs b/lyra-game/src/render/resource/render_pipeline.rs index defb332..a93ce36 100755 --- a/lyra-game/src/render/resource/render_pipeline.rs +++ b/lyra-game/src/render/resource/render_pipeline.rs @@ -2,44 +2,7 @@ use std::{num::NonZeroU32, ops::Deref, rc::Rc}; use wgpu::PipelineLayout; -#[derive(Debug, Default, Clone)] -pub struct VertexBufferLayout { - pub array_stride: wgpu::BufferAddress, - pub step_mode: wgpu::VertexStepMode, - pub attributes: Vec, -} - -/// Describes the vertex stage in a render pipeline. -#[derive(Debug, Clone)] -pub struct VertexState { - // TODO: make this a ResHandle - /// The compiled shader module for the stage. - pub module: Rc, - /// The entry point in the compiled shader. - /// There must be a function in the shader with the same name. - pub entry_point: String, - /// The format of the vertex buffers used with this pipeline. - pub buffers: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Shader { - pub label: Option, - pub source: String, -} - -/// Describes the fragment stage in the render pipeline. -#[derive(Debug, Clone)] -pub struct FragmentState { - // TODO: make this a ResHandle - /// The compiled shader module for the stage. - pub module: Rc, - /// The entry point in the compiled shader. - /// There must be a function in the shader with the same name. - pub entry_point: String, - /// The color state of the render targets. - pub targets: Vec>, -} +use super::{FragmentState, VertexState}; //#[derive(Debug, Clone)] pub struct RenderPipelineDescriptor { diff --git a/lyra-game/src/render/resource/shader.rs b/lyra-game/src/render/resource/shader.rs new file mode 100644 index 0000000..fa1c9b4 --- /dev/null +++ b/lyra-game/src/render/resource/shader.rs @@ -0,0 +1,40 @@ +use std::rc::Rc; + +#[derive(Debug, Default, Clone)] +pub struct VertexBufferLayout { + pub array_stride: wgpu::BufferAddress, + pub step_mode: wgpu::VertexStepMode, + pub attributes: Vec, +} + +/// Describes the vertex stage in a render pipeline. +#[derive(Debug, Clone)] +pub struct VertexState { + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub module: Rc, + /// The entry point in the compiled shader. + /// There must be a function in the shader with the same name. + pub entry_point: String, + /// The format of the vertex buffers used with this pipeline. + pub buffers: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Shader { + pub label: Option, + pub source: String, +} + +/// Describes the fragment stage in the render pipeline. +#[derive(Debug, Clone)] +pub struct FragmentState { + // TODO: make this a ResHandle + /// The compiled shader module for the stage. + pub module: Rc, + /// The entry point in the compiled shader. + /// There must be a function in the shader with the same name. + pub entry_point: String, + /// The color state of the render targets. + pub targets: Vec>, +} \ No newline at end of file diff --git a/lyra-game/src/render/shaders/simple_phong.wgsl b/lyra-game/src/render/shaders/simple_phong.wgsl deleted file mode 100644 index aa9f8a9..0000000 --- a/lyra-game/src/render/shaders/simple_phong.wgsl +++ /dev/null @@ -1,87 +0,0 @@ -// Vertex shader - -const max_light_count: u32 = 16u; - -const LIGHT_TY_DIRECTIONAL = 0u; -const LIGHT_TY_POINT = 1u; -const LIGHT_TY_SPOT = 2u; - -const ALPHA_CUTOFF = 0.1; - -struct VertexInput { - @location(0) position: vec3, - @location(1) tex_coords: vec2, - @location(2) normal: vec3, -} - -struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, - @location(1) world_position: vec3, - @location(2) world_normal: vec3, -} - -struct TransformData { - transform: mat4x4, - normal_matrix: mat4x4, -} - -struct CameraUniform { - view: mat4x4, - inverse_projection: mat4x4, - view_projection: mat4x4, - projection: mat4x4, - position: vec3, - tile_debug: u32, -}; - -@group(1) @binding(0) -var u_model_transform_data: TransformData; - -@group(2) @binding(0) -var u_camera: CameraUniform; - -@vertex -fn vs_main( - model: VertexInput, -) -> VertexOutput { - var out: VertexOutput; - - out.tex_coords = model.tex_coords; - out.clip_position = u_camera.view_projection * u_model_transform_data.transform * vec4(model.position, 1.0); - - // the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36 - let normal_mat4 = u_model_transform_data.normal_matrix; - let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz); - out.world_normal = normalize(normal_mat * model.normal, ); - - var world_position: vec4 = u_model_transform_data.transform * vec4(model.position, 1.0); - out.world_position = world_position.xyz; - - return out; -} - -// Fragment shader - -struct Material { - ambient: vec4, - diffuse: vec4, - specular: vec4, - shininess: f32, -} - -@group(0) @binding(0) -var t_diffuse: texture_2d; -@group(0) @binding(1) -var s_diffuse: sampler; - -@fragment -fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let object_color: vec4 = textureSample(t_diffuse, s_diffuse, in.tex_coords); - - if (object_color.a < ALPHA_CUTOFF) { - discard; - } - - return object_color; -} \ No newline at end of file From 7f5a1cd953bfe39a3fad25cc18be685490a4553a Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 25 May 2024 19:37:43 -0400 Subject: [PATCH 11/20] render: a bit of code cleanup --- lyra-game/src/render/graph/execution_path.rs | 89 ----------- lyra-game/src/render/graph/mod.rs | 138 ++++++++++-------- .../render/graph/passes/light_cull_compute.rs | 3 +- 3 files changed, 82 insertions(+), 148 deletions(-) delete mode 100644 lyra-game/src/render/graph/execution_path.rs diff --git a/lyra-game/src/render/graph/execution_path.rs b/lyra-game/src/render/graph/execution_path.rs deleted file mode 100644 index bb16ed8..0000000 --- a/lyra-game/src/render/graph/execution_path.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::collections::{HashMap, VecDeque}; - -use rustc_hash::{FxHashMap, FxHashSet}; - -use super::RenderGraphPassDesc; - -pub struct GraphExecutionPath { - /// Queue of the path, top is the first to be executed. - /// Each element is the handle of a pass. - pub queue: VecDeque, -} - -impl GraphExecutionPath { - pub fn new(pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { - // collect all the output slots - let mut total_outputs = HashMap::new(); - total_outputs.reserve(pass_descriptions.len()); - - for desc in pass_descriptions.iter() { - for slot in desc.output_slots() { - total_outputs.insert(slot.name.clone(), SlotOwnerPair { - pass: desc.id, - slot: slot.id, - }); - } - } - - let mut nodes = FxHashMap::::default(); - for desc in pass_descriptions.iter() { - // find the node inputs - let mut inputs = vec![]; - for slot in desc.input_slots() { - let inp = total_outputs.get(&slot.name) - .expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name)); - inputs.push(*inp); - } - - let node = Node { - id: desc.id, - desc: (*desc), - slot_inputs: inputs - }; - nodes.insert(node.id, node); - } - - // sort the graph - let mut stack = VecDeque::new(); - let mut visited = FxHashSet::default(); - for (_, no) in nodes.iter() { - Self::topological_sort(&nodes, &mut stack, &mut visited, no); - } - - Self { - queue: stack, - } - } - - fn topological_sort(graph: &FxHashMap, stack: &mut VecDeque, visited: &mut FxHashSet, node: &Node) { - if !visited.contains(&node.id) { - visited.insert(node.id); - - for depend in &node.slot_inputs { - let depend_node = graph.get(&depend.pass) - .expect("could not find dependent node"); - - if !visited.contains(&depend.pass) { - Self::topological_sort(graph, stack, visited, depend_node); - } - } - - stack.push_back(node.id); - } - - } -} - -#[allow(dead_code)] -#[derive(Debug, Clone, Copy)] -struct SlotOwnerPair { - pass: u64, - slot: u64, -} - -#[allow(dead_code)] -struct Node<'a> { - id: u64, - desc: &'a RenderGraphPassDesc, - slot_inputs: Vec, -} diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 84c82b1..b04f8b2 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -16,17 +16,12 @@ pub use passes::*; mod slot_desc; pub use slot_desc::*; -mod execution_path; - use rustc_hash::FxHashMap; use tracing::{debug_span, instrument, trace, warn}; use wgpu::ComputePass; -use self::execution_path::GraphExecutionPath; - use super::resource::{ComputePipeline, Pipeline, RenderPipeline}; -//#[derive(Clone)] struct PassEntry { inner: Arc>, desc: Arc, @@ -45,7 +40,6 @@ pub struct BindGroupEntry { #[allow(dead_code)] struct ResourcedSlot { name: String, - //slot: RenderPassSlot, ty: SlotType, value: SlotValue, } @@ -74,13 +68,13 @@ pub struct RenderGraph { passes: FxHashMap, // TODO: Use a SlotMap bind_groups: FxHashMap, - bind_group_names: FxHashMap, + bind_group_names: HashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. pipelines: FxHashMap, current_id: u64, - exec_path: Option, - new_path: petgraph::matrix_graph::DiMatrix, usize>, + /// A directed graph describing the execution path of the RenderGraph + execution_graph: petgraph::matrix_graph::DiMatrix, usize>, } impl RenderGraph { @@ -95,8 +89,7 @@ impl RenderGraph { bind_group_names: Default::default(), pipelines: Default::default(), current_id: 1, - exec_path: None, - new_path: Default::default(), + execution_graph: Default::default(), } } @@ -132,7 +125,8 @@ impl RenderGraph { trace!( "Found existing slot for {}, changing id to {}", - slot.name, id + slot.name, + id ); // if there is a slot of the same name @@ -163,7 +157,7 @@ impl RenderGraph { self.bind_group_names.insert(name.clone(), bg_id); } - let index = self.new_path.add_node(desc.id); + let index = self.execution_graph.add_node(desc.id); self.passes.insert( desc.id, @@ -182,14 +176,18 @@ impl RenderGraph { for pass in self.passes.values() { if let Some(pipeline_desc) = &pass.desc.pipeline_desc { let pipeline = match pass.desc.pass_type { - RenderPassType::Render => { - Pipeline::Render(RenderPipeline::create(device, pipeline_desc.as_render_pipeline_descriptor() - .expect("got compute pipeline descriptor in a render pass"))) - }, - RenderPassType::Compute => { - Pipeline::Compute(ComputePipeline::create(device, pipeline_desc.as_compute_pipeline_descriptor() - .expect("got render pipeline descriptor in a compute pass"))) - }, + RenderPassType::Render => Pipeline::Render(RenderPipeline::create( + device, + pipeline_desc + .as_render_pipeline_descriptor() + .expect("got compute pipeline descriptor in a render pass"), + )), + RenderPassType::Compute => Pipeline::Compute(ComputePipeline::create( + device, + pipeline_desc + .as_compute_pipeline_descriptor() + .expect("got render pipeline descriptor in a compute pass"), + )), RenderPassType::Presenter | RenderPassType::Node => { panic!("Present or Node RenderGraph passes should not have a pipeline descriptor!"); } @@ -234,25 +232,19 @@ impl RenderGraph { self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes); } } - - // create the execution path for the graph. This will be executed in `RenderGraph::render` - let descs = self.passes.values().map(|p| &*p.desc).collect(); - let path = GraphExecutionPath::new(descs); - trace!( - "Found {} steps in the rendergraph to execute", - path.queue.len() - ); - - self.exec_path = Some(path); } #[instrument(skip(self))] pub fn render(&mut self) { - let mut sorted: VecDeque = petgraph::algo::toposort(&self.new_path, None) + let mut sorted: VecDeque = petgraph::algo::toposort(&self.execution_graph, None) .expect("RenderGraph had cycled!") - .iter().map(|i| self.new_path[i.clone()]) + .iter() + .map(|i| self.execution_graph[i.clone()]) .collect(); - let path_names = sorted.iter().map(|i| self.pass(*i).unwrap().name.clone()).collect_vec(); + let path_names = sorted + .iter() + .map(|i| self.pass(*i).unwrap().name.clone()) + .collect_vec(); trace!("Render graph execution order: {:?}", path_names); let mut encoders = Vec::with_capacity(self.passes.len() / 2); @@ -293,9 +285,12 @@ impl RenderGraph { } if !encoders.is_empty() { - warn!("{} encoders were not submitted in the same render cycle they were created. \ + warn!( + "{} encoders were not submitted in the same render cycle they were created. \ Make sure there is a presenting pass at the end. You may still see something, \ - however it will be delayed a render cycle.", encoders.len()); + however it will be delayed a render cycle.", + encoders.len() + ); self.queue.submit(encoders.into_iter()); } } @@ -344,13 +339,59 @@ impl RenderGraph { } pub fn add_edge(&mut self, from: &str, to: &str) { - let from_idx = self.passes.iter().find(|p| p.1.desc.name == from).map(|p| p.1.graph_index) + let from_idx = self + .passes + .iter() + .find(|p| p.1.desc.name == from) + .map(|p| p.1.graph_index) .expect("Failed to find from pass"); - let to_idx = self.passes.iter().find(|p| p.1.desc.name == to).map(|p| p.1.graph_index) + let to_idx = self + .passes + .iter() + .find(|p| p.1.desc.name == to) + .map(|p| p.1.graph_index) .expect("Failed to find to pass"); - self.new_path.add_edge(from_idx, to_idx, ()); - //self.new_path.add_edge(NodeIndex::new(from_id as usize), NodeIndex::new(to_id as usize), ()); + self.execution_graph.add_edge(from_idx, to_idx, ()); + } + + /// Utility method for setting the bind groups for a pass. + /// + /// The parameter `bind_groups` can be used to specify the labels of a bind group, and the + /// index of the bind group in the pipeline for the pass. If a bind group of the provided + /// name is not found in the graph, a panic will occur. + /// + /// # Example: + /// ```rust,nobuild + /// graph.set_bind_groups( + /// &mut pass, + /// &[ + /// // retrieves the "depth_texture" bind group and sets the index 0 in the + /// // pass to it. + /// ("depth_texture", 0), + /// ("camera", 1), + /// ("light_buffers", 2), + /// ("light_indices_grid", 3), + /// ("screen_size", 4), + /// ], + /// ); + /// ``` + /// + /// # Panics + /// Panics if a bind group of a provided name is not found. + pub fn set_bind_groups<'a>( + &'a self, + pass: &mut ComputePass<'a>, + bind_groups: &[(&str, u32)], + ) { + for (name, index) in bind_groups { + let bg = self + .bind_group_id(name) + .map(|bgi| self.bind_group(bgi)) + .expect(&format!("Could not find bind group '{}'", name)); + + pass.set_bind_group(*index, bg, &[]); + } } } @@ -428,21 +469,4 @@ impl<'a> RenderGraphContext<'a> { ) { self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes)); } - - pub fn get_bind_groups<'b>(&self, graph: &'b RenderGraph, bind_group_names: &[&str]) -> Vec>> { - bind_group_names - .iter() - .map(|name| graph.bind_group_id(name)) - .map(|bgi| bgi.map(|bgi| graph.bind_group(bgi))) - .collect() - } - - pub fn set_bind_groups<'b, 'c>(graph: &'b RenderGraph, pass: &'c mut ComputePass<'b>, bind_groups: &[(&str, u32)]) { - for (name, index) in bind_groups { - let bg = graph.bind_group_id(name).map(|bgi| graph.bind_group(bgi)) - .expect(&format!("Could not find bind group '{}'", name)); - - pass.set_bind_group(*index, bg, &[]); - } - } } diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index 10ec948..01a21ef 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -236,8 +236,7 @@ impl RenderGraphPass for LightCullComputePass { pass.set_bind_group(3, grid_bg, &[]); pass.set_bind_group(4, screen_size_bg, &[]); */ - RenderGraphContext::set_bind_groups( - graph, + graph.set_bind_groups( &mut pass, &[ ("depth_texture", 0), From c846d52b0d6cff8df94c8b15433e927586b424c6 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 31 May 2024 20:11:35 -0400 Subject: [PATCH 12/20] render: finally get meshes and entities rendering again with the render graph! --- examples/simple_scene/src/main.rs | 2 +- lyra-game/src/render/graph/mod.rs | 12 +- lyra-game/src/render/graph/passes/base.rs | 4 +- lyra-game/src/render/graph/passes/meshes.rs | 560 ++++++++++++++++++ lyra-game/src/render/graph/passes/mod.rs | 3 + lyra-game/src/render/renderer.rs | 111 +--- lyra-game/src/render/resource/shader.rs | 10 + lyra-game/src/render/texture.rs | 5 +- .../src/render/transform_buffer_storage.rs | 7 +- 9 files changed, 606 insertions(+), 108 deletions(-) create mode 100644 lyra-game/src/render/graph/passes/meshes.rs diff --git a/examples/simple_scene/src/main.rs b/examples/simple_scene/src/main.rs index 5faefb0..64eca82 100644 --- a/examples/simple_scene/src/main.rs +++ b/examples/simple_scene/src/main.rs @@ -125,7 +125,7 @@ fn setup_scene_plugin(game: &mut Game) { world.spawn(( cube_mesh.clone(), WorldTransform::default(), - Transform::from_xyz(0.0, -5.0, -2.0), + Transform::from_xyz(0.0, 0.0, -2.0), )); { diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index b04f8b2..ba50510 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -205,7 +205,7 @@ impl RenderGraph { #[instrument(skip(self, world))] pub fn prepare(&mut self, world: &mut World) { // prepare all passes - let mut context = RenderGraphContext::new(&self.queue, None); + let mut context = RenderGraphContext::new(&self.device, &self.queue, None); for (_, pass) in &mut self.passes { let mut inner = pass.inner.borrow_mut(); inner.prepare(world, &mut context); @@ -266,8 +266,10 @@ impl RenderGraph { None }; - let queue = self.queue.clone(); // clone is required to appease the borrow checker - let mut context = RenderGraphContext::new(&queue, encoder); + // clone of the Rc's is required to appease the borrow checker + let device = self.device.clone(); + let queue = self.queue.clone(); + let mut context = RenderGraphContext::new(&device, &queue, encoder); // all encoders need to be submitted before a presenter node is executed. if pass_desc.pass_type == RenderPassType::Presenter { @@ -407,15 +409,17 @@ pub(crate) struct GraphBufferWrite { pub struct RenderGraphContext<'a> { /// Becomes None when the encoder is submitted pub(crate) encoder: Option, + pub(crate) device: &'a wgpu::Device, pub(crate) queue: &'a wgpu::Queue, pub(crate) buffer_writes: VecDeque, renderpass_desc: Vec>, } impl<'a> RenderGraphContext<'a> { - pub fn new(queue: &'a wgpu::Queue, encoder: Option) -> Self { + pub(crate) fn new(device: &'a wgpu::Device, queue: &'a wgpu::Queue, encoder: Option) -> Self { Self { encoder, + device, queue, buffer_writes: Default::default(), renderpass_desc: vec![], diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index 9700f0a..59a610e 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -90,7 +90,7 @@ impl RenderGraphPass for BasePass { let mut desc = RenderGraphPassDesc::new( graph.next_id(), "base", - RenderPassType::Render, + RenderPassType::Node, None, vec![ ("depth_texture", depth_texture_bg, Some(depth_texture_bgl)), @@ -117,7 +117,7 @@ impl RenderGraphPass for BasePass { Some(SlotValue::Lazy), ); desc.add_texture_view_slot( - self.window_tv_id, + graph.next_id(), "depth_texture_view", SlotAttribute::Output, Some(SlotValue::TextureView(depth_texture_view)), diff --git a/lyra-game/src/render/graph/passes/meshes.rs b/lyra-game/src/render/graph/passes/meshes.rs new file mode 100644 index 0000000..1b07d68 --- /dev/null +++ b/lyra-game/src/render/graph/passes/meshes.rs @@ -0,0 +1,560 @@ +use std::{collections::{HashSet, VecDeque}, rc::Rc}; + +use glam::Vec3; +use itertools::izip; +use lyra_ecs::{query::{filter::{Has, Not, Or}, Entities, Res, TickOf}, relation::{ChildOf, RelationOriginComponent}, Component, Entity}; +use lyra_math::Transform; +use lyra_resource::{gltf::Mesh, ResHandle}; +use lyra_scene::{SceneGraph, WorldTransform}; +use rustc_hash::FxHashMap; +use tracing::{debug, instrument, warn}; +use uuid::Uuid; +use wgpu::util::DeviceExt; + +use crate::{ + render::{ + desc_buf_lay::DescVertexBufferLayout, graph::{ + RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, + RenderPassType, + }, material::{Material, MaterialUniform}, render_buffer::{BufferStorage, BufferWrapper}, render_job::RenderJob, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, texture::RenderTexture, transform_buffer_storage::{TransformBuffers, TransformGroup}, vertex::Vertex + }, + DeltaTime, +}; + +type MeshHandle = ResHandle; +type SceneHandle = ResHandle; + +struct MeshBufferStorage { + buffer_vertex: BufferStorage, + buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>, + + //#[allow(dead_code)] + //render_texture: Option, + material: Option>, + + // The index of the transform for this entity. + // The tuple is structured like this: (transform index, index of transform inside the buffer) + //transform_index: TransformBufferIndices, +} + +#[derive(Clone, Debug, Component)] +struct InterpTransform { + last_transform: Transform, + alpha: f32, +} + +#[derive(Default)] +pub struct MeshPass { + transforms: Option, + mesh_buffers: FxHashMap, + render_jobs: VecDeque, + + texture_bind_group_layout: Option>, + material_buffer: Option, + material_buffers: FxHashMap>, + entity_meshes: FxHashMap, + + default_texture: Option, +} + +impl MeshPass { + pub fn new() -> Self { + Self::default() + } + + /// Checks if the mesh buffers in the GPU need to be updated. + #[instrument(skip(self, device, queue, mesh_han))] + fn check_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh_han: &ResHandle) { + let mesh_uuid = mesh_han.uuid(); + + if let (Some(mesh), Some(buffers)) = (mesh_han.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) { + // check if the buffer sizes dont match. If they dont, completely remake the buffers + let vertices = mesh.position().unwrap(); + if buffers.buffer_vertex.count() != vertices.len() { + debug!("Recreating buffers for mesh {}", mesh_uuid.to_string()); + let (vert, idx) = self.create_vertex_index_buffers(device, &mesh); + + // have to re-get buffers because of borrow checker + let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap(); + buffers.buffer_indices = idx; + buffers.buffer_vertex = vert; + + return; + } + + // update vertices + let vertex_buffer = buffers.buffer_vertex.buffer(); + let vertices = vertices.as_slice(); + // align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT) + let (_, vertices, _) = bytemuck::pod_align_to::(vertices); + queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices)); + + // update the indices if they're given + if let Some(index_buffer) = buffers.buffer_indices.as_ref() { + let aligned_indices = match mesh.indices.as_ref().unwrap() { + // U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size. + lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::(v).1, + lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::(v).1, + }; + + let index_buffer = index_buffer.1.buffer(); + queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices)); + } + } + } + + #[instrument(skip(self, device, mesh))] + fn create_vertex_index_buffers(&mut self, device: &wgpu::Device, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) { + let positions = mesh.position().unwrap(); + let tex_coords: Vec = mesh.tex_coords().cloned() + .unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]); + let normals = mesh.normals().unwrap(); + + assert!(positions.len() == tex_coords.len() && positions.len() == normals.len()); + + let mut vertex_inputs = vec![]; + for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) { + vertex_inputs.push(Vertex::new(*v, *t, *n)); + } + + let vertex_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(), + usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST, + } + ); + let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len()); + + let indices = match mesh.indices.as_ref() { + Some(indices) => { + let (idx_type, len, contents) = match indices { + lyra_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)), + lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)), + }; + + let index_buffer = device.create_buffer_init( + &wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents, + usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST, + } + ); + + let buffer_indices = BufferStorage::new(index_buffer, 0, len); + + Some((idx_type, buffer_indices)) + }, + None => { + None + } + }; + + ( vertex_buffer, indices ) + } + + #[instrument(skip(self, device, queue, mesh))] + fn create_mesh_buffers(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, mesh: &Mesh) -> MeshBufferStorage { + let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(device, mesh); + + let material = mesh.material.as_ref() + .expect("Material resource not loaded yet"); + let material_ref = material.data_ref() + .unwrap(); + + let material = self.material_buffers.entry(material.uuid()) + .or_insert_with(|| { + debug!(uuid=material.uuid().to_string(), "Sending material to gpu"); + Rc::new(Material::from_resource(&device, &queue, self.texture_bind_group_layout.clone().unwrap(), &material_ref)) + }); + + // TODO: support material uniforms from multiple uniforms + let uni = MaterialUniform::from(&**material); + queue.write_buffer(&self.material_buffer.as_ref().unwrap(), 0, bytemuck::bytes_of(&uni)); + + MeshBufferStorage { + buffer_vertex: vertex_buffer, + buffer_indices, + material: Some(material.clone()), + } + } + + /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. + #[instrument(skip(self, device, queue, transform, mesh, entity))] + fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool { + let _ = transform; + /* if self.transform_buffers.should_expand() { + self.transform_buffers.expand_buffers(&self.device); + } + + self.transform_buffers.update_or_insert(&self.queue, &self.render_limits, + entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); */ + + #[allow(clippy::map_entry)] + if !self.mesh_buffers.contains_key(&mesh_uuid) { + // create the mesh's buffers + let buffers = self.create_mesh_buffers(device, queue, mesh); + self.mesh_buffers.insert(mesh_uuid, buffers); + self.entity_meshes.insert(entity, mesh_uuid); + + true + } else { false } + } +} + +impl RenderGraphPass for MeshPass { + fn desc( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + ) -> crate::render::graph::RenderGraphPassDesc { + + let device = graph.device(); + + let transforms = TransformBuffers::new(device); + let transform_bgl = transforms.bindgroup_layout.clone(); + self.transforms = Some(transforms); + + let texture_bind_group_layout = Rc::new(RenderTexture::create_layout(&device)); + self.texture_bind_group_layout = Some(texture_bind_group_layout.clone()); + + let (material_bgl, material_bg, material_buf, _) = BufferWrapper::builder() + .label_prefix("material") + .visibility(wgpu::ShaderStages::FRAGMENT) + .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) + .contents(&[MaterialUniform::default()]) + .finish_parts(device); + let material_bgl = Rc::new(material_bgl); + let material_bg = Rc::new(material_bg); + + self.material_buffer = Some(material_buf); + + // load the default texture + let bytes = include_bytes!("../../default_texture.png"); + self.default_texture = Some(RenderTexture::from_bytes(&device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap()); + + // get surface config format + let main_rt = graph.slot_id("main_render_target") + .and_then(|s| graph.slot_value(s)) + .and_then(|s| s.as_render_target()) + .expect("missing main render target"); + let surface_config_format = main_rt.surface_config.format; + drop(main_rt); + + // get the id here to make borrow checker happy + let pass_id = graph.next_id(); + + let camera_bgl = graph.bind_group_layout(graph.bind_group_id("camera").unwrap()); + let lights_bgl = graph.bind_group_layout(graph.bind_group_id("light_buffers").unwrap()); + let light_grid_bgl = graph + .bind_group_layout(graph.bind_group_id("light_indices_grid") + .expect("Missing light grid bind group")); + + let shader = Rc::new(Shader { + label: Some("base_shader".into()), + source: include_str!("../../shaders/base.wgsl").to_string(), + }); + + let desc = RenderGraphPassDesc::new( + pass_id, + "meshes", + RenderPassType::Render, + Some(PipelineDescriptor::Render(RenderPipelineDescriptor { + label: Some("meshes".into()), + layouts: vec![ + texture_bind_group_layout.clone(), + transform_bgl, + camera_bgl.clone(), + lights_bgl.clone(), + material_bgl.clone(), + texture_bind_group_layout, + light_grid_bgl.clone(), + ], + push_constant_ranges: vec![], + vertex: VertexState { + module: shader.clone(), + entry_point: "vs_main".into(), + buffers: vec![ + Vertex::desc().into(), + ], + }, + fragment: Some(FragmentState { + module: shader, + entry_point: "fs_main".into(), + targets: vec![Some(wgpu::ColorTargetState { + format: surface_config_format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + }), + depth_stencil: Some(wgpu::DepthStencilState { + format: RenderTexture::DEPTH_FORMAT, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Less, + stencil: wgpu::StencilState::default(), // TODO: stencil buffer + bias: wgpu::DepthBiasState::default(), + }), + primitive: wgpu::PrimitiveState::default(), + multisample: wgpu::MultisampleState::default(), + multiview: None, + })), + vec![ + ("material", material_bg, Some(material_bgl)), + ], + ); + + desc + } + + #[instrument(skip(self, world, context))] + fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) { + let device = context.device; + let queue = context.queue; + let render_limits = device.limits(); + + let last_epoch = world.current_tick(); + let mut alive_entities = HashSet::new(); + + let view = world.view_iter::<( + Entities, + &Transform, + TickOf, + Or< + (&MeshHandle, TickOf), + (&SceneHandle, TickOf) + >, + Option<&mut InterpTransform>, + Res, + )>(); + + // used to store InterpTransform components to add to entities later + let mut component_queue: Vec<(Entity, InterpTransform)> = vec![]; + + for ( + entity, + transform, + _transform_epoch, + ( + mesh_pair, + scene_pair + ), + interp_tran, + delta_time, + ) in view + { + alive_entities.insert(entity); + + let interp_transform = match interp_tran { + Some(mut interp_transform) => { + // found in https://youtu.be/YJB1QnEmlTs?t=472 + interp_transform.alpha = 1.0 - interp_transform.alpha.powf(**delta_time); + + interp_transform.last_transform = interp_transform.last_transform.lerp(*transform, interp_transform.alpha); + interp_transform.last_transform + }, + None => { + let interp = InterpTransform { + last_transform: *transform, + alpha: 0.5, + }; + component_queue.push((entity, interp)); + + *transform + } + }; + + if let Some((mesh_han, mesh_epoch)) = mesh_pair { + if let Some(mesh) = mesh_han.data_ref() { + // if process mesh did not just create a new mesh, and the epoch + // shows that the scene has changed, verify that the mesh buffers + // dont need to be resent to the gpu. + if !self.process_mesh(device, queue, entity, interp_transform, &*mesh, mesh_han.uuid()) + && mesh_epoch == last_epoch { + self.check_mesh_buffers(device, queue, &mesh_han); + } + + let transforms = self.transforms.as_mut().unwrap(); + if transforms.needs_expand() { + debug!("Expanding transform buffers"); + transforms.expand_buffers(device); + } + + let group = TransformGroup::EntityRes(entity, mesh_han.uuid()); + let transform_id = transforms.update_or_push(device, queue, &render_limits, + group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation)); + + let material = mesh.material.as_ref().unwrap() + .data_ref().unwrap(); + let shader = material.shader_uuid.unwrap_or(0); + let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); + self.render_jobs.push_back(job); + } + } + + if let Some((scene_han, scene_epoch)) = scene_pair { + if let Some(scene) = scene_han.data_ref() { + if scene_epoch == last_epoch { + let view = scene.world().view::<(Entities, &mut WorldTransform, &Transform, Not>>)>(); + lyra_scene::system_update_world_transforms(scene.world(), view).unwrap(); + } + + for (mesh_han, pos) in scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() { + if let Some(mesh) = mesh_han.data_ref() { + let mesh_interpo = interp_transform + **pos; + + // if process mesh did not just create a new mesh, and the epoch + // shows that the scene has changed, verify that the mesh buffers + // dont need to be resent to the gpu. + if !self.process_mesh(device, queue, entity, mesh_interpo, &*mesh, mesh_han.uuid()) + && scene_epoch == last_epoch { + self.check_mesh_buffers(device, queue, &mesh_han); + } + + let transforms = self.transforms.as_mut().unwrap(); + if transforms.needs_expand() { + debug!("Expanding transform buffers"); + transforms.expand_buffers(device); + } + + let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); + let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); + let transform_id = transforms.update_or_push(device, queue, &render_limits, + group, mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) ); + + let material = mesh.material.as_ref().unwrap() + .data_ref().unwrap(); + let shader = material.shader_uuid.unwrap_or(0); + let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id); + self.render_jobs.push_back(job); + } + } + } + } + } + + for (en, interp) in component_queue { + world.insert(en, interp); + } + + let transforms = self.transforms.as_mut().unwrap(); + transforms.send_to_gpu(queue); + } + + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + desc: &crate::render::graph::RenderGraphPassDesc, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let encoder = context.encoder.as_mut().unwrap(); + + let view = graph + .slot_value(graph.slot_id("window_texture_view").unwrap()) + .unwrap() + .as_texture_view(); + + let depth_view = graph + .slot_value(graph.slot_id("depth_texture_view").unwrap()) + .unwrap() + .as_texture_view(); + + let camera_bg = graph + .bind_group(graph.bind_group_id("camera") + .expect("Missing camera bind group")); + + let lights_bg = graph + .bind_group(graph.bind_group_id("light_buffers") + .expect("Missing lights bind group")); + + let light_grid_bg = graph + .bind_group(graph.bind_group_id("light_indices_grid") + .expect("Missing light grid bind group")); + + let material_bg = graph + .bind_group(graph.bind_group_id("material") + .expect("Missing material bind group")); + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: true, + }, + })], + // enable depth buffer + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(1.0), + store: true, + }), + stencil_ops: None, + }), + }); + + let pipeline = graph.pipeline(desc.id); + pass.set_pipeline(&pipeline.as_render()); + + //let material_buffer_bg = self.material_buffer.as_ref().unwrap().bindgroup(); + let default_texture = self.default_texture.as_ref().unwrap(); + let transforms = self.transforms.as_mut().unwrap(); + + while let Some(job) = self.render_jobs.pop_front() { + // get the mesh (containing vertices) and the buffers from storage + let buffers = self.mesh_buffers.get(&job.mesh_uuid); + if buffers.is_none() { + warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid); + continue; + } + let buffers = buffers.unwrap(); + + // Bind the optional texture + if let Some(tex) = buffers.material.as_ref() + .and_then(|m| m.diffuse_texture.as_ref()) { + pass.set_bind_group(0, tex.bind_group(), &[]); + } else { + pass.set_bind_group(0, default_texture.bind_group(), &[]); + } + + if let Some(tex) = buffers.material.as_ref() + .and_then(|m| m.specular.as_ref()) + .and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) { + pass.set_bind_group(5, tex.bind_group(), &[]); + } else { + pass.set_bind_group(5, default_texture.bind_group(), &[]); + } + + // Get the bindgroup for job's transform and bind to it using an offset. + let bindgroup = transforms.bind_group(job.transform_id); + let offset = transforms.buffer_offset(job.transform_id); + pass.set_bind_group(1, bindgroup, &[ offset, ]); + + pass.set_bind_group(2, &camera_bg, &[]); + pass.set_bind_group(3, &lights_bg, &[]); + pass.set_bind_group(4, &material_bg, &[]); + + pass.set_bind_group(6, &light_grid_bg, &[]); + + // if this mesh uses indices, use them to draw the mesh + if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() { + let indices_len = indices.count() as u32; + + pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); + pass.set_index_buffer(indices.buffer().slice(..), *idx_type); + pass.draw_indexed(0..indices_len, 0, 0..1); + } else { + let vertex_count = buffers.buffer_vertex.count(); + + pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); + pass.draw(0..vertex_count as u32, 0..1); + } + } + } +} diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index bd590fc..3aff4e2 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -10,6 +10,9 @@ pub use simple_phong::*; */ mod base; pub use base::*; +mod meshes; +pub use meshes::*; + mod light_base; pub use light_base::*; diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 56fbc9e..c1f585f 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -1,35 +1,16 @@ -use std::cell::RefCell; use std::collections::VecDeque; use std::ops::{Deref, DerefMut}; use std::rc::Rc; use std::sync::Arc; -use std::borrow::Cow; -use lyra_ecs::Component; use lyra_ecs::World; -use lyra_scene::SceneGraph; use tracing::{debug, instrument, warn}; -use wgpu::Limits; use winit::window::Window; -use crate::math::Transform; -use crate::render::graph::{BasePass, LightBasePass, LightCullComputePass, PresentPass, TrianglePass}; -use crate::render::material::MaterialUniform; -use crate::render::render_buffer::BufferWrapperBuilder; +use crate::render::graph::{BasePass, LightBasePass, LightCullComputePass, MeshPass, PresentPass}; -use super::camera::CameraUniform; use super::graph::RenderGraph; -use super::light::LightUniformBuffers; -use super::material::Material; -use super::render_buffer::BufferWrapper; -use super::texture::RenderTexture; -use super::transform_buffer_storage::TransformBuffers; -use super::{resource::RenderPipeline, render_buffer::BufferStorage, render_job::RenderJob}; - -use lyra_resource::{gltf::Mesh, ResHandle}; - -type MeshHandle = ResHandle; -type SceneHandle = ResHandle; +use super::{resource::RenderPipeline, render_job::RenderJob}; #[derive(Clone, Copy, Debug)] pub struct ScreenSize(glam::UVec2); @@ -63,25 +44,6 @@ pub trait RenderPass { fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize); } -struct MeshBufferStorage { - buffer_vertex: BufferStorage, - buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>, - - //#[allow(dead_code)] - //render_texture: Option, - material: Option>, - - // The index of the transform for this entity. - // The tuple is structured like this: (transform index, index of transform inside the buffer) - //transform_index: TransformBufferIndices, -} - -#[derive(Clone, Debug, Component)] -pub struct InterpTransform { - last_transform: Transform, - alpha: f32, -} - pub struct BasicRenderer { pub device: Rc, // device does not need to be mutable, no need for refcell pub queue: Rc, @@ -93,24 +55,6 @@ pub struct BasicRenderer { pub render_pipelines: rustc_hash::FxHashMap>, pub render_jobs: VecDeque, - //mesh_buffers: rustc_hash::FxHashMap, // TODO: clean up left over buffers from deleted entities/components - //material_buffers: rustc_hash::FxHashMap>, - //entity_meshes: rustc_hash::FxHashMap, - - //transform_buffers: TransformBuffers, - - render_limits: Limits, - - //inuse_camera: RenderCamera, - //camera_buffer: BufferWrapper, - - //bgl_texture: Rc, - //default_texture: RenderTexture, - //depth_buffer_texture: RenderTexture, - //material_buffer: BufferWrapper, - //light_buffers: LightUniformBuffers, - //light_cull_compute: LightCullCompute, - graph: RenderGraph, } @@ -156,11 +100,8 @@ impl BasicRenderer { None, ).await.unwrap(); - let render_limits = device.limits(); let surface_caps = surface.get_capabilities(&adapter); - let present_mode = surface_caps.present_modes[0]; - debug!("present mode: {:?}", present_mode); let surface_format = surface_caps.formats.iter() @@ -172,43 +113,12 @@ impl BasicRenderer { format: surface_format, width: size.width, height: size.height, - present_mode: wgpu::PresentMode::Immediate, + present_mode: wgpu::PresentMode::default(), //wgpu::PresentMode::Mailbox, // "Fast Vsync" alpha_mode: surface_caps.alpha_modes[0], view_formats: vec![], }; surface.configure(&device, &config); - let bgl_texture = Rc::new(RenderTexture::create_layout(&device)); - - let shader_src = include_str!("shaders/base.wgsl"); - let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Shader"), - source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(shader_src)), - }); - - let transform_buffers = TransformBuffers::new(&device); - let camera_buffer = BufferWrapper::builder() - .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) - .contents(&[CameraUniform::default()]) - .label_prefix("Camera") - .visibility(wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT | wgpu::ShaderStages::COMPUTE) - .buffer_dynamic_offset(false) - .finish(&device); - - let mut depth_texture = RenderTexture::create_depth_texture(&device, &config, "Tex_Depth"); - - // load the default texture - let bytes = include_bytes!("default_texture.png"); - let default_texture = RenderTexture::from_bytes(&device, &queue, bgl_texture.clone(), bytes, "default_texture").unwrap(); - - let light_uniform_buffers = LightUniformBuffers::new(&device); - - let mat_buffer = BufferWrapperBuilder::new() - .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) - .visibility(wgpu::ShaderStages::FRAGMENT) - .contents(&[MaterialUniform::default()]) - .finish(&device); - let device = Rc::new(device); let queue = Rc::new(queue); @@ -220,15 +130,23 @@ impl BasicRenderer { g.add_pass(LightBasePass::new()); debug!("Adding light cull compute pass"); g.add_pass(LightCullComputePass::new(size)); - debug!("Adding triangle pass"); - g.add_pass(TrianglePass::new()); + //debug!("Adding triangle pass"); + //g.add_pass(TrianglePass::new()); + + debug!("Adding mesh pass"); + g.add_pass(MeshPass::new()); + debug!("Adding present pass"); g.add_pass(PresentPass::new("main_render_target")); g.add_edge("base", "light_base"); g.add_edge("light_base", "light_cull_compute"); - g.add_edge("base", "triangle"); + g.add_edge("base", "meshes"); + + // make sure that present runs last g.add_edge("base", "present_main_render_target"); + g.add_edge("light_cull_compute", "present_main_render_target"); + g.add_edge("meshes", "present_main_render_target"); g.setup(&device); @@ -246,7 +164,6 @@ impl BasicRenderer { render_pipelines: Default::default(), render_jobs: Default::default(), - render_limits, graph: g, } } diff --git a/lyra-game/src/render/resource/shader.rs b/lyra-game/src/render/resource/shader.rs index fa1c9b4..71c9669 100644 --- a/lyra-game/src/render/resource/shader.rs +++ b/lyra-game/src/render/resource/shader.rs @@ -7,6 +7,16 @@ pub struct VertexBufferLayout { pub attributes: Vec, } +impl<'a> From> for VertexBufferLayout { + fn from(value: wgpu::VertexBufferLayout) -> Self { + Self { + array_stride: value.array_stride, + step_mode: value.step_mode, + attributes: value.attributes.to_vec(), + } + } +} + /// Describes the vertex stage in a render pipeline. #[derive(Debug, Clone)] pub struct VertexState { diff --git a/lyra-game/src/render/texture.rs b/lyra-game/src/render/texture.rs index 64e2b59..c3927dd 100755 --- a/lyra-game/src/render/texture.rs +++ b/lyra-game/src/render/texture.rs @@ -273,7 +273,10 @@ impl RenderTexture { }; let texture = device.create_texture(&desc); - let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + let view = texture.create_view(&wgpu::TextureViewDescriptor { + format: Some(wgpu::TextureFormat::Depth32Float), + ..Default::default() + }); let sampler = device.create_sampler( &wgpu::SamplerDescriptor { // 4. address_mode_u: wgpu::AddressMode::ClampToEdge, diff --git a/lyra-game/src/render/transform_buffer_storage.rs b/lyra-game/src/render/transform_buffer_storage.rs index 9fae477..34796c4 100644 --- a/lyra-game/src/render/transform_buffer_storage.rs +++ b/lyra-game/src/render/transform_buffer_storage.rs @@ -1,4 +1,4 @@ -use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64}; +use std::{collections::{HashMap, VecDeque}, hash::{BuildHasher, DefaultHasher, Hash, Hasher, RandomState}, num::NonZeroU64, rc::Rc}; use lyra_ecs::Entity; use tracing::instrument; @@ -162,7 +162,7 @@ impl CachedValMap, //groups: CachedValMap, //groups: SlotMap, entries: Vec, @@ -192,7 +192,7 @@ impl TransformBuffers { }); let mut s = Self { - bindgroup_layout, + bindgroup_layout: Rc::new(bindgroup_layout), entries: Default::default(), max_transform_count: (limits.max_uniform_buffer_binding_size) as usize / (limits.min_uniform_buffer_offset_alignment as usize), //(mem::size_of::()), limits, @@ -209,6 +209,7 @@ impl TransformBuffers { /// /// This uses [`wgpu::Queue::write_buffer`], so the write is not immediately submitted, /// and instead enqueued internally to happen at the start of the next submit() call. + #[instrument(skip(self, queue))] pub fn send_to_gpu(&mut self, queue: &wgpu::Queue) { self.next_index = 0; From bb21805278f2e9934e8e26b1285d21f951c640db Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 1 Jun 2024 22:55:50 -0400 Subject: [PATCH 13/20] render: add a debug_assert to ensure the developer doesn't reuse ids for slots --- lyra-game/src/render/graph/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index ba50510..21fe746 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -132,6 +132,11 @@ impl RenderGraph { // if there is a slot of the same name slot.id = *id; } else { + debug_assert!(!self.slots.contains_key(&slot.id), + "Reuse of id detected in render graph! Pass: {}, slot: {}", + desc.name, slot.name, + ); + let res_slot = ResourcedSlot { name: slot.name.clone(), ty: slot.ty, From 41d77c5687516964fb564604e3485f21e39f732a Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 1 Jun 2024 23:05:43 -0400 Subject: [PATCH 14/20] render: a tiny bit of code cleanup in mesh pass --- lyra-game/src/render/graph/passes/meshes.rs | 50 +++++++++------------ 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/lyra-game/src/render/graph/passes/meshes.rs b/lyra-game/src/render/graph/passes/meshes.rs index 1b07d68..0e366f1 100644 --- a/lyra-game/src/render/graph/passes/meshes.rs +++ b/lyra-game/src/render/graph/passes/meshes.rs @@ -28,13 +28,9 @@ struct MeshBufferStorage { buffer_vertex: BufferStorage, buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>, - //#[allow(dead_code)] - //render_texture: Option, + // maybe this should just be a Uuid and the material can be retrieved though + // MeshPass's `material_buffers` field? material: Option>, - - // The index of the transform for this entity. - // The tuple is structured like this: (transform index, index of transform inside the buffer) - //transform_index: TransformBufferIndices, } #[derive(Clone, Debug, Component)] @@ -180,16 +176,8 @@ impl MeshPass { } /// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed. - #[instrument(skip(self, device, queue, transform, mesh, entity))] - fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool { - let _ = transform; - /* if self.transform_buffers.should_expand() { - self.transform_buffers.expand_buffers(&self.device); - } - - self.transform_buffers.update_or_insert(&self.queue, &self.render_limits, - entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); */ - + #[instrument(skip(self, device, queue, mesh, entity))] + fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, mesh: &Mesh, mesh_uuid: Uuid) -> bool { #[allow(clippy::map_entry)] if !self.mesh_buffers.contains_key(&mesh_uuid) { // create the mesh's buffers @@ -343,6 +331,9 @@ impl RenderGraphPass for MeshPass { { alive_entities.insert(entity); + // Interpolate the transform for this entity using a component. + // If the entity does not have the component then it will be queued to be added + // to it after all the entities are prepared for rendering. let interp_transform = match interp_tran { Some(mut interp_transform) => { // found in https://youtu.be/YJB1QnEmlTs?t=472 @@ -362,22 +353,28 @@ impl RenderGraphPass for MeshPass { } }; + { + // expand the transform buffers if they need to be. + // this is done in its own scope to avoid multiple mutable references to self at + // once; aka, make the borrow checker happy + let transforms = self.transforms.as_mut().unwrap(); + if transforms.needs_expand() { + debug!("Expanding transform buffers"); + transforms.expand_buffers(device); + } + } + if let Some((mesh_han, mesh_epoch)) = mesh_pair { if let Some(mesh) = mesh_han.data_ref() { // if process mesh did not just create a new mesh, and the epoch // shows that the scene has changed, verify that the mesh buffers // dont need to be resent to the gpu. - if !self.process_mesh(device, queue, entity, interp_transform, &*mesh, mesh_han.uuid()) + if !self.process_mesh(device, queue, entity, &*mesh, mesh_han.uuid()) && mesh_epoch == last_epoch { self.check_mesh_buffers(device, queue, &mesh_han); } - - let transforms = self.transforms.as_mut().unwrap(); - if transforms.needs_expand() { - debug!("Expanding transform buffers"); - transforms.expand_buffers(device); - } + let transforms = self.transforms.as_mut().unwrap(); let group = TransformGroup::EntityRes(entity, mesh_han.uuid()); let transform_id = transforms.update_or_push(device, queue, &render_limits, group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation)); @@ -404,17 +401,12 @@ impl RenderGraphPass for MeshPass { // if process mesh did not just create a new mesh, and the epoch // shows that the scene has changed, verify that the mesh buffers // dont need to be resent to the gpu. - if !self.process_mesh(device, queue, entity, mesh_interpo, &*mesh, mesh_han.uuid()) + if !self.process_mesh(device, queue, entity, &*mesh, mesh_han.uuid()) && scene_epoch == last_epoch { self.check_mesh_buffers(device, queue, &mesh_han); } let transforms = self.transforms.as_mut().unwrap(); - if transforms.needs_expand() { - debug!("Expanding transform buffers"); - transforms.expand_buffers(device); - } - let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid()); let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into()); let transform_id = transforms.update_or_push(device, queue, &render_limits, From ef68b2a4c5bb6d95a2ca5bd35e1023a74c47c8f5 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 2 Jun 2024 21:35:59 -0400 Subject: [PATCH 15/20] render: create a RenderGraphLabel trait for graph labels instead of strings --- Cargo.lock | 10 ++ lyra-game/Cargo.toml | 1 + lyra-game/lyra-game-derive/Cargo.toml | 14 ++ lyra-game/lyra-game-derive/src/lib.rs | 35 ++++ lyra-game/src/render/graph/mod.rs | 151 ++++++++++++----- lyra-game/src/render/graph/pass.rs | 48 +++--- lyra-game/src/render/graph/passes/base.rs | 36 +++-- .../src/render/graph/passes/light_base.rs | 16 +- .../render/graph/passes/light_cull_compute.rs | 46 ++++-- lyra-game/src/render/graph/passes/meshes.rs | 35 ++-- lyra-game/src/render/graph/passes/mod.rs | 11 +- .../src/render/graph/passes/present_pass.rs | 32 +++- lyra-game/src/render/graph/passes/triangle.rs | 153 ------------------ lyra-game/src/render/renderer.rs | 18 +-- 14 files changed, 317 insertions(+), 289 deletions(-) create mode 100644 lyra-game/lyra-game-derive/Cargo.toml create mode 100644 lyra-game/lyra-game-derive/src/lib.rs delete mode 100644 lyra-game/src/render/graph/passes/triangle.rs diff --git a/Cargo.lock b/Cargo.lock index ebb7297..a2362f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1867,6 +1867,7 @@ dependencies = [ "instant", "itertools 0.11.0", "lyra-ecs", + "lyra-game-derive", "lyra-math", "lyra-reflect", "lyra-resource", @@ -1887,6 +1888,15 @@ dependencies = [ "winit", ] +[[package]] +name = "lyra-game-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.51", +] + [[package]] name = "lyra-math" version = "0.1.0" diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index 197f2f8..efba477 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -4,6 +4,7 @@ version = "0.0.1" edition = "2021" [dependencies] +lyra-game-derive = { path = "./lyra-game-derive" } lyra-resource = { path = "../lyra-resource" } lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] } diff --git a/lyra-game/lyra-game-derive/Cargo.toml b/lyra-game/lyra-game-derive/Cargo.toml new file mode 100644 index 0000000..a176391 --- /dev/null +++ b/lyra-game/lyra-game-derive/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "lyra-game-derive" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.70" +quote = "1.0.33" +syn = "2.0.41" \ No newline at end of file diff --git a/lyra-game/lyra-game-derive/src/lib.rs b/lyra-game/lyra-game-derive/src/lib.rs new file mode 100644 index 0000000..1de240a --- /dev/null +++ b/lyra-game/lyra-game-derive/src/lib.rs @@ -0,0 +1,35 @@ +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(RenderGraphLabel)] +pub fn derive_render_graph_label(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let type_ident = &input.ident; + + proc_macro::TokenStream::from(quote! { + impl #impl_generics crate::render::graph::RenderGraphLabel for #type_ident #ty_generics #where_clause { + fn rc_clone(&self) -> std::rc::Rc { + std::rc::Rc::new(self.clone()) + } + + /* fn as_dyn(&self) -> &dyn crate::render::graph::RenderGraphLabel { + &self + } + + fn as_partial_eq(&self) -> &dyn PartialEq { + self + } */ + + fn as_label_hash(&self) -> u64 { + let tyid = ::std::any::TypeId::of::(); + + let mut s = ::std::hash::DefaultHasher::new(); + ::std::hash::Hash::hash(&tyid, &mut s); + ::std::hash::Hash::hash(self, &mut s); + ::std::hash::Hasher::finish(&s) + } + } + }) +} \ No newline at end of file diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 21fe746..27b71c4 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -1,9 +1,6 @@ mod pass; use std::{ - cell::RefCell, - collections::{HashMap, VecDeque}, - rc::Rc, - sync::Arc, + cell::RefCell, collections::{HashMap, VecDeque}, fmt::Debug, hash::Hash, rc::Rc, sync::Arc }; use itertools::Itertools; @@ -22,6 +19,62 @@ use wgpu::ComputePass; use super::resource::{ComputePipeline, Pipeline, RenderPipeline}; +pub trait RenderGraphLabel: Debug + 'static { + fn rc_clone(&self) -> Rc; + //fn as_dyn(&self) -> &dyn RenderGraphLabel; + //fn as_partial_eq(&self) -> &dyn PartialEq; + fn as_label_hash(&self) -> u64; + + fn label_eq_rc(&self, other: &Rc) -> bool { + self.as_label_hash() == other.as_label_hash() + } + + fn label_eq(&self, other: &dyn RenderGraphLabel) -> bool { + self.as_label_hash() == other.as_label_hash() + } +} + +#[derive(Clone)] +pub struct RenderGraphLabelValue(Rc); + +impl Debug for RenderGraphLabelValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +impl From for RenderGraphLabelValue { + fn from(value: L) -> Self { + Self(Rc::new(value)) + } +} + +impl From> for RenderGraphLabelValue { + fn from(value: Rc) -> Self { + Self(value) + } +} + +impl From<&Rc> for RenderGraphLabelValue { + fn from(value: &Rc) -> Self { + Self(value.clone()) + } +} + +impl Hash for RenderGraphLabelValue { + fn hash(&self, state: &mut H) { + state.write_u64(self.0.as_label_hash()); + } +} + +impl PartialEq for RenderGraphLabelValue { + fn eq(&self, other: &Self) -> bool { + self.0.label_eq_rc(&other.0) + } +} + +impl Eq for RenderGraphLabelValue {} + struct PassEntry { inner: Arc>, desc: Arc, @@ -30,7 +83,7 @@ struct PassEntry { } pub struct BindGroupEntry { - pub name: String, + pub label: RenderGraphLabelValue, /// BindGroup pub bg: Rc, /// BindGroupLayout @@ -39,7 +92,7 @@ pub struct BindGroupEntry { #[allow(dead_code)] struct ResourcedSlot { - name: String, + label: RenderGraphLabelValue, ty: SlotType, value: SlotValue, } @@ -64,11 +117,13 @@ pub struct RenderGraph { device: Rc, queue: Rc, slots: FxHashMap, - slot_names: HashMap, + /// HashMap used to lookup the slot id using the label's hash + slot_label_lookup: FxHashMap, passes: FxHashMap, // TODO: Use a SlotMap bind_groups: FxHashMap, - bind_group_names: HashMap, + /// HashMap used to lookup the bind group id using the label's hash + bind_group_names: FxHashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. pipelines: FxHashMap, @@ -83,7 +138,7 @@ impl RenderGraph { device, queue, slots: Default::default(), - slot_names: Default::default(), + slot_label_lookup: Default::default(), passes: Default::default(), bind_groups: Default::default(), bind_group_names: Default::default(), @@ -102,8 +157,12 @@ impl RenderGraph { self.current_id } - pub fn slot_id(&self, name: &str) -> Option { - self.slot_names.get(name).cloned() + pub(crate) fn slot_id_rc(&self, label: &RenderGraphLabelValue) -> Option { + self.slot_label_lookup.get(&label.clone().into()).cloned() + } + + pub fn slot_id(&self, label: &dyn RenderGraphLabel) -> Option { + self.slot_label_lookup.get(&label.rc_clone().into()).cloned() } #[instrument(skip(self, pass), level = "debug")] @@ -113,19 +172,19 @@ impl RenderGraph { // collect all the slots of the pass for slot in &mut desc.slots { if let Some((id, other)) = self - .slot_names - .get(&slot.name) + .slot_label_lookup + .get(&slot.label) .and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) { debug_assert_eq!( slot.ty, other.ty, - "slot {} in pass {} does not match existing slot of same name", - slot.name, desc.name + "slot {:?} in pass {:?} does not match existing slot of same name", + slot.label, desc.label ); trace!( - "Found existing slot for {}, changing id to {}", - slot.name, + "Found existing slot for {:?}, changing id to {}", + slot.label, id ); @@ -133,33 +192,33 @@ impl RenderGraph { slot.id = *id; } else { debug_assert!(!self.slots.contains_key(&slot.id), - "Reuse of id detected in render graph! Pass: {}, slot: {}", - desc.name, slot.name, + "Reuse of id detected in render graph! Pass: {:?}, slot: {:?}", + desc.label, slot.label, ); let res_slot = ResourcedSlot { - name: slot.name.clone(), + label: slot.label.clone(), ty: slot.ty, value: slot.value.clone().unwrap_or(SlotValue::None), }; self.slots.insert(slot.id, res_slot); - self.slot_names.insert(slot.name.clone(), slot.id); + self.slot_label_lookup.insert(slot.label.clone(), slot.id); } } // get clones of the bind groups and layouts - for (name, bg, bgl) in &desc.bind_groups { + for (label, bg, bgl) in &desc.bind_groups { let bg_id = self.next_id(); self.bind_groups.insert( bg_id, BindGroupEntry { - name: name.clone(), + label: label.clone(), bg: bg.clone(), layout: bgl.clone(), }, ); - self.bind_group_names.insert(name.clone(), bg_id); + self.bind_group_names.insert(label.clone().into(), bg_id); } let index = self.execution_graph.add_node(desc.id); @@ -224,15 +283,15 @@ impl RenderGraph { while let Some(bufwr) = context.buffer_writes.pop_front() { let slot = self .slots - .get(&self.slot_id(&bufwr.target_slot).unwrap()) + .get(&self.slot_id_rc(&bufwr.target_slot).unwrap()) .expect(&format!( - "Failed to find slot '{}' for buffer write", + "Failed to find slot '{:?}' for buffer write", bufwr.target_slot )); let buf = slot .value .as_buffer() - .expect(&format!("Slot '{}' is not a buffer", bufwr.target_slot)); + .expect(&format!("Slot '{:?}' is not a buffer", bufwr.target_slot)); self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes); } @@ -248,7 +307,7 @@ impl RenderGraph { .collect(); let path_names = sorted .iter() - .map(|i| self.pass(*i).unwrap().name.clone()) + .map(|i| self.pass(*i).unwrap().label.clone()) .collect_vec(); trace!("Render graph execution order: {:?}", path_names); @@ -257,7 +316,7 @@ impl RenderGraph { let pass = self.passes.get(&pass_id).unwrap(); let pass_inn = pass.inner.clone(); let pass_desc = pass.desc.clone(); - let label = format!("{} Encoder", pass_desc.name); + let label = format!("{:?} Encoder", pass_desc.label); // encoders are not needed for presenter nodes. let encoder = if pass_desc.pass_type.should_have_pipeline() { @@ -282,7 +341,7 @@ impl RenderGraph { self.queue.submit(encoders.drain(..)); } - trace!("Executing {}", pass_desc.name); + trace!("Executing {:?}", pass_desc.label); let mut inner = pass_inn.borrow_mut(); inner.execute(self, &*pass_desc, &mut context); @@ -341,24 +400,30 @@ impl RenderGraph { } #[inline(always)] - pub fn bind_group_id(&self, name: &str) -> Option { - self.bind_group_names.get(name).copied() + pub fn bind_group_id(&self, label: &dyn RenderGraphLabel) -> Option { + self.bind_group_names.get(&label.rc_clone().into()).copied() } - pub fn add_edge(&mut self, from: &str, to: &str) { + pub fn add_edge(&mut self, from: impl RenderGraphLabel, to: impl RenderGraphLabel) + { + let from = RenderGraphLabelValue::from(from); + let to = RenderGraphLabelValue::from(to); + let from_idx = self .passes .iter() - .find(|p| p.1.desc.name == from) + .find(|p| p.1.desc.label == from) .map(|p| p.1.graph_index) .expect("Failed to find from pass"); let to_idx = self .passes .iter() - .find(|p| p.1.desc.name == to) + .find(|p| p.1.desc.label == to) .map(|p| p.1.graph_index) .expect("Failed to find to pass"); + debug_assert_ne!(from_idx, to_idx, "cannot add edges between the same node"); + self.execution_graph.add_edge(from_idx, to_idx, ()); } @@ -389,13 +454,13 @@ impl RenderGraph { pub fn set_bind_groups<'a>( &'a self, pass: &mut ComputePass<'a>, - bind_groups: &[(&str, u32)], + bind_groups: &[(&dyn RenderGraphLabel, u32)], ) { - for (name, index) in bind_groups { + for (label, index) in bind_groups { let bg = self - .bind_group_id(name) + .bind_group_id(*label) .map(|bgi| self.bind_group(bgi)) - .expect(&format!("Could not find bind group '{}'", name)); + .expect(&format!("Could not find bind group '{:?}'", label)); pass.set_bind_group(*index, bg, &[]); } @@ -405,7 +470,7 @@ impl RenderGraph { /// A queued write to a GPU buffer targeting a graph slot. pub(crate) struct GraphBufferWrite { /// The name of the slot that has the resource that will be written - target_slot: String, + target_slot: RenderGraphLabelValue, offset: u64, bytes: Vec, } @@ -460,9 +525,9 @@ impl<'a> RenderGraphContext<'a> { /// data will be submitted to the GPU queue right after the prepare stage for all passes /// is ran. #[instrument(skip(self, bytes), level="trace", fields(size = bytes.len()))] - pub fn queue_buffer_write(&mut self, target_slot: &str, offset: u64, bytes: &[u8]) { + pub fn queue_buffer_write(&mut self, target_slot: impl RenderGraphLabel, offset: u64, bytes: &[u8]) { self.buffer_writes.push_back(GraphBufferWrite { - target_slot: target_slot.to_string(), + target_slot: target_slot.into(), offset, bytes: bytes.to_vec(), }) @@ -472,7 +537,7 @@ impl<'a> RenderGraphContext<'a> { #[instrument(skip(self, bytes), level="trace", fields(size = std::mem::size_of::()))] pub fn queue_buffer_write_with( &mut self, - target_slot: &str, + target_slot: impl RenderGraphLabel, offset: u64, bytes: T, ) { diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/pass.rs index c995290..6c0f7bb 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/pass.rs @@ -4,7 +4,7 @@ use lyra_ecs::World; use crate::render::resource::PipelineDescriptor; -use super::{RenderGraph, RenderGraphContext, RenderTarget}; +use super::{RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub enum RenderPassType { @@ -96,7 +96,7 @@ pub struct RenderPassSlot { pub ty: SlotType, pub attribute: SlotAttribute, pub id: u64, - pub name: String, + pub label: RenderGraphLabelValue, /// The descriptor of the slot value. /// This will be `None` if this slot is an input. pub value: Option, @@ -159,36 +159,36 @@ impl RenderGraphPipelineInfo { pub struct RenderGraphPassDesc { pub id: u64, - pub name: String, + pub label: RenderGraphLabelValue, pub pass_type: RenderPassType, pub slots: Vec, - slot_names: HashMap, + slot_label_lookup: HashMap, pub pipeline_desc: Option, pub bind_groups: Vec<( - String, + RenderGraphLabelValue, Rc, Option>, )>, } impl RenderGraphPassDesc { - pub fn new( + pub fn new( id: u64, - name: &str, + label: L, pass_type: RenderPassType, pipeline_desc: Option, - bind_groups: Vec<(&str, Rc, Option>)>, + bind_groups: Vec<(&dyn RenderGraphLabel, Rc, Option>)>, ) -> Self { Self { id, - name: name.to_string(), + label: label.into(), pass_type, slots: vec![], - slot_names: HashMap::default(), + slot_label_lookup: HashMap::default(), pipeline_desc, bind_groups: bind_groups .into_iter() - .map(|bg| (bg.0.to_string(), bg.1, bg.2)) + .map(|bg| (bg.0.rc_clone().into(), bg.1, bg.2)) .collect(), } } @@ -199,15 +199,15 @@ impl RenderGraphPassDesc { "input slots should not have values" ); - self.slot_names.insert(slot.name.clone(), slot.id); + self.slot_label_lookup.insert(slot.label.clone().into(), slot.id); self.slots.push(slot); } #[inline(always)] - pub fn add_buffer_slot( + pub fn add_buffer_slot( &mut self, id: u64, - name: &str, + label: L, attribute: SlotAttribute, value: Option, ) { @@ -218,7 +218,7 @@ impl RenderGraphPassDesc { let slot = RenderPassSlot { id, - name: name.to_string(), + label: label.into(), ty: SlotType::Buffer, attribute, value, @@ -227,10 +227,10 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_slot( + pub fn add_texture_slot( &mut self, id: u64, - name: &str, + label: L, attribute: SlotAttribute, value: Option, ) { @@ -241,7 +241,7 @@ impl RenderGraphPassDesc { let slot = RenderPassSlot { id, - name: name.to_string(), + label: label.into(), ty: SlotType::Texture, attribute, value, @@ -250,10 +250,10 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_texture_view_slot( + pub fn add_texture_view_slot( &mut self, id: u64, - name: &str, + label: L, attribute: SlotAttribute, value: Option, ) { @@ -264,7 +264,7 @@ impl RenderGraphPassDesc { let slot = RenderPassSlot { id, - name: name.to_string(), + label: label.into(), ty: SlotType::TextureView, attribute, value, @@ -273,10 +273,10 @@ impl RenderGraphPassDesc { } #[inline(always)] - pub fn add_sampler_slot( + pub fn add_sampler_slot( &mut self, id: u64, - name: &str, + label: L, attribute: SlotAttribute, value: Option, ) { @@ -287,7 +287,7 @@ impl RenderGraphPassDesc { let slot = RenderPassSlot { id, - name: name.to_string(), + label: label.into(), ty: SlotType::Sampler, attribute, value, diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index 59a610e..c89dcc4 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use glam::UVec2; +use lyra_game_derive::RenderGraphLabel; use tracing::warn; use winit::dpi::PhysicalSize; @@ -16,6 +17,19 @@ use crate::{ scene::CameraComponent, }; +#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)] +pub struct BasePassLabel; + +#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)] +pub enum BasePassSlots { + DepthTexture, + ScreenSize, + Camera, + MainRenderTarget, + WindowTextureView, + DepthTextureView, +} + /// Supplies some basic things other passes needs. /// /// screen size buffer, camera buffer, @@ -89,13 +103,13 @@ impl RenderGraphPass for BasePass { let mut desc = RenderGraphPassDesc::new( graph.next_id(), - "base", + BasePassLabel, RenderPassType::Node, None, vec![ - ("depth_texture", depth_texture_bg, Some(depth_texture_bgl)), - ("screen_size", screen_size_bg, Some(screen_size_bgl)), - ("camera", camera_bg, Some(camera_bgl)), + (&BasePassSlots::DepthTexture, depth_texture_bg, Some(depth_texture_bgl)), + (&BasePassSlots::ScreenSize, screen_size_bg, Some(screen_size_bgl)), + (&BasePassSlots::Camera, camera_bg, Some(camera_bgl)), ], ); @@ -104,7 +118,7 @@ impl RenderGraphPass for BasePass { ty: SlotType::RenderTarget, attribute: SlotAttribute::Output, id: self.main_rt_id, - name: "main_render_target".into(), + label: BasePassSlots::MainRenderTarget.into(), value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new( render_target, )))), @@ -112,25 +126,25 @@ impl RenderGraphPass for BasePass { self.window_tv_id = graph.next_id(); desc.add_texture_view_slot( self.window_tv_id, - "window_texture_view", + BasePassSlots::WindowTextureView, SlotAttribute::Output, Some(SlotValue::Lazy), ); desc.add_texture_view_slot( graph.next_id(), - "depth_texture_view", + BasePassSlots::DepthTextureView, SlotAttribute::Output, Some(SlotValue::TextureView(depth_texture_view)), ); desc.add_buffer_slot( graph.next_id(), - "screen_size_buffer", + BasePassSlots::ScreenSize, SlotAttribute::Output, Some(SlotValue::Buffer(Rc::new(screen_size_buf))), ); desc.add_buffer_slot( graph.next_id(), - "camera_buffer", + BasePassSlots::Camera, SlotAttribute::Output, Some(SlotValue::Buffer(Rc::new(camera_buf))), ); @@ -144,7 +158,7 @@ impl RenderGraphPass for BasePass { RenderCamera::new(PhysicalSize::new(self.screen_size.x, self.screen_size.y)); let uniform = render_cam.calc_view_projection(&camera); - context.queue_buffer_write_with("camera_buffer", 0, uniform) + context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform) } else { warn!("Missing camera!"); } @@ -170,7 +184,7 @@ impl RenderGraphPass for BasePass { || rt.surface_config.height != self.screen_size.y { self.screen_size = UVec2::new(rt.surface_config.width, rt.surface_config.height); - context.queue_buffer_write_with("screen_size_buffer", 0, self.screen_size) + context.queue_buffer_write_with(BasePassSlots::ScreenSize, 0, self.screen_size) } let surface_tex = rt.surface.get_current_texture().unwrap(); diff --git a/lyra-game/src/render/graph/passes/light_base.rs b/lyra-game/src/render/graph/passes/light_base.rs index e26c30d..b5176a4 100644 --- a/lyra-game/src/render/graph/passes/light_base.rs +++ b/lyra-game/src/render/graph/passes/light_base.rs @@ -1,3 +1,5 @@ +use lyra_game_derive::RenderGraphLabel; + use crate::render::{ graph::{ RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, @@ -6,6 +8,14 @@ use crate::render::{ light::LightUniformBuffers, }; +#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)] +pub struct LightBasePassLabel; + +#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)] +pub enum LightBasePassSlots { + Lights +} + /// Supplies some basic things other passes needs. /// /// screen size buffer, camera buffer, @@ -31,11 +41,11 @@ impl RenderGraphPass for LightBasePass { let mut desc = RenderGraphPassDesc::new( graph.next_id(), - "light_base", + LightBasePassLabel, RenderPassType::Node, None, vec![( - "light_buffers", + &LightBasePassSlots::Lights, light_buffers.bind_group.clone(), Some(light_buffers.bind_group_layout.clone()), )], @@ -43,7 +53,7 @@ impl RenderGraphPass for LightBasePass { desc.add_buffer_slot( graph.next_id(), - "light_buffers", + LightBasePassSlots::Lights, SlotAttribute::Output, Some(SlotValue::Buffer(light_buffers.buffer.clone())), ); diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index 01a21ef..41ca789 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -1,6 +1,7 @@ use std::{mem, rc::Rc}; use lyra_ecs::World; +use lyra_game_derive::RenderGraphLabel; use wgpu::util::DeviceExt; use crate::render::{ @@ -11,6 +12,19 @@ use crate::render::{ resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader}, }; +use super::{BasePassSlots, LightBasePassSlots}; + +#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)] +pub struct LightCullComputePassLabel; + +#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)] +pub enum LightCullComputePassSlots { + LightGridTexture, + LightGridTextureView, + IndexCounterBuffer, + LightIndicesGridGroup, +} + pub struct LightCullComputePass { workgroup_size: glam::UVec2, } @@ -35,7 +49,7 @@ impl RenderGraphPass for LightCullComputePass { // get the size of the work group for the grid let main_rt = graph - .slot_id("main_render_target") + .slot_id(&BasePassSlots::MainRenderTarget) .and_then(|s| graph.slot_value(s)) .and_then(|s| s.as_render_target()) .expect("missing main render target"); @@ -157,14 +171,14 @@ impl RenderGraphPass for LightCullComputePass { drop(main_rt); let pass_id = graph.next_id(); - let depth_tex_bgl = graph.bind_group_layout(graph.bind_group_id("depth_texture").unwrap()); - let camera_bgl = graph.bind_group_layout(graph.bind_group_id("camera").unwrap()); - let lights_bgl = graph.bind_group_layout(graph.bind_group_id("light_buffers").unwrap()); - let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id("screen_size").unwrap()); + let depth_tex_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::DepthTexture).unwrap()); + let camera_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::Camera).unwrap()); + let lights_bgl = graph.bind_group_layout(graph.bind_group_id(&LightBasePassSlots::Lights).unwrap()); + let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::ScreenSize).unwrap()); let mut desc = RenderGraphPassDesc::new( pass_id, - "light_cull_compute", + LightCullComputePassLabel, RenderPassType::Compute, Some(PipelineDescriptor::Compute(ComputePipelineDescriptor { label: Some("light_cull_pipeline".into()), @@ -180,7 +194,7 @@ impl RenderGraphPass for LightCullComputePass { shader_entry_point: "cs_main".into(), })), vec![( - "light_indices_grid", + &LightCullComputePassSlots::LightIndicesGridGroup, light_indices_bg, Some(light_indices_bg_layout), )], @@ -188,20 +202,20 @@ impl RenderGraphPass for LightCullComputePass { desc.add_texture_view_slot( graph.next_id(), - "window_texture_view", + BasePassSlots::WindowTextureView, SlotAttribute::Input, None, ); desc.add_buffer_slot( graph.next_id(), - "screen_size_buffer", + BasePassSlots::ScreenSize, SlotAttribute::Input, None, ); - desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Input, None); + desc.add_buffer_slot(graph.next_id(), BasePassSlots::Camera, SlotAttribute::Input, None); desc.add_buffer_slot( graph.next_id(), - "index_counter_buffer", + LightCullComputePassSlots::IndexCounterBuffer, SlotAttribute::Output, Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))), ); @@ -239,11 +253,11 @@ impl RenderGraphPass for LightCullComputePass { graph.set_bind_groups( &mut pass, &[ - ("depth_texture", 0), - ("camera", 1), - ("light_buffers", 2), - ("light_indices_grid", 3), - ("screen_size", 4), + (&BasePassSlots::DepthTexture, 0), + (&BasePassSlots::Camera, 1), + (&LightBasePassSlots::Lights, 2), + (&LightCullComputePassSlots::LightIndicesGridGroup, 3), + (&BasePassSlots::ScreenSize, 4), ], ); diff --git a/lyra-game/src/render/graph/passes/meshes.rs b/lyra-game/src/render/graph/passes/meshes.rs index 0e366f1..ac85c61 100644 --- a/lyra-game/src/render/graph/passes/meshes.rs +++ b/lyra-game/src/render/graph/passes/meshes.rs @@ -3,6 +3,7 @@ use std::{collections::{HashSet, VecDeque}, rc::Rc}; use glam::Vec3; use itertools::izip; use lyra_ecs::{query::{filter::{Has, Not, Or}, Entities, Res, TickOf}, relation::{ChildOf, RelationOriginComponent}, Component, Entity}; +use lyra_game_derive::RenderGraphLabel; use lyra_math::Transform; use lyra_resource::{gltf::Mesh, ResHandle}; use lyra_scene::{SceneGraph, WorldTransform}; @@ -21,9 +22,19 @@ use crate::{ DeltaTime, }; +use super::{BasePassSlots, LightBasePassSlots, LightCullComputePassSlots}; + type MeshHandle = ResHandle; type SceneHandle = ResHandle; +#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)] +pub struct MeshesPassLabel; + +#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)] +pub enum MeshesPassSlots { + Material +} + struct MeshBufferStorage { buffer_vertex: BufferStorage, buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>, @@ -221,7 +232,7 @@ impl RenderGraphPass for MeshPass { self.default_texture = Some(RenderTexture::from_bytes(&device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap()); // get surface config format - let main_rt = graph.slot_id("main_render_target") + let main_rt = graph.slot_id(&BasePassSlots::MainRenderTarget) .and_then(|s| graph.slot_value(s)) .and_then(|s| s.as_render_target()) .expect("missing main render target"); @@ -231,10 +242,10 @@ impl RenderGraphPass for MeshPass { // get the id here to make borrow checker happy let pass_id = graph.next_id(); - let camera_bgl = graph.bind_group_layout(graph.bind_group_id("camera").unwrap()); - let lights_bgl = graph.bind_group_layout(graph.bind_group_id("light_buffers").unwrap()); + let camera_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::Camera).unwrap()); + let lights_bgl = graph.bind_group_layout(graph.bind_group_id(&LightBasePassSlots::Lights).unwrap()); let light_grid_bgl = graph - .bind_group_layout(graph.bind_group_id("light_indices_grid") + .bind_group_layout(graph.bind_group_id(&LightCullComputePassSlots::LightIndicesGridGroup) .expect("Missing light grid bind group")); let shader = Rc::new(Shader { @@ -244,7 +255,7 @@ impl RenderGraphPass for MeshPass { let desc = RenderGraphPassDesc::new( pass_id, - "meshes", + MeshesPassLabel, RenderPassType::Render, Some(PipelineDescriptor::Render(RenderPipelineDescriptor { label: Some("meshes".into()), @@ -286,7 +297,7 @@ impl RenderGraphPass for MeshPass { multiview: None, })), vec![ - ("material", material_bg, Some(material_bgl)), + (&MeshesPassSlots::Material, material_bg, Some(material_bgl)), ], ); @@ -440,29 +451,29 @@ impl RenderGraphPass for MeshPass { let encoder = context.encoder.as_mut().unwrap(); let view = graph - .slot_value(graph.slot_id("window_texture_view").unwrap()) + .slot_value(graph.slot_id(&BasePassSlots::WindowTextureView).unwrap()) .unwrap() .as_texture_view(); let depth_view = graph - .slot_value(graph.slot_id("depth_texture_view").unwrap()) + .slot_value(graph.slot_id(&BasePassSlots::DepthTextureView).unwrap()) .unwrap() .as_texture_view(); let camera_bg = graph - .bind_group(graph.bind_group_id("camera") + .bind_group(graph.bind_group_id(&BasePassSlots::Camera) .expect("Missing camera bind group")); let lights_bg = graph - .bind_group(graph.bind_group_id("light_buffers") + .bind_group(graph.bind_group_id(&LightBasePassSlots::Lights) .expect("Missing lights bind group")); let light_grid_bg = graph - .bind_group(graph.bind_group_id("light_indices_grid") + .bind_group(graph.bind_group_id(&LightCullComputePassSlots::LightIndicesGridGroup) .expect("Missing light grid bind group")); let material_bg = graph - .bind_group(graph.bind_group_id("material") + .bind_group(graph.bind_group_id(&MeshesPassSlots::Material) .expect("Missing material bind group")); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index 3aff4e2..967d440 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -1,12 +1,6 @@ mod light_cull_compute; pub use light_cull_compute::*; -/*mod depth_prepass; -pub use depth_prepass::*; */ - -/* mod simple_phong; -pub use simple_phong::*; */ - mod base; pub use base::*; @@ -17,7 +11,4 @@ mod light_base; pub use light_base::*; mod present_pass; -pub use present_pass::*; - -mod triangle; -pub use triangle::*; \ No newline at end of file +pub use present_pass::*; \ No newline at end of file diff --git a/lyra-game/src/render/graph/passes/present_pass.rs b/lyra-game/src/render/graph/passes/present_pass.rs index 221837a..e496595 100644 --- a/lyra-game/src/render/graph/passes/present_pass.rs +++ b/lyra-game/src/render/graph/passes/present_pass.rs @@ -1,16 +1,32 @@ -use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, SlotAttribute, SlotType}; +use std::hash::Hash; + +use lyra_game_derive::RenderGraphLabel; + +use crate::render::graph::{RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, SlotAttribute, SlotType}; + +#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)] +pub struct PresentPassLabel(RenderGraphLabelValue); + +impl PresentPassLabel { + pub fn new(label: impl RenderGraphLabel) -> Self { + Self(label.into()) + } +} /// Supplies some basic things other passes needs. /// /// screen size buffer, camera buffer, pub struct PresentPass { - render_target_slot: String, + /// Label of this pass + label: PresentPassLabel, + //render_target_slot: Rc, } impl PresentPass { - pub fn new(render_target_slot: &str) -> Self { + pub fn new(render_target_slot: impl RenderGraphLabel) -> Self { Self { - render_target_slot: render_target_slot.into(), + //render_target_slot: render_target_slot.rc_clone(), + label: PresentPassLabel::new(render_target_slot), } } } @@ -19,7 +35,7 @@ impl RenderGraphPass for PresentPass { fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::RenderGraphPassDesc { let mut desc = RenderGraphPassDesc::new( graph.next_id(), - &format!("present_{}", self.render_target_slot), + self.label.clone(), RenderPassType::Presenter, None, vec![], @@ -30,7 +46,7 @@ impl RenderGraphPass for PresentPass { ty: SlotType::RenderTarget, attribute: SlotAttribute::Input, id: graph.next_id(), - name: self.render_target_slot.clone(), + label: self.label.0.clone(), value: None, } ); @@ -43,8 +59,8 @@ impl RenderGraphPass for PresentPass { } fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::RenderGraphPassDesc, _context: &mut crate::render::graph::RenderGraphContext) { - let id = graph.slot_id(&self.render_target_slot) - .expect(&format!("render target slot '{}' for PresentPass is missing", self.render_target_slot)); + let id = graph.slot_id_rc(&self.label.0) + .expect(&format!("render target slot '{:?}' for PresentPass is missing", self.label.0)); let mut slot = graph.slot_value_mut(id).unwrap().as_render_target_mut().unwrap(); let surf_tex = slot.current_texture.take().unwrap(); surf_tex.present(); diff --git a/lyra-game/src/render/graph/passes/triangle.rs b/lyra-game/src/render/graph/passes/triangle.rs deleted file mode 100644 index b6417b0..0000000 --- a/lyra-game/src/render/graph/passes/triangle.rs +++ /dev/null @@ -1,153 +0,0 @@ -use std::rc::Rc; - -use crate::{ - render::{ - graph::{ - RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, - SlotAttribute, SlotValue, - }, - render_buffer::BufferWrapper, - resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, - }, - DeltaTime, -}; - -/// A demo pass that renders a triangle that changes colors every frame. -#[derive(Default)] -pub struct TrianglePass { - acc: f32, -} - -impl TrianglePass { - pub fn new() -> Self { - Self::default() - } -} - -impl RenderGraphPass for TrianglePass { - fn desc( - &mut self, - graph: &mut crate::render::graph::RenderGraph, - ) -> crate::render::graph::RenderGraphPassDesc { - let shader = Rc::new(Shader { - label: Some("triangle_shader".into()), - source: include_str!("../../shaders/triangle.wgsl").to_string(), - }); - - let device = graph.device(); - let (color_bgl, color_bg, color_buf, _) = BufferWrapper::builder() - .label_prefix("color") - .visibility(wgpu::ShaderStages::FRAGMENT) - .buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST) - .contents(&[glam::Vec4::new(0.902, 0.639, 0.451, 1.0)]) - .finish_parts(device); - let color_bgl = Rc::new(color_bgl); - let color_bg = Rc::new(color_bg); - - let main_rt = graph.slot_id("main_render_target") - .and_then(|s| graph.slot_value(s)) - .and_then(|s| s.as_render_target()) - .expect("missing main render target"); - let surface_config_format = main_rt.surface_config.format; - drop(main_rt); - - let mut desc = RenderGraphPassDesc::new( - graph.next_id(), - "triangle", - RenderPassType::Render, - Some(PipelineDescriptor::Render(RenderPipelineDescriptor { - label: Some("triangle_pipeline".into()), - layouts: vec![color_bgl.clone()], - push_constant_ranges: vec![], - vertex: VertexState { - module: shader.clone(), - entry_point: "vs_main".into(), - buffers: vec![], - }, - fragment: Some(FragmentState { - module: shader, - entry_point: "fs_main".into(), - targets: vec![Some(wgpu::ColorTargetState { - format: surface_config_format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - }), - depth_stencil: None, - primitive: wgpu::PrimitiveState::default(), - multisample: wgpu::MultisampleState::default(), - multiview: None, - })), - vec![("color_bg", color_bg, Some(color_bgl))], - ); - - desc.add_texture_view_slot( - graph.next_id(), - "window_texture_view", - SlotAttribute::Input, - None, - ); - - desc.add_buffer_slot( - graph.next_id(), - "color_buffer", - SlotAttribute::Output, - Some(SlotValue::Buffer(Rc::new(color_buf))), - ); - - desc - } - - fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) { - const SPEED: f32 = 1.5; - - let dt = **world.get_resource::(); - self.acc += dt; - let x = (self.acc * SPEED).sin(); - let y = ((self.acc + 2.15) * SPEED).sin(); - let z = ((self.acc + 5.35) * SPEED).sin(); - - let color = glam::Vec4::new(x, y, z, 1.0); - context.queue_buffer_write_with("color_buffer", 0, color); - } - - fn execute( - &mut self, - graph: &mut crate::render::graph::RenderGraph, - desc: &crate::render::graph::RenderGraphPassDesc, - context: &mut crate::render::graph::RenderGraphContext, - ) { - let view = graph - .slot_value(graph.slot_id("window_texture_view").unwrap()) - .unwrap() - .as_texture_view(); - let color_bg = graph.bind_group(graph.bind_group_id("color_bg").unwrap()); - - let encoder = context.encoder.as_mut().unwrap(); - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("triangle_pass"), - color_attachments: &[ - // This is what @location(0) in the fragment shader targets - Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.2, - b: 0.3, - a: 1.0, - }), - store: true, - }, - }), - ], - depth_stencil_attachment: None, - }); - - let pipeline = graph.pipeline(desc.id); - pass.set_pipeline(&pipeline.as_render()); - pass.set_bind_group(0, color_bg, &[]); - pass.draw(0..3, 0..1); - } -} diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index c1f585f..353f45d 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -7,7 +7,7 @@ use lyra_ecs::World; use tracing::{debug, instrument, warn}; use winit::window::Window; -use crate::render::graph::{BasePass, LightBasePass, LightCullComputePass, MeshPass, PresentPass}; +use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel}; use super::graph::RenderGraph; use super::{resource::RenderPipeline, render_job::RenderJob}; @@ -137,16 +137,16 @@ impl BasicRenderer { g.add_pass(MeshPass::new()); debug!("Adding present pass"); - g.add_pass(PresentPass::new("main_render_target")); + g.add_pass(PresentPass::new(BasePassSlots::MainRenderTarget)); - g.add_edge("base", "light_base"); - g.add_edge("light_base", "light_cull_compute"); - g.add_edge("base", "meshes"); + g.add_edge(BasePassLabel, LightBasePassLabel); + g.add_edge(LightBasePassLabel, LightCullComputePassLabel); + g.add_edge(BasePassLabel, MeshesPassLabel); // make sure that present runs last - g.add_edge("base", "present_main_render_target"); - g.add_edge("light_cull_compute", "present_main_render_target"); - g.add_edge("meshes", "present_main_render_target"); + g.add_edge(BasePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget)); + g.add_edge(LightCullComputePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget)); + g.add_edge(MeshesPassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget)); g.setup(&device); @@ -188,7 +188,7 @@ impl Renderer for BasicRenderer { self.size = new_size; // update surface config and the surface - let mut rt = self.graph.slot_value_mut(self.graph.slot_id("main_render_target").unwrap()) + let mut rt = self.graph.slot_value_mut(self.graph.slot_id(&BasePassSlots::MainRenderTarget).unwrap()) .unwrap().as_render_target_mut().unwrap(); rt.surface_config.width = new_size.width; rt.surface_config.height = new_size.height; From a0a2acfec05ff73a148e3a7df2e0280f3e0b8a90 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Mon, 3 Jun 2024 19:06:07 -0400 Subject: [PATCH 16/20] render: add todo in code --- lyra-game/src/render/graph/passes/base.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index c89dcc4..ebb6d94 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -107,6 +107,12 @@ impl RenderGraphPass for BasePass { RenderPassType::Node, None, vec![ + // TODO: Make this a trait maybe? + // Could impl it for (RenderGraphLabel, wgpu::BindGroup) and also + // (RenderGraphLabel, wgpu::BindGroup, wgpu::BindGroupLabel) AND + // (RenderGraphLabel, wgpu::BindGroup, Option) + // + // This could make it slightly easier to create this (&BasePassSlots::DepthTexture, depth_texture_bg, Some(depth_texture_bgl)), (&BasePassSlots::ScreenSize, screen_size_bg, Some(screen_size_bgl)), (&BasePassSlots::Camera, camera_bg, Some(camera_bgl)), From 28b9604189024115e1cd3319710db8c127b0fcb2 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 6 Jun 2024 19:37:25 -0400 Subject: [PATCH 17/20] render: rename RenderGraphPass to Node to better represent what it actually is in the RenderGraph A node wont always render or compute, so it wouldn't actually be a pass. Calling it a node is a better representation of what it actually is --- Cargo.lock | 7 + lyra-game/Cargo.toml | 1 + lyra-game/src/render/graph/mod.rs | 49 +++-- .../src/render/graph/{pass.rs => node.rs} | 170 ++++++++++++------ lyra-game/src/render/graph/passes/base.rs | 16 +- .../src/render/graph/passes/depth_prepass.rs | 94 ---------- .../src/render/graph/passes/light_base.rs | 12 +- .../render/graph/passes/light_cull_compute.rs | 12 +- lyra-game/src/render/graph/passes/meshes.rs | 20 ++- .../src/render/graph/passes/present_pass.rs | 14 +- 10 files changed, 191 insertions(+), 204 deletions(-) rename lyra-game/src/render/graph/{pass.rs => node.rs} (57%) delete mode 100644 lyra-game/src/render/graph/passes/depth_prepass.rs diff --git a/Cargo.lock b/Cargo.lock index a2362f3..b5960e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -384,6 +384,12 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bind_match" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171f0236f66c7be99f32060539c2bade94033ded356ecf4c9dc9b1e6198326cd" + [[package]] name = "bit-set" version = "0.5.3" @@ -1859,6 +1865,7 @@ dependencies = [ "anyhow", "async-std", "async-trait", + "bind_match", "bytemuck", "cfg-if", "gilrs-core", diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index efba477..bc28133 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -37,6 +37,7 @@ thiserror = "1.0.56" unique = "0.9.1" rustc-hash = "1.1.0" petgraph = { version = "0.6.5", features = ["matrix_graph"] } +bind_match = "0.1.2" [features] tracy = ["dep:tracing-tracy"] diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 27b71c4..9e99897 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -1,11 +1,11 @@ -mod pass; +mod node; use std::{ cell::RefCell, collections::{HashMap, VecDeque}, fmt::Debug, hash::Hash, rc::Rc, sync::Arc }; use itertools::Itertools; use lyra_ecs::World; -pub use pass::*; +pub use node::*; mod passes; pub use passes::*; @@ -76,8 +76,8 @@ impl PartialEq for RenderGraphLabelValue { impl Eq for RenderGraphLabelValue {} struct PassEntry { - inner: Arc>, - desc: Arc, + inner: Arc>, + desc: Arc, /// The index of the pass in the execution graph graph_index: petgraph::matrix_graph::NodeIndex, } @@ -165,8 +165,18 @@ impl RenderGraph { self.slot_label_lookup.get(&label.rc_clone().into()).cloned() } + /// Add a [`Node`] to the RenderGraph. + /// + /// When the node is added, its [`Node::desc`] method will be executed. + /// + /// Additionally, all [`Slot`](node::NodeSlot)s of the node will be iterated, + /// 1. Ensuring that there are no two slots of the same name, with different value types + /// 2. Changing the id of insert slots to match the id of the output slot of the same name. + /// * This means that the id of insert slots **ARE NOT STABLE**. **DO NOT** rely on them to + /// not change. The IDs of output slots do stay the same. + /// 3. Ensuring that no two slots share the same ID when the names do not match. #[instrument(skip(self, pass), level = "debug")] - pub fn add_pass(&mut self, mut pass: P) { + pub fn add_pass(&mut self, mut pass: P) { let mut desc = pass.desc(self); // collect all the slots of the pass @@ -234,25 +244,28 @@ impl RenderGraph { } /// Creates all buffers required for the passes, also creates an internal execution path. + /// + /// This only needs to be ran when the [`Node`]s in the graph change, or they are removed or + /// added. #[instrument(skip(self, device))] pub fn setup(&mut self, device: &wgpu::Device) { // For all passes, create their pipelines for pass in self.passes.values() { if let Some(pipeline_desc) = &pass.desc.pipeline_desc { - let pipeline = match pass.desc.pass_type { - RenderPassType::Render => Pipeline::Render(RenderPipeline::create( + let pipeline = match pass.desc.ty { + NodeType::Render => Pipeline::Render(RenderPipeline::create( device, pipeline_desc .as_render_pipeline_descriptor() .expect("got compute pipeline descriptor in a render pass"), )), - RenderPassType::Compute => Pipeline::Compute(ComputePipeline::create( + NodeType::Compute => Pipeline::Compute(ComputePipeline::create( device, pipeline_desc .as_compute_pipeline_descriptor() .expect("got render pipeline descriptor in a compute pass"), )), - RenderPassType::Presenter | RenderPassType::Node => { + NodeType::Presenter | NodeType::Node => { panic!("Present or Node RenderGraph passes should not have a pipeline descriptor!"); } }; @@ -319,7 +332,7 @@ impl RenderGraph { let label = format!("{:?} Encoder", pass_desc.label); // encoders are not needed for presenter nodes. - let encoder = if pass_desc.pass_type.should_have_pipeline() { + let encoder = if pass_desc.ty.should_have_pipeline() { Some( self.device .create_command_encoder(&wgpu::CommandEncoderDescriptor { @@ -336,7 +349,7 @@ impl RenderGraph { let mut context = RenderGraphContext::new(&device, &queue, encoder); // all encoders need to be submitted before a presenter node is executed. - if pass_desc.pass_type == RenderPassType::Presenter { + if pass_desc.ty == NodeType::Presenter { trace!("Submitting {} encoderd before presenting", encoders.len()); self.queue.submit(encoders.drain(..)); } @@ -369,7 +382,7 @@ impl RenderGraph { self.slots.get_mut(&id).map(|s| &mut s.value) } - pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { + pub fn pass(&self, id: u64) -> Option<&NodeDesc> { self.passes.get(&id).map(|s| &*s.desc) } @@ -438,13 +451,13 @@ impl RenderGraph { /// graph.set_bind_groups( /// &mut pass, /// &[ - /// // retrieves the "depth_texture" bind group and sets the index 0 in the + /// // retrieves the `BasePassSlots::DepthTexture` bind group and sets the index 0 in the /// // pass to it. - /// ("depth_texture", 0), - /// ("camera", 1), - /// ("light_buffers", 2), - /// ("light_indices_grid", 3), - /// ("screen_size", 4), + /// (&BasePassSlots::DepthTexture, 0), + /// (&BasePassSlots::Camera, 1), + /// (&LightBasePassSlots::Lights, 2), + /// (&LightCullComputePassSlots::LightIndicesGridGroup, 3), + /// (&BasePassSlots::ScreenSize, 4), /// ], /// ); /// ``` diff --git a/lyra-game/src/render/graph/pass.rs b/lyra-game/src/render/graph/node.rs similarity index 57% rename from lyra-game/src/render/graph/pass.rs rename to lyra-game/src/render/graph/node.rs index 6c0f7bb..db45f02 100644 --- a/lyra-game/src/render/graph/pass.rs +++ b/lyra-game/src/render/graph/node.rs @@ -1,5 +1,6 @@ use std::{cell::{Ref, RefCell, RefMut}, collections::HashMap, num::NonZeroU32, rc::Rc}; +use bind_match::bind_match; use lyra_ecs::World; use crate::render::resource::PipelineDescriptor; @@ -7,26 +8,31 @@ use crate::render::resource::PipelineDescriptor; use super::{RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] -pub enum RenderPassType { +pub enum NodeType { /// A node doesn't render, compute, or present anything. This likely means it injects data into the graph. - Node, - Compute, #[default] + Node, + /// A Compute pass node type. + Compute, + /// A render pass node type. Render, + /// A node that presents render results to a render target. Presenter, } -impl RenderPassType { +impl NodeType { + /// Returns a boolean indicating if the node should have a [`Pipeline`](crate::render::resource::Pipeline). pub fn should_have_pipeline(&self) -> bool { match self { - RenderPassType::Node => false, - RenderPassType::Compute => true, - RenderPassType::Render => true, - RenderPassType::Presenter => false, + NodeType::Node => false, + NodeType::Compute => true, + NodeType::Render => true, + NodeType::Presenter => false, } } } +/// The type of data that is stored in a [`Node`] slot. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SlotType { TextureView, @@ -36,10 +42,13 @@ pub enum SlotType { RenderTarget, } +/// The value of a slot in a [`Node`]. #[derive(Debug, Clone)] pub enum SlotValue { + /// This slot doesn't have any value None, - /// The value will be set during a later phase of the render graph. + /// The value will be set during a later phase of the render graph. To see the type of value + /// this will be set to, see the slots type. Lazy, TextureView(Rc), Sampler(Rc), @@ -49,56 +58,51 @@ pub enum SlotValue { } impl SlotValue { - /// Gets `self` as a texture, panics if self is not a instance of [`SlotValue::Texture`]. - pub fn as_texture(&self) -> &wgpu::Texture { - match self { - Self::Texture(v) => v, - _ => panic!("self is not an instance of SlotValue::Texture"), - } + pub fn as_texture_view(&self) -> Option<&Rc> { + bind_match!(self, Self::TextureView(v) => v) } - pub fn as_texture_view(&self) -> &wgpu::TextureView { - match self { - Self::TextureView(v) => v, - _ => panic!("self is not an instance of SlotValue::TextureView"), - } + pub fn as_sampler(&self) -> Option<&Rc> { + bind_match!(self, Self::Sampler(v) => v) } - pub fn as_buffer(&self) -> Option<&wgpu::Buffer> { - match self { - Self::Buffer(v) => Some(v), - _ => None, - } + pub fn as_texture(&self) -> Option<&Rc> { + bind_match!(self, Self::Texture(v) => v) + } + + pub fn as_buffer(&self) -> Option<&Rc> { + bind_match!(self, Self::Buffer(v) => v) } pub fn as_render_target(&self) -> Option> { - match self { - Self::RenderTarget(v) => Some(v.borrow()), - _ => None, - } + bind_match!(self, Self::RenderTarget(v) => v.borrow()) } pub fn as_render_target_mut(&mut self) -> Option> { - match self { - Self::RenderTarget(v) => Some(v.borrow_mut()), - _ => None, - } + bind_match!(self, Self::RenderTarget(v) => v.borrow_mut()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum SlotAttribute { + /// This slot inputs a value into the node, expecting another node to `Output` it. Input, + /// This slot outputs a value from the node, providing the value to other nodes that + /// `Input`it. Output, } #[derive(Clone)] -pub struct RenderPassSlot { +pub struct NodeSlot { + /// The type of the value that this slot inputs/outputs. pub ty: SlotType, + /// The way this slot uses the value. Defines if this slot is an output or input. pub attribute: SlotAttribute, + // What if these are just completely removed and labels are used everywhere instead? pub id: u64, + /// The identifying label of this slot. pub label: RenderGraphLabelValue, - /// The descriptor of the slot value. - /// This will be `None` if this slot is an input. + /// The value of the slot. + /// This is `None` if the slot is a `SlotAttribute::Input` type. pub value: Option, } @@ -157,13 +161,23 @@ impl RenderGraphPipelineInfo { } } -pub struct RenderGraphPassDesc { +/// Descriptor of a Node in a [`RenderGraph`]. +pub struct NodeDesc { pub id: u64, + /// The label of the Node, used to identify the Node. pub label: RenderGraphLabelValue, - pub pass_type: RenderPassType, - pub slots: Vec, + /// The [`NodeType`] of the node. + pub ty: NodeType, + /// The slots that the Node uses. + /// This defines the resources that the node uses and creates in the graph. + pub slots: Vec, slot_label_lookup: HashMap, + /// An optional pipeline descriptor for the Node. + /// This is `None` if the Node type is not a node that requires a pipeline + /// (see [`NodeType::should_have_pipeline`]). pub pipeline_desc: Option, + /// The bind groups that this Node creates. + /// This makes the bind groups accessible to other Nodes. pub bind_groups: Vec<( RenderGraphLabelValue, Rc, @@ -171,18 +185,19 @@ pub struct RenderGraphPassDesc { )>, } -impl RenderGraphPassDesc { +impl NodeDesc { + /// Create a new node descriptor. pub fn new( id: u64, label: L, - pass_type: RenderPassType, + pass_type: NodeType, pipeline_desc: Option, bind_groups: Vec<(&dyn RenderGraphLabel, Rc, Option>)>, ) -> Self { Self { id, label: label.into(), - pass_type, + ty: pass_type, slots: vec![], slot_label_lookup: HashMap::default(), pipeline_desc, @@ -193,7 +208,11 @@ impl RenderGraphPassDesc { } } - pub fn add_slot(&mut self, slot: RenderPassSlot) { + /// Add a slot to the descriptor. + /// + /// In debug builds, there is an assert that triggers if the slot is an input slot and has + /// a value set. + pub fn add_slot(&mut self, slot: NodeSlot) { debug_assert!( !(slot.attribute == SlotAttribute::Input && slot.value.is_some()), "input slots should not have values" @@ -203,6 +222,11 @@ impl RenderGraphPassDesc { self.slots.push(slot); } + /// Add a buffer slot to the descriptor. + /// + /// In debug builds, there is an assert that triggers if the slot is an input slot and has + /// a value set. There is also an assert that is triggered if this slot value is not `None`, + /// `SlotValue::Lazy` or a `Buffer`. #[inline(always)] pub fn add_buffer_slot( &mut self, @@ -216,7 +240,7 @@ impl RenderGraphPassDesc { "slot value is not a buffer" ); - let slot = RenderPassSlot { + let slot = NodeSlot { id, label: label.into(), ty: SlotType::Buffer, @@ -226,6 +250,11 @@ impl RenderGraphPassDesc { self.add_slot(slot); } + /// Add a slot that stores a [`wgpu::Texture`] to the descriptor. + /// + /// In debug builds, there is an assert that triggers if the slot is an input slot and has + /// a value set. There is also an assert that is triggered if this slot value is not `None`, + /// `SlotValue::Lazy` or a `SlotValue::Texture`. #[inline(always)] pub fn add_texture_slot( &mut self, @@ -239,7 +268,7 @@ impl RenderGraphPassDesc { "slot value is not a texture" ); - let slot = RenderPassSlot { + let slot = NodeSlot { id, label: label.into(), ty: SlotType::Texture, @@ -249,6 +278,11 @@ impl RenderGraphPassDesc { self.add_slot(slot); } + /// Add a slot that stores a [`wgpu::TextureView`] to the descriptor. + /// + /// In debug builds, there is an assert that triggers if the slot is an input slot and has + /// a value set. There is also an assert that is triggered if this slot value is not `None`, + /// `SlotValue::Lazy` or a `SlotValue::TextureView`. #[inline(always)] pub fn add_texture_view_slot( &mut self, @@ -262,7 +296,7 @@ impl RenderGraphPassDesc { "slot value is not a texture view" ); - let slot = RenderPassSlot { + let slot = NodeSlot { id, label: label.into(), ty: SlotType::TextureView, @@ -272,6 +306,11 @@ impl RenderGraphPassDesc { self.add_slot(slot); } + /// Add a slot that stores a [`wgpu::Sampler`] to the descriptor. + /// + /// In debug builds, there is an assert that triggers if the slot is an input slot and has + /// a value set. There is also an assert that is triggered if this slot value is not `None`, + /// `SlotValue::Lazy` or a `SlotValue::Sampler`. #[inline(always)] pub fn add_sampler_slot( &mut self, @@ -285,7 +324,7 @@ impl RenderGraphPassDesc { "slot value is not a sampler" ); - let slot = RenderPassSlot { + let slot = NodeSlot { id, label: label.into(), ty: SlotType::Sampler, @@ -295,14 +334,16 @@ impl RenderGraphPassDesc { self.add_slot(slot); } - pub fn input_slots(&self) -> Vec<&RenderPassSlot> { + /// Returns all input slots that the descriptor defines. + pub fn input_slots(&self) -> Vec<&NodeSlot> { self.slots .iter() .filter(|s| s.attribute == SlotAttribute::Input) .collect() } - - pub fn output_slots(&self) -> Vec<&RenderPassSlot> { + + /// Returns all output slots that the descriptor defines. + pub fn output_slots(&self) -> Vec<&NodeSlot> { self.slots .iter() .filter(|s| s.attribute == SlotAttribute::Output) @@ -310,17 +351,34 @@ impl RenderGraphPassDesc { } } -pub trait RenderGraphPass: 'static { - /// Create a render pass describer. - /// - /// The `id` argument is passed as mutable so you can increment it as you use it for new slots. - fn desc<'a, 'b>(&'a mut self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc; +/// A node that can be executed and scheduled in a [`RenderGraph`]. +/// +/// A node can be used for rendering, computing data on the GPU, collecting data from the main +/// world and writing it to GPU buffers, or presenting renders to a surface. +/// +/// The [`RenderGraph`] is ran in phases. The first phase is `prepare`, then `execute`. When a node +/// is first added to a RenderGraph, its [`Node::desc`] function will be ran. The descriptor +/// describes all resources the node requires for execution during the `execute` phase. +pub trait Node: 'static { + /// Retrieve a descriptor of the Node. + fn desc<'a, 'b>(&'a mut self, graph: &'b mut RenderGraph) -> NodeDesc; + /// Prepare the node for rendering. + /// + /// This phase runs before `execute` and is meant to be used to collect data from the World + /// and write to GPU buffers. fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext); + + /// Execute the node. + /// + /// Parameters: + /// * `graph` - The RenderGraph that this node is a part of. Can be used to get bind groups and bind to them. + /// * `desc` - The descriptor of this node. + /// * `context` - The rendering graph context. fn execute( &mut self, graph: &mut RenderGraph, - desc: &RenderGraphPassDesc, + desc: &NodeDesc, context: &mut RenderGraphContext, ); } diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index ebb6d94..cfe8877 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -9,8 +9,8 @@ use crate::{ render::{ camera::{CameraUniform, RenderCamera}, graph::{ - RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, - RenderPassType, RenderTarget, SlotAttribute, SlotType, SlotValue, + RenderGraphContext, Node, NodeDesc, NodeSlot, + NodeType, RenderTarget, SlotAttribute, SlotType, SlotValue, }, render_buffer::BufferWrapper, texture::RenderTexture, }, @@ -61,11 +61,11 @@ impl BasePass { } } -impl RenderGraphPass for BasePass { +impl Node for BasePass { fn desc( &mut self, graph: &mut crate::render::graph::RenderGraph, - ) -> crate::render::graph::RenderGraphPassDesc { + ) -> crate::render::graph::NodeDesc { let render_target = self.temp_render_target.take().unwrap(); self.screen_size = UVec2::new( render_target.surface_config.width, @@ -101,10 +101,10 @@ impl RenderGraphPass for BasePass { let depth_texture_bgl = dt_bg_pair.layout; let depth_texture_view = Rc::new(depth_texture.view); - let mut desc = RenderGraphPassDesc::new( + let mut desc = NodeDesc::new( graph.next_id(), BasePassLabel, - RenderPassType::Node, + NodeType::Node, None, vec![ // TODO: Make this a trait maybe? @@ -120,7 +120,7 @@ impl RenderGraphPass for BasePass { ); self.main_rt_id = graph.next_id(); - desc.add_slot(RenderPassSlot { + desc.add_slot(NodeSlot { ty: SlotType::RenderTarget, attribute: SlotAttribute::Output, id: self.main_rt_id, @@ -173,7 +173,7 @@ impl RenderGraphPass for BasePass { fn execute( &mut self, graph: &mut crate::render::graph::RenderGraph, - _desc: &crate::render::graph::RenderGraphPassDesc, + _desc: &crate::render::graph::NodeDesc, context: &mut crate::render::graph::RenderGraphContext, ) { let tv_slot = graph diff --git a/lyra-game/src/render/graph/passes/depth_prepass.rs b/lyra-game/src/render/graph/passes/depth_prepass.rs deleted file mode 100644 index 85f654c..0000000 --- a/lyra-game/src/render/graph/passes/depth_prepass.rs +++ /dev/null @@ -1,94 +0,0 @@ -use glam::UVec2; - -use crate::render::{ - camera::CameraUniform, - graph::{ - BufferInitDescriptor, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SamplerDescriptor, SlotAttribute, SlotDescriptor, TextureDescriptor, TextureViewDescriptor - }, -}; - -/// Supplies some basic things other passes needs. -/// -/// screen size buffer, camera buffer, -#[derive(Default)] -pub struct DepthPrePass; - -impl DepthPrePass { - pub fn new() -> Self { - Self::default() - } -} - -impl RenderGraphPass for DepthPrePass { - fn desc( - &self, - graph: &mut crate::render::graph::RenderGraph, - id: &mut u64, - ) -> crate::render::graph::RenderGraphPassDesc { - let mut desc = RenderGraphPassDesc::new(*id, "DepthPrePass", RenderPassType::Compute); - *id += 1; - - let size = wgpu::Extent3d { - width: graph.surface_config.width, - height: graph.surface_config.height, - depth_or_array_layers: 1, - }; - - desc.add_texture_slot( - *id, - "depth_texture", - SlotAttribute::Output, - Some(SlotDescriptor::Texture(TextureDescriptor { - size, - mip_level_count: 1, - sample_count: 1, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth32Float, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT - | wgpu::TextureUsages::TEXTURE_BINDING, - view_formats: vec![], - })), - ); - *id += 1; - - desc.add_texture_view_slot( - *id, - "depth_texture_view", - SlotAttribute::Output, - Some(SlotDescriptor::TextureView(TextureViewDescriptor::default_view("depth_texture"))), - ); - *id += 1; - - desc.add_texture_view_slot( - *id, - "depth_texture_sampler", - SlotAttribute::Output, - Some(SlotDescriptor::Sampler(SamplerDescriptor { - mag_filter: wgpu::FilterMode::Linear, - min_filter: wgpu::FilterMode::Linear, - compare: Some(wgpu::CompareFunction::LessEqual), - lod_min_clamp: 0.0, - lod_max_clamp: 100.0, - ..SamplerDescriptor::default_sampler("depth_texture") - })), - ); - *id += 1; - - desc - } - - fn prepare(&mut self, world: &mut lyra_ecs::World) { - let _ = world; - todo!() - } - - fn execute( - &mut self, - graph: &mut crate::render::graph::RenderGraph, - desc: &crate::render::graph::RenderGraphPassDesc, - context: &mut crate::render::graph::RenderGraphContext, - ) { - let _ = (graph, desc, context); - todo!() - } -} diff --git a/lyra-game/src/render/graph/passes/light_base.rs b/lyra-game/src/render/graph/passes/light_base.rs index b5176a4..31e7af3 100644 --- a/lyra-game/src/render/graph/passes/light_base.rs +++ b/lyra-game/src/render/graph/passes/light_base.rs @@ -2,7 +2,7 @@ use lyra_game_derive::RenderGraphLabel; use crate::render::{ graph::{ - RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, + RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute, SlotValue, }, light::LightUniformBuffers, @@ -30,19 +30,19 @@ impl LightBasePass { } } -impl RenderGraphPass for LightBasePass { +impl Node for LightBasePass { fn desc( &mut self, graph: &mut crate::render::graph::RenderGraph, - ) -> crate::render::graph::RenderGraphPassDesc { + ) -> crate::render::graph::NodeDesc { let device = &graph.device; self.light_buffers = Some(LightUniformBuffers::new(device)); let light_buffers = self.light_buffers.as_ref().unwrap(); - let mut desc = RenderGraphPassDesc::new( + let mut desc = NodeDesc::new( graph.next_id(), LightBasePassLabel, - RenderPassType::Node, + NodeType::Node, None, vec![( &LightBasePassSlots::Lights, @@ -70,7 +70,7 @@ impl RenderGraphPass for LightBasePass { fn execute( &mut self, _graph: &mut crate::render::graph::RenderGraph, - _desc: &crate::render::graph::RenderGraphPassDesc, + _desc: &crate::render::graph::NodeDesc, _context: &mut crate::render::graph::RenderGraphContext, ) { } diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index 41ca789..dec5d74 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -6,7 +6,7 @@ use wgpu::util::DeviceExt; use crate::render::{ graph::{ - RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, + RenderGraphContext, Node, NodeDesc, NodeType, SlotAttribute, SlotValue, }, resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader}, @@ -37,11 +37,11 @@ impl LightCullComputePass { } } -impl RenderGraphPass for LightCullComputePass { +impl Node for LightCullComputePass { fn desc( &mut self, graph: &mut crate::render::graph::RenderGraph, - ) -> crate::render::graph::RenderGraphPassDesc { + ) -> crate::render::graph::NodeDesc { let shader = Rc::new(Shader { label: Some("light_cull_comp_shader".into()), source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(), @@ -176,10 +176,10 @@ impl RenderGraphPass for LightCullComputePass { let lights_bgl = graph.bind_group_layout(graph.bind_group_id(&LightBasePassSlots::Lights).unwrap()); let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::ScreenSize).unwrap()); - let mut desc = RenderGraphPassDesc::new( + let mut desc = NodeDesc::new( pass_id, LightCullComputePassLabel, - RenderPassType::Compute, + NodeType::Compute, Some(PipelineDescriptor::Compute(ComputePipelineDescriptor { label: Some("light_cull_pipeline".into()), push_constant_ranges: vec![], @@ -228,7 +228,7 @@ impl RenderGraphPass for LightCullComputePass { fn execute( &mut self, graph: &mut crate::render::graph::RenderGraph, - desc: &crate::render::graph::RenderGraphPassDesc, + desc: &crate::render::graph::NodeDesc, context: &mut RenderGraphContext, ) { let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor { diff --git a/lyra-game/src/render/graph/passes/meshes.rs b/lyra-game/src/render/graph/passes/meshes.rs index ac85c61..a773587 100644 --- a/lyra-game/src/render/graph/passes/meshes.rs +++ b/lyra-game/src/render/graph/passes/meshes.rs @@ -15,8 +15,8 @@ use wgpu::util::DeviceExt; use crate::{ render::{ desc_buf_lay::DescVertexBufferLayout, graph::{ - RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, - RenderPassType, + RenderGraphContext, Node, NodeDesc, + NodeType, }, material::{Material, MaterialUniform}, render_buffer::{BufferStorage, BufferWrapper}, render_job::RenderJob, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, texture::RenderTexture, transform_buffer_storage::{TransformBuffers, TransformGroup}, vertex::Vertex }, DeltaTime, @@ -201,11 +201,11 @@ impl MeshPass { } } -impl RenderGraphPass for MeshPass { +impl Node for MeshPass { fn desc( &mut self, graph: &mut crate::render::graph::RenderGraph, - ) -> crate::render::graph::RenderGraphPassDesc { + ) -> crate::render::graph::NodeDesc { let device = graph.device(); @@ -253,10 +253,10 @@ impl RenderGraphPass for MeshPass { source: include_str!("../../shaders/base.wgsl").to_string(), }); - let desc = RenderGraphPassDesc::new( + let desc = NodeDesc::new( pass_id, MeshesPassLabel, - RenderPassType::Render, + NodeType::Render, Some(PipelineDescriptor::Render(RenderPipelineDescriptor { label: Some("meshes".into()), layouts: vec![ @@ -445,7 +445,7 @@ impl RenderGraphPass for MeshPass { fn execute( &mut self, graph: &mut crate::render::graph::RenderGraph, - desc: &crate::render::graph::RenderGraphPassDesc, + desc: &crate::render::graph::NodeDesc, context: &mut crate::render::graph::RenderGraphContext, ) { let encoder = context.encoder.as_mut().unwrap(); @@ -453,12 +453,14 @@ impl RenderGraphPass for MeshPass { let view = graph .slot_value(graph.slot_id(&BasePassSlots::WindowTextureView).unwrap()) .unwrap() - .as_texture_view(); + .as_texture_view() + .expect("BasePassSlots::WindowTextureView was not a TextureView slot"); let depth_view = graph .slot_value(graph.slot_id(&BasePassSlots::DepthTextureView).unwrap()) .unwrap() - .as_texture_view(); + .as_texture_view() + .expect("BasePassSlots::DepthTextureView was not a TextureView slot"); let camera_bg = graph .bind_group(graph.bind_group_id(&BasePassSlots::Camera) diff --git a/lyra-game/src/render/graph/passes/present_pass.rs b/lyra-game/src/render/graph/passes/present_pass.rs index e496595..2302d8f 100644 --- a/lyra-game/src/render/graph/passes/present_pass.rs +++ b/lyra-game/src/render/graph/passes/present_pass.rs @@ -2,7 +2,7 @@ use std::hash::Hash; use lyra_game_derive::RenderGraphLabel; -use crate::render::graph::{RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, SlotAttribute, SlotType}; +use crate::render::graph::{RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, Node, NodeDesc, NodeSlot, NodeType, SlotAttribute, SlotType}; #[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)] pub struct PresentPassLabel(RenderGraphLabelValue); @@ -31,18 +31,18 @@ impl PresentPass { } } -impl RenderGraphPass for PresentPass { - fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::RenderGraphPassDesc { - let mut desc = RenderGraphPassDesc::new( +impl Node for PresentPass { + fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc { + let mut desc = NodeDesc::new( graph.next_id(), self.label.clone(), - RenderPassType::Presenter, + NodeType::Presenter, None, vec![], ); desc.add_slot( - RenderPassSlot { + NodeSlot { ty: SlotType::RenderTarget, attribute: SlotAttribute::Input, id: graph.next_id(), @@ -58,7 +58,7 @@ impl RenderGraphPass for PresentPass { } - fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::RenderGraphPassDesc, _context: &mut crate::render::graph::RenderGraphContext) { + fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::NodeDesc, _context: &mut crate::render::graph::RenderGraphContext) { let id = graph.slot_id_rc(&self.label.0) .expect(&format!("render target slot '{:?}' for PresentPass is missing", self.label.0)); let mut slot = graph.slot_value_mut(id).unwrap().as_render_target_mut().unwrap(); From 6d7932f6a5f01b877ed141383b177885ba4b88aa Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Tue, 11 Jun 2024 20:59:55 -0400 Subject: [PATCH 18/20] render: remove IDs for everything, use only labels to identify things --- lyra-game/src/render/graph/mod.rs | 210 +++++++++--------- lyra-game/src/render/graph/node.rs | 27 +-- lyra-game/src/render/graph/passes/base.rs | 15 +- .../src/render/graph/passes/light_base.rs | 3 - .../render/graph/passes/light_cull_compute.rs | 42 ++-- lyra-game/src/render/graph/passes/meshes.rs | 37 ++- .../src/render/graph/passes/present_pass.rs | 14 +- lyra-game/src/render/renderer.rs | 13 +- 8 files changed, 150 insertions(+), 211 deletions(-) diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 9e99897..7fd0ec4 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -1,9 +1,8 @@ mod node; use std::{ - cell::RefCell, collections::{HashMap, VecDeque}, fmt::Debug, hash::Hash, rc::Rc, sync::Arc + cell::{Ref, RefCell}, collections::{HashMap, VecDeque}, fmt::Debug, hash::Hash, rc::Rc, sync::Arc }; -use itertools::Itertools; use lyra_ecs::World; pub use node::*; @@ -34,6 +33,8 @@ pub trait RenderGraphLabel: Debug + 'static { } } +pub struct RenderGraphHash(u64); + #[derive(Clone)] pub struct RenderGraphLabelValue(Rc); @@ -77,9 +78,10 @@ impl Eq for RenderGraphLabelValue {} struct PassEntry { inner: Arc>, - desc: Arc, + desc: Rc>, /// The index of the pass in the execution graph graph_index: petgraph::matrix_graph::NodeIndex, + pipeline: Rc>>, } pub struct BindGroupEntry { @@ -116,20 +118,19 @@ pub struct RenderTarget { pub struct RenderGraph { device: Rc, queue: Rc, - slots: FxHashMap, + slots: FxHashMap, /// HashMap used to lookup the slot id using the label's hash - slot_label_lookup: FxHashMap, - passes: FxHashMap, + //slot_label_lookup: FxHashMap, + passes: FxHashMap, // TODO: Use a SlotMap - bind_groups: FxHashMap, + bind_groups: FxHashMap, /// HashMap used to lookup the bind group id using the label's hash - bind_group_names: FxHashMap, + //bind_group_names: FxHashMap, // TODO: make pipelines a `type` parameter in RenderPasses, // then the pipelines can be retrieved via TypeId to the pass. - pipelines: FxHashMap, - current_id: u64, + //pipelines: FxHashMap, /// A directed graph describing the execution path of the RenderGraph - execution_graph: petgraph::matrix_graph::DiMatrix, usize>, + execution_graph: petgraph::matrix_graph::DiMatrix, usize>, } impl RenderGraph { @@ -138,12 +139,8 @@ impl RenderGraph { device, queue, slots: Default::default(), - slot_label_lookup: Default::default(), passes: Default::default(), bind_groups: Default::default(), - bind_group_names: Default::default(), - pipelines: Default::default(), - current_id: 1, execution_graph: Default::default(), } } @@ -152,19 +149,6 @@ impl RenderGraph { &*self.device } - pub fn next_id(&mut self) -> u64 { - self.current_id += 1; - self.current_id - } - - pub(crate) fn slot_id_rc(&self, label: &RenderGraphLabelValue) -> Option { - self.slot_label_lookup.get(&label.clone().into()).cloned() - } - - pub fn slot_id(&self, label: &dyn RenderGraphLabel) -> Option { - self.slot_label_lookup.get(&label.rc_clone().into()).cloned() - } - /// Add a [`Node`] to the RenderGraph. /// /// When the node is added, its [`Node::desc`] method will be executed. @@ -176,34 +160,35 @@ impl RenderGraph { /// not change. The IDs of output slots do stay the same. /// 3. Ensuring that no two slots share the same ID when the names do not match. #[instrument(skip(self, pass), level = "debug")] - pub fn add_pass(&mut self, mut pass: P) { + pub fn add_pass(&mut self, label: impl RenderGraphLabel, mut pass: P) { let mut desc = pass.desc(self); // collect all the slots of the pass for slot in &mut desc.slots { - if let Some((id, other)) = self - .slot_label_lookup - .get(&slot.label) - .and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) + if let Some(other) = self + .slots + .get_mut(&slot.label) + //.map(|s| (id, s)) + //.and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) { debug_assert_eq!( slot.ty, other.ty, "slot {:?} in pass {:?} does not match existing slot of same name", - slot.label, desc.label + slot.label, label ); - trace!( + /* trace!( "Found existing slot for {:?}, changing id to {}", slot.label, id - ); + ); */ // if there is a slot of the same name - slot.id = *id; + //slot.id = *id; } else { - debug_assert!(!self.slots.contains_key(&slot.id), + debug_assert!(!self.slots.contains_key(&slot.label), "Reuse of id detected in render graph! Pass: {:?}, slot: {:?}", - desc.label, slot.label, + label, slot.label, ); let res_slot = ResourcedSlot { @@ -212,33 +197,29 @@ impl RenderGraph { value: slot.value.clone().unwrap_or(SlotValue::None), }; - self.slots.insert(slot.id, res_slot); - self.slot_label_lookup.insert(slot.label.clone(), slot.id); + self.slots.insert(slot.label.clone(), res_slot); } } // get clones of the bind groups and layouts for (label, bg, bgl) in &desc.bind_groups { - let bg_id = self.next_id(); - self.bind_groups.insert( - bg_id, - BindGroupEntry { - label: label.clone(), - bg: bg.clone(), - layout: bgl.clone(), - }, - ); - self.bind_group_names.insert(label.clone().into(), bg_id); + self.bind_groups.insert(label.clone(), BindGroupEntry { + label: label.clone(), + bg: bg.clone(), + layout: bgl.clone(), + }); } - let index = self.execution_graph.add_node(desc.id); + let label: RenderGraphLabelValue = label.into(); + let index = self.execution_graph.add_node(label.clone()); self.passes.insert( - desc.id, + label, PassEntry { inner: Arc::new(RefCell::new(pass)), - desc: Arc::new(desc), + desc: Rc::new(RefCell::new(desc)), graph_index: index, + pipeline: Rc::new(RefCell::new(None)), }, ); } @@ -250,9 +231,10 @@ impl RenderGraph { #[instrument(skip(self, device))] pub fn setup(&mut self, device: &wgpu::Device) { // For all passes, create their pipelines - for pass in self.passes.values() { - if let Some(pipeline_desc) = &pass.desc.pipeline_desc { - let pipeline = match pass.desc.ty { + for pass in self.passes.values_mut() { + let desc = (*pass.desc).borrow(); + if let Some(pipeline_desc) = &desc.pipeline_desc { + let pipeline = match desc.ty { NodeType::Render => Pipeline::Render(RenderPipeline::create( device, pipeline_desc @@ -270,11 +252,14 @@ impl RenderGraph { } }; + drop(desc); let res = PipelineResource { pipeline, bg_layout_name_lookup: Default::default(), }; - self.pipelines.insert(pass.desc.id, res); + + let mut pipeline = pass.pipeline.borrow_mut(); + *pipeline = Some(res); } } } @@ -282,10 +267,16 @@ impl RenderGraph { #[instrument(skip(self, world))] pub fn prepare(&mut self, world: &mut World) { // prepare all passes - let mut context = RenderGraphContext::new(&self.device, &self.queue, None); - for (_, pass) in &mut self.passes { + let mut buffer_writes = VecDeque::::new(); + // reserve some buffer writes. not all nodes write so half the amount of them is probably + // fine. + buffer_writes.reserve(self.passes.len() / 2); + + for (label, pass) in &mut self.passes { + let mut context = RenderGraphContext::new(&self.device, &self.queue, None, label.clone()); let mut inner = pass.inner.borrow_mut(); inner.prepare(world, &mut context); + buffer_writes.append(&mut context.buffer_writes); } { @@ -293,10 +284,10 @@ impl RenderGraph { let s = debug_span!("queue_buffer_writes"); let _e = s.enter(); - while let Some(bufwr) = context.buffer_writes.pop_front() { + while let Some(bufwr) = buffer_writes.pop_front() { let slot = self .slots - .get(&self.slot_id_rc(&bufwr.target_slot).unwrap()) + .get(&bufwr.target_slot) .expect(&format!( "Failed to find slot '{:?}' for buffer write", bufwr.target_slot @@ -313,23 +304,22 @@ impl RenderGraph { #[instrument(skip(self))] pub fn render(&mut self) { - let mut sorted: VecDeque = petgraph::algo::toposort(&self.execution_graph, None) + let mut sorted: VecDeque = petgraph::algo::toposort(&self.execution_graph, None) .expect("RenderGraph had cycled!") .iter() - .map(|i| self.execution_graph[i.clone()]) + .map(|i| self.execution_graph[i.clone()].clone()) .collect(); - let path_names = sorted - .iter() - .map(|i| self.pass(*i).unwrap().label.clone()) - .collect_vec(); - trace!("Render graph execution order: {:?}", path_names); + //debug!("Render graph execution order: {:?}", sorted); let mut encoders = Vec::with_capacity(self.passes.len() / 2); - while let Some(pass_id) = sorted.pop_front() { - let pass = self.passes.get(&pass_id).unwrap(); + while let Some(pass_label) = sorted.pop_front() { + let pass = self.passes.get(&pass_label).unwrap(); let pass_inn = pass.inner.clone(); + let pass_desc = pass.desc.clone(); - let label = format!("{:?} Encoder", pass_desc.label); + let pass_desc = (*pass_desc).borrow(); + + let label = format!("{:?} Encoder", pass_label.0); // encoders are not needed for presenter nodes. let encoder = if pass_desc.ty.should_have_pipeline() { @@ -346,7 +336,7 @@ impl RenderGraph { // clone of the Rc's is required to appease the borrow checker let device = self.device.clone(); let queue = self.queue.clone(); - let mut context = RenderGraphContext::new(&device, &queue, encoder); + let mut context = RenderGraphContext::new(&device, &queue, encoder, pass_label.clone()); // all encoders need to be submitted before a presenter node is executed. if pass_desc.ty == NodeType::Presenter { @@ -354,9 +344,9 @@ impl RenderGraph { self.queue.submit(encoders.drain(..)); } - trace!("Executing {:?}", pass_desc.label); + trace!("Executing {:?}", pass_label.0); let mut inner = pass_inn.borrow_mut(); - inner.execute(self, &*pass_desc, &mut context); + inner.execute(self, &pass_desc, &mut context); if let Some(encoder) = context.encoder { encoders.push(encoder.finish()); @@ -374,49 +364,52 @@ impl RenderGraph { } } - pub fn slot_value(&self, id: u64) -> Option<&SlotValue> { - self.slots.get(&id).map(|s| &s.value) + pub fn slot_value>(&self, label: L) -> Option<&SlotValue> { + self.slots.get(&label.into()).map(|s| &s.value) } - pub fn slot_value_mut(&mut self, id: u64) -> Option<&mut SlotValue> { - self.slots.get_mut(&id).map(|s| &mut s.value) + pub fn slot_value_mut>(&mut self, label: L) -> Option<&mut SlotValue> { + self.slots.get_mut(&label.into()).map(|s| &mut s.value) } - pub fn pass(&self, id: u64) -> Option<&NodeDesc> { - self.passes.get(&id).map(|s| &*s.desc) + pub fn node_desc>(&self, label: L) -> Option> { + self.passes.get(&label.into()).map(|s| (*s.desc).borrow()) } #[inline(always)] - pub fn pipeline(&self, id: u64) -> &Pipeline { - &self.pipelines.get(&id).unwrap().pipeline + pub fn pipeline>(&self, label: L) -> Option> { + self.passes.get(&label.into()) + .and_then(|p| { + let v = p.pipeline.borrow(); + + match &*v { + Some(_) => Some(Ref::map(v, |p| &p.as_ref().unwrap().pipeline)), + None => None, + } + }) } #[inline(always)] - pub fn try_bind_group(&self, id: u64) -> Option<&Rc> { - self.bind_groups.get(&id).map(|e| &e.bg) + pub fn try_bind_group>(&self, label: L) -> Option<&Rc> { + self.bind_groups.get(&label.into()).map(|e| &e.bg) } #[inline(always)] - pub fn bind_group(&self, id: u64) -> &Rc { - self.try_bind_group(id).expect("Unknown id for bind group") + pub fn bind_group>(&self, label: L) -> &Rc { + self.try_bind_group(label).expect("Unknown id for bind group") } #[inline(always)] - pub fn try_bind_group_layout(&self, id: u64) -> Option<&Rc> { - self.bind_groups.get(&id).and_then(|e| e.layout.as_ref()) + pub fn try_bind_group_layout>(&self, label: L) -> Option<&Rc> { + self.bind_groups.get(&label.into()).and_then(|e| e.layout.as_ref()) } #[inline(always)] - pub fn bind_group_layout(&self, id: u64) -> &Rc { - self.try_bind_group_layout(id) + pub fn bind_group_layout>(&self, label: L) -> &Rc { + self.try_bind_group_layout(label) .expect("Unknown id for bind group layout") } - #[inline(always)] - pub fn bind_group_id(&self, label: &dyn RenderGraphLabel) -> Option { - self.bind_group_names.get(&label.rc_clone().into()).copied() - } - pub fn add_edge(&mut self, from: impl RenderGraphLabel, to: impl RenderGraphLabel) { let from = RenderGraphLabelValue::from(from); @@ -425,13 +418,13 @@ impl RenderGraph { let from_idx = self .passes .iter() - .find(|p| p.1.desc.label == from) + .find(|p| *p.0 == from) .map(|p| p.1.graph_index) .expect("Failed to find from pass"); let to_idx = self .passes .iter() - .find(|p| p.1.desc.label == to) + .find(|p| *p.0 == to) .map(|p| p.1.graph_index) .expect("Failed to find to pass"); @@ -471,9 +464,8 @@ impl RenderGraph { ) { for (label, index) in bind_groups { let bg = self - .bind_group_id(*label) - .map(|bgi| self.bind_group(bgi)) - .expect(&format!("Could not find bind group '{:?}'", label)); + .bind_group(label.rc_clone()); + //.expect(&format!("Could not find bind group '{:?}'", label)); pass.set_bind_group(*index, bg, &[]); } @@ -490,22 +482,28 @@ pub(crate) struct GraphBufferWrite { #[allow(dead_code)] pub struct RenderGraphContext<'a> { - /// Becomes None when the encoder is submitted - pub(crate) encoder: Option, - pub(crate) device: &'a wgpu::Device, - pub(crate) queue: &'a wgpu::Queue, + /// The [`wgpu::CommandEncoder`] used to encode GPU operations. + /// + /// This is `None` during the `prepare` stage. + pub encoder: Option, + /// The gpu device that is being used. + pub device: &'a wgpu::Device, + pub queue: &'a wgpu::Queue, pub(crate) buffer_writes: VecDeque, renderpass_desc: Vec>, + /// The label of this Node. + pub label: RenderGraphLabelValue, } impl<'a> RenderGraphContext<'a> { - pub(crate) fn new(device: &'a wgpu::Device, queue: &'a wgpu::Queue, encoder: Option) -> Self { + pub(crate) fn new(device: &'a wgpu::Device, queue: &'a wgpu::Queue, encoder: Option, label: RenderGraphLabelValue) -> Self { Self { encoder, device, queue, buffer_writes: Default::default(), renderpass_desc: vec![], + label, } } diff --git a/lyra-game/src/render/graph/node.rs b/lyra-game/src/render/graph/node.rs index db45f02..806594e 100644 --- a/lyra-game/src/render/graph/node.rs +++ b/lyra-game/src/render/graph/node.rs @@ -1,4 +1,4 @@ -use std::{cell::{Ref, RefCell, RefMut}, collections::HashMap, num::NonZeroU32, rc::Rc}; +use std::{cell::{Ref, RefCell, RefMut}, num::NonZeroU32, rc::Rc}; use bind_match::bind_match; use lyra_ecs::World; @@ -97,8 +97,6 @@ pub struct NodeSlot { pub ty: SlotType, /// The way this slot uses the value. Defines if this slot is an output or input. pub attribute: SlotAttribute, - // What if these are just completely removed and labels are used everywhere instead? - pub id: u64, /// The identifying label of this slot. pub label: RenderGraphLabelValue, /// The value of the slot. @@ -163,15 +161,12 @@ impl RenderGraphPipelineInfo { /// Descriptor of a Node in a [`RenderGraph`]. pub struct NodeDesc { - pub id: u64, - /// The label of the Node, used to identify the Node. - pub label: RenderGraphLabelValue, /// The [`NodeType`] of the node. pub ty: NodeType, /// The slots that the Node uses. /// This defines the resources that the node uses and creates in the graph. pub slots: Vec, - slot_label_lookup: HashMap, + //slot_label_lookup: HashMap, /// An optional pipeline descriptor for the Node. /// This is `None` if the Node type is not a node that requires a pipeline /// (see [`NodeType::should_have_pipeline`]). @@ -187,19 +182,14 @@ pub struct NodeDesc { impl NodeDesc { /// Create a new node descriptor. - pub fn new( - id: u64, - label: L, + pub fn new( pass_type: NodeType, pipeline_desc: Option, bind_groups: Vec<(&dyn RenderGraphLabel, Rc, Option>)>, ) -> Self { Self { - id, - label: label.into(), ty: pass_type, slots: vec![], - slot_label_lookup: HashMap::default(), pipeline_desc, bind_groups: bind_groups .into_iter() @@ -218,7 +208,6 @@ impl NodeDesc { "input slots should not have values" ); - self.slot_label_lookup.insert(slot.label.clone().into(), slot.id); self.slots.push(slot); } @@ -230,7 +219,6 @@ impl NodeDesc { #[inline(always)] pub fn add_buffer_slot( &mut self, - id: u64, label: L, attribute: SlotAttribute, value: Option, @@ -241,7 +229,6 @@ impl NodeDesc { ); let slot = NodeSlot { - id, label: label.into(), ty: SlotType::Buffer, attribute, @@ -258,7 +245,6 @@ impl NodeDesc { #[inline(always)] pub fn add_texture_slot( &mut self, - id: u64, label: L, attribute: SlotAttribute, value: Option, @@ -269,7 +255,6 @@ impl NodeDesc { ); let slot = NodeSlot { - id, label: label.into(), ty: SlotType::Texture, attribute, @@ -286,7 +271,6 @@ impl NodeDesc { #[inline(always)] pub fn add_texture_view_slot( &mut self, - id: u64, label: L, attribute: SlotAttribute, value: Option, @@ -297,7 +281,6 @@ impl NodeDesc { ); let slot = NodeSlot { - id, label: label.into(), ty: SlotType::TextureView, attribute, @@ -314,7 +297,6 @@ impl NodeDesc { #[inline(always)] pub fn add_sampler_slot( &mut self, - id: u64, label: L, attribute: SlotAttribute, value: Option, @@ -325,7 +307,6 @@ impl NodeDesc { ); let slot = NodeSlot { - id, label: label.into(), ty: SlotType::Sampler, attribute, @@ -341,7 +322,7 @@ impl NodeDesc { .filter(|s| s.attribute == SlotAttribute::Input) .collect() } - + /// Returns all output slots that the descriptor defines. pub fn output_slots(&self) -> Vec<&NodeSlot> { self.slots diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index cfe8877..e524398 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -40,8 +40,6 @@ pub struct BasePass { /// This should be Some when the pass is first created then after its added to /// the render graph it will be None and stay None. temp_render_target: Option, - main_rt_id: u64, - window_tv_id: u64, screen_size: glam::UVec2, } @@ -102,8 +100,6 @@ impl Node for BasePass { let depth_texture_view = Rc::new(depth_texture.view); let mut desc = NodeDesc::new( - graph.next_id(), - BasePassLabel, NodeType::Node, None, vec![ @@ -119,37 +115,30 @@ impl Node for BasePass { ], ); - self.main_rt_id = graph.next_id(); desc.add_slot(NodeSlot { ty: SlotType::RenderTarget, attribute: SlotAttribute::Output, - id: self.main_rt_id, label: BasePassSlots::MainRenderTarget.into(), value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new( render_target, )))), }); - self.window_tv_id = graph.next_id(); desc.add_texture_view_slot( - self.window_tv_id, BasePassSlots::WindowTextureView, SlotAttribute::Output, Some(SlotValue::Lazy), ); desc.add_texture_view_slot( - graph.next_id(), BasePassSlots::DepthTextureView, SlotAttribute::Output, Some(SlotValue::TextureView(depth_texture_view)), ); desc.add_buffer_slot( - graph.next_id(), BasePassSlots::ScreenSize, SlotAttribute::Output, Some(SlotValue::Buffer(Rc::new(screen_size_buf))), ); desc.add_buffer_slot( - graph.next_id(), BasePassSlots::Camera, SlotAttribute::Output, Some(SlotValue::Buffer(Rc::new(camera_buf))), @@ -177,7 +166,7 @@ impl Node for BasePass { context: &mut crate::render::graph::RenderGraphContext, ) { let tv_slot = graph - .slot_value_mut(self.main_rt_id) + .slot_value_mut(BasePassSlots::MainRenderTarget) .expect("somehow the main render target slot is missing"); let mut rt = tv_slot.as_render_target_mut().unwrap(); debug_assert!( @@ -203,7 +192,7 @@ impl Node for BasePass { // store the surface texture to the slot let tv_slot = graph - .slot_value_mut(self.window_tv_id) + .slot_value_mut(BasePassSlots::WindowTextureView) .expect("somehow the window texture view slot is missing"); *tv_slot = SlotValue::TextureView(Rc::new(view)); } diff --git a/lyra-game/src/render/graph/passes/light_base.rs b/lyra-game/src/render/graph/passes/light_base.rs index 31e7af3..62c3416 100644 --- a/lyra-game/src/render/graph/passes/light_base.rs +++ b/lyra-game/src/render/graph/passes/light_base.rs @@ -40,8 +40,6 @@ impl Node for LightBasePass { let light_buffers = self.light_buffers.as_ref().unwrap(); let mut desc = NodeDesc::new( - graph.next_id(), - LightBasePassLabel, NodeType::Node, None, vec![( @@ -52,7 +50,6 @@ impl Node for LightBasePass { ); desc.add_buffer_slot( - graph.next_id(), LightBasePassSlots::Lights, SlotAttribute::Output, Some(SlotValue::Buffer(light_buffers.buffer.clone())), diff --git a/lyra-game/src/render/graph/passes/light_cull_compute.rs b/lyra-game/src/render/graph/passes/light_cull_compute.rs index dec5d74..7b5c487 100644 --- a/lyra-game/src/render/graph/passes/light_cull_compute.rs +++ b/lyra-game/src/render/graph/passes/light_cull_compute.rs @@ -49,8 +49,7 @@ impl Node for LightCullComputePass { // get the size of the work group for the grid let main_rt = graph - .slot_id(&BasePassSlots::MainRenderTarget) - .and_then(|s| graph.slot_value(s)) + .slot_value(BasePassSlots::MainRenderTarget) .and_then(|s| s.as_render_target()) .expect("missing main render target"); self.workgroup_size = @@ -169,16 +168,13 @@ impl Node for LightCullComputePass { })); drop(main_rt); - let pass_id = graph.next_id(); - let depth_tex_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::DepthTexture).unwrap()); - let camera_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::Camera).unwrap()); - let lights_bgl = graph.bind_group_layout(graph.bind_group_id(&LightBasePassSlots::Lights).unwrap()); - let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::ScreenSize).unwrap()); + let depth_tex_bgl = graph.bind_group_layout(BasePassSlots::DepthTexture); + let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera); + let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights); + let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize); let mut desc = NodeDesc::new( - pass_id, - LightCullComputePassLabel, NodeType::Compute, Some(PipelineDescriptor::Compute(ComputePipelineDescriptor { label: Some("light_cull_pipeline".into()), @@ -201,20 +197,17 @@ impl Node for LightCullComputePass { ); desc.add_texture_view_slot( - graph.next_id(), BasePassSlots::WindowTextureView, SlotAttribute::Input, None, ); desc.add_buffer_slot( - graph.next_id(), BasePassSlots::ScreenSize, SlotAttribute::Input, None, ); - desc.add_buffer_slot(graph.next_id(), BasePassSlots::Camera, SlotAttribute::Input, None); + desc.add_buffer_slot(BasePassSlots::Camera, SlotAttribute::Input, None); desc.add_buffer_slot( - graph.next_id(), LightCullComputePassSlots::IndexCounterBuffer, SlotAttribute::Output, Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))), @@ -228,27 +221,20 @@ impl Node for LightCullComputePass { fn execute( &mut self, graph: &mut crate::render::graph::RenderGraph, - desc: &crate::render::graph::NodeDesc, + _: &crate::render::graph::NodeDesc, context: &mut RenderGraphContext, ) { + let label = context.label.clone(); + + let pipeline = graph.pipeline(label) + .expect("Failed to find Pipeline for LightCullComputePass"); + let pipeline = pipeline.as_compute(); + let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor { label: Some("light_cull_pass"), }); - let pipeline = graph.pipeline(desc.id); - pass.set_pipeline(pipeline.as_compute()); - - /* let depth_tex_bg = graph.bind_group(graph.bind_group_id("depth_texture").unwrap()); - let camera_bg = graph.bind_group(graph.bind_group_id("camera").unwrap()); - let lights_bg = graph.bind_group(graph.bind_group_id("light_buffers").unwrap()); - let grid_bg = graph.bind_group(graph.bind_group_id("light_indices_grid").unwrap()); - let screen_size_bg = graph.bind_group(graph.bind_group_id("screen_size").unwrap()); - - pass.set_bind_group(0, depth_tex_bg, &[]); - pass.set_bind_group(1, camera_bg, &[]); - pass.set_bind_group(2, lights_bg, &[]); - pass.set_bind_group(3, grid_bg, &[]); - pass.set_bind_group(4, screen_size_bg, &[]); */ + pass.set_pipeline(pipeline); graph.set_bind_groups( &mut pass, diff --git a/lyra-game/src/render/graph/passes/meshes.rs b/lyra-game/src/render/graph/passes/meshes.rs index a773587..c773ecb 100644 --- a/lyra-game/src/render/graph/passes/meshes.rs +++ b/lyra-game/src/render/graph/passes/meshes.rs @@ -232,21 +232,16 @@ impl Node for MeshPass { self.default_texture = Some(RenderTexture::from_bytes(&device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap()); // get surface config format - let main_rt = graph.slot_id(&BasePassSlots::MainRenderTarget) - .and_then(|s| graph.slot_value(s)) + let main_rt = graph.slot_value(BasePassSlots::MainRenderTarget) .and_then(|s| s.as_render_target()) .expect("missing main render target"); let surface_config_format = main_rt.surface_config.format; drop(main_rt); - // get the id here to make borrow checker happy - let pass_id = graph.next_id(); - - let camera_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::Camera).unwrap()); - let lights_bgl = graph.bind_group_layout(graph.bind_group_id(&LightBasePassSlots::Lights).unwrap()); + let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera); + let lights_bgl = graph.bind_group_layout(LightBasePassSlots::Lights); let light_grid_bgl = graph - .bind_group_layout(graph.bind_group_id(&LightCullComputePassSlots::LightIndicesGridGroup) - .expect("Missing light grid bind group")); + .bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); let shader = Rc::new(Shader { label: Some("base_shader".into()), @@ -254,8 +249,6 @@ impl Node for MeshPass { }); let desc = NodeDesc::new( - pass_id, - MeshesPassLabel, NodeType::Render, Some(PipelineDescriptor::Render(RenderPipelineDescriptor { label: Some("meshes".into()), @@ -445,38 +438,37 @@ impl Node for MeshPass { fn execute( &mut self, graph: &mut crate::render::graph::RenderGraph, - desc: &crate::render::graph::NodeDesc, + _: &crate::render::graph::NodeDesc, context: &mut crate::render::graph::RenderGraphContext, ) { let encoder = context.encoder.as_mut().unwrap(); let view = graph - .slot_value(graph.slot_id(&BasePassSlots::WindowTextureView).unwrap()) + .slot_value(BasePassSlots::WindowTextureView) .unwrap() .as_texture_view() .expect("BasePassSlots::WindowTextureView was not a TextureView slot"); let depth_view = graph - .slot_value(graph.slot_id(&BasePassSlots::DepthTextureView).unwrap()) + .slot_value(BasePassSlots::DepthTextureView) .unwrap() .as_texture_view() .expect("BasePassSlots::DepthTextureView was not a TextureView slot"); let camera_bg = graph - .bind_group(graph.bind_group_id(&BasePassSlots::Camera) - .expect("Missing camera bind group")); + .bind_group(BasePassSlots::Camera); let lights_bg = graph - .bind_group(graph.bind_group_id(&LightBasePassSlots::Lights) - .expect("Missing lights bind group")); + .bind_group(LightBasePassSlots::Lights); let light_grid_bg = graph - .bind_group(graph.bind_group_id(&LightCullComputePassSlots::LightIndicesGridGroup) - .expect("Missing light grid bind group")); + .bind_group(LightCullComputePassSlots::LightIndicesGridGroup); let material_bg = graph - .bind_group(graph.bind_group_id(&MeshesPassSlots::Material) - .expect("Missing material bind group")); + .bind_group(MeshesPassSlots::Material); + + let pipeline = graph.pipeline(context.label.clone()) + .expect("Failed to find pipeline for MeshPass"); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), @@ -504,7 +496,6 @@ impl Node for MeshPass { }), }); - let pipeline = graph.pipeline(desc.id); pass.set_pipeline(&pipeline.as_render()); //let material_buffer_bg = self.material_buffer.as_ref().unwrap().bindgroup(); diff --git a/lyra-game/src/render/graph/passes/present_pass.rs b/lyra-game/src/render/graph/passes/present_pass.rs index 2302d8f..af7d52d 100644 --- a/lyra-game/src/render/graph/passes/present_pass.rs +++ b/lyra-game/src/render/graph/passes/present_pass.rs @@ -18,8 +18,7 @@ impl PresentPassLabel { /// screen size buffer, camera buffer, pub struct PresentPass { /// Label of this pass - label: PresentPassLabel, - //render_target_slot: Rc, + pub label: PresentPassLabel, } impl PresentPass { @@ -32,10 +31,8 @@ impl PresentPass { } impl Node for PresentPass { - fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc { + fn desc(&mut self, _graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::NodeDesc { let mut desc = NodeDesc::new( - graph.next_id(), - self.label.clone(), NodeType::Presenter, None, vec![], @@ -45,7 +42,6 @@ impl Node for PresentPass { NodeSlot { ty: SlotType::RenderTarget, attribute: SlotAttribute::Input, - id: graph.next_id(), label: self.label.0.clone(), value: None, } @@ -59,9 +55,9 @@ impl Node for PresentPass { } fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::NodeDesc, _context: &mut crate::render::graph::RenderGraphContext) { - let id = graph.slot_id_rc(&self.label.0) - .expect(&format!("render target slot '{:?}' for PresentPass is missing", self.label.0)); - let mut slot = graph.slot_value_mut(id).unwrap().as_render_target_mut().unwrap(); + let mut slot = graph.slot_value_mut(self.label.0.clone()) + .expect(&format!("render target slot '{:?}' for PresentPass is missing", self.label.0)) + .as_render_target_mut().unwrap(); let surf_tex = slot.current_texture.take().unwrap(); surf_tex.present(); } diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index 353f45d..b73a0b9 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -125,19 +125,20 @@ impl BasicRenderer { let mut g = RenderGraph::new(device.clone(), queue.clone()); debug!("Adding base pass"); - g.add_pass(BasePass::new(surface, config)); + g.add_pass(BasePassLabel, BasePass::new(surface, config)); debug!("Adding light base pass"); - g.add_pass(LightBasePass::new()); + g.add_pass(LightBasePassLabel, LightBasePass::new()); debug!("Adding light cull compute pass"); - g.add_pass(LightCullComputePass::new(size)); + g.add_pass(LightCullComputePassLabel, LightCullComputePass::new(size)); //debug!("Adding triangle pass"); //g.add_pass(TrianglePass::new()); debug!("Adding mesh pass"); - g.add_pass(MeshPass::new()); + g.add_pass(MeshesPassLabel, MeshPass::new()); debug!("Adding present pass"); - g.add_pass(PresentPass::new(BasePassSlots::MainRenderTarget)); + let p = PresentPass::new(BasePassSlots::MainRenderTarget); + g.add_pass(p.label.clone(), p); g.add_edge(BasePassLabel, LightBasePassLabel); g.add_edge(LightBasePassLabel, LightCullComputePassLabel); @@ -188,7 +189,7 @@ impl Renderer for BasicRenderer { self.size = new_size; // update surface config and the surface - let mut rt = self.graph.slot_value_mut(self.graph.slot_id(&BasePassSlots::MainRenderTarget).unwrap()) + let mut rt = self.graph.slot_value_mut(BasePassSlots::MainRenderTarget) .unwrap().as_render_target_mut().unwrap(); rt.surface_config.width = new_size.width; rt.surface_config.height = new_size.height; From 9ce79e6b296f423d659c3f93dd1a6d72dd932afd Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 12 Jun 2024 21:23:27 -0400 Subject: [PATCH 19/20] resource: fix tests, render: remove warning --- lyra-game/src/render/light/mod.rs | 2 -- lyra-resource/Cargo.toml | 2 +- lyra-resource/src/gltf/material.rs | 8 ++++---- lyra-resource/src/gltf/mesh.rs | 6 +++--- lyra-resource/src/resource_manager.rs | 12 ++++++------ 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lyra-game/src/render/light/mod.rs b/lyra-game/src/render/light/mod.rs index 9135632..91b9ee6 100644 --- a/lyra-game/src/render/light/mod.rs +++ b/lyra-game/src/render/light/mod.rs @@ -12,8 +12,6 @@ use crate::math::Transform; use self::directional::DirectionalLight; -use super::render_buffer::BindGroupPair; - const MAX_LIGHT_COUNT: usize = 16; /// A struct that stores a list of lights in a wgpu::Buffer. diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 9e5e149..51b160d 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" [dependencies] lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } -lyra-reflect = { path = "../lyra-reflect" } +lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] } lyra-math = { path = "../lyra-math" } lyra-scene = { path = "../lyra-scene" } anyhow = "1.0.75" diff --git a/lyra-resource/src/gltf/material.rs b/lyra-resource/src/gltf/material.rs index 3a24d9d..372d7ee 100644 --- a/lyra-resource/src/gltf/material.rs +++ b/lyra-resource/src/gltf/material.rs @@ -34,10 +34,10 @@ impl From> for PbrRoughness { #[derive(Clone, Debug, Default, Reflect)] pub struct PbrGlossiness { /// The rgba diffuse color of the material - pub diffuse_color: glam::Vec4, + pub diffuse_color: lyra_math::Vec4, // The base color texture // pub diffuse_texture // TODO - pub specular: glam::Vec3, + pub specular: lyra_math::Vec3, /// The glossiness factor of the material. /// From 0.0 (no glossiness) to 1.0 (full glossiness) pub glossiness: f32, @@ -101,7 +101,7 @@ pub struct Specular { pub factor: f32, /// The color of the specular reflection - pub color_factor: glam::Vec3, + pub color_factor: lyra_math::Vec3, /// A texture that defines the strength of the specular reflection, /// stored in the alpha (`A`) channel. This will be multiplied by @@ -140,7 +140,7 @@ pub struct Material { //pub pbr_roughness: PbrRoughness, /// The RGBA base color of the model. If a texture is supplied with `base_color_texture`, this value /// will tint the texture. If a texture is not provided, this value would be the color of the Material. - pub base_color: glam::Vec4, + pub base_color: lyra_math::Vec4, /// The metalness of the material /// From 0.0 (non-metal) to 1.0 (metal) pub metallic: f32, diff --git a/lyra-resource/src/gltf/mesh.rs b/lyra-resource/src/gltf/mesh.rs index a23affe..3cddd3c 100644 --- a/lyra-resource/src/gltf/mesh.rs +++ b/lyra-resource/src/gltf/mesh.rs @@ -50,9 +50,9 @@ impl From> for MeshIndices { #[repr(C)] #[derive(Clone, Debug, PartialEq, Reflect)] pub enum VertexAttributeData { - Vec2(Vec), - Vec3(Vec), - Vec4(Vec), + Vec2(Vec), + Vec3(Vec), + Vec4(Vec), } impl VertexAttributeData { diff --git a/lyra-resource/src/resource_manager.rs b/lyra-resource/src/resource_manager.rs index 4d2f0ba..99e01b0 100644 --- a/lyra-resource/src/resource_manager.rs +++ b/lyra-resource/src/resource_manager.rs @@ -374,7 +374,7 @@ pub(crate) mod tests { use instant::Instant; - use crate::{Image, ResourceData, Texture}; + use crate::{Image, ResourceData}; use super::*; @@ -431,10 +431,10 @@ pub(crate) mod tests { #[test] fn ensure_single() { let man = ResourceManager::new(); - let res = man.request::(&get_image("squiggles.png")).unwrap(); + let res = man.request::(&get_image("squiggles.png")).unwrap(); assert_eq!(Arc::strong_count(&res.handle.res), 3); - let resagain = man.request::(&get_image("squiggles.png")).unwrap(); + let resagain = man.request::(&get_image("squiggles.png")).unwrap(); assert_eq!(Arc::strong_count(&resagain.handle.res), 4); } @@ -442,7 +442,7 @@ pub(crate) mod tests { #[test] fn ensure_none() { let man = ResourceManager::new(); - let res = man.request::(&get_image("squigglesfff.png")).unwrap(); + let res = man.request::(&get_image("squigglesfff.png")).unwrap(); //let err = res.err().unwrap(); // 1 second should be enough to run into an error @@ -468,7 +468,7 @@ pub(crate) mod tests { #[test] fn reload_image() { let man = ResourceManager::new(); - let res = man.request::(&get_image("squiggles.png")).unwrap(); + let res = man.request::(&get_image("squiggles.png")).unwrap(); busy_wait_resource(&res, 10.0); let img = res.data_ref(); img.unwrap(); @@ -489,7 +489,7 @@ pub(crate) mod tests { std::fs::copy(orig_path, &image_path).unwrap(); let man = ResourceManager::new(); - let res = man.request::(&image_path).unwrap(); + let res = man.request::(&image_path).unwrap(); busy_wait_resource(&res, 10.0); let img = res.data_ref(); img.unwrap(); From d5348ec1723e86a52589b328733acf5f213055e3 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 12 Jun 2024 21:30:09 -0400 Subject: [PATCH 20/20] render: a tiny bit of code cleanup --- lyra-game/src/render/graph/mod.rs | 32 ++++++++++++------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/lyra-game/src/render/graph/mod.rs b/lyra-game/src/render/graph/mod.rs index 7fd0ec4..89111f3 100644 --- a/lyra-game/src/render/graph/mod.rs +++ b/lyra-game/src/render/graph/mod.rs @@ -119,16 +119,8 @@ pub struct RenderGraph { device: Rc, queue: Rc, slots: FxHashMap, - /// HashMap used to lookup the slot id using the label's hash - //slot_label_lookup: FxHashMap, - passes: FxHashMap, - // TODO: Use a SlotMap + nodes: FxHashMap, bind_groups: FxHashMap, - /// HashMap used to lookup the bind group id using the label's hash - //bind_group_names: FxHashMap, - // TODO: make pipelines a `type` parameter in RenderPasses, - // then the pipelines can be retrieved via TypeId to the pass. - //pipelines: FxHashMap, /// A directed graph describing the execution path of the RenderGraph execution_graph: petgraph::matrix_graph::DiMatrix, usize>, } @@ -139,7 +131,7 @@ impl RenderGraph { device, queue, slots: Default::default(), - passes: Default::default(), + nodes: Default::default(), bind_groups: Default::default(), execution_graph: Default::default(), } @@ -213,7 +205,7 @@ impl RenderGraph { let label: RenderGraphLabelValue = label.into(); let index = self.execution_graph.add_node(label.clone()); - self.passes.insert( + self.nodes.insert( label, PassEntry { inner: Arc::new(RefCell::new(pass)), @@ -231,7 +223,7 @@ impl RenderGraph { #[instrument(skip(self, device))] pub fn setup(&mut self, device: &wgpu::Device) { // For all passes, create their pipelines - for pass in self.passes.values_mut() { + for pass in self.nodes.values_mut() { let desc = (*pass.desc).borrow(); if let Some(pipeline_desc) = &desc.pipeline_desc { let pipeline = match desc.ty { @@ -270,9 +262,9 @@ impl RenderGraph { let mut buffer_writes = VecDeque::::new(); // reserve some buffer writes. not all nodes write so half the amount of them is probably // fine. - buffer_writes.reserve(self.passes.len() / 2); + buffer_writes.reserve(self.nodes.len() / 2); - for (label, pass) in &mut self.passes { + for (label, pass) in &mut self.nodes { let mut context = RenderGraphContext::new(&self.device, &self.queue, None, label.clone()); let mut inner = pass.inner.borrow_mut(); inner.prepare(world, &mut context); @@ -311,9 +303,9 @@ impl RenderGraph { .collect(); //debug!("Render graph execution order: {:?}", sorted); - let mut encoders = Vec::with_capacity(self.passes.len() / 2); + let mut encoders = Vec::with_capacity(self.nodes.len() / 2); while let Some(pass_label) = sorted.pop_front() { - let pass = self.passes.get(&pass_label).unwrap(); + let pass = self.nodes.get(&pass_label).unwrap(); let pass_inn = pass.inner.clone(); let pass_desc = pass.desc.clone(); @@ -373,12 +365,12 @@ impl RenderGraph { } pub fn node_desc>(&self, label: L) -> Option> { - self.passes.get(&label.into()).map(|s| (*s.desc).borrow()) + self.nodes.get(&label.into()).map(|s| (*s.desc).borrow()) } #[inline(always)] pub fn pipeline>(&self, label: L) -> Option> { - self.passes.get(&label.into()) + self.nodes.get(&label.into()) .and_then(|p| { let v = p.pipeline.borrow(); @@ -416,13 +408,13 @@ impl RenderGraph { let to = RenderGraphLabelValue::from(to); let from_idx = self - .passes + .nodes .iter() .find(|p| *p.0 == from) .map(|p| p.1.graph_index) .expect("Failed to find from pass"); let to_idx = self - .passes + .nodes .iter() .find(|p| *p.0 == to) .map(|p| p.1.graph_index)