NeomerArcana
NeomerArcana

Reputation: 2249

OpenGL SegFault in glDrawElements

My call to glDrawElements is producing a segmentation fault. If I scrap the indices and use glDrawArrays everything in my vertex buffer is drawn. So my assumption is that the error is in the way I'm populating the GL_ELEMENT_ARRAY_BUFFER, but I don't see the problem.

I have a sphere class that's producing vertices and indices for a sphere. I then just the following code to setup the vbo, vao etc:

    GLuint vbo;
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(struct VertexData) * sphere.GetVertices()->size(), sphere.GetVertices()->data(), GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    GLuint vao;
    glGenVertexArrays(1,&vao);
    glBindVertexArray(vao);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 4, GL_DOUBLE, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, position));
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, color));
    glEnableVertexAttribArray(2);
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, normal));
    glEnableVertexAttribArray(3);
    glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, tcoords));

    GLuint ibo;
    glGenBuffers(1, &ibo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * sphere.GetIndices()->size(), sphere.GetIndices()->data(), GL_STATIC_DRAW);
    glBindVertexArray(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

..in which I can't see anything wrong.

My drawing is then done like this:

glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

float t = watch.GetElapsedTimePeriod().count();
float rotation = 0.5/1000.0*t;
rotationMatrix = glm::rotate(rotationMatrix,rotation,glm::vec3(0,1,0));
glm::mat4 modelMatrix = perspectiveMatrix * viewMatrix * rotationMatrix * identitymatrix;

glUseProgram(shaderprogram);
glUniformMatrix4fv(glGetUniformLocation(shaderprogram, "mvpmatrix"), 1, GL_FALSE, glm::value_ptr(modelMatrix));

glBindVertexArray(vao);
std::shared_ptr<std::vector<GLushort>> p = sphere.GetIndices();
size_t tt = p->size();
glDrawElements(GL_TRIANGLES,tt,GL_UNSIGNED_SHORT,0);
//glDrawArrays(GL_POINTS,0,(*sphere.GetVertices()).size());
glBindVertexArray(0);

glUseProgram(0);

SDL_GL_SwapWindow(sdlglHandler);

You can see my commented out glDrawArrays call that succeeds if I use it (and don't setup the index buffer in the top code). (It's GL_POINTS so that I can see every vertex being drawn.)

I've noted that if I generate less vertices in my sphere the program doesn't crash. So at first I thought it was the use of GLushort as the type for my indices, but there aren't that many vertices being produced for it to be a problem. I tested by using GLuint as well but that didn't do anything.

Next, I've noted that if I use less than the size of my indices vector (being returned from the sphere) I can get it to work; but it has to be about 1/6th of the size (and of course only about 1/6th of my sphere's triangles are drawn.

I've looked through the rest of my code as well to see if I'm writing past an array or anything and quite simply it's only in the openGL code where I'm doing anything with arrays and pointers.

Because I'm able to remove the index array and see it working, I'm convinced that it has something to do with this; but I cannot work it out.

Oh and finally, the VertexData structure looks like this:

struct VertexData
{
    GLdouble position[4];
    GLfloat color[3];
    GLfloat normal[3];
    GLfloat tcoords[2];
};

Massive Edit

I've since removed all of my C++ encapsulation and produced this code which gives me the same error. I'm at a complete loss to see where trying to access private memory, nothing looks like it's overflowing to me:

#include <exception>
#include <iostream>
#include <fstream>
#include <sstream>
#include <list>
#include <string>
#include <algorithm>
#include <functional>

#include <utilities.hpp>

#include <GL/glew.h>
#include <SDL2/sdl.h>
#define GLM_FORCE_RADIANS
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/transform.hpp>

struct VertexData
{
    GLdouble position[4];
    GLfloat color[3];
    GLfloat normal[3];
    GLfloat tcoords[2];
};

int main(int argc, char* argv[])
{
    try
    {
        SDL_Window* window;
        SDL_GLContext context;
        if(SDL_Init(SDL_INIT_VIDEO) < 0)
            throw std::runtime_error("unable to initialise video");

        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
        SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 4);

        SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
        SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
        SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);

        window = SDL_CreateWindow("SpaceEngine", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        800, 800, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN);
        if(!window)
            throw std::runtime_error("unable to create window");

        context = SDL_GL_CreateContext(window);

        SDL_GL_SetSwapInterval(1);

        GLenum glewErr = glewInit();
        if(glewErr != GLEW_OK)
        {
            SDL_GL_DeleteContext(context);
            SDL_DestroyWindow(window);
            SDL_Quit();
            throw std::runtime_error(reinterpret_cast<const char*>(glewGetErrorString(glewErr)));
        }

        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LESS);

        std::vector<VertexData> vertices;
        std::vector<GLushort> indices;
        int rings = 200;
        int sectors = 200;
        float radius = 1.0;

        if(rings < 2)
            rings = 2;
        if(sectors < 2)
            sectors = 2;
        while(rings * sectors >= std::numeric_limits<GLushort>::max())
        {
            rings /= 2;
            sectors /= 2;
        }

        const GLuint polyCountXPitch = rings+1;

        GLuint level = 0;

        for(GLuint p1 = 0; p1 < sectors-1; ++p1)
        {
            for(GLuint p2 = 0; p2 < rings-1; ++p2)
            {
                GLuint curr = level + p2;
                indices.push_back(curr + polyCountXPitch);
                indices.push_back(curr);
                indices.push_back(curr + 1);
                indices.push_back(curr + polyCountXPitch);
                indices.push_back(curr + 1);
                indices.push_back(curr + 1 + polyCountXPitch);
            }

            indices.push_back(level + rings - 1 + polyCountXPitch);
            indices.push_back(level + rings - 1);
            indices.push_back(level + rings);

            indices.push_back(level + rings - 1 + polyCountXPitch);
            indices.push_back(level + rings);
            indices.push_back(level + rings + polyCountXPitch);

            level += polyCountXPitch;
        }

        const GLuint polyCountSq = polyCountXPitch * sectors;           //top point
        const GLuint polyCountSq1 = polyCountSq + 1;                    //bottom point
        const GLuint polyCountSqM1 = (sectors - 1) * polyCountXPitch;   //last rows first index

        for(GLuint p2 = 0; p2 < rings - 1; ++p2)
        {
            indices.push_back(polyCountSq);
            indices.push_back(p2 + 1);
            indices.push_back(p2);

            indices.push_back(polyCountSqM1 + p2);
            indices.push_back(polyCountSqM1 + p2 + 1);
            indices.push_back(polyCountSq1);
        }

        indices.push_back(polyCountSq);
        indices.push_back(rings);
        indices.push_back(rings - 1);

        indices.push_back(polyCountSqM1 + rings - 1);
        indices.push_back(polyCountSqM1);
        indices.push_back(polyCountSq1);

        const GLdouble angleX = 2 * pi() / rings;
        const GLdouble angleY = pi() / sectors;

        GLuint i = 0;
        GLdouble axz;
        GLdouble ay = 0;

        vertices.resize(polyCountXPitch * sectors + 2);
        for(GLuint y = 0; y < sectors; ++y)
        {
            ay += angleY;
            const GLdouble sinay = std::sin(ay);
            axz = 0;

            for(GLuint xz = 0; xz < rings; ++xz)
            {
                const glm::vec3 pos((radius * std::cos(axz) * sinay),radius * std::cos(ay), radius * std::sin(axz) * sinay);
                glm::vec3 normal = pos;
                normal = glm::normalize(normal);

                GLuint tu = 0.5f;
                if(y == 0)
                {
                    if(normal.y != -1.0f && normal.y != 1.0f)
                        tu = std::acos(glm::clamp<GLdouble>(normal.x/sinay, -1.0f, 1.0f)) * 0.5 * (1.0f/pi());
                    if(normal.z < 0.0f)
                        tu = 1 - tu;
                }
                else
                    tu = vertices[i-polyCountXPitch].tcoords[0];

                VertexData v;
                v.color[0] = 1;
                v.color[1] = 1;
                v.color[2] = 1;
                v.position[0] = pos.x;
                v.position[1] = pos.y;
                v.position[2] = pos.z;
                v.position[3] = 1.0f;
                v.normal[0] = normal.x;
                v.normal[1] = normal.y;
                v.normal[2] = normal.z;
                v.tcoords[0] = tu;
                v.tcoords[1] = ay * (1.0f/pi());
                vertices.at(i) = v;

                ++i;
                axz += angleX;
            }

            vertices.at(i) = vertices.at(i - rings);
            vertices.at(i).tcoords[0] = 1.0f;
            ++i;
        }

        VertexData v;
        v.color[0] = 1;
        v.color[1] = 1;
        v.color[2] = 1;
        v.position[0] = 0;
        v.position[1] = radius;
        v.position[2] = 0;
        v.position[3] = 1.0f;
        v.normal[0] = 0;
        v.normal[1] = 1;
        v.normal[2] = 0;
        v.tcoords[0] = 0.5f;
        v.tcoords[1] = 0.0f;
        vertices.at(i) = v;
        ++i;
        v.position[1] = -radius;
        v.normal[1] = -1.0f;
        v.tcoords[1] = 1.0f;
        vertices.at(i) = v;

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

        GLuint vbo;
        glGenBuffers(1, &vbo);
        glBindBuffer(GL_ARRAY_BUFFER, vbo);
        glBufferData(GL_ARRAY_BUFFER, sizeof(struct VertexData) * vertices.size(), vertices.data(), GL_STATIC_DRAW);

        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 4, GL_DOUBLE, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, position));
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, color));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, normal));
        glEnableVertexAttribArray(3);
        glVertexAttribPointer(3, 2, GL_FLOAT, GL_FALSE, sizeof(struct VertexData), (const GLvoid*)offsetof(struct VertexData, tcoords));

        GLuint ibo;
        glGenBuffers(1, &ibo);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * indices.size(), indices.data(), GL_STATIC_DRAW);

        glBindVertexArray(0);
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

        GLuint shader1,shader2;
        std::ifstream file("tutorial2.vert");
        if(!file)
            throw std::runtime_error("The file tutorial2.vert was not opened");
        else
        {
            std::string fileContents((std::istreambuf_iterator<char>(file)),std::istreambuf_iterator<char>());

            shader1 = glCreateShader(GL_VERTEX_SHADER);
            std::string fail = "glCreateShader failed using " + GL_VERTEX_SHADER;

            if(!shader1)
                throw std::runtime_error(fail.c_str());

            const GLchar* contents[1];
            contents[0] = fileContents.c_str();
            glShaderSource(shader1, 1, contents, NULL);

            glCompileShader(shader1);
            int compiled;
            glGetShaderiv(shader1, GL_COMPILE_STATUS, &compiled);
            if(compiled == 0)
            {
                int maxLength;
                glGetShaderiv(shader1, GL_INFO_LOG_LENGTH, &maxLength);
                char* vertexInfoLog = new char[maxLength];
                glGetShaderInfoLog(shader1, maxLength, &maxLength, vertexInfoLog);
                throw std::runtime_error("Shader failed to compile:\n>\t" + std::string(vertexInfoLog));
            }
        }
        std::ifstream file2("tutorial2.frag");
        if(!file2)
            throw std::runtime_error("The file tutorial2.frag was not opened");
        else
        {
            std::string fileContents((std::istreambuf_iterator<char>(file2)),std::istreambuf_iterator<char>());

            shader2 = glCreateShader(GL_FRAGMENT_SHADER);
            std::string fail = "glCreateShader failed using " + GL_FRAGMENT_SHADER;
            if(!shader2)
                throw std::runtime_error(fail.c_str());

            const GLchar* contents[1];
            contents[0] = fileContents.c_str();
            glShaderSource(shader2, 1, contents, NULL);

            glCompileShader(shader2);
            int compiled;
            glGetShaderiv(shader2, GL_COMPILE_STATUS, &compiled);
            if(compiled == 0)
            {
                int maxLength;
                glGetShaderiv(shader2, GL_INFO_LOG_LENGTH, &maxLength);
                char* vertexInfoLog = new char[maxLength];
                glGetShaderInfoLog(shader2, maxLength, &maxLength, vertexInfoLog);
                throw std::runtime_error("Shader failed to compile:\n>\t" + std::string(vertexInfoLog));
            }
        }

        GLuint program = glCreateProgram();
        if(!program)
            throw std::runtime_error("glCreateProgram failed");

        glAttachShader(program, shader1);
        glAttachShader(program, shader2);

        glBindAttribLocation(program, 0, "in_Position");
        glBindAttribLocation(program, 1, "in_Color");
        glBindAttribLocation(program, 2, "in_Normal");
        glBindAttribLocation(program, 3, "in_UV");

        glLinkProgram(program);
        int IsLinked;
        glGetProgramiv(program, GL_LINK_STATUS, (int *)&IsLinked);
        if(IsLinked == 0)
        {
           int maxLength;
           glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
           char* shaderProgramInfoLog = new char[maxLength];
           glGetProgramInfoLog(program, maxLength, &maxLength, shaderProgramInfoLog);
           throw std::runtime_error("Program failed to link:\n>\t" + std::string(shaderProgramInfoLog) + "");
        }

        glDetachShader(program, shader1);
        glDetachShader(program, shader2);

        bool done = false;

        while(!done)
        {
            SDL_Event event;
            while(SDL_PollEvent(&event))
            {
                switch(event.type)
                {
                case SDL_WINDOWEVENT:
                    switch(event.window.event)
                    {
                    case SDL_WINDOWEVENT_CLOSE:
                        done = true;
                        break;
                    }
                    break;
                }
            }

            glClearColor(0.0, 0.0, 0.0, 1.0);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            glUseProgram(program);

            glBindVertexArray(vao);

            glDrawElements(GL_TRIANGLES,indices.size(),GL_UNSIGNED_SHORT,0);

            glBindVertexArray(0);

            glUseProgram(0);

            SDL_GL_SwapWindow(window);
            }

        glUseProgram(0);
        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);

        glDeleteProgram(program);
        glDeleteBuffers(1, &vbo);
        glDeleteVertexArrays(1, &vao);

        SDL_GL_DeleteContext(context);
        SDL_DestroyWindow(window);
        SDL_Quit();

        std::cout << "all good in the hood" << std::endl;
    }
    catch(const std::exception& e)
    {
        std::cout << "ERROR:\t" << e.what() << std::endl;
    }
    catch(...)
    {
        std::cout << "ERROR" << std::endl;
    }
    exit(EXIT_SUCCESS);
}

