Why is my program slow while rendering 128 particles? I think that's not enough to get less than 30 fps.
All I do is rendering 128 particles and giving them some basic gravitation
on_draw function
def on_draw(self, time=None):
glClearColor(0.0, 0.0, 0.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glLoadIdentity()
self.particles.append(Particle())
for particle in self.particles:
particle.draw()
if particle.is_dead:
self.particles.remove(particle)
Particle class
class Particle:
def __init__(self, **kwargs):
self.acceleration = Vector2(0, 0.05)
self.velocity = Vector2(random.uniform(-1, 1), random.uniform(-1, 0))
self.position = Vector2()
self.time_to_live = 255
self.numpoints = 50
self._vertices = []
for i in range(self.numpoints):
angle = math.radians(float(i) / self.numpoints * 360.0)
x = 10 * math.cos(angle) + self.velocity[0] + 300
y = 10 * math.sin(angle) + self.velocity[1] + 400
self._vertices += [x, y]
def update(self, time=None):
self.velocity += self.acceleration
self.position -= self.velocity
self.time_to_live -= 2
def draw(self):
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glPushMatrix()
glTranslatef(self.position[0], self.position[1], 0)
pyglet.graphics.draw(self.numpoints, GL_TRIANGLE_FAN, ('v2f', self._vertices), ('c4B', self.color))
glPopMatrix()
self.update()
@property
def is_dead(self):
if self.time_to_live <= 0:
return True
return False
@property
def color(self):
return tuple(color for i in range(self.numpoints) for color in (255, 255, 255, self.time_to_live))
I'm not overly happy about using GL_TRIANGLE_FAN
because it's caused a lot of odd shapes when using batched rendering. So consider moving over to GL_TRIANGLES
instead and simply add all the points to the object rather than leaning on GL to close the shape for you.
That way, you can easily move over to doing batched rendering:
import pyglet
from pyglet.gl import *
from collections import OrderedDict
from time import time, sleep
from math import *
from random import randint
key = pyglet.window.key
class CustomGroup(pyglet.graphics.Group):
def set_state(self):
#pyglet.gl.glLineWidth(5)
#glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
#glColor4f(1, 0, 0, 1) #FFFFFF
#glLineWidth(1)
#glEnable(texture.target)
#glBindTexture(texture.target, texture.id)
pass
def unset_state(self):
glLineWidth(1)
#glDisable(texture.target)
class Particle():
def __init__(self, x, y, batch, particles):
self.batch = batch
self.particles = particles
self.group = CustomGroup()
self.add_point(x, y)
def add_point(self, x, y):
colors = ()#255,0,0
sides = 50
radius = 25
deg = 360/sides
points = ()#x, y # Starting point is x, y?
prev = None
for i in range(sides):
n = ((deg*i)/180)*pi # Convert degrees to radians
point = int(radius * cos(n)) + x, int(radius * sin(n)) + y
if prev:
points += x, y
points += prev
points += point
colors += (255, i*int(255/sides), 0)*3 # Add a color pair for each point (r,g,b) * points[3 points added]
prev = point
points += x, y
points += prev
points += points[2:4]
colors += (255, 0, 255)*3
self.particles[len(self.particles)] = self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
class main(pyglet.window.Window):
def __init__ (self, demo=False):
super(main, self).__init__(800, 600, fullscreen = False, vsync = True)
#print(self.context.config.sample_buffers)
self.x, self.y = 0, 0
self.sprites = OrderedDict()
self.batches = OrderedDict()
self.batches['default'] = pyglet.graphics.Batch()
self.active_batch = 'default'
for i in range(1000):
self.sprites[len(self.sprites)] = Particle(randint(0, 800), randint(0, 600), self.batches[self.active_batch], self.sprites)
self.alive = True
self.fps = 0
self.last_fps = time()
self.fps_label = pyglet.text.Label(str(self.fps) + ' fps', font_size=12, x=3, y=self.height-15)
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
#self.bg.draw()
self.batches[self.active_batch].draw()
self.fps += 1
if time()-self.last_fps > 1:
self.fps_label.text = str(self.fps) + ' fps'
self.fps = 0
self.last_fps = time()
self.fps_label.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
if __name__ == '__main__':
x = main(demo=True)
x.run()
Bare in mind, on my nVidia 1070 I managed to get roughly 35fps out of this code, which isn't mind blowing. But it is 1000 objects * sides, give or take.
What I've changed is essentially this:
self.batch.add(int(len(points)/2), pyglet.gl.GL_TRIANGLES, self.group, ('v2i/stream', points), ('c3B', colors))
and in your draw loop, you'll do:
self.batch.draw()
Instead of calling Particle.draw()
for each particle object.
What this does is that it'll send all the objects to the graphics card in one gigantic batch rather than having to tell the graphics card what to render object by object.
As @thokra pointed out, your code is more CPU intensive than GPU intensive.
Hopefully this fixes it or gives you a few pointers.
Most of this code is taking from a LAN project I did with a good friend of mine a while back:
Because I didn't have all your code, mainly the main loop. I applied your problem to my own code and "solved " it by tweaking it a bit. Again, hope it helps and steal ideas from that github project if you need to. Happy new year!