Very weird texture memory problem on Android

So to give a brief overview:
We created a game utilizing the storyboard API. We have several scenes in the game, and each time we switch from one scene to another, we call storyboard.removeScene(scene). Technically, this should free all texture memory and Lua memory. This implementation works as we tested it on the iPhone 4, iPad 2, Mac/Win Simulators, as well as on a HTC Desire Z running Gingerbread, all built using 840.

So we release our game on Google Play and App Store, and started getting bad ratings and feedback because of a bug with assets not loading. We used an ASUS Transformer Prime running ICS to test our app and encountered a bug where some of our game assets do not load, causing the app to hang.

We fired up adb logcat, and discovered the following error:

E/dalvikvm-heap( 4768): Out of memory on a 16777232-byte allocation.  
I/dalvikvm( 4768): "GLThread 363" prio=5 tid=13 RUNNABLE  
I/dalvikvm( 4768): | group="main" sCount=0 dsCount=0 obj=0x410b1c18 self=0x1abd750  
I/dalvikvm( 4768): | sysTid=4784 nice=0 sched=0/0 cgrp=default handle=27830064  
I/dalvikvm( 4768): | schedstat=( 87021431000 11326609000 54061 ) utm=7239 stm=1463 core=0  
I/dalvikvm( 4768): at com.ansca.corona.NativeToJavaBridge.getBitmapAsset(NativeToJavaBridge.java:~  
798)  
I/dalvikvm( 4768): at com.ansca.corona.NativeToJavaBridge.callGetBitmapAsset(NativeToJavaBridge.ja  
va:1023)  
I/dalvikvm( 4768): at com.ansca.corona.JavaToNativeShim.nativeResize(Native Method)  
I/dalvikvm( 4768): at com.ansca.corona.JavaToNativeShim.resize(JavaToNativeShim.java:147)  
I/dalvikvm( 4768): at com.ansca.corona.CoronaRenderer.onSurfaceChanged(CoronaRenderer.java:37)  
I/dalvikvm( 4768): at android.opengl.derived.SwapGLSurfaceView$GLThread.guardedRun(SwapGLSurfaceVi  
ew.java:944)  
I/dalvikvm( 4768): at android.opengl.derived.SwapGLSurfaceView$GLThread.run(SwapGLSurfaceView.java  
:809)  
I/dalvikvm( 4768):  
I/System.out( 4768): getBitmapAsset: Ran out of memory in the Java VM loading an image (Assets/Backg  
rounds/robotbldg@2x.png) of size 2048x2048  
I/Corona ( 5969): Lua Runtime Error: lua\_pcall failed with status: 2, error message is:  
lid parameter passed to sprite.newSpriteSheetFromData(). Frame overlaps sprite sheet.  

First things that ran through our mind was that we had a Texture Memory leak somewhere, or that the asset in question was simply too big to be loaded into Texture Memory. However, further testing showed no memory leaks (both texture or otherwise).

Clearly, this should be the case as we always call a storyboard.removeScene() call when we switch scenes.

So we assumed the asset must be too big to be loaded into texture memory, but all our assets are within the 2048 X 2048 limit. We discovered this bug only occurs when you follow a certain sequence of steps. To elaborate, let me first describe the scenes we have build in the game.

For the purpose of this problem, we focus on 4 scenes in question:
mainmenu.lua - as the name suggests, the main menu scene
loadScene.lua - a transition scene to display a loading screen between scene loads
shop.lua - the shop and IAP scene for our game
game.lua - game scene proper, this is where the asset in question is loaded

When a user starts the game from scratch, and goes from main menu straight into the game, the storyboard scenes transition from:
main.lua -> loadScene.lua -> mainmenu.lua -> loadScene.lua -> game.lua,
we discovered that the assets would load without any errors or crashes, the game runs as intended.

However, if the user starts the game and goes from main menu to shop, then to the game, i.e.:
main.lua -> loadScene.lua -> mainmenu.lua -> loadScene.lua -> shop.lua -> loadScene.lua -> mainmenu.lua -> loadScene.lua -> game.lua,
the above error will occur.

