sw_engine: replaced texture mapping AA with 4x sampling interp
Some checks are pending
Android / build_x86_64 (push) Waiting to run
Android / build_aarch64 (push) Waiting to run
iOS / build_x86_64 (push) Waiting to run
iOS / build_arm64 (push) Waiting to run
macOS / build (push) Waiting to run
macOS / compact_test (push) Waiting to run
macOS / unit_test (push) Waiting to run
Ubuntu / build (push) Waiting to run
Ubuntu / compact_test (push) Waiting to run
Ubuntu / unit_test (push) Waiting to run
Windows / build (push) Waiting to run
Windows / compact_test (push) Waiting to run
Windows / unit_test (push) Waiting to run

The old approach often produced incorrect results,
especially when the fixed pixel had a noticeably
different color from the texture due to the AA
target blending position was fixed.

Although the previous method worked well as an analytical AA
solution with good speed and fair quality, it couldn't
overcome the above limitation.

The new approach still applies AA only to polygon edges
for efficiency. While the quality may be slightly reduced,
it offers greater stability.

- binary size: -1.1kb
- performance diff: ignoreable

issue: https://github.com/thorvg/thorvg/issues/1729
This commit is contained in:
Hermet Park 2025-06-11 01:20:01 +09:00 committed by Hermet Park
parent 596f7f767f
commit 174fae9089

View file

