mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-14 15:00:30 +02:00
SoftGPU: Skin a vertex without per-operation fallback checks
Every GEAdd4 and GEMulFloat24x4 checked its lanes for the cases it can't do and branched to a fallback, a vector-to-scalar move and a branch per operation, about 14% of skinning. The unchecked variants only collect those lanes; a vertex where any appears is skinned again with the checked ones. The products use GEMulFloat24x4 (the same as a one-term GERowSum4 here), and the position's and normal's add chains are interleaved. Skinning is about 20% faster. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
d272703721
commit
a14712c65e
2 files changed
+73
-47
No files matched your search
+22
-4
@@ -251,7 +251,8 @@ Vec4F32 GEAdd4Fallback(Vec4F32 a, Vec4F32 b, Vec4S32 lanes, Vec4F32 result);
|
||||
|
||||
// Four of GEAdd at once: each term goes to 16-bit fixed point at the larger exponent by scaling with a power
|
||||
// of two and truncating toward zero, which drops what GEAdd's masks clear, then the sum is exact.
|
||||
inline Vec4F32 GEAdd4(Vec4F32 a, Vec4F32 b) {
|
||||
// GEAdd4 without its fallback: the lanes that need it are added to bad, and their results are wrong.
|
||||
inline Vec4F32 GEAdd4Unchecked(Vec4F32 a, Vec4F32 b, Vec4S32 &bad) {
|
||||
const Vec4S32 expMask = Vec4S32::Splat(0x7F800000);
|
||||
const Vec4S32 ba = Vec4S32FromBits(a);
|
||||
const Vec4S32 bb = Vec4S32FromBits(b);
|
||||
@@ -269,7 +270,13 @@ inline Vec4F32 GEAdd4(Vec4F32 a, Vec4F32 b) {
|
||||
const Vec4S32 none = e.CompareEq(Vec4S32::Zero());
|
||||
const Vec4F32 sum = Vec4F32FromS32(Vec4S32FromF32(za * down) + Vec4S32FromF32(zb * down)) * up;
|
||||
const Vec4F32 result = Vec4F32FromBits(Vec4S32FromBits(sum).AndNot(none));
|
||||
const Vec4S32 outside = e.CompareLt(Vec4S32::Splat(16 << 23)).AndNot(none) | e.CompareEq(expMask);
|
||||
bad = bad | e.CompareLt(Vec4S32::Splat(16 << 23)).AndNot(none) | e.CompareEq(expMask);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Vec4F32 GEAdd4(Vec4F32 a, Vec4F32 b) {
|
||||
Vec4S32 outside = Vec4S32::Zero();
|
||||
const Vec4F32 result = GEAdd4Unchecked(a, b, outside);
|
||||
if (AnyCompareBitsSet(outside))
|
||||
return GEAdd4Fallback(a, b, outside, result);
|
||||
return result;
|
||||
@@ -282,7 +289,8 @@ Vec4F32 GEMulFloat24x4Fallback(Vec4F32 a, Vec4F32 b, Vec4S32 lanes, Vec4F32 resu
|
||||
// Per lane, the product of a and b truncated to float24, as ProductToFloat24((double)a * b) with both
|
||||
// truncated to float24 first, except that a zero operand gives +0. With 15 fraction bits each, the
|
||||
// significand product truncated to 17 bits is 32768 + a' + b' + (a' b' >> 15), as in GERowSum4.
|
||||
inline Vec4F32 GEMulFloat24x4(Vec4F32 a, Vec4F32 b) {
|
||||
// GEMulFloat24x4 without its fallback, as GEAdd4Unchecked.
|
||||
inline Vec4F32 GEMulFloat24x4Unchecked(Vec4F32 a, Vec4F32 b, Vec4S32 &bad) {
|
||||
const Vec4S32 expMask = Vec4S32::Splat(0x7F800000);
|
||||
const Vec4S32 fracMask = Vec4S32::Splat(0x7FFF);
|
||||
const Vec4S32 aBits = Vec4S32FromBits(a) & Vec4S32::Splat((int)0xFFFFFF00);
|
||||
@@ -300,7 +308,13 @@ inline Vec4F32 GEMulFloat24x4(Vec4F32 a, Vec4F32 b) {
|
||||
const Vec4S32 zero = (aBits & Vec4S32::Splat(0x7FFFFFFF)).CompareEq(Vec4S32::Zero()) | (bBits & Vec4S32::Splat(0x7FFFFFFF)).CompareEq(Vec4S32::Zero());
|
||||
const Vec4F32 result = Vec4F32FromBits((((fbits + lsb.Shl<23>()) & Vec4S32::Splat((int)0xFFFFFF00)) | sign).AndNot(zero));
|
||||
const Vec4S32 special = ea.CompareEq(Vec4S32::Zero()) | eb.CompareEq(Vec4S32::Zero()) | ea.CompareEq(Vec4S32::Splat(255)) | eb.CompareEq(Vec4S32::Splat(255));
|
||||
const Vec4S32 outside = (special | resultExp.CompareLt(Vec4S32::Splat(1)) | resultExp.CompareGt(Vec4S32::Splat(254))).AndNot(zero);
|
||||
bad = bad | (special | resultExp.CompareLt(Vec4S32::Splat(1)) | resultExp.CompareGt(Vec4S32::Splat(254))).AndNot(zero);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Vec4F32 GEMulFloat24x4(Vec4F32 a, Vec4F32 b) {
|
||||
Vec4S32 outside = Vec4S32::Zero();
|
||||
const Vec4F32 result = GEMulFloat24x4Unchecked(a, b, outside);
|
||||
if (AnyCompareBitsSet(outside))
|
||||
return GEMulFloat24x4Fallback(a, b, outside, result);
|
||||
return result;
|
||||
@@ -310,6 +324,10 @@ inline Vec4F32 GEMulFloat24x4(Vec4F32 a, Vec4F32 b) {
|
||||
inline Vec4F32 GEAddFloat24x4(Vec4F32 a, Vec4F32 b) {
|
||||
return TruncateToFloat24x4(GEAdd4(a, b));
|
||||
}
|
||||
|
||||
inline Vec4F32 GEAddFloat24x4Unchecked(Vec4F32 a, Vec4F32 b, Vec4S32 &bad) {
|
||||
return TruncateToFloat24x4(GEAdd4Unchecked(a, b, bad));
|
||||
}
|
||||
float GEDot(const Vec3f &a, const Vec3f &b);
|
||||
float GENormalize(Vec3f &v);
|
||||
|
||||
|
||||
@@ -616,57 +616,65 @@ static float GEMorphComponent(const VertexDecoder &dec, const u8 *in, int off, i
|
||||
static void ApplyGESkinning(u8 *decoded, const VertexDecoder &dec, const u8 *raw, int count) {
|
||||
const DecVtxFormat &fmt = dec.GetDecVtxFmt();
|
||||
const int nweights = dec.nweights;
|
||||
// The x, y and z components go in lanes 0-2. Each one is a chain of GEAdds in a fixed order: per bone, the
|
||||
// translation, then the three products. GEAdd drops a GEProduct's 17th bit, so float24 products (one-term
|
||||
// row sums) give the same sums.
|
||||
auto product = [](float a, Vec4F32 b) {
|
||||
return GERowSum4<1>(Vec4F32::Splat(a), &b);
|
||||
};
|
||||
const bool hasPos = dec.pos != 0;
|
||||
const bool hasNrm = dec.nrm && fmt.nrmfmt == DEC_FLOAT_3;
|
||||
for (int v = 0; v < count; ++v) {
|
||||
const u8 *in = raw + v * dec.VertexSize();
|
||||
u8 *out = decoded + v * fmt.stride;
|
||||
auto component = [&](int off, int c, auto read) {
|
||||
return dec.morphcount > 1 ? GEMorphComponent(dec, in, off, c, read) : read(in + off, c);
|
||||
};
|
||||
// Each bone's rows (matrix entries 3j..3j+2, the translation j = 3), times its weight. Lane 3 reads past
|
||||
// the row (and past the matrices, for the last translation).
|
||||
Vec4F32 bones[8][4];
|
||||
bool used[8];
|
||||
for (int b = 0; b < nweights; ++b) {
|
||||
const float w = TruncateToFloat24(component(dec.weightoff, b, [&](const u8 *p, int i) { return ReadRawWeight(p, dec.weighttype, i); }));
|
||||
used[b] = w != 0.0f;
|
||||
if (!used[b])
|
||||
continue;
|
||||
const float *m = gstate.boneMatrix + b * 12;
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
bones[b][j] = GEMulFloat24x4(Vec4F32::Splat(w), Vec4F32::Load(m + 3 * j).WithLane3Zero());
|
||||
}
|
||||
}
|
||||
auto skin = [&](const float src[3], bool translate, u8 *dst) {
|
||||
Vec4F32 acc = Vec4F32::Zero();
|
||||
for (int b = 0; b < nweights; ++b) {
|
||||
if (!used[b])
|
||||
continue;
|
||||
if (translate)
|
||||
acc = GEAddFloat24x4(acc, bones[b][3]);
|
||||
for (int j = 0; j < 3; ++j)
|
||||
acc = GEAddFloat24x4(acc, product(src[j], bones[b][j]));
|
||||
}
|
||||
alignas(16) float result[4];
|
||||
acc.Store(result);
|
||||
memcpy(dst, result, 3 * sizeof(float));
|
||||
};
|
||||
if (dec.pos) {
|
||||
float pos[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
float weights[8];
|
||||
for (int b = 0; b < nweights; ++b)
|
||||
weights[b] = TruncateToFloat24(component(dec.weightoff, b, [&](const u8 *p, int i) { return ReadRawWeight(p, dec.weighttype, i); }));
|
||||
float pos[3]{}, nrm[3]{};
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
if (hasPos)
|
||||
pos[i] = component(dec.posoff, i, [&](const u8 *p, int c) { return ReadRawComponent(p, dec.pos, c); });
|
||||
skin(pos, true, out + fmt.posoff);
|
||||
}
|
||||
if (dec.nrm && fmt.nrmfmt == DEC_FLOAT_3) {
|
||||
float nrm[3];
|
||||
for (int i = 0; i < 3; ++i)
|
||||
if (hasNrm)
|
||||
nrm[i] = component(dec.nrmoff, i, [&](const u8 *p, int c) { return ReadRawComponent(p, dec.nrm, c); });
|
||||
skin(nrm, false, out + fmt.nrmoff);
|
||||
}
|
||||
|
||||
// The x, y and z components go in lanes 0-2. Each one is a chain of GEAdds in a fixed order: per bone, the
|
||||
// translation, then the three products. GEAdd drops a GEProduct's 17th bit, so float24 products give the
|
||||
// same sums. The position's and the normal's chains are independent, interleaved.
|
||||
Vec4F32 accPos, accNrm;
|
||||
auto skin = [&](auto mul, auto add) {
|
||||
accPos = Vec4F32::Zero();
|
||||
accNrm = Vec4F32::Zero();
|
||||
for (int b = 0; b < nweights; ++b) {
|
||||
const float w = weights[b];
|
||||
if (w == 0.0f)
|
||||
continue;
|
||||
// The bone's rows (matrix entries 3j..3j+2, the translation j = 3), times its weight.
|
||||
const float *m = gstate.boneMatrix + b * 12;
|
||||
const Vec4F32 wv = Vec4F32::Splat(w);
|
||||
Vec4F32 rows[4];
|
||||
for (int j = 0; j < 4; ++j)
|
||||
rows[j] = mul(wv, Vec4F32::Load(m + 3 * j).WithLane3Zero());
|
||||
accPos = add(accPos, rows[3]);
|
||||
for (int j = 0; j < 3; ++j) {
|
||||
accPos = add(accPos, mul(Vec4F32::Splat(pos[j]), rows[j]));
|
||||
if (hasNrm)
|
||||
accNrm = add(accNrm, mul(Vec4F32::Splat(nrm[j]), rows[j]));
|
||||
}
|
||||
}
|
||||
};
|
||||
// Without the fallbacks first: they're rarely needed, and then the vertex is done again with them.
|
||||
Vec4S32 bad = Vec4S32::Zero();
|
||||
skin([&](Vec4F32 a, Vec4F32 b) { return GEMulFloat24x4Unchecked(a, b, bad); },
|
||||
[&](Vec4F32 a, Vec4F32 b) { return GEAddFloat24x4Unchecked(a, b, bad); });
|
||||
if (AnyCompareBitsSet(bad))
|
||||
skin(GEMulFloat24x4, GEAddFloat24x4);
|
||||
|
||||
alignas(16) float result[4];
|
||||
if (hasPos) {
|
||||
accPos.Store(result);
|
||||
memcpy(out + fmt.posoff, result, 3 * sizeof(float));
|
||||
}
|
||||
if (hasNrm) {
|
||||
accNrm.Store(result);
|
||||
memcpy(out + fmt.nrmoff, result, 3 * sizeof(float));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user