PDA

View Full Version : map OpenGL events to OS waitable primitives



l_belev
07-21-2011, 03:45 PM
The idea mentioned in the specification of GL_ARB_sync, issue 18, behavior B6)

I just want to express big support for this idea and to make some comments/suggestions.

In my view the 2 main benefits of this would be that it will allow us to:
1) use OS-specific multi-condition waiting APIs (select()/poll() on unix and WaitForMultipleObjects() on ms windows) with OpenGL sync objects.
Currently there is no efficient way for a thread to wait until any of several events occur, one of which is opengl sync and
others are e.g. signal from another thread, window-system-specific event (window message on windows and X event on unix), IO events, etc.
2) sync with OpenGL events from within different threads without the need to create multiple shared opengl contexts and even from separate processes.

For unix the GL_ARB_sync spec mentions file descriptors, semaphores and X events. File descriptors can be used with select/poll, so they are great choice.
X events can be "converted" to a file descriptor by using the function ConnectionNumber(); this may be more conceptually appropriate in the sense that OpenGL is more close to the X
than to the unix system api but still i vote for plain file descriptors because X events would have to pass through the X server, which adds unnecessary delay and overhead.
(The point of direct glX context is to avoid the overhead of communicating with the X server as much as possible).
Even on non-direct glX contexts that run on remote machine a file descriptor can still be used - in this case it may be internally a socket.
Or this mechanism can just be unavailable for non-direct contexts. I don't think anyone really cares about those.
Semaphores are not good choice (i assume they were talking about the sysV IPC semaphores) because they can not be used with select/poll.
It is worth to mention that the linux-specific synchronization primitive "futex" can be converted to file descriptor too.

Under windows any object handle that can be used with WaitForMultipleObjects will do; can be windows event or anything else - whatever is most convenient for the implementation.
Is is probably worth to note that if it can be used with WaitForMultipleObjects then it also can be used with MsgWaitForMultipleObjects,
which allows the thread to wait simultaneously for GL syncs and window messages.

I think it would be enough to have only single OS waitable object per opengl context. A thread can use it to wait until anything happens in the context and then use
other means to determine what exactly happened e.g. a function that returns the id of the earliest triggered sync that is not returned by the same function yet:
GLsync glGetNextTriggeredSync(); // return 0 if no sync has been triggered since the last check

The function that gives the OS specific waitable object should probably be located in the window-system APIs - glX, wgl, agl.
int glXGetContextWaitableFileDescriptor(Display *dpy, GLXContext ctx); - for glX
HANDLE wglGetContextWaitableHandle(HGLRC ctx); - for wgl
(add agl variant here - im not familiar with agl)

The application need not close these file descriptors or handles - when the context is destroyed, the implementation will take care of them automatically.
I don't know what should happen if the application still closes them. Maybe this should lead to undefined consequences or maybe the implementation should support it.

l_belev
07-25-2011, 09:26 AM
there is a problem with "GLsync glGetNextTriggeredSync();"
the "next triggered fence" may change after each call to the function and in multi-threaded situation this will lead to race condition.

Instead we can have a function that returns the "last triggered sync" (which does not lead to race condition) and rely on the fact that all syncs of given type are sequential in time. Then we also will need a way to get the order of given 2 syncs.
Also, in order to avoid the need to have current context, these functions may have variants in the window-system APIs and take the context as parameter.

GLsync glGetLastTriggerendSync(GLenum type);
GLsync glXGetLastTriggerendSync(Display *dpy, GLXContext ctx, GLenum type);
GLsync wglGetLastTriggerendSync(HGLRC ctx, GLenum type);

GLenum glGetSyncOrder(GLsync s1, GLsync s2);
GLenum glXGetSyncOrder(Display *dpy, GLXContext ctx, GLsync s1, GLsync s2);
GLenum wglGetSyncOrder(HGLRC ctx, GLsync s1, GLsync s2);

*GetSyncOrder can return GL_LESS, GL_GREATER or GL_EQUAL to indicate sync order in time, regardless of whether any of them is already triggered or not.
If any of the syncs is invalid, GL_NOT_EQUAL is returned and GL_INVALID_VALUE is generated.
If the syncs can not be compared, GL_NOT_EQUAL is returned and GL_INVALID_OPERATION is generated.

Fence syncs can always be compared with other fence syncs.
Other sync types may have different rules about if and when they can be compared (e.g. hypothetical future vertical retrace syncs can not be compared with untriggered fence syncs)

For example, if a thread wants to check if given sync S is triggered or not, it can get the last triggered sync L of the same type and then compare it with S. IF S is less or equal to L, then S is also triggered. Otherwise it is not triggered.

Why not just have a function that tells us if given sync is triggered? Because if we want to check many different syncs we will need to call it many times while the last triggered sync gives the whole information. *GetSyncOrder is only additional helper, and the application can avoid using it if it makes it's own arrangements to remember the sync order.

Then again we already have a function to check if a sync is triggered (glClientWaitSync with timeout 0), so probably *GetSyncOrder is unnecessary
Only we need a explicit-context variant for glClientWaitSync.