Drawing without attrib array enabled gets "optimized out" by AMD driver?

I recently had to replace my laptop and in the process got something with an AMD GPU to complement my Nvidia “testing hardware”. It turned out that two of my examples didn’t work. The first one was a clear case of the Nvidia driver letting me get away with something that it shouldn’t (I wasn’t binding the whole relevante range of a buffer with BindBufferRange), but the second one is puzzling me.
In my tessellation example I draw without any vertex attribs by deriving vertex positions from gl_VertexID and gl_InstanceID and then tessellating the result according to a “displacement texture”. On AMD it simply does nothing. Black screen, no errors and a GL_PRIMITIVES_GENERATED query reveals that it doesn’t even generate primitives. Just creating a dummy vao+vbo and specifying a AttribArray with stride 0 (that isn’t read in the shader) makes it work.
So my interpretation is that the driver “optimizes” out the drawcall when there is no attrib array enabled? I can make it work with that dummy array, but what I really wonder is whether that is valid behavior? I so far couldn’t really clarify this by just reading the spec, but I might be looking in the wrong places.

You shouldn’t need to generate a VBO and define an array/enable the array in the first place. All you should need is an “empty” and bound VAO before issuing the draw call, as explained by Alfonse in one of his tutorials.

Yes, I realized I should be using a VAO and my example most likely worked on Nvidia without it because I work in a compatibility profile (since GLEW doesn’t do core…). But on the AMD box it really needs a enabled attrib array for some reason, the VAO alone doesn’t do it. I’m mostly looking for confirmation that it should work without the attrib array and that i’m possibly dealing with the AMD driver being nonconformant here (which then instantly brings up the question whether an example program should contain a workaround or strictly follow the spec…).

Edit:
If someone wants to try here is a more minimal example (see line 144 for code to uncomment):


#include <GL/glew.h>
#include <GL/glfw.h>

#include <iostream>
#include <string>
#include <vector>

bool running;

int closedWindow()
{
    running = false;
    return GL_TRUE;
}

bool check_shader_compile_status(GLuint obj)
{
    GLint status;
    glGetShaderiv(obj, GL_COMPILE_STATUS, &status);
    if(status == GL_FALSE)
    {
        GLint length;
        glGetShaderiv(obj, GL_INFO_LOG_LENGTH, &length);
        std::vector<char> log(length);
        glGetShaderInfoLog(obj, length, &length, &log[0]);
        std::cerr << &log[0];
        return false;
    }
    return true;
}

bool check_program_link_status(GLuint obj)
{
    GLint status;
    glGetProgramiv(obj, GL_LINK_STATUS, &status);
    if(status == GL_FALSE)
    {
        GLint length;
        glGetProgramiv(obj, GL_INFO_LOG_LENGTH, &length);
        std::vector<char> log(length);
        glGetProgramInfoLog(obj, length, &length, &log[0]);
        std::cerr << &log[0];
        return false;   
    }
    return true;
}

int main()
{
    int width = 640;
    int height = 480;
    
    if(glfwInit() == GL_FALSE)
    {
        std::cerr << "failed to init GLFW" << std::endl;
        return 1;
    }

    glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 3);
    glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
 
    if(glfwOpenWindow(width, height, 0, 0, 0, 8, 24, 8, GLFW_WINDOW) == GL_FALSE)
    {
        std::cerr << "failed to open window" << std::endl;
        glfwTerminate();
        return 1;
    }
    
    glfwSetWindowCloseCallback(closedWindow);
    
    glewExperimental = GL_TRUE;
    GLenum glew_error = glewInit();
    if (glew_error != GLEW_OK)
    {
        std::cerr << "failed to init GLEW: " << glewGetErrorString(glew_error) << std::endl;
        glfwCloseWindow();
        glfwTerminate();
        return 1;
    }

    std::string vertex_source =
        "#version 330
"
        "out vec4 fcolor;
"
        
        "const vec2 quad[6] = vec2[](
"
        "   vec2(0,0),vec2(1,0),vec2(1,1),
"
        "   vec2(0,0),vec2(1,1),vec2(0,1)
"
        ");
"

        "void main() {
"
        "   fcolor = vec4(quad[gl_VertexID],0,1);
"
        "   gl_Position = vec4(2*quad[gl_VertexID] - 1,0,1);
"
        "}
";
        
    std::string fragment_source =
        "#version 330
"
        "in vec4 fcolor;
"
        "layout(location = 0) out vec4 FragColor;
"
        "void main() {
"
        "   FragColor = fcolor;
"
        "}
