old school music tracker
at main 394 lines 15 kB view raw
1use wgpu::{ 2 AddressMode, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayoutDescriptor, 3 BindGroupLayoutEntry, BindingResource, BindingType, BlendState, ColorTargetState, ColorWrites, 4 CommandEncoderDescriptor, Device, DeviceDescriptor, Extent3d, Face, FilterMode, FragmentState, 5 FrontFace, Instance, MultisampleState, Operations, Origin3d, PipelineCompilationOptions, 6 PipelineLayoutDescriptor, PolygonMode, PowerPreference, PrimitiveState, PrimitiveTopology, 7 Queue, RenderPassColorAttachment, RenderPassDescriptor, RenderPipeline, 8 RenderPipelineDescriptor, RequestAdapterOptions, SamplerBindingType, SamplerDescriptor, 9 ShaderModuleDescriptor, ShaderStages, Surface, SurfaceConfiguration, SurfaceError, 10 TexelCopyBufferLayout, TexelCopyTextureInfo, Texture, TextureAspect, TextureDescriptor, 11 TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureViewDescriptor, 12 TextureViewDimension, VertexState, include_wgsl, 13}; 14use winit::window::Window; 15 16use crate::palettes::{PALETTE_SIZE, Palette, RGB10A2}; 17 18use super::coordinates::WINDOW_SIZE; 19 20pub struct GPUState { 21 surface: Surface<'static>, 22 23 device: Device, 24 queue: Queue, 25 config: SurfaceConfiguration, 26 size: winit::dpi::PhysicalSize<u32>, 27 28 render_pipeline: RenderPipeline, 29 30 streaming_texture: Texture, 31 color_map: Texture, 32 diffuse_bind_group: BindGroup, 33} 34 35impl GPUState { 36 /// Size of the Framebuffer in GPU speech 37 const TEXTURE_SIZE: Extent3d = Extent3d { 38 width: WINDOW_SIZE.0 as u32, 39 height: WINDOW_SIZE.1 as u32, 40 depth_or_array_layers: 1, 41 }; 42 43 const COLOR_MAP_SIZE: Extent3d = Extent3d { 44 width: PALETTE_SIZE as u32, 45 height: 1, 46 depth_or_array_layers: 1, 47 }; 48 49 pub async fn new(window: std::sync::Arc<Window>) -> Self { 50 let size = window.inner_size(); 51 let instance = Instance::default(); 52 53 let surface: Surface<'static> = instance.create_surface(window).unwrap(); 54 55 let adapter = instance 56 .request_adapter(&RequestAdapterOptions { 57 // i do very little on the GPU (only scale the whole image once per frame) 58 // so i think that this setting should be correct. 59 power_preference: PowerPreference::LowPower, 60 compatible_surface: Some(&surface), 61 force_fallback_adapter: false, 62 }) 63 .await 64 .unwrap(); 65 66 let (device, queue) = adapter 67 .request_device(&DeviceDescriptor { 68 label: None, 69 required_features: wgpu::Features::empty(), 70 required_limits: wgpu::Limits::downlevel_webgl2_defaults(), 71 // i only have two buffers, one constant size and the other one changing with the window size 72 // so i don't need a lot of allocations and they don't have to be that fast 73 memory_hints: wgpu::MemoryHints::MemoryUsage, 74 trace: wgpu::Trace::Off, 75 }) 76 .await 77 .unwrap(); 78 79 let surface_caps = surface.get_capabilities(&adapter); 80 81 let surface_format = *surface_caps 82 .formats 83 .iter() 84 .find(|f| !f.is_srgb()) 85 .unwrap_or(&surface_caps.formats[0]); 86 87 let config = SurfaceConfiguration { 88 usage: TextureUsages::RENDER_ATTACHMENT, 89 format: surface_format, 90 width: size.width, 91 height: size.height, 92 present_mode: surface_caps.present_modes[0], 93 alpha_mode: surface_caps.alpha_modes[0], 94 view_formats: vec![], 95 desired_maximum_frame_latency: 2, // default 96 }; 97 98 surface.configure(&device, &config); 99 100 let window_texture = device.create_texture(&TextureDescriptor { 101 size: Self::TEXTURE_SIZE, 102 mip_level_count: 1, 103 sample_count: 1, 104 dimension: TextureDimension::D2, 105 format: TextureFormat::R8Unorm, 106 usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, 107 label: Some("window texture"), 108 view_formats: &[], 109 }); 110 111 let texure_view = window_texture.create_view(&TextureViewDescriptor::default()); 112 let texture_sampler = device.create_sampler(&SamplerDescriptor { 113 address_mode_u: AddressMode::ClampToEdge, 114 address_mode_v: AddressMode::ClampToEdge, 115 address_mode_w: AddressMode::ClampToEdge, 116 mag_filter: FilterMode::Nearest, 117 min_filter: FilterMode::Nearest, 118 mipmap_filter: FilterMode::Nearest, 119 ..Default::default() 120 }); 121 122 let color_map = device.create_texture(&TextureDescriptor { 123 size: Self::COLOR_MAP_SIZE, 124 mip_level_count: 1, 125 sample_count: 1, 126 dimension: TextureDimension::D1, 127 format: TextureFormat::Rgb10a2Unorm, 128 usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING, 129 label: Some("color map"), 130 view_formats: &[], 131 }); 132 let color_view = color_map.create_view(&TextureViewDescriptor::default()); 133 134 let color_map_sampler = device.create_sampler(&wgpu::wgt::SamplerDescriptor { 135 label: Some("color map sampler"), 136 address_mode_u: AddressMode::Repeat, 137 address_mode_v: AddressMode::Repeat, 138 address_mode_w: AddressMode::Repeat, 139 mag_filter: FilterMode::Nearest, 140 min_filter: FilterMode::Nearest, 141 mipmap_filter: FilterMode::Nearest, 142 ..Default::default() 143 }); 144 145 let texture_bind_group_layout = 146 device.create_bind_group_layout(&BindGroupLayoutDescriptor { 147 label: Some("texture_bind_group_layout"), 148 entries: &[ 149 BindGroupLayoutEntry { 150 binding: 0, 151 visibility: ShaderStages::FRAGMENT, 152 ty: BindingType::Texture { 153 sample_type: TextureSampleType::Float { filterable: false }, 154 view_dimension: TextureViewDimension::D2, 155 multisampled: false, 156 }, 157 count: None, 158 }, 159 BindGroupLayoutEntry { 160 binding: 1, 161 visibility: ShaderStages::FRAGMENT, 162 ty: BindingType::Sampler(SamplerBindingType::NonFiltering), 163 count: None, 164 }, 165 BindGroupLayoutEntry { 166 binding: 2, 167 visibility: ShaderStages::FRAGMENT, 168 ty: BindingType::Texture { 169 sample_type: TextureSampleType::Float { filterable: false }, 170 view_dimension: TextureViewDimension::D1, 171 multisampled: false, 172 }, 173 count: None, 174 }, 175 BindGroupLayoutEntry { 176 binding: 3, 177 visibility: ShaderStages::FRAGMENT, 178 ty: BindingType::Sampler(SamplerBindingType::NonFiltering), 179 count: None, 180 }, 181 ], 182 }); 183 184 let diffuse_bind_group = device.create_bind_group(&BindGroupDescriptor { 185 label: Some("diffuse_bind_group"), 186 layout: &texture_bind_group_layout, 187 entries: &[ 188 BindGroupEntry { 189 binding: 0, 190 resource: BindingResource::TextureView(&texure_view), 191 }, 192 BindGroupEntry { 193 binding: 1, 194 resource: BindingResource::Sampler(&texture_sampler), 195 }, 196 BindGroupEntry { 197 binding: 2, 198 resource: BindingResource::TextureView(&color_view), 199 }, 200 BindGroupEntry { 201 binding: 3, 202 resource: BindingResource::Sampler(&color_map_sampler), 203 }, 204 ], 205 }); 206 207 const SHADER_DESCRIPTOR: ShaderModuleDescriptor = include_wgsl!("shader.wgsl"); 208 let shader = device.create_shader_module(SHADER_DESCRIPTOR); 209 210 // print shader compilation errors 211 #[cfg(debug_assertions)] 212 { 213 let compilation_info = shader.get_compilation_info().await; 214 println!( 215 "{} Shader Compilation Messages", 216 compilation_info.messages.len() 217 ); 218 for message in compilation_info.messages { 219 println!("{message:?}"); 220 } 221 } 222 223 let render_pipeline_layout = device.create_pipeline_layout(&PipelineLayoutDescriptor { 224 label: Some("Render Pipeline Layout"), 225 bind_group_layouts: &[&texture_bind_group_layout], 226 push_constant_ranges: &[], 227 }); 228 229 let render_pipeline = device.create_render_pipeline(&RenderPipelineDescriptor { 230 label: Some("Render Pipeline"), 231 layout: Some(&render_pipeline_layout), 232 vertex: VertexState { 233 module: &shader, 234 // only one entrypoint in the shader 235 entry_point: None, 236 buffers: &[], 237 compilation_options: PipelineCompilationOptions::default(), 238 }, 239 fragment: Some(FragmentState { 240 module: &shader, 241 // only one entry point in the shader 242 entry_point: None, 243 targets: &[Some(ColorTargetState { 244 format: config.format, 245 blend: Some(BlendState::REPLACE), 246 write_mask: ColorWrites::COLOR, 247 })], 248 compilation_options: PipelineCompilationOptions::default(), 249 }), 250 251 primitive: PrimitiveState { 252 topology: PrimitiveTopology::TriangleList, 253 strip_index_format: None, 254 front_face: FrontFace::Ccw, 255 cull_mode: Some(Face::Back), 256 polygon_mode: PolygonMode::Fill, 257 unclipped_depth: false, 258 conservative: false, 259 }, 260 depth_stencil: None, 261 multisample: MultisampleState { 262 count: 1, 263 mask: !0, 264 alpha_to_coverage_enabled: false, 265 }, 266 multiview: None, 267 cache: None, 268 }); 269 270 Self { 271 surface, 272 device, 273 queue, 274 config, 275 size, 276 render_pipeline, 277 diffuse_bind_group, 278 streaming_texture: window_texture, 279 color_map, 280 } 281 } 282 283 /// on next render the new palette will be used 284 pub fn queue_palette_update(&mut self, palette: Palette<RGB10A2>) { 285 let palette = palette.0.map(|c| c.0.to_le_bytes()); 286 287 // the queue will remember this and on the next render submission this will be submitted as well 288 self.queue.write_texture( 289 TexelCopyTextureInfo { 290 texture: &self.color_map, 291 mip_level: 0, 292 origin: Origin3d::ZERO, 293 aspect: TextureAspect::All, 294 }, 295 palette.as_flattened(), 296 TexelCopyBufferLayout { 297 offset: 0, 298 bytes_per_row: Some((PALETTE_SIZE * size_of::<u32>()) as u32), 299 rows_per_image: Some(1), 300 }, 301 Self::COLOR_MAP_SIZE, 302 ); 303 } 304 305 pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { 306 if new_size.width > 0 && new_size.height > 0 { 307 self.size = new_size; 308 self.config.width = new_size.width; 309 self.config.height = new_size.height; 310 self.surface.configure(&self.device, &self.config); 311 } 312 } 313 314 pub fn reinit_surface(&self) { 315 if self.size.width > 0 && self.size.height > 0 { 316 self.surface.configure(&self.device, &self.config); 317 } 318 } 319 320 pub fn render( 321 &mut self, 322 framebuffer: &[[u8; WINDOW_SIZE.0]; WINDOW_SIZE.1], 323 ) -> Result<(), SurfaceError> { 324 // SAFETY: 325 326 let framebuffer = framebuffer.as_flattened(); 327 assert!(framebuffer.len() == WINDOW_SIZE.0 * WINDOW_SIZE.1); 328 329 const BYTES_PER_ROW: Option<u32> = Some(WINDOW_SIZE.0 as u32); 330 const ROWS_PER_IMAGE: Option<u32> = Some(WINDOW_SIZE.1 as u32); 331 332 // push framebuffer to GPU-Texture 333 self.queue.write_texture( 334 TexelCopyTextureInfo { 335 texture: &self.streaming_texture, 336 mip_level: 0, 337 origin: Origin3d::ZERO, 338 aspect: TextureAspect::All, 339 }, 340 framebuffer, 341 TexelCopyBufferLayout { 342 offset: 0, 343 bytes_per_row: BYTES_PER_ROW, 344 rows_per_image: ROWS_PER_IMAGE, 345 }, 346 Self::TEXTURE_SIZE, 347 ); 348 349 let output = self.surface.get_current_texture()?; 350 351 let view = output 352 .texture 353 .create_view(&TextureViewDescriptor::default()); 354 355 let mut encoder = self 356 .device 357 .create_command_encoder(&CommandEncoderDescriptor { 358 label: Some("Render Encoder"), 359 }); 360 361 let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor { 362 label: Some("Render Pass"), 363 color_attachments: &[Some(RenderPassColorAttachment { 364 view: &view, 365 resolve_target: None, 366 ops: Operations { 367 load: wgpu::LoadOp::Clear(wgpu::Color { 368 r: 0.1, 369 g: 0.2, 370 b: 0.3, 371 a: 1.0, 372 }), 373 store: wgpu::StoreOp::Store, 374 }, 375 depth_slice: None, 376 })], 377 depth_stencil_attachment: None, 378 timestamp_writes: None, 379 occlusion_query_set: None, 380 }); 381 382 render_pass.set_pipeline(&self.render_pipeline); 383 render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); 384 385 render_pass.draw(0..3, 0..1); 386 // before finishing the encoding the render_pass must be dropped 387 drop(render_pass); 388 389 self.queue.submit(std::iter::once(encoder.finish())); 390 output.present(); 391 392 Ok(()) 393 } 394}