Convert GrDitherEffect to a runtime FP

Includes a change so that we can create non-ES2 runtime effects, even
outside of tests/tools. It's still locked to a private API, so clients
can't access the functionality.

Change-Id: Ie0643da2071bd223fccf05b35f3a7b6f7bbc4876
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/423578
Reviewed-by: Brian Salomon <bsalomon@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
This commit is contained in:
Brian Osman 2021-06-30 15:43:33 -04:00 committed by Skia Commit-Bot
parent a8d2bf7b29
commit 2fa843abc2
13 changed files with 94 additions and 231 deletions

View File

@ -309,8 +309,6 @@ skia_gpu_sources = [
"$_src/gpu/effects/GrTextureEffect.h",
"$_src/gpu/effects/GrYUVtoRGBEffect.cpp",
"$_src/gpu/effects/GrYUVtoRGBEffect.h",
"$_src/gpu/effects/generated/GrDitherEffect.cpp",
"$_src/gpu/effects/generated/GrDitherEffect.h",
"$_src/gpu/geometry/GrPathUtils.cpp",
"$_src/gpu/geometry/GrPathUtils.h",
"$_src/gpu/geometry/GrQuad.cpp",

View File

@ -207,7 +207,5 @@ skia_sksl_gpu_sources = [
"$_src/sksl/codegen/SkSLSPIRVtoHLSL.h",
]
skia_gpu_processor_sources = [
"$_src/gpu/effects/GrDitherEffect.fp",
"$_src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp",
]
skia_gpu_processor_sources =
[ "$_src/gpu/gradients/GrUnrolledBinaryGradientColorizer.fp" ]

View File

@ -87,14 +87,20 @@ public:
int index;
};
struct Options {
class Options {
public:
// For testing purposes, completely disable the inliner. (Normally, Runtime Effects don't
// run the inliner directly, but they still get an inlining pass once they are painted.)
bool forceNoInline = false;
// For testing purposes only; only honored when GR_TEST_UTILS is enabled. This flag lifts
// the ES2 restrictions on Runtime Effects that are gated by the `strictES2Mode` check.
// Be aware that the software renderer and pipeline-stage effect are still largely
// ES3-unaware and can still fail or crash if post-ES2 features are used.
private:
friend class SkRuntimeEffect;
friend class SkRuntimeEffectPriv;
// This flag lifts the ES2 restrictions on Runtime Effects that are gated by the
// `strictES2Mode` check. Be aware that the software renderer and pipeline-stage effect are
// still largely ES3-unaware and can still fail or crash if post-ES2 features are used.
// This is only intended for use by tests and certain internally created effects.
bool enforceES2Restrictions = true;
};

View File

@ -149,9 +149,7 @@ SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl, const Options& opti
SkSL::Program::Settings settings;
settings.fInlineThreshold = 0;
settings.fForceNoInline = options.forceNoInline;
#if GR_TEST_UTILS
settings.fEnforceES2Restrictions = options.enforceES2Restrictions;
#endif
settings.fAllowNarrowingConversions = true;
program = compiler->convertProgram(kind, SkSL::String(sksl.c_str(), sksl.size()), settings);
@ -174,10 +172,6 @@ SkRuntimeEffect::Result SkRuntimeEffect::Make(std::unique_ptr<SkSL::Program> pro
const Options& options,
SkSL::ProgramKind kind) {
SkSL::SharedCompiler compiler;
SkSL::Program::Settings settings;
settings.fInlineThreshold = 0;
settings.fForceNoInline = options.forceNoInline;
settings.fAllowNarrowingConversions = true;
// Find 'main', then locate the sample coords parameter. (It might not be present.)
const SkSL::FunctionDefinition* main = SkSL::Program_GetFunction(*program, "main");

View File

@ -32,9 +32,11 @@ inline sk_sp<SkRuntimeEffect> SkMakeCachedRuntimeEffect(SkRuntimeEffect::Result
// Internal API that assumes (and asserts) that the shader code is valid, but does no internal
// caching. Used when the caller will cache the result in a static variable.
inline sk_sp<SkRuntimeEffect> SkMakeRuntimeEffect(SkRuntimeEffect::Result (*make)(SkString sksl),
const char* sksl) {
auto result = make(SkString{sksl});
inline sk_sp<SkRuntimeEffect> SkMakeRuntimeEffect(
SkRuntimeEffect::Result (*make)(SkString, const SkRuntimeEffect::Options&),
const char* sksl,
SkRuntimeEffect::Options options = SkRuntimeEffect::Options{}) {
auto result = make(SkString{sksl}, options);
SkASSERTF(result.effect, "%s", result.errorText.c_str());
return result.effect;
}
@ -126,6 +128,12 @@ public:
static bool SupportsConstantOutputForConstantInput(sk_sp<SkRuntimeEffect> effect) {
return effect->getFilterColorProgram();
}
static SkRuntimeEffect::Options ES3Options() {
SkRuntimeEffect::Options options;
options.enforceES2Restrictions = false;
return options;
}
};
#endif

View File

@ -63,7 +63,6 @@ public:
kGrDistanceFieldA8TextGeoProc_ClassID,
kGrDistanceFieldLCDTextGeoProc_ClassID,
kGrDistanceFieldPathGeoProc_ClassID,
kGrDitherEffect_ClassID,
kGrDSLFPTest_DoStatement_ClassID,
kGrDSLFPTest_ForStatement_ClassID,
kGrDSLFPTest_IfStatement_ClassID,

View File

@ -146,7 +146,7 @@ SkTArray<GrXPFactoryTestFactory*, true>* GrXPFactoryTestFactory::GetFactories()
* we verify the count is as expected. If a new factory is added, then these numbers must be
* manually adjusted.
*/
static constexpr int kFPFactoryCount = 17;
static constexpr int kFPFactoryCount = 16;
static constexpr int kGPFactoryCount = 14;
static constexpr int kXPFactoryCount = 4;

View File

@ -28,6 +28,7 @@
#include "src/core/SkMipmap.h"
#include "src/core/SkPaintPriv.h"
#include "src/core/SkResourceCache.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "src/core/SkTraceEvent.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrColorInfo.h"
@ -42,7 +43,7 @@
#include "src/gpu/effects/GrBicubicEffect.h"
#include "src/gpu/effects/GrBlendFragmentProcessor.h"
#include "src/gpu/effects/GrPorterDuffXferProcessor.h"
#include "src/gpu/effects/generated/GrDitherEffect.h"
#include "src/gpu/effects/GrSkSLFP.h"
#include "src/image/SkImage_Base.h"
#include "src/shaders/SkShaderBase.h"
@ -322,6 +323,67 @@ static inline float dither_range_for_config(GrColorType dstColorType) {
}
SkUNREACHABLE;
}
static std::unique_ptr<GrFragmentProcessor> make_dither_effect(
std::unique_ptr<GrFragmentProcessor> inputFP, float range, const GrShaderCaps* caps) {
if (range == 0 || inputFP == nullptr) {
return inputFP;
}
if (caps->integerSupport()) {
// This ordered-dither code is lifted from the cpu backend.
static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
uniform half range;
half4 main(float2 xy, half4 color) {
uint x = uint(sk_FragCoord.x);
uint y = uint(sk_FragCoord.y) ^ x;
uint m = (y & 1) << 5 | (x & 1) << 4 |
(y & 2) << 2 | (x & 2) << 1 |
(y & 4) >> 1 | (x & 4) >> 2;
half value = half(m) * 1.0 / 64.0 - 63.0 / 128.0;
// For each color channel, add the random offset to the channel value and then clamp
// between 0 and alpha to keep the color premultiplied.
return half4(clamp(color.rgb + value * range, 0.0, color.a), color.a);
}
)", SkRuntimeEffectPriv::ES3Options());
return GrSkSLFP::Make(effect, "Dither", std::move(inputFP),
GrSkSLFP::OptFlags::kPreservesOpaqueInput,
"range", range);
} else {
// Simulate the integer effect used above using step/mod/abs. For speed, simulates a 4x4
// dither pattern rather than an 8x8 one. Since it's 4x4, this is effectively computing:
// uint m = (y & 1) << 3 | (x & 1) << 2 |
// (y & 2) << 0 | (x & 2) >> 1;
// where 'y' has already been XOR'ed with 'x' as in the integer-supported case.
static auto effect = SkMakeRuntimeEffect(SkRuntimeEffect::MakeForShader, R"(
uniform half range;
half4 main(float2 xy, half4 color) {
// To get the low bit of p.xy, we compute mod 2.0; for the high bit, we mod 4.0
half4 bits = mod(half4(sk_FragCoord.yxyx), half4(2.0, 2.0, 4.0, 4.0));
// Use step to convert the 0-3 value in bits.zw into a 0|1 value. bits.xy is
// already 0|1.
bits.zw = step(2.0, bits.zw);
// bits was constructed such that the p.x bits were already in the right place for
// interleaving (in bits.yw). We just need to update the other bits from p.y to
// (p.x ^ p.y). These are in bits.xz. Since the values are 0|1, we can simulate ^ as
// abs(y - x).
bits.xz = abs(bits.xz - bits.yw);
// Manual binary sum, divide by N^2, and offset
value = dot(bits, half4(8.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0))
- 15.0 / 32.0;
// For each color channel, add the random offset to the channel value and then clamp
// between 0 and alpha to keep the color premultiplied.
return half4(clamp(color.rgb + value * range, 0.0, color.a), color.a);
}
)");
return GrSkSLFP::Make(effect, "Dither", std::move(inputFP),
GrSkSLFP::OptFlags::kPreservesOpaqueInput,
"range", range);
}
}
#endif
static inline bool skpaint_to_grpaint_impl(GrRecordingContext* context,
@ -435,7 +497,8 @@ static inline bool skpaint_to_grpaint_impl(GrRecordingContext* context,
GrColorType ct = dstColorInfo.colorType();
if (SkPaintPriv::ShouldDither(skPaint, GrColorTypeToSkColorType(ct)) && paintFP != nullptr) {
float ditherRange = dither_range_for_config(ct);
paintFP = GrDitherEffect::Make(std::move(paintFP), ditherRange);
paintFP = make_dither_effect(
std::move(paintFP), ditherRange, context->priv().caps()->shaderCaps());
}
#endif

View File

@ -1,66 +0,0 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
in fragmentProcessor inputFP;
// Larger values increase the strength of the dithering effect.
in uniform half range;
half4 main() {
half4 color = sample(inputFP);
half value;
@if (sk_Caps.integerSupport)
{
// This ordered-dither code is lifted from the cpu backend.
uint x = uint(sk_FragCoord.x);
uint y = uint(sk_FragCoord.y) ^ x;
uint m = (y & 1) << 5 | (x & 1) << 4 |
(y & 2) << 2 | (x & 2) << 1 |
(y & 4) >> 1 | (x & 4) >> 2;
value = half(m) * 1.0 / 64.0 - 63.0 / 128.0;
} else {
// Simulate the integer effect used above using step/mod/abs. For speed, simulates a 4x4
// dither pattern rather than an 8x8 one. Since it's 4x4, this is effectively computing:
// uint m = (y & 1) << 3 | (x & 1) << 2 |
// (y & 2) << 0 | (x & 2) >> 1;
// where 'y' has already been XOR'ed with 'x' as in the integer-supported case.
// To get the low bit of p.x and p.y, we compute mod 2.0; for the high bit, we mod 4.0
half4 bits = mod(half4(sk_FragCoord.yxyx), half4(2.0, 2.0, 4.0, 4.0));
// Use step to convert the 0-3 value in bits.zw into a 0|1 value. bits.xy is already 0|1.
bits.zw = step(2.0, bits.zw);
// bits was constructed such that the p.x bits were already in the right place for
// interleaving (in bits.yw). We just need to update the other bits from p.y to (p.x ^ p.y).
// These are in bits.xz. Since the values are 0|1, we can simulate ^ as abs(y - x).
bits.xz = abs(bits.xz - bits.yw);
// Manual binary sum, divide by N^2, and offset
value = dot(bits, half4(8.0 / 16.0, 4.0 / 16.0, 2.0 / 16.0, 1.0 / 16.0)) - 15.0 / 32.0;
}
// For each color channel, add the random offset to the channel value and then clamp
// between 0 and alpha to keep the color premultiplied.
return half4(clamp(color.rgb + value * range, 0.0, color.a), color.a);
}
@optimizationFlags {
ProcessorOptimizationFlags(inputFP.get()) & kPreservesOpaqueInput_OptimizationFlag
}
@test(d) {
float range = 1.0f - d->fRandom->nextRangeF(0.0f, 1.0f);
return GrDitherEffect::Make(GrProcessorUnitTest::MakeChildFP(d), range);
}
@make {
static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
float range) {
if (range == 0.0 || inputFP == nullptr) {
return inputFP;
}
return std::unique_ptr<GrFragmentProcessor>(new GrDitherEffect(std::move(inputFP), range));
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**************************************************************************************************
*** This file was autogenerated from GrDitherEffect.fp; do not modify.
**************************************************************************************************/
#include "GrDitherEffect.h"
#include "src/core/SkUtils.h"
#include "src/gpu/GrTexture.h"
#include "src/gpu/glsl/GrGLSLFragmentProcessor.h"
#include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
#include "src/gpu/glsl/GrGLSLProgramBuilder.h"
#include "src/sksl/SkSLCPP.h"
#include "src/sksl/SkSLUtil.h"
class GrGLSLDitherEffect : public GrGLSLFragmentProcessor {
public:
GrGLSLDitherEffect() {}
void emitCode(EmitArgs& args) override {
GrGLSLFPFragmentBuilder* fragBuilder = args.fFragBuilder;
const GrDitherEffect& _outer = args.fFp.cast<GrDitherEffect>();
(void)_outer;
auto range = _outer.range;
(void)range;
rangeVar = args.fUniformHandler->addUniform(
&_outer, kFragment_GrShaderFlag, kHalf_GrSLType, "range");
SkString _sample0 = this->invokeChild(0, args);
fragBuilder->codeAppendf(
R"SkSL(half4 color = %s;
half value;
@if (sk_Caps.integerSupport) {
uint x = uint(sk_FragCoord.x);
uint y = uint(sk_FragCoord.y) ^ x;
uint m = (((((y & 1) << 5 | (x & 1) << 4) | (y & 2) << 2) | (x & 2) << 1) | (y & 4) >> 1) | (x & 4) >> 2;
value = half(m) / 64.0 - 0.4921875;
} else {
half4 bits = mod(half4(sk_FragCoord.yxyx), half4(2.0, 2.0, 4.0, 4.0));
bits.zw = step(2.0, bits.zw);
bits.xz = abs(bits.xz - bits.yw);
value = dot(bits, half4(0.5, 0.25, 0.125, 0.0625)) - 0.46875;
}
return half4(clamp(color.xyz + value * %s, 0.0, color.w), color.w);
)SkSL",
_sample0.c_str(),
args.fUniformHandler->getUniformCStr(rangeVar));
}
private:
void onSetData(const GrGLSLProgramDataManager& pdman,
const GrFragmentProcessor& _proc) override {
const GrDitherEffect& _outer = _proc.cast<GrDitherEffect>();
{ pdman.set1f(rangeVar, _outer.range); }
}
UniformHandle rangeVar;
};
std::unique_ptr<GrGLSLFragmentProcessor> GrDitherEffect::onMakeProgramImpl() const {
return std::make_unique<GrGLSLDitherEffect>();
}
void GrDitherEffect::onGetGLSLProcessorKey(const GrShaderCaps& caps,
GrProcessorKeyBuilder* b) const {}
bool GrDitherEffect::onIsEqual(const GrFragmentProcessor& other) const {
const GrDitherEffect& that = other.cast<GrDitherEffect>();
(void)that;
if (range != that.range) return false;
return true;
}
GrDitherEffect::GrDitherEffect(const GrDitherEffect& src)
: INHERITED(kGrDitherEffect_ClassID, src.optimizationFlags()), range(src.range) {
this->cloneAndRegisterAllChildProcessors(src);
}
std::unique_ptr<GrFragmentProcessor> GrDitherEffect::clone() const {
return std::make_unique<GrDitherEffect>(*this);
}
#if GR_TEST_UTILS
SkString GrDitherEffect::onDumpInfo() const { return SkStringPrintf("(range=%f)", range); }
#endif
GR_DEFINE_FRAGMENT_PROCESSOR_TEST(GrDitherEffect);
#if GR_TEST_UTILS
std::unique_ptr<GrFragmentProcessor> GrDitherEffect::TestCreate(GrProcessorTestData* d) {
float range = 1.0f - d->fRandom->nextRangeF(0.0f, 1.0f);
return GrDitherEffect::Make(GrProcessorUnitTest::MakeChildFP(d), range);
}
#endif

View File

@ -1,50 +0,0 @@
/*
* Copyright 2020 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**************************************************************************************************
*** This file was autogenerated from GrDitherEffect.fp; do not modify.
**************************************************************************************************/
#ifndef GrDitherEffect_DEFINED
#define GrDitherEffect_DEFINED
#include "include/core/SkM44.h"
#include "include/core/SkTypes.h"
#include "src/gpu/GrFragmentProcessor.h"
class GrDitherEffect : public GrFragmentProcessor {
public:
static std::unique_ptr<GrFragmentProcessor> Make(std::unique_ptr<GrFragmentProcessor> inputFP,
float range) {
if (range == 0.0 || inputFP == nullptr) {
return inputFP;
}
return std::unique_ptr<GrFragmentProcessor>(new GrDitherEffect(std::move(inputFP), range));
}
GrDitherEffect(const GrDitherEffect& src);
std::unique_ptr<GrFragmentProcessor> clone() const override;
const char* name() const override { return "DitherEffect"; }
float range;
private:
GrDitherEffect(std::unique_ptr<GrFragmentProcessor> inputFP, float range)
: INHERITED(kGrDitherEffect_ClassID,
(OptimizationFlags)ProcessorOptimizationFlags(inputFP.get()) &
kPreservesOpaqueInput_OptimizationFlag)
, range(range) {
this->registerChild(std::move(inputFP), SkSL::SampleUsage::PassThrough());
}
std::unique_ptr<GrGLSLFragmentProcessor> onMakeProgramImpl() const override;
void onGetGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override;
bool onIsEqual(const GrFragmentProcessor&) const override;
#if GR_TEST_UTILS
SkString onDumpInfo() const override;
#endif
GR_DECLARE_FRAGMENT_PROCESSOR_TEST
using INHERITED = GrFragmentProcessor;
};
#endif

View File

@ -79,8 +79,7 @@ DEF_TEST(SkRuntimeEffectInvalid_SkCapsDisallowed, r) {
DEF_TEST(SkRuntimeEffectCanDisableES2Restrictions, r) {
auto test_valid_es3 = [](skiatest::Reporter* r, const char* sksl) {
SkRuntimeEffect::Options opt;
opt.enforceES2Restrictions = false;
SkRuntimeEffect::Options opt = SkRuntimeEffectPriv::ES3Options();
auto [effect, errorText] = SkRuntimeEffect::MakeForShader(SkString(sksl), opt);
REPORTER_ASSERT(r, effect, "%s", errorText.c_str());
};

View File

@ -19,6 +19,7 @@
#include "include/effects/SkRuntimeEffect.h"
#include "include/private/SkSLDefines.h" // for kDefaultInlineThreshold
#include "include/utils/SkRandom.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "src/gpu/GrCaps.h"
#include "src/gpu/GrDirectContextPriv.h"
#include "tests/Test.h"
@ -98,8 +99,8 @@ static void test_permutations(skiatest::Reporter* r,
SkSurface* surface,
const char* testFile,
bool worksInES2) {
SkRuntimeEffect::Options options;
options.enforceES2Restrictions = worksInES2;
SkRuntimeEffect::Options options =
worksInES2 ? SkRuntimeEffect::Options{} : SkRuntimeEffectPriv::ES3Options();
options.forceNoInline = false;
test_one_permutation(r, surface, testFile, "", options);