blob: 0951cef6c632376975391d1f695ea9d5686c2c20 [file]
/*
* Copyright 2026 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef skgpu_graphite_sparse_strips_CoverageTestUtils_DEFINED
#define skgpu_graphite_sparse_strips_CoverageTestUtils_DEFINED
#include "include/core/SkPoint.h"
#include "include/core/SkString.h"
#include "include/private/SkTDArray.h"
#include "src/core/SkVx.h"
#include "src/gpu/graphite/sparse_strips/MSAA_LUT.h"
#include "src/gpu/graphite/sparse_strips/Polyline.h"
#include "src/gpu/graphite/sparse_strips/SparseStripsTypes.h"
#include "src/gpu/graphite/sparse_strips/Tiler.h"
#include "tests/Test.h"
#include "tests/graphite/sparse_strips/Oracle.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <vector>
namespace skgpu::graphite {
class CoverageTestUtils {
public:
CoverageTestUtils() = delete;
// Simplified point-in-polygon verification using Even-Odd rule.
static bool PointInPolygon(SkPoint pt, const Polyline& polyline) {
bool inside = false;
for (auto it = polyline.begin(); it != polyline.end(); ++it) {
auto [line, idx] = *it;
if ((line.p0.fY > pt.fY) != (line.p1.fY > pt.fY)) {
float t = (pt.fY - line.p0.fY) / (line.p1.fY - line.p0.fY);
float xInt = line.p0.fX + t * (line.p1.fX - line.p0.fX);
if (pt.fX < xInt) {
inside = !inside;
}
}
}
return inside;
}
// Prints ASCII map diagnostic comparing expected polygon coverage against actual rasterized
// mask.
template <uint16_t kTileWidth, uint16_t kTileHeight>
static void PrintCoverageDiagnostics(skiatest::Reporter* reporter,
const Polyline& polyline,
const Tiles<kTileWidth, kTileHeight>& tiler,
int failX,
int failY,
const SkTDArray<uint8_t>& actualMasks,
size_t tileStartIdx) {
SkString out("\n--- FAILURE DIAGNOSTICS ---\n");
AppendGeometryLines(&out, &polyline);
AppendTileIntersections(&out, &tiler);
AppendTileAsciiMap<kTileWidth, kTileHeight>(
&out,
failX,
failY,
&polyline,
[&](int px, int py, int sy, SkPoint cp) { return PointInPolygon(cp, polyline); },
[&](int px, int py, int sy, uint16_t localX, uint16_t localY) {
size_t bufIdx = tileStartIdx + (localY * kTileWidth + localX);
return (bufIdx < static_cast<size_t>(actualMasks.size())) &&
((actualMasks[bufIdx] & (1 << sy)) != 0);
});
INFOF(reporter, "%s", out.c_str());
}
// Prints ASCII map diagnostic comparing Oracle expected coverage against actual winding.
template <uint16_t kTileWidth, uint16_t kTileHeight>
static void PrintWindingDiagnostics(skiatest::Reporter* reporter,
const char* testName,
uint16_t tileStartX,
uint16_t tileStartY,
uint16_t px,
uint16_t py,
skvx::int8 actualWinding,
skvx::int8 oracleWinding,
const Polyline* polyline,
const Tiles<kTileWidth, kTileHeight>* tiler,
const SkTDArray<skvx::int8>& exactWindings,
uint32_t tileStartAlphaIdx,
ScanlineOracle8x* oracle) {
SkString out;
out.appendf("\n=== MISMATCH DIAGNOSTIC [%s] ===\n", testName);
out.appendf("Location: Tile(%d,%d), Pixel(%d,%d) [Global px=%u, py=%u]\n",
static_cast<int>(tileStartX / kTileWidth),
static_cast<int>(tileStartY / kTileHeight),
static_cast<int>(px - tileStartX),
static_cast<int>(py - tileStartY),
px,
py);
out.appendf("Actual Winding: [%d, %d, %d, %d, %d, %d, %d, %d]\n",
actualWinding[0], actualWinding[1], actualWinding[2], actualWinding[3],
actualWinding[4], actualWinding[5], actualWinding[6], actualWinding[7]);
out.appendf("Oracle Winding: [%d, %d, %d, %d, %d, %d, %d, %d]\n",
oracleWinding[0], oracleWinding[1], oracleWinding[2], oracleWinding[3],
oracleWinding[4], oracleWinding[5], oracleWinding[6], oracleWinding[7]);
AppendGeometryLines(&out, polyline, px, py);
AppendTileIntersections(&out, tiler, tileStartX / kTileWidth, tileStartY / kTileHeight);
std::vector<ScanlineOracle8x::RowWindingInterval> oracleIntervals;
int cachedRow = -1;
AppendTileAsciiMap<kTileWidth, kTileHeight>(
&out,
tileStartX,
tileStartY,
polyline,
[&](int currX, int currY, int sy, SkPoint cp) {
if (oracle) {
if (cachedRow != currY) {
oracleIntervals = oracle->buildRowIntervals(currY);
cachedRow = currY;
}
skvx::int8 expW =
ScanlineOracle8x::GetOracleWinding(currX, oracleIntervals);
return (expW[sy] != 0);
}
return false;
},
[&](int currX, int currY, int sy, uint16_t localX, uint16_t localY) {
uint32_t bufIdx = tileStartAlphaIdx + (localY * kTileWidth + localX);
return (bufIdx < static_cast<uint32_t>(exactWindings.size())) &&
(exactWindings[bufIdx][sy] != 0);
},
"Oracle Expected",
"Actual");
INFOF(reporter, "%s", out.c_str());
}
private:
static void AppendGeometryLines(SkString* out,
const Polyline* polyline,
int nearPx = -1,
int nearPy = -1) {
if (!polyline) {
return;
}
if (nearPx < 0) {
out->append("Geometry Lines: {\n");
for (auto it = polyline->begin(); it != polyline->end(); ++it) {
auto [l, idx] = *it;
out->appendf(" {{%f, %f}, {%f, %f}},\n", l.p0.fX, l.p0.fY, l.p1.fX, l.p1.fY);
}
out->append("}\n\n");
} else {
out->append("Polyline Lines near pixel:\n");
for (auto it = polyline->begin(); it != polyline->end(); ++it) {
auto [l, idx] = *it;
float minX = std::min(l.p0.fX, l.p1.fX);
float maxX = std::max(l.p0.fX, l.p1.fX);
float minY = std::min(l.p0.fY, l.p1.fY);
float maxY = std::max(l.p0.fY, l.p1.fY);
if (maxX >= nearPx - 1 && minX <= nearPx + 2 && maxY >= nearPy - 1 &&
minY <= nearPy + 2) {
out->appendf(" Line %d: (%.2f, %.2f) -> (%.2f, %.2f)\n",
idx, l.p0.fX, l.p0.fY, l.p1.fX, l.p1.fY);
}
}
}
}
template <uint16_t kTileWidth, uint16_t kTileHeight>
static void AppendTileIntersections(SkString* out,
const Tiles<kTileWidth, kTileHeight>* tiler,
int tileX = -1,
int tileY = -1) {
if (!tiler) {
return;
}
out->append("Tile Intersections:\n");
for (const auto& tile : tiler->getTiles()) {
if (tileX < 0 || (tile.x == tileX && tile.y == tileY)) {
uint32_t mask = tile.intersectionMask();
uint32_t lineIdx = tile.lineIdx();
out->appendf(" Tile(%u,%u) Line %u Mask: %s\n",
tile.x,
tile.y,
lineIdx,
IntersectionBits::MaskToString(mask).c_str());
}
}
}
// Checks if a point is within threshold distance to any line in polyline.
static bool PointNearLine(SkPoint pt, const Polyline& polyline, float threshold) {
for (auto it = polyline.begin(); it != polyline.end(); ++it) {
auto [line, idx] = *it;
float l2 = (line.p0.fX - line.p1.fX) * (line.p0.fX - line.p1.fX) +
(line.p0.fY - line.p1.fY) * (line.p0.fY - line.p1.fY);
float dist = 0.0f;
if (l2 == 0.0f) {
float dx = pt.fX - line.p0.fX;
float dy = pt.fY - line.p0.fY;
dist = std::sqrt(dx * dx + dy * dy);
} else {
float t = std::max(0.0f,
std::min(1.0f,
((pt.fX - line.p0.fX) * (line.p1.fX - line.p0.fX) +
(pt.fY - line.p0.fY) * (line.p1.fY - line.p0.fY)) /
l2));
float projX = line.p0.fX + t * (line.p1.fX - line.p0.fX);
float projY = line.p0.fY + t * (line.p1.fY - line.p0.fY);
dist = std::sqrt((pt.fX - projX) * (pt.fX - projX) +
(pt.fY - projY) * (pt.fY - projY));
}
if (dist <= threshold) {
return true;
}
}
return false;
}
// Formats a side-by-side ASCII map comparing expected and actual tile coverage.
template <uint16_t kTileWidth, uint16_t kTileHeight, typename ExpectedFn, typename ActualFn>
static void AppendTileAsciiMap(SkString* out,
uint16_t tileStartX,
uint16_t tileStartY,
const Polyline* polyline,
ExpectedFn&& isExpectedActive,
ActualFn&& isActualActive,
const char* leftLabel = "Expected",
const char* rightLabel = "Actual") {
out->appendf("\nASCII Map Tile(%d,%d) [Left: %s | Right: %s]\n",
static_cast<int>(tileStartX / kTileWidth),
static_cast<int>(tileStartY / kTileHeight),
leftLabel,
rightLabel);
SkString border("+");
for (int x = 0; x < kTileWidth; ++x) {
border.append("----------------+");
}
for (uint16_t localY = 0; localY < kTileHeight; ++localY) {
uint16_t currY = tileStartY + localY;
out->appendf("%s %s\n", border.c_str(), border.c_str());
for (int sy = 0; sy < 8; ++sy) {
for (int side = 0; side < 2; ++side) { // 0: Left/Expected, 1: Right/Actual
out->append("|");
for (uint16_t localX = 0; localX < kTileWidth; ++localX) {
uint16_t currX = tileStartX + localX;
for (int sx = 0; sx < 8; ++sx) {
SkPoint cp = {currX + (sx + 0.5f) / 8.0f, currY + (sy + 0.5f) / 8.0f};
bool onLine =
polyline ? PointNearLine(cp, *polyline, 0.6f / 8.0f) : false;
bool active =
(side == 0) ? isExpectedActive(currX, currY, sy, cp)
: isActualActive(currX, currY, sy, localX, localY);
if (MSAA_LUT<uint8_t>::kPattern[sy] == sx) {
out->append(active ? (onLine ? "*#" : " #")
: (onLine ? "*o" : " o"));
} else {
out->append(onLine ? "**" : " ");
}
}
out->append("|");
}
if (side == 0) {
out->append(" ");
}
}
out->append("\n");
}
}
out->appendf("%s %s\n", border.c_str(), border.c_str());
}
};
} // namespace skgpu::graphite
#endif // skgpu_graphite_sparse_strips_CoverageTestUtils_DEFINED