From https://www.smspower.org/forums/8224-TMS9918ColorsForSMSVDP Used for TMS9918 modes on SMS
11pub const TMS9918_COLOR_TO_SMS_COLOR: &[u8; 16] = &[ 12 0x00, // Transparent (Black) 13 0x00, // Black 14 0x08, // Medium green 15 0x0C, // Light green 16 0x10, // Dark blue 17 0x30, // Light blue 18 0x01, // Dark red 19 0x3C, // Cyan 20 0x02, // Medium red 21 0x03, // Light red 22 0x05, // Dark yellow 23 0x0F, // Light yellow 24 0x04, // Dark green 25 0x33, // Magenta 26 0x15, // Gray 27 0x3F, // White 28];
From https://www.smspower.org/Development/Palette&num=2#SG1000SC3000 Used for SG-1000
32pub const TMS9918_COLOR_TO_RGB8: &[Color; 16] = &[ 33 Color::rgb(0x00, 0x00, 0x00), // Transparent 34 Color::rgb(0x00, 0x00, 0x00), // Black 35 Color::rgb(0x21, 0xC8, 0x42), // Medium green 36 Color::rgb(0x5E, 0xDC, 0x78), // Light green 37 Color::rgb(0x54, 0x55, 0xED), // Dark blue 38 Color::rgb(0x7D, 0x76, 0xFC), // Light blue 39 Color::rgb(0xD4, 0x52, 0x4D), // Dark red 40 Color::rgb(0x42, 0xEB, 0xF5), // Cyan 41 Color::rgb(0xFC, 0x55, 0x54), // Medium red 42 Color::rgb(0xFF, 0x79, 0x78), // Light red 43 Color::rgb(0xD4, 0xC1, 0x54), // Dark yellow 44 Color::rgb(0xE6, 0xCE, 0x80), // Light yellow 45 Color::rgb(0x21, 0xB0, 0x3B), // Dark green 46 Color::rgb(0xC9, 0x5B, 0xBA), // Magenta 47 Color::rgb(0xCC, 0xCC, 0xCC), // Gray 48 Color::rgb(0xFF, 0xFF, 0xFF), // White 49];
51pub const TMS9918_NOOP_LOOKUP: &[u8; 16] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; 52 53pub fn color_table(hardware: SmsGgHardware) -> &'static [u8; 16] { 54 match hardware { 55 SmsGgHardware::MasterSystem | SmsGgHardware::GameGear => TMS9918_COLOR_TO_SMS_COLOR, 56 SmsGgHardware::Sg1000 => TMS9918_NOOP_LOOKUP, // VDP-to-RGB8 code will convert to actual color 57 } 58} 59 60#[derive(Debug, Clone, Copy, Default)] 61struct Graphics2SpriteData { 62 y: u8, 63 x: u8, 64 name: u8, 65 color: u8, 66 early_clock: bool, 67} 68 69impl Vdp { 70 pub(super) fn render_text_scanline(&mut self, scanline: u16) { 71 let tms9918_color_table = color_table(self.registers.version.hardware()); 72 73 let frame_buffer_row = self.frame_buffer_row(scanline); 74 75 let text_colors = [ 76 tms9918_color_table[self.registers.backdrop_color as usize], 77 tms9918_color_table[self.registers.text_mode_color_1 as usize], 78 ]; 79 80 let base_name_table_addr = self.registers.base_name_table_address; 81 let base_pattern_generator = self.registers.pattern_generator_address; 82 83 let nametable_row = scanline / 8; 84 let line_name_table_addr = base_name_table_addr + nametable_row * 40; 85 86 let pattern_y_offset = scanline % 8; 87 88 for text_pattern in 0..40 { 89 let name_table_byte = self.vram[(line_name_table_addr + text_pattern) as usize]; 90 91 let pattern_generator_addr = 92 base_pattern_generator | (8 * u16::from(name_table_byte)) | pattern_y_offset; 93 let pattern_byte = self.vram[pattern_generator_addr as usize]; 94 95 for pattern_y in 0..6 { 96 let pixel = 6 * text_pattern + u16::from(pattern_y); 97 let pixel_color = text_colors[usize::from(pattern_byte.bit(7 - pattern_y))]; 98 self.frame_buffer.set(frame_buffer_row, pixel, pixel_color.into()); 99 } 100 } 101 102 for pixel in 40 * 6..vdp::SCREEN_WIDTH { 103 self.frame_buffer.set(frame_buffer_row, pixel, text_colors[0].into()); 104 } 105 } 106 107 pub(super) fn render_graphics_12_scanline(&mut self, scanline: u16, mode: Mode) { 108 let graphics2 = mode == Mode::GraphicsII; 109 110 let tms9918_color_table = color_table(self.registers.version.hardware()); 111 112 let frame_buffer_row = self.frame_buffer_row(scanline); 113 let backdrop_color = tms9918_color_table[self.registers.backdrop_color as usize]; 114 115 let base_name_table_addr = self.registers.base_name_table_address; 116 let base_color_table_addr = if graphics2 { 117 self.registers.color_table_address & 0x2000 118 } else { 119 self.registers.color_table_address 120 }; 121 let base_pattern_generator = if graphics2 { 122 self.registers.pattern_generator_address & 0x2000 123 } else { 124 self.registers.pattern_generator_address 125 }; 126 127 let nametable_row = scanline / 8; 128 let line_name_table_addr = base_name_table_addr | (nametable_row * 32); 129 130 // In Graphics II mode, pattern generator and color table are split into 3 blocks of 2048 131 // bytes each: one for the first 8 rows, one for the middle 8 rows, and one for the last 8 rows 132 let table_offset = if !graphics2 { 133 0 134 } else if nametable_row >= 16 { 135 4096 136 } else if nametable_row >= 8 { 137 2048 138 } else { 139 0 140 }; 141 142 let tile_row = scanline % 8; 143 144 // Scan for sprites on this line 145 let sprite_buffer = self.find_sprites_on_line(scanline as u8); 146 147 for nametable_col in 0..vdp::SCREEN_WIDTH / 8 { 148 let name_table_entry = self.vram[(line_name_table_addr | nametable_col) as usize]; 149 150 let pattern_generator_addr = 151 base_pattern_generator + table_offset + 8 * u16::from(name_table_entry) + tile_row; 152 let pattern_generator_entry = self.vram[pattern_generator_addr as usize]; 153 154 let color_table_addr = if graphics2 { 155 base_color_table_addr + table_offset + 8 * u16::from(name_table_entry) + tile_row 156 } else { 157 base_color_table_addr + u16::from(name_table_entry / 8) 158 }; 159 let color_table_entry = self.vram[color_table_addr as usize]; 160 let bg_color_0 = color_table_entry & 0x0F; 161 let bg_color_1 = color_table_entry >> 4; 162 163 for tile_col in 0..8 { 164 let pixel = 8 * nametable_col + u16::from(tile_col); 165 166 let sprite_color = 167 self.resolve_tms9918_sprite_color(&sprite_buffer, scanline, pixel); 168 169 let bg_color = 170 if pattern_generator_entry.bit(7 - tile_col) { bg_color_1 } else { bg_color_0 }; 171 172 let pixel_color = if sprite_color != 0 { 173 tms9918_color_table[sprite_color as usize] 174 } else if bg_color != 0 { 175 tms9918_color_table[bg_color as usize] 176 } else { 177 backdrop_color 178 }; 179 self.frame_buffer.set(frame_buffer_row, pixel, pixel_color.into()); 180 } 181 } 182 } 183 184 pub(super) fn render_multicolor_scanline(&mut self, scanline: u16) { 185 let tms9918_color_table = color_table(self.registers.version.hardware()); 186 187 let backdrop_color = tms9918_color_table[self.registers.backdrop_color as usize]; 188 let frame_buffer_row = self.frame_buffer_row(scanline); 189 190 let base_name_table_addr = self.registers.base_name_table_address; 191 192 let nametable_row = scanline / 8; 193 let line_name_table_addr = base_name_table_addr | (nametable_row * 32); 194 195 let base_pattern_generator_addr = self.registers.pattern_generator_address; 196 let pattern_y_offset = (scanline / 4) % 8; 197 198 let sprite_buffer = self.find_sprites_on_line(scanline as u8); 199 200 for nametable_col in 0..vdp::SCREEN_WIDTH / 8 { 201 let name_table_entry = self.vram[(line_name_table_addr | nametable_col) as usize]; 202 203 let pattern_generator_addr = 204 base_pattern_generator_addr | (8 * u16::from(name_table_entry)); 205 let pattern_byte = self.vram[(pattern_generator_addr | pattern_y_offset) as usize]; 206 207 let first_color = pattern_byte >> 4; 208 let second_color = pattern_byte & 0xF; 209 210 for x in 0..8 { 211 let pixel = 8 * nametable_col + x; 212 let sprite_color = 213 self.resolve_tms9918_sprite_color(&sprite_buffer, scanline, pixel); 214 215 let pixel_color = if sprite_color != 0 { 216 tms9918_color_table[sprite_color as usize] 217 } else if x < 4 && first_color != 0 { 218 tms9918_color_table[first_color as usize] 219 } else if x >= 4 && second_color != 0 { 220 tms9918_color_table[second_color as usize] 221 } else { 222 backdrop_color 223 }; 224 225 self.frame_buffer.set(frame_buffer_row, pixel, pixel_color.into()); 226 } 227 } 228 } 229 230 fn find_sprites_on_line( 231 &mut self, 232 scanline: u8, 233 ) -> ArrayVec<[Graphics2SpriteData; MAX_SPRITES_PER_LINE]> { 234 let base_sprite_table_addr = self.registers.latched_sprite.base_sprite_table_address; 235 236 let large_sprites = self.registers.latched_sprite.double_sprite_height; 237 let magnify_sprites = self.registers.latched_sprite.double_sprite_size; 238 let sprite_size = 8 << (u8::from(large_sprites) + u8::from(magnify_sprites)); 239 240 let mut sprite_buffer = ArrayVec::new(); 241 for sprite_idx in 0..32 { 242 let sprite_table_addr = base_sprite_table_addr + 4 * sprite_idx; 243 let y = self.vram[sprite_table_addr as usize]; 244 245 if y == 0xD0 { 246 // Termination signal 247 break; 248 } 249 250 // Sprites can wrap from below the bottom of the screen to the top 251 let sprite_bottom = y.wrapping_add(sprite_size); 252 let sprite_in_y_range = if y < sprite_bottom { 253 (y..sprite_bottom).contains(&scanline) 254 } else { 255 scanline >= y || scanline < sprite_bottom 256 }; 257 if !sprite_in_y_range { 258 continue; 259 } 260 261 if sprite_buffer.len() == sprite_buffer.capacity() { 262 self.registers.sprite_overflow = true; 263 self.registers.tms9918_5th_sprite = sprite_idx as u8; 264 break; 265 } 266 267 let x = self.vram[(sprite_table_addr + 1) as usize]; 268 let name = self.vram[(sprite_table_addr + 2) as usize]; 269 let attributes = self.vram[(sprite_table_addr + 3) as usize]; 270 let color = attributes & 0x0F; 271 let early_clock = attributes.bit(7); 272 273 if color == 0 { 274 // Transparent 275 continue; 276 } 277 278 sprite_buffer.push(Graphics2SpriteData { y, x, name, color, early_clock }); 279 } 280 281 sprite_buffer 282 } 283 284 fn resolve_tms9918_sprite_color( 285 &mut self, 286 sprite_buffer: &[Graphics2SpriteData], 287 scanline: u16, 288 pixel: u16, 289 ) -> u8 { 290 let large_sprites = self.registers.latched_sprite.double_sprite_height; 291 let magnify_sprites = self.registers.latched_sprite.double_sprite_size; 292 let sprite_size = 8 << (u8::from(large_sprites) + u8::from(magnify_sprites)); 293 294 let mut found_color: Option<u8> = None; 295 296 for &sprite in sprite_buffer { 297 let sprite_x = 298 if sprite.early_clock { i16::from(sprite.x) - 32 } else { i16::from(sprite.x) }; 299 300 let sprite_right = sprite_x + sprite_size; 301 if !(sprite_x..sprite_right).contains(&(pixel as i16)) { 302 continue; 303 } 304 305 let mut sprite_row = (scanline as u8).wrapping_sub(sprite.y); 306 let mut sprite_col = (pixel as i16 - sprite_x) as u8; 307 if magnify_sprites { 308 // Magnifying sprites simply blows up the sprite to 2x size in each dimension 309 sprite_row >>= 1; 310 sprite_col >>= 1; 311 } 312 313 // Mask out the lowest 2 bits of sprite name when using 16x16 sprites 314 let sprite_name_mask = if large_sprites { !0x03 } else { !0x00 }; 315 let mut sprite_pattern_addr = self.registers.latched_sprite.base_sprite_pattern_address 316 + 8 * u16::from(sprite.name & sprite_name_mask) 317 + u16::from(sprite_row % 8); 318 if sprite_row >= 8 { 319 sprite_pattern_addr += 8; 320 } 321 if sprite_col >= 8 { 322 sprite_pattern_addr += 16; 323 } 324 325 let sprite_pattern = self.vram[sprite_pattern_addr as usize]; 326 327 if !sprite_pattern.bit(7 - (sprite_col % 8)) { 328 continue; 329 } 330 331 if let Some(found_color) = found_color { 332 self.registers.sprite_collision = true; 333 return found_color; 334 } 335 336 found_color = Some(sprite.color); 337 } 338 339 found_color.unwrap_or(0) 340 } 341}