FWIW my 2cents is that this is a great piece of info to know. Ideally a user starts with good looking and can then tweak settings to optimise for whatever they need to optimise.
So I would suggest we add to the docs this new found info and maybe make it easy (by adding helper functions?) for people to set the depth and stencil buffers to various "legal" bitdepths and see the effect on quality and performance. E.g. As another extreme I might go with a 16bit stencil and no depth buffer and rely on layer order only because my project might be moving large areas of alpha screen around.

E.g. a helper function that specifically reports on those capabilities for their graphics card (assuming they still make cards with reduced capabilities like this)..


On 5/27/2018 7:17 AM, Bruce Smith wrote:
One argument in favor of defaulting to 24 bit depth buffer instead of 16 bit depth buffer is that:

- some people understand all this and can optimize the settings for their application;

- but for people who don't understand it all, or who don't delve into the details, the kind of bugs they'll get from a lower resolution depth buffer (namely, z-fighting or the wrong polygon being in front) might seem mysterious to them (even assuming they learned enough OpenGL to write or use code to generate 3d polygons), and it might never occur to them that they should try increasing their depth buffer resolution -- they might not even know it's possible, or they might assume pyglet already gave them the best (for accuracy, not speed) result it could.

(It's also possible that if they manually increase it, their app will now fail on some platforms, whereas the default code you mentioned automatically downgrades instead of failing -- I don't know.)

In other words, it seems to me like pyglet is now optimizing for "just working out of the box, and giving the most accurate results", rather than "giving the best possible speed"; and it seems to me this is the right choice for pyglet's target audience, which I see as "people who want to learn just enough OpenGL to do something interesting" rather than "people who want to write a high-performance app and don't mind becoming experts in order to do that" (the latter will probably realize they could get better speed by moving to something other than pyglet).

(BTW, I do also appreciate the OP's finding -- it's great to know how to speed things up when you do want to (after getting them working).)

- Bruce Smith

On Sat, May 26, 2018 at 6:54 AM, Benjamin Moran <[email protected]> wrote:
Agreed! Nice find!

The default Window config code can be found in pyglet/window/__init__.py, starting from line 511. It simply tries a few configs, starting with 24bit. Looking at your results, I don't see any reason why we can't switch this around to try a 16bit buffer first. This is only for the default, so anyone with a specific need can easily create their own Config.
We're defaulting to orthographic rendering anyway!

....
if not config:
    for template_config in [
        gl.Config(double_buffer=True, depth_size=24),
        gl.Config(double_buffer=True, depth_size=16),
        None]:
        try:
            config = screen.get_best_config(template_config)
            break
        except NoSuchConfigException:
            pass
    if not config:
        raise NoSuchConfigException('No standard config is available.')
...

On Saturday, May 26, 2018 at 4:34:25 PM UTC+9, Richard Jones wrote:
That is a great finding, thanks for delving into it and sharing!!

On 26 May 2018 at 17:01, Daniel Gillet <[email protected]> wrote:
Hello,

I recently was trying some code and I found something which was both surprising and interesting. I thought I would share my findings with you.

I wanted to play a bit with moderngl, an OpenGL binding. I started initially by using pygame to create the window and handle the events. My goal was just to use OpenGL 3 features to see how I could display lots of moving and rotating sprites on the screen. I also used numpy for storing my arrays and I managed to get 10,000 moving and rotating quads on my screen while barely maintaining 60 FPS.

