I'm trying to use Pyglet's batch drawing capabilities to speed up the rendering of an animation which mainly consists of drawing a large set of vertices many times per second using GL_LINES. Speed-wise, I succeeded, as previously laggy animations now run crisp up to 60 fps. However, I noticed that every time a new frame of animation is drawn (by calling batch.draw() on a few batches, within a function scheduled to be called every frame by pyglet.clock.schedule_interval()), the memory used by the program steadily rises.
I expect the memory usage to substantially increase while creating the batches (and it does), but I don't understand why a mere call to batch.draw() also seems to be causing it, or at least why the memory usage seems to accumulate over future frames. Do I need to somehow manually delete previously drawn images from memory each time I need to redraw a frame? If so, I've found nothing in the Pyglet documentation about needing to do such a thing.
In my program, I call pyglet.gl.glClear(pyglet.gl.GL_COLOR_BUFFER_BIT) at the beginning of each new frame update in order to clear the previous frame, but perhaps the previous frame is still persisting in memory?
Below is an independent example script which exhibits this precise behavior:
import pyglet as pg
import random
currentFrame = 0
window = pg.window.Window(600,600)
# A list of lists of batches to be drawn at each frame.
frames = []
# Make 200 frames...
for frameCount in range(200):
batches = []
for n in range(3):
batch = pg.graphics.Batch()
# Generate a random set of vertices within the window space
batch.add(1000, pg.gl.GL_LINES, None,
("v2f", tuple(random.random()*600 for i in range(2000))),
("c3B", (255,255,255)*1000))
batches.append(batch)
frames.append(batches)
# This function will be called every time the window is redrawn
def update(dt, frames=frames):
global currentFrame
if currentFrame >= len(frames):
pg.clock.unschedule(update)
print("Animation complete")
return
batches = frames[currentFrame]
# Clear the previous frame
pg.gl.glClearColor(0,0,0,1) # Black background color
pg.gl.glClear(pg.gl.GL_COLOR_BUFFER_BIT)
# Define line width for this frame and draw the batches
pg.gl.glLineWidth(5)
for batch in batches:
batch.draw()
currentFrame += 1
# Call the update() function 30 times per second.
pg.clock.schedule_interval(update, 1.0/30)
pg.app.run()
While the animation is running, no new batches should be created, and yet the memory used by Python steadily increases thruout the duration of the animation.
I'm sorry that this might be an extremely late answer, but for anyone coming across this...
you have created 2 lists which are the source of the linear memory usage increase:
frames = []
and
batches = []
on lines 8 and 12 respectively.
Then, in your for loop you add 3 batches into the "batches" list and then add those into the "frames" list.
for frameCount in range(200):
batches = []
for n in range(3):
batch = pg.graphics.Batch()
# Generate a random set of vertices within the window space
batch.add(1000, pg.gl.GL_LINES, None,
("v2f", tuple(random.random()*600 for i in range(2000))),
("c3B", (255,255,255)*1000))
batches.append(batch)
frames.append(batches)
fortunately, you do override whatever is in "batches" in here:
batches = frames[currentFrame]
but that still leaves out "frames", which is never reset and for every frame, has 3 more batches in it.
if you start with 0 frames and end with 200 frames, your memory usage with this "frames" list will go from storing nothing to storing 600 batches.
try to reset the "frames" lst after each frame and keeping track of your count of frames with a simple integer.
I hope this helps at least someone
Related
my character sprite moves faster if my game is in window mode. to set the velocity i used ROOTwidth, in theory the velocity should be scaled...
this is my code (simplified)
#MAIN CODE
#ROOT dimension don't change (window can't be resized while playing,
#only in main menu function where ROOTwidth, ROOTheight are obtained)
ROOTwidth, ROOTheight = pygame.display.get_surface().get_size()
velocity = ROOTheight/450
playertopx = ROOTwidth/2.2
playertopy = ROOTwidth/2
playermovement = PlayerMovement(playertopx, playertopy)
while True:
key = pygame.key.get_pressed()
if key[pygame.K_w]:
playermovement.human_moveup(velocity)
#PLAYER MOVEMENT CLASS
import pygame
class PlayerMovement:
#init
def __init__(self, playertopx, playertopy):
self.x = playertopx
self.y = playertopy
#movement
def human_moveup(self, velocity):
self.y -= velocity
#MAIN CODE
ROOT.blit(playermovement.spritesheet_human, (playermovement.x, playermovement.y), (0, 50, 25, 18))
I don't know what to do... for every element in my game, using ROOT dimensions works fine, only in velocity I have problems
I guess it might be a case of your main event loop's looping speed being dependent on the time it takes to draw on the window and that main event's code not accounting for that.
Execution speed
Assuming you have this code as your main event loop:
while NotExited:
doGameLogic() # Your velocity computations and other stuff
drawOnWindow() # Filling entire window with background, drawing sprites over, refreshing it, etc
Now, imagine doGameLogic() always takes 1ms (0.001 seconds) of time, and drawOnWindow() always takes 50ms. While this loop is running, therefore, the loop will take up 51 milliseconds total, and hence doGameLogic() will be called once every 51ms.
Then you perform your velocity computation in there. Let's, for simplicity, say you do playermovement.x += 5 in there every time.
As a result, your player's X coordinate is increased by 5 units every 51 milliseconds. That amounts to an increase of about 98 units in one second.
Variance in execution speed
Now imagine that drawOnWindow() starts taking 20ms of time instead. Then the loop takes 21ms of time total to run, which causes doGameLogic() to run every 21ms aswell. In that case the X coordinate increases by 5 units every 21 milliseconds instead, amounting to increasing by 238 units every second.
That is way faster than the previous 98 units every second. Because drawing takes less time now, your character ends up moving way faster.
This is what I assume is happening in your case. As you make the window smaller, drawing calls (like drawing a background/filling it with a color) take less time as there's less pixels to draw on, and therefore change how long drawOnWindow() takes time, and therefore the frequency at which doGameLogic() is run changes.
Fixing
There are many different ways to fix this. Here are some:
Enforcing loop speed
One of them is to ensure that your loop always takes the exact same amount of time to run regardless of how much time the calls take:
import time
while NotExited:
startTime = time.time() # Record when the loop was started
doGameLogic()
drawOnWindow()
# Calculate how long did it take the loop to run.
HowLong = time.time() - startTime
# Sleep until this loop takes exactly 0.05 seconds.
# The "max" call is to ensure we don't try to sleep
# for a negative value if the loop took longer than that.
time.sleep(max(0, 0.05-HowLong))
Or alternatively, the library you are using for rendering may allow you to set an upper limit to FPS (frames per second), which can also work to make sure the time it takes to draw is constant.
This method has a disadvantage in that it becomes ineffective if the loop takes longer than the designated time, and restricts how fast your game runs in the opposite case, but it is very easy to implement.
Scaling with speed
Instead of making sure playermovement.x += 5 and the rest of the logic is ran exactly once every 50 milliseconds, you can make sure that it is run with values scaled proportionally to how often it is run, producing the same results.
In other words, running playermovement.x += 5 once every 50ms is fully equivalent to running playermovement.x += 1 once every 10ms: as a result of either, every 50ms the value is increased by 5 units.
We can calculate how long it took to render the last frame, and then adjust the values in the calculations proportionally to that:
import time
# This will store when was the last frame started.
# Initialize with a reasonable value for now.
previousTime = time.time()
while NotExited:
# Get how long it took to run the loop the last time.
difference = time.time() - previousTime
# Get a scale value to adjust for the delay.
# The faster the game runs, the smaller this value is.
# If difference is 50ms, this returns 1.
# If difference is 100ms, this returns 2.
timeScale = difference / 0.05
doGameLogic(timeScale)
drawOnWindow()
previousTime = time.time()
# ... in the game logic:
def doGameLogic(timeScale):
# ...
# Perform game logic proportionally to the loop speed.
playermovement.x += 5 * timeScale
This method is more adaptable depending on the speed, but requires to be taken in account whereever time dependent actions like this one are done.
It can also be a source of unique problems: for example, if your game runs very very slowly even for one frame, the time scale value might get disproportionally large, causing playermovement.x to be incremented by 5*100000, teleporting your player character very far away. It can also produce jerky results if the loop speed is unstable, and provide more problems since it is performed with floating point math.
Decoupling logic and rendering
Another more reliable than the other ones but harder to implement way is to decouple doGameLogic() from drawOnWindow(), allowing one to be run independently from the other. This is most often implemented with use of multithreading.
You could make two loops running concurrently: one that runs doGameLogic() on a fixed interval, like 10ms, with the aforementioned "Enforcing loop speed" method, and another one that runs drawOnWindow() as fast as it can to render on the window at any arbitrary speed.
This method also involves questions of interpolation (if drawOnWindow() runs twice as fast as doGameLogic(), you probably don't want every second time to draw an identical image, but an intermediate one that appears smoother), and threading management (make sure you don't draw on the window while doGameLogic() is still running, as you might draw an incomplete game state in the middle of processing).
Unfortunately I am not knowledgeable enough to provide an example of code for that, nor I am even sure if that is doable in Python or PyGame.
I am rendering an image which is updated every frame by making it a texture of a square 2D plate (comprised from 2 triangles). However, GPU memory seems to increase monotonically with every frame.
The draw function looks like this:
prog = gloo.Program(vertex, fragment, count=4)
def Draw(self, inImRGB):
texture = inImRGB.copy().view(gloo.Texture2D)
texture.interpolation = gl.GL_LINEAR
CBackgroundImage._prog['texture'] = texture
CBackgroundImage._prog.draw(gl.GL_TRIANGLE_STRIP)
And it is called periodically, for each new available image, using the following callback:
from glumpy import app
window = app.Window(...)
#window.event
def on_draw():
window.clear()
bgImageObj.Draw(newImRGB)
Any idea why GPU memory keeps accumulating ? Should I somehow free the texture each new frame, or fill it in a different way? If so, how?
texture = inImRGB.copy().view(gloo.Texture2D)
Creates and all new texture; eventually the Phython GC will clean up the old stuff, but that's not going to happen, if there's no shortage of memory.
Create the texture during initialization and then reuse.
I am writing an application in Python using Qt (currently PyQt) that receives telemetry (position data) over UDP and displays that data. It can handle several packet types each with several data fields each. I have a separate thread that handles receiving and decoding packets and they emits a signal to send that data to the Qt GUI for display. The data get displayed in several ways: a current value display, scrolling log display, and a 3D view display. All this runs great and in real time with each packet updating at up to about 30Hz.
I want to add another tab that shows a live plot. The user can select which data streams to plot and it will update in real time, showing a sliding window of the last 60sec of data (maybe configurable at some point). However my first approach is particularly slow. It is barely keeping up plotting only one line with our emulator that runs much slower than 30Hz.
I am using pyqtgraph to do the plotting. I have a PlotWidget instantiated in my .ui file and create a PlotDataItem for each data line that could be drawn on the plot. I am using deques to store the data to be plotted, both the value and the time. This way I can quickly add data as it comes in and remove it as it falls outside of the sliding window. I am storing all this in a dict for each packet type and field:
self.plotData[pktType][field] = {}
self.plotData[pktType][field]['curve'] = self.pwPlot.plot()
self.plotData[pktType][field]['starttime'] = time
self.plotData[pktType][field]['data'] = coll.deque()
self.plotData[pktType][field]['realtime'] = coll.deque()
self.plotData[pktType][field]['time'] = coll.deque()
'starttime' stores an initial datetime value for computing elapsed seconds. 'realtime' stores datetime values of when each packet was received (I am not currently using this, so I could drop it if it would save time). 'time' stores elapsed seconds from the 'starttime' for easier plotting and 'data' stores the values.
When a packet that comes in, I store data in the deques for each field I might want to parse. I then trim off any data outside the sliding window. Finally, the deque gets packaged in a numpy array and passed to the PlotDataItem setData method. Here's a simplified version of the code that runs for each received packet:
def updatePlot(self, time, pktData):
pktType = pktData['ptype']
keys = self.getPlottableFromType(pktType) # list of fields that can be plotted
if keys == None:
return
for k in keys:
self.plotData[pktType][k]['data'].append(pktData[k])
self.plotData[pktType][k]['realtime'].append(time)
runtime = (time - self.plotData[pktType][k]['starttime']).total_seconds()
if self.plotRate == 0:
self.plotData[pktType][k]['time'].append(runtime)
else:
if self.plotData[pktType][k]['time']: # has items
nexttime = self.plotData[pktType][k]['time'][-1] + 1. / self.plotRate
else:
nexttime = 0
self.plotData[pktType][k]['time'].append(nexttime)
while (self.plotData[pktType][k]['time'][-1] - self.plotData[pktType][k]['time'][0]) > self.plotRangeSec:
self.plotData[pktType][k]['data'].popleft()
self.plotData[pktType][k]['realtime'].popleft()
self.plotData[pktType][k]['time'].popleft()
self.drawPlot(pktType, k)
def drawPlot(self, pktType, k):
if self.plotIsEnabled(pktType, k) and self.plotData[pktType][k]['time']: # has items
npt = np.array(self.plotData[pktType][k]['time'])
npd = np.array(self.plotData[pktType][k]['data'])
self.plotData[pktType][k]['curve'].setData(npt, npd)
else:
self.plotData[pktType][k]['curve'].clear()
self.plotRate can be used to plot the data either using wall time or force the time axis to a fixed update rate. This is useful for using with the emulator since it runs slower than the real system.
First thing I should probably do is not call .clear() every time for plots that are not being plotted (just complicates the logic a little bit). Other than that, anyone have any tips for improving performance? Is the deque to numpy array a good strategy? Is there a better way to update the data since I am only changing a few values per line (adding a point and possibly removing a point or two)?
I have a little app that allows me to change an input value with a tKinter scale widget and see how a graph reacts to different changes in inputs. Every time I move the scale, it's bound to an event that redoes the calculations for a list and replots. It's kind of slow.
Now, I'm replotting the entire thing, but it's stacking one axis on top of the other, hundreds after a few minutes of use.
deltaPlot = Figure(figsize=(4,3.5), dpi=75, frameon=False)
c = deltaPlot.add_subplot(111)
c.set_title('Delta')
deltaDataPlot = FigureCanvasTkAgg(deltaPlot, master=master)
deltaDataPlot.get_tk_widget().grid(row=0,rowspan=2)
and the main loop runs
c.cla()
c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
It's clearing the initial plot, but like I said the axes are stacking (because it's redrawing one each time, corresponding to the slightly altered data points). Anyone know a fix?
To improve speed there are a couple of things you could do:
Either Run the remove method on the line produced by plot:
# inside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
deltaDataPlot.show()
...
line.remove()
Or Re-use the line, updating its coordinates appropriately:
# outside the loop
line, = c.plot(timeSpread,tdeltas,'g-')
# inside the loop
deltaDataPlot.show()
line.set_data(timeSpread,tdeltas)
The documentation of Line2d can be found here.
You might also like to read the cookbook article on animation.
HTH
I'm attempting to do real-time plotting of data in matplotlib, in a "production" application that uses wxPython. I had been using Chaco for this purpose, but I'm trying to avoid Chaco in the future for many reasons, one of which is that since it's not well-documented I often must spend a long time reading the Chaco source code when I want to add even the smallest feature to one of my plots. One aspect where Chaco wins out over matplotlib is in speed, so I'm exploring ways to get acceptable performance from matplotlib.
One technique I've seen widely used for fast plots in matplotlib is to set animated to True for elements of the plot which you wish to update often, then draw the background (axes, tick marks, etc.) only once, and use the canvas.copy_from_bbox() method to save the background. Then, when drawing a new foreground (the plot trace, etc.), you use canvas.restore_region() to simply copy the pre-rendered background to the screen, then draw the new foreground with axis.draw_artist() and canvas.blit() it to the screen.
I wrote up a fairly simple example that embeds a FigureCanvasWxAgg in a wxPython Frame and tries to display a single trace of random data at 45 FPS. When the program is running with the Frame at the default size (hard-coded in my source), it achieves ~13-14 frames per second on my machine. When I maximize the window, the refresh drops to around 5.5 FPS. I don't think this will be fast enough for my application, especially once I start adding additional elements to be rendered in real-time.
My code is posted here: basic_fastplot.py
I wondered if this could be made faster, so I profiled the code and found that by far the largest consumers of processing time are the calls to canvas.blit() at lines 99 and 109. I dug a little further, instrumenting the matplotlib code itself to find that most of this time is spent in a specific call to MemoryDC.SelectObject(). There are several calls to SelectObject in surrounding code, but only the one marked below takes any appreciable amount of time.
From the matplotlib source, backend_wxagg.py:
class FigureCanvasWxAgg(FigureCanvasAgg, FigureCanvasWx):
# ...
def blit(self, bbox=None):
"""
Transfer the region of the agg buffer defined by bbox to the display.
If bbox is None, the entire buffer is transferred.
"""
if bbox is None:
self.bitmap = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
self.gui_repaint()
return
l, b, w, h = bbox.bounds
r = l + w
t = b + h
x = int(l)
y = int(self.bitmap.GetHeight() - t)
srcBmp = _convert_agg_to_wx_bitmap(self.get_renderer(), None)
srcDC = wx.MemoryDC()
srcDC.SelectObject(srcBmp) # <<<< Most time is spent here, 30milliseconds or more!
destDC = wx.MemoryDC()
destDC.SelectObject(self.bitmap)
destDC.BeginDrawing()
destDC.Blit(x, y, int(w), int(h), srcDC, x, y)
destDC.EndDrawing()
destDC.SelectObject(wx.NullBitmap)
srcDC.SelectObject(wx.NullBitmap)
self.gui_repaint()
My questions:
What could SelectObject() be doing that is taking so long? I had sort of assumed it would just be setting up pointers, etc., not doing much copying or computation.
Is there any way I might be able to speed this up (to get maybe 10 FPS at full-screen)?