¿Cómo resolver el problema de fuga de memoria de Python OpenGL?

Sep 28 2020

Estoy tratando de dibujar algunos traingles y renderizar algunos textos en pantalla. Pero he observado que la memoria (RAM) aumenta gradualmente solo para 6 llamadas de extracción. Tengo 8 GB de RAM. Cuando ejecuto el programa, el uso de memoria va de 4.2 a 6 en 1 minuto. Aquí está el código completo.

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GL import shaders

from shader import *

import glfw
import freetype
import glm

import numpy as np
from PIL import Image
import math

class CharacterSlot:
    def __init__(self, texture, glyph):
        self.texture = texture
        self.textureSize = (glyph.bitmap.width, glyph.bitmap.rows)

        if isinstance(glyph, freetype.GlyphSlot):
            self.bearing = (glyph.bitmap_left, glyph.bitmap_top)
            self.advance = glyph.advance.x
        elif isinstance(glyph, freetype.BitmapGlyph):
            self.bearing = (glyph.left, glyph.top)
            self.advance = None
        else:
            raise RuntimeError('unknown glyph type')

def _get_rendering_buffer(xpos, ypos, w, h, zfix=0.0):
    return np.asarray([
        xpos,     ypos + h, 0, 0,
        xpos,     ypos,     0, 1,
        xpos + w, ypos,     1, 1,
        xpos,     ypos + h, 0, 0,
        xpos + w, ypos,     1, 1,
        xpos + w, ypos + h, 1, 0
    ], np.float32)

def init_chars(shaderProgram,window_height,window_width,font_size=24,fontfile = "Vera.ttf"):
    glUseProgram(shaderProgram)
    
    #get projection
    shader_projection = glGetUniformLocation(shaderProgram, "projection")
    W = window_width
    H = window_height
    projection = glm.ortho(-W/2, W/2, -H/2, H/2)
    glUniformMatrix4fv(shader_projection, 1, GL_FALSE, glm.value_ptr(projection))
    
    #disable byte-alignment restriction
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1)

    face = freetype.Face(fontfile)
    face.set_char_size(font_size*64 )

    #load first 128 characters of ASCII set
    Characters = dict()
    for i in range(0,128):
        face.load_char(chr(i))
        glyph = face.glyph
        
        #generate texture
        texture = glGenTextures(1)
        glBindTexture(GL_TEXTURE_2D, texture)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, glyph.bitmap.width, glyph.bitmap.rows, 0,
                     GL_RED, GL_UNSIGNED_BYTE, glyph.bitmap.buffer)

        #texture options
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)

        #now store character for later use
        Characters[chr(i)] = CharacterSlot(texture,glyph)

    glBindTexture(GL_TEXTURE_2D, 0)    
    glUseProgram(0)
    
    return Characters
    
def render_text(window,shaderProgram,text,x,y,scale,Characters,color=(170,250,255)):
    r,g,b = color
    
    glUseProgram(shaderProgram)

    #configure VAO/VBO for texture quads
    VAO = glGenVertexArrays(1)
    glBindVertexArray(VAO)
    
    VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_STATIC_DRAW)
    glEnableVertexAttribArray(0)
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glBindVertexArray(0)
    
    glUniform3f(glGetUniformLocation(shaderProgram, "textColor"),r/255,g/255,b/255) 
    glActiveTexture(GL_TEXTURE0)
    glBindVertexArray(VAO)
    
    for c in text:
        ch = Characters[c]
        w, h = ch.textureSize
        w = w*scale
        h = h*scale
        vertices = _get_rendering_buffer(x,y,w,h)

        #render glyph texture over quad
        glBindTexture(GL_TEXTURE_2D, ch.texture)
        #update content of VBO memory
        glBindBuffer(GL_ARRAY_BUFFER, VBO)
        glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)

        glBindBuffer(GL_ARRAY_BUFFER, 0)
        #render quad
        glDrawArrays(GL_TRIANGLES, 0, 6)
        #now advance cursors for next glyph (note that advance is number of 1/64 pixels)
        x += (ch.advance>>6)*scale

    glBindTexture(GL_TEXTURE_2D, 0);
    glUseProgram(0)

    #UNBIND and DELETE VAO/VBO
    glBindVertexArray(0)
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glDeleteBuffers(1, id(VBO))
    glDeleteBuffers(1, id(VAO))
    

