Avoid waiting on SwapBuffers
I have discovered that SwapBuffers in OpenGL will busy-wait as long as the graphics card isn't done with its rendering or if it's waiting on V-Sync.
This is a problem for me because I don't want to waste 100% of a CPU core while just waiting for the card to be finished. I'm not writing a game, so I can not use the CPU cycles for anything more productive, I just want to yield them to some other process in the operating system.
I've foun开发者_如何学God callback-functions such as glutTimerFunc and glutIdleFunc that could work for me, but I don't want to use glut. Still, glut must in some way use the normal gl functions to do this, right?
Is there any function such as "glReadyToSwap" or similar? In that case I could check that every millisecond or so and determine if I should wait a while longer or do the swap. I could also imagine perhaps skip SwapBuffers and write my own similar function that doesn't busy-wait if someone could point me in the right direction.
SwapBuffers is not busy waiting, it just blocks your thread in the driver context, which makes Windows calculating the CPU usage wrongly: Windows calculates the CPU usage by determining how much CPU time the idle process gets + how much time programs don't spend in driver context. SwapBuffers will block in driver context and your program obviously takes away that CPU time from the idle process. But your CPU is doing literally nothing in the time, the scheduler happily waiting to pass the time to other processes. The idle process OTOH does nothing else than immediately yield its time to the rest of the system, so the scheduler jumps right back into your process, which blocks in the driver what Windows counts as "is clogging CPU". If you'd measure the actual power consumption or heat output, for a simple OpenGL program this will stay rather low.
This irritating behaviour is actually an OpenGL FAQ!
Just create additional threads for parallel data processing. Keep OpenGL in one thread, the data processing in the other. If you want to get down the reported CPU usage, adding a Sleep(0) or Sleep(1) after SwapBuffers will do the trick. The Sleep(1) will make your process spend blocking a little time in user context, so the idle process gets more time, which will even out the numbers. If you don't want to sleep, you may do the following:
const float time_margin = ... // some margin
float display_refresh_period; // something like 1./60. or so.
void render(){
float rendertime_start = get_time();
render_scene();
glFinish();
float rendertime_finish = get_time();
float time_to_finish = rendertime_finish - rendertime_start;
float time_rest = fmod(render_finish - time_margin, display_refresh_period);
sleep(time_rest);
SwapBuffers();
}
In my programs I use this kind of timing but for another reason: I let SwapBuffers block without any helper Sleeps, however I give some other worker threads about that time to do stuff on the GPU through shared context (like updating textures) and I have the garbage collector running. It's not really neccesary to exactly time it, but the worker threads being finished just before SwapBuffers returns allows one to start rendering the next frame almost immediately since most mutexes are already unlocked then.
Though eglSwapBuffers
does not busy wait a legitimate use for a nonblocking eglSwapBuffers
is to have a more responsive GUI thread that can listen to user input or exit signals instead of waiting for OpenGL to finish swapping buffers. I have a solution to half of this problem. First in your main loop you buffer up your OpenGL commands to execute on your swapped out buffer. Then you poll on a sync object to see if your commands have finished executing on your swapped out buffer. Then you can swap buffers if the commands have finished executing. Unfortunately, this solution only asynchronously waits for commands to finish executing on your swapped out buffer and does not asynchronously wait for vsync. Here is the code:
void process_gpu_stuff(struct gpu_context *gpu_context)
{
int errnum = 0;
switch (gpu_context->state) {
case BUFFER_COMMANDS:
glDeleteSync(gpu_context->sync_object);
gpu_context->sync_object = 0;
real_draw(gpu_context);
glFlush();
gpu_context->sync_object = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
if (0 == gpu_context->sync_object) {
errnum = get_gl_error();
break;
}
gpu_context->state = SWAP_BUFFERS;
break;
case SWAP_BUFFERS:
/* Poll to see if the buffer is ready for swapping, if
* it is not in ready we can listen for updates in the
* meanwhile. */
switch (glClientWaitSync(gpu_context->sync_object, 0, 1000U)) {
case GL_ALREADY_SIGNALED:
case GL_CONDITION_SATISFIED:
if (EGL_FALSE == eglSwapBuffers(display, surface)) {
errnum = get_egl_error();
break;
}
gpu_context->state = BUFFER_COMMANDS;
break;
case GL_TIMEOUT_EXPIRED:
/* Do nothing. */
break;
case GL_WAIT_FAILED:
errnum = get_gl_error();
break;
}
break;
}
}
the popular answer here is wrong. windows is not reporting the cpu usage "wrongly", lol. opengl, with vsync on, even while rendering a blank screen is actually burning 100% of 1 thread of your cpu. (you can check your CPU temps)
but the solution is simple. just call DwmFlush();
before or after SwapBuffers
精彩评论