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

    xiaoxiao2026-04-14  5

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

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

    因为Cocos2d-X是游戏引擎呗,人家是专业做游戏特效的好不好,直接调用GPU的OpenGL绘图的好不好。打开Cocos2d-X代码,感触最深的不是CCNode这些游戏节点,cocos2d-x已经开始为App开发做准备了!大家看到了什么?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版本。在这个版本中,我们只能画三角形,一个正方形的图片纹理,需要两个三角形才可以描述:我们要画图片之前,就要告诉OpenGLES图片顶点的位置,先忽略三角形的顺序问题,就这样告诉GPU:【ABDBDC】,同时设置当前的激活的纹理,就可以绘制图片了。

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

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

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

    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,图片的渲染和展示做了什么?

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

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

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

    左边的是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者之间的关系,让我们大胆大猜测一下吧:

    一)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:

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

    最新回复(0)