core vdp, fix sprite rendering issues with Overdrive 1/2
authorkub <derkub@gmail.com>
Wed, 5 Apr 2023 18:00:37 +0000 (20:00 +0200)
committerkub <derkub@gmail.com>
Wed, 5 Apr 2023 18:00:37 +0000 (20:00 +0200)
pico/draw.c
pico/pico.c
pico/pico_cmn.c
pico/pico_int.h
pico/videoport.c

index 011a1a3..0e43fb9 100644 (file)
@@ -97,7 +97,9 @@ u32 VdpSATCache[2*128];  // VDP sprite cache (1st 32 sprite attr bits)
 #define SPRL_HAVE_MASK0  0x02 // have sprite with x == 0 in 1st slot\r
 #define SPRL_MASKED      0x01 // lo prio masking by sprite with x == 0 active\r
 \r
-unsigned char HighLnSpr[240][4+MAX_LINE_SPRITES+1]; // sprite_count, ^flags, tile_count, sprites_total, [spritep]..., last_width\r
+// sprite cache. stores results of sprite parsing for each display line:\r
+// [visible_sprites_count, sprl_flags, tile_count, sprites_processed, sprite_idx[sprite_count], last_width]\r
+unsigned char HighLnSpr[240][4+MAX_LINE_SPRITES+1];\r
 \r
 int rendstatus_old;\r
 int rendlines;\r
@@ -290,7 +292,8 @@ TileFlipMaker(TileFlip_and, pix_and)
   pal |= 0xc0; /* leave s/h bits untouched in pixel "and" */ \\r
   if (likely(m & (1<<(x+8)))) { \\r
     m &= ~(1<<(x+8)); \\r
-    if (t<0xe) pd[x] &= pal|t; \\r
+    /* if (!t) pd[x] |= 0x40; as per titan hw notes? */ \\r
+    pd[x] &= pal|t; \\r
   }\r
  \r
 TileNormMakerAS(TileNormSH_AS_and, pix_sh_as_and)\r
@@ -977,7 +980,7 @@ static void DrawSpritesSHi(unsigned char *sprited, const struct PicoEState *est)
   unsigned char *p;\r
   int cnt, w;\r
 \r
-  cnt = sprited[0] & 0x7f;\r
+  cnt = sprited[0];\r
   if (cnt == 0) return;\r
 \r
   p = &sprited[4];\r
@@ -1046,7 +1049,7 @@ static void DrawSpritesHiAS(unsigned char *sprited, int sh)
   unsigned m;\r
   int entry, cnt;\r
 \r
-  cnt = sprited[0] & 0x7f;\r
+  cnt = sprited[0];\r
   if (cnt == 0) return;\r
 \r
   memset(mb, 0xff, sizeof(mb));\r
@@ -1323,7 +1326,7 @@ static void DrawSpritesForced(unsigned char *sprited)
   unsigned m;\r
   int entry, cnt;\r
 \r
-  cnt = sprited[0] & 0x7f;\r
+  cnt = sprited[0];\r
   if (cnt == 0) { memset(pd, 0, sizeof(DefHighCol)); return; }\r
   \r
   memset(mb, 0xff, sizeof(mb));\r
@@ -1382,7 +1385,9 @@ static void DrawSpritesForced(unsigned char *sprited)
     *mp = m; // write last mask byte\r
   }\r
 \r
