1use crate::sa1::Iram; 2use crate::sa1::mmc::Sa1Mmc; 3use crate::sa1::registers::{ 4 CharacterConversionColorBits, DmaDestinationDevice, DmaSourceDevice, DmaState, Sa1Registers, 5}; 6use jgenesis_common::num::GetBit; 7 8impl Sa1Registers { 9 pub fn next_ccdma_byte(&mut self, iram: &mut Iram, bwram: &[u8]) -> u8 { 10 let DmaState::CharacterConversion1Active { 11 buffer_idx, 12 dma_bytes_remaining, 13 next_tile_number, 14 } = self.dma_state 15 else { 16 log::error!( 17 "next_ccdma_byte() called while CCDMA type 1 is not active; current DMA state is {:?}", 18 self.dma_state 19 ); 20 return 0; 21 }; 22 23 let tile_size = self.ccdma_color_depth.tile_size(); 24 let base_iram_addr = (self.dma_destination_address & self.ccdma_dest_addr_mask()) 25 + u32::from(buffer_idx) * tile_size; 26 let iram_addr = base_iram_addr + tile_size - u32::from(dma_bytes_remaining); 27 let next_byte = iram[(iram_addr & 0x7FF) as usize]; 28 29 if dma_bytes_remaining == 1 { 30 self.progress_ccdma_type_1(buffer_idx, next_tile_number, iram, bwram); 31 } else { 32 self.dma_state = DmaState::CharacterConversion1Active { 33 buffer_idx, 34 dma_bytes_remaining: dma_bytes_remaining - 1, 35 next_tile_number, 36 }; 37 } 38 39 next_byte 40 } 41 42 pub fn progress_normal_dma( 43 &mut self, 44 mmc: &Sa1Mmc, 45 rom: &[u8], 46 iram: &mut Iram, 47 bwram: &mut [u8], 48 ) { 49 let source_byte = match self.dma_source { 50 DmaSourceDevice::Rom => { 51 let Some(rom_addr) = mmc.map_rom_address(self.dma_source_address) else { 52 self.dma_state = DmaState::Idle; 53 return; 54 }; 55 rom.get(rom_addr as usize).copied().unwrap_or(0) 56 } 57 DmaSourceDevice::Iram => { 58 let iram_addr = self.dma_source_address & 0x7FF; 59 iram[iram_addr as usize] 60 } 61 DmaSourceDevice::Bwram => { 62 let bwram_addr = (self.dma_source_address as usize) & (bwram.len() - 1); 63 bwram[bwram_addr] 64 } 65 }; 66 67 match self.dma_destination { 68 DmaDestinationDevice::Iram => { 69 let iram_addr = self.dma_destination_address & 0x7FF; 70 iram[iram_addr as usize] = source_byte; 71 } 72 DmaDestinationDevice::Bwram => { 73 let bwram_addr = (self.dma_destination_address as usize) & (bwram.len() - 1); 74 bwram[bwram_addr] = source_byte; 75 } 76 } 77 78 self.dma_source_address = (self.dma_source_address + 1) & 0xFFFFFF; 79 self.dma_destination_address = (self.dma_destination_address + 1) & 0xFFFFFF; 80 self.dma_terminal_counter = self.dma_terminal_counter.wrapping_sub(1); 81 82 self.dma_state = match (self.dma_terminal_counter, self.dma_source, self.dma_destination) { 83 (0, _, _) => DmaState::Idle, 84 (_, DmaSourceDevice::Rom, DmaDestinationDevice::Iram) => DmaState::NormalCopying, 85 _ => DmaState::NormalWaitCycle, 86 }; 87 88 if self.dma_state == DmaState::Idle { 89 log::trace!("SA-1 DMA complete"); 90 self.sa1_dma_irq = true; 91 } 92 } 93 94 pub fn character_conversion_2( 95 &mut self, 96 base_idx: usize, 97 buffer_idx: u8, 98 rows_copied: u8, 99 iram: &mut Iram, 100 ) { 101 let buffer_idx: u32 = buffer_idx.into(); 102 let rows_copied: u32 = rows_copied.into(); 103 104 let color_depth = self.ccdma_color_depth; 105 let tile_size = color_depth.tile_size(); 106 107 let base_iram_addr = 108 (self.dma_destination_address & 0x7FF) + buffer_idx * tile_size + 2 * rows_copied; 109 110 // Convert 8 packed pixels to 1 row in an SNES tile 111 for pixel_idx in 0..8 { 112 let pixel = self.bitmap_pixels[base_idx + pixel_idx]; 113 let shift = 7 - pixel_idx; 114 115 for plane in (0..color_depth.bitplanes()).step_by(2) { 116 let iram_addr = ((base_iram_addr + 8 * plane) & 0x7FF) as usize; 117 let next_iram_addr = (iram_addr + 1) & 0x7FF; 118 119 iram[iram_addr] = 120 (iram[iram_addr] & !(1 << shift)) | (u8::from(pixel.bit(plane as u8)) << shift); 121 iram[next_iram_addr] = (iram[next_iram_addr] & !(1 << shift)) 122 | (u8::from(pixel.bit((plane + 1) as u8)) << shift); 123 } 124 } 125 126 let rows_copied = ((rows_copied + 1) & 0x07) as u8; 127 let buffer_idx = (if rows_copied == 0 { 1 - buffer_idx } else { buffer_idx }) as u8; 128 self.dma_state = DmaState::CharacterConversion2 { buffer_idx, rows_copied } 129 } 130 131 pub fn start_ccdma_type_1(&mut self, iram: &mut Iram, bwram: &[u8]) { 132 let source_addr = 133 self.dma_source_address & (bwram.len() as u32 - 1) & self.ccdma_source_addr_mask(); 134 let dest_addr = self.dma_destination_address & self.ccdma_dest_addr_mask(); 135 136 character_conversion_1_copy_tile( 137 source_addr, 138 dest_addr, 139 0, 140 self.ccdma_color_depth, 141 self.virtual_vram_width_tiles.into(), 142 iram, 143 bwram, 144 ); 145 146 self.dma_state = DmaState::CharacterConversion1Active { 147 buffer_idx: 0, 148 dma_bytes_remaining: self.ccdma_color_depth.tile_size() as u8, 149 next_tile_number: 1, 150 }; 151 152 log::trace!("CCDMA type 1 initial copy completed; generating IRQ"); 153 self.character_conversion_irq = true; 154 } 155 156 fn progress_ccdma_type_1( 157 &mut self, 158 buffer_idx: u8, 159 next_tile_number: u16, 160 iram: &mut Iram, 161 bwram: &[u8], 162 ) { 163 // Invert buffer index 164 let buffer_idx: u32 = (1 - buffer_idx).into(); 165 166 let source_addr = 167 self.dma_source_address & (bwram.len() as u32 - 1) & self.ccdma_source_addr_mask(); 168 let dest_addr = (self.dma_destination_address & self.ccdma_dest_addr_mask()) 169 + buffer_idx * self.ccdma_color_depth.tile_size(); 170 character_conversion_1_copy_tile( 171 source_addr, 172 dest_addr, 173 next_tile_number.into(), 174 self.ccdma_color_depth, 175 self.virtual_vram_width_tiles.into(), 176 iram, 177 bwram, 178 ); 179 180 self.dma_state = DmaState::CharacterConversion1Active { 181 buffer_idx: buffer_idx as u8, 182 dma_bytes_remaining: self.ccdma_color_depth.tile_size() as u8, 183 next_tile_number: next_tile_number + 1, 184 }; 185 } 186 187 fn ccdma_source_addr_mask(&self) -> u32 { 188 let shift = self.ccdma_color_depth.bitplanes().trailing_zeros() 189 + self.virtual_vram_width_tiles.trailing_zeros() 190 + 3; 191 !((1 << shift) - 1) 192 } 193 194 fn ccdma_dest_addr_mask(&self) -> u32 { 195 match self.ccdma_color_depth { 196 CharacterConversionColorBits::Two => !((1 << 5) - 1), 197 CharacterConversionColorBits::Four => !((1 << 6) - 1), 198 CharacterConversionColorBits::Eight => !((1 << 7) - 1), 199 } 200 } 201} 202 203fn character_conversion_1_copy_tile( 204 source_addr: u32, 205 dest_addr: u32, 206 tile_number: u32, 207 color_depth: CharacterConversionColorBits, 208 vram_width: u32, 209 iram: &mut Iram, 210 bwram: &[u8], 211) { 212 let bitplanes = color_depth.bitplanes(); 213 let pixels_per_byte = 8 / bitplanes; 214 let pixel_mask = if bitplanes == 8 { 0xFF } else { (1 << bitplanes) - 1 }; 215 216 let source_tile_addr = source_addr 217 + (tile_number & (vram_width - 1)) * bitplanes 218 + tile_number / vram_width * color_depth.tile_size() * vram_width; 219 220 for line in 0..8 { 221 let source_line_addr = source_tile_addr + line * bitplanes * vram_width; 222 let dest_line_addr = dest_addr + 2 * line; 223 224 for pixel in 0..8 { 225 let pixel_addr = 226 ((source_line_addr + pixel / pixels_per_byte) as usize) & (bwram.len() - 1); 227 let pixel_shift = match color_depth { 228 CharacterConversionColorBits::Two => 2 * (pixel % 4), 229 CharacterConversionColorBits::Four => 4 * (pixel % 2), 230 CharacterConversionColorBits::Eight => 0, 231 }; 232 233 let bm_pixel = (bwram[pixel_addr] >> pixel_shift) & pixel_mask; 234 let bm_shift = 7 - pixel; 235 236 for plane in (0..bitplanes).step_by(2) { 237 let iram_addr = ((dest_line_addr + 8 * plane) & 0x7FF) as usize; 238 let next_iram_addr = (iram_addr + 1) & 0x7FF; 239 240 iram[iram_addr] = (iram[iram_addr] & !(1 << bm_shift)) 241 | (u8::from(bm_pixel.bit(plane as u8)) << bm_shift); 242 iram[next_iram_addr] = (iram[next_iram_addr] & !(1 << bm_shift)) 243 | (u8::from(bm_pixel.bit((plane + 1) as u8)) << bm_shift); 244 } 245 } 246 } 247}