old school music tracker
1#[cfg(feature = "gpu_scaling")]
2pub mod gpu;
3pub mod palettes;
4
5use std::sync::Arc;
6
7use winit::{dpi::PhysicalSize, event_loop::ActiveEventLoop, window::Window};
8
9use crate::coordinates::WINDOW_SIZE as WINDOW_SIZE16;
10const WINDOW_SIZE: (usize, usize) = (WINDOW_SIZE16.0 as usize, WINDOW_SIZE16.1 as usize);
11use palettes::{Palette, RGB8};
12
13#[cfg(not(any(feature = "gpu_scaling", feature = "soft_scaling")))]
14compile_error!("at least one of gpu_scaling or soft_scaling needs to be active");
15
16#[expect(
17 clippy::large_enum_variant,
18 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"
19)]
20#[cfg(all(feature = "gpu_scaling", feature = "soft_scaling"))]
21pub enum BothRenderBackend {
22 GPU(GPURenderBackend),
23 Soft(SoftRenderBackend),
24}
25
26#[cfg(all(feature = "gpu_scaling", feature = "soft_scaling"))]
27impl BothRenderBackend {
28 pub fn new(window: Arc<Window>, palette: Palette<RGB8>) -> Self {
29 match GPURenderBackend::try_new(window.clone(), palette) {
30 Ok(b) => Self::GPU(b),
31 Err(e) => {
32 eprintln!(
33 "GPU render backend creation failed: {e}.\n\nFalling back to software scaling"
34 );
35 Self::Soft(SoftRenderBackend::new(window, palette))
36 }
37 }
38 }
39
40 pub fn resize(&mut self, size: PhysicalSize<u32>) {
41 match self {
42 BothRenderBackend::GPU(b) => b.resize(size),
43 BothRenderBackend::Soft(b) => b.resize(size),
44 }
45 }
46
47 pub fn render(
48 &mut self,
49 frame_buffer: &[[u8; WINDOW_SIZE.0]; WINDOW_SIZE.1],
50 event_loop: &ActiveEventLoop,
51 ) {
52 match self {
53 BothRenderBackend::GPU(b) => b.render(frame_buffer, event_loop),
54 BothRenderBackend::Soft(b) => b.render(frame_buffer, event_loop),
55 }
56 }
57
58 pub fn get_size(&self) -> PhysicalSize<u32> {
59 match self {
60 BothRenderBackend::GPU(b) => b.get_size(),
61 BothRenderBackend::Soft(b) => b.get_size(),
62 }
63 }
64}
65
66#[cfg(feature = "gpu_scaling")]
67pub struct GPURenderBackend {
68 backend: gpu::GPUState,
69}
70
71#[cfg(feature = "gpu_scaling")]
72impl GPURenderBackend {
73 fn try_new(window: Arc<Window>, palette: Palette<RGB8>) -> Result<Self, String> {
74 let mut backend = smol::block_on(gpu::GPUState::new(window))?;
75 backend.queue_palette_update(palette.into());
76
77 Ok(Self { backend })
78 }
79
80 pub fn new(window: Arc<Window>, palette: Palette<RGB8>) -> Self {
81 Self::try_new(window, palette).unwrap()
82 }
83
84 pub fn resize(&mut self, size: PhysicalSize<u32>) {
85 self.backend.resize(size);
86 }
87
88 pub fn render(
89 &mut self,
90 frame_buffer: &[[u8; WINDOW_SIZE.0]; WINDOW_SIZE.1],
91 event_loop: &ActiveEventLoop,
92 ) {
93 match self.backend.render(frame_buffer) {
94 Ok(_) => {}
95 Err(wgpu::SurfaceError::Lost) => self.backend.reinit_surface(),
96 Err(wgpu::SurfaceError::OutOfMemory) => event_loop.exit(),
97 Err(e) => eprint!("{:?}", e),
98 }
99 }
100
101 pub fn get_size(&self) -> PhysicalSize<u32> {
102 self.backend.size()
103 }
104}
105
106// softscaling is small enough to not have its own file / module
107#[cfg(feature = "soft_scaling")]
108pub struct SoftRenderBackend {
109 backend: softbuffer::Surface<Arc<Window>, Arc<Window>>,
110 width: u32,
111 height: u32,
112 palette: Palette<palettes::ZRGB>,
113}
114
115#[cfg(feature = "soft_scaling")]
116impl SoftRenderBackend {
117 pub fn new(window: Arc<Window>, palette: Palette<RGB8>) -> Self {
118 let size = window.inner_size();
119 let context = softbuffer::Context::new(window.clone()).unwrap();
120 Self {
121 backend: softbuffer::Surface::new(&context, window).unwrap(),
122 width: size.width,
123 height: size.height,
124 palette: palette.into(),
125 }
126 }
127
128 pub fn resize(&mut self, size: PhysicalSize<u32>) {
129 self.width = size.width;
130 self.height = size.height;
131 self.backend
132 .resize(
133 std::num::NonZeroU32::new(size.width).unwrap(),
134 std::num::NonZeroU32::new(size.height).unwrap(),
135 )
136 .unwrap()
137 }
138
139 pub fn render(
140 &mut self,
141 frame_buffer: &[[u8; WINDOW_SIZE.0]; WINDOW_SIZE.1],
142 _: &ActiveEventLoop,
143 ) {
144 let mut buffer = self.backend.buffer_mut().unwrap();
145 assert!(buffer.len() == usize::try_from(self.width * self.height).unwrap());
146 let x_step = WINDOW_SIZE.0 as f32 / self.width as f32;
147 let y_step = WINDOW_SIZE.1 as f32 / self.height as f32;
148 for (y_idx, row) in buffer
149 .chunks_exact_mut(self.width.try_into().unwrap())
150 .enumerate()
151 {
152 for (x_idx, pixel) in row.iter_mut().enumerate() {
153 let x_idx = x_idx as f32 * x_step;
154 let y_idx = y_idx as f32 * y_step;
155 *pixel = self
156 .palette
157 .get_raw(frame_buffer[y_idx.floor() as usize][x_idx.floor() as usize]);
158 }
159 }
160 buffer.present().unwrap();
161 }
162
163 pub fn get_size(&self) -> PhysicalSize<u32> {
164 PhysicalSize {
165 width: self.width,
166 height: self.height,
167 }
168 }
169}