07. WebApp2.0时代启程:倒立者赢,从CPU到GPU,一张图片的旅行

简介: 紧接上文,终端开发使用的WindVane、wax、ReactNative等已经是一种跨平台的技术,我们称之为上层跨平台,Cocos2d-x这种直接使用C/C++,我们成为底层跨平台。上层跨平台,提升开发效率;下层跨平台,提升程序性能。 ####1. 为什么Cocos2d-x性能比Native开发要好? 因为Cocos2d-X是游戏引擎呗,人家是专业做游戏特效的好不好,直接调用GPU的Ope

紧接上文,终端开发使用的WindVane、wax、ReactNative等已经是一种跨平台的技术,我们称之为上层跨平台,Cocos2d-x这种直接使用C/C++,我们成为底层跨平台。上层跨平台,提升开发效率;下层跨平台,提升程序性能。

1. 为什么Cocos2d-x性能比Native开发要好?

因为Cocos2d-X是游戏引擎呗,人家是专业做游戏特效的好不好,直接调用GPU的OpenGL绘图的好不好。打开Cocos2d-X代码,感触最深的不是CCNode这些游戏节点,cocos2d-x已经开始为App开发做准备了!
screenshot
大家看到了什么?UIImageView?UIScrollView?OMG!当我们还在纠结要不要学习一下游戏开发的时候,人家都已经开始做UI系统了,有木有!!

很长一段时间,我们以为Game和App之间有一种天然的鸿沟,长久以来,这两种技术之间没有直接的联系,App可以做各种UI控件,游戏可以做各种UI特效,当游戏开发者想NativeApp大步的走过来的时候,我们还在学习如何用UITableView去优化图片的显示,岂不知在游戏世界里,早已实现了图片的异步加载、解码、缓存了!

别忘了,从iOS7.0开始,苹果已经默认集成了GameKit.framework,颤抖吧!游戏和App即将面临傻傻分不清楚的时候了。

2. 一张图片在OpenGL的世界里,是如何从SDCard显示到屏幕

这一次,我们不是讲UIImage如何加载一个图片,而是自己读取图片文件,解码图像,上传纹理,推送顶点缓冲,刷新帧缓冲,一步步显示图片到屏幕上。

上图之前,我们先了解几个概念:
2.1 帧缓冲:
我们要显示的视图绘制到屏幕上,本质是一个int32 buffer[width x height x 4]的一个数组,不管2D还是3D,我们都要吧GPU的对象投影到这个buffer中,一个像素4字节表示[R/G/B/A]。

2.2 顶点缓冲:
终端设备,只支持精简版的OpenGL,称之为OpenGL ES,这里我们只用2.0版本。在这个版本中,我们只能画三角形,一个正方形的图片纹理,需要两个三角形才可以描述:
screenshot
我们要画图片之前,就要告诉OpenGLES图片顶点的位置,先忽略三角形的顺序问题,就这样告诉GPU:【ABDBDC】,同时设置当前的激活的纹理,就可以绘制图片了。

2.3 上传纹理:
现在绝大多数手机,都有独立的显卡芯片,每个显卡芯片都有独立的显存,这么就清楚了:我们常说的内存是CPU直接操控的内存条容量,算上显存,就有两个缓存空间了,程序员可以发挥的空间又多了,是不是有点小激动?

2.4 图片解码:
常见的jpg、png等图片文件,都是压缩后的文件,如果不压缩,一张1024x1024的图片,至少需要4M空间。一张图片文件解码到内存,需要解码器来解码,不幸的是,Android很多机型没有集成硬件解码,庆幸的是iOS设备内部支持硬件解码。如果一张图片文件可以瞬间在底层硬件解码完成,要比上层代码优化,更加直接!这就是我们为什么一直追求的是底层跨平台的原因。

2.5 讲了很多概念了,我们上图吧
screenshot

3. 原来图片可以不占用内存!

我们用C的API来读取一张图片,用libpng来解码一张png图片,调用OpenGL ES2.0API上传图片到GPU显存,指定绘图坐标,显示一张图片,我们上代码吧:

一)读取文件:


