Buffering Solutions on BlackBerry, Part 1


BlackBerry Games Development Challenges - Slide1

Slide 1

In 2008, RIM debuted the first of what is now an annual conference called the BlackBerry Developer’s Conference, or DEVCON for short. It was held in a Marriott in Santa Clara, California and was a relatively small show, however the who’s who of BlackBerry development made the trip and everyone raved about what an awesome show it was.

I, and my colleague Simon Dale, were invited to give a presentation on BlackBerry game development. The meat of the presentation was on buffering strategies for BlackBerry as graphics performance was (and still may be) the foremost roadblock to great games on BlackBerry. I thought it would be good to try to give that presentation again, here, so it’s available for more people to see. In this four part series of articles, I will cover the sections on buffering. We covered a few other topics in our presentation but this series of articles will focus on the buffering sections.

The powerpoint itself is attached to this post (link below) and there’s a lot of animations in it that really help explain some of the concepts so I strongly encourage you to download it and follow along as you read the post. I’ll include some images in the post but I took a lot of time to create instructional animations (though more so in the later slides I’ll attach in future posts) so I highly recommend you view it in PowerPoint. This post only covers up to Slide 7 in the presentation. Future posts will cover further topics on buffering as it’s not a trivial subject for a single post to adequately cover.

BlackBerry Game Development Challenges – Section 1

Buffering Strategies

Buffering screen painting is critical to achieving high enough frame rates in most games to make the experience acceptable to the player. If the game feels slow, doesn’t react quickly to user input, or is choppy, many players will be unhappy with your game. They will stop playing, ask for a refund, or have a bad impression of your brand making it more difficult to convince them to purchase your next game.

What do we mean by buffering?

BlackBerry Games Development Challenges - Slide 4

Slide 4

Effectively, buffering means to pre-render sections of the screen that are time intensive to draw, and then drawing the cache (or ‘buffer’) to the screen. To be advantageous, drawing the buffer to the screen must be faster than  re-rendering the cached area.

For example, in the slide to the right you can see a screenshot from an old Magmic game called ‘Raging Rivers’. In this game, you kayak down a river between gates as fast as you can — time is critical here. The player needs to be able to make fast adjustments to their kayak course and feel a sense of speed as the kayak barrels down the river.

The screen you see is actually composed of a number of smaller images called ’tiles’. Each tile represents a section of the screen and when the tiles are lined up correctly, they mesh well into what appears to be a continuous river environment.

Tiles are used instead of one huge image depicting the entire run of the river from start to finish because the size (file size and run-time memory requirements) of an image that large is way too big to fit into the memory limitations of cell phones. Modern high-end smartphones may be able to handle the memory requirements but there are many other reasons to use tiles in games as well (but that’s a whole other article). For now, we’ll accept that tiling is required to achieve the look we want in the memory limitations we have.

The first screenshot shown has a white grid overlaid on it to show where the tile breaks are. There are about 110 tiles that are required in this configuration to compose the full screen. If you were to draw each tile individually, it would take 110 calls to Graphics.drawBitmap to render the scene (plus the time to render the foreground on top of that). Each call to Graphics.drawBitmap has overhead that is repeated: the area of the screen is identified, the section of the tiles image to be drawn is identified, the Graphics object sets up its drawing region, some temporary variables are allocated, etc. When you make a lot of calls to the same method (in most cases, not just for a drawBitmap example) the time spent in the overhead of the calls relative to the time spent actually performing the action the call is making, becomes quite high.

If instead of looking at the screen as 110 small sections we look at is as 11 larger sections (but still much smaller than the full screen) we can see that the number of calls to Graphics.drawBitmap required to render the background drops by an order of magnitude.

This is our goal: use buffering strategies to buffer the tiles so that the time to render the entire scene on the screen takes much less time than individually painting each tile every frame.

Motivation

BlackBerry Games Development Challenges - Slide5

Slide 5

Why is buffering on BlackBerry so important? The attached slide (right) shows the results of running a couple of standard J2ME benchmarks on some of the popular (or new) phones on the market. This is circa 2008 remember, however the relative painting performance of BlackBerry still requires these buffering optimizations — as do many other devices.

What these numbers show is that the performance of the high-end J2ME devices (the Motorola and SonyEricsson) far exceeds even a modern BlackBerry like the BOLD (9000). Even the popular Windows Mobile device at the time (HTC Touch) had better J2ME performance than the BlackBerries — and they were not designed with J2ME performance in mind.

