/* * 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 "src/gpu/ops/GrQuadPerEdgeAA.h" #include "include/private/SkVx.h" #include "src/gpu/SkGr.h" #include "src/gpu/geometry/GrQuadUtils.h" #include "src/gpu/glsl/GrGLSLColorSpaceXformHelper.h" #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h" #include "src/gpu/glsl/GrGLSLGeometryProcessor.h" #include "src/gpu/glsl/GrGLSLVarying.h" #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h" static_assert((int)GrQuadAAFlags::kLeft == SkCanvas::kLeft_QuadAAFlag); static_assert((int)GrQuadAAFlags::kTop == SkCanvas::kTop_QuadAAFlag); static_assert((int)GrQuadAAFlags::kRight == SkCanvas::kRight_QuadAAFlag); static_assert((int)GrQuadAAFlags::kBottom == SkCanvas::kBottom_QuadAAFlag); static_assert((int)GrQuadAAFlags::kNone == SkCanvas::kNone_QuadAAFlags); static_assert((int)GrQuadAAFlags::kAll == SkCanvas::kAll_QuadAAFlags); namespace { // 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& geomSubset, const SkRect& texSubset) { static constexpr auto If = GrVertexWriter::If; SkASSERT(!spec.hasLocalCoords() || localQuad); 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. vb->write(deviceQuad->x(i), deviceQuad->y(i), If(spec.deviceQuadType() == GrQuad::Type::kPerspective, deviceQuad->w(i)), If(mode == GrQuadPerEdgeAA::CoverageMode::kWithPosition, coverage[i])); // save color if (spec.hasVertexColors()) { bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kFloat; vb->write(GrVertexColor( color * (mode == GrQuadPerEdgeAA::CoverageMode::kWithColor ? coverage[i] : 1.f), wide)); } // save local position if (spec.hasLocalCoords()) { vb->write(localQuad->x(i), localQuad->y(i), If(spec.localQuadType() == GrQuad::Type::kPerspective, localQuad->w(i))); } // save the geometry subset if (spec.requiresGeometrySubset()) { vb->write(geomSubset); } // save the texture subset if (spec.hasSubset()) { vb->write(texSubset); } } } // 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 subset, no texture subsetn // 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& geomSubset, const SkRect& texSubset) { // 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.requiresGeometrySubset()); SkASSERT(!spec.hasSubset()); // We don't assert that localQuad == nullptr, since it is possible for GrFillRectOp to // accumulate local coords conservatively (paint not trivial), and then after analysis realize // the processors don't need local coordinates. bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kFloat; 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 subset, no texture subset // 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& geomSubset, const SkRect& texSubset) { // 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.requiresGeometrySubset()); SkASSERT(!spec.hasSubset()); SkASSERT(localQuad); 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 subsets // 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& geomSubset, const SkRect& texSubset) { // 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.requiresGeometrySubset()); SkASSERT(!spec.hasSubset()); SkASSERT(localQuad); bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kFloat; 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 subset, no texture subset // 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& geomSubset, const SkRect& texSubset) { // 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.requiresGeometrySubset()); SkASSERT(!spec.hasSubset()); SkASSERT(localQuad); 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 subset. These are included to benefit SkiaRenderer, which must make // use of both fast and strict constrained subsets. 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 subsets more, these 3 functions should probably be removed for simplicity. // 2D (XY), no explicit coverage, UV locals, no color, tex subset but no geometry subset // 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& geomSubset, const SkRect& texSubset) { // 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.requiresGeometrySubset()); SkASSERT(spec.hasSubset()); SkASSERT(localQuad); for (int i = 0; i < 4; ++i) { vb->write(deviceQuad->x(i), deviceQuad->y(i), localQuad->x(i), localQuad->y(i), texSubset); } } // 2D (XY), no explicit coverage, UV locals, vertex color, tex subset but no geometry subset // 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& geomSubset, const SkRect& texSubset) { // 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.requiresGeometrySubset()); SkASSERT(spec.hasSubset()); SkASSERT(localQuad); bool wide = spec.colorType() == GrQuadPerEdgeAA::ColorType::kFloat; 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), texSubset); } } // 2D (XY), explicit coverage, UV locals, no color, tex subset but no geometry subset // 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& geomSubset, const SkRect& texSubset) { // 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.requiresGeometrySubset()); SkASSERT(spec.hasSubset()); SkASSERT(localQuad); for (int i = 0; i < 4; ++i) { vb->write(deviceQuad->x(i), deviceQuad->y(i), coverage[i], localQuad->x(i), localQuad->y(i), texSubset); } } } // anonymous namespace namespace GrQuadPerEdgeAA { IndexBufferOption CalcIndexBufferOption(GrAAType aa, int numQuads) { if (aa == GrAAType::kCoverage) { return IndexBufferOption::kPictureFramed; } else if (numQuads > 1) { return IndexBufferOption::kIndexedRects; } else { return IndexBufferOption::kTriStrips; } } // This is a more elaborate version of fitsInBytes() that allows "no color" for white ColorType MinColorType(SkPMColor4f color) { if (color == SK_PMColor4fWHITE) { return ColorType::kNone; } else { return color.fitsInBytes() ? ColorType::kByte : ColorType::kFloat; } } ////////////////// Tessellator Implementation Tessellator::WriteQuadProc Tessellator::GetWriteQuadProc(const VertexSpec& spec) { // All specialized writing functions requires 2D geometry and no geometry subset. This is not // the same as just checking device type vs. kRectilinear since non-AA general 2D quads do not // require a geometry subset and could then go through a fast path. if (spec.deviceQuadType() != GrQuad::Type::kPerspective && !spec.requiresGeometrySubset()) { 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.hasSubset() ? 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.hasSubset() ? write_2d_cov_uv_strict : write_2d_cov_uv; } else { SkASSERT(mode == CoverageMode::kNone); return spec.hasSubset() ? 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. } // Arbitrary spec hits the slow path return write_quad_generic; } Tessellator::Tessellator(const VertexSpec& spec, char* vertices) : fVertexSpec(spec) , fVertexWriter{vertices} , fWriteProc(Tessellator::GetWriteQuadProc(spec)) {} void Tessellator::append(GrQuad* deviceQuad, GrQuad* localQuad, const SkPMColor4f& color, const SkRect& uvSubset, GrQuadAAFlags aaFlags) { // We allow Tessellator to be created with a null vertices pointer for convenience, but it is // assumed it will never actually be used in those cases. SkASSERT(fVertexWriter.fPtr); SkASSERT(deviceQuad->quadType() <= fVertexSpec.deviceQuadType()); SkASSERT(localQuad || !fVertexSpec.hasLocalCoords()); 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 kIgnoredSubset = SkRect::MakeEmpty(); 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 subset if corners are not right angles SkRect geomSubset; if (fVertexSpec.requiresGeometrySubset()) { #ifdef SK_USE_LEGACY_AA_QUAD_SUBSET geomSubset = deviceQuad->bounds(); geomSubset.outset(0.5f, 0.5f); // account for AA expansion #else // Our GP code expects a 0.5 outset rect (coverage is computed as 0 at the values of // the uniform). However, if we have quad edges that aren't supposed to be antialiased // they may lie close to the bounds. So in that case we outset by an additional 0.5. // This is a sort of backup clipping mechanism for cases where quad outsetting of nearly // parallel edges produces long thin extrusions from the original geometry. float outset = aaFlags == GrQuadAAFlags::kAll ? 0.5f : 1.f; geomSubset = deviceQuad->bounds().makeOutset(outset, outset); #endif } 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. fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kFullCoverage, color, geomSubset, uvSubset); // 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. fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kZeroCoverage, color, geomSubset, uvSubset); } else { // Reset the tessellation helper to match the current geometry fAAHelper.reset(*deviceQuad, localQuad); // 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. skvx::Vec<4, float> edgeDistances; if (aaFlags == GrQuadAAFlags::kAll) { edgeDistances = 0.5f; } else { edgeDistances = { (aaFlags & GrQuadAAFlags::kLeft) ? 0.5f : 0.f, (aaFlags & GrQuadAAFlags::kBottom) ? 0.5f : 0.f, (aaFlags & GrQuadAAFlags::kTop) ? 0.5f : 0.f, (aaFlags & GrQuadAAFlags::kRight) ? 0.5f : 0.f }; } // Write inner vertices first float coverage[4]; fAAHelper.inset(edgeDistances, deviceQuad, localQuad).store(coverage); fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, coverage, color, geomSubset, uvSubset); // Then outer vertices, which use 0.f for their coverage. If the inset was degenerate // to a line (had all coverages < 1), tweak the outset distance so the outer frame's // narrow axis reaches out to 2px, which gives better animation under translation. if (coverage[0] < 1.f && coverage[1] < 1.f && coverage[2] < 1.f && coverage[3] < 1.f) { skvx::Vec<4, float> len = fAAHelper.getEdgeLengths(); // Using max guards us against trying to scale a degenerate triangle edge of 0 len // up to 2px. The shuffles are so that edge 0's adjustment is based on the lengths // of its connecting edges (1 and 2), and so forth. skvx::Vec<4, float> maxWH = max(skvx::shuffle<1, 0, 3, 2>(len), skvx::shuffle<2, 3, 0, 1>(len)); // wh + 2e' = 2, so e' = (2 - wh) / 2 => e' = e * (2 - wh). But if w or h > 1, then // 2 - wh < 1 and represents the non-narrow axis so clamp to 1. edgeDistances *= max(1.f, 2.f - maxWH); } fAAHelper.outset(edgeDistances, deviceQuad, localQuad); fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kZeroCoverage, color, geomSubset, uvSubset); } } else { // No outsetting needed, just write a single quad with full coverage SkASSERT(fVertexSpec.coverageMode() == CoverageMode::kNone && !fVertexSpec.requiresGeometrySubset()); fWriteProc(&fVertexWriter, fVertexSpec, deviceQuad, localQuad, kFullCoverage, color, kIgnoredSubset, uvSubset); } } sk_sp GetIndexBuffer(GrMeshDrawOp::Target* target, IndexBufferOption indexBufferOption) { auto resourceProvider = target->resourceProvider(); switch (indexBufferOption) { case IndexBufferOption::kPictureFramed: return resourceProvider->refAAQuadIndexBuffer(); case IndexBufferOption::kIndexedRects: return resourceProvider->refNonAAQuadIndexBuffer(); case IndexBufferOption::kTriStrips: // fall through default: return nullptr; } } int QuadLimit(IndexBufferOption option) { switch (option) { case IndexBufferOption::kPictureFramed: return GrResourceProvider::MaxNumAAQuads(); case IndexBufferOption::kIndexedRects: return GrResourceProvider::MaxNumNonAAQuads(); case IndexBufferOption::kTriStrips: return SK_MaxS32; // not limited by an indexBuffer } SkUNREACHABLE; } void IssueDraw(const GrCaps& caps, GrOpsRenderPass* renderPass, const VertexSpec& spec, int runningQuadCount, int quadsInDraw, int maxVerts, int absVertBufferOffset) { if (spec.indexBufferOption() == IndexBufferOption::kTriStrips) { int offset = absVertBufferOffset + runningQuadCount * GrResourceProvider::NumVertsPerNonAAQuad(); renderPass->draw(4, offset); return; } SkASSERT(spec.indexBufferOption() == IndexBufferOption::kPictureFramed || spec.indexBufferOption() == IndexBufferOption::kIndexedRects); int maxNumQuads, numIndicesPerQuad, numVertsPerQuad; if (spec.indexBufferOption() == IndexBufferOption::kPictureFramed) { // AA uses 8 vertices and 30 indices per quad, basically nested rectangles maxNumQuads = GrResourceProvider::MaxNumAAQuads(); numIndicesPerQuad = GrResourceProvider::NumIndicesPerAAQuad(); numVertsPerQuad = GrResourceProvider::NumVertsPerAAQuad(); } else { // Non-AA uses 4 vertices and 6 indices per quad maxNumQuads = GrResourceProvider::MaxNumNonAAQuads(); numIndicesPerQuad = GrResourceProvider::NumIndicesPerNonAAQuad(); numVertsPerQuad = GrResourceProvider::NumVertsPerNonAAQuad(); } SkASSERT(runningQuadCount + quadsInDraw <= maxNumQuads); if (caps.avoidLargeIndexBufferDraws()) { // When we need to avoid large index buffer draws we modify the base vertex of the draw // which, in GL, requires rebinding all vertex attrib arrays, so a base index is generally // preferred. int offset = absVertBufferOffset + runningQuadCount * numVertsPerQuad; renderPass->drawIndexPattern(numIndicesPerQuad, quadsInDraw, maxNumQuads, numVertsPerQuad, offset); } else { int baseIndex = runningQuadCount * numIndicesPerQuad; int numIndicesToDraw = quadsInDraw * numIndicesPerQuad; int minVertex = runningQuadCount * numVertsPerQuad; int maxVertex = (runningQuadCount + quadsInDraw) * numVertsPerQuad - 1; // inclusive renderPass->drawIndexed(numIndicesToDraw, baseIndex, minVertex, maxVertex, absVertBufferOffset); } } ////////////////// VertexSpec Implementation int VertexSpec::deviceDimensionality() const { return this->deviceQuadType() == GrQuad::Type::kPerspective ? 3 : 2; } int VertexSpec::localDimensionality() const { return fHasLocalCoords ? (this->localQuadType() == GrQuad::Type::kPerspective ? 3 : 2) : 0; } CoverageMode VertexSpec::coverageMode() const { if (this->usesCoverageAA()) { if (this->compatibleWithCoverageAsAlpha() && this->hasVertexColors() && !this->requiresGeometrySubset()) { // Using a geometric subset acts as a second source of coverage and folding // the original coverage into color makes it impossible to apply the color's // alpha to the geometric subset's coverage when the original shape is clipped. return CoverageMode::kWithColor; } else { return CoverageMode::kWithPosition; } } else { return CoverageMode::kNone; } } // This needs to stay in sync w/ QuadPerEdgeAAGeometryProcessor::initializeAttrs size_t VertexSpec::vertexSize() const { bool needsPerspective = (this->deviceDimensionality() == 3); CoverageMode coverageMode = this->coverageMode(); size_t count = 0; if (coverageMode == CoverageMode::kWithPosition) { if (needsPerspective) { count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType); } else { count += GrVertexAttribTypeSize(kFloat2_GrVertexAttribType) + GrVertexAttribTypeSize(kFloat_GrVertexAttribType); } } else { if (needsPerspective) { count += GrVertexAttribTypeSize(kFloat3_GrVertexAttribType); } else { count += GrVertexAttribTypeSize(kFloat2_GrVertexAttribType); } } if (this->requiresGeometrySubset()) { count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType); } count += this->localDimensionality() * GrVertexAttribTypeSize(kFloat_GrVertexAttribType); if (ColorType::kByte == this->colorType()) { count += GrVertexAttribTypeSize(kUByte4_norm_GrVertexAttribType); } else if (ColorType::kFloat == this->colorType()) { count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType); } if (this->hasSubset()) { count += GrVertexAttribTypeSize(kFloat4_GrVertexAttribType); } return count; } ////////////////// Geometry Processor Implementation class QuadPerEdgeAAGeometryProcessor : public GrGeometryProcessor { public: using Saturate = GrTextureOp::Saturate; static GrGeometryProcessor* Make(SkArenaAlloc* arena, const VertexSpec& spec) { return arena->make([&](void* ptr) { return new (ptr) QuadPerEdgeAAGeometryProcessor(spec); }); } static GrGeometryProcessor* Make(SkArenaAlloc* arena, const VertexSpec& vertexSpec, const GrShaderCaps& caps, const GrBackendFormat& backendFormat, GrSamplerState samplerState, const GrSwizzle& swizzle, sk_sp textureColorSpaceXform, Saturate saturate) { return arena->make([&](void* ptr) { return new (ptr) QuadPerEdgeAAGeometryProcessor( vertexSpec, caps, backendFormat, samplerState, swizzle, std::move(textureColorSpaceXform), saturate); }); } const char* name() const override { return "QuadPerEdgeAAGeometryProcessor"; } void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const override { // texturing, device-dimensions are single bit flags b->addBool(fTexSubset.isInitialized(), "subset"); b->addBool(fSampler.isInitialized(), "textured"); b->addBool(fNeedsPerspective, "perspective"); b->addBool((fSaturate == Saturate::kYes), "saturate"); b->addBool(fLocalCoord.isInitialized(), "hasLocalCoords"); if (fLocalCoord.isInitialized()) { // 2D (0) or 3D (1) b->addBits(1, (kFloat3_GrVertexAttribType == fLocalCoord.cpuType()), "localCoordsType"); } b->addBool(fColor.isInitialized(), "hasColor"); if (fColor.isInitialized()) { // bytes (0) or floats (1) b->addBits(1, (kFloat4_GrVertexAttribType == fColor.cpuType()), "colorType"); } // and coverage mode, 00 for none, 01 for withposition, 10 for withcolor, 11 for // position+geomsubset uint32_t coverageKey = 0; SkASSERT(!fGeomSubset.isInitialized() || fCoverageMode == CoverageMode::kWithPosition); if (fCoverageMode != CoverageMode::kNone) { coverageKey = fGeomSubset.isInitialized() ? 0x3 : (CoverageMode::kWithPosition == fCoverageMode ? 0x1 : 0x2); } b->addBits(2, coverageKey, "coverageMode"); b->add32(GrColorSpaceXform::XformKey(fTextureColorSpaceXform.get()), "colorSpaceXform"); } GrGLSLGeometryProcessor* createGLSLInstance(const GrShaderCaps& caps) const override { class GLSLProcessor : public GrGLSLGeometryProcessor { public: void setData(const GrGLSLProgramDataManager& pdman, const GrShaderCaps&, const GrGeometryProcessor& geomProc) override { const auto& gp = geomProc.cast(); fTextureColorSpaceXformHelper.setData(pdman, gp.fTextureColorSpaceXform.get()); } private: void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override { using Interpolation = GrGLSLVaryingHandler::Interpolation; const auto& gp = args.fGeomProc.cast(); fTextureColorSpaceXformHelper.emitCode(args.fUniformHandler, gp.fTextureColorSpaceXform.get()); args.fVaryingHandler->emitAttributes(gp); if (gp.fCoverageMode == CoverageMode::kWithPosition) { // Strip last channel from the vertex attribute to remove coverage and get the // actual position if (gp.fNeedsPerspective) { args.fVertBuilder->codeAppendf("float3 position = %s.xyz;", gp.fPosition.name()); } else { args.fVertBuilder->codeAppendf("float2 position = %s.xy;", gp.fPosition.name()); } gpArgs->fPositionVar = {"position", gp.fNeedsPerspective ? kFloat3_GrSLType : kFloat2_GrSLType, GrShaderVar::TypeModifier::None}; } else { // No coverage to eliminate gpArgs->fPositionVar = gp.fPosition.asShaderVar(); } // This attribute will be uninitialized if earlier FP analysis determined no // local coordinates are needed (and this will not include the inline texture // fetch this GP does before invoking FPs). gpArgs->fLocalCoordVar = gp.fLocalCoord.asShaderVar(); // Solid color before any texturing gets modulated in const char* blendDst; if (gp.fColor.isInitialized()) { SkASSERT(gp.fCoverageMode != CoverageMode::kWithColor || !gp.fNeedsPerspective); // The color cannot be flat if the varying coverage has been modulated into it args.fFragBuilder->codeAppendf("half4 %s;", args.fOutputColor); args.fVaryingHandler->addPassThroughAttribute(gp.fColor, args.fOutputColor, gp.fCoverageMode == CoverageMode::kWithColor ? Interpolation::kInterpolated : Interpolation::kCanBeFlat); blendDst = args.fOutputColor; } else { // Output color must be initialized to something args.fFragBuilder->codeAppendf("half4 %s = half4(1);", args.fOutputColor); blendDst = nullptr; } // 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 subset 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 subset if it is provided if (gp.fTexSubset.isInitialized()) { args.fFragBuilder->codeAppend("float4 subset;"); args.fVaryingHandler->addPassThroughAttribute(gp.fTexSubset, "subset", Interpolation::kCanBeFlat); args.fFragBuilder->codeAppend( "texCoord = clamp(texCoord, subset.LT, subset.RB);"); } // Now modulate the starting output color by the texture lookup args.fFragBuilder->codeAppendf( "%s = %s(", args.fOutputColor, (gp.fSaturate == Saturate::kYes) ? "saturate" : ""); args.fFragBuilder->appendTextureLookupAndBlend( blendDst, SkBlendMode::kModulate, args.fTexSamplers[0], "texCoord", &fTextureColorSpaceXformHelper); args.fFragBuilder->codeAppend(");"); } else { // Saturate is only intended for use with a proxy to account for the fact // that GrTextureOp skips SkPaint conversion, which normally handles this. SkASSERT(gp.fSaturate == Saturate::kNo); } // And lastly, output the coverage calculation code if (gp.fCoverageMode == CoverageMode::kWithPosition) { GrGLSLVarying coverage(kFloat_GrSLType); args.fVaryingHandler->addVarying("coverage", &coverage); if (gp.fNeedsPerspective) { // Multiply by "W" in the vertex shader, then by 1/w (sk_FragCoord.w) in // the fragment shader to get screen-space linear coverage. args.fVertBuilder->codeAppendf("%s = %s.w * %s.z;", coverage.vsOut(), gp.fPosition.name(), gp.fPosition.name()); args.fFragBuilder->codeAppendf("float coverage = %s * sk_FragCoord.w;", coverage.fsIn()); } else { args.fVertBuilder->codeAppendf("%s = %s;", coverage.vsOut(), gp.fCoverage.name()); args.fFragBuilder->codeAppendf("float coverage = %s;", coverage.fsIn()); } if (gp.fGeomSubset.isInitialized()) { // Calculate distance from sk_FragCoord to the 4 edges of the subset // and clamp them to (0, 1). Use the minimum of these and the original // coverage. This only has to be done in the exterior triangles, the // interior of the quad geometry can never be clipped by the subset box. args.fFragBuilder->codeAppend("float4 geoSubset;"); args.fVaryingHandler->addPassThroughAttribute(gp.fGeomSubset, "geoSubset", Interpolation::kCanBeFlat); #ifdef SK_USE_LEGACY_AA_QUAD_SUBSET args.fFragBuilder->codeAppend( "if (coverage < 0.5) {" " float4 dists4 = clamp(float4(1, 1, -1, -1) * " "(sk_FragCoord.xyxy - geoSubset), 0, 1);" " float2 dists2 = dists4.xy * dists4.zw;" " coverage = min(coverage, dists2.x * dists2.y);" "}"); #else args.fFragBuilder->codeAppend( // This is lifted from GrAARectEffect. It'd be nice if we could // invoke a FP from a GP rather than duplicate this code. "half4 dists4 = clamp(half4(1, 1, -1, -1) * " "half4(sk_FragCoord.xyxy - geoSubset), 0, 1);\n" "half2 dists2 = dists4.xy + dists4.zw - 1;\n" "half subsetCoverage = dists2.x * dists2.y;\n" "coverage = min(coverage, subsetCoverage);"); #endif } args.fFragBuilder->codeAppendf("half4 %s = half4(half(coverage));", args.fOutputCoverage); } else { // Set coverage to 1, since it's either non-AA or the coverage was already // folded into the output color SkASSERT(!gp.fGeomSubset.isInitialized()); args.fFragBuilder->codeAppendf("const half4 %s = half4(1);", args.fOutputCoverage); } } GrGLSLColorSpaceXformHelper fTextureColorSpaceXformHelper; }; return new GLSLProcessor; } private: QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec) : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID) , fTextureColorSpaceXform(nullptr) { SkASSERT(!spec.hasSubset()); this->initializeAttrs(spec); this->setTextureSamplerCnt(0); } QuadPerEdgeAAGeometryProcessor(const VertexSpec& spec, const GrShaderCaps& caps, const GrBackendFormat& backendFormat, GrSamplerState samplerState, const GrSwizzle& swizzle, sk_sp textureColorSpaceXform, Saturate saturate) : INHERITED(kQuadPerEdgeAAGeometryProcessor_ClassID) , fSaturate(saturate) , fTextureColorSpaceXform(std::move(textureColorSpaceXform)) , fSampler(samplerState, backendFormat, swizzle) { SkASSERT(spec.hasLocalCoords()); this->initializeAttrs(spec); this->setTextureSamplerCnt(1); } // This needs to stay in sync w/ VertexSpec::vertexSize void initializeAttrs(const VertexSpec& spec) { fNeedsPerspective = spec.deviceDimensionality() == 3; fCoverageMode = spec.coverageMode(); if (fCoverageMode == CoverageMode::kWithPosition) { if (fNeedsPerspective) { fPosition = {"positionWithCoverage", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; } else { fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; fCoverage = {"coverage", kFloat_GrVertexAttribType, kFloat_GrSLType}; } } else { if (fNeedsPerspective) { fPosition = {"position", kFloat3_GrVertexAttribType, kFloat3_GrSLType}; } else { fPosition = {"position", kFloat2_GrVertexAttribType, kFloat2_GrSLType}; } } // Need a geometry subset when the quads are AA and not rectilinear, since their AA // outsetting can go beyond a half pixel. if (spec.requiresGeometrySubset()) { fGeomSubset = {"geomSubset", 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 (spec.hasVertexColors()) { fColor = MakeColorAttribute("color", ColorType::kFloat == spec.colorType()); } if (spec.hasSubset()) { fTexSubset = {"texSubset", kFloat4_GrVertexAttribType, kFloat4_GrSLType}; } this->setVertexAttributes(&fPosition, 6); } const TextureSampler& onTextureSampler(int) const override { return fSampler; } Attribute fPosition; // May contain coverage as last channel Attribute fCoverage; // Used for non-perspective position to avoid Intel Metal issues Attribute fColor; // May have coverage modulated in if the FPs support it Attribute fLocalCoord; Attribute fGeomSubset; // Screen-space bounding box on geometry+aa outset Attribute fTexSubset; // Texture-space bounding box on local coords // The positions attribute may have coverage built into it, so float3 is an ambiguous type // and may mean 2d with coverage, or 3d with no coverage bool fNeedsPerspective; // Should saturate() be called on the color? Only relevant when created with a texture. Saturate fSaturate = Saturate::kNo; CoverageMode fCoverageMode; // Color space will be null and fSampler.isInitialized() returns false when the GP is configured // to skip texturing. sk_sp fTextureColorSpaceXform; TextureSampler fSampler; using INHERITED = GrGeometryProcessor; }; GrGeometryProcessor* MakeProcessor(SkArenaAlloc* arena, const VertexSpec& spec) { return QuadPerEdgeAAGeometryProcessor::Make(arena, spec); } GrGeometryProcessor* MakeTexturedProcessor(SkArenaAlloc* arena, const VertexSpec& spec, const GrShaderCaps& caps, const GrBackendFormat& backendFormat, GrSamplerState samplerState, const GrSwizzle& swizzle, sk_sp textureColorSpaceXform, Saturate saturate) { return QuadPerEdgeAAGeometryProcessor::Make(arena, spec, caps, backendFormat, samplerState, swizzle, std::move(textureColorSpaceXform), saturate); } } // namespace GrQuadPerEdgeAA