diff --git a/include/SDL_hints.h b/include/SDL_hints.h index f4878686d..05e0cbeaa 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -798,14 +798,22 @@ extern "C" { #define SDL_HINT_RPI_VIDEO_LAYER "SDL_RPI_VIDEO_LAYER" /** - * \brief Tell SDL the KMS/DRM video driver that we want double buffer only. + * \brief Tell the video driver that we only want a double buffer. * - * By default KMS/DRM will use a triple buffer solution that wastes no CPU - * time on waiting for vsync after issuing a flip, but introduces a frame of - * latency. Waiting for vsync immediately after issuing a flip on the other - * hand is recommended for cases where low latency is an important factor. + * By default, most lowlevel 2D APIs will use a triple buffer scheme that + * wastes no CPU time on waiting for vsync after issuing a flip, but + * introduces a frame of latency. On the other hand, using a double buffer + * scheme instead is recommended for cases where low latency is an important + * factor because we save a whole frame of latency. + * We do so by waiting for vsync immediately after issuing a flip, usually just + * after eglSwapBuffers call in the backend's *_SwapWindow function. + * + * Since it's driver-specific, it's only supported where possible and + * implemented. Currently supported the following drivers: + * - KMSDRM (kmsdrm) + * - Raspberry Pi (raspberrypi) */ -#define SDL_HINT_KMSDRM_DOUBLE_BUFFER "SDL_KMSDRM_DOUBLE_BUFFER" +#define SDL_HINT_VIDEO_DOUBLE_BUFFER "SDL_VIDEO_DOUBLE_BUFFER" /** * \brief A variable controlling what driver to use for OpenGL ES contexts. diff --git a/src/video/kmsdrm/SDL_kmsdrmvideo.c b/src/video/kmsdrm/SDL_kmsdrmvideo.c index 318191507..9c1b29181 100644 --- a/src/video/kmsdrm/SDL_kmsdrmvideo.c +++ b/src/video/kmsdrm/SDL_kmsdrmvideo.c @@ -525,7 +525,7 @@ KMSDRM_CreateWindow(_THIS, SDL_Window * window) /* In case we want low-latency, double-buffer video, we take note here */ wdata->double_buffer = SDL_FALSE; - if (SDL_GetHintBoolean(SDL_HINT_KMSDRM_DOUBLE_BUFFER, SDL_FALSE)) { + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, SDL_FALSE)) { wdata->double_buffer = SDL_TRUE; } diff --git a/src/video/raspberry/SDL_rpiopengles.c b/src/video/raspberry/SDL_rpiopengles.c index 5b23a3f9d..7b7353ddd 100644 --- a/src/video/raspberry/SDL_rpiopengles.c +++ b/src/video/raspberry/SDL_rpiopengles.c @@ -19,6 +19,8 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "../../SDL_internal.h" +#include "SDL_hints.h" +#include "SDL_log.h" #if SDL_VIDEO_DRIVER_RPI && SDL_VIDEO_OPENGL_EGL @@ -40,8 +42,27 @@ RPI_GLES_LoadLibrary(_THIS, const char *path) { return SDL_EGL_LoadLibrary(_this, path, EGL_DEFAULT_DISPLAY, 0); } +int +RPI_GLES_SwapWindow(_THIS, SDL_Window * window) { + SDL_WindowData *wdata = ((SDL_WindowData *) window->driverdata); + + if (!(_this->egl_data->eglSwapBuffers(_this->egl_data->egl_display, wdata->egl_surface))) { + SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "eglSwapBuffers failed."); + return 0; + } + + /* Wait immediately for vsync (as if we only had two buffers), for low input-lag scenarios. + * Run your SDL2 program with "SDL_RPI_DOUBLE_BUFFER=1 " to enable this. */ + if (wdata->double_buffer) { + SDL_LockMutex(wdata->vsync_cond_mutex); + SDL_CondWait(wdata->vsync_cond, wdata->vsync_cond_mutex); + SDL_UnlockMutex(wdata->vsync_cond_mutex); + } + + return 0; +} + SDL_EGL_CreateContext_impl(RPI) -SDL_EGL_SwapWindow_impl(RPI) SDL_EGL_MakeCurrent_impl(RPI) #endif /* SDL_VIDEO_DRIVER_RPI && SDL_VIDEO_OPENGL_EGL */ diff --git a/src/video/raspberry/SDL_rpivideo.c b/src/video/raspberry/SDL_rpivideo.c index 1e1f569f4..bbd2f07fa 100644 --- a/src/video/raspberry/SDL_rpivideo.c +++ b/src/video/raspberry/SDL_rpivideo.c @@ -214,6 +214,16 @@ RPI_SetDisplayMode(_THIS, SDL_VideoDisplay * display, SDL_DisplayMode * mode) return 0; } +static void +RPI_vsync_callback(DISPMANX_UPDATE_HANDLE_T u, void *data) +{ + SDL_WindowData *wdata = ((SDL_WindowData *) data); + + SDL_LockMutex(wdata->vsync_cond_mutex); + SDL_CondSignal(wdata->vsync_cond); + SDL_UnlockMutex(wdata->vsync_cond_mutex); +} + int RPI_CreateWindow(_THIS, SDL_Window * window) { @@ -289,9 +299,18 @@ RPI_CreateWindow(_THIS, SDL_Window * window) return SDL_SetError("Could not create GLES window surface"); } + /* Start generating vsync callbacks if necesary */ + wdata->double_buffer = SDL_FALSE; + if (SDL_GetHintBoolean(SDL_HINT_VIDEO_DOUBLE_BUFFER, SDL_FALSE)) { + wdata->vsync_cond = SDL_CreateCond(); + wdata->vsync_cond_mutex = SDL_CreateMutex(); + wdata->double_buffer = SDL_TRUE; + vc_dispmanx_vsync_callback(displaydata->dispman_display, RPI_vsync_callback, (void*)wdata); + } + /* Setup driver data for this window */ window->driverdata = wdata; - + /* One window, it always has focus */ SDL_SetMouseFocus(window); SDL_SetKeyboardFocus(window); @@ -304,7 +323,22 @@ void RPI_DestroyWindow(_THIS, SDL_Window * window) { SDL_WindowData *data = (SDL_WindowData *) window->driverdata; + SDL_VideoDisplay *display = SDL_GetDisplayForWindow(window); + SDL_DisplayData *displaydata = (SDL_DisplayData *) display->driverdata; + if(data) { + if (data->double_buffer) { + /* Wait for vsync, and then stop vsync callbacks and destroy related stuff, if needed */ + SDL_LockMutex(data->vsync_cond_mutex); + SDL_CondWait(data->vsync_cond, data->vsync_cond_mutex); + SDL_UnlockMutex(data->vsync_cond_mutex); + + vc_dispmanx_vsync_callback(displaydata->dispman_display, NULL, NULL); + + SDL_DestroyCond(data->vsync_cond); + SDL_DestroyMutex(data->vsync_cond_mutex); + } + #if SDL_VIDEO_OPENGL_EGL if (data->egl_surface != EGL_NO_SURFACE) { SDL_EGL_DestroySurface(_this, data->egl_surface); diff --git a/src/video/raspberry/SDL_rpivideo.h b/src/video/raspberry/SDL_rpivideo.h index ba1f0b2ef..0678b9f69 100644 --- a/src/video/raspberry/SDL_rpivideo.h +++ b/src/video/raspberry/SDL_rpivideo.h @@ -48,6 +48,12 @@ typedef struct SDL_WindowData #if SDL_VIDEO_OPENGL_EGL EGLSurface egl_surface; #endif + + /* Vsync callback cond and mutex */ + SDL_cond *vsync_cond; + SDL_mutex *vsync_cond_mutex; + SDL_bool double_buffer; + } SDL_WindowData; #define SDL_RPI_VIDEOLAYER 10000 /* High enough so to occlude everything */