Coverage Report

Created: 2021-08-22 09:07

/src/skia/modules/skottie/src/layers/shapelayer/ShapeLayer.cpp
Line
Count
Source (jump to first uncovered line)
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
8
#include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
9
10
#include "include/core/SkPath.h"
11
#include "modules/skottie/src/SkottieJson.h"
12
#include "modules/skottie/src/SkottiePriv.h"
13
#include "modules/skottie/src/SkottieValue.h"
14
#include "modules/sksg/include/SkSGDraw.h"
15
#include "modules/sksg/include/SkSGGeometryEffect.h"
16
#include "modules/sksg/include/SkSGGroup.h"
17
#include "modules/sksg/include/SkSGMerge.h"
18
#include "modules/sksg/include/SkSGPaint.h"
19
#include "modules/sksg/include/SkSGPath.h"
20
#include "modules/sksg/include/SkSGRect.h"
21
#include "modules/sksg/include/SkSGRenderEffect.h"
22
#include "modules/sksg/include/SkSGTransform.h"
23
#include "src/utils/SkJSON.h"
24
25
#include <algorithm>
26
#include <iterator>
27
28
namespace skottie {
29
namespace internal {
30
31
namespace {
32
33
using GeometryAttacherT = sk_sp<sksg::GeometryNode> (*)(const skjson::ObjectValue&,
34
                                                        const AnimationBuilder*);
35
static constexpr GeometryAttacherT gGeometryAttachers[] = {
36
    ShapeBuilder::AttachPathGeometry,
37
    ShapeBuilder::AttachRRectGeometry,
38
    ShapeBuilder::AttachEllipseGeometry,
39
    ShapeBuilder::AttachPolystarGeometry,
40
};
41
42
using GeometryEffectAttacherT =
43
    std::vector<sk_sp<sksg::GeometryNode>> (*)(const skjson::ObjectValue&,
44
                                               const AnimationBuilder*,
45
                                               std::vector<sk_sp<sksg::GeometryNode>>&&);
46
static constexpr GeometryEffectAttacherT gGeometryEffectAttachers[] = {
47
    ShapeBuilder::AttachMergeGeometryEffect,
48
    ShapeBuilder::AttachTrimGeometryEffect,
49
    ShapeBuilder::AttachRoundGeometryEffect,
50
    ShapeBuilder::AttachOffsetGeometryEffect,
51
    ShapeBuilder::AttachPuckerBloatGeometryEffect,
52
};
53
54
using PaintAttacherT = sk_sp<sksg::PaintNode> (*)(const skjson::ObjectValue&,
55
                                                  const AnimationBuilder*);
56
static constexpr PaintAttacherT gPaintAttachers[] = {
57
    ShapeBuilder::AttachColorFill,
58
    ShapeBuilder::AttachColorStroke,
59
    ShapeBuilder::AttachGradientFill,
60
    ShapeBuilder::AttachGradientStroke,
61
};
62
63
// Some paint types (looking at you dashed-stroke) mess with the local geometry.
64
static constexpr GeometryEffectAttacherT gPaintGeometryAdjusters[] = {
65
    nullptr,                             // color fill
66
    ShapeBuilder::AdjustStrokeGeometry,  // color stroke
67
    nullptr,                             // gradient fill
68
    ShapeBuilder::AdjustStrokeGeometry,  // gradient stroke
69
};
70
static_assert(SK_ARRAY_COUNT(gPaintGeometryAdjusters) == SK_ARRAY_COUNT(gPaintAttachers), "");
71
72
using DrawEffectAttacherT =
73
    std::vector<sk_sp<sksg::RenderNode>> (*)(const skjson::ObjectValue&,
74
                                             const AnimationBuilder*,
75
                                             std::vector<sk_sp<sksg::RenderNode>>&&);
76
77
static constexpr DrawEffectAttacherT gDrawEffectAttachers[] = {
78
    ShapeBuilder::AttachRepeaterDrawEffect,
79
};
80
81
enum class ShapeType {
82
    kGeometry,
83
    kGeometryEffect,
84
    kPaint,
85
    kGroup,
86
    kTransform,
87
    kDrawEffect,
88
};
89
90
enum ShapeFlags : uint16_t {
91
    kNone          = 0x00,
92
    kSuppressDraws = 0x01,
93
};
94
95
struct ShapeInfo {
96
    const char* fTypeString;
97
    ShapeType   fShapeType;
98
    uint16_t    fAttacherIndex; // index into respective attacher tables
99
    uint16_t    fFlags;
100
};
101
102
1.16M
const ShapeInfo* FindShapeInfo(const skjson::ObjectValue& jshape) {
103
1.16M
    static constexpr ShapeInfo gShapeInfo[] = {
104
1.16M
        { "el", ShapeType::kGeometry      , 2, kNone          }, // ellipse
105
1.16M
        { "fl", ShapeType::kPaint         , 0, kNone          }, // fill
106
1.16M
        { "gf", ShapeType::kPaint         , 2, kNone          }, // gfill
107
1.16M
        { "gr", ShapeType::kGroup         , 0, kNone          }, // group
108
1.16M
        { "gs", ShapeType::kPaint         , 3, kNone          }, // gstroke
109
1.16M
        { "mm", ShapeType::kGeometryEffect, 0, kSuppressDraws }, // merge
110
1.16M
        { "op", ShapeType::kGeometryEffect, 3, kNone          }, // offset
111
1.16M
        { "pb", ShapeType::kGeometryEffect, 4, kNone          }, // pucker/bloat
112
1.16M
        { "rc", ShapeType::kGeometry      , 1, kNone          }, // rrect
113
1.16M
        { "rd", ShapeType::kGeometryEffect, 2, kNone          }, // round
114
1.16M
        { "rp", ShapeType::kDrawEffect    , 0, kNone          }, // repeater
115
1.16M
        { "sh", ShapeType::kGeometry      , 0, kNone          }, // shape
116
1.16M
        { "sr", ShapeType::kGeometry      , 3, kNone          }, // polystar
117
1.16M
        { "st", ShapeType::kPaint         , 1, kNone          }, // stroke
118
1.16M
        { "tm", ShapeType::kGeometryEffect, 1, kNone          }, // trim
119
1.16M
        { "tr", ShapeType::kTransform     , 0, kNone          }, // transform
120
1.16M
    };
121
122
1.16M
    const skjson::StringValue* type = jshape["ty"];
123
1.16M
    if (!type) {
124
223k
        return nullptr;
125
223k
    }
126
127
941k
    const auto* info = bsearch(type->begin(),
128
941k
                               gShapeInfo,
129
941k
                               SK_ARRAY_COUNT(gShapeInfo),
130
941k
                               sizeof(ShapeInfo),
131
3.46M
                               [](const void* key, const void* info) {
132
3.46M
                                  return strcmp(static_cast<const char*>(key),
133
3.46M
                                                static_cast<const ShapeInfo*>(info)->fTypeString);
134
3.46M
                               });
135
136
941k
    return static_cast<const ShapeInfo*>(info);
137
941k
}
138
139
struct GeometryEffectRec {
140
    const skjson::ObjectValue& fJson;
141
    GeometryEffectAttacherT    fAttach;
142
};
143
144
} // namespace
145
146
sk_sp<sksg::GeometryNode> ShapeBuilder::AttachPathGeometry(const skjson::ObjectValue& jpath,
147
71.4k
                                                           const AnimationBuilder* abuilder) {
148
71.4k
    return abuilder->attachPath(jpath["ks"]);
149
71.4k
}
150
151
struct AnimationBuilder::AttachShapeContext {
152
    AttachShapeContext(std::vector<sk_sp<sksg::GeometryNode>>* geos,
153
                       std::vector<GeometryEffectRec>* effects,
154
                       size_t committedAnimators)
155
        : fGeometryStack(geos)
156
        , fGeometryEffectStack(effects)
157
214k
        , fCommittedAnimators(committedAnimators) {}
158
159
    std::vector<sk_sp<sksg::GeometryNode>>* fGeometryStack;
160
    std::vector<GeometryEffectRec>*         fGeometryEffectStack;
161
    size_t                                  fCommittedAnimators;
162
};
163
164
sk_sp<sksg::RenderNode> AnimationBuilder::attachShape(const skjson::ArrayValue* jshape,
165
                                                      AttachShapeContext* ctx,
166
214k
                                                      bool suppress_draws) const {
167
214k
    if (!jshape)
168
16.8k
        return nullptr;
169
170
197k
    SkDEBUGCODE(const auto initialGeometryEffects = ctx->fGeometryEffectStack->size();)
171
172
197k
    const skjson::ObjectValue* jtransform = nullptr;
173
174
197k
    struct ShapeRec {
175
197k
        const skjson::ObjectValue& fJson;
176
197k
        const ShapeInfo&           fInfo;
177
197k
        bool                       fSuppressed;
178
197k
    };
179
180
    // First pass (bottom->top):
181
    //
182
    //   * pick up the group transform and opacity
183
    //   * push local geometry effects onto the stack
184
    //   * store recs for next pass
185
    //
186
197k
    std::vector<ShapeRec> recs;
187
1.36M
    for (size_t i = 0; i < jshape->size(); ++i) {
188
1.16M
        const skjson::ObjectValue* shape = (*jshape)[jshape->size() - 1 - i];
189
1.16M
        if (!shape) continue;
190
191
1.16M
        const auto* info = FindShapeInfo(*shape);
192
1.16M
        if (!info) {
193
291k
            this->log(Logger::Level::kError, &(*shape)["ty"], "Unknown shape.");
194
291k
            continue;
195
291k
        }
196
197
873k
        if (ParseDefault<bool>((*shape)["hd"], false)) {
198
            // Ignore hidden shapes.
199
61
            continue;
200
61
        }
201
202
873k
        recs.push_back({ *shape, *info, suppress_draws });
203
204
        // Some effects (merge) suppress any paints above them.
205
873k
        suppress_draws |= (info->fFlags & kSuppressDraws) != 0;
206
207
873k
        switch (info->fShapeType) {
208
28.2k
        case ShapeType::kTransform:
209
            // Just track the transform property for now -- we'll deal with it later.
210
28.2k
            jtransform = shape;
211
28.2k
            break;
212
331k
        case ShapeType::kGeometryEffect:
213
331k
            SkASSERT(info->fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
214
331k
            ctx->fGeometryEffectStack->push_back(
215
331k
                { *shape, gGeometryEffectAttachers[info->fAttacherIndex] });
216
331k
            break;
217
514k
        default:
218
514k
            break;
219
873k
        }
220
873k
    }
221
222
    // Second pass (top -> bottom, after 2x reverse):
223
    //
224
    //   * track local geometry
225
    //   * emit local paints
226
    //
227
197k
    std::vector<sk_sp<sksg::GeometryNode>> geos;
228
197k
    std::vector<sk_sp<sksg::RenderNode  >> draws;
229
230
154k
    const auto add_draw = [this, &draws](sk_sp<sksg::RenderNode> draw, const ShapeRec& rec) {
231
        // All draws can have an optional blend mode.
232
154k
        draws.push_back(this->attachBlendMode(rec.fJson, std::move(draw)));
233
154k
    };
234
235
1.07M
    for (auto rec = recs.rbegin(); rec != recs.rend(); ++rec) {
236
873k
        const AutoPropertyTracker apt(this, rec->fJson, PropertyObserver::NodeType::OTHER);
237
238
873k
        switch (rec->fInfo.fShapeType) {
239
243k
        case ShapeType::kGeometry: {
240
243k
            SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryAttachers));
241
243k
            if (auto geo = gGeometryAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this)) {
242
242k
                geos.push_back(std::move(geo));
243
242k
            }
244
243k
        } break;
245
331k
        case ShapeType::kGeometryEffect: {
246
            // Apply the current effect and pop from the stack.
247
331k
            SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gGeometryEffectAttachers));
248
331k
            if (!geos.empty()) {
249
324k
                geos = gGeometryEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
250
324k
                                                                           this,
251
324k
                                                                           std::move(geos));
252
324k
            }
253
254
331k
            SkASSERT(&ctx->fGeometryEffectStack->back().fJson == &rec->fJson);
255
331k
            SkASSERT(ctx->fGeometryEffectStack->back().fAttach ==
256
331k
                     gGeometryEffectAttachers[rec->fInfo.fAttacherIndex]);
257
331k
            ctx->fGeometryEffectStack->pop_back();
258
331k
        } break;
