[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:
parent
5532c2a34f
commit
bd64f18947
@ -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;
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user