shithub: zelda3

Download patch

ref: 576b98b206030cb01fa658ce7d797c55dcd19fbf
parent: 51780f3db8ae3780adec661665d5445116ef07f9
author: Snesrev <snesrev@protonmail.com>
date: Sat Sep 3 20:12:54 EDT 2022

Remove/optimize more of the ppu

--- a/snes/ppu.c
+++ b/snes/ppu.c
@@ -8,54 +8,8 @@
 #include <assert.h>
 #include "ppu.h"
 #include "snes.h"
+#include "../types.h"
 
-// array for layer definitions per mode:
-//   0-7: mode 0-7; 8: mode 1 + l3prio; 9: mode 7 + extbg
-
-//   0-3; layers 1-4; 4: sprites; 5: nonexistent
-static const int layersPerMode[10][12] = {
-  {4, 0, 1, 4, 0, 1, 4, 2, 3, 4, 2, 3},
-  {4, 0, 1, 4, 0, 1, 4, 2, 4, 2, 5, 5},
-  {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5},
-  {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5},
-  {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5},
-  {4, 0, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5},
-  {4, 0, 4, 4, 0, 4, 5, 5, 5, 5, 5, 5},
-  {4, 4, 4, 0, 4, 5, 5, 5, 5, 5, 5, 5},
-  {2, 4, 0, 1, 4, 0, 1, 4, 4, 2, 5, 5},
-  {4, 4, 1, 4, 0, 4, 1, 5, 5, 5, 5, 5}
-};
-
-static const int prioritysPerMode[10][12] = {
-  {3, 1, 1, 2, 0, 0, 1, 1, 1, 0, 0, 0},
-  {3, 1, 1, 2, 0, 0, 1, 1, 0, 0, 5, 5},
-  {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5},
-  {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5},
-  {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5},
-  {3, 1, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5},
-  {3, 1, 2, 1, 0, 0, 5, 5, 5, 5, 5, 5},
-  {3, 2, 1, 0, 0, 5, 5, 5, 5, 5, 5, 5},
-  {1, 3, 1, 1, 2, 0, 0, 1, 0, 0, 5, 5},
-  {3, 2, 1, 1, 0, 0, 0, 5, 5, 5, 5, 5}
-};
-
-static const int layerCountPerMode[10] = {
-  12, 10, 8, 8, 8, 8, 6, 5, 10, 7
-};
-
-static const int bitDepthsPerMode[10][4] = {
-  {2, 2, 2, 2},
-  {4, 4, 2, 5},
-  {4, 4, 5, 5},
-  {8, 4, 5, 5},
-  {8, 2, 5, 5},
-  {4, 2, 5, 5},
-  {4, 5, 5, 5},
-  {8, 5, 5, 5},
-  {4, 4, 2, 5},
-  {8, 7, 5, 5}
-};
-
 static const int spriteSizes[8][2] = {
   {8, 16}, {8, 32}, {8, 64}, {16, 32},
   {16, 64}, {32, 64}, {16, 32}, {16, 32}
@@ -63,12 +17,9 @@
 
 static void ppu_handlePixel(Ppu* ppu, int x, int y);
 static int ppu_getPixel(Ppu* ppu, int x, int y, bool sub, int* r, int* g, int* b);
-static uint16_t ppu_getOffsetValue(Ppu* ppu, int col, int row);
-static int ppu_getPixelForBgLayer(Ppu* ppu, int x, int y, int layer, bool priority);
-static void ppu_handleOPT(Ppu* ppu, int layer, int* lx, int* ly);
 static void ppu_calculateMode7Starts(Ppu* ppu, int y);
 static int ppu_getPixelForMode7(Ppu* ppu, int x, int layer, bool priority);
-static bool ppu_getWindowState(Ppu* ppu, int layer, int x);
+static inline bool ppu_getWindowState(Ppu* ppu, int layer, int x);
 static void ppu_evaluateSprites(Ppu* ppu, int line);
 static uint16_t ppu_getVramRemap(Ppu* ppu);
 
@@ -125,10 +76,10 @@
   ppu->mosaicSize = 1;
   ppu->mosaicStartLine = 1;
   for(int i = 0; i < 5; i++) {
-    ppu->layer[i].mainScreenEnabled = false;
-    ppu->layer[i].subScreenEnabled = false;
-    ppu->layer[i].mainScreenWindowed = false;
-    ppu->layer[i].subScreenWindowed = false;
+    ppu->layer[i].screenEnabled[0] = false;
+    ppu->layer[i].screenEnabled[1] = false;
+    ppu->layer[i].screenWindowed[0] = false;
+    ppu->layer[i].screenWindowed[1] = false;
   }
   memset(ppu->m7matrix, 0, sizeof(ppu->m7matrix));
   ppu->m7prev = 0;
@@ -144,7 +95,7 @@
     ppu->windowLayer[i].window2enabled = false;
     ppu->windowLayer[i].window1inversed = false;
     ppu->windowLayer[i].window2inversed = false;
-    ppu->windowLayer[i].maskLogic = 0;
+    ppu->windowLayer[i].maskLogic_always_zero = 0;
   }
   ppu->window1left = 0;
   ppu->window1right = 0;
