SDL: Added an OpenVR video driver (thanks @cnlohr!)

From e81e917c5e3319a3331488df93494423bbf5e1af Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 17 Oct 2024 19:38:04 -0700
Subject: [PATCH] Added an OpenVR video driver (thanks @cnlohr!)

---
 CMakeLists.txt                                |    2 +
 cmake/sdlchecks.cmake                         |   13 +
 include/SDL3/SDL_hints.h                      |   11 +
 include/SDL3/SDL_video.h                      |    6 +
 include/build_config/SDL_build_config.h.cmake |    1 +
 src/video/SDL_sysvideo.h                      |    1 +
 src/video/SDL_video.c                         |    7 +
 src/video/openvr/SDL_openvrvideo.c            | 1728 +++++++++
 src/video/openvr/SDL_openvrvideo.h            |  109 +
 src/video/openvr/openvr_capi.h                | 3182 +++++++++++++++++
 10 files changed, 5060 insertions(+)
 create mode 100644 src/video/openvr/SDL_openvrvideo.c
 create mode 100644 src/video/openvr/SDL_openvrvideo.h
 create mode 100644 src/video/openvr/openvr_capi.h

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1bade6c07dff7..8b801655dd4d2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -337,6 +337,7 @@ dep_option(SDL_VIVANTE             "Use Vivante EGL video driver" ON "${UNIX_SYS
 dep_option(SDL_VULKAN              "Enable Vulkan support" ON "SDL_VIDEO;ANDROID OR APPLE OR LINUX OR FREEBSD OR WINDOWS" OFF)
 dep_option(SDL_RENDER_VULKAN       "Enable the Vulkan render driver" ON "SDL_RENDER;SDL_VULKAN" OFF)
 dep_option(SDL_METAL               "Enable Metal support" ON "APPLE" OFF)
+set_option(SDL_OPENVR              "Use OpenVR video driver" OFF)
 dep_option(SDL_KMSDRM              "Use KMS DRM video driver" ${UNIX_SYS} "SDL_VIDEO" OFF)
 dep_option(SDL_KMSDRM_SHARED       "Dynamically load KMS DRM support" ON "SDL_KMSDRM" OFF)
 set_option(SDL_OFFSCREEN           "Use offscreen video driver" ON)
@@ -1544,6 +1545,7 @@ elseif(UNIX AND NOT APPLE AND NOT RISCOS AND NOT HAIKU)
     CheckOpenGL()
     CheckOpenGLES()
     CheckWayland()
+    CheckOpenVR()
     CheckVivante()
     CheckVulkan()
     CheckQNXScreen()
diff --git a/cmake/sdlchecks.cmake b/cmake/sdlchecks.cmake
index 48e2ad8e78196..4668772f59ec5 100644
--- a/cmake/sdlchecks.cmake
+++ b/cmake/sdlchecks.cmake
@@ -662,6 +662,19 @@ macro(CheckVivante)
   endif()
 endmacro()
 
+# Requires:
+# - n/a
+macro(CheckOpenVR)
+  if(SDL_OPENVR)
+    set(HAVE_OPENVR TRUE)
+    set(HAVE_OPENVR_VIDEO TRUE)
+
+    sdl_glob_sources("${SDL3_SOURCE_DIR}/src/video/openvr/*.c")
+    set(SDL_VIDEO_DRIVER_OPENVR 1)
+    sdl_link_dependency(egl LIBS EGL)
+  endif()
+endmacro()
+
 # Requires:
 # - nada
 macro(CheckGLX)
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 0d585851cd9db..6f1ddd28cd52f 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -2629,6 +2629,17 @@ extern "C" {
  */
 #define SDL_HINT_OPENGL_ES_DRIVER "SDL_OPENGL_ES_DRIVER"
 
+/**
+ *  Mechanism to specify openvr_api library location
+ *
+ *  By default, when using the OpenVR driver, it will search for the API
+ *  library in the current folder.  But, if you wish to use a system API
+ *  you can specify that by using this hint.  This should be the full or
+ *  relative path to a .dll on Windows or .so on Linux.
+ *
+ */
+#define SDL_HINT_OPENVR_LIBRARY              "SDL_OPENVR_LIBRARY"
+
 /**
  * A variable controlling which orientations are allowed on iOS/Android.
  *
diff --git a/include/SDL3/SDL_video.h b/include/SDL3/SDL_video.h
index f4d6949be065c..1e2a42e59fc78 100644
--- a/include/SDL3/SDL_video.h
+++ b/include/SDL3/SDL_video.h
@@ -1282,6 +1282,11 @@ extern SDL_DECLSPEC SDL_Window * SDLCALL SDL_GetWindowParent(SDL_Window *window)
  * - `SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER`: the NSInteger tag
  *   assocated with metal views on the window
  *
+ * On OpenVR:
+ *
+ * - `SDL_PROP_WINDOW_OPENVR_OVERLAY_ID`: the OpenVR Overlay Handle ID for
+ *   the associated overlay window.
+ *
  * On Vivante:
  *
  * - `SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER`: the EGLNativeDisplayType
@@ -1354,6 +1359,7 @@ extern SDL_DECLSPEC SDL_PropertiesID SDLCALL SDL_GetWindowProperties(SDL_Window
 #define SDL_PROP_WINDOW_KMSDRM_GBM_DEVICE_POINTER                   "SDL.window.kmsdrm.gbm_dev"
 #define SDL_PROP_WINDOW_COCOA_WINDOW_POINTER                        "SDL.window.cocoa.window"
 #define SDL_PROP_WINDOW_COCOA_METAL_VIEW_TAG_NUMBER                 "SDL.window.cocoa.metal_view_tag"
+#define SDL_PROP_WINDOW_OPENVR_OVERLAY_ID                           "SDL.window.openvr.overlay_id"
 #define SDL_PROP_WINDOW_VIVANTE_DISPLAY_POINTER                     "SDL.window.vivante.display"
 #define SDL_PROP_WINDOW_VIVANTE_WINDOW_POINTER                      "SDL.window.vivante.window"
 #define SDL_PROP_WINDOW_VIVANTE_SURFACE_POINTER                     "SDL.window.vivante.surface"
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index ae900e8af0b65..e131e9afeb464 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -397,6 +397,7 @@
 #cmakedefine SDL_VIDEO_DRIVER_VITA @SDL_VIDEO_DRIVER_VITA@
 #cmakedefine SDL_VIDEO_DRIVER_VIVANTE @SDL_VIDEO_DRIVER_VIVANTE@
 #cmakedefine SDL_VIDEO_DRIVER_VIVANTE_VDK @SDL_VIDEO_DRIVER_VIVANTE_VDK@
+#cmakedefine SDL_VIDEO_DRIVER_OPENVR @SDL_VIDEO_DRIVER_OPENVR@
 #cmakedefine SDL_VIDEO_DRIVER_WAYLAND @SDL_VIDEO_DRIVER_WAYLAND@
 #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC@
 #cmakedefine SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR @SDL_VIDEO_DRIVER_WAYLAND_DYNAMIC_CURSOR@
diff --git a/src/video/SDL_sysvideo.h b/src/video/SDL_sysvideo.h
index bd8c56982751d..cb5a650a185d4 100644
--- a/src/video/SDL_sysvideo.h
+++ b/src/video/SDL_sysvideo.h
@@ -521,6 +521,7 @@ extern VideoBootStrap Emscripten_bootstrap;
 extern VideoBootStrap OFFSCREEN_bootstrap;
 extern VideoBootStrap NGAGE_bootstrap;
 extern VideoBootStrap QNX_bootstrap;
+extern VideoBootStrap OPENVR_bootstrap;
 
 // Use SDL_OnVideoThread() sparingly, to avoid regressions in use cases that currently happen to work
 extern bool SDL_OnVideoThread(void);
diff --git a/src/video/SDL_video.c b/src/video/SDL_video.c
index c0687fb4cfc97..ceb4b6b9ef7c7 100644
--- a/src/video/SDL_video.c
+++ b/src/video/SDL_video.c
@@ -139,6 +139,9 @@ static VideoBootStrap *bootstrap[] = {
 #ifdef SDL_INPUT_LINUXEV
     &DUMMY_evdev_bootstrap,
 #endif
+#endif
+#ifdef SDL_VIDEO_DRIVER_OPENVR
+    &OPENVR_bootstrap,
 #endif
     NULL
 };
@@ -2339,6 +2342,10 @@ SDL_Window *SDL_CreateWindowWithProperties(SDL_PropertiesID props)
         flags |= SDL_DefaultGraphicsBackends(_this);
     }
 
+#if defined(SDL_VIDEO_OPENGL) && defined(SDL_VIDEO_DRIVER_OPENVR)
+    flags |= SDL_WINDOW_OPENGL;
+#endif
+
     if (flags & SDL_WINDOW_OPENGL) {
         if (!_this->GL_CreateContext) {
             SDL_ContextNotSupported("OpenGL");
diff --git a/src/video/openvr/SDL_openvrvideo.c b/src/video/openvr/SDL_openvrvideo.c
new file mode 100644
index 0000000000000..770ce00ea93b7
--- /dev/null
+++ b/src/video/openvr/SDL_openvrvideo.c
@@ -0,0 +1,1728 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 2022 Charles Lohr <charlesl@valvesoftware.com>
+
+  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 "SDL_internal.h"
+
+#ifdef SDL_VIDEO_DRIVER_OPENVR
+
+#define DEBUG_OPENVR
+
+#include "../../events/SDL_mouse_c.h"
+#include "../../events/SDL_keyboard_c.h"
+#include "../../events/SDL_events_c.h"
+#include "../SDL_sysvideo.h"
+#include "../SDL_pixels_c.h"
+#include "../SDL_egl_c.h"
+#include "SDL_openvrvideo.h"
+
+#include <SDL3/SDL_opengl.h>
+
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+#include "../windows/SDL_windowsopengles.h"
+#include "../windows/SDL_windowsopengl.h"
+#include "../windows/SDL_windowsvulkan.h"
+#define DEFAULT_OPENGL "OPENGL32.DLL"
+static bool OPENVR_GL_LoadLibrary(SDL_VideoDevice *_this, const char *path);
+static SDL_GLContext OPENVR_GL_CreateContext(SDL_VideoDevice *_this, SDL_Window *window);
+
+struct SDL_GLContextState
+{
+    HGLRC hglrc;
+};
+#else
+#include <SDL3/SDL_opengles2_gl2.h>
+#endif
+
+#define MARKER_ID 0
+#define MARKER_STR "vr-marker,frame_end,type,application"
+
+#undef EXTERN_C
+
+// For access to functions that don't get the video data context.
+SDL_VideoData * global_openvr_driver;
+
+static void InitializeMouseFunctions();
+
+struct SDL_CursorData
+{
+    unsigned texture_id_handle;
+    int hot_x, hot_y;
+    int w, h;
+};
+
+// GL Extensions for functions we will be using.
+void (*ov_glGenFramebuffers)(GLsizei n, GLuint *framebuffers);
+void (*ov_glGenRenderbuffers)(GLsizei n, GLuint *renderbuffers);
+void (*ov_glBindFramebuffer)(GLenum target, GLuint framebuffer);
+void (*ov_glBindRenderbuffer)(GLenum target, GLuint renderbuffer);
+void (*ov_glRenderbufferStorage)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
+void (*ov_glFramebufferRenderbuffer)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
+void (*ov_glFramebufferTexture2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
+GLenum (*ov_glCheckNamedFramebufferStatus)(GLuint framebuffer, GLenum target);
+GLenum (*ov_glGetError)();
+void (*ov_glFlush)();
+void (*ov_glFinish)();
+void (*ov_glGenTextures)(GLsizei n, GLuint *textures);
+void (*ov_glDeleteTextures)(GLsizei n, GLuint *textures);
+void (*ov_glTexParameterf)(GLenum target, GLenum pname, GLfloat param);
+void (*ov_glTexParameteri)(GLenum target, GLenum pname, GLenum param);
+void (*ov_glTexImage2D)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *data);
+void (*ov_glBindTexture)(GLenum target, GLuint texture);
+void (*ov_glDrawBuffers)(GLsizei n, const GLenum *bufs);
+void (*ov_glGetIntegerv)(GLenum pname, GLint * data);
+const GLubyte *(*ov_glGetStringi)(GLenum name, GLuint index);
+void (*ov_glClear)(GLbitfield mask);
+void (*ov_glClearColor)(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
+void (*ov_glColorMask)(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
+void (*ov_glDebugMessageInsert)(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char *message);
+
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+PROC(*ov_wglGetProcAddress)(LPCSTR);
+HGLRC(*ov_wglCreateContext)(HDC);
+BOOL(*ov_wglDeleteContext)(HGLRC);
+BOOL(*ov_wglMakeCurrent)(HDC, HGLRC);
+HGLRC(*ov_wglGetCurrentContext)();
+#endif
+
+
+#define OPENVR_DEFAULT_WIDTH 1920
+#define OPENVR_DEFAULT_HEIGHT 1080
+
+SDL_DisplayMode openvr_dm_default = {
+    .format = SDL_PIXELFORMAT_RGBA32,
+    .w = OPENVR_DEFAULT_WIDTH,
+    .h = OPENVR_DEFAULT_HEIGHT,
+    .refresh_rate = 120,
+    .internal = 0
+};
+
+SDL_VideoDisplay openvr_vd_default = {
+    .name = 0,
+    .max_fullscreen_modes = 0,
+    .num_fullscreen_modes = 0,
+    .fullscreen_modes = 0,
+    .desktop_mode = {
+        .format = SDL_PIXELFORMAT_RGBA32,
+        .w = OPENVR_DEFAULT_WIDTH,
+        .h = OPENVR_DEFAULT_HEIGHT,
+        .refresh_rate = 120,
+        .internal = 0
+    },
+    .current_mode = 0,
+    .natural_orientation = SDL_ORIENTATION_LANDSCAPE,
+    .current_orientation = SDL_ORIENTATION_LANDSCAPE,
+    .fullscreen_window = 0,
+    .device = 0,
+    .content_scale = 1.0f,
+    .internal = 0
+};
+
+#define OPENVR_SetupProc(proc) { proc = (void*)SDL_GL_GetProcAddress((#proc)+3); if (!proc) { failed_extension = (#proc)+3; } }
+
+static bool OPENVR_InitExtensions(SDL_VideoDevice *_this)
+{
+    if (!ov_glGetError) {
+        const char * failed_extension = 0;
+        OPENVR_SetupProc(ov_glGenFramebuffers);
+        OPENVR_SetupProc(ov_glGenRenderbuffers);
+        OPENVR_SetupProc(ov_glBindFramebuffer);
+        OPENVR_SetupProc(ov_glBindRenderbuffer);
+        OPENVR_SetupProc(ov_glRenderbufferStorage);
+        OPENVR_SetupProc(ov_glFramebufferRenderbuffer);
+        OPENVR_SetupProc(ov_glFramebufferTexture2D);
+        OPENVR_SetupProc(ov_glCheckNamedFramebufferStatus);
+        OPENVR_SetupProc(ov_glGetError);
+        OPENVR_SetupProc(ov_glFlush);
+        OPENVR_SetupProc(ov_glFinish);
+        OPENVR_SetupProc(ov_glGenTextures);
+        OPENVR_SetupProc(ov_glDeleteTextures);
+        OPENVR_SetupProc(ov_glTexParameterf);
+        OPENVR_SetupProc(ov_glTexParameteri);
+        OPENVR_SetupProc(ov_glTexImage2D);
+        OPENVR_SetupProc(ov_glBindTexture);
+        OPENVR_SetupProc(ov_glDrawBuffers);
+        OPENVR_SetupProc(ov_glClear);
+        OPENVR_SetupProc(ov_glClearColor);
+        OPENVR_SetupProc(ov_glColorMask);
+        OPENVR_SetupProc(ov_glGetStringi);
+        OPENVR_SetupProc(ov_glGetIntegerv);
+        OPENVR_SetupProc(ov_glDebugMessageInsert);
+        if (failed_extension) {
+            return SDL_SetError("Error loading GL extension for %s", failed_extension);
+        }
+    }
+    return true;
+}
+
+static bool OPENVR_SetOverlayError(EVROverlayError e)
+{
+    switch (e) {
+#define CASE(X) case EVROverlayError_VROverlayError_##X: return SDL_SetError("VROverlayError %s", #X)
+    CASE(UnknownOverlay);
+    CASE(InvalidHandle);
+    CASE(PermissionDenied);
+    CASE(OverlayLimitExceeded);
+    CASE(WrongVisibilityType);
+    CASE(KeyTooLong);
+    CASE(NameTooLong);
+    CASE(KeyInUse);
+    CASE(WrongTransformType);
+    CASE(InvalidTrackedDevice);
+    CASE(InvalidParameter);
+    CASE(ThumbnailCantBeDestroyed);
+    CASE(ArrayTooSmall);
+    CASE(RequestFailed);
+    CASE(InvalidTexture);
+    CASE(UnableToLoadFile);
+    CASE(KeyboardAlreadyInUse);
+    CASE(NoNeighbor);
+    CASE(TooManyMaskPrimitives);
+    CASE(BadMaskPrimitive);
+    CASE(TextureAlreadyLocked);
+    CASE(TextureLockCapacityReached);
+    CASE(TextureNotLocked);
+    CASE(TimedOut);
+#undef CASE
+    default:
+        return SDL_SetError("Unknown VROverlayError %d", e);
+    }
+}
+
+#ifdef SDL_VIDEO_DRIVER_WINDOWS
+
+#define STYLE_BASIC         (WS_CLIPSIBLINGS | WS_CLIPCHILDREN)
+#define STYLE_FULLSCREEN    (WS_POPUP | WS_MINIMIZEBOX)
+#define STYLE_BORDERLESS    (WS_POPUP | WS_MINIMIZEBOX)
+#define STYLE_BORDERLESS_WINDOWED (WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
+#define STYLE_NORMAL        (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX)
+#define STYLE_RESIZABLE     (WS_THICKFRAME | WS_MAXIMIZEBOX)
+#define STYLE_MASK          (STYLE_FULLSCREEN | STYLE_BORDERLESS | STYLE_NORMAL | STYLE_RESIZABLE)
+
+static DWORD GetWindowStyle(SDL_Window *window)
+{
+    DWORD style = 0;
+
+    if (window->flags & SDL_WINDOW_FULLSCREEN) {
+        style |= STYLE_FULLSCREEN;
+    } else {
+        if (window->flags & SDL_WINDOW_BORDERLESS) {
+            /* SDL 2.1:
+               This behavior more closely matches other platform where the window is borderless
+               but still interacts with the window manager (e.g. task bar shows above it, it can
+               be resized to fit within usable desktop area, etc.) so this should be the behavior
+               for a future SDL release.
+
+               If you want a borderless window the size of the desktop that looks like a fullscreen
+               window, then you should use the SDL_WINDOW_FULLSCREEN_DESKTOP flag.
+             */
+            if (SDL_GetHintBoolean("SDL_BORDERLESS_WINDOWED_STYLE", false)) {
+                style |= STYLE_BORDERLESS_WINDOWED;
+            } else {
+                style |= STYLE_BORDERLESS;
+            }
+        } else {
+            style |= STYLE_NORMAL;
+        }
+
+        if (window->flags & SDL_WINDOW_RESIZABLE) {
+            /* You can have a borderless resizable window, but Windows doesn't always draw it correctly,
+               see https://bugzilla.libsdl.org/show_bug.cgi?id=4466
+             */
+            if (!(window->flags & SDL_WINDOW_BORDERLESS) ||
+                 SDL_GetHintBoolean("SDL_BORDERLESS_RESIZABLE_STYLE", false)) {
+                style |= STYLE_RESIZABLE;
+            }
+        }
+
+        // Need to set initialize minimize style, or when we call ShowWindow with WS_MINIMIZE it will activate a random window
+        if (window->flags & SDL_WINDOW_MINIMIZED) {
+            style |= WS_MINIMIZE;
+        }
+    }
+    return style;
+}
+// Importing from the Windows code.
+void WIN_ScreenPointFromSDL(int *x, int *y, int *dpiOut);
+void WIN_AdjustWindowRectWithStyle(SDL_Window *window, DWORD style, BOOL menu, int *x, int *y, int *width, int *height, bool use_current);
+
+#endif
+
+static bool OPENVR_InitializeOverlay(SDL_VideoDevice *_this, SDL_Window *window);
+
+static bool OPENVR_VideoInit(SDL_VideoDevice *_this)
+{
+    SDL_VideoData *data = (SDL_VideoData *)_this->internal;
+
+    {
+        const char * hintWidth = SDL_GetHint("SDL_DEFAULT_WIDTH");
+        const char * hintHeight = SDL_GetHint("SDL_DEFAULT_HEIGHT");
+        const char * hintFPS = SDL_GetHint("SDL_DEFAULT_FPS");
+        int width = hintWidth?atoi(hintWidth):0;
+        int height = hintHeight?atoi(hintHeight):0;
+        if (height > 0 && width > 0) {
+            openvr_vd_default.desktop_mode.w = width;
+            openvr_vd_default.desktop_mode.h = height;
+        }
+        int fps = hintFPS?atoi(hintFPS):0;
+        if (fps) {
+            openvr_vd_default.desktop_mode.refresh_rate = fps;
+        } else {
+            openvr_vd_default.desktop_mode.refresh_rate = data->oSystem->GetFloatTrackedDeviceProperty(k_unTrackedDeviceIndex_Hmd, ETrackedDeviceProperty_Prop_DisplayFrequency_Float, 0);
+        }
+    }
+
+    openvr_vd_default.internal = (SDL_DisplayData *)data;
+    openvr_vd_default.name = (char*)"OpenVRDisplay";
+    SDL_AddVideoDisplay(&openvr_vd_default, false);
+
+    return true;
+}
+
+static void OPENVR_VideoQuit(SDL_VideoDevice *_this)
+{
+    SDL_VideoData *videodata = (SDL_VideoData *)_this->internal;
+    if (videodata->bDidCreateOverlay && videodata->overlayID != 0) {
+        videodata->oOverlay->DestroyOverlay(videodata->overlayID);
+    }
+}
+
+static void OPENVR_Destroy(SDL_VideoDevice *device)
+{
+    // Don't need to destroy and free internal, since it is already done in SDL_VideoQuit
+}
+
+static uint32_t *ImageSDLToOpenVRGL(SDL_Surface * surf, bool bFlipY)
+{
+    int w = surf->w;
+    int h = surf->h;
+    int pitch = surf->pitch;
+    int x, y;
+    uint32_t * pxd = SDL_malloc(4 * surf->w * surf->h);
+    for(y = 0; y < h; y++) {
+        uint32_t * iline = (uint32_t*)&(((uint8_t*)surf->pixels)[y*pitch]);
+        uint32_t * oline = &pxd[(bFlipY?(h-y-1):y)*w];
+        for(x = 0; x < w; x++)
+        {
+            uint32_t pr = iline[x];
+            oline[x] = (pr & 0xff00ff00) | ((pr & 0xff) << 16) | ((pr & 0xff0000)>>16);
+        }
+    }
+    return pxd;
+}
+
+static bool OPENVR_CheckRenderbuffer(SDL_VideoDevice *_this)
+{
+    SDL_VideoData *videodata = (SDL_VideoData *)_this->internal;
+
+    if (videodata->targw == 0 || videodata->targh == 0) {
+        videodata->targw = OPENVR_DEFAULT_WIDTH;
+        videodata->targh = OPENVR_DEFAULT_HEIGHT;
+    }
+
+    if (videodata->targh != videodata->last_targh
+     || videodata->targw != videodata->last_targw) {
+
+        struct HmdVector2_t ms;
+        int status;
+
+        if (videodata->fbo <= 0) {
+            ov_glGenFramebuffers(1, &videodata->fbo);
+            ov_glGenRenderbuffers(1, &videodata->rbo);
+            ov_glGenTextures(1, &videodata->overlaytexture);
+        }
+
+        // Generate the OpenGL Backing buffers/etc.
+        ov_glBindFramebuffer(GL_FRAMEBUFFER, videodata->fbo);
+        ov_glBindRenderbuffer(GL_RENDERBUFFER, videodata->rbo);
+        ov_glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, videodata->targw, videodata->targh);
+        ov_glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, videodata->rbo);
+        ov_glBindTexture(GL_TEXTURE_2D, videodata->overlaytexture);
+        ov_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        ov_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        ov_glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, videodata->targw, videodata->targh, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
+        ov_glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, videodata->overlaytexture, 0);
+        status = ov_glCheckNamedFramebufferStatus(videodata->fbo, GL_FRAMEBUFFER);
+        if (status != GL_FRAMEBUFFER_COMPLETE) {
+            return SDL_SetError("OPENVR: Can't generate overlay buffer");
+        }
+        ov_glBindFramebuffer(GL_FRAMEBUFFER, 0);
+
+        ms.v[0] = (float)videodata->targw;
+        ms.v[1] = (float)videodata->targh;
+        videodata->oOverlay->SetOverlayMouseScale(videodata->overlayID, &ms);
+
+        videodata->last_targh = videodata->targh;
+        videodata->last_targw = videodata->targw;
+    }
+    return true;
+}
+
+static bool OPENVR_VirtualControllerRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+    // On XBOX Controllers Low/High maps to Left/Right
+    SDL_VideoData *videodata = (SDL_VideoData *)userdata;
+
+    const float k_flIntensity = 320.f; // Maximum frequency
+    float flLeftFrequency = (float)low_frequency_rumble * k_flIntensity / 65535.f;
+    float flRightFrequency = (float)high_frequency_rumble * k_flIntensity / 65535.f;
+    float flDurationSeconds = 2.f;
+    float flAmplitude = 1.f;
+
+    videodata->oInput->TriggerHapticVibrationAction(videodata->input_action_handles_haptics[0], 0, flDurationSeconds, flLeftFrequency, flAmplitude, 0);
+    videodata->oInput->TriggerHapticVibrationAction(videodata->input_action_handles_haptics[1], 0, flDurationSeconds, flRightFrequency, flAmplitude, 0);
+
+    return true;
+}
+
+static bool OPENVR_VirtualControllerRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble)
+{
+    SDL_VideoData *videodata = (SDL_VideoData *)userdata;
+    videodata->oInput->TriggerHapticVibrationAction(videodata->input_action_handles_haptics[0], 0, 0.1f, left_rumble, 1.0, 0);
+    videodata->oInput->TriggerHapticVibrationAction(videodata->input_action_handles_haptics[1], 0, 0.1f, right_rumble, 1.0, 0);
+    return true;
+}
+
+static void OPENVR_VirtualControllerUpdate(void *userdata)
+{
+    SDL_VideoData *videodata = (SDL_VideoData *)userdata;
+    SDL_Joystick * joystick = videodata->virtual_joystick;
+    InputDigitalActionData_t digital_input_action;
+    InputAnalogActionData_t analog_input_action;
+    EVRInputError e;
+#ifdef DEBUG_OPENVR
+    //char cts[10240];
+    //char * ctsx = cts;
+#endif
+    VRActiveActionSet_t actionSet = { 0 };
+    actionSet.ulActionSet = videodata->input_action_set;
+    e = videodata->oInput->UpdateActionState(&actionSet, sizeof(actionSet), 1);
+    if (e)
+    {
+#ifdef DEBUG_OPENVR
+        SDL_Log("ERROR: Failed to update action state");
+#endif
+        return;
+    }
+
+    for (int d = 0; d < videodata->input_action_handles_buttons_count; d++)
+    {
+        if (videodata->input_action_handles_buttons[d] == k_ulInvalidActionHandle)
+            continue;
+        e = videodata->oInput->GetDigitalActionData(videodata->input_action_handles_buttons[d], &digital_input_action, sizeof(digital_input_action), k_ulInvalidInputValueHandle);
+        if (e)
+        {
+#ifdef DEBUG_OPENVR
+            SDL_Log("ERROR: Failed to get digital action data: %d", d);
+#endif
+            return;
+        }
+        SDL_SetJoystickVirtualButton(joystick, d, digital_input_action.bState);
+#ifdef DEBUG_OPENVR
+        //ctsx+=sprintf(ctsx,"%d", digital_input_action.bState);
+#endif
+    }
+
+    // Left Stick
+    e = videodata->oInput->GetAnalogActionData(videodata->input_action_handles_axes[0], &analog_input_action, sizeof(analog_input_action), k_ulInvalidInputValueHandle);
+    if (e)
+    {
+#ifdef DEBUG_OPENVR
+        SDL_Log("ERROR: Failed to get analog action data: left stick");
+#endif
+        return;
+    }
+    SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_LEFTX, (Sint16)(analog_input_action.x * SDL_JOYSTICK_AXIS_MAX));
+    SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_LEFTY, (Sint16)(-analog_input_action.y * SDL_JOYSTICK_AXIS_MAX));
+
+    // Right Stick
+    e = videodata->oInput->GetAnalogActionData(videodata->input_action_handles_axes[1], &analog_input_action, sizeof(analog_input_action), k_ulInvalidInputValueHandle);
+    if (e)
+    {
+#ifdef DEBUG_OPENVR
+        SDL_Log("ERROR: Failed to get analog action data: right stick");
+#endif
+        return;
+    }
+    SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_RIGHTX, (Sint16)(analog_input_action.x * SDL_JOYSTICK_AXIS_MAX));
+    SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_RIGHTY, (Sint16)(-analog_input_action.y * SDL_JOYSTICK_AXIS_MAX));
+
+    // Left Trigger
+    e = videodata->oInput->GetAnalogActionData(videodata->input_action_handles_axes[2], &analog_input_action, sizeof(analog_input_action), k_ulInvalidInputValueHandle);
+    if (e)
+    {
+#ifdef DEBUG_OPENVR
+        SDL_Log("ERROR: Failed to get analog action data: left trigger");
+#endif
+        return;
+    }
+    SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (Sint16)((analog_input_action.x * 2.0f - 1.0f) * SDL_JOYSTICK_AXIS_MAX));
+
+    // Right Trigger
+    e = videodata->oInput->GetAnalogActionData(videodata->input_action_handles_axes[3], &analog_input_action, sizeof(analog_input_action), k_ulInvalidInputValueHandle);
+    if (e)
+    {
+#ifdef DEBUG_OPENVR
+        SDL_Log("ERROR: Failed to get analog action data: right trigger");
+#endif
+        return;
+    }
+    SDL_SetJoystickVirtualAxis(joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (Sint16)((analog_input_action.x * 2.0f - 1.0f) * SDL_JOYSTICK_AXIS_MAX));
+
+#if 0
+    for (a = 0; a < videodata->input_action_handles_axes_count; a++)
+    {
+        float xval = 0.0f;
+        e = videodata->oInput->GetAnalogActionData(videodata->input_action_handles_axes[a], &analog_input_action, sizeof(analog_input_action), k_ulInvalidInputValueHandle);
+        if (e) goto updatefail;
+        xval = analog_input_action.x;
+        if (a == SDL_CONTROLLER_AXIS_LEFTY || a == SDL_CONTROLLER_AXIS_RIGHTY)
+          xval *= -1.0f;
+        if (a == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || a == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER)
+          xval = xval * 2.0f - 1.0f;
+        //SDL_SetJoystickVirtualAxis(joystick, a, analog_input_action.x*32767);
+        xval *= SDL_JOYSTICK_AXIS_MAX;
+        SDL_SetJoystickVirtualAxis(joystick, a, xval);
+#ifdef DEBUG_OPENVR
+        //ctsx+=sprintf(ctsx,"[%f]", analog_input_action.x);
+#endif
+    }
+#endif
+#ifdef DEBUG_OPENVR
+    //SDL_Log("Debug Input States: %s", cts);
+#endif
+    return;
+}
+
+static bool OPENVR_SetupJoystckBasedOnLoadedActionManifest(SDL_VideoData * videodata)
+{
+    SDL_VirtualJoystickDesc desc;
+    int virtual_index;
+
+    EVRInputError e = 0;
+
+    char * k_pchBooleanActionPaths[SDL_GAMEPAD_BUTTON_COUNT] = {
+        "/actions/virtualgamepad/in/a",
+        "/actions/virtualgamepad/in/b",
+        "/actions/virtualgamepad/in/x",
+        "/actions/virtualgamepad/in/y",
+        "/actions/virtualgamepad/in/back",
+        "/actions/virtualgamepad/in/guide",
+        "/actions/virtualgamepad/in/start",
+        "/actions/virtualgamepad/in/stick_click_left",
+        "/actions/virtualgamepad/in/stick_click_right",
+        "/actions/virtualgamepad/in/shoulder_left",
+        "/actions/virtualgamepad/in/shoulder_right",
+        "/actions/virtualgamepad/in/dpad_up",
+        "/actions/virtualgamepad/in/dpad_down",
+        "/actions/virtualgamepad/in/dpad_left",
+        "/actions/virtualgamepad/in/dpad_right",
+        "/actions/virtualgamepad/in/misc_1",
+        "/actions/virtualgamepad/in/paddle_1",
+        "/actions/virtualgamepad/in/paddle_2",
+        "/actions/virtualgamepad/in/paddle_3",
+        "/actions/virtualgamepad/in/paddle_4",
+        "/actions/virtualgamepad/in/touchpad_click",
+        "/actions/virtualgamepad/in/misc_2",
+        "/actions/virtualgamepad/in/misc_3",
+        "/actions/virtualgamepad/in/misc_4",
+        "/actions/virtualgamepad/in/misc_5",
+        "/actions/virtualgamepad/in/misc_6",
+    };
+    char * k_pchAnalogActionPaths[4] = {
+        "/actions/virtualgamepad/in/stick_left",
+        "/actions/virtualgamepad/in/stick_right",
+        "/actions/virtualgamepad/in/trigger_left",
+        "/actions/virtualgamepad/in/trigger_right",
+    };
+
+    if ((e = videodata->oInput->GetActionSetHandle("/actions/virtualgamepad", &videodata->input_action_set)) != EVRInputError_VRInputError_None)
+    {
+#ifdef DEBUG_OPENVR
+        SDL_Log("ERROR: Failed to get action set handle: %d", e);
+#endif
+        return SDL_SetError("Failed to get action set handle");
+    }
+
+    videodata->input_action_handles_buttons_count = sizeof(k_pchBooleanActionPaths) / sizeof(k_pchBooleanActionPaths[0]);
+    videodata->input_action_handles_buttons = SDL_malloc(videodata->input_action_handles_buttons_count * sizeof(VRActionHandle_t));
+
+    for (int i = 0; i < videodata->input_action_handles_buttons_count; i++)
+    {
+        e = videodata->oInput->GetActionHandle(k_pchBooleanActionPaths[i], &videodata->input_action_handles_buttons[i]);
+        if (e)
+        {
+            SDL_Log("ERROR: Failed to get button action %d ('%s')", i, k_pchBooleanActionPaths[i]);
+            return SDL_SetError("ERROR: Failed to get button action");
+        }
+    }
+
+    videodata->input_action_handles_axes_count = sizeof(k_pchAnalogActionPaths) / sizeof(k_pchAnalogActionPaths[0]);
+    videodata->input_action_handles_axes = SDL_malloc(videodata->input_action_handles_axes_count * sizeof(VRActionHandle_t));
+
+    for (int i = 0; i < videodata->input_action_handles_axes_count; i++)
+    {
+        e = videodata->oInput->GetActionHandle(k_pchAnalogActionPaths[i], &videodata->input_action_handles_axes[i]);
+        if (e)
+        {
+            SDL_Log("ERROR: Failed to get analog action %d ('%s')", i, k_pchAnalogActionPaths[i]);
+            return SDL_SetError("ERROR: Failed to get analog action");
+        }
+    }
+
+    e  = videodata->oInput->GetActionHandle("/actions/virtualgamepad/out/haptic_left", &videodata->input_action_handles_haptics[0]);
+    e |= videodata->oInput->GetActionHandle("/actions/virtualgamepad/out/haptic_right", &videodata->input_action_handles_haptics[1]);
+    if (e)
+    {
+#ifdef DEBUG_OPENVR
+        SDL_Log("ERROR: Failed to get haptics action");
+#endif
+        return SDL_SetError("ERROR: Failed to get haptics action");
+    }
+
+    // Create a virtual joystick.
+    SDL_INIT_INTERFACE(&desc);
+    desc.type = SDL_JOYSTICK_TYPE_GAMEPAD;
+    desc.naxes = SDL_GAMEPAD_AXIS_COUNT;
+    desc.nbuttons = SDL_GAMEPAD_BUTTON_COUNT;
+    desc.Rumble = OPENVR_VirtualControllerRumble;
+    desc.RumbleTriggers = OPENVR_VirtualControllerRumbleTriggers;
+    desc.Update = OPENVR_VirtualControllerUpdate;
+    

(Patch may be truncated, please check the link at the top of this post.)