Files
SDL/src/gpu/xr/SDL_gpu_openxr.c
2026-02-07 20:42:59 +03:00

206 lines
8.8 KiB
C

/*
Simple DirectMedia Layer
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>
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 HAVE_GPU_OPENXR
#include "SDL_gpu_openxr.h"
#ifdef SDL_PLATFORM_ANDROID
#include "../../core/android/SDL_android.h"
#endif
#define VALIDATION_LAYER_API_NAME "XR_APILAYER_LUNARG_core_validation"
/* On Android, the OpenXR loader is initialized by SDL_OpenXR_LoadLibrary() in SDL_openxrdyn.c
* which must be called before this. That function handles the complex initialization using
* direct SDL_LoadFunction calls to avoid issues with xrGetInstanceProcAddr from runtime
* negotiation not supporting pre-instance calls. This stub is kept for API compatibility. */
#ifdef SDL_PLATFORM_ANDROID
static bool SDL_OPENXR_INTERNAL_InitializeAndroidLoader(void)
{
/* The loader should already be initialized by SDL_OpenXR_LoadLibrary().
* We just verify that xrGetInstanceProcAddr is available. */
if (xrGetInstanceProcAddr == NULL) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "xrGetInstanceProcAddr is NULL - SDL_OpenXR_LoadLibrary was not called first");
return false;
}
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Android OpenXR loader verified (was initialized by SDL_OpenXR_LoadLibrary)");
return true;
}
#endif /* SDL_PLATFORM_ANDROID */
static bool SDL_OPENXR_INTERNAL_ValidationLayerAvailable(void)
{
#ifdef SDL_PLATFORM_ANDROID
/* On Android/Quest, the xrGetInstanceProcAddr obtained through runtime negotiation
* crashes when used for pre-instance global functions. Skip validation layer check. */
return false;
#else
Uint32 apiLayerCount;
if (XR_FAILED(xrEnumerateApiLayerProperties(0, &apiLayerCount, NULL))) {
return false;
}
if (apiLayerCount <= 0) {
return false;
}
XrApiLayerProperties *apiLayerProperties = SDL_stack_alloc(XrApiLayerProperties, apiLayerCount);
if (XR_FAILED(xrEnumerateApiLayerProperties(apiLayerCount, &apiLayerCount, apiLayerProperties))) {
SDL_stack_free(apiLayerProperties);
return false;
}
bool found = false;
for (Uint32 i = 0; i < apiLayerCount; i++) {
XrApiLayerProperties apiLayer = apiLayerProperties[i];
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "api layer available: %s", apiLayer.layerName);
if (SDL_strcmp(apiLayer.layerName, VALIDATION_LAYER_API_NAME) == 0) {
found = true;
break;
}
}
SDL_stack_free(apiLayerProperties);
return found;
#endif
}
XrResult SDL_OPENXR_INTERNAL_GPUInitOpenXR(
bool debugMode,
XrExtensionProperties gpuExtension,
SDL_PropertiesID props,
XrInstance *instance,
XrSystemId *systemId,
XrInstancePfns **xr)
{
XrResult xrResult;
#ifdef SDL_PLATFORM_ANDROID
// Android requires loader initialization before any other XR calls
if (!SDL_OPENXR_INTERNAL_InitializeAndroidLoader()) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to initialize Android OpenXR loader");
return XR_ERROR_INITIALIZATION_FAILED;
}
#endif
bool validationLayersAvailable = SDL_OPENXR_INTERNAL_ValidationLayerAvailable();
Uint32 userApiLayerCount = (Uint32)SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_LAYER_COUNT_NUMBER, 0);
const char *const *userApiLayerNames = SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_LAYER_NAMES_POINTER, NULL);
Uint32 userExtensionCount = (Uint32)SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_EXTENSION_COUNT_NUMBER, 0);
const char *const *userExtensionNames = SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_EXTENSION_NAMES_POINTER, NULL);
// allocate enough space for the validation layer + the user's api layers
const char **apiLayerNames = SDL_stack_alloc(const char *, userApiLayerCount + 1);
SDL_memcpy((void *)apiLayerNames, userApiLayerNames, sizeof(const char *) * (userApiLayerCount));
apiLayerNames[userApiLayerCount] = VALIDATION_LAYER_API_NAME;
// On Android, we need an extra extension for android_create_instance
#ifdef SDL_PLATFORM_ANDROID
const Uint32 platformExtensionCount = 2; // GPU extension + Android create instance
#else
const Uint32 platformExtensionCount = 1; // GPU extension only
#endif
const char **extensionNames = SDL_stack_alloc(const char *, userExtensionCount + platformExtensionCount);
SDL_memcpy((void *)extensionNames, userExtensionNames, sizeof(const char *) * (userExtensionCount));
extensionNames[userExtensionCount] = gpuExtension.extensionName;
#ifdef SDL_PLATFORM_ANDROID
extensionNames[userExtensionCount + 1] = XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME;
#endif
XrInstanceCreateInfo xrInstanceCreateInfo;
SDL_zero(xrInstanceCreateInfo);
xrInstanceCreateInfo.type = XR_TYPE_INSTANCE_CREATE_INFO;
xrInstanceCreateInfo.applicationInfo.apiVersion = SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_VERSION_NUMBER, XR_API_VERSION_1_0);
xrInstanceCreateInfo.enabledApiLayerCount = userApiLayerCount + ((debugMode && validationLayersAvailable) ? 1 : 0); // in debug mode, we enable the validation layer
xrInstanceCreateInfo.enabledApiLayerNames = apiLayerNames;
xrInstanceCreateInfo.enabledExtensionCount = userExtensionCount + platformExtensionCount;
xrInstanceCreateInfo.enabledExtensionNames = extensionNames;
#ifdef SDL_PLATFORM_ANDROID
// Get JNI environment and JavaVM for Android instance creation
JNIEnv *env = (JNIEnv *)SDL_GetAndroidJNIEnv();
JavaVM *vm = NULL;
if (env) {
(*env)->GetJavaVM(env, &vm);
}
void *activity = SDL_GetAndroidActivity();
XrInstanceCreateInfoAndroidKHR instanceCreateInfoAndroid = {};
instanceCreateInfoAndroid.type = XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR;
instanceCreateInfoAndroid.applicationVM = vm;
instanceCreateInfoAndroid.applicationActivity = activity;
xrInstanceCreateInfo.next = &instanceCreateInfoAndroid;
#endif
const char *applicationName = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_NAME_STRING, "SDL Application");
Uint32 applicationVersion = (Uint32)SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_APPLICATION_VERSION_NUMBER, 0);
SDL_strlcpy(xrInstanceCreateInfo.applicationInfo.applicationName, applicationName, XR_MAX_APPLICATION_NAME_SIZE);
xrInstanceCreateInfo.applicationInfo.applicationVersion = applicationVersion;
const char *engineName = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENGINE_NAME_STRING, "SDLGPU");
uint32_t engineVersion = (uint32_t)SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENGINE_VERSION_NUMBER, SDL_VERSION);
SDL_strlcpy(xrInstanceCreateInfo.applicationInfo.engineName, engineName, XR_MAX_APPLICATION_NAME_SIZE);
xrInstanceCreateInfo.applicationInfo.engineVersion = engineVersion;
if ((xrResult = xrCreateInstance(&xrInstanceCreateInfo, instance)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to create OpenXR instance");
SDL_stack_free(apiLayerNames);
SDL_stack_free(extensionNames);
return false;
}
SDL_stack_free(apiLayerNames);
SDL_stack_free(extensionNames);
*xr = SDL_OPENXR_LoadInstanceSymbols(*instance);
if (!*xr) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to load required OpenXR instance symbols");
/* NOTE: we can't actually destroy the created OpenXR instance here,
as we only get that function pointer by loading the instance symbols...
let's just hope that doesn't happen. */
return false;
}
XrSystemGetInfo systemGetInfo;
SDL_zero(systemGetInfo);
systemGetInfo.type = XR_TYPE_SYSTEM_GET_INFO;
systemGetInfo.formFactor = (XrFormFactor)SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_FORM_FACTOR_NUMBER, XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY);
if ((xrResult = (*xr)->xrGetSystem(*instance, &systemGetInfo, systemId)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get OpenXR system");
(*xr)->xrDestroyInstance(*instance);
SDL_free(*xr);
return false;
}
return true;
}
#endif /* HAVE_GPU_OPENXR */