【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系统中, ...
随机推荐
- Django QuerySet API文档
在查询时发生了什么(When QuerySets are evaluated) QuerySet 可以被构造,过滤,切片,做为参数传递,这些行为都不会对数据库进行操作.只要你查询的时候才真正的操作数据 ...
- PHP集成支付宝快速实现充值功能
http://blog.lixiphp.com/php-alipay-fast-chongzhi/#axzz2tOypIl4r
- Linux下安装Wine运行windows程序
资料 首页 https://www.winehq.org/ 安装 https://www.winehq.org/download/ 教程 https://www.winehq.org/document ...
- 音频(3)Android TTS技术支持朗读英文
Android对TTS技术的支持 Android 1.6开始支持TTS(Text To Speech)技术,通过该技术可以将文本转换成语音.目前2015-09-06只支持朗读英文. TTS技术的核心是 ...
- word文档左侧显示目录
word2007 选择word的视图,然后选择文档结构图
- 基于Struts2的用户登录程序
基本步骤: 1.新建Java工程,File>New>Project>Web>Dynamic Web Project,并将工程命名为:Struts2_Demo 2.导入strut ...
- spring-security用户权限认证框架
大家知道在spring中有一个基于acegi开发的spring-security的权限管理模块,它是一个轻量级框架. SpringSecurity能以声明的方式来保护Web应用程序的URL访问,只需简 ...
- (3)java棧
java棧和函数调用的关系图 [名词解释]--->java棧是一块线程的私有空间--->java的棧是先进后出的数据结构.函数返回,则该函数的棧帧被弹出.--->一个函数对应一个棧帧 ...
- 【转】使用NDK生成native C/C++的可执行程序
原文网址:http://www.linuxidc.com/Linux/2011-08/40901.htm 众所周知, NDK可以生成lib,让java程序通过jni来调用,其实,NDK也可以生成C/C ...
- 异常处理 Exception
一.异常类 1.在C#中所有的异常都是使用一个异常类型的示例对象表示的,这些异常类型都是继承自System.Exception类型,或者直接使用System.Exception类型的实例对象: 2.在 ...