1
$\begingroup$

I've written a program that uses OpenGL and GLFW to display some simple geometric shapes. I'm now trying to add some transform feedback capability to it and I find that as soon as I do so, the program stops displaying anything. So in pursuit of tracking down the problem I've started with a simple tutorial from the open.gl website that displays a red triangle (see https://open.gl/drawing for the original code) and then started adding the necessary transform feedback gl calls in order to reproduce the problem. The program is below:

#include <iostream>
#include <cmath>
#include <chrono>

#define GL_GLEXT_PROTOTYPES
#include <GLFW/glfw3.h>
#include <GL/gl.h>
// #include <GL/glu.h>
#include <thread>

#define gl_error_check do { GLenum err; while ((err = glGetError()) != GL_NO_ERROR) { fprintf(stderr, "gl error %d at %s:%d\n", err, __FILE__, __LINE__); } } while (0)

int main()
{
    float vertices[] = {
        0.0f,  0.5f, // Vertex 1 (X, Y)
        0.5f, -0.5f, // Vertex 2 (X, Y)
        -0.5f, -0.5f  // Vertex 3 (X, Y)
    };

    GLuint vbo;

    const char* vertexSource = R"glsl(
        #version 330 core

        in vec2 position;
        // out float outValue;

        void main()
        {
            gl_Position = vec4(position, 0.0, 1.0);
        }
    )glsl";

    const char* fragmentSource = R"glsl(
        #version 330 core

        uniform vec3 triangleColor;

        out vec4 outColor;

        void main()
        {
            outColor = vec4(triangleColor, 1.0);
        }
    )glsl";


    glfwInit();

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", nullptr, nullptr); // Windowed

    glfwMakeContextCurrent(window);

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

    glGenBuffers(1, &vbo); // Generate 1 buffer
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexSource, NULL);
    glCompileShader(vertexShader);

    GLint status;
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status);

    if (status != GL_TRUE) {
        std::cerr << "vertex shader compile failed " << status << std::endl;
        return -1;
    }

    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentSource, NULL);
    glCompileShader(fragmentShader);

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status);

    if (status != GL_TRUE) {
        std::cerr << "fragment shader compile failed " << status << std::endl;
        return -1;
    }

    GLuint shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);

    glBindFragDataLocation(shaderProgram, 0, "outColor");   // not necessary

