技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBR

GPU 加速在前端的应用:从浏览器渲染流程到合成层

所属馆藏
Browser
文件格式
Markdown
原始路径
Browser/05-GPU加速在前端的应用(浏览器渲染流程)
本文目录10 个章节
  1. 一、GPU 和 CPU 分别擅长什么?
  2. 二、为什么 GPU 适合图形处理?
  3. 三、浏览器页面是怎样绘制到屏幕上的?
  4. 四、Layout、Paint 和 Composite 的三种常见更新路径
  5. 五、渲染层、合成层与图层树
  6. 六、如何合理使用硬件加速和合成动画?
  7. 七、GPU 加速的代价和常见误区
  8. 八、如何验证是否真的变快?
  9. 九、小结
  10. 参考资料

GPU 加速在前端的应用:从浏览器渲染流程到合成层

Category(分类): Browser Status: 已更新

本文尽量保留原文关于 CPU/GPU、浏览器渲染流程、Composite(合成)和合成层的讨论,并修正“GPU 一定比 CPU 快”“开启 GPU 加速就不会重排重绘”等容易误导的说法。浏览器内部实现会随版本和平台变化,文中的渲染阶段是便于理解的模型,不应当当成所有浏览器都完全相同的实现细节。

一、GPU 和 CPU 分别擅长什么?

GPU(Graphics Processing Unit,图形处理器)最初主要用于图形图像处理,现在也广泛用于视频解码、通用并行计算和机器学习等场景。CPU 则是面向通用任务的处理器,擅长复杂控制流、分支判断、低延迟响应和各种不同类型的任务。

CPU 与 GPU 的结构对比

GPU 通常包含大量相对简单的执行单元,适合对大量相似数据执行相同或相近的操作,例如对像素、顶点和纹理进行批量计算。CPU 的核心数量通常较少,但单核控制能力、缓存层次和分支预测能力更强,多个 CPU 核心同样可以提供真正的并行执行。

因此,不能简单地说“GPU 的浮点性能是某款 CPU 的多少倍”。不同型号、指令集、精度、内存带宽、任务分支和数据传输开销都会改变结果。GPU 也不是所有任务都更快:如果任务很小、分支复杂,或者需要频繁把数据在 CPU 与 GPU 之间搬运,使用 GPU 反而可能增加开销。

二、为什么 GPU 适合图形处理?

图形处理经常包含大量相互独立的计算:

  • 把顶点坐标变换到屏幕空间;
  • 为像素计算颜色、纹理和光照;
  • 对图片进行缩放、滤镜和混合;
  • 将多个已经绘制好的表面按顺序合成。

这类任务具有较高的数据并行性。GPU 可以把工作拆成许多相似的小任务交给不同的执行单元处理:

数据并行处理示意

但“并行”并不代表所有数据都能同时无条件计算。任务之间存在依赖、分支或同步时,GPU 的优势会降低;GPU 的工作还要经过驱动、命令队列和显存/共享内存访问,不能脱离完整系统单独比较理论峰值。

三、浏览器页面是怎样绘制到屏幕上的?

简化后,一个页面通常会经历如下阶段:

  1. 解析:解析 HTML 生成 DOM,解析 CSS 生成 CSSOM;
  2. 样式计算(Style):计算每个节点最终适用的样式;
  3. 布局(Layout):计算盒子的尺寸和位置;
  4. 绘制准备(Paint):把背景、文字、边框、阴影、图片等绘制指令记录下来;
  5. 栅格化(Raster):把绘制指令转成位图,通常会按图块(tile)处理;
  6. 合成(Composite):把不同图层或表面按正确顺序、变换和透明度合成为最终画面;
  7. 显示:交给操作系统和显示设备呈现。

页面渲染流程

这不是严格的一条直线。浏览器会缓存样式、布局和栅格结果,只更新受影响的部分;动画、滚动、资源加载和合成可能在不同线程或不同时间片执行。某些阶段还可能并行或被跳过。