@@ -169,7 +120,7 @@
   ppu->frameOverscan_always_zero = false;
   ppu->interlace_always_zero = false;
   ppu->frameInterlace_always_zero = false;
-  ppu->directColor = false;
+  ppu->directColor_always_zero = false;
   ppu->hCount = 0;
   ppu->vCount = 0;
   ppu->hCountSecond = false;
@@ -196,8 +147,16 @@
 
 void ppu_runLine(Ppu* ppu, int line) {
   if(line == 0) {
-    // pre-render line
-    // TODO: this now happens halfway into the first line
+
+    // Ensure all window layer fields are just 0 or 1
+    for (int i = 0; i < 6; i++) {
+      WindowLayer *wl = &ppu->windowLayer[i];
+      wl->window1enabled = (wl->window1enabled != 0);
+      wl->window2enabled = (wl->window2enabled != 0);
+      wl->window1inversed = (wl->window1inversed != 0);
+      wl->window2inversed = (wl->window2inversed != 0);
+    }
+
     ppu->mosaicStartLine = 1;
     ppu->rangeOver = false;
     ppu->timeOver = false;
@@ -205,6 +164,8 @@
   } else {
     // evaluate sprites
     memset(ppu->objPixelBuffer, 0, sizeof(ppu->objPixelBuffer));
+    memset(ppu->objPriorityBuffer, 0xff, sizeof(ppu->objPriorityBuffer));
+
     if(!ppu->forcedBlank) ppu_evaluateSprites(ppu, line - 1);
     // actual line
     if(ppu->mode == 7) ppu_calculateMode7Starts(ppu, line);
@@ -214,38 +175,54 @@
   }
 }
 
