[skottie] Optimize opacity layerization
- plumb a RenderContext RenderNode::render() argument to track pending
opacity
- defer opacity application until we can determine whether a layer is
required (group with multiple children) or the opacity can be pushed
onto the draw paint (for single/atomic draws)
Bug: skia:
Change-Id: I962ba08bad780395d5d738307bde986e9efa502b
Reviewed-on: https://skia-review.googlesource.com/146445
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/Skottie.cpp b/modules/skottie/src/Skottie.cpp
index 2d30292..0331cbb 100644
--- a/modules/skottie/src/Skottie.cpp
+++ b/modules/skottie/src/Skottie.cpp
@@ -715,7 +715,9 @@
return SkRect::MakeSize(fAnimation->size());
}
- void onRender(SkCanvas* canvas) const override {
+ void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
+ const auto local_scope =
+ ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), true);
fAnimation->render(canvas);
}
diff --git a/modules/sksg/include/SkSGClipEffect.h b/modules/sksg/include/SkSGClipEffect.h
index 674edb2..52ba7c7 100644
--- a/modules/sksg/include/SkSGClipEffect.h
+++ b/modules/sksg/include/SkSGClipEffect.h
@@ -32,7 +32,7 @@
protected:
ClipEffect(sk_sp<RenderNode>, sk_sp<GeometryNode>, bool aa);
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGColorFilter.h b/modules/sksg/include/SkSGColorFilter.h
index 9a7a837..a9e12e7 100644
--- a/modules/sksg/include/SkSGColorFilter.h
+++ b/modules/sksg/include/SkSGColorFilter.h
@@ -26,7 +26,7 @@
protected:
explicit ColorFilter(sk_sp<RenderNode>);
- void onRender(SkCanvas*) const final;
+ void onRender(SkCanvas*, const RenderContext*) const final;
sk_sp<SkColorFilter> fColorFilter;
diff --git a/modules/sksg/include/SkSGDraw.h b/modules/sksg/include/SkSGDraw.h
index 20ead3d..018dd1a 100644
--- a/modules/sksg/include/SkSGDraw.h
+++ b/modules/sksg/include/SkSGDraw.h
@@ -32,7 +32,7 @@
Draw(sk_sp<GeometryNode>, sk_sp<PaintNode> paint);
~Draw() override;
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGEffectNode.h b/modules/sksg/include/SkSGEffectNode.h
index ab0968e..575432d 100644
--- a/modules/sksg/include/SkSGEffectNode.h
+++ b/modules/sksg/include/SkSGEffectNode.h
@@ -23,7 +23,7 @@
explicit EffectNode(sk_sp<RenderNode>);
~EffectNode() override;
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGGroup.h b/modules/sksg/include/SkSGGroup.h
index b32f9af..3b8de32 100644
--- a/modules/sksg/include/SkSGGroup.h
+++ b/modules/sksg/include/SkSGGroup.h
@@ -33,7 +33,7 @@
Group();
~Group() override;
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
private:
diff --git a/modules/sksg/include/SkSGImage.h b/modules/sksg/include/SkSGImage.h
index 7d17a50..dd524c3 100644
--- a/modules/sksg/include/SkSGImage.h
+++ b/modules/sksg/include/SkSGImage.h
@@ -32,7 +32,7 @@
protected:
explicit Image(sk_sp<SkImage>);
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGMaskEffect.h b/modules/sksg/include/SkSGMaskEffect.h
index c4fd012..f668777 100644
--- a/modules/sksg/include/SkSGMaskEffect.h
+++ b/modules/sksg/include/SkSGMaskEffect.h
@@ -35,7 +35,7 @@
protected:
MaskEffect(sk_sp<RenderNode>, sk_sp<RenderNode> mask, Mode);
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGOpacityEffect.h b/modules/sksg/include/SkSGOpacityEffect.h
index d906775..a982304 100644
--- a/modules/sksg/include/SkSGOpacityEffect.h
+++ b/modules/sksg/include/SkSGOpacityEffect.h
@@ -27,7 +27,7 @@
protected:
OpacityEffect(sk_sp<RenderNode>, float);
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/include/SkSGRenderNode.h b/modules/sksg/include/SkSGRenderNode.h
index 4ca1aec..df69270 100644
--- a/modules/sksg/include/SkSGRenderNode.h
+++ b/modules/sksg/include/SkSGRenderNode.h
@@ -9,8 +9,10 @@
#define SkSGRenderNode_DEFINED
#include "SkSGNode.h"
+#include "SkTLazy.h"
class SkCanvas;
+class SkPaint;
namespace sksg {
@@ -18,14 +20,70 @@
* Base class for nodes which can render to a canvas.
*/
class RenderNode : public Node {
+protected:
+ struct RenderContext;
+
public:
// Render the node and its descendants to the canvas.
- void render(SkCanvas*) const;
+ void render(SkCanvas*, const RenderContext* = nullptr) const;
protected:
RenderNode();
- virtual void onRender(SkCanvas*) const = 0;
+ virtual void onRender(SkCanvas*, const RenderContext*) const = 0;
+
+ // Paint property overrides.
+ // These are deferred until we can determine whether they can be applied to the individual
+ // draw paints, or whether they require content isolation (applied to a layer).
+ struct RenderContext {
+ float fOpacity = 1;
+
+ // Returns true if the paint was modified.
+ bool modulatePaint(SkPaint*) const;
+ };
+
+ class ScopedRenderContext final {
+ public:
+ ScopedRenderContext(SkCanvas*, const RenderContext*);
+ ~ScopedRenderContext();
+
+ ScopedRenderContext(ScopedRenderContext&& that) { *this = std::move(that); }
+
+ ScopedRenderContext& operator=(ScopedRenderContext&& that) {
+ fCanvas = that.fCanvas;
+ fCtx = std::move(that.fCtx);
+ fRestoreCount = that.fRestoreCount;
+
+ // scope ownership is being transferred
+ that.fRestoreCount = -1;
+
+ return *this;
+ }
+
+ operator const RenderContext* () const { return fCtx.get(); }
+
+ // Add opacity to a render node sub-DAG.
+ ScopedRenderContext&& modulateOpacity(float opacity);
+
+ // Force content isolation for a node sub-DAG by applying the RenderContext
+ // overrides via a layer.
+ ScopedRenderContext&& setIsolation(const SkRect& bounds, bool do_isolate);
+
+ private:
+ // stack-only
+ void* operator new(size_t) = delete;
+ void* operator new(size_t, void*) = delete;
+
+ // Scopes cannot be copied.
+ ScopedRenderContext(const ScopedRenderContext&) = delete;
+ ScopedRenderContext& operator=(const ScopedRenderContext&) = delete;
+
+ RenderContext* writableContext();
+
+ SkCanvas* fCanvas;
+ SkTCopyOnFirstWrite<RenderContext> fCtx;
+ int fRestoreCount;
+ };
private:
typedef Node INHERITED;
diff --git a/modules/sksg/include/SkSGTransform.h b/modules/sksg/include/SkSGTransform.h
index 6b7fbc0..c878773 100644
--- a/modules/sksg/include/SkSGTransform.h
+++ b/modules/sksg/include/SkSGTransform.h
@@ -64,7 +64,7 @@
const sk_sp<Matrix>& getMatrix() const { return fMatrix; }
protected:
- void onRender(SkCanvas*) const override;
+ void onRender(SkCanvas*, const RenderContext*) const override;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
diff --git a/modules/sksg/src/SkSGClipEffect.cpp b/modules/sksg/src/SkSGClipEffect.cpp
index b2d68fc..045605d 100644
--- a/modules/sksg/src/SkSGClipEffect.cpp
+++ b/modules/sksg/src/SkSGClipEffect.cpp
@@ -24,7 +24,7 @@
this->unobserveInval(fClipNode);
}
-void ClipEffect::onRender(SkCanvas* canvas) const {
+void ClipEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
if (this->bounds().isEmpty())
return;
@@ -33,7 +33,7 @@
fClipNode->clip(canvas, fAntiAlias);
}
- this->INHERITED::onRender(canvas);
+ this->INHERITED::onRender(canvas, ctx);
}
SkRect ClipEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
diff --git a/modules/sksg/src/SkSGColorFilter.cpp b/modules/sksg/src/SkSGColorFilter.cpp
index d10b0ce..ab21117 100644
--- a/modules/sksg/src/SkSGColorFilter.cpp
+++ b/modules/sksg/src/SkSGColorFilter.cpp
@@ -16,7 +16,7 @@
ColorFilter::ColorFilter(sk_sp<RenderNode> child)
: INHERITED(std::move(child)) {}
-void ColorFilter::onRender(SkCanvas* canvas) const {
+void ColorFilter::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
if (this->bounds().isEmpty())
return;
@@ -28,7 +28,7 @@
canvas->saveLayer(this->bounds(), &p);
}
- this->INHERITED::onRender(canvas);
+ this->INHERITED::onRender(canvas, ctx);
}
ColorModeFilter::ColorModeFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
diff --git a/modules/sksg/src/SkSGDraw.cpp b/modules/sksg/src/SkSGDraw.cpp
index b73bf3b..a75886f 100644
--- a/modules/sksg/src/SkSGDraw.cpp
+++ b/modules/sksg/src/SkSGDraw.cpp
@@ -10,6 +10,7 @@
#include "SkSGGeometryNode.h"
#include "SkSGInvalidationController.h"
#include "SkSGPaintNode.h"
+#include "SkTLazy.h"
namespace sksg {
@@ -25,13 +26,17 @@
this->unobserveInval(fPaint);
}
-void Draw::onRender(SkCanvas* canvas) const {
- const auto& paint = fPaint->makePaint();
- const auto skipDraw = paint.nothingToDraw() ||
- (paint.getStyle() == SkPaint::kStroke_Style && paint.getStrokeWidth() <= 0);
+void Draw::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
+ SkTCopyOnFirstWrite<SkPaint> paint(fPaint->makePaint());
+ if (ctx) {
+ ctx->modulatePaint(paint.writable());
+ }
+
+ const auto skipDraw = paint->nothingToDraw() ||
+ (paint->getStyle() == SkPaint::kStroke_Style && paint->getStrokeWidth() <= 0);
if (!skipDraw) {
- fGeometry->draw(canvas, paint);
+ fGeometry->draw(canvas, *paint);
}
}
diff --git a/modules/sksg/src/SkSGEffectNode.cpp b/modules/sksg/src/SkSGEffectNode.cpp
index 70050cc..272d509 100644
--- a/modules/sksg/src/SkSGEffectNode.cpp
+++ b/modules/sksg/src/SkSGEffectNode.cpp
@@ -18,8 +18,8 @@
this->unobserveInval(fChild);
}
-void EffectNode::onRender(SkCanvas* canvas) const {
- fChild->render(canvas);
+void EffectNode::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
+ fChild->render(canvas, ctx);
}
SkRect EffectNode::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
diff --git a/modules/sksg/src/SkSGGroup.cpp b/modules/sksg/src/SkSGGroup.cpp
index aeccf23..9911292 100644
--- a/modules/sksg/src/SkSGGroup.cpp
+++ b/modules/sksg/src/SkSGGroup.cpp
@@ -45,9 +45,15 @@
this->invalidate();
}
-void Group::onRender(SkCanvas* canvas) const {
+void Group::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
+ // TODO: this heuristic works at the moment, but:
+ // a) it is fragile because it relies on all leaf render nodes being atomic draws
+ // b) could be improved by e.g. detecting all leaf render draws are non-overlapping
+ const auto isolate = fChildren.count() > 1;
+ const auto local_ctx = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(), isolate);
+
for (const auto& child : fChildren) {
- child->render(canvas);
+ child->render(canvas, local_ctx);
}
}
diff --git a/modules/sksg/src/SkSGImage.cpp b/modules/sksg/src/SkSGImage.cpp
index d06f6be..87c686d 100644
--- a/modules/sksg/src/SkSGImage.cpp
+++ b/modules/sksg/src/SkSGImage.cpp
@@ -14,11 +14,15 @@
Image::Image(sk_sp<SkImage> image) : fImage(std::move(image)) {}
-void Image::onRender(SkCanvas* canvas) const {
+void Image::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
SkPaint paint;
paint.setAntiAlias(fAntiAlias);
paint.setFilterQuality(fQuality);
+ if (ctx) {
+ ctx->modulatePaint(&paint);
+ }
+
canvas->drawImage(fImage, 0, 0, &paint);
}
diff --git a/modules/sksg/src/SkSGMaskEffect.cpp b/modules/sksg/src/SkSGMaskEffect.cpp
index 16e4c0d..ff869fd 100644
--- a/modules/sksg/src/SkSGMaskEffect.cpp
+++ b/modules/sksg/src/SkSGMaskEffect.cpp
@@ -22,13 +22,14 @@
this->unobserveInval(fMaskNode);
}
-void MaskEffect::onRender(SkCanvas* canvas) const {
+void MaskEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
if (this->bounds().isEmpty())
return;
SkAutoCanvasRestore acr(canvas, false);
canvas->saveLayer(this->bounds(), nullptr);
+ // Note: the paint overrides in ctx don't apply to the mask.
fMaskNode->render(canvas);
@@ -36,7 +37,7 @@
p.setBlendMode(fMaskMode == Mode::kNormal ? SkBlendMode::kSrcIn : SkBlendMode::kSrcOut);
canvas->saveLayer(this->bounds(), &p);
- this->INHERITED::onRender(canvas);
+ this->INHERITED::onRender(canvas, ctx);
}
diff --git a/modules/sksg/src/SkSGOpacityEffect.cpp b/modules/sksg/src/SkSGOpacityEffect.cpp
index b1ff10d..bb2fadf 100644
--- a/modules/sksg/src/SkSGOpacityEffect.cpp
+++ b/modules/sksg/src/SkSGOpacityEffect.cpp
@@ -17,19 +17,14 @@
: INHERITED(std::move(child))
, fOpacity(opacity) {}
-void OpacityEffect::onRender(SkCanvas* canvas) const {
+void OpacityEffect::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
// opacity <= 0 disables rendering
if (fOpacity <= 0)
return;
- // TODO: we could avoid savelayer if there is no more than one drawing primitive
- // in the sub-DAG.
- SkAutoCanvasRestore acr(canvas, false);
- if (fOpacity < 1) {
- canvas->saveLayerAlpha(&this->bounds(), roundf(fOpacity * 255));
- }
+ const auto local_context = ScopedRenderContext(canvas, ctx).modulateOpacity(fOpacity);
- this->INHERITED::onRender(canvas);
+ this->INHERITED::onRender(canvas, local_context);
}
SkRect OpacityEffect::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
diff --git a/modules/sksg/src/SkSGRenderNode.cpp b/modules/sksg/src/SkSGRenderNode.cpp
index e952c69..417cae9 100644
--- a/modules/sksg/src/SkSGRenderNode.cpp
+++ b/modules/sksg/src/SkSGRenderNode.cpp
@@ -7,13 +7,72 @@
#include "SkSGRenderNode.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
namespace sksg {
RenderNode::RenderNode() : INHERITED(0) {}
-void RenderNode::render(SkCanvas* canvas) const {
+void RenderNode::render(SkCanvas* canvas, const RenderContext* ctx) const {
SkASSERT(!this->hasInval());
- this->onRender(canvas);
+ this->onRender(canvas, ctx);
+}
+
+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) {
+ paint->setAlpha(alpha);
+ return true;
+ }
+
+ return false;
+}
+
+RenderNode::ScopedRenderContext::ScopedRenderContext(SkCanvas* canvas, const RenderContext* ctx)
+ : fCanvas(canvas)
+ , fCtx(ctx)
+ , fRestoreCount(canvas->getSaveCount()) {}
+
+RenderNode::ScopedRenderContext::~ScopedRenderContext() {
+ if (fRestoreCount >= 0) {
+ fCanvas->restoreToCount(fRestoreCount);
+ }
+}
+
+RenderNode::RenderContext* RenderNode::ScopedRenderContext::writableContext() {
+ // If no inherited context is present, allocate one in local storage.
+ if (!fCtx.get()) {
+ // N.B.: we have to force a copy while the default source is in scope.
+ // TODO: add SkTCopyOnWrite::init_copy() to simplify this
+ RenderContext default_ctx;
+ fCtx.init(default_ctx);
+ return fCtx.writable();
+ }
+ return fCtx.writable();
+}
+
+RenderNode::ScopedRenderContext&&
+RenderNode::ScopedRenderContext::modulateOpacity(float opacity) {
+ SkASSERT(opacity >= 0 && opacity <= 1);
+ if (opacity < 1) {
+ this->writableContext()->fOpacity *= opacity;
+ }
+ return std::move(*this);
+}
+
+RenderNode::ScopedRenderContext&&
+RenderNode::ScopedRenderContext::setIsolation(const SkRect& bounds, bool isolation) {
+ if (isolation && fCtx.get()) {
+ SkPaint layer_paint;
+ if (fCtx->modulatePaint(&layer_paint)) {
+ fCanvas->saveLayer(bounds, &layer_paint);
+ *fCtx.writable() = RenderContext();
+ }
+ }
+ return std::move(*this);
}
} // namespace sksg
diff --git a/modules/sksg/src/SkSGTransform.cpp b/modules/sksg/src/SkSGTransform.cpp
index 6a985a9..41b6a35 100644
--- a/modules/sksg/src/SkSGTransform.cpp
+++ b/modules/sksg/src/SkSGTransform.cpp
@@ -47,11 +47,11 @@
this->unobserveInval(fMatrix);
}
-void Transform::onRender(SkCanvas* canvas) const {
+void Transform::onRender(SkCanvas* canvas, const RenderContext* ctx) const {
const auto& m = fMatrix->getTotalMatrix();
SkAutoCanvasRestore acr(canvas, !m.isIdentity());
canvas->concat(m);
- this->INHERITED::onRender(canvas);
+ this->INHERITED::onRender(canvas, ctx);
}
SkRect Transform::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
diff --git a/tools/viewer/SlideDir.cpp b/tools/viewer/SlideDir.cpp
index 517d823..584bfe8 100644
--- a/tools/viewer/SlideDir.cpp
+++ b/tools/viewer/SlideDir.cpp
@@ -72,9 +72,11 @@
return SkRect::MakeIWH(isize.width(), isize.height());
}
- void onRender(SkCanvas* canvas) const override {
+ void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
SkAutoCanvasRestore acr(canvas, true);
canvas->clipRect(SkRect::Make(fSlide->getDimensions()), true);
+
+ // TODO: commit the context?
fSlide->draw(canvas);
}