xref: /aosp_15_r20/external/skia/src/gpu/ganesh/ops/QuadPerEdgeAA.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2018 Google Inc.
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 #include "src/gpu/ganesh/ops/QuadPerEdgeAA.h"
8 
9 #include "include/core/SkBlendMode.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkColor.h"
12 #include "include/core/SkRect.h"
13 #include "include/private/base/SkMath.h"
14 #include "src/base/SkArenaAlloc.h"
15 #include "src/base/SkVx.h"
16 #include "src/core/SkSLTypeShared.h"
17 #include "src/gpu/KeyBuilder.h"
18 #include "src/gpu/ganesh/GrBuffer.h"
19 #include "src/gpu/ganesh/GrCaps.h"
20 #include "src/gpu/ganesh/GrColorSpaceXform.h"
21 #include "src/gpu/ganesh/GrGeometryProcessor.h"
22 #include "src/gpu/ganesh/GrMeshDrawTarget.h"
23 #include "src/gpu/ganesh/GrOpsRenderPass.h"
24 #include "src/gpu/ganesh/GrResourceProvider.h"
25 #include "src/gpu/ganesh/GrShaderVar.h"
26 #include "src/gpu/ganesh/geometry/GrQuadUtils.h"
27 #include "src/gpu/ganesh/glsl/GrGLSLColorSpaceXformHelper.h"
28 #include "src/gpu/ganesh/glsl/GrGLSLFragmentShaderBuilder.h"
29 #include "src/gpu/ganesh/glsl/GrGLSLVarying.h"
30 #include "src/gpu/ganesh/glsl/GrGLSLVertexGeoBuilder.h"
31 
32 #include <cstdint>
33 #include <memory>
34 #include <utility>
35 
36 class GrBackendFormat;
37 class GrGLSLProgramDataManager;
38 namespace skgpu {
39 class Swizzle;
40 }
41 
42 static_assert((int)GrQuadAAFlags::kLeft   == SkCanvas::kLeft_QuadAAFlag);
43 static_assert((int)GrQuadAAFlags::kTop    == SkCanvas::kTop_QuadAAFlag);
44 static_assert((int)GrQuadAAFlags::kRight  == SkCanvas::kRight_QuadAAFlag);
45 static_assert((int)GrQuadAAFlags::kBottom == SkCanvas::kBottom_QuadAAFlag);
46 static_assert((int)GrQuadAAFlags::kNone   == SkCanvas::kNone_QuadAAFlags);
47 static_assert((int)GrQuadAAFlags::kAll    == SkCanvas::kAll_QuadAAFlags);
48 
49 namespace skgpu::ganesh::QuadPerEdgeAA {
50 
51 namespace {
52 
53 using VertexSpec = skgpu::ganesh::QuadPerEdgeAA::VertexSpec;
54 using CoverageMode = skgpu::ganesh::QuadPerEdgeAA::CoverageMode;
55 using ColorType = skgpu::ganesh::QuadPerEdgeAA::ColorType;
56 
57 // Generic WriteQuadProc that can handle any VertexSpec. It writes the 4 vertices in triangle strip
58 // order, although the data per-vertex is dependent on the VertexSpec.
write_quad_generic(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)59 void write_quad_generic(VertexWriter* vb,
60                         const VertexSpec& spec,
61                         const GrQuad* deviceQuad,
62                         const GrQuad* localQuad,
63                         const float coverage[4],
64                         const SkPMColor4f& color,
65                         const SkRect& geomSubset,
66                         const SkRect& texSubset) {
67     static constexpr auto If = VertexWriter::If<float>;
68 
69     SkASSERT(!spec.hasLocalCoords() || localQuad);
70 
71     CoverageMode mode = spec.coverageMode();
72     for (int i = 0; i < 4; ++i) {
73         // save position, this is a float2 or float3 or float4 depending on the combination of
74         // perspective and coverage mode.
75         *vb << deviceQuad->x(i)
76             << deviceQuad->y(i)
77             << If(spec.deviceQuadType() == GrQuad::Type::kPerspective, deviceQuad->w(i))
78             << If(mode == CoverageMode::kWithPosition, coverage[i]);
79 
80         // save color
81         if (spec.hasVertexColors()) {
82             bool wide = spec.colorType() == ColorType::kFloat;
83             *vb << VertexColor(color * (mode == CoverageMode::kWithColor ? coverage[i] : 1), wide);
84         }
85 
86         // save local position
87         if (spec.hasLocalCoords()) {
88             *vb << localQuad->x(i)
89                 << localQuad->y(i)
90                 << If(spec.localQuadType() == GrQuad::Type::kPerspective, localQuad->w(i));
91         }
92 
93         // save the geometry subset
94         if (spec.requiresGeometrySubset()) {
95             *vb << geomSubset;
96         }
97 
98         // save the texture subset
99         if (spec.hasSubset()) {
100             *vb << texSubset;
101         }
102     }
103 }
104 
105 // Specialized WriteQuadProcs for particular VertexSpecs that show up frequently (determined
106 // experimentally through recorded GMs, SKPs, and SVGs, as well as SkiaRenderer's usage patterns):
107 
108 // 2D (XY), no explicit coverage, vertex color, no locals, no geometry subset, no texture subsetn
109 // This represents simple, solid color or shader, non-AA (or AA with cov. as alpha) rects.
write_2d_color(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)110 void write_2d_color(VertexWriter* vb,
111                     const VertexSpec& spec,
112                     const GrQuad* deviceQuad,
113                     const GrQuad* localQuad,
114                     const float coverage[4],
115                     const SkPMColor4f& color,
116                     const SkRect& geomSubset,
117                     const SkRect& texSubset) {
118     // Assert assumptions about VertexSpec
119     SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
120     SkASSERT(!spec.hasLocalCoords());
121     SkASSERT(spec.coverageMode() == CoverageMode::kNone ||
122              spec.coverageMode() == CoverageMode::kWithColor);
123     SkASSERT(spec.hasVertexColors());
124     SkASSERT(!spec.requiresGeometrySubset());
125     SkASSERT(!spec.hasSubset());
126     // We don't assert that localQuad == nullptr, since it is possible for FillRectOp to
127     // accumulate local coords conservatively (paint not trivial), and then after analysis realize
128     // the processors don't need local coordinates.
129 
130     bool wide = spec.colorType() == ColorType::kFloat;
131     for (int i = 0; i < 4; ++i) {
132         // If this is not coverage-with-alpha, make sure coverage == 1 so it doesn't do anything
133         SkASSERT(spec.coverageMode() == CoverageMode::kWithColor || coverage[i] == 1.f);
134         *vb << deviceQuad->x(i)
135             << deviceQuad->y(i)
136             << VertexColor(color * coverage[i], wide);
137     }
138 }
139 
140 // 2D (XY), no explicit coverage, UV locals, no color, no geometry subset, no texture subset
141 // This represents opaque, non AA, textured rects
write_2d_uv(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)142 void write_2d_uv(VertexWriter* vb,
143                  const VertexSpec& spec,
144                  const GrQuad* deviceQuad,
145                  const GrQuad* localQuad,
146                  const float coverage[4],
147                  const SkPMColor4f& color,
148                  const SkRect& geomSubset,
149                  const SkRect& texSubset) {
150     // Assert assumptions about VertexSpec
151     SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
152     SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
153     SkASSERT(spec.coverageMode() == CoverageMode::kNone);
154     SkASSERT(!spec.hasVertexColors());
155     SkASSERT(!spec.requiresGeometrySubset());
156     SkASSERT(!spec.hasSubset());
157     SkASSERT(localQuad);
158 
159     for (int i = 0; i < 4; ++i) {
160         *vb << deviceQuad->x(i)
161             << deviceQuad->y(i)
162             << localQuad->x(i)
163             << localQuad->y(i);
164     }
165 }
166 
167 // 2D (XY), no explicit coverage, UV locals, vertex color, no geometry or texture subsets
168 // This represents transparent, non AA (or AA with cov. as alpha), textured rects
write_2d_color_uv(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)169 void write_2d_color_uv(VertexWriter* vb,
170                        const VertexSpec& spec,
171                        const GrQuad* deviceQuad,
172                        const GrQuad* localQuad,
173                        const float coverage[4],
174                        const SkPMColor4f& color,
175                        const SkRect& geomSubset,
176                        const SkRect& texSubset) {
177     // Assert assumptions about VertexSpec
178     SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
179     SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
180     SkASSERT(spec.coverageMode() == CoverageMode::kNone ||
181              spec.coverageMode() == CoverageMode::kWithColor);
182     SkASSERT(spec.hasVertexColors());
183     SkASSERT(!spec.requiresGeometrySubset());
184     SkASSERT(!spec.hasSubset());
185     SkASSERT(localQuad);
186 
187     bool wide = spec.colorType() == ColorType::kFloat;
188     for (int i = 0; i < 4; ++i) {
189         // If this is not coverage-with-alpha, make sure coverage == 1 so it doesn't do anything
190         SkASSERT(spec.coverageMode() == CoverageMode::kWithColor || coverage[i] == 1.f);
191         *vb << deviceQuad->x(i)
192             << deviceQuad->y(i)
193             << VertexColor(color * coverage[i], wide)
194             << localQuad->x(i)
195             << localQuad->y(i);
196     }
197 }
198 
199 // 2D (XY), explicit coverage, UV locals, no color, no geometry subset, no texture subset
200 // This represents opaque, AA, textured rects
write_2d_cov_uv(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)201 void write_2d_cov_uv(VertexWriter* vb,
202                      const VertexSpec& spec,
203                      const GrQuad* deviceQuad,
204                      const GrQuad* localQuad,
205                      const float coverage[4],
206                      const SkPMColor4f& color,
207                      const SkRect& geomSubset,
208                      const SkRect& texSubset) {
209     // Assert assumptions about VertexSpec
210     SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
211     SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
212     SkASSERT(spec.coverageMode() == CoverageMode::kWithPosition);
213     SkASSERT(!spec.hasVertexColors());
214     SkASSERT(!spec.requiresGeometrySubset());
215     SkASSERT(!spec.hasSubset());
216     SkASSERT(localQuad);
217 
218     for (int i = 0; i < 4; ++i) {
219         *vb << deviceQuad->x(i)
220             << deviceQuad->y(i)
221             << coverage[i]
222             << localQuad->x(i)
223             << localQuad->y(i);
224     }
225 }
226 
227 // NOTE: The three _strict specializations below match the non-strict uv functions above, except
228 // that they also write the UV subset. These are included to benefit SkiaRenderer, which must make
229 // use of both fast and strict constrained subsets. When testing _strict was not that common across
230 // GMS, SKPs, and SVGs but we have little visibility into actual SkiaRenderer statistics. If
231 // SkiaRenderer can avoid subsets more, these 3 functions should probably be removed for simplicity.
232 
233 // 2D (XY), no explicit coverage, UV locals, no color, tex subset but no geometry subset
234 // This represents opaque, non AA, textured rects with strict uv sampling
write_2d_uv_strict(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)235 void write_2d_uv_strict(VertexWriter* vb,
236                         const VertexSpec& spec,
237                         const GrQuad* deviceQuad,
238                         const GrQuad* localQuad,
239                         const float coverage[4],
240                         const SkPMColor4f& color,
241                         const SkRect& geomSubset,
242                         const SkRect& texSubset) {
243     // Assert assumptions about VertexSpec
244     SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
245     SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
246     SkASSERT(spec.coverageMode() == CoverageMode::kNone);
247     SkASSERT(!spec.hasVertexColors());
248     SkASSERT(!spec.requiresGeometrySubset());
249     SkASSERT(spec.hasSubset());
250     SkASSERT(localQuad);
251 
252     for (int i = 0; i < 4; ++i) {
253         *vb << deviceQuad->x(i)
254             << deviceQuad->y(i)
255             << localQuad->x(i)
256             << localQuad->y(i)
257             << texSubset;
258     }
259 }
260 
261 // 2D (XY), no explicit coverage, UV locals, vertex color, tex subset but no geometry subset
262 // This represents transparent, non AA (or AA with cov. as alpha), textured rects with strict sample
write_2d_color_uv_strict(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)263 void write_2d_color_uv_strict(VertexWriter* vb,
264                               const VertexSpec& spec,
265                               const GrQuad* deviceQuad,
266                               const GrQuad* localQuad,
267                               const float coverage[4],
268                               const SkPMColor4f& color,
269                               const SkRect& geomSubset,
270                               const SkRect& texSubset) {
271     // Assert assumptions about VertexSpec
272     SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
273     SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
274     SkASSERT(spec.coverageMode() == CoverageMode::kNone ||
275              spec.coverageMode() == CoverageMode::kWithColor);
276     SkASSERT(spec.hasVertexColors());
277     SkASSERT(!spec.requiresGeometrySubset());
278     SkASSERT(spec.hasSubset());
279     SkASSERT(localQuad);
280 
281     bool wide = spec.colorType() == ColorType::kFloat;
282     for (int i = 0; i < 4; ++i) {
283         // If this is not coverage-with-alpha, make sure coverage == 1 so it doesn't do anything
284         SkASSERT(spec.coverageMode() == CoverageMode::kWithColor || coverage[i] == 1.f);
285         *vb << deviceQuad->x(i)
286             << deviceQuad->y(i)
287             << VertexColor(color * coverage[i], wide)
288             << localQuad->x(i)
289             << localQuad->y(i)
290             << texSubset;
291     }
292 }
293 
294 // 2D (XY), explicit coverage, UV locals, no color, tex subset but no geometry subset
295 // This represents opaque, AA, textured rects with strict uv sampling
write_2d_cov_uv_strict(VertexWriter * vb,const VertexSpec & spec,const GrQuad * deviceQuad,const GrQuad * localQuad,const float coverage[4],const SkPMColor4f & color,const SkRect & geomSubset,const SkRect & texSubset)296 void write_2d_cov_uv_strict(VertexWriter* vb,
297                             const VertexSpec& spec,
298                             const GrQuad* deviceQuad,
299                             const GrQuad* localQuad,
300                             const float coverage[4],
301                             const SkPMColor4f& color,
302                             const SkRect& geomSubset,
303                             const SkRect& texSubset) {
304     // Assert assumptions about VertexSpec
305     SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
306     SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
307     SkASSERT(spec.coverageMode() == CoverageMode::kWithPosition);
308     SkASSERT(!spec.hasVertexColors());
309     SkASSERT(!spec.requiresGeometrySubset());
310     SkASSERT(spec.hasSubset());
311     SkASSERT(localQuad);
312 
313     for (int i = 0; i < 4; ++i) {
314         *vb << deviceQuad->x(i)
315             << deviceQuad->y(i)
316             << coverage[i]
317             << localQuad->x(i)
318             << localQuad->y(i)
319             << texSubset;
320     }
321 }
322 
323 } // anonymous namespace
324 
CalcIndexBufferOption(GrAAType aa,int numQuads)325 IndexBufferOption CalcIndexBufferOption(GrAAType aa, int numQuads) {
326     if (aa == GrAAType::kCoverage) {
327         return IndexBufferOption::kPictureFramed;
328     } else if (numQuads > 1) {
329         return IndexBufferOption::kIndexedRects;
330     } else {
331         return IndexBufferOption::kTriStrips;
332     }
333 }
334 
335 // This is a more elaborate version of fitsInBytes() that allows "no color" for white
MinColorType(SkPMColor4f color)336 ColorType MinColorType(SkPMColor4f color) {
337     if (color == SK_PMColor4fWHITE) {
338         return ColorType::kNone;
339     } else {
340         return color.fitsInBytes() ? ColorType::kByte : ColorType::kFloat;
341     }
342 }
343 
344 ////////////////// Tessellator Implementation
345 
GetWriteQuadProc(const VertexSpec & spec)346 Tessellator::WriteQuadProc Tessellator::GetWriteQuadProc(const VertexSpec& spec) {
347     // All specialized writing functions requires 2D geometry and no geometry subset. This is not
348     // the same as just checking device type vs. kRectilinear since non-AA general 2D quads do not
349     // require a geometry subset and could then go through a fast path.
350     if (spec.deviceQuadType() != GrQuad::Type::kPerspective && !spec.requiresGeometrySubset()) {
351         CoverageMode mode = spec.coverageMode();
352         if (spec.hasVertexColors()) {
353             if (mode != CoverageMode::kWithPosition) {
354                 // Vertex colors, but no explicit coverage
355                 if (!spec.hasLocalCoords()) {
356                     // Non-UV with vertex colors (possibly with coverage folded into alpha)
357                     return write_2d_color;
358                 } else if (spec.localQuadType() != GrQuad::Type::kPerspective) {
359                     // UV locals with vertex colors (possibly with coverage-as-alpha)
360                     return spec.hasSubset() ? write_2d_color_uv_strict : write_2d_color_uv;
361                 }
362             }
363             // Else fall through; this is a spec that requires vertex colors and explicit coverage,
364             // which means it's anti-aliased and the FPs don't support coverage as alpha, or
365             // it uses 3D local coordinates.
366         } else if (spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective) {
367             if (mode == CoverageMode::kWithPosition) {
368                 // UV locals with explicit coverage
369                 return spec.hasSubset() ? write_2d_cov_uv_strict : write_2d_cov_uv;
370             } else {
371                 SkASSERT(mode == CoverageMode::kNone);
372                 return spec.hasSubset() ? write_2d_uv_strict : write_2d_uv;
373             }
374         }
375         // Else fall through to generic vertex function; this is a spec that has no vertex colors
376         // and [no|uvr] local coords, which doesn't happen often enough to warrant specialization.
377     }
378 
379     // Arbitrary spec hits the slow path
380     return write_quad_generic;
381 }
382 
Tessellator(const VertexSpec & spec,char * vertices)383 Tessellator::Tessellator(const VertexSpec& spec, char* vertices)
384         : fVertexSpec(spec)
385         , fVertexWriter{vertices}
386         , fWriteProc(Tessellator::GetWriteQuadProc(spec)) {}
387 
append(GrQuad * deviceQuad,GrQuad * localQuad,const SkPMColor4f & color,const SkRect & uvSubset,GrQuadAAFlags aaFlags)388 void Tessellator::append(GrQuad* deviceQuad, GrQuad* localQuad,
389                          const SkPMColor4f& color, const SkRect& uvSubset, GrQuadAAFlags aaFlags) {
390     // We allow Tessellator to be created with a null vertices pointer for convenience, but it is
391     // assumed it will never actually be used in those cases.
392     SkASSERT(fVertexWriter);
393     SkASSERT(deviceQuad->quadType() <= fVertexSpec.deviceQuadType());
394     SkASSERT(localQuad || !fVertexSpec.hasLocalCoords());
395     SkASSERT(!fVertexSpec.hasLocalCoords() || localQuad->quadType() <= fVertexSpec.localQuadType());
396 
397     static const float kFullCoverage[4] = {1.f, 1.f, 1.f, 1.f};
398     static const float kZeroCoverage[4] = {0.f, 0.f, 0.f, 0.f};
399     static const SkRect kIgnoredSubset = SkRect::MakeEmpty();
400 
401     if (fVertexSpec.usesCoverageAA()) {
402         SkASSERT(fVertexSpec.coverageMode() == CoverageMode::kWithColor ||
403                  fVertexSpec.coverageMode() == CoverageMode::kWithPosition);
404         // Must calculate inner and outer quadrilaterals for the vertex coverage ramps, and possibly
405         // a geometry subset if corners are not right angles
406         SkRect geomSubset;
407         if (fVertexSpec.requiresGeometrySubset()) {
408             // Our GP code expects a 0.5 outset rect (coverage is computed as 0 at the values of
409             // the uniform). However, if we have quad edges that aren't supposed to be antialiased
410             // they may lie close to the bounds. So in that case we outset by an additional 0.5.
411             // This is a sort of backup clipping mechanism for cases where quad outsetting of nearly
412             // parallel edges produces long thin extrusions from the original geometry.
413             float outset = aaFlags == GrQuadAAFlags::kAll ? 0.5f : 1.f;
414             geomSubset = deviceQuad->bounds().makeOutset(outset, outset);
415         }
416 
417         if (aaFlags == GrQuadAAFlags::kNone) {
418             // Have to write the coverage AA vertex structure, but there's no math to be done for a
419             // non-aa quad batched into a coverage AA op.
420             fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kFullCoverage, color,
421                        geomSubset, uvSubset);
422             // Since we pass the same corners in, the outer vertex structure will have 0 area and
423             // the coverage interpolation from 1 to 0 will not be visible.
424             fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kZeroCoverage, color,
425                        geomSubset, uvSubset);
426         } else {
427             // Reset the tessellation helper to match the current geometry
428             fAAHelper.reset(*deviceQuad, localQuad);
429 
430             // Edge inset/outset distance ordered LBTR, set to 0.5 for a half pixel if the AA flag
431             // is turned on, or 0.0 if the edge is not anti-aliased.
432             skvx::Vec<4, float> edgeDistances;
433             if (aaFlags == GrQuadAAFlags::kAll) {
434                 edgeDistances = 0.5f;
435             } else {
436                 edgeDistances = { (aaFlags & GrQuadAAFlags::kLeft)   ? 0.5f : 0.f,
437                                   (aaFlags & GrQuadAAFlags::kBottom) ? 0.5f : 0.f,
438                                   (aaFlags & GrQuadAAFlags::kTop)    ? 0.5f : 0.f,
439                                   (aaFlags & GrQuadAAFlags::kRight)  ? 0.5f : 0.f };
440             }
441 
442             // Write inner vertices first
443             float coverage[4];
444             fAAHelper.inset(edgeDistances, deviceQuad, localQuad).store(coverage);
445             fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, coverage, color,
446                        geomSubset, uvSubset);
447 
448             // Then outer vertices, which use 0.f for their coverage. If the inset was degenerate
449             // to a line (had all coverages < 1), tweak the outset distance so the outer frame's
450             // narrow axis reaches out to 2px, which gives better animation under translation.
451             const bool hairline = aaFlags == GrQuadAAFlags::kAll &&
452                                   coverage[0] < 1.f &&
453                                   coverage[1] < 1.f &&
454                                   coverage[2] < 1.f &&
455                                   coverage[3] < 1.f;
456             if (hairline) {
457                 skvx::Vec<4, float> len = fAAHelper.getEdgeLengths();
458                 // Using max guards us against trying to scale a degenerate triangle edge of 0 len
459                 // up to 2px. The shuffles are so that edge 0's adjustment is based on the lengths
460                 // of its connecting edges (1 and 2), and so forth.
461                 skvx::Vec<4, float> maxWH = max(skvx::shuffle<1, 0, 3, 2>(len),
462                                                 skvx::shuffle<2, 3, 0, 1>(len));
463                 // wh + 2e' = 2, so e' = (2 - wh) / 2 => e' = e * (2 - wh). But if w or h > 1, then
464                 // 2 - wh < 1 and represents the non-narrow axis so clamp to 1.
465                 edgeDistances *= max(1.f, 2.f - maxWH);
466             }
467             fAAHelper.outset(edgeDistances, deviceQuad, localQuad);
468             fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kZeroCoverage, color,
469                        geomSubset, uvSubset);
470         }
471     } else {
472         // No outsetting needed, just write a single quad with full coverage
473         SkASSERT(fVertexSpec.coverageMode() == CoverageMode::kNone &&
474                  !fVertexSpec.requiresGeometrySubset());
475         fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kFullCoverage, color,
476                    kIgnoredSubset, uvSubset);
477     }
478 }
479 
GetIndexBuffer(GrMeshDrawTarget * target,IndexBufferOption indexBufferOption)480 sk_sp<const GrBuffer> GetIndexBuffer(GrMeshDrawTarget* target,
481                                      IndexBufferOption indexBufferOption) {
482     auto resourceProvider = target->resourceProvider();
483 
484     switch (indexBufferOption) {
485         case IndexBufferOption::kPictureFramed: return resourceProvider->refAAQuadIndexBuffer();
486         case IndexBufferOption::kIndexedRects:  return resourceProvider->refNonAAQuadIndexBuffer();
487         case IndexBufferOption::kTriStrips:     // fall through
488         default:                                return nullptr;
489     }
490 }
491 
QuadLimit(IndexBufferOption option)492 int QuadLimit(IndexBufferOption option) {
493     switch (option) {
494         case IndexBufferOption::kPictureFramed: return GrResourceProvider::MaxNumAAQuads();
495         case IndexBufferOption::kIndexedRects:  return GrResourceProvider::MaxNumNonAAQuads();
496         case IndexBufferOption::kTriStrips:     return SK_MaxS32; // not limited by an indexBuffer
497     }
498 
499     SkUNREACHABLE;
500 }
501 
IssueDraw(const GrCaps & caps,GrOpsRenderPass * renderPass,const VertexSpec & spec,int runningQuadCount,int quadsInDraw,int maxVerts,int absVertBufferOffset)502 void IssueDraw(const GrCaps& caps, GrOpsRenderPass* renderPass, const VertexSpec& spec,
503                int runningQuadCount, int quadsInDraw, int maxVerts, int absVertBufferOffset) {
504     if (spec.indexBufferOption() == IndexBufferOption::kTriStrips) {
505         int offset = absVertBufferOffset +
506                                     runningQuadCount * GrResourceProvider::NumVertsPerNonAAQuad();
507         renderPass->draw(4, offset);
508         return;
509     }
510 
511     SkASSERT(spec.indexBufferOption() == IndexBufferOption::kPictureFramed ||
512              spec.indexBufferOption() == IndexBufferOption::kIndexedRects);
513 
514     int maxNumQuads, numIndicesPerQuad, numVertsPerQuad;
515 
516     if (spec.indexBufferOption() == IndexBufferOption::kPictureFramed) {
517         // AA uses 8 vertices and 30 indices per quad, basically nested rectangles
518         maxNumQuads = GrResourceProvider::MaxNumAAQuads();
519         numIndicesPerQuad = GrResourceProvider::NumIndicesPerAAQuad();
520         numVertsPerQuad = GrResourceProvider::NumVertsPerAAQuad();
521     } else {
522         // Non-AA uses 4 vertices and 6 indices per quad
523         maxNumQuads = GrResourceProvider::MaxNumNonAAQuads();
524         numIndicesPerQuad = GrResourceProvider::NumIndicesPerNonAAQuad();
525         numVertsPerQuad = GrResourceProvider::NumVertsPerNonAAQuad();
526     }
527 
528     SkASSERT(runningQuadCount + quadsInDraw <= maxNumQuads);
529 
530     if (caps.avoidLargeIndexBufferDraws()) {
531         // When we need to avoid large index buffer draws we modify the base vertex of the draw
532         // which, in GL, requires rebinding all vertex attrib arrays, so a base index is generally
533         // preferred.
534         int offset = absVertBufferOffset + runningQuadCount * numVertsPerQuad;
535 
536         renderPass->drawIndexPattern(numIndicesPerQuad, quadsInDraw, maxNumQuads, numVertsPerQuad,
537                                      offset);
538     } else {
539         int baseIndex = runningQuadCount * numIndicesPerQuad;
540         int numIndicesToDraw = quadsInDraw * numIndicesPerQuad;
541 
542         int minVertex = runningQuadCount * numVertsPerQuad;
543         int maxVertex = (runningQuadCount + quadsInDraw) * numVertsPerQuad - 1; // inclusive
544 
545         renderPass->drawIndexed(numIndicesToDraw, baseIndex, minVertex, maxVertex,
546                                 absVertBufferOffset);
547     }
548 }
549 
550 ////////////////// VertexSpec Implementation
551 
deviceDimensionality() const552 int VertexSpec::deviceDimensionality() const {
553     return this->deviceQuadType() == GrQuad::Type::kPerspective ? 3 : 2;
554 }
555 
localDimensionality() const556 int VertexSpec::localDimensionality() const {
557     return fHasLocalCoords ? (this->localQuadType() == GrQuad::Type::kPerspective ? 3 : 2) : 0;
558 }
559 
coverageMode() const560 CoverageMode VertexSpec::coverageMode() const {
561     if (this->usesCoverageAA()) {
562         if (this->compatibleWithCoverageAsAlpha() && this->hasVertexColors() &&
563             !this->requiresGeometrySubset()) {
564             // Using a geometric subset acts as a second source of coverage and folding
565             // the original coverage into color makes it impossible to apply the color's
566             // alpha to the geometric subset's coverage when the original shape is clipped.
567             return CoverageMode::kWithColor;
568         } else {
569             return CoverageMode::kWithPosition;
570         }
571     } else {
572         return CoverageMode::kNone;
573     }
574 }
575 
576 // This needs to stay in sync w/ QuadPerEdgeAAGeometryProcessor::initializeAttrs
vertexSize() const577 size_t VertexSpec::vertexSize() const {
578     bool needsPerspective = (this->deviceDimensionality() == 3);
579     CoverageMode coverageMode = this->coverageMode();
580 
581     size_t count = 0;
582 
583     if (coverageMode == CoverageMode::kWithPosition) {
584         if (needsPerspective) {
585             count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType);
586         } else {
587             count += GrVertexAttribTypeSize(kFloat2_GrVertexAttribType) +
588                      GrVertexAttribTypeSize(kFloat_GrVertexAttribType);
589         }
590     } else {
591         if (needsPerspective) {
592             count += GrVertexAttribTypeSize(kFloat3_GrVertexAttribType);
593         } else {
594             count += GrVertexAttribTypeSize(kFloat2_GrVertexAttribType);
595         }
596     }
597 
598     if (this->requiresGeometrySubset()) {
599         count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType);
600     }
601 
602     count += this->localDimensionality() * GrVertexAttribTypeSize(kFloat_GrVertexAttribType);
603 
604     if (ColorType::kByte == this->colorType()) {
605         count += GrVertexAttribTypeSize(kUByte4_norm_GrVertexAttribType);
606     } else if (ColorType::kFloat == this->colorType()) {
607         count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType);
608     }
609 
610     if (this->hasSubset()) {
611         count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType);
612     }
613 
614     return count;
615 }
616 
617 ////////////////// Geometry Processor Implementation
618 
619 class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
620 public:
Make(SkArenaAlloc * arena,const VertexSpec & spec)621     static GrGeometryProcessor* Make(SkArenaAlloc* arena, const VertexSpec& spec) {
622         return arena->make([&](void* ptr) {
623             return new (ptr) QuadPerEdgeAAGeometryProcessor(spec);
624         });
625     }
626 
Make(SkArenaAlloc * arena,const VertexSpec & vertexSpec,const GrShaderCaps & caps,const GrBackendFormat & backendFormat,GrSamplerState samplerState,const skgpu::Swizzle & swizzle,sk_sp<GrColorSpaceXform> textureColorSpaceXform,Saturate saturate)627     static GrGeometryProcessor* Make(SkArenaAlloc* arena,
628                                      const VertexSpec& vertexSpec,
629                                      const GrShaderCaps& caps,
630                                      const GrBackendFormat& backendFormat,
631                                      GrSamplerState samplerState,
632                                      const skgpu::Swizzle& swizzle,
633                                      sk_sp<GrColorSpaceXform> textureColorSpaceXform,
634                                      Saturate saturate) {
635         return arena->make([&](void* ptr) {
636             return new (ptr) QuadPerEdgeAAGeometryProcessor(
637                     vertexSpec, caps, backendFormat, samplerState, swizzle,
638                     std::move(textureColorSpaceXform), saturate);
639         });
640     }
641 
name() const642     const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
643 
addToKey(const GrShaderCaps &,KeyBuilder * b) const644     void addToKey(const GrShaderCaps&, KeyBuilder* b) const override {
645         // texturing, device-dimensions are single bit flags
646         b->addBool(fTexSubset.isInitialized(),    "subset");
647         b->addBool(fSampler.isInitialized(),      "textured");
648         b->addBool(fNeedsPerspective,             "perspective");
649         b->addBool((fSaturate == Saturate::kYes), "saturate");
650 
651         b->addBool(fLocalCoord.isInitialized(),   "hasLocalCoords");
652         if (fLocalCoord.isInitialized()) {
653             // 2D (0) or 3D (1)
654             b->addBits(1, (kFloat3_GrVertexAttribType == fLocalCoord.cpuType()), "localCoordsType");
655         }
656         b->addBool(fColor.isInitialized(),        "hasColor");
657         if (fColor.isInitialized()) {
658             // bytes (0) or floats (1)
659             b->addBits(1, (kFloat4_GrVertexAttribType == fColor.cpuType()), "colorType");
660         }
661         // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor, 11 for
662         // position+geomsubset
663         uint32_t coverageKey = 0;
664         SkASSERT(!fGeomSubset.isInitialized() || fCoverageMode == CoverageMode::kWithPosition);
665         if (fCoverageMode != CoverageMode::kNone) {
666             coverageKey = fGeomSubset.isInitialized()
667                                   ? 0x3
668                                   : (CoverageMode::kWithPosition == fCoverageMode ? 0x1 : 0x2);
669         }
670         b->addBits(2, coverageKey, "coverageMode");
671 
672         b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()), "colorSpaceXform");
673     }
674 
makeProgramImpl(const GrShaderCaps &) const675     std::unique_ptr<ProgramImpl> makeProgramImpl(const GrShaderCaps&) const override {
676         class Impl : public ProgramImpl {
677         public:
678             void setData(const GrGLSLProgramDataManager& pdman,
679                          const GrShaderCaps&,
680                          const GrGeometryProcessor& geomProc) override {
681                 const auto& gp = geomProc.cast<QuadPerEdgeAAGeometryProcessor>();
682                 fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
683             }
684 
685         private:
686             void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
687                 using Interpolation = GrGLSLVaryingHandler::Interpolation;
688 
689                 const auto& gp = args.fGeomProc.cast<QuadPerEdgeAAGeometryProcessor>();
690                 fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
691                                                        gp.fTextureColorSpaceXform.get());
692 
693                 args.fVaryingHandler->emitAttributes(gp);
694 
695                 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
696                     // Strip last channel from the vertex attribute to remove coverage and get the
697                     // actual position
698                     if (gp.fNeedsPerspective) {
699                         args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
700                                                        gp.fPosition.name());
701                     } else {
702                         args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
703                                                        gp.fPosition.name());
704                     }
705                     gpArgs->fPositionVar = {"position",
706                                             gp.fNeedsPerspective ? SkSLType::kFloat3
707                                                                  : SkSLType::kFloat2,
708                                             GrShaderVar::TypeModifier::None};
709                 } else {
710                     // No coverage to eliminate
711                     gpArgs->fPositionVar = gp.fPosition.asShaderVar();
712                 }
713 
714                 // This attribute will be uninitialized if earlier FP analysis determined no
715                 // local coordinates are needed (and this will not include the inline texture
716                 // fetch this GP does before invoking FPs).
717                 gpArgs->fLocalCoordVar = gp.fLocalCoord.asShaderVar();
718 
719                 // Solid color before any texturing gets modulated in
720                 const char* blendDst;
721                 if (gp.fColor.isInitialized()) {
722                     SkASSERT(gp.fCoverageMode != CoverageMode::kWithColor || !gp.fNeedsPerspective);
723                     // The color cannot be flat if the varying coverage has been modulated into it
724                     args.fFragBuilder->codeAppendf("half4 %s;", args.fOutputColor);
725                     args.fVaryingHandler->addPassThroughAttribute(
726                             gp.fColor.asShaderVar(),
727                             args.fOutputColor,
728                             gp.fCoverageMode == CoverageMode::kWithColor
729                                     ? Interpolation::kInterpolated
730                                     : Interpolation::kCanBeFlat);
731                     blendDst = args.fOutputColor;
732                 } else {
733                     // Output color must be initialized to something
734                     args.fFragBuilder->codeAppendf("half4 %s = half4(1);", args.fOutputColor);
735                     blendDst = nullptr;
736                 }
737 
738                 // If there is a texture, must also handle texture coordinates and reading from
739                 // the texture in the fragment shader before continuing to fragment processors.
740                 if (gp.fSampler.isInitialized()) {
741                     // Texture coordinates clamped by the subset on the fragment shader; if the GP
742                     // has a texture, it's guaranteed to have local coordinates
743                     args.fFragBuilder->codeAppend("float2 texCoord;");
744                     if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
745                         // Can't do a pass through since we need to perform perspective division
746                         GrGLSLVarying v(gp.fLocalCoord.gpuType());
747                         args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
748                         args.fVertBuilder->codeAppendf("%s = %s;",
749                                                        v.vsOut(), gp.fLocalCoord.name());
750                         args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
751                                                        v.fsIn(), v.fsIn());
752                     } else {
753                         args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord.asShaderVar(),
754                                                                       "texCoord");
755                     }
756 
757                     // Clamp the now 2D localCoordName variable by the subset if it is provided
758                     if (gp.fTexSubset.isInitialized()) {
759                         args.fFragBuilder->codeAppend("float4 subset;");
760                         args.fVaryingHandler->addPassThroughAttribute(gp.fTexSubset.asShaderVar(),
761                                                                       "subset",
762                                                                       Interpolation::kCanBeFlat);
763                         args.fFragBuilder->codeAppend(
764                                 "texCoord = clamp(texCoord, subset.LT, subset.RB);");
765                     }
766 
767                     // Now modulate the starting output color by the texture lookup
768                     args.fFragBuilder->codeAppendf(
769                             "%s = %s(",
770                             args.fOutputColor,
771                             (gp.fSaturate == Saturate::kYes) ? "saturate" : "");
772                     args.fFragBuilder->appendTextureLookupAndBlend(
773                             blendDst, SkBlendMode::kModulate, args.fTexSamplers[0],
774                             "texCoord", &fTextureColorSpaceXformHelper);
775                     args.fFragBuilder->codeAppend(");");
776                 } else {
777                     // Saturate is only intended for use with a proxy to account for the fact
778                     // that TextureOp skips SkPaint conversion, which normally handles this.
779                     SkASSERT(gp.fSaturate == Saturate::kNo);
780                 }
781 
782                 // And lastly, output the coverage calculation code
783                 if (gp.fCoverageMode == CoverageMode::kWithPosition) {
784                     GrGLSLVarying coverage(SkSLType::kFloat);
785                     args.fVaryingHandler->addVarying("coverage", &coverage);
786                     if (gp.fNeedsPerspective) {
787                         // Multiply by "W" in the vertex shader, then by 1/w (sk_FragCoord.w) in
788                         // the fragment shader to get screen-space linear coverage.
789                         args.fVertBuilder->codeAppendf("%s = %s.w * %s.z;",
790                                                        coverage.vsOut(), gp.fPosition.name(),
791                                                        gp.fPosition.name());
792                         args.fFragBuilder->codeAppendf("float coverage = %s * sk_FragCoord.w;",
793                                                         coverage.fsIn());
794                     } else {
795                         args.fVertBuilder->codeAppendf("%s = %s;",
796                                                        coverage.vsOut(), gp.fCoverage.name());
797                         args.fFragBuilder->codeAppendf("float coverage = %s;", coverage.fsIn());
798                     }
799 
800                     if (gp.fGeomSubset.isInitialized()) {
801                         // Calculate distance from sk_FragCoord to the 4 edges of the subset
802                         // and clamp them to (0, 1). Use the minimum of these and the original
803                         // coverage. This only has to be done in the exterior triangles, the
804                         // interior of the quad geometry can never be clipped by the subset box.
805                         args.fFragBuilder->codeAppend("float4 geoSubset;");
806                         args.fVaryingHandler->addPassThroughAttribute(gp.fGeomSubset.asShaderVar(),
807                                                                       "geoSubset",
808                                                                       Interpolation::kCanBeFlat);
809                         args.fFragBuilder->codeAppend(
810                                 // This is lifted from GrFragmentProcessor::Rect.
811                                 "float4 dists4 = saturate(float4(1, 1, -1, -1) * "
812                                                          "(sk_FragCoord.xyxy - geoSubset));"
813                                 "float2 dists2 = dists4.xy + dists4.zw - 1;"
814                                 "coverage = min(coverage, dists2.x * dists2.y);");
815                     }
816 
817                     args.fFragBuilder->codeAppendf("half4 %s = half4(coverage);",
818                                                    args.fOutputCoverage);
819                 } else {
820                     // Set coverage to 1, since it's either non-AA or the coverage was already
821                     // folded into the output color
822                     SkASSERT(!gp.fGeomSubset.isInitialized());
823                     args.fFragBuilder->codeAppendf("const half4 %s = half4(1);",
824                                                    args.fOutputCoverage);
825                 }
826             }
827 
828             GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
829         };
830 
831         return std::make_unique<Impl>();
832     }
833 
834 private:
835     using Saturate = skgpu::ganesh::TextureOp::Saturate;
836 
QuadPerEdgeAAGeometryProcessor(const VertexSpec & spec)837     QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
838             : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
839             , fTextureColorSpaceXform(nullptr) {
840         SkASSERT(!spec.hasSubset());
841         this->initializeAttrs(spec);
842         this->setTextureSamplerCnt(0);
843     }
844 
QuadPerEdgeAAGeometryProcessor(const VertexSpec & spec,const GrShaderCaps & caps,const GrBackendFormat & backendFormat,GrSamplerState samplerState,const skgpu::Swizzle & swizzle,sk_sp<GrColorSpaceXform> textureColorSpaceXform,Saturate saturate)845     QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec,
846                                    const GrShaderCaps& caps,
847                                    const GrBackendFormat& backendFormat,
848                                    GrSamplerState samplerState,
849                                    const skgpu::Swizzle& swizzle,
850                                    sk_sp<GrColorSpaceXform> textureColorSpaceXform,
851                                    Saturate saturate)
852             : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
853             , fSaturate(saturate)
854             , fTextureColorSpaceXform(std::move(textureColorSpaceXform))
855             , fSampler(samplerState, backendFormat, swizzle) {
856         SkASSERT(spec.hasLocalCoords());
857         this->initializeAttrs(spec);
858         this->setTextureSamplerCnt(1);
859     }
860 
861     // This needs to stay in sync w/ VertexSpec::vertexSize
initializeAttrs(const VertexSpec & spec)862     void initializeAttrs(const VertexSpec& spec) {
863         fNeedsPerspective = spec.deviceDimensionality() == 3;
864         fCoverageMode = spec.coverageMode();
865 
866         if (fCoverageMode == CoverageMode::kWithPosition) {
867             if (fNeedsPerspective) {
868                 fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, SkSLType::kFloat4};
869             } else {
870                 fPosition = {"position", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
871                 fCoverage = {"coverage", kFloat_GrVertexAttribType, SkSLType::kFloat};
872             }
873         } else {
874             if (fNeedsPerspective) {
875                 fPosition = {"position", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
876             } else {
877                 fPosition = {"position", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
878             }
879         }
880 
881         // Need a geometry subset when the quads are AA and not rectilinear, since their AA
882         // outsetting can go beyond a half pixel.
883         if (spec.requiresGeometrySubset()) {
884             fGeomSubset = {"geomSubset", kFloat4_GrVertexAttribType, SkSLType::kFloat4};
885         }
886 
887         int localDim = spec.localDimensionality();
888         if (localDim == 3) {
889             fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, SkSLType::kFloat3};
890         } else if (localDim == 2) {
891             fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, SkSLType::kFloat2};
892         } // else localDim == 0 and attribute remains uninitialized
893 
894         if (spec.hasVertexColors()) {
895             fColor = MakeColorAttribute("color", ColorType::kFloat == spec.colorType());
896         }
897 
898         if (spec.hasSubset()) {
899             fTexSubset = {"texSubset", kFloat4_GrVertexAttribType, SkSLType::kFloat4};
900         }
901 
902         this->setVertexAttributesWithImplicitOffsets(&fPosition, 6);
903     }
904 
onTextureSampler(int) const905     const TextureSampler& onTextureSampler(int) const override { return fSampler; }
906 
907     Attribute fPosition; // May contain coverage as last channel
908     Attribute fCoverage; // Used for non-perspective position to avoid Intel Metal issues
909     Attribute fColor; // May have coverage modulated in if the FPs support it
910     Attribute fLocalCoord;
911     Attribute fGeomSubset; // Screen-space bounding box on geometry+aa outset
912     Attribute fTexSubset; // Texture-space bounding box on local coords
913 
914     // The positions attribute may have coverage built into it, so float3 is an ambiguous type
915     // and may mean 2d with coverage, or 3d with no coverage
916     bool fNeedsPerspective;
917     // Should saturate() be called on the color? Only relevant when created with a texture.
918     Saturate fSaturate = Saturate::kNo;
919     CoverageMode fCoverageMode;
920 
921     // Color space will be null and fSampler.isInitialized() returns false when the GP is configured
922     // to skip texturing.
923     sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
924     TextureSampler fSampler;
925 
926     using INHERITED = GrGeometryProcessor;
927 };
928 
MakeProcessor(SkArenaAlloc * arena,const VertexSpec & spec)929 GrGeometryProcessor* MakeProcessor(SkArenaAlloc* arena, const VertexSpec& spec) {
930     return QuadPerEdgeAAGeometryProcessor::Make(arena, spec);
931 }
932 
MakeTexturedProcessor(SkArenaAlloc * arena,const VertexSpec & spec,const GrShaderCaps & caps,const GrBackendFormat & backendFormat,GrSamplerState samplerState,const skgpu::Swizzle & swizzle,sk_sp<GrColorSpaceXform> textureColorSpaceXform,Saturate saturate)933 GrGeometryProcessor* MakeTexturedProcessor(SkArenaAlloc* arena,
934                                            const VertexSpec& spec,
935                                            const GrShaderCaps& caps,
936                                            const GrBackendFormat& backendFormat,
937                                            GrSamplerState samplerState,
938                                            const skgpu::Swizzle& swizzle,
939                                            sk_sp<GrColorSpaceXform> textureColorSpaceXform,
940                                            Saturate saturate) {
941     return QuadPerEdgeAAGeometryProcessor::Make(arena, spec, caps, backendFormat, samplerState,
942                                                 swizzle, std::move(textureColorSpaceXform),
943                                                 saturate);
944 }
945 
946 }  // namespace skgpu::ganesh::QuadPerEdgeAA
947