chore(ppu): improve accuracy of sprite pixel fifo
This commit is contained in:
		
							
								
								
									
										92
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										92
									
								
								src/ppu.rs
									
									
									
									
									
								
							| @@ -33,6 +33,7 @@ pub struct Ppu { | |||||||
|     pub vram: Box<[u8; VRAM_SIZE]>, |     pub vram: Box<[u8; VRAM_SIZE]>, | ||||||
|     pub stat: LCDStatus, |     pub stat: LCDStatus, | ||||||
|     pub oam: ObjectAttributeTable, |     pub oam: ObjectAttributeTable, | ||||||
|  |     clock: TimingClock, | ||||||
|     fetcher: PixelFetcher, |     fetcher: PixelFetcher, | ||||||
|     fifo: FifoRenderer, |     fifo: FifoRenderer, | ||||||
|     obj_buffer: ObjectBuffer, |     obj_buffer: ObjectBuffer, | ||||||
| @@ -82,7 +83,12 @@ impl Ppu { | |||||||
|  |  | ||||||
|                         self.stat.set_mode(Mode::HBlank); |                         self.stat.set_mode(Mode::HBlank); | ||||||
|                     } else { |                     } else { | ||||||
|  |                         if self.control.lcd_enabled() { | ||||||
|  |                             // Only Draw when the LCD Is Enabled | ||||||
|                             self.draw(self.cycles.into()); |                             self.draw(self.cycles.into()); | ||||||
|  |                         } else { | ||||||
|  |                             self.reset(); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 Mode::HBlank => { |                 Mode::HBlank => { | ||||||
| @@ -147,17 +153,22 @@ impl Ppu { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|  |             // The TimingClock is either Tick or Tock, and it changes | ||||||
|  |             // every other cycle, which means that we can use it to ensure | ||||||
|  |             // that things run every other cycle | ||||||
|  |             self.clock_next(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn scan_oam(&mut self, cycle: u32) { |     fn scan_oam(&mut self, cycle: u32) { | ||||||
|         if cycle % 2 == 0 { |         if self.clock == TimingClock::Tock { | ||||||
|             // This is run 50% of the time, or 40 times |             // This is run 50% of the time, or 40 times | ||||||
|             // which is the number of sprites in OAM |             // which is the number of sprites in OAM | ||||||
|  |  | ||||||
|             let sprite_height = match self.control.obj_size() { |             let sprite_height = match self.control.obj_size() { | ||||||
|                 ObjectSize::EightByEight => 8, |                 ObjectSize::Eight => 8, | ||||||
|                 ObjectSize::EightBySixteen => 16, |                 ObjectSize::Sixteen => 16, | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             let attr = self.oam.attribute((cycle / 2) as usize); |             let attr = self.oam.attribute((cycle / 2) as usize); | ||||||
| @@ -201,17 +212,17 @@ impl Ppu { | |||||||
|         if let Some(attr) = obj_attr { |         if let Some(attr) = obj_attr { | ||||||
|             match self.fetcher.obj.state { |             match self.fetcher.obj.state { | ||||||
|                 TileNumber => { |                 TileNumber => { | ||||||
|                     if cycle % 2 != 0 { |                     if self.clock == TimingClock::Tick { | ||||||
|                         self.fetcher.obj.tile.with_id(attr.tile_index); |                         self.fetcher.obj.tile.with_id(attr.tile_index); | ||||||
|  |  | ||||||
|                         self.fetcher.obj.next(TileDataLow); |                         self.fetcher.obj.next(TileDataLow); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 TileDataLow => { |                 TileDataLow => { | ||||||
|                     if cycle % 2 != 0 { |                     if self.clock == TimingClock::Tick { | ||||||
|                         let obj_size = match self.control.obj_size() { |                         let obj_size = match self.control.obj_size() { | ||||||
|                             ObjectSize::EightByEight => 8, |                             ObjectSize::Eight => 8, | ||||||
|                             ObjectSize::EightBySixteen => 16, |                             ObjectSize::Sixteen => 16, | ||||||
|                         }; |                         }; | ||||||
|  |  | ||||||
|                         let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size); |                         let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size); | ||||||
| @@ -223,10 +234,10 @@ impl Ppu { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 TileDataHigh => { |                 TileDataHigh => { | ||||||
|                     if cycle % 2 != 0 { |                     if self.clock == TimingClock::Tick { | ||||||
|                         let obj_size = match self.control.obj_size() { |                         let obj_size = match self.control.obj_size() { | ||||||
|                             ObjectSize::EightByEight => 8, |                             ObjectSize::Eight => 8, | ||||||
|                             ObjectSize::EightBySixteen => 16, |                             ObjectSize::Sixteen => 16, | ||||||
|                         }; |                         }; | ||||||
|  |  | ||||||
|                         let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size); |                         let addr = PixelFetcher::get_obj_low_addr(&attr, &self.pos, obj_size); | ||||||
| @@ -251,8 +262,8 @@ impl Ppu { | |||||||
|                             let pixel = TwoBitsPerPixel::from_bytes(high, low); |                             let pixel = TwoBitsPerPixel::from_bytes(high, low); | ||||||
|  |  | ||||||
|                             let palette = match attr.flags.palette() { |                             let palette = match attr.flags.palette() { | ||||||
|                                 ObjectPaletteId::Palette0 => self.monochrome.obj_palette_0, |                                 ObjectPaletteId::Zero => self.monochrome.obj_palette_0, | ||||||
|                                 ObjectPaletteId::Palette1 => self.monochrome.obj_palette_1, |                                 ObjectPaletteId::One => self.monochrome.obj_palette_1, | ||||||
|                             }; |                             }; | ||||||
|  |  | ||||||
|                             let num_to_add = 8 - self.fifo.object.len(); |                             let num_to_add = 8 - self.fifo.object.len(); | ||||||
| @@ -288,7 +299,7 @@ impl Ppu { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // By only running on odd cycles, we can ensure that we draw every two T cycles |         // By only running on odd cycles, we can ensure that we draw every two T cycles | ||||||
|         if cycle % 2 != 0 && self.fetcher.bg.is_enabled() { |         if self.clock == TimingClock::Tick && self.fetcher.bg.is_enabled() { | ||||||
|             match self.fetcher.bg.state { |             match self.fetcher.bg.state { | ||||||
|                 TileNumber => { |                 TileNumber => { | ||||||
|                     // Increment Window line counter if scanline had any window pixels on it |                     // Increment Window line counter if scanline had any window pixels on it | ||||||
| @@ -369,6 +380,30 @@ impl Ppu { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     fn clock_next(&mut self) { | ||||||
|  |         use TimingClock::*; | ||||||
|  |  | ||||||
|  |         self.clock = match self.clock { | ||||||
|  |             Tick => Tock, | ||||||
|  |             Tock => Tick, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn reset(&mut self) { | ||||||
|  |         // FIXME: Discover what actually is supposed to be reset here | ||||||
|  |  | ||||||
|  |         self.clock = Default::default(); | ||||||
|  |         self.cycles = Cycle::new(0); | ||||||
|  |  | ||||||
|  |         self.x_pos = 0; | ||||||
|  |         self.stat.set_mode(Mode::OamScan); | ||||||
|  |         self.pos.line_y = 0; | ||||||
|  |  | ||||||
|  |         self.fetcher.bg.reset(); | ||||||
|  |         self.fetcher.obj.reset(); | ||||||
|  |         self.obj_buffer.clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     pub fn copy_to_gui(&self, frame: &mut [u8]) { |     pub fn copy_to_gui(&self, frame: &mut [u8]) { | ||||||
|         frame.copy_from_slice(self.frame_buf.as_ref()); |         frame.copy_from_slice(self.frame_buf.as_ref()); | ||||||
|     } |     } | ||||||
| @@ -386,6 +421,7 @@ impl Default for Ppu { | |||||||
|             pos: Default::default(), |             pos: Default::default(), | ||||||
|             stat: Default::default(), |             stat: Default::default(), | ||||||
|             oam: Default::default(), |             oam: Default::default(), | ||||||
|  |             clock: Default::default(), | ||||||
|             fetcher: Default::default(), |             fetcher: Default::default(), | ||||||
|             fifo: Default::default(), |             fifo: Default::default(), | ||||||
|             obj_buffer: Default::default(), |             obj_buffer: Default::default(), | ||||||
| @@ -394,6 +430,18 @@ impl Default for Ppu { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||||
|  | enum TimingClock { | ||||||
|  |     Tick = 0, | ||||||
|  |     Tock = 1, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Default for TimingClock { | ||||||
|  |     fn default() -> Self { | ||||||
|  |         Self::Tick | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy, Default)] | #[derive(Debug, Clone, Copy, Default)] | ||||||
| pub struct Interrupt { | pub struct Interrupt { | ||||||
|     _vblank: bool, |     _vblank: bool, | ||||||
| @@ -607,15 +655,15 @@ impl Default for TileDataAddress { | |||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| enum ObjectSize { | enum ObjectSize { | ||||||
|     EightByEight = 0, |     Eight = 0, | ||||||
|     EightBySixteen = 1, |     Sixteen = 1, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<u8> for ObjectSize { | impl From<u8> for ObjectSize { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         match byte { |         match byte { | ||||||
|             0b00 => Self::EightByEight, |             0b00 => Self::Eight, | ||||||
|             0b01 => Self::EightBySixteen, |             0b01 => Self::Sixteen, | ||||||
|             _ => unreachable!("{:#04X} is not a valid value for ObjSize", byte), |             _ => unreachable!("{:#04X} is not a valid value for ObjSize", byte), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -629,7 +677,7 @@ impl From<ObjectSize> for u8 { | |||||||
|  |  | ||||||
| impl Default for ObjectSize { | impl Default for ObjectSize { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         Self::EightByEight |         Self::Eight | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -938,15 +986,15 @@ impl Default for RenderPriority { | |||||||
|  |  | ||||||
| #[derive(Debug, Clone, Copy)] | #[derive(Debug, Clone, Copy)] | ||||||
| pub enum ObjectPaletteId { | pub enum ObjectPaletteId { | ||||||
|     Palette0 = 0, |     Zero = 0, | ||||||
|     Palette1 = 1, |     One = 1, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl From<u8> for ObjectPaletteId { | impl From<u8> for ObjectPaletteId { | ||||||
|     fn from(byte: u8) -> Self { |     fn from(byte: u8) -> Self { | ||||||
|         match byte { |         match byte { | ||||||
|             0b00 => ObjectPaletteId::Palette0, |             0b00 => ObjectPaletteId::Zero, | ||||||
|             0b01 => ObjectPaletteId::Palette1, |             0b01 => ObjectPaletteId::One, | ||||||
|             _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), |             _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user