/* * Copyright 2021 Google LLC. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #ifndef tessellate_PatchWriter_DEFINED #define tessellate_PatchWriter_DEFINED #include "include/private/SkColorData.h" #include "src/gpu/GrVertexChunkArray.h" #include "src/gpu/tessellate/Tessellation.h" #include "src/gpu/tessellate/WangsFormula.h" #define AI SK_ALWAYS_INLINE namespace skgpu { #if SK_GPU_V1 class PathTessellator; class StrokeTessellator; #endif // Writes out tessellation patches, formatted with their specific attribs, to a GPU buffer. class PatchWriter { using VectorXform = wangs_formula::VectorXform; public: PatchWriter(GrMeshDrawTarget* target, GrVertexChunkArray* vertexChunkArray, PatchAttribs attribs, int maxTessellationSegments, size_t patchStride, int initialAllocCount) : fAttribs(attribs) , fMaxSegments_pow2(pow2(maxTessellationSegments)) , fMaxSegments_pow4(pow2(fMaxSegments_pow2)) , fChunker(target, vertexChunkArray, patchStride, initialAllocCount) { // For fans or strokes, the minimum required segment count is 1 (making either a triangle // with the fan point, or a stroked line). Otherwise, we need 2 segments to represent // triangles purely from the tessellated vertices. fCurrMinSegments_pow4 = (attribs & PatchAttribs::kFanPoint || attribs & PatchAttribs::kJoinControlPoint) ? 1.f : 16.f; // 2^4 } #if SK_GPU_V1 // Create PatchWriters that write directly to the GrVertexChunkArrays stored on the provided // tessellators. PatchWriter(GrMeshDrawTarget*, PathTessellator*, int maxTessellationSegments, int initialPatchAllocCount); PatchWriter(GrMeshDrawTarget*, StrokeTessellator*, int maxTessellationSegments, int initialPatchAllocCount); #endif ~PatchWriter() { // finishStrokeContour() should have been called before this was deleted (or never used). SkASSERT(!fHasDeferredPatch && !fHasJoinControlPoint); } PatchAttribs attribs() const { return fAttribs; } // Fast log2 of minimum required # of segments per tracked Wang's formula calculations. int requiredResolveLevel() const { return wangs_formula::nextlog16(fCurrMinSegments_pow4); // log16(n^4) == log2(n) } // Fast minimum required # of segments from tracked Wang's formula calculations. int requiredFixedSegments() const { return SkScalarCeilToInt(wangs_formula::root4(fCurrMinSegments_pow4)); } // Updates the stroke's join control point that will be written out with each patch. This is // automatically adjusted when appending various geometries (e.g. Conic/Cubic), but sometimes // must be set explicitly. // // PatchAttribs::kJoinControlPoint must be enabled. void updateJoinControlPointAttrib(SkPoint lastControlPoint) { SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint && lastControlPoint.isFinite()); fJoinControlPointAttrib = lastControlPoint; fHasJoinControlPoint = true; } // Completes a closed contour of a stroke by rewriting a deferred patch with now-available // join control point information. Automatically resets the join control point attribute. // // PatchAttribs::kJoinControlPoint must be enabled. void writeDeferredStrokePatch() { SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint); if (fHasDeferredPatch) { SkASSERT(fHasJoinControlPoint); // Overwrite join control point with updated value, which is the first attribute // after the 4 control points. memcpy(SkTAddOffset(fDeferredPatchStorage, 4 * sizeof(SkPoint)), &fJoinControlPointAttrib, sizeof(SkPoint)); if (VertexWriter vw = fChunker.appendVertex()) { vw << VertexWriter::Array(fDeferredPatchStorage, fChunker.stride()); } } fHasDeferredPatch = false; fHasJoinControlPoint = false; } // TODO: These are only used by StrokeHardwareTessellator and ideally its patch writing logic // should be simplified like StrokeFixedCountTessellator's, and then this can go away. void resetJoinControlPointAttrib() { SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint); // Should have already been written or caller should manually defer SkASSERT(!fHasDeferredPatch); fHasJoinControlPoint = false; } SkPoint joinControlPoint() const { return fJoinControlPointAttrib; } bool hasJoinControlPoint() const { return fHasJoinControlPoint; } // Updates the fan point that will be written out with each patch (i.e., the point that wedges // fan around). // PatchAttribs::kFanPoint must be enabled. void updateFanPointAttrib(SkPoint fanPoint) { SkASSERT(fAttribs & PatchAttribs::kFanPoint); fFanPointAttrib = fanPoint; } // Updates the stroke params that are written out with each patch. // PatchAttribs::kStrokeParams must be enabled. void updateStrokeParamsAttrib(StrokeParams strokeParams) { SkASSERT(fAttribs & PatchAttribs::kStrokeParams); fStrokeParamsAttrib = strokeParams; } // Updates the color that will be written out with each patch. // PatchAttribs::kColor must be enabled. void updateColorAttrib(const SkPMColor4f& color) { SkASSERT(fAttribs & PatchAttribs::kColor); fColorAttrib.set(color, fAttribs & PatchAttribs::kWideColorIfEnabled); } /** * writeX functions for supported patch geometry types. Every geometric type is converted to an * equivalent cubic or conic, so this will always write at minimum 8 floats for the four control * points (cubic) or three control points and {w, inf} (conics). The PatchWriter additionally * writes the current values of all attributes enabled in its PatchAttribs flags. */ // Writes four control points manually prepared. // TODO: Only used by StrokeHardwareTessellator AI void writeHwPatch(const SkPoint p[4]) { float4 p0p1 = float4::Load(p); float4 p2p3 = float4::Load(p + 2); this->writePatch(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi, /*unused*/0.f); } // Write a cubic curve with its four control points. AI void writeCubic(float2 p0, float2 p1, float2 p2, float2 p3, const VectorXform& shaderXform, float precision = kTessellationPrecision) { float n4 = wangs_formula::cubic_pow4(precision, p0, p1, p2, p3, shaderXform); if (this->writesCurvesOnly()) { if (n4 <= 1.f) { // This cubic only needs one segment (e.g. a line) but we're not filling space with // fans or stroking, so nothing actually needs to be drawn. return; } } if (this->updateRequiredSegments(n4)) { this->writeCubicPatch(p0, p1, p2, p3); } else { int numPatches = SkScalarCeilToInt(wangs_formula::root4( std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4)); this->chopAndWriteCubics(p0, p1, p2, p3, numPatches); } } AI void writeCubic(const SkPoint pts[4], const VectorXform& shaderXform, float precision = kTessellationPrecision) { float4 p0p1 = float4::Load(pts); float4 p2p3 = float4::Load(pts + 2); this->writeCubic(p0p1.lo, p0p1.hi, p2p3.lo, p2p3.hi, shaderXform, precision); } // Write a conic curve with three control points and 'w', with the last coord of the last // control point signaling a conic by being set to infinity. AI void writeConic(float2 p0, float2 p1, float2 p2, float w, const VectorXform& shaderXform, float precision = kTessellationPrecision) { float n2 = wangs_formula::conic_pow2(precision, p0, p1, p2, w, shaderXform); if (this->writesCurvesOnly()) { if (n2 <= 1.f) { // This conic only needs one segment (e.g. a line) but we're not filling space with // fans or stroking, so nothing actually needs to be drawn. return; } } if (this->updateRequiredSegments(n2*n2)) { this->writeConicPatch(p0, p1, p2, w); } else { int numPatches = SkScalarCeilToInt(sqrtf( std::min(n2, pow2(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow2)); this->chopAndWriteConics(p0, p1, p2, w, numPatches); } } AI void writeConic(const SkPoint pts[3], float w, const VectorXform& shaderXform, float precision = kTessellationPrecision) { this->writeConic(skvx::bit_pun(pts[0]), skvx::bit_pun(pts[1]), skvx::bit_pun(pts[2]), w, shaderXform, precision); } // Write a quadratic curve that automatically converts its three control points into an // equivalent cubic. AI void writeQuadratic(float2 p0, float2 p1, float2 p2, const VectorXform& shaderXform, float precision = kTessellationPrecision) { float n4 = wangs_formula::quadratic_pow4(precision, p0, p1, p2, shaderXform); if (this->writesCurvesOnly()) { if (n4 <= 1.f) { // This quad only needs one segment (e.g. a line) but we're not filling space with // fans or stroking, so nothing actually needs to be drawn. return; } } if (this->updateRequiredSegments(n4)) { this->writeQuadPatch(p0, p1, p2); } else { int numPatches = SkScalarCeilToInt(wangs_formula::root4( std::min(n4, pow4(kMaxTessellationSegmentsPerCurve)) / fMaxSegments_pow4)); this->chopAndWriteQuads(p0, p1, p2, numPatches); } } AI void writeQuadratic(const SkPoint pts[3], const VectorXform& shaderXform, float precision = kTessellationPrecision) { this->writeQuadratic(skvx::bit_pun(pts[0]), skvx::bit_pun(pts[1]), skvx::bit_pun(pts[2]), shaderXform, precision); } // Write a line that is automatically converted into an equivalent cubic. AI void writeLine(float4 p0p1) { // No chopping needed, and should have been reset to 1 segment if using writeLine SkASSERT(fCurrMinSegments_pow4 >= 1.f); this->writeCubicPatch(p0p1.lo, (p0p1.zwxy() - p0p1) * (1/3.f) + p0p1, p0p1.hi); } AI void writeLine(float2 p0, float2 p1) { this->writeLine({p0, p1}); } AI void writeLine(SkPoint p0, SkPoint p1) { this->writeLine(skvx::bit_pun(p0), skvx::bit_pun(p1)); } // Write a triangle by setting it to a conic with w=Inf, and using a distinct // explicit curve type for when inf isn't supported in shaders. AI void writeTriangle(float2 p0, float2 p1, float2 p2) { // No chopping needed, and should have been reset to 2 segments if using writeTriangle. SkASSERT(fCurrMinSegments_pow4 >= (2*2*2*2)); this->writePatch(p0, p1, p2, {SK_FloatInfinity, SK_FloatInfinity}, kTriangularConicCurveType); } AI void writeTriangle(SkPoint p0, SkPoint p1, SkPoint p2) { this->writeTriangle(skvx::bit_pun(p0), skvx::bit_pun(p1), skvx::bit_pun(p2)); } // Writes a circle used for round caps and joins in stroking, encoded as a cubic with // identical control points and an empty join. AI void writeCircle(SkPoint p) { // This does not use writePatch() because it uses its own location as the join attribute // value instead of fJoinControlPointAttrib and never defers. SkASSERT(fAttribs & PatchAttribs::kJoinControlPoint); if (VertexWriter vw = fChunker.appendVertex()) { vw << VertexWriter::Repeat<4>(p); // p0,p1,p2,p3 = p -> 4 copies this->emitPatchAttribs(std::move(vw), p, kCubicCurveType); } } private: template static VertexWriter::Conditional If(bool c, const T& v) { return VertexWriter::If(c,v); } AI void emitPatchAttribs(VertexWriter vertexWriter, SkPoint joinControlPoint, float explicitCurveType) { vertexWriter << If((fAttribs & PatchAttribs::kJoinControlPoint), joinControlPoint) << If((fAttribs & PatchAttribs::kFanPoint), fFanPointAttrib) << If((fAttribs & PatchAttribs::kStrokeParams), fStrokeParamsAttrib) << If((fAttribs & PatchAttribs::kColor), fColorAttrib) << If((fAttribs & PatchAttribs::kExplicitCurveType), explicitCurveType); } AI void writePatch(float2 p0, float2 p1, float2 p2, float2 p3, float explicitCurveType) { const bool defer = (fAttribs & PatchAttribs::kJoinControlPoint) && !fHasJoinControlPoint; SkASSERT(!defer || !fHasDeferredPatch); SkASSERT(fChunker.stride() <= kMaxStride); VertexWriter vw = defer ? VertexWriter{fDeferredPatchStorage, fChunker.stride()} : fChunker.appendVertex(); fHasDeferredPatch |= defer; if (vw) { vw << p0 << p1 << p2 << p3; // NOTE: fJoinControlPointAttrib will contain NaN if we're writing to a deferred // patch. If that's the case, correct data will overwrite it when the contour is // closed (this is fine since a deferred patch writes to CPU memory instead of // directly to the GPU buffer). this->emitPatchAttribs(std::move(vw), fJoinControlPointAttrib, explicitCurveType); // Automatically update join control point for next patch. if (fAttribs & PatchAttribs::kJoinControlPoint) { fHasJoinControlPoint = true; if (explicitCurveType == kCubicCurveType && any(p3 != p2)) { // p2 is control point defining the tangent vector into the next patch. p2.store(&fJoinControlPointAttrib); } else if (any(p2 != p1)) { // p1 is the control point defining the tangent vector. p1.store(&fJoinControlPointAttrib); } else { // p0 is the control point defining the tangent vector. p0.store(&fJoinControlPointAttrib); } } } } // Helpers that normalize curves to a generic patch, but does no other work. AI void writeCubicPatch(float2 p0, float2 p1, float2 p2, float2 p3) { this->writePatch(p0, p1, p2, p3, kCubicCurveType); } AI void writeCubicPatch(float2 p0, float4 p1p2, float2 p3) { this->writeCubicPatch(p0, p1p2.lo, p1p2.hi, p3); } AI void writeQuadPatch(float2 p0, float2 p1, float2 p2) { this->writeCubicPatch(p0, mix(float4(p0, p2), p1.xyxy(), 2/3.f), p2); } AI void writeConicPatch(float2 p0, float2 p1, float2 p2, float w) { this->writePatch(p0, p1, p2, {w, SK_FloatInfinity}, kConicCurveType); } // Helpers that chop the curve type into 'numPatches' parametrically uniform curves. It is // assumed that 'numPatches' is calculated such that the resulting curves require the maximum // number of segments to draw appropriately (since the original presumably needed even more). void chopAndWriteQuads(float2 p0, float2 p1, float2 p2, int numPatches); void chopAndWriteConics(float2 p0, float2 p1, float2 p2, float w, int numPatches); void chopAndWriteCubics(float2 p0, float2 p1, float2 p2, float2 p3, int numPatches); // Returns true if curve can be written w/o needing to chop bool updateRequiredSegments(float n4) { if (n4 <= fMaxSegments_pow4) { fCurrMinSegments_pow4 = std::max(n4, fCurrMinSegments_pow4); return true; } else { fCurrMinSegments_pow4 = fMaxSegments_pow4; return false; } } // True if the patch writer only draws curves (presumably for filling), e.g. does not add a // wedge fan point to help fill space, or a join control point for stroking. bool writesCurvesOnly() const { return !(fAttribs & (PatchAttribs::kJoinControlPoint | PatchAttribs::kFanPoint)); } const PatchAttribs fAttribs; const float fMaxSegments_pow2; const float fMaxSegments_pow4; float fCurrMinSegments_pow4; GrVertexChunkBuilder fChunker; SkPoint fJoinControlPointAttrib; SkPoint fFanPointAttrib; StrokeParams fStrokeParamsAttrib; VertexColor fColorAttrib; static constexpr size_t kMaxStride = 4 * sizeof(SkPoint) + // control points sizeof(SkPoint) + // join control point or fan attrib point (not used at same time) sizeof(StrokeParams) + // stroke params 4 * sizeof(uint32_t); // wide vertex color // Only used if kJoinControlPointAttrib is set in fAttribs, in which case it holds data for // a single patch waiting for the incoming join control point to be computed. // Contents are valid (sans join control point) if fHasDeferredPatch is true. char fDeferredPatchStorage[kMaxStride]; bool fHasDeferredPatch = false; bool fHasJoinControlPoint = false; }; } // namespace skgpu #undef AI #endif // tessellate_PatchWriter_DEFINED