Skip to main content

cvkg_render_gpu/passes/
ui.rs

1use crate::kvasir::node::{ExecutionContext, KvasirNode};
2use crate::kvasir::nodes::{PassId, RES_SCENE};
3use crate::kvasir::resource::ResourceId;
4
5/// A render graph node responsible for drawing top-level UI overlays onto the resolved scene texture.
6///
7/// Draws overlays back-to-front using painter's algorithm without depth buffer testing,
8/// enabling crisp vector elements and text rendering over the composited 3D scene.
9pub struct UINode {
10    pub inputs: Vec<ResourceId>,
11    pub outputs: Vec<ResourceId>,
12}
13
14impl UINode {
15    /// Creates a new instance of the UINode.
16    ///
17    /// It consumes `RES_SCENE` and writes the rendered UI overlays directly back into it.
18    pub fn new() -> Self {
19        Self {
20            inputs: vec![RES_SCENE],
21            outputs: vec![RES_SCENE],
22        }
23    }
24}
25
26impl Default for UINode {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32impl KvasirNode for UINode {
33    fn label(&self) -> &'static str {
34        "UI"
35    }
36
37    fn inputs(&self) -> &[ResourceId] {
38        &self.inputs
39    }
40
41    fn outputs(&self) -> &[ResourceId] {
42        &self.outputs
43    }
44
45    fn pass_id(&self) -> PassId {
46        PassId::UI
47    }
48
49    fn execute(&self, ctx: &mut ExecutionContext) {
50        let rt_w = ctx.renderer.current_width() as i32;
51        let rt_h = ctx.renderer.current_height() as i32;
52        let scale = ctx.renderer.current_scale_factor();
53
54        let scene_view = match ctx.registry.get_texture_view(RES_SCENE) {
55            Some(v) => v,
56            None => {
57                tracing::error!("Missing texture view for {}", stringify!(RES_SCENE));
58                return;
59            }
60        };
61
62        let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
63            label: Some("Surtr P4 UI Layer"),
64            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
65                view: &scene_view,
66                resolve_target: None,
67                ops: wgpu::Operations {
68                    load: wgpu::LoadOp::Load,
69                    store: wgpu::StoreOp::Store,
70                },
71                depth_slice: None,
72            })],
73            depth_stencil_attachment: None,
74            ..Default::default()
75        });
76
77        p.set_pipeline(&ctx.renderer.ui_pipeline);
78        p.set_vertex_buffer(0, ctx.renderer.geometry_buffers.vertex_buffer.slice(..));
79        p.set_vertex_buffer(1, ctx.renderer.geometry_buffers.instance_buffer.slice(..));
80        p.set_index_buffer(
81            ctx.renderer.geometry_buffers.index_buffer.slice(..),
82            wgpu::IndexFormat::Uint32,
83        );
84        p.set_bind_group(1, &ctx.renderer.dummy_env_bind_group, &[]);
85        p.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
86        p.set_bind_group(3, &ctx.renderer.gradient_bind_group, &[]);
87
88        for call in ctx
89            .renderer
90            .draw_calls
91            .iter()
92            .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI))
93        {
94            let bg = if let Some(id) = call.texture_id {
95                if id == 0 {
96                    &ctx.renderer.mega_heim_bind_group
97                } else {
98                    ctx.renderer
99                        .texture_bind_groups
100                        .get(id as usize)
101                        .unwrap_or(&ctx.renderer.dummy_texture_bind_group)
102                }
103            } else {
104                &ctx.renderer.dummy_texture_bind_group
105            };
106
107            p.set_bind_group(0, bg, &[]);
108
109            if let Some(rect) = call.scissor_rect
110                && rt_w > 0
111                && rt_h > 0
112            {
113                let x1 = (rect.x * scale).round() as i32;
114                let y1 = (rect.y * scale).round() as i32;
115                let x2 = ((rect.x + rect.width) * scale).round() as i32;
116                let y2 = ((rect.y + rect.height) * scale).round() as i32;
117                let w = (x2 - x1).clamp(0, rt_w);
118                let h = (y2 - y1).clamp(0, rt_h);
119                if w > 0 && h > 0 {
120                    p.set_scissor_rect(x1 as u32, y1 as u32, w as u32, h as u32);
121                } else {
122                    p.set_scissor_rect(0, 0, 0, 0);
123                }
124            } else {
125                p.set_scissor_rect(0, 0, rt_w as u32, rt_h as u32);
126            }
127            p.draw_indexed(
128                call.index_start..call.index_start + call.index_count,
129                0,
130                call.instance_start..call.instance_start + call.instance_count,
131            );
132        }
133    }
134}