259
138k
        case ShapeType::kGroup: {
260
138k
            AttachShapeContext groupShapeCtx(&geos,
261
138k
                                             ctx->fGeometryEffectStack,
262
138k
                                             ctx->fCommittedAnimators);
263
138k
            if (auto subgroup =
264
60.5k
                this->attachShape(rec->fJson["it"], &groupShapeCtx, rec->fSuppressed)) {
265
60.5k
                add_draw(std::move(subgroup), *rec);
266
60.5k
                SkASSERT(groupShapeCtx.fCommittedAnimators >= ctx->fCommittedAnimators);
267
60.5k
                ctx->fCommittedAnimators = groupShapeCtx.fCommittedAnimators;
268
60.5k
            }
269
138k
        } break;
270
131k
        case ShapeType::kPaint: {
271
131k
            SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintAttachers));
272
131k
            auto paint = gPaintAttachers[rec->fInfo.fAttacherIndex](rec->fJson, this);
273
131k
            if (!paint || geos.empty() || rec->fSuppressed)
274
37.7k
                break;
275
276
93.5k
            auto drawGeos = geos;
277
278
            // Apply all pending effects from the stack.
279
93.5k
            for (auto it = ctx->fGeometryEffectStack->rbegin();
280
340k
                 it != ctx->fGeometryEffectStack->rend(); ++it) {
281
246k
                drawGeos = it->fAttach(it->fJson, this, std::move(drawGeos));
282
246k
            }
283
284
            // Apply local paint geometry adjustments (e.g. dashing).
285
93.5k
            SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gPaintGeometryAdjusters));
286
93.5k
            if (const auto adjuster = gPaintGeometryAdjusters[rec->fInfo.fAttacherIndex]) {
287
4.02k
                drawGeos = adjuster(rec->fJson, this, std::move(drawGeos));
288
4.02k
            }
289
290
            // If we still have multiple geos, reduce using 'merge'.
291
93.5k
            auto geo = drawGeos.size() > 1
292
16.3k
                ? ShapeBuilder::MergeGeometry(std::move(drawGeos), sksg::Merge::Mode::kMerge)
293
77.2k
                : drawGeos[0];
294
295
93.5k
            SkASSERT(geo);
296
93.5k
            add_draw(sksg::Draw::Make(std::move(geo), std::move(paint)), *rec);
297
93.5k
            ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
298
93.5k
        } break;
299
1.24k
        case ShapeType::kDrawEffect: {
300
1.24k
            SkASSERT(rec->fInfo.fAttacherIndex < SK_ARRAY_COUNT(gDrawEffectAttachers));
301
1.24k
            if (!draws.empty()) {
302
537
                draws = gDrawEffectAttachers[rec->fInfo.fAttacherIndex](rec->fJson,
303
537
                                                                        this,
304
537
                                                                        std::move(draws));
305
537
                ctx->fCommittedAnimators = fCurrentAnimatorScope->size();
306
537
            }
307
1.24k
        } break;