Then I thought I would try to port that code to pyglet, but still using moderngl. And this is where the situation gets interesting. After finishing the porting, my performances dropped to 30 FPS. The interesting part was that I was not using any OpenGL calls from either pygame (doesn't have any anyways) nor pyglet. So I knew the OpenGL stuff was not responsible for this drop in performances. I started to profile both examples and both showed that the bottleneck was the flip function. This makes sense as this is where all the data get loaded on the graphic card. But pygame flip function was taking something like 15 ms while pyglet flip function was taking twice as much, around 30 ms.

I went down the rabbit hole and I'll spare you with the details of all the things I tried. I'll go straight to my findings. Pygame and Pyglet were not giving me the same OpenGL context. Pygame gave me an OpenGL with a 16 bits depth buffer and no stencil buffer, while Pyglet was giving me a 24 bits depth buffer together with an 8 bits stencil buffer. And obviously swapping extra information has a cost.

I added the following code to my pyglet code:

config = pyglet.gl.Config(double_buffer=True, depth_size=16)
window = pyglet.window.Window(
    width=1366, height=768, resizable=True, vsync=True, config=config
)

And guess what? I had the same performances as with Pygame, reaching barely 60 FPS with 10,000 textured quads moving and rotating on the screen.

In conclusion Pyglet offers by default an OpenGL context with better depth buffer and a stencil buffer. But if you don't use them, they come at a cost. I don't know how many Pyglet app really need the 24 bits depth buffer together with an 8 bits stencil buffer. Just by changing the OpenGL config, you might get a performance boost if you're hitting heavily the graphics card.

For those interested in my little test code, here it is:

import time
import collections

import pyglet

import moderngl
import numpy as np
from vec_utils import create_orthogonal_projection

import cProfile, pstats

pr = cProfile.Profile()
pr.enable()


class FPSCounter:
    def __init__(self):
        self.time = time.perf_counter()
        self.frame_times = collections.deque(maxlen=60)

    def tick(self):
        t1 = time.perf_counter()
        dt = t1 - self.time
        self.time = t1
        self.frame_times.append(dt)

    def get_fps(self):
        return len(self.frame_times) / sum(self.frame_times)


fps_counter = FPSCounter()
config = pyglet.gl.Config(double_buffer=True, depth_size=16)
window = pyglet.window.Window(
    width=1366, height=768, resizable=True, vsync=True, config=config
)

ctx = moderngl.create_context()
ctx.viewport = (0, 0) + window.get_size()
prog = ctx.program(
    vertex_shader='''
        #version 330
        uniform mat4 Projection;

        in vec2 in_vert;
        in vec2 in_texture;

        in vec3 in_pos;
        in float in_angle;
        in vec2 in_scale;

        out vec2 v_texture;

        void main() {
            mat2 rotate = mat2(
                        cos(in_angle), sin(in_angle),
                        -sin(in_angle), cos(in_angle)
                    );
            vec3 pos;
            pos = in_pos + vec3(rotate * (in_vert * in_scale), 0.);
            gl_Position = Projection * vec4(pos, 1.0);
            v_texture = in_texture;
        }
    ''',
    fragment_shader='''
        #version 330
        uniform sampler2D Texture;

        in vec2 v_texture;

        out vec4 f_color;

        void main() {
            vec4 basecolor = texture(Texture, v_texture);

            if (basecolor.a == 0.0){
                discard;
            }
            f_color = basecolor;
        }
    ''',
)
vertices = np.array([
    #  x,    y,   u,   v
    -1.0, -1.0, 0.0, 0.0,
    -1.0,  1.0, 0.0, 1.0,
     1.0, -1.0, 1.0, 0.0,
     1.0,  1.0, 1.0, 1.0,
    ], dtype=np.float32
)

proj = create_orthogonal_projection(
    left=0, right=600, bottom=0, top=400, near=-1000, far=100, dtype=np.float32
)

img = pyglet.image.load('grossinis2.png', file=pyglet.resource.file('grossinis2.png'))
texture = ctx.texture((img.width, img.height), 4, img.get_data("RGBA", img.pitch))
texture.use(0)

INSTANCES = 10_000

# pos_scale = np.array([
#       # pos_x, pos_y,  z, angle,   scale_x,       scale_y
#         100.0, 150.0, 0., 0., rect.width/2, rect.height/2,
#         120.5, 200.0, 10., 0., rect.width/2, rect.height/2,
#     ], dtype=np.float32)

positions = (np.random.rand(INSTANCES, 3) * 1000).astype('f4')
angles = (np.random.rand(INSTANCES, 1) * 2 * np.pi).astype('f4')
sizes = np.tile(np.array([img.width / 2, img.height / 2], dtype=np.float32),
                (INSTANCES, 1)
                )
pos_scale = np.hstack((positions, angles, sizes))
player_pos = pos_scale[0, :]

pos_scale_buf = ctx.buffer(pos_scale.tobytes())


vbo = ctx.buffer(vertices.tobytes())
vao_content = [
    (vbo, '2f 2f', 'in_vert', 'in_texture'),
    (pos_scale_buf, '3f 1f 2f/i', 'in_pos', 'in_angle', 'in_scale')
]
vao = ctx.vertex_array(prog, vao_content)
ctx.enable(moderngl.BLEND)
ctx.enable(moderngl.DEPTH_TEST)


def show_fps(dt):
    print(f"FPS: {fps_counter.get_fps()}")


def update(dt):
    pos_scale[1:, 0] += 0.1
    pos_scale[1:, 3] += 0.01
    pos_scale[1::2, 2] += 0.1


def report(dt):
    pr.disable()
    with open("pyglet_stats.txt", "w") as f:
        sortby = 'tottime'
        ps = pstats.Stats(pr, stream=f).sort_stats(sortby)
        ps.print_stats()
    print("Report written")


pyglet.clock.schedule_once(report, 5)

pyglet.clock.schedule_interval(show_fps, 1)
pyglet.clock.schedule_interval(update, 1 / 60)


@window.event
def on_resize(width, height):
    global proj
    ctx.viewport = (0, 0, width, height)
    proj = create_orthogonal_projection(
        left=0, right=width, bottom=0, top=height, near=-1000, far=100, dtype=np.float32
    )
    return True


@window.event
def on_draw():
    ctx.clear(0.0, 0.0, 0.0, 0.0, depth=1.0)

    prog['Texture'].value = 0
    prog['Projection'].write(proj.tobytes())
    pos_scale_buf.write(pos_scale.tobytes())
    vao.render(moderngl.TRIANGLE_STRIP, instances=INSTANCES)
    pos_scale_buf.orphan()
    fps_counter.tick()


pyglet.app.run()

And the vec_utils.py contains the create_orthogonal_projection function which is a straight copy paste from the Pyrr project.

import numpy as np

def create_orthogonal_projection(
    left,
    right,
    bottom,
    top,
    near,
    far,
    dtype=None
):
    """Creates an orthogonal projection matrix.
    :param float left: The left of the near plane relative to the plane's centre.
    :param float right: The right of the near plane relative to the plane's centre.
    :param float top: The top of the near plane relative to the plane's centre.
    :param float bottom: The bottom of the near plane relative to the plane's centre.
    :param float near: The distance of the near plane from the camera's origin.
        It is recommended that the near plane is set to 1.0 or above to avoid rendering issues
        at close range.
    :param float far: The distance of the far plane from the camera's origin.
    :rtype: numpy.array
    :return: A projection matrix representing the specified orthogonal perspective.
    """

    """
    A 0 0 Tx
    0 B 0 Ty
    0 0 C Tz
    0 0 0 1
    A = 2 / (right - left)
    B = 2 / (top - bottom)
    C = -2 / (far - near)
    Tx = (right + left) / (right - left)
    Ty = (top + bottom) / (top - bottom)
    Tz = (far + near) / (far - near)
    """
    rml = right - left
    tmb = top - bottom
    fmn = far - near

    A = 2. / rml
    B = 2. / tmb
    C = -2. / fmn
    Tx = -(right + left) / rml
    Ty = -(top + bottom) / tmb
    Tz = -(far + near) / fmn

    return np.array((
        ( A, 0., 0., 0.),
        (0.,  B, 0., 0.),
        (0., 0.,  C, 0.),
        (Tx, Ty, Tz, 1.),
    ), dtype=dtype)

Daniel

--
You received this message because you are subscribed to the Google Groups "pyglet-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected]om.
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/pyglet-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "pyglet-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyglet-users+unsubscribe@googlegroups.com.
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/pyglet-users.
For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "pyglet-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/pyglet-users.
For more options, visit https://groups.google.com/d/optout.



--
You received this message because you are subscribed to the Google Groups "pyglet-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [email protected].
To post to this group, send email to [email protected].
Visit this group at https://groups.google.com/group/pyglet-users.
For more options, visit https://groups.google.com/d/optout.

Reply via email to