| /* | |
| * 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/gpu/graphite/Recorder.h" | |
| #include "include/private/SkTDArray.h" | |
| #include "src/core/SkVx.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/MakeStrips.h" | |
| #include "src/gpu/graphite/sparse_strips/Polyline.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, | |
| Recorder* recorder, | |
| const SkPath& path, | |
| const char* testName, | |
| const SparseStripConfig::LUTArray& 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); | |
| SkIRect clipRect = SkIRect::MakeWH(vpWidth, vpHeight); | |
| Flatten flattener; | |
| Polyline polyline; | |
| flattener.processPaths<FlattenMode::kSimd>(localPath, SkMatrix(), clipRect, &polyline); | |
| if (polyline.count() == 0 && !localPath.isInverseFillType()) { | |
| return true; | |
| } | |
| uint16_t numTileRows = (vpHeight + kTileHeight - 1) / kTileHeight; | |
| WindingHistogram windingHistogram(numTileRows); | |
| windingHistogram.resize(numTileRows); | |
| windingHistogram.clear(); | |
| Tiles<kTileWidth, kTileHeight> tiler; | |
| bool isCulled = tiler.makeTilesMSAA(polyline, clipRect, &windingHistogram); | |
| tiler.sortTiles(); | |
| if (tiler.getTiles().empty() && !isCulled && | |
| !SkPathFillType_IsInverse(localPath.getFillType())) { | |
| return true; | |
| } | |
| SkTDArray<skvx::int8> exactWindings; | |
| auto observer = [&](uint8_t exactMask, skvx::int8 winding) { | |
| exactWindings.push_back(winding); | |
| }; | |
| WideTiles wides; | |
| EndCaps ends; | |
| AlphaAtlasManager atlasManager(recorder); | |
| MakeStrips::MsaaSimd<kTileWidth, kTileHeight>(tiler, | |
| &wides, | |
| &ends, | |
| &atlasManager, | |
| localPath.getFillType(), | |
| polyline, | |
| maskLut, | |
| isCulled, | |
| windingHistogram, | |
| clipRect, | |
| observer); | |
| OracleValidator<kTileWidth, kTileHeight> validator( | |
| localPath, | |
| wides, | |
| ends, | |
| 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, | |
| Recorder* recorder, | |
| const char* filepath, | |
| const SparseStripConfig::LUTArray& 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, recorder, 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*, | |
| Recorder*, | |
| const SkPath&, | |
| const char*, | |
| const SparseStripConfig::LUTArray&, | |
| std::array<uint32_t, 3>*); | |
| template bool SkpValidator::ValidatePath<8, 8>(skiatest::Reporter*, | |
| Recorder*, | |
| const SkPath&, | |
| const char*, | |
| const SparseStripConfig::LUTArray&, | |
| std::array<uint32_t, 3>*); | |
| template bool SkpValidator::ValidateSkp<4, 4>(skiatest::Reporter*, | |
| Recorder*, | |
| const char*, | |
| const SparseStripConfig::LUTArray&); | |
| template bool SkpValidator::ValidateSkp<8, 8>(skiatest::Reporter*, | |
| Recorder*, | |
| const char*, | |
| const SparseStripConfig::LUTArray&); | |
| } // namespace skgpu::graphite |