api: revise the canvas update() API

Removed the paint parameter previously used to forcibly
update a single paint.

This behavior was unsafe in ThorVG, as a single paint
object may be connected to others through the scene graph.

Now, the canvas engine is responsible for properly updating
all damaged paints as needed, allowing the user to simply
call update() in any case.

API Modifications:

C++ API:
 * Result Canvas::update(Paint* paint) -> Result Canvas::update()

CAPI:
 - tvg_canvas_update_paint()

Issue: https://github.com/thorvg/thorvg/issues/3116
This commit is contained in:
Hermet Park 2025-06-15 14:35:06 +09:00 committed by Hermet Park
parent f606d58dfb
commit 39b0f87cb3
9 changed files with 17 additions and 51 deletions

View file

@ -54,7 +54,7 @@ struct UserExample : tvgexam::Example
picture->scale((1.0f - progress) * 1.5f);
canvas->update(picture);
canvas->update();
return true;
}

View file

@ -733,16 +733,12 @@ public:
Result remove(Paint* paint = nullptr) noexcept;
/**
* @brief Request the canvas to update the paint objects.
* @brief Requests the canvas to update the paint for up-to-date render preparation.
*
* If a @c nullptr is passed all paint objects retained by the Canvas are updated,
* otherwise only the paint to which the given @p paint points.
*
* @param[in] paint A pointer to the Paint object or @c nullptr.
*
* @note The Update behavior can be asynchronous if the assigned thread number is greater than zero.
* @note Only modified paint instances will undergo the internal update process.
* @note The update operation may be asynchronous if the assigned thread count is greater than zero.
*/
Result update(Paint* paint = nullptr) noexcept;
Result update() noexcept;
/**
* @brief Requests the canvas to render Paint objects.

View file

@ -630,23 +630,6 @@ TVG_API Tvg_Result tvg_canvas_remove(Tvg_Canvas* canvas, Tvg_Paint* paint);
TVG_API Tvg_Result tvg_canvas_update(Tvg_Canvas* canvas);
/*!
* @brief Updates the given Tvg_Paint object from the canvas before the rendering.
*
* If a client application using the TVG library does not update the entire canvas with tvg_canvas_update() in the frame
* rendering process, Tvg_Paint objects previously added to the canvas should be updated manually with this function.
*
* @param[in] canvas The Tvg_Canvas object to which the @p paint belongs.
* @param[in] paint The Tvg_Paint object to be updated.
*
* @return Tvg_Result enumeration.
* @retval TVG_RESULT_INVALID_ARGUMENT In case a @c nullptr is passed as the argument.
*
* @see tvg_canvas_update()
*/
TVG_API Tvg_Result tvg_canvas_update_paint(Tvg_Canvas* canvas, Tvg_Paint* paint);
/*!
* @brief Requests the canvas to draw the Tvg_Paint objects.
*

View file

@ -134,14 +134,7 @@ TVG_API Tvg_Result tvg_canvas_remove(Tvg_Canvas* canvas, Tvg_Paint* paint)
TVG_API Tvg_Result tvg_canvas_update(Tvg_Canvas* canvas)
{
if (canvas) return (Tvg_Result) reinterpret_cast<Canvas*>(canvas)->update(nullptr);
return TVG_RESULT_INVALID_ARGUMENT;
}
TVG_API Tvg_Result tvg_canvas_update_paint(Tvg_Canvas* canvas, Tvg_Paint* paint)
{
if (canvas && paint) return (Tvg_Result) reinterpret_cast<Canvas*>(canvas)->update((Paint*) paint);
if (canvas) return (Tvg_Result) reinterpret_cast<Canvas*>(canvas)->update();
return TVG_RESULT_INVALID_ARGUMENT;
}

View file

@ -55,11 +55,11 @@ Result Canvas::draw(bool clear) noexcept
}
Result Canvas::update(Paint* paint) noexcept
Result Canvas::update() noexcept
{
TVGLOG("RENDERER", "Update S. ------------------------------ Canvas(%p)", this);
if (pImpl->scene->paints().empty() || pImpl->status == Status::Drawing) return Result::InsufficientCondition;
auto ret = pImpl->update(paint, false);
auto ret = pImpl->update(false);
TVGLOG("RENDERER", "Update E. ------------------------------ Canvas(%p)", this);
return ret;

View file

@ -56,7 +56,7 @@ struct Canvas::Impl
auto ret = scene->push(target, at);
if (ret != Result::Success) return ret;
return update(target, true);
return update(true);
}
Result remove(Paint* paint)
@ -65,18 +65,16 @@ struct Canvas::Impl
return scene->remove(paint);
}
Result update(Paint* paint, bool force)
Result update(bool force)
{
Array<RenderData> clips;
auto flag = RenderUpdateFlag::None;
if (status == Status::Damaged || force) flag = RenderUpdateFlag::All;
auto m = tvg::identity();
if (!renderer->preUpdate()) return Result::InsufficientCondition;
if (paint) PAINT(paint)->update(renderer, m, clips, 255, flag);
else PAINT(scene)->update(renderer, m, clips, 255, flag);
auto m = tvg::identity();
PAINT(scene)->update(renderer, m, clips, 255, flag);
if (!renderer->postUpdate()) return Result::InsufficientCondition;
@ -87,13 +85,9 @@ struct Canvas::Impl
Result draw(bool clear)
{
if (status == Status::Drawing) return Result::InsufficientCondition;
if (clear && !renderer->clear()) return Result::InsufficientCondition;
if (scene->paints().empty()) return Result::InsufficientCondition;
if (status == Status::Damaged) update(nullptr, false);
if (status == Status::Damaged) update(false);
if (!renderer->preRender()) return Result::InsufficientCondition;
if (!PAINT(scene)->render(renderer) || !renderer->postRender()) return Result::InsufficientCondition;

View file

@ -116,7 +116,7 @@ struct PictureImpl : Picture
return Result::Success;
}
Result bounds(Point* pt4, Matrix& m, TVG_UNUSED bool obb, TVG_UNUSED bool stroking)
Result bounds(Point* pt4, Matrix& m, TVG_UNUSED bool obb, TVG_UNUSED bool stroking) const
{
pt4[0] = Point{0.0f, 0.0f} * m;
pt4[1] = Point{w, 0.0f} * m;

View file

@ -116,7 +116,7 @@ struct SceneImpl : Scene
opacity = 255;
}
for (auto paint : paints) {
paint->pImpl->update(renderer, transform, clips, opacity, flag, false);
PAINT(paint)->update(renderer, transform, clips, opacity, flag, false);
}
if (effects) {

View file

@ -141,7 +141,7 @@ TEST_CASE("Bounding Box", "[tvgPaint]")
REQUIRE(w == 20.0f);
REQUIRE(h == 100.0f);
REQUIRE(canvas->update(shape) == Result::Success);
REQUIRE(canvas->update() == Result::Success);
Point pts[4];
REQUIRE(shape->bounds(pts) == Result::Success);
REQUIRE(pts[0].x == 100.0f);
@ -165,7 +165,7 @@ TEST_CASE("Bounding Box", "[tvgPaint]")
REQUIRE(w == 20.0f);
REQUIRE(h == 200.0f);
REQUIRE(canvas->update(shape) == Result::Success);
REQUIRE(canvas->update() == Result::Success);
REQUIRE(shape->bounds(pts) == Result::Success);
REQUIRE(pts[0].x == 0.0f);
REQUIRE(pts[3].x == 0.0f);