Flutter Engine
The Flutter Engine
ShapeLayer.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2018 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9
23#include "modules/sksg/include/SkSGPath.h" // IWYU pragma: keep
26#include "src/utils/SkJSON.h"
27
28#include <string.h>
29#include <algorithm>
30#include <cstdint>
31#include <cstdlib>
32#include <iterator>
33#include <utility>
34
35namespace skottie {
36namespace internal {
37
38namespace {
39
40using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
41 const AnimationBuilder*);
42static constexpr GeometryAttacherT gGeometryAttachers[] = {
47};
48
49using GeometryEffectAttacherT =
50 std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
51 const AnimationBuilder*,
52 std::vector<sk_sp<sksg::GeometryNode>>&&);
53static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
59};
60
61using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
62 const AnimationBuilder*);
63static constexpr PaintAttacherT gPaintAttachers[] = {
68};
69
70// Some paint types (looking at you dashed-stroke) mess with the local geometry.
71static constexpr GeometryEffectAttacherT gPaintGeometryAdjusters[] = {
72 nullptr, // color fill
74 nullptr, // gradient fill
75 ShapeBuilder::AdjustStrokeGeometry, // gradient stroke
76};
77static_assert(std::size(gPaintGeometryAdjusters) == std::size(gPaintAttachers), "");
78
79using DrawEffectAttacherT =
80 std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
81 const AnimationBuilder*,
82 std::vector<sk_sp<sksg::RenderNode>>&&);
83
84static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
86};
87
88enum class ShapeType {
89 kGeometry,
90 kGeometryEffect,
91 kPaint,
92 kGroup,
94 kDrawEffect,
95};
96
97enum ShapeFlags : uint16_t {
98 kNone = 0x00,
99 kSuppressDraws = 0x01,
100};
101
102struct ShapeInfo {
103 const char* fTypeString;
104 ShapeType fShapeType;
105 uint16_t fAttacherIndex; // index into respective attacher tables
106 uint16_t fFlags;
107};
108
109const ShapeInfo* FindShapeInfo(const skjson::ObjectValue& jshape) {
110 static constexpr ShapeInfo gShapeInfo[] = {
111 { "el", ShapeType::kGeometry , 2, kNone }, // ellipse
112 { "fl", ShapeType::kPaint , 0, kNone }, // fill
113 { "gf", ShapeType::kPaint , 2, kNone }, // gfill
114 { "gr", ShapeType::kGroup , 0, kNone }, // group
115 { "gs", ShapeType::kPaint , 3, kNone }, // gstroke
116 { "mm", ShapeType::kGeometryEffect, 0, kSuppressDraws }, // merge
117 { "op", ShapeType::kGeometryEffect, 3, kNone }, // offset
118 { "pb", ShapeType::kGeometryEffect, 4, kNone }, // pucker/bloat
119 { "rc", ShapeType::kGeometry , 1, kNone }, // rrect
120 { "rd", ShapeType::kGeometryEffect, 2, kNone }, // round
121 { "rp", ShapeType::kDrawEffect , 0, kNone }, // repeater
122 { "sh", ShapeType::kGeometry , 0, kNone }, // shape
123 { "sr", ShapeType::kGeometry , 3, kNone }, // polystar
124 { "st", ShapeType::kPaint , 1, kNone }, // stroke
125 { "tm", ShapeType::kGeometryEffect, 1, kNone }, // trim
126 { "tr", ShapeType::kTransform , 0, kNone }, // transform
127 };
128
129 const skjson::StringValue* type = jshape["ty"];
130 if (!type) {
131 return nullptr;
132 }
133
134 const auto* info = bsearch(type->begin(),
135 gShapeInfo,
136 std::size(gShapeInfo),
137 sizeof(ShapeInfo),
138 [](const void* key, const void* info) {
139 return strcmp(static_cast<const char*>(key),
140 static_cast<const ShapeInfo*>(info)->fTypeString);
141 });
142
143 return static_cast<const ShapeInfo*>(info);
144}
145
146struct GeometryEffectRec {
148 GeometryEffectAttacherT fAttach;
149};
150
151} // namespace
152
154 const AnimationBuilder* abuilder) {
155 return abuilder->attachPath(jpath["ks"]);
156}
157
160 std::vector<GeometryEffectRec>* effects,
161 size_t committedAnimators)
162 : fGeometryStack(geos)
163 , fGeometryEffectStack(effects)
164 , fCommittedAnimators(committedAnimators) {}
165
166 std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
167 std::vector<GeometryEffectRec>* fGeometryEffectStack;
169};
170
171sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
173 bool suppress_draws) const {
174 if (!jshape)
175 return nullptr;
176
177 SkDEBUGCODE(const auto initialGeometryEffects = ctx->fGeometryEffectStack->size();)
178
179 const skjson::ObjectValue* jtransform = nullptr;
180
181 struct ShapeRec {
183 const ShapeInfo& fInfo;
184 bool fSuppressed;
185 };
186
187 // First pass (bottom->top):
188 //
189 // * pick up the group transform and opacity
190 // * push local geometry effects onto the stack
191 // * store recs for next pass
192 //
193 std::vector<ShapeRec> recs;
194 for (size_t i = 0; i < jshape->size(); ++i) {
195 const skjson::ObjectValue* shape = (*jshape)[jshape->size() - 1 - i];
196 if (!shape) continue;
197
198 const auto* info = FindShapeInfo(*shape);
199 if (!info) {
200 this->log(Logger::Level::kError, &(*shape)["ty"], "Unknown shape.");
201 continue;
202 }
203
204 if (ParseDefault<bool>((*shape)["hd"], false)) {
205 // Ignore hidden shapes.
206 continue;
207 }
208
209 recs.push_back({ *shape, *info, suppress_draws });
210
211 // Some effects (merge) suppress any paints above them.
212 suppress_draws |= (info->fFlags & kSuppressDraws) != 0;
213
214 switch (info->fShapeType) {
215 case ShapeType::kTransform:
216 // Just track the transform property for now -- we'll deal with it later.
217 jtransform = shape;
218 break;
219 case ShapeType::kGeometryEffect:
220 SkASSERT(info->fAttacherIndex < std::size(gGeometryEffectAttachers));
221 ctx->fGeometryEffectStack->push_back(
222 { *shape, gGeometryEffectAttachers[info->fAttacherIndex] });
223 break;
224 default:
225 break;
226 }
227 }
228
229 // Second pass (top -> bottom, after 2x reverse):
230 //
231 // * track local geometry
232 // * emit local paints
233 //
234 std::vector<sk_sp<sksg::GeometryNode>> geos;
235 std::vector<sk_sp<sksg::RenderNode >> draws;
236
237 const auto add_draw = [this, &draws](sk_sp<sksg::RenderNode> draw, const ShapeRec& rec) {
238 // All draws can have an optional blend mode.
239 draws.push_back(this->attachBlendMode(rec.fJson, std::move(draw)));
240 };
241
242 for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
243 const AutoPropertyTracker apt(this, rec->fJson, PropertyObserver::NodeType::OTHER);
244
245 switch (rec->fInfo.fShapeType) {
246 case ShapeType::kGeometry: {
247 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gGeometryAttachers));
248 if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this)) {
249 geos.push_back(std::move(geo));
250 }
251 } break;
252 case ShapeType::kGeometryEffect: {
253 // Apply the current effect and pop from the stack.
254 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gGeometryEffectAttachers));
255 if (!geos.empty()) {
256 geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
257 this,
258 std::move(geos));
259 }
260
261 SkASSERT(&ctx->fGeometryEffectStack->back().fJson == &rec->fJson);
262 SkASSERT(ctx->fGeometryEffectStack->back().fAttach ==
263 gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
264 ctx->fGeometryEffectStack->pop_back();
265 } break;
266 case ShapeType::kGroup: {
267 AttachShapeContext groupShapeCtx(&geos,
270 if (auto subgroup =
271 this->attachShape(rec->fJson["it"], &groupShapeCtx, rec->fSuppressed)) {
272 add_draw(std::move(subgroup), *rec);
273 SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
274 ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
275 }
276 } break;
277 case ShapeType::kPaint: {
278 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gPaintAttachers));
279 auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this);
280 if (!paint || geos.empty() || rec->fSuppressed)
281 break;
282
283 auto drawGeos = geos;
284
285 // Apply all pending effects from the stack.
286 for (auto it = ctx->fGeometryEffectStack->rbegin();
287 it != ctx->fGeometryEffectStack->rend(); ++it) {
288 drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
289 }
290
291 // Apply local paint geometry adjustments (e.g. dashing).
292 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gPaintGeometryAdjusters));
293 if (const auto adjuster = gPaintGeometryAdjusters[rec->fInfo.fAttacherIndex]) {
294 drawGeos = adjuster(rec->fJson, this, std::move(drawGeos));
295 }
296
297 // If we still have multiple geos, reduce using 'merge'.
298 auto geo = drawGeos.size() > 1
300 : drawGeos[0];
301
302 SkASSERT(geo);
303 add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
304 ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
305 } break;
306 case ShapeType::kDrawEffect: {
307 SkASSERT(rec->fInfo.fAttacherIndex < std::size(gDrawEffectAttachers));
308 if (!draws.empty()) {
309 draws = gDrawEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
310 this,
311 std::move(draws));
312 ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
313 }
314 } break;
315 default:
316 break;
317 }
318 }
319
320 // By now we should have popped all local geometry effects.
321 SkASSERT(ctx->fGeometryEffectStack->size() == initialGeometryEffects);
322
323 sk_sp<sksg::RenderNode> shape_wrapper;
324 if (draws.size() == 1) {
325 // For a single draw, we don't need a group.
326 shape_wrapper = std::move(draws.front());
327 } else if (!draws.empty()) {
328 // Emit local draws reversed (bottom->top, per spec).
329 std::reverse(draws.begin(), draws.end());
330 draws.shrink_to_fit();
331
332 // We need a group to dispatch multiple draws.
333 shape_wrapper = sksg::Group::Make(std::move(draws));
334 }
335
336 sk_sp<sksg::Transform> shape_transform;
337 if (jtransform) {
338 const AutoPropertyTracker apt(this, *jtransform, PropertyObserver::NodeType::OTHER);
339
340 // This is tricky due to the interaction with ctx->fCommittedAnimators: we want any
341 // animators related to tranform/opacity to be committed => they must be inserted in front
342 // of the dangling/uncommitted ones.
343 AutoScope ascope(this);
344
345 if ((shape_transform = this->attachMatrix2D(*jtransform, nullptr))) {
346 shape_wrapper = sksg::TransformEffect::Make(std::move(shape_wrapper), shape_transform);
347 }
348 shape_wrapper = this->attachOpacity(*jtransform, std::move(shape_wrapper));
349
350 auto local_scope = ascope.release();
351 fCurrentAnimatorScope->insert(fCurrentAnimatorScope->begin() + ctx->fCommittedAnimators,
352 std::make_move_iterator(local_scope.begin()),
353 std::make_move_iterator(local_scope.end()));
354 ctx->fCommittedAnimators += local_scope.size();
355 }
356
357 // Push transformed local geometries to parent list, for subsequent paints.
358 for (auto& geo : geos) {
359 ctx->fGeometryStack->push_back(shape_transform
360 ? sksg::GeometryTransform::Make(std::move(geo), shape_transform)
361 : std::move(geo));
362 }
363
364 return shape_wrapper;
365}
366
367sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
368 LayerInfo*) const {
369 std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
370 std::vector<GeometryEffectRec> geometryEffectStack;
371 AttachShapeContext shapeCtx(&geometryStack, &geometryEffectStack,
372 fCurrentAnimatorScope->size());
373 auto shapeNode = this->attachShape(layer["shapes"], &shapeCtx);
374
375 // Trim uncommitted animators: AttachShape consumes effects on the fly, and greedily attaches
376 // geometries => at the end, we can end up with unused geometries, which are nevertheless alive
377 // due to attached animators. To avoid this, we track committed animators and discard the
378 // orphans here.
379 SkASSERT(shapeCtx.fCommittedAnimators <= fCurrentAnimatorScope->size());
380 fCurrentAnimatorScope->resize(shapeCtx.fCommittedAnimators);
381
382 return shapeNode;
383}
384
385} // namespace internal
386} // namespace skottie
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
ShapeType fShapeType
Definition: ShapeLayer.cpp:104
uint16_t fAttacherIndex
Definition: ShapeLayer.cpp:105
const skjson::ObjectValue & fJson
Definition: ShapeLayer.cpp:147
GeometryEffectAttacherT fAttach
Definition: ShapeLayer.cpp:148
const char * fTypeString
Definition: ShapeLayer.cpp:103
uint16_t fFlags
Definition: ShapeLayer.cpp:106
#define SkASSERT(cond)
Definition: SkAssert.h:116
SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID()
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition: aaclip.cpp:27
GLenum type
size_t size() const
Definition: SkJSON.h:262
sk_sp< sksg::Path > attachPath(const skjson::Value &) const
Definition: Path.cpp:50
void sk_sp< sksg::Transform > attachMatrix2D(const skjson::ObjectValue &, sk_sp< sksg::Transform >, bool auto_orient=false) const
Definition: Transform.cpp:114
sk_sp< sksg::RenderNode > attachOpacity(const skjson::ObjectValue &, sk_sp< sksg::RenderNode >) const
Definition: Skottie.cpp:118
void log(Logger::Level, const skjson::Value *, const char fmt[],...) const SK_PRINTF_LIKE(4
Definition: Skottie.cpp:71
static sk_sp< sksg::GeometryNode > AttachPathGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: ShapeLayer.cpp:153
static sk_sp< sksg::GeometryNode > AttachPolystarGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: Polystar.cpp:101
static std::vector< sk_sp< sksg::RenderNode > > AttachRepeaterDrawEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::RenderNode > > &&)
Definition: Repeater.cpp:184
static std::vector< sk_sp< sksg::GeometryNode > > AttachOffsetGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
Definition: OffsetPaths.cpp:61
static sk_sp< sksg::PaintNode > AttachColorStroke(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: FillStroke.cpp:173
static sk_sp< sksg::PaintNode > AttachColorFill(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: FillStroke.cpp:165
static std::vector< sk_sp< sksg::GeometryNode > > AdjustStrokeGeometry(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
Definition: FillStroke.cpp:181
static std::vector< sk_sp< sksg::GeometryNode > > AttachRoundGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
static sk_sp< sksg::PaintNode > AttachGradientStroke(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: Gradient.cpp:259
static sk_sp< sksg::GeometryNode > AttachEllipseGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: Ellipse.cpp:57
static std::vector< sk_sp< sksg::GeometryNode > > AttachMergeGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
Definition: MergePaths.cpp:37
static sk_sp< sksg::GeometryNode > AttachRRectGeometry(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: Rectangle.cpp:59
static sk_sp< sksg::PaintNode > AttachGradientFill(const skjson::ObjectValue &, const AnimationBuilder *)
Definition: Gradient.cpp:250
static sk_sp< sksg::Merge > MergeGeometry(std::vector< sk_sp< sksg::GeometryNode > > &&, sksg::Merge::Mode)
Definition: MergePaths.cpp:24
static std::vector< sk_sp< sksg::GeometryNode > > AttachPuckerBloatGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
static std::vector< sk_sp< sksg::GeometryNode > > AttachTrimGeometryEffect(const skjson::ObjectValue &, const AnimationBuilder *, std::vector< sk_sp< sksg::GeometryNode > > &&)
Definition: TrimPaths.cpp:81
static sk_sp< Draw > Make(sk_sp< GeometryNode > geo, sk_sp< PaintNode > paint)
Definition: SkSGDraw.h:35
static sk_sp< GeometryTransform > Make(sk_sp< GeometryNode > child, sk_sp< Transform > transform)
static sk_sp< Group > Make()
Definition: SkSGGroup.h:31
static sk_sp< TransformEffect > Make(sk_sp< RenderNode > child, sk_sp< Transform > transform)
Definition: SkSGTransform.h:97
const Paint & paint
Definition: color_source.cc:38
@ kNone
Definition: layer.h:53
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
std::vector< sk_sp< sksg::GeometryNode > > * fGeometryStack
Definition: ShapeLayer.cpp:166
AttachShapeContext(std::vector< sk_sp< sksg::GeometryNode > > *geos, std::vector< GeometryEffectRec > *effects, size_t committedAnimators)
Definition: ShapeLayer.cpp:159
std::vector< GeometryEffectRec > * fGeometryEffectStack
Definition: ShapeLayer.cpp:167