| 1 | /* |
| 2 | * SMS renderer |
| 3 | * (C) notaz, 2009-2010 |
| 4 | * (C) irixxxx, 2020-2024 |
| 5 | * |
| 6 | * currently supports VDP mode 4 (SMS and GG) and mode 3-0 (TMS) |
| 7 | * modes numbered after the bit numbers used in Sega and TI documentation |
| 8 | * |
| 9 | * This work is licensed under the terms of MAME license. |
| 10 | * See COPYING file in the top-level directory. |
| 11 | */ |
| 12 | #include "pico_int.h" |
| 13 | #include <platform/common/upscale.h> |
| 14 | |
| 15 | static void (*FinalizeLineSMS)(int line); |
| 16 | static int skip_next_line; |
| 17 | static int screen_offset, line_offset; |
| 18 | static u8 mode; |
| 19 | |
| 20 | static unsigned int sprites_addr[32]; // bitmap address |
| 21 | static unsigned char sprites_c[32]; // TMS sprites color |
| 22 | static int sprites_x[32]; // x position |
| 23 | static int sprites; // count |
| 24 | static unsigned char sprites_map[2+256/8+2]; // collision detection map |
| 25 | |
| 26 | unsigned int sprites_status; |
| 27 | |
| 28 | int sprites_zoom; // latched sprite zoom flag |
| 29 | int xscroll; // horizontal scroll |
| 30 | |
| 31 | /* sprite collision detection */ |
| 32 | static int CollisionDetect(u8 *mb, u16 sx, unsigned int pack, int zoomed) |
| 33 | { |
| 34 | static u8 morton[16] = { 0x00,0x03,0x0c,0x0f,0x30,0x33,0x3c,0x3f, |
| 35 | 0xc0,0xc3,0xcc,0xcf,0xf0,0xf3,0xfc,0xff }; |
| 36 | u8 *mp = mb + (sx>>3); |
| 37 | unsigned col, m; |
| 38 | |
| 39 | // check sprite map for collision and update map with current sprite |
| 40 | if (!zoomed) { // 8 sprite pixels |
| 41 | m = mp[0] | (mp[1]<<8); |
| 42 | col = m & (pack<<(sx&7)); // collision if current sprite overlaps sprite map |
| 43 | m |= pack<<(sx&7); |
| 44 | mp[0] = m, mp[1] = m>>8; |
| 45 | } else { // 16 sprite pixels in zoom mode |
| 46 | pack = morton[pack&0x0f] | (morton[(pack>>4)&0x0f] << 8); |
| 47 | m = mp[0] | (mp[1]<<8) | (mp[2]<<16); |
| 48 | col = m & (pack<<(sx&7)); |
| 49 | m |= pack<<(sx&7); |
| 50 | mp[0] = m, mp[1] = m>>8, mp[2] = m>>16; |
| 51 | } |
| 52 | |
| 53 | // invisible overscan area, not tested for collision |
| 54 | mb[0] = mb[33] = mb[34] = 0; |
| 55 | return col; |
| 56 | } |
| 57 | |
| 58 | /* Mode 4 - SMS Graphics */ |
| 59 | /*=======================*/ |
| 60 | |
| 61 | static void TileBGM4(u16 sx, int pal) |
| 62 | { |
| 63 | if (sx & 3) { |
| 64 | u8 *pd = (u8 *)(Pico.est.HighCol + sx); |
| 65 | pd[0] = pd[1] = pd[2] = pd[3] = pal; |
| 66 | pd[4] = pd[5] = pd[6] = pd[7] = pal; |
| 67 | } else { |
| 68 | u32 *pd = (u32 *)(Pico.est.HighCol + sx); |
| 69 | pd[0] = pd[1] = pal * 0x01010101; |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | // 8 pixels are arranged in 4 bitplane bytes in a 32 bit word. To pull the |
| 74 | // 4 bitplanes together multiply with each bit distance (multiples of 1<<7) |
| 75 | #define PLANAR_PIXELBG(x,p) \ |
| 76 | t = (pack>>(7-p)) & 0x01010101; \ |
| 77 | t = (t*0x10204080) >> 28; \ |
| 78 | pd[x] = pal|t; |
| 79 | |
| 80 | static void TileNormBGM4(u16 sx, unsigned int pack, int pal) |
| 81 | { |
| 82 | u8 *pd = Pico.est.HighCol + sx; |
| 83 | u32 t; |
| 84 | |
| 85 | PLANAR_PIXELBG(0, 0) |
| 86 | PLANAR_PIXELBG(1, 1) |
| 87 | PLANAR_PIXELBG(2, 2) |
| 88 | PLANAR_PIXELBG(3, 3) |
| 89 | PLANAR_PIXELBG(4, 4) |
| 90 | PLANAR_PIXELBG(5, 5) |
| 91 | PLANAR_PIXELBG(6, 6) |
| 92 | PLANAR_PIXELBG(7, 7) |
| 93 | } |
| 94 | |
| 95 | static void TileFlipBGM4(u16 sx, unsigned int pack, int pal) |
| 96 | { |
| 97 | u8 *pd = Pico.est.HighCol + sx; |
| 98 | u32 t; |
| 99 | |
| 100 | PLANAR_PIXELBG(0, 7) |
| 101 | PLANAR_PIXELBG(1, 6) |
| 102 | PLANAR_PIXELBG(2, 5) |
| 103 | PLANAR_PIXELBG(3, 4) |
| 104 | PLANAR_PIXELBG(4, 3) |
| 105 | PLANAR_PIXELBG(5, 2) |
| 106 | PLANAR_PIXELBG(6, 1) |
| 107 | PLANAR_PIXELBG(7, 0) |
| 108 | } |
| 109 | |
| 110 | // non-transparent sprite pixels apply if no higher prio pixel is already there |
| 111 | #define PLANAR_PIXELSP(x,p) \ |
| 112 | t = (pack>>(7-p)) & 0x01010101; \ |
| 113 | if (t && (pd[x] & 0x2f) <= 0x20) { \ |
| 114 | t = (t*0x10204080) >> 28; \ |
| 115 | pd[x] = pal|t; \ |
| 116 | } |
| 117 | |
| 118 | static void TileNormSprM4(u16 sx, unsigned int pack, int pal) |
| 119 | { |
| 120 | u8 *pd = Pico.est.HighCol + sx; |
| 121 | u32 t; |
| 122 | |
| 123 | PLANAR_PIXELSP(0, 0) |
| 124 | PLANAR_PIXELSP(1, 1) |
| 125 | PLANAR_PIXELSP(2, 2) |
| 126 | PLANAR_PIXELSP(3, 3) |
| 127 | PLANAR_PIXELSP(4, 4) |
| 128 | PLANAR_PIXELSP(5, 5) |
| 129 | PLANAR_PIXELSP(6, 6) |
| 130 | PLANAR_PIXELSP(7, 7) |
| 131 | } |
| 132 | |
| 133 | static void TileDoubleSprM4(int sx, unsigned int pack, int pal) |
| 134 | { |
| 135 | u8 *pd = Pico.est.HighCol + sx; |
| 136 | u32 t; |
| 137 | |
| 138 | PLANAR_PIXELSP(0, 0) |
| 139 | PLANAR_PIXELSP(1, 0) |
| 140 | PLANAR_PIXELSP(2, 1) |
| 141 | PLANAR_PIXELSP(3, 1) |
| 142 | PLANAR_PIXELSP(4, 2) |
| 143 | PLANAR_PIXELSP(5, 2) |
| 144 | PLANAR_PIXELSP(6, 3) |
| 145 | PLANAR_PIXELSP(7, 3) |
| 146 | PLANAR_PIXELSP(8, 4) |
| 147 | PLANAR_PIXELSP(9, 4) |
| 148 | PLANAR_PIXELSP(10, 5) |
| 149 | PLANAR_PIXELSP(11, 5) |
| 150 | PLANAR_PIXELSP(12, 6) |
| 151 | PLANAR_PIXELSP(13, 6) |
| 152 | PLANAR_PIXELSP(14, 7) |
| 153 | PLANAR_PIXELSP(15, 7) |
| 154 | } |
| 155 | |
| 156 | static void ParseSpritesM4(int scanline) |
| 157 | { |
| 158 | struct PicoVideo *pv = &Pico.video; |
| 159 | u8 *sat; |
| 160 | int xoff = line_offset; |
| 161 | int sprite_base, addr_mask; |
| 162 | int zoomed = sprites_zoom & 0x1; // zoomed sprites, e.g. Earthworm Jim |
| 163 | unsigned int pack; |
| 164 | int i, s, h, m; |
| 165 | |
| 166 | if (pv->reg[0] & 8) |
| 167 | xoff -= 8; // sprite shift |
| 168 | if (Pico.m.hardware & PMS_HW_LCD) |
| 169 | xoff -= 48; // GG LCD, adjust to center 160 px |
| 170 | |
| 171 | sat = (u8 *)PicoMem.vram + ((pv->reg[5] & 0x7e) << 7); |
| 172 | if (sprites_zoom & 2) { |
| 173 | addr_mask = 0xfe; h = 16; |
| 174 | } else { |
| 175 | addr_mask = 0xff; h = 8; |
| 176 | } |
| 177 | if (zoomed) h *= 2; |
| 178 | sprite_base = (pv->reg[6] & 4) << (13-2-1); |
| 179 | |
| 180 | m = pv->status & SR_C; |
| 181 | memset(sprites_map, 0, sizeof(sprites_map)); |
| 182 | for (i = s = 0; i < 64; i++) |
| 183 | { |
| 184 | int y; |
| 185 | y = sat[MEM_LE2(i)]; |
| 186 | if (y == 0xd0 && !((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18))) |
| 187 | break; |
| 188 | if (y >= 0xe0) |
| 189 | y -= 256; |
| 190 | y &= ~zoomed; // zoomed sprites apparently only on even lines, see GG Tarzan |
| 191 | if (y + h <= scanline || scanline < y) |
| 192 | continue; // not on this line |
| 193 | if (s >= 8) { |
| 194 | if (scanline >= 0) sprites_status |= SR_SOVR; |
| 195 | if (!(PicoIn.opt & POPT_DIS_SPRITE_LIM) || s >= 32) |
| 196 | break; |
| 197 | } |
| 198 | |
| 199 | if (xoff + sat[MEM_LE2(0x80 + i*2)] >= 0) { |
| 200 | sprites_x[s] = xoff + sat[MEM_LE2(0x80 + i*2)]; |
| 201 | sprites_addr[s] = sprite_base + ((sat[MEM_LE2(0x80 + i*2 + 1)] & addr_mask) << (5-1)) + |
| 202 | ((scanline - y) >> zoomed << (2-1)); |
| 203 | if (pv->reg[1] & 0x40) { |
| 204 | // collision detection. Do it here since off-screen lines aren't drawn |
| 205 | pack = CPU_LE2(*(u32 *)(PicoMem.vram + sprites_addr[s])); |
| 206 | // make sprite pixel map by merging the 4 bitplanes |
| 207 | pack = ((pack | (pack>>16)) | ((pack | (pack>>16))>>8)) & 0xff; |
| 208 | if (!m) m = CollisionDetect(sprites_map, sprites_x[s], pack, zoomed); |
| 209 | // no collision detection in 1st column if it's masked |
| 210 | if (pv->reg[0] & 0x20) |
| 211 | sprites_map[1] = 0; |
| 212 | } |
| 213 | s++; |
| 214 | } |
| 215 | } |
| 216 | if (m) |
| 217 | sprites_status |= SR_C; |
| 218 | sprites = s; |
| 219 | } |
| 220 | |
| 221 | static void DrawSpritesM4(void) |
| 222 | { |
| 223 | unsigned int pack; |
| 224 | int zoomed = sprites_zoom & 0x1; // zoomed sprites, e.g. Earthworm Jim |
| 225 | int s = sprites; |
| 226 | |
| 227 | // now draw all sprites backwards |
| 228 | for (--s; s >= 0; s--) { |
| 229 | pack = CPU_LE2(*(u32 *)(PicoMem.vram + sprites_addr[s])); |
| 230 | if (zoomed) TileDoubleSprM4(sprites_x[s], pack, 0x10); |
| 231 | else TileNormSprM4(sprites_x[s], pack, 0x10); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // cells_dx, tilex_ty merged to reduce register pressure |
| 236 | static void DrawStripM4(const u16 *nametab, int cells_dx, int tilex_ty) |
| 237 | { |
| 238 | int oldcode = -1; |
| 239 | int addr = 0, pal = 0; |
| 240 | |
| 241 | // Draw tiles across screen: |
| 242 | for (; cells_dx >= 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000) |
| 243 | { |
| 244 | unsigned int pack; |
| 245 | unsigned code; |
| 246 | |
| 247 | code = nametab[tilex_ty & 0x1f]; |
| 248 | |
| 249 | if (code != oldcode) { |
| 250 | oldcode = code; |
| 251 | // Get tile address/2: |
| 252 | addr = (code & 0x1ff) << 4; |
| 253 | addr += tilex_ty >> 16; |
| 254 | if (code & 0x0400) |
| 255 | addr ^= 0xe; // Y-flip |
| 256 | |
| 257 | pal = (code>>7) & 0x30; // prio | palette select |
| 258 | } |
| 259 | |
| 260 | pack = CPU_LE2(*(u32 *)(PicoMem.vram + addr)); // Get 4 bitplanes / 8 pixels |
| 261 | if (pack == 0) TileBGM4(cells_dx, pal); |
| 262 | else if (code & 0x0200) TileFlipBGM4(cells_dx, pack, pal); |
| 263 | else TileNormBGM4(cells_dx, pack, pal); |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | static void DrawDisplayM4(int scanline) |
| 268 | { |
| 269 | struct PicoVideo *pv = &Pico.video; |
| 270 | u16 *nametab, *nametab2; |
| 271 | int line, tilex, dx, ty, cells; |
| 272 | int cellskip = 0; // XXX |
| 273 | int maxcells = 32; |
| 274 | |
| 275 | // Find the line in the name table |
| 276 | line = pv->reg[9] + scanline; // vscroll + scanline |
| 277 | |
| 278 | // Find name table: |
| 279 | nametab = PicoMem.vram; |
| 280 | if ((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)) { |
| 281 | // 224/240 line mode |
| 282 | line &= 0xff; |
| 283 | nametab += ((pv->reg[2] & 0x0c) << (10-1)) + (0x700 >> 1); |
| 284 | } else { |
| 285 | while (line >= 224) line -= 224; |
| 286 | nametab += (pv->reg[2] & 0x0e) << (10-1); |
| 287 | // old SMS only, masks line:7 with reg[2]:0 for address calculation |
| 288 | //if ((pv->reg[2] & 0x01) == 0) line &= 0x7f; |
| 289 | } |
| 290 | nametab2 = nametab + ((scanline>>3) << (6-1)); |
| 291 | nametab = nametab + ((line>>3) << (6-1)); |
| 292 | |
| 293 | dx = xscroll; // hscroll |
| 294 | if (scanline < 16 && (pv->reg[0] & 0x40)) |
| 295 | dx = 0; // hscroll disabled for top 2 rows (e.g. Fantasy Zone II) |
| 296 | |
| 297 | tilex = (32 - (dx >> 3) + cellskip) & 0x1f; |
| 298 | ty = (line & 7) << 1; // Y-Offset into tile |
| 299 | cells = maxcells - cellskip - 1; |
| 300 | |
| 301 | dx = (dx & 7); |
| 302 | dx += cellskip << 3; |
| 303 | dx += line_offset; |
| 304 | |
| 305 | // tiles |
| 306 | if (!(pv->debug_p & PVD_KILL_B)) { |
| 307 | if (Pico.m.hardware & PMS_HW_LCD) { |
| 308 | // on GG render only the center 160 px, but mind hscroll |
| 309 | DrawStripM4(nametab , (dx-8) | ((cells-11)<< 16),(tilex+5) | (ty << 16)); |
| 310 | } else if (pv->reg[0] & 0x80) { |
| 311 | // vscroll disabled for rightmost 8 columns (e.g. Gauntlet) |
| 312 | int dx2 = dx + (cells-8)*8, tilex2 = tilex + (cells-8), ty2 = scanline&7; |
| 313 | DrawStripM4(nametab, dx | ((cells-8) << 16), tilex | (ty << 16)); |
| 314 | DrawStripM4(nametab2, dx2 | (8 << 16), tilex2 | (ty2 << 17)); |
| 315 | } else |
| 316 | DrawStripM4(nametab , dx | ( cells << 16), tilex | (ty << 16)); |
| 317 | } |
| 318 | |
| 319 | // sprites |
| 320 | if (!(pv->debug_p & PVD_KILL_S_LO)) |
| 321 | DrawSpritesM4(); |
| 322 | |
| 323 | if ((pv->reg[0] & 0x20) && !(Pico.m.hardware & PMS_HW_LCD)) { |
| 324 | // first column masked with background, caculate offset to start of line |
| 325 | dx = line_offset / 4; |
| 326 | ty = ((pv->reg[7]&0x0f)|0x10) * 0x01010101; |
| 327 | ((u32 *)Pico.est.HighCol)[dx] = ((u32 *)Pico.est.HighCol)[dx+1] = ty; |
| 328 | } |
| 329 | } |
| 330 | |
| 331 | |
| 332 | /* TMS Modes */ |
| 333 | /*===========*/ |
| 334 | |
| 335 | /* Background */ |
| 336 | |
| 337 | #define TMS_PIXELBG(x,p) \ |
| 338 | t = (pack>>(7-p)) & 0x01; \ |
| 339 | t = (pal >> (t << 2)) & 0x0f; \ |
| 340 | if (t) \ |
| 341 | pd[x] = t; |
| 342 | |
| 343 | static void TileNormBgM1(u16 sx, unsigned int pack, int pal) /* Text */ |
| 344 | { |
| 345 | u8 *pd = Pico.est.HighCol + sx; |
| 346 | unsigned int t; |
| 347 | |
| 348 | TMS_PIXELBG(0, 0) |
| 349 | TMS_PIXELBG(1, 1) |
| 350 | TMS_PIXELBG(2, 2) |
| 351 | TMS_PIXELBG(3, 3) |
| 352 | TMS_PIXELBG(4, 4) |
| 353 | TMS_PIXELBG(5, 5) |
| 354 | } |
| 355 | |
| 356 | static void TileNormBgM2(u16 sx, int pal) /* Multicolor */ |
| 357 | { |
| 358 | u8 *pd = Pico.est.HighCol + sx; |
| 359 | unsigned int pack = 0xf0; |
| 360 | unsigned int t; |
| 361 | |
| 362 | TMS_PIXELBG(0, 0) |
| 363 | TMS_PIXELBG(1, 1) |
| 364 | TMS_PIXELBG(2, 2) |
| 365 | TMS_PIXELBG(3, 3) |
| 366 | TMS_PIXELBG(4, 4) |
| 367 | TMS_PIXELBG(5, 5) |
| 368 | TMS_PIXELBG(6, 6) |
| 369 | TMS_PIXELBG(7, 7) |
| 370 | } |
| 371 | |
| 372 | static void TileNormBgMg(u16 sx, unsigned int pack, int pal) /* Graphics */ |
| 373 | { |
| 374 | u8 *pd = Pico.est.HighCol + sx; |
| 375 | unsigned int t; |
| 376 | |
| 377 | TMS_PIXELBG(0, 0) |
| 378 | TMS_PIXELBG(1, 1) |
| 379 | TMS_PIXELBG(2, 2) |
| 380 | TMS_PIXELBG(3, 3) |
| 381 | TMS_PIXELBG(4, 4) |
| 382 | TMS_PIXELBG(5, 5) |
| 383 | TMS_PIXELBG(6, 6) |
| 384 | TMS_PIXELBG(7, 7) |
| 385 | } |
| 386 | |
| 387 | /* Sprites */ |
| 388 | |
| 389 | #define TMS_PIXELSP(x,p) \ |
| 390 | t = (pack>>(7-p)) & 0x01; \ |
| 391 | if (t) \ |
| 392 | pd[x] = pal; |
| 393 | |
| 394 | static void TileNormSprTMS(u16 sx, unsigned int pack, int pal) |
| 395 | { |
| 396 | u8 *pd = Pico.est.HighCol + sx; |
| 397 | unsigned int t; |
| 398 | |
| 399 | TMS_PIXELSP(0, 0) |
| 400 | TMS_PIXELSP(1, 1) |
| 401 | TMS_PIXELSP(2, 2) |
| 402 | TMS_PIXELSP(3, 3) |
| 403 | TMS_PIXELSP(4, 4) |
| 404 | TMS_PIXELSP(5, 5) |
| 405 | TMS_PIXELSP(6, 6) |
| 406 | TMS_PIXELSP(7, 7) |
| 407 | } |
| 408 | |
| 409 | static void TileDoubleSprTMS(u16 sx, unsigned int pack, int pal) |
| 410 | { |
| 411 | u8 *pd = Pico.est.HighCol + sx; |
| 412 | unsigned int t; |
| 413 | |
| 414 | TMS_PIXELSP(0, 0) |
| 415 | TMS_PIXELSP(1, 0) |
| 416 | TMS_PIXELSP(2, 1) |
| 417 | TMS_PIXELSP(3, 1) |
| 418 | TMS_PIXELSP(4, 2) |
| 419 | TMS_PIXELSP(5, 2) |
| 420 | TMS_PIXELSP(6, 3) |
| 421 | TMS_PIXELSP(7, 3) |
| 422 | TMS_PIXELSP(8, 4) |
| 423 | TMS_PIXELSP(9, 4) |
| 424 | TMS_PIXELSP(10, 5) |
| 425 | TMS_PIXELSP(11, 5) |
| 426 | TMS_PIXELSP(12, 6) |
| 427 | TMS_PIXELSP(13, 6) |
| 428 | TMS_PIXELSP(14, 7) |
| 429 | TMS_PIXELSP(15, 7) |
| 430 | } |
| 431 | |
| 432 | static void ParseSpritesTMS(int scanline) |
| 433 | { |
| 434 | struct PicoVideo *pv = &Pico.video; |
| 435 | unsigned int pack; |
| 436 | u8 *sat; |
| 437 | int xoff; |
| 438 | int sprite_base, addr_mask; |
| 439 | int zoomed = sprites_zoom & 0x1; // zoomed sprites |
| 440 | int i, s, h, m; |
| 441 | |
| 442 | xoff = line_offset; |
| 443 | |
| 444 | sat = (u8 *)PicoMem.vramb + ((pv->reg[5] & 0x7f) << 7); |
| 445 | if (sprites_zoom & 2) { |
| 446 | addr_mask = 0xfc; h = 16; |
| 447 | } else { |
| 448 | addr_mask = 0xff; h = 8; |
| 449 | } |
| 450 | if (zoomed) h *= 2; |
| 451 | sprite_base = (pv->reg[6] & 0x7) << 11; |
| 452 | |
| 453 | m = pv->status & SR_C; |
| 454 | memset(sprites_map, 0, sizeof(sprites_map)); |
| 455 | /* find sprites on this scanline */ |
| 456 | for (i = s = 0; i < 32; i++) |
| 457 | { |
| 458 | int x, y; |
| 459 | y = sat[MEM_LE2(4*i)]; |
| 460 | if (y == 0xd0) |
| 461 | break; |
| 462 | if (y >= 0xe0) |
| 463 | y -= 256; |
| 464 | y &= ~zoomed; |
| 465 | if (y + h <= scanline || scanline < y) |
| 466 | continue; // not on this line |
| 467 | if (s >= 4) { |
| 468 | if (scanline >= 0) sprites_status |= SR_SOVR | i; |
| 469 | if (!(PicoIn.opt & POPT_DIS_SPRITE_LIM) || s >= 32) |
| 470 | break; |
| 471 | } |
| 472 | x = sat[MEM_LE2(4*i+1)] + xoff; |
| 473 | if (sat[MEM_LE2(4*i+3)] & 0x80) |
| 474 | x -= 32; |
| 475 | |
| 476 | sprites_c[s] = sat[MEM_LE2(4*i+3)] & 0x0f; |
| 477 | sprites_x[s] = x; |
| 478 | sprites_addr[s] = sprite_base + ((sat[MEM_LE2(4*i + 2)] & addr_mask) << 3) + |
| 479 | ((scanline - y) >> zoomed); |
| 480 | if (pv->reg[1] & 0x40) { |
| 481 | // collision detection. Do it here since off-screen lines aren't drawn |
| 482 | if (sprites_c[s] && x > 0) { |
| 483 | pack = PicoMem.vramb[MEM_LE2(sprites_addr[s])]; |
| 484 | if (!m) m = CollisionDetect(sprites_map, x, pack, zoomed); |
| 485 | } |
| 486 | x += (zoomed ? 16:8); |
| 487 | if (sprites_c[s] && (sprites_zoom & 0x2) && x > 0 && x < 8+256) { |
| 488 | pack = PicoMem.vramb[MEM_LE2(sprites_addr[s]+0x10)]; |
| 489 | if (!m) m = CollisionDetect(sprites_map, x, pack, zoomed); |
| 490 | } |
| 491 | } |
| 492 | s++; |
| 493 | } |
| 494 | if (m) |
| 495 | sprites_status |= SR_C; |
| 496 | sprites = s; |
| 497 | } |
| 498 | |
| 499 | /* Draw sprites into a scanline, max 4 */ |
| 500 | static void DrawSpritesTMS(void) |
| 501 | { |
| 502 | unsigned int pack; |
| 503 | int zoomed = sprites_zoom & 0x1; // zoomed sprites |
| 504 | int s = sprites; |
| 505 | |
| 506 | // now draw all sprites backwards |
| 507 | for (--s; s >= 0; s--) { |
| 508 | int x, c, w = (zoomed ? 16: 8); |
| 509 | x = sprites_x[s]; |
| 510 | c = sprites_c[s]; |
| 511 | // c may be 0 (transparent): sprite invisible |
| 512 | if (c && x > 0) { |
| 513 | pack = PicoMem.vramb[MEM_LE2(sprites_addr[s])]; |
| 514 | if (zoomed) TileDoubleSprTMS(x, pack, c); |
| 515 | else TileNormSprTMS(x, pack, c); |
| 516 | } |
| 517 | if (c && (sprites_zoom & 0x2) && (x+=w) > 0 && x < 8+256) { |
| 518 | pack = PicoMem.vramb[MEM_LE2(sprites_addr[s]+0x10)]; |
| 519 | if (zoomed) TileDoubleSprTMS(x, pack, c); |
| 520 | else TileNormSprTMS(x, pack, c); |
| 521 | } |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | |
| 526 | /* Mode 1 - Text */ |
| 527 | /*===============*/ |
| 528 | |
| 529 | /* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */ |
| 530 | static void DrawStripM1(const u8 *nametab, const u8 *pattab, int cells_dx, int tilex_ty) |
| 531 | { |
| 532 | // Draw tiles across screen: |
| 533 | for (; cells_dx >= 0; cells_dx += 6, tilex_ty++, cells_dx -= 0x10000) |
| 534 | { |
| 535 | unsigned int pack, pal; |
| 536 | unsigned code; |
| 537 | |
| 538 | code = nametab[tilex_ty & 0x3f]; |
| 539 | pal = Pico.video.reg[7]; |
| 540 | pack = pattab[code << 3]; |
| 541 | TileNormBgM1(cells_dx, pack, pal); |
| 542 | } |
| 543 | } |
| 544 | |
| 545 | /* Draw a scanline */ |
| 546 | static void DrawDisplayM1(int scanline) |
| 547 | { |
| 548 | struct PicoVideo *pv = &Pico.video; |
| 549 | u8 *nametab, *pattab; |
| 550 | int tilex, dx, cells; |
| 551 | int cellskip = 0; // XXX |
| 552 | int maxcells = 40; |
| 553 | unsigned mask = pv->reg[0] & 0x2 ? 0x2000 : 0x3800; // M3: 2 bits table select |
| 554 | |
| 555 | // name, color, pattern table: |
| 556 | nametab = PicoMem.vramb + ((pv->reg[2]<<10) & 0x3c00); |
| 557 | pattab = PicoMem.vramb + ((pv->reg[4]<<11) & mask); |
| 558 | pattab += ((scanline>>6) << 11) & ~mask; // table select bits for M3 |
| 559 | |
| 560 | nametab += ((scanline>>3) * maxcells); |
| 561 | pattab += (scanline & 0x7); |
| 562 | |
| 563 | tilex = cellskip & 0x1f; |
| 564 | cells = maxcells - cellskip - 1; |
| 565 | dx = 8 + (cellskip << 3) + line_offset; |
| 566 | |
| 567 | // tiles |
| 568 | if (!(pv->debug_p & PVD_KILL_B)) |
| 569 | DrawStripM1(nametab, pattab, dx | (cells << 16), tilex | (scanline << 16)); |
| 570 | } |
| 571 | |
| 572 | |
| 573 | /* Mode 2 - Multicolor */ |
| 574 | /*=====================*/ |
| 575 | |
| 576 | /* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */ |
| 577 | static void DrawStripM2(const u8 *nametab, const u8 *pattab, int cells_dx, int tilex_ty) |
| 578 | { |
| 579 | // Draw tiles across screen: |
| 580 | for (; cells_dx >= 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000) |
| 581 | { |
| 582 | unsigned int pal; |
| 583 | unsigned code; |
| 584 | |
| 585 | code = nametab[tilex_ty & 0x1f]; |
| 586 | pal = pattab[code << 3]; |
| 587 | TileNormBgM2(cells_dx, pal); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | /* Draw a scanline */ |
| 592 | static void DrawDisplayM2(int scanline) |
| 593 | { |
| 594 | struct PicoVideo *pv = &Pico.video; |
| 595 | u8 *nametab, *pattab; |
| 596 | int tilex, dx, cells; |
| 597 | int cellskip = 0; // XXX |
| 598 | int maxcells = 32; |
| 599 | unsigned mask = pv->reg[0] & 0x2 ? 0x2000 : 0x3800; // M3: 2 bits table select |
| 600 | |
| 601 | // name, color, pattern table: |
| 602 | nametab = PicoMem.vramb + ((pv->reg[2]<<10) & 0x3c00); |
| 603 | pattab = PicoMem.vramb + ((pv->reg[4]<<11) & mask); |
| 604 | pattab += ((scanline>>6) << 11) & ~mask; // table select bits for M3 |
| 605 | |
| 606 | nametab += (scanline>>3) << 5; |
| 607 | pattab += (scanline>>2) & 0x7; |
| 608 | |
| 609 | tilex = cellskip & 0x1f; |
| 610 | cells = maxcells - cellskip - 1; |
| 611 | dx = (cellskip << 3) + line_offset; |
| 612 | |
| 613 | // tiles |
| 614 | if (!(pv->debug_p & PVD_KILL_B)) |
| 615 | DrawStripM2(nametab, pattab, dx | (cells << 16), tilex | (scanline << 16)); |
| 616 | |
| 617 | // sprites |
| 618 | if (!(pv->debug_p & PVD_KILL_S_LO)) |
| 619 | DrawSpritesTMS(); |
| 620 | } |
| 621 | |
| 622 | |
| 623 | /* Mode 3 - Graphics II */ |
| 624 | /*======================*/ |
| 625 | |
| 626 | /* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */ |
| 627 | static void DrawStripM3(const u8 *nametab, const u8 *coltab, const u8 *pattab, int cells_dx, int tilex_ty) |
| 628 | { |
| 629 | // Draw tiles across screen: |
| 630 | for (; cells_dx >= 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000) |
| 631 | { |
| 632 | unsigned int pack, pal; |
| 633 | unsigned code; |
| 634 | |
| 635 | code = nametab[tilex_ty & 0x1f] << 3; |
| 636 | pal = coltab[code]; |
| 637 | pack = pattab[code]; |
| 638 | TileNormBgMg(cells_dx, pack, pal); |
| 639 | } |
| 640 | } |
| 641 | |
| 642 | /* Draw a scanline */ |
| 643 | static void DrawDisplayM3(int scanline) |
| 644 | { |
| 645 | struct PicoVideo *pv = &Pico.video; |
| 646 | u8 *nametab, *coltab, *pattab; |
| 647 | int tilex, dx, cells; |
| 648 | int cellskip = 0; // XXX |
| 649 | int maxcells = 32; |
| 650 | |
| 651 | // name, color, pattern table: |
| 652 | nametab = PicoMem.vramb + ((pv->reg[2]<<10) & 0x3c00); |
| 653 | coltab = PicoMem.vramb + ((pv->reg[3]<< 6) & 0x2000); |
| 654 | pattab = PicoMem.vramb + ((pv->reg[4]<<11) & 0x2000); |
| 655 | |
| 656 | nametab += ((scanline>>3) << 5); |
| 657 | coltab += ((scanline>>6) <<11) + (scanline & 0x7); |
| 658 | pattab += ((scanline>>6) <<11) + (scanline & 0x7); |
| 659 | |
| 660 | tilex = cellskip & 0x1f; |
| 661 | cells = maxcells - cellskip - 1; |
| 662 | dx = (cellskip << 3) + line_offset; |
| 663 | |
| 664 | // tiles |
| 665 | if (!(pv->debug_p & PVD_KILL_B)) |
| 666 | DrawStripM3(nametab, coltab, pattab, dx | (cells << 16), tilex | (scanline << 16)); |
| 667 | |
| 668 | // sprites |
| 669 | if (!(pv->debug_p & PVD_KILL_S_LO)) |
| 670 | DrawSpritesTMS(); |
| 671 | } |
| 672 | |
| 673 | |
| 674 | /* Mode 0 - Graphics I */ |
| 675 | /*=====================*/ |
| 676 | |
| 677 | /* Draw the background into a scanline; cells, dx, tilex, ty merged to reduce registers */ |
| 678 | static void DrawStripM0(const u8 *nametab, const u8 *coltab, const u8 *pattab, int cells_dx, int tilex_ty) |
| 679 | { |
| 680 | // Draw tiles across screen: |
| 681 | for (; cells_dx >= 0; cells_dx += 8, tilex_ty++, cells_dx -= 0x10000) |
| 682 | { |
| 683 | unsigned int pack, pal; |
| 684 | unsigned code; |
| 685 | |
| 686 | code = nametab[tilex_ty & 0x1f]; |
| 687 | pal = coltab[code >> 3]; |
| 688 | pack = pattab[code << 3]; |
| 689 | TileNormBgMg(cells_dx, pack, pal); |
| 690 | } |
| 691 | } |
| 692 | |
| 693 | /* Draw a scanline */ |
| 694 | static void DrawDisplayM0(int scanline) |
| 695 | { |
| 696 | struct PicoVideo *pv = &Pico.video; |
| 697 | u8 *nametab, *coltab, *pattab; |
| 698 | int tilex, dx, cells; |
| 699 | int cellskip = 0; // XXX |
| 700 | int maxcells = 32; |
| 701 | |
| 702 | // name, color, pattern table: |
| 703 | nametab = PicoMem.vramb + ((pv->reg[2]<<10) & 0x3c00); |
| 704 | coltab = PicoMem.vramb + ((pv->reg[3]<< 6) & 0x3fc0); |
| 705 | pattab = PicoMem.vramb + ((pv->reg[4]<<11) & 0x3800); |
| 706 | |
| 707 | nametab += (scanline>>3) << 5; |
| 708 | pattab += (scanline & 0x7); |
| 709 | |
| 710 | tilex = cellskip & 0x1f; |
| 711 | cells = maxcells - cellskip - 1; |
| 712 | dx = (cellskip << 3) + line_offset; |
| 713 | |
| 714 | // tiles |
| 715 | if (!(pv->debug_p & PVD_KILL_B)) |
| 716 | DrawStripM0(nametab, coltab, pattab, dx | (cells << 16), tilex | (scanline << 16)); |
| 717 | |
| 718 | // sprites |
| 719 | if (!(pv->debug_p & PVD_KILL_S_LO)) |
| 720 | DrawSpritesTMS(); |
| 721 | } |
| 722 | |
| 723 | |
| 724 | /* Common/global */ |
| 725 | /*===============*/ |
| 726 | |
| 727 | static void FinalizeLineRGB555SMS(int line); |
| 728 | static void FinalizeLine8bitSMS(int line); |
| 729 | |
| 730 | void PicoFrameStartSMS(void) |
| 731 | { |
| 732 | struct PicoEState *est = &Pico.est; |
| 733 | int lines = 192, columns = 256, loffs, coffs; |
| 734 | |
| 735 | skip_next_line = 0; |
| 736 | loffs = screen_offset = 24; // 192 lines is really 224 with top/bottom bars |
| 737 | est->rendstatus = PDRAW_32_COLS; |
| 738 | |
| 739 | // if mode changes make palette dirty since some modes switch to a fixed one |
| 740 | if (mode != ((Pico.video.reg[0]&0x06) | (Pico.video.reg[1]&0x18))) { |
| 741 | mode = (Pico.video.reg[0]&0x06) | (Pico.video.reg[1]&0x18); |
| 742 | Pico.m.dirtyPal = 1; |
| 743 | } |
| 744 | |
| 745 | switch (mode) { |
| 746 | // SMS2 only 224/240 line modes, e.g. Micro Machines |
| 747 | case 0x06|0x08: |
| 748 | est->rendstatus |= PDRAW_30_ROWS; |
| 749 | loffs = screen_offset = 0; |
| 750 | lines = 240; |
| 751 | break; |
| 752 | case 0x06|0x10: |
| 753 | loffs = screen_offset = 8; |
| 754 | lines = 224; |
| 755 | break; |
| 756 | } |
| 757 | |
| 758 | Pico.m.hardware &= ~PMS_HW_TMS; |
| 759 | if (PicoIn.tmsPalette || (PicoIn.AHW & (PAHW_SG|PAHW_SC))) |
| 760 | Pico.m.hardware |= PMS_HW_TMS; |
| 761 | |
| 762 | // Copy LCD enable flag for easier handling |
| 763 | Pico.m.hardware &= ~PMS_HW_LCD; |
| 764 | if ((PicoIn.opt & POPT_EN_GG_LCD) && (PicoIn.AHW & PAHW_GG)) { |
| 765 | Pico.m.hardware |= PMS_HW_LCD; |
| 766 | |
| 767 | // GG LCD always has 160x144 regardless of settings |
| 768 | loffs = 48; |
| 769 | lines = 144; |
| 770 | columns = 160; |
| 771 | } else { |
| 772 | if ((mode & 4) && (Pico.video.reg[0] & 0x20)) { |
| 773 | // SMS mode 4 with 1st column blanked |
| 774 | est->rendstatus |= PDRAW_SMS_BLANK_1; |
| 775 | columns = 248; |
| 776 | } |
| 777 | } |
| 778 | |
| 779 | line_offset = 8; // FinalizeLine requires HighCol+8 |
| 780 | // ugh... nonetheless has offset in 8-bit fast mode if 1st col blanked! |
| 781 | coffs = (FinalizeLineSMS == NULL && columns == 248 ? 8 : 0); |
| 782 | if (FinalizeLineSMS != NULL && (PicoIn.opt & POPT_EN_SOFTSCALE)) { |
| 783 | // softscaling always generates 320px, but no scaling in 8bit fast |
| 784 | est->rendstatus |= PDRAW_SOFTSCALE; |
| 785 | coffs = 0; |
| 786 | columns = 320; |
| 787 | } else if (!(PicoIn.opt & POPT_DIS_32C_BORDER)) { |
| 788 | est->rendstatus |= PDRAW_BORDER_32; |
| 789 | line_offset -= coffs; |
| 790 | coffs = (320-columns) / 2; |
| 791 | if (FinalizeLineSMS == NULL) |
| 792 | line_offset += coffs; // ... else centering done in FinalizeLine |
| 793 | } |
| 794 | |
| 795 | if (est->rendstatus != rendstatus_old || lines != rendlines) { |
| 796 | // mode_change() might reset rendstatus_old by calling SetOutFormat |
| 797 | int rendstatus = est->rendstatus; |
| 798 | emu_video_mode_change(loffs, lines, coffs, columns); |
| 799 | rendstatus_old = rendstatus; |
| 800 | rendlines = lines; |
| 801 | sprites = 0; |
| 802 | } |
| 803 | |
| 804 | est->HighCol = HighColBase + screen_offset * HighColIncrement; |
| 805 | est->DrawLineDest = (char *)DrawLineDestBase + screen_offset * DrawLineDestIncrement; |
| 806 | |
| 807 | if (FinalizeLineSMS == FinalizeLine8bitSMS) { |
| 808 | Pico.m.dirtyPal = (Pico.m.dirtyPal || est->SonicPalCount ? 2 : 0); |
| 809 | memcpy(est->SonicPal, PicoMem.cram, 0x40*2); |
| 810 | } |
| 811 | est->SonicPalCount = 0; |
| 812 | } |
| 813 | |
| 814 | void PicoParseSATSMS(int line) |
| 815 | { |
| 816 | if (Pico.video.reg[0] & 0x04) ParseSpritesM4(line); |
| 817 | else ParseSpritesTMS(line); |
| 818 | } |
| 819 | |
| 820 | void PicoLineSMS(int line) |
| 821 | { |
| 822 | int skip = skip_next_line; |
| 823 | unsigned bgcolor; |
| 824 | int first = 48 - screen_offset; |
| 825 | |
| 826 | // GG LCD, render only visible part of screen |
| 827 | if ((Pico.m.hardware & PMS_HW_LCD) && (line < first || line >= first+144)) |
| 828 | goto norender; |
| 829 | |
| 830 | if (PicoScanBegin != NULL && skip == 0) |
| 831 | skip = PicoScanBegin(line + screen_offset); |
| 832 | |
| 833 | if (skip) { |
| 834 | skip_next_line = skip - 1; |
| 835 | return; |
| 836 | } |
| 837 | |
| 838 | // Draw screen: |
| 839 | bgcolor = (Pico.video.reg[7] & 0x0f) | ((Pico.video.reg[0] & 0x04) << 2); |
| 840 | BackFill(bgcolor, 0, &Pico.est); // bgcolor is from 2nd palette in mode 4 |
| 841 | if (Pico.video.reg[1] & 0x40) { |
| 842 | if (Pico.video.reg[0] & 0x04) DrawDisplayM4(line); // also M4+M3 |
| 843 | else if (Pico.video.reg[1] & 0x08) DrawDisplayM2(line); // also M2+M3 |
| 844 | else if (Pico.video.reg[1] & 0x10) DrawDisplayM1(line); // also M1+M3 |
| 845 | else if (Pico.video.reg[0] & 0x02) DrawDisplayM3(line); |
| 846 | else DrawDisplayM0(line); |
| 847 | } |
| 848 | |
| 849 | // latch current register values (may be overwritten by VDP reg writes later) |
| 850 | sprites_zoom = (Pico.video.reg[1] & 0x3) | (Pico.video.reg[0] & 0x8); |
| 851 | xscroll = Pico.video.reg[8]; |
| 852 | |
| 853 | if (FinalizeLineSMS != NULL) |
| 854 | FinalizeLineSMS(line); |
| 855 | |
| 856 | if (PicoScanEnd != NULL) |
| 857 | skip_next_line = PicoScanEnd(line + screen_offset); |
| 858 | |
| 859 | norender: |
| 860 | Pico.est.HighCol += HighColIncrement; |
| 861 | Pico.est.DrawLineDest = (char *)Pico.est.DrawLineDest + DrawLineDestIncrement; |
| 862 | } |
| 863 | |
| 864 | /* Palette for TMS9918 mode, see https://www.smspower.org/Development/Palette */ |
| 865 | // RGB values: #000000 #000000 #21c842 #5edc78 #5455ed #7d76fc #d4524d #42ebf5 |
| 866 | // #fc5554 #ff7978 #d4c154 #e6ce80 #21b03b #c95bba #cccccc #ffffff |
| 867 | static u16 tmspal[] = { |
| 868 | // SMS palette |
| 869 | 0x0000, 0x0000, 0x00a0, 0x00f0, 0x0a00, 0x0f00, 0x0005, 0x0ff0, |
| 870 | 0x000a, 0x000f, 0x00aa, 0x00ff, 0x0050, 0x0f0f, 0x0aaa, 0x0fff, |
| 871 | // TMS palette |
| 872 | 0x0000, 0x0000, 0x04c2, 0x07d6, 0x0e55, 0x0f77, 0x055c, 0x0ee4, |
| 873 | 0x055f, 0x077f, 0x05bc, 0x08ce, 0x03a2, 0x0b5c, 0x0ccc, 0x0fff, |
| 874 | // SMS palette, closer to the TMS one |
| 875 | 0x0000, 0x0000, 0x05f0, 0x05f5, 0x0a50, 0x0f55, 0x055a, 0x0ff0, |
| 876 | 0x055f, 0x0aaf, 0x05aa, 0x05af, 0x00a0, 0x0f5f, 0x0aaa, 0x0fff, |
| 877 | }; |
| 878 | |
| 879 | void PicoDoHighPal555SMS(void) |
| 880 | { |
| 881 | u32 *spal = (void *)Pico.est.SonicPal; |
| 882 | u32 *dpal = (void *)Pico.est.HighPal; |
| 883 | unsigned int cnt = Pico.est.SonicPalCount+1; |
| 884 | unsigned int t; |
| 885 | int i, j; |
| 886 | |
| 887 | if (FinalizeLineSMS == FinalizeLineRGB555SMS || Pico.m.dirtyPal == 2) |
| 888 | Pico.m.dirtyPal = 0; |
| 889 | |
| 890 | // use hardware palette if not in 8bit accurate mode |
| 891 | if (FinalizeLineSMS != FinalizeLine8bitSMS) |
| 892 | spal = (void *)PicoMem.cram; |
| 893 | |
| 894 | /* SMS 6 bit cram data was already converted to MD/GG format by vdp write, |
| 895 | * hence GG/SMS/TMS can all be handled the same here */ |
| 896 | for (j = cnt; j > 0; j--) { |
| 897 | if (!(Pico.video.reg[0] & 0x4)) // fixed palette in TMS modes |
| 898 | spal = (u32 *)tmspal + (Pico.m.hardware & PMS_HW_TMS ? 16/2:0); |
| 899 | for (i = 0x20/2; i > 0; i--, spal++, dpal++) { |
| 900 | t = *spal; |
| 901 | #if defined(USE_BGR555) |
| 902 | t = ((t & 0x000f000f)<<1) | ((t & 0x00f000f0)<<2) | ((t & 0x0f000f00)<<3); |
| 903 | t |= (t >> 4) & 0x04210421; |
| 904 | #elif defined(USE_BGR565) |
| 905 | t = ((t & 0x000f000f)<<1) | ((t & 0x00f000f0)<<3) | ((t & 0x0f000f00)<<4); |
| 906 | t |= (t >> 4) & 0x08610861; |
| 907 | #else |
| 908 | t = ((t & 0x000f000f)<<12)| ((t & 0x00f000f0)<<3) | ((t & 0x0f000f00)>>7); |
| 909 | t |= (t >> 4) & 0x08610861; |
| 910 | #endif |
| 911 | *dpal = t; |
| 912 | } |
| 913 | memcpy(dpal, dpal-0x20/2, 0x20*2); // for prio bit |
| 914 | spal += 0x20/2, dpal += 0x20/2; |
| 915 | } |
| 916 | Pico.est.HighPal[0xe0] = 0; |
| 917 | } |
| 918 | |
| 919 | static void FinalizeLineRGB555SMS(int line) |
| 920 | { |
| 921 | if (Pico.m.dirtyPal) |
| 922 | PicoDoHighPal555SMS(); |
| 923 | |
| 924 | // standard FinalizeLine can finish it for us, |
| 925 | // with features like scaling and such |
| 926 | FinalizeLine555(0, line, &Pico.est); |
| 927 | } |
| 928 | |
| 929 | static void FinalizeLine8bitSMS(int line) |
| 930 | { |
| 931 | FinalizeLine8bit(0, line, &Pico.est); |
| 932 | } |
| 933 | |
| 934 | void PicoDrawSetOutputSMS(pdso_t which) |
| 935 | { |
| 936 | switch (which) |
| 937 | { |
| 938 | case PDF_8BIT: FinalizeLineSMS = FinalizeLine8bitSMS; break; |
| 939 | case PDF_RGB555: FinalizeLineSMS = FinalizeLineRGB555SMS; break; |
| 940 | default: FinalizeLineSMS = NULL; // no multiple palettes, no scaling |
| 941 | PicoDrawSetInternalBuf(Pico.est.Draw2FB, 328); break; |
| 942 | } |
| 943 | rendstatus_old = -1; |
| 944 | mode = -1; |
| 945 | } |
| 946 | |
| 947 | // vim:shiftwidth=2:ts=2:expandtab |