Flutter Engine
The Flutter Engine
Classes | Functions
GrQuadUtils Namespace Reference

Classes

class  TessellationHelper
 

Functions

void ResolveAAType (GrAAType requestedAAType, GrQuadAAFlags requestedEdgeFlags, const GrQuad &quad, GrAAType *outAAType, GrQuadAAFlags *outEdgeFlags)
 
int ClipToW0 (DrawQuad *quad, DrawQuad *extraVertices)
 
bool CropToRect (const SkRect &cropRect, GrAA cropAA, DrawQuad *quad, bool computeLocal)
 
bool WillUseHairline (const GrQuad &quad, GrAAType aaType, GrQuadAAFlags edgeFlags)
 
void Outset (const skvx::float4 &edgeDistances, GrQuad *quad)
 

Function Documentation

◆ ClipToW0()

int GrQuadUtils::ClipToW0 ( DrawQuad quad,
DrawQuad extraVertices 
)

Clip the device vertices of 'quad' to be in front of the W = 0 plane (w/in epsilon). The local coordinates will be updated to match the new clipped vertices. This returns the number of clipped quads that need to be drawn: 0 if 'quad' was entirely behind the plane, 1 if 'quad' did not need to be clipped or if 2 or 3 vertices were clipped, or 2 if 'quad' had one vertex clipped (producing a pentagonal shape spanned by 'quad' and 'extraVertices').

Definition at line 399 of file GrQuadUtils.cpp.

