批处理是使GPU进行高效绘制的一种技术手段,也是整个渲染流程中最核心的技术,到目前为止我们并没有使用到这种技术手段,下面我们看看我们现在的渲染机制。

先想一想我们最开始是怎么向GPU绘制一幅图像的,可以回头查看Stage3D学习笔记(五):通过矩阵操作纹理这篇文章;

绘制流程:

  1. 我们创建了两个矩阵,一个正交矩阵一个模型矩阵;
  2. 清除3D图像;
  3. 我们创建了顶点缓冲对象和索引缓冲对象比上传数据到GPU,我们编写了四个顶点数据表示图像的四个顶点,以及使用索引数据指示了这四个顶点数据要绘制的两个三角形(Stage3D是基于三角形进行绘制的,两个三角形拼成一个矩形,即我们的图像);
  4. 我们上传了需要绘制的纹理对象到GPU;
  5. 我们对模型矩阵进行了转换;
  6. 初始化着色器和设置着色器为当前使用的着色器程序;
  7. 指定着色器如何使用我们上传的数据,把我们的正交矩阵和模型矩阵合并并作为一个常量设置到着色器;
  8. 调用drawTriangles方法绘制我们的图像;
  9. 调用present方法将后台缓冲中的图像显示到屏幕;

下面看看我们v0.2的引擎绘制100个对象会是什么样的绘制流程(简化过,参考上面的绘制流程):

  1. 程序启动先执行第1步;
  2. 每帧开始执行第2步;
  3. 执行第3步到8第步绘制第一个图像;
  4. 重复执行第3步到8第步绘制完这100个图像;
  5. 执行第9步,一帧绘制结束;

我把第3步到8第步的绘制称为一次DrawCall,那么我们按照这个逻辑一帧会执行100次DrawCall,并且DrawCall的次数和我们绘制的图像个数是一致的。

下面我们来说说批处理的思想:假如我们绘制的这100个图像除了位置(或者旋转等顶点数据)外所有的需要的条件(纹理,着色器等)都是一致的,那么我们其实可以在上传顶点和索引数据时一口气就把这100个图像的数据都上传(反正其他的数据都一致),这样进行绘制的话,其实我们只需执行一个DrawCall就把所有的图像都绘制了出来。

这就是批次绘制即批处理的核心思想,再考虑下,如果前50个图像和后50个图像的纹理不一致呢?这样就有2次DrawCall了,如果极端情况呢,100个图像都不一致?那样批处理也会执行100次DrawCall,但是这种情况也是有一种优化方法可以降低到最少1次DrawCall,不急,这个方法我们后面会谈到。

2014年11月25日补充:DrawCall次数的减少本质上是减轻CPU和GPU之间相互通信的开销,从而获得效率的提升。

理解了批处理的思想,下面我们看看Starling中是怎么用代码实现的吧:

未优化的Starling中,带有渲染逻辑(rander方法中)会进行实际渲染的类只有两个分别是:

  1. 渲染没有纹理仅显示颜色的正方形类Quad;
  2. 渲染带有纹理的图形类Image;

这两个类都带有渲染所需的所有数据,每次渲染都可以看做一次DrawCall,所以可以理解为有多少个Quad或者Image对象在舞台每帧就会有多少次DrawCall;

而基于批处理,我们优化的目的是如果状态一致(指着色器、纹理等一致的情况),多个Quad或者Image的渲染合并为一次DrawCall,那么实际渲染逻辑就需要从Quad和Image类中去掉;

QuadBatch:

Starling引入了一个新的类QuadBatch,从名称就可以看出该类是用来进行批处理操作的,Starling将实际的渲染逻辑从Quad和Image中移除,并移到该类中,即Starling中的所有模型渲染都是在该类中完成的。

QuadBatch类在Straling中有3个重要的用途:

1.实现自动批处理功能(核心逻辑):

RenderSupport类中保存一个QuadBatch的数组mQuadBatches,每帧循环时,每个需要渲染的对象的rander方法中会将自己的数据提交到RenderSupport当前的QuadBatch对象中,具体就是Quad类会调用QuadBatch的addQuad方法添加自身的数据,Image类会调用QuadBatch的addImage方法添加自身的数据;

每次添加之前QuadBatch对象都会判断一下状态是否改变,如果改变则立即渲染当前QuadBatch中收集的数据,并且新建一个QuadBatch对象为当前使用的新的批处理对象来使用;如果状态没有改变则说明下一个需要渲染的对象是可以提交到本次批处理中的,会添加该对象对应的数据到当前QuadBatch对象中;