AEFileData AEFileDataWithPathfile(std::string& pathfile) {
    AEFileData data;
    FILE* file = fopen(pathfile.c_str(), "rb");
    if (file == NULL) {
        return data;
    }
    fseek(file, 0, SEEK_END);
    data.size = (GLuint)ftell(file);
    fseek(file, 0, SEEK_SET);
    data.bytes = (GLchar*)malloc(sizeof(GLchar) * data.size);
    fread(data.bytes, sizeof(GLchar), data.size, file);
    fclose(file);
    return data;
}

二)解码图片:
为了跨平台通用,我们用libpng来解码图片,获取png的format和rgba数组,【代码太多,就不贴了】

三)上传GPU:


glGenTextures(1, &_textureId);
glBindTexture(type, _textureId);
glTexImage2D(type, 0, _format, _size.width, _size.height, 0, _format, GL_UNSIGNED_BYTE, pixels);

四)释放内存


AEFileData file = AEFileDataWithPathfile(pathfile);
this->initPNG((GLuchar*)file.bytes, file.size);
__FREE(file.bytes);

五)指定顶点索引,并绘制


vb[0] = {d11, t11, color};
vb[1] = {d21, t21, color};
vb[2] = {d12, t12, color};
vb[3] = {d21, t21, color};
vb[4] = {d12, t12, color};
vb[5] = {d22, t22, color};
glDrawArrays(GL_TRIANGLES, 0, _vertexIndex);

4. 对比iOS与Android,图片的渲染和展示做了什么?

screenshot
Android的技术框架更偏于上层,看不到底层C/C++代码实现,优化的空间有限;Objective-C更靠近C语言,优化的空间明显。下面我们以iOS为例:

5. 主流框架的图片优化思路

要读懂图片的展示与优化,就要看看当前主流的图片优化框架,他们是如何做优化的?主流的ImageCache框架:SDWebImage/FastImageCache/TBImageCache

screenshot

左边的是SDWebImage,中间是FastImageCache,右边是TBImageCache

SDWebImage:
将解码后的图片分别保存到内存和SDCard,将复杂的操作放到了子线程中;

FastImageCache:
直接将文件读取到mmap映射的文件中,减少了一次从系统内核到用户空间的一次拷贝【然并卵】

TBImageCache:
在首次加载图片时,根据用户需要,对图片增加圆角阴影处理,增加了异步线程的处理的工作量,同时保存到sdcard中,消耗了额外的资源,换取的是更好的图片性能【解决了圆角阴影在终端的痛点】

图片缓存框架,只是让CPU负载曲线更均衡,有没有可能再进一步优化?

6. 游戏世界与App世界对比

CPU与GPU是终端的双驾马车,缺一不可,CPU load过高,导致终端卡顿,同理GPU也是如此。iOS的UIKit框架封装的太好了,也是优化做的最好的,让我们看不到底层的原理,看不到iOS是如何平衡CPU、GPU、RAM、GPU RAM的4者之间的关系,让我们大胆大猜测一下吧:

screenshot

一)UIImageView管理者UIImage对象
UIImage就是内存中得bitmap数组,当我们需要绘制一张图片的时候,CPU发出指令,上传BitMap到GPU的显存中,GPU返回生成的纹理ID给CPU。

二)为了提高iOS的性能,苹果做出了一个优化策略,不释放CPU的bitmap
当GPU显存空间不足的时候,iOS会释放一部分图片纹理,以节省显存空间,但不会释放内存的bitmap。当一张图片重新需要绘制的时候,iO快速从内存bitmap快速上传到显存,完成绘制过程。

三)当内存的bitmap释放的时候,同步释放显存的纹理对象
本质意义上说,bitmap在内存中得存储,不是真正显示的那个缓存,当bitmap上传到GPU的时候,GPU会在显存中申请同样大小的空间,来存放bitmap,并返回纹理ID,每当CPU需要显示某张图片的时候,只需要高速GPU要绘制那个纹理ID就可以了。

四)上传GPU后,我们完全可以释放内存bitmap
释放内存的bitmap很简单: free(bitmap);
这样内存里只剩下了对应的GPU纹理的ID,当GPU内存紧张的时候,程序要要自己释放不需要的纹理ID,如果这个ID在某个时候有需要使用了,怎么办?那要把之前的读取文件到上传GPU重走一遍,太耗性能了,我想苹果也是不想看到这个原因,才不会轻易释放bitmap的

