Diligent VS OpenGL

A few weeks ago, working on a Diligent project, I needed something fairly common in graphics rendering that many people have run into: using multiple textures.

Traditional approach

If you have used OpenGL you probably think this is straightforward. Your textures end up as uniforms in the shader like this:

uniform sampler2D uDiffuseTexture;
uniform sampler2D uNormalTexture;
uniform sampler2D uRoughnessTexture;

To load the textures you use a decoding library like stb_image, then create the corresponding OpenGL texture with glGenTexture and the rest of the calls to upload it to the GPU and configure the sampler.

In the render loop you bind the textures each object needs before issuing the draw call. One draw call per object, each using whatever textures its material has assigned. Something like this:

ctx->useProgram(program_pbr);
for(Model& model : models) {
    ctx->bindMesh(model.mesh)
    ctx->bindTexture(model.material.diffuseTexture, DIFFUSE);
    ctx->bindTexture(model.material.specularTexture, SPECULAR);
    ctx->bindTexture(model.material.normalTexture, NORMAL);
    ctx->bindUniform(model.transform, MODEL_MATRIX);
    ctx->draw();
}

This has a few performance problems. Leaving aside the cost of uploading a texture to the GPU (multithreading or not), there is already a problem in the render loop itself: what if an object has several textures?

Batching and Instancing

It is common to batch objects into a single buffer, especially when they share the same vertex layout and are drawn at the same time, to avoid one draw call per object. But to do this we need a way to access the correct texture from the shader. The most common approach is a uniform array:

uniform sampler2D textures[200];

Then in the shader we just use the right index for the texture we want. There are several ways to pass that index, but one of the most common is a per-instance vertex attribute with a divisor, which lets us specify which texture each instance should use.

in (layout = 5) uint textureID;
out uint fsTextureID;

void main() {
  fsTextureID = textureID;
  ...
}
uniform sampler2D uTextures[200];
in uint fsTextureID;
int vec2 fsUV;
out vec4 color;
void main() {
    color = texture2D(uTextures[fsTextureID], fsUV);
}