1//! Sega Master System / Game Gear VDP (video display processor) 2//! 3//! The SMS and GG VDPs are nearly identical, with only a few minor differences: 4//! * SMS VDP renders 256x192 frames; GG VDP also renders 256x192 but only displays the center 160x144 5//! * SMS VDP has 32 bytes of CRAM and uses 6-bit RGB color; GG VDP has 32 _words_ of CRAM and uses 12-bit RGB color 6 7mod debug; 8mod tms9918; 9 10use crate::{SmsGgEmulatorConfig, SmsGgHardware}; 11use bincode::{Decode, Encode}; 12use jgenesis_common::boxedarray::BoxedWordArray; 13use jgenesis_common::frontend::{Color, TimingMode}; 14use jgenesis_common::num::{GetBit, U16Ext}; 15use jgenesis_proc_macros::EnumDisplay; 16use std::array; 17use std::fmt::{Display, Formatter}; 18use z80_emu::traits::InterruptLine; 19 20const VRAM_LEN: usize = 16 * 1024; 21const COLOR_RAM_LEN: usize = 64; 22 23pub const SCREEN_WIDTH: u16 = 256; 24pub const SCREEN_HEIGHT: u16 = 240; 25pub const FRAME_BUFFER_LEN: usize = SCREEN_WIDTH as usize * SCREEN_HEIGHT as usize; 26 27// Data address is 14 bits 28const DATA_ADDRESS_MASK: u16 = 0x3FFF; 29 30pub const DOTS_PER_SCANLINE: u16 = 342; 31pub const MCLK_CYCLES_PER_SCANLINE: u16 = 10 * DOTS_PER_SCANLINE; 32 33pub const NTSC_SCANLINES_PER_FRAME: u16 = 262; 34pub const PAL_SCANLINES_PER_FRAME: u16 = 313; 35 36type Vram = [u8; VRAM_LEN]; 37 38trait TimingModeExt { 39 fn scanlines_per_frame(self) -> u16; 40} 41 42impl TimingModeExt for TimingMode { 43 fn scanlines_per_frame(self) -> u16 { 44 match self { 45 Self::Ntsc => NTSC_SCANLINES_PER_FRAME, 46 Self::Pal => PAL_SCANLINES_PER_FRAME, 47 } 48 } 49} 50 51#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 52pub struct ViewportSize { 53 pub width: u16, 54 pub height: u16, 55 pub top: u16, 56 pub left: u16, 57 pub top_border_height: u16, 58 pub bottom_border_height: u16, 59 pub left_border_width: u16, 60} 61 62impl ViewportSize { 63 const NTSC_SMS: Self = Self { 64 width: 256, 65 height: 224, 66 top: 0, 67 left: 0, 68 top_border_height: 16, 69 bottom_border_height: 16, 70 left_border_width: 8, 71 }; 72 73 const PAL_SMS: Self = Self { 74 width: 256, 75 height: 240, 76 top: 0, 77 left: 0, 78 top_border_height: 24, 79 bottom_border_height: 24, 80 left_border_width: 8, 81 }; 82 83 const GAME_GEAR: Self = Self { 84 width: 160, 85 height: 144, 86 top: 24, 87 left: 48, 88 top_border_height: 0, 89 bottom_border_height: 0, 90 left_border_width: 0, 91 }; 92 93 const GAME_GEAR_EXPANDED: Self = Self { 94 width: 256, 95 height: 192, 96 top: 0, 97 left: 0, 98 top_border_height: 0, 99 bottom_border_height: 0, 100 left_border_width: 8, 101 }; 102 103 pub fn height_without_border(self) -> u16 { 104 self.height - self.top_border_height - self.bottom_border_height 105 } 106 107 pub fn width_without_border(self) -> u16 { 108 self.width - self.left_border_width 109 } 110} 111 112#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode, EnumDisplay)] 113pub enum VdpVersion { 114 NtscMasterSystem1, 115 PalMasterSystem1, 116 #[default] 117 NtscMasterSystem2, 118 PalMasterSystem2, 119 GameGear, 120 NtscSg1000, 121 PalSg1000, 122} 123 124impl VdpVersion { 125 #[must_use] 126 pub fn hardware(self) -> SmsGgHardware { 127 match self { 128 Self::NtscSg1000 | Self::PalSg1000 => SmsGgHardware::Sg1000, 129 Self::NtscMasterSystem1 130 | Self::NtscMasterSystem2 131 | Self::PalMasterSystem1 132 | Self::PalMasterSystem2 => SmsGgHardware::MasterSystem, 133 Self::GameGear => SmsGgHardware::GameGear, 134 } 135 } 136 137 fn is_sms1(self) -> bool { 138 matches!(self, Self::NtscMasterSystem1 | Self::PalMasterSystem1) 139 } 140 141 #[must_use] 142 pub fn timing_mode(self) -> TimingMode { 143 match self { 144 Self::NtscMasterSystem1 145 | Self::NtscMasterSystem2 146 | Self::GameGear 147 | Self::NtscSg1000 => TimingMode::Ntsc, 148 Self::PalMasterSystem1 | Self::PalMasterSystem2 | Self::PalSg1000 => TimingMode::Pal, 149 } 150 } 151 152 #[must_use] 153 const fn viewport_size(self, gg_use_sms_resolution: bool, mode: Mode) -> ViewportSize { 154 let mut viewport = match self { 155 Self::NtscMasterSystem1 | Self::NtscMasterSystem2 | Self::NtscSg1000 => { 156 ViewportSize::NTSC_SMS 157 } 158 Self::PalMasterSystem1 | Self::PalMasterSystem2 | Self::PalSg1000 => { 159 ViewportSize::PAL_SMS 160 } 161 Self::GameGear => { 162 if gg_use_sms_resolution { 163 ViewportSize::GAME_GEAR_EXPANDED 164 } else { 165 ViewportSize::GAME_GEAR 166 } 167 } 168 }; 169 170 if matches!(mode, Mode::Four224Line) { 171 match self { 172 Self::NtscSg1000 | Self::PalSg1000 => {} 173 Self::NtscMasterSystem1 174 | Self::NtscMasterSystem2 175 | Self::PalMasterSystem1 176 | Self::PalMasterSystem2 => { 177 viewport.top_border_height -= 16; 178 viewport.bottom_border_height -= 16; 179 } 180 Self::GameGear => { 181 if !gg_use_sms_resolution { 182 viewport.top += 16; 183 } 184 } 185 } 186 } 187 188 viewport 189 } 190} 191 192#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 193enum ControlWriteFlag { 194 First, 195 Second, 196} 197 198impl ControlWriteFlag { 199 fn toggle(self) -> Self { 200 match self { 201 Self::First => Self::Second, 202 Self::Second => Self::First, 203 } 204 } 205} 206 207#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 208enum DataWriteLocation { 209 Vram, 210 Cram, 211} 212 213#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 214enum Mode { 215 #[default] 216 Four, 217 Four224Line, 218 Text, 219 GraphicsI, 220 GraphicsII, 221 Multicolor, 222} 223 224impl Display for Mode { 225 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 226 match self { 227 Self::Four => write!(f, "4"), 228 Self::Four224Line => write!(f, "4 (224-line)"), 229 Self::Text => write!(f, "Text"), 230 Self::GraphicsI => write!(f, "Graphics I"), 231 Self::GraphicsII => write!(f, "Graphics II"), 232 Self::Multicolor => write!(f, "Multicolor"), 233 } 234 } 235} 236 237impl Mode { 238 fn from_mode_bits(hardware: SmsGgHardware, mode_bits: [bool; 4]) -> Self { 239 let mode = match mode_bits { 240 [false, _, false, true] | [false, false, true, true] | [true, true, true, true] => { 241 Self::Four 242 } 243 [true, true, false, true] => Self::Four224Line, 244 [false, false, false, false] => Self::GraphicsI, 245 [true, false, false, false] => Self::Text, 246 [false, true, false, false] => Self::GraphicsII, 247 [false, false, true, false] => Self::Multicolor, 248 _ => { 249 log::debug!("Unsupported mode, defaulting to mode 4: {mode_bits:?}"); 250 Self::Four 251 } 252 }; 253 254 match (hardware, mode) { 255 (SmsGgHardware::Sg1000, Mode::Four | Mode::Four224Line) => { 256 // Invalid; just render in text mode 257 Mode::Text 258 } 259 _ => mode, 260 } 261 } 262 263 const fn name_table_rows(self) -> u16 { 264 match self { 265 Self::Four224Line => 32, 266 _ => 28, 267 } 268 } 269 270 const fn active_scanlines(self) -> u16 { 271 match self { 272 Self::Four224Line => 224, 273 _ => 192, 274 } 275 } 276} 277 278#[derive(Debug, Clone, Copy, Encode, Decode)] 279struct SpriteRegisters { 280 double_sprite_height: bool, 281 double_sprite_size: bool, 282 shift_sprites_left: bool, 283 base_sprite_table_address: u16, 284 base_sprite_pattern_address: u16, 285} 286 287impl SpriteRegisters { 288 fn new() -> Self { 289 Self { 290 double_sprite_height: false, 291 double_sprite_size: false, 292 shift_sprites_left: false, 293 base_sprite_table_address: 0x3F00, 294 base_sprite_pattern_address: 0x2000, 295 } 296 } 297 298 fn sprite_height(self) -> u8 { 299 match (self.double_sprite_size, self.double_sprite_height) { 300 (true, true) => 32, 301 (true, false) | (false, true) => 16, 302 (false, false) => 8, 303 } 304 } 305 306 fn sprite_width(self) -> u8 { 307 if self.double_sprite_size { 16 } else { 8 } 308 } 309} 310 311#[derive(Debug, Clone, Encode, Decode)] 312struct Registers { 313 version: VdpVersion, 314 mode: Mode, 315 mode_bits: [bool; 4], 316 sprite: SpriteRegisters, 317 latched_sprite: SpriteRegisters, 318 control_write_flag: ControlWriteFlag, 319 latched_control_byte: u8, 320 data_write_location: DataWriteLocation, 321 data_address: u16, 322 data_read_buffer: u8, 323 cram_write_latch: u8, 324 display_enabled: bool, 325 frame_interrupt_enabled: bool, 326 frame_interrupt_pending: bool, 327 frame_interrupt_flag: bool, 328 line_interrupt_enabled: bool, 329 line_interrupt_pending: bool, 330 sprite_overflow: bool, 331 sprite_collision: bool, 332 tms9918_5th_sprite: u8, 333 vertical_scroll_lock: bool, 334 horizontal_scroll_lock: bool, 335 hide_left_column: bool, 336 base_name_table_address: u16, 337 name_table_address_mask: u16, 338 backdrop_color: u8, 339 x_scroll: u8, 340 y_scroll: u8, 341 line_counter_reload_value: u8, 342 // Registers used only in legacy TMS9918 modes 343 color_table_address: u16, 344 pattern_generator_address: u16, 345 text_mode_color_1: u8, 346} 347 348impl Registers { 349 fn new(version: VdpVersion) -> Self { 350 Self { 351 version, 352 mode: Mode::Four, 353 mode_bits: [false, false, false, true], 354 sprite: SpriteRegisters::new(), 355 latched_sprite: SpriteRegisters::new(), 356 control_write_flag: ControlWriteFlag::First, 357 latched_control_byte: 0, 358 data_write_location: DataWriteLocation::Vram, 359 data_address: 0, 360 data_read_buffer: 0, 361 cram_write_latch: 0, 362 display_enabled: false, 363 frame_interrupt_enabled: false, 364 frame_interrupt_pending: false, 365 frame_interrupt_flag: false, 366 line_interrupt_enabled: false, 367 line_interrupt_pending: false, 368 sprite_overflow: false, 369 sprite_collision: false, 370 tms9918_5th_sprite: 0, 371 vertical_scroll_lock: false, 372 horizontal_scroll_lock: false, 373 hide_left_column: false, 374 base_name_table_address: 0x3800, 375 name_table_address_mask: 0xFFFF, 376 backdrop_color: 0, 377 x_scroll: 0, 378 y_scroll: 0, 379 line_counter_reload_value: 0, 380 color_table_address: 0, 381 pattern_generator_address: 0, 382 text_mode_color_1: 0, 383 } 384 } 385 386 fn read_control(&mut self) -> u8 { 387 let status_flags = (u8::from(self.frame_interrupt_flag) << 7) 388 | (u8::from(self.sprite_overflow) << 6) 389 | (u8::from(self.sprite_collision) << 5); 390 391 let fifth_sprite = 392 if self.sprite_overflow && self.version.hardware() == SmsGgHardware::Sg1000 { 393 self.tms9918_5th_sprite 394 } else { 395 0 396 }; 397 398 // Control reads clear all status/interrupt flags and reset the control write toggle 399 self.frame_interrupt_pending = false; 400 self.frame_interrupt_flag = false; 401 self.line_interrupt_pending = false; 402 self.sprite_overflow = false; 403 self.sprite_collision = false; 404 self.control_write_flag = ControlWriteFlag::First; 405 406 status_flags | fifth_sprite 407 } 408 409 fn write_control(&mut self, value: u8, vram: &Vram) { 410 let write_flag = self.control_write_flag; 411 412 log::trace!("VDP control write with flag {write_flag:?}"); 413 414 match write_flag { 415 ControlWriteFlag::First => { 416 self.latched_control_byte = value; 417 418 // Set low byte of data address 419 self.data_address.set_lsb(value); 420 } 421 ControlWriteFlag::Second => { 422 self.data_address.set_msb(value & 0x3F); 423 424 log::trace!("VRAM address set to {:04X?}", self.data_address); 425 426 match value & 0xC0 { 427 0x00 => { 428 // VRAM read 429 self.data_read_buffer = vram[self.data_address as usize]; 430 self.data_address = (self.data_address + 1) & DATA_ADDRESS_MASK; 431 432 self.data_write_location = DataWriteLocation::Vram; 433 434 log::trace!("VRAM read"); 435 } 436 0x40 => { 437 // VRAM write 438 self.data_write_location = DataWriteLocation::Vram; 439 440 log::trace!("VRAM write"); 441 } 442 0x80 => { 443 // Internal register write 444 let register = value & 0x0F; 445 self.write_internal_register(register, self.latched_control_byte); 446 447 self.data_write_location = DataWriteLocation::Vram; 448 449 log::debug!( 450 "Internal register write: {register} set to {:02X}", 451 self.latched_control_byte 452 ); 453 } 454 0xC0 => { 455 // CRAM write 456 self.data_write_location = DataWriteLocation::Cram; 457 458 log::trace!("CRAM write"); 459 } 460 _ => unreachable!("value & 0xC0 is always 0x00/0x40/0x80/0xC0"), 461 } 462 } 463 } 464 465 self.control_write_flag = write_flag.toggle(); 466 } 467 468 fn read_data(&mut self, vram: &Vram) -> u8 { 469 let buffered_byte = self.data_read_buffer; 470 471 self.data_read_buffer = vram[self.data_address as usize]; 472 self.data_address = (self.data_address + 1) & DATA_ADDRESS_MASK; 473 474 // All data accesses reset the write toggle 475 self.control_write_flag = ControlWriteFlag::First; 476 477 buffered_byte 478 } 479 480 fn write_data(&mut self, value: u8, vram: &mut Vram, cram: &mut [u8]) { 481 log::trace!("VDP data write with address {:04X}", self.data_address); 482 483 match self.data_write_location { 484 DataWriteLocation::Vram => { 485 vram[self.data_address as usize] = value; 486 } 487 DataWriteLocation::Cram => { 488 // CRAM only uses the lowest 5 or 6 address bits 489 match self.version.hardware() { 490 SmsGgHardware::MasterSystem => { 491 // SMS CRAM is 8-bit; writes go through directly 492 let cram_addr = self.data_address & 0x1F; 493 cram[cram_addr as usize] = value; 494 } 495 SmsGgHardware::GameGear => { 496 // Game Gear CRAM is 16-bit; even addr writes are latched, odd addr writes 497 // persist a 16-bit word 498 let cram_addr = self.data_address & 0x3F; 499 if !cram_addr.bit(0) { 500 self.cram_write_latch = value; 501 } else { 502 cram[(cram_addr & !1) as usize] = self.cram_write_latch; 503 cram[cram_addr as usize] = value; 504 } 505 } 506 SmsGgHardware::Sg1000 => { 507 // SG-1000 does not have CRAM 508 } 509 } 510 } 511 } 512 513 self.data_address = (self.data_address + 1) & DATA_ADDRESS_MASK; 514 515 // All data accesses reset the write toggle 516 self.control_write_flag = ControlWriteFlag::First; 517 518 // Hardware quirk: writing to the data port also updates the read buffer 519 self.data_read_buffer = value; 520 } 521 522 fn write_internal_register(&mut self, register: u8, value: u8) { 523 match register { 524 0 => { 525 // Mode control #1 526 self.vertical_scroll_lock = value.bit(7); 527 self.horizontal_scroll_lock = value.bit(6); 528 self.hide_left_column = value.bit(5); 529 self.line_interrupt_enabled = value.bit(4); 530 self.sprite.shift_sprites_left = value.bit(3); 531 self.mode_bits[3] = value.bit(2); 532 self.mode_bits[1] = value.bit(1); 533 self.mode = Mode::from_mode_bits(self.version.hardware(), self.mode_bits); 534 // TODO sync/monochrome bit 535 536 log::debug!(" Vertical scroll lock: {}", self.vertical_scroll_lock); 537 log::debug!(" Horizontal scroll lock: {}", self.horizontal_scroll_lock); 538 log::debug!(" Hide left column: {}", self.hide_left_column); 539 log::debug!(" Line interrupt enabled: {}", self.line_interrupt_enabled); 540 log::debug!(" Shift sprites left: {}", self.sprite.shift_sprites_left); 541 log::debug!(" Mode: {:?}", self.mode); 542 } 543 1 => { 544 // Mode control #2 545 self.display_enabled = value.bit(6); 546 self.frame_interrupt_enabled = value.bit(5); 547 self.mode_bits[0] = value.bit(4); 548 self.mode_bits[2] = value.bit(3); 549 self.mode = Mode::from_mode_bits(self.version.hardware(), self.mode_bits); 550 self.sprite.double_sprite_height = value.bit(1); 551 self.sprite.double_sprite_size = value.bit(0); 552 553 log::debug!(" Display enabled: {}", self.display_enabled); 554 log::debug!(" Frame interrupt enabled: {}", self.frame_interrupt_enabled); 555 log::debug!(" Double sprite height: {}", self.sprite.double_sprite_height); 556 log::debug!(" Double sprite size: {}", self.sprite.double_sprite_size); 557 log::debug!(" Mode: {:?}", self.mode); 558 } 559 2 => { 560 // Base name table address (note: least significant bit is only used in legacy modes) 561 self.base_name_table_address = u16::from(value & 0x0F) << 10; 562 563 // On the SMS1, bit 0 of register #2 is ANDed with A10 when doing nametable lookups 564 self.name_table_address_mask = if self.version.is_sms1() { 565 0xFBFF | (u16::from(value & 0x01) << 10) 566 } else { 567 0xFFFF 568 }; 569 570 log::debug!(" Base nametable address: {:04X}", self.base_name_table_address); 571 log::debug!(" Nametable address mask: {:04X}", self.name_table_address_mask); 572 } 573 3 => { 574 // Color table address (used only in TMS9918 modes) 575 self.color_table_address = u16::from(value) << 6; 576 577 log::debug!(" TMS9918 color table address: {:04X}", self.color_table_address); 578 } 579 4 => { 580 // Pattern generator start address (used only in TMS9918 modes) 581 self.pattern_generator_address = u16::from(value & 0x07) << 11; 582 583 log::debug!( 584 " TMS9918 pattern generator address: {:04X}", 585 self.pattern_generator_address 586 ); 587 } 588 5 => { 589 // Sprite attribute table base address (note: LSB is only used in legacy modes) 590 // TODO SMS1 hardware quirk - if bit 0 is cleared then X position and tile index are 591 // fetched from the lower half of the table instead of the upper half 592 self.sprite.base_sprite_table_address = u16::from(value & 0x7F) << 7; 593 594 log::debug!( 595 " Sprite attribute table address: {:04X}", 596 self.sprite.base_sprite_table_address 597 ); 598 } 599 6 => { 600 // Sprite pattern table base address (note: bits 1 and 0 are only used in legacy modes) 601 // TODO SMS1 hardware quirk - bits 1 and 0 are ANDed with bits 8 and 6 of the tile index 602 self.sprite.base_sprite_pattern_address = u16::from(value & 0x07) << 11; 603 604 log::debug!( 605 " Sprite pattern generator address: {:04X}", 606 self.sprite.base_sprite_pattern_address 607 ); 608 } 609 7 => { 610 // Backdrop color 611 self.backdrop_color = value & 0x0F; 612 613 // Bit 1 color in TMS9918 text mode 614 self.text_mode_color_1 = value >> 4; 615 616 log::debug!(" Backdrop color: {}", self.backdrop_color); 617 log::debug!(" TMS9918 text mode color 1: {}", self.text_mode_color_1); 618 } 619 8 => { 620 // X scrollf 621 self.x_scroll = value; 622 623 log::debug!(" X scroll: {value}"); 624 } 625 9 => { 626 // Y scroll 627 // TODO updates to Y scroll should only take effect at end-of-frame? 628 self.y_scroll = value; 629 630 log::debug!(" Y scroll: {value}"); 631 } 632 10 => { 633 // Line counter 634 self.line_counter_reload_value = value; 635 636 log::debug!(" Line interrupt counter reload: {value}"); 637 } 638 _ => {} 639 } 640 } 641} 642 643#[derive(Debug, Clone, Copy, PartialEq, Eq)] 644enum Palette { 645 Palette0, 646 Palette1, 647} 648 649impl Palette { 650 fn base_cram_addr(self) -> u8 { 651 match self { 652 Self::Palette0 => 0x00, 653 Self::Palette1 => 0x10, 654 } 655 } 656} 657 658#[derive(Debug, Clone, Copy)] 659struct BgTileData { 660 priority: bool, 661 palette: Palette, 662 vertical_flip: bool, 663 horizontal_flip: bool, 664 tile_index: u16, 665} 666 667#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 668struct SpriteData { 669 y: u8, 670 x: u8, 671 tile_index: u16, 672} 673 674#[derive(Debug, Clone, Encode, Decode)] 675struct SpriteBuffer { 676 sprites: [SpriteData; 64], 677 len: usize, 678 overflow: bool, 679} 680 681impl SpriteBuffer { 682 fn new() -> Self { 683 Self { sprites: [SpriteData::default(); 64], len: 0, overflow: false } 684 } 685 686 fn iter(&self) -> BufferIter<'_, SpriteData> { 687 BufferIter { buffer: &self.sprites, idx: 0, len: self.len } 688 } 689 690 fn clear(&mut self) { 691 self.len = 0; 692 self.overflow = false; 693 } 694} 695 696impl<'a> IntoIterator for &'a SpriteBuffer { 697 type Item = SpriteData; 698 type IntoIter = BufferIter<'a, SpriteData>; 699 700 fn into_iter(self) -> Self::IntoIter { 701 self.iter() 702 } 703} 704 705#[derive(Debug, Clone)] 706struct BufferIter<'a, T> { 707 buffer: &'a [T], 708 idx: usize, 709 len: usize, 710} 711 712impl<T: Copy> Iterator for BufferIter<'_, T> { 713 type Item = T; 714 715 #[allow(clippy::if_then_some_else_none)] 716 fn next(&mut self) -> Option<Self::Item> { 717 if self.idx < self.len { 718 let data = self.buffer[self.idx]; 719 self.idx += 1; 720 Some(data) 721 } else { 722 None 723 } 724 } 725} 726 727fn find_sprites_on_scanline( 728 scanline: u8, 729 mode: Mode, 730 registers: SpriteRegisters, 731 vram: &Vram, 732 sprite_buffer: &mut SpriteBuffer, 733 remove_sprite_limit: bool, 734) { 735 let sprite_height = registers.sprite_height(); 736 737 let base_sat_addr = registers.base_sprite_table_address & 0xFF00; 738 for i in 0..64 { 739 let y = vram[(base_sat_addr | i) as usize]; 740 if mode != Mode::Four224Line && y == 0xD0 { 741 return; 742 } 743 744 let x = vram[(base_sat_addr | 0x80 | (2 * i)) as usize]; 745 let tile_index = vram[(base_sat_addr | 0x80 | (2 * i + 1)) as usize]; 746 747 let sprite_bottom = y.wrapping_add(sprite_height); 748 749 let sprite_overlaps_line = if y < sprite_bottom { 750 (y..sprite_bottom).contains(&scanline) 751 } else { 752 scanline >= y || scanline < sprite_bottom 753 }; 754 if sprite_overlaps_line { 755 if sprite_buffer.len == 8 { 756 sprite_buffer.overflow = true; 757 if !remove_sprite_limit { 758 return; 759 } 760 } 761 762 sprite_buffer.sprites[sprite_buffer.len] = 763 SpriteData { y, x, tile_index: tile_index.into() }; 764 sprite_buffer.len += 1; 765 } 766 } 767} 768 769#[derive(Debug, Clone, Encode, Decode)] 770struct SpriteLineBuffer { 771 pixels: [u8; SCREEN_WIDTH as usize], 772 collisions: [bool; SCREEN_WIDTH as usize], 773} 774 775impl SpriteLineBuffer { 776 fn new() -> Self { 777 Self { pixels: array::from_fn(|_| 0), collisions: array::from_fn(|_| false) } 778 } 779 780 fn clear(&mut self) { 781 self.pixels.fill(0); 782 self.collisions.fill(false); 783 } 784} 785 786fn render_sprite_pixels( 787 scanline: u8, 788 registers: SpriteRegisters, 789 vram: &[u8; VRAM_LEN], 790 active_sprites: &SpriteBuffer, 791 line_buffer: &mut SpriteLineBuffer, 792) { 793 let sprite_width: u16 = registers.sprite_width().into(); 794 let sprite_x_downshift: i32 = registers.double_sprite_size.into(); 795 796 // Mask out bits 11-12 (only used in legacy modes) 797 let base_sprite_pattern_addr = registers.base_sprite_pattern_address & 0x2000; 798 799 let sprite_x_delta = if registers.shift_sprites_left { -8 } else { 0 }; 800 801 for sprite in active_sprites { 802 let sprite_left: u16 = sprite.x.into(); 803 let sprite_tile_row = u16::from(scanline.wrapping_sub(sprite.y)) >> sprite_x_downshift; 804 805 let tile_index = if registers.double_sprite_height { 806 let top_tile = sprite.tile_index & 0xFE; 807 top_tile | u16::from(sprite_tile_row >= 8) 808 } else { 809 sprite.tile_index 810 }; 811 812 let sprite_tile_addr = (base_sprite_pattern_addr | (tile_index * 32)) as usize; 813 let sprite_tile = &vram[sprite_tile_addr..sprite_tile_addr + 32]; 814 815 for dx in 0..sprite_width { 816 let x = sprite_left + dx; 817 let pixel_idx = i32::from(x) + sprite_x_delta; 818 if !(0..SCREEN_WIDTH.into()).contains(&pixel_idx) { 819 continue; 820 } 821 822 let sprite_tile_col = dx >> sprite_x_downshift; 823 let color_id = get_color_id(sprite_tile, sprite_tile_row & 7, sprite_tile_col, false); 824 if color_id == 0 { 825 continue; 826 } 827 828 if line_buffer.pixels[pixel_idx as usize] != 0 { 829 line_buffer.collisions[pixel_idx as usize] = true; 830 } else { 831 line_buffer.pixels[pixel_idx as usize] = color_id; 832 } 833 } 834 } 835} 836 837#[derive(Debug, Clone, Encode, Decode)] 838pub struct VdpBuffer { 839 buffer: BoxedWordArray<FRAME_BUFFER_LEN>, 840 viewport: ViewportSize, 841} 842 843impl VdpBuffer { 844 fn new(version: VdpVersion, gg_use_sms_resolution: bool) -> Self { 845 Self { 846 buffer: BoxedWordArray::new(), 847 viewport: version.viewport_size(gg_use_sms_resolution, Mode::default()), 848 } 849 } 850 851 #[inline] 852 fn idx(&self, row: u16, col: u16) -> usize { 853 (self.viewport.top as usize + row as usize) * SCREEN_WIDTH as usize 854 + self.viewport.left as usize 855 + col as usize 856 } 857 858 #[inline] 859 pub fn get(&self, row: u16, col: u16) -> u16 { 860 self.buffer[self.idx(row, col)] 861 } 862 863 #[inline] 864 fn set(&mut self, row: u16, col: u16, value: u16) { 865 self.buffer[row as usize * SCREEN_WIDTH as usize + col as usize] = value; 866 } 867 868 pub fn iter(&self) -> FrameBufferRowIter<'_> { 869 FrameBufferRowIter { buffer: self, row: 0 } 870 } 871} 872 873#[derive(Debug, Clone)] 874pub struct FrameBufferRowIter<'a> { 875 buffer: &'a VdpBuffer, 876 row: u16, 877} 878 879impl<'a> Iterator for FrameBufferRowIter<'a> { 880 type Item = &'a [u16]; 881 882 #[inline] 883 #[allow(clippy::if_then_some_else_none)] 884 fn next(&mut self) -> Option<Self::Item> { 885 if self.row < self.buffer.viewport.height { 886 let start_idx = self.buffer.idx(self.row, 0); 887 let row_slice = 888 &self.buffer.buffer[start_idx..start_idx + self.buffer.viewport.width as usize]; 889 self.row += 1; 890 Some(row_slice) 891 } else { 892 None 893 } 894 } 895} 896 897impl<'a> IntoIterator for &'a VdpBuffer { 898 type Item = &'a [u16]; 899 type IntoIter = FrameBufferRowIter<'a>; 900 901 fn into_iter(self) -> Self::IntoIter { 902 self.iter() 903 } 904} 905 906#[derive(Debug, Clone, Encode, Decode)] 907pub struct Vdp { 908 frame_buffer: VdpBuffer, 909 registers: Registers, 910 vram: Box<Vram>, 911 color_ram: [u8; COLOR_RAM_LEN], 912 scanline: u16, 913 dot: u16, 914 event_idx: u8, 915 sprite_buffer: SpriteBuffer, 916 sprite_line_buffer: SpriteLineBuffer, 917 remove_sprite_limit: bool, 918 gg_use_sms_resolution: bool, 919 line_counter: u8, 920 latched_h_counter: u8, 921} 922 923#[derive(Debug, Clone, Copy, PartialEq, Eq)] 924pub enum VdpTickEffect { 925 None, 926 FrameComplete, 927} 928 929impl Vdp { 930 pub fn new(version: VdpVersion, config: &SmsGgEmulatorConfig) -> Self { 931 Self { 932 frame_buffer: VdpBuffer::new(version, config.gg_use_sms_resolution), 933 registers: Registers::new(version), 934 vram: Box::new(array::from_fn(|_| 0)), 935 color_ram: [0; COLOR_RAM_LEN], 936 scanline: 0, 937 dot: 0, 938 event_idx: 0, 939 sprite_buffer: SpriteBuffer::new(), 940 sprite_line_buffer: SpriteLineBuffer::new(), 941 remove_sprite_limit: config.remove_sprite_limit, 942 gg_use_sms_resolution: config.gg_use_sms_resolution, 943 line_counter: 0xFF, 944 latched_h_counter: 0, 945 } 946 } 947 948 fn read_color_ram_word(&self, address: u8) -> u16 { 949 match self.registers.version.hardware() { 950 SmsGgHardware::MasterSystem => self.color_ram[address as usize].into(), 951 SmsGgHardware::GameGear => u16::from_le_bytes([ 952 self.color_ram[(2 * address) as usize], 953 self.color_ram[(2 * address + 1) as usize], 954 ]), 955 SmsGgHardware::Sg1000 => { 956 // SG-1000 does not have CRAM 957 0xFFFF 958 } 959 } 960 } 961 962 fn read_name_table_word(&self, row: u16, col: u16) -> BgTileData { 963 let base_name_table_addr = match self.registers.mode { 964 // Mask out bit 11 and offset by $0700 965 Mode::Four224Line => (self.registers.base_name_table_address & 0xF000) | 0x0700, 966 // Mask out bit 10 (only used by legacy modes) 967 _ => self.registers.base_name_table_address & 0xF800, 968 }; 969 970 let name_table_address_mask = if self.registers.version.is_sms1() { 971 self.registers.name_table_address_mask 972 } else { 973 0xFFFF 974 }; 975 let name_table_addr = 976 (base_name_table_addr + (row << 6) + (col << 1)) & name_table_address_mask; 977 let low_byte = self.vram[name_table_addr as usize]; 978 let high_byte = self.vram[(name_table_addr + 1) as usize]; 979 980 let priority = high_byte.bit(4); 981 let palette = if !high_byte.bit(3) { Palette::Palette0 } else { Palette::Palette1 }; 982 let vertical_flip = high_byte.bit(2); 983 let horizontal_flip = high_byte.bit(1); 984 let tile_index = (u16::from(high_byte.bit(0)) << 8) | u16::from(low_byte); 985 986 BgTileData { priority, palette, vertical_flip, horizontal_flip, tile_index } 987 } 988 989 fn render_scanline(&mut self, scanline: u16) { 990 match self.registers.mode { 991 Mode::Text => return self.render_text_scanline(scanline), 992 Mode::GraphicsI | Mode::GraphicsII => { 993 return self.render_graphics_12_scanline(scanline, self.registers.mode); 994 } 995 Mode::Multicolor => return self.render_multicolor_scanline(scanline), 996 Mode::Four | Mode::Four224Line => {} 997 } 998 999 let frame_buffer_row = self.frame_buffer_row(scanline); 1000 1001 let (coarse_x_scroll, fine_x_scroll) = 1002 if scanline < 16 && self.registers.horizontal_scroll_lock { 1003 (0, 0) 1004 } else { 1005 (u16::from(self.registers.x_scroll >> 3), u16::from(self.registers.x_scroll & 0x07)) 1006 }; 1007 1008 let backdrop_color = self.backdrop_color(); 1009 for dot in 0..fine_x_scroll { 1010 self.frame_buffer.set(frame_buffer_row, dot, backdrop_color); 1011 } 1012 1013 for column in 0..32 { 1014 let (coarse_y_scroll, fine_y_scroll) = if column >= 24 1015 && self.registers.vertical_scroll_lock 1016 { 1017 (0, 0) 1018 } else { 1019 (u16::from(self.registers.y_scroll >> 3), u16::from(self.registers.y_scroll & 0x07)) 1020 }; 1021 1022 let name_table_rows = self.registers.mode.name_table_rows(); 1023 let name_table_row = 1024 ((scanline + fine_y_scroll) / 8 + coarse_y_scroll) % name_table_rows; 1025 let name_table_col = (column + (32 - coarse_x_scroll)) % 32; 1026 let bg_tile_data = self.read_name_table_word(name_table_row, name_table_col); 1027 1028 let bg_tile_addr = (bg_tile_data.tile_index * 32) as usize; 1029 let bg_tile = &self.vram[bg_tile_addr..bg_tile_addr + 32]; 1030 1031 let bg_base_cram_addr = bg_tile_data.palette.base_cram_addr(); 1032 1033 let bg_tile_row = if bg_tile_data.vertical_flip { 1034 7 - ((scanline + fine_y_scroll) % 8) 1035 } else { 1036 (scanline + fine_y_scroll) % 8 1037 }; 1038 1039 for bg_tile_col in 0..8 { 1040 let dot = 8 * column + fine_x_scroll + bg_tile_col; 1041 if dot == SCREEN_WIDTH { 1042 break; 1043 } 1044 1045 if self.registers.hide_left_column && dot < 8 { 1046 self.frame_buffer.set(frame_buffer_row, dot, backdrop_color); 1047 continue; 1048 } 1049 1050 let bg_color_id = 1051 get_color_id(bg_tile, bg_tile_row, bg_tile_col, bg_tile_data.horizontal_flip); 1052 1053 let sprite_color_id = self.sprite_line_buffer.pixels[dot as usize]; 1054 1055 let pixel_color = 1056 if sprite_color_id != 0 && (bg_color_id == 0 || !bg_tile_data.priority) { 1057 // Sprites can only use palette 1 1058 self.read_color_ram_word(0x10 | sprite_color_id) 1059 } else { 1060 self.read_color_ram_word(bg_base_cram_addr | bg_color_id) 1061 }; 1062 self.frame_buffer.set(frame_buffer_row, dot, pixel_color); 1063 } 1064 } 1065 } 1066 1067 fn clear_scanline(&mut self, scanline: u16) { 1068 let frame_buffer_row = self.frame_buffer_row(scanline); 1069 let backdrop_color = self.backdrop_color(); 1070 1071 for pixel in 0..SCREEN_WIDTH { 1072 self.frame_buffer.set(frame_buffer_row, pixel, backdrop_color); 1073 } 1074 } 1075 1076 fn frame_buffer_row(&self, scanline: u16) -> u16 { 1077 scanline + self.frame_buffer.viewport.top_border_height 1078 } 1079 1080 fn backdrop_color(&self) -> u16 { 1081 match self.registers.version.hardware() { 1082 SmsGgHardware::MasterSystem | SmsGgHardware::GameGear => { 1083 // Backdrop color always reads from the second half of CRAM (sprite colors) 1084 self.read_color_ram_word(0x10 | self.registers.backdrop_color) 1085 } 1086 SmsGgHardware::Sg1000 => { 1087 let color_table = tms9918::color_table(self.registers.version.hardware()); 1088 color_table[self.registers.backdrop_color as usize].into() 1089 } 1090 } 1091 } 1092 1093 fn trace_log_current_state(&self) { 1094 log::trace!("Registers: {:04X?}", self.registers); 1095 1096 log::trace!("CRAM:"); 1097 for (i, value) in self.color_ram.into_iter().enumerate() { 1098 log::trace!(" {i:02X}: {value:02X}"); 1099 } 1100 1101 log::trace!("Nametable ({:04X}):", self.registers.base_name_table_address); 1102 for row in 0..28 { 1103 for col in 0..32 { 1104 let name_table_word = self.read_name_table_word(row, col); 1105 log::trace!(" ({row}, {col}): {name_table_word:03X?}"); 1106 1107 let name_table_addr = 1108 (self.registers.base_name_table_address | (row << 6) | (col << 1)) as usize; 1109 let memory = &self.vram[name_table_addr..name_table_addr + 2]; 1110 log::trace!(" RAM bytes: {memory:02X?}"); 1111 } 1112 } 1113 1114 log::trace!("Tiles:"); 1115 for i in 0..512 { 1116 let address = i * 32; 1117 let values = &self.vram[address..address + 32]; 1118 log::trace!(" {i:03X}: {values:02X?}"); 1119 } 1120 } 1121 1122 #[must_use] 1123 pub fn tick(&mut self) -> VdpTickEffect { 1124 if log::log_enabled!(log::Level::Trace) && self.scanline == 0 && self.dot == 0 { 1125 self.trace_log_current_state(); 1126 } 1127 1128 let timing_mode = self.timing_mode(); 1129 let active_scanlines = self.registers.mode.active_scanlines(); 1130 let scanlines_per_frame = timing_mode.scanlines_per_frame(); 1131 1132 self.dot += 1; 1133 if self.dot == DOTS_PER_SCANLINE { 1134 self.scanline += 1; 1135 self.dot = 0; 1136 self.event_idx = 0; 1137 1138 if self.scanline == scanlines_per_frame { 1139 self.scanline = 0; 1140 } 1141 } 1142 1143 self.process_events(active_scanlines, scanlines_per_frame); 1144 1145 if self.registers.display_enabled 1146 && self.scanline < active_scanlines 1147 && self 1148 .sprite_line_buffer 1149 .collisions 1150 .get(self.dot.wrapping_sub(2) as usize) 1151 .copied() 1152 .unwrap_or(false) 1153 { 1154 log::debug!("Sprite collision at line {} dot {}", self.scanline, self.dot); 1155 self.registers.sprite_collision = true; 1156 } 1157 1158 let frame_complete = self.scanline == active_scanlines + 1 && self.dot == 0; 1159 if frame_complete { VdpTickEffect::FrameComplete } else { VdpTickEffect::None } 1160 } 1161 1162 fn process_events(&mut self, active_scanlines: u16, scanlines_per_frame: u16) { 1163 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 1164 enum VdpEvent { 1165 None, 1166 SpriteProcessing, 1167 RenderLine, 1168 SetSpriteOverflow, 1169 FrameInterruptFlag, 1170 FrameInterruptPending, 1171 DecrementLineCounter, 1172 } 1173 1174 // These timings are slightly early to account for how Z80 and VDP execution are interleaved. 1175 // Z80 execution is instruction-level, so when the Z80 accesses a VDP port, the VDP's current 1176 // cycle count will be pre-instruction rather than post-instruction. Any Z80-visible state 1177 // needs to be updated "early" to account for this 1178 const EVENT_DOTS: [u16; 7] = [ 1179 DOTS_PER_SCANLINE - 45, 1180 DOTS_PER_SCANLINE - 35, 1181 DOTS_PER_SCANLINE - 34, 1182 DOTS_PER_SCANLINE - 33, 1183 DOTS_PER_SCANLINE - 17, 1184 DOTS_PER_SCANLINE - 16, 1185 u16::MAX, 1186 ]; 1187 1188 const EVENTS: [VdpEvent; 7] = [ 1189 VdpEvent::SpriteProcessing, 1190 VdpEvent::RenderLine, 1191 VdpEvent::SetSpriteOverflow, 1192 VdpEvent::FrameInterruptFlag, 1193 VdpEvent::FrameInterruptPending, 1194 VdpEvent::DecrementLineCounter, 1195 VdpEvent::None, 1196 ]; 1197 1198 while self.dot >= EVENT_DOTS[self.event_idx as usize] { 1199 match EVENTS[self.event_idx as usize] { 1200 VdpEvent::SpriteProcessing => { 1201 self.per_line_sprite_processing(active_scanlines, scanlines_per_frame); 1202 self.registers.latched_sprite = self.registers.sprite; 1203 } 1204 VdpEvent::RenderLine => { 1205 let next_line = if self.scanline == scanlines_per_frame - 1 { 1206 0 1207 } else { 1208 self.scanline + 1 1209 }; 1210 if next_line < active_scanlines { 1211 if self.registers.display_enabled { 1212 self.render_scanline(next_line); 1213 } else { 1214 self.clear_scanline(next_line); 1215 } 1216 } 1217 } 1218 VdpEvent::SetSpriteOverflow => { 1219 self.registers.sprite_overflow |= self.sprite_buffer.overflow; 1220 } 1221 VdpEvent::FrameInterruptFlag => { 1222 if self.scanline == active_scanlines { 1223 self.registers.frame_interrupt_flag = true; 1224 } 1225 } 1226 VdpEvent::FrameInterruptPending => { 1227 if self.scanline == active_scanlines { 1228 self.registers.frame_interrupt_pending = 1229 self.registers.frame_interrupt_flag; 1230 1231 self.fill_vertical_border(); 1232 } 1233 } 1234 VdpEvent::DecrementLineCounter => { 1235 if self.scanline < active_scanlines || self.scanline == scanlines_per_frame - 1 1236 { 1237 let (new_counter, overflowed) = self.line_counter.overflowing_sub(1); 1238 if overflowed { 1239 self.line_counter = self.registers.line_counter_reload_value; 1240 self.registers.line_interrupt_pending = true; 1241 } else { 1242 self.line_counter = new_counter; 1243 } 1244 } else { 1245 // Line counter is constantly reloaded outside of the active display period 1246 self.line_counter = self.registers.line_counter_reload_value; 1247 } 1248 } 1249 VdpEvent::None => {} 1250 } 1251 1252 self.event_idx += 1; 1253 } 1254 } 1255 1256 fn per_line_sprite_processing(&mut self, active_scanlines: u16, scanlines_per_frame: u16) { 1257 if !matches!(self.registers.mode, Mode::Four | Mode::Four224Line) { 1258 return; 1259 } 1260 1261 self.sprite_buffer.clear(); 1262 self.sprite_line_buffer.clear(); 1263 1264 let sprite_line = if self.scanline == scanlines_per_frame - 1 { 1265 255 1266 } else if self.scanline < active_scanlines - 1 { 1267 self.scanline 1268 } else { 1269 return; 1270 }; 1271 let sprite_line = sprite_line as u8; 1272 1273 find_sprites_on_scanline( 1274 sprite_line, 1275 self.registers.mode, 1276 self.registers.latched_sprite, 1277 &self.vram, 1278 &mut self.sprite_buffer, 1279 self.remove_sprite_limit, 1280 ); 1281 1282 if self.registers.display_enabled { 1283 render_sprite_pixels( 1284 sprite_line, 1285 self.registers.latched_sprite, 1286 &self.vram, 1287 &self.sprite_buffer, 1288 &mut self.sprite_line_buffer, 1289 ); 1290 } 1291 } 1292 1293 fn fill_vertical_border(&mut self) { 1294 let backdrop_color = self.backdrop_color(); 1295 1296 let ViewportSize { top_border_height, height, bottom_border_height, .. } = 1297 self.frame_buffer.viewport; 1298 1299 let viewport_top = top_border_height; 1300 for scanline in 0..viewport_top { 1301 for pixel in 0..256 { 1302 self.frame_buffer.set(scanline, pixel, backdrop_color); 1303 } 1304 } 1305 1306 let viewport_bottom = height - bottom_border_height; 1307 for scanline in viewport_bottom..height { 1308 for pixel in 0..256 { 1309 self.frame_buffer.set(scanline, pixel, backdrop_color); 1310 } 1311 } 1312 } 1313 1314 pub fn frame_buffer(&self) -> &VdpBuffer { 1315 &self.frame_buffer 1316 } 1317 1318 pub fn viewport(&self) -> ViewportSize { 1319 self.frame_buffer.viewport 1320 } 1321 1322 pub fn read_control(&mut self) -> u8 { 1323 log::debug!("VDP control read at line {} dot {}", self.scanline, self.dot); 1324 self.registers.read_control() 1325 } 1326 1327 pub fn write_control(&mut self, value: u8) { 1328 log::debug!("VDP control write {value:02X} at line {} dot {}", self.scanline, self.dot); 1329 self.registers.write_control(value, &self.vram); 1330 1331 // Update viewport in case mode changed 1332 self.frame_buffer.viewport = 1333 self.registers.version.viewport_size(self.gg_use_sms_resolution, self.registers.mode); 1334 } 1335 1336 pub fn read_data(&mut self) -> u8 { 1337 self.registers.read_data(&self.vram) 1338 } 1339 1340 pub fn write_data(&mut self, value: u8) { 1341 self.registers.write_data(value, &mut self.vram, &mut self.color_ram); 1342 } 1343 1344 pub fn v_counter(&self) -> u8 { 1345 let scanline = if self.dot >= DOTS_PER_SCANLINE - 34 { 1346 (self.scanline + 1) % self.timing_mode().scanlines_per_frame() 1347 } else { 1348 self.scanline 1349 }; 1350 1351 let v_counter = match (self.registers.version.timing_mode(), self.registers.mode) { 1352 (TimingMode::Ntsc, Mode::Four224Line) => { 1353 if scanline <= 0xEA { 1354 scanline as u8 1355 } else { 1356 (scanline - 6) as u8 1357 } 1358 } 1359 (TimingMode::Pal, Mode::Four224Line) => { 1360 if scanline <= 0xFF { 1361 scanline as u8 1362 } else if scanline <= 0x102 { 1363 (scanline - 0x100) as u8 1364 } else { 1365 (scanline - 57) as u8 1366 } 1367 } 1368 (TimingMode::Ntsc, _) => { 1369 if scanline <= 0xDA { 1370 scanline as u8 1371 } else { 1372 (scanline - 6) as u8 1373 } 1374 } 1375 (TimingMode::Pal, _) => { 1376 if scanline <= 0xF2 { 1377 scanline as u8 1378 } else { 1379 (scanline - 57) as u8 1380 } 1381 } 1382 }; 1383 1384 log::debug!( 1385 "V counter read at line {} dot {}, value {v_counter:02X}", 1386 self.scanline, 1387 self.dot 1388 ); 1389 1390 v_counter 1391 } 1392 1393 pub fn h_counter(&self) -> u8 { 1394 self.latched_h_counter 1395 } 1396 1397 pub fn latch_h_counter_on_th_change(&mut self) { 1398 let mut dot = self.dot + 10; 1399 if dot >= DOTS_PER_SCANLINE { 1400 dot -= DOTS_PER_SCANLINE; 1401 } 1402 1403 self.latched_h_counter = if dot >= DOTS_PER_SCANLINE - 46 { 1404 let diff = -((DOTS_PER_SCANLINE - dot) as i16); 1405 (diff >> 1) as u8 1406 } else { 1407 (dot >> 1) as u8 1408 }; 1409 1410 log::debug!( 1411 "Latched H counter at line {} dot {}, value {:02X}", 1412 self.scanline, 1413 self.dot, 1414 self.latched_h_counter 1415 ); 1416 } 1417 1418 pub fn interrupt_line(&self) -> InterruptLine { 1419 if (self.registers.frame_interrupt_enabled && self.registers.frame_interrupt_pending) 1420 || (self.registers.line_interrupt_enabled && self.registers.line_interrupt_pending) 1421 { 1422 InterruptLine::Low 1423 } else { 1424 InterruptLine::High 1425 } 1426 } 1427 1428 pub fn timing_mode(&self) -> TimingMode { 1429 self.registers.version.timing_mode() 1430 } 1431 1432 pub fn update_config(&mut self, version: VdpVersion, config: &SmsGgEmulatorConfig) { 1433 self.registers.version = version; 1434 self.frame_buffer.viewport = 1435 version.viewport_size(config.gg_use_sms_resolution, self.registers.mode); 1436 self.remove_sprite_limit = config.remove_sprite_limit; 1437 self.gg_use_sms_resolution = config.gg_use_sms_resolution; 1438 } 1439} 1440 1441fn get_color_id(tile: &[u8], tile_row: u16, tile_col: u16, horizontal_flip: bool) -> u8 { 1442 let shift = if horizontal_flip { tile_col } else { 7 - tile_col }; 1443 let mask = 1 << shift; 1444 ((tile[(4 * tile_row) as usize] & mask) >> shift) 1445 | (((tile[(4 * tile_row + 1) as usize] & mask) >> shift) << 1) 1446 | (((tile[(4 * tile_row + 2) as usize] & mask) >> shift) << 2) 1447 | (((tile[(4 * tile_row + 3) as usize] & mask) >> shift) << 3) 1448} 1449 1450pub fn convert_sms_color(color: u16) -> u8 { 1451 [0, 85, 170, 255][color as usize] 1452} 1453 1454#[must_use] 1455pub fn sms_color_to_rgb(color: u16) -> Color { 1456 let r = convert_sms_color(color & 0x03); 1457 let g = convert_sms_color((color >> 2) & 0x03); 1458 let b = convert_sms_color((color >> 4) & 0x03); 1459 Color::rgb(r, g, b) 1460} 1461 1462pub fn convert_gg_color(color: u16) -> u8 { 1463 [0, 17, 34, 51, 68, 85, 102, 119, 136, 153, 170, 187, 204, 221, 238, 255][color as usize] 1464} 1465 1466#[must_use] 1467pub fn gg_color_to_rgb(color: u16) -> Color { 1468 let r = convert_gg_color(color & 0x0F); 1469 let g = convert_gg_color((color >> 4) & 0x0F); 1470 let b = convert_gg_color((color >> 8) & 0x0F); 1471 Color::rgb(r, g, b) 1472} 1473 1474pub fn convert_sg_color(color: u16) -> Color { 1475 tms9918::TMS9918_COLOR_TO_RGB8[(color & 0xF) as usize] 1476}