Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-renderer/src/gpu/buffer_mgr.rs
StratoSDK / crates / strato-renderer / src / gpu / buffer_mgr.rs
1//! Buffer management for vertices, indices, and uniforms
2//!
3//! BLOCCO 4: Buffer Management
4//! Handles GPU buffer creation, upload, and management
5 
6use std::mem;
7use wgpu::{
8 util::{BufferInitDescriptor, DeviceExt},
9 Buffer, BufferAddress, BufferUsages, Device, Queue, VertexAttribute, VertexBufferLayout,
10 VertexFormat, VertexStepMode,
11};
12 
13/// Simple vertex for 2D rendering (position + color + UV)
14#[repr(C)]
15#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
16pub struct SimpleVertex {
17 pub position: [f32; 2],
18 pub color: [f32; 4],
19 pub uv: [f32; 2],
20 pub params: [f32; 4],
21 pub flags: u32,
22}
23 
24impl SimpleVertex {
25 /// Vertex buffer layout descriptor
26 pub fn desc() -> VertexBufferLayout<'static> {
27 VertexBufferLayout {
28 array_stride: mem::size_of::<SimpleVertex>() as BufferAddress,
29 step_mode: VertexStepMode::Vertex,
30 attributes: &[
31 // Position (Location 0)
32 VertexAttribute {
33 offset: 0,
34 shader_location: 0,
35 format: VertexFormat::Float32x2,
36 },
37 // Color (Location 1)
38 VertexAttribute {
39 offset: mem::size_of::<[f32; 2]>() as BufferAddress,
40 shader_location: 1,
41 format: VertexFormat::Float32x4,
42 },
43 // UV (Location 2)
44 VertexAttribute {
45 offset: (mem::size_of::<[f32; 2]>() + mem::size_of::<[f32; 4]>())
46 as BufferAddress,
47 shader_location: 2,
48 format: VertexFormat::Float32x2,
49 },
50 // Params (Location 3)
51 VertexAttribute {
52 offset: (mem::size_of::<[f32; 2]>() * 2 + mem::size_of::<[f32; 4]>())
53 as BufferAddress,
54 shader_location: 3,
55 format: VertexFormat::Float32x4,
56 },
57 // Flags (Location 4)
58 VertexAttribute {
59 offset: (mem::size_of::<[f32; 2]>() * 2 + mem::size_of::<[f32; 4]>() * 2)
60 as BufferAddress,
61 shader_location: 4,
62 format: VertexFormat::Uint32,
63 },
64 ],
65 }
66 }
67}
68 
69/// Manages GPU buffers
70pub struct BufferManager {
71 vertex_buffer: Buffer,
72 index_buffer: Buffer,
73 uniform_buffer: Buffer,
74 vertex_capacity: u64,
75 index_capacity: u64,
76}
77 
78impl BufferManager {
79 /// Initial capacity for vertex buffer (number of vertices)
80 const INITIAL_VERTEX_COUNT: u64 = 1024;
81 /// Initial capacity for index buffer (number of indices)
82 const INITIAL_INDEX_COUNT: u64 = 1024 * 3;
83 
84 /// Create new buffer manager
85 ///
86 /// # Arguments
87 /// * `device` - GPU device
88 pub fn new(device: &Device) -> Self {
89 // Create initial empty buffers with COPY_DST usage for updates
90 
91 let vertex_size = Self::INITIAL_VERTEX_COUNT * mem::size_of::<SimpleVertex>() as u64;
92 let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
93 label: Some("Vertex Buffer"),
94 size: vertex_size,
95 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
96 mapped_at_creation: false,
97 });
98 
99 let index_size = Self::INITIAL_INDEX_COUNT * mem::size_of::<u32>() as u64;
100 let index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
101 label: Some("Index Buffer"),
102 size: index_size,
103 usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
104 mapped_at_creation: false,
105 });
106 
107 // Uniform buffer for projection matrix (4x4 float matrix = 64 bytes)
108 let uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor {
109 label: Some("Uniform Buffer"),
110 size: 64,
111 usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
112 mapped_at_creation: false,
113 });
114 
115 Self {
116 vertex_buffer,
117 index_buffer,
118 uniform_buffer,
119 vertex_capacity: vertex_size,
120 index_capacity: index_size,
121 }
122 }
123 
124 /// Upload vertices to GPU
125 pub fn upload_vertices(&mut self, device: &Device, queue: &Queue, data: &[SimpleVertex]) {
126 let required_size = (data.len() * mem::size_of::<SimpleVertex>()) as u64;
127 
128 if required_size > self.vertex_capacity {
129 // Resize buffer
130 self.vertex_capacity = required_size.max(self.vertex_capacity * 2);
131 self.vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
132 label: Some("Vertex Buffer (Resized)"),
133 size: self.vertex_capacity,
134 usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
135 mapped_at_creation: false,
136 });
137 }
138 
139 queue.write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(data));
140 }
141 
142 /// Upload indices to GPU
143 pub fn upload_indices(&mut self, device: &Device, queue: &Queue, data: &[u32]) {
144 let required_size = (data.len() * mem::size_of::<u32>()) as u64;
145 
146 if required_size > self.index_capacity {
147 // Resize buffer
148 self.index_capacity = required_size.max(self.index_capacity * 2);
149 self.index_buffer = device.create_buffer(&wgpu::BufferDescriptor {
150 label: Some("Index Buffer (Resized)"),
151 size: self.index_capacity,
152 usage: BufferUsages::INDEX | BufferUsages::COPY_DST,
153 mapped_at_creation: false,
154 });
155 }
156 
157 queue.write_buffer(&self.index_buffer, 0, bytemuck::cast_slice(data));
158 }
159 
160 /// Upload projection matrix
161 pub fn upload_projection(&mut self, queue: &Queue, matrix: &[[f32; 4]; 4]) {
162 // Flatten matrix to [f32; 16] for upload
163 let data: &[f32; 16] = bytemuck::cast_ref(matrix);
164 queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(data));
165 }
166 
167 /// Get vertex buffer reference
168 pub fn vertex_buffer(&self) -> &Buffer {
169 &self.vertex_buffer
170 }
171 
172 /// Get index buffer reference
173 pub fn index_buffer(&self) -> &Buffer {
174 &self.index_buffer
175 }
176 
177 /// Get uniform buffer reference
178 pub fn uniform_buffer(&self) -> &Buffer {
179 &self.uniform_buffer
180 }
181}
182 
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::gpu::DeviceManager;
187 use wgpu::Backends;
188 
189 #[test]
190 fn test_vertex_layout() {
191 let layout = SimpleVertex::desc();
192 assert_eq!(layout.array_stride, mem::size_of::<SimpleVertex>() as u64);
193 assert_eq!(layout.step_mode, VertexStepMode::Vertex);
194 assert_eq!(layout.attributes.len(), 5);
195 // Position
196 assert_eq!(layout.attributes[0].format, VertexFormat::Float32x2);
197 assert_eq!(layout.attributes[0].offset, 0);
198 // Color
199 assert_eq!(layout.attributes[1].format, VertexFormat::Float32x4);
200 assert_eq!(layout.attributes[1].offset, 8);
201 // UV
202 assert_eq!(layout.attributes[2].format, VertexFormat::Float32x2);
203 assert_eq!(layout.attributes[2].offset, 24);
204 // Params
205 assert_eq!(layout.attributes[3].format, VertexFormat::Float32x4);
206 assert_eq!(layout.attributes[3].offset, 32);
207 // Flags
208 assert_eq!(layout.attributes[4].format, VertexFormat::Uint32);
209 assert_eq!(layout.attributes[4].offset, 48);
210 }
211 
212 #[tokio::test]
213 async fn test_buffer_creation() {
214 let dm = DeviceManager::new(Backends::all()).await.unwrap();
215 let buffer_mgr = BufferManager::new(dm.device());
216 
217 // Check buffers exist
218 // Note: We can't easily check internal wgpu state, but we can ensure no panic
219 let _v = buffer_mgr.vertex_buffer();
220 let _i = buffer_mgr.index_buffer();
221 let _u = buffer_mgr.uniform_buffer();
222 }
223 
224 #[tokio::test]
225 async fn test_buffer_upload() {
226 let dm = DeviceManager::new(Backends::all()).await.unwrap();
227 let mut buffer_mgr = BufferManager::new(dm.device());
228 
229 let vertices = vec![
230 SimpleVertex {
231 position: [0.0, 0.0],
232 color: [1.0, 0.0, 0.0, 1.0],
233 uv: [0.0, 0.0],
234 params: [0.0; 4],
235 flags: 0,
236 },
237 SimpleVertex {
238 position: [1.0, 1.0],
239 color: [0.0, 1.0, 0.0, 1.0],
240 uv: [1.0, 1.0],
241 params: [0.0; 4],
242 flags: 0,
243 },
244 ];
245 let indices: Vec<u32> = vec![0, 1, 2];
246 let projection = [[1.0; 4]; 4];
247 
248 // Should not panic
249 buffer_mgr.upload_vertices(dm.device(), dm.queue(), &vertices);
250 buffer_mgr.upload_indices(dm.device(), dm.queue(), &indices);
251 buffer_mgr.upload_projection(dm.queue(), &projection);
252 }
253}
254