1. ThorVG API
1.1 API 구조와 데이터 관계
코드 보기: Public API와 공통 내부 타입의 관계
참고: thorvg.h
- Canvas: 렌더링 진입점, root
Scene과RenderMethod보관SwCanvas: CPU renderer → 사용자 메모리 버퍼GlCanvas: OpenGL/ES renderer → GL framebufferWgCanvas: WebGPU renderer → surface 또는 texture
- Paint: transform·opacity·clip·mask·blend 상태를 가진 공통 기반 타입
Shape,Picture,Text,Scene이 상속Canvas는 Paint 상속 계층에 포함되지 않음
- Scene: 여러 Paint를 묶는 논리적인 그룹
Scene::add(Paint*)로 자식 추가- 다른 Scene을 자식으로 추가하여 장면 그래프 구성
- 그룹 합성·효과 처리가 필요한 경우 중간 render target 사용
struct Canvas { /* ... 생략 */ };
struct SwCanvas final : Canvas { /* ... 생략 */ };
struct GlCanvas final : Canvas { /* ... 생략 */ };
struct WgCanvas final : Canvas { /* ... 생략 */ };
struct Paint {
Result transform(const Matrix& m) noexcept;
// ... 생략
};
struct Scene : Paint {
Result add(Paint* target, Paint* at = nullptr) noexcept;
// ... 생략
};
struct Shape : Paint {
Result fill(Fill* f) noexcept;
Result appendPath(const PathCommand* cmds, uint32_t cmdCnt,
const Point* pts, uint32_t ptsCnt) noexcept;
// ... 생략
};
struct Picture : Paint { /* ... 생략 */ };
struct Text : Paint { /* ... 생략 */ };
struct Fill {
struct ColorStop {
float offset;
uint8_t r, g, b, a;
};
Result colorStops(const ColorStop* colorStops, uint32_t cnt) noexcept;
// ... 생략
};
struct LinearGradient : Fill { /* ... 생략 */ };
struct RadialGradient : Fill { /* ... 생략 */ };
struct Animation {
Picture* picture() const noexcept; // Animation이 보유한 Picture
// ... 생략
};
// Lottie 확장 헤더 thorvg_lottie.h
struct LottieAnimation final : Animation { /* ... 생략 */ };LottieAnimation의 선언은 thorvg_lottie.h에 있습니다.
코드 보기: Shape의 RenderPath와 Text의 내부 Shape
참고: tvgRender.h, tvgShape.h, tvgText.h
struct RenderPath {
Array<PathCommand> cmds;
Array<Point> pts;
// ... 생략
};
struct RenderShape {
RenderPath path;
Fill* fill = nullptr;
// ... 생략
};
struct ShapeImpl : Shape {
Paint::Impl impl;
RenderShape rs;
// ... 생략
};
struct TextImpl : Text {
Paint::Impl impl;
Shape* shape;
// 내부 Shape 생성
TextImpl() : impl(Paint::Impl(this)), shape(Shape::gen())
{
PAINT(shape)->parent = this;
shape->strokeJoin(StrokeJoin::Round);
}
bool render(RenderMethod* renderer, TVG_UNUSED CompositionFlag flag)
{
if (!loader || !fm.engine) return true;
renderer->blend(impl.blendMethod);
return PAINT(shape)->render(renderer);
}
// ... 생략
};Public API Shape::appendPath()는 Point 배열을 입력받습니다. 내부에서는 점 배열과 명령 배열을 RenderPath로 함께 보관합니다.
Text::fill() 역시 tvgText.cpp에서 내부 Shape로 위임합니다. 따라서 그림은 Text–Shape–RenderShape–Fill의 보유 관계를 중심으로 표현했습니다.
Result Text::fill(Fill* f) noexcept
{
return to<TextImpl>(this)->shape->fill(f);
}코드 보기: RenderMethod 인터페이스와 SW·GL·WG 구현체
RenderMethod는 렌더링 backend를 추상화하는 내부 인터페이스입니다. Canvas·Paint가 사용하는 공통 렌더링 연산과 CPU·GPU별 구현을 연결합니다.
- 구현체:
SwRenderer,GlRenderer,WgRenderer - 공통 계약: Shape·Image 준비와 렌더링, 합성, 완료 대기
- 선택 시점: 각 Canvas의
gen()이 구현체를 생성해Canvas::Impl::renderer에 연결 - 호출 경로: Canvas → root Scene → Paint → 선택된 renderer
참고: tvgRender.h
struct RenderMethod
{
// ... 공통 상태·기능 생략
virtual ~RenderMethod() {}
virtual bool preUpdate() = 0;
virtual RenderData prepare(const RenderShape& rshape, RenderData data,
const Matrix& transform, const Array<RenderData>& clips,
uint8_t opacity, RenderUpdateFlag flags, bool clipper) = 0;
virtual bool postUpdate() = 0;
virtual bool preRender() = 0;
virtual bool renderShape(RenderData data) = 0;
virtual bool renderImage(RenderData data) = 0;
virtual bool postRender() = 0;
virtual bool sync() = 0;
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;
// ... 나머지 가상 함수 생략
};
struct SwRenderer : RenderMethod { /* ... 생략 */ };
struct GlRenderer : RenderMethod { /* ... 생략 */ };
struct WgRenderer : RenderMethod { /* ... 생략 */ };구현체의 선언은 tvgSwRenderer.h, tvgGlRenderer.h, tvgWgRenderer.h에 있습니다.
Canvas::Impl은 root Scene과 함께 실제 backend를 가리키는 RenderMethod*를 보관합니다.
struct Canvas::Impl
{
Scene* scene;
RenderMethod* renderer;
// ... 나머지 멤버·함수 생략
};SwCanvas::gen() 전체입니다. CPU backend 지원 여부와 초기화 상태를 확인한 뒤 renderer를 생성해 연결합니다.
SwCanvas* SwCanvas::gen(EngineOption op) noexcept
{
#ifdef THORVG_CPU_ENGINE_SUPPORT
if (engineInit > 0) {
auto renderer = new SwRenderer(TaskScheduler::threads(), op);
renderer->ref();
auto ret = new SwCanvas;
ret->pImpl->renderer = renderer;
return ret;
}
#endif
TVGLOG("RENDERER", "SwCanvas is not supported");
return nullptr;
}각 Canvas의 gen()은 대응하는 RenderMethod 구현체를 선택해 Canvas::Impl::renderer에 보관합니다.
1.2 Scene — 트리, 그리기 순서와 그룹
Scene은 여러 Paint를 보관하는 논리적인 그룹입니다. add()로 보관한 순서가 z-order를 결정하며, Scene 안에 Scene을 넣어 트리를 만들 수 있습니다.
add(): 목록의 마지막에 추가add(paint, at): 지정한at앞에 삽입draw: 각 부모의 자식 목록을 앞에서 뒤로 순회

