diff --git a/src/renderer/sw_engine/tvgSwRenderer.cpp b/src/renderer/sw_engine/tvgSwRenderer.cpp index e44b7e27..2b760509 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.cpp +++ b/src/renderer/sw_engine/tvgSwRenderer.cpp @@ -40,18 +40,21 @@ struct SwTask : Task { SwSurface* surface = nullptr; SwMpool* mpool = nullptr; - RenderRegion bbox; //Rendering Region + RenderRegion bbox[2] = {{}, {}}; //Rendering Region 0:current, 1:prevous Matrix transform; Array clips; RenderUpdateFlag flags = RenderUpdateFlag::None; uint8_t opacity; - bool pushed = false; //Pushed into task list? - bool disposed = false; //Disposed task? + bool pushed : 1; //Pushed into task list? + bool disposed : 1; //Disposed task? + bool nodirty : 1; //target for partial rendering? + + SwTask() : pushed(false), disposed(false) {} const RenderRegion& bounds() { done(); - return bbox; + return bbox[0]; } virtual void dispose() = 0; @@ -91,7 +94,7 @@ struct SwShapeTask : SwTask bool clip(SwRle* target) override { 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); return false; } @@ -100,7 +103,7 @@ struct SwShapeTask : SwTask { //Invisible if (opacity == 0 && !clipper) { - bbox.reset(); + bbox[0].reset(); return; } @@ -114,7 +117,7 @@ struct SwShapeTask : SwTask updateFill = (MULTIPLY(rshape->color.a, opacity) || rshape->fill); if (updateShape) shapeReset(&shape); 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; } else { updateFill = false; @@ -134,7 +137,7 @@ struct SwShapeTask : SwTask if (updateShape || flags & RenderUpdateFlag::Stroke) { if (strokeWidth > 0.0f) { 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()) { auto ctable = (flags & RenderUpdateFlag::GradientStroke) ? true : false; if (ctable) shapeResetStrokeFill(&shape); @@ -156,12 +159,11 @@ struct SwShapeTask : SwTask if (!clipShapeRle && !clipStrokeRle) goto err; } - bbox = renderBox; //sync - + bbox[0] = renderBox; //sync return; err: - bbox.reset(); + bbox[0].reset(); shapeReset(&shape); rleReset(shape.strokeRle); shapeDelOutline(&shape, mpool, tid); @@ -187,7 +189,7 @@ struct SwImageTask : SwTask void run(unsigned tid) override { - auto clipBox = bbox; + auto clipBox = bbox[0]; //Convert colorspace if it's not aligned. rasterConvertCS(source, surface->cs); @@ -203,9 +205,11 @@ struct SwImageTask : SwTask if ((flags & (RenderUpdateFlag::Image | RenderUpdateFlag::Transform | RenderUpdateFlag::Color)) && (opacity > 0)) { imageReset(&image); 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 (!imageGenRle(&image, bbox, false)) goto end; + if (!imageGenRle(&image, bbox[0], false)) goto end; if (image.rle) { //Clear current task memorypool here if the clippers would use the same memory pool imageDelOutline(&image, mpool, tid); @@ -219,7 +223,7 @@ struct SwImageTask : SwTask } goto end; err: - bbox.reset(); + bbox[0].reset(); rleReset(image.rle); end: imageDelOutline(&image, mpool, tid); @@ -265,7 +269,10 @@ SwRenderer::~SwRenderer() 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; } @@ -320,7 +327,34 @@ bool SwRenderer::postUpdate() 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); } + dirtyRegion.clear(); + fulldraw = false; + return true; } @@ -354,32 +391,47 @@ bool SwRenderer::renderImage(RenderData data) if (task->opacity == 0) return true; - //Outside of the viewport, skip the rendering - auto& bbox = task->bbox; - if (bbox.invalid() || bbox.x() >= surface->w || bbox.y() >= surface->h) return true; + auto raster = [&](SwSurface* surface, const SwImage& image, const Matrix& transform, const RenderRegion& bbox, uint8_t opacity) { + if (bbox.invalid() || bbox.x() >= surface->w || bbox.y() >= surface->h) return true; - auto& image = task->image; - - //RLE Image - if (image.rle) { - if (image.direct) return rasterDirectRleImage(surface, image, bbox, task->opacity); - else if (image.scaled) return rasterScaledRleImage(surface, image, task->transform, bbox, task->opacity); - else { - //create a intermediate buffer for rle clipping - auto cmp = request(sizeof(pixel_t), false); - cmp->compositor->method = MaskMethod::None; - cmp->compositor->valid = true; - cmp->compositor->image.rle = image.rle; - rasterClear(cmp, bbox.x(), bbox.y(), bbox.w(), bbox.h(), 0); - rasterTexmapPolygon(cmp, image, task->transform, bbox, 255); - return rasterDirectRleImage(surface, cmp->compositor->image, bbox, task->opacity); + //RLE Image + if (image.rle) { + if (image.direct) return rasterDirectRleImage(surface, image, bbox, opacity); + else if (image.scaled) return rasterScaledRleImage(surface, image, transform, bbox, opacity); + else { + //create a intermediate buffer for rle clipping + auto cmp = request(sizeof(pixel_t), false); + cmp->compositor->method = MaskMethod::None; + cmp->compositor->valid = true; + cmp->compositor->image.rle = image.rle; + rasterClear(cmp, bbox.x(), bbox.y(), bbox.w(), bbox.h(), 0); + rasterTexmapPolygon(cmp, image, transform, bbox, 255); + return rasterDirectRleImage(surface, cmp->compositor->image, bbox, 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 { - if (image.direct) return rasterDirectImage(surface, image, bbox, task->opacity); - else if (image.scaled) return rasterScaledImage(surface, image, task->transform, bbox, task->opacity); - else return rasterTexmapPolygon(surface, image, task->transform, bbox, task->opacity); + ARRAY_FOREACH(p, dirtyRegion.get()) { + if (task->bbox[0].min.x >= p->max.x) break; //dirtyRegion is sorted in x order + 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()) { - stroke(task, surface, task->bbox); - fill(task, surface, task->shape.bbox); + //full scene or partial rendering + if (fulldraw || task->nodirty || task->pushed || dirtyRegion.deactivated()) { + 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 { - fill(task, surface, task->shape.bbox); - stroke(task, surface, task->bbox); + ARRAY_FOREACH(p, dirtyRegion.get()) { + 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; } @@ -684,6 +752,9 @@ void SwRenderer::dispose(RenderData data) task->done(); task->dispose(); + //should be updated for the region; the current paint is removed + dirtyRegion.add(task->bbox[0]); + if (task->pushed) task->disposed = true; else delete(task); } @@ -695,10 +766,11 @@ void* SwRenderer::prepareCommon(SwTask* task, const Matrix& transform, const Arr task->surface = surface; 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->clips = clips; task->opacity = opacity; + task->nodirty = dirtyRegion.disabled; task->flags = flags; if (!task->pushed) { diff --git a/src/renderer/sw_engine/tvgSwRenderer.h b/src/renderer/sw_engine/tvgSwRenderer.h index 6e21d19c..3b9d7c36 100644 --- a/src/renderer/sw_engine/tvgSwRenderer.h +++ b/src/renderer/sw_engine/tvgSwRenderer.h @@ -75,6 +75,7 @@ private: Array compositors; //render targets cache list SwMpool* mpool; //private memory pool bool sharedMpool; //memory-pool behavior policy + bool fulldraw = true; //buffer is cleared (need to redraw full screen) SwRenderer(); ~SwRenderer();