当前位置: 首页 > 编程日记 > 正文

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

批处理是使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渲染的核心代码,没有特别需要说的,因为所有需要的数据在该代码执行前都已经正确处理完毕了。

相关文章:

MBProgressHUD 使用详解

MBProgressHUD是一个显示HUD窗口的第三方类库&#xff0c;用于在执行一些后台任务时&#xff0c;在程序中显示一个表示进度的loading视图和两个可选的文本提示的HUD窗口。我想最多是应用在加载网络数据的时候。其实苹果官方自己有一个带有此功能的类UIProgressHUD&#xff0c;只…

V4L2获取usb视频流测试代码

Video4Linux2(Video for Linux Two, 简称V4L2)是Linux中关于视频设备的驱动框架&#xff0c;为上层访问底层的视频设备提供统一接口。V4L2主要支持三类设备&#xff1a;视频输入输出设备、VBI设备和Radio设备&#xff0c;分别会在/dev目录下产生videoX、vbiX和radioX设备节点&a…

深度学习渐趋冷静,为何图形计算却逆势反涨?

不知不觉&#xff0c;2019 年的进度条已经快撑不住了 ▓▓▓▓▓▓▓▓▓▓▓▓▓░░ 88%。就像这个进度条一样&#xff0c;人工智能的发展也绝不是一蹴而就的事&#xff0c;而是一步一个脚印逐渐发展&#xff0c;最后达成某个目标。近年来&#xff0c;深度学习领域的技术发展…

数字图像处理课设

2019独角兽企业重金招聘Python工程师标准>>> 对于整个窗体的设计&#xff0c;菜单组件&#xff08;MenuStrip)和工具条&#xff08;ToolStrip&#xff09;来添加相应的功能事件当然还有右击鼠标就能弹出相应的属性框&#xff0c;也是使用了叫contextMenuStrip的组件…

Swift3.0带来的变化汇总

var string "Hello-Swift" //获取某个下标后一个下标对应的字符 char"e" //swift2.2 //var char string[startIndex.successor()] //swift3.0 var char string[string.index(after: startIndex)] //获取某个下标前一个下标对应的字符 char2 "t&qu…

通过配置NFS使Ubuntu和海思3559A板子共享目录

之前在Ubuntu和海思3559A板子之间来回拷贝文件都是用的scp命令&#xff0c;不是很方便&#xff0c;这里通过配置NFS来实现它们之间共享目录&#xff0c;操作步骤如下&#xff1a; 1. 在Ubuntu上安装NFS&#xff0c;执行以下命令&#xff0c;执行结果如下&#xff1a; sudo ap…

22w+的人选择了这款蓝牙耳机

01现在有两个电子产品&#xff0c;应该是你们现在必有的&#xff0c;手机和电脑。但还有一样不可缺少的电子产品&#xff0c;那就是耳机。不管喜不喜欢带&#xff0c;反正你手里肯定有一个&#xff0c;应该没人不认可我说的这个话吧。反正我去哪都会带着耳机&#xff0c;听不听…

自定义数字格式字符串输出示例

自定义数字格式字符串输出示例 MSDN 下表阐释了通过给特定数据类型和值应用某些自定义数字格式字符串来创建的输出。输出是通过使用 ToString 方法和美国英语 (en-US) 区域性生成的。 “格式字符串”列指示格式字符串&#xff0c;“数据类型”列指示所用的数据类型&#xff0c;…

海思3559A上编译LIVE555源码操作步骤

1. 从http://www.live555.com/liveMedia/public/ 下载live.2019.06.28.tar.gz&#xff0c;并解压缩&#xff1b; 2. 生成静态库&#xff0c;拷贝一份config.armlinux&#xff0c;取名为config.hi3559a&#xff0c;修改后的内容如下: CROSS_COMPILE? /opt/hisi-linux/x86-ar…

基于海康机器视觉算法平台的对位贴合项目个人理解 | CSDN原力计划

