old school music tracker
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}