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
This commit is contained in:
Hermet Park 2025-06-12 19:37:14 +09:00
parent b22ceaae7c
commit 6ce477854d
9 changed files with 101 additions and 3 deletions

View file

@ -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 {};

View file

@ -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;

View file

@ -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<SwTask*>(data)->bounds();

View file

@ -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;

View file

@ -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;

View file

@ -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<RenderRegion>& get()
{
return list[current];
}
void commit();
private:
void subdivide(Array<RenderRegion>& 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<RenderRegion> 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<PathCommand> 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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;