Sunday, 3 July 2011

Android Adventures #2

Here are a few more issues I remember having while porting Fling to Android (Note: When I refer to java code, I mean Android Java-specific code):
  • Optimizing the code with the compiler (e.g., -O1/2/3) caused awful graphical corruptions (see http://i51.tinypic.com/raqogl.png for a sample). Solution: Use vs-android and enable compiler optimization flags for certain files you figure could have something to do with it (e.g., your texture loading / resource loading class. You may even have to split the offending files so you can keep non-OpenGL code optimized.
  • The Java VM has a limit of 16MB for the Java heap, while the native heap has no limit. That means that for any minimally sophisticated game out there, you'll have to use the Android NDK to make a native binary while using java to access the Android APIs.
  • The file management APIs don't have any way to get the size of a file or seek it. This is especially bad for Asset files, which are resource files bundled with your package, since those are encrypted. So you have to read each file you need in chunks until you get a EOF flag while reading. In fact, there is an available() method that will return the total amount of bytes you can read until it "blocks for more input". That means that it can return 0, 1, 1234, infinite, etc. See http://developer.android.com/reference/android/content/res/AssetManager.AssetInputStream.html#available%28%29
  • (@#2 and #3) Because of these two problems, when our native code needed to read a resource file, I had to read the file to a java byte[] array, pass it as a buffer to C++, and then use a hackish way to change our File class's implementation between a resource file and a regular file since regular files didn't need to go through Java.
  • Java code sometimes doesn't behave in a standard way. An example of this would be when I created a texture through Java and used glTexImage2D, so I wouldn't have to compile libPNG and zlib statically with our bin. It would always fail with an INVALID_VALUE error. The documentations say that INVALID_VALUE only happens when width or height are < 0. The very same code, in C++, worked with 0 GL errors.
  • I had to load the game in a separate thread, not only because it could take a significant amount of time (On debug anyway), but also because a black screen for more than a second can cause any user to quit the app. The fact that the OpenGL thread is not on the main thread due to the way the GLSurfaceView class works didn't help either. So I had to make a proxy to properly init textures, and to draw the loading screen.
  • (@ #4, #5, #6) Because of all this, I had to severely modify the way textures are loaded and ended up doing the following when a resource file was required:
Java reads multiple chunks into memory while keeping a huge-ass buffer (e.g., 5MB in size) to prevent reallocs (this happens with any resource file request)
Copy the resource data to another array to prevent potential overlaps of read/writes through threads (Only copies actual data, not the whole 5MB buffer)
Copy the java resource data array to a C++ array
Free the java resource data since memory is of the essence
Decode the resource data with libPNG
Save the bitmap data, verify whether we're starting up (e.g., not on a GL thread), if so, queue it for init, otherwise init it now
On the GL thread, at the beginning of a frame, for each texture queued up, init it.
  • The Emulator runs at nearly 1 to 10 FPS most of the time. On a Core i7 2.80GHz computer. In fact, it will not run Fling, and will in fact freeze (as in, actually freeze, not give a "App not responding" Android warning when an app doesn't respond) and/or crash.
  • Installing the app packages takes over 2 minutes on the emulator (60KB/s), while on device takes less than 20 seconds (1-3MB/s on any decent device, 4+MB on a Samsung Galaxy S makes it take less than 6 seconds).

    0 comments:

    Post a Comment