mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
Merge pull request #22467 from hrydgard/softgpu-sampling
SoftGPU: Optimize texture sampling (no-JIT case)
This commit is contained in:
4 files changed
+95
-57
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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<int> mask(maskBuf[k], maskBuf[k + 1], maskBuf[k + 2], maskBuf[k + 3]);
|
||||
if (!AnyMask<false>(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) {
|
||||
|
||||
+72
-46
@@ -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 <GETextureFormat fmt, bool swizzled, GEPaletteFormat clutFmt>
|
||||
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<fmt>(tptr, bufw, us0[i], vs0[i]);
|
||||
texels[i][1] = ReadDXTTexelT<fmt>(tptr, bufw, us1[i], vs0[i]);
|
||||
texels[i][2] = ReadDXTTexelT<fmt>(tptr, bufw, us0[i], vs1[i]);
|
||||
texels[i][3] = ReadDXTTexelT<fmt>(tptr, bufw, us1[i], vs1[i]);
|
||||
texels[0][i] = ReadDXTTexelT<fmt>(tptr, bufw, us0[i], vs0[i]);
|
||||
texels[1][i] = ReadDXTTexelT<fmt>(tptr, bufw, us1[i], vs0[i]);
|
||||
texels[2][i] = ReadDXTTexelT<fmt>(tptr, bufw, us0[i], vs1[i]);
|
||||
texels[3][i] = ReadDXTTexelT<fmt>(tptr, bufw, us1[i], vs1[i]);
|
||||
}
|
||||
} else {
|
||||
constexpr uint32_t bits = TexelBitsT<fmt>();
|
||||
@@ -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<fmt, clutFmt>(tptr + (uint32_t)tl[i], us0[i], clutOffset, samplerID);
|
||||
texels[i][1] = ReadTexelT<fmt, clutFmt>(tptr + (uint32_t)tr[i], us1[i], clutOffset, samplerID);
|
||||
texels[i][2] = ReadTexelT<fmt, clutFmt>(tptr + (uint32_t)bl[i], us0[i], clutOffset, samplerID);
|
||||
texels[i][3] = ReadTexelT<fmt, clutFmt>(tptr + (uint32_t)br[i], us1[i], clutOffset, samplerID);
|
||||
texels[0][i] = ReadTexelT<fmt, clutFmt>(tptr + (uint32_t)tl[i], us0[i], clutOffset, samplerID);
|
||||
texels[1][i] = ReadTexelT<fmt, clutFmt>(tptr + (uint32_t)tr[i], us1[i], clutOffset, samplerID);
|
||||
texels[2][i] = ReadTexelT<fmt, clutFmt>(tptr + (uint32_t)bl[i], us0[i], clutOffset, samplerID);
|
||||
texels[3][i] = ReadTexelT<fmt, clutFmt>(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 <GETextureFormat fmt, bool swizzled, GEPaletteFormat clutFmt>
|
||||
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<fmt, swizzled, clutFmt>(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<fmt, swizzled, clutFmt>(sv, tv, texptr[l], texbufw[l], l, samplerID, c);
|
||||
if (levelFrac[first]) {
|
||||
uint64_t c1[4];
|
||||
SampleLinearLevel4T<fmt, swizzled, clutFmt>(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<fmt, swizzled, clutFmt>(sv, tv, texptr[l], texbufw[l], l, samplerID, tex);
|
||||
if (level && levelFrac[first]) {
|
||||
Vec4S32 tex1[4];
|
||||
SampleLinearLevel4LanesT<fmt, swizzled, clutFmt>(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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user