9
\$\begingroup\$

I have a simple OpenGL test app in C which draws different things in response to key input. (Mesa 8.0.4, tried with Mesa-EGL and with GLFW, Ubuntu 12.04LTS on a PC with NVIDIA GTX650). The draws are quite simple/fast (rotating triangle type of stuff). My test code does not limit the framerate deliberately in any way, it just looks like this:

while (true)
{
    draw();
    swap_buffers();
}

I have timed this very carefully, and I find that the time from one eglSwapBuffers() (or glfwSwapBuffers, same thing) call to the next is ~16.6 milliseconds. The time from after a call to eglSwapBuffers() to just before the next call is only a little bit less than that, even though what is drawn is very simple. The time that the swap buffers call takes is well under 1ms.

However, the time from the app changing what it's drawing in response to the key press to the change actually showing up on screen is >150ms (approx 8-9 frames worth). This is measured with a camera recording of the screen and keyboard at 60fps.

Therefore, the questions:

  1. Where are draws buffered between a call to swap buffers and actually showing up on screen? Why the delay? It sure looks like the app is drawing many frames ahead of the screen at all times.

  2. What can an OpenGL application do to cause an immediate draw to screen? (ie: no buffering, just block until draw is complete; I don't need high throughput, I do need low latency)

  3. What can an application do to make the above immediate draw happen as fast as possible?

  4. How can an application know what is actually on screen right now? (Or, how long/how many frames the current buffering delay is?)

\$\endgroup\$
  • \$\begingroup\$ Can you separate the time that the keyboard takes to notify your application to the time it takes to actually render that? \$\endgroup\$ – ThorinII Jan 25 '14 at 9:54
  • \$\begingroup\$ @ThorinII: Fair point. I can probably set up something hacky using a LED on a parallel port etc to get an indication of when the app actually gets the key press. Still, it's just fgetc(stdin), how slow can that be? :/ \$\endgroup\$ – Alex I Jan 25 '14 at 10:00
  • \$\begingroup\$ I was under the impression that glFlush was used to cause an immediate flush of all commands. \$\endgroup\$ – Programmdude Jan 25 '14 at 11:20
  • 1
    \$\begingroup\$ @AlexI check this gamedev.stackexchange.com/questions/66543/… might help you \$\endgroup\$ – concept3d Jan 25 '14 at 12:24
  • 1
    \$\begingroup\$ @concept3d: Yes, it's basically answered, just thought you'd like some extra rep :) Apparently I have to wait a day to award it though. \$\endgroup\$ – Alex I Jan 27 '14 at 11:05
6
+50
\$\begingroup\$

Any drawing API function called from the CPU will be submitted to the GPU command ring buffer to be executed later by the GPU. This means that OpenGL functions are mostly non-blocking functions. So the CPU and the GPU will be working in parallel.

The most important thing to note is that your application can be CPU or GPU bound. once you call glFinish the CPU should will wait the GPU to complete it's drawing commands, if the GPU is taking more time and may/is causing the CPU to stall then your applications is GPU bound. If the GPU finishes it drawing commands and the CPU is taking too long to glFinish then your application is CPU bound.

And note that there is a difference between glFlush and glFinish.

glFlush:indicates that all commands that have previously been sent to the GL must complete in finite time.

glFinish: forces all previous GL commands to complete. Finish does not return until all effects from previously issued commands on GL client and server state and the framebuffer are fully realized."

glXSwapBuffers performs an implicit glFlush before it returns. Subsequent OpenGL commands may be issued immediately after calling glXSwapBuffers, but are not executed until the buffer exchange is completed.

The actual frame time will most likely be determined by which of the two CPU/GPU is taking more time to complete its work.

