Coverage counting path renderer
Initial implementation of a GPU path renderer that draws antialiased
paths by counting coverage in an offscreen buffer.
Initially disabled until it has had time to soak.
Bug: skia:
Change-Id: I003d8cfdf8dc62641581b5ea2dc4f0aa00108df6
Reviewed-on: https://skia-review.googlesource.com/21541
Commit-Queue: Chris Dalton <csmartdalton@google.com>
Reviewed-by: Greg Daniel <egdaniel@google.com>
Reviewed-by: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
new file mode 100644
index 0000000..45fad1f
--- /dev/null
+++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "GrCoverageCountingPathRenderer.h"
+
+#include "GrCaps.h"
+#include "GrClip.h"
+#include "GrGpu.h"
+#include "GrGpuCommandBuffer.h"
+#include "SkMakeUnique.h"
+#include "SkMatrix.h"
+#include "GrOpFlushState.h"
+#include "GrRenderTargetOpList.h"
+#include "GrStyle.h"
+#include "ccpr/GrCCPRPathProcessor.h"
+
+using DrawPathsOp = GrCoverageCountingPathRenderer::DrawPathsOp;
+using ScissorMode = GrCCPRCoverageOpsBuilder::ScissorMode;
+
+bool GrCoverageCountingPathRenderer::IsSupported(const GrCaps& caps) {
+ const GrShaderCaps& shaderCaps = *caps.shaderCaps();
+ return shaderCaps.geometryShaderSupport() &&
+ shaderCaps.texelBufferSupport() &&
+ shaderCaps.integerSupport() &&
+ shaderCaps.flatInterpolationSupport() &&
+ shaderCaps.maxVertexSamplers() >= 1 &&
+ caps.instanceAttribSupport() &&
+ caps.isConfigTexturable(kAlpha_half_GrPixelConfig) &&
+ caps.isConfigRenderable(kAlpha_half_GrPixelConfig, /*withMSAA=*/false);
+}
+
+sk_sp<GrCoverageCountingPathRenderer>
+GrCoverageCountingPathRenderer::CreateIfSupported(const GrCaps& caps) {
+ return sk_sp<GrCoverageCountingPathRenderer>(IsSupported(caps) ?
+ new GrCoverageCountingPathRenderer : nullptr);
+}
+
+bool GrCoverageCountingPathRenderer::onCanDrawPath(const CanDrawPathArgs& args) const {
+ if (!args.fShape->style().isSimpleFill() ||
+ args.fShape->inverseFilled() ||
+ args.fViewMatrix->hasPerspective() ||
+ GrAAType::kCoverage != args.fAAType) {
+ return false;
+ }
+
+ SkPath path;
+ args.fShape->asPath(&path);
+ return !SkPathPriv::ConicWeightCnt(path);
+}
+
+bool GrCoverageCountingPathRenderer::onDrawPath(const DrawPathArgs& args) {
+ SkASSERT(!fFlushing);
+ SkASSERT(!args.fShape->isEmpty());
+
+ auto op = skstd::make_unique<DrawPathsOp>(this, args, args.fPaint.getColor());
+ args.fRenderTargetContext->addDrawOp(*args.fClip, std::move(op));
+
+ return true;
+}
+
+GrCoverageCountingPathRenderer::DrawPathsOp::DrawPathsOp(GrCoverageCountingPathRenderer* ccpr,
+ const DrawPathArgs& args, GrColor color)
+ : INHERITED(ClassID())
+ , fCCPR(ccpr)
+ , fSRGBFlags(GrPipeline::SRGBFlagsFromPaint(args.fPaint))
+ , fProcessors(std::move(args.fPaint))
+ , fTailDraw(&fHeadDraw)
+ , fOwningRTPendingOps(nullptr) {
+ SkDEBUGCODE(fBaseInstance = -1);
+ SkDEBUGCODE(fDebugInstanceCount = 1;)
+
+ GrRenderTargetContext* const rtc = args.fRenderTargetContext;
+
+ SkRect devBounds;
+ args.fViewMatrix->mapRect(&devBounds, args.fShape->bounds());
+
+ args.fClip->getConservativeBounds(rtc->width(), rtc->height(), &fHeadDraw.fClipBounds, nullptr);
+ fHeadDraw.fScissorMode = fHeadDraw.fClipBounds.contains(devBounds) ?
+ ScissorMode::kNonScissored : ScissorMode::kScissored;
+ fHeadDraw.fMatrix = *args.fViewMatrix;
+ args.fShape->asPath(&fHeadDraw.fPath);
+ fHeadDraw.fColor = color; // Can't call args.fPaint.getColor() because it has been std::move'd.
+
+ // FIXME: intersect with clip bounds to (hopefully) improve batching.
+ // (This is nontrivial due to assumptions in generating the octagon cover geometry.)
+ this->setBounds(devBounds, GrOp::HasAABloat::kYes, GrOp::IsZeroArea::kNo);
+}
+
+GrDrawOp::RequiresDstTexture DrawPathsOp::finalize(const GrCaps& caps, const GrAppliedClip* clip) {
+ SingleDraw& onlyDraw = this->getOnlyPathDraw();
+ GrProcessorSet::Analysis analysis = fProcessors.finalize(onlyDraw.fColor,
+ GrProcessorAnalysisCoverage::kSingleChannel,
+ clip, false, caps, &onlyDraw.fColor);
+ return analysis.requiresDstTexture() ? RequiresDstTexture::kYes : RequiresDstTexture::kNo;
+}
+
+bool DrawPathsOp::onCombineIfPossible(GrOp* op, const GrCaps& caps) {
+ DrawPathsOp* that = op->cast<DrawPathsOp>();
+ SkASSERT(fCCPR == that->fCCPR);
+ SkASSERT(fOwningRTPendingOps);
+ SkASSERT(fDebugInstanceCount);
+ SkASSERT(that->fDebugInstanceCount);
+
+ if (this->getFillType() != that->getFillType() ||
+ fSRGBFlags != that->fSRGBFlags ||
+ fProcessors != that->fProcessors) {
+ return false;
+ }
+
+ if (RTPendingOps* owningRTPendingOps = that->fOwningRTPendingOps) {
+ SkASSERT(owningRTPendingOps == fOwningRTPendingOps);
+ owningRTPendingOps->fOpList.remove(that);
+ } else {
+ // wasRecorded is not called when the op gets combined first. Count path items here instead.
+ SingleDraw& onlyDraw = that->getOnlyPathDraw();
+ fOwningRTPendingOps->fMaxBufferItems.countPathItems(onlyDraw.fScissorMode, onlyDraw.fPath);
+ }
+
+ fTailDraw->fNext = &fOwningRTPendingOps->fDrawsAllocator.push_back(that->fHeadDraw);
+ fTailDraw = that->fTailDraw == &that->fHeadDraw ? fTailDraw->fNext : that->fTailDraw;
+
+ this->joinBounds(*that);
+
+ SkDEBUGCODE(fDebugInstanceCount += that->fDebugInstanceCount;)
+ SkDEBUGCODE(that->fDebugInstanceCount = 0);
+ return true;
+}
+
+void DrawPathsOp::wasRecorded(GrRenderTargetOpList* opList) {
+ SkASSERT(!fOwningRTPendingOps);
+ SingleDraw& onlyDraw = this->getOnlyPathDraw();
+ fOwningRTPendingOps = &fCCPR->fRTPendingOpsMap[opList->uniqueID()];
+ fOwningRTPendingOps->fOpList.addToTail(this);
+ fOwningRTPendingOps->fMaxBufferItems.countPathItems(onlyDraw.fScissorMode, onlyDraw.fPath);
+}
+
+void GrCoverageCountingPathRenderer::preFlush(GrOnFlushResourceProvider* onFlushRP,
+ const uint32_t* opListIDs, int numOpListIDs,
+ SkTArray<sk_sp<GrRenderTargetContext>>* results) {
+ using PathInstance = GrCCPRPathProcessor::Instance;
+
+ SkASSERT(!fPerFlushIndexBuffer);
+ SkASSERT(!fPerFlushVertexBuffer);
+ SkASSERT(!fPerFlushInstanceBuffer);
+ SkASSERT(fPerFlushAtlases.empty());
+ SkASSERT(!fFlushing);
+ SkDEBUGCODE(fFlushing = true;)
+
+ if (fRTPendingOpsMap.empty()) {
+ return; // Nothing to draw.
+ }
+
+ SkTInternalLList<DrawPathsOp> flushingOps;
+ GrCCPRCoverageOpsBuilder::MaxBufferItems maxBufferItems;
+
+ for (int i = 0; i < numOpListIDs; ++i) {
+ auto it = fRTPendingOpsMap.find(opListIDs[i]);
+ if (fRTPendingOpsMap.end() != it) {
+ RTPendingOps& rtPendingOps = it->second;
+ SkASSERT(!rtPendingOps.fOpList.isEmpty());
+ flushingOps.concat(std::move(rtPendingOps.fOpList));
+ maxBufferItems += rtPendingOps.fMaxBufferItems;
+ }
+ }
+
+ SkASSERT(flushingOps.isEmpty() == !maxBufferItems.fMaxPaths);
+ if (flushingOps.isEmpty()) {
+ return; // Still nothing to draw.
+ }
+
+ fPerFlushIndexBuffer = GrCCPRPathProcessor::FindOrMakeIndexBuffer(onFlushRP);
+ if (!fPerFlushIndexBuffer) {
+ SkDebugf("WARNING: failed to allocate ccpr path index buffer.\n");
+ return;
+ }
+
+ fPerFlushVertexBuffer = GrCCPRPathProcessor::FindOrMakeVertexBuffer(onFlushRP);
+ if (!fPerFlushVertexBuffer) {
+ SkDebugf("WARNING: failed to allocate ccpr path vertex buffer.\n");
+ return;
+ }
+
+ GrCCPRCoverageOpsBuilder atlasOpsBuilder;
+ if (!atlasOpsBuilder.init(onFlushRP, maxBufferItems)) {
+ SkDebugf("WARNING: failed to allocate buffers for coverage ops. No paths will be drawn.\n");
+ return;
+ }
+
+ fPerFlushInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType,
+ maxBufferItems.fMaxPaths * sizeof(PathInstance));
+ if (!fPerFlushInstanceBuffer) {
+ SkDebugf("WARNING: failed to allocate path instance buffer. No paths will be drawn.\n");
+ return;
+ }
+
+ PathInstance* pathInstanceData = static_cast<PathInstance*>(fPerFlushInstanceBuffer->map());
+ SkASSERT(pathInstanceData);
+ int pathInstanceIdx = 0;
+
+ GrCCPRAtlas* atlas = nullptr;
+ SkDEBUGCODE(int skippedPaths = 0;)
+
+ SkTInternalLList<DrawPathsOp>::Iter iter;
+ iter.init(flushingOps, SkTInternalLList<DrawPathsOp>::Iter::kHead_IterStart);
+ while (DrawPathsOp* op = iter.get()) {
+ SkASSERT(op->fDebugInstanceCount > 0);
+ SkASSERT(-1 == op->fBaseInstance);
+ op->fBaseInstance = pathInstanceIdx;
+
+ for (const DrawPathsOp::SingleDraw* draw = &op->fHeadDraw; draw; draw = draw->fNext) {
+ // parsePath gives us two tight bounding boxes: one in device space, as well as a second
+ // one rotated an additional 45 degrees. The path vertex shader uses these two bounding
+ // boxes to generate an octagon that circumscribes the path.
+ SkRect devBounds, devBounds45;
+ atlasOpsBuilder.parsePath(draw->fScissorMode, draw->fMatrix, draw->fPath, &devBounds,
+ &devBounds45);
+
+ SkRect clippedDevBounds = devBounds;
+ if (ScissorMode::kScissored == draw->fScissorMode &&
+ !clippedDevBounds.intersect(devBounds, SkRect::Make(draw->fClipBounds))) {
+ SkDEBUGCODE(--op->fDebugInstanceCount);
+ SkDEBUGCODE(++skippedPaths;)
+ continue;
+ }
+
+ SkIRect clippedDevIBounds;
+ clippedDevBounds.roundOut(&clippedDevIBounds);
+ const int h = clippedDevIBounds.height(), w = clippedDevIBounds.width();
+
+ SkIPoint16 atlasLocation;
+ if (atlas && !atlas->addRect(w, h, &atlasLocation)) {
+ // The atlas is out of room and can't grow any bigger.
+ auto atlasOp = atlasOpsBuilder.createIntermediateOp(atlas->drawBounds());
+ if (auto rtc = atlas->finalize(onFlushRP, std::move(atlasOp))) {
+ results->push_back(std::move(rtc));
+ }
+ if (pathInstanceIdx > op->fBaseInstance) {
+ op->addAtlasBatch(atlas, pathInstanceIdx);
+ }
+ atlas = nullptr;
+ }
+
+ if (!atlas) {
+ atlas = &fPerFlushAtlases.emplace_back(*onFlushRP->caps(), w, h);
+ SkAssertResult(atlas->addRect(w, h, &atlasLocation));
+ }
+
+ const SkMatrix& m = draw->fMatrix;
+ const int16_t offsetX = atlasLocation.x() - static_cast<int16_t>(clippedDevIBounds.x()),
+ offsetY = atlasLocation.y() - static_cast<int16_t>(clippedDevIBounds.y());
+
+ pathInstanceData[pathInstanceIdx++] = {
+ devBounds,
+ devBounds45,
+ {{m.getScaleX(), m.getSkewY(), m.getSkewX(), m.getScaleY()}},
+ {{m.getTranslateX(), m.getTranslateY()}},
+ {{offsetX, offsetY}},
+ draw->fColor
+ };
+
+ atlasOpsBuilder.saveParsedPath(clippedDevIBounds, offsetX, offsetY);
+ }
+
+ SkASSERT(pathInstanceIdx == op->fBaseInstance + op->fDebugInstanceCount);
+ op->addAtlasBatch(atlas, pathInstanceIdx);
+
+ iter.next();
+ }
+
+ SkASSERT(pathInstanceIdx == maxBufferItems.fMaxPaths - skippedPaths);
+ fPerFlushInstanceBuffer->unmap();
+
+ std::unique_ptr<GrDrawOp> atlasOp = atlasOpsBuilder.finalize(atlas->drawBounds());
+ if (auto rtc = atlas->finalize(onFlushRP, std::move(atlasOp))) {
+ results->push_back(std::move(rtc));
+ }
+
+ // Erase these last, once we are done accessing data from the SingleDraw allocators.
+ for (int i = 0; i < numOpListIDs; ++i) {
+ fRTPendingOpsMap.erase(opListIDs[i]);
+ }
+}
+
+void DrawPathsOp::onExecute(GrOpFlushState* flushState) {
+ SkASSERT(fCCPR->fFlushing);
+
+ if (!fCCPR->fPerFlushInstanceBuffer) {
+ return; // Setup failed.
+ }
+
+ GrPipeline pipeline;
+ GrPipeline::InitArgs args;
+ args.fAppliedClip = flushState->drawOpArgs().fAppliedClip;
+ args.fCaps = &flushState->caps();
+ args.fProcessors = &fProcessors;
+ args.fFlags = fSRGBFlags;
+ args.fRenderTarget = flushState->drawOpArgs().fRenderTarget;
+ args.fDstProxy = flushState->drawOpArgs().fDstProxy;
+ pipeline.init(args);
+
+ int baseInstance = fBaseInstance;
+
+ for (int i = 0; i < fAtlasBatches.count(); baseInstance = fAtlasBatches[i++].fEndInstanceIdx) {
+ const AtlasBatch& batch = fAtlasBatches[i];
+ SkASSERT(batch.fEndInstanceIdx > baseInstance);
+
+ if (!batch.fAtlas->textureProxy()) {
+ continue; // Atlas failed to allocate.
+ }
+
+ GrCCPRPathProcessor coverProc(flushState->resourceProvider(), batch.fAtlas->textureProxy(),
+ this->getFillType(), *flushState->gpu()->caps()->shaderCaps());
+
+ GrMesh mesh(GrPrimitiveType::kTriangles);
+ mesh.setIndexedInstanced(fCCPR->fPerFlushIndexBuffer.get(),
+ GrCCPRPathProcessor::kPerInstanceIndexCount,
+ fCCPR->fPerFlushInstanceBuffer.get(),
+ batch.fEndInstanceIdx - baseInstance, baseInstance);
+ mesh.setVertexData(fCCPR->fPerFlushVertexBuffer.get());
+
+ flushState->commandBuffer()->draw(pipeline, coverProc, &mesh, nullptr, 1, this->bounds());
+ }
+
+ SkASSERT(baseInstance == fBaseInstance + fDebugInstanceCount);
+}
+
+void GrCoverageCountingPathRenderer::postFlush() {
+ SkASSERT(fFlushing);
+ fPerFlushAtlases.reset();
+ fPerFlushInstanceBuffer.reset();
+ fPerFlushVertexBuffer.reset();
+ fPerFlushIndexBuffer.reset();
+ SkDEBUGCODE(fFlushing = false;)
+}