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/effects/SkRuntimeEffect.h"
12 #include "include/private/base/SkMalloc.h"
13 #include "modules/skottie/src/Adapter.h"
14 #include "modules/skottie/src/SkottieJson.h"
15 #include "modules/skottie/src/SkottieValue.h"
16 #include "modules/sksg/include/SkSGColorFilter.h"
17 
18 namespace skottie::internal {
19 
20 #ifdef SK_ENABLE_SKSL
21 
22 namespace  {
23 class SkSLShaderNode final : public sksg::CustomRenderNode {
24 public:
SkSLShaderNode(sk_sp<RenderNode> child)25     explicit SkSLShaderNode(sk_sp<RenderNode> child) : INHERITED({std::move(child)}) {}
26 
27     SG_ATTRIBUTE(Shader, sk_sp<SkShader>, fEffectShader)
28 private:
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)29     SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
30         const auto& child = this->children()[0];
31         return child->revalidate(ic, ctm);
32     }
33 
onRender(SkCanvas * canvas,const RenderContext * ctx) const34     void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
35         const auto& bounds = this->bounds();
36         const auto local_ctx = ScopedRenderContext(canvas, ctx)
37                 .setIsolation(bounds, canvas->getTotalMatrix(), true);
38 
39         canvas->saveLayer(&bounds, nullptr);
40         this->children()[0]->render(canvas, local_ctx);
41 
42         SkPaint effect_paint;
43         effect_paint.setShader(fEffectShader);
44         effect_paint.setBlendMode(SkBlendMode::kSrcIn);
45 
46         canvas->drawPaint(effect_paint);
47     }
48 
onNodeAt(const SkPoint &) const49     const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
50 
51     sk_sp<SkShader> fEffectShader;
52 
53     using INHERITED = sksg::CustomRenderNode;
54 };
55 
56 class SkSLEffectBase {
57 public:
SkSLEffectBase(const skjson::ArrayValue & jprops,const AnimationBuilder & abuilder)58     SkSLEffectBase(const skjson::ArrayValue& jprops,
59                    const AnimationBuilder& abuilder)
60     {
61         if (jprops.size() < 1) {
62             return;
63         }
64         const skjson::ObjectValue* jSkSL = jprops[kSkSL_index];
65         if (!jSkSL) {
66             return;
67         }
68         const skjson::StringValue* jShader = (*jSkSL)["sh"];
69         if (!jShader) {
70             return;
71         }
72         SkString shader = SkString(jShader->begin(), jShader->size());
73         auto result = SkRuntimeEffect::MakeForShader(shader, {});
74         if (!result.effect) {
75             abuilder.log(Logger::Level::kError, nullptr, "Failed to parse SkSL shader: %s",
76                result.errorText.c_str());
77             return;
78         }
79         fEffect = std::move(result.effect);
80     }
81 protected:
82     enum : size_t {
83         kSkSL_index = 0,
84         kFirstUniform_index = 1,
85     };
86 
bindUniforms(const skjson::ArrayValue & jprops,const AnimationBuilder & abuilder,AnimatablePropertyContainer * const & container)87     void bindUniforms(const skjson::ArrayValue& jprops,
88                       const AnimationBuilder& abuilder,
89                       AnimatablePropertyContainer * const &container) {
90         // construct dynamic uniform list from jprops, skip SkSL property
91         for (size_t i = kFirstUniform_index; i < jprops.size(); i++) {
92             const skjson::ObjectValue* jprop = jprops[i];
93             if (!jprop) { continue; }
94             const skjson::StringValue* uniformName = (*jprop)["nm"];
95             if (!uniformName) { continue; }
96             auto uniformTuple = std::make_tuple(SkString(uniformName->begin(),
97                                                          uniformName->size()),
98                                                 std::make_unique<VectorValue>());
99             fUniforms.push_back(std::move(uniformTuple));
100             container->bind(abuilder, (*jprop)["v"], std::get<1>(fUniforms.back()).get());
101         }
102     }
103 
buildUniformData() const104     sk_sp<SkData> buildUniformData() const {
105         auto uniformData = SkData::MakeUninitialized(fEffect->uniformSize());
106         SkASSERT(uniformData);
107         sk_bzero(uniformData->writable_data(), uniformData->size());
108         for (const auto& uniform : fUniforms) {
109             const auto& name = std::get<0>(uniform);
110             const auto& data = std::get<1>(uniform);
111             auto metadata = fEffect->findUniform(name.c_str());
112             if (metadata && metadata->count == static_cast<int>(data->size())) {
113                 auto dst = reinterpret_cast<uint8_t*>(uniformData->writable_data()) + metadata->offset;
114                 memcpy(reinterpret_cast<void*>(dst), data->data(), data->size() * sizeof(float));
115             } else {
116                 SkDebugf("cannot set malformed uniform: %s", name.c_str());
117             }
118         }
119         return uniformData;
120     }
121     sk_sp<SkRuntimeEffect> fEffect;
122     std::vector<std::tuple<SkString, std::unique_ptr<VectorValue>>> fUniforms;
123 };
124 
125 class SkSLShaderAdapter final : public DiscardableAdapterBase<SkSLShaderAdapter,
126                                                               SkSLShaderNode>,
127                                 public SkSLEffectBase {
128 public:
SkSLShaderAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder & abuilder,sk_sp<SkSLShaderNode> node)129     SkSLShaderAdapter(const skjson::ArrayValue& jprops,
130                       const AnimationBuilder& abuilder,
131                       sk_sp<SkSLShaderNode> node)
132         : DiscardableAdapterBase<SkSLShaderAdapter, SkSLShaderNode>(std::move(node))
133         , SkSLEffectBase(jprops, abuilder)
134     {
135         this->bindUniforms(jprops, abuilder, this);
136     }
137 
138 private:
onSync()139     void onSync() override {
140         if (!fEffect) {
141             return;
142         }
143         sk_sp<SkShader> shader =
144                 fEffect->makeShader(buildUniformData(), {/* TODO: child support */});
145         this->node()->setShader(std::move(shader));
146     }
147 };
148 
149 class SkSLColorFilterAdapter final : public DiscardableAdapterBase<SkSLColorFilterAdapter,
150                                                              sksg::ExternalColorFilter>
151                                    , public SkSLEffectBase {
152 public:
SkSLColorFilterAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder & abuilder,sk_sp<sksg::ExternalColorFilter> node)153     SkSLColorFilterAdapter(const skjson::ArrayValue& jprops,
154                       const AnimationBuilder& abuilder,
155                       sk_sp<sksg::ExternalColorFilter> node)
156         : DiscardableAdapterBase<SkSLColorFilterAdapter, sksg::ExternalColorFilter>(std::move(node))
157         , SkSLEffectBase(jprops, abuilder)
158     {
159         this->bindUniforms(jprops, abuilder, this);
160     }
161 
162 private:
onSync()163     void onSync() override {
164         if (!fEffect) {
165             return;
166         }
167         auto cf = fEffect->makeColorFilter(buildUniformData());
168         this->node()->setColorFilter(std::move(cf));
169     }
170 };
171 
172 } // namespace
173 
174 #endif  // SK_ENABLE_SKSL
175 
attachSkSLShader(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const176 sk_sp<sksg::RenderNode> EffectBuilder::attachSkSLShader(const skjson::ArrayValue& jprops,
177                                                         sk_sp<sksg::RenderNode> layer) const {
178 #ifdef SK_ENABLE_SKSL
179     auto shaderNode = sk_make_sp<SkSLShaderNode>(std::move(layer));
180     return fBuilder->attachDiscardableAdapter<SkSLShaderAdapter>(jprops, *fBuilder,
181                                                                  std::move(shaderNode));
182 #else
183     return layer;
184 #endif
185 }
186 
attachSkSLColorFilter(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const187 sk_sp<sksg::RenderNode> EffectBuilder::attachSkSLColorFilter(const skjson::ArrayValue& jprops,
188                                                              sk_sp<sksg::RenderNode> layer) const {
189 #ifdef SK_ENABLE_SKSL
190     auto cfNode = sksg::ExternalColorFilter::Make(std::move(layer));
191     return fBuilder->attachDiscardableAdapter<SkSLColorFilterAdapter>(jprops, *fBuilder,
192                                                                       std::move(cfNode));
193 #else
194     return layer;
195 #endif
196 }
197 
198 } // namespace skottie::internal
199