xref: /aosp_15_r20/external/skia/src/gpu/graphite/render/CoverageMaskRenderStep.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2023 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 #include "src/gpu/graphite/render/CoverageMaskRenderStep.h"
8*c8dee2aaSAndroid Build Coastguard Worker 
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkM44.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkMatrix.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSamplingOptions.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTileMode.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkDebug.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "src/base/SkEnumBitMask.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkSLTypeShared.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/BufferWriter.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/Attribute.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/ContextUtils.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawOrder.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawParams.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawTypes.h"
26*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/DrawWriter.h"
27*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/PipelineData.h"
28*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/TextureProxy.h"
29*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/CoverageMaskShape.h"
30*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Geometry.h"
31*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Rect.h"
32*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/geom/Transform_graphite.h"
33*c8dee2aaSAndroid Build Coastguard Worker #include "src/gpu/graphite/render/CommonDepthStencilSettings.h"
34*c8dee2aaSAndroid Build Coastguard Worker 
35*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
36*c8dee2aaSAndroid Build Coastguard Worker #include <string_view>
37*c8dee2aaSAndroid Build Coastguard Worker 
38*c8dee2aaSAndroid Build Coastguard Worker namespace skgpu::graphite {
39*c8dee2aaSAndroid Build Coastguard Worker 
40*c8dee2aaSAndroid Build Coastguard Worker // The device origin is applied *before* the maskToDeviceRemainder matrix so that it can be
41*c8dee2aaSAndroid Build Coastguard Worker // combined with the mask atlas origin. This is necessary so that the mask bounds can be inset or
42*c8dee2aaSAndroid Build Coastguard Worker // outset for clamping w/o affecting the alignment of the mask sampling.
get_device_translation(const SkM44 & localToDevice)43*c8dee2aaSAndroid Build Coastguard Worker static skvx::float2 get_device_translation(const SkM44& localToDevice) {
44*c8dee2aaSAndroid Build Coastguard Worker     float m00 = localToDevice.rc(0,0), m01 = localToDevice.rc(0,1);
45*c8dee2aaSAndroid Build Coastguard Worker     float m10 = localToDevice.rc(1,0), m11 = localToDevice.rc(1,1);
46*c8dee2aaSAndroid Build Coastguard Worker 
47*c8dee2aaSAndroid Build Coastguard Worker     float det = m00*m11 - m01*m10;
48*c8dee2aaSAndroid Build Coastguard Worker     if (SkScalarNearlyZero(det)) {
49*c8dee2aaSAndroid Build Coastguard Worker         // We can't extract any pre-translation, since the upper 2x2 is not invertible. Return (0,0)
50*c8dee2aaSAndroid Build Coastguard Worker         // so that the maskToDeviceRemainder matrix remains the full transform.
51*c8dee2aaSAndroid Build Coastguard Worker         return {0.f, 0.f};
52*c8dee2aaSAndroid Build Coastguard Worker     }
53*c8dee2aaSAndroid Build Coastguard Worker 
54*c8dee2aaSAndroid Build Coastguard Worker     // Calculate inv([[m00,m01][m10,m11]])*[[m30][m31]] to get the pre-remainder device translation.
55*c8dee2aaSAndroid Build Coastguard Worker     float tx = localToDevice.rc(0,3), ty = localToDevice.rc(1,3);
56*c8dee2aaSAndroid Build Coastguard Worker     skvx::float4 invT = skvx::float4{m11, -m10, -m01, m00} * skvx::float4{tx,tx,ty,ty};
57*c8dee2aaSAndroid Build Coastguard Worker     return (invT.xy() + invT.zw()) / det;
58*c8dee2aaSAndroid Build Coastguard Worker }
59*c8dee2aaSAndroid Build Coastguard Worker 
CoverageMaskRenderStep()60*c8dee2aaSAndroid Build Coastguard Worker CoverageMaskRenderStep::CoverageMaskRenderStep()
61*c8dee2aaSAndroid Build Coastguard Worker         : RenderStep("CoverageMaskRenderStep",
62*c8dee2aaSAndroid Build Coastguard Worker                      "",
63*c8dee2aaSAndroid Build Coastguard Worker                      // The mask will have AA outsets baked in, but the original bounds for clipping
64*c8dee2aaSAndroid Build Coastguard Worker                      // still require the outset for analytic coverage.
65*c8dee2aaSAndroid Build Coastguard Worker                      Flags::kPerformsShading | Flags::kHasTextures | Flags::kEmitsCoverage |
66*c8dee2aaSAndroid Build Coastguard Worker                      Flags::kOutsetBoundsForAA,
67*c8dee2aaSAndroid Build Coastguard Worker                      /*uniforms=*/{{"maskToDeviceRemainder", SkSLType::kFloat3x3}},
68*c8dee2aaSAndroid Build Coastguard Worker                      PrimitiveType::kTriangleStrip,
69*c8dee2aaSAndroid Build Coastguard Worker                      kDirectDepthGreaterPass,
70*c8dee2aaSAndroid Build Coastguard Worker                      /*vertexAttrs=*/{},
71*c8dee2aaSAndroid Build Coastguard Worker                      /*instanceAttrs=*/
72*c8dee2aaSAndroid Build Coastguard Worker                      // Draw bounds and mask bounds are in normalized relative to the mask texture,
73*c8dee2aaSAndroid Build Coastguard Worker                      // but 'drawBounds' is stored in float since the coords may map outside of
74*c8dee2aaSAndroid Build Coastguard Worker                      // [0,1] for inverse-filled masks. 'drawBounds' is relative to the logical mask
75*c8dee2aaSAndroid Build Coastguard Worker                      // entry's origin, while 'maskBoundsIn' is atlas-relative. Inverse fills swap
76*c8dee2aaSAndroid Build Coastguard Worker                      // the order in 'maskBoundsIn' to be RBLT.
77*c8dee2aaSAndroid Build Coastguard Worker                      {{"drawBounds", VertexAttribType::kFloat4 , SkSLType::kFloat4},  // ltrb
78*c8dee2aaSAndroid Build Coastguard Worker                       {"maskBoundsIn", VertexAttribType::kUShort4_norm, SkSLType::kFloat4},
79*c8dee2aaSAndroid Build Coastguard Worker                       // Remaining translation extracted from actual 'maskToDevice' transform.
80*c8dee2aaSAndroid Build Coastguard Worker                       {"deviceOrigin", VertexAttribType::kFloat2, SkSLType::kFloat2},
81*c8dee2aaSAndroid Build Coastguard Worker                       {"depth"     , VertexAttribType::kFloat, SkSLType::kFloat},
82*c8dee2aaSAndroid Build Coastguard Worker                       {"ssboIndices", VertexAttribType::kUInt2, SkSLType::kUInt2},
83*c8dee2aaSAndroid Build Coastguard Worker                       // deviceToLocal matrix for producing local coords for shader evaluation
84*c8dee2aaSAndroid Build Coastguard Worker                       {"mat0", VertexAttribType::kFloat3, SkSLType::kFloat3},
85*c8dee2aaSAndroid Build Coastguard Worker                       {"mat1", VertexAttribType::kFloat3, SkSLType::kFloat3},
86*c8dee2aaSAndroid Build Coastguard Worker                       {"mat2", VertexAttribType::kFloat3, SkSLType::kFloat3}},
87*c8dee2aaSAndroid Build Coastguard Worker                      /*varyings=*/
88*c8dee2aaSAndroid Build Coastguard Worker                      {// `maskBounds` are the atlas-relative, sorted bounds of the coverage mask.
89*c8dee2aaSAndroid Build Coastguard Worker                       // `textureCoords` are the atlas-relative UV coordinates of the draw, which
90*c8dee2aaSAndroid Build Coastguard Worker                       // can spill beyond `maskBounds` for inverse fills.
91*c8dee2aaSAndroid Build Coastguard Worker                       // TODO: maskBounds is constant for all fragments for a given instance,
92*c8dee2aaSAndroid Build Coastguard Worker                       // could we store them in the draw's SSBO?
93*c8dee2aaSAndroid Build Coastguard Worker                       {"maskBounds"   , SkSLType::kFloat4},
94*c8dee2aaSAndroid Build Coastguard Worker                       {"textureCoords", SkSLType::kFloat2},
95*c8dee2aaSAndroid Build Coastguard Worker                       // 'invert' is set to 0 use unmodified coverage, and set to 1 for "1-c".
96*c8dee2aaSAndroid Build Coastguard Worker                       {"invert", SkSLType::kHalf}}) {}
97*c8dee2aaSAndroid Build Coastguard Worker 
vertexSkSL() const98*c8dee2aaSAndroid Build Coastguard Worker std::string CoverageMaskRenderStep::vertexSkSL() const {
99*c8dee2aaSAndroid Build Coastguard Worker     // Returns the body of a vertex function, which must define a float4 devPosition variable and
100*c8dee2aaSAndroid Build Coastguard Worker     // must write to an already-defined float2 stepLocalCoords variable.
101*c8dee2aaSAndroid Build Coastguard Worker     return "float4 devPosition = coverage_mask_vertex_fn("
102*c8dee2aaSAndroid Build Coastguard Worker                     "float2(sk_VertexID >> 1, sk_VertexID & 1), "
103*c8dee2aaSAndroid Build Coastguard Worker                     "maskToDeviceRemainder, drawBounds, maskBoundsIn, deviceOrigin, "
104*c8dee2aaSAndroid Build Coastguard Worker                     "depth, float3x3(mat0, mat1, mat2), "
105*c8dee2aaSAndroid Build Coastguard Worker                     "maskBounds, textureCoords, invert, stepLocalCoords);\n";
106*c8dee2aaSAndroid Build Coastguard Worker }
107*c8dee2aaSAndroid Build Coastguard Worker 
texturesAndSamplersSkSL(const ResourceBindingRequirements & bindingReqs,int * nextBindingIndex) const108*c8dee2aaSAndroid Build Coastguard Worker std::string CoverageMaskRenderStep::texturesAndSamplersSkSL(
109*c8dee2aaSAndroid Build Coastguard Worker         const ResourceBindingRequirements& bindingReqs, int* nextBindingIndex) const {
110*c8dee2aaSAndroid Build Coastguard Worker     return EmitSamplerLayout(bindingReqs, nextBindingIndex) + " sampler2D pathAtlas;";
111*c8dee2aaSAndroid Build Coastguard Worker }
112*c8dee2aaSAndroid Build Coastguard Worker 
fragmentCoverageSkSL() const113*c8dee2aaSAndroid Build Coastguard Worker const char* CoverageMaskRenderStep::fragmentCoverageSkSL() const {
114*c8dee2aaSAndroid Build Coastguard Worker     return R"(
115*c8dee2aaSAndroid Build Coastguard Worker         half c = sample(pathAtlas, clamp(textureCoords, maskBounds.LT, maskBounds.RB)).r;
116*c8dee2aaSAndroid Build Coastguard Worker         outputCoverage = half4(mix(c, 1 - c, invert));
117*c8dee2aaSAndroid Build Coastguard Worker     )";
118*c8dee2aaSAndroid Build Coastguard Worker }
119*c8dee2aaSAndroid Build Coastguard Worker 
writeVertices(DrawWriter * dw,const DrawParams & params,skvx::uint2 ssboIndices) const120*c8dee2aaSAndroid Build Coastguard Worker void CoverageMaskRenderStep::writeVertices(DrawWriter* dw,
121*c8dee2aaSAndroid Build Coastguard Worker                                            const DrawParams& params,
122*c8dee2aaSAndroid Build Coastguard Worker                                            skvx::uint2 ssboIndices) const {
123*c8dee2aaSAndroid Build Coastguard Worker     const CoverageMaskShape& coverageMask = params.geometry().coverageMaskShape();
124*c8dee2aaSAndroid Build Coastguard Worker     const TextureProxy* proxy = coverageMask.textureProxy();
125*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(proxy);
126*c8dee2aaSAndroid Build Coastguard Worker 
127*c8dee2aaSAndroid Build Coastguard Worker     // A quad is a 4-vertex instance. The coordinates are derived from the vertex IDs.
128*c8dee2aaSAndroid Build Coastguard Worker     DrawWriter::Instances instances(*dw, {}, {}, 4);
129*c8dee2aaSAndroid Build Coastguard Worker 
130*c8dee2aaSAndroid Build Coastguard Worker     // The device origin is the  translation extracted from the mask-to-device matrix so
131*c8dee2aaSAndroid Build Coastguard Worker     // that the remaining matrix uniform has less variance between draws.
132*c8dee2aaSAndroid Build Coastguard Worker     const auto& maskToDevice = params.transform().matrix();
133*c8dee2aaSAndroid Build Coastguard Worker     skvx::float2 deviceOrigin = get_device_translation(maskToDevice);
134*c8dee2aaSAndroid Build Coastguard Worker 
135*c8dee2aaSAndroid Build Coastguard Worker     // Relative to mask space (device origin and mask-to-device remainder must be applied in shader)
136*c8dee2aaSAndroid Build Coastguard Worker     skvx::float4 maskBounds = coverageMask.bounds().ltrb();
137*c8dee2aaSAndroid Build Coastguard Worker     skvx::float4 drawBounds;
138*c8dee2aaSAndroid Build Coastguard Worker 
139*c8dee2aaSAndroid Build Coastguard Worker     if (coverageMask.inverted()) {
140*c8dee2aaSAndroid Build Coastguard Worker         // Only mask filters trigger complex transforms, and they are never inverse filled. Since
141*c8dee2aaSAndroid Build Coastguard Worker         // we know this is an inverted mask, then we can exactly map the draw's clip bounds to mask
142*c8dee2aaSAndroid Build Coastguard Worker         // space so that the clip is still fully covered without branching in the vertex shader.
143*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(maskToDevice == SkM44::Translate(deviceOrigin.x(), deviceOrigin.y()));
144*c8dee2aaSAndroid Build Coastguard Worker         drawBounds = params.clip().drawBounds().makeOffset(-deviceOrigin).ltrb();
145*c8dee2aaSAndroid Build Coastguard Worker 
146*c8dee2aaSAndroid Build Coastguard Worker         // If the mask is fully clipped out, then the shape's mask info should be (0,0,0,0).
147*c8dee2aaSAndroid Build Coastguard Worker         // If it's not fully clipped out, then the mask info should be non-empty.
148*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(!params.clip().transformedShapeBounds().isEmptyNegativeOrNaN() ^
149*c8dee2aaSAndroid Build Coastguard Worker                  all(maskBounds == 0.f));
150*c8dee2aaSAndroid Build Coastguard Worker 
151*c8dee2aaSAndroid Build Coastguard Worker         if (params.clip().transformedShapeBounds().isEmptyNegativeOrNaN()) {
152*c8dee2aaSAndroid Build Coastguard Worker             // The inversion check is strict inequality, so (0,0,0,0) would not be detected. Adjust
153*c8dee2aaSAndroid Build Coastguard Worker             // to (0,0,1/2,1/2) to restrict sampling to the top-left quarter of the top-left pixel,
154*c8dee2aaSAndroid Build Coastguard Worker             // which should have a value of 0 regardless of filtering mode.
155*c8dee2aaSAndroid Build Coastguard Worker             maskBounds = skvx::float4{0.f, 0.f, 0.5f, 0.5f};
156*c8dee2aaSAndroid Build Coastguard Worker         } else {
157*c8dee2aaSAndroid Build Coastguard Worker             // Add 1/2px outset to the mask bounds so that clamped coordinates sample the texel
158*c8dee2aaSAndroid Build Coastguard Worker             // center of the padding around the atlas entry.
159*c8dee2aaSAndroid Build Coastguard Worker             maskBounds += skvx::float4{-0.5f, -0.5f, 0.5f, 0.5f};
160*c8dee2aaSAndroid Build Coastguard Worker         }
161*c8dee2aaSAndroid Build Coastguard Worker 
162*c8dee2aaSAndroid Build Coastguard Worker         // and store RBLT so that the 'maskBoundsIn' attribute has xy > zw to detect inverse fill.
163*c8dee2aaSAndroid Build Coastguard Worker         maskBounds = skvx::shuffle<2,3,0,1>(maskBounds);
164*c8dee2aaSAndroid Build Coastguard Worker     } else {
165*c8dee2aaSAndroid Build Coastguard Worker         // If we aren't inverted, then the originally assigned values don't need to be adjusted, but
166*c8dee2aaSAndroid Build Coastguard Worker         // also ensure the mask isn't empty (otherwise the draw should have been skipped earlier).
167*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(!coverageMask.bounds().isEmptyNegativeOrNaN());
168*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(all(maskBounds.xy() < maskBounds.zw()));
169*c8dee2aaSAndroid Build Coastguard Worker 
170*c8dee2aaSAndroid Build Coastguard Worker         // Since the mask bounds and draw bounds are 1-to-1 with each other, the clamping of texture
171*c8dee2aaSAndroid Build Coastguard Worker         // coords is mostly a formality. We inset the mask bounds by 1/2px so that we clamp to the
172*c8dee2aaSAndroid Build Coastguard Worker         // texel center of the outer row/column of the mask. This should be a no-op for nearest
173*c8dee2aaSAndroid Build Coastguard Worker         // sampling but prevents any linear sampling from incorporating adjacent data; for atlases
174*c8dee2aaSAndroid Build Coastguard Worker         // this would just be 0 but for non-atlas coverage masks that might not have padding this
175*c8dee2aaSAndroid Build Coastguard Worker         // avoids filtering unknown values in an approx-fit texture.
176*c8dee2aaSAndroid Build Coastguard Worker         drawBounds = maskBounds;
177*c8dee2aaSAndroid Build Coastguard Worker         maskBounds -= skvx::float4{-0.5f, -0.5f, 0.5f, 0.5f};
178*c8dee2aaSAndroid Build Coastguard Worker     }
179*c8dee2aaSAndroid Build Coastguard Worker 
180*c8dee2aaSAndroid Build Coastguard Worker     // Move 'drawBounds' and 'maskBounds' into the atlas coordinate space, then adjust the
181*c8dee2aaSAndroid Build Coastguard Worker     // device translation to undo the atlas origin automatically in the vertex shader.
182*c8dee2aaSAndroid Build Coastguard Worker     skvx::float2 textureOrigin = skvx::cast<float>(coverageMask.textureOrigin());
183*c8dee2aaSAndroid Build Coastguard Worker     maskBounds += textureOrigin.xyxy();
184*c8dee2aaSAndroid Build Coastguard Worker     drawBounds += textureOrigin.xyxy();
185*c8dee2aaSAndroid Build Coastguard Worker     deviceOrigin -= textureOrigin;
186*c8dee2aaSAndroid Build Coastguard Worker 
187*c8dee2aaSAndroid Build Coastguard Worker     // Normalize drawBounds and maskBounds after possibly correcting drawBounds for inverse fills.
188*c8dee2aaSAndroid Build Coastguard Worker     // The maskToDevice matrix uniform will handle de-normalizing drawBounds for vertex positions.
189*c8dee2aaSAndroid Build Coastguard Worker     auto atlasSizeInv = skvx::float2{1.f / proxy->dimensions().width(),
190*c8dee2aaSAndroid Build Coastguard Worker                                      1.f / proxy->dimensions().height()};
191*c8dee2aaSAndroid Build Coastguard Worker     drawBounds *= atlasSizeInv.xyxy();
192*c8dee2aaSAndroid Build Coastguard Worker     maskBounds *= atlasSizeInv.xyxy();
193*c8dee2aaSAndroid Build Coastguard Worker     deviceOrigin *= atlasSizeInv;
194*c8dee2aaSAndroid Build Coastguard Worker 
195*c8dee2aaSAndroid Build Coastguard Worker     // Since the mask bounds define normalized texels of the texture, we can encode them as
196*c8dee2aaSAndroid Build Coastguard Worker     // ushort_norm without losing precision to save space.
197*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(all((maskBounds >= 0.f) & (maskBounds <= 1.f)));
198*c8dee2aaSAndroid Build Coastguard Worker     maskBounds = 65535.f * maskBounds + 0.5f;
199*c8dee2aaSAndroid Build Coastguard Worker 
200*c8dee2aaSAndroid Build Coastguard Worker     const SkM44& m = coverageMask.deviceToLocal();
201*c8dee2aaSAndroid Build Coastguard Worker     instances.append(1) << drawBounds << skvx::cast<uint16_t>(maskBounds) << deviceOrigin
202*c8dee2aaSAndroid Build Coastguard Worker                         << params.order().depthAsFloat() << ssboIndices
203*c8dee2aaSAndroid Build Coastguard Worker                         << m.rc(0,0) << m.rc(1,0) << m.rc(3,0)   // mat0
204*c8dee2aaSAndroid Build Coastguard Worker                         << m.rc(0,1) << m.rc(1,1) << m.rc(3,1)   // mat1
205*c8dee2aaSAndroid Build Coastguard Worker                         << m.rc(0,3) << m.rc(1,3) << m.rc(3,3);  // mat2
206*c8dee2aaSAndroid Build Coastguard Worker }
207*c8dee2aaSAndroid Build Coastguard Worker 
writeUniformsAndTextures(const DrawParams & params,PipelineDataGatherer * gatherer) const208*c8dee2aaSAndroid Build Coastguard Worker void CoverageMaskRenderStep::writeUniformsAndTextures(const DrawParams& params,
209*c8dee2aaSAndroid Build Coastguard Worker                                                       PipelineDataGatherer* gatherer) const {
210*c8dee2aaSAndroid Build Coastguard Worker     SkDEBUGCODE(UniformExpectationsValidator uev(gatherer, this->uniforms());)
211*c8dee2aaSAndroid Build Coastguard Worker 
212*c8dee2aaSAndroid Build Coastguard Worker     const CoverageMaskShape& coverageMask = params.geometry().coverageMaskShape();
213*c8dee2aaSAndroid Build Coastguard Worker     const TextureProxy* proxy = coverageMask.textureProxy();
214*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(proxy);
215*c8dee2aaSAndroid Build Coastguard Worker 
216*c8dee2aaSAndroid Build Coastguard Worker     // Most coverage masks are aligned with the device pixels, so the params' transform is an
217*c8dee2aaSAndroid Build Coastguard Worker     // integer translation matrix. This translation is extracted as an instance attribute so that
218*c8dee2aaSAndroid Build Coastguard Worker     // the remaining transform has a much lower frequency of changing (only complex-transformed
219*c8dee2aaSAndroid Build Coastguard Worker     // mask filters).
220*c8dee2aaSAndroid Build Coastguard Worker     skvx::float2 deviceOrigin = get_device_translation(params.transform().matrix());
221*c8dee2aaSAndroid Build Coastguard Worker     SkMatrix maskToDevice = params.transform().matrix().asM33();
222*c8dee2aaSAndroid Build Coastguard Worker     maskToDevice.preTranslate(-deviceOrigin.x(), -deviceOrigin.y());
223*c8dee2aaSAndroid Build Coastguard Worker 
224*c8dee2aaSAndroid Build Coastguard Worker     // The mask coordinates in the vertex shader will be normalized, so scale by the proxy size
225*c8dee2aaSAndroid Build Coastguard Worker     // to get back to Skia's texel-based coords.
226*c8dee2aaSAndroid Build Coastguard Worker     maskToDevice.preScale(proxy->dimensions().width(), proxy->dimensions().height());
227*c8dee2aaSAndroid Build Coastguard Worker 
228*c8dee2aaSAndroid Build Coastguard Worker     // Write uniforms:
229*c8dee2aaSAndroid Build Coastguard Worker     gatherer->write(maskToDevice);
230*c8dee2aaSAndroid Build Coastguard Worker 
231*c8dee2aaSAndroid Build Coastguard Worker     // Write textures and samplers:
232*c8dee2aaSAndroid Build Coastguard Worker     const bool pixelAligned =
233*c8dee2aaSAndroid Build Coastguard Worker             params.transform().type() <= Transform::Type::kSimpleRectStaysRect &&
234*c8dee2aaSAndroid Build Coastguard Worker             params.transform().maxScaleFactor() == 1.f &&
235*c8dee2aaSAndroid Build Coastguard Worker             all(deviceOrigin == floor(deviceOrigin + SK_ScalarNearlyZero));
236*c8dee2aaSAndroid Build Coastguard Worker     gatherer->add(sk_ref_sp(proxy), {pixelAligned ? SkFilterMode::kNearest : SkFilterMode::kLinear,
237*c8dee2aaSAndroid Build Coastguard Worker                                      SkTileMode::kClamp});
238*c8dee2aaSAndroid Build Coastguard Worker }
239*c8dee2aaSAndroid Build Coastguard Worker 
240*c8dee2aaSAndroid Build Coastguard Worker }  // namespace skgpu::graphite
241