399 {
400 using Vertices = TessellationHelper::Vertices;
401
402 SkASSERT(quad && extraVertices);
403
405 // W implicitly 1s for each vertex, so nothing to do but draw unmodified 'quad'
406 return 1;
407 }
408
409 mask4 validW = quad->fDevice.w4f() >= SkPathPriv::kW0PlaneDistance;
410 if (all(validW)) {
411 // Nothing to clip, can proceed normally drawing just 'quad'
412 return 1;
413 } else if (!any(validW)) {
414 // Everything is clipped, so draw nothing
415 return 0;
416 }
417
418 // The clipped local coordinates will most likely not remain rectilinear
419 GrQuad::Type localType = quad->fLocal.quadType();
420 if (localType < GrQuad::Type::kGeneral) {
421 localType = GrQuad::Type::kGeneral;
422 }
423
424 // If we got here, there are 1, 2, or 3 points behind the w = 0 plane. If 2 or 3 points are
425 // clipped we can define a new quad that covers the clipped shape directly. If there's 1 clipped
426 // out, the new geometry is a pentagon.
427 Vertices v;
428 v.reset(quad->fDevice, &quad->fLocal);
429
430 int clipCount = (validW[0] ? 0 : 1) + (validW[1] ? 0 : 1) +
431 (validW[2] ? 0 : 1) + (validW[3] ? 0 : 1);
432 SkASSERT(clipCount >= 1 && clipCount <= 3);
433
434 // FIXME de-duplicate from the projectedBounds() calculations.
435 float4 t = (SkPathPriv::kW0PlaneDistance - v.fW) / (next_ccw(v.fW) - v.fW);
436
437 Vertices clip;
438 clip.fX = (t * next_ccw(v.fX) + (1.f - t) * v.fX);
439 clip.fY = (t * next_ccw(v.fY) + (1.f - t) * v.fY);
441
442 clip.fU = (t * next_ccw(v.fU) + (1.f - t) * v.fU);
443 clip.fV = (t * next_ccw(v.fV) + (1.f - t) * v.fV);
444 clip.fR = (t * next_ccw(v.fR) + (1.f - t) * v.fR);
445
446 mask4 ccwValid = next_ccw(v.fW) >= SkPathPriv::kW0PlaneDistance;
447 mask4 cwValid = next_cw(v.fW) >= SkPathPriv::kW0PlaneDistance;
448
449 if (clipCount != 1) {
450 // Simplest case, replace behind-w0 points with their clipped points by following CCW edge
451 // or CW edge, depending on if the edge crosses from neg. to pos. w or pos. to neg.
452 SkASSERT(clipCount == 2 || clipCount == 3);
453
454 // NOTE: when 3 vertices are clipped, this results in a degenerate quad where one vertex
455 // is replicated. This is preferably to inserting a 3rd vertex on the w = 0 intersection
456 // line because two parallel edges make inset/outset math unstable for large quads.
457 v.fX = if_then_else(validW, v.fX,
458 if_then_else((!ccwValid) & (!cwValid), next_ccw(clip.fX),
459 if_then_else(ccwValid, clip.fX, /* cwValid */ next_cw(clip.fX))));
460 v.fY = if_then_else(validW, v.fY,
461 if_then_else((!ccwValid) & (!cwValid), next_ccw(clip.fY),
462 if_then_else(ccwValid, clip.fY, /* cwValid */ next_cw(clip.fY))));
463 v.fW = if_then_else(validW, v.fW, clip.fW);
464
465 v.fU = if_then_else(validW, v.fU,
466 if_then_else((!ccwValid) & (!cwValid), next_ccw(clip.fU),
467 if_then_else(ccwValid, clip.fU, /* cwValid */ next_cw(clip.fU))));
468 v.fV = if_then_else(validW, v.fV,
469 if_then_else((!ccwValid) & (!cwValid), next_ccw(clip.fV),
470 if_then_else(ccwValid, clip.fV, /* cwValid */ next_cw(clip.fV))));
471 v.fR = if_then_else(validW, v.fR,
472 if_then_else((!ccwValid) & (!cwValid), next_ccw(clip.fR),
473 if_then_else(ccwValid, clip.fR, /* cwValid */ next_cw(clip.fR))));
474
475 // For 2 or 3 clipped vertices, the resulting shape is a quad or a triangle, so it can be
476 // entirely represented in 'quad'.
477 v.asGrQuads(&quad->fDevice, GrQuad::Type::kPerspective,
478 &quad->fLocal, localType);
479 return 1;
480 } else {
481 // The clipped geometry is a pentagon, so it will be represented as two quads connected by
482 // a new non-AA edge. Use the midpoint along one of the unclipped edges as a split vertex.
483 Vertices mid;
484 mid.fX = 0.5f * (v.fX + next_ccw(v.fX));
485 mid.fY = 0.5f * (v.fY + next_ccw(v.fY));
486 mid.fW = 0.5f * (v.fW + next_ccw(v.fW));
487
488 mid.fU = 0.5f * (v.fU + next_ccw(v.fU));
489 mid.fV = 0.5f * (v.fV + next_ccw(v.fV));
490 mid.fR = 0.5f * (v.fR + next_ccw(v.fR));
491
492 // Make a quad formed by the 2 clipped points, the inserted mid point, and the good vertex
493 // that is CCW rotated from the clipped vertex.
494 Vertices v2;
495 v2.fUVRCount = v.fUVRCount;
496 v2.fX = if_then_else((!validW) | (!ccwValid), clip.fX,
497 if_then_else(cwValid, next_cw(mid.fX), v.fX));
498 v2.fY = if_then_else((!validW) | (!ccwValid), clip.fY,
499 if_then_else(cwValid, next_cw(mid.fY), v.fY));
500 v2.fW = if_then_else((!validW) | (!ccwValid), clip.fW,
501 if_then_else(cwValid, next_cw(mid.fW), v.fW));
502
503 v2.fU = if_then_else((!validW) | (!ccwValid), clip.fU,
504 if_then_else(cwValid, next_cw(mid.fU), v.fU));
505 v2.fV = if_then_else((!validW) | (!ccwValid), clip.fV,
506 if_then_else(cwValid, next_cw(mid.fV), v.fV));
507 v2.fR = if_then_else((!validW) | (!ccwValid), clip.fR,
508 if_then_else(cwValid, next_cw(mid.fR), v.fR));
509 // The non-AA edge for this quad is the opposite of the clipped vertex's edge
510 GrQuadAAFlags v2EdgeFlag = (!validW[0] ? GrQuadAAFlags::kRight : // left clipped -> right
511 (!validW[1] ? GrQuadAAFlags::kTop : // bottom clipped -> top
512 (!validW[2] ? GrQuadAAFlags::kBottom : // top clipped -> bottom
513 GrQuadAAFlags::kLeft))); // right clipped -> left
514 extraVertices->fEdgeFlags = quad->fEdgeFlags & ~v2EdgeFlag;
515
516 // Make a quad formed by the remaining two good vertices, one clipped point, and the
517 // inserted mid point.
518 v.fX = if_then_else(!validW, next_cw(clip.fX),
519 if_then_else(!cwValid, mid.fX, v.fX));
520 v.fY = if_then_else(!validW, next_cw(clip.fY),
521 if_then_else(!cwValid, mid.fY, v.fY));
522 v.fW = if_then_else(!validW, clip.fW,
523 if_then_else(!cwValid, mid.fW, v.fW));
524
525 v.fU = if_then_else(!validW, next_cw(clip.fU),
526 if_then_else(!cwValid, mid.fU, v.fU));
527 v.fV = if_then_else(!validW, next_cw(clip.fV),
528 if_then_else(!cwValid, mid.fV, v.fV));
529 v.fR = if_then_else(!validW, next_cw(clip.fR),
530 if_then_else(!cwValid, mid.fR, v.fR));
531 // The non-AA edge for this quad is the clipped vertex's edge
532 GrQuadAAFlags v1EdgeFlag = (!validW[0] ? GrQuadAAFlags::kLeft :
533 (!validW[1] ? GrQuadAAFlags::kBottom :
534 (!validW[2] ? GrQuadAAFlags::kTop :
536
537 v.asGrQuads(&quad->fDevice, GrQuad::Type::kPerspective,
538 &quad->fLocal, localType);
539 quad->fEdgeFlags &= ~v1EdgeFlag;
540
541 v2.asGrQuads(&extraVertices->fDevice, GrQuad::Type::kPerspective,
542 &extraVertices->fLocal, localType);
543 // Caller must draw both 'quad' and 'extraVertices' to cover the clipped geometry
544 return 2;
545 }
546}
static AI skvx::Vec< 4, T > next_ccw(const skvx::Vec< 4, T > &v)
Definition: GrQuadUtils.cpp:43
static AI skvx::Vec< 4, T > next_cw(const skvx::Vec< 4, T > &v)
Definition: GrQuadUtils.cpp:38
GrQuadAAFlags
Definition: GrTypesPriv.h:247
#define SkASSERT(cond)
Definition: SkAssert.h:116
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
SI T if_then_else(C cond, T t, T e)
Vec2Value v2
Type quadType() const
Definition: GrQuad.h:118
Type
Definition: GrQuad.h:35
skvx::Vec< 4, float > w4f() const
Definition: GrQuad.h:115
static constexpr SkScalar kW0PlaneDistance
Definition: SkPathPriv.h:41
SIT bool all(const Vec< 1, T > &x)
Definition: SkVx.h:582
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530
GrQuad fLocal
Definition: GrQuad.h:186
GrQuad fDevice
Definition: GrQuad.h:185
GrQuadAAFlags fEdgeFlags
Definition: GrQuad.h:187
Definition: SkVx.h:83

◆ CropToRect()

bool GrQuadUtils::CropToRect ( const SkRect cropRect,
GrAA  cropAA,
DrawQuad quad,
bool  computeLocal = true 
)

Crops quad to the provided device-space axis-aligned rectangle. If the intersection of this quad (projected) and cropRect results in a quadrilateral, this returns true. If not, this quad may be updated to be a smaller quad of the same type such that its intersection with cropRect is visually the same. This function assumes that the 'quad' coordinates are finite.

The provided edge flags are updated to reflect edges clipped by cropRect (toggling on or off based on cropAA policy). If provided, the local coordinates will be updated to reflect the updated device coordinates of this quad.

If 'computeLocal' is false, the local coordinates in 'quad' will not be modified.

Definition at line 548 of file GrQuadUtils.cpp.

548 {
549 SkASSERT(quad->fDevice.isFinite());
550
552 // crop_rect and crop_rect_simple keep the rectangles as rectangles, so the intersection
553 // of the crop and quad can be calculated exactly. Some care must be taken if the quad
554 // is axis-aligned but does not satisfy asRect() due to flips, etc.
555 GrQuadAAFlags clippedEdges;
556 if (computeLocal) {
557 if (is_simple_rect(quad->fDevice) && is_simple_rect(quad->fLocal)) {
558 clippedEdges = crop_simple_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
559 quad->fLocal.xs(), quad->fLocal.ys());
560 } else {
561 clippedEdges = crop_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
562 quad->fLocal.xs(), quad->fLocal.ys(), quad->fLocal.ws());
563 }
564 } else {
565 if (is_simple_rect(quad->fDevice)) {
566 clippedEdges = crop_simple_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
567 nullptr, nullptr);
568 } else {
569 clippedEdges = crop_rect(cropRect, quad->fDevice.xs(), quad->fDevice.ys(),
570 nullptr, nullptr, nullptr);
571 }
572 }
573
574 // Apply the clipped edge updates to the original edge flags
575 if (cropAA == GrAA::kYes) {
576 // Turn on all edges that were clipped
577 quad->fEdgeFlags |= clippedEdges;
578 } else {
579 // Turn off all edges that were clipped
580 quad->fEdgeFlags &= ~clippedEdges;
581 }
582 return true;
583 }
584
585 if (computeLocal || quad->fDevice.quadType() == GrQuad::Type::kPerspective) {
586 // FIXME (michaelludwig) Calculate cropped local coordinates when not kAxisAligned
587 // FIXME (michaelludwig) crbug.com/1204347 and skbug.com/9906 - disable this when there's
588 // perspective; it does not prove numerical robust enough in the wild and should be
589 // revisited.
590 return false;
591 }
592
593 float4 devX = quad->fDevice.x4f();
594 float4 devY = quad->fDevice.y4f();
595
596 float4 clipX = {cropRect.fLeft, cropRect.fLeft, cropRect.fRight, cropRect.fRight};
597 float4 clipY = {cropRect.fTop, cropRect.fBottom, cropRect.fTop, cropRect.fBottom};
598
599 // Calculate barycentric coordinates for the 4 rect corners in the 2 triangles that the quad
600 // is tessellated into when drawn.
601 float4 u1, v1, w1;
602 float4 u2, v2, w2;
603 if (!barycentric_coords(devX[0], devY[0], devX[1], devY[1], devX[2], devY[2], clipX, clipY,
604 &u1, &v1, &w1) ||
605 !barycentric_coords(devX[1], devY[1], devX[3], devY[3], devX[2], devY[2], clipX, clipY,
606 &u2, &v2, &w2)) {
607 // Bad triangles, skip cropping
608 return false;
609 }
610
611 // clipDevRect is completely inside this quad if each corner is in at least one of two triangles
612 mask4 inTri1 = inside_triangle(u1, v1, w1);
613 mask4 inTri2 = inside_triangle(u2, v2, w2);
614 if (all(inTri1 | inTri2)) {
615 // We can crop to exactly the clipDevRect.
616 // FIXME (michaelludwig) - there are other ways to have determined quad covering the clip
617 // rect, but the barycentric coords will be useful to derive local coordinates in the future
618
619 // Since we are cropped to exactly clipDevRect, we have discarded any perspective and the
620 // type becomes kRect. If updated locals were requested, they will incorporate perspective.
621 // FIXME (michaelludwig) - once we have local coordinates handled, it may be desirable to
622 // keep the draw as perspective so that the hardware does perspective interpolation instead
623 // of pushing it into a local coord w and having the shader do an extra divide.
624 clipX.store(quad->fDevice.xs());
625 clipY.store(quad->fDevice.ys());
627
628 // Update the edge flags to match the clip setting since all 4 edges have been clipped
630
631 return true;
632 }
633
634 // FIXME (michaelludwig) - use TessellationHelper's inset/outset math to move
635 // edges to the closest clip corner they are outside of
636
637 return false;
638}
static GrQuadAAFlags crop_rect(const SkRect &clipDevRect, float x[4], float y[4], float lx[4], float ly[4], float lw[4])
static bool barycentric_coords(float x0, float y0, float x1, float y1, float x2, float y2, const float4 &testX, const float4 &testY, float4 *u, float4 *v, float4 *w)
static GrQuadAAFlags crop_simple_rect(const SkRect &clipDevRect, float x[4], float y[4], float lx[4], float ly[4])
static mask4 inside_triangle(const float4 &u, const float4 &v, const float4 &w)
static bool is_simple_rect(const GrQuad &quad)
const float * xs() const
Definition: GrQuad.h:132
const float * ys() const
Definition: GrQuad.h:134
skvx::Vec< 4, float > y4f() const
Definition: GrQuad.h:114
skvx::Vec< 4, float > x4f() const
Definition: GrQuad.h:113
void setQuadType(Type newType)
Definition: GrQuad.h:140
bool isFinite() const
Definition: GrQuad.h:95
const float * ws() const
Definition: GrQuad.h:136
SkScalar fBottom
larger y-axis bounds
Definition: extension.cpp:17
SkScalar fLeft
smaller x-axis bounds
Definition: extension.cpp:14
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
SkScalar fTop
smaller y-axis bounds
Definition: extension.cpp:15
SKVX_ALWAYS_INLINE void store(void *ptr) const
Definition: SkVx.h:112

