Profile and optimize retained rendering
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user