user7210680
user7210680

Reputation:

glVertexAttribDivisor not working properly on nVidia?

I wrote some code to test instancing in OpenGL:

#include "exception"
#include "fstream"
#include "GL/glew.h"
#include "glm/fwd.hpp"
#include "glm/glm.hpp"
#include "iostream"
#include "SDL2/SDL.h"
#include "sstream"
#include "stdexcept"
#include "unordered_map"

int                     g_xRes = 1024, g_yRes = 768;
SDL_Window *            g_window = nullptr;
SDL_GLContext           g_context = 0;

void init(int * argc, char ** argv);
void cleanup();
void mainLoop();
GLuint loadShader(const char * filepath, GLenum type);
GLuint makeProgram(GLuint vs, GLuint gs, GLuint fs, 
                   const std::unordered_map<std::string, GLuint> & fragDataLocations);

// This is done in order to prevent missing reference error on Windows
#ifdef main
# undef main
#endif

int main(int argc, char ** argv)
{
    try
    {
        init(&argc, argv);
        mainLoop();
    }
    catch(std::exception & ex)
    {
        std::cerr << argv[0] << ": " << ex.what() << std::endl;
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

void mainLoop()
{
    glm::vec3 vertices[] = {
        glm::vec3(0, 0, 0),
        glm::vec3(0, 0.25, 0),
        glm::vec3(0.25, 0, 0),
        glm::vec3(0.25, 0.25, 0),
        glm::vec3(0.50, 0, 0),
        glm::vec3(0.50, 0.25, 0),
        glm::vec3(0.75, 0, 0),
        glm::vec3(0.75, 0.25, 0),
    };

    GLushort elements[] = {
        0, 1, 2, 3, 2, 1, 4, 5, 6, 7, 6, 5
    };

    glm::vec3 colors[] = {
        glm::vec3(1, 0, 0),
        glm::vec3(0, 1, 0)
    };

    GLuint vao, vboPos, vboCol, ebo;

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    glGenBuffers(1, &vboCol);
    glGenBuffers(1, &vboPos);
    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ARRAY_BUFFER, vboPos);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, vboCol);
    glBufferData(GL_ARRAY_BUFFER, sizeof(colors), colors, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);

    GLuint vs = loadShader("vs.glsl", GL_VERTEX_SHADER),
           fs = loadShader("fs.glsl", GL_FRAGMENT_SHADER);

    GLuint program = makeProgram(vs, 0, fs, { { "outColor", 0 } });
    glUseProgram(program);

    glBindBuffer(GL_ARRAY_BUFFER, vboPos);
    GLuint posAttr = glGetAttribLocation(program, "pos");
    glEnableVertexAttribArray(posAttr);
    glVertexAttribPointer(posAttr, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, vboCol);
    GLuint colAttr = glGetAttribLocation(program, "col");
    glEnableVertexAttribArray(colAttr);
    glVertexAttribPointer(colAttr, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glVertexAttribDivisor(colAttr, 1);

    glEnable(GL_DEPTH_TEST);

    SDL_Event e;
    while(true)
    {
        while(SDL_PollEvent(&e) != 0)
        {
            if(e.type == SDL_QUIT)
                return;
        }

        glClearColor(0, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glDrawElementsInstanced(GL_TRIANGLES, sizeof(elements)/sizeof(elements[0]), GL_UNSIGNED_SHORT, 
            nullptr, sizeof(colors)/sizeof(colors[0]));

        assert(glGetError() == GL_NO_ERROR);

        SDL_GL_SwapWindow(g_window);
    }
}

void init(int * argc, char ** argv)
{
    std::ostringstream errMsgSStream;

    // Initialize SDL
    if(SDL_Init(SDL_INIT_EVERYTHING) < 0)
    {
        errMsgSStream << "" << SDL_GetError();
        throw std::runtime_error(errMsgSStream.str());
    }

    // Manage OpenGL attributes
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, true);
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);

    // Create window
    g_window = SDL_CreateWindow("Example", SDL_WINDOWPOS_CENTERED, 
      SDL_WINDOWPOS_CENTERED, g_xRes, g_yRes, SDL_WINDOW_OPENGL);
    if(!g_window)
    {
        errMsgSStream << "" << SDL_GetError();
        throw std::runtime_error(errMsgSStream.str());
    }

    // Create a new device context
    g_context = SDL_GL_CreateContext(g_window);
    if(!g_context)
    {
        errMsgSStream << "" << SDL_GetError();
        throw std::runtime_error(errMsgSStream.str());
    }

    // Initialize GLEW
    glewExperimental = GL_TRUE;
    if(glewInit() != GLEW_OK)
    {
        errMsgSStream << "failed to initialize GLEW";
        throw std::runtime_error(errMsgSStream.str());
    }

    // Apparently GLEW can make glGetError return GL_INVALID_ENUM even if initialized correctly 
    while(glGetError() != GL_NO_ERROR);

    if(atexit(cleanup))
    {
        errMsgSStream << "failed to register cleanup function";
        throw std::runtime_error(errMsgSStream.str());
    }

    return;
}

