renderer: introduced paint reference counting

The reference count of the Paint object allows
easy & safe shared ownership and control over its lifetime
among users and the engine.

New APIs:
- uint8_t Paint::ref()
- uint8_t Paint::unref(bool free = true)
- uint8_t Paint::refCnt() const

issue: https://github.com/thorvg/thorvg/issues/1372 https://github.com/thorvg/thorvg/issues/2598
This commit is contained in:
Hermet Park 2024-11-19 16:15:01 +09:00 committed by Hermet Park
parent 511495f6d8
commit 22d94ea629
12 changed files with 113 additions and 61 deletions

View file

@ -447,6 +447,53 @@ public:
*/ */
MaskMethod mask(const Paint** target) const noexcept; MaskMethod mask(const Paint** target) const noexcept;
/**
* @brief Increment the reference count for the Paint instance.
*
* This method increases the reference count of the Paint object, allowing shared ownership and control over its lifetime.
*
* @return The updated reference count after the increment by 1.
*
* @warning Please ensure that each call to ref() is paired with a corresponding call to unref() to prevent a dangling instance.
*
* @see Paint::unref()
* @see Paint::refCnt()
*
* @since 1.0
*/
uint8_t ref() noexcept;
/**
* @brief Decrement the reference count for the Paint instance.
*
* This method decreases the reference count of the Paint object by 1.
* If the reference count reaches zero and the @p free flag is set to true, the Paint instance is automatically deleted.
*
* @param[in] free Flag indicating whether to delete the Paint instance when the reference count reaches zero.
*
* @return The updated reference count after the decrement.
*
* @see Paint::ref()
* @see Paint::refCnt()
*
* @since 1.0
*/
uint8_t unref(bool free = true) noexcept;
/**
* @brief Retrieve the current reference count of the Paint instance.
*
* This method provides the current reference count, allowing the user to check the shared ownership state of the Paint object.
*
* @return The current reference count of the Paint instance.
*
* @see Paint::ref()
* @see Paint::unref()
*
* @since 1.0
*/
uint8_t refCnt() const noexcept;
/** /**
* @brief Returns the ID value of this class. * @brief Returns the ID value of this class.
* *

View file

@ -25,7 +25,6 @@
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgInlist.h" #include "tvgInlist.h"
#include "tvgPaint.h"
#include "tvgShape.h" #include "tvgShape.h"
#include "tvgLottieExpressions.h" #include "tvgLottieExpressions.h"
#include "tvgLottieModifier.h" #include "tvgLottieModifier.h"
@ -64,13 +63,13 @@ struct RenderContext
RenderContext(Shape* propagator) RenderContext(Shape* propagator)
{ {
P(propagator)->reset(); P(propagator)->reset();
PP(propagator)->ref(); propagator->ref();
this->propagator = propagator; this->propagator = propagator;
} }
~RenderContext() ~RenderContext()
{ {
PP(propagator)->unref(); propagator->unref(false);
free(transform); free(transform);
delete(roundness); delete(roundness);
delete(offsetPath); delete(offsetPath);
@ -79,7 +78,7 @@ struct RenderContext
RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false) RenderContext(const RenderContext& rhs, Shape* propagator, bool mergeable = false)
{ {
if (mergeable) merging = rhs.merging; if (mergeable) merging = rhs.merging;
PP(propagator)->ref(); propagator->ref();
this->propagator = propagator; this->propagator = propagator;
this->repeaters = rhs.repeaters; this->repeaters = rhs.repeaters;
if (rhs.roundness) this->roundness = new LottieRoundnessModifier(rhs.roundness->r); if (rhs.roundness) this->roundness = new LottieRoundnessModifier(rhs.roundness->r);

View file

@ -21,8 +21,6 @@
*/ */
#include "tvgMath.h" #include "tvgMath.h"
#include "tvgPaint.h"
#include "tvgFill.h"
#include "tvgTaskScheduler.h" #include "tvgTaskScheduler.h"
#include "tvgLottieModel.h" #include "tvgLottieModel.h"
@ -170,7 +168,7 @@ void LottieImage::prepare()
TaskScheduler::async(true); TaskScheduler::async(true);
picture->size(data.width, data.height); picture->size(data.width, data.height);
PP(picture)->ref(); picture->ref();
pooler.push(picture); pooler.push(picture);
} }
@ -472,14 +470,14 @@ void LottieLayer::prepare(RGB24* color)
if (type == LottieLayer::Precomp) { if (type == LottieLayer::Precomp) {
auto clipper = Shape::gen(); auto clipper = Shape::gen();
clipper->appendRect(0.0f, 0.0f, w, h); clipper->appendRect(0.0f, 0.0f, w, h);
PP(clipper)->ref(); clipper->ref();
statical.pooler.push(clipper); statical.pooler.push(clipper);
//prepare solid fill in advance if it is a layer type. //prepare solid fill in advance if it is a layer type.
} else if (color && type == LottieLayer::Solid) { } else if (color && type == LottieLayer::Solid) {
auto solidFill = Shape::gen(); auto solidFill = Shape::gen();
solidFill->appendRect(0, 0, static_cast<float>(w), static_cast<float>(h)); solidFill->appendRect(0, 0, static_cast<float>(w), static_cast<float>(h));
solidFill->fill(color->rgb[0], color->rgb[1], color->rgb[2]); solidFill->fill(color->rgb[0], color->rgb[1], color->rgb[2]);
PP(solidFill)->ref(); solidFill->ref();
statical.pooler.push(solidFill); statical.pooler.push(solidFill);
} }

