Specialize vertex writing function for common quad VertexSpecs
Change-Id: I204f04596809e1e33f20b67b454cdd0e1f269630
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/255303
Commit-Queue: Michael Ludwig <michaelludwig@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/ops/GrQuadPerEdgeAA.cpp b/src/gpu/ops/GrQuadPerEdgeAA.cpp
index 6833107..c0608b8 100644
--- a/src/gpu/ops/GrQuadPerEdgeAA.cpp
+++ b/src/gpu/ops/GrQuadPerEdgeAA.cpp
@@ -21,14 +21,14 @@
namespace {
-// Writes four vertices in triangle strip order, including the additional data for local
-// coordinates, geometry + texture domains, color, and coverage as needed to satisfy the vertex spec
-static void write_quad(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
- GrQuadPerEdgeAA::CoverageMode mode, const skvx::Vec<4, float>& coverage,
- SkPMColor4f color4f, const SkRect& geomDomain, const SkRect& texDomain,
- const GrQuad& deviceQuad, const GrQuad& localQuad) {
+// Generic WriteQuadProc that can handle any VertexSpec. It writes the 4 vertices in triangle strip
+// order, although the data per-vertex is dependent on the VertexSpec.
+static void write_quad_generic(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
static constexpr auto If = GrVertexWriter::If<float>;
-
+ GrQuadPerEdgeAA::CoverageMode mode = spec.coverageMode();
for (int i = 0; i < 4; ++i) {
// save position, this is a float2 or float3 or float4 depending on the combination of
// perspective and coverage mode.
@@ -40,7 +40,7 @@
if (spec.hasVertexColors()) {
bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
vb->write(GrVertexColor(
- color4f * (mode == GrQuadPerEdgeAA::CoverageMode::kWithColor ? coverage[i] : 1.f),
+ color * (mode == GrQuadPerEdgeAA::CoverageMode::kWithColor ? coverage[i] : 1.f),
wide));
}
@@ -62,6 +62,166 @@
}
}
+// Specialized WriteQuadProcs for particular VertexSpecs that show up frequently (determined
+// experimentally through recorded GMs, SKPs, and SVGs, as well as SkiaRenderer's usage patterns):
+
+// 2D (XY), no explicit coverage, vertex color, no locals, no geometry domain, no texture domain
+// This represents simple, solid color or shader, non-AA (or AA with cov. as alpha) rects.
+static void write_2d_color(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
+ // Assert assumptions about VertexSpec
+ SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(!spec.hasLocalCoords());
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kNone ||
+ spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithColor);
+ SkASSERT(spec.hasVertexColors());
+ SkASSERT(!spec.requiresGeometryDomain());
+ SkASSERT(!spec.hasDomain());
+
+ bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
+ for (int i = 0; i < 4; ++i) {
+ // If this is not coverage-with-alpha, make sure coverage == 1 so it doesn't do anything
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithColor ||
+ coverage[i] == 1.f);
+ vb->write(deviceQuad.x(i), deviceQuad.y(i), GrVertexColor(color * coverage[i], wide));
+ }
+}
+
+// 2D (XY), no explicit coverage, UV locals, no color, no geometry domain, no texture domain
+// This represents opaque, non AA, textured rects
+static void write_2d_uv(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
+ // Assert assumptions about VertexSpec
+ SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kNone);
+ SkASSERT(!spec.hasVertexColors());
+ SkASSERT(!spec.requiresGeometryDomain());
+ SkASSERT(!spec.hasDomain());
+
+ for (int i = 0; i < 4; ++i) {
+ vb->write(deviceQuad.x(i), deviceQuad.y(i), localQuad.x(i), localQuad.y(i));
+ }
+}
+
+// 2D (XY), no explicit coverage, UV locals, vertex color, no geometry or texture domains
+// This represents transparent, non AA (or AA with cov. as alpha), textured rects
+static void write_2d_color_uv(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
+ // Assert assumptions about VertexSpec
+ SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kNone ||
+ spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithColor);
+ SkASSERT(spec.hasVertexColors());
+ SkASSERT(!spec.requiresGeometryDomain());
+ SkASSERT(!spec.hasDomain());
+
+ bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
+ for (int i = 0; i < 4; ++i) {
+ // If this is not coverage-with-alpha, make sure coverage == 1 so it doesn't do anything
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithColor ||
+ coverage[i] == 1.f);
+ vb->write(deviceQuad.x(i), deviceQuad.y(i), GrVertexColor(color * coverage[i], wide),
+ localQuad.x(i), localQuad.y(i));
+ }
+}
+
+// 2D (XY), explicit coverage, UV locals, no color, no geometry domain, no texture domain
+// This represents opaque, AA, textured rects
+static void write_2d_cov_uv(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
+ // Assert assumptions about VertexSpec
+ SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithPosition);
+ SkASSERT(!spec.hasVertexColors());
+ SkASSERT(!spec.requiresGeometryDomain());
+ SkASSERT(!spec.hasDomain());
+
+ for (int i = 0; i < 4; ++i) {
+ vb->write(deviceQuad.x(i), deviceQuad.y(i), coverage[i], localQuad.x(i), localQuad.y(i));
+ }
+}
+
+// NOTE: The three _strict specializations below match the non-strict uv functions above, except
+// that they also write the UV domain. These are included to benefit SkiaRenderer, which must make
+// use of both fast and strict constrained domains. When testing _strict was not that common across
+// GMS, SKPs, and SVGs but we have little visibility into actual SkiaRenderer statistics. If
+// SkiaRenderer can avoid domains more, these 3 functions should probably be removed for simplicity.
+
+// 2D (XY), no explicit coverage, UV locals, no color, tex domain but no geometry domain
+// This represents opaque, non AA, textured rects with strict uv sampling
+static void write_2d_uv_strict(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
+ // Assert assumptions about VertexSpec
+ SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kNone);
+ SkASSERT(!spec.hasVertexColors());
+ SkASSERT(!spec.requiresGeometryDomain());
+ SkASSERT(spec.hasDomain());
+
+ for (int i = 0; i < 4; ++i) {
+ vb->write(deviceQuad.x(i), deviceQuad.y(i), localQuad.x(i), localQuad.y(i), texDomain);
+ }
+}
+
+// 2D (XY), no explicit coverage, UV locals, vertex color, tex domain but no geometry domain
+// This represents transparent, non AA (or AA with cov. as alpha), textured rects with strict sample
+static void write_2d_color_uv_strict(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
+ // Assert assumptions about VertexSpec
+ SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kNone ||
+ spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithColor);
+ SkASSERT(spec.hasVertexColors());
+ SkASSERT(!spec.requiresGeometryDomain());
+ SkASSERT(spec.hasDomain());
+
+ bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kHalf;
+ for (int i = 0; i < 4; ++i) {
+ // If this is not coverage-with-alpha, make sure coverage == 1 so it doesn't do anything
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithColor ||
+ coverage[i] == 1.f);
+ vb->write(deviceQuad.x(i), deviceQuad.y(i), GrVertexColor(color * coverage[i], wide),
+ localQuad.x(i), localQuad.y(i), texDomain);
+ }
+}
+
+// 2D (XY), explicit coverage, UV locals, no color, tex domain but no geometry domain
+// This represents opaque, AA, textured rects with strict uv sampling
+static void write_2d_cov_uv_strict(GrVertexWriter* vb, const GrQuadPerEdgeAA::VertexSpec& spec,
+ const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const float coverage[4], const SkPMColor4f& color,
+ const SkRect& geomDomain, const SkRect& texDomain) {
+ // Assert assumptions about VertexSpec
+ SkASSERT(spec.deviceQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective);
+ SkASSERT(spec.coverageMode() == GrQuadPerEdgeAA::CoverageMode::kWithPosition);
+ SkASSERT(!spec.hasVertexColors());
+ SkASSERT(!spec.requiresGeometryDomain());
+ SkASSERT(spec.hasDomain());
+
+ for (int i = 0; i < 4; ++i) {
+ vb->write(deviceQuad.x(i), deviceQuad.y(i), coverage[i],
+ localQuad.x(i), localQuad.y(i), texDomain);
+ }
+}
+
} // anonymous namespace
namespace GrQuadPerEdgeAA {
@@ -86,40 +246,82 @@
}
}
-////////////////// Tessellate Implementation
+////////////////// Tessellator Implementation
-void* Tessellate(void* vertices, const VertexSpec& spec, const GrQuad& deviceQuad,
- const SkPMColor4f& color4f, const GrQuad& localQuad, const SkRect& domain,
- GrQuadAAFlags aaFlags) {
- SkASSERT(deviceQuad.quadType() <= spec.deviceQuadType());
- SkASSERT(!spec.hasLocalCoords() || localQuad.quadType() <= spec.localQuadType());
+Tessellator::WriteQuadProc Tessellator::GetWriteQuadProc(const VertexSpec& spec) {
+ // All specialized writing functions requires 2D geometry and no geometry domain. This is not
+ // the same as just checking device type vs. kRectilinear since non-AA general 2D quads do not
+ // require a geometry domain and could then go through a fast path.
+ if (spec.deviceQuadType() != GrQuad::Type::kPerspective && !spec.requiresGeometryDomain()) {
+ CoverageMode mode = spec.coverageMode();
+ if (spec.hasVertexColors()) {
+ if (mode != CoverageMode::kWithPosition) {
+ // Vertex colors, but no explicit coverage
+ if (!spec.hasLocalCoords()) {
+ // Non-UV with vertex colors (possibly with coverage folded into alpha)
+ return write_2d_color;
+ } else if (spec.localQuadType() != GrQuad::Type::kPerspective) {
+ // UV locals with vertex colors (possibly with coverage-as-alpha)
+ return spec.hasDomain() ? write_2d_color_uv_strict : write_2d_color_uv;
+ }
+ }
+ // Else fall through; this is a spec that requires vertex colors and explicit coverage,
+ // which means it's anti-aliased and the FPs don't support coverage as alpha, or
+ // it uses 3D local coordinates.
+ } else if (spec.hasLocalCoords() && spec.localQuadType() != GrQuad::Type::kPerspective) {
+ if (mode == CoverageMode::kWithPosition) {
+ // UV locals with explicit coverage
+ return spec.hasDomain() ? write_2d_cov_uv_strict : write_2d_cov_uv;
+ } else {
+ SkASSERT(mode == CoverageMode::kNone);
+ return spec.hasDomain() ? write_2d_uv_strict : write_2d_uv;
+ }
+ }
+ // Else fall through to generic vertex function; this is a spec that has no vertex colors
+ // and [no|uvr] local coords, which doesn't happen often enough to warrant specialization.
+ }
- GrQuadPerEdgeAA::CoverageMode mode = spec.coverageMode();
+ // Arbitrary spec hits the slow path
+ return write_quad_generic;
+}
+
+Tessellator::Tessellator(const VertexSpec& spec)
+ : fVertexSpec(spec)
+ , fWriteProc(Tessellator::GetWriteQuadProc(spec)) {}
+
+void* Tessellator::append(void* vertices, const GrQuad& deviceQuad, const GrQuad& localQuad,
+ const SkPMColor4f& color, const SkRect& uvDomain, GrQuadAAFlags aaFlags) {
+ SkASSERT(deviceQuad.quadType() <= fVertexSpec.deviceQuadType());
+ SkASSERT(!fVertexSpec.hasLocalCoords() || localQuad.quadType() <= fVertexSpec.localQuadType());
+
+ static const float kFullCoverage[4] = {1.f, 1.f, 1.f, 1.f};
+ static const float kZeroCoverage[4] = {0.f, 0.f, 0.f, 0.f};
+ static const SkRect kIgnoredDomain = SkRect::MakeEmpty();
GrVertexWriter vb{vertices};
- if (spec.usesCoverageAA()) {
- SkASSERT(mode == CoverageMode::kWithPosition || mode == CoverageMode::kWithColor);
+ if (fVertexSpec.usesCoverageAA()) {
+ SkASSERT(fVertexSpec.coverageMode() == CoverageMode::kWithColor ||
+ fVertexSpec.coverageMode() == CoverageMode::kWithPosition);
// Must calculate inner and outer quadrilaterals for the vertex coverage ramps, and possibly
- // a geometry domain
+ // a geometry domain if corners are not right angles
SkRect geomDomain;
- if (spec.requiresGeometryDomain()) {
+ if (fVertexSpec.requiresGeometryDomain()) {
geomDomain = deviceQuad.bounds();
geomDomain.outset(0.5f, 0.5f); // account for AA expansion
}
- GrQuadUtils::TessellationHelper helper;
if (aaFlags == GrQuadAAFlags::kNone) {
// Have to write the coverage AA vertex structure, but there's no math to be done for a
// non-aa quad batched into a coverage AA op.
- write_quad(&vb, spec, mode, 1.f, color4f, geomDomain, domain, deviceQuad, localQuad);
+ fWriteProc(&vb, fVertexSpec, deviceQuad, localQuad, kFullCoverage, color,
+ geomDomain, uvDomain);
// Since we pass the same corners in, the outer vertex structure will have 0 area and
// the coverage interpolation from 1 to 0 will not be visible.
- write_quad(&vb, spec, mode, 0.f, color4f, geomDomain, domain, deviceQuad, localQuad);
+ fWriteProc(&vb, fVertexSpec, deviceQuad, localQuad, kZeroCoverage, color,
+ geomDomain, uvDomain);
} else {
- // TODO(michaelludwig) - Update TessellateHelper to select processing functions based on
- // the vertexspec once per op, and then burn through all quads with the selected
- // function ptr.
- helper.reset(deviceQuad, spec.hasLocalCoords() ? &localQuad : nullptr);
+ // Reset the tessellation helper to match the current geometry
+ fAAHelper.reset(deviceQuad, fVertexSpec.hasLocalCoords() ? &localQuad : nullptr);
// Edge inset/outset distance ordered LBTR, set to 0.5 for a half pixel if the AA flag
// is turned on, or 0.0 if the edge is not anti-aliased.
@@ -133,22 +335,25 @@
(aaFlags & GrQuadAAFlags::kRight) ? 0.5f : 0.f };
}
- // Write inner vertices first
GrQuad aaDeviceQuad, aaLocalQuad;
- skvx::Vec<4, float> coverage = helper.inset(edgeDistances, &aaDeviceQuad, &aaLocalQuad);
- write_quad(&vb, spec, mode, coverage, color4f, geomDomain, domain,
- aaDeviceQuad, aaLocalQuad);
+
+ // Write inner vertices first
+ float coverage[4];
+ fAAHelper.inset(edgeDistances, &aaDeviceQuad, &aaLocalQuad).store(coverage);
+ fWriteProc(&vb, fVertexSpec, aaDeviceQuad, aaLocalQuad, coverage, color,
+ geomDomain, uvDomain);
// Then outer vertices, which use 0.f for their coverage
- helper.outset(edgeDistances, &aaDeviceQuad, &aaLocalQuad);
- write_quad(&vb, spec, mode, 0.f, color4f, geomDomain, domain,
- aaDeviceQuad, aaLocalQuad);
+ fAAHelper.outset(edgeDistances, &aaDeviceQuad, &aaLocalQuad);
+ fWriteProc(&vb, fVertexSpec, aaDeviceQuad, aaLocalQuad, kZeroCoverage, color,
+ geomDomain, uvDomain);
}
} else {
// No outsetting needed, just write a single quad with full coverage
- SkASSERT(mode == CoverageMode::kNone && !spec.requiresGeometryDomain());
- write_quad(&vb, spec, mode, 1.f, color4f, SkRect::MakeEmpty(), domain,
- deviceQuad, localQuad);
+ SkASSERT(fVertexSpec.coverageMode() == CoverageMode::kNone &&
+ !fVertexSpec.requiresGeometryDomain());
+ fWriteProc(&vb, fVertexSpec, deviceQuad, localQuad, kFullCoverage, color,
+ kIgnoredDomain, uvDomain);
}
return vb.fPtr;