Direct color DMA demos demonstrate that there's a ~3-slot latency between when an entry is written to the FIFO and when it can be popped
7pub const INITIAL_FIFO_LATENCY: u8 = 3;
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 10pub enum VramWriteFlag { 11 First, 12 Second, 13} 14 15#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 16pub enum VramWriteSize { 17 Word, 18 Byte, 19} 20 21#[derive(Debug, Clone, Encode, Decode)] 22pub struct VdpFifoEntry { 23 pub mode: DataPortMode, 24 pub location: DataPortLocation, 25 pub address: u32, 26 pub word: u16, 27 pub size: VramWriteSize, 28 pub vram_write: VramWriteFlag, 29 pub latency: u8, 30} 31 32impl VdpFifoEntry { 33 pub fn new( 34 mode: DataPortMode, 35 location: DataPortLocation, 36 address: u32, 37 word: u16, 38 size: VramWriteSize, 39 ) -> Self { 40 Self { 41 mode, 42 location, 43 address, 44 word, 45 size, 46 vram_write: VramWriteFlag::First, 47 latency: INITIAL_FIFO_LATENCY, 48 } 49 } 50} 51 52impl Default for VdpFifoEntry { 53 fn default() -> Self { 54 Self { 55 mode: DataPortMode::Read, 56 location: DataPortLocation::Vram, 57 address: 0, 58 word: 0, 59 size: VramWriteSize::Word, 60 vram_write: VramWriteFlag::First, 61 latency: INITIAL_FIFO_LATENCY, 62 } 63 } 64} 65 66const FIFO_LEN: u8 = 4; 67 68#[derive(Debug, Clone, Encode, Decode)] 69pub struct VdpFifo { 70 buffer: [VdpFifoEntry; FIFO_LEN as usize], 71 push_idx: u8, 72 pop_idx: u8, 73 len: u8, 74} 75 76impl VdpFifo { 77 pub fn new() -> Self { 78 Self { 79 buffer: array::from_fn(|_| VdpFifoEntry::default()), 80 push_idx: 0, 81 pop_idx: 0, 82 len: 0, 83 } 84 } 85 86 pub fn push(&mut self, entry: VdpFifoEntry) { 87 debug_assert!(self.len < FIFO_LEN); 88 89 self.buffer[self.push_idx as usize] = entry; 90 self.push_idx = (self.push_idx + 1) % FIFO_LEN; 91 self.len += 1; 92 } 93 94 pub fn front(&self) -> &VdpFifoEntry { 95 &self.buffer[self.pop_idx as usize] 96 } 97 98 pub fn next_slot_word(&self) -> u16 { 99 self.buffer[self.push_idx as usize].word 100 } 101 102 pub fn pop(&mut self) { 103 debug_assert_ne!(self.len, 0); 104 105 // Clue depends on FIFO entries for invalid targets taking 2 slots to pop, same as VRAM writes. 106 // Otherwise main menu graphics will be randomly corrupted 107 let front = &mut self.buffer[self.pop_idx as usize]; 108 if matches!(front.location, DataPortLocation::Vram | DataPortLocation::Invalid) 109 && front.size == VramWriteSize::Word 110 && front.vram_write == VramWriteFlag::First 111 { 112 front.vram_write = VramWriteFlag::Second; 113 return; 114 } 115 116 self.pop_idx = (self.pop_idx + 1) % FIFO_LEN; 117 self.len -= 1; 118 } 119 120 pub fn decrement_latency(&mut self) { 121 for i in 0..self.len { 122 let idx = (self.pop_idx + i) % 4; 123 let entry = &mut self.buffer[idx as usize]; 124 entry.latency = entry.latency.saturating_sub(1); 125 } 126 } 127 128 pub fn is_empty(&self) -> bool { 129 self.len == 0 130 } 131 132 pub fn is_full(&self) -> bool { 133 self.len == FIFO_LEN 134 } 135 136 pub fn len(&self) -> u8 { 137 self.len 138 } 139 140 pub fn iter(&self) -> VdpFifoIter<'_> { 141 VdpFifoIter { buffer: &self.buffer, idx: self.pop_idx, remaining: self.len } 142 } 143} 144 145pub struct VdpFifoIter<'fifo> { 146 buffer: &'fifo [VdpFifoEntry; FIFO_LEN as usize], 147 idx: u8, 148 remaining: u8, 149} 150 151impl<'fifo> Iterator for VdpFifoIter<'fifo> { 152 type Item = &'fifo VdpFifoEntry; 153 154 fn next(&mut self) -> Option<Self::Item> { 155 if self.remaining == 0 { 156 return None; 157 } 158 159 let value = &self.buffer[self.idx as usize]; 160 161 self.idx = (self.idx + 1) % FIFO_LEN; 162 self.remaining -= 1; 163 164 Some(value) 165 } 166 167 fn size_hint(&self) -> (usize, Option<usize>) { 168 (self.remaining.into(), Some(self.remaining.into())) 169 } 170}