浏览器进程、合成线程与 GPU 进程

现代浏览器通常把页面放在渲染器进程中,并通过合成线程处理部分滚动和合成工作;GPU 进程负责与图形 API、GPU 设备交互。不同浏览器、操作系统和硬件可能采用不同的进程与线程安排:

浏览器内部结构示意

因此,“CSS 属性由 GPU 处理”并不准确。一个更可靠的说法是:某些属性变化可以让浏览器只更新合成阶段,浏览器可能把相关内容放在独立的合成表面上,最终使用 GPU 或其他图形路径完成合成。栅格化、图片解码和绘制不一定全部由 GPU 完成。

四、Layout、Paint 和 Composite 的三种常见更新路径

原文用三种路径解释为什么某些动画更流畅。可以把它整理为以下模型:

1. 几何属性变化:可能触发布局和绘制

修改 widthheightmarginpaddingtopleft 等属性,可能改变元素和其他元素的几何关系:

box.style.width = `${nextWidth}px`

浏览器可能需要重新计算布局,再绘制受影响的区域,最后合成。影响范围取决于元素在布局树中的位置,不一定每次都导致整页重排,但频繁修改这类属性可能产生较高成本。

2. 绘制属性变化:通常跳过布局,但仍需要 Paint

颜色、阴影、背景和部分滤镜变化不一定改变布局尺寸,却可能使浏览器重新绘制像素:

box.style.backgroundColor = nextColor

绘制面积、文字、阴影、滤镜和图片复杂度都会影响成本。跳过 Layout 不等于免费。

3. 合成属性变化:有机会只更新 Composite

transformopacity 是最常用于合成动画的属性:

.card {
  transition: transform 200ms ease, opacity 200ms ease;
}

.card.is-moving {
  transform: translate3d(0, -12px, 0);
  opacity: 0.85;
}

如果浏览器已经为元素准备了合适的合成表面,动画过程中可能只改变变换矩阵或透明度,不必每一帧重新布局和绘制:

合成动画与普通动画的对比:未使用合成路径

合成动画与普通动画的对比:使用合成路径

“可能只更新合成”是优化机会,不是规范保证。元素首次进入合成层、改变了其他属性、包含复杂滤镜或受到祖先/后代影响时,仍可能发生 Paint 或 Layout。

浏览器 Composite 合成流程

常见渲染更新路径

五、渲染层、合成层与图层树

旧资料中经常出现 WebKit 的 RenderLayerGraphicsLayer 等术语。它们可以帮助理解历史实现,但不能直接当作今天所有 Chromium、Safari 或 Firefox 的公开 API。现代浏览器内部还会使用布局树、绘制块、合成层、图块和 GPU 表面等多个概念。

从开发者角度,可以这样理解:

  • 页面首先产生绘制指令;
  • 浏览器根据重叠、变换、滚动、视频、滤镜等因素决定哪些内容需要单独的合成表面;
  • 合成器再按照层级、裁剪、混合和变换关系把这些表面组合起来;
  • 单独的合成表面可以减少重复绘制,但会占用更多内存和纹理资源。

浏览器图层与合成层示意

合成层的价值主要在于隔离更新范围和支持高效的变换、滚动或透明度动画,而不是“把所有计算都交给 GPU”。

六、如何合理使用硬件加速和合成动画?

1. 优先使用 transformopacity

对于位移、缩放、旋转、淡入淡出等动画,优先使用:

.panel {
  transition: transform 240ms ease, opacity 240ms ease;
}

.panel.is-entering {
  transform: translateY(16px);
  opacity: 0;
}

如果动画确实会改变文档流位置,transform 只是视觉移动,不会让后续内容重新排版。此时应先确认它是否符合交互和可访问性需求,不能为了性能改变布局语义。

2. 谨慎使用 will-change

will-change 是给浏览器的提示,不是“强制开启 GPU”的开关:

