【问题标题】:Low frame rate when drawing full screen drawable on canvas在画布上绘制全屏可绘制对象时帧速率低
【发布时间】:2018-11-05 15:10:18
【问题描述】:

我正在开发的应用是 Flappy Bird 克隆。 我正在使用一个 surfaceView 对象,其中我有一个 gameThread 并且在它的 run 方法内部,我在画布上绘制了游戏的各种组件。

只要我只绘制 Rects 来表示对象,一切都会顺利进行,但是当我添加第一个 Drawables 时,我注意到平滑度有一点损失。如果我尝试将背景绘制为可绘制对象,游戏会遭受非常严重的帧速率损失。

我尝试了什么:

  • 使用 png 和所有不同类型的位图作为资产
  • 调整资源大小以完美适合画布,从而避免重新缩放

这些都没有任何实际影响。

基本上:

  • 如果我只使用drawRect:60fps
  • 如果我用 drawRect 绘制背面,而用 drawable.draw(canvas) 绘制其他组件:57fps
  • 如果我使用 drawable.draw(canvas) 绘制所有内容(包括背景):15fps

有点相关的代码:

public class CannonView extends SurfaceView
        implements SurfaceHolder.Callback {
    private CannonThread cannonThread; // controls the game loop
    private Drawable background;

    // constructor
    public CannonView(Context context, AttributeSet attrs) {
        super(context, attrs); // call superclass constructor
        getHolder().addCallback(this);
        background= ResourcesCompat.getDrawable(getResources(), R.drawable.background, null);
    }
    public void newGame() {
        background.setBounds(0,0, getScreenWidth(),getScreenHeight());
    }
    public void drawGameElements(Canvas canvas) {
        background.draw(canvas);
    }
    public void stopGame() {
        if (cannonThread != null)
            cannonThread.setRunning(false); // tell thread to terminate
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        if (!dialogIsDisplayed) {
            newGame(); // set up and start a new game
            cannonThread = new CannonThread(holder); // create thread
            cannonThread.setRunning(true); // start game running
            cannonThread.start(); // start the game loop thread
        }
    }
    private class CannonThread extends Thread {
        private SurfaceHolder surfaceHolder; // for manipulating canvas
        private boolean threadIsRunning = true; // running by default

        // initializes the surface holder
        public CannonThread(SurfaceHolder holder) {
            surfaceHolder = holder;
            setName("CannonThread");
        }

        // changes running state
        public void setRunning(boolean running) {
            threadIsRunning = running;
        }
        // controls the game loop
        @Override
        public void run() {
            Canvas canvas = null; // used for drawing
            while (threadIsRunning) {
                try {
                    // get Canvas for exclusive drawing from this thread
                    canvas = surfaceHolder.lockCanvas(null);
                    synchronized(surfaceHolder) {
                        drawGameElements(canvas);
                    }
                }
                finally {
                    if (canvas != null)
                        surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }
        }
    }

}

【问题讨论】:

  • 请在您的问题中添加drawGameElements代码,以便我们知道您的代码可以优化的部分。
  • 其实是包含在内的,你可以在第15行找到。我省略了除背景之外的元素的绘制,因为根据我的测试,它们只会造成一小部分的帧丢失。跨度>
  • 似乎有点奇怪。我建议您将Drawable 预渲染为Bitmap(参见here),然后使用Bitmap 进行渲染,因为很难知道DrawableonDraw() 中做了多少工作.
  • 这帮了大忙,游戏现在达到了 ~42 fps。尽管如此,对于一个总共只包含 5 个精灵的游戏来说,以 60fps 运行应该没有问题。
  • @fasiume 有几个想法:它可能在软件中进行 alpha 计算。 Bitmap.setHasAlpha(false) 可能会有所帮助。而且,专业人士可能会使用 OpenGL,以获得最大的精灵性能。除此之外,我想知道这是否是drawGameElements() 的时间和屏幕刷新率之间的交互。只是好奇,你是如何测量fps的?我假设您已经确定 drawGameElements() 每秒执行 42 次,这意味着从开始到结束大约需要 23 毫秒?

标签: android canvas background android-drawable frame-rate


【解决方案1】:

很明显,低帧率的主要原因是background.draw()。切换到Bitmap 在某种程度上改善了这一点,可能是因为它缓存了draw() 的输出,并且因为它可以与保证不需要缩放的Canvas 函数一起使用(例如,drawBitmap( Bitmap, float, float, Paint)

您还发现,切换到 RGB_565 作为中间格式可以大大提高性能,大概是因为它丢弃了 alpha。 (否则,我预计这会慢一些,b/c 格式必须转换回 RGBA_8888,因为它已被 blitted 到 SurfaceView。)

很明显,Android 不会让您超过 60fps。这几乎可以肯定是因为lockCanvas() 参与了一个限制绘制速率的triple buffering 方案,以防止您提交永远无法显示的帧(由于您的设备的固定屏幕刷新率为 60Hz)。

这就留下了一个问题,即为什么您没有获得完整的 60fps,而是接近它的东西。如果drawGameElements() 每次运行所需的时间相同,并且不到 16 毫秒,那么lockCanvas() 应该会限制您,并且不会丢帧(连续 60 fps)。似乎线程调度程序或某事存在故障,并且每隔一段时间,CannonThread 的执行速度不够快,无法在三重缓冲方案需要page-flip 之前提供帧。在这种情况下,必须将帧延迟到下一次屏幕刷新。您可以尝试增加CannonThread'sthread priority,删除drawGameElements() 中的任何额外处理,这些处理在CannonThread 上绝对不需要发生,或者关闭您设备上运行的其他应用程序。

如前所述,OpenGL 是为此类游戏获得最大精灵性能的标准方法,因为它能够将许多操作卸载到硬件上。您可能正在接近基于drawBitmap() 的游戏的性能极限。

【讨论】:

    猜你喜欢
    • 2011-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-10
    • 1970-01-01
    相关资源
    最近更新 更多