扫码参与CSDN“原力计划”作者 | 果汁分你一半哈哈来源 | CSDN原力计划获奖作品都说“纸上得来终觉浅&#xff0c;绝知此事要躬行”&#xff0c;可惜咱没这条件呀&#xff0c;没项目咱也不能干坐着呀&#xff0c;那咱发挥主观能动性&#xff0c;咱不是学机械的么&#xff0c;还…

解决Swift中present(uiImagePickerController,animated: true,completion: nil)闪退的问题

swift中开发选择图片上传&#xff0c;会使用到Tap Gesture Recognizer控件&#xff0c;对应 UITapGestureRecognizer API&#xff0c;以下是代码示例&#xff08;取自IOS developer library&#xff09;&#xff1a; IBAction func selectImageFromPhotoLibrary(_ sender: UIT…

测试发现equals和hashCode与书上描述的不一样

2019独角兽企业重金招聘Python工程师标准>>> 如果两个对象根据equals()方法比较是相等的&#xff0c;那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。但是现实情况&#xff0c;两者产生的hashCode值却不相等求解&#xff1f; 转载于:htt…

海思3559A上编译OpenCV4.1.0源码操作步骤

1. 从https://github.com/opencv/opencv/releases 下载opencv源码opencv-4.1.0.zip并解压缩&#xff1b; 2. 修改最顶层的CMakeLists.txt&#xff0c;在第658行加入一条语句如下&#xff0c;fix error: pngstruct.h:30:18: fatal error: zlib.h: No such file or directory #i…

如何从零开始设计与开发一款通用模型预测调度系统 | 干货分享

机器学习模型从训练导出到生成环境部署&#xff0c;这个过程中涉及大量工作&#xff0c;会面临着各种问题与挑战&#xff0c;比如不断丰富的业务场景&#xff0c;系统需要负载种类繁多的神经网络&#xff0c;预测任务不均匀等&#xff0c;然而企业拥有的计算资源&#xff08;如…

闲来无事,总结 Xcode常用快捷键

IOS模拟器home键&#xff1a;Command Shift H 构建应用程序&#xff1a;Command &#xff0b; B 运行app&#xff1a;Command &#xff0b; R 清理工程&#xff1a;Command &#xff0b; Shift &#xff0b; K 打开Xcode首选项&#xff1a;Command &#xff0b; &#xff…

openNebulafrontEnd ComputeNode 配置记录

openNebulafrontEnd ComputeNode 配置记录 1,OpenNebula nfs(file system shared) for image datastore; openNebula computeNode挂载,如果frontend与compute在同一台服务器则不用做NFS shared; 3,make raw image 4,配置过程中配置服务器网桥报错 报错信息 bringing up interfa…

武汉大学提出ARGAN:注意力循环生成对抗模型用于检测、去除图像阴影 | ICCV 2019...

作者 | 王红成出品&#xff5c;AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09;【导读】如何去除一张图像中的阴影部分&#xff1f;在ICCV 2019会上&#xff0c;武汉大学的一篇论文针对这一问题提出了一种用于阴影检测和去除的注意循环生成对抗网络——ARGAN。论…

海思3559A上编译libyuv源码操作步骤

1. 下载libyuv源代码&#xff0c;执行&#xff1a; git clone https://chromium.googlesource.com/libyuv/libyuv 2. 通过CMakeLists.txt编译生成库&#xff0c;build.sh脚本内容如下&#xff1a; cmake \-DCMAKE_BUILD_TYPERELEASE \-DCMAKE_C_COMPILER/opt/hisi-linux/x86…

Cocoapods的安装以及使用(2)

Cocoapods 在网上看博客&#xff0c;看了好多次&#xff0c;都没有学会cocoapods&#xff0c;今天上午浪费了一上午的时间&#xff0c;终于算是学会了。其实也是很简单的。 iOS 新版 CocoaPods 安装流程 1.换掉现有Ruby默认源&#xff08;由于好多人都没有翻墙&#xff0c;所…

MyEclipse10中导入的jquery文件报错(出现红叉叉,提示语法错误)

为了做一个页面特效&#xff0c;导入了一个jquery文件&#xff0c;怎想&#xff0c;myeclipse竟然报错说是语法错误&#xff0c;但是这个js文件我是从官网上下载的&#xff0c;不应该出错才对&#xff0c;百度谷歌之后终于找到了解决办法&#xff1a; 选中报错的js文件&#xf…

