blob: f459c37b2ef9f750578b6af021e0b2a323c8b627 [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 "tests/graphite/sparse_strips/SkpValidator.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
#include "include/core/SkPathUtils.h"
#include "include/core/SkPicture.h"
#include "include/core/SkRect.h"
#include "include/core/SkStream.h"
#include "include/core/SkString.h"
#include "include/private/SkTDArray.h"
#include "src/core/SkVx.h"
#include "src/gpu/graphite/sparse_strips/Flatten.h"
#include "src/gpu/graphite/sparse_strips/MakeStrips.h"
#include "src/gpu/graphite/sparse_strips/Polyline.h"
#include "src/gpu/graphite/sparse_strips/Strip.h"
#include "src/gpu/graphite/sparse_strips/Tiler.h"
#include "tests/Test.h"
#include "tests/graphite/sparse_strips/OracleValidator.h"
#include "tools/ToolUtils.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstdint>
#include <vector>
namespace skgpu::graphite {
std::vector<SkpValidator::ExtractedPath> SkpValidator::ExtractPaths(const char* filepath) {
std::vector<ExtractedPath> paths;
auto callback = [&](const SkMatrix& ctm, const SkPath& path, const SkPaint& paint) {
SkPath fillPath = skpathutils::FillPathWithPaint(path, paint);
SkPath devPath = fillPath.makeTransform(ctm);
paths.push_back({path, paint, ctm, devPath});
};
ToolUtils::ExtractPathsFromSKP(filepath, callback);
return paths;
}
std::vector<SkpValidator::ExtractedPath> SkpValidator::ExtractPaths(SkStream* stream) {
if (!stream) {
return {};
}
sk_sp<SkPicture> picture = SkPicture::MakeFromStream(stream, nullptr);
if (!picture) {
return {};
}
return ExtractPaths(picture.get());
}
std::vector<SkpValidator::ExtractedPath> SkpValidator::ExtractPaths(const SkPicture* picture) {
if (!picture) {
return {};
}
class PathSniffer : public SkCanvas {
public:
explicit PathSniffer(std::function<ToolUtils::PathSniffCallback> callback)
: SkCanvas(4096, 4096, nullptr), fCallback(callback) {}
private:
void onDrawPath(const SkPath& path, const SkPaint& paint) override {
fCallback(this->getTotalMatrix(), path, paint);
}
std::function<ToolUtils::PathSniffCallback> fCallback;
};
std::vector<ExtractedPath> paths;
auto callback = [&](const SkMatrix& ctm, const SkPath& path, const SkPaint& paint) {
SkPath fillPath = skpathutils::FillPathWithPaint(path, paint);
SkPath devPath = fillPath.makeTransform(ctm);
paths.push_back({path, paint, ctm, devPath});
};
PathSniffer sniffer(callback);
picture->playback(&sniffer);
return paths;
}
template <uint16_t kTileWidth, uint16_t kTileHeight>
bool SkpValidator::ValidatePath(skiatest::Reporter* reporter,
const SkPath& path,
const char* testName,
const SkTDArray<uint8_t>& maskLut,
std::array<uint32_t, 3>* minorErrorCount) {
if (path.isEmpty() || !path.isFinite()) {
return true;
}
SkRect bounds = path.getBounds();
if (bounds.width() <= 0.0f || bounds.height() <= 0.0f) {
return true;
}
float margin = 4.0f;
SkMatrix trans;
trans.setTranslate(margin - bounds.fLeft, margin - bounds.fTop);
SkPath localPath = path.makeTransform(trans);
SkRect localBounds = localPath.getBounds();
uint16_t rawWidth = static_cast<uint16_t>(std::ceil(localBounds.fRight + margin));
uint16_t rawHeight = static_cast<uint16_t>(std::ceil(localBounds.fBottom + margin));
uint16_t vpWidth = (rawWidth + kTileWidth - 1) & ~(kTileWidth - 1);
uint16_t vpHeight = (rawHeight + kTileHeight - 1) & ~(kTileHeight - 1);
Flatten flattener;
Polyline polyline;
flattener.processPaths<FlattenMode::kSimd>(localPath,
SkMatrix(),
static_cast<float>(vpWidth),
static_cast<float>(vpHeight),
&polyline);
if (polyline.count() == 0) {
return true;
}
Tiles<kTileWidth, kTileHeight> tiler;
tiler.makeTilesMSAA(polyline, vpWidth, vpHeight);
tiler.sortTiles();
if (tiler.getTiles().empty()) {
return true;
}
SkTDArray<Strip> stripBuf;
SkTDArray<uint8_t> alphaBuf;
SkTDArray<skvx::int8> exactWindings;
auto observer = [&](uint8_t exactMask, skvx::int8 winding) {
exactWindings.push_back(winding);
};
MakeStrips::MsaaSimd(
tiler, &stripBuf, &alphaBuf, localPath.getFillType(), polyline, maskLut, observer);
OracleValidator<kTileWidth, kTileHeight> validator(
localPath,
stripBuf,
alphaBuf,
exactWindings,
&polyline,
&tiler,
testName,
OracleValidator<kTileWidth, kTileHeight>::Strictness::kNonStrict);
uint32_t maxSampleDiff = 0;
bool pass = validator.validate(reporter, vpWidth, vpHeight, &maxSampleDiff);
if (minorErrorCount && maxSampleDiff > 0 && maxSampleDiff <= 3) {
(*minorErrorCount)[maxSampleDiff - 1]++;
}
return pass;
}
template <uint16_t kTileWidth, uint16_t kTileHeight>
bool SkpValidator::ValidateSkp(skiatest::Reporter* reporter,
const char* filepath,
const SkTDArray<uint8_t>& maskLut) {
auto paths = ExtractPaths(filepath);
if (paths.empty()) {
INFOF(reporter, "[SkpValidator] SKP file not found or empty: %s", filepath);
return true;
}
int count = static_cast<int>(paths.size());
std::array<uint32_t, 3> minorErrors = {0, 0, 0};
int passed = 0;
for (int i = 0; i < count; ++i) {
SkString name;
name.printf("%s_path_%d", filepath, i);
if (ValidatePath<kTileWidth, kTileHeight>(
reporter, paths[i].devicePath, name.c_str(), maskLut, &minorErrors)) {
passed++;
}
}
INFOF(reporter,
"[SIMD (%dx%d)] Tested %d / %d paths from '%s'. Passed: %d. Minor Errors: [1s:%u, "
"2s:%u, 3s:%u]\n",
kTileWidth,
kTileHeight,
count,
static_cast<int>(paths.size()),
filepath,
passed,
minorErrors[0],
minorErrors[1],
minorErrors[2]);
return passed == count;
}
// Explicit template instantiations for SkpValidator
template bool SkpValidator::ValidatePath<4, 4>(skiatest::Reporter*,
const SkPath&,
const char*,
const SkTDArray<uint8_t>&,
std::array<uint32_t, 3>*);
template bool SkpValidator::ValidatePath<8, 8>(skiatest::Reporter*,
const SkPath&,
const char*,
const SkTDArray<uint8_t>&,
std::array<uint32_t, 3>*);
template bool SkpValidator::ValidateSkp<4, 4>(skiatest::Reporter*,
const char*,
const SkTDArray<uint8_t>&);
template bool SkpValidator::ValidateSkp<8, 8>(skiatest::Reporter*,
const char*,
const SkTDArray<uint8_t>&);
} // namespace skgpu::graphite