readImage limits?

i’ve been trying to build a large game, one with 2D characters that have lots of animations, which obviously means lots of image files (or large sprite sheet)

i use readImage(asset.name) and i’ve noticed that there appears to be a hard crash (from Codea) when exceeding a number of image assets

well, truly the limit appears to be a number of pixels read by the function, around 600million, once i get past this number the crash happens

i wrote a small test project here where you can reproduce the issue:
there are 3 assets included
1 a large high quality image around 570KB and 1260x1600 pix
2 a small reduced quality image around 40KB and 250x400 pix
3 a small compresses image around 9KB and 250x400 pix

you can swap out which type you’re testing in the draw and touched functions and touch the right side of the screen while running to load more copies

so is there a hardcoded reason this crashes at 600m pix? the file size appears to have no effect, both small images reach the same limit… is that the limit of runtime memory for readImage? i’m on iPad Air 4, I image it has much more memory than this…

@skar - just out of interest what is the memory size in you pad?

@skar - Codea crashed on my iPad Pro 256M at about 285 million. To achieve that I was using the largest sprite and stroking my finger up and down rather than tapping.

@skar I think you’re hitting the RAM limits of your device

Here are the actual “in memory” sizes for the images you describe:

1 a large high quality image around 570KB and 1260x1600 pix

  • 1260 * 1600 * 4 = 8 MB

2 a small reduced quality image around 40KB and 250x400 pix

  • 250 * 400 * 4 = 400 KB

3 a small compresses image around 9KB and 250x400 pix

  • 250 * 400 * 4 = 400 KB

600 million pixels = ~2.5 GB

It’s probably using enough ram that iOS is shutting it down.

It may be that you can’t keep everything in memory at once, and will need to cycle assets in and out as needed

@skar Assuming Codea is probably loading the images in an RGBA8 format, that’s 4 bytes per pixel. So 600 million pixels = ~ 2.4GB which would likely push Codea over iOS’s maximum memory threshold on a device with 4GB of RAM, leading iOS to kill the app with no warning.

Well, technically there is a warning when we approach the limit but it’s not bound to anything in Codea (https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623063-applicationdidreceivememorywarni).

Having dealt with this myself there’s actually no way that I’m aware of to detect when the app has crashed due to an Out-Of-Memory issue as iOS does not create a crash log or anything (it simply kills the process) so the best one can do is set a flag when the app is closed intentionally and check it when the app is launched.

@skar hmm also important to note the memory use above may actually be double what I quoted!

Because once you use readImage to get the image, it loads it into CPU memory, but then when rendering to the GPU it makes a copy of the image data. There are some tricks in the image type to copy only as needed, and to flush unused copies, but it’s possible that each image takes up more than I quoted

Thanks for the feedback! I’m glad you both chimed in.

This will take some thought on my part on how to best approach. My game goal is to basically be a side scroller rpg with fighting game mechanics. So my characters have a lot of combos and a huge number of frames. If a character has for example 50 moves, an average of 10 frames per move would be 500 images, and if a player can perform any combos at any point I need to have those images loaded up and ready to play.

A smaller image gives me more room for more frames but at the cost of HD quality. One thing I tried is to see if storing the image in localData or as a mesh.texture would help but no, data can only be a string or number so Image doesn’t work (and neither does trying to encode Image as json), and mesh.texture has the same memory band as Image so it will crash at the same point.

@skar I tried your code on my iPad Pro 1 12.9” with 128GB and I didn’t crash until just over 730 million. Tried it several times and it was always just over that value.

i found a work around for this,

basically instead of using readImage, I just don’t lol

mesh.texture accepts a direct asset reference like asset.name which i didn’t realize until testing this out more and more, and this doesn’t add to the memory consumption that readImage does,

the only draw back is that with readImage we can get the width and height of the image, without it i just have to add this data directly from a map which isn’t a big deal at all, i’ve already tested this out and loaded over 14billion pixels

i also included the mesh rendering to confirm it can actually draw the meshes, you can uncomment it in the draw function in this new version zip

@skar yeah this is a more optimal way to do it. readImage loads the image into RAM so you can access things like width, height, pixel data. If you just need to show it on the screen assigning to a mesh texture (or using sprite) will just hand off the asset to the GPU

@Simeon - does that mean images are read directly from storage memory and duplicated on the screen? Is the next bottleneck the GPU capability? Finally how will the introduction of metal affect graphic capability for the programmer?

Might a readImageInfo function be useful?

@Steppers thanks that works brilliantly
@Bri_G sorry i missed your first comment, the air 4 i have is 4GB RAM but i’m getting a 12.9” with 16GB soon! still i read that the pros even though they have 8 or 16 GB RAM, any single app can only use 5GB of ram on iPadOS, that’s probably why @dave1707 ’s pro crashed just above 750m with the extra 1GB of RAM

@RonJeffries @skar there’s a spriteSize() function

alas there appears to be a limit when using assets directly… they cannot be larger than something around 2025 (maybe 2048?) pixels in either direction, readImage doesn’t have this limit, the image can be huge in dimensions