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