오늘의 'The Fast and the Curious' 포스팅에서는 애플 실리콘(Apple Silicon) 맥용 크롬에 도입된 Skia의 새로운 래스터화 백엔드, Graphite의 출시 소식을 다룹니다. Graphite는 크롬이 Motionmark 1.3에서 뛰어난 점수를 기록하는 데 결정적인 역할을 했으며, 향후 크롬 그래픽의 수많은 개선 사항을 실현할 핵심 기술입니다.
크롬 내 Skia의 짧은 역사
크롬에서 Skia는 Blink 엔진과 브라우저 UI의 그리기 명령(paint commands)을 화면의 픽셀로 변환하는 '래스터화(rasterization)' 프로세스에 사용됩니다. Skia는 아주 초기부터 크롬 그래픽의 기반이 되었습니다. 하지만 웹이 진화하고 복잡해짐에 따라 Skia는 성능 문제에 직면했고, 이에 크롬과 Skia 팀은 'Ganesh'라는 GPU 가속 래스터화 백엔드에 투자하게 되었습니다.
수년 동안 Ganesh는 고성능 래스터화 백엔드로 성숙했으며, 모든 플랫폼의 크롬에서 GL(윈도우 D3D9/11에서는 ANGLE 사용)을 기반으로 GPU 래스터화가 출시되었습니다. 하지만 Ganesh는 GL 중심의 설계로 인해 특수 코드 경로가 너무 많았고, 팀이 현대적 그래픽 API를 활용한 최적화를 체계적으로 구현하려 할 때 한계에 부딪혔습니다.
이를 계기로 팀은 GPU 래스터화를 처음부터 다시 생각하게 되었고, 그 결과가 바로 새로운 래스터화 백엔드인 Graphite입니다. Graphite는 처음부터 더 적고 이해하기 쉬운 코드 경로를 가진 원칙적인(principled) 설계를 목표로 개발되었습니다. 이러한 미래지향적 설계는 Metal, Vulkan, D3D12와 같은 현대적 그래픽 API와 컴퓨트 기반 경로 래스터화(compute based path rasterization) 같은 패러다임을 활용하는 데 도움을 주며, 기본적으로 멀티스레드 방식으로 작동합니다.
결과
크롬에 Graphite를 도입한 결과, 맥북 프로 M3에서 Motionmark 1.3 점수가 약 15% 향상되었습니다. 동시에 INP(다음 페인트 상호작용 시간), LCP(최대 콘텐츠 풀 페인트 시간), 그래픽 매끄러움(프레임 드랍률), GPU 프로세스 메모리 사용량 등 실제 지표도 개선되었습니다. 이는 결과적으로 더 부드러운 상호작용, 스크롤 시 끊김 감소, 사이트가 표시될 때까지 대기하는 시간 단축을 의미합니다.
Graphite와 Ganesh의 차이점
현대적 그래픽 API
Ganesh는 원래 멀티스레딩이나 컴퓨트 셰이더와 같은 GPU 기능을 최소한으로 지원하던 OpenGL ES를 기반으로 구현되었습니다. 이후 Vulkan, Metal, D3D12와 같은 현대적 그래픽 API가 등장하여 멀티스레딩을 활용하고 새로운 GPU 기능을 노출하며 진화했습니다. 이러한 API는 애플리케이션이 CPU와 GPU를 모두 효율적으로 활용하면서, GPU 리소스 할당과 같은 비용이 많이 드는 작업의 시점과 방식을 훨씬 더 세밀하게 제어할 수 있게 해줍니다.
Ganesh를 현대적 그래픽 API에 맞게 조정할 수는 있었지만, 이미 쌓인 기술 부채로 인해 현대적 API의 멀티스레딩 및 GPU 컴퓨트 기능을 온전히 활용하기는 어려웠습니다.
크롬의 Graphite에서는 플랫폼 네이티브 그래픽 API(Metal, Vulkan, D3D)의 추상화 계층으로 크롬의 WebGPU 구현체인 Dawn을 사용하기로 했습니다. Dawn은 현대적 그래픽 API의 공통적인 기본 역량을 제공하며, Graphite를 위해 네이티브 백엔드를 처음부터 구현하는 대신 이미 검증된 Dawn의 백엔드를 활용함으로써 장기적인 유지보수 부담을 줄여줍니다.
2D 깊이(?!) 테스트
GPU 렌더링 파이프라인의 핵심 요소 중 하나는 '깊이 테스트(depth testing)'입니다. 이는 불투명한 객체를 앞에서 뒤로 그리고, 투명한 객체를 뒤에서 앞으로 그림으로써 오버드로우(overdraw)를 줄이거나 제거할 수 있는 기술입니다. 그래픽에서 '오버드로우'란 동일한 픽셀을 불필요하게 여러 번 렌더링하는 것을 의미하며, 특히 모바일 기기에서 성능과 배터리 수명에 부정적인 영향을 미칠 수 있습니다.
Ganesh는 원래 3D 콘텐츠 렌더링용으로 설계된 그래픽 카드의 깊이 테스트 기능을 2D 그래픽 가속에는 활용하지 않았습니다. 이 때문에 Ganesh는 불투명 및 투명 객체를 그릴 때 엄격한 '화가의 순서(painter’s order)'에만 의존하여 오버드로우 문제를 안고 있었습니다.
Graphite는 각 '그리기(draw)' 작업에 화가의 순서 인덱스를 정의하는 z값을 할당하여 GPU 렌더링이 깊이 테스트를 활용할 수 있도록 확장했습니다. 투명한 효과와 이미지는 여전히 뒤에서 앞으로 그려야 하지만, 전경의 불투명한 객체는 이제 오버드로우를 자동으로 제거할 수 있습니다. 즉, 불투명한 그리기 작업은 값비싼 GPU 상태 변경을 최소화하도록 재정렬될 수 있으며, 깊이 버퍼를 통해 정확한 결과물을 생성합니다.
깊이 테스트는 Graphite에서 '클리핑(clipping)'을 구현하는 데도 사용됩니다. Ganesh처럼 클립 스택을 유지하는 대신, 클립 모양을 깊이 전용 그리기 작업으로 처리합니다. 이 방식은 알고리즘 복잡도를 줄일 뿐만 아니라, 그리기에 필요한 셰이더 프로그램이 클립 스택 상태에 의존하지 않게 된다는 큰 장점이 있습니다.
왼쪽: Motionmark Suits의 한 프레임 / 오른쪽: 동일한 프레임의 깊이 버퍼.
멀티스레딩
크롬은 복잡한 멀티 프로세스 애플리케이션으로, 렌더러 프로세스가 웹페이지, 탭, 브라우저 UI 등을 실제로 화면에 표시하는 공유 GPU 프로세스에 명령을 내립니다. GPU 프로세스의 메인 스레드는 모든 렌더링 작업을 주도하며 모든 GPU 명령이 발행되는 곳입니다.
Ganesh와 OpenGL의 단일 스레드 특성상 아주 제한적인 작업만 다른 스레드로 옮길 수 있었기 때문에, GPU 메인 스레드에 부하가 걸리기 쉬웠습니다. 이는 결국 끊김(jank)과 지연 시간을 유발하여 사용자 경험을 해치는 원인이 되었습니다.
반면 Graphite의 API는 현대적 그래픽 API의 멀티스레딩 기능을 활용하도록 설계되었습니다. Graphite의 새로운 핵심 API는 여러 스레드에서 서로 동기화할 필요 없이 '기록(Recordings)'을 생성할 수 있는 독립적인 '기록기(Recorders)'를 중심으로 구성됩니다. 기록 자체는 메인 스레드에서 GPU로 제출되지만, 기록을 생성하는 동안 발생하는 비용이 많이 드는 작업은 다른 스레드로 이동하여 GPU 메인 스레드를 자유롭게 유지합니다.
성능 급락(Performance cliffs) 및 파이프라인 컴파일
Ganesh가 처음 구현되었을 때는 그래픽 카드의 프로그래밍 기능이 상당히 제한적이었고, 특히 분기(branching) 처리에 비용이 많이 들었습니다. 이를 해결하기 위해 Ganesh는 일반적인 사례를 처리하기 위한 수많은 특수 셰이더 파이프라인을 가졌습니다. 이러한 특수화는 예측하기 어렵고 각 그리기의 수많은 요인에 따라 달라지므로, 본질적으로 동일한 페이지 콘텐츠임에도 수많은 다른 파이프라인이 생성되는 결과를 초래했습니다. 이 파이프라인들은 각각 컴파일되어야 하므로, 애니메이션이나 효과가 수시로 새로운 파이프라인을 트리거하는 현대적 웹 콘텐츠에서는 눈에 띄는 끊김을 유발했습니다.
Graphite의 설계 철학은 성능을 유지하면서 렌더링 파이프라인의 수를 최대한 통합하는 것입니다. 이를 통해 컴파일해야 할 파이프라인 수를 줄이고, 크롬이 시작 시점에 이를 미리 컴파일하여 브라우징 중에 방해받지 않도록 보장합니다. Ganesh의 특수화 방식은 예기치 않은 성능 급락을 초래하기도 했습니다. 간단한 케이스는 잘 처리했지만, 실제 페이지 콘텐츠는 복잡한 혼합인 경우가 많았기 때문입니다. 파이프라인을 통합함으로써 복잡한 콘텐츠도 간단한 콘텐츠만큼 효과적으로 렌더링할 수 있게 되었습니다.
향후 계획
멀티스레드 래스터화
현재 Graphite는 두 개의 기록기(Recorder)를 사용하여 크롬에 통합되어 있습니다. 하나는 메인 스레드에서 웹 콘텐츠 타일과 Canvas2D를 처리하고, 다른 하나는 합성을 담당합니다. 앞으로 이 모델은 크롬의 성능을 더욱 향상시킬 수 있는 수많은 흥미로운 가능성을 열어줄 것입니다. 각 렌더러 프로세스의 작업으로 GPU 메인 스레드를 포화시키는 대신, 래스터화를 여러 스레드에 걸쳐 분산시킬 수 있습니다.
현재 방식:
미래 방식:
단순한 콘텐츠를 위한 GPU 메모리 절약
Graphite 기록(Recordings)은 평행 이동(translation)과 같은 특정 동적 변경 사항을 적용하여 GPU에 다시 발행될 수 있습니다. 이는 렌더링 명령을 다시 생성하는 불필요한 작업을 제거하면서 스크롤 성능을 가속화하는 데 사용될 수 있습니다. 또한 웹 콘텐츠를 타일 형태로 캐싱하는 데 필요한 GPU 메모리 양을 자동으로 줄일 수 있게 해줍니다. 콘텐츠가 충분히 단순하다면, 캐시된 이미지를 그리는 것과 콘텐츠를 직접 다시 그리는 것 사이의 성능 차이가 크지 않으므로 타일 할당을 생략하고 매 프레임마다 다시 렌더링하는 것이 더 나을 수 있습니다.
GPU 컴퓨트 경로 래스터화
2D 그래픽 렌더링 분야에서는 GPU 컴퓨트 기반 경로 래스터화가 Pathfinder나 vello와 같은 최근 구현 사례와 함께 매우 각광받고 있습니다. 저희는 이러한 아이디어를 하이브리드 방식을 사용하여 Skia에 구현하고자 합니다. 현재 Graphite는 가능한 경우 MSAA에 의존하지만, 구형 통합 GPU에서의 성능 저하나 비타일링 GPU에서의 높은 메모리 오버헤드로 인해 사용 불가능한 경우가 많습니다. 이럴 때는 아틀라스를 사용해 캐싱하는 CPU 경로 래스터화로 폴백(fallback)해야 합니다. GPU 컴퓨트 기반 경로 래스터화를 도입하면 픽셀당 4개 샘플로 제한되는 경우가 많은 MSAA의 시각적 품질과 CPU 래스터화의 성능을 모두 뛰어넘을 수 있을 것입니다.
이는 크롬 그래픽 팀이 추진하고자 하는 향후 방향이며, 우리가 이 기술을 어디까지 발전시킬 수 있을지 기대가 큽니다.