#if 0
    const GLchar* feedbackVaryings[] = { "outValue" };
    glTransformFeedbackVaryings(shaderProgram, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
    gl_error_check;
#endif

    glLinkProgram(shaderProgram);
    glUseProgram(shaderProgram);

    GLint posAttrib = glGetAttribLocation(shaderProgram, "position");
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(posAttrib);

    GLint uniColor = glGetUniformLocation(shaderProgram, "triangleColor");
    glUniform3f(uniColor, 1.0f, 0.0f, 0.0f);

    GLfloat data[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f };

    // shader output buffer
    GLuint tbo;
    glGenBuffers(1, &tbo);
    glBindBuffer(GL_ARRAY_BUFFER, tbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(data), nullptr, GL_STATIC_READ);

    std::cout << "Init complete" << std::endl;

    while(!glfwWindowShouldClose(window))
    {
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
            glfwSetWindowShouldClose(window, GL_TRUE);

        // Clear the screen to black
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glUniform3f(uniColor, 1.0f, 0.0f, 0.0f);

        // Draw a triangle from the 3 vertices
        glDrawArrays(GL_TRIANGLES, 0, 3);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // std::this_thread::sleep_for(std::chrono::seconds(10));

    glDeleteProgram(shaderProgram);
    glDeleteShader(fragmentShader);
    glDeleteShader(vertexShader);

    glDeleteBuffers(1, &tbo);
    glDeleteBuffers(1, &vbo);

    glDeleteVertexArrays(1, &vao);

    glfwTerminate();
    return 0;
}

If I compile and run the program as shown above, it displays a red triangle, like so:

working triangle

So far so good. In the code above, I've commented and ifdef'ed out a couple of what appear to be problematic lines. In the vertex shader, there's the declaration of the output variable outValue:

    // out float outValue;

and then in the CPU code:

#if 0
    const GLchar* feedbackVaryings[] = { "outValue" };
    glTransformFeedbackVaryings(shaderProgram, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
    gl_error_check;
#endif

If I uncomment the declaration of outValue and change the 0 in the #if to a 1 so that the call to glTransformFeedbackVaryings() is compiled, the resulting executable displays only an empty window, no triangle.

So it seems that the call to glTransformFeedbackVaryings() is somehow breaking the rasterization process, even though so far the shader doesn't yet write anything to outValue nor does the CPU code try to read the feedback buffer.

Thus my question.

Is it a requirement that when a GL program is using transform feedback that it cannot actually render pixels to the screen?

Assuming this is not the case, is there something I'm missing?

Thanks in advance.

=============================================

Edit: updated the version declarations in the shaders from 150 to 330. I still get a blank window with the problematic lines enabled.

=============================================

Following Nathan Reed's suggestion, I added gl_error_check s following every gl API call. This one seems to be triggering the first error:

glUseProgram(shaderProgram); gl_error_check;

as the console shows:

~/opengl/matrix_code/draw_feed$ ./a.out 
gl error 1282 at main.cpp:125

and line 125 is the call to glUseProgram(). The reported 1282 is 0x502 in hex which equates to:

GL_INVALID_OPERATION, 0x0502 Given when the set of state for a command is not legal for the parameters given to that command. It is also given for commands where combinations of parameters define what the legal parameters are.

When I lookup the reference to glUseProgram() at:

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glUseProgram.xhtml

and read the list of errors at the bottom, it shows:

GL_INVALID_OPERATION is generated if program is not a program object.

GL_INVALID_OPERATION is generated if program could not be made part of current state.

GL_INVALID_OPERATION is generated if transform feedback mode is active.

That last reason for GL_INVALID_OPERATION seems pertinent, but its not clear to me what I should do about. Is there some call necessary to disable transform feedback mode during the call to glUseProgram()?

If so, do I turn it back on again after the call to glUseProgram()?

Also, I added this code to display the shader compile log:

glCompileShader(vertexShader); gl_error_check;

GLint bufflen;
glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &bufflen); gl_error_check;
if (bufflen > 1)
{
    GLchar* log_string = new char[bufflen + 1];
    glGetShaderInfoLog(vertexShader, bufflen, 0, log_string); gl_error_check;
    printf("vertex shader: %s\n", log_string);
    delete[] log_string;
}

But nothing gets logged.

And thanks for the suggestion about Debug Output. I was unaware of that feature. I'll bone up on that and add it to my code.

$\endgroup$
1
  • 2
    $\begingroup$ Turning on transform feedback doesn't normally cause rendering to stop, no. (There's a separate toggle for that, GL_RASTERIZER_DISCARD, if you want only the transform feedback without actually drawing anything.) In OpenGL, when things mysteriously stop rendering it's usually because of an error. I see that you are checking glGetError, but have you checked the info log for the program after linking? (glGetProgramInfoLog) There might be more information in there. Also, try sprinkling more glGetError calls, or using Debug Output. $\endgroup$ Commented Jul 26, 2021 at 23:56

1 Answer 1

-1
$\begingroup$

To answer my original question:

"is doing transform feedback while rendering vertices not possible?"

No. It is possible, but not directly like my original code was trying to do.

It appears (somebody please tell why I'm wrong if I've still got this wrong, 8^)) that what's prohibited is performing rasterization and transform feedback in the same GLSL program. One can however create two GLSL programs with one program's vertex shader outputting pixels for rasterization and the other program's vertex shader outputting the same data for transform feedback.

Below I've copied the updated code that does this. It displays the same red triangle as shown above, but also feeds back the pixel coordinates and displays them on stdout like so:

