sw_engine: add support for partial rendering

This implements RenderDirtyRegion.

issue: https://github.com/thorvg/thorvg/issues/1747
This commit is contained in:
Hermet Park 2025-06-18 16:02:23 +09:00
parent a005ef2811
commit fc712c9c36
2 changed files with 118 additions and 45 deletions

View file

@ -40,18 +40,21 @@ struct SwTask : Task
{ {
SwSurface* surface = nullptr; SwSurface* surface = nullptr;
SwMpool* mpool = nullptr; SwMpool* mpool = nullptr;
RenderRegion bbox; //Rendering Region RenderRegion bbox[2] = {{}, {}}; //Rendering Region 0:current, 1:prevous
Matrix transform; Matrix transform;
Array<RenderData> clips; Array<RenderData> clips;
RenderUpdateFlag flags = RenderUpdateFlag::None; RenderUpdateFlag flags = RenderUpdateFlag::None;
uint8_t opacity; uint8_t opacity;
bool pushed = false; //Pushed into task list? bool pushed : 1; //Pushed into task list?
bool disposed = false; //Disposed task? bool disposed : 1; //Disposed task?
bool nodirty : 1; //target for partial rendering?
SwTask() : pushed(false), disposed(false) {}
const RenderRegion& bounds() const RenderRegion& bounds()
{ {
done(); done();
return bbox; return bbox[0];
} }
virtual void dispose() = 0; virtual void dispose() = 0;
@ -91,7 +94,7 @@ struct SwShapeTask : SwTask
bool clip(SwRle* target) override bool clip(SwRle* target) override
{ {
if (shape.strokeRle) return rleClip(target, shape.strokeRle); if (shape.strokeRle) return rleClip(target, shape.strokeRle);
if (shape.fastTrack) return rleClip(target, &bbox); if (shape.fastTrack) return rleClip(target, &bbox[0]);
if (shape.rle) return rleClip(target, shape.rle); if (shape.rle) return rleClip(target, shape.rle);
return false; return false;
} }
@ -100,7 +103,7 @@ struct SwShapeTask : SwTask
{ {
//Invisible //Invisible
if (opacity == 0 && !clipper) { if (opacity == 0 && !clipper) {
bbox.reset(); bbox[0].reset();
return; return;
} }
@ -114,7 +117,7 @@ struct SwShapeTask : SwTask
updateFill = (MULTIPLY(rshape->color.a, opacity) || rshape->fill); updateFill = (MULTIPLY(rshape->color.a, opacity) || rshape->fill);
if (updateShape) shapeReset(&shape); if (updateShape) shapeReset(&shape);
if (updateFill || clipper) { if (updateFill || clipper) {
if (shapePrepare(&shape, rshape, transform, bbox, renderBox, mpool, tid, clips.count > 0 ? true : false)) { if (shapePrepare(&shape, rshape, transform, bbox[0], renderBox, mpool, tid, clips.count > 0 ? true : false)) {
if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err; if (!shapeGenRle(&shape, rshape, antialiasing(strokeWidth))) goto err;
} else { } else {
updateFill = false; updateFill = false;
@ -134,7 +137,7 @@ struct SwShapeTask : SwTask
if (updateShape || flags & RenderUpdateFlag::Stroke) { if (updateShape || flags & RenderUpdateFlag::Stroke) {
if (strokeWidth > 0.0f) { if (strokeWidth > 0.0f) {
shapeResetStroke(&shape, rshape, transform); shapeResetStroke(&shape, rshape, transform);
if (!shapeGenStrokeRle(&shape, rshape, transform, bbox, renderBox, mpool, tid)) goto err; if (!shapeGenStrokeRle(&shape, rshape, transform, bbox[0], renderBox, mpool, tid)) goto err;
if (auto fill = rshape->strokeFill()) { if (auto fill = rshape->strokeFill()) {
auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false;
if (ctable) shapeResetStrokeFill(&shape); if (ctable) shapeResetStrokeFill(&shape);
@ -156,12 +159,11 @@ struct SwShapeTask : SwTask
if (!clipShapeRle && !clipStrokeRle) goto err; if (!clipShapeRle && !clipStrokeRle) goto err;
} }
bbox = renderBox; //sync bbox[0] = renderBox; //sync
return; return;
err: err:
bbox.reset(); bbox[0].reset();
shapeReset(&shape); shapeReset(&shape);
rleReset(shape.strokeRle); rleReset(shape.strokeRle);
shapeDelOutline(&shape, mpool, tid); shapeDelOutline(&shape, mpool, tid);
@ -187,7 +189,7 @@ struct SwImageTask : SwTask
void run(unsigned tid) override void run(unsigned tid) override
{ {
auto clipBox = bbox; auto clipBox = bbox[0];
//Convert colorspace if it's not aligned. //Convert colorspace if it's not aligned.
rasterConvertCS(source, surface->cs); rasterConvertCS(source, surface->cs);
@ -203,9 +205,11 @@ struct SwImageTask : SwTask
if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) { if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) {
imageReset(&image); imageReset(&image);
if (!image.data || image.w == 0 || image.h == 0) goto end; if (!image.data || image.w == 0 || image.h == 0) goto end;
if (!imagePrepare(&image, transform, clipBox, bbox, mpool, tid)) goto end;
if (!imagePrepare(&image, transform, clipBox, bbox[0], mpool, tid)) goto end;
if (clips.count > 0) { if (clips.count > 0) {
if (!imageGenRle(&image, bbox, false)) goto end; if (!imageGenRle(&image, bbox[0], false)) goto end;
if (image.rle) { if (image.rle) {
//Clear current task memorypool here if the clippers would use the same memory pool //Clear current task memorypool here if the clippers would use the same memory pool
imageDelOutline(&image, mpool, tid); imageDelOutline(&image, mpool, tid);
@ -219,7 +223,7 @@ struct SwImageTask : SwTask
} }
goto end; goto end;
err: err:
bbox.reset(); bbox[0].reset();
rleReset(image.rle); rleReset(image.rle);
end: end:
imageDelOutline(&image, mpool, tid); imageDelOutline(&image, mpool, tid);
@ -265,7 +269,10 @@ SwRenderer::~SwRenderer()
bool SwRenderer::clear() bool SwRenderer::clear()
{ {
if (surface) return rasterClear(surface, 0, 0, surface->w, surface->h); if (surface) {
fulldraw = true;
return rasterClear(surface, 0, 0, surface->w, surface->h);
}
return false; return false;
} }
@ -320,7 +327,34 @@ bool SwRenderer::postUpdate()
bool SwRenderer::preRender() bool SwRenderer::preRender()
{ {
return surface != nullptr; if (!surface) return false;
if (fulldraw || !dirtyRegion.prepare(tasks.count)) return true;
//collect the old and new dirty regions
constexpr const int32_t GENEROUS_DIST = 5;
ARRAY_FOREACH(p, tasks) {
auto task = *p;
if (task->nodirty) continue;
task->done();
auto& cur = task->bbox[0];
auto& prv = task->bbox[1];
//generous merge if two regions are close enough.
if (abs(cur.min.x - prv.min.x) < GENEROUS_DIST && abs(cur.min.y - prv.min.y) < GENEROUS_DIST) {
dirtyRegion.add(RenderRegion::add(task->bbox[0], task->bbox[1]));
} else {
dirtyRegion.add(task->bbox[0]);
dirtyRegion.add(task->bbox[1]);
}
}
dirtyRegion.commit();
//clear buffer for partial regions
ARRAY_FOREACH(p, dirtyRegion.get()) {
rasterClear(surface, p->x(), p->y(), p->w(), p->h());
}
return true;
} }
@ -343,6 +377,9 @@ bool SwRenderer::postRender()
rasterUnpremultiply(surface); rasterUnpremultiply(surface);
} }
dirtyRegion.clear();
fulldraw = false;
return true; return true;
} }
@ -354,32 +391,47 @@ bool SwRenderer::renderImage(RenderData data)
if (task->opacity == 0) return true; if (task->opacity == 0) return true;
//Outside of the viewport, skip the rendering auto raster = [&](SwSurface* surface, const SwImage& image, const Matrix& transform, const RenderRegion& bbox, uint8_t opacity) {
auto& bbox = task->bbox; if (bbox.invalid() || bbox.x() >= surface->w || bbox.y() >= surface->h) return true;
if (bbox.invalid() || bbox.x() >= surface->w || bbox.y() >= surface->h) return true;
auto& image = task->image; //RLE Image
if (image.rle) {
//RLE Image if (image.direct) return rasterDirectRleImage(surface, image, bbox, opacity);
if (image.rle) { else if (image.scaled) return rasterScaledRleImage(surface, image, transform, bbox, opacity);
if (image.direct) return rasterDirectRleImage(surface, image, bbox, task->opacity); else {
else if (image.scaled) return rasterScaledRleImage(surface, image, task->transform, bbox, task->opacity); //create a intermediate buffer for rle clipping
else { auto cmp = request(sizeof(pixel_t), false);
//create a intermediate buffer for rle clipping cmp->compositor->method = MaskMethod::None;
auto cmp = request(sizeof(pixel_t), false); cmp->compositor->valid = true;
cmp->compositor->method = MaskMethod::None; cmp->compositor->image.rle = image.rle;
cmp->compositor->valid = true; rasterClear(cmp, bbox.x(), bbox.y(), bbox.w(), bbox.h(), 0);
cmp->compositor->image.rle = image.rle; rasterTexmapPolygon(cmp, image, transform, bbox, 255);
rasterClear(cmp, bbox.x(), bbox.y(), bbox.w(), bbox.h(), 0); return rasterDirectRleImage(surface, cmp->compositor->image, bbox, opacity);
rasterTexmapPolygon(cmp, image, task->transform, bbox, 255); }
return rasterDirectRleImage(surface, cmp->compositor->image, bbox, task->opacity); //Whole Image
} else {
if (image.direct) return rasterDirectImage(surface, image, bbox, opacity);
else if (image.scaled) return rasterScaledImage(surface, image, transform, bbox, opacity);
else return rasterTexmapPolygon(surface, image, transform, bbox, opacity);
} }
//Whole Image };
//full scene or partial rendering
if (fulldraw || task->nodirty || task->pushed || dirtyRegion.deactivated()) {
raster(surface, task->image, task->transform, task->bbox[0], task->opacity);
} else { } else {
if (image.direct) return rasterDirectImage(surface, image, bbox, task->opacity); ARRAY_FOREACH(p, dirtyRegion.get()) {
else if (image.scaled) return rasterScaledImage(surface, image, task->transform, bbox, task->opacity); if (task->bbox[0].min.x >= p->max.x) break; //dirtyRegion is sorted in x order
else return rasterTexmapPolygon(surface, image, task->transform, bbox, task->opacity); if (task->bbox[0].intersected(*p)) {
auto bbox = RenderRegion::intersect(task->bbox[0], *p);
raster(surface, task->image, task->transform, bbox, task->opacity);
}
}
} }
task->bbox[1] = task->bbox[0];
return true;
} }
@ -415,14 +467,30 @@ bool SwRenderer::renderShape(RenderData data)
} }
}; };
if (task->rshape->strokeFirst()) { //full scene or partial rendering
stroke(task, surface, task->bbox); if (fulldraw || task->nodirty || task->pushed || dirtyRegion.deactivated()) {
fill(task, surface, task->shape.bbox); if (task->rshape->strokeFirst()) {
stroke(task, surface, task->bbox[0]);
fill(task, surface, task->shape.bbox);
} else {
fill(task, surface, task->shape.bbox);
stroke(task, surface, task->bbox[0]);
}
} else { } else {
fill(task, surface, task->shape.bbox); ARRAY_FOREACH(p, dirtyRegion.get()) {
stroke(task, surface, task->bbox); if (task->bbox[0].min.x >= p->max.x) break; //dirtyRegion is sorted in x order
if (task->rshape->strokeFirst()) {
if (task->rshape->stroke && task->bbox[0].intersected(*p)) stroke(task, surface, RenderRegion::intersect(task->bbox[0], *p));
if (task->shape.bbox.intersected(*p)) fill(task, surface, RenderRegion::intersect(task->shape.bbox, *p));
} else {
if (task->shape.bbox.intersected(*p)) fill(task, surface, RenderRegion::intersect(task->shape.bbox, *p));
if (task->rshape->stroke && task->bbox[0].intersected(*p)) stroke(task, surface, RenderRegion::intersect(task->bbox[0], *p));
}
}
} }
task->bbox[1] = task->bbox[0];
return true; return true;
} }
@ -684,6 +752,9 @@ void SwRenderer::dispose(RenderData data)
task->done(); task->done();
task->dispose(); task->dispose();
//should be updated for the region; the current paint is removed
dirtyRegion.add(task->bbox[0]);
if (task->pushed) task->disposed = true; if (task->pushed) task->disposed = true;
else delete(task); else delete(task);
} }
@ -695,10 +766,11 @@ void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Arr
task->surface = surface; task->surface = surface;
task->mpool = mpool; task->mpool = mpool;
task->bbox = RenderRegion::intersect(vport, {{0, 0}, {int32_t(surface->w), int32_t(surface->h)}}); task->bbox[0] = RenderRegion::intersect(vport, {{0, 0}, {int32_t(surface->w), int32_t(surface->h)}});
task->transform = transform; task->transform = transform;
task->clips = clips; task->clips = clips;
task->opacity = opacity; task->opacity = opacity;
task->nodirty = dirtyRegion.disabled;
task->flags = flags; task->flags = flags;
if (!task->pushed) { if (!task->pushed) {

View file

@ -75,6 +75,7 @@ private:
Array<SwSurface*> compositors; //render targets cache list Array<SwSurface*> compositors; //render targets cache list
SwMpool* mpool; //private memory pool SwMpool* mpool; //private memory pool
bool sharedMpool; //memory-pool behavior policy bool sharedMpool; //memory-pool behavior policy
bool fulldraw = true; //buffer is cleared (need to redraw full screen)
SwRenderer(); SwRenderer();
~SwRenderer(); ~SwRenderer();