+static FORCEINLINE bool ppu_checkmodecondition(uint8_t mode, bool state) {
+  return mode & (state + 1);
+  return mode == 3 || mode == 2 && state || mode == 1 && !state;
+  return (mode >> 1) & state | mode & (state ^ 1);
+}
+
+static FORCEINLINE int ppu_clamp0_31(int v) {
+  if (v < 0) v = 0;
+  if (v > 31) v = 31;
+  return v;
+}
+
 static void ppu_handlePixel(Ppu* ppu, int x, int y) {
-  int r = 0;
-  int g = 0;
-  int b = 0;
+  int r = 0, g = 0, b = 0;
+  
   if(!ppu->forcedBlank) {
     int mainLayer = ppu_getPixel(ppu, x, y, false, &r, &g, &b);
 
     bool colorWindowState = ppu_getWindowState(ppu, 5, x);
-    if(ppu->clipMode == 3 || (ppu->clipMode == 2 && colorWindowState) || (ppu->clipMode == 1 && !colorWindowState)) {
+    if(ppu_checkmodecondition(ppu->clipMode, colorWindowState)) {
       r = g = b = 0;
     }
     int secondLayer = 5; // backdrop
-    bool mathEnabled = mainLayer < 6 && ppu->mathEnabled[mainLayer] && !(
-      ppu->preventMathMode == 3 ||
-      (ppu->preventMathMode == 2 && colorWindowState) ||
-      (ppu->preventMathMode == 1 && !colorWindowState)
-    );
+    bool mathEnabled = mainLayer < 6 && ppu->mathEnabled[mainLayer] && !ppu_checkmodecondition(ppu->preventMathMode, colorWindowState);
     // TODO: subscreen pixels can be clipped to black as well
     // TODO: math for subscreen pixels (add/sub sub to main)
     if(mathEnabled) {
       int r2 = 0, g2 = 0, b2 = 0;
-      if (ppu->addSubscreen) {
-        secondLayer = ppu_getPixel(ppu, x, y, true, &r2, &g2, &b2);
-      }
-      if(ppu->subtractColor) {
-        r -= (ppu->addSubscreen && secondLayer != 5) ? r2 : ppu->fixedColorR;
-        g -= (ppu->addSubscreen && secondLayer != 5) ? g2 : ppu->fixedColorG;
-        b -= (ppu->addSubscreen && secondLayer != 5) ? b2 : ppu->fixedColorB;
+      if (ppu->addSubscreen && (secondLayer = ppu_getPixel(ppu, x, y, true, &r2, &g2, &b2)) != 5) {
+        if (ppu->subtractColor) {
+          r -= r2;
+          g -= g2;
+          b -= b2;
+        } else {
+          r += r2;
+          g += g2;
+          b += b2;
+        }
       } else {
-        r += (ppu->addSubscreen && secondLayer != 5) ? r2 : ppu->fixedColorR;
-        g += (ppu->addSubscreen && secondLayer != 5) ? g2 : ppu->fixedColorG;
-        b += (ppu->addSubscreen && secondLayer != 5) ? b2 : ppu->fixedColorB;
+        if (ppu->subtractColor) {
+          r -= ppu->fixedColorR;
+          g -= ppu->fixedColorG;
+          b -= ppu->fixedColorB;
+        } else {
+          r += ppu->fixedColorR;
+          g += ppu->fixedColorG;
+          b += ppu->fixedColorB;
+        }
       }
       if(ppu->halfColor && (secondLayer != 5 || !ppu->addSubscreen)) {
         r >>= 1;
@@ -252,12 +229,9 @@
         g >>= 1;
         b >>= 1;
       }
-      if(r > 31) r = 31;
-      if(g > 31) g = 31;
-      if(b > 31) b = 31;
-      if(r < 0) r = 0;
-      if(g < 0) g = 0;
-      if(b < 0) b = 0;
+      r = ppu_clamp0_31(r);
+      g = ppu_clamp0_31(g);
+      b = ppu_clamp0_31(b);
     }
   }
   int row = (y - 1) + (ppu->evenFrame ? 0 : 239);
@@ -268,116 +242,169 @@
   dst[3] = dst[7] = ((r << 3) | (r >> 2)) * ppu_brightness / 15;
 }
 
-static int ppu_getPixel(Ppu* ppu, int x, int y, bool sub, int* r, int* g, int* b) {
+
+static bool FORCEINLINE ppu_islayeractive(Ppu *ppu, int curLayer, bool sub, int x) {
+  Layer *layerp = &ppu->layer[curLayer];
+  return layerp->screenEnabled[sub] && (!layerp->screenWindowed[sub] || !ppu_getWindowState(ppu, curLayer, x));
+}
+
+typedef struct GetPixelRV {
+  uint8_t pixel;
+  uint8_t layer;
+} GetPixelRV;
+
+typedef struct TileAndXY {
+  uint16_t tile;
+  uint8_t x, y;
+} TileAndXY;
+
+static GetPixelRV ppu_getPixel_Mode7(Ppu *ppu, int x, int y, bool sub);
+static GetPixelRV ppu_getPixel_Mode1(Ppu *ppu, int x, int y, bool sub);
+
+static int FORCEINLINE ppu_getPixel(Ppu *ppu, int x, int y, bool sub, int *r, int *g, int *b) {
+  GetPixelRV rv;
+  if (ppu->mode == 1) {
+    rv = ppu_getPixel_Mode1(ppu, x, y, sub);
+  } else {
+    rv = ppu_getPixel_Mode7(ppu, x, y, sub);
+  }
+  uint16_t color = ppu->cgram[rv.pixel];
+  *r = color & 0x1f;
+  *g = (color >> 5) & 0x1f;
+  *b = (color >> 10) & 0x1f;
+  return rv.layer;
+}
+
+static GetPixelRV ppu_getPixel_Mode7(Ppu *ppu, int x, int y, bool sub) {
   // figure out which color is on this location on main- or subscreen, sets it in r, g, b
   // returns which layer it is: 0-3 for bg layer, 4 or 6 for sprites (depending on palette), 5 for backdrop
-  int actMode = ppu->mode == 1 && ppu->bg3priority ? 8 : ppu->mode;
-  int layer = 5;
-  int pixel = 0;
-  for(int i = 0; i < layerCountPerMode[actMode]; i++) {
-    int curLayer = layersPerMode[actMode][i];
-    int curPriority = prioritysPerMode[actMode][i];
-    bool layerActive = false;
-    if(!sub) {
-      layerActive = ppu->layer[curLayer].mainScreenEnabled && (
-        !ppu->layer[curLayer].mainScreenWindowed || !ppu_getWindowState(ppu, curLayer, x)
-      );
-    } else {
-      layerActive = ppu->layer[curLayer].subScreenEnabled && (
-        !ppu->layer[curLayer].subScreenWindowed || !ppu_getWindowState(ppu, curLayer, x)
-      );
-    }
-    if(layerActive) {
-      if(curLayer < 4) {
-        // bg layer
-        int lx = x;
-        int ly = y;
-        if(ppu->bgLayer[curLayer].mosaicEnabled && ppu->mosaicSize > 1) {
-          lx -= lx % ppu->mosaicSize;
-          ly -= (ly - ppu->mosaicStartLine) % ppu->mosaicSize;
-        }
-        if(ppu->mode == 7) {
-          pixel = ppu_getPixelForMode7(ppu, lx, curLayer, curPriority);
-        } else {
-          lx += ppu->bgLayer[curLayer].hScroll;
-          ly += ppu->bgLayer[curLayer].vScroll;
-          pixel = ppu_getPixelForBgLayer(
-            ppu, lx & 0x3ff, ly & 0x3ff,
-            curLayer, curPriority
-          );
-        }
+  int actMode = ppu->mode == 1 ? 8 : ppu->mode;
+  static const uint8_t kLayersMode7[] = { 4, 4, 4, 0, 4 };
+  static const uint8_t kPrioritysMode7[] = { 3, 2, 1, 0, 0, };
+  for (int i = 0; i < 5; i++) {
+    int curLayer = kLayersMode7[i];
+    int curPriority = kPrioritysMode7[i];
+    int pixel;
+    if (ppu_islayeractive(ppu, curLayer, sub, x)) {
+      if (curLayer < 4) {
+        pixel = ppu_getPixelForMode7(ppu, x, curLayer, curPriority);
       } else {
-        // get a pixel from the sprite buffer
-        pixel = 0;
-        if(ppu->objPriorityBuffer[x] == curPriority) pixel = ppu->objPixelBuffer[x];
+        pixel = (ppu->objPriorityBuffer[x] == curPriority) ? ppu->objPixelBuffer[x] : 0;
+        if (pixel < 0xc0)
+          curLayer = 6;
       }
+      if (pixel != 0)
+        return (GetPixelRV) { pixel, curLayer };
     }
-    if(pixel > 0) {
-      layer = curLayer;
-      break;
+  }
+  return (GetPixelRV) { 0, 5 };
+}
+
+static FORCEINLINE TileAndXY ppu_GetBgTileAndXy(Ppu *ppu, int x, int y, int layer);
+static FORCEINLINE int ppu_GetPixelFromTileAndXY_2bpp(Ppu *ppu, TileAndXY txy, int tileAdr);
+static FORCEINLINE int ppu_GetPixelFromTileAndXY_4bpp(Ppu *ppu, TileAndXY txy, int tileAdr);
+
+static FORCEINLINE GetPixelRV ppu_getSpritePixelRV(uint8_t pixel) {
+  // sprites with palette color < 0xc0 are layer 6 instead of 4.
+  return (GetPixelRV) { pixel, pixel < 0xc0 ? 6 : 4 };
+}
+
+static GetPixelRV ppu_getPixel_Mode1(Ppu *ppu, int x, int y, bool sub) {
+  // figure out which color is on this location on main- or subscreen
+  // returns which layer it is: 0-3 for bg layer, 4 or 6 for sprites (depending on palette), 5 for backdrop
+  TileAndXY BG1, BG2, BG3;
+  uint8_t pixel;
+
+  // BG3 tiles with priority 1, 2bpp
+  bool layer3active = ppu_islayeractive(ppu, 2, sub, x);
+  if (layer3active) {
+    BG3 = ppu_GetBgTileAndXy(ppu, x, y, 2);
+    if (BG3.tile & 0x2000) {
+      if ((pixel = ppu_GetPixelFromTileAndXY_2bpp(ppu, BG3, ppu->bgLayer[2].tileAdr)) != 0)
+        return (GetPixelRV) { pixel, 2 };
+      layer3active = false;
     }
   }
-  if(ppu->directColor && layer < 4 && bitDepthsPerMode[actMode][layer] == 8) {
-    *r = ((pixel & 0x7) << 2) | ((pixel & 0x100) >> 7);
-    *g = ((pixel & 0x38) >> 1) | ((pixel & 0x200) >> 8);
-    *b = ((pixel & 0xc0) >> 3) | ((pixel & 0x400) >> 8);
-  } else {
-    uint16_t color = ppu->cgram[pixel & 0xff];
-    *r = color & 0x1f;
-    *g = (color >> 5) & 0x1f;
-    *b = (color >> 10) & 0x1f;
+  // Sprites with priority 3
+  uint8_t obj_prio = ppu->objPriorityBuffer[x];
+  if (obj_prio == 3 && ppu_islayeractive(ppu, 4, sub, x))
+    return ppu_getSpritePixelRV(ppu->objPixelBuffer[x]);
+  
+  // BG1 tiles with priority 1
+  bool layer1active = ppu_islayeractive(ppu, 0, sub, x);
+  if (layer1active) {
+    BG1 = ppu_GetBgTileAndXy(ppu, x, y, 0);
+    if (BG1.tile & 0x2000) {
+      if ((pixel = ppu_GetPixelFromTileAndXY_4bpp(ppu, BG1, ppu->bgLayer[0].tileAdr)) != 0)
+        return (GetPixelRV) { pixel, 0 };
+      layer1active = false;
+    }
   }
-  if(layer == 4 && pixel < 0xc0) layer = 6; // sprites with palette color < 0xc0
-  return layer;
+  // BG2 tiles with priority 1
+  bool layer2active = ppu_islayeractive(ppu, 1, sub, x);
+  if (layer2active) {
+    BG2 = ppu_GetBgTileAndXy(ppu, x, y, 1);
+    if (BG2.tile & 0x2000) {
+      if ((pixel = ppu_GetPixelFromTileAndXY_4bpp(ppu, BG2, ppu->bgLayer[1].tileAdr)) != 0)
+        return (GetPixelRV) { pixel, 1 };
+      layer2active = false;
+    }
+  }
+  // Sprites with priority 2
+  if (obj_prio == 2 && ppu_islayeractive(ppu, 4, sub, x))
+    return ppu_getSpritePixelRV(ppu->objPixelBuffer[x]);
+  // BG1 tiles with priority 0
+  if (layer1active && (pixel = ppu_GetPixelFromTileAndXY_4bpp(ppu, BG1, ppu->bgLayer[0].tileAdr)))
+    return (GetPixelRV) { pixel, 0 };
+  // BG2 tiles with priority 0
+  if (layer2active && (pixel = ppu_GetPixelFromTileAndXY_4bpp(ppu, BG2, ppu->bgLayer[1].tileAdr)))
+    return (GetPixelRV) { pixel, 1 };
+  // Sprites with priority 1
+  // Sprites with priority 0
+  if (obj_prio <= 1 && ppu_islayeractive(ppu, 4, sub, x))
+    return ppu_getSpritePixelRV(ppu->objPixelBuffer[x]);
+  // BG3 tiles with priority 0
+  if (layer3active && (pixel = ppu_GetPixelFromTileAndXY_2bpp(ppu, BG3, ppu->bgLayer[2].tileAdr)))
+    return (GetPixelRV) { pixel, 2 };
+  // backdrop
+  return (GetPixelRV) { 0, 5 };
 }
 
-static int ppu_getPixelForBgLayer(Ppu* ppu, int x, int y, int layer, bool priority) {
+static FORCEINLINE TileAndXY ppu_GetBgTileAndXy(Ppu *ppu, int x, int y, int layer) {
   BgLayer *layerp = &ppu->bgLayer[layer];
-  // figure out address of tilemap word and read it
-  int tileBitsX = 3;
-  int tileHighBitX = 0x100;
-  int tileBitsY = 3;
-  int tileHighBitY = 0x100;
-  uint16_t tilemapAdr = layerp->tilemapAdr + (((y >> tileBitsY) & 0x1f) << 5 | ((x >> tileBitsX) & 0x1f));
-  if ((x & tileHighBitX) && layerp->tilemapWider) tilemapAdr += 0x400;
-  if ((y & tileHighBitY) && layerp->tilemapHigher) tilemapAdr += layerp->tilemapWider ? 0x800 : 0x400;
-  uint16_t tile = ppu->vram[tilemapAdr & 0x7fff];
-  // check priority, get palette
-  if(((bool) (tile & 0x2000)) != priority) return 0; // wrong priority
-  int paletteNum = (tile & 0x1c00) >> 10;
-  // figure out position within tile
-  int row = (tile & 0x8000) ? 7 - (y & 0x7) : (y & 0x7);
-  int col = (tile & 0x4000) ? (x & 0x7) : 7 - (x & 0x7);
-  int tileNum = tile & 0x3ff;
-  // read tiledata, ajust palette for mode 0
-  int bitDepth = bitDepthsPerMode[ppu->mode][layer];
-  if(ppu->mode == 0) paletteNum += 8 * layer;
-  // plane 1 (always)
-  int paletteSize = 4;
-  uint16_t plane1 = ppu->vram[(layerp->tileAdr + ((tileNum & 0x3ff) * 4 * bitDepth) + row) & 0x7fff];
-  int pixel = (plane1 >> col) & 1;
-  pixel |= ((plane1 >> (8 + col)) & 1) << 1;
-  // plane 2 (for 4bpp, 8bpp)
-  if(bitDepth > 2) {
-    paletteSize = 16;
-    uint16_t plane2 = ppu->vram[(layerp->tileAdr + ((tileNum & 0x3ff) * 4 * bitDepth) + 8 + row) & 0x7fff];
-    pixel |= ((plane2 >> col) & 1) << 2;
-    pixel |= ((plane2 >> (8 + col)) & 1) << 3;
+  if (layerp->mosaicEnabled && ppu->mosaicSize > 1) {
+    x -= x % ppu->mosaicSize;
+    y -= (y - ppu->mosaicStartLine) % ppu->mosaicSize;
   }
-  // plane 3 & 4 (for 8bpp)
-  if(bitDepth > 4) {
-    paletteSize = 256;
-    uint16_t plane3 = ppu->vram[(layerp->tileAdr + ((tileNum & 0x3ff) * 4 * bitDepth) + 16 + row) & 0x7fff];
-    pixel |= ((plane3 >> col) & 1) << 4;
-    pixel |= ((plane3 >> (8 + col)) & 1) << 5;
-    uint16_t plane4 = ppu->vram[(layerp->tileAdr + ((tileNum & 0x3ff) * 4 * bitDepth) + 24 + row) & 0x7fff];
-    pixel |= ((plane4 >> col) & 1) << 6;
-    pixel |= ((plane4 >> (8 + col)) & 1) << 7;
-  }
-  // return cgram index, or 0 if transparent, palette number in bits 10-8 for 8-color layers
-  return pixel == 0 ? 0 : paletteSize * paletteNum + pixel;
+  x += layerp->hScroll;
+  y += layerp->vScroll;
+  // figure out address of tilemap word and read it
+  int tilemapAdr = layerp->tilemapAdr + (((y >> 3) & 0x1f) << 5 | ((x >> 3) & 0x1f));
+  if (layerp->tilemapWider)
+    tilemapAdr += (x & 0x100) << 2;
+  if ((y & 0x100) && layerp->tilemapHigher) tilemapAdr += layerp->tilemapWider ? 0x800 : 0x400;
+  return (TileAndXY) { ppu->vram[tilemapAdr & 0x7fff], x, y };
 }
 
+static FORCEINLINE int ppu_GetPixelFromTileAndXY_2bpp(Ppu *ppu, TileAndXY txy, int tileAdr) {
+  int paletteNum = (txy.tile & 0x1c00) >> 10;
+  int row = (txy.tile & 0x8000) ? 7 - (txy.y & 0x7) : (txy.y & 0x7);
+  int col = (txy.tile & 0x4000) ? (txy.x & 0x7) : 7 - (txy.x & 0x7);
+  int plane1 = ppu->vram[(tileAdr + ((txy.tile & 0x3ff) * 4 * 2) + row) & 0x7fff] >> col;
+  int pixel = plane1 & 1 | (plane1 >> 7) & 2;
+  return pixel == 0 ? 0 : 4 * paletteNum + pixel;
+}
+
+static FORCEINLINE int ppu_GetPixelFromTileAndXY_4bpp(Ppu *ppu, TileAndXY txy, int tileAdr) {
+  int paletteNum = (txy.tile & 0x1c00) >> 10;
+  int row = (txy.tile & 0x8000) ? 7 - (txy.y & 0x7) : (txy.y & 0x7);
+  int col = (txy.tile & 0x4000) ? (txy.x & 0x7) : 7 - (txy.x & 0x7);
+  uint16_t *addr = &ppu->vram[(tileAdr + ((txy.tile & 0x3ff) * 4 * 4) + row) & 0x7fff];
+  uint16_t plane1 = addr[0] >> col, plane2 = addr[8] >> col;
+  int pixel = plane1 & 1 | (plane1 >> 7) & 2 | (plane2 << 2) & 4 | (plane2 >> 5) & 8;
+  return pixel == 0 ? 0 : 16 * paletteNum + pixel;
+}
+
 static void ppu_calculateMode7Starts(Ppu* ppu, int y) {
   // expand 13-bit values to signed values
   int hScroll = ((int16_t) (ppu->m7matrix[6] << 3)) >> 3;
@@ -408,6 +435,8 @@
 }
 
 static int ppu_getPixelForMode7(Ppu* ppu, int x, int layer, bool priority) {
+  if (ppu->bgLayer[layer].mosaicEnabled && ppu->mosaicSize > 1)
+    x -= x % ppu->mosaicSize;
   uint8_t rx = ppu->m7xFlip ? 255 - x : x;
   int xPos = (ppu->m7startX + ppu->m7matrix[0] * rx) >> 8;
   int yPos = (ppu->m7startY + ppu->m7matrix[2] * rx) >> 8;
@@ -424,29 +453,13 @@
   return pixel;
 }
 
-static bool ppu_getWindowState(Ppu* ppu, int layer, int x) {
-  if(!ppu->windowLayer[layer].window1enabled && !ppu->windowLayer[layer].window2enabled) {
+static inline bool ppu_getWindowState(Ppu* ppu, int layer, int x) {
+  WindowLayer *wl = &ppu->windowLayer[layer];
+  if(!(wl->window1enabled | wl->window2enabled))
     return false;
-  }
-  if(ppu->windowLayer[layer].window1enabled && !ppu->windowLayer[layer].window2enabled) {
-    bool test = x >= ppu->window1left && x <= ppu->window1right;
-    return ppu->windowLayer[layer].window1inversed ? !test : test;
-  }
-  if(!ppu->windowLayer[layer].window1enabled && ppu->windowLayer[layer].window2enabled) {
-    bool test = x >= ppu->window2left && x <= ppu->window2right;
-    return ppu->windowLayer[layer].window2inversed ? !test : test;
-  }
   bool test1 = x >= ppu->window1left && x <= ppu->window1right;
   bool test2 = x >= ppu->window2left && x <= ppu->window2right;
-  if(ppu->windowLayer[layer].window1inversed) test1 = !test1;
-  if(ppu->windowLayer[layer].window2inversed) test2 = !test2;
-  switch(ppu->windowLayer[layer].maskLogic) {
-    case 0: return test1 || test2;
-    case 1: return test1 && test2;
-    case 2: return test1 != test2;
-    case 3: return test1 == test2;
-  }
-  return false;
+  return ((test1 ^ wl->window1inversed) & wl->window1enabled) | ((test2 ^ wl->window2inversed) & wl->window2enabled);
 }
 
 static void ppu_evaluateSprites(Ppu* ppu, int line) {
@@ -504,7 +517,7 @@
               pixel |= ((plane2 >> (8 + shift)) & 1) << 3;
               // draw it in the buffer if there is a pixel here, and the buffer there is still empty
               int screenCol = col + x + px;
-              if(pixel > 0 && screenCol >= 0 && screenCol < 256 && ppu->objPixelBuffer[screenCol] == 0) {
+              if(pixel != 0 && screenCol >= 0 && screenCol < 256 && ppu->objPixelBuffer[screenCol] == 0) {
                 ppu->objPixelBuffer[screenCol] = 0x80 + 16 * palette + pixel;
                 ppu->objPriorityBuffer[screenCol] = (ppu->oam[index + 1] & 0x3000) >> 12;
               }
@@ -511,7 +524,8 @@
             }
           }
         }
-        if(tilesFound > 34) break; // break out of sprite-loop if max tiles found
+        if(tilesFound > 34)
+          break; // break out of sprite-loop if max tiles found
       }
     }
     index += 2;
@@ -606,16 +620,7 @@
       return ret;
     }
     case 0x3c: {
-#if 0
-      uint8_t val = 0;
-      if(ppu->hCountSecond) {
-        val = ((ppu->hCount >> 8) & 1) | (ppu->ppu2openBus & 0xfe);
-      } else {
-        val = ppu->hCount & 0xff;
-      }
-#else
       uint8_t val = 0x17;// (ppu->ppu2openBus + ppu->cgramPointer * 7) * 0x31337 >> 8;
-#endif
       ppu->hCountSecond = !ppu->hCountSecond;
       ppu->ppu2openBus = val;
       return val;
@@ -707,6 +712,7 @@
     case 0x05: {
       ppu->mode = val & 0x7;
       ppu->bg3priority = val & 0x8;
+      assert(val == 7 || val == 9);
       assert(ppu->mode == 1 || ppu->mode == 7);
       // bigTiles are never used
       assert((val & 0xf0) == 0);
@@ -843,14 +849,14 @@
     case 0x23:
     case 0x24:
     case 0x25: {
-      ppu->windowLayer[(adr - 0x23) * 2].window1inversed = val & 0x1;
-      ppu->windowLayer[(adr - 0x23) * 2].window1enabled = val & 0x2;
-      ppu->windowLayer[(adr - 0x23) * 2].window2inversed = val & 0x4;
-      ppu->windowLayer[(adr - 0x23) * 2].window2enabled = val & 0x8;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window1inversed = val & 0x10;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window1enabled = val & 0x20;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window2inversed = val & 0x40;
-      ppu->windowLayer[(adr - 0x23) * 2 + 1].window2enabled = val & 0x80;
+      ppu->windowLayer[(adr - 0x23) * 2].window1inversed = (val & 0x1) != 0;
+      ppu->windowLayer[(adr - 0x23) * 2].window1enabled = (val & 0x2) != 0;
+      ppu->windowLayer[(adr - 0x23) * 2].window2inversed = (val & 0x4) != 0;
+      ppu->windowLayer[(adr - 0x23) * 2].window2enabled = (val & 0x8) != 0;
+      ppu->windowLayer[(adr - 0x23) * 2 + 1].window1inversed = (val & 0x10) != 0;
+      ppu->windowLayer[(adr - 0x23) * 2 + 1].window1enabled = (val & 0x20) != 0;
+      ppu->windowLayer[(adr - 0x23) * 2 + 1].window2inversed = (val & 0x40) != 0;
+      ppu->windowLayer[(adr - 0x23) * 2 + 1].window2enabled = (val & 0x80) != 0;
       break;
     }
     case 0x26: {
@@ -870,51 +876,49 @@
       break;
     }
     case 0x2a: {
-      ppu->windowLayer[0].maskLogic = val & 0x3;
-      ppu->windowLayer[1].maskLogic = (val >> 2) & 0x3;
-      ppu->windowLayer[2].maskLogic = (val >> 4) & 0x3;
-      ppu->windowLayer[3].maskLogic = (val >> 6) & 0x3;
+      assert(val == 0);
+      // maskLogic_always_zero
       break;
     }
     case 0x2b: {
-      ppu->windowLayer[4].maskLogic = val & 0x3;
-      ppu->windowLayer[5].maskLogic = (val >> 2) & 0x3;
+      assert(val == 0);
+      // maskLogic_always_zero
       break;
     }
     case 0x2c: {
-      ppu->layer[0].mainScreenEnabled = val & 0x1;
-      ppu->layer[1].mainScreenEnabled = val & 0x2;
-      ppu->layer[2].mainScreenEnabled = val & 0x4;
-      ppu->layer[3].mainScreenEnabled = val & 0x8;
-      ppu->layer[4].mainScreenEnabled = val & 0x10;
+      ppu->layer[0].screenEnabled[0] = val & 0x1;
+      ppu->layer[1].screenEnabled[0] = val & 0x2;
+      ppu->layer[2].screenEnabled[0] = val & 0x4;
+      ppu->layer[3].screenEnabled[0] = val & 0x8;
+      ppu->layer[4].screenEnabled[0] = val & 0x10;
       break;
     }
     case 0x2d: {
-      ppu->layer[0].subScreenEnabled = val & 0x1;
-      ppu->layer[1].subScreenEnabled = val & 0x2;
-      ppu->layer[2].subScreenEnabled = val & 0x4;
-      ppu->layer[3].subScreenEnabled = val & 0x8;
-      ppu->layer[4].subScreenEnabled = val & 0x10;
+      ppu->layer[0].screenEnabled[1] = val & 0x1;
+      ppu->layer[1].screenEnabled[1] = val & 0x2;
+      ppu->layer[2].screenEnabled[1] = val & 0x4;
+      ppu->layer[3].screenEnabled[1] = val & 0x8;
+      ppu->layer[4].screenEnabled[1] = val & 0x10;
       break;
     }
     case 0x2e: {
-      ppu->layer[0].mainScreenWindowed = val & 0x1;
-      ppu->layer[1].mainScreenWindowed = val & 0x2;
-      ppu->layer[2].mainScreenWindowed = val & 0x4;
-      ppu->layer[3].mainScreenWindowed = val & 0x8;
-      ppu->layer[4].mainScreenWindowed = val & 0x10;
+      ppu->layer[0].screenWindowed[0] = val & 0x1;
+      ppu->layer[1].screenWindowed[0] = val & 0x2;
+      ppu->layer[2].screenWindowed[0] = val & 0x4;
+      ppu->layer[3].screenWindowed[0] = val & 0x8;
+      ppu->layer[4].screenWindowed[0] = val & 0x10;
       break;
     }
     case 0x2f: {
-      ppu->layer[0].subScreenWindowed = val & 0x1;
-      ppu->layer[1].subScreenWindowed = val & 0x2;
-      ppu->layer[2].subScreenWindowed = val & 0x4;
-      ppu->layer[3].subScreenWindowed = val & 0x8;
-      ppu->layer[4].subScreenWindowed = val & 0x10;
+      ppu->layer[0].screenWindowed[1] = val & 0x1;
+      ppu->layer[1].screenWindowed[1] = val & 0x2;
+      ppu->layer[2].screenWindowed[1] = val & 0x4;
+      ppu->layer[3].screenWindowed[1] = val & 0x8;
+      ppu->layer[4].screenWindowed[1] = val & 0x10;
       break;
     }
     case 0x30: {
-      ppu->directColor = val & 0x1;
+      assert((val & 1) == 0);  // directColor always zero
       ppu->addSubscreen = val & 0x2;
       ppu->preventMathMode = (val & 0x30) >> 4;
       ppu->clipMode = (val & 0xc0) >> 6;
@@ -956,7 +960,7 @@
     memcpy(pixels + (dest * 2048), &ppu->pixelBuffer[y1 * 2048], 2048);
     memcpy(pixels + ((dest + 1) * 2048), &ppu->pixelBuffer[y1 * 2048], 2048);
   }
-  // clear top 16 and last 16 lines if not overscanning
+  // clear top 16 and last 16 lines
   memset(pixels, 0, 2048 * 16);
   memset(pixels + (464 * 2048), 0, 2048 * 16);
 }
--- a/snes/ppu.h
+++ b/snes/ppu.h
@@ -24,10 +24,8 @@
 } BgLayer;
 
 typedef struct Layer {
-  bool mainScreenEnabled;
-  bool subScreenEnabled;
-  bool mainScreenWindowed;
-  bool subScreenWindowed;
+  bool screenEnabled[2];   // 0 = main, 1 = sub
+  bool screenWindowed[2];  // 0 = main, 1 = sub
 } Layer;
 
 typedef struct WindowLayer {
@@ -35,7 +33,7 @@
   bool window2enabled;
   bool window1inversed;
   bool window2inversed;
-  uint8_t maskLogic;
+  uint8_t maskLogic_always_zero;
 } WindowLayer;
 
 struct Ppu {
@@ -117,7 +115,7 @@
   bool frameOverscan_always_zero; // if we are overscanning this frame (determined at 0,225)
   bool interlace_always_zero;
   bool frameInterlace_always_zero; // if we are interlacing this frame (determined at start vblank)
-  bool directColor;
+  bool directColor_always_zero;
   // latching
   uint16_t hCount;
   uint16_t vCount;