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:
parent
4483c22ead
commit
0393505a2d
@ -349,7 +349,7 @@ namespace {
|
|||||||
sk_sp<SkShader> fShader;
|
sk_sp<SkShader> fShader;
|
||||||
uint32_t fAlpha; // [0,255], 4 bytes to keep nice alignment in uniform buffer.
|
uint32_t fAlpha; // [0,255], 4 bytes to keep nice alignment in uniform buffer.
|
||||||
|
|
||||||
bool program(skvm::Builder* p,
|
bool onProgram(skvm::Builder* p,
|
||||||
SkColorSpace* dstCS,
|
SkColorSpace* dstCS,
|
||||||
skvm::Arg uniforms, int offset,
|
skvm::Arg uniforms, int offset,
|
||||||
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override {
|
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override {
|
||||||
@ -388,16 +388,45 @@ namespace {
|
|||||||
const char* getTypeName() const override { return "AlphaShader"; }
|
const char* getTypeName() const override { return "AlphaShader"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
static sk_sp<SkShader> effective_shader(sk_sp<SkShader> shader, SkColor4f paintColor) {
|
static Params effective_params(const SkPixmap& device, const SkPaint& paint) {
|
||||||
// When there's no shader, the paint color becomes the shader.
|
// 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) {
|
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.
|
// The most common blend mode is SrcOver, and it can be strength-reduced
|
||||||
uint8_t alpha = paintColor.toBytes_RGBA() >> 24;
|
// _greatly_ to Src mode when the shader is opaque.
|
||||||
return alpha < 0xff ? sk_make_sp<AlphaShader>(std::move(shader), alpha)
|
SkBlendMode blendMode = paint.getBlendMode();
|
||||||
: std::move(shader);
|
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 {
|
class Blitter final : public SkBlitter {
|
||||||
@ -406,18 +435,9 @@ namespace {
|
|||||||
|
|
||||||
Blitter(const SkPixmap& device, const SkPaint& paint)
|
Blitter(const SkPixmap& device, const SkPaint& paint)
|
||||||
: fDevice(device)
|
: fDevice(device)
|
||||||
, fParams {
|
, fParams(effective_params(device, paint))
|
||||||
device.refColorSpace(),
|
|
||||||
effective_shader(paint.refShader(), paint.getColor4f()),
|
|
||||||
device.colorType(),
|
|
||||||
device.alphaType(),
|
|
||||||
paint.getBlendMode(),
|
|
||||||
Coverage::Full,
|
|
||||||
}
|
|
||||||
, fKey(key(fParams))
|
, fKey(key(fParams))
|
||||||
{
|
{
|
||||||
// Color filters have been folded back into shader and/or paint color by now.
|
|
||||||
SkASSERT(!paint.getColorFilter());
|
|
||||||
ok = Builder::CanBuild(fParams);
|
ok = Builder::CanBuild(fParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,14 +124,14 @@ static size_t common_uniforms(SkColor4f color, SkColorSpace* cs,
|
|||||||
return sizeof(rgba);
|
return sizeof(rgba);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SkColorShader::program(skvm::Builder* p,
|
bool SkColorShader::onProgram(skvm::Builder* p,
|
||||||
SkColorSpace* dstCS,
|
SkColorSpace* dstCS,
|
||||||
skvm::Arg uniforms, int offset,
|
skvm::Arg uniforms, int offset,
|
||||||
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
|
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
|
||||||
return common_program(SkColor4f::FromColor(fColor), sk_srgb_singleton(),
|
return common_program(SkColor4f::FromColor(fColor), sk_srgb_singleton(),
|
||||||
p, dstCS, uniforms, offset, r,g,b,a);
|
p, dstCS, uniforms, offset, r,g,b,a);
|
||||||
}
|
}
|
||||||
bool SkColor4Shader::program(skvm::Builder* p,
|
bool SkColor4Shader::onProgram(skvm::Builder* p,
|
||||||
SkColorSpace* dstCS,
|
SkColorSpace* dstCS,
|
||||||
skvm::Arg uniforms, int offset,
|
skvm::Arg uniforms, int offset,
|
||||||
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
|
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
|
||||||
|
@ -44,7 +44,7 @@ private:
|
|||||||
|
|
||||||
bool onAppendStages(const SkStageRec&) const override;
|
bool onAppendStages(const SkStageRec&) const override;
|
||||||
|
|
||||||
bool program(skvm::Builder*,
|
bool onProgram(skvm::Builder*,
|
||||||
SkColorSpace* dstCS,
|
SkColorSpace* dstCS,
|
||||||
skvm::Arg uniforms, int offset,
|
skvm::Arg uniforms, int offset,
|
||||||
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
|
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
|
||||||
@ -70,7 +70,7 @@ private:
|
|||||||
void flatten(SkWriteBuffer&) const override;
|
void flatten(SkWriteBuffer&) const override;
|
||||||
bool onAppendStages(const SkStageRec&) const override;
|
bool onAppendStages(const SkStageRec&) const override;
|
||||||
|
|
||||||
bool program(skvm::Builder*,
|
bool onProgram(skvm::Builder*,
|
||||||
SkColorSpace* dstCS,
|
SkColorSpace* dstCS,
|
||||||
skvm::Arg uniforms, int offset,
|
skvm::Arg uniforms, int offset,
|
||||||
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
|
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const override;
|
||||||
|
@ -192,6 +192,31 @@ bool SkShaderBase::onAppendStages(const SkStageRec& rec) const {
|
|||||||
return false;
|
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&) {
|
sk_sp<SkFlattenable> SkEmptyShader::CreateProc(SkReadBuffer&) {
|
||||||
|
@ -207,8 +207,12 @@ public:
|
|||||||
return this->onAppendUpdatableStages(rec);
|
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*,
|
virtual bool onProgram(skvm::Builder*,
|
||||||
SkColorSpace* dstCS,
|
SkColorSpace* dstCS,
|
||||||
skvm::Arg uniforms, int offset,
|
skvm::Arg uniforms, int offset,
|
||||||
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
|
skvm::I32* r, skvm::I32* g, skvm::I32* b, skvm::I32* a) const {
|
||||||
|
Loading…
Reference in New Issue
Block a user