@ -33,9 +33,7 @@ struct Polygon
struct AALine
{
int32_t x[2];
int32_t coverage[2];
int32_t length[2];
uint16_t x[2];
};
struct AASpans
@ -138,8 +136,8 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage
//Anti-Aliasing frames
if (aaSpans) {
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = static_cast<uint16_t>(x1);
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = static_cast<uint16_t>(x2);
}
//Range allowed
@ -149,9 +147,7 @@ static void _rasterBlendingPolygonImageSegment(SwSurface* surface, const SwImage
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
v = _va + dx * _dvdx;
buf = dbuf + ((y * surface->stride) + x1);
x = x1;
//Draw horizontal line
@ -273,12 +269,13 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image,
//Anti-Aliasing frames
if (aaSpans) {
ay = y - aaSpans->yStart;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = x1;
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = x2;
if (aaSpans->lines[ay].x[0] > x1) aaSpans->lines[ay].x[0] = static_cast<uint16_t>(x1);
if (aaSpans->lines[ay].x[1] < x2) aaSpans->lines[ay].x[1] = static_cast<uint16_t>(x2);
}
//Range allowed
if ((x2 - x1) >= 1 && (x1 < maxx) && (x2 > minx)) {
//Perform subtexel pre-stepping on UV
dx = 1 - (_xa - x1);
u = _ua + dx * _dudx;
@ -299,6 +296,7 @@ static void _rasterPolygonImageSegment(SwSurface* surface, const SwImage* image,
ar = _modf(u);
ab = _modf(v);
iru = uu + 1;
irv = vv + 1;
@ -547,243 +545,40 @@ static AASpans* _AASpans(int yStart, int yEnd)
aaSpans->lines = tvg::malloc<AALine*>(height * sizeof(AALine));
for (int32_t i = 0; i < height; i++) {
aaSpans->lines[i].x[0] = INT32_MAX;
aaSpans->lines[i].x[0] = UINT16_MAX;
aaSpans->lines[i].x[1] = 0;
aaSpans->lines[i].length[0] = 0;
aaSpans->lines[i].length[1] = 0;
}
return aaSpans;
}
static void _calcIrregularCoverage(AALine* lines, int32_t eidx, int32_t y, int32_t diagonal, int32_t edgeDist, bool reverse)
{
if (eidx == 1) reverse = !reverse;
int32_t coverage = (255 / (diagonal + 2));
int32_t tmp;
for (int32_t ry = 0; ry < (diagonal + 2); ry++) {
tmp = y - ry - edgeDist;
if (tmp < 0) return;
lines[tmp].length[eidx] = 1;
if (reverse) lines[tmp].coverage[eidx] = 255 - (coverage * ry);
else lines[tmp].coverage[eidx] = (coverage * ry);
}
}
static void _calcVertCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t rewind, bool reverse)
{
if (eidx == 1) reverse = !reverse;
int32_t coverage = (255 / (rewind + 1));
int32_t tmp;
for (int ry = 1; ry < (rewind + 1); ry++) {
tmp = y - ry;
if (tmp < 0) return;
lines[tmp].length[eidx] = 1;
if (reverse) lines[tmp].coverage[eidx] = (255 - (coverage * ry));
else lines[tmp].coverage[eidx] = (coverage * ry);
}
}
static void _calcHorizCoverage(AALine *lines, int32_t eidx, int32_t y, int32_t x, int32_t x2)
{
lines[y].length[eidx] = abs(x - x2);
lines[y].coverage[eidx] = (255 / (lines[y].length[eidx] + 1));
}
/*
* This Anti-Aliasing mechanism is originated from Hermet Park's idea.
* To understand this AA logic, you can refer this page:
* https://uigraphics.tistory.com/1
*/
static void _calcAAEdge(AASpans *aaSpans, int32_t eidx)
{
//Previous edge direction:
#define DirOutHor 0x0011
#define DirOutVer 0x0001
#define DirInHor 0x0010
#define DirInVer 0x0000
#define DirNone 0x1000
#define PUSH_VERTEX() \
do { \
pEdge.x = lines[y].x[eidx]; \
pEdge.y = y; \
ptx[0] = tx[0]; \
ptx[1] = tx[1]; \
} while (0)
int32_t y = 0;
SwPoint pEdge = {-1, -1}; //previous edge point
SwPoint edgeDiff = {0, 0}; //temporary used for point distance
/* store bigger to tx[0] between prev and current edge's x positions. */
int32_t tx[2] = {0, 0};
/* back up prev tx values */
int32_t ptx[2] = {0, 0};
int32_t diagonal = 0; //straight diagonal pixels count
auto yStart = aaSpans->yStart;
auto yEnd = aaSpans->yEnd;
auto lines = aaSpans->lines;
int32_t prevDir = DirNone;
int32_t curDir = DirNone;
yEnd -= yStart;
//Start Edge
if (y < yEnd) {
pEdge.x = lines[y].x[eidx];
pEdge.y = y;
}
//Calculates AA Edges
for (y++; y < yEnd; y++) {
if (lines[y].x[0] == INT32_MAX) continue;
//Ready tx
if (eidx == 0) {
tx[0] = pEdge.x;
tx[1] = lines[y].x[0];
} else {
tx[0] = lines[y].x[1];
tx[1] = pEdge.x;
}
edgeDiff.x = (tx[0] - tx[1]);
edgeDiff.y = (y - pEdge.y);
//Confirm current edge direction
if (edgeDiff.x > 0) {
if (edgeDiff.y == 1) curDir = DirOutHor;
else curDir = DirOutVer;
} else if (edgeDiff.x < 0) {
if (edgeDiff.y == 1) curDir = DirInHor;
else curDir = DirInVer;
} else curDir = DirNone;
//straight diagonal increase
if ((curDir == prevDir) && (y < yEnd)) {
if ((abs(edgeDiff.x) == 1) && (edgeDiff.y == 1)) {
++diagonal;
PUSH_VERTEX();
continue;
}
}
switch (curDir) {
case DirOutHor: {
_calcHorizCoverage(lines, eidx, y, tx[0], tx[1]);
if (diagonal > 0) {
_calcIrregularCoverage(lines, eidx, y, diagonal, 0, true);
diagonal = 0;
}
/* Increment direction is changed: Outside Vertical -> Outside Horizontal */
if (prevDir == DirOutVer) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
//Trick, but fine-tunning!
if (y == 1) _calcHorizCoverage(lines, eidx, pEdge.y, tx[0], tx[1]);
PUSH_VERTEX();
}
break;
case DirOutVer: {
_calcVertCoverage(lines, eidx, y, edgeDiff.y, true);
if (diagonal > 0) {
_calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, false);
diagonal = 0;
}
/* Increment direction is changed: Outside Horizontal -> Outside Vertical */
if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
PUSH_VERTEX();
}
break;
case DirInHor: {
_calcHorizCoverage(lines, eidx, (y - 1), tx[0], tx[1]);
if (diagonal > 0) {
_calcIrregularCoverage(lines, eidx, y, diagonal, 0, false);
diagonal = 0;
}
/* Increment direction is changed: Outside Horizontal -> Inside Horizontal */
if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
PUSH_VERTEX();
}
break;
case DirInVer: {
_calcVertCoverage(lines, eidx, y, edgeDiff.y, false);
if (prevDir == DirOutHor) edgeDiff.y -= 1; //Weird, fine tuning?????????????????????
if (diagonal > 0) {
_calcIrregularCoverage(lines, eidx, y, diagonal, edgeDiff.y, true);
diagonal = 0;
}
/* Increment direction is changed: Outside Horizontal -> Inside Vertical */
if (prevDir == DirOutHor) _calcHorizCoverage(lines, eidx, pEdge.y, ptx[0], ptx[1]);
PUSH_VERTEX();
}
break;
}
if (curDir != DirNone) prevDir = curDir;
}
//leftovers...?
if ((edgeDiff.y == 1) && (edgeDiff.x != 0)) {
if (y >= yEnd) y = (yEnd - 1);
_calcHorizCoverage(lines, eidx, y - 1, ptx[0], ptx[1]);
_calcHorizCoverage(lines, eidx, y, tx[0], tx[1]);
} else {
++y;
if (y > yEnd) y = yEnd;
_calcVertCoverage(lines, eidx, y, (edgeDiff.y + 1), (prevDir & 0x00000001));
}
}
static void _apply(SwSurface* surface, AASpans* aaSpans)
{
auto end = surface->buf32 + surface->h * surface->stride;
auto buf = surface->buf32 + surface->stride * aaSpans->yStart;
auto w = int32_t(surface->w - 1);
auto h = int32_t(surface->h - 1);
auto y = aaSpans->yStart;
auto line = aaSpans->lines;
uint32_t pix;
uint32_t* dst;
int32_t pos;
_calcAAEdge(aaSpans, 0); //left side
_calcAAEdge(aaSpans, 1); //right side
constexpr int WEIGHT = 190;
auto feathering = [&](pixel_t* dst, int32_t x) {
if (y > 0) {
auto top = dst - surface->stride;
if (x < w) *dst = INTERPOLATE(*dst, *(top + 1), WEIGHT);
if (x > 0) *dst = INTERPOLATE(*dst, *(top - 1), WEIGHT);
}
if (y < h) {
auto bottom = dst + surface->stride;
if (x < w) *dst = INTERPOLATE(*dst, *(bottom + 1), WEIGHT);
if (x > 0) *dst = INTERPOLATE(*dst, *(bottom - 1), WEIGHT);
}
};
while (y < aaSpans->yEnd) {
if (line->x[1] - line->x[0] > 0) {
//Left edge
dst = buf + line->x[0];
pix = *(dst - ((line->x[0] > 1) ? 1 : 0));
pos = 1;
//exceptional handling. out of memory bound.
if (dst + line->length[0] >= end) {
pos += (dst + line->length[0] - end);
}
while (pos <= line->length[0]) {
*dst = INTERPOLATE(*dst, pix, line->coverage[0] * pos);
++dst;
++pos;
}
//Right edge
dst = buf + line->x[1] - 1;
pix = *(dst + (line->x[1] < (int32_t)(surface->w - 1) ? 1 : 0));
pos = line->length[1];
//exceptional handling. out of memory bound.
if (dst - pos < surface->buf32) --pos;
while (pos > 0) {
*dst = INTERPOLATE(*dst, pix, 255 - (line->coverage[1] * pos));
--dst;
--pos;
}
feathering(buf + line->x[0], line->x[0]); //left
feathering(buf + line->x[1] - 1, line->x[1] - 1); //right
}
buf += surface->stride;
++line;