text: /*
   * Copyright 2018 Google Inc.
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "src/gpu/ccpr/GrCCStroker.h"
  
  #include "include/core/SkStrokeRec.h"
  #include "src/core/SkPathPriv.h"
  #include "src/gpu/GrOnFlushResourceProvider.h"
  #include "src/gpu/GrOpsRenderPass.h"
  #include "src/gpu/GrProgramInfo.h"
  #include "src/gpu/ccpr/GrCCCoverageProcessor.h"
  #include "src/gpu/glsl/GrGLSLFragmentShaderBuilder.h"
  #include "src/gpu/glsl/GrGLSLVertexGeoBuilder.h"
  
  static constexpr int kMaxNumLinearSegmentsLog2 = GrCCStrokeGeometry::kMaxNumLinearSegmentsLog2;
  using TriangleInstance = GrCCCoverageProcessor::TriPointInstance;
  using ConicInstance = GrCCCoverageProcessor::QuadPointInstance;
  
  namespace {
  
  struct LinearStrokeInstance {
      float fEndpoints[4];
      float fStrokeRadius;
  
      inline void set(const SkPoint[2], float dx, float dy, float strokeRadius);
  };
  
  inline void LinearStrokeInstance::set(const SkPoint P[2], float dx, float dy, float strokeRadius) {
      Sk2f X, Y;
      Sk2f::Load2(P, &X, &Y);
      Sk2f::Store2(fEndpoints, X + dx, Y + dy);
      fStrokeRadius = strokeRadius;
  }
  
  struct CubicStrokeInstance {
      float fX[4];
      float fY[4];
      float fStrokeRadius;
      float fNumSegments;
  
      inline void set(const SkPoint[4], float dx, float dy, float strokeRadius, int numSegments);
      inline void set(const Sk4f& X, const Sk4f& Y, float dx, float dy, float strokeRadius,
                      int numSegments);
  };
  
  inline void CubicStrokeInstance::set(const SkPoint P[4], float dx, float dy, float strokeRadius,
                                       int numSegments) {
      Sk4f X, Y;
      Sk4f::Load2(P, &X, &Y);
      this->set(X, Y, dx, dy, strokeRadius, numSegments);
  }
  
  inline void CubicStrokeInstance::set(const Sk4f& X, const Sk4f& Y, float dx, float dy,
                                       float strokeRadius, int numSegments) {
      (X + dx).store(&fX);
      (Y + dy).store(&fY);
      fStrokeRadius = strokeRadius;
      fNumSegments = static_cast<float>(numSegments);
  }
  
  // This class draws stroked lines in post-transform device space (a.k.a. rectangles). Rigid-body
  // transforms can be achieved by transforming the line ahead of time and adjusting the stroke
  // width. Skews of the stroke itself are not yet supported.
  //
  // Corner coverage is AA-correct, meaning, n^2 attenuation along the diagonals. This is important
  // for seamless integration with the connecting geometry.
  class LinearStrokeProcessor : public GrGeometryProcessor {
  public:
      LinearStrokeProcessor() : INHERITED(kLinearStrokeProcessor_ClassID) {
          this->setInstanceAttributes(kInstanceAttribs, 2);
  #ifdef SK_DEBUG
          using Instance = LinearStrokeInstance;
          SkASSERT(this->instanceStride() == sizeof(Instance));
  #endif
      }
  
  private:
      const char* name() const override { return "LinearStrokeProcessor"; }
      void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
  
      static constexpr Attribute kInstanceAttribs[2] = {
              {"endpts", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
              {"stroke_radius", kFloat_GrVertexAttribType, kFloat_GrSLType}
      };
  
      class Impl : public GrGLSLGeometryProcessor {
          void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
                       FPCoordTransformIter&&) override {}
          void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
      };
  
      GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
          return new Impl();
      }
  
      typedef GrGeometryProcessor INHERITED;
  };
  
  void LinearStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
      GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
      GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
  
      varyingHandler->emitAttributes(args.fGP.cast<LinearStrokeProcessor>());
  
      GrGLSLVertexBuilder* v = args.fVertBuilder;
      v->codeAppend ("float2 tan = normalize(endpts.zw - endpts.xy);");
      v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
      v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
  
      // Outset the vertex position for AA butt caps.
      v->codeAppend ("float2 outset = tan*nwidth/2;");
      v->codeAppend ("float2 position = (sk_VertexID < 2) "
                             "? endpts.xy - outset : endpts.zw + outset;");
  
      // Calculate Manhattan distance from both butt caps, where distance=0 on the actual endpoint and
      // distance=-.5 on the outset edge.
      GrGLSLVarying edgeDistances(kFloat4_GrSLType);
      varyingHandler->addVarying("edge_distances", &edgeDistances);
      v->codeAppendf("%s.xz = float2(-.5, dot(endpts.zw - endpts.xy, tan) / nwidth + .5);",
                     edgeDistances.vsOut());
      v->codeAppendf("%s.xz = (sk_VertexID < 2) ? %s.xz : %s.zx;",
                     edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
  
      // Outset the vertex position for stroke radius plus edge AA.
      v->codeAppend ("outset = n * (stroke_radius + nwidth/2);");
      v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? +outset : -outset;");
  
      // Calculate Manhattan distance from both edges, where distance=0 on the actual edge and
      // distance=-.5 on the outset.
      v->codeAppendf("%s.yw = float2(-.5, 2*stroke_radius / nwidth + .5);", edgeDistances.vsOut());
      v->codeAppendf("%s.yw = (0 == (sk_VertexID & 1)) ? %s.yw : %s.wy;",
                     edgeDistances.vsOut(), edgeDistances.vsOut(), edgeDistances.vsOut());
  
      gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
      this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
                           SkMatrix::I(), args.fFPCoordTransformHandler);
  
      // Use the 4 edge distances to calculate coverage in the fragment shader.
      GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
      f->codeAppendf("half2 coverages = half2(min(%s.xy, .5) + min(%s.zw, .5));",
                     edgeDistances.fsIn(), edgeDistances.fsIn());
      f->codeAppendf("%s = half4(coverages.x * coverages.y);", args.fOutputColor);
  
      // This shader doesn't use the built-in Ganesh coverage.
      f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
  }
  
  constexpr GrPrimitiveProcessor::Attribute LinearStrokeProcessor::kInstanceAttribs[];
  
  // This class draws stroked cubics in post-transform device space. Rigid-body transforms can be
  // achieved by transforming the curve ahead of time and adjusting the stroke width. Skews of the
  // stroke itself are not yet supported. Quadratics can be drawn by converting them to cubics.
  //
  // This class works by finding stroke-width line segments orthogonal to the curve at a
  // pre-determined number of evenly spaced points along the curve (evenly spaced in the parametric
  // sense). It then connects the segments with a triangle strip. As for common in CCPR, clockwise-
  // winding triangles from the strip emit positive coverage, counter-clockwise triangles emit
  // negative, and we use SkBlendMode::kPlus.
  class CubicStrokeProcessor : public GrGeometryProcessor {
  public:
      CubicStrokeProcessor() : GrGeometryProcessor(kCubicStrokeProcessor_ClassID) {
          this->setInstanceAttributes(kInstanceAttribs, 3);
  #ifdef SK_DEBUG
          using Instance = CubicStrokeInstance;
          SkASSERT(this->instanceStride() == sizeof(Instance));
  #endif
      }
  
  private:
      const char* name() const override { return "CubicStrokeProcessor"; }
      void getGLSLProcessorKey(const GrShaderCaps&, GrProcessorKeyBuilder*) const override {}
  
      static constexpr Attribute kInstanceAttribs[3] = {
              {"X", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
              {"Y", kFloat4_GrVertexAttribType, kFloat4_GrSLType},
              {"stroke_info", kFloat2_GrVertexAttribType, kFloat2_GrSLType}
      };
  
      class Impl : public GrGLSLGeometryProcessor {
          void setData(const GrGLSLProgramDataManager&, const GrPrimitiveProcessor&,
                       FPCoordTransformIter&&) override {}
          void onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) override;
      };
  
      GrGLSLPrimitiveProcessor* createGLSLInstance(const GrShaderCaps&) const override {
          return new Impl();
      }
  };
  
  void CubicStrokeProcessor::Impl::onEmitCode(EmitArgs& args, GrGPArgs* gpArgs) {
      GrGLSLVaryingHandler* varyingHandler = args.fVaryingHandler;
      GrGLSLUniformHandler* uniHandler = args.fUniformHandler;
  
      varyingHandler->emitAttributes(args.fGP.cast<CubicStrokeProcessor>());
  
      GrGLSLVertexBuilder* v = args.fVertBuilder;
      v->codeAppend ("float4x2 P = transpose(float2x4(X, Y));");
      v->codeAppend ("float stroke_radius = stroke_info[0];");
      v->codeAppend ("float num_segments = stroke_info[1];");
  
      // Find the parametric T value at which we will emit our orthogonal line segment. We emit two
      // line segments at T=0 and double at T=1 as well for AA butt caps.
      v->codeAppend ("float point_id = float(sk_VertexID/2);");
      v->codeAppend ("float T = max((point_id - 1) / num_segments, 0);");
      v->codeAppend ("T = (point_id >= num_segments + 1) ? 1 : T;");  // In case x/x !== 1.
  
      // Use De Casteljau's algorithm to find the position and tangent for our orthogonal line
      // segment. De Casteljau's is more numerically stable than evaluating the curve and derivative
      // directly.
      v->codeAppend ("float2 ab = mix(P[0], P[1], T);");
      v->codeAppend ("float2 bc = mix(P[1], P[2], T);");
      v->codeAppend ("float2 cd = mix(P[2], P[3], T);");
      v->codeAppend ("float2 abc = mix(ab, bc, T);");
      v->codeAppend ("float2 bcd = mix(bc, cd, T);");
      v->codeAppend ("float2 position = mix(abc, bcd, T);");
      v->codeAppend ("float2 tan = bcd - abc;");
  
      // Find actual tangents for the corner cases when De Casteljau's yields tan=0. (We shouldn't
      // encounter other numerically unstable cases where tan ~= 0, because GrCCStrokeGeometry snaps
      // control points to endpoints in curves where they are almost equal.)
      v->codeAppend ("if (0 == T && P[0] == P[1]) {");
      v->codeAppend (    "tan = P[2] - P[0];");
      v->codeAppend ("}");
      v->codeAppend ("if (1 == T && P[2] == P[3]) {");
      v->codeAppend (    "tan = P[3] - P[1];");
      v->codeAppend ("}");
      v->codeAppend ("tan = normalize(tan);");
      v->codeAppend ("float2 n = float2(tan.y, -tan.x);");
      v->codeAppend ("float nwidth = abs(n.x) + abs(n.y);");
  
      // Outset the vertex position for stroke radius plus edge AA.
      v->codeAppend ("float2 outset = n * (stroke_radius + nwidth/2);");
      v->codeAppend ("position += (0 == (sk_VertexID & 1)) ? -outset : +outset;");
  
      // Calculate the Manhattan distance from both edges, where distance=0 on the actual edge and
      // distance=-.5 on the outset.
      GrGLSLVarying coverages(kFloat3_GrSLType);
      varyingHandler->addVarying("coverages", &coverages);
      v->codeAppendf("%s.xy = float2(-.5, 2*stroke_radius / nwidth + .5);", coverages.vsOut());
      v->codeAppendf("%s.xy = (0 == (sk_VertexID & 1)) ? %s.xy : %s.yx;",
                     coverages.vsOut(), coverages.vsOut(), coverages.vsOut());
  
      // Adjust the orthogonal line segments on the endpoints so they straddle the actual endpoint
      // at a Manhattan distance of .5 on either side.
      v->codeAppend ("if (0 == point_id || num_segments+1 == point_id) {");
      v->codeAppend (    "position -= tan*nwidth/2;");
      v->codeAppend ("}");
      v->codeAppend ("if (1 == point_id || num_segments+2 == point_id) {");
      v->codeAppend (    "position += tan*nwidth/2;");
      v->codeAppend ("}");
  
      // Interpolate coverage for butt cap AA from 0 on the outer segment to 1 on the inner.
      v->codeAppendf("%s.z = (0 == point_id || num_segments+2 == point_id) ? 0 : 1;",
                     coverages.vsOut());
  
      gpArgs->fPositionVar.set(kFloat2_GrSLType, "position");
      this->emitTransforms(v, varyingHandler, uniHandler, GrShaderVar("position", kFloat2_GrSLType),
                           SkMatrix::I(), args.fFPCoordTransformHandler);
  
      // Use the 2 edge distances and interpolated butt cap AA to calculate fragment coverage.
      GrGLSLFPFragmentBuilder* f = args.fFragBuilder;
      f->codeAppendf("half2 edge_coverages = min(half2(%s.xy), .5);", coverages.fsIn());
      f->codeAppend ("half coverage = edge_coverages.x + edge_coverages.y;");
      f->codeAppendf("coverage *= half(%s.z);", coverages.fsIn());  // Butt cap AA.
  
      // As is common for CCPR, clockwise-winding triangles from the strip emit positive coverage, and
      // counter-clockwise triangles emit negative.
      f->codeAppendf("%s = half4(sk_Clockwise ? +coverage : -coverage);", args.fOutputColor);
  
      // This shader doesn't use the built-in Ganesh coverage.
      f->codeAppendf("%s = half4(1);", args.fOutputCoverage);
  }
  
  constexpr GrPrimitiveProcessor::Attribute CubicStrokeProcessor::kInstanceAttribs[];
  
  }  // anonymous namespace
  
  void GrCCStroker::parseDeviceSpaceStroke(const SkPath& path, const SkPoint* deviceSpacePts,
                                           const SkStrokeRec& stroke, float strokeDevWidth,
                                           GrScissorTest scissorTest,
                                           const SkIRect& clippedDevIBounds,
                                           const SkIVector& devToAtlasOffset) {
      SkASSERT(SkStrokeRec::kStroke_Style == stroke.getStyle() ||
               SkStrokeRec::kHairline_Style == stroke.getStyle());
      SkASSERT(!fInstanceBuffer);
      SkASSERT(!path.isEmpty());
  
      if (!fHasOpenBatch) {
          fBatches.emplace_back(&fTalliesAllocator, *fInstanceCounts[(int)GrScissorTest::kDisabled],
                                fScissorSubBatches.count());
          fInstanceCounts[(int)GrScissorTest::kDisabled] = fBatches.back().fNonScissorEndInstances;
          fHasOpenBatch = true;
      }
  
      InstanceTallies* currStrokeEndIndices;
      if (GrScissorTest::kEnabled == scissorTest) {
          SkASSERT(fBatches.back().fEndScissorSubBatch == fScissorSubBatches.count());
          fScissorSubBatches.emplace_back(&fTalliesAllocator,
                                          *fInstanceCounts[(int)GrScissorTest::kEnabled],
                                          clippedDevIBounds.makeOffset(devToAtlasOffset));
          fBatches.back().fEndScissorSubBatch = fScissorSubBatches.count();
          fInstanceCounts[(int)GrScissorTest::kEnabled] =
                  currStrokeEndIndices = fScissorSubBatches.back().fEndInstances;
      } else {
          currStrokeEndIndices = fBatches.back().fNonScissorEndInstances;
      }
  
      fGeometry.beginPath(stroke, strokeDevWidth, currStrokeEndIndices);
  
      fPathInfos.push_back() = {devToAtlasOffset, strokeDevWidth/2, scissorTest};
  
      int devPtsIdx = 0;
      SkPath::Verb previousVerb = SkPath::kClose_Verb;
  
      for (SkPath::Verb verb : SkPathPriv::Verbs(path)) {
          SkASSERT(SkPath::kDone_Verb != previousVerb);
          const SkPoint* P = &deviceSpacePts[devPtsIdx - 1];
          switch (verb) {
              case SkPath::kMove_Verb:
                  if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
                      fGeometry.capContourAndExit();
                  }
                  fGeometry.moveTo(deviceSpacePts[devPtsIdx]);
                  ++devPtsIdx;
                  break;
              case SkPath::kClose_Verb:
                  SkASSERT(SkPath::kClose_Verb != previousVerb);
                  fGeometry.closeContour();
                  break;
              case SkPath::kLine_Verb:
                  SkASSERT(SkPath::kClose_Verb != previousVerb);
                  fGeometry.lineTo(P[1]);
                  ++devPtsIdx;
                  break;
              case SkPath::kQuad_Verb:
                  SkASSERT(SkPath::kClose_Verb != previousVerb);
                  fGeometry.quadraticTo(P);
                  devPtsIdx += 2;
                  break;
              case SkPath::kCubic_Verb: {
                  SkASSERT(SkPath::kClose_Verb != previousVerb);
                  fGeometry.cubicTo(P);
                  devPtsIdx += 3;
                  break;
              }
              case SkPath::kConic_Verb:
                  SkASSERT(SkPath::kClose_Verb != previousVerb);
                  SK_ABORT("Stroked conics not supported.");
                  break;
              case SkPath::kDone_Verb:
                  break;
          }
          previousVerb = verb;
      }
  
      if (devPtsIdx > 0 && SkPath::kClose_Verb != previousVerb) {
          fGeometry.capContourAndExit();
      }
  }
  
  // This class encapsulates the process of expanding ready-to-draw geometry from GrCCStrokeGeometry
  // directly into GPU instance buffers.
  class GrCCStroker::InstanceBufferBuilder {
  public:
      InstanceBufferBuilder(GrOnFlushResourceProvider* onFlushRP, GrCCStroker* stroker) {
          memcpy(fNextInstances, stroker->fBaseInstances, sizeof(fNextInstances));
  #ifdef SK_DEBUG
          fEndInstances[0] = stroker->fBaseInstances[0] + *stroker->fInstanceCounts[0];
          fEndInstances[1] = stroker->fBaseInstances[1] + *stroker->fInstanceCounts[1];
  #endif
  
          int endConicsIdx = stroker->fBaseInstances[1].fConics +
                             stroker->fInstanceCounts[1]->fConics;
          fInstanceBuffer = onFlushRP->makeBuffer(GrGpuBufferType::kVertex,
                                                  endConicsIdx * sizeof(ConicInstance));
          if (!fInstanceBuffer) {
              SkDebugf("WARNING: failed to allocate CCPR stroke instance buffer.\n");
              return;
          }
          fInstanceBufferData = fInstanceBuffer->map();
      }
  
      bool isMapped() const { return SkToBool(fInstanceBufferData); }
  
      void updateCurrentInfo(const PathInfo& pathInfo) {
          SkASSERT(this->isMapped());
          fCurrDX = static_cast<float>(pathInfo.fDevToAtlasOffset.x());
          fCurrDY = static_cast<float>(pathInfo.fDevToAtlasOffset.y());
          fCurrStrokeRadius = pathInfo.fStrokeRadius;
          fCurrNextInstances = &fNextInstances[(int)pathInfo.fScissorTest];
          SkDEBUGCODE(fCurrEndInstances = &fEndInstances[(int)pathInfo.fScissorTest]);
      }
  
      void appendLinearStroke(const SkPoint endpts[2]) {
          SkASSERT(this->isMapped());
          this->appendLinearStrokeInstance().set(endpts, fCurrDX, fCurrDY, fCurrStrokeRadius);
      }
  
      void appendQuadraticStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
          SkASSERT(this->isMapped());
          SkASSERT(numLinearSegmentsLog2 > 0);
  
          Sk4f ptsT[2];
          Sk2f p0 = Sk2f::Load(P);
          Sk2f p1 = Sk2f::Load(P+1);
          Sk2f p2 = Sk2f::Load(P+2);
  
          // Convert the quadratic to cubic.
          Sk2f c1 = SkNx_fma(Sk2f(2/3.f), p1 - p0, p0);
          Sk2f c2 = SkNx_fma(Sk2f(1/3.f), p2 - p1, p1);
          Sk2f::Store4(ptsT, p0, c1, c2, p2);
  
          this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
                  ptsT[0], ptsT[1], fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
      }
  
      void appendCubicStroke(const SkPoint P[3], int numLinearSegmentsLog2) {
          SkASSERT(this->isMapped());
          SkASSERT(numLinearSegmentsLog2 > 0);
          this->appendCubicStrokeInstance(numLinearSegmentsLog2).set(
                  P, fCurrDX, fCurrDY, fCurrStrokeRadius, 1 << numLinearSegmentsLog2);
      }
  
      void appendJoin(Verb joinVerb, const SkPoint& center, const SkVector& leftNorm,
                      const SkVector& rightNorm, float miterCapHeightOverWidth, float conicWeight) {
          SkASSERT(this->isMapped());
  
          Sk2f offset = Sk2f::Load(&center) + Sk2f(fCurrDX, fCurrDY);
          Sk2f n0 = Sk2f::Load(&leftNorm);
          Sk2f n1 = Sk2f::Load(&rightNorm);
  
          // Identify the outer edge.
          Sk2f cross = n0 * SkNx_shuffle<1,0>(n1);
          if (cross[0] < cross[1]) {
              Sk2f tmp = n0;
              n0 = -n1;
              n1 = -tmp;
          }
  
          if (!GrCCStrokeGeometry::IsInternalJoinVerb(joinVerb)) {
              // Normal joins are a triangle that connects the outer corners of two adjoining strokes.
              this->appendTriangleInstance().set(
                      n1 * fCurrStrokeRadius, Sk2f(0, 0), n0 * fCurrStrokeRadius, offset,
                      TriangleInstance::Ordering::kXYTransposed);
              if (Verb::kBevelJoin == joinVerb) {
                  return;
              }
          } else {
              // Internal joins are coverage-counted, self-intersecting quadrilaterals that tie the
              // four corners of two adjoining strokes together a like a shoelace. Coverage is
              // negative on the inside half. We implement this geometry with a pair of triangles.
              this->appendTriangleInstance().set(
                      -n0 * fCurrStrokeRadius, n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius,
                      offset, TriangleInstance::Ordering::kXYTransposed);
              if (Verb::kBevelJoin == joinVerb) {
                  return;
              }
              this->appendTriangleInstance().set(
                      -n0 * fCurrStrokeRadius, n1 * fCurrStrokeRadius, -n1 * fCurrStrokeRadius,
                      offset, TriangleInstance::Ordering::kXYTransposed);
              if (Verb::kBevelJoin == joinVerb) {
                  return;
              }
              if (Verb::kInternalBevelJoin == joinVerb) {
                  return;
              }
          }
  
          // For miter and round joins, we place an additional triangle cap on top of the bevel. This
          // triangle is literal for miters and is conic control points for round joins.
          SkASSERT(miterCapHeightOverWidth >= 0 || SkScalarIsNaN(miterCapHeightOverWidth));
          Sk2f base = n1 - n0;
          Sk2f baseNorm = Sk2f(base[1], -base[0]);
          Sk2f c = (n0 + n1) * .5f + baseNorm * miterCapHeightOverWidth;
  
          if (Verb::kMiterJoin == joinVerb) {
              this->appendTriangleInstance().set(
                      n0 * fCurrStrokeRadius, c * fCurrStrokeRadius, n1 * fCurrStrokeRadius, offset,
                      TriangleInstance::Ordering::kXYTransposed);
          } else {
              SkASSERT(Verb::kRoundJoin == joinVerb || Verb::kInternalRoundJoin == joinVerb);
              this->appendConicInstance().setW(n0 * fCurrStrokeRadius, c * fCurrStrokeRadius,
                                               n1 * fCurrStrokeRadius, offset, conicWeight);
              if (Verb::kInternalRoundJoin == joinVerb) {
                  this->appendConicInstance().setW(-n1 * fCurrStrokeRadius, c * -fCurrStrokeRadius,
                                                   -n0 * fCurrStrokeRadius, offset, conicWeight);
              }
          }
      }
  
      void appendCap(Verb capType, const SkPoint& pt, const SkVector& norm) {
          SkASSERT(this->isMapped());
  
          Sk2f n = Sk2f::Load(&norm) * fCurrStrokeRadius;
          Sk2f v = Sk2f(-n[1], n[0]);
          Sk2f offset = Sk2f::Load(&pt) + Sk2f(fCurrDX, fCurrDY);
  
          if (Verb::kSquareCap == capType) {
              SkPoint endPts[2] = {{0, 0}, {v[0], v[1]}};
              this->appendLinearStrokeInstance().set(endPts, offset[0], offset[1], fCurrStrokeRadius);
          } else {
              SkASSERT(Verb::kRoundCap == capType);
              this->appendTriangleInstance().set(
                      n, v, -n, offset, TriangleInstance::Ordering::kXYTransposed);
              this->appendConicInstance().setW(n, n + v, v, offset, SK_ScalarRoot2Over2);
              this->appendConicInstance().setW(v, v - n, -n, offset, SK_ScalarRoot2Over2);
          }
      }
  
      sk_sp<GrGpuBuffer> finish() {
          SkASSERT(this->isMapped());
          SkASSERT(!memcmp(fNextInstances, fEndInstances, sizeof(fNextInstances)));
          fInstanceBuffer->unmap();
          fInstanceBufferData = nullptr;
          SkASSERT(!this->isMapped());
          return std::move(fInstanceBuffer);
      }
  
  private:
      LinearStrokeInstance& appendLinearStrokeInstance() {
          int instanceIdx = fCurrNextInstances->fStrokes[0]++;
          SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[0]);
  
          return reinterpret_cast<LinearStrokeInstance*>(fInstanceBufferData)[instanceIdx];
      }
  
      CubicStrokeInstance& appendCubicStrokeInstance(int numLinearSegmentsLog2) {
          SkASSERT(numLinearSegmentsLog2 > 0);
          SkASSERT(numLinearSegmentsLog2 <= kMaxNumLinearSegmentsLog2);
  
          int instanceIdx = fCurrNextInstances->fStrokes[numLinearSegmentsLog2]++;
          SkASSERT(instanceIdx < fCurrEndInstances->fStrokes[numLinearSegmentsLog2]);
  
          return reinterpret_cast<CubicStrokeInstance*>(fInstanceBufferData)[instanceIdx];
      }
  
      TriangleInstance& appendTriangleInstance() {
          int instanceIdx = fCurrNextInstances->fTriangles++;
          SkASSERT(instanceIdx < fCurrEndInstances->fTriangles);
  
          return reinterpret_cast<TriangleInstance*>(fInstanceBufferData)[instanceIdx];
      }
  
      ConicInstance& appendConicInstance() {
          int instanceIdx = fCurrNextInstances->fConics++;
          SkASSERT(instanceIdx < fCurrEndInstances->fConics);
  
          return reinterpret_cast<ConicInstance*>(fInstanceBufferData)[instanceIdx];
      }
  
      float fCurrDX, fCurrDY;
      float fCurrStrokeRadius;
      InstanceTallies* fCurrNextInstances;
      SkDEBUGCODE(const InstanceTallies* fCurrEndInstances);
  
      sk_sp<GrGpuBuffer> fInstanceBuffer;
      void* fInstanceBufferData = nullptr;
      InstanceTallies fNextInstances[2];
      SkDEBUGCODE(InstanceTallies fEndInstances[2]);
  };
  
  GrCCStroker::BatchID GrCCStroker::closeCurrentBatch() {
      if (!fHasOpenBatch) {
          return kEmptyBatchID;
      }
      int start = (fBatches.count() < 2) ? 0 : fBatches[fBatches.count() - 2].fEndScissorSubBatch;
      int end = fBatches.back().fEndScissorSubBatch;
      fMaxNumScissorSubBatches = SkTMax(fMaxNumScissorSubBatches, end - start);
      fHasOpenBatch = false;
      return fBatches.count() - 1;
  }
  
  bool GrCCStroker::prepareToDraw(GrOnFlushResourceProvider* onFlushRP) {
      SkASSERT(!fInstanceBuffer);
      SkASSERT(!fHasOpenBatch);  // Call closeCurrentBatch() first.
  
      // Here we layout a single instance buffer to share with every internal batch.
      //
      // Rather than place each instance array in its own GPU buffer, we allocate a single
      // megabuffer and lay them all out side-by-side. We can offset the "baseInstance" parameter in
      // our draw calls to direct the GPU to the applicable elements within a given array.
      fBaseInstances[0].fStrokes[0] = 0;
      fBaseInstances[1].fStrokes[0] = fInstanceCounts[0]->fStrokes[0];
      int endLinearStrokesIdx = fBaseInstances[1].fStrokes[0] + fInstanceCounts[1]->fStrokes[0];
  
      int cubicStrokesIdx = GrSizeDivRoundUp(endLinearStrokesIdx * sizeof(LinearStrokeInstance),
                                             sizeof(CubicStrokeInstance));
      for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
          for (int j = 0; j < kNumScissorModes; ++j) {
              fBaseInstances[j].fStrokes[i] = cubicStrokesIdx;
              cubicStrokesIdx += fInstanceCounts[j]->fStrokes[i];
          }
      }
  
      int trianglesIdx = GrSizeDivRoundUp(cubicStrokesIdx * sizeof(CubicStrokeInstance),
                                          sizeof(TriangleInstance));
      fBaseInstances[0].fTriangles = trianglesIdx;
      fBaseInstances[1].fTriangles =
              fBaseInstances[0].fTriangles + fInstanceCounts[0]->fTriangles;
      int endTrianglesIdx =
              fBaseInstances[1].fTriangles + fInstanceCounts[1]->fTriangles;
  
      int conicsIdx =
              GrSizeDivRoundUp(endTrianglesIdx * sizeof(TriangleInstance), sizeof(ConicInstance));
      fBaseInstances[0].fConics = conicsIdx;
      fBaseInstances[1].fConics = fBaseInstances[0].fConics + fInstanceCounts[0]->fConics;
  
      InstanceBufferBuilder builder(onFlushRP, this);
      if (!builder.isMapped()) {
          return false;  // Buffer allocation failed.
      }
  
      // Now parse the GrCCStrokeGeometry and expand it into the instance buffer.
      int pathIdx = 0;
      int ptsIdx = 0;
      int paramsIdx = 0;
      int normalsIdx = 0;
  
      const SkTArray<GrCCStrokeGeometry::Parameter, true>& params = fGeometry.params();
      const SkTArray<SkPoint, true>& pts = fGeometry.points();
      const SkTArray<SkVector, true>& normals = fGeometry.normals();
  
      float miterCapHeightOverWidth=0, conicWeight=0;
  
      for (Verb verb : fGeometry.verbs()) {
          switch (verb) {
              case Verb::kBeginPath:
                  builder.updateCurrentInfo(fPathInfos[pathIdx]);
                  ++pathIdx;
                  continue;
  
              case Verb::kLinearStroke:
                  builder.appendLinearStroke(&pts[ptsIdx]);
                  ++ptsIdx;
                  continue;
              case Verb::kQuadraticStroke:
                  builder.appendQuadraticStroke(&pts[ptsIdx],
                                                params[paramsIdx++].fNumLinearSegmentsLog2);
                  ptsIdx += 2;
                  ++normalsIdx;
                  continue;
              case Verb::kCubicStroke:
                  builder.appendCubicStroke(&pts[ptsIdx], params[paramsIdx++].fNumLinearSegmentsLog2);
                  ptsIdx += 3;
                  ++normalsIdx;
                  continue;
  
              case Verb::kRoundJoin:
              case Verb::kInternalRoundJoin:
                  conicWeight = params[paramsIdx++].fConicWeight;
                  // fallthru
              case Verb::kMiterJoin:
                  miterCapHeightOverWidth = params[paramsIdx++].fMiterCapHeightOverWidth;
                  // fallthru
              case Verb::kBevelJoin:
              case Verb::kInternalBevelJoin:
                  builder.appendJoin(verb, pts[ptsIdx], normals[normalsIdx], normals[normalsIdx + 1],
                                     miterCapHeightOverWidth, conicWeight);
                  ++normalsIdx;
                  continue;
  
              case Verb::kSquareCap:
              case Verb::kRoundCap:
                  builder.appendCap(verb, pts[ptsIdx], normals[normalsIdx]);
                  continue;
  
              case Verb::kEndContour:
                  ++ptsIdx;
                  ++normalsIdx;
                  continue;
          }
          SK_ABORT("Invalid CCPR stroke element.");
      }
  
      fInstanceBuffer = builder.finish();
      SkASSERT(fPathInfos.count() == pathIdx);
      SkASSERT(pts.count() == ptsIdx);
      SkASSERT(normals.count() == normalsIdx);
  
      fMeshesBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
      fScissorsBuffer.reserve((1 + fMaxNumScissorSubBatches) * kMaxNumLinearSegmentsLog2);
      return true;
  }
  
  void GrCCStroker::drawStrokes(GrOpFlushState* flushState, GrCCCoverageProcessor* proc,
                                BatchID batchID, const SkIRect& drawBounds) const {
      using PrimitiveType = GrCCCoverageProcessor::PrimitiveType;
      SkASSERT(fInstanceBuffer);
  
      if (kEmptyBatchID == batchID) {
          return;
      }
      const Batch& batch = fBatches[batchID];
      int startScissorSubBatch = (!batchID) ? 0 : fBatches[batchID - 1].fEndScissorSubBatch;
  
      const InstanceTallies* startIndices[2];
      startIndices[(int)GrScissorTest::kDisabled] = (!batchID)
              ? &fZeroTallies : fBatches[batchID - 1].fNonScissorEndInstances;
      startIndices[(int)GrScissorTest::kEnabled] = (!startScissorSubBatch)
              ? &fZeroTallies : fScissorSubBatches[startScissorSubBatch - 1].fEndInstances;
  
      GrPipeline pipeline(GrScissorTest::kEnabled, SkBlendMode::kPlus,
                          flushState->drawOpArgs().outputSwizzle());
  
      // Draw linear strokes.
      this->appendStrokeMeshesToBuffers(0, batch, startIndices, startScissorSubBatch, drawBounds);
      if (!fMeshesBuffer.empty()) {
          LinearStrokeProcessor linearProc;
          this->flushBufferedMeshesAsStrokes(linearProc, flushState, pipeline, drawBounds);
      }
  
      // Draw cubic strokes. (Quadratics were converted to cubics for GPU processing.)
      for (int i = 1; i <= kMaxNumLinearSegmentsLog2; ++i) {
          this->appendStrokeMeshesToBuffers(i, batch, startIndices, startScissorSubBatch, drawBounds);
      }
      if (!fMeshesBuffer.empty()) {
          CubicStrokeProcessor cubicProc;
          this->flushBufferedMeshesAsStrokes(cubicProc, flushState, pipeline, drawBounds);
      }
  
      // Draw triangles.
      proc->reset(PrimitiveType::kTriangles, flushState->resourceProvider());
      this->drawConnectingGeometry<&InstanceTallies::fTriangles>(
              flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
  
      // Draw conics.
      proc->reset(PrimitiveType::kConics, flushState->resourceProvider());
      this->drawConnectingGeometry<&InstanceTallies::fConics>(
              flushState, pipeline, *proc, batch, startIndices, startScissorSubBatch, drawBounds);
  }
  
  void GrCCStroker::appendStrokeMeshesToBuffers(int numSegmentsLog2, const Batch& batch,
                                                const InstanceTallies* startIndices[2],
                                                int startScissorSubBatch,
                                                const SkIRect& drawBounds) const {
      // Linear strokes draw a quad. Cubic strokes emit a strip with normals at "numSegments"
      // evenly-spaced points along the curve, plus one more for the final endpoint, plus two more for
      // AA butt caps. (i.e., 2 vertices * (numSegments + 3).)
      int numStripVertices = (0 == numSegmentsLog2) ? 4 : ((1 << numSegmentsLog2) + 3) * 2;
  
      // Append non-scissored meshes.
      int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].fStrokes[numSegmentsLog2];
      int startIdx = startIndices[(int)GrScissorTest::kDisabled]->fStrokes[numSegmentsLog2];
      int endIdx = batch.fNonScissorEndInstances->fStrokes[numSegmentsLog2];
      SkASSERT(endIdx >= startIdx);
      if (int instanceCount = endIdx - startIdx) {
          GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
          mesh.setInstanced(fInstanceBuffer, instanceCount, baseInstance + startIdx,
                            numStripVertices);
          fScissorsBuffer.push_back(drawBounds);
      }
  
      // Append scissored meshes.
      baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].fStrokes[numSegmentsLog2];
      startIdx = startIndices[(int)GrScissorTest::kEnabled]->fStrokes[numSegmentsLog2];
      for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
          const ScissorSubBatch& subBatch = fScissorSubBatches[i];
          endIdx = subBatch.fEndInstances->fStrokes[numSegmentsLog2];
          SkASSERT(endIdx >= startIdx);
          if (int instanceCount = endIdx - startIdx) {
              GrMesh& mesh = fMeshesBuffer.emplace_back(GrPrimitiveType::kTriangleStrip);
              mesh.setInstanced(fInstanceBuffer, instanceCount, baseInstance + startIdx,
                                numStripVertices);
              fScissorsBuffer.push_back(subBatch.fScissor);
              startIdx = endIdx;
          }
      }
  }
  
  void GrCCStroker::flushBufferedMeshesAsStrokes(const GrPrimitiveProcessor& processor,
                                                 GrOpFlushState* flushState,
                                                 const GrPipeline& pipeline,
                                                 const SkIRect& drawBounds) const {
      SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
      GrPipeline::DynamicStateArrays dynamicStateArrays;
      dynamicStateArrays.fScissorRects = fScissorsBuffer.begin();
  
      GrProgramInfo programInfo(flushState->proxy()->numSamples(),
                                flushState->proxy()->numStencilSamples(),
                                flushState->drawOpArgs().origin(),
                                &pipeline,
                                &processor,
                                nullptr,
                                &dynamicStateArrays, 0, GrPrimitiveType::kTriangleStrip);
  
      flushState->opsRenderPass()->draw(programInfo,
                                        fMeshesBuffer.begin(), fMeshesBuffer.count(),
                                        SkRect::Make(drawBounds));
      // Don't call reset(), as that also resets the reserve count.
      fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
      fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
  }
  
  template<int GrCCStrokeGeometry::InstanceTallies::* InstanceType>
  void GrCCStroker::drawConnectingGeometry(GrOpFlushState* flushState, const GrPipeline& pipeline,
                                           const GrCCCoverageProcessor& processor,
                                           const Batch& batch, const InstanceTallies* startIndices[2],
                                           int startScissorSubBatch,
                                           const SkIRect& drawBounds) const {
      // Append non-scissored meshes.
      int baseInstance = fBaseInstances[(int)GrScissorTest::kDisabled].*InstanceType;
      int startIdx = startIndices[(int)GrScissorTest::kDisabled]->*InstanceType;
      int endIdx = batch.fNonScissorEndInstances->*InstanceType;
      SkASSERT(endIdx >= startIdx);
      if (int instanceCount = endIdx - startIdx) {
          processor.appendMesh(fInstanceBuffer, instanceCount, baseInstance + startIdx,
                               &fMeshesBuffer);
          fScissorsBuffer.push_back(drawBounds);
      }
  
      // Append scissored meshes.
      baseInstance = fBaseInstances[(int)GrScissorTest::kEnabled].*InstanceType;
      startIdx = startIndices[(int)GrScissorTest::kEnabled]->*InstanceType;
      for (int i = startScissorSubBatch; i < batch.fEndScissorSubBatch; ++i) {
          const ScissorSubBatch& subBatch = fScissorSubBatches[i];
          endIdx = subBatch.fEndInstances->*InstanceType;
          SkASSERT(endIdx >= startIdx);
          if (int instanceCount = endIdx - startIdx) {
              processor.appendMesh(fInstanceBuffer, instanceCount, baseInstance + startIdx,
                                   &fMeshesBuffer);
              fScissorsBuffer.push_back(subBatch.fScissor);
              startIdx = endIdx;
          }
      }
  
      // Flush the geometry.
      if (!fMeshesBuffer.empty()) {
          SkASSERT(fMeshesBuffer.count() == fScissorsBuffer.count());
          processor.draw(flushState, pipeline, fScissorsBuffer.begin(), fMeshesBuffer.begin(),
                         fMeshesBuffer.count(), SkRect::Make(drawBounds));
          // Don't call reset(), as that also resets the reserve count.
          fMeshesBuffer.pop_back_n(fMeshesBuffer.count());
          fScissorsBuffer.pop_back_n(fScissorsBuffer.count());
      }
  }
  