Add SkBlender class; thread into SkVMBlitter.
Like SkColorFilter, SkShader, etc., this has a public-facing component
(SkBlender) and a private subclass (SkBlenderBase) which can be
obtained via a helper function (as_BB). At present there are no public-
facing methods, but the type needs to be exposed to be usable by the
outside world.
These classes exist for SkRuntimeEffect to subclass. The blender base
provides a `program` method with the parameters that blending will use.
Change-Id: I75c772fd4108a9c21fbda84201a8b23d3750a0df
Bug: skia:12080
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/416916
Auto-Submit: John Stiles <johnstiles@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
diff --git a/bench/SkSLBench.cpp b/bench/SkSLBench.cpp
index 154853c..75beb50 100644
--- a/bench/SkSLBench.cpp
+++ b/bench/SkSLBench.cpp
@@ -586,7 +586,7 @@
SkSL::Compiler compiler(&caps);
compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeColorFilter);
compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeShader);
- compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeBlendFilter);
+ compiler.moduleForProgramKind(SkSL::ProgramKind::kRuntimeBlender);
int after = heap_bytes_used();
bench("sksl_compiler_runtimeeffect", after - before);
}
diff --git a/gn/core.gni b/gn/core.gni
index 1342d5d..e333487 100644
--- a/gn/core.gni
+++ b/gn/core.gni
@@ -11,6 +11,7 @@
"$_include/core/SkAnnotation.h",
"$_include/core/SkBBHFactory.h",
"$_include/core/SkBitmap.h",
+ "$_include/core/SkBlender.h",
"$_include/core/SkBlendMode.h",
"$_include/core/SkBlurTypes.h",
"$_include/core/SkCanvas.h",
@@ -125,6 +126,7 @@
"$_src/core/SkBitmapProcState.h",
"$_src/core/SkBitmapProcState_matrixProcs.cpp",
"$_src/core/SkBlendMode.cpp",
+ "$_src/core/SkBlenderBase.h",
"$_src/core/SkBlitBWMaskTemplate.h",
"$_src/core/SkBlitRow.h",
"$_src/core/SkBlitRow_D32.cpp",
@@ -163,6 +165,7 @@
"$_src/core/SkCubicClipper.cpp",
"$_src/core/SkCubicClipper.h",
"$_src/core/SkCubicMap.cpp",
+ "$_src/core/SkCubicSolver.h",
"$_src/core/SkData.cpp",
"$_src/core/SkDataTable.cpp",
"$_src/core/SkDebug.cpp",
diff --git a/include/core/SkBlender.h b/include/core/SkBlender.h
new file mode 100644
index 0000000..4e007e3
--- /dev/null
+++ b/include/core/SkBlender.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBlender_DEFINED
+#define SkBlender_DEFINED
+
+#include "include/core/SkFlattenable.h"
+
+/**
+ * SkBlender represents a custom blend function in the Skia pipeline. When an SkBlender is
+ * present in a paint, the SkBlendMode is ignored. A blender combines a source color (the
+ * result of our paint) and destination color (from the canvas) into a final color.
+ */
+class SK_API SkBlender : public SkFlattenable {
+private:
+ SkBlender() = default;
+ friend class SkBlenderBase;
+
+ using INHERITED = SkFlattenable;
+};
+
+#endif
diff --git a/include/core/SkFlattenable.h b/include/core/SkFlattenable.h
index b105f6d..9de6e7f 100644
--- a/include/core/SkFlattenable.h
+++ b/include/core/SkFlattenable.h
@@ -27,8 +27,9 @@
public:
enum Type {
kSkColorFilter_Type,
+ kSkBlender_Type,
kSkDrawable_Type,
- kSkDrawLooper_Type, // no longer used internally by Skia
+ kSkDrawLooper_Type, // no longer used internally by Skia
kSkImageFilter_Type,
kSkMaskFilter_Type,
kSkPathEffect_Type,
diff --git a/include/effects/SkRuntimeEffect.h b/include/effects/SkRuntimeEffect.h
index 32d0665..c5204a7 100644
--- a/include/effects/SkRuntimeEffect.h
+++ b/include/effects/SkRuntimeEffect.h
@@ -204,7 +204,7 @@
kUsesSampleCoords_Flag = 0x1,
kAllowColorFilter_Flag = 0x2,
kAllowShader_Flag = 0x4,
- kAllowBlendFilter_Flag = 0x8,
+ kAllowBlender_Flag = 0x8,
};
SkRuntimeEffect(SkString sksl,
@@ -227,7 +227,7 @@
bool usesSampleCoords() const { return (fFlags & kUsesSampleCoords_Flag); }
bool allowShader() const { return (fFlags & kAllowShader_Flag); }
bool allowColorFilter() const { return (fFlags & kAllowColorFilter_Flag); }
- bool allowBlendFilter() const { return (fFlags & kAllowBlendFilter_Flag); }
+ bool allowBlender() const { return (fFlags & kAllowBlender_Flag); }
const SkFilterColorProgram* getFilterColorProgram();
diff --git a/include/private/SkSLProgramKind.h b/include/private/SkSLProgramKind.h
index e8cc587..5b381d7 100644
--- a/include/private/SkSLProgramKind.h
+++ b/include/private/SkSLProgramKind.h
@@ -22,7 +22,7 @@
kFragmentProcessor,
kRuntimeColorFilter, // Runtime effect only suitable as SkColorFilter
kRuntimeShader, // " " " " " SkShader
- kRuntimeBlendFilter, // " " " " " SkBlendFilter
+ kRuntimeBlender, // " " " " " SkBlender
kGeneric,
};
diff --git a/src/core/SkBlenderBase.h b/src/core/SkBlenderBase.h
new file mode 100644
index 0000000..37a3abc
--- /dev/null
+++ b/src/core/SkBlenderBase.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkBlenderBase_DEFINED
+#define SkBlenderBase_DEFINED
+
+#include "include/core/SkBlender.h"
+#include "include/core/SkColorSpace.h"
+#include "src/core/SkArenaAlloc.h"
+#include "src/core/SkVM.h"
+
+/**
+ * Encapsulates a custom blend function for Runtime Effects. These combine a source color (the
+ * result of our paint) and destination color (from the canvas) into a final color.
+ */
+class SkBlenderBase : public SkBlender {
+public:
+ SK_WARN_UNUSED_RESULT
+ skvm::Color program(skvm::Builder* p, skvm::Color src, skvm::Color dst,
+ const SkColorInfo& colorInfo, skvm::Uniforms* uniforms,
+ SkArenaAlloc* alloc) const {
+ return this->onProgram(p, src, dst, colorInfo, uniforms, alloc);
+ }
+
+ static SkFlattenable::Type GetFlattenableType() { return kSkBlender_Type; }
+ Type getFlattenableType() const override { return GetFlattenableType(); }
+
+private:
+ virtual skvm::Color onProgram(skvm::Builder* p, skvm::Color src, skvm::Color dst,
+ const SkColorInfo& colorInfo, skvm::Uniforms* uniforms,
+ SkArenaAlloc* alloc) const = 0;
+
+ using INHERITED = SkFlattenable;
+};
+
+inline SkBlenderBase* as_BB(SkBlender* blend) {
+ return static_cast<SkBlenderBase*>(blend);
+}
+
+inline const SkBlenderBase* as_BB(const SkBlender* blend) {
+ return static_cast<const SkBlenderBase*>(blend);
+}
+
+inline const SkBlenderBase* as_BB(const sk_sp<SkBlender>& blend) {
+ return static_cast<SkBlenderBase*>(blend.get());
+}
+
+#endif // SkBlenderBase_DEFINED
diff --git a/src/core/SkRuntimeEffect.cpp b/src/core/SkRuntimeEffect.cpp
index 6b33208..f365183 100644
--- a/src/core/SkRuntimeEffect.cpp
+++ b/src/core/SkRuntimeEffect.cpp
@@ -187,7 +187,7 @@
switch (kind) {
case SkSL::ProgramKind::kRuntimeColorFilter: flags |= kAllowColorFilter_Flag; break;
case SkSL::ProgramKind::kRuntimeShader: flags |= kAllowShader_Flag; break;
- case SkSL::ProgramKind::kRuntimeBlendFilter: flags |= kAllowBlendFilter_Flag; break;
+ case SkSL::ProgramKind::kRuntimeBlender: flags |= kAllowBlender_Flag; break;
default: SkUNREACHABLE;
}
diff --git a/src/core/SkVMBlitter.cpp b/src/core/SkVMBlitter.cpp
index c7e5968..7a94cd3 100644
--- a/src/core/SkVMBlitter.cpp
+++ b/src/core/SkVMBlitter.cpp
@@ -9,6 +9,7 @@
#include "include/private/SkMacros.h"
#include "src/core/SkArenaAlloc.h"
#include "src/core/SkBlendModePriv.h"
+#include "src/core/SkBlenderBase.h"
#include "src/core/SkColorFilterBase.h"
#include "src/core/SkColorSpacePriv.h"
#include "src/core/SkColorSpaceXformSteps.h"
@@ -38,6 +39,7 @@
struct Params {
sk_sp<SkShader> shader;
sk_sp<SkShader> clip;
+ sk_sp<SkBlender> blender;
SkColorInfo dst;
SkBlendMode blendMode;
Coverage coverage;
@@ -55,6 +57,7 @@
struct Key {
uint64_t shader,
clip,
+ blender,
colorSpace;
uint8_t colorType,
alphaType,
@@ -66,13 +69,14 @@
// they'll be folded into the shader key if used.
bool operator==(const Key& that) const {
- return this->shader == that.shader
- && this->clip == that.clip
- && this->colorSpace == that.colorSpace
- && this->colorType == that.colorType
- && this->alphaType == that.alphaType
- && this->blendMode == that.blendMode
- && this->coverage == that.coverage;
+ return this->shader == that.shader
+ && this->clip == that.clip
+ && this->blender == that.blender
+ && this->colorSpace == that.colorSpace
+ && this->colorType == that.colorType
+ && this->alphaType == that.alphaType
+ && this->blendMode == that.blendMode
+ && this->coverage == that.coverage;
}
Key withCoverage(Coverage c) const {
@@ -84,15 +88,16 @@
SK_END_REQUIRE_DENSE;
static SkString debug_name(const Key& key) {
- return SkStringPrintf(
- "Shader-%" PRIx64 "_Clip-%" PRIx64 "_CS-%" PRIx64 "_CT-%d_AT-%d_Blend-%d_Cov-%d",
- key.shader,
- key.clip,
- key.colorSpace,
- key.colorType,
- key.alphaType,
- key.blendMode,
- key.coverage);
+ return SkStringPrintf("Shader-%" PRIx64 "_Clip-%" PRIx64 "_Blender-%" PRIx64
+ "_CS-%" PRIx64 "_CT-%d_AT-%d_Blend-%d_Cov-%d",
+ key.shader,
+ key.clip,
+ key.blender,
+ key.colorSpace,
+ key.colorType,
+ key.alphaType,
+ key.blendMode,
+ key.coverage);
}
static SkLRUCache<Key, skvm::Program>* try_acquire_program_cache() {
@@ -119,6 +124,12 @@
};
}
+ static skvm::Color dst_color(skvm::Builder* p, const Params& params) {
+ skvm::PixelFormat dstFormat = skvm::SkColorType_to_PixelFormat(params.dst.colorType());
+ skvm::Ptr dst_ptr = p->arg(SkColorTypeBytesPerPixel(params.dst.colorType()));
+ return p->load(dstFormat, dst_ptr);
+ }
+
// If build_program() can't build this program, cache_key() sets *ok to false.
static Key cache_key(const Params& params,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc, bool* ok) {
@@ -127,7 +138,8 @@
g = uniforms->pushF(params.paint.fG),
b = uniforms->pushF(params.paint.fB),
a = uniforms->pushF(params.paint.fA);
- auto hash_shader = [&](const sk_sp<SkShader>& shader) {
+
+ auto hash_shader = [&](const sk_sp<SkShader>& shader, skvm::Color* outColor) {
const SkShaderBase* sb = as_SB(shader);
skvm::Builder p;
@@ -140,16 +152,20 @@
};
uint64_t hash = 0;
- if (auto c = sb->program(&p,
- device,/*local=*/device, paint,
- params.matrices, /*localM=*/nullptr,
- params.dst, uniforms,alloc)) {
+ *outColor = sb->program(&p, device, /*local=*/device, paint, params.matrices,
+ /*localM=*/nullptr, params.dst, uniforms, alloc);
+ if (*outColor) {
hash = p.hash();
// p.hash() folds in all instructions to produce r,g,b,a but does not know
// precisely which value we'll treat as which channel. Imagine the shader
// called std::swap(*r,*b)... it draws differently, but p.hash() is unchanged.
// We'll fold the hash of their IDs in order to disambiguate.
- const skvm::Val outputs[] = { c.r.id, c.g.id, c.b.id, c.a.id };
+ const skvm::Val outputs[] = {
+ outColor->r.id,
+ outColor->g.id,
+ outColor->b.id,
+ outColor->a.id
+ };
hash ^= SkOpts::hash(outputs, sizeof(outputs));
} else {
*ok = false;
@@ -157,20 +173,52 @@
return hash;
};
+ // Calculate a hash for the color shader.
SkASSERT(params.shader);
- uint64_t shaderHash = hash_shader(params.shader);
+ skvm::Color src;
+ uint64_t shaderHash = hash_shader(params.shader, &src);
+ // Calculate a hash for the clip shader, if one exists.
uint64_t clipHash = 0;
+ skvm::Color cov;
if (params.clip) {
- clipHash = hash_shader(params.clip);
+ clipHash = hash_shader(params.clip, &cov);
if (clipHash == 0) {
clipHash = 1;
}
}
+ // Calculate a hash for the blend shader, if one exists.
+ uint64_t blendHash = 0;
+ if (params.blender) {
+ const SkBlenderBase* blender = as_BB(params.blender);
+ skvm::Builder p;
+
+ skvm::Color dst = dst_color(&p, params);
+ skvm::Color outColor = blender->program(&p, src, dst, params.dst, uniforms, alloc);
+ if (outColor) {
+ blendHash = p.hash();
+ // Like in `hash_shader` above, we must fold the color component IDs into our hash.
+ const skvm::Val outputs[] = {
+ outColor.r.id,
+ outColor.g.id,
+ outColor.b.id,
+ outColor.a.id
+ };
+ blendHash ^= SkOpts::hash(outputs, sizeof(outputs));
+ } else {
+ *ok = false;
+ }
+
+ if (blendHash == 0) {
+ blendHash = 1;
+ }
+ }
+
return {
shaderHash,
clipHash,
+ blendHash,
params.dst.colorSpace() ? params.dst.colorSpace()->hash() : 0,
SkToU8(params.dst.colorType()),
SkToU8(params.dst.alphaType()),
@@ -196,7 +244,7 @@
skvm::Color paint = p->uniformColor(params.paint, uniforms);
// See note about arguments above: a SpriteShader will call p->arg() once during program().
- skvm::Color src = as_SB(params.shader)->program(p, device,/*local=*/device, paint,
+ skvm::Color src = as_SB(params.shader)->program(p, device, /*local=*/device, paint,
params.matrices, /*localM=*/nullptr,
params.dst, uniforms, alloc);
SkASSERT(src);
@@ -266,7 +314,7 @@
} break;
}
if (params.clip) {
- skvm::Color clip = as_SB(params.clip)->program(p, device,/*local=*/device, paint,
+ skvm::Color clip = as_SB(params.clip)->program(p, device, /*local=*/device, paint,
params.matrices, /*localM=*/nullptr,
params.dst, uniforms, alloc);
SkAssertResult(clip);
@@ -276,19 +324,25 @@
cov.a *= clip.a;
}
- // The math for some blend modes lets us fold coverage into src before the blend,
- // which is simpler than the canonical post-blend lerp().
- if (SkBlendMode_ShouldPreScaleCoverage(params.blendMode,
+ // The math for some blend modes lets us fold coverage into src before the blend, which is
+ // simpler than the canonical post-blend lerp().
+ bool applyPostBlendCoverage = true;
+ if (!params.blender &&
+ SkBlendMode_ShouldPreScaleCoverage(params.blendMode,
params.coverage == Coverage::MaskLCD16)) {
+ applyPostBlendCoverage = false;
src.r *= cov.r;
src.g *= cov.g;
src.b *= cov.b;
src.a *= cov.a;
+ }
- src = blend(params.blendMode, src, dst);
- } else {
- src = blend(params.blendMode, src, dst);
+ // Apply our blend function to the computed color.
+ src = params.blender
+ ? as_BB(params.blender)->program(p, src, dst, params.dst, uniforms, alloc)
+ : blend(params.blendMode, src, dst);
+ if (applyPostBlendCoverage) {
src.r = lerp(dst.r, src.r, cov.r);
src.g = lerp(dst.g, src.g, cov.g);
src.b = lerp(dst.b, src.b, cov.b);
@@ -528,6 +582,7 @@
return {
std::move(shader),
std::move(clip),
+ /*blender=*/nullptr,
{ device.colorType(), device.alphaType(), device.refColorSpace() },
blendMode,
Coverage::Full, // Placeholder... withCoverage() will change as needed.
diff --git a/src/sksl/SkSLCompiler.cpp b/src/sksl/SkSLCompiler.cpp
index e653aad..dabebc2 100644
--- a/src/sksl/SkSLCompiler.cpp
+++ b/src/sksl/SkSLCompiler.cpp
@@ -298,13 +298,13 @@
return fRuntimeShaderModule;
}
-const ParsedModule& Compiler::loadRuntimeBlendFilterModule() {
- if (!fRuntimeBlendFilterModule.fSymbols) {
- fRuntimeBlendFilterModule = this->parseModule(
- ProgramKind::kRuntimeBlendFilter, MODULE_DATA(rt_blend), this->loadPublicModule());
- add_glsl_type_aliases(fRuntimeBlendFilterModule.fSymbols.get(), fContext->fTypes);
+const ParsedModule& Compiler::loadRuntimeBlenderModule() {
+ if (!fRuntimeBlenderModule.fSymbols) {
+ fRuntimeBlenderModule = this->parseModule(
+ ProgramKind::kRuntimeBlender, MODULE_DATA(rt_blend), this->loadPublicModule());
+ add_glsl_type_aliases(fRuntimeBlenderModule.fSymbols.get(), fContext->fTypes);
}
- return fRuntimeBlendFilterModule;
+ return fRuntimeBlenderModule;
}
const ParsedModule& Compiler::moduleForProgramKind(ProgramKind kind) {
@@ -315,7 +315,7 @@
case ProgramKind::kFragmentProcessor: return this->loadFPModule(); break;
case ProgramKind::kRuntimeColorFilter: return this->loadRuntimeColorFilterModule(); break;
case ProgramKind::kRuntimeShader: return this->loadRuntimeShaderModule(); break;
- case ProgramKind::kRuntimeBlendFilter: return this->loadRuntimeBlendFilterModule(); break;
+ case ProgramKind::kRuntimeBlender: return this->loadRuntimeBlenderModule(); break;
case ProgramKind::kGeneric: return this->loadPublicModule(); break;
}
SkUNREACHABLE;
diff --git a/src/sksl/SkSLCompiler.h b/src/sksl/SkSLCompiler.h
index 543f5ba..decf660 100644
--- a/src/sksl/SkSLCompiler.h
+++ b/src/sksl/SkSLCompiler.h
@@ -208,7 +208,7 @@
const ParsedModule& loadPublicModule();
const ParsedModule& loadRuntimeColorFilterModule();
const ParsedModule& loadRuntimeShaderModule();
- const ParsedModule& loadRuntimeBlendFilterModule();
+ const ParsedModule& loadRuntimeBlenderModule();
/** Verifies that @if and @switch statements were actually optimized away. */
void verifyStaticTests(const Program& program);
@@ -248,7 +248,7 @@
ParsedModule fPublicModule; // [Root] + Public features
ParsedModule fRuntimeColorFilterModule; // [Public] + Runtime shader decls
ParsedModule fRuntimeShaderModule; // [Public] + Runtime color filter decls
- ParsedModule fRuntimeBlendFilterModule; // [Public] + Runtime blend filter decls
+ ParsedModule fRuntimeBlenderModule; // [Public] + Runtime blender decls
// holds ModifiersPools belonging to the core includes for lifetime purposes
ModifiersPool fCoreModifiers;
diff --git a/src/sksl/SkSLMain.cpp b/src/sksl/SkSLMain.cpp
index 1dce022..258ac17 100644
--- a/src/sksl/SkSLMain.cpp
+++ b/src/sksl/SkSLMain.cpp
@@ -292,7 +292,7 @@
} else if (inputPath.ends_with(".fp")) {
kind = SkSL::ProgramKind::kFragmentProcessor;
} else if (inputPath.ends_with(".rtb")) {
- kind = SkSL::ProgramKind::kRuntimeBlendFilter;
+ kind = SkSL::ProgramKind::kRuntimeBlender;
} else if (inputPath.ends_with(".rtcf")) {
kind = SkSL::ProgramKind::kRuntimeColorFilter;
} else if (inputPath.ends_with(".rts")) {
diff --git a/src/sksl/dsl/DSLFunction.cpp b/src/sksl/dsl/DSLFunction.cpp
index cd8181d..1be891d 100644
--- a/src/sksl/dsl/DSLFunction.cpp
+++ b/src/sksl/dsl/DSLFunction.cpp
@@ -46,7 +46,7 @@
SkSL::ProgramKind kind = DSLWriter::Context().fConfig->fKind;
if (isMain && (kind == ProgramKind::kRuntimeColorFilter ||
kind == ProgramKind::kRuntimeShader ||
- kind == ProgramKind::kRuntimeBlendFilter ||
+ kind == ProgramKind::kRuntimeBlender ||
kind == ProgramKind::kFragmentProcessor)) {
const SkSL::Type& type = param->fType.skslType();
// We verify that the signature is fully correct later. For now, if this is an .fp
diff --git a/src/sksl/ir/SkSLFunctionDeclaration.cpp b/src/sksl/ir/SkSLFunctionDeclaration.cpp
index 68b0559..29f8b54 100644
--- a/src/sksl/ir/SkSLFunctionDeclaration.cpp
+++ b/src/sksl/ir/SkSLFunctionDeclaration.cpp
@@ -92,7 +92,7 @@
ProgramKind kind = context.fConfig->fKind;
if (isMain && (kind == ProgramKind::kRuntimeColorFilter ||
kind == ProgramKind::kRuntimeShader ||
- kind == ProgramKind::kRuntimeBlendFilter ||
+ kind == ProgramKind::kRuntimeBlender ||
kind == ProgramKind::kFragmentProcessor)) {
// We verify that the signature is fully correct later. For now, if this is an .fp or
// runtime effect of any flavor, a float2 param is supposed to be the coords, and
@@ -178,7 +178,7 @@
}
break;
}
- case ProgramKind::kRuntimeBlendFilter: {
+ case ProgramKind::kRuntimeBlender: {
// (half4|float4) main(half4|float4, half4|float4)
if (!typeIsValidForColor(returnType)) {
errors.error(offset, "'main' must return: 'vec4', 'float4', or 'half4'");