afe683100e96d743676a377fc3bcbb8741eea5f3
[picodrive.git] / pico / mode4.c
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