GPU: OpenXR integration (#14837)

Based on Beyley's initial draft in #11601.

Co-authored-by: Beyley Cardellio <ep1cm1n10n123@gmail.com>
Co-authored-by: Ethan Lee <flibitijibibo@gmail.com>
This commit is contained in:
Aaron Benjamin
2026-01-30 17:18:51 -05:00
committed by GitHub
parent 8fa8c331a5
commit 9a91d7236a
36 changed files with 19723 additions and 102 deletions

View File

@@ -607,6 +607,13 @@ static const SDL_GPUBootstrap * SDL_GPUSelectBackend(SDL_PropertiesID props)
return NULL;
}
#ifndef HAVE_GPU_OPENXR
if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, false)) {
SDL_SetError("OpenXR is not enabled in this build of SDL");
return NULL;
}
#endif
gpudriver = SDL_GetHint(SDL_HINT_GPU_DRIVER);
if (gpudriver == NULL) {
gpudriver = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_NAME_STRING, NULL);
@@ -752,6 +759,13 @@ void SDL_DestroyGPUDevice(SDL_GPUDevice *device)
device->DestroyDevice(device);
}
XrResult SDL_DestroyGPUXRSwapchain(SDL_GPUDevice *device, XrSwapchain swapchain, SDL_GPUTexture **swapchainImages)
{
CHECK_DEVICE_MAGIC(device, XR_ERROR_HANDLE_INVALID);
return device->DestroyXRSwapchain(device->driverData, swapchain, swapchainImages);
}
int SDL_GetNumGPUDrivers(void)
{
#ifndef SDL_GPU_DISABLED
@@ -3567,3 +3581,36 @@ SDL_GPUTextureFormat SDL_GetGPUTextureFormatFromPixelFormat(SDL_PixelFormat form
return SDL_GPU_TEXTUREFORMAT_INVALID;
}
}
XrResult SDL_CreateGPUXRSession(
SDL_GPUDevice *device,
const XrSessionCreateInfo *createinfo,
XrSession *session)
{
CHECK_DEVICE_MAGIC(device, XR_ERROR_HANDLE_INVALID);
return device->CreateXRSession(device->driverData, createinfo, session);
}
SDL_GPUTextureFormat* SDL_GetGPUXRSwapchainFormats(
SDL_GPUDevice *device,
XrSession session,
int *num_formats)
{
CHECK_DEVICE_MAGIC(device, NULL);
return device->GetXRSwapchainFormats(device->driverData, session, num_formats);
}
XrResult SDL_CreateGPUXRSwapchain(
SDL_GPUDevice *device,
XrSession session,
const XrSwapchainCreateInfo *createinfo,
SDL_GPUTextureFormat format,
XrSwapchain *swapchain,
SDL_GPUTexture ***textures)
{
CHECK_DEVICE_MAGIC(device, XR_ERROR_HANDLE_INVALID);
return device->CreateXRSwapchain(device->driverData, session, createinfo, format, swapchain, textures);
}

View File

