there isn’t a function to proactively compile a shader

I was looking into some lag spikes in Ultraprocessor Ribbon that would only happen when something was being rendered in the scene for the first time. This was annoying, because the player could be in the middle of doing something when suddenly the game lags for a short moment.

Unfortunately, these issues were hard to debug on desktop, but I found out using Firefox’s profiling tools (which turned out to be quite nice to use, wow!) that the lag spike was being caused by shader compilation.

A picture of the firefox performance debugger at the moment of the lag spike. It shows the canvas renderer waiting on the native code, which is busy calling GL functions to compile a shader.

As it turns out, Godot compiles custom shaders just in time right before something using that shader for the first time needs to be drawn. Unfortunately, as far as I can tell, there is no way to proactively tell Godot to compile a shader. The only way, as documented in devlog 11 of Wrought Flesh, to get Godot to compile a shader is to render something onscreen for one frame.

After feverishly pulling random levers to find a solution, here’s the workaround I got:

## In Godot, shaders cannot be compiled ahead of time before they are needed;
## they are only compiled just in time. This causes lag when rendering things
## for the first time on slower platforms such as web.
##
## To get around this issue, we can force Godot to render something using the
## shader.
func force_compile(shader: Shader):
    var mat := ShaderMaterial.new()
    mat.shader = shader
 
    var node := TextureRect.new()
    node.material = mat
 
    # The shader won't be compiled if there isn't a texture because then there
    # won't be anything to render, so slap a dummy texture on there.
    node.texture = GradientTexture2D.new()
 
    # But we don't want to see this control, so it would be nice if we can make
    # it invisible. The shader won't be compiled if the texture is transparent,
    # presumably because Godot will cull it, so use a scale of zero instead as
    # this seems to circumvent the render culling.
    node.scale = Vector2.ZERO
    add_child(node)
 
    # We don't want to keep this in memory, but we do need it around long enough
    # for Godot to render it.
    to_unload.push_back(node)

This code runs for every shader in our game when a level is loaded. When the loading animation is done, we call queue_free on all of the nodes we created to force Godot to compile our shaders. This fixes the issue and now the game doesn’t lag in the middle of the game when something is added to to the scene for the first time.

If you know of a function in Godot that lets you explicitly compile a shader, please let me know! I haven’t been able to find it. Edit: I found it, it hasn’t been merged yet: https://github.com/godotengine/godot/pull/81496