skvm clip shader support

This appears to work.

As a follow up, I want to try taking a little care handling a null and
opaque clip shaders explicitly, to cut down on the overhead in the
common case when it's null and we burn non-zero CPU determining NoClip
is a no-op.

Change-Id: Ie30c4995b3242a5b52935a90005e5af79c9eae7b
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/276227
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Mike Klein 2020-03-10 14:24:35 -05:00 committed by Skia Commit-Bot
parent c376cb4b42
commit 272c56ba73
4 changed files with 124 additions and 53 deletions

View File

@ -744,8 +744,8 @@ SkBlitter* SkBlitter::Choose(const SkPixmap& device,
#if defined(SK_USE_SKVM_BLITTER)
try_skvm_blitter = true;
#endif
if (try_skvm_blitter && !clipShader) {
if (auto blitter = SkCreateSkVMBlitter(device, *paint, matrix, alloc)) {
if (try_skvm_blitter) {
if (auto blitter = SkCreateSkVMBlitter(device, *paint, matrix, alloc, clipShader)) {
return blitter;
}
}

View File

@ -174,6 +174,10 @@ SkBlitter* SkCreateRasterPipelineBlitter(const SkPixmap&, const SkPaint&,
bool shader_is_opaque,
SkArenaAlloc*, sk_sp<SkShader> clipShader);
SkBlitter* SkCreateSkVMBlitter(const SkPixmap&, const SkPaint&, const SkMatrix& ctm, SkArenaAlloc*);
SkBlitter* SkCreateSkVMBlitter(const SkPixmap&,
const SkPaint&,
const SkMatrix& ctm,
SkArenaAlloc*,
sk_sp<SkShader> clipShader);
#endif

View File

@ -34,6 +34,7 @@ namespace {
struct Params {
sk_sp<SkColorSpace> colorSpace;
sk_sp<SkShader> shader;
sk_sp<SkShader> clip;
SkColorType colorType;
SkAlphaType alphaType;
SkBlendMode blendMode;
@ -52,18 +53,20 @@ namespace {
struct Key {
uint64_t colorSpace;
uint64_t shader;
uint64_t clip;
uint8_t colorType,
alphaType,
blendMode,
coverage;
uint32_t padding{0};
// Params::quality and Params::ctm are only passed to shader->program(),
// Params::quality and Params::ctm are only passed to {shader,clip}->program(),
// not used here by the blitter itself. No need to include them in the key;
// they'll be folded into the shader key if used.
bool operator==(const Key& that) const {
return this->colorSpace == that.colorSpace
&& this->shader == that.shader
&& this->clip == that.clip
&& this->colorType == that.colorType
&& this->alphaType == that.alphaType
&& this->blendMode == that.blendMode
@ -79,13 +82,14 @@ namespace {
SK_END_REQUIRE_DENSE;
static SkString debug_name(const Key& key) {
return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-CS%llx-Shader%llx",
return SkStringPrintf("CT%d-AT%d-Cov%d-Blend%d-CS%llx-Shader%llx-Clip%llx",
key.colorType,
key.alphaType,
key.coverage,
key.blendMode,
key.colorSpace,
key.shader);
key.shader,
key.clip);
}
static SkLRUCache<Key, skvm::Program>* try_acquire_program_cache() {
@ -110,10 +114,9 @@ namespace {
skvm::Uniforms* uniforms,
SkArenaAlloc* alloc,
bool* ok) {
SkASSERT(params.shader);
uint64_t shaderHash = 0;
{
const SkShaderBase* shader = as_SB(params.shader);
auto hash_shader = [&](const sk_sp<SkShader>& shader) {
const SkShaderBase* sb = as_SB(shader);
skvm::Builder p;
skvm::I32 dx = p.sub(p.uniform32(uniforms->base, offsetof(BlitterUniforms, right)),
@ -123,21 +126,28 @@ namespace {
y = p.add(p.to_f32(dy), p.splat(0.5f));
skvm::F32 r,g,b,a;
if (shader->program(&p,
params.ctm, /*localM=*/nullptr,
params.quality, params.colorSpace.get(),
uniforms,alloc,
x,y, &r,&g,&b,&a)) {
shaderHash = p.hash();
uint64_t hash = 0;
if (sb->program(&p,
params.ctm, /*localM=*/nullptr,
params.quality, params.colorSpace.get(),
uniforms,alloc,
x,y, &r,&g,&b,&a)) {
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.
const int outputs[] = { r.id, g.id, b.id, a.id };
shaderHash ^= SkOpts::hash(outputs, sizeof(outputs));
hash ^= SkOpts::hash(outputs, sizeof(outputs));
} else {
*ok = false;
}
}
return hash;
};
SkASSERT(params.shader);
SkASSERT(params. clip);
uint64_t shaderHash = hash_shader(params.shader),
clipHash = hash_shader(params. clip);
switch (params.colorType) {
default: *ok = false;
@ -160,6 +170,7 @@ namespace {
return {
params.colorSpace ? params.colorSpace->hash() : 0,
shaderHash,
clipHash,
SkToU8(params.colorType),
SkToU8(params.alphaType),
SkToU8(params.blendMode),
@ -228,27 +239,42 @@ namespace {
// load_coverage() returns false when there's no need to apply coverage.
auto load_coverage = [&](skvm::Color* cov) {
bool partial_coverage = true;
switch (params.coverage) {
case Coverage::Full: return false;
case Coverage::Full: cov->r = cov->g = cov->b = cov->a = splat(1.0f);
partial_coverage = false;
break;
case Coverage::UniformA8: cov->r = cov->g = cov->b = cov->a =
from_unorm(8, uniform8(uniform(), 0));
return true;
break;
case Coverage::Mask3D:
case Coverage::MaskA8: cov->r = cov->g = cov->b = cov->a =
from_unorm(8, load8(varying<uint8_t>()));
return true;
break;
case Coverage::MaskLCD16:
SkASSERT(dst_loaded);
*cov = unpack_565(load16(varying<uint16_t>()));
cov->a = select(lt(src.a, dst.a), min(cov->r, min(cov->g,cov->b))
, max(cov->r, max(cov->g,cov->b)));
return true;
cov->a = select(lt(src.a, dst.a), min(cov->r, min(cov->g, cov->b))
, max(cov->r, max(cov->g, cov->b)));
break;
}
// GCC insists...
return false;
skvm::Color clip;
SkAssertResult(as_SB(params.clip)->program(this,
params.ctm, /*localM=*/nullptr,
params.quality, params.colorSpace.get(),
uniforms, alloc,
x,y,
&clip.r, &clip.g, &clip.b, &clip.a));
cov->r = mul(cov->r, clip.a); // We use the alpha channel of clip for all four.
cov->g = mul(cov->g, clip.a);
cov->b = mul(cov->b, clip.a);
cov->a = mul(cov->a, clip.a);
return partial_coverage || !params.clip->isOpaque();
};
// The math for some blend modes lets us fold coverage into src before the blend,
@ -305,8 +331,7 @@ namespace {
src = skvm::BlendModeProgram(this, params.blendMode, src, dst);
// Lerp with coverage post-blend if needed.
skvm::Color cov;
if (lerp_coverage_post_blend && load_coverage(&cov)) {
if (skvm::Color cov; lerp_coverage_post_blend && load_coverage(&cov)) {
src.r = mad(sub(src.r, dst.r), cov.r, dst.r);
src.g = mad(sub(src.g, dst.g), cov.g, dst.g);
src.b = mad(sub(src.b, dst.b), cov.b, dst.b);
@ -444,9 +469,31 @@ namespace {
}
};
// A shader that behaves the same as a nullptr clip shader. It will be completely folded away.
struct NoClip : public SkShaderBase {
// Only created here temporarily... never serialized.
Factory getFactory() const override { return nullptr; }
const char* getTypeName() const override { return "NoClip"; }
bool isOpaque() const override { return true; }
bool onProgram(skvm::Builder* p,
const SkMatrix& ctm, const SkMatrix* localM,
SkFilterQuality quality, SkColorSpace* dstCS,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc,
skvm::F32 x, skvm::F32 y,
skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32* a) const override {
*r = *g = *b = *a = p->splat(1.0f);
return true;
}
};
static Params effective_params(const SkPixmap& device,
const SkPaint& paint,
const SkMatrix& ctm) {
const SkMatrix& ctm,
sk_sp<SkShader> clip) {
// Color filters have been handled for us by SkBlitter::Choose().
SkASSERT(!paint.getColorFilter());
@ -496,11 +543,7 @@ namespace {
// The most common blend mode is SrcOver, and it can be strength-reduced
// _greatly_ to Src mode when the shader is opaque.
SkBlendMode blendMode = paint.getBlendMode();
if (blendMode == SkBlendMode::kSrcOver && shader->isOpaque()) {
blendMode = SkBlendMode::kSrc;
}
//
// In general all the information we use to make decisions here need to
// be reflected in Params and Key to make program caching sound, and it
// might appear that shader->isOpaque() is a property of the shader's
@ -511,10 +554,20 @@ namespace {
// the opaque bit is strongly guaranteed to be part of the program and
// not just a property of the uniforms. The shader program hash includes
// this information, making it safe to use anywhere in the blitter codegen.
SkBlendMode blendMode = paint.getBlendMode();
if (blendMode == SkBlendMode::kSrcOver && shader->isOpaque()) {
blendMode = SkBlendMode::kSrc;
}
// A nullptr clip shader does not change coverage, as if producing 1.0f like NoClip.
if (!clip) {
clip = sk_make_sp<NoClip>();
}
return {
device.refColorSpace(),
std::move(shader),
std::move(clip),
device.colorType(),
device.alphaType(),
blendMode,
@ -526,10 +579,14 @@ namespace {
class Blitter final : public SkBlitter {
public:
Blitter(const SkPixmap& device, const SkPaint& paint, const SkMatrix& ctm, bool* ok)
Blitter(const SkPixmap& device,
const SkPaint& paint,
const SkMatrix& ctm,
sk_sp<SkShader> clip,
bool* ok)
: fDevice(device)
, fUniforms(kBlitterUniformsCount)
, fParams(effective_params(device, paint, ctm))
, fParams(effective_params(device, paint, ctm, std::move(clip)))
, fKey(Builder::CacheKey(fParams, &fUniforms, &fAlloc, ok))
{}
@ -588,7 +645,8 @@ namespace {
SkDEBUGCODE(size_t prev = fUniforms.buf.size();)
fUniforms.buf.resize(kBlitterUniformsCount);
Builder builder{fParams.withCoverage(coverage), &fUniforms, &fAlloc};
SkASSERT(fUniforms.buf.size() == prev);
SkASSERTF(fUniforms.buf.size() == prev,
"%zu, prev was %zu", fUniforms.buf.size(), prev);
skvm::Program program = builder.done(debug_name(key).c_str());
if (false) {
@ -789,8 +847,9 @@ skvm::Color skvm::BlendModeProgram(skvm::Builder* p,
SkBlitter* SkCreateSkVMBlitter(const SkPixmap& device,
const SkPaint& paint,
const SkMatrix& ctm,
SkArenaAlloc* alloc) {
SkArenaAlloc* alloc,
sk_sp<SkShader> clip) {
bool ok = true;
auto blitter = alloc->make<Blitter>(device, paint, ctm, &ok);
auto blitter = alloc->make<Blitter>(device, paint, ctm, std::move(clip), &ok);
return ok ? blitter : nullptr;
}

View File

@ -139,7 +139,28 @@ protected:
Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override { return nullptr; }
#endif
bool onAppendStages(const SkStageRec&) const override;
bool onAppendStages(const SkStageRec& rec) const override {
SkStageRec newRec = {
rec.fPipeline,
rec.fAlloc,
rec.fDstColorType,
rec.fDstCS,
rec.fPaint,
rec.fLocalM,
fCTM,
};
return as_SB(fProxyShader)->appendStages(newRec);
}
bool onProgram(skvm::Builder* p,
const SkMatrix& ctm, const SkMatrix* localM,
SkFilterQuality quality, SkColorSpace* dstCS,
skvm::Uniforms* uniforms, SkArenaAlloc* alloc,
skvm::F32 x, skvm::F32 y,
skvm::F32* r, skvm::F32* g, skvm::F32* b, skvm::F32* a) const override {
return as_SB(fProxyShader)
->program(p, fCTM,localM, quality,dstCS, uniforms,alloc, x,y, r,g,b,a);
}
private:
SK_FLATTENABLE_HOOKS(SkCTMShader)
@ -164,19 +185,6 @@ sk_sp<SkFlattenable> SkCTMShader::CreateProc(SkReadBuffer& buffer) {
return nullptr;
}
bool SkCTMShader::onAppendStages(const SkStageRec& rec) const {
SkStageRec newRec = {
rec.fPipeline,
rec.fAlloc,
rec.fDstColorType,
rec.fDstCS,
rec.fPaint,
rec.fLocalM,
fCTM,
};
return as_SB(fProxyShader)->appendStages(newRec);
}
sk_sp<SkShader> SkShaderBase::makeWithCTM(const SkMatrix& postM) const {
return postM.isIdentity() ? sk_ref_sp(this)
: sk_sp<SkShader>(new SkCTMShader(sk_ref_sp(this), postM));