【Stage3D学习笔记续】山寨Starling(八):核心优化(批处理)的实现
批处理是使GPU进行高效绘制的一种技术手段,也是整个渲染流程中最核心的技术,到目前为止我们并没有使用到这种技术手段,下面我们看看我们现在的渲染机制。
先想一想我们最开始是怎么向GPU绘制一幅图像的,可以回头查看Stage3D学习笔记(五):通过矩阵操作纹理这篇文章;
绘制流程:
- 我们创建了两个矩阵,一个正交矩阵一个模型矩阵;
- 清除3D图像;
- 我们创建了顶点缓冲对象和索引缓冲对象比上传数据到GPU,我们编写了四个顶点数据表示图像的四个顶点,以及使用索引数据指示了这四个顶点数据要绘制的两个三角形(Stage3D是基于三角形进行绘制的,两个三角形拼成一个矩形,即我们的图像);
- 我们上传了需要绘制的纹理对象到GPU;
- 我们对模型矩阵进行了转换;
- 初始化着色器和设置着色器为当前使用的着色器程序;
- 指定着色器如何使用我们上传的数据,把我们的正交矩阵和模型矩阵合并并作为一个常量设置到着色器;
- 调用drawTriangles方法绘制我们的图像;
- 调用present方法将后台缓冲中的图像显示到屏幕;
下面看看我们v0.2的引擎绘制100个对象会是什么样的绘制流程(简化过,参考上面的绘制流程):
- 程序启动先执行第1步;
- 每帧开始执行第2步;
- 执行第3步到8第步绘制第一个图像;
- 重复执行第3步到8第步绘制完这100个图像;
- 执行第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方法中)会进行实际渲染的类只有两个分别是:
- 渲染没有纹理仅显示颜色的正方形类Quad;
- 渲染带有纹理的图形类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的上升:
/** Indicates if a quad can be added to the batch without causing a state change.
* A state change occurs if the quad uses a different base texture or has a different
* 'smoothing', 'repeat' or 'blendMode' setting. */
public function isStateChange(quad:Quad, parentAlpha:Number, texture:Texture,
smoothing:String, blendMode:String):Boolean
{
if (mNumQuads == 0) return false;
else if (mNumQuads == 8192) return true; // maximum buffer size
else if (mTexture == null && texture == null) return false;
else if (mTexture != null && texture != null)
return mTexture.base != texture.base ||
mTexture.repeat != texture.repeat ||
mSmoothing != smoothing ||
mTinted != (quad.tinted || parentAlpha != 1.0) ||
this.blendMode != blendMode;
else return true;
}
矩阵转换:
看到这里,你如果认为QuadBatch的addQuad和addImage方法就是简单的把目标对象的顶点数据添加到QuadBatch自身的顶点数据中就大错特错了,其实这一步我们又会面临曾经遇到过的两个问题:
- 确定添加的顶点数据(即一堆三角形)谁先绘制,需要正确的遮挡关系;
- 确定每个绘制的对象最终绘制到3D画布上的最终状态(位置、旋转和缩放等属性);
对于第一个问题,Starling框架的正确渲染顺序已经解决了,先添加的三角形会被后添加的三角形覆盖,即先调用rander的对象会被后调用rander的对象覆盖,即使使用批处理技术也一样;
对于第二个问题,由于Starling的状态改变不包括判断我们的对象是否位于同一父容器,所以位于不同父级容器的对象都可以作为一次性绘制的对象,导致出现需要对每个额外进行矩阵的转换,那么我们在Starling的源码中寻找答案吧:
在RenderSupport的batchQuad方法中(该方法会在Quad和Image的rander方法中调用):
/** Adds a quad to the current batch of unrendered quads. If there is a state change,
* all previous quads are rendered at once, and the batch is reset. */
public function batchQuad(quad:Quad, parentAlpha:Number,
texture:Texture=null, smoothing:String=null):void
{
if (currentQuadBatch.isStateChange(quad, parentAlpha, texture, smoothing, mBlendMode))
finishQuadBatch(); currentQuadBatch.addQuad(quad, parentAlpha, texture, smoothing, mModelViewMatrix, mBlendMode);
}
是将当前对象的坐标转换矩阵mModelViewMatrix作为参数传入的,说明在addQuad方法中不需要考虑父级和自身的转换矩阵,直接针对mModelViewMatrix处理即可,我们看看mModelViewMatrix是何时被处理的,DisplayObjectContainer类的render方法:
/** @inheritDoc */
public override function render(support:RenderSupport, parentAlpha:Number):void
{
var alpha:Number = parentAlpha * this.alpha;
var numChildren:int = mChildren.length; for (var i:int=0; i<numChildren; ++i)
{
var child:DisplayObject = mChildren[i];
if (child.alpha != 0.0 && child.visible && child.scaleX != 0.0 && child.scaleY != 0.0)
{
support.pushMatrix();
support.pushBlendMode(); support.blendMode = child.blendMode;
support.transformMatrix(child);
child.render(support, alpha); support.popMatrix();
support.popBlendMode();
}
}
}
16行代码,将当期处理的child对象的转换矩阵数据设置为mModelViewMatrix,然后处理子项的rander方法。
我们接下来看看BatchQuad的addQuad方法:
/** Adds a quad to the batch. The first quad determines the state of the batch,
* i.e. the values for texture, smoothing and blendmode. When you add additional quads,
* make sure they share that state (e.g. with the 'isStageChange' method), or reset
* the batch. */
public function addQuad(quad:Quad, parentAlpha:Number=1.0, texture:Texture=null,
smoothing:String=null, modelViewMatrix:Matrix3D=null,
blendMode:String=null):void
{
if (modelViewMatrix == null)
{
modelViewMatrix = sHelperMatrix3D;
modelViewMatrix.identity();
RenderSupport.transformMatrixForObject(modelViewMatrix, quad);
} var tinted:Boolean = texture ? (quad.tinted || parentAlpha != 1.0) : false;
var alpha:Number = parentAlpha * quad.alpha;
var vertexID:int = mNumQuads * 4; if (mNumQuads + 1 > mVertexData.numVertices / 4) expand();
if (mNumQuads == 0)
{
this.blendMode = blendMode ? blendMode : quad.blendMode;
mTexture = texture;
mTinted = tinted;
mSmoothing = smoothing;
mVertexData.setPremultipliedAlpha(
texture ? texture.premultipliedAlpha : true, false);
} quad.copyVertexDataTo(mVertexData, vertexID); if (alpha != 1.0)
mVertexData.scaleAlpha(vertexID, alpha, 4); mVertexData.transformVertex(vertexID, modelViewMatrix, 4); mSyncRequired = true;
mNumQuads++;
}
我们主要集中注意到第36行的代码,该行代码将新添加的顶点坐标和mModelViewMatrix矩阵进行运算,得到的结果是该对象最终会显示到3D画布的坐标;
我们在看看Starling是怎么对批处理对象进行绘制的,RenderSupport的finishQuadBatch方法:
/** Renders the current quad batch and resets it. */
public function finishQuadBatch():void
{
currentQuadBatch.renderCustom(mProjectionMatrix);
currentQuadBatch.reset(); ++mCurrentQuadBatchID; if (mQuadBatches.length <= mCurrentQuadBatchID)
mQuadBatches.push(new QuadBatch());
}
这个方法我们调用QuadBatch的renderCustom方法传入的是正交矩阵mProjectionMatrix,而不是和当前mModelViewMatrix运算过的mvpMatrix矩阵,因为我们的运算在合并顶点数据时已经进行了。
批处理渲染:
QuadBatch类的renderCustom方法是Starling中真正进行3D渲染的核心代码,没有特别需要说的,因为所有需要的数据在该代码执行前都已经正确处理完毕了。
【Stage3D学习笔记续】山寨Starling(八):核心优化(批处理)的实现的更多相关文章
- 【Stage3D学习笔记续】山寨Starling(十二):总结和一些没提到的东西
我们的山寨Starling到这里就告一段落了,不得不说这是一个非常优秀的2D框架,他的设计和架构为后来的许多框架都提供了很好的参考,比如现在正在崛起的Egret,我们的一番解读也只是窥见了Starli ...
- 【Stage3D学习笔记续】山寨Starling(十一):Touch事件体系
我们的山寨Starling版本将会在这里停止更新了,主要还是由于时间比较有限,而且我们的山寨版本也很好的完成了他的任务“了解Starling的核心渲染”,接下来的Starling解析我们将会直接阅读S ...
- 【Stage3D学习笔记续】山寨Starling(二):VertexData探幽
还记得之前的学习笔记中我们的顶点缓冲数组中的顶点数据么,我们使用一个一维数组来记录所有的顶点数据,这是由于顶点缓冲上传数据时是使用的一维数组. 如果对顶点数据进行一层代码的封装,就能更加的方便我们来操 ...
- 【Stage3D学习笔记续】真正的3D世界(一):透视矩阵
如果各位看官跟着我的学习笔记一路看过来的话,一定会吐槽我的,这都是什么3D啊?从头到尾整个都是在使用GPU绘制一堆2D图像而已,的确,之前我们一直使用正交矩阵利用GPU加速来实现2D世界的展示,算不上 ...
- 【Stage3D学习笔记续】山寨Starling(三):Starling核心渲染流程
这篇文章我们剔除Starling的Touch事件体系和动画体系,专门来看看Starling中的渲染流程实现,以及其搭建的显示列表结构. 由于Starling是模仿Flash的原生显示列表,所以我们可以 ...
- 【Stage3D学习笔记续】山寨Starling(四):渲染代码实现及测试程序
本章会实现最核心的代码,所以涉及点会比较多,这里会发布一个版本,方便日后的回退查看. 点击下载:https://codeload.github.com/hammerc/hammerc-study-St ...
- 【Stage3D学习笔记续】山寨Starling(一):从事件说起
我在GitHub上新开了一个项目:https://github.com/hammerc/hammerc-study-Stage3D 山寨的Starling版本我取名叫做Scorpio2D,以后的笔记中 ...
- 【Stage3D学习笔记续】山寨Starling(十):高效游戏设计、纹理集和ATF
我发布了经过批处理优化的v0.3版,点击下载:https://github.com/hammerc/hammerc-study-Stage3D/archive/v0.3.zip 先看看我们批处理优化后 ...
- 【Stage3D学习笔记续】山寨Starling(九):上下文丢失处理方法
Stage3D在运行中是存在随时会丢失上下文的尴尬情况. 渲染内容丢失的问题本身就说明是因为丢失了Context3D对象.出现此问题的原因很多,通常还不是因为Stage3D应用.比如在win7系统中, ...
随机推荐
- 性能优化工具 MVC Mini Profiler
性能优化工具 MVC Mini Profiler MVC MiniProfiler是Stack Overflow团队设计的一款对ASP.NET MVC.WebForm 以及WCF 的性能分析的小程 ...
- Android开发之使用AlertDialog创建对话框,单选框和多选框
对话框: 对话框的icon,title,message等都可以不设置. 单选框和多选框与对话框勾选步骤基本上一致. 对话框的构建步骤: 1.使用AlertDialog类的内部类Builder类new ...
- 2013ACM省赛题目
地址就贴这一个吧 都在附近 当时回来也没做做 一伤心了 二是当时实在太弱了 先补两道DP E题的区间DP dp[i][j] 截止到i位置以字母j为结束的上升序列 正序 逆序各来一遍 再循环一遍保存一 ...
- bzoj1485
首先考虑dp,设f[i,j]表示1~i用过了,期中j个放在偶数位然后转移大家都会 这显然TLE,我们观察这个dp,任意前i个数,无论怎么放,放在奇数位的数的个数一定要大于等于放在偶数位的个数 于是很明 ...
- UVa 1639 (期望) Candy
题意: 两个盒子里各有n颗糖,每天有p的概率从第一个盒子里取一颗糖,1-p的概率从第二个盒子里去一颗糖.直到某一天打开某个盒子忽然发现没糖了,求另一个盒子里剩余糖果数的期望. 分析: 紫书上面已经分析 ...
- WCF学习笔记(一):WCF简介
转:http://www.cnblogs.com/wengyuli/archive/2009/11/04/1595693.html MSDN上关于WCF给出如下注解: 设计 Windows Commu ...
- TortoiseSVN中分支和合并实践
使用svn几年了,一直对分支和合并敬而远之,一来是因为分支的管理不该我操心,二来即使涉及到分支的管理,也不敢贸然使用合并功能,生怕合并出了问题对团队造成不良影响,最主要的原因是,自己对分支的目的和合并 ...
- java jvm学习笔记六(实现写自己的安全管理器)
安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用AccessController的checkPerssiom方法,访问控 ...
- Zabbix探索:资产信息的妙用
前一阵子还在考虑CMDB的问题,因此Zabbix中的Inventory,也就是所谓的资产信息,遭到了我的不少鄙视. 这几天在研究告警通知对应责任人的问题,突然想起Zabbix的资产信息中应该有这么一栏 ...
- 【windows核心编程】 第六章 线程基础
Windows核心编程 第六章 线程基础 欢迎转载 转载请注明出处:http://www.cnblogs.com/cuish/p/3145214.html 1. 线程的组成 ① 一个是线程的内核 ...