blankart/src/vr/vr_main.c
2026-05-10 22:50:13 -05:00

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, &currentRefreshRate)) &&
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