|  | /* | 
|  | * 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 "samplecode/Sample.h" | 
|  | #include "src/core/SkPathPriv.h" | 
|  |  | 
|  | #if SK_SUPPORT_GPU | 
|  |  | 
|  | #include "src/core/SkCanvasPriv.h" | 
|  | #include "src/gpu/GrOpFlushState.h" | 
|  | #include "src/gpu/GrRecordingContextPriv.h" | 
|  | #include "src/gpu/ops/GrDrawOp.h" | 
|  | #include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" | 
|  | #include "src/gpu/ops/TessellationPathRenderer.h" | 
|  | #include "src/gpu/tessellate/GrPathCurveTessellator.h" | 
|  | #include "src/gpu/tessellate/GrPathWedgeTessellator.h" | 
|  | #include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" | 
|  | #include "src/gpu/v1/SurfaceDrawContext_v1.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | enum class Mode { | 
|  | kWedgeMiddleOut, | 
|  | kCurveMiddleOut, | 
|  | kWedgeTessellate, | 
|  | kCurveTessellate | 
|  | }; | 
|  |  | 
|  | static const char* ModeName(Mode mode) { | 
|  | switch (mode) { | 
|  | case Mode::kWedgeMiddleOut: | 
|  | return "MiddleOutShader (kWedges)"; | 
|  | case Mode::kCurveMiddleOut: | 
|  | return "MiddleOutShader (kCurves)"; | 
|  | case Mode::kWedgeTessellate: | 
|  | return "HardwareWedgeShader"; | 
|  | case Mode::kCurveTessellate: | 
|  | return "HardwareCurveShader"; | 
|  | } | 
|  | 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(); | 
|  | int numVerbsToGetMiddleOut = 0; | 
|  | int numVerbsToGetTessellation = caps.minPathVerbsForHwTessellation(); | 
|  | auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors), | 
|  | fPipelineFlags); | 
|  | switch (fMode) { | 
|  | using DrawInnerFan = GrPathCurveTessellator::DrawInnerFan; | 
|  | case Mode::kWedgeMiddleOut: | 
|  | fTessellator = GrPathWedgeTessellator::Make(alloc, shaderMatrix, kCyan, | 
|  | numVerbsToGetMiddleOut, *pipeline, | 
|  | caps); | 
|  | break; | 
|  | case Mode::kCurveMiddleOut: | 
|  | fTessellator = GrPathCurveTessellator::Make(alloc, shaderMatrix, kCyan, | 
|  | DrawInnerFan::kYes, | 
|  | numVerbsToGetMiddleOut, *pipeline, | 
|  | caps); | 
|  | break; | 
|  | case Mode::kWedgeTessellate: | 
|  | fTessellator = GrPathWedgeTessellator::Make(alloc, shaderMatrix, kCyan, | 
|  | numVerbsToGetTessellation, *pipeline, | 
|  | caps); | 
|  | break; | 
|  | case Mode::kCurveTessellate: | 
|  | fTessellator = GrPathCurveTessellator::Make(alloc, shaderMatrix, kCyan, | 
|  | DrawInnerFan::kYes, | 
|  | numVerbsToGetTessellation, *pipeline, | 
|  | caps); | 
|  | break; | 
|  | } | 
|  | fTessellator->prepare(flushState, this->bounds(), {pathMatrix, fPath}, fPath.countVerbs()); | 
|  | fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(), | 
|  | flushState->usesMSAASurface(), | 
|  | &flushState->dstProxyView(), | 
|  | flushState->renderPassBarriers(), | 
|  | GrLoadOp::kClear, &flushState->caps()}, | 
|  | fTessellator->shader(), 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; | 
|  | GrPathTessellator* fTessellator = nullptr; | 
|  | GrProgramInfo* fProgram; | 
|  | GrProcessorSet fProcessors{SkBlendMode::kSrcOver}; | 
|  |  | 
|  | friend class GrOp;  // For ctor. | 
|  | }; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | // This sample enables wireframe and visualizes the triangles generated by path tessellators. | 
|  | class SamplePathTessellators : public Sample { | 
|  | public: | 
|  | SamplePathTessellators() { | 
|  | #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 | 
|  | } | 
|  |  | 
|  | private: | 
|  | void onDrawContent(SkCanvas*) override; | 
|  | Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override; | 
|  | bool onClick(Sample::Click*) override; | 
|  | bool onChar(SkUnichar) override; | 
|  |  | 
|  | SkString name() override { return SkString("PathTessellators"); } | 
|  |  | 
|  | SkPath fPath; | 
|  | GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kWireframe; | 
|  | Mode fMode = Mode::kWedgeMiddleOut; | 
|  |  | 
|  | float fConicWeight = .5; | 
|  |  | 
|  | class Click; | 
|  | }; | 
|  |  | 
|  | void SamplePathTessellators::onDrawContent(SkCanvas* canvas) { | 
|  | canvas->clear(SK_ColorBLACK); | 
|  |  | 
|  | auto ctx = canvas->recordingContext(); | 
|  | auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas); | 
|  |  | 
|  | SkString error; | 
|  | if (!sdc || !ctx) { | 
|  | error = "GPU Only."; | 
|  | } else if (!skgpu::v1::TessellationPathRenderer::IsSupported(*ctx->priv().caps())) { | 
|  | error = "TessellationPathRenderer not supported."; | 
|  | } else if (fMode >= Mode::kWedgeTessellate && | 
|  | !ctx->priv().caps()->shaderCaps()->tessellationSupport()) { | 
|  | error.printf("%s requires hardware tessellation support.", ModeName(fMode)); | 
|  | } | 
|  | if (!error.isEmpty()) { | 
|  | canvas->clear(SK_ColorRED); | 
|  | SkFont font(nullptr, 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(nullptr, 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 SamplePathTessellators::Click : public Sample::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; | 
|  | }; | 
|  |  | 
|  | Sample::Click* SamplePathTessellators::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 SamplePathTessellators::onClick(Sample::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 SamplePathTessellators::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': | 
|  | case '3': | 
|  | case '4': | 
|  | fMode = (Mode)(unichar - '1'); | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; } | 
|  | static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample); | 
|  |  | 
|  | #endif  // SK_SUPPORT_GPU |