308
28.2k
        default:
309
28.2k
            break;
310
873k
        }
311
873k
    }
312
313
    // By now we should have popped all local geometry effects.
314
197k
    SkASSERT(ctx->fGeometryEffectStack->size() == initialGeometryEffects);
315
316
197k
    sk_sp<sksg::RenderNode> shape_wrapper;
317
197k
    if (draws.size() == 1) {
318
        // For a single draw, we don't need a group.
319
108k
        shape_wrapper = std::move(draws.front());
320
89.3k
    } else if (!draws.empty()) {
321
        // Emit local draws reversed (bottom->top, per spec).
322
11.8k
        std::reverse(draws.begin(), draws.end());
323
11.8k
        draws.shrink_to_fit();
324
325
        // We need a group to dispatch multiple draws.
326
11.8k
        shape_wrapper = sksg::Group::Make(std::move(draws));
327
11.8k
    }
328
329
197k
    sk_sp<sksg::Transform> shape_transform;
330
197k
    if (jtransform) {
331
20.9k
        const AutoPropertyTracker apt(this, *jtransform, PropertyObserver::NodeType::OTHER);
332
333
        // This is tricky due to the interaction with ctx->fCommittedAnimators: we want any
334
        // animators related to tranform/opacity to be committed => they must be inserted in front
335
        // of the dangling/uncommitted ones.
336
20.9k
        AutoScope ascope(this);
337
338
20.9k
        if ((shape_transform = this->attachMatrix2D(*jtransform, nullptr))) {
339
14.0k
            shape_wrapper = sksg::TransformEffect::Make(std::move(shape_wrapper), shape_transform);
340
14.0k
        }
341
20.9k
        shape_wrapper = this->attachOpacity(*jtransform, std::move(shape_wrapper));
342
343
20.9k
        auto local_scope = ascope.release();
344
20.9k
        fCurrentAnimatorScope->insert(fCurrentAnimatorScope->begin() + ctx->fCommittedAnimators,
345
20.9k
                                      std::make_move_iterator(local_scope.begin()),
346
20.9k
                                      std::make_move_iterator(local_scope.end()));
347
20.9k
        ctx->fCommittedAnimators += local_scope.size();
348
20.9k
    }
