51
votes

I've been attempting to write a two-pass GPU implementation of the Marching Cubes algorithm, similar to the one detailed in the first chapter of GPU Gems 3, using OpenGL and GLSL. However, the call to glDrawArrays in my first pass consistently fails with a GL_INVALID_OPERATION.

I've looked up all the documentation I can find, and found these conditions under which glDrawArrays can throw that error:

  1. GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to an enabled array or to the GL_DRAW_INDIRECT_BUFFER binding and the buffer object's data store is currently mapped.
  2. GL_INVALID_OPERATION is generated if glDrawArrays is executed between the execution of glBegin and the corresponding glEnd.
  3. GL_INVALID_OPERATION will be generated by glDrawArrays or glDrawElements if any two active samplers in the current program object are of different types, but refer to the same texture image unit.
  4. GL_INVALID_OPERATION is generated if a geometry shader is active and mode is incompatible with the input primitive type of the geometry shader in the currently installed program object.
  5. GL_INVALID_OPERATION is generated if mode is GL_PATCHES and no tessellation control shader is active.
  6. GL_INVALID_OPERATION is generated if recording the vertices of a primitive to the buffer objects being used for transform feedback purposes would result in either exceeding the limits of any buffer object’s size, or in exceeding the end position offset + size - 1, as set by glBindBufferRange.
  7. GL_INVALID_OPERATION is generated by glDrawArrays() if no geometry shader is present, transform feedback is active and mode is not one of the allowed modes.
  8. GL_INVALID_OPERATION is generated by glDrawArrays() if a geometry shader is present, transform feedback is active and the output primitive type of the geometry shader does not match the transform feedback primitiveMode.
  9. GL_INVALID_OPERATION is generated if the bound shader program is invalid.
  10. EDIT 10/10/12: GL_INVALID_OPERATION is generated if transform feedback is in use, and the buffer bound to the transform feedback binding point is also bound to the array buffer binding point. This is the problem I was having, due to a typo in which buffer I bound. While the spec does state that this is illegal, it isn't listed under glDrawArrays as one of the reasons it can throw an error, in any documentation I found.

Unfortunately, no one piece of official documentation I can find covers more than 3 of these. I had to collect this list from numerous sources. Points 7 and 8 actually come from the documentation for glBeginTransformFeedback, and point 9 doesn't seem to be documented at all. I found it mentioned in a forum post somewhere. However, I still don't think this list is complete, as none of these seem to explain the error I'm getting.

  1. I'm not mapping any buffers at all in my program, anywhere.
  2. I'm using the Core profile, so glBegin and glEnd aren't even available.
  3. I have two samplers, and they are of different types, but they're definitely mapped to different textures.
  4. A geometry shader is active, but it's input layout is layout (points) in, and glDrawArrays is being called with GL_POINTS.
  5. I'm not using GL_PATCHES or tessellation shaders of any sort.
  6. I've made sure I'm allocating the maximum amount of space my geometry shaders could possible output. Then I tried quadrupling it. Didn't help.
  7. There is a geometry shader. See the next point.
  8. Transform feedback is being used, and there is a geometry shader, but the output layout is layout (points) out and glBeginTransformFeedback is called with GL_POINTS.
  9. I tried inserting a call to glValidateProgram right before the call to glDrawArrays, and it returned GL_TRUE.

