floooh / sokol

minimal cross-platform standalone C headers
https://floooh.github.io/sokol-html5
zlib License
7.11k stars 501 forks source link

Each frame, the first call to `begin_pass()` stalls for ~16ms #1113

Closed ekliot closed 2 months ago

ekliot commented 2 months ago

for some reason, the first pass I perform every frame stalls for ~16ms. this screws with my frame timing.

subsequent passes before a commit resolve in what looks like a normal amount of time

I think this may be some kind of vsync, maybe? how do I debug this? I’m using Odin bindings, on Linux w/ OpenGL backend, and SDL windowing. not sure if this is a local hardware config or what

Outline of problem:

  1. frame begins
  2. begin_pass() takes 16ms
  3. work happens
  4. end_pass()
  5. begin_pass() basically instant
  6. work happens
  7. end_pass()
  8. commit()
  9. end frame

if I only have one pass per frame, that one pass will stall, otherwise it is always the first pass.

here's an example of what call will stall:

sg.begin_pass(sg.Pass {
    action = sg.Pass_Action {
        colors = ColorActions{
            0 = {
                load_action  = .CLEAR,
                store_action = .DONTCARE,
                clear_value  = color,
            },
        },
    },
    swapchain = swapchain(),
})
floooh commented 2 months ago

That sounds like the vsync throttling happens in sg_begin_frame() which doesn't seem unusual, since at some point the OpenGL driver needs to wait until a free swapchain surface becomes available before it can start rendering into it (e.g. the vsync throttling needs to happen somewhere, and on your config that place seems to an OpenGL call happening inside the first sg_begin_pass().

In the GL backend in sokol_gfx.h there isn't any explicit synchronization code, since that sort of stuff happens implicitly inside the OpenGL implementation, so there's also not much sokol_gfx.h can do about that (you could try to add a glFlush+glFinish into the _sg_gl_commit() function, but I doubt that would help, if anything it might move some of the waiting time into the sg_commit() call instead of the first sg_begin_pass() call, but then that time isn't available for your game logic code which runs before the first sg_begin_pass().

The only advice I can give is to move as much work as possible before the last sg_begin_pass() call in a frame which renders to the swapchain surface (but I don't know if it would make any sense to move expensive rendering into an offscreen pass, hoping that an sg_begin_pass() into an offscreen render target wouldn't need to wait for a swapchain surface to become available, that would put a lot of trust into the OpenGL driver doing the right thing and probably is different between drivers and platforms.

PS: also see: https://github.com/floooh/sokol/issues/867

ekliot commented 2 months ago

thanks @floooh for the quick and detailed response! that other issue is illuminating too.

with your details I looked into SDL2's vsync/swapchain stuff for a GL backend, and found these docs.

for any future troubleshooters, SDL_GL_SetSwapInterval(0) disabled the vsync and I was pumping out frames without bottleneck.

floooh commented 1 month ago

Yeah, there's a couple of open issues about being able to disable vsync in sokol_app.h, but it's not trivial to do across all supported platforms. Also the current sapp_desc.swap_interval isn't very useful (it should instead be a target frame rate).