user10778394
user10778394

Reputation:

Will any of the following texture lookups cause undefined behavior, non-uniform flow, or both?

I am drawing sprites to the screen using a vertex shader, fragment shader, and a small number of texture atlas (images that contain dozens of smaller sprites). I aim to use a single draw call for the entire scene so need to create a shader that can dynamically choose the texture based on an attribute. Each texture atlas is bound sequentially and a flat attribute textureid is sent to determine the texture to use, and the region of that texture is sent by uv.

The GLSL 3.30 specification says that arrays of samplers need a constant expression as an index, but the following compiles and links without error (on latest Nvidia driver):

#version 330

uniform sampler2D sampler[4];
in vec2 uv;
flat in int textureid;
out vec4 endcolor;

void main() {
    endcolor = texture(sampler[textureid], uv);
}

I can't guarantee this works on all hardware, and am confused as to why it linked without any warnings. I then decided to try the following instead:

void main() {
    if (textureid == 0) {
        endcolor = texture(sampler[0], uv);
    } else if (textureid == 1) {
        endcolor = texture(sampler[1], uv)
    } else if (textureid == 2) {
        endcolor = texture(sampler[2], uv);
    } else if (textureid == 3) {
        endcolor = texture(sampler[3], uv);
    } else {
        endcolor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

I learned that this will cause undefined behavior as it relies on non-uniform flow control. The textures that it sample depend on the input attribute. So, I then updated it to:

void main() {
    vec4 one = texture(sampler[0], uv);
    vec4 two = texture(sampler[1], uv);
    vec4 three = texture(sampler[2], uv);
    vec4 four = texture(sampler[3], uv);

    if (textureid == 0) {
        endcolor = one;
    } else if (textureid == 1) {
        endcolor = two;
    } else if (textureid == 2) {
        endcolor = three;
    } else if (textureid == 3) {
        endcolor = four;
    } else {
        endcolor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

Out of these three methods:

  1. Which ones cause undefined or erroneous behavior, which do not, and why?
  2. Are there any other methods that would be better (excluding sampler2DArray)?
  3. Why did the first method continue to compile, link, and work with no error?

I understand I could use a sampler2DArray but my images are likely to be of different sizes.

Upvotes: 1

Views: 783

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473457

void main() {
  endcolor = texture(sampler[textureid], uv);
}

That doesn't work in GLSL 3.30 period, because 3.30 does not allow you to index an array of opaque types by non-constant expressions. That NVIDIA's compiler allows this on some platforms is irrelevant: the spec says you can't do that.


void main() {
    if (textureid == 0) {
        endcolor = texture(sampler[0], uv);
    } else if (textureid == 1) {
        endcolor = texture(sampler[1], uv)
    } else if (textureid == 2) {
        endcolor = texture(sampler[2], uv);
    } else if (textureid == 3) {
        endcolor = texture(sampler[3], uv);
    } else {
        endcolor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

This is wrong as well, but for (slightly) different reasons. You access those within non-uniform control flow, which makes implicit derivatives undefined. The way to fix this is to get the derivatives before accessing the texture and then use textureGrad to pass them in:

void main() {
    vec2 uvDx = dFdx(uv);
    vec2 uvDy = dFdy(uv);
    switch(textureid) {
    case 0:
        endcolor = textureGrad(sampler[0], uv, uvDx, uvDy);
        break;
    case 1:
        endcolor = textureGrad(sampler[1], uv, uvDx, uvDy);
        break;
    case 2:
        endcolor = textureGrad(sampler[2], uv, uvDx, uvDy);
        break;
    case 3:
        endcolor = textureGrad(sampler[3], uv, uvDx, uvDy);
        break;
    default:
       endcolor = vec4(1.0, 1.0, 1.0, 1.0);
    }
}

Why did the first method continue to compile, link, and work with no error?

Because NVIDIA's gonna NVIDIA. They don't really care about making sure you're not accidentally using features you aren't supposed to be, or following the explicit wording of the specification.

Upvotes: 4

Related Questions