Your tone is a bit confrontational, but I also lament the lack of
monitor resolution switching support.
While the referenced page states otherwise, for high resolutions (eg.
2560x1600) fill-rate is definitely an issue and slowdown is
noticeable. It is not unusual for me to have to play games at
1680x1050 in order to achieve acceptable frame rates.
I understand there are also other issues at stake, such as leaving X11
in a low resolution if the application segfaults, but this is
relatively "normal" for games under Linux, Pyglet would not be alone.
> 2. The fact that image.Texture.blit_into() isn't alpha-aware
>
> The bottom two URLs link to more specific info/discussion about these
> issues, to which Alex has basically responded to saying he thinks it's
> out of pyglet's scope to implement these two features.
>
> I highly disagree.
>
> I mean, I'm not demanding here 'ALEX: CODE THIS STUFF'.. but I'm
> saying why completely reject them? Maybe put them on the 'to do
> slate', if not at least say something like 'if someone codes it, i
> will include it'?
>
> I mean the way Alex is currently responding is as if someone hands him
> the code he won't even be willing to add it to pyglet..?
>
> I think these two are both _utterly_ in the realm of pyglet _and_ are
> pretty significant things to have.
>
> :)
>
>
> 1.
> http://groups.google.com/group/pyglet-users/web/issues-with-full-screen-resolution
>
> 2.
> http://code.google.com/p/pyglet/issues/detail?id=340&can=1&sort=-id&colspec=ID%20Status%20Type%20Milestone%20OpSys%20Summary
>
>
>
> Cheerio!
> >
>
--
And the next thing you know, you're at the zoo, shaving a yak,
all so you can wax your car.
If you're interested in monitor resolution switching, there's (mostly)
working code in trunk/experimental/modeswitch. The code there even
handles segfaults cleanly on Linux. It's not yet stable enough for
inclusion into pyglet, nor am I willing to support the code that's
there in its current state.
> Customized texture to texture blitting is
> actually even easier to write as long as it can go super slow.
Well, it's impossible at the texture level. If the texture you're
blitting to is also in memory (or you're willing to pull it out of
graphics memory), you can use PIL or another graphics toolkit to
recompose the image, and then upload it again. There's not much
functionality pyglet could grow here, unless it's growing a complete
image toolkit (which would be great! but infeasible in Python in my
opinion). As I suggested to the OP, it's usually much faster/easier
to composite on the framebuffer instead.
See also the wishlist at
http://code.google.com/p/pyglet/wiki/ReleaseSchedule if you haven't
already; I collect requests/ideas here.
Alex.
stampson was correct in identifying one of the problems in your code;
that you were relying on undefined framebuffer behaviour.
The next two code examples you give fail because the OpenGL defaults
are not the same as pyglet's defaults, and you haven't given pyglet
the chance to set itself up.
>
> #1
> from pyglet import *
> the_color_red = image.SolidColorImagePattern( ( 255, 0, 0, 255 ) )
> red_square = the_color_red.create_image( 100, 100 )
> screen = window.Window()
> while True:
> screen.clear()
> red_square.blit( 0, 0 )
> screen.flip()
In this example (#1) you haven't called screen.dispatch_events() in
your run loop. This is needed firstly to avoid the application
hanging; and secondly to ensure the on_resize event is dispatched to
the window. The default on_resize handler is what sets up the window
coordinate system you're expecting (by default, OpenGL uses normalized
device coordinates in range [-1,1] on all axes).
>
> #2
> from pyglet import *
> the_color_blue = image.SolidColorImagePattern( ( 0, 0, 255, 255 ) )
> blue_square = the_color_blue.create_image( 100, 100 )
> screen = window.Window()
> screen.clear()
> blue_square.blit( 0, 0 )
> box = image.get_buffer_manager().get_color_buffer().get_texture()
>
> @screen.event
> def on_draw():
>
> screen.clear()
> box.blit( 0, 0 )
> app.run()
The problem is similar in this example (#2): you're operating on the
framebuffer before the default on_resize handler is called for the
first time. An easy fix is to call either screen.dispatch_events() or
screen.on_resize(screen.width, screen.height) straight away (or to
restructure your code such that all drawing is in response to the
on_draw event).
To preempt any suggestion that on_resize should be called
automatically as soon as a window is created: this would generate
undesirable behaviour for people wanting to replace the default
on_resize handler with their own.
Cheers
Alex.
All OpenGL implementations I'm aware of have a maximum texture size of
8192x8192, and most have maximum 4096x4096. Older cards are limited
to 2048x2048. Also be aware that most older non-nvidia cards will not
keep textures intact when swapped out of memory -- if you exceed your
graphics memory you'll start getting blank textures. (Newer cards
don't have this problem).
> Also, your suggestion of setting up a hidden window for use as this
> 'temporary buffer' isn't quite a good idea because I've tested it
> thoroughly and there's no obvious way to avoid that window having
> 'popped up' at some time, for a fraction of a second.
Use the visible=False parameter in the Window constructor.
>
> Well, actually now that I think about it, I haven't tried it with
> dispatch_events() which might help.. but even so it would be much
> nicer if pyglet.image.Texture.blit_into() would call the necessary GL
> for alpha preservation. :>
It think you misunderstand: there _is_ no GL command that does this.
blit_into uses glTexSubImage2D, which has no options for blending or
masking. OpenGL can only composite into a framebuffer or renderbuffer
(using the framebuffer object extension), or by creating a separate
context on a pbuffer rendering to the texture (not supported at all on
Linux or older video cards; and pyglet does not (yet) have any pbuffer
support).
Alex.
Pyglet does not have serious FPS issues. set_fps_limit() is
deprecated. If you want an almost constant FPS, write your own event
loop with blocking clock ticks. Otherwise, schedule your animations
and be happy.
-Drew
Writing a good main-loop is not simple - mine took many days and is
still not perfect. I hoped pyglet will do this work for me, since
everything else seems really well thought out. Thanks :) But
considering main-loops - all examples that come with pyglet have some
issues (at least under windows. pyglet version 1.1.1 2224 2008-08-26).
Pyglet 1.1 has the new application event loop. Docs recommend using
clock.schedule_interval(update, 1/60.0)
to get 60fps, and
clock.schedule(update)
to get max fps. Neither works, as fps is not even near the desired
fps. Trying to use the deprecated
pyglet.clock.set_fps_limit(60)
results in jumpy animations (if vsync is off) and too high cpu usage.
Now what..
I've created four test-programs, each one a slightly modified version
of the pyglet-1.1.1-docs.zip opengl.py example. WinMerge can be used
to quickly see the differences between these versions.
http://etm.blastnet.ee/var/list/opengl_clock_schedule.py
http://etm.blastnet.ee/var/list/opengl_clock_schedule_interval.py
http://etm.blastnet.ee/var/list/opengl_fps_limit.py
http://etm.blastnet.ee/var/list/opengl_custom.py
And here are the results I got: (copy-pasted from the py files)
----
opengl_clock_schedule.py
pyglet.clock.schedule(update)
#
# after startup : 64 fps 0% cpu
# while moving mouse inside the window: 143..149 fps, 3% cpu
# after window resize or move : 3300..3500 fps, 60..70% cpu
(and stays this way)
#
# (core2duo e8200, nvidia 8800gt, xp sp2. 50% cpu means 100% of one
core)
#
# window.invalid = True has no effect on fps
----
opengl_clock_schedule_interval.py
pyglet.clock.schedule_interval(update, ..)
#
# param : fps cpu cpu while mousemove cpu after move or resize
# :
# 1/30. : 21 0% 3% 20..23%
# 1/60. : 32 0% 3% 32..39%
# 1/100.: 64 0% 3% 58..63%
# 1/200.: 64 0% 3% 62..75%
#
# no change if i move the window, or move mouse inside the window.
#
----
opengl_fps_limit.py
pyglet.clock.set_fps_limit(..)
#
# limit : fps cpu
# :
# 0 : 3400..3600 70..82%
# 1/30. : 29..60 14%
# 1/60. : 59..60 28%
# 1/100. : 99..102 44%
# 1/200. : 180..185 50%
# 1/400. : 126..130 50%
# 1/800. : 126..130 50%
#
# fps was NOT stable. even changing between 59..60 means jumpy
animations.
#
----
opengl_custom.py
This has my own main-loop. Compare cpu usage.. And animations really
are as smooth without vsync as with (ok, some occasional tearing, but
absolutely no jumping).
#
# fps_dt : fps cpu
# :
# 0. : 3400..3600 70..82%
# 1/60. : 60 0%
# 1/100. : 100 0%
# 1/200. : 200 0%
# 1/400. : 400 0..20%
# 1/800. : 800 50..76%
#
----
Sadly I chose a bad example. Problems would've been much more visible
on a big rotating checkerboard. Well, until next time..
Some of the jumpiness can be traced back to
pyglet.clock.tick()
returning too big values ~4 times per second (if the program runs at
60fps), and too small values rest of the time. Using standard time
module
dt = time.clock() - prev_clock
is much more precise.
I'll see if I can find something in pyglet source.
Hi, thanks for your detailed research. I'll look into this in the
next week and let you know what I find.
Alex.
> I've created four test-programs, each one a slightly modified version
> of the pyglet-1.1.1-docs.zip opengl.py example. WinMerge can be used
> to quickly see the differences between these versions.
>
> http://etm.blastnet.ee/var/list/opengl_clock_schedule.py
> http://etm.blastnet.ee/var/list/opengl_clock_schedule_interval.py
> http://etm.blastnet.ee/var/list/opengl_fps_limit.py
> http://etm.blastnet.ee/var/list/opengl_custom.py
>
> And here are the results I got: (copy-pasted from the py files)
>
> ----
>
> opengl_clock_schedule.py
> pyglet.clock.schedule(update)
>
> #
> # after startup : 64 fps 0% cpu
> # while moving mouse inside the window: 143..149 fps, 3% cpu
> # after window resize or move : 3300..3500 fps, 60..70% cpu
> (and stays this way)
> #
> # (core2duo e8200, nvidia 8800gt, xp sp2. 50% cpu means 100% of one
> core)
> #
> # window.invalid = True has no effect on fps
Thanks, this is fixed in r2259 (pyglet-1.1-maintenance) and r2260
(trunk). You should now see max FPS (3300-3500, for you) before
resizing or moving the window; the problem was merely in event loop
initialisation.
>
> ----
>
> opengl_clock_schedule_interval.py
> pyglet.clock.schedule_interval(update, ..)
>
> #
> # param : fps cpu cpu while mousemove cpu after move or resize
> # :
> # 1/30. : 21 0% 3% 20..23%
> # 1/60. : 32 0% 3% 32..39%
> # 1/100.: 64 0% 3% 58..63%
> # 1/200.: 64 0% 3% 62..75%
> #
> # no change if i move the window, or move mouse inside the window.
> #
These results are surprising (and don't match mine at all). You
should see far higher FPS readings for the code you supplied for
intervals 1/100 and 1/200; because the event loop degenerates to
polling in this case (by design). For the 1/30 and 1/60 cases you
should also see framerates far above the target interval.
Note that to actually achieve the target framerate, you should set
window.invalid to True in on_draw(), and to False in update(). This
gives approximately the target framerate on my machine (slightly
under, actually). There's definitely room for improvement if someone
wants to write a better clock function, ideally using a better
integration function, such as phased lock loop, to correct for jitter
over longer time periods than the current implementation. I
personally don't see this as a big necessity though -- developers
should either be targetting low framerates (below 30) or the refresh
rate (enable vsync); anything else just doesn't make sense.
>
> ----
>
> opengl_fps_limit.py
> pyglet.clock.set_fps_limit(..)
>
> #
> # limit : fps cpu
> # :
> # 0 : 3400..3600 70..82%
> # 1/30. : 29..60 14%
> # 1/60. : 59..60 28%
> # 1/100. : 99..102 44%
> # 1/200. : 180..185 50%
> # 1/400. : 126..130 50%
> # 1/800. : 126..130 50%
> #
> # fps was NOT stable. even changing between 59..60 means jumpy
> animations.
> #
>
I'm not interested in attempting to make set_fps_limit work with the
event loop; so long as it degenerates gracefully (as it seems to now)
I'm happy -- the function is deprecated in favour of scheduling on the
clock.
> ----
>
> opengl_custom.py
> This has my own main-loop. Compare cpu usage.. And animations really
> are as smooth without vsync as with (ok, some occasional tearing, but
> absolutely no jumping).
>
> #
> # fps_dt : fps cpu
> # :
> # 0. : 3400..3600 70..82%
> # 1/60. : 60 0%
> # 1/100. : 100 0%
> # 1/200. : 200 0%
> # 1/400. : 400 0..20%
> # 1/800. : 800 50..76%
> #
>
This event loop isn't very general: it only works for one window, and
doesn't let events preempt the loop; making it ultimately more latent
than the pyglet event loop, unless your application is
non-interactive. It also doesn't run while windows are being moved or
resized, or while menus are being tracked on OS X.
> ----
>
> Sadly I chose a bad example. Problems would've been much more visible
> on a big rotating checkerboard. Well, until next time..
>
>
> Some of the jumpiness can be traced back to
>
> pyglet.clock.tick()
>
> returning too big values ~4 times per second (if the program runs at
> 60fps), and too small values rest of the time. Using standard time
> module
>
> dt = time.clock() - prev_clock
>
> is much more precise.
Is this due to pyglet.clock using time.time() instead of time.clock()?
Alex.
Hm. Now I'm interested. I'll find what's wrong with my computer by
tomorrow..
> Note that to actually achieve the target framerate, you should set
> window.invalid to True in on_draw(), and to False in update().
Not if I render in update() and overload the flip() method : )
Why I would want that:
For my little hobby-games/programs I make the assumption that every
computer worth supporting can run the program at 60fps. 30fps and
animating non-blurry graphics is too ugly for me. I just can't stand
it. And since I'm too lazy to decouple game-physics from framerate and
use interpolation to get updated positions in the renderer, I have no
other choice but to run *everything* at 60fps. (sadly physics has to
work with a fixed timestep. no other way).
Once I made the 60fps decision, there's no need for externally
generated on_draw events (resizing the window, moving something over
the window..).
Btw, the on_draw event is always triggered twice at program startup.
Once for resize, once for move (told by WindowEventLogger). Setting
window.Invalid to False on window constructor has no effect.
> This gives approximately the target framerate on my machine (slightly
> under, actually). There's definitely room for improvement if someone
> wants to write a better clock function, ideally using a better
> integration function, such as phased lock loop, to correct for jitter
> over longer time periods than the current implementation. I
> personally don't see this as a big necessity though -- developers
> should either be targetting low framerates (below 30) or the refresh
> rate (enable vsync); anything else just doesn't make sense.
More than 30fps makes sense if the game has physics that is not
decoupled from the framerate. Everything I do : /
> I'm not interested in attempting to make set_fps_limit work with the
> event loop; so long as it degenerates gracefully (as it seems to now)
> I'm happy -- the function is deprecated in favour of scheduling on the
> clock.
Agreed. I just included the example in hopes it would give some ideas
for the schedule_interval problem.
>> ----
>>
>> opengl_custom.py
>> This has my own main-loop. Compare cpu usage.. And animations really
>> are as smooth without vsync as with (ok, some occasional tearing, but
>> absolutely no jumping).
>>
>> #
>> # fps_dt : fps cpu
>> # :
>> # 0. : 3400..3600 70..82%
>> # 1/60. : 60 0%
>> # 1/100. : 100 0%
>> # 1/200. : 200 0%
>> # 1/400. : 400 0..20%
>> # 1/800. : 800 50..76%
>> #
>>
>
> This event loop isn't very general: it only works for one window, and
> doesn't let events preempt the loop; making it ultimately more latent
> than the pyglet event loop, unless your application is
> non-interactive. It also doesn't run while windows are being moved or
> resized, or while menus are being tracked on OS X.
Latency and preemption are not a problem for 30-60fps programs
(usually.. But if someone has a 200Hz mouse, some of the coordinate
post-processing potential might be lost due to the mousemove events
having no timestamp. I don't know if the mouse example even makes
sense : ). I'll try to find out if the preemptive loop can be made as
smooth as my own loop under windows. Maybe it can't in principle..
Maybe there'll always be a choice between a general event loop, and a
smoothest-possible-animation single window event loop. I'm not sure of
anything yet. But thanks for the explanation.
>> Some of the jumpiness can be traced back to
>>
>> pyglet.clock.tick()
>>
>> returning too big values ~4 times per second (if the program runs at
>> 60fps), and too small values rest of the time. Using standard time
>> module
>>
>> dt = time.clock() - prev_clock
>>
>> is much more precise.
>
> Is this due to pyglet.clock using time.time() instead of time.clock()?
Yes. I changed time.time to time.clock in pyglet/clock.py
class Clock...:
def __init__(self, fps_limit=None, time_function=time.time)
and now the set_fps_limit version of opengl.py is exactly as smooth as
my own main loop, and the other versions are smoother than before.
But that's only windows-specific. time.time should be used on linux
and macosx. python-list has some discussions that explain it better
than I could. Keywords are time.time, time.clock and time.sleep.
(time.clock can jump backwards in time sometimes (i've never seen it
jump more than a few ms). but so can time.time if someone decides to
change computer date while pyglet is running)
Well, I don't quite agree with your premise, but anyway...
You can get _exactly_ the monitor refresh rate fps using
clock.schedule() and vsync=True. Surely you'd prefer to run at 75 fps
on a 75 Hz display, to avoid duplicating frames (and creating jitter).
Using schedule_interval and invalid=False, you should get quite close
to the desired framerate (it's actually pretty spot on on Linux and OS
X, and slightly behind on Windows). Note that this at least
guarantees on_draw() will not be called more often than 60 fps.
>
> Once I made the 60fps decision, there's no need for externally
> generated on_draw events (resizing the window, moving something over
> the window..).
>
> Btw, the on_draw event is always triggered twice at program startup.
> Once for resize, once for move (told by WindowEventLogger). Setting
> window.Invalid to False on window constructor has no effect.
You should be setting it to True in the on_draw handler.
> More than 30fps makes sense if the game has physics that is not
> decoupled from the framerate. Everything I do : /
FWIW, my latest PyWeek entry (http://pyweek.org/e/midnightsun/) is
physics-based and runs at 30 FPS using the method described above
(schedule_interval + invalid flag), without interpolation, and doesn't
seem to exhibit any jitter on slow or fast machines.
Alex.
The following code fixes schedule_interval(update, 1/100.) timings (in
trunk and pyglet-1.1-maintenance). But schedule_interval(update,
1/90.) still results in 64-70fps. In other words - the polling version
(>100fps) of the main-loop works ok with this change:
import pyglet
pyglet.clock.set_default(pyglet.clock.Clock(time_function=time.clock))
Finally, after timing nearly everything in pyglet/app/win32.py, I made
a little test-program and got some depressing results:
# python 2.5.2 (r252:60911, Feb 21 2008, 13:11:45) [MSC v.1310 32 bit
(Intel)]
import time
p = 0.
while 1:
t = time.time()
if t != p: print "timeupdates/s", 1. / (t - p)
p = t
...
timeupdates/s 62.4998733404
timeupdates/s 66.6662004292
timeupdates/s 62.5008046731
timeupdates/s 66.6662004292
...
time.time() updates only ~64 times per second on my computer. And
seems that _user32.SetTimer has the same resolution.
>> For my little hobby-games/programs I make the assumption that every
>> computer worth supporting can run the program at 60fps. 30fps and
>> animating non-blurry graphics is too ugly for me. I just can't stand
>> it. And since I'm too lazy to decouple game-physics from framerate
>> and
>> use interpolation to get updated positions in the renderer, I have no
>> other choice but to run *everything* at 60fps. (sadly physics has to
>> work with a fixed timestep. no other way).
>
> Well, I don't quite agree with your premise, but anyway...
>
> You can get _exactly_ the monitor refresh rate fps using
> clock.schedule() and vsync=True. Surely you'd prefer to run at 75 fps
> on a 75 Hz display, to avoid duplicating frames (and creating jitter).
I might prefer some jitter over slightly changing gameplay physics and
over fixed 30fps. I'll do some testing to find out how bad 60fps is on
a 75Hz display.
> FWIW, my latest PyWeek entry (http://pyweek.org/e/midnightsun/) is
> physics-based and runs at 30 FPS using the method described above
> (schedule_interval + invalid flag), without interpolation, and doesn't
> seem to exhibit any jitter on slow or fast machines.
Very nice : ) although 22fps here..