Monitor resolution & blit_into alpha

54 views
Skip to first unread message

3TATUK

unread,
Sep 8, 2008, 2:20:41 AM9/8/08
to pyglet-users
What are your general opinions on the following two issues?

1. The lack of monitor resolution changing capability
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!

Alec Thomas

unread,
Sep 8, 2008, 2:31:18 AM9/8/08
to pyglet...@googlegroups.com
2008/9/8 3TATUK <rememb...@gmail.com>:

>
> What are your general opinions on the following two issues?
>
> 1. The lack of monitor resolution changing capability

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.

Brian Fisher

unread,
Sep 8, 2008, 2:37:15 AM9/8/08
to pyglet...@googlegroups.com
Those 2 issues are the major reasons I don't use pyglet at the moment, so they are critically important to me. But at the same time I understand completely why he's not doing them, and support that. Basically he wants to do what he can do well - He wants no dependencies whatsoever and wants everything to work well and reliably cross platform.

Also, there are no patches he is refusing for those features and there is no reason why those features couldn't be independently implemented, or monkey patched into pyglet or any of that. Furthermore he releases pyglet under the generous BSD license (much better than GPL, in my opinion) so he's basically saying if you want to make a fork and relicense it how you want, go ahead just do attribution. Really I think there's nothing to fault him on here.

If you really want this stuff, I would suggest you go start writing it. Resolution changing isn't that hard really, there is plenty of source for it, SDL has it for tons of platforms, and ctypes makes it easy to call platform stuff from python. Customized texture to texture blitting is actually even easier to write as long as it can go super slow. I understand it's better to have those things in a library so people can help you with the really hard work of testing and iterating and improving until it's great - but who knows, maybe Alex would help you with all that by putting it in pyglet if you had a good start at it.

3TATUK

unread,
Sep 8, 2008, 3:00:53 AM9/8/08
to pyglet-users
Hmm like you just said about alpha blitting being really slow yeah
that's obviously true because I already _wrote_ my own routine which
blits pixel-per-pixel by using image.Texture.get_region() which
results in a 64x64 texture taking 4096 blits which is just ridiculous.

It seems like Alex almost feels he's perfectly capable of quickly and
easily implementing these things for the exact reasons you just stated
and the fact he's comfortable with working with OpenGL at that low
level.

As far as dependencies and reliability .. there really wouldn't be
any. The alpha blitting would just be a rewrite of blit_into with
proper OpenGL functioning.. and the screen resolution like you just
said wouldn't be so hard, just look at how SDL does it low-level and
write a ctypes port.. Plus there's already some super easy pure-python
code for (semi-reliable) windows resolution changing:

import os, pywintypes, sys, win32api

display_modes = dict()
n = 0
while True:
try:
devmode = win32api.EnumDisplaySettings( None, n )
except pywintypes.error:
break
else:
key = (
devmode.BitsPerPel,
devmode.PelsWidth,
devmode.PelsHeight,
devmode.DisplayFrequency
)
display_modes[key] = devmode
n += 1

win32api.ChangeDisplaySettings( display_modes[( 8, 640, 480, 60 )],
0 )

also, here's my -super slow * you're crazy if you use it cause I don't
even use it * alpha_blit_into code-:

alpha = chr( 0 ) + chr( 17 ) + chr( 0 ) + chr( 0 ) + chr( 46 ) * 252
def alpha_blit_into( source, destination, X = 0, Y = 0 ):
for x in range( source.width ):
for y in range( source.height ):
pixel = source.get_region( x, y, 1, 1 )
if pixel.get_data( 'RGBA', -256 ) != alpha:
destination.texture.blit_into( pixel, X+x, Y+y, 0 )


On Sep 8, 1:37 am, "Brian Fisher" <thorbr...@gmail.com> wrote:
> Those 2 issues are the major reasons I don't use pyglet at the moment, so
> they are critically important to me. But at the same time I understand
> completely why he's not doing them, and support that. Basically he wants to
> do what he can do well - He wants no dependencies whatsoever and wants
> everything to work well and reliably cross platform.
>
> Also, there are no patches he is refusing for those features and there is no
> reason why those features couldn't be independently implemented, or monkey
> patched into pyglet or any of that. Furthermore he releases pyglet under the
> generous BSD license (much better than GPL, in my opinion) so he's basically
> saying if you want to make a fork and relicense it how you want, go ahead
> just do attribution. Really I think there's nothing to fault him on here.
>
> If you really want this stuff, I would suggest you go start writing it.
> Resolution changing isn't that hard really, there is plenty of source for
> it, SDL has it for tons of platforms, and ctypes makes it easy to call
> platform stuff from python. Customized texture to texture blitting is
> actually even easier to write as long as it can go super slow. I understand
> it's better to have those things in a library so people can help you with
> the really hard work of testing and iterating and improving until it's great
> - but who knows, maybe Alex would help you with all that by putting it in
> pyglet if you had a good start at it.
>
> On Sun, Sep 7, 2008 at 11:20 PM, 3TATUK <remember....@gmail.com> wrote:
>
> > What are your general opinions on the following two issues?
>
> > 1. The lack of monitor resolution changing capability
> > 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-scre...
>
> > 2.
>
> >http://code.google.com/p/pyglet/issues/detail?id=340&can=1&sort=-id&c...
>
> > Cheerio!

