dma.rsannotateddma.rssource247 lines · 8.8 KB · raw
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}