2.为Sprite类的flatten方法提供支持:

QuadBatch类提供静态方法compile为Sprite类的flatten方法提供支持,Sprite类调用flatten方法后,会将其内部的所有子对象编译为多个QuadBatch对象保存到mFlattenedContents属性中,重写rander方法,如果调用过flatten方法,则以后每帧都会跳过处理其内部的所有子对象,而是直接使用编译好的mFlattenedContents直接进行渲染来提高运行效率;

当然这种技术的局限是,Sprite中的所有子项都不会发生改动,同时再也看不到子节点属性的任何变化(位置,旋转,透明度等)。 要更新这个显示对象的屏幕显示,只需要再次调用flatten一次,或者unflatten这个对象。

3.作为更加高效的容器使用:

有趣的是QuadBatch类被Starling的作者设计为DisplayObject的子类,表示其可以作为一个显示对象添加到舞台之中,但是由于QuadBatch类必须调用addQuad或addImage方法添加子项进行渲染,使其又有了类似容器的功能,但和容器不同的是,QuadBatch类并不是将子项添加到自身,而是将子项的数据拷贝到自身。

使用QuadBatch类作为容器使用好处是可以更加高效,因为避开了容器逻辑运算和事件派发,坏处是添加的子项状态必须一致,并且所有添加的子项其实都融合为一个独立的显示对象了。

状态是否改变的判断:

状态是否改变的判断是QuadBatch类的isStateChange方法,我们可以直接查看来确定我们设计的游戏中是否存在导致状态改变的因素从而导致DrawCall的上升:

 1 /** Indicates if a quad can be added to the batch without causing a state change.
 2  *  A state change occurs if the quad uses a different base texture or has a different
 3  *  'smoothing', 'repeat' or 'blendMode' setting. */
 4 public function isStateChange(quad:Quad, parentAlpha:Number, texture:Texture,
 5                               smoothing:String, blendMode:String):Boolean
 6 {
 7     if (mNumQuads == 0) return false;
 8     else if (mNumQuads == 8192) return true; // maximum buffer size
 9     else if (mTexture == null && texture == null) return false;
10     else if (mTexture != null && texture != null)
11         return mTexture.base != texture.base ||
12                mTexture.repeat != texture.repeat ||
13                mSmoothing != smoothing ||
14                mTinted != (quad.tinted || parentAlpha != 1.0) ||
15                this.blendMode != blendMode;
16     else return true;
17 }

矩阵转换:

看到这里,你如果认为QuadBatch的addQuad和addImage方法就是简单的把目标对象的顶点数据添加到QuadBatch自身的顶点数据中就大错特错了,其实这一步我们又会面临曾经遇到过的两个问题:

  1. 确定添加的顶点数据(即一堆三角形)谁先绘制,需要正确的遮挡关系;
  2. 确定每个绘制的对象最终绘制到3D画布上的最终状态(位置、旋转和缩放等属性);

对于第一个问题,Starling框架的正确渲染顺序已经解决了,先添加的三角形会被后添加的三角形覆盖,即先调用rander的对象会被后调用rander的对象覆盖,即使使用批处理技术也一样;

对于第二个问题,由于Starling的状态改变不包括判断我们的对象是否位于同一父容器,所以位于不同父级容器的对象都可以作为一次性绘制的对象,导致出现需要对每个额外进行矩阵的转换,那么我们在Starling的源码中寻找答案吧:

在RenderSupport的batchQuad方法中(该方法会在Quad和Image的rander方法中调用):

 1 /** Adds a quad to the current batch of unrendered quads. If there is a state change,
 2  *  all previous quads are rendered at once, and the batch is reset. */
 3 public function batchQuad(quad:Quad, parentAlpha:Number,
 4                           texture:Texture=null, smoothing:String=null):void
 5 {
 6     if (currentQuadBatch.isStateChange(quad, parentAlpha, texture, smoothing, mBlendMode))
 7         finishQuadBatch();
 8
 9     currentQuadBatch.addQuad(quad, parentAlpha, texture, smoothing, mModelViewMatrix, mBlendMode);
10 }

