Skip to main content

cvkg_render_gpu/passes/
effects.rs

1use crate::kvasir::node::{ExecutionContext, KvasirNode};
2use crate::kvasir::nodes::{PassId, RES_SCENE};
3use crate::kvasir::resource::ResourceId;
4
5pub struct OffscreenGeometryNode {
6    pub target_id: u64,
7    pub output_texture: ResourceId,
8    pub inputs: Vec<ResourceId>,
9    pub outputs: Vec<ResourceId>,
10}
11
12impl OffscreenGeometryNode {
13    pub fn new(target_id: u64, output_texture: ResourceId) -> Self {
14        Self {
15            target_id,
16            output_texture,
17            inputs: vec![],
18            outputs: vec![output_texture],
19        }
20    }
21}
22
23impl KvasirNode for OffscreenGeometryNode {
24    fn label(&self) -> &'static str {
25        "OffscreenGeometry"
26    }
27    fn inputs(&self) -> &[ResourceId] {
28        &self.inputs
29    }
30    fn outputs(&self) -> &[ResourceId] {
31        &self.outputs
32    }
33    fn pass_id(&self) -> PassId {
34        PassId::Geometry
35    }
36
37    fn execute(&self, ctx: &mut ExecutionContext) {
38        let view = match ctx.registry.get_texture_view(self.output_texture) {
39            Some(v) => v,
40            None => {
41                log::error!(
42                    "Missing texture view for {}",
43                    stringify!(self.output_texture)
44                );
45                return;
46            }
47        };
48        // Use a dummy depth view for offscreen passes for now (no depth testing)
49        // or we need a dynamic depth texture in the registry.
50
51        let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
52            label: Some("Surtr Offscreen Geometry"),
53            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
54                view: &view,
55                resolve_target: None,
56                ops: wgpu::Operations {
57                    load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
58                    store: wgpu::StoreOp::Store,
59                },
60                depth_slice: None,
61            })],
62            depth_stencil_attachment: None, // No depth testing for now
63            timestamp_writes: None,
64            occlusion_query_set: None,
65            multiview_mask: None,
66        });
67
68        if !ctx.renderer.draw_calls.is_empty() {
69            p.set_vertex_buffer(0, ctx.renderer.geometry_buffers.vertex_buffer.slice(..));
70            p.set_vertex_buffer(1, ctx.renderer.geometry_buffers.instance_buffer.slice(..));
71            p.set_index_buffer(
72                ctx.renderer.geometry_buffers.index_buffer.slice(..),
73                wgpu::IndexFormat::Uint32,
74            );
75            p.set_bind_group(1, &ctx.renderer.dummy_env_bind_group, &[]);
76            p.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
77            p.set_bind_group(3, &ctx.renderer.gradient_bind_group, &[]);
78
79            for call in ctx
80                .renderer
81                .draw_calls
82                .iter()
83                .filter(|c| c.target_id == Some(self.target_id))
84            {
85                p.set_pipeline(&ctx.renderer.opaque_pipeline);
86                let bg = if let Some(id) = call.texture_id {
87                    if id == 0 {
88                        &ctx.renderer.mega_heim_bind_group
89                    } else {
90                        ctx.renderer
91                            .texture_bind_groups
92                            .get(id as usize)
93                            .unwrap_or(&ctx.renderer.dummy_texture_bind_group)
94                    }
95                } else {
96                    &ctx.renderer.dummy_texture_bind_group
97                };
98                p.set_bind_group(0, bg, &[]);
99                p.draw_indexed(
100                    call.index_start..call.index_start + call.index_count,
101                    0,
102                    call.instance_start..call.instance_start + call.instance_count,
103                );
104            }
105        }
106    }
107}
108
109pub struct EffectCompositeNode {
110    pub target_id: u64,
111    pub input_texture: ResourceId,
112    pub output_scene: ResourceId,
113    pub effect: String,
114    pub blend_mode: u32,
115    pub effect_args: [f32; 16],
116    pub inputs: Vec<ResourceId>,
117    pub outputs: Vec<ResourceId>,
118}
119
120impl EffectCompositeNode {
121    pub fn new(
122        target_id: u64,
123        input_texture: ResourceId,
124        effect: String,
125        blend_mode: u32,
126        effect_args: [f32; 16],
127    ) -> Self {
128        Self {
129            target_id,
130            input_texture,
131            output_scene: RES_SCENE,
132            effect,
133            blend_mode,
134            effect_args,
135            inputs: vec![input_texture],
136            outputs: vec![RES_SCENE],
137        }
138    }
139}
140
141impl KvasirNode for EffectCompositeNode {
142    fn label(&self) -> &'static str {
143        "EffectComposite"
144    }
145    fn inputs(&self) -> &[ResourceId] {
146        &self.inputs
147    }
148    fn outputs(&self) -> &[ResourceId] {
149        &self.outputs
150    }
151    fn pass_id(&self) -> PassId {
152        PassId::PostProcess {
153            pipeline_id: self.target_id,
154        }
155    }
156
157    fn execute(&self, ctx: &mut ExecutionContext) {
158        let input_view = match ctx.registry.get_texture_view(self.input_texture) {
159            Some(v) => v,
160            None => {
161                log::error!(
162                    "Missing texture view for {}",
163                    stringify!(self.input_texture)
164                );
165                return;
166            }
167        };
168        let scene_view = match ctx.registry.get_texture_view(self.output_scene) {
169            Some(v) => v,
170            None => {
171                log::error!("Missing texture view for {}", stringify!(self.output_scene));
172                return;
173            }
174        };
175
176        // 1. Retrieve or create bind group for the input texture from cache
177        let bind_group = ctx.get_or_create_bind_group(
178            (self.input_texture, 0, false),
179            &ctx.renderer.texture_bind_group_layout,
180            &[
181                wgpu::BindGroupEntry {
182                    binding: 0,
183                    resource: wgpu::BindingResource::TextureViewArray(&vec![&input_view; 32]),
184                },
185                wgpu::BindGroupEntry {
186                    binding: 1,
187                    resource: wgpu::BindingResource::Sampler(&ctx.renderer.linear_sampler),
188                },
189            ],
190            Some("Effect Input Bind Group"),
191        );
192
193        // 2. Map effect name to pipeline
194        // For now, we will use a dummy pipeline, or compile shaders on the fly?
195        // Wait, WGSL is compiled into ctx.renderer.effect_pipelines!
196        // We'll need to look it up from ctx.renderer.effect_pipelines
197        let pipeline = if let Some(p) = ctx.renderer.effect_pipelines.get(&self.effect) {
198            p
199        } else {
200            return;
201        };
202
203        // 3. Write parameters to uniform buffer
204        ctx.renderer.queue.write_buffer(
205            &ctx.renderer.effect_params_buffer,
206            0,
207            bytemuck::cast_slice(&[crate::types::EffectUniforms {
208                time: ctx.renderer.start_time.elapsed().as_secs_f32(),
209                pad0: 0.0,
210                size: [
211                    ctx.renderer.current_width() as f32,
212                    ctx.renderer.current_height() as f32,
213                ],
214                args: self.effect_args,
215            }]),
216        );
217
218        let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
219            label: Some("Surtr Effect Composite"),
220            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
221                view: &scene_view,
222                resolve_target: None,
223                ops: wgpu::Operations {
224                    load: wgpu::LoadOp::Load,
225                    store: wgpu::StoreOp::Store,
226                },
227                depth_slice: None,
228            })],
229            depth_stencil_attachment: None,
230            timestamp_writes: None,
231            occlusion_query_set: None,
232            multiview_mask: None,
233        });
234
235        p.set_pipeline(pipeline);
236        p.set_bind_group(0, &bind_group, &[]);
237        p.set_bind_group(1, &ctx.renderer.effect_params_bind_group, &[]);
238        p.draw(0..3, 0..1); // Fullscreen triangle
239    }
240}