1use crate::ppu::{BgAffineLatch, State}; 2use bincode::{Decode, Encode}; 3use jgenesis_common::define_bit_enum; 4use jgenesis_common::num::{GetBit, U16Ext}; 5use std::{array, iter}; 6 7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 8pub enum BgMode { 9 #[default] 10 Zero, // 4 text mode BGs 11 One, // 2 text mode BGs + 1 affine BG 12 Two, // 2 affine BGs 13 Three, // 15bpp bitmap, one frame buffer 14 Four, // 8bpp bitmap, two frame buffers 15 Five, // 15bpp bitmap, two frame buffers (reduced resolution) 16 Invalid(u8), 17} 18 19impl BgMode { 20 fn to_bits(self) -> u8 { 21 match self { 22 Self::Zero => 0, 23 Self::One => 1, 24 Self::Two => 2, 25 Self::Three => 3, 26 Self::Four => 4, 27 Self::Five => 5, 28 Self::Invalid(bits) => bits, 29 } 30 } 31 32 fn from_bits(bits: u16) -> Self { 33 match bits & 7 { 34 0 => Self::Zero, 35 1 => Self::One, 36 2 => Self::Two, 37 3 => Self::Three, 38 4 => Self::Four, 39 5 => Self::Five, 40 b @ (6 | 7) => Self::Invalid(b as u8), 41 _ => unreachable!("value & 7 is always <= 7"), 42 } 43 } 44 45 #[allow(clippy::manual_range_patterns)] 46 pub fn bg_active_in_mode(self, bg: usize) -> bool { 47 matches!( 48 (self, bg), 49 (BgMode::Zero, _) 50 | (BgMode::One, 0 | 1 | 2) 51 | (BgMode::Two, 2 | 3) 52 | (BgMode::Three | BgMode::Four | BgMode::Five, 2) 53 ) 54 } 55 56 pub fn is_bitmap(self) -> bool { 57 matches!(self, Self::Three | Self::Four | Self::Five) 58 } 59} 60 61define_bit_enum!(BitmapFrameBuffer, [Zero, One]); 62 63impl BitmapFrameBuffer { 64 pub fn vram_address(self) -> u32 { 65 match self { 66 Self::Zero => 0x00000, 67 Self::One => 0x0A000, 68 } 69 } 70} 71 72define_bit_enum!(ObjVramMapDimensions, [Two, One]); 73define_bit_enum!(BitsPerPixel, [Four, Eight]); 74 75impl BitsPerPixel { 76 pub fn tile_size_bytes(self) -> u32 { 77 match self { 78 Self::Four => 32, 79 Self::Eight => 64, 80 } 81 } 82} 83 84define_bit_enum!(AffineOverflowBehavior, [Transparent, Wrap]); 85 86#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 87pub enum ScreenSize { 88 #[default] 89 Zero = 0, // 256x256 text / 128x128 affine 90 One = 1, // 512x256 text / 256x256 affine 91 Two = 2, // 256x512 text / 512x512 affine 92 Three = 3, // 512x512 text / 1024x1024 affine 93} 94 95impl ScreenSize { 96 fn from_bits(bits: u16) -> Self { 97 match bits & 3 { 98 0 => Self::Zero, 99 1 => Self::One, 100 2 => Self::Two, 101 3 => Self::Three, 102 _ => unreachable!("value & 3 is always <= 3"), 103 } 104 } 105 106 pub fn text_width_pixels(self) -> u32 { 107 match self { 108 Self::Zero | Self::Two => 256, 109 Self::One | Self::Three => 512, 110 } 111 } 112 113 pub fn text_width_tiles(self) -> u32 { 114 self.text_width_pixels() / 8 115 } 116 117 pub fn text_height_pixels(self) -> u32 { 118 match self { 119 Self::Zero | Self::One => 256, 120 Self::Two | Self::Three => 512, 121 } 122 } 123 124 pub fn text_height_tiles(self) -> u32 { 125 self.text_height_pixels() / 8 126 } 127 128 pub fn affine_dimension_tiles(self) -> u32 { 129 match self { 130 Self::Zero => 16, 131 Self::One => 32, 132 Self::Two => 64, 133 Self::Three => 128, 134 } 135 } 136} 137 138#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 139pub struct BgControl { 140 pub priority: u8, 141 pub tile_data_addr: u32, 142 pub mosaic: bool, 143 pub bpp: BitsPerPixel, 144 pub tile_map_addr: u32, 145 pub affine_overflow: AffineOverflowBehavior, 146 pub size: ScreenSize, 147 // Bits 4-5: Unused but R/W 148 pub tile_data_extra_bits: u16, 149} 150 151impl BgControl { 152 fn read(&self) -> u16 { 153 u16::from(self.priority) 154 | (((self.tile_data_addr as u16) >> 14) << 2) 155 | (self.tile_data_extra_bits << 4) 156 | (u16::from(self.mosaic) << 6) 157 | ((self.bpp as u16) << 7) 158 | (((self.tile_map_addr as u16) >> 11) << 8) 159 | ((self.affine_overflow as u16) << 13) 160 | ((self.size as u16) << 14) 161 } 162 163 fn write(&mut self, value: u16, supports_affine: bool) { 164 self.priority = (value & 3) as u8; 165 166 let tile_data_addr_16kb = (value >> 2) & 3; 167 self.tile_data_addr = (tile_data_addr_16kb << 14).into(); 168 169 self.tile_data_extra_bits = (value >> 4) & 3; 170 171 self.mosaic = value.bit(6); 172 self.bpp = BitsPerPixel::from_bit(value.bit(7)); 173 174 let tile_map_addr_2kb = (value >> 8) & 0x1F; 175 self.tile_map_addr = (tile_map_addr_2kb << 11).into(); 176 177 if supports_affine { 178 self.affine_overflow = AffineOverflowBehavior::from_bit(value.bit(13)); 179 } 180 181 self.size = ScreenSize::from_bits(value >> 14); 182 } 183} 184 185#[derive(Debug, Clone, Copy, Encode, Decode)] 186pub struct BgAffineParameters { 187 // BG2X / BG3X 188 pub reference_x: i32, 189 // BG2Y / BG3Y 190 pub reference_y: i32, 191 // BG2PA / BG3PA 192 pub a: i32, 193 // BG2PB / BG3PB 194 pub b: i32, 195 // BG2PC / BG3PC 196 pub c: i32, 197 // BG2PD / BG3PD 198 pub d: i32, 199} 200 201impl Default for BgAffineParameters { 202 fn default() -> Self { 203 Self { reference_x: 0, reference_y: 0, a: 1 << 8, b: 0, c: 0, d: 1 << 8 } 204 } 205} 206 207#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 208pub enum BlendMode { 209 #[default] 210 None = 0, 211 AlphaBlending = 1, 212 BrightnessIncrease = 2, 213 BrightnessDecrease = 3, 214} 215 216impl BlendMode { 217 fn from_bits(bits: u16) -> Self { 218 match bits & 3 { 219 0 => Self::None, 220 1 => Self::AlphaBlending, 221 2 => Self::BrightnessIncrease, 222 3 => Self::BrightnessDecrease, 223 _ => unreachable!("value & 3 is always <= 3"), 224 } 225 } 226} 227 228#[derive(Debug, Clone, Copy, PartialEq, Eq)] 229pub enum Window { 230 Inside0, 231 Inside1, 232 InsideObj, 233 Outside, 234} 235 236#[derive(Debug, Clone, Copy, PartialEq, Eq)] 237pub struct WindowEnabled { 238 pub bg: [bool; 4], 239 pub obj: bool, 240 pub blend: bool, 241} 242 243impl WindowEnabled { 244 pub const ALL: Self = Self { bg: [true; 4], obj: true, blend: true }; 245} 246 247#[derive(Debug, Clone, Encode, Decode)] 248pub struct Registers { 249 // DISPCNT (Display control) 250 pub bg_mode: BgMode, 251 pub bitmap_frame_buffer: BitmapFrameBuffer, 252 pub oam_free_during_hblank: bool, 253 pub obj_vram_map_dimensions: ObjVramMapDimensions, 254 pub forced_blanking: bool, 255 pub bg_enabled: [bool; 4], 256 pub obj_enabled: bool, 257 pub window_enabled: [bool; 2], 258 pub obj_window_enabled: bool, 259 // Undocumented green swap register ($4000002) 260 pub green_swap: bool, 261 // DISPSTAT (Display status) 262 pub vblank_irq_enabled: bool, 263 pub hblank_irq_enabled: bool, 264 pub v_counter_irq_enabled: bool, 265 pub v_counter_match: u8, 266 // BGxCNT (BG0-3 control) 267 pub bg_control: [BgControl; 4], 268 // BGxHOFS (BG0-3 horizontal offset) 269 pub bg_h_scroll: [u32; 4], 270 // BGxVOFS (BG0-3 vertical offset) 271 pub bg_v_scroll: [u32; 4], 272 // BG2/3 affine registers 273 pub bg_affine_parameters: [BgAffineParameters; 2], 274 // WINxH (Window horizontal coordinates) 275 pub window_x1: [u32; 2], 276 pub window_x2: [u32; 2], 277 // WINxV (Window vertical coordinates) 278 pub window_y1: [u32; 2], 279 pub window_y2: [u32; 2], 280 // WININ (Window inside control) 281 pub window_in_bg_enabled: [[bool; 4]; 2], 282 pub window_in_obj_enabled: [bool; 2], 283 pub window_in_blend_enabled: [bool; 2], 284 // WINOUT (Window outside control) 285 pub window_out_bg_enabled: [bool; 4], 286 pub window_out_obj_enabled: bool, 287 pub window_out_blend_enabled: bool, 288 pub obj_window_bg_enabled: [bool; 4], 289 pub obj_window_obj_enabled: bool, 290 pub obj_window_blend_enabled: bool, 291 // MOSAIC (Mosaic size) 292 pub bg_mosaic_h_size: u8, 293 pub bg_mosaic_v_size: u8, 294 pub obj_mosaic_h_size: u8, 295 pub obj_mosaic_v_size: u8, 296 // BLDCNT (Blending control) 297 pub bg_blend_1st_target: [bool; 4], 298 pub obj_blend_1st_target: bool, 299 pub backdrop_blend_1st_target: bool, 300 pub blend_mode: BlendMode, 301 pub bg_blend_2nd_target: [bool; 4], 302 pub obj_blend_2nd_target: bool, 303 pub backdrop_blend_2nd_target: bool, 304 // BLDALPHA (Alpha blending coefficients) 305 pub blend_alpha_a: u8, 306 pub blend_alpha_b: u8, 307 // BLDY (Blending brightness coefficient) 308 pub blend_brightness: u8, 309} 310 311impl Registers { 312 pub fn new() -> Self { 313 Self { 314 bg_mode: BgMode::default(), 315 bitmap_frame_buffer: BitmapFrameBuffer::default(), 316 oam_free_during_hblank: false, 317 obj_vram_map_dimensions: ObjVramMapDimensions::default(), 318 forced_blanking: true, 319 bg_enabled: [false; 4], 320 obj_enabled: false, 321 window_enabled: [false; 2], 322 obj_window_enabled: false, 323 green_swap: false, 324 vblank_irq_enabled: false, 325 hblank_irq_enabled: false, 326 v_counter_irq_enabled: false, 327 v_counter_match: 0, 328 bg_control: [BgControl::default(); 4], 329 bg_h_scroll: [0; 4], 330 bg_v_scroll: [0; 4], 331 bg_affine_parameters: array::from_fn(|_| BgAffineParameters::default()), 332 window_x1: [0; 2], 333 window_x2: [0; 2], 334 window_y1: [0; 2], 335 window_y2: [0; 2], 336 window_in_bg_enabled: [[false; 4]; 2], 337 window_in_obj_enabled: [false; 2], 338 window_in_blend_enabled: [false; 2], 339 window_out_bg_enabled: [false; 4], 340 window_out_obj_enabled: false, 341 window_out_blend_enabled: false, 342 obj_window_bg_enabled: [false; 4], 343 obj_window_obj_enabled: false, 344 obj_window_blend_enabled: false, 345 bg_mosaic_h_size: 0, 346 bg_mosaic_v_size: 0, 347 obj_mosaic_h_size: 0, 348 obj_mosaic_v_size: 0, 349 bg_blend_1st_target: [false; 4], 350 obj_blend_1st_target: false, 351 backdrop_blend_1st_target: false, 352 blend_mode: BlendMode::default(), 353 bg_blend_2nd_target: [false; 4], 354 obj_blend_2nd_target: false, 355 backdrop_blend_2nd_target: false, 356 blend_alpha_a: 0, 357 blend_alpha_b: 0, 358 blend_brightness: 0, 359 } 360 } 361 362 // $4000000: DISPCNT (Display control) 363 pub fn write_dispcnt(&mut self, value: u16, state: &mut State) { 364 let prev_bg_enabled = self.bg_enabled; 365 let prev_obj_enabled = self.obj_enabled; 366 let prev_forced_blanking = self.forced_blanking; 367 368 self.bg_mode = BgMode::from_bits(value); 369 self.bitmap_frame_buffer = BitmapFrameBuffer::from_bit(value.bit(4)); 370 self.oam_free_during_hblank = value.bit(5); 371 self.obj_vram_map_dimensions = ObjVramMapDimensions::from_bit(value.bit(6)); 372 self.forced_blanking = value.bit(7); 373 self.bg_enabled = array::from_fn(|i| value.bit((8 + i) as u8)); 374 self.obj_enabled = value.bit(12); 375 self.window_enabled = [value.bit(13), value.bit(14)]; 376 self.obj_window_enabled = value.bit(15); 377 378 // Somewhat arbitrary threshold - produces closest-to-correct results in mgba test 379 // suite's video layout 2 test and Lady Sia 380 // Pretty sure there's more going on here than just a dot threshold though 381 // 382 // Enable latency also shouldn't apply to bitmap modes (e.g. Pinball Tycoon) 383 let layer_enable_latency = if state.dot < 30 { 2 } else { 3 }; 384 385 for (i, (prev_enabled, enabled)) in iter::zip(prev_bg_enabled, self.bg_enabled).enumerate() 386 { 387 if !prev_enabled && enabled { 388 state.bg_enabled_latency[i] = layer_enable_latency; 389 } 390 } 391 392 if !prev_obj_enabled && self.obj_enabled { 393 state.obj_enabled_latency = layer_enable_latency; 394 } 395 396 if prev_forced_blanking && !self.forced_blanking { 397 state.forced_blanking_latency = layer_enable_latency; 398 } 399 400 log::debug!("DISPCNT write (line {} dot {}): {value:04X}", state.scanline, state.dot); 401 log::debug!(" BG mode: {:?}", self.bg_mode); 402 log::debug!(" Bitmap frame buffer: {:?}", self.bitmap_frame_buffer); 403 log::debug!(" OAM accessible during HBlank: {}", self.oam_free_during_hblank); 404 log::debug!(" OBJ VRAM map dimensions: {:?}", self.obj_vram_map_dimensions); 405 log::debug!(" Forced blanking enabled: {}", self.forced_blanking); 406 log::debug!(" BGs enabled: {:?}", self.bg_enabled); 407 log::debug!(" OBJ enabled: {}", self.obj_enabled); 408 log::debug!(" Window 0 enabled: {}", self.window_enabled[0]); 409 log::debug!(" Window 1 enabled: {}", self.window_enabled[1]); 410 log::debug!(" OBJ window enabled: {}", self.obj_window_enabled); 411 } 412 413 // $4000000: DISPCNT (Display control) 414 pub fn read_dispcnt(&self) -> u16 { 415 let bg_enabled_bits = bool_array_to_bits(self.bg_enabled); 416 417 u16::from(self.bg_mode.to_bits()) 418 | ((self.bitmap_frame_buffer as u16) << 4) 419 | (u16::from(self.oam_free_during_hblank) << 5) 420 | ((self.obj_vram_map_dimensions as u16) << 6) 421 | (u16::from(self.forced_blanking) << 7) 422 | (bg_enabled_bits << 8) 423 | (u16::from(self.obj_enabled) << 12) 424 | (u16::from(self.window_enabled[0]) << 13) 425 | (u16::from(self.window_enabled[1]) << 14) 426 | (u16::from(self.obj_window_enabled) << 15) 427 } 428 429 pub fn write_green_swap(&mut self, value: u16) { 430 self.green_swap = value.bit(0); 431 432 log::debug!("Green swap write: {value:04X}"); 433 } 434 435 pub fn read_green_swap(&self) -> u16 { 436 self.green_swap.into() 437 } 438 439 // $4000004: DISPSTAT (Display status) 440 pub fn write_dispstat(&mut self, value: u16) { 441 self.vblank_irq_enabled = value.bit(3); 442 self.hblank_irq_enabled = value.bit(4); 443 self.v_counter_irq_enabled = value.bit(5); 444 self.v_counter_match = value.msb(); 445 446 log::debug!("DISPSTAT write: {value:04X}"); 447 log::debug!(" VBlank IRQs enabled: {}", self.vblank_irq_enabled); 448 log::debug!(" HBlank IRQs enabled: {}", self.hblank_irq_enabled); 449 log::debug!(" V counter IRQs enabled: {}", self.v_counter_irq_enabled); 450 log::debug!(" V counter match target: {}", self.v_counter_match); 451 } 452 453 // $4000008-$400000E: BG0CNT/BG1CNT/BG2CNT/BG3CNT (BG0-3 control) 454 pub fn read_bgcnt(&self, index: usize) -> u16 { 455 self.bg_control[index].read() 456 } 457 458 // $4000008-$400000E: BG0CNT/BG1CNT/BG2CNT/BG3CNT (BG0-3 control) 459 pub fn write_bgcnt(&mut self, index: usize, value: u16) { 460 let supports_affine = index >= 2; 461 self.bg_control[index].write(value, supports_affine); 462 463 log::debug!("BG{index}CNT write: {value:04X}"); 464 log::debug!(" Priority: {}", self.bg_control[index].priority); 465 log::debug!(" Tile data base address: {:04X}", self.bg_control[index].tile_data_addr); 466 log::debug!(" Mosaic enabled: {}", self.bg_control[index].mosaic); 467 log::debug!(" Bits per pixel: {:?}", self.bg_control[index].bpp); 468 log::debug!(" Tile map base address: {:04X}", self.bg_control[index].tile_map_addr); 469 log::debug!(" Affine overflow behavior: {:?}", self.bg_control[index].affine_overflow); 470 log::debug!(" Screen size: {}", self.bg_control[index].size as u8); 471 } 472 473 // $4000010/$4000014/$4000018/$400001C: BG1HOFS/BG2HOFS/BG3HOFS/BG4HOFS (BG0-3 horizontal offset) 474 pub fn write_bghofs(&mut self, index: usize, value: u16) { 475 self.bg_h_scroll[index] = (value & 0x1FF).into(); 476 477 log::debug!("BG{index}HOFS write: {value:04X}"); 478 } 479 480 // $4000012/$4000016/$400001A/$400001E: BG1VOFS/BG2VOFS/BG3VOFS/BG4VOFS (BG0-3 vertical offset) 481 pub fn write_bgvofs(&mut self, index: usize, value: u16) { 482 self.bg_v_scroll[index] = (value & 0x1FF).into(); 483 484 log::debug!("BG{index}VOFS write: {value:04X}"); 485 } 486 487 // $4000020-$400003E: BG2/3 affine parameter registers 488 pub fn write_bg_affine_register( 489 &mut self, 490 address: u32, 491 value: u16, 492 latch: &mut BgAffineLatch, 493 ) { 494 fn cast_parameter(parameter: u16) -> i32 { 495 // Parameters are signed 16-bit; sign extend to 32-bit 496 i32::from(parameter as i16) 497 } 498 499 let bg_idx = ((address >> 4) & 1) as usize; 500 let affine_parameters = &mut self.bg_affine_parameters[bg_idx]; 501 502 match address & 0xE { 503 0x0 => { 504 // BG2PA / BG3PA 505 affine_parameters.a = cast_parameter(value); 506 log::debug!("BG{}PA write: {value:04X}", bg_idx + 2); 507 } 508 0x2 => { 509 // BG2PB // BG3PB 510 affine_parameters.b = cast_parameter(value); 511 log::debug!("BG{}PB write: {value:04X}", bg_idx + 2); 512 } 513 0x4 => { 514 // BG2PC // BG3PC 515 affine_parameters.c = cast_parameter(value); 516 log::debug!("BG{}PC write: {value:04X}", bg_idx + 2); 517 } 518 0x6 => { 519 // BG2PD // BG3PD 520 affine_parameters.d = cast_parameter(value); 521 log::debug!("BG{}PD write: {value:04X}", bg_idx + 2); 522 } 523 0x8 => { 524 // BG2X_L / BG3X_L 525 affine_parameters.reference_x = 526 (affine_parameters.reference_x & !0xFFFF) | i32::from(value); 527 528 // Update X latch on register writes 529 latch.x_written[bg_idx] = true; 530 531 log::debug!("BG{}X_L write: {value:04X}", bg_idx + 2); 532 log::debug!(" Reference point X: {:07X}", affine_parameters.reference_x); 533 } 534 0xA => { 535 // BG2X_H / BG3X_H 536 // Truncate highest 4 bits (sign extend) 537 affine_parameters.reference_x = 538 (affine_parameters.reference_x & 0xFFFF) | ((i32::from(value) << 20) >> 4); 539 540 // Update X latch on register writes 541 latch.x_written[bg_idx] = true; 542 543 log::debug!("BG{}X_H write: {value:04X}", bg_idx + 2); 544 log::debug!(" Reference point X: {:07X}", affine_parameters.reference_x); 545 } 546 0xC => { 547 // BG2Y_L / BG3Y_L 548 affine_parameters.reference_y = 549 (affine_parameters.reference_y & !0xFFFF) | i32::from(value); 550 551 // Update Y latch on register writes 552 latch.y_written[bg_idx] = true; 553 554 log::debug!("BG{}Y_L write: {value:04X}", bg_idx + 2); 555 log::debug!(" Reference point Y: {:07X}", affine_parameters.reference_y); 556 } 557 0xE => { 558 // BG2Y_H / BG3Y_H 559 // Truncate highest 4 bits (sign extend) 560 affine_parameters.reference_y = 561 (affine_parameters.reference_y & 0xFFFF) | ((i32::from(value) << 20) >> 4); 562 563 // Update Y latch on register writes 564 latch.y_written[bg_idx] = true; 565 566 log::debug!("BG{}X_H write: {value:04X}", bg_idx + 2); 567 log::debug!(" Reference point X: {:07X}", affine_parameters.reference_y); 568 } 569 _ => unreachable!("value & 0xE is always one of the above 8 values"), 570 } 571 } 572 573 pub fn read_bg_affine_register(&self, address: u32) -> u16 { 574 let bg_idx = ((address >> 4) & 1) as usize; 575 let affine_parameters = &self.bg_affine_parameters[bg_idx]; 576 577 match address & 0xE { 578 0x0 => affine_parameters.a as u16, 579 0x2 => affine_parameters.b as u16, 580 0x4 => affine_parameters.c as u16, 581 0x6 => affine_parameters.d as u16, 582 0x8 => affine_parameters.reference_x as u16, 583 0xA => (affine_parameters.reference_x >> 16) as u16, 584 0xC => affine_parameters.reference_y as u16, 585 0xE => (affine_parameters.reference_y >> 16) as u16, 586 _ => unreachable!("value & 0xE is always one of the above values"), 587 } 588 } 589 590 // $4000040/$4000042: WIN0H/WIN1H (Window 0/1 horizontal coordinates) 591 pub fn write_winh(&mut self, window: usize, value: u16) { 592 [self.window_x1[window], self.window_x2[window]] = value.to_be_bytes().map(u32::from); 593 594 log::debug!("WIN{window}H write: {value:04X}"); 595 log::debug!(" X1: {}", self.window_x1[window]); 596 log::debug!(" X2: {}", self.window_x2[window]); 597 } 598 599 pub fn write_winh_low(&mut self, window: usize, value: u8) { 600 self.window_x2[window] = value.into(); 601 602 log::debug!("WIN{window}H_L write: {value:02X}"); 603 } 604 605 pub fn write_winh_high(&mut self, window: usize, value: u8) { 606 self.window_x1[window] = value.into(); 607 608 log::debug!("WIN{window}H_H write: {value:02X}"); 609 } 610 611 // $4000044/$4000046: WIN0V/WIN1V (Window 0/1 vertical coordinates) 612 pub fn write_winv(&mut self, window: usize, value: u16) { 613 [self.window_y1[window], self.window_y2[window]] = value.to_be_bytes().map(u32::from); 614 615 log::debug!("WIN{window}V write: {value:04X}"); 616 log::debug!(" Y1: {}", self.window_y1[window]); 617 log::debug!(" Y2: {}", self.window_y2[window]); 618 } 619 620 pub fn write_winv_low(&mut self, window: usize, value: u8) { 621 self.window_y2[window] = value.into(); 622 623 log::debug!("WIN{window}V_L write: {value:02X}"); 624 } 625 626 pub fn write_winv_high(&mut self, window: usize, value: u8) { 627 self.window_y1[window] = value.into(); 628 629 log::debug!("WIN{window}V_H write: {value:02X}"); 630 } 631 632 // $4000048: WININ (Window inside control) 633 pub fn read_winin(&self) -> u16 { 634 let win0_in_bg_enabled = bool_array_to_bits(self.window_in_bg_enabled[0]); 635 let win1_in_bg_enabled = bool_array_to_bits(self.window_in_bg_enabled[1]); 636 637 win0_in_bg_enabled 638 | (u16::from(self.window_in_obj_enabled[0]) << 4) 639 | (u16::from(self.window_in_blend_enabled[0]) << 5) 640 | (win1_in_bg_enabled << 8) 641 | (u16::from(self.window_in_obj_enabled[1]) << 12) 642 | (u16::from(self.window_in_blend_enabled[1]) << 13) 643 } 644 645 // $4000048: WININ (Window inside control) 646 pub fn write_winin(&mut self, value: u16) { 647 self.window_in_bg_enabled[0] = array::from_fn(|i| value.bit(i as u8)); 648 self.window_in_obj_enabled[0] = value.bit(4); 649 self.window_in_blend_enabled[0] = value.bit(5); 650 self.window_in_bg_enabled[1] = array::from_fn(|i| value.bit((8 + i) as u8)); 651 self.window_in_obj_enabled[1] = value.bit(12); 652 self.window_in_blend_enabled[1] = value.bit(13); 653 654 log::debug!("WININ write: {value:04X}"); 655 log::debug!(" Window 0 BG enabled: {:?}", self.window_in_bg_enabled[0]); 656 log::debug!(" Window 0 OBJ enabled: {}", self.window_in_obj_enabled[0]); 657 log::debug!(" Window 0 blending enabled: {}", self.window_in_blend_enabled[0]); 658 log::debug!(" Window 1 BG enabled: {:?}", self.window_in_bg_enabled[1]); 659 log::debug!(" Window 1 OBJ enabled: {}", self.window_in_obj_enabled[1]); 660 log::debug!(" Window 1 blending enabled: {}", self.window_in_blend_enabled[1]); 661 } 662 663 // $400004A: WINOUT (Window outside control) 664 pub fn read_winout(&self) -> u16 { 665 let window_out_bg_enabled = bool_array_to_bits(self.window_out_bg_enabled); 666 let obj_window_bg_enabled = bool_array_to_bits(self.obj_window_bg_enabled); 667 668 window_out_bg_enabled 669 | (u16::from(self.window_out_obj_enabled) << 4) 670 | (u16::from(self.window_out_blend_enabled) << 5) 671 | (obj_window_bg_enabled << 8) 672 | (u16::from(self.obj_window_obj_enabled) << 12) 673 | (u16::from(self.obj_window_blend_enabled) << 13) 674 } 675 676 // $400004A: WINOUT (Window outside control) 677 pub fn write_winout(&mut self, value: u16) { 678 self.window_out_bg_enabled = array::from_fn(|i| value.bit(i as u8)); 679 self.window_out_obj_enabled = value.bit(4); 680 self.window_out_blend_enabled = value.bit(5); 681 self.obj_window_bg_enabled = array::from_fn(|i| value.bit((8 + i) as u8)); 682 self.obj_window_obj_enabled = value.bit(12); 683 self.obj_window_blend_enabled = value.bit(13); 684 685 log::debug!("WINOUT write: {value:04X}"); 686 log::debug!(" Window outside BG enabled: {:?}", self.window_out_bg_enabled); 687 log::debug!(" Window outside OBJ enabled: {}", self.window_out_obj_enabled); 688 log::debug!(" Window outside blending enabled: {}", self.window_out_blend_enabled); 689 log::debug!(" OBJ window BG enabled: {:?}", self.obj_window_bg_enabled); 690 log::debug!(" OBJ window OBJ enabled: {}", self.obj_window_obj_enabled); 691 log::debug!(" OBJ window blending enabled: {}", self.obj_window_blend_enabled); 692 } 693 694 // $4000004C: MOSAIC (Mosaic size) 695 pub fn write_mosaic(&mut self, value: u16) { 696 log::debug!("MOSAIC write: {value:04X}"); 697 698 let [bg_mosaic, obj_mosaic] = value.to_le_bytes(); 699 self.write_bg_mosaic(bg_mosaic); 700 self.write_obj_mosaic(obj_mosaic); 701 } 702 703 pub fn write_bg_mosaic(&mut self, value: u8) { 704 self.bg_mosaic_h_size = value & 0xF; 705 self.bg_mosaic_v_size = value >> 4; 706 707 log::debug!("MOSAIC low write: {value:02X}"); 708 log::debug!(" BG H size: {}", self.bg_mosaic_h_size); 709 log::debug!(" BG V size: {}", self.bg_mosaic_v_size); 710 } 711 712 pub fn write_obj_mosaic(&mut self, value: u8) { 713 self.obj_mosaic_h_size = value & 0xF; 714 self.obj_mosaic_v_size = value >> 4; 715 716 log::debug!("MOSAIC high write: {value:02X}"); 717 log::debug!(" OBJ H size: {}", self.obj_mosaic_h_size); 718 log::debug!(" OBJ V size: {}", self.obj_mosaic_v_size); 719 } 720 721 // $4000050: BLDCNT (Blending control) 722 pub fn read_bldcnt(&self) -> u16 { 723 let bg_1st_target = bool_array_to_bits(self.bg_blend_1st_target); 724 let bg_2nd_target = bool_array_to_bits(self.bg_blend_2nd_target); 725 726 bg_1st_target 727 | (u16::from(self.obj_blend_1st_target) << 4) 728 | (u16::from(self.backdrop_blend_1st_target) << 5) 729 | ((self.blend_mode as u16) << 6) 730 | (bg_2nd_target << 8) 731 | (u16::from(self.obj_blend_2nd_target) << 12) 732 | (u16::from(self.backdrop_blend_2nd_target) << 13) 733 } 734 735 // $4000050: BLDCNT (Blending control) 736 pub fn write_bldcnt(&mut self, value: u16) { 737 self.bg_blend_1st_target = array::from_fn(|i| value.bit(i as u8)); 738 self.obj_blend_1st_target = value.bit(4); 739 self.backdrop_blend_1st_target = value.bit(5); 740 self.blend_mode = BlendMode::from_bits(value >> 6); 741 self.bg_blend_2nd_target = array::from_fn(|i| value.bit((8 + i) as u8)); 742 self.obj_blend_2nd_target = value.bit(12); 743 self.backdrop_blend_2nd_target = value.bit(13); 744 745 log::debug!("BLDCNT write: {value:04X}"); 746 log::debug!(" Blend mode: {:?}", self.blend_mode); 747 log::debug!(" BG 1st target: {:?}", self.bg_blend_1st_target); 748 log::debug!(" OBJ 1st target: {:?}", self.obj_blend_1st_target); 749 log::debug!(" Backdrop 1st target: {:?}", self.backdrop_blend_1st_target); 750 log::debug!(" BG 2nd target: {:?}", self.bg_blend_2nd_target); 751 log::debug!(" OBJ 2nd target: {}", self.obj_blend_2nd_target); 752 log::debug!(" Backdrop 2nd target: {}", self.backdrop_blend_2nd_target); 753 } 754 755 // $4000052: BLDALPHA (Alpha blending coefficients) 756 pub fn read_bldalpha(&self) -> u16 { 757 u16::from_le_bytes([self.blend_alpha_a, self.blend_alpha_b]) 758 } 759 760 // $4000052: BLDALPHA (Alpha blending coefficients) 761 pub fn write_bldalpha(&mut self, value: u16) { 762 self.blend_alpha_a = (value & 0x1F) as u8; 763 self.blend_alpha_b = ((value >> 8) & 0x1F) as u8; 764 765 log::debug!("BLDALPHA write: {value:04X}"); 766 log::debug!(" A: {}", self.blend_alpha_a); 767 log::debug!(" B: {}", self.blend_alpha_b); 768 } 769 770 // $4000054: BLDY (Blending brightness coefficient) 771 pub fn write_bldy(&mut self, value: u16) { 772 self.blend_brightness = (value & 0x1F) as u8; 773 774 log::debug!("BLDY write: {value:04X} (coefficient = {})", self.blend_brightness); 775 } 776 777 pub fn window_layers_enabled(&self, window: Window) -> WindowEnabled { 778 match window { 779 Window::Inside0 => WindowEnabled { 780 bg: self.window_in_bg_enabled[0], 781 obj: self.window_in_obj_enabled[0], 782 blend: self.window_in_blend_enabled[0], 783 }, 784 Window::Inside1 => WindowEnabled { 785 bg: self.window_in_bg_enabled[1], 786 obj: self.window_in_obj_enabled[1], 787 blend: self.window_in_blend_enabled[1], 788 }, 789 Window::InsideObj => WindowEnabled { 790 bg: self.obj_window_bg_enabled, 791 obj: self.obj_window_obj_enabled, 792 blend: self.obj_window_blend_enabled, 793 }, 794 Window::Outside => WindowEnabled { 795 bg: self.window_out_bg_enabled, 796 obj: self.window_out_obj_enabled, 797 blend: self.window_out_blend_enabled, 798 }, 799 } 800 } 801} 802 803fn bool_array_to_bits(arr: [bool; 4]) -> u16 { 804 (0..4).map(|i| u16::from(arr[i]) << i).reduce(|a, b| a | b).unwrap() 805}