Customers don’t know, or want to know, the intricacies of why different devices have different performance. Most consumers see similarly positioned and priced devices in the stores, all promoted by carriers as new, hot, top or fast, and expect that they should play games and play them relatively well. Meeting customer expectations is critical for the long-term success of any games company. You want players to not only have a good experience with the one game they buy from you but also have them come back and buy more games from you.

Full Screen Image

So where do we start? The easiest, simplest buffer is to buffer and area the same size as the screen. Basically, you create an image (width and height the same as the screen) and draw all your static content onto it. Static content is the images and visuals that are not changing frame-to-frame. For example, in a card game, your table background, score and probably deck are most likely static frame-to-frame and only changing occasionally. In River Run, the forest and river don’t actually change each frame, just the kayaker and some foreground objects that animate.

Create a method that will re-paint all the static objects onto your buffer and also keep track of whether your buffer needs to be re-painted. When your buffer needs to be re-painted, it’s considered ‘dirty’; clearing and re-rendering all the static content will ‘clean’ it so it’s no longer dirty. See the example code snippet below.

Bitmap buffer = new Bitmap(screenWidth, screenHeight);
private boolean bufferDirty = true;

private void repaintBuffer() {
  synchronized (buffer) {
    // ... draw all the static content onto the buffer ...
    bufferDirty = false;
  }
}

public void setBufferDirty() {
  synchronized (buffer) { bufferDirty = true; }
}

The repaintBuffer method will trigger the entire buffer to be re-painted. setBufferDirty allows anyone to mark the buffer as dirty.

It’s critical to note the synchronization implemented above. If there is no synchronization, someone could mark the buffer as dirty during the actual repainting of the buffer, but since the repaintBuffer method is going to set the buffer as clean at the end of it, the extra setBufferDirty call would not actually trigger a repaint and then you’d be working with a dirty buffer and not know it. Also, since most of the time the event thread will be used (in a paint method or input handler) to trigger a repaint, you’re almost guaranteed that different threads will be reading and writing to the bufferDirty variable and therefore need be synchronized.

How do you know when to actually trigger the buffer to be repainted? That’s up to you as the game designer to understand what actions are going to potentially cause the static content on the buffer to change and therefore need to be repainted. Typically, input handling and the core game engine are the main sources for events that may cause the buffer to become dirty but it is up to you to figure this out in the context of your game and action on those events appropriately.

Buffering Bugs

It’s important to remember, when adding buffering to your application, that painting is no longer simple and deterministic. Just because you call paint, doesn’t mean the screen actually get all the updates done correctly unless you have correctly implemented paint. Typically, when adapting a game to use buffering, you’ll find bugs like:

  • the screen isn’t repainted even though your game model has changed. Generally this is caused because your previous rendering implementation re-drew the entire screen each time, dynamically, based upon the model data at the time of the paint call. Since you’re using buffering now, when the model changes, and it affects the display, you need to specifically set the buffer dirty or else you will not see the effect of your model changes.
  • you don’t see the effect of key presses (or input) on-screen. Just like above, because when everything is rendered dynamically, key presses that update the model have immediate effect on the dynamic rendering, but when you have a buffered solution, key presses that effect items that are drawn to the buffer need to explicitly invoke the call to set the buffer dirty.

When do you actually re-paint your buffer?

There are two main ways to implement your game that cause the buffer to be repainted. The first is fairly simple to add to any game that was painting everything dynamically.

// OPTION 1: Paint Method
public void paint(Graphics g) {
  if (buffer_is_dirty) { repaintBuffer(); }
  g.drawBitmap(0, 0, screenWidth, screenHeight, buffer, 0, 0);
  // ... draw all dynamic content ...
}

Each time the screen is painted, the paint method checks if the buffer has been marked as dirty. If it has, the buffer is repainted. Then, the entire buffer is drawn to the screen. Since the buffer is the same size as the screen, it’s anchored as the top-left corner (0,0) and the full image is painted, wiping off whatever else was on the screen.

After the buffer is painted, the dynamic content (usually the foreground, animations, etc.) is drawn on top.

This is easy to add in as you just replace the section of the paint method that was drawing all the static content with the above code block.

It’s good because:

  1. it guarantees that you always have clean paints. The buffer is always updated on the paint call immediately following any call to set the buffer as dirty.
  2. Different code blocks also do not have to communicate between each other to deal with buffer repaints; they are effectively aggregated by the paint method into one buffer repaint.
  3. You’re never drawing a dirty buffer to the screen.

