作为一款VR实时操作游戏App,我们需要根据重力感应系统,实时监控手机的角度,并渲染出相应位置的VR图像,因此在不同 Android 设备之间,由于使用的芯片组和不同架构的GPU,游戏性能会因此受到影响。举例来说:游戏在 Galaxy S20+ 上可能以 60fps 的速度渲染,但它在HUAWEI P50 Pro上的表现可能与前者大相径庭。 由于新版本的手机具有良好的配置,而游戏需要考虑基于底层硬件的运行情况。

如果玩家遇到帧速率下降或加载时间变慢,他们很快就会对游戏失去兴趣。
如果游戏耗尽电池电量或设备过热,我们也会流失处于长途旅行中的游戏玩家。
如果提前预渲染不必要的游戏素材,会大大增加游戏的启动时间,导致玩家失去耐心。
如果帧率和手机不能适配,在运行时会由于手机自我保护机制造成闪退,带来极差的游戏体验。

基于此,我们需要对代码进行优化以适配市场上不同手机的不同帧率运行。

所遇到的挑战

首先我们使用Streamline 获取在 Android 设备上运行的游戏的配置文件,在运行测试场景时将 CPU 和 GPU性能计数器活动可视化,以准确了解设备处理 CPU 和 GPU 工作负载,从而去定位帧速率下降的主要问题。

以下的帧率分析图表显示了应用程序如何随时间运行。

在下面的图中,我们可以看到执行引擎周期与 FPS 下降之间的相关性。显然GPU 正忙于算术运算,并且着色器可能过于复杂。

为了测试在不同设备中的帧率情况,使用友盟+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是否在不同机型的设备上都产生了极高的占用时间,此处采用了友盟+U-APM来检测用户在不同机型上的函数占用比例。

友盟+ 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

我们可以看到该方法在每次调用时都花费时间:

Method call summary Total(ms) Self(ms) Calls Method name 782 5 100 MyApp.MainActivity:Log (string,object[]) 775 3 100 Android.Util.Log:Debug (string,string,object[]) 634 10 100 Android.Util.Log:Debug (string,string)

在这里定位到我们的日志记录花费了大量时间,我们的下一步方向可能需要改进单个调用,或者寻求全新的解决方案。

log:alloc还让我们看到内存分配;日志调用直接导致了大量的不合理内存分配:

Allocation summary Bytes Count Average Type name 41784 839 49 System.String 4280 144 29 System.Object[]

硬件加速

最后尝试引入硬件加速,获得了一个新的绘图模型来将应用程序渲染到屏幕上。它引入了 DisplayList 结构并且记录视图的绘图命令以加快渲染速度。

同时,可以将 View 渲染到屏幕外缓冲区并随心所欲地修改它而不用担心被引用的问题。此功能主要适用于动画,非常适合解决我们的帧率问题,可以更快地为复杂的视图设置动画。

如果没有图层,在更改动画属性后,动画视图将使其无效。对于复杂的视图,这种失效会传播到所有的子视图,它们反过来会重绘自己。

在使用由硬件支持的视图层后,GPU 会为视图创建纹理。因此我们可以在我们的屏幕上为复杂的视图设置动画,并且使动画更加流畅。

代码示例:

// Using the Object animator view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 20f); objectAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { view.setLayerType(View.LAYER_TYPE_NONE, null); } }); objectAnimator.start(); // Using the Property animator view.animate().translationX(20f).withLayer().start();

另外还有几点在使用硬件层中仍需注意:

(1)在使用之后进行清理:

硬件层会占用GPU上的空间。在上面的 ObjectAnimator代码中,侦听器会在动画结束时移除图层。在 Property animator 示例中,withLayers() 方法会在开始时自动创建图层并在动画结束时将其删除。

(2)需要将硬件层更新可视化:

使用开发人员选项,可以启用“显示硬件层更新”。
如果在应用硬件层后更改视图,它将使硬件层无效并将视图重新渲染到该屏幕外缓冲区。

硬件加速优化

但是由此带来了一个问题是,在不需要快速渲染的界面,比如滚动栏, 硬件层也会更快地渲染它们。当将 ViewPager 滚动到两侧时,它的页面在整个滚动阶段会以绿色突出显示。

因此当我滚动 ViewPager 时,我使用 DDMS 运行 TraceView

,按名称对方法调用进行排序,搜索“android/view/View.setLayerType”,然后跟踪它的引用:

ViewPager#enableLayers(): private void enableLayers(boolean enable) { final int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { final int layerType = enable ? ViewCompat.LAYER_TYPE_HARDWARE : ViewCompat.LAYER_TYPE_NONE; ViewCompat.setLayerType(getChildAt(i), layerType, null); } }

该方法负责为 ViewPager 的孩子启用/禁用硬件层。它从 ViewPaper#setScrollState() 调用一次:

private void 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 旨在“使用动画属性将自定义转换应用于页面视图”(Source)。

基于我们的需求,只在渲染动画的时候启用硬件层,所以我想覆盖ViewPager 方法,但由于它们是私有的,我们无法修改这个方法。

所以我采取了另外的解决方案:在 ViewPage#setScrollState() 上,在调用 enableLayers() 之后,我们还会调用

OnPageChangeListener#onPageScrollStateChanged()

。所以我设置了一个监听器,当 ViewPager 的滚动状态不同于 IDLE 时,它将所有 ViewPager 的孩子的图层类型重置为 NONE

