render: implement view target chains for post processing steps

This commit is contained in:
SeanOMik 2024-06-27 23:48:24 -04:00
parent 545da71cda
commit 007b1047ef
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
8 changed files with 157 additions and 138 deletions

View File

@ -12,7 +12,7 @@ lyra-math = { path = "../lyra-math" }
lyra-scene = { path = "../lyra-scene" } lyra-scene = { path = "../lyra-scene" }
winit = "0.28.1" winit = "0.28.1"
wgpu = "0.15.1" wgpu = { version = "0.15.1", features = [ "expose-ids"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] } tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }

View File

@ -135,7 +135,7 @@ impl Node for BasePass {
) { ) {
let mut vt = graph.view_target_mut(); let mut vt = graph.view_target_mut();
vt.primary.create_frame(); vt.primary.create_frame();
vt.next_chain(context.encoder.as_mut().unwrap()); //vt.next_chain();
vt.primary.create_frame_view(); vt.primary.create_frame_view();
/* debug_assert!( /* debug_assert!(
!rt.current_texture.is_some(), !rt.current_texture.is_some(),

View File

@ -16,5 +16,5 @@ pub use present_pass::*;
mod init; mod init;
pub use init::*; pub use init::*;
/* mod tint; mod tint;
pub use tint::*; */ pub use tint::*;

View File

@ -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()); vt.copy_to_primary(context.encoder.as_mut().unwrap());
context.submit_encoder(); context.submit_encoder();

View File

@ -1,35 +1,34 @@
use std::{cell::RefCell, rc::Rc}; use std::{collections::HashMap, rc::Rc};
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
use crate::render::{ use crate::render::{
graph::{Node, NodeDesc, NodeSlot, NodeType, RenderTarget, SlotAttribute, SlotType, SlotValue}, graph::{Node, NodeDesc, NodeType},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
}; };
use super::BasePassSlots;
#[derive(Debug, Clone, Copy, Hash, RenderGraphLabel)] #[derive(Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub enum TintPassSlots { pub enum TintPassSlots {
InputRenderTarget, InputRenderTarget,
InputTextureView, InputTextureView,
TextureViewBindGroup, TextureViewBindGroup,
Frame Frame,
} }
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct TintPassLabel; pub struct TintPassLabel;
#[derive(Debug, Default)]
pub struct TintPass { pub struct TintPass {
render_target: Option<RenderTarget>, target_sampler: Option<wgpu::Sampler>,
bgl: Option<Rc<wgpu::BindGroupLayout>>,
bg_cache: HashMap<wgpu::Id, wgpu::BindGroup>,
} }
impl TintPass { impl TintPass {
pub fn new(render_target: RenderTarget) -> Self { pub fn new() -> Self {
Self { Self::default()
render_target: Some(render_target)
}
} }
} }
@ -40,14 +39,6 @@ impl Node for TintPass {
) -> crate::render::graph::NodeDesc { ) -> crate::render::graph::NodeDesc {
let device = &graph.device; 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 { let bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("tint_bgl"), label: Some("tint_bgl"),
entries: &[ entries: &[
@ -70,27 +61,16 @@ impl Node for TintPass {
], ],
}); });
let bgl = Rc::new(bgl); let bgl = Rc::new(bgl);
self.bgl = Some(bgl.clone());
let input_view = graph self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default()));
.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 { let shader = Rc::new(Shader {
label: Some("tint_shader".into()), label: Some("tint_shader".into()),
source: include_str!("../../shaders/tint.wgsl").to_string(), source: include_str!("../../shaders/tint.wgsl").to_string(),
}); });
let mut desc = NodeDesc::new( let vt = graph.view_target();
let desc = NodeDesc::new(
NodeType::Render, NodeType::Render,
Some(PipelineDescriptor::Render(RenderPipelineDescriptor { Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
label: Some("tint_pass".into()), label: Some("tint_pass".into()),
@ -105,7 +85,7 @@ impl Node for TintPass {
module: shader, module: shader,
entry_point: "fs_main".into(), entry_point: "fs_main".into(),
targets: vec![Some(wgpu::ColorTargetState { targets: vec![Some(wgpu::ColorTargetState {
format: self.render_target.as_ref().unwrap().format(), format: vt.format(),
blend: Some(wgpu::BlendState::REPLACE), blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL, write_mask: wgpu::ColorWrites::ALL,
})], })],
@ -115,22 +95,9 @@ impl Node for TintPass {
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None, 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 desc
} }
@ -149,31 +116,45 @@ impl Node for TintPass {
_: &crate::render::graph::NodeDesc, _: &crate::render::graph::NodeDesc,
context: &mut crate::render::graph::RenderGraphContext, context: &mut crate::render::graph::RenderGraphContext,
) { ) {
let rt = self.render_target.as_ref().unwrap(); let pipeline = graph
let frame = rt.create_frame(); .pipeline(context.label.clone())
.expect("Failed to find pipeline for MeshPass");
let view = frame.texture() let mut vt = graph.view_target_mut();
.create_view(&wgpu::TextureViewDescriptor::default()); 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 let bg = self
.slot_value_mut(TintPassSlots::Frame) .bg_cache
.expect("somehow the frame slot is missing"); .entry(source_view.global_id())
*frame_slot = SlotValue::Frame(Rc::new(RefCell::new(Some(frame)))); .or_insert_with(|| {
graph
let bg = graph.bind_group(TintPassSlots::TextureViewBindGroup); .device()
.create_bind_group(&wgpu::BindGroupDescriptor {
/* let view = graph label: Some("tint_bg"),
.slot_value(BasePassSlots::WindowTextureView) layout: self.bgl.as_ref().unwrap(),
.unwrap() entries: &[
.as_texture_view() wgpu::BindGroupEntry {
.expect("BasePassSlots::WindowTextureView was not a TextureView slot"); */ 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 encoder = context.encoder.as_mut().unwrap();
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("tint_pass"), label: Some("tint_pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view, view: &dest_view,
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Load, load: wgpu::LoadOp::Load,
@ -182,9 +163,10 @@ impl Node for TintPass {
})], })],
depth_stencil_attachment: None, depth_stencil_attachment: None,
}); });
pass.set_pipeline(&pipeline.as_render());
pass.set_bind_group(0, bg, &[]); pass.set_bind_group(0, bg, &[]);
pass.draw(0..4, 0..1); pass.draw(0..3, 0..1);
} }
} }
} }

