preflower / blog

preflower's blog
0 stars 0 forks source link

[译] CSS GPU渲染 #8

Open preflower opened 4 years ago

preflower commented 4 years ago

TLDR

基本概念

什么是GPU渲染

即将动画从CPU渲染,移交给GPU做

优势

劣势

隐式合成

隐式合成就是特定场景下,部分元素会被默认提升为合成层的情况

如何触发GPU渲染

优化点


引言

本文旨在帮助您更好地理解浏览器是如何使用GPU进行渲染的,这样你就可以创建令人印象深刻的网站。

大多数人都知道现代浏览器使用GPU去渲染部分网页,特别是动画。举个例子,一个CSS动画使用transform属性会比使用left, top属性更加顺滑。但是如果你问,“如何从GPU上获得平滑动画?“大部分情况下,你会听到例如”使用transform: translateZ(0)will-change: transform

这些已经成为唤起GPU渲染(compositing - 浏览器产商的称呼)的通用属性,就像是如何使用zoom: 1在IE6中一样

但有时GPU渲染的一个顺滑的动画demo在真实环境下会运行的很慢甚至是导致浏览器崩溃。为什么会这样?如何修复这个问题?让我们走进科学。

Compositing 是如何工作的?

设置A, B两个元素,具有absolute属性和不同的z-index属性。浏览器将会从CPU绘制它们,并把生成的图像发送给GPU,后者将其展示到屏幕上

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 30px;
 top: 30px;
 z-index: 2;
}

#b {
 z-index: 1;
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

例子1 我们决定使用left属性和animation属性使A元素移动

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 10px;
 top: 10px;
 z-index: 2;
 animation: move 1s linear;
}

#b {
 left: 50px;
 top: 50px;
 z-index: 1;
}

@keyframes move {
 from { left: 30px; }
 to { left: 100px; }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

2022-09-20 15 51 06

在这种情况下,对于每个动画帧,浏览器必须重新计算元素的几何形状(即回流 - Reflow),渲染页面的新状态的图像(即重绘 - Repaint),然后再次发送给GPU在屏幕上显示。我们知道重绘是非常耗费性能的,但每个现代浏览器都足够聪明,只重新绘制页面的更改区域,而不是整个页面。虽然浏览器在大多数情况下可以很快地重新绘制,但我们的动画仍然不流畅。

在动画的每个帧中(甚至是增量地)重绘和回流整个页面听起来非常缓慢,特别是对于大型和复杂的布局。如果只绘制两个独立的图像,一个用于A元素,另一个用于没有A元素的整个页面,然后简单地相互偏移这些图像,效果会更好。换句话说,合成(Compositing)已缓存元素的图像会更快。这正是GPU的亮点所在: 它能够以亚像素的精度非常快速地合成图像,这为动画增添了一种性感的流畅感。

为了优化合成(Compositing)性能,浏览器需要动画属性确保以下几点:

有人可能会认为positionabsolute/fixed下的top, left等属性不依赖元素环境,但事实并非如此。例如:left属性设置支持百分比,其依赖于.offsetParent的大小;除此以外,em, vh这些单位也都依赖于元素环境。相反,transfomropacity是唯一满足上述条件的CSS属性。

让我们用transform代替left来制作动画:

<style>
#a, #b {
 position: absolute;
}

#a {
 left: 10px;
 top: 10px;
 z-index: 2;
 animation: move 1s linear;
}

#b {
 left: 50px;
 top: 50px;
 z-index: 1;
}

@keyframes move {
 from { transform: translateX(0); }
 to { transform: translateX(70px); }
}
</style>
<div id="#a">A</div>
<div id="#b">B</div>

这里我们显式地描述了动画:起始位置和持续时间等。这等于提前告诉浏览器这些CSS属性将会被改变,因为浏览器看到这些属性不会造成重绘和回流就会提供一个合成(Compositing)优化:绘制两个图像作为合成层(Compositing Layers),并将它们发送到GPU。

这些优化的优点是什么?

一切看起来都很简单明了,对吧?我们会遇到什么问题?让我们看看这个优化是如何工作的。

