1/* 2 * Copyright 2019 Google LLC. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "include/core/SkCanvas.h" 9#include "samplecode/Sample.h" 10#include "src/core/SkPathPriv.h" 11 12#if SK_SUPPORT_GPU 13 14#include "src/core/SkCanvasPriv.h" 15#include "src/gpu/GrOpFlushState.h" 16#include "src/gpu/GrRecordingContextPriv.h" 17#include "src/gpu/ops/GrDrawOp.h" 18#include "src/gpu/ops/GrSimpleMeshDrawOpHelper.h" 19#include "src/gpu/ops/TessellationPathRenderer.h" 20#include "src/gpu/tessellate/AffineMatrix.h" 21#include "src/gpu/tessellate/PathCurveTessellator.h" 22#include "src/gpu/tessellate/PathWedgeTessellator.h" 23#include "src/gpu/tessellate/shaders/GrPathTessellationShader.h" 24#include "src/gpu/v1/SurfaceDrawContext_v1.h" 25 26namespace skgpu { 27 28using TrianglePatch = PatchWriter::TrianglePatch; 29 30namespace { 31 32enum class Mode { 33 kWedgeMiddleOut, 34 kCurveMiddleOut, 35 kWedgeTessellate, 36 kCurveTessellate 37}; 38 39static const char* ModeName(Mode mode) { 40 switch (mode) { 41 case Mode::kWedgeMiddleOut: 42 return "MiddleOutShader (kWedges)"; 43 case Mode::kCurveMiddleOut: 44 return "MiddleOutShader (kCurves)"; 45 case Mode::kWedgeTessellate: 46 return "HardwareWedgeShader"; 47 case Mode::kCurveTessellate: 48 return "HardwareCurveShader"; 49 } 50 SkUNREACHABLE; 51} 52 53// Draws a path directly to the screen using a specific tessellator. 54class SamplePathTessellatorOp : public GrDrawOp { 55private: 56 DEFINE_OP_CLASS_ID 57 58 SamplePathTessellatorOp(const SkRect& drawBounds, const SkPath& path, const SkMatrix& m, 59 GrPipeline::InputFlags pipelineFlags, Mode mode) 60 : GrDrawOp(ClassID()) 61 , fPath(path) 62 , fMatrix(m) 63 , fPipelineFlags(pipelineFlags) 64 , fMode(mode) { 65 this->setBounds(drawBounds, HasAABloat::kNo, IsHairline::kNo); 66 } 67 const char* name() const override { return "SamplePathTessellatorOp"; } 68 void visitProxies(const GrVisitProxyFunc&) const override {} 69 FixedFunctionFlags fixedFunctionFlags() const override { 70 return FixedFunctionFlags::kUsesHWAA; 71 } 72 GrProcessorSet::Analysis finalize(const GrCaps& caps, const GrAppliedClip* clip, 73 GrClampType clampType) override { 74 SkPMColor4f color; 75 return fProcessors.finalize(SK_PMColor4fWHITE, GrProcessorAnalysisCoverage::kNone, clip, 76 nullptr, caps, clampType, &color); 77 } 78 void onPrePrepare(GrRecordingContext*, const GrSurfaceProxyView&, GrAppliedClip*, 79 const GrDstProxyView&, GrXferBarrierFlags, GrLoadOp colorLoadOp) override {} 80 void onPrepare(GrOpFlushState* flushState) override { 81 constexpr static SkPMColor4f kCyan = {0,1,1,1}; 82 auto alloc = flushState->allocator(); 83 const SkMatrix& shaderMatrix = SkMatrix::I(); 84 const SkMatrix& pathMatrix = fMatrix; 85 const GrCaps& caps = flushState->caps(); 86 const GrShaderCaps& shaderCaps = *caps.shaderCaps(); 87 int numVerbsToGetMiddleOut = 0; 88 int numVerbsToGetTessellation = caps.minPathVerbsForHwTessellation(); 89 auto pipeline = GrSimpleMeshDrawOpHelper::CreatePipeline(flushState, std::move(fProcessors), 90 fPipelineFlags); 91 int numVerbs; 92 bool needsInnerFan; 93 switch (fMode) { 94 case Mode::kWedgeMiddleOut: 95 fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport()); 96 numVerbs = numVerbsToGetMiddleOut; 97 needsInnerFan = false; 98 break; 99 case Mode::kCurveMiddleOut: 100 fTessellator = PathCurveTessellator::Make(alloc, 101 shaderCaps.infinitySupport()); 102 numVerbs = numVerbsToGetMiddleOut; 103 needsInnerFan = true; 104 break; 105 case Mode::kWedgeTessellate: 106 fTessellator = PathWedgeTessellator::Make(alloc, shaderCaps.infinitySupport()); 107 numVerbs = numVerbsToGetTessellation; 108 needsInnerFan = false; 109 break; 110 case Mode::kCurveTessellate: 111 fTessellator = PathCurveTessellator::Make(alloc, 112 shaderCaps.infinitySupport()); 113 numVerbs = numVerbsToGetTessellation; 114 needsInnerFan = true; 115 break; 116 } 117 auto* tessShader = GrPathTessellationShader::Make(alloc, 118 shaderMatrix, 119 kCyan, 120 numVerbs, 121 *pipeline, 122 fTessellator->patchAttribs(), 123 caps); 124 fProgram = GrTessellationShader::MakeProgram({alloc, flushState->writeView(), 125 flushState->usesMSAASurface(), 126 &flushState->dstProxyView(), 127 flushState->renderPassBarriers(), 128 GrLoadOp::kClear, &flushState->caps()}, 129 tessShader, 130 pipeline, 131 &GrUserStencilSettings::kUnused); 132 133 134 int patchPreallocCount = fTessellator->patchPreallocCount(fPath.countVerbs()); 135 if (needsInnerFan) { 136 patchPreallocCount += fPath.countVerbs() - 1; 137 } 138 PatchWriter patchWriter(flushState, fTessellator, patchPreallocCount); 139 140 if (needsInnerFan) { 141 // Write out inner fan triangles. 142 AffineMatrix m(pathMatrix); 143 for (PathMiddleOutFanIter it(fPath); !it.done();) { 144 for (auto [p0, p1, p2] : it.nextStack()) { 145 TrianglePatch(patchWriter) << m.map2Points(p0, p1) << m.mapPoint(p2); 146 } 147 } 148 } 149 150 // Write out the curves. 151 fTessellator->writePatches(patchWriter, 152 tessShader->maxTessellationSegments(*caps.shaderCaps()), 153 shaderMatrix, 154 {pathMatrix, fPath, kCyan}); 155 156 if (!tessShader->willUseTessellationShaders()) { 157 fTessellator->prepareFixedCountBuffers(flushState); 158 } 159 160 } 161 void onExecute(GrOpFlushState* flushState, const SkRect& chainBounds) override { 162 flushState->bindPipeline(*fProgram, chainBounds); 163 fTessellator->draw(flushState, fProgram->geomProc().willUseTessellationShaders()); 164 } 165 166 const SkPath fPath; 167 const SkMatrix fMatrix; 168 const GrPipeline::InputFlags fPipelineFlags; 169 const Mode fMode; 170 PathTessellator* fTessellator = nullptr; 171 GrProgramInfo* fProgram; 172 GrProcessorSet fProcessors{SkBlendMode::kSrcOver}; 173 174 friend class GrOp; // For ctor. 175}; 176 177} // namespace 178 179// This sample enables wireframe and visualizes the triangles generated by path tessellators. 180class SamplePathTessellators : public Sample { 181public: 182 SamplePathTessellators() { 183#if 0 184 // For viewing middle-out triangulations of the inner fan. 185 fPath.moveTo(1, 0); 186 int numSides = 32 * 3; 187 for (int i = 1; i < numSides; ++i) { 188 float theta = 2*3.1415926535897932384626433832785 * i / numSides; 189 fPath.lineTo(std::cos(theta), std::sin(theta)); 190 } 191 fPath.transform(SkMatrix::Scale(200, 200)); 192 fPath.transform(SkMatrix::Translate(300, 300)); 193#else 194 fPath.moveTo(100, 500); 195 fPath.cubicTo(300, 400, -100, 300, 100, 200); 196 fPath.quadTo(250, 0, 400, 200); 197 fPath.conicTo(600, 350, 400, 500, fConicWeight); 198 fPath.close(); 199#endif 200 } 201 202private: 203 void onDrawContent(SkCanvas*) override; 204 Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override; 205 bool onClick(Sample::Click*) override; 206 bool onChar(SkUnichar) override; 207 208 SkString name() override { return SkString("PathTessellators"); } 209 210 SkPath fPath; 211 GrPipeline::InputFlags fPipelineFlags = GrPipeline::InputFlags::kWireframe; 212 Mode fMode = Mode::kWedgeMiddleOut; 213 214 float fConicWeight = .5; 215 216 class Click; 217}; 218 219void SamplePathTessellators::onDrawContent(SkCanvas* canvas) { 220 canvas->clear(SK_ColorBLACK); 221 222 auto ctx = canvas->recordingContext(); 223 auto sdc = SkCanvasPriv::TopDeviceSurfaceDrawContext(canvas); 224 225 SkString error; 226 if (!sdc || !ctx) { 227 error = "GPU Only."; 228 } else if (!skgpu::v1::TessellationPathRenderer::IsSupported(*ctx->priv().caps())) { 229 error = "TessellationPathRenderer not supported."; 230 } else if (fMode >= Mode::kWedgeTessellate && 231 !ctx->priv().caps()->shaderCaps()->tessellationSupport()) { 232 error.printf("%s requires hardware tessellation support.", ModeName(fMode)); 233 } 234 if (!error.isEmpty()) { 235 canvas->clear(SK_ColorRED); 236 SkFont font(nullptr, 20); 237 SkPaint captionPaint; 238 captionPaint.setColor(SK_ColorWHITE); 239 canvas->drawString(error.c_str(), 10, 30, font, captionPaint); 240 return; 241 } 242 243 sdc->addDrawOp(GrOp::Make<SamplePathTessellatorOp>(ctx, 244 sdc->asRenderTargetProxy()->getBoundsRect(), 245 fPath, canvas->getTotalMatrix(), 246 fPipelineFlags, fMode)); 247 248 // Draw the path points. 249 SkPaint pointsPaint; 250 pointsPaint.setColor(SK_ColorBLUE); 251 pointsPaint.setStrokeWidth(8); 252 SkPath devPath = fPath; 253 devPath.transform(canvas->getTotalMatrix()); 254 { 255 SkAutoCanvasRestore acr(canvas, true); 256 canvas->setMatrix(SkMatrix::I()); 257 SkString caption(ModeName(fMode)); 258 caption.appendf(" (w=%g)", fConicWeight); 259 SkFont font(nullptr, 20); 260 SkPaint captionPaint; 261 captionPaint.setColor(SK_ColorWHITE); 262 canvas->drawString(caption, 10, 30, font, captionPaint); 263 canvas->drawPoints(SkCanvas::kPoints_PointMode, devPath.countPoints(), 264 SkPathPriv::PointData(devPath), pointsPaint); 265 } 266} 267 268class SamplePathTessellators::Click : public Sample::Click { 269public: 270 Click(int ptIdx) : fPtIdx(ptIdx) {} 271 272 void doClick(SkPath* path) { 273 SkPoint pt = path->getPoint(fPtIdx); 274 SkPathPriv::UpdatePathPoint(path, fPtIdx, pt + fCurr - fPrev); 275 } 276 277private: 278 int fPtIdx; 279}; 280 281Sample::Click* SamplePathTessellators::onFindClickHandler(SkScalar x, SkScalar y, 282 skui::ModifierKey) { 283 const SkPoint* pts = SkPathPriv::PointData(fPath); 284 float fuzz = 30; 285 for (int i = 0; i < fPath.countPoints(); ++i) { 286 if (fabs(x - pts[i].x()) < fuzz && fabsf(y - pts[i].y()) < fuzz) { 287 return new Click(i); 288 } 289 } 290 return nullptr; 291} 292 293bool SamplePathTessellators::onClick(Sample::Click* click) { 294 Click* myClick = (Click*)click; 295 myClick->doClick(&fPath); 296 return true; 297} 298 299static SkPath update_weight(const SkPath& path, float w) { 300 SkPath path_; 301 for (auto [verb, pts, _] : SkPathPriv::Iterate(path)) { 302 switch (verb) { 303 case SkPathVerb::kMove: 304 path_.moveTo(pts[0]); 305 break; 306 case SkPathVerb::kLine: 307 path_.lineTo(pts[1]); 308 break; 309 case SkPathVerb::kQuad: 310 path_.quadTo(pts[1], pts[2]); 311 break; 312 case SkPathVerb::kCubic: 313 path_.cubicTo(pts[1], pts[2], pts[3]); 314 break; 315 case SkPathVerb::kConic: 316 path_.conicTo(pts[1], pts[2], (w != 1) ? w : .99f); 317 break; 318 case SkPathVerb::kClose: 319 break; 320 } 321 } 322 return path_; 323} 324 325bool SamplePathTessellators::onChar(SkUnichar unichar) { 326 switch (unichar) { 327 case 'w': 328 fPipelineFlags = (GrPipeline::InputFlags)( 329 (int)fPipelineFlags ^ (int)GrPipeline::InputFlags::kWireframe); 330 return true; 331 case 'D': { 332 fPath.dump(); 333 return true; 334 } 335 case '+': 336 fConicWeight *= 2; 337 fPath = update_weight(fPath, fConicWeight); 338 return true; 339 case '=': 340 fConicWeight *= 5/4.f; 341 fPath = update_weight(fPath, fConicWeight); 342 return true; 343 case '_': 344 fConicWeight *= .5f; 345 fPath = update_weight(fPath, fConicWeight); 346 return true; 347 case '-': 348 fConicWeight *= 4/5.f; 349 fPath = update_weight(fPath, fConicWeight); 350 return true; 351 case '1': 352 case '2': 353 case '3': 354 case '4': 355 fMode = (Mode)(unichar - '1'); 356 return true; 357 } 358 return false; 359} 360 361Sample* MakeTessellatedPathSample() { return new SamplePathTessellators; } 362static SampleRegistry gTessellatedPathSample(MakeTessellatedPathSample); 363 364} // namespace skgpu 365 366#endif // SK_SUPPORT_GPU 367