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
> <https://github.com/cprogrammer1994/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.
>     .. seealso:: http://msdn.microsoft.com/en-
> us/library/dd373965(v=vs.85).aspx
>     """
>
>     """
>     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].
> 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