My Vertex shader:

#version 150

precision highp float;

in vec4 in_Position;
in vec3 in_Color;
in vec3 in_Normal;
in vec2 in_UV;

uniform mat4 mvpmatrix;

out vec3 ex_Color;
void main(void) 
{
    gl_Position = in_Position;

    ex_Color = in_Color;
}

And my Frag shader:

#version 150

precision highp float;

in  vec3 ex_Color;
out vec4 gl_FragColor;

void main(void) 
{
    gl_FragColor = vec4(ex_Color,1.0);
}

EDIT2

I've even tried outputing a line of text to a file and iterate through each triangle in my indices 1 by 1. If I run it a few times I find the segfault happens at different points in the indices array. The vertex that the index is looking up is within bounds of my vertices array.

for(int i = 0; i < tt; i+=3)
{
    file << "attempting " << i/3 << std::endl;
    glDrawElements(GL_TRIANGLES,i,GL_UNSIGNED_SHORT,0);
    SDL_GL_SwapWindow(sdlglHandler);
}

So this tells me that I'm trying to access a index that is outside of the bounds from what I sent to the GL_ELEMENT_ARRAY_BUFFER but again, I cannot see the problem.

Upvotes: 3

Views: 7466

Answers (2)

NeomerArcana
NeomerArcana

Reputation: 2249

The issue was in using GL_DOUBLE for the vertices. Changing to GL_FLOAT seems to have done the trick.

You need to use glVertexAttribLPointer when using double.

To use glVertexAttribLPointer meaningfully would require you to re-write a line in your shader (in dvec4 in_Position)

Upvotes: 3

datenwolf
datenwolf

Reputation: 162164

Your use of VAOs looks correct. Nevertheless may I suggest that you explicitly add a glBindBuffer on the index buffer before doing the glDrawElements call, i.e.

std::shared_ptr<std::vector<GLushort>> p = sphere.GetIndices();
size_t tt = p->size();

glBindVertexArray(vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glDrawElements(GL_TRIANGLES,tt,GL_UNSIGNED_SHORT,0);
glBindVertexArray(0);

Note that even today VAOs are not very much optimized and changing the VAO binding may mean a performance hit. Valve for example benchmarked it and as of now still has the codepaths using multiple VAOs disabled in their Source engine. Until this changes the recommended way is to just bind the buffer objects themself, and for OpenGL profiles that mandate a VAO to create and bind that once per context and then just ignore it in further operations.

Upvotes: 4

Related Questions