1use crate::{DebugRenderContext, DebugRenderFn, SelectableButton}; 2use egui::{CentralPanel, Grid, ScrollArea, Vec2}; 3use gb_core::api::{BackgroundTileMap, GameBoyEmulator}; 4use jgenesis_common::frontend::Color; 5 6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 7enum Tab { 8 #[default] 9 Background, 10 Sprites, 11 Palettes, 12} 13 14#[derive(Debug)] 15struct State { 16 tab: Tab, 17 background_tile_map: BackgroundTileMap, 18 background_buffer: Vec<Color>, 19 background_texture: Option<(wgpu::Texture, egui::TextureId)>, 20 sprites_buffer: Vec<Color>, 21 sprites_texture: Option<(wgpu::Texture, egui::TextureId)>, 22 sprites_double_height_texture: Option<(wgpu::Texture, egui::TextureId)>, 23 bg_palettes_texture: Option<(wgpu::Texture, egui::TextureId)>, 24 obj_palettes_texture: Option<(wgpu::Texture, egui::TextureId)>, 25} 26 27impl State { 28 fn new() -> Self { 29 Self { 30 tab: Tab::default(), 31 background_tile_map: BackgroundTileMap::default(), 32 background_buffer: vec![Color::default(); 256 * 256], 33 background_texture: None, 34 sprites_buffer: vec![Color::default(); 40 * 8 * 16], 35 sprites_texture: None, 36 sprites_double_height_texture: None, 37 bg_palettes_texture: None, 38 obj_palettes_texture: None, 39 } 40 } 41} 42 43#[must_use] 44pub fn render_fn() -> Box<DebugRenderFn<GameBoyEmulator>> { 45 let mut state = State::new(); 46 Box::new(move |ctx, emulator| render(ctx, emulator, &mut state)) 47} 48 49fn render(mut ctx: DebugRenderContext<'_>, emulator: &mut GameBoyEmulator, state: &mut State) { 50 update_background_texture(&mut ctx, emulator, state); 51 update_sprite_texture(&mut ctx, emulator, state); 52 update_palettes_texture(&mut ctx, emulator, state); 53 54 let screen_width = crate::screen_width(ctx.egui_ui); 55 56 CentralPanel::default().show_inside(ctx.egui_ui, |ui| { 57 ui.horizontal(|ui| { 58 ui.add(SelectableButton::new("Background", &mut state.tab, Tab::Background)); 59 ui.add(SelectableButton::new("Sprites", &mut state.tab, Tab::Sprites)); 60 ui.add(SelectableButton::new("Palettes", &mut state.tab, Tab::Palettes)); 61 }); 62 63 ui.add_space(15.0); 64 65 match state.tab { 66 Tab::Background => { 67 ui.horizontal(|ui| { 68 ui.label("Tile map:"); 69 70 ui.radio_value( 71 &mut state.background_tile_map, 72 BackgroundTileMap::Zero, 73 "$9800-$9BFF", 74 ); 75 ui.radio_value( 76 &mut state.background_tile_map, 77 BackgroundTileMap::One, 78 "$9C00-$9FFF", 79 ); 80 }); 81 82 ui.add_space(10.0); 83 84 ScrollArea::vertical().show(ui, |ui| { 85 ui.vertical_centered(|ui| { 86 let egui_texture = state.background_texture.as_ref().unwrap().1; 87 ui.image(( 88 egui_texture, 89 Vec2::new(screen_width * 0.95, screen_width * 0.95), 90 )) 91 }); 92 }); 93 } 94 Tab::Sprites => { 95 if emulator.is_using_double_height_sprites() { 96 ScrollArea::vertical().show(ui, |ui| { 97 ui.vertical_centered(|ui| { 98 let egui_texture = 99 state.sprites_double_height_texture.as_ref().unwrap().1; 100 ui.image(( 101 egui_texture, 102 Vec2::new(screen_width * 0.6, screen_width * 0.6 * 80.0 / 64.0), 103 )); 104 }); 105 }); 106 } else { 107 ScrollArea::vertical().show(ui, |ui| { 108 ui.vertical_centered(|ui| { 109 let egui_texture = state.sprites_texture.as_ref().unwrap().1; 110 ui.image(( 111 egui_texture, 112 Vec2::new(screen_width * 0.95, screen_width * 0.95 * 40.0 / 64.0), 113 )); 114 }); 115 }); 116 } 117 } 118 Tab::Palettes => { 119 let bg_texture = state.bg_palettes_texture.as_ref().unwrap().1; 120 let obj_texture = state.obj_palettes_texture.as_ref().unwrap().1; 121 122 Grid::new("debug_gb_palettes_grid").show(ui, |ui| { 123 ui.vertical_centered(|ui| { 124 ui.heading("Background"); 125 }); 126 ui.vertical_centered(|ui| { 127 ui.heading("Sprites"); 128 }); 129 ui.end_row(); 130 131 ui.image((bg_texture, Vec2::new(screen_width * 0.3, screen_width * 0.3 * 2.0))); 132 ui.image(( 133 obj_texture, 134 Vec2::new(screen_width * 0.3, screen_width * 0.3 * 2.0), 135 )); 136 ui.end_row(); 137 }); 138 } 139 } 140 }); 141} 142 143fn update_background_texture( 144 ctx: &mut DebugRenderContext<'_>, 145 emulator: &mut GameBoyEmulator, 146 state: &mut State, 147) { 148 if state.tab == Tab::Background { 149 emulator.copy_background(state.background_tile_map, &mut state.background_buffer); 150 } 151 152 if state.background_texture.is_none() { 153 let (wgpu_texture, egui_texture) = 154 crate::create_texture("debug_gb_bg", 256, 256, ctx.device, ctx.renderer); 155 state.background_texture = Some((wgpu_texture, egui_texture)); 156 } 157 158 let (wgpu_texture, egui_texture) = state.background_texture.as_ref().unwrap(); 159 let egui_texture = *egui_texture; 160 161 crate::write_textures( 162 wgpu_texture, 163 egui_texture, 164 bytemuck::cast_slice(&state.background_buffer), 165 ctx, 166 ); 167} 168 169fn update_sprite_texture( 170 ctx: &mut DebugRenderContext<'_>, 171 emulator: &mut GameBoyEmulator, 172 state: &mut State, 173) { 174 if state.tab == Tab::Sprites { 175 emulator.copy_sprites(&mut state.sprites_buffer); 176 } 177 178 if state.sprites_texture.is_none() { 179 let (wgpu_texture, egui_texture) = 180 crate::create_texture("debug_gb_sprites", 8 * 8, 5 * 8, ctx.device, ctx.renderer); 181 state.sprites_texture = Some((wgpu_texture, egui_texture)); 182 } 183 184 if state.sprites_double_height_texture.is_none() { 185 let (wgpu_texture, egui_texture) = crate::create_texture( 186 "debug_gb_sprites_2x_height", 187 8 * 8, 188 2 * 5 * 8, 189 ctx.device, 190 ctx.renderer, 191 ); 192 state.sprites_double_height_texture = Some((wgpu_texture, egui_texture)); 193 } 194 195 if emulator.is_using_double_height_sprites() { 196 let (wgpu_texture, egui_texture) = state.sprites_double_height_texture.as_ref().unwrap(); 197 let egui_texture = *egui_texture; 198 199 crate::write_textures( 200 wgpu_texture, 201 egui_texture, 202 bytemuck::cast_slice(&state.sprites_buffer), 203 ctx, 204 ); 205 } else { 206 let (wgpu_texture, egui_texture) = state.sprites_texture.as_ref().unwrap(); 207 let egui_texture = *egui_texture; 208 209 crate::write_textures( 210 wgpu_texture, 211 egui_texture, 212 bytemuck::cast_slice(&state.sprites_buffer[..40 * 64]), 213 ctx, 214 ); 215 } 216} 217 218fn update_palettes_texture( 219 ctx: &mut DebugRenderContext<'_>, 220 emulator: &mut GameBoyEmulator, 221 state: &mut State, 222) { 223 let mut palettes_buffer = [Color::default(); 64]; 224 225 if state.tab == Tab::Palettes { 226 emulator.copy_palettes(&mut palettes_buffer); 227 } 228 229 if state.bg_palettes_texture.is_none() { 230 let (wgpu_texture, egui_texture) = 231 crate::create_texture("debug_gb_bg_palettes", 4, 8, ctx.device, ctx.renderer); 232 state.bg_palettes_texture = Some((wgpu_texture, egui_texture)); 233 } 234 235 if state.obj_palettes_texture.is_none() { 236 let (wgpu_texture, egui_texture) = 237 crate::create_texture("debug_gb_obj_palettes", 4, 8, ctx.device, ctx.renderer); 238 state.obj_palettes_texture = Some((wgpu_texture, egui_texture)); 239 } 240 241 let mut bg_palettes_buffer = [Color::TRANSPARENT; 32]; 242 let mut obj_palettes_buffer = [Color::TRANSPARENT; 32]; 243 if emulator.is_cgb_mode() { 244 bg_palettes_buffer.copy_from_slice(&palettes_buffer[..32]); 245 obj_palettes_buffer.copy_from_slice(&palettes_buffer[32..]); 246 } else { 247 bg_palettes_buffer[..4].copy_from_slice(&palettes_buffer[..4]); 248 obj_palettes_buffer[..8].copy_from_slice(&palettes_buffer[4..12]); 249 } 250 251 let (bg_wgpu_texture, bg_egui_texture) = state.bg_palettes_texture.as_ref().unwrap(); 252 crate::write_textures( 253 bg_wgpu_texture, 254 *bg_egui_texture, 255 bytemuck::cast_slice(&bg_palettes_buffer), 256 ctx, 257 ); 258 259 let (obj_wgpu_texture, obj_egui_texture) = state.obj_palettes_texture.as_ref().unwrap(); 260 crate::write_textures( 261 obj_wgpu_texture, 262 *obj_egui_texture, 263 bytemuck::cast_slice(&obj_palettes_buffer), 264 ctx, 265 ); 266}