pythonopenglgtk3glfwrender-to-texture

Framebuffer issue (render to texture) with GTK3 GLArea vs GLFW: Identical OpenGL program works in GLFW but not GTK3's GLArea


About my project: I am working with OpenGL3.2 on Linux and I have built a basic application using GTK3 with a GLArea widget. The program is written in Python using the PyCharm IDE. The project interpreter is set to Python 3.8 and I have loaded up the following packages: Pillow 7.1.2, PyGObject 3.36.1, PyOpenGL 3.1.5, numpy 1.18, pyrr 0.10.3 and glfw 1.11.2 (see image at bottom)

My Problem:

I have a program that runs correctly using GLFW but will not run correctly using GTK3's GLArea. I am trying to render to a texture using a custom framebuffer object The GTK3 based program does not successfully render to the custom framebuffer. However, the GLFW based program renders just fine. There is no difference in the OpenGL code. I am only changing the windowing code. Is there something I need to enable with GTK3 in order to use custom framebuffers? The documentation for GTK3 (here) only indicates that special flags need to be set to enable the depth buffer and the stencil buffer (I have enabled both of these) but nothing related to custom texture buffers.

Any and all insights greatly appreciated.

Here is the problematic GTK3 GLArea program:

import sys
import gi, pyrr
import numpy

gi.require_version('Gtk', '3.0')
from pyrr import matrix44, Vector3
from gi.repository import Gtk
from OpenGL.GL import *
from OpenGL.GL.shaders import compileProgram
from PIL import Image


class GLCanvas(Gtk.GLArea):
    def __init__(self):
        Gtk.GLArea.__init__(self)
        self.set_required_version(3, 2)             # Sets the version of OpenGL required by this OpenGL program
        self.connect("realize", self.on_initialize) # This signal is used to initialize the OpenGL state
        self.connect("render", self.on_render)      # This signal is emitted for each frame that is rendered
        self.add_tick_callback(self.tick)           # This is a frame time clock that is called each time a frame is rendered
        self.set_start_time = False                 # Boolean to track whether the clock has been initialized
        self.set_has_depth_buffer(True)
        self.set_has_stencil_buffer(True)

    def tick(self, widget, frame_clock):
        self.current_frame_time = frame_clock.get_frame_time()  # Gets the current timestamp in microseconds
        if self.set_start_time == False:                        # Initializes the timer at the start of the program
            self.starting_time = self.current_frame_time        # Stores the timestamp set when the program was initalized
            self.set_start_time = True                          # Prevents the initialization routine from running again in this instance
        self.application_clock = (self.current_frame_time - self.starting_time)/1000000    # Calculate the total number of seconds that the program has been running
        return True                                                     # Returns true to indicate that tick callback should contine to be called

    def on_initialize(self, gl_area):
        # Prints information about our OpenGL Context
        opengl_context = self.get_context()             # Retrieves the Gdk.GLContext used by gl_area
        opengl_context.make_current()                   # Makes the Gdk.GLContext current to the drawing surfaced used by Gtk.GLArea
        major, minor = opengl_context.get_version()     # Gets the version of OpenGL currently used by the opengl_context
        print("\033[93m OpenGL context created successfully.\n -- Using OpenGL Version \033[94m" + str(major) + "." + str(minor) + "\033[0m")

        # Checks to see if there were errors creating the context
        if gl_area.get_error() != None:
            print(gl_area.get_error())

        # Get information about current GTK GLArea canvas
        window = gl_area.get_allocation()

        w_width, w_height = window.width, window.height
        self.aspect_ratio = w_width / w_height

        self.cube_positions = [(1.0, 1.0, 0.0), (0.0, 0.0, 0.0), (2.0, 0.0, 0.0)]
        self.plane_position = matrix44.create_from_translation(Vector3([-3.0, 1.0, 0.0]))

        cube = [-0.5, -0.5, 0.5, 0.0, 0.0,
                0.5, -0.5, 0.5, 1.0, 0.0,
                0.5, 0.5, 0.5, 1.0, 1.0,
                -0.5, 0.5, 0.5, 0.0, 1.0,

                -0.5, -0.5, -0.5, 0.0, 0.0,
                0.5, -0.5, -0.5, 1.0, 0.0,
                0.5, 0.5, -0.5, 1.0, 1.0,
                -0.5, 0.5, -0.5, 0.0, 1.0,

                0.5, -0.5, -0.5, 0.0, 0.0,
                0.5, 0.5, -0.5, 1.0, 0.0,
                0.5, 0.5, 0.5, 1.0, 1.0,
                0.5, -0.5, 0.5, 0.0, 1.0,

                -0.5, 0.5, -0.5, 0.0, 0.0,
                -0.5, -0.5, -0.5, 1.0, 0.0,
                -0.5, -0.5, 0.5, 1.0, 1.0,
                -0.5, 0.5, 0.5, 0.0, 1.0,

                -0.5, -0.5, -0.5, 0.0, 0.0,
                0.5, -0.5, -0.5, 1.0, 0.0,
                0.5, -0.5, 0.5, 1.0, 1.0,
                -0.5, -0.5, 0.5, 0.0, 1.0,

                0.5, 0.5, -0.5, 0.0, 0.0,
                -0.5, 0.5, -0.5, 1.0, 0.0,
                -0.5, 0.5, 0.5, 1.0, 1.0,
                0.5, 0.5, 0.5, 0.0, 1.0]

        cube = numpy.array(cube, dtype=numpy.float32)

        self.cube_indices = [0, 1, 2, 2, 3, 0,
                        4, 5, 6, 6, 7, 4,
                        8, 9, 10, 10, 11, 8,
                        12, 13, 14, 14, 15, 12,
                        16, 17, 18, 18, 19, 16,
                        20, 21, 22, 22, 23, 20]

        self.cube_indices = numpy.array(self.cube_indices, dtype=numpy.uint32)

        plane = [-0.5, -0.5, 0.0, 0.0, 0.0,
                 2.0, -0.5, 0.0, 1.0, 0.0,
                 2.0, 1.0, 0.0, 1.0, 1.0,
                 -0.5, 1.0, 0.0, 0.0, 1.0]

        plane = numpy.array(plane, dtype=numpy.float32)

        self.plane_indices = [0, 1, 2, 2, 3, 0]
        self.plane_indices = numpy.array(self.plane_indices, dtype=numpy.uint32)

        vertex_shader = """
        #version 330
        in layout(location = 0) vec3 position;
        in layout(location = 1) vec2 textCoords;
        uniform mat4 vp;
        uniform mat4 model;
        out vec2 outText;
        void main()
        {
            gl_Position =  vp * model * vec4(position, 1.0f);
            outText = textCoords;
        }
        """

        fragment_shader = """
        #version 330
        in vec2 outText;
        out vec4 outColor;
        uniform sampler2D renderedTexture;
        void main()
        {
            outColor = texture(renderedTexture, outText);
        }
        """

        shader = OpenGL.GL.shaders.compileProgram(OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
                                                  OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))

        # cube VAO
        self.cube_vao = glGenVertexArrays(1)
        glBindVertexArray(self.cube_vao)
        cube_VBO = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, cube_VBO)
        glBufferData(GL_ARRAY_BUFFER, cube.itemsize * len(cube), cube, GL_STATIC_DRAW)
        cube_EBO = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_EBO)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.cube_indices.itemsize * len(self.cube_indices), self.cube_indices, GL_STATIC_DRAW)
        # position
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, cube.itemsize * 5, ctypes.c_void_p(0))
        glEnableVertexAttribArray(0)
        # textures
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, cube.itemsize * 5, ctypes.c_void_p(12))
        glEnableVertexAttribArray(1)
        glBindVertexArray(0)

        # plane VAO
        self.plane_vao = glGenVertexArrays(1)
        glBindVertexArray(self.plane_vao)
        plane_VBO = glGenBuffers(1)
        glBindBuffer(GL_ARRAY_BUFFER, plane_VBO)
        glBufferData(GL_ARRAY_BUFFER, plane.itemsize * len(plane), plane, GL_STATIC_DRAW)
        plane_EBO = glGenBuffers(1)
        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, plane_EBO)
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.plane_indices.itemsize * len(self.plane_indices), self.plane_indices,
                     GL_STATIC_DRAW)
        # position
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, plane.itemsize * 5, ctypes.c_void_p(0))
        glEnableVertexAttribArray(0)
        # textures
        glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, plane.itemsize * 5, ctypes.c_void_p(12))
        glEnableVertexAttribArray(1)
        glBindVertexArray(0)

        ###########################################################################################

        self.plane_texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.plane_texture)
        # texture wrapping params
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        # texture filtering params
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w_width, w_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
        glBindTexture(GL_TEXTURE_2D, 0)

        depth_buff = glGenRenderbuffers(1)
        glBindRenderbuffer(GL_RENDERBUFFER, depth_buff)
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w_width, w_height)

        self.FBO = glGenFramebuffers(1)
        glBindFramebuffer(GL_FRAMEBUFFER, self.FBO)
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.plane_texture, 0)
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buff)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)

        ###########################################################################################
        self.crate_texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, self.crate_texture)
        # Set the texture wrapping parameters
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        # Set texture filtering parameters
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        # load image
        image = Image.open("models/crate.jpg")
        img_data = numpy.array(list(image.getdata()), numpy.uint8)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
        glBindTexture(GL_TEXTURE_2D, 0)
        ###########################################################################################

        glEnable(GL_DEPTH_TEST)

        view = matrix44.create_from_translation(Vector3([0.0, 0.0, -5.0]))
        projection = matrix44.create_perspective_projection_matrix(45.0, self.aspect_ratio, 0.1, 100.0)

        vp = matrix44.multiply(view, projection)

        glUseProgram(shader)
        vp_loc = glGetUniformLocation(shader, "vp")
        self.model_loc = glGetUniformLocation(shader, "model")
        glUniformMatrix4fv(vp_loc, 1, GL_FALSE, vp)

        return True

    def on_render(self, gl_area, gl_context):

        glClearColor(0.2, 0.25, 0.27, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        rot_y = pyrr.Matrix44.from_y_rotation(self.application_clock * 2)

        # draw to the default frame buffer
        glBindVertexArray(self.cube_vao)
        glBindTexture(GL_TEXTURE_2D, self.crate_texture)
        for i in range(len(self.cube_positions)):
            model = matrix44.create_from_translation(self.cube_positions[i])
            if i == 0:
                glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, rot_y * model)
            elif i == 1:
                glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)
            else:
                glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)

            glDrawElements(GL_TRIANGLES, len(self.cube_indices), GL_UNSIGNED_INT, None)

        # draw to the custom frame buffer
        glBindFramebuffer(GL_FRAMEBUFFER, self.FBO)
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        for i in range(len(self.cube_positions)):
            model = matrix44.create_from_translation(self.cube_positions[i])
            if i == 0:
                glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, rot_y * model)
            elif i == 1:
                glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)
            else:
                glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)

            glDrawElements(GL_TRIANGLES, len(self.cube_indices), GL_UNSIGNED_INT, None)
        glBindVertexArray(0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)

        # draw the plane
        glBindVertexArray(self.plane_vao)
        glBindTexture(GL_TEXTURE_2D, self.plane_texture)
        glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, self.plane_position)
        glDrawElements(GL_TRIANGLES, len(self.plane_indices), GL_UNSIGNED_INT, None)
        glBindVertexArray(0)

        self.queue_draw()   # Schedules a redraw for Gtk.GLArea

class RootWindow(Gtk.Application):
    def __init__(self):
        Gtk.Application.__init__(self)

    def do_activate(self):
        window = Gtk.Window(application=self)
        window.set_title("Render To Texture")
        window.set_default_size(1280, 720)
        window.set_position(Gtk.WindowPosition.CENTER)
        window.add(GLCanvas())
        window.show_all()

win = RootWindow()
exit_status = win.run(sys.argv)
sys.exit(exit_status)

Here is the full working GLFW program (original source):

import glfw
from OpenGL.GL import *
import OpenGL.GL.shaders
import numpy
import pyrr
from pyrr import matrix44, Vector3
from PIL import Image


def window_resize(window, width, height):
    glViewport(0, 0, width, height)


cube_positions = [(1.0, 1.0, 0.0), (0.0, 0.0, 0.0), (2.0, 0.0, 0.0)]
plane_position = matrix44.create_from_translation(Vector3([-3.0, 1.0, 0.0]))


def main():
    if not glfw.init():
        return

    w_width, w_height = 1280, 720
    aspect_ratio = w_width / w_height

    window = glfw.create_window(w_width, w_height, "My OpenGL window", None, None)

    if not window:
        glfw.terminate()
        return

    glfw.make_context_current(window)
    glfw.set_window_size_callback(window, window_resize)

    cube = [-0.5, -0.5,  0.5, 0.0, 0.0,
             0.5, -0.5,  0.5, 1.0, 0.0,
             0.5,  0.5,  0.5, 1.0, 1.0,
            -0.5,  0.5,  0.5, 0.0, 1.0,

            -0.5, -0.5, -0.5, 0.0, 0.0,
             0.5, -0.5, -0.5, 1.0, 0.0,
             0.5,  0.5, -0.5, 1.0, 1.0,
            -0.5,  0.5, -0.5, 0.0, 1.0,

             0.5, -0.5, -0.5, 0.0, 0.0,
             0.5,  0.5, -0.5, 1.0, 0.0,
             0.5,  0.5,  0.5, 1.0, 1.0,
             0.5, -0.5,  0.5, 0.0, 1.0,

            -0.5,  0.5, -0.5, 0.0, 0.0,
            -0.5, -0.5, -0.5, 1.0, 0.0,
            -0.5, -0.5,  0.5, 1.0, 1.0,
            -0.5,  0.5,  0.5, 0.0, 1.0,

            -0.5, -0.5, -0.5, 0.0, 0.0,
             0.5, -0.5, -0.5, 1.0, 0.0,
             0.5, -0.5,  0.5, 1.0, 1.0,
            -0.5, -0.5,  0.5, 0.0, 1.0,

             0.5, 0.5, -0.5,  0.0, 0.0,
            -0.5, 0.5, -0.5,  1.0, 0.0,
            -0.5, 0.5,  0.5,  1.0, 1.0,
             0.5, 0.5,  0.5,  0.0, 1.0]

    cube = numpy.array(cube, dtype=numpy.float32)

    cube_indices = [ 0,  1,  2,  2,  3,  0,
                     4,  5,  6,  6,  7,  4,
                     8,  9, 10, 10, 11,  8,
                    12, 13, 14, 14, 15, 12,
                    16, 17, 18, 18, 19, 16,
                    20, 21, 22, 22, 23, 20]

    cube_indices = numpy.array(cube_indices, dtype=numpy.uint32)

    plane = [-0.5, -0.5, 0.0, 0.0, 0.0,
              2.0, -0.5, 0.0, 1.0, 0.0,
              2.0,  1.0, 0.0, 1.0, 1.0,
             -0.5,  1.0, 0.0, 0.0, 1.0]

    plane = numpy.array(plane, dtype=numpy.float32)

    plane_indices = [0, 1, 2, 2, 3, 0]
    plane_indices = numpy.array(plane_indices, dtype=numpy.uint32)

    vertex_shader = """
    #version 330
    in layout(location = 0) vec3 position;
    in layout(location = 1) vec2 textCoords;
    uniform mat4 vp;
    uniform mat4 model;
    out vec2 outText;
    void main()
    {
        gl_Position =  vp * model * vec4(position, 1.0f);
        outText = textCoords;
    }
    """

    fragment_shader = """
    #version 330
    in vec2 outText;
    out vec4 outColor;
    uniform sampler2D renderedTexture;
    void main()
    {
        outColor = texture(renderedTexture, outText);
    }
    """

    shader = OpenGL.GL.shaders.compileProgram(OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
                                              OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))

    # cube VAO
    cube_vao = glGenVertexArrays(1)
    glBindVertexArray(cube_vao)
    cube_VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, cube_VBO)
    glBufferData(GL_ARRAY_BUFFER, cube.itemsize * len(cube), cube, GL_STATIC_DRAW)
    cube_EBO = glGenBuffers(1)
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_EBO)
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, cube_indices.itemsize * len(cube_indices), cube_indices, GL_STATIC_DRAW)
    # position
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, cube.itemsize * 5, ctypes.c_void_p(0))
    glEnableVertexAttribArray(0)
    # textures
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, cube.itemsize * 5, ctypes.c_void_p(12))
    glEnableVertexAttribArray(1)
    glBindVertexArray(0)


    # plane VAO
    plane_vao = glGenVertexArrays(1)
    glBindVertexArray(plane_vao)
    plane_VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, plane_VBO)
    glBufferData(GL_ARRAY_BUFFER, plane.itemsize * len(plane), plane, GL_STATIC_DRAW)
    plane_EBO = glGenBuffers(1)
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, plane_EBO)
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, plane_indices.itemsize * len(plane_indices), plane_indices, GL_STATIC_DRAW)
    # position
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, plane.itemsize * 5, ctypes.c_void_p(0))
    glEnableVertexAttribArray(0)
    # textures
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, plane.itemsize * 5, ctypes.c_void_p(12))
    glEnableVertexAttribArray(1)
    glBindVertexArray(0)

    ###########################################################################################

    plane_texture = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, plane_texture)
    # texture wrapping params
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
    # texture filtering params
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w_width, w_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
    glBindTexture(GL_TEXTURE_2D, 0)

    depth_buff = glGenRenderbuffers(1)
    glBindRenderbuffer(GL_RENDERBUFFER, depth_buff)
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w_width, w_height)

    FBO = glGenFramebuffers(1)
    glBindFramebuffer(GL_FRAMEBUFFER, FBO)
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, plane_texture, 0)
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buff)
    glBindFramebuffer(GL_FRAMEBUFFER, 0)

    ###########################################################################################
    crate_texture = glGenTextures(1)
    glBindTexture(GL_TEXTURE_2D, crate_texture)
    # Set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
    # Set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
    # load image
    image = Image.open("res/crate.jpg")
    img_data = numpy.array(list(image.getdata()), numpy.uint8)
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
    glBindTexture(GL_TEXTURE_2D, 0)
    ###########################################################################################

    glEnable(GL_DEPTH_TEST)

    view = matrix44.create_from_translation(Vector3([0.0, 0.0, -5.0]))
    projection = matrix44.create_perspective_projection_matrix(45.0, aspect_ratio, 0.1, 100.0)

    vp = matrix44.multiply(view, projection)

    glUseProgram(shader)
    vp_loc = glGetUniformLocation(shader, "vp")
    model_loc = glGetUniformLocation(shader, "model")
    glUniformMatrix4fv(vp_loc, 1, GL_FALSE, vp)

    while not glfw.window_should_close(window):
        glfw.poll_events()

        glClearColor(0.2, 0.25, 0.27, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        rot_y = pyrr.Matrix44.from_y_rotation(glfw.get_time() * 2)

        # draw to the default frame buffer
        glBindVertexArray(cube_vao)
        glBindTexture(GL_TEXTURE_2D, crate_texture)
        for i in range(len(cube_positions)):
            model = matrix44.create_from_translation(cube_positions[i])
            if i == 0:
                glUniformMatrix4fv(model_loc, 1, GL_FALSE, rot_y * model)
            elif i == 1:
                glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)
            else:
                glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)

            glDrawElements(GL_TRIANGLES, len(cube_indices), GL_UNSIGNED_INT, None)

        # draw to the custom frame buffer
        glBindFramebuffer(GL_FRAMEBUFFER, FBO)
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        for i in range(len(cube_positions)):
            model = matrix44.create_from_translation(cube_positions[i])
            if i == 0:
                glUniformMatrix4fv(model_loc, 1, GL_FALSE, rot_y * model)
            elif i == 1:
                glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)
            else:
                glUniformMatrix4fv(model_loc, 1, GL_FALSE, model)

            glDrawElements(GL_TRIANGLES, len(cube_indices), GL_UNSIGNED_INT, None)
        glBindVertexArray(0)
        glBindFramebuffer(GL_FRAMEBUFFER, 0)

        # draw the plane
        glBindVertexArray(plane_vao)
        glBindTexture(GL_TEXTURE_2D, plane_texture)
        glUniformMatrix4fv(model_loc, 1, GL_FALSE, plane_position)
        glDrawElements(GL_TRIANGLES, len(plane_indices), GL_UNSIGNED_INT, None)
        glBindVertexArray(0)

        glfw.swap_buffers(window)

    glfw.terminate()


if __name__ == "__main__":
    main()

Settings for Project Interpreter


Solution

  • Solution

    It turns out that GLArea is not using framebuffer 0 as the default. The issue here is resetting the framebuffer back to 0 at the end of the rendering loop with glBindFramebuffer(GL_FRAMEBUFFER, 0). Instead, use default_ID = glGetIntegerv(GL_FRAMEBUFFER_BINDING) at the beginning of the rendering loop to get the current default ID. At the end of the loop, reset to the default framebuffer using glBindFramebuffer(GL_FRAMEBUFFER, default_ID).

        import sys
    import gi, pyrr
    import numpy
    
    gi.require_version('Gtk', '3.0')
    from pyrr import matrix44, Vector3
    from gi.repository import Gtk
    from OpenGL.GL import *
    from OpenGL.GL.shaders import compileProgram
    from PIL import Image
    
    
    class GLCanvas(Gtk.GLArea):
        def __init__(self):
            Gtk.GLArea.__init__(self)
            self.set_required_version(3, 2)             # Sets the version of OpenGL required by this OpenGL program
            self.connect("realize", self.on_initialize) # This signal is used to initialize the OpenGL state
            self.connect("render", self.on_render)      # This signal is emitted for each frame that is rendered
            self.add_tick_callback(self.tick)           # This is a frame time clock that is called each time a frame is rendered
            self.set_start_time = False                 # Boolean to track whether the clock has been initialized
            self.set_has_depth_buffer(True)
            self.set_has_stencil_buffer(True)
    
        def tick(self, widget, frame_clock):
            self.current_frame_time = frame_clock.get_frame_time()  # Gets the current timestamp in microseconds
            if self.set_start_time == False:                        # Initializes the timer at the start of the program
                self.starting_time = self.current_frame_time        # Stores the timestamp set when the program was initalized
                self.set_start_time = True                          # Prevents the initialization routine from running again in this instance
            self.application_clock = (self.current_frame_time - self.starting_time)/1000000    # Calculate the total number of seconds that the program has been running
            return True                                                     # Returns true to indicate that tick callback should contine to be called
    
        def on_initialize(self, gl_area):
            # Prints information about our OpenGL Context
            opengl_context = self.get_context()             # Retrieves the Gdk.GLContext used by gl_area
            opengl_context.make_current()                   # Makes the Gdk.GLContext current to the drawing surfaced used by Gtk.GLArea
            major, minor = opengl_context.get_version()     # Gets the version of OpenGL currently used by the opengl_context
            # https://stackoverflow.com/questions/287871/how-to-print-colored-text-in-terminal-in-python
            print("\033[93m OpenGL context created successfully.\n -- Using OpenGL Version \033[94m" + str(major) + "." + str(minor) + "\033[0m")
    
            # Checks to see if there were errors creating the context
            if gl_area.get_error() != None:
                print(gl_area.get_error())
    
            # Get information about current GTK GLArea canvas
            window = gl_area.get_allocation()
    
            w_width, w_height = window.width, window.height
            self.aspect_ratio = w_width / w_height
    
            self.cube_positions = [(1.0, 1.0, 0.0), (0.0, 0.0, 0.0), (2.0, 0.0, 0.0)]
            self.plane_position = matrix44.create_from_translation(Vector3([-3.0, 1.0, 0.0]))
    
            cube = [-0.5, -0.5, 0.5, 0.0, 0.0,
                    0.5, -0.5, 0.5, 1.0, 0.0,
                    0.5, 0.5, 0.5, 1.0, 1.0,
                    -0.5, 0.5, 0.5, 0.0, 1.0,
    
                    -0.5, -0.5, -0.5, 0.0, 0.0,
                    0.5, -0.5, -0.5, 1.0, 0.0,
                    0.5, 0.5, -0.5, 1.0, 1.0,
                    -0.5, 0.5, -0.5, 0.0, 1.0,
    
                    0.5, -0.5, -0.5, 0.0, 0.0,
                    0.5, 0.5, -0.5, 1.0, 0.0,
                    0.5, 0.5, 0.5, 1.0, 1.0,
                    0.5, -0.5, 0.5, 0.0, 1.0,
    
                    -0.5, 0.5, -0.5, 0.0, 0.0,
                    -0.5, -0.5, -0.5, 1.0, 0.0,
                    -0.5, -0.5, 0.5, 1.0, 1.0,
                    -0.5, 0.5, 0.5, 0.0, 1.0,
    
                    -0.5, -0.5, -0.5, 0.0, 0.0,
                    0.5, -0.5, -0.5, 1.0, 0.0,
                    0.5, -0.5, 0.5, 1.0, 1.0,
                    -0.5, -0.5, 0.5, 0.0, 1.0,
    
                    0.5, 0.5, -0.5, 0.0, 0.0,
                    -0.5, 0.5, -0.5, 1.0, 0.0,
                    -0.5, 0.5, 0.5, 1.0, 1.0,
                    0.5, 0.5, 0.5, 0.0, 1.0]
    
            cube = numpy.array(cube, dtype=numpy.float32)
    
            self.cube_indices = [0, 1, 2, 2, 3, 0,
                            4, 5, 6, 6, 7, 4,
                            8, 9, 10, 10, 11, 8,
                            12, 13, 14, 14, 15, 12,
                            16, 17, 18, 18, 19, 16,
                            20, 21, 22, 22, 23, 20]
    
            self.cube_indices = numpy.array(self.cube_indices, dtype=numpy.uint32)
    
            plane = [-0.5, -0.5, 0.0, 0.0, 0.0,
                     2.0, -0.5, 0.0, 1.0, 0.0,
                     2.0, 1.0, 0.0, 1.0, 1.0,
                     -0.5, 1.0, 0.0, 0.0, 1.0]
    
            plane = numpy.array(plane, dtype=numpy.float32)
    
            self.plane_indices = [0, 1, 2, 2, 3, 0]
            self.plane_indices = numpy.array(self.plane_indices, dtype=numpy.uint32)
    
            vertex_shader = """
            #version 330
            in vec3 position;
            in vec2 textCoords;
            uniform mat4 vp;
            uniform mat4 model;
            out vec2 outText;
            void main()
            {
                gl_Position =  vp * model * vec4(position, 1.0f);
                outText = textCoords;
            }
            """
    
            fragment_shader = """
            #version 330
            in vec2 outText;
            out vec4 outColor;
            uniform sampler2D renderedTexture;
            void main()
            {
                outColor = texture(renderedTexture, outText);
            }
            """
    
            shader = OpenGL.GL.shaders.compileProgram(OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
                                                      OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))
    
            # cube VAO
            self.cube_vao = glGenVertexArrays(1)
            glBindVertexArray(self.cube_vao)
            cube_VBO = glGenBuffers(1)
            glBindBuffer(GL_ARRAY_BUFFER, cube_VBO)
            glBufferData(GL_ARRAY_BUFFER, cube.itemsize * len(cube), cube, GL_STATIC_DRAW)
            cube_EBO = glGenBuffers(1)
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cube_EBO)
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.cube_indices.itemsize * len(self.cube_indices), self.cube_indices, GL_STATIC_DRAW)
            # position
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, cube.itemsize * 5, ctypes.c_void_p(0))
            glEnableVertexAttribArray(0)
            # textures
            glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, cube.itemsize * 5, ctypes.c_void_p(12))
            glEnableVertexAttribArray(1)
            glBindVertexArray(0)
    
            # plane VAO
            self.plane_vao = glGenVertexArrays(1)
            glBindVertexArray(self.plane_vao)
            plane_VBO = glGenBuffers(1)
            glBindBuffer(GL_ARRAY_BUFFER, plane_VBO)
            glBufferData(GL_ARRAY_BUFFER, plane.itemsize * len(plane), plane, GL_STATIC_DRAW)
            plane_EBO = glGenBuffers(1)
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, plane_EBO)
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, self.plane_indices.itemsize * len(self.plane_indices), self.plane_indices,
                         GL_STATIC_DRAW)
            # position
            glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, plane.itemsize * 5, ctypes.c_void_p(0))
            glEnableVertexAttribArray(0)
            # textures
            glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, plane.itemsize * 5, ctypes.c_void_p(12))
            glEnableVertexAttribArray(1)
            glBindVertexArray(0)
    
            ###########################################################################################
    
            self.plane_texture = glGenTextures(1)
            glBindTexture(GL_TEXTURE_2D, self.plane_texture)
            # texture wrapping params
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
            # texture filtering params
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w_width, w_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
            glBindTexture(GL_TEXTURE_2D, 0)
    
            depth_buff = glGenRenderbuffers(1)
            glBindRenderbuffer(GL_RENDERBUFFER, depth_buff)
            glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w_width, w_height)
    
            self.FBO = glGenFramebuffers(1)
            glBindFramebuffer(GL_FRAMEBUFFER, self.FBO)
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, self.plane_texture, 0)
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_buff)
            glBindFramebuffer(GL_FRAMEBUFFER, 0)
    
            ###########################################################################################
            self.crate_texture = glGenTextures(1)
            glBindTexture(GL_TEXTURE_2D, self.crate_texture)
            # Set the texture wrapping parameters
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
            # Set texture filtering parameters
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
            # load image
            image = Image.open("models/crate.jpg")
            img_data = numpy.array(list(image.getdata()), numpy.uint8)
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width, image.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img_data)
            glBindTexture(GL_TEXTURE_2D, 0)
            ###########################################################################################
    
            glEnable(GL_DEPTH_TEST)
    
            view = matrix44.create_from_translation(Vector3([0.0, 0.0, -5.0]))
            projection = matrix44.create_perspective_projection_matrix(45.0, self.aspect_ratio, 0.1, 100.0)
    
            vp = matrix44.multiply(view, projection)
    
            glUseProgram(shader)
            vp_loc = glGetUniformLocation(shader, "vp")
            self.model_loc = glGetUniformLocation(shader, "model")
            glUniformMatrix4fv(vp_loc, 1, GL_FALSE, vp)
    
            return True
    
        def on_render(self, gl_area, gl_context):
            default_ID = glGetIntegerv(GL_FRAMEBUFFER_BINDING)
            glClearColor(0.2, 0.25, 0.27, 1.0)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
            rot_y = pyrr.Matrix44.from_y_rotation(self.application_clock * 2)
    
            # draw to the default frame buffer
            glBindVertexArray(self.cube_vao)
            glBindTexture(GL_TEXTURE_2D, self.crate_texture)
            for i in range(len(self.cube_positions)):
                model = matrix44.create_from_translation(self.cube_positions[i])
                if i == 0:
                    glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, rot_y * model)
                elif i == 1:
                    glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)
                else:
                    glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)
    
                glDrawElements(GL_TRIANGLES, len(self.cube_indices), GL_UNSIGNED_INT, None)
    
            # draw to the custom frame buffer
            glBindFramebuffer(GL_FRAMEBUFFER, self.FBO)
            glClearColor(0.0, 0.0, 0.0, 1.0)
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
    
            for i in range(len(self.cube_positions)):
                model = matrix44.create_from_translation(self.cube_positions[i])
                if i == 0:
                    glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, rot_y * model)
                elif i == 1:
                    glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)
                else:
                    glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, model)
    
                glDrawElements(GL_TRIANGLES, len(self.cube_indices), GL_UNSIGNED_INT, None)
    
            glBindFramebuffer(GL_FRAMEBUFFER, default_ID)
            glBindVertexArray(0)
    
            # draw the plane
            glBindVertexArray(self.plane_vao)
            glBindTexture(GL_TEXTURE_2D, self.plane_texture)
            glUniformMatrix4fv(self.model_loc, 1, GL_FALSE, self.plane_position)
            glDrawElements(GL_TRIANGLES, len(self.plane_indices), GL_UNSIGNED_INT, None)
            glBindVertexArray(0)
    
            self.queue_draw()   # Schedules a redraw for Gtk.GLArea
    
    class RootWindow(Gtk.Application):
        def __init__(self):
            Gtk.Application.__init__(self)
    
        def do_activate(self):
            window = Gtk.Window(application=self)
            window.set_title("Render To Texture")
            window.set_default_size(1280, 720)
            window.set_position(Gtk.WindowPosition.CENTER)
            window.add(GLCanvas())
            window.show_all()
    
    win = RootWindow()
    exit_status = win.run(sys.argv)
    sys.exit(exit_status)