opaque shaders and srcover -> src

Program caching makes whether or not this is safe a little subtle.  It
is safe and I've tried to explain the subtlety in comments in the new
non-virtual Shader program() hook that forces all opaque shaders to
opaque, and in comments in SkVMBlitter where we make use of isOpaque().

I think this is the tidiest way to get the optimization and safety,
though it is a little awkward to have to write epic cross-referenced
comments explaining first why it might not generally be safe to use
shader->isOpaque() to make decisions in the blitter, and then how we
actually have made it really quite safe.

Many tiny partial-coverage diffs from how coverage interacts differently
with SrcOver and Src, SrcOver folding coverage into the src color before
blending with dst, Src just lerping with dst.

This all comes together really quite magically, changing a program to
draw a solid opaque color from this,

    11 registers, 41 instructions:
    r0 = uniform32 arg(0) 0
    r1 = splat FF (3.5733111e-43)
    r2 = extract r0 8 r1
    r3 = splat 80 (1.793662e-43)
    r4 = extract r0 24 r1
    r5 = sub_i32 r1 r4
    r6 = extract r0 16 r1
    r0 = extract r0 0 r1
    loop:
    r7 = load32 arg(1)
    r8 = extract r7 8 r1
    r8 = mul_i32 r8 r5
    r8 = add_i32 r8 r3
    r9 = shr_i32 r8 8
    r9 = add_i32 r8 r9
    r9 = shr_i32 r9 8
    r9 = add_i32 r2 r9
    r8 = extract r7 0 r1
    r8 = mul_i32 r8 r5
    r8 = add_i32 r8 r3
    r10 = shr_i32 r8 8
    r10 = add_i32 r8 r10
    r10 = shr_i32 r10 8
    r10 = add_i32 r6 r10
    r9 = pack r10 r9 8
    r10 = extract r7 16 r1
    r10 = mul_i32 r10 r5
    r10 = add_i32 r10 r3
    r8 = shr_i32 r10 8
    r8 = add_i32 r10 r8
    r8 = shr_i32 r8 8
    r8 = add_i32 r0 r8
    r7 = extract r7 24 r1
    r7 = mul_i32 r7 r5
    r7 = add_i32 r7 r3
    r10 = shr_i32 r7 8
    r10 = add_i32 r7 r10
    r10 = shr_i32 r10 8
    r10 = add_i32 r4 r10
    r10 = pack r8 r10 8
    r10 = pack r9 r10 16
    store32 arg(1) r10

to this,

    4 registers, 9 instructions:
    r0 = uniform32 arg(0) 0
    r1 = splat FF (3.5733111e-43)
    r2 = extract r0 8 r1
    r3 = extract r0 16 r1
    r2 = pack r3 r2 8
    r0 = extract r0 0 r1
    r1 = pack r0 r1 8
    r1 = pack r2 r1 16
    loop:
    store32 arg(1) r1

Change-Id: Ic1e2ac1496252970743ef44b17908965bc73e384
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/252198
Commit-Queue: Mike Klein <mtklein@google.com>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Mike Klein 2019-11-01 11:36:55 -05:00 committed by Skia Commit-Bot
parent 4483c22ead
commit 0393505a2d
5 changed files with 90 additions and 41 deletions

View File

