diff --git a/.rive_head b/.rive_head index 9bc3b750..0a67fde2 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -56511a2858f8e01481eb23d2cca00cbf5c25fd93 +4566a1208a50671aede1fd1bf30a1806b757c343 diff --git a/include/rive/math/mat2d.hpp b/include/rive/math/mat2d.hpp index dd1414b3..b2a099b5 100644 --- a/include/rive/math/mat2d.hpp +++ b/include/rive/math/mat2d.hpp @@ -66,6 +66,7 @@ class Mat2D static Mat2D compose(const TransformComponents&); float findMaxScale() const; Mat2D scale(Vec2D) const; + Mat2D translate(Vec2D) const; static Mat2D multiply(const Mat2D& a, const Mat2D& b); diff --git a/renderer/include/rive/renderer/draw.hpp b/renderer/include/rive/renderer/draw.hpp index 3b8f219b..42b940b4 100644 --- a/renderer/include/rive/renderer/draw.hpp +++ b/renderer/include/rive/renderer/draw.hpp @@ -44,7 +44,7 @@ class Draw stencilClipReset, }; - Draw(IAABB pixelBounds, const Mat2D&, BlendMode, rcp imageTexture, Type); + Draw(AABB bounds, const Mat2D&, BlendMode, rcp imageTexture, Type); const Texture* imageTexture() const { return m_imageTextureRef; } const IAABB& pixelBounds() const { return m_pixelBounds; } @@ -95,6 +95,7 @@ class Draw protected: const Texture* const m_imageTextureRef; + const AABB m_bounds; const IAABB m_pixelBounds; const Mat2D m_matrix; const BlendMode m_blendMode; @@ -142,7 +143,7 @@ class RiveRenderPathDraw : public Draw void releaseRefs() override; public: - RiveRenderPathDraw(IAABB, + RiveRenderPathDraw(AABB, const Mat2D&, rcp, FillRule, @@ -150,6 +151,15 @@ class RiveRenderPathDraw : public Draw Type, gpu::InterlockMode); + // Copy constructor + RiveRenderPathDraw(const RiveRenderPathDraw&, + float tx, + float ty, + rcp, + FillRule fillRule, + const RiveRenderPaint* paint, + gpu::InterlockMode); + void onPushToRenderContext(RenderContext::LogicalFlush*); const RiveRenderPath* const m_pathRef; diff --git a/renderer/src/draw.cpp b/renderer/src/draw.cpp index ec297d3d..0471e8ca 100644 --- a/renderer/src/draw.cpp +++ b/renderer/src/draw.cpp @@ -295,13 +295,14 @@ RIVE_ALWAYS_INLINE uint32_t join_type_flags(StrokeJoin join) } } // namespace -Draw::Draw(IAABB pixelBounds, +Draw::Draw(AABB bounds, const Mat2D& matrix, BlendMode blendMode, rcp imageTexture, Type type) : m_imageTextureRef(imageTexture.release()), - m_pixelBounds(pixelBounds), + m_bounds(bounds), + m_pixelBounds(bounds.roundOut()), m_matrix(matrix), m_blendMode(blendMode), m_type(type) @@ -426,14 +427,14 @@ DrawUniquePtr RiveRenderPathDraw::Make(RenderContext* context, return DrawUniquePtr(draw); } -RiveRenderPathDraw::RiveRenderPathDraw(IAABB pixelBounds, +RiveRenderPathDraw::RiveRenderPathDraw(AABB bounds, const Mat2D& matrix, rcp path, FillRule fillRule, const RiveRenderPaint* paint, Type type, gpu::InterlockMode frameInterlockMode) : - Draw(pixelBounds, matrix, paint->getBlendMode(), ref_rcp(paint->getImageTexture()), type), + Draw(bounds, matrix, paint->getBlendMode(), ref_rcp(paint->getImageTexture()), type), m_pathRef(path.release()), m_fillRule(paint->getIsStroked() ? FillRule::nonZero : fillRule), m_paintType(paint->getType()) @@ -500,6 +501,48 @@ RiveRenderPathDraw::RiveRenderPathDraw(IAABB pixelBounds, assert(isStroked() == (strokeRadius() > 0)); } +RiveRenderPathDraw::RiveRenderPathDraw(const RiveRenderPathDraw& from, + float tx, + float ty, + rcp path, + FillRule fillRule, + const RiveRenderPaint* paint, + gpu::InterlockMode frameInterlockMode) : + RiveRenderPathDraw( + from.m_bounds.offset(tx - from.m_matrix.tx(), ty - from.m_matrix.ty()), + from.m_matrix.translate(Vec2D(tx - from.m_matrix.tx(), ty - from.m_matrix.ty())), + path, + fillRule, + paint, + from.m_type, + frameInterlockMode) + +{ + m_resourceCounts = from.m_resourceCounts; + m_strokeMatrixMaxScale = from.m_strokeMatrixMaxScale; + + if (isStroked()) + { + m_strokeMatrixMaxScale = from.m_strokeMatrixMaxScale; + m_strokeJoin = from.m_strokeJoin; + m_strokeCap = from.m_strokeCap; + } + m_contours = from.m_contours; + m_numChops = from.m_numChops; + m_chopVertices = from.m_chopVertices; + m_tangentPairs = from.m_tangentPairs; + m_polarSegmentCounts = from.m_polarSegmentCounts; + m_parametricSegmentCounts = from.m_parametricSegmentCounts; + m_triangulator = from.m_triangulator; + + RIVE_DEBUG_CODE(m_pendingLineCount = from.m_pendingLineCount;) + RIVE_DEBUG_CODE(m_pendingCurveCount = from.m_pendingCurveCount;) + RIVE_DEBUG_CODE(m_pendingRotationCount = from.m_pendingRotationCount;) + RIVE_DEBUG_CODE(m_pendingStrokeJoinCount = from.m_pendingStrokeJoinCount;) + RIVE_DEBUG_CODE(m_pendingStrokeCapCount = from.m_pendingStrokeCapCount;) + RIVE_DEBUG_CODE(m_pendingEmptyStrokeCountForCaps = from.m_pendingEmptyStrokeCountForCaps;) +} + void RiveRenderPathDraw::pushToRenderContext(RenderContext::LogicalFlush* flush) { // Make sure the rawPath in our path reference hasn't changed since we began holding! @@ -523,6 +566,7 @@ void RiveRenderPathDraw::pushToRenderContext(RenderContext::LogicalFlush* flush) void RiveRenderPathDraw::releaseRefs() { + m_pathRef->invalidateDrawCache(); Draw::releaseRefs(); RIVE_DEBUG_CODE(m_pathRef->unlockRawPathMutations();) m_pathRef->unref(); diff --git a/renderer/src/render_context.cpp b/renderer/src/render_context.cpp index e1e1eecb..51bd2755 100644 --- a/renderer/src/render_context.cpp +++ b/renderer/src/render_context.cpp @@ -925,7 +925,6 @@ void RenderContext::LogicalFlush::writeResources() for (size_t i = 0; i < m_draws.size(); ++i) { Draw* draw = m_draws[i].get(); - int4 drawBounds = simd::load4i(&m_draws[i]->pixelBounds()); // Add one extra pixel of padding to the draw bounds to make absolutely certain we get diff --git a/renderer/src/rive_render_path.cpp b/renderer/src/rive_render_path.cpp index 38ec2382..66af1496 100644 --- a/renderer/src/rive_render_path.cpp +++ b/renderer/src/rive_render_path.cpp @@ -10,6 +10,7 @@ namespace rive { + RiveRenderPath::RiveRenderPath(FillRule fillRule, RawPath& rawPath) { m_rawPath.swap(rawPath); @@ -163,4 +164,73 @@ uint64_t RiveRenderPath::getRawPathMutationID() const } return m_rawPathMutationID; } + +void RiveRenderPath::setDrawCache(gpu::RiveRenderPathDraw* drawCache, + const Mat2D& mat, + rive::RiveRenderPaint* riveRenderPaint) const +{ + CacheElements& cache = + m_cachedElements[riveRenderPaint->getIsStroked() ? CACHE_STROKED : CACHE_FILLED]; + + cache.draw = drawCache; + + cache.xx = mat.xx(); + cache.xy = mat.xy(); + cache.yx = mat.yx(); + cache.yy = mat.yy(); + + if (riveRenderPaint->getIsStroked()) + { + m_cachedThickness = riveRenderPaint->getThickness(); + m_cachedJoin = riveRenderPaint->getJoin(); + m_cachedCap = riveRenderPaint->getCap(); + } +} + +gpu::DrawUniquePtr RiveRenderPath::getDrawCache(const Mat2D& matrix, + const RiveRenderPaint* paint, + FillRule fillRule, + TrivialBlockAllocator* allocator, + gpu::InterlockMode interlockMode) const +{ + const CacheElements& cache = + m_cachedElements[paint->getIsStroked() ? CACHE_STROKED : CACHE_FILLED]; + + if (cache.draw == nullptr) + { + return nullptr; + } + + if (paint->getIsStroked()) + { + if (m_cachedThickness != paint->getThickness()) + { + return nullptr; + } + + if (m_cachedJoin != paint->getJoin()) + { + return nullptr; + } + + if (m_cachedCap != paint->getCap()) + { + return nullptr; + } + } + + if (matrix.xx() != cache.xx || matrix.xy() != cache.xy || matrix.yx() != cache.yx || + matrix.yy() != cache.yy) + { + return nullptr; + } + + return gpu::DrawUniquePtr(allocator->make(*cache.draw, + matrix.tx(), + matrix.ty(), + ref_rcp(this), + fillRule, + paint, + interlockMode)); +} } // namespace rive diff --git a/renderer/src/rive_render_path.hpp b/renderer/src/rive_render_path.hpp index 58d21e72..5344a9df 100644 --- a/renderer/src/rive_render_path.hpp +++ b/renderer/src/rive_render_path.hpp @@ -6,6 +6,9 @@ #include "rive/math/raw_path.hpp" #include "rive/renderer.hpp" +#include "rive/renderer/draw.hpp" +#include "rive_render_paint.hpp" +#include "../renderer/src/rive_render_path.hpp" namespace rive { @@ -17,7 +20,21 @@ class RiveRenderPath : public lite_rtti_override RiveRenderPath(FillRule fillRule, RawPath& rawPath); void rewind() override; - void fillRule(FillRule rule) override { m_fillRule = rule; } + void fillRule(FillRule rule) override + { + if (m_fillRule == rule) + { + return; + } + m_fillRule = rule; + // Most cached draws can be used interchangeably with any fill rule, but if there is a + // triangulator, it needs to be invalidated when the fill rule changes. + if (m_cachedElements[CACHE_FILLED].draw != nullptr && + m_cachedElements[CACHE_FILLED].draw->triangulator() != nullptr) + { + invalidateDrawCache(CACHE_FILLED); + } + } void moveTo(float x, float y) override; void lineTo(float x, float y) override; @@ -64,5 +81,44 @@ class RiveRenderPath : public lite_rtti_override mutable uint32_t m_dirt = kAllDirt; RIVE_DEBUG_CODE(mutable int m_rawPathMutationLockCount = 0;) + +public: + void invalidateDrawCache() const + { + invalidateDrawCache(CACHE_STROKED); + invalidateDrawCache(CACHE_FILLED); + } + + void invalidateDrawCache(int index) const { m_cachedElements[index].draw = nullptr; } + + void setDrawCache(gpu::RiveRenderPathDraw* drawCache, + const Mat2D& mat, + rive::RiveRenderPaint* riveRenderPaint) const; + + gpu::DrawUniquePtr getDrawCache(const Mat2D& matrix, + const RiveRenderPaint* paint, + FillRule fillRule, + TrivialBlockAllocator* allocator, + gpu::InterlockMode interlockMode) const; + +private: + enum + { + CACHE_STROKED, + CACHE_FILLED, + NUM_CACHES, + }; + struct CacheElements + { + gpu::RiveRenderPathDraw* draw = nullptr; + float xx; + float xy; + float yx; + float yy; + }; + mutable CacheElements m_cachedElements[NUM_CACHES]; + mutable float m_cachedThickness; + mutable StrokeJoin m_cachedJoin; + mutable StrokeCap m_cachedCap; }; } // namespace rive diff --git a/renderer/src/rive_renderer.cpp b/renderer/src/rive_renderer.cpp index 843b9736..9fcabad3 100644 --- a/renderer/src/rive_renderer.cpp +++ b/renderer/src/rive_renderer.cpp @@ -129,12 +129,30 @@ void RiveRenderer::drawPath(RenderPath* renderPath, RenderPaint* renderPaint) return; } - clipAndPushDraw(gpu::RiveRenderPathDraw::Make(m_context, - m_stack.back().matrix, - ref_rcp(path), - path->getFillRule(), - paint, - &m_scratchPath)); + gpu::DrawUniquePtr cacheDraw = path->getDrawCache(m_stack.back().matrix, + paint, + path->getFillRule(), + &m_context->perFrameAllocator(), + m_context->frameInterlockMode()); + + if (cacheDraw != nullptr) + { + clipAndPushDraw(std::move(cacheDraw)); + return; + } + + auto draw = gpu::RiveRenderPathDraw::Make(m_context, + m_stack.back().matrix, + ref_rcp(path), + path->getFillRule(), + paint, + &m_scratchPath); + + path->setDrawCache(static_cast(draw.get()), + m_stack.back().matrix, + paint); + + clipAndPushDraw(std::move(draw)); } void RiveRenderer::clipPath(RenderPath* renderPath) @@ -469,16 +487,32 @@ RiveRenderer::ApplyClipResult RiveRenderer::applyClip(gpu::Draw* draw) { RiveRenderPaint clipUpdatePaint; clipUpdatePaint.clipUpdate(/*clip THIS clipDraw against:*/ lastClipID); - auto clipDraw = gpu::RiveRenderPathDraw::Make(m_context, - clip.matrix, - clip.path, - clip.fillRule, - &clipUpdatePaint, - &m_scratchPath); - if (clipDraw.get() == nullptr) + + gpu::DrawUniquePtr clipDraw = clip.path->getDrawCache(clip.matrix, + &clipUpdatePaint, + clip.fillRule, + &m_context->perFrameAllocator(), + m_context->frameInterlockMode()); + + if (clipDraw == nullptr) { - return ApplyClipResult::clipEmpty; + clipDraw = gpu::RiveRenderPathDraw::Make(m_context, + clip.matrix, + clip.path, + clip.fillRule, + &clipUpdatePaint, + &m_scratchPath); + + if (clipDraw == nullptr) + { + return ApplyClipResult::clipEmpty; + } + + clip.path->setDrawCache(static_cast(clipDraw.get()), + clip.matrix, + &clipUpdatePaint); } + clipDrawBounds = clipDraw->pixelBounds(); // Generate a new clipID every time we (re-)render an element to the clip buffer. // (Each embodiment of the element needs its own separate readBounds.) diff --git a/src/math/mat2d.cpp b/src/math/mat2d.cpp index 0980c8f4..f45c3bb7 100644 --- a/src/math/mat2d.cpp +++ b/src/math/mat2d.cpp @@ -30,6 +30,18 @@ Mat2D Mat2D::scale(Vec2D vec) const }; } +Mat2D Mat2D::translate(Vec2D vec) const +{ + return { + m_buffer[0], + m_buffer[1], + m_buffer[2], + m_buffer[3], + m_buffer[4] + vec.x, + m_buffer[5] + vec.y, + }; +} + Mat2D Mat2D::multiply(const Mat2D& a, const Mat2D& b) { float a0 = a[0], a1 = a[1], a2 = a[2], a3 = a[3], a4 = a[4], a5 = a[5], b0 = b[0], b1 = b[1],