1//! GBA PPU (picture processing unit) 2 3mod debug; 4mod registers; 5 6use crate::dma::DmaState; 7use crate::interrupts::{InterruptRegisters, InterruptType}; 8use crate::ppu::registers::{ 9 AffineOverflowBehavior, BgMode, BitsPerPixel, BlendMode, ObjVramMapDimensions, Registers, 10 Window, WindowEnabled, 11}; 12use crate::scheduler::{Scheduler, SchedulerEvent}; 13use bincode::{Decode, Encode}; 14use jgenesis_common::boxedarray::{BoxedByteArray, BoxedColorArray, BoxedWordArray}; 15use jgenesis_common::frontend::{Color, FrameSize}; 16use jgenesis_common::num::{GetBit, U16Ext}; 17use std::ops::Range; 18use std::{array, cmp, iter, mem}; 19 20const VRAM_LOW_LEN: usize = 64 * 1024; 21const VRAM_HIGH_LEN: usize = 32 * 1024; 22const VRAM_LEN: usize = VRAM_LOW_LEN + VRAM_HIGH_LEN; 23const VRAM_ADDR_MASK: usize = (128 * 1024) - 1; 24 25const PALETTE_RAM_LEN_HALFWORDS: usize = 1024 / 2; 26 27const OAM_LEN_HALFWORDS: usize = 1024 / 2; 28 29pub const SCREEN_HEIGHT: u32 = 160; 30pub const SCREEN_WIDTH: u32 = 240; 31pub const FRAME_BUFFER_LEN: usize = (SCREEN_HEIGHT as usize) * (SCREEN_WIDTH as usize); 32pub const FRAME_SIZE: FrameSize = FrameSize { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }; 33 34pub const LINES_PER_FRAME: u32 = 228; 35pub const DOTS_PER_LINE: u32 = 1232; 36 37// VBlank flag is not set on the last line of the frame because of sprite processing for line 0 38const VBLANK_LINES: Range<u32> = 160..227; 39const VBLANK_IRQ_DOT: u32 = 1; 40const V_COUNTER_IRQ_DOT: u32 = 2; 41const HBLANK_START_DOT: u32 = 1006; 42const HBLANK_IRQ_DOT: u32 = 1008; 43 44#[derive(Debug, Clone, Encode, Decode)] 45struct GbaFrameBuffer(BoxedWordArray<FRAME_BUFFER_LEN>); 46 47impl GbaFrameBuffer { 48 fn new() -> Self { 49 Self(BoxedWordArray::new()) 50 } 51 52 fn set(&mut self, line: u32, pixel: u32, color: u16) { 53 let frame_buffer_addr = (line * SCREEN_WIDTH + pixel) as usize; 54 self.0[frame_buffer_addr] = color; 55 } 56} 57 58#[derive(Debug, Clone, Encode, Decode)] 59struct RgbaFrameBuffer(BoxedColorArray<FRAME_BUFFER_LEN>); 60 61impl RgbaFrameBuffer { 62 fn new() -> Self { 63 Self(BoxedColorArray::new()) 64 } 65 66 fn copy_from(&mut self, frame_buffer: &GbaFrameBuffer) { 67 let mut address = 0; 68 69 for _ in 0..SCREEN_HEIGHT { 70 for _ in 0..SCREEN_WIDTH { 71 let gba_color = frame_buffer.0[address]; 72 self.0[address] = gba_color_to_rgb8(gba_color); 73 address += 1; 74 } 75 } 76 } 77} 78 79#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 80struct BgAffineLatch { 81 x: [i32; 2], 82 y: [i32; 2], 83 x_written: [bool; 2], 84 y_written: [bool; 2], 85} 86 87impl BgAffineLatch { 88 // Called once per line during active display 89 fn update_reference_points(&mut self, registers: &Registers, bg_enabled_latency: [u8; 4]) { 90 for (i, (x, y)) in iter::zip(&mut self.x, &mut self.y).enumerate() { 91 // Only increment latched X/Y if they haven't been written within the last scanline 92 // e.g. Iridion 3D (game over screen), Star Wars Episode II (text scroll) 93 // 94 // Also, only increment latches when corresponding BG is enabled (e.g. Pinball Tycoon) 95 let bg_enabled = registers.bg_enabled[i + 2] && bg_enabled_latency[i + 2] == 0; 96 97 if mem::take(&mut self.x_written[i]) { 98 *x = registers.bg_affine_parameters[i].reference_x; 99 } else if bg_enabled { 100 *x += registers.bg_affine_parameters[i].b; 101 } 102 103 if mem::take(&mut self.y_written[i]) { 104 *y = registers.bg_affine_parameters[i].reference_y; 105 } else if bg_enabled { 106 *y += registers.bg_affine_parameters[i].d; 107 } 108 } 109 } 110} 111 112#[derive(Debug, Clone, Default, Encode, Decode)] 113struct MosaicState { 114 bg_v_counter: u8, 115 bg_text_line: u32, 116 bg_affine: BgAffineLatch, 117 obj_v_counter: u8, 118 obj_line: u32, 119} 120 121#[derive(Debug, Clone, Encode, Decode)] 122struct State { 123 scanline: u32, 124 dot: u32, 125 frame_complete: bool, 126 bg_affine_latch: BgAffineLatch, 127 mosaic: MosaicState, 128 bg_enabled_latency: [u8; 4], 129 obj_enabled_latency: u8, 130 forced_blanking_latency: u8, 131 window_y_active: [bool; 2], 132 video_capture_latch: bool, 133} 134 135impl State { 136 fn new(skip_bios_animation: bool) -> Self { 137 let (scanline, dot) = if skip_bios_animation { 138 // These are probably not accurate, but currently match where the BIOS hands over control 139 // in this emulator 140 (126, 827) 141 } else { 142 (0, 0) 143 }; 144 145 Self { 146 scanline, 147 dot, 148 frame_complete: false, 149 bg_affine_latch: BgAffineLatch::default(), 150 mosaic: MosaicState::default(), 151 bg_enabled_latency: [0; 4], 152 obj_enabled_latency: 0, 153 forced_blanking_latency: 0, 154 window_y_active: [false; 2], 155 video_capture_latch: false, 156 } 157 } 158 159 // Should be called at the start of each line 160 fn update_mosaic_v_state(&mut self, registers: &Registers) { 161 // BG V mosaic 162 if self.scanline == 0 || self.mosaic.bg_v_counter == registers.bg_mosaic_v_size { 163 self.mosaic.bg_v_counter = 0; 164 self.mosaic.bg_text_line = self.scanline; 165 self.mosaic.bg_affine = self.bg_affine_latch; 166 } else { 167 self.mosaic.bg_v_counter = (self.mosaic.bg_v_counter + 1) & 0xF; 168 } 169 170 // OBJ V mosaic 171 if self.scanline == LINES_PER_FRAME - 1 { 172 self.mosaic.obj_v_counter = 0; 173 self.mosaic.obj_line = 0; 174 } else if self.mosaic.obj_v_counter == registers.obj_mosaic_v_size { 175 self.mosaic.obj_v_counter = 0; 176 self.mosaic.obj_line = self.scanline + 1; 177 } else { 178 self.mosaic.obj_v_counter = (self.mosaic.obj_v_counter + 1) & 0xF; 179 } 180 } 181} 182 183#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 184struct Pixel(u16); 185 186impl Pixel { 187 const TRANSPARENT: Self = Self(0); 188 189 fn transparent(self) -> bool { 190 !self.0.bit(15) 191 } 192 193 fn red(self) -> u16 { 194 self.0 & 0x1F 195 } 196 197 fn green(self) -> u16 { 198 (self.0 >> 5) & 0x1F 199 } 200 201 fn blue(self) -> u16 { 202 (self.0 >> 10) & 0x1F 203 } 204 205 fn new_opaque(color: u16) -> Self { 206 Self(color | 0x8000) 207 } 208 209 fn new_opaque_rgb(r: u16, g: u16, b: u16) -> Self { 210 Self(0x8000 | r | (g << 5) | (b << 10)) 211 } 212 213 fn new_transparent(color: u16) -> Self { 214 Self(color & 0x7FFF) 215 } 216} 217 218#[derive(Debug, Clone, Copy, Default, Encode, Decode)] 219struct ObjPixel { 220 color: Pixel, 221 priority: u8, 222 mosaic: bool, 223 semi_transparent: bool, 224} 225 226#[derive(Debug, Clone, Encode, Decode)] 227struct Buffers { 228 bg_pixels: [[Pixel; SCREEN_WIDTH as usize]; 4], 229 obj_pixels: [ObjPixel; SCREEN_WIDTH as usize], 230 obj_window: [bool; SCREEN_WIDTH as usize], 231} 232 233impl Buffers { 234 fn new() -> Self { 235 Self { 236 bg_pixels: array::from_fn(|_| array::from_fn(|_| Pixel::default())), 237 obj_pixels: array::from_fn(|_| ObjPixel::default()), 238 obj_window: array::from_fn(|_| false), 239 } 240 } 241} 242 243#[derive(Debug, Clone, Copy, PartialEq, Eq)] 244enum Layer { 245 Bg0, 246 Bg1, 247 Bg2, 248 Bg3, 249 Obj, 250 Backdrop, 251 None, 252} 253 254impl Layer { 255 const BG: [Self; 4] = [Self::Bg0, Self::Bg1, Self::Bg2, Self::Bg3]; 256 257 fn is_1st_target_enabled(self, registers: &Registers) -> bool { 258 match self { 259 Self::Bg0 => registers.bg_blend_1st_target[0], 260 Self::Bg1 => registers.bg_blend_1st_target[1], 261 Self::Bg2 => registers.bg_blend_1st_target[2], 262 Self::Bg3 => registers.bg_blend_1st_target[3], 263 Self::Obj => registers.obj_blend_1st_target, 264 Self::Backdrop => registers.backdrop_blend_1st_target, 265 Self::None => false, 266 } 267 } 268 269 fn is_2nd_target_enabled(self, registers: &Registers) -> bool { 270 match self { 271 Self::Bg0 => registers.bg_blend_2nd_target[0], 272 Self::Bg1 => registers.bg_blend_2nd_target[1], 273 Self::Bg2 => registers.bg_blend_2nd_target[2], 274 Self::Bg3 => registers.bg_blend_2nd_target[3], 275 Self::Obj => registers.obj_blend_2nd_target, 276 Self::Backdrop => registers.backdrop_blend_2nd_target, 277 Self::None => false, 278 } 279 } 280} 281 282#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 283enum SpriteMode { 284 #[default] 285 Normal, 286 SemiTransparent, 287 ObjWindow, 288 Invalid, 289} 290 291impl SpriteMode { 292 fn from_bits(bits: u16) -> Self { 293 match bits & 3 { 294 0 => Self::Normal, 295 1 => Self::SemiTransparent, 296 2 => Self::ObjWindow, 297 3 => Self::Invalid, 298 _ => unreachable!("value & 3 is always <= 3"), 299 } 300 } 301} 302 303#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 304enum SpriteSize { 305 #[default] 306 Zero, 307 One, 308 Two, 309 Three, 310} 311 312impl SpriteSize { 313 fn from_bits(bits: u16) -> Self { 314 match bits & 3 { 315 0 => Self::Zero, 316 1 => Self::One, 317 2 => Self::Two, 318 3 => Self::Three, 319 _ => unreachable!("value & 3 is always <= 3"), 320 } 321 } 322} 323 324#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)] 325enum SpriteShape { 326 #[default] 327 Square, 328 HorizontalRect, 329 VerticalRect, 330 Invalid, 331} 332 333impl SpriteShape { 334 fn from_bits(bits: u16) -> Self { 335 match bits & 3 { 336 0 => Self::Square, 337 1 => Self::HorizontalRect, 338 2 => Self::VerticalRect, 339 3 => Self::Invalid, 340 _ => unreachable!("value & 3 is always <= 3"), 341 } 342 } 343 344 #[allow(clippy::match_same_arms)] 345 fn size_pixels(self, size: SpriteSize) -> (u32, u32) { 346 use SpriteShape::{HorizontalRect, Invalid, Square, VerticalRect}; 347 use SpriteSize::{One, Three, Two, Zero}; 348 349 match (self, size) { 350 (Square, Zero) => (8, 8), 351 (Square, One) => (16, 16), 352 (Square, Two) => (32, 32), 353 (Square, Three) => (64, 64), 354 (HorizontalRect, Zero) => (16, 8), 355 (HorizontalRect, One) => (32, 8), 356 (HorizontalRect, Two) => (32, 16), 357 (HorizontalRect, Three) => (64, 32), 358 (VerticalRect, Zero) => (8, 16), 359 (VerticalRect, One) => (8, 32), 360 (VerticalRect, Two) => (16, 32), 361 (VerticalRect, Three) => (32, 64), 362 (Invalid, _) => { 363 // TODO ??? 364 (8, 8) 365 } 366 } 367 } 368} 369 370#[derive(Debug, Clone, Default, Encode, Decode)] 371struct OamEntry { 372 x: u32, 373 y: u32, 374 tile_number: u32, 375 affine: bool, 376 affine_double_size: bool, 377 affine_parameter_group: u16, 378 disabled: bool, 379 mode: SpriteMode, 380 mosaic: bool, 381 bpp: BitsPerPixel, 382 shape: SpriteShape, 383 size: SpriteSize, 384 h_flip: bool, 385 v_flip: bool, 386 priority: u8, 387 palette: u16, 388} 389 390impl OamEntry { 391 fn parse(attributes: [u16; 3]) -> Self { 392 // First halfword 393 // Bit 9 means double size for affine sprites and disabled for non-affine 394 let y: u32 = (attributes[0] & 0xFF).into(); 395 let affine = attributes[0].bit(8); 396 let affine_double_size = affine && attributes[0].bit(9); 397 let disabled = !affine && attributes[0].bit(9); 398 let mode = SpriteMode::from_bits(attributes[0] >> 10); 399 let mosaic = attributes[0].bit(12); 400 let bpp = BitsPerPixel::from_bit(attributes[0].bit(13)); 401 let shape = SpriteShape::from_bits(attributes[0] >> 14); 402 403 // Second halfword 404 // Bits 9-13 are parameter group for affine sprites and H/V flip for non-affine 405 let x: u32 = (attributes[1] & 0x1FF).into(); 406 let affine_parameter_group = (attributes[1] >> 9) & 0x1F; 407 let h_flip = !affine && attributes[1].bit(12); 408 let v_flip = !affine && attributes[1].bit(13); 409 let size = SpriteSize::from_bits(attributes[1] >> 14); 410 411 // Third halfword 412 let tile_number: u32 = (attributes[2] & 0x3FF).into(); 413 let priority = ((attributes[2] >> 10) & 3) as u8; 414 let palette = attributes[2] >> 12; 415 416 Self { 417 x, 418 y, 419 tile_number, 420 affine, 421 affine_double_size, 422 affine_parameter_group, 423 disabled, 424 mode, 425 mosaic, 426 bpp, 427 shape, 428 size, 429 h_flip, 430 v_flip, 431 priority, 432 palette, 433 } 434 } 435} 436 437#[derive(Debug, Clone, Copy)] 438struct MergePixel { 439 color: Pixel, 440 layer: Layer, 441 priority: u8, 442 semi_transparent: bool, 443} 444 445impl MergePixel { 446 fn bg(bg: usize, color: Pixel, priority: u8) -> Self { 447 Self { color, layer: Layer::BG[bg], priority, semi_transparent: false } 448 } 449 450 fn obj(pixel: ObjPixel) -> Self { 451 Self { 452 color: pixel.color, 453 layer: Layer::Obj, 454 priority: pixel.priority, 455 semi_transparent: pixel.semi_transparent, 456 } 457 } 458 459 fn backdrop(color: Pixel) -> Self { 460 Self { color, layer: Layer::Backdrop, priority: u8::MAX, semi_transparent: false } 461 } 462 463 fn none() -> Self { 464 Self { 465 color: Pixel::TRANSPARENT, 466 layer: Layer::None, 467 priority: u8::MAX, 468 semi_transparent: false, 469 } 470 } 471} 472 473#[derive(Debug, Clone, Encode, Decode)] 474pub struct Ppu { 475 frame_buffer: GbaFrameBuffer, 476 ready_frame_buffer: RgbaFrameBuffer, 477 vram: BoxedByteArray<VRAM_LEN>, 478 palette_ram: BoxedWordArray<PALETTE_RAM_LEN_HALFWORDS>, 479 oam: BoxedWordArray<OAM_LEN_HALFWORDS>, 480 oam_parsed: Box<[OamEntry; 128]>, 481 registers: Registers, 482 state: State, 483 buffers: Box<Buffers>, 484 cycles: u64, 485 next_event_cycles: u64, 486} 487 488impl Ppu { 489 pub fn new(skip_bios_animation: bool) -> Self { 490 Self { 491 frame_buffer: GbaFrameBuffer::new(), 492 ready_frame_buffer: RgbaFrameBuffer::new(), 493 vram: BoxedByteArray::new(), 494 palette_ram: BoxedWordArray::new(), 495 oam: BoxedWordArray::new(), 496 oam_parsed: Box::new(array::from_fn(|_| OamEntry::default())), 497 registers: Registers::new(), 498 state: State::new(skip_bios_animation), 499 buffers: Box::new(Buffers::new()), 500 cycles: 0, 501 next_event_cycles: 0, 502 } 503 } 504 505 pub fn step_to(&mut self, cycles: u64, dma: &mut DmaState, scheduler: &mut Scheduler) { 506 if cycles <= self.cycles { 507 return; 508 } 509 510 if cycles < self.next_event_cycles { 511 self.state.dot += (cycles - self.cycles) as u32; 512 self.cycles = cycles; 513 return; 514 } 515 516 self.tick(cycles, dma); 517 scheduler.insert_or_update(SchedulerEvent::PpuEvent, self.next_event_cycles); 518 } 519 520 fn tick(&mut self, cycles: u64, dma: &mut DmaState) { 521 type EventFn = fn(&mut Ppu, &mut DmaState, u64); 522 523 fn render_line(ppu: &mut Ppu, _: &mut DmaState, _: u64) { 524 ppu.render_current_line(); 525 ppu.render_next_sprite_line(); 526 } 527 528 fn hblank_start(ppu: &mut Ppu, dma: &mut DmaState, cycles: u64) { 529 if ppu.state.scanline < SCREEN_HEIGHT { 530 dma.notify_hblank_start(cycles); 531 } 532 } 533 534 fn end_of_line(ppu: &mut Ppu, dma: &mut DmaState, cycles: u64) { 535 ppu.state.dot = 0; 536 537 ppu.state.scanline += 1; 538 match ppu.state.scanline { 539 LINES_PER_FRAME => { 540 ppu.state.scanline = 0; 541 542 // Force reference point re-latch 543 ppu.state.bg_affine_latch.x_written = [true; 2]; 544 ppu.state.bg_affine_latch.y_written = [true; 2]; 545 } 546 SCREEN_HEIGHT => { 547 dma.notify_vblank_start(cycles); 548 549 ppu.state.frame_complete = true; 550 ppu.ready_frame_buffer.copy_from(&ppu.frame_buffer); 551 } 552 162 => { 553 if ppu.state.video_capture_latch { 554 dma.end_video_capture(); 555 } 556 ppu.state.video_capture_latch = dma.video_capture_active(); 557 } 558 _ => {} 559 } 560 561 if ppu.state.scanline < SCREEN_HEIGHT { 562 ppu.state 563 .bg_affine_latch 564 .update_reference_points(&ppu.registers, ppu.state.bg_enabled_latency); 565 } 566 567 if ppu.state.video_capture_latch && (2..162).contains(&ppu.state.scanline) { 568 // Video capture DMA triggers at dot 3 (then begins at dot 5 due to 2-cycle DMA latency) 569 dma.notify_video_capture(cycles + 3); 570 } 571 572 ppu.state.update_mosaic_v_state(&ppu.registers); 573 574 for latency in &mut ppu.state.bg_enabled_latency { 575 *latency = latency.saturating_sub(1); 576 } 577 ppu.state.obj_enabled_latency = ppu.state.obj_enabled_latency.saturating_sub(1); 578 ppu.state.forced_blanking_latency = ppu.state.forced_blanking_latency.saturating_sub(1); 579 580 for (i, window_y_active) in ppu.state.window_y_active.iter_mut().enumerate() { 581 *window_y_active |= ppu.state.scanline == ppu.registers.window_y1[i]; 582 *window_y_active &= ppu.state.scanline != ppu.registers.window_y2[i]; 583 } 584 } 585 586 // Arbitrary dot around the middle of the line 587 const RENDER_DOT: u32 = 526; 588 589 const LINE_EVENTS: &[(u32, EventFn)] = &[ 590 (RENDER_DOT, render_line), 591 (HBLANK_START_DOT + 1, hblank_start), 592 (DOTS_PER_LINE, end_of_line), 593 ]; 594 595 if cycles <= self.cycles { 596 return; 597 } 598 599 let mut elapsed_cycles = (cycles - self.cycles) as u32; 600 let mut event_idx = 0; 601 while elapsed_cycles != 0 { 602 while self.state.dot >= LINE_EVENTS[event_idx].0 { 603 event_idx += 1; 604 } 605 606 let change = cmp::min(elapsed_cycles, LINE_EVENTS[event_idx].0 - self.state.dot); 607 self.state.dot += change; 608 elapsed_cycles -= change; 609 self.cycles += u64::from(change); 610 611 if self.state.dot == LINE_EVENTS[event_idx].0 { 612 (LINE_EVENTS[event_idx].1)(self, dma, self.cycles); 613 614 event_idx += 1; 615 if event_idx == LINE_EVENTS.len() { 616 event_idx = 0; 617 } 618 } 619 } 620 621 self.next_event_cycles = self.cycles + u64::from(LINE_EVENTS[event_idx].0 - self.state.dot); 622 } 623 624 fn render_current_line(&mut self) { 625 if self.state.scanline >= SCREEN_HEIGHT { 626 return; 627 } 628 629 if self.registers.forced_blanking || self.state.forced_blanking_latency != 0 { 630 self.clear_current_line(); 631 return; 632 } 633 634 self.render_bg_layers(); 635 636 self.merge_layers(); 637 } 638 639 fn clear_current_line(&mut self) { 640 const WHITE: u16 = 0x7FFF; 641 642 for pixel in 0..SCREEN_WIDTH { 643 self.frame_buffer.set(self.state.scanline, pixel, WHITE); 644 } 645 } 646 647 #[allow(clippy::match_same_arms)] 648 fn render_bg_layers(&mut self) { 649 match self.registers.bg_mode { 650 BgMode::Zero => { 651 // BG0-3 in text mode 652 for bg in 0..4 { 653 self.render_text_bg(bg); 654 } 655 } 656 BgMode::One => { 657 // BG0-1 in text mode, BG2 in affine mode 658 for bg in 0..2 { 659 self.render_text_bg(bg); 660 } 661 self.render_affine_bg(2, self.affine_sample_tile_map(2)); 662 } 663 BgMode::Two => { 664 // BG2-3 in affine mode 665 for bg in 2..4 { 666 self.render_affine_bg(bg, self.affine_sample_tile_map(bg)); 667 } 668 } 669 BgMode::Three => { 670 // Bitmap mode: 240x160, 15bpp, single frame buffer 671 self.render_affine_bg(2, Self::affine_sample_mode_3); 672 } 673 BgMode::Four => { 674 // Bitmap mode: 240x160, 8bpp, two frame buffers 675 self.render_affine_bg(2, self.affine_sample_mode_4()); 676 } 677 BgMode::Five => { 678 // Bitmap mode: 160x128, 15bpp, two frame buffers 679 self.render_affine_bg(2, self.affine_sample_mode_5()); 680 } 681 BgMode::Invalid(_) => {} 682 } 683 } 684 685 fn render_text_bg(&mut self, bg: usize) { 686 self.buffers.bg_pixels[bg].fill(Pixel::TRANSPARENT); 687 688 if !self.registers.bg_enabled[bg] { 689 return; 690 } 691 692 let bg_control = &self.registers.bg_control[bg]; 693 694 let width_tiles = bg_control.size.text_width_tiles(); 695 let width_screens = width_tiles / 32; 696 let height_tiles = bg_control.size.text_height_tiles(); 697 698 let h_scroll = self.registers.bg_h_scroll[bg]; 699 let fine_h_scroll = h_scroll % 8; 700 let coarse_h_scroll = h_scroll / 8; 701 702 let scanline = 703 if bg_control.mosaic { self.state.mosaic.bg_text_line } else { self.state.scanline }; 704 let v_scroll = self.registers.bg_v_scroll[bg]; 705 let scrolled_line = scanline + v_scroll; 706 707 let (tile_map_row, screen_map_row) = { 708 let tile_map_row = (scrolled_line / 8) & (height_tiles - 1); 709 let screen_map_row = tile_map_row / 32; 710 (tile_map_row % 32, screen_map_row) 711 }; 712 let tile_row = scrolled_line % 8; 713 714 let tile_size_bytes = bg_control.bpp.tile_size_bytes(); 715 716 let end_tile = if fine_h_scroll != 0 { SCREEN_WIDTH / 8 + 1 } else { SCREEN_WIDTH / 8 }; 717 718 for tile_idx in 0..end_tile { 719 let base_pixel = (8 * tile_idx) as i32 - fine_h_scroll as i32; 720 721 let (tile_map_col, screen_map_col) = { 722 let tile_map_col = (tile_idx + coarse_h_scroll) & (width_tiles - 1); 723 let screen_map_col = tile_map_col / 32; 724 (tile_map_col % 32, screen_map_col) 725 }; 726 727 let screen_idx = screen_map_row * width_screens + screen_map_col; 728 let screen_addr = bg_control.tile_map_addr + screen_idx * 2 * 32 * 32; 729 730 let tile_map_addr = screen_addr + 2 * (tile_map_row * 32 + tile_map_col); 731 let tile_map_entry = if tile_map_addr <= 0xFFFF { 732 u16::from_le_bytes([ 733 self.vram[tile_map_addr as usize], 734 self.vram[(tile_map_addr + 1) as usize], 735 ]) 736 } else { 737 // TODO should read VRAM open bus? 738 0 739 }; 740 741 let tile_number: u32 = (tile_map_entry & 0x3FF).into(); 742 let h_flip = tile_map_entry.bit(10); 743 let v_flip = tile_map_entry.bit(11); 744 let palette = match bg_control.bpp { 745 BitsPerPixel::Four => tile_map_entry >> 12, 746 BitsPerPixel::Eight => 0, 747 }; 748 749 let tile_base_addr = bg_control.tile_data_addr + tile_number * tile_size_bytes; 750 let tile_row = if v_flip { 7 - tile_row } else { tile_row }; 751 752 match bg_control.bpp { 753 BitsPerPixel::Four => { 754 let tile_row_addr = tile_base_addr + tile_row * 4; 755 756 for pixel_idx in 0..8 { 757 let pixel = pixel_idx as i32 + base_pixel; 758 if !(0..SCREEN_WIDTH as i32).contains(&pixel) { 759 continue; 760 } 761 let pixel = pixel as usize; 762 763 let tile_col = if h_flip { 7 - pixel_idx } else { pixel_idx }; 764 let tile_addr = tile_row_addr + (tile_col >> 1); 765 766 let tile_byte = if tile_addr <= 0xFFFF { 767 self.vram[tile_addr as usize] 768 } else { 769 // TODO should read VRAM open bus? 770 0 771 }; 772 773 let color_id = (tile_byte >> (4 * (tile_col & 1))) & 0xF; 774 if color_id == 0 { 775 // Transparent pixel 776 continue; 777 } 778 779 let palette_ram_addr = 16 * palette + u16::from(color_id); 780 let color = self.palette_ram[palette_ram_addr as usize]; 781 self.buffers.bg_pixels[bg][pixel] = Pixel::new_opaque(color); 782 } 783 } 784 BitsPerPixel::Eight => { 785 let tile_row_addr = tile_base_addr + tile_row * 8; 786 787 for pixel_idx in 0..8 { 788 let pixel = pixel_idx as i32 + base_pixel; 789 if !(0..SCREEN_WIDTH as i32).contains(&pixel) { 790 continue; 791 } 792 let pixel = pixel as usize; 793 794 let tile_col = if h_flip { 7 - pixel_idx } else { pixel_idx }; 795 let tile_addr = tile_row_addr + tile_col; 796 797 let color_id = if tile_addr <= 0xFFFF { 798 self.vram[tile_addr as usize] 799 } else { 800 // TODO should read VRAM open bus? 801 0 802 }; 803 804 if color_id == 0 { 805 // Transparent pixel 806 continue; 807 } 808 809 let color = self.palette_ram[color_id as usize]; 810 self.buffers.bg_pixels[bg][pixel] = Pixel::new_opaque(color); 811 } 812 } 813 } 814 } 815 816 self.apply_bg_h_mosaic(bg); 817 } 818 819 fn render_affine_bg(&mut self, bg: usize, sample_fn: impl Fn(&Self, i32, i32) -> Pixel) { 820 assert!(bg == 2 || bg == 3); 821 822 self.buffers.bg_pixels[bg].fill(Pixel::TRANSPARENT); 823 824 if !self.registers.bg_enabled[bg] { 825 return; 826 } 827 828 let bg_control = &self.registers.bg_control[bg]; 829 830 let dx = self.registers.bg_affine_parameters[bg - 2].a; 831 let dy = self.registers.bg_affine_parameters[bg - 2].c; 832 833 let bg_affine_latch = if bg_control.mosaic { 834 self.state.mosaic.bg_affine 835 } else { 836 self.state.bg_affine_latch 837 }; 838 let mut x = bg_affine_latch.x[bg - 2]; 839 let mut y = bg_affine_latch.y[bg - 2]; 840 841 for pixel in 0..SCREEN_WIDTH { 842 // Affine coordinates are in 1/256 pixel units - convert to pixel 843 let x_pixel = x >> 8; 844 let y_pixel = y >> 8; 845 846 self.buffers.bg_pixels[bg][pixel as usize] = sample_fn(self, x_pixel, y_pixel); 847 848 x += dx; 849 y += dy; 850 } 851 852 self.apply_bg_h_mosaic(bg); 853 } 854 855 fn affine_sample_tile_map(&self, bg: usize) -> impl Fn(&Self, i32, i32) -> Pixel + 'static { 856 let bg_control = &self.registers.bg_control[bg]; 857 858 let dimension_tiles = bg_control.size.affine_dimension_tiles(); 859 let dimension_pixels = (8 * dimension_tiles) as i32; 860 861 let base_tile_map_addr = bg_control.tile_map_addr; 862 let base_tile_data_addr = bg_control.tile_data_addr; 863 let affine_overflow = bg_control.affine_overflow; 864 865 move |ppu, mut x, mut y| { 866 if !(0..dimension_pixels).contains(&x) || !(0..dimension_pixels).contains(&y) { 867 match affine_overflow { 868 AffineOverflowBehavior::Transparent => return Pixel::TRANSPARENT, 869 AffineOverflowBehavior::Wrap => { 870 x &= dimension_pixels - 1; 871 y &= dimension_pixels - 1; 872 } 873 } 874 } 875 876 let x = x as u32; 877 let y = y as u32; 878 879 let tile_map_row = y / 8; 880 let tile_row = y % 8; 881 882 let tile_map_col = x / 8; 883 let tile_col = x % 8; 884 885 let tile_map_addr = base_tile_map_addr + tile_map_row * dimension_tiles + tile_map_col; 886 let tile_number = if tile_map_addr <= 0xFFFF { 887 ppu.vram[tile_map_addr as usize] 888 } else { 889 // TODO should be VRAM open bus? 890 0 891 }; 892 let tile_number: u32 = tile_number.into(); 893 894 // Affine tiles are always 8bpp 895 let tile_base_addr = base_tile_data_addr + 64 * tile_number; 896 897 // Tile data address will never exceed $FFFF because tile numbers are 8-bit and tile 898 // data base address is in 16KB steps 899 assert!(tile_base_addr <= 0x10000 - 64); 900 901 let tile_row_addr = tile_base_addr + 8 * tile_row; 902 let tile_addr = tile_row_addr + tile_col; 903 let color_id = ppu.vram[tile_addr as usize]; 904 905 if color_id == 0 { 906 return Pixel::TRANSPARENT; 907 } 908 909 let color = ppu.palette_ram[color_id as usize]; 910 Pixel::new_opaque(color) 911 } 912 } 913 914 fn affine_sample_mode_3(&self, x: i32, y: i32) -> Pixel { 915 if !(0..SCREEN_WIDTH as i32).contains(&x) || !(0..SCREEN_HEIGHT as i32).contains(&y) { 916 return Pixel::TRANSPARENT; 917 } 918 919 let x = x as u32; 920 let y = y as u32; 921 922 let pixel_addr = (2 * (y * SCREEN_WIDTH + x)) as usize; 923 let color = u16::from_le_bytes([self.vram[pixel_addr], self.vram[pixel_addr + 1]]); 924 Pixel::new_opaque(color) 925 } 926 927 fn affine_sample_mode_4(&self) -> impl Fn(&Self, i32, i32) -> Pixel + 'static { 928 let fb_addr = self.registers.bitmap_frame_buffer.vram_address(); 929 930 move |ppu, x, y| { 931 if !(0..SCREEN_WIDTH as i32).contains(&x) || !(0..SCREEN_HEIGHT as i32).contains(&y) { 932 return Pixel::TRANSPARENT; 933 } 934 935 let x = x as u32; 936 let y = y as u32; 937 938 let pixel_addr = (fb_addr + y * SCREEN_WIDTH + x) as usize; 939 let color_id = ppu.vram[pixel_addr]; 940 941 if color_id == 0 { 942 return Pixel::TRANSPARENT; 943 } 944 945 let color = ppu.palette_ram[color_id as usize]; 946 Pixel::new_opaque(color) 947 } 948 } 949 950 fn affine_sample_mode_5(&self) -> impl Fn(&Self, i32, i32) -> Pixel + 'static { 951 const MODE_5_WIDTH: u32 = 160; 952 const MODE_5_HEIGHT: u32 = 128; 953 954 let fb_addr = self.registers.bitmap_frame_buffer.vram_address(); 955 956 move |ppu, x, y| { 957 if !(0..MODE_5_WIDTH as i32).contains(&x) || !(0..MODE_5_HEIGHT as i32).contains(&y) { 958 return Pixel::TRANSPARENT; 959 } 960 961 let x = x as u32; 962 let y = y as u32; 963 964 let pixel_addr = (fb_addr + 2 * (y * MODE_5_WIDTH + x)) as usize; 965 let color = u16::from_le_bytes([ppu.vram[pixel_addr], ppu.vram[pixel_addr + 1]]); 966 Pixel::new_opaque(color) 967 } 968 } 969 970 fn apply_bg_h_mosaic(&mut self, bg: usize) { 971 if !self.registers.bg_control[bg].mosaic { 972 return; 973 } 974 975 let mut h_counter = 0; 976 let mut color_latch = self.buffers.bg_pixels[bg][0]; 977 for pixel in 1..SCREEN_WIDTH { 978 if h_counter == self.registers.bg_mosaic_h_size { 979 h_counter = 0; 980 color_latch = self.buffers.bg_pixels[bg][pixel as usize]; 981 } else { 982 h_counter += 1; 983 self.buffers.bg_pixels[bg][pixel as usize] = color_latch; 984 } 985 } 986 } 987 988 fn merge_layers(&mut self) { 989 let backdrop_color = Pixel::new_transparent(self.palette_ram[0]); 990 991 // Alpha blending coefficients 992 let eva: u16 = cmp::min(16, self.registers.blend_alpha_a).into(); 993 let evb: u16 = cmp::min(16, self.registers.blend_alpha_b).into(); 994 995 // Brightness increase/decrease coefficient 996 let evy: u16 = cmp::min(16, self.registers.blend_brightness).into(); 997 998 let bg_enabled: [bool; 4] = array::from_fn(|bg| { 999 self.registers.bg_enabled[bg] 1000 && self.registers.bg_mode.bg_active_in_mode(bg) 1001 && self.state.bg_enabled_latency[bg] == 0 1002 }); 1003 1004 let obj_window_enabled = self.registers.obj_window_enabled; 1005 let any_window_enabled = self.registers.window_enabled[0] 1006 || self.registers.window_enabled[1] 1007 || obj_window_enabled; 1008 1009 let mut window_x_active: [bool; 2] = 1010 array::from_fn(|i| self.registers.window_x1[i] > self.registers.window_x2[i]); 1011 1012 let window_y_active: [bool; 2] = 1013 array::from_fn(|i| self.registers.window_enabled[i] && self.state.window_y_active[i]); 1014 1015 let obj_enabled = self.registers.obj_enabled && self.state.obj_enabled_latency == 0; 1016 1017 let mut obj_mosaic_h_counter = self.registers.obj_mosaic_h_size; 1018 let mut obj_mosaic_latch = ObjPixel::default(); 1019 1020 for pixel in 0..SCREEN_WIDTH { 1021 for (i, window_x_active) in window_x_active.iter_mut().enumerate() { 1022 *window_x_active |= pixel == self.registers.window_x1[i]; 1023 *window_x_active &= pixel != self.registers.window_x2[i]; 1024 } 1025 1026 let window_layers_enabled = if any_window_enabled { 1027 let window = if window_y_active[0] && window_x_active[0] { 1028 Window::Inside0 1029 } else if window_y_active[1] && window_x_active[1] { 1030 Window::Inside1 1031 } else if obj_window_enabled && self.buffers.obj_window[pixel as usize] { 1032 Window::InsideObj 1033 } else { 1034 Window::Outside 1035 }; 1036 self.registers.window_layers_enabled(window) 1037 } else { 1038 WindowEnabled::ALL 1039 }; 1040 1041 let mut first_pixel = MergePixel::backdrop(backdrop_color); 1042 let mut second_pixel = MergePixel::none(); 1043 1044 // When priority value is equal, layer priority is OBJ > BG0 > BG1 > BG2 > BG3 1045 // Process layers in that order 1046 1047 if obj_enabled { 1048 let obj_pixel = self.buffers.obj_pixels[pixel as usize]; 1049 1050 if obj_mosaic_h_counter == self.registers.obj_mosaic_h_size { 1051 obj_mosaic_h_counter = 0; 1052 obj_mosaic_latch = obj_pixel; 1053 } else { 1054 obj_mosaic_h_counter += 1; 1055 1056 // Update the mosaic latch if either the current or latched pixel is not 1057 // mosaic-enabled, or if the current pixel has priority over latched pixel 1058 // e.g. sprite-hmosaic test ROM 1059 if !obj_mosaic_latch.mosaic 1060 || !obj_pixel.mosaic 1061 || obj_pixel.priority < obj_mosaic_latch.priority 1062 { 1063 obj_mosaic_latch = obj_pixel; 1064 } 1065 } 1066 1067 if window_layers_enabled.obj && !obj_mosaic_latch.color.transparent() { 1068 second_pixel = first_pixel; 1069 first_pixel = MergePixel::obj(obj_mosaic_latch); 1070 } 1071 } 1072 1073 for (bg, enabled) in bg_enabled.into_iter().enumerate() { 1074 if !enabled || !window_layers_enabled.bg[bg] { 1075 continue; 1076 } 1077 1078 let bg_pixel = self.buffers.bg_pixels[bg][pixel as usize]; 1079 if bg_pixel.transparent() { 1080 continue; 1081 } 1082 1083 let priority = self.registers.bg_control[bg].priority; 1084 if priority < first_pixel.priority { 1085 second_pixel = first_pixel; 1086 first_pixel = MergePixel::bg(bg, bg_pixel, priority); 1087 } else if priority < second_pixel.priority { 1088 second_pixel = MergePixel::bg(bg, bg_pixel, priority); 1089 } 1090 } 1091 1092 let mut blend_color = first_pixel.color; 1093 1094 // Semi-transparency takes priority over normal blending effect if the condition is true 1095 if first_pixel.semi_transparent 1096 && second_pixel.layer.is_2nd_target_enabled(&self.registers) 1097 { 1098 blend_color = alpha_blend(first_pixel.color, second_pixel.color, eva, evb); 1099 } else if window_layers_enabled.blend 1100 && first_pixel.layer.is_1st_target_enabled(&self.registers) 1101 { 1102 match self.registers.blend_mode { 1103 BlendMode::AlphaBlending => { 1104 if second_pixel.layer.is_2nd_target_enabled(&self.registers) { 1105 blend_color = 1106 alpha_blend(first_pixel.color, second_pixel.color, eva, evb); 1107 } 1108 } 1109 BlendMode::BrightnessIncrease => { 1110 blend_color = adjust_brightness::<true>(first_pixel.color, evy); 1111 } 1112 BlendMode::BrightnessDecrease => { 1113 blend_color = adjust_brightness::<false>(first_pixel.color, evy); 1114 } 1115 BlendMode::None => {} 1116 } 1117 } 1118 1119 self.frame_buffer.set(self.state.scanline, pixel, blend_color.0); 1120 } 1121 1122 if self.registers.green_swap { 1123 self.green_swap_line(); 1124 } 1125 } 1126 1127 fn green_swap_line(&mut self) { 1128 let scanline_addr = (self.state.scanline * SCREEN_WIDTH) as usize; 1129 for [c0, c1] in self.frame_buffer.0[scanline_addr..scanline_addr + SCREEN_WIDTH as usize] 1130 .as_chunks_mut::<2>() 1131 .0 1132 { 1133 let g0 = *c0 & (0x1F << 5); 1134 let g1 = *c1 & (0x1F << 5); 1135 1136 *c0 = (*c0 & !(0x1F << 5)) | g1; 1137 *c1 = (*c1 & !(0x1F << 5)) | g0; 1138 } 1139 } 1140 1141 #[allow(clippy::many_single_char_names)] 1142 fn render_next_sprite_line(&mut self) { 1143 if self.registers.forced_blanking 1144 || (self.state.scanline >= SCREEN_HEIGHT && self.state.scanline != LINES_PER_FRAME - 1) 1145 { 1146 return; 1147 } 1148 1149 let is_bitmap_mode = self.registers.bg_mode.is_bitmap(); 1150 1151 self.buffers.obj_pixels.fill(ObjPixel::default()); 1152 self.buffers.obj_window.fill(false); 1153 1154 let target_line = 1155 if self.state.scanline == LINES_PER_FRAME - 1 { 0 } else { self.state.scanline + 1 }; 1156 1157 // One memory access every 2 cycles 1158 // When OAM is free during HBlank, sprite rendering runs from dots 40 to 1006 (HBlank start) 1159 let mut memory_accesses = if self.registers.oam_free_during_hblank { 1160 // -3 based on Sprite_Last_VRAM_Access_Free test ROM 1161 (HBLANK_START_DOT - 40) / 2 - 3 1162 } else { 1163 DOTS_PER_LINE / 2 1164 }; 1165 1166 'outer: for oam_idx in 0..128 { 1167 // 32-bit OAM read of first two attribute words 1168 memory_accesses -= 1; 1169 if memory_accesses == 0 { 1170 break 'outer; 1171 } 1172 1173 let oam_entry = &self.oam_parsed[oam_idx as usize]; 1174 1175 if oam_entry.disabled { 1176 continue; 1177 } 1178 1179 let (sprite_width, sprite_height) = oam_entry.shape.size_pixels(oam_entry.size); 1180 let display_width = sprite_width << u8::from(oam_entry.affine_double_size); 1181 let display_height = sprite_height << u8::from(oam_entry.affine_double_size); 1182 1183 if oam_entry.x >= SCREEN_WIDTH && oam_entry.x + display_width <= 512 { 1184 // Sprite is fully horizontally offscreen 1185 continue; 1186 } 1187 1188 if oam_entry.y + display_height > 256 && target_line > 128 { 1189 // 128px tall sprites with Y>128 never display on lines >128 1190 continue; 1191 } 1192 1193 let sprite_y = { 1194 let mut sprite_y = target_line.wrapping_sub(oam_entry.y) & 0xFF; 1195 if sprite_y >= display_height { 1196 // Sprite does not overlap this scanline 1197 continue; 1198 } 1199 1200 if oam_entry.mosaic { 1201 let mosaic_line = self.state.mosaic.obj_line; 1202 sprite_y = mosaic_line.wrapping_sub(oam_entry.y) & 0xFF; 1203 if sprite_y >= display_height { 1204 // If mosaic moves the Y coordinate out of bounds, clamp to 0 1205 // e.g. Castlevania: Aria of Sorrow, Shrek 2, sprite-vmosaic test ROM 1206 sprite_y = 0; 1207 } 1208 } 1209 1210 sprite_y 1211 }; 1212 1213 // 16-bit OAM read of third attribute word 1214 memory_accesses -= 1; 1215 if memory_accesses == 0 { 1216 break 'outer; 1217 } 1218 1219 let oam_entry = oam_entry.clone(); 1220 1221 if oam_entry.affine { 1222 // 4 OAM reads for the affine parameters plus 1 idle slot at the beginning of VRAM fetch 1223 memory_accesses = memory_accesses.saturating_sub(5); 1224 if memory_accesses == 0 { 1225 break 'outer; 1226 } 1227 1228 let group_base_addr = 16 * oam_entry.affine_parameter_group as usize; 1229 let [a, b, c, d] = [ 1230 self.oam[group_base_addr + 3], 1231 self.oam[group_base_addr + 7], 1232 self.oam[group_base_addr + 11], 1233 self.oam[group_base_addr + 15], 1234 ] 1235 .map(|p| i32::from(p as i16)); 1236 1237 let half_sprite_width = (sprite_width / 2) as i32; 1238 let half_sprite_height = (sprite_height / 2) as i32; 1239 let half_display_width = (display_width / 2) as i32; 1240 let half_display_height = (display_height / 2) as i32; 1241 1242 let y_offset = (sprite_y as i32) - half_display_height; 1243 let x_offset = -half_display_width; 1244 1245 let mut x = a * x_offset + b * y_offset - a; 1246 let mut y = c * x_offset + d * y_offset - c; 1247 1248 let start_x = if oam_entry.x < SCREEN_WIDTH { 0 } else { 512 - oam_entry.x }; 1249 1250 for sprite_x in 0..display_width { 1251 x += a; 1252 y += c; 1253 1254 if sprite_x < start_x { 1255 // Pixel is offscreen to left of frame; PPU skips VRAM fetch 1256 continue; 1257 } 1258 1259 // 1 VRAM read per pixel for affine sprites 1260 memory_accesses -= 1; 1261 if memory_accesses == 0 { 1262 break 'outer; 1263 } 1264 1265 let pixel = (oam_entry.x + sprite_x) & 0x1FF; 1266 if !(0..SCREEN_WIDTH).contains(&pixel) { 1267 // Pixel is offscreen to right of frame; PPU does _not_ skip VRAM fetch 1268 // Famicom Mini - Metroid depends on this 1269 continue; 1270 } 1271 1272 let sample_x = (x >> 8) + half_sprite_width; 1273 let sample_y = (y >> 8) + half_sprite_height; 1274 1275 if !(0..sprite_width as i32).contains(&sample_x) 1276 || !(0..sprite_height as i32).contains(&sample_y) 1277 { 1278 // Sampling point is out of bounds; pixel is transparent 1279 continue; 1280 } 1281 1282 self.render_sprite_pixel( 1283 pixel, 1284 &oam_entry, 1285 sample_x as u32, 1286 sample_y as u32, 1287 sprite_width, 1288 is_bitmap_mode, 1289 ); 1290 } 1291 } else { 1292 // Non-affine sprite 1293 let sample_y = 1294 if oam_entry.v_flip { sprite_height - 1 - sprite_y } else { sprite_y }; 1295 1296 // Skip over VRAM fetches for pixels that are offscreen to left of frame, aligned to a 2-pixel boundary 1297 let start_x = if oam_entry.x < SCREEN_WIDTH { 0 } else { (512 - oam_entry.x) & !1 }; 1298 1299 for sprite_x in start_x..sprite_width { 1300 // 1 VRAM read per 2 pixels for non-affine sprites 1301 if sprite_x & 1 == 0 { 1302 memory_accesses -= 1; 1303 if memory_accesses == 0 { 1304 break 'outer; 1305 } 1306 } 1307 1308 let pixel = (oam_entry.x + sprite_x) & 0x1FF; 1309 if !(0..SCREEN_WIDTH).contains(&pixel) { 1310 // Sprite pixel is offscreen 1311 continue; 1312 } 1313 1314 let sample_x = 1315 if oam_entry.h_flip { sprite_width - 1 - sprite_x } else { sprite_x }; 1316 1317 self.render_sprite_pixel( 1318 pixel, 1319 &oam_entry, 1320 sample_x, 1321 sample_y, 1322 sprite_width, 1323 is_bitmap_mode, 1324 ); 1325 } 1326 } 1327 1328 // Next 2 OAM reads overlap with VRAM reads from the previous sprite 1329 memory_accesses += 2; 1330 } 1331 } 1332 1333 fn render_sprite_pixel( 1334 &mut self, 1335 pixel: u32, 1336 oam_entry: &OamEntry, 1337 sample_x: u32, 1338 sample_y: u32, 1339 sprite_width: u32, 1340 is_bitmap_mode: bool, 1341 ) { 1342 let map_step = match oam_entry.bpp { 1343 BitsPerPixel::Four => 1, 1344 BitsPerPixel::Eight => 2, 1345 }; 1346 1347 let sprite_tile_x = sample_x / 8; 1348 let sprite_tile_y = sample_y / 8; 1349 1350 let tile_number = match self.registers.obj_vram_map_dimensions { 1351 ObjVramMapDimensions::Two => { 1352 let map_col = (oam_entry.tile_number + sprite_tile_x * map_step) & 0x1F; 1353 let map_row = (oam_entry.tile_number + sprite_tile_y * 32) & 0x3FF & !0x1F; 1354 map_row | map_col 1355 } 1356 ObjVramMapDimensions::One => { 1357 let sprite_width_tiles = sprite_width / 8; 1358 let map_row_width = map_step * sprite_width_tiles; 1359 (oam_entry.tile_number + sprite_tile_y * map_row_width + sprite_tile_x * map_step) 1360 & 0x3FF 1361 } 1362 }; 1363 1364 if is_bitmap_mode && tile_number < 512 { 1365 // Sprite tile numbers 0-511 are not usable in bitmap modes; tiles are fully transparent 1366 return; 1367 } 1368 1369 let tile_col = sample_x % 8; 1370 let tile_row = sample_y % 8; 1371 let tile_base_addr = 0x10000 | (tile_number * 32); 1372 1373 let color_id = match oam_entry.bpp { 1374 BitsPerPixel::Four => { 1375 let tile_addr = tile_base_addr + 4 * tile_row + (tile_col >> 1); 1376 let tile_byte = self.vram[tile_addr as usize]; 1377 (tile_byte >> (4 * (tile_col & 1))) & 0xF 1378 } 1379 BitsPerPixel::Eight => { 1380 let tile_addr = tile_base_addr + 8 * tile_row + tile_col; 1381 if tile_addr <= 0x17FFF { 1382 self.vram[tile_addr as usize] 1383 } else { 1384 // TODO what should this do? can happen when using an odd tile number 1385 0 1386 } 1387 } 1388 }; 1389 1390 if oam_entry.mode == SpriteMode::ObjWindow && color_id != 0 { 1391 // Opaque OBJ window pixel; mark OBJ window and don't update any other buffers 1392 self.buffers.obj_window[pixel as usize] = true; 1393 return; 1394 } 1395 1396 let buffer_pixel = &mut self.buffers.obj_pixels[pixel as usize]; 1397 1398 if oam_entry.priority >= buffer_pixel.priority && !buffer_pixel.color.transparent() { 1399 // Existing opaque pixel with the same or lower priority; do nothing 1400 return; 1401 } 1402 1403 // Always update priority and mosaic flags here because of a hardware bug: 1404 // A transparent pixel that overlaps an opaque pixel from a sprite with lower OAM index and 1405 // higher priority will overwrite the priority and mosaic flags 1406 buffer_pixel.priority = oam_entry.priority; 1407 buffer_pixel.mosaic = oam_entry.mosaic; 1408 1409 if color_id == 0 { 1410 // Transparent pixel; don't update color or semi-transparency flag 1411 return; 1412 } 1413 1414 let palette = match oam_entry.bpp { 1415 BitsPerPixel::Four => oam_entry.palette, 1416 BitsPerPixel::Eight => 0, 1417 }; 1418 let palette_ram_addr = 0x100 | (16 * palette + u16::from(color_id)); 1419 let color = self.palette_ram[palette_ram_addr as usize]; 1420 1421 buffer_pixel.color = Pixel::new_opaque(color); 1422 buffer_pixel.semi_transparent = oam_entry.mode == SpriteMode::SemiTransparent; 1423 } 1424 1425 pub fn frame_complete(&self) -> bool { 1426 self.state.frame_complete 1427 } 1428 1429 pub fn clear_frame_complete(&mut self) { 1430 self.state.frame_complete = false; 1431 } 1432 1433 pub fn frame_buffer(&self) -> &[Color] { 1434 self.ready_frame_buffer.0.as_slice() 1435 } 1436 1437 fn mask_vram_address(address: u32) -> usize { 1438 let vram_addr = (address as usize) & VRAM_ADDR_MASK & !1; 1439 if vram_addr & 0x10000 != 0 { 0x10000 | (vram_addr & 0x7FFF) } else { vram_addr } 1440 } 1441 1442 fn should_ignore_vram_access(&self, address: u32) -> bool { 1443 // When the PPU is in a bitmap mode, accesses to mirrored VRAM at $18000-$1C000 do not work (vram-mirror test ROM) 1444 // Reads always return 0 (or open bus?) and writes are discarded 1445 self.registers.bg_mode.is_bitmap() && (0x18000..0x1C000).contains(&(address & 0x1FFFF)) 1446 } 1447 1448 pub fn read_vram(&self, address: u32) -> u16 { 1449 if self.should_ignore_vram_access(address) { 1450 return 0; 1451 } 1452 1453 let vram_addr = Self::mask_vram_address(address); 1454 u16::from_le_bytes(self.vram[vram_addr..vram_addr + 2].try_into().unwrap()) 1455 } 1456 1457 pub fn write_vram(&mut self, address: u32, value: u16) { 1458 if self.should_ignore_vram_access(address) { 1459 return; 1460 } 1461 1462 let vram_addr = Self::mask_vram_address(address); 1463 self.vram[vram_addr..vram_addr + 2].copy_from_slice(&value.to_le_bytes()); 1464 } 1465 1466 pub fn write_vram_byte(&mut self, address: u32, value: u8) { 1467 let in_obj_vram = if self.registers.bg_mode.is_bitmap() { 1468 // $14000-$17FFF 1469 address & 0x10000 != 0 && address & 0x04000 != 0 1470 } else { 1471 // $10000-$17FFF 1472 address & 0x10000 != 0 1473 }; 1474 1475 if in_obj_vram { 1476 // 8-bit writes to OBJ VRAM are ignored 1477 return; 1478 } 1479 1480 // 8-bit writes to BG VRAM duplicate the byte 1481 self.write_vram(address & !1, u16::from_le_bytes([value; 2])); 1482 } 1483 1484 pub fn read_palette_ram(&self, address: u32) -> u16 { 1485 let palette_ram_addr = ((address >> 1) as usize) & (PALETTE_RAM_LEN_HALFWORDS - 1); 1486 self.palette_ram[palette_ram_addr] 1487 } 1488 1489 pub fn write_palette_ram(&mut self, address: u32, value: u16) { 1490 let palette_ram_addr = ((address >> 1) as usize) & (PALETTE_RAM_LEN_HALFWORDS - 1); 1491 self.palette_ram[palette_ram_addr] = value; 1492 } 1493 1494 pub fn read_oam(&self, address: u32) -> u16 { 1495 let oam_addr = ((address >> 1) as usize) & (OAM_LEN_HALFWORDS - 1); 1496 self.oam[oam_addr] 1497 } 1498 1499 pub fn write_oam(&mut self, address: u32, value: u16) { 1500 let oam_addr = ((address >> 1) as usize) & (OAM_LEN_HALFWORDS - 1); 1501 self.oam[oam_addr] = value; 1502 1503 if address & 3 != 3 { 1504 let oam_idx = oam_addr >> 2; 1505 self.oam_parsed[oam_idx] = OamEntry::parse([ 1506 self.oam[4 * oam_idx], 1507 self.oam[4 * oam_idx + 1], 1508 self.oam[4 * oam_idx + 2], 1509 ]); 1510 } 1511 } 1512 1513 // TODO this is not accurate 1514 // PPU usually performs 1 palette RAM access per 4 cycles, but it will perform a second access 1515 // if it needs to for alpha blending. 1516 // In modes 3 and 5, it also skips accesses where the layer is BG2 (direct color bitmap) 1517 pub fn palette_ram_in_use(&self) -> bool { 1518 const RENDER_START_DOT: u32 = 46; 1519 1520 !self.registers.forced_blanking 1521 && self.state.scanline < SCREEN_HEIGHT 1522 && (RENDER_START_DOT..HBLANK_START_DOT).contains(&self.state.dot) 1523 && self.state.dot.is_multiple_of(4) 1524 } 1525 1526 pub fn vram_in_use(&self, address: u32) -> bool { 1527 if self.registers.forced_blanking || self.state.forced_blanking_latency != 0 { 1528 return false; 1529 } 1530 1531 let sprite_vram_start = if self.registers.bg_mode.is_bitmap() { 0x14000 } else { 0x10000 }; 1532 if address & 0x1FFFF < sprite_vram_start { 1533 self.bg_vram_in_use() 1534 } else { 1535 self.sprite_vram_in_use() 1536 } 1537 } 1538 1539 fn bg_vram_in_use(&self) -> bool { 1540 const FETCH_START_DOT: u32 = 30; 1541 1542 if self.state.scanline >= SCREEN_HEIGHT || self.state.dot >= HBLANK_START_DOT { 1543 // No BG fetching during VBlank or HBlank 1544 return false; 1545 } 1546 1547 let is_bg_enabled = 1548 |bg: usize| self.registers.bg_enabled[bg] && self.state.bg_enabled_latency[bg] == 0; 1549 1550 // Text BG access pattern (32-cycle batches): 1551 // 4bpp: M--- T--- ---- ---- ---- T--- ---- ---- 1552 // 8bpp: M--- T--- ---- T--- ---- T--- ---- T--- 1553 // In mode 0, slots constantly cycle: BG0, BG1, BG2, BG3, BG0, BG1, etc. 1554 // In mode 1, the BG2 and BG3 slots are both used for affine BG2 1555 let check_text_bg = |bg: usize| { 1556 if !is_bg_enabled(bg) { 1557 return false; 1558 } 1559 1560 // Fetching starts earlier if BG is using fine horizontal scrolling 1561 let start_dot = 1562 FETCH_START_DOT + (bg as u32) - 4 * (self.registers.bg_h_scroll[bg] % 8); 1563 if self.state.dot < start_dot { 1564 return false; 1565 } 1566 1567 let offset = (self.state.dot - start_dot) % 32; 1568 if !offset.is_multiple_of(4) { 1569 return false; 1570 } 1571 1572 // Check slots used for both 4bpp and 8bpp accesses 1573 let slot = offset / 4; 1574 if slot == 0 || slot == 1 || slot == 5 { 1575 return true; 1576 } 1577 1578 // If 8bpp, additionally check slots used for only 8bpp accesses 1579 self.registers.bg_control[bg].bpp == BitsPerPixel::Eight && (slot == 3 || slot == 7) 1580 }; 1581 1582 // Affine BGs access during every cycle if enabled 1583 // In mode 1, alternates between 2 cycles of BG0/1 (text), 2 cycles of BG2, 2 cycles of BG0/1, etc. 1584 // In mode 2, alternates between 2 cycles of BG3, 2 cycles of BG2, 2 cycles of BG3, etc. 1585 let check_affine_bg = |bg: usize| is_bg_enabled(bg) && self.state.dot >= FETCH_START_DOT; 1586 1587 let offset = self.state.dot % 4; 1588 match self.registers.bg_mode { 1589 BgMode::Zero => check_text_bg(offset as usize), 1590 BgMode::One => { 1591 if offset < 2 { 1592 check_text_bg(offset as usize) 1593 } else { 1594 check_affine_bg(2) 1595 } 1596 } 1597 BgMode::Two => check_affine_bg(2 + usize::from(offset < 2)), 1598 _ => { 1599 // Bitmap modes fetch from VRAM every 4th cycle 1600 offset == 3 && self.state.dot >= FETCH_START_DOT 1601 } 1602 } 1603 } 1604 1605 // TODO this is not entirely accurate - some even cycles only perform an OAM access, and some 1606 // even cycles don't perform an access at all (e.g. for affine sprites) 1607 fn sprite_vram_in_use(&self) -> bool { 1608 const FETCH_START_DOT: u32 = 40; 1609 1610 if !self.registers.obj_enabled || self.state.obj_enabled_latency != 0 { 1611 return false; 1612 } 1613 1614 if !self.state.dot.is_multiple_of(2) { 1615 // Sprite hardware only accesses VRAM/OAM on even cycles 1616 return false; 1617 } 1618 1619 if (SCREEN_HEIGHT..LINES_PER_FRAME - 1).contains(&self.state.scanline) { 1620 // VBlank lines; sprite hardware is idle 1621 return false; 1622 } 1623 1624 if (self.state.scanline == SCREEN_HEIGHT - 1 && self.state.dot >= FETCH_START_DOT) 1625 || (self.state.scanline == LINES_PER_FRAME - 1 && self.state.dot < FETCH_START_DOT) 1626 { 1627 // Too late in the last line (159) or too early in the first line (227) 1628 return false; 1629 } 1630 1631 let interval = if self.registers.oam_free_during_hblank { 1632 FETCH_START_DOT..HBLANK_START_DOT 1633 } else { 1634 0..DOTS_PER_LINE 1635 }; 1636 1637 interval.contains(&self.state.dot) 1638 } 1639 1640 pub fn read_register( 1641 &mut self, 1642 address: u32, 1643 cycles: u64, 1644 dma: &mut DmaState, 1645 scheduler: &mut Scheduler, 1646 ) -> Option<u16> { 1647 self.step_to(cycles, dma, scheduler); 1648 1649 log::trace!("PPU register read {address:08X}"); 1650 1651 let value = match address { 1652 0x4000000 => self.registers.read_dispcnt(), 1653 0x4000002 => self.registers.read_green_swap(), 1654 0x4000004 => self.read_dispstat(), 1655 0x4000006 => self.state.scanline as u16, 1656 0x4000008..=0x400000E => { 1657 let bg = (address & 7) >> 1; 1658 self.registers.read_bgcnt(bg as usize) 1659 } 1660 0x4000048 => self.registers.read_winin(), 1661 0x400004A => self.registers.read_winout(), 1662 0x4000050 => self.registers.read_bldcnt(), 1663 0x4000052 => self.registers.read_bldalpha(), 1664 _ => { 1665 log::debug!("Unhandled PPU register read {address:08X}"); 1666 return None; 1667 } 1668 }; 1669 1670 Some(value) 1671 } 1672 1673 // $4000004: DISPSTAT (Display status) 1674 fn read_dispstat(&self) -> u16 { 1675 let in_vblank = self.in_vblank(); 1676 let in_hblank = self.in_hblank(); 1677 let v_counter_match = self.v_counter_for_dispstat() == self.registers.v_counter_match; 1678 1679 u16::from(in_vblank) 1680 | (u16::from(in_hblank) << 1) 1681 | (u16::from(v_counter_match) << 2) 1682 | (u16::from(self.registers.vblank_irq_enabled) << 3) 1683 | (u16::from(self.registers.hblank_irq_enabled) << 4) 1684 | (u16::from(self.registers.v_counter_irq_enabled) << 5) 1685 | (u16::from(self.registers.v_counter_match) << 8) 1686 } 1687 1688 // $4000004: DISPSTAT (Display status) 1689 fn write_dispstat( 1690 &mut self, 1691 value: u16, 1692 cycles: u64, 1693 interrupts: &mut InterruptRegisters, 1694 scheduler: &mut Scheduler, 1695 ) { 1696 let prev_vblank_enabled = self.registers.vblank_irq_enabled; 1697 let prev_hblank_enabled = self.registers.hblank_irq_enabled; 1698 let prev_v_count_enabled = self.registers.v_counter_irq_enabled; 1699 let prev_v_counter_match = self.registers.v_counter_match; 1700 1701 let v_counter = self.v_counter_for_dispstat(); 1702 let prev_v_match = self.registers.v_counter_match == v_counter; 1703 1704 self.registers.write_dispstat(value); 1705 1706 // Changing VCOUNT match mid-line can trigger VCOUNT match IRQs 1707 // e.g. lyc_midline and window_midframe test ROMs 1708 // TODO is it right that this only happens if VCOUNT enabled status doesn't change? 1709 if prev_v_count_enabled 1710 && self.registers.v_counter_irq_enabled 1711 && !prev_v_match 1712 && self.registers.v_counter_match == v_counter 1713 { 1714 interrupts.set_flag(InterruptType::VCounter, cycles + 1); 1715 } 1716 1717 if prev_vblank_enabled != self.registers.vblank_irq_enabled { 1718 self.schedule_vblank_irq(scheduler); 1719 } 1720 1721 if prev_hblank_enabled != self.registers.hblank_irq_enabled { 1722 self.schedule_hblank_irq(scheduler); 1723 } 1724 1725 if prev_v_count_enabled != self.registers.v_counter_irq_enabled 1726 || prev_v_counter_match != self.registers.v_counter_match 1727 { 1728 self.schedule_v_counter_irq(scheduler); 1729 } 1730 } 1731 1732 fn in_vblank(&self) -> bool { 1733 VBLANK_LINES.contains(&self.state.scanline) 1734 } 1735 1736 fn in_hblank(&self) -> bool { 1737 (HBLANK_START_DOT + 1..DOTS_PER_LINE).contains(&self.state.dot) 1738 } 1739 1740 fn v_counter_for_dispstat(&self) -> u8 { 1741 // V counter match bit in DISPSTAT changes at dot 1 1742 let line = if self.state.dot == 0 { 1743 if self.state.scanline == 0 { LINES_PER_FRAME - 1 } else { self.state.scanline - 1 } 1744 } else { 1745 self.state.scanline 1746 }; 1747 1748 line as u8 1749 } 1750 1751 #[allow(clippy::match_same_arms)] 1752 pub fn write_register( 1753 &mut self, 1754 address: u32, 1755 value: u16, 1756 cycles: u64, 1757 dma: &mut DmaState, 1758 interrupts: &mut InterruptRegisters, 1759 scheduler: &mut Scheduler, 1760 ) { 1761 log::debug!( 1762 "PPU register write {address:08X} {value:04X} (line {} dot {})", 1763 self.state.scanline, 1764 self.state.dot 1765 ); 1766 1767 self.step_to(cycles, dma, scheduler); 1768 1769 match address { 1770 0x4000000 => self.registers.write_dispcnt(value, &mut self.state), 1771 0x4000002 => self.registers.write_green_swap(value), 1772 0x4000004 => self.write_dispstat(value, cycles, interrupts, scheduler), 1773 0x4000006 => {} // High halfword of word-size writes to DISPSTAT 1774 0x4000008..=0x400000E => { 1775 // BGxCNT 1776 let bg = (address & 7) >> 1; 1777 self.registers.write_bgcnt(bg as usize, value); 1778 } 1779 0x4000010..=0x400001E => { 1780 // BGxHOFS / BGxVOFS 1781 let bg = (address & 0xF) >> 2; 1782 if !address.bit(1) { 1783 self.registers.write_bghofs(bg as usize, value); 1784 } else { 1785 self.registers.write_bgvofs(bg as usize, value); 1786 } 1787 } 1788 0x4000020..=0x400003E => self.registers.write_bg_affine_register( 1789 address, 1790 value, 1791 &mut self.state.bg_affine_latch, 1792 ), 1793 0x4000040 => self.registers.write_winh(0, value), 1794 0x4000042 => self.registers.write_winh(1, value), 1795 0x4000044 => self.registers.write_winv(0, value), 1796 0x4000046 => self.registers.write_winv(1, value), 1797 0x4000048 => self.registers.write_winin(value), 1798 0x400004A => self.registers.write_winout(value), 1799 0x400004C => self.registers.write_mosaic(value), 1800 0x400004E => {} // High halfword of word-size writes to MOSAIC 1801 0x4000050 => self.registers.write_bldcnt(value), 1802 0x4000052 => self.registers.write_bldalpha(value), 1803 0x4000054 => self.registers.write_bldy(value), 1804 0x4000056 => {} // High halfword of word-size writes to BLDY 1805 _ => { 1806 log::debug!("Unhandled PPU register write {address:08X} {value:04X}"); 1807 } 1808 } 1809 } 1810 1811 pub fn write_register_byte( 1812 &mut self, 1813 address: u32, 1814 value: u8, 1815 cycles: u64, 1816 dma: &mut DmaState, 1817 interrupts: &mut InterruptRegisters, 1818 scheduler: &mut Scheduler, 1819 ) { 1820 trait U16Ext { 1821 fn set_byte(&mut self, i: bool, value: u8); 1822 } 1823 1824 impl U16Ext for u16 { 1825 fn set_byte(&mut self, i: bool, value: u8) { 1826 if !i { 1827 self.set_lsb(value); 1828 } else { 1829 self.set_msb(value); 1830 } 1831 } 1832 } 1833 1834 self.step_to(cycles, dma, scheduler); 1835 1836 // TODO BGxHOFS, BGxVOFS, MOSAIC, blend registers 1837 match address { 1838 0x4000000..=0x4000005 1839 | 0x4000008..=0x400000F 1840 | 0x4000048..=0x400004B 1841 | 0x4000050..=0x4000053 => { 1842 // R/W registers: DISPCNT, green swap, DISPSTAT, BGxCNT, WININ, WINOUT, BLDCNT, BLDALPHA 1843 let Some(mut halfword) = self.read_register(address & !1, cycles, dma, scheduler) 1844 else { 1845 return; 1846 }; 1847 halfword.set_byte(address.bit(0), value); 1848 self.write_register(address & !1, halfword, cycles, dma, interrupts, scheduler); 1849 } 1850 0x4000010..=0x400001F => { 1851 // BGxHOFS / BGxVOFS 1852 let bg = ((address >> 2) & 3) as usize; 1853 if !address.bit(1) { 1854 let mut hofs = self.registers.bg_h_scroll[bg] as u16; 1855 hofs.set_byte(address.bit(0), value); 1856 self.registers.write_bghofs(bg, hofs); 1857 } else { 1858 let mut vofs = self.registers.bg_v_scroll[bg] as u16; 1859 vofs.set_byte(address.bit(0), value); 1860 self.registers.write_bgvofs(bg, vofs); 1861 } 1862 } 1863 0x4000020..=0x400003F => { 1864 // BG affine registers 1865 let mut halfword = self.registers.read_bg_affine_register(address & !1); 1866 halfword.set_byte(address.bit(0), value); 1867 self.registers.write_bg_affine_register( 1868 address & !1, 1869 halfword, 1870 &mut self.state.bg_affine_latch, 1871 ); 1872 } 1873 0x4000040 => self.registers.write_winh_low(0, value), 1874 0x4000041 => self.registers.write_winh_high(0, value), 1875 0x4000042 => self.registers.write_winh_low(1, value), 1876 0x4000043 => self.registers.write_winh_high(1, value), 1877 0x4000044 => self.registers.write_winv_low(0, value), 1878 0x4000045 => self.registers.write_winv_high(0, value), 1879 0x4000046 => self.registers.write_winv_low(1, value), 1880 0x4000047 => self.registers.write_winv_high(1, value), 1881 0x400004C => self.registers.write_bg_mosaic(value), 1882 0x400004D => self.registers.write_obj_mosaic(value), 1883 0x4000054 => self.registers.write_bldy(value.into()), // BLDY is only a 5-bit register 1884 _ => { 1885 log::debug!("Unexpected PPU byte register write {address:08X} {value:02X}"); 1886 } 1887 } 1888 } 1889 1890 fn schedule_vblank_irq(&self, scheduler: &mut Scheduler) { 1891 if !self.registers.vblank_irq_enabled { 1892 scheduler.remove(SchedulerEvent::VBlankIrq); 1893 return; 1894 } 1895 1896 let irq_cycles = self.cycles + self.cycles_until_dot(SCREEN_HEIGHT, VBLANK_IRQ_DOT); 1897 scheduler.insert_or_update(SchedulerEvent::VBlankIrq, irq_cycles); 1898 } 1899 1900 pub fn schedule_next_vblank_irq(&self, scheduler: &mut Scheduler, last_irq_cycles: u64) { 1901 debug_assert!(self.registers.vblank_irq_enabled); 1902 1903 let next_irq_cycles = last_irq_cycles + u64::from(DOTS_PER_LINE * LINES_PER_FRAME); 1904 scheduler.insert_or_update(SchedulerEvent::VBlankIrq, next_irq_cycles); 1905 } 1906 1907 fn schedule_hblank_irq(&self, scheduler: &mut Scheduler) { 1908 if !self.registers.hblank_irq_enabled { 1909 scheduler.remove(SchedulerEvent::HBlankIrq); 1910 return; 1911 } 1912 1913 let cycles_until_irq = if self.state.dot >= HBLANK_IRQ_DOT { 1914 DOTS_PER_LINE + HBLANK_IRQ_DOT - self.state.dot 1915 } else { 1916 HBLANK_IRQ_DOT - self.state.dot 1917 }; 1918 scheduler 1919 .insert_or_update(SchedulerEvent::HBlankIrq, self.cycles + u64::from(cycles_until_irq)); 1920 } 1921 1922 pub fn schedule_next_hblank_irq(&self, scheduler: &mut Scheduler, last_irq_cycles: u64) { 1923 debug_assert!(self.registers.hblank_irq_enabled); 1924 1925 let next_irq_cycles = last_irq_cycles + u64::from(DOTS_PER_LINE); 1926 scheduler.insert_or_update(SchedulerEvent::HBlankIrq, next_irq_cycles); 1927 } 1928 1929 fn schedule_v_counter_irq(&self, scheduler: &mut Scheduler) { 1930 if !self.registers.v_counter_irq_enabled 1931 || self.registers.v_counter_match >= LINES_PER_FRAME as u8 1932 { 1933 scheduler.remove(SchedulerEvent::VCounterIrq); 1934 return; 1935 } 1936 1937 let irq_cycles = self.cycles 1938 + self.cycles_until_dot(self.registers.v_counter_match.into(), V_COUNTER_IRQ_DOT); 1939 scheduler.insert_or_update(SchedulerEvent::VCounterIrq, irq_cycles); 1940 } 1941 1942 pub fn schedule_next_v_counter_irq(&self, scheduler: &mut Scheduler, last_irq_cycles: u64) { 1943 debug_assert!(self.registers.v_counter_irq_enabled); 1944 1945 let next_irq_cycles = last_irq_cycles + u64::from(DOTS_PER_LINE * LINES_PER_FRAME); 1946 scheduler.insert_or_update(SchedulerEvent::VCounterIrq, next_irq_cycles); 1947 } 1948 1949 fn cycles_until_dot(&self, line: u32, dot: u32) -> u64 { 1950 let cycles = if self.state.dot < dot { 1951 let full_lines = if self.state.scanline > line { 1952 LINES_PER_FRAME + line - self.state.scanline 1953 } else { 1954 line - self.state.scanline 1955 }; 1956 dot - self.state.dot + full_lines * DOTS_PER_LINE 1957 } else { 1958 let full_lines = if self.state.scanline >= line { 1959 LINES_PER_FRAME - 1 + line - self.state.scanline 1960 } else { 1961 line - self.state.scanline - 1 1962 }; 1963 DOTS_PER_LINE + dot - self.state.dot + full_lines * DOTS_PER_LINE 1964 }; 1965 1966 cycles.into() 1967 } 1968} 1969 1970fn alpha_blend(first: Pixel, second: Pixel, eva: u16, evb: u16) -> Pixel { 1971 let alpha_blend_component = 1972 |first: u16, second: u16| cmp::min(31, (eva * first + evb * second) >> 4); 1973 1974 let r = alpha_blend_component(first.red(), second.red()); 1975 let g = alpha_blend_component(first.green(), second.green()); 1976 let b = alpha_blend_component(first.blue(), second.blue()); 1977 1978 Pixel::new_opaque_rgb(r, g, b) 1979} 1980 1981fn adjust_brightness<const INCREASE: bool>(color: Pixel, evy: u16) -> Pixel { 1982 let adjust_component = |component: u16| { 1983 if INCREASE { 1984 component + ((evy * (31 - component)) >> 4) 1985 } else { 1986 component - ((evy * component) >> 4) 1987 } 1988 }; 1989 1990 let r = adjust_component(color.red()); 1991 let g = adjust_component(color.green()); 1992 let b = adjust_component(color.blue()); 1993 1994 Pixel::new_opaque_rgb(r, g, b) 1995} 1996 1997fn gba_color_to_rgb8(gba_color: u16) -> Color { 1998 const RGB_5_TO_8: &[u8; 32] = &[ 1999 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 2000 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255, 2001 ]; 2002 2003 let r = gba_color & 0x1F; 2004 let g = (gba_color >> 5) & 0x1F; 2005 let b = (gba_color >> 10) & 0x1F; 2006 2007 Color::rgb(RGB_5_TO_8[r as usize], RGB_5_TO_8[g as usize], RGB_5_TO_8[b as usize]) 2008} 2009 2010#[cfg(test)] 2011mod tests { 2012 use super::*; 2013 2014 #[test] 2015 fn cycles_until_dot() { 2016 let mut ppu = Ppu::new(false); 2017 2018 ppu.state.scanline = 7; 2019 ppu.state.dot = 1; 2020 assert_eq!(ppu.cycles_until_dot(7, 1), u64::from(DOTS_PER_LINE * LINES_PER_FRAME)); 2021 2022 ppu.state.scanline = 5; 2023 ppu.state.dot = 0; 2024 assert_eq!(ppu.cycles_until_dot(7, 1), u64::from(2 * DOTS_PER_LINE + 1)); 2025 ppu.state.dot = 1; 2026 assert_eq!(ppu.cycles_until_dot(7, 1), u64::from(2 * DOTS_PER_LINE)); 2027 ppu.state.dot = 2; 2028 assert_eq!(ppu.cycles_until_dot(7, 1), u64::from(2 * DOTS_PER_LINE - 1)); 2029 2030 ppu.state.scanline = 10; 2031 ppu.state.dot = 0; 2032 assert_eq!(ppu.cycles_until_dot(7, 1), u64::from(225 * DOTS_PER_LINE + 1)); 2033 ppu.state.dot = 1; 2034 assert_eq!(ppu.cycles_until_dot(7, 1), u64::from(225 * DOTS_PER_LINE)); 2035 ppu.state.dot = 2; 2036 assert_eq!(ppu.cycles_until_dot(7, 1), u64::from(225 * DOTS_PER_LINE - 1)); 2037 } 2038}