总结:游戏的架构模式,需要开发者自己优化所有场景

游戏开发者不仅需要优化CPU内存,也要优化GPU内存,通过将所有图片合成一张大的纹理图片,来统一管理GPU内存,需要把大量浮点数运算根据不同的场景,或者交给CPU或者交给GPU,让CPU和GPU的负载达到均衡,各司其职。
这一些优化,对于我们做系统上层应用的无线开发,是很难想象有多少坑等着我们,终端App的开发,还是使用上层框架好,图形图像的世界水很深,有想法的同学,可以继续关注下一章分享,想了解我们的同学,可以用Android下载安装我们的Demo:
screenshot

下一章,我们一起见证所及即所得的开发方式,WebApp2.0初体验

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
10天前
|
监控 异构计算
Jetson 学习笔记(八):htop查看CPU占用情况和jtop监控CPU和GPU
在NVIDIA Jetson平台上使用htop和jtop工具来监控CPU、GPU和内存的使用情况,并提供了安装和使用这些工具的具体命令。
52 0
|
1月前
|
人工智能 自然语言处理 文字识别
MinerU-大语言语料处理神器,CPU/GPU均可跑,开源免费“敲”好用
在7月4日举行的WAIC 2024科学前沿主论坛上,书生·浦语2.5正式发布,面向大模型研发与应用的全链条工具体系同时迎来升级。
MinerU-大语言语料处理神器,CPU/GPU均可跑,开源免费“敲”好用
|
2月前
|
机器学习/深度学习 人工智能 并行计算
【人工智能】CPU、GPU与TPU:人工智能领域的核心处理器概述
在人工智能和计算技术的快速发展中,CPU(中央处理器)、GPU(图形处理器)和TPU(张量处理器)作为核心处理器,各自扮演着不可或缺的角色。它们不仅在性能上各有千秋,还在不同的应用场景中发挥着重要作用
161 2
|
3月前
|
并行计算 API 数据处理
GPU(图形处理单元)因其强大的并行计算能力而备受关注。与传统的CPU相比,GPU在处理大规模数据密集型任务时具有显著的优势。
GPU(图形处理单元)因其强大的并行计算能力而备受关注。与传统的CPU相比,GPU在处理大规模数据密集型任务时具有显著的优势。
|
3月前
|
机器学习/深度学习 人工智能 并行计算
GPU 和 CPU 处理器的架构
CPU(中央处理器)和 GPU(图形处理单元)是计算机系统中最重要的两种处理器。它们各自的架构设计和技术体系决定了其在不同应用领域中的性能和效率。
121 1
|
3月前
|
机器学习/深度学习 TensorFlow API
Keras是一个高层神经网络API,由Python编写,并能够在TensorFlow、Theano或CNTK之上运行。Keras的设计初衷是支持快速实验,能够用最少的代码实现想法,并且能够方便地在CPU和GPU上运行。
Keras是一个高层神经网络API,由Python编写,并能够在TensorFlow、Theano或CNTK之上运行。Keras的设计初衷是支持快速实验,能够用最少的代码实现想法,并且能够方便地在CPU和GPU上运行。
|
4月前
|
机器学习/深度学习 并行计算 调度
对比GPU与CPU
对比GPU与CPU
105 0
|
移动开发 JavaScript 前端开发
用开发本地tcpip程序的思路开发webapp
本文关键字:the headless cms,b/s web to c/s web, headless webapp backend.
469 0
用开发本地tcpip程序的思路开发webapp
|
Web App开发 监控 Serverless
十分钟上线-基于函数计算开发 Restful web api & asp.net core web app
.NET Core是一个开源通用的开发框架,支持跨平台, 阿里云函数计算推出了 dotnetcore2.1 runtime, 使用 C# 编写 serverless 函数, 除了很好地支持通常意义上的函数外, 还可以基于函数计算开发 asp.
5733 0
|
Web App开发 JavaScript 测试技术
手机端webApp开发本地调试环境搭建
背景 手机端WebApp开发阶段,用chrome devtools模拟手机设备,很多兼容性问题不能提前发现。考虑到很多同学在开发时不便经常发版,方便设备可通过ip地址直接在移动端调试,提前发现问题,且不用发版到 test/pre 环境。
1795 0