[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 <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2019-02-25 09:55:04 -05:00 committed by Skia Commit-Bot
parent 5532c2a34f
commit bd64f18947
8 changed files with 159 additions and 10 deletions

View File

@ -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<sksg::Scene>, SkString ver, const SkSize& size,
SkScalar inPoint, SkScalar outPoint, SkScalar duration);
SkScalar inPoint, SkScalar outPoint, SkScalar duration, uint32_t flags = 0);
std::unique_ptr<sksg::Scene> fScene;
const SkString fVersion;
@ -226,6 +240,7 @@ private:
const SkScalar fInPoint,
fOutPoint,
fDuration;
const uint32_t fFlags;
typedef SkNVRefCnt<Animation> INHERITED;
};

View File

@ -227,7 +227,8 @@ AnimationBuilder::AnimationBuilder(sk_sp<ResourceProvider> rp, sk_sp<SkFontMgr>
, fMarkerObserver(std::move(mobserver))
, fStats(stats)
, fDuration(duration)
, fFrameRate(framerate) {}
, fFrameRate(framerate)
, fHasNontrivialBlending(false) {}
std::unique_ptr<sksg::Scene> AnimationBuilder::parse(const skjson::ObjectValue& jroot) {
this->dispatchMarkers(jroot["markers"]);
@ -462,8 +463,18 @@ sk_sp<Animation> Animation::Builder::make(const char* data, size_t data_len) {
fLogger->log(Logger::Level::kError, "Could not parse animation.\n");
}
return sk_sp<Animation>(
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<Animation>(new Animation(std::move(scene),
std::move(version),
size,
inPoint,
outPoint,
duration,
flags));
}
sk_sp<Animation> Animation::Builder::makeFromFile(const char path[]) {
@ -474,13 +485,14 @@ sk_sp<Animation> Animation::Builder::makeFromFile(const char path[]) {
}
Animation::Animation(std::unique_ptr<sksg::Scene> 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);
}

View File

@ -25,6 +25,7 @@
#include "SkSGOpacityEffect.h"
#include "SkSGPath.h"
#include "SkSGRect.h"
#include "SkSGRenderEffect.h"
#include "SkSGTransform.h"
#include <algorithm>
@ -173,6 +174,44 @@ sk_sp<sksg::RenderNode> AttachMask(const skjson::ArrayValue* jmask,
} // namespace
sk_sp<sksg::RenderNode> AnimationBuilder::attachBlendMode(const skjson::NumberValue& jbm,
sk_sp<sksg::RenderNode> 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<size_t>(*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<sksg::RenderNode> AnimationBuilder::attachNestedAnimation(const char* name,
AnimatorScope* ascope) const {
class SkottieSGAdapter final : public sksg::RenderNode {
@ -462,7 +501,7 @@ private:
};
sk_sp<sksg::RenderNode> 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<sksg::RenderNode> 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,

View File

@ -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<sksg::RenderNode>) const;
sk_sp<sksg::Path> attachPath(const skjson::Value&, AnimatorScope*) const;
bool hasNontrivialBlending() const { return fHasNontrivialBlending; }
private:
struct AttachLayerContext;
struct AttachShapeContext;
@ -95,6 +98,9 @@ private:
sk_sp<sksg::RenderNode> attachLayer(const skjson::ObjectValue*, AttachLayerContext*) const;
sk_sp<sksg::RenderNode> attachLayerEffects(const skjson::ArrayValue& jeffects, AnimatorScope*,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachBlendMode(const skjson::NumberValue& jbm,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachShape(const skjson::ArrayValue*, AttachShapeContext*) const;
sk_sp<sksg::RenderNode> 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,

View File

@ -10,6 +10,7 @@
#include "SkSGEffectNode.h"
#include "SkBlendMode.h"
#include "SkColor.h"
#include <memory>
@ -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<BlendModeEffect> Make(sk_sp<RenderNode> 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<RenderNode>, SkBlendMode);
SkBlendMode fMode;
using INHERITED = EffectNode;
};
} // namespace sksg
#endif // SkSGRenderEffect_DEFINED

View File

@ -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<SkColorFilter> 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<SkColorFilter>);
ScopedRenderContext&& modulateBlendMode(SkBlendMode);
// Force content isolation for a node sub-DAG by applying the RenderContext
// overrides via a layer.

View File

@ -105,4 +105,26 @@ sk_sp<SkImageFilter> DropShadowImageFilter::onRevalidateFilter() {
fColor, mode, this->refInput(0));
}
sk_sp<BlendModeEffect> BlendModeEffect::Make(sk_sp<RenderNode> child, SkBlendMode mode) {
return child ? sk_sp<BlendModeEffect>(new BlendModeEffect(std::move(child), mode))
: nullptr;
}
BlendModeEffect::BlendModeEffect(sk_sp<RenderNode> 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

View File

@ -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<SkColorFilter> 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) {