-  // anything not covered by a sprite is off (XXX or bg?)\r
+  // anything not covered by a sprite is off \r
+  // XXX Titan hw notes say that transparent pixels remove shadow. Is this also\r
+  // the case in areas where no sprites are displayed?\r
   for (cnt = 1; cnt < sizeof(mb)-1; cnt++)\r
     if (mb[cnt] == 0xff) {\r
       *(u32 *)(pd+8*cnt+0) = 0;\r
@@ -1401,7 +1406,7 @@ static void DrawSpritesForced(unsigned char *sprited)
 // Index + 0  :    hhhhvvvv ----hhvv yyyyyyyy yyyyyyyy // v, h: vert./horiz. size\r
 // Index + 4  :    xxxxxxxx xxxxxxxx pccvhnnn nnnnnnnn // x: x coord + 8\r
 \r
-static NOINLINE void ParseSprites(int max_lines)\r
+static NOINLINE void ParseSprites(int max_lines, int limit)\r
 {\r
   const struct PicoVideo *pvid=&Pico.video;\r
   const struct PicoEState *est=&Pico.est;\r
@@ -1417,6 +1422,10 @@ static NOINLINE void ParseSprites(int max_lines)
   if (max_lines > rendlines-1)\r
     max_lines = rendlines-1;\r
 \r
+  // look-ahead SAT parsing for next line and sprite pixel fetching for current\r
+  // line are limited if display was disabled during HBLANK before current line\r
+  if (limit) limit = 16; // max sprites/pixels processed\r
+\r
   if (!(Pico.video.reg[12]&1))\r
     max_sprites = 64, max_line_sprites = 16, max_width = 264;\r
   if (*est->PicoOpt & POPT_DIS_SPRITE_LIM)\r
@@ -1453,6 +1462,8 @@ static NOINLINE void ParseSprites(int max_lines)
     if (sy <= max_lines && sy + (height<<3) >= first_line) // sprite onscreen (y)?\r
     {\r
       int entry, y, w, sx_min, onscr_x, maybe_op = 0;\r
+      // omit look-ahead line if sprite parsing limit reached\r
+      int last_line = (limit && u >= 2*limit ? max_lines-1 : max_lines);\r
 \r
       sx_min = 8-(width<<3);\r
       onscr_x = sx_min < sx && sx < max_width;\r
@@ -1461,13 +1472,15 @@ static NOINLINE void ParseSprites(int max_lines)
 \r
       entry = ((pd - HighPreSpr) / 2) | ((code2>>8)&0x80);\r
       y = (sy >= first_line) ? sy : first_line;\r
-      for (; y < sy + (height<<3) && y <= max_lines; y++)\r
+      for (; y < sy + (height<<3) && y <= last_line; y++)\r
       {\r
         unsigned char *p = &HighLnSpr[y][0];\r
         int cnt = p[0];\r
-        if (p[3] >= max_line_sprites) continue;         // sprite limit?\r
         if (p[1] & SPRL_MASKED) continue;               // masked?\r
 \r
+        if (p[3] >= max_line_sprites) continue;         // sprite limit?\r
+        p[3] ++;\r
+\r
         w = width;\r
         if (p[2] + width > max_line_sprites*2) {        // tile limit?\r
           if (y+1 < 240) HighLnSpr[y+1][1] |= SPRL_TILE_OVFL;\r
@@ -1475,7 +1488,6 @@ static NOINLINE void ParseSprites(int max_lines)
           w = max_line_sprites*2 - p[2];\r
         }\r
         p[2] += w;\r
-        p[3] ++;\r
 \r
         if (sx == -0x78) {\r
           if (p[1] & (SPRL_HAVE_X|SPRL_TILE_OVFL))\r
@@ -1487,13 +1499,15 @@ static NOINLINE void ParseSprites(int max_lines)
 \r
         if (!onscr_x) continue; // offscreen x\r
 \r
-        p[4+cnt] = entry;\r
-        p[5+cnt] = w; // width clipped by tile limit for sprite renderer\r
-        p[0] = cnt + 1;\r
+        // sprite is (partly) visible, store info for renderer\r
         p[1] |= (entry & 0x80) ? SPRL_HAVE_HI : SPRL_HAVE_LO;\r
         p[1] |= maybe_op; // there might be op sprites on this line\r
         if (cnt > 0 && (code2 & 0x8000) && !(p[4+cnt-1]&0x80))\r
           p[1] |= SPRL_LO_ABOVE_HI;\r
+\r
+        p[4+cnt] = entry;\r
+        p[5+cnt] = w; // width clipped by tile limit for sprite renderer\r
+        p[0] = cnt + 1;\r
       }\r
     }\r
 \r
@@ -1506,6 +1520,23 @@ static NOINLINE void ParseSprites(int max_lines)
   }\r
   *pd = 0;\r
 \r
+  // fetching sprite pixels isn't done while display is disabled during HBLANK\r
+  if (limit) {\r
+    int w = 0;\r
+    unsigned char *sprited = &HighLnSpr[max_lines-1][0]; // current render line\r
+\r
+    for (u = 0; u < sprited[0]; u++) {\r
+      s32 *sp = HighPreSpr + (sprited[4+u] & 0x7f) * 2;\r
+      int sw = sp[0] >> 28;\r
+      if (w + sw > limit) {\r
+        sprited[0] = u;\r
+        sprited[4+u] = limit-w;\r
+        break;\r
+      }\r
+      w += sw;\r
+    }\r
+  }\r
+\r
 #if 0\r
   for (u = first_line; u <= max_lines; u++)\r
   {\r
@@ -1528,7 +1559,7 @@ static void DrawAllSprites(unsigned char *sprited, int prio, int sh,
   unsigned char *p;\r
   int cnt, w;\r
 \r
-  cnt = sprited[0] & 0x7f;\r
+  cnt = sprited[0];\r
   if (cnt == 0) return;\r
 \r
   p = &sprited[4];\r
@@ -1974,7 +2005,7 @@ static void PicoLine(int line, int offs, int sh, int bgc)
   Pico.est.DrawLineDest = (char *)Pico.est.DrawLineDest + DrawLineDestIncrement;\r
 }\r
 \r
-void PicoDrawSync(int to, int blank_last_line)\r
+void PicoDrawSync(int to, int blank_last_line, int limit_sprites)\r
 {\r
   struct PicoEState *est = &Pico.est;\r
   int line, offs = 0;\r
@@ -1990,7 +2021,7 @@ void PicoDrawSync(int to, int blank_last_line)
   }\r
   if (est->DrawScanline <= to && (est->rendstatus &\r
                 (PDRAW_SPRITES_MOVED|PDRAW_DIRTY_SPRITES|PDRAW_PARSE_SPRITES)))\r
-    ParseSprites(to + 1);\r
+    ParseSprites(to + 1, limit_sprites);\r
 \r
   for (line = est->DrawScanline; line < to; line++)\r
     PicoLine(line, offs, sh, bgc);\r
index bc06d7b..0bfcb9f 100644 (file)
@@ -286,7 +286,7 @@ void PicoFrameDrawOnly(void)
 {\r
   if (!(PicoIn.AHW & PAHW_SMS)) {\r
     PicoFrameStart();\r
-    PicoDrawSync(Pico.m.pal?239:223, 0);\r
+    PicoDrawSync(Pico.m.pal?239:223, 0, 0);\r
   } else {\r
     PicoFrameDrawOnlyMS();\r
   }\r
index 1644b0c..8cad1dd 100644 (file)
@@ -167,7 +167,7 @@ static int PicoFrameHints(void)
   if (!skip)
   {
     if (Pico.est.DrawScanline < y)
-      PicoDrawSync(y - 1, 0);
+      PicoDrawSync(y - 1, 0, 0);
 #ifdef DRAW_FINISH_FUNC
     DRAW_FINISH_FUNC();
 #endif
index e330413..6b245c3 100644 (file)
@@ -692,7 +692,7 @@ int CM_compareRun(int cyc, int is_sub);
 // draw.c\r
 void PicoDrawInit(void);\r
 PICO_INTERNAL void PicoFrameStart(void);\r
-void PicoDrawSync(int to, int blank_last_line);\r
+void PicoDrawSync(int to, int blank_last_line, int limit_sprites);\r
 void BackFill(int reg7, int sh, struct PicoEState *est);\r
 void FinalizeLine555(int sh, int line, struct PicoEState *est);\r
 void FinalizeLine8bit(int sh, int line, struct PicoEState *est);\r
index ce356f6..1038515 100644 (file)
@@ -139,6 +139,7 @@ void PicoVideoInit(void)
 \r
 \r
 static int blankline;           // display disabled for this line\r
+static int limitsprites;\r
 \r
 u32 SATaddr, SATmask;      // VRAM addr of sprite attribute table\r
 \r
@@ -793,6 +794,11 @@ static NOINLINE void CommandChange(struct PicoVideo *pvid)
 \r
 // VDP interface\r
  \r
+static inline int InHblank(int offs)\r
+{\r
+  return SekCyclesDone() - Pico.t.m68c_line_start <= 488-offs;\r
+}\r
+\r
 static void DrawSync(int skip)\r
 {\r
   int lines = Pico.video.reg[1]&0x08 ? 240 : 224;\r
@@ -802,10 +808,12 @@ static void DrawSync(int skip)
       !PicoIn.skipFrame && Pico.est.DrawScanline <= last) {\r
     //elprintf(EL_ANOMALY, "sync");\r
     if (blankline >= 0 && blankline < last) {\r
-      PicoDrawSync(blankline, 1);\r
+      PicoDrawSync(blankline, 1, 0);\r
       blankline = -1;\r
     }\r
-    PicoDrawSync(last, 0);\r
+    PicoDrawSync(last, 0, last == limitsprites);\r
+    if (last >= limitsprites)\r
+      limitsprites = -1;\r
   }\r
 }\r
 \r
@@ -820,18 +828,18 @@ PICO_INTERNAL_ASM void PicoVideoWrite(u32 a,unsigned short d)
   switch (a)\r
   {\r
   case 0x00: // Data port 0 or 2\r
-    // try avoiding the sync..\r
-    if (Pico.m.scanline < (pvid->reg[1]&0x08 ? 240 : 224) && (pvid->reg[1]&0x40) &&\r
-        !(!pvid->pending && ((pvid->command & 0xc00000f0) == 0x40000010 &&\r
-                        PicoMem.vsram[(pvid->addr>>1) & 0x3f] == (d & 0x7ff)))\r
-       )\r
-      DrawSync(0); // XXX  it's unclear when vscroll data is fetched from vsram?\r
-\r
     if (pvid->pending) {\r
       CommandChange(pvid);\r
       pvid->pending=0;\r
     }\r
 \r
+    // try avoiding the sync. can't easily do this for VRAM writes since they\r
+    // might update the SAT cache\r
+    if (Pico.m.scanline < (pvid->reg[1]&0x08 ? 240 : 224) && (pvid->reg[1]&0x40) &&\r
+        !(pvid->type == 3 && PicoMem.cram[(pvid->addr>>1) & 0x3f] == (d & 0xeee)) &&\r
+        !(pvid->type == 5 && PicoMem.vsram[(pvid->addr>>1) & 0x3f] == (d & 0x7ff)))\r
+      DrawSync(InHblank(440)); // experimentally, Overdrive 2\r
+\r
     if (!(PicoIn.opt&POPT_DIS_VDP_FIFO))\r
     {\r
       VdpFIFO.fifo_data[++VdpFIFO.fifo_dx&3] = d;\r
@@ -862,7 +870,7 @@ PICO_INTERNAL_ASM void PicoVideoWrite(u32 a,unsigned short d)
       CommandChange(pvid);\r
       // Check for dma:\r
       if (d & 0x80) {\r
-        DrawSync(SekCyclesDone() - Pico.t.m68c_line_start <= 488-390);\r
+        DrawSync(InHblank(390));\r
         CommandDma();\r
       }\r
     }\r
@@ -884,12 +892,17 @@ PICO_INTERNAL_ASM void PicoVideoWrite(u32 a,unsigned short d)
         if (num == 1 && ((pvid->reg[1]^d)&0x40)) {\r
           PicoVideoFIFOMode(d & 0x40, pvid->reg[12]&1);\r
           // handle line blanking before line rendering\r
-          if (SekCyclesDone() - Pico.t.m68c_line_start <= 488-390)\r
-            blankline = d&0x40 ? -1 : Pico.m.scanline;\r
+          if (InHblank(390)) {\r
+            // sprite rendering is limited if display is disabled and reenabled\r
+            // in HBLANK of the same scanline (Overdrive)\r
+            limitsprites = (d&0x40) && blankline == Pico.m.scanline ? Pico.m.scanline : -1;\r
+            blankline = (d&0x40) ? -1 : Pico.m.scanline;\r
+          }\r
         }\r
         if (num == 12 && ((pvid->reg[12]^d)&0x01))\r
           PicoVideoFIFOMode(pvid->reg[1]&0x40, d & 1);\r
-        DrawSync(SekCyclesDone() - Pico.t.m68c_line_start <= 488-390);\r
+        if (num <= 18) // no sync needed for DMA setup registers\r
+          DrawSync(InHblank(390));\r
         d &= 0xff;\r
         pvid->reg[num]=(unsigned char)d;\r
         switch (num)\r