Coverage Report

Created: 2024-05-20 07:14

/src/skia/src/gpu/graphite/geom/AnalyticBlurMask.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2024 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 "src/gpu/graphite/geom/AnalyticBlurMask.h"
9
10
#include "include/core/SkBitmap.h"
11
#include "include/core/SkMatrix.h"
12
#include "include/core/SkRRect.h"
13
#include "include/gpu/graphite/Recorder.h"
14
#include "src/core/SkRRectPriv.h"
15
#include "src/gpu/BlurUtils.h"
16
#include "src/gpu/graphite/Caps.h"
17
#include "src/gpu/graphite/RecorderPriv.h"
18
#include "src/gpu/graphite/geom/Transform_graphite.h"
19
#include "src/sksl/SkSLUtil.h"
20
21
namespace skgpu::graphite {
22
23
namespace {
24
25
std::optional<Rect> outset_bounds(const SkMatrix& localToDevice,
26
                                  float devSigma,
27
0
                                  const SkRect& srcRect) {
28
0
    float outsetX = 3.0f * devSigma;
29
0
    float outsetY = 3.0f * devSigma;
30
0
    if (localToDevice.isScaleTranslate()) {
31
0
        outsetX /= std::fabs(localToDevice.getScaleX());
32
0
        outsetY /= std::fabs(localToDevice.getScaleY());
33
0
    } else {
34
0
        SkSize scale;
35
0
        if (!localToDevice.decomposeScale(&scale, nullptr)) {
36
0
            return std::nullopt;
37
0
        }
38
0
        outsetX /= scale.width();
39
0
        outsetY /= scale.height();
40
0
    }
41
0
    return srcRect.makeOutset(outsetX, outsetY);
42
0
}
43
44
}  // anonymous namespace
45
46
std::optional<AnalyticBlurMask> AnalyticBlurMask::Make(Recorder* recorder,
47
                                                       const Transform& localToDeviceTransform,
48
                                                       float deviceSigma,
49
0
                                                       const SkRRect& srcRRect) {
50
    // TODO: Implement SkMatrix functionality used below for Transform.
51
0
    SkMatrix localToDevice = localToDeviceTransform;
52
53
0
    if (srcRRect.isRect() && localToDevice.preservesRightAngles()) {
54
0
        return MakeRect(recorder, localToDevice, deviceSigma, srcRRect.rect());
55
0
    }
56
57
0
    SkRRect devRRect;
58
0
    const bool devRRectIsValid = srcRRect.transform(localToDevice, &devRRect);
59
0
    if (devRRectIsValid && SkRRectPriv::IsCircle(devRRect)) {
60
0
        return MakeCircle(recorder, localToDevice, deviceSigma, srcRRect.rect(), devRRect.rect());
61
0
    }
62
63
    // A local-space circle transformed by a rotation matrix will fail SkRRect::transform since it
64
    // only supports scale + translate matrices, but is still a valid circle that can be blurred.
65
0
    if (SkRRectPriv::IsCircle(srcRRect) && localToDevice.isSimilarity()) {
66
0
        const SkRect srcRect = srcRRect.rect();
67
0
        const SkPoint devCenter = localToDevice.mapPoint(srcRect.center());
68
0
        const float devRadius = localToDevice.mapVector(0.0f, srcRect.width() / 2.0f).length();
69
0
        const SkRect devRect = {devCenter.x() - devRadius,
70
0
                                devCenter.y() - devRadius,
71
0
                                devCenter.x() + devRadius,
72
0
                                devCenter.y() + devRadius};
73
0
        return MakeCircle(recorder, localToDevice, deviceSigma, srcRect, devRect);
74
0
    }
75
76
0
    if (devRRectIsValid && SkRRectPriv::IsSimpleCircular(devRRect) &&
77
0
        localToDevice.isScaleTranslate()) {
78
0
        return MakeRRect(recorder, localToDevice, deviceSigma, srcRRect, devRRect);
79
0
    }
80
81
0
    return std::nullopt;
82
0
}
83
84
std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeRect(Recorder* recorder,
85
                                                           const SkMatrix& localToDevice,
86
                                                           float devSigma,
87
0
                                                           const SkRect& srcRect) {
88
0
    SkASSERT(srcRect.isSorted());
89
90
0
    SkRect devRect;
91
0
    SkMatrix devToScaledShape;
92
0
    if (localToDevice.rectStaysRect()) {
93
        // We can do everything in device space when the src rect projects to a rect in device
94
        // space.
95
0
        SkAssertResult(localToDevice.mapRect(&devRect, srcRect));
96
97
0
    } else {
98
        // The view matrix may scale, perhaps anisotropically. But we want to apply our device space
99
        // sigma to the delta of frag coord from the rect edges. Factor out the scaling to define a
100
        // space that is purely rotation / translation from device space (and scale from src space).
101
        // We'll meet in the middle: pre-scale the src rect to be in this space and then apply the
102
        // inverse of the rotation / translation portion to the frag coord.
103
0
        SkMatrix m;
104
0
        SkSize scale;
105
0
        if (!localToDevice.decomposeScale(&scale, &m)) {
106
0
            return std::nullopt;
107
0
        }
108
0
        if (!m.invert(&devToScaledShape)) {
109
0
            return std::nullopt;
110
0
        }
111
0
        devRect = {srcRect.left() * scale.width(),
112
0
                   srcRect.top() * scale.height(),
113
0
                   srcRect.right() * scale.width(),
114
0
                   srcRect.bottom() * scale.height()};
115
0
    }
116
117
0
    if (!recorder->priv().caps()->shaderCaps()->fFloatIs32Bits) {
118
        // We promote the math that gets us into the Gaussian space to full float when the rect
119
        // coords are large. If we don't have full float then fail. We could probably clip the rect
120
        // to an outset device bounds instead.
121
0
        if (std::fabs(devRect.left()) > 16000.0f || std::fabs(devRect.top()) > 16000.0f ||
122
0
            std::fabs(devRect.right()) > 16000.0f || std::fabs(devRect.bottom()) > 16000.0f) {
123
0
            return std::nullopt;
124
0
        }
125
0
    }
126
127
0
    const float sixSigma = 6.0f * devSigma;
128
0
    SkBitmap integralBitmap = skgpu::CreateIntegralTable(sixSigma);
129
0
    if (integralBitmap.empty()) {
130
0
        return std::nullopt;
131
0
    }
132
133
0
    sk_sp<TextureProxy> integral = RecorderPriv::CreateCachedProxy(recorder, integralBitmap,
134
0
                                                                   "BlurredRectIntegralTable");
135
0
    if (!integral) {
136
0
        return std::nullopt;
137
0
    }
138
139
    // In the fast variant we think of the midpoint of the integral texture as aligning with the
140
    // closest rect edge both in x and y. To simplify texture coord calculation we inset the rect so
141
    // that the edge of the inset rect corresponds to t = 0 in the texture. It actually simplifies
142
    // things a bit in the !isFast case, too.
143
0
    const float threeSigma = 3.0f * devSigma;
144
0
    const Rect shapeData = Rect(devRect.left() + threeSigma,
145
0
                                devRect.top() + threeSigma,
146
0
                                devRect.right() - threeSigma,
147
0
                                devRect.bottom() - threeSigma);
148
149
    // In our fast variant we find the nearest horizontal and vertical edges and for each do a
150
    // lookup in the integral texture for each and multiply them. When the rect is less than 6*sigma
151
    // wide then things aren't so simple and we have to consider both the left and right edge of the
152
    // rectangle (and similar in y).
153
0
    const bool isFast = shapeData.left() <= shapeData.right() && shapeData.top() <= shapeData.bot();
154
155
0
    const float invSixSigma = 1.0f / sixSigma;
156
157
    // Determine how much to outset the draw bounds to ensure we hit pixels within 3*sigma.
158
0
    std::optional<Rect> drawBounds = outset_bounds(localToDevice, devSigma, srcRect);
159
0
    if (!drawBounds) {
160
0
        return std::nullopt;
161
0
    }
162
163
0
    return AnalyticBlurMask(*drawBounds,
164
0
                            SkM44(devToScaledShape),
165
0
                            ShapeType::kRect,
166
0
                            shapeData,
167
0
                            {static_cast<float>(isFast), invSixSigma},
168
0
                            integral);
169
0
}
Unexecuted instantiation: skgpu::graphite::AnalyticBlurMask::MakeRect(skgpu::graphite::Recorder*, SkMatrix const&, float, SkRect const&)
Unexecuted instantiation: skgpu::graphite::AnalyticBlurMask::MakeRect(skgpu::graphite::Recorder*, SkMatrix const&, float, SkRect const&)
170
171
std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeCircle(Recorder* recorder,
172
                                                             const SkMatrix& localToDevice,
173
                                                             float devSigma,
174
                                                             const SkRect& srcRect,
175
0
                                                             const SkRect& devRect) {
176
0
    const float radius = devRect.width() / 2.0f;
177
0
    if (!SkIsFinite(radius) || radius < SK_ScalarNearlyZero) {
178
0
        return std::nullopt;
179
0
    }
180
181
    // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
182
    // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to the
183
    // Guassian and the profile texture is a just a Gaussian evaluation. However, we haven't yet
184
    // implemented this latter optimization.
185
0
    constexpr float kHalfPlaneThreshold = 0.1f;
186
0
    const float sigmaToRadiusRatio = std::min(devSigma / radius, 8.0f);
187
0
    const bool useHalfPlaneApprox = sigmaToRadiusRatio <= kHalfPlaneThreshold;
188
189
0
    float solidRadius;
190
0
    float textureRadius;
191
0
    if (useHalfPlaneApprox) {
192
0
        solidRadius = radius - 3.0f * devSigma;
193
0
        textureRadius = 6.0f * devSigma;
194
0
    } else {
195
0
        devSigma = radius * sigmaToRadiusRatio;
196
0
        solidRadius = 0.0f;
197
0
        textureRadius = radius + 3.0f * devSigma;
198
0
    }
199
200
0
    constexpr int kProfileTextureWidth = 512;
201
202
0
    SkBitmap profileBitmap;
203
0
    if (useHalfPlaneApprox) {
204
0
        profileBitmap = skgpu::CreateHalfPlaneProfile(kProfileTextureWidth);
205
0
    } else {
206
        // Rescale params to the size of the texture we're creating.
207
0
        const float scale = kProfileTextureWidth / textureRadius;
208
0
        profileBitmap =
209
0
                skgpu::CreateCircleProfile(devSigma * scale, radius * scale, kProfileTextureWidth);
210
0
    }
211
0
    if (profileBitmap.empty()) {
212
0
        return std::nullopt;
213
0
    }
214
215
0
    sk_sp<TextureProxy> profile = RecorderPriv::CreateCachedProxy(recorder, profileBitmap,
216
0
                                                                  "BlurredCircleIntegralTable");
217
0
    if (!profile) {
218
0
        return std::nullopt;
219
0
    }
220
221
    // In the shader we calculate an index into the blur profile
222
    // "i = (length(fragCoords - circleCenter) - solidRadius + 0.5) / textureRadius" as
223
    // "i = length((fragCoords - circleCenter) / textureRadius) -
224
    //      (solidRadius - 0.5) / textureRadius"
225
    // to avoid passing large values to length() that would overflow. We precalculate
226
    // "1 / textureRadius" and "(solidRadius - 0.5) / textureRadius" here.
227
0
    const Rect shapeData = Rect(devRect.centerX(),
228
0
                                devRect.centerY(),
229
0
                                1.0f / textureRadius,
230
0
                                (solidRadius - 0.5f) / textureRadius);
231
232
    // Determine how much to outset the draw bounds to ensure we hit pixels within 3*sigma.
233
0
    std::optional<Rect> drawBounds = outset_bounds(localToDevice, devSigma, srcRect);
234
0
    if (!drawBounds) {
235
0
        return std::nullopt;
236
0
    }
237
238
0
    constexpr float kUnusedBlurData = 0.0f;
239
0
    return AnalyticBlurMask(*drawBounds,
240
0
                            SkM44(),
241
0
                            ShapeType::kCircle,
242
0
                            shapeData,
243
0
                            {kUnusedBlurData, kUnusedBlurData},
244
0
                            profile);
245
0
}
246
247
std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeRRect(Recorder* recorder,
248
                                                            const SkMatrix& localToDevice,
249
                                                            float devSigma,
250
                                                            const SkRRect& srcRRect,
251
0
                                                            const SkRRect& devRRect) {
252
0
    const int devBlurRadius = 3 * SkScalarCeilToInt(devSigma - 1.0f / 6.0f);
253
254
0
    const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner);
255
0
    const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner);
