diff options
24 files changed, 3820 insertions, 3 deletions
diff --git a/gn/gpu.gni b/gn/gpu.gni index f7160f0e2f..06f990866d 100644 --- a/gn/gpu.gni +++ b/gn/gpu.gni @@ -289,6 +289,24 @@ skia_gpu_sources = [ "$_src/gpu/ops/GrTessellatingPathRenderer.cpp", "$_src/gpu/ops/GrTessellatingPathRenderer.h", + # coverage counting path renderer + "$_src/gpu/ccpr/GrCCPRAtlas.cpp", + "$_src/gpu/ccpr/GrCCPRAtlas.h", + "$_src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp", + "$_src/gpu/ccpr/GrCCPRCoverageOpsBuilder.h", + "$_src/gpu/ccpr/GrCCPRCoverageProcessor.cpp", + "$_src/gpu/ccpr/GrCCPRCoverageProcessor.h", + "$_src/gpu/ccpr/GrCCPRCubicProcessor.cpp", + "$_src/gpu/ccpr/GrCCPRCubicProcessor.h", + "$_src/gpu/ccpr/GrCCPRPathProcessor.cpp", + "$_src/gpu/ccpr/GrCCPRPathProcessor.h", + "$_src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp", + "$_src/gpu/ccpr/GrCCPRQuadraticProcessor.h", + "$_src/gpu/ccpr/GrCCPRTriangleProcessor.cpp", + "$_src/gpu/ccpr/GrCCPRTriangleProcessor.h", + "$_src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp", + "$_src/gpu/ccpr/GrCoverageCountingPathRenderer.h", + "$_src/gpu/effects/Gr1DKernelEffect.h", "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.cpp", "$_src/gpu/effects/GrBlurredEdgeFragmentProcessor.h", diff --git a/gn/samples.gni b/gn/samples.gni index f388e791e0..58f89729da 100644 --- a/gn/samples.gni +++ b/gn/samples.gni @@ -26,6 +26,7 @@ samples_sources = [ "$_samplecode/SampleBigGradient.cpp", "$_samplecode/SampleBitmapRect.cpp", "$_samplecode/SampleBlur.cpp", + "$_samplecode/SampleCCPRGeometry.cpp", "$_samplecode/SampleCamera.cpp", "$_samplecode/SampleChart.cpp", "$_samplecode/SampleCircle.cpp", diff --git a/include/gpu/GrContextOptions.h b/include/gpu/GrContextOptions.h index 5337138f05..f560b31e7b 100644 --- a/include/gpu/GrContextOptions.h +++ b/include/gpu/GrContextOptions.h @@ -91,10 +91,12 @@ struct GrContextOptions { kAAConvex = 1 << 4, kAALinearizing = 1 << 5, kSmall = 1 << 6, - kTessellating = 1 << 7, - kDefault = 1 << 8, + kCoverageCounting = 1 << 7, + kTessellating = 1 << 8, + kDefault = 1 << 9, - kAll = kDefault | (kDefault - 1), + // Temporarily disabling CCPR by default until it has had a time to soak. + kAll = (kDefault | (kDefault - 1)) & ~kCoverageCounting, // For legacy. To be removed when updated in Android. kDistanceField = kSmall diff --git a/samplecode/SampleCCPRGeometry.cpp b/samplecode/SampleCCPRGeometry.cpp new file mode 100644 index 0000000000..a44f03b404 --- /dev/null +++ b/samplecode/SampleCCPRGeometry.cpp @@ -0,0 +1,342 @@ +/* + * 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 "SkTypes.h" + +#if SK_SUPPORT_GPU + +#include "GrContextPriv.h" +#include "GrRenderTargetContext.h" +#include "GrRenderTargetContextPriv.h" +#include "GrResourceProvider.h" +#include "SampleCode.h" +#include "SkCanvas.h" +#include "SkGeometry.h" +#include "SkMakeUnique.h" +#include "SkPaint.h" +#include "SkPath.h" +#include "SkView.h" +#include "ccpr/GrCCPRCoverageProcessor.h" +#include "gl/GrGLGpu.cpp" +#include "ops/GrDrawOp.h" + +using PrimitiveInstance = GrCCPRCoverageProcessor::PrimitiveInstance; +using Mode = GrCCPRCoverageProcessor::Mode; + +static int num_points(Mode mode) { + return mode >= GrCCPRCoverageProcessor::Mode::kSerpentineInsets ? 4 : 3; +} + +static int is_curve(Mode mode) { + return mode >= GrCCPRCoverageProcessor::Mode::kQuadraticHulls; +} + +/** + * This sample visualizes the AA bloat geometry generated by the ccpr geometry shaders. It + * increases the AA bloat by 50x and outputs color instead of coverage (coverage=+1 -> green, + * coverage=0 -> black, coverage=-1 -> red). Use the keys 1-7 to cycle through the different + * geometry processors. + */ +class CCPRGeometryView : public SampleView { +public: + CCPRGeometryView() { this->updateGpuData(); } + void onDrawContent(SkCanvas*) override; + + SkView::Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned) override; + bool onClick(SampleView::Click*) override; + bool onQuery(SkEvent* evt) override; + +private: + class Click; + class Op; + + void updateAndInval() { + this->updateGpuData(); + this->inval(nullptr); + } + + void updateGpuData(); + + Mode fMode = Mode::kTriangleHulls; + + SkPoint fPoints[4] = { + {100.05f, 100.05f}, + {100.05f, 300.95f}, + {400.75f, 300.95f}, + {400.75f, 100.05f} + }; + + SkSTArray<16, SkPoint> fGpuPoints; + SkSTArray<3, PrimitiveInstance> fGpuInstances; + + typedef SampleView INHERITED; +}; + +class CCPRGeometryView::Op : public GrDrawOp { + DEFINE_OP_CLASS_ID + +public: + Op(CCPRGeometryView* view) + : INHERITED(ClassID()) + , fView(view) { + this->setBounds(SkRect::MakeLargest(), GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo); + } + + const char* name() const override { return "[Testing/Sample code] CCPRGeometryView::Op"; } + +private: + FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } + RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override { + return RequiresDstTexture::kNo; + } + bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; } + void onPrepare(GrOpFlushState*) override {} + void onExecute(GrOpFlushState*) override; + + CCPRGeometryView* fView; + + typedef GrDrawOp INHERITED; +}; + +void CCPRGeometryView::onDrawContent(SkCanvas* canvas) { + SkAutoCanvasRestore acr(canvas, true); + canvas->setMatrix(SkMatrix::I()); + + SkPath outline; + outline.moveTo(fPoints[0]); + if (4 == num_points(fMode)) { + outline.cubicTo(fPoints[1], fPoints[2], fPoints[3]); + } else if (is_curve(fMode)) { + outline.quadTo(fPoints[1], fPoints[3]); + } else { + outline.lineTo(fPoints[1]); + outline.lineTo(fPoints[3]); + } + outline.close(); + + SkPaint outlinePaint; + outlinePaint.setColor(0x30000000); + outlinePaint.setStyle(SkPaint::kStroke_Style); + outlinePaint.setStrokeWidth(0); + outlinePaint.setAntiAlias(true); + + canvas->drawPath(outline, outlinePaint); + + const char* caption = "Use GPU backend to visualize geometry."; + + if (GrRenderTargetContext* rtc = + canvas->internal_private_accessTopLayerRenderTargetContext()) { + rtc->priv().testingOnly_addDrawOp(skstd::make_unique<Op>(this)); + caption = GrCCPRCoverageProcessor::GetProcessorName(fMode); + } + + SkPaint pointsPaint; + pointsPaint.setColor(SK_ColorBLUE); + pointsPaint.setStrokeWidth(8); + pointsPaint.setAntiAlias(true); + + if (4 == num_points(fMode)) { + canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, fPoints, pointsPaint); + } else { + canvas->drawPoints(SkCanvas::kPoints_PointMode, 2, fPoints, pointsPaint); + canvas->drawPoints(SkCanvas::kPoints_PointMode, 1, fPoints + 3, pointsPaint); + } + + SkPaint captionPaint; + captionPaint.setTextSize(20); + captionPaint.setColor(SK_ColorBLACK); + captionPaint.setAntiAlias(true); + canvas->drawText(caption, strlen(caption), 10, 30, captionPaint); +} + +void CCPRGeometryView::updateGpuData() { + int vertexCount = num_points(fMode); + int instanceCount = 1; + + fGpuPoints.reset(); + fGpuInstances.reset(); + + if (4 == vertexCount) { + double t[2], s[2]; + SkCubicType type = SkClassifyCubic(fPoints, t, s); + SkSTArray<2, float> chops; + for (int i = 0; i < 2; ++i) { + float chop = t[i] / s[i]; + if (chop > 0 && chop < 1) { + chops.push_back(chop); + } + } + + instanceCount = chops.count() + 1; + SkPoint chopped[10]; + SkChopCubicAt(fPoints, chopped, chops.begin(), chops.count()); + + // Endpoints first, then control points. + for (int i = 0; i <= instanceCount; ++i) { + fGpuPoints.push_back(chopped[3*i]); + } + if (3 == instanceCount && SkCubicType::kLoop == type) { + fGpuPoints[2] = fGpuPoints[1]; // Account for floating point error. + } + for (int i = 0; i < instanceCount; ++i) { + fGpuPoints.push_back(chopped[3*i + 1]); + fGpuPoints.push_back(chopped[3*i + 2]); + // FIXME: we don't bother to send down the correct KLM t,s roots. + fGpuPoints.push_back({0, 0}); + fGpuPoints.push_back({0, 0}); + } + + if (fMode < Mode::kLoopInsets && SkCubicType::kLoop == type) { + fMode = (Mode) ((int) fMode + 2); + } + if (fMode >= Mode::kLoopInsets && SkCubicType::kLoop != type) { + fMode = (Mode) ((int) fMode - 2); + } + } else { + // Endpoints. + fGpuPoints.push_back(fPoints[0]); + fGpuPoints.push_back(fPoints[3]); + // Control points. + fGpuPoints.push_back(fPoints[1]); + } + + if (4 == vertexCount) { + int controlPointsIdx = instanceCount + 1; + for (int i = 0; i < instanceCount; ++i) { + fGpuInstances.push_back().fCubicData = {controlPointsIdx + i * 4, i}; + } + } else if (is_curve(fMode)) { + fGpuInstances.push_back().fQuadraticData = {2, 0}; + } else { + fGpuInstances.push_back().fTriangleData = {0, 2, 1}; // Texel buffer has endpoints first. + } + + for (PrimitiveInstance& instance : fGpuInstances) { + instance.fPackedAtlasOffset = 0; + } +} + +void CCPRGeometryView::Op::onExecute(GrOpFlushState* state) { + GrResourceProvider* rp = state->resourceProvider(); + GrContext* context = state->gpu()->getContext(); + GrGLGpu* glGpu = kOpenGL_GrBackend == context->contextPriv().getBackend() ? + static_cast<GrGLGpu*>(state->gpu()) : nullptr; + int vertexCount = num_points(fView->fMode); + + sk_sp<GrBuffer> pointsBuffer(rp->createBuffer(fView->fGpuPoints.count() * sizeof(SkPoint), + kTexel_GrBufferType, kDynamic_GrAccessPattern, + GrResourceProvider::kNoPendingIO_Flag | + GrResourceProvider::kRequireGpuMemory_Flag, + fView->fGpuPoints.begin())); + if (!pointsBuffer) { + return; + } + + sk_sp<GrBuffer> instanceBuffer(rp->createBuffer(fView->fGpuInstances.count() * 4 * sizeof(int), + kVertex_GrBufferType, kDynamic_GrAccessPattern, + GrResourceProvider::kNoPendingIO_Flag | + GrResourceProvider::kRequireGpuMemory_Flag, + fView->fGpuInstances.begin())); + if (!instanceBuffer) { + return; + } + + GrPipeline pipeline(state->drawOpArgs().fRenderTarget, GrPipeline::ScissorState::kDisabled, + SkBlendMode::kSrcOver); + + GrCCPRCoverageProcessor ccprProc(fView->fMode, pointsBuffer.get()); + SkDEBUGCODE(ccprProc.enableDebugVisualizations();) + + GrMesh mesh(4 == vertexCount ? GrPrimitiveType::kLinesAdjacency : GrPrimitiveType::kTriangles); + mesh.setInstanced(instanceBuffer.get(), fView->fGpuInstances.count(), 0, vertexCount); + + if (glGpu) { + glGpu->handleDirtyContext(); + GR_GL_CALL(glGpu->glInterface(), PolygonMode(GR_GL_FRONT_AND_BACK, GR_GL_LINE)); + GR_GL_CALL(glGpu->glInterface(), Enable(GR_GL_LINE_SMOOTH)); + } + + state->commandBuffer()->draw(pipeline, ccprProc, &mesh, nullptr, 1, this->bounds()); + + if (glGpu) { + context->resetContext(kMisc_GrGLBackendState); + } +} + +class CCPRGeometryView::Click : public SampleView::Click { +public: + Click(SkView* target, int ptIdx) : SampleView::Click(target), fPtIdx(ptIdx) {} + + void doClick(SkPoint points[]) { + if (fPtIdx >= 0) { + this->dragPoint(points, fPtIdx); + } else { + for (int i = 0; i < 4; ++i) { + this->dragPoint(points, i); + } + } + } + +private: + void dragPoint(SkPoint points[], int idx) { + SkIPoint delta = fICurr - fIPrev; + points[idx] += SkPoint::Make(delta.x(), delta.y()); + } + + int fPtIdx; +}; + +SkView::Click* CCPRGeometryView::onFindClickHandler(SkScalar x, SkScalar y, unsigned) { + for (int i = 0; i < 4; ++i) { + if (4 != num_points(fMode) && 2 == i) { + continue; + } + if (fabs(x - fPoints[i].x()) < 20 && fabsf(y - fPoints[i].y()) < 20) { + return new Click(this, i); + } + } + return new Click(this, -1); +} + +bool CCPRGeometryView::onClick(SampleView::Click* click) { + Click* myClick = (Click*) click; + myClick->doClick(fPoints); + this->updateAndInval(); + return true; +} + +bool CCPRGeometryView::onQuery(SkEvent* evt) { + if (SampleCode::TitleQ(*evt)) { + SampleCode::TitleR(evt, "CCPRGeometry"); + return true; + } + SkUnichar unichar; + if (SampleCode::CharQ(*evt, &unichar)) { + if (unichar >= '1' && unichar <= '7') { + fMode = Mode(unichar - '1'); + if (fMode >= Mode::kCombinedTriangleHullsAndEdges) { + fMode = Mode(int(fMode) + 1); + } + this->updateAndInval(); + return true; + } + if (unichar == 'D') { + SkDebugf(" SkPoint fPoints[4] = {\n"); + SkDebugf(" {%f, %f},\n", fPoints[0].x(), fPoints[0].y()); + SkDebugf(" {%f, %f},\n", fPoints[1].x(), fPoints[1].y()); + SkDebugf(" {%f, %f},\n", fPoints[2].x(), fPoints[2].y()); + SkDebugf(" {%f, %f}\n", fPoints[3].x(), fPoints[3].y()); + SkDebugf(" };\n"); + return true; + } + } + return this->INHERITED::onQuery(evt); +} + +DEF_SAMPLE( return new CCPRGeometryView; ) + +#endif // SK_SUPPORT_GPU diff --git a/src/gpu/GrPathRendererChain.cpp b/src/gpu/GrPathRendererChain.cpp index 076dea7b82..eda7a65319 100644 --- a/src/gpu/GrPathRendererChain.cpp +++ b/src/gpu/GrPathRendererChain.cpp @@ -12,8 +12,11 @@ #include "GrShaderCaps.h" #include "gl/GrGLCaps.h" #include "GrContext.h" +#include "GrContextPriv.h" #include "GrGpu.h" +#include "ccpr/GrCoverageCountingPathRenderer.h" + #include "ops/GrAAConvexPathRenderer.h" #include "ops/GrAAHairLinePathRenderer.h" #include "ops/GrAALinearizingConvexPathRenderer.h" @@ -56,6 +59,12 @@ GrPathRendererChain::GrPathRendererChain(GrContext* context, const Options& opti if (options.fGpuPathRenderers & GpuPathRenderers::kSmall) { fChain.push_back(sk_make_sp<GrSmallPathRenderer>()); } + if (options.fGpuPathRenderers & Options::GpuPathRenderers::kCoverageCounting) { + if (auto ccpr = GrCoverageCountingPathRenderer::CreateIfSupported(*context->caps())) { + context->contextPriv().addOnFlushCallbackObject(ccpr.get()); + fChain.push_back(std::move(ccpr)); + } + } if (options.fGpuPathRenderers & GpuPathRenderers::kTessellating) { fChain.push_back(sk_make_sp<GrTessellatingPathRenderer>()); } diff --git a/src/gpu/GrRenderTargetContext.h b/src/gpu/GrRenderTargetContext.h index 4485d6333c..21f90185cf 100644 --- a/src/gpu/GrRenderTargetContext.h +++ b/src/gpu/GrRenderTargetContext.h @@ -20,7 +20,9 @@ #include "SkSurfaceProps.h" class GrBackendSemaphore; +class GrCCPRAtlas; class GrClip; +class GrCoverageCountingPathRenderer; class GrDrawingManager; class GrDrawOp; class GrFixedClip; @@ -381,6 +383,8 @@ private: friend class GrMSAAPathRenderer; // for access to add[Mesh]DrawOp friend class GrStencilAndCoverPathRenderer; // for access to add[Mesh]DrawOp friend class GrTessellatingPathRenderer; // for access to add[Mesh]DrawOp + friend class GrCCPRAtlas; // for access to addDrawOp + friend class GrCoverageCountingPathRenderer; // for access to addDrawOp // for a unit test friend void test_draw_op(GrRenderTargetContext*, sk_sp<GrFragmentProcessor>, sk_sp<GrTextureProxy>); diff --git a/src/gpu/ccpr/GrCCPRAtlas.cpp b/src/gpu/ccpr/GrCCPRAtlas.cpp new file mode 100644 index 0000000000..8eb3086945 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRAtlas.cpp @@ -0,0 +1,125 @@ +/* + * 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 "GrCCPRAtlas.h" + +#include "GrOnFlushResourceProvider.h" +#include "GrClip.h" +#include "GrRectanizer_skyline.h" +#include "GrTextureProxy.h" +#include "GrRenderTargetContext.h" +#include "SkMakeUnique.h" +#include "SkMathPriv.h" +#include "ccpr/GrCCPRCoverageProcessor.h" +#include "ops/GrDrawOp.h" + +class GrCCPRAtlas::Node { +public: + Node(std::unique_ptr<Node> previous, int l, int t, int r, int b) + : fPrevious(std::move(previous)) + , fX(l), fY(t) + , fRectanizer(r - l, b - t) {} + + Node* previous() const { return fPrevious.get(); } + + bool addRect(int w, int h, SkIPoint16* loc) { + static constexpr int kPad = 1; + + if (!fRectanizer.addRect(w + kPad, h + kPad, loc)) { + return false; + } + loc->fX += fX; + loc->fY += fY; + return true; + } + +private: + const std::unique_ptr<Node> fPrevious; + const int fX, fY; + GrRectanizerSkyline fRectanizer; +}; + +GrCCPRAtlas::GrCCPRAtlas(const GrCaps& caps, int minWidth, int minHeight) + : fMaxAtlasSize(caps.maxRenderTargetSize()) + , fDrawBounds{0, 0} { + SkASSERT(fMaxAtlasSize <= caps.maxTextureSize()); + SkASSERT(SkTMax(minWidth, minHeight) <= fMaxAtlasSize); + int initialSize = GrNextPow2(SkTMax(minWidth, minHeight)); + initialSize = SkTMax(int(kMinSize), initialSize); + initialSize = SkTMin(initialSize, fMaxAtlasSize); + fHeight = fWidth = initialSize; + fTopNode = skstd::make_unique<Node>(nullptr, 0, 0, initialSize, initialSize); +} + +GrCCPRAtlas::~GrCCPRAtlas() { +} + +bool GrCCPRAtlas::addRect(int w, int h, SkIPoint16* loc) { + // This can't be called anymore once finalize() has been called. + SkASSERT(!fTextureProxy); + + if (!this->internalPlaceRect(w, h, loc)) { + return false; + } + + fDrawBounds.fWidth = SkTMax(fDrawBounds.width(), loc->x() + w); + fDrawBounds.fHeight = SkTMax(fDrawBounds.height(), loc->y() + h); + return true; +} + +bool GrCCPRAtlas::internalPlaceRect(int w, int h, SkIPoint16* loc) { + SkASSERT(SkTMax(w, h) < fMaxAtlasSize); + + for (Node* node = fTopNode.get(); node; node = node->previous()) { + if (node->addRect(w, h, loc)) { + return true; + } + } + + // The rect didn't fit. Grow the atlas and try again. + do { + SkASSERT(SkTMax(fWidth, fHeight) <= fMaxAtlasSize); + if (fWidth == fMaxAtlasSize && fHeight == fMaxAtlasSize) { + return false; + } + if (fHeight <= fWidth) { + int top = fHeight; + fHeight = SkTMin(fHeight * 2, fMaxAtlasSize); + fTopNode = skstd::make_unique<Node>(std::move(fTopNode), 0, top, fWidth, fHeight); + } else { + int left = fWidth; + fWidth = SkTMin(fWidth * 2, fMaxAtlasSize); + fTopNode = skstd::make_unique<Node>(std::move(fTopNode), left, 0, fWidth, fHeight); + } + } while (!fTopNode->addRect(w, h, loc)); + + return true; +} + +sk_sp<GrRenderTargetContext> GrCCPRAtlas::finalize(GrOnFlushResourceProvider* onFlushRP, + std::unique_ptr<GrDrawOp> atlasOp) { + SkASSERT(!fTextureProxy); + + GrSurfaceDesc desc; + desc.fOrigin = GrCCPRCoverageProcessor::kAtlasOrigin; + desc.fWidth = fWidth; + desc.fHeight = fHeight; + desc.fConfig = kAlpha_half_GrPixelConfig; + sk_sp<GrRenderTargetContext> rtc = onFlushRP->makeRenderTargetContext(desc, nullptr, nullptr); + if (!rtc) { + SkDebugf("WARNING: failed to allocate a %ix%i atlas. Some paths will not be drawn.\n", + fWidth, fHeight); + return nullptr; + } + + SkIRect clearRect = SkIRect::MakeSize(fDrawBounds); + rtc->clear(&clearRect, 0, true); + rtc->addDrawOp(GrNoClip(), std::move(atlasOp)); + + fTextureProxy = sk_ref_sp(rtc->asTextureProxy()); + return rtc; +} diff --git a/src/gpu/ccpr/GrCCPRAtlas.h b/src/gpu/ccpr/GrCCPRAtlas.h new file mode 100644 index 0000000000..a9ccd73c1c --- /dev/null +++ b/src/gpu/ccpr/GrCCPRAtlas.h @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPRAtlas_DEFINED +#define GrCCPRAtlas_DEFINED + +#include "SkRefCnt.h" +#include "SkSize.h" + +class GrCaps; +class GrDrawOp; +class GrOnFlushResourceProvider; +class GrRenderTargetContext; +class GrTextureProxy; +struct SkIPoint16; + +/** + * This class implements a dynamic size GrRectanizer that grows until it reaches the implementation- + * dependent max texture size. When finalized, it also creates and stores a GrTextureProxy for the + * underlying atlas. + */ +class GrCCPRAtlas { +public: + static constexpr int kMinSize = 1024; + + GrCCPRAtlas(const GrCaps&, int minWidth, int minHeight); + ~GrCCPRAtlas(); + + bool addRect(int devWidth, int devHeight, SkIPoint16* loc); + const SkISize& drawBounds() { return fDrawBounds; } + + sk_sp<GrRenderTargetContext> SK_WARN_UNUSED_RESULT finalize(GrOnFlushResourceProvider*, + std::unique_ptr<GrDrawOp> atlasOp); + + sk_sp<GrTextureProxy> textureProxy() const { return fTextureProxy; } + +private: + class Node; + + bool internalPlaceRect(int w, int h, SkIPoint16* loc); + + const int fMaxAtlasSize; + + int fWidth; + int fHeight; + SkISize fDrawBounds; + std::unique_ptr<Node> fTopNode; + + sk_sp<GrTextureProxy> fTextureProxy; +}; + +#endif diff --git a/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp b/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp new file mode 100644 index 0000000000..d14cf1e727 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.cpp @@ -0,0 +1,640 @@ +/* + * 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 "GrCCPRCoverageOpsBuilder.h" + +#include "GrBuffer.h" +#include "GrGpuCommandBuffer.h" +#include "GrOnFlushResourceProvider.h" +#include "GrOpFlushState.h" +#include "SkGeometry.h" +#include "SkMakeUnique.h" +#include "SkMathPriv.h" +#include "SkPath.h" +#include "SkPathPriv.h" +#include "SkPoint.h" +#include "SkNx.h" +#include "ops/GrDrawOp.h" +#include "../pathops/SkPathOpsCubic.h" +#include <numeric> + +class GrCCPRCoverageOpsBuilder::CoverageOp : public GrDrawOp { +public: + using PrimitiveTallies = GrCCPRCoverageOpsBuilder::PrimitiveTallies; + + DEFINE_OP_CLASS_ID + + CoverageOp(const SkISize& drawBounds, sk_sp<GrBuffer> pointsBuffer, + sk_sp<GrBuffer> trianglesBuffer, + const PrimitiveTallies baseInstances[kNumScissorModes], + const PrimitiveTallies endInstances[kNumScissorModes], SkTArray<ScissorBatch>&&); + + // GrDrawOp interface. + const char* name() const override { return "GrCCPRCoverageOpsBuilder::CoverageOp"; } + FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } + RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override { + return RequiresDstTexture::kNo; + } + bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override { return false; } + void onPrepare(GrOpFlushState*) override {} + void onExecute(GrOpFlushState*) override; + +private: + void drawMaskPrimitives(GrOpFlushState*, const GrPipeline&, const GrCCPRCoverageProcessor::Mode, + GrPrimitiveType, int vertexCount, + int PrimitiveTallies::* instanceType) const; + + const SkISize fDrawBounds; + const sk_sp<GrBuffer> fPointsBuffer; + const sk_sp<GrBuffer> fTrianglesBuffer; + const PrimitiveTallies fBaseInstances[GrCCPRCoverageOpsBuilder::kNumScissorModes]; + const PrimitiveTallies fInstanceCounts[GrCCPRCoverageOpsBuilder::kNumScissorModes]; + const SkTArray<ScissorBatch> fScissorBatches; + + mutable SkTArray<GrMesh> fMeshesScratchBuffer; + mutable SkTArray<GrPipeline::DynamicState> fDynamicStatesScratchBuffer; + + typedef GrDrawOp INHERITED; +}; + +/** + * This is a view matrix that accumulates two bounding boxes as it maps points: device-space bounds + * and "45 degree" device-space bounds (| 1 -1 | * devCoords). + * | 1 1 | + */ +class GrCCPRCoverageOpsBuilder::AccumulatingViewMatrix { +public: + AccumulatingViewMatrix(const SkMatrix& m, const SkPoint& initialPoint); + + SkPoint transform(const SkPoint& pt); + void getAccumulatedBounds(SkRect* devBounds, SkRect* devBounds45) const; + +private: + Sk4f fX; + Sk4f fY; + Sk4f fT; + + Sk4f fTopLeft; + Sk4f fBottomRight; +}; + +static int num_pts(uint8_t verb) { + switch (verb) { + case SkPath::kClose_Verb: + case SkPath::kDone_Verb: + default: + SkFAIL("Path verb does not have an endpoint."); + return 0; + case SkPath::kMove_Verb: + case SkPath::kLine_Verb: + return 1; + case SkPath::kQuad_Verb: + return 2; + case SkPath::kConic_Verb: + return 2; + case SkPath::kCubic_Verb: + return 3; + } +} + +static SkPoint to_skpoint(double x, double y) { + return {static_cast<SkScalar>(x), static_cast<SkScalar>(y)}; +} + +static SkPoint to_skpoint(const SkDPoint& dpoint) { + return to_skpoint(dpoint.fX, dpoint.fY); +} + +bool GrCCPRCoverageOpsBuilder::init(GrOnFlushResourceProvider* onFlushRP, + const MaxBufferItems& maxBufferItems) { + const int maxPoints = maxBufferItems.fMaxFanPoints + maxBufferItems.fMaxControlPoints; + fPointsBuffer = onFlushRP->makeBuffer(kTexel_GrBufferType, maxPoints * 2 * sizeof(float)); + if (!fPointsBuffer) { + return false; + } + + const MaxPrimitives* const maxPrimitives = maxBufferItems.fMaxPrimitives; + const int maxInstances = (maxPrimitives[0].sum() + maxPrimitives[1].sum()); + fInstanceBuffer = onFlushRP->makeBuffer(kVertex_GrBufferType, maxInstances * 4 * sizeof(int)); + if (!fInstanceBuffer) { + fPointsBuffer.reset(); + return false; + } + + fFanPtsIdx = 0; + fControlPtsIdx = maxBufferItems.fMaxFanPoints; + SkDEBUGCODE(fMaxFanPoints = maxBufferItems.fMaxFanPoints); + SkDEBUGCODE(fMaxControlPoints = maxBufferItems.fMaxControlPoints); + + int baseInstance = 0; + for (int i = 0; i < kNumScissorModes; ++i) { + fBaseInstances[i].fTriangles = baseInstance; + baseInstance += maxPrimitives[i].fMaxTriangles; + + fBaseInstances[i].fQuadratics = baseInstance; + baseInstance += maxPrimitives[i].fMaxQuadratics; + + fBaseInstances[i].fSerpentines = baseInstance; + baseInstance += maxPrimitives[i].fMaxCubics; + + // Loops grow backwards. + fBaseInstances[i].fLoops = baseInstance; + + fInstanceIndices[i] = fBaseInstances[i]; + } + + fPointsData = static_cast<SkPoint*>(fPointsBuffer->map()); + SkASSERT(fPointsData); + GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); + GR_STATIC_ASSERT(8 == sizeof(SkPoint)); + + fInstanceData = static_cast<PrimitiveInstance*>(fInstanceBuffer->map()); + SkASSERT(fInstanceData); + + return true; +} + +void GrCCPRCoverageOpsBuilder::parsePath(ScissorMode scissorMode, const SkMatrix& viewMatrix, + const SkPath& path, SkRect* devBounds, + SkRect* devBounds45) { + // Make sure they haven't called finalize yet (or not called init). + SkASSERT(fPointsData); + SkASSERT(fInstanceData); + + fCurrScissorMode = scissorMode; + fCurrPathIndices = fInstanceIndices[(int)fCurrScissorMode]; + fCurrContourStartIdx = fFanPtsIdx; + + const SkPoint* const pts = SkPathPriv::PointData(path); + int ptsIdx = 0; + + SkASSERT(!path.isEmpty()); + SkASSERT(path.countPoints() > 0); + AccumulatingViewMatrix m(viewMatrix, pts[0]); + + for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { + switch (verb) { + case SkPath::kMove_Verb: + this->startContour(m, pts[ptsIdx++]); + continue; + case SkPath::kClose_Verb: + this->closeContour(); + continue; + case SkPath::kLine_Verb: + this->fanTo(m, pts[ptsIdx]); + break; + case SkPath::kQuad_Verb: + SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed. + this->quadraticTo(m, &pts[ptsIdx - 1]); + break; + case SkPath::kCubic_Verb: + SkASSERT(ptsIdx >= 1); // SkPath should have inserted an implicit moveTo if needed. + this->cubicTo(m, &pts[ptsIdx - 1]); + break; + case SkPath::kConic_Verb: + SkFAIL("Conics are not supported."); + default: + SkFAIL("Unexpected path verb."); + } + + ptsIdx += num_pts(verb); + } + + this->closeContour(); + + m.getAccumulatedBounds(devBounds, devBounds45); + SkDEBUGCODE(this->validate();) +} + +void GrCCPRCoverageOpsBuilder::saveParsedPath(const SkIRect& clippedDevIBounds, + int16_t atlasOffsetX, int16_t atlasOffsetY) { + const PrimitiveTallies& baseIndices = fInstanceIndices[(int)fCurrScissorMode]; + const int32_t packedAtlasOffset = (atlasOffsetY << 16) | (atlasOffsetX & 0xffff); + for (int i = baseIndices.fTriangles; i < fCurrPathIndices.fTriangles; ++i) { + fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; + } + for (int i = baseIndices.fQuadratics; i < fCurrPathIndices.fQuadratics; ++i) { + fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; + } + for (int i = baseIndices.fSerpentines; i < fCurrPathIndices.fSerpentines; ++i) { + fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; + } + for (int i = baseIndices.fLoops - 1; i >= fCurrPathIndices.fLoops; --i) { + fInstanceData[i].fPackedAtlasOffset = packedAtlasOffset; + } + if (ScissorMode::kScissored == fCurrScissorMode) { + fScissorBatches.push_back() = { + fCurrPathIndices - fInstanceIndices[(int)fCurrScissorMode], + clippedDevIBounds.makeOffset(atlasOffsetX, atlasOffsetY) + }; + } + fInstanceIndices[(int)fCurrScissorMode] = fCurrPathIndices; +} + +void GrCCPRCoverageOpsBuilder::startContour(AccumulatingViewMatrix& m, const SkPoint& anchorPoint) { + this->closeContour(); + fCurrPathSpaceAnchorPoint = anchorPoint; + fPointsData[fFanPtsIdx++] = m.transform(anchorPoint); + SkASSERT(fCurrContourStartIdx == fFanPtsIdx - 1); +} + +void GrCCPRCoverageOpsBuilder::fanTo(AccumulatingViewMatrix& m, const SkPoint& pt) { + SkASSERT(fCurrContourStartIdx < fFanPtsIdx); + if (pt == fCurrPathSpaceAnchorPoint) { + this->startContour(m, pt); + return; + } + fPointsData[fFanPtsIdx++] = m.transform(pt); +} + +void GrCCPRCoverageOpsBuilder::quadraticTo(AccumulatingViewMatrix& m, const SkPoint P[3]) { + SkASSERT(fCurrPathIndices.fQuadratics < fBaseInstances[(int)fCurrScissorMode].fSerpentines); + + this->fanTo(m, P[2]); + fPointsData[fControlPtsIdx++] = m.transform(P[1]); + + fInstanceData[fCurrPathIndices.fQuadratics++].fQuadraticData = { + fControlPtsIdx - 1, + fFanPtsIdx - 2 + }; +} + +void GrCCPRCoverageOpsBuilder::cubicTo(AccumulatingViewMatrix& m, const SkPoint P[4]) { + double t[2], s[2]; + SkCubicType type = SkClassifyCubic(P, t, s); + + if (SkCubicType::kLineOrPoint == type) { + this->fanTo(m, P[3]); + return; + } + + if (SkCubicType::kQuadratic == type) { + SkScalar x1 = P[1].y() - P[0].y(), y1 = P[0].x() - P[1].x(), + k1 = x1 * P[0].x() + y1 * P[0].y(); + SkScalar x2 = P[2].y() - P[3].y(), y2 = P[3].x() - P[2].x(), + k2 = x2 * P[3].x() + y2 * P[3].y(); + SkScalar rdet = 1 / (x1*y2 - y1*x2); + SkPoint Q[3] = {P[0], {(y2*k1 - y1*k2) * rdet, (x1*k2 - x2*k1) * rdet}, P[3]}; + this->quadraticTo(m, Q); + return; + } + + SkDCubic C; + C.set(P); + + for (int x = 0; x <= 1; ++x) { + if (t[x] * s[x] <= 0) { // This is equivalent to tx/sx <= 0. + // This technically also gets taken if tx/sx = infinity, but the code still does + // the right thing in that edge case. + continue; // Don't increment x0. + } + if (fabs(t[x]) >= fabs(s[x])) { // tx/sx >= 1. + break; + } + + const double chopT = double(t[x]) / double(s[x]); + SkASSERT(chopT >= 0 && chopT <= 1); + if (chopT <= 0 || chopT >= 1) { // floating-point error. + continue; + } + + SkDCubicPair chopped = C.chopAt(chopT); + + // Ensure the double points are identical if this is a loop (more workarounds for FP error). + if (SkCubicType::kLoop == type && 0 == t[0]) { + chopped.pts[3] = chopped.pts[0]; + } + + // (This might put ts0/ts1 out of order, but it doesn't matter anymore at this point.) + this->emitCubicSegment(m, type, chopped.first(), + to_skpoint(t[1 - x], s[1 - x] * chopT), to_skpoint(1, 1)); + t[x] = 0; + s[x] = 1; + + const double r = s[1 - x] * chopT; + t[1 - x] -= r; + s[1 - x] -= r; + + C = chopped.second(); + } + + this->emitCubicSegment(m, type, C, to_skpoint(t[0], s[0]), to_skpoint(t[1], s[1])); +} + +void GrCCPRCoverageOpsBuilder::emitCubicSegment(AccumulatingViewMatrix& m, + SkCubicType type, const SkDCubic& C, + const SkPoint& ts0, const SkPoint& ts1) { + SkASSERT(fCurrPathIndices.fSerpentines < fCurrPathIndices.fLoops); + + fPointsData[fControlPtsIdx++] = m.transform(to_skpoint(C[1])); + fPointsData[fControlPtsIdx++] = m.transform(to_skpoint(C[2])); + this->fanTo(m, to_skpoint(C[3])); + + // Also emit the cubic's root t,s values as "control points". + fPointsData[fControlPtsIdx++] = ts0; + fPointsData[fControlPtsIdx++] = ts1; + + // Serpentines grow up from the front, and loops grow down from the back. + fInstanceData[SkCubicType::kLoop != type ? + fCurrPathIndices.fSerpentines++ : --fCurrPathIndices.fLoops].fCubicData = { + fControlPtsIdx - 4, + fFanPtsIdx - 2 + }; +} + +void GrCCPRCoverageOpsBuilder::closeContour() { + int fanSize = fFanPtsIdx - fCurrContourStartIdx; + if (fanSize >= 3) { + // Technically this can grow to fanSize + log3(fanSize), but we approximate with log2. + SkAutoSTMalloc<300, int32_t> indices(fanSize + SkNextLog2(fanSize)); + std::iota(indices.get(), indices.get() + fanSize, fCurrContourStartIdx); + this->emitHierarchicalFan(indices, fanSize); + } + + // Reset the current contour. + fCurrContourStartIdx = fFanPtsIdx; +} + +void GrCCPRCoverageOpsBuilder::emitHierarchicalFan(int32_t indices[], int count) { + if (count < 3) { + return; + } + + const int32_t oneThirdPt = count / 3; + const int32_t twoThirdsPt = (2 * count) / 3; + SkASSERT(fCurrPathIndices.fTriangles < fBaseInstances[(int)fCurrScissorMode].fQuadratics); + + fInstanceData[fCurrPathIndices.fTriangles++].fTriangleData = { + indices[0], + indices[oneThirdPt], + indices[twoThirdsPt] + }; + + this->emitHierarchicalFan(indices, oneThirdPt + 1); + this->emitHierarchicalFan(&indices[oneThirdPt], twoThirdsPt - oneThirdPt + 1); + + int32_t oldIndex = indices[count]; + indices[count] = indices[0]; + this->emitHierarchicalFan(&indices[twoThirdsPt], count - twoThirdsPt + 1); + indices[count] = oldIndex; +} + +std::unique_ptr<GrDrawOp> GrCCPRCoverageOpsBuilder::createIntermediateOp(SkISize drawBounds) { + auto op = skstd::make_unique<CoverageOp>(drawBounds, fPointsBuffer, fInstanceBuffer, + fBaseInstances, fInstanceIndices, + std::move(fScissorBatches)); + SkASSERT(fScissorBatches.empty()); + + fBaseInstances[0] = fInstanceIndices[0]; + fBaseInstances[1] = fInstanceIndices[1]; + return std::move(op); +} + +std::unique_ptr<GrDrawOp> GrCCPRCoverageOpsBuilder::finalize(SkISize drawBounds) { + fPointsBuffer->unmap(); + SkDEBUGCODE(fPointsData = nullptr); + + fInstanceBuffer->unmap(); + SkDEBUGCODE(fInstanceData = nullptr); + + return skstd::make_unique<CoverageOp>(drawBounds, std::move(fPointsBuffer), + std::move(fInstanceBuffer), fBaseInstances, + fInstanceIndices, std::move(fScissorBatches)); +} + +#ifdef SK_DEBUG + +void GrCCPRCoverageOpsBuilder::validate() { + SkASSERT(fFanPtsIdx <= fMaxFanPoints); + SkASSERT(fControlPtsIdx <= fMaxFanPoints + fMaxControlPoints); + for (int i = 0; i < kNumScissorModes; ++i) { + SkASSERT(fInstanceIndices[i].fTriangles <= fBaseInstances[i].fQuadratics); + SkASSERT(fInstanceIndices[i].fQuadratics <= fBaseInstances[i].fSerpentines); + SkASSERT(fInstanceIndices[i].fSerpentines <= fInstanceIndices[i].fLoops); + } +} + +#endif + +using MaxBufferItems = GrCCPRCoverageOpsBuilder::MaxBufferItems; + +void MaxBufferItems::countPathItems(GrCCPRCoverageOpsBuilder::ScissorMode scissorMode, + const SkPath& path) { + MaxPrimitives& maxPrimitives = fMaxPrimitives[(int)scissorMode]; + int currFanPts = 0; + + for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { + switch (verb) { + case SkPath::kMove_Verb: + case SkPath::kClose_Verb: + fMaxFanPoints += currFanPts; + maxPrimitives.fMaxTriangles += SkTMax(0, currFanPts - 2); + currFanPts = SkPath::kMove_Verb == verb ? 1 : 0; + continue; + case SkPath::kLine_Verb: + SkASSERT(currFanPts > 0); + ++currFanPts; + continue; + case SkPath::kQuad_Verb: + SkASSERT(currFanPts > 0); + ++currFanPts; + ++fMaxControlPoints; + ++maxPrimitives.fMaxQuadratics; + continue; + case SkPath::kCubic_Verb: { + SkASSERT(currFanPts > 0); + // Over-allocate for the worst case when the cubic is chopped into 3 segments. + static constexpr int kMaxSegments = 3; + currFanPts += kMaxSegments; + // Each cubic segment has two control points. + fMaxControlPoints += kMaxSegments * 2; + // Each cubic segment also emits two root t,s values as "control points". + fMaxControlPoints += kMaxSegments * 2; + maxPrimitives.fMaxCubics += kMaxSegments; + // The cubic may also turn out to be a quadratic. While we over-allocate by a fair + // amount, this is still a relatively small amount of space. + ++maxPrimitives.fMaxQuadratics; + continue; + } + case SkPath::kConic_Verb: + SkASSERT(currFanPts > 0); + SkFAIL("Conics are not supported."); + default: + SkFAIL("Unexpected path verb."); + } + } + + fMaxFanPoints += currFanPts; + maxPrimitives.fMaxTriangles += SkTMax(0, currFanPts - 2); + + ++fMaxPaths; +} + +using CoverageOp = GrCCPRCoverageOpsBuilder::CoverageOp; + +GrCCPRCoverageOpsBuilder::CoverageOp::CoverageOp(const SkISize& drawBounds, + sk_sp<GrBuffer> pointsBuffer, + sk_sp<GrBuffer> trianglesBuffer, + const PrimitiveTallies baseInstances[kNumScissorModes], + const PrimitiveTallies endInstances[kNumScissorModes], + SkTArray<ScissorBatch>&& scissorBatches) + : INHERITED(ClassID()) + , fDrawBounds(drawBounds) + , fPointsBuffer(std::move(pointsBuffer)) + , fTrianglesBuffer(std::move(trianglesBuffer)) + , fBaseInstances{baseInstances[0], baseInstances[1]} + , fInstanceCounts{endInstances[0] - baseInstances[0], endInstances[1] - baseInstances[1]} + , fScissorBatches(std::move(scissorBatches)) { + SkASSERT(fPointsBuffer); + SkASSERT(fTrianglesBuffer); + this->setBounds(SkRect::MakeIWH(fDrawBounds.width(), fDrawBounds.height()), + GrOp::HasAABloat::kNo, GrOp::IsZeroArea::kNo); +} + +void CoverageOp::onExecute(GrOpFlushState* flushState) { + using Mode = GrCCPRCoverageProcessor::Mode; + + SkDEBUGCODE(GrCCPRCoverageProcessor::Validate(flushState->drawOpArgs().fRenderTarget)); + + GrPipeline pipeline(flushState->drawOpArgs().fRenderTarget, GrPipeline::ScissorState::kEnabled, + SkBlendMode::kPlus); + + fMeshesScratchBuffer.reserve(1 + fScissorBatches.count()); + fDynamicStatesScratchBuffer.reserve(1 + fScissorBatches.count()); + + // Triangles. + auto constexpr kTrianglesGrPrimitiveType = GrCCPRCoverageProcessor::kTrianglesGrPrimitiveType; + this->drawMaskPrimitives(flushState, pipeline, Mode::kCombinedTriangleHullsAndEdges, + kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles); + this->drawMaskPrimitives(flushState, pipeline, Mode::kTriangleCorners, + kTrianglesGrPrimitiveType, 3, &PrimitiveTallies::fTriangles); + + // Quadratics. + auto constexpr kQuadraticsGrPrimitiveType = GrCCPRCoverageProcessor::kQuadraticsGrPrimitiveType; + this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticHulls, + kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics); + this->drawMaskPrimitives(flushState, pipeline, Mode::kQuadraticFlatEdges, + kQuadraticsGrPrimitiveType, 3, &PrimitiveTallies::fQuadratics); + + // Cubics. + auto constexpr kCubicsGrPrimitiveType = GrCCPRCoverageProcessor::kCubicsGrPrimitiveType; + this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineInsets, + kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines); + this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopInsets, + kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops); + this->drawMaskPrimitives(flushState, pipeline, Mode::kSerpentineBorders, + kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fSerpentines); + this->drawMaskPrimitives(flushState, pipeline, Mode::kLoopBorders, + kCubicsGrPrimitiveType, 4, &PrimitiveTallies::fLoops); +} + +void CoverageOp::drawMaskPrimitives(GrOpFlushState* flushState, const GrPipeline& pipeline, + GrCCPRCoverageProcessor::Mode mode, GrPrimitiveType primType, + int vertexCount, int PrimitiveTallies::* instanceType) const { + SkASSERT(pipeline.getScissorState().enabled()); + + fMeshesScratchBuffer.reset(); + fDynamicStatesScratchBuffer.reset(); + + if (const int instanceCount = fInstanceCounts[(int)ScissorMode::kNonScissored].*instanceType) { + const int baseInstance = fBaseInstances[(int)ScissorMode::kNonScissored].*instanceType; + // Loops grow backwards, which is indicated by a negative instance count. + GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType); + mesh.setInstanced(fTrianglesBuffer.get(), abs(instanceCount), + baseInstance + SkTMin(instanceCount, 0), vertexCount); + fDynamicStatesScratchBuffer.push_back().fScissorRect.setXYWH(0, 0, fDrawBounds.width(), + fDrawBounds.height()); + } + + if (fInstanceCounts[(int)ScissorMode::kScissored].*instanceType) { + int baseInstance = fBaseInstances[(int)ScissorMode::kScissored].*instanceType; + for (const ScissorBatch& batch : fScissorBatches) { + SkASSERT(this->bounds().contains(batch.fScissor)); + const int instanceCount = batch.fInstanceCounts.*instanceType; + if (!instanceCount) { + continue; + } + // Loops grow backwards, which is indicated by a negative instance count. + GrMesh& mesh = fMeshesScratchBuffer.emplace_back(primType); + mesh.setInstanced(fTrianglesBuffer.get(), abs(instanceCount), + baseInstance + SkTMin(instanceCount,0), vertexCount); + fDynamicStatesScratchBuffer.push_back().fScissorRect = batch.fScissor; + baseInstance += instanceCount; + } + } + + SkASSERT(fMeshesScratchBuffer.count() == fDynamicStatesScratchBuffer.count()); + + if (!fMeshesScratchBuffer.empty()) { + GrCCPRCoverageProcessor proc(mode, fPointsBuffer.get()); + flushState->commandBuffer()->draw(pipeline, proc, fMeshesScratchBuffer.begin(), + fDynamicStatesScratchBuffer.begin(), + fMeshesScratchBuffer.count(), this->bounds()); + } +} + +using PrimitiveTallies = CoverageOp::PrimitiveTallies; + +inline PrimitiveTallies PrimitiveTallies::operator+(const PrimitiveTallies& b) const { + return {fTriangles + b.fTriangles, + fQuadratics + b.fQuadratics, + fSerpentines + b.fSerpentines, + fLoops + b.fLoops}; +} + +inline PrimitiveTallies PrimitiveTallies::operator-(const PrimitiveTallies& b) const { + return {fTriangles - b.fTriangles, + fQuadratics - b.fQuadratics, + fSerpentines - b.fSerpentines, + fLoops - b.fLoops}; +} + +inline int PrimitiveTallies::sum() const { + return fTriangles + fQuadratics + fSerpentines + fLoops; +} + +using AccumulatingViewMatrix = GrCCPRCoverageOpsBuilder::AccumulatingViewMatrix; + +inline AccumulatingViewMatrix::AccumulatingViewMatrix(const SkMatrix& m, + const SkPoint& initialPoint) { + // m45 transforms into 45 degree space in order to find the octagon's diagonals. We could + // use SK_ScalarRoot2Over2 if we wanted an orthonormal transform, but this is irrelevant as + // long as the shader uses the correct inverse when coming back to device space. + SkMatrix m45; + m45.setSinCos(1, 1); + m45.preConcat(m); + + fX = Sk4f(m.getScaleX(), m.getSkewY(), m45.getScaleX(), m45.getSkewY()); + fY = Sk4f(m.getSkewX(), m.getScaleY(), m45.getSkewX(), m45.getScaleY()); + fT = Sk4f(m.getTranslateX(), m.getTranslateY(), m45.getTranslateX(), m45.getTranslateY()); + + Sk4f transformed = SkNx_fma(fY, Sk4f(initialPoint.y()), fT); + transformed = SkNx_fma(fX, Sk4f(initialPoint.x()), transformed); + fTopLeft = fBottomRight = transformed; +} + +inline SkPoint AccumulatingViewMatrix::transform(const SkPoint& pt) { + Sk4f transformed = SkNx_fma(fY, Sk4f(pt.y()), fT); + transformed = SkNx_fma(fX, Sk4f(pt.x()), transformed); + + fTopLeft = Sk4f::Min(fTopLeft, transformed); + fBottomRight = Sk4f::Max(fBottomRight, transformed); + + // TODO: vst1_lane_f32? (Sk4f::storeLane?) + float data[4]; + transformed.store(data); + return SkPoint::Make(data[0], data[1]); +} + +inline void AccumulatingViewMatrix::getAccumulatedBounds(SkRect* devBounds, + SkRect* devBounds45) const { + float topLeft[4], bottomRight[4]; + fTopLeft.store(topLeft); + fBottomRight.store(bottomRight); + devBounds->setLTRB(topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]); + devBounds45->setLTRB(topLeft[2], topLeft[3], bottomRight[2], bottomRight[3]); +} diff --git a/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.h b/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.h new file mode 100644 index 0000000000..92d0203e8f --- /dev/null +++ b/src/gpu/ccpr/GrCCPRCoverageOpsBuilder.h @@ -0,0 +1,167 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPRCoverageOpsBuilder_DEFINED +#define GrCCPRCoverageOpsBuilder_DEFINED + +#include "GrBuffer.h" +#include "SkRefCnt.h" +#include "SkRect.h" +#include "ccpr/GrCCPRCoverageProcessor.h" + +class GrCCPRCoverageOp; +class GrDrawOp; +class GrOnFlushResourceProvider; +class GrResourceProvider; +class SkMatrix; +class SkPath; +struct SkDCubic; +enum class SkCubicType; + +/** + * This class produces GrDrawOps that render coverage count masks and atlases. A path is added to + * the current op in two steps: + * + * 1) parsePath(ScissorMode, viewMatrix, path, &devBounds, &devBounds45); + * + * <client decides where to put the mask within an atlas, if wanted> + * + * 2) saveParsedPath(offsetX, offsetY, clipBounds); + * + * The client can then produce a GrDrawOp for all currently saved paths by calling either + * createIntermediateOp() or finalize(). + */ +class GrCCPRCoverageOpsBuilder { +public: + // Indicates whether a path should enforce a scissor clip when rendering its mask. (Specified + // as an int because these values get used directly as indices into arrays.) + enum class ScissorMode : int { + kNonScissored = 0, + kScissored = 1 + }; + static constexpr int kNumScissorModes = 2; + + struct MaxPrimitives { + int fMaxTriangles = 0; + int fMaxQuadratics = 0; + int fMaxCubics = 0; + + void operator+=(const MaxPrimitives&); + int sum() const; + }; + + struct MaxBufferItems { + int fMaxFanPoints = 0; + int fMaxControlPoints = 0; + MaxPrimitives fMaxPrimitives[kNumScissorModes]; + int fMaxPaths = 0; + + void operator+=(const MaxBufferItems&); + void countPathItems(ScissorMode, const SkPath&); + }; + + GrCCPRCoverageOpsBuilder() : fScissorBatches(512) { + SkDEBUGCODE(fPointsData = nullptr;) + SkDEBUGCODE(fInstanceData = nullptr;) + } + + bool init(GrOnFlushResourceProvider*, const MaxBufferItems&); + + // Parses an SkPath into a temporary staging area. The path will not yet be included in the next + // Op until there is a matching call to saveParsedPath. + // + // Returns two tight bounding boxes: device space and "45 degree" (| 1 -1 | * devCoords) space. + // | 1 1 | + void parsePath(ScissorMode, const SkMatrix&, const SkPath&, SkRect* devBounds, + SkRect* devBounds45); + + // Commits the currently-parsed path from the staging area to the GPU buffers and next Op. + // Accepts an optional post-device-space translate for placement in an atlas. + void saveParsedPath(const SkIRect& clippedDevIBounds, + int16_t atlasOffsetX, int16_t atlasOffsetY); + + // Flushes all currently-saved paths to a GrDrawOp and leaves the GPU buffers open to accept + // new paths (e.g. for when an atlas runs out of space). + // NOTE: if there is a parsed path in the staging area, it will not be included. But the client + // may still call saveParsedPath to include it in a future Op. + std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT createIntermediateOp(SkISize drawBounds); + + // Flushes the remaining saved paths to a final GrDrawOp and closes off the GPU buffers. This + // must be called before attempting to draw any Ops produced by this class. + std::unique_ptr<GrDrawOp> SK_WARN_UNUSED_RESULT finalize(SkISize drawBounds); + + class CoverageOp; + class AccumulatingViewMatrix; + +private: + using PrimitiveInstance = GrCCPRCoverageProcessor::PrimitiveInstance; + + struct PrimitiveTallies { + int fTriangles; + int fQuadratics; + int fSerpentines; + int fLoops; + + PrimitiveTallies operator+(const PrimitiveTallies&) const; + PrimitiveTallies operator-(const PrimitiveTallies&) const; + int sum() const; + }; + + struct ScissorBatch { + PrimitiveTallies fInstanceCounts; + SkIRect fScissor; + }; + + void startContour(AccumulatingViewMatrix&, const SkPoint& anchorPoint); + void fanTo(AccumulatingViewMatrix&, const SkPoint& pt); + void quadraticTo(AccumulatingViewMatrix&, const SkPoint P[3]); + void cubicTo(AccumulatingViewMatrix&, const SkPoint P[4]); + void emitCubicSegment(AccumulatingViewMatrix&, SkCubicType, const SkDCubic&, + const SkPoint& ts0, const SkPoint& ts1); + void closeContour(); + void emitHierarchicalFan(int32_t indices[], int count); + SkDEBUGCODE(void validate();) + + ScissorMode fCurrScissorMode; + PrimitiveTallies fCurrPathIndices; + int32_t fCurrContourStartIdx; + SkPoint fCurrPathSpaceAnchorPoint; + + sk_sp<GrBuffer> fPointsBuffer; + SkPoint* fPointsData; + int32_t fFanPtsIdx; + int32_t fControlPtsIdx; + SkDEBUGCODE(int fMaxFanPoints;) + SkDEBUGCODE(int fMaxControlPoints;) + + sk_sp<GrBuffer> fInstanceBuffer; + PrimitiveInstance* fInstanceData; + PrimitiveTallies fBaseInstances[kNumScissorModes]; + PrimitiveTallies fInstanceIndices[kNumScissorModes]; + + SkTArray<ScissorBatch> fScissorBatches; +}; + +inline void GrCCPRCoverageOpsBuilder::MaxBufferItems::operator+=(const MaxBufferItems& b) { + fMaxFanPoints += b.fMaxFanPoints; + fMaxControlPoints += b.fMaxControlPoints; + fMaxPrimitives[0] += b.fMaxPrimitives[0]; + fMaxPrimitives[1] += b.fMaxPrimitives[1]; + fMaxPaths += b.fMaxPaths; +} + +inline void GrCCPRCoverageOpsBuilder::MaxPrimitives::operator+=(const MaxPrimitives& b) { + fMaxTriangles += b.fMaxTriangles; + fMaxQuadratics += b.fMaxQuadratics; + fMaxCubics += b.fMaxCubics; +} + +inline int GrCCPRCoverageOpsBuilder::MaxPrimitives::sum() const { + return fMaxTriangles + fMaxQuadratics + fMaxCubics; +} + +#endif diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp b/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp new file mode 100644 index 0000000000..5f1833a678 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRCoverageProcessor.cpp @@ -0,0 +1,355 @@ +/* + * 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 "GrCCPRCoverageProcessor.h" + +#include "ccpr/GrCCPRTriangleProcessor.h" +#include "ccpr/GrCCPRQuadraticProcessor.h" +#include "ccpr/GrCCPRCubicProcessor.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLGeometryShaderBuilder.h" +#include "glsl/GrGLSLProgramBuilder.h" +#include "glsl/GrGLSLVertexShaderBuilder.h" + +const char* GrCCPRCoverageProcessor::GetProcessorName(Mode mode) { + switch (mode) { + case Mode::kTriangleHulls: + return "GrCCPRTriangleHullAndEdgeProcessor (hulls)"; + case Mode::kTriangleEdges: + return "GrCCPRTriangleHullAndEdgeProcessor (edges)"; + case Mode::kCombinedTriangleHullsAndEdges: + return "GrCCPRTriangleHullAndEdgeProcessor (combined hulls & edges)"; + case Mode::kTriangleCorners: + return "GrCCPRTriangleCornerProcessor"; + case Mode::kQuadraticHulls: + return "GrCCPRQuadraticHullProcessor"; + case Mode::kQuadraticFlatEdges: + return "GrCCPRQuadraticSharedEdgeProcessor"; + case Mode::kSerpentineInsets: + return "GrCCPRCubicInsetProcessor (serpentine)"; + case Mode::kSerpentineBorders: + return "GrCCPRCubicBorderProcessor (serpentine)"; + case Mode::kLoopInsets: + return "GrCCPRCubicInsetProcessor (loop)"; + case Mode::kLoopBorders: + return "GrCCPRCubicBorderProcessor (loop)"; + } + SkFAIL("Unexpected ccpr coverage processor mode."); + return nullptr; +} + +GrCCPRCoverageProcessor::GrCCPRCoverageProcessor(Mode mode, GrBuffer* pointsBuffer) + : fMode(mode) + , fInstanceAttrib(this->addInstanceAttrib("instance", kVec4i_GrVertexAttribType, + kHigh_GrSLPrecision)) { + fPointsBufferAccess.reset(kRG_float_GrPixelConfig, pointsBuffer, kVertex_GrShaderFlag); + this->addBufferAccess(&fPointsBufferAccess); + + this->setWillUseGeoShader(); + + this->initClassID<GrCCPRCoverageProcessor>(); +} + +void GrCCPRCoverageProcessor::getGLSLProcessorKey(const GrShaderCaps&, + GrProcessorKeyBuilder* b) const { + b->add32(int(fMode)); +} + +GrGLSLPrimitiveProcessor* GrCCPRCoverageProcessor::createGLSLInstance(const GrShaderCaps&) const { + switch (fMode) { + using GeometryType = GrCCPRTriangleHullAndEdgeProcessor::GeometryType; + + case Mode::kTriangleHulls: + return new GrCCPRTriangleHullAndEdgeProcessor(GeometryType::kHulls); + case Mode::kTriangleEdges: + return new GrCCPRTriangleHullAndEdgeProcessor(GeometryType::kEdges); + case Mode::kCombinedTriangleHullsAndEdges: + return new GrCCPRTriangleHullAndEdgeProcessor(GeometryType::kHullsAndEdges); + case Mode::kTriangleCorners: + return new GrCCPRTriangleCornerProcessor(); + case Mode::kQuadraticHulls: + return new GrCCPRQuadraticHullProcessor(); + case Mode::kQuadraticFlatEdges: + return new GrCCPRQuadraticSharedEdgeProcessor(); + case Mode::kSerpentineInsets: + return new GrCCPRCubicInsetProcessor(GrCCPRCubicProcessor::Type::kSerpentine); + case Mode::kSerpentineBorders: + return new GrCCPRCubicBorderProcessor(GrCCPRCubicProcessor::Type::kSerpentine); + case Mode::kLoopInsets: + return new GrCCPRCubicInsetProcessor(GrCCPRCubicProcessor::Type::kLoop); + case Mode::kLoopBorders: + return new GrCCPRCubicBorderProcessor(GrCCPRCubicProcessor::Type::kLoop); + } + SkFAIL("Unexpected ccpr coverage processor mode."); + return nullptr; +} + +using PrimitiveProcessor = GrCCPRCoverageProcessor::PrimitiveProcessor; + +void PrimitiveProcessor::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + const GrCCPRCoverageProcessor& proc = args.fGP.cast<GrCCPRCoverageProcessor>(); + + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + switch (fCoverageType) { + case CoverageType::kOne: + case CoverageType::kShader: + varyingHandler->addFlatVarying("wind", &fFragWind, kLow_GrSLPrecision); + break; + case CoverageType::kInterpolated: + varyingHandler->addVarying("coverage_times_wind", &fFragCoverageTimesWind, + kMedium_GrSLPrecision); + break; + } + this->resetVaryings(varyingHandler); + + varyingHandler->emitAttributes(proc); + + this->emitVertexShader(proc, args.fVertBuilder, args.fTexelBuffers[0], args.fRTAdjustName, + gpArgs); + this->emitGeometryShader(proc, args.fGeomBuilder, args.fRTAdjustName); + this->emitCoverage(proc, args.fFragBuilder, args.fOutputColor, args.fOutputCoverage); + + SkASSERT(!args.fFPCoordTransformHandler->nextCoordTransform()); +} + +void PrimitiveProcessor::emitVertexShader(const GrCCPRCoverageProcessor& proc, + GrGLSLVertexBuilder* v, + const TexelBufferHandle& pointsBuffer, + const char* rtAdjust, GrGPArgs* gpArgs) const { + v->codeAppendf("int packedoffset = %s.w;", proc.instanceAttrib()); + v->codeAppend ("highp vec2 atlasoffset = vec2((packedoffset<<16) >> 16, packedoffset >> 16);"); + + this->onEmitVertexShader(proc, v, pointsBuffer, "atlasoffset", rtAdjust, gpArgs); +} + +void PrimitiveProcessor::emitGeometryShader(const GrCCPRCoverageProcessor& proc, + GrGLSLGeometryBuilder* g, const char* rtAdjust) const { + g->declareGlobal(fGeomWind); + this->emitWind(g, rtAdjust, fGeomWind.c_str()); + + SkString emitVertexFn; + SkSTArray<2, GrShaderVar> emitArgs; + const char* position = emitArgs.emplace_back("position", kVec2f_GrSLType, + GrShaderVar::kNonArray, + kHigh_GrSLPrecision).c_str(); + const char* coverage = emitArgs.emplace_back("coverage", kFloat_GrSLType, + GrShaderVar::kNonArray, + kHigh_GrSLPrecision).c_str(); + g->emitFunction(kVoid_GrSLType, "emitVertex", emitArgs.count(), emitArgs.begin(), [&]() { + SkString fnBody; + this->emitPerVertexGeometryCode(&fnBody, position, coverage, fGeomWind.c_str()); + if (fFragWind.gsOut()) { + fnBody.appendf("%s = %s;", fFragWind.gsOut(), fGeomWind.c_str()); + } + if (fFragCoverageTimesWind.gsOut()) { + fnBody.appendf("%s = %s * %s;", + fFragCoverageTimesWind.gsOut(), coverage, fGeomWind.c_str()); + } + fnBody.append ("gl_Position = vec4(position, 0, 1);"); + fnBody.append ("EmitVertex();"); + return fnBody; + }().c_str(), &emitVertexFn); + + g->codeAppendf("highp vec2 bloat = %f * abs(%s.xz);", kAABloatRadius, rtAdjust); + +#ifdef SK_DEBUG + if (proc.debugVisualizations()) { + g->codeAppendf("bloat *= %f;", GrCCPRCoverageProcessor::kDebugBloat); + } +#endif + + return this->onEmitGeometryShader(g, emitVertexFn.c_str(), fGeomWind.c_str(), rtAdjust); +} + +int PrimitiveProcessor::emitHullGeometry(GrGLSLGeometryBuilder* g, const char* emitVertexFn, + const char* polygonPts, int numSides, + const char* wedgeIdx, const char* insetPts) const { + SkASSERT(numSides >= 3); + + if (!insetPts) { + g->codeAppendf("highp vec2 centroidpt = %s * vec%i(%f);", + polygonPts, numSides, 1.0 / numSides); + } + + g->codeAppendf("int previdx = (%s + %i) %% %i, " + "nextidx = (%s + 1) %% %i;", + wedgeIdx, numSides - 1, numSides, wedgeIdx, numSides); + + g->codeAppendf("highp vec2 self = %s[%s];" + "highp int leftidx = %s > 0 ? previdx : nextidx;" + "highp int rightidx = %s > 0 ? nextidx : previdx;", + polygonPts, wedgeIdx, fGeomWind.c_str(), fGeomWind.c_str()); + + // Which quadrant does the vector from self -> right fall into? + g->codeAppendf("highp vec2 right = %s[rightidx];", polygonPts); + if (3 == numSides) { + // TODO: evaluate perf gains. + g->codeAppend ("highp vec2 qsr = sign(right - self);"); + } else { + SkASSERT(4 == numSides); + g->codeAppendf("highp vec2 diag = %s[(%s + 2) %% 4];", polygonPts, wedgeIdx); + g->codeAppend ("highp vec2 qsr = sign((right != self ? right : diag) - self);"); + } + + // Which quadrant does the vector from left -> self fall into? + g->codeAppendf("highp vec2 qls = sign(self - %s[leftidx]);", polygonPts); + + // d2 just helps us reduce triangle counts with orthogonal, axis-aligned lines. + // TODO: evaluate perf gains. + const char* dr2 = "dr"; + if (3 == numSides) { + // TODO: evaluate perf gains. + g->codeAppend ("highp vec2 dr = vec2(qsr.y != 0 ? +qsr.y : +qsr.x, " + "qsr.x != 0 ? -qsr.x : +qsr.y);"); + g->codeAppend ("highp vec2 dr2 = vec2(qsr.y != 0 ? +qsr.y : -qsr.x, " + "qsr.x != 0 ? -qsr.x : -qsr.y);"); + g->codeAppend ("highp vec2 dl = vec2(qls.y != 0 ? +qls.y : +qls.x, " + "qls.x != 0 ? -qls.x : +qls.y);"); + dr2 = "dr2"; + } else { + g->codeAppend ("highp vec2 dr = vec2(qsr.y != 0 ? +qsr.y : 1, " + "qsr.x != 0 ? -qsr.x : 1);"); + g->codeAppend ("highp vec2 dl = (qls == vec2(0)) ? dr : vec2(qls.y != 0 ? +qls.y : 1, " + "qls.x != 0 ? -qls.x : 1);"); + } + g->codeAppendf("bvec2 dnotequal = notEqual(%s, dl);", dr2); + + // Emit one third of what is the convex hull of pixel-size boxes centered on the vertices. + // Each invocation emits a different third. + if (insetPts) { + g->codeAppendf("%s(%s[rightidx], 1);", emitVertexFn, insetPts); + } + g->codeAppendf("%s(right + bloat * dr, 1);", emitVertexFn); + if (insetPts) { + g->codeAppendf("%s(%s[%s], 1);", emitVertexFn, insetPts, wedgeIdx); + } else { + g->codeAppendf("%s(centroidpt, 1);", emitVertexFn); + } + g->codeAppendf("%s(self + bloat * %s, 1);", emitVertexFn, dr2); + g->codeAppend ("if (any(dnotequal)) {"); + g->codeAppendf( "%s(self + bloat * dl, 1);", emitVertexFn); + g->codeAppend ("}"); + g->codeAppend ("if (all(dnotequal)) {"); + g->codeAppendf( "%s(self + bloat * vec2(-dl.y, dl.x), 1);", emitVertexFn); + g->codeAppend ("}"); + g->codeAppend ("EndPrimitive();"); + + return insetPts ? 6 : 5; +} + +int PrimitiveProcessor::emitEdgeGeometry(GrGLSLGeometryBuilder* g, const char* emitVertexFn, + const char* leftPt, const char* rightPt, + const char* distanceEquation) const { + if (!distanceEquation) { + this->emitEdgeDistanceEquation(g, leftPt, rightPt, "highp vec3 edge_distance_equation"); + distanceEquation = "edge_distance_equation"; + } + + // qlr is defined in emitEdgeDistanceEquation. + g->codeAppendf("highp mat2 endpts = mat2(%s - bloat * qlr, %s + bloat * qlr);", + leftPt, rightPt); + g->codeAppendf("mediump vec2 endpts_coverage = %s.xy * endpts + %s.z;", + distanceEquation, distanceEquation); + + // d1 is defined in emitEdgeDistanceEquation. + g->codeAppend ("highp vec2 d2 = d1;"); + g->codeAppend ("bool aligned = qlr.x == 0 || qlr.y == 0;"); + g->codeAppend ("if (aligned) {"); + g->codeAppend ( "d1 -= qlr;"); + g->codeAppend ( "d2 += qlr;"); + g->codeAppend ("}"); + + // Emit the convex hull of 2 pixel-size boxes centered on the endpoints of the edge. Each + // invocation emits a different edge. Emit negative coverage that subtracts the appropiate + // amount back out from the hull we drew above. + g->codeAppend ("if (!aligned) {"); + g->codeAppendf( "%s(endpts[0], endpts_coverage[0]);", emitVertexFn); + g->codeAppend ("}"); + g->codeAppendf("%s(%s + bloat * d1, -1);", emitVertexFn, leftPt); + g->codeAppendf("%s(%s - bloat * d2, 0);", emitVertexFn, leftPt); + g->codeAppendf("%s(%s + bloat * d2, -1);", emitVertexFn, rightPt); + g->codeAppendf("%s(%s - bloat * d1, 0);", emitVertexFn, rightPt); + g->codeAppend ("if (!aligned) {"); + g->codeAppendf( "%s(endpts[1], endpts_coverage[1]);", emitVertexFn); + g->codeAppend ("}"); + g->codeAppend ("EndPrimitive();"); + + return 6; +} + +void PrimitiveProcessor::emitEdgeDistanceEquation(GrGLSLGeometryBuilder* g, + const char* leftPt, const char* rightPt, + const char* outputDistanceEquation) const { + // Which quadrant does the vector from left -> right fall into? + g->codeAppendf("highp vec2 qlr = sign(%s - %s);", rightPt, leftPt); + g->codeAppend ("highp vec2 d1 = vec2(qlr.y, -qlr.x);"); + + g->codeAppendf("highp vec2 n = vec2(%s.y - %s.y, %s.x - %s.x);", + rightPt, leftPt, leftPt, rightPt); + g->codeAppendf("highp vec2 kk = n * mat2(%s + bloat * d1, %s - bloat * d1);", leftPt, leftPt); + // Clamp for when n=0. wind=0 when n=0 so as long as we don't get Inf or NaN we are fine. + g->codeAppendf("highp float scale = 1 / max(kk[0] - kk[1], 1e-30);"); + + g->codeAppendf("%s = vec3(-n, kk[1]) * scale;", outputDistanceEquation); +} + +void PrimitiveProcessor::emitCoverage(const GrCCPRCoverageProcessor& proc, GrGLSLFragmentBuilder* f, + const char* outputColor, const char* outputCoverage) const { + switch (fCoverageType) { + case CoverageType::kOne: + f->codeAppendf("%s.a = %s;", outputColor, fFragWind.fsIn()); + break; + case CoverageType::kInterpolated: + f->codeAppendf("%s.a = %s;", outputColor, fFragCoverageTimesWind.fsIn()); + break; + case CoverageType::kShader: + f->codeAppendf("mediump float coverage = 0;"); + this->emitShaderCoverage(f, "coverage"); + f->codeAppendf("%s.a = coverage * %s;", outputColor, fFragWind.fsIn()); + break; + } + + f->codeAppendf("%s = vec4(1);", outputCoverage); + +#ifdef SK_DEBUG + if (proc.debugVisualizations()) { + f->codeAppendf("%s = vec4(-%s.a, %s.a, 0, 1);", outputColor, outputColor, outputColor); + } +#endif +} + +int PrimitiveProcessor::defineSoftSampleLocations(GrGLSLFragmentBuilder* f, + const char* samplesName) const { + // Standard DX11 sample locations. +#if defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_IOS) + f->defineConstant("highp vec2[8]", samplesName, "vec2[8](" + "vec2(+1, -3)/16, vec2(-1, +3)/16, vec2(+5, +1)/16, vec2(-3, -5)/16, " + "vec2(-5, +5)/16, vec2(-7, -1)/16, vec2(+3, +7)/16, vec2(+7, -7)/16." + ")"); + return 8; +#else + f->defineConstant("highp vec2[16]", samplesName, "vec2[16](" + "vec2(+1, +1)/16, vec2(-1, -3)/16, vec2(-3, +2)/16, vec2(+4, -1)/16, " + "vec2(-5, -2)/16, vec2(+2, +5)/16, vec2(+5, +3)/16, vec2(+3, -5)/16, " + "vec2(-2, +6)/16, vec2( 0, -7)/16, vec2(-4, -6)/16, vec2(-6, +4)/16, " + "vec2(-8, 0)/16, vec2(+7, -4)/16, vec2(+6, +7)/16, vec2(-7, -8)/16." + ")"); + return 16; +#endif +} + +#ifdef SK_DEBUG + +#include "GrRenderTarget.h" + +void GrCCPRCoverageProcessor::Validate(GrRenderTarget* atlasTexture) { + SkASSERT(kAtlasOrigin == atlasTexture->origin()); + SkASSERT(GrPixelConfigIsAlphaOnly(atlasTexture->config())); + SkASSERT(GrPixelConfigIsFloatingPoint(atlasTexture->config())); +} + +#endif diff --git a/src/gpu/ccpr/GrCCPRCoverageProcessor.h b/src/gpu/ccpr/GrCCPRCoverageProcessor.h new file mode 100644 index 0000000000..86f7d46c0e --- /dev/null +++ b/src/gpu/ccpr/GrCCPRCoverageProcessor.h @@ -0,0 +1,253 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPRCoverageProcessor_DEFINED +#define GrCCPRCoverageProcessor_DEFINED + +#include "GrGeometryProcessor.h" +#include "glsl/GrGLSLGeometryProcessor.h" +#include "glsl/GrGLSLVarying.h" + +class GrGLSLFragmentBuilder; + +/** + * This is the geometry processor for the simple convex primitive shapes (triangles and closed curve + * segments) from which ccpr paths are composed. The output is a single-channel alpha value, + * positive for clockwise primitives and negative for counter-clockwise, that indicates coverage. + * + * The caller is responsible to render all modes for all applicable primitives into a cleared, + * floating point, alpha-only render target using SkBlendMode::kPlus. Once all of a path's + * primitives have been drawn, the render target contains a composite coverage count that can then + * be used to draw the path (see GrCCPRPathProcessor). + * + * Caller provides the primitives' (x,y) points in an fp32x2 (RG) texel buffer, and an instance + * buffer with a single int32x4 attrib for each primitive (defined below). There are no vertex + * attribs. + * + * Draw calls are instanced, with one vertex per bezier point (3 for triangles). They use the + * corresponding GrPrimitiveType as defined below. + */ +class GrCCPRCoverageProcessor : public GrGeometryProcessor { +public: + // Use top-left to avoid a uniform access in the fragment shader. + static constexpr GrSurfaceOrigin kAtlasOrigin = kTopLeft_GrSurfaceOrigin; + + static constexpr GrPrimitiveType kTrianglesGrPrimitiveType = GrPrimitiveType::kTriangles; + static constexpr GrPrimitiveType kQuadraticsGrPrimitiveType = GrPrimitiveType::kTriangles; + static constexpr GrPrimitiveType kCubicsGrPrimitiveType = GrPrimitiveType::kLinesAdjacency; + + struct PrimitiveInstance { + union { + struct { + int32_t fPt0Idx; + int32_t fPt1Idx; + int32_t fPt2Idx; + } fTriangleData; + + struct { + int32_t fControlPtIdx; + int32_t fEndPtsIdx; // The endpoints (P0 and P2) are adjacent in the texel buffer. + } fQuadraticData; + + struct { + int32_t fControlPtsKLMRootsIdx; // The control points (P1 and P2) are adjacent in + // the texel buffer, followed immediately by the + // homogenous KLM roots ({tl,sl}, {tm,sm}). + int32_t fEndPtsIdx; // The endpoints (P0 and P3) are adjacent in the texel buffer. + } fCubicData; + }; + + int32_t fPackedAtlasOffset; // (offsetY << 16) | (offsetX & 0xffff) + }; + + GR_STATIC_ASSERT(4 * 4 == sizeof(PrimitiveInstance)); + + enum class Mode { + // Triangles. + kTriangleHulls, + kTriangleEdges, + kCombinedTriangleHullsAndEdges, + kTriangleCorners, + + // Quadratics. + kQuadraticHulls, + kQuadraticFlatEdges, + + // Cubics. + kSerpentineInsets, + kSerpentineBorders, + kLoopInsets, + kLoopBorders + }; + static const char* GetProcessorName(Mode); + + GrCCPRCoverageProcessor(Mode, GrBuffer* pointsBuffer); + + const char* instanceAttrib() const { return fInstanceAttrib.fName; } + const char* name() const override { return GetProcessorName(fMode); } + SkString dumpInfo() const override { + return SkStringPrintf("%s\n%s", this->name(), this->INHERITED::dumpInfo().c_str()); + } + + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +#ifdef SK_DEBUG + static constexpr float kDebugBloat = 50; + + // Increases the 1/2 pixel AA bloat by a factor of kDebugBloat and outputs color instead of + // coverage (coverage=+1 -> green, coverage=0 -> black, coverage=-1 -> red). + void enableDebugVisualizations() { fDebugVisualizations = true; } + bool debugVisualizations() const { return fDebugVisualizations; } + + static void Validate(GrRenderTarget* atlasTexture); +#endif + + class PrimitiveProcessor; + +private: + const Mode fMode; + const Attribute& fInstanceAttrib; + BufferAccess fPointsBufferAccess; + SkDEBUGCODE(bool fDebugVisualizations = false;) + + typedef GrGeometryProcessor INHERITED; +}; + +/** + * This class represents the actual SKSL implementation for the various primitives and modes of + * GrCCPRCoverageProcessor. + */ +class GrCCPRCoverageProcessor::PrimitiveProcessor : public GrGLSLGeometryProcessor { +protected: + // Slightly undershoot a bloat radius of 0.5 so vertices that fall on integer boundaries don't + // accidentally bleed into neighbor pixels. + static constexpr float kAABloatRadius = 0.491111f; + + // Specifies how the fragment shader should calculate sk_FragColor.a. + enum class CoverageType { + kOne, // Output +1 all around, modulated by wind. + kInterpolated, // Interpolate the coverage values that the geometry shader associates with + // each point, modulated by wind. + kShader // Call emitShaderCoverage and let the subclass decide, then a modulate by wind. + }; + + PrimitiveProcessor(CoverageType coverageType) + : fCoverageType(coverageType) + , fGeomWind("wind", kFloat_GrSLType, GrShaderVar::kNonArray, kLow_GrSLPrecision) + , fFragWind(kFloat_GrSLType) + , fFragCoverageTimesWind(kFloat_GrSLType) {} + + // Called before generating shader code. Subclass should add its custom varyings to the handler + // and update its corresponding internal member variables. + virtual void resetVaryings(GrGLSLVaryingHandler*) {} + + // Here the subclass fetches its vertex from the texel buffer, translates by atlasOffset, and + // sets "fPositionVar" in the GrGPArgs. + virtual void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*, + const TexelBufferHandle& pointsBuffer, const char* atlasOffset, + const char* rtAdjust, GrGPArgs*) const = 0; + + // Here the subclass determines the winding direction of its primitive. It must write a value of + // either -1, 0, or +1 to "outputWind" (e.g. "sign(area)"). Fractional values are not valid. + virtual void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust, + const char* outputWind) const = 0; + + // This is where the subclass generates the actual geometry to be rasterized by hardware: + // + // emitVertexFn(point1, coverage); + // emitVertexFn(point2, coverage); + // ... + // EndPrimitive(); + // + // Generally a subclass will want to use emitHullGeometry and/or emitEdgeGeometry rather than + // calling emitVertexFn directly. + // + // Subclass must also call GrGLSLGeometryBuilder::configure. + virtual void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const = 0; + + // This is a hook to inject code in the geometry shader's "emitVertex" function. Subclass + // should use this to write values to its custom varyings. + // NOTE: even flat varyings should be rewritten at each vertex. + virtual void emitPerVertexGeometryCode(SkString* fnBody, const char* position, + const char* coverage, const char* wind) const {} + + // Called when the subclass has selected CoverageType::kShader. Primitives should produce + // coverage values between +0..1. Base class modulates the sign for wind. + // TODO: subclasses might have good spots to stuff the winding information without burning a + // whole new varying slot. Consider requiring them to generate the correct coverage sign. + virtual void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const { + SkFAIL("Shader coverage not implemented when using CoverageType::kShader."); + } + + // Emits one wedge of the conservative raster hull of a convex polygon. The complete hull has + // one wedge for each side of the polygon (i.e. call this N times, generally from different + // geometry shader invocations). Coverage is +1 all around. + // + // Logically, the conservative raster hull is equivalent to the convex hull of pixel-size boxes + // centered on the vertices. + // + // If an optional inset polygon is provided, then this emits a border from the inset to the + // hull, rather than the entire hull. + // + // Geometry shader must be configured to output triangle strips. + // + // Returns the maximum number of vertices that will be emitted. + int emitHullGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* polygonPts, + int numSides, const char* wedgeIdx, const char* insetPts = nullptr) const; + + // Emits the conservative raster of an edge (i.e. convex hull of two pixel-size boxes centered + // on the endpoints). Coverage is -1 on the outside border of the edge geometry and 0 on the + // inside. This effectively converts a jagged conservative raster edge into a smooth antialiased + // edge when using CoverageType::kInterpolated. + // + // If the subclass has already called emitEdgeDistanceEquation, then provide the distance + // equation. Otherwise this function will call emitEdgeDistanceEquation implicitly. + // + // Geometry shader must be configured to output triangle strips. + // + // Returns the maximum number of vertices that will be emitted. + int emitEdgeGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* leftPt, + const char* rightPt, const char* distanceEquation = nullptr) const; + + // Defines an equation ("dot(vec3(pt, 1), distance_equation)") that is -1 on the outside border + // of a conservative raster edge and 0 on the inside (see emitEdgeGeometry). + void emitEdgeDistanceEquation(GrGLSLGeometryBuilder*, const char* leftPt, const char* rightPt, + const char* outputDistanceEquation) const; + + // Defines a global vec2 array that contains MSAA sample locations as offsets from pixel center. + // Subclasses can use this for software multisampling. + // + // Returns the number of samples. + int defineSoftSampleLocations(GrGLSLFragmentBuilder*, const char* samplesName) const; + +private: + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor&, + FPCoordTransformIter&& transformIter) final { + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } + + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) final; + + void emitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*, + const TexelBufferHandle& pointsBuffer, const char* rtAdjust, + GrGPArgs* gpArgs) const; + void emitGeometryShader(const GrCCPRCoverageProcessor&, GrGLSLGeometryBuilder*, + const char* rtAdjust) const; + void emitCoverage(const GrCCPRCoverageProcessor&, GrGLSLFragmentBuilder*, + const char* outputColor, const char* outputCoverage) const; + + const CoverageType fCoverageType; + GrShaderVar fGeomWind; + GrGLSLGeoToFrag fFragWind; + GrGLSLGeoToFrag fFragCoverageTimesWind; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +#endif diff --git a/src/gpu/ccpr/GrCCPRCubicProcessor.cpp b/src/gpu/ccpr/GrCCPRCubicProcessor.cpp new file mode 100644 index 0000000000..9dfa8e158f --- /dev/null +++ b/src/gpu/ccpr/GrCCPRCubicProcessor.cpp @@ -0,0 +1,323 @@ +/* + * 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 "GrCCPRCubicProcessor.h" + +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLGeometryShaderBuilder.h" +#include "glsl/GrGLSLVertexShaderBuilder.h" + +void GrCCPRCubicProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& proc, + GrGLSLVertexBuilder* v, + const TexelBufferHandle& pointsBuffer, + const char* atlasOffset, const char* rtAdjust, + GrGPArgs* gpArgs) const { + float inset = 1 - kAABloatRadius; +#ifdef SK_DEBUG + if (proc.debugVisualizations()) { + inset *= GrCCPRCoverageProcessor::kDebugBloat; + } +#endif + + // Fetch all 4 cubic bezier points. + v->codeAppendf("ivec4 indices = ivec4(%s.y, %s.x, %s.x + 1, %s.y + 1);", + proc.instanceAttrib(), proc.instanceAttrib(), proc.instanceAttrib(), + proc.instanceAttrib()); + v->codeAppend ("highp mat4x2 bezierpts = mat4x2("); + v->appendTexelFetch(pointsBuffer, "indices[sk_VertexID]"); + v->codeAppend (".xy, "); + v->appendTexelFetch(pointsBuffer, "indices[(sk_VertexID + 1) % 4]"); + v->codeAppend (".xy, "); + v->appendTexelFetch(pointsBuffer, "indices[(sk_VertexID + 2) % 4]"); + v->codeAppend (".xy, "); + v->appendTexelFetch(pointsBuffer, "indices[(sk_VertexID + 3) % 4]"); + v->codeAppend (".xy);"); + + // Find the corner of the inset geometry that corresponds to this bezier vertex (bezierpts[0]). + v->codeAppend ("highp mat2 N = mat2(bezierpts[3].y - bezierpts[0].y, " + "bezierpts[0].x - bezierpts[3].x, " + "bezierpts[1].y - bezierpts[0].y, " + "bezierpts[0].x - bezierpts[1].x);"); + v->codeAppend ("highp mat2 P = mat2(bezierpts[3], bezierpts[1]);"); + v->codeAppend ("if (abs(determinant(N)) < 2) {"); // Area of [pts[3], pts[0], pts[1]] < 1px. + // The inset corner doesn't exist because we are effectively colinear with + // both neighbor vertices. Just duplicate a neighbor's inset corner. + v->codeAppend ( "int smallidx = (dot(N[0], N[0]) > dot(N[1], N[1])) ? 1 : 0;"); + v->codeAppend ( "N[smallidx] = vec2(bezierpts[2].y - bezierpts[3 - smallidx * 2].y, " + "bezierpts[3 - smallidx * 2].x - bezierpts[2].x);"); + v->codeAppend ( "P[smallidx] = bezierpts[2];"); + v->codeAppend ("}"); + v->codeAppend ("N[0] *= sign(dot(N[0], P[1] - P[0]));"); + v->codeAppend ("N[1] *= sign(dot(N[1], P[0] - P[1]));"); + + v->codeAppendf("highp vec2 K = vec2(dot(N[0], P[0] + %f * sign(N[0])), " + "dot(N[1], P[1] + %f * sign(N[1])));", inset, inset); + v->codeAppendf("%s.xy = K * inverse(N) + %s;", fInset.vsOut(), atlasOffset); + v->codeAppendf("%s.xy = %s.xy * %s.xz + %s.yw;", + fInset.vsOut(), fInset.vsOut(), rtAdjust, rtAdjust); + + // The z component tells the gemetry shader how "sharp" this corner is. + v->codeAppendf("%s.z = determinant(N) * sign(%s.x) * sign(%s.z);", + fInset.vsOut(), rtAdjust, rtAdjust); + + // Fetch one of the t,s klm root values for the geometry shader. + v->codeAppendf("%s = ", fTS.vsOut()); + v->appendTexelFetch(pointsBuffer, + SkStringPrintf("%s.x + 2 + sk_VertexID/2", proc.instanceAttrib()).c_str()); + v->codeAppend ("[sk_VertexID % 2];"); + + // Emit the vertex position. + v->codeAppendf("highp vec2 self = bezierpts[0] + %s;", atlasOffset); + gpArgs->fPositionVar.set(kVec2f_GrSLType, "self"); +} + +void GrCCPRCubicProcessor::emitWind(GrGLSLGeometryBuilder* g, const char* rtAdjust, + const char* outputWind) const { + // We will define bezierpts in onEmitGeometryShader. + g->codeAppend ("highp float area_times_2 = determinant(mat3(1, bezierpts[0], " + "1, bezierpts[2], " + "0, bezierpts[3] - bezierpts[1]));"); + // Drop curves that are nearly flat. The KLM math becomes unstable in this case. + g->codeAppendf("if (2 * abs(area_times_2) < length((bezierpts[3] - bezierpts[0]) * %s.zx)) {", + rtAdjust); +#ifndef SK_BUILD_FOR_MAC + g->codeAppend ( "return;"); +#else + // Returning from this geometry shader makes Mac very unhappy. Instead we make wind 0. + g->codeAppend ( "area_times_2 = 0;"); +#endif + g->codeAppend ("}"); + g->codeAppendf("%s = sign(area_times_2);", outputWind); +} + +void GrCCPRCubicProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const { + // Prepend bezierpts at the start of the shader. + g->codePrependf("highp mat4x2 bezierpts = mat4x2(sk_in[0].gl_Position.xy, " + "sk_in[1].gl_Position.xy, " + "sk_in[2].gl_Position.xy, " + "sk_in[3].gl_Position.xy);"); + + // Evaluate the cubic at t=.5 for an approximate midpoint. + g->codeAppendf("highp vec2 midpoint = bezierpts * vec4(.125, .375, .375, .125);"); + + // Finish finding the inset geometry we started in the vertex shader. The z component tells us + // how "sharp" an inset corner is. And the vertex shader already skips one corner if it is + // colinear with its neighbors. So at this point, if a corner is flat, it means the inset + // geometry is all empty (it should never be non-convex because the curve gets chopped into + // convex segments ahead of time). + g->codeAppendf("bool isempty = " + "any(lessThan(vec4(%s[0].z, %s[1].z, %s[2].z, %s[3].z) * %s, vec4(2)));", + fInset.gsIn(), fInset.gsIn(), fInset.gsIn(), fInset.gsIn(), wind); + g->codeAppendf("highp vec2 inset[4];"); + g->codeAppend ("for (int i = 0; i < 4; ++i) {"); + g->codeAppendf( "inset[i] = isempty ? midpoint : %s[i].xy;", fInset.gsIn()); + g->codeAppend ("}"); + + // We determine crossover and/or degeneracy by how many inset edges run the opposite direction + // of their corresponding bezier edge. If there is one backwards edge, the inset geometry is + // actually triangle with a vertex at the crossover point. If there are >1 backwards edges, the + // inset geometry doesn't exist (i.e. the bezier quadrilateral isn't large enough) and we + // degenerate to the midpoint. + g->codeAppend ("lowp float backwards[4];"); + g->codeAppend ("lowp int numbackwards = 0;"); + g->codeAppend ("for (int i = 0; i < 4; ++i) {"); + g->codeAppend ( "lowp int j = (i + 1) % 4;"); + g->codeAppendf( "highp vec2 inner = inset[j] - inset[i];"); + g->codeAppendf( "highp vec2 outer = sk_in[j].gl_Position.xy - sk_in[i].gl_Position.xy;"); + g->codeAppendf( "backwards[i] = sign(dot(outer, inner));"); + g->codeAppendf( "numbackwards += backwards[i] < 0 ? 1 : 0;"); + g->codeAppend ("}"); + + // Find the crossover point. If there actually isn't one, this math is meaningless and will get + // dropped on the floor later. + g->codeAppend ("lowp int x = (backwards[0] != backwards[2]) ? 1 : 0;"); + g->codeAppend ("lowp int x3 = (x + 3) % 4;"); + g->codeAppend ("highp mat2 X = mat2(inset[x].y - inset[x+1].y, " + "inset[x+1].x - inset[x].x, " + "inset[x+2].y - inset[x3].y, " + "inset[x3].x - inset[x+2].x);"); + g->codeAppend ("highp vec2 KK = vec2(dot(X[0], inset[x]), dot(X[1], inset[x+2]));"); + g->codeAppend ("highp vec2 crossoverpoint = KK * inverse(X);"); + + // Determine what point backwards edges should collapse into. If there is one backwards edge, + // it should collapse to the crossover point. If >1, they should all collapse to the midpoint. + g->codeAppend ("highp vec2 collapsepoint = numbackwards == 1 ? crossoverpoint : midpoint;"); + + // Collapse backwards egdes to the "collapse" point. + g->codeAppend ("for (int i = 0; i < 4; ++i) {"); + g->codeAppend ( "if (backwards[i] < 0) {"); + g->codeAppend ( "inset[i] = inset[(i + 1) % 4] = collapsepoint;"); + g->codeAppend ( "}"); + g->codeAppend ("}"); + + // Calculate the KLM matrix. + g->declareGlobal(fKLMMatrix); + g->codeAppend ("highp vec4 K, L, M;"); + if (Type::kSerpentine == fType) { + g->codeAppend ("highp vec2 l,m;"); + g->codeAppendf("l.ts = vec2(%s[0], %s[1]);", fTS.gsIn(), fTS.gsIn()); + g->codeAppendf("m.ts = vec2(%s[2], %s[3]);", fTS.gsIn(), fTS.gsIn()); + g->codeAppend ("K = vec4(0, l.s * m.s, -l.t * m.s - m.t * l.s, l.t * m.t);"); + g->codeAppend ("L = vec4(-1,3,-3,1) * l.ssst * l.sstt * l.sttt;"); + g->codeAppend ("M = vec4(-1,3,-3,1) * m.ssst * m.sstt * m.sttt;"); + + } else { + g->codeAppend ("highp vec2 d,e;"); + g->codeAppendf("d.ts = vec2(%s[0], %s[1]);", fTS.gsIn(), fTS.gsIn()); + g->codeAppendf("e.ts = vec2(%s[2], %s[3]);", fTS.gsIn(), fTS.gsIn()); + g->codeAppend ("highp vec4 dxe = vec4(d.s * e.s, d.s * e.t, d.t * e.s, d.t * e.t);"); + g->codeAppend ("K = vec4(0, dxe.x, -dxe.y - dxe.z, dxe.w);"); + g->codeAppend ("L = vec4(-1,1,-1,1) * d.sstt * (dxe.xyzw + vec4(0, 2*dxe.zy, 0));"); + g->codeAppend ("M = vec4(-1,1,-1,1) * e.sstt * (dxe.xzyw + vec4(0, 2*dxe.yz, 0));"); + } + + g->codeAppend ("highp mat2x4 C = mat4(-1, 3, -3, 1, " + " 3, -6, 3, 0, " + "-3, 3, 0, 0, " + " 1, 0, 0, 0) * transpose(bezierpts);"); + + g->codeAppend ("highp vec2 absdet = abs(C[0].xx * C[1].zy - C[1].xx * C[0].zy);"); + g->codeAppend ("lowp int middlerow = absdet[0] > absdet[1] ? 2 : 1;"); + + g->codeAppend ("highp mat3 CI = inverse(mat3(C[0][0], C[0][middlerow], C[0][3], " + "C[1][0], C[1][middlerow], C[1][3], " + " 0, 0, 1));"); + g->codeAppendf("%s = CI * mat3(K[0], K[middlerow], K[3], " + "L[0], L[middlerow], L[3], " + "M[0], M[middlerow], M[3]);", fKLMMatrix.c_str()); + + // Orient the KLM matrix so we fill the correct side of the curve. + g->codeAppendf("lowp vec2 orientation = sign(vec3(midpoint, 1) * mat2x3(%s[1], %s[2]));", + fKLMMatrix.c_str(), fKLMMatrix.c_str()); + g->codeAppendf("%s *= mat3(orientation[0] * orientation[1], 0, 0, " + "0, orientation[0], 0, " + "0, 0, orientation[1]);", fKLMMatrix.c_str()); + + g->declareGlobal(fKLMDerivatives); + g->codeAppendf("%s[0] = %s[0].xy * %s.xz;", + fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust); + g->codeAppendf("%s[1] = %s[1].xy * %s.xz;", + fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust); + g->codeAppendf("%s[2] = %s[2].xy * %s.xz;", + fKLMDerivatives.c_str(), fKLMMatrix.c_str(), rtAdjust); + + this->emitCubicGeometry(g, emitVertexFn, wind, rtAdjust); +} + +void GrCCPRCubicInsetProcessor::emitCubicGeometry(GrGLSLGeometryBuilder* g, + const char* emitVertexFn, const char* wind, + const char* rtAdjust) const { + // FIXME: we should clip this geometry at the tip of the curve. + g->codeAppendf("%s(inset[0], 1);", emitVertexFn); + g->codeAppendf("%s(inset[1], 1);", emitVertexFn); + g->codeAppendf("%s(inset[3], 1);", emitVertexFn); + g->codeAppendf("%s(inset[2], 1);", emitVertexFn); + g->codeAppend ("EndPrimitive();"); + + g->configure(GrGLSLGeometryBuilder::InputType::kLinesAdjacency, + GrGLSLGeometryBuilder::OutputType::kTriangleStrip, + 4, 1); +} + +void GrCCPRCubicInsetProcessor::emitPerVertexGeometryCode(SkString* fnBody, const char* position, + const char* /*coverage*/, + const char* /*wind*/) const { + fnBody->appendf("highp vec3 klm = vec3(%s, 1) * %s;", position, fKLMMatrix.c_str()); + fnBody->appendf("%s = klm;", fKLM.gsOut()); + fnBody->appendf("%s[0] = 3 * klm[0] * %s[0];", fGradMatrix.gsOut(), fKLMDerivatives.c_str()); + fnBody->appendf("%s[1] = -klm[1] * %s[2].xy - klm[2] * %s[1].xy;", + fGradMatrix.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str()); +} + +void GrCCPRCubicInsetProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f, + const char* outputCoverage) const { + f->codeAppendf("highp float k = %s.x, l = %s.y, m = %s.z;", + fKLM.fsIn(), fKLM.fsIn(), fKLM.fsIn()); + f->codeAppend ("highp float f = k*k*k - l*m;"); + f->codeAppendf("highp vec2 grad = %s * vec2(k, 1);", fGradMatrix.fsIn()); + f->codeAppend ("highp float d = f * inversesqrt(dot(grad, grad));"); + f->codeAppendf("%s = clamp(0.5 - d, 0, 1);", outputCoverage); +} + +void GrCCPRCubicBorderProcessor::emitCubicGeometry(GrGLSLGeometryBuilder* g, + const char* emitVertexFn, const char* wind, + const char* rtAdjust) const { + // We defined bezierpts in onEmitGeometryShader. + g->declareGlobal(fEdgeDistanceEquation); + g->codeAppendf("int edgeidx0 = %s > 0 ? 3 : 0;", wind); + g->codeAppendf("highp vec2 edgept0 = bezierpts[edgeidx0];"); + g->codeAppendf("highp vec2 edgept1 = bezierpts[3 - edgeidx0];"); + this->emitEdgeDistanceEquation(g, "edgept0", "edgept1", fEdgeDistanceEquation.c_str()); + g->codeAppendf("%s.z += 0.5;", fEdgeDistanceEquation.c_str()); // outer = -.5, inner = .5 + + g->declareGlobal(fEdgeDistanceDerivatives); + g->codeAppendf("%s = %s.xy * %s.xz;", + fEdgeDistanceDerivatives.c_str(), fEdgeDistanceEquation.c_str(), rtAdjust); + + g->declareGlobal(fEdgeSpaceTransform); + g->codeAppend ("highp vec4 edgebbox = vec4(min(bezierpts[0], bezierpts[3]) - bloat, " + "max(bezierpts[0], bezierpts[3]) + bloat);"); + g->codeAppendf("%s.xy = 2 / vec2(edgebbox.zw - edgebbox.xy);", fEdgeSpaceTransform.c_str()); + g->codeAppendf("%s.zw = -1 - %s.xy * edgebbox.xy;", + fEdgeSpaceTransform.c_str(), fEdgeSpaceTransform.c_str()); + + int maxVertices = this->emitHullGeometry(g, emitVertexFn, "bezierpts", 4, "sk_InvocationID", + "inset"); + + g->configure(GrGLSLGeometryBuilder::InputType::kLinesAdjacency, + GrGLSLGeometryBuilder::OutputType::kTriangleStrip, + maxVertices, 4); +} + +void GrCCPRCubicBorderProcessor::emitPerVertexGeometryCode(SkString* fnBody, const char* position, + const char* /*coverage*/, + const char* /*wind*/) const { + fnBody->appendf("highp vec3 klm = vec3(%s, 1) * %s;", position, fKLMMatrix.c_str()); + fnBody->appendf("highp float d = dot(vec3(%s, 1), %s);", + position, fEdgeDistanceEquation.c_str()); + fnBody->appendf("%s = vec4(klm, d);", fKLMD.gsOut()); + fnBody->appendf("%s = vec4(%s[0].x, %s[1].x, %s[2].x, %s.x);", + fdKLMDdx.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str(), + fKLMDerivatives.c_str(), fEdgeDistanceDerivatives.c_str()); + fnBody->appendf("%s = vec4(%s[0].y, %s[1].y, %s[2].y, %s.y);", + fdKLMDdy.gsOut(), fKLMDerivatives.c_str(), fKLMDerivatives.c_str(), + fKLMDerivatives.c_str(), fEdgeDistanceDerivatives.c_str()); + fnBody->appendf("%s = position * %s.xy + %s.zw;", fEdgeSpaceCoord.gsOut(), + fEdgeSpaceTransform.c_str(), fEdgeSpaceTransform.c_str()); + + // Otherwise, fEdgeDistances = fEdgeDistances * sign(wind * rtAdjust.x * rdAdjust.z). + GR_STATIC_ASSERT(kTopLeft_GrSurfaceOrigin == GrCCPRCoverageProcessor::kAtlasOrigin); +} + +void GrCCPRCubicBorderProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f, + const char* outputCoverage) const { + // Use software msaa to determine coverage. + const int sampleCount = this->defineSoftSampleLocations(f, "samples"); + + // Along the shared edge, we start with distance-to-edge coverage, then subtract out the + // remaining pixel coverage that is still inside the shared edge, but outside the curve. + // Outside the shared edege, we just use standard msaa to count samples inside the curve. + f->codeAppendf("bool use_edge = all(lessThan(abs(%s), vec2(1)));", fEdgeSpaceCoord.fsIn()); + f->codeAppendf("%s = (use_edge ? clamp(%s.w + 0.5, 0, 1) : 0) * %i;", + outputCoverage, fKLMD.fsIn(), sampleCount); + + f->codeAppendf("highp mat2x4 grad_klmd = mat2x4(%s, %s);", fdKLMDdx.fsIn(), fdKLMDdy.fsIn()); + + f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount); + f->codeAppendf( "highp vec4 klmd = grad_klmd * samples[i] + %s;", fKLMD.fsIn()); + f->codeAppend ( "lowp float f = klmd.y * klmd.z - klmd.x * klmd.x * klmd.x;"); + // A sample is inside our cubic sub-section if it is inside the implicit AND L & M are both + // positive. This works because the sections get chopped at the K/L and K/M intersections. + f->codeAppend ( "bvec4 inside = greaterThan(vec4(f,klmd.yzw), vec4(0));"); + f->codeAppend ( "lowp float in_curve = all(inside.xyz) ? 1 : 0;"); + f->codeAppend ( "lowp float in_edge = inside.w ? 1 : 0;"); + f->codeAppendf( "%s += use_edge ? in_edge * (in_curve - 1) : in_curve;", outputCoverage); + f->codeAppend ("}"); + + f->codeAppendf("%s *= %f;", outputCoverage, 1.0 / sampleCount); +} diff --git a/src/gpu/ccpr/GrCCPRCubicProcessor.h b/src/gpu/ccpr/GrCCPRCubicProcessor.h new file mode 100644 index 0000000000..f31dad793e --- /dev/null +++ b/src/gpu/ccpr/GrCCPRCubicProcessor.h @@ -0,0 +1,147 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPRCubicProcessor_DEFINED +#define GrCCPRCubicProcessor_DEFINED + +#include "ccpr/GrCCPRCoverageProcessor.h" + +class GrGLSLGeometryBuilder; + +/** + * This class renders the coverage of convex closed cubic segments using the techniques outlined in + * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and + * Jim Blinn: + * + * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + * + * The caller is expected to chop cubics at the KLM roots (a.k.a. inflection points and loop + * intersection points, resulting in necessarily convex segments) before feeding them into this + * processor. + * + * The curves are rendered in two passes: + * + * Pass 1: Draw the (convex) bezier quadrilateral, inset by 1/2 pixel all around, and use the + * gradient-based AA technique outlined in the Loop/Blinn paper to compute coverage. + * + * Pass 2: Draw a border around the previous inset, up to the bezier quadrilatral's conservative + * raster hull, and compute coverage using pseudo MSAA. This pass is necessary because the + * gradient approach does not work near the L and M lines. + * + * FIXME: The pseudo MSAA border is slow and ugly. We should investigate an alternate solution of + * just approximating the curve with straight lines for short distances across the problem points + * instead. + */ +class GrCCPRCubicProcessor : public GrCCPRCoverageProcessor::PrimitiveProcessor { +public: + enum class Type { + kSerpentine, + kLoop + }; + + GrCCPRCubicProcessor(Type type) + : INHERITED(CoverageType::kShader) + , fType(type) + , fInset(kVec3f_GrSLType) + , fTS(kFloat_GrSLType) + , fKLMMatrix("klm_matrix", kMat33f_GrSLType, GrShaderVar::kNonArray, + kHigh_GrSLPrecision) + , fKLMDerivatives("klm_derivatives", kVec2f_GrSLType, 3, kHigh_GrSLPrecision) {} + + void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override { + varyingHandler->addVarying("insets", &fInset, kHigh_GrSLPrecision); + varyingHandler->addVarying("ts", &fTS, kHigh_GrSLPrecision); + } + + void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*, + const TexelBufferHandle& pointsBuffer, const char* atlasOffset, + const char* rtAdjust, GrGPArgs*) const override; + void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust, const char* outputWind) const final; + void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind, + const char* rtAdjust) const final; + +protected: + virtual void emitCubicGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const = 0; + + const Type fType; + GrGLSLVertToGeo fInset; + GrGLSLVertToGeo fTS; + GrShaderVar fKLMMatrix; + GrShaderVar fKLMDerivatives; + + typedef GrCCPRCoverageProcessor::PrimitiveProcessor INHERITED; +}; + +class GrCCPRCubicInsetProcessor : public GrCCPRCubicProcessor { +public: + GrCCPRCubicInsetProcessor(Type type) + : INHERITED(type) + , fKLM(kVec3f_GrSLType) + , fGradMatrix(kMat22f_GrSLType) {} + + void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override { + this->INHERITED::resetVaryings(varyingHandler); + varyingHandler->addVarying("klm", &fKLM, kHigh_GrSLPrecision); + varyingHandler->addVarying("grad_matrix", &fGradMatrix, kHigh_GrSLPrecision); + } + + void emitCubicGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const override; + void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage, + const char* wind) const override; + void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override; + +protected: + GrGLSLGeoToFrag fKLM; + GrGLSLGeoToFrag fGradMatrix; + + typedef GrCCPRCubicProcessor INHERITED; +}; + +class GrCCPRCubicBorderProcessor : public GrCCPRCubicProcessor { +public: + GrCCPRCubicBorderProcessor(Type type) + : INHERITED(type) + , fEdgeDistanceEquation("edge_distance_equation", kVec3f_GrSLType, + GrShaderVar::kNonArray, kHigh_GrSLPrecision) + , fEdgeDistanceDerivatives("edge_distance_derivatives", kVec2f_GrSLType, + GrShaderVar::kNonArray, kHigh_GrSLPrecision) + , fEdgeSpaceTransform("edge_space_transform", kVec4f_GrSLType, GrShaderVar::kNonArray, + kHigh_GrSLPrecision) + , fKLMD(kVec4f_GrSLType) + , fdKLMDdx(kVec4f_GrSLType) + , fdKLMDdy(kVec4f_GrSLType) + , fEdgeSpaceCoord(kVec2f_GrSLType) {} + + void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override { + this->INHERITED::resetVaryings(varyingHandler); + varyingHandler->addVarying("klmd", &fKLMD, kHigh_GrSLPrecision); + varyingHandler->addFlatVarying("dklmddx", &fdKLMDdx, kHigh_GrSLPrecision); + varyingHandler->addFlatVarying("dklmddy", &fdKLMDdy, kHigh_GrSLPrecision); + varyingHandler->addVarying("edge_space_coord", &fEdgeSpaceCoord, kHigh_GrSLPrecision); + } + + void emitCubicGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const override; + void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage, + const char* wind) const override; + void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override; + +protected: + GrShaderVar fEdgeDistanceEquation; + GrShaderVar fEdgeDistanceDerivatives; + GrShaderVar fEdgeSpaceTransform; + GrGLSLGeoToFrag fKLMD; + GrGLSLGeoToFrag fdKLMDdx; + GrGLSLGeoToFrag fdKLMDdy; + GrGLSLGeoToFrag fEdgeSpaceCoord; + + typedef GrCCPRCubicProcessor INHERITED; +}; + +#endif diff --git a/src/gpu/ccpr/GrCCPRPathProcessor.cpp b/src/gpu/ccpr/GrCCPRPathProcessor.cpp new file mode 100644 index 0000000000..bc2e45cb24 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRPathProcessor.cpp @@ -0,0 +1,206 @@ +/* + * 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 "GrCCPRPathProcessor.h" + +#include "GrOnFlushResourceProvider.h" +#include "GrTexture.h" +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLGeometryProcessor.h" +#include "glsl/GrGLSLProgramBuilder.h" +#include "glsl/GrGLSLVarying.h" + +// Slightly undershoot an AA bloat radius of 0.5 so vertices that fall on integer boundaries don't +// accidentally reach into neighboring path masks within the atlas. +constexpr float kAABloatRadius = 0.491111f; + +// Paths are drawn as octagons. Each point on the octagon is the intersection of two lines: one edge +// from the path's bounding box and one edge from its 45-degree bounding box. The below inputs +// define a vertex by the two edges that need to be intersected. Normals point out of the octagon, +// and the bounding boxes are sent in as instance attribs. +static constexpr float kOctoEdgeNorms[8 * 4] = { + // bbox // bbox45 + -1, 0, -1,+1, + -1, 0, -1,-1, + 0,-1, -1,-1, + 0,-1, +1,-1, + +1, 0, +1,-1, + +1, 0, +1,+1, + 0,+1, +1,+1, + 0,+1, -1,+1, +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gVertexBufferKey); + +// Index buffer for the octagon defined above. +static uint16_t kOctoIndices[GrCCPRPathProcessor::kPerInstanceIndexCount] = { + 0, 4, 2, + 0, 6, 4, + 0, 2, 1, + 2, 4, 3, + 4, 6, 5, + 6, 0, 7, +}; + +GR_DECLARE_STATIC_UNIQUE_KEY(gIndexBufferKey); + +GrCCPRPathProcessor::GrCCPRPathProcessor(GrResourceProvider* rp, sk_sp<GrTextureProxy> atlas, + SkPath::FillType fillType, const GrShaderCaps& shaderCaps) + : fFillType(fillType) { + this->addInstanceAttrib("devbounds", kVec4f_GrVertexAttribType, kHigh_GrSLPrecision); + this->addInstanceAttrib("devbounds45", kVec4f_GrVertexAttribType, kHigh_GrSLPrecision); + this->addInstanceAttrib("view_matrix", kVec4f_GrVertexAttribType, kHigh_GrSLPrecision); + this->addInstanceAttrib("view_translate", kVec2f_GrVertexAttribType, kHigh_GrSLPrecision); + // FIXME: this could be a vector of two shorts if it were supported by Ganesh. + this->addInstanceAttrib("atlas_offset", kVec2i_GrVertexAttribType, kHigh_GrSLPrecision); + this->addInstanceAttrib("color", kVec4ub_GrVertexAttribType, kLow_GrSLPrecision); + + SkASSERT(offsetof(Instance, fDevBounds) == + this->getInstanceAttrib(InstanceAttribs::kDevBounds).fOffsetInRecord); + SkASSERT(offsetof(Instance, fDevBounds45) == + this->getInstanceAttrib(InstanceAttribs::kDevBounds45).fOffsetInRecord); + SkASSERT(offsetof(Instance, fViewMatrix) == + this->getInstanceAttrib(InstanceAttribs::kViewMatrix).fOffsetInRecord); + SkASSERT(offsetof(Instance, fViewTranslate) == + this->getInstanceAttrib(InstanceAttribs::kViewTranslate).fOffsetInRecord); + SkASSERT(offsetof(Instance, fAtlasOffset) == + this->getInstanceAttrib(InstanceAttribs::kAtlasOffset).fOffsetInRecord); + SkASSERT(offsetof(Instance, fColor) == + this->getInstanceAttrib(InstanceAttribs::kColor).fOffsetInRecord); + SkASSERT(sizeof(Instance) == this->getInstanceStride()); + + GR_STATIC_ASSERT(6 == kNumInstanceAttribs); + + this->addVertexAttrib("edge_norms", kVec4f_GrVertexAttribType, kHigh_GrSLPrecision); + + fAtlasAccess.reset(std::move(atlas), GrSamplerParams::FilterMode::kNone_FilterMode, + SkShader::TileMode::kClamp_TileMode, kFragment_GrShaderFlag); + fAtlasAccess.instantiate(rp); + this->addTextureSampler(&fAtlasAccess); + + this->initClassID<GrCCPRPathProcessor>(); +} + +void GrCCPRPathProcessor::getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder* b) const { + b->add32((fFillType << 16) | this->atlas()->origin()); +} + +class GLSLPathProcessor : public GrGLSLGeometryProcessor { +public: + void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override; + +private: + void setData(const GrGLSLProgramDataManager& pdman, const GrPrimitiveProcessor& primProc, + FPCoordTransformIter&& transformIter) override { + const GrCCPRPathProcessor& proc = primProc.cast<GrCCPRPathProcessor>(); + pdman.set2f(fAtlasAdjustUniform, 1.0f / proc.atlas()->width(), + 1.0f / proc.atlas()->height()); + this->setTransformDataHelper(SkMatrix::I(), pdman, &transformIter); + } + + GrGLSLUniformHandler::UniformHandle fAtlasAdjustUniform; + + typedef GrGLSLGeometryProcessor INHERITED; +}; + +GrGLSLPrimitiveProcessor* GrCCPRPathProcessor::createGLSLInstance(const GrShaderCaps&) const { + return new GLSLPathProcessor(); +} + +void GLSLPathProcessor::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) { + using InstanceAttribs = GrCCPRPathProcessor::InstanceAttribs; + const GrCCPRPathProcessor& proc = args.fGP.cast<GrCCPRPathProcessor>(); + GrGLSLUniformHandler* uniHandler = args.fUniformHandler; + GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler; + + const char* atlasAdjust; + fAtlasAdjustUniform = uniHandler->addUniform( + kVertex_GrShaderFlag, + kVec2f_GrSLType, kHigh_GrSLPrecision, "atlas_adjust", &atlasAdjust); + + varyingHandler->emitAttributes(proc); + + GrGLSLVertToFrag texcoord(kVec2f_GrSLType); + GrGLSLVertToFrag color(kVec4f_GrSLType); + varyingHandler->addVarying("texcoord", &texcoord, kHigh_GrSLPrecision); + varyingHandler->addFlatPassThroughAttribute(&proc.getInstanceAttrib(InstanceAttribs::kColor), + args.fOutputColor, kLow_GrSLPrecision); + + // Vertex shader. + GrGLSLVertexBuilder* v = args.fVertBuilder; + + // Find the intersections of (bloated) devBounds and devBounds45 in order to come up with an + // octagon that circumscribes the (bloated) path. A vertex is the intersection of two lines: + // one edge from the path's bounding box and one edge from its 45-degree bounding box. + v->codeAppendf("highp mat2 N = mat2(%s);", proc.getEdgeNormsAttrib().fName); + + // N[0] is the normal for the edge we are intersecting from the regular bounding box, pointing + // out of the octagon. + v->codeAppendf("highp vec2 refpt = (min(N[0].x, N[0].y) < 0) ? %s.xy : %s.zw;", + proc.getInstanceAttrib(InstanceAttribs::kDevBounds).fName, + proc.getInstanceAttrib(InstanceAttribs::kDevBounds).fName); + v->codeAppendf("refpt += N[0] * %f;", kAABloatRadius); // bloat for AA. + + // N[1] is the normal for the edge we are intersecting from the 45-degree bounding box, pointing + // out of the octagon. + v->codeAppendf("highp vec2 refpt45 = (N[1].x < 0) ? %s.xy : %s.zw;", + proc.getInstanceAttrib(InstanceAttribs::kDevBounds45).fName, + proc.getInstanceAttrib(InstanceAttribs::kDevBounds45).fName); + v->codeAppendf("refpt45 *= mat2(.5,.5,-.5,.5);"); // transform back to device space. + v->codeAppendf("refpt45 += N[1] * %f;", kAABloatRadius); // bloat for AA. + + v->codeAppend ("highp vec2 K = vec2(dot(N[0], refpt), dot(N[1], refpt45));"); + v->codeAppendf("highp vec2 octocoord = K * inverse(N);"); + + gpArgs->fPositionVar.set(kVec2f_GrSLType, "octocoord"); + + // Convert to atlas coordinates in order to do our texture lookup. + v->codeAppendf("highp vec2 atlascoord = octocoord + vec2(%s);", + proc.getInstanceAttrib(InstanceAttribs::kAtlasOffset).fName); + if (kTopLeft_GrSurfaceOrigin == proc.atlas()->origin()) { + v->codeAppendf("%s = atlascoord * %s;", texcoord.vsOut(), atlasAdjust); + } else { + SkASSERT(kBottomLeft_GrSurfaceOrigin == proc.atlas()->origin()); + v->codeAppendf("%s = vec2(atlascoord.x * %s.x, 1 - atlascoord.y * %s.y);", + texcoord.vsOut(), atlasAdjust, atlasAdjust); + } + + // Convert to (local) path cordinates. + v->codeAppendf("highp vec2 pathcoord = inverse(mat2(%s)) * (octocoord - %s);", + proc.getInstanceAttrib(InstanceAttribs::kViewMatrix).fName, + proc.getInstanceAttrib(InstanceAttribs::kViewTranslate).fName); + + this->emitTransforms(v, varyingHandler, uniHandler, gpArgs->fPositionVar, "pathcoord", + args.fFPCoordTransformHandler); + + // Fragment shader. + GrGLSLPPFragmentBuilder* f = args.fFragBuilder; + + f->codeAppend ("mediump float coverage_count = "); + f->appendTextureLookup(args.fTexSamplers[0], texcoord.fsIn(), kVec2f_GrSLType); + f->codeAppend (".a;"); + + if (SkPath::kWinding_FillType == proc.fillType()) { + f->codeAppendf("%s = vec4(min(abs(coverage_count), 1));", args.fOutputCoverage); + } else { + SkASSERT(SkPath::kEvenOdd_FillType == proc.fillType()); + f->codeAppend ("mediump float t = mod(abs(coverage_count), 2);"); + f->codeAppendf("%s = vec4(1 - abs(t - 1));", args.fOutputCoverage); + } +} + +sk_sp<GrBuffer> GrCCPRPathProcessor::FindOrMakeIndexBuffer(GrOnFlushResourceProvider* onFlushRP) { + GR_DEFINE_STATIC_UNIQUE_KEY(gIndexBufferKey); + return onFlushRP->findOrMakeStaticBuffer(gIndexBufferKey, kIndex_GrBufferType, + sizeof(kOctoIndices), kOctoIndices); +} + +sk_sp<GrBuffer> GrCCPRPathProcessor::FindOrMakeVertexBuffer(GrOnFlushResourceProvider* onFlushRP) { + GR_DEFINE_STATIC_UNIQUE_KEY(gVertexBufferKey); + return onFlushRP->findOrMakeStaticBuffer(gVertexBufferKey, kVertex_GrBufferType, + sizeof(kOctoEdgeNorms), kOctoEdgeNorms); +} diff --git a/src/gpu/ccpr/GrCCPRPathProcessor.h b/src/gpu/ccpr/GrCCPRPathProcessor.h new file mode 100644 index 0000000000..a74455bc8e --- /dev/null +++ b/src/gpu/ccpr/GrCCPRPathProcessor.h @@ -0,0 +1,85 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPRPathProcessor_DEFINED +#define GrCCPRPathProcessor_DEFINED + +#include "GrGeometryProcessor.h" +#include "SkPath.h" +#include <array> + +class GrOnFlushResourceProvider; +class GrShaderCaps; + +/** + * This class draws AA paths using the coverage count masks produced by GrCCPRCoverageProcessor. + * + * Paths are drawn as bloated octagons, and coverage is derived from the coverage count mask and + * fill rule. + * + * The caller must set up an instance buffer as detailed below, then draw indexed-instanced + * triangles using the index and vertex buffers provided by this class. + */ +class GrCCPRPathProcessor : public GrGeometryProcessor { +public: + static constexpr int kPerInstanceIndexCount = 6 * 3; + static sk_sp<GrBuffer> FindOrMakeIndexBuffer(GrOnFlushResourceProvider*); + static sk_sp<GrBuffer> FindOrMakeVertexBuffer(GrOnFlushResourceProvider*); + + enum class InstanceAttribs { + kDevBounds, + kDevBounds45, + kViewMatrix, // FIXME: This causes a lot of duplication. It could move to a texel buffer. + kViewTranslate, + kAtlasOffset, + kColor + }; + static constexpr int kNumInstanceAttribs = 1 + (int)InstanceAttribs::kColor; + + struct Instance { + SkRect fDevBounds; + SkRect fDevBounds45; // Bounding box in "| 1 -1 | * devCoords" space. + // | 1 1 | + std::array<float, 4> fViewMatrix; // {kScaleX, kSkewy, kSkewX, kScaleY} + std::array<float, 2> fViewTranslate; + std::array<int32_t, 2> fAtlasOffset; + uint32_t fColor; + + GR_STATIC_ASSERT(SK_SCALAR_IS_FLOAT); + }; + + GR_STATIC_ASSERT(4 * 17 == sizeof(Instance)); // FIXME: 4 * 16 by making fAtlasOffset int16_t's. + + GrCCPRPathProcessor(GrResourceProvider*, sk_sp<GrTextureProxy> atlas, SkPath::FillType, + const GrShaderCaps&); + + const char* name() const override { return "GrCCPRPathProcessor"; } + const GrTexture* atlas() const { return fAtlasAccess.peekTexture(); } + SkPath::FillType fillType() const { return fFillType; } + const Attribute& getInstanceAttrib(InstanceAttribs attribID) const { + const Attribute& attrib = this->getAttrib((int)attribID); + SkASSERT(Attribute::InputRate::kPerInstance == attrib.fInputRate); + return attrib; + } + const Attribute& getEdgeNormsAttrib() const { + SkASSERT(1 + kNumInstanceAttribs == this->numAttribs()); + const Attribute& attrib = this->getAttrib(kNumInstanceAttribs); + SkASSERT(Attribute::InputRate::kPerVertex == attrib.fInputRate); + return attrib; + } + + void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override; + GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override; + +private: + const SkPath::FillType fFillType; + TextureSampler fAtlasAccess; + + typedef GrGeometryProcessor INHERITED; +}; + +#endif diff --git a/src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp b/src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp new file mode 100644 index 0000000000..8c58ea26a8 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRQuadraticProcessor.cpp @@ -0,0 +1,179 @@ +/* + * 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 "GrCCPRQuadraticProcessor.h" + +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLGeometryShaderBuilder.h" +#include "glsl/GrGLSLVertexShaderBuilder.h" + +void GrCCPRQuadraticProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& proc, + GrGLSLVertexBuilder* v, + const TexelBufferHandle& pointsBuffer, + const char* atlasOffset, const char* rtAdjust, + GrGPArgs* gpArgs) const { + v->codeAppendf("ivec3 indices = ivec3(%s.y, %s.x, %s.y + 1);", + proc.instanceAttrib(), proc.instanceAttrib(), proc.instanceAttrib()); + v->codeAppend ("highp vec2 self = "); + v->appendTexelFetch(pointsBuffer, "indices[sk_VertexID]"); + v->codeAppendf(".xy + %s;", atlasOffset); + gpArgs->fPositionVar.set(kVec2f_GrSLType, "self"); +} + +void GrCCPRQuadraticProcessor::emitWind(GrGLSLGeometryBuilder* g, const char* rtAdjust, + const char* outputWind) const { + // We will define bezierpts in onEmitGeometryShader. + g->codeAppend ("highp float area_times_2 = determinant(mat2(bezierpts[1] - bezierpts[0], " + "bezierpts[2] - bezierpts[0]));"); + // Drop curves that are nearly flat, in favor of the higher quality triangle antialiasing. + g->codeAppendf("if (2 * abs(area_times_2) < length((bezierpts[2] - bezierpts[0]) * %s.zx)) {", + rtAdjust); +#ifndef SK_BUILD_FOR_MAC + g->codeAppend ( "return;"); +#else + // Returning from this geometry shader makes Mac very unhappy. Instead we make wind 0. + g->codeAppend ( "area_times_2 = 0;"); +#endif + g->codeAppend ("}"); + g->codeAppendf("%s = sign(area_times_2);", outputWind); +} + +void GrCCPRQuadraticProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g, + const char* emitVertexFn, const char* wind, + const char* rtAdjust) const { + // Prepend bezierpts at the start of the shader. + g->codePrependf("highp mat3x2 bezierpts = mat3x2(sk_in[0].gl_Position.xy, " + "sk_in[1].gl_Position.xy, " + "sk_in[2].gl_Position.xy);"); + + g->declareGlobal(fCanonicalMatrix); + g->codeAppendf("%s = mat3(0.0, 0, 1, " + "0.5, 0, 1, " + "1.0, 1, 1) * " + "inverse(mat3(bezierpts[0], 1, " + "bezierpts[1], 1, " + "bezierpts[2], 1));", + fCanonicalMatrix.c_str()); + + g->declareGlobal(fCanonicalDerivatives); + g->codeAppendf("%s = mat2(%s) * mat2(%s.x, 0, 0, %s.z);", + fCanonicalDerivatives.c_str(), fCanonicalMatrix.c_str(), rtAdjust, rtAdjust); + + this->emitQuadraticGeometry(g, emitVertexFn, wind, rtAdjust); +} + +void GrCCPRQuadraticProcessor::emitPerVertexGeometryCode(SkString* fnBody, const char* position, + const char* /*coverage*/, + const char* /*wind*/) const { + fnBody->appendf("%s.xy = (%s * vec3(%s, 1)).xy;", + fCanonicalCoord.gsOut(), fCanonicalMatrix.c_str(), position); + fnBody->appendf("%s.zw = vec2(2 * %s.x * %s[0].x - %s[0].y, " + "2 * %s.x * %s[1].x - %s[1].y);", + fCanonicalCoord.gsOut(), fCanonicalCoord.gsOut(), + fCanonicalDerivatives.c_str(), fCanonicalDerivatives.c_str(), + fCanonicalCoord.gsOut(), fCanonicalDerivatives.c_str(), + fCanonicalDerivatives.c_str()); +} + +void GrCCPRQuadraticProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f, + const char* outputCoverage) const { + f->codeAppendf("highp float d = (%s.x * %s.x - %s.y) * inversesqrt(dot(%s.zw, %s.zw));", + fCanonicalCoord.fsIn(), fCanonicalCoord.fsIn(), fCanonicalCoord.fsIn(), + fCanonicalCoord.fsIn(), fCanonicalCoord.fsIn()); + f->codeAppendf("%s = clamp(0.5 - d, 0, 1);", outputCoverage); +} + +void GrCCPRQuadraticHullProcessor::emitQuadraticGeometry(GrGLSLGeometryBuilder* g, + const char* emitVertexFn, + const char* wind, + const char* rtAdjust) const { + // Find the point on the curve whose tangent is halfway between the tangents at the endpionts. + // We defined bezierpts in onEmitGeometryShader. + g->codeAppend ("highp vec2 n = (normalize(bezierpts[0] - bezierpts[1]) + " + "normalize(bezierpts[2] - bezierpts[1]));"); + g->codeAppend ("highp float t = dot(bezierpts[0] - bezierpts[1], n) / " + "dot(bezierpts[2] - 2 * bezierpts[1] + bezierpts[0], n);"); + g->codeAppend ("highp vec2 pt = (1 - t) * (1 - t) * bezierpts[0] + " + "2 * t * (1 - t) * bezierpts[1] + " + "t * t * bezierpts[2];"); + + // Clip the triangle by the tangent line at this halfway point. + g->codeAppend ("highp mat2 v = mat2(bezierpts[0] - bezierpts[1], " + "bezierpts[2] - bezierpts[1]);"); + g->codeAppend ("highp vec2 nv = n * v;"); + g->codeAppend ("highp vec2 d = abs(nv[0]) > 0.1 * max(bloat.x, bloat.y) ? " + "(dot(n, pt - bezierpts[1])) / nv : vec2(0);"); + + // Generate a 4-point hull of the curve from the clipped triangle. + g->codeAppendf("highp mat4x2 quadratic_hull = mat4x2(bezierpts[0], " + "bezierpts[1] + d[0] * v[0], " + "bezierpts[1] + d[1] * v[1], " + "bezierpts[2]);"); + + int maxVerts = this->emitHullGeometry(g, emitVertexFn, "quadratic_hull", 4, "sk_InvocationID"); + + g->configure(GrGLSLGeometryBuilder::InputType::kTriangles, + GrGLSLGeometryBuilder::OutputType::kTriangleStrip, + maxVerts, 4); +} + +void GrCCPRQuadraticSharedEdgeProcessor::emitQuadraticGeometry(GrGLSLGeometryBuilder* g, + const char* emitVertexFn, + const char* wind, + const char* rtAdjust) const { + // We defined bezierpts in onEmitGeometryShader. + g->codeAppendf("int leftidx = %s > 0 ? 2 : 0;", wind); + g->codeAppendf("highp vec2 left = bezierpts[leftidx];"); + g->codeAppendf("highp vec2 right = bezierpts[2 - leftidx];"); + this->emitEdgeDistanceEquation(g, "left", "right", "highp vec3 edge_distance_equation"); + + g->declareGlobal(fEdgeDistanceDerivatives); + g->codeAppendf("%s = edge_distance_equation.xy * %s.xz;", + fEdgeDistanceDerivatives.c_str(), rtAdjust); + + int maxVertices = this->emitEdgeGeometry(g, emitVertexFn, "left", "right", + "edge_distance_equation"); + + g->configure(GrGLSLGeometryBuilder::InputType::kTriangles, + GrGLSLGeometryBuilder::OutputType::kTriangleStrip, maxVertices, 1); +} + +void GrCCPRQuadraticSharedEdgeProcessor::emitPerVertexGeometryCode(SkString* fnBody, + const char* position, + const char* coverage, + const char* wind) const { + this->INHERITED::emitPerVertexGeometryCode(fnBody, position, coverage, wind); + fnBody->appendf("%s = %s;", fFragCanonicalDerivatives.gsOut(), fCanonicalDerivatives.c_str()); + fnBody->appendf("%s.x = %s + 0.5;", fEdgeDistance.gsOut(), coverage); // outer=-.5, inner=+.5. + fnBody->appendf("%s.yz = %s;", fEdgeDistance.gsOut(), fEdgeDistanceDerivatives.c_str()); +} + +void GrCCPRQuadraticSharedEdgeProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f, + const char* outputCoverage) const { + // Erase what the previous hull shader wrote and replace with edge coverage. + this->INHERITED::emitShaderCoverage(f, outputCoverage); + f->codeAppendf("%s = %s.x + 0.5 - %s;", + outputCoverage, fEdgeDistance.fsIn(), outputCoverage); + + // Use software msaa to subtract out the remaining pixel coverage that is still inside the + // shared edge, but outside the curve. + int sampleCount = this->defineSoftSampleLocations(f, "samples"); + + f->codeAppendf("highp mat2x3 grad_xyd = mat2x3(%s[0],%s.y, %s[1],%s.z);", + fFragCanonicalDerivatives.fsIn(), fEdgeDistance.fsIn(), + fFragCanonicalDerivatives.fsIn(), fEdgeDistance.fsIn()); + f->codeAppendf("highp vec3 center_xyd = vec3(%s.xy, %s.x);", + fCanonicalCoord.fsIn(), fEdgeDistance.fsIn()); + + f->codeAppendf("for (int i = 0; i < %i; ++i) {", sampleCount); + f->codeAppend ( "highp vec3 xyd = grad_xyd * samples[i] + center_xyd;"); + f->codeAppend ( "lowp float f = xyd.x * xyd.x - xyd.y;"); // f > 0 -> outside curve. + f->codeAppend ( "bvec2 outside_curve_inside_edge = greaterThan(vec2(f, xyd.z), vec2(0));"); + f->codeAppendf( "%s -= all(outside_curve_inside_edge) ? %f : 0;", + outputCoverage, 1.0 / sampleCount); + f->codeAppendf("}"); +} diff --git a/src/gpu/ccpr/GrCCPRQuadraticProcessor.h b/src/gpu/ccpr/GrCCPRQuadraticProcessor.h new file mode 100644 index 0000000000..c3e8d17f10 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRQuadraticProcessor.h @@ -0,0 +1,111 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPRQuadraticProcessor_DEFINED +#define GrCCPRQuadraticProcessor_DEFINED + +#include "ccpr/GrCCPRCoverageProcessor.h" + +/** + * This class renders the coverage of closed quadratic curves using the techniques outlined in + * "Resolution Independent Curve Rendering using Programmable Graphics Hardware" by Charles Loop and + * Jim Blinn: + * + * https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf + * + * The curves are rendered in two passes: + * + * Pass 1: Draw a conservative raster hull around the quadratic bezier points, and compute the + * curve's coverage using the gradient-based AA technique outlined in the Loop/Blinn paper. + * + * Pass 2: Touch up and antialias the flat edge from P2 back to P0. + */ +class GrCCPRQuadraticProcessor : public GrCCPRCoverageProcessor::PrimitiveProcessor { +public: + GrCCPRQuadraticProcessor() + : INHERITED(CoverageType::kShader) + , fCanonicalMatrix("canonical_matrix", kMat33f_GrSLType, GrShaderVar::kNonArray, + kHigh_GrSLPrecision) + , fCanonicalDerivatives("canonical_derivatives", kMat22f_GrSLType, + GrShaderVar::kNonArray, kHigh_GrSLPrecision) + , fCanonicalCoord(kVec4f_GrSLType) {} + + void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override { + varyingHandler->addVarying("canonical_coord", &fCanonicalCoord, kHigh_GrSLPrecision); + } + + void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*, + const TexelBufferHandle& pointsBuffer, const char* atlasOffset, + const char* rtAdjust, GrGPArgs*) const override; + void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust, const char* outputWind) const final; + void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind, + const char* rtAdjust) const final; + void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage, + const char* wind) const override; + void emitShaderCoverage(GrGLSLFragmentBuilder* f, const char* outputCoverage) const override; + +protected: + virtual void emitQuadraticGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const = 0; + + GrShaderVar fCanonicalMatrix; + GrShaderVar fCanonicalDerivatives; + GrGLSLGeoToFrag fCanonicalCoord; + + typedef GrCCPRCoverageProcessor::PrimitiveProcessor INHERITED; +}; + +class GrCCPRQuadraticHullProcessor : public GrCCPRQuadraticProcessor { +public: + void emitQuadraticGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const override; + +private: + typedef GrCCPRQuadraticProcessor INHERITED; +}; + +/** + * This pass touches up the flat edge (P2 -> P0) of a closed quadratic segment as follows: + * + * 1) Erase what the previous hull shader estimated for coverage. + * 2) Replace coverage with distance to the curve's flat edge (this is necessary when the edge + * is shared and must create a "water-tight" seam). + * 3) Use pseudo MSAA to subtract out the remaining pixel coverage that is still inside the flat + * edge, but outside the curve. + */ +class GrCCPRQuadraticSharedEdgeProcessor : public GrCCPRQuadraticProcessor { +public: + GrCCPRQuadraticSharedEdgeProcessor() + : fXYD("xyd", kMat33f_GrSLType, GrShaderVar::kNonArray, kHigh_GrSLPrecision) + , fEdgeDistanceDerivatives("edge_distance_derivatives", kVec2f_GrSLType, + GrShaderVar::kNonArray, kHigh_GrSLPrecision) + , fFragCanonicalDerivatives(kMat22f_GrSLType) + , fEdgeDistance(kVec3f_GrSLType) {} + + void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override { + this->INHERITED::resetVaryings(varyingHandler); + varyingHandler->addFlatVarying("canonical_derivatives", &fFragCanonicalDerivatives, + kHigh_GrSLPrecision); + varyingHandler->addVarying("edge_distance", &fEdgeDistance, kHigh_GrSLPrecision); + } + + void emitQuadraticGeometry(GrGLSLGeometryBuilder*, const char* emitVertexFn, + const char* wind, const char* rtAdjust) const override; + void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage, + const char* wind) const override; + void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override; + +private: + GrShaderVar fXYD; + GrShaderVar fEdgeDistanceDerivatives; + GrGLSLGeoToFrag fFragCanonicalDerivatives; + GrGLSLGeoToFrag fEdgeDistance; + + typedef GrCCPRQuadraticProcessor INHERITED; +}; + +#endif diff --git a/src/gpu/ccpr/GrCCPRTriangleProcessor.cpp b/src/gpu/ccpr/GrCCPRTriangleProcessor.cpp new file mode 100644 index 0000000000..23f7b143b1 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRTriangleProcessor.cpp @@ -0,0 +1,201 @@ +/* + * 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 "GrCCPRTriangleProcessor.h" + +#include "glsl/GrGLSLFragmentShaderBuilder.h" +#include "glsl/GrGLSLGeometryShaderBuilder.h" +#include "glsl/GrGLSLVertexShaderBuilder.h" + +void GrCCPRTriangleProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& proc, + GrGLSLVertexBuilder* v, + const TexelBufferHandle& pointsBuffer, + const char* atlasOffset, const char* rtAdjust, + GrGPArgs* gpArgs) const { + v->codeAppend ("highp vec2 self = "); + v->appendTexelFetch(pointsBuffer, + SkStringPrintf("%s[sk_VertexID]", proc.instanceAttrib()).c_str()); + v->codeAppendf(".xy + %s;", atlasOffset); + gpArgs->fPositionVar.set(kVec2f_GrSLType, "self"); +} + +void GrCCPRTriangleProcessor::defineInputVertices(GrGLSLGeometryBuilder* g) const { + // Prepend in_vertices at the start of the shader. + g->codePrependf("highp mat3x2 in_vertices = mat3x2(sk_in[0].gl_Position.xy, " + "sk_in[1].gl_Position.xy, " + "sk_in[2].gl_Position.xy);"); +} + +void GrCCPRTriangleProcessor::emitWind(GrGLSLGeometryBuilder* g, const char* /*rtAdjust*/, + const char* outputWind) const { + // We will define in_vertices in defineInputVertices. + g->codeAppendf("%s = sign(determinant(mat2(in_vertices[1] - in_vertices[0], " + "in_vertices[2] - in_vertices[0])));", outputWind); +} + +void GrCCPRTriangleHullAndEdgeProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g, + const char* emitVertexFn, + const char* wind, + const char* rtAdjust) const { + this->defineInputVertices(g); + int maxOutputVertices = 0; + + if (GeometryType::kEdges != fGeometryType) { + maxOutputVertices += this->emitHullGeometry(g, emitVertexFn, "in_vertices", 3, + "sk_InvocationID"); + } + + if (GeometryType::kHulls != fGeometryType) { + g->codeAppend ("int edgeidx0 = sk_InvocationID, " + "edgeidx1 = (edgeidx0 + 1) % 3;"); + g->codeAppendf("highp vec2 edgept0 = in_vertices[%s > 0 ? edgeidx0 : edgeidx1];", wind); + g->codeAppendf("highp vec2 edgept1 = in_vertices[%s > 0 ? edgeidx1 : edgeidx0];", wind); + + maxOutputVertices += this->emitEdgeGeometry(g, emitVertexFn, "edgept0", "edgept1"); + } + + g->configure(GrGLSLGeometryBuilder::InputType::kTriangles, + GrGLSLGeometryBuilder::OutputType::kTriangleStrip, + maxOutputVertices, 3); +} + +void GrCCPRTriangleCornerProcessor::onEmitVertexShader(const GrCCPRCoverageProcessor& proc, + GrGLSLVertexBuilder* v, + const TexelBufferHandle& pointsBuffer, + const char* atlasOffset, + const char* rtAdjust, + GrGPArgs* gpArgs) const { + this->INHERITED::onEmitVertexShader(proc, v, pointsBuffer, atlasOffset, rtAdjust, gpArgs); + + // Fetch and transform the next point in the triangle. + v->codeAppend ("highp vec2 next = "); + v->appendTexelFetch(pointsBuffer, + SkStringPrintf("%s[(sk_VertexID+1) %% 3]", proc.instanceAttrib()).c_str()); + v->codeAppendf(".xy + %s;", atlasOffset); + + // Find the plane that gives distance from the [self -> next] edge, normalized to its AA + // bloat width. + v->codeAppend ("highp vec2 n = vec2(next.y - self.y, self.x - next.x);"); + v->codeAppendf("highp vec2 d = n * mat2(self + %f * sign(n), " + "self - %f * sign(n));", kAABloatRadius, kAABloatRadius); + + // Clamp for when n=0. (wind=0 when n=0, so as long as we don't get Inf or NaN we are fine.) + v->codeAppendf("%s.xy = n / max(d[0] - d[1], 1e-30);", fEdgeDistance.vsOut()); + v->codeAppendf("%s.z = -dot(%s.xy, self);", fEdgeDistance.vsOut(), fEdgeDistance.vsOut()); + + // Emit device coords to geo shader. + v->codeAppendf("%s = self;", fDevCoord.vsOut()); +} + +void GrCCPRTriangleCornerProcessor::onEmitGeometryShader(GrGLSLGeometryBuilder* g, + const char* emitVertexFn, const char* wind, + const char* rtAdjust) const { + this->defineInputVertices(g); + + g->codeAppend ("highp vec2 self = in_vertices[sk_InvocationID];"); + g->codeAppendf("%s(self + vec2(-bloat.x, -bloat.y), 1);", emitVertexFn); + g->codeAppendf("%s(self + vec2(-bloat.x, +bloat.y), 1);", emitVertexFn); + g->codeAppendf("%s(self + vec2(+bloat.x, -bloat.y), 1);", emitVertexFn); + g->codeAppendf("%s(self + vec2(+bloat.x, +bloat.y), 1);", emitVertexFn); + g->codeAppend ("EndPrimitive();"); + + g->configure(GrGLSLGeometryBuilder::InputType::kTriangles, + GrGLSLGeometryBuilder::OutputType::kTriangleStrip, + 4, 3); +} + +void GrCCPRTriangleCornerProcessor::emitPerVertexGeometryCode(SkString* fnBody, + const char* position, + const char* /*coverage*/, + const char* wind) const { + fnBody->appendf("%s.xy = %s[(sk_InvocationID + 1) %% 3];", + fNeighbors.gsOut(), fDevCoord.gsIn()); + fnBody->appendf("%s.zw = %s[(sk_InvocationID + 2) %% 3];", + fNeighbors.gsOut(), fDevCoord.gsIn()); + fnBody->appendf("%s = mat3(%s[(sk_InvocationID + 2) %% 3], " + "%s[sk_InvocationID], " + "%s[(sk_InvocationID + 1) %% 3]) * %s;", + fEdgeDistances.gsOut(), fEdgeDistance.gsIn(), fEdgeDistance.gsIn(), + fEdgeDistance.gsIn(), wind); + + // Otherwise, fEdgeDistances = mat3(...) * sign(wind * rtAdjust.x * rdAdjust.z). + GR_STATIC_ASSERT(kTopLeft_GrSurfaceOrigin == GrCCPRCoverageProcessor::kAtlasOrigin); + + fnBody->appendf("%s = sk_InvocationID;", fCornerIdx.gsOut()); +} + +void GrCCPRTriangleCornerProcessor::emitShaderCoverage(GrGLSLFragmentBuilder* f, + const char* outputCoverage) const { + // FIXME: Adreno breaks if we don't put the frag coord in an intermediate highp variable. + f->codeAppendf("highp vec2 fragcoord = sk_FragCoord.xy;"); + + // Approximate coverage by tracking where 4 horizontal lines enter and leave the triangle. + GrShaderVar samples("samples", kVec4f_GrSLType, GrShaderVar::kNonArray, + kHigh_GrSLPrecision); + f->declareGlobal(samples); + f->codeAppendf("%s = fragcoord.y + vec4(-0.375, -0.125, 0.125, 0.375);", samples.c_str()); + + GrShaderVar leftedge("leftedge", kVec4f_GrSLType, GrShaderVar::kNonArray, + kHigh_GrSLPrecision); + f->declareGlobal(leftedge); + f->codeAppendf("%s = vec4(fragcoord.x - 0.5);", leftedge.c_str()); + + GrShaderVar rightedge("rightedge", kVec4f_GrSLType, GrShaderVar::kNonArray, + kHigh_GrSLPrecision); + f->declareGlobal(rightedge); + f->codeAppendf("%s = vec4(fragcoord.x + 0.5);", rightedge.c_str()); + + SkString sampleEdgeFn; + GrShaderVar edgeArg("edge_distance", kVec3f_GrSLType, GrShaderVar::kNonArray, + kHigh_GrSLPrecision); + f->emitFunction(kVoid_GrSLType, "sampleEdge", 1, &edgeArg, [&]() { + SkString b; + b.appendf("highp float m = abs(%s.x) < 1e-3 ? 1e18 : -1 / %s.x;", + edgeArg.c_str(), edgeArg.c_str()); + b.appendf("highp vec4 edge = m * (%s.y * samples + %s.z);", + edgeArg.c_str(), edgeArg.c_str()); + b.appendf("if (%s.x <= 1e-3 || (abs(%s.x) < 1e-3 && %s.y > 0)) {", + edgeArg.c_str(), edgeArg.c_str(), edgeArg.c_str()); + b.appendf( "%s = max(%s, edge);", leftedge.c_str(), leftedge.c_str()); + b.append ("} else {"); + b.appendf( "%s = min(%s, edge);", rightedge.c_str(), rightedge.c_str()); + b.append ("}"); + return b; + }().c_str(), &sampleEdgeFn); + + // See if the previous neighbor already handled this pixel. + f->codeAppendf("if (all(lessThan(abs(fragcoord - %s.zw), vec2(%f)))) {", + fNeighbors.fsIn(), kAABloatRadius); + // Handle the case where all 3 corners defer to the previous neighbor. + f->codeAppendf( "if (%s != 0 || !all(lessThan(abs(fragcoord - %s.xy), vec2(%f)))) {", + fCornerIdx.fsIn(), fNeighbors.fsIn(), kAABloatRadius); + f->codeAppend ( "discard;"); + f->codeAppend ( "}"); + f->codeAppend ("}"); + + // Erase what the hull and two edges wrote at this corner in previous shaders (the two .5's + // for the edges and the -1 for the hull cancel each other out). + f->codeAppendf("%s = dot(vec3(fragcoord, 1) * mat2x3(%s), vec2(1));", + outputCoverage, fEdgeDistances.fsIn()); + + // Sample the two edges at this corner. + f->codeAppendf("%s(%s[0]);", sampleEdgeFn.c_str(), fEdgeDistances.fsIn()); + f->codeAppendf("%s(%s[1]);", sampleEdgeFn.c_str(), fEdgeDistances.fsIn()); + + // Handle the opposite edge if the next neighbor will defer to us. + f->codeAppendf("if (all(lessThan(abs(fragcoord - %s.xy), vec2(%f)))) {", + fNeighbors.fsIn(), kAABloatRadius); + // Erase the coverage the opposite edge wrote to this corner. + f->codeAppendf( "%s += dot(%s[2], vec3(fragcoord, 1)) + 0.5;", + outputCoverage, fEdgeDistances.fsIn()); + // Sample the opposite edge. + f->codeAppendf( "%s(%s[2]);", sampleEdgeFn.c_str(), fEdgeDistances.fsIn()); + f->codeAppend ("}"); + + f->codeAppendf("highp vec4 widths = max(%s - %s, 0);", rightedge.c_str(), leftedge.c_str()); + f->codeAppendf("%s += dot(widths, vec4(0.25));", outputCoverage); +} diff --git a/src/gpu/ccpr/GrCCPRTriangleProcessor.h b/src/gpu/ccpr/GrCCPRTriangleProcessor.h new file mode 100644 index 0000000000..1e52d51a45 --- /dev/null +++ b/src/gpu/ccpr/GrCCPRTriangleProcessor.h @@ -0,0 +1,109 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCCPRTriangleProcessor_DEFINED +#define GrCCPRTriangleProcessor_DEFINED + +#include "ccpr/GrCCPRCoverageProcessor.h" + +/** + * This class renders the coverage of triangles. + * + * Triangles are rendered in three passes: + * + * Pass 1: Draw the triangle's conservative raster hull with a coverage of 1. (Conservative raster + * is drawn by considering 3 pixel size boxes, one centered at each vertex, and drawing the + * convex hull of those boxes.) + * + * Pass 2: Smooth the edges that were over-rendered during Pass 1. Draw the conservative raster of + * each edge (i.e. convex hull of two pixel-size boxes at the endpoints), interpolating from + * coverage=-1 on the outside edge to coverage=0 on the inside edge. + * + * Pass 3: Touch up the corner pixels to have the correct coverage. + */ +class GrCCPRTriangleProcessor : public GrCCPRCoverageProcessor::PrimitiveProcessor { +public: + GrCCPRTriangleProcessor(CoverageType initialCoverage) : INHERITED(initialCoverage) {} + + void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*, + const TexelBufferHandle& pointsBuffer, const char* atlasOffset, + const char* rtAdjust, GrGPArgs*) const override; + void emitWind(GrGLSLGeometryBuilder*, const char* rtAdjust, const char* outputWind) const final; + +protected: + void defineInputVertices(GrGLSLGeometryBuilder*) const; + +private: + typedef GrCCPRCoverageProcessor::PrimitiveProcessor INHERITED; +}; + +class GrCCPRTriangleHullAndEdgeProcessor : public GrCCPRTriangleProcessor { +public: + enum class GeometryType { + kHulls, + kEdges, + kHullsAndEdges + }; + + GrCCPRTriangleHullAndEdgeProcessor(GeometryType geometryType) + : INHERITED(GeometryType::kHulls == geometryType ? + CoverageType::kOne : CoverageType::kInterpolated) + , fGeometryType(geometryType) {} + + void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind, + const char* rtAdjust) const override; + +private: + const GeometryType fGeometryType; + + typedef GrCCPRTriangleProcessor INHERITED; +}; + +/** + * This pass fixes the corner pixels of a triangle. It erases the (incorrect) coverage that was + * written at the corners during the previous hull and edge passes, and then approximates the true + * coverage by sampling the triangle with horizontal lines. + */ +class GrCCPRTriangleCornerProcessor : public GrCCPRTriangleProcessor { +public: + GrCCPRTriangleCornerProcessor() + : INHERITED(CoverageType::kShader) + , fEdgeDistance(kVec3f_GrSLType) + , fDevCoord(kVec2f_GrSLType) + , fNeighbors(kVec4f_GrSLType) + , fEdgeDistances(kMat33f_GrSLType) + , fCornerIdx(kInt_GrSLType) {} + + void resetVaryings(GrGLSLVaryingHandler* varyingHandler) override { + this->INHERITED::resetVaryings(varyingHandler); + varyingHandler->addFlatVarying("edge_distance", &fEdgeDistance, kHigh_GrSLPrecision); + varyingHandler->addFlatVarying("devcoord", &fDevCoord, kHigh_GrSLPrecision); + varyingHandler->addFlatVarying("neighbors", &fNeighbors, kHigh_GrSLPrecision); + varyingHandler->addFlatVarying("edge_distances", &fEdgeDistances, kHigh_GrSLPrecision); + varyingHandler->addFlatVarying("corner_idx", &fCornerIdx, kLow_GrSLPrecision); + } + + void onEmitVertexShader(const GrCCPRCoverageProcessor&, GrGLSLVertexBuilder*, + const TexelBufferHandle& pointsBuffer, const char* atlasOffset, + const char* rtAdjust, GrGPArgs*) const override; + void onEmitGeometryShader(GrGLSLGeometryBuilder*, const char* emitVertexFn, const char* wind, + const char* rtAdjust) const override; + void emitPerVertexGeometryCode(SkString* fnBody, const char* position, const char* coverage, + const char* wind) const override; + void emitShaderCoverage(GrGLSLFragmentBuilder*, const char* outputCoverage) const override; + +private: + GrGLSLVertToGeo fEdgeDistance; + GrGLSLVertToGeo fDevCoord; + GrGLSLGeoToFrag fNeighbors; + GrGLSLGeoToFrag fEdgeDistances; + GrGLSLGeoToFrag fCornerIdx; + + typedef GrCCPRTriangleProcessor INHERITED; +}; + +#endif diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp b/src/gpu/ccpr/GrCoverageCountingPathRenderer.cpp new file mode 100644 index 0000000000..45fad1feb0 --- /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;) +} diff --git a/src/gpu/ccpr/GrCoverageCountingPathRenderer.h b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h new file mode 100644 index 0000000000..f55d0e1835 --- /dev/null +++ b/src/gpu/ccpr/GrCoverageCountingPathRenderer.h @@ -0,0 +1,135 @@ +/* + * Copyright 2017 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef GrCoverageCountingPathRenderer_DEFINED +#define GrCoverageCountingPathRenderer_DEFINED + +#include "GrAllocator.h" +#include "GrOnFlushResourceProvider.h" +#include "GrPathRenderer.h" +#include "SkTInternalLList.h" +#include "ccpr/GrCCPRAtlas.h" +#include "ccpr/GrCCPRCoverageOpsBuilder.h" +#include "ops/GrDrawOp.h" +#include <map> + +/** + * This is a path renderer that draws antialiased paths by counting coverage in an offscreen + * buffer. (See GrCCPRCoverageProcessor, GrCCPRPathProcessor) + * + * It also serves as the per-render-target tracker for pending path draws, and at the start of + * flush, it compiles GPU buffers and renders a "coverage count atlas" for the upcoming paths. + */ +class GrCoverageCountingPathRenderer + : public GrPathRenderer + , public GrOnFlushCallbackObject { + + struct RTPendingOps; + +public: + static bool IsSupported(const GrCaps&); + static sk_sp<GrCoverageCountingPathRenderer> CreateIfSupported(const GrCaps&); + + // GrPathRenderer overrides. + StencilSupport onGetStencilSupport(const GrShape&) const override { + return GrPathRenderer::kNoSupport_StencilSupport; + } + bool onCanDrawPath(const CanDrawPathArgs& args) const override; + bool onDrawPath(const DrawPathArgs&) final; + + // GrOnFlushCallbackObject overrides. + void preFlush(GrOnFlushResourceProvider*, const uint32_t* opListIDs, int numOpListIDs, + SkTArray<sk_sp<GrRenderTargetContext>>* results) override; + void postFlush() override; + + // This is the Op that ultimately draws a path into its final destination, using the atlas we + // generate at flush time. + class DrawPathsOp : public GrDrawOp { + public: + DEFINE_OP_CLASS_ID + SK_DECLARE_INTERNAL_LLIST_INTERFACE(DrawPathsOp); + + DrawPathsOp(GrCoverageCountingPathRenderer*, const DrawPathArgs&, GrColor); + + // GrDrawOp overrides. + const char* name() const override { return "GrCoverageCountingPathRenderer::DrawPathsOp"; } + FixedFunctionFlags fixedFunctionFlags() const override { return FixedFunctionFlags::kNone; } + RequiresDstTexture finalize(const GrCaps&, const GrAppliedClip*) override; + void wasRecorded(GrRenderTargetOpList*) override; + bool onCombineIfPossible(GrOp* other, const GrCaps& caps) override; + void onPrepare(GrOpFlushState*) override {} + void onExecute(GrOpFlushState*) override; + + private: + SkPath::FillType getFillType() const { + SkASSERT(fDebugInstanceCount >= 1); + return fHeadDraw.fPath.getFillType(); + } + + struct SingleDraw { + using ScissorMode = GrCCPRCoverageOpsBuilder::ScissorMode; + SkIRect fClipBounds; + ScissorMode fScissorMode; + SkMatrix fMatrix; + SkPath fPath; + GrColor fColor; + SingleDraw* fNext = nullptr; + }; + + SingleDraw& getOnlyPathDraw() { + SkASSERT(&fHeadDraw == fTailDraw); + SkASSERT(1 == fDebugInstanceCount); + return fHeadDraw; + } + + struct AtlasBatch { + const GrCCPRAtlas* fAtlas; + int fEndInstanceIdx; + }; + + void addAtlasBatch(const GrCCPRAtlas* atlas, int endInstanceIdx) { + SkASSERT(endInstanceIdx > fBaseInstance); + SkASSERT(fAtlasBatches.empty() || + endInstanceIdx > fAtlasBatches.back().fEndInstanceIdx); + fAtlasBatches.push_back() = {atlas, endInstanceIdx}; + } + + GrCoverageCountingPathRenderer* const fCCPR; + const uint32_t fSRGBFlags; + GrProcessorSet fProcessors; + SingleDraw fHeadDraw; + SingleDraw* fTailDraw; + RTPendingOps* fOwningRTPendingOps; + int fBaseInstance; + SkDEBUGCODE(int fDebugInstanceCount;) + SkSTArray<1, AtlasBatch, true> fAtlasBatches; + + friend class GrCoverageCountingPathRenderer; + + typedef GrDrawOp INHERITED; + }; + +private: + GrCoverageCountingPathRenderer() = default; + + struct RTPendingOps { + SkTInternalLList<DrawPathsOp> fOpList; + GrCCPRCoverageOpsBuilder::MaxBufferItems fMaxBufferItems; + GrSTAllocator<256, DrawPathsOp::SingleDraw> fDrawsAllocator; + }; + + // Map from render target ID to the individual render target's pending path ops. + std::map<uint32_t, RTPendingOps> fRTPendingOpsMap; + + sk_sp<GrBuffer> fPerFlushIndexBuffer; + sk_sp<GrBuffer> fPerFlushVertexBuffer; + sk_sp<GrBuffer> fPerFlushInstanceBuffer; + GrSTAllocator<4, GrCCPRAtlas> fPerFlushAtlases; + SkDEBUGCODE(bool fFlushing = false;) +}; + +#endif diff --git a/tools/flags/SkCommonFlagsPathRenderer.h b/tools/flags/SkCommonFlagsPathRenderer.h index 12755dcc06..ac293a1e79 100644 --- a/tools/flags/SkCommonFlagsPathRenderer.h +++ b/tools/flags/SkCommonFlagsPathRenderer.h @@ -40,6 +40,8 @@ inline GrContextOptions::GpuPathRenderers get_named_pathrenderers_flags(const ch return GpuPathRenderers::kAALinearizing; } else if (!strcmp(name, "small")) { return GpuPathRenderers::kSmall; + } else if (!strcmp(name, "ccpr")) { + return GpuPathRenderers::kCoverageCounting; } else if (!strcmp(name, "tess")) { return GpuPathRenderers::kTessellating; } else if (!strcmp(name, "grdefault")) { diff --git a/tools/viewer/Viewer.cpp b/tools/viewer/Viewer.cpp index 29e5b70cb8..6753b6b63e 100644 --- a/tools/viewer/Viewer.cpp +++ b/tools/viewer/Viewer.cpp @@ -37,6 +37,8 @@ #include "imgui.h" +#include "ccpr/GrCoverageCountingPathRenderer.h" + #include <stdlib.h> #include <map> @@ -262,6 +264,7 @@ Viewer::Viewer(int argc, char** argv, void* platformData) gPathRendererNames[GpuPathRenderers::kStencilAndCover] = "NV_path_rendering"; gPathRendererNames[GpuPathRenderers::kMSAA] = "Sample shading"; gPathRendererNames[GpuPathRenderers::kSmall] = "Small paths (cached sdf or alpha masks)"; + gPathRendererNames[GpuPathRenderers::kCoverageCounting] = "Coverage counting"; gPathRendererNames[GpuPathRenderers::kTessellating] = "Tessellating"; gPathRendererNames[GpuPathRenderers::kDefault] = "Original Ganesh path renderer"; gPathRendererNames[GpuPathRenderers::kNone] = "Software masks"; @@ -1069,6 +1072,9 @@ void Viewer::drawImGui(SkCanvas* canvas) { prButton(GpuPathRenderers::kNone); } else { prButton(GpuPathRenderers::kAll); + if (GrCoverageCountingPathRenderer::IsSupported(*ctx->caps())) { + prButton(GpuPathRenderers::kCoverageCounting); + } prButton(GpuPathRenderers::kSmall); prButton(GpuPathRenderers::kTessellating); prButton(GpuPathRenderers::kNone); @@ -1305,6 +1311,9 @@ void Viewer::updateUIState() { prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]); } else { prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kAll]); + if (GrCoverageCountingPathRenderer::IsSupported(*ctx->caps())) { + prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kCoverageCounting]); + } prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kSmall]); prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kTessellating]); prState[kOptions].append(gPathRendererNames[GpuPathRenderers::kNone]); |