Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rendering a small region of large Bitmap is time consuming

I've got the following code.

this.getGame().getGraphics().drawBitmap(Assets.playScreen_LaserBeamAnimation, new Rect(0, 0, 100, 750), new Rect(0, 0, 100, 750), null);
this.getGame().getGraphics().drawBitmap(Assets.playScreen_LaserBeamAnimation, new Rect(0, 200, 10, 800), new Rect(0, 0, 200, 600), null);

The first render statement takes around 0.6 - 1 second to render. The second one around 1 millisecond.

The Bitmap is large: 968 KB and is loaded with the following code:

public Bitmap readAssetsBitmap(String filename) throws IOException {
        try {
            BitmapFactory.Options options = new BitmapFactory.Options(); 
            options.inPurgeable = true;
            Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options);
            if(bitmap == null) {
                WSLog.d(Game.GAME_ENGINE_TAG, this,"File cannot be opened: It's value is null");
                throw new IOException("File cannot be opened: It's value is null");
            }
            else {
                WSLog.d(Game.GAME_ENGINE_TAG, this,"File successfully read: " + filename);
                return bitmap;
            }
        } 
        catch (IOException e) {
            WSLog.d(Game.GAME_ENGINE_TAG, this,"File cannot be opened: " + e.getMessage());
            throw new IOException("File cannot be opened: " + e.getMessage());
        }

    }

I expect the reading of the image and setting the Bitmap to take some time, however rendering a small region of it should not take so long (and mysteriously it is only the first render call).

How do I avoid the first render to take so long?

NB: It does not have to do anything with the scaling (I believe).

like image 544
Luke Taylor Avatar asked May 10 '13 17:05

Luke Taylor


1 Answers

TL;DR Loading and rendering graphics is actually a relatively expensive set of (internal) operations, first loading into memory then into the graphics module of the ARM CPU when it is first rendered to screen. Further, the animated GIF is expanded into a set of images, one for each animation frame making it quite a bit larger than you may think. To improve speed, you might consider decreasing the animation size (pixels and number of frames), or forcing the GIF onto the graphics memory earlier to avoid the first-frame pause.


There is one heck of a lot of things Android has to do internally to render game graphics, especially if you have a GIF animation:

  • Load the image from (relatively slow) RAM
  • Unzip the file
  • Map the 256 colour model into RGBA space
  • Generate each frame of the animation from the image deltas in the GIF file
  • Create a thread with a timer to switch between the different frames of the GIF

Now we have an image (actually a set of frames) in the CPUs memory. Note that a very small GIF with a lot of frames can become a very large in memory - Android has to have a full-sized image for each frame in your GIF. Also the 256 bit colour model is expanded into RGBA internally, so you have 4 bytes per pixel.

Take for example a GIF (128x128 pixels) with a small shape animated on it (32x32 pixels). Let's further suppose you have 100 steps in the animation - this might be 10k or so depending on how simple the background is. However, expanded in memory, each frame in the animation is 4x128x128 = 64kB. You have 100 frames, so your in-memory size is 6MB.

Next:

  • If using 3D, we have to compile the image into the graphic module's internal texture format
  • Stream the data into the graphics memory (which takes a surprisingly long time - this is why most 3D games have those "loading..." pages in between game segments)
  • Change the images from the graphic module's format to the colour depth of the actual screen (eg RGB format has a 24-bit colour but often the LCD display has 18 or 20 bit colour depth. The graphics card has to convert the image and typically introduces dithering to simulate the additional colours)

Once the image (which is actually a bunch of frames) is loaded into the graphics module, then things get fast.

OK, so what can you do about it? Here are some suggestions:

  • Reduce the pixel size of the GIF, or reduce the number of frames in the animation
  • Pre-loading
  • Tiling the image if large

Reducing the size of the GIF could reduce the memory usage significantly, and that will reduce the time Android takes to push the frames into the graphics module and the cost to compile into textures. The downside to that is you might not have the authority to modify the graphics.

Pre-loading pushes the image into the graphics module earlier by forcing a render during the game loading phase. The image might be off-screen (setting the window as hidden) or behind a top-layer by setting a low Z-order or in a 1 pixel region.

Tiling is discussed in my answer to a similar question). Take Google Earth as the most extreme example - this uses tiles (at any given resolution) for every area on earth. To render just the bit you are interested in, Google Earth has to load only the tiles that are to be shown. Because the tiles are small, they load fast. However, in your case you may have a GIF animation, so that's not going to work. Tiling an animation might not be practical or might lead to artefacts or delays in rendering portions of the image.

like image 177
Andrew Alcock Avatar answered Oct 08 '22 05:10

Andrew Alcock