From 007b1047ef7bf9fdceefacceb1fbb72799b1d1d0 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Thu, 27 Jun 2024 23:48:24 -0400 Subject: [PATCH] render: implement view target chains for post processing steps --- lyra-game/Cargo.toml | 2 +- lyra-game/src/render/graph/passes/base.rs | 2 +- lyra-game/src/render/graph/passes/mod.rs | 4 +- .../src/render/graph/passes/present_pass.rs | 3 +- lyra-game/src/render/graph/passes/tint.rs | 114 ++++++-------- lyra-game/src/render/graph/render_target.rs | 140 +++++++++++------- lyra-game/src/render/renderer.rs | 5 +- lyra-game/src/render/shaders/tint.wgsl | 25 ++-- 8 files changed, 157 insertions(+), 138 deletions(-) diff --git a/lyra-game/Cargo.toml b/lyra-game/Cargo.toml index bc28133..26cc050 100644 --- a/lyra-game/Cargo.toml +++ b/lyra-game/Cargo.toml @@ -12,7 +12,7 @@ lyra-math = { path = "../lyra-math" } lyra-scene = { path = "../lyra-scene" } winit = "0.28.1" -wgpu = "0.15.1" +wgpu = { version = "0.15.1", features = [ "expose-ids"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index 6d0d3fb..bb1a301 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -135,7 +135,7 @@ impl Node for BasePass { ) { let mut vt = graph.view_target_mut(); vt.primary.create_frame(); - vt.next_chain(context.encoder.as_mut().unwrap()); + //vt.next_chain(); vt.primary.create_frame_view(); /* debug_assert!( !rt.current_texture.is_some(), diff --git a/lyra-game/src/render/graph/passes/mod.rs b/lyra-game/src/render/graph/passes/mod.rs index f76e6af..ccd17ab 100644 --- a/lyra-game/src/render/graph/passes/mod.rs +++ b/lyra-game/src/render/graph/passes/mod.rs @@ -16,5 +16,5 @@ pub use present_pass::*; mod init; pub use init::*; -/* mod tint; -pub use tint::*; */ \ No newline at end of file +mod tint; +pub use tint::*; \ 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 8822b90..f7ffb66 100644 --- a/lyra-game/src/render/graph/passes/present_pass.rs +++ b/lyra-game/src/render/graph/passes/present_pass.rs @@ -32,7 +32,8 @@ 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 mut vt = graph.view_target_mut(); + fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::NodeDesc, context: &mut crate::render::graph::RenderGraphContext) { + let mut vt = graph.view_target_mut(); vt.copy_to_primary(context.encoder.as_mut().unwrap()); context.submit_encoder(); diff --git a/lyra-game/src/render/graph/passes/tint.rs b/lyra-game/src/render/graph/passes/tint.rs index 20046c9..fb406c8 100644 --- a/lyra-game/src/render/graph/passes/tint.rs +++ b/lyra-game/src/render/graph/passes/tint.rs @@ -1,35 +1,34 @@ -use std::{cell::RefCell, rc::Rc}; +use std::{collections::HashMap, rc::Rc}; use lyra_game_derive::RenderGraphLabel; use crate::render::{ - graph::{Node, NodeDesc, NodeSlot, NodeType, RenderTarget, SlotAttribute, SlotType, SlotValue}, + graph::{Node, NodeDesc, NodeType}, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, }; -use super::BasePassSlots; - #[derive(Debug, Clone, Copy, Hash, RenderGraphLabel)] pub enum TintPassSlots { InputRenderTarget, InputTextureView, TextureViewBindGroup, - Frame + Frame, } #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] pub struct TintPassLabel; +#[derive(Debug, Default)] pub struct TintPass { - render_target: Option, + target_sampler: Option, + bgl: Option>, + bg_cache: HashMap, } impl TintPass { - pub fn new(render_target: RenderTarget) -> Self { - Self { - render_target: Some(render_target) - } + pub fn new() -> Self { + Self::default() } } @@ -40,14 +39,6 @@ impl Node for TintPass { ) -> crate::render::graph::NodeDesc { let device = &graph.device; - // get surface config format - /* 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.format(); - drop(main_rt); */ - let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("tint_bgl"), entries: &[ @@ -70,27 +61,16 @@ impl Node for TintPass { ], }); let bgl = Rc::new(bgl); - - let input_view = graph - .slot_value(BasePassSlots::WindowTextureView) - .and_then(|s| s.as_texture_view()) - .expect("missing input texture view"); - - let bg = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("tint_bg"), - layout: &*bgl, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(input_view), - }], - }); + self.bgl = Some(bgl.clone()); + self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default())); let shader = Rc::new(Shader { label: Some("tint_shader".into()), source: include_str!("../../shaders/tint.wgsl").to_string(), }); - let mut desc = NodeDesc::new( + let vt = graph.view_target(); + let desc = NodeDesc::new( NodeType::Render, Some(PipelineDescriptor::Render(RenderPipelineDescriptor { label: Some("tint_pass".into()), @@ -105,7 +85,7 @@ impl Node for TintPass { module: shader, entry_point: "fs_main".into(), targets: vec![Some(wgpu::ColorTargetState { - format: self.render_target.as_ref().unwrap().format(), + format: vt.format(), blend: Some(wgpu::BlendState::REPLACE), write_mask: wgpu::ColorWrites::ALL, })], @@ -115,22 +95,9 @@ impl Node for TintPass { multisample: wgpu::MultisampleState::default(), multiview: None, })), - vec![(&TintPassSlots::TextureViewBindGroup, bg.into(), Some(bgl))], + vec![], ); - // desc.add_buffer_slot( - // FxaaPassSlots::Lights, - // SlotAttribute::Output, - // Some(SlotValue::Buffer(light_buffers.buffer.clone())), - // ); - - desc.add_slot(NodeSlot { - ty: SlotType::Frame, - attribute: SlotAttribute::Output, - label: TintPassSlots::Frame.into(), - value: Some(SlotValue::Lazy), - }); - desc } @@ -149,31 +116,45 @@ impl Node for TintPass { _: &crate::render::graph::NodeDesc, context: &mut crate::render::graph::RenderGraphContext, ) { - let rt = self.render_target.as_ref().unwrap(); - let frame = rt.create_frame(); + let pipeline = graph + .pipeline(context.label.clone()) + .expect("Failed to find pipeline for MeshPass"); - let view = frame.texture() - .create_view(&wgpu::TextureViewDescriptor::default()); + let mut vt = graph.view_target_mut(); + let chain = vt.get_chain(); + let source_view = chain.source.frame_view.as_ref().unwrap(); + let dest_view = chain.dest.frame_view.as_ref().unwrap(); - let frame_slot = graph - .slot_value_mut(TintPassSlots::Frame) - .expect("somehow the frame slot is missing"); - *frame_slot = SlotValue::Frame(Rc::new(RefCell::new(Some(frame)))); - - let bg = graph.bind_group(TintPassSlots::TextureViewBindGroup); - - /* let view = graph - .slot_value(BasePassSlots::WindowTextureView) - .unwrap() - .as_texture_view() - .expect("BasePassSlots::WindowTextureView was not a TextureView slot"); */ + let bg = self + .bg_cache + .entry(source_view.global_id()) + .or_insert_with(|| { + graph + .device() + .create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("tint_bg"), + layout: self.bgl.as_ref().unwrap(), + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(source_view), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Sampler( + self.target_sampler.as_ref().unwrap(), + ), + }, + ], + }) + }); { let encoder = context.encoder.as_mut().unwrap(); let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("tint_pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, + view: &dest_view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, @@ -182,9 +163,10 @@ impl Node for TintPass { })], depth_stencil_attachment: None, }); + pass.set_pipeline(&pipeline.as_render()); pass.set_bind_group(0, bg, &[]); - pass.draw(0..4, 0..1); + pass.draw(0..3, 0..1); } } } diff --git a/lyra-game/src/render/graph/render_target.rs b/lyra-game/src/render/graph/render_target.rs index 34f64ba..af1ee41 100644 --- a/lyra-game/src/render/graph/render_target.rs +++ b/lyra-game/src/render/graph/render_target.rs @@ -1,5 +1,7 @@ use std::sync::Arc; +use tracing::debug; + use crate::math; enum RenderTargetInner { @@ -187,39 +189,29 @@ impl FrameTarget { } } -pub struct TargetViewChain { - source: FrameTarget, - dest: FrameTarget, +//struct TargetViewChainPrimary + +pub struct TargetViewChain<'a> { + pub source: &'a mut FrameTarget, + pub dest: &'a mut FrameTarget, } -impl TargetViewChain { - pub fn next(&mut self, encoder: &mut wgpu::CommandEncoder) { - let size = self.source.size(); - let size = wgpu::Extent3d { - width: size.x, - height: size.y, - depth_or_array_layers: 1, - }; +struct ViewChain { + source: FrameTarget, + dest: FrameTarget, + /// tracks the target that is currently being presented + active: u8, +} - let source_tex = self.source.frame.as_ref().unwrap().texture(); - let dest_tex = self.dest.frame.as_ref().unwrap().texture(); - - let source_ict = wgpu::ImageCopyTexture { - texture: source_tex, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }; - - let dest_ict = wgpu::ImageCopyTexture { - texture: dest_tex, - mip_level: 0, - origin: wgpu::Origin3d::ZERO, - aspect: wgpu::TextureAspect::All, - }; - - encoder.copy_texture_to_texture(source_ict, dest_ict, size); - std::mem::swap(&mut self.source, &mut self.dest); +impl ViewChain { + fn active(&self) -> &FrameTarget { + if self.active == 0 { + &self.source + } else if self.active == 1 { + &self.dest + } else { + panic!("active chain index became invalid! ({})", self.active); + } } } @@ -227,16 +219,19 @@ pub struct ViewTarget { device: Arc, /// The primary RenderTarget, likely a Surface pub primary: FrameTarget, - pub chain: Option, + chain: Option, } impl ViewTarget { pub fn new(device: Arc, primary: RenderTarget) -> Self { - Self { + let mut s = Self { device, primary: FrameTarget::new(primary), - chain: None - } + chain: None, + }; + + s.create_chain(s.primary.format(), s.primary.size()); + s } pub fn size(&self) -> math::UVec2 { @@ -248,16 +243,15 @@ impl ViewTarget { } pub fn resize(&mut self, device: &wgpu::Device, size: math::UVec2) { - self.primary.render_target.resize(device, size); + if size != self.primary.size() { + self.primary.render_target.resize(device, size); + self.create_chain(self.primary.format(), size); + } } - /// Cycle the target view chain, storing it in self, and returning a mutable borrow to it. - pub fn next_chain(&mut self, encoder: &mut wgpu::CommandEncoder) -> &mut TargetViewChain { - // TODO: check if chain has already been made. If it has, use next on it. - let _ = encoder; - let format = self.primary.format(); - let size = self.primary.size(); - + fn create_chain(&mut self, format: wgpu::TextureFormat, size: math::UVec2) { + debug!("Creating chain with {:?} format and {:?} size", format, size); + let mut source = FrameTarget::new(RenderTarget::new_texture(&self.device, format, size)); source.create_frame(); source.create_frame_view(); @@ -266,36 +260,69 @@ impl ViewTarget { dest.create_frame(); dest.create_frame_view(); - self.chain = Some(TargetViewChain { + self.chain = Some(ViewChain { source, dest, + active: 0, }); - //self.reset_chain(encoder); - self.chain.as_mut().unwrap() + } + + /// Cycle the target view chain, storing it in self, and returning a mutable borrow to it. + pub fn get_chain(&mut self) -> TargetViewChain { + let format = self.primary.format(); + let size = self.primary.size(); + + if let Some(chain) = &self.chain { + // check if the chain needs to be recreated + if chain.source.format() != format || chain.source.size() != size { + self.create_chain(format, size); + } + } else { + self.create_chain(format, size); + } + + let chain = self.chain.as_mut().unwrap(); + + if chain.active == 0 { + chain.active = 1; + TargetViewChain { + source: &mut chain.source, + dest: &mut chain.dest, + } + } else if chain.active == 1 { + chain.active = 0; + TargetViewChain { + source: &mut chain.dest, + dest: &mut chain.source, + } + } else { + panic!("active chain index became invalid! ({})", chain.active); + } } /// Get the [`wgpu::TextureView`] to render to pub fn render_view(&self) -> &wgpu::TextureView { let chain = self.chain.as_ref().unwrap(); - chain.source.frame_view.as_ref().unwrap() + chain.active().frame_view.as_ref().unwrap() } /// Copy the chain target to the primary target /// - /// The primary target must have `wgpu::TextureUsages::COPY_DST`. - pub fn copy_to_primary(&self, encoder: &mut wgpu::CommandEncoder) { - let chain = self.chain.as_ref().unwrap(); - let chain_tex = chain.source.frame.as_ref().unwrap().texture(); + /// The primary target must have `wgpu::TextureUsages::COPY_DST`. This also resets the active + /// chain texture. + pub fn copy_to_primary(&mut self, encoder: &mut wgpu::CommandEncoder) { + let chain = self.chain.as_mut().unwrap(); + let active_tex = chain.active().frame.as_ref().unwrap().texture(); - let source_ict = wgpu::ImageCopyTexture { - texture: chain_tex, + let active_copy = wgpu::ImageCopyTexture { + texture: active_tex, mip_level: 0, origin: wgpu::Origin3d::ZERO, aspect: wgpu::TextureAspect::All, }; let dest_tex = self.primary.frame.as_ref().unwrap().texture(); - let dest_ict = wgpu::ImageCopyTexture { + let dest_copy = wgpu::ImageCopyTexture { texture: dest_tex, mip_level: 0, origin: wgpu::Origin3d::ZERO, @@ -309,6 +336,11 @@ impl ViewTarget { depth_or_array_layers: 1, }; - encoder.copy_texture_to_texture(source_ict, dest_ict, size); + encoder.copy_texture_to_texture(active_copy, dest_copy, size); + + // reset active texture after a render + // must get the chain again because of the borrow checker + let chain = self.chain.as_mut().unwrap(); + chain.active = 0; } } \ No newline at end of file diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index ef38375..83bfc09 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel; use tracing::{debug, instrument, warn}; use winit::window::Window; -use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, SubGraphNode, ViewTarget}; +use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel, RenderGraphLabelValue, RenderTarget, SubGraphNode, TintPass, TintPassLabel, ViewTarget}; use super::graph::RenderGraph; use super::{resource::RenderPipeline, render_job::RenderJob}; @@ -164,6 +164,9 @@ impl BasicRenderer { )); } + main_graph.add_node(TintPassLabel, TintPass::default()); + main_graph.add_edge(TestSubGraphLabel, TintPassLabel); + //let present_pass_label = PresentPassLabel::new(BasePassSlots::Frame);//TintPassSlots::Frame); let p = PresentPass::default(); main_graph.add_node(PresentPassLabel, p); diff --git a/lyra-game/src/render/shaders/tint.wgsl b/lyra-game/src/render/shaders/tint.wgsl index 37ce6f5..c7ba620 100644 --- a/lyra-game/src/render/shaders/tint.wgsl +++ b/lyra-game/src/render/shaders/tint.wgsl @@ -4,28 +4,29 @@ var t_screen: texture_2d; var s_screen: sampler; struct VertexOutput { - @builtin(position) clip_position: vec4, - @location(0) tex_coords: vec2, + @builtin(position) + clip_position: vec4, + @location(0) + tex_coords: vec2, } @vertex fn vs_main( @builtin(vertex_index) vertex_index: u32, ) -> VertexOutput { - - const vertices = array, 4>(vec4(-1.0, -1.0, 0.0, 1.0), vec4(1.0, -1.0, 0.0, 1.0), vec4(-1.0, 1.0, 0.0, 1.0), vec4(1.0, 1.0, 0.0, 1.0)); - const tex_coords = array, 4>(vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0)); - - var out: VertexOutput; - out.clip_position = vertices[vertex_index]; - out.tex_coords = tex_coords[vertex_index]; + let tex_coords = vec2(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0; + let clip_position = vec4(tex_coords * vec2(2.0, -2.0) + vec2(-1.0, 1.0), 0.0, 1.0); - return out; + return VertexOutput(clip_position, tex_coords); } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let rgb: vec3 = textureSample(t_screen, s_screen, in.tex_coords).xyz; - rgb *= vec3(0.8); + let resolution = vec2(textureDimensions(t_screen)); + let inverse_screen_size = 1.0 / resolution.xy; + let tex_coords = in.clip_position.xy * inverse_screen_size; + + var rgb: vec3 = textureSample(t_screen, s_screen, tex_coords).xyz; + rgb *= vec3(1.0, 0.2, 0.2); return vec4(rgb, 1.0); } \ No newline at end of file