/src/skia/src/gpu/BlurUtils.h
Line | Count | Source |
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 skgpu_BlurUtils_DEFINED |
9 | | #define skgpu_BlurUtils_DEFINED |
10 | | |
11 | | #include "include/core/SkSize.h" |
12 | | #include "include/core/SkSpan.h" |
13 | | #include "include/private/base/SkFloatingPoint.h" |
14 | | |
15 | | #include <array> |
16 | | |
17 | | class SkBitmap; |
18 | | class SkRRect; |
19 | | class SkRuntimeEffect; |
20 | | struct SkV4; |
21 | | |
22 | | // TODO(b/): Many of these utilities could be lifted even into src/core as part of the backend |
23 | | // agnostic blur engine once that API exists. |
24 | | |
25 | | namespace skgpu { |
26 | | |
27 | | // The kernel width of a Gaussian blur of the given pixel radius, for when all pixels are sampled. |
28 | 36.6k | constexpr int BlurKernelWidth(int radius) { return 2 * radius + 1; } |
29 | | |
30 | | // The kernel width of a Gaussian blur of the given pixel radius, that relies on HW bilinear |
31 | | // filtering to combine adjacent pixels. |
32 | 17.2k | constexpr int BlurLinearKernelWidth(int radius) { return radius + 1; } |
33 | | |
34 | | // Any sigmas smaller than this are effectively an identity blur so can skip convolution at a higher |
35 | | // level. The value was chosen because it corresponds roughly to a radius of 1/10px, and because |
36 | | // 2*sigma^2 is slightly greater than SK_ScalarNearlyZero. |
37 | 171k | constexpr bool BlurIsEffectivelyIdentity(float sigma) { return sigma <= 0.03f; } |
38 | | |
39 | | // Convert from a sigma Gaussian standard deviation to a pixel radius such that pixels outside the |
40 | | // radius would have an insignificant contribution to the final blurred value. |
41 | 30.9k | inline int BlurSigmaRadius(float sigma) { |
42 | | // sk_float_ceil2int is not constexpr |
43 | 30.9k | return BlurIsEffectivelyIdentity(sigma) ? 0 : sk_float_ceil2int(3.f * sigma); |
44 | 30.9k | } |
45 | | |
46 | | // The maximum sigma that can be computed without downscaling is based on the number of uniforms and |
47 | | // texture samples the effects will make in a single pass. For 1D passes, the number of samples |
48 | | // is equal to `BlurLinearKernelWidth`; for 2D passes, it is equal to |
49 | | // `BlurKernelWidth(radiusX)*BlurKernelWidth(radiusY)`. This maps back to different maximum sigmas |
50 | | // depending on the approach used, as well as the ratio between the sigmas for the X and Y axes if |
51 | | // a 2D blur is performed. |
52 | | static constexpr int kMaxBlurSamples = 28; |
53 | | |
54 | | // TODO(b/297393474): Update max linear sigma to 9; it had been 4 when a full 1D kernel was used, |
55 | | // but never updated after the linear filtering optimization reduced the number of sample() calls |
56 | | // required. Keep it at 4 for now to better isolate performance changes due to switching to a |
57 | | // runtime effect and constant loop structure. |
58 | | static constexpr float kMaxLinearBlurSigma = 4.f; // -> radius = 27 -> linear kernel width = 28 |
59 | | // NOTE: There is no defined kMaxBlurSigma for direct 2D blurs since it is entirely dependent on the |
60 | | // ratio between the two axes' sigmas, but generally it will be small on the order of a 5x5 kernel. |
61 | | |
62 | | // Return a runtime effect that applies a 2D Gaussian blur in a single pass. The returned effect can |
63 | | // perform arbitrarily sized blur kernels so long as the kernel area is less than kMaxBlurSamples. |
64 | | // An SkRuntimeEffect is returned to give flexibility for callers to convert it to an SkShader or |
65 | | // a GrFragmentProcessor. Callers are responsible for providing the uniform values (using the |
66 | | // appropriate API of the target effect type). The effect declares the following uniforms: |
67 | | // |
68 | | // uniform half4 kernel[7]; |
69 | | // uniform half4 offsets[14]; |
70 | | // uniform shader child; |
71 | | // |
72 | | // 'kernel' should be set to the output of Compute2DBlurKernel(). 'offsets' should be set to the |
73 | | // output of Compute2DBlurOffsets() with the same 'radii' passed to this function. 'child' should be |
74 | | // bound to whatever input is intended to be blurred, and can use nearest-neighbor sampling |
75 | | // (assuming it's an image). |
76 | | const SkRuntimeEffect* GetBlur2DEffect(const SkISize& radii); |
77 | | |
78 | | // Return a runtime effect that applies a 1D Gaussian blur, taking advantage of HW linear |
79 | | // interpolation to accumulate adjacent pixels with fewer samples. The returned effect can be used |
80 | | // for both X and Y axes by changing the 'dir' uniform value (see below). It can be used for all |
81 | | // 1D blurs such that BlurLinearKernelWidth(radius) is less than or equal to kMaxBlurSamples. |
82 | | // Like GetBlur2DEffect(), the caller is free to convert this to an SkShader or a |
83 | | // GrFragmentProcessor and is responsible for assigning uniforms with the appropriate API. Its |
84 | | // uniforms are declared as: |
85 | | // |
86 | | // uniform half4 offsetsAndKernel[14]; |
87 | | // uniform half2 dir; |
88 | | // uniform int radius; |
89 | | // uniform shader child; |
90 | | // |
91 | | // 'offsetsAndKernel' should be set to the output of Compute1DBlurLinearKernel(). 'radius' should |
92 | | // match the radius passed to that function. 'dir' should either be the vector {1,0} or {0,1} |
93 | | // for X and Y axis passes, respectively. 'child' should be bound to whatever input is intended to |
94 | | // be blurred and must use linear sampling in order for the outer blur effect to function correctly. |
95 | | const SkRuntimeEffect* GetLinearBlur1DEffect(int radius); |
96 | | |
97 | | // Calculates a set of weights for a 2D Gaussian blur of the given sigma and radius. It is assumed |
98 | | // that the radius was from prior calls to BlurSigmaRadius(sigma.width()|height()) and is passed in |
99 | | // to avoid redundant calculations. |
100 | | // |
101 | | // The provided span is fully written. The kernel is stored in row-major order based on the provided |
102 | | // radius. Any remaining indices in the span are zero initialized. The span must have at least |
103 | | // BlurKernelWidth(radius.width())*BlurKernelWidth(radius.height()) elements. |
104 | | // |
105 | | // NOTE: These take spans because it can be useful to compute full kernels that are larger than what |
106 | | // is supported in the GPU effects. |
107 | | void Compute2DBlurKernel(SkSize sigma, |
108 | | SkISize radius, |
109 | | SkSpan<float> kernel); |
110 | | |
111 | | // A convenience function that packs the kMaxBlurSample scalars into SkV4's to match the required |
112 | | // type of the uniforms in GetBlur2DEffect(). |
113 | | void Compute2DBlurKernel(SkSize sigma, |
114 | | SkISize radius, |
115 | | std::array<SkV4, kMaxBlurSamples/4>& kernel); |
116 | | |
117 | | // A convenience for the 2D case where one dimension has a sigma of 0. |
118 | 8.64k | inline void Compute1DBlurKernel(float sigma, int radius, SkSpan<float> kernel) { |
119 | 8.64k | Compute2DBlurKernel(SkSize{sigma, 0.f}, SkISize{radius, 0}, kernel); |
120 | 8.64k | } |
121 | | |
122 | | // Utility function to fill in 'offsets' for the effect returned by GetBlur2DEffect(). It |
123 | | // automatically fills in the elements beyond the kernel size with the last real offset to |
124 | | // maximize texture cache hits. Each offset is really an SkV2 but are packed into SkV4's to match |
125 | | // the uniform declaration, and are otherwise ordered row-major. |
126 | | void Compute2DBlurOffsets(SkISize radius, std::array<SkV4, kMaxBlurSamples/2>& offsets); |
127 | | |
128 | | // Calculates a set of weights and sampling offsets for a 1D blur that uses GPU hardware to linearly |
129 | | // combine two logical source pixel values. This assumes that 'radius' was from a prior call to |
130 | | // BlurSigmaRadius() and is passed in to avoid redundant calculations. To match std140 uniform |
131 | | // packing, the offset and kernel weight for adjacent samples are packed into a single SkV4 as |
132 | | // {offset[2*i], kernel[2*i], offset[2*i+1], kernel[2*i+1]} |
133 | | // |
134 | | // The provided array is fully written to. The calculated values are written to indices 0 through |
135 | | // BlurLinearKernelWidth(radius), with any remaining indices zero initialized. It requires the spans |
136 | | // to be the same size and have at least BlurLinearKernelWidth(radius) elements. |
137 | | // |
138 | | // NOTE: This takes an array of a constrained size because its main use is calculating uniforms for |
139 | | // an effect with a matching constraint. Knowing the size of the linear kernel means the full kernel |
140 | | // can be stored on the stack internally. |
141 | | void Compute1DBlurLinearKernel(float sigma, |
142 | | int radius, |
143 | | std::array<SkV4, kMaxBlurSamples/2>& offsetsAndKernel); |
144 | | |
145 | | // Calculates the integral table for an analytic rectangle blur. The integral values are stored in |
146 | | // the red channel of the provided bitmap, which will be 1D with a 1-pixel height. |
147 | | SkBitmap CreateIntegralTable(float sixSigma); |
148 | | |
149 | | // Returns the width of an integral table we will create for the given 6*sigma. |
150 | | int ComputeIntegralTableWidth(float sixSigma); |
151 | | |
152 | | // Creates a profile of a blurred circle. |
153 | | SkBitmap CreateCircleProfile(float sigma, float radius, int profileWidth); |
154 | | |
155 | | // Creates a half plane approximation profile of a blurred circle. |
156 | | SkBitmap CreateHalfPlaneProfile(int profileWidth); |
157 | | |
158 | | // Creates a blurred rounded rectangle mask. 'rrectToDraw' is the original rrect centered within |
159 | | // bounds defined by 'dimensions', which encompass the entire blurred rrect. |
160 | | SkBitmap CreateRRectBlurMask(const SkRRect& rrectToDraw, const SkISize& dimensions, float sigma); |
161 | | |
162 | | } // namespace skgpu |
163 | | |
164 | | #endif // skgpu_BlurUtils_DEFINED |