Discussion:
Circle cut off at 60+fps, fixed by display.flip
Abhas Bhattacharya
2014-06-12 07:31:39 UTC
Permalink
The following code draws a circle and moves it by 4px to left every step.

Problem is that at about 60 fps or more, the circle flickers and sometimes
looks cut off to me (approx. illustration -
Loading Image...). The flickering is not like a screen
flicker, more like state switching between full circle and cut-off circle.
This problems doesnt occur at all upto 45 fps, couldnt be reproduced by
screen capture or pausing the game, as if the problem is only visible to
the naked eye.

Now, there could be two possibilities:

* Screen is not updated over the required region.

None of the update code depends on the fps, so the flicker should have been
fps-independent. But, the problem disappears at 30/45 fps. But,
surprisingly, it is also fixed if display.flip is used instead of update.

* V-sync/monitor refresh problem

Some other pygame flicker questions mentioned this problem. I thought
software rendering doesnt have this problem, but not really sure. Also, why
would display.flip fix this?

Ver- 1.9.2 a0

import pygame
w,h=800,200
fps=60
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=800,100
def get_bbox(x,y):
left = x - radius
top = y - radius
width = radius * 2
height = radius * 2
return pygame.Rect((left, top), (width, height))

while True:
old_x=x
x-=4
screen.fill(pygame.Color("black"),get_bbox(old_x,y))
pygame.draw.circle(screen, color, (x, y), radius, 1)
pygame.display.update([get_bbox(x,y),get_bbox(old_x,y)])
clock.tick(fps)
Radomir Dopieralski
2014-06-12 09:05:03 UTC
Permalink
Post by Abhas Bhattacharya
Problem is that at about 60 fps or more, the circle flickers and
sometimes looks cut off to me (approx. illustration -
http://i.imgur.com/4nKzsCP.png). The flickering is not like a screen
flicker, more like state switching between full circle and cut-off
circle. This problems doesnt occur at all upto 45 fps, couldnt be
reproduced by screen capture or pausing the game, as if the problem is
only visible to the naked eye.
Please read this article for at least a partial explanation:
http://paulbakaus.com/tutorials/performance/the-illusion-of-motion/
--
The Sheep
Jesterea
2014-06-12 10:17:50 UTC
Permalink
Yes. The article seems long and detailed.

V-sync isn't only an issue on 3D-Accelerated Graphics, it has been there
since early TVs and throughout the entire 2D - Graphice - Era as well.

In short, if you don't wait for the "gap" or until the monitor had time to
draw the entire screen, it's possible that you push him the next image
while he's in the middle of the last image, and therefore creating a break
in animated sequences.

Best wishes

Daniel
Post by Radomir Dopieralski
Post by Abhas Bhattacharya
Problem is that at about 60 fps or more, the circle flickers and
sometimes looks cut off to me (approx. illustration -
http://i.imgur.com/4nKzsCP.png). The flickering is not like a screen
flicker, more like state switching between full circle and cut-off
circle. This problems doesnt occur at all upto 45 fps, couldnt be
reproduced by screen capture or pausing the game, as if the problem is
only visible to the naked eye.
http://paulbakaus.com/tutorials/performance/the-illusion-of-motion/
--
The Sheep
Abhas Bhattacharya
2014-06-12 12:12:25 UTC
Permalink
Great article, lots of new stuff to consume.
But coming to the problem, I've never really had such problems
earlier. Were you able to re-create the situation or is it just me?

I was under the impression that pygame handled all this stuff
internally just fine.
Abhas Bhattacharya
2014-06-12 12:16:19 UTC
Permalink
Also, just to mention again, screen,flip magically solves the problem,
which made me think that it was a incorrect screen update issue. But
the code seems pretty simple and couldnt really find any screen update
bug.
Vincent Michel
2014-06-12 13:35:42 UTC
Permalink
I ran your code and clearly noticed the same problem.
Also displayed the dirty rectangles and there's nothing wrong here.
I tried with a Sprite/RenderUpdates approach so I can use the
RenderUpdates.draw method and I observed the same result.

I also agree that pygame should handle this stuff internally. This behavior
really surprises me.

I'll keep experimenting a bit
Post by Abhas Bhattacharya
Also, just to mention again, screen,flip magically solves the problem,
which made me think that it was a incorrect screen update issue. But
the code seems pretty simple and couldnt really find any screen update
bug.
Christopher Night
2014-06-12 13:49:01 UTC
Permalink
I get that you want to understand what's going on, but beyond that, is
there some problem with just using display.flip?


On Thu, Jun 12, 2014 at 3:31 AM, Abhas Bhattacharya <
Post by Abhas Bhattacharya
The following code draws a circle and moves it by 4px to left every step.
Problem is that at about 60 fps or more, the circle flickers and sometimes
looks cut off to me (approx. illustration - http://i.imgur.com/4nKzsCP.png).
The flickering is not like a screen flicker, more like state switching
between full circle and cut-off circle. This problems doesnt occur at all
upto 45 fps, couldnt be reproduced by screen capture or pausing the game,
as if the problem is only visible to the naked eye.
* Screen is not updated over the required region.
None of the update code depends on the fps, so the flicker should have
been fps-independent. But, the problem disappears at 30/45 fps. But,
surprisingly, it is also fixed if display.flip is used instead of update.
* V-sync/monitor refresh problem
Some other pygame flicker questions mentioned this problem. I thought
software rendering doesnt have this problem, but not really sure. Also, why
would display.flip fix this?
Ver- 1.9.2 a0
import pygame
w,h=800,200
fps=60
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=800,100
left = x - radius
top = y - radius
width = radius * 2
height = radius * 2
return pygame.Rect((left, top), (width, height))
old_x=x
x-=4
screen.fill(pygame.Color("black"),get_bbox(old_x,y))
pygame.draw.circle(screen, color, (x, y), radius, 1)
pygame.display.update([get_bbox(x,y),get_bbox(old_x,y)])
clock.tick(fps)
Vincent Michel
2014-06-12 16:04:41 UTC
Permalink
Using display.flip is slower since it updates all the screen.
display.update updates only the area covered by the list of "dirty"
rectangles.
Post by Christopher Night
I get that you want to understand what's going on, but beyond that, is
there some problem with just using display.flip?
On Thu, Jun 12, 2014 at 3:31 AM, Abhas Bhattacharya <
Post by Abhas Bhattacharya
The following code draws a circle and moves it by 4px to left every step.
Problem is that at about 60 fps or more, the circle flickers and
sometimes looks cut off to me (approx. illustration -
http://i.imgur.com/4nKzsCP.png). The flickering is not like a screen
flicker, more like state switching between full circle and cut-off circle.
This problems doesnt occur at all upto 45 fps, couldnt be reproduced by
screen capture or pausing the game, as if the problem is only visible to
the naked eye.
* Screen is not updated over the required region.
None of the update code depends on the fps, so the flicker should have
been fps-independent. But, the problem disappears at 30/45 fps. But,
surprisingly, it is also fixed if display.flip is used instead of update.
* V-sync/monitor refresh problem
Some other pygame flicker questions mentioned this problem. I thought
software rendering doesnt have this problem, but not really sure. Also, why
would display.flip fix this?
Ver- 1.9.2 a0
import pygame
w,h=800,200
fps=60
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=800,100
left = x - radius
top = y - radius
width = radius * 2
height = radius * 2
return pygame.Rect((left, top), (width, height))
old_x=x
x-=4
screen.fill(pygame.Color("black"),get_bbox(old_x,y))
pygame.draw.circle(screen, color, (x, y), radius, 1)
pygame.display.update([get_bbox(x,y),get_bbox(old_x,y)])
clock.tick(fps)
Abhas Bhattacharya
2014-06-13 02:32:11 UTC
Permalink
What is also quite surprising is that the circle gets cut off in the
same direction in which it is moving. That makes me think it cant be a
v-sync/screen-refresh issue.
B W
2014-06-13 16:42:39 UTC
Permalink
Try this.

import pygame
from pygame import Color, KEYDOWN
w,h=800,200
fps=60
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=800,100
speedx=-4
def get_bbox(x,y):
left = x - radius + speedx
top = y - radius
width = radius * 2 + abs(speedx) * 2
height = radius * 2
return pygame.Rect((left, top), (width, height))

while True:
old_x=x
x+=speedx
screen.fill(pygame.Color("black"),get_bbox(old_x,y))
pygame.draw.circle(screen, color, (x, y), radius, 1)
get_bbox(x,y)
pygame.display.update([get_bbox(old_x,y),get_bbox(x,y)])
clock.tick(fps)



On Thu, Jun 12, 2014 at 7:32 PM, Abhas Bhattacharya <
Post by Abhas Bhattacharya
What is also quite surprising is that the circle gets cut off in the
same direction in which it is moving. That makes me think it cant be a
v-sync/screen-refresh issue.
B W
2014-06-13 16:50:02 UTC
Permalink
Oops. I spotted a couple things I changed to aid my experimentation, and
did not revert. They do not affect the operation of the program, but I want
to identify them as a red herring so you don't waste time wondering about
the differences.

The import Color and KEYDOWN. This had no impact on the behavior.

The calls to get_bbox() are reversed in pygame.display.update(). This had
no impact on the behavior either way.

The changes I made that do impact the code are the lines that contain
speedx.

Cheers,

Gumm
Post by B W
Try this.
import pygame
from pygame import Color, KEYDOWN
w,h=800,200
fps=60
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=800,100
speedx=-4
left = x - radius + speedx
top = y - radius
width = radius * 2 + abs(speedx) * 2
height = radius * 2
return pygame.Rect((left, top), (width, height))
old_x=x
x+=speedx
screen.fill(pygame.Color("black"),get_bbox(old_x,y))
pygame.draw.circle(screen, color, (x, y), radius, 1)
get_bbox(x,y)
pygame.display.update([get_bbox(old_x,y),get_bbox(x,y)])
clock.tick(fps)
On Thu, Jun 12, 2014 at 7:32 PM, Abhas Bhattacharya <
Post by Abhas Bhattacharya
What is also quite surprising is that the circle gets cut off in the
same direction in which it is moving. That makes me think it cant be a
v-sync/screen-refresh issue.
Greg Ewing
2014-06-13 23:41:23 UTC
Permalink
I just tried this on a Mac, and it works fine,
no flickering or tearing.

The only thing I can think of is that, on your system,
display.update() is not waiting for vertical sync,
whereas display.flip() is.

The pygame docs aren't very clear about whether it's
supposed to or not. They say that flip() does, and then
just say that update() is an "optimised version" of
flip().

This suggest to me that it should, and it does seem
to on my system, but maybe some implementations are
different.

Maybe you could try passing a single rect to update()
that's the union of the old an new rects. While that
won't eliminate all possibility of tearing, it may
make it less likely.
--
Greg
Jeffrey Kleykamp
2014-06-14 05:13:45 UTC
Permalink
One thing I noticed about your code is you're making your own rectangle.
The draw function returns a rect so you should pass that. It's useful
because the rect it returns is constrained by the screen so there's less to
update. Plus you don't have to make as many rects. But that doesn't fix
your problem.

Here's just a list of observations I made while experimenting.

I'm not sure what's causing this issue. It's weird because it goes in all
directions. It seems to be affected by the time it has to compute. Eg. if
you change fps down to 20 it goes away. And if you change it to 120 it gets
terrible. Also the amount of clipping is proportional to the speed at which
you move. More speed = more clipping.

I'm not sure why flipping or increasing the size of the rect fixes the
problem but it does. If you inflate your rect slightly it fixes it. But if
you increase speed too much then it comes back.

The final observation that I made is that's it's not possible to stop on a
clipped rect. This makes me think this is a vsync issue. I'm pretty sure
that pygame does not vsync unless it's in hardware accelerated fullscreen.
So maybe the reason that flipping and bigger rects stop the issue is
because they give the system a slight delay. I also managed to get two
circles to draw and the effect goes away entirely. This might also have to
do with a longer processing time.

Here's modified code that allows you to try to "catch" the clipping in
action (it's not observable). It also toggles from inflating to not if you
uncomment the right part. This doesn't have two circles.

import pygame
w,h=500,500
fps=40
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=w/2,h
dx = 10
r = pygame.Rect((0,0), (radius*2, radius*2))
r.center = (x, y)
inflate = False
def get_bbox(x,y):
left = x - radius
top = y - radius
width = radius * 2
height = radius * 2
return pygame.Rect((left, top), (width, height))

while True:
old_r=r
y-=dx
if y < 100:
y = w
#fps -= 5
#dx += 1
fps = 0
#inflate = not inflate
if fps <= 0:
fps = 40
pygame.time.wait(500)
screen.fill(pygame.Color("black"),old_r)
r = pygame.draw.circle(screen, color, (x, h-y), radius, 0)
if inflate: r.inflate_ip(20,20)
pygame.display.update([r,old_r])
clock.tick(fps)


I'm really at a loss as to what this actually is,
Jeffrey
Post by Greg Ewing
I just tried this on a Mac, and it works fine,
no flickering or tearing.
The only thing I can think of is that, on your system,
display.update() is not waiting for vertical sync,
whereas display.flip() is.
The pygame docs aren't very clear about whether it's
supposed to or not. They say that flip() does, and then
just say that update() is an "optimised version" of
flip().
This suggest to me that it should, and it does seem
to on my system, but maybe some implementations are
different.
Maybe you could try passing a single rect to update()
that's the union of the old an new rects. While that
won't eliminate all possibility of tearing, it may
make it less likely.
--
Greg
--
Jeffrey Kleykamp
Abhas Bhattacharya
2014-06-15 01:01:11 UTC
Permalink
I too think the "more processing time" inference might be right.
Interestingly, the issue got fixed on one restart and came back with
the next.

Pygame doesnt stop suprising me :)
Abhas Bhattacharya
2014-06-15 01:03:39 UTC
Permalink
I was just experimenting with my game engine on top of pygame and was
trying that to make sure its performant enough.
Why do you want 60 FPS so bad anyway? for most 2D applications, 30 FPS is
more than enough.
Post by Abhas Bhattacharya
The following code draws a circle and moves it by 4px to left every step.
Problem is that at about 60 fps or more, the circle flickers and
sometimes looks cut off to me (approx. illustration -
http://i.imgur.com/4nKzsCP.png). The flickering is not like a screen
flicker, more like state switching between full circle and cut-off circle.
This problems doesnt occur at all upto 45 fps, couldnt be reproduced by
screen capture or pausing the game, as if the problem is only visible to
the naked eye.
* Screen is not updated over the required region.
None of the update code depends on the fps, so the flicker should have
been fps-independent. But, the problem disappears at 30/45 fps. But,
surprisingly, it is also fixed if display.flip is used instead of update.
* V-sync/monitor refresh problem
Some other pygame flicker questions mentioned this problem. I thought
software rendering doesnt have this problem, but not really sure. Also, why
would display.flip fix this?
Ver- 1.9.2 a0
import pygame
w,h=800,200
fps=60
pygame.init()
screen = pygame.display.set_mode([w, h])
color=pygame.Color("white")
clock=pygame.time.Clock()
radius=20
x,y=800,100
left = x - radius
top = y - radius
width = radius * 2
height = radius * 2
return pygame.Rect((left, top), (width, height))
old_x=x
x-=4
screen.fill(pygame.Color("black"),get_bbox(old_x,y))
pygame.draw.circle(screen, color, (x, y), radius, 1)
pygame.display.update([get_bbox(x,y),get_bbox(old_x,y)])
clock.tick(fps)
Loading...