| /* |
| * Copyright 2019 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "include/core/SkCanvas.h" |
| #include "src/core/SkPathPriv.h" |
| #include "tools/viewer/ClickHandlerSlide.h" |
| |
| #if defined(SK_GANESH) |
| |
| #include "include/core/SkFont.h" |
| #include "src/core/SkCanvasPriv.h" |
| #include "src/gpu/ganesh/GrCanvas.h" |
| #include "src/gpu/ganesh/GrCaps.h" |
| #include "src/gpu/ganesh/GrOpFlushState.h" |
| #include "src/gpu/ganesh/GrRecordingContextPriv.h" |
| #include "src/gpu/ganesh/SurfaceDrawContext.h" |
| #include "src/gpu/ganesh/ops/GrDrawOp.h" |
| #include "src/gpu/ganesh/ops/GrSimpleMeshDrawOpHelper.h" |
| #include "src/gpu/ganesh/ops/TessellationPathRenderer.h" |
| #include "src/gpu/ganesh/tessellate/GrPathTessellationShader.h" |
| #include "src/gpu/ganesh/tessellate/PathTessellator.h" |
| #include "src/gpu/tessellate/AffineMatrix.h" |
| #include "src/gpu/tessellate/MiddleOutPolygonTriangulator.h" |
| #include "tools/fonts/FontToolUtils.h" |
| |
| namespace skgpu::ganesh { |
| |
| namespace { |
| |
| enum class Mode { |
| kWedgeMiddleOut, |
| kCurveMiddleOut |
| }; |
| |
| static const char* ModeName(Mode mode) { |
| switch (mode) { |
| case Mode::kWedgeMiddleOut: |
| return "MiddleOutShader (kWedges)"; |
| case Mode::kCurveMiddleOut: |
| return "MiddleOutShader (kCurves)"; |
| } |
| SkUNREACHABLE; |
| } |
| |
| // Draws a path directly to the screen using a specific tessellator. |
| class SamplePathTessellatorOp : public GrDrawOp { |
| private: |
| DEFINE_OP_CLASS_ID |
| |
| SamplePathTessellatorOp(const SkRect& drawBounds, const SkPath& path, const SkMatrix& m, |
| GrPipeline::InputFlags pipelineFlags, Mode mode) |
| : GrDrawOp(ClassID()) |
| , fPath(path) |
| , fMatrix(m) |
| , fPipelineFlags(pipelineFlags) |
| , fMode(mode) { |
| this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo); |
| } |
| const char* name() const override { return "SamplePathTessellatorOp"; } |
| void visitProxies(const GrVisitProxyFunc&) const override {} |
| FixedFunctionFlags fixedFunctionFlags() const override { |
| return FixedFunctionFlags::kUsesHWAA; |
| } |
| GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, |
| GrClampType clampType) override { |
| SkPMColor4f color; |
| return fProcessors.finalize(SK_PMColor4fWHITE, GrProcessorAnalysisCoverage::kNone, clip, |
| nullptr, caps, clampType, &color); |
| } |
| void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*, |
| const GrDstProxyView&, GrXferBarrierFlags, GrLoadOp colorLoadOp) override {} |
| void onPrepare(GrOpFlushState* flushState) override { |
| constexpr static SkPMColor4f kCyan = {0,1,1,1}; |
| auto alloc = flushState->allocator(); |
| const SkMatrix& shaderMatrix = SkMatrix::I(); |
| const SkMatrix& pathMatrix = fMatrix; |
| const GrCaps& caps = flushState->caps(); |
| const GrShaderCaps& shaderCaps = *caps.shaderCaps(); |
| |
| PathTessellator::PathDrawList pathList{pathMatrix, fPath, kCyan}; |
| if (fMode == Mode::kCurveMiddleOut) { |
| #if !defined(SK_ENABLE_OPTIMIZE_SIZE) |
| // This emulates what PathStencilCoverOp does when using curves, except we include the |
| // middle-out triangles directly in the written patches for convenience (normally they |
| // use a simple triangle pipeline). But PathCurveTessellator only knows how to read |
| // extra triangles from BreadcrumbTriangleList, so build on from the middle-out stack. |
| SkArenaAlloc storage{256}; |
| GrInnerFanTriangulator::BreadcrumbTriangleList triangles; |
| for (tess::PathMiddleOutFanIter it(fPath); !it.done();) { |
| for (auto [p0, p1, p2] : it.nextStack()) { |
| triangles.append(&storage, |
| pathMatrix.mapPoint(p0), |
| pathMatrix.mapPoint(p1), |
| pathMatrix.mapPoint(p2), |
| /*winding=*/1); |
| } |
| } |
| |
| auto* tess = PathCurveTessellator::Make(alloc, shaderCaps.fInfinitySupport); |
| tess->prepareWithTriangles(flushState, shaderMatrix, &triangles, pathList, |
| fPath.countVerbs()); |
| fTessellator = tess; |
| #else |
| auto* tess = PathCurveTessellator::Make(alloc, shaderCaps.fInfinitySupport); |
| tess->prepareWithTriangles(flushState, shaderMatrix, nullptr, pathList, |
| fPath.countVerbs()); |
| fTessellator = tess; |
| #endif |
| } else { |
| // This emulates what PathStencilCoverOp does when using wedges. |
| fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.fInfinitySupport); |
| fTessellator->prepare(flushState, shaderMatrix, pathList, fPath.countVerbs()); |
| } |
| |
| auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors), |
| fPipelineFlags); |
| auto* tessShader = GrPathTessellationShader::Make(*caps.shaderCaps(), |
| alloc, |
| shaderMatrix, |
| kCyan, |
| fTessellator->patchAttribs()); |
| fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(), |
| flushState->usesMSAASurface(), |
| &flushState->dstProxyView(), |
| flushState->renderPassBarriers(), |
| GrLoadOp::kClear, &flushState->caps()}, |
| tessShader, |
| pipeline, |
| &GrUserStencilSettings::kUnused); |
| } |
| |
| void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { |
| flushState->bindPipeline(*fProgram, chainBounds); |
| fTessellator->draw(flushState); |
| } |
| |
| const SkPath fPath; |
| const SkMatrix fMatrix; |
| const GrPipeline::InputFlags fPipelineFlags; |
| const Mode fMode; |
| PathTessellator* fTessellator = nullptr; |
| GrProgramInfo* fProgram; |
| GrProcessorSet fProcessors{SkBlendMode::kSrcOver}; |
| |
| friend class GrOp; // For ctor. |
| }; |
| |
| } // namespace |
| |
| // This slide enables wireframe and visualizes the triangles generated by path tessellators. |
| class PathTessellatorsSlide : public ClickHandlerSlide { |
| public: |
| PathTessellatorsSlide() { |
| #if 0 |
| // For viewing middle-out triangulations of the inner fan. |
| fPath.moveTo(1, 0); |
| int numSides = 32 * 3; |
| for (int i = 1; i < numSides; ++i) { |
| float theta = 2*3.1415926535897932384626433832785 * i / numSides; |
| fPath.lineTo(std::cos(theta), std::sin(theta)); |
| } |
| fPath.transform(SkMatrix::Scale(200, 200)); |
| fPath.transform(SkMatrix::Translate(300, 300)); |
| #else |
| fPath.moveTo(100, 500); |
| fPath.cubicTo(300, 400, -100, 300, 100, 200); |
| fPath.quadTo(250, 0, 400, 200); |
| fPath.conicTo(600, 350, 400, 500, fConicWeight); |
| fPath.close(); |
| #endif |
| fName = "PathTessellators"; |
| } |
| |
| bool onChar(SkUnichar) override; |
| |
| void draw(SkCanvas*) override; |
| |
| protected: |
| Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override; |
| bool onClick(Click*) override; |
| |
| SkPath fPath; |
| GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kWireframe; |
| Mode fMode = Mode::kWedgeMiddleOut; |
| |
| float fConicWeight = .5; |
| |
| class Click; |
| }; |
| |
| void PathTessellatorsSlide::draw(SkCanvas* canvas) { |
| canvas->clear(SK_ColorBLACK); |
| |
| auto ctx = canvas->recordingContext(); |
| auto sdc = skgpu::ganesh::TopDeviceSurfaceDrawContext(canvas); |
| |
| SkString error; |
| if (!sdc || !ctx) { |
| error = "GPU Only."; |
| } else if (!skgpu::ganesh::TessellationPathRenderer::IsSupported(*ctx->priv().caps())) { |
| error = "TessellationPathRenderer not supported."; |
| } |
| if (!error.isEmpty()) { |
| canvas->clear(SK_ColorRED); |
| SkFont font(ToolUtils::DefaultTypeface(), 20); |
| SkPaint captionPaint; |
| captionPaint.setColor(SK_ColorWHITE); |
| canvas->drawString(error.c_str(), 10, 30, font, captionPaint); |
| return; |
| } |
| |
| sdc->addDrawOp(GrOp::Make<SamplePathTessellatorOp>(ctx, |
| sdc->asRenderTargetProxy()->getBoundsRect(), |
| fPath, canvas->getTotalMatrix(), |
| fPipelineFlags, fMode)); |
| |
| // Draw the path points. |
| SkPaint pointsPaint; |
| pointsPaint.setColor(SK_ColorBLUE); |
| pointsPaint.setStrokeWidth(8); |
| SkPath devPath = fPath; |
| devPath.transform(canvas->getTotalMatrix()); |
| { |
| SkAutoCanvasRestore acr(canvas, true); |
| canvas->setMatrix(SkMatrix::I()); |
| SkString caption(ModeName(fMode)); |
| caption.appendf(" (w=%g)", fConicWeight); |
| SkFont font(ToolUtils::DefaultTypeface(), 20); |
| SkPaint captionPaint; |
| captionPaint.setColor(SK_ColorWHITE); |
| canvas->drawString(caption, 10, 30, font, captionPaint); |
| canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(), |
| SkPathPriv::PointData(devPath), pointsPaint); |
| } |
| } |
| |
| class PathTessellatorsSlide::Click : public ClickHandlerSlide::Click { |
| public: |
| Click(int ptIdx) : fPtIdx(ptIdx) {} |
| |
| void doClick(SkPath* path) { |
| SkPoint pt = path->getPoint(fPtIdx); |
| SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev); |
| } |
| |
| private: |
| int fPtIdx; |
| }; |
| |
| ClickHandlerSlide::Click* PathTessellatorsSlide::onFindClickHandler(SkScalar x, SkScalar y, |
| skui::ModifierKey) { |
| const SkPoint* pts = SkPathPriv::PointData(fPath); |
| float fuzz = 30; |
| for (int i = 0; i < fPath.countPoints(); ++i) { |
| if (fabs(x - pts[i].x()) < fuzz && fabsf(y - pts[i].y()) < fuzz) { |
| return new Click(i); |
| } |
| } |
| return nullptr; |
| } |
| |
| bool PathTessellatorsSlide::onClick(ClickHandlerSlide::Click* click) { |
| Click* myClick = (Click*)click; |
| myClick->doClick(&fPath); |
| return true; |
| } |
| |
| static SkPath update_weight(const SkPath& path, float w) { |
| SkPath path_; |
| for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) { |
| switch (verb) { |
| case SkPathVerb::kMove: |
| path_.moveTo(pts[0]); |
| break; |
| case SkPathVerb::kLine: |
| path_.lineTo(pts[1]); |
| break; |
| case SkPathVerb::kQuad: |
| path_.quadTo(pts[1], pts[2]); |
| break; |
| case SkPathVerb::kCubic: |
| path_.cubicTo(pts[1], pts[2], pts[3]); |
| break; |
| case SkPathVerb::kConic: |
| path_.conicTo(pts[1], pts[2], (w != 1) ? w : .99f); |
| break; |
| case SkPathVerb::kClose: |
| break; |
| } |
| } |
| return path_; |
| } |
| |
| bool PathTessellatorsSlide::onChar(SkUnichar unichar) { |
| switch (unichar) { |
| case 'w': |
| fPipelineFlags = (GrPipeline::InputFlags)( |
| (int)fPipelineFlags ^ (int)GrPipeline::InputFlags::kWireframe); |
| return true; |
| case 'D': { |
| fPath.dump(); |
| return true; |
| } |
| case '+': |
| fConicWeight *= 2; |
| fPath = update_weight(fPath, fConicWeight); |
| return true; |
| case '=': |
| fConicWeight *= 5/4.f; |
| fPath = update_weight(fPath, fConicWeight); |
| return true; |
| case '_': |
| fConicWeight *= .5f; |
| fPath = update_weight(fPath, fConicWeight); |
| return true; |
| case '-': |
| fConicWeight *= 4/5.f; |
| fPath = update_weight(fPath, fConicWeight); |
| return true; |
| case '1': |
| case '2': |
| fMode = (Mode)(unichar - '1'); |
| return true; |
| } |
| return false; |
| } |
| |
| DEF_SLIDE( return new PathTessellatorsSlide; ) |
| |
| } // namespace skgpu::ganesh |
| |
| #endif // defined(SK_GANESH) |