Alex Holkner

unread,
Sep 8, 2008, 3:35:05 AM9/8/08
to pyglet...@googlegroups.com
On Mon, Sep 8, 2008 at 4:37 PM, Brian Fisher <thor...@gmail.com> wrote:
> If you really want this stuff, I would suggest you go start writing it.
> Resolution changing isn't that hard really, there is plenty of source for
> it, SDL has it for tons of platforms, and ctypes makes it easy to call
> platform stuff from python.

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.

3TATUK

unread,
Sep 8, 2008, 4:55:33 AM9/8/08
to pyglet-users
:) Thanks for the reply Alex!

Nice to see there's actually some code for mode changing going and
it's on a wishlist. I just thought you were very inflexible as to the
whole idea when, from my perspective, and not only to myself, the idea
is rather significant.

Please to add alpha blit_into()'ing to the wishlist? ^_^

And as far as working directly with framebuffers.. it's not quite as
easy as I thought.

First off, I don't want to create off-screen FBO's because then the
framebuffer extension will be required. So that leaves working with
the current active framebuffer as a temporary 'drawing board' to blit
to (with alpha) then save from..

The issue that pops up right away with that is what if the destination
texture is bigger than the window size? My 'solution' is to 'simply'
work in 'pieces' which are the size of the window, then blit piece-by-
piece into a new resultant texture..

Obviously this can potentially still require lots of blit_into() calls
for a single blit_into() call with desired alpha. Slow.

And that's also not the only issue with using the active framebuffer
as a temp board for alpha blitting..

I've already started to try doing this 'piece construction' but
something strange happens when blitting textures outside of the
context of @window.even or the function specified by scheduling... The
texture gets like misplaced and distorted and stretched out? So it's
definitely not too feasible.

But on the other hand.. there _has_ to be some lower-level OpenGL
method of blitting one texture to another with alpha. I've even seen
the C/++ code for it and tried with pyglet but get that same
distortion result.

Here's the C/++ code:
http://www.gamedev.net/community/forums/topic.asp?topic_id=372568&whichpage=1&#2456662

And here's my simplified distortion example:

from pyglet import *

the_color_pink = image.SolidColorImagePattern( ( 255, 0, 255, 255 ) )
the_color_green = image.SolidColorImagePattern( ( 0, 255, 0, 255 ) )

pink_square = the_color_pink.create_image( 100, 100 )
green_square = the_color_green.create_image( 100, 100 )

screen = window.Window()

@screen.event
def on_draw():
green_square.blit( 0, 0 )
pass

pink_square.blit( 0, 0 )

app.run()

# Notice both green_square and pink_square are 100x100 pixels but the
pink one is 'distorted'
# whereas the green box turns out okay

# Thank you for your work on Pyglet, Alex!

On Sep 8, 2:35 am, "Alex Holkner" <alex.holk...@gmail.com> wrote:
> See also the wishlist athttp://code.google.com/p/pyglet/wiki/ReleaseScheduleif you haven't

stampson

unread,
Sep 8, 2008, 11:03:48 AM9/8/08
to pyglet-users
The C++ code example appears to be using multitexture. Multitexture
support is in the pyglet trunk, thanks to Alex, as a vertex list
attribute. I'm not sure what you're ultimately trying to do, but
multitexturing might work for you, depending. Of course, the
resulting texture will still not have alpha preserved - and that may
be why nobody suggested multitexture as an option for you. I posted a
crude multitexturing example here earlier, that works in pyglet 1.1.

