如何解决 Python OpenGL 内存泄漏问题?

How to solve Python OpenGL Memory Leakage problem?

我正在尝试绘制一些小三角并在屏幕上渲染一些文本。但我观察到内存 (RAM) 仅在 6 个绘制调用中逐渐增加。我有 8 GB 内存。当我 运行 时,程序内存使用量在 1 分钟内从 4.2 变为 6。这是完整的代码。

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()

着色器程序是here。但我认为问题出在缓冲对象中。我试图通过以下代码解除绑定 VAO/VBO 并删除缓冲区。但是我没有看到任何变化。

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

这是相关的 problem,其中接受的答案表明 glGenBuffers 导致内存泄漏。替代函数 glCreateBuffers 在 pyopengl 中不可用。我该如何解决这个问题?

每次 render_text 分别调用 triangle 时,我看不出有任何理由重新创建顶点数组对象和数组缓冲区对象。顶点规格和顶点数没有改变,所以更新缓冲区的内容就足够了。

在初始化时创建一次顶点数组对象和数组缓冲区对象:

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)

在函数 render_texttriangle 中使用 then:

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

在应用程序循环之前调用 init_buffers

def main():
    # [...]

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