615 lines
24 KiB
C
615 lines
24 KiB
C
#include "vr_main.h"
|
|
|
|
#ifdef HAVE_VR
|
|
|
|
#include "vr_render.h"
|
|
#include "vr_math.h"
|
|
#include "../m_argv.h"
|
|
#include "../console.h"
|
|
#include "../screen.h"
|
|
#include "../i_video.h"
|
|
#include "../r_fps.h"
|
|
#include "../v_video.h"
|
|
#ifdef HWRENDER
|
|
#include "../hardware/hw_glob.h"
|
|
#include "../hardware/hw_main.h"
|
|
#endif
|
|
#include <stdbool.h>
|
|
#include <SDL3/SDL.h>
|
|
#include "../sdl/sdlmain.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#ifdef __linux__
|
|
#include <GL/glx.h>
|
|
#endif
|
|
|
|
int vr_current_eye = 0;
|
|
boolean vr_started = false;
|
|
boolean vr_enabled = false;
|
|
boolean vr_drawing_ui = false;
|
|
vr_render_pass_t vr_render_pass = VR_PASS_NONE;
|
|
|
|
XrInstance vr_instance = XR_NULL_HANDLE;
|
|
XrSession vr_session = XR_NULL_HANDLE;
|
|
XrSystemId vr_system_id = XR_NULL_SYSTEM_ID;
|
|
XrSpace vr_local_space = XR_NULL_HANDLE;
|
|
XrSpace vr_view_space = XR_NULL_HANDLE;
|
|
|
|
float vrHMDPoseMatrix[16];
|
|
float vrHMDScaledPoseMatrix[16];
|
|
float vrHMDPoseSkyboxMatrix[16];
|
|
float vrEyeViewMatrix[2][16];
|
|
float vrScaledEyeViewMatrix[2][16];
|
|
float vrEyeSkyboxViewMatrix[2][16];
|
|
float vrEyeProjMatrix[2][16];
|
|
|
|
int vrWorldScale[3] = {400, 250, 100};
|
|
float vrPlayerScale = 1.0f;
|
|
|
|
uint32_t vr_render_width = 0;
|
|
uint32_t vr_render_height = 0;
|
|
|
|
float* vrVisibleAreaVertices[2] = {NULL, NULL};
|
|
float* vrVisibleAreaUVs[2] = {NULL, NULL};
|
|
uint32_t vrVisibleAreaVertexCount[2] = {0, 0};
|
|
|
|
static PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = NULL;
|
|
static PFN_xrGetVisibilityMaskKHR pfnGetVisibilityMaskKHR = NULL;
|
|
static PFN_xrEnumerateDisplayRefreshRatesFB pfnEnumerateDisplayRefreshRatesFB = NULL;
|
|
static PFN_xrGetDisplayRefreshRateFB pfnGetDisplayRefreshRateFB = NULL;
|
|
static PFN_xrRequestDisplayRefreshRateFB pfnRequestDisplayRefreshRateFB = NULL;
|
|
static boolean displayRefreshRateExtensionEnabled = false;
|
|
|
|
static float VR_RenderScaleForMode(int mode)
|
|
{
|
|
static const float multipliers[] = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
|
|
|
|
if (mode < 0)
|
|
mode = 0;
|
|
else if (mode > 6)
|
|
mode = 6;
|
|
|
|
return multipliers[mode];
|
|
}
|
|
|
|
static int VR_StartupResolutionMode(void)
|
|
{
|
|
int mode = cv_vrresolution.string ? cv_vrresolution.value : 2;
|
|
|
|
if (M_CheckParm("+vrresolution") && M_IsNextParm()) {
|
|
const char* value = M_GetNextParm();
|
|
|
|
if (!strcasecmp(value, "50%"))
|
|
mode = 0;
|
|
else if (!strcasecmp(value, "75%"))
|
|
mode = 1;
|
|
else if (!strcasecmp(value, "100%"))
|
|
mode = 2;
|
|
else if (!strcasecmp(value, "125%"))
|
|
mode = 3;
|
|
else if (!strcasecmp(value, "150%"))
|
|
mode = 4;
|
|
else if (!strcasecmp(value, "175%"))
|
|
mode = 5;
|
|
else if (!strcasecmp(value, "200%"))
|
|
mode = 6;
|
|
else
|
|
mode = atoi(value);
|
|
}
|
|
|
|
if (mode < 0)
|
|
mode = 0;
|
|
else if (mode > 6)
|
|
mode = 6;
|
|
|
|
return mode;
|
|
}
|
|
|
|
static boolean VR_RuntimeExtensionSupported(const char* extensionName)
|
|
{
|
|
uint32_t extensionCount = 0;
|
|
XrResult res = xrEnumerateInstanceExtensionProperties(NULL, 0, &extensionCount, NULL);
|
|
if (XR_FAILED(res) || extensionCount == 0)
|
|
return false;
|
|
|
|
XrExtensionProperties* extensions =
|
|
(XrExtensionProperties*)malloc(extensionCount * sizeof(XrExtensionProperties));
|
|
if (!extensions)
|
|
return false;
|
|
|
|
for (uint32_t i = 0; i < extensionCount; i++) {
|
|
extensions[i].type = XR_TYPE_EXTENSION_PROPERTIES;
|
|
extensions[i].next = NULL;
|
|
}
|
|
|
|
res = xrEnumerateInstanceExtensionProperties(NULL, extensionCount, &extensionCount, extensions);
|
|
if (XR_FAILED(res)) {
|
|
free(extensions);
|
|
return false;
|
|
}
|
|
|
|
for (uint32_t i = 0; i < extensionCount; i++) {
|
|
if (strcmp(extensions[i].extensionName, extensionName) == 0) {
|
|
free(extensions);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
free(extensions);
|
|
return false;
|
|
}
|
|
|
|
static void VR_ConfigureDisplayRefreshRate(void)
|
|
{
|
|
if (!displayRefreshRateExtensionEnabled || !pfnGetDisplayRefreshRateFB)
|
|
return;
|
|
|
|
float currentRefreshRate = 0.0f;
|
|
if (XR_SUCCEEDED(pfnGetDisplayRefreshRateFB(vr_session, ¤tRefreshRate)) &&
|
|
currentRefreshRate > 0.0f) {
|
|
CONS_Printf("VR: Runtime display refresh rate is %.2f Hz.\n", currentRefreshRate);
|
|
|
|
if (pfnRequestDisplayRefreshRateFB) {
|
|
XrResult res = pfnRequestDisplayRefreshRateFB(vr_session, currentRefreshRate);
|
|
if (XR_SUCCEEDED(res))
|
|
CONS_Printf("VR: Requested runtime refresh pacing at %.2f Hz.\n", currentRefreshRate);
|
|
}
|
|
}
|
|
|
|
if (!pfnEnumerateDisplayRefreshRatesFB)
|
|
return;
|
|
|
|
uint32_t refreshRateCount = 0;
|
|
if (XR_FAILED(pfnEnumerateDisplayRefreshRatesFB(vr_session, 0, &refreshRateCount, NULL)) ||
|
|
refreshRateCount == 0)
|
|
return;
|
|
|
|
float* refreshRates = (float*)malloc(refreshRateCount * sizeof(float));
|
|
if (!refreshRates)
|
|
return;
|
|
|
|
if (XR_SUCCEEDED(pfnEnumerateDisplayRefreshRatesFB(vr_session, refreshRateCount, &refreshRateCount, refreshRates))) {
|
|
char buffer[256];
|
|
size_t used = 0;
|
|
|
|
buffer[0] = '\0';
|
|
for (uint32_t i = 0; i < refreshRateCount && used < sizeof(buffer); i++) {
|
|
int written = snprintf(buffer + used, sizeof(buffer) - used,
|
|
"%s%.2f", i ? ", " : "", refreshRates[i]);
|
|
if (written < 0)
|
|
break;
|
|
used += (size_t)written;
|
|
}
|
|
CONS_Printf("VR: Runtime supported refresh rates: %s Hz.\n", buffer);
|
|
}
|
|
|
|
free(refreshRates);
|
|
}
|
|
|
|
static void openxr_process_visibility_mesh(int eye, uint32_t width, uint32_t height)
|
|
{
|
|
if (!pfnGetVisibilityMaskKHR) return;
|
|
|
|
XrVisibilityMaskKHR mask = {XR_TYPE_VISIBILITY_MASK_KHR};
|
|
XrResult res = pfnGetVisibilityMaskKHR(vr_session, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, eye, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &mask);
|
|
if (XR_SUCCEEDED(res) && mask.vertexCapacityInput == 0) {
|
|
mask.vertexCapacityInput = mask.vertexCountOutput;
|
|
mask.indexCapacityInput = mask.indexCountOutput;
|
|
mask.vertices = (XrVector2f*)malloc(mask.vertexCapacityInput * sizeof(XrVector2f));
|
|
mask.indices = (uint32_t*)malloc(mask.indexCapacityInput * sizeof(uint32_t));
|
|
|
|
res = pfnGetVisibilityMaskKHR(vr_session, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, eye, XR_VISIBILITY_MASK_TYPE_HIDDEN_TRIANGLE_MESH_KHR, &mask);
|
|
if (XR_SUCCEEDED(res) && mask.indexCountOutput > 0) {
|
|
vrVisibleAreaVertexCount[eye] = mask.indexCountOutput;
|
|
float* vertex = vrVisibleAreaVertices[eye] = (float*)malloc(mask.indexCountOutput * 3 * sizeof(float));
|
|
float* uv = vrVisibleAreaUVs[eye] = (float*)malloc(mask.indexCountOutput * 2 * sizeof(float));
|
|
|
|
uint32_t texsize = 512;
|
|
while (texsize < width || texsize < height) texsize <<= 1;
|
|
|
|
float xfix = 1.0f / ((float)texsize / (float)width);
|
|
float yfix = 1.0f / ((float)texsize / (float)height);
|
|
|
|
for (uint32_t i = 0; i < mask.indexCountOutput; i++) {
|
|
uint32_t idx = mask.indices[i];
|
|
vertex[i*3] = (mask.vertices[idx].x - 0.5f) * 2.0f;
|
|
vertex[i*3+1] = (mask.vertices[idx].y - 0.5f) * 2.0f;
|
|
vertex[i*3+2] = 1.0f;
|
|
|
|
uv[i*2] = mask.vertices[idx].x * xfix;
|
|
uv[i*2+1] = mask.vertices[idx].y * yfix;
|
|
}
|
|
}
|
|
free(mask.vertices);
|
|
free(mask.indices);
|
|
}
|
|
}
|
|
|
|
boolean VR_Init(void)
|
|
{
|
|
if (vr_started) return true;
|
|
|
|
// Check for explicit VR command-line flags
|
|
boolean wantVR = (M_CheckParm("-openvr") || M_CheckParm("-vr") || vr_enabled);
|
|
|
|
if (!wantVR)
|
|
{
|
|
CONS_Printf("VR: No -vr flag detected, skipping VR initialization.\n");
|
|
CONS_Printf("VR: Use '-vr' command-line flag to enable OpenXR.\n");
|
|
return false;
|
|
}
|
|
|
|
CONS_Printf("VR: Initializing OpenXR...\n");
|
|
|
|
XrResult res;
|
|
|
|
const boolean visibilityMaskSupported =
|
|
VR_RuntimeExtensionSupported(XR_KHR_VISIBILITY_MASK_EXTENSION_NAME);
|
|
const boolean displayRefreshRateSupported =
|
|
VR_RuntimeExtensionSupported(XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME);
|
|
const char* enabledExtensions[3];
|
|
uint32_t enabledExtensionCount = 0;
|
|
|
|
enabledExtensions[enabledExtensionCount++] = XR_KHR_OPENGL_ENABLE_EXTENSION_NAME;
|
|
if (visibilityMaskSupported)
|
|
enabledExtensions[enabledExtensionCount++] = XR_KHR_VISIBILITY_MASK_EXTENSION_NAME;
|
|
if (displayRefreshRateSupported)
|
|
enabledExtensions[enabledExtensionCount++] = XR_FB_DISPLAY_REFRESH_RATE_EXTENSION_NAME;
|
|
|
|
XrInstanceCreateInfo createInfo = {XR_TYPE_INSTANCE_CREATE_INFO};
|
|
createInfo.next = NULL;
|
|
strncpy(createInfo.applicationInfo.applicationName, "SRB2Kart Blankart", XR_MAX_APPLICATION_NAME_SIZE);
|
|
createInfo.applicationInfo.applicationVersion = 1;
|
|
strncpy(createInfo.applicationInfo.engineName, "SRB2Kart", XR_MAX_ENGINE_NAME_SIZE);
|
|
createInfo.applicationInfo.engineVersion = 1;
|
|
createInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
|
|
createInfo.enabledExtensionCount = enabledExtensionCount;
|
|
createInfo.enabledExtensionNames = enabledExtensions;
|
|
createInfo.createFlags = 0;
|
|
createInfo.enabledApiLayerCount = 0;
|
|
createInfo.enabledApiLayerNames = NULL;
|
|
|
|
res = xrCreateInstance(&createInfo, &vr_instance);
|
|
if (XR_FAILED(res)) {
|
|
CONS_Printf("VR: xrCreateInstance with optional extensions failed (XrResult=%d), retrying with OpenGL only...\n", (int)res);
|
|
createInfo.enabledExtensionCount = 1;
|
|
createInfo.enabledExtensionNames = enabledExtensions;
|
|
displayRefreshRateExtensionEnabled = false;
|
|
res = xrCreateInstance(&createInfo, &vr_instance);
|
|
} else {
|
|
displayRefreshRateExtensionEnabled = displayRefreshRateSupported;
|
|
}
|
|
|
|
if (XR_FAILED(res)) {
|
|
CONS_Printf("VR: Failed to create OpenXR instance (XrResult=%d).\n", (int)res);
|
|
CONS_Printf("VR: Is an OpenXR runtime (Monado, SteamVR, etc.) installed and active?\n");
|
|
CONS_Printf("VR: Check that XR_RUNTIME_JSON is set or /etc/xdg/openxr/1/active_runtime.json exists.\n");
|
|
return false;
|
|
}
|
|
|
|
CONS_Printf("VR: OpenXR instance created successfully.\n");
|
|
|
|
// Print runtime info
|
|
XrInstanceProperties instanceProps = {XR_TYPE_INSTANCE_PROPERTIES};
|
|
instanceProps.next = NULL;
|
|
if (XR_SUCCEEDED(xrGetInstanceProperties(vr_instance, &instanceProps))) {
|
|
CONS_Printf("VR: Runtime: %s (version %u.%u.%u)\n",
|
|
instanceProps.runtimeName,
|
|
XR_VERSION_MAJOR(instanceProps.runtimeVersion),
|
|
XR_VERSION_MINOR(instanceProps.runtimeVersion),
|
|
XR_VERSION_PATCH(instanceProps.runtimeVersion));
|
|
}
|
|
|
|
xrGetInstanceProcAddr(vr_instance, "xrGetOpenGLGraphicsRequirementsKHR", (PFN_xrVoidFunction*)&pfnGetOpenGLGraphicsRequirementsKHR);
|
|
xrGetInstanceProcAddr(vr_instance, "xrGetVisibilityMaskKHR", (PFN_xrVoidFunction*)&pfnGetVisibilityMaskKHR);
|
|
if (displayRefreshRateExtensionEnabled) {
|
|
xrGetInstanceProcAddr(vr_instance, "xrEnumerateDisplayRefreshRatesFB", (PFN_xrVoidFunction*)&pfnEnumerateDisplayRefreshRatesFB);
|
|
xrGetInstanceProcAddr(vr_instance, "xrGetDisplayRefreshRateFB", (PFN_xrVoidFunction*)&pfnGetDisplayRefreshRateFB);
|
|
xrGetInstanceProcAddr(vr_instance, "xrRequestDisplayRefreshRateFB", (PFN_xrVoidFunction*)&pfnRequestDisplayRefreshRateFB);
|
|
}
|
|
|
|
XrSystemGetInfo systemInfo = {XR_TYPE_SYSTEM_GET_INFO};
|
|
systemInfo.next = NULL;
|
|
systemInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
|
|
res = xrGetSystem(vr_instance, &systemInfo, &vr_system_id);
|
|
if (XR_FAILED(res)) {
|
|
CONS_Printf("VR: Failed to get OpenXR system (XrResult=%d). Is a headset connected?\n", (int)res);
|
|
xrDestroyInstance(vr_instance);
|
|
vr_instance = XR_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
|
|
CONS_Printf("VR: OpenXR system acquired (systemId=%llu).\n", (unsigned long long)vr_system_id);
|
|
|
|
if (pfnGetOpenGLGraphicsRequirementsKHR) {
|
|
XrGraphicsRequirementsOpenGLKHR graphicsRequirements = {XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR};
|
|
graphicsRequirements.next = NULL;
|
|
res = pfnGetOpenGLGraphicsRequirementsKHR(vr_instance, vr_system_id, &graphicsRequirements);
|
|
if (XR_SUCCEEDED(res)) {
|
|
CONS_Printf("VR: OpenGL requirements: min=%u.%u max=%u.%u\n",
|
|
XR_VERSION_MAJOR(graphicsRequirements.minApiVersionSupported),
|
|
XR_VERSION_MINOR(graphicsRequirements.minApiVersionSupported),
|
|
XR_VERSION_MAJOR(graphicsRequirements.maxApiVersionSupported),
|
|
XR_VERSION_MINOR(graphicsRequirements.maxApiVersionSupported));
|
|
}
|
|
}
|
|
|
|
// Query the runtime's recommended per-eye resolution before creating the
|
|
// OpenXR session, then resize Blankart's render surface to match it.
|
|
uint32_t viewCount = 0;
|
|
xrEnumerateViewConfigurationViews(vr_instance, vr_system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, 0, &viewCount, NULL);
|
|
|
|
if (viewCount < 2) {
|
|
CONS_Printf("VR: ERROR - Expected 2 views for stereo, got %u.\n", viewCount);
|
|
VR_Shutdown();
|
|
return false;
|
|
}
|
|
|
|
XrViewConfigurationView* configViews = (XrViewConfigurationView*)malloc(viewCount * sizeof(XrViewConfigurationView));
|
|
for (uint32_t i = 0; i < viewCount; i++) {
|
|
configViews[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
|
|
configViews[i].next = NULL;
|
|
}
|
|
xrEnumerateViewConfigurationViews(vr_instance, vr_system_id, XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO, viewCount, &viewCount, configViews);
|
|
|
|
const uint32_t recommendedWidth = configViews[0].recommendedImageRectWidth;
|
|
const uint32_t recommendedHeight = configViews[0].recommendedImageRectHeight;
|
|
const uint32_t maxWidth = configViews[0].maxImageRectWidth;
|
|
const uint32_t maxHeight = configViews[0].maxImageRectHeight;
|
|
const int resolutionMode = VR_StartupResolutionMode();
|
|
const float renderScale = VR_RenderScaleForMode(resolutionMode);
|
|
|
|
vr_render_width = (uint32_t)((float)recommendedWidth * renderScale + 0.5f);
|
|
vr_render_height = (uint32_t)((float)recommendedHeight * renderScale + 0.5f);
|
|
|
|
if (maxWidth > 0 && vr_render_width > maxWidth)
|
|
vr_render_width = maxWidth;
|
|
if (maxHeight > 0 && vr_render_height > maxHeight)
|
|
vr_render_height = maxHeight;
|
|
if (vr_render_width < 1)
|
|
vr_render_width = 1;
|
|
if (vr_render_height < 1)
|
|
vr_render_height = 1;
|
|
|
|
CONS_Printf("VR: Recommended render size: %ux%u (max: %ux%u); using %ux%u (%d%%).\n",
|
|
recommendedWidth, recommendedHeight, maxWidth, maxHeight,
|
|
vr_render_width, vr_render_height, (int)(renderScale * 100.0f + 0.5f));
|
|
free(configViews);
|
|
|
|
if ((uint32_t)vid.width != vr_render_width || (uint32_t)vid.height != vr_render_height) {
|
|
CONS_Printf("VR: Setting game render size to OpenXR eye size %ux%u.\n", vr_render_width, vr_render_height);
|
|
VID_SetMode(VID_GetModeForSize((INT32)vr_render_width, (INT32)vr_render_height));
|
|
}
|
|
|
|
SDL_PropertiesID props = SDL_GetWindowProperties(window);
|
|
if (!props) {
|
|
CONS_Printf("VR: Failed to get SDL window properties!\n");
|
|
xrDestroyInstance(vr_instance);
|
|
vr_instance = XR_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
XrGraphicsBindingOpenGLWin32KHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR};
|
|
graphicsBinding.next = NULL;
|
|
graphicsBinding.hDC = (HDC)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HDC_POINTER, NULL);
|
|
graphicsBinding.hGLRC = (HGLRC)SDL_GL_GetCurrentContext();
|
|
CONS_Printf("VR: Win32 graphics binding: hDC=%p, hGLRC=%p\n", (void*)graphicsBinding.hDC, (void*)graphicsBinding.hGLRC);
|
|
#elif defined(__linux__)
|
|
XrGraphicsBindingOpenGLXlibKHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR};
|
|
graphicsBinding.next = NULL;
|
|
graphicsBinding.xDisplay = (Display*)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL);
|
|
|
|
// Get the GLX context and drawable from the current context
|
|
graphicsBinding.glxContext = glXGetCurrentContext();
|
|
graphicsBinding.glxDrawable = glXGetCurrentDrawable();
|
|
|
|
// Get the visual ID and FB config from the current context
|
|
if (graphicsBinding.xDisplay && graphicsBinding.glxContext) {
|
|
// Query the FB config from the current context
|
|
int fbConfigId = 0;
|
|
glXQueryContext(graphicsBinding.xDisplay, graphicsBinding.glxContext, GLX_FBCONFIG_ID, &fbConfigId);
|
|
|
|
// Find the matching FB config
|
|
int numConfigs = 0;
|
|
int attribs[] = { GLX_FBCONFIG_ID, fbConfigId, None };
|
|
GLXFBConfig* configs = glXChooseFBConfig(graphicsBinding.xDisplay,
|
|
DefaultScreen(graphicsBinding.xDisplay), attribs, &numConfigs);
|
|
if (configs && numConfigs > 0) {
|
|
graphicsBinding.glxFBConfig = configs[0];
|
|
|
|
// Get visual ID from the FB config
|
|
XVisualInfo* vi = glXGetVisualFromFBConfig(graphicsBinding.xDisplay, configs[0]);
|
|
if (vi) {
|
|
graphicsBinding.visualid = vi->visualid;
|
|
XFree(vi);
|
|
}
|
|
XFree(configs);
|
|
} else {
|
|
CONS_Printf("VR: WARNING - Could not find matching GLX FB config.\n");
|
|
graphicsBinding.glxFBConfig = NULL;
|
|
graphicsBinding.visualid = 0;
|
|
}
|
|
}
|
|
|
|
CONS_Printf("VR: Xlib graphics binding: display=%p, context=%p, drawable=%lu, visualid=%lu\n",
|
|
(void*)graphicsBinding.xDisplay,
|
|
(void*)graphicsBinding.glxContext,
|
|
(unsigned long)graphicsBinding.glxDrawable,
|
|
(unsigned long)graphicsBinding.visualid);
|
|
|
|
if (!graphicsBinding.xDisplay || !graphicsBinding.glxContext) {
|
|
CONS_Printf("VR: ERROR - X11 display or GLX context is NULL! Is the game running under X11?\n");
|
|
CONS_Printf("VR: Wayland-native is not yet supported; try running with SDL_VIDEODRIVER=x11\n");
|
|
xrDestroyInstance(vr_instance);
|
|
vr_instance = XR_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
XrSessionCreateInfo sessionCreateInfo = {XR_TYPE_SESSION_CREATE_INFO};
|
|
sessionCreateInfo.next = &graphicsBinding;
|
|
sessionCreateInfo.systemId = vr_system_id;
|
|
sessionCreateInfo.createFlags = 0;
|
|
|
|
res = xrCreateSession(vr_instance, &sessionCreateInfo, &vr_session);
|
|
if (XR_FAILED(res)) {
|
|
CONS_Printf("VR: Failed to create OpenXR session (XrResult=%d).\n", (int)res);
|
|
xrDestroyInstance(vr_instance);
|
|
vr_instance = XR_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
|
|
CONS_Printf("VR: OpenXR session created successfully.\n");
|
|
|
|
// Begin the session
|
|
XrSessionBeginInfo beginInfo = {XR_TYPE_SESSION_BEGIN_INFO};
|
|
beginInfo.next = NULL;
|
|
beginInfo.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
|
|
res = xrBeginSession(vr_session, &beginInfo);
|
|
if (XR_FAILED(res)) {
|
|
CONS_Printf("VR: Failed to begin OpenXR session (XrResult=%d).\n", (int)res);
|
|
xrDestroySession(vr_session);
|
|
vr_session = XR_NULL_HANDLE;
|
|
xrDestroyInstance(vr_instance);
|
|
vr_instance = XR_NULL_HANDLE;
|
|
return false;
|
|
}
|
|
|
|
CONS_Printf("VR: OpenXR session begun.\n");
|
|
VR_ConfigureDisplayRefreshRate();
|
|
|
|
XrReferenceSpaceCreateInfo spaceCreateInfo = {XR_TYPE_REFERENCE_SPACE_CREATE_INFO};
|
|
spaceCreateInfo.next = NULL;
|
|
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
|
|
spaceCreateInfo.poseInReferenceSpace.orientation.w = 1.0f;
|
|
spaceCreateInfo.poseInReferenceSpace.orientation.x = 0.0f;
|
|
spaceCreateInfo.poseInReferenceSpace.orientation.y = 0.0f;
|
|
spaceCreateInfo.poseInReferenceSpace.orientation.z = 0.0f;
|
|
spaceCreateInfo.poseInReferenceSpace.position.x = 0.0f;
|
|
spaceCreateInfo.poseInReferenceSpace.position.y = 0.0f;
|
|
spaceCreateInfo.poseInReferenceSpace.position.z = 0.0f;
|
|
res = xrCreateReferenceSpace(vr_session, &spaceCreateInfo, &vr_local_space);
|
|
if (XR_FAILED(res)) {
|
|
CONS_Printf("VR: Failed to create local reference space.\n");
|
|
VR_Shutdown();
|
|
return false;
|
|
}
|
|
|
|
spaceCreateInfo.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW;
|
|
res = xrCreateReferenceSpace(vr_session, &spaceCreateInfo, &vr_view_space);
|
|
if (XR_FAILED(res)) {
|
|
CONS_Printf("VR: Failed to create view reference space.\n");
|
|
VR_Shutdown();
|
|
return false;
|
|
}
|
|
|
|
if (!VR_InitSwapchains()) {
|
|
CONS_Printf("VR: Failed to initialize swapchains!\n");
|
|
VR_Shutdown();
|
|
return false;
|
|
}
|
|
|
|
CONS_Printf("VR: Swapchains initialized.\n");
|
|
|
|
if (pfnGetVisibilityMaskKHR) {
|
|
openxr_process_visibility_mesh(0, vr_render_width, vr_render_height);
|
|
openxr_process_visibility_mesh(1, vr_render_width, vr_render_height);
|
|
CONS_Printf("VR: Visibility masks processed.\n");
|
|
} else {
|
|
CONS_Printf("VR: Visibility mask extension not available (cosmetic only).\n");
|
|
}
|
|
|
|
vr_started = true;
|
|
vr_enabled = true;
|
|
#ifdef HWRENDER
|
|
if (gl_shadersavailable) {
|
|
CONS_Printf("VR: Recompiling hardware shaders with OpenXR view uniforms.\n");
|
|
HWR_CompileShaders();
|
|
}
|
|
#endif
|
|
CV_StealthSet(&cv_vidwait, "Off");
|
|
CV_StealthSetValue(&cv_fpscap, -1);
|
|
CV_StealthSet(&cv_ticrate, "Compact");
|
|
CONS_Printf("VR: Desktop vsync disabled and FPS cap set to Unlimited; OpenXR xrWaitFrame will pace rendering.\n");
|
|
CONS_Printf("VR: OpenXR initialization complete! VR is now active.\n");
|
|
return true;
|
|
}
|
|
|
|
void VR_Shutdown(void)
|
|
{
|
|
if (!vr_started && vr_instance == XR_NULL_HANDLE) return;
|
|
|
|
if (vrVisibleAreaVertices[0]) { free(vrVisibleAreaVertices[0]); vrVisibleAreaVertices[0] = NULL; }
|
|
if (vrVisibleAreaVertices[1]) { free(vrVisibleAreaVertices[1]); vrVisibleAreaVertices[1] = NULL; }
|
|
if (vrVisibleAreaUVs[0]) { free(vrVisibleAreaUVs[0]); vrVisibleAreaUVs[0] = NULL; }
|
|
if (vrVisibleAreaUVs[1]) { free(vrVisibleAreaUVs[1]); vrVisibleAreaUVs[1] = NULL; }
|
|
|
|
VR_DestroySwapchains();
|
|
|
|
if (vr_local_space != XR_NULL_HANDLE) xrDestroySpace(vr_local_space);
|
|
if (vr_view_space != XR_NULL_HANDLE) xrDestroySpace(vr_view_space);
|
|
if (vr_session != XR_NULL_HANDLE) xrDestroySession(vr_session);
|
|
if (vr_instance != XR_NULL_HANDLE) xrDestroyInstance(vr_instance);
|
|
|
|
vr_local_space = XR_NULL_HANDLE;
|
|
vr_view_space = XR_NULL_HANDLE;
|
|
vr_session = XR_NULL_HANDLE;
|
|
vr_instance = XR_NULL_HANDLE;
|
|
vr_started = false;
|
|
vr_enabled = false;
|
|
vr_drawing_ui = false;
|
|
#ifdef HWRENDER
|
|
if (gl_shadersavailable)
|
|
HWR_CompileShaders();
|
|
#endif
|
|
displayRefreshRateExtensionEnabled = false;
|
|
pfnEnumerateDisplayRefreshRatesFB = NULL;
|
|
pfnGetDisplayRefreshRateFB = NULL;
|
|
pfnRequestDisplayRefreshRateFB = NULL;
|
|
}
|
|
|
|
void VR_ScaleViewMatrices(float player_scale, int skybox_scale)
|
|
{
|
|
memcpy(vrHMDScaledPoseMatrix, vrHMDPoseMatrix, sizeof(float) * 16);
|
|
vrHMDScaledPoseMatrix[12] *= player_scale;
|
|
vrHMDScaledPoseMatrix[13] *= player_scale;
|
|
vrHMDScaledPoseMatrix[14] *= player_scale;
|
|
|
|
memcpy(vrScaledEyeViewMatrix[0], vrEyeViewMatrix[0], sizeof(float) * 16);
|
|
memcpy(vrScaledEyeViewMatrix[1], vrEyeViewMatrix[1], sizeof(float) * 16);
|
|
|
|
vrScaledEyeViewMatrix[0][12] *= player_scale;
|
|
vrScaledEyeViewMatrix[0][13] *= player_scale;
|
|
vrScaledEyeViewMatrix[0][14] *= player_scale;
|
|
vrScaledEyeViewMatrix[1][12] *= player_scale;
|
|
vrScaledEyeViewMatrix[1][13] *= player_scale;
|
|
vrScaledEyeViewMatrix[1][14] *= player_scale;
|
|
|
|
vrPlayerScale = player_scale;
|
|
|
|
memcpy(vrEyeSkyboxViewMatrix[0], vrScaledEyeViewMatrix[0], sizeof(float) * 16);
|
|
memcpy(vrEyeSkyboxViewMatrix[1], vrScaledEyeViewMatrix[1], sizeof(float) * 16);
|
|
|
|
if(skybox_scale > 0)
|
|
{
|
|
vrEyeSkyboxViewMatrix[0][12] /= skybox_scale;
|
|
vrEyeSkyboxViewMatrix[0][13] /= skybox_scale;
|
|
vrEyeSkyboxViewMatrix[0][14] /= skybox_scale;
|
|
vrEyeSkyboxViewMatrix[1][12] /= skybox_scale;
|
|
vrEyeSkyboxViewMatrix[1][13] /= skybox_scale;
|
|
vrEyeSkyboxViewMatrix[1][14] /= skybox_scale;
|
|
}
|
|
else
|
|
{
|
|
vrEyeSkyboxViewMatrix[0][12] =
|
|
vrEyeSkyboxViewMatrix[0][13] =
|
|
vrEyeSkyboxViewMatrix[0][14] =
|
|
vrEyeSkyboxViewMatrix[1][12] =
|
|
vrEyeSkyboxViewMatrix[1][13] =
|
|
vrEyeSkyboxViewMatrix[1][14] = 0;
|
|
}
|
|
}
|
|
|
|
#endif // HAVE_VR
|