With regards to your distorted pink square, you are blitting the pink
square first, one time, before the window is ever cleared - then you
enter your app loop, and every time on_draw is called you're blitting
the pink square, but you are not clearing the window. Every time the
loop runs, the buffers are being flipped, but the pink square is only
in one of them (along with who knows what else). If you place a
'screen.clear()' line before the green_square.blit line, the result is
as expected - the pink square is blitted and then almost instantly
covered by the green one, as the pink square is never drawn again.

Pyglet excels at giving easy access to OpenGL, cross-platform, and
stably. If that was all it was, it would be great, but it also has
grown to have nifty things like graphics batches, sprites, etc. All
of these are python-based but are ultimately using OpenGL to make
things happen - just in a way that is pythonic and surprisingly
speedy. When you want something which is outside the scope of OpenGL,
then you are faced with using other modules (introducing
dependencies), or using pure python (probably slow).

Collision detection is an example. While there may be tricks to get
OpenGL to help with collision detection, it really boils down to a
task that requires boatloads of comparisons. This places a
significant limitation on how many sprites you could animate, i you
want to detect collisions unilaterally. Adding collision detection as
a feature in pyglet would require introducing a dependency, and not
one like OpenGL that is nigh-universal. But, there happens to be a
module called pymunk, which uses ctypes to access chipmunk physics,
with which a pyglet app can do collision detection and animation of
hundreds of sprites. It might be very useful to many people. Alex
could decide to integrate support for chimpunk physics into pyglet,
but that would radically change what pyglet is. On the other hand,
nothing stops me from doing that myself.

-price
> Here's the C/++ code:http://www.gamedev.net/community/forums/topic.asp?topic_id=372568&whi...
> > See also the wishlist athttp://code.google.com/p/pyglet/wiki/ReleaseScheduleifyou haven't

3TATUK

unread,
Sep 8, 2008, 3:56:36 PM9/8/08
to pyglet-users
I understand how things are 'supposed' to work, but I still don't
understand why a texture blit doesn't work 'normally', out of that
context it's 'supposed' to work in.

Whatever you said about 'screen.clear()' is _utterly_ irrelevant and I
will prove it with two examples:

#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()

#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()

# There's nothing you can do (as seen so far) that will allow proper
active framebuffer blitting outside of on_draw() or an explicitly
scheduled event. Probably has to do with how a GL context is set up /
executed within the built-in event loop... but it would be nice if
pyglet.image.Texture.blit() performed checks as to whether the context
has been properly set up yet, and if not .. do so. ;x

Also, some work is being done as far sprite collision ..
http://code.google.com/p/pyglet/wiki/Scene2dIdeas
> > > See also the wishlist athttp://code.google.com/p/pyglet/wiki/ReleaseScheduleifyouhaven't

Alex Holkner

unread,
Sep 8, 2008, 8:46:42 PM9/8/08
to pyglet...@googlegroups.com
On 9/9/08, 3TATUK <rememb...@gmail.com> wrote:
>
> I understand how things are 'supposed' to work, but I still don't
> understand why a texture blit doesn't work 'normally', out of that
> context it's 'supposed' to work in.
>
> Whatever you said about 'screen.clear()' is _utterly_ irrelevant and I
> will prove it with two examples:

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.

3TATUK

unread,
Sep 8, 2008, 10:31:51 PM9/8/08
to pyglet-users
Ah! Thank you very much. Yes, window.Window.dispatch_events() certain
_does_ set it up properly so I can now use the active framebuffer as a
'drawing board' for alpha blitting. So for example, say I have
20,000x20,000 pixel texture.. it will take ``only'' 1302
blit_into()s. This also assumes that the 'destination' texture
doesn't have any alpha in it :)

This is much better than 4096 blit_into()s for a single 64x64 pixel.

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.

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. :>

On Sep 8, 7:46 pm, "Alex Holkner" <alex.holk...@gmail.com> wrote:

Alex Holkner

unread,
Sep 8, 2008, 10:43:31 PM9/8/08
to pyglet...@googlegroups.com
On Tue, Sep 9, 2008 at 12:31 PM, 3TATUK <rememb...@gmail.com> wrote:
>
> Ah! Thank you very much. Yes, window.Window.dispatch_events() certain
> _does_ set it up properly so I can now use the active framebuffer as a
> 'drawing board' for alpha blitting. So for example, say I have
> 20,000x20,000 pixel texture.. it will take ``only'' 1302
> blit_into()s. This also assumes that the 'destination' texture
> doesn't have any alpha in it :)

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.

3TATUK

unread,
Sep 9, 2008, 1:27:37 AM9/9/08
to pyglet-users
I've already tried visible=False and set_visible() but the fact is
that the window _has_ to be visible at some point in time (even if for
a fraction of a second) in order to not get BLACK from
pyglet.image.get_buffer_manager().get_color_buffe().get_texture()