We then assumed that shop.lua was the problem, but:
main.lua -> loadScene.lua -> mainmenu.lua -> loadScene.lua -> game.lua -> loadScene.lua -> mainmenu.lua -> loadScene.lua -> shop.lua -> loadScene.lua -> mainmenu.lua -> loadScene.lua -> game.lua
does not yield any errors.

Now since the game can run normally with assets loading, clearly the asset in question can be loaded into texture memory. If that’s the case, then why is this bug occurring only when we go from the shop scene to game scene from a fresh instance of the app?

TL;DR: we encounter an asset loading problem on certain Android devices, can’t figure out why it happens as it’s not due to texture memory leaks or the asset being too big, but more of the precedence of scenes we goto using the storyboard API. [import]uid: 108204 topic_id: 29558 reply_id: 329558[/import]

Bump for help. [import]uid: 108204 topic_id: 29558 reply_id: 118999[/import]

Can any of the Corona staff help us?

EDIT: Nevermind, latest daily build seems to have solved the problem. [import]uid: 108204 topic_id: 29558 reply_id: 120277[/import]

Hello TimeSpaceMagic,

You were not running out of texture memory. You were running out of memory on the heap, which is exactly what the log was telling you. Your app first needs enough memory on the heap to uncompress the image before submitting it to OpenGL. An Android app is only given a limited heap size and does not have full access to the device’s RAM. Worst case, the maximum heap size can be 16 MB on low-end Android devices such as the original Droid. A more modern Android device will typically have 48 MB.

What was happening here is that you were loading so many images at one time that the Java garbage collector could not clean up memory in time before allocating memory for the next image. So, you could have worked around this issue by distributing the load by loading a few images at a time via timer.

Now, what we did in the newest daily build to make image loading more reliable was to force garbage collection when an OutOfMemory exception occurs, and then attempt to load the image one more time. Unfortunately, your app will incur a performance penalty when this happens, but Corona has no means of predicting when this will happen. You can avoid it by distributing the load like I said above.

Anyways, I hope this helps! [import]uid: 32256 topic_id: 29558 reply_id: 120358[/import]

Hi Joshua, that makes sense now! Thanks for the advice. We’ll be implementing timer delays in our future games. That opens up the possibility of creating progress bars for loading scenes too. Should be interesting. [import]uid: 108204 topic_id: 29558 reply_id: 120457[/import]

Can any of the Corona staff help us?

EDIT: Nevermind, latest daily build seems to have solved the problem. [import]uid: 108204 topic_id: 29558 reply_id: 120277[/import]

Hello TimeSpaceMagic,

You were not running out of texture memory. You were running out of memory on the heap, which is exactly what the log was telling you. Your app first needs enough memory on the heap to uncompress the image before submitting it to OpenGL. An Android app is only given a limited heap size and does not have full access to the device’s RAM. Worst case, the maximum heap size can be 16 MB on low-end Android devices such as the original Droid. A more modern Android device will typically have 48 MB.

What was happening here is that you were loading so many images at one time that the Java garbage collector could not clean up memory in time before allocating memory for the next image. So, you could have worked around this issue by distributing the load by loading a few images at a time via timer.

Now, what we did in the newest daily build to make image loading more reliable was to force garbage collection when an OutOfMemory exception occurs, and then attempt to load the image one more time. Unfortunately, your app will incur a performance penalty when this happens, but Corona has no means of predicting when this will happen. You can avoid it by distributing the load like I said above.

Anyways, I hope this helps! [import]uid: 32256 topic_id: 29558 reply_id: 120358[/import]

Hi Joshua, that makes sense now! Thanks for the advice. We’ll be implementing timer delays in our future games. That opens up the possibility of creating progress bars for loading scenes too. Should be interesting. [import]uid: 108204 topic_id: 29558 reply_id: 120457[/import]