It’s bad because of two main problems:

  1. The frame rate can get choppy. Some frames your entire buffer is re-drawn and some frames it’s not. This leads to variable timing on your frames. Sometimes, you won’t notice the occasional slow frame but often you’ll see the game stutter occasionally when the buffer gets dirty. Basically, either your frame paint time is the best possible (buffer is not repainted) or the worst possible (full buffer is repainted) so you will see the highest delta between frames when you get a buffer repaint.
  2. repaintBuffer() synchronizes for the duration of the time it takes to paint your buffer. If this time is short, no problem, but ideally you have long repaintBuffer times as that’s the whole point of buffering! Having a long synchronization in your paint method is bad because the BlackBerry will appear to freeze while the paint method is synchronized due to the paint call happening on the event thread. It is not recommended to synchronize at all on the event thread. You can see issues where the OS tries to paint things on-screen (dialogs, network notifications, etc.) that get messed up due to an excessively long synchronization.

The second way to trigger buffer repaints is by periodically checking for a dirty buffer in your game loop and triggering a re-paint there.

// OPTION 2: Game Run Loop
if (buffer_is_dirty) { repaintBuffer(); }

The major advantage of this is that your game engine can control exactly when the buffer is repainted and when the time to do the repaint is given. This means that your game engine can control when the frame rate suffers by putting off buffer repaints until the right times (maybe after animations are completed, for example) which can lessen the perceived impact of a slow frame. Since slow frames are most noticeable when there is movement on-screen, if you delay the buffer repaint until after any movement is complete, often users won’t even notice that that frame was slow.

The primary detriment to this method is that you can have dirty paints — the buffer could be painted to the screen even though it’s dirty because the paint method is not checking the clean/dirty status before painting to the screen. A lesser issue (generally), is that the updates to the buffer are limited to the speed of your game loop. Instead of, optionally, having every frame be able to have a buffer repaint, the repaint is limited to however fast your game loop is run. Usually this is not a problem but can be for cases where your game loop is not running at a high speed.

Handling key-input while buffering

Typically there are two ways to handle key input on mobile games, either record and queue up all the input events and handle them at a fixed rate in a game loop, or handle them immediately. I won’t delve into the advantages and disadvantages of each (that’s another whole separate topic) but just briefly touch on how buffering impacts them.

When key events are handed by a game loop at a fixed rate, generally the game is probably already designed for fixed-rate frames and having the game run loop deal with deciding when the re-paint the buffer is the correct choice. If this is the case, adding buffering and implementing it like Option 2 (above) should not cause any issues with this key handling method. Pressing many keys, or repeatedly pressing a key should not negatively impact on the frame rate in this design.

If key events are handled immediately by a game, adding buffering can have a significant impact on the game experience. Typically, to handle a key input in a game, a screen update is required. Moving a character around the screen, firing, etc. Buffering can have a major positive impact on the perceived response rate for the key input as lower frame painting times display the effect of the key input faster. However, if you are adding a lot of extra graphics because you have implemented buffering so that repainting your buffer takes a long time, the deviations in frame rate won’t just impact on the visuals but also on the response rate the user perceives. If the user presses left, left, left but the middle left takes extra time (due to a buffer re-paint) the user will feel like their input is stuttering — or even dropped if the event thread is too busy painting to deal with input. There’s also a problem that can occur if the user presses keys very quickly and repeatedly. If each press is triggering a buffer repaint, and those take a long time, your processor power will be sucked up painting your buffer, not running your game.

Key handling is important whether you’re buffering or not but sometimes improper key handling code gets exposed when buffering is added so make sure to take a deep look at how your key inputs are coded when you have various buffering solutions in place.

Summary

Buffering is critical to obtaining fast frame rates on BlackBerry (and many other platforms). This article is an introduction but there’s more to come. In my next article, I’ll talk about different buffer types (full screen is trivial by comparison), and later, how to combine buffering strategies together to enable some awesome effects.

Note: when I originally started writing these articles I did not expect to think of so much useful information that was not covered in the DEVCON presentation. Instead of writing a book, I have left out some details that I expect a competent developer should be able to figure out on their own after having read the articles. Please feel free to contact me with questions and I’ll answer them or clarify anything that’s not clear.

(Thanks to Simon Dale for working on the original presentation with me as well as reviewing my work here to make sure I didn’t miss anything!)

Advertisements
This entry was posted in Game Development and tagged , , , , , , , , , , , , . Bookmark the permalink.

2 Responses to Buffering Solutions on BlackBerry, Part 1

  1. Pingback: Buffering Solutions on BlackBerry, Part 2 | Bacon on the Go

  2. Pingback: Buffering Solutions on BlackBerry, Part 3 | Bacon on the Go

Comments are closed.