\$\endgroup\$
  • \$\begingroup\$ This is very helpful. Some possible corrections: opengl.org/sdk/docs/man2/xhtml/glXSwapBuffers.xml "glXSwapBuffers performs an implicit glFlush before it returns" it seems this doesn't actually do a glFinish, so the command buffer can have quite a lot of stuff in it when swap buffers returns. \$\endgroup\$ – Alex I Jan 25 '14 at 13:27
  • \$\begingroup\$ ... Also, I think the most interesting point is that my very simple test application is neither CPU not GPU limited - neither has much work to do here - but something else, it somehow ends up with both low framerate (exactly same as monitor refresh rate??) and high latency (because of the command buffer, actually you explained that part quite well). \$\endgroup\$ – Alex I Jan 25 '14 at 13:32
  • \$\begingroup\$ @AlexI both low framerate (exactly same as monitor refresh rate?? no unless you are explicitly using VSync. \$\endgroup\$ – concept3d Jan 25 '14 at 13:34
  • \$\begingroup\$ @AlexI I also want to point out that frame time is different from FPS, use frame time to profile your application because it's linear. FPS on the other hand is a poor measurement that is not linear. \$\endgroup\$ – concept3d Jan 25 '14 at 13:36
  • \$\begingroup\$ I'm not explicitly using vsync. I am not doing anything to change the defauls vsync settings, I don't even know where to change those in OpenGL. I just get exactly 60fps (to within a percent). \$\endgroup\$ – Alex I Jan 25 '14 at 13:36
4
\$\begingroup\$

OpenGL never updates the screen, technically.

There is a window system API that is separate from GL (e.g. GLX, WGL, CGL, EGL) that does this. Buffer swaps using these APIs generally implicitly invoke glFlush (...) but in some implementations (e.g. the GDI rasterizer on Windows) it does a full glFinish (...):

 

    *On the left side, is the ICD (hardware) path through SwapBuffers (...). On the right side, the GDI (software) path.

If you have VSYNC enabled and double-buffering, then any command that would modify the back buffer before a pending swap occurs must stall until the swap. There is a limited depth to the command queue, so this stalled command will eventually cause a traffic jam in the pipeline. This could mean that instead of blocking on SwapBuffers (...) your application actually blocks on some unrelated GL command until VBLANK rolls around. What it really boils down to is how many back buffers you have in your swap chain.

As long as all of the back buffers are full of finished frames yet to be moved to the front, swap buffers will implicitly cause blocking. Sadly, there is no way to explicitly control the number of back buffers used by most GL window system APIs (aside from 0 single-buffered or 1 double-buffered). The driver is free to use 2 back buffers if it wants (triple buffering), but you cannot request this at the application level using something like GLX or WGL.

\$\endgroup\$
  • \$\begingroup\$ Andon: Good info, thank you. I think what I am seeing is partly double buffering, but: "attempt to modify the back buffer before the swap occurs must block" - why? it sounds like everything can get sent to the command buffer, including the swap :) \$\endgroup\$ – Alex I Jan 25 '14 at 22:59
  • \$\begingroup\$ "explicitly control the number of back buffers used by most GL window system APIs (aside from 0 single-buffered or 1 double-buffered)" - how does one control single/double buffered? \$\endgroup\$ – Alex I Jan 25 '14 at 22:59
  • \$\begingroup\$ @AlexI: I clarified the language with respect to why the command can lead to blocking. In other APIs there is a concept known as render-ahead, which is effectively the depth of the command queue. As for controlling single/double buffered, that is controlled by the pixel format you select when you create your render context. In WGL, you can select single or double-buffered, but if you select double-buffered the driver might actually create 2 back buffers (thus double buffering becomes triple buffering) if the user set their driver to do this. \$\endgroup\$ – Andon M. Coleman Jan 25 '14 at 23:01
  • \$\begingroup\$ You actually do not want to render too many frames ahead because while this will reduce blocking, it also increases latency. The best way to minimize latency is actually to invoke glFinish (...) immediately after swapping buffers. This will clear out the command queue, but it also means that the GPU and CPU will be synchronized (which is not good if you want to keep the GPU doing work at all times). \$\endgroup\$ – Andon M. Coleman Jan 25 '14 at 23:07
1
\$\begingroup\$

I assume you're familiar with this experiment?

Essentially John Carmack was doing something similar, recording the screen and timing pixels sent to the screen. He found that a good deal of the latency came from the screen. Other factors were the input delay from the keyboard, video drivers and or course the execution of the program itself.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.