I have a rendering system that I use for drawing height maps. The system uses triangle strips of indexed verts in a vao. The system has worked fine on the 3 Nvidia cards I have tried it on across several versions of windows and OSX. I don't own any Systems with an ATI card but I sent the application to a colleague to look at for me, and on his machine no output is produced in the 3d viewport. He has a Radeon HD 7800 and is using windows 10 which is one of the operating systems I have tried it on with an Nvidia card. Running OpenGL extensions viewer indicates that his card supports a modern range of capabilities up to GL 4.5.

I attached an opengl debug callback. The shader program compiles fine and the vao is created without errors. There are no errors on draw either. The shaders use #version 330 core but I tried dropping down to #version 130 and simplifying the shaders to basically output the vertex position and a flat color, and it still works fine on nvidia but outputs nothing on ATI, so I doubt the shaders are the issue.

I suspect it has something to do with the way the vao is set up or possibly some feature that is missing in ATI drivers or hardware.

The following two blocks of code are in Kotlin using LWJGL3. The relevant GL calls are all the same as from Java though. They are a very tiny wrapper to the C calls.

The vao gets set up like this:

vao = glGenVertexArrays()

if (vao > 0) {

    val stride = floatsPerVertex * 4

    val vbo = glGenBuffers()
    val ibo = glGenBuffers()

    if (vbo > 0 && ibo > 0) {
        glBindBuffer(GL_ARRAY_BUFFER, vbo)

        glBufferData(GL_ARRAY_BUFFER, heightMapVertexData, GL_STATIC_DRAW)

        if (positionAttribute.location >= 0) {
            glVertexAttribPointer(positionAttribute.location, 2, GL_FLOAT, false, stride, 0)

        if (uvAttribute.location >= 0) {
            glVertexAttribPointer(uvAttribute.location, 2, GL_FLOAT, false, stride, 8)

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo)

        glBufferData(GL_ELEMENT_ARRAY_BUFFER, heightMapIndexData, GL_STATIC_DRAW)

    } else {
        throw RuntimeException("error setting up buffers")

} else {
    throw RuntimeException("error generating vao")

A draw call does this:

glClearColor(background.r, background.g, background.b, background.a)
glScissor(xPosition, flippedY, width, height)
glViewport(xPosition, flippedY, width, height)
glUniformMatrix4fv(mvMatrixUniform.location, false, mvMatrix.get(0, floatBuffer))
glUniformMatrix3fv(nMatrixUniform.location, false, normalMatrix.get(0, floatBuffer))
glUniformMatrix4fv(mvpMatrixUniform.location, false, mvpMatrix.get(0, floatBuffer))
glUniform3f(lightDirectionUniform.location, lightDirection.x, lightDirection.y, lightDirection.z)
glUniform4f(color1Uniform.location, 0.157f, 0.165f, 0.424f, 1.0f)
glUniform4f(color2Uniform.location, 0.459f, 0.761f, 0.859f, 1.0f)
glUniform4f(color3Uniform.location, 0.353f, 0.706f, 0.275f, 1.0f)
glUniform4f(color4Uniform.location, 0.922f, 0.922f, 0.157f, 1.0f)
glUniform4f(color5Uniform.location, 0.835f, 0.176f, 0.165f, 1.0f)
glUniform4f(color6Uniform.location, 0.955f, 0.955f, 0.955f, 1.0f)
glUniform4f(ambientUniform.location, 0.1f, 0.1f, 0.1f, 1.0f)
glUniform4f(diffuseUniform.location, 0.6f, 0.6f, 0.6f, 1.0f)
glUniform4f(specularUniform.location, 0.85f, 0.85f, 0.85f, 1.0f)
glUniform1f(shininessUniform.location, 1.7f)
glUniform1f(heightScaleUniform.location, heightMapScaleFactor.value)
glUniform1f(uvScaleUniform.location, heightMap.width / textureResolution)
glUniform1i(heightMapTextureUniform.location, 0)
glBindTexture(GL_TEXTURE_2D, textureId)
glDrawElements(GL_TRIANGLE_STRIP, indexCount, GL_UNSIGNED_INT, 0)

Here are the shaders for what they're worth even though reducing these to #version 130 doesn't help and it doesn't work with or without the commented code on ATI. Nvidia runs all variations. The shaders themselves don't appear to have any impact on the issue.


#version 330

uniform mat4 modelViewProjectionMatrix;
uniform float heightScale;
uniform sampler2D heightMapTexture;

in vec2 position;
in vec2 uv;

out VertexData {
    vec2 position;
    vec2 uv;
} VertexOut;

void main () {
    VertexOut.uv = uv;
    VertexOut.position = position;
    float height = texture(heightMapTexture, uv).r * heightScale;
    gl_Position = modelViewProjectionMatrix * vec4(position, height, 1.0);


#version 330

uniform mat4 modelViewProjectionMatrix;
uniform mat4 modelViewMatrix;
uniform mat3 normalMatrix;
uniform vec3 lightDirection;
uniform vec4 color1;
uniform vec4 color2;
uniform vec4 color3;
uniform vec4 color4;
uniform vec4 color5;
uniform vec4 color6;
uniform vec4 ambientColor;
uniform vec4 diffuseColor;
uniform vec4 specularColor;
uniform float shininess;
uniform float heightScale;
uniform float uvScale;
uniform sampler2D heightMapTexture;

in VertexData {
    vec2 position;
    vec2 uv;
} VertexIn;

out vec4 colorOut;

//const ivec3 off = ivec3(-1, 0, 1);
//const float gamma = 2.2;

//vec3 toLinear(vec3 v) {
//  return pow(v, vec3(gamma));
//vec4 toLinear(vec4 v) {
//  return vec4(toLinear(v.rgb), v.a);
//vec3 toGamma(vec3 v) {
//  return pow(v, vec3(1.0 / gamma));
//vec4 toGamma(vec4 v) {
//  return vec4(toGamma(v.rgb), v.a);

void main() {
//    if (!gl_FrontFacing) {
//        colorOut = vec4(0.076f, 0.082f, 0.212f, 1.0f);
//        return;
//    }
    colorOut = color1;
//    vec2 size = vec2(uvScale * 2, 0.0);
//    float unscaledHeight = texture(heightMapTexture, VertexIn.uv).r;
//    float height = unscaledHeight * heightScale;
//    float westPixel = textureOffset(heightMapTexture, VertexIn.uv, off.xy).r * heightScale;
//    float eastPixel = textureOffset(heightMapTexture, VertexIn.uv, off.zy).r * heightScale;
//    float northPixel = textureOffset(heightMapTexture, VertexIn.uv, off.yx).r * heightScale;
//    float southPixel = textureOffset(heightMapTexture, VertexIn.uv, off.yz).r * heightScale;
//    float northWestPixel = textureOffset(heightMapTexture, VertexIn.uv, off.xx).r * heightScale;
//    float southEastPixel = textureOffset(heightMapTexture, VertexIn.uv, off.zz).r * heightScale;
//    float northEastPixel = textureOffset(heightMapTexture, VertexIn.uv, off.zx).r * heightScale;
//    float southWestPixel = textureOffset(heightMapTexture, VertexIn.uv, off.xz).r * heightScale;
//    vec3 va = normalize(vec3(size.xy, eastPixel - westPixel));
//    vec3 vb = normalize(vec3(size.yx, southPixel - northPixel));
//    vec3 vc = normalize(vec3(size.xx, southEastPixel - northWestPixel));
//    vec3 vd = normalize(vec3(size.x, -size.x, southWestPixel - northEastPixel));
//    vec3 crossNormal = cross(va, vb);
//    crossNormal = normalize(vec3(crossNormal.x, -crossNormal.y, crossNormal.z));
//    vec3 xNormal = cross(vd, vc);
//    xNormal = normalize(vec3(xNormal.y, -xNormal.x, xNormal.z));
//    vec3 modelNormal = normalize(xNormal + crossNormal);
//    vec3 normal = normalize(normalMatrix * modelNormal);
//    float diffuse = max(dot(normal, lightDirection), 0.0) + 0.06;
//    vec4 lowColor;
//    vec4 highColor;
//    float interpolation;
//    if (unscaledHeight <= 0.05) {
//        lowColor = color1;
//        highColor = color1;
//        interpolation = 0.5;
//    } else if (unscaledHeight <= 0.19) {
//        lowColor = color1;
//        highColor = color2;
//        interpolation = (unscaledHeight - 0.05) * 7.1426;
//    } else if (unscaledHeight <= 0.24) {
//        lowColor = color2;
//        highColor = color2;
//        interpolation = 0.5;
//    } else if (unscaledHeight <= 0.38) {
//        lowColor = color2;
//        highColor = color3;
//        interpolation = (unscaledHeight - 0.24) * 7.1426;
//    } else if (unscaledHeight <= 0.43) {
//        lowColor = color3;
//        highColor = color3;
//        interpolation = 0.5;
//    } else if (unscaledHeight <= 0.57) {
//        lowColor = color3;
//        highColor = color4;
//        interpolation = (unscaledHeight - 0.43) * 7.1426;
//    } else if (unscaledHeight <= 0.62) {
//        lowColor = color4;
//        highColor = color4;
//        interpolation = 0.5;
//    } else if (unscaledHeight <= 0.76) {
//        lowColor = color4;
//        highColor = color5;
//        interpolation = (unscaledHeight - 0.62) * 7.1426;
//    } else if (unscaledHeight <= 0.81) {
//        lowColor = color5;
//        highColor = color5;
//        interpolation = 0.5;
//    } else if (unscaledHeight <= 0.95) {
//        lowColor = color5;
//        highColor = color6;
//        interpolation = (unscaledHeight - 0.81) * 7.1426;
//    } else {
//        lowColor = color6;
//        highColor = color6;
//        interpolation = 0.5;
//    }
//    vec4 baseColor = mix(toLinear(lowColor), toLinear(highColor), interpolation);
//    vec4 lightColor = toLinear(vec4(1.0));
//    vec4 diffuseColor = vec4(baseColor.rgb * lightColor.rgb * diffuse, 1.0);
//    vec4 spec = vec4(0.0);
//    vec3 eye = -normalize(vec3(modelViewMatrix * vec4(VertexIn.position.xy, height, 1.0)));
//    float intensity = max(dot(normal, lightDirection), 0.0);
//    if (intensity > 0.0) {
//        vec3 halfVector = normalize(lightDirection + eye);
//        spec = toLinear(specularColor) * pow(max(dot(halfVector, normal), 0.0), shininess);
//    }
//    colorOut = toGamma(diffuseColor * max(intensity * toLinear(diffuseColor) + spec, toLinear(ambientColor)));

The geometry itself is correctly formed in heightMapTexture to the best of my knowledge because I've inspected it piece by piece in a debugger and because the strips come out correctly on Nvidia cards. I am assuming there are no fundamental differences in how GL_TRIANGLE_STRIP works between Nvidia and ATI. If there is a difference then that's exactly the type of thing this question is trying to figure out. The description of triangle strips in the OpenGL spec doesn't seem to leave much room for differences here if any, so it seems likely that if the strips themselves are formed correctly according to the spec they should work everywhere or nowhere.

The geometry is a basic hexagonally arranged triangle grid with degenerates in between the strips. I'd like to figure out what features are causing this to output nothing except the glClear() on ATI cards so I can get people with different machines looking at the application.

Here is an example of what it outputs on all the Nvidia machines across various windows and OSX flavors, in case that helps

sample output

On ATI it displays the gray scissor clear rectangle that is drawn by the snippet:

glClearColor(background.r, background.g, background.b, background.a)
glScissor(xPosition, flippedY, width, height)

Nothing else besides the gray rectangle is displayed.

In summary, for someone who has something like this working on an ATI card, are these the OpenGL calls that you would use to get it drawing on an ATI card or is there some obvious feature I'm using that's not supported by ATI that's causing the whole thing to not draw. I'm looking for possible differences between how ATI and Nvidia would handle this geometry.

P.S. I added this note to answer some of the comments that suggest I should go back to a single triangle and debug from scratch on my own. The reason I'm asking this question is that I don't currently have access to ATI hardware. I'm in South America until May and hardware availability is basically nil where I am. I have several workstations that I brought with me for work, but they all have Nvidia hardware. I have to do a full build and then ship the product to someone in a different time zone each time I attempt to correct the issue. I need to be more purposeful in my fixes than going back to drawing a triangle and working up from scratch. I would love to debug it myself on the affected hardware step by step. I just can't right now. I need to get the code closer to a likely solution before sending another build, so I'm looking for some educated guesses from people with direct experience with this type of geometry on ATI hardware. I would greatly appreciate more understanding than "That's what debug is for. Go back to a triangle.", because those comments really don't help me or anyone else reading this later. This problem seems like something that someone must have encountered before because it seems like a pretty standard way to draw a heightmap. I think that this question will help anyone else who has this problem in the future. I've lost several days to this, and there is no published answer to anything similar that I could find. If this remains unanswered until May, when I return home, I will write the answer myself and remove this note, but I hope it doesn't come to that.

  • $\begingroup$ Have you seen that drawing works if you take out the degenerate triangles? If not, then what makes you think that degenerate triangles have anything to do with your problem? "The geometry itself is correctly formed because it works on Nvidia cards." No, that's not a reasonable conclusion to come to from that evidence. "Works on NVIDIA" only means "works on NVIDIA". There's plenty of stuff NVIDIA's GL implementations do that are outside the specification. $\endgroup$ – Nicol Bolas Dec 31 '16 at 17:41
  • 1
    $\begingroup$ "On ATI it displays the gray scissor clear box but that's it." What "gray scissor box" are you referring to? $\endgroup$ – Nicol Bolas Dec 31 '16 at 17:42
  • $\begingroup$ The only way to resolve problems like this is to boil them down to the simplest elements: draw one triangle. Get that working. Then keep adding elements until you find the one that causes things to disappear. That's your problem. $\endgroup$ – Nicol Bolas Dec 31 '16 at 17:44
  • $\begingroup$ I do not suspect that the degenerates have anything to do with the issue. I don't think I said that I did. I was simply describing the format of the geometry that is being sent that is not displaying correctly.By saying that the geometry itself is correctly formed what I meant is that the indices create triangle strips with the correct vertex indices specified in the correct order which produce the correct number of triangles with the correct alternating winding order. I was simply referring to the actual vertex data in heightMapVertexData. It is what i expect it to be. $\endgroup$ – Adam Brown Dec 31 '16 at 17:52
  • $\begingroup$ "I do not suspect that the degenerates have anything to do with the issue. I don't think I said that I did." But that is what you ask in the title of the question: "Are ATI cards lacking some feature that would allow them to draw triangle strips connected by degenerates with a single draw call using a vao?" So either that's what you're asking about or the title is misleading. $\endgroup$ – Nicol Bolas Dec 31 '16 at 17:55

Your Answer


Browse other questions tagged or ask your own question.