Flutter Engine
The Flutter Engine
SkRuntimeImageFilter.cpp
Go to the documentation of this file.
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
9
10#include "include/core/SkData.h"
13#include "include/core/SkRect.h"
17#include "include/core/SkSize.h"
18#include "include/core/SkSpan.h"
23#include "src/base/SkSpinlock.h"
28#include "src/core/SkRectPriv.h"
31
32#include <cstddef>
33#include <optional>
34#include <string>
35#include <string_view>
36#include <utility>
37
38using namespace skia_private;
39
40// NOTE: Not in an anonymous namespace so that SkRuntimeShaderBuilder can friend it.
42public:
44 float maxSampleRadius,
45 std::string_view childShaderNames[],
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
60protected:
61 void flatten(SkWriteBuffer&) const override;
62
63private:
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.
71 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
84 skif::LayerSpace<SkIRect> applyMaxSampleRadius(
85 const skif::Mapping& mapping,
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
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
117 SkScalar maxSampleRadius,
118 std::string_view childShaderNames[],
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
150}
151
152sk_sp<SkFlattenable> SkRuntimeImageFilter::CreateProc(SkReadBuffer& buffer) {
153 // We don't know how many inputs to expect yet. Passing -1 allows any number of children.
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) {
190 builder.child(name) = buffer.readBlender();
191 break;
192 }
194 builder.child(name) = buffer.readColorFilter();
195 break;
196 }
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
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
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
235skif::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()));
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
272skif::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) {
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.
286 inputCount,
287 [&](int i) {
288 return this->getChildInputLayerBounds(i, mapping, requiredInput, contentBounds);
289 });
290 }
291}
292
293std::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
298}
299
301 // Can't predict what the RT Shader will generate (see onGetOutputLayerBounds)
303}
static void Union(SkRegion *rgn, const SkIRect &rect)
Definition: RegionTest.cpp:27
#define SkASSERT(cond)
Definition: SkAssert.h:116
#define SK_FLATTENABLE_HOOKS(type)
#define SK_REGISTER_FLATTENABLE(type)
#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)
sk_sp< SkRuntimeEffect > SkMakeCachedRuntimeEffect(SkRuntimeEffect::Result(*make)(SkString sksl, const SkRuntimeEffect::Options &), SkString sksl)
void SkRegisterRuntimeImageFilterFlattenable()
size_t size() const
Definition: SkData.h:30
skif::LayerSpace< SkIRect > getChildInputLayerBounds(int index, const skif::Mapping &mapping, const skif::LayerSpace< SkIRect > &desiredOutput, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const
void flatten(SkWriteBuffer &) const override
skif::FilterResult getChildOutput(int index, const skif::Context &ctx) const
skif::MatrixCapability MatrixCapability
int countInputs() const
static sk_sp< SkImageFilter > RuntimeShader(const SkRuntimeShaderBuilder &builder, std::string_view childShaderName, sk_sp< SkImageFilter > input)
@ kRuntimeImageFilterSampleRadius
static SkRect MakeLargeS32()
Definition: SkRectPriv.h:33
BuilderChild child(std::string_view name)
sk_sp< const SkData > uniforms() const
const SkRuntimeEffect * effect() const
SkSpan< const SkRuntimeEffect::ChildPtr > children() const
static Result MakeForShader(SkString sksl, const Options &)
const std::string & source() const
void flatten(SkWriteBuffer &) const override
SkRuntimeImageFilter(const SkRuntimeShaderBuilder &builder, float maxSampleRadius, std::string_view childShaderNames[], const sk_sp< SkImageFilter > inputs[], int inputCount)
SkRect computeFastBounds(const SkRect &src) const override
sk_sp< SkShader > makeShader(const SkMatrix *localMatrix=nullptr) const
T * get() const
Definition: SkRefCnt.h:303
void resize(size_t count)
Definition: SkTArray.h:423
int size() const
Definition: SkTArray.h:421
void reserve_exact(int n)
Definition: SkTArray.h:181
const LayerSpace< SkIRect > & desiredOutput() const
Context withNewDesiredOutput(const LayerSpace< SkIRect > &desiredOutput) const
const Mapping & mapping() const
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
float SkScalar
Definition: extension.cpp:12
double x
Optional< SkRect > bounds
Definition: SkRecords.h:189
SK_API sk_sp< SkShader > Empty()
Definition: common.py:1
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259