$ ./draw_feed 
Init complete
loop_count = 1
1 primitives written!
0.000000 0.500000 0.000000 
0.500000 -0.500000 0.000000 
-0.500000 -0.500000 0.000000 

loop_count = 2
1 primitives written!
0.000000 0.500000 0.000000 
0.500000 -0.500000 0.000000 
-0.500000 -0.500000 0.000000 
$

The downside is it ends up passing all the input pixel data through the GPUs twice. For this code that's pretty lightweight, but for others I suspect that's not always the case. Anyway, this is the best I've been able to come up with.

Thanks to Jakob Progsch for providing the sample program I derived this from at:

https://github.com/progschj/OpenGL-Examples/blob/master/09transform_feedback.cpp

Jakob's code is much cleaner than mine, but while it does transform feedback first and uses that as input to rasterization, mine does the opposite.

#include <iostream>
#include <cmath>
#include <chrono>

#define GL_GLEXT_PROTOTYPES
#include <GLFW/glfw3.h>
#include <GL/gl.h>
#include <unistd.h>

#define gl_error_check do { GLenum err; while ((err = glGetError()) != GL_NO_ERROR) { fprintf(stderr, "gl error %d at %s:%d\n", err, __FILE__, __LINE__); } } while (0)

int main()
{
    float vertices[] = {
        0.0f,  0.5f, // Vertex 1 (X, Y)
        0.5f, -0.5f, // Vertex 2 (X, Y)
        -0.5f, -0.5f  // Vertex 3 (X, Y)
    };

    const char *vertexSource = R"glsl(
        #version 330 core

        in vec2 position;

        void main()
        {
            gl_Position = vec4(position, 0.0, 1.0);
        }
    )glsl";

    const char *fragmentSource = R"glsl(
        #version 330 core

        uniform vec3 triangleColor;

        out vec4 outColor;

        void main()
        {
            outColor = vec4(triangleColor, 1.0);
        }
    )glsl";

    const char *transformSource = R"glsl(
        #version 330 core

        in vec2 trans_position;
        out vec3 outValue;

        void main()
        {
            outValue = vec3(vec4(trans_position, 0.0, 1.0));
        }
    )glsl";


    glfwInit();

    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL", nullptr, nullptr); // Windowed

    glfwMakeContextCurrent(window);

