深入理解 CPU 和 GPU 渲染

在图片渲染的过程中,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 图像渲染的场景会变得更复杂,它涉及模型、灯光、纹理等各环节,这里引用网上他人的一张图,3D 渲染管线过程如下:

(图片来源: 知乎-灵知子)

GPU 和 CPU 渲染的区别

从芯片设计原理上来分析,中央处理单元(CPU) 设计上是通用计算核心,适合复杂逻辑计算和任务调度 ,而图形处理单元(GPU)设计上专注于并行计算,非常适合处理大量相同类型的操作,如像素着色和矩阵计算。

它们原本的职责功能就不一样,但都能用于处理图形的渲染工作,下面是两者之间的对比

特性 GPU 渲染 CPU 渲染
并行计算 超强(支持数千个并行线程) 较弱(线程数量有限)
适合任务 大规模数据、重复任务 复杂逻辑、动态任务
速度 高速(尤其是大规模任务) 较慢(受限于核心数量)
实时性 优秀(支持高帧率) 较差(耗时较长)
计算精度 较低(精度适中) 较高(支持高精度)
硬件需求 专用 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 处理。

css 复制代码
.element {
    transform: translateZ(0); /* 强制使用 GPU */
}
2. 使用 will-change

will-change 提前告诉浏览器某个属性即将发生变化,优化渲染路径。

css 复制代码
.element { 
    will-change: transform, opacity; /* 预告浏览器优化 */ 
}
3. 使用 opacity

opacity 改变元素透明度时通常会触发 GPU 加速。

css 复制代码
.element { 
    opacity: 0.5; /* 渐变效果通过 GPU 渲染 */
}
4. 过渡动画中使用opacity或transform

CSS 动画或过渡中使用transform、opacity、filter会触发 GPU 加速。

css 复制代码
@keyframes slideIn { 
    from { 
        transform: translate3d(-100%, 0, 0); /* GPU 渲染 */ 
    } 
    to { 
        transform: translate3d(0, 0, 0); /* GPU 渲染 */ 
    } 
} 

.element { 
    animation: slideIn 1s ease-out; 
}

JavaScript 启用 GPU 加速

其实 JavaScript 本身并不直接与 GPU 通信,但可以通过操作 DOM 元素触发 GPU 渲染路径。从上面我们可以知道,CSS 是如何让 GPU 加速的,因此,在 JavaScript 中,通过操作 CSS 属性也可以来启用 GPU 加速。

1. 操作 CSS 3D 相关的属性

改变 CSS 属性:如 transformopacitywill-change 等,其实本质上是 css 启动 GPU 的另一种形式,这里不再赘述。

javascript 复制代码
const element = document.querySelector('.box');

// 设置 transform,启用 GPU 加速
element.style.transform = 'translate3d(100px, 0, 0)';
2. 动画使用 requestAnimationFrame

当你进行动画时,使用 requestAnimationFrame 可以提供平滑的渲染并有效利用 GPU。

javascript 复制代码
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 直接交互。

javascript 复制代码
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 属性可以告知浏览器即将对某一元素进行变化,这样浏览器可以提前创建合成层。

css 复制代码
.element {
  will-change: transform, opacity; /* 提升元素的合成层 */
}
2)使用 CSS 转换和过渡
  • 应用 3D 转换 : 使用 translateZ(0)translate3d(0, 0, 0),可以强制将元素推入合成层。
css 复制代码
.element {
  transform: translateZ(0); /* 强制创建合成层 */
}
  • 过渡动画 : 在进行动画时,使用 transformopacity 等属性,这些属性的操作会尽量在合成层中处理。
css 复制代码
.box {
  transition: transform 0.3s, opacity 0.3s;
}
3)避免过大的合成层

虽然合成层能够提高性能,但过多或过大的合成层也会导致性能问题。不必要的合成层会增加 GPU 的内存使用,因此,在设计布局时,应合理安排需要提升的元素。

4)使用缓存的图像

图像常常在合成层中创建,确保图像的尺寸合适并尽量减少像素大小,以提升性能。例如,可以使用 image-rendering 属性来提高图像的渲染效果。

css 复制代码
.image {
  image-rendering: crisp-edges; /* 优化图像渲染 */
}
5)简化图层结构

尽可能减少 DOM 层级和图层数量。复杂的布局可能会阻碍浏览器合成层的使用,简化 DOM 结构可以提高性能。

4. 隐式合成层

这个概念也许你听过,但并可能不清晰。隐式合成层,是相对显式合成层而言的,上面所描述的其实都是显式合成层,而所谓隐式合成层,就是指那些图层原本不是合成层,但浏览器基于性能优化自动创建的合成层。

什么意思呢?举个简单的场景来讲,那种 z-index 在合成层之上的图层。这种图层即使不是合成层,也会被转换成为合成层。

css 复制代码
.div1 {
  transform: translateZ(0);
  z-index: 100;
}

.div2 {
  font-size: 12px;
  z-index: 1000;
}

上面例子当中,div1 是合成层,但 div2 为普通图层,但由于它位于 div1 之上,出于性能优化的目的,浏览器最终会将它提升为合成层,这种就是隐式合成层。

因此,我们在写 CSS 的时候,一定要注意不要过度地滥用z-index属性,认为值越大越好,过多地使用这种图层有时候会适得其反。

当然这只是隐式合成层其中的一种方式,还有动画与过渡、滚动优化、混合模式、遮罩、3D 变换等场景都会涉及到隐式合成层。

它的核心目标是减少重排和重绘的范围,提升渲染性能。

通过上面的分析,相信你对图形的渲染机制有了进一步的认识,码字不易,也欢迎大家一键三连和评论~

相关推荐
GISer_Jing1 分钟前
从0开始分享一个React项目:React-ant-admin
前端·react.js·前端框架
川石教育5 分钟前
Vue前端开发子组件向父组件传参
前端·vue.js·前端开发·vue前端开发·vue组件传参
GISer_Jing1 小时前
Vue前端进阶面试题目(二)
前端·vue.js·面试
乐闻x1 小时前
Pinia 实战教程:构建高效的 Vue 3 状态管理系统
前端·javascript·vue.js
weixin_431449681 小时前
web组态软件
前端·物联网·低代码·编辑器·组态
橘子味小白菜1 小时前
el-table的树形结构后端返回的id没有唯一键怎么办
前端·vue.js
前端Hardy2 小时前
HTML&CSS:比赛记分卡
前端·javascript·css·3d·html
疯狂的沙粒2 小时前
Vue项目开发 element-UI 前端实现 1到10排列选择的按钮
前端·vue.js·ui
刺客-Andy2 小时前
React第六节 组件属性prop的propTypes类型使用介绍
前端·javascript·react.js·typescript
Mr.Liu63 小时前
小程序24-滚动效果:scroll-view组件详解
前端·微信小程序·小程序