Flutter Engine
The Flutter Engine
SkBlurEngine.h
Go to the documentation of this file.
1/*
2 * Copyright 2023 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#ifndef SkBlurEngine_DEFINED
9#define SkBlurEngine_DEFINED
10
11#include "include/core/SkM44.h" // IWYU pragma: keep
13#include "include/core/SkSize.h"
14#include "include/core/SkSpan.h"
16
17#include <array>
18
19class SkDevice;
20class SkRuntimeEffect;
22class SkSpecialImage;
23struct SkImageInfo;
24struct SkIRect;
25
26enum class SkFilterMode;
27enum class SkTileMode;
28enum SkColorType : int;
29
30/**
31 * SkBlurEngine is a backend-agnostic provider of blur algorithms. Each Skia backend defines a blur
32 * engine with a set of supported algorithms and/or implementations. A given implementation may be
33 * optimized for a particular color type, sigma range, or available hardware. Each engine and its
34 * algorithms are assumed to operate only on SkImages corresponding to its Skia backend, and will
35 * produce output SkImages of the same type.
36 *
37 * Algorithms are allowed to specify a maximum supported sigma. If the desired sigma is higher than
38 * this, the input image and output region must be downscaled by the caller before invoking the
39 * algorithm. This is to provide the most flexibility for input representation (e.g. directly
40 * rasterize at half resolution or apply deferred filter effects during the first downsample pass).
41 *
42 * skif::FilterResult::Builder::blur() is a convenient wrapper around the blur engine and
43 * automatically handles resizing.
44*/
46public:
47 class Algorithm;
48
49 virtual ~SkBlurEngine() = default;
50
51 // Returns an Algorithm ideal for the requested 'sigma' that will support sampling an image of
52 // the given 'colorType'. If the engine does not support the requested configuration, it returns
53 // null. The engine maintains the lifetime of its algorithms, so the returned non-null
54 // Algorithms live as long as the engine does.
55 virtual const Algorithm* findAlgorithm(SkSize sigma,
56 SkColorType colorType) const = 0;
57
58 // TODO: Consolidate common utility functions from SkBlurMask.h into this header.
59
60 // Any sigmas smaller than this are effectively an identity blur so can skip convolution at a
61 // higher level. The value was chosen because it corresponds roughly to a radius of 1/10px, and
62 // because 2*sigma^2 is slightly greater than SK_ScalarNearlyZero.
63 static constexpr bool IsEffectivelyIdentity(float sigma) { return sigma <= 0.03f; }
64
65 // Convert from a sigma Gaussian standard deviation to a pixel radius such that pixels outside
66 // the radius would have an insignificant contribution to the final blurred value.
67 static int SigmaToRadius(float sigma) {
68 // sk_float_ceil2int is not constexpr
69 return IsEffectivelyIdentity(sigma) ? 0 : sk_float_ceil2int(3.f * sigma);
70 }
71};
72
74public:
75 virtual ~Algorithm() = default;
76
77 // The maximum sigma that can be passed to blur() in the X and/or Y sigma values. Larger
78 // requested sigmas must manually downscale the input image and upscale the output image.
79 virtual float maxSigma() const = 0;
80
81 // Whether or not the SkTileMode can be passed to blur() must be SkTileMode::kDecal, or if any
82 // tile mode is supported. If only kDecal is supported, then callers must manually apply the
83 // tilemode and account for that in the src and dst bounds passed into blur(). If this returns
84 // false, then the algorithm supports all SkTileModes.
85 // TODO: Once CPU blurs support all tile modes, this API can go away.
86 virtual bool supportsOnlyDecalTiling() const = 0;
87
88 // Produce a blurred image that fills 'dstRect' (their dimensions will match). 'dstRect's top
89 // left corner defines the output's location relative to the 'src' image. 'srcRect' restricts
90 // the pixels that are included in the blur and is also relative to 'src'. The 'tileMode'
91 // applies to the boundary of 'srcRect', which must be contained within 'src's dimensions.
92 //
93 // 'srcRect' and 'dstRect' may be different sizes and even be disjoint.
94 //
95 // The returned SkImage will have the same color type and colorspace as the input image. It will
96 // be an SkImage type matching the underlying Skia backend. If the 'src' SkImage is not a
97 // compatible SkImage type, null is returned.
98 // TODO(b/299474380): This only takes SkSpecialImage to work with skif::FilterResult and
99 // SkDevice::snapSpecial(); SkImage would be ideal.
102 const SkIRect& srcRect,
103 SkTileMode tileMode,
104 const SkIRect& dstRect) const = 0;
105};
106
107/**
108 * The default blur implementation uses internal runtime effects to evaluate either a single 2D
109 * kernel within a shader, or performs two 1D blur passes. This algorithm is backend agnostic but
110 * must be subclassed per backend to define the SkDevice creation function.
111 */
113public:
114 float maxSigma() const override { return kMaxLinearSigma; }
115 bool supportsOnlyDecalTiling() const override { return false; }
116
119 const SkIRect& srcRect,
120 SkTileMode tileMode,
121 const SkIRect& dstRect) const override;
122
123private:
124 // Create a new surface, which can be approx-fit and have undefined contents.
125 virtual sk_sp<SkDevice> makeDevice(const SkImageInfo&) const = 0;
126
127 sk_sp<SkSpecialImage> renderBlur(SkRuntimeShaderBuilder* blurEffectBuilder,
128 SkFilterMode filter,
129 SkISize radii,
131 const SkIRect& srcRect,
132 SkTileMode tileMode,
133 const SkIRect& dstRect) const;
134 sk_sp<SkSpecialImage> evalBlur2D(SkSize sigma,
135 SkISize radii,
137 const SkIRect& srcRect,
138 SkTileMode tileMode,
139 const SkIRect& dstRect) const;
140 sk_sp<SkSpecialImage> evalBlur1D(float sigma,
141 int radius,
142 SkV2 dir,
144 SkIRect srcRect,
145 SkTileMode tileMode,
146 SkIRect dstRect) const;
147
148// TODO: These are internal details of the blur shaders, but are public for now because multiple
149// backends invoke the blur shaders directly. Once everything just goes through this class, these
150// can be hidden.
151public:
152
153 // The kernel width of a Gaussian blur of the given pixel radius, when all pixels are sampled.
154 static constexpr int KernelWidth(int radius) { return 2 * radius + 1; }
155
156 // The kernel width of a Gaussian blur of the given pixel radius, that relies on HW bilinear
157 // filtering to combine adjacent pixels.
158 static constexpr int LinearKernelWidth(int radius) { return radius + 1; }
159
160 // The maximum sigma that can be computed without downscaling is based on the number of uniforms
161 // and texture samples the effects will make in a single pass. For 1D passes, the number of
162 // samples is equal to `LinearKernelWidth`; for 2D passes, it is equal to
163 // `KernelWidth(radiusX)*KernelWidth(radiusY)`. This maps back to different maximum sigmas
164 // depending on the approach used, as well as the ratio between the sigmas for the X and Y axes
165 // if a 2D blur is performed.
166 static constexpr int kMaxSamples = 28;
167
168 // TODO(b/297393474): Update max linear sigma to 9; it had been 4 when a full 1D kernel was
169 // used, but never updated after the linear filtering optimization reduced the number of
170 // sample() calls required. Keep it at 4 for now to better isolate performance changes due to
171 // switching to a runtime effect and constant loop structure.
172 static constexpr float kMaxLinearSigma = 4.f; // -> radius = 27 -> linear kernel width = 28
173 // NOTE: There is no defined kMaxBlurSigma for direct 2D blurs since it is entirely dependent on
174 // the ratio between the two axes' sigmas, but generally it will be small on the order of a
175 // 5x5 kernel.
176
177 // Return a runtime effect that applies a 2D Gaussian blur in a single pass. The returned effect
178 // can perform arbitrarily sized blur kernels so long as the kernel area is less than
179 // kMaxSamples. An SkRuntimeEffect is returned to give flexibility for callers to convert it to
180 // an SkShader or a GrFragmentProcessor. Callers are responsible for providing the uniform
181 // values (using the appropriate API of the target effect type). The effect declares the
182 // following uniforms:
183 //
184 // uniform half4 kernel[7];
185 // uniform half4 offsets[14];
186 // uniform shader child;
187 //
188 // 'kernel' should be set to the output of Compute2DBlurKernel(). 'offsets' should be set to the
189 // output of Compute2DBlurOffsets() with the same 'radii' passed to this function. 'child'
190 // should be bound to whatever input is intended to be blurred, and can use nearest-neighbor
191 // sampling (assuming it's an image).
192 static const SkRuntimeEffect* GetBlur2DEffect(const SkISize& radii);
193
194 // Return a runtime effect that applies a 1D Gaussian blur, taking advantage of HW linear
195 // interpolation to accumulate adjacent pixels with fewer samples. The returned effect can be
196 // used for both X and Y axes by changing the 'dir' uniform value (see below). It can be used
197 // for all 1D blurs such that BlurLinearKernelWidth(radius) is less than or equal to
198 // kMaxSamples. Like GetBlur2DEffect(), the caller is free to convert this to an SkShader or a
199 // GrFragmentProcessor and is responsible for assigning uniforms with the appropriate API. Its
200 // uniforms are declared as:
201 //
202 // uniform half4 offsetsAndKernel[14];
203 // uniform half2 dir;
204 // uniform int radius;
205 // uniform shader child;
206 //
207 // 'offsetsAndKernel' should be set to the output of Compute1DBlurLinearKernel(). 'radius'
208 // should match the radius passed to that function. 'dir' should either be the vector {1,0} or
209 // {0,1} for X and Y axis passes, respectively. 'child' should be bound to whatever input is
210 // intended to be blurred and must use linear sampling in order for the outer blur effect to
211 // function correctly.
212 static const SkRuntimeEffect* GetLinearBlur1DEffect(int radius);
213
214 // Calculates a set of weights for a 2D Gaussian blur of the given sigma and radius. It is
215 // assumed that the radius was from prior calls to BlurSigmaRadius(sigma.width()|height()) and
216 // is passed in to avoid redundant calculations.
217 //
218 // The provided span is fully written. The kernel is stored in row-major order based on the
219 // provided radius. Any remaining indices in the span are zero initialized. The span must have
220 // at least KernelWidth(radius.width())*KernelWidth(radius.height()) elements.
221 //
222 // NOTE: These take spans because it can be useful to compute full kernels that are larger than
223 // what is supported in the GPU effects.
224 static void Compute2DBlurKernel(SkSize sigma,
225 SkISize radius,
226 SkSpan<float> kernel);
227
228 // A convenience function that packs the kMaxBlurSample scalars into SkV4's to match the
229 // required type of the uniforms in GetBlur2DEffect().
230 static void Compute2DBlurKernel(SkSize sigma,
231 SkISize radius,
232 std::array<SkV4, kMaxSamples/4>& kernel);
233
234 // A convenience for the 2D case where one dimension has a sigma of 0.
235 static void Compute1DBlurKernel(float sigma, int radius, SkSpan<float> kernel) {
236 Compute2DBlurKernel(SkSize{sigma, 0.f}, SkISize{radius, 0}, kernel);
237 }
238
239 // Utility function to fill in 'offsets' for the effect returned by GetBlur2DEffect(). It
240 // automatically fills in the elements beyond the kernel size with the last real offset to
241 // maximize texture cache hits. Each offset is really an SkV2 but are packed into SkV4's to
242 // match the uniform declaration, and are otherwise ordered row-major.
243 static void Compute2DBlurOffsets(SkISize radius, std::array<SkV4, kMaxSamples/2>& offsets);
244
245 // Calculates a set of weights and sampling offsets for a 1D blur that uses GPU hardware to
246 // linearly combine two logical source pixel values. This assumes that 'radius' was from a prior
247 // call to BlurSigmaRadius() and is passed in to avoid redundant calculations. To match std140
248 // uniform packing, the offset and kernel weight for adjacent samples are packed into a single
249 // SkV4 as {offset[2*i], kernel[2*i], offset[2*i+1], kernel[2*i+1]}
250 //
251 // The provided array is fully written to. The calculated values are written to indices 0
252 // through LinearKernelWidth(radius), with any remaining indices zero initialized.
253 //
254 // NOTE: This takes an array of a constrained size because its main use is calculating uniforms
255 // for an effect with a matching constraint. Knowing the size of the linear kernel means the
256 // full kernel can be stored on the stack internally.
257 static void Compute1DBlurLinearKernel(float sigma,
258 int radius,
259 std::array<SkV4, kMaxSamples/2>& offsetsAndKernel);
260
261};
262
263#endif // SkBlurEngine_DEFINED
SkColorType
Definition: SkColorType.h:19
#define sk_float_ceil2int(x)
static SkColorType colorType(AImageDecoder *decoder, const AImageDecoderHeaderInfo *headerInfo)
SkFilterMode
SkTileMode
Definition: SkTileMode.h:13
virtual ~Algorithm()=default
virtual float maxSigma() const =0
virtual bool supportsOnlyDecalTiling() const =0
virtual sk_sp< SkSpecialImage > blur(SkSize sigma, sk_sp< SkSpecialImage > src, const SkIRect &srcRect, SkTileMode tileMode, const SkIRect &dstRect) const =0
static int SigmaToRadius(float sigma)
Definition: SkBlurEngine.h:67
virtual ~SkBlurEngine()=default
static constexpr bool IsEffectivelyIdentity(float sigma)
Definition: SkBlurEngine.h:63
virtual const Algorithm * findAlgorithm(SkSize sigma, SkColorType colorType) const =0
float maxSigma() const override
Definition: SkBlurEngine.h:114
static constexpr int kMaxSamples
Definition: SkBlurEngine.h:166
static void Compute1DBlurKernel(float sigma, int radius, SkSpan< float > kernel)
Definition: SkBlurEngine.h:235
static void Compute1DBlurLinearKernel(float sigma, int radius, std::array< SkV4, kMaxSamples/2 > &offsetsAndKernel)
static constexpr float kMaxLinearSigma
Definition: SkBlurEngine.h:172
virtual sk_sp< SkDevice > makeDevice(const SkImageInfo &) const =0
static void Compute2DBlurOffsets(SkISize radius, std::array< SkV4, kMaxSamples/2 > &offsets)
static constexpr int LinearKernelWidth(int radius)
Definition: SkBlurEngine.h:158
sk_sp< SkSpecialImage > blur(SkSize sigma, sk_sp< SkSpecialImage > src, const SkIRect &srcRect, SkTileMode tileMode, const SkIRect &dstRect) const override
static const SkRuntimeEffect * GetBlur2DEffect(const SkISize &radii)
static constexpr int KernelWidth(int radius)
Definition: SkBlurEngine.h:154
static const SkRuntimeEffect * GetLinearBlur1DEffect(int radius)
static void Compute2DBlurKernel(SkSize sigma, SkISize radius, SkSpan< float > kernel)
bool supportsOnlyDecalTiling() const override
Definition: SkBlurEngine.h:115
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 Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition: switches.h:145
list offsets
Definition: mskp_parser.py:37
Definition: SkRect.h:32
Definition: SkSize.h:16
Definition: SkSize.h:52
Definition: SkM44.h:19