plot.rsannotatedplot.rssource304 lines · 9.5 KB · raw
1use crate::superfx::gsu::instructions::{
2    MemoryType, clear_prefix_flags, read_register, write_register,
3};
4use crate::superfx::gsu::{ClockSpeed, GraphicsSupportUnit, ScreenHeight};
5use bincode::{Decode, Encode};
6use jgenesis_common::num::{GetBit, SignBit};
7use std::cmp;
8
9#[derive(Debug, Clone, Encode, Decode)]
10struct PixelBuffer {
11    pixels: [u8; 8],
12    valid_bits: u8,
13}
14
15impl PixelBuffer {
16    fn new() -> Self {
17        Self { pixels: [0; 8], valid_bits: 0 }
18    }
19
20    fn write_pixel(&mut self, i: u8, color: u8) {
21        self.pixels[i as usize] = color;
22        self.valid_bits |= 1 << i;
23    }
24
25    fn is_valid(&self, i: u8) -> bool {
26        self.valid_bits.bit(i)
27    }
28
29    fn any_valid(&self) -> bool {
30        self.valid_bits != 0
31    }
32
33    fn all_valid(&self) -> bool {
34        self.valid_bits == 0xFF
35    }
36
37    fn clear_valid(&mut self) {
38        self.valid_bits = 0;
39    }
40}
41
42#[derive(Debug, Clone, Encode, Decode)]
43pub struct PlotState {
44    pixel_buffer: PixelBuffer,
45    // The secondary pixel buffer is not explicitly stored; implementation writes values to RAM
46    // immediately when primary buffer is flushed
47    last_coarse_x: u8,
48    last_y: u8,
49    flush_cycles_remaining: u8,
50    just_flushed: bool,
51}
52
53impl PlotState {
54    pub fn new() -> Self {
55        Self {
56            pixel_buffer: PixelBuffer::new(),
57            last_coarse_x: 0,
58            last_y: 0,
59            flush_cycles_remaining: 0,
60            just_flushed: false,
61        }
62    }
63
64    pub fn tick(&mut self, gsu_cycles: u8) {
65        if self.just_flushed {
66            self.just_flushed = false;
67        } else {
68            self.flush_cycles_remaining = self.flush_cycles_remaining.saturating_sub(gsu_cycles);
69        }
70    }
71}
72
73pub(super) fn cmode(memory_type: MemoryType, gsu: &mut GraphicsSupportUnit) -> u8 {
74    // CMODE: Set POR (plot option register)
75    let source = read_register(gsu, gsu.sreg);
76
77    gsu.plot_transparent_pixels = source.bit(0);
78    gsu.dither_on = source.bit(1);
79    gsu.por_high_nibble_flag = source.bit(2);
80    gsu.por_freeze_high_nibble = source.bit(3);
81    gsu.force_obj_mode = source.bit(4);
82
83    log::trace!("Plot transparent pixels: {}", gsu.plot_transparent_pixels);
84    log::trace!("Dithering on: {}", gsu.dither_on);
85    log::trace!("High nibble only in color writes: {}", gsu.por_high_nibble_flag);
86    log::trace!("Freeze color high nibble: {}", gsu.por_freeze_high_nibble);
87    log::trace!("Force OBJ mode: {}", gsu.force_obj_mode);
88
89    clear_prefix_flags(gsu);
90    memory_type.access_cycles(gsu.clock_speed)
91}
92
93pub(super) fn color(memory_type: MemoryType, gsu: &mut GraphicsSupportUnit) -> u8 {
94    // COLOR: Set color register
95    let source = read_register(gsu, gsu.sreg);
96    gsu.color = mask_color(source as u8, gsu);
97
98    clear_prefix_flags(gsu);
99    memory_type.access_cycles(gsu.clock_speed)
100}
101
102pub(super) fn getc(memory_type: MemoryType, gsu: &mut GraphicsSupportUnit) -> u8 {
103    // GETC: Get byte from ROM into color register
104    let byte = gsu.state.rom_buffer;
105    gsu.color = mask_color(byte, gsu);
106
107    let cycles = gsu.state.rom_buffer_wait_cycles;
108    gsu.state.rom_buffer_wait_cycles = 0;
109
110    clear_prefix_flags(gsu);
111    cycles + memory_type.access_cycles(gsu.clock_speed)
112}
113
114fn mask_color(mut new_color: u8, gsu: &GraphicsSupportUnit) -> u8 {
115    if gsu.por_high_nibble_flag {
116        // Replace low nibble with a copy of high nibble
117        new_color = (new_color & 0xF0) | (new_color >> 4);
118    }
119
120    if gsu.por_freeze_high_nibble {
121        // Copy high nibble from existing color register
122        new_color = (new_color & 0x0F) | (gsu.color & 0xF0);
123    }
124
125    new_color
126}
127
128pub(super) fn plot(memory_type: MemoryType, gsu: &mut GraphicsSupportUnit, ram: &mut [u8]) -> u8 {
129    // PLOT: Plot a pixel to the primary pixel buffer
130    let x = gsu.r[1] as u8;
131    let y = gsu.r[2] as u8;
132
133    let mut cycles = 0;
134
135    let coarse_x = x & !0x07;
136    if (coarse_x != gsu.plot_state.last_coarse_x || y != gsu.plot_state.last_y)
137        && gsu.plot_state.pixel_buffer.any_valid()
138    {
139        cycles += flush_pixel_buffer(gsu, ram);
140    }
141
142    gsu.plot_state.last_coarse_x = coarse_x;
143    gsu.plot_state.last_y = y;
144
145    let color = if gsu.dither_on && (x.bit(0) ^ y.bit(0)) { gsu.color >> 4 } else { gsu.color };
146    let is_transparent = if gsu.por_freeze_high_nibble {
147        // If high nibble is frozen, transparency check only looks at the lowest 2/4 bits even in
148        // 256-color mode
149        color & 0x0F & gsu.color_gradient.color_mask() == 0
150    } else {
151        color & gsu.color_gradient.color_mask() == 0
152    };
153
154    if gsu.plot_transparent_pixels || !is_transparent {
155        let i = x & 0x07;
156        gsu.plot_state.pixel_buffer.write_pixel(i, color);
157
158        if gsu.plot_state.pixel_buffer.all_valid() {
159            cycles += flush_pixel_buffer(gsu, ram);
160        }
161    }
162
163    gsu.r[1] = gsu.r[1].wrapping_add(1);
164
165    log::trace!("PLOT: x={x}, y={y}, color={color:02X}");
166
167    clear_prefix_flags(gsu);
168    cmp::max(memory_type.access_cycles(gsu.clock_speed), cycles)
169}
170
171pub(super) fn rpix(
172    memory_type: MemoryType,
173    gsu: &mut GraphicsSupportUnit,
174    rom: &[u8],
175    ram: &mut [u8],
176) -> u8 {
177    // RPIX: Read a pixel from RAM and flush both pixel buffers
178    let bitplanes = gsu.color_gradient.bitplanes();
179    let mut cycles = bitplanes as u8 * gsu.clock_speed.memory_access_cycles();
180    if memory_type != MemoryType::CodeCache {
181        cycles += 4;
182    }
183
184    if !gsu.plot_state.pixel_buffer.any_valid() || gsu.plot_state.pixel_buffer.all_valid() {
185        cycles += match gsu.clock_speed {
186            ClockSpeed::Slow => 7 * bitplanes as u8 - bitplanes as u8 / 2,
187            ClockSpeed::Fast => 10 * bitplanes as u8,
188        };
189    }
190
191    if gsu.plot_state.pixel_buffer.any_valid() {
192        cycles += flush_pixel_buffer(gsu, ram);
193    }
194
195    cycles += gsu.plot_state.flush_cycles_remaining;
196    gsu.plot_state.flush_cycles_remaining = 0;
197
198    let x = gsu.r[1] as u8;
199    let y = gsu.r[2] as u8;
200
201    let tile_addr = compute_tile_addr(gsu, x, y, ram.len());
202    let tile_size = gsu.color_gradient.tile_size();
203    let tile_data = &ram[tile_addr..tile_addr + tile_size as usize];
204
205    let row = y & 0x07;
206    let line_base_addr: u32 = (row * 0x02).into();
207
208    let pixel_idx = x & 0x07;
209    let bitplane_idx = 7 - pixel_idx;
210
211    let mut color = 0;
212    for plane in (0..bitplanes).step_by(2) {
213        let plane_addr = (line_base_addr + 8 * plane) as usize;
214
215        color |= u8::from(tile_data[plane_addr].bit(bitplane_idx)) << plane;
216        color |= u8::from(tile_data[plane_addr + 1].bit(bitplane_idx)) << (plane + 1);
217    }
218
219    cycles += write_register(gsu, gsu.dreg, color.into(), rom, ram);
220
221    gsu.zero_flag = color == 0;
222    gsu.sign_flag = color.sign_bit();
223
224    clear_prefix_flags(gsu);
225    cycles
226}
227
228#[must_use]
229fn flush_pixel_buffer(gsu: &mut GraphicsSupportUnit, ram: &mut [u8]) -> u8 {
230    let x = gsu.plot_state.last_coarse_x;
231    let y = gsu.plot_state.last_y;
232
233    let tile_addr = compute_tile_addr(gsu, x, y, ram.len());
234    let tile_size = gsu.color_gradient.tile_size();
235
236    let tile_data = &mut ram[tile_addr..tile_addr + tile_size as usize];
237
238    let row = y & 0x07;
239    let line_base_addr: u32 = (row * 0x02).into();
240
241    log::trace!(
242        "  Flushing pixel buffer; base={:05X}, x={x}, y={y}, tile_addr={tile_addr:04X}, line_addr={line_base_addr:02X}",
243        gsu.screen_base
244    );
245
246    // Convert row of pixels from bitmap format to SNES bitplane format, only overwriting pixels that
247    // have the valid flag set in the pixel buffer
248    let bitplanes = gsu.color_gradient.bitplanes();
249    for pixel_idx in 0..8 {
250        if !gsu.plot_state.pixel_buffer.is_valid(pixel_idx) {
251            continue;
252        }
253
254        let shift = 7 - pixel_idx;
255        let color = gsu.plot_state.pixel_buffer.pixels[pixel_idx as usize];
256
257        for plane in (0..bitplanes).step_by(2) {
258            let plane_addr = (line_base_addr + 8 * plane) as usize;
259
260            tile_data[plane_addr] = (tile_data[plane_addr] & !(1 << shift))
261                | (u8::from(color.bit(plane as u8)) << shift);
262            tile_data[plane_addr + 1] = (tile_data[plane_addr + 1] & !(1 << shift))
263                | (u8::from(color.bit(plane as u8 + 1)) << shift);
264        }
265    }
266
267    let cycles = gsu.plot_state.flush_cycles_remaining;
268
269    let mut flush_cycles_required = gsu.clock_speed.memory_access_cycles() * bitplanes as u8;
270    if !gsu.plot_state.pixel_buffer.all_valid() {
271        // If not all 8 bit-pend flags are set, the chip needs to perform a read before each write
272        flush_cycles_required *= 2;
273    }
274
275    gsu.plot_state.pixel_buffer.clear_valid();
276    gsu.plot_state.flush_cycles_remaining = flush_cycles_required;
277    gsu.plot_state.just_flushed = true;
278
279    cycles
280}
281
282fn compute_tile_addr(gsu: &GraphicsSupportUnit, x: u8, y: u8, ram_len: usize) -> usize {
283    let tile_x: u16 = (x / 8).into();
284    let tile_y: u16 = (y / 8).into();
285
286    let screen_height = if gsu.force_obj_mode { ScreenHeight::ObjMode } else { gsu.screen_height };
287
288    let tile_number = match screen_height {
289        ScreenHeight::Bg128Pixel => tile_x * 0x10 + tile_y,
290        ScreenHeight::Bg160Pixel => tile_x * 0x14 + tile_y,
291        ScreenHeight::Bg192Pixel => tile_x * 0x18 + tile_y,
292        ScreenHeight::ObjMode => {
293            let grid_offset = (u16::from(y.bit(7)) << 9) | (u16::from(x.bit(7)) << 8);
294            let grid_x = tile_x & 0x0F;
295            let grid_y = tile_y & 0x0F;
296            grid_offset + grid_y * 0x10 + grid_x
297        }
298    };
299    let tile_number: u32 = tile_number.into();
300
301    let tile_size = gsu.color_gradient.tile_size();
302    let tile_addr = gsu.screen_base + tile_number * tile_size;
303    (tile_addr as usize) & (ram_len - 1)
304}