海思3559A上编译libjpeg-turbo源码操作步骤

1. 从https://github.com/libjpeg-turbo/libjpeg-turbo/releases/tag/2.0.2 下载libjpeg-turbo 2.0.2版本&#xff1b; 2. 脚本build.sh内容如下&#xff1a; cmake \-DCMAKE_BUILD_TYPERELEASE \-DCMAKE_C_COMPILER/opt/hisi-linux/x86-arm/aarch64-himix100-linux/bin/aarc…

免费!这里有一份开发者进阶“宝典”求带走

作为开发者&#xff0c;无论是前端还是后端&#xff0c;做机器学习还是云计算、架构&#xff0c;保持对技术的敏感性都是非常重要的事。新技术一般发源于人力、财力等各方面资源都很雄厚的大厂&#xff0c;经过时间的沉淀逐渐成为技术主流。因此&#xff0c;从 BAT、Google、Fa…

使用终端建立自己需要工程

cocoapods应该是时下类库比较全面的三方管理工具&#xff0c;使用也特别方便&#xff0c;你只需将别人的类库在你的 podfile中 pod "SomeoneNameLibrary" 就可以在你的工程中使用这个库&#xff0c;作为一个开发者&#xff0c;你是否也想让别人在他的项目中 pod &quo…

1.0 封装后的布局成本

2019独角兽企业重金招聘Python工程师标准>>> 1.0.1 C语言中&#xff08;.c&#xff09; 1. 数据和函数是分开声明的&#xff0c;语言本身没有支持“数据和函数”之间的关联性。 2. 这种程序方法为程序性的&#xff0c;由一组“分布在各个以功能为导向的函数…

通过gdb core dump方法查看程序异常时的堆栈信息

在Linux下可通过core文件来获取当程序异常退出(如异常信号SIGSEGV, SIGABRT等)时的堆栈信息。core dump叫做核心转储&#xff0c;当程序运行过程中发生异常的那一刻的一个内存快照&#xff0c;操作系统在程序发生异常而异常在进程内部又没有被捕获的情况下&#xff0c;会把进程…

日均350000亿接入量,腾讯TubeMQ性能超过Kafka

整理 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;【导读】近日&#xff0c;腾讯开源动作不断&#xff0c;相继开源了分布式消息中间件TubeMQ&#xff0c;基于最主流的 OpenJDK8开发的Tencent Kona JDK&#xff0c;分布式HTAP数据库 TBase&#xff0c;企业级…

iOS应用版本更新(自动提醒用户)

在#import "AppDelegate.h" 文件中的application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions 方法中调用检测结果 获得发布版本的Version 比较当前版本与新上线版本做比较 UIAlertView代理方法

Bash Shell脚本编程-变量知识

Shell:GUI CLI提供交互式接口&#xff1a;提高效率命令行展开&#xff1a;~ &#xff0c;{}命令别名&#xff1a;alias命令历史&#xff1a;historyGlobbing&#xff1a;*&#xff0c;&#xff1f;&#xff0c;[]&#xff0c;[^]命令补全&#xff1a;$PATH指定的目录下路径补全…

FFmpeg中可执行文件ffplay用法汇总

从https://ffbinaries.com/downloads 下载最新的4.1版本的windows 64位FFplay。目前linux下的只有3.2版本的。FFplay是一个由FFmpeg和SDL库组成的简单媒体播放器&#xff0c;它主要用作各种FFmpeg API的测试。 通过执行以下命令将FFplay信息重定位到ffplay_help.txt文件中便于…

用Go重构C语言系统,这个抗住春晚红包的百度转发引擎承接了万亿流量

整理 | 夕颜出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;11 月 20 日&#xff0c;百度的万亿流量转发引擎 BFE 登上了 GitHub Trending Top 3&#xff0c;今日 Star 已突破 270。事实上&#xff0c;这个曾经抗住 2019 年春晚抢红包的转发引擎早已于 2019 年夏在 G…