@Override public void onPageScrollStateChanged(int scrollState) { // A small hack to remove the HW layer that the viewpager add to each page when scrolling. 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); } }

这样,在 ViewPager#setScrollState() 为页面设置了一个硬件层之后——我将它们重新设置为NONE,这将禁用硬件层,因此而导致的帧率区别主要显示在 Nexus上。

作者:陈可心

如何用 GPU硬件层加速优化Android系统的游戏流畅度—应用性能管理相关推荐

  1. 如何用 GPU 硬件层加速优化 Android 系统的游戏流畅度

    作为一款VR实时操作游戏App,我们需要根据重力感应系统,实时监控手机的角度,并渲染出相应位置的VR图像,因此在不同 Android 设备之间,由于使用的芯片组和不同架构的GPU,游戏性能会因此受到影 ...

  2. Android App界面和流畅度优化

    Android App界面和流畅度优化 所谓界面和流畅度优化,就是尽可能多地消除用户可直接感知的.影响用户操作体验的bug 1.人为在UI线程中做轻微耗时操作,导致UI线程卡顿 人为避免一切耗时操作 ...

  3. android的ui怎么做到流畅,android提高UI的流畅度

    android提高UI的流畅度 Android中所有的界面绘制工作都是在UI线程中进行的,提高UI流畅度的最核心根本在于释放UI线程.即:不在主线程中做耗时的操作. 很多人都知道,耗时的操作要放到子线 ...

  4. 加速优化android应用

    加速你的Android应用 开发进阶 AndroidChina 2天前 247浏览 0评论 几周之前,我在Droidcon NYC上有过一次关于Android性能优化的演讲. 我在这个演讲中花费了大量 ...

  5. iOS性能优化系列篇之“列表流畅度优化”工具篇

    这一篇文章是iOS性能优化系列文章的的第二篇,主要内容是关于列表流畅度的优化.在具体内容的阐述过程中会结合性能优化的总体原则进行分析,所以建议大家在阅读这篇文章前先阅读一下上一篇文章:iOS性能优化系 ...

  6. 【Android性能】【流畅度】概念初识

    小菜鸡最近想在性能测试方面多了解一些,因此先了解了一下安卓性能中的流畅度相关概念,本文章纯理论概念,暂不涉及对应的代码机制,后面再慢慢补- 主要参考了以下两篇大佬文章,内容可能存在高度重复,仅作为学习 ...

  7. android 10 流畅,安卓10系统曝光:流畅度超越苹果?果粉:怕是活在梦中!

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 算起来,从第一代安卓系统也就是1.0到现在安卓9.0,已经过去了10年.那么你最早使用的安卓系统是哪个版本呢?又使用过哪几个安卓版本呢?都用上最新的安卓9 ...

  8. android动画帧率_Android流畅度之帧率

    背景:app改版,人为感受卡顿,需要客观数据支撑观点.故,搜索各种性能指标,并理解之.(这是一篇摘要文......) 首先,明确人为感受的性能不好属于下面哪种: 1. 响应时间,界面跳转后响应时间: ...

  9. android 手写 流畅,提高Android应用手写流畅度(基础篇)

    在使用android类的手写应用时,整体上都有这样一个印象:android的手写不流畅.不自然,和苹果应用比起来相差太远.本文结合作者亲身经历,介绍一下有效提高手写流畅度的几种方法: 1.未做任何处理 ...

最新文章

  1. jenkins添加git源码目录时报Error performing command错误
  2. 微软已经宣布自2009年4月14日起放弃对windows xp的主流支持
  3. 一天搞定CSS: 浮动(float)及文档流--10
  4. ASP.NET Web Pages:Chart 帮助器
  5. ubuntu中make头文件找不到_和平精英:游戏中找不到人怎么办?这些技巧帮你练出“火眼金睛”...
  6. bootstrap-table前端修改后台传来的数据重新进行渲染
  7. ios-团购代码的重构
  8. Chrome 为何会成功?
  9. oracle11g 日期型,Oracle11g: datetime
  10. 华为OJ-整形数组合并
  11. TVS管选型详细流程
  12. 解决 Elasticsearch 查询时 Fielddata is disabled on text fields by default 错误
  13. 美式口语发音技巧:《英美发音区别》
  14. 【聚英云农】无线节水灌溉系统应用智慧农业
  15. 00后必看:如何在互联网就业寒冬和疫情的双面夹击下找到心仪工作
  16. vyos-vyatta在ospf和bgp之间路由重发布使用 打tag形式进行路由过滤
  17. Flink China Meetup 资料整理
  18. 2017寒假作业 计科1501 李俊01
  19. vivo删除的便签怎么恢复,vivo手机便签恢复教学
  20. MDK中自动添加程序编译日期版本

热门文章

  1. python搭建流媒体服务器_Linux自建直播服务器一:使用Nginx+rtmp模块搭建流媒体服务器...
  2. 流媒体服务器在大屏系统,视频流媒体服务器
  3. mysql重装时没有权限_MySQL系列--1.安装卸载与用户权限管理
  4. 车联网Tbox智能车载终端基本功能介绍
  5. 九省联考2018 一双木棋
  6. 电脑桌面云便签怎么更改便签内容字体?
  7. 新版Maixhub部署(V831与K210)
  8. 【物联网无线通信技术】LoRa从理论到实现(SX1268)
  9. 「绿米服务商专访」黄身煌,重返20岁的自己
  10. Java8 之熟透 Lambda 表达式