◆ Outset()

void GrQuadUtils::Outset ( const skvx::float4 edgeDistances,
GrQuad quad 
)
inline

Definition at line 222 of file GrQuadUtils.h.

222 {
223 TessellationHelper outsetter;
224 outsetter.reset(*quad, nullptr);
225 outsetter.outset(edgeDistances, quad, nullptr);
226}
void reset(const GrQuad &deviceQuad, const GrQuad *localQuad)
void outset(const skvx::float4 &edgeDistances, GrQuad *deviceOutset, GrQuad *localOutset)

◆ ResolveAAType()

void GrQuadUtils::ResolveAAType ( GrAAType  requestedAAType,
GrQuadAAFlags  requestedEdgeFlags,
const GrQuad quad,
GrAAType outAAType,
GrQuadAAFlags outEdgeFlags 
)

Definition at line 366 of file GrQuadUtils.cpp.

367 {
368 // Most cases will keep the requested types unchanged
369 *outAAType = requestedAAType;
370 *outEdgeFlags = requestedEdgeFlags;
371
372 switch (requestedAAType) {
373 // When aa type is coverage, disable AA if the edge configuration doesn't actually need it
375 if (requestedEdgeFlags == GrQuadAAFlags::kNone) {
376 // This can happen when quads are drawn in bulk, where the requestedAAType was
377 // conservatively enabled and the edge flags are per-entry.
378 *outAAType = GrAAType::kNone;
379 } else if (quad.quadType() == GrQuad::Type::kAxisAligned &&
380 !quad.aaHasEffectOnRect(requestedEdgeFlags)) {
381 // For coverage AA, if the quad is a rect and AA-enabled edges line up with pixel
382 // boundaries, then overall AA and per-edge AA can be completely disabled.
383 *outAAType = GrAAType::kNone;
384 *outEdgeFlags = GrQuadAAFlags::kNone;
385 }
386
387 break;
388 // For no or msaa anti aliasing, override the edge flags since edge flags only make sense
389 // when coverage aa is being used.
390 case GrAAType::kNone:
391 *outEdgeFlags = GrQuadAAFlags::kNone;
392 break;
393 case GrAAType::kMSAA:
394 *outEdgeFlags = GrQuadAAFlags::kAll;
395 break;
396 }
397}
bool aaHasEffectOnRect(GrQuadAAFlags edgeFlags) const
Definition: GrQuad.cpp:135

