¿Qué puede causar que glDrawArrays genere un error de OPERACIÓN INVÁLIDO de GL?


He estado intentando escribir una implementación de GPU de dos pasos del algoritmo Marching Cubes, similar a la detallada en el primer capítulo de GPU Gems 3, usando OpenGL y GLSL. Sin embargo, la llamada a glDrawArrays en mi primer pase falla consistentemente con un GL_INVALID_OPERATION.

He buscado toda la documentación que puedo encontrar, y encontré estas condiciones bajo las cuales glDrawArrays puede lanzar ese error:

  1. GL_INVALID_OPERATION se genera si un nombre de objeto buffer distinto de cero está vinculado a una matriz habilitada o al enlace GL_DRAW_INDIRECT_BUFFER y el almacén de datos del objeto buffer está asignado actualmente.
  2. GL_INVALID_OPERATION se genera si glDrawArrays se ejecuta entre la ejecución de glBegin y la correspondiente glEnd.
  3. GL_INVALID_OPERATION será generado por glDrawArrays o glDrawElements si dos samplers activos en el objeto de programa actual son de diferentes tipos, pero se refieren a la misma unidad de imagen de textura.
  4. GL_INVALID_OPERATION se genera si un sombreador de geometría está activo y el modo es incompatible con el tipo primitivo de entrada del sombreador de geometría en el objeto de programa instalado actualmente.
  5. GL_INVALID_OPERATION se genera si mode es GL_PATCHES y no hay sombreado de control de teselación activo.
  6. GL_INVALID_OPERATION se genera si el registro de los vértices de una primitiva a los objetos de búfer que se utilizan para fines de retroalimentación de transformación resultaría en exceder los límites del tamaño de cualquier objeto de búfer, o en exceder el desplazamiento de posición final + tamaño - 1, según lo establecido por glBindBufferRange.
  7. GL_INVALID_OPERATION es generado por glDrawArrays() si no hay geometría shader está presente, transform feedback está activo y mode no es uno de los modos permitidos.
  8. GL_INVALID_OPERATION es generado por glDrawArrays() si un sombreador de geometría está presente, la retroalimentación de transformación está activa y el tipo primitivo de salida del sombreador de geometría no coincide con el modo primitiveMode de retroalimentación de transformación.
  9. GL_INVALID_OPERATION se genera si el programa de sombreado enlazado no es válido.
  10. EDITAR 10/10/12: GL_INVALID_OPERATION se genera si la retroalimentación de transformación está en uso, y el búfer el punto de enlace de retroalimentación de transformación también está vinculado al punto de enlace de búfer de matriz. Este es el problema que estaba teniendo, debido a un error tipográfico en el que buffer me enlazó. Si bien la especificación indica que esto es ilegal, no aparece en glDrawArrays como una de las razones por las que puede generar un error, en cualquier documentación que encuentre.

Desafortunadamente, ninguna pieza de documentación oficial que pueda encontrar cubre más de 3 de estos. Tuve que recoger esta lista de numerosas fuentes. Puntos 7 y 8 en realidad provienen de la documentación para glBeginTransformFeedback, y el punto 9 no parece estar documentado en absoluto. Lo encontré mencionado en una publicación del foro en algún lugar. Sin embargo, todavía no creo que esta lista esté completa, ya que ninguno de estos parece explicar el error que estoy recibiendo.

  1. No estoy mapeando ningún búfer en mi programa, en ninguna parte.
  2. Estoy usando el perfil Principal, así que glBegin y glEnd ni siquiera están disponibles.
  3. Tengo dos muestreadores, y son de diferentes tipos, pero definitivamente están mapeados a diferentes texturas.
  4. Un sombreador de geometría está activo, pero su diseño de entrada es layout (points) in, y glDrawArrays se llama con GL_POINTS.
  5. No estoy usando GL_PATCHES o sombreadores de teselación de ningún tipo.
  6. Me he asegurado de asignar la cantidad máxima de espacio que mis sombreadores de geometría podrían generar. Luego intenté cuadruplicarlo. No ayudó.
  7. Hay un sombreador de geometría. Ver el siguiente punto.
  8. Se está utilizando la retroalimentación de transformación, y hay un sombreador de geometría, pero el diseño de salida es layout (points) out y glBeginTransformFeedback se llama con GL_POINTS.
  9. Intenté insertar una llamada a glValidateProgram justo antes de la llamada a glDrawArrays, y devolvió GL_TRUE.

El código actual de OpenGL está aquí:

    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);
    }

El error real es la llamada a glDrawArrays en RenderPass1. Vale la pena señalar que si comento las llamadas a glBeginTransformFeedback y glEndTransformFeedback, entonces glDrawArrays deja de generar el error. Así que lo que sea que esté mal, probablemente esté relacionado con transformar comentario.

Edit 8/18/12, 9 PM:

Acabo de encontrar la función NVIDIA GLExpert en gDEBugger, con la que no estaba familiarizado anteriormente. Cuando encendí esto, dio información algo más sustancial sobre el GL_INVALID_OPERATION, específicamente The current operation is illegal in the current state: Buffer is mapped.. Así que me estoy topando con el punto 1, arriba. Aunque no tengo idea de cómo.

No tengo llamadas a glMapBuffer, ni a ninguna función relacionada, en ninguna parte de mi código. Me puse gDEBugger a romper en cualquier llamada a glMapBuffer, glMapBufferARB, glMapBufferRange, glUnmapBuffer y glUnmapBufferARB, y no se rompió en ninguna parte. Luego agregué código al inicio de RenderPass1 para desmapear explícitamente los búferes molestos. No solo el error no desapareció, sino que las llamadas a glUnmapBuffer ahora generan The current operation is illegal in the current state: Buffer is unbound or is already unmapped.. Así que si ninguno de los búferes que estoy usando están mapeados, ¿de dónde viene el error?

Edit 8/19/12, 12 AM:

Basado en los mensajes de error que estoy saliendo de GLExpert en gDEBugger, parece que llamar a glBeginTransformFeedback está causando que el búfer enlazado a GL_TRANSFORM_FEEDBACK_BUFFER se mapee. Específicamente, cuando hago clic en el búfer en "Textures, Buffers and Images Viewer", arroja el mensaje The current operation is illegal in the current state: Buffer must be bound and not mapped.. Sin embargo, si añado esto entre glBeginTransformFeedback y glEndTransformFeedback:

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

Produce 0, lo que indicaría que GL_TRANSFORM_FEEDBACK_BUFFER no está mapeado. Si este búfer está mapeado en otro punto de enlace, ¿devolvería esto 0? ¿Por qué glBeginTransformFeedback mapearía el búfer, haciéndolo inutilizable para la retroalimentación de transformación?

Cuanto más aprendo aquí, más confundido me estoy volviendo.

Editar 10/10/12:

Como se indica en mi respuesta a continuación a la solución de Nicol Bolas, encontré el problema, y es el mismo que él encontró: Debido a un error tipográfico estúpido, estaba vinculando el mismo búfer tanto a los puntos de enlace de entrada como de salida.

Lo encontré probablemente dos semanas después de publicar la pregunta. Me había dado por vencido en la frustración por un tiempo,y finalmente regresó y básicamente re-implementado todo el asunto desde cero, comparando regularmente bits y piezas de la más antigua, que no funciona. Cuando terminé, la nueva versión funcionó, y fue cuando busqué las diferencias que descubrí que había estado enlazando el búfer equivocado.

Author: Michael Powell, 2012-08-18

3 answers

Me di cuenta de su problema: está renderizando al mismo búfer que está obteniendo sus datos de vértices.

GlBindVertexArray (vaoPass2);

Creo que querías decir vaoPass1

De la especificación:

Los buffers no deben estar enlazados o en uso tanto para transform feedback como para otros propósitos en el LG. Específicamente, si un objeto buffer está enlazado simultáneamente a un transforme el punto de enlace del búfer de retroalimentación y en cualquier otro lugar del GL, o lee desde el búfer generar valores indefinidos. Ejemplos de tales encuadernaciones incluir readPixels a un punto de enlace de objeto de búfer de píxel y acceso del cliente a un buffer mapeado con MapBuffer.

Ahora, debe obtener valores indefinidos; no estoy seguro de que un error GL califique, pero probablemente debería ser un error.

 18
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-10 21:35:38

Otro caso (aparentemente indocumentado) donde glDrawArrays y glDrawElements fallan con GL_INVALID_OPERATION:

  • GL_INVALID_OPERATION se genera si un uniforme de sampler se establece en un identificador de unidad de textura no válido. (Yo había actuado por error glUniform1i(location, GL_TEXTURE0); cuando quise decir glUniform1i(location, 0);.)
 5
Author: smokris,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-29 19:19:37

Otro caso (indocumentado) donde glDraw*() las llamadas pueden fallar con GL_INVALID_OPERATION:

  • GL_INVALID_OPERATION se genera si un uniforme de sampler se establece en una unidad de textura enlazada a una textura del tipo incorrecto. Por ejemplo, si uniform sampler2D se establece glUniform1i(location, 0);, pero GL_TEXTURE0 tiene una textura GL_TEXTURE_2D_ARRAY limitada.
 1
Author: hacker64,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-18 02:16:42