Loading textures asynchronously?

Hello!

My question is, how do I load textures asynchronously, in the background?

A bit of background info: I am writing an app that is essentially Gigapan in Panda. It displays massive panoramic images by chopping them up into little pieces and then loading only the necessary pieces depending on which portion of the panoramic that the user is viewing. This streaming happens in the background while the user is panning and zooming around the panoramic.

All this works pretty well, but there’s significant stutter whenever the app has to load new pieces of the panoramic.

I know that loader.loadModel has a callback parameter, but loader.loadTexture has no such parameter.

I’ve also tried loading textures in separate threads, but this still produces a stutter whenever textures have to load. (Yes, I have compiled Panda with HAVE_THREADS. Yes, Thread.isThreadingSupported() returns 1.) Perhaps this has something to do with python’s GIL?

For the record, in my thread I’m loading the texture like this:
tex = Texture()
tex.read(filename)

Any thoughts on how best to approach this problem?

Thanks!

Walt

both Texture.load() and Texture.read() support LoaderOptions but i’m not sure if any option there helps you.

How about preloading the textures in an AsyncTask (using AsyncTaskManager).

panda3d.org/manual/index.php/Threading

Tried using async tasks, but loading textures still causes a stutter in the main thread.

I’ve read that manual page thoroughly, and have tried all the relevant things there, including texture uploading and compiling Panda with threading support. It definitely feels like either Panda isn’t releasing the GIL during Texture.read() or it locks rendering during Texture.read().

Thanks!

Walt

Hmm, you’re right, Texture.read() will incorrectly keep the GIL held. I can put in a fix for that shortly.

But another approach is to wrap your texture in an egg file, for instance with egg-texture-cards, then load that egg file with load.loadModel(). This is actually the preferred way to load a texture anyway, since it preserves all of the texture flags like mipmapping and such.

In your case, I guess this may be a little clumsy, because you have to dynamically generate the pieces of the image that you are loading? You can do the still lower-level approach of reading it into a PNMImage first, then using tex.load() to copy that into the texture.

You also might try setting:

preload-textures 0

this will tell Panda not to actually load the texture image when you call texture.load(), but instead to load it when you render it (or prepare it) later. But that will still introduce a hitch at render time, so it doesn’t help unless you also create a “simple” version of the texture to be used as a standin at render time, with something like:

tex.setSimpleRamImage(image, 16, 16)

where image is a CPTAUchar that contains the 16x16x4 RGBA data for the simple image. Since you are generating your own images, you might already have this data. It doesn’t have to be 16x16; it could even be a 1x1 solid color image:

image = PTAUchar()
image.setData('\xff\xff\xff\xff')
tex.setSimpleRamImage(image, 1, 1)

When you have a “simple” image in memory, then the texture will be rendered with that image, while the actual image is fetched from disk in a sub-thread. A few frames may go by before the texture is finally rendered in its full glory, and the user will be seeing the simple image in the meantime.

Note that, for the record, the egg loader will automatically pre-compute the simple images for textures that come in via an egg file.

David

I’m having the identical issue, and tried all the same things the OP did. This is an older post so I’m curious if this fix made it into a release. I’m running 1.7.2 and it doesn’t seem fixed in that.
I was heading down that Async route but I will try a couple of the other “tricks” like eggs and loading as PNMImage in the meantime. (or instead of?)

S

Oops, it looks like I forgot to fix it, to my chagrin. I’ll do so now.

But even so, in 1.7.2 you’d have to use Panda threading anyway (not the Python threading module, which will cause crashes), unless you got the source and compiled it yourself as the OP said he did, and in 1.7.2 this also mean the “simple” threading model, which has its own limitations.

But the same advice applies: if you wrap your texture in egg-texture-cards and load the egg file, you can load it asynchronously successfully, and even simply use the callback function of loader.loadModel() which means you don’t have to manage your own threads. Furthermore, I recommend wrapping textures in egg files in any case even if you aren’t trying to load them asynchronously.

David

Cool. got it. For what it’s worth. Using PNMImage to read in the png and then Texture.load() to populate the texture made a 10x reduction in blocking time.
That said, recommend I use cards and I will. It will fit into my terrain workflow without fuss anyway so I might as well. Thanks for the help.