void cleanup()
{
    SDL_GL_DeleteContext(g_context);
    SDL_DestroyWindow(g_window);
    SDL_Quit();
}

GLuint loadShader(const char * filepath, GLenum type)
{
    std::ifstream shaderFile;

    shaderFile.exceptions(std::ios_base::failbit);
    try {
        shaderFile.open(filepath);
    } catch(std::exception & ex) {
        std::ostringstream ss;
        ss << "failed to open file '" << filepath << "': " << ex.what();
        throw std::runtime_error(ss.str());
    }
    shaderFile.exceptions(std::ios_base::goodbit);

    GLuint res = glCreateShader(type);

    // Get the length of the file
    shaderFile.seekg(0, shaderFile.end);
    size_t fileSize = shaderFile.tellg();
    shaderFile.seekg(0, shaderFile.beg);

    // Allocate space for, and read the header source
    char * shaderSrc = new char[fileSize + 1];
    shaderFile.read(shaderSrc, fileSize);
    shaderSrc[fileSize] = '\0';

    // Try to compile shader
    GLint status;
    glShaderSource(res, 1, &shaderSrc, nullptr);
    glCompileShader(res);
    glGetShaderiv(res, GL_COMPILE_STATUS, &status);
    if(status != GL_TRUE)
    {
        char buf[0x10000];
        std::ostringstream ss;
        glGetShaderInfoLog(res, 0x10000, nullptr, buf);
        ss << "failed to compile file '" << filepath << "':" << std::endl << buf;
        glDeleteShader(res);
        throw std::runtime_error(ss.str());
    }

    return res;
}

GLuint makeProgram(GLuint vs, GLuint gs, GLuint fs, 
                   const std::unordered_map<std::string, GLuint> & fragDataLocations)
{
    GLuint res = glCreateProgram();

    // Attach shaders
    if(vs) glAttachShader(res, vs);
    if(gs) glAttachShader(res, gs);
    if(fs) glAttachShader(res, fs);

    // Bind data locations
    for(const auto & ent : fragDataLocations)
        glBindFragDataLocation(res, ent.second, ent.first.c_str());

    // Link program
    GLint status;
    glLinkProgram(res);
    glGetProgramiv(res, GL_LINK_STATUS, &status);
    if(vs) glDetachShader(res, vs);
    if(gs) glDetachShader(res, gs);
    if(fs) glDetachShader(res, fs); 
    if(status != GL_TRUE)
    {
        char buf[0x10000];
        std::ostringstream ss;
        glGetProgramInfoLog(res, 0x10000, nullptr, buf);
        ss << "failed to link program: " << std::endl << buf;
        glDeleteProgram(res);
        throw std::runtime_error(ss.str());
    }

    return res;
}

VS:

#version 330

in vec3 pos;
in vec3 col;

out vec3 fCol;

void main()
{
    gl_Position = vec4(pos, 1);
    fCol = col;
}

PS:

#version 330

in vec3 fCol;

out vec4 outColor;

void main()
{
    outColor = vec4(fCol, 1);
}

The output I was expecting would look like this:

enter image description here

However, what I got is this:

enter image description here

Is there something wrong with my code, or does my driver not implement this function properly (I use proprietary NVIDIA driver v340.102 on Ubuntu Linux 16.04)?

Upvotes: 1

Views: 512

Answers (1)

keltar
keltar

Reputation: 18409

First of all, your glVertexAttribPointer says there is only 1 component when there are 3. Then, you render 2 instances, with both quads in each, with the same positions, so you don't see second instance.

You can shift position of second instance with e.g. gl_Position = vec4(pos+gl_InstanceID*0.25, 1); to at least see both instances.

Upvotes: 1

Related Questions