def triangle(shaderProgram,window,x=0,y=0):
    vertices = [-0.5, -0.5, 0.0,
                 0.5, -0.5, 0.0,
                 0.0, 0.5, 0.0]
    vertices = np.array(vertices, dtype=np.float32)

    VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, VBO)
    glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
    glEnableVertexAttribArray(0)

    #use shader program
    glUseProgram(shaderProgram)

    #accessing ourColor variable from shaderProgram
    vertexColorLoc = glGetUniformLocation(shaderProgram, "ourColor")
    glUniform4f(vertexColorLoc, 255, 28/255.0, 20/255.0, 0.7);

    #transform matrix
    transform = glm.mat4(1)
    transform = glm.translate(transform,glm.vec3(x,y,0))
    MVP = glGetUniformLocation(shaderProgram, "MVP")
    glUniformMatrix4fv(MVP, 1, GL_FALSE, glm.value_ptr(transform))

    #drawing trangle 
    glLineWidth(3)
    glDrawArrays(GL_TRIANGLES, 0, 3)
    
    glUseProgram(0)

    #UNBIND and DELETE VAO/VBO
    glBindVertexArray(0)
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glDeleteBuffers(1, id(VBO))
   

def main():
    glfw.init()
    window = glfw.create_window(640, 640,"EXAMPLE PROGRAM",None,None)    
    glfw.make_context_current(window)
    
    #initliaze shader programs
    shaderProgram = get_shaderProgram()
    text_shaderProgram = get_text_shaderProgram()

    #load characters and VAO/VBO for text rendering
    Characters = init_chars(text_shaderProgram,640,640)
    #window loop
    while not glfw.window_should_close(window):
        glfw.poll_events()

        #screen
        glClearColor(0, 0, 0, 1)
        glClear(GL_COLOR_BUFFER_BIT)
        glEnable(GL_BLEND)
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)

        #draw functions
        
        render_text(window,text_shaderProgram,"TRIANGLE",-50,-200,1,Characters)
        render_text(window,text_shaderProgram,"A",0,180,1,Characters)
        render_text(window,text_shaderProgram,"B",-160,-180,1,Characters)
        render_text(window,text_shaderProgram,"C",150,-180,1,Characters)
        
        triangle(shaderProgram,window)
        triangle(shaderProgram,window,x=0.5,y=0.5)
        #swap buffers 
        glfw.swap_buffers(window)
        glfw.swap_interval(1)
        
    glfw.terminate()
    
if __name__ == '__main__':
    main()

El programa de sombreado está aquí . Pero creo que el problema está en el objeto de búfer. Intenté desvincular VAO / VBO y eliminar búferes siguiendo el código. Pero no veo ningún cambio.

    #UNBIND and DELETE VAO/VBO
    glBindVertexArray(0)
    glBindBuffer(GL_ARRAY_BUFFER, 0)
    glDeleteBuffers(1, id(VBO))
    glDeleteBuffers(1, id(VAO))

Aquí está el problema relacionado donde la respuesta aceptada sugirió que glGenBuffers causa pérdida de memoria. La función alternativa glCreateBuffers no está disponible en pyopengl. ¿Cómo puedo solucionar este problema?

Respuestas

1 Rabbid76 Sep 28 2020 at 02:21

No veo ninguna buena razón para recrear el objeto Vertex Array y el objeto Array Buffer cada vez que se llama render_textrespectivamente triangle. La especificación del vértice y el número de vértices no cambian, por lo que sería suficiente actualizar el contenido del búfer.

Cree el objeto Vertex Array y el objeto Array Buffer una vez en la inicialización:

def init_buffers():
    global text_VAO, text_VBO, triangle_VAO, triangle_VBO

    text_VAO = glGenVertexArrays(1)
    glBindVertexArray(text_VAO)
    
    text_VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, text_VBO)
    glBufferData(GL_ARRAY_BUFFER, 6 * 4 * 4, None, GL_STATIC_DRAW)
    glEnableVertexAttribArray(0)
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, None)

    vertices = [-0.5, -0.5, 0.0,
                 0.5, -0.5, 0.0,
                 0.0, 0.5, 0.0]
    vertices = np.array(vertices, dtype=np.float32)

    triangle_VAO = glGenVertexArrays(1)
    glBindVertexArray(triangle_VAO)

    triangle_VBO = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, triangle_VBO)
    glBufferData(GL_ARRAY_BUFFER, vertices.nbytes, vertices, GL_STATIC_DRAW)

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
    glEnableVertexAttribArray(0)

Use entonces en las funciones render_texty triangle:

def render_text(window,shaderProgram,text,x,y,scale,Characters,color=(170,250,255)):
    # [...]

    glBindVertexArray(text_VAO)
    
    for c in text:
        # [...]

        glBindBuffer(GL_ARRAY_BUFFER, text_VBO)
        glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.nbytes, vertices)

    # glDeleteBuffers(1, id(VBO)) <--- DELETE
    # glDeleteBuffers(1, id(VAO)) <--- DELETE

def triangle(shaderProgram,window,x=0,y=0):
    
    glBindVertexArray(triangle_VAO)

    # [...]

    # glDeleteBuffers(1, id(VBO)) <--- DELETE

Invocar init_buffersantes del ciclo de aplicación:

def main():
    # [...]

    init_buffers()
    
    while not glfw.window_should_close(window):
        # [...]