在图片渲染的过程中,CPU 和 GPU 扮演了截然不同但又紧密协作的角色,它们各自其实都可以实现图片的渲染,那你有没有想过它们之间的差异究竟有何不同?下面就让我们一起揭开图片渲染的工作原理,探索 CPU 和 GPU 渲染的奥秘,以及它们在性能优化中的实际应用。
一、图片渲染过程
首先,我们先来思考一下一张图片究竟是如何渲染在屏幕上的呢?
浏览器图片渲染是一个从文件加载到最终绘制的复杂过程,涉及网络传输、图片解码、位图生成以及 GPU/CPU 渲染等多个环节。其渲染过程如下:
二、CPU 和 GPU 渲染
或许有人会提出疑问,解码后的图片需要绘制到屏幕上,浏览器会通过 GPU 和渲染管线进行处理,上图为什么没有体现 GPU 渲染的环节?
其实在浏览器处理图片渲染时,不一定都会用到 GPU 渲染,使用 CPU 也一样可以渲染,只不过使用 CPU 渲染容易出现性能瓶颈而已。
在计算机图形学中,图形渲染可以通过 CPU(中央处理器)或 GPU(图形处理单元)来完成。CPU 和 GPU 在处理图像工作的过程中有不同的工作模式和性能特征,下面让我们一起来看下这两种渲染方式的过程。
CPU 渲染图片
-
CPU 首先会接收准备好的图形数据,包括顶点坐标、颜色、纹理等信息。
-
CPU 逐个处理这些顶点的坐标,进行变换(比如世界坐标到屏幕坐标的变换),通常涉及到矩阵multiplication 和其他数学运算。
-
CPU 会将这些顶点转换为实际在屏幕上显示的像素。这个过程可能需要进行三角形的拆分、插值计算(确定颜色、深度、法线等属性),这个过程也叫光栅化,复杂的图片会涉及到很大的计算,这是瓶颈之一。
-
在渲染过程中,CPU 计算光照(如漫反射、镜面反射)和阴影,然后将这些信息应用到像素上。
-
在图像渲染的最后一步,CPU 将所有的像素信息合成一个图像。对于复杂场景,可能还需要通过多次取样(如抗锯齿)来提高图像质量,这个过程会涉及大量缓存区数据的读写操作,这是渲染瓶颈之二。
-
最后,将渲染好的图像输出到显示设备。
接下来让我们看一看 GPU 的渲染过程。
GPU 渲染图片
-
CPU 将图形数据(顶点、纹理等)上传到 GPU 内存中。GPU 预期这些数据在渲染过程中会被多次使用。
-
渲染过程中使用着色器(GPU 编程中独有的)来处理顶点和像素。着色器是运行在 GPU 上的程序,包括顶点着色器、片段着色器等。
-
GPU 的强大之处在于它可以同时处理大量的顶点和像素。不同的块可以同时并行地执行相同的代码,因此可以在短时间内处理大量的数据。其并行计算处理能力,这是与 CPU 渲染最大的不同之处。
-
GPU 会将处理后的顶点数据光栅化,将其转换为实际在屏幕上显示的像素,这一步GPU尤其高效,单位时间内处理海量图像数据,实现高吞吐量。
-
GPU 在光栅化后立即开始处理光照、纹理映射和其他特效(如反射、阴影等)。着色器允许高度的灵活性和复杂性。其充分利用计算机的硬件加速,优化了常见图像操作,如纹理映射、光栅化、抗锯齿等。
-
GPU 将渲染好的图像合成并输出到缓冲区,最后将其显示在屏幕上~
上述是 2D 图像的渲染过程,而针对 3D 图像渲染的场景又会变得更复杂,因为它涉及模型、灯光、纹理等各环节,这里引用他人的一张图,原理其实大致都差不多,但会有更多的阶段细分,过程如下:
(图片来源: 知乎-灵知子)
GPU 和 CPU 渲染的区别
从芯片设计原理上来分析,中央处理单元(CPU) 设计上是通用计算核心,适合复杂逻辑计算和任务调度,而图形处理单元(GPU)设计上专注于并行计算,非常适合处理大量相同类型的操作,如像素着色和矩阵计算等。
它们原本的职责功能就不一样,虽然都能用于处理图形的渲染工作,但两者还是有一定的差别,对比如下:
特性 | GPU 渲染 | CPU 渲染 |
---|---|---|
并行计算 | 超强(支持数千个并行线程) | 较弱(线程数量有限) |
适合任务 | 大规模数据、重复任务 | 复杂逻辑、动态任务 |
速度 | 高速(尤其是大规模任务) | 较慢(受限于核心数量) |
实时性 | 优秀(支持高帧率) | 较差(耗时较长) |
计算精度 | 较低(精度适中) | 较高(支持高精度) |
CPU 则胜在灵活性和精确性,适合逻辑复杂的离线渲染任务。
GPU 优势在于速度和并行处理,适合需要实时渲染或大量重复计算的场景。
实际业务当中,CPU 具有灵活性,GPU 具有高效计算能力,两者是相辅相成的关系,配合利用得当,才能实现复杂图形的最佳渲染效果。
三、GPU加速
既然 GPU 原本就是因图形处理而生,那我们日常中该如何将它应用到我们的图形渲染优化当中呢?这其实也是很多面试官经常提到的问题,如何启用 GPU 加速?
其实一些现代图像格式(比如 WebP、SVG 等)可能会在浏览器内部使用 GPU 加速解析。尽管这些图像本身的加载仍由 CPU 负责,后续的绘制操作可能会通过 GPU 提升性能。
从点到面,从图片渲染到图层渲染,我们在实际性能优化场景中如何利用硬件加速(GPU 加速)?
在前端开发中,启用 GPU 加速的方式其实有两种,一种是利用 CSS,另一种则通过使用 JavaScript,利用得当,两者都可以显著提高动画和视觉效果的性能。下面分别介绍如何通过 CSS 和 JavaScript 启用 GPU 加速。
CSS 启用 GPU 加速
GPU 加速的核心是将特定的渲染任务交给 GPU 处理,而不是依赖 CPU。以下是通过 CSS 启用 GPU 加速的一些方法:
1. 使用 translateZ(0)
或 translate3d(0, 0, 0)
CSS transform
是启用 GPU 加速的最常用方式。通过定义 2D 或 3D 转换,浏览器会创建一个独立的合成层,并将该层交给 GPU 处理。
.div {
transform: translateZ(0); /* 强制使用 GPU */
}
2. 使用 will-change
will-change
提前告诉浏览器某个属性即将发生变化,优化渲染路径。
.div {
will-change: transform, opacity; /* 预告浏览器优化 */
}
3. 使用 opacity
opacity
改变元素透明度时通常会触发 GPU 加速。
.div {
opacity: 0.5; /* 渐变效果通过 GPU 渲染 */
}
4. 过渡动画中使用opacity或transform
CSS 动画或过渡中使用transform、opacity、filter会触发 GPU 加速。
@keyframes slideIn {
from {
transform: translate3d(-100%, 0, 0); /* GPU 渲染 */
}
to {
transform: translate3d(0, 0, 0); /* GPU 渲染 */
}
}
.div {
animation: slideIn 1s ease-out;
}
JavaScript 启用 GPU 加速
其实 JavaScript 本身并不直接与 GPU 通信,但可以通过操作 DOM 元素触发 GPU 渲染路径。从上面我们可以知道,CSS 是如何让 GPU 加速的,因此,在 JavaScript 中,通过操作 CSS 属性也可以来启用 GPU 加速。
1. 操作 CSS 3D 相关的属性
改变 CSS 属性:如 transform
、opacity
、will-change
等,其实本质上是 css 启动 GPU 的另一种形式,这里不再赘述。
const box = document.querySelector('.box');
// 设置 transform,启用 GPU 加速
box.style.transform = 'translate3d(100px, 0, 0)';
2. 动画使用 requestAnimationFrame
当你进行动画时,使用 requestAnimationFrame
可以提供平滑的渲染并有效利用 GPU。
const box = document.querySelector('.animationBox');
let start = null;
function animate(timestamp) {
if (!start) start = timestamp;
const progress = timestamp - start;
box.style.transform = `translateY(${Math.min(progress / 2, 200)}px)`; // 使用 transform
if (progress < 400) {
requestAnimationFrame(animate);
}
}
// 触发动画
requestAnimationFrame(animate);
3. 使用 WebGL
如果需要更高级的 GPU 操作,可以通过 WebGL 与 GPU 直接交互。
const canvas = document.querySelector('canvas');
const wgl = canvas.getContext('webgl');
// 设置清屏颜色
wgl.clearColor(0.0, 0.0, 0.0, 1.0);
wgl.clear(gl.COLOR_BUFFER_BIT);
以上就是两种启动 GPU 加速的一些具体方法,通过合理使用 CSS 和 JavaScript,开发者可以有效启用 GPU 加速,从而提升页面的动画性能和用户体验。
但在实际业务中,启动 GPU 加速与另外一个概念息息相关,那就是合成层。从上面图像在浏览器上的渲染原理,我们就可以看出,光栅化后会有一个合成图像的过程,因此合成层的提升也至关重要。
四、合成层的提升
1. 什么是合成层?
当浏览器渲染网页时,通常会将页面拆分成多个图层,这些图层分别负责不同的部分(例如背景、文本、图像等)。合成层允许浏览器在 GPU 上渲染这些层,并在最终一步将它们合成到屏幕上。
举个不太恰当但形象的例子,接触过 Photoshop(PS)的都知道,图像的最终形态是由右侧小窗口一张张图层叠加而成,而合成层的概念就与 PS 合成最终图像形态的过程类似,而直接影响合成层的就是最上面那几张图层。
2. 合成层的作用
合成层(Compositing Layers),尤其是在处理动画和复杂布局时,合成层的提升有助于确保这些操作在 GPU 上进行,从而减少 CPU 的负担。这种方法相比传统的渲染方式可以减少重绘(Repaint)和重排(Reflow)的次数,提高渲染效率。
3. 如何提升合成层?
要提升合成层的使用,这个过程与启用 GPU 加速操作非常类似,因此,您可以采用以下几种策略:
1)使用 will-change
属性
使用 CSS 的 will-change
属性可以告知浏览器即将对某一元素进行变化,这样浏览器可以提前创建合成层。
.div {
will-change: transform, opacity; /* 提升元素的合成层 */
}
2)使用 CSS 转换和过渡
- 应用 3D 转换: 使用
translateZ(0)
或translate3d(0, 0, 0)
,可以强制将元素推入合成层。
.div {
transform: translateZ(0); /* 强制创建合成层 */
}
- 过渡动画: 在进行动画时,使用
transform
和opacity
等属性,这些属性的操作会尽量在合成层中处理。
.box {
transition: transform 0.3s, opacity 0.3s;
}
3)避免过大的合成层
虽然合成层能够提高性能,但过多或过大的合成层也会导致性能问题。不必要的合成层会增加 GPU 的内存使用,因此,在设计布局时,应合理安排需要提升的元素。
4)使用缓存的图像
图像常常在合成层中创建,确保图像的尺寸合适并尽量减少像素大小,以提升性能。例如,可以使用 image-rendering
属性来提高图像的渲染效果。
.img {
image-rendering: crisp-edges; /* 优化图像渲染 */
}
5)简化图层结构
尽可能减少 DOM 层级和图层数量。复杂的布局可能会阻碍浏览器合成层的使用,简化 DOM 结构可以提高性能。
4. 隐式合成层
这个概念也许你听过,但并可能不清晰。隐式合成层,是相对显式合成层而言的,上面所描述的其实都是显式合成层,而所谓隐式合成层,就是指那些图层原本不是合成层,但浏览器基于性能优化自动创建的合成层。
什么意思呢?举个简单的场景来讲,那种 z-index 在合成层之上的图层。这种图层即使不是合成层,也会被转换成为合成层。
.div1 {
transform: translateZ(0);
z-index: 100;
}
.div2 {
font-size: 12px;
z-index: 1000;
}
上面例子当中,div1 是合成层,但 div2 为普通图层,但由于它位于 div1 之上,出于性能优化的目的,浏览器最终会将它提升为合成层,这种就是隐式合成层。
因此,我们在写 CSS 的时候,一定要注意不要过度地滥用z-index属性,认为值越大越好,过多地使用这种图层有时候会适得其反。
当然这只是隐式合成层其中的一种方式,还有动画与过渡、滚动优化、混合模式、遮罩、3D 变换等场景都会涉及到隐式合成层。
它的核心目标是减少重排和重绘的范围,提升渲染性能。
通过上面的分析,相信你对图形的渲染机制有了进一步的认识,码字不易,也欢迎大家一键三连和评论~