sprite rendering improvements for masking and limit edge cases
authorkub <derkub@gmail.com>
Sun, 26 Jan 2020 19:40:07 +0000 (20:40 +0100)
committerkub <derkub@gmail.com>
Sun, 26 Jan 2020 19:40:07 +0000 (20:40 +0100)
pico/draw.c
pico/draw2.c
pico/draw_arm.S
pico/pico_int.h
pico/videoport.c

index 7fd93f8..652b9df 100644 (file)
@@ -53,7 +53,11 @@ static int  HighPreSpr[80*2+1]; // slightly preprocessed sprites
 #define SPRL_HAVE_LO     0x40 // *lo*\r
 #define SPRL_MAY_HAVE_OP 0x20 // may have operator sprites on the line\r
 #define SPRL_LO_ABOVE_HI 0x10 // low priority sprites may be on top of hi\r
-unsigned char HighLnSpr[240][3 + MAX_LINE_SPRITES]; // sprite_count, ^flags, tile_count, [spritep]...\r
+#define SPRL_HAVE_X      0x08 // have sprites with x != 0\r
+#define SPRL_TILE_OVFL   0x04 // tile limit exceeded on previous line\r
+#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
+unsigned char HighLnSpr[240][4+MAX_LINE_SPRITES+1]; // sprite_count, ^flags, tile_count, sprites_total, [spritep]..., last_width\r
 \r
 int rendstatus_old;\r
 int rendlines;\r
@@ -706,7 +710,7 @@ last_cut_tile:
 // Index + 0  :    hhhhvvvv ab--hhvv yyyyyyyy yyyyyyyy // a: offscreen h, b: offs. v, h: horiz. size\r
 // Index + 4  :    xxxxxxxx xxxxxxxx pccvhnnn nnnnnnnn // x: x coord + 8\r
 \r
-static void DrawSprite(int *sprite, int sh)\r
+static void DrawSprite(int *sprite, int sh, int w)\r
 {\r
   void (*fTileFunc)(unsigned char *pd, unsigned int pack, int pal);\r
   unsigned char *pd = Pico.est.HighCol;\r
@@ -746,6 +750,7 @@ static void DrawSprite(int *sprite, int sh)
     else            fTileFunc=TileNorm;\r
   }\r
 \r
