blob: cd131c56ce699a30c6c56b84d495780c2e8702aa [file] [log] [blame]
/*
* Copyright 2022 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/core/SkColorSpace.h"
#include "include/effects/SkRuntimeEffect.h"
#include "include/gpu/graphite/precompile/PrecompileBlender.h"
#include "include/gpu/graphite/precompile/PrecompileColorFilter.h"
#include "include/gpu/graphite/precompile/PrecompileRuntimeEffect.h"
#include "include/gpu/graphite/precompile/PrecompileShader.h"
#include "src/gpu/graphite/ContextPriv.h"
#include "src/gpu/graphite/KeyContext.h"
#include "src/gpu/graphite/PipelineData.h"
#include "src/gpu/graphite/PrecompileInternal.h"
#include "src/gpu/graphite/RenderPassDesc.h"
#include "src/gpu/graphite/Renderer.h"
#include "src/gpu/graphite/RuntimeEffectDictionary.h"
#include "src/gpu/graphite/precompile/PaintOptionsPriv.h"
#include <array>
using namespace::skgpu::graphite;
namespace {
// colorfilters
static constexpr int kExpectedBlendCFCombos = 15;
static constexpr int kExpectedColorSpaceCFCombos = 1;
static constexpr int kExpectedHighContrastCFCombos = 1;
static constexpr int kExpectedLightingCFCombos = 1;
static constexpr int kExpectedLumaCFCombos = 1;
static constexpr int kExpectedMatrixCFCombos = 1;
static constexpr int kExpectedOverdrawCFCombos = 1;
static constexpr int kExpectedTableCFCombos = 1;
// shaders
static constexpr int kExpectedGradientCombos = 3;
static constexpr int kExpectedImageCombos = 24;
static constexpr int kExpectedPerlinNoiseCombos = 1;
static constexpr int kExpectedPictureCombos = 48;
static constexpr int kExpectedRawImageCombos = 10;
static constexpr int kExpectedSolidColorCombos = 1;
// A default kSrcOver blend mode will be supplied if no other blend options are added
void no_blend_mode_option_test(const KeyContext& keyContext,
const RenderPassDesc& renderPassDesc,
skiatest::Reporter* reporter) {
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::Color() });
REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == 1);
std::vector<UniquePaintParamsID> precompileIDs;
paintOptions.priv().buildCombinations(keyContext,
DrawTypeFlags::kNone,
/* withPrimitiveBlender= */ false,
Coverage::kNone,
renderPassDesc,
[&precompileIDs](UniquePaintParamsID id,
DrawTypeFlags,
bool /* withPrimitiveBlender */,
Coverage,
const RenderPassDesc&) {
precompileIDs.push_back(id);
});
SkASSERT(precompileIDs.size() == 1);
}
// This test checks that the 'PaintOptions::numCombinations' method and the number actually
// generated by 'buildCombinations' agree with the expected number of combinations.
void run_test(const KeyContext& keyContext,
const RenderPassDesc& renderPassDesc,
skiatest::Reporter* reporter,
const PaintOptions& paintOptions,
int expectedNumOptions) {
REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == expectedNumOptions,
"expected %d, but was %d",
expectedNumOptions, paintOptions.priv().numCombinations());
std::vector<UniquePaintParamsID> precompileIDs;
paintOptions.priv().buildCombinations(keyContext,
DrawTypeFlags::kNone,
/* withPrimitiveBlender= */ false,
Coverage::kNone,
renderPassDesc,
[&precompileIDs](UniquePaintParamsID id,
DrawTypeFlags,
bool /* withPrimitiveBlender */,
Coverage,
const RenderPassDesc&) {
precompileIDs.push_back(id);
});
SkASSERT(static_cast<int>(precompileIDs.size()) == expectedNumOptions);
}
void big_test(const KeyContext& keyContext,
const RenderPassDesc& renderPassDesc,
skiatest::Reporter* reporter) {
static constexpr int kNumExpected = 1596;
// paintOptions (1596 = 4*399)
// |- (399 = 3+396) sweepGrad_0 (3) |
// | blendShader_0 (396 = 1*4*99)
// | |- 0: (1) kSrc (1)
// | |- 1: (4=3+1) (dsts) linearGrad_0 (3) | solid_0 (1)
// | |- 2: (99=3+96) (srcs) linearGrad_1 (3) |
// | blendShader_1 (96=1*4*24)
// | |- 0: (1) kDst (1)
// | |- 1: (4=3+1) (dsts) radGrad_0 (3) | solid_1 (1)
// | |- 2: (24) (srcs) imageShader_0 (24)
// |
// |- (4) 4-built-in-blend-modes
PaintOptions paintOptions;
// first, shaders. First top-level option (sweepGrad_0)
sk_sp<PrecompileShader> sweepGrad_0 = PrecompileShaders::SweepGradient();
std::array<SkBlendMode, 1> blendModes{ SkBlendMode::kSrc };
std::vector<SkBlendMode> moreBlendModes{ SkBlendMode::kDst };
// Second top-level option (blendShader_0)
auto blendShader_0 = PrecompileShaders::Blend(
SkSpan<const SkBlendMode>(blendModes), // std::array
{ // initializer_list
PrecompileShaders::LinearGradient(),
PrecompileShaders::Color()
},
{
PrecompileShaders::LinearGradient(),
PrecompileShaders::Blend(
SkSpan<const SkBlendMode>(moreBlendModes),// std::vector
{
PrecompileShaders::RadialGradient(),
PrecompileShaders::Color()
},
{
PrecompileShaders::Image()
})
});
paintOptions.setShaders({ sweepGrad_0, blendShader_0 });
static const SkBlendMode kEvenMoreBlendModes[] = {
SkBlendMode::kSrcOver,
SkBlendMode::kSrc,
SkBlendMode::kDstOver,
SkBlendMode::kDst
};
// now, blend modes
paintOptions.setBlendModes(kEvenMoreBlendModes); // c array
REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == kNumExpected,
"Actual # of combinations %d, expected %d",
paintOptions.priv().numCombinations(),
kNumExpected);
std::vector<UniquePaintParamsID> precompileIDs;
paintOptions.priv().buildCombinations(keyContext,
DrawTypeFlags::kNone,
/* withPrimitiveBlender= */ false,
Coverage::kNone,
renderPassDesc,
[&precompileIDs](UniquePaintParamsID id,
DrawTypeFlags,
bool /* withPrimitiveBlender */,
Coverage,
const RenderPassDesc&) {
precompileIDs.push_back(id);
});
SkASSERT(precompileIDs.size() == kNumExpected);
}
template <typename T>
std::vector<sk_sp<T>> create_runtime_combos(
skiatest::Reporter* reporter,
SkRuntimeEffect::Result effectFactory(SkString),
sk_sp<T> precompileFactory(sk_sp<SkRuntimeEffect>,
SkSpan<const PrecompileChildOptions> childOptions),
const char* redCode,
const char* greenCode,
const char* blueCode,
const char* combineCode) {
auto [redEffect, error1] = effectFactory(SkString(redCode));
REPORTER_ASSERT(reporter, redEffect, "%s", error1.c_str());
auto [greenEffect, error2] = effectFactory(SkString(greenCode));
REPORTER_ASSERT(reporter, greenEffect, "%s", error2.c_str());
auto [blueEffect, error3] = effectFactory(SkString(blueCode));
REPORTER_ASSERT(reporter, blueEffect, "%s", error3.c_str());
auto [combineEffect, error4] = effectFactory(SkString(combineCode));
REPORTER_ASSERT(reporter, combineEffect, "%s", error4.c_str());
sk_sp<T> red = precompileFactory(redEffect, {});
REPORTER_ASSERT(reporter, red);
sk_sp<T> green = precompileFactory(greenEffect, {});
REPORTER_ASSERT(reporter, green);
sk_sp<T> blue = precompileFactory(blueEffect, {});
REPORTER_ASSERT(reporter, blue);
sk_sp<T> combine = precompileFactory(combineEffect, { { red, green },
{ blue, sk_sp<T>(nullptr) } });
REPORTER_ASSERT(reporter, combine);
return { combine };
}
void runtime_effect_test(const KeyContext& keyContext,
const RenderPassDesc& renderPassDesc,
skiatest::Reporter* reporter) {
// paintOptions (64 = 4*4*4)
// |- combineShader (4)
// | 0: redShader | greenShader
// | 1: blueShader | nullptr
// |
// |- combineColorFilter (4)
// | 0: redColorFilter | greenColorFilter
// | 1: blueColorFilter | nullptr
// |
// |- combineBlender (4)
// | 0: redBlender | greenBlender
// | 1: blueBlender | nullptr
PaintOptions paintOptions;
// shaders
{
static const char* kRedS = R"(
half4 main(vec2 fragcoord) { return half4(.5, 0, 0, .5); }
)";
static const char* kGreenS = R"(
half4 main(vec2 fragcoord) { return half4(0, .5, 0, .5); }
)";
static const char* kBlueS = R"(
half4 main(vec2 fragcoord) { return half4(0, 0, .5, .5); }
)";
static const char* kCombineS = R"(
uniform shader first;
uniform shader second;
half4 main(vec2 fragcoords) {
return first.eval(fragcoords) + second.eval(fragcoords);
}
)";
std::vector<sk_sp<PrecompileShader>> combinations =
create_runtime_combos<PrecompileShader>(reporter,
SkRuntimeEffect::MakeForShader,
PrecompileRuntimeEffects::MakePrecompileShader,
kRedS,
kGreenS,
kBlueS,
kCombineS);
paintOptions.setShaders(combinations);
}
// color filters
{
static const char* kRedCF = R"(
half4 main(half4 color) { return half4(.5, 0, 0, .5); }
)";
static const char* kGreenCF = R"(
half4 main(half4 color) { return half4(0, .5, 0, .5); }
)";
static const char* kBlueCF = R"(
half4 main(half4 color) { return half4(0, 0, .5, .5); }
)";
static const char* kCombineCF = R"(
uniform colorFilter first;
uniform colorFilter second;
half4 main(half4 color) { return first.eval(color) + second.eval(color); }
)";
std::vector<sk_sp<PrecompileColorFilter>> combinations =
create_runtime_combos<PrecompileColorFilter>(
reporter,
SkRuntimeEffect::MakeForColorFilter,
PrecompileRuntimeEffects::MakePrecompileColorFilter,
kRedCF,
kGreenCF,
kBlueCF,
kCombineCF);
paintOptions.setColorFilters(combinations);
}
// blenders
{
static const char* kRedB = R"(
half4 main(half4 src, half4 dst) { return half4(.5, 0, 0, .5); }
)";
static const char* kGreenB = R"(
half4 main(half4 src, half4 dst) { return half4(0, .5, 0, .5); }
)";
static const char* kBlueB = R"(
half4 main(half4 src, half4 dst) { return half4(0, 0, .5, .5); }
)";
static const char* kCombineB = R"(
uniform blender first;
uniform blender second;
half4 main(half4 src, half4 dst) {
return first.eval(src, dst) + second.eval(src, dst);
}
)";
std::vector<sk_sp<PrecompileBlender>> combinations =
create_runtime_combos<PrecompileBlender>(
reporter,
SkRuntimeEffect::MakeForBlender,
PrecompileRuntimeEffects::MakePrecompileBlender,
kRedB,
kGreenB,
kBlueB,
kCombineB);
paintOptions.setBlenders(combinations);
}
REPORTER_ASSERT(reporter, paintOptions.priv().numCombinations() == 64);
std::vector<UniquePaintParamsID> precompileIDs;
paintOptions.priv().buildCombinations(keyContext,
DrawTypeFlags::kNone,
/* withPrimitiveBlender= */ false,
Coverage::kNone,
renderPassDesc,
[&precompileIDs](UniquePaintParamsID id,
DrawTypeFlags,
bool /* withPrimitiveBlender */,
Coverage,
const RenderPassDesc&) {
precompileIDs.push_back(id);
});
SkASSERT(precompileIDs.size() == 64);
}
// Exercise all the PrecompileBlenders factories
void blend_subtest(const KeyContext& keyContext,
const RenderPassDesc& renderPassDesc,
skiatest::Reporter* reporter) {
// The BlendMode PrecompileBlender only ever has 1 combination
{
PaintOptions paintOptions;
paintOptions.setBlenders({ PrecompileBlenders::Mode(SkBlendMode::kColorDodge) });
run_test(keyContext, renderPassDesc, reporter, paintOptions, /* expectedNumOptions= */ 1);
}
// Specifying the BlendMode PrecompileBlender by SkBlendMode should also only ever
// yield 1 combination.
{
PaintOptions paintOptions;
paintOptions.setBlendModes({ SkBlendMode::kSrcOver });
run_test(keyContext, renderPassDesc, reporter, paintOptions, /* expectedNumOptions= */ 1);
}
// The Arithmetic PrecompileBlender only ever has 1 combination
{
PaintOptions paintOptions;
paintOptions.setBlenders({ PrecompileBlenders::Arithmetic() });
run_test(keyContext, renderPassDesc, reporter, paintOptions, /* expectedNumOptions= */ 1);
}
}
// Exercise all the PrecompileShaders factories
void shader_subtest(const KeyContext& keyContext,
const RenderPassDesc& renderPassDesc,
skiatest::Reporter* reporter) {
{
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::Empty() });
run_test(keyContext, renderPassDesc, reporter, paintOptions, /* expectedNumOptions= */ 1);
}
// The solid color shader only ever generates one combination. Because it is constant
// everywhere it can cause other shaders to be elided (e.g., the LocalMatrix shader -
// see the LocalMatrix test(s) below).
{
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::Color() });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedSolidColorCombos);
}
// In general, the blend shader generates the product of the options in each of its slots.
// The rules for how many combinations the SkBlendModes yield are:
// all Porter-Duff SkBlendModes collapse to one option (see GetPorterDuffBlendConstants))
// all HSCL SkBlendModes collapse to another option
// all other SkBlendModes produce unique options
{
const SkBlendMode kBlendModes[] = {
SkBlendMode::kSrcOut, // Porter-Duff
SkBlendMode::kSrcOver, // Porter-Duff
SkBlendMode::kHue, // HSLC
SkBlendMode::kColor, // HSLC
SkBlendMode::kScreen, // Fixed Screen
SkBlendMode::kDarken, // fixed Darken
};
PaintOptions paintOptions;
paintOptions.setShaders(
{ PrecompileShaders::Blend(SkSpan<const SkBlendMode>(kBlendModes),
{ PrecompileShaders::Color() },
{ PrecompileShaders::MakeFractalNoise() }) });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ 4 * // Porter-Duff, HSLC, Screen, Darken
kExpectedSolidColorCombos *
kExpectedPerlinNoiseCombos);
}
// The ImageShaders have 24 combinations
// (6 sampling/tiling x 4 color space xform)
// The CoordClamp shader doesn't add any additional combinations to its wrapped shader.
{
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::CoordClamp({ PrecompileShaders::Image() }) });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedImageCombos);
}
// RawImageShaders only have 10 combinations since they never do cubic filtering and only have
// two possible color space xform variants.
{
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::RawImage() });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedRawImageCombos);
}
// Each Perlin noise shader only has one combination
{
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::MakeFractalNoise(),
PrecompileShaders::MakeTurbulence() });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedPerlinNoiseCombos + kExpectedPerlinNoiseCombos);
}
// Each gradient shader generates 3 combinations
{
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::LinearGradient(),
PrecompileShaders::RadialGradient(),
PrecompileShaders::TwoPointConicalGradient(),
PrecompileShaders::SweepGradient() });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedGradientCombos + kExpectedGradientCombos +
kExpectedGradientCombos + kExpectedGradientCombos);
}
// Each picture shader generates 48 combinations:
// 2 (pictureShader LM) x 24 (imageShader variations)
{
PaintOptions paintOptions;
paintOptions.setShaders({ PrecompileShaders::Picture() });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedPictureCombos);
}
// In general, the local matrix shader just generates however many options its wrapped
// shader generates.
{
PaintOptions paintOptions;
paintOptions.setShaders(
{ PrecompileShaders::LocalMatrix({ PrecompileShaders::LinearGradient() }) });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedGradientCombos);
}
// The ColorFilter shader just creates the cross product of its child options
{
PaintOptions paintOptions;
paintOptions.setShaders(
{ PrecompileShaders::ColorFilter({ PrecompileShaders::LinearGradient() },
{ PrecompileColorFilters::Blend() }) });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedGradientCombos * kExpectedBlendCFCombos);
}
{
PaintOptions paintOptions;
paintOptions.setShaders(
{ PrecompileShaders::WorkingColorSpace({ PrecompileShaders::LinearGradient() },
{ SkColorSpace::MakeSRGBLinear() }) });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ kExpectedGradientCombos *
1 /* only one colorSpace */);
}
}
// Exercise all the PrecompileColorFilters factories. The impact of colorfilters on the number
// of combinations is very predictable. Except for the Compose and Lerp color filters, all the
// color filters only ever have one combination. The Compose and Lerp color filters also just
// simply generate the cross product of their children.
void colorfilter_subtest(const KeyContext& keyContext,
const RenderPassDesc& renderPassDesc,
skiatest::Reporter* reporter) {
{
// The compose colorfilter just generates the cross product of its children.
static constexpr int kExpectedNumOptions =
kExpectedTableCFCombos * (kExpectedHighContrastCFCombos + kExpectedLumaCFCombos) +
kExpectedLightingCFCombos * (kExpectedHighContrastCFCombos + kExpectedLumaCFCombos);
PaintOptions paintOptions;
paintOptions.setColorFilters(
{ PrecompileColorFilters::Compose(
{ PrecompileColorFilters::Table(), PrecompileColorFilters::Lighting() },
{ PrecompileColorFilters::HighContrast(), PrecompileColorFilters::Luma() }) });
run_test(keyContext, renderPassDesc, reporter, paintOptions, kExpectedNumOptions);
}
{
PaintOptions paintOptions;
paintOptions.setColorFilters({ PrecompileColorFilters::Blend() });
run_test(keyContext, renderPassDesc, reporter, paintOptions,
kExpectedBlendCFCombos);
}
{
PaintOptions paintOptions;
paintOptions.setColorFilters({ PrecompileColorFilters::Matrix(),
PrecompileColorFilters::HSLAMatrix() });
// HSLAMatrix and Matrix map to the same color filter
run_test(keyContext, renderPassDesc, reporter, paintOptions,
kExpectedMatrixCFCombos + kExpectedMatrixCFCombos);
}
{
PaintOptions paintOptions;
paintOptions.setColorFilters({ PrecompileColorFilters::LinearToSRGBGamma(),
PrecompileColorFilters::SRGBToLinearGamma() });
// LinearToSRGB and SRGBToLinear both map to the colorspace colorfilter
run_test(keyContext, renderPassDesc, reporter, paintOptions,
kExpectedColorSpaceCFCombos + kExpectedColorSpaceCFCombos);
}
{
// The lerp colorfilter just generates the cross product of its children.
static constexpr int kExpectedNumOptions =
kExpectedMatrixCFCombos * (kExpectedBlendCFCombos + kExpectedOverdrawCFCombos) +
kExpectedLumaCFCombos * (kExpectedBlendCFCombos + kExpectedOverdrawCFCombos);
PaintOptions paintOptions;
paintOptions.setColorFilters(
{ PrecompileColorFilters::Lerp(
{ PrecompileColorFilters::Matrix(), PrecompileColorFilters::Luma() },
{ PrecompileColorFilters::Blend(), PrecompileColorFilters::Overdraw() }) });
run_test(keyContext, renderPassDesc, reporter, paintOptions, kExpectedNumOptions);
}
}
} // anonymous namespace
DEF_GRAPHITE_TEST_FOR_ALL_CONTEXTS(CombinationBuilderTest, reporter, context,
CtsEnforcement::kNever) {
ShaderCodeDictionary* dict = context->priv().shaderCodeDictionary();
sk_sp<RuntimeEffectDictionary> rtEffectDict = sk_make_sp<RuntimeEffectDictionary>();
SkColorInfo ci(kRGBA_8888_SkColorType, kPremul_SkAlphaType, nullptr);
FloatStorageManager floatStorageManager;
PaintParamsKeyBuilder builder(dict);
PipelineDataGatherer gatherer(Layout::kMetal);
KeyContext keyContext(context->priv().caps(), &floatStorageManager, &builder, &gatherer, dict,
rtEffectDict, ci);
RenderPassDesc unusedRenderPassDesc;
// The default PaintOptions should create a single combination with a solid color shader and
// kSrcOver blending
{
PaintOptions paintOptions;
run_test(keyContext, unusedRenderPassDesc, reporter, paintOptions,
/* expectedNumOptions= */ 1);
}
blend_subtest(keyContext, unusedRenderPassDesc, reporter);
shader_subtest(keyContext, unusedRenderPassDesc, reporter);
colorfilter_subtest(keyContext, unusedRenderPassDesc, reporter);
no_blend_mode_option_test(keyContext, unusedRenderPassDesc, reporter);
big_test(keyContext, unusedRenderPassDesc, reporter);
runtime_effect_test(keyContext, unusedRenderPassDesc, reporter);
}
#endif // SK_GRAPHITE