在 canvas 上绘制全屏可绘制对象时帧率低
Low frame rate when drawing full screen drawable on canvas
我正在开发的应用程序是 Flappy Bird 的克隆版。
我正在使用一个 surfaceView 对象,其中我有一个 gameThread,在它的 运行 方法中,我在 canvas 上绘制了游戏的各个组件。
一切 运行 只要我只绘制 Rects 来表示对象,一切都很顺利,但是当我添加第一个 Drawables 时,我注意到平滑度有点下降。如果我尝试将背景绘制为 Drawable,游戏会遭受非常严重的帧率损失。
我尝试了什么:
- 使用 png 和各种位图作为资源
- 调整资产大小以完美适应 canvas,从而避免重新缩放
None 这有任何实际效果。
基本上:
- 如果我只使用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);
}
}
}
}
}
很明显,低帧率的主要原因是 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()
应该会限制你,并且永远不会丢帧(连续 60fps)。似乎线程调度器或其他东西中有一个 burble,而且每隔一段时间,CannonThread
执行得不够快,无法在三重缓冲方案需要 page-flip. In this event, the frame must be delayed until the next screen refresh. You might try increasing CannonThread's
thread priority 之前提供帧,删除drawGameElements()
中不需要在 CannonThread
上发生的任何额外处理,或关闭您设备上 运行 的其他应用程序。
如前所述,OpenGL 是为此类游戏获得最大精灵性能的标准方式,因为它能够将许多操作卸载到硬件。您可能正在接近基于 drawBitmap()
的游戏的性能极限。
我正在开发的应用程序是 Flappy Bird 的克隆版。 我正在使用一个 surfaceView 对象,其中我有一个 gameThread,在它的 运行 方法中,我在 canvas 上绘制了游戏的各个组件。
一切 运行 只要我只绘制 Rects 来表示对象,一切都很顺利,但是当我添加第一个 Drawables 时,我注意到平滑度有点下降。如果我尝试将背景绘制为 Drawable,游戏会遭受非常严重的帧率损失。
我尝试了什么:
- 使用 png 和各种位图作为资源
- 调整资产大小以完美适应 canvas,从而避免重新缩放
None 这有任何实际效果。
基本上:
- 如果我只使用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);
}
}
}
}
}
很明显,低帧率的主要原因是 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()
应该会限制你,并且永远不会丢帧(连续 60fps)。似乎线程调度器或其他东西中有一个 burble,而且每隔一段时间,CannonThread
执行得不够快,无法在三重缓冲方案需要 page-flip. In this event, the frame must be delayed until the next screen refresh. You might try increasing CannonThread's
thread priority 之前提供帧,删除drawGameElements()
中不需要在 CannonThread
上发生的任何额外处理,或关闭您设备上 运行 的其他应用程序。
如前所述,OpenGL 是为此类游戏获得最大精灵性能的标准方式,因为它能够将许多操作卸载到硬件。您可能正在接近基于 drawBitmap()
的游戏的性能极限。