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}