mirror of
https://github.com/thorvg/thorvg.git
synced 2025-06-23 16:35:59 +00:00
sw_engine: add support for partial rendering
This implements partial rendering with RenderDirtyRegion interfaces issue: https://github.com/thorvg/thorvg/issues/1747
This commit is contained in:
parent
b98514a51b
commit
9a7e636089
2 changed files with 120 additions and 45 deletions
|
@ -40,18 +40,23 @@ struct SwTask : Task
|
||||||
{
|
{
|
||||||
SwSurface* surface = nullptr;
|
SwSurface* surface = nullptr;
|
||||||
SwMpool* mpool = nullptr;
|
SwMpool* mpool = nullptr;
|
||||||
RenderRegion bbox; //Rendering Region
|
RenderRegion curBox = {}; //current rendering region
|
||||||
|
RenderRegion prvBox = {}; //previous rendering region
|
||||||
Matrix transform;
|
Matrix transform;
|
||||||
Array<RenderData> clips;
|
Array<RenderData> clips;
|
||||||
|
RenderDirtyRegion* dirtyRegion;
|
||||||
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 curBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void dispose() = 0;
|
virtual void dispose() = 0;
|
||||||
|
@ -91,7 +96,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, &curBox);
|
||||||
if (shape.rle) return rleClip(target, shape.rle);
|
if (shape.rle) return rleClip(target, shape.rle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -100,7 +105,7 @@ struct SwShapeTask : SwTask
|
||||||
{
|
{
|
||||||
//Invisible
|
//Invisible
|
||||||
if (opacity == 0 && !clipper) {
|
if (opacity == 0 && !clipper) {
|
||||||
bbox.reset();
|
curBox.reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +119,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, curBox, 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 +139,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, curBox, 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,15 +161,16 @@ struct SwShapeTask : SwTask
|
||||||
if (!clipShapeRle && !clipStrokeRle) goto err;
|
if (!clipShapeRle && !clipStrokeRle) goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
bbox = renderBox; //sync
|
curBox = renderBox; //sync
|
||||||
|
dirtyRegion->add(prvBox, curBox);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
bbox.reset();
|
curBox.reset();
|
||||||
shapeReset(&shape);
|
shapeReset(&shape);
|
||||||
rleReset(shape.strokeRle);
|
rleReset(shape.strokeRle);
|
||||||
shapeDelOutline(&shape, mpool, tid);
|
shapeDelOutline(&shape, mpool, tid);
|
||||||
|
dirtyRegion->add(prvBox, curBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() override
|
void dispose() override
|
||||||
|
@ -187,7 +193,7 @@ struct SwImageTask : SwTask
|
||||||
|
|
||||||
void run(unsigned tid) override
|
void run(unsigned tid) override
|
||||||
{
|
{
|
||||||
auto clipBox = bbox;
|
auto clipBox = curBox;
|
||||||
|
|
||||||
//Convert colorspace if it's not aligned.
|
//Convert colorspace if it's not aligned.
|
||||||
rasterConvertCS(source, surface->cs);
|
rasterConvertCS(source, surface->cs);
|
||||||
|
@ -203,9 +209,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, curBox, mpool, tid)) goto end;
|
||||||
|
|
||||||
if (clips.count > 0) {
|
if (clips.count > 0) {
|
||||||
if (!imageGenRle(&image, bbox, false)) goto end;
|
if (!imageGenRle(&image, curBox, 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);
|
||||||
|
@ -213,16 +221,18 @@ struct SwImageTask : SwTask
|
||||||
auto clipper = static_cast<SwTask*>(*p);
|
auto clipper = static_cast<SwTask*>(*p);
|
||||||
if (!clipper->clip(image.rle)) goto err;
|
if (!clipper->clip(image.rle)) goto err;
|
||||||
}
|
}
|
||||||
|
dirtyRegion->add(prvBox, curBox);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
goto end;
|
goto end;
|
||||||
err:
|
err:
|
||||||
bbox.reset();
|
curBox.reset();
|
||||||
rleReset(image.rle);
|
rleReset(image.rle);
|
||||||
end:
|
end:
|
||||||
imageDelOutline(&image, mpool, tid);
|
imageDelOutline(&image, mpool, tid);
|
||||||
|
dirtyRegion->add(prvBox, curBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() override
|
void dispose() override
|
||||||
|
@ -265,7 +275,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,6 +315,10 @@ bool SwRenderer::target(pixel_t* data, uint32_t stride, uint32_t w, uint32_t h,
|
||||||
surface->channelSize = CHANNEL_SIZE(cs);
|
surface->channelSize = CHANNEL_SIZE(cs);
|
||||||
surface->premultiplied = true;
|
surface->premultiplied = true;
|
||||||
|
|
||||||
|
dirtyRegion.init(w, h);
|
||||||
|
|
||||||
|
fulldraw = true; //reset the screen
|
||||||
|
|
||||||
return rasterCompositor(surface);
|
return rasterCompositor(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,7 +337,19 @@ bool SwRenderer::postUpdate()
|
||||||
|
|
||||||
bool SwRenderer::preRender()
|
bool SwRenderer::preRender()
|
||||||
{
|
{
|
||||||
return surface != nullptr;
|
if (!surface) return false;
|
||||||
|
if (fulldraw || dirtyRegion.disabled) return true;
|
||||||
|
|
||||||
|
dirtyRegion.commit();
|
||||||
|
|
||||||
|
//clear buffer for partial regions
|
||||||
|
for (int idx = 0; idx < RenderDirtyRegion::PARTITIONING; ++idx) {
|
||||||
|
ARRAY_FOREACH(p, dirtyRegion.get(idx)) {
|
||||||
|
rasterClear(surface, p->x(), p->y(), p->w(), p->h());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -343,6 +372,9 @@ bool SwRenderer::postRender()
|
||||||
rasterUnpremultiply(surface);
|
rasterUnpremultiply(surface);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dirtyRegion.clear();
|
||||||
|
fulldraw = false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,32 +386,50 @@ 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->curBox, task->opacity);
|
||||||
} else {
|
} else {
|
||||||
if (image.direct) return rasterDirectImage(surface, image, bbox, task->opacity);
|
for (int idx = 0; idx < RenderDirtyRegion::PARTITIONING; ++idx) {
|
||||||
else if (image.scaled) return rasterScaledImage(surface, image, task->transform, bbox, task->opacity);
|
if (!dirtyRegion.partition(idx).intersected(task->curBox)) continue;
|
||||||
else return rasterTexmapPolygon(surface, image, task->transform, bbox, task->opacity);
|
ARRAY_FOREACH(p, dirtyRegion.get(idx)) {
|
||||||
|
if (task->curBox.min.x >= p->max.x) break; //dirtyRegion is sorted in x order
|
||||||
|
if (task->curBox.intersected(*p)) {
|
||||||
|
auto bbox = RenderRegion::intersect(task->curBox, *p);
|
||||||
|
raster(surface, task->image, task->transform, bbox, task->opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task->prvBox = task->curBox;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -415,14 +465,33 @@ 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->curBox);
|
||||||
|
fill(task, surface, task->shape.bbox);
|
||||||
|
} else {
|
||||||
|
fill(task, surface, task->shape.bbox);
|
||||||
|
stroke(task, surface, task->curBox);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fill(task, surface, task->shape.bbox);
|
for (int idx = 0; idx < RenderDirtyRegion::PARTITIONING; ++idx) {
|
||||||
stroke(task, surface, task->bbox);
|
if (!dirtyRegion.partition(idx).intersected(task->curBox)) continue;
|
||||||
|
ARRAY_FOREACH(p, dirtyRegion.get(idx)) {
|
||||||
|
if (task->curBox.min.x >= p->max.x) break; //dirtyRegion is sorted in x order
|
||||||
|
if (task->rshape->strokeFirst()) {
|
||||||
|
if (task->rshape->stroke && task->curBox.intersected(*p)) stroke(task, surface, RenderRegion::intersect(task->curBox, *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->curBox.intersected(*p)) stroke(task, surface, RenderRegion::intersect(task->curBox, *p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task->prvBox = task->curBox;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -684,6 +753,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->curBox);
|
||||||
|
|
||||||
if (task->pushed) task->disposed = true;
|
if (task->pushed) task->disposed = true;
|
||||||
else delete(task);
|
else delete(task);
|
||||||
}
|
}
|
||||||
|
@ -695,10 +767,12 @@ 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->curBox = 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->dirtyRegion = &dirtyRegion;
|
||||||
task->opacity = opacity;
|
task->opacity = opacity;
|
||||||
|
task->nodirty = dirtyRegion.disabled;
|
||||||
task->flags = flags;
|
task->flags = flags;
|
||||||
|
|
||||||
if (!task->pushed) {
|
if (!task->pushed) {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue