Search code examples
cmacosopengltexturesframebuffer

off-screen rendering with fbo and texture got black screen


I am developing a simple off-screen rendering application on mac os x (10.12), but always got a black screen. I confirm that the shader compiles and links no problem, and changes the texture to a single color is also ok. Then I comment the code about depth information related, but no effect. When single-step debugging, did not find glGetError return error. The following is the code, may I ask what problems?

GLuint program;
if (compile_link_shader(vertex_shader_source, fragment_shader_source, &program) < 0) {
   base_error_log("call compile_link_shader failed\n");
   return -1;
}
glViewport(0, 0, target->width, target->height);

GLuint vao, vbo, ibo, loc_attr;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ibo);

glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

glBufferData(GL_ARRAY_BUFFER, sizeof(pos_coord), pos_coord, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(element_index), element_index, GL_STATIC_DRAW);

loc_attr = _static_cast(GLuint) glGetAttribLocation(program, "vertexPosition");
glVertexAttribPointer(loc_attr, 2, GL_FLOAT, GL_FALSE,
                          4 * sizeof(GLfloat), _static_cast(GLvoid*) (0 * 2 * sizeof(GLfloat)));
glEnableVertexAttribArray(loc_attr);

loc_attr = (GLuint) glGetAttribLocation(program, "textureCoordinate");
glVertexAttribPointer(loc_attr, 2, GL_FLOAT, GL_FALSE,
                          4 * sizeof(GLfloat), (GLvoid*) (1 * 2 * sizeof(GLfloat)));
glEnableVertexAttribArray(loc_attr);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GLuint texture;
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/* glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // Removed from GL 3.1 and above */

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLenum format = origin->channels == 3 ? GL_RGB : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, origin->width, origin->height, 0,
                 format, GL_UNSIGNED_BYTE, origin->image);
glGenerateMipmap(GL_TEXTURE_2D);

glBindTexture(GL_TEXTURE_2D, 0);

GLuint fbo;
GLuint depthBufferName;
glGenRenderbuffers(1, &depthBufferName);
glBindRenderbuffer(GL_RENDERBUFFER, depthBufferName);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, origin->width, origin->height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);

glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferName);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        base_error_log("glCheckFramebufferStatus not GL_FRAMEBUFFER_COMPLETE\n");
    return -1;
}

glUseProgram(program);

GLint loc_uniform = glGetUniformLocation(program, "texture");
glUniform1i(loc_uniform, texture);

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glCullFace(GL_BACK);
glFrontFace(GL_CCW);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);

glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glDrawElements(GL_TRIANGLES, 2 * 3, GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);

glReadBuffer(GL_COLOR_ATTACHMENT0);
target->image = malloc(_static_cast(size_t) (target->width * target->height * target->channels));
if (!target->image) {
     base_error_log("malloc target->image failed\n");
     return -1;
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glReadPixels(0, 0, target->width, target->height, (target->channels == 3 ? GL_RGB : GL_RGBA),
                 GL_UNSIGNED_BYTE, target->image);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

and another part is:

#define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED
#define GLEW_STATIC
#include <GL/glew.h>
#include <OpenGL/OpenGL.h>
#include <OpenGL/gl3.h>
#include <OpenGL/gl3ext.h>
static GLfloat pos_coord[] = {
        // Vertex Positions     // Texture Coordinates
        -1.0f,  1.0f,           0.0f, 1.0f, // Left Top
         1.0f,  1.0f,           1.0f, 1.0f, // Right Top
         1.0f, -1.0f,           1.0f, 0.0f, // Right Bottom
        -1.0f, -1.0f,           0.0f, 0.0f, // Left Bottom
};
static GLuint element_index[] = { // Note that we start from 0 with GL_CCW!
        0, 3, 2, // First Triangle
        0, 2, 1, // Second Triangle
};
static const char *vertex_shader_source = ""
        "#version 330 core\n"
        "precision mediump float;\n"
        "in vec2 vertexPosition;\n"
        "in vec2 textureCoordinate;\n"
        "out vec2 varyingTextureCoordinate;\n"
        "void main() {\n"
        "    gl_Position = vec4(vertexPosition, 0.0, 1.0);\n"
        "    varyingTextureCoordinate = textureCoordinate;\n"
        "}";
static const char *default_fragment_shader_source = ""
        "#version 330 core\n"
        "precision mediump float;\n"
        "in vec2 varyingTextureCoordinate;\n"
        "out vec4 fragmentColor;\n"
        "uniform sampler2D texture;\n"
        "void main() {\n"
        "    fragmentColor = texture(texture, varyingTextureCoordinate);\n"
       /* "    fragmentColor = vec4(1.0, 1.0, 0.0, 1.0);\n" */ // this line work fine
        "}";

The following code has same effect: black screen. Is it due to same problem?

glViewport(0, 0, target->width, target->height);

GLuint vao, vbo, ibo, loc_attr;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ibo);

glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

glBufferData(GL_ARRAY_BUFFER, sizeof(pos_coord), pos_coord, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(element_index), element_index, GL_STATIC_DRAW);

loc_attr = _static_cast(GLuint) glGetAttribLocation(program, "vertexPosition");
glVertexAttribPointer(loc_attr, 2, GL_FLOAT, GL_FALSE,
                      4 * sizeof(GLfloat), _static_cast(GLvoid*) (0 * 2 * sizeof(GLfloat)));
glEnableVertexAttribArray(loc_attr);

loc_attr = (GLuint) glGetAttribLocation(program, "textureCoordinate");
glVertexAttribPointer(loc_attr, 2, GL_FLOAT, GL_FALSE,
                      4 * sizeof(GLfloat), (GLvoid*) (1 * 2 * sizeof(GLfloat)));
glEnableVertexAttribArray(loc_attr);

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

glActiveTexture(GL_TEXTURE0);
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
/* glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // Removed from GL 3.1 and above */

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLenum format = origin->channels == 3 ? GL_RGB : GL_RGBA;
glTexImage2D(GL_TEXTURE_2D, 0, format, origin->width, origin->height, 0,
             format, GL_UNSIGNED_BYTE, origin->image);
glGenerateMipmap(GL_TEXTURE_2D);

glBindTexture(GL_TEXTURE_2D, 0);

GLuint fbo;
GLuint colorBufferName, depthBufferName;

glGenRenderbuffers(1, &colorBufferName);
glBindRenderbuffer(GL_RENDERBUFFER, colorBufferName);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, origin->width, origin->height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);

glGenRenderbuffers(1, &depthBufferName);
glBindRenderbuffer(GL_RENDERBUFFER, depthBufferName);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, origin->width, origin->height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);

glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBufferName);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBufferName);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
    base_error_log("glCheckFramebufferStatus not GL_FRAMEBUFFER_COMPLETE\n");
    return -1;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);

glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glUseProgram(program);

GLint loc_uniform = glGetUniformLocation(program, "texture");
glUniform1i(loc_uniform, texture);

glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glDrawElements(GL_TRIANGLES, 2 * 3, GL_UNSIGNED_INT, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);

glDrawBuffer(GL_COLOR_ATTACHMENT0);
glReadBuffer(GL_COLOR_ATTACHMENT0);

target->image = malloc(_static_cast(size_t) (target->width * target->height * target->channels));
if (!target->image) {
    base_error_log("malloc target->image failed\n");
    return -1;
}
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glReadPixels(0, 0, target->width, target->height, (target->channels == 3 ? GL_RGB : GL_RGBA),
             GL_UNSIGNED_BYTE, target->image);

glBindFramebuffer(GL_FRAMEBUFFER, 0);

Solution

  • The texture is a color attachment to the frame buffer which is written to and the texture is bound to the texture sampler which is read from. This is an undefined behavior.

    Khronos OpenGL documantation Framebuffer Object - Feedback Loops says:

    It is possible to bind a texture to an FBO, bind that same texture to a shader, and then try to render with it at the same time.
    It is perfectly valid to bind one image from a texture to an FBO and then render with that texture, as long as you prevent yourself from sampling from that image. If you do try to read and write to the same image, you get undefined results. Meaning it may do what you want, the sampler may get old data, the sampler may get half old and half new data, or it may get garbage data. Any of these are possible outcomes.
    Do not do this. What you will get is undefined behavior.


    Since the texture is the color attachment to the active framebuffer, the glClear command will clear the texture:

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    
    .....
    
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    

    If you want to render to a texture, then you have to create a second texture, which you attach to the frame buffers color plane and write to.

    see also:


    Extension to the answer

    You have to set the index of the texture unit to the texture sampler uniform, not the texture object itself. The texture unit is set by glActiveTexture and glBindTexture binds the texture to the target (e.g. GL_TEXTURE_2D) and the active texture unit.
    By the way, you have to rename the name of the uniform texture, because it is identical with the name of the GLSL function texture. In the following code I use u_texture.

    GLint loc_uniform = glGetUniformLocation( program, "u_texture" );
    
    .....
    
    int textureUnitIndex = 0; // 0, 1, 2 ...
    glActiveTexture( GL_TEXTURE0 + textureUnitIndex );
    glBindTexture( GL_TEXTURE_2D, texture );
    
    .....
    
    glUseProgram( program );
    glUniform1i( loc_uniform, textureUnitIndex ); // <-- index of the texture unit
                                                  //     NOT the texture object