← 과제 목록

[과제] 3주차) ThorVG 활용사례 조사 정선용

@jeongsunyong
  • #과제
목차

DALi에 통합된 ThorVG: 역할과 렌더링 구조

코드로 확인한 DALi의 벡터 그래픽 backend 통합 사례

ThorVG가 실제 제품과 오픈소스 프로젝트에 어떻게 통합되는지 확인하기 위해
Tizen의 UI 프레임워크인 NUI와 그 기반 그래픽 프로젝트 DALi를 조사했다.

Tizen UI 개발자는 View, ImageView, CanvasView 같은 NUI API를 사용하고, 그 아래에서는 오픈소스 프로젝트인 DALi가 UI 장면과 렌더링을 처리하는데,

과제에서는 TizenFX, DALi Toolkit, DALi Adaptor와 ThorVG의 공개 코드를 기준으로, ThorVG가 DALi의 어느 지점에 연결되고 어떤 역할을 담당하는지 정리한다.

1. DALi와 ThorVG의 역할

DALi는 Tizen에서 UI 장면을 구성하고 화면에 합성하는 그래픽 프레임워크다. View의 배치와 상태, 렌더링 리소스, 애니메이션과 최종 GPU 렌더링을 관리한다.

ThorVG와 DALi는 모두 화면을 그리는 그래픽 엔진이다. 최근에 thorVG를 접하게되면서 이 그래픽 엔진이라는 측면에서 겹치는 부분들이 있다고 생각이 들었고, 굳이 백엔드를 두 개 쓰는 이유가 있을까 의문이 들었다.

그러나 둘은 다루는 범위가 다르다. ThorVG는 DALi의 그래픽 전체를 대신하지 않는다.

DALi는 Tizen에서 UI 장면 전체를 관리하는 그래픽 프레임워크이고 View의 배치와 상태, 애니메이션, 렌더링 리소스를 관리하고 최종적으로 화면을 GPU에서 합성하지만, ThorVG는 SVG와 Lottie를 해석하거나 도형과 경로를 그리는 벡터 그래픽 엔진이다. DALi처럼 UI 장면 전체를 관리하지는 않지만, 벡터 콘텐츠를 표현하는 데 필요한 path, fill, stroke와 같은 기능과 이를 픽셀로 변환하는 래스터라이저를 제공한다.

일반적인 DALi View는 DALi가 구성한 Geometry와 Shader를 통해 GPU에서 직접 렌더링된다. 반면 CanvasView의 Shape와 Picture, SVG Visual 같은 벡터 콘텐츠는 ThorVG가 픽셀 버퍼로 래스터라이즈한다. DALi는 이 결과를 Texture로 받아 다른 UI 요소와 함께 최종 화면에 합성한다.

따라서 두 라이브러리의 관계는 다음처럼 구분할 수 있다.

구분담당
UI 장면, View, 갱신 일정, Texture, 최종 합성DALi
Shape·Picture 구성, SVG·Lottie 해석, 벡터 래스터라이즈ThorVG
image

2. DALi와 ThorVG의 역할 - example

DALi에서 Rounded Corner의 Rect를 배치한다던지, Stroke를 그린다던지 할 때 사용하는 CornerRadius, Borderline, gradient, masking은 벡터 그래픽과 비슷해 보이지만 모두 ThorVG가 처리하는 것은 아니다. DALi의 일반 View는 대부분 ThorVG를 거치지 않는다.

예를 들어 NUI View에 배경색, 둥근 모서리와 테두리를 지정하면 image Toolkit은 ColorVisual과 DecoratedVisualRenderer를 구성한다. DALi는 사각형 Geometry와 속성값을 GPU shader에 전달하고, fragment shader가 둥근 영역과 테두리를 계산한다. 일반 GradientVisual과 ImageVisual의 alpha mask도 DALi 전용 shader 경로를 사용할 수 있다.

** 일반 Visual은 DALi shader 경로를 사용한다.

일반 View의 배경·모서리·테두리는 위 처럼 ThorVG를 거치지 않고 DALi가 Geometry와 Shader로 구성해 GPU에서 직접 그린다

반면 아래 콘텐츠는 정해진 사각형 shader만으로 처리하기 어렵다. image

  • 사용자가 MoveTo, LineTo, CubicTo로 만드는 임의 path
  • stroke cap, join, dash와 fill rule
  • SVG에 포함된 여러 path, transform, clip과 gradient
  • 시간에 따라 path가 변하는 Lottie animation

DALi는 이 범용 벡터 콘텐츠를 직접 해석하고 rasterization하는 대신 ThorVG를 사용한다. 현재 DALi 코드는 ThorVG를 일반 SVG Visual뿐 아니라 CanvasRenderer Shape와 Picture, vector animation에도 공통으로 사용한다.

3. ThorVG는 DALi Adaptor에 통합되어 있다

DALi 공개 코드는 역할에 따라 Core, Adaptor, Toolkit으로 나뉜다. 이 구분을 모두 상세히 알 필요는 없지만, ThorVG의 위치를 이해하려면 Adaptor가 중요하다.

  • Toolkit은 CanvasView와 SVG Visual 같은 상위 UI 기능 및 작업 일정을 관리한다.
  • Adaptor는 플랫폼 기능을 DALi에 연결하며, ThorVG 객체 생성과 API 호출을 담당한다.
  • Core는 완성된 Texture를 다른 UI 요소와 함께 GPU에서 합성한다.

실제 ThorVG 의존성과 구현 코드는 주로 DALi Adaptor에서 확인된다. 즉 Toolkit이 벡터 렌더링 작업을 요청하면 Adaptor가 ThorVG를 호출하고, 결과 버퍼가 다시 DALi의 Texture로 전달되는 구조다. (최종 composite은 DALi에서.)

dali drawio

4. Shape.AddRect()로 본 실제 호출 흐름 : Dali 객체는 ThorVG객체를 어떻게 사용하는지.

DALi의 CanvasRenderer는 ThorVG 객체를 DALi API로 감싸 제공하는데, DALi Adaptor는 ThorVG를 필요한 순간에 함수만 호출하는 유틸리티처럼 사용하지 않는다. DALi의 CanvasRenderer 객체가 대응하는 ThorVG 객체를 내부에 보관하고, NUI에서 들어온 요청을 ThorVG API로 전달하는 어댑터 역할을 한다.

DALiThorVG역할
Shapetvg::Shape도형, 경로, fill과 stroke
Picturetvg::PictureSVG, Lottie 등 벡터 리소스
DrawableGrouptvg::Scene여러 벡터 객체를 묶은 장면
CanvasRenderertvg::SwCanvas장면을 픽셀 버퍼에 래스터라이즈

adaptor가 연결하는 것을 확인해보면, Shape::Initialize()에서 DALi Adaptor는 tvg::Shape를 생성해, DALi Drawable의 내부 객체로 등록한다.

mTvgShape = tvg::Shape::gen();

Drawable::SetObject(static_cast<void*>(mTvgShape));
Drawable::SetType(Drawable::Types::SHAPE);

두 객체는 호출할 때마다 임시 변환되는 관계가 아니고, DALi Shape가 mTvgShape를 보관하고 이후 도형과 스타일 변경 요청을 이 객체로 전달한다.

이 대응 관계에서 DALi는 객체의 수명과 갱신 시점을 관리하고, 실제 벡터 데이터는 ThorVG 객체가 가진다. Shape.AddRect()의 호출 경로를 따라가면 이 구조를 구체적으로 확인할 수 있다.

NUI에서 둥근 사각형을 추가하는 작업은 Shape.AddRect() 호출로 시작한다.(TizenFX)

Shape rect = new Shape();
rect.AddRect(50, 50, 200, 200, 20, 20);
canvasView.AddDrawable(rect);
public bool AddRect(
    float x, float y,
    float width, float height,
    float roundedCornerX, float roundedCornerY);

