如何加速使用Python,Pyglet和顶点缓冲区编写的OpenGL代码?

时间:2016-03-03 16:56:13

标签: python opengl pyglet

我希望升级使用Python库" Pygame"它基于SDL 1 C ++库,使用更现代的OpenGL代码。我有一个简单的Pygame程序,它绘制了2000个矩形:

import pygame
import random
import time

# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
RED = (255, 0, 0)


class Rectangle():
    '''Draw a rectangle'''
    def __init__(self):
        '''These are the rectangle's attributes.'''
        self.x = 0
        self.y = 0
        self.width = 0
        self.height = 0
        self.change_x = 0
        self.change_y = 0
        self.color = [0, 0, 0]

    def draw(self, screen):
        '''Drawing the rectangle.'''
        pygame.draw.rect(screen, self.color, [self.x, self.y, self.width, self.height])

    def move(self):
        '''Moving the rectangle around the screen.'''
        self.x += self.change_x
        self.y += self.change_y


def main():
    pygame.init()

    # Set the width and height of the screen [width, height]
    size = (700, 500)
    screen = pygame.display.set_mode(size)

    pygame.display.set_caption("My Game")

    # Loop until the user clicks the close button.
    done = False

    # Used to manage how fast the screen updates
    clock = pygame.time.Clock()

    my_list = []
    color_list = []
    '''Creates 1000 rectangles.'''
    for i in range(2000):
        my_object = Rectangle()

        r = random.randrange(256)
        g = random.randrange(256)
        b = random.randrange(256)
        my_object.color = [r, g, b]
        color_list.append(my_object)

        my_object.x = random.randrange(701)
        my_object.y = random.randrange(501)

        my_object.change_x = random.randrange(-3, 4)
        my_object.change_y = random.randrange(-3, 4)

        my_object.width = random.randrange(20, 71)
        my_object.height = random.randrange(20, 71)

        my_list.append(my_object)

    # -------- Main Program Loop -----------
    while not done:
        # --- Main event loop
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

        screen.fill(WHITE)

        # Start the clock
        start = time.time()

        for item in my_list:
            item.draw(screen)
            item.move()

        # --- Go ahead and update the screen with what we've drawn.
        pygame.display.flip()

        # End the clock
        elapsed = time.time() - start
        print(elapsed)

        # --- Limit to 60 frames per second
        clock.tick(60)

    # Close the window and quit.
    pygame.quit()

main()

在我的电脑上绘制每一帧大约需要0.012秒。

我还有一个使用Python Pyglet库来访问OpenGL的程序。我正在使用顶点缓冲区加快速度。这是代码:

"""
This example uses OpenGL via Pyglet and draws
a bunch of rectangles on the screen.
"""

import random
import time
import pyglet.gl as GL
import pyglet
import ctypes

# Set up the constants
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500

RECT_WIDTH = 50
RECT_HEIGHT = 50


class VertexBuffer():
    """ Class to hold vertex buffer info. """
    def __init__(self, vbo_id, size, width, height, color):
        self.vbo_id = vbo_id
        self.size = size
        self.width = width
        self.height = height
        self.color = color


