0. Overview
이 Study 주제의 목적은, 위 스크린 샷 처럼 Topdown 방식으로 Example의 SVG 파일 하나가 Picture로 들어와 CPU Renderer를 통해 화면에 그려지는 전체 Call Flow 설명하는 것을 목적으로 한다. (3~4회에 걸쳐 정리 예정)
빠른 basic flow 확인을 위해, 우선 하나의 단색 벡터 도형을 렌더링하기 위한 흐름을 최소 단위로 관찰하기 쉽도록 아래의 최소 client sample을 별도로 만들었다.
//StudyShape.cpp
#include "Example.h"
struct UserExample : tvgexam::Example
{
bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) override
{
auto shape = tvg::Shape::gen();
shape->appendRect(100, 100, 300, 200);
shape->fill(255, 0, 0);
canvas->add(shape);
return true;
}
};
int main(int argc, char** argv)
{
return tvgexam::main(new UserExample, argc, argv);
}
1. thorVG 라이브러리와 렌더링 엔진 overview
Paint,Shape,Scene,Picture,Text등 그래픽 객체 모델Canvas를 통한 Paint 관리와 렌더링 생명주기- Software/OpenGL/WebGPU backend
- SVG, Lottie 등 리소스 loader
- 경로 처리, rasterization, blending
2. Example 구조 overview
thorvg.example: 라이브러리 사용자 관점에서의 샘플. ThorVG Core가 아니다. ThorVG를 사용하는 client이자 여러 기능을 같은 방식으로 실행하기 위한 example 이다.
main()과 애플리케이션 생명주기- SDL 초기화와 OS Window 생성
- Software/OpenGL/WebGPU 실행 모드 선택
- 각 backend에 맞는 출력 target 준비
- ThorVG의
draw()결과를 화면에 표시
여기까지 봤을 때에는, OS Window와 실행모드에 대한 권한이 client(example)단에 있어 thorVG의 관심사가 아닌 것으로 순간 생각이 들었는데, Window 자체는 ThorVG의 관심사가 아니지만 애플리케이션이 Window의 그래픽 환경에 맞는 Canvas와 target을 선택해서 ThorVG에 제공하면, ThorVG는 그 이후 해당 backend에 맞는 실제 벡터 렌더링을 처리한다.
참고) Example implementation
Issue를 재현해보거나, 특정 동작 시나리오 확인을 위해서 example을 구성해야할 경우, 아래 정보를 참고할 수 있다.
Example.h의 tvgexam::Example에 예제별 동작을 끼워 넣기 위한 인터페이스가 갖춰져있다.
struct Example
{
virtual bool content(tvg::Canvas* canvas, uint32_t w, uint32_t h) = 0;
virtual bool update(tvg::Canvas* canvas, uint32_t elapsed) { return false; }
virtual bool clickdown(...);
virtual bool clickup(...);
virtual bool keydown(...);
virtual bool motion(...);
};
content()만 순수 가상 함수이므로 모든 예제는 최소한 Canvas에 무엇을 넣을 것인가를 반드시 정의해야 한다. 반면 animation이나 interaction이 필요하지 않은 예제는 update(), mouse, keyboard hook을 override하지 않아도 된다.
공통 실행부가 전체 생명주기를 소유하고 예제는 필요한 hook만 구현한다.
FrameRenderCallback이나 일반적인 OpenGL 프로그램의 RenderLoop와 유사하다.
contribution에서 디버그 포인트가 될 수 있는 것은 (by chatGPT) content() 호출 전 문제는 대개 example harness나 backend 초기화 문제이고, content()에서 구성한 Paint가 이후 잘못 렌더링되는 문제는 ThorVG Core까지 추적해야 하는 문제라고 함.
3. Window
ThorVG는 OS Window toolkit이 아니라 렌더링 라이브러리다. ThorVG의 책임은 주어진 target에 그래픽 결과를 만드는 것이며, 화면 창과 이벤트 루프는 client가 준비해야 한다.
Software 경로에서 SwWindow는 SDL을 이용해 다음 외부 자원을 준비한다.
window = SDL_CreateWindow(...);
auto surface = SDL_GetWindowSurface(window);
canvas = tvg::SwCanvas::gen();
static_cast<tvg::SwCanvas*>(canvas)->target(
(uint32_t*)surface->pixels,
surface->pitch / 4,
surface->w,
surface->h,
tvg::ColorSpace::ARGB8888
);
역할은 다음과 같이 나뉜다.
| 구성요소 | 소속 | 책임 |
|---|---|---|
SDL_Window | client/SDL | OS 화면 창과 이벤트 제공 |
SDL_Surface | client/SDL | CPU가 쓸 수 있는 픽셀 메모리 제공 |
SwCanvas | ThorVG | Software raster engine과 Paint 렌더링 관리 |
SwCanvas::target() | ThorVG API | 외부 픽셀 버퍼를 Software Renderer의 출력 대상으로 연결 |
SDL_UpdateWindowSurface() | client/SDL | 변경된 Surface를 실제 Window에 표시 |
즉, 화면 출력은 하나의 책임이 아니라 두 단계다.
ThorVG가 SDL Surface의 pixels에 rasterize
→ client가 SDL_UpdateWindowSurface()로 Window에 present
이 구조 덕분에 ThorVG는 SDL에 종속되지 않는다. 다른 UI framework도 자체 버퍼나 GPU target을 Canvas에 연결할 수 있다.
4. 현재 환경 : SwCanvas
PS C:\dev\thorvg.example> .\builddir\src\StudyShape.exe gl
GlCanvas is not supported. Did you enable the GlEngine?
PS C:\dev\thorvg.example> .\builddir\src\StudyShape.exe wg
webgpu driver is not detected!
이는 ThorVG가 자동으로 하드웨어를 감지한 건 아니고 example 에 정의되어있다.
...
if (engine == 0) {
window = unique_ptr<Window>(new SwWindow(...));
} else if (engine == 1) {
window = unique_ptr<Window>(new GlWindow(...));
} else if (engine == 2) {
window = unique_ptr<Window>(new WgWindow(...));
}
}
현재 ThorVG는 engines=[cpu]이므로 Software는 동작하지만 GlCanvas::gen()은 지원되지 않는다. 이는 client의 runtime 분기와 library의 compile-time feature 구성이 분리돼 있음을 보여준다.
Canvas?
Canvas : drawing engine과 target buffer를 설정하고, 주어진 Paint 객체를 관리하는 추상 객체
- Canvas는 Backend 추상화이다.
client는
tvg::Canvas*를 사용하지만 실제 구현은 달라질 수 있다.
Canvas
├─ SwCanvas → CPU buffer
├─ GlCanvas → OpenGL target
└─ WgCanvas → WebGPU target
- 출력 target 관리 Shape는 무엇을 그릴지를 표현하지만 어디에 그릴지는 알지 못한다. Canvas가 buffer 크기, stride, color space 또는 GPU target을 Renderer와 연결한다.
- Root scene과 렌더링 순서 관리
Canvas::add(Paint*)는 Paint를 root scene에 넣는다. 공개 계약에 따르면 추가 순서가 렌더링 순서가 되며, 성공하면 Paint의 ownership도 Canvas로 이전된다. - 렌더링 생명주기 조정 Canvas는 State를 관리한다. (Paint 등록 변경 → update → draw → sync)
따라서 Shape를 Renderer에 직접 전달하면 client가 backend 선택, scene ordering, target 상태, 비동기 동기화까지 직접 관리해야 한다. Canvas는 이 책임들을 하나의 public API boundary로 묶는다.
5. Shape Example의 콘텐츠 구성에 사용된 ThorVG Public API
ThorVG의 Shape API는 벡터 그래픽을 구성하는 주요 요소인 geometry, fill, stroke같은 것들을 public interface로 제공한다. 아마 fill rule이라던지, path 그리는 cubic to라던지 line join 방식을 제공한다던지 아직 보진 않았지만 이런 그릴 수 있는 방법을 제공하고, 이것들을 이용해서 shape를 그린다.
| 렌더링 입력 질문 | StudyShape Example | API |
|---|---|---|
| 어떤 그래픽 객체인가? | Shape | Shape::gen() |
| 어떤 기하를 갖는가? | 300×200 사각형 path | appendRect() |
| 내부를 어떻게 표현하는가? | 빨간색 fill | fill() |
| 어느 scene에서 그릴 것인가? | Canvas root scene | Canvas::add() |
Shape::gen()
ThorVG의 Paint는 추상 그래픽 요소이며 직접 생성할 수 없다. 사각형처럼 path 기반 벡터 기하를 표현하기 위해 구체 타입 Shape를 선택한다. Picture는 파일/데이터 로딩, Text는 글리프, Scene은 Paint 그룹을 위한 타입이므로 이번 최소 입력에는 맞지 않는다.
appendRect()
Shape의 핵심 속성 중 하나는 path다. 직접 moveTo/lineTo/close로 사각형을 만들 수도 있지만 appendRect()는 같은 path를 더 명확하고 적은 호출로 만든다. Renderer 관점에서는 “사각형 객체”라기보다 Shape에 축적된 path 입력으로 이어지는지 이후 구현에서 확인해야 한다.
fill()
기하만 있고 fill과 stroke가 모두 없다면 화면에 나타낼 색상 정보가 없다. 이번 study에서는 stroke와 gradient 분기를 제거하고 단색 fill 경로 하나만 남긴다.
Canvas::add()
Shape를 생성하고 속성을 설정하는 것만으로는 렌더링 대상이 아니다. Canvas의 root scene에 등록해야 이후 update/draw가 해당 Paint를 순회할 수 있다. 공개 API 계약상 성공한 add()는 ownership도 Canvas로 이전한다.
이 네 호출은 다음 최소 파이프라인을 구성한다.
객체 선택
→ 기하 정의
→ 외형 정의
→ 렌더링 scene 등록
위 API들에서는 최종 픽셀을 직접 기록하지 않고, 실제 렌더링 요청은 Canvas::draw()에서 시작된다.
6. 실제 제어 흐름
StudyShape.cpp::main() [client]
→ new UserExample [client]
→ tvgexam::main(example, argc, argv) [example harness]
→ engine 기본값 0 유지 [example policy]
→ new SwWindow [example harness]
→ Window 생성자
→ tvg::Initializer::init(4) [ThorVG public API]
→ SDL_Init() [SDL]
→ SDL_CreateWindow() [SDL]
→ tvg::SwCanvas::gen() [ThorVG public API]
→ SDL_GetWindowSurface() [SDL]
→ SwCanvas::target(surface->pixels, ...) [ThorVG public API]
→ Window::ready() [example harness]
→ UserExample::content(canvas, 800, 800) [client hook]
→ Shape::gen() [ThorVG public API]
→ Shape::appendRect() [ThorVG public API]
→ Shape::fill() [ThorVG public API]
→ Canvas::add() [ThorVG public API]
→ Canvas::draw() [ThorVG public API]
→ Canvas::sync() [ThorVG public API]
→ Window::show() [example harness/event loop]
→ SwWindow::refresh()
→ SDL_UpdateWindowSurface() [SDL present]
Example harness가 실행 환경과 target을 준비 → client hook이 ThorVG 객체로 scene을 기술 → Canvas가 scene을 Renderer에 실행하도록 요청 → example harness가 결과 target을 화면에 present 의 제어 흐름을 따른다고 한다. 참고차. by chat GPT
7. Next
각 public api 호출이 변경하는 내부 상태를 추적
Shape::gen()
→ Shape의 public/private 객체 구성
Shape::appendRect()
→ path command/point 저장
Shape::fill()
→ fill state와 dirty flag 변경
Canvas::add()
→ root scene 등록, parent/ownership, update 대상 변경
그 뒤 Canvas::draw()가 이 상태를 Software Renderer 입력으로 바꾸는 경계를 찾아 볼 것이다.
댓글
Discussion 원문