SoftGPU: Signed doubled inverse alpha blend factors

DOUBLEINVSRCALPHA and DOUBLEINVDSTALPHA are 255 - 2a without a clamp: for
a >= 128 the factor is negative and its term subtracts,
-(((2c + 1) * (2|f| + 1)) >> 10), with only the sum clamped. Measured with
every factor and equation against a CPU-filled destination (gpu/probe
exp116, all exact). The x86 jit clamps the factors at 0, so these blends
use the C++ pixel function.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-02 15:09:43 -06:00
1 parent 0ae6385505
commit f2bc80e66e
1 file changed
+39 -5
+39 -5
View File
@@ -445,13 +445,14 @@ static inline Vec3<int> GetSourceFactor(PixelBlendFactor factor, const Vec4<int>
return Vec3<int>::AssignToAll(2 * source.a());
case PixelBlendFactor::DOUBLEINVSRCALPHA:
return Vec3<int>::AssignToAll(255 - std::min(2 * source.a(), 255));
// Can be negative, see SignedBlendingResult().
return Vec3<int>::AssignToAll(255 - 2 * source.a());
case PixelBlendFactor::DOUBLEDSTALPHA:
return Vec3<int>::AssignToAll(2 * dst.a());
case PixelBlendFactor::DOUBLEINVDSTALPHA:
return Vec3<int>::AssignToAll(255 - std::min(2 * dst.a(), 255));
return Vec3<int>::AssignToAll(255 - 2 * dst.a());
case PixelBlendFactor::FIX:
default:
@@ -502,13 +503,14 @@ static inline Vec3<int> GetDestFactor(PixelBlendFactor factor, const Vec4<int> &
return Vec3<int>::AssignToAll(2 * source.a());
case PixelBlendFactor::DOUBLEINVSRCALPHA:
return Vec3<int>::AssignToAll(255 - std::min(2 * source.a(), 255));
// Can be negative, see SignedBlendingResult().
return Vec3<int>::AssignToAll(255 - 2 * source.a());
case PixelBlendFactor::DOUBLEDSTALPHA:
return Vec3<int>::AssignToAll(2 * dst.a());
case PixelBlendFactor::DOUBLEINVDSTALPHA:
return Vec3<int>::AssignToAll(255 - std::min(2 * dst.a(), 255));
return Vec3<int>::AssignToAll(255 - 2 * dst.a());
case PixelBlendFactor::FIX:
default:
@@ -524,10 +526,39 @@ static inline Vec3<int> GetDestFactor(PixelBlendFactor factor, const Vec4<int> &
}
// Removed inline here - it was never chosen to be inlined by the compiler anyway, too complex.
static bool IsSignedBlendFactor(PixelBlendFactor factor) {
return factor == PixelBlendFactor::DOUBLEINVSRCALPHA || factor == PixelBlendFactor::DOUBLEINVDSTALPHA;
}
// 255 - 2a goes negative for a >= 128, and then the term subtracts: -(((2c + 1) * (2|f| + 1)) >> 10)
// (gpu/probe exp116, all factors and equations exact).
static Vec3<int> SignedBlendingResult(const PixelFuncID &pixelID, const Vec3<int> &srcfactor, const Vec3<int> &dstfactor, const Vec4<int> &source, const Vec4<int> &dst) {
auto term = [](int c, int f) {
const int t = ((2 * c + 1) * (2 * std::abs(f) + 1)) >> 10;
return f < 0 ? -t : t;
};
Vec3<int> result;
for (int i = 0; i < 3; ++i) {
const int s = term(source[i], srcfactor[i]);
const int d = term(dst[i], dstfactor[i]);
switch (pixelID.AlphaBlendEq()) {
case GE_BLENDMODE_MUL_AND_SUBTRACT: result[i] = s - d; break;
case GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE: result[i] = d - s; break;
default: result[i] = s + d; break;
}
}
return result;
}
static Vec3<int> AlphaBlendingResult(const PixelFuncID &pixelID, const Vec4<int> &source, const Vec4<int> &dst) {
// Note: These factors cannot go below 0, but they can go above 255 when doubling.
// Note: These factors can go above 255 when doubling, and below 0 for the doubled inverses.
Vec3<int> srcfactor = GetSourceFactor(pixelID.AlphaBlendSrc(), source, dst, pixelID.cached.alphaBlendSrc);
Vec3<int> dstfactor = GetDestFactor(pixelID.AlphaBlendDst(), source, dst, pixelID.cached.alphaBlendDst);
const GEBlendMode eq = pixelID.AlphaBlendEq();
if ((IsSignedBlendFactor(pixelID.AlphaBlendSrc()) || IsSignedBlendFactor(pixelID.AlphaBlendDst())) &&
(eq == GE_BLENDMODE_MUL_AND_ADD || eq == GE_BLENDMODE_MUL_AND_SUBTRACT || eq == GE_BLENDMODE_MUL_AND_SUBTRACT_REVERSE)) {
return SignedBlendingResult(pixelID, srcfactor, dstfactor, source, dst);
}
switch (pixelID.AlphaBlendEq()) {
case GE_BLENDMODE_MUL_AND_ADD:
@@ -769,6 +800,9 @@ void SOFTRAST_CALL DrawSinglePixel(int x, int y, int z, int fog, Vec4IntArg colo
}
SingleFunc GetSingleFunc(const PixelFuncID &id, BinManager *binner) {
// The jit clamps blend factors at 0.
if (id.alphaBlend && (IsSignedBlendFactor(id.AlphaBlendSrc()) || IsSignedBlendFactor(id.AlphaBlendDst())))
return jitCache->GenericSingle(id);
SingleFunc jitted = jitCache->GetSingle(id, binner);
if (jitted) {
return jitted;