Again, I have not tried this yet with dispatch_events(), which I
suspect _will_ solve the issue.

Also, one more thing as far as performance goes..
Say I _do_ have a 2048x2048 texture and am blitting it to a 640x480
window (every frame)..

Would it be more efficient to do texture.get_region(x,y,
640,480).get_texture()

or just blit the while 2048x2048 texture?

At this point it's kind of pre-mature optimization because pyglet has
serious FPS issues as it is.
( see http://code.google.com/p/pyglet/issues/detail?id=341 )

But, I'm still curious.

On Sep 8, 9:43 pm, "Alex Holkner" <alex.holk...@gmail.com> wrote:

Drew Smathers

unread,
Sep 9, 2008, 11:32:36 AM9/9/08
to pyglet...@googlegroups.com
On Tue, Sep 9, 2008 at 1:27 AM, 3TATUK <rememb...@gmail.com> wrote:
> ...

> At this point it's kind of pre-mature optimization because pyglet has
> serious FPS issues as it is.
> ( see http://code.google.com/p/pyglet/issues/detail?id=341 )
>

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

3TATUK

unread,
Sep 9, 2008, 8:53:24 PM9/9/08
to pyglet-users
Schedules still result in inconsistency.

On Sep 9, 10:32 am, "Drew Smathers" <drew.smath...@gmail.com> wrote:

Elmo Trolla

unread,
Sep 9, 2008, 9:09:48 PM9/9/08
to pyglet...@googlegroups.com
> On Tue, Sep 9, 2008 at 1:27 AM, 3TATUK <remember....@gmail.com>


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.

Alex Holkner

unread,
Sep 9, 2008, 11:41:06 PM9/9/08
to pyglet...@googlegroups.com

Hi, thanks for your detailed research. I'll look into this in the
next week and let you know what I find.

Alex.

3TATUK

unread,
Sep 10, 2008, 4:29:46 AM9/10/08
to pyglet-users
hmm... yeah, this looks really good indeed. It's in-depth and
precise.. The only thing I'd like to tag on as a comment to you and
Alex when looking into implementing any of these changes is a way to
make this non-blocking, and render-specific.. meaning... any events
(mouse/keyboard) or other functions scheduled to happen in between the
rendering of two frames should happen exactly then, instead of waiting
for the frame to render. :) really nice work!
> http://etm.blastnet.ee/var/list/opengl_clock_schedule.pyhttp://etm.blastnet.ee/var/list/opengl_clock_schedule_interval.pyhttp://etm.blastnet.ee/var/list/opengl_fps_limit.pyhttp://etm.blastnet.ee/var/list/opengl_custom.py

3TATUK

unread,
Sep 11, 2008, 6:48:49 AM9/11/08
to pyglet-users
Just as a follow-up .. this is what I'm currently using to limit FPS
in my game .. although, note, it -is- `blocking' (events caught in-
between frames don't get processed immediately).. it's basically just
a busy-loop:

def sleeper( duration, dt ):
if dt: duration -= abs( dt - duration )
duration += pyglet.clock.time.clock()
while True:
if pyglet.clock.time.clock() >= duration: return

assuming you have `import pyglet`d .. and that it's called from within
a `schedule[_interval]()`d event (ie, _not_ `on_draw()`), with the
following syntax:

sleeper( 1.0 / FPS, dt )
> >http://etm.blastnet.ee/var/list/opengl_clock_schedule.pyhttp://etm.bl...

Alex Holkner

unread,
Sep 16, 2008, 10:12:29 AM9/16/08
to pyglet...@googlegroups.com
On Wed, Sep 10, 2008 at 12:09 PM, Elmo Trolla <e...@blastnet.ee> wrote:

> 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.

Elmo Trolla

unread,
Sep 16, 2008, 8:37:38 PM9/16/08
to pyglet...@googlegroups.com

On Sep 16, 2008, at 5:12 PM, Alex Holkner wrote:
>> ----
>>
>> 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.

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)

Alex Holkner

unread,
Sep 16, 2008, 9:01:41 PM9/16/08
to pyglet...@googlegroups.com

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.

Elmo Trolla

unread,
Sep 17, 2008, 7:55:24 PM9/17/08
to pyglet...@googlegroups.com

On Sep 17, 2008, at 4:01 AM, Alex Holkner wrote:
>>> 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.

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..

Reply all
Reply to author
Forward
0 new messages