Skia深入分析9——延迟渲染和显示列表

简介: 概念Android的硬件加速,是先将绘制命令存储起来,然后回放,作为软件绘制的引擎Skia中同样有这样的机制。在Android 4.4的版本中又加入了延迟渲染的Canvas,它相当于默认使用显示列表的Canvas。 先得到显示列表,再进行渲染,便有机会基于绘制API的整体情况做优化调度。比如使用GPU加速,裁剪过度绘制等。从原理上看,很可能在这一层级做比较大的效率提升,

概念

Android的硬件加速,是先将绘制命令存储起来,然后回放,作为软件绘制的引擎Skia中同样有这样的机制。在Android 4.4的版本中又加入了延迟渲染的Canvas,它相当于默认使用显示列表的Canvas。
先得到显示列表,再进行渲染,便有机会基于绘制API的整体情况做优化调度。比如使用GPU加速,裁剪过度绘制等。从原理上看,很可能在这一层级做比较大的效率提升,不过,由于Android既定的渲染框架限制,尽管Google在这方面做的东西很多,生效场景很少,收益也很有限。

显示列表——SkPicture

用法

SkPicture的用法如下:

const int w = 720;
const int h = 1280;
SkPictureRecorder recoder;
SkCanvas* displayCanvas =recoder.beginRecording(w, h, NULL, 0);//这里得到一个专门用来记录的SkCanvas
/*调用SkCanvas的API,但起的是记录命令的作用*/
displayCanvas->drawRect(...);
displayCanvas->drawSprite(...);
displayCanvas->clipRect(...);
displayCanvas->drawText(...);
/*终止绘制API的调用,得到SkPicture*/
SkPicture* picture = recoder.endRecording();

SkBitmap dst;
dst.allocN32Pixels(w, h);
SkCanvas canvas(dst);
c.drawPicture(picture);//用 picture->draw(&canvas)也可以,但最好用前面一种方法
/*此时内容已经在 dst 上面了*/
/*也可以用GPU的方式创建Canvas渲染,怎么用可参考上一篇,*/
picture->unref();//释放 picture

显示列表的记录

记录方案

我们先看一下beginRecording函数:

SkCanvas* SkPictureRecorder::beginRecording(int width, int height,
                                            SkBBHFactory* bbhFactory /* = NULL */,
                                            uint32_t recordFlags /* = 0 */) {
    this->reset();  // terminate any prior recording(s)
    fWidth = width;
    fHeight = height;

    const SkISize size = SkISize::Make(width, height);

    if (NULL != bbhFactory) {
        SkAutoTUnref<SkBBoxHierarchy> tree((*bbhFactory)(width, height));
        SkASSERT(NULL != tree);
        fPictureRecord = SkNEW_ARGS(SkBBoxHierarchyRecord, (size, recordFlags, tree.get()));
    } else {
        fPictureRecord = SkNEW_ARGS(SkPictureRecord, (size, recordFlags));
    }

    fPictureRecord->beginRecording();
    return this->getRecordingCanvas();
}

如上述代码所看到的,主要有两种记录方法,一种是SkBBoxHierarchyRecord,另一种是SkPictureRecord。
SkBBoxHierarchyRecord:以 R-Tree 算法组织绘制任务,这种方式需要计算绘制区域的包围盒,在记录文本绘制命令时这个计算还是相当耗时的,采用R-Tree组织的好处是绘制指定区域时可以快速找出相关的绘制任务。
SkPictureRecord:顺序记录方式,不做处理

任务记录的一些注意点

  1. 图片存储时需要判断该图片是否是可变的(immutable),若不是可变的,需要复制图片到缓存。
  2. SkPaint的Shader及各种特效只存储指针/引用。需要应用自行保证不做更改。
  3. SkPath也是复制了整个SkPath及SkPathRef中所有点到缓存,注意到需要记录和恢复的比较复杂的类,需要实现 writeToMemory 和 readFromMemory 两个方法
  4. 使用SkWriter32写入,在结束记录后将其内容全部复制到 SkPicture

回放过程

Created with Raphaël 2.1.2SkCanvas::drawPictureGPUDeviceEXPERIMENTAL_drawPictureSkPicture::drawSkPicturePlayBack::drawyesno

鉴于GPU绘图时还需要把之前存储在缓存区的bitmap、path等上传为纹理或vbo,直接以纹理/vbo建立缓存机制效率更高,所以有EXPERIMENTAL_drawPicture一条分支,不过目前看来还是实验阶段。
具体如何回放的看src/core/SkPicturePlayBack.cpp中SkPicturePlayBack::draw函数即可,不详述。

延迟渲染——SkDeferredCanvas

Google的注释很清楚地解释了这个类的作用。这个类的用法和原始的SkCanvas基本一致。只是设置了延迟属性后,需要在使用绘图结果前flush一下。