";

    GLuint shader_program, vertex_shader, fragment_shader;
    
    const char *source;
    int length;

    vertex_shader = glCreateShader(GL_VERTEX_SHADER);
    source = vertex_source.c_str();
    length = vertex_source.size();
    glShaderSource(vertex_shader, 1, &source, &length); 
    glCompileShader(vertex_shader);
    if(!check_shader_compile_status(vertex_shader))
    {
        return 1;
    }
 
    fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
    source = fragment_source.c_str();
    length = fragment_source.size();
    glShaderSource(fragment_shader, 1, &source, &length);   
    glCompileShader(fragment_shader);
    if(!check_shader_compile_status(fragment_shader))
    {
        return 1;
    }
    
    shader_program = glCreateProgram();
    
    glAttachShader(shader_program, vertex_shader);
    glAttachShader(shader_program, fragment_shader);

    glLinkProgram(shader_program);
    check_program_link_status(shader_program);
    
	glUseProgram(shader_program);
	
    GLuint vao;
 
    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);
    
    // stuff that shouldn't be required...
    /*
    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat), 0, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, (char*)0 + 0*sizeof(GLfloat));
	*/

    running = true;
    while(running)
    {    
        if(glfwGetKey(GLFW_KEY_ESC))
        {
            running = false;
        }
        
        glClear(GL_COLOR_BUFFER_BIT);

        glDrawArrays(GL_TRIANGLES, 0, 6);
       
        GLenum error = glGetError();
        if(error != GL_NO_ERROR)
        {
            std::cerr << error;
            running = false;       
        }
        
        glfwSwapBuffers();       
    }

    glDeleteVertexArrays(1, &vao);
    //glDeleteBuffers(1, &vbo);
    
    glDetachShader(shader_program, vertex_shader);	
    glDetachShader(shader_program, fragment_shader);
    glDeleteShader(vertex_shader);
    glDeleteShader(fragment_shader);
    glDeleteProgram(shader_program);
    
    glfwCloseWindow();
    glfwTerminate();
    return 0;
}

which then instantly brings up the question whether an example program should contain a workaround or strictly follow the spec…

Absolutely the latter - IMHO.

The spec states in section 10.4, 10.5 and appendix D.2.2 (in order):

An INVALID_OPERATION error is generated by any commands which modify, draw from, or query vertex array state when no vertex array is bound.

An INVALID_OPERATION error is generated if no vertex array object is bound (see section 10.4)

Calling VertexAttribPointer when no buffer object or no vertex array object is bound will generate an INVALID_OPERATION error, as will calling any array drawing command when no vertex array object is bound.

So yes, I suppose AMD is probably doing it wrong.

Uh, vaos not being bound is not the issue though. The issue is that the AMD driver simply draws nothing as long as there is no attrib array enabled. It doesn’t even error or anything, it just doesn’t draw anything or even generate primitives.

I guess in that case enabling the “dummy array” isn’t per se incorrect it just shouldn’t be required.

This is a “feature” of the compatibility profile: Vertices are only ever generated when generic vertex attribute 0 or the legacy vertex (glVertex, glVertexPointer etc.) data is specified. If it works without on NVidia, then it is a (useful) bug in the NVidia driver.

In the core profile this requirement is removed.

Ah, thank you using a core profile indeed changes the behavior. I guess i’ll change all my examples to use gl3w instead of glew then…

Uh, vaos not being bound is not the issue though. The issue is that the AMD driver simply draws nothing as long as there is no attrib array enabled.

I know, and that’s exactly the point. All you should need to do is bind a VAO and issue a draw call.

I guess in that case enabling the “dummy array” isn’t per se incorrect it just shouldn’t be required.

Maybe not incorrect but completely unnecessary as there aren’t any vertex attributes which could be transferred to the vertex shader. Also, this is exactly what you want, no vertex attribs being transfered. Anything else would defeat the purpose of allowing draw calls to be issued without a bound VBO and enabled, non-empty vertex arrays in the first place.

Besides, Alfonse reported in a thread with a similar problem that Catalyst 12.1 handled rendering without vertex arrays correctly. I can’t see anything wrong with your code, so maybe you caught a regression.

Ah, thank you using a core profile indeed changes the behavior.

Well, you should have mentioned you’re using a compat context. :wink: Yes, rendering without buffer backing and vertex arrays being defined is only legal with a core context.

Aren’t we lucky we have GL_ARB_compatibility? :wink:

If you are using freeglut then the core profile can be used as follows:


int main(int argc, char* argv[])
{
    glutInit(&argc, argv);
    glutInitContextVersion(4, 0);
    glutInitContextProfile(GLUT_CORE_PROFILE);//GLUT_CORE_PROFILE|GLUT_COMPATIBILITY_PROFILE
    
...
}