1use crate::debug::{CopySpriteAttributesResult, CramEntry, SpriteAttributeEntry}; 2use crate::vdp; 3use crate::vdp::colors::ColorTables; 4use crate::vdp::registers::{DebugRegister, Registers}; 5use crate::vdp::render::PatternGeneratorRowArgs; 6use crate::vdp::{ 7 CachedSpriteData, ColorModifier, ControlWriteFlag, Cram, DataPortLocation, DataPortMode, 8 SpriteData, Vdp, Vram, Vsram, colors, render, sprites, 9}; 10use jgenesis_common::debug::{DebugBytesView, DebugMemoryView, DebugWordsView, Endian}; 11use jgenesis_common::frontend::Color; 12use jgenesis_common::num::U16Ext; 13use std::ops::Range;
Internal state that is mostly not directly readable by software, but can be useful for debugging
16#[derive(Debug, Clone)] 17pub struct VdpInternalDebugState { 18 pub scanline: u16, 19 pub scanline_mclk: u64, 20 pub pixel: u16, 21 pub active_display_pixels: Range<u16>, 22 pub internal_h: u16, 23 pub external_h: u8, 24 pub v: u8, 25 pub vblank_flag: bool, 26 pub v_interrupt_pending: bool, 27 pub h_interrupt_pending: bool, 28 pub h_interrupt_counter: u16, 29 pub data_port_mode: DataPortMode, 30 pub data_port_location: DataPortLocation, 31 pub data_port_address: u32, 32 pub write_flag: ControlWriteFlag, 33 pub dma_active: bool, 34}
36#[derive(Debug, Clone)] 37pub struct VdpDebugState { 38 vram: Box<Vram>, 39 cram: Box<Cram>, 40 vsram: Box<Vsram>, 41 color_tables: ColorTables, 42 registers: Registers, 43 latched_registers: Registers, 44 debug_register: DebugRegister, 45 cached_sprite_attributes: Box<[CachedSpriteData]>, 46 internal_state: VdpInternalDebugState, 47} 48 49impl Vdp { 50 #[must_use] 51 pub fn to_debug_state(&self) -> VdpDebugState { 52 let hv = self.hv_counter_internal(self.state.scanline_mclk_cycles); 53 54 VdpDebugState { 55 vram: self.vram.clone().into(), 56 cram: self.cram.clone().into(), 57 vsram: self.vsram.clone().into(), 58 color_tables: self.color_tables.clone(), 59 registers: self.registers.clone(), 60 latched_registers: self.latched_registers.clone(), 61 debug_register: self.debug_register, 62 cached_sprite_attributes: self.cached_sprite_attributes.clone(), 63 internal_state: VdpInternalDebugState { 64 scanline: self.state.scanline, 65 scanline_mclk: self.state.scanline_mclk_cycles, 66 pixel: self.state.pixel, 67 active_display_pixels: self 68 .registers 69 .horizontal_display_size 70 .active_display_h_range(), 71 internal_h: hv.internal_h, 72 external_h: hv.hv_counter.lsb(), 73 v: hv.internal_v, 74 vblank_flag: hv.vblank_flag, 75 v_interrupt_pending: self.state.v_interrupt_pending, 76 h_interrupt_pending: self.state.h_interrupt_pending, 77 h_interrupt_counter: self.state.h_interrupt_counter, 78 data_port_mode: self.control_port.mode, 79 data_port_location: self.control_port.location, 80 data_port_address: self.control_port.data_port_address, 81 write_flag: self.control_port.write_flag, 82 dma_active: self.control_port.dma_active, 83 }, 84 } 85 } 86 87 pub fn debug_vram_view(&mut self) -> impl DebugMemoryView { 88 DebugBytesView(self.vram.as_mut_slice()) 89 } 90 91 pub fn debug_cram_view(&mut self) -> impl DebugMemoryView { 92 DebugWordsView(self.cram.as_mut_slice(), Endian::Big) 93 } 94 95 pub fn debug_vsram_view(&mut self) -> impl DebugMemoryView { 96 DebugWordsView(self.vsram.as_mut_slice(), Endian::Big) 97 } 98} 99 100impl VdpDebugState { 101 pub fn copy_cram(&self, out: &mut [CramEntry], modifier: ColorModifier) { 102 for (out_entry, &cram_color) in out.iter_mut().zip(self.cram.as_ref()) { 103 *out_entry = CramEntry { 104 value: cram_color, 105 color: parse_gen_color(cram_color, modifier, &self.color_tables), 106 }; 107 } 108 } 109 110 pub fn copy_vram(&self, out: &mut [Color], palette: u8, row_len: usize) { 111 for pattern in 0..vdp::VRAM_LEN / 32 { 112 let base_idx = pattern / row_len * row_len * 64 + (pattern % row_len) * 8; 113 114 for row in 0..8 { 115 let colors = render::read_pattern_generator_row( 116 &self.vram, 117 PatternGeneratorRowArgs { 118 vertical_flip: false, 119 horizontal_flip: false, 120 pattern_generator: pattern as u16, 121 row: row as u16, 122 cell_height_shift: 3, 123 }, 124 ); 125 126 for (col, color_id) in colors.into_iter().enumerate() { 127 let out_idx = base_idx + row * row_len * 8 + col; 128 let color = colors::resolve(&self.cram, palette, color_id); 129 out[out_idx] = parse_gen_color(color, ColorModifier::None, &self.color_tables); 130 out[out_idx].a = 255; 131 } 132 } 133 } 134 } 135 136 pub fn copy_h_scroll(&self, out: &mut [(u16, u16)]) { 137 let h_scroll_table_addr = self.latched_registers.h_scroll_table_base_addr; 138 139 for i in 0..256 { 140 let h_scroll_addr = h_scroll_table_addr.wrapping_add(4 * i) as usize; 141 let h_scroll_a = 142 u16::from_be_bytes([self.vram[h_scroll_addr], self.vram[h_scroll_addr + 1]]) 143 & 0x3FF; 144 let h_scroll_b = 145 u16::from_be_bytes([self.vram[h_scroll_addr + 2], self.vram[h_scroll_addr + 3]]) 146 & 0x3FF; 147 out[i as usize] = (h_scroll_a, h_scroll_b); 148 } 149 } 150 151 pub fn dump_registers(&self, mut callback: impl FnMut(&str, &[(&str, &str)])) { 152 callback( 153 "Register #0", 154 &[ 155 ("Horizontal interrupt enabled", bool_str(self.registers.h_interrupt_enabled)), 156 ("HV counter latched", bool_str(self.registers.hv_counter_stopped)), 157 ], 158 ); 159 160 callback( 161 "Register #1", 162 &[ 163 ("Display enabled", bool_str(self.registers.display_enabled)), 164 ("Vertical interrupt enabled", bool_str(self.registers.v_interrupt_enabled)), 165 ("DMA enabled", bool_str(self.registers.dma_enabled)), 166 ("Vertical resolution", &self.registers.vertical_display_size.to_string()), 167 ("Mode", if self.registers.mode_4 { "4" } else { "5" }), 168 ("VRAM size", &self.registers.vram_size.to_string()), 169 ], 170 ); 171 172 callback( 173 "Register #2", 174 &[( 175 "Plane A nametable address", 176 &format!("${:04X}", self.registers.scroll_a_base_nt_addr), 177 )], 178 ); 179 180 callback( 181 "Register #3", 182 &[( 183 "Window nametable address", 184 &format!("${:04X}", self.registers.window_base_nt_addr), 185 )], 186 ); 187 188 callback( 189 "Register #4", 190 &[( 191 "Plane B nametable address", 192 &format!("${:04X}", self.registers.scroll_b_base_nt_addr), 193 )], 194 ); 195 196 callback( 197 "Register #5", 198 &[( 199 "Sprite attribute table address", 200 &format!("${:04X}", self.registers.sprite_attribute_table_base_addr), 201 )], 202 ); 203 204 callback( 205 "Register #7", 206 &[ 207 ("Backdrop palette", &self.registers.background_palette.to_string()), 208 ("Backdrop color index", &self.registers.background_color_id.to_string()), 209 ], 210 ); 211 212 callback( 213 "Register #10", 214 &[("Horizontal interrupt interval", &self.registers.h_interrupt_interval.to_string())], 215 ); 216 217 callback( 218 "Register #11", 219 &[ 220 ("Vertical scroll mode", &self.registers.vertical_scroll_mode.to_string()), 221 ("Horizontal scroll mode", &self.registers.horizontal_scroll_mode.to_string()), 222 ], 223 ); 224 225 callback( 226 "Register #12", 227 &[ 228 ("Horizontal resolution", &self.registers.horizontal_display_size.to_string()), 229 ("Shadow/highlight enabled", bool_str(self.registers.shadow_highlight_flag)), 230 ("Screen mode", &self.registers.interlacing_mode.to_string()), 231 ], 232 ); 233 234 callback( 235 "Register #13", 236 &[( 237 "H scroll table address", 238 &format!("${:04X}", self.registers.h_scroll_table_base_addr), 239 )], 240 ); 241 242 callback( 243 "Register #15", 244 &[( 245 "Data port auto-increment", 246 &format!("0x{:X}", self.registers.data_port_auto_increment), 247 )], 248 ); 249 250 callback( 251 "Register #16", 252 &[ 253 ("Vertical plane size", &self.registers.vertical_scroll_size.to_string()), 254 ("Horizontal plane size", &self.registers.horizontal_scroll_size.to_string()), 255 ], 256 ); 257 258 callback( 259 "Register #17", 260 &[ 261 ("Window horizontal mode", &self.registers.window_horizontal_mode.to_string()), 262 ("Window X", &self.registers.window_x_position.to_string()), 263 ], 264 ); 265 266 callback( 267 "Register #18", 268 &[ 269 ("Window vertical mode", &self.registers.window_vertical_mode.to_string()), 270 ("Window Y", &self.registers.window_y_position.to_string()), 271 ], 272 ); 273 274 callback("Registers #19-20", &[("DMA length", &self.registers.dma_length.to_string())]); 275 276 callback( 277 "Registers #21-23", 278 &[ 279 ("DMA source address", &format!("${:06X}", self.registers.dma_source_address)), 280 ("DMA mode", &self.registers.dma_mode.to_string()), 281 ], 282 ); 283 284 callback( 285 "Debug Register", 286 &[ 287 ("Display disabled", bool_str(self.debug_register.display_disabled)), 288 ("Forced layer", &self.debug_register.forced_plane.to_string()), 289 ], 290 ); 291 } 292 293 pub fn copy_sprite_attributes( 294 &self, 295 out: &mut [SpriteAttributeEntry], 296 ) -> CopySpriteAttributesResult { 297 let h_display_size = self.registers.horizontal_display_size; 298 let base_sprite_table_addr = self.registers.sprite_attribute_table_base_addr 299 & h_display_size.sprite_attribute_table_mask(); 300 let len = h_display_size.sprite_table_len(); 301 302 let interlacing_mode = self.registers.interlacing_mode; 303 let y_mask = sprites::y_position_mask(interlacing_mode); 304 305 for i in 0..len { 306 let sprite_table_addr = base_sprite_table_addr.wrapping_add(8 * i) as usize; 307 let data = SpriteData::create( 308 self.cached_sprite_attributes[i as usize], 309 &self.vram[sprite_table_addr + 4..sprite_table_addr + 8], 310 ); 311 312 out[i as usize] = SpriteAttributeEntry { 313 tile_number: data.pattern_generator, 314 x: data.h_position, 315 y: data.v_position & y_mask, 316 h_cells: data.h_size_cells, 317 v_cells: data.v_size_cells, 318 palette: data.palette, 319 priority: data.priority, 320 h_flip: data.horizontal_flip, 321 v_flip: data.vertical_flip, 322 link: data.link_data, 323 }; 324 } 325 326 CopySpriteAttributesResult { 327 sprite_table_len: len, 328 top_left_x: sprites::SPRITE_H_DISPLAY_START, 329 top_left_y: interlacing_mode.sprite_display_top(), 330 } 331 } 332 333 pub fn debug_vram_view(&mut self) -> impl DebugMemoryView { 334 DebugBytesView(self.vram.as_mut_slice()) 335 } 336 337 pub fn debug_cram_view(&mut self) -> impl DebugMemoryView { 338 DebugWordsView(self.cram.as_mut_slice(), Endian::Big) 339 } 340 341 pub fn debug_vsram_view(&mut self) -> impl DebugMemoryView { 342 DebugWordsView(self.vsram.as_mut_slice(), Endian::Big) 343 } 344 345 #[must_use] 346 pub fn internal_state(&self) -> &VdpInternalDebugState { 347 &self.internal_state 348 } 349} 350 351fn bool_str(b: bool) -> &'static str { 352 if b { "true" } else { "false" } 353} 354 355fn parse_gen_color(gen_color: u16, modifier: ColorModifier, tables: &ColorTables) -> Color { 356 let r = ((gen_color >> 1) & 0x07) as u8; 357 let g = ((gen_color >> 5) & 0x07) as u8; 358 let b = ((gen_color >> 9) & 0x07) as u8; 359 colors::gen_to_rgba(r, g, b, 255, modifier, tables) 360}