GrQuadUtils: Handle degenerate persp quads where edges intersect outside quad

We were replacing points with the intersection of opposite edges.
Because of the distance tolerance we're using that may fall outside
of the original quad. Detect those cases and use averages of
intersection points instead.

Bug: chromium:1167277
Change-Id: I36b172f19339839bb21c060ddfe8109c184e9327
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/356311
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
Brian Salomon 2021-01-20 15:56:04 -05:00 committed by Skia Commit-Bot
parent c8f44ca25c
commit 6d717d401a
3 changed files with 76 additions and 17 deletions

41
gm/crbug_1167277.cpp Normal file
View File

@ -0,0 +1,41 @@
/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "gm/gm.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
// This quad would, depending on which aa flags are used, would degenerate when inset. We'd replace
// and duplicate some of the inset points to make a triangle. However, one of the triangle points
// would be far outside the original quad.
DEF_SIMPLE_GM(crbug_1167277, canvas, 230, 320) {
canvas->translate(-1250, -900);
// Matrix, clip, and quad values taken from Chrome repro scenario.
SkMatrix ctm = SkMatrix::MakeAll(
SkBits2Float(0xbf8fcfae), SkBits2Float(0xbeae25ee), SkBits2Float(0x449ca6db),
SkBits2Float(0x3c9dc40f), SkBits2Float(0xbf950e35), SkBits2Float(0x4487da43),
SkBits2Float(0xb8d4d6bc), SkBits2Float(0xb92fbb29), SkBits2Float(0x3f6f605c));
SkRect rect = {SkBits2Float(0x00000000), SkBits2Float(0x00000000),
SkBits2Float(0x41880000), SkBits2Float(0x43440000)};
SkPoint clip[4] = {{SkBits2Float(0x3ef434a2), SkBits2Float(0x43440004)},
{SkBits2Float(0x00000000), SkBits2Float(0x43440009)},
{SkBits2Float(0x38ef605d), SkBits2Float(0x38ef605d)},
{SkBits2Float(0x3ef436e3), SkBits2Float(0x396f5d30)}};
SkColor color = SK_ColorGREEN;
for (int flags = 0; flags < static_cast<int>(SkCanvas::kAll_QuadAAFlags); ++flags) {
SkCanvas::QuadAAFlags aaFlags = static_cast<SkCanvas::QuadAAFlags>(flags);
canvas->save();
canvas->concat(ctm);
canvas->experimental_DrawEdgeAAQuad(rect, clip, aaFlags, color, SkBlendMode::kSrcOver);
canvas->restore();
canvas->translate(5, 0);
SkColor rgb = color & 0x00FFFFFF;
color = 0xFF000000 | (rgb << 4) | (rgb >> 20);
}
}

View File

@ -115,6 +115,7 @@ gm_sources = [
"$_gm/crbug_1139750.cpp",
"$_gm/crbug_1156804.cpp",
"$_gm/crbug_1162942.cpp",
"$_gm/crbug_1167277.cpp",
"$_gm/crbug_224618.cpp",
"$_gm/crbug_691386.cpp",
"$_gm/crbug_788500.cpp",

View File

@ -783,7 +783,14 @@ int TessellationHelper::EdgeEquations::computeDegenerateQuad(const V4f& signedEd
return 2;
} else {
// This turns into a triangle. Replace corners as needed with the intersections between
// (e0,e3) and (e1,e2), which must now be calculated
// (e0,e3) and (e1,e2), which must now be calculated. Because of kDistTolarance we can
// have cases where the intersection lies far outside the quad. For example, consider top
// and bottom edges that are nearly parallel and their intersections with the right edge are
// nearly but not quite swapped (top edge intersection is barely above bottom edge
// intersection). In this case we replace the point with the average of itself and the point
// calculated using the edge equation it failed (in the example case this would be the
// average of the points calculated by the top and bottom edges intersected with the right
// edge.)
using V2f = skvx::Vec<2, float>;
V2f eDenom = skvx::shuffle<0, 1>(fA) * skvx::shuffle<3, 2>(fB) -
skvx::shuffle<0, 1>(fB) * skvx::shuffle<3, 2>(fA);
@ -792,9 +799,26 @@ int TessellationHelper::EdgeEquations::computeDegenerateQuad(const V4f& signedEd
V2f ey = (skvx::shuffle<0, 1>(oc) * skvx::shuffle<3, 2>(fA) -
skvx::shuffle<0, 1>(fA) * skvx::shuffle<3, 2>(oc)) / eDenom;
if (SkScalarAbs(eDenom[0]) > kTolerance) {
px = if_then_else(d1v0, V4f(ex[0]), px);
py = if_then_else(d1v0, V4f(ey[0]), py);
V4f avgX = 0.5f * (skvx::shuffle<0, 1, 0, 2>(px) + skvx::shuffle<2, 3, 1, 3>(px));
V4f avgY = 0.5f * (skvx::shuffle<0, 1, 0, 2>(py) + skvx::shuffle<2, 3, 1, 3>(py));
for (int i = 0; i < 4; ++i) {
// Note that we would not have taken this branch if any point failed both of its edges
// tests. That is, it can't be the case that d1v0[i] and d2v0[i] are both true.
if (dists1[i] < -kDistTolerance && abs(eDenom[0]) > kTolerance) {
px[i] = ex[0];
py[i] = ey[0];
} else if (d1v0[i]) {
px[i] = avgX[i % 2];
py[i] = avgY[i % 2];
} else if (dists2[i] < -kDistTolerance && abs(eDenom[1]) > kTolerance) {
px[i] = ex[1];
py[i] = ey[1];
} else if (d2v0[i]) {
px[i] = avgX[i / 2 + 2];
py[i] = avgY[i / 2 + 2];
}
}
// If we replace a vertex with an intersection then it will not fall along the
// edges that intersect at the original vertex. When we apply AA later to the
// original points we move along the original 3d edges to move towards the 2d
@ -802,14 +826,7 @@ int TessellationHelper::EdgeEquations::computeDegenerateQuad(const V4f& signedEd
// can only move along 1 edge, but now the point we're moving toward isn't
// on that edge. Thus, we provide an additional degree of freedom by turning
// AA on for both edges if either edge is AA.
*aaMask = *aaMask | (d1v0 & skvx::shuffle<2, 0, 3, 1>(*aaMask));
}
if (SkScalarAbs(eDenom[1]) > kTolerance) {
px = if_then_else(d2v0, V4f(ex[1]), px);
py = if_then_else(d2v0, V4f(ey[1]), py);
*aaMask = *aaMask | (d2v0 & skvx::shuffle<2, 0, 3, 1>(*aaMask));
}
*aaMask = *aaMask | (d1Or2 & skvx::shuffle<2, 0, 3, 1>(*aaMask));
*x2d = px;
*y2d = py;
return 3;