349
350
    // Push transformed local geometries to parent list, for subsequent paints.
351
229k
    for (auto& geo : geos) {
352
229k
        ctx->fGeometryStack->push_back(shape_transform
353
18.8k
            ? sksg::GeometryTransform::Make(std::move(geo), shape_transform)
354
211k
            : std::move(geo));
355
229k
    }
356
357
197k
    return shape_wrapper;
358
197k
}
359
360
sk_sp<sksg::RenderNode> AnimationBuilder::attachShapeLayer(const skjson::ObjectValue& layer,
361
75.7k
                                                           LayerInfo*) const {
362
75.7k
    std::vector<sk_sp<sksg::GeometryNode>> geometryStack;
363
75.7k
    std::vector<GeometryEffectRec> geometryEffectStack;
364
75.7k
    AttachShapeContext shapeCtx(&geometryStack, &geometryEffectStack,
365
75.7k
                                fCurrentAnimatorScope->size());
366
75.7k
    auto shapeNode = this->attachShape(layer["shapes"], &shapeCtx);
367
368
    // Trim uncommitted animators: AttachShape consumes effects on the fly, and greedily attaches
369
    // geometries => at the end, we can end up with unused geometries, which are nevertheless alive
370
    // due to attached animators.  To avoid this, we track committed animators and discard the
371
    // orphans here.
372
75.7k
    SkASSERT(shapeCtx.fCommittedAnimators <= fCurrentAnimatorScope->size());
373
75.7k
    fCurrentAnimatorScope->resize(shapeCtx.fCommittedAnimators);
374
375
75.7k
    return shapeNode;
376
75.7k
}
377
378
} // namespace internal
379
} // namespace skottie