Skip to main content

cvkg_render_gpu/api/
frame.rs

1use crate::renderer::GpuRenderer;
2use crate::types::{MAX_INDICES, MAX_VERTICES};
3use cvkg_core::LAYOUT_DIRTY;
4use cvkg_core::Renderer;
5use std::sync::atomic::Ordering;
6
7impl cvkg_core::FrameRenderer<wgpu::CommandEncoder> for GpuRenderer {
8    fn begin_frame(&mut self) -> wgpu::CommandEncoder {
9        cvkg_core::begin_render_phase();
10        self.frame_rendered = false;
11        self.app_drew_background = false;
12        let id = self
13            .current_window
14            .expect("No target window set for frame. Call set_target_window first.");
15        self.begin_frame(id)
16    }
17
18    fn render_frame(&mut self) {
19        // Visual Lint: If layout was dirtied during the render phase (layout thrashing),
20        // draw a 10px red border as a warning flash.
21        if LAYOUT_DIRTY.swap(false, Ordering::AcqRel)
22            && let Some(window_id) = self.current_window
23            && let Some(surface_ctx) = self.surfaces.get(&window_id)
24        {
25            let w = surface_ctx.config.width as f32;
26            let h = surface_ctx.config.height as f32;
27            let border_rect = cvkg_core::Rect {
28                x: 0.0,
29                y: 0.0,
30                width: w,
31                height: h,
32            };
33            // Draw a thick red border to signal layout-thrashing
34            self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
35        }
36
37        // Dynamic Buffer Growth (Up to 4x capacity)
38        let max_v_capacity = MAX_VERTICES * 4;
39        let grown = self.geometry_buffers.grow_vertex_buffer(
40            &self.device,
41            self.vertices.len(),
42            max_v_capacity,
43        );
44        if grown {
45            tracing::info!("Grew vertex buffer to fit {} vertices", self.vertices.len());
46        }
47        if self.vertices.len() > max_v_capacity {
48            tracing::error!("Exceeded dynamic vertex buffer max capacity! Capping geometry.");
49            self.vertices.truncate(max_v_capacity);
50        }
51
52        let max_i_capacity = MAX_INDICES * 4;
53        let grown = self.geometry_buffers.grow_index_buffer(
54            &self.device,
55            self.indices.len(),
56            max_i_capacity,
57        );
58        if grown {
59            tracing::info!("Grew index buffer to fit {} indices", self.indices.len());
60        }
61        if self.indices.len() > max_i_capacity {
62            tracing::error!("Exceeded dynamic index buffer max capacity! Capping geometry.");
63            self.indices.truncate(max_i_capacity);
64        }
65
66        // Forge Submission: Sync all geometry to GPU using StagingBelt with a dedicated encoder
67        let mut staging_encoder =
68            self.device
69                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
70                    label: Some("Surtr Staging Encoder"),
71                });
72
73        let mut has_writes = false;
74
75        if !self.vertices.is_empty() {
76            let v_bytes = bytemuck::cast_slice(&self.vertices);
77            self.staging_belt
78                .write_buffer(
79                    &mut staging_encoder,
80                    &self.geometry_buffers.vertex_buffer,
81                    0,
82                    wgpu::BufferSize::new(v_bytes.len() as u64).unwrap(),
83                )
84                .copy_from_slice(v_bytes);
85            has_writes = true;
86        }
87
88        if !self.indices.is_empty() {
89            let i_bytes = bytemuck::cast_slice(&self.indices);
90            self.staging_belt
91                .write_buffer(
92                    &mut staging_encoder,
93                    &self.geometry_buffers.index_buffer,
94                    0,
95                    wgpu::BufferSize::new(i_bytes.len() as u64).unwrap(),
96                )
97                .copy_from_slice(i_bytes);
98            has_writes = true;
99        }
100
101        if !self.instance_data.is_empty() {
102            let inst_bytes = bytemuck::cast_slice(&self.instance_data);
103            self.staging_belt
104                .write_buffer(
105                    &mut staging_encoder,
106                    &self.geometry_buffers.instance_buffer,
107                    0,
108                    wgpu::BufferSize::new(inst_bytes.len() as u64).unwrap(),
109                )
110                .copy_from_slice(inst_bytes);
111            has_writes = true;
112        }
113
114        if !self.instance_data_3d.is_empty()
115            && let Some(ref buffer_3d) = self.instance_buffer_3d
116        {
117            let inst_3d_bytes = bytemuck::cast_slice(&self.instance_data_3d);
118            self.staging_belt
119                .write_buffer(
120                    &mut staging_encoder,
121                    buffer_3d,
122                    0,
123                    wgpu::BufferSize::new(inst_3d_bytes.len() as u64).unwrap(),
124                )
125                .copy_from_slice(inst_3d_bytes);
126            has_writes = true;
127        }
128
129        if has_writes {
130            self.staging_belt.finish();
131            self.staging_command_buffers.push(staging_encoder.finish());
132        }
133
134        // Update Time & Uniforms (Direct write is fine for small uniforms)
135        self.current_scene.time = self.start_time.elapsed().as_secs_f32();
136        self.queue.write_buffer(
137            &self.scene_buffer,
138            0,
139            bytemuck::bytes_of(&self.current_scene),
140        );
141        self.queue.write_buffer(
142            &self.theme_buffer,
143            0,
144            bytemuck::bytes_of(&self.current_theme),
145        );
146
147        // Populate telemetry for this frame
148        self.telemetry.draw_calls = self.draw_calls.len() as u32;
149        self.telemetry.vertices = self.vertices.len() as u32;
150        self.frame_rendered = true;
151
152        tracing::debug!(
153            "[Perf] draw_calls={} vertices={} instances={} staging_cmds={}",
154            self.draw_calls.len(),
155            self.vertices.len(),
156            self.instance_data.len(),
157            self.staging_command_buffers.len()
158        );
159    }
160
161    fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
162        GpuRenderer::end_frame(self, encoder);
163        cvkg_core::end_render_phase();
164    }
165}