Commit 14a3fe0a authored by Camilla Löwy's avatar Camilla Löwy
Browse files

Make glfwGetError also provide description

Related to #970.
parent beaeb0d4
......@@ -122,7 +122,8 @@ information on what to include when reporting a bug.
## Changelog
- Added `glfwGetError` function for querying the last error code (#970)
- Added `glfwGetError` function for querying the last error code and its
description (#970)
- Added `glfwRequestWindowAttention` function for requesting attention from the
user (#732,#988)
- Added `glfwGetKeyScancode` function that allows retrieving platform dependent
......
......@@ -128,15 +128,19 @@ immediately.
@section error_handling Error handling
Some GLFW functions have return values that indicate an error, but this is often
not very helpful when trying to figure out _why_ the error occurred. Other
functions have no return value reserved for errors, so error notification needs
a separate channel. Finally, far from all GLFW functions have return values.
not very helpful when trying to figure out what happened or why it occurred.
Other functions have no return value reserved for errors, so error notification
needs a separate channel. Finally, far from all GLFW functions have return
values.
The last error code for the calling thread can be queried at any time with @ref
glfwGetError.
The last [error code](@ref errors) for the calling thread can be queried at any
time with @ref glfwGetError.
@code
int error = glfwGetError();
int code = glfwGetError(NULL);
if (code != GLFW_NO_ERROR)
handle_error(code);
@endcode
If no error has occurred since the last call, @ref GLFW_NO_ERROR is returned.
......@@ -146,42 +150,52 @@ The error code indicates the general category of the error. Some error codes,
such as @ref GLFW_NOT_INITIALIZED has only a single meaning, whereas others like
@ref GLFW_PLATFORM_ERROR are used for many different errors.
GLFW usually has much more information about the error than its general category
at the point where it occurred. This is where the error callback comes in.
This callback is called whenever an error occurs. It is set with @ref
glfwSetErrorCallback, a function that may be called regardless of whether GLFW
is initialized.
GLFW often has more information about an error than its general category. You
can retrieve a UTF-8 encoded human-readable description along with the error
code. If no error has occurred since the last call, the description is set to
`NULL`.
@code
const char* description;
int code = glfwGetError(&description);
if (description)
display_error_message(code, description);
@endcode
The retrieved description string is only valid until the next error occurs.
This means you must make a copy of it if you want to keep it.
You can also set an error callback, which will be called each time an error
occurs. It is set with @ref glfwSetErrorCallback.
@code
glfwSetErrorCallback(error_callback);
@endcode
The error callback receives a human-readable description of the error and (when
possible) its cause. The description encoded as UTF-8. The callback is also
provided with an [error code](@ref errors).
The error callback receives the same error code and human-readable description
returned by @ref glfwGetError.
@code
void error_callback(int error, const char* description)
void error_callback(int code, const char* description)
{
puts(description);
display_error_message(code, description);
}
@endcode
The error callback is called after the error code is set, so calling @ref
glfwGetError from within the error callback returns the same value as the
The error callback is called after the error is stored, so calling @ref
glfwGetError from within the error callback returns the same values as the
callback argument.
The description string passed to the callback is only valid until the error
callback returns. This means you must make a copy of it if you want to keep it.
__Reported errors are never fatal.__ As long as GLFW was successfully
initialized, it will remain initialized and in a safe state until terminated
regardless of how many errors occur. If an error occurs during initialization
that causes @ref glfwInit to fail, any part of the library that was initialized
will be safely terminated.
The description string is only valid until the error callback returns, as it may
have been generated specifically for that error. This lets GLFW provide much
more specific error descriptions but means you must make a copy if you want to
keep the description string.
Do not rely on a currently invalid call to generate a specific error, as in the
future that same call may generate a different error or become valid.
......
......@@ -7,8 +7,8 @@
@subsection news_33_geterror Error query
GLFW now supports querying the last error code for the calling thread with @ref
glfwGetError.
GLFW now supports querying the last error code for the calling thread and its
human-readable description with @ref glfwGetError.
@see @ref error_handling
......
......@@ -1646,13 +1646,20 @@ GLFWAPI const char* glfwGetVersionString(void);
/*! @brief Returns and clears the last error for the calling thread.
*
* This function returns and clears the [error code](@ref error) of the last
* error that occurred on the calling thread. If no error has occurred since
* the last call, it returns @ref GLFW_NO_ERROR.
* error that occurred on the calling thread, and optionally a UTF-8 encoded
* human-readable description of it. If no error has occurred since the last
* call, it returns @ref GLFW_NO_ERROR and the description pointer is set to
* `NULL`.
*
* @param[in] description Where to store the error description pointer, or `NULL`.
* @return The last error code for the calling thread, or @ref GLFW_NO_ERROR.
*
* @errors None.
*
* @pointer_lifetime The returned string is allocated and freed by GLFW. You
* should not free it yourself. It is guaranteed to be valid only until the
* next error occurs or the library is terminated.
*
* @remark This function may be called before @ref glfwInit.
*
* @thread_safety This function may be called from any thread.
......@@ -1664,7 +1671,7 @@ GLFWAPI const char* glfwGetVersionString(void);
*
* @ingroup init
*/
GLFWAPI int glfwGetError(void);
GLFWAPI int glfwGetError(const char** description);
/*! @brief Sets the error callback.
*
......
......@@ -331,7 +331,7 @@ GLFWbool _glfwRefreshContextAttribs(const _GLFWctxconfig* ctxconfig)
NULL
};
window = _glfwPlatformGetTls(&_glfw.context);
window = _glfwPlatformGetTls(&_glfw.contextSlot);
window->context.source = ctxconfig->source;
window->context.client = GLFW_OPENGL_API;
......@@ -578,7 +578,7 @@ GLFWbool _glfwStringInExtensionString(const char* string, const char* extensions
GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle)
{
_GLFWwindow* window = (_GLFWwindow*) handle;
_GLFWwindow* previous = _glfwPlatformGetTls(&_glfw.context);
_GLFWwindow* previous = _glfwPlatformGetTls(&_glfw.contextSlot);
_GLFW_REQUIRE_INIT();
......@@ -601,7 +601,7 @@ GLFWAPI void glfwMakeContextCurrent(GLFWwindow* handle)
GLFWAPI GLFWwindow* glfwGetCurrentContext(void)
{
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
return _glfwPlatformGetTls(&_glfw.context);
return _glfwPlatformGetTls(&_glfw.contextSlot);
}
GLFWAPI void glfwSwapBuffers(GLFWwindow* handle)
......@@ -626,7 +626,7 @@ GLFWAPI void glfwSwapInterval(int interval)
_GLFW_REQUIRE_INIT();
window = _glfwPlatformGetTls(&_glfw.context);
window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window)
{
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL);
......@@ -643,7 +643,7 @@ GLFWAPI int glfwExtensionSupported(const char* extension)
_GLFW_REQUIRE_INIT_OR_RETURN(GLFW_FALSE);
window = _glfwPlatformGetTls(&_glfw.context);
window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window)
{
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL);
......@@ -708,7 +708,7 @@ GLFWAPI GLFWglproc glfwGetProcAddress(const char* procname)
_GLFW_REQUIRE_INIT_OR_RETURN(NULL);
window = _glfwPlatformGetTls(&_glfw.context);
window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (!window)
{
_glfwInputError(GLFW_NO_CURRENT_CONTEXT, NULL);
......
......@@ -199,12 +199,12 @@ static void makeContextCurrentEGL(_GLFWwindow* window)
}
}
_glfwPlatformSetTls(&_glfw.context, window);
_glfwPlatformSetTls(&_glfw.contextSlot, window);
}
static void swapBuffersEGL(_GLFWwindow* window)
{
if (window != _glfwPlatformGetTls(&_glfw.context))
if (window != _glfwPlatformGetTls(&_glfw.contextSlot))
{
_glfwInputError(GLFW_PLATFORM_ERROR,
"EGL: The context must be current on the calling thread when swapping buffers");
......@@ -233,7 +233,7 @@ static int extensionSupportedEGL(const char* extension)
static GLFWglproc getProcAddressEGL(const char* procname)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.context);
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (window->context.egl.client)
{
......
......@@ -165,7 +165,7 @@ static void makeContextCurrentGLX(_GLFWwindow* window)
}
}
_glfwPlatformSetTls(&_glfw.context, window);
_glfwPlatformSetTls(&_glfw.contextSlot, window);
}
static void swapBuffersGLX(_GLFWwindow* window)
......@@ -175,7 +175,7 @@ static void swapBuffersGLX(_GLFWwindow* window)
static void swapIntervalGLX(int interval)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.context);
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
if (_glfw.glx.EXT_swap_control)
{
......
......@@ -43,7 +43,7 @@ _GLFWlibrary _glfw = { GLFW_FALSE };
// These are outside of _glfw so they can be used before initialization and
// after termination
//
static int _glfwErrorCode;
static _GLFWerror _glfwMainThreadError;
static GLFWerrorfun _glfwErrorCallback;
static _GLFWinitconfig _glfwInitHints =
{
......@@ -56,9 +56,9 @@ static _GLFWinitconfig _glfwInitHints =
// Returns a generic string representation of the specified error
//
static const char* getErrorString(int error)
static const char* getErrorString(int code)
{
switch (error)
switch (code)
{
case GLFW_NOT_INITIALIZED:
return "The GLFW library is not initialized";
......@@ -114,11 +114,18 @@ static void terminate(void)
_glfwTerminateVulkan();
_glfwPlatformTerminate();
if (_glfwPlatformIsValidTls(&_glfw.error))
_glfwErrorCode = (int) (intptr_t) _glfwPlatformGetTls(&_glfw.error);
_glfw.initialized = GLFW_FALSE;
_glfwPlatformDestroyTls(&_glfw.context);
_glfwPlatformDestroyTls(&_glfw.error);
while (_glfw.errorListHead)
{
_GLFWerror* error = _glfw.errorListHead;
_glfw.errorListHead = error->next;
free(error);
}
_glfwPlatformDestroyTls(&_glfw.contextSlot);
_glfwPlatformDestroyTls(&_glfw.errorSlot);
_glfwPlatformDestroyMutex(&_glfw.errorLock);
memset(&_glfw, 0, sizeof(_glfw));
}
......@@ -128,37 +135,47 @@ static void terminate(void)
////// GLFW event API //////
//////////////////////////////////////////////////////////////////////////
void _glfwInputError(int error, const char* format, ...)
void _glfwInputError(int code, const char* format, ...)
{
if (_glfw.initialized)
_glfwPlatformSetTls(&_glfw.error, (void*) (intptr_t) error);
else
_glfwErrorCode = error;
_GLFWerror* error;
char description[1024];
if (_glfwErrorCallback)
if (format)
{
char buffer[8192];
const char* description;
int count;
va_list vl;
if (format)
{
int count;
va_list vl;
va_start(vl, format);
count = vsnprintf(buffer, sizeof(buffer), format, vl);
va_end(vl);
va_start(vl, format);
count = vsnprintf(description, sizeof(description), format, vl);
va_end(vl);
if (count < 0)
buffer[sizeof(buffer) - 1] = '\0';
if (count < 0)
description[sizeof(description) - 1] = '\0';
}
else
strcpy(description, getErrorString(code));
description = buffer;
if (_glfw.initialized)
{
error = _glfwPlatformGetTls(&_glfw.errorSlot);
if (!error)
{
error = calloc(1, sizeof(_GLFWerror));
_glfwPlatformSetTls(&_glfw.errorSlot, error);
_glfwPlatformLockMutex(&_glfw.errorLock);
error->next = _glfw.errorListHead;
_glfw.errorListHead = error;
_glfwPlatformUnlockMutex(&_glfw.errorLock);
}
else
description = getErrorString(error);
_glfwErrorCallback(error, description);
}
else
error = &_glfwMainThreadError;
error->code = code;
strcpy(error->description, description);
if (_glfwErrorCallback)
_glfwErrorCallback(code, description);
}
......@@ -180,12 +197,14 @@ GLFWAPI int glfwInit(void)
return GLFW_FALSE;
}
if (!_glfwPlatformCreateTls(&_glfw.error))
if (!_glfwPlatformCreateMutex(&_glfw.errorLock))
return GLFW_FALSE;
if (!_glfwPlatformCreateTls(&_glfw.context))
if (!_glfwPlatformCreateTls(&_glfw.errorSlot))
return GLFW_FALSE;
if (!_glfwPlatformCreateTls(&_glfw.contextSlot))
return GLFW_FALSE;
_glfwPlatformSetTls(&_glfw.error, (void*) (intptr_t) _glfwErrorCode);
_glfwPlatformSetTls(&_glfw.errorSlot, &_glfwMainThreadError);
_glfw.initialized = GLFW_TRUE;
_glfw.timer.offset = _glfwPlatformGetTimerValue();
......@@ -237,22 +256,28 @@ GLFWAPI const char* glfwGetVersionString(void)
return _glfwPlatformGetVersionString();
}
GLFWAPI int glfwGetError(void)
GLFWAPI int glfwGetError(const char** description)
{
int error;
_GLFWerror* error;
int code = GLFW_NO_ERROR;
if (description)
*description = NULL;
if (_glfw.initialized)
{
error = (int) (intptr_t) _glfwPlatformGetTls(&_glfw.error);
_glfwPlatformSetTls(&_glfw.error, (void*) (intptr_t) GLFW_NO_ERROR);
}
error = _glfwPlatformGetTls(&_glfw.errorSlot);
else
error = &_glfwMainThreadError;
if (error)
{
error = _glfwErrorCode;
_glfwErrorCode = GLFW_NO_ERROR;
code = error->code;
error->code = GLFW_NO_ERROR;
if (description && code)
*description = error->description;
}
return error;
return code;
}
GLFWAPI GLFWerrorfun glfwSetErrorCallback(GLFWerrorfun cbfun)
......
......@@ -55,6 +55,7 @@
typedef int GLFWbool;
typedef struct _GLFWerror _GLFWerror;
typedef struct _GLFWinitconfig _GLFWinitconfig;
typedef struct _GLFWwndconfig _GLFWwndconfig;
typedef struct _GLFWctxconfig _GLFWctxconfig;
......@@ -66,6 +67,7 @@ typedef struct _GLFWmonitor _GLFWmonitor;
typedef struct _GLFWcursor _GLFWcursor;
typedef struct _GLFWjoystick _GLFWjoystick;
typedef struct _GLFWtls _GLFWtls;
typedef struct _GLFWmutex _GLFWmutex;
typedef void (* _GLFWmakecontextcurrentfun)(_GLFWwindow*);
typedef void (* _GLFWswapbuffersfun)(_GLFWwindow*);
......@@ -257,6 +259,13 @@ typedef void (APIENTRY * PFN_vkVoidFunction)(void);
// Platform-independent structures
//========================================================================
struct _GLFWerror
{
_GLFWerror* next;
int code;
char description[1024];
};
/*! @brief Initialization configuration.
*
* Parameters relating to the initialization of the library.
......@@ -490,6 +499,14 @@ struct _GLFWtls
_GLFW_PLATFORM_TLS_STATE;
};
/*! @brief Mutex structure.
*/
struct _GLFWmutex
{
// This is defined in the platform's tls.h
_GLFW_PLATFORM_MUTEX_STATE;
};
/*! @brief Library global data.
*/
struct _GLFWlibrary
......@@ -504,8 +521,8 @@ struct _GLFWlibrary
int refreshRate;
} hints;
_GLFWerror* errorListHead;
_GLFWcursor* cursorListHead;
_GLFWwindow* windowListHead;
_GLFWmonitor** monitors;
......@@ -513,8 +530,9 @@ struct _GLFWlibrary
_GLFWjoystick joysticks[GLFW_JOYSTICK_LAST + 1];
_GLFWtls error;
_GLFWtls context;
_GLFWtls errorSlot;
_GLFWtls contextSlot;
_GLFWmutex errorLock;
struct {
uint64_t offset;
......@@ -651,7 +669,11 @@ GLFWbool _glfwPlatformCreateTls(_GLFWtls* tls);
void _glfwPlatformDestroyTls(_GLFWtls* tls);
void* _glfwPlatformGetTls(_GLFWtls* tls);
void _glfwPlatformSetTls(_GLFWtls* tls, void* value);
GLFWbool _glfwPlatformIsValidTls(_GLFWtls* tls);
GLFWbool _glfwPlatformCreateMutex(_GLFWmutex* mutex);
void _glfwPlatformDestroyMutex(_GLFWmutex* mutex);
void _glfwPlatformLockMutex(_GLFWmutex* mutex);
void _glfwPlatformUnlockMutex(_GLFWmutex* mutex);
/*! @} */
......@@ -798,15 +820,15 @@ void _glfwInputMonitor(_GLFWmonitor* monitor, int action, int placement);
void _glfwInputMonitorWindow(_GLFWmonitor* monitor, _GLFWwindow* window);
/*! @brief Notifies shared code of an error.
* @param[in] error The error code most suitable for the error.
* @param[in] code The error code most suitable for the error.
* @param[in] format The `printf` style format string of the error
* description.
* @ingroup event
*/
#if defined(__GNUC__)
void _glfwInputError(int error, const char* format, ...) __attribute__((format(printf, 2, 3)));
void _glfwInputError(int code, const char* format, ...) __attribute__((format(printf, 2, 3)));
#else
void _glfwInputError(int error, const char* format, ...);
void _glfwInputError(int code, const char* format, ...);
#endif
/*! @brief Notifies shared code of files or directories dropped on a window.
......
......@@ -34,7 +34,7 @@ static void makeContextCurrentNSGL(_GLFWwindow* window)
else
[NSOpenGLContext clearCurrentContext];
_glfwPlatformSetTls(&_glfw.context, window);
_glfwPlatformSetTls(&_glfw.contextSlot, window);
}
static void swapBuffersNSGL(_GLFWwindow* window)
......@@ -45,7 +45,7 @@ static void swapBuffersNSGL(_GLFWwindow* window)
static void swapIntervalNSGL(int interval)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.context);
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
GLint sync = interval;
[window->context.nsgl.object setValues:&sync
......
......@@ -63,7 +63,7 @@ static void makeContextCurrentOSMesa(_GLFWwindow* window)
}
}
_glfwPlatformSetTls(&_glfw.context, window);
_glfwPlatformSetTls(&_glfw.contextSlot, window);
}
static GLFWglproc getProcAddressOSMesa(const char* procname)
......
......@@ -69,8 +69,35 @@ void _glfwPlatformSetTls(_GLFWtls* tls, void* value)
pthread_setspecific(tls->posix.key, value);
}
GLFWbool _glfwPlatformIsValidTls(_GLFWtls* tls)
GLFWbool _glfwPlatformCreateMutex(_GLFWmutex* mutex)
{
return tls->posix.allocated;
assert(mutex->posix.allocated == GLFW_FALSE);
if (pthread_mutex_init(&mutex->posix.handle, NULL) != 0)
{
_glfwInputError(GLFW_PLATFORM_ERROR, "POSIX: Failed to create mutex");
return GLFW_FALSE;
}
return mutex->posix.allocated = GLFW_TRUE;
}
void _glfwPlatformDestroyMutex(_GLFWmutex* mutex)
{
if (mutex->posix.allocated)
pthread_mutex_destroy(&mutex->posix.handle);
memset(mutex, 0, sizeof(_GLFWmutex));
}
void _glfwPlatformLockMutex(_GLFWmutex* mutex)
{
assert(mutex->posix.allocated == GLFW_TRUE);
pthread_mutex_lock(&mutex->posix.handle);
}
void _glfwPlatformUnlockMutex(_GLFWmutex* mutex)
{
assert(mutex->posix.allocated == GLFW_TRUE);
pthread_mutex_unlock(&mutex->posix.handle);
}
......@@ -27,7 +27,8 @@
#include <pthread.h>
#define _GLFW_PLATFORM_TLS_STATE _GLFWtlsPOSIX posix
#define _GLFW_PLATFORM_TLS_STATE _GLFWtlsPOSIX posix
#define _GLFW_PLATFORM_MUTEX_STATE _GLFWmutexPOSIX posix
// POSIX-specific thread local storage data
......@@ -39,3 +40,12 @@ typedef struct _GLFWtlsPOSIX
} _GLFWtlsPOSIX;
// POSIX-specific mutex data
//
typedef struct _GLFWmutexPOSIX
{
GLFWbool allocated;
pthread_mutex_t handle;
} _GLFWmutexPOSIX;
......@@ -249,12 +249,12 @@ static void makeContextCurrentWGL(_GLFWwindow* window)
if (window)
{
if (wglMakeCurrent(window->context.wgl.dc, window->context.wgl.handle))
_glfwPlatformSetTls(&_glfw.context, window);
_glfwPlatformSetTls(&_glfw.contextSlot, window);
else
{
_glfwInputErrorWin32(GLFW_PLATFORM_ERROR,
"WGL: Failed to make context current");
_glfwPlatformSetTls(&_glfw.context, NULL);
_glfwPlatformSetTls(&_glfw.contextSlot, NULL);
}
}
else
......@@ -265,7 +265,7 @@ static void makeContextCurrentWGL(_GLFWwindow* window)
"WGL: Failed to clear current context");
}
_glfwPlatformSetTls(&_glfw.context, NULL);
_glfwPlatformSetTls(&_glfw.contextSlot, NULL);
}
}
......@@ -284,7 +284,7 @@ static void swapBuffersWGL(_GLFWwindow* window)
static void swapIntervalWGL(int interval)
{
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.context);
_GLFWwindow* window = _glfwPlatformGetTls(&_glfw.contextSlot);
window->context.wgl.interval = interval;
......
......@@ -221,6 +221,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWin32PresentationSupportKHR)(
#define _GLFW_PLATFORM_MONITOR_STATE _GLFWmonitorWin32 win32
#define _GLFW_PLATFORM_CURSOR_STATE _GLFWcursorWin32 win32
#define _GLFW_PLATFORM_TLS_STATE _GLFWtlsWin32 win32
#define _GLFW_PLATFORM_MUTEX_STATE _GLFWmutexWin32 win32