View File

@ -1,5 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use tracing::debug;
use crate::math; use crate::math;
enum RenderTargetInner { enum RenderTargetInner {
@ -187,39 +189,29 @@ impl FrameTarget {
} }
} }
pub struct TargetViewChain { //struct TargetViewChainPrimary
source: FrameTarget,
dest: FrameTarget, pub struct TargetViewChain<'a> {
pub source: &'a mut FrameTarget,
pub dest: &'a mut FrameTarget,
} }
impl TargetViewChain { struct ViewChain {
pub fn next(&mut self, encoder: &mut wgpu::CommandEncoder) { source: FrameTarget,
let size = self.source.size(); dest: FrameTarget,
let size = wgpu::Extent3d { /// tracks the target that is currently being presented
width: size.x, active: u8,
height: size.y, }
depth_or_array_layers: 1,
};
let source_tex = self.source.frame.as_ref().unwrap().texture(); impl ViewChain {
let dest_tex = self.dest.frame.as_ref().unwrap().texture(); fn active(&self) -> &FrameTarget {
if self.active == 0 {
let source_ict = wgpu::ImageCopyTexture { &self.source
texture: source_tex, } else if self.active == 1 {
mip_level: 0, &self.dest
origin: wgpu::Origin3d::ZERO, } else {
aspect: wgpu::TextureAspect::All, panic!("active chain index became invalid! ({})", self.active);
}; }
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);
} }
} }
@ -227,16 +219,19 @@ pub struct ViewTarget {
device: Arc<wgpu::Device>, device: Arc<wgpu::Device>,
/// The primary RenderTarget, likely a Surface /// The primary RenderTarget, likely a Surface
pub primary: FrameTarget, pub primary: FrameTarget,
pub chain: Option<TargetViewChain>, chain: Option<ViewChain>,
} }
impl ViewTarget { impl ViewTarget {
pub fn new(device: Arc<wgpu::Device>, primary: RenderTarget) -> Self { pub fn new(device: Arc<wgpu::Device>, primary: RenderTarget) -> Self {
Self { let mut s = Self {
device, device,
primary: FrameTarget::new(primary), primary: FrameTarget::new(primary),
chain: None chain: None,
} };
s.create_chain(s.primary.format(), s.primary.size());
s
} }
pub fn size(&self) -> math::UVec2 { pub fn size(&self) -> math::UVec2 {
@ -248,15 +243,14 @@ impl ViewTarget {
} }
pub fn resize(&mut self, device: &wgpu::Device, size: math::UVec2) { pub fn resize(&mut self, device: &wgpu::Device, size: math::UVec2) {
if size != self.primary.size() {
self.primary.render_target.resize(device, 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. fn create_chain(&mut self, format: wgpu::TextureFormat, size: math::UVec2) {
pub fn next_chain(&mut self, encoder: &mut wgpu::CommandEncoder) -> &mut TargetViewChain { debug!("Creating chain with {:?} format and {:?} size", format, size);
// 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();
let mut source = FrameTarget::new(RenderTarget::new_texture(&self.device, format, size)); let mut source = FrameTarget::new(RenderTarget::new_texture(&self.device, format, size));
source.create_frame(); source.create_frame();
@ -266,36 +260,69 @@ impl ViewTarget {
dest.create_frame(); dest.create_frame();
dest.create_frame_view(); dest.create_frame_view();
self.chain = Some(TargetViewChain { self.chain = Some(ViewChain {
source, source,
dest, 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 /// Get the [`wgpu::TextureView`] to render to
pub fn render_view(&self) -> &wgpu::TextureView { pub fn render_view(&self) -> &wgpu::TextureView {
let chain = self.chain.as_ref().unwrap(); 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 /// Copy the chain target to the primary target
/// ///
/// The primary target must have `wgpu::TextureUsages::COPY_DST`. /// The primary target must have `wgpu::TextureUsages::COPY_DST`. This also resets the active
pub fn copy_to_primary(&self, encoder: &mut wgpu::CommandEncoder) { /// chain texture.
let chain = self.chain.as_ref().unwrap(); pub fn copy_to_primary(&mut self, encoder: &mut wgpu::CommandEncoder) {
let chain_tex = chain.source.frame.as_ref().unwrap().texture(); let chain = self.chain.as_mut().unwrap();
let active_tex = chain.active().frame.as_ref().unwrap().texture();
let source_ict = wgpu::ImageCopyTexture { let active_copy = wgpu::ImageCopyTexture {
texture: chain_tex, texture: active_tex,
mip_level: 0, mip_level: 0,
origin: wgpu::Origin3d::ZERO, origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All, aspect: wgpu::TextureAspect::All,
}; };
let dest_tex = self.primary.frame.as_ref().unwrap().texture(); let dest_tex = self.primary.frame.as_ref().unwrap().texture();
let dest_ict = wgpu::ImageCopyTexture { let dest_copy = wgpu::ImageCopyTexture {
texture: dest_tex, texture: dest_tex,
mip_level: 0, mip_level: 0,
origin: wgpu::Origin3d::ZERO, origin: wgpu::Origin3d::ZERO,
@ -309,6 +336,11 @@ impl ViewTarget {
depth_or_array_layers: 1, 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;
} }
} }

View File

@ -9,7 +9,7 @@ use lyra_game_derive::RenderGraphLabel;
use tracing::{debug, instrument, warn}; use tracing::{debug, instrument, warn};
use winit::window::Window; 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::graph::RenderGraph;
use super::{resource::RenderPipeline, render_job::RenderJob}; 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 present_pass_label = PresentPassLabel::new(BasePassSlots::Frame);//TintPassSlots::Frame);
let p = PresentPass::default(); let p = PresentPass::default();
main_graph.add_node(PresentPassLabel, p); main_graph.add_node(PresentPassLabel, p);

View File

@ -4,28 +4,29 @@ var t_screen: texture_2d<f32>;
var s_screen: sampler; var s_screen: sampler;
struct VertexOutput { struct VertexOutput {
@builtin(position) clip_position: vec4<f32>, @builtin(position)
@location(0) tex_coords: vec2<f32>, clip_position: vec4<f32>,
@location(0)
tex_coords: vec2<f32>,
} }
@vertex @vertex
fn vs_main( fn vs_main(
@builtin(vertex_index) vertex_index: u32, @builtin(vertex_index) vertex_index: u32,
) -> VertexOutput { ) -> VertexOutput {
let tex_coords = vec2<f32>(f32(vertex_index >> 1u), f32(vertex_index & 1u)) * 2.0;
let clip_position = vec4<f32>(tex_coords * vec2<f32>(2.0, -2.0) + vec2<f32>(-1.0, 1.0), 0.0, 1.0);
const vertices = array<vec4<f32>, 4>(vec4<f32>(-1.0, -1.0, 0.0, 1.0), vec4<f32>(1.0, -1.0, 0.0, 1.0), vec4<f32>(-1.0, 1.0, 0.0, 1.0), vec4<f32>(1.0, 1.0, 0.0, 1.0)); return VertexOutput(clip_position, tex_coords);
const tex_coords = array<vec2<f32>, 4>(vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 1.0));
var out: VertexOutput;
out.clip_position = vertices[vertex_index];
out.tex_coords = tex_coords[vertex_index];
return out;
} }
@fragment @fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let rgb: vec3<f32> = textureSample(t_screen, s_screen, in.tex_coords).xyz; let resolution = vec2<f32>(textureDimensions(t_screen));
rgb *= vec3<f32>(0.8); let inverse_screen_size = 1.0 / resolution.xy;
let tex_coords = in.clip_position.xy * inverse_screen_size;
var rgb: vec3<f32> = textureSample(t_screen, s_screen, tex_coords).xyz;
rgb *= vec3<f32>(1.0, 0.2, 0.2);
return vec4<f32>(rgb, 1.0); return vec4<f32>(rgb, 1.0);
} }