blob: 8ff4c97fd08fdbfa08c814353de7efb6547007a4 [file] [edit]
/*
* Copyright 2026 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/core/SkCanvas.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathUtils.h"
#include "include/core/SkPicture.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/Recorder.h"
#include "include/private/SkTDArray.h"
#include "src/gpu/graphite/geom/EndCaps.h"
#include "src/gpu/graphite/geom/WideTiles.h"
#include "src/gpu/graphite/sparse_strips/AlphaAtlasManager.h"
#include "src/gpu/graphite/sparse_strips/Flatten.h"
#include "src/gpu/graphite/sparse_strips/MSAA_LUT.h"
#include "src/gpu/graphite/sparse_strips/MakeStrips.h"
#include "src/gpu/graphite/sparse_strips/Polyline.h"
#include "src/gpu/graphite/sparse_strips/Tiler.h"
#include "tests/CtsEnforcement.h"
#include "tests/Test.h"
#include "tests/graphite/sparse_strips/CoverageTestUtils.h"
#include "tests/graphite/sparse_strips/SkpValidator.h"
#include "tools/Resources.h"
#include "tools/ToolUtils.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <vector>
namespace skgpu::graphite {
template <uint16_t kTileWidth, uint16_t kTileHeight> class CoverageTestRunner {
public:
static constexpr float kTileWidthF = static_cast<float>(kTileWidth);
static constexpr float kTileHeightF = static_cast<float>(kTileHeight);
static constexpr uint32_t kViewportWidth = 400;
static constexpr uint32_t kViewportHeight = 400;
static constexpr float kViewportWidthF = static_cast<float>(kViewportWidth);
static constexpr float kViewportHeightF = static_cast<float>(kViewportHeight);
using StripFunc = void (*)(const Tiles<kTileWidth, kTileHeight>&,
WideTiles* wides,
EndCaps* ends,
AlphaAtlasManager* atlasManager,
bool isInverse,
const Polyline& polyline,
const SkTDArray<uint8_t>& msaaLut,
MsaaExactMaskObserver observer);
static void RunScalarWinding(const Tiles<kTileWidth, kTileHeight>& tileContainer,
WideTiles* wides,
EndCaps* ends,
AlphaAtlasManager* atlasManager,
bool isInverse,
const Polyline& polyline,
const SkTDArray<uint8_t>& maskLut,
MsaaExactMaskObserver observer) {
SkPathFillType fillType =
isInverse ? SkPathFillType::kInverseWinding : SkPathFillType::kWinding;
MakeStrips::MsaaScalar<kTileWidth, kTileHeight>(
tileContainer, wides, ends, atlasManager,
fillType, polyline, maskLut, kViewportWidth, kViewportHeight, observer);
}
static void RunSimdWinding(const Tiles<kTileWidth, kTileHeight>& tileContainer,
WideTiles* wides,
EndCaps* ends,
AlphaAtlasManager* atlasManager,
bool isInverse,
const Polyline& polyline,
const SkTDArray<uint8_t>& maskLut,
MsaaExactMaskObserver observer) {
SkPathFillType fillType =
isInverse ? SkPathFillType::kInverseWinding : SkPathFillType::kWinding;
MakeStrips::MsaaSimd<kTileWidth, kTileHeight>(
tileContainer, wides, ends, atlasManager,
fillType, polyline, maskLut, kViewportWidth, kViewportHeight, observer);
}
CoverageTestRunner(StripFunc func, const char* implName) : fFunc(func), fImplName(implName) {}
void runAll(skiatest::Reporter* reporter, Recorder* recorder) {
const SkTDArray<uint8_t> lut = GenerateMSAALUT<uint8_t>();
constexpr int kErrorLimit = 3;
std::array<uint32_t, kErrorLimit> minorErrorCount = {0, 0, 0};
int totalTestsRun = 0;
struct TestCase {
SkPath path;
const char* name;
};
std::vector<TestCase> baseGeometries;
auto addRect = [&](float w, float h, const char* name) {
baseGeometries.push_back(
{SkPathBuilder().addRect(SkRect::MakeWH(w, h)).detach(), name});
};
addRect(kTileWidthF * 0.5f, kTileHeightF * 0.5f, "Rect(Small)");
addRect(kTileWidthF, kTileHeightF, "Rect(ExactTile)");
addRect(kTileWidthF * 2.5f, kTileHeightF * 1.5f, "Rect(MultiTile)");
addRect(kTileWidthF * 4.0f, 0.2f, "Rect(HorizSliver)");
addRect(0.2f, kTileHeightF * 4.0f, "Rect(VertSliver)");
baseGeometries.push_back(
{SkPathBuilder()
.addCircle(kTileWidthF * 1.5f, kTileHeightF * 1.5f, kTileWidthF * 1.2f)
.detach(),
"Circle"});
baseGeometries.push_back(
{SkPathBuilder().addOval(SkRect::MakeWH(kTileWidth * 4.0f, 0.5f)).detach(),
"ThinOval"});
SkPathBuilder inset;
inset.addRect(SkRect::MakeWH(kTileWidthF * 3.0f, kTileHeightF * 3.0f),
SkPathDirection::kCW);
inset.addRect(SkRect::MakeXYWH(kTileWidthF, kTileHeightF, kTileWidthF, kTileHeightF),
SkPathDirection::kCCW);
baseGeometries.push_back({inset.detach(), "InsetRect"});
// Tile-relative alignments (dx, dy)
const SkPoint alignments[] = {
{0.0f, 0.0f}, // Top & Left aligned
{0.0f, kTileHeightF * 0.5f}, // Left aligned, offset top
{kTileWidthF * 0.5f, 0.0f}, // Top aligned, offset left
{kTileWidthF * 0.33f, kTileHeightF * 0.33f}, // Strictly inside
{kTileWidthF - 0.01f, kTileHeightF - 0.01f} // Right on a tile boundary edge
};
for (const TestCase& geom : baseGeometries) {
// Progressively rotate the geometry
for (int angleDeg = 0; angleDeg < 360; ++angleDeg) {
float angle = static_cast<float>(angleDeg);
for (const SkPoint& alignment : alignments) {
SkMatrix rotMatrix;
rotMatrix.setRotate(angle);
SkRect rotatedBounds = geom.path.makeTransform(rotMatrix).getBounds();
SkMatrix transMatrix;
transMatrix.setTranslate(alignment.fX - rotatedBounds.fLeft,
alignment.fY - rotatedBounds.fTop);
SkMatrix ctm = SkMatrix::Concat(transMatrix, rotMatrix);
SkPath deviceSpacePath = geom.path.makeTransform(ctm);
SkString testName;
testName.printf("%s - %s Rot(%.1f) Align(%.2f,%.2f)",
fImplName,
geom.name,
angle,
alignment.fX,
alignment.fY);
if (!this->runSingleTest(reporter,
recorder,
deviceSpacePath,
testName.c_str(),
lut,
&minorErrorCount)) {
return;
}
totalTestsRun++;
}
}
}
INFOF(reporter,
"[%s (%dx%d)] Coverage LUT Test Complete. Ran %d variants. "
"Minor Error Summary: 1-sample: %u, 2-sample: %u, 3-sample: %u\n",
fImplName,
kTileWidth,
kTileHeight,
totalTestsRun,
minorErrorCount[0],
minorErrorCount[1],
minorErrorCount[2]);
}
private:
StripFunc fFunc;
const char* fImplName;
bool runSingleTest(skiatest::Reporter* reporter,
Recorder* recorder,
const SkPath& path,
const char* name,
const SkTDArray<uint8_t>& lut,
std::array<uint32_t, 3>* minorErrorCount) {
Flatten flattener;
Polyline polyline;
flattener.processPaths<FlattenMode::kSimd>(
path, SkMatrix(), kViewportWidthF, kViewportHeightF, &polyline);
Tiles<kTileWidth, kTileHeight> tiler;
tiler.makeTilesMSAA(polyline, kViewportWidth, kViewportHeight);
tiler.sortTiles();
WideTiles wides;
EndCaps ends;
SkTDArray<uint8_t> exactMasks;
AlphaAtlasManager atlasManager(recorder);
auto observer = [&](uint8_t exactMask, skvx::int8) { exactMasks.push_back(exactMask); };
fFunc(tiler, &wides, &ends, &atlasManager, /*isInverse=*/false, polyline, lut, observer);
if (ends.empty()) {
bool bufferSizeMatch = exactMasks.empty();
REPORTER_ASSERT(
reporter, bufferSizeMatch, "[%s] No endcaps but observer has data.", name);
return bufferSizeMatch;
}
int32_t maskIdx = 0;
for (const auto& cap : ends.caps()) {
uint16_t spannedTiles = cap.fWidth / kTileWidth;
uint16_t currX = cap.fX;
uint16_t currY = cap.fY;
for (int32_t s = 0; s < spannedTiles; ++s) {
int32_t tileStartIdx = maskIdx;
for (int32_t y = 0; y < kTileHeight; ++y) {
for (int32_t x = 0; x < kTileWidth; ++x) {
uint8_t expectedMask = 0;
int expectedSamples = 0;
for (int k = 0; k < 8; ++k) {
if (CoverageTestUtils::PointInPolygon(
{currX + x + (MSAA_LUT<uint8_t>::kPattern[k] + 0.5f) / 8.0f,
currY + y + (k + 0.5f) / 8.0f},
polyline)) {
expectedSamples++;
expectedMask |= (1 << k);
}
}
uint8_t actualMask =
(maskIdx < exactMasks.size()) ? exactMasks[maskIdx] : 0;
int sampleDiff = 0;
int actualSamples = 0;
for (int k = 0; k < 8; ++k) {
if (actualMask & (1 << k)) actualSamples++;
if ((expectedMask & (1 << k)) != (actualMask & (1 << k))) sampleDiff++;
}
if (sampleDiff > 3) {
CoverageTestUtils::PrintCoverageDiagnostics(reporter,
polyline,
tiler,
currX,
currY,
exactMasks,
tileStartIdx);
REPORTER_ASSERT(reporter,
false,
"[%s] Fail at tile(%d,%d). Exp %d, Got %d samples",
name,
currX / kTileWidth,
currY / kTileHeight,
expectedSamples,
actualSamples);
return false;
} else if (sampleDiff > 0) {
(*minorErrorCount)[sampleDiff - 1]++;
}
maskIdx++;
}
}
currX += kTileWidth;
}
}
bool bufferSizeMatch = (maskIdx == exactMasks.size());
REPORTER_ASSERT(reporter,
bufferSizeMatch,
"[%s] Checked %d mask bytes but observer size is %d",
name,
maskIdx,
exactMasks.size());
return bufferSizeMatch;
}
};
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_CoverageScalar_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
skgpu::graphite::CoverageTestRunner<4, 4> scalarRunner(
&skgpu::graphite::CoverageTestRunner<4, 4>::RunScalarWinding, "Scalar");
scalarRunner.runAll(reporter, recorder.get());
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_CoverageSIMD_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
skgpu::graphite::CoverageTestRunner<4, 4> simdRunner(
&skgpu::graphite::CoverageTestRunner<4, 4>::RunSimdWinding, "SIMD");
simdRunner.runAll(reporter, recorder.get());
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_CoverageSIMD_8x8,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
skgpu::graphite::CoverageTestRunner<8, 8> simdRunner(
&skgpu::graphite::CoverageTestRunner<8, 8>::RunSimdWinding, "SIMD");
simdRunner.runAll(reporter, recorder.get());
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_SKP_SIMD_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
const SkTDArray<uint8_t> lut = GenerateMSAALUT<uint8_t>();
SkpValidator::ValidateSkp<4, 4>(reporter, recorder.get(), "skps/desk_tiger8svg.skp", lut);
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_SKP_SIMD_8x8,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
const SkTDArray<uint8_t> lut = GenerateMSAALUT<uint8_t>();
SkpValidator::ValidateSkp<8, 8>(reporter, recorder.get(), "skps/desk_tiger8svg.skp", lut);
}
} // namespace skgpu::graphite