diff --git a/GPU/Software/BinManager.cpp b/GPU/Software/BinManager.cpp index c4d637b0cf..b6c76ece3f 100644 --- a/GPU/Software/BinManager.cpp +++ b/GPU/Software/BinManager.cpp @@ -1106,20 +1106,19 @@ bool BinManager::NeedsOrder(const BinItem &item) { return false; } -// Each extra tile a triangle touches sets it up again, so tiles are as large as they can be with plenty to -// spare for every thread: at least sixteen per thread over a 480x272 screen. With fewer, a scene whose -// drawing is concentrated on part of the screen leaves threads idle (LocoRoco took a third longer with four -// per thread on eight threads). +// Each extra tile a triangle touches sets it up again, so tiles are as large as they can be with enough to +// spare for every thread: at least eight per thread over a 480x272 screen (64x32 on eight threads). With +// fewer, a scene whose drawing is concentrated on part of the screen leaves threads idle: four per thread +// used the least CPU but took longer in wall time than eight in most games, Wipeout longer than sixteen. // The multiplier trades the repeated setup against the load balance, so the best one depends on what drawing -// a pixel costs. When that gets cheaper, larger tiles can win: with faster span drawing, four per thread was -// as fast in wall time and used less CPU. Measure again (Tools/headless_bench.py, wall and CPU time on -// several games) after changes to the per-pixel cost. +// a pixel costs (sixteen was best before the span drawing got faster). Measure again +// (Tools/headless_bench.py, wall and CPU time on several games) after changes to the per-pixel cost. void BinManager::PickTileSize(int threads) { static const int sizes[][2] = { { 128, 32 }, { 64, 32 }, { 64, 16 } }; int w = MIN_TILE_W, h = MIN_TILE_H; for (const auto &size : sizes) { const int tiles = ((480 + size[0] - 1) / size[0]) * ((272 + size[1] - 1) / size[1]); - if (tiles >= 16 * threads) { + if (tiles >= 8 * threads) { w = size[0]; h = size[1]; break; diff --git a/GPU/Software/Rasterizer.cpp b/GPU/Software/Rasterizer.cpp index 7463de14ed..213de1fc9a 100644 --- a/GPU/Software/Rasterizer.cpp +++ b/GPU/Software/Rasterizer.cpp @@ -720,7 +720,13 @@ static inline void ApplyTexturing(const RasterizerState &state, int *colors, int int levelFrac; bool bilinear; bool perPixel = state.TexLevelMode() == GE_TEXLEVEL_MODE_SLOPE || (state.TexLevelMode() == GE_TEXLEVEL_MODE_AUTO && autoGrad >= 0.0f); - if (!perPixel) { + if (state.maxTexLevel == 0 && state.minFilt == state.magFilt) { + // Nothing depends on the level of detail: level 0, and either filter is the same. + level = 0; + levelFrac = 0; + bilinear = state.magFilt; + perPixel = false; + } else if (!perPixel) { CalculateSamplingParams(ds, dt, 0.0f, state, level, levelFrac, bilinear); } else if (sameQ) { CalculateSamplingParams(ds, dt, q[0], state, level, levelFrac, bilinear, autoGrad); @@ -1272,6 +1278,8 @@ static void DrawStagedSpans(const StagedSpans &ctx, int count, int chunkX, int64 const RasterizerState &state = *ctx.state; const UVPlanes &uvPlanes = *ctx.uvPlanes; const int64_t qdx = uvPlanes.q.kx * SCREEN_SCALE_FACTOR, sdx = uvPlanes.s.kx * SCREEN_SCALE_FACTOR, tdx = uvPlanes.t.kx * SCREEN_SCALE_FACTOR; + // No mip levels and one filter, bilinear: what ApplyTexturing comes to is the quad sampler at level 0. + const bool directQuad = textured && state.linearQuad && state.maxTexLevel == 0 && state.minFilt == state.magFilt && state.magFilt; for (int k = 0; k < count; k += 4, qv += 4 * qdx, sv += 4 * sdx, tv += 4 * tdx) { const Vec4 mask(maskBuf[k], maskBuf[k + 1], maskBuf[k + 2], maskBuf[k + 3]); if (!AnyMask(mask)) @@ -1301,7 +1309,12 @@ static void DrawStagedSpans(const StagedSpans &ctx, int count, int chunkX, int64 s *= 1.0f / (float)(1 << state.samplerID.width0Shift); t *= 1.0f / (float)(1 << state.samplerID.height0Shift); } - ApplyTexturing(state, prim_color, STAGED_CHUNK, mask, s, t, q, 0.0f, 0.0f, ctx.sameQ, ctx.autoGrad); + if (directQuad) { + const int active = (mask[0] >= 0 ? 1 : 0) | (mask[1] >= 0 ? 2 : 0) | (mask[2] >= 0 ? 4 : 0) | (mask[3] >= 0 ? 8 : 0); + state.linearQuad(s.AsArray(), t.AsArray(), nullptr, nullptr, active, state.texptr, state.texbufw, prim_color, STAGED_CHUNK, state.samplerID); + } else { + ApplyTexturing(state, prim_color, STAGED_CHUNK, mask, s, t, q, 0.0f, 0.0f, ctx.sameQ, ctx.autoGrad); + } } if (ctx.addSecondary) { for (int c = 0; c < 3; ++c) { diff --git a/GPU/Software/Sampler.cpp b/GPU/Software/Sampler.cpp index 216c7219a4..068a7879e1 100644 --- a/GPU/Software/Sampler.cpp +++ b/GPU/Software/Sampler.cpp @@ -962,32 +962,25 @@ static inline Vec4S32 ColumnOffsets4(Vec4S32 u) { return swizzled ? b.Shr<4>().Shl<7>() + (b & Vec4S32::Splat(15)) : b; } -// SampleLinearLevelT for four pixels at the same level: the coordinates and offsets in vector lanes, the -// texel reads one by one. +// The four texels of SampleLinearLevelT for four pixels at the same level (texels[tl, tr, bl, br][pixel]), +// the coordinates and offsets in vector lanes, the texel reads one by one. template -static inline void SampleLinearLevel4T(Vec4F32 s, Vec4F32 t, const u8 *tptr, int bufw, int level, const SamplerID &samplerID, uint64_t out[4]) { - if (!tptr) { - out[0] = out[1] = out[2] = out[3] = 0; - return; - } - Vec4S32 u0, u1, v0, v1, fracU, fracV; +static inline void ReadTexels4T(Vec4F32 s, Vec4F32 t, const u8 *tptr, int bufw, int level, const SamplerID &samplerID, uint32_t texels[4][4], Vec4S32 &fracU, Vec4S32 &fracV) { + Vec4S32 u0, u1, v0, v1; TexelPairs4(s, samplerID.cached.sizes[level].w, samplerID.clampS, u0, u1, fracU); TexelPairs4(t, samplerID.cached.sizes[level].h, samplerID.clampT, v0, v1, fracV); - alignas(16) int us0[4], us1[4], fu[4], fv[4]; + alignas(16) int us0[4], us1[4]; u0.Store(us0); u1.Store(us1); - fracU.Store(fu); - fracV.Store(fv); - uint32_t texels[4][4]; if constexpr (fmt == GE_TFMT_DXT1 || fmt == GE_TFMT_DXT3 || fmt == GE_TFMT_DXT5) { alignas(16) int vs0[4], vs1[4]; v0.Store(vs0); v1.Store(vs1); for (int i = 0; i < 4; ++i) { - texels[i][0] = ReadDXTTexelT(tptr, bufw, us0[i], vs0[i]); - texels[i][1] = ReadDXTTexelT(tptr, bufw, us1[i], vs0[i]); - texels[i][2] = ReadDXTTexelT(tptr, bufw, us0[i], vs1[i]); - texels[i][3] = ReadDXTTexelT(tptr, bufw, us1[i], vs1[i]); + texels[0][i] = ReadDXTTexelT(tptr, bufw, us0[i], vs0[i]); + texels[1][i] = ReadDXTTexelT(tptr, bufw, us1[i], vs0[i]); + texels[2][i] = ReadDXTTexelT(tptr, bufw, us0[i], vs1[i]); + texels[3][i] = ReadDXTTexelT(tptr, bufw, us1[i], vs1[i]); } } else { constexpr uint32_t bits = TexelBitsT(); @@ -1002,20 +995,56 @@ static inline void SampleLinearLevel4T(Vec4F32 s, Vec4F32 t, const u8 *tptr, int (row1 + col0).Store(bl); (row1 + col1).Store(br); for (int i = 0; i < 4; ++i) { - texels[i][0] = ReadTexelT(tptr + (uint32_t)tl[i], us0[i], clutOffset, samplerID); - texels[i][1] = ReadTexelT(tptr + (uint32_t)tr[i], us1[i], clutOffset, samplerID); - texels[i][2] = ReadTexelT(tptr + (uint32_t)bl[i], us0[i], clutOffset, samplerID); - texels[i][3] = ReadTexelT(tptr + (uint32_t)br[i], us1[i], clutOffset, samplerID); + texels[0][i] = ReadTexelT(tptr + (uint32_t)tl[i], us0[i], clutOffset, samplerID); + texels[1][i] = ReadTexelT(tptr + (uint32_t)tr[i], us1[i], clutOffset, samplerID); + texels[2][i] = ReadTexelT(tptr + (uint32_t)bl[i], us0[i], clutOffset, samplerID); + texels[3][i] = ReadTexelT(tptr + (uint32_t)br[i], us1[i], clutOffset, samplerID); } } - for (int i = 0; i < 4; ++i) { - // Like the GE: horizontal lerps truncated to 8 bits, then the vertical one (gpu/probe exp52). - const uint64_t top = LerpSpread(SpreadRGBA(texels[i][0]), SpreadRGBA(texels[i][1]), fu[i]); - const uint64_t bot = LerpSpread(SpreadRGBA(texels[i][2]), SpreadRGBA(texels[i][3]), fu[i]); - out[i] = LerpSpread(top, bot, fv[i]); - } } +// LerpSpread of one channel in vector lanes. +static inline Vec4S32 LerpLanes(Vec4S32 a, Vec4S32 b, Vec4S32 f) { + return (a * (Vec4S32::Splat(16) - f) + b * f).Shr<4>(); +} + +// SampleLinearLevelT for four pixels at the same level, a channel per vector (as TextureFunction4 takes them). +template +static inline void SampleLinearLevel4LanesT(Vec4F32 s, Vec4F32 t, const u8 *tptr, int bufw, int level, const SamplerID &samplerID, Vec4S32 out[4]) { + if (!tptr) { + out[0] = out[1] = out[2] = out[3] = Vec4S32::Zero(); + return; + } + alignas(16) uint32_t texels[4][4]; + Vec4S32 fracU, fracV; + ReadTexels4T(s, t, tptr, bufw, level, samplerID, texels, fracU, fracV); + // Two channels per lane, R and B (G and A) as 16-bit halves: no lerp of 8-bit values exceeds 16 bits, + // so the halves don't carry into each other, and masking after each shift truncates both to 8 bits. + const Vec4S32 tl = Vec4S32::LoadAligned((const int *)texels[0]), tr = Vec4S32::LoadAligned((const int *)texels[1]); + const Vec4S32 bl = Vec4S32::LoadAligned((const int *)texels[2]), br = Vec4S32::LoadAligned((const int *)texels[3]); + const Vec4S32 halves = Vec4S32::Splat(0x00FF00FF); + const Vec4S32 invU = Vec4S32::Splat(16) - fracU, invV = Vec4S32::Splat(16) - fracV; + auto lerp = [&](Vec4S32 a, Vec4S32 b, Vec4S32 inv, Vec4S32 f) { + return (a.Mul(inv) + b.Mul(f)).Shr<4>() & halves; + }; + Vec4S32 pairs[2]; + for (int h = 0; h < 2; ++h) { + auto part = [&](Vec4S32 v) { + return (h == 0 ? v : v.Shr<8>()) & halves; + }; + // Like the GE: horizontal lerps truncated to 8 bits, then the vertical one (gpu/probe exp52). + const Vec4S32 top = lerp(part(tl), part(tr), invU, fracU); + const Vec4S32 bot = lerp(part(bl), part(br), invU, fracU); + pairs[h] = lerp(top, bot, invV, fracV); + } + const Vec4S32 byteMask = Vec4S32::Splat(0xFF); + out[0] = pairs[0] & byteMask; + out[1] = pairs[1] & byteMask; + out[2] = pairs[0].Shr<16>(); + out[3] = pairs[1].Shr<16>(); +} + + // GetTextureFunctionOutput for four pixels, one per lane: prim and tex are R, G, B and A (all 0 to 255). static inline void TextureFunction4(const Vec4S32 prim[4], const Vec4S32 tex[4], const SamplerID &samplerID, Vec4S32 out[4]) { const bool rgba = samplerID.useTextureAlpha; @@ -1077,34 +1106,31 @@ static void SOFTRAST_CALL SampleLinearQuadT(const float *s, const float *t, cons if (!active) return; uint64_t c[4]; - // Usually all at one level (a span of a triangle always is): then in vector lanes. + // Usually all at one level (a span of a triangle always is): then in vector lanes. No level arrays: all at + // level 0, without a fraction. const int first = active & 1 ? 0 : active & 2 ? 1 : active & 4 ? 2 : 3; bool sameLevel = true; - for (int i = first + 1; i < 4; ++i) { - if ((active & (1 << i)) && (level[i] != level[first] || levelFrac[i] != levelFrac[first])) - sameLevel = false; + if (level) { + for (int i = first + 1; i < 4; ++i) { + if ((active & (1 << i)) && (level[i] != level[first] || levelFrac[i] != levelFrac[first])) + sameLevel = false; + } } if (sameLevel) { - const int l = level[first]; + const int l = level ? level[first] : 0; const Vec4F32 sv = Vec4F32::Load(s), tv = Vec4F32::Load(t); - SampleLinearLevel4T(sv, tv, texptr[l], texbufw[l], l, samplerID, c); - if (levelFrac[first]) { - uint64_t c1[4]; - SampleLinearLevel4T(sv, tv, texptr[l + 1], texbufw[l + 1], l + 1, samplerID, c1); - for (int i = 0; i < 4; ++i) - c[i] = LerpSpread(c[i], c1[i], levelFrac[first]); + Vec4S32 prim[4], tex[4], out[4]; + SampleLinearLevel4LanesT(sv, tv, texptr[l], texbufw[l], l, samplerID, tex); + if (level && levelFrac[first]) { + Vec4S32 tex1[4]; + SampleLinearLevel4LanesT(sv, tv, texptr[l + 1], texbufw[l + 1], l + 1, samplerID, tex1); + const Vec4S32 f = Vec4S32::Splat(levelFrac[first]); + for (int ch = 0; ch < 4; ++ch) + tex[ch] = LerpLanes(tex[ch], tex1[ch], f); } // The texture function, in lanes too. - alignas(16) int texLanes[4][4]; - for (int i = 0; i < 4; ++i) { - for (int ch = 0; ch < 4; ++ch) - texLanes[ch][i] = (int)((c[i] >> (16 * ch)) & 0xFFFF); - } - Vec4S32 prim[4], tex[4], out[4]; - for (int ch = 0; ch < 4; ++ch) { + for (int ch = 0; ch < 4; ++ch) prim[ch] = Vec4S32::Load(colors + ch * colorStride); - tex[ch] = Vec4S32::Load(texLanes[ch]); - } TextureFunction4(prim, tex, samplerID, out); alignas(16) static const int laneBits[4] = { 1, 2, 4, 8 }; const Vec4S32 keep = (Vec4S32::Splat(active) & Vec4S32::LoadAligned(laneBits)).CompareEq(Vec4S32::Zero()); diff --git a/GPU/Software/Sampler.h b/GPU/Software/Sampler.h index 6e77dfe034..b4124559a7 100644 --- a/GPU/Software/Sampler.h +++ b/GPU/Software/Sampler.h @@ -48,7 +48,7 @@ LinearFunc GetLinearFunc(SamplerID id, BinManager *binner); // Bilinear samples for the pixels of a quad with bit i of active set, each at its own level, through the // texture function: colors goes in as the primitive's colors and comes out textured. A channel at a time, -// as for SpanFunc. +// as for SpanFunc. level and levelFrac nullptr: all at level 0. typedef void (SOFTRAST_CALL *LinearQuadFunc)(const float *s, const float *t, const int *level, const int *levelFrac, int active, const u8 *const *texptr, const uint16_t *texbufw, int *colors, int colorStride, const SamplerID &samplerID); // Only for the samplers without a JIT (linear is what GetLinearFunc returned), nullptr otherwise. LinearQuadFunc GetLinearQuadFunc(const SamplerID &id, LinearFunc linear);