Optimize trivial per-edge aa rect tessellation
Bug: skia: Change-Id: I380b443216b238e13bfff7ed13526dda1380ef99 Reviewed-on: https://skia-review.googlesource.com/c/171723 Reviewed-by: Brian Salomon <bsalomon@google.com> Commit-Queue: Michael Ludwig <michaelludwig@google.com>
This commit is contained in:
parent
9a061a5c2b
commit
b336c39e07
@ -19,6 +19,7 @@
|
|||||||
// Allow some tolerance from floating point matrix transformations, but SkScalarNearlyEqual doesn't
|
// Allow some tolerance from floating point matrix transformations, but SkScalarNearlyEqual doesn't
|
||||||
// support comparing infinity, and coords_form_rect should return true for infinite edges
|
// support comparing infinity, and coords_form_rect should return true for infinite edges
|
||||||
#define NEARLY_EQUAL(f1, f2) (f1 == f2 || SkScalarNearlyEqual(f1, f2, 1e-5f))
|
#define NEARLY_EQUAL(f1, f2) (f1 == f2 || SkScalarNearlyEqual(f1, f2, 1e-5f))
|
||||||
|
#define NEARLY_ZERO(f1) NEARLY_EQUAL(f1, 0.f)
|
||||||
|
|
||||||
// This is not the most performance critical function; code using GrQuad should rely on the faster
|
// This is not the most performance critical function; code using GrQuad should rely on the faster
|
||||||
// quad type from matrix path, so this will only be called as part of SkASSERT.
|
// quad type from matrix path, so this will only be called as part of SkASSERT.
|
||||||
@ -29,10 +30,31 @@ static bool coords_form_rect(const float xs[4], const float ys[4]) {
|
|||||||
NEARLY_EQUAL(ys[0], ys[1]) && NEARLY_EQUAL(ys[2], ys[3]));
|
NEARLY_EQUAL(ys[0], ys[1]) && NEARLY_EQUAL(ys[2], ys[3]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool coords_rectilinear(const float xs[4], const float ys[4]) {
|
||||||
|
// Edge from 0 to 1 should have the same length as edge from 3 to 2
|
||||||
|
// and edge from 1 to 3 should have the same length as edge from 2 to 0
|
||||||
|
// noo that makes it a parallelogram, need dot product between edge 0 to 1 and edge 1 to 3 = 0
|
||||||
|
// and 0-2 and 2-3 is 0.
|
||||||
|
static constexpr auto dot = SkPoint::DotProduct;
|
||||||
|
SkVector e0{xs[1] - xs[0], ys[1] - ys[0]}; // Connects to e1 and e2(repeat)
|
||||||
|
SkVector e1{xs[3] - xs[1], ys[3] - ys[1]}; // connects to e0(repeat) and e3
|
||||||
|
SkVector e2{xs[0] - xs[2], ys[0] - ys[2]}; // connects to e0 and e3(repeat)
|
||||||
|
SkVector e3{xs[2] - xs[3], ys[2] - ys[3]}; // connects to e1(repeat) and e2
|
||||||
|
|
||||||
|
return NEARLY_ZERO(dot(e0, e1)) && NEARLY_ZERO(dot(e1, e3)) &&
|
||||||
|
NEARLY_ZERO(dot(e2, e0)) && NEARLY_ZERO(dot(e3, e2));
|
||||||
|
}
|
||||||
|
|
||||||
GrQuadType GrQuad::quadType() const {
|
GrQuadType GrQuad::quadType() const {
|
||||||
// Since GrQuad applies any perspective information at construction time, there's only two
|
// Since GrQuad applies any perspective information at construction time, there's only two
|
||||||
// types to choose from.
|
// types to choose from.
|
||||||
return coords_form_rect(fX, fY) ? GrQuadType::kRect : GrQuadType::kStandard;
|
if (coords_form_rect(fX, fY)) {
|
||||||
|
return GrQuadType::kRect;
|
||||||
|
} else if (coords_rectilinear(fX, fY)) {
|
||||||
|
return GrQuadType::kRectilinear;
|
||||||
|
} else {
|
||||||
|
return GrQuadType::kStandard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GrQuadType GrPerspQuad::quadType() const {
|
GrQuadType GrPerspQuad::quadType() const {
|
||||||
@ -40,7 +62,13 @@ GrQuadType GrPerspQuad::quadType() const {
|
|||||||
return GrQuadType::kPerspective;
|
return GrQuadType::kPerspective;
|
||||||
} else {
|
} else {
|
||||||
// Rect or standard quad, can ignore w since they are all ones
|
// Rect or standard quad, can ignore w since they are all ones
|
||||||
return coords_form_rect(fX, fY) ? GrQuadType::kRect : GrQuadType::kStandard;
|
if (coords_form_rect(fX, fY)) {
|
||||||
|
return GrQuadType::kRect;
|
||||||
|
} else if (coords_rectilinear(fX, fY)) {
|
||||||
|
return GrQuadType::kRectilinear;
|
||||||
|
} else {
|
||||||
|
return GrQuadType::kStandard;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -97,6 +125,8 @@ template void GrResolveAATypeForQuad(GrAAType, GrQuadAAFlags, const GrPerspQuad&
|
|||||||
GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix) {
|
GrQuadType GrQuadTypeForTransformedRect(const SkMatrix& matrix) {
|
||||||
if (matrix.rectStaysRect()) {
|
if (matrix.rectStaysRect()) {
|
||||||
return GrQuadType::kRect;
|
return GrQuadType::kRect;
|
||||||
|
} else if (matrix.preservesRightAngles()) {
|
||||||
|
return GrQuadType::kRectilinear;
|
||||||
} else if (matrix.hasPerspective()) {
|
} else if (matrix.hasPerspective()) {
|
||||||
return GrQuadType::kPerspective;
|
return GrQuadType::kPerspective;
|
||||||
} else {
|
} else {
|
||||||
|
@ -20,11 +20,13 @@ enum class GrQuadAAFlags;
|
|||||||
// 1. Stays a rectangle - the matrix rectStaysRect() is true, or x(0) == x(1) && x(2) == x(3)
|
// 1. Stays a rectangle - the matrix rectStaysRect() is true, or x(0) == x(1) && x(2) == x(3)
|
||||||
// and y(0) == y(2) && y(1) == y(3). Or under mirrors, x(0) == x(2) && x(1) == x(3) and
|
// and y(0) == y(2) && y(1) == y(3). Or under mirrors, x(0) == x(2) && x(1) == x(3) and
|
||||||
// y(0) == y(1) && y(2) == y(3).
|
// y(0) == y(1) && y(2) == y(3).
|
||||||
// 2. Is a quadrilateral - the matrix does not have perspective, but may rotate or skew, or
|
// 2. Is rectilinear - the matrix does not have skew or perspective, but may rotate (unlike #1)
|
||||||
|
// 3. Is a quadrilateral - the matrix does not have perspective, but may rotate or skew, or
|
||||||
// ws() == all ones.
|
// ws() == all ones.
|
||||||
// 3. Is a perspective quad - the matrix has perspective, subsuming all previous quad types.
|
// 4. Is a perspective quad - the matrix has perspective, subsuming all previous quad types.
|
||||||
enum class GrQuadType {
|
enum class GrQuadType {
|
||||||
kRect,
|
kRect,
|
||||||
|
kRectilinear,
|
||||||
kStandard,
|
kStandard,
|
||||||
kPerspective,
|
kPerspective,
|
||||||
kLast = kPerspective
|
kLast = kPerspective
|
||||||
|
@ -79,6 +79,14 @@ struct GrVertexWriter {
|
|||||||
this->write(remainder...);
|
this->write(remainder...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename... Args>
|
||||||
|
void write(const Sk4f& vector, const Args&... remainder) {
|
||||||
|
float buffer[4];
|
||||||
|
vector.store(buffer);
|
||||||
|
this->write<float, 4>(buffer);
|
||||||
|
this->write(remainder...);
|
||||||
|
}
|
||||||
|
|
||||||
void write() {}
|
void write() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,25 +15,91 @@
|
|||||||
#include "glsl/GrGLSLVertexGeoBuilder.h"
|
#include "glsl/GrGLSLVertexGeoBuilder.h"
|
||||||
#include "SkNx.h"
|
#include "SkNx.h"
|
||||||
|
|
||||||
|
#define AI SK_ALWAYS_INLINE
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
static AI Sk4f fma(const Sk4f& f, const Sk4f& m, const Sk4f& a) {
|
||||||
|
return SkNx_fma<4, float>(f, m, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// These rotate the points/edge values either clockwise or counterclockwise assuming tri strip
|
||||||
|
// order.
|
||||||
|
static AI Sk4f nextCW(const Sk4f& v) {
|
||||||
|
return SkNx_shuffle<2, 0, 3, 1>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AI Sk4f nextCCW(const Sk4f& v) {
|
||||||
|
return SkNx_shuffle<1, 3, 0, 2>(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fills Sk4f with 1f if edge bit is set, 0f otherwise. Edges are ordered LBTR to match CCW ordering
|
||||||
|
// of vertices in the quad.
|
||||||
|
static AI Sk4f compute_edge_mask(GrQuadAAFlags aaFlags) {
|
||||||
|
return Sk4f((GrQuadAAFlags::kLeft & aaFlags) ? 1.f : 0.f,
|
||||||
|
(GrQuadAAFlags::kBottom & aaFlags) ? 1.f : 0.f,
|
||||||
|
(GrQuadAAFlags::kTop & aaFlags) ? 1.f : 0.f,
|
||||||
|
(GrQuadAAFlags::kRight & aaFlags) ? 1.f : 0.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AI void outset_masked_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths,
|
||||||
|
const Sk4f& mask, Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r,
|
||||||
|
int uvrCount) {
|
||||||
|
auto halfMask = 0.5f * mask;
|
||||||
|
auto maskCW = nextCW(halfMask);
|
||||||
|
*x += maskCW * -xdiff + halfMask * nextCW(xdiff);
|
||||||
|
*y += maskCW * -ydiff + halfMask * nextCW(ydiff);
|
||||||
|
if (uvrCount > 0) {
|
||||||
|
// We want to extend the texture coords by the same proportion as the positions.
|
||||||
|
maskCW *= invLengths;
|
||||||
|
halfMask *= nextCW(invLengths);
|
||||||
|
Sk4f udiff = nextCCW(*u) - *u;
|
||||||
|
Sk4f vdiff = nextCCW(*v) - *v;
|
||||||
|
*u += maskCW * -udiff + halfMask * nextCW(udiff);
|
||||||
|
*v += maskCW * -vdiff + halfMask * nextCW(vdiff);
|
||||||
|
if (uvrCount == 3) {
|
||||||
|
Sk4f rdiff = nextCCW(*r) - *r;
|
||||||
|
*r += maskCW * -rdiff + halfMask * nextCW(rdiff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static AI void outset_vertices(const Sk4f& xdiff, const Sk4f& ydiff, const Sk4f& invLengths,
|
||||||
|
Sk4f* x, Sk4f* y, Sk4f* u, Sk4f* v, Sk4f* r, int uvrCount) {
|
||||||
|
*x += 0.5f * (-xdiff + nextCW(xdiff));
|
||||||
|
*y += 0.5f * (-ydiff + nextCW(ydiff));
|
||||||
|
if (uvrCount > 0) {
|
||||||
|
Sk4f t = 0.5f * invLengths;
|
||||||
|
Sk4f udiff = nextCCW(*u) - *u;
|
||||||
|
Sk4f vdiff = nextCCW(*v) - *v;
|
||||||
|
*u += t * -udiff + nextCW(t) * nextCW(udiff);
|
||||||
|
*v += t * -vdiff + nextCW(t) * nextCW(vdiff);
|
||||||
|
if (uvrCount == 3) {
|
||||||
|
Sk4f rdiff = nextCCW(*r) - *r;
|
||||||
|
*r += t * -rdiff + nextCW(t) * nextCW(rdiff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static AI void compute_edge_distances(const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& x,
|
||||||
|
const Sk4f& y, const Sk4f& w, Sk4f edgeDistances[]) {
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
edgeDistances[i] = a * x[i] + b * y[i] + c * w[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// This computes the four edge equations for a quad, then outsets them and optionally computes a new
|
// This computes the four edge equations for a quad, then outsets them and optionally computes a new
|
||||||
// quad as the intersection points of the outset edges. 'x' and 'y' contain the original points as
|
// quad as the intersection points of the outset edges. 'x' and 'y' contain the original points as
|
||||||
// input and the outset points as output. 'a', 'b', and 'c' are the edge equation coefficients on
|
// input and the outset points as output. In order to be used as a component of perspective edge
|
||||||
// output. The values in x, y, u, v, and r are possibly updated if outsetting is needed.
|
// distance calculation, this exports edge equations in 'a', 'b', and 'c'. Use
|
||||||
// r is the local position's w component if it exists.
|
// compute_edge_distances to turn these equations into the distances needed by the shader. The
|
||||||
static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y, Sk4f* a,
|
// values in x, y, u, v, and r are possibly updated if outsetting is needed. r is the local
|
||||||
Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r,
|
// position's w component if it exists.
|
||||||
int uvrChannelCount, bool outsetCorners) {
|
static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y,
|
||||||
|
Sk4f* a, Sk4f* b, Sk4f* c, Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount, bool outset) {
|
||||||
SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
|
SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
|
||||||
|
|
||||||
static constexpr auto fma = SkNx_fma<4, float>;
|
// Compute edge vectors for the quad.
|
||||||
// These rotate the points/edge values either clockwise or counterclockwise assuming tri strip
|
|
||||||
// order.
|
|
||||||
auto nextCW = [](const Sk4f& v) { return SkNx_shuffle<2, 0, 3, 1>(v); };
|
|
||||||
auto nextCCW = [](const Sk4f& v) { return SkNx_shuffle<1, 3, 0, 2>(v); };
|
|
||||||
|
|
||||||
// Compute edge equations for the quad.
|
|
||||||
auto xnext = nextCCW(*x);
|
auto xnext = nextCCW(*x);
|
||||||
auto ynext = nextCCW(*y);
|
auto ynext = nextCCW(*y);
|
||||||
// xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
|
// xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
|
||||||
@ -43,7 +109,7 @@ static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f*
|
|||||||
xdiff *= invLengths;
|
xdiff *= invLengths;
|
||||||
ydiff *= invLengths;
|
ydiff *= invLengths;
|
||||||
|
|
||||||
// Use above vectors to compute edge equations.
|
// Use above vectors to compute edge equations (importantly before we outset positions).
|
||||||
*c = fma(xnext, *y, -ynext * *x) * invLengths;
|
*c = fma(xnext, *y, -ynext * *x) * invLengths;
|
||||||
// Make sure the edge equations have their normals facing into the quad in device space.
|
// Make sure the edge equations have their normals facing into the quad in device space.
|
||||||
auto test = fma(ydiff, nextCW(*x), fma(-xdiff, nextCW(*y), *c));
|
auto test = fma(ydiff, nextCW(*x), fma(-xdiff, nextCW(*y), *c));
|
||||||
@ -62,66 +128,91 @@ static void compute_quad_edges_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f*
|
|||||||
if (aaFlags != GrQuadAAFlags::kAll) {
|
if (aaFlags != GrQuadAAFlags::kAll) {
|
||||||
// This order is the same order the edges appear in xdiff/ydiff and therefore as the
|
// This order is the same order the edges appear in xdiff/ydiff and therefore as the
|
||||||
// edges in a/b/c.
|
// edges in a/b/c.
|
||||||
auto mask = Sk4f(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f,
|
Sk4f mask = compute_edge_mask(aaFlags);
|
||||||
GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f,
|
|
||||||
GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f,
|
|
||||||
GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f);
|
|
||||||
// Outset edge equations for masked out edges another pixel so that they always evaluate
|
// Outset edge equations for masked out edges another pixel so that they always evaluate
|
||||||
// >= 1.
|
// >= 1.
|
||||||
*c += (1.f - mask);
|
*c += (1.f - mask);
|
||||||
if (outsetCorners) {
|
if (outset) {
|
||||||
// Do the vertex outset.
|
outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount);
|
||||||
mask *= 0.5f;
|
|
||||||
auto maskCW = nextCW(mask);
|
|
||||||
*x += maskCW * -xdiff + mask * nextCW(xdiff);
|
|
||||||
*y += maskCW * -ydiff + mask * nextCW(ydiff);
|
|
||||||
if (uvrChannelCount > 0) {
|
|
||||||
// We want to extend the texture coords by the same proportion as the positions.
|
|
||||||
maskCW *= invLengths;
|
|
||||||
mask *= nextCW(invLengths);
|
|
||||||
Sk4f udiff = nextCCW(*u) - *u;
|
|
||||||
Sk4f vdiff = nextCCW(*v) - *v;
|
|
||||||
*u += maskCW * -udiff + mask * nextCW(udiff);
|
|
||||||
*v += maskCW * -vdiff + mask * nextCW(vdiff);
|
|
||||||
if (uvrChannelCount == 3) {
|
|
||||||
Sk4f rdiff = nextCCW(*r) - *r;
|
|
||||||
*r += maskCW * -rdiff + mask * nextCW(rdiff);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (outsetCorners) {
|
|
||||||
*x += 0.5f * (-xdiff + nextCW(xdiff));
|
|
||||||
*y += 0.5f * (-ydiff + nextCW(ydiff));
|
|
||||||
if (uvrChannelCount > 0) {
|
|
||||||
Sk4f t = 0.5f * invLengths;
|
|
||||||
Sk4f udiff = nextCCW(*u) - *u;
|
|
||||||
Sk4f vdiff = nextCCW(*v) - *v;
|
|
||||||
*u += t * -udiff + nextCW(t) * nextCW(udiff);
|
|
||||||
*v += t * -vdiff + nextCW(t) * nextCW(vdiff);
|
|
||||||
if (uvrChannelCount == 3) {
|
|
||||||
Sk4f rdiff = nextCCW(*r) - *r;
|
|
||||||
*r += t * -rdiff + nextCW(t) * nextCW(rdiff);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if (outset) {
|
||||||
|
outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generalizes the above function to extrapolate local coords such that after perspective division
|
// A specialization of the above function that can compute edge distances very quickly when it knows
|
||||||
// of the device coordinate, the original local coordinate value is at the original un-outset
|
// that the edges intersect at right angles, i.e. any transform other than skew and perspective
|
||||||
// device position. r is the local coordinate's w component.
|
// (GrQuadType::kRectilinear). Unlike the above function, this always outsets the corners since it
|
||||||
static void compute_quad_edges_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x, Sk4f* y,
|
// cannot be reused in the perspective case.
|
||||||
Sk4f* w, Sk4f* a, Sk4f* b, Sk4f* c,
|
static void compute_rectilinear_dists_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x,
|
||||||
Sk4f* u, Sk4f* v, Sk4f* r,
|
Sk4f* y, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) {
|
||||||
int uvrChannelCount) {
|
SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
|
||||||
|
auto xnext = nextCCW(*x);
|
||||||
|
auto ynext = nextCCW(*y);
|
||||||
|
// xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
|
||||||
|
auto xdiff = xnext - *x;
|
||||||
|
auto ydiff = ynext - *y;
|
||||||
|
// Need length and 1/length in this variant
|
||||||
|
auto lengths = fma(xdiff, xdiff, ydiff * ydiff).sqrt();
|
||||||
|
auto invLengths = lengths.invert();
|
||||||
|
xdiff *= invLengths;
|
||||||
|
ydiff *= invLengths;
|
||||||
|
|
||||||
|
// Since the quad is rectilinear, the edge distances are predictable and independent of the
|
||||||
|
// actual orientation of the quad. The lengths vector stores |p1-p0|, |p3-p1|, |p0-p2|, |p2-p3|,
|
||||||
|
// matching the CCW order. For instance, edge distances for p0 are 0 for e0 and e2 since they
|
||||||
|
// intersect at p0. Distance to e1 is the same as p0 to p1. Distance to e3 is p0 to p2 since
|
||||||
|
// e3 goes through p2 and since the quad is rectilinear, we know that's the shortest distance.
|
||||||
|
edgeDistances[0] = Sk4f(0.f, lengths[0], 0.f, lengths[2]);
|
||||||
|
edgeDistances[1] = Sk4f(0.f, 0.f, lengths[0], lengths[1]);
|
||||||
|
edgeDistances[2] = Sk4f(lengths[2], lengths[3], 0.f, 0.f);
|
||||||
|
edgeDistances[3] = Sk4f(lengths[1], 0.f, lengths[3], 0.f);
|
||||||
|
|
||||||
|
if (aaFlags != GrQuadAAFlags::kAll) {
|
||||||
|
// This order is the same order the edges appear in xdiff/ydiff and therefore as the
|
||||||
|
// edges in a/b/c.
|
||||||
|
Sk4f mask = compute_edge_mask(aaFlags);
|
||||||
|
|
||||||
|
// Update opposite corner distances by the 0.5 pixel outset
|
||||||
|
edgeDistances[0] += Sk4f(0.f, 0.5f, 0.f, 0.5f) * mask;
|
||||||
|
edgeDistances[1] += Sk4f(0.f, 0.f, 0.5f, 0.5f) * mask;
|
||||||
|
edgeDistances[2] += Sk4f(0.5f, 0.5f, 0.f, 0.f) * mask;
|
||||||
|
edgeDistances[3] += Sk4f(0.5f, 0.f, 0.5f, 0.f) * mask;
|
||||||
|
|
||||||
|
// Outset edge equations for masked out edges another pixel so that they always evaluate
|
||||||
|
// So add 1-mask to each point's edge distances vector so that coverage >= 1 on non-aa
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
edgeDistances[i] += (1.f - mask);
|
||||||
|
}
|
||||||
|
outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount);
|
||||||
|
} else {
|
||||||
|
// Update opposite corner distances by 0.5 pixel, skipping the need for mask since that's 1s
|
||||||
|
edgeDistances[0] += Sk4f(0.f, 0.5f, 0.f, 0.5f);
|
||||||
|
edgeDistances[1] += Sk4f(0.f, 0.f, 0.5f, 0.5f);
|
||||||
|
edgeDistances[2] += Sk4f(0.5f, 0.5f, 0.f, 0.f);
|
||||||
|
edgeDistances[3] += Sk4f(0.5f, 0.f, 0.5f, 0.f);
|
||||||
|
|
||||||
|
outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generalizes compute_quad_edge_distances_and_outset_vertices to extrapolate local coords such that
|
||||||
|
// after perspective division of the device coordinate, the original local coordinate value is at
|
||||||
|
// the original un-outset device position. r is the local coordinate's w component.
|
||||||
|
static void compute_quad_dists_and_outset_persp_vertices(GrQuadAAFlags aaFlags, Sk4f* x,
|
||||||
|
Sk4f* y, Sk4f* w, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) {
|
||||||
SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
|
SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
|
||||||
|
|
||||||
auto iw = (*w).invert();
|
auto iw = (*w).invert();
|
||||||
auto x2d = (*x) * iw;
|
auto x2d = (*x) * iw;
|
||||||
auto y2d = (*y) * iw;
|
auto y2d = (*y) * iw;
|
||||||
|
Sk4f a, b, c;
|
||||||
// Don't compute outset corners in the normalized space, which means u, v, and r don't need
|
// Don't compute outset corners in the normalized space, which means u, v, and r don't need
|
||||||
// to be provided here (outset separately below).
|
// to be provided here (outset separately below). Since this is computing distances for a
|
||||||
compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d, a, b, c, nullptr, nullptr, nullptr,
|
// projected quad, there is a very good chance it's not rectilinear so use the general 2D path.
|
||||||
/* uvr ct */ 0, /* outsetCorners */ false);
|
compute_quad_edges_and_outset_vertices(aaFlags, &x2d, &y2d, &a, &b, &c, nullptr, nullptr,
|
||||||
|
nullptr, /* uvr ct */ 0, /* outsetCorners */ false);
|
||||||
|
|
||||||
static const float kOutset = 0.5f;
|
static const float kOutset = 0.5f;
|
||||||
if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) {
|
if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) {
|
||||||
@ -205,36 +296,13 @@ static void compute_quad_edges_and_outset_persp_vertices(GrQuadAAFlags aaFlags,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Fast path for non-AA quads batched into an AA op. Since they are part of the AA op, the vertices
|
// Use the original edge equations with the outset homogeneous coordinates to get the edge
|
||||||
// need to have valid edge equations that ensure coverage is set to 1. To get perspective
|
// distance (technically multiplied by w, so that the fragment shader can do perspective
|
||||||
// interpolation of the edge distance, the vertex shader outputs d*w and then multiplies by 1/w in
|
// interpolation when it multiplies by 1/w later).
|
||||||
// the fragment shader. For non-AA edges, the edge equation can be simplified to 0*x/w + y/w + c >=
|
compute_edge_distances(a, b, c, *x, *y, *w, edgeDistances);
|
||||||
// 1, so the vertex shader outputs c*w. The quad is sent as two triangles, so a fragment is the
|
|
||||||
// interpolation between 3 of the 4 vertices. If iX are the weights for the 3 involved quad
|
|
||||||
// vertices, then the fragment shader's state is:
|
|
||||||
// f_cw = c * (iA*wA + iB*wB + iC*wC) and f_1/w = iA/wA + iB/wB + iC/wC
|
|
||||||
// (where A,B,C are chosen from {1,2,3, 4})
|
|
||||||
// When there's no perspective, then f_cw*f_1/w = c and setting c = 1 guarantees a proper non-AA
|
|
||||||
// edge. Unfortunately when there is perspective, f_cw*f_1/w != c unless the fragment is at a
|
|
||||||
// vertex. We must pick a c such that f_cw*f_1/w >= 1 across the whole primitive.
|
|
||||||
// Let n = min(w1,w2,w3,w4) and m = max(w1,w2,w3,w4) and rewrite
|
|
||||||
// f_1/w=(iA*wB*wC + iB*wA*wC + iC*wA*wB) / (wA*wB*wC)
|
|
||||||
// Since the iXs are weights for the interior of the primitive, then we have:
|
|
||||||
// n <= (iA*wA + iB*wB + iC*wC) <= m and
|
|
||||||
// n^2 <= (iA*wB*wC + iB*wA*wC + iC*wA*wB) <= m^2 and
|
|
||||||
// n^3 <= wA*wB*wC <= m^3 regardless of the choice of A,B, and C
|
|
||||||
// Thus if we set c = m^3/n^3, it guarantees f_cw*f_1/w >= 1 for any perspective.
|
|
||||||
static SkPoint3 compute_non_aa_persp_edge_coeffs(const Sk4f& w) {
|
|
||||||
float n = w.min();
|
|
||||||
float m = w.max();
|
|
||||||
return {0.f, 0.f, (m * m * m) / (n * n * n)};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// When there's guaranteed no perspective, the edge coefficients for non-AA quads is constant
|
|
||||||
static constexpr SkPoint3 kNonAANoPerspEdgeCoeffs = {0, 0, 1};
|
|
||||||
|
|
||||||
} // anonymous namespace
|
} // anonymous namespace
|
||||||
|
|
||||||
namespace GrQuadPerEdgeAA {
|
namespace GrQuadPerEdgeAA {
|
||||||
@ -248,13 +316,10 @@ void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& devi
|
|||||||
bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective;
|
bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective;
|
||||||
GrVertexColor color(color4f, GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType());
|
GrVertexColor color(color4f, GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType());
|
||||||
|
|
||||||
// Load position data into Sk4fs (always x, y and maybe w)
|
// Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
|
||||||
Sk4f x = deviceQuad.x4f();
|
Sk4f x = deviceQuad.x4f();
|
||||||
Sk4f y = deviceQuad.y4f();
|
Sk4f y = deviceQuad.y4f();
|
||||||
Sk4f w;
|
Sk4f w = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
|
||||||
if (deviceHasPerspective) {
|
|
||||||
w = deviceQuad.w4f();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load local position data into Sk4fs (either none, just u,v or all three)
|
// Load local position data into Sk4fs (either none, just u,v or all three)
|
||||||
Sk4f u, v, r;
|
Sk4f u, v, r;
|
||||||
@ -267,48 +332,42 @@ void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& devi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Sk4f a, b, c;
|
// Index into array refers to vertex. Index into particular Sk4f refers to edge.
|
||||||
|
Sk4f edgeDistances[4];
|
||||||
if (spec.usesCoverageAA()) {
|
if (spec.usesCoverageAA()) {
|
||||||
// Must calculate edges and possibly outside the positions
|
// Must calculate edges and possibly outside the positions
|
||||||
if (aaFlags == GrQuadAAFlags::kNone) {
|
if (aaFlags == GrQuadAAFlags::kNone) {
|
||||||
// A non-AA quad that got batched into an AA group, so its edges will be the same for
|
// A non-AA quad that got batched into an AA group, so it should have full coverage
|
||||||
// all four vertices and it does not need to be outset
|
// everywhere, so set the edge distances to w for each vertex (so that after perspective
|
||||||
SkPoint3 edgeCoeffs;
|
// division, it is equal to 1).
|
||||||
if (deviceHasPerspective) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
edgeCoeffs = compute_non_aa_persp_edge_coeffs(w);
|
edgeDistances[i] = w[i];
|
||||||
} else {
|
|
||||||
edgeCoeffs = kNonAANoPerspEdgeCoeffs;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the coefficients into all four equations
|
|
||||||
a = edgeCoeffs.fX;
|
|
||||||
b = edgeCoeffs.fY;
|
|
||||||
c = edgeCoeffs.fZ;
|
|
||||||
} else if (deviceHasPerspective) {
|
} else if (deviceHasPerspective) {
|
||||||
// For simplicity, pointers to u, v, and r are always provided, but the local dim param
|
// For simplicity, pointers to u, v, and r are always provided, but the local dim param
|
||||||
// ensures that only loaded Sk4fs are modified in the compute functions.
|
// ensures that only loaded Sk4fs are modified in the compute functions.
|
||||||
compute_quad_edges_and_outset_persp_vertices(aaFlags, &x, &y, &w, &a, &b, &c,
|
compute_quad_dists_and_outset_persp_vertices(aaFlags, &x, &y, &w, edgeDistances,
|
||||||
&u, &v, &r, spec.localDimensionality());
|
&u, &v, &r, spec.localDimensionality());
|
||||||
|
} else if (spec.deviceQuadType() <= GrQuadType::kRectilinear) {
|
||||||
|
compute_rectilinear_dists_and_outset_vertices(aaFlags, &x, &y, edgeDistances,
|
||||||
|
&u, &v, &r, spec.localDimensionality());
|
||||||
} else {
|
} else {
|
||||||
|
Sk4f a, b, c;
|
||||||
compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c, &u, &v, &r,
|
compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c, &u, &v, &r,
|
||||||
spec.localDimensionality(), /* outset */ true);
|
spec.localDimensionality(), /*outset*/ true);
|
||||||
|
compute_edge_distances(a, b, c, x, y, w, edgeDistances); // w holds 1.f as desired
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now rearrange the Sk4fs into the interleaved vertex layout:
|
// Now rearrange the Sk4fs into the interleaved vertex layout:
|
||||||
// i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y
|
// i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y
|
||||||
// while also calculating the edge distance for each outset vertex for the four edges.
|
|
||||||
SkPoint3 p; // Stores the homogeneous vertex position, w = 1 explicitly for 2D cases
|
|
||||||
Sk4f edgeDists;
|
|
||||||
GrVertexWriter vb{vertices};
|
GrVertexWriter vb{vertices};
|
||||||
for (int i = 0; i < 4; ++i) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
// save position and hold on to the homogeneous point for later
|
// save position and hold on to the homogeneous point for later
|
||||||
if (deviceHasPerspective) {
|
if (deviceHasPerspective) {
|
||||||
p.set(x[i], y[i], w[i]);
|
vb.write<SkPoint3>({x[i], y[i], w[i]});
|
||||||
vb.write<SkPoint3>(p);
|
|
||||||
} else {
|
} else {
|
||||||
p.set(x[i], y[i], 1.f);
|
vb.write<SkPoint>({x[i], y[i]});
|
||||||
vb.write<SkPoint>({p.fX, p.fY});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// save color
|
// save color
|
||||||
@ -327,16 +386,12 @@ void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& devi
|
|||||||
|
|
||||||
// save the domain
|
// save the domain
|
||||||
if (spec.hasDomain()) {
|
if (spec.hasDomain()) {
|
||||||
vb.write<SkRect>(domain);
|
vb.write(domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// save the edges
|
// save the edges
|
||||||
if (spec.usesCoverageAA()) {
|
if (spec.usesCoverageAA()) {
|
||||||
// w is stored in point's fZ, and set to 1 for 2D,
|
vb.write(edgeDistances[i]);
|
||||||
// fragment shader will automatically divide by pixel's w if the quad has perspective
|
|
||||||
// to get the perceptually interpolated edge distance.
|
|
||||||
edgeDists = p.fX * a + p.fY * b + p.fZ * c;
|
|
||||||
vb.write<float>(edgeDists[0], edgeDists[1], edgeDists[2], edgeDists[3]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +307,7 @@ private:
|
|||||||
GrResolveAATypeForQuad(aaType, aaFlags, quad, quadType, &aaType, &aaFlags);
|
GrResolveAATypeForQuad(aaType, aaFlags, quad, quadType, &aaType, &aaFlags);
|
||||||
fAAType = static_cast<unsigned>(aaType);
|
fAAType = static_cast<unsigned>(aaType);
|
||||||
|
|
||||||
fPerspective = static_cast<unsigned>(quadType == GrQuadType::kPerspective);
|
fQuadType = static_cast<unsigned>(quadType);
|
||||||
// We expect our caller to have already caught this optimization.
|
// We expect our caller to have already caught this optimization.
|
||||||
SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
|
SkASSERT(!srcRect.contains(proxy->getWorstCaseBoundsRect()) ||
|
||||||
constraint == SkCanvas::kFast_SrcRectConstraint);
|
constraint == SkCanvas::kFast_SrcRectConstraint);
|
||||||
@ -386,7 +386,7 @@ private:
|
|||||||
fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
|
fFilter = static_cast<unsigned>(GrSamplerState::Filter::kNearest);
|
||||||
}
|
}
|
||||||
this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
|
this->setBounds(bounds, HasAABloat(this->aaType() == GrAAType::kCoverage), IsZeroArea::kNo);
|
||||||
fPerspective = static_cast<unsigned>(viewMatrix.hasPerspective());
|
fQuadType = static_cast<unsigned>(quadType);
|
||||||
fDomain = static_cast<unsigned>(false);
|
fDomain = static_cast<unsigned>(false);
|
||||||
fWideColor = static_cast<unsigned>(false);
|
fWideColor = static_cast<unsigned>(false);
|
||||||
}
|
}
|
||||||
@ -410,7 +410,7 @@ private:
|
|||||||
|
|
||||||
void onPrepareDraws(Target* target) override {
|
void onPrepareDraws(Target* target) override {
|
||||||
TRACE_EVENT0("skia", TRACE_FUNC);
|
TRACE_EVENT0("skia", TRACE_FUNC);
|
||||||
bool hasPerspective = false;
|
GrQuadType quadType = GrQuadType::kRect;
|
||||||
Domain domain = Domain::kNo;
|
Domain domain = Domain::kNo;
|
||||||
bool wideColor = false;
|
bool wideColor = false;
|
||||||
int numProxies = 0;
|
int numProxies = 0;
|
||||||
@ -419,7 +419,9 @@ private:
|
|||||||
auto config = fProxies[0].fProxy->config();
|
auto config = fProxies[0].fProxy->config();
|
||||||
GrAAType aaType = this->aaType();
|
GrAAType aaType = this->aaType();
|
||||||
for (const auto& op : ChainRange<TextureOp>(this)) {
|
for (const auto& op : ChainRange<TextureOp>(this)) {
|
||||||
hasPerspective |= op.fPerspective;
|
if (op.quadType() > quadType) {
|
||||||
|
quadType = op.quadType();
|
||||||
|
}
|
||||||
if (op.fDomain) {
|
if (op.fDomain) {
|
||||||
domain = Domain::kYes;
|
domain = Domain::kYes;
|
||||||
}
|
}
|
||||||
@ -440,9 +442,8 @@ private:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VertexSpec vertexSpec(hasPerspective ? GrQuadType::kPerspective : GrQuadType::kStandard,
|
VertexSpec vertexSpec(quadType, wideColor ? ColorType::kHalf : ColorType::kByte,
|
||||||
wideColor ? ColorType::kHalf : ColorType::kByte, GrQuadType::kRect,
|
GrQuadType::kRect, /* hasLocal */ true, domain, aaType);
|
||||||
/* hasLocal */ true, domain, aaType);
|
|
||||||
|
|
||||||
sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
|
sk_sp<GrGeometryProcessor> gp = TextureGeometryProcessor::Make(
|
||||||
textureType, config, this->filter(), std::move(fTextureColorSpaceXform),
|
textureType, config, this->filter(), std::move(fTextureColorSpaceXform),
|
||||||
@ -565,7 +566,9 @@ private:
|
|||||||
}
|
}
|
||||||
fProxies[0].fQuadCnt += that->fQuads.count();
|
fProxies[0].fQuadCnt += that->fQuads.count();
|
||||||
fQuads.push_back_n(that->fQuads.count(), that->fQuads.begin());
|
fQuads.push_back_n(that->fQuads.count(), that->fQuads.begin());
|
||||||
fPerspective |= that->fPerspective;
|
if (that->fQuadType > fQuadType) {
|
||||||
|
fQuadType = that->fQuadType;
|
||||||
|
}
|
||||||
fDomain |= that->fDomain;
|
fDomain |= that->fDomain;
|
||||||
fWideColor |= that->fWideColor;
|
fWideColor |= that->fWideColor;
|
||||||
if (upgradeToCoverageAAOnMerge) {
|
if (upgradeToCoverageAAOnMerge) {
|
||||||
@ -576,6 +579,7 @@ private:
|
|||||||
|
|
||||||
GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
|
GrAAType aaType() const { return static_cast<GrAAType>(fAAType); }
|
||||||
GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); }
|
GrSamplerState::Filter filter() const { return static_cast<GrSamplerState::Filter>(fFilter); }
|
||||||
|
GrQuadType quadType() const { return static_cast<GrQuadType>(fQuadType); }
|
||||||
|
|
||||||
class Quad {
|
class Quad {
|
||||||
public:
|
public:
|
||||||
@ -609,15 +613,17 @@ private:
|
|||||||
sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
|
sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
|
||||||
unsigned fFilter : 2;
|
unsigned fFilter : 2;
|
||||||
unsigned fAAType : 2;
|
unsigned fAAType : 2;
|
||||||
unsigned fPerspective : 1;
|
unsigned fQuadType : 2; // Device quad, src quad is always trivial
|
||||||
unsigned fDomain : 1;
|
unsigned fDomain : 1;
|
||||||
unsigned fWideColor : 1;
|
unsigned fWideColor : 1;
|
||||||
// Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called.
|
// Used to track whether fProxy is ref'ed or has a pending IO after finalize() is called.
|
||||||
unsigned fFinalized : 1;
|
unsigned fFinalized : 1;
|
||||||
unsigned fCanSkipAllocatorGather : 1;
|
unsigned fCanSkipAllocatorGather : 1;
|
||||||
unsigned fProxyCnt : 32 - 8;
|
unsigned fProxyCnt : 32 - 10;
|
||||||
Proxy fProxies[1];
|
Proxy fProxies[1];
|
||||||
|
|
||||||
|
static_assert(kGrQuadTypeCount <= 4, "GrQuadType does not fit in 2 bits");
|
||||||
|
|
||||||
typedef GrMeshDrawOp INHERITED;
|
typedef GrMeshDrawOp INHERITED;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user