如果玩家遇到帧速率下降或加载时间缓慢的情况,他们很快就会对游戏失去兴趣。如果游戏耗尽电池或设备过热,我们也会失去长途旅行的玩家。如果提前预渲染不必要的游戏素材,会大大增加游戏的启动时间,导致玩家失去耐心。如果帧率无法适配手机,在运行过程中就会因为手机的自我保护机制而死机,导致游戏体验极差。
基于此,我们需要优化代码,以适应市面上不同手机的不同帧率。
所遇到的挑战
首先,我们使用Streamline获取Android设备上运行的游戏的配置文件,并可视化运行测试场景时CPU和GPU性能计数器的活动,以准确了解设备处理CPU和GPU的工作负载,从而定位帧率下降。主要问题。
下面的帧速率分析图显示了应用程序随时间的变化情况。
在下图中,我们可以看到执行引擎周期和FPS 下降之间的相关性。显然GPU 正忙于算术运算,而着色器可能过于复杂。
为了测试不同设备的帧率,采用Umeng+U-APM来测试不同机型上的延迟。发现在onSurfaceCreated函数中渲染时出现了卡顿。这证实了前面的分析,可以确定GPU在算术运算时出现了滞后:
由于不同的设备有不同的性能预期,因此每个设备都需要设置自己的性能预算。例如,给定设备中GPU 的最高频率,并给定目标帧速率,可以计算每帧GPU 成本的绝对限制。
数学公式: $ 每帧GPU 成本=GPU 最大频率/目标帧率$
CPU 到GPU 的调度存在一定的限制。由于调度限制,我们无法达到目标帧速率。此外,由于CPU-GPU 接口上的工作负载序列化,渲染过程异步发生。 CPU 将新的渲染工作排队,稍后由GPU 处理。
数据资源问题
CPU控制渲染过程并实时提供最新数据,例如每帧的变换和灯光位置。然而,GPU 处理是异步的。这意味着数据资源将被排队的命令引用并在命令流中停留一段时间。程序中的OpenGL ES 需要渲染以反映进行绘制调用时资源的状态,因此在引用资源的GPU 工作负载完成之前无法修改资源。
调试过程
我们已尝试编辑和优化引用资源的代码。然而,当我们尝试修改这部分内容时,就会触发创建这部分的新副本。这在一定程度上可以达到我们的目的,但是会产生大量的CPU开销。
因此,我们使用Streamline 来查明CPU 负载较高的实例。在图形驱动内部libGLES_Mali.so路径函数中,视图中看到占用时间极高。
由于我们希望适配不同手机上的不同帧率,因此我们需要了解libGLES_Mali.so在不同类型的设备上是否占用极高的时间。这里我们使用Umeng+U-APM来检测用户在不同机型上占用的功能比例。
通过Umeng + U-APM自定义异常测试,以下模型会导致libGLES_Mali.so使用率过高的问题。因此,我们需要根据底层硬件的运行来解决流畅度问题。同时,有不止一种型号存在问题。我们需要从内存层面入手,考虑如何调用较少的内存缓存区域并及时释放内存。
解决方案及优化
根据前面的分析,我们首先尝试优化缓冲区。单缓冲区解决方案• 使用glMapBufferRange 和GL_MAP_UNSYNCHRONIZED。然后使用单个缓冲区内的子区域构建旋转。这避免了对多个缓冲区的需要,但该解决方案仍然存在一些问题。我们仍然需要处理管理子区域依赖关系,这给我们带来了额外的工作。多缓冲区场景• 我们尝试在系统中创建多个缓冲区并以循环方式使用这些缓冲区。通过计算适合的缓冲区数量,代码可以在后续帧中重用这些循环缓冲区。由于我们使用了大量的循环缓冲区,因此需要进行大量的日志记录和数据库写入。但这里有几个因素导致性能不佳: 1. 产生额外的内存使用和GC压力2. Android操作系统实际上将日志消息写入日志而不是文件,这需要额外的时间。 3、如果只有一次调用,这里的性能消耗是最小的。然而,由于使用了循环缓冲区,这里需要多次调用。我们将在基于C#的Mono分析器中启用内存分配跟踪功能来定位问题:
$ adb shell setprop debug.mono.profile log:calls,alloc
我们可以看到该方法每次调用都需要时间:
方法调用汇总Total(ms) Self(ms) Calls 方法名称782 5 100 MyApp.MainActivity:Log (string,object[]) 775 3 100 Android.Util.Log:Debug (string,string,object[]) 634 10 100 Android.Util .Log:调试(字符串,字符串)
我们花了很多时间在这里找到我们的日志记录,我们的下一步可能需要改进单个调用,或者寻求一个全新的解决方案。
log:alloc还可以让我们看到内存分配情况; log调用直接导致大量不合理的内存分配:
分配摘要字节数平均类型名称41784 839 49 System.String 4280 144 29 System.Object[]
硬件加速
引入硬件加速的最后一次尝试,获得用于将应用程序渲染到屏幕的新绘图模型。它引入了DisplayList结构,记录了视图的绘制命令,以加快渲染速度。
同时,您可以将View渲染到离屏缓冲区并根据需要进行修改,而不必担心被引用。此功能主要适用于动画,非常适合解决我们的帧速率问题并允许我们更快地制作复杂视图的动画。
如果没有图层,动画视图将在更改动画属性后使其无效。对于复杂视图,此失败将传播到所有子视图,而子视图又会重绘自身。
使用硬件支持的视图层后,GPU 会为视图创建纹理。因此我们可以在屏幕上制作复杂视图的动画并使动画更加流畅。
代码示例:
在使用硬件层的时候还有几点还是需要注意的:
(1)使用后清理:
硬件层占用GPU上的空间。在上面的ObjectAnimator 代码中,侦听器会在动画结束时删除该图层。在属性动画器示例中,withLayers() 方法会在动画开始时自动创建图层,并在动画结束时删除它们。
(2)硬件层更新需要可视化:
使用开发人员选项,您可以启用“显示硬件层更新”。如果在应用硬件层后更改视图,它将使硬件层无效并将视图重新渲染到该离屏缓冲区。
硬件加速优化
但这带来了一个问题,在不需要快速渲染的界面中,例如滚动条,硬件层会将它们渲染得更快。当ViewPager 滚动到两侧时,其页面在整个滚动阶段以绿色突出显示。
因此,当我滚动ViewPager 时,我使用DDMS 运行TraceView,按名称对方法调用进行排序,搜索“android/view/View.setLayerType”,然后跟踪其引用:
ViewPager#enableLayers(): 私人无效enableLayers(布尔enable) { 最终int childCount=getChildCount(); for (int i=0; i childCount; i++) { 最终int layerType=启用? ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE; ViewCompat .setLayerType(getChildAt(i),layerType,null); } }
此方法负责启用/禁用ViewPager 子级的硬件层。它从ViewPaper#setScrollState() 中调用一次:
私有无效setScrollState(int newState) { if (mScrollState==newState) { return; } mScrollState=newState; if (mPageTransformer !=null) { enableLayers(newState !=SCROLL_STATE_IDLE); } if (mOnPageChangeListener !=null) { mOnPageChangeListener. onPageScrollStateChanged(newState); } }
如代码所示,当滚动状态为IDLE时硬件被禁用,否则当DRAGGING或SETTLING时启用。 PageTransformer 旨在“使用动画属性将自定义转换应用于页面视图”(来源)。
根据我们的要求,为了在渲染动画时仅启用硬件层,我想重写ViewPager 方法,但由于它们是私有的,我们无法修改此方法。
所以我采取了另一种解决方案:在ViewPage#setScrollState()上,调用enableLayers()后,我们还调用OnPageChangeListener#onPageScrollStateChanged()。因此,我设置了一个侦听器,当ViewPager 的滚动状态与IDLE 不同时,该侦听器将所有ViewPager 子级的图层类型重置为NONE:
@Override public void onPageScrollStateChanged(int scrollState) { //一个小技巧,用于删除viewpager 在滚动时添加到每个页面的HW 层。 if (scrollState !=ViewPager.SCROLL_STATE_IDLE) { Final int childCount=your_viewpager.getChildCount(); for (int i=0; i childCount; i++) your_viewpager.getChildAt(i).setLayerType(View.LAYER_TYPE_NONE, null); } }
原创文章,作者:xiaobian,如若转载,请注明出处:https://www.xinyuspace.com/5623.html
用户评论
清原
终于找到解决游戏卡顿的办法了!这篇文章简直是宝藏!
有5位网友表示赞同!
烟花巷陌
感谢分享,学习了利用GPU硬件加速优化游戏的方法,希望我的手机也能流畅运行游戏。
有6位网友表示赞同!
陌然淺笑
文章内容很实用,特别是讲解了GPU硬件层加速的概念,让我对手机游戏优化有了更深的理解。
有20位网友表示赞同!
哭着哭着就萌了°
文章中提到的方法值得尝试,希望能有效提升游戏体验。
有14位网友表示赞同!
最怕挣扎
Android游戏优化,GPU硬件加速是关键!
有14位网友表示赞同!
作业是老师的私生子
看完文章,感觉手机游戏性能提升了!
有5位网友表示赞同!
话扎心
原来GPU硬件层加速可以这么用!
有12位网友表示赞同!
ヅ她的身影若隐若现
强烈推荐这篇文章!
有10位网友表示赞同!
你tm的滚
手机游戏卡顿,快来看看这篇文章吧!
有17位网友表示赞同!
回忆未来
对游戏优化感兴趣的朋友,一定要看看这篇文章。
有20位网友表示赞同!
喜欢梅西
文章内容清晰易懂,实战性强。
有17位网友表示赞同!
雁過藍天
这篇文章为我们打开了通往流畅游戏体验的大门!
有11位网友表示赞同!
有些人,只适合好奇~
终于可以告别游戏卡顿了!
有11位网友表示赞同!
在哪跌倒こ就在哪躺下
感谢作者的分享,让我对GPU硬件层加速有了更深入的了解。
有6位网友表示赞同!
久爱不厌
这篇文章值得收藏,以后遇到游戏卡顿问题可以参考。
有11位网友表示赞同!
刺心爱人i
手机游戏优化,GPU硬件加速是必不可少的!
有15位网友表示赞同!
早不爱了
文章中提到的方法非常实用,可以有效提升手机游戏流畅度。
有18位网友表示赞同!
毒舌妖后
看了这篇文章,我对游戏性能优化有了新的认识。
有18位网友表示赞同!
月下独酌
这篇文章让我受益匪浅!
有20位网友表示赞同!
墨染天下
强烈推荐给所有手机游戏爱好者!
有12位网友表示赞同!