/** \class SkDeferredCanvas
    Subclass of SkCanvas that encapsulates an SkPicture or SkGPipe for deferred
    drawing. The main difference between this class and SkPictureRecord (the
    canvas provided by SkPicture) is that this is a full drop-in replacement
    for SkCanvas, while SkPictureRecord only supports draw operations.
    SkDeferredCanvas will transparently trigger the flushing of deferred
    draw operations when an attempt is made to access the pixel data.
*/

用法如下:

SkSurface* surface =  SkSurface::NewRasterPMColor(720, 1280);
SkDeferredCanvas* canvas = SkDeferredCanvas::Create(surface);
canvas->clear(0x0);
canavs->setDeferredDrawing(true);
/*......*/
canvas->drawRect(...);
canvas->drawText(...);
/*......*/
canvas->flush();//需要这一步来保持绘制完成
canvas->unref();
surface->unref();

另外可以为SkDeferredCanvas设置一个监听器NotificationClient:
class NotificationClient {
public:
virtual ~NotificationClient() {}
virtual void prepareForDraw() {}//准备开始渲染时调用
virtual void storageAllocatedForRecordingChanged(
size_t /newAllocatedStorage/) {}
virtual void flushedDrawCommands() {}//当前绘制任务清空时调用,可以是拿去编码/显示/后处理等
virtual void skippedPendingDrawCommands() {}//当前绘制任务被取消掉时调用,可以用来清除额外的资源
};

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
目录
相关文章
|
Python
python文件的打开、读取、关闭、写入与二进制文件的读写操作
python文件的打开、读取、关闭、写入与二进制文件的读写操作
2892 0
python文件的打开、读取、关闭、写入与二进制文件的读写操作
|
存储 传感器 编解码
Android OpenGL 渲染图像读取哪家强
glReadPixels 是 OpenGL ES 的 API ,OpenGL ES 2.0 和 3.0 均支持。 使用非常方便,下面一行代码即可搞定,但是效率也是最低的。
2068 0
Android OpenGL 渲染图像读取哪家强
|
Ubuntu 安全 关系型数据库
MariaDB二进制包安装
本教程详细介绍了在多种Linux发行版上二进制安装MariaDB的步骤,包括创建专用用户、下载并解压程序文件、配置环境变量与服务文件、初始化数据库、设置安全策略等内容,并提供了一键安装脚本,适用于Rocky、CentOS、Ubuntu、Debian等多个系统,帮助用户快速完成MariaDB的部署与初始化配置。
1180 2
MariaDB二进制包安装
|
传感器 安全 前端开发
如何开发一套EHS健康安全环境管理系统中的风险管理板块?(附架构图+流程图+代码参考)
本文详解企业EHS(健康·安全·环境)系统中的风险管控板块,强调其核心在于构建“识别—评估—巡检—治理—验证”的闭环流程,将风险数据可视化并转化为可落地的行动指引。内容涵盖风险管控的意义、功能边界、系统架构、LEC评估方法、巡检流程、看板设计、开发技巧、落地建议、实现效果及代码参考,帮助技术团队和EHS负责人快速掌握系统搭建要点,提升企业安全管理水平。
|
开发者
10 公众号开发 - 关注/取消关注事件
10 公众号开发 - 关注/取消关注事件
396 0
|
存储 人工智能 前端开发
【AI系统】LLVM IR 基本概念
本文深入探讨了LLVM的IR(中间表示)概念,解释了其在编译器工作原理中的重要性及应用方式。LLVM IR作为一种适中抽象级别的表示形式,能有效捕捉源代码信息,支持编译器的灵活操作与优化。文章进一步分析了LLVM IR的不同表现形式,包括内存中的编译中间语言、硬盘上的二进制格式和人类可读的文本格式,以及通过具体示例展示了如何使用Clang将C语言程序编译为LLVM IR。此外,还详细解析了LLVM IR的基本语法、条件语句、循环结构和指针操作等内容。
722 3
|
Web App开发 前端开发 JavaScript
Web前端项目的跨平台桌面客户端打包方案之——CEF框架
Chromium Embedded Framework (CEF) 是一个基于 Google Chromium 项目的开源 Web 浏览器控件,旨在为第三方应用提供嵌入式浏览器支持。CEF 隔离了底层 Chromium 和 Blink 的复杂性,提供了稳定的产品级 API。它支持 Windows、Linux 和 Mac 平台,不仅限于 C/C++ 接口,还支持多种语言。CEF 功能强大,性能优异,广泛应用于桌面端开发,如 QQ、微信、网易云音乐等。CEF 开源且采用 BSD 授权,商业友好,装机量已超 1 亿。此外,GitHub 项目 CefDetector 可帮助检测电脑中使用 CEF
4939 3
|
API Windows
微软新一代输入法框架 TSF - Text Service Framework 小小的研究
原文:微软新一代输入法框架 TSF - Text Service Framework 小小的研究 虽说是转载的,但是其中,有很多我自己的评论,我会用红色的字标出来,参考的博文有: TSF架构:http://blog.
5602 0
|
运维 安全 Android开发
手机与电脑投屏互联方案
手机与电脑投屏互联方案