Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android: Low FPS drawing many bitmaps in a surfaceview

I'm trying to create a bullethell game and Ive run into a bit of a trouble. I can't get more than 17 fps after about 500 bullets. The update logic code takes around 1-4ms for all of them while the render code takes around 40ms

For now my code is

private void drawEntities(Canvas canvas) {
    for (HashMap<UUID, Spatial> h: spatialList) {
        for (Spatial spatial: h.values()) {
            spatial.render(canvas);
            if(spatial.life > 0)
                spatial.life--;
            else if (spatial.life == 0)
                engine.deleteEntity(spatial.owner);
        }
    }
}

spatialList is an arrayList where each index is a zLevel

The spatial which displays the actual bullet is

public void render(Canvas canvas) {
    float angle = (float) (vel.getAngle() * (180 / Math.PI));
    matrix.reset();
    matrix.setTranslate(pos.x - bullet.getWidth() / 2, pos.y - bullet.getHeight() / 2);
    matrix.postRotate(angle + 90,  pos.x, pos.y);
    canvas.drawBitmap(bullet, matrix, paint);
    canvas.drawCircle(pos.x, pos.y, col.getRadius(), paint);
}

I can provide more code but these seem to be the main issue. I've tried everything I can think of and can't find much else online. The only thing I can think of to fix this is to switch from a surfaceview to a GLSurfaceview but I really think there is a better way and I'm just using bad code.

Edit: I noticed my timer was off and removed the drawcircle and after running it again I get 40ms~ around 500 which is still a bit too low for reasonable performance.

TLDR; 500 entities = 17 fps.

like image 559
Metalith Avatar asked Mar 16 '23 22:03

Metalith


2 Answers

You may be limited by pixel fill rate. How large (in pixels) is the display on your test device?

One simple thing to play with is to use setFixedSize() to reduce the size of the SurfaceView's Surface. That will reduce the number of pixels you're touching. Example here, video here, blog post here.

It's generally a good idea to do this, as newer devices seem to be racing toward absurd pixel counts. A full-screen game that performs all rendering in software is going to struggle on a 2560x1440 display, and flail badly at 4K. "Limiting" the game to 1080p and letting the display scaler do the heavy lifting should help. Depending on the nature of the game you could set the resolution even lower with no apparent loss of quality.

Another thing to try is to eliminate the drawBitmap() call, check your timings, then restore it and eliminate the drawCircle() call, to see if one or the other is chewing up the bulk of the time.

You may find switching to OpenGL ES not so bad. The "hardware scaler exerciser" activity in Grafika (from the video linked above) shows some simple bitmap rendering. Replace drawCircle() with a scaled bitmap and you might be most of the way done. (Note it uses SurfaceView, not GLSurfaceView, for GLES in that activity.)

like image 126
fadden Avatar answered Mar 18 '23 12:03

fadden


I had the same issue. The problem will be solved to a large extent if you do all the drawing on a bitmap framebuffer and then draw the framebuffer to the canvas. If you are directly drawing on the canvas then there are several overheads. I looked around and found this tutorial "http://www.kilobolt.com/day-6-the-android-game-framework-part-ii.html"

Look at the implementation for AndroidGraphics and AndroidFastRenderView and see how he uses AndroidGraphics to do all the actual drawing in a buffer and paints that buffer to the canvas in AndroidFastRenderView.

like image 32
Aseem Vyas Avatar answered Mar 18 '23 10:03

Aseem Vyas