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