@@ -24,6 +24,8 @@
#ifndef SDL_GPU_DRIVER_H
#define SDL_GPU_DRIVER_H
#include <SDL3/SDL_openxr.h>
// GraphicsDevice Limits
#define MAX_TEXTURE_SAMPLERS_PER_STAGE 16
@@ -669,6 +671,11 @@ struct SDL_GPUDevice
void (*DestroyDevice)(SDL_GPUDevice *device);
XrResult (*DestroyXRSwapchain)(
SDL_GPURenderer *device,
XrSwapchain swapchain,
SDL_GPUTexture **swapchainImages);
SDL_PropertiesID (*GetDeviceProperties)(SDL_GPUDevice *device);
// State Creation
@@ -705,6 +712,24 @@ struct SDL_GPUDevice
Uint32 size,
const char *debugName);
XrResult (*CreateXRSession)(
SDL_GPURenderer *driverData,
const XrSessionCreateInfo *createinfo,
XrSession *session);
SDL_GPUTextureFormat* (*GetXRSwapchainFormats)(
SDL_GPURenderer *driverData,
XrSession session,
int *num_formats);
XrResult (*CreateXRSwapchain)(
SDL_GPURenderer *driverData,
XrSession session,
const XrSwapchainCreateInfo *createinfo,
SDL_GPUTextureFormat format,
XrSwapchain *swapchain,
SDL_GPUTexture ***textures);
// Debug Naming
void (*SetBufferName)(
@@ -1105,6 +1130,7 @@ struct SDL_GPUDevice
result->func = name##_##func;
#define ASSIGN_DRIVER(name) \
ASSIGN_DRIVER_FUNC(DestroyDevice, name) \
ASSIGN_DRIVER_FUNC(DestroyXRSwapchain, name) \
ASSIGN_DRIVER_FUNC(GetDeviceProperties, name) \
ASSIGN_DRIVER_FUNC(CreateComputePipeline, name) \
ASSIGN_DRIVER_FUNC(CreateGraphicsPipeline, name) \
@@ -1113,6 +1139,9 @@ struct SDL_GPUDevice
ASSIGN_DRIVER_FUNC(CreateTexture, name) \
ASSIGN_DRIVER_FUNC(CreateBuffer, name) \
ASSIGN_DRIVER_FUNC(CreateTransferBuffer, name) \
ASSIGN_DRIVER_FUNC(CreateXRSession, name) \
ASSIGN_DRIVER_FUNC(GetXRSwapchainFormats, name) \
ASSIGN_DRIVER_FUNC(CreateXRSwapchain, name) \
ASSIGN_DRIVER_FUNC(SetBufferName, name) \
ASSIGN_DRIVER_FUNC(SetTextureName, name) \
ASSIGN_DRIVER_FUNC(InsertDebugLabel, name) \

View File

@@ -26,6 +26,14 @@
#include "../../events/SDL_windowevents_c.h"
#include "../../core/windows/SDL_windows.h"
#include "../../video/directx/SDL_d3d12.h"
#ifdef HAVE_GPU_OPENXR
#define XR_USE_GRAPHICS_API_D3D12 1
#include "../xr/SDL_openxr_internal.h"
#include "../xr/SDL_openxrdyn.h"
#include "../xr/SDL_gpu_openxr.h"
#endif
#include "../SDL_sysgpu.h"
#ifdef __IDXGIInfoQueue_INTERFACE_DEFINED__
@@ -584,6 +592,30 @@ static DXGI_FORMAT SDLToD3D12_TypelessFormat[] = {
};
SDL_COMPILE_TIME_ASSERT(SDLToD3D12_TypelessFormat, SDL_arraysize(SDLToD3D12_TypelessFormat) == SDL_GPU_TEXTUREFORMAT_MAX_ENUM_VALUE);
#ifdef HAVE_GPU_OPENXR
// For XR sRGB format selection - maps DXGI sRGB formats to SDL formats
typedef struct TextureFormatPair
{
DXGI_FORMAT dxgi;
SDL_GPUTextureFormat sdl;
} TextureFormatPair;
static TextureFormatPair SDLToD3D12_TextureFormat_SrgbOnly[] = {
{ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB },
{ DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB },
{ DXGI_FORMAT_BC1_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB },
{ DXGI_FORMAT_BC2_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB },
{ DXGI_FORMAT_BC3_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB },
{ DXGI_FORMAT_BC7_UNORM_SRGB, SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB },
};
// Forward declarations for XR helper functions
static bool D3D12_INTERNAL_SearchForOpenXrGpuExtension(XrExtensionProperties *found_extension);
static XrResult D3D12_INTERNAL_GetXrGraphicsRequirements(XrInstance instance, XrSystemId systemId, D3D_FEATURE_LEVEL *minimumFeatureLevel, LUID *adapter);
static bool D3D12_INTERNAL_GetAdapterByLuid(LUID luid, IDXGIFactory1 *factory, IDXGIAdapter1 **outAdapter);
static bool D3D12_INTERNAL_FindXRSrgbSwapchain(int64_t *supportedFormats, Uint32 supportedFormatsCount, SDL_GPUTextureFormat *sdlFormat, DXGI_FORMAT *dxgiFormat);
#endif /* HAVE_GPU_OPENXR */
static D3D12_COMPARISON_FUNC SDLToD3D12_CompareOp[] = {
D3D12_COMPARISON_FUNC_NEVER, // INVALID
D3D12_COMPARISON_FUNC_NEVER, // NEVER
@@ -789,6 +821,9 @@ typedef struct D3D12TextureContainer
bool canBeCycled;
char *debugName;
// XR swapchain images are managed by OpenXR runtime
bool externallyManaged;
} D3D12TextureContainer;
// Null views represent by heap = NULL
@@ -819,6 +854,9 @@ struct D3D12Texture
D3D12StagingDescriptor srvHandle;
SDL_AtomicInt referenceCount;
// XR swapchain images are managed by OpenXR runtime
bool externallyManaged;
};
typedef struct D3D12Sampler
@@ -977,6 +1015,13 @@ struct D3D12Renderer
SDL_Mutex *windowLock;
SDL_Mutex *fenceLock;
SDL_Mutex *disposeLock;
#ifdef HAVE_GPU_OPENXR
// OpenXR state
XrInstance xrInstance;
XrSystemId xrSystemId;
XrInstancePfns *xr;
#endif
};
struct D3D12CommandBuffer
@@ -1409,7 +1454,8 @@ static void D3D12_INTERNAL_DestroyTexture(
D3D12_INTERNAL_ReleaseStagingDescriptorHandle(
&texture->srvHandle);
if (texture->resource) {
// XR swapchain images are owned by the OpenXR runtime
if (texture->resource && !texture->externallyManaged) {
ID3D12Resource_Release(texture->resource);
}
@@ -8623,6 +8669,25 @@ static bool D3D12_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props)
return false;
}
#ifdef HAVE_GPU_OPENXR
// Check for OpenXR support if requested
bool xr = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, false);
if (xr) {
if (!SDL_OpenXR_LoadLibrary()) {
SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Failed to load OpenXR");
return false;
}
XrExtensionProperties gpuExtension;
if (!D3D12_INTERNAL_SearchForOpenXrGpuExtension(&gpuExtension)) {
SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "D3D12: Failed to find " XR_KHR_D3D12_ENABLE_EXTENSION_NAME " extension");
SDL_OpenXR_UnloadLibrary();
return false;
}
SDL_OpenXR_UnloadLibrary();
}
#endif
return true;
#endif
}
@@ -8824,6 +8889,403 @@ static void D3D12_INTERNAL_TryInitializeD3D12DebugInfoLogger(D3D12Renderer *rend
}
#endif
// OpenXR D3D12 Support
#ifdef HAVE_GPU_OPENXR
static bool D3D12_INTERNAL_SearchForOpenXrGpuExtension(XrExtensionProperties *found_extension)
{
XrResult result;
Uint32 extension_count;
Uint32 i;
result = xrEnumerateInstanceExtensionProperties(NULL, 0, &extension_count, NULL);
if (result != XR_SUCCESS) {
return false;
}
XrExtensionProperties *extension_properties = (XrExtensionProperties *)SDL_calloc(extension_count, sizeof(XrExtensionProperties));
for (i = 0; i < extension_count; i++) {
extension_properties[i] = (XrExtensionProperties){ XR_TYPE_EXTENSION_PROPERTIES };
}
result = xrEnumerateInstanceExtensionProperties(NULL, extension_count, &extension_count, extension_properties);
if (result != XR_SUCCESS) {
SDL_free(extension_properties);
return false;
}
for (i = 0; i < extension_count; i++) {
XrExtensionProperties extension = extension_properties[i];
if (SDL_strcmp(extension.extensionName, XR_KHR_D3D12_ENABLE_EXTENSION_NAME) == 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Found " XR_KHR_D3D12_ENABLE_EXTENSION_NAME " extension");
*found_extension = extension;
SDL_free(extension_properties);
return true;
}
}
SDL_free(extension_properties);
return false;
}
static XrResult D3D12_INTERNAL_GetXrGraphicsRequirements(XrInstance instance, XrSystemId systemId, D3D_FEATURE_LEVEL *minimumFeatureLevel, LUID *adapter)
{
XrResult xrResult;
PFN_xrGetD3D12GraphicsRequirementsKHR xrGetD3D12GraphicsRequirementsKHR;
if ((xrResult = xrGetInstanceProcAddr(instance, "xrGetD3D12GraphicsRequirementsKHR", (PFN_xrVoidFunction *)&xrGetD3D12GraphicsRequirementsKHR)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get xrGetD3D12GraphicsRequirementsKHR");
return xrResult;
}
XrGraphicsRequirementsD3D12KHR graphicsRequirementsD3D12 = { XR_TYPE_GRAPHICS_REQUIREMENTS_D3D12_KHR };
if ((xrResult = xrGetD3D12GraphicsRequirementsKHR(instance, systemId, &graphicsRequirementsD3D12)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get D3D12 graphics requirements, got OpenXR error %d", xrResult);
return xrResult;
}
*adapter = graphicsRequirementsD3D12.adapterLuid;
*minimumFeatureLevel = graphicsRequirementsD3D12.minFeatureLevel;
return XR_SUCCESS;
}
static bool D3D12_INTERNAL_GetAdapterByLuid(LUID luid, IDXGIFactory1 *factory, IDXGIAdapter1 **outAdapter)
{
HRESULT res;
// We need to iterate over all the adapters in the system to find the one with the matching LUID
for (Uint32 adapterIndex = 0;; adapterIndex++) {
// EnumAdapters1 will fail with DXGI_ERROR_NOT_FOUND when there are no more adapters to enumerate.
IDXGIAdapter1 *adapter;
res = IDXGIFactory1_EnumAdapters1(factory, adapterIndex, &adapter);
if (FAILED(res)) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to get an adapter when iterating, i: %d, res: %ld", adapterIndex, res);
return false;
}
DXGI_ADAPTER_DESC1 adapterDesc;
res = IDXGIAdapter1_GetDesc1(adapter, &adapterDesc);
if (FAILED(res)) {
IDXGIAdapter1_Release(adapter);
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to get description of adapter, i: %d, res %ld", adapterIndex, res);
return false;
}
if (SDL_memcmp(&adapterDesc.AdapterLuid, &luid, sizeof(luid)) == 0) {
*outAdapter = adapter;
return true;
}
IDXGIAdapter1_Release(adapter);
}
}
static bool D3D12_INTERNAL_FindXRSrgbSwapchain(
int64_t *supportedFormats,
Uint32 supportedFormatsCount,
SDL_GPUTextureFormat *sdlFormat,
DXGI_FORMAT *dxgiFormat)
{
for (Uint32 i = 0; i < SDL_arraysize(SDLToD3D12_TextureFormat_SrgbOnly); i++) {
for (Uint32 j = 0; j < supportedFormatsCount; j++) {
if (SDLToD3D12_TextureFormat_SrgbOnly[i].dxgi == supportedFormats[j]) {
*sdlFormat = SDLToD3D12_TextureFormat_SrgbOnly[i].sdl;
*dxgiFormat = SDLToD3D12_TextureFormat_SrgbOnly[i].dxgi;
return true;
}
}
}
return false;
}
#endif /* HAVE_GPU_OPENXR */
static XrResult D3D12_DestroyXRSwapchain(
SDL_GPURenderer *driverData,
XrSwapchain swapchain,
SDL_GPUTexture **swapchainImages)
{
#ifdef HAVE_GPU_OPENXR
XrResult result;
D3D12Renderer *renderer = (D3D12Renderer *)driverData;
D3D12_Wait(driverData);
Uint32 swapchainCount;
result = renderer->xr->xrEnumerateSwapchainImages(swapchain, 0, &swapchainCount, NULL);
if (result != XR_SUCCESS) {
return result;
}
// We always want to destroy the swapchain images, so don't early return if xrDestroySwapchain fails for some reason
for (Uint32 i = 0; i < swapchainCount; i++) {
D3D12Texture *container = (D3D12Texture *)swapchainImages[i];
if (!container->externallyManaged) {
SDL_SetError("Invalid GPU Texture handle.");
return XR_ERROR_HANDLE_INVALID;
}
D3D12_INTERNAL_DestroyTexture(container);
// Free the container now that it's unused
SDL_free(container);
}
SDL_free(swapchainImages);
return renderer->xr->xrDestroySwapchain(swapchain);
#else
(void)driverData;
(void)swapchain;
(void)swapchainImages;
SDL_SetError("SDL not built with OpenXR support");
return XR_ERROR_FUNCTION_UNSUPPORTED;
#endif
}
static SDL_GPUTextureFormat* D3D12_GetXRSwapchainFormats(
SDL_GPURenderer *driverData,
XrSession session,
int *num_formats)
{
#ifdef HAVE_GPU_OPENXR
XrResult result;
Uint32 i, j, num_supported_formats;
int64_t *supported_formats;
D3D12Renderer *renderer = (D3D12Renderer *)driverData;
result = renderer->xr->xrEnumerateSwapchainFormats(session, 0, &num_supported_formats, NULL);
if (result != XR_SUCCESS) {
return NULL;
}
supported_formats = SDL_stack_alloc(int64_t, num_supported_formats);
result = renderer->xr->xrEnumerateSwapchainFormats(session, num_supported_formats, &num_supported_formats, supported_formats);
if (result != XR_SUCCESS) {
SDL_stack_free(supported_formats);
return NULL;
}
// FIXME: For now we're just searching for the optimal format, not all supported formats.
// FIXME: Expand this search for all SDL_GPU formats!
SDL_GPUTextureFormat sdlFormat;
DXGI_FORMAT dxgiFormat = DXGI_FORMAT_UNKNOWN;
// The OpenXR spec recommends applications not submit linear data, so let's try to explicitly find an sRGB swapchain before we search the whole list
if (!D3D12_INTERNAL_FindXRSrgbSwapchain(supported_formats, num_supported_formats, &sdlFormat, &dxgiFormat)) {
// Iterate over all formats the runtime supports
for (i = 0; i < num_supported_formats && dxgiFormat == DXGI_FORMAT_UNKNOWN; i++) {
// Iterate over all formats we support
for (j = 0; j < SDL_arraysize(SDLToD3D12_TextureFormat); j++) {
// Pick the first format the runtime wants that we also support, the runtime should return these in order of preference
if (SDLToD3D12_TextureFormat[j] == supported_formats[i]) {
dxgiFormat = (DXGI_FORMAT)supported_formats[i];
sdlFormat = j;
break;
}
}
}
}
SDL_stack_free(supported_formats);
if (dxgiFormat == DXGI_FORMAT_UNKNOWN) {
SDL_SetError("Failed to find a swapchain format supported by both OpenXR and SDL");
return NULL;
}
SDL_GPUTextureFormat *retval = (SDL_GPUTextureFormat*) SDL_malloc(sizeof(SDL_GPUTextureFormat) * 2);
retval[0] = sdlFormat;
retval[1] = SDL_GPU_TEXTUREFORMAT_INVALID;
*num_formats = 1;
return retval;
#else
SDL_SetError("SDL not built with OpenXR support");
return NULL;
#endif
}
static XrResult D3D12_CreateXRSwapchain(
SDL_GPURenderer *driverData,
XrSession session,
const XrSwapchainCreateInfo *oldCreateInfo,
SDL_GPUTextureFormat format,
XrSwapchain *swapchain,
SDL_GPUTexture ***textures)
{
#ifdef HAVE_GPU_OPENXR
XrResult result;
Uint32 layerIndex, levelIndex;
D3D12Renderer *renderer = (D3D12Renderer *)driverData;
XrSwapchainCreateInfo createInfo = *oldCreateInfo;
createInfo.format = SDLToD3D12_TextureFormat[format];
result = renderer->xr->xrCreateSwapchain(session, &createInfo, swapchain);
if (result != XR_SUCCESS) {
return result;
}
Uint32 swapchainImageCount;
result = renderer->xr->xrEnumerateSwapchainImages(*swapchain, 0, &swapchainImageCount, NULL);
if (result != XR_SUCCESS) {
return result;
}
XrSwapchainImageD3D12KHR *swapchainImages = (XrSwapchainImageD3D12KHR *)SDL_calloc(swapchainImageCount, sizeof(XrSwapchainImageD3D12KHR));
for (layerIndex = 0; layerIndex < swapchainImageCount; layerIndex++) {
swapchainImages[layerIndex].type = XR_TYPE_SWAPCHAIN_IMAGE_D3D12_KHR;
}
result = renderer->xr->xrEnumerateSwapchainImages(*swapchain, swapchainImageCount, &swapchainImageCount, (XrSwapchainImageBaseHeader *)swapchainImages);
if (result != XR_SUCCESS) {
SDL_free(swapchainImages);
return result;
}
D3D12TextureContainer **textureContainers = (D3D12TextureContainer **)SDL_calloc(swapchainImageCount, sizeof(D3D12TextureContainer *));
Uint32 depth = 1;
for (Uint32 idx = 0; idx < swapchainImageCount; idx++) {
ID3D12Resource *d3d12Texture = swapchainImages[idx].texture;
D3D12Texture *texture = (D3D12Texture *)SDL_calloc(1, sizeof(D3D12Texture));
texture->resource = d3d12Texture;
texture->externallyManaged = true;
SDL_SetAtomicInt(&texture->referenceCount, 0);
texture->subresourceCount = createInfo.arraySize * createInfo.mipCount;
texture->subresources = (D3D12TextureSubresource *)SDL_calloc(
texture->subresourceCount,
sizeof(D3D12TextureSubresource));
for (layerIndex = 0; layerIndex < createInfo.arraySize; layerIndex += 1) {
for (levelIndex = 0; levelIndex < createInfo.mipCount; levelIndex += 1) {
Uint32 subresourceIndex = D3D12_INTERNAL_CalcSubresource(
levelIndex,
layerIndex,
createInfo.mipCount);
texture->subresources[subresourceIndex].parent = texture;
texture->subresources[subresourceIndex].layer = layerIndex;
texture->subresources[subresourceIndex].level = levelIndex;
texture->subresources[subresourceIndex].depth = depth;
texture->subresources[subresourceIndex].index = subresourceIndex;
texture->subresources[subresourceIndex].rtvHandles = NULL;
texture->subresources[subresourceIndex].uavHandle.heap = NULL;
texture->subresources[subresourceIndex].dsvHandle.heap = NULL;
if (createInfo.usageFlags & XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT) {
texture->subresources[subresourceIndex].rtvHandles = (D3D12StagingDescriptor *)SDL_calloc(depth, sizeof(D3D12StagingDescriptor));
for (Uint32 depthIndex = 0; depthIndex < depth; depthIndex += 1) {
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
D3D12_INTERNAL_AssignStagingDescriptorHandle(
renderer,
D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
&texture->subresources[subresourceIndex].rtvHandles[depthIndex]);
rtvDesc.Format = SDLToD3D12_TextureFormat[format];
// For XR we typically use 2D array textures for stereo rendering
if (createInfo.arraySize > 1) {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Texture2DArray.MipSlice = levelIndex;
rtvDesc.Texture2DArray.FirstArraySlice = layerIndex;
rtvDesc.Texture2DArray.ArraySize = 1;
rtvDesc.Texture2DArray.PlaneSlice = 0;
} else {
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;
rtvDesc.Texture2D.MipSlice = levelIndex;
rtvDesc.Texture2D.PlaneSlice = 0;
}
ID3D12Device_CreateRenderTargetView(
renderer->device,
texture->resource,
&rtvDesc,
texture->subresources[subresourceIndex].rtvHandles[depthIndex].cpuHandle);
}
}
}
}
textureContainers[idx] = (D3D12TextureContainer *)SDL_malloc(sizeof(D3D12TextureContainer));
D3D12TextureContainer *container = textureContainers[idx];
SDL_zero(container->header.info);
container->header.info.width = createInfo.width;
container->header.info.height = createInfo.height;
container->header.info.format = format;
container->header.info.layer_count_or_depth = createInfo.arraySize;
container->header.info.num_levels = createInfo.mipCount;
container->header.info.sample_count = SDL_GPU_SAMPLECOUNT_1;
container->header.info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
container->externallyManaged = true;
container->canBeCycled = false;
container->activeTexture = texture;
container->textureCapacity = 1;
container->textureCount = 1;
container->textures = (D3D12Texture **)SDL_malloc(
container->textureCapacity * sizeof(D3D12Texture *));
container->textures[0] = container->activeTexture;
container->debugName = NULL;
texture->container = container;
texture->containerIndex = 0;
}
*textures = (SDL_GPUTexture **)textureContainers;
SDL_free(swapchainImages);
return XR_SUCCESS;
#else
(void)driverData;
(void)session;
(void)oldCreateInfo;
(void)format;
(void)swapchain;
(void)textures;
SDL_SetError("SDL not built with OpenXR support");
return XR_ERROR_FUNCTION_UNSUPPORTED;
#endif
}
static XrResult D3D12_CreateXRSession(
SDL_GPURenderer *driverData,
const XrSessionCreateInfo *createinfo,
XrSession *session)
{
#ifdef HAVE_GPU_OPENXR
D3D12Renderer *renderer = (D3D12Renderer *)driverData;
// Copy out the existing next ptr so that we can append it to the end of the chain we create
const void *XR_MAY_ALIAS currentNextPtr = createinfo->next;
XrGraphicsBindingD3D12KHR graphicsBinding = { XR_TYPE_GRAPHICS_BINDING_D3D12_KHR };
graphicsBinding.device = renderer->device;
graphicsBinding.queue = renderer->commandQueue;
graphicsBinding.next = currentNextPtr;
XrSessionCreateInfo sessionCreateInfo = *createinfo;
sessionCreateInfo.systemId = renderer->xrSystemId;
sessionCreateInfo.next = &graphicsBinding;
return renderer->xr->xrCreateSession(renderer->xrInstance, &sessionCreateInfo, session);
#else
(void)driverData;
(void)createinfo;
(void)session;
SDL_SetError("SDL not built with OpenXR support");
return XR_ERROR_FUNCTION_UNSUPPORTED;
#endif
}
static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SDL_PropertiesID props)
{
SDL_GPUDevice *result;
@@ -8942,29 +9404,100 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD
IDXGIFactory5_Release(factory5);
}
// Select the appropriate device for rendering
res = IDXGIFactory4_QueryInterface(
renderer->factory,
D3D_GUID(D3D_IID_IDXGIFactory6),
(void **)&factory6);
if (SUCCEEDED(res)) {
res = IDXGIFactory6_EnumAdapterByGpuPreference(
factory6,
0,
preferLowPower ? DXGI_GPU_PREFERENCE_MINIMUM_POWER : DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
D3D_GUID(D3D_IID_IDXGIAdapter1),
(void **)&renderer->adapter);
IDXGIFactory6_Release(factory6);
} else {
res = IDXGIFactory4_EnumAdapters1(
renderer->factory,
0,
&renderer->adapter);
}
#ifdef HAVE_GPU_OPENXR
bool xr = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, false);
XrInstance *xrInstance = (XrInstance *)SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_INSTANCE_POINTER, NULL);
XrSystemId *xrSystemId = (XrSystemId *)SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_SYSTEM_ID_POINTER, NULL);
D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_0;
if (FAILED(res)) {
D3D12_INTERNAL_DestroyRenderer(renderer);
CHECK_D3D12_ERROR_AND_RETURN("Could not find adapter for D3D12Device", NULL);
if (xr) {
XrExtensionProperties gpuExtension;
if (!xrInstance) {
D3D12_INTERNAL_DestroyRenderer(renderer);
SET_STRING_ERROR_AND_RETURN("You must specify an out pointer for the OpenXR instance", NULL);
}
if (!xrSystemId) {
D3D12_INTERNAL_DestroyRenderer(renderer);
SET_STRING_ERROR_AND_RETURN("You must specify an out pointer for the OpenXR system ID", NULL);
}
if (!SDL_OpenXR_LoadLibrary()) {
D3D12_INTERNAL_DestroyRenderer(renderer);
SET_STRING_ERROR_AND_RETURN("This should have failed in PrepareDevice first!", NULL);
}
if (!D3D12_INTERNAL_SearchForOpenXrGpuExtension(&gpuExtension)) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to find a compatible OpenXR D3D12 extension");
SDL_OpenXR_UnloadLibrary();
D3D12_INTERNAL_DestroyRenderer(renderer);
SET_STRING_ERROR_AND_RETURN("Failed to find XR_KHR_D3D12_enable extension", NULL);
}
if (!SDL_OPENXR_INTERNAL_GPUInitOpenXR(debugMode, gpuExtension, props, xrInstance, xrSystemId, &renderer->xr)) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to init OpenXR");
SDL_OpenXR_UnloadLibrary();
D3D12_INTERNAL_DestroyRenderer(renderer);
SET_STRING_ERROR_AND_RETURN("Failed to initialize OpenXR", NULL);
}
renderer->xrInstance = *xrInstance;
renderer->xrSystemId = *xrSystemId;
LUID xrAdapter;
if (D3D12_INTERNAL_GetXrGraphicsRequirements(*xrInstance, *xrSystemId, &featureLevel, &xrAdapter) != XR_SUCCESS) {
SDL_OpenXR_UnloadLibrary();
D3D12_INTERNAL_DestroyRenderer(renderer);
SET_STRING_ERROR_AND_RETURN("Failed to get XR graphics requirements", NULL);
}
// Need factory1 again for adapter enumeration
IDXGIFactory1 *factory1ForXr;
res = pCreateDXGIFactory1(
&D3D_IID_IDXGIFactory1,
(void **)&factory1ForXr);
if (FAILED(res)) {
SDL_OpenXR_UnloadLibrary();
D3D12_INTERNAL_DestroyRenderer(renderer);
CHECK_D3D12_ERROR_AND_RETURN("Could not create DXGIFactory for XR adapter lookup", NULL);
}
if (!D3D12_INTERNAL_GetAdapterByLuid(xrAdapter, factory1ForXr, &renderer->adapter)) {
IDXGIFactory1_Release(factory1ForXr);
SDL_OpenXR_UnloadLibrary();
D3D12_INTERNAL_DestroyRenderer(renderer);
SET_STRING_ERROR_AND_RETURN("Failed to find adapter matching XR LUID", NULL);
}
IDXGIFactory1_Release(factory1ForXr);
} else
#endif /* HAVE_GPU_OPENXR */
{
// Select the appropriate device for rendering
res = IDXGIFactory4_QueryInterface(
renderer->factory,
D3D_GUID(D3D_IID_IDXGIFactory6),
(void **)&factory6);
if (SUCCEEDED(res)) {
res = IDXGIFactory6_EnumAdapterByGpuPreference(
factory6,
0,
preferLowPower ? DXGI_GPU_PREFERENCE_MINIMUM_POWER : DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
D3D_GUID(D3D_IID_IDXGIAdapter1),
(void **)&renderer->adapter);
IDXGIFactory6_Release(factory6);
} else {
res = IDXGIFactory4_EnumAdapters1(
renderer->factory,
0,
&renderer->adapter);
}
if (FAILED(res)) {
D3D12_INTERNAL_DestroyRenderer(renderer);
CHECK_D3D12_ERROR_AND_RETURN("Could not find adapter for D3D12Device", NULL);
}
}
// Get information about the selected adapter. Used for logging info.

View File

@@ -26,6 +26,12 @@
#include <Metal/Metal.h>
#include <QuartzCore/CoreAnimation.h>
#ifdef HAVE_GPU_OPENXR
#define XR_USE_GRAPHICS_API_METAL 1
#include "../xr/SDL_openxr_internal.h"
#include "../xr/SDL_openxrdyn.h"
#endif
#include "../SDL_sysgpu.h"
// Defines
@@ -4337,6 +4343,10 @@ static bool METAL_SupportsTextureFormat(
static bool METAL_PrepareDriver(SDL_VideoDevice *this, SDL_PropertiesID props)
{
if (SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, false)) {
return false;
}
if (!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_MSL_BOOLEAN, false) &&
!SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_SHADERS_METALLIB_BOOLEAN, false)) {
return false;
@@ -4499,6 +4509,45 @@ static void METAL_INTERNAL_DestroyBlitResources(
SDL_free(renderer->blitPipelines);
}
static XrResult METAL_DestroyXRSwapchain(
SDL_GPURenderer *driverData,
XrSwapchain swapchain,
SDL_GPUTexture **swapchainImages)
{
SDL_SetError("The metal backend does not currently support OpenXR");
return XR_ERROR_FUNCTION_UNSUPPORTED;
}
static SDL_GPUTextureFormat* METAL_GetXRSwapchainFormats(
SDL_GPURenderer *driverData,
XrSession session,
int *num_formats)
{
SDL_SetError("The metal backend does not currently support OpenXR");
return NULL;
}
static XrResult METAL_CreateXRSwapchain(
SDL_GPURenderer *driverData,
XrSession session,
const XrSwapchainCreateInfo *oldCreateInfo,
SDL_GPUTextureFormat format,
XrSwapchain *swapchain,
SDL_GPUTexture ***textures)
{
SDL_SetError("The metal backend does not currently support OpenXR");
return XR_ERROR_FUNCTION_UNSUPPORTED;
}
static XrResult METAL_CreateXRSession(
SDL_GPURenderer *driverData,
const XrSessionCreateInfo *createinfo,
XrSession *session)
{
SDL_SetError("The metal backend does not currently support OpenXR");
return XR_ERROR_FUNCTION_UNSUPPORTED;
}
static SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SDL_PropertiesID props)
{
@autoreleasepool {

View File

@@ -29,6 +29,13 @@
#define VK_NO_PROTOTYPES
#include "../../video/khronos/vulkan/vulkan.h"
#ifdef HAVE_GPU_OPENXR
#define XR_USE_GRAPHICS_API_VULKAN 1
#include "../xr/SDL_openxr_internal.h"
#include "../xr/SDL_openxrdyn.h"
#include "../xr/SDL_gpu_openxr.h"
#endif
#include <SDL3/SDL_vulkan.h>
#include "../SDL_sysgpu.h"
@@ -88,6 +95,37 @@ static VkPresentModeKHR SDLToVK_PresentMode[] = {
VK_PRESENT_MODE_MAILBOX_KHR
};
// NOTE: this is behind an ifdef guard because without, it would trigger an "unused variable" error when OpenXR support is disabled
#ifdef HAVE_GPU_OPENXR
typedef struct TextureFormatPair {
VkFormat vk;
SDL_GPUTextureFormat sdl;
} TextureFormatPair;
static TextureFormatPair SDLToVK_TextureFormat_SrgbOnly[] = {
{VK_FORMAT_R8G8B8A8_SRGB, SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB},
{VK_FORMAT_B8G8R8A8_SRGB, SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM_SRGB},
{VK_FORMAT_BC1_RGBA_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_BC1_RGBA_UNORM_SRGB},
{VK_FORMAT_BC2_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_BC2_RGBA_UNORM_SRGB},
{VK_FORMAT_BC3_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_BC3_RGBA_UNORM_SRGB},
{VK_FORMAT_BC7_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_BC7_RGBA_UNORM_SRGB},
{VK_FORMAT_ASTC_4x4_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_4x4_UNORM_SRGB},
{VK_FORMAT_ASTC_5x4_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_5x4_UNORM_SRGB},
{VK_FORMAT_ASTC_5x5_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_5x5_UNORM_SRGB},
{VK_FORMAT_ASTC_6x5_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_6x5_UNORM_SRGB},
{VK_FORMAT_ASTC_6x6_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_6x6_UNORM_SRGB},
{VK_FORMAT_ASTC_8x5_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_8x5_UNORM_SRGB},
{VK_FORMAT_ASTC_8x6_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_8x6_UNORM_SRGB},
{VK_FORMAT_ASTC_8x8_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_8x8_UNORM_SRGB},
{VK_FORMAT_ASTC_10x5_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_10x5_UNORM_SRGB},
{VK_FORMAT_ASTC_10x6_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_10x6_UNORM_SRGB},
{VK_FORMAT_ASTC_10x8_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_10x8_UNORM_SRGB},
{VK_FORMAT_ASTC_10x10_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_10x10_UNORM_SRGB},
{VK_FORMAT_ASTC_12x10_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_12x10_UNORM_SRGB},
{VK_FORMAT_ASTC_12x12_SRGB_BLOCK, SDL_GPU_TEXTUREFORMAT_ASTC_12x12_UNORM_SRGB},
};
#endif // HAVE_GPU_OPENXR
static VkFormat SDLToVK_TextureFormat[] = {
VK_FORMAT_UNDEFINED, // INVALID
VK_FORMAT_R8_UNORM, // A8_UNORM
@@ -609,6 +647,7 @@ struct VulkanTexture
VulkanTextureSubresource *subresources;
bool markedForDestroy; // so that defrag doesn't double-free
bool externallyManaged; // true for XR swapchain images
SDL_AtomicInt referenceCount;
};
@@ -624,6 +663,7 @@ struct VulkanTextureContainer
char *debugName;
bool canBeCycled;
bool externallyManaged; // true for XR swapchain images
};
typedef enum VulkanBufferUsageMode
@@ -1113,6 +1153,14 @@ struct VulkanRenderer
Uint8 outofBARMemoryWarning;
Uint8 fillModeOnlyWarning;
// OpenXR
Uint32 minimumVkVersion;
#ifdef HAVE_GPU_OPENXR
XrInstance xrInstance; // a non-null instance also states this vk device was created by OpenXR
XrSystemId xrSystemId;
XrInstancePfns *xr;
#endif
bool debugMode;
bool preferLowPower;
bool requireHardwareAcceleration;
@@ -3026,7 +3074,8 @@ static void VULKAN_INTERNAL_DestroyTexture(
NULL);
}
if (texture->image) {
/* Don't free an externally managed VkImage (e.g. XR swapchain images) */
if (texture->image && !texture->externallyManaged) {
renderer->vkDestroyImage(
renderer->logicalDevice,
texture->image,
@@ -11846,7 +11895,31 @@ static Uint8 VULKAN_INTERNAL_CreateInstance(VulkanRenderer *renderer, VulkanFeat
createInfo.enabledLayerCount = 0;
}
vulkanResult = vkCreateInstance(&createInfo, NULL, &renderer->instance);
#ifdef HAVE_GPU_OPENXR
if (renderer->xrInstance) {
XrResult xrResult;
PFN_xrCreateVulkanInstanceKHR xrCreateVulkanInstanceKHR;
if ((xrResult = xrGetInstanceProcAddr(renderer->xrInstance, "xrCreateVulkanInstanceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanInstanceKHR)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get xrCreateVulkanInstanceKHR");
SDL_stack_free((char *)instanceExtensionNames);
return 0;
}
XrVulkanInstanceCreateInfoKHR xrCreateInfo = {XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR};
xrCreateInfo.vulkanCreateInfo = &createInfo;
xrCreateInfo.systemId = renderer->xrSystemId;
xrCreateInfo.pfnGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr();
SDL_assert(xrCreateInfo.pfnGetInstanceProcAddr);
if ((xrResult = xrCreateVulkanInstanceKHR(renderer->xrInstance, &xrCreateInfo, &renderer->instance, &vulkanResult)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to create vulkan instance, reason %d, %d", xrResult, vulkanResult);
SDL_stack_free((char *)instanceExtensionNames);
return 0;
}
} else
#endif // HAVE_GPU_OPENXR
{
vulkanResult = vkCreateInstance(&createInfo, NULL, &renderer->instance);
}
SDL_stack_free((char *)instanceExtensionNames);
if (vulkanResult != VK_SUCCESS) {
@@ -12133,87 +12206,139 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer, V
Uint32 suitableQueueFamilyIndex;
Uint64 highestRank;
vulkanResult = renderer->vkEnumeratePhysicalDevices(
renderer->instance,
&physicalDeviceCount,
NULL);
CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkEnumeratePhysicalDevices, 0);
if (physicalDeviceCount == 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Failed to find any GPUs with Vulkan support");
return 0;
}
physicalDevices = SDL_stack_alloc(VkPhysicalDevice, physicalDeviceCount);
physicalDeviceExtensions = SDL_stack_alloc(VulkanExtensions, physicalDeviceCount);
vulkanResult = renderer->vkEnumeratePhysicalDevices(
renderer->instance,
&physicalDeviceCount,
physicalDevices);
/* This should be impossible to hit, but from what I can tell this can
* be triggered not because the array is too small, but because there
* were drivers that turned out to be bogus, so this is the loader's way
* of telling us that the list is now smaller than expected :shrug:
*/
if (vulkanResult == VK_INCOMPLETE) {
SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "vkEnumeratePhysicalDevices returned VK_INCOMPLETE, will keep trying anyway...");
vulkanResult = VK_SUCCESS;
}
if (vulkanResult != VK_SUCCESS) {
SDL_LogWarn(
SDL_LOG_CATEGORY_GPU,
"vkEnumeratePhysicalDevices failed: %s",
VkErrorMessages(vulkanResult));
SDL_stack_free(physicalDevices);
SDL_stack_free(physicalDeviceExtensions);
return 0;
}
// Any suitable device will do, but we'd like the best
suitableIndex = -1;
suitableQueueFamilyIndex = 0;
highestRank = 0;
for (i = 0; i < physicalDeviceCount; i += 1) {
#ifdef HAVE_GPU_OPENXR
// When XR is enabled, let the OpenXR runtime choose the physical device
if (renderer->xrInstance) {
XrResult xrResult;
VulkanExtensions xrPhysicalDeviceExtensions;
Uint32 queueFamilyIndex;
Uint64 deviceRank;
PFN_xrGetVulkanGraphicsDevice2KHR xrGetVulkanGraphicsDevice2KHR;
xrResult = xrGetInstanceProcAddr(
renderer->xrInstance,
"xrGetVulkanGraphicsDevice2KHR",
(PFN_xrVoidFunction *)&xrGetVulkanGraphicsDevice2KHR);
if (xrResult != XR_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to get xrGetVulkanGraphicsDevice2KHR, result: %d", xrResult);
return 0;
}
XrVulkanGraphicsDeviceGetInfoKHR graphicsDeviceGetInfo;
SDL_zero(graphicsDeviceGetInfo);
graphicsDeviceGetInfo.type = XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR;
graphicsDeviceGetInfo.systemId = renderer->xrSystemId;
graphicsDeviceGetInfo.vulkanInstance = renderer->instance;
xrResult = xrGetVulkanGraphicsDevice2KHR(
renderer->xrInstance,
&graphicsDeviceGetInfo,
&renderer->physicalDevice);
if (xrResult != XR_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "xrGetVulkanGraphicsDevice2KHR failed, result: %d", xrResult);
return 0;
}
// Verify the XR-chosen device is suitable
if (!VULKAN_INTERNAL_IsDeviceSuitable(
renderer,
features,
physicalDevices[i],
&physicalDeviceExtensions[i],
renderer->physicalDevice,
&xrPhysicalDeviceExtensions,
&queueFamilyIndex)) {
// Device does not meet the minimum requirements, skip it entirely
continue;
SDL_LogError(SDL_LOG_CATEGORY_GPU, "The physical device chosen by the OpenXR runtime is not suitable");
return 0;
}
deviceRank = highestRank;
if (VULKAN_INTERNAL_GetDeviceRank(
renderer,
physicalDevices[i],
&physicalDeviceExtensions[i],
&deviceRank)) {
/* Use this for rendering.
* Note that this may override a previous device that
* supports rendering, but shares the same device rank.
*/
suitableIndex = i;
suitableQueueFamilyIndex = queueFamilyIndex;
highestRank = deviceRank;
}
}
renderer->supports = xrPhysicalDeviceExtensions;
renderer->queueFamilyIndex = queueFamilyIndex;
} else
#endif // HAVE_GPU_OPENXR
{
vulkanResult = renderer->vkEnumeratePhysicalDevices(
renderer->instance,
&physicalDeviceCount,
NULL);
CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkEnumeratePhysicalDevices, 0);
if (physicalDeviceCount == 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Failed to find any GPUs with Vulkan support");
return 0;
}
physicalDevices = SDL_stack_alloc(VkPhysicalDevice, physicalDeviceCount);
physicalDeviceExtensions = SDL_stack_alloc(VulkanExtensions, physicalDeviceCount);
vulkanResult = renderer->vkEnumeratePhysicalDevices(
renderer->instance,
&physicalDeviceCount,
physicalDevices);
/* This should be impossible to hit, but from what I can tell this can
* be triggered not because the array is too small, but because there
* were drivers that turned out to be bogus, so this is the loader's way
* of telling us that the list is now smaller than expected :shrug:
*/
if (vulkanResult == VK_INCOMPLETE) {
SDL_LogWarn(SDL_LOG_CATEGORY_GPU, "vkEnumeratePhysicalDevices returned VK_INCOMPLETE, will keep trying anyway...");
vulkanResult = VK_SUCCESS;
}
if (vulkanResult != VK_SUCCESS) {
SDL_LogWarn(
SDL_LOG_CATEGORY_GPU,
"vkEnumeratePhysicalDevices failed: %s",
VkErrorMessages(vulkanResult));
SDL_stack_free(physicalDevices);
SDL_stack_free(physicalDeviceExtensions);
return 0;
}
// Any suitable device will do, but we'd like the best
suitableIndex = -1;
suitableQueueFamilyIndex = 0;
highestRank = 0;
for (i = 0; i < physicalDeviceCount; i += 1) {
Uint32 queueFamilyIndex;
Uint64 deviceRank;
if (!VULKAN_INTERNAL_IsDeviceSuitable(
renderer,
features,
physicalDevices[i],
&physicalDeviceExtensions[i],
&queueFamilyIndex)) {
// Device does not meet the minimum requirements, skip it entirely
continue;
}
deviceRank = highestRank;
if (VULKAN_INTERNAL_GetDeviceRank(
renderer,
physicalDevices[i],
&physicalDeviceExtensions[i],
&deviceRank)) {
/* Use this for rendering.
* Note that this may override a previous device that
* supports rendering, but shares the same device rank.
*/
suitableIndex = i;
suitableQueueFamilyIndex = queueFamilyIndex;
highestRank = deviceRank;
}
}
if (suitableIndex != -1) {
renderer->supports = physicalDeviceExtensions[suitableIndex];
renderer->physicalDevice = physicalDevices[suitableIndex];
renderer->queueFamilyIndex = suitableQueueFamilyIndex;
} else {
SDL_stack_free(physicalDevices);
SDL_stack_free(physicalDeviceExtensions);
return 0;
}
if (suitableIndex != -1) {
renderer->supports = physicalDeviceExtensions[suitableIndex];
renderer->physicalDevice = physicalDevices[suitableIndex];
renderer->queueFamilyIndex = suitableQueueFamilyIndex;
} else {
SDL_stack_free(physicalDevices);
SDL_stack_free(physicalDeviceExtensions);
return 0;
}
renderer->physicalDeviceProperties.sType =
@@ -12241,8 +12366,6 @@ static Uint8 VULKAN_INTERNAL_DeterminePhysicalDevice(VulkanRenderer *renderer, V
renderer->physicalDevice,
&renderer->memoryProperties);
SDL_stack_free(physicalDevices);
SDL_stack_free(physicalDeviceExtensions);
return 1;
}
@@ -12384,11 +12507,36 @@ static Uint8 VULKAN_INTERNAL_CreateLogicalDevice(
deviceCreateInfo.pEnabledFeatures = &features->desiredVulkan10DeviceFeatures;
}
vulkanResult = renderer->vkCreateDevice(
renderer->physicalDevice,
&deviceCreateInfo,
NULL,
&renderer->logicalDevice);
#ifdef HAVE_GPU_OPENXR
if (renderer->xrInstance) {
XrResult xrResult;
PFN_xrCreateVulkanDeviceKHR xrCreateVulkanDeviceKHR;
if ((xrResult = xrGetInstanceProcAddr(renderer->xrInstance, "xrCreateVulkanDeviceKHR", (PFN_xrVoidFunction *)&xrCreateVulkanDeviceKHR)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get xrCreateVulkanDeviceKHR");
SDL_stack_free((void *)deviceExtensions);
return 0;
}
XrVulkanDeviceCreateInfoKHR xrDeviceCreateInfo = {XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR};
xrDeviceCreateInfo.vulkanCreateInfo = &deviceCreateInfo;
xrDeviceCreateInfo.systemId = renderer->xrSystemId;
xrDeviceCreateInfo.vulkanPhysicalDevice = renderer->physicalDevice;
xrDeviceCreateInfo.pfnGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr();
SDL_assert(xrDeviceCreateInfo.pfnGetInstanceProcAddr);
if ((xrResult = xrCreateVulkanDeviceKHR(renderer->xrInstance, &xrDeviceCreateInfo, &renderer->logicalDevice, &vulkanResult)) != XR_SUCCESS) {
SDL_LogError(SDL_LOG_CATEGORY_GPU, "Failed to create OpenXR Vulkan logical device, result %d, %d", xrResult, vulkanResult);
SDL_stack_free((void *)deviceExtensions);
return 0;
}
} else
#endif // HAVE_GPU_OPENXR
{
vulkanResult = renderer->vkCreateDevice(
renderer->physicalDevice,
&deviceCreateInfo,
NULL,
&renderer->logicalDevice);
}
SDL_stack_free((void *)deviceExtensions);
CHECK_VULKAN_ERROR_AND_RETURN(vulkanResult, vkCreateDevice, 0);
@@ -12487,6 +12635,67 @@ static bool VULKAN_INTERNAL_PrepareVulkan(
return true;
}
#ifdef HAVE_GPU_OPENXR
static bool VULKAN_INTERNAL_SearchForOpenXrGpuExtension(XrExtensionProperties *found_extension)
{
XrResult result;
Uint32 extension_count;
Uint32 i;
result = xrEnumerateInstanceExtensionProperties(NULL, 0, &extension_count, NULL);
if (result != XR_SUCCESS)
return false;
XrExtensionProperties *extension_properties = (XrExtensionProperties *)SDL_calloc(extension_count, sizeof(XrExtensionProperties));
for (i = 0; i < extension_count; i++)
extension_properties[i] = (XrExtensionProperties){XR_TYPE_EXTENSION_PROPERTIES};
result = xrEnumerateInstanceExtensionProperties(NULL, extension_count, &extension_count, extension_properties);
if (result != XR_SUCCESS) {
SDL_free(extension_properties);
return false;
}
for (i = 0; i < extension_count; i++) {
XrExtensionProperties extension = extension_properties[i];
// NOTE: as generally recommended, we support KHR_vulkan_enable2 *only*
// see https://fredemmott.com/blog/2024/11/25/best-practices-for-openxr-api-layers.html
if (SDL_strcmp(extension.extensionName, XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME) == 0) {
SDL_LogInfo(SDL_LOG_CATEGORY_GPU, "Found " XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME " extension");
*found_extension = extension;
SDL_free(extension_properties);
return true;
}
}
SDL_free(extension_properties);
return false;
}
static XrResult VULKAN_INTERNAL_GetXrMinimumVulkanApiVersion(XrVersion *minimumVulkanApiVersion, XrInstance instance, XrSystemId systemId)
{
XrResult xrResult;
PFN_xrGetVulkanGraphicsRequirements2KHR xrGetVulkanGraphicsRequirements2KHR;
if ((xrResult = xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsRequirements2KHR", (PFN_xrVoidFunction *)&xrGetVulkanGraphicsRequirements2KHR)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get xrGetVulkanGraphicsRequirements2KHR");
return xrResult;
}
XrGraphicsRequirementsVulkanKHR graphicsRequirementsVulkan = {XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR};
if ((xrResult = xrGetVulkanGraphicsRequirements2KHR(instance, systemId, &graphicsRequirementsVulkan)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get vulkan graphics requirements, got OpenXR error %d", xrResult);
return xrResult;
}
*minimumVulkanApiVersion = graphicsRequirementsVulkan.minApiVersionSupported;
return XR_SUCCESS;
}
#endif // HAVE_GPU_OPENXR
static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props)
{
// Set up dummy VulkanRenderer
@@ -12506,6 +12715,79 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props)
return false;
}
#ifdef HAVE_GPU_OPENXR
XrResult xrResult;
XrInstancePfns *instancePfns = NULL;
XrInstance xrInstance = XR_NULL_HANDLE;
XrSystemId xrSystemId = XR_NULL_HANDLE;
bool xr = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, false);
#ifdef SDL_PLATFORM_ANDROID
/* On Android/Quest, don't test XR in PrepareDriver. The Quest OpenXR runtime
* can't handle having its instance created and destroyed during preparation
* and then recreated during device creation. Just return true for XR mode
* and let CreateDevice do the real work. */
if (xr) {
SDL_Vulkan_UnloadLibrary();
return true;
}
#endif
if (xr) {
if (!SDL_OpenXR_LoadLibrary()) {
SDL_SetError("Failed to load OpenXR loader or a required symbol");
SDL_Vulkan_UnloadLibrary();
return false;
}
XrExtensionProperties gpuExtension;
if (!VULKAN_INTERNAL_SearchForOpenXrGpuExtension(&gpuExtension)) {
SDL_SetError("Failed to find a suitable OpenXR GPU extension.");
SDL_Vulkan_UnloadLibrary();
SDL_OpenXR_UnloadLibrary();
return false;
}
const char *extensionName = gpuExtension.extensionName;
if ((xrResult = xrCreateInstance(&(XrInstanceCreateInfo){
.type = XR_TYPE_INSTANCE_CREATE_INFO,
.applicationInfo = {
.apiVersion = SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_VERSION_NUMBER, XR_API_VERSION_1_0),
.applicationName = "SDL",
},
.enabledExtensionCount = 1,
.enabledExtensionNames = &extensionName,
},
&xrInstance)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to create OpenXR instance");
SDL_Vulkan_UnloadLibrary();
SDL_OpenXR_UnloadLibrary();
return false;
}
instancePfns = SDL_OPENXR_LoadInstanceSymbols(xrInstance);
if (!instancePfns) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to load needed OpenXR instance symbols");
SDL_Vulkan_UnloadLibrary();
SDL_OpenXR_UnloadLibrary();
return false;
}
if ((xrResult = instancePfns->xrGetSystem(xrInstance, &(XrSystemGetInfo){
.type = XR_TYPE_SYSTEM_GET_INFO,
.formFactor = (XrFormFactor)SDL_GetNumberProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_FORM_FACTOR_NUMBER, XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY),
},
&xrSystemId)) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get OpenXR system");
instancePfns->xrDestroyInstance(xrInstance);
SDL_Vulkan_UnloadLibrary();
SDL_OpenXR_UnloadLibrary();
SDL_free(instancePfns);
return false;
}
}
#endif // HAVE_GPU_OPENXR
renderer = (VulkanRenderer *)SDL_calloc(1, sizeof(*renderer));
if (renderer) {
// This needs to be set early for log filtering
@@ -12513,11 +12795,44 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props)
renderer->preferLowPower = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_PREFERLOWPOWER_BOOLEAN, false);
renderer->minimumVkVersion = VK_API_VERSION_1_0;
#ifdef HAVE_GPU_OPENXR
renderer->xrInstance = xrInstance;
renderer->xrSystemId = xrSystemId;
if (xr) {
XrVersion minimumVkVersionXr;
xrResult = VULKAN_INTERNAL_GetXrMinimumVulkanApiVersion(&minimumVkVersionXr, xrInstance, xrSystemId);
if (xrResult != XR_SUCCESS) {
SDL_SetError("Failed to get the minimum supported Vulkan API version.");
instancePfns->xrDestroyInstance(xrInstance);
SDL_Vulkan_UnloadLibrary();
SDL_OpenXR_UnloadLibrary();
SDL_free(instancePfns);
SDL_free(renderer);
return false;
}
renderer->minimumVkVersion = VK_MAKE_API_VERSION(
0,
XR_VERSION_MAJOR(minimumVkVersionXr),
XR_VERSION_MINOR(minimumVkVersionXr),
XR_VERSION_PATCH(minimumVkVersionXr));
}
#endif // HAVE_GPU_OPENXR
result = VULKAN_INTERNAL_PrepareVulkan(renderer, &features, props);
if (result) {
renderer->vkDestroyInstance(renderer->instance, NULL);
}
#ifdef HAVE_GPU_OPENXR
if (instancePfns) {
instancePfns->xrDestroyInstance(xrInstance);
SDL_free(instancePfns);
SDL_OpenXR_UnloadLibrary();
}
#endif // HAVE_GPU_OPENXR
SDL_free(renderer);
}
SDL_Vulkan_UnloadLibrary();
@@ -12525,6 +12840,278 @@ static bool VULKAN_PrepareDriver(SDL_VideoDevice *_this, SDL_PropertiesID props)
return result;
}
static XrResult VULKAN_DestroyXRSwapchain(
SDL_GPURenderer *driverData,
XrSwapchain swapchain,
SDL_GPUTexture **swapchainImages)
{
#ifdef HAVE_GPU_OPENXR
XrResult result;
VulkanRenderer *renderer = (VulkanRenderer *)driverData;
VULKAN_Wait(driverData);
Uint32 swapchainCount;
result = renderer->xr->xrEnumerateSwapchainImages(swapchain, 0, &swapchainCount, NULL);
if (result != XR_SUCCESS) {
return result;
}
// We always want to destroy the swapchain images, so don't early return if xrDestroySwapchain fails for some reason
for (Uint32 i = 0; i < swapchainCount; i++) {
VulkanTextureContainer *container = (VulkanTextureContainer *)swapchainImages[i];
if (!container->externallyManaged) {
SDL_SetError("Invalid GPU Texture handle.");
return XR_ERROR_HANDLE_INVALID;
}
VULKAN_INTERNAL_DestroyTexture(renderer, container->activeTexture);
// Free the container now that it's unused
SDL_free(container);
}
SDL_free(swapchainImages);
return renderer->xr->xrDestroySwapchain(swapchain);
#else
SDL_SetError("SDL not built with OpenXR support");
return XR_ERROR_FUNCTION_UNSUPPORTED;
#endif
}
#ifdef HAVE_GPU_OPENXR
static bool VULKAN_INTERNAL_FindXRSrgbSwapchain(int64_t *supportedFormats, Uint32 numFormats, SDL_GPUTextureFormat *sdlFormat, int64_t *vkFormat)
{
for (Uint32 i = 0; i < SDL_arraysize(SDLToVK_TextureFormat_SrgbOnly); i++) {
for (Uint32 j = 0; j < numFormats; j++) {
if (SDLToVK_TextureFormat_SrgbOnly[i].vk == supportedFormats[j]) {
*sdlFormat = SDLToVK_TextureFormat_SrgbOnly[i].sdl;
*vkFormat = SDLToVK_TextureFormat_SrgbOnly[i].vk;
return true;
}
}
}
return false;
}
#endif // HAVE_GPU_OPENXR
static SDL_GPUTextureFormat* VULKAN_GetXRSwapchainFormats(
SDL_GPURenderer *driverData,
XrSession session,
int *num_formats)
{
#ifdef HAVE_GPU_OPENXR
XrResult result;
Uint32 i, j, num_supported_formats;
int64_t *supported_formats;
VulkanRenderer *renderer = (VulkanRenderer *)driverData;
result = renderer->xr->xrEnumerateSwapchainFormats(session, 0, &num_supported_formats, NULL);
if (result != XR_SUCCESS) return NULL;
supported_formats = SDL_stack_alloc(int64_t, num_supported_formats);
result = renderer->xr->xrEnumerateSwapchainFormats(session, num_supported_formats, &num_supported_formats, supported_formats);
if (result != XR_SUCCESS) {
SDL_stack_free(supported_formats);
return NULL;
}
// FIXME: For now we're just searching for the optimal format, not all supported formats.
// FIXME: Expand this search for all SDL_GPU formats!
SDL_GPUTextureFormat sdlFormat;
int64_t vkFormat = VK_FORMAT_UNDEFINED;
// The OpenXR spec recommends applications not submit linear data, so let's try to explicitly find an sRGB swapchain before we search the whole list
if (!VULKAN_INTERNAL_FindXRSrgbSwapchain(supported_formats, num_supported_formats, &sdlFormat, &vkFormat)) {
// Iterate over all formats the runtime supports
for (i = 0; i < num_supported_formats && vkFormat == VK_FORMAT_UNDEFINED; i++) {
// Iterate over all formats we support
for (j = 0; j < SDL_arraysize(SDLToVK_TextureFormat); j++) {
// Pick the first format the runtime wants that we also support, the runtime should return these in order of preference
if (SDLToVK_TextureFormat[j] == supported_formats[i]) {
vkFormat = supported_formats[i];
sdlFormat = j;
break;
}
}
}
}
SDL_stack_free(supported_formats);
if (vkFormat == VK_FORMAT_UNDEFINED) {
SDL_SetError("Failed to find a swapchain format supported by both OpenXR and SDL");
return NULL;
}
SDL_GPUTextureFormat *retval = (SDL_GPUTextureFormat*) SDL_malloc(sizeof(SDL_GPUTextureFormat) * 2);
retval[0] = sdlFormat;
retval[1] = SDL_GPU_TEXTUREFORMAT_INVALID;
*num_formats = 1;
return retval;
#else
SDL_SetError("SDL not built with OpenXR support");
return NULL;
#endif
}
static XrResult VULKAN_CreateXRSwapchain(
SDL_GPURenderer *driverData,
XrSession session,
const XrSwapchainCreateInfo *oldCreateInfo,
SDL_GPUTextureFormat format,
XrSwapchain *swapchain,
SDL_GPUTexture ***textures)
{
#ifdef HAVE_GPU_OPENXR
XrResult result;
Uint32 i, j;
VulkanRenderer *renderer = (VulkanRenderer *)driverData;
XrSwapchainCreateInfo createInfo = *oldCreateInfo;
createInfo.format = SDLToVK_TextureFormat[format];
result = renderer->xr->xrCreateSwapchain(session, &createInfo, swapchain);
if (result != XR_SUCCESS) return result;
Uint32 swapchainImageCount;
result = renderer->xr->xrEnumerateSwapchainImages(*swapchain, 0, &swapchainImageCount, NULL);
if (result != XR_SUCCESS) return result;
XrSwapchainImageVulkan2KHR *swapchainImages = (XrSwapchainImageVulkan2KHR *)SDL_calloc(swapchainImageCount, sizeof(XrSwapchainImageVulkan2KHR));
for (i = 0; i < swapchainImageCount; i++) swapchainImages[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN2_KHR;
result = renderer->xr->xrEnumerateSwapchainImages(*swapchain, swapchainImageCount, &swapchainImageCount, (XrSwapchainImageBaseHeader *)swapchainImages);
if (result != XR_SUCCESS) {
SDL_free(swapchainImages);
return result;
}
VulkanTextureContainer **textureContainers = (VulkanTextureContainer **)SDL_calloc(swapchainImageCount, sizeof(VulkanTextureContainer *));
for (Uint32 idx = 0; idx < swapchainImageCount; idx++) {
VkImage vkImage = swapchainImages[idx].image;
VulkanTexture *texture = SDL_calloc(1, sizeof(VulkanTexture));
texture->swizzle = SwizzleForSDLFormat(format);
texture->depth = 1;
texture->usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
SDL_SetAtomicInt(&texture->referenceCount, 0);
texture->image = vkImage;
texture->externallyManaged = true;
texture->aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT;
texture->subresourceCount = createInfo.arraySize * createInfo.mipCount;
texture->subresources = SDL_calloc(
texture->subresourceCount,
sizeof(VulkanTextureSubresource));
for (i = 0; i < createInfo.arraySize; i += 1) {
for (j = 0; j < createInfo.mipCount; j += 1) {
Uint32 subresourceIndex = VULKAN_INTERNAL_GetTextureSubresourceIndex(
j,
i,
createInfo.mipCount);
if (createInfo.usageFlags & XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT) {
texture->subresources[subresourceIndex].renderTargetViews = SDL_malloc(sizeof(VkImageView));
if (!VULKAN_INTERNAL_CreateRenderTargetView(
renderer,
texture,
i,
j,
SDLToVK_TextureFormat[format],
texture->swizzle,
&texture->subresources[subresourceIndex].renderTargetViews[0])) {
VULKAN_INTERNAL_DestroyTexture(renderer, texture);
SDL_SetError("Failed to create render target view");
return XR_ERROR_RUNTIME_FAILURE;
}
}
texture->subresources[subresourceIndex].parent = texture;
texture->subresources[subresourceIndex].layer = i;
texture->subresources[subresourceIndex].level = j;
}
}
// Transition to the default barrier state
VulkanCommandBuffer *barrierCommandBuffer = (VulkanCommandBuffer *)VULKAN_AcquireCommandBuffer((SDL_GPURenderer *)renderer);
VULKAN_INTERNAL_TextureTransitionToDefaultUsage(
renderer,
barrierCommandBuffer,
VULKAN_TEXTURE_USAGE_MODE_UNINITIALIZED,
texture);
VULKAN_INTERNAL_TrackTexture(barrierCommandBuffer, texture);
VULKAN_Submit((SDL_GPUCommandBuffer *)barrierCommandBuffer);
textureContainers[idx] = SDL_malloc(sizeof(VulkanTextureContainer));
VulkanTextureContainer *container = textureContainers[idx];
SDL_zero(container->header.info);
container->header.info.width = createInfo.width;
container->header.info.height = createInfo.height;
container->header.info.format = format;
container->header.info.layer_count_or_depth = createInfo.arraySize;
container->header.info.num_levels = createInfo.mipCount;
container->header.info.sample_count = SDL_GPU_SAMPLECOUNT_1;
container->header.info.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET;
container->externallyManaged = true;
container->canBeCycled = false;
container->activeTexture = texture;
container->textureCapacity = 1;
container->textureCount = 1;
container->textures = SDL_malloc(
container->textureCapacity * sizeof(VulkanTexture *));
container->textures[0] = container->activeTexture;
container->debugName = NULL;
}
*textures = (SDL_GPUTexture **)textureContainers;
SDL_free(swapchainImages);
return XR_SUCCESS;
#else
SDL_SetError("SDL not built with OpenXR support");
return XR_ERROR_FUNCTION_UNSUPPORTED;
#endif
}
static XrResult VULKAN_CreateXRSession(
SDL_GPURenderer *driverData,
const XrSessionCreateInfo *createinfo,
XrSession *session)
{
#ifdef HAVE_GPU_OPENXR
VulkanRenderer *renderer = (VulkanRenderer *)driverData;
// Copy out the existing next ptr so that we can append it to the end of the chain we create
const void *XR_MAY_ALIAS currentNextPtr = createinfo->next;
// KHR_vulkan_enable and KHR_vulkan_enable2 share this structure, so we don't need to change any logic here to handle both
XrGraphicsBindingVulkanKHR graphicsBinding = {XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR};
graphicsBinding.instance = renderer->instance;
graphicsBinding.physicalDevice = renderer->physicalDevice;
graphicsBinding.device = renderer->logicalDevice;
graphicsBinding.queueFamilyIndex = renderer->queueFamilyIndex;
graphicsBinding.queueIndex = 0; // we only ever have one queue, so hardcode queue index 0
graphicsBinding.next = currentNextPtr;
XrSessionCreateInfo sessionCreateInfo = *createinfo;
sessionCreateInfo.systemId = renderer->xrSystemId;
sessionCreateInfo.next = &graphicsBinding;
return renderer->xr->xrCreateSession(renderer->xrInstance, &sessionCreateInfo, session);
#else
SDL_SetError("SDL not built with OpenXR support");
return XR_ERROR_FUNCTION_UNSUPPORTED;
#endif
}
static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, SDL_PropertiesID props)
{
VulkanRenderer *renderer;
@@ -12552,9 +13139,83 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S
renderer->debugMode = debugMode;
renderer->preferLowPower = preferLowPower;
renderer->allowedFramesInFlight = 2;
renderer->minimumVkVersion = VK_API_VERSION_1_0;
#ifdef HAVE_GPU_OPENXR
bool xr = SDL_GetBooleanProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_ENABLE_BOOLEAN, false);
XrInstance *xrInstance = SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_INSTANCE_POINTER, NULL);
XrSystemId *xrSystemId = SDL_GetPointerProperty(props, SDL_PROP_GPU_DEVICE_CREATE_XR_SYSTEM_ID_POINTER, NULL);
if (xr) {
XrExtensionProperties gpuExtension;
if (!xrInstance) {
SDL_SetError("You must specify an out pointer for the OpenXR instance");
SDL_free(renderer);
SDL_Vulkan_UnloadLibrary();
return NULL;
}
if (!xrSystemId) {
SDL_SetError("You must specify an out pointer for the OpenXR system ID");
SDL_free(renderer);
SDL_Vulkan_UnloadLibrary();
return NULL;
}
if (!SDL_OpenXR_LoadLibrary()) {
SDL_assert(!"This should have failed in PrepareDevice first!");
SDL_free(renderer);
SDL_Vulkan_UnloadLibrary();
return NULL;
}
if (!VULKAN_INTERNAL_SearchForOpenXrGpuExtension(&gpuExtension)) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to find a compatible OpenXR vulkan extension");
SDL_OpenXR_UnloadLibrary();
SDL_free(renderer);
SDL_Vulkan_UnloadLibrary();
return NULL;
}
if (!SDL_OPENXR_INTERNAL_GPUInitOpenXR(debugMode, gpuExtension, props, xrInstance, xrSystemId, &renderer->xr)) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to init OpenXR");
SDL_OpenXR_UnloadLibrary();
SDL_free(renderer);
SDL_Vulkan_UnloadLibrary();
return NULL;
}
renderer->xrInstance = *xrInstance;
renderer->xrSystemId = *xrSystemId;
XrVersion minimumVulkanApiVersion;
if (VULKAN_INTERNAL_GetXrMinimumVulkanApiVersion(&minimumVulkanApiVersion, *xrInstance, *xrSystemId) != XR_SUCCESS) {
SDL_LogDebug(SDL_LOG_CATEGORY_GPU, "Failed to get OpenXR graphics requirements");
renderer->xr->xrDestroyInstance(*xrInstance);
SDL_OpenXR_UnloadLibrary();
SDL_free(renderer->xr);
SDL_free(renderer);
SDL_Vulkan_UnloadLibrary();
return NULL;
}
renderer->minimumVkVersion = VK_MAKE_VERSION(
XR_VERSION_MAJOR(minimumVulkanApiVersion),
XR_VERSION_MINOR(minimumVulkanApiVersion),
XR_VERSION_PATCH(minimumVulkanApiVersion));
}
#endif // HAVE_GPU_OPENXR
if (!VULKAN_INTERNAL_PrepareVulkan(renderer, &features, props)) {
SET_STRING_ERROR("Failed to initialize Vulkan!");
#ifdef HAVE_GPU_OPENXR
if (xr) {
renderer->xr->xrDestroyInstance(*xrInstance);
SDL_OpenXR_UnloadLibrary();
SDL_free(renderer->xr);
}
#endif // HAVE_GPU_OPENXR
SDL_free(renderer);
SDL_Vulkan_UnloadLibrary();
return NULL;

199
src/gpu/xr/SDL_gpu_openxr.c Normal file
View File

@@ -0,0 +1,199 @@
/*
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 */
bool SDL_OPENXR_INTERNAL_ValidationLayerAvailable()
{
#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;
#endif
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;
}
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(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(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 = { 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 = { 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 = { 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 */

View File

@@ -0,0 +1,30 @@
/*
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_openxrdyn.h"
XrResult SDL_OPENXR_INTERNAL_GPUInitOpenXR(
bool debugMode,
XrExtensionProperties gpuExtension,
SDL_PropertiesID props,
XrInstance *instance,
XrSystemId *systemId,
XrInstancePfns **xr);

View File

@@ -0,0 +1,52 @@
/*
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.
*/
/* This internal header provides access to the vendored OpenXR headers
* without requiring include path modifications in project files.
* Similar to SDL_vulkan_internal.h for Vulkan headers.
*/
#ifndef SDL_openxr_internal_h_
#define SDL_openxr_internal_h_
#include "SDL_internal.h"
/* Define platform-specific OpenXR macros BEFORE including openxr headers */
#ifdef SDL_PLATFORM_ANDROID
#include <jni.h>
#define XR_USE_PLATFORM_ANDROID
#endif
/* Include the vendored OpenXR headers using relative path */
#include "../../video/khronos/openxr/openxr.h"
#include "../../video/khronos/openxr/openxr_platform.h"
/* Compatibility: XR_API_VERSION_1_0 was added in OpenXR 1.1.x */
#ifndef XR_API_VERSION_1_0
#define XR_API_VERSION_1_0 XR_MAKE_VERSION(1, 0, XR_VERSION_PATCH(XR_CURRENT_API_VERSION))
#endif
#define SDL_OPENXR_CHECK_VERSION(x, y, z) \
(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION) > x || \
(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION) == x && XR_VERSION_MINOR(XR_CURRENT_API_VERSION) > y) || \
(XR_VERSION_MAJOR(XR_CURRENT_API_VERSION) == x && XR_VERSION_MINOR(XR_CURRENT_API_VERSION) == y && XR_VERSION_PATCH(XR_CURRENT_API_VERSION) >= z))
#endif /* SDL_openxr_internal_h_ */

414
src/gpu/xr/SDL_openxrdyn.c Normal file
View File

@@ -0,0 +1,414 @@
/*
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"
#include "SDL_openxrdyn.h"
#ifdef HAVE_GPU_OPENXR
#include <SDL3/SDL_dlopennote.h>
#if defined(SDL_PLATFORM_APPLE)
static const char *openxr_library_names[] = { "libopenxr_loader.dylib", NULL };
#elif defined(SDL_PLATFORM_WINDOWS)
static const char *openxr_library_names[] = { "openxr_loader.dll", NULL };
#elif defined(SDL_PLATFORM_ANDROID)
/* On Android, use the Khronos OpenXR loader (libopenxr_loader.so) which properly
* exports xrGetInstanceProcAddr. This is bundled via the Gradle dependency:
* implementation 'org.khronos.openxr:openxr_loader_for_android:X.Y.Z'
*
* The Khronos loader handles runtime discovery internally via the Android broker
* pattern and properly supports all pre-instance global functions.
*
* Note: Do NOT use Meta's forwardloader (libopenxr_forwardloader.so) - it doesn't
* export xrGetInstanceProcAddr directly and the function obtained via runtime
* negotiation crashes on pre-instance calls (e.g., xrEnumerateApiLayerProperties). */
static const char *openxr_library_names[] = { "libopenxr_loader.so", NULL };
#else
static const char *openxr_library_names[] = { "libopenxr_loader.so.1", "libopenxr_loader.so", NULL };
SDL_ELF_NOTE_DLOPEN(
"gpu-openxr",
"Support for OpenXR with SDL_GPU rendering",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
"libopenxr_loader.so.1", "libopenxr_loader.so"
)
#endif
#define DEBUG_DYNAMIC_OPENXR 0
typedef struct
{
SDL_SharedObject *lib;
} openxrdynlib;
static openxrdynlib openxr_loader = { NULL };
#ifndef SDL_PLATFORM_ANDROID
static void *OPENXR_GetSym(const char *fnname, bool *failed)
{
void *fn = SDL_LoadFunction(openxr_loader.lib, fnname);
#if DEBUG_DYNAMIC_OPENXR
if (fn) {
SDL_Log("OPENXR: Found '%s' in %s (%p)\n", fnname, dynlib->libname, fn);
} else {
SDL_Log("OPENXR: Symbol '%s' NOT FOUND!\n", fnname);
}
#endif
return fn;
}
#endif
// Define all the function pointers and wrappers...
#define SDL_OPENXR_SYM(name) PFN_##name OPENXR_##name = NULL;
#include "SDL_openxrsym.h"
static int openxr_load_refcount = 0;
#ifdef SDL_PLATFORM_ANDROID
#include <jni.h>
#include "../../video/khronos/openxr/openxr_platform.h"
/* On Android, we need to initialize the loader with JNI context before use */
static bool openxr_android_loader_initialized = false;
static bool OPENXR_InitializeAndroidLoader(void)
{
XrResult result;
PFN_xrInitializeLoaderKHR initializeLoader = NULL;
PFN_xrGetInstanceProcAddr loaderGetProcAddr = NULL;
JNIEnv *env = NULL;
JavaVM *vm = NULL;
jobject activity = NULL;
if (openxr_android_loader_initialized) {
return true;
}
/* The Khronos OpenXR loader (libopenxr_loader.so) properly exports xrGetInstanceProcAddr.
* Get it directly from the library - this is the standard approach. */
loaderGetProcAddr = (PFN_xrGetInstanceProcAddr)SDL_LoadFunction(openxr_loader.lib, "xrGetInstanceProcAddr");
if (loaderGetProcAddr == NULL) {
SDL_SetError("Failed to get xrGetInstanceProcAddr from OpenXR loader. "
"Make sure you're using the Khronos loader (libopenxr_loader.so), "
"not Meta's forwardloader.");
return false;
}
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Got xrGetInstanceProcAddr from loader: %p", (void*)loaderGetProcAddr);
#endif
/* Get xrInitializeLoaderKHR via xrGetInstanceProcAddr */
result = loaderGetProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", (PFN_xrVoidFunction*)&initializeLoader);
if (XR_FAILED(result) || initializeLoader == NULL) {
SDL_SetError("Failed to get xrInitializeLoaderKHR (result: %d)", (int)result);
return false;
}
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Got xrInitializeLoaderKHR: %p", (void*)initializeLoader);
#endif
/* Get Android environment info from SDL */
env = (JNIEnv *)SDL_GetAndroidJNIEnv();
if (!env) {
SDL_SetError("Failed to get Android JNI environment");
return false;
}
if ((*env)->GetJavaVM(env, &vm) != 0) {
SDL_SetError("Failed to get JavaVM from JNIEnv");
return false;
}
activity = (jobject)SDL_GetAndroidActivity();
if (!activity) {
SDL_SetError("Failed to get Android activity");
return false;
}
XrLoaderInitInfoAndroidKHR loaderInitInfo = {
.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR,
.next = NULL,
.applicationVM = vm,
.applicationContext = activity
};
result = initializeLoader((XrLoaderInitInfoBaseHeaderKHR *)&loaderInitInfo);
if (XR_FAILED(result)) {
SDL_SetError("xrInitializeLoaderKHR failed with result %d", (int)result);
return false;
}
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: xrInitializeLoaderKHR succeeded");
#endif
/* Store the xrGetInstanceProcAddr function - this one properly handles
* all pre-instance calls (unlike Meta's forwardloader runtime negotiation) */
OPENXR_xrGetInstanceProcAddr = loaderGetProcAddr;
xrGetInstanceProcAddr = loaderGetProcAddr;
openxr_android_loader_initialized = true;
return true;
}
#endif /* SDL_PLATFORM_ANDROID */
SDL_DECLSPEC void SDLCALL SDL_OpenXR_UnloadLibrary(void)
{
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: UnloadLibrary called, current refcount=%d", openxr_load_refcount);
#endif
// Don't actually unload if more than one module is using the libs...
if (openxr_load_refcount > 0) {
if (--openxr_load_refcount == 0) {
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Refcount reached 0, unloading library");
#endif
#ifdef SDL_PLATFORM_ANDROID
/* On Android/Quest, don't actually unload the library or reset the loader state.
* The Quest OpenXR runtime doesn't support being re-initialized after teardown.
* xrInitializeLoaderKHR and xrNegotiateLoaderRuntimeInterface must only be called once.
* We keep the library loaded and the loader initialized.
*
* IMPORTANT: We also keep xrGetInstanceProcAddr intact so we can reload other
* function pointers on the next LoadLibrary call. Only NULL out the other symbols. */
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Android - keeping library loaded and loader initialized");
#endif
// Only NULL out non-essential function pointers, keep xrGetInstanceProcAddr
#define SDL_OPENXR_SYM(name) \
if (SDL_strcmp(#name, "xrGetInstanceProcAddr") != 0) { \
OPENXR_##name = NULL; \
}
#include "SDL_openxrsym.h"
#else
// On non-Android, NULL everything and unload
#define SDL_OPENXR_SYM(name) OPENXR_##name = NULL;
#include "SDL_openxrsym.h"
SDL_UnloadObject(openxr_loader.lib);
openxr_loader.lib = NULL;
#endif
}
#if DEBUG_DYNAMIC_OPENXR
else {
SDL_Log("SDL/OpenXR: Refcount is now %d, not unloading", openxr_load_refcount);
}
#endif
}
}
// returns non-zero if all needed symbols were loaded.
SDL_DECLSPEC bool SDLCALL SDL_OpenXR_LoadLibrary(void)
{
bool result = true;
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: LoadLibrary called, current refcount=%d, lib=%p", openxr_load_refcount, (void*)openxr_loader.lib);
#endif
// deal with multiple modules (gpu, openxr, etc) needing these symbols...
if (openxr_load_refcount++ == 0) {
#ifdef SDL_PLATFORM_ANDROID
/* On Android, the library may already be loaded if this is a reload after
* unload (we don't actually unload on Android to preserve runtime state) */
if (openxr_loader.lib == NULL) {
#endif
const char *path_hint = SDL_GetHint(SDL_HINT_OPENXR_LIBRARY);
// If a hint was specified, try that first
if (path_hint && *path_hint) {
openxr_loader.lib = SDL_LoadObject(path_hint);
}
// If no hint or hint failed, try the default library names
if (!openxr_loader.lib) {
for (int i = 0; openxr_library_names[i] != NULL; i++) {
openxr_loader.lib = SDL_LoadObject(openxr_library_names[i]);
if (openxr_loader.lib) {
break;
}
}
}
if (!openxr_loader.lib) {
SDL_SetError("Failed loading OpenXR library");
openxr_load_refcount--;
return false;
}
#if defined(SDL_PLATFORM_ANDROID)
} else {
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Library already loaded (Android reload), skipping SDL_LoadObject");
#endif
}
#endif
#ifdef SDL_PLATFORM_ANDROID
/* On Android, we need to initialize the loader before other functions work.
* OPENXR_InitializeAndroidLoader() will return early if already initialized. */
if (!OPENXR_InitializeAndroidLoader()) {
SDL_UnloadObject(openxr_loader.lib);
openxr_loader.lib = NULL;
openxr_load_refcount--;
return false;
}
#endif
bool failed = false;
#ifdef SDL_PLATFORM_ANDROID
/* On Android with Meta's forwardloader, we need special handling.
* After calling xrInitializeLoaderKHR, the global functions should be available
* either as direct exports from the forwardloader or via xrGetInstanceProcAddr(NULL, ...).
*
* Try getting functions directly from the forwardloader first since they'll go
* through the proper forwarding path. */
XrResult xrResult;
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Loading global functions...");
#endif
/* First try to get functions directly from the forwardloader library */
OPENXR_xrEnumerateApiLayerProperties = (PFN_xrEnumerateApiLayerProperties)SDL_LoadFunction(openxr_loader.lib, "xrEnumerateApiLayerProperties");
OPENXR_xrCreateInstance = (PFN_xrCreateInstance)SDL_LoadFunction(openxr_loader.lib, "xrCreateInstance");
OPENXR_xrEnumerateInstanceExtensionProperties = (PFN_xrEnumerateInstanceExtensionProperties)SDL_LoadFunction(openxr_loader.lib, "xrEnumerateInstanceExtensionProperties");
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Direct symbols - xrEnumerateApiLayerProperties=%p, xrCreateInstance=%p, xrEnumerateInstanceExtensionProperties=%p",
(void*)OPENXR_xrEnumerateApiLayerProperties,
(void*)OPENXR_xrCreateInstance,
(void*)OPENXR_xrEnumerateInstanceExtensionProperties);
#endif
/* If direct loading failed, fall back to xrGetInstanceProcAddr(NULL, ...) */
if (OPENXR_xrEnumerateApiLayerProperties == NULL) {
xrResult = xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrEnumerateApiLayerProperties", (PFN_xrVoidFunction*)&OPENXR_xrEnumerateApiLayerProperties);
if (XR_FAILED(xrResult) || OPENXR_xrEnumerateApiLayerProperties == NULL) {
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Failed to get xrEnumerateApiLayerProperties via xrGetInstanceProcAddr");
#endif
failed = true;
}
}
if (OPENXR_xrCreateInstance == NULL) {
xrResult = xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrCreateInstance", (PFN_xrVoidFunction*)&OPENXR_xrCreateInstance);
if (XR_FAILED(xrResult) || OPENXR_xrCreateInstance == NULL) {
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Failed to get xrCreateInstance via xrGetInstanceProcAddr");
#endif
failed = true;
}
}
if (OPENXR_xrEnumerateInstanceExtensionProperties == NULL) {
xrResult = xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrEnumerateInstanceExtensionProperties", (PFN_xrVoidFunction*)&OPENXR_xrEnumerateInstanceExtensionProperties);
if (XR_FAILED(xrResult) || OPENXR_xrEnumerateInstanceExtensionProperties == NULL) {
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Failed to get xrEnumerateInstanceExtensionProperties via xrGetInstanceProcAddr");
#endif
failed = true;
}
}
#if DEBUG_DYNAMIC_OPENXR
SDL_Log("SDL/OpenXR: Final symbols - xrEnumerateApiLayerProperties=%p, xrCreateInstance=%p, xrEnumerateInstanceExtensionProperties=%p",
(void*)OPENXR_xrEnumerateApiLayerProperties,
(void*)OPENXR_xrCreateInstance,
(void*)OPENXR_xrEnumerateInstanceExtensionProperties);
SDL_Log("SDL/OpenXR: Global functions loading %s", failed ? "FAILED" : "succeeded");
#endif
#else
#define SDL_OPENXR_SYM(name) OPENXR_##name = (PFN_##name)OPENXR_GetSym(#name, &failed);
#include "SDL_openxrsym.h"
#endif
if (failed) {
// in case something got loaded...
SDL_OpenXR_UnloadLibrary();
result = false;
}
}
#if DEBUG_DYNAMIC_OPENXR
else {
SDL_Log("SDL/OpenXR: Library already loaded (refcount=%d), skipping", openxr_load_refcount);
}
#endif
return result;
}
SDL_DECLSPEC PFN_xrGetInstanceProcAddr SDLCALL SDL_OpenXR_GetXrGetInstanceProcAddr(void)
{
if (xrGetInstanceProcAddr == NULL) {
SDL_SetError("The OpenXR loader has not been loaded");
}
return xrGetInstanceProcAddr;
}
XrInstancePfns *SDL_OPENXR_LoadInstanceSymbols(XrInstance instance)
{
XrResult result;
XrInstancePfns *pfns = SDL_calloc(1, sizeof(XrInstancePfns));
#define SDL_OPENXR_INSTANCE_SYM(name) \
result = xrGetInstanceProcAddr(instance, #name, (PFN_xrVoidFunction *)&pfns->name); \
if (result != XR_SUCCESS) { \
SDL_free(pfns); \
return NULL; \
}
#include "SDL_openxrsym.h"
return pfns;
}
#else
SDL_DECLSPEC bool SDLCALL SDL_OpenXR_LoadLibrary(void)
{
return SDL_SetError("OpenXR is not enabled in this build of SDL");
}
SDL_DECLSPEC void SDLCALL SDL_OpenXR_UnloadLibrary(void)
{
SDL_SetError("OpenXR is not enabled in this build of SDL");
}
SDL_DECLSPEC PFN_xrGetInstanceProcAddr SDLCALL SDL_OpenXR_GetXrGetInstanceProcAddr(void)
{
return (PFN_xrGetInstanceProcAddr)SDL_SetError("OpenXR is not enabled in this build of SDL");
}
#endif // HAVE_GPU_OPENXR

View File

@@ -0,0 +1,55 @@
/*
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.
*/
#ifndef SDL_openxrdyn_h_
#define SDL_openxrdyn_h_
/* Use the internal header for vendored OpenXR includes */
#include "SDL_openxr_internal.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct XrInstancePfns
{
#define SDL_OPENXR_INSTANCE_SYM(name) \
PFN_##name name;
#include "SDL_openxrsym.h"
} XrInstancePfns;
extern XrInstancePfns *SDL_OPENXR_LoadInstanceSymbols(XrInstance instance);
/* Define the function pointers */
#define SDL_OPENXR_SYM(name) \
extern PFN_##name OPENXR_##name;
#include "SDL_openxrsym.h"
#define xrGetInstanceProcAddr OPENXR_xrGetInstanceProcAddr
#define xrEnumerateApiLayerProperties OPENXR_xrEnumerateApiLayerProperties
#define xrEnumerateInstanceExtensionProperties OPENXR_xrEnumerateInstanceExtensionProperties
#define xrCreateInstance OPENXR_xrCreateInstance
#ifdef __cplusplus
}
#endif
#endif // SDL_openxrdyn_h_

View File

@@ -0,0 +1,49 @@
/*
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.
*/
/* *INDENT-OFF* */ // clang-format off
#include "../../video/khronos/openxr/openxr.h"
#ifndef SDL_OPENXR_SYM
#define SDL_OPENXR_SYM(name)
#endif
#ifndef SDL_OPENXR_INSTANCE_SYM
#define SDL_OPENXR_INSTANCE_SYM(name)
#endif
SDL_OPENXR_SYM(xrGetInstanceProcAddr)
SDL_OPENXR_SYM(xrEnumerateApiLayerProperties)
SDL_OPENXR_SYM(xrCreateInstance)
SDL_OPENXR_SYM(xrEnumerateInstanceExtensionProperties)
SDL_OPENXR_INSTANCE_SYM(xrEnumerateSwapchainFormats)
SDL_OPENXR_INSTANCE_SYM(xrCreateSession)
SDL_OPENXR_INSTANCE_SYM(xrGetSystem)
SDL_OPENXR_INSTANCE_SYM(xrCreateSwapchain)
SDL_OPENXR_INSTANCE_SYM(xrEnumerateSwapchainImages)
SDL_OPENXR_INSTANCE_SYM(xrDestroyInstance)
SDL_OPENXR_INSTANCE_SYM(xrDestroySwapchain)
#undef SDL_OPENXR_SYM
#undef SDL_OPENXR_INSTANCE_SYM
/* *INDENT-ON* */ // clang-format on