@ Joshua - I think we may be running into this issue again on Kindle Fire HD (just purchased yesterday). Basically, we have a 2048x2048 spritesheet. When we try to load the sheet via

foobar = sprite.newSpriteSheetFromData( hdFileName, coordinateData );  

We get the following errors and the app crashes. It looks like the same as the issue mentioned above. But I am surprised we are running into this issue given that we are running a brand-new Kindle Fire HD, and using the latest 2012.971 build.

If I should open a new thread for this, let me know.

[code]
D/dalvikvm( 9325): GC_FOR_ALLOC freed 1677K, 61% free 13215K/33799K, paused 13ms
I/dalvikvm-heap( 9325): Grow heap (frag case) to 28.966MB for 16777232-byte allocation
D/dalvikvm( 9325): GC_FOR_ALLOC freed <1K, 13% free 29599K/33799K, paused 13ms
D/dalvikvm( 9325): GC_CONCURRENT freed <1K, 13% free 29599K/33799K, paused 1ms+2ms
D/dalvikvm( 9325): GC_FOR_ALLOC freed 35K, 13% free 29567K/33799K, paused 14ms
I/dalvikvm-heap( 9325): Forcing collection of SoftReferences for 16777232-byte allocation
D/dalvikvm( 9325): GC_BEFORE_OOM freed 14K, 13% free 29553K/33799K, paused 20ms
E/dalvikvm-heap( 9325): Out of memory on a 16777232-byte allocation.
I/dalvikvm( 9325): “GLThread 621” prio=5 tid=13 RUNNABLE
I/dalvikvm( 9325): | group=“main” sCount=0 dsCount=0 obj=0x415a6b30 self=0x1748a18
I/dalvikvm( 9325): | sysTid=9341 nice=0 sched=0/0 cgrp=default handle=24312888
I/dalvikvm( 9325): | schedstat=( 0 0 0 ) utm=1203 stm=321 core=0
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.getBitmapAsset(NativeToJavaBridge.java:~1118)
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.callGetBitmapAsset(NativeToJavaBridge.java:1398)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.nativeRender(Native Method)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.render(JavaToNativeShim.java:137)
I/dalvikvm( 9325): at com.ansca.corona.Controller.onDrawFrame(Controller.java:247)
I/dalvikvm( 9325): at com.ansca.corona.CoronaRenderer.onDrawFrame(CoronaRenderer.java:74)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.guardedRun(SwapGLSurfaceView.java:964)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.run(SwapGLSurfaceView.java:825)
I/dalvikvm( 9325):
D/dalvikvm( 9325): GC_EXPLICIT freed <1K, 13% free 29553K/33799K, paused 2ms+3ms
D/dalvikvm( 9325): GC_FOR_ALLOC freed <1K, 13% free 29552K/33799K, paused 13ms
I/dalvikvm-heap( 9325): Forcing collection of SoftReferences for 16777232-byte allocation
D/dalvikvm( 9325): GC_BEFORE_OOM freed 0K, 13% free 29552K/33799K, paused 21ms
E/dalvikvm-heap( 9325): Out of memory on a 16777232-byte allocation.
I/dalvikvm( 9325): “GLThread 621” prio=5 tid=13 RUNNABLE
I/dalvikvm( 9325): | group=“main” sCount=0 dsCount=0 obj=0x415a6b30 self=0x1748a18
I/dalvikvm( 9325): | sysTid=9341 nice=0 sched=0/0 cgrp=default handle=24312888
I/dalvikvm( 9325): | schedstat=( 0 0 0 ) utm=1208 stm=321 core=0
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.getBitmapAsset(NativeToJavaBridge.java:~1125)
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.callGetBitmapAsset(NativeToJavaBridge.java:1398)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.nativeRender(Native Method)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.render(JavaToNativeShim.java:137)
I/dalvikvm( 9325): at com.ansca.corona.Controller.onDrawFrame(Controller.java:247)
I/dalvikvm( 9325): at com.ansca.corona.CoronaRenderer.onDrawFrame(CoronaRenderer.java:74)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.guardedRun(SwapGLSurfaceView.java:964)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.run(SwapGLSurfaceView.java:825)

