diff --git a/.gitignore b/.gitignore index 0496a03c..da61d802 100644 --- a/.gitignore +++ b/.gitignore @@ -6,13 +6,13 @@ cmake_uninstall.cmake .DS_Store docs/Doxyfile src/config.h -src/libglfw.pc -src/libglfw.so -src/libglfw.a -src/libglfw.dylib -src/glfw.lib -src/glfw.dll -src/glfwdll.lib +src/glfw3.pc +src/libglfw3.so +src/libglfw3.a +src/libglfw3.dylib +src/glfw3.lib +src/glfw3.dll +src/glfw3dll.lib examples/boing examples/gears examples/heightmap @@ -22,8 +22,8 @@ examples/wave examples/*.app examples/*.exe tests/accuracy +tests/clipboard tests/defaults -tests/dynamic tests/events tests/fsaa tests/fsfocus @@ -31,11 +31,12 @@ tests/gamma tests/glfwinfo tests/iconify tests/joysticks -tests/listmodes +tests/modes tests/peter tests/reopen tests/sharing tests/tearing +tests/threads tests/title tests/windows tests/*.app diff --git a/CMakeLists.txt b/CMakeLists.txt index d37146ae..8d68b4c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,12 @@ else() find_package(OpenGL REQUIRED) endif() +set(CMAKE_THREAD_PREFER_PTHREADS YES) +find_package(Threads) +if (CMAKE_THREAD_LIBS_INIT) + list(APPEND glfw_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) +endif() + #-------------------------------------------------------------------- # Enable all warnings on GCC, regardless of OS #-------------------------------------------------------------------- diff --git a/readme.html b/readme.html index e577891b..0de14bd4 100644 --- a/readme.html +++ b/readme.html @@ -287,6 +287,7 @@ version of GLFW.

  • Added windows simple multi-window test program
  • Added sharing simple OpenGL object sharing test program
  • Added modes video mode enumeration and setting test program
  • +
  • Added threads simple multi-threaded rendering test program
  • Added glfw3native.h header and platform-specific functions for explicit access to native display, window and context handles
  • Added glfwSetGamma, glfwSetGammaRamp and glfwGetGammaRamp functions and GLFWgammaramp type for monitor gamma ramp control
  • Added window parameter to glfwSwapBuffers
  • @@ -349,6 +350,7 @@ version of GLFW.

  • [X11] Bugfix: Some window properties required by the ICCCM were not set
  • [X11] Bugfix: Calling glXCreateContextAttribsARB with an unavailable OpenGL version caused the application to terminate with a BadMatch Xlib error
  • [X11] Bugfix: A synchronization point necessary for jitter-free locked cursor mode was incorrectly removed
  • +
  • [X11] Bugfix: The window size hints were not updated when calling glfwSetWindowSize on a non-resizable window
  • [Win32] Changed port to use Unicode mode only
  • [Win32] Removed explicit support for versions of Windows older than Windows XP
  • [Win32] Bugfix: Window activation and iconification did not work as expected
  • @@ -907,7 +909,7 @@ their skills. Special thanks go out to:

    language
  • Shane Liesegang, for providing a bug fix relating to Cocoa window - restoration
  • + restoration and reporting a bug on 32-bit Cocoa builds
  • Tristam MacDonald, for his bug reports and feedback on the Cocoa port
  • @@ -924,9 +926,14 @@ their skills. Special thanks go out to:

    Much of the Windows code of GLFW was originally based on Jeff's code +
  • Julian Møller, for reporting a bug in the Cocoa joystick code
  • +
  • Arturo J. Pérez, for a bug fix for cursor tracking on Mac OS X 10.6 Snow Leopard
  • +
  • Riku Salminen, for making the X11 event processing able to support + multi-threaded rendering
  • +
  • Douglas C. Schmidt and Irfan Pyarali, for their excellent article Strategies for Implementing POSIX Condition Variables on Win32
  • diff --git a/src/cocoa_init.m b/src/cocoa_init.m index 7c208b90..3bb61c18 100644 --- a/src/cocoa_init.m +++ b/src/cocoa_init.m @@ -103,6 +103,9 @@ int _glfwPlatformInit(void) _glfwInitJoysticks(); + if (!_glfwInitOpenGL()) + return GL_FALSE; + _glfwLibrary.NS.eventSource = CGEventSourceCreate(kCGEventSourceStateHIDSystemState); if (!_glfwLibrary.NS.eventSource) return GL_FALSE; @@ -143,6 +146,8 @@ int _glfwPlatformTerminate(void) _glfwTerminateJoysticks(); + _glfwTerminateOpenGL(); + return GL_TRUE; } diff --git a/src/cocoa_joystick.m b/src/cocoa_joystick.m index 7eac7f91..04c9e972 100644 --- a/src/cocoa_joystick.m +++ b/src/cocoa_joystick.m @@ -550,15 +550,15 @@ int _glfwPlatformGetJoystickAxes(int joy, float* axes, int numaxes) for (i = 0; i < numaxes; i++) { - _glfwJoystickElement* axes = + _glfwJoystickElement* elements = (_glfwJoystickElement*) CFArrayGetValueAtIndex(joystick.axes, i); - long readScale = axes->maxReport - axes->minReport; + long readScale = elements->maxReport - elements->minReport; if (readScale == 0) - axes[i] = axes->value; + axes[i] = elements->value; else - axes[i] = (2.0f * (axes->value - axes->minReport) / readScale) - 1.0f; + axes[i] = (2.0f * (elements->value - elements->minReport) / readScale) - 1.0f; if (i & 1) axes[i] = -axes[i]; diff --git a/src/cocoa_opengl.m b/src/cocoa_opengl.m index 2ffd774c..4d4c4299 100644 --- a/src/cocoa_opengl.m +++ b/src/cocoa_opengl.m @@ -29,11 +29,46 @@ #include "internal.h" +#include + + +//======================================================================== +// The per-thread current context/window pointer +//======================================================================== +static pthread_key_t _glfwCurrentTLS; + ////////////////////////////////////////////////////////////////////////// ////// GLFW platform API ////// ////////////////////////////////////////////////////////////////////////// +//======================================================================== +// Initialize OpenGL support +//======================================================================== + +int _glfwInitOpenGL(void) +{ + if (pthread_key_create(&_glfwCurrentTLS, NULL) != 0) + { + _glfwSetError(GLFW_PLATFORM_ERROR, + "Cocoa/NSGL: Failed to create context TLS"); + return GL_FALSE; + } + + return GL_TRUE; +} + + +//======================================================================== +// Terminate OpenGL support +//======================================================================== + +void _glfwTerminateOpenGL(void) +{ + pthread_key_delete(_glfwCurrentTLS); +} + + //======================================================================== // Make the OpenGL context associated with the specified window current //======================================================================== @@ -44,6 +79,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window) [window->NSGL.context makeCurrentContext]; else [NSOpenGLContext clearCurrentContext]; + + pthread_setspecific(_glfwCurrentTLS, window); +} + + +//======================================================================== +// Return the window object whose context is current +//======================================================================== + +_GLFWwindow* _glfwPlatformGetCurrentContext(void) +{ + return (_GLFWwindow*) pthread_getspecific(_glfwCurrentTLS); } @@ -64,7 +111,7 @@ void _glfwPlatformSwapBuffers(_GLFWwindow* window) void _glfwPlatformSwapInterval(int interval) { - _GLFWwindow* window = _glfwLibrary.currentWindow; + _GLFWwindow* window = _glfwPlatformGetCurrentContext(); GLint sync = interval; [window->NSGL.context setValues:&sync forParameter:NSOpenGLCPSwapInterval]; diff --git a/src/cocoa_platform.h b/src/cocoa_platform.h index 026d40ad..06055a25 100644 --- a/src/cocoa_platform.h +++ b/src/cocoa_platform.h @@ -123,4 +123,8 @@ void _glfwTerminateJoysticks(void); GLboolean _glfwSetVideoMode(int* width, int* height, int* bpp, int* refreshRate); void _glfwRestoreVideoMode(void); +// OpenGL support +int _glfwInitOpenGL(void); +void _glfwTerminateOpenGL(void); + #endif // _platform_h_ diff --git a/src/cocoa_window.m b/src/cocoa_window.m index 6b192f09..337dace8 100644 --- a/src/cocoa_window.m +++ b/src/cocoa_window.m @@ -686,7 +686,7 @@ static GLboolean createWindow(_GLFWwindow* window, [window->NS.object setAcceptsMouseMovedEvents:YES]; [window->NS.object center]; - if ([window->NS.object respondsToSelector:@selector(setRestorable)]) + if ([window->NS.object respondsToSelector:@selector(setRestorable:)]) [window->NS.object setRestorable:NO]; return GL_TRUE; @@ -1100,7 +1100,7 @@ void _glfwPlatformSetCursorPos(_GLFWwindow* window, int x, int y) CGPoint mainScreenOrigin = CGDisplayBounds(CGMainDisplayID()).origin; double mainScreenHeight = CGDisplayBounds(CGMainDisplayID()).size.height; CGPoint targetPoint = CGPointMake(globalPoint.x - mainScreenOrigin.x, - mainScreenHeight - globalPoint.y - + mainScreenHeight - globalPoint.y - mainScreenOrigin.y); CGDisplayMoveCursorToPoint(CGMainDisplayID(), targetPoint); } diff --git a/src/egl_opengl.c b/src/egl_opengl.c index 0b0033e3..bc7c0b43 100644 --- a/src/egl_opengl.c +++ b/src/egl_opengl.c @@ -34,6 +34,24 @@ #include +//======================================================================== +// Thread local storage attribute macro +//======================================================================== +#if defined(_MSC_VER) + #define _GLFW_TLS __declspec(thread) +#elif defined(__GNUC__) + #define _GLFW_TLS __thread +#else + #define _GLFW_TLS +#endif + + +//======================================================================== +// The per-thread current context/window pointer +//======================================================================== +static _GLFW_TLS _GLFWwindow* _glfwCurrentWindow = NULL; + + //======================================================================== // Returns the specified attribute of the specified EGLConfig //======================================================================== @@ -508,6 +526,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window) eglMakeCurrent(_glfwLibrary.EGL.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } + + _glfwCurrentWindow = window; +} + + +//======================================================================== +// Return the window object whose context is current +//======================================================================== + +_GLFWwindow* _glfwPlatformGetCurrentContext(void) +{ + return _glfwCurrentWindow; } diff --git a/src/fullscreen.c b/src/fullscreen.c index f632749f..4372d153 100644 --- a/src/fullscreen.c +++ b/src/fullscreen.c @@ -32,10 +32,8 @@ #include "internal.h" #include -#ifdef __APPLE__ -#include -#else -#include +#if _WIN32 + #include #endif diff --git a/src/glx_opengl.c b/src/glx_opengl.c index c13cac60..bf93f878 100644 --- a/src/glx_opengl.c +++ b/src/glx_opengl.c @@ -38,6 +38,22 @@ void (*glXGetProcAddressEXT(const GLubyte* procName))(); +//======================================================================== +// Thread local storage attribute macro +//======================================================================== +#if defined(__GNUC__) + #define _GLFW_TLS __thread +#else + #define _GLFW_TLS +#endif + + +//======================================================================== +// The per-thread current context/window pointer +//======================================================================== +static _GLFW_TLS _GLFWwindow* _glfwCurrentWindow = NULL; + + //======================================================================== // Returns the specified attribute of the specified GLXFBConfig // NOTE: Do not call this unless we have found GLX 1.3+ or GLX_SGIX_fbconfig @@ -611,6 +627,10 @@ void _glfwDestroyContext(_GLFWwindow* window) } +////////////////////////////////////////////////////////////////////////// +////// GLFW platform API ////// +////////////////////////////////////////////////////////////////////////// + //======================================================================== // Make the OpenGL context associated with the specified window current //======================================================================== @@ -625,6 +645,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window) } else glXMakeCurrent(_glfwLibrary.X11.display, None, NULL); + + _glfwCurrentWindow = window; +} + + +//======================================================================== +// Return the window object whose context is current +//======================================================================== + +_GLFWwindow* _glfwPlatformGetCurrentContext(void) +{ + return _glfwCurrentWindow; } @@ -644,7 +676,7 @@ void _glfwPlatformSwapBuffers(_GLFWwindow* window) void _glfwPlatformSwapInterval(int interval) { - _GLFWwindow* window = _glfwLibrary.currentWindow; + _GLFWwindow* window = _glfwCurrentWindow; if (_glfwLibrary.GLX.EXT_swap_control) { diff --git a/src/internal.h b/src/internal.h index 99781fbd..75aa4eb9 100644 --- a/src/internal.h +++ b/src/internal.h @@ -208,7 +208,6 @@ struct _GLFWlibrary _GLFWhints hints; _GLFWwindow* windowListHead; - _GLFWwindow* currentWindow; _GLFWwindow* activeWindow; GLFWwindowsizefun windowSizeCallback; @@ -298,6 +297,7 @@ void _glfwPlatformWaitEvents(void); // OpenGL context management void _glfwPlatformMakeContextCurrent(_GLFWwindow* window); +_GLFWwindow* _glfwPlatformGetCurrentContext(void); void _glfwPlatformSwapBuffers(_GLFWwindow* window); void _glfwPlatformSwapInterval(int interval); void _glfwPlatformRefreshWindowParams(_GLFWwindow* window); diff --git a/src/opengl.c b/src/opengl.c index 825ccaf6..722767f8 100644 --- a/src/opengl.c +++ b/src/opengl.c @@ -392,7 +392,7 @@ GLboolean _glfwIsValidContextConfig(_GLFWwndconfig* wndconfig) GLboolean _glfwRefreshContextParams(void) { - _GLFWwindow* window = _glfwLibrary.currentWindow; + _GLFWwindow* window = _glfwPlatformGetCurrentContext(); if (!parseGLVersion(&window->clientAPI, &window->glMajor, @@ -460,7 +460,7 @@ GLboolean _glfwRefreshContextParams(void) GLboolean _glfwIsValidContext(_GLFWwndconfig* wndconfig) { - _GLFWwindow* window = _glfwLibrary.currentWindow; + _GLFWwindow* window = _glfwPlatformGetCurrentContext(); if (window->glMajor < wndconfig->glMajor || (window->glMajor == wndconfig->glMajor && @@ -535,11 +535,10 @@ GLFWAPI void glfwMakeContextCurrent(GLFWwindow handle) return; } - if (_glfwLibrary.currentWindow == window) + if (_glfwPlatformGetCurrentContext() == window) return; _glfwPlatformMakeContextCurrent(window); - _glfwLibrary.currentWindow = window; } @@ -555,7 +554,7 @@ GLFWAPI GLFWwindow glfwGetCurrentContext(void) return NULL; } - return _glfwLibrary.currentWindow; + return _glfwPlatformGetCurrentContext(); } @@ -589,7 +588,7 @@ GLFWAPI void glfwSwapInterval(int interval) return; } - if (!_glfwLibrary.currentWindow) + if (!_glfwPlatformGetCurrentContext()) { _glfwSetError(GLFW_NO_CURRENT_CONTEXT, NULL); return; @@ -614,7 +613,7 @@ GLFWAPI int glfwExtensionSupported(const char* extension) return GL_FALSE; } - window = _glfwLibrary.currentWindow; + window = _glfwPlatformGetCurrentContext(); if (!window) { _glfwSetError(GLFW_NO_CURRENT_CONTEXT, NULL); @@ -675,7 +674,7 @@ GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname) return NULL; } - if (!_glfwLibrary.currentWindow) + if (!_glfwPlatformGetCurrentContext()) { _glfwSetError(GLFW_NO_CURRENT_CONTEXT, NULL); return NULL; @@ -703,7 +702,7 @@ GLFWAPI void glfwCopyContext(GLFWwindow hsrc, GLFWwindow hdst, unsigned long mas src = (_GLFWwindow*) hsrc; dst = (_GLFWwindow*) hdst; - if (_glfwLibrary.currentWindow == dst) + if (_glfwPlatformGetCurrentContext() == dst) { _glfwSetError(GLFW_INVALID_VALUE, "glfwCopyContext: Cannot copy OpenGL state to a current context"); diff --git a/src/win32_opengl.c b/src/win32_opengl.c index fcca019f..0bcfe452 100644 --- a/src/win32_opengl.c +++ b/src/win32_opengl.c @@ -34,6 +34,24 @@ #include +//======================================================================== +// Thread local storage attribute macro +//======================================================================== +#if defined(_MSC_VER) + #define _GLFW_TLS __declspec(thread) +#elif defined(__GNUC__) + #define _GLFW_TLS __thread +#else + #define _GLFW_TLS +#endif + + +//======================================================================== +// The per-thread current context/window pointer +//======================================================================== +static _GLFW_TLS _GLFWwindow* _glfwCurrentWindow = NULL; + + //======================================================================== // Initialize WGL-specific extensions // This function is called once before initial context creation, i.e. before @@ -447,7 +465,7 @@ static GLboolean createContext(_GLFWwindow* window, } } - glfwMakeContextCurrent(window); + _glfwPlatformMakeContextCurrent(window); initWGLExtensions(window); return GL_TRUE; @@ -512,8 +530,8 @@ void _glfwDestroyContext(_GLFWwindow* window) { // This is duplicated from glfwDestroyWindow // TODO: Stop duplicating code - if (window == _glfwLibrary.currentWindow) - glfwMakeContextCurrent(NULL); + if (window == _glfwCurrentWindow) + _glfwPlatformMakeContextCurrent(NULL); if (window->WGL.context) { @@ -543,6 +561,18 @@ void _glfwPlatformMakeContextCurrent(_GLFWwindow* window) wglMakeCurrent(window->WGL.DC, window->WGL.context); else wglMakeCurrent(NULL, NULL); + + _glfwCurrentWindow = window; +} + + +//======================================================================== +// Return the window object whose context is current +//======================================================================== + +_GLFWwindow* _glfwPlatformGetCurrentContext(void) +{ + return _glfwCurrentWindow; } @@ -562,7 +592,7 @@ void _glfwPlatformSwapBuffers(_GLFWwindow* window) void _glfwPlatformSwapInterval(int interval) { - _GLFWwindow* window = _glfwLibrary.currentWindow; + _GLFWwindow* window = _glfwCurrentWindow; if (window->WGL.EXT_swap_control) window->WGL.SwapIntervalEXT(interval); @@ -577,7 +607,7 @@ int _glfwPlatformExtensionSupported(const char* extension) { const GLubyte* extensions; - _GLFWwindow* window = _glfwLibrary.currentWindow; + _GLFWwindow* window = _glfwCurrentWindow; if (window->WGL.GetExtensionsStringEXT != NULL) { diff --git a/src/window.c b/src/window.c index aea54603..ba592fc1 100644 --- a/src/window.c +++ b/src/window.c @@ -33,10 +33,8 @@ #include #include -#ifdef __APPLE__ -#include -#else -#include +#if _WIN32 + #include #endif @@ -468,8 +466,9 @@ GLFWAPI void glfwDestroyWindow(GLFWwindow handle) return; // Clear the current context if this window's context is current - if (window == _glfwLibrary.currentWindow) - glfwMakeContextCurrent(NULL); + // TODO: Re-examine this in light of multithreading + if (window == _glfwPlatformGetCurrentContext()) + _glfwPlatformMakeContextCurrent(NULL); // Clear the active window pointer if this is the active window if (window == _glfwLibrary.activeWindow) diff --git a/src/x11_init.c b/src/x11_init.c index 75aa6c59..8399a81a 100644 --- a/src/x11_init.c +++ b/src/x11_init.c @@ -634,6 +634,8 @@ static void terminateDisplay(void) int _glfwPlatformInit(void) { + XInitThreads(); + if (!initDisplay()) return GL_FALSE; @@ -646,7 +648,8 @@ int _glfwPlatformInit(void) _glfwLibrary.X11.cursor = createNULLCursor(); - _glfwInitJoysticks(); + if (!_glfwInitJoysticks()) + return GL_FALSE; // Start the timer _glfwInitTimer(); diff --git a/src/x11_joystick.c b/src/x11_joystick.c index a5700c68..c1fb62be 100644 --- a/src/x11_joystick.c +++ b/src/x11_joystick.c @@ -37,7 +37,8 @@ #include #include #include - +#include +#include #include #include #endif // _GLFW_USE_LINUX_JOYSTICKS @@ -53,7 +54,7 @@ static int openJoystickDevice(int joy, const char* path) char numAxes, numButtons; int fd, version; - fd = open(path, O_NONBLOCK); + fd = open(path, O_RDONLY | O_NONBLOCK); if (fd == -1) return GL_FALSE; @@ -127,7 +128,7 @@ static void pollJoystickEvents(void) if (errno == ENODEV) _glfwLibrary.X11.joystick[i].present = GL_FALSE; - if (result < sizeof(e)) + if (result == -1) break; // We don't care if it's an init event or not @@ -171,30 +172,52 @@ static void pollJoystickEvents(void) // Initialize joystick interface //======================================================================== -void _glfwInitJoysticks(void) +int _glfwInitJoysticks(void) { #ifdef _GLFW_USE_LINUX_JOYSTICKS - int i, j, joy = 0; - char path[20]; - const char* bases[] = + int i, joy = 0; + regex_t regex; + DIR* dir; + const char* dirs[] = { - "/dev/input/js", - "/dev/js" + "/dev/input", + "/dev" }; - for (i = 0; i < sizeof(bases) / sizeof(bases[0]); i++) + if (regcomp(®ex, "^js[0-9]\\+$", 0) != 0) { - for (j = 0; j < 50; j++) - { - if (joy > GLFW_JOYSTICK_LAST) - break; + _glfwSetError(GLFW_PLATFORM_ERROR, "X11: Failed to compile regex"); + return GL_FALSE; + } - sprintf(path, "%s%i", bases[i], j); + for (i = 0; i < sizeof(dirs) / sizeof(dirs[0]); i++) + { + struct dirent* entry; + + dir = opendir(dirs[i]); + if (!dir) + continue; + + while ((entry = readdir(dir))) + { + char path[20]; + regmatch_t match; + + if (regexec(®ex, entry->d_name, 1, &match, 0) != 0) + continue; + + snprintf(path, sizeof(path), "%s/%s", dirs[i], entry->d_name); if (openJoystickDevice(joy, path)) joy++; } + + closedir(dir); } + + regfree(®ex); #endif // _GLFW_USE_LINUX_JOYSTICKS + + return GL_TRUE; } diff --git a/src/x11_platform.h b/src/x11_platform.h index ff21eaf5..3bc06a23 100644 --- a/src/x11_platform.h +++ b/src/x11_platform.h @@ -235,7 +235,7 @@ void _glfwSetVideoMode(int* width, int* height, int* rate); void _glfwRestoreVideoMode(void); // Joystick input -void _glfwInitJoysticks(void); +int _glfwInitJoysticks(void); void _glfwTerminateJoysticks(void); // Unicode support diff --git a/src/x11_window.c b/src/x11_window.c index f5573084..7b66b007 100644 --- a/src/x11_window.c +++ b/src/x11_window.c @@ -30,6 +30,8 @@ #include "internal.h" +#include + #include #include #include @@ -460,34 +462,31 @@ static _GLFWwindow* findWindow(Window handle) //======================================================================== -// Get and process next X event (called by _glfwPlatformPollEvents) +// Process the specified X event //======================================================================== -static void processSingleEvent(void) +static void processEvent(XEvent *event) { _GLFWwindow* window; - XEvent event; - XNextEvent(_glfwLibrary.X11.display, &event); - - switch (event.type) + switch (event->type) { case KeyPress: { // A keyboard key was pressed - window = findWindow(event.xkey.window); + window = findWindow(event->xkey.window); if (window == NULL) return; - _glfwInputKey(window, translateKey(event.xkey.keycode), GLFW_PRESS); - _glfwInputChar(window, translateChar(&event.xkey)); + _glfwInputKey(window, translateKey(event->xkey.keycode), GLFW_PRESS); + _glfwInputChar(window, translateChar(&event->xkey)); break; } case KeyRelease: { // A keyboard key was released - window = findWindow(event.xkey.window); + window = findWindow(event->xkey.window); if (window == NULL) return; @@ -501,15 +500,15 @@ static void processSingleEvent(void) XPeekEvent(_glfwLibrary.X11.display, &nextEvent); if (nextEvent.type == KeyPress && - nextEvent.xkey.window == event.xkey.window && - nextEvent.xkey.keycode == event.xkey.keycode) + nextEvent.xkey.window == event->xkey.window && + nextEvent.xkey.keycode == event->xkey.keycode) { // This last check is a hack to work around key repeats // leaking through due to some sort of time drift // Toshiyuki Takahashi can press a button 16 times per // second so it's fairly safe to assume that no human is // pressing the key 50 times per second (value is ms) - if ((nextEvent.xkey.time - event.xkey.time) < 20) + if ((nextEvent.xkey.time - event->xkey.time) < 20) { // Do not report anything for this event break; @@ -517,34 +516,34 @@ static void processSingleEvent(void) } } - _glfwInputKey(window, translateKey(event.xkey.keycode), GLFW_RELEASE); + _glfwInputKey(window, translateKey(event->xkey.keycode), GLFW_RELEASE); break; } case ButtonPress: { // A mouse button was pressed or a scrolling event occurred - window = findWindow(event.xbutton.window); + window = findWindow(event->xbutton.window); if (window == NULL) return; - if (event.xbutton.button == Button1) + if (event->xbutton.button == Button1) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_PRESS); - else if (event.xbutton.button == Button2) + else if (event->xbutton.button == Button2) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_PRESS); - else if (event.xbutton.button == Button3) + else if (event->xbutton.button == Button3) _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, GLFW_PRESS); // XFree86 3.3.2 and later translates mouse wheel up/down into // mouse button 4 & 5 presses - else if (event.xbutton.button == Button4) + else if (event->xbutton.button == Button4) _glfwInputScroll(window, 0.0, 1.0); - else if (event.xbutton.button == Button5) + else if (event->xbutton.button == Button5) _glfwInputScroll(window, 0.0, -1.0); - else if (event.xbutton.button == Button6) + else if (event->xbutton.button == Button6) _glfwInputScroll(window, -1.0, 0.0); - else if (event.xbutton.button == Button7) + else if (event->xbutton.button == Button7) _glfwInputScroll(window, 1.0, 0.0); break; @@ -553,23 +552,23 @@ static void processSingleEvent(void) case ButtonRelease: { // A mouse button was released - window = findWindow(event.xbutton.window); + window = findWindow(event->xbutton.window); if (window == NULL) return; - if (event.xbutton.button == Button1) + if (event->xbutton.button == Button1) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE); } - else if (event.xbutton.button == Button2) + else if (event->xbutton.button == Button2) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_MIDDLE, GLFW_RELEASE); } - else if (event.xbutton.button == Button3) + else if (event->xbutton.button == Button3) { _glfwInputMouseClick(window, GLFW_MOUSE_BUTTON_RIGHT, @@ -581,7 +580,7 @@ static void processSingleEvent(void) case EnterNotify: { // The cursor entered the window - window = findWindow(event.xcrossing.window); + window = findWindow(event->xcrossing.window); if (window == NULL) return; @@ -595,7 +594,7 @@ static void processSingleEvent(void) case LeaveNotify: { // The cursor left the window - window = findWindow(event.xcrossing.window); + window = findWindow(event->xcrossing.window); if (window == NULL) return; @@ -609,12 +608,12 @@ static void processSingleEvent(void) case MotionNotify: { // The cursor was moved - window = findWindow(event.xmotion.window); + window = findWindow(event->xmotion.window); if (window == NULL) return; - if (event.xmotion.x != window->X11.cursorPosX || - event.xmotion.y != window->X11.cursorPosY) + if (event->xmotion.x != window->X11.cursorPosX || + event->xmotion.y != window->X11.cursorPosY) { // The cursor was moved by something other than GLFW @@ -625,17 +624,17 @@ static void processSingleEvent(void) if (_glfwLibrary.activeWindow != window) break; - x = event.xmotion.x - window->X11.cursorPosX; - y = event.xmotion.y - window->X11.cursorPosY; + x = event->xmotion.x - window->X11.cursorPosX; + y = event->xmotion.y - window->X11.cursorPosY; } else { - x = event.xmotion.x; - y = event.xmotion.y; + x = event->xmotion.x; + y = event->xmotion.y; } - window->X11.cursorPosX = event.xmotion.x; - window->X11.cursorPosY = event.xmotion.y; + window->X11.cursorPosX = event->xmotion.x; + window->X11.cursorPosY = event->xmotion.y; window->X11.cursorCentered = GL_FALSE; _glfwInputCursorMotion(window, x, y); @@ -647,17 +646,17 @@ static void processSingleEvent(void) case ConfigureNotify: { // The window configuration changed somehow - window = findWindow(event.xconfigure.window); + window = findWindow(event->xconfigure.window); if (window == NULL) return; _glfwInputWindowSize(window, - event.xconfigure.width, - event.xconfigure.height); + event->xconfigure.width, + event->xconfigure.height); _glfwInputWindowPos(window, - event.xconfigure.x, - event.xconfigure.y); + event->xconfigure.x, + event->xconfigure.y); break; } @@ -665,11 +664,11 @@ static void processSingleEvent(void) case ClientMessage: { // Custom client message, probably from the window manager - window = findWindow(event.xclient.window); + window = findWindow(event->xclient.window); if (window == NULL) return; - if ((Atom) event.xclient.data.l[0] == _glfwLibrary.X11.wmDeleteWindow) + if ((Atom) event->xclient.data.l[0] == _glfwLibrary.X11.wmDeleteWindow) { // The window manager was asked to close the window, for example by // the user pressing a 'close' window decoration button @@ -677,17 +676,17 @@ static void processSingleEvent(void) _glfwInputWindowCloseRequest(window); } else if (_glfwLibrary.X11.wmPing != None && - (Atom) event.xclient.data.l[0] == _glfwLibrary.X11.wmPing) + (Atom) event->xclient.data.l[0] == _glfwLibrary.X11.wmPing) { // The window manager is pinging the application to ensure it's // still responding to events - event.xclient.window = _glfwLibrary.X11.root; + event->xclient.window = _glfwLibrary.X11.root; XSendEvent(_glfwLibrary.X11.display, - event.xclient.window, + event->xclient.window, False, SubstructureNotifyMask | SubstructureRedirectMask, - &event); + event); } break; @@ -696,7 +695,7 @@ static void processSingleEvent(void) case MapNotify: { // The window was mapped - window = findWindow(event.xmap.window); + window = findWindow(event->xmap.window); if (window == NULL) return; @@ -707,7 +706,7 @@ static void processSingleEvent(void) case UnmapNotify: { // The window was unmapped - window = findWindow(event.xmap.window); + window = findWindow(event->xmap.window); if (window == NULL) return; @@ -718,7 +717,7 @@ static void processSingleEvent(void) case FocusIn: { // The window gained focus - window = findWindow(event.xfocus.window); + window = findWindow(event->xfocus.window); if (window == NULL) return; @@ -733,7 +732,7 @@ static void processSingleEvent(void) case FocusOut: { // The window lost focus - window = findWindow(event.xfocus.window); + window = findWindow(event->xfocus.window); if (window == NULL) return; @@ -748,7 +747,7 @@ static void processSingleEvent(void) case Expose: { // The window's contents was damaged - window = findWindow(event.xexpose.window); + window = findWindow(event->xexpose.window); if (window == NULL) return; @@ -769,7 +768,7 @@ static void processSingleEvent(void) { // The selection conversion status is available - XSelectionEvent* request = &event.xselection; + XSelectionEvent* request = &event->xselection; if (_glfwReadSelection(request)) _glfwLibrary.X11.selection.status = _GLFW_CONVERSION_SUCCEEDED; @@ -783,7 +782,7 @@ static void processSingleEvent(void) { // The contents of the selection was requested - XSelectionRequestEvent* request = &event.xselectionrequest; + XSelectionRequestEvent* request = &event->xselectionrequest; XEvent response; memset(&response, 0, sizeof(response)); @@ -808,11 +807,11 @@ static void processSingleEvent(void) default: { #if defined(_GLFW_HAS_XRANDR) - switch (event.type - _glfwLibrary.X11.RandR.eventBase) + switch (event->type - _glfwLibrary.X11.RandR.eventBase) { case RRScreenChangeNotify: { - XRRUpdateConfiguration(&event); + XRRUpdateConfiguration(event); break; } } @@ -957,7 +956,6 @@ void _glfwPlatformSetWindowTitle(_GLFWwindow* window, const char* title) void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { int mode = 0, rate, sizeChanged = GL_FALSE; - XSizeHints* sizehints; rate = window->refreshRate; @@ -971,14 +969,14 @@ void _glfwPlatformSetWindowSize(_GLFWwindow* window, int width, int height) { // Update window size restrictions to match new window size - sizehints = XAllocSizeHints(); - sizehints->flags = 0; + XSizeHints* hints = XAllocSizeHints(); - sizehints->min_width = sizehints->max_width = width; - sizehints->min_height = sizehints->max_height = height; + hints->flags |= (PMinSize | PMaxSize); + hints->min_width = hints->max_width = width; + hints->min_height = hints->max_height = height; - XSetWMNormalHints(_glfwLibrary.X11.display, window->X11.handle, sizehints); - XFree(sizehints); + XSetWMNormalHints(_glfwLibrary.X11.display, window->X11.handle, hints); + XFree(hints); } // Change window size before changing fullscreen mode? @@ -1093,13 +1091,18 @@ void _glfwPlatformRefreshWindowParams(_GLFWwindow* window) void _glfwPlatformPollEvents(void) { + XEvent event; + + while (XCheckMaskEvent(_glfwLibrary.X11.display, ~0, &event) || + XCheckTypedEvent(_glfwLibrary.X11.display, ClientMessage, &event)) + { + processEvent(&event); + } + + // Check whether the cursor has moved inside an active window that has + // captured the cursor (because then it needs to be re-centered) + _GLFWwindow* window; - - // Process all pending events - while (XPending(_glfwLibrary.X11.display)) - processSingleEvent(); - - // Did the cursor move in an active window that has captured the cursor window = _glfwLibrary.activeWindow; if (window) { @@ -1126,13 +1129,18 @@ void _glfwPlatformPollEvents(void) void _glfwPlatformWaitEvents(void) { - XEvent event; + int fd; + fd_set fds; - // Block waiting for an event to arrive - XNextEvent(_glfwLibrary.X11.display, &event); - XPutBackEvent(_glfwLibrary.X11.display, &event); + fd = ConnectionNumber(_glfwLibrary.X11.display); - _glfwPlatformPollEvents(); + FD_ZERO(&fds); + FD_SET(fd, &fds); + + XFlush(_glfwLibrary.X11.display); + + if (select(fd + 1, &fds, NULL, NULL, NULL) > 0) + _glfwPlatformPollEvents(); } diff --git a/support/tinycthread.c b/support/tinycthread.c new file mode 100644 index 00000000..ced7cf3f --- /dev/null +++ b/support/tinycthread.c @@ -0,0 +1,588 @@ +/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#include "tinycthread.h" +#include + +/* Platform specific includes */ +#if defined(_TTHREAD_POSIX_) + #include + #include + #include + #include + #include +#elif defined(_TTHREAD_WIN32_) + #include + #include +#endif + +/* Standard, good-to-have defines */ +#ifndef NULL + #define NULL (void*)0 +#endif +#ifndef TRUE + #define TRUE 1 +#endif +#ifndef FALSE + #define FALSE 0 +#endif + +int mtx_init(mtx_t *mtx, int type) +{ +#if defined(_TTHREAD_WIN32_) + mtx->mAlreadyLocked = FALSE; + mtx->mRecursive = type & mtx_recursive; + InitializeCriticalSection(&mtx->mHandle); + return thrd_success; +#else + int ret; + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + if (type & mtx_recursive) + { + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + } + ret = pthread_mutex_init(mtx, &attr); + pthread_mutexattr_destroy(&attr); + return ret == 0 ? thrd_success : thrd_error; +#endif +} + +void mtx_destroy(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + DeleteCriticalSection(&mtx->mHandle); +#else + pthread_mutex_destroy(mtx); +#endif +} + +int mtx_lock(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + EnterCriticalSection(&mtx->mHandle); + if (!mtx->mRecursive) + { + while(mtx->mAlreadyLocked) Sleep(1000); /* Simulate deadlock... */ + mtx->mAlreadyLocked = TRUE; + } + return thrd_success; +#else + return pthread_mutex_lock(mtx) == 0 ? thrd_success : thrd_error; +#endif +} + +int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) +{ + /* FIXME! */ + (void)mtx; + (void)ts; + return thrd_error; +} + +int mtx_trylock(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + int ret = TryEnterCriticalSection(&mtx->mHandle) ? thrd_success : thrd_busy; + if ((!mtx->mRecursive) && (ret == thrd_success) && mtx->mAlreadyLocked) + { + LeaveCriticalSection(&mtx->mHandle); + ret = thrd_busy; + } + return ret; +#else + return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; +#endif +} + +int mtx_unlock(mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + mtx->mAlreadyLocked = FALSE; + LeaveCriticalSection(&mtx->mHandle); + return thrd_success; +#else + return pthread_mutex_unlock(mtx) == 0 ? thrd_success : thrd_error;; +#endif +} + +#if defined(_TTHREAD_WIN32_) +#define _CONDITION_EVENT_ONE 0 +#define _CONDITION_EVENT_ALL 1 +#endif + +int cnd_init(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + cond->mWaitersCount = 0; + + /* Init critical section */ + InitializeCriticalSection(&cond->mWaitersCountLock); + + /* Init events */ + cond->mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); + if (cond->mEvents[_CONDITION_EVENT_ONE] == NULL) + { + cond->mEvents[_CONDITION_EVENT_ALL] = NULL; + return thrd_error; + } + cond->mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (cond->mEvents[_CONDITION_EVENT_ALL] == NULL) + { + CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); + cond->mEvents[_CONDITION_EVENT_ONE] = NULL; + return thrd_error; + } + + return thrd_success; +#else + return pthread_cond_init(cond, NULL) == 0 ? thrd_success : thrd_error; +#endif +} + +void cnd_destroy(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + if (cond->mEvents[_CONDITION_EVENT_ONE] != NULL) + { + CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); + } + if (cond->mEvents[_CONDITION_EVENT_ALL] != NULL) + { + CloseHandle(cond->mEvents[_CONDITION_EVENT_ALL]); + } + DeleteCriticalSection(&cond->mWaitersCountLock); +#else + pthread_cond_destroy(cond); +#endif +} + +int cnd_signal(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + int haveWaiters; + + /* Are there any waiters? */ + EnterCriticalSection(&cond->mWaitersCountLock); + haveWaiters = (cond->mWaitersCount > 0); + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* If we have any waiting threads, send them a signal */ + if(haveWaiters) + { + if (SetEvent(cond->mEvents[_CONDITION_EVENT_ONE]) == 0) + { + return thrd_error; + } + } + + return thrd_success; +#else + return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error; +#endif +} + +int cnd_broadcast(cnd_t *cond) +{ +#if defined(_TTHREAD_WIN32_) + int haveWaiters; + + /* Are there any waiters? */ + EnterCriticalSection(&cond->mWaitersCountLock); + haveWaiters = (cond->mWaitersCount > 0); + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* If we have any waiting threads, send them a signal */ + if(haveWaiters) + { + if (SetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) + { + return thrd_error; + } + } + + return thrd_success; +#else + return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error; +#endif +} + +#if defined(_TTHREAD_WIN32_) +static int _cnd_timedwait_win32(cnd_t *cond, mtx_t *mtx, DWORD timeout) +{ + int result, lastWaiter; + + /* Increment number of waiters */ + EnterCriticalSection(&cond->mWaitersCountLock); + ++ cond->mWaitersCount; + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* Release the mutex while waiting for the condition (will decrease + the number of waiters when done)... */ + mtx_unlock(mtx); + + /* Wait for either event to become signaled due to cnd_signal() or + cnd_broadcast() being called */ + result = WaitForMultipleObjects(2, cond->mEvents, FALSE, timeout); + if (result == WAIT_TIMEOUT) + { + return thrd_timeout; + } + else if (result == (int)WAIT_FAILED) + { + return thrd_error; + } + + /* Check if we are the last waiter */ + EnterCriticalSection(&cond->mWaitersCountLock); + -- cond->mWaitersCount; + lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && + (cond->mWaitersCount == 0); + LeaveCriticalSection(&cond->mWaitersCountLock); + + /* If we are the last waiter to be notified to stop waiting, reset the event */ + if (lastWaiter) + { + if (ResetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) + { + return thrd_error; + } + } + + /* Re-acquire the mutex */ + mtx_lock(mtx); + + return thrd_success; +} +#endif + +int cnd_wait(cnd_t *cond, mtx_t *mtx) +{ +#if defined(_TTHREAD_WIN32_) + return _cnd_timedwait_win32(cond, mtx, INFINITE); +#else + return pthread_cond_wait(cond, mtx) == 0 ? thrd_success : thrd_error; +#endif +} + +int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts) +{ +#if defined(_TTHREAD_WIN32_) + struct timespec now; + if (clock_gettime(TIME_UTC, &now) == 0) + { + DWORD delta = (ts->tv_sec - now.tv_sec) * 1000 + + (ts->tv_nsec - now.tv_nsec + 500000) / 1000000; + return _cnd_timedwait_win32(cond, mtx, delta); + } + else + return thrd_error; +#else + int ret; + ret = pthread_cond_timedwait(cond, mtx, ts); + if (ret == ETIMEDOUT) + { + return thrd_timeout; + } + return ret == 0 ? thrd_success : thrd_error; +#endif +} + + +/** Information to pass to the new thread (what to run). */ +typedef struct { + thrd_start_t mFunction; /**< Pointer to the function to be executed. */ + void * mArg; /**< Function argument for the thread function. */ +} _thread_start_info; + +/* Thread wrapper function. */ +#if defined(_TTHREAD_WIN32_) +static unsigned WINAPI _thrd_wrapper_function(void * aArg) +#elif defined(_TTHREAD_POSIX_) +static void * _thrd_wrapper_function(void * aArg) +#endif +{ + thrd_start_t fun; + void *arg; + int res; +#if defined(_TTHREAD_POSIX_) + void *pres; +#endif + + /* Get thread startup information */ + _thread_start_info *ti = (_thread_start_info *) aArg; + fun = ti->mFunction; + arg = ti->mArg; + + /* The thread is responsible for freeing the startup information */ + free((void *)ti); + + /* Call the actual client thread function */ + res = fun(arg); + +#if defined(_TTHREAD_WIN32_) + return res; +#else + pres = malloc(sizeof(int)); + if (pres != NULL) + { + *(int*)pres = res; + } + return pres; +#endif +} + +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) +{ + /* Fill out the thread startup information (passed to the thread wrapper, + which will eventually free it) */ + _thread_start_info* ti = (_thread_start_info*)malloc(sizeof(_thread_start_info)); + if (ti == NULL) + { + return thrd_nomem; + } + ti->mFunction = func; + ti->mArg = arg; + + /* Create the thread */ +#if defined(_TTHREAD_WIN32_) + *thr = (HANDLE)_beginthreadex(NULL, 0, _thrd_wrapper_function, (void *)ti, 0, NULL); +#elif defined(_TTHREAD_POSIX_) + if(pthread_create(thr, NULL, _thrd_wrapper_function, (void *)ti) != 0) + { + *thr = 0; + } +#endif + + /* Did we fail to create the thread? */ + if(!*thr) + { + free(ti); + return thrd_error; + } + + return thrd_success; +} + +thrd_t thrd_current(void) +{ +#if defined(_TTHREAD_WIN32_) + return GetCurrentThread(); +#else + return pthread_self(); +#endif +} + +int thrd_detach(thrd_t thr) +{ + /* FIXME! */ + (void)thr; + return thrd_error; +} + +int thrd_equal(thrd_t thr0, thrd_t thr1) +{ +#if defined(_TTHREAD_WIN32_) + return thr0 == thr1; +#else + return pthread_equal(thr0, thr1); +#endif +} + +void thrd_exit(int res) +{ +#if defined(_TTHREAD_WIN32_) + ExitThread(res); +#else + void *pres = malloc(sizeof(int)); + if (pres != NULL) + { + *(int*)pres = res; + } + pthread_exit(pres); +#endif +} + +int thrd_join(thrd_t thr, int *res) +{ +#if defined(_TTHREAD_WIN32_) + if (WaitForSingleObject(thr, INFINITE) == WAIT_FAILED) + { + return thrd_error; + } + if (res != NULL) + { + DWORD dwRes; + GetExitCodeThread(thr, &dwRes); + *res = dwRes; + } +#elif defined(_TTHREAD_POSIX_) + void *pres; + int ires = 0; + if (pthread_join(thr, &pres) != 0) + { + return thrd_error; + } + if (pres != NULL) + { + ires = *(int*)pres; + free(pres); + } + if (res != NULL) + { + *res = ires; + } +#endif + return thrd_success; +} + +int thrd_sleep(const struct timespec *time_point, struct timespec *remaining) +{ + struct timespec now; +#if defined(_TTHREAD_WIN32_) + DWORD delta; +#else + long delta; +#endif + + /* Get the current time */ + if (clock_gettime(TIME_UTC, &now) != 0) + return -2; // FIXME: Some specific error code? + +#if defined(_TTHREAD_WIN32_) + /* Delta in milliseconds */ + delta = (time_point->tv_sec - now.tv_sec) * 1000 + + (time_point->tv_nsec - now.tv_nsec + 500000) / 1000000; + if (delta > 0) + { + Sleep(delta); + } +#else + /* Delta in microseconds */ + delta = (time_point->tv_sec - now.tv_sec) * 1000000L + + (time_point->tv_nsec - now.tv_nsec + 500L) / 1000L; + + /* On some systems, the usleep argument must be < 1000000 */ + while (delta > 999999L) + { + usleep(999999); + delta -= 999999L; + } + if (delta > 0L) + { + usleep((useconds_t)delta); + } +#endif + + /* We don't support waking up prematurely (yet) */ + if (remaining) + { + remaining->tv_sec = 0; + remaining->tv_nsec = 0; + } + return 0; +} + +void thrd_yield(void) +{ +#if defined(_TTHREAD_WIN32_) + Sleep(0); +#else + sched_yield(); +#endif +} + +int tss_create(tss_t *key, tss_dtor_t dtor) +{ +#if defined(_TTHREAD_WIN32_) + /* FIXME: The destructor function is not supported yet... */ + if (dtor != NULL) + { + return thrd_error; + } + *key = TlsAlloc(); + if (*key == TLS_OUT_OF_INDEXES) + { + return thrd_error; + } +#else + if (pthread_key_create(key, dtor) != 0) + { + return thrd_error; + } +#endif + return thrd_success; +} + +void tss_delete(tss_t key) +{ +#if defined(_TTHREAD_WIN32_) + TlsFree(key); +#else + pthread_key_delete(key); +#endif +} + +void *tss_get(tss_t key) +{ +#if defined(_TTHREAD_WIN32_) + return TlsGetValue(key); +#else + return pthread_getspecific(key); +#endif +} + +int tss_set(tss_t key, void *val) +{ +#if defined(_TTHREAD_WIN32_) + if (TlsSetValue(key, val) == 0) + { + return thrd_error; + } +#else + if (pthread_setspecific(key, val) != 0) + { + return thrd_error; + } +#endif + return thrd_success; +} + +#if defined(_TTHREAD_EMULATE_CLOCK_GETTIME_) +int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts) +{ +#if defined(_TTHREAD_WIN32_) + struct _timeb tb; + _ftime(&tb); + ts->tv_sec = (time_t)tb.time; + ts->tv_nsec = 1000000L * (long)tb.millitm; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + ts->tv_sec = (time_t)tv.tv_sec; + ts->tv_nsec = 1000L * (long)tv.tv_usec; +#endif + return 0; +} +#endif // _TTHREAD_EMULATE_CLOCK_GETTIME_ + diff --git a/support/tinycthread.h b/support/tinycthread.h new file mode 100644 index 00000000..18451ef9 --- /dev/null +++ b/support/tinycthread.h @@ -0,0 +1,439 @@ +/* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- +Copyright (c) 2012 Marcus Geelnard + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +*/ + +#ifndef _TINYCTHREAD_H_ +#define _TINYCTHREAD_H_ + +/** +* @file +* @mainpage TinyCThread API Reference +* +* @section intro_sec Introduction +* TinyCThread is a minimal, portable implementation of basic threading +* classes for C. +* +* They closely mimic the functionality and naming of the C11 standard, and +* should be easily replaceable with the corresponding standard variants. +* +* @section port_sec Portability +* The Win32 variant uses the native Win32 API for implementing the thread +* classes, while for other systems, the POSIX threads API (pthread) is used. +* +* @section misc_sec Miscellaneous +* The following special keywords are available: #_Thread_local. +* +* For more detailed information, browse the different sections of this +* documentation. A good place to start is: +* tinycthread.h. +*/ + +/* Which platform are we on? */ +#if !defined(_TTHREAD_PLATFORM_DEFINED_) + #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) + #define _TTHREAD_WIN32_ + #else + #define _TTHREAD_POSIX_ + #endif + #define _TTHREAD_PLATFORM_DEFINED_ +#endif + +/* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes) */ +#if defined(_TTHREAD_POSIX_) + #undef _FEATURES_H + #if !defined(_GNU_SOURCE) + #define _GNU_SOURCE + #endif + #if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L) + #undef _POSIX_C_SOURCE + #define _POSIX_C_SOURCE 199309L + #endif + #if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500) + #undef _XOPEN_SOURCE + #define _XOPEN_SOURCE 500 + #endif +#endif + +/* Generic includes */ +#include + +/* Platform specific includes */ +#if defined(_TTHREAD_POSIX_) + #include +#elif defined(_TTHREAD_WIN32_) + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #define __UNDEF_LEAN_AND_MEAN + #endif + #include + #ifdef __UNDEF_LEAN_AND_MEAN + #undef WIN32_LEAN_AND_MEAN + #undef __UNDEF_LEAN_AND_MEAN + #endif +#endif + +/* Workaround for missing TIME_UTC: If time.h doesn't provide TIME_UTC, + it's quite likely that libc does not support it either. Hence, fall back to + the only other supported time specifier: CLOCK_REALTIME (and if that fails, + we're probably emulating clock_gettime anyway, so anything goes). */ +#ifndef TIME_UTC + #ifdef CLOCK_REALTIME + #define TIME_UTC CLOCK_REALTIME + #else + #define TIME_UTC 0 + #endif +#endif + +/* Workaround for missing clock_gettime (most Windows compilers, afaik) */ +#if defined(_TTHREAD_WIN32_) || defined(__APPLE_CC__) +#define _TTHREAD_EMULATE_CLOCK_GETTIME_ +/* Emulate struct timespec */ +#if defined(_TTHREAD_WIN32_) +struct _ttherad_timespec { + time_t tv_sec; + long tv_nsec; +}; +#define timespec _ttherad_timespec +#endif + +/* Emulate clockid_t */ +typedef int _tthread_clockid_t; +#define clockid_t _tthread_clockid_t + +/* Emulate clock_gettime */ +int _tthread_clock_gettime(clockid_t clk_id, struct timespec *ts); +#define clock_gettime _tthread_clock_gettime +#endif + + +/** TinyCThread version (major number). */ +#define TINYCTHREAD_VERSION_MAJOR 1 +/** TinyCThread version (minor number). */ +#define TINYCTHREAD_VERSION_MINOR 1 +/** TinyCThread version (full version). */ +#define TINYCTHREAD_VERSION (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR) + +/** +* @def _Thread_local +* Thread local storage keyword. +* A variable that is declared with the @c _Thread_local keyword makes the +* value of the variable local to each thread (known as thread-local storage, +* or TLS). Example usage: +* @code +* // This variable is local to each thread. +* _Thread_local int variable; +* @endcode +* @note The @c _Thread_local keyword is a macro that maps to the corresponding +* compiler directive (e.g. @c __declspec(thread)). +* @note This directive is currently not supported on Mac OS X (it will give +* a compiler error), since compile-time TLS is not supported in the Mac OS X +* executable format. Also, some older versions of MinGW (before GCC 4.x) do +* not support this directive. +* @hideinitializer +*/ + +/* FIXME: Check for a PROPER value of __STDC_VERSION__ to know if we have C11 */ +#if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) && !defined(_Thread_local) + #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) + #define _Thread_local __thread + #else + #define _Thread_local __declspec(thread) + #endif +#endif + +/* Macros */ +#define TSS_DTOR_ITERATIONS 0 + +/* Function return values */ +#define thrd_error 0 /**< The requested operation failed */ +#define thrd_success 1 /**< The requested operation succeeded */ +#define thrd_timeout 2 /**< The time specified in the call was reached without acquiring the requested resource */ +#define thrd_busy 3 /**< The requested operation failed because a tesource requested by a test and return function is already in use */ +#define thrd_nomem 4 /**< The requested operation failed because it was unable to allocate memory */ + +/* Mutex types */ +#define mtx_plain 1 +#define mtx_timed 2 +#define mtx_try 4 +#define mtx_recursive 8 + +/* Mutex */ +#if defined(_TTHREAD_WIN32_) +typedef struct { + CRITICAL_SECTION mHandle; /* Critical section handle */ + int mAlreadyLocked; /* TRUE if the mutex is already locked */ + int mRecursive; /* TRUE if the mutex is recursive */ +} mtx_t; +#else +typedef pthread_mutex_t mtx_t; +#endif + +/** Create a mutex object. +* @param mtx A mutex object. +* @param type Bit-mask that must have one of the following six values: +* @li @c mtx_plain for a simple non-recursive mutex +* @li @c mtx_timed for a non-recursive mutex that supports timeout +* @li @c mtx_try for a non-recursive mutex that supports test and return +* @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive) +* @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive) +* @li @c mtx_try | @c mtx_recursive (same as @c mtx_try, but recursive) +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int mtx_init(mtx_t *mtx, int type); + +/** Release any resources used by the given mutex. +* @param mtx A mutex object. +*/ +void mtx_destroy(mtx_t *mtx); + +/** Lock the given mutex. +* Blocks until the given mutex can be locked. If the mutex is non-recursive, and +* the calling thread already has a lock on the mutex, this call will block +* forever. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int mtx_lock(mtx_t *mtx); + +/** NOT YET IMPLEMENTED. +*/ +int mtx_timedlock(mtx_t *mtx, const struct timespec *ts); + +/** Try to lock the given mutex. +* The specified mutex shall support either test and return or timeout. If the +* mutex is already locked, the function returns without blocking. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_busy if the resource +* requested is already in use, or @ref thrd_error if the request could not be +* honored. +*/ +int mtx_trylock(mtx_t *mtx); + +/** Unlock the given mutex. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int mtx_unlock(mtx_t *mtx); + +/* Condition variable */ +#if defined(_TTHREAD_WIN32_) +typedef struct { + HANDLE mEvents[2]; /* Signal and broadcast event HANDLEs. */ + unsigned int mWaitersCount; /* Count of the number of waiters. */ + CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */ +} cnd_t; +#else +typedef pthread_cond_t cnd_t; +#endif + +/** Create a condition variable object. +* @param cond A condition variable object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_init(cnd_t *cond); + +/** Release any resources used by the given condition variable. +* @param cond A condition variable object. +*/ +void cnd_destroy(cnd_t *cond); + +/** Signal a condition variable. +* Unblocks one of the threads that are blocked on the given condition variable +* at the time of the call. If no threads are blocked on the condition variable +* at the time of the call, the function does nothing and return success. +* @param cond A condition variable object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_signal(cnd_t *cond); + +/** Broadcast a condition variable. +* Unblocks all of the threads that are blocked on the given condition variable +* at the time of the call. If no threads are blocked on the condition variable +* at the time of the call, the function does nothing and return success. +* @param cond A condition variable object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_broadcast(cnd_t *cond); + +/** Wait for a condition variable to become signaled. +* The function atomically unlocks the given mutex and endeavors to block until +* the given condition variable is signaled by a call to cnd_signal or to +* cnd_broadcast. When the calling thread becomes unblocked it locks the mutex +* before it returns. +* @param cond A condition variable object. +* @param mtx A mutex object. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int cnd_wait(cnd_t *cond, mtx_t *mtx); + +/** Wait for a condition variable to become signaled. +* The function atomically unlocks the given mutex and endeavors to block until +* the given condition variable is signaled by a call to cnd_signal or to +* cnd_broadcast, or until after the specified time. When the calling thread +* becomes unblocked it locks the mutex before it returns. +* @param cond A condition variable object. +* @param mtx A mutex object. +* @param xt A point in time at which the request will time out (absolute time). +* @return @ref thrd_success upon success, or @ref thrd_timeout if the time +* specified in the call was reached without acquiring the requested resource, or +* @ref thrd_error if the request could not be honored. +*/ +int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts); + +/* Thread */ +#if defined(_TTHREAD_WIN32_) +typedef HANDLE thrd_t; +#else +typedef pthread_t thrd_t; +#endif + +/** Thread start function. +* Any thread that is started with the @ref thrd_create() function must be +* started through a function of this type. +* @param arg The thread argument (the @c arg argument of the corresponding +* @ref thrd_create() call). +* @return The thread return value, which can be obtained by another thread +* by using the @ref thrd_join() function. +*/ +typedef int (*thrd_start_t)(void *arg); + +/** Create a new thread. +* @param thr Identifier of the newly created thread. +* @param func A function pointer to the function that will be executed in +* the new thread. +* @param arg An argument to the thread function. +* @return @ref thrd_success on success, or @ref thrd_nomem if no memory could +* be allocated for the thread requested, or @ref thrd_error if the request +* could not be honored. +* @note A thread’s identifier may be reused for a different thread once the +* original thread has exited and either been detached or joined to another +* thread. +*/ +int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); + +/** Identify the calling thread. +* @return The identifier of the calling thread. +*/ +thrd_t thrd_current(void); + +/** NOT YET IMPLEMENTED. +*/ +int thrd_detach(thrd_t thr); + +/** Compare two thread identifiers. +* The function determines if two thread identifiers refer to the same thread. +* @return Zero if the two thread identifiers refer to different threads. +* Otherwise a nonzero value is returned. +*/ +int thrd_equal(thrd_t thr0, thrd_t thr1); + +/** Terminate execution of the calling thread. +* @param res Result code of the calling thread. +*/ +void thrd_exit(int res); + +/** Wait for a thread to terminate. +* The function joins the given thread with the current thread by blocking +* until the other thread has terminated. +* @param thr The thread to join with. +* @param res If this pointer is not NULL, the function will store the result +* code of the given thread in the integer pointed to by @c res. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int thrd_join(thrd_t thr, int *res); + +/** Put the calling thread to sleep. +* Suspend execution of the calling thread. +* @param time_point A point in time at which the thread will resume (absolute time). +* @param remaining If non-NULL, this parameter will hold the remaining time until +* time_point upon return. This will typically be zero, but if +* the thread was woken up by a signal that is not ignored before +* time_point was reached @c remaining will hold a positive +* time. +* @return 0 (zero) on successful sleep, or -1 if an interrupt occurred. +*/ +int thrd_sleep(const struct timespec *time_point, struct timespec *remaining); + +/** Yield execution to another thread. +* Permit other threads to run, even if the current thread would ordinarily +* continue to run. +*/ +void thrd_yield(void); + +/* Thread local storage */ +#if defined(_TTHREAD_WIN32_) +typedef DWORD tss_t; +#else +typedef pthread_key_t tss_t; +#endif + +/** Destructor function for a thread-specific storage. +* @param val The value of the destructed thread-specific storage. +*/ +typedef void (*tss_dtor_t)(void *val); + +/** Create a thread-specific storage. +* @param key The unique key identifier that will be set if the function is +* successful. +* @param dtor Destructor function. This can be NULL. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +* @note The destructor function is not supported under Windows. If @c dtor is +* not NULL when calling this function under Windows, the function will fail +* and return @ref thrd_error. +*/ +int tss_create(tss_t *key, tss_dtor_t dtor); + +/** Delete a thread-specific storage. +* The function releases any resources used by the given thread-specific +* storage. +* @param key The key that shall be deleted. +*/ +void tss_delete(tss_t key); + +/** Get the value for a thread-specific storage. +* @param key The thread-specific storage identifier. +* @return The value for the current thread held in the given thread-specific +* storage. +*/ +void *tss_get(tss_t key); + +/** Set the value for a thread-specific storage. +* @param key The thread-specific storage identifier. +* @param val The value of the thread-specific storage to set for the current +* thread. +* @return @ref thrd_success on success, or @ref thrd_error if the request could +* not be honored. +*/ +int tss_set(tss_t key, void *val); + + +#endif /* _TINYTHREAD_H_ */ + diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bcad7ef5..11bcfe9e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,12 +8,19 @@ else() link_libraries(${glfw_LIBRARIES}) endif() +list(APPEND thread_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) +if (UNIX AND NOT APPLE) + list(APPEND thread_LIBRARIES ${RT_LIBRARY}) +endif() + include_directories(${GLFW_SOURCE_DIR}/include ${GLFW_SOURCE_DIR}/support ${OPENGL_INCLUDE_DIR}) set(GETOPT ${GLFW_SOURCE_DIR}/support/getopt.h ${GLFW_SOURCE_DIR}/support/getopt.c) +set(TINYCTHREAD ${GLFW_SOURCE_DIR}/support/tinycthread.h + ${GLFW_SOURCE_DIR}/support/tinycthread.c) add_executable(clipboard clipboard.c ${GETOPT}) add_executable(defaults defaults.c) @@ -37,13 +44,20 @@ set_target_properties(sharing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Sharing") add_executable(tearing WIN32 MACOSX_BUNDLE tearing.c) set_target_properties(tearing PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Tearing") +add_executable(threads WIN32 MACOSX_BUNDLE threads.c ${TINYCTHREAD}) +set_target_properties(threads PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Threads") + add_executable(title WIN32 MACOSX_BUNDLE title.c) set_target_properties(title PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Title") add_executable(windows WIN32 MACOSX_BUNDLE windows.c) set_target_properties(windows PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "Windows") -set(WINDOWS_BINARIES accuracy sharing tearing title windows) +if (BUILD_SHARED_LIBS) + target_link_libraries(threads ${thread_LIBRARIES}) +endif() + +set(WINDOWS_BINARIES accuracy sharing tearing threads title windows) set(CONSOLE_BINARIES clipboard defaults events fsaa fsfocus gamma glfwinfo iconify joysticks modes peter reopen) diff --git a/tests/glfwinfo.c b/tests/glfwinfo.c index b889d9b6..b5497155 100644 --- a/tests/glfwinfo.c +++ b/tests/glfwinfo.c @@ -317,6 +317,9 @@ int main(int argc, char** argv) get_client_api_name(api), glGetString(GL_VENDOR)); + printf("OpenGL context debug flag saved by GLFW: %s\n", + glfwGetWindowParam(window, GLFW_OPENGL_DEBUG_CONTEXT) ? "true" : "false"); + if (major > 1) { printf("%s context shading language version: \"%s\"\n", diff --git a/tests/threads.c b/tests/threads.c new file mode 100644 index 00000000..35c83716 --- /dev/null +++ b/tests/threads.c @@ -0,0 +1,131 @@ +//======================================================================== +// Multithreading test +// Copyright (c) Camilla Berglund +// +// This software is provided 'as-is', without any express or implied +// warranty. In no event will the authors be held liable for any damages +// arising from the use of this software. +// +// Permission is granted to anyone to use this software for any purpose, +// including commercial applications, and to alter it and redistribute it +// freely, subject to the following restrictions: +// +// 1. The origin of this software must not be misrepresented; you must not +// claim that you wrote the original software. If you use this software +// in a product, an acknowledgment in the product documentation would +// be appreciated but is not required. +// +// 2. Altered source versions must be plainly marked as such, and must not +// be misrepresented as being the original software. +// +// 3. This notice may not be removed or altered from any source +// distribution. +// +//======================================================================== +// +// This test is intended to verify whether the OpenGL context part of +// the GLFW API is able to be used from multiple threads +// +//======================================================================== + +#include + +#include +#include +#include +#include + +#include "tinycthread.h" + +typedef struct +{ + GLFWwindow window; + const char* title; + float r, g, b; + thrd_t id; +} Thread; + +static volatile GLboolean running = GL_TRUE; + +static int thread_main(void* data) +{ + const Thread* thread = (const Thread*) data; + + glfwMakeContextCurrent(thread->window); + assert(glfwGetCurrentContext() == thread->window); + + glfwSwapInterval(1); + + while (running) + { + const float v = (float) fabs(sin(glfwGetTime() * 2.f)); + glClearColor(thread->r * v, thread->g * v, thread->b * v, 0.f); + + glClear(GL_COLOR_BUFFER_BIT); + glfwSwapBuffers(thread->window); + } + + glfwMakeContextCurrent(NULL); + return 0; +} + +int main(void) +{ + int i, result; + Thread threads[] = + { + { NULL, "Red", 1.f, 0.f, 0.f, 0 }, + { NULL, "Green", 0.f, 1.f, 0.f, 0 }, + { NULL, "Blue", 0.f, 0.f, 1.f, 0 } + }; + const int count = sizeof(threads) / sizeof(Thread); + + if (!glfwInit()) + { + fprintf(stderr, "Failed to initialize GLFW: %s\n", + glfwErrorString(glfwGetError())); + exit(EXIT_FAILURE); + } + + for (i = 0; i < count; i++) + { + threads[i].window = glfwCreateWindow(200, 200, + GLFW_WINDOWED, + threads[i].title, + NULL); + if (!threads[i].window) + { + fprintf(stderr, "Failed to open GLFW window: %s\n", + glfwErrorString(glfwGetError())); + exit(EXIT_FAILURE); + } + + glfwSetWindowPos(threads[i].window, 200 + 250 * i, 200); + + if (thrd_create(&threads[i].id, thread_main, threads + i) != + thrd_success) + { + fprintf(stderr, "Failed to create secondary thread\n"); + exit(EXIT_FAILURE); + } + } + + while (running) + { + assert(glfwGetCurrentContext() == NULL); + + glfwWaitEvents(); + + for (i = 0; i < count; i++) + { + if (glfwGetWindowParam(threads[i].window, GLFW_CLOSE_REQUESTED)) + running = GL_FALSE; + } + } + + for (i = 0; i < count; i++) + thrd_join(threads[i].id, &result); + + exit(EXIT_SUCCESS); +} +