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:
parent
c376cb4b42
commit
272c56ba73
@ -744,8 +744,8 @@ SkBlitter* SkBlitter::Choose(const SkPixmap& device,
|
|||||||
#if defined(SK_USE_SKVM_BLITTER)
|
#if defined(SK_USE_SKVM_BLITTER)
|
||||||
try_skvm_blitter = true;
|
try_skvm_blitter = true;
|
||||||
#endif
|
#endif
|
||||||
if (try_skvm_blitter && !clipShader) {
|
if (try_skvm_blitter) {
|
||||||
if (auto blitter = SkCreateSkVMBlitter(device, *paint, matrix, alloc)) {
|
if (auto blitter = SkCreateSkVMBlitter(device, *paint, matrix, alloc, clipShader)) {
|
||||||
return blitter;
|
return blitter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,10 @@ SkBlitter* SkCreateRasterPipelineBlitter(const SkPixmap&, const SkPaint&,
|
|||||||
bool shader_is_opaque,
|
bool shader_is_opaque,
|
||||||
SkArenaAlloc*, sk_sp<SkShader> clipShader);
|
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
|
#endif
|
||||||
|
@ -34,6 +34,7 @@ namespace {
|
|||||||
struct Params {
|
struct Params {
|
||||||
sk_sp<SkColorSpace> colorSpace;
|
sk_sp<SkColorSpace> colorSpace;
|
||||||
sk_sp<SkShader> shader;
|
sk_sp<SkShader> shader;
|
||||||
|
sk_sp<SkShader> clip;
|
||||||
SkColorType colorType;
|
SkColorType colorType;
|
||||||
SkAlphaType alphaType;
|
SkAlphaType alphaType;
|
||||||
SkBlendMode blendMode;
|
SkBlendMode blendMode;
|
||||||
@ -52,18 +53,20 @@ namespace {
|
|||||||
struct Key {
|
struct Key {
|
||||||
uint64_t colorSpace;
|
uint64_t colorSpace;
|
||||||
uint64_t shader;
|
uint64_t shader;
|
||||||
|
uint64_t clip;
|
||||||
uint8_t colorType,
|
uint8_t colorType,
|
||||||
alphaType,
|
alphaType,
|
||||||
blendMode,
|
blendMode,
|
||||||
coverage;
|
coverage;
|
||||||
uint32_t padding{0};
|
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;
|
// 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.
|
// they'll be folded into the shader key if used.
|
||||||
|
|
||||||
bool operator==(const Key& that) const {
|
bool operator==(const Key& that) const {
|
||||||
return this->colorSpace == that.colorSpace
|
return this->colorSpace == that.colorSpace
|
||||||
&& this->shader == that.shader
|
&& this->shader == that.shader
|
||||||
|
&& this->clip == that.clip
|
||||||
&& this->colorType == that.colorType
|
&& this->colorType == that.colorType
|
||||||
&& this->alphaType == that.alphaType
|
&& this->alphaType == that.alphaType
|
||||||
&& this->blendMode == that.blendMode
|
&& this->blendMode == that.blendMode
|
||||||
@ -79,13 +82,14 @@ namespace {
|
|||||||
SK_END_REQUIRE_DENSE;
|
SK_END_REQUIRE_DENSE;
|
||||||
|
|
||||||
static SkString debug_name(const Key& key) {
|
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.colorType,
|
||||||
key.alphaType,
|
key.alphaType,
|
||||||
key.coverage,
|
key.coverage,
|
||||||
key.blendMode,
|
key.blendMode,
|
||||||
key.colorSpace,
|
key.colorSpace,
|
||||||
key.shader);
|
key.shader,
|
||||||
|
key.clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
static SkLRUCache<Key, skvm::Program>* try_acquire_program_cache() {
|
static SkLRUCache<Key, skvm::Program>* try_acquire_program_cache() {
|
||||||
@ -110,10 +114,9 @@ namespace {
|
|||||||
skvm::Uniforms* uniforms,
|
skvm::Uniforms* uniforms,
|
||||||
SkArenaAlloc* alloc,
|
SkArenaAlloc* alloc,
|
||||||
bool* ok) {
|
bool* ok) {
|
||||||
SkASSERT(params.shader);
|
|
||||||
uint64_t shaderHash = 0;
|
auto hash_shader = [&](const sk_sp<SkShader>& shader) {
|
||||||
{
|
const SkShaderBase* sb = as_SB(shader);
|
||||||
const SkShaderBase* shader = as_SB(params.shader);
|
|
||||||
skvm::Builder p;
|
skvm::Builder p;
|
||||||
|
|
||||||
skvm::I32 dx = p.sub(p.uniform32(uniforms->base, offsetof(BlitterUniforms, right)),
|
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));
|
y = p.add(p.to_f32(dy), p.splat(0.5f));
|
||||||
|
|
||||||
skvm::F32 r,g,b,a;
|
skvm::F32 r,g,b,a;
|
||||||
if (shader->program(&p,
|
uint64_t hash = 0;
|
||||||
params.ctm, /*localM=*/nullptr,
|
if (sb->program(&p,
|
||||||
params.quality, params.colorSpace.get(),
|
params.ctm, /*localM=*/nullptr,
|
||||||
uniforms,alloc,
|
params.quality, params.colorSpace.get(),
|
||||||
x,y, &r,&g,&b,&a)) {
|
uniforms,alloc,
|
||||||
shaderHash = p.hash();
|
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
|
// 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
|
// 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.
|
// called std::swap(*r,*b)... it draws differently, but p.hash() is unchanged.
|
||||||
const int outputs[] = { r.id, g.id, b.id, a.id };
|
const int outputs[] = { r.id, g.id, b.id, a.id };
|
||||||
shaderHash ^= SkOpts::hash(outputs, sizeof(outputs));
|
hash ^= SkOpts::hash(outputs, sizeof(outputs));
|
||||||
} else {
|
} else {
|
||||||
*ok = false;
|
*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) {
|
switch (params.colorType) {
|
||||||
default: *ok = false;
|
default: *ok = false;
|
||||||
@ -160,6 +170,7 @@ namespace {
|
|||||||
return {
|
return {
|
||||||
params.colorSpace ? params.colorSpace->hash() : 0,
|
params.colorSpace ? params.colorSpace->hash() : 0,
|
||||||
shaderHash,
|
shaderHash,
|
||||||
|
clipHash,
|
||||||
SkToU8(params.colorType),
|
SkToU8(params.colorType),
|
||||||
SkToU8(params.alphaType),
|
SkToU8(params.alphaType),
|
||||||
SkToU8(params.blendMode),
|
SkToU8(params.blendMode),
|
||||||
@ -228,27 +239,42 @@ namespace {
|
|||||||
|
|
||||||
// load_coverage() returns false when there's no need to apply coverage.
|
// load_coverage() returns false when there's no need to apply coverage.
|
||||||
auto load_coverage = [&](skvm::Color* cov) {
|
auto load_coverage = [&](skvm::Color* cov) {
|
||||||
|
bool partial_coverage = true;
|
||||||
switch (params.coverage) {
|
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 =
|
case Coverage::UniformA8: cov->r = cov->g = cov->b = cov->a =
|
||||||
from_unorm(8, uniform8(uniform(), 0));
|
from_unorm(8, uniform8(uniform(), 0));
|
||||||
return true;
|
break;
|
||||||
|
|
||||||
case Coverage::Mask3D:
|
case Coverage::Mask3D:
|
||||||
case Coverage::MaskA8: cov->r = cov->g = cov->b = cov->a =
|
case Coverage::MaskA8: cov->r = cov->g = cov->b = cov->a =
|
||||||
from_unorm(8, load8(varying<uint8_t>()));
|
from_unorm(8, load8(varying<uint8_t>()));
|
||||||
return true;
|
break;
|
||||||
|
|
||||||
case Coverage::MaskLCD16:
|
case Coverage::MaskLCD16:
|
||||||
SkASSERT(dst_loaded);
|
SkASSERT(dst_loaded);
|
||||||
*cov = unpack_565(load16(varying<uint16_t>()));
|
*cov = unpack_565(load16(varying<uint16_t>()));
|
||||||
cov->a = select(lt(src.a, dst.a), min(cov->r, min(cov->g,cov->b))
|
cov->a = select(lt(src.a, dst.a), min(cov->r, min(cov->g, cov->b))
|
||||||
, max(cov->r, max(cov->g,cov->b)));
|
, max(cov->r, max(cov->g, cov->b)));
|
||||||
return true;
|
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,
|
// 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);
|
src = skvm::BlendModeProgram(this, params.blendMode, src, dst);
|
||||||
|
|
||||||
// Lerp with coverage post-blend if needed.
|
// Lerp with coverage post-blend if needed.
|
||||||
skvm::Color cov;
|
if (skvm::Color cov; lerp_coverage_post_blend && load_coverage(&cov)) {
|
||||||
if (lerp_coverage_post_blend && load_coverage(&cov)) {
|
|
||||||
src.r = mad(sub(src.r, dst.r), cov.r, dst.r);
|
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.g = mad(sub(src.g, dst.g), cov.g, dst.g);
|
||||||
src.b = mad(sub(src.b, dst.b), cov.b, dst.b);
|
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,
|
static Params effective_params(const SkPixmap& device,
|
||||||
const SkPaint& paint,
|
const SkPaint& paint,
|
||||||
const SkMatrix& ctm) {
|
const SkMatrix& ctm,
|
||||||
|
sk_sp<SkShader> clip) {
|
||||||
// Color filters have been handled for us by SkBlitter::Choose().
|
// Color filters have been handled for us by SkBlitter::Choose().
|
||||||
SkASSERT(!paint.getColorFilter());
|
SkASSERT(!paint.getColorFilter());
|
||||||
|
|
||||||
@ -496,11 +543,7 @@ namespace {
|
|||||||
|
|
||||||
// The most common blend mode is SrcOver, and it can be strength-reduced
|
// The most common blend mode is SrcOver, and it can be strength-reduced
|
||||||
// _greatly_ to Src mode when the shader is opaque.
|
// _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
|
// 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
|
// 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
|
// 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
|
// 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
|
// not just a property of the uniforms. The shader program hash includes
|
||||||
// this information, making it safe to use anywhere in the blitter codegen.
|
// 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 {
|
return {
|
||||||
device.refColorSpace(),
|
device.refColorSpace(),
|
||||||
std::move(shader),
|
std::move(shader),
|
||||||
|
std::move(clip),
|
||||||
device.colorType(),
|
device.colorType(),
|
||||||
device.alphaType(),
|
device.alphaType(),
|
||||||
blendMode,
|
blendMode,
|
||||||
@ -526,10 +579,14 @@ namespace {
|
|||||||
|
|
||||||
class Blitter final : public SkBlitter {
|
class Blitter final : public SkBlitter {
|
||||||
public:
|
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)
|
: fDevice(device)
|
||||||
, fUniforms(kBlitterUniformsCount)
|
, fUniforms(kBlitterUniformsCount)
|
||||||
, fParams(effective_params(device, paint, ctm))
|
, fParams(effective_params(device, paint, ctm, std::move(clip)))
|
||||||
, fKey(Builder::CacheKey(fParams, &fUniforms, &fAlloc, ok))
|
, fKey(Builder::CacheKey(fParams, &fUniforms, &fAlloc, ok))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
@ -588,7 +645,8 @@ namespace {
|
|||||||
SkDEBUGCODE(size_t prev = fUniforms.buf.size();)
|
SkDEBUGCODE(size_t prev = fUniforms.buf.size();)
|
||||||
fUniforms.buf.resize(kBlitterUniformsCount);
|
fUniforms.buf.resize(kBlitterUniformsCount);
|
||||||
Builder builder{fParams.withCoverage(coverage), &fUniforms, &fAlloc};
|
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());
|
skvm::Program program = builder.done(debug_name(key).c_str());
|
||||||
if (false) {
|
if (false) {
|
||||||
@ -789,8 +847,9 @@ skvm::Color skvm::BlendModeProgram(skvm::Builder* p,
|
|||||||
SkBlitter* SkCreateSkVMBlitter(const SkPixmap& device,
|
SkBlitter* SkCreateSkVMBlitter(const SkPixmap& device,
|
||||||
const SkPaint& paint,
|
const SkPaint& paint,
|
||||||
const SkMatrix& ctm,
|
const SkMatrix& ctm,
|
||||||
SkArenaAlloc* alloc) {
|
SkArenaAlloc* alloc,
|
||||||
|
sk_sp<SkShader> clip) {
|
||||||
bool ok = true;
|
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;
|
return ok ? blitter : nullptr;
|
||||||
}
|
}
|
||||||
|
@ -139,7 +139,28 @@ protected:
|
|||||||
Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override { return nullptr; }
|
Context* onMakeContext(const ContextRec&, SkArenaAlloc*) const override { return nullptr; }
|
||||||
#endif
|
#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:
|
private:
|
||||||
SK_FLATTENABLE_HOOKS(SkCTMShader)
|
SK_FLATTENABLE_HOOKS(SkCTMShader)
|
||||||
@ -164,19 +185,6 @@ sk_sp<SkFlattenable> SkCTMShader::CreateProc(SkReadBuffer& buffer) {
|
|||||||
return nullptr;
|
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 {
|
sk_sp<SkShader> SkShaderBase::makeWithCTM(const SkMatrix& postM) const {
|
||||||
return postM.isIdentity() ? sk_ref_sp(this)
|
return postM.isIdentity() ? sk_ref_sp(this)
|
||||||
: sk_sp<SkShader>(new SkCTMShader(sk_ref_sp(this), postM));
|
: sk_sp<SkShader>(new SkCTMShader(sk_ref_sp(this), postM));
|
||||||
|
Loading…
Reference in New Issue
Block a user