[/code] [import]uid: 41124 topic_id: 29558 reply_id: 131837[/import]

@Josh BTW, we tested the same code on NOOK, and Nexus 7, and both appear to work just fine. So, I suspect this is really a Kindle Fire issue. Should we file a bug on this? [import]uid: 41124 topic_id: 29558 reply_id: 131840[/import]

@akao & @Josh this sounds exactly like the issue I reported seeing in this thread http://developer.coronalabs.com/forum/2012/11/17/build-971-candidate-new-public-release

Glad to see we’re not the only ones running into it. [import]uid: 135827 topic_id: 29558 reply_id: 131842[/import]

The log is clearly stating that you’ve ran out of memory. This suggests that the Kindle Fire HD provides a smaller heap to the app compared to other devices. Just so you know, an app does not get full access to the device’s RAM and is limited to a maximum heap size… and that maximum is set by the device manufacturer and can vary wildly. We can look into this, but don’t get your hopes up, because the OS is clearly refusing to give your app anymore memory.

Also, those “out of memory” exceptions don’t actually crash the app. We correctly catch those exceptions on our end and print them to the log. We then force garbage collection to clear memory in the heap (which you can see in your log), perform fallback solutions such as reduce color quality and down-sample the image to reduce the memory footprint, and then attempt to reload the image again. If the OS still refuses to give us the memory to load the image then we have no choice but to give up and return nil from functions such as display.newImage() and the like. The most likely cause of the crash is your code attempting to use the nil object returned by these functions.

In situations like this, the best solution is to not load all images on app startup. Instead, it is better to load images on demand, such as between screen transitions, and removing references to old images to free up memory.
[import]uid: 32256 topic_id: 29558 reply_id: 131863[/import]

Thanks for the response Joshua.

There definitely seems to be something wrong here though…

I spent today trying a bunch of things and here are my results…

  1. Set the spritesheet to be a power-of-two size. - Didn’t help.
  2. Increase the emulator heap size to 100MB. - This worked
  3. Try the Kindle Fire HD emulator (per-app heap size appears to be 64MB) - This worked
  4. Remove a large sprite with transparency from the spritesheet (but still maintain its size) - Didn’t help
  5. Removed our game loading scene which uses the same exact 2048x2048 spritesheet that fails later on. - Didn’t help
    6. Load a smaller sprite from the same spritesheet before trying to load a big sprite from the exact same spritesheet - This worked once, but now doesn’t work
    7. Load a different sprite from a smaller spritesheet before loading the big sprite from the large spritesheet. - This worked once, but now doesn’t work ??

#6 and #7 seems to be the strongest indicator that there is a Corona bug.

I captured a log of both the success and failure case in #7, with print statements indicating when each event took place. https://gist.github.com/2e5a316f9b8597e725cb [import]uid: 135827 topic_id: 29558 reply_id: 131876[/import]

I tried a bunch of other random things with no success.

Looking at the logs both akao and I posted, I noticed something a little strange. It looks like memory is allocated twice on the heap for each sprite (and our crashes happen on the second attempt to allocate)? Not sure if that’s normal, but thought I’d point it out just in case.

Edit: Tried a couple more things

  1. Reverting to Corona version 900. Android 4.1 - Worked
  2. Reverting to Corona version 868. Android 4.1 - Worked
  3. Tested on the HTC Droid Incredible. Android 2.2 - Worked. Also notably, did not seem to allocate memory twice when loading sprites. [import]uid: 135827 topic_id: 29558 reply_id: 131902[/import]

Thanks for the quick response Josh. Yes, the Kindle Fire HD is definitely running out of memory, which is surprising given that it’s a newer device. But to your second point, I believe the crash is actually happening within Corona or the underlying OS.

To clarify, we actually had 2 print statements, around the code that I sent above. It actually looks something like this:

print ("Mark 1");  
foobar = sprite.newSpriteSheetFromData( hdFileName, coordinateData );  
print ("Mark 2");  