256
0
    const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner);
257
0
    const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner);
258
259
0
    const int devLeft = SkScalarCeilToInt(std::max<float>(devRadiiUL.fX, devRadiiLL.fX));
260
0
    const int devTop = SkScalarCeilToInt(std::max<float>(devRadiiUL.fY, devRadiiUR.fY));
261
0
    const int devRight = SkScalarCeilToInt(std::max<float>(devRadiiUR.fX, devRadiiLR.fX));
262
0
    const int devBot = SkScalarCeilToInt(std::max<float>(devRadiiLL.fY, devRadiiLR.fY));
263
264
    // This is a conservative check for nine-patchability.
265
0
    const SkRect& devOrig = devRRect.getBounds();
266
0
    if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight - devRight - devBlurRadius ||
267
0
        devOrig.fTop + devTop + devBlurRadius >= devOrig.fBottom - devBot - devBlurRadius) {
268
0
        return std::nullopt;
269
0
    }
270
271
0
    const int newRRWidth = 2 * devBlurRadius + devLeft + devRight + 1;
272
0
    const int newRRHeight = 2 * devBlurRadius + devTop + devBot + 1;
273
274
0
    const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius),
275
0
                                            SkIntToScalar(devBlurRadius),
276
0
                                            SkIntToScalar(newRRWidth),
