StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | //! Buffer management for vertices, indices, and uniforms |
| 2 | //! |
| 3 | //! BLOCCO 4: Buffer Management |
| 4 | //! Handles GPU buffer creation, upload, and management |
| 5 | |
| 6 | use std::mem; |
| 7 | use 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)] |
| 16 | pub 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 | |
| 24 | impl 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 |
| 70 | pub struct BufferManager { |
| 71 | vertex_buffer: Buffer, |
| 72 | index_buffer: Buffer, |
| 73 | uniform_buffer: Buffer, |
| 74 | vertex_capacity: u64, |
| 75 | index_capacity: u64, |
| 76 | } |
| 77 | |
| 78 | impl 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)] |
| 184 | mod 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 |