Asynchronous Loading

We’ve seen a basic way to load models in Model Files using loader.loadModel(). The major problem with this call is that it blocks the main thread while the model is being loaded, which means that all other tasks on the main thread (including Panda’s rendering task) are blocked until the model has finished loading. This is noticeable by the user as a jarring lag, especially when the application freezes for longer periods of time.

Clearly, this does not provide a good user experience. Therefore, it is recommended that models are loaded in an asynchronous manner, in a separate thread of execution, so that the application can continue rendering while the load operation occurs in the background. Panda3D provides several ways of doing so.

Loading in a coroutine

A convenient way to do this would be by using Coroutines, introduced in C++20. These are special functions that can be suspended temporarily and resumed at a later point (pending the completion of an asynchronous operation). Instead, we could write our code as though it were synchronous, but we insert the co_await keyword where we want the task to be suspended while waiting for the following operation.

Unfortunately, as of Panda3D 1.10, this feature of C++20 is not yet supported by Panda3D. If you are feeling adventurous, see this forum thread for a way to use C++20 coroutines with the Panda3D task system:

https://discourse.panda3d.org/t/using-c-20-coroutines-with-panda3d/27323

Loading in a thread

Alternatively, it is possible to use a separate thread to initiate the model load. Panda3D’s scene graph is thread-safe and can safely handle model operations from any thread. See the Threading page for more details.

One thing to note is that you may want to make sure that you complete all model operations (positioning, material assignments, etc.) before attaching it into the scene graph. Otherwise, if Panda3D happens to render a frame in between those calls, there is a chance that the model may briefly appear in its original state.

On-demand texture loading

In addition, you can further ask textures to be loaded to the graphics card asynchronously. This means that the first time you look at a particular model, the texture might not be available; but instead of holding up the frame while we wait for it to be loaded, Panda can render the model immediately, with a very low-resolution version of the texture or even a flat color, and start loading of the full-resolution version in the background. When the texture is eventually loaded, it will be applied. This results in fewer frame-rate chugs, but it means that the model looks a little weird at first. It has the greatest advantage when you are using lazy-load textures as well as texture compression, because it means these things will happen in the background. Use these configuration options to enable this behavior:

preload-textures 0
preload-simple-textures 1
simple-image-size 16 16
compressed-textures 1
allow-incomplete-render 1

When converting models to .bam with preload-simple-textures active, simple textures will be baked into the model, so that Panda (starting with version 1.10.11) doesn’t need to load the textures from disk at all until they first come into view.

To test this process, you can set async-load-delay with a value in seconds, which artificially delays each individual texture load by the given amount. This is useful for simulating the user experience on older computers with slower hard drives. Set it to a value like 0.1 and you should see the textures pop in as you move around the scene.

You can use set_texture_reload_priority() if you want ensure that textures in some scenes are loaded with higher priority than other scenes.

Animation loading

A similar behavior can be enabled for Actors, so that when you have an Actor with a large number of animations (too many to preload them all at once), you can have the Actor load them on-demand, so that when you play an animation, the animation may not start playing immediately, but will instead be loaded in the background. Until it is ready, the actor will hold its last pose, and then when the animation is fully loaded, the actor will start playing where it would have been had the animation been loaded from the beginning. To make this work, you have to run all of the animations through egg-optchar with the -preload option, and you might also want to set:

allow-async-bind 1
restore-initial-pose 0

Configuration

All of the above asynchronous operations will take place on a separate task chain, automatically created by Loader. By default, one low-priority thread is created to serve these requests. To increase the number of available threads, or to increase their priority, these configuration variables can be changed:

# default is 1
loader-num-threads 2
# default is low
loader-thread-priority normal