#define NUM_BUFFERS 2

    GLuint vao[NUM_BUFFERS];
    glGenVertexArrays(NUM_BUFFERS, vao); gl_error_check;
    // printf("vao[0] = %u, vao[1] = %u\n", vao[0], vao[1]);

    GLuint vbo[NUM_BUFFERS];
    glGenBuffers(NUM_BUFFERS, vbo); gl_error_check; // Generate 1 buffer
    // printf("vbo[0] = %u, vbo[1] = %u\n", vbo[0], vbo[1]);

    // compile vertex shader
    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); gl_error_check;
    glShaderSource(vertexShader, 1, &vertexSource, NULL); gl_error_check;
    glCompileShader(vertexShader); gl_error_check;

    GLint bufflen;
    glGetShaderiv(vertexShader, GL_INFO_LOG_LENGTH, &bufflen); gl_error_check;
    if (bufflen > 1)
    {
        GLchar* log_string = new char[bufflen + 1];
        glGetShaderInfoLog(vertexShader, bufflen, 0, log_string); gl_error_check;
        printf("vertex shader: %s\n", log_string);
        delete[] log_string;
    }

    GLint status;
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &status); gl_error_check;

    if (status != GL_TRUE) {
        std::cerr << "vertex shader compile failed " << status << std::endl;
        return -1;
    }

    // compile fragment shader
    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); gl_error_check;
    glShaderSource(fragmentShader, 1, &fragmentSource, NULL); gl_error_check;
    glCompileShader(fragmentShader); gl_error_check;

    glGetShaderiv(fragmentShader, GL_INFO_LOG_LENGTH, &bufflen); gl_error_check;
    if (bufflen > 1)
    {
        GLchar* log_string = new char[bufflen + 1];
        glGetShaderInfoLog(fragmentShader, bufflen, 0, log_string); gl_error_check;
        printf("fragment shader: %s\n", log_string);
        delete[] log_string;
    }

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &status); gl_error_check;

    if (status != GL_TRUE) {
        std::cerr << "fragment shader compile failed " << status << std::endl;
        return -1;
    }

    // link vertex and fragment shaders into shader program
    GLuint shaderProgram = glCreateProgram(); gl_error_check;
    glAttachShader(shaderProgram, vertexShader); gl_error_check;
    glAttachShader(shaderProgram, fragmentShader); gl_error_check;

    glBindFragDataLocation(shaderProgram, 0, "outColor"); gl_error_check;   // not necessary?

    glLinkProgram(shaderProgram); gl_error_check;

    glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, &bufflen);  gl_error_check;
    if (bufflen > 1) {
        GLchar* log_string = new char[bufflen + 1];
        glGetProgramInfoLog(shaderProgram, bufflen, 0, log_string); gl_error_check;
        printf("shader program: %s\n", log_string);
        delete[] log_string;
    }

    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &status); gl_error_check;

    if (status != GL_TRUE) {
        std::cerr << "shader program compile failed " << status << std::endl;
        return -1;
    }

    // glUseProgram() needs to happen for glVertexAttribPointer() and glUniform3f() to not generate 1282 errors
    // though the program seems to work ok otherwise.
    glUseProgram(shaderProgram); gl_error_check;

    glBindVertexArray(vao[0]); gl_error_check;
    glBindBuffer(GL_ARRAY_BUFFER, vbo[0]); gl_error_check;
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); gl_error_check;

    GLint posAttrib = glGetAttribLocation(shaderProgram, "position"); gl_error_check;
    // printf("posAttrib = %d\n", posAttrib);
    glEnableVertexAttribArray(posAttrib); gl_error_check; // XXX
    glVertexAttribPointer(posAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0); gl_error_check;

    GLint uniColor = glGetUniformLocation(shaderProgram, "triangleColor"); gl_error_check;
    glUniform3f(uniColor, 1.0f, 0.0f, 0.0f); gl_error_check;

    // compile transform shader
    GLuint transformShader = glCreateShader(GL_VERTEX_SHADER); gl_error_check;
    glShaderSource(transformShader, 1, &transformSource, NULL); gl_error_check;
    glCompileShader(transformShader); gl_error_check;

    glGetShaderiv(transformShader, GL_INFO_LOG_LENGTH, &bufflen); gl_error_check;
    if (bufflen > 1)
    {
        GLchar* log_string = new char[bufflen + 1];
        glGetShaderInfoLog(transformShader, bufflen, 0, log_string); gl_error_check;
        printf("transform shader: %s\n", log_string);
        delete[] log_string;
    }

    glGetShaderiv(transformShader, GL_COMPILE_STATUS, &status); gl_error_check;

    if (status != GL_TRUE) {
        std::cerr << "transform shader compile failed " << status << std::endl;
        return -1;
    }

    // link transform shader into transform program
    GLuint transformProgram = glCreateProgram(); gl_error_check;
    glAttachShader(transformProgram, transformShader); gl_error_check;

    const GLchar* feedbackVaryings[] = { "outValue" };
    glTransformFeedbackVaryings(transformProgram, 1, feedbackVaryings, GL_INTERLEAVED_ATTRIBS); gl_error_check;

    glLinkProgram(transformProgram); gl_error_check;

    glGetProgramiv(transformProgram, GL_INFO_LOG_LENGTH, &bufflen);  gl_error_check;
    if (bufflen > 1) {
        GLchar* log_string = new char[bufflen + 1];
        glGetProgramInfoLog(transformProgram, bufflen, 0, log_string); gl_error_check;
        printf("transform program: %s\n", log_string);
        delete[] log_string;
    }

    glGetProgramiv(transformProgram, GL_LINK_STATUS, &status); gl_error_check;

    if (status != GL_TRUE) {
        std::cerr << "transform program compile failed " << status << std::endl;
        return -1;
    }

    glUseProgram(transformProgram); gl_error_check;

    glBindVertexArray(vao[1]); gl_error_check;
    glBindBuffer(GL_ARRAY_BUFFER, vbo[1]); gl_error_check;
    // 1 triangle of 3 vertices, 3 output coords (X, Y, X) per vertex == 9 floats per primitive
    glBufferData(GL_ARRAY_BUFFER, 9 * sizeof(float), nullptr, GL_STATIC_READ); gl_error_check;


    GLint transPosAttrib = glGetAttribLocation(transformProgram, "trans_position"); gl_error_check;
    // printf("transPosAttrib = %d\n", transPosAttrib);
    glEnableVertexAttribArray(transPosAttrib); gl_error_check;
    glVertexAttribPointer(transPosAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0); gl_error_check;

    std::cout << "Init complete" << std::endl;

    int loop_count = 0;

    while(!glfwWindowShouldClose(window))
    {
        if ((glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) || (++loop_count > 1))
            glfwSetWindowShouldClose(window, GL_TRUE);

        printf("loop_count = %d\n", loop_count);

        // render triangle to screen using shaderProgram and vao[0]
        glUseProgram(shaderProgram); gl_error_check;
        glBindVertexArray(vao[0]); gl_error_check;

        // Clear the screen to black
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f); gl_error_check;
        glClear(GL_COLOR_BUFFER_BIT); gl_error_check;

        glUniform3f(uniColor, 1.0f, 0.0f, 0.0f); gl_error_check;

        // Draw a triangle from the 3 vertices
        glDrawArrays(GL_TRIANGLES, 0, 3); gl_error_check;

        // render triangle coordinates to feedback buffer
        glUseProgram(transformProgram); gl_error_check;
        glBindVertexArray(vao[0]); gl_error_check;

        GLuint query;
        glGenQueries(1, &query); gl_error_check;

        glEnable(GL_RASTERIZER_DISCARD); gl_error_check;
        glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, vbo[1]); gl_error_check;
        glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query); gl_error_check;
        glBeginTransformFeedback(GL_TRIANGLES); gl_error_check;

        glDrawArrays(GL_TRIANGLES, 0, 3); gl_error_check; gl_error_check;

        glEndTransformFeedback(); gl_error_check;
        glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); gl_error_check;
        glDisable(GL_RASTERIZER_DISCARD); gl_error_check;
        glFlush(); gl_error_check;

        GLuint primitives;
        glGetQueryObjectuiv(query, GL_QUERY_RESULT, &primitives); gl_error_check;
        printf("%u primitives written!\n", primitives);

        // 1 triangle of 3 vertices, 3 output coords (X, Y, X) per vertex == 9 floats per primitive

        // read output of shader from out buffer to here
        GLfloat feedback[9];
        glGetBufferSubData(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(feedback), feedback); gl_error_check;

        for (int i = 0; i < primitives * 9; ) {
            printf("%f ", feedback[i]);
            if (!(++i % 3)) {
                printf("\n");
            }
        }
        printf("\n");

        glDeleteQueries(1, &query); gl_error_check;

        glfwSwapBuffers(window);
        glfwPollEvents();
        sleep(1);
    }

    glDeleteProgram(transformProgram); gl_error_check;
    glDeleteShader(transformShader); gl_error_check;

    glDeleteProgram(shaderProgram); gl_error_check;
    glDeleteShader(fragmentShader); gl_error_check;
    glDeleteShader(vertexShader); gl_error_check;

    glDeleteBuffers(NUM_BUFFERS, vbo); gl_error_check;

    glDeleteVertexArrays(NUM_BUFFERS, vao); gl_error_check;

    glfwTerminate();
    return 0;
}
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.