The first print shows up right before the dump posted above, and the second print statement never shows up. So, the crash is actually within the newSpriteSheetFromData() call. My guess is that somehow the try/retry that Corona is trying to do isn’t working for Kindle specifically. When we try the same code on NOOK and Nexus 7 devices, it appears that the try/retry does work. No error.

In terms of work around, our only workaround is to not use the HD 2048x2048 spritesheet. We already delay loading assets in our app. But this one spritesheet seem to bust Kindle. I suppose we can try breaking the sheet up into smaller chunks and lazy load each chunk, but that would create a tons of additional work… [import]uid: 41124 topic_id: 29558 reply_id: 131924[/import]

@ Joshua - I think we may be running into this issue again on Kindle Fire HD (just purchased yesterday). Basically, we have a 2048x2048 spritesheet. When we try to load the sheet via

foobar = sprite.newSpriteSheetFromData( hdFileName, coordinateData );  

We get the following errors and the app crashes. It looks like the same as the issue mentioned above. But I am surprised we are running into this issue given that we are running a brand-new Kindle Fire HD, and using the latest 2012.971 build.

If I should open a new thread for this, let me know.

[code]
D/dalvikvm( 9325): GC_FOR_ALLOC freed 1677K, 61% free 13215K/33799K, paused 13ms
I/dalvikvm-heap( 9325): Grow heap (frag case) to 28.966MB for 16777232-byte allocation
D/dalvikvm( 9325): GC_FOR_ALLOC freed <1K, 13% free 29599K/33799K, paused 13ms
D/dalvikvm( 9325): GC_CONCURRENT freed <1K, 13% free 29599K/33799K, paused 1ms+2ms
D/dalvikvm( 9325): GC_FOR_ALLOC freed 35K, 13% free 29567K/33799K, paused 14ms
I/dalvikvm-heap( 9325): Forcing collection of SoftReferences for 16777232-byte allocation
D/dalvikvm( 9325): GC_BEFORE_OOM freed 14K, 13% free 29553K/33799K, paused 20ms
E/dalvikvm-heap( 9325): Out of memory on a 16777232-byte allocation.
I/dalvikvm( 9325): “GLThread 621” prio=5 tid=13 RUNNABLE
I/dalvikvm( 9325): | group=“main” sCount=0 dsCount=0 obj=0x415a6b30 self=0x1748a18
I/dalvikvm( 9325): | sysTid=9341 nice=0 sched=0/0 cgrp=default handle=24312888
I/dalvikvm( 9325): | schedstat=( 0 0 0 ) utm=1203 stm=321 core=0
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.getBitmapAsset(NativeToJavaBridge.java:~1118)
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.callGetBitmapAsset(NativeToJavaBridge.java:1398)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.nativeRender(Native Method)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.render(JavaToNativeShim.java:137)
I/dalvikvm( 9325): at com.ansca.corona.Controller.onDrawFrame(Controller.java:247)
I/dalvikvm( 9325): at com.ansca.corona.CoronaRenderer.onDrawFrame(CoronaRenderer.java:74)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.guardedRun(SwapGLSurfaceView.java:964)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.run(SwapGLSurfaceView.java:825)
I/dalvikvm( 9325):
D/dalvikvm( 9325): GC_EXPLICIT freed <1K, 13% free 29553K/33799K, paused 2ms+3ms
D/dalvikvm( 9325): GC_FOR_ALLOC freed <1K, 13% free 29552K/33799K, paused 13ms
I/dalvikvm-heap( 9325): Forcing collection of SoftReferences for 16777232-byte allocation
D/dalvikvm( 9325): GC_BEFORE_OOM freed 0K, 13% free 29552K/33799K, paused 21ms
E/dalvikvm-heap( 9325): Out of memory on a 16777232-byte allocation.
I/dalvikvm( 9325): “GLThread 621” prio=5 tid=13 RUNNABLE
I/dalvikvm( 9325): | group=“main” sCount=0 dsCount=0 obj=0x415a6b30 self=0x1748a18
I/dalvikvm( 9325): | sysTid=9341 nice=0 sched=0/0 cgrp=default handle=24312888
I/dalvikvm( 9325): | schedstat=( 0 0 0 ) utm=1208 stm=321 core=0
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.getBitmapAsset(NativeToJavaBridge.java:~1125)
I/dalvikvm( 9325): at com.ansca.corona.NativeToJavaBridge.callGetBitmapAsset(NativeToJavaBridge.java:1398)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.nativeRender(Native Method)
I/dalvikvm( 9325): at com.ansca.corona.JavaToNativeShim.render(JavaToNativeShim.java:137)
I/dalvikvm( 9325): at com.ansca.corona.Controller.onDrawFrame(Controller.java:247)
I/dalvikvm( 9325): at com.ansca.corona.CoronaRenderer.onDrawFrame(CoronaRenderer.java:74)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.guardedRun(SwapGLSurfaceView.java:964)
I/dalvikvm( 9325): at android.opengl.derived.SwapGLSurfaceView$GLThread.run(SwapGLSurfaceView.java:825)

