diff --git a/src/ppu.c b/src/ppu.c index 2a5ba88..9bad747 100644 --- a/src/ppu.c +++ b/src/ppu.c @@ -131,6 +131,29 @@ uint8_t UPALRAM[0x03];/* for 0x4/0x8/0xC addresses in palette, the ones in * 0x20 are 0 to not break fceu rendering. */ +/* Background pair LUT for pputile.h's per-tile 8-pixel palette gather. + * Each 8-bit chunk of pixdata indexes two 4-bit pens (low nibble = even + * pixel, high nibble = odd), so a 256-entry uint16_t table maps each + * pixdata byte to a pre-packed 2-pen pair, letting the per-tile body + * emit four pair-lookups and a single 64-bit store instead of eight + * serial nibble-load-store iterations. + * + * Rebuilt at the start of every RefreshLine call so that mid-frame + * raster effects writing to $3F00-$3F0F see their pen changes on the + * very next scanline. 256 entries x ~3 ops = ~768 ops per rebuild; + * RefreshLine runs once per scanline plus the occasional mid-scanline + * call from FCEUPPU_LineUpdate, so the worst-case rebuild cost is + * well under 1% of frame budget. */ +static uint16_t fceu_bg_pair_lut[256]; + +static void FCEU_BuildBgPairLUT(void) +{ + int b; + for (b = 0; b < 256; b++) + fceu_bg_pair_lut[b] = (uint16_t)PALRAM[b & 0x0F] | + ((uint16_t)PALRAM[b >> 4] << 8); +} + #define MMC5SPRVRAMADR(V) &MMC5SPRVPage[(V) >> 10][(V)] #define VRAMADR(V) &VPage[(V) >> 10][(V)] @@ -445,6 +468,13 @@ static void FASTAPASS(1) RefreshLine(int lastpixel) { if (numtiles <= 0) return; + /* Rebuild the bg pair LUT once per RefreshLine call. This covers + * both per-scanline calls from FCEUPPU_Loop and mid-scanline calls + * from FCEUPPU_LineUpdate (raster effects); any PALRAM[0x00..0x0F] + * change done since the previous RefreshLine is reflected for the + * about-to-be-rendered tile span. */ + FCEU_BuildBgPairLUT(); + P = Pline; vofs = 0; diff --git a/src/pputile.h b/src/pputile.h index aed172b..74d59c7 100644 --- a/src/pputile.h +++ b/src/pputile.h @@ -12,28 +12,33 @@ uint32_t vadr; #endif if (X1 >= 2) { - uint8_t *S = PALRAM; uint32_t pixdata; pixdata = ppulut1[(pshift[0] >> (8 - XOffset)) & 0xFF] | ppulut2[(pshift[1] >> (8 - XOffset)) & 0xFF]; pixdata |= ppulut3[XOffset | (atlatch << 3)]; - P[0] = S[pixdata & 0xF]; - pixdata >>= 4; - P[1] = S[pixdata & 0xF]; - pixdata >>= 4; - P[2] = S[pixdata & 0xF]; - pixdata >>= 4; - P[3] = S[pixdata & 0xF]; - pixdata >>= 4; - P[4] = S[pixdata & 0xF]; - pixdata >>= 4; - P[5] = S[pixdata & 0xF]; - pixdata >>= 4; - P[6] = S[pixdata & 0xF]; - pixdata >>= 4; - P[7] = S[pixdata & 0xF]; + /* Per-tile palette gather: 8 nibbles of pixdata index PALRAM[0..15]. + * Equivalent scalar form is + * for (k = 0; k < 8; k++) { P[k] = PALRAM[pixdata & 0xF]; pixdata >>= 4; } + * which gcc unrolls into 8 byte loads + a shift-OR-accumulate chain + * + one 64-bit store. The pair-LUT replacement at ppu.c file scope + * (fceu_bg_pair_lut[256], rebuilt at the start of RefreshLine) + * halves the load count: each 8-bit slice of pixdata fetches a + * pre-packed 2-pen pair, so we read four 16-bit values, OR-merge + * into a 64-bit tile word, and emit a single store. may_alias + * + aligned(1) lets gcc emit a direct movq without going through + * a stack scratch (which the obvious __builtin_memcpy / packed- + * struct form would force, costing the optimisation entirely). */ + { + typedef uint64_t fceu_u64_unaligned __attribute__((may_alias, aligned(1))); + uint64_t packed = + (uint64_t)fceu_bg_pair_lut[ pixdata & 0xFF] | + ((uint64_t)fceu_bg_pair_lut[(pixdata >> 8) & 0xFF] << 16) | + ((uint64_t)fceu_bg_pair_lut[(pixdata >> 16) & 0xFF] << 32) | + ((uint64_t)fceu_bg_pair_lut[(pixdata >> 24) & 0xFF] << 48); + *(fceu_u64_unaligned *)P = packed; + } P += 8; }