Stop automatically pruning empty segments in RawPath RawPath used to discard empty segments from lineTo/quadTo/cubicTo. However, it was still possible produce empty segments when transforming a path, and RawPath didn't catch those. This change adds a simple utility, RawPath::pruneEmptySegments(), and passes the responsibility on to the client for using this method when necessary (including after a transform). Diffs= bae069339 Stop automatically pruning empty segments in RawPath (#5557) Co-authored-by: Chris Dalton <99840794+csmartdalton@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 3ec3129..685b6a6 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -2d2d8c4131249c3e82d081cd592466d35ddee744 +bae069339f54793b8d16ec9bcacf5ef19d0f3cc5
diff --git a/renderer/pls_path.cpp b/renderer/pls_path.cpp new file mode 100644 index 0000000..60fdb85 --- /dev/null +++ b/renderer/pls_path.cpp
@@ -0,0 +1,84 @@ +/* + * Copyright 2022 Rive + */ + +#include "pls_path.hpp" + +namespace rive::pls +{ +PLSPath::PLSPath(FillRule fillRule, RawPath& rawPath) +{ + m_rawPath.swap(rawPath); + m_rawPath.pruneEmptySegments(); +} + +void PLSPath::rewind() +{ + m_rawPath.rewind(); + m_boundsDirty = true; +} + +void PLSPath::moveTo(float x, float y) +{ + m_rawPath.moveTo(x, y); + m_boundsDirty = true; +} + +void PLSPath::lineTo(float x, float y) +{ + // Make sure to start a new contour, even if this line is empty. + m_rawPath.injectImplicitMoveIfNeeded(); + + Vec2D p1 = {x, y}; + if (m_rawPath.points().back() != p1) + { + m_rawPath.line(p1); + } + + m_boundsDirty = true; +} + +void PLSPath::cubicTo(float ox, float oy, float ix, float iy, float x, float y) +{ + // Make sure to start a new contour, even if this cubic is empty. + m_rawPath.injectImplicitMoveIfNeeded(); + + Vec2D p1 = {ox, oy}; + Vec2D p2 = {ix, iy}; + Vec2D p3 = {x, y}; + if (m_rawPath.points().back() != p1 || p1 != p2 || p2 != p3) + { + m_rawPath.cubic(p1, p2, p3); + } + + m_boundsDirty = true; +} + +void PLSPath::close() +{ + m_rawPath.close(); + m_boundsDirty = true; +} + +void PLSPath::addRenderPath(RenderPath* path, const Mat2D& matrix) +{ + PLSPath* plsPath = static_cast<PLSPath*>(path); + RawPath::Iter transformedPathIter = m_rawPath.addPath(plsPath->m_rawPath, &matrix); + if (matrix != Mat2D()) + { + // Prune any segments that became empty after the transform. + m_rawPath.pruneEmptySegments(transformedPathIter); + } + m_boundsDirty = true; +} + +const AABB& PLSPath::getBounds() +{ + if (m_boundsDirty) + { + m_bounds = m_rawPath.bounds(); + m_boundsDirty = false; + } + return m_bounds; +} +} // namespace rive::pls
diff --git a/renderer/pls_path.hpp b/renderer/pls_path.hpp index 29a7556..12692a1 100644 --- a/renderer/pls_path.hpp +++ b/renderer/pls_path.hpp
@@ -14,59 +14,23 @@ { public: PLSPath() = default; - PLSPath(FillRule fillRule, RawPath& rawPath) { m_rawPath.swap(rawPath); } + PLSPath(FillRule fillRule, RawPath& rawPath); - void rewind() override - { - m_rawPath.rewind(); - m_boundsDirty = true; - } + void rewind() override; void fillRule(FillRule rule) override { m_fillRule = rule; } - void moveTo(float x, float y) override - { - m_rawPath.moveTo(x, y); - m_boundsDirty = true; - } - void lineTo(float x, float y) override - { - m_rawPath.lineTo(x, y); - m_boundsDirty = true; - } - void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override - { - m_rawPath.cubicTo(ox, oy, ix, iy, x, y); - m_boundsDirty = true; - } - void close() override - { - m_rawPath.close(); - m_boundsDirty = true; - } + void moveTo(float x, float y) override; + void lineTo(float x, float y) override; + void cubicTo(float ox, float oy, float ix, float iy, float x, float y) override; + void close() override; - void addPath(CommandPath* path, const Mat2D& matrix) override - { - addRenderPath(path->renderPath(), matrix); - m_boundsDirty = true; - } - void addRenderPath(RenderPath* path, const Mat2D& matrix) override - { - m_rawPath.addPath(static_cast<PLSPath*>(path)->m_rawPath, &matrix); - m_boundsDirty = true; - } + void addPath(CommandPath* p, const Mat2D& m) override { addRenderPath(p->renderPath(), m); } + void addRenderPath(RenderPath* path, const Mat2D& matrix) override; const RawPath& getRawPath() const { return m_rawPath; } FillRule getFillRule() const { return m_fillRule; } - const AABB& getBounds() - { - if (m_boundsDirty) - { - m_bounds = m_rawPath.bounds(); - m_boundsDirty = false; - } - return m_bounds; - } + const AABB& getBounds(); private: FillRule m_fillRule = FillRule::nonZero;
diff --git a/renderer/pls_renderer.cpp b/renderer/pls_renderer.cpp index 2c2b073..e379969 100644 --- a/renderer/pls_renderer.cpp +++ b/renderer/pls_renderer.cpp
@@ -405,11 +405,12 @@ } // Returns the smallest number that can be added to 'value', such that 'value % alignment' == 0. -RIVE_ALWAYS_INLINE uint32_t padding_to_align_up(uint32_t value, uint32_t alignment) +template <uint32_t Alignment> RIVE_ALWAYS_INLINE uint32_t padding_to_align_up(uint32_t value) { - uint32_t maxMultipleOfAlignment = std::numeric_limits<uint32_t>::max() / alignment * alignment; - uint32_t padding = (maxMultipleOfAlignment - value) % alignment; - assert((value + padding) % alignment == 0); + 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 @@ -419,7 +420,6 @@ class PLSRenderer::InteriorTriangulationHelper { public: - size_t contourCount() const { return m_contourCount; } size_t patchCount() const { return m_patchCount; } bool empty() const { return m_patchCount == 0; } @@ -572,7 +572,6 @@ // We also draw each "grout" triangle using an outerCubic patch. patchCount += path->triangulator->groutList().count(); path->tessVertexCount = patchCount * kOuterCurvePatchSegmentSpan; - m_contourCount += contourCount; m_patchCount += patchCount; } else @@ -591,7 +590,6 @@ } assert(path->paddingVertexCount + patchCount * kOuterCurvePatchSegmentSpan == path->tessVertexCount); - RIVE_DEBUG_CODE(m_writtenContourCount += contourCount;) RIVE_DEBUG_CODE(m_writtenPatchCount += patchCount;) RIVE_DEBUG_CODE(m_writtenTessVertexCount += patchCount * (kPatchSegmentCountExcludingJoin + kJoinSegmentCount);) @@ -601,7 +599,7 @@ #ifdef DEBUG bool didSubmitAllData() { - return m_writtenContourCount == m_contourCount && m_writtenPatchCount == m_patchCount && + return m_writtenPatchCount == m_patchCount && m_writtenTessVertexCount == m_patchCount * kOuterCurvePatchSegmentSpan; } #endif @@ -626,9 +624,7 @@ return std::clamp<size_t>(numSubdivisions, 1, kMaxCurveSubdivisions); } - size_t m_contourCount = 0; size_t m_patchCount = 0; - RIVE_DEBUG_CODE(size_t m_writtenContourCount = 0;) RIVE_DEBUG_CODE(size_t m_writtenPatchCount = 0;) RIVE_DEBUG_CODE(size_t m_writtenTessVertexCount = 0;) }; @@ -945,8 +941,8 @@ // Iteration pass 2: Finish calculating the numbers of tessellation segments in each contour, // using SIMD. - uint32_t tessVertexCount = 0; - uint32_t baseVertex = m_context->currentTessVertexCount(); + uint32_t batchTotalTessVertexCount = 0; + uint32_t batchBaseVertex = m_context->currentTessVertexCount(); size_t contourFirstLineIdx = 0; size_t contourFirstCurveIdx = 0; size_t contourFirstRotationIdx = 0; @@ -961,192 +957,192 @@ continue; } - if (path.triangulator != nullptr) + // (If we used interior triangulation, interiorTriHelper already counted the path's vertices + // for us.) + if (path.triangulator == nullptr) { - // This path will be drawn with interior triangulation. Its tessellation vertex count - // (not including padding) has already been written to 'path.tessVertexCount'. Pad it up - // so its first vertex falls on a multiple of kOuterCurvePatchSegmentSpan. - path.paddingVertexCount = - padding_to_align_up(baseVertex + tessVertexCount, kOuterCurvePatchSegmentSpan); - assert((baseVertex + tessVertexCount + path.paddingVertexCount) % - kOuterCurvePatchSegmentSpan == - 0); - path.tessVertexCount += path.paddingVertexCount; - tessVertexCount += path.tessVertexCount; - continue; - } - - // Align the beginning of the path on a multiple of kMidpointFanPatchSegmentSpan. - path.paddingVertexCount = - padding_to_align_up(baseVertex + tessVertexCount, kMidpointFanPatchSegmentSpan); - path.tessVertexCount = path.paddingVertexCount; - tessVertexCount += path.paddingVertexCount; - assert((baseVertex + tessVertexCount) % kMidpointFanPatchSegmentSpan == 0); - - assert(contour == endContour || contour->pathIdx >= currentPathIdx); - for (; contour != endContour && contour->pathIdx == currentPathIdx; ++contour) - { - size_t contourLineCount = contour->endLineIdx - contourFirstLineIdx; - uint32_t contourVertexCount = - contourLineCount * 2; // Each line tessellates to 2 vertices. - uint4 mergedTessVertexSums4 = 0; - - // Finish calculating and counting parametric segments for each curve. - size_t j; - for (j = contourFirstCurveIdx; j < contour->endCurveIdx; j += 4) + assert(path.tessVertexCount == 0); + assert(contour == endContour || contour->pathIdx >= currentPathIdx); + for (; contour != endContour && contour->pathIdx == currentPathIdx; ++contour) { - assert(j + 4 <= m_parametricSegmentCounts_pow4.capacity()); - // Curves recorded their segment counts raised to the 4th power. Now find their - // roots and convert to integers in batches of 4. - float4 n = simd::load4f(m_parametricSegmentCounts_pow4.get() + j); - n = simd::ceil(simd::sqrt(simd::sqrt(n))); - n = simd::clamp(n, float4(1), float4(kMaxParametricSegments)); - uint4 n_ = simd::cast<uint32_t>(n); - assert(j + 4 <= m_parametricSegmentCounts.capacity()); - simd::store(m_parametricSegmentCounts.get() + j, n_); - mergedTessVertexSums4 += n_; - } - // We counted in batches of 4. Undo the values we counted from beyond the end of the - // path. - while (j-- > contour->endCurveIdx) - { - contourVertexCount -= m_parametricSegmentCounts[j]; - } + size_t contourLineCount = contour->endLineIdx - contourFirstLineIdx; + uint32_t contourVertexCount = + contourLineCount * 2; // Each line tessellates to 2 vertices. + uint4 mergedTessVertexSums4 = 0; - bool stroked = contour->pathIdx == strokeIdx; - if (stroked) - { - // Finish calculating and counting polar segments for each stroked curve and round - // join. - const float r_ = strokeRadius * strokeMatrixMaxScale; - const float polarSegmentsPerRad = - pathutils::CalcPolarSegmentsPerRadian<kPolarPrecision>(r_); - for (j = contourFirstRotationIdx; j < contour->endRotationIdx; j += 4) + // Finish calculating and counting parametric segments for each curve. + size_t j; + for (j = contourFirstCurveIdx; j < contour->endCurveIdx; j += 4) { - // Measure the rotations of curves in batches of 4. - assert(j + 4 <= m_tangentPairs.capacity()); - auto [tx0, ty0, tx1, ty1] = simd::load4x4f(&m_tangentPairs[j][0].x); - float4 numer = tx0 * tx1 + ty0 * ty1; - float4 denom_pow2 = (tx0 * tx0 + ty0 * ty0) * (tx1 * tx1 + ty1 * ty1); - float4 cosTheta = numer / simd::sqrt(denom_pow2); - cosTheta = simd::clamp(cosTheta, float4(-1), float4(1)); - float4 theta = simd::fast_acos(cosTheta); - // Find polar segment counts from the rotation angles. - float4 n = simd::ceil(theta * polarSegmentsPerRad); - n = simd::clamp(n, float4(1), float4(kMaxPolarSegments)); + assert(j + 4 <= m_parametricSegmentCounts_pow4.capacity()); + // Curves recorded their segment counts raised to the 4th power. Now find their + // roots and convert to integers in batches of 4. + float4 n = simd::load4f(m_parametricSegmentCounts_pow4.get() + j); + n = simd::ceil(simd::sqrt(simd::sqrt(n))); + n = simd::clamp(n, float4(1), float4(kMaxParametricSegments)); uint4 n_ = simd::cast<uint32_t>(n); - assert(j + 4 <= m_polarSegmentCounts.capacity()); - simd::store(m_polarSegmentCounts.get() + j, n_); - // Polar and parametric segments share the first and final vertices. Therefore: - // - // parametricVertexCount = parametricSegmentCount + 1 - // - // polarVertexCount = polarVertexCount + 1 - // - // mergedVertexCount = parametricVertexCount + polarVertexCount - 2 - // = parametricSegmentCount + 1 + polarSegmentCount + 1 - 2 - // = parametricSegmentCount + polarSegmentCount - // + assert(j + 4 <= m_parametricSegmentCounts.capacity()); + simd::store(m_parametricSegmentCounts.get() + j, n_); mergedTessVertexSums4 += n_; } - // We counted in batches of 4. Undo the values we counted from beyond the end of the // path. - while (j-- > contour->endRotationIdx) + while (j-- > contour->endCurveIdx) { - contourVertexCount -= m_polarSegmentCounts[j]; + contourVertexCount -= m_parametricSegmentCounts[j]; } - // Count joins. - if (finalPathPaint->getJoin() == StrokeJoin::round) + bool stroked = contour->pathIdx == strokeIdx; + if (stroked) { - // Round joins share their beginning and ending vertices with the curve on - // either side. Therefore, the number of vertices we need to allocate for a - // round join is "joinSegmentCount - 1". Do all the -1's here. - contourVertexCount -= contour->strokeJoinCount; - } - else - { - // The shader needs 3 segments for each miter and bevel join (which translates - // to two interior vertices, since joins share their beginning and ending - // vertices with the curve on either side). - contourVertexCount += - contour->strokeJoinCount * (kNumSegmentsInMiterOrBevelJoin - 1); - } - - // Count stroke caps, if any. - bool empty = contour->endLineIdx == contourFirstLineIdx && - contour->endCurveIdx == contourFirstCurveIdx; - StrokeCap cap; - bool needsCaps; - if (!empty) - { - cap = finalPathPaint->getCap(); - needsCaps = !contour->closed; - } - else - { - cap = empty_stroke_cap(finalPathPaint, contour->closed); - needsCaps = - cap != StrokeCap::butt; // Ignore butt caps when the contour is empty. - } - if (needsCaps) - { - // We emulate stroke caps as 180-degree joins. - if (cap == StrokeCap::round) + // Finish calculating and counting polar segments for each stroked curve and + // round join. + const float r_ = strokeRadius * strokeMatrixMaxScale; + const float polarSegmentsPerRad = + pathutils::CalcPolarSegmentsPerRadian<kPolarPrecision>(r_); + for (j = contourFirstRotationIdx; j < contour->endRotationIdx; j += 4) { - // Round caps rotate 180 degrees. - contour->strokeCapSegmentCount = ceilf(polarSegmentsPerRad * math::PI); - // +2 because round caps emulated as joins need to emit vertices at T=0 and - // T=1, unlike normal round joins. - contour->strokeCapSegmentCount += 2; - // Make sure not to exceed kMaxPolarSegments. - contour->strokeCapSegmentCount = - std::min(contour->strokeCapSegmentCount, kMaxPolarSegments); + // Measure the rotations of curves in batches of 4. + assert(j + 4 <= m_tangentPairs.capacity()); + auto [tx0, ty0, tx1, ty1] = simd::load4x4f(&m_tangentPairs[j][0].x); + float4 numer = tx0 * tx1 + ty0 * ty1; + float4 denom_pow2 = (tx0 * tx0 + ty0 * ty0) * (tx1 * tx1 + ty1 * ty1); + float4 cosTheta = numer / simd::sqrt(denom_pow2); + cosTheta = simd::clamp(cosTheta, float4(-1), float4(1)); + float4 theta = simd::fast_acos(cosTheta); + // Find polar segment counts from the rotation angles. + float4 n = simd::ceil(theta * polarSegmentsPerRad); + n = simd::clamp(n, float4(1), float4(kMaxPolarSegments)); + uint4 n_ = simd::cast<uint32_t>(n); + assert(j + 4 <= m_polarSegmentCounts.capacity()); + simd::store(m_polarSegmentCounts.get() + j, n_); + // Polar and parametric segments share the first and final vertices. + // Therefore: + // + // parametricVertexCount = parametricSegmentCount + 1 + // + // polarVertexCount = polarVertexCount + 1 + // + // mergedVertexCount = parametricVertexCount + polarVertexCount - 2 + // = parametricSegmentCount + 1 + polarSegmentCount + 1 + // - 2 = parametricSegmentCount + polarSegmentCount + // + mergedTessVertexSums4 += n_; + } + + // We counted in batches of 4. Undo the values we counted from beyond the end of + // the path. + while (j-- > contour->endRotationIdx) + { + contourVertexCount -= m_polarSegmentCounts[j]; + } + + // Count joins. + if (finalPathPaint->getJoin() == StrokeJoin::round) + { + // Round joins share their beginning and ending vertices with the curve on + // either side. Therefore, the number of vertices we need to allocate for a + // round join is "joinSegmentCount - 1". Do all the -1's here. + contourVertexCount -= contour->strokeJoinCount; } else { - contour->strokeCapSegmentCount = kNumSegmentsInMiterOrBevelJoin; + // The shader needs 3 segments for each miter and bevel join (which + // translates to two interior vertices, since joins share their beginning + // and ending vertices with the curve on either side). + contourVertexCount += + contour->strokeJoinCount * (kNumSegmentsInMiterOrBevelJoin - 1); } - // pushContour() uses "strokeCapSegmentCount != 0" to tell if it needs stroke - // caps. - assert(contour->strokeCapSegmentCount != 0); - // As long as a contour isn't empty, we can tack the end cap onto the join - // section of the final curve in the stroke. Otherwise, we need to introduce - // 0-tessellation-segment curves with non-empty joins to carry the caps. - emptyStrokeCountForCaps += empty ? 2 : 1; - contourVertexCount += (contour->strokeCapSegmentCount - 1) * 2; + + // Count stroke caps, if any. + bool empty = contour->endLineIdx == contourFirstLineIdx && + contour->endCurveIdx == contourFirstCurveIdx; + StrokeCap cap; + bool needsCaps; + if (!empty) + { + cap = finalPathPaint->getCap(); + needsCaps = !contour->closed; + } + else + { + cap = empty_stroke_cap(finalPathPaint, contour->closed); + needsCaps = + cap != StrokeCap::butt; // Ignore butt caps when the contour is empty. + } + if (needsCaps) + { + // We emulate stroke caps as 180-degree joins. + if (cap == StrokeCap::round) + { + // Round caps rotate 180 degrees. + contour->strokeCapSegmentCount = ceilf(polarSegmentsPerRad * math::PI); + // +2 because round caps emulated as joins need to emit vertices at T=0 + // and T=1, unlike normal round joins. + contour->strokeCapSegmentCount += 2; + // Make sure not to exceed kMaxPolarSegments. + contour->strokeCapSegmentCount = + std::min(contour->strokeCapSegmentCount, kMaxPolarSegments); + } + else + { + contour->strokeCapSegmentCount = kNumSegmentsInMiterOrBevelJoin; + } + // pushContour() uses "strokeCapSegmentCount != 0" to tell if it needs + // stroke caps. + assert(contour->strokeCapSegmentCount != 0); + // As long as a contour isn't empty, we can tack the end cap onto the join + // section of the final curve in the stroke. Otherwise, we need to introduce + // 0-tessellation-segment curves with non-empty joins to carry the caps. + emptyStrokeCountForCaps += empty ? 2 : 1; + contourVertexCount += (contour->strokeCapSegmentCount - 1) * 2; + } } + else + { + // Fills don't have polar segments: + // + // mergedVertexCount = parametricVertexCount = parametricSegmentCount + 1 + // + // Just collect the +1 for each non-stroked curve. + size_t contourCurveCount = contour->endCurveIdx - contourFirstCurveIdx; + contourVertexCount += contourCurveCount; + } + contourVertexCount += simd::sum(mergedTessVertexSums4); + + // Add padding vertices until the number of tessellation vertices in the contour is + // an exact multiple of kMidpointFanPatchSegmentSpan. This ensures that patch + // boundaries align with contour boundaries. + contour->paddingVertexCount = + padding_to_align_up<kMidpointFanPatchSegmentSpan>(contourVertexCount); + contourVertexCount += contour->paddingVertexCount; + assert(contourVertexCount % kMidpointFanPatchSegmentSpan == 0); + RIVE_DEBUG_CODE(contour->tessVertexCount = contourVertexCount;) + + path.tessVertexCount += contourVertexCount; + contourFirstLineIdx = contour->endLineIdx; + 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) + { + path.paddingVertexCount = padding_to_align_up<kOuterCurvePatchSegmentSpan>( + batchBaseVertex + batchTotalTessVertexCount); } else { - // Fills don't have polar segments: - // - // mergedVertexCount = parametricVertexCount = parametricSegmentCount + 1 - // - // Just collect the +1 for each non-stroked curve. - size_t contourCurveCount = contour->endCurveIdx - contourFirstCurveIdx; - contourVertexCount += contourCurveCount; + path.paddingVertexCount = padding_to_align_up<kMidpointFanPatchSegmentSpan>( + batchBaseVertex + batchTotalTessVertexCount); } - contourVertexCount += simd::sum(mergedTessVertexSums4); - - // Add padding vertices until the number of tessellation vertices in the contour is an - // exact multiple of kMidpointFanPatchSegmentSpan. This ensures that patch boundaries - // aligh with contour boundaries. - constexpr uint32_t maxMultipleOfPatchSpan = std::numeric_limits<uint32_t>::max() / - kMidpointFanPatchSegmentSpan * - kMidpointFanPatchSegmentSpan; - contour->paddingVertexCount = - (maxMultipleOfPatchSpan - contourVertexCount) % kMidpointFanPatchSegmentSpan; - contourVertexCount += contour->paddingVertexCount; - assert(contourVertexCount % kMidpointFanPatchSegmentSpan == 0); - RIVE_DEBUG_CODE(contour->tessVertexCount = contourVertexCount;) - - path.tessVertexCount += contourVertexCount; - tessVertexCount += contourVertexCount; - contourFirstLineIdx = contour->endLineIdx; - contourFirstCurveIdx = contour->endCurveIdx; - contourFirstRotationIdx = contour->endRotationIdx; + path.tessVertexCount += path.paddingVertexCount; + batchTotalTessVertexCount += path.tessVertexCount; } } assert(contourFirstLineIdx == lineCount); @@ -1154,11 +1150,15 @@ assert(contourFirstRotationIdx == rotationCount); // Attempt to reserve space on the GPU for our entire batch of paths. - if (!m_context->reservePathData(m_pathBatch.size(), - m_contourBatch.size() + interiorTriHelper.contourCount(), - curveCount + lineCount + emptyStrokeCountForCaps + - interiorTriHelper.patchCount(), - tessVertexCount)) + size_t pathReserveCount = m_pathBatch.size(); + // (Interior triangulation doesn't write contour records to the GPU.) + size_t contourReserveCount = m_contourBatch.size(); + size_t curveReserveCount = + curveCount + lineCount + emptyStrokeCountForCaps + interiorTriHelper.patchCount(); + if (!m_context->reservePathData(pathReserveCount, + contourReserveCount, + curveReserveCount, + batchTotalTessVertexCount)) { // The paths don't fit. Give up and let the caller flush and try again. return false; @@ -1174,7 +1174,9 @@ // Iteration pass 3: Now that we have space reserved, push the whole batch of paths to the GPU. RIVE_DEBUG_CODE(size_t pushedPathCount = 0;) + RIVE_DEBUG_CODE(size_t skippedPathCount = 0;) RIVE_DEBUG_CODE(size_t pushedContourCount = 0;) + RIVE_DEBUG_CODE(size_t skippedContourCount = 0;) RIVE_DEBUG_CODE(m_pushedLineCount = 0;) RIVE_DEBUG_CODE(m_pushedCurveCount = 0;) RIVE_DEBUG_CODE(m_pushedRotationCount = 0;) @@ -1188,11 +1190,19 @@ endContour = m_contourBatch.end(); for (size_t currentPathIdx = 0; currentPathIdx < m_pathBatch.size(); ++currentPathIdx) { + assert(contour == endContour || contour->pathIdx >= currentPathIdx); + PathDraw& path = m_pathBatch[currentPathIdx]; - if (path.rawPath->empty()) + if (path.tessVertexCount == 0) { + for (; contour != endContour && contour->pathIdx == currentPathIdx; ++contour) + { + RIVE_DEBUG_CODE(++skippedContourCount;) + } + RIVE_DEBUG_CODE(++skippedPathCount;) continue; } + assert(!path.rawPath->empty()); // Push a path record. bool isClipPath = currentPathIdx != finalPathIdx; @@ -1210,6 +1220,7 @@ isClipPath ? PaintData{} : paintData, path.tessVertexCount, path.paddingVertexCount); + RIVE_DEBUG_CODE(++pushedPathCount;) RIVE_DEBUG_CODE(uint32_t pathStartingTessVertexCount = m_context->currentTessVertexCount();) @@ -1225,53 +1236,55 @@ blendMode); assert(m_context->currentTessVertexCount() == pathStartingTessVertexCount + path.tessVertexCount); - continue; } - - RIVE_DEBUG_CODE(++pushedPathCount); - startOfContour = path.rawPath->begin(); - - assert(contour == endContour || contour->pathIdx >= currentPathIdx); - RIVE_DEBUG_CODE(uint32_t contourStartingTessVertexCount = - m_context->currentTessVertexCount() + path.paddingVertexCount;) - for (; contour != endContour && contour->pathIdx == currentPathIdx; ++contour) + else { - // Push a contour and curve records. - RIVE_DEBUG_CODE(m_pushedStrokeJoinCount = 0;) - RIVE_DEBUG_CODE(m_pushedStrokeCapCount = 0;) - pushContour(startOfContour, - *contour, - curveIdx, - rotationIdx, - strokeMatrixMaxScale, - currentPathIdx == strokeIdx ? finalPathPaint : nullptr); - assert(m_pushedCurveCount == contour->endCurveIdx); - assert(m_pushedRotationCount == contour->endRotationIdx); - 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();) + RIVE_DEBUG_CODE(uint32_t contourStartingTessVertexCount = + m_context->currentTessVertexCount() + path.paddingVertexCount;) + startOfContour = path.rawPath->begin(); + for (; contour != endContour && contour->pathIdx == currentPathIdx; ++contour) + { + // Push a contour and curve records. + RIVE_DEBUG_CODE(m_pushedStrokeJoinCount = 0;) + RIVE_DEBUG_CODE(m_pushedStrokeCapCount = 0;) + pushContour(startOfContour, + *contour, + curveIdx, + rotationIdx, + strokeMatrixMaxScale, + currentPathIdx == strokeIdx ? finalPathPaint : nullptr); + assert(m_pushedCurveCount == contour->endCurveIdx); + assert(m_pushedRotationCount == contour->endRotationIdx); + 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); } - assert(contourStartingTessVertexCount == - pathStartingTessVertexCount + path.tessVertexCount); } // Make sure we only pushed the amount of data we reserved. - assert(pushedPathCount <= - m_pathBatch.size()); // Empty paths get skipped, so this won't match exactly. - assert(pushedContourCount == m_contourBatch.size()); + assert(pushedPathCount + skippedPathCount == pathReserveCount); + assert(pushedContourCount + skippedContourCount == contourReserveCount); assert(m_pushedLineCount == lineCount); assert(m_pushedCurveCount == curveCount); assert(m_pushedRotationCount == rotationCount); assert(m_pushedEmptyStrokeCountForCaps == emptyStrokeCountForCaps); - assert(m_context->currentTessVertexCount() == batchStartingTessVertexCount + tessVertexCount); assert(interiorTriHelper.didSubmitAllData()); + assert(m_pushedLineCount + m_pushedCurveCount + m_pushedEmptyStrokeCountForCaps + + interiorTriHelper.patchCount() == + curveReserveCount); + assert(m_context->currentTessVertexCount() == + batchStartingTessVertexCount + batchTotalTessVertexCount); return true; }
diff --git a/submodules/rive-cpp b/submodules/rive-cpp index ac7200a..947505e 160000 --- a/submodules/rive-cpp +++ b/submodules/rive-cpp
@@ -1 +1 @@ -Subproject commit ac7200aaa7fc1f5b056b434784d454e4b1256731 +Subproject commit 947505eac23cb69c0c4dcefd65e19d8f620c8d32