[/code] [import]uid: 41124 topic_id: 29558 reply_id: 131837[/import]

@Josh BTW, we tested the same code on NOOK, and Nexus 7, and both appear to work just fine. So, I suspect this is really a Kindle Fire issue. Should we file a bug on this? [import]uid: 41124 topic_id: 29558 reply_id: 131840[/import]

@akao & @Josh this sounds exactly like the issue I reported seeing in this thread http://developer.coronalabs.com/forum/2012/11/17/build-971-candidate-new-public-release

Glad to see we’re not the only ones running into it. [import]uid: 135827 topic_id: 29558 reply_id: 131842[/import]

The log is clearly stating that you’ve ran out of memory. This suggests that the Kindle Fire HD provides a smaller heap to the app compared to other devices. Just so you know, an app does not get full access to the device’s RAM and is limited to a maximum heap size… and that maximum is set by the device manufacturer and can vary wildly. We can look into this, but don’t get your hopes up, because the OS is clearly refusing to give your app anymore memory.

Also, those “out of memory” exceptions don’t actually crash the app. We correctly catch those exceptions on our end and print them to the log. We then force garbage collection to clear memory in the heap (which you can see in your log), perform fallback solutions such as reduce color quality and down-sample the image to reduce the memory footprint, and then attempt to reload the image again. If the OS still refuses to give us the memory to load the image then we have no choice but to give up and return nil from functions such as display.newImage() and the like. The most likely cause of the crash is your code attempting to use the nil object returned by these functions.

In situations like this, the best solution is to not load all images on app startup. Instead, it is better to load images on demand, such as between screen transitions, and removing references to old images to free up memory.
[import]uid: 32256 topic_id: 29558 reply_id: 131863[/import]

Thanks for the response Joshua.

There definitely seems to be something wrong here though…

I spent today trying a bunch of things and here are my results…

  1. Set the spritesheet to be a power-of-two size. - Didn’t help.
  2. Increase the emulator heap size to 100MB. - This worked
  3. Try the Kindle Fire HD emulator (per-app heap size appears to be 64MB) - This worked
  4. Remove a large sprite with transparency from the spritesheet (but still maintain its size) - Didn’t help
  5. Removed our game loading scene which uses the same exact 2048x2048 spritesheet that fails later on. - Didn’t help
    6. Load a smaller sprite from the same spritesheet before trying to load a big sprite from the exact same spritesheet - This worked once, but now doesn’t work
    7. Load a different sprite from a smaller spritesheet before loading the big sprite from the large spritesheet. - This worked once, but now doesn’t work ??

#6 and #7 seems to be the strongest indicator that there is a Corona bug.

I captured a log of both the success and failure case in #7, with print statements indicating when each event took place. https://gist.github.com/2e5a316f9b8597e725cb [import]uid: 135827 topic_id: 29558 reply_id: 131876[/import]