From a38494152380f263a935d84ac06cb3a97c06cdb0 Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Thu, 5 Jun 2025 11:59:27 +0900 Subject: [PATCH] renderer: add partial rendering support Partial Rendering refers to a rendering technique where only a portion of the scene or screen is updated, rather than redrawing the entire output. It is commonly used as a performance optimization strategy, focusing on redrawing only the regions that have changed, often called dirty regions. This introduces RenderDirtyRegion, which assists in collecting a compact dirty region from render tasks. Each backend can utilize this class to support efficient partial rendering. This is implemented using a Line Sweep and Subdivision Merging O(NlogN). The basic per-frame workflow is as follows: 1. RenderDirtyRegion::prepare() //Call this in Renderer::preRender(). 2. RenderDirtyRegion::add() //Add all dirty paints for the frame before rendering. 3. RenderDirtyRegion::commit() //Generate the partial rendering region list before rendering. 4. RenderDirtyRegion::get() //Retrieve the current dirty region list and use it when drawing paints. 5. RenderDirtyRegion::clear() //Reset the state. issue: https://github.com/thorvg/thorvg/issues/1747 --- src/renderer/gl_engine/tvgGlRenderer.cpp | 6 +++ src/renderer/gl_engine/tvgGlRenderer.h | 1 + src/renderer/sw_engine/tvgSwRenderer.cpp | 6 +++ src/renderer/sw_engine/tvgSwRenderer.h | 1 + src/renderer/tvgRender.h | 64 +++++++++++++++++++++++- src/renderer/tvgScene.h | 14 ++++-- src/renderer/wg_engine/tvgWgRenderer.cpp | 6 +++ src/renderer/wg_engine/tvgWgRenderer.h | 1 + 8 files changed, 93 insertions(+), 6 deletions(-) diff --git a/src/renderer/gl_engine/tvgGlRenderer.cpp b/src/renderer/gl_engine/tvgGlRenderer.cpp index 8a2db298..771ed5ce 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.cpp +++ b/src/renderer/gl_engine/tvgGlRenderer.cpp @@ -862,6 +862,12 @@ bool GlRenderer::sync() } +void GlRenderer::damage(TVG_UNUSED const RenderRegion& region) +{ + //TODO: +} + + RenderRegion GlRenderer::region(RenderData data) { if (currentPass()->isEmpty()) return {}; diff --git a/src/renderer/gl_engine/tvgGlRenderer.h b/src/renderer/gl_engine/tvgGlRenderer.h index 1f29c5fa..29a64f91 100644 --- a/src/renderer/gl_engine/tvgGlRenderer.h +++ b/src/renderer/gl_engine/tvgGlRenderer.h @@ -80,6 +80,7 @@ public: bool postRender() override; void dispose(RenderData data) override;; RenderRegion region(RenderData data) override; + void damage(const RenderRegion& region) override; RenderRegion viewport() override; bool viewport(const RenderRegion& vp) override; bool blend(BlendMethod method) override; diff --git a/src/renderer/sw_engine/tvgSwRenderer.cpp b/src/renderer/sw_engine/tvgSwRenderer.cpp index 83fde987..4ca4f3b7 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -501,6 +501,12 @@ bool SwRenderer::blend(BlendMethod method) } +void SwRenderer::damage(TVG_UNUSED const RenderRegion& region) +{ + //TODO: +} + + RenderRegion SwRenderer::region(RenderData data) { return static_cast(data)->bounds(); diff --git a/src/renderer/sw_engine/tvgSwRenderer.h b/src/renderer/sw_engine/tvgSwRenderer.h index 40a6c428..95671e0c 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.h +++ b/src/renderer/sw_engine/tvgSwRenderer.h @@ -46,6 +46,7 @@ public: bool postRender() override; void dispose(RenderData data) override; RenderRegion region(RenderData data) override; + void damage(const RenderRegion& region) override; RenderRegion viewport() override; bool viewport(const RenderRegion& vp) override; bool blend(BlendMethod method) override; diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index 46bd0e82..9f18aa62 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -50,7 +50,6 @@ static inline RenderUpdateFlag operator|(const RenderUpdateFlag a, const RenderU return RenderUpdateFlag(uint16_t(a) | uint16_t(b)); } - struct RenderSurface { union { @@ -137,6 +136,68 @@ struct RenderRegion uint32_t h() const { return (uint32_t) sh(); } }; +struct RenderDirtyRegion +{ + void add(const RenderRegion& region) + { + if (!disabled && region.valid()) { + list[current].push(region); + } + } + + bool prepare(uint32_t count = 0) + { + if (disabled) return false; + + if (count > THRESHOLD) { + skip = true; + return false; + } + + count *= 120; //FIXME: enough? + + list[0].reserve(count); + list[1].reserve(count); + + return true; + } + + bool deactivated() + { + if (disabled || skip) return true; + return false; + } + + void clear() + { + list[0].clear(); + list[1].clear(); + skip = false; + } + + const Array& get() + { + return list[current]; + } + + void commit(); + +private: + void subdivide(Array& targets, uint32_t idx, RenderRegion& lhs, RenderRegion& rhs); + + /* We deactivate partial rendering if there are more than N moving elements. + Imagine thousands of moving objects covering the entire screen, That case partial rendering will lose any benefits. + Even if they don't, the overhead of subdividing and merging partial regions + could be more expensive than simply rendering the full screen. + The number is experimentally confirmed and we are open to improve this. */ + static constexpr const uint32_t THRESHOLD = 5000; + + Array list[2]; //double buffer swapping + uint8_t current = 0; //list index. 0 or 1 + bool disabled = false; + bool skip = false; +}; + struct RenderPath { Array cmds; @@ -433,6 +494,7 @@ public: virtual bool renderImage(RenderData data) = 0; virtual bool postRender() = 0; virtual void dispose(RenderData data) = 0; + virtual void damage(const RenderRegion& region) = 0; virtual RenderRegion region(RenderData data) = 0; virtual RenderRegion viewport() = 0; virtual bool viewport(const RenderRegion& vp) = 0; diff --git a/src/renderer/tvgScene.h b/src/renderer/tvgScene.h index 506bb876..6ce29c94 100644 --- a/src/renderer/tvgScene.h +++ b/src/renderer/tvgScene.h @@ -105,7 +105,7 @@ struct SceneImpl : Scene RenderData update(RenderMethod* renderer, const Matrix& transform, Array& clips, uint8_t opacity, RenderUpdateFlag flag, TVG_UNUSED bool clipper) { - this->vport = renderer->viewport(); + vport = renderer->viewport(); if (needComposition(opacity)) { /* Overriding opacity value. If this scene is half-translucent, @@ -123,6 +123,9 @@ struct SceneImpl : Scene } } + if (compFlag) vport = bounds(renderer); + if (effects) renderer->damage(vport); + return nullptr; } @@ -134,7 +137,7 @@ struct SceneImpl : Scene renderer->blend(impl.blendMethod); if (compFlag) { - cmp = renderer->target(bounds(renderer), renderer->colorSpace(), static_cast(compFlag)); + cmp = renderer->target(vport, renderer->colorSpace(), static_cast(compFlag)); renderer->beginComposite(cmp, MaskMethod::None, opacity); } @@ -157,7 +160,7 @@ struct SceneImpl : Scene return ret; } - RenderRegion bounds(RenderMethod* renderer) const + RenderRegion bounds(RenderMethod* renderer) { if (paints.empty()) return {}; @@ -185,8 +188,8 @@ struct SceneImpl : Scene pRegion.max.x += eRegion.max.x; pRegion.max.y += eRegion.max.y; - pRegion.intersect(this->vport); - return pRegion; + vport = RenderRegion::intersect(renderer->viewport(), pRegion); + return vport; } Result bounds(Point* pt4, Matrix& m, bool obb, bool stroking) @@ -298,6 +301,7 @@ struct SceneImpl : Scene } delete(effects); effects = nullptr; + impl.renderer->damage(vport); } return Result::Success; } diff --git a/src/renderer/wg_engine/tvgWgRenderer.cpp b/src/renderer/wg_engine/tvgWgRenderer.cpp index af860865..a203e9ff 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -274,6 +274,12 @@ void WgRenderer::dispose(RenderData data) { } +void WgRenderer::damage(TVG_UNUSED const RenderRegion& region) +{ + //TODO: +} + + RenderRegion WgRenderer::region(RenderData data) { auto renderData = (WgRenderDataPaint*)data; diff --git a/src/renderer/wg_engine/tvgWgRenderer.h b/src/renderer/wg_engine/tvgWgRenderer.h index 50825279..317828ad 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.h +++ b/src/renderer/wg_engine/tvgWgRenderer.h @@ -38,6 +38,7 @@ public: bool postRender() override; void dispose(RenderData data) override; RenderRegion region(RenderData data) override; + void damage(const RenderRegion& region) override; RenderRegion viewport() override; bool viewport(const RenderRegion& vp) override; bool blend(BlendMethod method) override;