View file

@ -36,7 +36,7 @@ struct LottieRenderPooler
~LottieRenderPooler() ~LottieRenderPooler()
{ {
for (auto p = pooler.begin(); p < pooler.end(); ++p) { for (auto p = pooler.begin(); p < pooler.end(); ++p) {
if (PP(*p)->unref() == 0) delete(*p); (*p)->unref();
} }
} }
@ -44,12 +44,12 @@ struct LottieRenderPooler
{ {
//return available one. //return available one.
for (auto p = pooler.begin(); p < pooler.end(); ++p) { for (auto p = pooler.begin(); p < pooler.end(); ++p) {
if (PP(*p)->refCnt == 1) return *p; if ((*p)->refCnt() == 1) return *p;
} }
//no empty, generate a new one. //no empty, generate a new one.
auto p = copy ? static_cast<T*>(pooler[0]->duplicate()) : T::gen(); auto p = copy ? static_cast<T*>(pooler[0]->duplicate()) : T::gen();
PP(p)->ref(); p->ref();
pooler.push(p); pooler.push(p);
return p; return p;
} }

View file

@ -24,7 +24,6 @@
#define _TVG_ANIMATION_H_ #define _TVG_ANIMATION_H_
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgPaint.h"
#include "tvgPicture.h" #include "tvgPicture.h"
struct Animation::Impl struct Animation::Impl
@ -34,14 +33,12 @@ struct Animation::Impl
Impl() Impl()
{ {
picture = Picture::gen(); picture = Picture::gen();
PP(picture)->ref(); picture->ref();
} }
~Impl() ~Impl()
{ {
if (PP(picture)->unref() == 0) { picture->unref();
delete(picture);
}
} }
}; };

View file

@ -53,7 +53,7 @@ struct Canvas::Impl
void clearPaints() void clearPaints()
{ {
for (auto paint : paints) { for (auto paint : paints) {
if (P(paint)->unref() == 0) delete(paint); paint->unref();
} }
paints.clear(); paints.clear();
} }
@ -64,7 +64,7 @@ struct Canvas::Impl
if (status == Status::Drawing) return Result::InsufficientCondition; if (status == Status::Drawing) return Result::InsufficientCondition;
if (!paint) return Result::MemoryCorruption; if (!paint) return Result::MemoryCorruption;
PP(paint)->ref(); paint->ref();
paints.push_back(paint); paints.push_back(paint);
return update(paint, true); return update(paint, true);
@ -72,16 +72,18 @@ struct Canvas::Impl
Result clear(bool paints, bool buffer) Result clear(bool paints, bool buffer)
{ {
auto ret = Result::Success;
if (status == Status::Drawing) return Result::InsufficientCondition; if (status == Status::Drawing) return Result::InsufficientCondition;
//Clear render target //Clear render target
if (buffer) { if (buffer && !renderer->clear()) {
if (!renderer->clear()) return Result::InsufficientCondition; ret = Result::InsufficientCondition;
} }
if (paints) clearPaints(); if (paints) clearPaints();
return Result::Success; return ret;
} }
Result update(Paint* paint, bool force) Result update(Paint* paint, bool force)

