1use crate::ppu; 2use crate::ppu::Vram; 3use bincode::{Decode, Encode}; 4use jgenesis_common::num::{GetBit, U16Ext, U24Ext}; 5use std::cmp; 6 7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 8pub enum VerticalDisplaySize { 9 #[default] 10 TwoTwentyFour, 11 TwoThirtyNine, 12} 13 14impl VerticalDisplaySize { 15 pub fn to_lines(self) -> u16 { 16 match self { 17 Self::TwoTwentyFour => 224, 18 Self::TwoThirtyNine => 239, 19 } 20 } 21} 22 23#[derive(Debug, Clone, Copy, PartialEq, Eq)] 24pub enum BitsPerPixel { 25 // 4-color 26 Two, 27 // 16-color 28 Four, 29 // 256-color 30 Eight, 31} 32 33impl BitsPerPixel { 34 // BG3 and BG4 are always 2bpp 35 pub const BG3: Self = Self::Two; 36 pub const BG4: Self = Self::Two; 37 38 // OBJ is always 4bpp 39 pub const OBJ: Self = Self::Four; 40 41 pub const fn bitplanes(self) -> usize { 42 match self { 43 Self::Two => 2, 44 Self::Four => 4, 45 Self::Eight => 8, 46 } 47 } 48 49 pub const fn tile_size_words(self) -> u16 { 50 match self { 51 Self::Two => 8, 52 Self::Four => 16, 53 Self::Eight => 32, 54 } 55 } 56} 57 58#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 59pub enum BgMode { 60 // Mode 0: 4x 2bpp background layers 61 Zero, 62 // Mode 1: 2x 4bpp and 1x 2bpp background layers 63 One, 64 // Mode 2: 2x 4bpp background layers with offset-per-tile 65 Two, 66 // Mode 3: 1x 8bpp and 1x 4bpp background layers 67 Three, 68 // Mode 4: 1x 8bpp and 1x 2bpp background layers with offset-per-tile 69 Four, 70 // Mode 5: 1x 4bpp and 1x 2bpp background layers in 512px hi-res 71 Five, 72 // Mode 6: 1x 4bpp background layer in 512px hi-res with offset-per-tile 73 Six, 74 // Mode 7: 1x 8bpp background layer with rotation/scaling 75 #[default] 76 Seven, 77} 78 79impl BgMode { 80 fn from_byte(byte: u8) -> Self { 81 match byte & 0x07 { 82 0x00 => Self::Zero, 83 0x01 => Self::One, 84 0x02 => Self::Two, 85 0x03 => Self::Three, 86 0x04 => Self::Four, 87 0x05 => Self::Five, 88 0x06 => Self::Six, 89 0x07 => Self::Seven, 90 _ => unreachable!("value & 0x07 is always <= 0x07"), 91 } 92 } 93 94 pub fn bg1_bpp(self) -> BitsPerPixel { 95 use BitsPerPixel as BPP; 96 97 match self { 98 Self::Zero => BPP::Two, 99 Self::One | Self::Two | Self::Five | Self::Six => BPP::Four, 100 Self::Three | Self::Four | Self::Seven => BPP::Eight, 101 } 102 } 103 104 pub fn bg2_enabled(self, extbg_enabled: bool) -> bool { 105 // BG2 is enabled in all modes except 6 and 7 106 // Can still display in Mode 7 if EXTBG is enabled (uses same pixel data as BG1) 107 !matches!((self, extbg_enabled), (BgMode::Six, _) | (BgMode::Seven, false)) 108 } 109 110 pub fn bg2_bpp(self) -> BitsPerPixel { 111 use BitsPerPixel as BPP; 112 113 match self { 114 Self::Zero | Self::Four | Self::Five => BPP::Two, 115 Self::One | Self::Two | Self::Three => BPP::Four, 116 // BG2 is not rendered in mode 6 or 7; return value doesn't matter 117 Self::Six | Self::Seven => BPP::Eight, 118 } 119 } 120 121 pub fn bg3_enabled(self) -> bool { 122 // BG3 is only _really_ enabled in modes 0 and 1; modes 2/4/6 use it for offset-per-tile 123 matches!(self, Self::Zero | Self::One) 124 } 125 126 pub fn bg4_enabled(self) -> bool { 127 // BG4 is only enabled in mode 0 128 self == Self::Zero 129 } 130 131 pub fn is_offset_per_tile(self) -> bool { 132 // Modes 2/4/6 use BG3 map entries as offsets for BG1/BG2 tiles 133 matches!(self, Self::Two | Self::Four | Self::Six) 134 } 135 136 pub fn is_hi_res(self) -> bool { 137 matches!(self, Self::Five | Self::Six) 138 } 139} 140 141#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 142pub enum TileSize { 143 // 8x8 144 #[default] 145 Small, 146 // 16x16 147 Large, 148} 149 150impl TileSize { 151 fn from_bit(bit: bool) -> Self { 152 if bit { Self::Large } else { Self::Small } 153 } 154} 155 156#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 157pub enum ObjTileSize { 158 // 0: 8x8 / 16x16 159 #[default] 160 Zero, 161 // 1: 8x8 / 32x32 162 One, 163 // 2: 8x8 / 64x64 164 Two, 165 // 3: 16x16 / 32x32 166 Three, 167 // 4: 16x16 / 64x64 168 Four, 169 // 5: 32x32 / 64x64 170 Five, 171 // 6: 16x32 / 32x64 172 Six, 173 // 7: 16x32 / 32x32 174 Seven, 175} 176 177impl ObjTileSize { 178 fn from_byte(byte: u8) -> Self { 179 match byte & 0xE0 { 180 0x00 => Self::Zero, 181 0x20 => Self::One, 182 0x40 => Self::Two, 183 0x60 => Self::Three, 184 0x80 => Self::Four, 185 0xA0 => Self::Five, 186 0xC0 => Self::Six, 187 0xE0 => Self::Seven, 188 _ => unreachable!("value & 0xE0 will always be one of the above values"), 189 } 190 } 191 192 pub fn small_size(self) -> (u16, u16) { 193 match self { 194 Self::Zero | Self::One | Self::Two => (8, 8), 195 Self::Three | Self::Four => (16, 16), 196 Self::Five => (32, 32), 197 Self::Six | Self::Seven => (16, 32), 198 } 199 } 200 201 pub fn large_size(self) -> (u16, u16) { 202 match self { 203 Self::Zero => (16, 16), 204 Self::One | Self::Three | Self::Seven => (32, 32), 205 Self::Two | Self::Four | Self::Five => (64, 64), 206 Self::Six => (32, 64), 207 } 208 } 209} 210 211#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 212pub enum BgScreenSize { 213 #[default] 214 OneScreen, 215 VerticalMirror, 216 HorizontalMirror, 217 FourScreen, 218} 219 220impl BgScreenSize { 221 fn from_byte(byte: u8) -> Self { 222 match byte & 0x03 { 223 0x00 => Self::OneScreen, 224 0x01 => Self::VerticalMirror, 225 0x02 => Self::HorizontalMirror, 226 0x03 => Self::FourScreen, 227 _ => unreachable!("value & 0x03 is always <= 0x03"), 228 } 229 } 230 231 pub fn width_tiles(self) -> u16 { 232 match self { 233 Self::OneScreen | Self::HorizontalMirror => 32, 234 Self::VerticalMirror | Self::FourScreen => 64, 235 } 236 } 237 238 pub fn height_tiles(self) -> u16 { 239 match self { 240 Self::OneScreen | Self::VerticalMirror => 32, 241 Self::HorizontalMirror | Self::FourScreen => 64, 242 } 243 } 244} 245 246#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 247pub enum WindowAreaMode { 248 #[default] 249 Disabled, 250 Inside, 251 Outside, 252} 253 254impl WindowAreaMode { 255 fn from_bits(bits: u8) -> Self { 256 match bits & 0x03 { 257 0x00 | 0x01 => Self::Disabled, 258 0x02 => Self::Inside, 259 0x03 => Self::Outside, 260 _ => unreachable!("value & 0x03 is always <= 0x03"), 261 } 262 } 263 264 pub fn to_optional_bool(self, inside: bool) -> Option<bool> { 265 match self { 266 Self::Disabled => None, 267 Self::Inside => Some(inside), 268 Self::Outside => Some(!inside), 269 } 270 } 271} 272 273#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 274pub enum WindowMaskLogic { 275 #[default] 276 Or, 277 And, 278 Xor, 279 Xnor, 280} 281 282impl WindowMaskLogic { 283 fn from_bits(bits: u8) -> Self { 284 match bits & 0x03 { 285 0x00 => Self::Or, 286 0x01 => Self::And, 287 0x02 => Self::Xor, 288 0x03 => Self::Xnor, 289 _ => unreachable!("value & 0x03 is always <= 0x03"), 290 } 291 } 292 293 pub fn apply(self, window_1: Option<bool>, window_2: Option<bool>) -> bool { 294 match (self, window_1, window_2) { 295 (Self::Or, Some(window_1), Some(window_2)) => window_1 || window_2, 296 (Self::And, Some(window_1), Some(window_2)) => window_1 && window_2, 297 (Self::Xor, Some(window_1), Some(window_2)) => window_1 ^ window_2, 298 (Self::Xnor, Some(window_1), Some(window_2)) => !(window_1 ^ window_2), 299 (_, Some(window_1), None) => window_1, 300 (_, None, Some(window_2)) => window_2, 301 (_, None, None) => false, 302 } 303 } 304} 305 306#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 307pub enum ColorMathEnableMode { 308 Never, 309 OutsideColorWindow, 310 InsideColorWindow, 311 Always, 312} 313 314impl ColorMathEnableMode { 315 pub fn enabled(self, in_color_window: bool) -> bool { 316 match self { 317 Self::Always => true, 318 Self::InsideColorWindow => in_color_window, 319 Self::OutsideColorWindow => !in_color_window, 320 Self::Never => false, 321 } 322 } 323} 324 325#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 326pub enum ColorMathOperation { 327 #[default] 328 Add, 329 Subtract, 330} 331 332impl ColorMathOperation { 333 pub fn apply(self, a: u16, b: u16, divide: bool) -> u16 { 334 let (r, g, b) = match self { 335 Self::Add => { 336 let r = (a & 0x1F) + (b & 0x1F); 337 let g = ((a >> 5) & 0x1F) + ((b >> 5) & 0x1F); 338 let b = ((a >> 10) & 0x1F) + ((b >> 10) & 0x1F); 339 (r, g, b) 340 } 341 Self::Subtract => { 342 let r = (a & 0x1F).saturating_sub(b & 0x1F); 343 let g = ((a >> 5) & 0x1F).saturating_sub((b >> 5) & 0x1F); 344 let b = ((a >> 10) & 0x1F).saturating_sub((b >> 10) & 0x1F); 345 (r, g, b) 346 } 347 }; 348 349 if divide { 350 let r = r >> 1; 351 let g = g >> 1; 352 let b = b >> 1; 353 r | (g << 5) | (b << 10) 354 } else { 355 let r = cmp::min(r, 0x1F); 356 let g = cmp::min(g, 0x1F); 357 let b = cmp::min(b, 0x1F); 358 r | (g << 5) | (b << 10) 359 } 360 } 361} 362 363#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 364pub enum VramAddressTranslation { 365 #[default] 366 None, 367 EightBit, 368 NineBit, 369 TenBit, 370} 371 372impl VramAddressTranslation { 373 fn from_byte(byte: u8) -> Self { 374 match byte & 0x0C { 375 0x00 => Self::None, 376 0x04 => Self::EightBit, 377 0x08 => Self::NineBit, 378 0x0C => Self::TenBit, 379 _ => unreachable!("value & 0x0C is always one of the above values"), 380 } 381 } 382 383 pub fn apply(self, vram_addr: u16) -> u16 { 384 match self { 385 Self::None => vram_addr, 386 Self::EightBit => { 387 (vram_addr & 0xFF00) | ((vram_addr >> 5) & 0x0007) | ((vram_addr & 0x001F) << 3) 388 } 389 Self::NineBit => { 390 (vram_addr & 0xFE00) | ((vram_addr >> 6) & 0x0007) | ((vram_addr & 0x003F) << 3) 391 } 392 Self::TenBit => { 393 (vram_addr & 0xFC00) | ((vram_addr >> 7) & 0x0007) | ((vram_addr & 0x007F) << 3) 394 } 395 } 396 } 397} 398 399#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 400pub enum VramIncrementMode { 401 #[default] 402 Low, 403 High, 404} 405 406impl VramIncrementMode { 407 fn from_byte(byte: u8) -> Self { 408 if byte.bit(7) { Self::High } else { Self::Low } 409 } 410} 411 412#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 413pub enum ObjPriorityMode { 414 #[default] 415 Normal, 416 Rotate, 417} 418 419impl ObjPriorityMode { 420 fn from_byte(byte: u8) -> Self { 421 if byte.bit(7) { Self::Rotate } else { Self::Normal } 422 } 423} 424 425#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 426pub enum Mode7OobBehavior { 427 #[default] 428 Wrap, 429 Transparent, 430 Tile0, 431} 432 433impl Mode7OobBehavior { 434 fn from_byte(byte: u8) -> Self { 435 match byte & 0xC0 { 436 0x00 | 0x40 => Self::Wrap, 437 0x80 => Self::Transparent, 438 0xC0 => Self::Tile0, 439 _ => unreachable!("value & 0xC0 is always one of the above values"), 440 } 441 } 442} 443 444#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 445pub enum AccessFlipflop { 446 #[default] 447 First, 448 Second, 449} 450 451impl AccessFlipflop { 452 #[must_use] 453 pub fn toggle(self) -> Self { 454 match self { 455 Self::First => Self::Second, 456 Self::Second => Self::First, 457 } 458 } 459} 460 461#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 462pub enum MidScanlineUpdate { 463 Inidisp, 464 Scroll, 465} 466 467#[derive(Debug, Clone, Encode, Decode)] 468pub struct Mode7Registers { 469 // M7SEL 470 pub h_flip: bool, 471 pub v_flip: bool, 472 pub oob_behavior: Mode7OobBehavior, 473 // M7A/M7B/M7C/M7D 474 pub parameter_a: u16, 475 pub parameter_b: u16, 476 pub parameter_c: u16, 477 pub parameter_d: u16, 478 // M7HOFS/M7VOFS 479 pub h_scroll: u16, 480 pub v_scroll: u16, 481 // M7X/M7Y 482 pub center_x: u16, 483 pub center_y: u16, 484} 485 486impl Mode7Registers { 487 fn new() -> Self { 488 Self { 489 h_flip: false, 490 v_flip: false, 491 oob_behavior: Mode7OobBehavior::default(), 492 parameter_a: 0xFFFF, 493 parameter_b: 0xFFFF, 494 parameter_c: 0, 495 parameter_d: 0, 496 h_scroll: 0, 497 v_scroll: 0, 498 center_x: 0, 499 center_y: 0, 500 } 501 } 502} 503 504#[derive(Debug, Clone, Encode, Decode)] 505pub struct Registers { 506 // INIDISP 507 pub forced_blanking: bool, 508 pub brightness: u8, 509 // SETINI 510 pub extbg_enabled: bool, 511 pub pseudo_h_hi_res: bool, 512 pub pseudo_obj_hi_res: bool, 513 pub v_display_size: VerticalDisplaySize, 514 pub interlaced: bool, 515 // TM 516 pub main_bg_enabled: [bool; 4], 517 pub main_obj_enabled: bool, 518 // TS 519 pub sub_bg_enabled: [bool; 4], 520 pub sub_obj_enabled: bool, 521 // BGMODE 522 pub bg_mode: BgMode, 523 pub mode_1_bg3_priority: bool, 524 pub bg_tile_size: [TileSize; 4], 525 // MOSAIC 526 pub mosaic_size: u8, 527 pub bg_mosaic_enabled: [bool; 4], 528 // BG1SC/BG2SC/BG3SC/BG4SC 529 pub bg_screen_size: [BgScreenSize; 4], 530 pub bg_base_address: [u16; 4], 531 // BG12NBA/BG34NBA 532 pub bg_tile_base_address: [u16; 4], 533 // BG1VOFS/BG2VOFS/BG3VOFS/BG4VOFS 534 pub bg_h_scroll: [u16; 4], 535 pub bg_v_scroll: [u16; 4], 536 pub bg_scroll_write_buffer: u8, 537 // OBSEL 538 pub obj_tile_base_address: u16, 539 pub obj_tile_gap_size: u16, 540 pub obj_tile_size: ObjTileSize, 541 // TMW 542 pub main_bg_disabled_in_window: [bool; 4], 543 pub main_obj_disabled_in_window: bool, 544 // TSW 545 pub sub_bg_disabled_in_window: [bool; 4], 546 pub sub_obj_disabled_in_window: bool, 547 // WH0/WH1/WH2/WH3 548 pub window_1_left: u16, 549 pub window_1_right: u16, 550 pub window_2_left: u16, 551 pub window_2_right: u16, 552 // W12SEL/W34SEL/WOBJSEL 553 pub bg_window_1_area: [WindowAreaMode; 4], 554 pub bg_window_2_area: [WindowAreaMode; 4], 555 pub obj_window_1_area: WindowAreaMode, 556 pub obj_window_2_area: WindowAreaMode, 557 pub math_window_1_area: WindowAreaMode, 558 pub math_window_2_area: WindowAreaMode, 559 // WBGLOG/WOBJLOG 560 pub bg_window_mask_logic: [WindowMaskLogic; 4], 561 pub obj_window_mask_logic: WindowMaskLogic, 562 pub math_window_mask_logic: WindowMaskLogic, 563 // CGWSEL 564 pub force_main_screen_black: ColorMathEnableMode, 565 pub color_math_enabled: ColorMathEnableMode, 566 pub sub_bg_obj_enabled: bool, 567 pub direct_color_mode_enabled: bool, 568 // CGADSUB 569 pub color_math_operation: ColorMathOperation, 570 pub color_math_divide_enabled: bool, 571 pub bg_color_math_enabled: [bool; 4], 572 pub obj_color_math_enabled: bool, 573 pub backdrop_color_math_enabled: bool, 574 // COLDATA 575 pub sub_backdrop_color: u16, 576 // VMAIN 577 pub vram_address_increment_step: u16, 578 pub vram_address_translation: VramAddressTranslation, 579 pub vram_address_increment_mode: VramIncrementMode, 580 // VMADDL/VMADDH 581 pub vram_address: u16, 582 // RDVRAML/RDVRAMH 583 pub vram_prefetch_buffer: u16, 584 // OAMADDL/OAMADDH 585 pub oam_address: u16, 586 pub oam_address_reload_value: u16, 587 pub obj_priority_mode: ObjPriorityMode, 588 pub oam_write_buffer: u8, 589 pub oam_data_flipflop: AccessFlipflop, 590 // CGADD 591 pub cgram_address: u8, 592 // CGDATA/RDCGRAM 593 pub cgram_write_buffer: u8, 594 pub cgram_flipflop: AccessFlipflop, 595 // Mode 7 596 pub mode_7: Mode7Registers, 597 pub mode_7_latched: Mode7Registers, 598 pub mode_7_write_buffer: u8, 599 // PPU multiply unit (M7A/M7B) 600 pub multiply_operand_l: i16, 601 pub multiply_operand_r: i8, 602 // OPHCT/OPVCT 603 pub latched_h_counter: u16, 604 pub latched_v_counter: u16, 605 pub new_hv_latched: bool, 606 pub h_counter_flipflop: AccessFlipflop, 607 pub v_counter_flipflop: AccessFlipflop, 608 // Sprite overflow flags (readable in STAT77) 609 pub sprite_overflow: bool, 610 pub sprite_pixel_overflow: bool, 611 // Tracks mid-scanline writes to certain registers (currently just scroll registers and INIDISP) 612 pub mid_line_update: Option<MidScanlineUpdate>, 613 // Copied from WRIO CPU register (needed for H/V counter latching) 614 pub programmable_joypad_port: u8, 615} 616 617impl Registers { 618 pub fn new() -> Self { 619 Self { 620 forced_blanking: true, 621 brightness: 0, 622 extbg_enabled: false, 623 pseudo_h_hi_res: false, 624 pseudo_obj_hi_res: false, 625 v_display_size: VerticalDisplaySize::default(), 626 interlaced: false, 627 main_bg_enabled: [false; 4], 628 main_obj_enabled: false, 629 sub_bg_enabled: [false; 4], 630 sub_obj_enabled: false, 631 bg_mode: BgMode::default(), 632 mode_1_bg3_priority: true, 633 bg_tile_size: [TileSize::default(); 4], 634 mosaic_size: 0, 635 bg_mosaic_enabled: [false; 4], 636 bg_screen_size: [BgScreenSize::default(); 4], 637 bg_base_address: [0; 4], 638 bg_tile_base_address: [0; 4], 639 bg_h_scroll: [0; 4], 640 bg_v_scroll: [0; 4], 641 bg_scroll_write_buffer: 0, 642 obj_tile_base_address: 0, 643 obj_tile_gap_size: 0, 644 obj_tile_size: ObjTileSize::default(), 645 main_bg_disabled_in_window: [false; 4], 646 main_obj_disabled_in_window: false, 647 sub_bg_disabled_in_window: [false; 4], 648 sub_obj_disabled_in_window: false, 649 window_1_left: 0, 650 window_1_right: 0, 651 window_2_left: 0, 652 window_2_right: 0, 653 bg_window_1_area: [WindowAreaMode::default(); 4], 654 bg_window_2_area: [WindowAreaMode::default(); 4], 655 obj_window_1_area: WindowAreaMode::default(), 656 obj_window_2_area: WindowAreaMode::default(), 657 math_window_1_area: WindowAreaMode::default(), 658 math_window_2_area: WindowAreaMode::default(), 659 bg_window_mask_logic: [WindowMaskLogic::default(); 4], 660 obj_window_mask_logic: WindowMaskLogic::default(), 661 math_window_mask_logic: WindowMaskLogic::default(), 662 force_main_screen_black: ColorMathEnableMode::Never, 663 color_math_enabled: ColorMathEnableMode::Always, 664 sub_bg_obj_enabled: false, 665 direct_color_mode_enabled: false, 666 color_math_operation: ColorMathOperation::default(), 667 color_math_divide_enabled: false, 668 bg_color_math_enabled: [false; 4], 669 obj_color_math_enabled: false, 670 backdrop_color_math_enabled: false, 671 sub_backdrop_color: 0, 672 vram_address_increment_step: 1, 673 vram_address_translation: VramAddressTranslation::default(), 674 vram_address_increment_mode: VramIncrementMode::default(), 675 vram_address: 0, 676 vram_prefetch_buffer: 0, 677 oam_address: 0, 678 oam_address_reload_value: 0, 679 obj_priority_mode: ObjPriorityMode::default(), 680 oam_write_buffer: 0, 681 oam_data_flipflop: AccessFlipflop::default(), 682 cgram_address: 0, 683 cgram_write_buffer: 0, 684 cgram_flipflop: AccessFlipflop::default(), 685 mode_7: Mode7Registers::new(), 686 mode_7_latched: Mode7Registers::new(), 687 mode_7_write_buffer: 0, 688 multiply_operand_l: !0, 689 multiply_operand_r: !0, 690 latched_h_counter: 0, 691 latched_v_counter: 0, 692 new_hv_latched: false, 693 h_counter_flipflop: AccessFlipflop::default(), 694 v_counter_flipflop: AccessFlipflop::default(), 695 sprite_overflow: false, 696 sprite_pixel_overflow: false, 697 mid_line_update: None, 698 programmable_joypad_port: 0xFF, 699 } 700 } 701 702 pub fn write_inidisp(&mut self, value: u8, first_vblank_scanline: bool) { 703 // INIDISP: Display control 1 704 let prev_forced_blanking = self.forced_blanking; 705 let prev_brightness = self.brightness; 706 707 self.forced_blanking = value.bit(7); 708 self.brightness = value & 0x0F; 709 710 // Disabling forced blanking during the first VBlank scanline immediately reloads OAM address 711 if prev_forced_blanking && !self.forced_blanking && first_vblank_scanline { 712 self.reload_oam_address(); 713 } 714 715 if prev_forced_blanking != self.forced_blanking 716 || (!self.forced_blanking && prev_brightness != self.brightness) 717 { 718 self.mid_line_update = Some(MidScanlineUpdate::Inidisp); 719 } 720 721 log::trace!(" Forced blanking: {}", self.forced_blanking); 722 log::trace!(" Brightness: {}", self.brightness); 723 } 724 725 pub fn write_setini(&mut self, value: u8) { 726 // SETINI: Display control 2 727 self.interlaced = value.bit(0); 728 self.pseudo_obj_hi_res = value.bit(1); 729 self.v_display_size = if value.bit(2) { 730 VerticalDisplaySize::TwoThirtyNine 731 } else { 732 VerticalDisplaySize::TwoTwentyFour 733 }; 734 self.pseudo_h_hi_res = value.bit(3); 735 self.extbg_enabled = value.bit(6); 736 737 log::trace!(" Interlaced: {}", self.interlaced); 738 log::trace!(" Pseudo H hi-res: {}", self.pseudo_h_hi_res); 739 log::trace!(" Smaller OBJs: {}", self.pseudo_obj_hi_res); 740 log::trace!(" EXTBG enabled: {}", self.extbg_enabled); 741 log::trace!(" V display size: {:?}", self.v_display_size); 742 } 743 744 pub fn write_tm(&mut self, value: u8) { 745 // TM: Main screen designation 746 for (i, bg_enabled) in self.main_bg_enabled.iter_mut().enumerate() { 747 *bg_enabled = value.bit(i as u8); 748 } 749 self.main_obj_enabled = value.bit(4); 750 751 log::trace!(" Main screen BG enabled: {:?}", self.main_bg_enabled); 752 log::trace!(" Main screen OBJ enabled: {}", self.main_obj_enabled); 753 } 754 755 pub fn write_ts(&mut self, value: u8) { 756 // TS: Sub screen designation 757 for (i, bg_enabled) in self.sub_bg_enabled.iter_mut().enumerate() { 758 *bg_enabled = value.bit(i as u8); 759 } 760 self.sub_obj_enabled = value.bit(4); 761 762 log::trace!(" Sub screen BG enabled: {:?}", self.sub_bg_enabled); 763 log::trace!(" Sub screen OBJ enabled: {:?}", self.sub_obj_enabled); 764 } 765 766 pub fn write_bgmode(&mut self, value: u8) { 767 // BGMODE: BG mode and character size 768 self.bg_mode = BgMode::from_byte(value); 769 self.mode_1_bg3_priority = value.bit(3); 770 771 for (i, tile_size) in self.bg_tile_size.iter_mut().enumerate() { 772 *tile_size = TileSize::from_bit(value.bit(i as u8 + 4)); 773 } 774 775 log::trace!(" BG mode: {:?}", self.bg_mode); 776 log::trace!(" Mode 1 BG3 priority: {}", self.mode_1_bg3_priority); 777 log::trace!(" BG tile sizes: {:?}", self.bg_tile_size); 778 } 779 780 pub fn write_mosaic(&mut self, value: u8) { 781 // MOSAIC: Mosaic size and enable 782 self.mosaic_size = value >> 4; 783 784 for (i, mosaic_enabled) in self.bg_mosaic_enabled.iter_mut().enumerate() { 785 *mosaic_enabled = value.bit(i as u8); 786 } 787 788 log::trace!(" Mosaic size: {}", self.mosaic_size); 789 log::trace!(" Mosaic enabled: {:?}", self.bg_mosaic_enabled); 790 } 791 792 pub fn write_bg1234sc(&mut self, bg: usize, value: u8) { 793 // BG1SC/BG2SC/BG3SC/BG4SC: BG1-4 screen base and size 794 self.bg_screen_size[bg] = BgScreenSize::from_byte(value); 795 self.bg_base_address[bg] = u16::from(value & 0xFC) << 8; 796 797 log::trace!(" BG{} screen size: {:?}", bg + 1, self.bg_screen_size[bg]); 798 log::trace!(" BG{} base address: {:04X}", bg + 1, self.bg_base_address[bg]); 799 } 800 801 pub fn write_bg1234nba(&mut self, base_bg: usize, value: u8) { 802 // BG12NBA/BG34NBA: BG 1/2/3/4 character data area designation 803 self.bg_tile_base_address[base_bg] = u16::from(value & 0x0F) << 12; 804 self.bg_tile_base_address[base_bg + 1] = u16::from(value & 0xF0) << 8; 805 806 log::trace!( 807 " BG{} tile base address: {:04X}", 808 base_bg + 1, 809 self.bg_tile_base_address[base_bg] 810 ); 811 log::trace!( 812 " BG{} tile base address: {:04X}", 813 base_bg + 2, 814 self.bg_tile_base_address[base_bg + 1], 815 ); 816 } 817 818 pub fn write_bg1hofs(&mut self, value: u8) { 819 // BG1HOFS: BG1 horizontal scroll / M7HOFS: Mode 7 horizontal scroll 820 self.write_bg_h_scroll(0, value); 821 822 self.mode_7.h_scroll = u16::from_le_bytes([self.mode_7_write_buffer, value]); 823 self.mode_7_write_buffer = value; 824 825 log::trace!(" Mode 7 H scroll: {:04X}", self.mode_7.h_scroll); 826 } 827 828 pub fn write_bg1vofs(&mut self, value: u8) { 829 // BG1VOFS: BG1 vertical scroll / M7VOFS: Mode 7 vertical scroll 830 self.write_bg_v_scroll(0, value); 831 832 self.mode_7.v_scroll = u16::from_le_bytes([self.mode_7_write_buffer, value]); 833 self.mode_7_write_buffer = value; 834 835 log::trace!(" Mode 7 V scroll: {:04X}", self.mode_7.v_scroll); 836 } 837 838 pub fn write_bg_h_scroll(&mut self, i: usize, value: u8) { 839 let current = self.bg_h_scroll[i]; 840 let prev = self.bg_scroll_write_buffer; 841 842 // H scroll formula from https://wiki.superfamicom.org/backgrounds 843 self.bg_h_scroll[i] = 844 (u16::from(value) << 8) | u16::from(prev & !0x07) | ((current >> 8) & 0x07); 845 self.bg_scroll_write_buffer = value; 846 847 if self.bg_mode != BgMode::Seven { 848 self.mid_line_update = Some(MidScanlineUpdate::Scroll); 849 } 850 851 log::trace!(" BG{} H scroll: {:04X}", i + 1, self.bg_h_scroll[i]); 852 } 853 854 pub fn write_bg_v_scroll(&mut self, i: usize, value: u8) { 855 let prev = self.bg_scroll_write_buffer; 856 857 self.bg_v_scroll[i] = u16::from_le_bytes([prev, value]); 858 self.bg_scroll_write_buffer = value; 859 860 if self.bg_mode != BgMode::Seven { 861 self.mid_line_update = Some(MidScanlineUpdate::Scroll); 862 } 863 864 log::trace!(" BG{} V scroll: {:04X}", i + 1, self.bg_v_scroll[i]); 865 } 866 867 pub fn write_m7sel(&mut self, value: u8) { 868 // M7SEL: Mode 7 settings 869 self.mode_7.h_flip = value.bit(0); 870 self.mode_7.v_flip = value.bit(1); 871 self.mode_7.oob_behavior = Mode7OobBehavior::from_byte(value); 872 873 log::trace!(" Mode 7 H flip: {}", self.mode_7.h_flip); 874 log::trace!(" Mode 7 V flip: {}", self.mode_7.v_flip); 875 log::trace!(" Mode 7 OOB behavior: {:?}", self.mode_7.oob_behavior); 876 } 877 878 pub fn write_m7a(&mut self, value: u8) { 879 // M7A: Mode 7 parameter A / multiply 16-bit operand 880 self.mode_7.parameter_a = u16::from_le_bytes([self.mode_7_write_buffer, value]); 881 self.multiply_operand_l = i16::from_le_bytes([self.mode_7_write_buffer, value]); 882 self.mode_7_write_buffer = value; 883 884 log::trace!(" Mode 7 parameter A: {:04X}", self.mode_7.parameter_a); 885 } 886 887 pub fn write_m7b(&mut self, value: u8) { 888 // M7B: Mode 7 parameter B / multiply 8-bit operand 889 self.mode_7.parameter_b = u16::from_le_bytes([self.mode_7_write_buffer, value]); 890 self.multiply_operand_r = value as i8; 891 self.mode_7_write_buffer = value; 892 893 log::trace!(" Mode 7 parameter B: {:04X}", self.mode_7.parameter_b); 894 } 895 896 pub fn write_m7c(&mut self, value: u8) { 897 // M7C: Mode 7 parameter C 898 self.mode_7.parameter_c = u16::from_le_bytes([self.mode_7_write_buffer, value]); 899 self.mode_7_write_buffer = value; 900 901 log::trace!(" Mode 7 parameter C: {:04X}", self.mode_7.parameter_c); 902 } 903 904 pub fn write_m7d(&mut self, value: u8) { 905 // M7D: Mode 7 parameter D 906 self.mode_7.parameter_d = u16::from_le_bytes([self.mode_7_write_buffer, value]); 907 self.mode_7_write_buffer = value; 908 909 log::trace!(" Mode 7 parameter D: {:04X}", self.mode_7.parameter_d); 910 } 911 912 pub fn write_m7x(&mut self, value: u8) { 913 // M7X: Mode 7 center X coordinate 914 self.mode_7.center_x = u16::from_le_bytes([self.mode_7_write_buffer, value]) & 0x1FFF; 915 self.mode_7_write_buffer = value; 916 917 log::trace!(" Mode 7 center X: {:04X}", self.mode_7.center_x); 918 } 919 920 pub fn write_m7y(&mut self, value: u8) { 921 // M7Y: Mode 7 center Y coordinate 922 self.mode_7.center_y = u16::from_le_bytes([self.mode_7_write_buffer, value]) & 0x1FFF; 923 self.mode_7_write_buffer = value; 924 925 log::trace!(" Mode 7 center Y: {:04X}", self.mode_7.center_y); 926 } 927 928 pub fn write_obsel(&mut self, value: u8) { 929 // OBSEL: Object size and base 930 self.obj_tile_base_address = u16::from(value & 0x07) << 13; 931 self.obj_tile_gap_size = u16::from(value & 0x18) << 9; 932 self.obj_tile_size = ObjTileSize::from_byte(value); 933 934 log::trace!(" OBJ tile base address: {:04X}", self.obj_tile_base_address); 935 log::trace!(" OBJ tile gap size: {:04X}", self.obj_tile_gap_size); 936 log::trace!(" OBJ tile size: {:?}", self.obj_tile_size); 937 } 938 939 pub fn write_w1234sel(&mut self, base_bg: usize, value: u8) { 940 // W12SEL/W34SEL: Window BG1/2/3/4 mask settings 941 self.bg_window_1_area[base_bg] = WindowAreaMode::from_bits(value); 942 self.bg_window_2_area[base_bg] = WindowAreaMode::from_bits(value >> 2); 943 self.bg_window_1_area[base_bg + 1] = WindowAreaMode::from_bits(value >> 4); 944 self.bg_window_2_area[base_bg + 1] = WindowAreaMode::from_bits(value >> 6); 945 946 log::trace!(" BG{} window 1 mask: {:?}", base_bg + 1, self.bg_window_1_area[base_bg]); 947 log::trace!(" BG{} window 2 mask: {:?}", base_bg + 1, self.bg_window_2_area[base_bg]); 948 log::trace!(" BG{} window 1 mask: {:?}", base_bg + 2, self.bg_window_1_area[base_bg + 1]); 949 log::trace!(" BG{} window 2 mask: {:?}", base_bg + 2, self.bg_window_2_area[base_bg + 1]); 950 } 951 952 pub fn write_wobjsel(&mut self, value: u8) { 953 // WOBJSEL: Window OBJ/MATH mask settings 954 self.obj_window_1_area = WindowAreaMode::from_bits(value); 955 self.obj_window_2_area = WindowAreaMode::from_bits(value >> 2); 956 self.math_window_1_area = WindowAreaMode::from_bits(value >> 4); 957 self.math_window_2_area = WindowAreaMode::from_bits(value >> 6); 958 959 log::trace!(" OBJ window 1 mask: {:?}", self.obj_window_1_area); 960 log::trace!(" OBJ window 2 mask: {:?}", self.obj_window_2_area); 961 log::trace!(" MATH window 1 mask: {:?}", self.math_window_1_area); 962 log::trace!(" MATH window 2 mask: {:?}", self.math_window_2_area); 963 } 964 965 pub fn write_wh0(&mut self, value: u8) { 966 // WH0: Window 1 left position 967 self.window_1_left = value.into(); 968 log::trace!(" Window 1 left: {value:02X}"); 969 } 970 971 pub fn write_wh1(&mut self, value: u8) { 972 // WH1: Window 1 right position 973 self.window_1_right = value.into(); 974 log::trace!(" Window 1 right: {value:02X}"); 975 } 976 977 pub fn write_wh2(&mut self, value: u8) { 978 // WH2: Window 2 left position 979 self.window_2_left = value.into(); 980 log::trace!(" Window 2 left: {value:02X}"); 981 } 982 983 pub fn write_wh3(&mut self, value: u8) { 984 // WH3: Window 2 right position 985 self.window_2_right = value.into(); 986 log::trace!(" Window 2 right: {value:02X}"); 987 } 988 989 pub fn write_wbglog(&mut self, value: u8) { 990 // WBGLOG: Window BG mask logic 991 for (i, mask_logic) in self.bg_window_mask_logic.iter_mut().enumerate() { 992 *mask_logic = WindowMaskLogic::from_bits(value >> (2 * i)); 993 } 994 995 log::trace!(" BG window mask logic: {:?}", self.bg_window_mask_logic); 996 } 997 998 pub fn write_wobjlog(&mut self, value: u8) { 999 // WOBJLOG: Window OBJ/MATH mask logic 1000 self.obj_window_mask_logic = WindowMaskLogic::from_bits(value); 1001 self.math_window_mask_logic = WindowMaskLogic::from_bits(value >> 2); 1002 1003 log::trace!(" OBJ window mask logic: {:?}", self.obj_window_mask_logic); 1004 log::trace!(" MATH window mask logic: {:?}", self.math_window_mask_logic); 1005 } 1006 1007 pub fn write_tmw(&mut self, value: u8) { 1008 // TMW: Window area main screen disable 1009 for (i, bg_disabled) in self.main_bg_disabled_in_window.iter_mut().enumerate() { 1010 *bg_disabled = value.bit(i as u8); 1011 } 1012 self.main_obj_disabled_in_window = value.bit(4); 1013 1014 log::trace!( 1015 " Main screen BG disabled inside window: {:?}", 1016 self.main_bg_disabled_in_window 1017 ); 1018 log::trace!( 1019 " Main screen OBJ disabled inside window: {}", 1020 self.main_obj_disabled_in_window 1021 ); 1022 } 1023 1024 pub fn write_tsw(&mut self, value: u8) { 1025 // TSW: Window area sub screen disable 1026 for (i, bg_disabled) in self.sub_bg_disabled_in_window.iter_mut().enumerate() { 1027 *bg_disabled = value.bit(i as u8); 1028 } 1029 self.sub_obj_disabled_in_window = value.bit(4); 1030 1031 log::trace!(" Sub screen BG disabled inside window: {:?}", self.sub_bg_disabled_in_window); 1032 log::trace!(" Sub screen OBJ disabled inside window: {}", self.sub_obj_disabled_in_window); 1033 } 1034 1035 pub fn write_cgwsel(&mut self, value: u8) { 1036 // CGWSEL: Color math control register 1 1037 self.direct_color_mode_enabled = value.bit(0); 1038 self.sub_bg_obj_enabled = value.bit(1); 1039 1040 self.color_math_enabled = match value & 0x30 { 1041 0x00 => ColorMathEnableMode::Always, 1042 0x10 => ColorMathEnableMode::InsideColorWindow, 1043 0x20 => ColorMathEnableMode::OutsideColorWindow, 1044 0x30 => ColorMathEnableMode::Never, 1045 _ => unreachable!("value & 0x30 is always one of the above values"), 1046 }; 1047 self.force_main_screen_black = match value & 0xC0 { 1048 0x00 => ColorMathEnableMode::Never, 1049 0x40 => ColorMathEnableMode::OutsideColorWindow, 1050 0x80 => ColorMathEnableMode::InsideColorWindow, 1051 0xC0 => ColorMathEnableMode::Always, 1052 _ => unreachable!("value & 0xC0 is always one of the above values"), 1053 }; 1054 1055 log::trace!(" Direct color mode enabled: {}", self.direct_color_mode_enabled); 1056 log::trace!(" Sub screen BG/OBJ enabled: {}", self.sub_bg_obj_enabled); 1057 log::trace!(" Color math enabled: {:?}", self.color_math_enabled); 1058 log::trace!(" Force main screen black: {:?}", self.force_main_screen_black); 1059 } 1060 1061 pub fn write_cgadsub(&mut self, value: u8) { 1062 // CGADSUB: Color math control register 2 1063 for (i, enabled) in self.bg_color_math_enabled.iter_mut().enumerate() { 1064 *enabled = value.bit(i as u8); 1065 } 1066 1067 self.obj_color_math_enabled = value.bit(4); 1068 self.backdrop_color_math_enabled = value.bit(5); 1069 self.color_math_divide_enabled = value.bit(6); 1070 self.color_math_operation = 1071 if value.bit(7) { ColorMathOperation::Subtract } else { ColorMathOperation::Add }; 1072 1073 log::trace!(" Color math operation: {:?}", self.color_math_operation); 1074 log::trace!(" Color math divide: {}", self.color_math_divide_enabled); 1075 log::trace!(" BG color math enabled: {:?}", self.bg_color_math_enabled); 1076 log::trace!(" OBJ color math enabled: {}", self.obj_color_math_enabled); 1077 log::trace!(" Backdrop color math enabled: {}", self.backdrop_color_math_enabled); 1078 } 1079 1080 pub fn write_coldata(&mut self, value: u8) { 1081 // COLDATA: Sub screen backdrop color 1082 let intensity: u16 = (value & 0x1F).into(); 1083 1084 let mut sub_backdrop_color = self.sub_backdrop_color; 1085 1086 if value.bit(7) { 1087 // Update B 1088 sub_backdrop_color = (sub_backdrop_color & 0x03FF) | (intensity << 10); 1089 } 1090 1091 if value.bit(6) { 1092 // Update G 1093 sub_backdrop_color = (sub_backdrop_color & 0xFC1F) | (intensity << 5); 1094 } 1095 1096 if value.bit(5) { 1097 // Update R 1098 sub_backdrop_color = (sub_backdrop_color & 0xFFE0) | intensity; 1099 } 1100 1101 self.sub_backdrop_color = sub_backdrop_color; 1102 1103 log::trace!(" Sub screen backdrop color: {sub_backdrop_color:04X}"); 1104 } 1105 1106 pub fn write_oamaddl(&mut self, value: u8, in_active_display: bool) { 1107 // OAMADDL: OAM address, low byte 1108 self.oam_address_reload_value.set_lsb(value); 1109 1110 if !in_active_display { 1111 self.reload_oam_address(); 1112 } 1113 1114 log::trace!(" OAM address reload value: {:04X}", self.oam_address_reload_value); 1115 } 1116 1117 pub fn write_oamaddh(&mut self, value: u8, in_active_display: bool) { 1118 // OAMADDH: OAM address, high byte 1119 self.oam_address_reload_value.set_msb(value & 0x01); 1120 self.obj_priority_mode = ObjPriorityMode::from_byte(value); 1121 1122 if !in_active_display { 1123 self.reload_oam_address(); 1124 } 1125 1126 log::trace!(" OAM address reload value: {:04X}", self.oam_address_reload_value); 1127 log::trace!(" OBJ priority mode: {:?}", self.obj_priority_mode); 1128 } 1129 1130 pub fn write_vmain(&mut self, value: u8) { 1131 // VMAIN: VRAM address increment mode 1132 self.vram_address_increment_step = match value & 0x03 { 1133 0x00 => 1, 1134 0x01 => 32, 1135 0x02 | 0x03 => 128, 1136 _ => unreachable!("value & 0x03 is always <= 0x03"), 1137 }; 1138 self.vram_address_translation = VramAddressTranslation::from_byte(value); 1139 self.vram_address_increment_mode = VramIncrementMode::from_byte(value); 1140 1141 log::trace!(" VRAM data port increment step: {}", self.vram_address_increment_step); 1142 log::trace!(" VRAM data port address translation: {:?}", self.vram_address_translation); 1143 log::trace!(" VRAM data port increment on byte: {:?}", self.vram_address_increment_mode); 1144 } 1145 1146 pub fn write_vmaddl(&mut self, value: u8, vram: &Vram) { 1147 // VMADDL: VRAM address, low byte 1148 self.vram_address.set_lsb(value); 1149 self.vram_prefetch_buffer = vram[(self.vram_address & ppu::VRAM_ADDRESS_MASK) as usize]; 1150 1151 log::trace!(" VRAM data port address: {:04X}", self.vram_address); 1152 } 1153 1154 pub fn write_vmaddh(&mut self, value: u8, vram: &Vram) { 1155 // VMADDH: VRAM address, high byte 1156 self.vram_address.set_msb(value); 1157 self.vram_prefetch_buffer = vram[(self.vram_address & ppu::VRAM_ADDRESS_MASK) as usize]; 1158 1159 log::trace!(" VRAM data port address: {:04X}", self.vram_address); 1160 } 1161 1162 pub fn write_cgadd(&mut self, value: u8) { 1163 // CGADD: CGRAM address 1164 self.cgram_address = value; 1165 self.cgram_flipflop = AccessFlipflop::First; 1166 1167 log::trace!(" CGRAM data port address: {value:02X}"); 1168 } 1169 1170 pub fn read_mpyl(&self) -> u8 { 1171 // MPYL: PPU multiply result, low byte 1172 let mpy_result = i32::from(self.multiply_operand_l) * i32::from(self.multiply_operand_r); 1173 mpy_result.low_byte() 1174 } 1175 1176 pub fn read_mpym(&self) -> u8 { 1177 // MPYM: PPU multiply result, middle byte 1178 let mpy_result = i32::from(self.multiply_operand_l) * i32::from(self.multiply_operand_r); 1179 mpy_result.mid_byte() 1180 } 1181 1182 pub fn read_mpyh(&self) -> u8 { 1183 // MPYH: PPU multiply result, high byte 1184 let mpy_result = i32::from(self.multiply_operand_l) * i32::from(self.multiply_operand_r); 1185 mpy_result.high_byte() 1186 } 1187 1188 pub fn read_slhv(&mut self, h_counter: u16, v_counter: u16) { 1189 if self.programmable_joypad_port.bit(7) { 1190 self.latched_h_counter = h_counter; 1191 self.latched_v_counter = v_counter; 1192 } 1193 } 1194 1195 pub fn read_ophct(&mut self, ppu2_open_bus: u8) -> u8 { 1196 // Bits 1-7 of high byte are PPU2 open bus 1197 let value = match self.h_counter_flipflop { 1198 AccessFlipflop::First => self.latched_h_counter.lsb(), 1199 AccessFlipflop::Second => (ppu2_open_bus & 0xFE) | self.latched_h_counter.msb(), 1200 }; 1201 self.h_counter_flipflop = self.h_counter_flipflop.toggle(); 1202 value 1203 } 1204 1205 pub fn read_opvct(&mut self, ppu2_open_bus: u8) -> u8 { 1206 // Bits 1-7 of high byte are PPU2 open bus 1207 let value = match self.v_counter_flipflop { 1208 AccessFlipflop::First => self.latched_v_counter.lsb(), 1209 AccessFlipflop::Second => (ppu2_open_bus & 0xFE) | self.latched_v_counter.msb(), 1210 }; 1211 self.v_counter_flipflop = self.v_counter_flipflop.toggle(); 1212 value 1213 } 1214 1215 pub fn reset_hv_counter_flipflops(&mut self) { 1216 self.h_counter_flipflop = AccessFlipflop::First; 1217 self.v_counter_flipflop = AccessFlipflop::First; 1218 } 1219 1220 pub fn update_wrio(&mut self, wrio: u8, h_counter: u16, v_counter: u16) { 1221 if self.programmable_joypad_port.bit(7) & !wrio.bit(7) { 1222 self.latched_h_counter = h_counter; 1223 self.latched_v_counter = v_counter; 1224 } 1225 self.programmable_joypad_port = wrio; 1226 } 1227 1228 pub fn latch_mode_7(&mut self) { 1229 self.mode_7_latched = self.mode_7.clone(); 1230 } 1231 1232 pub fn reload_oam_address(&mut self) { 1233 self.oam_address = self.oam_address_reload_value; 1234 self.oam_data_flipflop = AccessFlipflop::First; 1235 } 1236 1237 #[allow(clippy::range_plus_one)] 1238 pub fn is_inside_window_1(&self, pixel: u16) -> bool { 1239 (self.window_1_left..self.window_1_right + 1).contains(&pixel) 1240 } 1241 1242 #[allow(clippy::range_plus_one)] 1243 pub fn is_inside_window_2(&self, pixel: u16) -> bool { 1244 (self.window_2_left..self.window_2_right + 1).contains(&pixel) 1245 } 1246 1247 pub fn bg_in_window(&self, bg: usize, window_x: u16) -> bool { 1248 self.bg_window_mask_logic[bg].apply( 1249 self.bg_window_1_area[bg].to_optional_bool(self.is_inside_window_1(window_x)), 1250 self.bg_window_2_area[bg].to_optional_bool(self.is_inside_window_2(window_x)), 1251 ) 1252 } 1253 1254 pub fn obj_in_window(&self, window_x: u16) -> bool { 1255 self.obj_window_mask_logic.apply( 1256 self.obj_window_1_area.to_optional_bool(self.is_inside_window_1(window_x)), 1257 self.obj_window_2_area.to_optional_bool(self.is_inside_window_2(window_x)), 1258 ) 1259 } 1260 1261 pub fn in_math_window(&self, window_x: u16) -> bool { 1262 self.math_window_mask_logic.apply( 1263 self.math_window_1_area.to_optional_bool(self.is_inside_window_1(window_x)), 1264 self.math_window_2_area.to_optional_bool(self.is_inside_window_2(window_x)), 1265 ) 1266 } 1267 1268 pub fn in_hi_res_mode(&self) -> bool { 1269 self.bg_mode.is_hi_res() || self.pseudo_h_hi_res 1270 } 1271 1272 pub fn color_math_enabled_for_any_layer(&self) -> bool { 1273 self.color_math_enabled != ColorMathEnableMode::Never 1274 && (self.backdrop_color_math_enabled 1275 || self.obj_color_math_enabled 1276 || self.bg_color_math_enabled.iter().any(|&b| b)) 1277 } 1278}