1use crate::vdp::{Mode, Vdp};
2use crate::{SmsGgHardware, vdp};
3use jgenesis_common::frontend::Color;
4use jgenesis_common::num::GetBit;
5use tinyvec::ArrayVec;
6
7const MAX_SPRITES_PER_LINE: usize = 4;
8
9// From https://www.smspower.org/forums/8224-TMS9918ColorsForSMSVDP
10// 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];
29
30// From https://www.smspower.org/Development/Palette&num=2#SG1000SC3000
31// 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];
50
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}