@ -349,10 +349,10 @@ namespace {
sk_sp<SkShader> fShader;
uint32_t fAlpha; // [0,255], 4 bytes to keep nice alignment in uniform buffer.
bool program(skvm::Builder* p,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override {
bool onProgram(skvm::Builder* p,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override {
if (as_SB(fShader)->program(p, dstCS, uniforms, offset + sizeof(fAlpha), r,g,b,a)) {
if (p) {
// TODO: move the helpers onto skvm::Builder so I don't have to duplicate?
@ -388,16 +388,45 @@ namespace {
const char* getTypeName() const override { return "AlphaShader"; }
};
static sk_sp<SkShader> effective_shader(sk_sp<SkShader> shader, SkColor4f paintColor) {
// When there's no shader, the paint color becomes the shader.
static Params effective_params(const SkPixmap& device, const SkPaint& paint) {
// Color filters have been handled for us by SkBlitter::Choose().
SkASSERT(!paint.getColorFilter());
// If there's no explicit shader, the paint color is the shader,
// but if there is a shader, it's modulated by the paint alpha.
sk_sp<SkShader> shader = paint.refShader();
if (!shader) {
return SkShaders::Color(paintColor, nullptr);
shader = SkShaders::Color(paint.getColor4f(), nullptr);
} else if (paint.getAlpha() < 0xff) {
shader = sk_make_sp<AlphaShader>(std::move(shader), paint.getAlpha());
}
// When there is a shader, we modulate it by the paint's alpha.
uint8_t alpha = paintColor.toBytes_RGBA() >> 24;
return alpha < 0xff ? sk_make_sp<AlphaShader>(std::move(shader), alpha)
: std::move(shader);
// 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
// uniforms than its fundamental program structure and so unsafe to use.
//
// Opacity is such a powerful property that SkShaderBase::program()
// forces opacity for any shader subclass that claims isOpaque(), so
// 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.
return {
device.refColorSpace(),
std::move(shader),
device.colorType(),
device.alphaType(),
blendMode,
Coverage::Full, // Placeholder... withCoverage() will change as needed.
};
}
class Blitter final : public SkBlitter {
@ -406,18 +435,9 @@ namespace {
Blitter(const SkPixmap& device, const SkPaint& paint)
: fDevice(device)
, fParams {
device.refColorSpace(),
effective_shader(paint.refShader(), paint.getColor4f()),
device.colorType(),
device.alphaType(),
paint.getBlendMode(),
Coverage::Full,
}
, fParams(effective_params(device, paint))
, fKey(key(fParams))
{
// Color filters have been folded back into shader and/or paint color by now.
SkASSERT(!paint.getColorFilter());
ok = Builder::CanBuild(fParams);
}

View File

@ -124,17 +124,17 @@ static size_t common_uniforms(SkColor4f color, SkColorSpace* cs,
return sizeof(rgba);
}
bool SkColorShader::program(skvm::Builder* p,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
return common_program(SkColor4f::FromColor(fColor), sk_srgb_singleton(),
p, dstCS, uniforms, offset, r,g,b,a);
}
bool SkColor4Shader::program(skvm::Builder* p,
bool SkColorShader::onProgram(skvm::Builder* p,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
return common_program(SkColor4f::FromColor(fColor), sk_srgb_singleton(),
p, dstCS, uniforms, offset, r,g,b,a);
}
bool SkColor4Shader::onProgram(skvm::Builder* p,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
return common_program(fColor, fColorSpace.get(),
p, dstCS, uniforms, offset, r,g,b,a);
}

View File

@ -44,10 +44,10 @@ private:
bool onAppendStages(const SkStageRec&) const override;
bool program(skvm::Builder*,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
bool onProgram(skvm::Builder*,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
size_t uniforms(SkColorSpace* dstCS, uint8_t* uniform_buffer) const override;
SkColor fColor;
@ -70,10 +70,10 @@ private:
void flatten(SkWriteBuffer&) const override;
bool onAppendStages(const SkStageRec&) const override;
bool program(skvm::Builder*,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
bool onProgram(skvm::Builder*,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
size_t uniforms(SkColorSpace* dstCS, uint8_t* uniform_buffer) const override;
sk_sp<SkColorSpace> fColorSpace;

View File

@ -192,6 +192,31 @@ bool SkShaderBase::onAppendStages(const SkStageRec& rec) const {
return false;
}
bool SkShaderBase::program(skvm::Builder* p,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
// Force opaque alpha for all opaque shaders.
//
// This is primarily nice in that we usually have a 0xff constant splat
// somewhere in the program anyway, and this will let us drop the work the
// shader notionally does to produce alpha, p->extract(...), etc. in favor
// of that simple hoistable splat.
//
// More subtly, it makes isOpaque() a parameter to all shader program
// generation, guaranteeing that is-opaque bit is mixed into the overall
// shader program hash and blitter Key. This makes it safe for us to use
// that bit to make decisions when constructing an SkVMBlitter, like doing
// SrcOver -> Src strength reduction.
if (this->onProgram(p, dstCS, uniforms,offset, r,g,b,a)) {
if (p && this->isOpaque()) {
*a = p->splat(0xff);
}
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkFlattenable> SkEmptyShader::CreateProc(SkReadBuffer&) {

View File

@ -207,11 +207,15 @@ public:
return this->onAppendUpdatableStages(rec);
}
bool program(skvm::Builder*,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const;
virtual bool program(skvm::Builder*,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
virtual bool onProgram(skvm::Builder*,
SkColorSpace* dstCS,
skvm::Arg uniforms, int offset,
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
return false;
}