From bd64f18947abf273017a11c8aad09cc0508a184e Mon Sep 17 00:00:00 2001 From: Florin Malita Date: Mon, 25 Feb 2019 09:55:04 -0500 Subject: [PATCH] [skottie] Layer blend mode support Introduce sksg::BlendModeEffect and use to wrap Skottie layers based on their "bm" property. Depending on the presence of non-trivial layer blend modes and the nature of the destination buffer (fully transparent vs. unknown/pre-filled), we may now need to render animation frames into a separate layer for correct compositing. Track the presence of non-trivial layer blend modes such that we only incur this extra layer overhead when needed. Also allow clients to pass a "drawing to fully transparent buffer" hint such that we can avoid the extra layer even when blend modes are present. Change-Id: Iaf645878666da4349d0bef8890bbecad23a0aa9b Reviewed-on: https://skia-review.googlesource.com/c/194840 Reviewed-by: Mike Reed Commit-Queue: Florin Malita --- modules/skottie/include/Skottie.h | 17 ++++++++- modules/skottie/src/Skottie.cpp | 36 ++++++++++++++++--- modules/skottie/src/SkottieLayer.cpp | 46 ++++++++++++++++++++++++- modules/skottie/src/SkottiePriv.h | 9 ++++- modules/sksg/include/SkSGRenderEffect.h | 25 ++++++++++++++ modules/sksg/include/SkSGRenderNode.h | 5 ++- modules/sksg/src/SkSGRenderEffect.cpp | 22 ++++++++++++ modules/sksg/src/SkSGRenderNode.cpp | 9 ++++- 8 files changed, 159 insertions(+), 10 deletions(-) diff --git a/modules/skottie/include/Skottie.h b/modules/skottie/include/Skottie.h index 15b175e553..dfd73813d8 100644 --- a/modules/skottie/include/Skottie.h +++ b/modules/skottie/include/Skottie.h @@ -190,13 +190,23 @@ public: ~Animation(); + enum RenderFlag : uint32_t { + // When rendering into a known transparent buffer, clients can pass + // this flag to avoid some unnecessary compositing overhead for + // animations using layer blend modes. + kSkipTopLevelIsolation = 0x01, + }; + using RenderFlags = uint32_t; + /** * Draws the current animation frame. * * @param canvas destination canvas * @param dst optional destination rect + * @param flags optional RenderFlags */ void render(SkCanvas* canvas, const SkRect* dst = nullptr) const; + void render(SkCanvas* canvas, const SkRect* dst, RenderFlags) const; /** * Updates the animation state for |t|. @@ -217,8 +227,12 @@ public: void setShowInval(bool show); private: + enum Flags : uint32_t { + kRequiresTopLevelIsolation = 1 << 0, // Needs to draw into a layer due to layer blending. + }; + Animation(std::unique_ptr, SkString ver, const SkSize& size, - SkScalar inPoint, SkScalar outPoint, SkScalar duration); + SkScalar inPoint, SkScalar outPoint, SkScalar duration, uint32_t flags = 0); std::unique_ptr fScene; const SkString fVersion; @@ -226,6 +240,7 @@ private: const SkScalar fInPoint, fOutPoint, fDuration; + const uint32_t fFlags; typedef SkNVRefCnt INHERITED; }; diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp index 17205177f0..d674ba6c05 100644 --- a/modules/skottie/src/Skottie.cpp +++ b/modules/skottie/src/Skottie.cpp @@ -227,7 +227,8 @@ AnimationBuilder::AnimationBuilder(sk_sp rp, sk_sp , fMarkerObserver(std::move(mobserver)) , fStats(stats) , fDuration(duration) - , fFrameRate(framerate) {} + , fFrameRate(framerate) + , fHasNontrivialBlending(false) {} std::unique_ptr AnimationBuilder::parse(const skjson::ObjectValue& jroot) { this->dispatchMarkers(jroot["markers"]); @@ -462,8 +463,18 @@ sk_sp Animation::Builder::make(const char* data, size_t data_len) { fLogger->log(Logger::Level::kError, "Could not parse animation.\n"); } - return sk_sp( - new Animation(std::move(scene), std::move(version), size, inPoint, outPoint, duration)); + uint32_t flags = 0; + if (builder.hasNontrivialBlending()) { + flags |= Flags::kRequiresTopLevelIsolation; + } + + return sk_sp(new Animation(std::move(scene), + std::move(version), + size, + inPoint, + outPoint, + duration, + flags)); } sk_sp Animation::Builder::makeFromFile(const char path[]) { @@ -474,13 +485,14 @@ sk_sp Animation::Builder::makeFromFile(const char path[]) { } Animation::Animation(std::unique_ptr scene, SkString version, const SkSize& size, - SkScalar inPoint, SkScalar outPoint, SkScalar duration) + SkScalar inPoint, SkScalar outPoint, SkScalar duration, uint32_t flags) : fScene(std::move(scene)) , fVersion(std::move(version)) , fSize(size) , fInPoint(inPoint) , fOutPoint(outPoint) - , fDuration(duration) { + , fDuration(duration) + , fFlags(flags) { // In case the client calls render before the first tick. this->seek(0); @@ -495,17 +507,31 @@ void Animation::setShowInval(bool show) { } void Animation::render(SkCanvas* canvas, const SkRect* dstR) const { + this->render(canvas, dstR, 0); +} + +void Animation::render(SkCanvas* canvas, const SkRect* dstR, RenderFlags renderFlags) const { TRACE_EVENT0("skottie", TRACE_FUNC); if (!fScene) return; SkAutoCanvasRestore restore(canvas, true); + const SkRect srcR = SkRect::MakeSize(this->size()); if (dstR) { canvas->concat(SkMatrix::MakeRectToRect(srcR, *dstR, SkMatrix::kCenter_ScaleToFit)); } + + if ((fFlags & Flags::kRequiresTopLevelIsolation) && + !(renderFlags & RenderFlag::kSkipTopLevelIsolation)) { + // The animation uses non-trivial blending, and needs + // to be rendered into a separate/transparent layer. + canvas->saveLayer(srcR, nullptr); + } + canvas->clipRect(srcR); + fScene->render(canvas); } diff --git a/modules/skottie/src/SkottieLayer.cpp b/modules/skottie/src/SkottieLayer.cpp index 24d64c8899..78c680d8be 100644 --- a/modules/skottie/src/SkottieLayer.cpp +++ b/modules/skottie/src/SkottieLayer.cpp @@ -25,6 +25,7 @@ #include "SkSGOpacityEffect.h" #include "SkSGPath.h" #include "SkSGRect.h" +#include "SkSGRenderEffect.h" #include "SkSGTransform.h" #include @@ -173,6 +174,44 @@ sk_sp AttachMask(const skjson::ArrayValue* jmask, } // namespace +sk_sp AnimationBuilder::attachBlendMode(const skjson::NumberValue& jbm, + sk_sp child) const { + static constexpr SkBlendMode kBlendModeMap[] = { + SkBlendMode::kSrcOver, // 0:'normal' + SkBlendMode::kMultiply, // 1:'multiply' + SkBlendMode::kScreen, // 2:'screen' + SkBlendMode::kOverlay, // 3:'overlay + SkBlendMode::kDarken, // 4:'darken' + SkBlendMode::kLighten, // 5:'lighten' + SkBlendMode::kColorDodge, // 6:'color-dodge' + SkBlendMode::kColorBurn, // 7:'color-burn' + SkBlendMode::kHardLight, // 8:'hard-light' + SkBlendMode::kSoftLight, // 9:'soft-light' + SkBlendMode::kDifference, // 10:'difference' + SkBlendMode::kExclusion, // 11:'exclusion' + SkBlendMode::kHue, // 12:'hue' + SkBlendMode::kSaturation, // 13:'saturation' + SkBlendMode::kColor, // 14:'color' + SkBlendMode::kLuminosity, // 15:'luminosity' + }; + + const auto bm_index = static_cast(*jbm); + if (bm_index >= SK_ARRAY_COUNT(kBlendModeMap)) { + this->log(Logger::Level::kWarning, &jbm, "Unsupported blend mode %lu\n", bm_index); + return child; + } + + const auto bm = kBlendModeMap[bm_index]; + + if (bm == SkBlendMode::kSrcOver) { + return child; + } + + fHasNontrivialBlending = true; + + return sksg::BlendModeEffect::Make(std::move(child), bm); +} + sk_sp AnimationBuilder::attachNestedAnimation(const char* name, AnimatorScope* ascope) const { class SkottieSGAdapter final : public sksg::RenderNode { @@ -462,7 +501,7 @@ private: }; sk_sp AnimationBuilder::attachLayer(const skjson::ObjectValue* jlayer, - AttachLayerContext* layerCtx) const { + AttachLayerContext* layerCtx) const { if (!jlayer) return nullptr; const LayerInfo layer_info = { @@ -526,6 +565,11 @@ sk_sp AnimationBuilder::attachLayer(const skjson::ObjectValue* layer = this->attachLayerEffects(*jeffects, &layer_animators, std::move(layer)); } + // Optional blend mode. + if (const skjson::NumberValue* jbm = (*jlayer)["bm"]) { + layer = this->attachBlendMode(*jbm, std::move(layer)); + } + class LayerController final : public sksg::GroupAnimator { public: LayerController(sksg::AnimatorList&& layer_animators, diff --git a/modules/skottie/src/SkottiePriv.h b/modules/skottie/src/SkottiePriv.h index 659d222b09..2a46b418d0 100644 --- a/modules/skottie/src/SkottiePriv.h +++ b/modules/skottie/src/SkottiePriv.h @@ -24,6 +24,7 @@ class SkFontMgr; namespace skjson { class ArrayValue; +class NumberValue; class ObjectValue; class Value; } // namespace skjson @@ -79,6 +80,8 @@ public: sk_sp) const; sk_sp attachPath(const skjson::Value&, AnimatorScope*) const; + bool hasNontrivialBlending() const { return fHasNontrivialBlending; } + private: struct AttachLayerContext; struct AttachShapeContext; @@ -95,6 +98,9 @@ private: sk_sp attachLayer(const skjson::ObjectValue*, AttachLayerContext*) const; sk_sp attachLayerEffects(const skjson::ArrayValue& jeffects, AnimatorScope*, sk_sp) const; + sk_sp attachBlendMode(const skjson::NumberValue& jbm, + sk_sp) const; + sk_sp attachShape(const skjson::ArrayValue*, AttachShapeContext*) const; sk_sp attachAssetRef(const skjson::ObjectValue&, AnimatorScope*, @@ -171,8 +177,9 @@ private: Animation::Builder::Stats* fStats; const float fDuration, fFrameRate; - mutable const char* fPropertyObserverContext; + mutable bool fHasNontrivialBlending : 1; + struct LayerInfo { float fInPoint, diff --git a/modules/sksg/include/SkSGRenderEffect.h b/modules/sksg/include/SkSGRenderEffect.h index 08eb87d520..9c0bd0a43b 100644 --- a/modules/sksg/include/SkSGRenderEffect.h +++ b/modules/sksg/include/SkSGRenderEffect.h @@ -10,6 +10,7 @@ #include "SkSGEffectNode.h" +#include "SkBlendMode.h" #include "SkColor.h" #include @@ -106,6 +107,30 @@ private: using INHERITED = ImageFilter; }; +/** + * Applies a SkBlendMode to descendant render nodes. + */ +class BlendModeEffect final : public EffectNode { +public: + ~BlendModeEffect() override; + + static sk_sp Make(sk_sp child, + SkBlendMode = SkBlendMode::kSrcOver); + + SG_ATTRIBUTE(Mode, SkBlendMode, fMode) + +protected: + void onRender(SkCanvas*, const RenderContext*) const override; + const RenderNode* onNodeAt(const SkPoint&) const override; + +private: + BlendModeEffect(sk_sp, SkBlendMode); + + SkBlendMode fMode; + + using INHERITED = EffectNode; +}; + } // namespace sksg #endif // SkSGRenderEffect_DEFINED diff --git a/modules/sksg/include/SkSGRenderNode.h b/modules/sksg/include/SkSGRenderNode.h index 6e44b5bc65..fa07a06ff0 100644 --- a/modules/sksg/include/SkSGRenderNode.h +++ b/modules/sksg/include/SkSGRenderNode.h @@ -10,6 +10,7 @@ #include "SkSGNode.h" +#include "SkBlendMode.h" #include "SkColorFilter.h" class SkCanvas; @@ -44,7 +45,8 @@ protected: // draw paints, or whether they require content isolation (applied to a layer). struct RenderContext { sk_sp fColorFilter; - float fOpacity = 1; + float fOpacity = 1; + SkBlendMode fBlendMode = SkBlendMode::kSrcOver; // Returns true if the paint was modified. bool modulatePaint(SkPaint*) const; @@ -73,6 +75,7 @@ protected: // Add (cumulative) paint overrides to a render node sub-DAG. ScopedRenderContext&& modulateOpacity(float opacity); ScopedRenderContext&& modulateColorFilter(sk_sp); + ScopedRenderContext&& modulateBlendMode(SkBlendMode); // Force content isolation for a node sub-DAG by applying the RenderContext // overrides via a layer. diff --git a/modules/sksg/src/SkSGRenderEffect.cpp b/modules/sksg/src/SkSGRenderEffect.cpp index 23254c2b97..7056ee9ded 100644 --- a/modules/sksg/src/SkSGRenderEffect.cpp +++ b/modules/sksg/src/SkSGRenderEffect.cpp @@ -105,4 +105,26 @@ sk_sp DropShadowImageFilter::onRevalidateFilter() { fColor, mode, this->refInput(0)); } +sk_sp BlendModeEffect::Make(sk_sp child, SkBlendMode mode) { + return child ? sk_sp(new BlendModeEffect(std::move(child), mode)) + : nullptr; +} + +BlendModeEffect::BlendModeEffect(sk_sp child, SkBlendMode mode) + : INHERITED(std::move(child)) + , fMode(mode) {} + +BlendModeEffect::~BlendModeEffect() = default; + +void BlendModeEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const { + const auto local_ctx = ScopedRenderContext(canvas, ctx).modulateBlendMode(fMode); + + this->INHERITED::onRender(canvas, local_ctx); +} + +const RenderNode* BlendModeEffect::onNodeAt(const SkPoint& p) const { + // TODO: we likely need to do something more sophisticated than delegate to descendants here. + return this->INHERITED::onNodeAt(p); +} + } // namespace sksg diff --git a/modules/sksg/src/SkSGRenderNode.cpp b/modules/sksg/src/SkSGRenderNode.cpp index f46a5620b7..c7b25b4c12 100644 --- a/modules/sksg/src/SkSGRenderNode.cpp +++ b/modules/sksg/src/SkSGRenderNode.cpp @@ -30,10 +30,11 @@ bool RenderNode::RenderContext::modulatePaint(SkPaint* paint) const { const auto initial_alpha = paint->getAlpha(), alpha = SkToU8(sk_float_round2int(initial_alpha * fOpacity)); - if (alpha != initial_alpha || fColorFilter) { + if (alpha != initial_alpha || fColorFilter || fBlendMode != paint->getBlendMode()) { paint->setAlpha(alpha); paint->setColorFilter(SkColorFilter::MakeComposeFilter(fColorFilter, paint->refColorFilter())); + paint->setBlendMode(fBlendMode); return true; } @@ -65,6 +66,12 @@ RenderNode::ScopedRenderContext::modulateColorFilter(sk_sp cf) { return std::move(*this); } +RenderNode::ScopedRenderContext&& +RenderNode::ScopedRenderContext::modulateBlendMode(SkBlendMode mode) { + fCtx.fBlendMode = mode; + return std::move(*this); +} + RenderNode::ScopedRenderContext&& RenderNode::ScopedRenderContext::setIsolation(const SkRect& bounds, bool isolation) { if (isolation) {