Skip to main content

cvkg_render_gpu/api/
frame.rs

1use crate::renderer::GpuRenderer;
2use crate::types::{MAX_INDICES, MAX_VERTICES};
3use cvkg_core::Renderer;
4use cvkg_core::LAYOUT_DIRTY;
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            if let Some(window_id) = self.current_window {
23                if let Some(surface_ctx) = self.surfaces.get(&window_id) {
24                    let w = surface_ctx.config.width as f32;
25                    let h = surface_ctx.config.height as f32;
26                    let border_rect = cvkg_core::Rect {
27                        x: 0.0,
28                        y: 0.0,
29                        width: w,
30                        height: h,
31                    };
32                    // Draw a thick red border to signal layout-thrashing
33                    self.stroke_rect(border_rect, [1.0, 0.0, 0.0, 1.0], 10.0);
34                }
35            }
36        }
37
38        // Dynamic Buffer Growth (Up to 4x capacity)
39        let max_v_capacity = MAX_VERTICES * 4;
40        let grown = self.geometry_buffers.grow_vertex_buffer(
41            &self.device,
42            self.vertices.len(),
43            max_v_capacity,
44        );
45        if grown {
46            log::info!("Grew vertex buffer to fit {} vertices", self.vertices.len());
47        }
48        if self.vertices.len() > max_v_capacity {
49            log::error!("Exceeded dynamic vertex buffer max capacity! Capping geometry.");
50            self.vertices.truncate(max_v_capacity);
51        }
52
53        let max_i_capacity = MAX_INDICES * 4;
54        let grown = self.geometry_buffers.grow_index_buffer(
55            &self.device,
56            self.indices.len(),
57            max_i_capacity,
58        );
59        if grown {
60            log::info!("Grew index buffer to fit {} indices", self.indices.len());
61        }
62        if self.indices.len() > max_i_capacity {
63            log::error!("Exceeded dynamic index buffer max capacity! Capping geometry.");
64            self.indices.truncate(max_i_capacity);
65        }
66
67        // Forge Submission: Sync all geometry to GPU using StagingBelt with a dedicated encoder
68        let mut staging_encoder =
69            self.device
70                .create_command_encoder(&wgpu::CommandEncoderDescriptor {
71                    label: Some("Surtr Staging Encoder"),
72                });
73
74        let mut has_writes = false;
75
76        if !self.vertices.is_empty() {
77            let v_bytes = bytemuck::cast_slice(&self.vertices);
78            self.staging_belt
79                .write_buffer(
80                    &mut staging_encoder,
81                    &self.geometry_buffers.vertex_buffer,
82                    0,
83                    wgpu::BufferSize::new(v_bytes.len() as u64).unwrap(),
84                )
85                .copy_from_slice(v_bytes);
86            has_writes = true;
87        }
88
89        if !self.indices.is_empty() {
90            let i_bytes = bytemuck::cast_slice(&self.indices);
91            self.staging_belt
92                .write_buffer(
93                    &mut staging_encoder,
94                    &self.geometry_buffers.index_buffer,
95                    0,
96                    wgpu::BufferSize::new(i_bytes.len() as u64).unwrap(),
97                )
98                .copy_from_slice(i_bytes);
99            has_writes = true;
100        }
101
102        if !self.instance_data.is_empty() {
103            let inst_bytes = bytemuck::cast_slice(&self.instance_data);
104            self.staging_belt
105                .write_buffer(
106                    &mut staging_encoder,
107                    &self.geometry_buffers.instance_buffer,
108                    0,
109                    wgpu::BufferSize::new(inst_bytes.len() as u64).unwrap(),
110                )
111                .copy_from_slice(inst_bytes);
112            has_writes = true;
113        }
114
115        if has_writes {
116            self.staging_belt.finish();
117            self.staging_command_buffers.push(staging_encoder.finish());
118        }
119
120        // Update Time & Uniforms (Direct write is fine for small uniforms)
121        self.current_scene.time = self.start_time.elapsed().as_secs_f32();
122        self.queue.write_buffer(
123            &self.scene_buffer,
124            0,
125            bytemuck::bytes_of(&self.current_scene),
126        );
127        self.queue.write_buffer(
128            &self.theme_buffer,
129            0,
130            bytemuck::bytes_of(&self.current_theme),
131        );
132
133        // Populate telemetry for this frame
134        self.telemetry.draw_calls = self.draw_calls.len() as u32;
135        self.telemetry.vertices = self.vertices.len() as u32;
136        self.frame_rendered = true;
137
138        log::debug!(
139            "[Perf] draw_calls={} vertices={} instances={} staging_cmds={}",
140            self.draw_calls.len(),
141            self.vertices.len(),
142            self.instance_data.len(),
143            self.staging_command_buffers.len()
144        );
145    }
146
147    fn end_frame(&mut self, encoder: wgpu::CommandEncoder) {
148        GpuRenderer::end_frame(self, encoder);
149        cvkg_core::end_render_phase();
150    }
151}