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 KvasirNode for UINode {
27    fn label(&self) -> &'static str {
28        "UI"
29    }
30
31    fn inputs(&self) -> &[ResourceId] {
32        &self.inputs
33    }
34
35    fn outputs(&self) -> &[ResourceId] {
36        &self.outputs
37    }
38
39    fn pass_id(&self) -> PassId {
40        PassId::UI
41    }
42
43    fn execute(&self, ctx: &mut ExecutionContext) {
44        let rt_w = ctx.renderer.current_width() as i32;
45        let rt_h = ctx.renderer.current_height() as i32;
46        let scale = ctx.renderer.current_scale_factor();
47
48        let scene_view = match ctx.registry.get_texture_view(RES_SCENE) {
49            Some(v) => v,
50            None => {
51                log::error!("Missing texture view for {}", stringify!(RES_SCENE));
52                return;
53            }
54        };
55
56        let mut p = ctx.encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
57            label: Some("Surtr P4 UI Layer"),
58            color_attachments: &[Some(wgpu::RenderPassColorAttachment {
59                view: &scene_view,
60                resolve_target: None,
61                ops: wgpu::Operations {
62                    load: wgpu::LoadOp::Load,
63                    store: wgpu::StoreOp::Store,
64                },
65                depth_slice: None,
66            })],
67            depth_stencil_attachment: None,
68            ..Default::default()
69        });
70
71        p.set_pipeline(&ctx.renderer.ui_pipeline);
72        p.set_vertex_buffer(0, ctx.renderer.geometry_buffers.vertex_buffer.slice(..));
73        p.set_vertex_buffer(1, ctx.renderer.geometry_buffers.instance_buffer.slice(..));
74        p.set_index_buffer(
75            ctx.renderer.geometry_buffers.index_buffer.slice(..),
76            wgpu::IndexFormat::Uint32,
77        );
78        p.set_bind_group(1, &ctx.renderer.dummy_env_bind_group, &[]);
79        p.set_bind_group(2, &ctx.renderer.berserker_bind_group, &[]);
80
81        for call in ctx
82            .renderer
83            .draw_calls
84            .iter()
85            .filter(|c| matches!(c.material, cvkg_core::DrawMaterial::TopUI))
86        {
87            let bg = if let Some(id) = call.texture_id {
88                if id == 0 {
89                    &ctx.renderer.mega_heim_bind_group
90                } else {
91                    ctx.renderer
92                        .texture_bind_groups
93                        .get(id as usize)
94                        .unwrap_or(&ctx.renderer.dummy_texture_bind_group)
95                }
96            } else {
97                &ctx.renderer.dummy_texture_bind_group
98            };
99
100            p.set_bind_group(0, bg, &[]);
101
102            if let Some(rect) = call.scissor_rect
103                && rt_w > 0
104                && rt_h > 0
105            {
106                let x1 = (rect.x * scale).round() as i32;
107                let y1 = (rect.y * scale).round() as i32;
108                let x2 = ((rect.x + rect.width) * scale).round() as i32;
109                let y2 = ((rect.y + rect.height) * scale).round() as i32;
110                let w = (x2 - x1).clamp(0, rt_w);
111                let h = (y2 - y1).clamp(0, rt_h);
112                if w > 0 && h > 0 {
113                    p.set_scissor_rect(x1 as u32, y1 as u32, w as u32, h as u32);
114                } else {
115                    p.set_scissor_rect(0, 0, 0, 0);
116                }
117            } else {
118                p.set_scissor_rect(0, 0, rt_w as u32, rt_h as u32);
119            }
120            p.draw_indexed(
121                call.index_start..call.index_start + call.index_count,
122                0,
123                call.instance_start..call.instance_start + call.instance_count,
124            );
125        }
126    }
127}