vdp sprite handling improvement (SAT cache)
authorkub <derkub@gmail.com>
Sun, 16 Feb 2020 12:53:50 +0000 (13:53 +0100)
committerkub <derkub@gmail.com>
Sun, 16 Feb 2020 13:10:14 +0000 (14:10 +0100)
pico/draw.c
pico/pico_int.h
pico/videoport.c

index 49d4152..da87ede 100644 (file)
@@ -45,6 +45,8 @@ static int  HighCacheA[41+1];   // caches for high layers
 static int  HighCacheB[41+1];\r
 static int  HighPreSpr[80*2+1]; // slightly preprocessed sprites\r
 \r
+unsigned int VdpSATCache[128];  // VDP sprite cache (1st 32 sprite attr bits)\r
+\r
 #define LF_PLANE_1 (1 << 0)\r
 #define LF_SH      (1 << 1) // must be = 2\r
 #define LF_FORCE   (1 << 2)\r
@@ -1124,14 +1126,14 @@ 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 PrepareSprites(int full)\r
+static NOINLINE void PrepareSprites(int max_lines)\r
 {\r
   const struct PicoVideo *pvid=&Pico.video;\r
   const struct PicoEState *est=&Pico.est;\r
   int u,link=0,sh;\r
   int table=0;\r
   int *pd = HighPreSpr;\r
-  int max_lines = 224, max_sprites = 80, max_width = 328;\r
+  int max_sprites = 80, max_width = 328;\r
   int max_line_sprites = 20; // 20 sprites, 40 tiles\r
 \r
   if (!(Pico.video.reg[12]&1))\r
@@ -1139,160 +1141,101 @@ static NOINLINE void PrepareSprites(int full)
   if (PicoIn.opt & POPT_DIS_SPRITE_LIM)\r
     max_line_sprites = MAX_LINE_SPRITES;\r
 \r
-  if (pvid->reg[1]&8) max_lines = 240;\r
   sh = Pico.video.reg[0xC]&8; // shadow/hilight?\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
-  if (!full)\r
-  {\r
-    int pack;\r
-    // updates: tilecode, sx\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, width;\r
-\r
-      sprite=(unsigned int *)(PicoMem.vram+((table+(link<<2))&0x7ffc)); // Find sprite\r
+  for (u = est->DrawScanline; u < max_lines; u++)\r
+    *((int *)&HighLnSpr[u][0]) = 0;\r
 \r
-      // parse sprite info\r
-      code2 = sprite[1];\r
-      sx = (code2>>16)&0x1ff;\r
-      sx -= 0x78; // Get X coordinate + 8\r
-      sy = (pack << 16) >> 16;\r
-      height = (pack >> 24) & 0xf;\r
-      width  = (pack >> 28);\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
 \r
-      if (sy < max_lines &&\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];\r
-          if (HighLnSpr[y][3] >= max_line_sprites) continue;   // sprite limit?\r
-\r
-          for (i = 0; i < cnt; i++)\r
-            if (((HighLnSpr[y][4+i] ^ entry) & 0x7f) == 0) goto found;\r
-\r
-          // this sprite was previously missing\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
-          else HighLnSpr[y][1] |= SPRL_HAVE_LO;\r
-        }\r
-      }\r
+    sprite=(unsigned int *)(PicoMem.vram+((table+(link<<2))&0x7ffc)); // Find sprite\r
 \r
-      code2 &= ~0xfe000000;\r
-      code2 -=  0x00780000; // Get X coordinate + 8 in upper 16 bits\r
-      pd[1] = code2;\r
+    // parse sprite info. the 1st half comes from the VDPs internal cache,\r
+    // the 2nd half is read from VRAM\r
+    code = VdpSATCache[link]; // normally but not always equal to sprite[0]\r
+    sy = (code&0x1ff)-0x80;\r
+    hv = (code>>24)&0xf;\r
+    height = (hv&3)+1;\r
+    width  = (hv>>2)+1;\r
 \r