TizenFX의 Shape.AddRect()는 직접 도형을 그리지 않고 네이티브 DALi 호출로 요청을 전달한다.

Tizen.NUI Shape.AddRect()
  → TizenFX native interop
  → DALi CanvasRenderer::Shape::AddRect()

DALi Adaptor의 실제 Shape::AddRect()mTvgShapetvg::Shape로 사용해 ThorVG의 appendRect()를 호출하고, 성공하면 SetChanged(true)로 갱신이 필요하다는 상태도 기록한다.

if(static_cast<tvg::Shape*>(mTvgShape)->appendRect(
     rect.x, rect.y, rect.width, rect.height,
     roundedCorner.x, roundedCorner.y) != tvg::Result::Success)
{
  return false;
}
Drawable::SetChanged(true);
return true;

DALi가 사각형의 벡터 Path를 별도로 계산하는 것이 아니라, API 요청과 변경 상태를 관리하면서 실제 Path 구성은 보관 중인 ThorVG 객체에 위임하는 구조. 네이티브 요청을 받은 DALi Adaptor의 Shape 구현은 내부에서 관리하는 ThorVG Shape에 같은 좌표와 반지름을 전달한다.

Result Shape::appendRect(float x, float y, float w, float h,
                         float rx, float ry, bool cw) noexcept
{
    to(this)->addRect(x, y, w, h, rx, ry, cw);
    return Result::Success;
}

전체 호출 관계를 정리하면 다음과 같다.

NUI Shape.AddRect()
  → TizenFX native interop
  → DALi Adaptor Shape::AddRect()
  → tvg::Shape::appendRect()
  → ThorVG 내부 Path에 rounded rectangle 추가

이 호출만으로 화면에 즉시 픽셀이 그려지는 것은 아니고. appendRect()는 ThorVG의 벡터 장면에 사각형 Path를 추가하는 단계다. 이후 DALi가 CanvasView의 갱신과 래스터라이즈 작업을 예약하면 ThorVG가 장면을 픽셀 버퍼로 변환하고, DALi는 그 결과를 Texture로 받아 다른 UI 요소와 함께 최종 화면에 합성한다.

5. SVG에서 ThorVG가 사용되는 두 경로

DALi의 SVG는 크게 두 경로에서 ThorVG를 사용한다.

CanvasRenderer Picture 경로

NUI의 CanvasView에서는 Picture를 Shape와 같은 Drawable로 추가할 수 있다.

var canvasView = new CanvasView();

var picture = new Picture();
picture.Load("icon.svg");
canvasView.AddDrawable(picture);

Picture.Load()는 P/Invoke를 통해 DALi의 네이티브 Picture 구현을 호출한다.

bool ret = Interop.Picture.Load(View.getCPtr(this), url);

DALi Adaptor는 tvg::Picture를 생성해 DALi의 Drawable 내부 객체로 등록하고, 파일 경로를 ThorVG의 load()에 직접 전달한다.

mTvgPicture = tvg::Picture::gen();
Drawable::SetObject(static_cast<void*>(mTvgPicture));
Drawable::SetType(Drawable::Types::PICTURE);

if(mTvgPicture->load(url.c_str()) != tvg::Result::Success)
{
  return false;
}
Drawable::SetChanged(true);

로드한 Picture는 CanvasView.AddDrawable()을 통해 CanvasRenderer에 전달된다. DALi Toolkit의 CanvasView는 이 요청을 내부 CanvasRenderer로 넘긴다.

bool CanvasView::AddDrawable(Dali::CanvasRenderer::Drawable& drawable)
{
  if(mCanvasRenderer && mCanvasRenderer.AddDrawable(drawable))
  {
    return true;
  }
  return false;
}

따라서 이 경로에서 SVG는 독립적인 이미지 한 장이 아니라 Canvas를 구성하는 Drawable 중 하나다. 같은 Canvas에 Shape나 다른 Picture를 추가해 함께 래스터라이즈할 수 있다.