◆ WillUseHairline()

bool GrQuadUtils::WillUseHairline ( const GrQuad quad,
GrAAType  aaType,
GrQuadAAFlags  edgeFlags 
)

Definition at line 640 of file GrQuadUtils.cpp.

640 {
641 if (aaType != GrAAType::kCoverage || edgeFlags != GrQuadAAFlags::kAll) {
642 // Non-aa or msaa don't do any outsetting so they will not be hairlined; mixed edge flags
643 // could be hairlined in theory, but applying hairline bloat would extend beyond the
644 // original tiled shape.
645 return false;
646 }
647
648 if (quad.quadType() == GrQuad::Type::kAxisAligned) {
649 // Fast path that avoids computing edge properties via TessellationHelper.
650 // Taking the absolute value of the diagonals always produces the minimum of width or
651 // height given that this is axis-aligned, regardless of mirror or 90/180-degree rotations.
652 float d = std::min(std::abs(quad.x(3) - quad.x(0)), std::abs(quad.y(3) - quad.y(0)));
653 return d < 1.f;
654 } else {
655 TessellationHelper helper;
656 helper.reset(quad, nullptr);
657 return helper.isSubpixel();
658 }
659}
float y(int i) const
Definition: GrQuad.h:109
float x(int i) const
Definition: GrQuad.h:108
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE auto & d
Definition: main.cc:19
static float min(float r, float g, float b)
Definition: hsl.cpp:48
SIN Vec< N, float > abs(const Vec< N, float > &x)
Definition: SkVx.h:707