Skip to content

Instantly share code, notes, and snippets.

@dlon
Last active May 26, 2019 16:25
Show Gist options
  • Save dlon/85be7654d0781a1a70b14cc0f815ac33 to your computer and use it in GitHub Desktop.
Save dlon/85be7654d0781a1a70b14cc0f815ac33 to your computer and use it in GitHub Desktop.
pygame _sdl2.Renderer stress test
"""
Moving Sprite Stress Test
Simple program to test how fast we can draw sprites that are moving
Artwork from http://kenney.nl
If Python and Arcade are installed, this example can be run from the command line with:
python -m arcade.examples.stress_test_draw_moving_pygame
"""
import pygame
import pygame._sdl2
import random
import os
import timeit
import time
import collections
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
# --- Constants ---
SPRITE_SCALING_COIN = 0.09
SPRITE_NATIVE_SIZE = 128
SPRITE_SIZE = int(SPRITE_NATIVE_SIZE * SPRITE_SCALING_COIN)
COIN_COUNT_INCREMENT = 100
#COIN_COUNT_INCREMENT = 2000
STOP_COUNT = 6000
#STOP_COUNT = 60000
RESULTS_FILE = "stress_test_draw_moving_pygame_render.csv"
SCREEN_WIDTH = 1800
SCREEN_HEIGHT = 1000
SCREEN_TITLE = "Moving Sprite Stress Test"
class FPSCounter:
def __init__(self):
self.time = time.perf_counter()
self.frame_times = collections.deque(maxlen=60)
def tick(self):
t1 = time.perf_counter()
dt = t1 - self.time
self.time = t1
self.frame_times.append(dt)
def get_fps(self):
total_time = sum(self.frame_times)
if total_time == 0:
return 0
else:
return len(self.frame_times) / sum(self.frame_times)
class Coin(pygame.sprite.Sprite):
"""
This class represents the ball
It derives from the "Sprite" class in Pygame
"""
image = None
def __init__(self, mygame):
""" Constructor. Pass in the color of the block,
and its x and y position. """
# Call the parent class (Sprite) constructor
super().__init__()
# Create an image of the block, and fill it with a color.
# This could also be an image loaded from the disk.
if Coin.image is None:
Coin.image = pygame._sdl2.Texture.from_surface(mygame.renderer, pygame.image.load("coin_01.png"))
rect = self.image.get_rect()
rect.w = int(rect.w * SPRITE_SCALING_COIN)
rect.h = int(rect.h * SPRITE_SCALING_COIN)
self.rect = rect
# Instance variables for our current speed and direction
self.change_x = 0
self.change_y = 0
def update(self):
""" Called each frame. """
self.rect.x += self.change_x
self.rect.y += self.change_y
class MyGame:
""" Our custom Window Class"""
def __init__(self):
""" Initializer """
# Set the working directory (where we expect to find files) to the same
# directory this .py file is in. You can leave this out of your own
# code, but it is needed to easily run the examples using "python -m"
# as mentioned at the top of this program.
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
# Variables that will hold sprite lists
self.coin_list = None
self.processing_time = 0
self.draw_time = 0
self.program_start_time = timeit.default_timer()
self.sprite_count_list = []
self.fps_list = []
self.processing_time_list = []
self.drawing_time_list = []
self.last_fps_reading = 0
self.fps = FPSCounter()
# Initialize Pygame
pygame.init()
# Set the height and width of the screen
self.window = pygame._sdl2.Window(size=[SCREEN_WIDTH, SCREEN_HEIGHT])
self.renderer = pygame._sdl2.Renderer(self.window)
self.renderer.draw_color = 59, 122, 87, 255
# This is a list of every sprite. All blocks and the player block as well.
self.coin_list = pygame.sprite.Group()
self.font = pygame.font.SysFont('Calibri', 25, True, False)
# Open file to save timings
self.results_file = open(RESULTS_FILE, "w")
def add_coins(self):
# Create the coins
for i in range(COIN_COUNT_INCREMENT):
# Create the coin instance
# Coin image from kenney.nl
coin = Coin(self)
# Position the coin
coin.rect.x = random.randrange(SPRITE_SIZE, SCREEN_WIDTH - SPRITE_SIZE)
coin.rect.y = random.randrange(SPRITE_SIZE, SCREEN_HEIGHT - SPRITE_SIZE)
coin.change_x = random.randrange(-3, 4)
coin.change_y = random.randrange(-3, 4)
# Add the coin to the lists
self.coin_list.add(coin)
def on_draw(self):
""" Draw everything """
draw_start_time = timeit.default_timer()
self.renderer.clear()
self.coin_list.draw(self.renderer)
# Display timings
"""
output = f"Processing time: {self.processing_time:.3f}"
text = self.font.render(output, True, BLACK)
self.screen.blit(text, [ 20, SCREEN_HEIGHT - 40])
output = f"Drawing time: {self.draw_time:.3f}"
text = self.font.render(output, True, BLACK)
self.screen.blit(text, [20, SCREEN_HEIGHT - 60])
fps = self.fps.get_fps()
output = f"FPS: {fps:3.0f}"
text = self.font.render(output, True, BLACK)
self.screen.blit(text, [20, SCREEN_HEIGHT - 80])
"""
self.renderer.present()
self.draw_time = timeit.default_timer() - draw_start_time
self.fps.tick()
def update(self, delta_time):
# Start update timer
start_time = timeit.default_timer()
self.coin_list.update()
for sprite in self.coin_list:
if sprite.rect.x < 0:
sprite.change_x *= -1
elif sprite.rect.x > SCREEN_WIDTH:
sprite.change_x *= -1
if sprite.rect.y < 0:
sprite.change_y *= -1
elif sprite.rect.y > SCREEN_HEIGHT:
sprite.change_y *= -1
# Save the time it took to do this.
self.processing_time = timeit.default_timer() - start_time
# Total time program has been running
total_program_time = int(timeit.default_timer() - self.program_start_time)
# Print out stats, or add more sprites
if total_program_time > self.last_fps_reading:
self.last_fps_reading = total_program_time
# It takes the program a while to "warm up", so the first
# few seconds our readings will be off. So wait some time
# before taking readings
if total_program_time > 5:
# We want the program to run for a while before taking
# timing measurements. We don't want the time it takes
# to add new sprites to be part of that measurement. So
# make sure we have a clear second of nothing but
# running the sprites, and not adding the sprites.
if total_program_time % 2 == 1:
# Take timings
output = f"{total_program_time}, {len(self.coin_list)}, {self.fps.get_fps():.1f}, {self.processing_time:.4f}, {self.draw_time:.4f}\n"
print(output, end="")
self.results_file.write(output)
if len(self.coin_list) >= STOP_COUNT:
pygame.event.post(pygame.event.Event(pygame.QUIT))
return
self.sprite_count_list.append(len(self.coin_list))
self.fps_list.append(round(self.fps.get_fps(), 1))
self.processing_time_list.append(self.processing_time)
self.drawing_time_list.append(self.draw_time)
# Now add the coins
self.add_coins()
def main():
""" Main method """
window = MyGame()
# Loop until the user clicks the close button.
done = False
# Used to manage how fast the screen updates
clock = pygame.time.Clock()
# -------- Main Program Loop -----------
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
window.update(0)
window.on_draw()
clock.tick(60)
pygame.quit()
# Plot our results
plt.plot(window.sprite_count_list, window.processing_time_list, label="Processing Time")
plt.plot(window.sprite_count_list, window.drawing_time_list, label="Drawing Time")
plt.legend(loc='upper left', shadow=True, fontsize='x-large')
plt.ylabel('Time')
plt.xlabel('Sprite Count')
plt.show()
# Plot our results
plt.plot(window.sprite_count_list, window.fps_list)
plt.ylabel('FPS')
plt.xlabel('Sprite Count')
plt.show()
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment