mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-04-21 23:05:49 +00:00
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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
199
src/gpu/xr/SDL_gpu_openxr.c
Normal 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 */
|
||||
30
src/gpu/xr/SDL_gpu_openxr.h
Normal file
30
src/gpu/xr/SDL_gpu_openxr.h
Normal 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);
|
||||
52
src/gpu/xr/SDL_openxr_internal.h
Normal file
52
src/gpu/xr/SDL_openxr_internal.h
Normal 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
414
src/gpu/xr/SDL_openxrdyn.c
Normal 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
|
||||
55
src/gpu/xr/SDL_openxrdyn.h
Normal file
55
src/gpu/xr/SDL_openxrdyn.h
Normal 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_
|
||||
49
src/gpu/xr/SDL_openxrsym.h
Normal file
49
src/gpu/xr/SDL_openxrsym.h
Normal 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
|
||||
Reference in New Issue
Block a user