blob: f893a919f9bf4c4182c0948cd143f701b74a6627 [file] [log] [blame]
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "GrQuadPerEdgeAA.h"
#include "GrQuad.h"
#include "GrVertexWriter.h"
#include "glsl/GrGLSLColorSpaceXformHelper.h"
#include "glsl/GrGLSLGeometryProcessor.h"
#include "glsl/GrGLSLPrimitiveProcessor.h"
#include "glsl/GrGLSLFragmentShaderBuilder.h"
#include "glsl/GrGLSLVarying.h"
#include "glsl/GrGLSLVertexGeoBuilder.h"
#include "SkNx.h"
#define AI SK_ALWAYS_INLINE
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);
}
// Outputs normalized edge vectors in xdiff and ydiff, as well as the reciprocal of the original
// edge lengths in invLengths
static AI void compute_edge_vectors(const Sk4f& x, const Sk4f& y, const Sk4f& xnext,
const Sk4f& ynext, Sk4f* xdiff, Sk4f* ydiff, Sk4f* invLengths) {
*xdiff = xnext - x;
*ydiff = ynext - y;
*invLengths = fma(*xdiff, *xdiff, *ydiff * *ydiff).rsqrt();
*xdiff *= *invLengths;
*ydiff *= *invLengths;
}
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];
}
}
static AI float get_max_coverage(const Sk4f& lengths) {
float minWidth = SkMinScalar(lengths[0], lengths[3]);
float minHeight = SkMinScalar(lengths[1], lengths[2]);
// Calculate approximate area of the quad, pinning dimensions to 1 in case the quad is larger
// than a pixel. Sub-pixel quads that are rotated may in fact have a different true maximum
// coverage than this calculation, but this will be close and is stable.
return SkMinScalar(minWidth, 1.f) * SkMinScalar(minHeight, 1.f);
}
// 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
// input and the outset points as output. In order to be used as a component of perspective edge
// distance calculation, this exports edge equations in 'a', 'b', and 'c'. Use
// compute_edge_distances to turn these equations into the distances needed by the shader. The
// values in x, y, u, v, and r are possibly updated if outsetting is needed. r is the local
// position's w component if it exists.
//
// Returns maximum coverage allowed for any given pixel.
static float 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);
// Compute edge vectors for the quad.
auto xnext = nextCCW(*x);
auto ynext = nextCCW(*y);
// xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
Sk4f xdiff, ydiff, invLengths;
compute_edge_vectors(*x, *y, xnext, ynext, &xdiff, &ydiff, &invLengths);
// Use above vectors to compute edge equations (importantly before we outset positions).
*c = fma(xnext, *y, -ynext * *x) * invLengths;
// 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));
if ((test < Sk4f(0)).anyTrue()) {
*a = -ydiff;
*b = xdiff;
*c = -*c;
} else {
*a = ydiff;
*b = -xdiff;
}
// Outset the edge equations so aa coverage evaluates to zero half a pixel away from the
// original quad edge.
*c += 0.5f;
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);
// Outset edge equations for masked out edges another pixel so that they always evaluate
// >= 1.
*c += (1.f - mask);
if (outset) {
outset_masked_vertices(xdiff, ydiff, invLengths, mask, x, y, u, v, r, uvrChannelCount);
}
} else if (outset) {
outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount);
}
return get_max_coverage(invLengths.invert());
}
// A specialization of the above function that can compute edge distances very quickly when it knows
// that the edges intersect at right angles, i.e. any transform other than skew and perspective
// (GrQuadType::kRectilinear). Unlike the above function, this always outsets the corners since it
// cannot be reused in the perspective case.
static float compute_rectilinear_dists_and_outset_vertices(GrQuadAAFlags aaFlags, Sk4f* x,
Sk4f* y, Sk4f edgeDistances[4], Sk4f* u, Sk4f* v, Sk4f* r, int uvrChannelCount) {
SkASSERT(uvrChannelCount == 0 || uvrChannelCount == 2 || uvrChannelCount == 3);
// xdiff and ydiff will comprise the normalized vectors pointing along each quad edge.
Sk4f xdiff, ydiff, invLengths;
compute_edge_vectors(*x, *y, nextCCW(*x), nextCCW(*y), &xdiff, &ydiff, &invLengths);
Sk4f lengths = invLengths.invert();
// 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 1 (when enabled by the mask). The distance
// calculations used in compute_quad_edges_... calculates the edge equations from original
// positions and then shifts the coefficient by 0.5. If the opposite edges are also outset
// then must add an additional 0.5 to account for its shift away from that edge.
Sk4f maskWithOpposites = mask + SkNx_shuffle<3, 2, 1, 0>(mask);
edgeDistances[0] += Sk4f(0.f, 0.5f, 0.f, 0.5f) * maskWithOpposites;
edgeDistances[1] += Sk4f(0.f, 0.f, 0.5f, 0.5f) * maskWithOpposites;
edgeDistances[2] += Sk4f(0.5f, 0.5f, 0.f, 0.f) * maskWithOpposites;
edgeDistances[3] += Sk4f(0.5f, 0.f, 0.5f, 0.f) * maskWithOpposites;
// 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 and 0.5 edge shift, skipping the need for
// mask since that's 1s
edgeDistances[0] += Sk4f(0.f, 1.f, 0.f, 1.f);
edgeDistances[1] += Sk4f(0.f, 0.f, 1.f, 1.f);
edgeDistances[2] += Sk4f(1.f, 1.f, 0.f, 0.f);
edgeDistances[3] += Sk4f(1.f, 0.f, 1.f, 0.f);
outset_vertices(xdiff, ydiff, invLengths, x, y, u, v, r, uvrChannelCount);
}
return get_max_coverage(lengths);
}
// 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 float 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);
auto iw = (*w).invert();
auto x2d = (*x) * 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
// to be provided here (outset separately below). Since this is computing distances for a
// projected quad, there is a very good chance it's not rectilinear so use the general 2D path.
float maxProjectedCoverage = 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;
if ((GrQuadAAFlags::kLeft | GrQuadAAFlags::kRight) & aaFlags) {
// For each entry in x the equivalent entry in opX is the left/right opposite and so on.
Sk4f opX = SkNx_shuffle<2, 3, 0, 1>(*x);
Sk4f opW = SkNx_shuffle<2, 3, 0, 1>(*w);
Sk4f opY = SkNx_shuffle<2, 3, 0, 1>(*y);
// vx/vy holds the device space left-to-right vectors along top and bottom of the quad.
Sk2f vx = SkNx_shuffle<2, 3>(x2d) - SkNx_shuffle<0, 1>(x2d);
Sk2f vy = SkNx_shuffle<2, 3>(y2d) - SkNx_shuffle<0, 1>(y2d);
Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt();
// For each device space corner, devP, label its left/right opposite device space point
// opDevPt. The new device space point is opDevPt + s (devPt - opDevPt) where s is
// (length(devPt - opDevPt) + 0.5) / length(devPt - opDevPt);
Sk4f s = SkNx_shuffle<0, 1, 0, 1>((len + kOutset) / len);
// Compute t in homogeneous space from s using similar triangles so that we can produce
// homogeneous outset vertices for perspective-correct interpolation.
Sk4f sOpW = s * opW;
Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
// mask is used to make the t values be 1 when the left/right side is not antialiased.
Sk4f mask(GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f,
GrQuadAAFlags::kLeft & aaFlags ? 1.f : 0.f,
GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f,
GrQuadAAFlags::kRight & aaFlags ? 1.f : 0.f);
t = t * mask + (1.f - mask);
*x = opX + t * (*x - opX);
*y = opY + t * (*y - opY);
*w = opW + t * (*w - opW);
if (uvrChannelCount > 0) {
Sk4f opU = SkNx_shuffle<2, 3, 0, 1>(*u);
Sk4f opV = SkNx_shuffle<2, 3, 0, 1>(*v);
*u = opU + t * (*u - opU);
*v = opV + t * (*v - opV);
if (uvrChannelCount == 3) {
Sk4f opR = SkNx_shuffle<2, 3, 0, 1>(*r);
*r = opR + t * (*r - opR);
}
}
if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
// Update the 2D points for the top/bottom calculation.
iw = (*w).invert();
x2d = (*x) * iw;
y2d = (*y) * iw;
}
}
if ((GrQuadAAFlags::kTop | GrQuadAAFlags::kBottom) & aaFlags) {
// This operates the same as above but for top/bottom rather than left/right.
Sk4f opX = SkNx_shuffle<1, 0, 3, 2>(*x);
Sk4f opW = SkNx_shuffle<1, 0, 3, 2>(*w);
Sk4f opY = SkNx_shuffle<1, 0, 3, 2>(*y);
Sk2f vx = SkNx_shuffle<1, 3>(x2d) - SkNx_shuffle<0, 2>(x2d);
Sk2f vy = SkNx_shuffle<1, 3>(y2d) - SkNx_shuffle<0, 2>(y2d);
Sk2f len = SkNx_fma(vx, vx, vy * vy).sqrt();
Sk4f s = SkNx_shuffle<0, 0, 1, 1>((len + kOutset) / len);
Sk4f sOpW = s * opW;
Sk4f t = sOpW / (sOpW + (1.f - s) * (*w));
Sk4f mask(GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f,
GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f,
GrQuadAAFlags::kTop & aaFlags ? 1.f : 0.f,
GrQuadAAFlags::kBottom & aaFlags ? 1.f : 0.f);
t = t * mask + (1.f - mask);
*x = opX + t * (*x - opX);
*y = opY + t * (*y - opY);
*w = opW + t * (*w - opW);
if (uvrChannelCount > 0) {
Sk4f opU = SkNx_shuffle<1, 0, 3, 2>(*u);
Sk4f opV = SkNx_shuffle<1, 0, 3, 2>(*v);
*u = opU + t * (*u - opU);
*v = opV + t * (*v - opV);
if (uvrChannelCount == 3) {
Sk4f opR = SkNx_shuffle<1, 0, 3, 2>(*r);
*r = opR + t * (*r - opR);
}
}
}
// Use the original edge equations with the outset homogeneous coordinates to get the edge
// distance (technically multiplied by w, so that the fragment shader can do perspective
// interpolation when it multiplies by 1/w later).
compute_edge_distances(a, b, c, *x, *y, *w, edgeDistances);
return maxProjectedCoverage;
}
// Calculate safe edge distances for non-aa quads that have been batched with aa quads. Since the
// fragment shader multiples by 1/w, so the edge distance cannot just be set to 1. It cannot just
// be set to w either due to interpolation across the triangle. If iA, iB, and iC are the
// barycentric weights of the triangle, and we set the edge distance to w, the fragment shader
// actually sees d = (iA*wA + iB*wB + iC*wC) * (iA/wA + iB/wB + iC/wC). Without perspective this
// simplifies to 1 as necessary, but we must choose something other than w when there is perspective
// to ensure that d >= 1 and the edge shows as non-aa.
static float compute_nonaa_edge_distances(const Sk4f& w, bool hasPersp, Sk4f edgeDistances[4]) {
// Let n = min(w1,w2,w3,w4) and m = max(w1,w2,w3,w4) and rewrite
// d = (iA*wA + iB*wB + iC*wC) * (iA*wB*wC + iB*wA*wC + iC*wA*wB) / (wA*wB*wC)
// | e=attr from VS | | fragCoord.w = 1/w |
// Since the weights are 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 verts in the quad
// Thus if we set e = m^3/n^3, it guarantees d >= 1 for any perspective.
float e;
if (hasPersp) {
float m = w.max();
float n = w.min();
e = (m * m * m) / (n * n * n);
} else {
e = 1.f;
}
// All edge distances set to the same
for (int i = 0; i < 4; ++i) {
edgeDistances[i] = e;
}
// Non-aa, so always use full coverage
return 1.f;
}
} // anonymous namespace
namespace GrQuadPerEdgeAA {
////////////////// Tessellate Implementation
void* Tessellate(void* vertices, const VertexSpec& spec, const GrPerspQuad& deviceQuad,
const SkPMColor4f& color4f, const GrPerspQuad& localQuad, const SkRect& domain,
GrQuadAAFlags aaFlags) {
bool deviceHasPerspective = spec.deviceQuadType() == GrQuadType::kPerspective;
bool localHasPerspective = spec.localQuadType() == GrQuadType::kPerspective;
GrVertexColor color(color4f, GrQuadPerEdgeAA::ColorType::kHalf == spec.colorType());
// Load position data into Sk4fs (always x, y, and load w to avoid branching down the road)
Sk4f x = deviceQuad.x4f();
Sk4f y = deviceQuad.y4f();
Sk4f w = deviceQuad.w4f(); // Guaranteed to be 1f if it's not perspective
// Load local position data into Sk4fs (either none, just u,v or all three)
Sk4f u, v, r;
if (spec.hasLocalCoords()) {
u = localQuad.x4f();
v = localQuad.y4f();
if (localHasPerspective) {
r = localQuad.w4f();
}
}
// Index into array refers to vertex. Index into particular Sk4f refers to edge.
Sk4f edgeDistances[4];
float maxCoverage = 1.f;
if (spec.usesCoverageAA()) {
// Must calculate edges and possibly outside the positions
if (aaFlags == GrQuadAAFlags::kNone) {
// A non-AA quad that got batched into an AA group, so it should have full coverage
maxCoverage = compute_nonaa_edge_distances(w, deviceHasPerspective, edgeDistances);
} else if (deviceHasPerspective) {
// 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.
maxCoverage = compute_quad_dists_and_outset_persp_vertices(aaFlags, &x, &y, &w,
edgeDistances, &u, &v, &r, spec.localDimensionality());
} else if (spec.deviceQuadType() <= GrQuadType::kRectilinear) {
maxCoverage = compute_rectilinear_dists_and_outset_vertices(aaFlags, &x, &y,
edgeDistances, &u, &v, &r, spec.localDimensionality());
} else {
Sk4f a, b, c;
maxCoverage = compute_quad_edges_and_outset_vertices(aaFlags, &x, &y, &a, &b, &c,
&u, &v, &r, 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:
// i.e. x1x2x3x4 y1y2y3y4 -> x1y1 x2y2 x3y3 x4y
GrVertexWriter vb{vertices};
for (int i = 0; i < 4; ++i) {
// save position, always send a vec4 because we embed max coverage in the last component.
// For 2D quads, we know w holds the correct 1.f, so just write it out without branching
vb.write(x[i], y[i], w[i], maxCoverage);
// save color
if (spec.hasVertexColors()) {
vb.write(color);
}
// save local position
if (spec.hasLocalCoords()) {
if (localHasPerspective) {
vb.write<SkPoint3>({u[i], v[i], r[i]});
} else {
vb.write<SkPoint>({u[i], v[i]});
}
}
// save the domain
if (spec.hasDomain()) {
vb.write(domain);
}
// save the edges
if (spec.usesCoverageAA()) {
vb.write(edgeDistances[i]);
}
}
return vb.fPtr;
}
////////////////// VertexSpec Implementation
int VertexSpec::deviceDimensionality() const {
return this->deviceQuadType() == GrQuadType::kPerspective ? 3 : 2;
}
int VertexSpec::localDimensionality() const {
return fHasLocalCoords ? (this->localQuadType() == GrQuadType::kPerspective ? 3 : 2) : 0;
}
////////////////// Geometry Processor Implementation
class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor {
public:
static sk_sp<GrGeometryProcessor> Make(const VertexSpec& spec) {
return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(spec));
}
static sk_sp<GrGeometryProcessor> Make(const VertexSpec& vertexSpec, const GrShaderCaps& caps,
GrTextureType textureType, GrPixelConfig textureConfig,
const GrSamplerState& samplerState,
uint32_t extraSamplerKey,
sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
return sk_sp<QuadPerEdgeAAGeometryProcessor>(new QuadPerEdgeAAGeometryProcessor(
vertexSpec, caps, textureType, textureConfig, samplerState, extraSamplerKey,
std::move(textureColorSpaceXform)));
}
const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; }
void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override {
// aa, domain, texturing are single bit flags
uint32_t x = fAAEdgeDistances.isInitialized() ? 0 : 1;
x |= fDomain.isInitialized() ? 0 : 2;
x |= fSampler.isInitialized() ? 0 : 4;
// regular position has two options as well
x |= fNeedsPerspective ? 0 : 8;
// local coords require 2 bits (3 choices), 00 for none, 01 for 2d, 10 for 3d
if (fLocalCoord.isInitialized()) {
x |= kFloat3_GrVertexAttribType == fLocalCoord.cpuType() ? 16 : 32;
}
// similar for colors, 00 for none, 01 for bytes, 10 for half-floats
if (this->fColor.isInitialized()) {
x |= kUByte4_norm_GrVertexAttribType == fColor.cpuType() ? 64 : 128;
}
b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()));
b->add32(x);
}
GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps& caps) const override {
class GLSLProcessor : public GrGLSLGeometryProcessor {
public:
void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& proc,
FPCoordTransformIter&& transformIter) override {
const auto& gp = proc.cast<QuadPerEdgeAAGeometryProcessor>();
if (gp.fLocalCoord.isInitialized()) {
this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter);
}
fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get());
}
private:
void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override {
using Interpolation = GrGLSLVaryingHandler::Interpolation;
const auto& gp = args.fGP.cast<QuadPerEdgeAAGeometryProcessor>();
fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler,
gp.fTextureColorSpaceXform.get());
args.fVaryingHandler->emitAttributes(gp);
// Extract effective position out of vec4 as a local variable in the vertex shader
if (gp.fNeedsPerspective) {
args.fVertBuilder->codeAppendf("float3 position = %s.xyz;",
gp.fPositionWithCoverage.name());
} else {
args.fVertBuilder->codeAppendf("float2 position = %s.xy;",
gp.fPositionWithCoverage.name());
}
gpArgs->fPositionVar = {"position",
gp.fNeedsPerspective ? kFloat3_GrSLType : kFloat2_GrSLType,
GrShaderVar::kNone_TypeModifier};
// Handle local coordinates if they exist
if (gp.fLocalCoord.isInitialized()) {
// NOTE: If the only usage of local coordinates is for the inline texture fetch
// before FPs, then there are no registered FPCoordTransforms and this ends up
// emitting nothing, so there isn't a duplication of local coordinates
this->emitTransforms(args.fVertBuilder,
args.fVaryingHandler,
args.fUniformHandler,
gp.fLocalCoord.asShaderVar(),
args.fFPCoordTransformHandler);
}
// Solid color before any texturing gets modulated in
if (gp.fColor.isInitialized()) {
args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor,
Interpolation::kCanBeFlat);
}
// If there is a texture, must also handle texture coordinates and reading from
// the texture in the fragment shader before continuing to fragment processors.
if (gp.fSampler.isInitialized()) {
// Texture coordinates clamped by the domain on the fragment shader; if the GP
// has a texture, it's guaranteed to have local coordinates
args.fFragBuilder->codeAppend("float2 texCoord;");
if (gp.fLocalCoord.cpuType() == kFloat3_GrVertexAttribType) {
// Can't do a pass through since we need to perform perspective division
GrGLSLVarying v(gp.fLocalCoord.gpuType());
args.fVaryingHandler->addVarying(gp.fLocalCoord.name(), &v);
args.fVertBuilder->codeAppendf("%s = %s;",
v.vsOut(), gp.fLocalCoord.name());
args.fFragBuilder->codeAppendf("texCoord = %s.xy / %s.z;",
v.fsIn(), v.fsIn());
} else {
args.fVaryingHandler->addPassThroughAttribute(gp.fLocalCoord, "texCoord");
}
// Clamp the now 2D localCoordName variable by the domain if it is provided
if (gp.fDomain.isInitialized()) {
args.fFragBuilder->codeAppend("float4 domain;");
args.fVaryingHandler->addPassThroughAttribute(gp.fDomain, "domain",
Interpolation::kCanBeFlat);
args.fFragBuilder->codeAppend(
"texCoord = clamp(texCoord, domain.xy, domain.zw);");
}
// Now modulate the starting output color by the texture lookup
args.fFragBuilder->codeAppendf("%s = ", args.fOutputColor);
args.fFragBuilder->appendTextureLookupAndModulate(
args.fOutputColor, args.fTexSamplers[0], "texCoord", kFloat2_GrSLType,
&fTextureColorSpaceXformHelper);
args.fFragBuilder->codeAppend(";");
}
// And lastly, output the coverage calculation code
if (gp.fAAEdgeDistances.isInitialized()) {
GrGLSLVarying maxCoverage(kFloat_GrSLType);
args.fVaryingHandler->addVarying("maxCoverage", &maxCoverage);
args.fVertBuilder->codeAppendf("%s = %s.w;",
maxCoverage.vsOut(), gp.fPositionWithCoverage.name());
args.fFragBuilder->codeAppend("float4 edgeDists;");
args.fVaryingHandler->addPassThroughAttribute(gp.fAAEdgeDistances, "edgeDists");
args.fFragBuilder->codeAppend(
"float minDist = min(min(edgeDists.x, edgeDists.y),"
" min(edgeDists.z, edgeDists.w));");
if (gp.fNeedsPerspective) {
// The distance from edge equation e to homogeneous point p=sk_Position is
// e.x*p.x/p.w + e.y*p.y/p.w + e.z. However, we want screen space
// interpolation of this distance. We can do this by multiplying the vertex
// attribute by p.w and then multiplying by sk_FragCoord.w in the FS. So we
// output e.x*p.x + e.y*p.y + e.z * p.w
args.fFragBuilder->codeAppend("minDist *= sk_FragCoord.w;");
}
// Clamp to max coverage after the perspective divide since perspective quads
// calculated the max coverage in projected space.
args.fFragBuilder->codeAppendf("%s = float4(clamp(minDist, 0.0, %s));",
args.fOutputCoverage, maxCoverage.fsIn());
} else {
// Set coverage to 1
args.fFragBuilder->codeAppendf("%s = float4(1);", args.fOutputCoverage);
}
}
GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper;
};
return new GLSLProcessor;
}
private:
QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec)
: INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
, fTextureColorSpaceXform(nullptr) {
SkASSERT(spec.hasVertexColors() && !spec.hasDomain());
this->initializeAttrs(spec);
this->setTextureSamplerCnt(0);
}
QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
GrTextureType textureType, GrPixelConfig textureConfig,
const GrSamplerState& samplerState,
uint32_t extraSamplerKey,
sk_sp<GrColorSpaceXform> textureColorSpaceXform)
: INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID)
, fTextureColorSpaceXform(std::move(textureColorSpaceXform))
, fSampler(textureType, textureConfig, samplerState, extraSamplerKey) {
SkASSERT(spec.hasVertexColors() && spec.hasLocalCoords());
this->initializeAttrs(spec);
this->setTextureSamplerCnt(1);
}
void initializeAttrs(const VertexSpec& spec) {
fNeedsPerspective = spec.deviceDimensionality() == 3;
fPositionWithCoverage = {"posAndCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
int localDim = spec.localDimensionality();
if (localDim == 3) {
fLocalCoord = {"localCoord", kFloat3_GrVertexAttribType, kFloat3_GrSLType};
} else if (localDim == 2) {
fLocalCoord = {"localCoord", kFloat2_GrVertexAttribType, kFloat2_GrSLType};
} // else localDim == 0 and attribute remains uninitialized
if (ColorType::kByte == spec.colorType()) {
fColor = {"color", kUByte4_norm_GrVertexAttribType, kHalf4_GrSLType};
} else if (ColorType::kHalf == spec.colorType()) {
fColor = {"color", kHalf4_GrVertexAttribType, kHalf4_GrSLType};
}
if (spec.hasDomain()) {
fDomain = {"domain", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
}
if (spec.usesCoverageAA()) {
fAAEdgeDistances = {"aaEdgeDist", kFloat4_GrVertexAttribType, kFloat4_GrSLType};
}
this->setVertexAttributes(&fPositionWithCoverage, 5);
}
const TextureSampler& onTextureSampler(int) const override { return fSampler; }
Attribute fPositionWithCoverage;
Attribute fColor;
Attribute fLocalCoord;
Attribute fDomain;
Attribute fAAEdgeDistances;
// The positions attribute is always a vec4 and can't be used to encode perspectiveness
bool fNeedsPerspective;
// Color space will be null and fSampler.isInitialized() returns false when the GP is configured
// to skip texturing.
sk_sp<GrColorSpaceXform> fTextureColorSpaceXform;
TextureSampler fSampler;
typedef GrGeometryProcessor INHERITED;
};
sk_sp<GrGeometryProcessor> MakeProcessor(const VertexSpec& spec) {
return QuadPerEdgeAAGeometryProcessor::Make(spec);
}
sk_sp<GrGeometryProcessor> MakeTexturedProcessor(const VertexSpec& spec, const GrShaderCaps& caps,
GrTextureType textureType, GrPixelConfig textureConfig,
const GrSamplerState& samplerState, uint32_t extraSamplerKey,
sk_sp<GrColorSpaceXform> textureColorSpaceXform) {
return QuadPerEdgeAAGeometryProcessor::Make(spec, caps, textureType, textureConfig,
samplerState, extraSamplerKey,
std::move(textureColorSpaceXform));
}
} // namespace GrQuadPerEdgeAA