More PLS simplifications 1) Remove the "FIRST_VERTEX_OF_CONTOUR" flag. Rather than relying on this flag, the vertex shader can know it's crossed into the wrong contour when the tessellation vertex's contourID does not match what we expect it to be. 2) PLSRenderer shouldn't have to think about PLSRenderContext::m_tessVertexCount. Remove the getter for this value and move all the logic and asserts dealing with it into PLSRenderContext. Diffs= 7b3d53e53 More PLS simplifications (#5626) Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index db70053..c9b5939 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -7f3990675865b06c7cf71b01d1c7be1e3e4f7d31 +7b3d53e53437047637e4e68d5fb17b0b00498210
diff --git a/include/rive/pls/pls.hpp b/include/rive/pls/pls.hpp index 279de98..b659666 100644 --- a/include/rive/pls/pls.hpp +++ b/include/rive/pls/pls.hpp
@@ -87,34 +87,26 @@ // Flags for on-GPU rendering data. namespace flags { -// Tells the GPU that a given vertex is the *first* tessellated vertex in its contour. -// -// When emitting patches, this flag is how the GPU detects when it has crossed past the end of the -// current contour. When this happens, the GPU knows to no not connect those two vertices, and -// instead, either wraps back around to the beginning of the contour to close it, or else handles -// the endcap if it's an open stroke. -constexpr static uint32_t kFirstVertexOfContour = 1u << 31; - // Tells shaders that a cubic should actually be drawn as the single, non-AA triangle: [p0, p1, p3]. // This is used to squeeze in more rare triangles, like "grout" triangles from self intersections on // interior triangulation, where it wouldn't be worth it to put them in their own dedicated draw // call. -constexpr static uint32_t kRetrofittedTriangle = 1u << 30; +constexpr static uint32_t kRetrofittedTriangle = 1u << 31; // Tells the tessellation shader to re-run Wang's formula on the given curve, figure out how many // segments it actually needs, and make any excess segments degenerate by co-locating their vertices // at T=0. (Used on the "outerCurve" patches that are drawn with interior triangulations.) -constexpr static uint32_t kCullExcessTessellationSegments = 1u << 29; +constexpr static uint32_t kCullExcessTessellationSegments = 1u << 30; // Flags for specifying the join type. -constexpr static uint32_t kJoinTypeMask = 3u << 27; -constexpr static uint32_t kMiterClipJoin = 3u << 27; // Miter that clips when too sharp. -constexpr static uint32_t kMiterRevertJoin = 2u << 27; // Standard miter that pops when too sharp. -constexpr static uint32_t kBevelJoin = 1u << 27; +constexpr static uint32_t kJoinTypeMask = 3u << 28; +constexpr static uint32_t kMiterClipJoin = 3u << 28; // Miter that clips when too sharp. +constexpr static uint32_t kMiterRevertJoin = 2u << 28; // Standard miter that pops when too sharp. +constexpr static uint32_t kBevelJoin = 1u << 28; // When a join is being used to emulate a stroke cap, the shader emits additional vertices at T=0 // and T=1 for round joins, and changes the miter limit to 1 for miter-clip joins. -constexpr static uint32_t kEmulatedStrokeCap = 1u << 26; +constexpr static uint32_t kEmulatedStrokeCap = 1u << 27; RIVE_ALWAYS_INLINE static uint32_t JoinTypeFlags(StrokeJoin join) { @@ -427,6 +419,16 @@ void GeneratePatchBufferData(PatchVertex[kPatchVertexBufferCount], uint16_t indices[kPatchIndexBufferCount]); +// Returns the smallest number that can be added to 'value', such that 'value % alignment' == 0. +template <uint32_t Alignment> RIVE_ALWAYS_INLINE uint32_t PaddingToAlignUp(uint32_t value) +{ + constexpr uint32_t maxMultipleOfAlignment = + std::numeric_limits<uint32_t>::max() / Alignment * Alignment; + uint32_t padding = (maxMultipleOfAlignment - value) % Alignment; + assert((value + padding) % Alignment == 0); + return padding; +} + // Returns the area of the (potentially non-rectangular) quadrilateral that results from // transforming the given bounds by the given matrix. float FindTransformedArea(const AABB& bounds, const Mat2D&);
diff --git a/include/rive/pls/pls_render_context.hpp b/include/rive/pls/pls_render_context.hpp index 54a91b3..183d47e 100644 --- a/include/rive/pls/pls_render_context.hpp +++ b/include/rive/pls/pls_render_context.hpp
@@ -138,8 +138,36 @@ return m_clipContentID; } - // Returns the number of tessellation vertices that have been pushed during the current flush. - uint32_t currentTessVertexCount() const { return m_tessVertexCount; } + // Utility for calculating the total tessellation vertex count of a batch of paths, and finding + // how much padding to insert between paths. + class TessVertexCounter + { + public: + TessVertexCounter(PLSRenderContext* context) : + m_initialTessVertexCount(context->m_tessVertexCount), + m_runningTessVertexCount(context->m_tessVertexCount) + { + // Don't create a path calculator while the context is in the middle of pushing a path. + assert(m_runningTessVertexCount == context->m_expectedTessVertexCountAtEndOfPath); + } + + // Returns the required number of padding vertices to insert before the path. + template <size_t PatchSize> [[nodiscard]] size_t countPath(size_t pathTessVertexCount) + { + size_t padding = PaddingToAlignUp<PatchSize>(m_runningTessVertexCount); + m_runningTessVertexCount += padding + pathTessVertexCount; + return padding; + } + + size_t totalVertexCount() const + { + return m_runningTessVertexCount - m_initialTessVertexCount; + } + + private: + size_t m_initialTessVertexCount; + size_t m_runningTessVertexCount; + }; // Reserves space for 'pathCount', 'contourCount', 'curveCount', and 'tessVertexCount' records // in their respective GPU buffers, prior to calling pushPath(), pushContour(), pushCubic(). @@ -163,7 +191,7 @@ // // The first curve of the path will be pre-padded with 'paddingVertexCount' tessellation // vertices, colocated at T=0. The caller must use this argument to align the beginning of the - // path on a boundary of the patch size. + // path on a boundary of the patch size. (See PLSRenderContext::TessVertexCounter.) void pushPath(PatchType, const Mat2D&, float strokeRadius, @@ -180,7 +208,7 @@ // // The first curve of the contour will be pre-padded with 'paddingVertexCount' tessellation // vertices, colocated at T=0. The caller must use this argument to align the end of the contour - // on a boundary of the patch size. + // on a boundary of the patch size. (See pls::PaddingToAlignUp().) void pushContour(Vec2D midpoint, bool closed, uint32_t paddingVertexCount); // Appends a cubic curve and join to the most-recently pushed contour, and reserves the @@ -474,6 +502,21 @@ // Updates the draw's ShaderFeatures according to the passed parameters. void pushDraw(DrawType, size_t baseVertex, FillRule, PaintType, uint32_t clipID, PLSBlendMode); + // Writes padding vertices to the tessellation texture, with an invalid contour ID that is + // guaranteed to not be the same ID as any neighbors. + void pushPaddingVertices(uint32_t count); + + // Allocates a (potentially wrapped) span in the tessellation texture and pushes an instance to + // render it. If the span does wraps, pushes multiple instances to render each horizontal + // segment. + RIVE_ALWAYS_INLINE void pushTessellationSpans(const Vec2D pts[4], + Vec2D joinTangent, + uint32_t totalVertexCount, + uint32_t parametricSegmentCount, + uint32_t polarSegmentCount, + uint32_t joinSegmentCount, + uint32_t contourIDWithFlags); + // Capacities of all our GPU resource allocations. struct GPUResourceLimits { @@ -594,9 +637,9 @@ bool m_currentPathIsStroked = false; uint32_t m_currentPathID = 0; uint32_t m_currentContourID = 0; - uint32_t m_currentContourIDWithFlags = 0; uint32_t m_currentContourPaddingVertexCount = 0; // Padding vertices to add to the first curve. uint32_t m_tessVertexCount = 0; + RIVE_DEBUG_CODE(uint32_t m_expectedTessVertexCountAtNextReserve = 0;) RIVE_DEBUG_CODE(uint32_t m_expectedTessVertexCountAtEndOfPath = 0;) // Simple gradients have one stop at t=0 and one stop at t=1. They're implemented with 2 texels.
diff --git a/renderer/pls_render_context.cpp b/renderer/pls_render_context.cpp index e298797..1b0afe0 100644 --- a/renderer/pls_render_context.cpp +++ b/renderer/pls_render_context.cpp
@@ -19,11 +19,6 @@ // When we exceed the capacity of a GPU resource mid-flush, double it immediately. constexpr static double kGPUResourceIntermediateGrowthFactor = 2; -// The final patch of the final contour in a flush needs to see the kFirstVertexOfContour flag in -// order to render properly, so we tessellate (and don't draw) one more empty line at the -// end of the buffer as an "end of previous contour" marker. -constexpr static size_t kEndOfTessMarkerVertexCount = 2; - uint32_t PLSRenderContext::ShaderFeatures::getPreprocessorDefines(SourceType sourceType) const { uint32_t defines = 0; @@ -422,16 +417,20 @@ uint32_t tessVertexCount) { assert(m_didBeginFrame); + assert(m_tessVertexCount == m_expectedTessVertexCountAtNextReserve); + // +1 for the padding vertex at the end. + size_t maxTessVertexCountWithInternalPadding = tessVertexCount + 1; // Line breaks potentially introduce a new span. Count the maximum number of line breaks we // might encounter. size_t y0 = m_tessVertexCount / kTessTextureWidth; - size_t y1 = (m_tessVertexCount + tessVertexCount - 1) / kTessTextureWidth; + size_t y1 = (m_tessVertexCount + maxTessVertexCountWithInternalPadding - 1) / kTessTextureWidth; size_t maxSpanBreakCount = y1 - y0; - // +1 for our empty "end-of-contour" marker's span. - size_t maxTessellationSpans = curveCount + maxSpanBreakCount + 1; - // +kEndOfTessMarkerVertexCount for our empty "end-of-contour" marker's vertices. - size_t tessVertexCountWithMarkers = tessVertexCount + kEndOfTessMarkerVertexCount; + // +pathCount for a span of padding vertices at the beginning of each path. + // +1 for the padding vertex at the end of the entire tessellation texture (in case this happens + // to be the final batch of paths in the flush). + size_t maxPaddingVertexSpans = pathCount + 1; + size_t maxTessellationSpans = maxPaddingVertexSpans + curveCount + maxSpanBreakCount; // Guard against the case where a single draw overwhelms our GPU resources. Since nothing has // been mapped yet on the first draw, we have a unique opportunity at this time to grow our @@ -455,9 +454,9 @@ newLimits.maxTessellationSpans = maxTessellationSpans; needsRealloc = true; } - if (newLimits.maxTessellationVertices < tessVertexCountWithMarkers) + if (newLimits.maxTessellationVertices < maxTessVertexCountWithInternalPadding) { - newLimits.maxTessellationVertices = tessVertexCountWithMarkers; + newLimits.maxTessellationVertices = maxTessVertexCountWithInternalPadding; needsRealloc = true; } assert(!m_pathBuffer.mapped()); @@ -479,11 +478,13 @@ if (m_currentPathID + pathCount <= m_currentResourceLimits.maxPathID && m_currentContourID + contourCount <= m_currentResourceLimits.maxContourID && m_tessSpanBuffer.hasRoomFor(maxTessellationSpans) && - m_tessVertexCount + tessVertexCountWithMarkers <= + m_tessVertexCount + maxTessVertexCountWithInternalPadding <= m_currentResourceLimits.maxTessellationVertices) { assert(m_pathBuffer.hasRoomFor(pathCount)); assert(m_contourBuffer.hasRoomFor(contourCount)); + RIVE_DEBUG_CODE(m_expectedTessVertexCountAtNextReserve = + m_tessVertexCount + tessVertexCount); return true; } @@ -649,6 +650,7 @@ uint32_t patchSize = PatchSegmentSpan(drawType); uint32_t baseInstance = baseVertexToDraw / patchSize; // The caller is responsible to pad each path so it begins on a multiple of the patch size. + // (See PLSRenderContext::PathPaddingCalculator.) assert(baseInstance * patchSize == baseVertexToDraw); pushDraw(drawType, baseInstance, fillRule, paintType, clipID, blendMode); assert(m_drawList.tail().baseVertexOrInstance + m_drawList.tail().vertexOrInstanceCount == @@ -659,12 +661,15 @@ assert(instanceCount * patchSize == vertexCountToDraw); m_drawList.tail().vertexOrInstanceCount += instanceCount; + RIVE_DEBUG_CODE(m_expectedTessVertexCountAtEndOfPath = m_tessVertexCount + tessVertexCount); + // The first curve of the path will be pre-padded with 'paddingVertexCount' tessellation // vertices, colocated at T=0. The caller must use this argument align the beginning of the path - // on a boundary of the patch size. - m_currentContourPaddingVertexCount = paddingVertexCount; - - RIVE_DEBUG_CODE(m_expectedTessVertexCountAtEndOfPath = m_tessVertexCount + tessVertexCount); + // on a boundary of the patch size. (See PLSRenderContext::TessVertexCounter.) + if (paddingVertexCount > 0) + { + pushPaddingVertices(paddingVertexCount); + } } void PLSRenderContext::pushContour(Vec2D midpoint, bool closed, uint32_t paddingVertexCount) @@ -685,17 +690,10 @@ assert(0 < m_currentContourID && m_currentContourID <= kMaxContourID); assert(m_currentContourID == m_contourBuffer.bytesWritten() / sizeof(ContourData)); - m_currentContourIDWithFlags = m_currentContourID; - - // The first tessellated vertex in every contour gets the kFirstVertexOfContour flag as a marker - // for the GPU. Start out with the kFirstVertexOfContour flag set. We will flip it off after we - // push the first curve in this contour. - m_currentContourIDWithFlags |= flags::kFirstVertexOfContour; - // The first curve of the contour will be pre-padded with 'paddingVertexCount' tessellation // vertices, colocated at T=0. The caller must use this argument align the end of the contour on - // a boundary of the patch size. - m_currentContourPaddingVertexCount += paddingVertexCount; + // a boundary of the patch size. (See pls::PaddingToAlignUp().) + m_currentContourPaddingVertexCount = paddingVertexCount; } void PLSRenderContext::pushCubic(const Vec2D pts[4], @@ -709,7 +707,7 @@ assert(0 <= parametricSegmentCount && parametricSegmentCount <= kMaxParametricSegments); assert(0 <= polarSegmentCount && polarSegmentCount <= kMaxPolarSegments); assert(joinSegmentCount > 0); - assert((m_currentContourIDWithFlags & kContourIDMask) != 0); // contourID can't be zero. + assert(m_currentContourID != 0); // contourID can't be zero. // Polar and parametric segments share the same beginning and ending vertices, so the merged // *vertex* count is equal to the sum of polar and parametric *segment* counts. @@ -717,9 +715,40 @@ // -1 because the curve and join share an ending/beginning vertex. uint32_t totalVertexCount = m_currentContourPaddingVertexCount + curveMergedVertexCount + joinSegmentCount - 1; + + // Only the first curve of a contour gets padding vertices. + m_currentContourPaddingVertexCount = 0; + + pushTessellationSpans(pts, + joinTangent, + totalVertexCount, + parametricSegmentCount, + polarSegmentCount, + joinSegmentCount, + m_currentContourID | additionalPLSFlags); +} + +void PLSRenderContext::pushPaddingVertices(uint32_t count) +{ + constexpr static Vec2D kEmptyCubic[4]{}; + // This is guaranteed to not collide with a neighboring contour ID. + constexpr static uint32_t kInvalidContourID = 0; + RIVE_DEBUG_CODE(size_t startingVertexCount = m_tessVertexCount;) + pushTessellationSpans(kEmptyCubic, {0, 0}, count, 0, 0, 1, kInvalidContourID); + assert(m_tessVertexCount == startingVertexCount + count); +} + +RIVE_ALWAYS_INLINE void PLSRenderContext::pushTessellationSpans(const Vec2D pts[4], + Vec2D joinTangent, + uint32_t totalVertexCount, + uint32_t parametricSegmentCount, + uint32_t polarSegmentCount, + uint32_t joinSegmentCount, + uint32_t contourIDWithFlags) +{ + int32_t y = m_tessVertexCount / kTessTextureWidth; int32_t x0 = m_tessVertexCount % kTessTextureWidth; int32_t x1 = x0 + totalVertexCount; - uint32_t y = (m_tessVertexCount / kTessTextureWidth); for (;;) { m_tessSpanBuffer.set_back(pts, @@ -730,7 +759,7 @@ parametricSegmentCount, polarSegmentCount, joinSegmentCount, - m_currentContourIDWithFlags | additionalPLSFlags); + contourIDWithFlags); if (x1 > kTessTextureWidth) { // The span was too long to fit on the current line. Wrap and draw it again, this @@ -744,14 +773,6 @@ break; } - // The first tessellated vertex in every contour gets the kFirstVertexOfContour flag as a marker - // for the GPU. Ensure all other packed IDs do not have this flag after that first tessellated - // vertex. - m_currentContourIDWithFlags &= ~flags::kFirstVertexOfContour; - - // Only the first curve of a contour gets padding vertices. - m_currentContourPaddingVertexCount = 0; - m_tessVertexCount += totalVertexCount; assert(m_tessVertexCount <= m_currentResourceLimits.maxTessellationVertices); } @@ -810,27 +831,23 @@ { assert(m_didBeginFrame); assert(m_tessVertexCount == m_expectedTessVertexCountAtEndOfPath); + if (flushType == FlushType::intermediate) + { + // We might not have pushed as many tessellation vertices as expected if we ran out of room + // for the paint and had to flush. + assert(m_tessVertexCount <= m_expectedTessVertexCountAtNextReserve); + } + else + { + assert(m_tessVertexCount == m_expectedTessVertexCountAtNextReserve); + } - // The first tessellated vertex in every contour gets the kFirstVertexOfContour flag, and when - // emitting patches, this flag is how the GPU detects when it has crossed past the end of the - // current contour. When this happens, the GPU knows to no not connect those two vertices, and - // instead, either wraps back around to the beginning of the contour to close it, or else - // handles the endcap if it's an open stroke. - // - // The final patch of the final contour in our buffer also needs to see that - // kFirstVertexOfContour flag in order to render properly, so we tessellate (and don't draw) one - // more empty line at the end of the buffer as an end-of-previous-contour marker. - // - // TODO: This is a little kludgey. Maybe we can find a cleaner way to accomplish this? + // The final vertex of the final patch of each contour crosses over into the next contour. (This + // is how we wrap around back to the beginning.) Therefore, the final contour of the flush needs + // an out-of-contour vertex to cross into as well, so we emit a padding vertex here at the end. if (!m_tessSpanBuffer.empty()) { - Vec2D emptyLine[4]{}; - // Make sure this empty line gets the kFirstVertexOfContour flag. - m_currentContourIDWithFlags = ~0; - assert(m_currentContourPaddingVertexCount == 0); - RIVE_DEBUG_CODE(size_t startingVertexCount = m_tessVertexCount;) - this->pushCubic(emptyLine, Vec2D{}, 0, 1, 1, 1); - assert(m_tessVertexCount == startingVertexCount + kEndOfTessMarkerVertexCount); + pushPaddingVertices(1); } if (m_maxTriangleVertexCount > 0) @@ -951,12 +968,12 @@ m_currentPathID = 0; m_currentContourID = 0; - m_currentContourIDWithFlags = 0; m_simpleGradients.clear(); m_complexGradients.clear(); m_tessVertexCount = 0; + RIVE_DEBUG_CODE(m_expectedTessVertexCountAtNextReserve = 0); RIVE_DEBUG_CODE(m_expectedTessVertexCountAtEndOfPath = 0); m_maxTriangleVertexCount = 0;
diff --git a/renderer/pls_renderer.cpp b/renderer/pls_renderer.cpp index 47049c2..e53314d 100644 --- a/renderer/pls_renderer.cpp +++ b/renderer/pls_renderer.cpp
@@ -402,16 +402,6 @@ { return iter.rawVerbsPtr() + 1 == end.rawVerbsPtr(); } - -// Returns the smallest number that can be added to 'value', such that 'value % alignment' == 0. -template <uint32_t Alignment> RIVE_ALWAYS_INLINE uint32_t padding_to_align_up(uint32_t value) -{ - constexpr uint32_t maxMultipleOfAlignment = - std::numeric_limits<uint32_t>::max() / Alignment * Alignment; - uint32_t padding = (maxMultipleOfAlignment - value) % Alignment; - assert((value + padding) % Alignment == 0); - return padding; -} } // namespace // Helps count required resources for, and submit data to the render context that will be used to @@ -948,12 +938,11 @@ // Iteration pass 2: Finish calculating the numbers of tessellation segments in each contour, // using SIMD. - uint32_t batchTotalTessVertexCount = 0; - uint32_t batchBaseVertex = m_context->currentTessVertexCount(); size_t contourFirstLineIdx = 0; size_t contourFirstCurveIdx = 0; size_t contourFirstRotationIdx = 0; size_t emptyStrokeCountForCaps = 0; + PLSRenderContext::TessVertexCounter tessVertexCounter(m_context); for (size_t currentPathIdx = 0; currentPathIdx < m_pathBatch.size(); ++currentPathIdx) { PathDraw& path = m_pathBatch[currentPathIdx]; @@ -964,7 +953,19 @@ // (If we used interior triangulation, interiorTriHelper already counted the path's vertices // for us.) - if (path.triangulator == nullptr) + if (path.triangulator != nullptr) + { + assert(path.paddingVertexCount == 0); + // If the path has a nonzero number of tessellation vertices, pad them so they align on + // a multiple of the patch size. + if (path.tessVertexCount > 0) + { + path.paddingVertexCount = + tessVertexCounter.countPath<kOuterCurvePatchSegmentSpan>(path.tessVertexCount); + path.tessVertexCount += path.paddingVertexCount; + } + } + else { assert(path.tessVertexCount == 0); for (size_t i = 0; i < path.contourCount; ++i) @@ -1119,7 +1120,7 @@ // an exact multiple of kMidpointFanPatchSegmentSpan. This ensures that patch // boundaries align with contour boundaries. contour->paddingVertexCount = - padding_to_align_up<kMidpointFanPatchSegmentSpan>(contourVertexCount); + PaddingToAlignUp<kMidpointFanPatchSegmentSpan>(contourVertexCount); contourVertexCount += contour->paddingVertexCount; assert(contourVertexCount % kMidpointFanPatchSegmentSpan == 0); RIVE_DEBUG_CODE(contour->tessVertexCount = contourVertexCount;) @@ -1129,25 +1130,15 @@ contourFirstCurveIdx = contour->endCurveIdx; contourFirstRotationIdx = contour->endRotationIdx; } - } - - // If the path has a nonzero number of tessellation vertices, pad them so they align on a - // multiple of the patch size. - assert(path.paddingVertexCount == 0); - if (path.tessVertexCount > 0) - { - if (path.triangulator != nullptr) + assert(path.paddingVertexCount == 0); + // If the path has a nonzero number of tessellation vertices, pad them so they align on + // a multiple of the patch size. + if (path.tessVertexCount > 0) { - path.paddingVertexCount = padding_to_align_up<kOuterCurvePatchSegmentSpan>( - batchBaseVertex + batchTotalTessVertexCount); + path.paddingVertexCount = + tessVertexCounter.countPath<kMidpointFanPatchSegmentSpan>(path.tessVertexCount); + path.tessVertexCount += path.paddingVertexCount; } - else - { - path.paddingVertexCount = padding_to_align_up<kMidpointFanPatchSegmentSpan>( - batchBaseVertex + batchTotalTessVertexCount); - } - path.tessVertexCount += path.paddingVertexCount; - batchTotalTessVertexCount += path.tessVertexCount; } } assert(contourFirstLineIdx == lineCount); @@ -1160,7 +1151,7 @@ if (!m_context->reservePathData(m_pathBatch.size(), contourCount, curveReserveCount, - batchTotalTessVertexCount)) + tessVertexCounter.totalVertexCount())) { // The paths don't fit. Give up and let the caller flush and try again. return false; @@ -1183,7 +1174,6 @@ RIVE_DEBUG_CODE(m_pushedCurveCount = 0;) RIVE_DEBUG_CODE(m_pushedRotationCount = 0;) RIVE_DEBUG_CODE(m_pushedEmptyStrokeCountForCaps = 0;) - RIVE_DEBUG_CODE(size_t batchStartingTessVertexCount = m_context->currentTessVertexCount()); size_t curveIdx = 0; size_t rotationIdx = 0; RawPath::Iter startOfContour; @@ -1217,8 +1207,6 @@ path.paddingVertexCount); RIVE_DEBUG_CODE(++pushedPathCount;) - RIVE_DEBUG_CODE(uint32_t pathStartingTessVertexCount = m_context->currentTessVertexCount();) - if (path.triangulator != nullptr) { // This path is drawn with the interior triangulation algorithm instead. @@ -1231,13 +1219,9 @@ paintType, path.clipID, blendMode); - assert(m_context->currentTessVertexCount() == - pathStartingTessVertexCount + path.tessVertexCount); } else { - RIVE_DEBUG_CODE(uint32_t contourStartingTessVertexCount = - m_context->currentTessVertexCount() + path.paddingVertexCount;) startOfContour = path.rawPath->begin(); for (size_t i = 0; i < path.contourCount; ++i) { @@ -1256,17 +1240,11 @@ assert(m_pushedStrokeJoinCount == (currentPathIdx == strokeIdx ? contour.strokeJoinCount : 0)); assert(m_pushedStrokeCapCount == (contour.strokeCapSegmentCount != 0 ? 2 : 0)); - assert(m_context->currentTessVertexCount() == - contourStartingTessVertexCount + contour.tessVertexCount); curveIdx = contour.endCurveIdx; rotationIdx = contour.endRotationIdx; startOfContour = contour.endOfContour; RIVE_DEBUG_CODE(++pushedContourCount); - RIVE_DEBUG_CODE(contourStartingTessVertexCount = - m_context->currentTessVertexCount();) } - assert(contourStartingTessVertexCount == - pathStartingTessVertexCount + path.tessVertexCount); } } @@ -1281,8 +1259,6 @@ assert(m_pushedLineCount + m_pushedCurveCount + m_pushedEmptyStrokeCountForCaps + interiorTriHelper.patchCount() == curveReserveCount); - assert(m_context->currentTessVertexCount() == - batchStartingTessVertexCount + batchTotalTessVertexCount); return true; }
diff --git a/renderer/shaders/common.glsl b/renderer/shaders/common.glsl index 20f1fe6..1c9d0f9 100644 --- a/renderer/shaders/common.glsl +++ b/renderer/shaders/common.glsl
@@ -7,18 +7,17 @@ #define TESS_TEXTURE_WIDTH 2048. #define TESS_TEXTURE_WIDTH_LOG2 11 -#define FIRST_VERTEX_OF_CONTOUR_FLAG (1u << 31) -#define RETROFITTED_TRIANGLE_FLAG (1u << 30) -#define CULL_EXCESS_TESSELLATION_SEGMENTS_FLAG (1u << 29) -#define JOIN_TYPE_MASK (3u << 27) -#define MITER_CLIP_JOIN (3u << 27) -#define MITER_REVERT_JOIN (2u << 27) -#define BEVEL_JOIN (1u << 27) -#define EMULATED_STROKE_CAP_FLAG (1u << 26) -#define JOIN_TANGENT_0_FLAG (1u << 25) -#define JOIN_TANGENT_INNER_FLAG (1u << 24) -#define LEFT_JOIN_FLAG (1u << 23) -#define RIGHT_JOIN_FLAG (1u << 22) +#define RETROFITTED_TRIANGLE_FLAG (1u << 31) +#define CULL_EXCESS_TESSELLATION_SEGMENTS_FLAG (1u << 30) +#define JOIN_TYPE_MASK (3u << 28) +#define MITER_CLIP_JOIN (3u << 28) +#define MITER_REVERT_JOIN (2u << 28) +#define BEVEL_JOIN (1u << 28) +#define EMULATED_STROKE_CAP_FLAG (1u << 27) +#define JOIN_TANGENT_0_FLAG (1u << 26) +#define JOIN_TANGENT_INNER_FLAG (1u << 25) +#define LEFT_JOIN_FLAG (1u << 24) +#define RIGHT_JOIN_FLAG (1u << 23) #define CONTOUR_ID_MASK 0xffffu #define PI 3.141592653589793238
diff --git a/renderer/shaders/draw.glsl b/renderer/shaders/draw.glsl index fac423f..90c9a6a 100644 --- a/renderer/shaders/draw.glsl +++ b/renderer/shaders/draw.glsl
@@ -104,24 +104,14 @@ int patchSegmentSpan = floatBitsToInt(@a_patchVertexData.w) >> 2; int vertexType = floatBitsToInt(@a_patchVertexData.w) & 3; - // Fetch the tessellation vertex we belong to. - int tessVertexIdx = _instanceID * patchSegmentSpan + localVertexID; + // Fetch a vertex that definitely belongs to the contour we're drawing. + int vertexIDOnContour = min(localVertexID, patchSegmentSpan - 1); + int tessVertexIdx = _instanceID * patchSegmentSpan + vertexIDOnContour; #ifdef @ENABLE_BASE_INSTANCE_POLYFILL tessVertexIdx += @baseInstancePolyfill * patchSegmentSpan; #endif - int2 tessVertexTexelCoord = tessTexelCoord(tessVertexIdx); - uint4 tessVertexData = TEXEL_FETCH(textures, @tessVertexTexture, tessVertexTexelCoord); + uint4 tessVertexData = TEXEL_FETCH(textures, @tessVertexTexture, tessTexelCoord(tessVertexIdx)); uint contourIDWithFlags = tessVertexData.w; - bool isClosingVertexOfContour = localVertexID == patchSegmentSpan && - (contourIDWithFlags & FIRST_VERTEX_OF_CONTOUR_FLAG) != 0u; - if (isClosingVertexOfContour) - { - // The right vertex crossed over into a new contour. Fetch the previous vertex, which will - // be the final vertex of the contour we're trying to draw. - tessVertexTexelCoord = tessTexelCoord(tessVertexIdx - 1); - tessVertexData = TEXEL_FETCH(textures, @tessVertexTexture, tessVertexTexelCoord); - contourIDWithFlags = tessVertexData.w; - } // Fetch and unpack the contour referenced by the tessellation vertex. uint4 contourData = @@ -148,20 +138,33 @@ #else float strokeRadius = uintBitsToFloat(pathData.z); - // Finish unpacking tessVertexData. - if (isClosingVertexOfContour) + // Fix the tessellation vertex if we fetched the wrong one in order to guarantee we got the + // correct contour ID and flags. + if (localVertexID != vertexIDOnContour) { - bool isClosed = strokeRadius == .0 || // filled - midpoint.x != .0; // explicity closed stroke - if (isClosed) + tessVertexIdx += localVertexID - vertexIDOnContour; + uint4 replacementTessVertexData = + TEXEL_FETCH(textures, @tessVertexTexture, tessTexelCoord(tessVertexIdx)); + if ((replacementTessVertexData.w & 0xffffu) != (contourIDWithFlags & 0xffffu)) { - // The contour is closed and we are the closing vertex. Wrap back around full circle and - // re-emit the first tessellation vertex. - int2 vertexTexelCoord0 = tessTexelCoord(int(vertexIndex0)); - tessVertexData = TEXEL_FETCH(textures, @tessVertexTexture, vertexTexelCoord0); - contourIDWithFlags = tessVertexData.w; + // We crossed over into a new contour. Either wrap to the first vertex in the contour or + // leave it clamped at the final vertex of the contour. + bool isClosed = strokeRadius == .0 || // filled + midpoint.x != .0; // explicity closed stroke + if (isClosed) + { + tessVertexData = + TEXEL_FETCH(textures, @tessVertexTexture, tessTexelCoord(int(vertexIndex0))); + } } + else + { + tessVertexData = replacementTessVertexData; + } + contourIDWithFlags = tessVertexData.w; } + + // Finish unpacking tessVertexData. float theta = uintBitsToFloat(tessVertexData.z); float2 norm = float2(sin(theta), -cos(theta)); float2 origin = uintBitsToFloat(tessVertexData.xy);
diff --git a/renderer/shaders/tessellate.glsl b/renderer/shaders/tessellate.glsl index 566a6ff..66c39c7 100644 --- a/renderer/shaders/tessellate.glsl +++ b/renderer/shaders/tessellate.glsl
@@ -185,11 +185,6 @@ float joinSegmentCount = float(joinSegmentCount_and_parametricSegmentCount >> 10); float radsPerPolarSegment = v_args.w; uint contourIDWithFlags = v_contourIDWithFlags; - // PLSRenderer sends down the first curve of each contour with the - // "FIRST_VERTEX_OF_CONTOUR_FLAG" flag. But from here we need to only output this flag for the - // first *vertex* of that first curve. - if (vertexIdx != .0) - contourIDWithFlags &= ~FIRST_VERTEX_OF_CONTOUR_FLAG; // mergedVertexID/mergedSegmentCount are relative to the sub-section of the instance this vertex // belongs to (either the curve section that consists of merged polar and parametric segments,