a13c38b0d68d947726e274fd970be4551ab586e5
[picodrive.git] / pico / mode4.c
1 /*
2  * mode4/SMS renderer
3  * (C) notaz, 2009-2010
4  *
5  * This work is licensed under the terms of MAME license.
6  * See COPYING file in the top-level directory.
7  */
8 /*
9  * TODO:
10  * - TMS9918 modes?
11  * - gg mode?
12  * - column scroll (reg 0 bit7)
13  * - 224/240 line modes
14  * - doubled sprites
15  */
16 #include "pico_int.h"
17
18 static void (*FinalizeLineM4)(int line);
19 static int skip_next_line;
20 static int screen_offset;
21
22 #define PLANAR_PIXEL(x,p) \
23   t = pack & (0x80808080 >> p); \
24   if (t) { \
25     t = ((t >> (7-p)) | (t >> (14-p)) | (t >> (21-p)) | (t >> (28-p))) & 0x0f; \
26     pd[x] = pal|t; \
27   }
28
29 static int TileNormM4(int sx, int addr, int pal)
30 {
31   unsigned char *pd = Pico.est.HighCol + sx;
32   unsigned int pack, t;
33
34   pack = *(unsigned int *)(PicoMem.vram + addr); /* Get 4 bitplanes / 8 pixels */
35   if (pack)
36   {
37     PLANAR_PIXEL(0, 0)
38     PLANAR_PIXEL(1, 1)
39     PLANAR_PIXEL(2, 2)
40     PLANAR_PIXEL(3, 3)
41     PLANAR_PIXEL(4, 4)
42     PLANAR_PIXEL(5, 5)
43     PLANAR_PIXEL(6, 6)
44     PLANAR_PIXEL(7, 7)
45     return 0;
46   }
47
48   return 1; /* Tile blank */
49 }
50
51 static int TileFlipM4(int sx,int addr,int pal)
52 {
53   unsigned char *pd = Pico.est.HighCol + sx;
54   unsigned int pack, t;
55
56   pack = *(unsigned int *)(PicoMem.vram + addr); /* Get 4 bitplanes / 8 pixels */
57   if (pack)
58   {
59     PLANAR_PIXEL(0, 7)
60     PLANAR_PIXEL(1, 6)
61     PLANAR_PIXEL(2, 5)
62     PLANAR_PIXEL(3, 4)
63     PLANAR_PIXEL(4, 3)
64     PLANAR_PIXEL(5, 2)
65     PLANAR_PIXEL(6, 1)
66     PLANAR_PIXEL(7, 0)
67     return 0;
68   }
69
70   return 1; /* Tile blank */
71 }
72
73 static void draw_sprites(int scanline)
74 {
75   struct PicoVideo *pv = &Pico.video;
76   unsigned int sprites_addr[8];
77   unsigned int sprites_x[8];
78   unsigned char *sat;
79   int xoff = 8; // relative to HighCol, which is (screen - 8)
80   int sprite_base, addr_mask;
81   int i, s, h;
82
83   if (pv->reg[0] & 8)
84     xoff = 0;
85
86   sat = (unsigned char *)PicoMem.vram + ((pv->reg[5] & 0x7e) << 7);
87   if (pv->reg[1] & 2) {
88     addr_mask = 0xfe; h = 16;
89   } else {
90     addr_mask = 0xff; h = 8;
91   }
92   sprite_base = (pv->reg[6] & 4) << (13-2-1);
93
94   for (i = s = 0; i < 64; i++)
95   {
96     int y;
97     y = sat[i] + 1;
98     if (y == 0xd1)
99       break;
100     if (y + h <= scanline || scanline < y)
101       continue; // not on this line
102     if (s >= 8) {
103       pv->status |= SR_SOVR;
104       break;
105     }
106
107     sprites_x[s] = xoff + sat[0x80 + i*2];
108     sprites_addr[s] = sprite_base + ((sat[0x80 + i*2 + 1] & addr_mask) << (5-1)) +
109       ((scanline - y) << (2-1));
110     s++;
111   }
112
113   // really half-assed but better than nothing
114   if (s > 1)
115     pv->status |= SR_C;
116
117   // now draw all sprites backwards
118   for (--s; s >= 0; s--)
119     TileNormM4(sprites_x[s], sprites_addr[s], 0x10);
120 }
121
122 // tilex_ty_prio merged to reduce register pressure
123 static void draw_strip(const unsigned short *nametab, int dx, int cells, int tilex_ty_prio)
124 {
125   int oldcode = -1, blank = -1; // The tile we know is blank
126   int addr = 0, pal = 0;
127
128   // Draw tiles across screen:
129   for (; cells > 0; dx += 8, tilex_ty_prio++, cells--)
130   {
131     int code, zero;
132
133     code = nametab[tilex_ty_prio & 0x1f];
134     if (code == blank)
135       continue;
136     if ((code ^ tilex_ty_prio) & 0x1000) // priority differs?
137       continue;
138
139     if (code != oldcode) {
140       oldcode = code;
141       // Get tile address/2:
142       addr = (code & 0x1ff) << 4;
143       addr += tilex_ty_prio >> 16;
144       if (code & 0x0400)
145         addr ^= 0xe; // Y-flip
146
147       pal = (code>>7) & 0x10;
148     }
149
150     if (code&0x0200) zero = TileFlipM4(dx, addr, pal);
151     else             zero = TileNormM4(dx, addr, pal);
152
153     if (zero)
154       blank = code; // We know this tile is blank now
155   }
156 }
157
158 static void DrawDisplayM4(int scanline)
159 {
160   struct PicoVideo *pv = &Pico.video;
161   unsigned short *nametab;
162   int line, tilex, dx, ty, cells;
163   int cellskip = 0; // XXX
164   int maxcells = 32;
165
166   // Find the line in the name table
167   line = pv->reg[9] + scanline; // vscroll + scanline
168   if (line >= 224)
169     line -= 224;
170
171   // Find name table:
172   nametab = PicoMem.vram;
173   nametab += (pv->reg[2] & 0x0e) << (10-1);
174   nametab += (line>>3) << (6-1);
175
176   dx = pv->reg[8]; // hscroll
177   if (scanline < 16 && (pv->reg[0] & 0x40))
178     dx = 0; // hscroll disabled for top 2 rows
179
180   tilex = ((-dx >> 3) + cellskip) & 0x1f;
181   ty = (line & 7) << 1; // Y-Offset into tile
182   cells = maxcells - cellskip;
183
184   dx = ((dx - 1) & 7) + 1;
185   if (dx != 8)
186     cells++; // have hscroll, need to draw 1 cell more
187   dx += cellskip << 3;
188
189   // low priority tiles
190   if (!(pv->debug_p & PVD_KILL_B))
191     draw_strip(nametab, dx, cells, tilex | 0x0000 | (ty << 16));
192
193   // sprites
194   if (!(pv->debug_p & PVD_KILL_S_LO))
195     draw_sprites(scanline);
196
197   // high priority tiles (use virtual layer switch just for fun)
198   if (!(pv->debug_p & PVD_KILL_A))
199     draw_strip(nametab, dx, cells, tilex | 0x1000 | (ty << 16));
200
201   if (pv->reg[0] & 0x20)
202     // first column masked
203     ((int *)Pico.est.HighCol)[2] = ((int *)Pico.est.HighCol)[3] = 0xe0e0e0e0;
204 }
205
206 void PicoFrameStartMode4(void)
207 {
208   int lines = 192;
209   skip_next_line = 0;
210   screen_offset = 24;
211   Pico.est.rendstatus = PDRAW_32_COLS;
212
213   if ((Pico.video.reg[0] & 6) == 6 && (Pico.video.reg[1] & 0x18)) {
214     if (Pico.video.reg[1] & 0x08) {
215       screen_offset = 0;
216       lines = 240;
217     }
218     else {
219       screen_offset = 8;
220       lines = 224;
221     }
222   }
223
224   if (Pico.est.rendstatus != rendstatus_old || lines != rendlines) {
225     emu_video_mode_change(screen_offset, lines, 1);
226     rendstatus_old = Pico.est.rendstatus;
227     rendlines = lines;
228   }
229
230   Pico.est.DrawLineDest = (char *)DrawLineDestBase + screen_offset * DrawLineDestIncrement;
231 }
232
233 void PicoLineMode4(int line)
234 {
235   if (skip_next_line > 0) {
236     skip_next_line--;
237     return;
238   }
239
240   if (PicoScanBegin != NULL)
241     skip_next_line = PicoScanBegin(line + screen_offset);
242
243   // Draw screen:
244   BackFill(Pico.video.reg[7] & 0x0f, 0, &Pico.est);
245   if (Pico.video.reg[1] & 0x40)
246     DrawDisplayM4(line);
247
248   if (FinalizeLineM4 != NULL)
249     FinalizeLineM4(line);
250
251   if (PicoScanEnd != NULL)
252     skip_next_line = PicoScanEnd(line + screen_offset);
253
254   Pico.est.DrawLineDest = (char *)Pico.est.DrawLineDest + DrawLineDestIncrement;
255 }
256
257 void PicoDoHighPal555M4(void)
258 {
259   unsigned int *spal=(void *)PicoMem.cram;
260   unsigned int *dpal=(void *)Pico.est.HighPal;
261   unsigned int t;
262   int i;
263
264   Pico.m.dirtyPal = 0;
265
266   /* cram is always stored as shorts, even though real hardware probably uses bytes */
267   for (i = 0x20/2; i > 0; i--, spal++, dpal++) {
268     t = *spal;
269 #ifdef USE_BGR555
270     t = ((t & 0x00030003)<< 3) | ((t & 0x000c000c)<<7) | ((t & 0x00300030)<<10);
271 #else
272     t = ((t & 0x00030003)<<14) | ((t & 0x000c000c)<<7) | ((t & 0x00300030)>>1);
273 #endif
274     t |= t >> 2;
275     t |= (t >> 4) & 0x08610861;
276     *dpal = t;
277   }
278   Pico.est.HighPal[0xe0] = 0;
279 }
280
281 static void FinalizeLineRGB555M4(int line)
282 {
283   if (Pico.m.dirtyPal)
284     PicoDoHighPal555M4();
285
286   // standard FinalizeLine can finish it for us,
287   // with features like scaling and such
288   FinalizeLine555(0, line, &Pico.est);
289 }
290
291 static void FinalizeLine8bitM4(int line)
292 {
293   unsigned char *pd = Pico.est.DrawLineDest;
294
295   if (!(PicoOpt & POPT_DIS_32C_BORDER))
296     pd += 32;
297
298   memcpy(pd, Pico.est.HighCol + 8, 256);
299 }
300
301 void PicoDrawSetOutputMode4(pdso_t which)
302 {
303   switch (which)
304   {
305     case PDF_8BIT:   FinalizeLineM4 = FinalizeLine8bitM4; break;
306     case PDF_RGB555: FinalizeLineM4 = FinalizeLineRGB555M4; break;
307     default:         FinalizeLineM4 = NULL; break;
308   }
309 }
310
311 // vim:shiftwidth=2:ts=2:expandtab