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.)