I have the following code, which is a simplified from a project I'm working on. I thought the issue was the same as Why is the PyGame animation is flickering, but with the simplified code below, I've confirmed that I'm only calling pg.display.update() exactly once per pg.event.get() call. To be really explicit, I don't believe this is a duplicate of the linked question above because a) I have confirmed I only have one update() call already, which was the solution for the linked question; and b) it only happens on the CM4, not on when running on other computers.
That said, here's code that is able to reproduce the issue on a cm4:
import pygame as pg
class Game:
def __init__(self):
pg.init()
self.__clock = pg.time.Clock()
self.__window_size = (1024, 768)
self.__project_size = (392, 291)
self.screen = pg.display.set_mode(self.__window_size, pg.FULLSCREEN)
self.blank_background = pg.transform.scale(pg.image.load("assets/default_background.png"),
self.__window_size).convert()
self.grid_image = pg.transform.scale(pg.image.load("assets/new_project_thumb.png"),
self.__project_size).convert()
self.project_selected = 0
self.rect_pixels = 3
self.grid_locations = self.calculate_grid_locations()
def run(self) -> None:
pg.mouse.set_visible(False)
playing = True
while playing:
events = pg.event.get()
button_event = None
self.screen.blit(self.blank_background, (0, 0))
for event in events:
if event.type == pg.KEYDOWN:
button_event = event.key
if button_event == pg.K_LEFT:
self.project_selected = max((self.project_selected - 1), 0)
if button_event == pg.K_RIGHT:
self.project_selected = min((self.project_selected + 1), 3)
if button_event == pg.K_ESCAPE:
playing = False
self.draw_menu()
pg.display.update()
self.__clock.tick(6)
def draw_menu(self):
for i in range(4):
if i == self.project_selected:
rect_loc = (self.grid_locations[i][0] - self.rect_pixels,
self.grid_locations[i][1] - self.rect_pixels)
pg.draw.rect(self.screen,
(237, 28, 36),
pg.Rect(rect_loc,
(self.__project_size[0] + self.rect_pixels * 2,
self.__project_size[1] + self.rect_pixels * 2)))
self.screen.blit(self.grid_image, self.grid_locations[i])
def calculate_grid_locations(self):
w=int((self.__window_size[0] - 2 * self.__project_size[0]) / 3)
h=int((self.__window_size[1] - 2 * self.__project_size[1]) / 3)
return [(w, h),
(w * 2 + self.__project_size[0], h),
(w, h * 2 + self.__project_size[1]),
(w * 2 + self.__project_size[0], h * 2 + self.__project_size[1])]
if __name__ == "__main__":
game = Game()
game.run()
The issue I'm having is that, specifically when I run it on a raspberry pi cm4 (and not on my linux laptop), I get intermittent flickering and/or lagging between frames - when I press an input, sometimes it will jitter back and forth between the image from the previous state and the current state very quickly before staying on the next frame.
One thing I have been able to figure out so far is that it might be framerate related - my laptop is easily able to hit 30 FPS, but the cm4 seems to only be able to do around 8 reliably (as reported by clock.get_fps()). Lowering the clock.tick() argument to below 8 does not fix the problem (as seen in the example code above) - there is still intermittent jittering even if the pi is able to maintain the framerate. It is also not specific to a given monitor - I have tried several.
This does not occur with the example games from the pygame library when they're run on the cm4, just my program.
Does anyone have any pointers for what to look into here? I'm not really sure where to examine this further. Hypothoses I've had include:
Maybe the update() call happens asynchronously, and I need to find a way to block the game until it completes.
Maybe I need to draw the menu images / selection rectangle as sprites. Looking at the code for the example games (which work great on the cm4), it looks like most of it is implemented as a sprite layer, and then just that is updated each cycle by passing the result of sprite.RenderUpdates().draw() as an argument to the update() call.