-      // Find next sprite\r
-      link=(sprite[0]>>16)&0x7f;\r
-      if (!link) break; // End of sprites\r
-    }\r
-  }\r
-  else\r
-  {\r
-    for (u = 0; u < max_lines; u++)\r
-      *((int *)&HighLnSpr[u][0]) = 0;\r
+    code2 = sprite[1];\r
+    sx = (code2>>16)&0x1ff;\r
+    sx -= 0x78; // Get X coordinate + 8\r
 \r
-    for (u = 0; u < max_sprites && link < max_sprites; u++)\r
+    if (sy < max_lines && sy + (height<<3) >= est->DrawScanline) // sprite onscreen (y)?\r
     {\r
-      unsigned int *sprite;\r
-      int code, code2, sx, sy, hv, height, width;\r
+      int entry, y, w, sx_min, onscr_x, maybe_op = 0;\r
 \r
-      sprite=(unsigned int *)(PicoMem.vram+((table+(link<<2))&0x7ffc)); // Find sprite\r
+      sx_min = 8-(width<<3);\r
+      onscr_x = sx_min < sx && sx < max_width;\r
+      if (sh && (code2 & 0x6000) == 0x6000)\r
+        maybe_op = SPRL_MAY_HAVE_OP;\r
 \r
-      // parse sprite info\r
-      code = sprite[0];\r
-      sy = (code&0x1ff)-0x80;\r
-      hv = (code>>24)&0xf;\r
-      height = (hv&3)+1;\r
-\r
-      width  = (hv>>2)+1;\r
-      code2 = sprite[1];\r
-      sx = (code2>>16)&0x1ff;\r
-      sx -= 0x78; // Get X coordinate + 8\r
-\r
-      if (sy < max_lines && sy + (height<<3) > est->DrawScanline) // sprite onscreen (y)?\r
+      entry = ((pd - HighPreSpr) / 2) | ((code2>>8)&0x80);\r
+      y = (sy >= est->DrawScanline) ? sy : est->DrawScanline;\r
+      for (; y < sy + (height<<3) && y < max_lines; y++)\r
       {\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
-        if (sh && (code2 & 0x6000) == 0x6000)\r
-          maybe_op = SPRL_MAY_HAVE_OP;\r
-\r
-        entry = ((pd - HighPreSpr) / 2) | ((code2>>8)&0x80);\r
-        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
-          int cnt = p[0];\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] += w;\r
-          p[3] ++;\r
-\r
-          if (sx == -0x78) {\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[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[4+cnt-1]&0x80))\r
-            p[1] |= SPRL_LO_ABOVE_HI;\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) && !(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] += w;\r
+        p[3] ++;\r
+\r
+        if (sx == -0x78) {\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[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[4+cnt-1]&0x80))\r
+          p[1] |= SPRL_LO_ABOVE_HI;\r
       }\r
+    }\r
 \r
-      *pd++ = (width<<28)|(height<<24)|(hv<<16)|((unsigned short)sy);\r
-      *pd++ = (sx<<16)|((unsigned short)code2);\r
+    *pd++ = (width<<28)|(height<<24)|(hv<<16)|((unsigned short)sy);\r
+    *pd++ = (sx<<16)|((unsigned short)code2);\r
 \r
-      // Find next sprite\r
-      link=(code>>16)&0x7f;\r
-      if (!link) break; // End of sprites\r
-    }\r
-    *pd = 0;\r
+    // Find next sprite\r
+    link=(code>>16)&0x7f;\r
+    if (!link) break; // End of sprites\r
+  }\r
+  *pd = 0;\r
 \r
 #if 0\r
