1use crate::video::WordByte;
2use crate::video::vdc::{CgMode, DmaStep, PendingCpuAccess, Vdc};
3use bincode::{Decode, Encode};
4use jgenesis_common::define_bit_enum;
5use jgenesis_common::num::{GetBit, U16Ext};
6use std::cmp;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
9pub enum VramAccessWidth {
10    #[default]
11    One, // 1 CPU access every 2 dots during BG fetching
12    Two,  // 1 CPU access every 8 dots during BG fetching
13    Four, // No CPU access during BG fetching, and BG fetches only fetch half of the bitplanes
14}
15
16impl VramAccessWidth {
17    fn from_bits(bits: u8) -> Self {
18        match bits & 3 {
19            0 => Self::One,
20            1 | 2 => Self::Two,
21            3 => Self::Four,
22            _ => unreachable!("value & 3 is always <= 3"),
23        }
24    }
25
26    fn display(self) -> &'static str {
27        match self {
28            Self::One => "1",
29            Self::Two => "2",
30            Self::Four => "4",
31        }
32    }
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
36#[rustfmt::skip]
37pub enum SpriteAccessWidth {
38    #[default]
39    One,       // Fetch 1 sprite per 4 dots
40    TwoHalfBpp, // Fetch 1 sprite per 4 dots but only half of the bitplanes
41    TwoFullBpp, // Fetch 1 sprite per 8 dots
42    Four,      // Fetch 1 sprite per 8 dots but only half of the bitplanes
43}
44
45impl SpriteAccessWidth {
46    fn from_bits(bits: u8) -> Self {
47        match bits & 3 {
48            0 => Self::One,
49            1 => Self::TwoHalfBpp,
50            2 => Self::TwoFullBpp,
51            3 => Self::Four,
52            _ => unreachable!("value & 3 is always <= 3"),
53        }
54    }
55
56    fn display(self) -> &'static str {
57        match self {
58            Self::One => "1",
59            Self::TwoHalfBpp => "2 (2bpp sprites)",
60            Self::TwoFullBpp => "2 (4bpp sprites)",
61            Self::Four => "4",
62        }
63    }
64
65    pub fn is_half_bpp(self) -> bool {
66        matches!(self, Self::TwoHalfBpp | Self::Four)
67    }
68}
69
70define_bit_enum!(VirtualScreenHeight, [Single, Double]);
71
72impl VirtualScreenHeight {
73    fn display(self) -> &'static str {
74        match self {
75            Self::Single => "32 tiles",
76            Self::Double => "64 tiles",
77        }
78    }
79
80    pub fn to_tiles(self) -> u16 {
81        match self {
82            Self::Single => 32,
83            Self::Double => 64,
84        }
85    }
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
89pub enum VirtualScreenWidth {
90    #[default]
91    Single,
92    Double,
93    Quad,
94}
95
96impl VirtualScreenWidth {
97    fn from_bits(bits: u8) -> Self {
98        match bits & 3 {
99            0 => Self::Single,
100            1 => Self::Double,
101            2 | 3 => Self::Quad,
102            _ => unreachable!("value & 3 is always <= 3"),
103        }
104    }
105
106    fn display(self) -> &'static str {
107        match self {
108            Self::Single => "32 tiles",
109            Self::Double => "64 tiles",
110            Self::Quad => "128 tiles",
111        }
112    }
113
114    pub fn to_tiles(self) -> u16 {
115        match self {
116            Self::Single => 32,
117            Self::Double => 64,
118            Self::Quad => 128,
119        }
120    }
121}
122
123#[derive(Debug, Clone, Encode, Decode)]
124pub struct VdcRegisters {
125    // $00: MAWR (Memory address write register)
126    pub vram_write_address: u16,
127    // $01: MARR (Memory address read register)
128    pub vram_read_address: u16,
129    // $02: VRR (VRAM read register)
130    pub vram_read_buffer: u16,
131    // $03: VWR (VRAM write register)
132    pub vram_write_latch: u8,
133    // $05: CR (Control register)
134    pub vblank_irq_enabled: bool,
135    pub raster_compare_irq_enabled: bool,
136    pub sprite_overflow_irq_enabled: bool,
137    pub sprite_collision_irq_enabled: bool,
138    pub bg_enabled: bool,
139    pub sprites_enabled: bool,
140    pub vram_address_increment: u16,
141    // $06: RCR (Raster compare register)
142    pub raster_compare: u16,
143    // $07: BXR (BG X scroll register)
144    pub bg_x_scroll: u16,
145    // $08: BYR (BG Y scroll register)
146    pub bg_y_scroll: u16,
147    // $09: MWR (Memory width register)
148    pub vram_access_width: VramAccessWidth,
149    pub sprite_access_width: SpriteAccessWidth,
150    pub virtual_screen_width: VirtualScreenWidth,
151    pub virtual_screen_height: VirtualScreenHeight,
152    pub bg_cg_mode: CgMode,
153    // $0A: HSR (Horizontal sync register)
154    pub h_sync_width: u16,    // HSW (+1 * 8)
155    pub h_display_start: u16, // HDS (+1 * 8)
156    // $0B: HDR (Horizontal display register)
157    pub h_display_width: u16, // HDW (+1 * 8)
158    pub h_display_end: u16,   // HDE (+1 * 8)
159    // $0C: VSR (Vertical sync register)
160    pub v_sync_width: u16,    // VSW (+1)
161    pub v_display_start: u16, // VDS (+2)
162    // $0D: VDR (Vertical display register)
163    pub v_display_width_raw: u16,
164    pub v_display_width: u16, // VDW (+1)
165    // $0E: VCR (Vertical display end position register)
166    pub v_display_end: u16, // VCR
167    // $0F: DCR (DMA control register)
168    pub vram_dma_irq_enabled: bool,
169    pub sat_dma_irq_enabled: bool,
170    pub vram_dma_source_step: DmaStep,
171    pub vram_dma_destination_step: DmaStep,
172    pub sat_dma_repeat: bool,
173    // $10: SOUR (DMA source address register)
174    pub vram_dma_source_address: u16,
175    // $11: DESR (DMA destination address register)
176    pub vram_dma_destination_address: u16,
177    // $12: LENR (DMA length register)
178    pub vram_dma_length: u16,
179    // $13: DVSSR (VRAM-SAT DMA source address register)
180    pub sat_dma_source_address: u16,
181}
182
183impl VdcRegisters {
184    pub fn new() -> Self {
185        Self {
186            vram_write_address: !0,
187            vram_read_address: !0,
188            vram_read_buffer: !0,
189            vram_write_latch: !0,
190            vblank_irq_enabled: false,
191            raster_compare_irq_enabled: false,
192            sprite_overflow_irq_enabled: false,
193            sprite_collision_irq_enabled: false,
194            bg_enabled: false,
195            sprites_enabled: false,
196            vram_address_increment: 1,
197            raster_compare: !0,
198            bg_x_scroll: 0,
199            bg_y_scroll: 0,
200            vram_access_width: VramAccessWidth::default(),
201            sprite_access_width: SpriteAccessWidth::default(),
202            virtual_screen_width: VirtualScreenWidth::default(),
203            virtual_screen_height: VirtualScreenHeight::default(),
204            bg_cg_mode: CgMode::default(),
205            // Arbitrarily default H/V display registers to the settings used by Bonk's Adventure
206            h_sync_width: 24,
207            h_display_start: 24,
208            h_display_width: 256,
209            h_display_end: 32,
210            v_sync_width: 3,
211            v_display_start: 17,
212            v_display_width_raw: 239,
213            v_display_width: 240,
214            v_display_end: 3,
215            vram_dma_irq_enabled: false,
216            sat_dma_irq_enabled: false,
217            vram_dma_source_step: DmaStep::default(),
218            vram_dma_destination_step: DmaStep::default(),
219            sat_dma_repeat: false,
220            vram_dma_source_address: !0,
221            vram_dma_destination_address: !0,
222            vram_dma_length: !0,
223            sat_dma_source_address: !0,
224        }
225    }
226}
227
228impl Vdc {
229    pub fn read_status(&mut self) -> u8 {
230        // TODO should DMA accesses set the busy flag?
231        let busy = self.state.pending_cpu_access.is_some();
232
233        let status = (u8::from(busy) << 6)
234            | (u8::from(self.state.vblank_irq_pending) << 5)
235            | (u8::from(self.state.vram_dma_irq_pending) << 4)
236            | (u8::from(self.state.sat_dma_irq_pending) << 3)
237            | (u8::from(self.state.raster_compare_irq_pending) << 2)
238            | (u8::from(self.state.sprite_overflow_irq_pending) << 1)
239            | u8::from(self.state.sprite_collision_irq_pending);
240
241        // All VDC IRQ flags are cleared on status register read
242        self.state.vblank_irq_pending = false;
243        self.state.raster_compare_irq_pending = false;
244        self.state.sprite_collision_irq_pending = false;
245        self.state.sprite_overflow_irq_pending = false;
246        self.state.vram_dma_irq_pending = false;
247        self.state.sat_dma_irq_pending = false;
248
249        self.state.any_irq_pending = false;
250
251        status
252    }
253
254    pub fn write_register_select(&mut self, value: u8) {
255        self.selected_register = value & 0x1F;
256
257        log::trace!("Selected register ${:02X}", self.selected_register);
258    }
259
260    pub fn read_data(&mut self, byte: WordByte) -> u8 {
261        let value = byte.get(self.registers.vram_read_buffer);
262
263        // MARR only increments when selected register is VRR/VWR
264        if self.selected_register == 0x02 && byte == WordByte::High {
265            debug_assert!(self.state.pending_cpu_access.is_none());
266
267            self.state.pending_cpu_access =
268                Some(PendingCpuAccess::Read { address: self.registers.vram_read_address });
269            self.increment_vram_read_address();
270        }
271
272        value
273    }
274
275    pub fn write_data(&mut self, value: u8, byte: WordByte) {
276        match self.selected_register {
277            0x00 => {
278                // MAWR (Memory address write register)
279                byte.set(&mut self.registers.vram_write_address, value);
280
281                log::trace!(
282                    "MAWR {byte:?} write: {value:02X} (address {:04X})",
283                    self.registers.vram_write_address
284                );
285            }
286            0x01 => {
287                // MARR (Memory address read register);
288                byte.set(&mut self.registers.vram_read_address, value);
289
290                log::trace!(
291                    "MAAR {byte:?} write: {value:02X} (address {:04X})",
292                    self.registers.vram_read_address
293                );
294
295                // Writing to MSB initiates VRAM read
296                if byte == WordByte::High {
297                    debug_assert!(self.state.pending_cpu_access.is_none());
298
299                    self.state.pending_cpu_access =
300                        Some(PendingCpuAccess::Read { address: self.registers.vram_read_address });
301                    self.increment_vram_read_address();
302                }
303            }
304            0x02 => {
305                // VWR (VRAM write register)
306                match byte {
307                    WordByte::Low => {
308                        // LSB writes latch the byte
309                        self.registers.vram_write_latch = value;
310                    }
311                    WordByte::High => {
312                        // MSB writes persist to VRAM along with latched byte
313                        debug_assert!(self.state.pending_cpu_access.is_none());
314
315                        let word = u16::from_le_bytes([self.registers.vram_write_latch, value]);
316                        self.state.pending_cpu_access = Some(PendingCpuAccess::Write {
317                            address: self.registers.vram_write_address,
318                            value: word,
319                        });
320                        self.increment_vram_write_address();
321                    }
322                }
323            }
324            0x05 => {
325                // CR (Control register)
326                match byte {
327                    WordByte::Low => {
328                        self.registers.sprite_collision_irq_enabled = value.bit(0);
329                        self.registers.sprite_overflow_irq_enabled = value.bit(1);
330                        self.registers.raster_compare_irq_enabled = value.bit(2);
331                        self.registers.vblank_irq_enabled = value.bit(3);
332                        self.registers.sprites_enabled = value.bit(6);
333                        self.registers.bg_enabled = value.bit(7);
334
335                        log::trace!("CR Low write: {value:02X}");
336                        log::trace!("  BG enabled: {}", self.registers.bg_enabled);
337                        log::trace!("  Sprites enabled: {}", self.registers.sprites_enabled);
338                        log::trace!("  VBlank IRQ enabled: {}", self.registers.vblank_irq_enabled);
339                        log::trace!(
340                            "  Raster compare IRQ enabled: {}",
341                            self.registers.raster_compare_irq_enabled
342                        );
343                        log::trace!(
344                            "  Sprite overflow IRQ enabled: {}",
345                            self.registers.sprite_overflow_irq_enabled
346                        );
347                        log::trace!(
348                            "  Sprite collision IRQ enabled: {}",
349                            self.registers.sprite_collision_irq_enabled
350                        );
351                    }
352                    WordByte::High => {
353                        let increment_idx = ((value >> 3) & 3) as usize;
354                        self.registers.vram_address_increment =
355                            [0x01, 0x20, 0x40, 0x80][increment_idx];
356
357                        log::trace!("CR High write: {value:02X}");
358                        log::trace!(
359                            "  VRAM address increment: 0x{:02X}",
360                            self.registers.vram_address_increment
361                        );
362                    }
363                }
364            }
365            0x06 => {
366                // RCR (Raster compare register)
367                match byte {
368                    WordByte::Low => self.registers.raster_compare.set_lsb(value),
369                    WordByte::High => self.registers.raster_compare.set_msb(value & 3),
370                }
371
372                log::trace!(
373                    "RCR {byte:?} write: {value:02X} (raster compare {})",
374                    self.registers.raster_compare
375                );
376            }
377            0x07 => {
378                // BXR (BG X scroll register)
379                match byte {
380                    WordByte::Low => self.registers.bg_x_scroll.set_lsb(value),
381                    WordByte::High => self.registers.bg_x_scroll.set_msb(value & 3),
382                }
383
384                log::trace!(
385                    "BXR {byte:?} write: {value:02X} (BG X scroll {})",
386                    self.registers.bg_x_scroll
387                );
388            }
389            0x08 => {
390                // BYR (BG Y scroll register)
391                match byte {
392                    WordByte::Low => self.registers.bg_y_scroll.set_lsb(value),
393                    WordByte::High => self.registers.bg_y_scroll.set_msb(value & 1),
394                }
395
396                self.state.bg_y_scroll_written = true;
397
398                log::trace!(
399                    "BYR {byte:?} write: {value:02X} (BG Y scroll {})",
400                    self.registers.bg_y_scroll
401                );
402            }
403            0x09 => {
404                // MWR (Memory width register)
405                if byte == WordByte::Low {
406                    self.registers.vram_access_width = VramAccessWidth::from_bits(value);
407                    self.registers.sprite_access_width = SpriteAccessWidth::from_bits(value >> 2);
408                    self.registers.virtual_screen_width = VirtualScreenWidth::from_bits(value >> 4);
409                    self.registers.virtual_screen_height =
410                        VirtualScreenHeight::from_bit(value.bit(6));
411                    self.registers.bg_cg_mode = CgMode::from_bit(value.bit(7));
412                }
413
414                log::trace!("MWR {byte:?} write: {value:02X}");
415                log::trace!("  VRAM access width: {}", self.registers.vram_access_width.display());
416                log::trace!(
417                    "  Sprite access width: {}",
418                    self.registers.sprite_access_width.display()
419                );
420                log::trace!(
421                    "  Virtual screen width: {}",
422                    self.registers.virtual_screen_width.display()
423                );
424                log::trace!(
425                    "  Virtual screen height: {}",
426                    self.registers.virtual_screen_height.display()
427                );
428                log::trace!("  BG CG mode: {:?}", self.registers.bg_cg_mode);
429            }
430            0x0A => {
431                // HSR (Horizontal sync register)
432                log::trace!("HSR {byte:?} write: {value:02X}");
433
434                match byte {
435                    WordByte::Low => {
436                        // Don't allow games to set extremely low HSW values
437                        // Fixes horribly glitched graphics in Yo' Bro
438                        self.registers.h_sync_width =
439                            cmp::max(24, 8 * u16::from((value & 0x1F) + 1));
440                        log::trace!("  H sync pulse width: {}", self.registers.h_sync_width);
441                    }
442                    WordByte::High => {
443                        self.registers.h_display_start = 8 * u16::from((value & 0x7F) + 1);
444                        log::trace!(
445                            "  H display start position: {}",
446                            self.registers.h_display_start
447                        );
448                    }
449                }
450            }
451            0x0B => {
452                // HDR (Horizontal display register)
453                log::trace!("HDR {byte:?} write: {value:02X}");
454
455                match byte {
456                    WordByte::Low => {
457                        self.registers.h_display_width = 8 * u16::from((value & 0x7F) + 1);
458                        log::trace!("  H display width: {}", self.registers.h_display_width);
459                    }
460                    WordByte::High => {
461                        self.registers.h_display_end = 8 * u16::from((value & 0x7F) + 1);
462                        log::trace!("  H display end position: {}", self.registers.h_display_end);
463                    }
464                }
465            }
466            0x0C => {
467                // VSR (Vertical sync register)
468                log::trace!("VSR {byte:?} write: {value:02X}");
469
470                match byte {
471                    WordByte::Low => {
472                        self.registers.v_sync_width = ((value & 0x1F) + 1).into();
473                        log::trace!("  V sync pulse width: {}", self.registers.v_sync_width);
474                    }
475                    WordByte::High => {
476                        self.registers.v_display_start = u16::from(value) + 2;
477                        log::trace!(
478                            "  V display start position: {}",
479                            self.registers.v_display_start
480                        );
481                    }
482                }
483            }
484            0x0D => {
485                // VDR (Vertical display register)
486                match byte {
487                    WordByte::Low => self.registers.v_display_width_raw.set_lsb(value),
488                    WordByte::High => self.registers.v_display_width_raw.set_msb(value & 1),
489                }
490                self.registers.v_display_width = self.registers.v_display_width_raw + 1;
491
492                log::trace!("VDR {byte:?} write: {value:02X}");
493                log::trace!("  V display width: {}", self.registers.v_display_width);
494            }
495            0x0E => {
496                // VCR (Vertical display end position register)
497                if byte == WordByte::Low {
498                    self.registers.v_display_end = value.into();
499                }
500
501                log::trace!("VCR {byte:?} write: {value:02X}");
502                log::trace!("  V display end position: {}", self.registers.v_display_end);
503            }
504            0x0F => {
505                // DCR (DMA control register)
506                if byte == WordByte::Low {
507                    self.registers.sat_dma_irq_enabled = value.bit(0);
508                    self.registers.vram_dma_irq_enabled = value.bit(1);
509                    self.registers.vram_dma_source_step = DmaStep::from_bit(value.bit(2));
510                    self.registers.vram_dma_destination_step = DmaStep::from_bit(value.bit(3));
511                    self.registers.sat_dma_repeat = value.bit(4);
512                }
513
514                log::trace!("DCR {byte:?} write: {value:02X}");
515                log::trace!(
516                    "  VRAM-to-VRAM DMA IRQ enabled: {}",
517                    self.registers.vram_dma_irq_enabled
518                );
519                log::trace!(
520                    "  VRAM-to-SAT DMA IRQ enabled: {}",
521                    self.registers.sat_dma_irq_enabled
522                );
523                log::trace!(
524                    "  VRAM-to-VRAM DMA source step: {:?}",
525                    self.registers.vram_dma_source_step
526                );
527                log::trace!(
528                    "  VRAM-to-VRAM DMA destination step: {:?}",
529                    self.registers.vram_dma_destination_step
530                );
531                log::trace!("  VRAM_to-SAT DMA repeat: {}", self.registers.sat_dma_repeat);
532            }
533            0x10 => {
534                // SOUR (DMA source address register)
535                byte.set(&mut self.registers.vram_dma_source_address, value);
536
537                log::trace!(
538                    "SOUR {byte:?} write: {value:02X} (address {:04X})",
539                    self.registers.vram_dma_source_address
540                );
541            }
542            0x11 => {
543                // DESR (DMA destination address register)
544                byte.set(&mut self.registers.vram_dma_destination_address, value);
545
546                log::trace!(
547                    "DESR {byte:?} write: {value:02X} (address {:04X})",
548                    self.registers.vram_dma_destination_address
549                );
550            }
551            0x12 => {
552                // LENR (DMA length register)
553                byte.set(&mut self.registers.vram_dma_length, value);
554
555                if byte == WordByte::High {
556                    self.state.dma.vram_triggered = true;
557
558                    // VRAM-to-VRAM DMA can start immediately in burst mode or VBlank
559                    if self.state.can_start_vram_dma() {
560                        self.state.dma.start_vram();
561
562                        log::trace!("Starting VRAM-to-VRAM DMA on line {}", self.state.scanline);
563                    }
564                }
565
566                log::trace!(
567                    "LENR {byte:?} write: {value:02X} (length {:04X})",
568                    self.registers.vram_dma_length
569                );
570            }
571            0x13 => {
572                // DVSSR (VRAM-to-SAT DMA source address register)
573                byte.set(&mut self.registers.sat_dma_source_address, value);
574
575                if byte == WordByte::High {
576                    self.state.dma.sat_triggered = true;
577                }
578
579                log::trace!(
580                    "DVSSR {byte:?} write: {value:02X} (address {:04X})",
581                    self.registers.sat_dma_source_address
582                );
583            }
584            _ => {
585                log::warn!(
586                    "Invalid VDC register write {:02X} {byte:?} {value:02X}",
587                    self.selected_register
588                );
589            }
590        }
591    }
592}