clock

Caching Particle Materials In Godot

When using particles in Godot, it is a common problem to unintentionally introduce lag spikes in your game due to how shaders are compiled in the engine.

Before we jump into the solution, let's wrap our heads around what is causing this problem at a high level. The simple answer, as mentioned above, is shader compilation. But, if you're like me, you're probably wondering exactly what that means. So, to better explain what is going on, let's take a moment to understand what shader compilation is and why it is can cause performance issues.

In Godot, when you have an object in the game that uses a Particles2D node—the first time it is instanced in the scene and a particle is emitted—the material for that Particles2D node has to be compiled. When this compilation occurs, it will take whatever resources are necessary to complete the compilation and in doing so may cause the performance of the game to drop for a few moments. Once it has been compiled, any subsequent instances of those particles will not incur a performance hit.

The deeper technical reason behind this is beyond my understanding at the moment, but what I've gathered is that shader caching doesn't exist in OpenGL which is what Godot uses under the hood. So, to resolve the problem we will need to create a cache ourselves. We can then use this cache to give us control over when the compilation occurs and it will also keep the compiled materials in memory so they persist throughout the duration of the game.

Note: It has been mentioned that Vulkan has proper shader caching and will likely resolve this problem entirely. Vulkan is scheduled for the 4.0 release of Godot, so hopefully this solution won't be required for much longer, but until then this will be a simple and easy way to provide more control of when this compilation occurs.

Save Particle Materials To Files

Before we begin building the actual cache scene, we need to save all ParticlesMaterial instances to files. You are likely creating these in the Particles2D node, but by default they are stored within the scene itself. So, we just need to save these to their own files. You can do so by selecting the ParticlesMaterial in the Inspector to view the resource details inside of the UI and then selecting the "Save As..." option to save it to the file system.

For the purposes of this article, we're going to save two material files FirstParticles.material and SecondParticles.material. We will be saving these files to a new res://Materials/ directory, but you can save them wherever you would like.

Create A Cache Scene

Once we have our material files created, we can begin creating our scene that will act as our cache. Let's create a new scene and name it ParticlesCache and inside of it let's create a single CanvasLayer node.

Next, we'll need to add a script to the scene so we can programmatically load in our materials.

extends CanvasLayer

var FirstParticlesMaterial = preload("res://Materials/FirstParticles.material")
var SecondParticlesMaterial = preload("res://Materials/SecondParticles.material")

var materials = [
    FirstParticlesMaterial,
    SecondParticlesMaterial,
]

func _ready():
    for material in materials:
        var particles_instance = Particles2D.new()
        particles_instance.set_process_material(material)
        particles_instance.set_one_shot(true)
        particles_instance.set_emitting(true)
        self.add_child(particles_instance)

This script preloads our material files and then adds them to a local materials array. Once they are in the array, we can loop over them and then programmatically create new Particles2D nodes and add them to our scene.

For each of these Particles2D nodes, we are going to assign the process material, ensure the particles are only fired once, emit the particles, and then add it to our ParticlesCache scene as children to our root CanvasLayer node. Depending on the size of your particles, you may also want to set their position to ensure that they are displayed outside of the viewport.

AutoLoad The Scene

The final step is to add these newly created scene to our list of autoload scenes. Open up your project settings, select the "AutoLoad" tab, and then open the ParticlesCache.tscn file and click the "Add" button.

Now this scene will be added when the game is started and will persist throughout the lifetime of the execution. This is what keeps the particle material files compiled and loaded in memory to prevent further compilation.


I hope you have found this quick tip helpful. Depending on the device—these lag spikes caused by shader compilation can be extremely obtrusive. For example, my 2011 MacBook Pro will completely hang for 3 seconds when a particle is first emitted while my 2014 MacBook Pro only experiences a small stutter.

Since there is no safe assumption on what devices our games will be running on - we should always aim for the least common denominator and treat that as our target. If we can solve this solution for them, then we've solved it for everyone.