diff --git a/src/renderer/tvgPaint.h b/src/renderer/tvgPaint.h index 05ddbe53..9cbeb7ea 100644 --- a/src/renderer/tvgPaint.h +++ b/src/renderer/tvgPaint.h @@ -144,6 +144,11 @@ namespace tvg return (renderFlag & flag) ? true : false; } + void damage() + { + if (renderer) renderer->damage(bounds(renderer)); + } + void mark(RenderUpdateFlag flag) { renderFlag |= flag; diff --git a/src/renderer/tvgRender.cpp b/src/renderer/tvgRender.cpp index d943b821..d74b9ce8 100644 --- a/src/renderer/tvgRender.cpp +++ b/src/renderer/tvgRender.cpp @@ -20,6 +20,7 @@ * SOFTWARE. */ +#include #include "tvgMath.h" #include "tvgRender.h" @@ -54,6 +55,18 @@ bool RenderMethod::viewport(const RenderRegion& vp) } +void RenderMethod::damage(const RenderRegion& region) +{ + dirtyRegion.add(region); +} + + +bool RenderMethod::partial(bool disable) +{ + std::swap(dirtyRegion.disabled, disable); + return disable; +} + /************************************************************************/ /* RenderPath Class Implementation */ /************************************************************************/ @@ -130,6 +143,130 @@ void RenderRegion::intersect(const RenderRegion& rhs) if (max.y < min.y) max.y = min.y; } + +void RenderDirtyRegion::subdivide(Array& targets, uint32_t idx, RenderRegion& lhs, RenderRegion& rhs) +{ + RenderRegion temp[5]; + int cnt = 0; + temp[cnt++] = RenderRegion::intersect(lhs, rhs); + auto max = std::min(lhs.max.x, rhs.max.x); + + auto subtract = [&](RenderRegion& lhs, RenderRegion& rhs) { + //top + if (rhs.min.y < lhs.min.y) { + temp[cnt++] = {{rhs.min.x, rhs.min.y}, {rhs.max.x, lhs.min.y}}; + rhs.min.y = lhs.min.y; + } + //bottom + if (rhs.max.y > lhs.max.y) { + temp[cnt++] = {{rhs.min.x, lhs.max.y}, {rhs.max.x, rhs.max.y}}; + rhs.max.y = lhs.max.y; + } + //left + if (rhs.min.x < lhs.min.x) { + temp[cnt++] = {{rhs.min.x, rhs.min.y}, {lhs.min.x, rhs.max.y}}; + rhs.min.x = lhs.min.x; + } + //right + if (rhs.max.x > lhs.max.x) { + temp[cnt++] = {{lhs.max.x, rhs.min.y}, {rhs.max.x, rhs.max.y}}; + //rhs.max.x = lhs.max.x; + } + }; + + subtract(temp[0], lhs); + subtract(temp[0], rhs); + + //TODO: remove this + if (targets.reserved < targets.count + cnt - 1) { + TVGERR("RENDERER", "reserved: %d, required: %d (+%d)\n", targets.reserved, targets.count + cnt - 1, cnt - 1); + abort(); + } + + /* Note: We considered using a list to avoid memory shifting, + but ultimately, the array outperformed the list due to better cache locality. */ + + //shift data + auto dst = &targets[idx + cnt]; + memmove(dst, &targets[idx + 1], sizeof(RenderRegion) * (targets.count - idx - 1)); + memcpy(&targets[idx], temp, sizeof(RenderRegion) * cnt); + targets.count += (cnt - 1); + + //sorting by x coord again, only for the updated region + while (dst < targets.end() && dst->min.x < max) ++dst; + stable_sort(&targets[idx], dst, [](const RenderRegion& a, const RenderRegion& b) -> bool { + return a.min.x < b.min.x; + }); +} + + +void RenderDirtyRegion::commit() +{ + if (disabled) return; + + auto& targets = list[current]; + if (targets.empty()) return; + + current = !current; //swapping buffers + + auto& output = list[current]; + + //sorting by x coord. guarantee the stable performance: O(NlogN) + stable_sort(targets.begin(), targets.end(), [](const RenderRegion& a, const RenderRegion& b) -> bool { + return a.min.x < b.min.x; + }); + + //Optimized using sweep-line algorithm: O(NlogN) + for (uint32_t i = 0; i < targets.count; ++i) { + auto& lhs = targets[i]; + if (lhs.invalid()) continue; + auto merged = false; + + for (uint32_t j = i + 1; j < targets.count; ++j) { + auto& rhs = targets[j]; + if (rhs.invalid()) continue; + if (lhs.max.x < rhs.min.x) break; //line sweeping + + //fully overlapped. drop lhs + if (rhs.contained(lhs)) { + merged = true; + break; + } + //fully overlapped. replace the lhs with rhs + if (lhs.contained(rhs)) { + rhs = {}; + continue; + } + //just merge & expand on x axis + if (lhs.min.y == rhs.min.y && lhs.max.y == rhs.max.y) { + if (lhs.min.x <= rhs.max.x && rhs.min.x <= lhs.max.x) { + rhs.min.x = std::min(lhs.min.x, rhs.min.x); + rhs.max.x = std::max(lhs.max.x, rhs.max.x); + merged = true; + break; + } + } + //just merge & expand on y axis + if (lhs.min.x == rhs.min.x && lhs.max.x == rhs.max.x) { + if (lhs.min.y <= rhs.max.y && rhs.min.y < lhs.max.y) { + rhs.min.y = std::min(lhs.min.y, rhs.min.y); + rhs.max.y = std::max(lhs.max.y, rhs.max.y); + merged = true; + break; + } + } + //subdivide regions + if (lhs.intersected(rhs)) { + subdivide(targets, j, lhs, rhs); + merged = true; + break; + } + } + if (!merged) output.push(lhs); //this region is complete isolated + lhs = {}; + } +} + /************************************************************************/ /* RenderTrimPath Class Implementation */ /************************************************************************/ diff --git a/src/renderer/tvgRender.h b/src/renderer/tvgRender.h index 6d802661..85e4c616 100644 --- a/src/renderer/tvgRender.h +++ b/src/renderer/tvgRender.h @@ -50,7 +50,6 @@ static inline RenderUpdateFlag operator|(const RenderUpdateFlag a, const RenderU return RenderUpdateFlag(uint16_t(a) | uint16_t(b)); } - struct RenderSurface { union { @@ -111,6 +110,11 @@ struct RenderRegion return ret; } + static constexpr RenderRegion add(const RenderRegion& lhs, const RenderRegion& rhs) + { + return {{std::min(lhs.min.x, rhs.min.x), std::min(lhs.min.y, rhs.min.y)}, {std::max(lhs.max.x, rhs.max.x), std::max(lhs.max.y, rhs.max.y)}}; + } + void intersect(const RenderRegion& rhs); void add(const RenderRegion& rhs) @@ -121,6 +125,16 @@ struct RenderRegion if (rhs.max.y > max.y) max.y = rhs.max.y; } + bool contained(const RenderRegion& rhs) + { + return (min.x <= rhs.min.x && max.x >= rhs.max.x && min.y <= rhs.min.y && max.y >= rhs.max.y); + } + + bool intersected(const RenderRegion& rhs) const + { + return (rhs.min.x < max.x && rhs.max.x > min.x && rhs.min.y < max.y && rhs.max.y > min.y); + } + bool operator==(const RenderRegion& rhs) const { return (min.x == rhs.min.x && min.y == rhs.min.y && max.x == rhs.max.x && max.y == rhs.max.y); @@ -141,6 +155,56 @@ struct RenderRegion uint32_t h() const { return (uint32_t) sh(); } }; +struct RenderDirtyRegion +{ + void add(const RenderRegion& region) + { + if (disabled || region.invalid()) return; + ScopedLock lock(key); + list[current].push(region); + } + + bool prepare(uint32_t count = 0) + { + if (disabled) return false; + + count *= 120; //FIXME: enough? + + list[0].reserve(count); + list[1].reserve(count); + + return true; + } + + bool deactivated() + { + if (disabled) return true; + return false; + } + + void clear() + { + list[0].clear(); + list[1].clear(); + } + + const Array& get() + { + return list[current]; + } + + void commit(); + + bool disabled = false; + +private: + void subdivide(Array& targets, uint32_t idx, RenderRegion& lhs, RenderRegion& rhs); + + Array list[2]; //double buffer swapping + Key key; + uint8_t current = 0; //list index. 0 or 1 +}; + struct RenderPath { Array cmds; @@ -420,11 +484,12 @@ struct RenderEffectTritone : RenderEffect class RenderMethod { private: - uint32_t refCnt = 0; //reference count + uint32_t refCnt = 0; Key key; protected: RenderRegion vport; //viewport + RenderDirtyRegion dirtyRegion; public: //common implementation @@ -448,11 +513,10 @@ public: virtual bool blend(BlendMethod method) = 0; virtual ColorSpace colorSpace() = 0; virtual const RenderSurface* mainSurface() = 0; - virtual bool clear() = 0; virtual bool sync() = 0; - //compositions + //composition virtual RenderCompositor* target(const RenderRegion& region, ColorSpace cs, CompositionFlag flags) = 0; virtual bool beginComposite(RenderCompositor* cmp, MaskMethod method, uint8_t opacity) = 0; virtual bool endComposite(RenderCompositor* cmp) = 0; @@ -462,6 +526,10 @@ public: virtual bool region(RenderEffect* effect) = 0; virtual bool render(RenderCompositor* cmp, const RenderEffect* effect, bool direct) = 0; virtual void dispose(RenderEffect* effect) = 0; + + //partial rendering + virtual void damage(const RenderRegion& region); + virtual bool partial(bool disable); }; static inline bool MASK_REGION_MERGING(MaskMethod method) @@ -532,4 +600,4 @@ static inline uint8_t MULTIPLY(uint8_t c, uint8_t a) } -#endif //_TVG_RENDER_H_ +#endif //_TVG_RENDER_H_ \ No newline at end of file diff --git a/src/renderer/tvgScene.h b/src/renderer/tvgScene.h index c761645f..68e7056a 100644 --- a/src/renderer/tvgScene.h +++ b/src/renderer/tvgScene.h @@ -131,6 +131,9 @@ struct SceneImpl : Scene vport = renderer->viewport(); vdirty = true; + //bounds(renderer) here hinders parallelization. + if (effects) renderer->damage(vport); + return true; } @@ -257,7 +260,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; @@ -266,6 +272,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; @@ -310,6 +318,7 @@ struct SceneImpl : Scene } delete(effects); effects = nullptr; + impl.renderer->damage(vport); } return Result::Success; } diff --git a/src/renderer/tvgText.h b/src/renderer/tvgText.h index db4b3aa1..7e11e2f3 100644 --- a/src/renderer/tvgText.h +++ b/src/renderer/tvgText.h @@ -111,7 +111,7 @@ struct TextImpl : Text return loader->transform(shape, metrics, fontSize, italic); } - + bool skip(RenderUpdateFlag flag) { if (flag == RenderUpdateFlag::None) return true;