xref: /aosp_15_r20/external/skia/src/effects/imagefilters/SkRuntimeImageFilter.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2021 Google LLC
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 "include/effects/SkImageFilters.h"
9 
10 #include "include/core/SkData.h"
11 #include "include/core/SkFlattenable.h"
12 #include "include/core/SkImageFilter.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkShader.h"
17 #include "include/core/SkSize.h"
18 #include "include/core/SkSpan.h"
19 #include "include/core/SkString.h"
20 #include "include/effects/SkRuntimeEffect.h"
21 #include "include/private/base/SkAssert.h"
22 #include "include/private/base/SkTArray.h"
23 #include "src/base/SkSpinlock.h"
24 #include "src/core/SkImageFilterTypes.h"
25 #include "src/core/SkImageFilter_Base.h"
26 #include "src/core/SkPicturePriv.h"
27 #include "src/core/SkReadBuffer.h"
28 #include "src/core/SkRectPriv.h"
29 #include "src/core/SkRuntimeEffectPriv.h"
30 #include "src/core/SkWriteBuffer.h"
31 
32 #include <cstddef>
33 #include <optional>
34 #include <string>
35 #include <string_view>
36 #include <utility>
37 
38 using namespace skia_private;
39 
40 // NOTE: Not in an anonymous namespace so that SkRuntimeShaderBuilder can friend it.
41 class SkRuntimeImageFilter final : public SkImageFilter_Base {
42 public:
SkRuntimeImageFilter(const SkRuntimeShaderBuilder & builder,float maxSampleRadius,std::string_view childShaderNames[],const sk_sp<SkImageFilter> inputs[],int inputCount)43     SkRuntimeImageFilter(const SkRuntimeShaderBuilder& builder,
44                          float maxSampleRadius,
45                          std::string_view childShaderNames[],
46                          const sk_sp<SkImageFilter> inputs[],
47                          int inputCount)
48             : SkImageFilter_Base(inputs, inputCount)
49             , fRuntimeEffectBuilder(builder)
50             , fMaxSampleRadius(maxSampleRadius) {
51         SkASSERT(maxSampleRadius >= 0.f);
52         fChildShaderNames.reserve_exact(inputCount);
53         for (int i = 0; i < inputCount; i++) {
54             fChildShaderNames.push_back(SkString(childShaderNames[i]));
55         }
56     }
57 
58     SkRect computeFastBounds(const SkRect& src) const override;
59 
60 protected:
61     void flatten(SkWriteBuffer&) const override;
62 
63 private:
64     friend void ::SkRegisterRuntimeImageFilterFlattenable();
SK_FLATTENABLE_HOOKS(SkRuntimeImageFilter)65     SK_FLATTENABLE_HOOKS(SkRuntimeImageFilter)
66 
67     bool onAffectsTransparentBlack() const override { return true; }
68     // Currently there is no way for a client to specify the semantics of geometric uniforms that
69     // should respond to the canvas matrix. Forcing translate-only is a hammer that lets the output
70     // be correct at the expense of resolution when there's a lot of scaling. See skbug.com/13416.
onGetCTMCapability() const71     MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kTranslate; }
72 
73     skif::FilterResult onFilterImage(const skif::Context&) const override;
74 
75     skif::LayerSpace<SkIRect> onGetInputLayerBounds(
76             const skif::Mapping& mapping,
77             const skif::LayerSpace<SkIRect>& desiredOutput,
78             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
79 
80     std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
81             const skif::Mapping& mapping,
82             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
83 
applyMaxSampleRadius(const skif::Mapping & mapping,skif::LayerSpace<SkIRect> bounds) const84     skif::LayerSpace<SkIRect> applyMaxSampleRadius(
85             const skif::Mapping& mapping,
86             skif::LayerSpace<SkIRect> bounds) const {
87         skif::LayerSpace<SkISize> maxSampleRadius = mapping.paramToLayer(
88                 skif::ParameterSpace<SkSize>({fMaxSampleRadius, fMaxSampleRadius})).ceil();
89         bounds.outset(maxSampleRadius);
90         return bounds;
91     }
92 
93     mutable SkSpinlock fRuntimeEffectLock;
94     mutable SkRuntimeShaderBuilder fRuntimeEffectBuilder;
95     STArray<1, SkString> fChildShaderNames;
96     float fMaxSampleRadius;
97 };
98 
RuntimeShader(const SkRuntimeShaderBuilder & builder,SkScalar sampleRadius,std::string_view childShaderName,sk_sp<SkImageFilter> input)99 sk_sp<SkImageFilter> SkImageFilters::RuntimeShader(const SkRuntimeShaderBuilder& builder,
100                                                    SkScalar sampleRadius,
101                                                    std::string_view childShaderName,
102                                                    sk_sp<SkImageFilter> input) {
103     // If no childShaderName is provided, check to see if we can implicitly assign it to the only
104     // child in the effect.
105     if (childShaderName.empty()) {
106         auto children = builder.effect()->children();
107         if (children.size() != 1) {
108             return nullptr;
109         }
110         childShaderName = children.front().name;
111     }
112 
113     return SkImageFilters::RuntimeShader(builder, sampleRadius, &childShaderName, &input, 1);
114 }
115 
RuntimeShader(const SkRuntimeShaderBuilder & builder,SkScalar maxSampleRadius,std::string_view childShaderNames[],const sk_sp<SkImageFilter> inputs[],int inputCount)116 sk_sp<SkImageFilter> SkImageFilters::RuntimeShader(const SkRuntimeShaderBuilder& builder,
117                                                    SkScalar maxSampleRadius,
118                                                    std::string_view childShaderNames[],
119                                                    const sk_sp<SkImageFilter> inputs[],
120                                                    int inputCount) {
121     if (maxSampleRadius < 0.f) {
122         return nullptr; // invalid sample radius
123     }
124 
125     auto child_is_shader = [](const SkRuntimeEffect::Child* child) {
126         return child && child->type == SkRuntimeEffect::ChildType::kShader;
127     };
128 
129     for (int i = 0; i < inputCount; i++) {
130         std::string_view name = childShaderNames[i];
131         // All names must be non-empty, and present as a child shader in the effect:
132         if (name.empty() || !child_is_shader(builder.effect()->findChild(name))) {
133             return nullptr;
134         }
135 
136         // We don't allow duplicates, either:
137         for (int j = 0; j < i; j++) {
138             if (name == childShaderNames[j]) {
139                 return nullptr;
140             }
141         }
142     }
143 
144     return sk_sp<SkImageFilter>(new SkRuntimeImageFilter(builder, maxSampleRadius, childShaderNames,
145                                                          inputs, inputCount));
146 }
147 
SkRegisterRuntimeImageFilterFlattenable()148 void SkRegisterRuntimeImageFilterFlattenable() {
149     SK_REGISTER_FLATTENABLE(SkRuntimeImageFilter);
150 }
151 
CreateProc(SkReadBuffer & buffer)152 sk_sp<SkFlattenable> SkRuntimeImageFilter::CreateProc(SkReadBuffer& buffer) {
153     // We don't know how many inputs to expect yet. Passing -1 allows any number of children.
154     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, -1);
155     if (common.cropRect()) {
156         return nullptr;
157     }
158 
159     // Read the SkSL string and convert it into a runtime effect
160     SkString sksl;
161     buffer.readString(&sksl);
162     auto effect = SkMakeCachedRuntimeEffect(SkRuntimeEffect::MakeForShader, std::move(sksl));
163     if (!buffer.validate(effect != nullptr)) {
164         return nullptr;
165     }
166 
167     // Read the uniform data and make sure it matches the size from the runtime effect
168     sk_sp<SkData> uniforms = buffer.readByteArrayAsData();
169     if (!buffer.validate(uniforms->size() == effect->uniformSize())) {
170         return nullptr;
171     }
172 
173     // Read the child shader names
174     STArray<4, std::string_view> childShaderNames;
175     STArray<4, SkString> childShaderNameStrings;
176     childShaderNames.resize(common.inputCount());
177     childShaderNameStrings.resize(common.inputCount());
178     for (int i = 0; i < common.inputCount(); i++) {
179         buffer.readString(&childShaderNameStrings[i]);
180         childShaderNames[i] = childShaderNameStrings[i].c_str();
181     }
182 
183     SkRuntimeShaderBuilder builder(std::move(effect), std::move(uniforms));
184 
185     // Populate the builder with the corresponding children
186     for (const SkRuntimeEffect::Child& child : builder.effect()->children()) {
187         std::string_view name = child.name;
188         switch (child.type) {
189             case SkRuntimeEffect::ChildType::kBlender: {
190                 builder.child(name) = buffer.readBlender();
191                 break;
192             }
193             case SkRuntimeEffect::ChildType::kColorFilter: {
194                 builder.child(name) = buffer.readColorFilter();
195                 break;
196             }
197             case SkRuntimeEffect::ChildType::kShader: {
198                 builder.child(name) = buffer.readShader();
199                 break;
200             }
201         }
202     }
203 
204     float maxSampleRadius = 0.f; // default before sampleRadius was exposed in the factory
205     if (!buffer.isVersionLT(SkPicturePriv::kRuntimeImageFilterSampleRadius)) {
206         maxSampleRadius = buffer.readScalar();
207     }
208 
209     if (!buffer.isValid()) {
210         return nullptr;
211     }
212 
213     return SkImageFilters::RuntimeShader(builder, maxSampleRadius, childShaderNames.data(),
214                                          common.inputs(), common.inputCount());
215 }
216 
flatten(SkWriteBuffer & buffer) const217 void SkRuntimeImageFilter::flatten(SkWriteBuffer& buffer) const {
218     this->SkImageFilter_Base::flatten(buffer);
219     fRuntimeEffectLock.acquire();
220     buffer.writeString(fRuntimeEffectBuilder.effect()->source().c_str());
221     buffer.writeDataAsByteArray(fRuntimeEffectBuilder.uniforms().get());
222     for (const SkString& name : fChildShaderNames) {
223         buffer.writeString(name.c_str());
224     }
225     for (size_t x = 0; x < fRuntimeEffectBuilder.children().size(); x++) {
226         buffer.writeFlattenable(fRuntimeEffectBuilder.children()[x].flattenable());
227     }
228     fRuntimeEffectLock.release();
229 
230     buffer.writeScalar(fMaxSampleRadius);
231 }
232 
233 ///////////////////////////////////////////////////////////////////////////////////////////////////
234 
onFilterImage(const skif::Context & ctx) const235 skif::FilterResult SkRuntimeImageFilter::onFilterImage(const skif::Context& ctx) const {
236     using ShaderFlags = skif::FilterResult::ShaderFlags;
237 
238     const int inputCount = this->countInputs();
239     SkASSERT(inputCount == fChildShaderNames.size());
240 
241     skif::Context inputCtx = ctx.withNewDesiredOutput(
242             this->applyMaxSampleRadius(ctx.mapping(), ctx.desiredOutput()));
243     skif::FilterResult::Builder builder{ctx};
244     for (int i = 0; i < inputCount; ++i) {
245         // Record the input context's desired output as the sample bounds for the child shaders
246         // since the runtime shader can go up to max sample radius away from its desired output
247         // (which is the default sample bounds if we didn't override it here).
248         builder.add(this->getChildOutput(i, inputCtx),
249                     inputCtx.desiredOutput(),
250                     ShaderFlags::kNonTrivialSampling);
251     }
252     return builder.eval([&](SkSpan<sk_sp<SkShader>> inputs) {
253         // lock the mutation of the builder and creation of the shader so that the builder's state
254         // is const and is safe for multi-threaded access.
255         fRuntimeEffectLock.acquire();
256         for (int i = 0; i < inputCount; i++) {
257             fRuntimeEffectBuilder.child(fChildShaderNames[i].c_str()) = inputs[i];
258         }
259         sk_sp<SkShader> shader = fRuntimeEffectBuilder.makeShader();
260 
261         // Remove the inputs from the builder to avoid unnecessarily prolonging the input shaders'
262         // lifetimes.
263         for (int i = 0; i < inputCount; i++) {
264             fRuntimeEffectBuilder.child(fChildShaderNames[i].c_str()) = nullptr;
265         }
266         fRuntimeEffectLock.release();
267 
268         return shader;
269     }, {}, /*evaluateInParameterSpace=*/true);
270 }
271 
onGetInputLayerBounds(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & desiredOutput,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const272 skif::LayerSpace<SkIRect> SkRuntimeImageFilter::onGetInputLayerBounds(
273         const skif::Mapping& mapping,
274         const skif::LayerSpace<SkIRect>& desiredOutput,
275         std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
276     const int inputCount = this->countInputs();
277     if (inputCount <= 0) {
278         return skif::LayerSpace<SkIRect>::Empty();
279     } else {
280         // Provide 'maxSampleRadius' pixels (in layer space) to the child shaders.
281         skif::LayerSpace<SkIRect> requiredInput =
282                 this->applyMaxSampleRadius(mapping, desiredOutput);
283 
284         // Union of all child input bounds so that one source image can provide for all of them.
285         return skif::LayerSpace<SkIRect>::Union(
286                 inputCount,
287                 [&](int i) {
288                     return this->getChildInputLayerBounds(i, mapping, requiredInput, contentBounds);
289                 });
290     }
291 }
292 
onGetOutputLayerBounds(const skif::Mapping &,std::optional<skif::LayerSpace<SkIRect>>) const293 std::optional<skif::LayerSpace<SkIRect>> SkRuntimeImageFilter::onGetOutputLayerBounds(
294         const skif::Mapping& /*mapping*/,
295         std::optional<skif::LayerSpace<SkIRect>> /*contentBounds*/) const {
296     // Pessimistically assume it can cover anything
297     return skif::LayerSpace<SkIRect>::Unbounded();
298 }
299 
computeFastBounds(const SkRect & src) const300 SkRect SkRuntimeImageFilter::computeFastBounds(const SkRect& src) const {
301     // Can't predict what the RT Shader will generate (see onGetOutputLayerBounds)
302     return SkRectPriv::MakeLargeS32();
303 }
304