Search code examples
openglpython-3.xffmpegpygamepyopengl

Blitted OpenGL Textures take less memory and CPU


I'm making a game using pygame + pyopengl, and right now i'm trying to make a video player on this context. To do so I use ffmpeg to load different video formats, then convert each frame to an opengl texture, as designed below, and then play the video.

class Texture(object):
    def __init__(self, data, w=0, h=0):
        """
        Initialize the texture from 3 diferents types of data:
        filename = open the image, get its string and produce texture
        surface = get its string and produce texture
        string surface = gets it texture and use w and h provided
        """
        if type(data) == str:
            texture_data = self.load_image(data)

        elif type(data) == pygame.Surface:
            texture_data = pygame.image.tostring(data, "RGBA", True)
            self.w, self.h = data.get_size()

        elif type(data) == bytes:
            self.w, self.h = w, h
            texture_data = data

        self.texID = 0
        self.load_texture(texture_data)

    def load_image(self, data):
        texture_surface = pygame.image.load(data).convert_alpha()
        texture_data = pygame.image.tostring(texture_surface, "RGBA", True)
        self.w, self.h = texture_surface.get_size()

        return texture_data

    def load_texture(self, texture_data):
        self.texID = glGenTextures(1)

        glBindTexture(GL_TEXTURE_2D, self.texID)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.w,
                     self.h, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     texture_data)

Problem is that when i load all the textures of a given video, my RAM goes off the ceiling, about 800mb. But it's possible to work around this by blitting each texture as it loads, like shown below.

def render():
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
    glLoadIdentity()
    glDisable(GL_LIGHTING)
    glEnable(GL_TEXTURE_2D)
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
    glClearColor(0, 0, 0, 1.0)

def Draw(texture, top, left, bottom, right):
    """
    Draw the image on the Opengl Screen
    """
    # Make sure he is looking at the position (0,0,0)
    glBindTexture(GL_TEXTURE_2D, texture.texID)
    glBegin(GL_QUADS)

    # The top left of the image must be the indicated position
    glTexCoord2f(0.0, 1.0)
    glVertex2f(left, top)

    glTexCoord2f(1.0, 1.0)
    glVertex2f(right, top)

    glTexCoord2f(1.0, 0.0)
    glVertex2f(right, bottom)

    glTexCoord2f(0.0, 0.0)
    glVertex2f(left, bottom)

    glEnd()

def update(t): render() Draw(t, -0.5, -0.5, 0.5, 0.5)

# Check for basic Events on the pygame interface
for event in pygame.event.get():
    BASIC_Game.QUIT_Event(event)

pygame.display.flip()

Although this reduces the RAM consumption to an acceptable value it makes the loading time bigger than the video length.

I really don't understand why opengl works this way, but is there a way to make a texture efficient without blitting it first?


Solution

  • I can't tell for sure based off the code you have in your question right now, but I'm going to guess it's because you're creating a new Texture instance for each frame, which means that you're calling glGenTextures(1) for every frame of your video. This allocates a new buffer in memory for every frame of your video, and then stores a full, uncompressed version of the frame.

    When you blit the image, you're not generating a new texture, but just overwriting the old one. This is the solution you want, but the way you're implementing it is inefficient.

    There are a number of ways you can change the data in a texture without blitting on the CPU (assuming pygame blitting) to make things go faster, some are listed in this answer:

    https://stackoverflow.com/a/13248668/1122135