From a12c15376ce3f94c81f461120067f8bbcf0a0c69 Mon Sep 17 00:00:00 2001 From: Brian Salomon Date: Mon, 13 Feb 2017 12:41:44 -0500 Subject: Add preserves premul and modulate optimization to compose fragment processors. Fixes out of range colors produced by matrix convolution and dither effects. Adds modulate optimization to matrix convolution. Change-Id: I8424250a52e864f4b5feaf4474293695c26039d8 Reviewed-on: https://skia-review.googlesource.com/8351 Commit-Queue: Brian Salomon Reviewed-by: Greg Daniel --- src/gpu/effects/GrDitherEffect.cpp | 2 +- src/gpu/effects/GrMatrixConvolutionEffect.cpp | 8 +- src/gpu/effects/GrXfermodeFragmentProcessor.cpp | 199 +++++++++++++++++++++--- 3 files changed, 180 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/gpu/effects/GrDitherEffect.cpp b/src/gpu/effects/GrDitherEffect.cpp index 036d42c4cf..adae12c979 100644 --- a/src/gpu/effects/GrDitherEffect.cpp +++ b/src/gpu/effects/GrDitherEffect.cpp @@ -73,7 +73,7 @@ void GLDitherEffect::emitCode(EmitArgs& args) { fragBuilder->codeAppendf("\t\tfloat r = " "fract(sin(dot(sk_FragCoord.xy, vec2(12.9898,78.233))) * " "43758.5453);\n"); - fragBuilder->codeAppendf("\t\t%s = (1.0/255.0) * vec4(r, r, r, r) + %s;\n", + fragBuilder->codeAppendf("\t\t%s = clamp((1.0/255.0) * vec4(r, r, r, r) + %s, 0, 1);\n", args.fOutputColor, GrGLSLExpr4(args.fInputColor).c_str()); } diff --git a/src/gpu/effects/GrMatrixConvolutionEffect.cpp b/src/gpu/effects/GrMatrixConvolutionEffect.cpp index 1a40514c83..fc8add85fd 100644 --- a/src/gpu/effects/GrMatrixConvolutionEffect.cpp +++ b/src/gpu/effects/GrMatrixConvolutionEffect.cpp @@ -98,6 +98,7 @@ void GrGLMatrixConvolutionEffect::emitCode(EmitArgs& args) { } if (mce.convolveAlpha()) { fragBuilder->codeAppendf("%s = sum * %s + %s;", args.fOutputColor, gain, bias); + fragBuilder->codeAppendf("%s.a = clamp(%s.a, 0, 1);", args.fOutputColor, args.fOutputColor); fragBuilder->codeAppendf("%s.rgb = clamp(%s.rgb, 0.0, %s.a);", args.fOutputColor, args.fOutputColor, args.fOutputColor); } else { @@ -109,7 +110,7 @@ void GrGLMatrixConvolutionEffect::emitCode(EmitArgs& args) { coords2D, args.fTexSamplers[0]); fragBuilder->codeAppendf("%s.a = c.a;", args.fOutputColor); - fragBuilder->codeAppendf("%s.rgb = sum.rgb * %s + %s;", args.fOutputColor, gain, bias); + fragBuilder->codeAppendf("%s.rgb = clamp(sum.rgb * %s + %s, 0, 1);", args.fOutputColor, gain, bias); fragBuilder->codeAppendf("%s.rgb *= %s.a;", args.fOutputColor, args.fOutputColor); } @@ -157,9 +158,8 @@ GrMatrixConvolutionEffect::GrMatrixConvolutionEffect(GrTexture* texture, const SkIPoint& kernelOffset, GrTextureDomain::Mode tileMode, bool convolveAlpha) - // To advertise either the modulation or opaqueness optimizations we'd have to examine the - // parameters. - : INHERITED(texture, nullptr, SkMatrix::I(), kNone_OptimizationFlags) + // To advertise the preserves opaqueness optimization we'd have to examine the parameters. + : INHERITED(texture, nullptr, SkMatrix::I(), kModulatesInput_OptimizationFlag) , fKernelSize(kernelSize) , fGain(SkScalarToFloat(gain)) , fBias(SkScalarToFloat(bias) / 255.0f) diff --git a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp index 612ebeab69..5433b67352 100644 --- a/src/gpu/effects/GrXfermodeFragmentProcessor.cpp +++ b/src/gpu/effects/GrXfermodeFragmentProcessor.cpp @@ -1,9 +1,9 @@ /* -* Copyright 2015 Google Inc. -* -* Use of this source code is governed by a BSD-style license that can be -* found in the LICENSE file. -*/ + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ #include "effects/GrXfermodeFragmentProcessor.h" @@ -49,11 +49,73 @@ public: private: static OptimizationFlags OptFlags(const GrFragmentProcessor* src, const GrFragmentProcessor* dst, SkBlendMode mode) { + OptimizationFlags flags; + switch (mode) { + case SkBlendMode::kClear: + case SkBlendMode::kSrc: + case SkBlendMode::kDst: + SkFAIL("Should never create clear, src, or dst compose two FP."); + flags = kNone_OptimizationFlags; + break; + + // Produces opaque if both src and dst are opaque. + case SkBlendMode::kSrcIn: + case SkBlendMode::kDstIn: + case SkBlendMode::kModulate: + flags = src->preservesOpaqueInput() && dst->preservesOpaqueInput() + ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // Produces zero when both are opaque, indeterminate if one is opaque. + case SkBlendMode::kSrcOut: + case SkBlendMode::kDstOut: + case SkBlendMode::kXor: + flags = kNone_OptimizationFlags; + break; + + // Is opaque if the dst is opaque. + case SkBlendMode::kSrcATop: + flags = dst->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // DstATop is the converse of kSrcATop. Screen is also opaque if the src is a opaque. + case SkBlendMode::kDstATop: + case SkBlendMode::kScreen: + flags = src->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // These modes are all opaque if either src or dst is opaque. All the advanced modes + // compute alpha as src-over. + case SkBlendMode::kSrcOver: + case SkBlendMode::kDstOver: + case SkBlendMode::kPlus: + case SkBlendMode::kOverlay: + case SkBlendMode::kDarken: + case SkBlendMode::kLighten: + case SkBlendMode::kColorDodge: + case SkBlendMode::kColorBurn: + case SkBlendMode::kHardLight: + case SkBlendMode::kSoftLight: + case SkBlendMode::kDifference: + case SkBlendMode::kExclusion: + case SkBlendMode::kMultiply: + case SkBlendMode::kHue: + case SkBlendMode::kSaturation: + case SkBlendMode::kColor: + case SkBlendMode::kLuminosity: + flags = src->preservesOpaqueInput() || dst->preservesOpaqueInput() + ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + } if (does_cpu_blend_impl_match_gpu(mode) && src->hasConstantOutputForConstantInput() && dst->hasConstantOutputForConstantInput()) { - return kConstantOutputForConstantInput_OptimizationFlag; + flags |= kConstantOutputForConstantInput_OptimizationFlag; } - return kNone_OptimizationFlags; + return flags; } bool onIsEqual(const GrFragmentProcessor& other) const override { @@ -101,8 +163,10 @@ sk_sp ComposeTwoFragmentProcessor::TestCreate(GrProcessorTe sk_sp fpA(GrProcessorUnitTest::MakeChildFP(d)); sk_sp fpB(GrProcessorUnitTest::MakeChildFP(d)); - SkBlendMode mode = static_cast( - d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); + SkBlendMode mode; + do { + mode = static_cast(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); + } while (SkBlendMode::kClear == mode || SkBlendMode::kSrc == mode || SkBlendMode::kDst == mode); return sk_sp( new ComposeTwoFragmentProcessor(std::move(fpA), std::move(fpB), mode)); } @@ -172,10 +236,10 @@ public: kSrc_Child, }; - ComposeOneFragmentProcessor(sk_sp dst, SkBlendMode mode, Child child) - : INHERITED(OptFlags(dst.get(), mode)), fMode(mode), fChild(child) { + ComposeOneFragmentProcessor(sk_sp fp, SkBlendMode mode, Child child) + : INHERITED(OptFlags(fp.get(), mode, child)), fMode(mode), fChild(child) { this->initClassID(); - SkDEBUGCODE(int dstIndex = )this->registerChildProcessor(std::move(dst)); + SkDEBUGCODE(int dstIndex =) this->registerChildProcessor(std::move(fp)); SkASSERT(0 == dstIndex); } @@ -200,11 +264,90 @@ public: Child child() const { return fChild; } private: - OptimizationFlags OptFlags(const GrFragmentProcessor* child, SkBlendMode mode) { - if (does_cpu_blend_impl_match_gpu(mode) && child->hasConstantOutputForConstantInput()) { - return kConstantOutputForConstantInput_OptimizationFlag; + OptimizationFlags OptFlags(const GrFragmentProcessor* fp, SkBlendMode mode, Child child) { + OptimizationFlags flags; + switch (mode) { + case SkBlendMode::kClear: + SkFAIL("Should never create clear compose one FP."); + flags = kNone_OptimizationFlags; + break; + + case SkBlendMode::kSrc: + SkASSERT(child == kSrc_Child); + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + case SkBlendMode::kDst: + SkASSERT(child == kDst_Child); + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + break; + + // Produces opaque if both src and dst are opaque. These also will modulate the child's + // output by either the input color or alpha. + case SkBlendMode::kSrcIn: + case SkBlendMode::kDstIn: + case SkBlendMode::kModulate: + flags = fp->preservesOpaqueInput() + ? kPreservesOpaqueInput_OptimizationFlag | kModulatesInput_OptimizationFlag + : kModulatesInput_OptimizationFlag; + break; + + // Produces zero when both are opaque, indeterminate if one is opaque. + case SkBlendMode::kSrcOut: + case SkBlendMode::kDstOut: + case SkBlendMode::kXor: + flags = kNone_OptimizationFlags; + break; + + // Is opaque if the dst is opaque. + case SkBlendMode::kSrcATop: + if (child == kDst_Child) { + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + } else { + flags = kPreservesOpaqueInput_OptimizationFlag; + } + break; + + // DstATop is the converse of kSrcATop. Screen is also opaque if the src is a opaque. + case SkBlendMode::kDstATop: + case SkBlendMode::kScreen: + if (child == kSrc_Child) { + flags = fp->preservesOpaqueInput() ? kPreservesOpaqueInput_OptimizationFlag + : kNone_OptimizationFlags; + } else { + flags = kPreservesOpaqueInput_OptimizationFlag; + } + break; + + // These modes are all opaque if either src or dst is opaque. All the advanced modes + // compute alpha as src-over. + case SkBlendMode::kSrcOver: + case SkBlendMode::kDstOver: + case SkBlendMode::kPlus: + case SkBlendMode::kOverlay: + case SkBlendMode::kDarken: + case SkBlendMode::kLighten: + case SkBlendMode::kColorDodge: + case SkBlendMode::kColorBurn: + case SkBlendMode::kHardLight: + case SkBlendMode::kSoftLight: + case SkBlendMode::kDifference: + case SkBlendMode::kExclusion: + case SkBlendMode::kMultiply: + case SkBlendMode::kHue: + case SkBlendMode::kSaturation: + case SkBlendMode::kColor: + case SkBlendMode::kLuminosity: + flags = kPreservesOpaqueInput_OptimizationFlag; + break; + } + if (does_cpu_blend_impl_match_gpu(mode) && fp->hasConstantOutputForConstantInput()) { + flags |= kConstantOutputForConstantInput_OptimizationFlag; } - return kNone_OptimizationFlags; + return flags; } bool onIsEqual(const GrFragmentProcessor& that) const override { @@ -280,11 +423,13 @@ sk_sp ComposeOneFragmentProcessor::TestCreate(GrProcessorTe // For now, we'll prevent either children from being a shader with children to prevent the // possibility of an arbitrarily large tree of procs. sk_sp dst(GrProcessorUnitTest::MakeChildFP(d)); - SkBlendMode mode = static_cast( - d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); - ComposeOneFragmentProcessor::Child child = d->fRandom->nextBool() ? - ComposeOneFragmentProcessor::kDst_Child : - ComposeOneFragmentProcessor::kSrc_Child; + SkBlendMode mode; + ComposeOneFragmentProcessor::Child child; + do { + mode = static_cast(d->fRandom->nextRangeU(0, (int)SkBlendMode::kLastMode)); + child = d->fRandom->nextBool() ? kDst_Child : kSrc_Child; + } while (SkBlendMode::kClear == mode || (SkBlendMode::kDst == mode && child == kSrc_Child) || + (SkBlendMode::kSrc == mode && child == kDst_Child)); return sk_sp(new ComposeOneFragmentProcessor(std::move(dst), mode, child)); } #endif @@ -295,12 +440,18 @@ GrGLSLFragmentProcessor* ComposeOneFragmentProcessor::onCreateGLSLInstance() con ////////////////////////////////////////////////////////////////////////////// +// It may seems as though when the input FP is the dst and the mode is kDst (or same for src/kSrc) +// that these factories could simply return the input FP. However, that doesn't have quite +// the same effect as the returned compose FP will replace the FP's input with solid white and +// ignore the original input. This could be implemented as: +// RunInSeries(ConstColor(GrColor_WHITE, kIgnoreInput), inputFP). + sk_sp GrXfermodeFragmentProcessor::MakeFromDstProcessor( sk_sp dst, SkBlendMode mode) { switch (mode) { case SkBlendMode::kClear: return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), - GrConstColorProcessor::kIgnore_InputMode); + GrConstColorProcessor::kIgnore_InputMode); case SkBlendMode::kSrc: return nullptr; default: @@ -315,12 +466,12 @@ sk_sp GrXfermodeFragmentProcessor::MakeFromSrcProcessor( switch (mode) { case SkBlendMode::kClear: return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(), - GrConstColorProcessor::kIgnore_InputMode); + GrConstColorProcessor::kIgnore_InputMode); case SkBlendMode::kDst: return nullptr; default: return sk_sp( - new ComposeOneFragmentProcessor(src, mode, + new ComposeOneFragmentProcessor(std::move(src), mode, ComposeOneFragmentProcessor::kSrc_Child)); } } -- cgit v1.2.3