Profile and optimize retained rendering

This commit is contained in:
gamer147
2026-07-22 14:19:20 -04:00
parent 1625b3bf54
commit 838f6d4245
17 changed files with 1619 additions and 144 deletions

View File

@@ -115,10 +115,110 @@ public class SoftwareAffineRasterizerTests
Assert.Equal(new byte[] { 43, 57, 109, 255 }, background);
}
[Fact]
public void BlitRgba_AxisAlignedScaleFastPath_MatchesGeneralAffineAcrossRandomizedCases()
{
var random = new Random(0x231);
double[] scales = { -2.0, -1.25, -0.5, 0.25, 0.5, 0.75, 1.25, 2.0, 3.0 };
double[] translations = { -5.5, -2.25, -0.5, 0.0, 0.5, 1.25, 4.5, 8.0 };
BlendKind[] blends = { BlendKind.Opaque, BlendKind.Alpha, BlendKind.Additive };
for (int iteration = 0; iteration < 250; iteration++)
{
int srcW = random.Next(2, 12), srcH = random.Next(2, 10);
byte[] src = new byte[srcW * srcH * 4];
random.NextBytes(src);
int srcX = random.Next(0, srcW), srcY = random.Next(0, srcH);
int width = random.Next(1, srcW - srcX + 1), height = random.Next(1, srcH - srcY + 1);
int dstW = random.Next(3, 15), dstH = random.Next(3, 13);
byte[] expected = new byte[dstW * dstH * 4];
random.NextBytes(expected);
byte[] actual = (byte[])expected.Clone();
var transform = new Affine2D(
scales[random.Next(scales.Length)], 0, 0, scales[random.Next(scales.Length)],
translations[random.Next(translations.Length)], translations[random.Next(translations.Length)]);
long tint = random.Next(0x1000000);
float tintStrength = random.NextSingle();
float opacity = random.NextSingle();
bool multiplyTint = random.Next(2) != 0;
BlendKind blend = blends[random.Next(blends.Length)];
ReferenceBlit(expected, dstW, dstH, src, srcW, srcX, srcY, width, height,
transform, tint, tintStrength, opacity, multiplyTint, blend);
SoftwareAffineRasterizer.BlitRgba(actual, dstW, dstH, src, srcW, srcH,
srcX, srcY, width, height, transform,
tint, tintStrength, opacity, multiplyTint, blend);
Assert.Equal(expected, actual);
}
}
[Fact]
public void FillRgba_AxisAlignedScaleFastPath_MatchesGeneralAffineAcrossRandomizedCases()
{
var random = new Random(0x22d);
double[] scales = { -2.0, -1.0, -0.5, 0.25, 0.5, 0.75, 1.5, 2.0, 3.0 };
double[] translations = { -5.5, -1.25, -0.5, 0.0, 0.5, 2.25, 7.0 };
for (int iteration = 0; iteration < 250; iteration++)
{
int width = random.Next(1, 10), height = random.Next(1, 9);
int dstW = random.Next(3, 15), dstH = random.Next(3, 13);
byte[] expected = new byte[dstW * dstH * 4];
random.NextBytes(expected);
byte[] actual = (byte[])expected.Clone();
var transform = new Affine2D(
scales[random.Next(scales.Length)], 0, 0, scales[random.Next(scales.Length)],
translations[random.Next(translations.Length)], translations[random.Next(translations.Length)]);
long color = random.Next(0x1000000);
float opacity = random.NextSingle();
ReferenceFill(expected, dstW, dstH, width, height, transform, color, opacity);
SoftwareAffineRasterizer.FillRgba(actual, dstW, dstH, width, height,
transform, color, opacity);
Assert.Equal(expected, actual);
}
}
[Fact]
public void BlitRgba_UnmodulatedSourceOverFastPaths_MatchGeneralAffineAcrossAlphaEdges()
{
var random = new Random(0x2e0);
Affine2D[] transforms =
{
new(1, 0, 0, 1, -2, 1),
new(1, 0, 0, 1, 3, -1),
new(0.5, 0, 0, 1.5, -1.25, 0.5),
new(-1.25, 0, 0, 0.75, 8.5, -0.5),
};
foreach (var transform in transforms)
foreach (BlendKind blend in new[] { BlendKind.Opaque, BlendKind.Alpha })
for (int iteration = 0; iteration < 50; iteration++)
{
int srcW = random.Next(3, 12), srcH = random.Next(3, 10);
byte[] src = new byte[srcW * srcH * 4];
random.NextBytes(src);
for (int pixel = 0; pixel < srcW * srcH; pixel++)
src[pixel * 4 + 3] = (byte)(pixel % 4 switch { 0 => 0, 1 => 1, 2 => 254, _ => 255 });
int dstW = random.Next(5, 16), dstH = random.Next(5, 14);
byte[] expected = new byte[dstW * dstH * 4];
random.NextBytes(expected);
byte[] actual = (byte[])expected.Clone();
ReferenceBlit(expected, dstW, dstH, src, srcW, 1, 1, srcW - 1, srcH - 1,
transform, 0x6a4c2e, 0, 1, false, blend);
SoftwareAffineRasterizer.BlitRgba(actual, dstW, dstH, src, srcW, srcH,
1, 1, srcW - 1, srcH - 1, transform,
0x6a4c2e, 0, 1, false, blend);
Assert.Equal(expected, actual);
}
}
// Pre-fast-path affine algorithm retained here as an independent differential oracle.
private static void ReferenceBlit(byte[] dst, int dstW, int dstH, byte[] src, int srcW,
int srcX, int srcY, int width, int height, Affine2D transform,
long tint, float tintStrength, float opacity, bool multiplyTint)
long tint, float tintStrength, float opacity, bool multiplyTint,
BlendKind blend = BlendKind.Alpha)
{
if (!transform.TryInverse(out var inv)) return;
ReferenceBounds(transform, width, height, dstW, dstH, out int x0, out int y0, out int x1, out int y1);
@@ -136,7 +236,7 @@ public class SoftwareAffineRasterizerTests
int sr = multiplyTint ? src[si] * tr / 255 : (src[si] * (255 - istr) + tr * istr) / 255;
int sg = multiplyTint ? src[si + 1] * tg / 255 : (src[si + 1] * (255 - istr) + tg * istr) / 255;
int sb = multiplyTint ? src[si + 2] * tb / 255 : (src[si + 2] * (255 - istr) + tb * istr) / 255;
ReferenceBlend(dst, di, sr, sg, sb, sa);
ReferenceBlend(dst, di, sr, sg, sb, sa, blend);
}
}
@@ -165,8 +265,17 @@ public class SoftwareAffineRasterizerTests
y1 = System.Math.Min(dh, (int)System.Math.Ceiling(System.Math.Max(System.Math.Max(a.Y, b.Y), System.Math.Max(c.Y, d.Y))));
}
private static void ReferenceBlend(byte[] dst, int i, int r, int g, int b, int a)
private static void ReferenceBlend(byte[] dst, int i, int r, int g, int b, int a,
BlendKind blend = BlendKind.Alpha)
{
if (blend == BlendKind.Additive)
{
dst[i] = (byte)System.Math.Min(255, dst[i] + r * a / 255);
dst[i + 1] = (byte)System.Math.Min(255, dst[i + 1] + g * a / 255);
dst[i + 2] = (byte)System.Math.Min(255, dst[i + 2] + b * a / 255);
dst[i + 3] = (byte)System.Math.Min(255, dst[i + 3] + a);
return;
}
dst[i] = (byte)((r * a + dst[i] * (255 - a)) / 255);
dst[i + 1] = (byte)((g * a + dst[i + 1] * (255 - a)) / 255);
dst[i + 2] = (byte)((b * a + dst[i + 2] * (255 - a)) / 255);