介绍Skia Graphite:Chrome未来的光栅化后端
今日的"The Fast and the Curious"系列文章介绍了Skia新光栅化后端Graphite在Apple Silicon Mac版Chrome中的发布。Graphite助力Chrome在Motionmark 1.3测试中获得卓越成绩,并为Chrome图形功能的未来改进奠定基础。
Chrome中Skia的发展历程
在Chrome中,Skia用于将Blink和浏览器UI的绘制命令渲染为屏幕上的像素,这个过程称为光栅化。自Chrome诞生之初,Skia就一直是其图形功能的核心引擎。随着网页技术的发展和复杂化,Skia最终遇到了性能瓶颈,这促使Chrome和Skia团队投资开发了名为Ganesh的GPU加速光栅化后端。
多年来,Ganesh已发展成为稳定高性能的光栅化后端,并在所有平台的Chrome上基于GL(在Windows上通过ANGLE支持D3D9/11)实现了GPU光栅化。然而,Ganesh始终采用以GL为中心的设计,包含过多专用代码路径,团队在尝试以原则性方式利用现代图形API进行优化时遇到了瓶颈。
这为团队重新构思GPU光栅化奠定了基础,最终形成了新的光栅化后端Graphite。Graphite从开发之初就遵循原则性设计,采用更少且更易理解的代码路径。这种前瞻性设计有助于利用Metal、Vulkan和D3D12等现代图形API以及基于计算的光栅化路径等范式,并默认支持多线程。
性能成果
在Chrome中集成Graphite后,我们在Macbook Pro M3上的Motionmark 1.3得分提高了近15%。同时,我们改进了实际用户体验指标,包括INP(交互到下次绘制时间)、LCP(最大内容绘制时间)、图形流畅度(丢帧百分比)、GPU进程malloc内存使用量等。这意味着交互更加流畅,滚动时的卡顿减少,等待网站显示的时间缩短。
Graphite与Ganesh的区别
现代图形API
Ganesh最初基于OpenGL ES实现,对多线程或计算着色器等GPU功能的支持极为有限。此后,Vulkan、Metal和D3D12等现代图形API不断发展,能够充分利用多线程并暴露新的GPU能力。这些API让应用程序能够更精细地控制何时以及如何执行和调度昂贵的工作(如分配GPU资源),同时有效利用CPU和GPU。
虽然我们能够调整Ganesh以支持现代图形API,但它积累了足够多的技术债务,难以充分利用现代图形API的多线程和GPU计算能力。
对于Chrome中的Graphite,我们选择使用Chrome的WebGPU实现Dawn作为平台原生图形API(如Metal、Vulkan和D3D)的抽象层。Dawn为现代图形API的通用功能提供了基准,并通过利用Dawn成熟且经过充分测试的原生后端,而不是为Graphite从头实现,帮助我们减少了长期维护负担。
2D深度测试
GPU渲染管线的核心部分是深度测试,它可以通过从前到后绘制不透明对象,然后从后到前绘制半透明对象,来减少或消除过度绘制。在图形学中,“过度绘制"指的是对相同像素进行多次不必要的渲染,这可能对性能和电池寿命产生负面影响,尤其是在移动设备上。
Ganesh从未利用显卡的深度测试能力,这原本是为渲染3D内容而非加速2D图形设计的。由于Ganesh在绘制不透明和半透明对象时都依赖严格的画家算法顺序,因此容易产生过度绘制。
Graphite扩展了Skia的GPU渲染以利用深度测试,为每个"绘制"分配一个z值,定义其在画家算法中的顺序索引。虽然透明效果和图像仍需从后到前绘制,但前景中的不透明对象现在可以自动消除过度绘制。这意味着可以重新排序不透明绘制以最小化昂贵的GPU状态更改,同时依赖深度缓冲区产生正确输出。
深度测试还用于在Graphite中实现裁剪,将裁剪形状视为仅深度绘制,而不是像Ganesh那样维护裁剪堆栈。除了降低算法复杂度外,这种方法的一个重要优势是渲染"绘制"所需的着色器程序不再依赖于裁剪堆栈的状态。
多线程
Chromium是一个复杂的多进程应用程序,渲染进程向共享的GPU进程发出命令,GPU进程负责实际显示网页、标签页甚至浏览器UI中的所有内容。GPU进程主线程是所有渲染工作的主要驱动者,也是所有GPU命令发出的地方。
由于Ganesh和OpenGL的单线程特性,只有有限的工作可以转移到其他线程,这容易导致主线程过载,造成卡顿和延迟增加,最终损害用户体验。
相比之下,Graphite的API设计旨在利用现代图形API的多线程能力。Graphite的新核心API围绕独立的Recorder构建,这些Recorder可以在多个线程上生成Recording,且几乎不需要在它们之间同步。尽管Recording是在主线程上提交给GPU的,但更昂贵的工作在生成Recording时被转移到其他线程,从而释放GPU主线程。
性能悬崖和管线编译
当Ganesh最初实现时,显卡的可编程能力相当有限,分支特别昂贵。为了解决这个问题,Ganesh有许多专门的着色器管线来处理常见情况。这些专门化难以预测,并且依赖于与每个单独绘制相关的大量因素,导致本质上相同的页面内容需要大量不同的管线。由于这些管线必须分别编译,这对于可能随时因效果和动画触发新管线的现代网页内容效果不佳,导致明显的卡顿。
Graphite的设计理念是尽可能整合渲染管线的数量,同时保持性能。这减少了必须编译的管线数量,并使Chrome能够在启动时确保它们被编译,从而不会中断正在进行的浏览。Ganesh的专门化方法还导致了意外的性能悬崖。例如,虽然它能处理简单情况,但真实页面内容通常是复杂混合的。通过整合管线,复杂内容可以像简单内容一样有效地渲染。
未来计划
多线程光栅化
目前,Graphite使用两个Recorder集成到Chromium中:一个在主线程上处理网页内容瓦片和Canvas2D,另一个用于合成。未来,这种模型将开启许多令人兴奋的可能性,以进一步提高Chrome的性能。光栅化可以在多个线程上分叉执行,而不是让每个渲染器进程的任务饱和主GPU线程。
当前架构示意图
未来架构示意图
减少简单内容的GPU内存
Graphite recording还可以通过某些动态更改(如平移)重新发布到GPU。这可用于加速滚动,同时消除重新发布渲染命令的不必要工作。这使我们能够自动减少缓存网页内容为瓦片所需的GPU内存量。如果内容足够简单,绘制缓存图像与绘制其内容之间的性能差异可能值得跳过为其分配瓦片,而是每帧重新渲染它。
GPU计算路径光栅化
在2D图形渲染领域,基于GPU计算的路径光栅化非常流行,最近有Pathfinder和vello等实现。我们希望在Skia中实现这些想法,可能采用混合方法。目前,Graphite尽可能依赖MSAA,但在许多情况下,由于旧集成GPU性能较差或非平铺GPU内存开销高,我们无法使用MSAA,必须回退到使用图集进行缓存的CPU路径光栅化。基于GPU计算的路径光栅化将使我们能够改进MSAA的视觉质量(通常限于每像素4个样本)和CPU光栅化的性能。
这些是Chrome图形团队计划追求的未来方向,我们期待看到我们能将技术推进到什么程度。
发布者:Michael Ludwig & Sunny Sachanandani