xref: /aosp_15_r20/external/skia/src/gpu/graphite/geom/AnalyticBlurMask.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2024 Google LLC
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/AnalyticBlurMask.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkBitmap.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRRect.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRect.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/gpu/graphite/Recorder.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAlign.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkMacros.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkPoint_impl.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkFloatBits.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkRRectPriv.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/BlurUtils.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/ResourceKey.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/Caps.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/ProxyCache.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/RecorderPriv.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Transform_graphite.h"
30*c8dee2aaSAndroid Build Coastguard Worker #include "src/sksl/SkSLUtil.h"
31*c8dee2aaSAndroid Build Coastguard Worker 
32*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
33*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
34*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
35*c8dee2aaSAndroid Build Coastguard Worker #include <cstring>
36*c8dee2aaSAndroid Build Coastguard Worker 
37*c8dee2aaSAndroid Build Coastguard Worker namespace skgpu::graphite {
38*c8dee2aaSAndroid Build Coastguard Worker 
39*c8dee2aaSAndroid Build Coastguard Worker namespace {
40*c8dee2aaSAndroid Build Coastguard Worker 
outset_bounds(const SkMatrix & localToDevice,float devSigma,const SkRect & srcRect)41*c8dee2aaSAndroid Build Coastguard Worker std::optional<Rect> outset_bounds(const SkMatrix& localToDevice,
42*c8dee2aaSAndroid Build Coastguard Worker                                   float devSigma,
43*c8dee2aaSAndroid Build Coastguard Worker                                   const SkRect& srcRect) {
44*c8dee2aaSAndroid Build Coastguard Worker     float outsetX = 3.0f * devSigma;
45*c8dee2aaSAndroid Build Coastguard Worker     float outsetY = 3.0f * devSigma;
46*c8dee2aaSAndroid Build Coastguard Worker     if (localToDevice.isScaleTranslate()) {
47*c8dee2aaSAndroid Build Coastguard Worker         outsetX /= std::fabs(localToDevice.getScaleX());
48*c8dee2aaSAndroid Build Coastguard Worker         outsetY /= std::fabs(localToDevice.getScaleY());
49*c8dee2aaSAndroid Build Coastguard Worker     } else {
50*c8dee2aaSAndroid Build Coastguard Worker         SkSize scale;
51*c8dee2aaSAndroid Build Coastguard Worker         if (!localToDevice.decomposeScale(&scale, nullptr)) {
52*c8dee2aaSAndroid Build Coastguard Worker             return std::nullopt;
53*c8dee2aaSAndroid Build Coastguard Worker         }
54*c8dee2aaSAndroid Build Coastguard Worker         outsetX /= scale.width();
55*c8dee2aaSAndroid Build Coastguard Worker         outsetY /= scale.height();
56*c8dee2aaSAndroid Build Coastguard Worker     }
57*c8dee2aaSAndroid Build Coastguard Worker     return srcRect.makeOutset(outsetX, outsetY);
58*c8dee2aaSAndroid Build Coastguard Worker }
59*c8dee2aaSAndroid Build Coastguard Worker 
60*c8dee2aaSAndroid Build Coastguard Worker }  // anonymous namespace
61*c8dee2aaSAndroid Build Coastguard Worker 
Make(Recorder * recorder,const Transform & localToDeviceTransform,float deviceSigma,const SkRRect & srcRRect)62*c8dee2aaSAndroid Build Coastguard Worker std::optional<AnalyticBlurMask> AnalyticBlurMask::Make(Recorder* recorder,
63*c8dee2aaSAndroid Build Coastguard Worker                                                        const Transform& localToDeviceTransform,
64*c8dee2aaSAndroid Build Coastguard Worker                                                        float deviceSigma,
65*c8dee2aaSAndroid Build Coastguard Worker                                                        const SkRRect& srcRRect) {
66*c8dee2aaSAndroid Build Coastguard Worker     // TODO: Implement SkMatrix functionality used below for Transform.
67*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix localToDevice = localToDeviceTransform;
68*c8dee2aaSAndroid Build Coastguard Worker 
69*c8dee2aaSAndroid Build Coastguard Worker     if (srcRRect.isRect() && localToDevice.preservesRightAngles()) {
70*c8dee2aaSAndroid Build Coastguard Worker         return MakeRect(recorder, localToDevice, deviceSigma, srcRRect.rect());
71*c8dee2aaSAndroid Build Coastguard Worker     }
72*c8dee2aaSAndroid Build Coastguard Worker 
73*c8dee2aaSAndroid Build Coastguard Worker     SkRRect devRRect;
74*c8dee2aaSAndroid Build Coastguard Worker     const bool devRRectIsValid = srcRRect.transform(localToDevice, &devRRect);
75*c8dee2aaSAndroid Build Coastguard Worker     if (devRRectIsValid && SkRRectPriv::IsCircle(devRRect)) {
76*c8dee2aaSAndroid Build Coastguard Worker         return MakeCircle(recorder, localToDevice, deviceSigma, srcRRect.rect(), devRRect.rect());
77*c8dee2aaSAndroid Build Coastguard Worker     }
78*c8dee2aaSAndroid Build Coastguard Worker 
79*c8dee2aaSAndroid Build Coastguard Worker     // A local-space circle transformed by a rotation matrix will fail SkRRect::transform since it
80*c8dee2aaSAndroid Build Coastguard Worker     // only supports scale + translate matrices, but is still a valid circle that can be blurred.
81*c8dee2aaSAndroid Build Coastguard Worker     if (SkRRectPriv::IsCircle(srcRRect) && localToDevice.isSimilarity()) {
82*c8dee2aaSAndroid Build Coastguard Worker         const SkRect srcRect = srcRRect.rect();
83*c8dee2aaSAndroid Build Coastguard Worker         const SkPoint devCenter = localToDevice.mapPoint(srcRect.center());
84*c8dee2aaSAndroid Build Coastguard Worker         const float devRadius = localToDevice.mapVector(0.0f, srcRect.width() / 2.0f).length();
85*c8dee2aaSAndroid Build Coastguard Worker         const SkRect devRect = {devCenter.x() - devRadius,
86*c8dee2aaSAndroid Build Coastguard Worker                                 devCenter.y() - devRadius,
87*c8dee2aaSAndroid Build Coastguard Worker                                 devCenter.x() + devRadius,
88*c8dee2aaSAndroid Build Coastguard Worker                                 devCenter.y() + devRadius};
89*c8dee2aaSAndroid Build Coastguard Worker         return MakeCircle(recorder, localToDevice, deviceSigma, srcRect, devRect);
90*c8dee2aaSAndroid Build Coastguard Worker     }
91*c8dee2aaSAndroid Build Coastguard Worker 
92*c8dee2aaSAndroid Build Coastguard Worker     if (devRRectIsValid && SkRRectPriv::IsSimpleCircular(devRRect) &&
93*c8dee2aaSAndroid Build Coastguard Worker         localToDevice.isScaleTranslate()) {
94*c8dee2aaSAndroid Build Coastguard Worker         return MakeRRect(recorder, localToDevice, deviceSigma, srcRRect, devRRect);
95*c8dee2aaSAndroid Build Coastguard Worker     }
96*c8dee2aaSAndroid Build Coastguard Worker 
97*c8dee2aaSAndroid Build Coastguard Worker     return std::nullopt;
98*c8dee2aaSAndroid Build Coastguard Worker }
99*c8dee2aaSAndroid Build Coastguard Worker 
MakeRect(Recorder * recorder,const SkMatrix & localToDevice,float devSigma,const SkRect & srcRect)100*c8dee2aaSAndroid Build Coastguard Worker std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeRect(Recorder* recorder,
101*c8dee2aaSAndroid Build Coastguard Worker                                                            const SkMatrix& localToDevice,
102*c8dee2aaSAndroid Build Coastguard Worker                                                            float devSigma,
103*c8dee2aaSAndroid Build Coastguard Worker                                                            const SkRect& srcRect) {
104*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(srcRect.isSorted());
105*c8dee2aaSAndroid Build Coastguard Worker 
106*c8dee2aaSAndroid Build Coastguard Worker     SkRect devRect;
107*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix devToScaledShape;
108*c8dee2aaSAndroid Build Coastguard Worker     if (localToDevice.rectStaysRect()) {
109*c8dee2aaSAndroid Build Coastguard Worker         // We can do everything in device space when the src rect projects to a rect in device
110*c8dee2aaSAndroid Build Coastguard Worker         // space.
111*c8dee2aaSAndroid Build Coastguard Worker         SkAssertResult(localToDevice.mapRect(&devRect, srcRect));
112*c8dee2aaSAndroid Build Coastguard Worker 
113*c8dee2aaSAndroid Build Coastguard Worker     } else {
114*c8dee2aaSAndroid Build Coastguard Worker         // The view matrix may scale, perhaps anisotropically. But we want to apply our device space
115*c8dee2aaSAndroid Build Coastguard Worker         // sigma to the delta of frag coord from the rect edges. Factor out the scaling to define a
116*c8dee2aaSAndroid Build Coastguard Worker         // space that is purely rotation / translation from device space (and scale from src space).
117*c8dee2aaSAndroid Build Coastguard Worker         // We'll meet in the middle: pre-scale the src rect to be in this space and then apply the
118*c8dee2aaSAndroid Build Coastguard Worker         // inverse of the rotation / translation portion to the frag coord.
119*c8dee2aaSAndroid Build Coastguard Worker         SkMatrix m;
120*c8dee2aaSAndroid Build Coastguard Worker         SkSize scale;
121*c8dee2aaSAndroid Build Coastguard Worker         if (!localToDevice.decomposeScale(&scale, &m)) {
122*c8dee2aaSAndroid Build Coastguard Worker             return std::nullopt;
123*c8dee2aaSAndroid Build Coastguard Worker         }
124*c8dee2aaSAndroid Build Coastguard Worker         if (!m.invert(&devToScaledShape)) {
125*c8dee2aaSAndroid Build Coastguard Worker             return std::nullopt;
126*c8dee2aaSAndroid Build Coastguard Worker         }
127*c8dee2aaSAndroid Build Coastguard Worker         devRect = {srcRect.left() * scale.width(),
128*c8dee2aaSAndroid Build Coastguard Worker                    srcRect.top() * scale.height(),
129*c8dee2aaSAndroid Build Coastguard Worker                    srcRect.right() * scale.width(),
130*c8dee2aaSAndroid Build Coastguard Worker                    srcRect.bottom() * scale.height()};
131*c8dee2aaSAndroid Build Coastguard Worker     }
132*c8dee2aaSAndroid Build Coastguard Worker 
133*c8dee2aaSAndroid Build Coastguard Worker     if (!recorder->priv().caps()->shaderCaps()->fFloatIs32Bits) {
134*c8dee2aaSAndroid Build Coastguard Worker         // We promote the math that gets us into the Gaussian space to full float when the rect
135*c8dee2aaSAndroid Build Coastguard Worker         // coords are large. If we don't have full float then fail. We could probably clip the rect
136*c8dee2aaSAndroid Build Coastguard Worker         // to an outset device bounds instead.
137*c8dee2aaSAndroid Build Coastguard Worker         if (std::fabs(devRect.left()) > 16000.0f || std::fabs(devRect.top()) > 16000.0f ||
138*c8dee2aaSAndroid Build Coastguard Worker             std::fabs(devRect.right()) > 16000.0f || std::fabs(devRect.bottom()) > 16000.0f) {
139*c8dee2aaSAndroid Build Coastguard Worker             return std::nullopt;
140*c8dee2aaSAndroid Build Coastguard Worker         }
141*c8dee2aaSAndroid Build Coastguard Worker     }
142*c8dee2aaSAndroid Build Coastguard Worker 
143*c8dee2aaSAndroid Build Coastguard Worker     const float sixSigma = 6.0f * devSigma;
144*c8dee2aaSAndroid Build Coastguard Worker     const int tableWidth = ComputeIntegralTableWidth(sixSigma);
145*c8dee2aaSAndroid Build Coastguard Worker     UniqueKey key;
146*c8dee2aaSAndroid Build Coastguard Worker     {
147*c8dee2aaSAndroid Build Coastguard Worker         static const UniqueKey::Domain kRectBlurDomain = UniqueKey::GenerateDomain();
148*c8dee2aaSAndroid Build Coastguard Worker         UniqueKey::Builder builder(&key, kRectBlurDomain, 1, "BlurredRectIntegralTable");
149*c8dee2aaSAndroid Build Coastguard Worker         builder[0] = tableWidth;
150*c8dee2aaSAndroid Build Coastguard Worker     }
151*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<TextureProxy> integral = recorder->priv().proxyCache()->findOrCreateCachedProxy(
152*c8dee2aaSAndroid Build Coastguard Worker             recorder, key, &tableWidth,
153*c8dee2aaSAndroid Build Coastguard Worker             [](const void* context) {
154*c8dee2aaSAndroid Build Coastguard Worker                 int tableWidth = *static_cast<const int*>(context);
155*c8dee2aaSAndroid Build Coastguard Worker                 return CreateIntegralTable(tableWidth);
156*c8dee2aaSAndroid Build Coastguard Worker             });
157*c8dee2aaSAndroid Build Coastguard Worker 
158*c8dee2aaSAndroid Build Coastguard Worker     if (!integral) {
159*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
160*c8dee2aaSAndroid Build Coastguard Worker     }
161*c8dee2aaSAndroid Build Coastguard Worker 
162*c8dee2aaSAndroid Build Coastguard Worker     // In the fast variant we think of the midpoint of the integral texture as aligning with the
163*c8dee2aaSAndroid Build Coastguard Worker     // closest rect edge both in x and y. To simplify texture coord calculation we inset the rect so
164*c8dee2aaSAndroid Build Coastguard Worker     // that the edge of the inset rect corresponds to t = 0 in the texture. It actually simplifies
165*c8dee2aaSAndroid Build Coastguard Worker     // things a bit in the !isFast case, too.
166*c8dee2aaSAndroid Build Coastguard Worker     const float threeSigma = 3.0f * devSigma;
167*c8dee2aaSAndroid Build Coastguard Worker     const Rect shapeData = Rect(devRect.left() + threeSigma,
168*c8dee2aaSAndroid Build Coastguard Worker                                 devRect.top() + threeSigma,
169*c8dee2aaSAndroid Build Coastguard Worker                                 devRect.right() - threeSigma,
170*c8dee2aaSAndroid Build Coastguard Worker                                 devRect.bottom() - threeSigma);
171*c8dee2aaSAndroid Build Coastguard Worker 
172*c8dee2aaSAndroid Build Coastguard Worker     // In our fast variant we find the nearest horizontal and vertical edges and for each do a
173*c8dee2aaSAndroid Build Coastguard Worker     // lookup in the integral texture for each and multiply them. When the rect is less than 6*sigma
174*c8dee2aaSAndroid Build Coastguard Worker     // wide then things aren't so simple and we have to consider both the left and right edge of the
175*c8dee2aaSAndroid Build Coastguard Worker     // rectangle (and similar in y).
176*c8dee2aaSAndroid Build Coastguard Worker     const bool isFast = shapeData.left() <= shapeData.right() && shapeData.top() <= shapeData.bot();
177*c8dee2aaSAndroid Build Coastguard Worker 
178*c8dee2aaSAndroid Build Coastguard Worker     const float invSixSigma = 1.0f / sixSigma;
179*c8dee2aaSAndroid Build Coastguard Worker 
180*c8dee2aaSAndroid Build Coastguard Worker     // Determine how much to outset the draw bounds to ensure we hit pixels within 3*sigma.
181*c8dee2aaSAndroid Build Coastguard Worker     std::optional<Rect> drawBounds = outset_bounds(localToDevice, devSigma, srcRect);
182*c8dee2aaSAndroid Build Coastguard Worker     if (!drawBounds) {
183*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
184*c8dee2aaSAndroid Build Coastguard Worker     }
185*c8dee2aaSAndroid Build Coastguard Worker 
186*c8dee2aaSAndroid Build Coastguard Worker     return AnalyticBlurMask(*drawBounds,
187*c8dee2aaSAndroid Build Coastguard Worker                             SkM44(devToScaledShape),
188*c8dee2aaSAndroid Build Coastguard Worker                             ShapeType::kRect,
189*c8dee2aaSAndroid Build Coastguard Worker                             shapeData,
190*c8dee2aaSAndroid Build Coastguard Worker                             {static_cast<float>(isFast), invSixSigma},
191*c8dee2aaSAndroid Build Coastguard Worker                             integral);
192*c8dee2aaSAndroid Build Coastguard Worker }
193*c8dee2aaSAndroid Build Coastguard Worker 
quantize(float deviceSpaceFloat)194*c8dee2aaSAndroid Build Coastguard Worker static float quantize(float deviceSpaceFloat) {
195*c8dee2aaSAndroid Build Coastguard Worker     // Snap the device-space value to the nearest 1/32 to increase cache hits w/o impacting the
196*c8dee2aaSAndroid Build Coastguard Worker     // visible output since it should be hard to see a change limited to 1/32 of a pixel.
197*c8dee2aaSAndroid Build Coastguard Worker     return SkScalarRoundToInt(deviceSpaceFloat * 32.f) / 32.f;
198*c8dee2aaSAndroid Build Coastguard Worker }
199*c8dee2aaSAndroid Build Coastguard Worker 
MakeCircle(Recorder * recorder,const SkMatrix & localToDevice,float devSigma,const SkRect & srcRect,const SkRect & devRect)200*c8dee2aaSAndroid Build Coastguard Worker std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeCircle(Recorder* recorder,
201*c8dee2aaSAndroid Build Coastguard Worker                                                              const SkMatrix& localToDevice,
202*c8dee2aaSAndroid Build Coastguard Worker                                                              float devSigma,
203*c8dee2aaSAndroid Build Coastguard Worker                                                              const SkRect& srcRect,
204*c8dee2aaSAndroid Build Coastguard Worker                                                              const SkRect& devRect) {
205*c8dee2aaSAndroid Build Coastguard Worker     const float radius = devRect.width() / 2.0f;
206*c8dee2aaSAndroid Build Coastguard Worker     if (!SkIsFinite(radius) || radius < SK_ScalarNearlyZero) {
207*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
208*c8dee2aaSAndroid Build Coastguard Worker     }
209*c8dee2aaSAndroid Build Coastguard Worker 
210*c8dee2aaSAndroid Build Coastguard Worker     // Pack profile-dependent properties and derived values into a struct that can be passed into
211*c8dee2aaSAndroid Build Coastguard Worker     // findOrCreateCachedProxy to lazily invoke the profile creation bitmap factories.
212*c8dee2aaSAndroid Build Coastguard Worker     struct DerivedParams {
213*c8dee2aaSAndroid Build Coastguard Worker         float fQuantizedRadius;
214*c8dee2aaSAndroid Build Coastguard Worker         float fQuantizedDevSigma;
215*c8dee2aaSAndroid Build Coastguard Worker 
216*c8dee2aaSAndroid Build Coastguard Worker         float fSolidRadius;
217*c8dee2aaSAndroid Build Coastguard Worker         float fTextureRadius;
218*c8dee2aaSAndroid Build Coastguard Worker 
219*c8dee2aaSAndroid Build Coastguard Worker         bool  fUseHalfPlaneApprox;
220*c8dee2aaSAndroid Build Coastguard Worker 
221*c8dee2aaSAndroid Build Coastguard Worker         DerivedParams(float devSigma, float radius)
222*c8dee2aaSAndroid Build Coastguard Worker                 : fQuantizedRadius(quantize(radius))
223*c8dee2aaSAndroid Build Coastguard Worker                 , fQuantizedDevSigma(quantize(devSigma)) {
224*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(fQuantizedRadius > 0.f); // quantization shouldn't have rounded to 0
225*c8dee2aaSAndroid Build Coastguard Worker 
226*c8dee2aaSAndroid Build Coastguard Worker             // When sigma is really small this becomes a equivalent to convolving a Gaussian with a
227*c8dee2aaSAndroid Build Coastguard Worker             // half-plane. Similarly, in the extreme high ratio cases circle becomes a point WRT to
228*c8dee2aaSAndroid Build Coastguard Worker             // the Guassian and the profile texture is a just a Gaussian evaluation. However, we
229*c8dee2aaSAndroid Build Coastguard Worker             // haven't yet implemented this latter optimization.
230*c8dee2aaSAndroid Build Coastguard Worker             constexpr float kHalfPlaneThreshold = 0.1f;
231*c8dee2aaSAndroid Build Coastguard Worker             const float sigmaToRadiusRatio = std::min(fQuantizedDevSigma / fQuantizedRadius, 8.0f);
232*c8dee2aaSAndroid Build Coastguard Worker             if (sigmaToRadiusRatio <= kHalfPlaneThreshold) {
233*c8dee2aaSAndroid Build Coastguard Worker                 fUseHalfPlaneApprox = true;
234*c8dee2aaSAndroid Build Coastguard Worker                 fSolidRadius = fQuantizedRadius - 3.0f * fQuantizedDevSigma;
235*c8dee2aaSAndroid Build Coastguard Worker                 fTextureRadius = 6.0f * fQuantizedDevSigma;
236*c8dee2aaSAndroid Build Coastguard Worker             } else {
237*c8dee2aaSAndroid Build Coastguard Worker                 fUseHalfPlaneApprox = false;
238*c8dee2aaSAndroid Build Coastguard Worker                 fQuantizedDevSigma = fQuantizedRadius * sigmaToRadiusRatio;
239*c8dee2aaSAndroid Build Coastguard Worker                 fSolidRadius = 0.0f;
240*c8dee2aaSAndroid Build Coastguard Worker                 fTextureRadius = fQuantizedRadius + 3.0f * fQuantizedDevSigma;
241*c8dee2aaSAndroid Build Coastguard Worker             }
242*c8dee2aaSAndroid Build Coastguard Worker         }
243*c8dee2aaSAndroid Build Coastguard Worker     } params{devSigma, radius};
244*c8dee2aaSAndroid Build Coastguard Worker 
245*c8dee2aaSAndroid Build Coastguard Worker     UniqueKey key;
246*c8dee2aaSAndroid Build Coastguard Worker     {
247*c8dee2aaSAndroid Build Coastguard Worker         static const UniqueKey::Domain kCircleBlurDomain = UniqueKey::GenerateDomain();
248*c8dee2aaSAndroid Build Coastguard Worker         UniqueKey::Builder builder(&key, kCircleBlurDomain, 2, "BlurredCircleIntegralTable");
249*c8dee2aaSAndroid Build Coastguard Worker         if (params.fUseHalfPlaneApprox) {
250*c8dee2aaSAndroid Build Coastguard Worker             // There only ever needs to be one half plane approximation table, so store {0,0} into
251*c8dee2aaSAndroid Build Coastguard Worker             // the key, which never arises under normal use because we reject radius = 0 above.
252*c8dee2aaSAndroid Build Coastguard Worker             builder[0] = SkFloat2Bits(0.f);
253*c8dee2aaSAndroid Build Coastguard Worker             builder[1] = SkFloat2Bits(0.f);
254*c8dee2aaSAndroid Build Coastguard Worker         } else {
255*c8dee2aaSAndroid Build Coastguard Worker             builder[0] = SkFloat2Bits(params.fQuantizedDevSigma);
256*c8dee2aaSAndroid Build Coastguard Worker             builder[1] = SkFloat2Bits(params.fQuantizedRadius);
257*c8dee2aaSAndroid Build Coastguard Worker         }
258*c8dee2aaSAndroid Build Coastguard Worker     }
259*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<TextureProxy> profile = recorder->priv().proxyCache()->findOrCreateCachedProxy(
260*c8dee2aaSAndroid Build Coastguard Worker             recorder, key, &params,
261*c8dee2aaSAndroid Build Coastguard Worker             [](const void* context) {
262*c8dee2aaSAndroid Build Coastguard Worker                 constexpr int kProfileTextureWidth = 512;
263*c8dee2aaSAndroid Build Coastguard Worker                 const DerivedParams* params = static_cast<const DerivedParams*>(context);
264*c8dee2aaSAndroid Build Coastguard Worker                 if (params->fUseHalfPlaneApprox) {
265*c8dee2aaSAndroid Build Coastguard Worker                     return CreateHalfPlaneProfile(kProfileTextureWidth);
266*c8dee2aaSAndroid Build Coastguard Worker                 } else {
267*c8dee2aaSAndroid Build Coastguard Worker                     // Rescale params to the size of the texture we're creating.
268*c8dee2aaSAndroid Build Coastguard Worker                     const float scale = kProfileTextureWidth / params->fTextureRadius;
269*c8dee2aaSAndroid Build Coastguard Worker                     return CreateCircleProfile(params->fQuantizedDevSigma * scale,
270*c8dee2aaSAndroid Build Coastguard Worker                                                params->fQuantizedRadius * scale,
271*c8dee2aaSAndroid Build Coastguard Worker                                                kProfileTextureWidth);
272*c8dee2aaSAndroid Build Coastguard Worker                 }
273*c8dee2aaSAndroid Build Coastguard Worker             });
274*c8dee2aaSAndroid Build Coastguard Worker 
275*c8dee2aaSAndroid Build Coastguard Worker     if (!profile) {
276*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
277*c8dee2aaSAndroid Build Coastguard Worker     }
278*c8dee2aaSAndroid Build Coastguard Worker 
279*c8dee2aaSAndroid Build Coastguard Worker     // In the shader we calculate an index into the blur profile
280*c8dee2aaSAndroid Build Coastguard Worker     // "i = (length(fragCoords - circleCenter) - solidRadius + 0.5) / textureRadius" as
281*c8dee2aaSAndroid Build Coastguard Worker     // "i = length((fragCoords - circleCenter) / textureRadius) -
282*c8dee2aaSAndroid Build Coastguard Worker     //      (solidRadius - 0.5) / textureRadius"
283*c8dee2aaSAndroid Build Coastguard Worker     // to avoid passing large values to length() that would overflow. We precalculate
284*c8dee2aaSAndroid Build Coastguard Worker     // "1 / textureRadius" and "(solidRadius - 0.5) / textureRadius" here.
285*c8dee2aaSAndroid Build Coastguard Worker     const Rect shapeData = Rect(devRect.centerX(),
286*c8dee2aaSAndroid Build Coastguard Worker                                 devRect.centerY(),
287*c8dee2aaSAndroid Build Coastguard Worker                                 1.0f / params.fTextureRadius,
288*c8dee2aaSAndroid Build Coastguard Worker                                 (params.fSolidRadius - 0.5f) / params.fTextureRadius);
289*c8dee2aaSAndroid Build Coastguard Worker 
290*c8dee2aaSAndroid Build Coastguard Worker     // Determine how much to outset the draw bounds to ensure we hit pixels within 3*sigma.
291*c8dee2aaSAndroid Build Coastguard Worker     std::optional<Rect> drawBounds = outset_bounds(localToDevice,
292*c8dee2aaSAndroid Build Coastguard Worker                                                    params.fQuantizedDevSigma,
293*c8dee2aaSAndroid Build Coastguard Worker                                                    srcRect);
294*c8dee2aaSAndroid Build Coastguard Worker     if (!drawBounds) {
295*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
296*c8dee2aaSAndroid Build Coastguard Worker     }
297*c8dee2aaSAndroid Build Coastguard Worker 
298*c8dee2aaSAndroid Build Coastguard Worker     constexpr float kUnusedBlurData = 0.0f;
299*c8dee2aaSAndroid Build Coastguard Worker     return AnalyticBlurMask(*drawBounds,
300*c8dee2aaSAndroid Build Coastguard Worker                             SkM44(),
301*c8dee2aaSAndroid Build Coastguard Worker                             ShapeType::kCircle,
302*c8dee2aaSAndroid Build Coastguard Worker                             shapeData,
303*c8dee2aaSAndroid Build Coastguard Worker                             {kUnusedBlurData, kUnusedBlurData},
304*c8dee2aaSAndroid Build Coastguard Worker                             profile);
305*c8dee2aaSAndroid Build Coastguard Worker }
306*c8dee2aaSAndroid Build Coastguard Worker 
MakeRRect(Recorder * recorder,const SkMatrix & localToDevice,float devSigma,const SkRRect & srcRRect,const SkRRect & devRRect)307*c8dee2aaSAndroid Build Coastguard Worker std::optional<AnalyticBlurMask> AnalyticBlurMask::MakeRRect(Recorder* recorder,
308*c8dee2aaSAndroid Build Coastguard Worker                                                             const SkMatrix& localToDevice,
309*c8dee2aaSAndroid Build Coastguard Worker                                                             float devSigma,
310*c8dee2aaSAndroid Build Coastguard Worker                                                             const SkRRect& srcRRect,
311*c8dee2aaSAndroid Build Coastguard Worker                                                             const SkRRect& devRRect) {
312*c8dee2aaSAndroid Build Coastguard Worker     const int devBlurRadius = 3 * SkScalarCeilToInt(devSigma - 1.0f / 6.0f);
313*c8dee2aaSAndroid Build Coastguard Worker 
314*c8dee2aaSAndroid Build Coastguard Worker     const SkVector& devRadiiUL = devRRect.radii(SkRRect::kUpperLeft_Corner);
315*c8dee2aaSAndroid Build Coastguard Worker     const SkVector& devRadiiUR = devRRect.radii(SkRRect::kUpperRight_Corner);
316*c8dee2aaSAndroid Build Coastguard Worker     const SkVector& devRadiiLR = devRRect.radii(SkRRect::kLowerRight_Corner);
317*c8dee2aaSAndroid Build Coastguard Worker     const SkVector& devRadiiLL = devRRect.radii(SkRRect::kLowerLeft_Corner);
318*c8dee2aaSAndroid Build Coastguard Worker 
319*c8dee2aaSAndroid Build Coastguard Worker     const int devLeft = SkScalarCeilToInt(std::max<float>(devRadiiUL.fX, devRadiiLL.fX));
320*c8dee2aaSAndroid Build Coastguard Worker     const int devTop = SkScalarCeilToInt(std::max<float>(devRadiiUL.fY, devRadiiUR.fY));
321*c8dee2aaSAndroid Build Coastguard Worker     const int devRight = SkScalarCeilToInt(std::max<float>(devRadiiUR.fX, devRadiiLR.fX));
322*c8dee2aaSAndroid Build Coastguard Worker     const int devBot = SkScalarCeilToInt(std::max<float>(devRadiiLL.fY, devRadiiLR.fY));
323*c8dee2aaSAndroid Build Coastguard Worker 
324*c8dee2aaSAndroid Build Coastguard Worker     // This is a conservative check for nine-patchability.
325*c8dee2aaSAndroid Build Coastguard Worker     const SkRect& devOrig = devRRect.getBounds();
326*c8dee2aaSAndroid Build Coastguard Worker     if (devOrig.fLeft + devLeft + devBlurRadius >= devOrig.fRight - devRight - devBlurRadius ||
327*c8dee2aaSAndroid Build Coastguard Worker         devOrig.fTop + devTop + devBlurRadius >= devOrig.fBottom - devBot - devBlurRadius) {
328*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
329*c8dee2aaSAndroid Build Coastguard Worker     }
330*c8dee2aaSAndroid Build Coastguard Worker 
331*c8dee2aaSAndroid Build Coastguard Worker     const int newRRWidth = 2 * devBlurRadius + devLeft + devRight + 1;
332*c8dee2aaSAndroid Build Coastguard Worker     const int newRRHeight = 2 * devBlurRadius + devTop + devBot + 1;
333*c8dee2aaSAndroid Build Coastguard Worker 
334*c8dee2aaSAndroid Build Coastguard Worker     const SkRect newRect = SkRect::MakeXYWH(SkIntToScalar(devBlurRadius),
335*c8dee2aaSAndroid Build Coastguard Worker                                             SkIntToScalar(devBlurRadius),
336*c8dee2aaSAndroid Build Coastguard Worker                                             SkIntToScalar(newRRWidth),
337*c8dee2aaSAndroid Build Coastguard Worker                                             SkIntToScalar(newRRHeight));
338*c8dee2aaSAndroid Build Coastguard Worker     SkVector newRadii[4];
339*c8dee2aaSAndroid Build Coastguard Worker     newRadii[0] = {SkScalarCeilToScalar(devRadiiUL.fX), SkScalarCeilToScalar(devRadiiUL.fY)};
340*c8dee2aaSAndroid Build Coastguard Worker     newRadii[1] = {SkScalarCeilToScalar(devRadiiUR.fX), SkScalarCeilToScalar(devRadiiUR.fY)};
341*c8dee2aaSAndroid Build Coastguard Worker     newRadii[2] = {SkScalarCeilToScalar(devRadiiLR.fX), SkScalarCeilToScalar(devRadiiLR.fY)};
342*c8dee2aaSAndroid Build Coastguard Worker     newRadii[3] = {SkScalarCeilToScalar(devRadiiLL.fX), SkScalarCeilToScalar(devRadiiLL.fY)};
343*c8dee2aaSAndroid Build Coastguard Worker 
344*c8dee2aaSAndroid Build Coastguard Worker     // NOTE: SkRRect does not satisfy std::has_unique_object_representation because NaN's in float
345*c8dee2aaSAndroid Build Coastguard Worker     // values violate that, but all SkRRects that get here will be finite so it's not really a
346*c8dee2aaSAndroid Build Coastguard Worker     // an issue for hashing the data directly.
347*c8dee2aaSAndroid Build Coastguard Worker     SK_BEGIN_REQUIRE_DENSE
348*c8dee2aaSAndroid Build Coastguard Worker     struct DerivedParams {
349*c8dee2aaSAndroid Build Coastguard Worker         SkRRect fRRectToDraw;
350*c8dee2aaSAndroid Build Coastguard Worker         SkISize fDimensions;
351*c8dee2aaSAndroid Build Coastguard Worker         float   fDevSigma;
352*c8dee2aaSAndroid Build Coastguard Worker     } params;
353*c8dee2aaSAndroid Build Coastguard Worker     SK_END_REQUIRE_DENSE
354*c8dee2aaSAndroid Build Coastguard Worker 
355*c8dee2aaSAndroid Build Coastguard Worker     params.fRRectToDraw.setRectRadii(newRect, newRadii);
356*c8dee2aaSAndroid Build Coastguard Worker     params.fDimensions =
357*c8dee2aaSAndroid Build Coastguard Worker             SkISize::Make(newRRWidth + 2 * devBlurRadius, newRRHeight + 2 * devBlurRadius);
358*c8dee2aaSAndroid Build Coastguard Worker     params.fDevSigma = devSigma;
359*c8dee2aaSAndroid Build Coastguard Worker 
360*c8dee2aaSAndroid Build Coastguard Worker     // TODO(b/343684954, b/338032240): This is just generating a blurred rrect mask image on the CPU
361*c8dee2aaSAndroid Build Coastguard Worker     // and uploading it. We should either generate them on the GPU and cache them here, or if we
362*c8dee2aaSAndroid Build Coastguard Worker     // have a general-purpose blur mask cache, then there's no reason rrects couldn't just use that
363*c8dee2aaSAndroid Build Coastguard Worker     // since this "analytic" blur isn't actually simplifying work like the circle and rect case.
364*c8dee2aaSAndroid Build Coastguard Worker     // That would also allow us to support arbitrary blurred rrects and not just ninepatch rrects.
365*c8dee2aaSAndroid Build Coastguard Worker     static const UniqueKey::Domain kRRectBlurDomain = UniqueKey::GenerateDomain();
366*c8dee2aaSAndroid Build Coastguard Worker     UniqueKey key;
367*c8dee2aaSAndroid Build Coastguard Worker     {
368*c8dee2aaSAndroid Build Coastguard Worker         static constexpr int kKeySize = sizeof(DerivedParams) / sizeof(uint32_t);
369*c8dee2aaSAndroid Build Coastguard Worker         static_assert(SkIsAlign4(sizeof(DerivedParams)));
370*c8dee2aaSAndroid Build Coastguard Worker         // TODO: We should discretize the sigma to perceptibly meaningful changes to the table,
371*c8dee2aaSAndroid Build Coastguard Worker         // as well as the underlying the round rect geometry.
372*c8dee2aaSAndroid Build Coastguard Worker         UniqueKey::Builder builder(&key, kRRectBlurDomain, kKeySize, "BlurredRRectNinePatch");
373*c8dee2aaSAndroid Build Coastguard Worker         memcpy(&builder[0], &params, sizeof(DerivedParams));
374*c8dee2aaSAndroid Build Coastguard Worker     }
375*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<TextureProxy> ninePatch = recorder->priv().proxyCache()->findOrCreateCachedProxy(
376*c8dee2aaSAndroid Build Coastguard Worker             recorder, key, &params,
377*c8dee2aaSAndroid Build Coastguard Worker             [](const void* context) {
378*c8dee2aaSAndroid Build Coastguard Worker                 const DerivedParams* params = static_cast<const DerivedParams*>(context);
379*c8dee2aaSAndroid Build Coastguard Worker                 return CreateRRectBlurMask(params->fRRectToDraw,
380*c8dee2aaSAndroid Build Coastguard Worker                                            params->fDimensions,
381*c8dee2aaSAndroid Build Coastguard Worker                                            params->fDevSigma);
382*c8dee2aaSAndroid Build Coastguard Worker             });
383*c8dee2aaSAndroid Build Coastguard Worker 
384*c8dee2aaSAndroid Build Coastguard Worker     if (!ninePatch) {
385*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
386*c8dee2aaSAndroid Build Coastguard Worker     }
387*c8dee2aaSAndroid Build Coastguard Worker 
388*c8dee2aaSAndroid Build Coastguard Worker     const float blurRadius = 3.0f * SkScalarCeilToScalar(devSigma - 1.0f / 6.0f);
389*c8dee2aaSAndroid Build Coastguard Worker     const float edgeSize = 2.0f * blurRadius + SkRRectPriv::GetSimpleRadii(devRRect).fX + 0.5f;
390*c8dee2aaSAndroid Build Coastguard Worker     const Rect shapeData = devRRect.rect().makeOutset(blurRadius, blurRadius);
391*c8dee2aaSAndroid Build Coastguard Worker 
392*c8dee2aaSAndroid Build Coastguard Worker     // Determine how much to outset the draw bounds to ensure we hit pixels within 3*sigma.
393*c8dee2aaSAndroid Build Coastguard Worker     std::optional<Rect> drawBounds = outset_bounds(localToDevice, devSigma, srcRRect.rect());
394*c8dee2aaSAndroid Build Coastguard Worker     if (!drawBounds) {
395*c8dee2aaSAndroid Build Coastguard Worker         return std::nullopt;
396*c8dee2aaSAndroid Build Coastguard Worker     }
397*c8dee2aaSAndroid Build Coastguard Worker 
398*c8dee2aaSAndroid Build Coastguard Worker     constexpr float kUnusedBlurData = 0.0f;
399*c8dee2aaSAndroid Build Coastguard Worker     return AnalyticBlurMask(*drawBounds,
400*c8dee2aaSAndroid Build Coastguard Worker                             SkM44(),
401*c8dee2aaSAndroid Build Coastguard Worker                             ShapeType::kRRect,
402*c8dee2aaSAndroid Build Coastguard Worker                             shapeData,
403*c8dee2aaSAndroid Build Coastguard Worker                             {edgeSize, kUnusedBlurData},
404*c8dee2aaSAndroid Build Coastguard Worker                             ninePatch);
405*c8dee2aaSAndroid Build Coastguard Worker }
406*c8dee2aaSAndroid Build Coastguard Worker 
407*c8dee2aaSAndroid Build Coastguard Worker }  // namespace skgpu::graphite
408