SVG Visual 경로

SVG 한 장을 일반 이미지처럼 표시할 때는 ImageView에 파일 경로를 지정할 수 있다.

var imageView = new ImageView("icon.svg");

ImageView는 URL을 DALi에 전달하고, Toolkit의 VisualFactory는 URL 형식을 확인해 SVG인 경우 SvgVisual을 생성한다.

case VisualUrl::TVG:
case VisualUrl::SVG:
{
  visualPtr = SvgVisual::New(
    GetFactoryCache(),
    GetImageVisualShaderFactory(),
    visualUrl,
    propertyMap);
  break;
}

이후 Toolkit의 SVG Visual 파이프라인이 비동기 로딩, 캐시, 크기 계산과 Texture 생성 과정을 관리한다. Toolkit이 생성한 VectorImageRenderer는 Adaptor에서 다시 tvg::Picture를 만들고, 파일 경로가 아니라 메모리에 읽어 둔 SVG 데이터와 형식 문자열을 전달한다.

mPicture = tvg::Picture::gen();

tvg::Result ret = mPicture->load(
  reinterpret_cast<char*>(data.Begin()),
  data.Size(), "svg", nullptr, true);

두 경로 모두 최종적으로 ThorVG Picture::load()에 도달하며, SVG 해석과 벡터 래스터라이즈는 ThorVG가 담당한다. 다만 CanvasRenderer::Picture 경로는 SVG를 Shape나 다른 Picture와 조합할 수 있는 Canvas의 구성 요소로 사용하고, SVG Visual 경로는 SVG 한 장을 ImageView의 이미지 리소스로 표시하는 데 사용한다. 이러한 사용 목적의 차이에 따라 ThorVG 앞에 놓인 DALi의 관리 방식도 달라진다. CanvasRenderer::Picture는 파일 경로를 직접 전달하고 Canvas의 다른 Drawable과 함께 관리한다. 반면 SVG Visual은 Toolkit의 비동기 작업과 캐시를 거친 메모리 데이터를 전달하고, 래스터라이즈 결과를 Texture로 관리한다.

6. ThorVG 빌드 통합

Tizen용 DALi Adaptor spec은 pkgconfig(thorvg)BuildRequires로 선언한다. 빌드 과정에서 ThorVG를 찾으면 THORVG_SUPPORT가 정의되고, 설치된 ThorVG API에 따라 THORVG_VERSION_0 또는 THORVG_VERSION_1 분기가 선택된다.

이 매크로는 단순히 header include 여부만 결정하지 않는다. CanvasRenderer의 ThorVG 구현 전체와 VectorImageRenderer의 SVG backend 선택에 사용된다. ThorVG 지원이 없는 빌드에서 SVG VectorImageRenderer는 NanoSVG 구현으로 분기하지만, Tizen 패키지는 ThorVG를 빌드 의존성으로 요구한다.

빌드 연결 지점

7. 정리

DALi는 개발자 API와 비동기 작업, 캐시, 네이티브 버퍼를 관리하고 최종 GPU 합성을 담당한다. SVG, Lottie, 임의 Path처럼 범용적인 벡터 처리가 필요한 콘텐츠는 ThorVG의 Shape와 Picture로 구성하고, SwCanvas를 통해 픽셀 버퍼로 래스터라이즈한다. DALi는 이 결과를 Texture로 연결해 기존 렌더링 파이프라인에서 다른 UI 요소와 함께 합성하는 방식으로 ThorVG를 통합했다. 이러한 역할 분리 덕분에 DALi는 기존 UI 렌더링 구조를 유지하면서도 SVG, Lottie, 임의 Path, fill과 stroke 같은 벡터 그래픽 기능을 활용할 수 있다.

ThorVG는 DALi를 대체하는 렌더러가 아니라, DALi가 벡터 콘텐츠를 처리할 때 사용하는 전용 벡터 엔진

댓글

Discussion 원문