这可能会让你感到惊讶,但GPU是一个独立的计算机。没错:每个现代设备的基本组成部分实际上都是一个独立的单元,有自己的处理器、自己的内存和数据处理模型。与其他应用或游戏一样,浏览器也必须像与外部设备一样与GPU通信。

为了更好地理解这是如何工作的,可以参考AJAX。假设您想要注册一个网站访问者的数据,他们已经在一个web表单中输入。您不能直接告诉远程服务器:“嘿,从这些输入字段中获取数据和JavaScript变量并将其保存到数据库。”远程服务器无法访问用户浏览器中的内存。相反,您必须将页面中的数据收集到一个有效负载中,该有效负载具有易于解析的简单数据格式(如JSON),并将其发送到远程服务器。

在合成过程中也会发生非常类似的事情。因为GPU就像一个远程服务器,浏览器必须首先创建一个有效负载(Payload),然后将其发送到设备。当然,GPU与CPU之间并没有几千公里的距离; 就在那儿。然而,在许多情况下,远程服务器请求和响应所需的2秒时间是可以接受的,GPU数据传输额外的3到5毫秒将导致笨拙的动画。

GPU有效负载(Payload)是什么样子的?在大多数情况下,它包括图层图像,以及附加的说明,如图层的大小,偏移量,动画参数等。以下是使用GPU制作有效负载和传输数据的大致情况:

// TODO:待重新翻译 正如您所看到的,每次向元素添加神奇的transform: translateZ(0)will-change: transform属性时,都会启动完全相同的过程。 重绘(Repaint)的性能消耗非常昂贵,但在这里它甚至更慢。在大多数情况下,浏览器无法增量地重新绘制。它必须用新创建的复合图层绘制之前覆盖的区域

image

Implicit Compositing - 隐式合成

让我们回到A和B元素的例子。前面,我们动画了A元素,它位于页面上所有其他元素的顶部。这样就形成了A元素和B元素和页面背景的两个合成层。

现在,让我们把B元素变成动画

2022-09-20 17 41 30

我们遇到了一个逻辑问题。元素B应该在一个单独的合成层上,屏幕的最终页面图像应该在GPU上合成。但是A元素应该出现在元素B的顶部,并且我们没有指定关于A的任何东西来将它提升到自己的层。

记住一个重要的免责声明: 特殊的GPU合成模式不是CSS规范的一部分;这只是浏览器内部应用的优化。我们必须让A按照z-index的顺序出现在B的上面。浏览器会做什么?

你猜对了! 它会强制为元素a创建一个新的合成层,并添加另一个重绘

2022-09-20 17 44 11

这被称为隐式合成: 一个或多个非合成元素按照堆叠顺序出现在合成元素之上,被提升到合成层,即被绘制成独立的图像,然后发送到GPU

我们发现隐性作曲的频率比你想象的要高得多。浏览器将一个元素提升到合成层的原因有很多,只是其中的一些原因:

似乎GPU动画的主要问题是意料之外的重绘。但事实并非如此。更大的问题是...

Memory Consumption - 内存消耗

另一个温和的提醒是,GPU是一个独立的计算机:它不仅需要发送渲染层图像到GPU,而且需要存储它们,以便以后在动画中重用。

一个复合层需要多少内存?让我们举一个简单的例子。试着猜测一个320x240像素的矩形,填充纯色#FF0000,需要多少内存。

image

一个典型的网页开发人员会想,嗯,这是一个纯色图像。我将它保存为PNG格式,并检查其大小。它应该小于1 KB。他们完全正确:这张图片PNG格式的大小是104字节。

计算公式: bytes = width(px)xheight(px)x3(RGB) 若存在透明度,则为bytes = width(px)xheight(px)x4(RGBa),而浏览器总是将合成图层绘制为RGBa图像。似乎没有有效的方法来确定一个元素是否包含透明区域。

优势和劣势

优势

劣势

优化点

须知

本文只做自身学习使用翻译,拒绝转载,拒绝商业化行为

原文:https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/

其他参考: