Pygame Screen tearing when moving line across screen

1.6k views Asked by At

I am trying to move this image:

enter image description here

Across my PyGame screen, from right to left and back again, However as the image moves, every second or so I have a flicker of screen tear make its way up the image like so:

enter image description here

The code I am using is a loop similar to this:

screen.blit(img, (x,y))
pygame.update((x,y),(w,h))
pygame.draw.rect(screen,(0,0,0),((x,y),(w,h)))

So far I have tried the following to solve the issue:

Using HWSURFACE, FULLSCREEN, DOUBLEBUF flags when creating the screen, this had no effect, I also adjusted my .update(rect) to a .flip() (as this is recommended when using DOUBLEBUF?)

Splitting the memory between GPU and CPU (I am running this on a raspberry pi 2) I have tried giving both more memory, no change.

Setting a clock.tick to throttle the update rate to 60 FPS (above and below as well) this did smooth out some of the tearing but not all

Adjusting the size of each increment left or right, making the increments smaller results in less tearing, but also less speed. (Can't have it going too slow)

Blitting a new black surface rather than drawing a black rect over the previous position (when moving the image to ensure there is no trail behind it) as I read somewhere that blit is better supported with HWSURFACE than drawing, although I can't confirm this? - This had no effect

If anyone has any other solutions that may improve the situation I'd be grateful.

I would rather not change from PyGame to anything else (like pyglet) as I have done quite a lot of implementation so far using PyGame, but I am open to suggestions.

Cheers

EDIT

Relevant code as requested:

if scanner == True:
    clocker.tick(clockspeed)
    if x < 11:
        slower = 3
        if firstTime == True:
            img.set_alpha(int(x * 25))
            newSurf.set_alpha(int(x * 25))
            screen.blit(newSurf,(xText,35))
            pygame.display.update((xText,35),((xText + newSurf.get_width()),(50 + newSurf.get_height())))
            img.set_alpha(255)

    elif x > (divider - 15):
        slower = 3
    else:
        slower = 0
        firstTime = False

    screen.blit(img, ((xStart - (x * increment) + slower),100))
    pygame.display.update(((xStart - (x * increment) + slower),100),(95,450))
    pygame.draw.rect(screen, (0,0,0), (((xStart - (x * increment) + slower),100),(95,450)))

The slower variable is to give a feel of inertia as the bar reaches the far left and right sides of its sweep across the speed it will slow down a little.

The firstTime variable is for fading in the bar when it first appears.

There is another loop just below this that is very similar, but sweeps the image back the other way.

2

There are 2 answers

0
ntram On

Set

vsync = true

in pygame.display.set_mode()

0
Omega0x013 On

this tearing is just the product of the way that screens render. The next position for the line is rendering while the previous position is being drawn over. If the refresh rate and update rate for the screen are synchronous then this problem disappears. But since that it near impossible to do accurately, I'd suggest making your program take the route that PICO-8 takes in managing this, waiting for a screen refresh to finish before changing the screen buffer to prevent tearing. I'm not sure if there is a method for waiting for a screen refresh to finish, but if there is, then the sequence should go: update back buffer -> wait for screen to stop being drawn -> flip buffers -> screen is drawn again.

A refresh is where the screen is drawn left to right, top to bottom, pixel by pixel. This is because the triple BUS connection to the screen only lets the brightness for one pixel at a time come from the screen memory, so each one is drawn sequentially. The tearing comes from that memory changing midway through a refresh so the line moves position midway down the screen.