From 0e71c5734ff3d5ae3e23a2a3f39c54c6573fa11a Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Wed, 26 Jun 2024 17:14:31 -0400 Subject: [PATCH] render: make it easier to get Frame from RenderTarget --- lyra-game/src/render/graph/context.rs | 11 -- lyra-game/src/render/graph/passes/base.rs | 2 +- lyra-game/src/render/graph/passes/tint.rs | 190 ++++++++++++++++++++ lyra-game/src/render/graph/render_target.rs | 28 ++- lyra-game/src/render/shaders/tint.wgsl | 31 ++++ 5 files changed, 247 insertions(+), 15 deletions(-) create mode 100644 lyra-game/src/render/graph/passes/tint.rs create mode 100644 lyra-game/src/render/shaders/tint.wgsl diff --git a/lyra-game/src/render/graph/context.rs b/lyra-game/src/render/graph/context.rs index 696ce57..06364e4 100644 --- a/lyra-game/src/render/graph/context.rs +++ b/lyra-game/src/render/graph/context.rs @@ -86,15 +86,4 @@ impl<'a> RenderGraphContext<'a> { ) { self.queue_buffer_write(target_slot, offset, bytemuck::bytes_of(&bytes)); } - - pub fn get_frame(&self, render_target: &RenderTarget) -> super::Frame { - let texture = render_target.frame_texture() - .expect("failed to create frame texture"); // should this be returned to the user? - - Frame { - device: self.device.clone(), - queue: self.queue.clone(), - texture, - } - } } diff --git a/lyra-game/src/render/graph/passes/base.rs b/lyra-game/src/render/graph/passes/base.rs index 33b989c..bc7359f 100644 --- a/lyra-game/src/render/graph/passes/base.rs +++ b/lyra-game/src/render/graph/passes/base.rs @@ -168,7 +168,7 @@ impl Node for BasePass { .expect("somehow the main render target slot is missing"); let rt = tv_slot.as_render_target().unwrap(); let rt_size = rt.size(); - let frame = context.get_frame(&rt); + let frame = rt.create_frame(); /* debug_assert!( !rt.current_texture.is_some(), "main render target surface was not presented!" diff --git a/lyra-game/src/render/graph/passes/tint.rs b/lyra-game/src/render/graph/passes/tint.rs new file mode 100644 index 0000000..20046c9 --- /dev/null +++ b/lyra-game/src/render/graph/passes/tint.rs @@ -0,0 +1,190 @@ +use std::{cell::RefCell, rc::Rc}; + +use lyra_game_derive::RenderGraphLabel; + +use crate::render::{ + graph::{Node, NodeDesc, NodeSlot, NodeType, RenderTarget, SlotAttribute, SlotType, SlotValue}, + resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, +}; + +use super::BasePassSlots; + +#[derive(Debug, Clone, Copy, Hash, RenderGraphLabel)] +pub enum TintPassSlots { + InputRenderTarget, + InputTextureView, + + TextureViewBindGroup, + Frame +} + +#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] +pub struct TintPassLabel; + +pub struct TintPass { + render_target: Option, +} + +impl TintPass { + pub fn new(render_target: RenderTarget) -> Self { + Self { + render_target: Some(render_target) + } + } +} + +impl Node for TintPass { + fn desc<'a, 'b>( + &'a mut self, + graph: &'b mut crate::render::graph::RenderGraph, + ) -> 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: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: false }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), + count: None, + }, + ], + }); + 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), + }], + }); + + let shader = Rc::new(Shader { + label: Some("tint_shader".into()), + source: include_str!("../../shaders/tint.wgsl").to_string(), + }); + + let mut desc = NodeDesc::new( + NodeType::Render, + Some(PipelineDescriptor::Render(RenderPipelineDescriptor { + label: Some("tint_pass".into()), + layouts: vec![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: self.render_target.as_ref().unwrap().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![(&TintPassSlots::TextureViewBindGroup, bg.into(), Some(bgl))], + ); + + // 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 + } + + fn prepare( + &mut self, + _: &mut crate::render::graph::RenderGraph, + _: &mut lyra_ecs::World, + _: &mut crate::render::graph::RenderGraphContext, + ) { + //todo!() + } + + fn execute( + &mut self, + graph: &mut crate::render::graph::RenderGraph, + _: &crate::render::graph::NodeDesc, + context: &mut crate::render::graph::RenderGraphContext, + ) { + let rt = self.render_target.as_ref().unwrap(); + let frame = rt.create_frame(); + + let view = frame.texture() + .create_view(&wgpu::TextureViewDescriptor::default()); + + 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 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, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: true, + }, + })], + depth_stencil_attachment: None, + }); + + pass.set_bind_group(0, bg, &[]); + pass.draw(0..4, 0..1); + } + } +} diff --git a/lyra-game/src/render/graph/render_target.rs b/lyra-game/src/render/graph/render_target.rs index 2e7eb5d..09efac5 100644 --- a/lyra-game/src/render/graph/render_target.rs +++ b/lyra-game/src/render/graph/render_target.rs @@ -89,6 +89,21 @@ impl RenderTarget { }, } } + + /// Create the frame of the RenderTarget. + /// + /// If this is target is a surface and the frame texture was already retrieved from the + /// swap chain, a [`wgpu::SurfaceError`] error will be returned. + pub fn create_frame(&self) -> Frame { + let texture = self.frame_texture() + .expect("failed to create frame texture"); // TODO: should be returned to the user + let size = self.size(); + + Frame { + size, + texture, + } + } } pub enum FrameTexture { @@ -96,10 +111,12 @@ pub enum FrameTexture { Texture(Arc), } -#[allow(dead_code)] +/// Represents the current frame that is being rendered to. +//#[allow(dead_code)] pub struct Frame { - pub(crate) device: Arc, - pub(crate) queue: Arc, + /* pub(crate) device: Arc, + pub(crate) queue: Arc, */ + pub(crate) size: math::UVec2, pub(crate) texture: FrameTexture, } @@ -120,4 +137,9 @@ impl Frame { FrameTexture::Texture(_) => {}, } } + + /// The size of the frame + pub fn size(&self) -> math::UVec2 { + self.size + } } \ No newline at end of file diff --git a/lyra-game/src/render/shaders/tint.wgsl b/lyra-game/src/render/shaders/tint.wgsl new file mode 100644 index 0000000..37ce6f5 --- /dev/null +++ b/lyra-game/src/render/shaders/tint.wgsl @@ -0,0 +1,31 @@ +@group(0) @binding(0) +var t_screen: texture_2d; +@group(0) @binding(1) +var s_screen: sampler; + +struct VertexOutput { + @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]; + + return out; +} + +@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); + return vec4(rgb, 1.0); +} \ No newline at end of file