blob: 04b444944ccb515b395564e1a2e5c304e7f5224a [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.
*/
#include "include/core/SkMatrix.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathBuilder.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.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/Polyline.h"
#include "src/gpu/graphite/sparse_strips/StripGenerator.h"
#include "src/gpu/graphite/sparse_strips/Tiler.h"
#include "src/gpu/graphite/sparse_strips/WindingHistogram.h"
#include "tests/CtsEnforcement.h"
#include "tests/Test.h"
#include "tests/graphite/sparse_strips/CullingRepros.h"
#include "tests/graphite/sparse_strips/FastValidator.h"
#include "tests/graphite/sparse_strips/SkpValidator.h"
#include <array>
#include <cmath>
#include <vector>
namespace skgpu::graphite {
namespace {
template <uint16_t kTileWidth, uint16_t kTileHeight>
void run_coverage_suite(skiatest::Reporter* reporter,
Recorder* recorder,
typename FastValidator<kTileWidth, kTileHeight>::StripFunc stripFunc,
const char* implName) {
constexpr float kTileWidthF = static_cast<float>(kTileWidth);
constexpr float kTileHeightF = static_cast<float>(kTileHeight);
constexpr uint32_t kViewportWidth = 400;
constexpr uint32_t kViewportHeight = 400;
const SparseStripConfig::LUTArray lut = GenerateMSAALUT();
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)",
implName,
geom.name,
angle,
alignment.fX,
alignment.fY);
if (!FastValidator<kTileWidth, kTileHeight>::ValidatePath(reporter,
recorder,
deviceSpacePath,
SkMatrix::I(),
kViewportWidth,
kViewportHeight,
testName.c_str(),
lut,
stripFunc,
&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",
implName,
kTileWidth,
kTileHeight,
totalTestsRun,
minorErrorCount[0],
minorErrorCount[1],
minorErrorCount[2]);
}
SkPath make_cubic_conic_culling_shape(float vpWidth, float vpHeight) {
SkPathBuilder b;
float w = vpWidth;
float h = vpHeight;
// Start on left inside the viewport
b.moveTo(20.0f, h * 0.5f);
// 1. Conic in top-left region (fully inside viewport)
b.conicTo(20.0f, 20.0f, w * 0.5f - 20.0f, 20.0f, SK_ScalarRoot2Over2);
// 2. Cross top boundary:
// 2a. Exit top (cubic from inside to Y = -15)
b.cubicTo(w * 0.5f - 20.0f, 5.0f, w * 0.5f - 15.0f, -15.0f, w * 0.5f - 10.0f, -15.0f);
// 2b. Fully outside top (conic at Y <= -15 < 0, testing top culling)
b.conicTo(w * 0.5f, -25.0f, w * 0.5f + 10.0f, -15.0f, SK_ScalarRoot2Over2);
// 2c. Re-enter top (cubic from Y = -15 back inside to Y = 20)
b.cubicTo(w * 0.5f + 15.0f, -15.0f, w * 0.5f + 20.0f, 5.0f, w * 0.5f + 20.0f, 20.0f);
// 3. Conic in top-right region (fully inside viewport)
b.conicTo(w - 20.0f, 20.0f, w - 20.0f, h * 0.5f - 20.0f, SK_ScalarRoot2Over2);
// 4. Cross right boundary:
// 4a. Exit right (cubic from inside to X = w + 15)
b.cubicTo(w - 5.0f, h * 0.5f - 20.0f, w + 15.0f, h * 0.5f - 15.0f, w + 15.0f, h * 0.5f - 10.0f);
// 4b. Fully outside right (conic at X >= w + 15 > w, testing right culling)
b.conicTo(w + 25.0f, h * 0.5f, w + 15.0f, h * 0.5f + 10.0f, SK_ScalarRoot2Over2);
// 4c. Re-enter right (cubic from X = w + 15 back inside to X = w - 20)
b.cubicTo(w + 15.0f, h * 0.5f + 15.0f, w - 5.0f, h * 0.5f + 20.0f, w - 20.0f, h * 0.5f + 20.0f);
// 5. Conic in bottom-right region (fully inside viewport)
b.conicTo(w - 20.0f, h - 20.0f, w * 0.5f + 20.0f, h - 20.0f, SK_ScalarRoot2Over2);
// 6. Cross bottom boundary:
// 6a. Exit bottom (cubic from inside to Y = h + 15)
b.cubicTo(w * 0.5f + 20.0f, h - 5.0f, w * 0.5f + 15.0f, h + 15.0f, w * 0.5f + 10.0f, h + 15.0f);
// 6b. Fully outside bottom (conic at Y >= h + 15 > h, testing bottom culling)
b.conicTo(w * 0.5f, h + 25.0f, w * 0.5f - 10.0f, h + 15.0f, SK_ScalarRoot2Over2);
// 6c. Re-enter bottom (cubic from Y = h + 15 back inside to Y = h - 20)
b.cubicTo(w * 0.5f - 15.0f, h + 15.0f, w * 0.5f - 20.0f, h - 5.0f, w * 0.5f - 20.0f, h - 20.0f);
// 7. Conic in bottom-left region (fully inside viewport)
b.conicTo(20.0f, h - 20.0f, 20.0f, h * 0.5f + 20.0f, SK_ScalarRoot2Over2);
// 8. Cross left boundary (simplification):
// 8a. Exit left (cubic from inside to X = -15)
b.cubicTo(5.0f, h * 0.5f + 20.0f, -15.0f, h * 0.5f + 15.0f, -15.0f, h * 0.5f + 10.0f);
// 8b. Fully outside left (conic at X <= -15 < 0, testing left simplification)
b.conicTo(-25.0f, h * 0.5f, -15.0f, h * 0.5f - 10.0f, SK_ScalarRoot2Over2);
// 8c. Re-enter left (cubic from X = -15 back inside to (20, h * 0.5f))
b.cubicTo(-15.0f, h * 0.5f - 15.0f, 5.0f, h * 0.5f - 20.0f, 20.0f, h * 0.5f);
b.close();
return b.detach();
}
// A sliver whose right side is a line that only crosses the right edge of a 64 pixel wide clip at
// the origin, and ends exactly on the clip's left edge, just below y = 24 (a tile row boundary for
// both 4x4 and 8x8 tiles). Tiler computes the x at which the line enters its last tile row as
// roughly -1e-6 tiles, i.e. left of the clip, whether or not that computation is fused.
SkPath make_right_crossing_sliver() {
return SkPathBuilder()
.moveTo(72.0f, -256.0f)
.lineTo(0.0f, std::nextafter(24.0f, 25.0f))
.lineTo(0.0f, -256.0f)
.close()
.detach();
}
// A frame around a 100x100 `clip`, with two circles inside of it: a hole on the left, and a circle
// with winding 2 on the right. The left side of the frame crosses the clip's left edge a little
// under halfway down. Above that, its tiles enter through their top. Below that, it is culled into
// the WindingHistogram (partially as a cubic that Flatten simplifies to its chord), so each row
// through the circles starts with a WideTile from the clip's left edge to its first EndCap if the
// culled winding is filled, and the rows between and below the circles have no tiles at all.
SkPath make_frame_with_circles(const SkIRect& clip) {
const float l = static_cast<float>(clip.fLeft);
const float t = static_cast<float>(clip.fTop);
const float r = static_cast<float>(clip.fRight);
const float b = static_cast<float>(clip.fBottom);
SkPathBuilder builder;
builder.moveTo(l + 30.0f, t - 20.0f)
.lineTo(r + 20.0f, t - 20.0f)
.lineTo(r + 20.0f, b + 20.0f)
.lineTo(l - 30.0f, b + 20.0f)
.cubicTo(l - 45.0f, b - 5.0f, l - 40.0f, t + 70.0f, l - 8.0f, t + 64.0f)
.close();
builder.addCircle(l + 30.0f, t + 72.0f, 13.0f, SkPathDirection::kCCW);
builder.addCircle(l + 68.0f, t + 75.0f, 16.0f, SkPathDirection::kCW);
return builder.detach();
}
template <uint16_t kTileWidth, uint16_t kTileHeight>
void run_culling_simplification_suite(
skiatest::Reporter* reporter,
Recorder* recorder,
typename FastValidator<kTileWidth, kTileHeight>::StripFunc stripFunc,
const char* implName) {
const SparseStripConfig::LUTArray lut = GenerateMSAALUT();
constexpr uint16_t kVpWidth = 120;
constexpr uint16_t kVpHeight = 120;
std::array<uint32_t, 3> minorErrors = {0, 0, 0};
SkPath shape = make_cubic_conic_culling_shape(static_cast<float>(kVpWidth),
static_cast<float>(kVpHeight));
const SkPathFillType fillTypes[] = {
SkPathFillType::kWinding,
SkPathFillType::kEvenOdd,
SkPathFillType::kInverseWinding,
SkPathFillType::kInverseEvenOdd,
};
const float angles[] = {0.0f, 45.0f, 90.0f, 135.0f, 180.0f, 225.0f, 270.0f};
const SkPoint offsets[] = {
{0.0f, 0.0f},
{1.5f, 2.5f},
{-2.0f, 3.0f},
{kTileWidth * 0.5f, kTileHeight * 0.5f},
};
int testsRun = 0;
for (SkPathFillType ft : fillTypes) {
SkPath ftShape = shape;
ftShape.setFillType(ft);
for (float angle : angles) {
SkMatrix rotMatrix;
rotMatrix.setRotate(angle, kVpWidth * 0.5f, kVpHeight * 0.5f);
for (const SkPoint& off : offsets) {
SkMatrix transMatrix;
transMatrix.setTranslate(off.fX, off.fY);
SkMatrix ctm = SkMatrix::Concat(transMatrix, rotMatrix);
SkPath devicePath = ftShape.makeTransform(ctm);
SkString name;
name.printf("Culling_%s_(%dx%d)_Fill(%d)_Rot(%.0f)_Off(%.1f,%.1f)",
implName,
kTileWidth,
kTileHeight,
static_cast<int>(ft),
angle,
off.fX,
off.fY);
if (!FastValidator<kTileWidth, kTileHeight>::ValidatePath(reporter,
recorder,
devicePath,
SkMatrix::I(),
kVpWidth,
kVpHeight,
name.c_str(),
lut,
stripFunc,
&minorErrors)) {
return;
}
testsRun++;
}
}
}
INFOF(reporter,
"[%s (%dx%d)] Culling & Simplification Suite Complete. Ran %d variants. "
"Minor Errors: [1s:%u, 2s:%u, 3s:%u]\n",
implName,
kTileWidth,
kTileHeight,
testsRun,
minorErrors[0],
minorErrors[1],
minorErrors[2]);
}
// Draws whose verbs lie (almost) entirely outside of the viewport, and are therefore culled, but
// whose fill still covers the viewport. Since that coverage consists solely of WideTiles, this
// relies on FastValidator validating the tiles outside of the EndCaps. Also covers draws that
// combine culled winding with EndCaps inside of the clip, and clips away from the origin.
template <uint16_t kTileWidth, uint16_t kTileHeight>
void run_culled_geometry_suite(skiatest::Reporter* reporter,
Recorder* recorder,
typename FastValidator<kTileWidth, kTileHeight>::StripFunc stripFunc,
const char* implName) {
const SparseStripConfig::LUTArray lut = GenerateMSAALUT();
std::array<uint32_t, 3> minorErrors = {0, 0, 0};
struct TestCase {
SkPath path;
SkMatrix ctm;
SkIRect clip;
const char* name;
};
std::vector<TestCase> cases;
// A quad that covers all of a tall clip from outside of it. Its left side is split into two
// lines that meet halfway through tile row 32, so the upper line is culled into the
// WindingHistogram rows [0, 32), an entire 32-row word. Its other sides are above, right of,
// and below the clip.
const SkIRect tallClip = SkIRect::MakeWH(8 * kTileWidth, 64 * kTileHeight);
const float l = static_cast<float>(tallClip.fLeft);
const float t = static_cast<float>(tallClip.fTop);
const float r = static_cast<float>(tallClip.fRight);
const float b = static_cast<float>(tallClip.fBottom);
cases.push_back({SkPathBuilder()
.moveTo(l - 10.0f, t - 20.0f)
.lineTo(l - 12.0f, t + 32.5f * kTileHeight)
.lineTo(l - 10.0f, b + 20.0f)
.lineTo(r + 20.0f, b + 20.0f)
.lineTo(r + 20.0f, t - 20.0f)
.close()
.detach(),
SkMatrix::I(),
tallClip,
"SplitLeftEdge_TallClip"});
// Every verb is culled, so the closing edge is the only geometry contributing winding.
const SkIRect clip = SkIRect::MakeWH(100, 100);
cases.push_back({CullingRepros::AllVerbsCulledContour(clip, /*close=*/false),
SkMatrix::I(),
clip,
"AllVerbsCulled_ClosingEdgeLeft_Open"});
cases.push_back({CullingRepros::AllVerbsCulledContour(clip, /*close=*/true),
SkMatrix::I(),
clip,
"AllVerbsCulled_ClosingEdgeLeft_Closed"});
cases.push_back({SkPathBuilder()
.moveTo(50, -20)
.cubicTo(70, -60, 100, -60, 120, -20) // Above
.cubicTo(160, 20, 160, 80, 120, 120) // Right
.cubicTo(100, 160, 70, 160, 50, 120) // Below
.detach(),
SkMatrix::I(),
clip,
"AllVerbsCulled_ClosingEdgeCrossesClip"});
// All verbs and closing edges are above/right/below the clip, so Flatten culls everything and
// produces an empty Polyline. Inverse fills must still emit WideTiles covering the clip.
cases.push_back({SkPathBuilder()
.moveTo(10, -50)
.cubicTo(30, -60, 70, -60, 90, -50)
.cubicTo(90, -30, 30, -30, 10, -50)
.close()
.detach(),
SkMatrix::I(),
clip,
"AllVerbsCulled_EmptyPolyline_AboveClip"});
// Closed contour completely to the left of the clip: both vertical edges are culled into the
// WindingHistogram, cancelling out to net 0 winding with no tiles emitted.
cases.push_back(
{SkPathBuilder().addRect(SkRect::MakeLTRB(-40.0f, 16.0f, -10.0f, 80.0f)).detach(),
SkMatrix::I(),
clip,
"ClosedContour_CompletelyLeft"});
// Two overlapping contours whose left edges are culled to the left of the clip and right edges
// are culled to the right of the clip, producing winding 2 in rows [24, 72) and winding 1 in
// rows [0, 24) and [72, 100), distinguishing kWinding/kInverseWinding from
// kEvenOdd/kInverseEvenOdd in ProcessEmptyTileRows.
{
SkPathBuilder doubleLeft;
doubleLeft.addRect(SkRect::MakeLTRB(-20.0f, -10.0f, 120.0f, 110.0f), SkPathDirection::kCW);
doubleLeft.addRect(SkRect::MakeLTRB(-15.0f, 24.0f, 125.0f, 72.0f), SkPathDirection::kCW);
cases.push_back({doubleLeft.detach(), SkMatrix::I(), clip, "DoubleWinding_LeftEdges"});
}
// Exact inputs captured from the viewer.
const CullingRepros::Repro repro = CullingRepros::DeskTiger8Capture3Draw2();
cases.push_back({repro.fPath, repro.fCtm, repro.fClip, "DeskTiger8Capture3Draw2"});
// The line that crosses only the right edge of the clip must keep its winding in its tile
// rather than moving it into the WindingHistogram: Since no line crosses the left edge, Tiler
// reports no culling event, and MakeStrips ignores the histogram.
cases.push_back({make_right_crossing_sliver(),
SkMatrix::I(),
SkIRect::MakeWH(64, 32),
"RightCrossingSliver_EndsOnClipLeft"});
// Combines EndCaps, winding culled into the WindingHistogram, and the WideTiles that lead from
// the clip's left edge to the first EndCap of a row.
cases.push_back({make_frame_with_circles(clip), SkMatrix::I(), clip, "FrameWithCircles"});
const SkPathFillType fillTypes[] = {
SkPathFillType::kWinding,
SkPathFillType::kEvenOdd,
SkPathFillType::kInverseWinding,
SkPathFillType::kInverseEvenOdd,
};
// Each case is also drawn with its clip (and geometry) translated away from the origin, since
// StripGenerator draws most paths with such a clip. Translating by whole tiles preserves the
// tiling of each draw, and moves most of the geometry that is culled left of the clip to
// positive device coordinates.
constexpr SkIVector kClipOffset = {64, 48};
static_assert(kClipOffset.fX % kTileWidth == 0 && kClipOffset.fY % kTileHeight == 0);
for (const TestCase& testCase : cases) {
for (bool translate : {false, true}) {
const SkIRect drawClip =
translate ? testCase.clip.makeOffset(kClipOffset) : testCase.clip;
const SkMatrix drawCtm =
translate ? SkMatrix::Translate(kClipOffset) * testCase.ctm : testCase.ctm;
for (SkPathFillType ft : fillTypes) {
SkPath pathWithFill = testCase.path;
pathWithFill.setFillType(ft);
SkString name;
name.printf("CulledGeometry_%s_(%dx%d)_%s_Clip(%d,%d)_Fill(%d)",
implName,
kTileWidth,
kTileHeight,
testCase.name,
drawClip.fLeft,
drawClip.fTop,
static_cast<int>(ft));
if (!FastValidator<kTileWidth, kTileHeight>::ValidatePath(reporter,
recorder,
pathWithFill,
drawCtm,
drawClip,
name.c_str(),
lut,
stripFunc,
&minorErrors)) {
return;
}
}
}
}
// Also verify sub-viewport clip bounds (non-zero left/top and bottom < deviceHeight) with a
// WindingHistogram sized to the full device height, as used by StripGenerator.
constexpr uint16_t kDeviceWidth = 16 * kTileWidth;
constexpr uint16_t kDeviceHeight = 16 * kTileHeight;
constexpr uint16_t kDeviceRows = kDeviceHeight / kTileHeight;
const SkIRect subClip =
SkIRect::MakeLTRB(2 * kTileWidth, 3 * kTileHeight, 10 * kTileWidth, 11 * kTileHeight);
const uint16_t expectedSubClipRows = static_cast<uint16_t>((subClip.height()) / kTileHeight);
struct SubClipCase {
SkPath path;
uint16_t expectedWideRows;
const char* label;
};
SkPath invAbove = SkPathBuilder()
.moveTo(20.0f, -40.0f)
.cubicTo(30.0f, -50.0f, 50.0f, -50.0f, 60.0f, -40.0f)
.close()
.detach();
invAbove.setFillType(SkPathFillType::kInverseWinding);
SkPath invLeftClosed = SkPathBuilder()
.addRect(SkRect::MakeLTRB(
-30.0f, 4.0f * kTileHeight, -10.0f, 8.0f * kTileHeight))
.detach();
invLeftClosed.setFillType(SkPathFillType::kInverseEvenOdd);
SkPath regEnclosing =
SkPathBuilder()
.addRect(SkRect::MakeLTRB(
-20.0f, -20.0f, kDeviceWidth + 20.0f, kDeviceHeight + 20.0f))
.detach();
regEnclosing.setFillType(SkPathFillType::kWinding);
const SubClipCase subClipCases[] = {
{invAbove, expectedSubClipRows, "SubClip_InverseAbove_EmptyPolyline"},
{invLeftClosed, expectedSubClipRows, "SubClip_InverseLeftClosed_CulledZeroWinding"},
{regEnclosing, expectedSubClipRows, "SubClip_RegularEnclosing_CulledFullFill"},
};
for (const SubClipCase& sc : subClipCases) {
Flatten flattener;
Polyline polyline;
flattener.processPaths<FlattenMode::kSimd>(sc.path, SkMatrix::I(), subClip, &polyline);
WindingHistogram windingHistogram(kDeviceRows);
windingHistogram.resize(kDeviceRows);
windingHistogram.clear();
Tiles<kTileWidth, kTileHeight> tiler;
bool isCulled = tiler.makeTilesMSAA(polyline, subClip, &windingHistogram);
tiler.sortTiles();
WideTiles wides;
EndCaps ends;
AlphaAtlasManager atlasManager(recorder);
auto observer = [](uint8_t, skvx::int8) {};
stripFunc(tiler,
&wides,
&ends,
&atlasManager,
sc.path.getFillType(),
polyline,
lut,
isCulled,
windingHistogram,
subClip,
observer);
REPORTER_ASSERT(reporter,
ends.empty(),
"[%s_%s] Expected 0 EndCaps, got %zu",
implName,
sc.label,
ends.size());
REPORTER_ASSERT(reporter,
wides.size() == sc.expectedWideRows,
"[%s_%s] Expected %u WideTiles, got %zu",
implName,
sc.label,
sc.expectedWideRows,
wides.size());
for (size_t i = 0; i < wides.size(); ++i) {
const auto& wide = wides.tiles()[static_cast<int>(i)];
const uint16_t expectedY = static_cast<uint16_t>(subClip.top() + i * kTileHeight);
REPORTER_ASSERT(reporter,
wide.fX == subClip.left() && wide.fY == expectedY &&
wide.fWidth == subClip.width(),
"[%s_%s] WideTile[%zu] got (%u, %u, %u), want (%d, %u, %d)",
implName,
sc.label,
i,
wide.fX,
wide.fY,
wide.fWidth,
subClip.left(),
expectedY,
subClip.width());
}
if constexpr (kTileWidth == SparseStripConfig::kTileWidth &&
kTileHeight == SparseStripConfig::kTileHeight) {
StripGenerator gen(kDeviceWidth, kDeviceHeight, lut, recorder);
REPORTER_ASSERT(reporter, gen.processGeometry(sc.path, SkMatrix::I(), subClip));
REPORTER_ASSERT(reporter, gen.ends().empty());
REPORTER_ASSERT(reporter, gen.wides().size() == sc.expectedWideRows);
}
}
}
} // namespace
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_CoverageScalar_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_coverage_suite<4, 4>(
reporter, recorder.get(), &FastValidator<4, 4>::RunScalarWinding, "Scalar");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_CoverageSIMD_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_coverage_suite<4, 4>(
reporter, recorder.get(), &FastValidator<4, 4>::RunSimdWinding, "SIMD");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_CoverageSIMD_8x8,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_coverage_suite<8, 8>(
reporter, recorder.get(), &FastValidator<8, 8>::RunSimdWinding, "SIMD");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_CullingSimplification_SIMD_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_culling_simplification_suite<4, 4>(
reporter, recorder.get(), &FastValidator<4, 4>::RunSimdWinding, "SIMD");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_CullingSimplification_SIMD_8x8,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_culling_simplification_suite<8, 8>(
reporter, recorder.get(), &FastValidator<8, 8>::RunSimdWinding, "SIMD");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_CullingSimplification_Scalar_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_culling_simplification_suite<4, 4>(
reporter, recorder.get(), &FastValidator<4, 4>::RunScalarWinding, "Scalar");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_CulledGeometry_SIMD_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_culled_geometry_suite<4, 4>(
reporter, recorder.get(), &FastValidator<4, 4>::RunSimdWinding, "SIMD");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_CulledGeometry_SIMD_8x8,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_culled_geometry_suite<8, 8>(
reporter, recorder.get(), &FastValidator<8, 8>::RunSimdWinding, "SIMD");
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_CulledGeometry_Scalar_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
run_culled_geometry_suite<4, 4>(
reporter, recorder.get(), &FastValidator<4, 4>::RunScalarWinding, "Scalar");
}
// StripGenerator reuses its WindingHistogram across draws, and only clears it after a draw that
// culled geometry into it. So a draw that does not cull must leave the histogram untouched, or its
// winding leaks into the next draw that culls.
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_StripGeneratorHistogramReuse,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
const SparseStripConfig::LUTArray lut = GenerateMSAALUT();
constexpr int kDeviceSize = 64;
constexpr size_t kDeviceRows = kDeviceSize / SparseStripConfig::kTileHeight;
const SkIRect clip = SkIRect::MakeWH(kDeviceSize, kDeviceSize);
StripGenerator gen(kDeviceSize, kDeviceSize, lut, recorder.get());
// The sliver does not cross the clip's left edge, and does not entirely cover any tile.
const SkPath sliver = make_right_crossing_sliver();
REPORTER_ASSERT(reporter, gen.processGeometry(sliver, SkMatrix::I(), clip));
REPORTER_ASSERT(reporter,
gen.wides().empty(),
"Expected no WideTiles for the sliver, got %zu",
gen.wides().size());
// The left edge of a rect enclosing the device is culled, so each row is a single WideTile.
const SkPath enclosing =
SkPathBuilder()
.addRect(SkRect::MakeLTRB(
-20.0f, -20.0f, kDeviceSize + 20.0f, kDeviceSize + 20.0f))
.detach();
REPORTER_ASSERT(reporter, gen.processGeometry(enclosing, SkMatrix::I(), clip));
REPORTER_ASSERT(reporter, gen.ends().empty());
REPORTER_ASSERT(reporter,
gen.wides().size() == kDeviceRows,
"Expected %zu WideTiles for the enclosing rect, got %zu",
kDeviceRows,
gen.wides().size());
}
DEF_GRAPHITE_TEST_FOR_RENDERING_CONTEXTS(SparseStrips_Coverage_SKP_SIMD_4x4,
reporter,
context,
CtsEnforcement::kToBeDetermined) {
auto recorder = context->makeRecorder();
const SparseStripConfig::LUTArray lut = GenerateMSAALUT();
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 SparseStripConfig::LUTArray lut = GenerateMSAALUT();
SkpValidator::ValidateSkp<8, 8>(reporter, recorder.get(), "skps/desk_tiger8svg.skp", lut);
}
} // namespace skgpu::graphite