The actual OpenGL code is here:

    const int SECTOR_SIZE = 32;
    const int SECTOR_SIZE_CUBED = SECTOR_SIZE * SECTOR_SIZE * SECTOR_SIZE;
    const int CACHE_SIZE = SECTOR_SIZE + 3;
    const int CACHE_SIZE_CUBED = CACHE_SIZE * CACHE_SIZE * CACHE_SIZE;

    MarchingCubesDoublePass::MarchingCubesDoublePass(ServiceProvider* svc, DensityMap* sourceData) {
        this->sourceData = sourceData;
        densityCache = new float[CACHE_SIZE_CUBED];
    }

    MarchingCubesDoublePass::~MarchingCubesDoublePass() {
        delete densityCache;
    }

    void MarchingCubesDoublePass::InitShaders() {
        ShaderInfo vertShader, geoShader, fragShader;

        vertShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass1.vert", GL_VERTEX_SHADER);
        svc->shader->Compile(vertShader);
        geoShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass1.geo", GL_GEOMETRY_SHADER);
        svc->shader->Compile(geoShader);
        shaderPass1 = glCreateProgram();
        static const char* outputVaryings[] = { "triangle" };
        glTransformFeedbackVaryings(shaderPass1, 1, outputVaryings, GL_SEPARATE_ATTRIBS);
        assert(svc->shader->Link(shaderPass1, vertShader, geoShader));

        uniPass1DensityMap = glGetUniformLocation(shaderPass1, "densityMap");
        uniPass1TriTable = glGetUniformLocation(shaderPass1, "triangleTable");
        uniPass1Size = glGetUniformLocation(shaderPass1, "size");
        attribPass1VertPosition = glGetAttribLocation(shaderPass1, "vertPosition");

        vertShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass2.vert", GL_VERTEX_SHADER);
        svc->shader->Compile(vertShader);
        geoShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass2.geo", GL_GEOMETRY_SHADER);
        svc->shader->Compile(geoShader);
        fragShader = svc->shader->Load("data/shaders/MarchingCubesDoublePass-Pass2.frag", GL_FRAGMENT_SHADER);
        svc->shader->Compile(fragShader);
        shaderPass2 = glCreateProgram();
        assert(svc->shader->Link(shaderPass2, vertShader, geoShader, fragShader));

        uniPass2DensityMap = glGetUniformLocation(shaderPass2, "densityMap");
        uniPass2Size = glGetUniformLocation(shaderPass2, "size");
        uniPass2Offset = glGetUniformLocation(shaderPass2, "offset");
        uniPass2Matrix = glGetUniformLocation(shaderPass2, "matrix");
        attribPass2Triangle = glGetAttribLocation(shaderPass2, "triangle");
    }

    void MarchingCubesDoublePass::InitTextures() {
        for (int x = 0; x < CACHE_SIZE; x++) {
            for (int y = 0; y < CACHE_SIZE; y++) {
                for (int z = 0; z < CACHE_SIZE; z++) {
                    densityCache[x + y*CACHE_SIZE + z*CACHE_SIZE*CACHE_SIZE] = sourceData->GetDensity(Vector3(x-1, y-1, z-1));
                }
            }
        }
        glGenTextures(1, &densityTex);
        glBindTexture(GL_TEXTURE_3D, densityTex);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
        glTexImage3D(GL_TEXTURE_3D, 0, GL_R32F, CACHE_SIZE, CACHE_SIZE, CACHE_SIZE, 0, GL_RED, GL_FLOAT, densityCache);

        glGenTextures(1, &triTableTex);
        glBindTexture(GL_TEXTURE_RECTANGLE, triTableTex);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_R16I, 16, 256, 0, GL_RED_INTEGER, GL_INT, triTable);
    }

    void MarchingCubesDoublePass::InitBuffers() {
        float* voxelGrid = new float[SECTOR_SIZE_CUBED*3];
        unsigned int index = 0;
        for (int x = 0; x < SECTOR_SIZE; x++) {
            for (int y = 0; y < SECTOR_SIZE; y++) {
                for (int z = 0; z < SECTOR_SIZE; z++) {
                    voxelGrid[index*3 + 0] = x;
                    voxelGrid[index*3 + 1] = y;
                    voxelGrid[index*3 + 2] = z;
                    index++;
                }
            }
        }

        glGenBuffers(1, &bufferPass1);
        glBindBuffer(GL_ARRAY_BUFFER, bufferPass1);
        glBufferData(GL_ARRAY_BUFFER, SECTOR_SIZE_CUBED*3*sizeof(float), voxelGrid, GL_STATIC_DRAW);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glGenBuffers(1, &bufferPass2);
        glBindBuffer(GL_ARRAY_BUFFER, bufferPass2);
        glBufferData(GL_ARRAY_BUFFER, SECTOR_SIZE_CUBED*5*sizeof(int), NULL, GL_DYNAMIC_COPY);
        glBindBuffer(GL_ARRAY_BUFFER, 0);

        glGenVertexArrays(1, &vaoPass1);
        glBindVertexArray(vaoPass1);
        glBindBuffer(GL_ARRAY_BUFFER, bufferPass1);
        glVertexAttribPointer(attribPass1VertPosition, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glEnableVertexAttribArray(attribPass1VertPosition);
        glBindVertexArray(0);

        glGenVertexArrays(1, &vaoPass2);
        glBindVertexArray(vaoPass2);
        glBindBuffer(GL_ARRAY_BUFFER, bufferPass2);
        glVertexAttribIPointer(attribPass2Triangle, 1, GL_INT, 0, (void*)0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glEnableVertexAttribArray(attribPass2Triangle);
        glBindVertexArray(0);

        glGenQueries(1, &queryNumTriangles);
    }

    void MarchingCubesDoublePass::Register(Genesis::ServiceProvider* svc, Genesis::Entity* ent) {
        this->svc = svc;
        this->ent = ent;
        svc->scene->RegisterEntity(ent);

        InitShaders();
        InitTextures();
        InitBuffers();
    }

    void MarchingCubesDoublePass::Unregister() {
        if (!ent->GetBehavior<Genesis::Render>()) {
            svc->scene->UnregisterEntity(ent);
        }
    }

    void MarchingCubesDoublePass::RenderPass1() {
        glEnable(GL_RASTERIZER_DISCARD);

        glUseProgram(shaderPass1);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, densityTex);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_RECTANGLE, triTableTex);
        glUniform1i(uniPass1DensityMap, 0);
        glUniform1i(uniPass1TriTable, 1);
        glUniform1i(uniPass1Size, SECTOR_SIZE);

        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, bufferPass2);

        glBindVertexArray(vaoPass2);
        glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, queryNumTriangles);
        glBeginTransformFeedback(GL_POINTS);
            GLenum error = glGetError();
            glDrawArrays(GL_POINTS, 0, SECTOR_SIZE_CUBED);
            error = glGetError();
        glEndTransformFeedback();
        glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
        glBindVertexArray(0);

        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);

        glUseProgram(0);

        glDisable(GL_RASTERIZER_DISCARD);

        glGetQueryObjectuiv(queryNumTriangles, GL_QUERY_RESULT, &numTriangles);
    }

    void MarchingCubesDoublePass::RenderPass2(Matrix mat) {
        glUseProgram(shaderPass2);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_3D, densityTex);

        glUniform1i(uniPass2DensityMap, 0);
        glUniform1i(uniPass2Size, SECTOR_SIZE);
        glUniform3f(uniPass2Offset, 0, 0, 0);
        mat.UniformMatrix(uniPass2Matrix);

        glBindVertexArray(vaoPass2);
        glDrawArrays(GL_POINTS, 0, numTriangles);
        glBindVertexArray(0);

        glUseProgram(0);
    }

    void MarchingCubesDoublePass::OnRender(Matrix mat) {
        RenderPass1();
        RenderPass2(mat);
    }