是将当前对象的坐标转换矩阵mModelViewMatrix作为参数传入的,说明在addQuad方法中不需要考虑父级和自身的转换矩阵,直接针对mModelViewMatrix处理即可,我们看看mModelViewMatrix是何时被处理的,DisplayObjectContainer类的render方法:

 1 /** @inheritDoc */
 2 public override function render(support:RenderSupport, parentAlpha:Number):void
 3 {
 4     var alpha:Number = parentAlpha * this.alpha;
 5     var numChildren:int = mChildren.length;
 6
 7     for (var i:int=0; i<numChildren; ++i)
 8     {
 9         var child:DisplayObject = mChildren[i];
10         if (child.alpha != 0.0 && child.visible && child.scaleX != 0.0 && child.scaleY != 0.0)
11         {
12             support.pushMatrix();
13             support.pushBlendMode();
14
15             support.blendMode = child.blendMode;
16             support.transformMatrix(child);
17             child.render(support, alpha);
18
19             support.popMatrix();
20             support.popBlendMode();
21         }
22     }
23 }

16行代码,将当期处理的child对象的转换矩阵数据设置为mModelViewMatrix,然后处理子项的rander方法。

我们接下来看看BatchQuad的addQuad方法:

 1 /** Adds a quad to the batch. The first quad determines the state of the batch,
 2  *  i.e. the values for texture, smoothing and blendmode. When you add additional quads,
 3  *  make sure they share that state (e.g. with the 'isStageChange' method), or reset
 4  *  the batch. */
 5 public function addQuad(quad:Quad, parentAlpha:Number=1.0, texture:Texture=null,
 6                         smoothing:String=null, modelViewMatrix:Matrix3D=null,
 7                         blendMode:String=null):void
 8 {
 9     if (modelViewMatrix == null)
10     {
11         modelViewMatrix = sHelperMatrix3D;
12         modelViewMatrix.identity();
13         RenderSupport.transformMatrixForObject(modelViewMatrix, quad);
14     }
15
16     var tinted:Boolean = texture ? (quad.tinted || parentAlpha != 1.0) : false;
17     var alpha:Number = parentAlpha * quad.alpha;
18     var vertexID:int = mNumQuads * 4;
19
20     if (mNumQuads + 1 > mVertexData.numVertices / 4) expand();
21     if (mNumQuads == 0)
22     {
23         this.blendMode = blendMode ? blendMode : quad.blendMode;
24         mTexture = texture;
25         mTinted = tinted;
26         mSmoothing = smoothing;
27         mVertexData.setPremultipliedAlpha(
28             texture ? texture.premultipliedAlpha : true, false);
29     }
30
31     quad.copyVertexDataTo(mVertexData, vertexID);
32
33     if (alpha != 1.0)
34         mVertexData.scaleAlpha(vertexID, alpha, 4);
35
36     mVertexData.transformVertex(vertexID, modelViewMatrix, 4);
37
38     mSyncRequired = true;
39     mNumQuads++;
40 }

我们主要集中注意到第36行的代码,该行代码将新添加的顶点坐标和mModelViewMatrix矩阵进行运算,得到的结果是该对象最终会显示到3D画布的坐标;

我们在看看Starling是怎么对批处理对象进行绘制的,RenderSupport的finishQuadBatch方法:

 1 /** Renders the current quad batch and resets it. */
 2 public function finishQuadBatch():void
 3 {
 4     currentQuadBatch.renderCustom(mProjectionMatrix);
 5     currentQuadBatch.reset();
 6
 7     ++mCurrentQuadBatchID;
 8
 9     if (mQuadBatches.length <= mCurrentQuadBatchID)
10         mQuadBatches.push(new QuadBatch());
11 }

这个方法我们调用QuadBatch的renderCustom方法传入的是正交矩阵mProjectionMatrix,而不是和当前mModelViewMatrix运算过的mvpMatrix矩阵,因为我们的运算在合并顶点数据时已经进行了。

批处理渲染:

QuadBatch类的renderCustom方法是Starling中真正进行3D渲染的核心代码,没有特别需要说的,因为所有需要的数据在该代码执行前都已经正确处理完毕了。