View file

@ -362,7 +362,7 @@ void Paint::Impl::reset()
} }
if (maskData) { if (maskData) {
if (P(maskData->target)->unref() == 0) delete(maskData->target); maskData->target->unref();
free(maskData); free(maskData);
maskData = nullptr; maskData = nullptr;
} }
@ -502,4 +502,34 @@ Result Paint::blend(BlendMethod method) noexcept
} }
return Result::Success; return Result::Success;
}
uint8_t Paint::ref() noexcept
{
if (pImpl->refCnt == UINT8_MAX) TVGERR("RENDERER", "Reference Count Overflow!");
else ++pImpl->refCnt;
return pImpl->refCnt;
}
uint8_t Paint::unref(bool free) noexcept
{
if (pImpl->refCnt > 0) --pImpl->refCnt;
else TVGERR("RENDERER", "Corrupted Reference Count!");
if (free && pImpl->refCnt == 0) {
//TODO: use the global dismiss function?
delete(this);
return 0;
}
return pImpl->refCnt;
}
uint8_t Paint::refCnt() const noexcept
{
return pImpl->refCnt;
} }

View file

@ -76,7 +76,7 @@ namespace tvg
uint8_t renderFlag; uint8_t renderFlag;
uint8_t ctxFlag; uint8_t ctxFlag;
uint8_t opacity; uint8_t opacity;
uint8_t refCnt = 0; //reference count uint8_t refCnt = 0; //reference count
Impl(Paint* pnt) : paint(pnt) Impl(Paint* pnt) : paint(pnt)
{ {
@ -86,25 +86,13 @@ namespace tvg
~Impl() ~Impl()
{ {
if (maskData) { if (maskData) {
if (P(maskData->target)->unref() == 0) delete(maskData->target); maskData->target->unref();
free(maskData); free(maskData);
} }
if (clipper && P(clipper)->unref() == 0) delete(clipper); if (clipper) clipper->unref();
if (renderer && (renderer->unref() == 0)) delete(renderer); if (renderer && (renderer->unref() == 0)) delete(renderer);
} }
uint8_t ref()
{
if (refCnt == 255) TVGERR("RENDERER", "Corrupted reference count!");
return ++refCnt;
}
uint8_t unref()
{
if (refCnt == 0) TVGERR("RENDERER", "Corrupted reference count!");
return --refCnt;
}
bool transform(const Matrix& m) bool transform(const Matrix& m)
{ {
if (&tr.m != &m) tr.m = m; if (&tr.m != &m) tr.m = m;
@ -124,16 +112,11 @@ namespace tvg
void clip(Paint* clp) void clip(Paint* clp)
{ {
if (this->clipper) { if (clipper) clipper->unref(clipper != clp);
P(this->clipper)->unref(); clipper = clp;
if (this->clipper != clp && P(this->clipper)->refCnt == 0) {
delete(this->clipper);
}
}
this->clipper = clp;
if (!clp) return; if (!clp) return;
P(clipper)->ref(); clipper->ref();
} }
bool mask(Paint* source, Paint* target, MaskMethod method) bool mask(Paint* source, Paint* target, MaskMethod method)
@ -142,10 +125,7 @@ namespace tvg
if ((!target && method != MaskMethod::None) || (target && method == MaskMethod::None)) return false; if ((!target && method != MaskMethod::None) || (target && method == MaskMethod::None)) return false;
if (maskData) { if (maskData) {
P(maskData->target)->unref(); maskData->target->unref(maskData->target != target);
if ((maskData->target != target) && P(maskData->target)->refCnt == 0) {
delete(maskData->target);
}
//Reset scenario //Reset scenario
if (!target && method == MaskMethod::None) { if (!target && method == MaskMethod::None) {
free(maskData); free(maskData);
@ -156,7 +136,7 @@ namespace tvg
if (!target && method == MaskMethod::None) return true; if (!target && method == MaskMethod::None) return true;
maskData = static_cast<Mask*>(malloc(sizeof(Mask))); maskData = static_cast<Mask*>(malloc(sizeof(Mask)));
} }
P(target)->ref(); target->ref();
maskData->target = target; maskData->target = target;
maskData->source = source; maskData->source = source;
maskData->method = method; maskData->method = method;

View file

@ -23,7 +23,6 @@
#include "tvgCommon.h" #include "tvgCommon.h"
#include "tvgStr.h" #include "tvgStr.h"
#include "tvgSaveModule.h" #include "tvgSaveModule.h"
#include "tvgPaint.h"
#ifdef THORVG_GIF_SAVER_SUPPORT #ifdef THORVG_GIF_SAVER_SUPPORT
#include "tvgGifSaver.h" #include "tvgGifSaver.h"
@ -107,7 +106,7 @@ Result Saver::save(Paint* paint, const char* filename, uint32_t quality) noexcep
//Already on saving another resource. //Already on saving another resource.
if (pImpl->saveModule) { if (pImpl->saveModule) {
if (P(paint)->refCnt == 0) delete(paint); if (paint->refCnt() == 0) delete(paint);
return Result::InsufficientCondition; return Result::InsufficientCondition;
} }
@ -116,12 +115,12 @@ Result Saver::save(Paint* paint, const char* filename, uint32_t quality) noexcep
pImpl->saveModule = saveModule; pImpl->saveModule = saveModule;
return Result::Success; return Result::Success;
} else { } else {
if (P(paint)->refCnt == 0) delete(paint); if (paint->refCnt() == 0) delete(paint);
delete(saveModule); delete(saveModule);
return Result::Unknown; return Result::Unknown;
} }
} }
if (P(paint)->refCnt == 0) delete(paint); if (paint->refCnt() == 0) delete(paint);
return Result::NonSupport; return Result::NonSupport;
} }
@ -140,7 +139,7 @@ Result Saver::save(Animation* animation, const char* filename, uint32_t quality,
if (!animation) return Result::MemoryCorruption; if (!animation) return Result::MemoryCorruption;
//animation holds the picture, it must be 1 at the bottom. //animation holds the picture, it must be 1 at the bottom.
auto remove = PP(animation->picture())->refCnt <= 1 ? true : false; auto remove = animation->picture()->refCnt() <= 1 ? true : false;
if (tvg::zero(animation->totalFrame())) { if (tvg::zero(animation->totalFrame())) {
if (remove) delete(animation); if (remove) delete(animation);

View file

@ -70,7 +70,7 @@ Type Scene::type() const noexcept
Result Scene::push(Paint* paint) noexcept Result Scene::push(Paint* paint) noexcept
{ {
if (!paint) return Result::MemoryCorruption; if (!paint) return Result::MemoryCorruption;
PP(paint)->ref(); paint->ref();
pImpl->paints.push_back(paint); pImpl->paints.push_back(paint);
return Result::Success; return Result::Success;

View file

@ -74,7 +74,7 @@ struct Scene::Impl
resetEffects(); resetEffects();
for (auto paint : paints) { for (auto paint : paints) {
if (P(paint)->unref() == 0) delete(paint); paint->unref();
} }
if (auto renderer = PP(scene)->renderer) { if (auto renderer = PP(scene)->renderer) {
@ -230,7 +230,7 @@ struct Scene::Impl
for (auto paint : paints) { for (auto paint : paints) {
auto cdup = paint->duplicate(); auto cdup = paint->duplicate();
P(cdup)->ref(); cdup->ref();
dup->paints.push_back(cdup); dup->paints.push_back(cdup);
} }
@ -242,7 +242,7 @@ struct Scene::Impl
void clear(bool free) void clear(bool free)
{ {
for (auto paint : paints) { for (auto paint : paints) {
if (P(paint)->unref() == 0 && free) delete(paint); paint->unref(free);
} }
paints.clear(); paints.clear();
} }

View file

@ -100,7 +100,7 @@ bool GifSaver::close()
bg = nullptr; bg = nullptr;
//animation holds the picture, it must be 1 at the bottom. //animation holds the picture, it must be 1 at the bottom.
if (animation && PP(animation->picture())->refCnt <= 1) delete(animation); if (animation && animation->picture()->refCnt() <= 1) delete(animation);
animation = nullptr; animation = nullptr;
free(path); free(path);