+  if (w) width = w; // tile limit\r
   for (; width; width--,sx+=8,tile+=delta)\r
   {\r
     unsigned int pack;\r
@@ -833,12 +838,13 @@ static NOINLINE void DrawAllSpritesInterlace(int pri, int sh)
   struct PicoVideo *pvid=&Pico.video;\r
   int i,u,table,link=0,sline=Pico.est.DrawScanline<<1;\r
   unsigned int *sprites[80]; // Sprite index\r
+  int max_sprites = Pico.video.reg[12]&1 ? 80 : 64;\r
 \r
   table=pvid->reg[5]&0x7f;\r
   if (pvid->reg[12]&1) table&=0x7e; // Lowest bit 0 in 40-cell mode\r
   table<<=8; // Get sprite table address/2\r
 \r
-  for (i=u=0; u < 80 && i < 21; u++)\r
+  for (i = u = 0; u < max_sprites && link < max_sprites; u++)\r
   {\r
     unsigned int *sprite;\r
     int code, sx, sy, height;\r
@@ -888,15 +894,18 @@ static void DrawSpritesSHi(unsigned char *sprited, const struct PicoEState *est)
   void (*fTileFunc)(unsigned char *pd, unsigned int pack, int pal);\r
   unsigned char *pd = Pico.est.HighCol;\r
   unsigned char *p;\r
-  int cnt;\r
+  int cnt, w;\r
 \r
   cnt = sprited[0] & 0x7f;\r
   if (cnt == 0) return;\r
 \r
-  p = &sprited[3];\r
+  p = &sprited[4];\r
+  if ((sprited[1] & (SPRL_TILE_OVFL|SPRL_HAVE_MASK0)) == (SPRL_TILE_OVFL|SPRL_HAVE_MASK0))\r
+    return; // masking effective due to tile overflow\r
 \r
   // Go through sprites backwards:\r
-  for (cnt--; cnt >= 0; cnt--)\r
+  w = p[cnt]; // possibly clipped width of last sprite\r
+  for (cnt--; cnt >= 0; cnt--, w = 0)\r
   {\r
     int *sprite, code, pal, tile, sx, sy;\r
     int offs, delta, width, height, row;\r
@@ -940,6 +949,7 @@ static void DrawSpritesSHi(unsigned char *sprited, const struct PicoEState *est)
     tile &= 0x7ff; tile<<=4; tile+=(row&7)<<1; // Tile address\r
     delta<<=4; // Delta of address\r
 \r
+    if (w) width = w; // tile limit\r
     for (; width; width--,sx+=8,tile+=delta)\r
     {\r
       unsigned int pack;\r
@@ -967,7 +977,9 @@ static void DrawSpritesHiAS(unsigned char *sprited, int sh)
   if (cnt == 0) return;\r
 \r
   memset(mb, 0xff, sizeof(mb));\r
-  p = &sprited[3];\r
+  p = &sprited[4];\r
+  if ((sprited[1] & (SPRL_TILE_OVFL|SPRL_HAVE_MASK0)) == (SPRL_TILE_OVFL|SPRL_HAVE_MASK0))\r
+    return; // masking effective due to tile overflow\r
 \r
   // Go through sprites:\r
   for (entry = 0; entry < cnt; entry++)\r
@@ -1019,6 +1031,7 @@ static void DrawSpritesHiAS(unsigned char *sprited, int sh)
     tile &= 0x7ff; tile<<=4; tile+=(row&7)<<1; // Tile address\r
     delta<<=4; // Delta of address\r
 \r
+    if (entry+1 == cnt) width = p[entry+1]; // last sprite width limited?\r
     for (; width; width--,sx+=8,tile+=delta)\r
     {\r
       unsigned int pack;\r
@@ -1065,10 +1078,10 @@ static NOINLINE void PrepareSprites(int full)
   {\r
     int pack;\r
     // updates: tilecode, sx\r
-    for (u=0; u < max_sprites && (pack = *pd); u++, pd+=2)\r
+    for (u=0; u < max_sprites && link < max_sprites && (pack = *pd); u++, pd+=2)\r
     {\r
       unsigned int *sprite;\r
-      int code2, sx, sy, height;\r
+      int code2, sx, sy, height, width;\r
 \r
       sprite=(unsigned int *)(PicoMem.vram+((table+(link<<2))&0x7ffc)); // Find sprite\r
 \r
@@ -1078,25 +1091,29 @@ static NOINLINE void PrepareSprites(int full)
       sx -= 0x78; // Get X coordinate + 8\r
       sy = (pack << 16) >> 16;\r
       height = (pack >> 24) & 0xf;\r
+      width  = (pack >> 28);\r
 \r
       if (sy < max_lines &&\r
-         sy + (height<<3) > est->DrawScanline && // sprite onscreen (y)?\r
-          (sx > -24 || sx < max_width))                   // onscreen x\r
+         sy + (height<<3) > est->DrawScanline)       // sprite onscreen (y)?\r
       {\r
         int y = (sy >= est->DrawScanline) ? sy : est->DrawScanline;\r
         int entry = ((pd - HighPreSpr) / 2) | ((code2>>8)&0x80);\r
         for (; y < sy + (height<<3) && y < max_lines; y++)\r
         {\r
           int i, cnt;\r
-          cnt = HighLnSpr[y][0] & 0x7f;\r
-          if (cnt >= max_line_sprites) continue;              // sprite limit?\r
+          cnt = HighLnSpr[y][0];\r
+          if (HighLnSpr[y][3] >= max_line_sprites) continue;   // sprite limit?\r
 \r
           for (i = 0; i < cnt; i++)\r
-            if (((HighLnSpr[y][3+i] ^ entry) & 0x7f) == 0) goto found;\r
+            if (((HighLnSpr[y][4+i] ^ entry) & 0x7f) == 0) goto found;\r
 \r
           // this sprite was previously missing\r
-          HighLnSpr[y][3+cnt] = entry;\r
-          HighLnSpr[y][0] = cnt + 1;\r
+          HighLnSpr[y][3] ++;\r
+          if (sx > -24 && sx < max_width) {                    // onscreen x\r
+            HighLnSpr[y][4+cnt] = entry; // XXX wrong sequence?\r
+            HighLnSpr[y][5+cnt] = width; // XXX should count tiles for limit\r
+            HighLnSpr[y][0] = cnt + 1;\r
+          }\r
 found:;\r
           if (entry & 0x80)\r
                HighLnSpr[y][1] |= SPRL_HAVE_HI;\r
@@ -1118,7 +1135,7 @@ found:;
     for (u = 0; u < max_lines; u++)\r
       *((int *)&HighLnSpr[u][0]) = 0;\r
 \r
-    for (u = 0; u < max_sprites; u++)\r
+    for (u = 0; u < max_sprites && link < max_sprites; u++)\r
     {\r
       unsigned int *sprite;\r
       int code, code2, sx, sy, hv, height, width;\r
@@ -1138,7 +1155,7 @@ found:;
 \r
       if (sy < max_lines && sy + (height<<3) > est->DrawScanline) // sprite onscreen (y)?\r
       {\r
-        int entry, y, sx_min, onscr_x, maybe_op = 0;\r
+        int entry, y, w, sx_min, onscr_x, maybe_op = 0;\r
 \r
         sx_min = 8-(width<<3);\r
         onscr_x = sx_min < sx && sx < max_width;\r
@@ -1149,29 +1166,36 @@ found:;
         y = (sy >= est->DrawScanline) ? sy : est->DrawScanline;\r
         for (; y < sy + (height<<3) && y < max_lines; y++)\r
         {\r
-         unsigned char *p = &HighLnSpr[y][0];\r
+          unsigned char *p = &HighLnSpr[y][0];\r
           int cnt = p[0];\r
-          if (cnt >= max_line_sprites) continue;              // sprite limit?\r
-\r
-          if (p[2] >= max_line_sprites*2) {        // tile limit?\r
-            p[0] |= 0x80;\r
-            continue;\r
+          if (p[3] >= max_line_sprites) continue;         // sprite limit?\r
+          if ((p[1] & SPRL_MASKED) && !(entry & 0x80)) continue; // masked?\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
+            if (p[2] >= max_line_sprites*2) continue;\r
+            w = max_line_sprites*2 - p[2];\r
           }\r
-          p[2] += width;\r
+          p[2] += w;\r
+          p[3] ++;\r
 \r
           if (sx == -0x78) {\r
-            if (cnt > 0)\r
-              p[0] |= 0x80; // masked, no more sprites for this line\r
-            continue;\r
-          }\r
-          // must keep the first sprite even if it's offscreen, for masking\r
-          if (cnt > 0 && !onscr_x) continue; // offscreen x\r
+            if (p[1] & (SPRL_HAVE_X|SPRL_TILE_OVFL))\r
+              p[1] |= SPRL_MASKED; // masked, no more low sprites for this line\r
+            if (!(p[1] & SPRL_HAVE_X) && cnt == 0)\r
+              p[1] |= SPRL_HAVE_MASK0; // 1st sprite is masking\r
+          } else\r
+            p[1] |= SPRL_HAVE_X;\r
+\r
+          if (!onscr_x) continue; // offscreen x\r
 \r
-          p[3+cnt] = entry;\r
+          p[4+cnt] = entry;\r
+          p[5+cnt] = w; // width clipped by tile limit for sprite renderer\r
           p[0] = cnt + 1;\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[3+cnt-1]&0x80))\r
+          if (cnt > 0 && (code2 & 0x8000) && !(p[4+cnt-1]&0x80))\r
             p[1] |= SPRL_LO_ABOVE_HI;\r
         }\r
       }\r
@@ -1189,9 +1213,10 @@ found:;
     for (u = 0; u < max_lines; u++)\r
     {\r
       int y;\r
-      printf("c%03i: %2i, %2i: ", u, HighLnSpr[u][0] & 0x7f, HighLnSpr[u][2]);\r
-      for (y = 0; y < HighLnSpr[u][0] & 0x7f; y++)\r
-        printf(" %i", HighLnSpr[u][y+3]);\r
+      printf("c%03i: f %x c %2i/%2i w %2i: ", u, HighLnSpr[u][1],\r
+             HighLnSpr[u][0], HighLnSpr[u][3], HighLnSpr[u][2]);\r
+      for (y = 0; y < HighLnSpr[u][0]; y++)\r
+        printf(" %i", HighLnSpr[u][y+4]);\r
       printf("\n");\r
     }\r
 #endif\r
@@ -1203,20 +1228,22 @@ static void DrawAllSprites(unsigned char *sprited, int prio, int sh,
                            struct PicoEState *est)\r
 {\r
   unsigned char *p;\r
-  int cnt;\r
+  int cnt, w = sprited[2];\r
 \r
   cnt = sprited[0] & 0x7f;\r
   if (cnt == 0) return;\r
 \r
-  p = &sprited[3];\r
+  p = &sprited[4];\r
+  if ((sprited[1] & (SPRL_TILE_OVFL|SPRL_HAVE_MASK0)) == (SPRL_TILE_OVFL|SPRL_HAVE_MASK0))\r
+    return; // masking effective due to tile overflow\r
 \r
   // Go through sprites backwards:\r
-  for (cnt--; cnt >= 0; cnt--)\r
+  w = p[cnt]; // possibly clipped width of last sprite\r
+  for (cnt--; cnt >= 0; cnt--, w = 0)\r
   {\r
-    int offs;\r
+    int *sp = HighPreSpr + (p[cnt]&0x7f) * 2;\r
     if ((p[cnt] >> 7) != prio) continue;\r
-    offs = (p[cnt]&0x7f) * 2;\r
-    DrawSprite(HighPreSpr + offs, sh);\r
+    DrawSprite(sp, sh, w);\r
   }\r
 }\r
 \r
index 38a90ef..85e2b27 100644 (file)
@@ -420,12 +420,13 @@ static void DrawAllSpritesFull(int prio, int maxwidth)
        int i,u,link=0;\r
        unsigned int *sprites[80]; // Sprites\r
        int y_min=START_ROW*8, y_max=END_ROW*8; // for a simple sprite masking\r
+       int max_sprites = Pico.video.reg[12]&1 ? 80 : 64;\r
 \r
        table=pvid->reg[5]&0x7f;\r
        if (pvid->reg[12]&1) table&=0x7e; // Lowest bit 0 in 40-cell mode\r
        table<<=8; // Get sprite table address/2\r
 \r
-       for (i=u=0; u < 80; u++)\r
+       for (i = u = 0; u < max_sprites && link < max_sprites; u++)\r
        {\r
                unsigned int *sprite=NULL;\r
                int code, code2, sx, sy, height;\r
index 2efc804..fb6d095 100644 (file)
@@ -942,17 +942,23 @@ DrawTilesFromCache:
 .global DrawSpritesSHi\r
 \r
 DrawSpritesSHi:\r
-    ldr     r3, [r0]\r
+    ldrb    r3, [r0]\r
     mov     r12,#0xff\r
     ands    r3, r3, #0x7f\r
     bxeq    lr\r
 \r
-    stmfd   sp!, {r1,r4-r11,lr} @ +est\r
-    strb    r12,[r0,#2]     @ set end marker\r
-    add     r10,r0, #3      @ r10=HighLnSpr end\r
+    stmfd   sp!, {r1,r3-r11,lr} @ +est\r
+    strb    r12,[r0,#3]     @ set end marker\r
+    ldrb    r12,[r0,#1]\r
+    add     r10,r0, #4      @ r10=HighLnSpr end\r
+    mvn     r12,r12\r
+    tst     r12,#0x6        @ masking in slot 1 and tile ovfl?\r
+    ldmeqfd sp!, {r1,r3-r11,pc}\r
     add     r10,r10,r3      @ r10=HighLnSpr end\r
 \r
+    ldrb    r12,[r10,#0]    @ width of last sprite\r
     ldr     r11,[r1, #OFS_EST_HighCol]\r
+    str     r12,[sp, #4]\r
     mov     r12,#0xf\r
     ldr     lr, [r1, #OFS_EST_PicoMem_vram]\r
 \r
@@ -963,7 +969,7 @@ DrawSpriteSHi:
     ldr     r7, [sp]        @ est\r
     ldr     r1, [r7, #OFS_EST_HighPreSpr]\r
     cmp     r0, #0xff\r
-    ldmeqfd sp!, {r1,r4-r11,pc} @ end of list\r
+    ldmeqfd sp!, {r1,r3-r11,pc} @ end of list\r
     and     r0, r0, #0x7f\r
     add     r0, r1, r0, lsl #3\r
 \r
@@ -1007,10 +1013,16 @@ DrawSpriteSHi:
     and     r7, r7, #7\r
     add     r8, r8, r7, lsl #1 @ tile+=(row&7)<<1; // Tile address\r
 \r
+    ldr     r0, [sp, #4]\r
+    add     r6, r6, #1         @ inc now\r
+    cmp     r0, #0             @ check width of last sprite\r
+    movne   r6, r0\r
+    movne   r0, #0\r
+    strne   r0, [sp, #4]\r
+\r
     mov     r5, r5, lsl #4     @ delta<<=4; // Delta of address\r
     mov     r3, r4, lsr #9     @ r3=pal=((code>>9)&0x30);\r
 \r
-    add     r6, r6, #1         @ inc now\r
     adds    r0, r2, #0         @ mov sx to r0 and set ZV flags\r
     b       .dsprShi_loop_enter\r
 \r
@@ -1126,11 +1138,18 @@ DrawAllSprites:
     @ time to do some real work\r
     stmfd   sp!, {r1,r3-r11,lr} @ +sh|prio<<1 +est\r
     mov     r12,#0xff\r
-    strb    r12,[r0,#2]     @ set end marker\r
-    add     r10,r0, #3\r
+    strb    r12,[r0,#3]     @ set end marker\r
+    ldrb    r12,[r0,#1]\r
+    add     r10,r0 ,#4\r
+    mvn     r12,r12\r
+    tst     r12,#0x6        @ masking in slot 1 and tile ovfl?\r
+    ldmeqfd sp!, {r1,r3-r11,pc}\r
     add     r10,r10,r2      @ r10=HighLnSpr end\r
 \r
+    ldrb    r12,[r10,#0]    @ width of last sprite\r
     ldr     r11,[r3, #OFS_EST_HighCol]\r
+    orr     r1 ,r1 ,r12,lsl #24\r
+    str     r1, [sp]\r
     mov     r12,#0xf\r
     ldr     lr, [r3, #OFS_EST_PicoMem_vram]\r
 \r
@@ -1140,13 +1159,13 @@ DrawAllSprites:
 DrawSprite:\r
     @ draw next sprite\r
     ldrb    r0, [r10,#-1]!\r
-    ldr     r8, [sp]        @ sh|prio<<1\r
+    ldr     r4, [sp]        @ sh|prio<<1|lastw<<24\r
     ldr     r7, [sp, #4]    @ est\r
-    mov     r2, r0, lsr #7\r
+    mov     r2, r0, lsl #24\r
     cmp     r0, #0xff\r
     ldmeqfd sp!, {r1,r3-r11,pc} @ end of list\r
-    cmp     r2, r8, lsr #1\r
-    bne     DrawSprite      @ wrong priority\r
+    eor     r2, r2, r4, lsl #30\r
+    bmi     DrawSprite      @ wrong priority\r
     ldr     r1, [r7, #OFS_EST_HighPreSpr]\r
     and     r0, r0, #0x7f\r
     add     r0, r1, r0, lsl #3\r
@@ -1158,20 +1177,20 @@ DrawSprite:
     mov     r5, r3, lsr #24\r
     and     r5, r5, #7      @ r5=height\r
 \r
-    mov     r4, r3, lsl #16 @ r4=sy<<16 (tmp)\r
+    mov     r8, r3, lsl #16 @ r8=sy<<16 (tmp)\r
 \r
     ldr     r9, [r0, #4]\r
-    sub     r7, r7, r4, asr #16 @ r7=row=DrawScanline-sy\r
+    sub     r7, r7, r8, asr #16 @ r7=row=DrawScanline-sy\r
 \r
     mov     r2, r9, asr #16 @ r2=sx\r
     mov     r9, r9, lsl #16\r
     mov     r9, r9, lsr #16\r
-    orr     r9, r9, r8, lsl #31 @ r9=code|sh[31]\r
+    orr     r9, r9, r4, lsl #31 @ r9=code|sh[31]\r
 \r
     tst     r9, #0x1000\r
-    movne   r4, r5, lsl #3\r
-    subne   r4, r4, #1\r
-    subne   r7, r4, r7      @ if (code&0x1000) row=(height<<3)-1-row; // Flip Y\r
+    movne   r8, r5, lsl #3\r
+    subne   r8, r8, #1\r
+    subne   r7, r8, r7      @ if (code&0x1000) row=(height<<3)-1-row; // Flip Y\r
 \r
     add     r8, r9, r7, lsr #3 @ tile+=row>>3; // Tile number increases going down\r
     tst     r9, #0x0800\r
@@ -1183,7 +1202,12 @@ DrawSprite:
     and     r7, r7, #7\r
     add     r8, r8, r7, lsl #1 @ tile+=(row&7)<<1; // Tile address\r
 \r
-.dspr_continue:\r
+    add     r6, r6, #1         @ inc now\r
+    cmp     r4, #0x1000000     @ check width of last sprite\r
+    movhs   r6, r4, lsr #24\r
+    bichs   r4, r4, #0xff000000\r
+    strhs   r4, [sp]\r
+\r
     @ cache some stuff to avoid mem access\r
     mov     r5, r5, lsl #4     @ delta<<=4; // Delta of address\r
     and     r4, r9, #0x6000\r
@@ -1193,7 +1217,6 @@ DrawSprite:
     mov     r3, r4, lsr #9     @ r3=pal=((code>>9)&0x30);\r
     orrmi   r3, r3, #0x40      @ for sh/hi\r
 \r
-    add     r6, r6, #1         @ inc now\r
     adds    r0, r2, #0         @ mov sx to r0 and set ZV flags\r
     b       .dspr_loop_enter\r
 \r
index 70bfa71..a24fc6f 100644 (file)
@@ -667,8 +667,8 @@ void FinalizeLine555(int sh, int line, struct PicoEState *est);
 void PicoDrawSetOutBufMD(void *dest, int increment);\r
 extern int (*PicoScanBegin)(unsigned int num);\r
 extern int (*PicoScanEnd)(unsigned int num);\r
-#define MAX_LINE_SPRITES 29\r
-extern unsigned char HighLnSpr[240][3 + MAX_LINE_SPRITES];\r
+#define MAX_LINE_SPRITES 27    // +1 last sprite width, +4 hdr; total 32\r
+extern unsigned char HighLnSpr[240][4+MAX_LINE_SPRITES+1];\r
 extern void *DrawLineDestBase;\r
 extern int DrawLineDestIncrement;\r
 \r
index d196ee4..c2fbd0c 100644 (file)
@@ -200,6 +200,7 @@ static void DmaSlow(int len, unsigned int source)
         a = (a + inc) & 0x1ffff;\r
       }\r
       Pico.video.addr_u = a >> 16;\r
+      Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
       break;\r
 \r
     default:\r
@@ -266,6 +267,7 @@ static NOINLINE void DmaFill(int data)
         // Increment address register\r
         a = (u16)(a + inc);\r
       }\r
+      Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
       break;\r
     case 3:   // cram\r
     case 5: { // vsram\r
@@ -289,7 +291,6 @@ static NOINLINE void DmaFill(int data)
   Pico.video.reg[0x15] = source;\r
   Pico.video.reg[0x16] = source >> 8;\r
 \r
-  Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
 }\r
 \r
 static NOINLINE void CommandDma(void)\r