-    for (u = 0; u < max_lines; u++)\r
-    {\r
-      int y;\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
+  for (u = 0; u < max_lines; u++)\r
+  {\r
+    int y;\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
 }\r
 \r
 #ifndef _ASM_DRAW_C\r
@@ -1505,12 +1448,11 @@ static int DrawDisplay(int sh)
   int win=0, edge=0, hvwind=0, lflags;\r
   int maxw, maxcells;\r
 \r
-  if (est->rendstatus & (PDRAW_SPRITES_MOVED|PDRAW_DIRTY_SPRITES)) {\r
-    // elprintf(EL_STATUS, "PrepareSprites(%i)", (est->rendstatus>>4)&1);\r
-    PrepareSprites(est->rendstatus & PDRAW_DIRTY_SPRITES);\r
-    est->rendstatus &= ~(PDRAW_SPRITES_MOVED|PDRAW_DIRTY_SPRITES);\r
-  }\r
+  if (!(est->DrawScanline & 15) ||\r
+      (est->rendstatus & (PDRAW_SPRITES_MOVED|PDRAW_DIRTY_SPRITES)))\r
+    PrepareSprites((est->DrawScanline+16) & ~15);\r
 \r
+  est->rendstatus &= ~(PDRAW_SPRITES_MOVED|PDRAW_DIRTY_SPRITES);\r
   est->rendstatus &= ~(PDRAW_SHHI_DONE|PDRAW_PLANE_HI_PRIO);\r
 \r
   if (pvid->reg[12]&1) {\r
@@ -1656,8 +1598,6 @@ PICO_INTERNAL void PicoFrameStart(void)
 \r
   if (PicoIn.opt & POPT_ALT_RENDERER)\r
     return;\r
-\r
-  PrepareSprites(1);\r
 }\r
 \r
 static void DrawBlankedLine(int line, int offs, int sh, int bgc)\r
index 7032922..12f35b5 100644 (file)
@@ -676,6 +676,7 @@ extern int (*PicoScanEnd)(unsigned int num);
 extern unsigned char HighLnSpr[240][4+MAX_LINE_SPRITES+1];\r
 extern void *DrawLineDestBase;\r
 extern int DrawLineDestIncrement;\r
+extern unsigned int VdpSATCache[128];\r
 \r
 // draw2.c\r
 void PicoDraw2Init(void);\r
index f64ac69..b9e0401 100644 (file)
@@ -15,6 +15,8 @@ extern const unsigned char  hcounts_32[];
 extern const unsigned char  hcounts_40[];\r
 \r
 static int blankline;           // display disabled for this line\r
+static unsigned sat;            // VRAM addr of sprite attribute table\r
+static int satxbits;            // index bits in SAT address\r
 \r
 int (*PicoDmaHook)(unsigned int source, int len, unsigned short **base, unsigned int *mask) = NULL;\r
 \r
@@ -315,14 +317,37 @@ void PicoVideoFIFOMode(int active)
 static __inline void AutoIncrement(void)\r
 {\r
   Pico.video.addr=(unsigned short)(Pico.video.addr+Pico.video.reg[0xf]);\r
+  if (Pico.video.addr < Pico.video.reg[0xf]) Pico.video.addr_u ^= 1;\r
 }\r
 \r
-static NOINLINE unsigned int VideoWrite128(u32 a, u16 d)\r
+static __inline void UpdateSAT(u32 a, u32 d)\r
+{\r
+  Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
+  if (!((a^sat) >> satxbits) && !(a & 4)) {\r
+    int num = (a >> 3) & 0x7f;\r
+    ((u16 *)&VdpSATCache[num])[(a&3) >> 1] = d;\r
+  }\r
+}\r
+\r
+static NOINLINE void VideoWriteVRAM128(u32 a, u16 d)\r
 {\r
   // nasty\r
-  a = ((a & 2) >> 1) | ((a & 0x400) >> 9) | (a & 0x3FC) | ((a & 0x1F800) >> 1);\r
-  ((u8 *)PicoMem.vram)[a] = d;\r
-  return a;\r
+  u32 b = ((a & 2) >> 1) | ((a & 0x400) >> 9) | (a & 0x3FC) | ((a & 0x1F800) >> 1);\r
+\r
+  ((u8 *)PicoMem.vram)[b] = d;\r
+  if (!((u16)(b^sat) >> satxbits))\r
+    Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
+\r
+  if (!((u16)(a^sat) >> satxbits))\r
+    UpdateSAT(a, d);\r
+}\r
+\r
+static void VideoWriteVRAM(u32 a, u16 d)\r
+{\r
+  PicoMem.vram [(u16)a >> 1] = d;\r
+\r
+  if (!((u16)(a^sat) >> satxbits))\r
+    UpdateSAT(a, d);\r
 }\r
 \r
 static void VideoWrite(u16 d)\r
@@ -333,19 +358,15 @@ static void VideoWrite(u16 d)
   {\r
     case 1: if (a & 1)\r
               d = (u16)((d << 8) | (d >> 8));\r
-            PicoMem.vram [(a >> 1) & 0x7fff] = d;\r
-            if ((unsigned)(a - ((Pico.video.reg[5]&0x7f) << 9)) < 0x400)\r
-              Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
+            a |= Pico.video.addr_u << 16;\r
+            VideoWriteVRAM(a, d);\r
             break;\r
     case 3: if (PicoMem.cram [(a >> 1) & 0x3f] != d) Pico.m.dirtyPal = 1;\r
-            PicoMem.cram [(a >> 1) & 0x3f] = d; break;\r
-    case 5: PicoMem.vsram[(a >> 1) & 0x3f] = d; break;\r
-    case 0x81: if (a & 1)\r
-              d = (u16)((d << 8) | (d >> 8));\r
+            PicoMem.cram [(a >> 1) & 0x3f] = d & 0xeee; break;\r
+    case 5: PicoMem.vsram[(a >> 1) & 0x3f] = d & 0x7ff; break;\r
+    case 0x81:\r
             a |= Pico.video.addr_u << 16;\r
-            a = VideoWrite128(a, d);\r
-            if ((unsigned)(a - ((Pico.video.reg[5]&0x7f) << 9)) < 0x400)\r
-              Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
+            VideoWriteVRAM128(a, d);\r
             break;\r
     //default:elprintf(EL_ANOMALY, "VDP write %04x with bad type %i", d, Pico.video.type); break;\r
   }\r
@@ -363,9 +384,10 @@ static unsigned int VideoRead(void)
   switch (Pico.video.type)\r
   {\r
     case 0: d=PicoMem.vram [a & 0x7fff]; break;\r
-    case 8: d=(PicoMem.cram [a & 0x003f] & 0x0eee) | (d & ~0x0eee); break;\r
+    case 8: d=PicoMem.cram [a & 0x003f] | (d & ~0x0eee); break;\r
+\r
     case 4: if ((a & 0x3f) >= 0x28) a = 0;\r
-            d=(PicoMem.vsram [a & 0x003f] & 0x07ff) | (d & ~0x07ff); break;\r
+            d=PicoMem.vsram [a & 0x003f] | (d & ~0x07ff); break;\r
     case 12:a=PicoMem.vram [a & 0x7fff]; if (Pico.video.addr&1) a >>= 8;\r
             d=(a & 0x00ff) | (d & ~0x00ff); break;\r
     default:elprintf(EL_ANOMALY, "VDP read with bad type %i", Pico.video.type); break;\r
@@ -391,7 +413,7 @@ static int GetDmaLength(void)
 static void DmaSlow(int len, unsigned int source)\r
 {\r
   u32 inc = Pico.video.reg[0xf];\r
-  u32 a = Pico.video.addr;\r
+  u32 a = Pico.video.addr | (Pico.video.addr_u << 16);\r
   u16 *r, *base = NULL;\r
   u32 mask = 0x1ffff;\r
 \r
@@ -451,26 +473,28 @@ static void DmaSlow(int len, unsigned int source)
   switch (Pico.video.type)\r
   {\r
     case 1: // vram\r
+#if 0\r
       r = PicoMem.vram;\r
-      if (inc == 2 && !(a & 1) && a + len * 2 < 0x10000\r
-          && !(((source + len - 1) ^ source) & ~mask))\r
+      if (inc == 2 && !(a & 1) && (a >> 16) == ((a + len*2) >> 16) &&\r
+          (source & ~mask) == ((source + len-1) & ~mask) &&\r
+          (a << 16 >= (sat+0x280) << 16 || (a + len*2) << 16 <= sat << 16))\r
       {\r
         // most used DMA mode\r
         memcpy((char *)r + a, base + (source & mask), len * 2);\r
         a += len * 2;\r
       }\r
       else\r
+#endif\r
       {\r
         for(; len; len--)\r
         {\r
           u16 d = base[source++ & mask];\r
           if(a & 1) d=(d<<8)|(d>>8);\r
-          r[a >> 1] = d;\r
+          VideoWriteVRAM(a, d);\r
           // AutoIncrement\r
-          a = (u16)(a + inc);\r
+          a = (a+inc) & ~0x20000;\r
         }\r
       }\r
-      Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
       break;\r
 \r
     case 3: // cram\r
@@ -478,9 +502,9 @@ static void DmaSlow(int len, unsigned int source)
       r = PicoMem.cram;\r
       for (; len; len--)\r
       {\r
-        r[(a / 2) & 0x3f] = base[source++ & mask];\r
+        r[(a / 2) & 0x3f] = base[source++ & mask] & 0xeee;\r
         // AutoIncrement\r
-        a += inc;\r
+        a = (a+inc) & ~0x20000;\r
       }\r
       break;\r
 \r
@@ -488,22 +512,20 @@ static void DmaSlow(int len, unsigned int source)
       r = PicoMem.vsram;\r
       for (; len; len--)\r
       {\r
-        r[(a / 2) & 0x3f] = base[source++ & mask];\r
+        r[(a / 2) & 0x3f] = base[source++ & mask] & 0x7ff;\r
         // AutoIncrement\r
-        a += inc;\r
+        a = (a+inc) & ~0x20000;\r
       }\r
       break;\r
 \r
     case 0x81: // vram 128k\r
-      a |= Pico.video.addr_u << 16;\r
       for(; len; len--)\r
       {\r
-        VideoWrite128(a, base[source++ & mask]);\r
+        u16 d = base[source++ & mask];\r
+        VideoWriteVRAM128(a, d);\r
         // AutoIncrement\r
-        a = (a + inc) & 0x1ffff;\r
+        a = (a+inc) & ~0x20000;\r
       }\r
-      Pico.video.addr_u = a >> 16;\r
-      Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
       break;\r
 \r
     default:\r
@@ -512,12 +534,13 @@ static void DmaSlow(int len, unsigned int source)
       break;\r
   }\r
   // remember addr\r
-  Pico.video.addr=(u16)a;\r
+  Pico.video.addr = a;\r
+  Pico.video.addr_u = a >> 16;\r
 }\r
 \r
 static void DmaCopy(int len)\r
 {\r
-  u16 a = Pico.video.addr;\r
+  u32 a = Pico.video.addr | (Pico.video.addr_u << 16);\r
   u8 *vr = (u8 *)PicoMem.vram;\r
   u8 inc = Pico.video.reg[0xf];\r
   int source;\r
@@ -528,21 +551,23 @@ static void DmaCopy(int len)
   source =Pico.video.reg[0x15];\r
   source|=Pico.video.reg[0x16]<<8;\r
 \r
-  // XXX implement VRAM 128k? Is this even working?\r
+  // XXX implement VRAM 128k? Is this even working? count still in bytes?\r
   for (; len; len--)\r
   {\r
-    vr[a] = vr[source++ & 0xffff];\r
+    vr[(u16)a] = vr[(u16)(source++)];\r
+    if (!((u16)(a^sat) >> satxbits))\r
+      UpdateSAT(a, ((u16 *)vr)[(u16)a >> 1]);\r
     // AutoIncrement\r
-    a=(u16)(a+inc);\r
+    a = (a+inc) & ~0x20000;\r
   }\r
   // remember addr\r
-  Pico.video.addr=a;\r
-  Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
+  Pico.video.addr = a;\r
+  Pico.video.addr_u = a >> 16;\r
 }\r
 \r
 static NOINLINE void DmaFill(int data)\r
 {\r
-  u16 a = Pico.video.addr;\r
+  u32 a = Pico.video.addr | (Pico.video.addr_u << 16);\r
   u8 *vr = (u8 *)PicoMem.vram;\r
   u8 high = (u8)(data >> 8);\r
   u8 inc = Pico.video.reg[0xf];\r
@@ -561,40 +586,41 @@ static NOINLINE void DmaFill(int data)
       for (l = len; l; l--) {\r
         // Write upper byte to adjacent address\r
         // (here we are byteswapped, so address is already 'adjacent')\r
-        vr[a] = high;\r
+        vr[(u16)a] = high;\r
+        if (!((u16)(a^sat) >> satxbits))\r
+          UpdateSAT(a, ((u16 *)vr)[(u16)a >> 1]);\r
 \r
         // Increment address register\r
-        a = (u16)(a + inc);\r
+        a = (a+inc) & ~0x20000;\r
       }\r
-      Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
       break;\r
     case 3:   // cram\r
       Pico.m.dirtyPal = 1;\r
+      data &= 0xeee;\r
       for (l = len; l; l--) {\r
         PicoMem.cram[(a/2) & 0x3f] = data;\r
 \r
         // Increment address register\r
-        a += inc;\r
+        a = (a+inc) & ~0x20000;\r
       }\r
       break;\r
     case 5: { // vsram\r
+      data &= 0x7ff;\r
       for (l = len; l; l--) {\r
         PicoMem.vsram[(a/2) & 0x3f] = data;\r
 \r
         // Increment address register\r
-        a += inc;\r
+        a = (a+inc) & ~0x20000;\r
       }\r
       break;\r
     }\r
     case 0x81: // vram 128k\r
       for (l = len; l; l--) {\r
-        VideoWrite128(a, data);\r
+        VideoWriteVRAM128(a, data);\r
 \r
         // Increment address register\r
-        a = (a + inc) & 0x1ffff;\r
+        a = (a+inc) & ~0x20000;\r
       }\r
-      Pico.video.addr_u = a >> 16;\r
-      Pico.est.rendstatus |= PDRAW_DIRTY_SPRITES;\r
       break;\r
     default:\r
       a += len * inc;\r
@@ -603,6 +629,7 @@ static NOINLINE void DmaFill(int data)
 \r
   // remember addr\r
   Pico.video.addr = a;\r
+  Pico.video.addr_u = a >> 16;\r
   // register update\r
   Pico.video.reg[0x13] = Pico.video.reg[0x14] = 0;\r
   source  = Pico.video.reg[0x15];\r
@@ -779,14 +806,21 @@ PICO_INTERNAL_ASM void PicoVideoWrite(unsigned int a,unsigned short d)
             pvid->status |= ((d >> 3) ^ SR_VB) & SR_VB; // forced blanking\r
             goto update_irq;\r
           case 0x05:\r
-            //elprintf(EL_STATUS, "spritep moved to %04x", (unsigned)(Pico.video.reg[5]&0x7f) << 9);\r
+          case 0x06:\r
             if (d^dold) Pico.est.rendstatus |= PDRAW_SPRITES_MOVED;\r
             break;\r
           case 0x0c:\r
             // renderers should update their palettes if sh/hi mode is changed\r
             if ((d^dold)&8) Pico.m.dirtyPal = 1;\r
             break;\r
+          default:\r
+            return;\r
         }\r
+        sat = ((pvid->reg[5]&0x7f) << 9) | ((pvid->reg[6]&0x20) << 11);\r
+        satxbits = 9;\r
+        if (Pico.video.reg[12]&1)\r
+          sat &= ~0x200, satxbits = 10; // H40, zero lowest SAT bit\r
+        //elprintf(EL_STATUS, "spritep moved to %04x", sat);\r
         return;\r
 \r
 update_irq:\r
@@ -991,6 +1025,11 @@ void PicoVideoLoad(void)
     Pico.m.dma_xfers = 0;\r
   }\r
 \r
+  sat = ((pv->reg[5]&0x7f) << 9) | ((pv->reg[6]&0x20) << 11);\r
+  satxbits = 9;\r
+  if (pv->reg[12]&1)\r
+    sat &= ~0x200, satxbits = 10; // H40, zero lowest SAT bit\r
+\r
   // rebuild SAT cache XXX wrong since cache and memory can differ\r
   for (l = 0; l < 80; l++) {\r
     *((u16 *)VdpSATCache + 2*l  ) = PicoMem.vram[(sat>>1) + l*4    ];\r