diff --git a/.rive_head b/.rive_head index 190ef839..5ba69c37 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -e9918f2496734dac2a3e16b6566dc2fb10ef0434 +edc91a599a7f182007e488b232d64e0166f43139 diff --git a/renderer/include/rive/renderer/draw.hpp b/renderer/include/rive/renderer/draw.hpp index a155e7a0..3b8f219b 100644 --- a/renderer/include/rive/renderer/draw.hpp +++ b/renderer/include/rive/renderer/draw.hpp @@ -137,12 +137,12 @@ class RiveRenderPathDraw : public Draw float strokeRadius() const { return m_strokeRadius; } gpu::ContourDirections contourDirections() const { return m_contourDirections; } - void pushToRenderContext(RenderContext::LogicalFlush*) final; + void pushToRenderContext(RenderContext::LogicalFlush*) override; void releaseRefs() override; public: - RiveRenderPathDraw(IAABB pathBounds, + RiveRenderPathDraw(IAABB, const Mat2D&, rcp, FillRule, @@ -150,7 +150,7 @@ class RiveRenderPathDraw : public Draw Type, gpu::InterlockMode); - virtual void onPushToRenderContext(RenderContext::LogicalFlush*) = 0; + void onPushToRenderContext(RenderContext::LogicalFlush*); const RiveRenderPath* const m_pathRef; const FillRule @@ -161,22 +161,13 @@ class RiveRenderPathDraw : public Draw // Used to guarantee m_pathRef doesn't change for the entire time we hold it. RIVE_DEBUG_CODE(uint64_t m_rawPathMutationID;) -}; -// Draws a path by fanning tessellation patches around the midpoint of each contour. -class MidpointFanPathDraw : public RiveRenderPathDraw -{ public: - MidpointFanPathDraw(RenderContext*, - IAABB pixelBounds, - const Mat2D&, - rcp, - FillRule, - const RiveRenderPaint*); - -protected: - void onPushToRenderContext(RenderContext::LogicalFlush*) override; + // Midpoint path draw + void initForMidpointFan(RenderContext*, const RiveRenderPaint*); +private: + // Draws a path by fanning tessellation patches around the midpoint of each contour. // Emulates a stroke cap before the given cubic by pushing a copy of the cubic, reversed, with 0 // tessellation segments leading up to the join section, and a 180-degree join that looks like // the desired stroke cap. @@ -220,13 +211,10 @@ class MidpointFanPathDraw : public RiveRenderPathDraw RIVE_DEBUG_CODE(size_t m_pendingStrokeCapCount;) // Counts how many additional curves were pushed by pushEmulatedStrokeCapAsJoinBeforeCubic(). RIVE_DEBUG_CODE(size_t m_pendingEmptyStrokeCountForCaps;) -}; -// Draws a path by triangulating the interior into non-overlapping triangles and tessellating the -// outer curves. -class InteriorTriangulationDraw : public RiveRenderPathDraw -{ public: + // Draws a path by triangulating the interior into non-overlapping triangles and tessellating + // the outer curves. enum class TriangulatorAxis { horizontal, @@ -234,38 +222,11 @@ class InteriorTriangulationDraw : public RiveRenderPathDraw dontCare, }; - InteriorTriangulationDraw(RenderContext*, - IAABB pixelBounds, - const Mat2D&, - rcp, - FillRule, - const RiveRenderPaint*, - RawPath* scratchPath, - TriangulatorAxis); - + // Interior Triangulation path draw + void initForInteriorTriangulation(RenderContext*, RawPath*, TriangulatorAxis); GrInnerFanTriangulator* triangulator() const { return m_triangulator; } -protected: - void onPushToRenderContext(RenderContext::LogicalFlush*) override; - - // The final segment in an outerCurve patch is a bowtie join. - constexpr static size_t kJoinSegmentCount = 1; - constexpr static size_t kPatchSegmentCountExcludingJoin = - kOuterCurvePatchSegmentSpan - kJoinSegmentCount; - - // Maximum # of outerCurve patches a curve on the path can be subdivided into. - constexpr static size_t kMaxCurveSubdivisions = - (kMaxParametricSegments + kPatchSegmentCountExcludingJoin - 1) / - kPatchSegmentCountExcludingJoin; - - static uint32_t FindSubdivisionCount(const Vec2D pts[], - const wangs_formula::VectorXform& vectorXform) - { - float numSubdivisions = ceilf(wangs_formula::cubic(pts, kParametricPrecision, vectorXform) * - (1.f / kPatchSegmentCountExcludingJoin)); - return static_cast(math::clamp(numSubdivisions, 1, kMaxCurveSubdivisions)); - } - +private: enum class PathOp : bool { countDataAndTriangulate, diff --git a/renderer/include/rive/renderer/render_context.hpp b/renderer/include/rive/renderer/render_context.hpp index da009e2b..5f49df64 100644 --- a/renderer/include/rive/renderer/render_context.hpp +++ b/renderer/include/rive/renderer/render_context.hpp @@ -29,8 +29,6 @@ class GradientLibrary; class IntersectionBoard; class ImageMeshDraw; class ImageRectDraw; -class InteriorTriangulationDraw; -class MidpointFanPathDraw; class StencilClipReset; class Draw; class Gradient; @@ -256,8 +254,6 @@ class RenderContext : public RiveRenderFactory private: friend class Draw; friend class RiveRenderPathDraw; - friend class MidpointFanPathDraw; - friend class InteriorTriangulationDraw; friend class ImageRectDraw; friend class ImageMeshDraw; friend class StencilClipReset; @@ -527,7 +523,7 @@ class RenderContext : public RiveRenderFactory // Pushes triangles to be drawn using the data records from the most recent calls to // pushPath() and pushPaint(). - void pushInteriorTriangulation(InteriorTriangulationDraw*); + void pushInteriorTriangulation(RiveRenderPathDraw*); // Pushes an imageRect to the draw list. // This should only be used when we don't have bindless textures in atomic mode. Otherwise, diff --git a/renderer/src/draw.cpp b/renderer/src/draw.cpp index 4a0936e9..ec297d3d 100644 --- a/renderer/src/draw.cpp +++ b/renderer/src/draw.cpp @@ -17,6 +17,25 @@ namespace rive::gpu { namespace { + +// The final segment in an outerCurve patch is a bowtie join. +constexpr static size_t kJoinSegmentCount = 1; +constexpr static size_t kPatchSegmentCountExcludingJoin = + kOuterCurvePatchSegmentSpan - kJoinSegmentCount; + +// Maximum # of outerCurve patches a curve on the path can be subdivided into. +constexpr static size_t kMaxCurveSubdivisions = + (kMaxParametricSegments + kPatchSegmentCountExcludingJoin - 1) / + kPatchSegmentCountExcludingJoin; + +static uint32_t FindSubdivisionCount(const Vec2D pts[], + const wangs_formula::VectorXform& vectorXform) +{ + float numSubdivisions = ceilf(wangs_formula::cubic(pts, kParametricPrecision, vectorXform) * + (1.f / kPatchSegmentCountExcludingJoin)); + return static_cast(math::clamp(numSubdivisions, 1, kMaxCurveSubdivisions)); +} + constexpr static int kNumSegmentsInMiterOrBevelJoin = 5; constexpr static int kStrokeStyleFlag = 8; constexpr static int kRoundJoinStyleFlag = kStrokeStyleFlag << 1; @@ -363,6 +382,8 @@ DrawUniquePtr RiveRenderPathDraw::Make(RenderContext* context, mappedBounds = mappedBounds.inset(-strokePixelOutset.width(), -strokePixelOutset.height()); } IAABB pixelBounds = mappedBounds.roundOut(); + bool doTriangulation = false; + const AABB& localBounds = path->getBounds(); if (context->isOutsideCurrentFrame(pixelBounds)) { return DrawUniquePtr(); @@ -371,31 +392,38 @@ DrawUniquePtr RiveRenderPathDraw::Make(RenderContext* context, { // Use interior triangulation to draw filled paths if they're large enough to benefit from // it. - const AABB& localBounds = path->getBounds(); // FIXME! Implement interior triangulation in depthStencil mode. + if (context->frameInterlockMode() != gpu::InterlockMode::depthStencil && path->getRawPath().verbs().count() < 1000 && gpu::FindTransformedArea(localBounds, matrix) > 512 * 512) { - return DrawUniquePtr(context->make( - context, - pixelBounds, - matrix, - std::move(path), - fillRule, - paint, - scratchPath, - localBounds.width() > localBounds.height() - ? InteriorTriangulationDraw::TriangulatorAxis::horizontal - : InteriorTriangulationDraw::TriangulatorAxis::vertical)); + doTriangulation = true; } } - return DrawUniquePtr(context->make(context, - pixelBounds, - matrix, - std::move(path), - fillRule, - paint)); + + auto draw = context->make(pixelBounds, + matrix, + std::move(path), + fillRule, + paint, + doTriangulation ? Type::interiorTriangulationPath + : Type::midpointFanPath, + context->frameInterlockMode()); + if (doTriangulation) + { + draw->initForInteriorTriangulation(context, + scratchPath, + localBounds.width() > localBounds.height() + ? RiveRenderPathDraw::TriangulatorAxis::horizontal + : RiveRenderPathDraw::TriangulatorAxis::vertical); + } + else + { + draw->initForMidpointFan(context, paint); + } + + return DrawUniquePtr(draw); } RiveRenderPathDraw::RiveRenderPathDraw(IAABB pixelBounds, @@ -500,19 +528,7 @@ void RiveRenderPathDraw::releaseRefs() m_pathRef->unref(); } -MidpointFanPathDraw::MidpointFanPathDraw(RenderContext* context, - IAABB pixelBounds, - const Mat2D& matrix, - rcp path, - FillRule fillRule, - const RiveRenderPaint* paint) : - RiveRenderPathDraw(pixelBounds, - matrix, - std::move(path), - fillRule, - paint, - Type::midpointFanPath, - context->frameInterlockMode()) +void RiveRenderPathDraw::initForMidpointFan(RenderContext* context, const RiveRenderPaint* paint) { if (isStroked()) { @@ -1012,8 +1028,24 @@ MidpointFanPathDraw::MidpointFanPathDraw(RenderContext* context, } } -void MidpointFanPathDraw::onPushToRenderContext(RenderContext::LogicalFlush* flush) +void RiveRenderPathDraw::onPushToRenderContext(RenderContext::LogicalFlush* flush) { + if (type() == Type::interiorTriangulationPath) + { + // Interior Triangulation Case + processPath(PathOp::submitOuterCubics, nullptr, nullptr, TriangulatorAxis::dontCare, flush); + if (flush->desc().interlockMode == gpu::InterlockMode::atomics) + { + // We need a barrier between the outer cubics and interior triangles in atomic mode. + flush->pushBarrier(); + } + flush->pushInteriorTriangulation(this); + return; + } + + assert(type() == Type::midpointFanPath); + + // Midpoint Fan Case const RawPath& rawPath = m_pathRef->getRawPath(); RawPath::Iter startOfContour = rawPath.begin(); for (size_t i = 0; i < m_resourceCounts.contourCount; ++i) @@ -1323,10 +1355,10 @@ void MidpointFanPathDraw::onPushToRenderContext(RenderContext::LogicalFlush* flu assert(m_pendingEmptyStrokeCountForCaps == 0); } -void MidpointFanPathDraw::pushEmulatedStrokeCapAsJoinBeforeCubic(RenderContext::LogicalFlush* flush, - const Vec2D cubic[], - uint32_t emulatedCapAsJoinFlags, - uint32_t strokeCapSegmentCount) +void RiveRenderPathDraw::pushEmulatedStrokeCapAsJoinBeforeCubic(RenderContext::LogicalFlush* flush, + const Vec2D cubic[], + uint32_t emulatedCapAsJoinFlags, + uint32_t strokeCapSegmentCount) { // Reverse the cubic and push it with zero parametric and polar segments, and a 180-degree join // tangent. This results in a solitary join, positioned immediately before the provided cubic, @@ -1342,21 +1374,9 @@ void MidpointFanPathDraw::pushEmulatedStrokeCapAsJoinBeforeCubic(RenderContext:: RIVE_DEBUG_CODE(--m_pendingEmptyStrokeCountForCaps;) } -InteriorTriangulationDraw::InteriorTriangulationDraw(RenderContext* context, - IAABB pixelBounds, - const Mat2D& matrix, - rcp path, - FillRule fillRule, - const RiveRenderPaint* paint, - RawPath* scratchPath, - TriangulatorAxis triangulatorAxis) : - RiveRenderPathDraw(pixelBounds, - matrix, - std::move(path), - fillRule, - paint, - Type::interiorTriangulationPath, - context->frameInterlockMode()) +void RiveRenderPathDraw::initForInteriorTriangulation(RenderContext* context, + RawPath* scratchPath, + TriangulatorAxis triangulatorAxis) { assert(!isStroked()); assert(m_strokeRadius == 0); @@ -1367,22 +1387,11 @@ InteriorTriangulationDraw::InteriorTriangulationDraw(RenderContext* context, nullptr); } -void InteriorTriangulationDraw::onPushToRenderContext(RenderContext::LogicalFlush* flush) -{ - processPath(PathOp::submitOuterCubics, nullptr, nullptr, TriangulatorAxis::dontCare, flush); - if (flush->desc().interlockMode == gpu::InterlockMode::atomics) - { - // We need a barrier between the outer cubics and interior triangles in atomic mode. - flush->pushBarrier(); - } - flush->pushInteriorTriangulation(this); -} - -void InteriorTriangulationDraw::processPath(PathOp op, - TrivialBlockAllocator* allocator, - RawPath* scratchPath, - TriangulatorAxis triangulatorAxis, - RenderContext::LogicalFlush* flush) +void RiveRenderPathDraw::processPath(PathOp op, + TrivialBlockAllocator* allocator, + RawPath* scratchPath, + TriangulatorAxis triangulatorAxis, + RenderContext::LogicalFlush* flush) { Vec2D chops[kMaxCurveSubdivisions * 3 + 1]; const RawPath& rawPath = m_pathRef->getRawPath(); diff --git a/renderer/src/render_context.cpp b/renderer/src/render_context.cpp index 142597c8..4d91d49a 100644 --- a/renderer/src/render_context.cpp +++ b/renderer/src/render_context.cpp @@ -1710,7 +1710,7 @@ RIVE_ALWAYS_INLINE void RenderContext::LogicalFlush::pushMirroredAndForwardTesse assert(m_pathMirroredTessLocation >= m_expectedPathMirroredTessLocationAtEndOfPath); } -void RenderContext::LogicalFlush::pushInteriorTriangulation(InteriorTriangulationDraw* draw) +void RenderContext::LogicalFlush::pushInteriorTriangulation(RiveRenderPathDraw* draw) { assert(m_hasDoneLayout); diff --git a/tests/gm/retrofittedcubictriangles.cpp b/tests/gm/retrofittedcubictriangles.cpp index 80bd5111..21053250 100644 --- a/tests/gm/retrofittedcubictriangles.cpp +++ b/tests/gm/retrofittedcubictriangles.cpp @@ -32,36 +32,53 @@ rcp make_nonempty_placeholder_path() class PushRetrofittedTrianglesGMDraw : public RiveRenderPathDraw { public: - PushRetrofittedTrianglesGMDraw(const RiveRenderPaint* paint, gpu::InterlockMode interlockMode) : + PushRetrofittedTrianglesGMDraw(RenderContext* context, const RiveRenderPaint* paint) : RiveRenderPathDraw(kFullscreenPixelBounds, Mat2D(), make_nonempty_placeholder_path(), FillRule::nonZero, paint, Type::interiorTriangulationPath, - interlockMode) + context->frameInterlockMode()) { m_resourceCounts.pathCount = 1; m_resourceCounts.contourCount = 1; m_resourceCounts.maxTessellatedSegmentCount = kNumTriangles; m_resourceCounts.outerCubicTessVertexCount = - interlockMode != gpu::InterlockMode::depthStencil + context->frameInterlockMode() != gpu::InterlockMode::depthStencil ? gpu::kOuterCurvePatchSegmentSpan * kNumTriangles * 2 : gpu::kOuterCurvePatchSegmentSpan * kNumTriangles; } - void onPushToRenderContext(RenderContext::LogicalFlush* flush) override + void pushToRenderContext(RenderContext::LogicalFlush* flush) override { - flush->pushContour({0, 0}, true, 0 /* gpu::kOuterCurvePatchSegmentSpan - 2 */); - for (const auto& pts : kTris) + // Make sure the rawPath in our path reference hasn't changed since we began holding! + assert(m_rawPathMutationID == m_pathRef->getRawPathMutationID()); + assert(!m_pathRef->getRawPath().empty()); + + size_t tessVertexCount = m_type == Type::midpointFanPath + ? m_resourceCounts.midpointFanTessVertexCount + : m_resourceCounts.outerCubicTessVertexCount; + if (tessVertexCount > 0) { - Vec2D tri[4] = {pts[0], pts[1], {0, 0}, pts[2]}; - flush->pushCubic(tri, - {0, 0}, - RETROFITTED_TRIANGLE_CONTOUR_FLAG, - gpu::kOuterCurvePatchSegmentSpan - 1, - 1, - 1); + // Push a path record. + flush->pushPath(this, + m_type == Type::midpointFanPath ? PatchType::midpointFan + : PatchType::outerCurves, + math::lossless_numeric_cast(tessVertexCount)); + + // PushRetrofittedTrianglesGMDraw specific push to render + flush->pushContour({0, 0}, true, 0 /* gpu::kOuterCurvePatchSegmentSpan - 2 */); + for (const auto& pts : kTris) + { + Vec2D tri[4] = {pts[0], pts[1], {0, 0}, pts[2]}; + flush->pushCubic(tri, + {0, 0}, + RETROFITTED_TRIANGLE_CONTOUR_FLAG, + gpu::kOuterCurvePatchSegmentSpan - 1, + 1, + 1); + } } } }; @@ -87,9 +104,8 @@ class RetrofittedCubicTrianglesGM : public GM TestingWindow::Get()->beginFrame(0xff000000, true); RiveRenderPaint paint; paint.color(0xffffffff); - DrawUniquePtr draw(renderContext->make( - &paint, - renderContext->frameInterlockMode())); + DrawUniquePtr draw( + renderContext->make(renderContext, &paint)); bool success RIVE_MAYBE_UNUSED = renderContext->pushDrawBatch(&draw, 1); assert(success); }