What can cause glDrawArrays to generate a GL_INVALID_OPERATION error?
geometry-shader, glsl, marching-cubes, opengl, shader
Solution
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.
Problem
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: - `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. - `GL_INVALID_OPERATION` is generated if `glDrawArrays` is executed between the execution of `glBegin` and the corresponding `glEnd`. - `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. - `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. - `GL_INVALID_OPERATION` is generated if mode is `GL_PATCHES` and no tessellation control shader is active. - `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`. - `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. - `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. - `GL_INVALID_OPERATION` is generated if the bound shader program is invalid. - 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. - I'm not mapping any buffers at all in my program, anywhere. - I'm using the Core profile, so `glBegin` and `glEnd` aren't even available. - I have two samplers, and they are of different types, but they're definitely mapped to different textures. - A geometry shader is active, but it's input layout is `layout (points) in`, and `glDrawArrays` is being called with `GL_POINTS`. - I'm not using `GL_PATCHES` or tessellation shaders of any sort. - 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. - There is a geometry shader. See the next point. - 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`. - 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.