50338827bf102bcdcabcd083549688f55a7f0cf0
[picodrive.git] / pico / cd / gfx.c
1 /***************************************************************************************
2  *  Genesis Plus
3  *  CD graphics processor
4  *
5  *  Copyright (C) 2012  Eke-Eke (Genesis Plus GX)
6  *
7  *  Redistribution and use of this code or any derivative works are permitted
8  *  provided that the following conditions are met:
9  *
10  *   - Redistributions may not be sold, nor may they be used in a commercial
11  *     product or activity.
12  *
13  *   - Redistributions that are modified from the original source must include the
14  *     complete source code, including the source code for all components used by a
15  *     binary built from the modified sources. However, as a special exception, the
16  *     source code distributed need not include anything that is normally distributed
17  *     (in either source or binary form) with the major components (compiler, kernel,
18  *     and so on) of the operating system on which the executable runs, unless that
19  *     component itself accompanies the executable.
20  *
21  *   - Redistributions must reproduce the above copyright notice, this list of
22  *     conditions and the following disclaimer in the documentation and/or other
23  *     materials provided with the distribution.
24  *
25  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
26  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
29  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35  *  POSSIBILITY OF SUCH DAMAGE.
36  *
37  ****************************************************************************************/
38 #include "../pico_int.h"
39 #include "genplus_macros.h"
40
41 typedef struct
42 {
43   //uint32 cycles;                    /* current cycles count for graphics operation */
44   //uint32 cyclesPerLine;             /* current graphics operation timings */
45   uint32 dotMask;                   /* stamp map size mask */
46   uint32 stampMask;                 /* stamp number mask */
47   uint16 *tracePtr;                 /* trace vector pointer */
48   uint16 *mapPtr;                   /* stamp map table base address */
49   uint8 stampShift;                 /* stamp pixel shift value (related to stamp size) */
50   uint8 mapShift;                   /* stamp map table shift value (related to stamp map size) */
51   uint16 bufferOffset;              /* image buffer column offset */
52   uint32 bufferStart;               /* image buffer start index */
53   uint32 y_step;                    /* pico: render line step */
54   uint8 lut_prio[4][0x10][0x10];    /* WORD-RAM data writes priority lookup table */
55   uint8 lut_pixel[0x200];           /* Graphics operation dot offset lookup table */
56   uint16 lut_cell2[0x80];           /* Graphics operation stamp offset lookup table */
57   uint16 lut_cell4[0x80];           /* Graphics operation stamp offset lookup table */
58 } gfx_t;
59
60 static gfx_t gfx;
61
62 static void gfx_schedule(void);
63
64 /***************************************************************/
65 /*      Rotation / Scaling operation (2M Mode)                 */
66 /***************************************************************/
67
68 void gfx_init(void)
69 {
70   int i, j;
71   uint8 row, col, temp;
72
73   memset(&gfx, 0, sizeof(gfx));
74
75   /* Initialize priority modes lookup table */
76   for (i = 0; i < 0x10; i++)
77   {
78     for (j = 0; j < 0x10; j++)
79     {
80       /* normal */
81       gfx.lut_prio[0][i][j] = j;
82       /* underwrite */
83       gfx.lut_prio[1][i][j] = i ? i : j;
84       /* overwrite */
85       gfx.lut_prio[2][i][j] = j ? j : i;
86       /* invalid */
87       gfx.lut_prio[3][i][j] = i;
88     }
89   }
90
91   /* Initialize cell lookup table             */
92   /* table entry = yyxxhrr (7 bits)           */
93   /* with: yy = cell row (0-3)                */
94   /*       xx = cell column (0-3)             */
95   /*      hrr = HFLIP & ROTATION bits         */
96   for (i=0; i<0x80; i++)
97   {
98     /* one stamp = 2x2 cells (16x16) or 4x4 cells (32x32) */
99     row = (i >> 5) & 3;
100     col = (i >> 3) & 3;
101
102     if (i & 4) { col = col ^ 3; }  /* HFLIP (always first)  */ 
103     if (i & 2) { col = col ^ 3; row = row ^ 3; }  /* ROLL1 */
104     if (i & 1) { temp = col; col = row ^ 3; row = temp; }  /* ROLL0  */
105
106     /* cell offset (0-3 or 0-15) */
107     gfx.lut_cell2[i] = ((row&1) + (col&1) * 2) << 6;
108     gfx.lut_cell4[i] = ((row&3) + (col&3) * 4) << 6;
109   }
110
111   /* Initialize pixel lookup table      */
112   /* table entry = yyyxxxhrr (9 bits)   */
113   /* with:  yyy = pixel row  (0-7)      */
114   /*        xxx = pixel column (0-7)    */
115   /*        hrr = HFLIP & ROTATION bits */
116   for (i=0; i<0x200; i++)
117   {
118     /* one cell = 8x8 pixels */
119     row = (i >> 6) & 7;
120     col = (i >> 3) & 7;
121
122     if (i & 4) { col = col ^ 7; }   /* HFLIP (always first) */ 
123     if (i & 2) { col = col ^ 7; row = row ^ 7; }  /* ROLL1 */
124     if (i & 1) { temp = col; col = row ^ 7; row = temp; } /* ROLL0 */
125
126     /* pixel offset (0-63) */
127     gfx.lut_pixel[i] = col + row * 8;
128   }
129 }
130
131 int gfx_context_save(uint8 *state)
132 {
133   uint32 tmp32;
134   int bufferptr = 0;
135
136   //save_param(&gfx.cycles, sizeof(gfx.cycles));
137   //save_param(&gfx.cyclesPerLine, sizeof(gfx.cyclesPerLine));
138   save_param(&gfx.dotMask, sizeof(gfx.dotMask));
139   save_param(&gfx.stampShift, sizeof(gfx.stampShift));
140   save_param(&gfx.mapShift, sizeof(gfx.mapShift));
141   save_param(&gfx.bufferOffset, sizeof(gfx.bufferOffset));
142   save_param(&gfx.bufferStart, sizeof(gfx.bufferStart));
143
144   tmp32 = (uint8 *)(gfx.tracePtr) - Pico_mcd->word_ram2M;
145   save_param(&tmp32, 4);
146
147   tmp32 = (uint8 *)(gfx.mapPtr) - Pico_mcd->word_ram2M;
148   save_param(&tmp32, 4);
149
150   save_param(&gfx.y_step, sizeof(gfx.y_step));
151   save_param(&gfx.stampMask, sizeof(gfx.stampMask));
152
153   return bufferptr;
154 }
155
156 int gfx_context_load(const uint8 *state)
157 {
158   uint32 tmp32;
159   int bufferptr = 0;
160
161   //load_param(&gfx.cycles, sizeof(gfx.cycles));
162   //load_param(&gfx.cyclesPerLine, sizeof(gfx.cyclesPerLine));
163   load_param(&gfx.dotMask, sizeof(gfx.dotMask));
164   load_param(&gfx.stampShift, sizeof(gfx.stampShift));
165   load_param(&gfx.mapShift, sizeof(gfx.mapShift));
166   load_param(&gfx.bufferOffset, sizeof(gfx.bufferOffset));
167   load_param(&gfx.bufferStart, sizeof(gfx.bufferStart));
168
169   load_param(&tmp32, 4);
170   gfx.tracePtr = (uint16 *)(Pico_mcd->word_ram2M + tmp32);
171
172   load_param(&tmp32, 4);
173   gfx.mapPtr = (uint16 *)(Pico_mcd->word_ram2M + tmp32);
174
175   load_param(&gfx.y_step, sizeof(gfx.y_step));
176   load_param(&gfx.stampMask, sizeof(gfx.stampMask));
177
178   return bufferptr;
179 }
180
181 static inline int gfx_pixel(uint32 xpos, uint32 ypos, uint16 *lut_cell)
182 {
183   uint16 stamp_data;
184   uint32 stamp_index;
185   uint8 pixel_out = 0x00;
186
187   /* check if pixel is outside stamp map */
188   if (((xpos | ypos) & ~gfx.dotMask) == 0)
189   {
190     /* read stamp map table data */
191     stamp_data = gfx.mapPtr[(xpos >> gfx.stampShift) | ((ypos >> gfx.stampShift) << gfx.mapShift)];
192
193     /* stamp generator base index                                     */
194     /* sss ssssssss ccyyyxxx (16x16) or sss sssssscc ccyyyxxx (32x32) */
195     /* with:  s = stamp number (1 stamp = 16x16 or 32x32 pixels)      */
196     /*        c = cell offset  (0-3 for 16x16, 0-15 for 32x32)        */
197     /*      yyy = line offset  (0-7)                                  */
198     /*      xxx = pixel offset (0-7)                                  */
199     stamp_index = (stamp_data & gfx.stampMask) << 8;
200
201     if (stamp_index)
202     {
203       /* extract HFLIP & ROTATION bits */
204       stamp_data = (stamp_data >> 13) & 7;
205
206       /* cell offset (0-3 or 0-15)                             */
207       /* table entry = yyxxhrr (7 bits)                        */
208       /* with: yy = cell row  (0-3) = (ypos >> (11 + 3)) & 3   */
209       /*       xx = cell column (0-3) = (xpos >> (11 + 3)) & 3 */
210       /*      hrr = HFLIP & ROTATION bits                      */
211       stamp_index |= lut_cell[stamp_data | ((ypos >> 9) & 0x60) | ((xpos >> 11) & 0x18)];
212
213       /* pixel  offset (0-63)                              */
214       /* table entry = yyyxxxhrr (9 bits)                  */
215       /* with: yyy = pixel row  (0-7) = (ypos >> 11) & 7   */
216       /*       xxx = pixel column (0-7) = (xpos >> 11) & 7 */
217       /*       hrr = HFLIP & ROTATION bits                 */
218       stamp_index |= gfx.lut_pixel[stamp_data | ((ypos >> 5) & 0x1c0) | ((xpos >> 8) & 0x38)];
219
220       /* read pixel pair (2 pixels/byte) */
221       pixel_out = READ_BYTE(Pico_mcd->word_ram2M, stamp_index >> 1);
222
223       /* extract left or right pixel */
224       pixel_out >>= 4 * !(stamp_index & 1);
225       pixel_out &= 0x0f;
226     }
227   }
228
229   return pixel_out;
230 }
231
232 #define RENDER_LOOP(N, UPDP, COND1, COND2) do {                         \
233   if (bufferIndex & 1) {                                                \
234     bufferIndex ^= 1;                                                   \
235     goto right##N; /* no initial left pixel */                          \
236   }                                                                     \
237   /* process all dots */                                                \
238   while (width--)                                                       \
239   {                                                                     \
240     /* left pixel */                                                    \
241     xpos &= mask;                                                       \
242     ypos &= mask;                                                       \
243                                                                         \
244     if (COND1) {                                                        \
245       pixel_out = gfx_pixel(xpos, ypos, lut_cell);                      \
246       UPDP;                                                             \
247     }                                                                   \
248                                                                         \
249     if (COND2) {                                                        \
250       /* read out paired pixel data */                                  \
251       pixel_in = READ_BYTE(Pico_mcd->word_ram2M, bufferIndex >> 1);     \
252                                                                         \
253       /* priority mode write */                                         \
254       pixel_in = (lut_prio[(pixel_in & 0xf0) >> 4][pixel_out] << 4) |   \
255                  (pixel_in & 0x0f);                                     \
256                                                                         \
257       /* write data to image buffer */                                  \
258       WRITE_BYTE(Pico_mcd->word_ram2M, bufferIndex >> 1, pixel_in);     \
259     }                                                                   \
260                                                                         \
261     /* increment pixel position */                                      \
262     xpos += xoffset;                                                    \
263     ypos += yoffset;                                                    \
264                                                                         \
265 right##N:                                                               \
266     if (width-- == 0) break;                                            \
267                                                                         \
268     /* right pixel */                                                   \
269     xpos &= mask;                                                       \
270     ypos &= mask;                                                       \
271                                                                         \
272     if (COND1) {                                                        \
273       pixel_out = gfx_pixel(xpos, ypos, lut_cell);                      \
274       UPDP;                                                             \
275     }                                                                   \
276                                                                         \
277     if (COND2) {                                                        \
278       /* read out paired pixel data */                                  \
279       pixel_in = READ_BYTE(Pico_mcd->word_ram2M, bufferIndex >> 1);     \
280                                                                         \
281       /* priority mode write */                                         \
282       pixel_in = (lut_prio[pixel_in & 0x0f][pixel_out]) |               \
283                  (pixel_in & 0xf0);                                     \
284                                                                         \
285       /* write data to image buffer */                                  \
286       WRITE_BYTE(Pico_mcd->word_ram2M, bufferIndex >> 1, pixel_in);     \
287     }                                                                   \
288                                                                         \
289     /* increment pixel position */                                      \
290     xpos += xoffset;                                                    \
291     ypos += yoffset;                                                    \
292                                                                         \
293     /* next pixel */                                                    \
294     bufferIndex += 2;                                                   \
295     /* check current pixel position  */                                 \
296     if ((bufferIndex & 7) == 0)                                         \
297     {                                                                   \
298       /* next cell: increment buffer offset by one column (minus 8 pixels) */ \
299       bufferIndex += gfx.bufferOffset-1;                                \
300     }                                                                   \
301   }                                                                     \
302 } while (0)
303
304 static void gfx_render(uint32 bufferIndex, uint32 width)
305 {
306   uint8 pixel_in, pixel_out;
307   uint32 priority;
308   uint8 (*lut_prio)[0x10];
309   uint16 *lut_cell;
310   uint32 mask;
311
312   /* pixel map start position for current line (13.3 format converted to 13.11) */
313   uint32 xpos = *gfx.tracePtr++ << 8;
314   uint32 ypos = *gfx.tracePtr++ << 8;
315
316   /* pixel map offset values for current line (5.11 format) */
317   uint32 xoffset = (int16) *gfx.tracePtr++;
318   uint32 yoffset = (int16) *gfx.tracePtr++;
319
320   priority = (Pico_mcd->s68k_regs[2] << 8) | Pico_mcd->s68k_regs[3];
321   priority = (priority >> 3) & 0x03;
322   lut_prio = gfx.lut_prio[priority];
323
324   lut_cell = (Pico_mcd->s68k_regs[0x58+1] & 0x02) ? gfx.lut_cell4 : gfx.lut_cell2;
325
326   /* check if stamp map is repeated */
327   mask = 0xffffff; /* 24-bit range */
328   if (Pico_mcd->s68k_regs[0x58+1] & 0x01)
329   {
330     /* stamp map range */
331     mask = gfx.dotMask;
332   }
333
334   pixel_out = 0;
335   if (xoffset+(1U<<10) <= 1U<<11 && yoffset+(1U<<10) <= 1U<<11) {
336     /* upscaling >= 2x, test for duplicate pixels to avoid recalculation */
337     uint32 oldx, oldy;
338     oldx = oldy = ~xpos;
339     RENDER_LOOP(1, oldx = xpos;oldy = ypos, (oldx^xpos ^ oldy^ypos) >> 11, (!priority) | pixel_out);
340   } else {
341     RENDER_LOOP(3,                        , 1, (!priority) | pixel_out);
342   }
343 }
344
345 void gfx_start(uint32 base)
346 {
347   /* make sure 2M mode is enabled */
348   if (!(Pico_mcd->s68k_regs[3] & 0x04))
349   {
350     uint32 mask = 0;
351     uint32 reg;
352     
353     /* trace vector pointer */
354     gfx.tracePtr = (uint16 *)(Pico_mcd->word_ram2M + ((base << 2) & 0x3fff8));
355
356     /* stamps & stamp map size */
357     switch ((Pico_mcd->s68k_regs[0x58+1] >> 1) & 0x03)
358     {
359       case 0:
360         gfx.dotMask = 0x07ffff;   /* 256x256 dots/map  */
361         gfx.stampMask = 0x7ff;    /* 16x16 dots/stamp  */
362         gfx.stampShift = 11 + 4;  /* 16x16 dots/stamp  */
363         gfx.mapShift = 4;         /* 16x16 stamps/map  */
364         mask = 0x3fe00;           /* 512 bytes/table   */
365         break;
366
367       case 1:
368         gfx.dotMask = 0x07ffff;   /* 256x256 dots/map  */
369         gfx.stampMask = 0x7fc;    /* 16x16 dots/stamp  */
370         gfx.stampShift = 11 + 5;  /* 32x32 dots/stamp  */
371         gfx.mapShift = 3;         /* 8x8 stamps/map    */
372         mask = 0x3ff80;           /* 128 bytes/table   */
373         break;
374
375       case 2:
376         gfx.dotMask = 0x7fffff;   /* 4096*4096 dots/map */
377         gfx.stampMask = 0x7ff;    /* 16x16 dots/stamp   */
378         gfx.stampShift = 11 + 4;  /* 16x16 dots/stamp   */
379         gfx.mapShift = 8;         /* 256x256 stamps/map */
380         mask = 0x20000;           /* 131072 bytes/table */
381         break;
382
383       case 3:
384         gfx.dotMask = 0x7fffff;   /* 4096*4096 dots/map */
385         gfx.stampMask = 0x7fc;    /* 16x16 dots/stamp   */
386         gfx.stampShift = 11 + 5;  /* 32x32 dots/stamp   */
387         gfx.mapShift = 7;         /* 128x128 stamps/map */
388         mask = 0x38000;           /* 32768 bytes/table  */
389         break;
390     }
391
392     /* stamp map table base address */
393     reg = (Pico_mcd->s68k_regs[0x5a] << 8) | Pico_mcd->s68k_regs[0x5b];
394     gfx.mapPtr = (uint16 *)(Pico_mcd->word_ram2M + ((reg << 2) & mask));
395
396     /* image buffer column offset (64 pixels/cell, minus 7 pixels to restart at cell beginning) */
397     gfx.bufferOffset = (((Pico_mcd->s68k_regs[0x5c+1] & 0x1f) + 1) << 6) - 7;
398
399     /* image buffer start index in dot units (2 pixels/byte) */
400     reg = (Pico_mcd->s68k_regs[0x5e] << 8) | Pico_mcd->s68k_regs[0x5f];
401     gfx.bufferStart = (reg << 3) & 0x7ffc0;
402
403     /* add image buffer horizontal dot offset */
404     gfx.bufferStart += (Pico_mcd->s68k_regs[0x60+1] & 0x3f);
405
406     /* reset GFX chip cycle counter */
407     //gfx.cycles = cycles;
408
409     /* update GFX chip timings (see AC3:Thunderhawk / Thunderstrike) */
410     //gfx.cyclesPerLine = 4 * 5 * scd.regs[0x62>>1].w;
411
412     /* start graphics operation */
413     Pico_mcd->s68k_regs[0x58] = 0x80;
414     Pico_mcd->m.state_flags &= ~PCD_ST_S68K_POLL;
415     Pico_mcd->m.s68k_poll_cnt = 0;
416
417     gfx_schedule();
418   }
419 }
420
421 /* PicoDrive specific */
422 #define UPDATE_CYCLES 20000
423
424 static void gfx_schedule(void)
425 {
426   int w, h, cycles;
427   int y_step;
428
429   w = (Pico_mcd->s68k_regs[0x62] << 8) | Pico_mcd->s68k_regs[0x63];
430   h = (Pico_mcd->s68k_regs[0x64] << 8) | Pico_mcd->s68k_regs[0x65];
431
432   cycles = 5 * w * h;
433   y_step = h;
434   if (cycles > UPDATE_CYCLES)
435     y_step = (UPDATE_CYCLES + 5 * w - 1) / (5 * w);
436
437   gfx.y_step = y_step;
438   pcd_event_schedule_s68k(PCD_EVENT_GFX, 5 * w * y_step);
439 }
440
441 void gfx_update(unsigned int cycles)
442 {
443   int lines, lines_reg;
444   int w;
445
446   if (!(Pico_mcd->s68k_regs[0x58] & 0x80))
447     return;
448
449   w = (Pico_mcd->s68k_regs[0x62] << 8) | Pico_mcd->s68k_regs[0x63];
450   lines = (Pico_mcd->s68k_regs[0x64] << 8) | Pico_mcd->s68k_regs[0x65];
451   lines_reg = lines - gfx.y_step;
452
453   if (lines_reg <= 0) {
454     Pico_mcd->s68k_regs[0x58] = 0;
455     Pico_mcd->s68k_regs[0x64] =
456     Pico_mcd->s68k_regs[0x65] = 0;
457
458     Pico_mcd->m.state_flags &= ~PCD_ST_S68K_POLL;
459     Pico_mcd->m.s68k_poll_cnt = 0;
460     if (Pico_mcd->s68k_regs[0x33] & PCDS_IEN1) {
461       elprintf(EL_INTS|EL_CD, "s68k: gfx_cd irq 1");
462       pcd_irq_s68k(1, 1);
463     }
464   }
465   else {
466     Pico_mcd->s68k_regs[0x64] = lines_reg >> 8;
467     Pico_mcd->s68k_regs[0x65] = lines_reg;
468
469     if (lines > gfx.y_step)
470       lines = gfx.y_step;
471
472     pcd_event_schedule(cycles, PCD_EVENT_GFX, 5 * w * lines);
473   }
474
475   if (PicoIn.opt & POPT_EN_MCD_GFX)
476   {
477     /* render lines */
478     while (lines--)
479     {
480       /* process dots to image buffer */
481       gfx_render(gfx.bufferStart, w);
482
483       /* increment image buffer start index for next line (8 pixels/line) */
484       gfx.bufferStart += 8;
485     }
486   }
487 }
488
489 // vim:shiftwidth=2:ts=2:expandtab