1//! Genesis VDP (video display processor) 2 3mod colors; 4mod cramdots; 5pub mod debug; 6mod fifo; 7mod registers; 8mod render; 9mod sprites; 10 11#[cfg(test)] 12mod tests; 13 14use crate::vdp::colors::ColorTables; 15use crate::vdp::cramdots::CramDotBuffer; 16use crate::vdp::fifo::{VdpFifo, VdpFifoEntry, VramWriteSize}; 17use crate::vdp::registers::{ 18 DebugRegister, DmaMode, H40_LEFT_BORDER, HorizontalDisplaySize, HorizontalScrollMode, 19 InterlacingMode, NTSC_BOTTOM_BORDER, NTSC_TOP_BORDER, PAL_V28_BOTTOM_BORDER, 20 PAL_V28_TOP_BORDER, PAL_V30_BOTTOM_BORDER, PAL_V30_TOP_BORDER, RIGHT_BORDER, Registers, 21 VerticalDisplaySize, VerticalScrollMode, VramSizeKb, 22}; 23use crate::vdp::render::RasterLine; 24use crate::vdp::sprites::{SpriteBuffers, SpriteState}; 25use bincode::{Decode, Encode}; 26use jgenesis_common::boxedarray::{BoxedByteArray, BoxedColorArray, BoxedWordArray}; 27use jgenesis_common::frontend::{ 28 Color, CompositeParams, FrameSize, SamplesPerColorCycle, TimingMode, 29}; 30use jgenesis_common::num::{GetBit, U16Ext}; 31use jgenesis_proc_macros::EnumAll; 32use std::collections::VecDeque; 33use std::ops::Range; 34use std::{array, cmp}; 35use z80_emu::traits::InterruptLine; 36 37pub use crate::vdp::colors::ColorModifier; 38 39const VRAM_LEN: usize = 64 * 1024; 40const CRAM_LEN_WORDS: usize = 64; 41const VSRAM_LEN_WORDS: usize = 40; 42 43const MAX_SCREEN_WIDTH: usize = 320 + H40_LEFT_BORDER as usize + RIGHT_BORDER as usize; 44const MAX_SCREEN_HEIGHT: usize = 240 + PAL_V30_TOP_BORDER as usize + PAL_V30_BOTTOM_BORDER as usize; 45 46// Double screen height to account for interlaced 2x mode 47pub const FRAME_BUFFER_LEN: usize = MAX_SCREEN_WIDTH * MAX_SCREEN_HEIGHT * 2; 48 49pub const MCLK_CYCLES_PER_SCANLINE: u64 = 3420; 50pub const ACTIVE_MCLK_CYCLES_PER_SCANLINE: u64 = 2560; 51pub const NTSC_SCANLINES_PER_FRAME: u16 = 262; 52pub const PAL_SCANLINES_PER_FRAME: u16 = 313; 53 54const MAX_SPRITES_PER_FRAME: usize = 80; 55 56macro_rules! new_bool256 { 57 ($($value:literal),* $(,)?) => { 58 { 59 let mut bools = [false; 256]; 60 $( 61 bools[$value] = true; 62 )* 63 bools 64 } 65 } 66} 67 68// Adapted from https://gendev.spritesmind.net/forum/viewtopic.php?t=851 and modified so that 0 69// is at H=0x000 rather than the H scroll fetch 70const H32_ACCESS_SLOTS: &[bool; 256] = 71 &new_bool256![5, 13, 21, 37, 45, 53, 69, 77, 85, 101, 109, 117, 132, 133, 147, 161]; 72const H40_ACCESS_SLOTS: &[bool; 256] = 73 &new_bool256![6, 14, 22, 38, 46, 54, 70, 78, 86, 102, 110, 118, 134, 142, 150, 165, 166, 190]; 74 75// Adapted from https://gendev.spritesmind.net/forum/viewtopic.php?p=20921#p20921 76// TODO H32 refresh slot locations are probably not accurate 77const H32_BLANK_REFRESH_SLOTS: &[bool; 256] = &new_bool256![1, 33, 65, 97, 129]; 78const H40_BLANK_REFRESH_SLOTS: &[bool; 256] = &new_bool256![26, 58, 90, 122, 154, 204]; 79 80// Most H values sourced from https://gendev.spritesmind.net/forum/viewtopic.php?p=17683#p17683 81impl HorizontalDisplaySize { 82 // Total number of slots minus number of refresh slots 83 const fn access_slots_per_blank_line(self) -> u16 { 84 match self { 85 Self::ThirtyTwoCell => 171 - 5, 86 Self::FortyCell => 210 - 6, 87 } 88 } 89 90 // H range during which the status HBlank flag is _not_ set 91 const fn hblank_flag_clear_h_range(self) -> Range<u16> { 92 match self { 93 Self::ThirtyTwoCell => 0x00A..0x126, 94 Self::FortyCell => 0x00B..0x166, 95 } 96 } 97 98 // H value at which the VDP increments the H interrupt counter, increments the V counter, and 99 // potentially sets HINT pending 100 const fn h_interrupt_h(self) -> u16 { 101 match self { 102 Self::ThirtyTwoCell => 0x10A, 103 Self::FortyCell => 0x14A, 104 } 105 } 106 107 const fn h_interrupt_scanline_mclk(self) -> u64 { 108 (self.h_interrupt_h() as u64) * self.active_display_mclk_divider() 109 } 110 111 // H value at which the VDP sets VINT pending on line 224/240 112 const fn v_interrupt_h(self) -> u16 { 113 match self { 114 Self::ThirtyTwoCell => 0x001, 115 Self::FortyCell => 0x002, 116 } 117 } 118 119 const fn v_interrupt_scanline_mclk(self) -> u64 { 120 (self.v_interrupt_h() as u64) * self.active_display_mclk_divider() 121 } 122 123 // Range when the VDP is actively displaying pixels 124 const fn active_display_h_range(self) -> Range<u16> { 125 match self { 126 Self::ThirtyTwoCell => 0x018..0x118, 127 Self::FortyCell => 0x01A..0x15A, 128 } 129 } 130 131 const fn read_h_scroll_h(self) -> u16 { 132 // Actually -26 but that overflows in H32 mode 133 self.active_display_h_range().start - 24 134 } 135 136 const fn rendering_begin_h(self) -> u16 { 137 self.active_display_h_range().start - 16 138 } 139 140 // H at which to execute sprite processing phase 2 (fetch sprite attributes) 141 const fn fetch_sprite_attributes_h(self) -> u16 { 142 // Chaekopon demo by Limp Ninja is sensitive to when phase 2 is executed 143 // This demo sometimes modifies the sprite attribute table address shortly before HINT, 144 // seemingly just after attributes are fetched for the last sprite scanned in phase 1 145 self.active_display_h_range().end - 16 - 8 146 } 147 148 // H where HBlank begins (should line up with the two consecutive external access slots) 149 const fn hblank_begin_h(self) -> u16 { 150 self.active_display_h_range().end - 16 151 } 152 153 // H by which VDP register latching for the next line is completed 154 const fn latch_registers_h(self) -> u16 { 155 // Estimated based on latching taking place within 36 CPU cycles of HINT 156 // TODO this is probably inaccurate for either H32 or H40 mode 157 match self { 158 Self::ThirtyTwoCell => 0x121, 159 Self::FortyCell => 0x169, 160 } 161 } 162 163 const fn active_display_mclk_divider(self) -> u64 { 164 match self { 165 Self::ThirtyTwoCell => 10, 166 Self::FortyCell => 8, 167 } 168 } 169} 170 171#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 172pub enum ControlWriteFlag { 173 First, 174 Second, 175} 176 177#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 178pub enum DataPortMode { 179 Read, 180 Write, 181} 182 183#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 184pub enum DataPortLocation { 185 Vram, 186 Vram8Bit, 187 Cram, 188 Vsram, 189 Invalid, 190} 191 192#[derive(Debug, Clone, Encode, Decode)] 193struct ControlPort { 194 mode: DataPortMode, 195 location_bits: u8, 196 location: DataPortLocation, 197 control_address: u32, 198 data_port_address: u32, 199 write_flag: ControlWriteFlag, 200 dma_active: bool, 201} 202 203impl ControlPort { 204 fn new() -> Self { 205 Self { 206 mode: DataPortMode::Read, 207 location_bits: 0, 208 location: DataPortLocation::Vram, 209 control_address: 0, 210 data_port_address: 0, 211 write_flag: ControlWriteFlag::First, 212 dma_active: false, 213 } 214 } 215 216 fn write_first_command(&mut self, value: u16) { 217 // First command word: Lowest 14 bits of address and lowest 2 bits of code 218 self.control_address = (self.control_address & !0x3FFF) | u32::from(value & 0x3FFF); 219 self.data_port_address = self.control_address; 220 221 self.mode = if value.bit(14) { DataPortMode::Write } else { DataPortMode::Read }; 222 self.location_bits = (self.location_bits & !1) | (value >> 15) as u8; 223 self.location = parse_location_bits(self.location_bits, self.mode); 224 } 225 226 fn write_second_command(&mut self, value: u16, registers: &Registers) { 227 // Second command word: Highest 3 bits of address (A14-A16) and highest 4 bits of code (CD2-CD5) 228 self.control_address = (self.control_address & 0x3FFF) | (u32::from(value & 7) << 14); 229 self.data_port_address = self.control_address; 230 231 self.location_bits = (self.location_bits & 1) | ((value >> 3) & 0b110) as u8; 232 self.location = parse_location_bits(self.location_bits, self.mode); 233 234 // CD5 is only writable if DMA is enabled in register #1 235 if registers.dma_enabled { 236 self.dma_active = value.bit(7); 237 } 238 } 239 240 fn new_fifo_entry(&self, word: u16, vram_size: VramSizeKb) -> VdpFifoEntry { 241 // Perform 128KB mode address conversion on FIFO push rather than pop; Overdrive 2 depends on this 242 // TODO does 128KB mode also cause invalid target FIFO entries to only take 1 slot? 243 let (address, size) = match (self.location, vram_size) { 244 (DataPortLocation::Vram | DataPortLocation::Invalid, VramSizeKb::OneTwentyEight) => { 245 (convert_128kb_vram_address(self.data_port_address), VramWriteSize::Byte) 246 } 247 _ => (self.data_port_address, VramWriteSize::Word), 248 }; 249 250 VdpFifoEntry::new(self.mode, self.location, address, word, size) 251 } 252 253 fn increment_data_port_address(&mut self, registers: &Registers) { 254 self.data_port_address = 255 self.data_port_address.wrapping_add(registers.data_port_auto_increment.into()); 256 } 257} 258 259fn parse_location_bits(bits: u8, mode: DataPortMode) -> DataPortLocation { 260 match (bits, mode) { 261 (0b000, _) => DataPortLocation::Vram, 262 (0b010, _) => DataPortLocation::Vsram, 263 (0b001, DataPortMode::Write) | (0b100, DataPortMode::Read) => DataPortLocation::Cram, 264 // Undocumented: Code 01100 enables 8-bit VRAM reads (verified by VDPFIFOTesting ROM) 265 (0b110, DataPortMode::Read) => DataPortLocation::Vram8Bit, 266 _ => DataPortLocation::Invalid, 267 } 268} 269 270#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 271enum PendingWrite { 272 Control(u16), 273 Data(u16), 274} 275 276impl Default for PendingWrite { 277 fn default() -> Self { 278 Self::Control(0) 279 } 280} 281 282#[derive(Debug, Clone, Encode, Decode)] 283struct InternalState { 284 // Whether the VDP is actively raising INT6 285 v_interrupt_pending: bool, 286 h_interrupt_pending: bool, 287 // V/H interrupts must be delayed by 1 CPU instruction if they are enabled while an interrupt is 288 // pending; Sesame Street Counting Cafe and Fatal Rewind depend on this 289 v_interrupt_enabled_latch: bool, 290 h_interrupt_enabled_latch: bool, 291 h_interrupt_counter: u16, 292 latched_hv_counter: Option<u16>, 293 v_border_forgotten: bool, 294 top_border: u16, 295 last_scroll_b_palettes: [u8; 2], 296 scanline: u16, 297 scanline_mclk_cycles: u64, 298 pixel: u16, 299 in_vblank: bool, 300 // Used to store writes to either VDP port while a memory-to-VRAM DMA is in progress 301 // (Can happen if a DMA is initiated using a longword write) 302 pending_writes: Vec<PendingWrite>, 303 display_enable_pending: bool, 304 data_port_read_wait: bool, 305 vram_fill_data: Option<u16>, 306 vram_copy_odd_slot: bool, 307 interlaced_frame: bool, 308 interlaced_odd: bool, 309 // Latched at start of VBlank 310 // This is not accurate to actual hardware, but nothing should change H resolution mid-frame 311 // during active display 312 frame_h_resolution: HorizontalDisplaySize, 313} 314 315impl InternalState { 316 fn new(timing_mode: TimingMode) -> Self { 317 Self { 318 v_interrupt_pending: false, 319 h_interrupt_pending: false, 320 v_interrupt_enabled_latch: false, 321 h_interrupt_enabled_latch: false, 322 h_interrupt_counter: 0, 323 latched_hv_counter: None, 324 v_border_forgotten: false, 325 top_border: VerticalDisplaySize::default().top_border(timing_mode), 326 last_scroll_b_palettes: [0; 2], 327 scanline: 0, 328 scanline_mclk_cycles: 0, 329 pixel: 0, 330 in_vblank: false, 331 pending_writes: Vec::with_capacity(10), 332 display_enable_pending: false, 333 data_port_read_wait: false, 334 vram_fill_data: None, 335 vram_copy_odd_slot: false, 336 interlaced_frame: false, 337 interlaced_odd: false, 338 frame_h_resolution: HorizontalDisplaySize::default(), 339 } 340 } 341 342 fn interlaced_odd(&self) -> bool { 343 self.interlaced_frame && self.interlaced_odd 344 } 345} 346 347#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 348struct CachedSpriteData { 349 v_position: u16, 350 h_size_cells: u8, 351 v_size_cells: u8, 352 link_data: u8, 353} 354 355impl CachedSpriteData { 356 fn update_first_word_msb(&mut self, msb: u8) { 357 self.v_position = (self.v_position & 0x00FF) | (u16::from(msb & 0x03) << 8); 358 } 359 360 fn update_first_word_lsb(&mut self, lsb: u8) { 361 self.v_position = (self.v_position & 0xFF00) | u16::from(lsb); 362 } 363 364 fn update_second_word_msb(&mut self, msb: u8) { 365 self.h_size_cells = ((msb >> 2) & 0x03) + 1; 366 self.v_size_cells = (msb & 0x03) + 1; 367 } 368 369 fn update_second_word_lsb(&mut self, lsb: u8) { 370 self.link_data = lsb & 0x7F; 371 } 372} 373 374#[derive(Debug, Clone, Encode, Decode)] 375struct SpriteData { 376 pattern_generator: u16, 377 v_position: u16, 378 h_position: u16, 379 h_size_cells: u8, 380 v_size_cells: u8, 381 palette: u8, 382 vertical_flip: bool, 383 horizontal_flip: bool, 384 priority: bool, 385 link_data: u8, 386} 387 388impl SpriteData { 389 fn create(cached_data: CachedSpriteData, uncached_bytes: &[u8]) -> Self { 390 // 3rd word 391 let priority = uncached_bytes[0].bit(7); 392 let palette = (uncached_bytes[0] >> 5) & 0x03; 393 let vertical_flip = uncached_bytes[0].bit(4); 394 let horizontal_flip = uncached_bytes[0].bit(3); 395 let pattern_generator = u16::from_be_bytes([uncached_bytes[0] & 0x07, uncached_bytes[1]]); 396 397 // 4th word 398 let h_position = u16::from_be_bytes([uncached_bytes[2] & 0x01, uncached_bytes[3]]); 399 400 Self { 401 pattern_generator, 402 v_position: cached_data.v_position, 403 h_position, 404 h_size_cells: cached_data.h_size_cells, 405 v_size_cells: cached_data.v_size_cells, 406 palette, 407 vertical_flip, 408 horizontal_flip, 409 priority, 410 link_data: cached_data.link_data, 411 } 412 } 413} 414 415#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 416struct TilePixel { 417 color: u8, 418 palette: u8, 419 priority: bool, 420} 421 422#[derive(Debug, Clone, Encode, Decode)] 423struct BgBuffers { 424 plane_a_pixels: [TilePixel; MAX_SCREEN_WIDTH], 425 plane_b_pixels: [TilePixel; MAX_SCREEN_WIDTH], 426} 427 428impl BgBuffers { 429 fn new() -> Self { 430 Self { 431 plane_a_pixels: array::from_fn(|_| TilePixel::default()), 432 plane_b_pixels: array::from_fn(|_| TilePixel::default()), 433 } 434 } 435} 436 437#[derive(Debug, Clone, Copy, PartialEq, Eq)] 438pub enum VdpTickEffect { 439 None, 440 FrameComplete, 441} 442 443pub(crate) trait TimingModeExt: Copy { 444 fn scanlines_per_frame(self, interlaced: bool, interlaced_odd: bool) -> u16; 445 446 fn rendered_lines_per_frame(self) -> u16; 447} 448 449impl TimingModeExt for TimingMode { 450 fn scanlines_per_frame(self, interlaced: bool, interlaced_odd: bool) -> u16 { 451 match self { 452 Self::Ntsc => NTSC_SCANLINES_PER_FRAME + u16::from(interlaced_odd), 453 Self::Pal => PAL_SCANLINES_PER_FRAME - 1 + u16::from(!interlaced || interlaced_odd), 454 } 455 } 456 457 // Includes border lines 458 fn rendered_lines_per_frame(self) -> u16 { 459 match self { 460 Self::Ntsc => 224 + NTSC_TOP_BORDER + NTSC_BOTTOM_BORDER, 461 Self::Pal => 224 + PAL_V28_TOP_BORDER + PAL_V28_BOTTOM_BORDER, 462 } 463 } 464} 465 466#[derive(Debug, Clone, Copy)] 467pub struct BorderSize { 468 pub left: u32, 469 pub right: u32, 470 pub top: u32, 471 pub bottom: u32, 472} 473 474#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 475pub enum DarkenColors { 476 No, 477 Yes, 478} 479 480#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 481pub struct VdpConfig { 482 pub enforce_sprite_limits: bool, 483 pub non_linear_color_scale: bool, 484 pub deinterlace: bool, 485 pub render_vertical_border: bool, 486 pub render_horizontal_border: bool, 487 pub plane_a_enabled: bool, 488 pub plane_b_enabled: bool, 489 pub sprites_enabled: bool, 490 pub window_enabled: bool, 491 pub color_adjustment: DarkenColors, 492} 493 494#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, EnumAll)] 495enum VdpEvent { 496 VInterrupt, 497 ReadHScroll, 498 RenderLine, 499 FetchSpriteAttributes, 500 HBlankStart, 501 HInterrupt, 502 LatchRegisters, 503 None, 504} 505 506impl VdpEvent { 507 const NUM: usize = VdpEvent::ALL.len(); 508} 509 510#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 511struct VdpEventWithTime { 512 event: VdpEvent, 513 h: u16, 514} 515 516impl VdpEventWithTime { 517 const fn new(h: u16, event: VdpEvent) -> Self { 518 Self { event, h } 519 } 520} 521 522#[derive(Debug, Clone, Copy)] 523struct VCounter { 524 counter: u8, 525 vblank_flag: bool, 526} 527 528#[derive(Debug, Clone, Copy)] 529struct HVCounter { 530 internal_h: u16, 531 internal_v: u8, 532 hv_counter: u16, 533 vblank_flag: bool, 534} 535 536pub trait VdpBusView { 537 fn read_word_for_dma(&mut self, address: u32) -> u16; 538} 539 540type Vram = [u8; VRAM_LEN]; 541type Cram = [u16; CRAM_LEN_WORDS]; 542type Vsram = [u16; VSRAM_LEN_WORDS]; 543 544#[derive(Debug, Clone, Encode, Decode)] 545pub struct Vdp { 546 frame_buffer: BoxedColorArray<FRAME_BUFFER_LEN>, 547 cram_dots: CramDotBuffer, 548 vram: BoxedByteArray<VRAM_LEN>, 549 cram: BoxedWordArray<CRAM_LEN_WORDS>, 550 vsram: BoxedWordArray<VSRAM_LEN_WORDS>, 551 fifo: VdpFifo, 552 dma_latency: u8, 553 // Used to store writes to data port while FIFO is full 554 pending_fifo_writes: VecDeque<VdpFifoEntry>, 555 timing_mode: TimingMode, 556 control_port: ControlPort, 557 state: InternalState, 558 sprite_state: SpriteState, 559 registers: Registers, 560 debug_register: DebugRegister, 561 latched_registers: Registers, 562 latched_h_scroll: (u16, u16), 563 latched_full_screen_v_scroll: (u16, u16), 564 cached_sprite_attributes: Box<[CachedSpriteData; MAX_SPRITES_PER_FRAME]>, 565 latched_sprite_attributes: Box<[CachedSpriteData; MAX_SPRITES_PER_FRAME]>, 566 bg_buffers: Box<BgBuffers>, 567 sprite_buffers: SpriteBuffers, 568 interlaced_sprite_buffers: SpriteBuffers, 569 config: VdpConfig, 570 color_tables: ColorTables, 571 vdp_event_times: [VdpEventWithTime; VdpEvent::NUM], 572 vdp_event_idx: u8, 573} 574 575impl Vdp { 576 #[allow(clippy::missing_panics_doc)] 577 #[must_use] 578 pub fn new(timing_mode: TimingMode, config: VdpConfig) -> Self { 579 Self { 580 frame_buffer: BoxedColorArray::new(), 581 cram_dots: CramDotBuffer::new(), 582 vram: BoxedByteArray::new(), 583 cram: BoxedWordArray::new(), 584 vsram: BoxedWordArray::new(), 585 fifo: VdpFifo::new(), 586 dma_latency: 0, 587 pending_fifo_writes: VecDeque::with_capacity(8), 588 timing_mode, 589 control_port: ControlPort::new(), 590 state: InternalState::new(timing_mode), 591 sprite_state: SpriteState::default(), 592 registers: Registers::new(), 593 debug_register: DebugRegister::new(), 594 latched_registers: Registers::new(), 595 latched_h_scroll: (0, 0), 596 latched_full_screen_v_scroll: (0, 0), 597 cached_sprite_attributes: vec![CachedSpriteData::default(); MAX_SPRITES_PER_FRAME] 598 .into_boxed_slice() 599 .try_into() 600 .unwrap(), 601 latched_sprite_attributes: vec![CachedSpriteData::default(); MAX_SPRITES_PER_FRAME] 602 .into_boxed_slice() 603 .try_into() 604 .unwrap(), 605 bg_buffers: Box::new(BgBuffers::new()), 606 sprite_buffers: SpriteBuffers::new(), 607 interlaced_sprite_buffers: SpriteBuffers::new(), 608 config, 609 color_tables: ColorTables::from_config(&config), 610 vdp_event_times: Self::vdp_event_times(HorizontalDisplaySize::default()), 611 vdp_event_idx: 0, 612 } 613 } 614 615 pub fn write_control(&mut self, value: u16) { 616 log::trace!( 617 "VDP control write on scanline {} / mclk {} / pixel {}: {value:04X} (flag = {:?}, dma_enabled = {})", 618 self.state.scanline, 619 self.state.scanline_mclk_cycles, 620 scanline_mclk_to_pixel( 621 self.state.scanline_mclk_cycles, 622 self.registers.horizontal_display_size 623 ), 624 self.control_port.write_flag, 625 self.registers.dma_enabled 626 ); 627 628 if self.control_port.dma_active && self.registers.dma_mode == DmaMode::MemoryToVram { 629 // VDP is locking the bus; buffer the write until the DMA is done 630 // Some games depend on this - they'll do a longword write where the first word starts 631 // a DMA and then the second word changes the control port address 632 self.state.pending_writes.push(PendingWrite::Control(value)); 633 return; 634 } 635 636 match self.control_port.write_flag { 637 ControlWriteFlag::First => { 638 // VDP register write OR first word of command write 639 640 // Always write first word to control port, even if this is a register write 641 self.control_port.write_first_command(value); 642 643 if value & 0xC000 == 0x8000 { 644 // VDP register write 645 self.write_vdp_register(value); 646 } else { 647 // First word of command write 648 self.control_port.write_flag = ControlWriteFlag::Second; 649 } 650 } 651 ControlWriteFlag::Second => { 652 // Second word of command write 653 self.control_port.write_second_command(value, &self.registers); 654 self.control_port.write_flag = ControlWriteFlag::First; 655 656 if self.control_port.dma_active { 657 // DMA started 658 self.state.vram_fill_data = None; 659 self.state.vram_copy_odd_slot = false; 660 661 // OutRunners depends on there being a delay to FIFO writes when starting 662 // memory-to-VRAM DMA 663 if self.registers.dma_mode == DmaMode::MemoryToVram { 664 // Hack: 8 slots gets roughly correct alignment for direct color DMA demos, 665 // but 8 breaks Overdrive 2's plasma twisters effect. Use a shorter delay when 666 // DMAing to VSRAM (almost certainly working around other timing issues) 667 self.dma_latency = match self.control_port.location { 668 DataPortLocation::Vsram => 5, 669 _ => 8, 670 }; 671 } 672 673 log::trace!( 674 "DMA of type {:?} initiated at line {} mclk {} pixel {}", 675 self.registers.dma_mode, 676 self.state.scanline, 677 self.state.scanline_mclk_cycles, 678 self.state.pixel 679 ); 680 } 681 } 682 } 683 684 log::trace!(" Mode: {:?}", self.control_port.mode); 685 log::trace!(" Location bits: {:03b}", self.control_port.location_bits); 686 log::trace!(" Location: {:?}", self.control_port.location); 687 log::trace!(" Address: {:05X}", self.control_port.control_address); 688 log::trace!(" DMA active: {}", self.control_port.dma_active); 689 } 690 691 fn write_vdp_register(&mut self, value: u16) { 692 let prev_display_enabled = self.registers.display_enabled; 693 let prev_h_display_size = self.registers.horizontal_display_size; 694 let prev_v_display_size = self.registers.vertical_display_size; 695 696 let register_number = ((value >> 8) & 0x1F) as u8; 697 self.registers.write_internal_register(register_number, value as u8); 698 699 if !prev_display_enabled && self.registers.display_enabled && !self.fifo.is_empty() { 700 // When display switches from disabled to enabled while FIFO is not empty, wait to 701 // enable display until the FIFO is emptied. 702 // Possibly not accurate, but fixes glitches in games that re-enable display immediately 703 // after the end of a DMA (e.g. Mickey Mania, Overdrive 1 & 2) 704 self.registers.display_enabled = false; 705 self.state.display_enable_pending = true; 706 } else if register_number == 1 { 707 self.state.display_enable_pending = false; 708 } 709 710 if self.registers.hv_counter_stopped && self.state.latched_hv_counter.is_none() { 711 let HVCounter { hv_counter, .. } = 712 self.hv_counter_internal(self.state.scanline_mclk_cycles); 713 self.state.latched_hv_counter = Some(hv_counter); 714 } else if !self.registers.hv_counter_stopped { 715 self.state.latched_hv_counter = None; 716 } 717 718 self.update_latched_registers_if_necessary(register_number); 719 720 if register_number == 1 { 721 // Update enabled pixels in sprite state if register #1 was written 722 self.sprite_state.handle_display_enabled_write( 723 self.registers.horizontal_display_size, 724 self.registers.display_enabled, 725 self.state.pixel, 726 ); 727 728 // Mark vertical border "forgotten" if V size was switched from V30 to V28 between lines 224-239 729 // This has a few effects: 730 // - The HINT counter continues to tick down every line throughout VBlank instead of getting reset 731 // - The VDP continues to render normally inside the vertical border 732 if prev_v_display_size == VerticalDisplaySize::ThirtyCell 733 && self.registers.vertical_display_size == VerticalDisplaySize::TwentyEightCell 734 && (VerticalDisplaySize::TwentyEightCell.active_scanlines() 735 ..VerticalDisplaySize::ThirtyCell.active_scanlines()) 736 .contains(&self.state.scanline) 737 { 738 log::trace!( 739 "V border forgotten; line {} pixel {}", 740 self.state.scanline, 741 self.state.pixel 742 ); 743 self.state.v_border_forgotten = true; 744 } 745 } 746 747 if prev_h_display_size != self.registers.horizontal_display_size { 748 self.handle_h_resolution_change(); 749 } 750 } 751 752 fn handle_h_resolution_change(&mut self) { 753 let h_display_size = self.registers.horizontal_display_size; 754 self.state.pixel = scanline_mclk_to_pixel(self.state.scanline_mclk_cycles, h_display_size); 755 756 let internal_h = pixel_to_internal_h(self.state.pixel, h_display_size); 757 let effective_v = if internal_h >= h_display_size.hblank_begin_h() { 758 self.state.scanline + 1 759 } else { 760 self.state.scanline 761 }; 762 let active_scanlines = self.registers.vertical_display_size.active_scanlines(); 763 let scanlines_per_frame = self.scanlines_in_current_frame(); 764 self.state.in_vblank = (active_scanlines..scanlines_per_frame - 1).contains(&effective_v); 765 766 self.vdp_event_times = Self::vdp_event_times(h_display_size); 767 self.vdp_event_idx = 0; 768 while internal_h >= self.vdp_event_times[self.vdp_event_idx as usize].h { 769 self.vdp_event_idx += 1; 770 } 771 } 772 773 fn update_latched_registers_if_necessary(&mut self, register_number: u8) { 774 // Writing to register #2, #3, or #4 immediately updates the corresponding nametable address; these registers 775 // are not latched. 776 // Writing to register #1 immediately updates the display enabled flag. 777 // Writing to register #7 immediately updates the background color. 778 // Other register writes do not take effect until the next scanline. 779 macro_rules! relatch_registers { 780 ($self:expr, [$($field:ident),* $(,)?]) => { 781 { 782 let mut changed = false; 783 $( 784 changed |= self.latched_registers.$field != self.registers.$field; 785 self.latched_registers.$field = self.registers.$field; 786 )* 787 changed 788 } 789 } 790 } 791 792 let changed = match register_number { 793 1 => { 794 relatch_registers!(self, [display_enabled]) 795 } 796 2 => { 797 relatch_registers!(self, [scroll_a_base_nt_addr]) 798 } 799 3 => { 800 relatch_registers!(self, [window_base_nt_addr]) 801 } 802 4 => { 803 relatch_registers!(self, [scroll_b_base_nt_addr]) 804 } 805 7 => { 806 relatch_registers!(self, [background_palette, background_color_id]) 807 } 808 _ => return, 809 }; 810 811 // If this write occurred during active display, re-render the current scanline starting from the current pixel 812 if changed { 813 self.maybe_render_partial_line(); 814 } 815 } 816 817 fn maybe_render_partial_line(&mut self) { 818 if self.state.scanline >= self.latched_registers.vertical_display_size.active_scanlines() { 819 return; 820 } 821 822 let active_display_range = self.registers.horizontal_display_size.active_display_h_range(); 823 if active_display_range.contains(&self.state.pixel) { 824 log::trace!( 825 "Re-rendering line {} from pixel {} (frame pixel {})", 826 self.state.scanline, 827 self.state.pixel, 828 self.state.pixel.saturating_sub(active_display_range.start) 829 ); 830 self.render_scanline( 831 self.state.scanline, 832 self.state.pixel.saturating_sub(active_display_range.start), 833 ); 834 } 835 } 836 837 pub fn read_data(&mut self) -> u16 { 838 log::trace!( 839 "VDP data read at line {} mclk {} pixel {}", 840 self.state.scanline, 841 self.state.scanline_mclk_cycles, 842 self.state.pixel 843 ); 844 845 // Reset write flag on all data port accesses 846 self.control_port.write_flag = ControlWriteFlag::First; 847 848 if self.control_port.mode != DataPortMode::Read { 849 // TODO return previous read buffer contents? 850 return 0xFFFF; 851 } 852 853 let mut value = match self.control_port.location { 854 DataPortLocation::Vram => match self.registers.vram_size { 855 VramSizeKb::SixtyFour => { 856 let vram_addr = (self.control_port.data_port_address & 0xFFFF & !1) as usize; 857 let msb = self.vram[vram_addr]; 858 let lsb = self.vram[vram_addr + 1]; 859 u16::from_be_bytes([msb, lsb]) 860 } 861 VramSizeKb::OneTwentyEight => { 862 let vram_addr = convert_128kb_vram_address(self.control_port.data_port_address); 863 let byte = self.vram[vram_addr as usize]; 864 u16::from_be_bytes([byte, byte]) 865 } 866 }, 867 DataPortLocation::Vram8Bit => { 868 // TODO 128KB mode? 869 let vram_addr = ((self.control_port.data_port_address & 0xFFFF) ^ 1) as usize; 870 let lsb = self.vram[vram_addr]; 871 u16::from_le_bytes([lsb, self.fifo.next_slot_word().msb()]) 872 } 873 DataPortLocation::Cram => { 874 let cram_addr = (self.control_port.data_port_address & 0x7F) >> 1; 875 self.cram[cram_addr as usize] 876 } 877 DataPortLocation::Vsram => { 878 let vsram_addr = ((self.control_port.data_port_address & 0x7F) >> 1) as usize; 879 if vsram_addr < VSRAM_LEN_WORDS { 880 self.vsram[vsram_addr] 881 } else { 882 // TODO return most recently used VSRAM entry? 883 self.vsram[0] 884 } 885 } 886 DataPortLocation::Invalid => { 887 // TODO return previous read buffer contents? 888 0xFFFF 889 } 890 }; 891 892 if !self.fifo.is_empty() { 893 self.state.data_port_read_wait = true; 894 895 if let Some(read_override) = self.data_port_fifo_override() { 896 value = read_override; 897 } 898 } 899 900 self.control_port.increment_data_port_address(&self.registers); 901 902 // CRAM is 9-bit memory and VSRAM is 11-bit memory 903 // Remaining bits are filled from the last word written to the next available FIFO slot 904 match self.control_port.location { 905 DataPortLocation::Cram => (value & 0x0EEE) | (self.fifo.next_slot_word() & !0x0EEE), 906 DataPortLocation::Vsram => (value & 0x07FF) | (self.fifo.next_slot_word() & !0x07FF), 907 _ => value, 908 } 909 } 910 911 // TODO this function is not well-tested 912 fn data_port_fifo_override(&self) -> Option<u16> { 913 let mut read_override: Option<u16> = None; 914 915 let address_mask = match self.control_port.location { 916 DataPortLocation::Vram | DataPortLocation::Vram8Bit => match self.registers.vram_size { 917 VramSizeKb::SixtyFour => 0xFFFF & !1, 918 VramSizeKb::OneTwentyEight => 0x1FFFF & !1, 919 }, 920 DataPortLocation::Cram | DataPortLocation::Vsram => 0x7F & !1, 921 DataPortLocation::Invalid => !0, 922 }; 923 924 for entry in self.fifo.iter() { 925 if entry.mode != DataPortMode::Write 926 || entry.location != self.control_port.location 927 || (entry.address & address_mask) 928 != (self.control_port.data_port_address & address_mask) 929 { 930 continue; 931 } 932 933 match self.control_port.location { 934 DataPortLocation::Vram => match self.registers.vram_size { 935 VramSizeKb::SixtyFour => { 936 read_override = Some(if !entry.address.bit(0) { 937 entry.word 938 } else { 939 entry.word.swap_bytes() 940 }); 941 } 942 VramSizeKb::OneTwentyEight => { 943 let byte = entry.word.lsb(); 944 read_override = Some(u16::from_le_bytes([byte, byte])); 945 } 946 }, 947 DataPortLocation::Cram | DataPortLocation::Vsram => { 948 read_override = Some(entry.word); 949 } 950 DataPortLocation::Vram8Bit | DataPortLocation::Invalid => {} 951 } 952 } 953 954 if let Some(read_override) = read_override { 955 log::trace!( 956 "Overriding {:?} read of {:04X} to {read_override:04X}", 957 self.control_port.location, 958 self.control_port.data_port_address 959 ); 960 } 961 962 read_override 963 } 964 965 pub fn write_data(&mut self, value: u16) { 966 log::trace!( 967 "VDP data write on scanline {} / mclk {} / pixel {}: {value:04X}, data port addr {:04X}", 968 self.state.scanline, 969 self.state.scanline_mclk_cycles, 970 self.state.pixel, 971 self.control_port.data_port_address 972 ); 973 974 if self.control_port.dma_active && self.registers.dma_mode == DmaMode::MemoryToVram { 975 // VDP is locking the bus; buffer the write until the DMA is done 976 self.state.pending_writes.push(PendingWrite::Data(value)); 977 return; 978 } 979 980 // Reset write flag on all data port accesses 981 self.control_port.write_flag = ControlWriteFlag::First; 982 983 let fifo_entry = self.control_port.new_fifo_entry(value, self.registers.vram_size); 984 self.control_port.increment_data_port_address(&self.registers); 985 986 if fifo_entry.mode == DataPortMode::Read { 987 log::debug!("Data port write with read target {fifo_entry:?}"); 988 } 989 990 if self.fifo.is_full() { 991 self.pending_fifo_writes.push_back(fifo_entry); 992 } else { 993 self.push_fifo(fifo_entry); 994 } 995 } 996 997 fn push_fifo(&mut self, entry: VdpFifoEntry) { 998 // Check sprite table cache on FIFO push; this works around some timing issues in rendering 999 // Overdrive 2's textured cube effect 1000 if entry.mode == DataPortMode::Write && entry.location == DataPortLocation::Vram { 1001 // Sprite cache bytes are effectively byteswapped in 128KB mode, since LSB is written 1002 // to the target address as a single byte, but MSB can still update the paired byte 1003 // in the sprite cache 1004 let word_bytes = match self.registers.vram_size { 1005 VramSizeKb::SixtyFour => entry.word.to_be_bytes(), 1006 VramSizeKb::OneTwentyEight => entry.word.to_le_bytes(), 1007 }; 1008 1009 // Full 17-bit address is checked for sprite cache updates in both 64KB and 128KB mode 1010 self.maybe_update_sprite_cache(entry.address, word_bytes[0]); 1011 self.maybe_update_sprite_cache(entry.address ^ 1, word_bytes[1]); 1012 } 1013 1014 log::trace!( 1015 "FIFO push (line {} pixel {}): {entry:04X?}", 1016 self.state.scanline, 1017 self.state.pixel 1018 ); 1019 1020 self.fifo.push(entry); 1021 } 1022 1023 fn pop_fifo(&mut self) { 1024 let entry = self.fifo.front(); 1025 1026 log::trace!( 1027 "FIFO pop (line {} pixel {} len {}): {entry:04X?}", 1028 self.state.scanline, 1029 self.state.pixel, 1030 self.fifo.len() 1031 ); 1032 1033 let mut render_partial_line = false; 1034 1035 if entry.mode == DataPortMode::Write { 1036 match entry.location { 1037 DataPortLocation::Vram => { 1038 let vram_addr = (entry.address & 0xFFFF) as usize; 1039 match entry.size { 1040 VramWriteSize::Word => { 1041 self.vram[vram_addr] = entry.word.msb(); 1042 self.vram[vram_addr ^ 1] = entry.word.lsb(); 1043 } 1044 VramWriteSize::Byte => { 1045 self.vram[vram_addr] = entry.word.lsb(); 1046 } 1047 } 1048 } 1049 DataPortLocation::Cram => { 1050 let cram_addr = (entry.address & 0x7F) >> 1; 1051 self.cram[cram_addr as usize] = entry.word; 1052 1053 self.cram_dots.check_for_dot( 1054 &self.registers, 1055 &self.fifo, 1056 self.state.pixel, 1057 cram_addr, 1058 entry.word, 1059 ); 1060 1061 render_partial_line = true; 1062 } 1063 DataPortLocation::Vsram => { 1064 let vsram_addr = ((entry.address & 0x7F) >> 1) as usize; 1065 if vsram_addr < VSRAM_LEN_WORDS { 1066 self.vsram[vsram_addr] = entry.word; 1067 1068 render_partial_line = self.latched_registers.vertical_scroll_mode 1069 == VerticalScrollMode::TwoCell; 1070 } 1071 } 1072 DataPortLocation::Vram8Bit | DataPortLocation::Invalid => {} 1073 } 1074 } 1075 1076 // VRAM fill begins when an entry is popped from the FIFO after starting VRAM fill DMA 1077 // Writing to the FIFO after the DMA begins will update the data used for the fill 1078 self.state.vram_fill_data = Some(entry.word); 1079 1080 self.fifo.pop(); 1081 if !self.fifo.is_full() 1082 && let Some(pending_write) = self.pending_fifo_writes.pop_front() 1083 { 1084 self.push_fifo(pending_write); 1085 } 1086 1087 self.state.data_port_read_wait &= !self.fifo.is_empty(); 1088 1089 if self.state.display_enable_pending && self.fifo.is_empty() { 1090 // Display was re-enabled with a non-empty FIFO; actually enable it now 1091 self.state.display_enable_pending = false; 1092 self.registers.display_enabled = true; 1093 self.latched_registers.display_enabled = true; 1094 1095 self.sprite_state.handle_display_enabled_write( 1096 self.registers.horizontal_display_size, 1097 self.registers.display_enabled, 1098 self.state.pixel, 1099 ); 1100 1101 render_partial_line = true; 1102 } 1103 1104 if render_partial_line { 1105 self.maybe_render_partial_line(); 1106 } 1107 } 1108 1109 pub fn write_debug_register(&mut self, value: u16) { 1110 self.debug_register.write(value); 1111 1112 log::trace!("VDP debug register write: {:?}", self.debug_register); 1113 } 1114 1115 pub fn read_status(&mut self, m68k_opcode: u16, m68k_divider: u64) -> u16 { 1116 log::trace!("VDP status register read"); 1117 1118 let read_adjustment = Self::status_read_mclk_adjustment(m68k_opcode, m68k_divider); 1119 let mut scanline_mclk = self.state.scanline_mclk_cycles + read_adjustment; 1120 let HVCounter { internal_h, internal_v: v_counter, vblank_flag, .. } = 1121 self.hv_counter_internal(scanline_mclk); 1122 1123 let hblank_flag = !self 1124 .registers 1125 .horizontal_display_size 1126 .hblank_flag_clear_h_range() 1127 .contains(&internal_h); 1128 1129 if scanline_mclk >= MCLK_CYCLES_PER_SCANLINE { 1130 scanline_mclk -= MCLK_CYCLES_PER_SCANLINE; 1131 } 1132 1133 // It must be possible for VINT to read 1 before the 68000 handles the interrupt; several 1134 // games depend on this (e.g. Ex-Mutants and Tyrants: Fight Through Time) 1135 let active_scanlines = self.registers.vertical_display_size.active_scanlines(); 1136 let passed_vint = u16::from(v_counter) == active_scanlines && { 1137 let vint_mclk = self.registers.horizontal_display_size.v_interrupt_scanline_mclk(); 1138 let original_scanline_mclk = self.state.scanline_mclk_cycles; 1139 scanline_mclk >= vint_mclk 1140 && (original_scanline_mclk < vint_mclk || original_scanline_mclk > scanline_mclk) 1141 }; 1142 let vint_flag = self.state.v_interrupt_pending || passed_vint; 1143 1144 let status = (u16::from(self.fifo.is_empty()) << 9) 1145 | (u16::from(self.fifo.is_full()) << 8) 1146 | (u16::from(vint_flag) << 7) 1147 | (u16::from(self.sprite_state.overflow_flag()) << 6) 1148 | (u16::from(self.sprite_state.collision_flag()) << 5) 1149 | (u16::from(self.state.interlaced_odd) << 4) 1150 | (u16::from(vblank_flag || !self.registers.display_enabled) << 3) 1151 | (u16::from(hblank_flag) << 2) 1152 | (u16::from(self.control_port.dma_active) << 1) 1153 | u16::from(self.timing_mode == TimingMode::Pal); 1154 1155 // Reading status register clears the sprite overflow and collision flags 1156 self.sprite_state.clear_status_flags(); 1157 1158 // Reset control write flag on all status register reads 1159 self.control_port.write_flag = ControlWriteFlag::First; 1160 1161 status 1162 } 1163 1164 fn status_read_mclk_adjustment(m68k_opcode: u16, m68k_divider: u64) -> u64 { 1165 // Timing hack: When the CPU reads the status register or the HV counter, return values 1166 // from slightly in the future to account for the actual read occurring towards the end 1167 // of the instruction. 1168 // Using the same value for everything will break either Overdrive 1 (background on the 1169 // heart screen) or Overdrive 2 (plasma twisters). It needs to vary based on what instruction 1170 // is performing the read 1171 match m68000_emu::cycles_if_move_btst_cmp(m68k_opcode) { 1172 Some(cycles) => u64::from(cycles - 4) * m68k_divider, 1173 None => 8 * m68k_divider, 1174 } 1175 } 1176 1177 #[must_use] 1178 pub fn hv_counter(&self, m68k_opcode: u16, m68k_divider: u64) -> u16 { 1179 let read_adjustment = Self::status_read_mclk_adjustment(m68k_opcode, m68k_divider); 1180 let hv = self.hv_counter_internal(self.state.scanline_mclk_cycles + read_adjustment); 1181 1182 log::trace!( 1183 "HV counter read on scanline {}; H={:02X}, V={:02X}, internal H={:03X}", 1184 self.state.scanline, 1185 hv.hv_counter.lsb(), 1186 hv.hv_counter.msb(), 1187 hv.internal_h, 1188 ); 1189 1190 hv.hv_counter 1191 } 1192 1193 fn hv_counter_internal(&self, mut scanline_mclk: u64) -> HVCounter { 1194 let VCounter { counter: v_counter, vblank_flag } = self.v_counter(scanline_mclk); 1195 1196 if scanline_mclk >= MCLK_CYCLES_PER_SCANLINE { 1197 scanline_mclk -= MCLK_CYCLES_PER_SCANLINE; 1198 } 1199 let pixel = scanline_mclk_to_pixel(scanline_mclk, self.registers.horizontal_display_size); 1200 let internal_h = pixel_to_internal_h(pixel, self.registers.horizontal_display_size); 1201 1202 let hv_counter = self.state.latched_hv_counter.unwrap_or_else(|| { 1203 let h_counter = (internal_h >> 1) as u8; 1204 u16::from_be_bytes([v_counter, h_counter]) 1205 }); 1206 1207 HVCounter { internal_h, internal_v: v_counter, hv_counter, vblank_flag } 1208 } 1209 1210 #[inline] 1211 fn v_counter(&self, scanline_mclk: u64) -> VCounter { 1212 // Values from https://gendev.spritesmind.net/forum/viewtopic.php?t=768 1213 1214 // V counter increments for the next line when HINT is generated 1215 let in_hblank = 1216 scanline_mclk >= self.registers.horizontal_display_size.h_interrupt_scanline_mclk(); 1217 let scanline = if in_hblank { 1218 let scanlines_per_frame = self.scanlines_in_current_frame(); 1219 if self.state.scanline == scanlines_per_frame - 1 { 0 } else { self.state.scanline + 1 } 1220 } else { 1221 self.state.scanline 1222 }; 1223 1224 let active_scanlines = match self.timing_mode { 1225 TimingMode::Ntsc => VerticalDisplaySize::TwentyEightCell.active_scanlines(), 1226 TimingMode::Pal => self.registers.vertical_display_size.active_scanlines(), 1227 }; 1228 1229 let interlacing_mode = if self.state.interlaced_frame { 1230 self.registers.interlacing_mode 1231 } else { 1232 InterlacingMode::Progressive 1233 }; 1234 match interlacing_mode { 1235 InterlacingMode::Progressive => { 1236 let threshold = match (self.timing_mode, self.registers.vertical_display_size) { 1237 (TimingMode::Ntsc, _) => 0xEA, 1238 (TimingMode::Pal, VerticalDisplaySize::TwentyEightCell) => 0x102, 1239 (TimingMode::Pal, VerticalDisplaySize::ThirtyCell) => 0x10A, 1240 }; 1241 1242 let scanlines_per_frame = match self.timing_mode { 1243 TimingMode::Ntsc => NTSC_SCANLINES_PER_FRAME, 1244 TimingMode::Pal => PAL_SCANLINES_PER_FRAME, 1245 }; 1246 1247 let counter = if scanline <= threshold { 1248 scanline 1249 } else { 1250 scanline.wrapping_sub(scanlines_per_frame) & 0x1FF 1251 }; 1252 let vblank_flag = counter >= active_scanlines && counter != 0x1FF; 1253 VCounter { counter: counter as u8, vblank_flag } 1254 } 1255 InterlacingMode::Interlaced | InterlacingMode::InterlacedDouble => { 1256 let threshold = match (self.timing_mode, self.registers.vertical_display_size) { 1257 (TimingMode::Ntsc, _) => 0xEA, 1258 (TimingMode::Pal, VerticalDisplaySize::TwentyEightCell) => 0x101, 1259 (TimingMode::Pal, VerticalDisplaySize::ThirtyCell) => 0x109, 1260 }; 1261 let scanlines_per_frame = 1262 self.timing_mode.scanlines_per_frame(true, self.state.interlaced_odd); 1263 1264 let internal_counter = if scanline <= threshold { 1265 scanline 1266 } else { 1267 scanline.wrapping_sub(scanlines_per_frame) & 0x1FF 1268 }; 1269 let vblank_flag = internal_counter >= active_scanlines && internal_counter != 0x1FF; 1270 1271 let external_counter = match interlacing_mode { 1272 InterlacingMode::Interlaced => { 1273 (internal_counter & 0xFE) | ((internal_counter >> 8) & 1) 1274 } 1275 InterlacingMode::InterlacedDouble => { 1276 ((internal_counter << 1) & 0xFE) | ((internal_counter >> 7) & 1) 1277 } 1278 InterlacingMode::Progressive => unreachable!("nested matches"), 1279 }; 1280 1281 VCounter { counter: external_counter as u8, vblank_flag } 1282 } 1283 } 1284 } 1285 1286 #[allow(clippy::missing_panics_doc)] 1287 #[must_use] 1288 pub fn tick( 1289 &mut self, 1290 master_clock_cycles: u64, 1291 memory: &mut impl VdpBusView, 1292 ) -> VdpTickEffect { 1293 // The longest 68k instruction (DIVS (xxx).l, Dn) takes 172 68k cycles / 1204 mclk cycles 1294 assert!( 1295 master_clock_cycles < 1250, 1296 "VDP tick {master_clock_cycles} mclk cycles, expected <1250" 1297 ); 1298 1299 let mut tick_effect = VdpTickEffect::None; 1300 self.state.scanline_mclk_cycles += master_clock_cycles; 1301 if self.state.scanline_mclk_cycles >= MCLK_CYCLES_PER_SCANLINE { 1302 let end_of_line = self.registers.horizontal_display_size.pixels_including_hblank(); 1303 self.advance_to_pixel(end_of_line, memory); 1304 1305 tick_effect = self.advance_to_next_line(); 1306 } 1307 1308 let pixel = scanline_mclk_to_pixel( 1309 self.state.scanline_mclk_cycles, 1310 self.registers.horizontal_display_size, 1311 ); 1312 self.advance_to_pixel(pixel, memory); 1313 1314 tick_effect 1315 } 1316 1317 fn advance_to_pixel(&mut self, end_pixel: u16, memory: &mut impl VdpBusView) { 1318 let check_access_slots = self.control_port.dma_active || !self.fifo.is_empty(); 1319 match (self.registers.horizontal_display_size, check_access_slots) { 1320 (HorizontalDisplaySize::ThirtyTwoCell, false) => { 1321 self.advance_to_pixel_no_slot_check::<false>(end_pixel); 1322 } 1323 (HorizontalDisplaySize::ThirtyTwoCell, true) => { 1324 self.advance_to_pixel_check_slots::<false>(end_pixel, memory); 1325 } 1326 (HorizontalDisplaySize::FortyCell, false) => { 1327 self.advance_to_pixel_no_slot_check::<true>(end_pixel); 1328 } 1329 (HorizontalDisplaySize::FortyCell, true) => { 1330 self.advance_to_pixel_check_slots::<true>(end_pixel, memory); 1331 } 1332 } 1333 } 1334 1335 #[inline] 1336 fn advance_to_pixel_check_slots<const H40: bool>( 1337 &mut self, 1338 end_pixel: u16, 1339 memory: &mut impl VdpBusView, 1340 ) { 1341 // Slower loop - check for access slots for DMA/FIFO progress 1342 let access_slots = if H40 { H40_ACCESS_SLOTS } else { H32_ACCESS_SLOTS }; 1343 let blank_refresh_slots = 1344 if H40 { H40_BLANK_REFRESH_SLOTS } else { H32_BLANK_REFRESH_SLOTS }; 1345 1346 while self.state.pixel < end_pixel { 1347 let pixel = self.state.pixel; 1348 if !pixel.bit(0) { 1349 let slot_idx = (pixel >> 1) as u8; 1350 let in_blank_refresh_slot = blank_refresh_slots[slot_idx as usize]; 1351 1352 // TODO correct refresh slot locations for active display 1353 if !in_blank_refresh_slot { 1354 // Need to do DMA read before FIFO pop - reverse order breaks Overdrive 512-color 1355 self.progress_memory_to_vram_dma(slot_idx, blank_refresh_slots, memory); 1356 } 1357 1358 let blank = !self.registers.display_enabled || self.state.in_vblank; 1359 if (blank && !blank_refresh_slots[slot_idx as usize]) 1360 || (!blank && access_slots[slot_idx as usize]) 1361 { 1362 self.handle_access_slot(); 1363 } 1364 1365 // TODO correct refresh slot locations for active display 1366 if !in_blank_refresh_slot { 1367 self.fifo.decrement_latency(); 1368 } 1369 } 1370 1371 let internal_h = 1372 if H40 { pixel_to_internal_h_h40(pixel) } else { pixel_to_internal_h_h32(pixel) }; 1373 while internal_h >= self.vdp_event_times[self.vdp_event_idx as usize].h { 1374 let event = self.vdp_event_times[self.vdp_event_idx as usize].event; 1375 self.vdp_event_idx += 1; 1376 1377 self.handle_vdp_event(event); 1378 } 1379 1380 self.state.pixel += 1; 1381 } 1382 } 1383 1384 #[inline] 1385 fn advance_to_pixel_no_slot_check<const H40: bool>(&mut self, end_pixel: u16) { 1386 // Faster loop - only check for passed VDP events 1387 debug_assert!(!self.control_port.dma_active && self.fifo.is_empty()); 1388 1389 let end_internal_h = if H40 { 1390 pixel_to_internal_h_h40(end_pixel) 1391 } else { 1392 pixel_to_internal_h_h32(end_pixel) 1393 }; 1394 while end_internal_h >= self.vdp_event_times[self.vdp_event_idx as usize].h { 1395 let internal_h = self.vdp_event_times[self.vdp_event_idx as usize].h; 1396 let pixel = if H40 { 1397 internal_h_to_pixel_h40(internal_h) 1398 } else { 1399 internal_h_to_pixel_h32(internal_h) 1400 }; 1401 let event = self.vdp_event_times[self.vdp_event_idx as usize].event; 1402 self.vdp_event_idx += 1; 1403 1404 self.state.pixel = pixel; 1405 self.handle_vdp_event(event); 1406 } 1407 1408 self.state.pixel = end_pixel; 1409 } 1410 1411 fn handle_access_slot(&mut self) { 1412 // Video memory write slot 1413 // FIFO takes priority over VRAM fill/copy DMA 1414 if !self.fifo.is_empty() { 1415 if self.fifo.front().latency == 0 { 1416 self.pop_fifo(); 1417 } 1418 } else if self.control_port.dma_active { 1419 match self.registers.dma_mode { 1420 DmaMode::VramFill => self.progress_vram_fill_dma(), 1421 DmaMode::VramCopy => self.progress_vram_copy_dma(), 1422 DmaMode::MemoryToVram => {} 1423 } 1424 } 1425 } 1426 1427 fn progress_memory_to_vram_dma( 1428 &mut self, 1429 slot_idx: u8, 1430 refresh_slots: &[bool; 256], 1431 memory: &mut impl VdpBusView, 1432 ) { 1433 if !self.control_port.dma_active || self.registers.dma_mode != DmaMode::MemoryToVram { 1434 return; 1435 } 1436 1437 self.dma_latency = self.dma_latency.saturating_sub(1); 1438 1439 if self.fifo.is_full() { 1440 return; 1441 } 1442 1443 // Lose an extra read slot for every VRAM refresh slot 1444 // Direct color DMA demos depend on this 1445 let should_skip_read = slot_idx != 0 && refresh_slots[(slot_idx - 1) as usize]; 1446 if should_skip_read { 1447 return; 1448 } 1449 1450 let word = memory.read_word_for_dma(self.registers.dma_source_address); 1451 self.increment_dma_source_address(); 1452 1453 self.push_fifo(VdpFifoEntry { 1454 latency: cmp::max(self.dma_latency, fifo::INITIAL_FIFO_LATENCY), 1455 ..self.control_port.new_fifo_entry(word, self.registers.vram_size) 1456 }); 1457 self.control_port.increment_data_port_address(&self.registers); 1458 1459 self.decrement_dma_length(); 1460 } 1461 1462 fn progress_vram_fill_dma(&mut self) { 1463 let Some(fill_data) = self.state.vram_fill_data else { return }; 1464 1465 // VRAM fill increments source address on every write even though it does not use the address 1466 self.increment_dma_source_address(); 1467 1468 match self.control_port.location { 1469 DataPortLocation::Vram | DataPortLocation::Vram8Bit => { 1470 let byte = fill_data.msb(); 1471 let vram_addr = (self.control_port.data_port_address ^ 1) & 0xFFFF; 1472 self.vram[vram_addr as usize] = byte; 1473 self.maybe_update_sprite_cache(vram_addr, byte); 1474 } 1475 DataPortLocation::Cram => { 1476 // CRAM fill is bugged; uses the value from the next FIFO slot instead of fill data 1477 let word = self.fifo.next_slot_word(); 1478 let cram_addr = (self.control_port.data_port_address & 0x7F) >> 1; 1479 self.cram[cram_addr as usize] = word; 1480 } 1481 DataPortLocation::Vsram => { 1482 // VSRAM fill is bugged; uses the value from the next FIFO slot instead of fill data 1483 let word = self.fifo.next_slot_word(); 1484 let vsram_addr = ((self.control_port.data_port_address & 0x7F) >> 1) as usize; 1485 if vsram_addr < VSRAM_LEN_WORDS { 1486 self.vsram[vsram_addr] = word; 1487 } 1488 } 1489 DataPortLocation::Invalid => {} 1490 } 1491 1492 self.control_port.increment_data_port_address(&self.registers); 1493 self.decrement_dma_length(); 1494 } 1495 1496 fn progress_vram_copy_dma(&mut self) { 1497 self.state.vram_copy_odd_slot = !self.state.vram_copy_odd_slot; 1498 if self.state.vram_copy_odd_slot { 1499 return; 1500 } 1501 1502 let source_addr = ((self.registers.dma_source_address >> 1) ^ 1) & 0xFFFF; 1503 self.increment_dma_source_address(); 1504 1505 let dest_addr = (self.control_port.data_port_address ^ 1) & 0xFFFF; 1506 self.control_port.increment_data_port_address(&self.registers); 1507 1508 let byte = self.vram[source_addr as usize]; 1509 self.vram[dest_addr as usize] = byte; 1510 self.maybe_update_sprite_cache(dest_addr, byte); 1511 1512 self.decrement_dma_length(); 1513 } 1514 1515 fn increment_dma_source_address(&mut self) { 1516 // DMA source address always wraps within a 0x20000-byte block 1517 self.registers.dma_source_address = (self.registers.dma_source_address & !0x1FFFF) 1518 | (self.registers.dma_source_address.wrapping_add(2) & 0x1FFFF); 1519 } 1520 1521 fn decrement_dma_length(&mut self) { 1522 // Check for 0 after decrementing; an initial DMA length of 0 should function as 65536 1523 self.registers.dma_length = self.registers.dma_length.wrapping_sub(1); 1524 if self.registers.dma_length != 0 { 1525 return; 1526 } 1527 1528 self.control_port.dma_active = false; 1529 1530 // Apply any writes that occurred mid-DMA (e.g. from a MOVE.L instruction where the first 1531 // word started a DMA) 1532 if !self.state.pending_writes.is_empty() { 1533 self.apply_pending_writes(); 1534 } 1535 1536 log::trace!( 1537 "DMA of type {:?} complete at line {} mclk {}; FIFO len {}", 1538 self.registers.dma_mode, 1539 self.state.scanline, 1540 self.state.scanline_mclk_cycles, 1541 self.fifo.len() 1542 ); 1543 } 1544 1545 fn vdp_event_times(h_display_size: HorizontalDisplaySize) -> [VdpEventWithTime; VdpEvent::NUM] { 1546 let mut events = [ 1547 VdpEventWithTime::new(h_display_size.v_interrupt_h(), VdpEvent::VInterrupt), 1548 VdpEventWithTime::new(h_display_size.read_h_scroll_h(), VdpEvent::ReadHScroll), 1549 VdpEventWithTime::new( 1550 h_display_size.active_display_h_range().start, 1551 VdpEvent::RenderLine, 1552 ), 1553 VdpEventWithTime::new( 1554 h_display_size.fetch_sprite_attributes_h(), 1555 VdpEvent::FetchSpriteAttributes, 1556 ), 1557 VdpEventWithTime::new(h_display_size.hblank_begin_h(), VdpEvent::HBlankStart), 1558 VdpEventWithTime::new(h_display_size.h_interrupt_h(), VdpEvent::HInterrupt), 1559 VdpEventWithTime::new(h_display_size.latch_registers_h(), VdpEvent::LatchRegisters), 1560 VdpEventWithTime::new(u16::MAX, VdpEvent::None), 1561 ]; 1562 1563 events.sort_by_key(|event| event.h); 1564 1565 let count_occurrences = 1566 |event: VdpEvent| events.iter().filter(|e| e.event == event).count(); 1567 debug_assert!( 1568 VdpEvent::ALL.into_iter().all(|event| count_occurrences(event) == 1), 1569 "Every VdpEvent value must be present exactly once" 1570 ); 1571 1572 events 1573 } 1574 1575 fn handle_vdp_event(&mut self, event: VdpEvent) { 1576 match event { 1577 VdpEvent::VInterrupt => { 1578 let active_scanlines = self.registers.vertical_display_size.active_scanlines(); 1579 if self.state.scanline == active_scanlines { 1580 log::trace!("Generating V interrupt"); 1581 self.state.v_interrupt_pending = true; 1582 1583 // Latch H resolution at start of VBlank in case the game changes resolution 1584 // at start of VBlank, e.g. Bugs Bunny in Double Trouble 1585 self.state.frame_h_resolution = self.registers.horizontal_display_size; 1586 } 1587 } 1588 VdpEvent::ReadHScroll => { 1589 if !self.registers.display_enabled 1590 || (self.state.scanline 1591 >= self.latched_registers.vertical_display_size.active_scanlines() 1592 && !self.state.v_border_forgotten) 1593 { 1594 return; 1595 } 1596 1597 let raster_line = RasterLine::from_scanline( 1598 self.state.scanline, 1599 &self.latched_registers, 1600 self.timing_mode, 1601 self.state.interlaced_frame, 1602 self.state.interlaced_odd, 1603 ); 1604 1605 // Only the lowest 8 bits of raster line are used for H scroll lookups 1606 let h_scroll_scanline = raster_line.line & 0xFF; 1607 1608 self.latched_h_scroll = read_h_scroll( 1609 &self.vram, 1610 self.registers.h_scroll_table_base_addr, 1611 self.registers.horizontal_scroll_mode, 1612 h_scroll_scanline, 1613 ); 1614 } 1615 VdpEvent::RenderLine => { 1616 self.sprite_state 1617 .handle_line_end(self.registers.horizontal_display_size, self.state.pixel); 1618 1619 // Sprite processing phase 1 1620 // In actual hardware this takes place during HBlank 1621 log::trace!("Scanning sprites"); 1622 self.scan_sprites_one_line_ahead(); 1623 1624 // Render current line 1625 self.render_scanline(self.state.scanline, 0); 1626 } 1627 VdpEvent::FetchSpriteAttributes => { 1628 // Sprite processing phase 2 1629 // In actual hardware this takes place during active display 1630 log::trace!("Fetching sprite attributes"); 1631 self.fetch_sprite_attributes(); 1632 } 1633 VdpEvent::HBlankStart => { 1634 self.sprite_state.handle_hblank_start( 1635 self.registers.horizontal_display_size, 1636 self.registers.display_enabled, 1637 ); 1638 1639 let active_scanlines = self.registers.vertical_display_size.active_scanlines(); 1640 let scanlines_per_frame = self.scanlines_in_current_frame(); 1641 if self.state.scanline == active_scanlines - 1 1642 || (self.state.v_border_forgotten 1643 && self.state.scanline 1644 == VerticalDisplaySize::ThirtyCell.active_scanlines() - 1) 1645 { 1646 self.state.in_vblank = true; 1647 } else if self.state.scanline == scanlines_per_frame - 2 { 1648 self.state.in_vblank = false; 1649 } 1650 } 1651 VdpEvent::HInterrupt => { 1652 log::trace!("Latching cached sprite table"); 1653 self.latched_sprite_attributes 1654 .copy_from_slice(self.cached_sprite_attributes.as_ref()); 1655 1656 self.decrement_h_interrupt_counter(); 1657 } 1658 VdpEvent::LatchRegisters => { 1659 // Almost all VDP registers and the full screen V scroll values are latched within the 36 CPU cycles after 1660 // HINT is generated. Changing values after this point will not take effect until after the next scanline 1661 // is rendered. 1662 // The only VDP registers that are not latched are the nametable addresses, the display enabled bit, and 1663 // the background color. 1664 log::trace!("Latching VDP registers"); 1665 self.latched_registers = self.registers.clone(); 1666 self.latched_full_screen_v_scroll = (self.vsram[0], self.vsram[1]); 1667 } 1668 VdpEvent::None => {} 1669 } 1670 } 1671 1672 fn decrement_h_interrupt_counter(&mut self) { 1673 let active_scanlines = self.registers.vertical_display_size.active_scanlines(); 1674 let scanlines_per_frame = self.scanlines_in_current_frame(); 1675 1676 if self.state.scanline < active_scanlines 1677 || self.state.scanline == scanlines_per_frame - 1 1678 || self.state.v_border_forgotten 1679 { 1680 if self.state.h_interrupt_counter == 0 { 1681 self.state.h_interrupt_counter = self.registers.h_interrupt_interval; 1682 1683 log::trace!("Generating H interrupt (scanline {})", self.state.scanline); 1684 self.state.h_interrupt_pending = true; 1685 } else { 1686 self.state.h_interrupt_counter -= 1; 1687 } 1688 } else { 1689 // H interrupt counter is constantly refreshed during VBlank 1690 self.state.h_interrupt_counter = self.registers.h_interrupt_interval; 1691 } 1692 } 1693 1694 fn advance_to_next_line(&mut self) -> VdpTickEffect { 1695 let scanlines_per_frame = self.scanlines_in_current_frame(); 1696 1697 self.cram_dots.swap_buffers_if_needed(); 1698 1699 self.state.scanline_mclk_cycles -= MCLK_CYCLES_PER_SCANLINE; 1700 self.vdp_event_idx = 0; 1701 self.state.scanline += 1; 1702 self.state.pixel = 0; 1703 if self.state.scanline == scanlines_per_frame { 1704 self.state.scanline = 0; 1705 self.state.v_border_forgotten = false; 1706 1707 let next_frame_interlaced = matches!( 1708 self.registers.interlacing_mode, 1709 InterlacingMode::Interlaced | InterlacingMode::InterlacedDouble 1710 ); 1711 1712 if next_frame_interlaced && !self.state.interlaced_frame { 1713 self.state.interlaced_odd = false; 1714 1715 if !self.config.deinterlace { 1716 self.prepare_frame_buffer_for_interlaced(); 1717 } 1718 } 1719 1720 self.state.interlaced_frame = next_frame_interlaced; 1721 1722 // Top border length needs to be saved at start-of-frame in case there is a mid-frame swap between V28 1723 // mode and V30 mode. Titan Overdrive 2 depends on this for the arcade scene 1724 self.state.top_border = 1725 self.registers.vertical_display_size.top_border(self.timing_mode); 1726 1727 // Re-latch H display mode at start of frame in case a game changed it mid-VBlank 1728 // Not doing this causes a glitchy frame on mode switches 1729 self.state.frame_h_resolution = self.registers.horizontal_display_size; 1730 } else if self.state.interlaced_frame { 1731 let toggle_odd_line = match self.registers.vertical_display_size { 1732 VerticalDisplaySize::TwentyEightCell => 240, 1733 VerticalDisplaySize::ThirtyCell => 256, 1734 }; 1735 if self.state.scanline == toggle_odd_line { 1736 // TODO this actually happens at H=0x001 or H=0x002 1737 self.state.interlaced_odd = !self.state.interlaced_odd; 1738 } 1739 } 1740 1741 let last_scanline_of_frame = 1742 self.timing_mode.rendered_lines_per_frame() - self.state.top_border; 1743 if self.state.scanline == last_scanline_of_frame { 1744 VdpTickEffect::FrameComplete 1745 } else { 1746 VdpTickEffect::None 1747 } 1748 } 1749 1750 fn apply_pending_writes(&mut self) { 1751 let mut pending_writes = [PendingWrite::default(); 10]; 1752 let pending_writes_len = self.state.pending_writes.len(); 1753 pending_writes[..pending_writes_len].copy_from_slice(&self.state.pending_writes); 1754 self.state.pending_writes.clear(); 1755 1756 for &pending_write in &pending_writes[..pending_writes_len] { 1757 match pending_write { 1758 PendingWrite::Control(value) => { 1759 self.write_control(value); 1760 } 1761 PendingWrite::Data(value) => { 1762 self.write_data(value); 1763 } 1764 } 1765 } 1766 } 1767 1768 #[inline] 1769 fn maybe_update_sprite_cache(&mut self, address: u32, value: u8) { 1770 if address.bit(2) { 1771 // Second 4 bytes of each sprite entry are not cached 1772 return; 1773 } 1774 1775 let sprite_table_addr = self.registers.masked_sprite_attribute_table_addr(); 1776 let h_size = self.registers.horizontal_display_size; 1777 1778 let sprite_table_end = sprite_table_addr + 8 * h_size.sprite_table_len(); 1779 if !(sprite_table_addr..sprite_table_end).contains(&address) { 1780 // Address is not in sprite table 1781 return; 1782 } 1783 1784 let idx = ((address - sprite_table_addr) / 8) as usize; 1785 match address & 0x3 { 1786 0x0 => self.cached_sprite_attributes[idx].update_first_word_msb(value), 1787 0x1 => self.cached_sprite_attributes[idx].update_first_word_lsb(value), 1788 0x2 => self.cached_sprite_attributes[idx].update_second_word_msb(value), 1789 0x3 => self.cached_sprite_attributes[idx].update_second_word_lsb(value), 1790 _ => unreachable!("value & 0x3 is always <= 0x3"), 1791 } 1792 } 1793 1794 fn prepare_frame_buffer_for_interlaced(&mut self) { 1795 // Duplicate every line to avoid a flickering frame if a game enables interlacing without 1796 // first blanking the screen 1797 let screen_width = self.screen_width(); 1798 let screen_height = self.screen_height(); 1799 for scanline in (0..screen_height).rev() { 1800 for pixel in 0..screen_width { 1801 let color = self.frame_buffer[(scanline * screen_width + pixel) as usize]; 1802 self.frame_buffer[((2 * scanline) * screen_width + pixel) as usize] = color; 1803 self.frame_buffer[((2 * scanline + 1) * screen_width + pixel) as usize] = color; 1804 } 1805 } 1806 } 1807 1808 #[must_use] 1809 pub fn m68k_interrupt_level(&self) -> u8 { 1810 // TODO external interrupts at level 2 1811 if self.state.v_interrupt_pending && self.state.v_interrupt_enabled_latch { 1812 6 1813 } else if self.state.h_interrupt_pending && self.state.h_interrupt_enabled_latch { 1814 4 1815 } else { 1816 0 1817 } 1818 } 1819 1820 #[inline] 1821 pub fn update_interrupt_latches(&mut self) { 1822 self.state.v_interrupt_enabled_latch = self.registers.v_interrupt_enabled; 1823 self.state.h_interrupt_enabled_latch = self.registers.h_interrupt_enabled; 1824 } 1825 1826 pub fn acknowledge_m68k_interrupt(&mut self) { 1827 let interrupt_level = self.m68k_interrupt_level(); 1828 log::trace!("M68K interrupt acknowledged; level {interrupt_level}"); 1829 if interrupt_level == 6 { 1830 self.state.v_interrupt_pending = false; 1831 } else if interrupt_level == 4 { 1832 self.state.h_interrupt_pending = false; 1833 } 1834 } 1835 1836 #[inline] 1837 #[must_use] 1838 pub fn should_halt_cpu(&self) -> bool { 1839 (self.control_port.dma_active && self.registers.dma_mode == DmaMode::MemoryToVram) 1840 || self.state.data_port_read_wait 1841 || !self.pending_fifo_writes.is_empty() 1842 } 1843 1844 #[inline] 1845 #[must_use] 1846 pub fn long_halting_dma_in_progress(&self) -> bool { 1847 self.control_port.dma_active 1848 && self.registers.dma_mode == DmaMode::MemoryToVram 1849 && self.registers.dma_length 1850 >= self.registers.horizontal_display_size.access_slots_per_blank_line() 1851 } 1852 1853 #[inline] 1854 #[must_use] 1855 pub fn z80_interrupt_line(&self) -> InterruptLine { 1856 // Z80 INT line is low only during the first scanline of VBlank 1857 if self.state.scanline == self.registers.vertical_display_size.active_scanlines() { 1858 InterruptLine::Low 1859 } else { 1860 InterruptLine::High 1861 } 1862 } 1863 1864 fn scan_sprites_one_line_ahead(&mut self) { 1865 let scanlines_per_frame = self.scanlines_in_current_frame(); 1866 let scanline_for_sprite_scan = if self.state.scanline == scanlines_per_frame - 1 { 1867 0 1868 } else { 1869 self.state.scanline + 1 1870 }; 1871 1872 self.scan_sprites(scanline_for_sprite_scan); 1873 } 1874 1875 #[inline] 1876 #[must_use] 1877 pub fn frame_buffer(&self) -> &[Color; FRAME_BUFFER_LEN] { 1878 &self.frame_buffer 1879 } 1880 1881 #[inline] 1882 #[must_use] 1883 pub fn frame_buffer_mut(&mut self) -> &mut [Color; FRAME_BUFFER_LEN] { 1884 &mut self.frame_buffer 1885 } 1886 1887 #[inline] 1888 #[must_use] 1889 pub fn frame_size(&self) -> FrameSize { 1890 FrameSize { width: self.screen_width(), height: self.screen_height() } 1891 } 1892 1893 #[inline] 1894 #[must_use] 1895 pub fn composite_params(&self) -> CompositeParams { 1896 let upscale_factor = match self.state.frame_h_resolution { 1897 HorizontalDisplaySize::ThirtyTwoCell => 10, 1898 HorizontalDisplaySize::FortyCell => 8, 1899 }; 1900 1901 CompositeParams { upscale_factor, samples_per_color_cycle: SamplesPerColorCycle::Fifteen } 1902 } 1903 1904 #[inline] 1905 #[must_use] 1906 pub fn scanlines_in_current_frame(&self) -> u16 { 1907 self.timing_mode.scanlines_per_frame(self.state.interlaced_frame, self.state.interlaced_odd) 1908 } 1909 1910 #[inline] 1911 #[must_use] 1912 pub fn is_interlaced_frame(&self) -> bool { 1913 self.state.interlaced_frame 1914 } 1915 1916 #[inline] 1917 #[must_use] 1918 pub fn is_interlaced_odd(&self) -> bool { 1919 self.state.interlaced_frame && self.state.interlaced_odd 1920 } 1921 1922 #[inline] 1923 #[must_use] 1924 pub fn average_scanlines_per_frame(&self) -> f64 { 1925 let interlaced_frame = self.state.interlaced_frame; 1926 match self.timing_mode { 1927 TimingMode::Ntsc => { 1928 f64::from(NTSC_SCANLINES_PER_FRAME) + 0.5 * f64::from(interlaced_frame) 1929 } 1930 TimingMode::Pal => { 1931 f64::from(PAL_SCANLINES_PER_FRAME) - 0.5 * f64::from(interlaced_frame) 1932 } 1933 } 1934 } 1935 1936 #[inline] 1937 #[must_use] 1938 pub fn screen_width(&self) -> u32 { 1939 let h_display_size = self.state.frame_h_resolution; 1940 let active_display_pixels: u32 = h_display_size.active_display_pixels().into(); 1941 1942 if self.config.render_horizontal_border { 1943 u32::from(h_display_size.left_border()) 1944 + active_display_pixels 1945 + u32::from(RIGHT_BORDER) 1946 } else { 1947 active_display_pixels 1948 } 1949 } 1950 1951 #[inline] 1952 #[must_use] 1953 pub fn screen_height(&self) -> u32 { 1954 let screen_height: u32 = if self.config.render_vertical_border { 1955 self.timing_mode.rendered_lines_per_frame().into() 1956 } else { 1957 self.registers.vertical_display_size.active_scanlines().into() 1958 }; 1959 1960 if self.state.interlaced_frame { 2 * screen_height } else { screen_height } 1961 } 1962 1963 #[inline] 1964 #[must_use] 1965 pub fn border_size(&self) -> BorderSize { 1966 let (left, right) = if self.config.render_horizontal_border { 1967 let h_display_size = self.state.frame_h_resolution; 1968 (h_display_size.left_border(), RIGHT_BORDER) 1969 } else { 1970 (0, 0) 1971 }; 1972 1973 let (top, bottom) = if self.config.render_vertical_border { 1974 let v_display_size = self.registers.vertical_display_size; 1975 ( 1976 v_display_size.top_border(self.timing_mode), 1977 v_display_size.bottom_border(self.timing_mode), 1978 ) 1979 } else { 1980 (0, 0) 1981 }; 1982 1983 BorderSize { 1984 left: left.into(), 1985 right: right.into(), 1986 top: top.into(), 1987 bottom: bottom.into(), 1988 } 1989 } 1990 1991 #[inline] 1992 #[must_use] 1993 pub fn config(&self) -> VdpConfig { 1994 self.config 1995 } 1996 1997 #[inline] 1998 pub fn reload_config(&mut self, config: VdpConfig) { 1999 self.config = config; 2000 self.color_tables = ColorTables::from_config(&self.config); 2001 } 2002 2003 #[inline] 2004 #[must_use] 2005 pub fn scanline(&self) -> u16 { 2006 self.state.scanline 2007 } 2008 2009 #[inline] 2010 #[must_use] 2011 pub fn scanline_mclk(&self) -> u64 { 2012 self.state.scanline_mclk_cycles 2013 } 2014 2015 #[inline] 2016 #[must_use] 2017 pub fn timing_mode(&self) -> TimingMode { 2018 self.timing_mode 2019 } 2020} 2021 2022fn read_h_scroll( 2023 vram: &Vram, 2024 h_scroll_table_addr: u16, 2025 h_scroll_mode: HorizontalScrollMode, 2026 scanline: u16, 2027) -> (u16, u16) { 2028 let h_scroll_addr = match h_scroll_mode { 2029 HorizontalScrollMode::FullScreen => h_scroll_table_addr, 2030 HorizontalScrollMode::Cell => h_scroll_table_addr.wrapping_add(32 * (scanline / 8)), 2031 HorizontalScrollMode::Line => h_scroll_table_addr.wrapping_add(4 * scanline), 2032 HorizontalScrollMode::Invalid => h_scroll_table_addr.wrapping_add(4 * (scanline & 0x7)), 2033 }; 2034 2035 let h_scroll_a = 2036 u16::from_be_bytes([vram[h_scroll_addr as usize], vram[(h_scroll_addr + 1) as usize]]); 2037 let h_scroll_b = u16::from_be_bytes([ 2038 vram[(h_scroll_addr + 2) as usize], 2039 vram[(h_scroll_addr + 3) as usize], 2040 ]); 2041 2042 (h_scroll_a & 0x03FF, h_scroll_b & 0x03FF) 2043} 2044 2045fn convert_128kb_vram_address(address: u32) -> u32 { 2046 // Formula from https://plutiedev.com/mirror/kabuto-hardware-notes#128k-abuse 2047 (((address & 0x2) ^ 0x2) >> 1) 2048 | ((address & 0x400) >> 9) 2049 | (address & 0x3FC) 2050 | ((address & 0x1F800) >> 1) 2051} 2052 2053fn scanline_mclk_to_pixel(scanline_mclk: u64, h_display_size: HorizontalDisplaySize) -> u16 { 2054 match h_display_size { 2055 HorizontalDisplaySize::ThirtyTwoCell => scanline_mclk_to_pixel_h32(scanline_mclk), 2056 HorizontalDisplaySize::FortyCell => scanline_mclk_to_pixel_h40(scanline_mclk), 2057 } 2058} 2059 2060fn pixel_to_internal_h(pixel: u16, h_display_size: HorizontalDisplaySize) -> u16 { 2061 match h_display_size { 2062 HorizontalDisplaySize::ThirtyTwoCell => pixel_to_internal_h_h32(pixel), 2063 HorizontalDisplaySize::FortyCell => pixel_to_internal_h_h40(pixel), 2064 } 2065} 2066 2067fn scanline_mclk_to_pixel_h32(scanline_mclk: u64) -> u16 { 2068 // H32 pixel clock is always mclk/10 2069 (scanline_mclk / 10) as u16 2070} 2071 2072fn pixel_to_internal_h_h32(pixel: u16) -> u16 { 2073 if pixel <= 0x127 { pixel } else { pixel + (0x1D2 - 0x128) } 2074} 2075 2076fn internal_h_to_pixel_h32(internal_h: u16) -> u16 { 2077 if internal_h <= 0x127 { internal_h } else { internal_h - (0x1D2 - 0x128) } 2078} 2079 2080fn scanline_mclk_to_pixel_h40(scanline_mclk: u64) -> u16 { 2081 // Note H jumps 0x16C to 0x1C9 right before HSYNC 2082 const JUMP_DIFF: u64 = 0x1C9 - 0x16D; 2083 2084 // Special cases due to pixel clock varying during HSYNC in H40 mode 2085 // https://gendev.spritesmind.net/forum/viewtopic.php?t=3221 2086 2087 // Pixel clock is mclk/8 from H=0x000 through H=0x1CB 2088 if scanline_mclk < (0x1CC - JUMP_DIFF) * 8 { 2089 return (scanline_mclk / 8) as u16; 2090 } 2091 2092 // From H=0x1CC through H=0x1ED, follows this pattern, repeated twice: 2093 // 1 mclk/8, 7 mclk/10, 2 mclk/9, 7 mclk/10 2094 let hsync_start_mclk = (0x1CC - JUMP_DIFF) * 8; 2095 let hsync_end_mclk = hsync_start_mclk + 2 * (8 + 7 * 10 + 2 * 9 + 7 * 10); 2096 if (hsync_start_mclk..hsync_end_mclk).contains(&scanline_mclk) { 2097 let hsync_mclk = scanline_mclk - hsync_start_mclk; 2098 let pattern_pixel = match hsync_mclk % (8 + 7 * 10 + 2 * 9 + 7 * 10) { 2099 // 1 pixel at mclk/8 2100 0..=7 => 0, 2101 // 7 pixels at mclk/10 2102 pattern_mclk @ 8..=77 => 1 + (pattern_mclk - 8) / 10, 2103 // 2 pixels at mclk/9 (effectively) 2104 pattern_mclk @ 78..=95 => 8 + (pattern_mclk - 78) / 9, 2105 // 7 pixels at mclk/10 2106 pattern_mclk @ 96..=165 => 10 + (pattern_mclk - 96) / 10, 2107 _ => unreachable!("value % 166 is always < 166"), 2108 }; 2109 2110 return if hsync_mclk < 166 { 2111 // First repetition 2112 (0x1CC - JUMP_DIFF + pattern_pixel) as u16 2113 } else { 2114 // Second repetition 2115 (0x1CC - JUMP_DIFF + 17 + pattern_pixel) as u16 2116 }; 2117 } 2118 2119 // From H=0x1EE to H=0x1FF, stays at mclk/8 2120 let post_hsync_mclk = scanline_mclk - hsync_end_mclk; 2121 (0x1CC - JUMP_DIFF + 34 + post_hsync_mclk / 8) as u16 2122} 2123 2124fn pixel_to_internal_h_h40(pixel: u16) -> u16 { 2125 if pixel <= 0x16C { pixel } else { pixel + (0x1C9 - 0x16D) } 2126} 2127 2128fn internal_h_to_pixel_h40(internal_h: u16) -> u16 { 2129 if internal_h <= 0x16C { internal_h } else { internal_h - (0x1C9 - 0x16D) } 2130}