Graphics2D的呈现过程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Graphics2D的呈现过程相关的知识,希望对你有一定的参考价值。
参考技术A呈现过程可以分为四个阶段,这四个阶段由 Graphics2D 呈现属性控制。呈现器可以优化这些步骤,方法是通过缓存结果以用于未来调用、通过将多个虚拟步骤合成一个操作,或者通过将多种属性识别为共用的简单情况(可通过修改操作的其他部分来消除各种属性间的差别)。 呈现过程中的步骤有:
确定呈现内容。 将呈现操作限制在当前的 Clip。 Clip 由用户空间中的 Shape 指定,并由该程序使用 Graphics 和 Graphics2D 的各种 clip 操作方法进行控制。此用户剪贴区 由当前的 Transform 转换到设备空间中,并且与设备剪贴区 组合,后者是通过窗口可见性和设备范围定义的。用户剪贴区和设备剪贴区的组合定义了复合剪贴区,它确定了最终的剪贴区域。用户剪贴区不能由呈现系统修改,以反映得到的复合剪贴区。 确定呈现的颜色。 使用 Graphics2D 上下文中当前的 Composite 属性将颜色应用于目标绘图面。
三种类型的呈现操作,以及各自特殊呈现过程的细节如下: Shape 操作 如果该操作为 draw(Shape) 操作,则 Graphics2D 上下文中当前 Stroke 属性上的 createStrokedShape 方法将用于构造包含指定 Shape 轮廓的新 Shape 对象。 使用 Graphics2D 上下文中的当前 Transform 将 Shape 从用户空间转换到设备空间。 Shape 的轮廓是通过使用 Shape 的 getPathIterator 方法提取的,该方法返回一个沿着 Shape 边界迭代得到的 PathIterator 对象。 如果 Graphics2D 对象无法处理 PathIterator 对象返回的曲线段,则可以调用 Shape 的 getPathIterator 替代方法,该方法可使 Shape 变得平滑。 对于 PaintContext,需要 Graphics2D 上下文中的当前 Paint,它指定了在设备空间中呈现的颜色。 文本操作 下面的步骤用于确定呈现指定 String 所需的字形集: 如果参数是一个 String,则要求 Graphics2D 上下文中的当前 Font 将 String 中的 Unicode 字符转换为一个字形集,以表现 font 实现的基本布局和成形算法。 如果参数是一个 AttributedCharacterIterator,则要求迭代器使用其内含的字体属性将其自身转换为 TextLayout。TextLayout 实现了更为复杂的字形布局算法,用于为不同书写方向的多种字体自动执行 Unicode 双方向布局调整。 如果参数是一个 GlyphVector,则 GlyphVector 对象已经包含了特定于字体的合适字形代码和每个字形位置的显式坐标。 查询当前的 Font 以获取指定字形的轮廓。这些轮廓被视为用户空间中相对于步骤 1 中确定的每个字形位置的形状。 字符轮廓按上面 Shape 操作下指示的方式填充。 为 PaintContext 查询当前的 Paint,Paint 指定了设备空间中呈现的颜色。 Image 操作 感兴趣区域由源 Image 的边框定义。此边框在图像空间中指定,该空间即 Image 对象的本地坐标系统。 如果 AffineTransform 被传递到 drawImage(Image, AffineTransform, ImageObserver),则使用 AffineTransform 将边框从图像空间转换到用户空间。如果未提供 AffineTransform,则认为边框已存在于用户空间中。 使用当前的 Transform 将 Image 的边框从用户空间转换到设备空间。注意,转换边框的结果不一定会得到设备空间中的矩形区域。 Image 对象确定要呈现的颜色,并根据由当前 Transform 和可选图像转换所指定的源到目标坐标的映射关系进行采样。
灵魂拷问第5篇:说一说从输入URL到页面呈现发生了什么?——渲染过程篇
上一节介绍了浏览器解析
的过程,其中包含构建DOM
、样式计算
和构建布局树
。
接下来就来拆解下一个过程——渲染
。分为以下几个步骤:
- 建立
图层树
(Layer Tree
) - 生成
绘制列表
- 生成
图块
并栅格化
- 显示器显示内容
一、建图层树
如果你觉得现在DOM节点
也有了,样式和位置信息也都有了,可以开始绘制页面了,那你就错了。
因为你考虑掉了另外一些复杂的场景,比如3D动画如何呈现出变换效果,当元素含有层叠上下文时如何控制显示和隐藏等等。
为了解决如上所述的问题,浏览器在构建完布局树
之后,还会对特定的节点进行分层,构建一棵图层树
(Layer Tree
)。
那这棵图层树是根据什么来构建的呢?
一般情况下,节点的图层会默认属于父亲节点的图层(这些图层也称为合成层)。那什么时候会提升为一个单独的合成层呢?
有两种情况需要分别讨论,一种是显式合成,一种是隐式合成。
显式合成
下面是显式合成
的情况:
一、 拥有层叠上下文的节点。
层叠上下文也基本上是有一些特定的CSS属性创建的,一般有以下情况:
- HTML根元素本身就具有层叠上下文。
- 普通元素设置position不为static并且设置了z-index属性,会产生层叠上下文。
- 元素的 opacity 值不是 1
- 元素的 transform 值不是 none
- 元素的 filter 值不是 none
- 元素的 isolation 值是isolate
- will-change指定的属性值为上面任意一个。(will-change的作用后面会详细介绍)
二、需要剪裁的地方。
比如一个div,你只给他设置 100 * 100 像素的大小,而你在里面放了非常多的文字,那么超出的文字部分就需要被剪裁。当然如果出现了滚动条,那么滚动条会被单独提升为一个图层。
隐式合成
接下来是隐式合成
,简单来说就是层叠等级低
的节点被提升为单独的图层之后,那么所有层叠等级比它高
的节点都会成为一个单独的图层。
这个隐式合成其实隐藏着巨大的风险,如果在一个大型应用中,当一个z-index
比较低的元素被提升为单独图层之后,层叠在它上面的的元素统统都会被提升为单独的图层,可能会增加上千个图层,大大增加内存的压力,甚至直接让页面崩溃。这就是层爆炸的原理。这里有一个具体的例子,点击打开。
值得注意的是,当需要repaint
时,只需要repaint
本身,而不会影响到其他的层。
二、生成绘制列表
接下来渲染引擎会将图层的绘制拆分成一个个绘制指令,比如先画背景、再描绘边框......然后将这些指令按顺序组合成一个待绘制列表,相当于给后面的绘制操作做了一波计划。
这里我以百度首页为例,大家可以在 Chrome 开发者工具中在设置栏中展开 more tools
, 然后选择Layers
面板,就能看到下面的绘制列表:
三、生成图块和生成位图
现在开始绘制操作,实际上在渲染进程中绘制操作是由专门的线程来完成的,这个线程叫合成线程。
绘制列表准备好了之后,渲染进程的主线程会给合成线程
发送commit
消息,把绘制列表提交给合成线程。接下来就是合成线程一展宏图的时候啦。
首先,考虑到视口就这么大,当页面非常大的时候,要滑很长时间才能滑到底,如果要一口气全部绘制出来是相当浪费性能的。因此,合成线程要做的第一件事情就是将图层分块。这些块的大小一般不会特别大,通常是 256 * 256 或者 512 * 512 这个规格。这样可以大大加速页面的首屏展示。
因为后面图块数据要进入 GPU 内存,考虑到浏览器内存上传到 GPU 内存的操作比较慢,即使是绘制一部分图块,也可能会耗费大量时间。针对这个问题,Chrome 采用了一个策略: 在首次合成图块时只采用一个低分辨率的图片,这样首屏展示的时候只是展示出低分辨率的图片,这个时候继续进行合成操作,当正常的图块内容绘制完毕后,会将当前低分辨率的图块内容替换。这也是 Chrome 底层优化首屏加载速度的一个手段。
顺便提醒一点,渲染进程中专门维护了一个栅格化线程池,专门负责把图块转换为位图数据。
然后合成线程会选择视口附近的图块,把它交给栅格化线程池生成位图。
生成位图的过程实际上都会使用 GPU 进行加速,生成的位图最后发送给合成线程
。
四、显示器显示内容
栅格化操作完成后,合成线程会生成一个绘制命令,即"DrawQuad",并发送给浏览器进程。
浏览器进程中的viz组件
接收到这个命令,根据这个命令,把页面内容绘制到内存,也就是生成了页面,然后把这部分内存发送给显卡。为什么发给显卡呢?我想有必要先聊一聊显示器显示图像的原理。
无论是 PC 显示器还是手机屏幕,都有一个固定的刷新频率,一般是 60 HZ,即 60 帧,也就是一秒更新 60 张图片,一张图片停留的时间约为 16.7 ms。而每次更新的图片都来自显卡的前缓冲区。而显卡接收到浏览器进程传来的页面后,会合成相应的图像,并将图像保存到后缓冲区,然后系统自动将前缓冲区
和后缓冲区
对换位置,如此循环更新。
看到这里你也就是明白,当某个动画大量占用内存的时候,浏览器生成图像的时候会变慢,图像传送给显卡就会不及时,而显示器还是以不变的频率刷新,因此会出现卡顿,也就是明显的掉帧现象。
总结
到这里,我们算是把整个过程给走通了,现在重新来梳理一下页面渲染的流程。
以上是关于Graphics2D的呈现过程的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Graphics2D.setStoke() 不适用于 Graphics2D.drawString?