Example #1
0
    def __init__(self):
        self.fbo = gl.GLuint(0)
        self.rendered_texture = gl.GLuint(0)
        self.depthrenderbuffer = gl.GLuint(0)
        self.pickingbuffer = gl.GLuint(0)
        self.vertex_buffer = gl.GLuint(0)

        self.program = GlProgram(shaders.vertex_copy, shaders.fragment_copy)
        gl.glGenBuffers(1, pointer(self.vertex_buffer))
        data = (gl.GLfloat * 16)(-1, -1, 0, 0,
                                 - 1, 1, 0, 1,
                                  1, 1, 1, 1,
                                  1, -1, 1, 0)

        gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertex_buffer)
        gl.glBufferData(gl.GL_ARRAY_BUFFER, sizeof(data), data, gl.GL_STATIC_DRAW)

        gl.glGenFramebuffers(1, pointer(self.fbo))
        if not self.fbo:
            logging.error('failed fbo')
        gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)

        gl.glGenTextures(1, pointer(self.rendered_texture))
        if not self.rendered_texture:
            logging.error('failed rendered_texture')

        gl.glGenRenderbuffers(1, pointer(self.depthrenderbuffer))
        gl.glGenRenderbuffers(1, pointer(self.pickingbuffer))

        self.resize(1, 1)
Example #2
0
        def createFramebuffer(width, height):
            """Function for setting up additional buffer"""
            # create a new framebuffer
            fboId = GL.GLuint()
            GL.glGenFramebuffers(1, ctypes.byref(fboId))
            GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fboId)

            # create a texture to render to, required for warping
            texId = GL.GLuint()
            GL.glGenTextures(1, ctypes.byref(texId))
            GL.glBindTexture(GL.GL_TEXTURE_2D, texId)
            GL.glTexParameteri(GL.GL_TEXTURE_2D,
                               GL.GL_TEXTURE_MAG_FILTER,
                               GL.GL_LINEAR)
            GL.glTexParameteri(GL.GL_TEXTURE_2D,
                               GL.GL_TEXTURE_MIN_FILTER,
                               GL.GL_LINEAR)
            GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA32F_ARB,
                            int(width), int(height), 0,
                            GL.GL_RGBA, GL.GL_FLOAT, None)
            GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER,
                                      GL.GL_COLOR_ATTACHMENT0,
                                      GL.GL_TEXTURE_2D,
                                      texId,
                                      0)

            # create a render buffer
            rbId = GL.GLuint()
            GL.glGenRenderbuffers(1, ctypes.byref(rbId))
            GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, rbId)
            GL.glRenderbufferStorage(
                GL.GL_RENDERBUFFER,
                GL.GL_DEPTH24_STENCIL8,
                int(width),
                int(height))
            GL.glFramebufferRenderbuffer(
                GL.GL_FRAMEBUFFER,
                GL.GL_DEPTH_ATTACHMENT,
                GL.GL_RENDERBUFFER,
                rbId)
            GL.glFramebufferRenderbuffer(
                GL.GL_FRAMEBUFFER,
                GL.GL_STENCIL_ATTACHMENT,
                GL.GL_RENDERBUFFER,
                rbId)
            GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0)

            # clear the buffer
            GL.glClear(GL.GL_COLOR_BUFFER_BIT)
            GL.glClear(GL.GL_STENCIL_BUFFER_BIT)
            GL.glClear(GL.GL_DEPTH_BUFFER_BIT)

            GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0)

            return fboId, texId, rbId
Example #3
0
    def __init__(self, width, height, internal_format, samples=1):
        """Create an instance of a Renderbuffer object."""
        self._id = gl.GLuint()
        self._width = width
        self._height = height
        self._internal_format = internal_format

        gl.glGenRenderbuffers(1, self._id)
        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, self._id)

        if samples > 1:
            gl.glRenderbufferStorageMultisample(gl.GL_RENDERBUFFER, samples,
                                                internal_format, width, height)
        else:
            gl.glRenderbufferStorage(gl.GL_RENDERBUFFER, internal_format,
                                     width, height)

        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, 0)
