Straightforward use of the pseudo-code above means that we create new shader for each rendered shape. This works correctly, but is very time and memory consuming for large scenes. A good implementation should try to reuse the shaders. This can be achieved by keeping a pool of available shaders. For each newly created shader, we calculate a hash value (reflecting the whole shader configuration) and insert this shader into the pool. When a new shader is needed, we first look for it in the pool (using the hash value of the desired configuration), and if we find it — we reuse it (increasing the reference count). Only if the shader is not found, we create a new one. This is quite simple to do, and it provides a perfect sharing of shaders.
The hash value could be calculated based solely on the final string
of the shader.
However, this is too slow in our experience —
as it means that all the string operations have to be performed before
we even know the hash. It's much faster to calculate
the hash value looking at all the parameters that will affect the generated
shader source, including all the participating Effect
and ComposedShader
nodes, as well as the standard X3D lights sources, textures,
fog parameters and so on. Only when we really need to create a new shader,
then we calculate the actual shader source code and compile it.
This means that our pseudo-code gets a little more complicated:
most operations are in fact delayed. For example, at the first stage
we only iterate over the light sources to update the hash value and remember
the light source parameters. Later, if the actual source code is needed,
we actually construct the shader code using the remembered light source
parameters.
An additional advantage of the “pool of shaders” appears when a subset of the needed shaders is known in advance. For example, imagine an evening outdoor scene, with a storm in the distance. The lighting cracks the sky occasionally, making everything temporarily bright. In technical words, the scene has highly dynamic lighting, and the shaders for various lighting conditions must be swapped instantly, to keep the simulation smooth. In such case, an application may initialize the pool to contain some shaders with artificial non-zero use count. This way, some shader configurations are always kept initialized and ready to be used immediately.