위쪽 Direct와 아래쪽 Group Opacity는 같은 Paint tree를 사용합니다. 차이는 Scene이라는 자료구조가 아니라 그룹 전체에 opacity를 적용해야 하는가에 있습니다.
opacity(255): 자식은 현재 target에 순서대로 그려질 수 있음opacity(128): 자식의 겹침 결과를 먼저 만든 뒤 그룹 전체에 opacity를 한 번 적용- effect·mask·blend: 공통 Scene 로직이 중간 합성을 요청할 수 있는 다른 조건
CPU의 offscreen Surface와 beginComposite() / endComposite() 흐름은 Compositing & Postprocessing에서 다룹니다.
코드 보기: Scene의 부모·자식 관계
Canvas 초기화와 target 연결은 생략한 예시입니다.
auto circle = tvg::Shape::gen();
auto triangle = tvg::Shape::gen();
auto square = tvg::Shape::gen();
auto group = tvg::Scene::gen();
group->add(square);
group->add(triangle); // [triangle, square]
canvas->add(circle);
canvas->add(group); // [circle, group]
canvas->update();
canvas->draw(true);
canvas->sync();1.3 Immediate vs Retained Mode


ThorVG는 Retained Mode 렌더링 모델을 지원합니다.
- Immediate: 사용자가 장면을 관리하고, 매 frame 필요한 그리기 명령 전달
- Retained: 엔진이 Scene·Paint와 상태를 보관. 사용자는 바뀐 객체를 수정한 뒤
update()·draw()로 반영
1.4 Frame lifecycle — update, draw, sync

코드 보기: update — Paint tree 준비 요청
Canvas::update()
-> Canvas::Impl::update()Canvas::Impl::update() 전체입니다. 상태를 검사한 뒤 preUpdate()와 postUpdate() 사이에서 root Scene에 update를 전달합니다.
Result update()
{
if (status == Status::Updating) return Result::Success;
if (status == Status::Drawing) {
TVGLOG("RENDERER", "update() was called during drawing.");
return Result::InsufficientCondition;
}
auto flag = RenderUpdateFlag::None;
//TODO: All is too harsh, can be optimized.
if (status == Status::Damaged) flag = RenderUpdateFlag::All;
if (!renderer->preUpdate()) return Result::InsufficientCondition;
clips.clear();
auto m = tvg::identity();
PAINT(scene)->update(renderer, m, clips, 255, flag);
if (!renderer->postUpdate()) return Result::InsufficientCondition;
status = Status::Updating;
return Result::Success;
}코드 보기: draw — Paint tree 렌더링
Canvas::draw(clear)
-> Canvas::Impl::draw(clear)Canvas::Impl::draw() 전체입니다. 중복 draw를 거절하고, Painting 또는 Damaged 상태이면 먼저 update()를 호출합니다. 이후 preRender() → Scene 렌더링 → postRender() 순서로 진행합니다.
Result draw(bool clear)
{
if (status == Status::Drawing) {
TVGLOG("RENDERER", "draw() was called multiple times.");
return Result::InsufficientCondition;
}
if (status == Status::Painting || status == Status::Damaged) update();
if (status != Status::Updating) return Result::InsufficientCondition;
if (clear && !renderer->clear()) return Result::InsufficientCondition;
if (!renderer->preRender()) return Result::InsufficientCondition;
if (!PAINT(scene)->render(renderer) || !renderer->postRender()) return Result::InsufficientCondition;
status = Status::Drawing;
return Result::Success;
}코드 보기: sync — backend 완료 대기와 상태 전환
Canvas::sync()
-> Canvas::Impl::sync()Canvas::Impl::sync() 전체입니다. 실제 완료 처리는 선택된 renderer에 위임하며, 성공하면 Canvas 상태를 Synced로 바꿉니다.
Result sync()
{
if (status == Status::Synced) return Result::Success;
if (renderer->sync()) {
status = Status::Synced;
return Result::Success;
}
return Result::Unknown;
}