From 8c7d600b74aff0ea4b2ad8fd03d1ea930bad460a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 3 Oct 2026 00:44:46 -0600 Subject: [PATCH] SoftGPU: Fewer address and plane computations per textured pixel A texel's byte offset is a sum of one for its row and one for its column, swizzled or not, so the four texels of a bilinear sample now take two of each, and the CLUT offset of a mip level is found once per sample. The UV planes at a quad's four pixels are one sum plus steps, as for depth. Co-Authored-By: Claude Opus 5.5 (1M context) --- GPU/Software/Rasterizer.cpp | 21 +++++++-- GPU/Software/Sampler.cpp | 94 ++++++++++++++++++++++++++----------- 2 files changed, 83 insertions(+), 32 deletions(-) diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 7c32858497..e09763e7a8 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -1211,10 +1211,23 @@ static inline Vec4 LodQFromPlanes(const UVPlanes &planes, int64_t centerX return q; } +// A plane's values at a quad's four pixel centers: a pixel's step adds k * 16 to the sum before the shift. +static inline void PlaneQuad(const DepthPlane &plane, int64_t centerX, int64_t centerY, int64_t out[4]) { + const int64_t v = plane.base + plane.kx * centerX + plane.ky * centerY; + const int64_t dx = plane.kx * SCREEN_SCALE_FACTOR, dy = plane.ky * SCREEN_SCALE_FACTOR; + out[0] = v >> 14; + out[1] = (v + dx) >> 14; + out[2] = (v + dy) >> 14; + out[3] = (v + dx + dy) >> 14; +} + static inline void GetTextureCoordinatesGE(const UVPlanes &planes, int64_t centerX, int64_t centerY, Vec4 &s, Vec4 &t, Vec4 &qOut) { + int64_t qs[4], ss[4], ts[4]; + PlaneQuad(planes.q, centerX, centerY, qs); + PlaneQuad(planes.s, centerX, centerY, ss); + PlaneQuad(planes.t, centerX, centerY, ts); for (int i = 0; i < 4; ++i) { - const int64_t x = centerX + (i & 1) * SCREEN_SCALE_FACTOR, y = centerY + (i >> 1) * SCREEN_SCALE_FACTOR; - const float q = TruncateToFloat24((float)((double)planes.q.At(x, y) * planes.scaleQ)); + const float q = TruncateToFloat24((float)((double)qs[i] * planes.scaleQ)); qOut[i] = q; if (!(q > 0.0f)) { s[i] = 0.0f; @@ -1222,8 +1235,8 @@ static inline void GetTextureCoordinatesGE(const UVPlanes &planes, int64_t cente continue; } const double r = GERecip(q); - s[i] = GEUVProduct((double)TruncateToFloat24((float)((double)planes.s.At(x, y) * planes.scaleS)) * r); - t[i] = GEUVProduct((double)TruncateToFloat24((float)((double)planes.t.At(x, y) * planes.scaleT)) * r); + s[i] = GEUVProduct((double)TruncateToFloat24((float)((double)ss[i] * planes.scaleS)) * r); + t[i] = GEUVProduct((double)TruncateToFloat24((float)((double)ts[i] * planes.scaleT)) * r); } } diff --git a/GPU/Software/Sampler.cpp b/GPU/Software/Sampler.cpp index d3a1f1d3ed..fd03534148 100644 --- a/GPU/Software/Sampler.cpp +++ b/GPU/Software/Sampler.cpp @@ -800,9 +800,7 @@ static inline uint64_t LerpSpread(uint64_t a, uint64_t b, int f) { } template -static inline uint32_t LookupClutT(uint32_t index, int level, const SamplerID &samplerID, bool clut4) { - if (!samplerID.useSharedClut) - index += clut4 ? level * 16 : (level & 1) * 256; +static inline uint32_t LookupClutT(uint32_t index, const SamplerID &samplerID) { switch (clutFmt) { case GE_CMODE_16BIT_BGR5650: return RGB565ToRGBA8888(samplerID.cached.clut16[index]); case GE_CMODE_16BIT_ABGR5551: return RGBA5551ToRGBA8888(samplerID.cached.clut16[index]); @@ -811,32 +809,54 @@ static inline uint32_t LookupClutT(uint32_t index, int level, const SamplerID &s } } -template -static inline uint32_t ReadTexelT(const u8 *src, int bufw, int u, int v, int level, const SamplerID &samplerID) { +template +static constexpr uint32_t TexelBitsT() { switch (fmt) { - case GE_TFMT_4444: return RGBA4444ToRGBA8888(*(const u16 *)(src + GetPixelDataOffset<16>(bufw, u, v, swizzled))); - case GE_TFMT_5551: return RGBA5551ToRGBA8888(*(const u16 *)(src + GetPixelDataOffset<16>(bufw, u, v, swizzled))); - case GE_TFMT_5650: return RGB565ToRGBA8888(*(const u16 *)(src + GetPixelDataOffset<16>(bufw, u, v, swizzled))); - case GE_TFMT_8888: return *(const u32 *)(src + GetPixelDataOffset<32>(bufw, u, v, swizzled)); - case GE_TFMT_CLUT32: { - const u8 *p = src + GetPixelDataOffset<32>(bufw, u, v, swizzled); - const u32 val = p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24); - return LookupClutT(TransformClutIndex(val, samplerID), 0, samplerID, false); + case GE_TFMT_8888: case GE_TFMT_CLUT32: return 32; + case GE_TFMT_CLUT8: return 8; + case GE_TFMT_CLUT4: return 4; + default: return 16; } - case GE_TFMT_CLUT16: { - const u8 *p = src + GetPixelDataOffset<16>(bufw, u, v, swizzled); - return LookupClutT(TransformClutIndex(p[0] + (p[1] << 8), samplerID), 0, samplerID, false); - } - case GE_TFMT_CLUT8: - return LookupClutT(TransformClutIndex(src[GetPixelDataOffset<8>(bufw, u, v, swizzled)], samplerID), level, samplerID, false); - case GE_TFMT_CLUT4: { - const u8 b = src[GetPixelDataOffset<4>(bufw, u, v, swizzled)]; - return LookupClutT(TransformClutIndex((u & 1) ? (b >> 4) : (b & 0xF), samplerID), level, samplerID, true); +} + +// A texel's byte offset is the sum of one for its row and one for its column (as GetPixelDataOffset), so +// the four texels of a bilinear sample only need two of each. +template +static inline uint32_t RowOffsetT(int bufw, int v) { + if (!swizzled) + return v * (bufw * bits >> 3); + // Blocks of 16 bytes by 8 rows. + return (v >> 3) * ((bufw * bits / 32) * 32) + (v & 7) * 16; +} + +template +static inline uint32_t ColumnOffsetT(int u) { + const uint32_t b = u * bits >> 3; + return swizzled ? (b >> 4) * 128 + (b & 15) : b; +} + +// The texel at p (u picks the nibble of CLUT4). clutOffset is where the level's palette starts. +template +static inline uint32_t ReadTexelT(const u8 *p, int u, uint32_t clutOffset, const SamplerID &samplerID) { + switch (fmt) { + case GE_TFMT_4444: return RGBA4444ToRGBA8888(*(const u16 *)p); + case GE_TFMT_5551: return RGBA5551ToRGBA8888(*(const u16 *)p); + case GE_TFMT_5650: return RGB565ToRGBA8888(*(const u16 *)p); + case GE_TFMT_8888: return *(const u32 *)p; + case GE_TFMT_CLUT32: return LookupClutT(TransformClutIndex(p[0] + (p[1] << 8) + (p[2] << 16) + (p[3] << 24), samplerID) + clutOffset, samplerID); + case GE_TFMT_CLUT16: return LookupClutT(TransformClutIndex(p[0] + (p[1] << 8), samplerID) + clutOffset, samplerID); + case GE_TFMT_CLUT8: return LookupClutT(TransformClutIndex(*p, samplerID) + clutOffset, samplerID); + case GE_TFMT_CLUT4: return LookupClutT(TransformClutIndex((u & 1) ? (*p >> 4) : (*p & 0xF), samplerID) + clutOffset, samplerID); + default: return 0; } +} + +template +static inline uint32_t ReadDXTTexelT(const u8 *src, int bufw, int u, int v) { + switch (fmt) { case GE_TFMT_DXT1: return GetDXT1Texel((const DXT1Block *)src + (v >> 2) * (bufw >> 2) + (u >> 2), u & 3, v & 3); case GE_TFMT_DXT3: return GetDXT3Texel((const DXT3Block *)src + (v >> 2) * (bufw >> 2) + (u >> 2), u & 3, v & 3); - case GE_TFMT_DXT5: return GetDXT5Texel((const DXT5Block *)src + (v >> 2) * (bufw >> 2) + (u >> 2), u & 3, v & 3); - default: return 0; + default: return GetDXT5Texel((const DXT5Block *)src + (v >> 2) * (bufw >> 2) + (u >> 2), u & 3, v & 3); } } @@ -863,10 +883,28 @@ static inline uint64_t SampleLinearLevelT(float s, float t, const u8 *tptr, int int u0, u1, v0, v1, fracU, fracV; TexelPairT(s, samplerID.cached.sizes[level].w, samplerID.clampS, u0, u1, fracU); TexelPairT(t, samplerID.cached.sizes[level].h, samplerID.clampT, v0, v1, fracV); - const uint64_t tl = SpreadRGBA(ReadTexelT(tptr, bufw, u0, v0, level, samplerID)); - const uint64_t tr = SpreadRGBA(ReadTexelT(tptr, bufw, u1, v0, level, samplerID)); - const uint64_t bl = SpreadRGBA(ReadTexelT(tptr, bufw, u0, v1, level, samplerID)); - const uint64_t br = SpreadRGBA(ReadTexelT(tptr, bufw, u1, v1, level, samplerID)); + uint64_t tl, tr, bl, br; + if constexpr (fmt == GE_TFMT_DXT1 || fmt == GE_TFMT_DXT3 || fmt == GE_TFMT_DXT5) { + tl = SpreadRGBA(ReadDXTTexelT(tptr, bufw, u0, v0)); + tr = SpreadRGBA(ReadDXTTexelT(tptr, bufw, u1, v0)); + bl = SpreadRGBA(ReadDXTTexelT(tptr, bufw, u0, v1)); + br = SpreadRGBA(ReadDXTTexelT(tptr, bufw, u1, v1)); + } else { + constexpr uint32_t bits = TexelBitsT(); + // Levels past the first have their own palette unless it's shared: 16 entries on for CLUT4, 256 on for + // CLUT8 at odd levels. + uint32_t clutOffset = 0; + if ((fmt == GE_TFMT_CLUT4 || fmt == GE_TFMT_CLUT8) && !samplerID.useSharedClut) + clutOffset = fmt == GE_TFMT_CLUT4 ? level * 16 : (level & 1) * 256; + const u8 *row0 = tptr + RowOffsetT(bufw, v0); + const u8 *row1 = tptr + RowOffsetT(bufw, v1); + const uint32_t col0 = ColumnOffsetT(u0); + const uint32_t col1 = ColumnOffsetT(u1); + tl = SpreadRGBA(ReadTexelT(row0 + col0, u0, clutOffset, samplerID)); + tr = SpreadRGBA(ReadTexelT(row0 + col1, u1, clutOffset, samplerID)); + bl = SpreadRGBA(ReadTexelT(row1 + col0, u0, clutOffset, samplerID)); + br = SpreadRGBA(ReadTexelT(row1 + col1, u1, clutOffset, samplerID)); + } // Like the GE: horizontal lerps truncated to 8 bits, then the vertical one (gpu/probe exp52). return LerpSpread(LerpSpread(tl, tr, fracU), LerpSpread(bl, br, fracU), fracV); }