• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 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/effects/Effects.h"
9 
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkPictureRecorder.h"
12 #include "include/effects/SkRuntimeEffect.h"
13 #include "modules/skottie/src/Adapter.h"
14 #include "modules/skottie/src/SkottieValue.h"
15 #include "modules/sksg/include/SkSGPaint.h"
16 #include "modules/sksg/include/SkSGRenderNode.h"
17 #include "src/utils/SkJSON.h"
18 
19 namespace skottie::internal {
20 
21 #ifdef SK_ENABLE_SKSL
22 
23 namespace {
24 
25 static constexpr char gBulgeDisplacementSkSL[] =
26     "uniform shader u_layer;"
27 
28     "uniform float2 u_center;"
29     "uniform float2 u_radius;"
30     "uniform float2 u_radius_inv;"
31     "uniform float u_h;"
32     "uniform float u_rcpR;"
33     "uniform float u_rcpAsinInvR;"
34     "uniform float u_selector;"
35 
36     // AE's bulge effect appears to be a combination of spherical displacement and
37     // exponential displacement along the radius.
38     // To simplify the math, we pre scale/translate such that the ellipse becomes a
39     // circle with radius == 1, centered on origin.
40     "float2 displace_sph(float2 v) {"
41         "float arc_ratio = asin(length(v)*u_rcpR)*u_rcpAsinInvR;"
42         "return normalize(v)*arc_ratio - v;"
43     "}"
44 
45     "float2 displace_exp(float2 v) {"
46         "return v*pow(dot(v,v),u_h) - v;"
47     "}"
48 
49     "half2 displace(float2 v) {"
50         "float t = dot(v, v);"
51         "if (t >= 1) {"
52             "return v;"
53         "}"
54         "float2 d = displace_sph(v) + displace_exp(v);"
55         "return v + (d * u_selector);"
56     "}"
57 
58     "half4 main(float2 xy) {"
59         // This normalization used to be handled externally, via a local matrix, but that started
60         // clashing with picture shader's tile sizing logic.
61         // TODO: investigate other ways to manage picture-shader's tile allocation.
62         "xy = (xy - u_center)*u_radius_inv;"
63 
64         "xy = displace(xy);"
65         "xy = xy*u_radius + u_center;"
66         "return u_layer.eval(xy);"
67     "}";
68 
bulge_effect()69 static sk_sp<SkRuntimeEffect> bulge_effect() {
70     static const SkRuntimeEffect* effect =
71         SkRuntimeEffect::MakeForShader(SkString(gBulgeDisplacementSkSL), {}).effect.release();
72     SkASSERT(effect);
73 
74     return sk_ref_sp(effect);
75 }
76 
77 class BulgeNode final : public sksg::CustomRenderNode {
78 public:
BulgeNode(sk_sp<RenderNode> child,const SkSize & child_size)79     explicit BulgeNode(sk_sp<RenderNode> child, const SkSize& child_size)
80         : INHERITED({std::move(child)})
81         , fChildSize(child_size) {}
82 
83     SG_ATTRIBUTE(Center  , SkPoint   , fCenter)
84     SG_ATTRIBUTE(Radius  , SkVector  , fRadius)
85     SG_ATTRIBUTE(Height  , float     , fHeight)
86 
87 private:
contentShader()88     sk_sp<SkShader> contentShader() {
89         if (!fContentShader || this->hasChildrenInval()) {
90             const auto& child = this->children()[0];
91             child->revalidate(nullptr, SkMatrix::I());
92 
93             SkPictureRecorder recorder;
94             child->render(recorder.beginRecording(SkRect::MakeSize(fChildSize)));
95 
96             fContentShader = recorder.finishRecordingAsPicture()
97                     ->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkFilterMode::kLinear,
98                                  nullptr, nullptr);
99         }
100 
101         return fContentShader;
102     }
103 
buildEffectShader()104     sk_sp<SkShader> buildEffectShader() {
105         if (fHeight == 0) {
106             return nullptr;
107         }
108 
109         SkRuntimeShaderBuilder builder(bulge_effect());
110         float adjHeight = std::abs(fHeight)/4;
111         float r = (1 + adjHeight)/2/sqrt(adjHeight);
112         float h = std::pow(adjHeight, 3)*1.3;
113         builder.uniform("u_center")       = fCenter;
114         builder.uniform("u_radius")       = fRadius;
115         builder.uniform("u_radius_inv")   = SkVector{1/fRadius.fX, 1/fRadius.fY};
116         builder.uniform("u_h")            = h;
117         builder.uniform("u_rcpR")         = 1.0f/r;
118         builder.uniform("u_rcpAsinInvR")  = 1.0f/std::asin(1/r);
119         builder.uniform("u_selector")     = (fHeight > 0 ? 1.0f : -1.0f);
120 
121         builder.child("u_layer") = this->contentShader();
122 
123         return builder.makeShader();
124     }
125 
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)126     SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
127         const auto& child = this->children()[0];
128         fEffectShader = buildEffectShader();
129         return child->revalidate(ic, ctm);
130     }
131 
onRender(SkCanvas * canvas,const RenderContext * ctx) const132     void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
133         if (fHeight == 0) {
134             this->children()[0]->render(canvas, ctx);
135             return;
136         }
137         const auto& bounds = this->bounds();
138         const auto local_ctx = ScopedRenderContext(canvas, ctx)
139                 .setIsolation(bounds, canvas->getTotalMatrix(), true);
140 
141         canvas->saveLayer(&bounds, nullptr);
142 
143         SkPaint effect_paint;
144         effect_paint.setShader(fEffectShader);
145         effect_paint.setBlendMode(SkBlendMode::kSrcOver);
146 
147         canvas->drawPaint(effect_paint);
148     }
149 
onNodeAt(const SkPoint &) const150     const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
151 
152     sk_sp<SkShader> fEffectShader;
153     sk_sp<SkShader> fContentShader;
154     const SkSize fChildSize;
155 
156     SkPoint  fCenter = {0,0};
157     SkVector fRadius = {0,0};
158     float   fHeight = 0;
159 
160     using INHERITED = sksg::CustomRenderNode;
161 };
162 
163 class BulgeEffectAdapter final : public DiscardableAdapterBase<BulgeEffectAdapter,
164                                                              BulgeNode> {
165 public:
BulgeEffectAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder & abuilder,sk_sp<BulgeNode> node)166     BulgeEffectAdapter(const skjson::ArrayValue& jprops,
167                       const AnimationBuilder& abuilder,
168                       sk_sp<BulgeNode> node)
169         : INHERITED(std::move(node))
170     {
171         enum : size_t {
172             kHorizontalRadius_Index = 0,
173             kVerticalRadius_Index = 1,
174             kBulgeCenter_Index = 2,
175             kBulgeHeight_Index = 3,
176             // kTaper_Index = 4,
177             // kAA_Index = 5,
178             // kPinning_Index = 6,
179         };
180         EffectBinder(jprops, abuilder, this).bind(kHorizontalRadius_Index, fHorizontalRadius)
181                                             .bind(kVerticalRadius_Index, fVerticalRadius)
182                                             .bind(kBulgeCenter_Index, fCenter)
183                                             .bind(kBulgeHeight_Index, fBulgeHeight);
184     }
185 
186 private:
onSync()187     void onSync() override {
188         // pre-shader math
189         auto n = this->node();
190         n->setCenter({fCenter.x, fCenter.y});
191         n->setRadius({fHorizontalRadius, fVerticalRadius});
192         n->setHeight(fBulgeHeight);
193     }
194 
195     Vec2Value fCenter;
196     ScalarValue fHorizontalRadius,
197                 fVerticalRadius,
198                 fBulgeHeight;
199     using INHERITED = DiscardableAdapterBase<BulgeEffectAdapter, BulgeNode>;
200 };
201 
202 } // namespace
203 
204 #endif  // SK_ENABLE_SKSL
205 
attachBulgeEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const206 sk_sp<sksg::RenderNode> EffectBuilder::attachBulgeEffect(const skjson::ArrayValue& jprops,
207                                                              sk_sp<sksg::RenderNode> layer) const {
208 #ifdef SK_ENABLE_SKSL
209     auto shaderNode = sk_make_sp<BulgeNode>(std::move(layer), fLayerSize);
210     return fBuilder->attachDiscardableAdapter<BulgeEffectAdapter>(jprops, *fBuilder, std::move(shaderNode));
211 #else
212     return layer;
213 #endif
214 }
215 
216 } // namespace skottie::internal
217