[skottie] Caching motion tile

Update MotionTileEffect to avoid rebuilding shaders redundantly,
at render time:

  1) build all shaders at revalidation time
  2) cache the layer content picture separately, and only rebuild when
     the layer content changes

To support #2, add some SG helpers for querying subtree inval state.

With this change, we avoid all render time allocations.

Notry: true
Change-Id: I55a1f95752704af6a667b266e725492de6640387
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/226512
Commit-Queue: Florin Malita <fmalita@chromium.org>
Commit-Queue: Mike Reed <reed@google.com>
Auto-Submit: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Florin Malita 2019-07-10 13:38:48 -04:00 committed by Skia Commit-Bot
parent 42ece2b7c9
commit ccacfa02d2
7 changed files with 84 additions and 37 deletions

View File

@ -55,8 +55,17 @@ protected:
const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
SkASSERT(this->children().size() == 1ul);
this->children()[0]->revalidate(ic, ctm);
// Re-record the layer picture if needed.
if (!fLayerPicture || this->hasChildrenInval()) {
SkASSERT(this->children().size() == 1ul);
const auto& layer = this->children()[0];
layer->revalidate(ic, ctm);
SkPictureRecorder recorder;
layer->render(recorder.beginRecording(fLayerSize.width(), fLayerSize.height()));
fLayerPicture = recorder.finishRecordingAsPicture();
}
// tileW and tileH use layer size percentage units.
const auto tileW = SkTPin(fTileW, 0.0f, 100.0f) * 0.01f * fLayerSize.width(),
@ -68,9 +77,12 @@ protected:
tile_size.width(),
tile_size.height());
fLayerShaderMatrix = SkMatrix::MakeRectToRect(SkRect::MakeWH(fLayerSize.width(),
fLayerSize.height()),
tile, SkMatrix::kFill_ScaleToFit);
const auto layerShaderMatrix = SkMatrix::MakeRectToRect(
SkRect::MakeWH(fLayerSize.width(), fLayerSize.height()),
tile, SkMatrix::kFill_ScaleToFit);
const auto tm = fMirrorEdges ? SkTileMode::kMirror : SkTileMode::kRepeat;
auto layer_shader = fLayerPicture->makeShader(tm, tm, &layerShaderMatrix);
if (fPhase) {
// To implement AE phase semantics, we construct a mask shader for the pass-through
@ -79,10 +91,10 @@ protected:
const auto phase_vec = fHorizontalPhase
? SkVector::Make(tile.width(), 0)
: SkVector::Make(0, tile.height());
const auto phase_shift = SkVector::Make(phase_vec.fX / fLayerShaderMatrix.getScaleX(),
phase_vec.fY / fLayerShaderMatrix.getScaleY())
const auto phase_shift = SkVector::Make(phase_vec.fX / layerShaderMatrix.getScaleX(),
phase_vec.fY / layerShaderMatrix.getScaleY())
* std::fmod(fPhase * (1/360.0f), 1);
fPhaseShaderMatrix.setTranslate(phase_shift);
const auto phase_shader_matrix = SkMatrix::MakeTrans(phase_shift.x(), phase_shift.y());
// The mask is generated using a step gradient shader, spanning 2 x tile width/height,
// and perpendicular to the phase vector.
@ -93,11 +105,18 @@ protected:
{ tile.x() + 2 * (tile.width() - phase_vec.fX),
tile.y() + 2 * (tile.height() - phase_vec.fY) }};
fMaskShader = SkGradientShader::MakeLinear(pts, colors, pos,
SK_ARRAY_COUNT(colors),
SkTileMode::kRepeat);
auto mask_shader = SkGradientShader::MakeLinear(pts, colors, pos,
SK_ARRAY_COUNT(colors),
SkTileMode::kRepeat);
// First drawing pass: in-place masked layer content.
fMainPassShader = SkShaders::Blend(SkBlendMode::kSrcIn , mask_shader, layer_shader);
// Second pass: phased-shifted layer content, with an inverse mask.
fPhasePassShader = SkShaders::Blend(SkBlendMode::kSrcOut, mask_shader, layer_shader,
&phase_shader_matrix);
} else {
fMaskShader = nullptr;
fMainPassShader = std::move(layer_shader);
fPhasePassShader = nullptr;
}
// outputW and outputH also use layer size percentage units.
@ -115,32 +134,14 @@ protected:
return;
}
const auto tm = fMirrorEdges ? SkTileMode::kMirror : SkTileMode::kRepeat;
SkPictureRecorder recorder;
SkASSERT(this->children().size() == 1ul);
this->children()[0]->render(recorder.beginRecording(fLayerSize.width(),
fLayerSize.height()));
const auto layer_shader =
recorder.finishRecordingAsPicture()->makeShader(tm, tm, &fLayerShaderMatrix);
SkPaint paint;
paint.setAntiAlias(true);
// First drawing pass: pass-through layer content, with an optional phase mask.
paint.setShader(fMaskShader
? SkShaders::Blend(SkBlendMode::kSrcIn, fMaskShader, layer_shader)
: layer_shader);
paint.setShader(fMainPassShader);
canvas->drawRect(this->bounds(), paint);
if (fMaskShader) {
// Second pass: phased/shifted layer rows/columns, with an inverse mask.
// TODO: would be nice for SkShaders::* to take an optional local matrix.
paint.setShader(
SkShaders::Blend(SkBlendMode::kSrcOut,
fMaskShader,
layer_shader->makeWithLocalMatrix(fPhaseShaderMatrix)));
if (fPhasePassShader) {
paint.setShader(fPhasePassShader);
canvas->drawRect(this->bounds(), paint);
}
}
@ -158,9 +159,9 @@ private:
bool fHorizontalPhase = false;
// These are computed/cached on revalidation.
SkMatrix fLayerShaderMatrix, // matrix for the pass-through layer content shader
fPhaseShaderMatrix; // matrix for the phased (shifted) rows/columns
sk_sp<SkShader> fMaskShader; // cached mask shader for the pass-through rows/columns
sk_sp<SkPicture> fLayerPicture; // cached picture for layer content
sk_sp<SkShader> fMainPassShader, // shader for the main tile(s)
fPhasePassShader; // shader for the phased tile(s)
using INHERITED = sksg::CustomRenderNode;
};