/* 对确实即将动画的元素短时间使用 */
.dialog.is-preparing {
  will-change: transform, opacity;
}

浏览器可能为 will-change 元素提前创建合成资源。大量元素、长期设置或给页面所有节点设置 will-change 会增加内存、栅格化和合成成本。更好的做法是:

  • 只对即将变化的少量元素使用;
  • 在动画结束后移除提示;
  • 先用 DevTools 验证是否真的改善了性能;
  • 不要把 will-change: top, left 当成避免布局的方案,它们仍可能触发布局。

3. translateZ(0) 不是通用秘籍

过去常见的 translateZ(0)translate3d(0, 0, 0) 等写法,有时会促使旧版浏览器创建合成层,但现代浏览器有自己的合成决策,结果依实现、平台和元素内容而不同。盲目使用可能造成:

  • 大量纹理占用 GPU 内存;
  • 文本和图片重新栅格化后变模糊;
  • 合成层数量过多;
  • 图层上传、合成和内存带宽成为新瓶颈。

4. Canvas、WebGL 和视频

canvas、WebGL 和视频播放可能使用硬件加速路径,但是否使用、使用哪种路径由浏览器、驱动、编码格式、跨域策略和设备能力决定。不要仅凭元素类型断言“必然由 GPU 绘制”。

七、GPU 加速的代价和常见误区

1. 合成层不是越多越好

粗略估算一个 RGBA 纹理的内存开销时,可以用下面的关系帮助建立直觉:

纹理内存 ≈ 宽度 × 高度 × 4 字节 × DPR²

真实实现还会受到图块尺寸、格式、双缓冲、缓存和压缩方式影响。一个覆盖整个移动端页面、DPR 为 3 的合成层可能比想象中占用更多内存。

2. “不重排、不重绘”不是性能保证

即使动画只改变 transformopacity,仍然可能受到以下因素影响:

  • 主线程正在执行长 JavaScript 任务;
  • 图片解码或字体加载阻塞;
  • 合成层太大或数量太多;
  • 阴影、滤镜、backdrop-filter 和混合模式成本很高;
  • 浏览器需要重新栅格化放大后的图片或文字;
  • 设备 GPU 性能和内存带宽不足。

3. 60 FPS 不是唯一目标

60 Hz 屏幕每帧预算约为 16.7ms,120 Hz 屏幕每帧预算约为 8.3ms。动画性能应根据目标设备和刷新率观察实际帧耗时、长任务、输入延迟和掉帧,而不是只背诵“达到 60 FPS”。

4. 动画要尊重用户偏好

对会移动、缩放或闪烁的动画,应提供减少动效选项:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

八、如何验证是否真的变快?

不要只看代码中有没有 will-changetranslate3d,应使用浏览器工具验证:

  1. 打开 Chrome DevTools 的 Performance,录制动画前后的主线程、Paint、Raster 和 Composite;
  2. Rendering 面板开启 Paint flashing,观察是否每帧都重新绘制大面积内容;
  3. Layers 面板检查合成层数量、尺寸和原因;
  4. 结合 FPS、长任务、内存和实际低端设备测试;
  5. 对页面级体验同时观察 LCP、INP、CLS,而不只看某个动画的帧率。

如果修改后只是让图层变多,却没有降低 Paint 或主线程工作量,就应撤销这项优化。

九、小结

  • GPU 擅长高并行图形计算,但不是所有前端任务都适合 GPU;
  • 浏览器会综合决定样式、布局、绘制、栅格化和合成路径,开发者不能直接“打开 GPU 加速”;
  • transformopacity 有机会走更轻量的合成路径,但不是绝对保证;
  • will-change 应少量、短时、经过测量后使用;
  • 合成层会消耗内存和纹理资源,层爆炸可能让性能更差;
  • 应使用 DevTools 和真实设备验证,而不是套用固定的 GPU 加速秘籍。

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS