blob: f0cba7bb0ab0f6c61cb367cede635cc3f1f87b3e [file] [log] [blame]
/*
* Copyright 2025 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tests/Test.h"
#if defined(SK_GRAPHITE)
#include "include/gpu/graphite/precompile/PrecompileColorFilter.h"
#include "include/gpu/graphite/precompile/PrecompileRuntimeEffect.h"
#include "include/gpu/graphite/precompile/PrecompileShader.h"
#include "src/base/SkMathPriv.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/ContextUtils.h"
#include "src/gpu/graphite/GraphicsPipelineDesc.h"
#include "src/gpu/graphite/PrecompileContextPriv.h"
#include "src/gpu/graphite/RenderPassDesc.h"
#include "src/gpu/graphite/RendererProvider.h"
#include "tests/graphite/precompile/PrecompileTestUtils.h"
#include "tools/graphite/UniqueKeyUtils.h"
#if defined (SK_VULKAN)
#include "include/gpu/vk/VulkanTypes.h"
#include "src/base/SkBase64.h"
#include "src/gpu/graphite/vk/VulkanYcbcrConversion.h"
#endif // SK_VULKAN
#include <cstring>
#include <set>
using namespace skgpu::graphite;
using PrecompileShaders::ImageShaderFlags;
using ::skgpu::graphite::DrawTypeFlags;
using ::skgpu::graphite::PaintOptions;
using ::skgpu::graphite::RenderPassProperties;
// Used in lieu of SkEnumBitMask
static constexpr DrawTypeFlags operator|(DrawTypeFlags a, DrawTypeFlags b) {
return static_cast<DrawTypeFlags>(static_cast<std::underlying_type<DrawTypeFlags>::type>(a) |
static_cast<std::underlying_type<DrawTypeFlags>::type>(b));
}
namespace PrecompileTestUtils {
#if defined(SK_VULKAN)
void Base642YCbCr(const char* str) {
size_t expectedDstLength;
SkBase64::Error error = SkBase64::Decode(str, strlen(str), nullptr, &expectedDstLength);
if (error != SkBase64::kNoError) {
return;
}
if (expectedDstLength % 4 != 0) {
return;
}
int numInts = expectedDstLength / 4;
skia_private::AutoTMalloc<uint32_t> dst(numInts);
size_t actualDstLength;
error = SkBase64::Decode(str, strlen(str), dst, &actualDstLength);
if (error != SkBase64::kNoError || expectedDstLength != actualDstLength) {
return;
}
SamplerDesc s(dst[0], dst[1], dst[2]);
SkDebugf("tileModes: %d %d filterMode: %d mipmap: %d ",
static_cast<int>(s.tileModeX()),
static_cast<int>(s.tileModeY()),
static_cast<int>(s.filterMode()),
static_cast<int>(s.mipmap()));
skgpu::VulkanYcbcrConversionInfo info =
VulkanYcbcrConversion::FromImmutableSamplerInfo(s.immutableSamplerInfo());
SkDebugf("VulkanYcbcrConversionInfo: format: %d extFormat: %llu model: %d range: %d "
"xOff: %d yOff: %d filter: %d explicit: %u features: %u components: %d %d %d %d\n",
info.fFormat,
(unsigned long long) info.fExternalFormat,
info.fYcbcrModel,
info.fYcbcrRange,
info.fXChromaOffset,
info.fYChromaOffset,
info.fChromaFilter,
info.fForceExplicitReconstruction,
info.fFormatFeatures,
info.fComponents.r,
info.fComponents.g,
info.fComponents.b,
info.fComponents.a);
}
#endif // SK_VULKAN
namespace {
[[maybe_unused]] void find_duplicates(SkSpan<const PipelineLabel> cases) {
for (size_t i = 0; i < std::size(cases); ++i) {
for (size_t j = i+1; j < std::size(cases); ++j) {
if (!strcmp(cases[i].fString, cases[j].fString)) {
SkDebugf("Duplicate %zu && %zu\n", i, j);
}
}
}
}
std::string rm_whitespace(const std::string& s) {
auto start = s.find_first_not_of(' ');
auto end = s.find_last_not_of(' ');
return s.substr(start, (end - start) + 1);
}
} // anonymous namespace
PipelineLabelInfoCollector::PipelineLabelInfoCollector(SkSpan<const PipelineLabel> cases,
SkipFunc skip) {
for (size_t i = 0; i < std::size(cases); ++i) {
const char* testStr = cases[i].fString;
if (skip(testStr)) {
fMap.insert({ testStr, PipelineLabelInfo(i, PipelineLabelInfo::kSkipped) });
} else {
fMap.insert({ testStr, PipelineLabelInfo(i) });
}
}
}
int PipelineLabelInfoCollector::processLabel(const std::string& precompiledLabel,
int precompileCase) {
++fNumLabelsProcessed;
auto result = fMap.find(precompiledLabel.c_str());
if (result == fMap.end()) {
SkDEBUGCODE(auto prior = fOverGenerated.find(precompiledLabel);)
SkASSERTF(prior == fOverGenerated.end(),
"duplicate (unused) Pipeline found for cases %d %d:\n%s\n",
prior->second.fOriginatingSetting,
precompileCase,
precompiledLabel.c_str());
fOverGenerated.insert({ precompiledLabel, OverGenInfo(precompileCase) });
return -1;
}
// We expect each PrecompileSettings case to handle disjoint sets of labels. If this
// assert fires some pair of PrecompileSettings are handling the same case.
SkASSERTF(result->second.fPrecompileCase == PipelineLabelInfo::kUninit,
"cases %d and %d cover the same label",
result->second.fPrecompileCase, precompileCase);
result->second.fPrecompileCase = precompileCase;
return result->second.fCasesIndex;
}
void PipelineLabelInfoCollector::finalReport() {
std::vector<int> skipped, missed;
int numCovered = 0, numIntentionallySkipped = 0, numMissed = 0;
for (const auto& iter : fMap) {
if (iter.second.fPrecompileCase == PipelineLabelInfo::kSkipped) {
++numIntentionallySkipped;
skipped.push_back(iter.second.fCasesIndex);
} else if (iter.second.fPrecompileCase == PipelineLabelInfo::kUninit) {
++numMissed;
missed.push_back(iter.second.fCasesIndex);
} else {
SkASSERT(iter.second.fPrecompileCase >= 0);
++numCovered;
}
}
SkASSERT(numMissed == (int) missed.size());
SkASSERT(numIntentionallySkipped == (int) skipped.size());
SkDebugf("-----------------------\n");
sort(missed.begin(), missed.end());
SkDebugf("not covered: ");
for (int i : missed) {
SkDebugf("%d, ", i);
}
SkDebugf("\n");
sort(skipped.begin(), skipped.end());
SkDebugf("skipped: ");
for (int i : skipped) {
SkDebugf("%d, ", i);
}
SkDebugf("\n");
SkASSERT(numCovered + static_cast<int>(fOverGenerated.size()) == fNumLabelsProcessed);
SkDebugf("covered %d notCovered %d skipped %d total %zu\n",
numCovered,
numMissed,
numIntentionallySkipped,
fMap.size());
SkDebugf("%d Pipelines were generated\n", fNumLabelsProcessed);
SkDebugf("of that %zu Pipelines were over-generated:\n", fOverGenerated.size());
#if 0 // enable to print out a list of the over-generated Pipeline labels
for (const auto& s : fOverGenerated) {
SkDebugf("from %d: %s\n", s.second.fOriginatingSetting, s.first.c_str());
}
#endif
}
// Precompile with the provided PrecompileSettings then verify that:
// 1) some case in 'kCases' is covered
// 2) more than 40% of the generated Pipelines are in kCases
void RunTest(skgpu::graphite::PrecompileContext* precompileContext,
skiatest::Reporter* reporter,
const PrecompileSettings& settings,
int precompileSettingsIndex,
SkSpan<const PipelineLabel> cases,
PipelineLabelInfoCollector* collector) {
using namespace skgpu::graphite;
precompileContext->priv().globalCache()->resetGraphicsPipelines();
Precompile(precompileContext,
settings.fPaintOptions,
settings.fDrawTypeFlags,
settings.fRenderPassProps);
if (settings.fAnalyticClipping) {
Precompile(precompileContext,
settings.fPaintOptions,
settings.fDrawTypeFlags | DrawTypeFlags::kAnalyticClip,
settings.fRenderPassProps);
}
std::set<std::string> generatedLabels;
{
const RendererProvider* rendererProvider = precompileContext->priv().rendererProvider();
const ShaderCodeDictionary* dict = precompileContext->priv().shaderCodeDictionary();
std::vector<skgpu::UniqueKey> generatedKeys;
UniqueKeyUtils::FetchUniqueKeys(precompileContext, &generatedKeys);
for (const skgpu::UniqueKey& key : generatedKeys) {
GraphicsPipelineDesc pipelineDesc;
RenderPassDesc renderPassDesc;
UniqueKeyUtils::ExtractKeyDescs(precompileContext, key, &pipelineDesc, &renderPassDesc);
const RenderStep* renderStep = rendererProvider->lookup(pipelineDesc.renderStepID());
std::string tmp = GetPipelineLabel(dict, renderPassDesc, renderStep,
pipelineDesc.paintParamsID());
generatedLabels.insert(rm_whitespace(tmp));
}
}
std::vector<bool> localMatches;
std::vector<size_t> matchesInCases;
for (const std::string& g : generatedLabels) {
int matchInCases = collector->processLabel(g, precompileSettingsIndex);
localMatches.push_back(matchInCases >= 0);
if (matchInCases >= 0) {
matchesInCases.push_back(matchInCases);
}
}
REPORTER_ASSERT(reporter, matchesInCases.size() >= 1, // This tests requirement 1, above
"%d: num matches: %zu", precompileSettingsIndex, matchesInCases.size());
float utilization = ((float) matchesInCases.size())/generatedLabels.size();
REPORTER_ASSERT(reporter, utilization >= 0.4f, // This tests requirement 2, above
"%d: utilization: %f", precompileSettingsIndex, utilization);
#if defined(PRINT_COVERAGE)
// This block will print out all the cases in 'kCases' that the given PrecompileSettings
// covered.
sort(matchesInCases.begin(), matchesInCases.end());
SkDebugf("// %d: %d%% (%zu/%zu) handles: ",
precompileSettingsIndex,
SkScalarRoundToInt(utilization * 100),
matchesInCases.size(), generatedLabels.size());
for (size_t h : matchesInCases) {
SkDebugf("%zu ", h);
}
SkDebugf("\n");
#endif
#if defined(PRINT_GENERATED_LABELS)
// This block will print out all the labels from the given PrecompileSettings marked with
// whether they were found in 'kCases'. This is useful for analyzing the set of Pipelines
// generated by a single PrecompileSettings and is usually used along with 'kChosenCase'.
SkASSERT(localMatches.size() == generatedLabels.size());
int index = 0;
for (const std::string& g : generatedLabels) {
SkDebugf("%c %d: %s\n", localMatches[index] ? 'h' : ' ', index, g.c_str());
++index;
}
#endif
}
} // namespace PrecompileTestUtils
#endif // SK_GRAPHITE