debug.rsannotateddebug.rssource360 lines · 12.5 KB · raw
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;
14
15// 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}
35
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}