277
0
                                            SkIntToScalar(newRRHeight));
278
0
    SkVector newRadii[4];
279
0
    newRadii[0] = {SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY)};
280
0
    newRadii[1] = {SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY)};
281
0
    newRadii[2] = {SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY)};
282
0
    newRadii[3] = {SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY)};
283
284
0
    SkRRect rrectToDraw;
285
0
    rrectToDraw.setRectRadii(newRect, newRadii);
286
0
    const SkISize dimensions =
287
0
            SkISize::Make(newRRWidth + 2 * devBlurRadius, newRRHeight + 2 * devBlurRadius);
288
0
    SkBitmap ninePatchBitmap = skgpu::CreateRRectBlurMask(rrectToDraw, dimensions, devSigma);
289
0
    if (ninePatchBitmap.empty()) {
290
0
        return std::nullopt;
291
0
    }
292
293
0
    sk_sp<TextureProxy> ninePatch = RecorderPriv::CreateCachedProxy(recorder, ninePatchBitmap,
294
0
                                                                    "BlurredRRectNinePatch");
295
0
    if (!ninePatch) {
296
0
        return std::nullopt;
297
0
    }
298
299
0
    const float blurRadius = 3.0f * SkScalarCeilToScalar(devSigma - 1.0f / 6.0f);
300
0
    const float edgeSize = 2.0f * blurRadius + SkRRectPriv::GetSimpleRadii(devRRect).fX + 0.5f;
301
0
    const Rect shapeData = devRRect.rect().makeOutset(blurRadius, blurRadius);
302
303
    // Determine how much to outset the draw bounds to ensure we hit pixels within 3*sigma.
304
0
    std::optional<Rect> drawBounds = outset_bounds(localToDevice, devSigma, srcRRect.rect());
305
0
    if (!drawBounds) {
306
0
        return std::nullopt;
307
0
    }
308
309
0
    constexpr float kUnusedBlurData = 0.0f;
310
0
    return AnalyticBlurMask(*drawBounds,
311
0
                            SkM44(),
312
0
                            ShapeType::kRRect,
313
0
                            shapeData,
314
0
                            {edgeSize, kUnusedBlurData},
315
0
                            ninePatch);
316
0
}
317
318
}  // namespace skgpu::graphite