【Stage3D学习笔记续】山寨Starling(八):核心优化(批处理)的实现相关推荐

  1. OpenCV学习笔记(五十六)——InputArray和OutputArray的那些事core OpenCV学习笔记(五十七)——在同一窗口显示多幅图片 OpenCV学习笔记(五十八)——读《Mast

    OpenCV学习笔记(五十六)--InputArray和OutputArray的那些事core 看过OpenCV源代码的朋友,肯定都知道很多函数的接口都是InputArray或者OutputArray ...

  2. OpenCV学习笔记(四十六)——FAST特征点检测features2D OpenCV学习笔记(四十七)——VideoWriter生成视频流highgui OpenCV学习笔记(四十八)——PCA算

    OpenCV学习笔记(四十六)--FAST特征点检测features2D 特征点检测和匹配是计算机视觉中一个很有用的技术.在物体检测,视觉跟踪,三维常年关键等领域都有很广泛的应用.这一次先介绍特征点检 ...

  3. OpenCV学习笔记(三十六)——Kalman滤波做运动目标跟踪 OpenCV学习笔记(三十七)——实用函数、系统函数、宏core OpenCV学习笔记(三十八)——显示当前FPS OpenC

    OpenCV学习笔记(三十六)--Kalman滤波做运动目标跟踪 kalman滤波大家都很熟悉,其基本思想就是先不考虑输入信号和观测噪声的影响,得到状态变量和输出信号的估计值,再用输出信号的估计误差加 ...

  4. OpenCV学习笔记(二十六)——小试SVM算法ml OpenCV学习笔记(二十七)——基于级联分类器的目标检测objdect OpenCV学习笔记(二十八)——光流法对运动目标跟踪Video Ope

    OpenCV学习笔记(二十六)--小试SVM算法ml 总感觉自己停留在码农的初级阶段,要想更上一层,就得静下心来,好好研究一下算法的东西.OpenCV作为一个计算机视觉的开源库,肯定不会只停留在数字图 ...

  5. Slicer学习笔记(二十八)Elastix扩展模块

    Slicer学习笔记(二十八)Elastix扩展模块 1.代码结构 1.1.代码结构 1.2.页面布局 1.3.Elastix路径选择 [可选] 2.执行配准 2.1 先去找到elastix.exe的 ...

  6. 【OS学习笔记】二十八 保护模式八:任务切换对应的汇编代码之内核代码

    本汇编代码对应以下两篇文章对应的内核汇编代码: OS学习笔记]二十六 保护模式八:任务门-任务切换 [OS学习笔记]二十七 保护模式八:任务切换的方法之----jmp与call的区别以及任务的中断嵌套 ...

  7. node JS獲取GPS_node学习笔记(三十八)

    一.什么是NodeJS? 1.Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境. 2.Node.js 使用了一个事件驱动.非阻塞式 I/O 的模型,使其轻量又高效 ...

  8. 【OS学习笔记】三十八 保护模式十:中断和异常的处理与抢占式多任务对应的汇编代码----微型内核汇代码

    本文是以下几篇文章对应的微型内核代码汇编代码: [OS学习笔记]三十四 保护模式十:中断和异常区别 [OS学习笔记]三十五 保护模式十:中断描述符表.中断门和陷阱门 [OS学习笔记]三十六 保护模式十 ...

  9. Spring源码学习笔记:起源发展和核心模块主要职能

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 1.Spring 的前世今生 早在 2007 年,一个基于 Java语言的开源框架正式发布,取了一个非常有活力 ...

最新文章

  1. python数据库管理实例_西游之路——python全栈——学员管理实例之数据库设计
  2. java课程 数独 文库_数独java
  3. hdu 4886(hash + dfs)
  4. R-CNN(Rich feature hierarchies for accurate object detection and semantic segmentation)论文理解...
  5. Android使用NDK---函数参数传递-基本类型和数组
  6. 定制iOS 7中的导航栏和状态栏
  7. java面试题35 给定以下JAVA代码,这段代码运行后输出的结果是()
  8. CentOS 6.4 升级 Mysq5.5l方法 和 用户远程登录数据库
  9. Notepad++ 大小写转换
  10. Using C++ in Eclipse - Program file not Specified problem
  11. 架构设计--逻辑层 vs 物理层
  12. AAAI 2019 使用循环条件注意力结构探索回答立场检测任务
  13. React Native For Android 架构初探
  14. 记录MS VisualStudio添加注释宏代码写法
  15. 高斯误差函数erf的数值计算方法(C++实现)
  16. java开发autocad_.NET AutoCAD二次开发之路(四、文字篇)
  17. Spring全家桶简介
  18. Mac清理系统用什么软件?
  19. Jupyter Notebook 自动补全、智能提示
  20. Android加密篇 RSA

热门文章

  1. 获取文件最后修改时间的VC代码
  2. Directx11教程(11) 增加一个debug宏
  3. 闭关纪要17.Google app engine的简单应用
  4. 01-HTML基础与进阶-day6-录像281
  5. 70.nodejs操作mongodb
  6. windows server 2012 application control policy
  7. java使用jeids实现redis2.6的String操作(1)
  8. Photoshop五步制作水晶按钮
  9. 新浪微博中的周期性爆发流量
  10. Cocos2d-x Eclipse下程序运行产生错误Effect initCheck() returned -1