#[cfg(feature = "gpu_scaling")] pub mod gpu; pub mod palettes; use std::sync::Arc; use winit::{dpi::PhysicalSize, event_loop::ActiveEventLoop, window::Window}; use crate::coordinates::WINDOW_SIZE as WINDOW_SIZE16; const WINDOW_SIZE: (usize, usize) = (WINDOW_SIZE16.0 as usize, WINDOW_SIZE16.1 as usize); use palettes::{Palette, RGB8}; #[cfg(not(any(feature = "gpu_scaling", feature = "soft_scaling")))] compile_error!("at least one of gpu_scaling or soft_scaling needs to be active"); #[expect( clippy::large_enum_variant, reason = "this doesn't get moved a lot, so it doesn't matter. If it's not in the enum i also don't box it" )] #[cfg(all(feature = "gpu_scaling", feature = "soft_scaling"))] pub enum BothRenderBackend { GPU(GPURenderBackend), Soft(SoftRenderBackend), } #[cfg(all(feature = "gpu_scaling", feature = "soft_scaling"))] impl BothRenderBackend { pub fn new(window: Arc, palette: Palette) -> Self { match GPURenderBackend::try_new(window.clone(), palette) { Ok(b) => Self::GPU(b), Err(e) => { eprintln!( "GPU render backend creation failed: {e}.\n\nFalling back to software scaling" ); Self::Soft(SoftRenderBackend::new(window, palette)) } } } pub fn resize(&mut self, size: PhysicalSize) { match self { BothRenderBackend::GPU(b) => b.resize(size), BothRenderBackend::Soft(b) => b.resize(size), } } pub fn render( &mut self, frame_buffer: &[[u8; WINDOW_SIZE.0]; WINDOW_SIZE.1], event_loop: &ActiveEventLoop, ) { match self { BothRenderBackend::GPU(b) => b.render(frame_buffer, event_loop), BothRenderBackend::Soft(b) => b.render(frame_buffer, event_loop), } } pub fn get_size(&self) -> PhysicalSize { match self { BothRenderBackend::GPU(b) => b.get_size(), BothRenderBackend::Soft(b) => b.get_size(), } } } #[cfg(feature = "gpu_scaling")] pub struct GPURenderBackend { backend: gpu::GPUState, } #[cfg(feature = "gpu_scaling")] impl GPURenderBackend { fn try_new(window: Arc, palette: Palette) -> Result { let mut backend = smol::block_on(gpu::GPUState::new(window))?; backend.queue_palette_update(palette.into()); Ok(Self { backend }) } pub fn new(window: Arc, palette: Palette) -> Self { Self::try_new(window, palette).unwrap() } pub fn resize(&mut self, size: PhysicalSize) { self.backend.resize(size); } pub fn render( &mut self, frame_buffer: &[[u8; WINDOW_SIZE.0]; WINDOW_SIZE.1], event_loop: &ActiveEventLoop, ) { match self.backend.render(frame_buffer) { Ok(_) => {} Err(wgpu::SurfaceError::Lost) => self.backend.reinit_surface(), Err(wgpu::SurfaceError::OutOfMemory) => event_loop.exit(), Err(e) => eprint!("{:?}", e), } } pub fn get_size(&self) -> PhysicalSize { self.backend.size() } } // softscaling is small enough to not have its own file / module #[cfg(feature = "soft_scaling")] pub struct SoftRenderBackend { backend: softbuffer::Surface, Arc>, width: u32, height: u32, palette: Palette, } #[cfg(feature = "soft_scaling")] impl SoftRenderBackend { pub fn new(window: Arc, palette: Palette) -> Self { let size = window.inner_size(); let context = softbuffer::Context::new(window.clone()).unwrap(); Self { backend: softbuffer::Surface::new(&context, window).unwrap(), width: size.width, height: size.height, palette: palette.into(), } } pub fn resize(&mut self, size: PhysicalSize) { self.width = size.width; self.height = size.height; self.backend .resize( std::num::NonZeroU32::new(size.width).unwrap(), std::num::NonZeroU32::new(size.height).unwrap(), ) .unwrap() } pub fn render( &mut self, frame_buffer: &[[u8; WINDOW_SIZE.0]; WINDOW_SIZE.1], _: &ActiveEventLoop, ) { let mut buffer = self.backend.buffer_mut().unwrap(); assert!(buffer.len() == usize::try_from(self.width * self.height).unwrap()); let x_step = WINDOW_SIZE.0 as f32 / self.width as f32; let y_step = WINDOW_SIZE.1 as f32 / self.height as f32; for (y_idx, row) in buffer .chunks_exact_mut(self.width.try_into().unwrap()) .enumerate() { for (x_idx, pixel) in row.iter_mut().enumerate() { let x_idx = x_idx as f32 * x_step; let y_idx = y_idx as f32 * y_step; *pixel = self .palette .get_raw(frame_buffer[y_idx.floor() as usize][x_idx.floor() as usize]); } } buffer.present().unwrap(); } pub fn get_size(&self) -> PhysicalSize { PhysicalSize { width: self.width, height: self.height, } } }