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