def create_rect(width, height, color):
    """ Create a vertex buffer for a rectangle. """
    v2f = [-width / 2, -height / 2,
            width / 2, -height / 2,
            width / 2, height / 2,
            -width / 2, height / 2]

    vbo_id = GL.GLuint()

    GL.glGenBuffers(1, ctypes.pointer(vbo_id))

    data2 = (GL.GLfloat*len(v2f))(*v2f)

    GL.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo_id)
    GL.glBufferData(GL.GL_ARRAY_BUFFER, ctypes.sizeof(data2), data2, GL.GL_STATIC_DRAW)

    shape = VertexBuffer(vbo_id, len(v2f)//2, width, height, color)
    return shape


def render_rect_filled(shape, x, y):
    """ Render the shape at the right spot. """
    # Set color
    GL.glDisable(GL.GL_BLEND)
    GL.glColor4ub(shape.color[0], shape.color[1], shape.color[2], 255)

    GL.glBindBuffer(GL.GL_ARRAY_BUFFER, shape.vbo_id)
    GL.glVertexPointer(2, GL.GL_FLOAT, 0, 0)

    GL.glLoadIdentity()
    GL.glTranslatef(x + shape.width / 2, y + shape.height / 2, 0)

    GL.glDrawArrays(GL.GL_QUADS, 0, shape.size)


class Rectangle():

    def __init__(self, x, y, width, height, delta_x, delta_y, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.delta_x = delta_x
        self.delta_y = delta_y
        self.color = color
        self.vbo = create_rect(self.width, self.height, self.color)

    def move(self):
        self.x += self.delta_x
        self.y += self.delta_y

    def draw(self):
        render_rect_filled(self.vbo, self.x, self.y)


class MyApplication():
    """ Main application class. """

    def setup(self):
        """ Set up the game and initialize the variables. """

        # Set background to white
        GL.glClearColor(1, 1, 1, 1)

        self.shape_list = []

        for i in range(2000):
            x = random.randrange(0, SCREEN_WIDTH)
            y = random.randrange(0, SCREEN_HEIGHT)
            width = random.randrange(20, 71)
            height = random.randrange(20, 71)

            d_x = random.randrange(-3, 4)
            d_y = random.randrange(-3, 4)

            red = random.randrange(256)
            green = random.randrange(256)
            blue = random.randrange(256)
            alpha = random.randrange(256)
            shape_type = random.randrange(2)
            shape = Rectangle(x, y, width, height, d_x, d_y, (red, green, blue))
            self.shape_list.append(shape)

    def animate(self, dt):
        """ Move everything """

        for shape in self.shape_list:
            shape.move()

    def on_draw(self):
        """
        Render the screen.
        """
        start = time.time()

        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glMatrixMode(GL.GL_MODELVIEW)
        GL.glEnableClientState(GL.GL_VERTEX_ARRAY)

        for shape in self.shape_list:
            shape.draw()

        elapsed = time.time() - start
        print(elapsed)


def main():
    window = pyglet.window.Window(SCREEN_WIDTH, SCREEN_HEIGHT)
    app = MyApplication()
    app.setup()
    pyglet.clock.schedule_interval(app.animate, 1/60)

    @window.event
    def on_draw():
        window.clear()
        app.on_draw()

    pyglet.app.run()

main()

OpenGL代码绘制每帧需要0.056秒。我希望我能更接近Pygame的表现。甚至更好。我可以采用哪些OpenGL技巧来加快速度?

1 个答案:

答案 0 :(得分:0)

好的,根据我从Reddit学到的东西,我可以做两件事来加快速度。

而不是2,000个VBO,使用一个。这是一个更快的例子:

"""
This example uses OpenGL via Pyglet and draws
a bunch of rectangles on the screen.
"""

import random
import time
import pyglet.gl as GL
import pyglet
import ctypes

# Set up the constants
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500

RECT_WIDTH = 50
RECT_HEIGHT = 50


def render_rect_filled(shape, offset):
    """ Render the shape at the right spot. """
    # Set color
    GL.glLoadIdentity()
    GL.glColor3ub(shape.color[0], shape.color[1], shape.color[2])

    GL.glTranslatef(shape.x + shape.width / 2, shape.y + shape.height / 2, 0)

    GL.glDrawArrays(GL.GL_QUADS, offset, 4)


class Rectangle():

    def __init__(self, x, y, width, height, delta_x, delta_y, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.delta_x = delta_x
        self.delta_y = delta_y
        self.color = color

    def move(self):
        self.x += self.delta_x
        self.y += self.delta_y

def create_rects(rect_list):
    """ Create a vertex buffer for a set of rectangles. """

    v2f = []
    for shape in rect_list:
        v2f.extend ([-shape.width / 2, -shape.height / 2,
                shape.width / 2, -shape.height / 2,
                shape.width / 2, shape.height / 2,
                -shape.width / 2, shape.height / 2])

    vbo_id = GL.GLuint()

    GL.glGenBuffers(1, ctypes.pointer(vbo_id))

    data2 = (GL.GLfloat*len(v2f))(*v2f)

    GL.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo_id)
    GL.glBufferData(GL.GL_ARRAY_BUFFER, ctypes.sizeof(data2), data2, GL.GL_STATIC_DRAW)

    return vbo_id


class MyApplication():
    """ Main application class. """

    def setup(self):
        """ Set up the game and initialize the variables. """

        # Set background to white
        GL.glClearColor(1, 1, 1, 1)

        self.shape_list = []

        for i in range(2000):
            x = random.randrange(0, SCREEN_WIDTH)
            y = random.randrange(0, SCREEN_HEIGHT)
            width = random.randrange(20, 71)
            height = random.randrange(20, 71)

            d_x = random.randrange(-3, 4)
            d_y = random.randrange(-3, 4)

            red = random.randrange(256)
            green = random.randrange(256)
            blue = random.randrange(256)
            alpha = random.randrange(256)
            shape_type = random.randrange(2)
            shape = Rectangle(x, y, width, height, d_x, d_y, (red, green, blue))
            self.shape_list.append(shape)

        self.vertex_vbo_id = create_rects(self.shape_list)

    def animate(self, dt):
        """ Move everything """

        for shape in self.shape_list:
            shape.move()

    def on_draw(self):
        """
        Render the screen.
        """
        start = time.time()

        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glMatrixMode(GL.GL_MODELVIEW)
        GL.glEnableClientState(GL.GL_VERTEX_ARRAY)

        GL.glDisable(GL.GL_BLEND)
        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vertex_vbo_id)
        GL.glVertexPointer(2, GL.GL_FLOAT, 0, 0)


        offset = 0
        for shape in self.shape_list:
            render_rect_filled(shape, offset)
            offset += 4

        elapsed = time.time() - start
        print(elapsed)


def main():
    window = pyglet.window.Window(SCREEN_WIDTH, SCREEN_HEIGHT)
    app = MyApplication()
    app.setup()
    pyglet.clock.schedule_interval(app.animate, 1/60)

    @window.event
    def on_draw():
        window.clear()
        app.on_draw()

    pyglet.app.run()

main()

此外,您可以将VBO用于颜色。这使用了VBO,甚至更快:

"""
This example uses OpenGL via Pyglet and draws
a bunch of rectangles on the screen.
"""

import random
import time
import pyglet.gl as GL
import pyglet
import ctypes

# Set up the constants
SCREEN_WIDTH = 700
SCREEN_HEIGHT = 500

RECT_WIDTH = 50
RECT_HEIGHT = 50


def create_rect(width, height, color):
    """ Create a vertex buffer for a rectangle. """
    v2f = [-width / 2, -height / 2,
            width / 2, -height / 2,
            width / 2, height / 2,
            -width / 2, height / 2]

    vbo_id = GL.GLuint()

    GL.glGenBuffers(1, ctypes.pointer(vbo_id))

    data2 = (GL.GLfloat*len(v2f))(*v2f)

    GL.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo_id)
    GL.glBufferData(GL.GL_ARRAY_BUFFER, ctypes.sizeof(data2), data2, GL.GL_STATIC_DRAW)

    shape = VertexBuffer(vbo_id, len(v2f)//2, width, height, color)
    return shape


def render_rect_filled(shape, offset):
    """ Render the shape at the right spot. """
    # Set color
    GL.glLoadIdentity()
    #GL.glColor4ub(shape.color[0], shape.color[1], shape.color[2], 255)

    GL.glTranslatef(shape.x + shape.width / 2, shape.y + shape.height / 2, 0)

    GL.glDrawArrays(GL.GL_QUADS, offset, 4)


class Rectangle():

    def __init__(self, x, y, width, height, delta_x, delta_y, color):
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.delta_x = delta_x
        self.delta_y = delta_y
        self.color = color

    def move(self):
        self.x += self.delta_x
        self.y += self.delta_y

def create_rects(rect_list):
    """ Create a vertex buffer for a set of rectangles. """

    v2f = []
    for shape in rect_list:
        v2f.extend ([-shape.width / 2, -shape.height / 2,
                shape.width / 2, -shape.height / 2,
                shape.width / 2, shape.height / 2,
                -shape.width / 2, shape.height / 2])

    vbo_id = GL.GLuint()

    GL.glGenBuffers(1, ctypes.pointer(vbo_id))

    data2 = (GL.GLfloat*len(v2f))(*v2f)

    GL.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo_id)
    GL.glBufferData(GL.GL_ARRAY_BUFFER, ctypes.sizeof(data2), data2, GL.GL_STATIC_DRAW)

    return vbo_id