The actual error is the call to glDrawArrays in RenderPass1. Worth noting that if I comment out the calls to glBeginTransformFeedback and glEndTransformFeedback, then glDrawArrays stops generating the error. So whatever's wrong, it's probably somehow related to transform feedback.

Edit 8/18/12, 9 PM:

I just found the NVIDIA GLExpert feature in gDEBugger, which I wasn't previously familiar with. When I turned this on, it gave somewhat more substantial information on the GL_INVALID_OPERATION, specifically The current operation is illegal in the current state: Buffer is mapped.. So I'm running into point 1, above. Though I have no idea how.

I have no calls to glMapBuffer, or any related function, anywhere in my code. I set gDEBugger to break on any calls to glMapBuffer, glMapBufferARB, glMapBufferRange, glUnmapBuffer and glUnmapBufferARB, and it didn't break anywhere. Then I added code to the start of RenderPass1 to explicitly unmap bother buffers. Not only did the error not go away, but calls to glUnmapBuffer now both generate The current operation is illegal in the current state: Buffer is unbound or is already unmapped.. So if neither of the buffers I'm using are mapped, where is the error coming from?

Edit 8/19/12, 12 AM:

Based on the error messages I'm getting out of GLExpert in gDEBugger, it appears that calling glBeginTransformFeedback is causing the buffer bound to GL_TRANSFORM_FEEDBACK_BUFFER to become mapped. Specifically, when I click on the buffer in "Textures, Buffers and Images Viewer" it outputs the message The current operation is illegal in the current state: Buffer must be bound and not mapped.. However, if I add this between glBeginTransformFeedback and glEndTransformFeedback:

