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-05 11:59:27 +09:00
parent 535ea16b95
commit afeb7c024a
8 changed files with 93 additions and 6 deletions

View file

@ -859,6 +859,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

@ -477,6 +477,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

@ -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<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
uint8_t current = 0; //list index. 0 or 1
bool disabled = false;
bool skip = false;
};
struct RenderPath
{
Array<PathCommand> 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;

View file

@ -105,7 +105,7 @@ struct SceneImpl : Scene
RenderData update(RenderMethod* renderer, const Matrix& transform, Array<RenderData>& 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<CompositionFlag>(compFlag));
cmp = renderer->target(vport, renderer->colorSpace(), static_cast<CompositionFlag>(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;
}

View file

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

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;