From 6ce477854d2bb0771a0ea75b4c206605d540a84b Mon Sep 17 00:00:00 2001 From: Hermet Park Date: Thu, 12 Jun 2025 19:37:14 +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/tvgPaint.h | 6 ++- src/renderer/tvgRender.h | 66 +++++++++++++++++++++++- src/renderer/tvgScene.h | 11 +++- src/renderer/wg_engine/tvgWgRenderer.cpp | 6 +++ src/renderer/wg_engine/tvgWgRenderer.h | 1 + 9 files changed, 101 insertions(+), 3 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 fcba1d42..b2c16e89 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -495,6 +495,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/tvgPaint.h b/src/renderer/tvgPaint.h index 68e66885..a34c63cc 100644 --- a/src/renderer/tvgPaint.h +++ b/src/renderer/tvgPaint.h @@ -119,7 +119,6 @@ namespace tvg uint8_t unrefx(bool free) { if (refCnt > 0) --refCnt; - else TVGERR("RENDERER", "Corrupted Reference Count!"); if (free && refCnt == 0) { delete(paint); @@ -129,6 +128,11 @@ namespace tvg return refCnt; } + void damage() + { + if (renderer) renderer->damage(bounds(renderer)); + } + void mark(RenderUpdateFlag flag) { renderFlag |= flag; diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index f45dae6e..71e7d050 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 { @@ -141,6 +140,70 @@ struct RenderRegion uint32_t h() const { return (uint32_t) sh(); } }; +struct RenderDirtyRegion +{ + void add(const RenderRegion& region) + { + if (!disabled && region.valid()) { + ScopedLock lock(key); + 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 + Key key; + uint8_t current = 0; //list index. 0 or 1 + bool disabled = false; + bool skip = false; +}; + struct RenderPath { Array cmds; @@ -437,6 +500,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 ed6f8cde..54c64f05 100644 --- a/src/renderer/tvgScene.h +++ b/src/renderer/tvgScene.h @@ -128,6 +128,9 @@ struct SceneImpl : Scene vport = renderer->viewport(); vdirty = true; + //bounds(renderer) here hinders parallelization. + if (effects) renderer->damage(vport); + return nullptr; } @@ -254,7 +257,10 @@ struct SceneImpl : Scene { auto itr = paints.begin(); while (itr != paints.end()) { - PAINT((*itr))->unref(); + auto paint = PAINT((*itr)); + //when the paint is destroyed damage will be triggered + if (paint->refCnt > 1) paint->damage(); + paint->unref(); paints.erase(itr++); } return Result::Success; @@ -263,6 +269,8 @@ struct SceneImpl : Scene Result remove(Paint* paint) { if (PAINT(paint)->parent != this) return Result::InsufficientCondition; + //when the paint is destroyed damage will be triggered + if (PAINT(paint)->refCnt > 1) PAINT(paint)->damage(); PAINT(paint)->unref(); paints.remove(paint); return Result::Success; @@ -307,6 +315,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 db030c97..abd92c1f 100644 --- a/src/renderer/wg_engine/tvgWgRenderer.cpp +++ b/src/renderer/wg_engine/tvgWgRenderer.cpp @@ -273,6 +273,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;