Profile and optimize retained rendering

This commit is contained in:
gamer147
2026-07-22 14:19:20 -04:00
parent 4c9719c5dc
commit 4ea352e45d
17 changed files with 1619 additions and 144 deletions

View File

@@ -13,14 +13,23 @@ public static class SoftwareAffineRasterizer
int ia = (int)(System.Math.Clamp(opacity, 0f, 1f) * 255);
if (ia == 0) return;
int tr=(int)(tint>>16&255), tg=(int)(tint>>8&255), tb=(int)(tint&255);
bool unmodulatedSourceOver = ia == 255 && istr == 0 && !multiplyTint && blend != BlendKind.Additive;
if (TryIntegerTranslation(localToDest, out int tx, out int ty))
{
BlitTranslated(dst, dstW, dstH, src, srcW, srcX, srcY, width, height,
tx, ty, tr, tg, tb, istr, ia, multiplyTint, blend);
tx, ty, tr, tg, tb, istr, ia, multiplyTint, blend, unmodulatedSourceOver);
return;
}
if (!localToDest.TryInverse(out var inv)) return;
Bounds(localToDest, width, height, dstW, dstH, out int x0, out int y0, out int x1, out int y1);
if (x1 <= x0 || y1 <= y0) return;
if (localToDest.XY == 0 && localToDest.YX == 0 && x1 - x0 <= 4096)
{
BlitAxisAligned(dst, dstW, src, srcW, srcX, srcY, width, height, inv,
x0, y0, x1, y1, tr, tg, tb, istr, ia, multiplyTint, blend,
unmodulatedSourceOver);
return;
}
for (int y=y0; y<y1; y++) for (int x=x0; x<x1; x++)
{
var p = inv.Apply(x + 0.5, y + 0.5);
@@ -48,6 +57,12 @@ public static class SoftwareAffineRasterizer
}
if (!localToDest.TryInverse(out var inv)) return;
Bounds(localToDest,width,height,dstW,dstH,out int x0,out int y0,out int x1,out int y1);
if (x1 <= x0 || y1 <= y0) return;
if (localToDest.XY == 0 && localToDest.YX == 0 && x1 - x0 <= 4096)
{
FillAxisAligned(dst, dstW, width, height, inv, x0, y0, x1, y1, r, g, b, a);
return;
}
for(int y=y0;y<y1;y++)for(int x=x0;x<x1;x++){
var p=inv.Apply(x+.5,y+.5);
if(p.X>=0&&p.X<width&&p.Y>=0&&p.Y<height) Blend(dst,(y*dstW+x)*4,r,g,b,a);
@@ -69,11 +84,16 @@ public static class SoftwareAffineRasterizer
private static void BlitTranslated(byte[] dst, int dstW, int dstH, byte[] src, int srcW,
int srcX, int srcY, int width, int height, int tx, int ty,
int tr, int tg, int tb, int istr, int ia, bool multiplyTint,
BlendKind blend)
BlendKind blend, bool unmodulatedSourceOver)
{
int x0 = System.Math.Max(0, tx), y0 = System.Math.Max(0, ty);
int x1 = (int)System.Math.Min(dstW, (long)tx + width);
int y1 = (int)System.Math.Min(dstH, (long)ty + height);
if (unmodulatedSourceOver)
{
BlitTranslatedUnmodulated(dst, dstW, src, srcW, srcX, srcY, tx, ty, x0, y0, x1, y1);
return;
}
for (int y = y0; y < y1; y++)
{
int v = y - ty;
@@ -91,6 +111,20 @@ public static class SoftwareAffineRasterizer
}
}
private static void BlitTranslatedUnmodulated(byte[] dst, int dstW, byte[] src, int srcW,
int srcX, int srcY, int tx, int ty,
int x0, int y0, int x1, int y1)
{
for (int y = y0; y < y1; y++)
{
int sourceRow = (srcY + y - ty) * srcW + srcX - tx;
int destinationRow = y * dstW;
for (int x = x0; x < x1; x++)
BlendUnmodulatedSourceOver(dst, (destinationRow + x) * 4,
src, (sourceRow + x) * 4);
}
}
private static void FillTranslated(byte[] dst, int dstW, int dstH, int width, int height,
int tx, int ty, int r, int g, int b, int a)
{
@@ -102,6 +136,102 @@ public static class SoftwareAffineRasterizer
Blend(dst, (y * dstW + x) * 4, r, g, b, a);
}
// Axis-aligned scale is FIELD's dominant non-integer path. Its inverse source column is independent
// of destination Y, and its source row is independent of destination X. Cache the former once per
// layer and compute the latter once per scanline while preserving the general path's exact center-
// sample/floor arithmetic. Pooled lookup storage avoids both per-layer garbage and repeated-stackalloc
// growth when the JIT inlines this hot path; the 4096-column caller gate keeps the rented bucket small.
private static void BlitAxisAligned(byte[] dst, int dstW, byte[] src, int srcW,
int srcX, int srcY, int width, int height, Affine2D inv,
int x0, int y0, int x1, int y1,
int tr, int tg, int tb, int istr, int ia,
bool multiplyTint, BlendKind blend, bool unmodulatedSourceOver)
{
int count = x1 - x0;
int[] sourceColumns = System.Buffers.ArrayPool<int>.Shared.Rent(count);
try
{
for (int x = x0; x < x1; x++)
{
int u = (int)System.Math.Floor(inv.Apply(x + 0.5, y0 + 0.5).X);
sourceColumns[x - x0] = (uint)u < (uint)width ? u : -1;
}
for (int y = y0; y < y1; y++)
{
int v = (int)System.Math.Floor(inv.Apply(x0 + 0.5, y + 0.5).Y);
if ((uint)v >= (uint)height) continue;
int sourceRow = (srcY + v) * srcW;
int destinationRow = y * dstW;
for (int x = x0; x < x1; x++)
{
int u = sourceColumns[x - x0];
if (u < 0) continue;
int si = (sourceRow + srcX + u) * 4;
int di = (destinationRow + x) * 4;
if (unmodulatedSourceOver)
{
BlendUnmodulatedSourceOver(dst, di, src, si);
continue;
}
int sa = src[si + 3] * ia / 255;
if (sa == 0) continue;
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;
Blend(dst, di, sr, sg, sb, sa, blend);
}
}
}
finally { System.Buffers.ArrayPool<int>.Shared.Return(sourceColumns); }
}
// Most FIELD layers have full object opacity and no tint. Preserve color-key transparency and partially
// transparent edge texels, but make the overwhelmingly common alpha-255 case a four-byte copy instead of
// performing tint and source-over multiply/divide work whose result is exactly the source texel.
private static void BlendUnmodulatedSourceOver(byte[] dst, int di, byte[] src, int si)
{
int a = src[si + 3];
if (a == 0) return;
if (a == 255)
{
dst[di] = src[si];
dst[di + 1] = src[si + 1];
dst[di + 2] = src[si + 2];
dst[di + 3] = 255;
return;
}
int inverse = 255 - a;
dst[di] = (byte)((src[si] * a + dst[di] * inverse) / 255);
dst[di + 1] = (byte)((src[si + 1] * a + dst[di + 1] * inverse) / 255);
dst[di + 2] = (byte)((src[si + 2] * a + dst[di + 2] * inverse) / 255);
dst[di + 3] = (byte)System.Math.Min(255, dst[di + 3] + a);
}
private static void FillAxisAligned(byte[] dst, int dstW, int width, int height, Affine2D inv,
int x0, int y0, int x1, int y1,
int r, int g, int b, int a)
{
int count = x1 - x0;
bool[] includedColumns = System.Buffers.ArrayPool<bool>.Shared.Rent(count);
try
{
for (int x = x0; x < x1; x++)
{
double u = inv.Apply(x + 0.5, y0 + 0.5).X;
includedColumns[x - x0] = u >= 0 && u < width;
}
for (int y = y0; y < y1; y++)
{
double v = inv.Apply(x0 + 0.5, y + 0.5).Y;
if (v < 0 || v >= height) continue;
int destinationRow = y * dstW;
for (int x = x0; x < x1; x++)
if (includedColumns[x - x0]) Blend(dst, (destinationRow + x) * 4, r, g, b, a);
}
}
finally { System.Buffers.ArrayPool<bool>.Shared.Return(includedColumns); }
}
private static void Bounds(Affine2D m,int w,int h,int dw,int dh,out int x0,out int y0,out int x1,out int y1)
{
var a=m.Apply(0,0);var b=m.Apply(w,0);var c=m.Apply(0,h);var d=m.Apply(w,h);