int bufferBinding;
glGetBufferParameteriv(GL_TRANSFORM_FEEDBACK_BUFFER, GL_BUFFER_MAPPED, &bufferBinding);
printf("Transform feedback buffer binding: %d\n", bufferBinding);

it outputs 0, which would indicate that GL_TRANSFORM_FEEDBACK_BUFFER is not mapped. If this buffer is mapped on another binding point, would this still return 0? Why would glBeginTransformFeedback map the buffer, thus rendering it unusable for transform feedback?

The more I learn here, the more confused I'm becoming.

Edit 10/10/12:

As indicated in my reply below to Nicol Bolas' solution, I found the problem, and it's the same one he found: Due to a stupid typo, I was binding the same buffer to both the input and output binding points.

I found it probably two weeks after posting the question. I'd given up in frustration for a time, and eventually came back and basically re-implemented the whole thing from scratch, regularly comparing bits and pieces the older, non-working one. When I was done, the new version worked, and it was when I searched out the differences that I discovered I'd been binding the wrong buffer.

3
The way to debug something like this is to stop doing so much stuff at once. Start from working code, then slowly build on it until it stops working. Start with code that just renders something from buffers; it doesn't matter what so long as it's what you expect. Then make it do transform feedback, and check that the values you get are what you expect. Then turn off rasterization. Then remove your fragment shader. Then add one texture. Then the second. And so on, until you build everything back up. You can also try using a debug context if your implementation supports ARB_debug_output.Nicol Bolas
I actually did build this up gradually. It all seemed to be working, right up to the point where I added the calls to begin and end transform feedback. So as I mentioned at the end, I know it's connected to the fact that it's using transform feedback, but I don't know what other factors might be contributing. I've tried stripping out other portions of code, but nothing that I can remove which the transform feedback doesn't require fixes it. I think at this point that it's as likely that I'm missing a step than that I'm doing a step wrong, and eliminating code won't find that.Michael Powell
Also worth noting: I pulled down the g-truc OpenGL samples, to go through their transform feedback sample and make sure I wasn't missing any steps. But didn't find anything that made a difference.Michael Powell
Haha great read - thanks!James Bedford

3 Answers

20
votes

I figured out your problem: you are rendering to the same buffer that you're sourcing your vertex data.

glBindVertexArray(vaoPass2);

I think you meant vaoPass1

From the spec:

Buffers should not be bound or in use for both transform feedback and other purposes in the GL. Specifically, if a buffer object is simultaneously bound to a transform feedback buffer binding point and elsewhere in the GL, any writes to or reads from the buffer generate undefined values. Examples of such bindings include ReadPixels to a pixel buffer object binding point and client access to a buffer mapped with MapBuffer.

Now, you should get undefined values; I'm not sure that a GL error qualifies, but it probably should be an error.

9
votes

Another (apparently undocumented) case where glDrawArrays and glDrawElements fail with GL_INVALID_OPERATION:

  • GL_INVALID_OPERATION is generated if a sampler uniform is set to an invalid texture unit identifier. (I had mistakenly performed glUniform1i(location, GL_TEXTURE0); when I meant glUniform1i(location, 0);.)
1
votes

Another (undocumented) case where glDraw*() calls can fail with GL_INVALID_OPERATION:

  • GL_INVALID_OPERATION is generated if a sampler uniform is set to a texture unit bound to a texture of the incorrect type. For example, if a uniform sampler2D is set glUniform1i(location, 0);, but GL_TEXTURE0 has a GL_TEXTURE_2D_ARRAY texture bound.