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>
This commit is contained in:
John Stiles 2021-06-16 11:33:13 -04:00 committed by Skia Commit-Bot
parent 3674f589ee
commit 2d8b835cad
14 changed files with 188 additions and 51 deletions

View File

@ -586,7 +586,7 @@ void RunSkSLMemoryBenchmarks(NanoJSONResultsWriter* log) {
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);
}

View File

@ -11,6 +11,7 @@ skia_core_public = [
"$_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 @@ skia_core_sources = [
"$_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 @@ skia_core_sources = [
"$_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",

26
include/core/SkBlender.h Normal file
View File

@ -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

View File

@ -27,8 +27,9 @@ class SK_API SkFlattenable : public SkRefCnt {
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,

View File

@ -204,7 +204,7 @@ private:
kUsesSampleCoords_Flag = 0x1,
kAllowColorFilter_Flag = 0x2,
kAllowShader_Flag = 0x4,
kAllowBlendFilter_Flag = 0x8,
kAllowBlender_Flag = 0x8,
};
SkRuntimeEffect(SkString sksl,
@ -227,7 +227,7 @@ private:
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();

View File

@ -22,7 +22,7 @@ enum class ProgramKind : int8_t {
kFragmentProcessor,
kRuntimeColorFilter, // Runtime effect only suitable as SkColorFilter
kRuntimeShader, // " " " " " SkShader
kRuntimeBlendFilter, // " " " " " SkBlendFilter
kRuntimeBlender, // " " " " " SkBlender
kGeneric,
};

52
src/core/SkBlenderBase.h Normal file
View File

@ -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

View File

@ -187,7 +187,7 @@ SkRuntimeEffect::Result SkRuntimeEffect::Make(SkString sksl,
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;
}

View File

@ -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 @@ namespace {
struct Params {
sk_sp<SkShader> shader;
sk_sp<SkShader> clip;
sk_sp<SkBlender> blender;
SkColorInfo dst;
SkBlendMode blendMode;
Coverage coverage;
@ -55,6 +57,7 @@ namespace {
struct Key {
uint64_t shader,
clip,
blender,
colorSpace;
uint8_t colorType,
alphaType,
@ -66,13 +69,14 @@ namespace {
// 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 @@ namespace {
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 @@ namespace {
};
}
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 @@ namespace {
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 @@ namespace {
};
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 @@ namespace {
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 @@ namespace {
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 @@ namespace {
} 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 @@ namespace {
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 @@ namespace {
return {
std::move(shader),
std::move(clip),
/*blender=*/nullptr,
{ device.colorType(), device.alphaType(), device.refColorSpace() },
blendMode,
Coverage::Full, // Placeholder... withCoverage() will change as needed.

View File

@ -298,13 +298,13 @@ const ParsedModule& Compiler::loadRuntimeShaderModule() {
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 @@ const ParsedModule& Compiler::moduleForProgramKind(ProgramKind kind) {
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;

View File

@ -208,7 +208,7 @@ private:
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 @@ private:
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;

View File

@ -292,7 +292,7 @@ ResultCode processCommand(std::vector<SkSL::String>& args) {
} 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")) {

View File

@ -46,7 +46,7 @@ void DSLFunction::init(const DSLType& returnType, const char* name,
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

View File

@ -92,7 +92,7 @@ static bool check_parameters(const Context& context,
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 @@ static bool check_main_signature(const Context& context, int offset, const Type&
}
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'");