1 /* 2 * Copyright 2021 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 #ifndef tessellate_PatchWriter_DEFINED 9 #define tessellate_PatchWriter_DEFINED 10 11 #include "include/private/SkColorData.h" 12 #include "src/gpu/GrVertexChunkArray.h" 13 #include "src/gpu/tessellate/Tessellation.h" 14 #include "src/gpu/tessellate/WangsFormula.h" 15 16 #define AI SK_ALWAYS_INLINE 17 18 namespace skgpu { 19 20 #if SK_GPU_V1 21 class PathTessellator; 22 class StrokeTessellator; 23 #endif 24 25 // Writes out tessellation patches, formatted with their specific attribs, to a GPU buffer. 26 class PatchWriter { 27 using VectorXform = wangs_formula::VectorXform; 28 public: PatchWriter(GrMeshDrawTarget * target,GrVertexChunkArray * vertexChunkArray,PatchAttribs attribs,int maxTessellationSegments,size_t patchStride,int initialAllocCount)29 PatchWriter(GrMeshDrawTarget* target, 30 GrVertexChunkArray* vertexChunkArray, 31 PatchAttribs attribs, 32 int maxTessellationSegments, 33 size_t patchStride, 34 int initialAllocCount) 35 : fAttribs(attribs) 36 , fMaxSegments_pow2(pow2(maxTessellationSegments)) 37 , fMaxSegments_pow4(pow2(fMaxSegments_pow2)) 38 , fChunker(target, vertexChunkArray, patchStride, initialAllocCount) { 39 // For fans or strokes, the minimum required segment count is 1 (making either a triangle 40 // with the fan point, or a stroked line). Otherwise, we need 2 segments to represent 41 // triangles purely from the tessellated vertices. 42 fCurrMinSegments_pow4 = (attribs & PatchAttribs::kFanPoint || 43 attribs & PatchAttribs::kJoinControlPoint) ? 1.f : 16.f; // 2^4 44 } 45 46 #if SK_GPU_V1 47 // Create PatchWriters that write directly to the GrVertexChunkArrays stored on the provided 48 // tessellators. 49 PatchWriter(GrMeshDrawTarget*, PathTessellator*, 50 int maxTessellationSegments, int initialPatchAllocCount); 51 PatchWriter(GrMeshDrawTarget*, StrokeTessellator*, 52 int maxTessellationSegments, int initialPatchAllocCount); 53 #endif 54 ~PatchWriter()55 ~PatchWriter() { 56 // finishStrokeContour() should have been called before this was deleted (or never used). 57 SkASSERT(!fHasDeferredPatch && !fHasJoinControlPoint); 58 } 59 attribs()60 PatchAttribs attribs() const { return fAttribs; } 61 62 // Fast log2 of minimum required # of segments per tracked Wang's formula calculations. requiredResolveLevel()63 int requiredResolveLevel() const { 64 return wangs_formula::nextlog16(fCurrMinSegments_pow4); // log16(n^4) == log2(n) 65 } 66 // Fast minimum required # of segments from tracked Wang's formula calculations. requiredFixedSegments()67 int requiredFixedSegments() const { 68 return SkScalarCeilToInt(wangs_formula::root4(fCurrMinSegments_pow4)); 69 } 70 71 // Updates the stroke's join control point that will be written out with each patch. This is 72 // automatically adjusted when appending various geometries (e.g. Conic/Cubic), but sometimes 73 // must be set explicitly. 74 // 75 // PatchAttribs::kJoinControlPoint must be enabled. updateJoinControlPointAttrib(SkPoint lastControlPoint)76 void updateJoinControlPointAttrib(SkPoint lastControlPoint) { 77 SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint && lastControlPoint.isFinite()); 78 fJoinControlPointAttrib = lastControlPoint; 79 fHasJoinControlPoint = true; 80 } 81 // Completes a closed contour of a stroke by rewriting a deferred patch with now-available 82 // join control point information. Automatically resets the join control point attribute. 83 // 84 // PatchAttribs::kJoinControlPoint must be enabled. writeDeferredStrokePatch()85 void writeDeferredStrokePatch() { 86 SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint); 87 if (fHasDeferredPatch) { 88 SkASSERT(fHasJoinControlPoint); 89 // Overwrite join control point with updated value, which is the first attribute 90 // after the 4 control points. 91 memcpy(SkTAddOffset<void>(fDeferredPatchStorage, 4 * sizeof(SkPoint)), 92 &fJoinControlPointAttrib, sizeof(SkPoint)); 93 if (VertexWriter vw = fChunker.appendVertex()) { 94 vw << VertexWriter::Array<char>(fDeferredPatchStorage, fChunker.stride()); 95 } 96 } 97 98 fHasDeferredPatch = false; 99 fHasJoinControlPoint = false; 100 } 101 // TODO: These are only used by StrokeHardwareTessellator and ideally its patch writing logic 102 // should be simplified like StrokeFixedCountTessellator's, and then this can go away. resetJoinControlPointAttrib()103 void resetJoinControlPointAttrib() { 104 SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint); 105 // Should have already been written or caller should manually defer 106 SkASSERT(!fHasDeferredPatch); 107 fHasJoinControlPoint = false; 108 } joinControlPoint()109 SkPoint joinControlPoint() const { return fJoinControlPointAttrib; } hasJoinControlPoint()110 bool hasJoinControlPoint() const { return fHasJoinControlPoint; } 111 112 // Updates the fan point that will be written out with each patch (i.e., the point that wedges 113 // fan around). 114 // PatchAttribs::kFanPoint must be enabled. updateFanPointAttrib(SkPoint fanPoint)115 void updateFanPointAttrib(SkPoint fanPoint) { 116 SkASSERT(fAttribs & PatchAttribs::kFanPoint); 117 fFanPointAttrib = fanPoint; 118 } 119 120 // Updates the stroke params that are written out with each patch. 121 // PatchAttribs::kStrokeParams must be enabled. updateStrokeParamsAttrib(StrokeParams strokeParams)122 void updateStrokeParamsAttrib(StrokeParams strokeParams) { 123 SkASSERT(fAttribs & PatchAttribs::kStrokeParams); 124 fStrokeParamsAttrib = strokeParams; 125 } 126 127 // Updates the color that will be written out with each patch. 128 // PatchAttribs::kColor must be enabled. updateColorAttrib(const SkPMColor4f & color)129 void updateColorAttrib(const SkPMColor4f& color) { 130 SkASSERT(fAttribs & PatchAttribs::kColor); 131 fColorAttrib.set(color, fAttribs & PatchAttribs::kWideColorIfEnabled); 132 } 133 134 /** 135 * writeX functions for supported patch geometry types. Every geometric type is converted to an 136 * equivalent cubic or conic, so this will always write at minimum 8 floats for the four control 137 * points (cubic) or three control points and {w, inf} (conics). The PatchWriter additionally 138 * writes the current values of all attributes enabled in its PatchAttribs flags. 139 */ 140 141 // Writes four control points manually prepared. 142 // TODO: Only used by StrokeHardwareTessellator writeHwPatch(const SkPoint p[4])143 AI void writeHwPatch(const SkPoint p[4]) { 144 float4 p0p1 = float4::Load(p); 145 float4 p2p3 = float4::Load(p + 2); 146 this->writePatch(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi, /*unused*/0.f); 147 } 148 149 // Write a cubic curve with its four control points. 150 AI void writeCubic(float2 p0, float2 p1, float2 p2, float2 p3, 151 const VectorXform& shaderXform, 152 float precision = kTessellationPrecision) { 153 float n4 = wangs_formula::cubic_pow4(precision, p0, p1, p2, p3, shaderXform); 154 if (this->writesCurvesOnly()) { 155 if (n4 <= 1.f) { 156 // This cubic only needs one segment (e.g. a line) but we're not filling space with 157 // fans or stroking, so nothing actually needs to be drawn. 158 return; 159 } 160 } 161 if (this->updateRequiredSegments(n4)) { 162 this->writeCubicPatch(p0, p1, p2, p3); 163 } else { 164 int numPatches = SkScalarCeilToInt(wangs_formula::root4( 165 std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4)); 166 this->chopAndWriteCubics(p0, p1, p2, p3, numPatches); 167 } 168 } 169 AI void writeCubic(const SkPoint pts[4], 170 const VectorXform& shaderXform, 171 float precision = kTessellationPrecision) { 172 float4 p0p1 = float4::Load(pts); 173 float4 p2p3 = float4::Load(pts + 2); 174 this->writeCubic(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi, shaderXform, precision); 175 } 176 177 // Write a conic curve with three control points and 'w', with the last coord of the last 178 // control point signaling a conic by being set to infinity. 179 AI void writeConic(float2 p0, float2 p1, float2 p2, float w, 180 const VectorXform& shaderXform, 181 float precision = kTessellationPrecision) { 182 float n2 = wangs_formula::conic_pow2(precision, p0, p1, p2, w, shaderXform); 183 if (this->writesCurvesOnly()) { 184 if (n2 <= 1.f) { 185 // This conic only needs one segment (e.g. a line) but we're not filling space with 186 // fans or stroking, so nothing actually needs to be drawn. 187 return; 188 } 189 } 190 if (this->updateRequiredSegments(n2*n2)) { 191 this->writeConicPatch(p0, p1, p2, w); 192 } else { 193 int numPatches = SkScalarCeilToInt(sqrtf( 194 std::min(n2, pow2(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow2)); 195 this->chopAndWriteConics(p0, p1, p2, w, numPatches); 196 } 197 } 198 AI void writeConic(const SkPoint pts[3], float w, 199 const VectorXform& shaderXform, 200 float precision = kTessellationPrecision) { 201 this->writeConic(skvx::bit_pun<float2>(pts[0]), 202 skvx::bit_pun<float2>(pts[1]), 203 skvx::bit_pun<float2>(pts[2]), 204 w, shaderXform, precision); 205 } 206 207 // Write a quadratic curve that automatically converts its three control points into an 208 // equivalent cubic. 209 AI void writeQuadratic(float2 p0, float2 p1, float2 p2, 210 const VectorXform& shaderXform, 211 float precision = kTessellationPrecision) { 212 float n4 = wangs_formula::quadratic_pow4(precision, p0, p1, p2, shaderXform); 213 if (this->writesCurvesOnly()) { 214 if (n4 <= 1.f) { 215 // This quad only needs one segment (e.g. a line) but we're not filling space with 216 // fans or stroking, so nothing actually needs to be drawn. 217 return; 218 } 219 } 220 if (this->updateRequiredSegments(n4)) { 221 this->writeQuadPatch(p0, p1, p2); 222 } else { 223 int numPatches = SkScalarCeilToInt(wangs_formula::root4( 224 std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4)); 225 this->chopAndWriteQuads(p0, p1, p2, numPatches); 226 } 227 } 228 AI void writeQuadratic(const SkPoint pts[3], 229 const VectorXform& shaderXform, 230 float precision = kTessellationPrecision) { 231 this->writeQuadratic(skvx::bit_pun<float2>(pts[0]), 232 skvx::bit_pun<float2>(pts[1]), 233 skvx::bit_pun<float2>(pts[2]), 234 shaderXform, precision); 235 } 236 237 // Write a line that is automatically converted into an equivalent cubic. writeLine(float4 p0p1)238 AI void writeLine(float4 p0p1) { 239 // No chopping needed, and should have been reset to 1 segment if using writeLine 240 SkASSERT(fCurrMinSegments_pow4 >= 1.f); 241 this->writeCubicPatch(p0p1.lo, (p0p1.zwxy() - p0p1) * (1/3.f) + p0p1, p0p1.hi); 242 } writeLine(float2 p0,float2 p1)243 AI void writeLine(float2 p0, float2 p1) { this->writeLine({p0, p1}); } writeLine(SkPoint p0,SkPoint p1)244 AI void writeLine(SkPoint p0, SkPoint p1) { 245 this->writeLine(skvx::bit_pun<float2>(p0), skvx::bit_pun<float2>(p1)); 246 } 247 248 // Write a triangle by setting it to a conic with w=Inf, and using a distinct 249 // explicit curve type for when inf isn't supported in shaders. writeTriangle(float2 p0,float2 p1,float2 p2)250 AI void writeTriangle(float2 p0, float2 p1, float2 p2) { 251 // No chopping needed, and should have been reset to 2 segments if using writeTriangle. 252 SkASSERT(fCurrMinSegments_pow4 >= (2*2*2*2)); 253 this->writePatch(p0, p1, p2, {SK_FloatInfinity, SK_FloatInfinity}, 254 kTriangularConicCurveType); 255 } writeTriangle(SkPoint p0,SkPoint p1,SkPoint p2)256 AI void writeTriangle(SkPoint p0, SkPoint p1, SkPoint p2) { 257 this->writeTriangle(skvx::bit_pun<float2>(p0), 258 skvx::bit_pun<float2>(p1), 259 skvx::bit_pun<float2>(p2)); 260 } 261 262 // Writes a circle used for round caps and joins in stroking, encoded as a cubic with 263 // identical control points and an empty join. writeCircle(SkPoint p)264 AI void writeCircle(SkPoint p) { 265 // This does not use writePatch() because it uses its own location as the join attribute 266 // value instead of fJoinControlPointAttrib and never defers. 267 SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint); 268 if (VertexWriter vw = fChunker.appendVertex()) { 269 vw << VertexWriter::Repeat<4>(p); // p0,p1,p2,p3 = p -> 4 copies 270 this->emitPatchAttribs(std::move(vw), p, kCubicCurveType); 271 } 272 } 273 274 private: 275 template <typename T> If(bool c,const T & v)276 static VertexWriter::Conditional<T> If(bool c, const T& v) { return VertexWriter::If(c,v); } 277 emitPatchAttribs(VertexWriter vertexWriter,SkPoint joinControlPoint,float explicitCurveType)278 AI void emitPatchAttribs(VertexWriter vertexWriter, 279 SkPoint joinControlPoint, 280 float explicitCurveType) { 281 vertexWriter << If((fAttribs & PatchAttribs::kJoinControlPoint), joinControlPoint) 282 << If((fAttribs & PatchAttribs::kFanPoint), fFanPointAttrib) 283 << If((fAttribs & PatchAttribs::kStrokeParams), fStrokeParamsAttrib) 284 << If((fAttribs & PatchAttribs::kColor), fColorAttrib) 285 << If((fAttribs & PatchAttribs::kExplicitCurveType), explicitCurveType); 286 } 287 writePatch(float2 p0,float2 p1,float2 p2,float2 p3,float explicitCurveType)288 AI void writePatch(float2 p0, float2 p1, float2 p2, float2 p3, float explicitCurveType) { 289 const bool defer = (fAttribs & PatchAttribs::kJoinControlPoint) && 290 !fHasJoinControlPoint; 291 292 SkASSERT(!defer || !fHasDeferredPatch); 293 SkASSERT(fChunker.stride() <= kMaxStride); 294 VertexWriter vw = defer ? VertexWriter{fDeferredPatchStorage, fChunker.stride()} 295 : fChunker.appendVertex(); 296 fHasDeferredPatch |= defer; 297 298 if (vw) { 299 vw << p0 << p1 << p2 << p3; 300 // NOTE: fJoinControlPointAttrib will contain NaN if we're writing to a deferred 301 // patch. If that's the case, correct data will overwrite it when the contour is 302 // closed (this is fine since a deferred patch writes to CPU memory instead of 303 // directly to the GPU buffer). 304 this->emitPatchAttribs(std::move(vw), fJoinControlPointAttrib, explicitCurveType); 305 // Automatically update join control point for next patch. 306 if (fAttribs & PatchAttribs::kJoinControlPoint) { 307 fHasJoinControlPoint = true; 308 if (explicitCurveType == kCubicCurveType && any(p3 != p2)) { 309 // p2 is control point defining the tangent vector into the next patch. 310 p2.store(&fJoinControlPointAttrib); 311 } else if (any(p2 != p1)) { 312 // p1 is the control point defining the tangent vector. 313 p1.store(&fJoinControlPointAttrib); 314 } else { 315 // p0 is the control point defining the tangent vector. 316 p0.store(&fJoinControlPointAttrib); 317 } 318 } 319 } 320 } 321 // Helpers that normalize curves to a generic patch, but does no other work. writeCubicPatch(float2 p0,float2 p1,float2 p2,float2 p3)322 AI void writeCubicPatch(float2 p0, float2 p1, float2 p2, float2 p3) { 323 this->writePatch(p0, p1, p2, p3, kCubicCurveType); 324 } writeCubicPatch(float2 p0,float4 p1p2,float2 p3)325 AI void writeCubicPatch(float2 p0, float4 p1p2, float2 p3) { 326 this->writeCubicPatch(p0, p1p2.lo, p1p2.hi, p3); 327 } writeQuadPatch(float2 p0,float2 p1,float2 p2)328 AI void writeQuadPatch(float2 p0, float2 p1, float2 p2) { 329 this->writeCubicPatch(p0, mix(float4(p0, p2), p1.xyxy(), 2/3.f), p2); 330 } writeConicPatch(float2 p0,float2 p1,float2 p2,float w)331 AI void writeConicPatch(float2 p0, float2 p1, float2 p2, float w) { 332 this->writePatch(p0, p1, p2, {w, SK_FloatInfinity}, kConicCurveType); 333 } 334 335 // Helpers that chop the curve type into 'numPatches' parametrically uniform curves. It is 336 // assumed that 'numPatches' is calculated such that the resulting curves require the maximum 337 // number of segments to draw appropriately (since the original presumably needed even more). 338 void chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatches); 339 void chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, int numPatches); 340 void chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3, int numPatches); 341 342 // Returns true if curve can be written w/o needing to chop updateRequiredSegments(float n4)343 bool updateRequiredSegments(float n4) { 344 if (n4 <= fMaxSegments_pow4) { 345 fCurrMinSegments_pow4 = std::max(n4, fCurrMinSegments_pow4); 346 return true; 347 } else { 348 fCurrMinSegments_pow4 = fMaxSegments_pow4; 349 return false; 350 } 351 } 352 353 // True if the patch writer only draws curves (presumably for filling), e.g. does not add a 354 // wedge fan point to help fill space, or a join control point for stroking. writesCurvesOnly()355 bool writesCurvesOnly() const { 356 return !(fAttribs & (PatchAttribs::kJoinControlPoint | PatchAttribs::kFanPoint)); 357 } 358 359 const PatchAttribs fAttribs; 360 361 const float fMaxSegments_pow2; 362 const float fMaxSegments_pow4; 363 float fCurrMinSegments_pow4; 364 365 GrVertexChunkBuilder fChunker; 366 367 SkPoint fJoinControlPointAttrib; 368 SkPoint fFanPointAttrib; 369 StrokeParams fStrokeParamsAttrib; 370 VertexColor fColorAttrib; 371 372 static constexpr size_t kMaxStride = 373 4 * sizeof(SkPoint) + // control points 374 sizeof(SkPoint) + // join control point or fan attrib point (not used at same time) 375 sizeof(StrokeParams) + // stroke params 376 4 * sizeof(uint32_t); // wide vertex color 377 378 // Only used if kJoinControlPointAttrib is set in fAttribs, in which case it holds data for 379 // a single patch waiting for the incoming join control point to be computed. 380 // Contents are valid (sans join control point) if fHasDeferredPatch is true. 381 char fDeferredPatchStorage[kMaxStride]; 382 bool fHasDeferredPatch = false; 383 384 bool fHasJoinControlPoint = false; 385 }; 386 387 } // namespace skgpu 388 389 #undef AI 390 391 #endif // tessellate_PatchWriter_DEFINED 392