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