Skip to main content

cvkg_render_gpu/passes/
pre_world_panel.rs

1use crate::kvasir::node::{ExecutionContext, KvasirNode};
2use crate::kvasir::resource::ResourceId;
3
4/// PreWorldPanelNode renders each WorldSpacePanel's VDOM subtree to its
5/// offscreen texture. The panel geometries are then submitted as textured
6/// quads by the Geometry pass.
7pub struct PreWorldPanelNode {
8    pub panel_textures: Vec<ResourceId>,
9    pub panel_ids: Vec<u64>,
10    pub inputs: Vec<ResourceId>,
11    pub outputs: Vec<ResourceId>,
12}
13
14impl PreWorldPanelNode {
15    pub fn new(panel_textures: Vec<ResourceId>, panel_ids: Vec<u64>) -> Self {
16        Self {
17            panel_textures: panel_textures.clone(),
18            panel_ids,
19            inputs: vec![],
20            outputs: panel_textures,
21        }
22    }
23}
24
25impl KvasirNode for PreWorldPanelNode {
26    fn label(&self) -> &'static str {
27        "PreWorldPanel"
28    }
29    fn inputs(&self) -> &[ResourceId] {
30        &self.inputs
31    }
32    fn outputs(&self) -> &[ResourceId] {
33        &self.outputs
34    }
35    fn pass_id(&self) -> crate::kvasir::nodes::PassId {
36        crate::kvasir::nodes::PassId::PreWorldPanel
37    }
38
39    fn execute(&self, ctx: &mut ExecutionContext) {
40        // For each panel texture, render the corresponding VDOM subtree.
41        // This is a simplified implementation - the actual implementation
42        // would iterate over panels, bind each offscreen texture as the
43        // render target, and run the UI rendering pipeline for that panel's
44        // VDOM subtree.
45
46        for (i, &panel_tex) in self.panel_textures.iter().enumerate() {
47            let panel_id = self.panel_ids[i];
48            let view = match ctx.registry.get_texture_view(panel_tex) {
49                Some(v) => v,
50                None => {
51                    tracing::error!(
52                        "PreWorldPanel: missing texture view for panel {} ({})",
53                        i,
54                        panel_tex.0
55                    );
56                    continue;
57                }
58            };
59
60            // Clear the offscreen texture
61            let mut pass = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
62                label: Some(&format!("Surtr PreWorldPanel {}", i)),
63                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
64                    view: &view,
65                    resolve_target: None,
66                    ops: wgpu::Operations {
67                        load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
68                        store: wgpu::StoreOp::Store,
69                    },
70                    depth_slice: None,
71                })],
72                depth_stencil_attachment: None,
73                timestamp_writes: None,
74                occlusion_query_set: None,
75                multiview_mask: None,
76            });
77
78            // Bind UI bind groups
79            pass.set_bind_group(1, &ctx.renderer.dummy_env_bind_group, &[]);
80            pass.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
81            pass.set_bind_group(3, &ctx.renderer.gradient_bind_group, &[]);
82
83            // Filter draw_calls to only those belonging to this panel's VDOM subtree.
84            if !ctx.renderer.draw_calls.is_empty() {
85                pass.set_vertex_buffer(0, ctx.renderer.geometry_buffers.vertex_buffer.slice(..));
86                pass.set_vertex_buffer(1, ctx.renderer.geometry_buffers.instance_buffer.slice(..));
87                pass.set_index_buffer(
88                    ctx.renderer.geometry_buffers.index_buffer.slice(..),
89                    wgpu::IndexFormat::Uint32,
90                );
91
92                for call in &ctx.renderer.draw_calls {
93                    if call.panel_id == Some(panel_id) {
94                        pass.draw_indexed(
95                            call.index_start..call.index_start + call.index_count,
96                            0,
97                            call.instance_start..call.instance_start + call.instance_count,
98                        );
99                    }
100                }
101            }
102        }
103    }
104}
105
106#[cfg(test)]
107mod pre_world_panel_tests {
108    use super::*;
109
110    #[test]
111    fn test_pre_world_panel_node_label() {
112        let node = PreWorldPanelNode::new(vec![], vec![]);
113        assert_eq!(node.label(), "PreWorldPanel");
114    }
115
116    #[test]
117    fn test_pre_world_panel_node_empty() {
118        let node = PreWorldPanelNode::new(vec![], vec![]);
119        assert!(node.inputs().is_empty());
120        assert!(node.outputs().is_empty());
121        assert!(node.panel_textures.is_empty());
122    }
123
124    #[test]
125    fn test_pre_world_panel_node_single_panel() {
126        let tex = ResourceId(2000);
127        let node = PreWorldPanelNode::new(vec![tex], vec![123]);
128        assert_eq!(node.inputs().len(), 0);
129        assert_eq!(node.outputs().len(), 1);
130        assert_eq!(node.outputs()[0], tex);
131        assert_eq!(node.panel_textures.len(), 1);
132    }
133
134    #[test]
135    fn test_pre_world_panel_node_multiple_panels() {
136        let texes = vec![ResourceId(2000), ResourceId(2001), ResourceId(2002)];
137        let node = PreWorldPanelNode::new(texes.clone(), vec![1, 2, 3]);
138        assert_eq!(node.outputs().len(), 3);
139        assert_eq!(node.panel_textures.len(), 3);
140        for (i, tex) in texes.iter().enumerate() {
141            assert_eq!(node.panel_textures[i], *tex);
142        }
143    }
144
145    #[test]
146    fn test_pre_world_panel_node_pass_id() {
147        let node = PreWorldPanelNode::new(vec![], vec![]);
148        assert!(matches!(
149            node.pass_id(),
150            crate::kvasir::nodes::PassId::PreWorldPanel
151        ));
152    }
153}