From a4e80d4fec4cb515f45be86426bff199eb06bffa Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 4 May 2024 10:02:50 -0400 Subject: [PATCH] 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