1//! SNES PPU (picture processing unit) 2 3mod colortable; 4mod debug; 5mod registers; 6mod sprites; 7 8use crate::api::SnesEmulatorConfig; 9use crate::ppu::registers::{ 10 AccessFlipflop, BgMode, BgScreenSize, BitsPerPixel, MidScanlineUpdate, Mode7OobBehavior, 11 Registers, TileSize, VramIncrementMode, 12}; 13use crate::ppu::sprites::{SpriteProcessor, SpriteState}; 14use bincode::{Decode, Encode}; 15use jgenesis_common::boxedarray::BoxedColorArray; 16use jgenesis_common::frontend::{ 17 Color, CompositeParams, FrameSize, SamplesPerColorCycle, TimingMode, 18}; 19use jgenesis_common::num::{GetBit, U16Ext}; 20use std::array; 21 22const MAX_BRIGHTNESS: u8 = 15; 23 24const NORMAL_SCREEN_WIDTH: usize = 256; 25const HIRES_SCREEN_WIDTH: usize = 512; 26const MAX_SCREEN_HEIGHT: usize = 478; 27const FRAME_BUFFER_LEN: usize = HIRES_SCREEN_WIDTH * MAX_SCREEN_HEIGHT; 28 29const MAX_SPRITES_PER_LINE: usize = 32; 30const MAX_SPRITE_TILES_PER_LINE: usize = 34; 31 32const VRAM_LEN_WORDS: usize = 64 * 1024 / 2; 33const OAM_LOW_LEN_WORDS: usize = 512 / 2; 34const OAM_HIGH_LEN_BYTES: usize = 32; 35const CGRAM_LEN_WORDS: usize = 256; 36 37const VRAM_ADDRESS_MASK: u16 = (1 << 15) - 1; 38const OAM_ADDRESS_MASK: u16 = (1 << 9) - 1; 39 40pub const MCLKS_PER_NORMAL_SCANLINE: u64 = 1364; 41const MCLKS_PER_SHORT_SCANLINE: u64 = 1360; 42const MCLKS_PER_LONG_SCANLINE: u64 = 1368; 43 44type Vram = [u16; VRAM_LEN_WORDS]; 45type OamLow = [u16; OAM_LOW_LEN_WORDS]; 46type OamHigh = [u8; OAM_HIGH_LEN_BYTES]; 47type Cgram = [u16; CGRAM_LEN_WORDS]; 48 49#[derive(Debug, Clone, Encode, Decode)] 50struct State { 51 scanline: u16, 52 scanline_master_cycles: u64, 53 current_line_length: ScanlineLength, 54 prev_line_length: ScanlineLength, 55 v_mosaic_counter: u8, 56 mosaic_size_latch: u8, 57 dot_event_idx: u8, 58 odd_frame: bool, 59 pending_sprite_pixel_overflow: bool, 60 ppu1_open_bus: u8, 61 ppu2_open_bus: u8, 62 last_rendered_scanline: Option<u16>, 63 // Tracks if Mode 5/6 or pseudo-hi-res was enabled at any point during active display 64 h_hi_res_frame: bool, 65 // Tracks if interlacing was enabled at the start of the frame 66 v_hi_res_frame: bool, 67 cycle_counter: u64, 68 frame_start_cycles: u64, 69} 70 71impl State { 72 fn new() -> Self { 73 Self { 74 scanline: 0, 75 scanline_master_cycles: 0, 76 current_line_length: ScanlineLength::Normal, 77 prev_line_length: ScanlineLength::Normal, 78 v_mosaic_counter: 0, 79 mosaic_size_latch: 0, 80 dot_event_idx: 0, 81 odd_frame: false, 82 pending_sprite_pixel_overflow: false, 83 ppu1_open_bus: 0, 84 ppu2_open_bus: 0, 85 last_rendered_scanline: None, 86 h_hi_res_frame: false, 87 v_hi_res_frame: false, 88 cycle_counter: 0, 89 frame_start_cycles: 0, 90 } 91 } 92 93 fn frame_screen_width(&self) -> u32 { 94 if self.h_hi_res_frame { HIRES_SCREEN_WIDTH as u32 } else { NORMAL_SCREEN_WIDTH as u32 } 95 } 96 97 fn update_v_mosaic(&mut self, mosaic_size: u8) { 98 if self.scanline == 1 || self.v_mosaic_counter == 0 { 99 self.v_mosaic_counter = mosaic_size; 100 self.mosaic_size_latch = mosaic_size; 101 } else { 102 self.v_mosaic_counter -= 1; 103 } 104 } 105 106 fn v_mosaic_filter(&self, scanline: u16, hi_res_mode: HiResMode) -> u16 { 107 let mut offset: u16 = (self.mosaic_size_latch - self.v_mosaic_counter).into(); 108 if hi_res_mode == HiResMode::True && self.v_hi_res_frame { 109 offset *= 2; 110 } 111 scanline - offset 112 } 113} 114 115#[derive(Debug, Clone, Copy, Encode, Decode)] 116struct CachedBgMapEntry { 117 map_x: u16, 118 map_y: u16, 119 tile_number: u16, 120 palette: u8, 121 priority: bool, 122 x_flip: bool, 123 y_flip: bool, 124} 125 126impl Default for CachedBgMapEntry { 127 fn default() -> Self { 128 Self { 129 map_x: u16::MAX, 130 map_y: u16::MAX, 131 tile_number: 0, 132 palette: 0, 133 priority: false, 134 x_flip: false, 135 y_flip: false, 136 } 137 } 138} 139 140#[derive(Debug, Clone, Copy, Encode, Decode)] 141struct Pixel { 142 palette: u8, 143 color: u8, 144 priority: u8, 145} 146 147impl Pixel { 148 const TRANSPARENT: Self = Self { palette: 0, color: 0, priority: 0 }; 149 150 fn is_transparent(self) -> bool { 151 self.color == 0 152 } 153} 154 155#[derive(Debug, Clone, Copy, Encode, Decode)] 156struct RenderedPixel { 157 color: u16, 158 palette: u8, 159 layer: Layer, 160} 161 162impl Default for RenderedPixel { 163 fn default() -> Self { 164 Self { color: 0, palette: 0, layer: Layer::Backdrop } 165 } 166} 167 168#[derive(Debug, Clone, Encode, Decode)] 169struct Buffers { 170 bg_pixels: [[Pixel; HIRES_SCREEN_WIDTH]; 4], 171 obj_pixels: [Pixel; NORMAL_SCREEN_WIDTH], 172 offset_per_tile_h_scroll: [[u16; NORMAL_SCREEN_WIDTH]; 2], 173 offset_per_tile_v_scroll: [[u16; NORMAL_SCREEN_WIDTH]; 2], 174 main_screen_pixels: [PriorityResolver; NORMAL_SCREEN_WIDTH], 175 main_screen_rendered_pixels: [RenderedPixel; NORMAL_SCREEN_WIDTH], 176 sub_screen_pixels: [PriorityResolver; NORMAL_SCREEN_WIDTH], 177 sub_screen_rendered_pixels: [RenderedPixel; NORMAL_SCREEN_WIDTH], 178} 179 180impl Buffers { 181 fn new() -> Self { 182 Self { 183 bg_pixels: array::from_fn(|_| array::from_fn(|_| Pixel::TRANSPARENT)), 184 obj_pixels: array::from_fn(|_| Pixel::TRANSPARENT), 185 offset_per_tile_h_scroll: array::from_fn(|_| array::from_fn(|_| 0)), 186 offset_per_tile_v_scroll: array::from_fn(|_| array::from_fn(|_| 0)), 187 main_screen_pixels: array::from_fn(|_| PriorityResolver::new()), 188 main_screen_rendered_pixels: array::from_fn(|_| RenderedPixel::default()), 189 sub_screen_pixels: array::from_fn(|_| PriorityResolver::new()), 190 sub_screen_rendered_pixels: array::from_fn(|_| RenderedPixel::default()), 191 } 192 } 193} 194 195#[derive(Debug, Clone, Copy, PartialEq, Eq)] 196pub enum PpuTickEffect { 197 None, 198 FrameComplete, 199} 200 201#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 202enum Layer { 203 Bg1, 204 Bg2, 205 Bg3, 206 Bg4, 207 Obj, 208 Backdrop, 209} 210 211#[derive(Debug, Clone, Copy, Encode, Decode)] 212struct PriorityResolver { 213 min_priority: u8, 214 min_pixel: Pixel, 215 min_layer: Layer, 216} 217 218impl PriorityResolver { 219 // Mode 0-1 priorities: 220 // OBJ.3 > BG1.1 > BG2.1 > OBJ.2 > BG1.0 > BG2.0 > OBJ.1 > BG3.1 > BG4.1 > OBJ.0 > BG3.0 > BG4.0 221 // 0 < 1 < 2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < 11 222 // (unless in Mode 1 and the BG3 high priority flag is set, which moves BG3.1 to highest priority) 223 // Mode 2-7 priorities: 224 // OBJ.3 > BG1.1 > OBJ.2 > BG2.1 > OBJ.1 > BG1.0 > OBJ.0 > BG2.0 225 // 0 < 1 < 2 < 3 < 4 < 5 < 6 < 7 226 // (BG3 and BG4 are never rendered in these modes) 227 const OBJ3: u8 = 0; 228 const BG1_HIGH: u8 = 1; 229 const MODE_01_BG2_HIGH: u8 = 2; 230 const MODE_01_OBJ2: u8 = 3; 231 const MODE_01_BG1_LOW: u8 = 4; 232 const MODE_01_BG2_LOW: u8 = 5; 233 const MODE_01_OBJ1: u8 = 6; 234 const BG3_HIGH: u8 = 7; 235 const BG4_HIGH: u8 = 8; 236 const MODE_01_OBJ0: u8 = 9; 237 const BG3_LOW: u8 = 10; 238 const BG4_LOW: u8 = 11; 239 240 // OBJ.3 and BG1.1 have the same priority in modes 2-7 as in modes 0-1 241 const MODE_27_OBJ2: u8 = 2; 242 const MODE_27_BG2_HIGH: u8 = 3; 243 const MODE_27_OBJ1: u8 = 4; 244 const MODE_27_BG1_LOW: u8 = 5; 245 const MODE_27_OBJ0: u8 = 6; 246 const MODE_27_BG2_LOW: u8 = 7; 247 248 fn new() -> Self { 249 Self { min_priority: u8::MAX, min_pixel: Pixel::TRANSPARENT, min_layer: Layer::Backdrop } 250 } 251 252 fn add_bg1(&mut self, pixel: Pixel, is_mode_0_or_1: bool) { 253 let priority = match (is_mode_0_or_1, pixel.priority) { 254 (true, 0) => Self::MODE_01_BG1_LOW, 255 (false, 0) => Self::MODE_27_BG1_LOW, 256 (_, 1) => Self::BG1_HIGH, 257 _ => panic!("Invalid BG1 pixel priority: {}", pixel.priority), 258 }; 259 self.add_pixel(pixel, Layer::Bg1, priority); 260 } 261 262 fn add_bg2(&mut self, pixel: Pixel, is_mode_0_or_1: bool) { 263 let priority = match (is_mode_0_or_1, pixel.priority) { 264 (true, 0) => Self::MODE_01_BG2_LOW, 265 (true, 1) => Self::MODE_01_BG2_HIGH, 266 (false, 0) => Self::MODE_27_BG2_LOW, 267 (false, 1) => Self::MODE_27_BG2_HIGH, 268 _ => panic!("Invalid BG2 pixel priority: {}", pixel.priority), 269 }; 270 self.add_pixel(pixel, Layer::Bg2, priority); 271 } 272 273 fn add_bg3(&mut self, pixel: Pixel, bg3_high_priority: bool) { 274 if bg3_high_priority && pixel.priority == 1 { 275 // In mode 1, non-transparent high-priority BG3 pixels display over all other layers 276 self.min_priority = 0; 277 self.min_pixel = pixel; 278 self.min_layer = Layer::Bg3; 279 return; 280 } 281 282 let priority = if pixel.priority == 1 { Self::BG3_HIGH } else { Self::BG3_LOW }; 283 self.add_pixel(pixel, Layer::Bg3, priority); 284 } 285 286 fn add_bg4(&mut self, pixel: Pixel) { 287 let priority = if pixel.priority == 1 { Self::BG4_HIGH } else { Self::BG4_LOW }; 288 self.add_pixel(pixel, Layer::Bg4, priority); 289 } 290 291 fn add_obj(&mut self, pixel: Pixel, is_mode_0_or_1: bool) { 292 let priority = match (is_mode_0_or_1, pixel.priority) { 293 (true, 0) => Self::MODE_01_OBJ0, 294 (true, 1) => Self::MODE_01_OBJ1, 295 (true, 2) => Self::MODE_01_OBJ2, 296 (_, 3) => Self::OBJ3, 297 (false, 0) => Self::MODE_27_OBJ0, 298 (false, 1) => Self::MODE_27_OBJ1, 299 (false, 2) => Self::MODE_27_OBJ2, 300 _ => panic!("Invalid OBJ pixel priority: {}", pixel.priority), 301 }; 302 self.add_pixel(pixel, Layer::Obj, priority); 303 } 304 305 #[inline(always)] 306 fn add_pixel(&mut self, pixel: Pixel, layer: Layer, layer_priority: u8) { 307 if layer_priority < self.min_priority { 308 self.min_priority = layer_priority; 309 self.min_pixel = pixel; 310 self.min_layer = layer; 311 } 312 } 313 314 fn get(self) -> Option<(Pixel, Layer)> { 315 (self.min_priority != u8::MAX).then_some((self.min_pixel, self.min_layer)) 316 } 317} 318 319#[derive(Debug, Clone, Copy, PartialEq, Eq)] 320enum Screen { 321 Main, 322 Sub, 323} 324 325#[derive(Debug, Clone, Copy, PartialEq, Eq)] 326enum HiResMode { 327 None, 328 Pseudo, 329 True, 330} 331 332impl HiResMode { 333 fn is_hi_res(self) -> bool { 334 matches!(self, Self::Pseudo | Self::True) 335 } 336} 337 338#[derive(Debug, Clone, Encode, Decode)] 339pub struct Ppu { 340 timing_mode: TimingMode, 341 registers: Registers, 342 state: State, 343 buffers: Box<Buffers>, 344 vram: Box<Vram>, 345 oam_low: Box<OamLow>, 346 oam_high: Box<OamHigh>, 347 cgram: Box<Cgram>, 348 frame_buffer: BoxedColorArray<FRAME_BUFFER_LEN>, 349 sprites: SpriteProcessor, 350 deinterlace: bool, 351} 352 353#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 354enum ScanlineLength { 355 Normal, 356 Short, 357 Long, 358} 359 360impl ScanlineLength { 361 fn scanline_mclks(self) -> u64 { 362 match self { 363 Self::Normal => MCLKS_PER_NORMAL_SCANLINE, 364 Self::Short => MCLKS_PER_SHORT_SCANLINE, 365 Self::Long => MCLKS_PER_LONG_SCANLINE, 366 } 367 } 368} 369 370// In actual hardware, PPU starts rendering pixels at H=22 / mclk=88 371// Some games depend on this 88-cycle delay to finish HDMA before rendering starts, e.g. Final Fantasy 6 372// 373// Here, start rendering at H=23 / mclk=92 to account for CPU writes occurring at the end of the 374// CPU cycle rather than in the middle of it; starting at mclk=88 causes flickering in Lemmings 375const RENDER_LINE_MCLK: u64 = 92; 376 377// Used as the threshold for applying mid-scanline register writes. 378// Ignore writes that occur during the last few pixels of active display; this fixes minor flickering 379// in Wild Guns 380const END_RENDER_LINE_MCLK: u64 = RENDER_LINE_MCLK + 256 * 4 - 3 * 4; 381 382// Latch Mode 7 registers a bit before rendering starts 383// Battle Clash depends on this to avoid a glitchy line where the screen transitions from Mode 1 to Mode 7 384const MODE_7_LATCH_MCLK: u64 = 40; 385 386impl Ppu { 387 pub fn new(timing_mode: TimingMode, config: SnesEmulatorConfig) -> Self { 388 Self { 389 timing_mode, 390 registers: Registers::new(), 391 state: State::new(), 392 buffers: Box::new(Buffers::new()), 393 vram: vec![0; VRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(), 394 oam_low: vec![0; OAM_LOW_LEN_WORDS].into_boxed_slice().try_into().unwrap(), 395 oam_high: vec![0; OAM_HIGH_LEN_BYTES].into_boxed_slice().try_into().unwrap(), 396 cgram: vec![0; CGRAM_LEN_WORDS].into_boxed_slice().try_into().unwrap(), 397 frame_buffer: BoxedColorArray::new(), 398 sprites: SpriteProcessor::new(), 399 deinterlace: config.deinterlace, 400 } 401 } 402 403 #[must_use] 404 pub fn tick(&mut self, master_cycles: u64) -> PpuTickEffect { 405 debug_assert!(master_cycles <= MCLKS_PER_SHORT_SCANLINE); 406 407 self.state.cycle_counter += master_cycles; 408 409 let new_scanline_mclks = self.state.scanline_master_cycles + master_cycles; 410 self.state.scanline_master_cycles = new_scanline_mclks; 411 412 self.advance_to_dot((new_scanline_mclks / 4) as u16); 413 414 let v_display_size = self.registers.v_display_size.to_lines(); 415 416 let mclks_per_scanline = self.state.current_line_length.scanline_mclks(); 417 418 let mut tick_effect = PpuTickEffect::None; 419 if new_scanline_mclks >= mclks_per_scanline { 420 self.state.scanline += 1; 421 self.state.scanline_master_cycles = new_scanline_mclks - mclks_per_scanline; 422 423 if self.state.pending_sprite_pixel_overflow { 424 self.state.pending_sprite_pixel_overflow = false; 425 self.registers.sprite_pixel_overflow = true; 426 } 427 428 // Interlaced mode adds an extra scanline every other frame 429 let scanlines_per_frame = self.scanlines_per_frame(); 430 if (self.state.scanline == scanlines_per_frame 431 && (!self.registers.interlaced || self.state.odd_frame)) 432 || self.state.scanline == scanlines_per_frame + 1 433 { 434 // Start new frame 435 self.state.scanline = 0; 436 437 if !self.state.v_hi_res_frame && self.registers.interlaced && !self.deinterlace { 438 self.fix_interlaced_frame_buffer(); 439 } 440 441 // TODO wait until H=1? 442 self.state.odd_frame = !self.state.odd_frame; 443 self.state.last_rendered_scanline = None; 444 self.state.h_hi_res_frame = self.registers.in_hi_res_mode(); 445 self.state.v_hi_res_frame = self.registers.interlaced; 446 447 if !self.registers.forced_blanking { 448 self.registers.sprite_overflow = false; 449 self.registers.sprite_pixel_overflow = false; 450 } 451 452 self.state.frame_start_cycles = 453 self.state.cycle_counter - self.state.scanline_master_cycles; 454 } 455 456 self.state.prev_line_length = self.state.current_line_length; 457 self.state.current_line_length = self.scanline_length(self.state.scanline); 458 459 self.state.update_v_mosaic(self.registers.mosaic_size); 460 461 self.sprites_finish_line(); 462 463 let sprites_interlaced_odd = 464 !self.deinterlace && self.state.v_hi_res_frame && self.state.odd_frame; 465 self.sprites_start_new_line(self.state.scanline, sprites_interlaced_odd); 466 467 self.state.dot_event_idx = 0; 468 self.advance_to_dot((self.state.scanline_master_cycles / 4) as u16); 469 470 if self.state.scanline == v_display_size + 1 { 471 // Reload OAM data port address at start of VBlank if not in forced blanking 472 if !self.registers.forced_blanking { 473 self.registers.oam_address = self.registers.oam_address_reload_value << 1; 474 } 475 476 tick_effect = PpuTickEffect::FrameComplete; 477 } 478 } 479 480 if let Some(mid_line_update) = self.registers.mid_line_update 481 && (1..=v_display_size).contains(&self.state.scanline) 482 && (RENDER_LINE_MCLK..END_RENDER_LINE_MCLK).contains(&new_scanline_mclks) 483 { 484 // Between H=22 and H=276 and INIDISP or one of the scroll registers was just modified; 485 // partially render current line 486 487 // Scroll register writes don't seem to apply immediately - see the "Good Luck" 488 // animation in Air Strike Patrol 489 let pixel_offset = match mid_line_update { 490 MidScanlineUpdate::Inidisp => 0, 491 MidScanlineUpdate::Scroll => 15, 492 }; 493 494 let from_pixel = (new_scanline_mclks - RENDER_LINE_MCLK) / 4 + pixel_offset; 495 if from_pixel < NORMAL_SCREEN_WIDTH as u64 { 496 self.render_current_line(from_pixel as u16); 497 } 498 } 499 500 self.registers.mid_line_update = None; 501 502 tick_effect 503 } 504 505 fn advance_to_dot(&mut self, dot: u16) { 506 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 507 enum PpuEvent { 508 LatchMode7Registers, 509 RenderLine, 510 FinishSpriteEvaluation, 511 StartSpriteTileFetch, 512 None, 513 } 514 515 const EVENT_DOTS: &[u16; 5] = &[ 516 (MODE_7_LATCH_MCLK / 4) as u16, 517 (RENDER_LINE_MCLK / 4) as u16, 518 sprites::SPRITE_EVALUATION_END_DOT, 519 sprites::SPRITE_FETCH_START_DOT, 520 u16::MAX, 521 ]; 522 523 const EVENTS: &[PpuEvent; 5] = &[ 524 PpuEvent::LatchMode7Registers, 525 PpuEvent::RenderLine, 526 PpuEvent::FinishSpriteEvaluation, 527 PpuEvent::StartSpriteTileFetch, 528 PpuEvent::None, 529 ]; 530 531 debug_assert!(EVENT_DOTS.is_sorted()); 532 533 while dot >= EVENT_DOTS[self.state.dot_event_idx as usize] { 534 match EVENTS[self.state.dot_event_idx as usize] { 535 PpuEvent::LatchMode7Registers => self.registers.latch_mode_7(), 536 PpuEvent::RenderLine => { 537 let v_display_size = self.registers.v_display_size.to_lines(); 538 let is_active_scanline = (1..=v_display_size).contains(&self.state.scanline); 539 if is_active_scanline { 540 self.render_current_line(0); 541 } 542 } 543 PpuEvent::FinishSpriteEvaluation => { 544 self.progress_sprite_evaluation(sprites::SPRITE_EVALUATION_END_DOT); 545 } 546 PpuEvent::StartSpriteTileFetch => self.begin_sprite_tile_fetch(), 547 PpuEvent::None => {} 548 } 549 550 self.state.dot_event_idx += 1; 551 } 552 } 553 554 fn render_current_line(&mut self, from_pixel: u16) { 555 let scanline = self.state.scanline; 556 self.state.last_rendered_scanline = Some(scanline); 557 558 if self.registers.forced_blanking { 559 // Forced blanking always draws black 560 let from_pixel = if self.state.h_hi_res_frame { 2 * from_pixel } else { from_pixel }; 561 let screen_width = self.state.frame_screen_width(); 562 563 if self.state.v_hi_res_frame { 564 for y in [2 * scanline - 1, 2 * scanline] { 565 for pixel in from_pixel..screen_width as u16 { 566 self.set_in_frame_buffer(y, pixel, Color::BLACK); 567 } 568 } 569 } else { 570 for pixel in from_pixel..screen_width as u16 { 571 self.set_in_frame_buffer(scanline, pixel, Color::BLACK); 572 } 573 } 574 575 return; 576 } 577 578 let hi_res_mode = if self.registers.bg_mode.is_hi_res() { 579 HiResMode::True 580 } else if self.registers.pseudo_h_hi_res { 581 HiResMode::Pseudo 582 } else { 583 HiResMode::None 584 }; 585 586 let bg_from_pixel = 587 if hi_res_mode == HiResMode::True { 2 * from_pixel } else { from_pixel }; 588 let screen_from_pixel = if hi_res_mode.is_hi_res() { 2 * from_pixel } else { from_pixel }; 589 let v_hi_res = hi_res_mode == HiResMode::True && self.registers.interlaced; 590 591 if self.state.v_hi_res_frame && v_hi_res { 592 // Vertical hi-res, 448px 593 if self.deinterlace || !self.state.odd_frame { 594 // Render even line 595 self.render_bg_layers_to_buffer(2 * scanline - 1, hi_res_mode, bg_from_pixel); 596 self.render_scanline(2 * scanline - 1, hi_res_mode, screen_from_pixel); 597 } 598 599 if self.deinterlace || self.state.odd_frame { 600 // Render odd line 601 if self.deinterlace && self.registers.pseudo_obj_hi_res { 602 self.render_sprite_tiles(); 603 } 604 605 self.render_bg_layers_to_buffer(2 * scanline, hi_res_mode, bg_from_pixel); 606 self.render_scanline(2 * scanline, hi_res_mode, screen_from_pixel); 607 } 608 } else if !self.state.v_hi_res_frame && v_hi_res { 609 // Probably should never happen - PPU is in 448px mode but interlacing was disabled at 610 // start of frame 611 let y = if self.state.odd_frame { 2 * scanline } else { 2 * scanline - 1 }; 612 613 self.render_bg_layers_to_buffer(y, hi_res_mode, bg_from_pixel); 614 self.render_scanline(scanline, hi_res_mode, screen_from_pixel); 615 } else if self.state.v_hi_res_frame { 616 // Interlacing was enabled at start of frame - duplicate lines 617 if !self.deinterlace { 618 let odd_frame: u16 = self.state.odd_frame.into(); 619 self.render_bg_layers_to_buffer(scanline, hi_res_mode, screen_from_pixel); 620 self.render_scanline(2 * scanline - 1 + odd_frame, hi_res_mode, screen_from_pixel); 621 } else { 622 // Render even line 623 self.render_bg_layers_to_buffer(scanline, hi_res_mode, bg_from_pixel); 624 self.render_scanline(2 * scanline - 1, hi_res_mode, screen_from_pixel); 625 626 // Duplicate to odd line, re-rendering OBJs if necessary 627 if self.registers.pseudo_obj_hi_res { 628 self.render_sprite_tiles(); 629 self.render_scanline(2 * scanline, hi_res_mode, screen_from_pixel); 630 } else { 631 self.duplicate_line( 632 (2 * scanline - 1).into(), 633 (2 * scanline).into(), 634 screen_from_pixel.into(), 635 ); 636 } 637 } 638 } else { 639 // Interlacing is disabled, render normally 640 self.render_bg_layers_to_buffer(scanline, hi_res_mode, bg_from_pixel); 641 self.render_scanline(scanline, hi_res_mode, screen_from_pixel); 642 } 643 } 644 645 fn render_bg_layers_to_buffer( 646 &mut self, 647 scanline: u16, 648 hi_res_mode: HiResMode, 649 from_pixel: u16, 650 ) { 651 let mode = self.registers.bg_mode; 652 653 if mode == BgMode::Seven { 654 self.render_mode_7_to_buffer(scanline, from_pixel); 655 return; 656 } 657 658 let bg1_enabled = self.registers.main_bg_enabled[0] || self.registers.sub_bg_enabled[0]; 659 let bg2_enabled = mode.bg2_enabled(false) 660 && (self.registers.main_bg_enabled[1] || self.registers.sub_bg_enabled[1]); 661 let bg3_enabled = mode.bg3_enabled() 662 && (self.registers.main_bg_enabled[2] || self.registers.sub_bg_enabled[2]); 663 let bg4_enabled = mode.bg4_enabled() 664 && (self.registers.main_bg_enabled[3] || self.registers.sub_bg_enabled[3]); 665 666 if mode.is_offset_per_tile() && (bg1_enabled || bg2_enabled) { 667 // Populate offset-per-tile buffers before rendering BG1 or BG2 668 self.populate_offset_per_tile_buffers(); 669 } 670 671 if bg1_enabled { 672 self.render_bg_to_buffer(0, scanline, hi_res_mode, from_pixel); 673 } 674 675 if bg2_enabled { 676 self.render_bg_to_buffer(1, scanline, hi_res_mode, from_pixel); 677 } 678 679 if bg3_enabled { 680 self.render_bg_to_buffer(2, scanline, hi_res_mode, from_pixel); 681 } 682 683 if bg4_enabled { 684 self.render_bg_to_buffer(3, scanline, hi_res_mode, from_pixel); 685 } 686 } 687 688 fn render_bg_to_buffer( 689 &mut self, 690 bg: usize, 691 scanline: u16, 692 hi_res_mode: HiResMode, 693 from_pixel: u16, 694 ) { 695 let screen_width = 696 if hi_res_mode == HiResMode::True { HIRES_SCREEN_WIDTH } else { NORMAL_SCREEN_WIDTH }; 697 698 let mode = self.registers.bg_mode; 699 let bpp = match bg { 700 0 => mode.bg1_bpp(), 701 1 => mode.bg2_bpp(), 702 2 => BitsPerPixel::BG3, 703 3 => BitsPerPixel::BG4, 704 _ => panic!("invalid BG layer: {bg}"), 705 }; 706 707 let bg_h_scroll = self.registers.bg_h_scroll[bg]; 708 let bg_v_scroll = self.registers.bg_v_scroll[bg]; 709 710 let mut bg_map_entry = CachedBgMapEntry::default(); 711 712 for pixel_idx in from_pixel..screen_width as u16 { 713 // Apply mosaic if enabled 714 let (base_y, mosaic_x) = self.apply_mosaic(bg, scanline, pixel_idx, hi_res_mode); 715 if mosaic_x != pixel_idx { 716 // Mosaic is enabled and this is not the far left pixel; copy last pixel and move on 717 self.buffers.bg_pixels[bg][pixel_idx as usize] = 718 self.buffers.bg_pixels[bg][(pixel_idx - 1) as usize]; 719 continue; 720 } 721 722 // Account for offset-per-tile if in mode 2/4/6 723 let (mut h_scroll, v_scroll) = if mode.is_offset_per_tile() { 724 let buffer_idx = match hi_res_mode { 725 HiResMode::True => (pixel_idx / 2) as usize, 726 HiResMode::None | HiResMode::Pseudo => pixel_idx as usize, 727 }; 728 ( 729 self.buffers.offset_per_tile_h_scroll[bg][buffer_idx], 730 self.buffers.offset_per_tile_v_scroll[bg][buffer_idx], 731 ) 732 } else { 733 (bg_h_scroll, bg_v_scroll) 734 }; 735 736 if hi_res_mode == HiResMode::True { 737 // H scroll values are effectively doubled in mode 5/6 738 h_scroll <<= 1; 739 } 740 741 // Apply scroll values 742 let x = pixel_idx.wrapping_add(h_scroll); 743 let y = base_y.wrapping_add(v_scroll); 744 745 // Retrieve background map entry (if different from the last pixel's) 746 // BG tiles can be larger than 8x8 but that doesn't matter here since this is just to 747 // avoid doing a BG map lookup on every pixel 748 if x / 8 != bg_map_entry.map_x || y / 8 != bg_map_entry.map_y { 749 let raw_entry = get_bg_map_entry(&self.vram, &self.registers, bg, x, y); 750 bg_map_entry = CachedBgMapEntry { 751 map_x: x / 8, 752 map_y: y / 8, 753 tile_number: raw_entry & 0x3FF, 754 palette: ((raw_entry >> 10) & 0x07) as u8, 755 priority: raw_entry.bit(13), 756 x_flip: raw_entry.bit(14), 757 y_flip: raw_entry.bit(15), 758 }; 759 } 760 761 // Retrieve tile bytes from VRAM 762 let tile_data = get_bg_tile( 763 &self.vram, 764 &self.registers, 765 bg, 766 x, 767 y, 768 bpp, 769 bg_map_entry.tile_number, 770 bg_map_entry.x_flip, 771 bg_map_entry.y_flip, 772 ); 773 774 let tile_row = if bg_map_entry.y_flip { 7 - (y % 8) } else { y % 8 }; 775 let tile_col = if bg_map_entry.x_flip { 7 - (x % 8) } else { x % 8 }; 776 let bit_index = (7 - tile_col) as u8; 777 778 // Parse color bits out of bitplane tile data 779 let mut color = 0_u8; 780 for plane in (0..bpp.bitplanes()).step_by(2) { 781 let word_index = tile_row as usize + 4 * plane; 782 let word = tile_data[word_index]; 783 784 color |= u8::from(word.bit(bit_index)) << plane; 785 color |= u8::from(word.bit(bit_index + 8)) << (plane + 1); 786 } 787 788 let pixel = Pixel { 789 palette: bg_map_entry.palette, 790 color, 791 priority: bg_map_entry.priority.into(), 792 }; 793 self.buffers.bg_pixels[bg][pixel_idx as usize] = pixel; 794 } 795 } 796 797 fn render_mode_7_to_buffer(&mut self, scanline: u16, from_pixel: u16) { 798 // Mode 7 tile map is always 128x128 799 const TILE_MAP_SIZE_PIXELS: i32 = 128 * 8; 800 801 fn clip_i11(value: i32) -> i32 { 802 // This is intentionally not equivalent to `(value << (32 - 11)) >> (32 - 11)` 803 // 804 // Some games depend on using the magnitude from the lowest 10 bits but using the sign 805 // of the full i14 value instead of clipping to i11. 806 // e.g. Kaite Tsukutte Asoberu Dezaemon, title screen animation 807 // e.g. Tiny Toon Adventures: Wacky Sports Challenge, the birdman event 808 let magnitude = value & 0x03FF; 809 let sign = (value >> 31) & !0x03FF; 810 magnitude | sign 811 } 812 813 fn sign_extend_i13(value: u16) -> i32 { 814 (((value as i16) << 3) >> 3).into() 815 } 816 817 // Several Mode 7 intermediate results truncate the lowest 6 bits 818 fn truncate_intermediate(value: i32) -> i32 { 819 value & !0x3F 820 } 821 822 // Affine transformation parameters (fixed point, 1/256 pixel units) 823 let m7a: i32 = (self.registers.mode_7_latched.parameter_a as i16).into(); 824 let m7b: i32 = (self.registers.mode_7_latched.parameter_b as i16).into(); 825 let m7c: i32 = (self.registers.mode_7_latched.parameter_c as i16).into(); 826 let m7d: i32 = (self.registers.mode_7_latched.parameter_d as i16).into(); 827 828 // Center of rotation 829 let m7x = sign_extend_i13(self.registers.mode_7_latched.center_x); 830 let m7y = sign_extend_i13(self.registers.mode_7_latched.center_y); 831 832 let h_scroll = sign_extend_i13(self.registers.mode_7_latched.h_scroll); 833 let v_scroll = sign_extend_i13(self.registers.mode_7_latched.v_scroll); 834 835 let h_flip = self.registers.mode_7_latched.h_flip; 836 let v_flip = self.registers.mode_7_latched.v_flip; 837 838 let oob_behavior = self.registers.mode_7_latched.oob_behavior; 839 840 for pixel in from_pixel..NORMAL_SCREEN_WIDTH as u16 { 841 let (base_y, mosaic_x) = self.apply_mosaic(0, scanline, pixel, HiResMode::None); 842 if mosaic_x != pixel { 843 // Copy last pixel and move on 844 self.buffers.bg_pixels[0][pixel as usize] = 845 self.buffers.bg_pixels[0][(pixel - 1) as usize]; 846 continue; 847 } 848 849 let screen_x: i32 = (if h_flip { 255 - pixel } else { pixel }).into(); 850 let screen_y: i32 = (if v_flip { 255 - base_y } else { base_y }).into(); 851 852 // Perform the following matrix transformation (logically): 853 // [ vram_x ] = [ m7a m7b ] * [ screen_x + m7hofs - m7x ] + [ m7x ] 854 // [ vram_y ] [ m7c m7d ] [ screen_y + m7vofs - m7y ] [ m7y ] 855 // m7a/m7b/m7c/m7d are in 1/256 pixel units, so the multiplication result is also in 856 // 1/256 pixel units, and m7x/m7y need to be converted for the addition 857 let scrolled_center_x = clip_i11(h_scroll - m7x); 858 let scrolled_center_y = clip_i11(v_scroll - m7y); 859 860 let mut tile_map_x = truncate_intermediate(m7a * scrolled_center_x) 861 + m7a * screen_x 862 + truncate_intermediate(m7b * scrolled_center_y) 863 + truncate_intermediate(m7b * screen_y) 864 + (m7x << 8); 865 let mut tile_map_y = truncate_intermediate(m7c * scrolled_center_x) 866 + m7c * screen_x 867 + truncate_intermediate(m7d * scrolled_center_y) 868 + truncate_intermediate(m7d * screen_y) 869 + (m7y << 8); 870 871 // Convert back from 1/256 pixel units to pixel units 872 tile_map_x >>= 8; 873 tile_map_y >>= 8; 874 875 let mut force_tile_0 = false; 876 if tile_map_x < 0 877 || tile_map_y < 0 878 || tile_map_x >= TILE_MAP_SIZE_PIXELS 879 || tile_map_y >= TILE_MAP_SIZE_PIXELS 880 { 881 match oob_behavior { 882 Mode7OobBehavior::Wrap => { 883 tile_map_x &= TILE_MAP_SIZE_PIXELS - 1; 884 tile_map_y &= TILE_MAP_SIZE_PIXELS - 1; 885 } 886 Mode7OobBehavior::Transparent => { 887 self.buffers.bg_pixels[0][pixel as usize] = Pixel::TRANSPARENT; 888 continue; 889 } 890 Mode7OobBehavior::Tile0 => { 891 tile_map_x &= 0x07; 892 tile_map_y &= 0x07; 893 force_tile_0 = true; 894 } 895 } 896 } 897 898 let tile_number = if force_tile_0 { 899 0 900 } else { 901 // Mode 7 tile map is always located at $0000 902 let tile_map_row = tile_map_y / 8; 903 let tile_map_col = tile_map_x / 8; 904 let tile_map_addr = tile_map_row * TILE_MAP_SIZE_PIXELS / 8 + tile_map_col; 905 self.vram[tile_map_addr as usize] & 0x00FF 906 }; 907 908 let tile_row = (tile_map_y % 8) as u16; 909 let tile_col = (tile_map_x % 8) as u16; 910 let pixel_addr = 64 * tile_number + 8 * tile_row + tile_col; 911 let color = self.vram[pixel_addr as usize].msb(); 912 913 self.buffers.bg_pixels[0][pixel as usize] = Pixel { palette: 0, color, priority: 0 }; 914 } 915 } 916 917 fn populate_offset_per_tile_buffers(&mut self) { 918 // Offset-per-tile does not apply to first visible BG1/BG2 tile (1-8 pixels) 919 for bg in 0..2 { 920 let bg_h_scroll = self.registers.bg_h_scroll[bg]; 921 let bg_v_scroll = self.registers.bg_v_scroll[bg]; 922 923 for pixel in 0..(8 - (bg_h_scroll & 7)) { 924 self.buffers.offset_per_tile_h_scroll[bg][pixel as usize] = bg_h_scroll; 925 self.buffers.offset_per_tile_v_scroll[bg][pixel as usize] = bg_v_scroll; 926 } 927 } 928 929 let mode = self.registers.bg_mode; 930 let bg3_h_scroll = self.registers.bg_h_scroll[2]; 931 let bg3_v_scroll = self.registers.bg_v_scroll[2]; 932 933 // Up to 33 8x8 tiles can be visible when fine H scrolling is used 934 for tile_idx in 0..33_u16 { 935 // Lowest 3 bits of BG3 H scroll do not apply in offset-per-tile 936 let bg3_x = (8 * tile_idx).wrapping_add(bg3_h_scroll & !7); 937 938 let (h_offset_entry, v_offset_entry) = match mode { 939 BgMode::Four => { 940 // In Mode 4, instead of loading both map entries, the PPU uses the highest bit 941 // of the first entry to determine whether to apply offset to H or V 942 let bg3_entry = 943 get_bg_map_entry(&self.vram, &self.registers, 2, bg3_x, bg3_v_scroll); 944 if bg3_entry.bit(15) { 945 // Apply to V scroll 946 (0, bg3_entry) 947 } else { 948 // Apply to H scroll 949 (bg3_entry, 0) 950 } 951 } 952 _ => { 953 let h_offset_entry = 954 get_bg_map_entry(&self.vram, &self.registers, 2, bg3_x, bg3_v_scroll); 955 let v_offset_entry = get_bg_map_entry( 956 &self.vram, 957 &self.registers, 958 2, 959 bg3_x, 960 bg3_v_scroll.wrapping_add(8), 961 ); 962 (h_offset_entry, v_offset_entry) 963 } 964 }; 965 966 for bg in 0..2 { 967 let bg_h_scroll = self.registers.bg_h_scroll[bg]; 968 let bg_v_scroll = self.registers.bg_v_scroll[bg]; 969 970 let use_offset_bit = [13, 14][bg]; 971 let h_scroll = if h_offset_entry.bit(use_offset_bit) { 972 // TODO is this right for fine H scroll? 973 (h_offset_entry & 0x03FF & !7) | (bg_h_scroll & 7) 974 } else { 975 bg_h_scroll 976 }; 977 let v_scroll = if v_offset_entry.bit(use_offset_bit) { 978 v_offset_entry & 0x03FF 979 } else { 980 bg_v_scroll 981 }; 982 983 // BG3 tile N is used as offsets for BG1/BG2 visible tile N+1 984 let base_x = (8 * (tile_idx + 1)).wrapping_sub(bg_h_scroll & 7); 985 for dx in 0..8 { 986 let pixel = base_x + dx; 987 if pixel >= NORMAL_SCREEN_WIDTH as u16 { 988 break; 989 } 990 991 self.buffers.offset_per_tile_h_scroll[bg][pixel as usize] = h_scroll; 992 self.buffers.offset_per_tile_v_scroll[bg][pixel as usize] = v_scroll; 993 } 994 } 995 } 996 } 997 998 fn render_scanline(&mut self, scanline: u16, hi_res_mode: HiResMode, screen_from_pixel: u16) { 999 // Main screen is always rendered 1000 self.render_screen_pixels(Screen::Main, hi_res_mode); 1001 1002 // Sub screen is rendered if in a hi-res mode (which causes even pixels to display the sub screen) OR color math 1003 // is enabled for at least one layer and the sub screen is not forced to the subbackdrop color. 1004 if hi_res_mode.is_hi_res() 1005 || (self.registers.sub_bg_obj_enabled 1006 && self.registers.color_math_enabled_for_any_layer()) 1007 { 1008 self.render_screen_pixels(Screen::Sub, hi_res_mode); 1009 } 1010 1011 let screen_width = 1012 if hi_res_mode.is_hi_res() { HIRES_SCREEN_WIDTH } else { NORMAL_SCREEN_WIDTH }; 1013 1014 let brightness = self.registers.brightness; 1015 let main_backdrop_pixel = 1016 RenderedPixel { palette: 0, color: self.cgram[0], layer: Layer::Backdrop }; 1017 let sub_backdrop_color = self.registers.sub_backdrop_color; 1018 1019 for pixel in screen_from_pixel..screen_width as u16 { 1020 let screen_x = match hi_res_mode { 1021 HiResMode::None => pixel, 1022 HiResMode::Pseudo | HiResMode::True => pixel / 2, 1023 }; 1024 1025 let mut main_screen_pixel = if hi_res_mode.is_hi_res() && !pixel.bit(0) { 1026 // Even pixels draw the sub screen in hi-res mode 1027 // If all sub screen pixels are transparent, draw the main backdrop color 1028 let sub_pixel = self.buffers.sub_screen_rendered_pixels[screen_x as usize]; 1029 if sub_pixel.layer == Layer::Backdrop { main_backdrop_pixel } else { sub_pixel } 1030 } else { 1031 self.buffers.main_screen_rendered_pixels[screen_x as usize] 1032 }; 1033 1034 // Check if inside the color window (used for clipping and color math) 1035 let in_color_window = self.registers.in_math_window(screen_x); 1036 1037 let force_main_screen_black = 1038 self.registers.force_main_screen_black.enabled(in_color_window); 1039 if force_main_screen_black { 1040 // Pixel is clipped; force color to 0 (black) 1041 main_screen_pixel.color = 0; 1042 } 1043 1044 // Check if color math is enabled globally and for this layer 1045 let color_math_enabled_global = 1046 self.registers.color_math_enabled.enabled(in_color_window); 1047 1048 let color_math_enabled_layer = match main_screen_pixel.layer { 1049 Layer::Bg1 => self.registers.bg_color_math_enabled[0], 1050 Layer::Bg2 => self.registers.bg_color_math_enabled[1], 1051 Layer::Bg3 => self.registers.bg_color_math_enabled[2], 1052 Layer::Bg4 => self.registers.bg_color_math_enabled[3], 1053 Layer::Obj => { 1054 self.registers.obj_color_math_enabled && main_screen_pixel.palette >= 4 1055 } 1056 Layer::Backdrop => self.registers.backdrop_color_math_enabled, 1057 }; 1058 1059 let snes_color = if color_math_enabled_global && color_math_enabled_layer { 1060 // Find the frontmost sub screen pixel 1061 let (sub_screen_color, sub_transparent) = if self.registers.sub_bg_obj_enabled { 1062 let pixel = self.buffers.sub_screen_rendered_pixels[screen_x as usize]; 1063 (pixel.color, pixel.layer == Layer::Backdrop) 1064 } else { 1065 (sub_backdrop_color, false) 1066 }; 1067 1068 // Apply color math to the main and sub screen pixels 1069 // Division only applies if the main pixel was not clipped and the sub pixel is not 1070 // transparent 1071 let divide = self.registers.color_math_divide_enabled 1072 && !force_main_screen_black 1073 && !sub_transparent; 1074 self.registers.color_math_operation.apply( 1075 main_screen_pixel.color, 1076 sub_screen_color, 1077 divide, 1078 ) 1079 } else { 1080 main_screen_pixel.color 1081 }; 1082 1083 let final_color = convert_snes_color(snes_color, brightness); 1084 1085 if self.state.h_hi_res_frame && !hi_res_mode.is_hi_res() { 1086 // Hi-res mode is not currently enabled, but it was enabled earlier in the frame; 1087 // draw in 512px 1088 self.set_in_frame_buffer(scanline, 2 * pixel, final_color); 1089 self.set_in_frame_buffer(scanline, 2 * pixel + 1, final_color); 1090 } else { 1091 self.set_in_frame_buffer(scanline, pixel, final_color); 1092 } 1093 } 1094 } 1095 1096 fn render_screen_pixels(&mut self, screen: Screen, hi_res_mode: HiResMode) { 1097 #[inline(always)] 1098 fn apply_screen_shift(x: usize, shift: i32, offset: usize) -> u16 { 1099 ((x << shift) | offset) as u16 1100 } 1101 1102 let ( 1103 screen_pixels, 1104 screen_rendered_pixels, 1105 bg_enabled, 1106 bg_disabled_in_window, 1107 obj_enabled, 1108 obj_disabled_in_window, 1109 backdrop_color, 1110 ) = match screen { 1111 Screen::Main => ( 1112 &mut self.buffers.main_screen_pixels, 1113 &mut self.buffers.main_screen_rendered_pixels, 1114 self.registers.main_bg_enabled, 1115 self.registers.main_bg_disabled_in_window, 1116 self.registers.main_obj_enabled, 1117 self.registers.main_obj_disabled_in_window, 1118 self.cgram[0], 1119 ), 1120 Screen::Sub => ( 1121 &mut self.buffers.sub_screen_pixels, 1122 &mut self.buffers.sub_screen_rendered_pixels, 1123 self.registers.sub_bg_enabled, 1124 self.registers.sub_bg_disabled_in_window, 1125 self.registers.sub_obj_enabled, 1126 self.registers.sub_obj_disabled_in_window, 1127 self.registers.sub_backdrop_color, 1128 ), 1129 }; 1130 1131 screen_pixels.fill(PriorityResolver::new()); 1132 1133 let screen_x_shift = match hi_res_mode { 1134 HiResMode::True => 1, 1135 HiResMode::None | HiResMode::Pseudo => 0, 1136 }; 1137 let screen_x_offset = match (hi_res_mode, screen) { 1138 (HiResMode::None | HiResMode::Pseudo, _) | (HiResMode::True, Screen::Sub) => 0, 1139 (HiResMode::True, Screen::Main) => 1, 1140 }; 1141 1142 let mode = self.registers.bg_mode; 1143 let is_mode_0_or_1 = matches!(mode, BgMode::Zero | BgMode::One); 1144 1145 // OBJ layer (enabled in all modes) 1146 if obj_enabled { 1147 for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() { 1148 if obj_disabled_in_window { 1149 let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset); 1150 let obj_in_window = self.registers.obj_in_window(screen_x); 1151 if obj_in_window { 1152 continue; 1153 } 1154 } 1155 1156 let obj_pixel = self.buffers.obj_pixels[x]; 1157 if !obj_pixel.is_transparent() { 1158 priority_resolver.add_obj(obj_pixel, is_mode_0_or_1); 1159 } 1160 } 1161 } 1162 1163 // BG1 layer (enabled in all modes) 1164 if bg_enabled[0] { 1165 for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() { 1166 let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset); 1167 1168 if bg_disabled_in_window[0] { 1169 let bg1_in_window = self.registers.bg_in_window(0, screen_x); 1170 if bg1_in_window { 1171 continue; 1172 } 1173 } 1174 1175 let bg1_pixel = self.buffers.bg_pixels[0][screen_x as usize]; 1176 if !bg1_pixel.is_transparent() { 1177 priority_resolver.add_bg1(bg1_pixel, is_mode_0_or_1); 1178 } 1179 } 1180 } 1181 1182 // BG2 layer (enabled in all modes except 6 and 7) 1183 if mode.bg2_enabled(self.registers.extbg_enabled) && bg_enabled[1] { 1184 for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() { 1185 let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset); 1186 1187 if bg_disabled_in_window[1] { 1188 let bg2_in_window = self.registers.bg_in_window(1, screen_x); 1189 if bg2_in_window { 1190 continue; 1191 } 1192 } 1193 1194 match mode { 1195 BgMode::Seven => { 1196 // When EXTBG is enabled in Mode 7, BG1 pixels are duplicated into BG2 but 1197 // use the highest color bit as priority 1198 let bg1_pixel = self.buffers.bg_pixels[0][screen_x as usize]; 1199 let bg2_pixel_color = bg1_pixel.color & 0x7F; 1200 if bg2_pixel_color == 0 { 1201 // Transparent 1202 continue; 1203 } 1204 1205 let bg2_priority = bg1_pixel.color >> 7; 1206 priority_resolver.add_bg2( 1207 Pixel { priority: bg2_priority, color: bg2_pixel_color, palette: 0 }, 1208 false, 1209 ); 1210 } 1211 _ => { 1212 let bg2_pixel = self.buffers.bg_pixels[1][screen_x as usize]; 1213 if !bg2_pixel.is_transparent() { 1214 priority_resolver.add_bg2(bg2_pixel, is_mode_0_or_1); 1215 } 1216 } 1217 } 1218 } 1219 } 1220 1221 // BG3 layer (enabled in modes 0 and 1) 1222 if mode.bg3_enabled() && bg_enabled[2] { 1223 let bg3_high_priority = mode == BgMode::One && self.registers.mode_1_bg3_priority; 1224 1225 for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() { 1226 let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset); 1227 1228 if bg_disabled_in_window[2] { 1229 let bg3_in_window = self.registers.bg_in_window(2, screen_x); 1230 if bg3_in_window { 1231 continue; 1232 } 1233 } 1234 1235 let bg3_pixel = self.buffers.bg_pixels[2][screen_x as usize]; 1236 if !bg3_pixel.is_transparent() { 1237 priority_resolver.add_bg3(bg3_pixel, bg3_high_priority); 1238 } 1239 } 1240 } 1241 1242 // BG4 layer (enabled in mode 0 only) 1243 if mode.bg4_enabled() && bg_enabled[3] { 1244 for (x, priority_resolver) in screen_pixels.iter_mut().enumerate() { 1245 let screen_x = apply_screen_shift(x, screen_x_shift, screen_x_offset); 1246 1247 if bg_disabled_in_window[3] { 1248 let bg4_in_window = self.registers.bg_in_window(3, screen_x); 1249 if bg4_in_window { 1250 continue; 1251 } 1252 } 1253 1254 let bg4_pixel = self.buffers.bg_pixels[3][screen_x as usize]; 1255 if !bg4_pixel.is_transparent() { 1256 priority_resolver.add_bg4(bg4_pixel); 1257 } 1258 } 1259 } 1260 1261 let backdrop_pixel = 1262 RenderedPixel { color: backdrop_color, palette: 0, layer: Layer::Backdrop }; 1263 for (priority_resolver, rendered_pixel) in 1264 screen_pixels.iter_mut().zip(screen_rendered_pixels) 1265 { 1266 *rendered_pixel = priority_resolver.get().map_or(backdrop_pixel, |(pixel, layer)| { 1267 let color = resolve_pixel_color( 1268 &self.cgram, 1269 layer, 1270 mode, 1271 self.registers.direct_color_mode_enabled, 1272 pixel.palette, 1273 pixel.color, 1274 ); 1275 RenderedPixel { color, palette: pixel.palette, layer } 1276 }); 1277 } 1278 } 1279 1280 fn apply_mosaic( 1281 &self, 1282 bg: usize, 1283 scanline: u16, 1284 pixel: u16, 1285 hi_res_mode: HiResMode, 1286 ) -> (u16, u16) { 1287 let mosaic_size = self.registers.mosaic_size; 1288 let mosaic_enabled = self.registers.bg_mosaic_enabled[bg]; 1289 if !mosaic_enabled { 1290 return (scanline, pixel); 1291 } 1292 1293 let mosaic_line = self.state.v_mosaic_filter(scanline, hi_res_mode); 1294 1295 // Mosaic size of N fills each (N+1)x(N+1) square with the pixel in the top-left corner 1296 // Mosaic sizes are doubled in true hi-res mode 1297 let mosaic_size: u16 = (mosaic_size + 1).into(); 1298 let mosaic_width = match hi_res_mode { 1299 HiResMode::True => 2 * mosaic_size, 1300 _ => mosaic_size, 1301 }; 1302 1303 (mosaic_line, pixel / mosaic_width * mosaic_width) 1304 } 1305 1306 fn enter_hi_res_mode(&mut self) { 1307 if !self.vblank_flag() && !self.state.h_hi_res_frame { 1308 // Hi-res mode enabled mid-frame; redraw previously rendered scanlines to 512x224 in-place 1309 if let Some(last_rendered_scanline) = self.state.last_rendered_scanline { 1310 let last_copy_line = if self.state.v_hi_res_frame { 1311 2 * last_rendered_scanline 1312 } else { 1313 last_rendered_scanline 1314 }; 1315 self.frame_buffer_h256_to_h512(last_copy_line); 1316 } 1317 } 1318 1319 self.state.h_hi_res_frame = true; 1320 } 1321 1322 fn frame_buffer_h256_to_h512(&mut self, to_scanline: u16) { 1323 for scanline in (1..=to_scanline).rev() { 1324 let src_line_addr = 256 * u32::from(scanline - 1); 1325 let dest_line_addr = 512 * u32::from(scanline - 1); 1326 for pixel in (0..256).rev() { 1327 let color = self.frame_buffer[(src_line_addr + pixel) as usize]; 1328 self.frame_buffer[(dest_line_addr + 2 * pixel) as usize] = color; 1329 self.frame_buffer[(dest_line_addr + 2 * pixel + 1) as usize] = color; 1330 } 1331 } 1332 } 1333 1334 fn set_in_frame_buffer(&mut self, scanline: u16, pixel: u16, color: Color) { 1335 let screen_width = self.state.frame_screen_width(); 1336 let index = u32::from(scanline - 1) * screen_width + u32::from(pixel); 1337 self.frame_buffer[index as usize] = color; 1338 } 1339 1340 fn duplicate_line(&mut self, from_line: u32, to_line: u32, from_pixel: u32) { 1341 let screen_width = self.state.frame_screen_width(); 1342 let from_row_addr = screen_width * (from_line - 1); 1343 let to_row_addr = screen_width * (to_line - 1); 1344 for pixel in from_pixel..screen_width { 1345 self.frame_buffer[(to_row_addr + pixel) as usize] = 1346 self.frame_buffer[(from_row_addr + pixel) as usize]; 1347 } 1348 } 1349 1350 fn fix_interlaced_frame_buffer(&mut self) { 1351 log::debug!("Just entered interlaced mode; rewriting frame buffer"); 1352 1353 let v_display_size = self.registers.v_display_size.to_lines(); 1354 1355 // Check if changed from H256px to H512px 1356 let next_frame_h_hi_res = 1357 self.registers.bg_mode.is_hi_res() || self.registers.pseudo_h_hi_res; 1358 if !self.state.h_hi_res_frame && next_frame_h_hi_res { 1359 log::debug!("Expanding previous frame from H256px to H512px"); 1360 self.frame_buffer_h256_to_h512(v_display_size); 1361 } 1362 1363 log::debug!( 1364 "Expanding previous frame from V{}px to V{}px; screen width H{}px", 1365 v_display_size, 1366 2 * v_display_size, 1367 if next_frame_h_hi_res { HIRES_SCREEN_WIDTH } else { NORMAL_SCREEN_WIDTH } 1368 ); 1369 1370 // Duplicate lines to expand frame buffer from V224px to V448px (or V239px to V478px) 1371 let screen_width = if next_frame_h_hi_res { 1372 HIRES_SCREEN_WIDTH as u32 1373 } else { 1374 NORMAL_SCREEN_WIDTH as u32 1375 }; 1376 for scanline in (1..=u32::from(v_display_size)).rev() { 1377 let even_line = 2 * scanline - 1; 1378 let odd_line = 2 * scanline; 1379 for pixel in 0..screen_width { 1380 let color = self.frame_buffer[(scanline * screen_width + pixel) as usize]; 1381 self.frame_buffer[(even_line * screen_width + pixel) as usize] = color; 1382 self.frame_buffer[(odd_line * screen_width + pixel) as usize] = color; 1383 } 1384 } 1385 } 1386 1387 pub fn scanlines_per_frame(&self) -> u16 { 1388 match self.timing_mode { 1389 TimingMode::Ntsc => 262, 1390 TimingMode::Pal => 312, 1391 } 1392 } 1393 1394 fn scanline_length(&self, scanline: u16) -> ScanlineLength { 1395 // In NTSC progressive mode, line 240 is short every other frame (1360 mclks) 1396 // In PAL interlaced mode, line 311 is long every other frame (1368 mclks) 1397 // All other lines are 1364 mclks 1398 if !self.state.odd_frame { 1399 return ScanlineLength::Normal; 1400 } 1401 1402 match (scanline, self.timing_mode, self.registers.interlaced) { 1403 (240, TimingMode::Ntsc, false) => ScanlineLength::Short, 1404 (311, TimingMode::Pal, true) => ScanlineLength::Long, 1405 _ => ScanlineLength::Normal, 1406 } 1407 } 1408 1409 fn in_active_display(&self, scanline: u16) -> bool { 1410 !self.registers.forced_blanking && scanline <= self.registers.v_display_size.to_lines() 1411 } 1412 1413 pub fn vblank_flag(&self) -> bool { 1414 self.state.scanline > self.registers.v_display_size.to_lines() 1415 } 1416 1417 pub fn hblank_flag(&self) -> bool { 1418 self.state.scanline_master_cycles < 4 || self.state.scanline_master_cycles >= 1096 1419 } 1420 1421 pub fn scanline(&self) -> u16 { 1422 self.state.scanline 1423 } 1424 1425 pub fn frame_start_cycles(&self) -> u64 { 1426 self.state.frame_start_cycles 1427 } 1428 1429 #[cfg(test)] 1430 pub fn set_scanline(&mut self, scanline: u16) { 1431 assert!( 1432 scanline < self.scanlines_per_frame(), 1433 "{scanline} must be less than {}", 1434 self.scanlines_per_frame() 1435 ); 1436 self.state.scanline = scanline; 1437 } 1438 1439 pub fn is_first_vblank_scanline(&self) -> bool { 1440 self.state.scanline == self.registers.v_display_size.to_lines() + 1 1441 } 1442 1443 pub fn scanline_master_cycles(&self) -> u64 { 1444 self.state.scanline_master_cycles 1445 } 1446 1447 #[cfg(test)] 1448 pub fn set_scanline_master_cycles(&mut self, scanline_master_cycles: u64) { 1449 assert!( 1450 scanline_master_cycles < self.state.current_line_length.scanline_mclks(), 1451 "{scanline_master_cycles} must be less than {}", 1452 self.state.current_line_length.scanline_mclks() 1453 ); 1454 self.state.scanline_master_cycles = scanline_master_cycles; 1455 } 1456 1457 // There is a 10 mclk / 2.5 dot delay on raising IRQ in response to VTIME/HTIME matches 1458 const IRQ_OFFSET_MCLKS: u64 = 10; 1459 1460 pub fn vtime_for_irq(&self) -> u16 { 1461 if self.state.scanline_master_cycles >= Self::IRQ_OFFSET_MCLKS { 1462 self.state.scanline 1463 } else if self.state.scanline == 0 { 1464 self.scanlines_per_frame() - 1 1465 } else { 1466 self.state.scanline - 1 1467 } 1468 } 1469 1470 pub fn htime_for_irq(&self) -> u16 { 1471 let scanline_mclks = if self.state.scanline_master_cycles >= Self::IRQ_OFFSET_MCLKS { 1472 self.state.scanline_master_cycles - Self::IRQ_OFFSET_MCLKS 1473 } else { 1474 self.state.prev_line_length.scanline_mclks() 1475 - (Self::IRQ_OFFSET_MCLKS - self.state.scanline_master_cycles) 1476 }; 1477 1478 // CPU HTIME counter does not account for some dots being shorter/longer; assumes all dots 1479 // are 4 mclks 1480 (scanline_mclks / 4) as u16 1481 } 1482 1483 pub fn previous_line_max_htime(&self) -> u16 { 1484 (self.state.prev_line_length.scanline_mclks() / 4) as u16 1485 } 1486 1487 pub fn frame_buffer(&self) -> &[Color] { 1488 self.frame_buffer.as_ref() 1489 } 1490 1491 pub fn frame_size(&self) -> FrameSize { 1492 let screen_width = self.state.frame_screen_width(); 1493 1494 let mut screen_height = self.registers.v_display_size.to_lines(); 1495 if self.state.v_hi_res_frame { 1496 screen_height *= 2; 1497 } 1498 1499 FrameSize { width: screen_width, height: screen_height.into() } 1500 } 1501 1502 pub fn composite_params(&self) -> CompositeParams { 1503 let upscale_factor = if self.state.h_hi_res_frame { 4 } else { 8 }; 1504 1505 CompositeParams { upscale_factor, samples_per_color_cycle: SamplesPerColorCycle::Twelve } 1506 } 1507 1508 pub fn read_port(&mut self, address: u32) -> Option<u8> { 1509 log::trace!("Read PPU register: {address:06X}"); 1510 1511 let address_lsb = address & 0xFF; 1512 let value = match address_lsb { 1513 0x34 => self.registers.read_mpyl(), 1514 0x35 => self.registers.read_mpym(), 1515 0x36 => self.registers.read_mpyh(), 1516 0x37 => { 1517 // SLHV: Latch H/V counter 1518 let h_counter = (self.state.scanline_master_cycles >> 2) as u16; 1519 let v_counter = self.state.scanline; 1520 self.registers.read_slhv(h_counter, v_counter); 1521 1522 // Reading from this address returns CPU open bus 1523 return None; 1524 } 1525 0x38 => { 1526 // RDOAM: OAM data port, read 1527 self.read_oam_data_port() 1528 } 1529 0x39 => { 1530 // RDVRAML: VRAM data port, read, low byte 1531 self.read_vram_data_port_low() 1532 } 1533 0x3A => { 1534 // RDVRAMH: VRAM data port, read, high byte 1535 self.read_vram_data_port_high() 1536 } 1537 0x3B => { 1538 // RDCGRAM: CGRAM data port, read 1539 self.read_cgram_data_port() 1540 } 1541 0x3C => self.registers.read_ophct(self.state.ppu2_open_bus), 1542 0x3D => self.registers.read_opvct(self.state.ppu2_open_bus), 1543 0x3E => { 1544 // STAT77: PPU1 status and version number 1545 // Version number hardcoded to 1 1546 // Bit 4 is PPU1 open bus 1547 (u8::from(self.registers.sprite_pixel_overflow) << 7) 1548 | (u8::from(self.registers.sprite_overflow) << 6) 1549 | (self.state.ppu1_open_bus & 0x10) 1550 | 0x01 1551 } 1552 0x3F => { 1553 // STAT78: PPU2 status and version number 1554 // Version number hardcoded to 1 1555 // Bit 5 is PPU2 open bus 1556 let value = (u8::from(self.state.odd_frame) << 7) 1557 | (u8::from(self.registers.new_hv_latched) << 6) 1558 | (self.state.ppu2_open_bus & 0x20) 1559 | (u8::from(self.timing_mode == TimingMode::Pal) << 4) 1560 | 0x01; 1561 1562 self.registers.new_hv_latched = false; 1563 self.registers.reset_hv_counter_flipflops(); 1564 1565 value 1566 } 1567 0x04 | 0x05 | 0x06 | 0x08 | 0x09 | 0x0A | 0x14 | 0x15 | 0x16 | 0x18 | 0x19 | 0x1A 1568 | 0x24 | 0x25 | 0x26 | 0x28 | 0x29 | 0x2A => { 1569 // PPU1 open bus (all 8 bits) 1570 self.state.ppu1_open_bus 1571 } 1572 _ => { 1573 // CPU open bus 1574 return None; 1575 } 1576 }; 1577 1578 if (0x34..0x37).contains(&address_lsb) 1579 || (0x38..0x3B).contains(&address_lsb) 1580 || address_lsb == 0x3E 1581 { 1582 // Reading $2134-$2136, $2138-$213A, or $213E sets PPU1 open bus 1583 self.state.ppu1_open_bus = value; 1584 } else if (0x3B..0x3E).contains(&address_lsb) || address_lsb == 0x3F { 1585 // Reading $213B-$213D or $213F sets PPU2 open bus 1586 self.state.ppu2_open_bus = value; 1587 } 1588 1589 Some(value) 1590 } 1591 1592 pub fn write_port(&mut self, address: u32, value: u8) { 1593 if log::log_enabled!(log::Level::Trace) { 1594 // Don't log data port writes 1595 let address = address & 0xFF; 1596 if address != 0x04 && address != 0x18 && address != 0x19 && address != 0x22 { 1597 log::trace!( 1598 "PPU register write: 21{address:02X} {value:02X} (scanline {} mclk {})", 1599 self.state.scanline, 1600 self.state.scanline_master_cycles, 1601 ); 1602 } 1603 } 1604 1605 let scanline = self.state.scanline; 1606 match address & 0xFF { 1607 0x00 => { 1608 let new_forced_blanking = value.bit(7); 1609 if self.registers.forced_blanking != new_forced_blanking { 1610 self.sprites_forced_blanking_change(new_forced_blanking); 1611 } 1612 1613 self.registers.write_inidisp(value, self.is_first_vblank_scanline()); 1614 } 1615 0x01 => self.registers.write_obsel(value), 1616 0x02 => self.registers.write_oamaddl(value, self.in_active_display(scanline)), 1617 0x03 => self.registers.write_oamaddh(value, self.in_active_display(scanline)), 1618 0x04 => { 1619 // OAMDATA: OAM data port (write) 1620 self.write_oam_data_port(value); 1621 } 1622 0x05 => { 1623 self.registers.write_bgmode(value); 1624 if self.registers.bg_mode.is_hi_res() { 1625 self.enter_hi_res_mode(); 1626 } 1627 } 1628 0x06 => self.registers.write_mosaic(value), 1629 0x07..=0x0A => { 1630 let bg = ((address + 1) & 0x3) as usize; 1631 self.registers.write_bg1234sc(bg, value); 1632 } 1633 0x0B => self.registers.write_bg1234nba(0, value), 1634 0x0C => self.registers.write_bg1234nba(2, value), 1635 0x0D => self.registers.write_bg1hofs(value), 1636 0x0E => self.registers.write_bg1vofs(value), 1637 address @ (0x0F | 0x11 | 0x13) => { 1638 // BG2HOFS/BG3HOFS/BG4HOFS: BG2-4 horizontal scroll 1639 let bg = (((address - 0x0F) >> 1) + 1) as usize; 1640 self.registers.write_bg_h_scroll(bg, value); 1641 } 1642 address @ (0x10 | 0x12 | 0x14) => { 1643 // BG2VOFS/BG3VOFS/BG4VOFS: BG2-4 vertical scroll 1644 let bg = (((address & 0x0F) >> 1) + 1) as usize; 1645 self.registers.write_bg_v_scroll(bg, value); 1646 } 1647 0x15 => self.registers.write_vmain(value), 1648 0x16 => self.registers.write_vmaddl(value, &self.vram), 1649 0x17 => self.registers.write_vmaddh(value, &self.vram), 1650 0x18 => { 1651 // VMDATAL: VRAM data port (write), low byte 1652 self.write_vram_data_port_low(value); 1653 } 1654 0x19 => { 1655 // VMDATAH: VRAM data port (write), high byte 1656 self.write_vram_data_port_high(value); 1657 } 1658 0x1A => self.registers.write_m7sel(value), 1659 0x1B => self.registers.write_m7a(value), 1660 0x1C => self.registers.write_m7b(value), 1661 0x1D => self.registers.write_m7c(value), 1662 0x1E => self.registers.write_m7d(value), 1663 0x1F => self.registers.write_m7x(value), 1664 0x20 => self.registers.write_m7y(value), 1665 0x21 => self.registers.write_cgadd(value), 1666 0x22 => { 1667 // CGDATA: CGRAM data port (write) 1668 self.write_cgram_data_port(value); 1669 } 1670 0x23 => self.registers.write_w1234sel(0, value), 1671 0x24 => self.registers.write_w1234sel(2, value), 1672 0x25 => self.registers.write_wobjsel(value), 1673 0x26 => self.registers.write_wh0(value), 1674 0x27 => self.registers.write_wh1(value), 1675 0x28 => self.registers.write_wh2(value), 1676 0x29 => self.registers.write_wh3(value), 1677 0x2A => self.registers.write_wbglog(value), 1678 0x2B => self.registers.write_wobjlog(value), 1679 0x2C => self.registers.write_tm(value), 1680 0x2D => self.registers.write_ts(value), 1681 0x2E => self.registers.write_tmw(value), 1682 0x2F => self.registers.write_tsw(value), 1683 0x30 => self.registers.write_cgwsel(value), 1684 0x31 => self.registers.write_cgadsub(value), 1685 0x32 => self.registers.write_coldata(value), 1686 0x33 => { 1687 self.registers.write_setini(value); 1688 if self.registers.pseudo_h_hi_res { 1689 self.enter_hi_res_mode(); 1690 } 1691 } 1692 _ => { 1693 // No other mappings are valid; do nothing 1694 } 1695 } 1696 } 1697 1698 fn write_vram_data_port_low(&mut self, value: u8) { 1699 if self.vblank_flag() || self.registers.forced_blanking { 1700 // VRAM writes only allowed during VBlank and forced blanking 1701 let vram_addr = 1702 (self.registers.vram_address_translation.apply(self.registers.vram_address) 1703 & VRAM_ADDRESS_MASK) as usize; 1704 self.vram[vram_addr].set_lsb(value); 1705 } 1706 1707 if self.registers.vram_address_increment_mode == VramIncrementMode::Low { 1708 self.increment_vram_address(); 1709 } 1710 } 1711 1712 fn write_vram_data_port_high(&mut self, value: u8) { 1713 if self.vblank_flag() || self.registers.forced_blanking { 1714 // VRAM writes only allowed during VBlank and forced blanking 1715 let vram_addr = 1716 (self.registers.vram_address_translation.apply(self.registers.vram_address) 1717 & VRAM_ADDRESS_MASK) as usize; 1718 self.vram[vram_addr].set_msb(value); 1719 } 1720 1721 if self.registers.vram_address_increment_mode == VramIncrementMode::High { 1722 self.increment_vram_address(); 1723 } 1724 } 1725 1726 fn read_vram_data_port_low(&mut self) -> u8 { 1727 let vram_byte = self.registers.vram_prefetch_buffer.lsb(); 1728 1729 if self.registers.vram_address_increment_mode == VramIncrementMode::Low { 1730 // Fill prefetch buffer *before* address increment 1731 self.fill_vram_prefetch_buffer(); 1732 self.increment_vram_address(); 1733 } 1734 1735 vram_byte 1736 } 1737 1738 fn read_vram_data_port_high(&mut self) -> u8 { 1739 let vram_byte = self.registers.vram_prefetch_buffer.msb(); 1740 1741 if self.registers.vram_address_increment_mode == VramIncrementMode::High { 1742 // Fill prefetch buffer *before* address increment 1743 self.fill_vram_prefetch_buffer(); 1744 self.increment_vram_address(); 1745 } 1746 1747 vram_byte 1748 } 1749 1750 fn increment_vram_address(&mut self) { 1751 self.registers.vram_address = 1752 self.registers.vram_address.wrapping_add(self.registers.vram_address_increment_step); 1753 } 1754 1755 fn fill_vram_prefetch_buffer(&mut self) { 1756 let vram_addr = self.registers.vram_address_translation.apply(self.registers.vram_address) 1757 & VRAM_ADDRESS_MASK; 1758 self.registers.vram_prefetch_buffer = self.vram[vram_addr as usize]; 1759 } 1760 1761 fn write_oam_data_port(&mut self, value: u8) { 1762 if self.in_active_display(self.state.scanline) { 1763 self.handle_active_display_oam_write(value); 1764 return; 1765 } 1766 1767 if self.registers.oam_address >= 0x100 { 1768 // Writes to $100 or higher immediately go through to high OAM at (address << 1) & 0x1F 1769 // $220-$3FF are mirrors of $200-$21F 1770 let second_write = self.registers.oam_data_flipflop == AccessFlipflop::Second; 1771 let oam_high_addr = (self.registers.oam_address << 1) | u16::from(second_write); 1772 self.oam_high[(oam_high_addr & 0x1F) as usize] = value; 1773 1774 self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle(); 1775 if second_write { 1776 self.registers.oam_address = (self.registers.oam_address + 1) & OAM_ADDRESS_MASK; 1777 } 1778 } else { 1779 // Writes to $000-$1FF go to low OAM; requires two writes to persist a word 1780 match self.registers.oam_data_flipflop { 1781 AccessFlipflop::First => { 1782 // First write: Latch LSB 1783 self.registers.oam_write_buffer = value; 1784 } 1785 AccessFlipflop::Second => { 1786 // Second write: Write word to OAM 1787 self.oam_low[self.registers.oam_address as usize] = 1788 u16::from_le_bytes([self.registers.oam_write_buffer, value]); 1789 self.registers.oam_address = 1790 (self.registers.oam_address + 1) & OAM_ADDRESS_MASK; 1791 } 1792 } 1793 self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle(); 1794 } 1795 } 1796 1797 fn handle_active_display_oam_write(&mut self, value: u8) { 1798 self.progress_for_mid_scanline_write((self.state.scanline_master_cycles / 4) as u16); 1799 1800 if log::log_enabled!(log::Level::Debug) { 1801 log::debug!( 1802 "Active display OAM write at line {} dot {}, state is {:?}", 1803 self.state.scanline, 1804 self.state.scanline_master_cycles / 4, 1805 self.sprites.state 1806 ); 1807 1808 if let SpriteState::TileFetch { oam_buffer_idx, .. } = self.sprites.state { 1809 log::debug!( 1810 " Current OAM idx = {}", 1811 self.sprites.scanned_oam_idxs[oam_buffer_idx as usize] 1812 ); 1813 } 1814 } 1815 1816 let oam_idx = self.sprites.state.oam_idx(&self.sprites.scanned_oam_idxs); 1817 1818 let oam_high_addr = (oam_idx >> 2) as usize; 1819 self.oam_high[oam_high_addr] = value; 1820 1821 log::debug!("Set OAMHigh[${oam_high_addr:02X}] = 0x{value:02X}"); 1822 1823 match self.registers.oam_data_flipflop { 1824 AccessFlipflop::First => { 1825 self.registers.oam_write_buffer = value; 1826 } 1827 AccessFlipflop::Second => { 1828 let word = u16::from_le_bytes([self.registers.oam_write_buffer, value]); 1829 let oam_low_addr = usize::from(oam_idx << 1); 1830 self.oam_low[oam_low_addr] = word; 1831 1832 log::debug!("Set OAMLow[${oam_low_addr:02X}] = 0x{value:04X}"); 1833 } 1834 } 1835 1836 self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle(); 1837 } 1838 1839 fn read_oam_data_port(&mut self) -> u8 { 1840 if self.in_active_display(self.state.scanline) { 1841 return self.handle_active_display_oam_read(); 1842 } 1843 1844 let second_read = self.registers.oam_data_flipflop == AccessFlipflop::Second; 1845 1846 let oam_byte = if self.registers.oam_address >= 0x100 { 1847 // High OAM; $220-$3FF mirrors $200-$21F 1848 let oam_high_addr = (self.registers.oam_address << 1) | u16::from(second_read); 1849 self.oam_high[(oam_high_addr & 0x1F) as usize] 1850 } else { 1851 // Low OAM 1852 let word_bytes = self.oam_low[self.registers.oam_address as usize].to_le_bytes(); 1853 word_bytes[usize::from(second_read)] 1854 }; 1855 1856 self.registers.oam_data_flipflop = self.registers.oam_data_flipflop.toggle(); 1857 if second_read { 1858 self.registers.oam_address = (self.registers.oam_address + 1) & OAM_ADDRESS_MASK; 1859 } 1860 1861 oam_byte 1862 } 1863 1864 fn handle_active_display_oam_read(&self) -> u8 { 1865 // TODO is this right? should active display reads always return from high OAM instead of low OAM? 1866 let oam_idx = self.sprites.state.oam_idx(&self.sprites.scanned_oam_idxs); 1867 let oam_high_addr = (oam_idx >> 2) & 0x1F; 1868 self.oam_high[oam_high_addr as usize] 1869 } 1870 1871 fn write_cgram_data_port(&mut self, value: u8) { 1872 match self.registers.cgram_flipflop { 1873 AccessFlipflop::First => { 1874 self.registers.cgram_write_buffer = value; 1875 self.registers.cgram_flipflop = AccessFlipflop::Second; 1876 } 1877 AccessFlipflop::Second => { 1878 // Only bits 6-0 of high byte are persisted 1879 self.cgram[self.registers.cgram_address as usize] = 1880 u16::from_le_bytes([self.registers.cgram_write_buffer, value & 0x7F]); 1881 self.registers.cgram_flipflop = AccessFlipflop::First; 1882 1883 self.registers.cgram_address = self.registers.cgram_address.wrapping_add(1); 1884 } 1885 } 1886 } 1887 1888 fn read_cgram_data_port(&mut self) -> u8 { 1889 let word = self.cgram[self.registers.cgram_address as usize]; 1890 1891 match self.registers.cgram_flipflop { 1892 AccessFlipflop::First => { 1893 // Low byte 1894 self.registers.cgram_flipflop = AccessFlipflop::Second; 1895 1896 word.lsb() 1897 } 1898 AccessFlipflop::Second => { 1899 // High byte; bit 7 is PPU2 open bus 1900 self.registers.cgram_flipflop = AccessFlipflop::First; 1901 self.registers.cgram_address = self.registers.cgram_address.wrapping_add(1); 1902 1903 (self.state.ppu2_open_bus & 0x80) | word.msb() 1904 } 1905 } 1906 } 1907 1908 pub fn update_wrio(&mut self, wrio: u8) { 1909 if wrio != self.registers.programmable_joypad_port { 1910 let h_counter = (self.state.scanline_master_cycles >> 2) as u16; 1911 let v_counter = self.state.scanline; 1912 self.registers.update_wrio(wrio, h_counter, v_counter); 1913 } 1914 } 1915 1916 pub fn update_controller_hv_latch(&mut self, h: u16, v: u16, master_cycles_elapsed: u64) { 1917 if v == self.state.scanline 1918 && h > (self.state.scanline_master_cycles / 4) as u16 1919 && h <= ((self.state.scanline_master_cycles + master_cycles_elapsed) / 4) as u16 1920 { 1921 self.registers.latched_h_counter = h; 1922 self.registers.latched_v_counter = v; 1923 self.registers.new_hv_latched = true; 1924 } 1925 } 1926 1927 pub fn update_config(&mut self, config: SnesEmulatorConfig) { 1928 self.deinterlace = config.deinterlace; 1929 } 1930 1931 pub fn reset(&mut self) { 1932 // Enable forced blanking 1933 self.registers.write_inidisp(0x80, self.is_first_vblank_scanline()); 1934 1935 // Return to default rendering mode (224-line, non-interlaced, no pseudo-hi-res or smaller OBJs) 1936 self.registers.write_setini(0x00); 1937 } 1938} 1939 1940#[allow(clippy::too_many_arguments)] 1941fn get_bg_tile<'vram>( 1942 vram: &'vram Vram, 1943 registers: &Registers, 1944 bg: usize, 1945 x: u16, 1946 y: u16, 1947 bpp: BitsPerPixel, 1948 raw_tile_number: u16, 1949 x_flip: bool, 1950 y_flip: bool, 1951) -> &'vram [u16] { 1952 let bg_mode = registers.bg_mode; 1953 let bg_tile_size = registers.bg_tile_size[bg]; 1954 let (bg_tile_width_pixels, bg_tile_height_pixels) = get_bg_tile_size(bg_mode, bg_tile_size); 1955 1956 let tile_number = { 1957 let x_shift = bg_tile_width_pixels == 16 && (if x_flip { x % 16 < 8 } else { x % 16 >= 8 }); 1958 let y_shift = 1959 bg_tile_height_pixels == 16 && (if y_flip { y % 16 < 8 } else { y % 16 >= 8 }); 1960 match (x_shift, y_shift) { 1961 (false, false) => raw_tile_number, 1962 (true, false) => raw_tile_number + 1, 1963 (false, true) => raw_tile_number + 16, 1964 (true, true) => raw_tile_number + 17, 1965 } 1966 }; 1967 1968 let bg_data_base_addr = registers.bg_tile_base_address[bg]; 1969 let tile_size_words = bpp.tile_size_words(); 1970 let tile_addr = (bg_data_base_addr.wrapping_add(tile_number * tile_size_words) 1971 & VRAM_ADDRESS_MASK) as usize; 1972 &vram[tile_addr..tile_addr + tile_size_words as usize] 1973} 1974 1975fn get_bg_map_entry(vram: &Vram, registers: &Registers, bg: usize, x: u16, y: u16) -> u16 { 1976 let bg_mode = registers.bg_mode; 1977 let bg_tile_size = registers.bg_tile_size[bg]; 1978 let (bg_tile_width_pixels, bg_tile_height_pixels) = get_bg_tile_size(bg_mode, bg_tile_size); 1979 1980 let bg_screen_size = registers.bg_screen_size[bg]; 1981 let screen_width_pixels = bg_screen_size.width_tiles() * bg_tile_width_pixels; 1982 let screen_height_pixels = bg_screen_size.height_tiles() * bg_tile_height_pixels; 1983 1984 let mut bg_map_base_addr = registers.bg_base_address[bg]; 1985 let mut x = x & (screen_width_pixels - 1); 1986 let mut y = y & (screen_height_pixels - 1); 1987 1988 // The larger BG screen is made up of 1-4 smaller 32x32 tile screens 1989 let single_screen_width_pixels = 32 * bg_tile_width_pixels; 1990 let single_screen_height_pixels = 32 * bg_tile_height_pixels; 1991 1992 if x >= single_screen_width_pixels { 1993 bg_map_base_addr += 32 * 32; 1994 x &= single_screen_width_pixels - 1; 1995 } 1996 1997 if y >= single_screen_height_pixels { 1998 bg_map_base_addr += match bg_screen_size { 1999 BgScreenSize::HorizontalMirror => 32 * 32, 2000 BgScreenSize::FourScreen => 2 * 32 * 32, 2001 _ => panic!( 2002 "y should always be <= 256/512 in OneScreen and VerticalMirror sizes; was {y}" 2003 ), 2004 }; 2005 y &= single_screen_height_pixels - 1; 2006 } 2007 2008 let tile_row = y / bg_tile_height_pixels; 2009 let tile_col = x / bg_tile_width_pixels; 2010 let tile_map_addr = 32 * tile_row + tile_col; 2011 2012 vram[(bg_map_base_addr.wrapping_add(tile_map_addr) & VRAM_ADDRESS_MASK) as usize] 2013} 2014 2015fn get_bg_tile_size(bg_mode: BgMode, tile_size: TileSize) -> (u16, u16) { 2016 match (bg_mode, tile_size) { 2017 (BgMode::Six, _) | (BgMode::Five, TileSize::Small) => (16, 8), 2018 (_, TileSize::Small) => (8, 8), 2019 (_, TileSize::Large) => (16, 16), 2020 } 2021} 2022 2023fn line_overlaps_sprite(sprite_y: u8, sprite_height: u16, scanline: u16) -> bool { 2024 let scanline = scanline as u8; 2025 let sprite_bottom = sprite_y.wrapping_add(sprite_height as u8); 2026 if sprite_bottom > sprite_y { 2027 (sprite_y..sprite_bottom).contains(&scanline) 2028 } else { 2029 scanline >= sprite_y || scanline < sprite_bottom 2030 } 2031} 2032 2033fn resolve_pixel_color( 2034 cgram: &Cgram, 2035 layer: Layer, 2036 bg_mode: BgMode, 2037 direct_color_mode: bool, 2038 palette: u8, 2039 color: u8, 2040) -> u16 { 2041 let bpp = match (layer, bg_mode) { 2042 (Layer::Bg1 | Layer::Bg2, BgMode::Seven) => BitsPerPixel::Eight, 2043 (Layer::Bg1, _) => bg_mode.bg1_bpp(), 2044 (Layer::Bg2, _) => bg_mode.bg2_bpp(), 2045 (Layer::Bg3, _) => BitsPerPixel::BG3, 2046 (Layer::Bg4, _) => BitsPerPixel::BG4, 2047 (Layer::Obj, _) => BitsPerPixel::OBJ, 2048 (Layer::Backdrop, _) => { 2049 panic!("invalid input to resolve_pixel_color: mode={bg_mode:?}, layer={layer:?}") 2050 } 2051 }; 2052 2053 let two_bpp_offset = if bg_mode == BgMode::Zero { 2054 // Mode 0 gives each BG layer its own set of 8 palettes 2055 match layer { 2056 Layer::Bg1 | Layer::Obj => 0x00, 2057 Layer::Bg2 => 0x20, 2058 Layer::Bg3 => 0x40, 2059 Layer::Bg4 => 0x60, 2060 Layer::Backdrop => unreachable!("above match checks layer is not backdrop"), 2061 } 2062 } else { 2063 0 2064 }; 2065 2066 // Sprites only use palettes in the second half of CGRAM 2067 let four_bpp_offset = if layer == Layer::Obj { 0x80 } else { 0 }; 2068 2069 match bpp { 2070 BitsPerPixel::Two => cgram[(two_bpp_offset | (palette << 2) | color) as usize], 2071 BitsPerPixel::Four => cgram[(four_bpp_offset | (palette << 4) | color) as usize], 2072 BitsPerPixel::Eight => { 2073 if direct_color_mode { 2074 resolve_direct_color(palette, color) 2075 } else { 2076 cgram[color as usize] 2077 } 2078 } 2079 } 2080} 2081 2082fn resolve_direct_color(palette: u8, color: u8) -> u16 { 2083 let color: u16 = color.into(); 2084 let palette: u16 = palette.into(); 2085 2086 // Color (8-bit) interpreted as BBGGGRRR 2087 // Palette (3-bit) interpreted as bgr 2088 // Result (16-bit): 0 BBb00 GGGg0 RRRr0 2089 let r_component = ((color & 0b00_000_111) << 2) | ((palette & 0b001) << 1); 2090 let g_component = ((color & 0b00_111_000) << 4) | ((palette & 0b010) << 5); 2091 let b_component = ((color & 0b11_000_000) << 7) | ((palette & 0b100) << 10); 2092 r_component | g_component | b_component 2093} 2094 2095fn convert_snes_color(snes_color: u16, brightness: u8) -> Color { 2096 let color_table = &colortable::TABLE[brightness as usize]; 2097 2098 let r = color_table[(snes_color & 0x1F) as usize]; 2099 let g = color_table[((snes_color >> 5) & 0x1F) as usize]; 2100 let b = color_table[((snes_color >> 10) & 0x1F) as usize]; 2101 Color::rgb(r, g, b) 2102} 2103 2104#[cfg(test)] 2105mod tests { 2106 use super::*; 2107 2108 #[test] 2109 fn direct_color() { 2110 assert_eq!(0b00000_00000_11100, resolve_direct_color(0b000, 0b00_000_111)); 2111 assert_eq!(0b00000_00000_11110, resolve_direct_color(0b001, 0b00_000_111)); 2112 2113 assert_eq!(0b00000_11100_00000, resolve_direct_color(0b000, 0b00_111_000)); 2114 assert_eq!(0b00000_11110_00000, resolve_direct_color(0b010, 0b00_111_000)); 2115 2116 assert_eq!(0b11000_00000_00000, resolve_direct_color(0b000, 0b11_000_000)); 2117 assert_eq!(0b11100_00000_00000, resolve_direct_color(0b100, 0b11_000_000)); 2118 2119 assert_eq!(0b11100_11110_11110, resolve_direct_color(0b111, 0b11_111_111)); 2120 } 2121}