← 블로그 목록

[Core2026] Engine Overview

Core2026 › Engine

@Nor-s
  • #ThorVG
  • #Core
목차

1. ThorVG API

1.1 API 구조와 데이터 관계

Canvas, Scene, Shape, Picture의 네 섹션으로 나눈 ThorVG API 상속·보유 관계와 Scene·Lottie·SVG 트리 예시
코드 보기: Public API와 공통 내부 타입의 관계

참고: thorvg.h

  • Canvas: 렌더링 진입점, root Scene과 RenderMethod 보관
    • SwCanvas: CPU renderer → 사용자 메모리 버퍼
    • GlCanvas: OpenGL/ES renderer → GL framebuffer
    • WgCanvas: 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에서 같은 Scene 트리를 동시에 그리며, 직접 그리기와 Offscreen 그룹 합성을 비교합니다.
0:00

위쪽 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

Immediate Mode는 매번 도형의 그리기 명령을 전달하고, Retained Mode는 객체를 보관한 뒤 변경된 원만 갱신합니다.
0:00
Scene은 한 번 구성하고, 매 프레임 원의 위치와 크기만 바꾼 뒤 update·draw·sync로 반영합니다.
0:00

ThorVG는 Retained Mode 렌더링 모델을 지원합니다.

  • Immediate: 사용자가 장면을 관리하고, 매 frame 필요한 그리기 명령 전달
  • Retained: 엔진이 Scene·Paint와 상태를 보관. 사용자는 바뀐 객체를 수정한 뒤 update()·draw()로 반영

Microsoft Learn — Retained Mode Versus Immediate Mode

Wikipedia — Retained mode

1.4 Frame lifecycle — update, draw, sync

Follow the five Canvas states through API calls: the internal update() in draw(), call restrictions during Drawing, and the return to Synced after sync().
0:00
코드 보기: 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;
}