View File

@ -88,6 +88,7 @@ private:
uint32_t fNodeFlags : 8; // Accessible from select subclasses.
// Free bits : 18;
friend class NodePriv;
friend class RenderNode; // node flags access
typedef SkRefCnt INHERITED;

View File

@ -134,6 +134,8 @@ protected:
const std::vector<sk_sp<RenderNode>>& children() const { return fChildren; }
bool hasChildrenInval() const;
private:
std::vector<sk_sp<RenderNode>> fChildren;

View File

@ -20,6 +20,7 @@ skia_sksg_sources = [
"$_src/SkSGMaskEffect.cpp",
"$_src/SkSGMerge.cpp",
"$_src/SkSGNode.cpp",
"$_src/SkSGNodePriv.h",
"$_src/SkSGOpacityEffect.cpp",
"$_src/SkSGPaint.cpp",
"$_src/SkSGPath.cpp",
@ -31,5 +32,6 @@ skia_sksg_sources = [
"$_src/SkSGScene.cpp",
"$_src/SkSGText.cpp",
"$_src/SkSGTransform.cpp",
"$_src/SkSGTransformPriv.h",
"$_src/SkSGTrimEffect.cpp",
]

View File

@ -0,0 +1,27 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkSGNodePriv_DEFINED
#define SkSGNodePriv_DEFINED
#include "modules/sksg/include/SkSGNode.h"
namespace sksg {
// Helper for accessing implementation-private Node methods.
class NodePriv final {
public:
static bool HasInval(const sk_sp<Node>& node) { return node->hasInval(); }
private:
NodePriv() = delete;
};
} // namespace sksg
#endif // SkSGNodePriv_DEFINED

View File

@ -10,6 +10,7 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkPaint.h"
#include "modules/sksg/src/SkSGNodePriv.h"
namespace sksg {
@ -200,4 +201,14 @@ CustomRenderNode::~CustomRenderNode() {
}
}
bool CustomRenderNode::hasChildrenInval() const {
for (const auto& child : fChildren) {
if (NodePriv::HasInval(child)) {
return true;
}
}
return false;
}
} // namespace sksg

View File

@ -670,7 +670,10 @@ def sksg_lib_hdrs():
return native.glob(["modules/sksg/include/*.h"])
def sksg_lib_srcs():
return native.glob(["modules/sksg/src/*.cpp"])
return native.glob([
"modules/sksg/src/*.cpp",
"modules/sksg/src/*.h",
])
################################################################################
## skparagraph_lib