Example #4
0
def createMultisampleFBO(width, height, samples, colorFormat=GL.GL_RGBA8):
    """Create a multisample framebuffer for rendering. Objects drawn to the
    framebuffer will be anti-aliased if 'GL_MULTISAMPLE' is active. A combined
    depth and stencil buffer is created with 'GL_DEPTH24_STENCIL8' format.

    Multisampling is computationally intensive for your graphics hardware and
    consumes substantial amounts of VRAM. Furthermore, samples must be
    'resolved' prior to display by copying (using blitFramebuffer, see Examples)
    to a non-multisample buffer.

    Parameters
    ----------
    width : :obj:`int`
        Buffer width in pixels.
    height : :obj:`int`
        Buffer height in pixels.
    samples : :obj:`int`
        Number of samples for multi-sampling, should be >1 and power-of-two.
        Work with one sample, but will raise a warning.
    colorFormat : :obj:`int`
        Format for color renderbuffer data (e.g. GL_RGBA8).

    Returns
    -------
    :obj:`list` of :obj:`int`
        List of OpenGL ids (FBO, Color RB, Depth/Stencil RB).

    Examples
    --------
    # create a multisample FBO with 8 samples
    msaaFbo, colorRb, depthRb = createMultisampleFBO(800, 600, 8)
    GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, msaaFbo)  # bind it

    # resolve samples into another framebuffer texture
    GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, msaaFbo)
    GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, fbo)
    gltools.blitFramebuffer((0, 0, 800, 600))

    """
    # determine if the 'samples' value is valid
    max_samples = getIntegerv(GL.GL_MAX_SAMPLES)
    if isinstance(samples, int):
        if (samples & (samples - 1)) != 0:
            raise ValueError(
                'Invalid number of samples, must be power-of-two.')
        elif samples < 0 or samples > max_samples:
            raise ValueError(
                'Invalid number of samples, must be <{}.'.format(max_samples))
        elif samples == 1:
            # warn that you are creating a single sample texture, use a regular
            # FBO instead.
            logging.warning('Creating a multisample FBO with one sample!')
    elif isinstance(samples, str):
        if samples == 'max':
            samples = max_samples

    # create the FBO, bind it for attachments
    fboId = GL.GLuint()
    GL.glGenFramebuffers(1, ctypes.byref(fboId))
    GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, fboId)

    # Color render buffer only instead of a texture. I can't think of a use case
    # to pass around a multisampled texture (yet).
    colorRbId = GL.GLuint()
    GL.glGenRenderbuffers(1, ctypes.byref(colorRbId))
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, colorRbId)
    GL.glRenderbufferStorageMultisample(GL.GL_RENDERBUFFER, samples,
                                        colorFormat, int(width), int(height))
    GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0,
                                 GL.GL_RENDERBUFFER, colorRbId)
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0)

    # setup the render buffer for depth and stencil
    depthRbId = GL.GLuint()
    GL.glGenRenderbuffers(1, ctypes.byref(depthRbId))
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, depthRbId)
    GL.glRenderbufferStorageMultisample(GL.GL_RENDERBUFFER, samples,
                                        GL.GL_DEPTH24_STENCIL8, int(width),
                                        int(height))
    GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT,
                                 GL.GL_RENDERBUFFER, depthRbId)
    GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_STENCIL_ATTACHMENT,
                                 GL.GL_RENDERBUFFER, depthRbId)
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0)

    # clear VRAM garbage
    GL.glClear(GL.GL_STENCIL_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT
               | GL.GL_STENCIL_BUFFER_BIT)

    GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0)

    # check completeness
    if not checkFramebufferComplete(fboId):
        # delete the framebuffer and all the resources associated with it
        GL.glDeleteRenderbuffers(1, colorRbId)
        GL.glDeleteRenderbuffers(1, depthRbId)
        GL.glDeleteFramebuffers(1, fboId)
        raise RuntimeError(
            'Failed to create a multisample framebuffer. Exiting.')

    return fboId, colorRbId, depthRbId
Example #5
0
def createFBO(width, height, colorFormat=GL.GL_RGBA8):
    """Generate a new Framebuffer object (FBO) for use as a render target.

    Parameters
    ----------
    width : :obj:`int`
        Buffer width in pixels.
    height : :obj:`int`
        Buffer height in pixels.
    colorFormat : :obj:`int`
        Format for color renderbuffer data (e.g. GL_RGBA8).

    Returns
    -------
    :obj:`list` of :obj:`int`
        List of OpenGL ids (FBO, Color Texture, Depth/Stencil RB).

    Examples
    --------
    # create a FBO
    frameBuffer, frameTexture, stencilTexture = createFBO(
        800, 600, GL.GL_RGBA32F_ARB)
    GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, frameBuffer)  # bind it

    """
    # Create a texture render target for color data, same _setupFramebuffer()
    #
    # We should avoid creating a texture here in the future since we might want
    # to bind an existing texture from elsewhere and reuse the same FBO.
    #
    colorTextureId = GL.GLuint()
    GL.glGenTextures(1, ctypes.byref(colorTextureId))
    GL.glBindTexture(GL.GL_TEXTURE_2D, colorTextureId)
    GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
                       GL.GL_LINEAR)
    GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
                       GL.GL_LINEAR)
    GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, colorFormat, int(width), int(height),
                    0, GL.GL_RGBA, GL.GL_FLOAT, None)
    GL.glBindTexture(GL.GL_TEXTURE_2D, 0)

    fboId = GL.GLuint()
    GL.glGenFramebuffers(1, ctypes.byref(fboId))

    # attach texture to the frame buffer
    GL.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0,
                              GL.GL_TEXTURE_2D, colorTextureId, 0)

    # create depth and stencil render buffers
    depthRbId = GL.GLuint()
    GL.glGenRenderbuffers(1, ctypes.byref(depthRbId))
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, depthRbId)
    GL.glRenderbufferStorage(GL.GL_RENDERBUFFER, GL.GL_DEPTH24_STENCIL8,
                             int(width), int(height))
    GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT,
                                 GL.GL_RENDERBUFFER, depthRbId)
    GL.glFramebufferRenderbuffer(GL.GL_FRAMEBUFFER, GL.GL_STENCIL_ATTACHMENT,
                                 GL.GL_RENDERBUFFER, depthRbId)
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0)

    # clear VRAM garbage
    GL.glClear(GL.GL_STENCIL_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT
               | GL.GL_STENCIL_BUFFER_BIT)

    GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0)

    # check completeness
    if not checkFramebufferComplete(fboId):
        # delete the framebuffer and all the resources associated with it
        GL.glDeleteTextures(1, colorTextureId)
        GL.glDeleteRenderbuffers(1, depthRbId)
        GL.glDeleteFramebuffers(1, fboId)
        raise RuntimeError('Failed to create a framebuffer. Exiting.')

    return fboId, colorTextureId, depthRbId
Example #6
0
def create_frame_buffers(width, height, num_samples):
    """Create the frame buffer objects"""
    from pyglet import gl

    # Create a frame buffer (rendering target)
    multi_fbo = gl.GLuint(0)
    gl.glGenFramebuffers(1, byref(multi_fbo))
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, multi_fbo)

    # The try block here is because some OpenGL drivers
    # (Intel GPU drivers on macbooks in particular) do not
    # support multisampling on frame buffer objects
    try:
        if not gl.gl_info.have_version(major=3, minor=2):
            raise Exception('OpenGL version 3.2+ required for \
                            GL_TEXTURE_2D_MULTISAMPLE')

        # Create a multisampled texture to render into
        fbTex = gl.GLuint(0)
        gl.glGenTextures(1, byref(fbTex))
        gl.glBindTexture(gl.GL_TEXTURE_2D_MULTISAMPLE, fbTex)
        gl.glTexImage2DMultisample(gl.GL_TEXTURE_2D_MULTISAMPLE, num_samples,
                                   gl.GL_RGBA32F, width, height, True)
        gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
                                  gl.GL_TEXTURE_2D_MULTISAMPLE, fbTex, 0)

        # Attach a multisampled depth buffer to the FBO
        depth_rb = gl.GLuint(0)
        gl.glGenRenderbuffers(1, byref(depth_rb))
        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, depth_rb)
        gl.glRenderbufferStorageMultisample(gl.GL_RENDERBUFFER, num_samples,
                                            gl.GL_DEPTH_COMPONENT, width,
                                            height)
        gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT,
                                     gl.GL_RENDERBUFFER, depth_rb)

    except:
        logger.debug('Falling back to non-multisampled frame buffer')

        # Create a plain texture texture to render into
        fbTex = gl.GLuint(0)
        gl.glGenTextures(1, byref(fbTex))
        gl.glBindTexture(gl.GL_TEXTURE_2D, fbTex)
        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0,
                        gl.GL_RGBA, gl.GL_FLOAT, None)
        gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
                                  gl.GL_TEXTURE_2D, fbTex, 0)

        # Attach depth buffer to FBO
        depth_rb = gl.GLuint(0)
        gl.glGenRenderbuffers(1, byref(depth_rb))
        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, depth_rb)
        gl.glRenderbufferStorage(gl.GL_RENDERBUFFER, gl.GL_DEPTH_COMPONENT,
                                 width, height)
        gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT,
                                     gl.GL_RENDERBUFFER, depth_rb)

    # Sanity check
    import pyglet
    if pyglet.options['debug_gl']:
        res = gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER)
        assert res == gl.GL_FRAMEBUFFER_COMPLETE

    # Create the frame buffer used to resolve the final render
    final_fbo = gl.GLuint(0)
    gl.glGenFramebuffers(1, byref(final_fbo))
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, final_fbo)

    # Create the texture used to resolve the final render
    fbTex = gl.GLuint(0)
    gl.glGenTextures(1, byref(fbTex))
    gl.glBindTexture(gl.GL_TEXTURE_2D, fbTex)
    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0,
                    gl.GL_RGBA, gl.GL_FLOAT, None)
    gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
                              gl.GL_TEXTURE_2D, fbTex, 0)
    import pyglet
    if pyglet.options['debug_gl']:
        res = gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER)
        assert res == gl.GL_FRAMEBUFFER_COMPLETE

    # Enable depth testing
    gl.glEnable(gl.GL_DEPTH_TEST)

    # Unbind the frame buffer
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)

    return multi_fbo, final_fbo
Example #7
0
def createRenderbuffer(width, height, internalFormat=GL.GL_RGBA8, samples=1):
    """Create a new Renderbuffer Object with a specified internal format. A
    multisample storage buffer is created if samples > 1.

    Renderbuffers contain image data and are optimized for use as render
    targets. See https://www.khronos.org/opengl/wiki/Renderbuffer_Object for
    more information.

    Parameters
    ----------
    width : :obj:`int`
        Buffer width in pixels.
    height : :obj:`int`
        Buffer height in pixels.
    internalFormat : :obj:`int`
        Format for renderbuffer data (e.g. GL_RGBA8, GL_DEPTH24_STENCIL8).
    samples : :obj:`int`
        Number of samples for multi-sampling, should be >1 and power-of-two.
        Work with one sample, but will raise a warning.

    Returns
    -------
    :obj:`Renderbuffer`
        A descriptor of the created renderbuffer.

    Notes
    -----
    The 'userData' field of the returned descriptor is a dictionary that can
    be used to store arbitrary data associated with the buffer.

    """
    width = int(width)
    height = int(height)

    # create a new renderbuffer ID
    rbId = GL.GLuint()
    GL.glGenRenderbuffers(1, ctypes.byref(rbId))
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, rbId)

    if samples > 1:
        # determine if the 'samples' value is valid
        maxSamples = getIntegerv(GL.GL_MAX_SAMPLES)
        if (samples & (samples - 1)) != 0:
            raise ValueError(
                'Invalid number of samples, must be power-of-two.')
        elif samples > maxSamples:
            raise ValueError(
                'Invalid number of samples, must be <{}.'.format(maxSamples))

        # create a multisample render buffer storage
        GL.glRenderbufferStorageMultisample(GL.GL_RENDERBUFFER, samples,
                                            internalFormat, width, height)

    else:
        GL.glRenderbufferStorage(GL.GL_RENDERBUFFER, internalFormat, width,
                                 height)

    # done, unbind it
    GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0)

    return Renderbuffer(rbId, GL.GL_RENDERBUFFER, width, height,
                        internalFormat, samples, samples > 1, dict())
Example #8
0
def create_frame_buffers(width: int, height: int,
                         num_samples: int) -> Tuple[int, int]:
    """Create the frame buffer objects"""

    # Create a frame buffer (rendering target)
    multi_fbo = gl.GLuint(0)
    gl.glGenFramebuffers(1, byref(multi_fbo))
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, multi_fbo)

    # The try block here is because some OpenGL drivers
    # (Intel GPU drivers on macbooks in particular) do not
    # support multisampling on frame buffer objects
    # noinspection PyBroadException
    try:
        # Create a multisampled texture to render into
        fbTex = gl.GLuint(0)
        gl.glGenTextures(1, byref(fbTex))
        gl.glBindTexture(gl.GL_TEXTURE_2D_MULTISAMPLE, fbTex)
        gl.glTexImage2DMultisample(gl.GL_TEXTURE_2D_MULTISAMPLE, num_samples,
                                   gl.GL_RGBA32F, width, height, True)
        gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
                                  gl.GL_TEXTURE_2D_MULTISAMPLE, fbTex, 0)

        # Attach a multisampled depth buffer to the FBO
        depth_rb = gl.GLuint(0)
        gl.glGenRenderbuffers(1, byref(depth_rb))
        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, depth_rb)
        gl.glRenderbufferStorageMultisample(gl.GL_RENDERBUFFER, num_samples,
                                            gl.GL_DEPTH_COMPONENT, width,
                                            height)
        gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT,
                                     gl.GL_RENDERBUFFER, depth_rb)

    except BaseException as e:
        # logger.warning(e=traceback.format_exc())
        logger.debug("Falling back to non-multisampled frame buffer")

        # Create a plain texture texture to render into
        fbTex = gl.GLuint(0)
        gl.glGenTextures(1, byref(fbTex))
        gl.glBindTexture(gl.GL_TEXTURE_2D, fbTex)
        gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0,
                        gl.GL_RGBA, gl.GL_FLOAT, None)
        gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
                                  gl.GL_TEXTURE_2D, fbTex, 0)

        # Attach depth buffer to FBO
        depth_rb = gl.GLuint(0)
        gl.glGenRenderbuffers(1, byref(depth_rb))
        gl.glBindRenderbuffer(gl.GL_RENDERBUFFER, depth_rb)
        gl.glRenderbufferStorage(gl.GL_RENDERBUFFER, gl.GL_DEPTH_COMPONENT,
                                 width, height)
        gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER, gl.GL_DEPTH_ATTACHMENT,
                                     gl.GL_RENDERBUFFER, depth_rb)

    # Sanity check

    if pyglet.options["debug_gl"]:
        res = gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER)
        assert res == gl.GL_FRAMEBUFFER_COMPLETE

    # Create the frame buffer used to resolve the final render
    final_fbo = gl.GLuint(0)
    gl.glGenFramebuffers(1, byref(final_fbo))
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, final_fbo)

    # Create the texture used to resolve the final render
    fbTex = gl.GLuint(0)
    gl.glGenTextures(1, byref(fbTex))
    gl.glBindTexture(gl.GL_TEXTURE_2D, fbTex)
    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGBA, width, height, 0,
                    gl.GL_RGBA, gl.GL_FLOAT, None)
    gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0,
                              gl.GL_TEXTURE_2D, fbTex, 0)

    if pyglet.options["debug_gl"]:
        res = gl.glCheckFramebufferStatus(gl.GL_FRAMEBUFFER)
        assert res == gl.GL_FRAMEBUFFER_COMPLETE

    # Enable depth testing
    gl.glEnable(gl.GL_DEPTH_TEST)

    # Unbind the frame buffer
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)

    return multi_fbo, final_fbo