def create_colors(rect_list):
    """ Create a vertex buffer for a set of rectangles. """

    v2f = []
    for shape in rect_list:
        for i in range(4):
            v2f.extend(shape.color)

    vbo_id = GL.GLuint()

    GL.glGenBuffers(1, ctypes.pointer(vbo_id))

    data2 = (GL.GLfloat*len(v2f))(*v2f)

    GL.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo_id)
    GL.glBufferData(GL.GL_ARRAY_BUFFER, ctypes.sizeof(data2), data2, GL.GL_STATIC_DRAW)

    return vbo_id

class MyApplication():
    """ Main application class. """

    def setup(self):
        """ Set up the game and initialize the variables. """

        # Set background to white
        GL.glClearColor(1, 1, 1, 1)

        self.shape_list = []

        for i in range(2000):
            x = random.randrange(0, SCREEN_WIDTH)
            y = random.randrange(0, SCREEN_HEIGHT)
            width = random.randrange(20, 71)
            height = random.randrange(20, 71)

            d_x = random.randrange(-3, 4)
            d_y = random.randrange(-3, 4)

            red = random.random()
            green = random.random()
            blue = random.random()
            shape = Rectangle(x, y, width, height, d_x, d_y, (red, green, blue))
            self.shape_list.append(shape)

        self.vertex_vbo_id = create_rects(self.shape_list)
        self.color_vbo_id = create_colors(self.shape_list)

    def animate(self, dt):
        """ Move everything """

        for shape in self.shape_list:
            shape.move()

    def on_draw(self):
        """
        Render the screen.
        """
        start = time.time()

        GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT)
        GL.glMatrixMode(GL.GL_MODELVIEW)
        GL.glDisable(GL.GL_BLEND)

        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.vertex_vbo_id)
        GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
        GL.glVertexPointer(2, GL.GL_FLOAT, 0, 0)

        GL.glBindBuffer(GL.GL_ARRAY_BUFFER, self.color_vbo_id)
        GL.glEnableClientState(GL.GL_COLOR_ARRAY)
        GL.glColorPointer(3, GL.GL_FLOAT, 0, 0)

        offset = 0
        for shape in self.shape_list:
            render_rect_filled(shape, offset)
            offset += 4

        elapsed = time.time() - start
        print(elapsed)


def main():
    window = pyglet.window.Window(SCREEN_WIDTH, SCREEN_HEIGHT)
    app = MyApplication()
    app.setup()
    pyglet.clock.schedule_interval(app.animate, 1/60)

    @window.event
    def on_draw():
        window.clear()
        app.on_draw()

    pyglet.app.run()

main()