#include #include "initguid.h" #include "openxr_loader.h" #include "wine/unixlib.h" #include "wine/vulkan_driver.h" #include "dxvk-interop.h" #include "vkd3d-proton-interop.h" WINE_DEFAULT_DEBUG_CHANNEL(openxr); static uint32_t g_physdev_vid, g_physdev_pid; static char *g_instance_extensions, *g_device_extensions; static XrVersion api_version = XR_CURRENT_API_VERSION; #define SESSION_TYPE_VULKAN 1 #define SESSION_TYPE_OPENGL 2 #define SESSION_TYPE_D3D11 3 #define SESSION_TYPE_D3D12 4 UINT64 g_vk_create_instance_callback; UINT64 g_vk_create_device_callback; union CompositionLayer { XrCompositionLayerProjection projection; XrCompositionLayerQuad quad; XrCompositionLayerCubeKHR cube; XrCompositionLayerDepthInfoKHR depth_info; XrCompositionLayerCylinderKHR cylinder; XrCompositionLayerEquirectKHR equirect; XrCompositionLayerEquirect2KHR equirect2; }; static CRITICAL_SECTION session_list_lock = {NULL, -1, 0, 0, 0, 0}; static struct list session_list = LIST_INIT(session_list); static wine_XrSession *get_wrapped_XrSession(XrSession native) { wine_XrSession *cursor; EnterCriticalSection(&session_list_lock); LIST_FOR_EACH_ENTRY(cursor, &session_list, wine_XrSession, entry) { if (cursor->host_session == native) { break; } } LeaveCriticalSection(&session_list_lock); if (&cursor->entry == &session_list) { return NULL; } return cursor; } static void parse_extensions(const char *in, uint32_t *out_count, char ***out_strs) { char *iter, *start; char **list, *str = strdup(in); uint32_t extension_count = 0, o = 0; iter = str; while (*iter) { if (*iter++ == ' ') { extension_count++; } } /* count the one ending in NUL */ if (iter != str) { extension_count++; } if (!extension_count) { *out_count = 0; *out_strs = NULL; return; } list = malloc(extension_count * sizeof(char *)); start = iter = str; do { if (*iter == ' ') { *iter = 0; list[o++] = strdup(start); TRACE("added %s to list\n", list[o - 1]); iter++; start = iter; } else if (*iter == 0) { list[o++] = strdup(start); TRACE("added %s to list\n", list[o - 1]); break; } else { iter++; } } while (1); *out_count = extension_count; *out_strs = list; } static BOOL get_vulkan_extensions(void) { /* Linux SteamVR's xrCreateInstance will hang forever if SteamVR hasn't * already been launched by the user. Since that's the only way to tell if * OpenXR is functioning, let's use OpenVR to tell whether SteamVR is * functioning before calling xrCreateInstance. * * This should be removed when SteamVR's bug is fixed. */ DWORD type, value, wait_status = 0, size; LSTATUS status; HANDLE event; HKEY vr_key; if ((status = RegOpenKeyExA(HKEY_CURRENT_USER, "Software\\Wine\\VR", 0, KEY_READ, &vr_key))) { ERR("Could not create key, status %#x.\n", status); return FALSE; } size = sizeof(value); if ((status = RegQueryValueExA(vr_key, "state", NULL, &type, (BYTE *)&value, &size))) { ERR("Could not query value, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } if (type != REG_DWORD) { ERR("Unexpected value type %#x.\n", type); RegCloseKey(vr_key); return FALSE; } if (value) { goto done; } event = CreateEventA(NULL, FALSE, FALSE, NULL); while (1) { if (RegNotifyChangeKeyValue(vr_key, FALSE, REG_NOTIFY_CHANGE_LAST_SET, event, TRUE)) { ERR("Error registering registry change notification.\n"); CloseHandle(event); goto done; } size = sizeof(value); if ((status = RegQueryValueExA(vr_key, "state", NULL, &type, (BYTE *)&value, &size))) { ERR("Could not query value, status %#x.\n", status); CloseHandle(event); goto done; } if (value) { break; } while ((wait_status = WaitForSingleObject(event, 1000)) == WAIT_TIMEOUT) { ERR("VR state wait timeout.\n"); } if (wait_status != WAIT_OBJECT_0) { ERR("Got unexpected wait status %#x.\n", wait_status); break; } } CloseHandle(event); done: if (value == 1) { if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_instance_extensions", NULL, &type, NULL, &size))) { ERR("Error getting openxr_vulkan_instance_extensions, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } g_instance_extensions = malloc(size); if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_instance_extensions", NULL, &type, (BYTE *)g_instance_extensions, &size))) { ERR("Error getting openxr_vulkan_instance_extensions, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_device_extensions", NULL, &type, NULL, &size))) { ERR("Error getting openxr_vulkan_device_extensions, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } g_device_extensions = malloc(size); if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_device_extensions", NULL, &type, (BYTE *)g_device_extensions, &size))) { ERR("Error getting openxr_vulkan_device_extensions, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_device_vid", NULL, &type, (BYTE *)&g_physdev_vid, &size))) { ERR("Error getting openxr_vulkan_device_vid, status: %#x.\n", status); RegCloseKey(vr_key); return FALSE; } if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_device_pid", NULL, &type, (BYTE *)&g_physdev_pid, &size))) { ERR("Error getting openxr_vulkan_device_pid, status: %#x.\n", status); RegCloseKey(vr_key); return FALSE; } } RegCloseKey(vr_key); return value == 1; } static BOOL WINAPI wine_openxr_unix_init(INIT_ONCE *once, void *param, void **context) { struct init_openxr_params params = {.winevulkan = LoadLibraryA("winevulkan.dll")}; if (__wine_init_unix_call() || UNIX_CALL(init, ¶ms)) { return FALSE; } g_vk_create_instance_callback = params.create_instance_callback; g_vk_create_device_callback = params.create_device_callback; return TRUE; } static BOOL wine_openxr_unix_init_once(void) { static INIT_ONCE init_once = INIT_ONCE_STATIC_INIT; return InitOnceExecuteOnce(&init_once, wine_openxr_unix_init, NULL, NULL); } static XrResult wine_openxr_init_once(void) { if (g_instance_extensions || g_device_extensions) { /* already done */ return XR_SUCCESS; } if (!wine_openxr_unix_init_once()) { return XR_ERROR_INITIALIZATION_FAILED; } if (!get_vulkan_extensions()) { return XR_ERROR_INITIALIZATION_FAILED; } return XR_SUCCESS; } static int get_extensions(char **ret_instance_extensions, char **ret_device_extensions, uint32_t *ret_physdev_vid, uint32_t *ret_physdev_pid) { PFN_xrGetVulkanInstanceExtensionsKHR pxrGetVulkanInstanceExtensionsKHR; PFN_xrGetSystem pxrGetSystem; PFN_xrGetVulkanDeviceExtensionsKHR pxrGetVulkanDeviceExtensionsKHR; PFN_xrGetVulkanGraphicsDeviceKHR pxrGetVulkanGraphicsDeviceKHR; PFN_xrGetVulkanGraphicsRequirementsKHR pxrGetVulkanGraphicsRequirementsKHR; PFN_xrGetInstanceProperties pxrGetInstanceProperties; PFN_xrEnumerateViewConfigurations pxrEnumerateViewConfigurations; char *instance_extensions, *device_extensions; uint32_t len, i; XrInstance instance; XrSystemId system; XrResult res; VkInstance vk_instance; VkResult vk_res; VkPhysicalDevice vk_physdev; VkPhysicalDeviceProperties vk_dev_props; static const char *xr_extensions[] = { "XR_KHR_vulkan_enable", }; XrInstanceCreateInfo xrCreateInfo = { .type = XR_TYPE_INSTANCE_CREATE_INFO, .next = NULL, .createFlags = 0, .applicationInfo = { .applicationVersion = 0, .engineVersion = 0, .apiVersion = api_version, }, .enabledApiLayerCount = 0, .enabledApiLayerNames = NULL, .enabledExtensionCount = ARRAY_SIZE(xr_extensions), .enabledExtensionNames = xr_extensions, }; XrInstanceProperties inst_props = { .type = XR_TYPE_INSTANCE_PROPERTIES, }; XrSystemGetInfo system_info = { .type = XR_TYPE_SYSTEM_GET_INFO, .formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY, }; XrGraphicsRequirementsVulkanKHR reqs = { .type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR, }; VkApplicationInfo vk_appinfo = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = NULL, .pApplicationName = "wineopenxr test instance", .applicationVersion = 0, .pEngineName = "wineopenxr test instance", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_MAKE_VERSION(1, 1, 0), }; VkInstanceCreateInfo vk_createinfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = NULL, .flags = 0, .pApplicationInfo = &vk_appinfo, .enabledLayerCount = 0, .ppEnabledLayerNames = NULL, .enabledExtensionCount = 0, .ppEnabledExtensionNames = NULL, }; XrViewConfigurationType *configs; if (!wine_openxr_unix_init_once()) { WARN("unixlib initialization failed.\n"); return XR_ERROR_INITIALIZATION_FAILED; } strcpy(xrCreateInfo.applicationInfo.applicationName, "wineopenxr test instance"); strcpy(xrCreateInfo.applicationInfo.engineName, "wineopenxr test instance"); res = xrCreateInstance(&xrCreateInfo, &instance); if (res == XR_ERROR_API_VERSION_UNSUPPORTED) { WARN("version %#lx unsupported.\n", (long)api_version); api_version = XR_MAKE_VERSION(1, 0, 0); xrCreateInfo.applicationInfo.apiVersion = api_version; res = xrCreateInstance(&xrCreateInfo, &instance); } if (res != XR_SUCCESS) { WARN("xrCreateInstance failed: %d\n", res); return res; } xrGetInstanceProcAddr(instance, "xrGetVulkanInstanceExtensionsKHR", (PFN_xrVoidFunction *)&pxrGetVulkanInstanceExtensionsKHR); xrGetInstanceProcAddr(instance, "xrGetVulkanDeviceExtensionsKHR", (PFN_xrVoidFunction *)&pxrGetVulkanDeviceExtensionsKHR); xrGetInstanceProcAddr(instance, "xrGetSystem", (PFN_xrVoidFunction *)&pxrGetSystem); xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsDeviceKHR", (PFN_xrVoidFunction *)&pxrGetVulkanGraphicsDeviceKHR); xrGetInstanceProcAddr(instance, "xrGetVulkanGraphicsRequirementsKHR", (PFN_xrVoidFunction *)&pxrGetVulkanGraphicsRequirementsKHR); xrGetInstanceProcAddr(instance, "xrGetInstanceProperties", (PFN_xrVoidFunction *)&pxrGetInstanceProperties); xrGetInstanceProcAddr(instance, "xrEnumerateViewConfigurations", (PFN_xrVoidFunction *)&pxrEnumerateViewConfigurations); res = pxrGetInstanceProperties(instance, &inst_props); if (res != XR_SUCCESS) { WARN("xrGetInstanceProperties failed: %d\n", res); } res = pxrGetSystem(instance, &system_info, &system); if (res != XR_SUCCESS) { WARN("xrGetSystem failed: %d\n", res); xrDestroyInstance(instance); return res; } res = pxrEnumerateViewConfigurations(instance, system, 0, &len, NULL); if (res != XR_SUCCESS) { WARN("xrEnumerateViewConfigurations failed: %d\n", res); } configs = malloc(len * sizeof(*configs)); res = pxrEnumerateViewConfigurations(instance, system, len, &len, configs); if (res != XR_SUCCESS) { WARN("xrEnumerateViewConfigurations failed: %d\n", res); } free(configs); res = pxrGetVulkanGraphicsRequirementsKHR(instance, system, &reqs); if (res != XR_SUCCESS) { WARN("xrGetVulkanGraphicsRequirementsKHR failed: %d\n", res); } res = pxrGetVulkanInstanceExtensionsKHR(instance, system, 0, &len, NULL); if (res != XR_SUCCESS) { WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); xrDestroyInstance(instance); return res; } instance_extensions = malloc(len); res = pxrGetVulkanInstanceExtensionsKHR(instance, system, len, &len, instance_extensions); if (res != XR_SUCCESS) { WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); xrDestroyInstance(instance); free(instance_extensions); return res; } parse_extensions(instance_extensions, &vk_createinfo.enabledExtensionCount, (char ***)&vk_createinfo.ppEnabledExtensionNames); vk_res = vkCreateInstance(&vk_createinfo, NULL, &vk_instance); if (vk_res != VK_SUCCESS) { WARN("vkCreateInstance failed: %d\n", vk_res); for (i = 0; i < vk_createinfo.enabledExtensionCount; ++i) { free((void *)vk_createinfo.ppEnabledExtensionNames[i]); } free((void *)vk_createinfo.ppEnabledExtensionNames); xrDestroyInstance(instance); free(instance_extensions); return XR_ERROR_INITIALIZATION_FAILED; } for (i = 0; i < vk_createinfo.enabledExtensionCount; ++i) { free((void *)vk_createinfo.ppEnabledExtensionNames[i]); } free((void *)vk_createinfo.ppEnabledExtensionNames); res = pxrGetVulkanGraphicsDeviceKHR(instance, system, vk_instance, &vk_physdev); if (res != XR_SUCCESS) { WARN("xrGetVulkanGraphicsDeviceKHR failed: %d\n", res); vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); free(instance_extensions); return res; } vkGetPhysicalDeviceProperties(vk_physdev, &vk_dev_props); *ret_physdev_vid = vk_dev_props.vendorID; *ret_physdev_pid = vk_dev_props.deviceID; res = pxrGetVulkanDeviceExtensionsKHR(instance, system, 0, &len, NULL); if (res != XR_SUCCESS) { WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); free(instance_extensions); return res; } device_extensions = malloc(len); res = pxrGetVulkanDeviceExtensionsKHR(instance, system, len, &len, device_extensions); if (res != XR_SUCCESS) { WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); free(instance_extensions); free(device_extensions); return res; } vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); TRACE("Got required instance extensions: %s\n", instance_extensions); TRACE("Got required device extensions: %s\n", device_extensions); *ret_instance_extensions = instance_extensions; *ret_device_extensions = device_extensions; return XR_SUCCESS; } XrResult WINAPI xrCreateInstance(const XrInstanceCreateInfo *createInfo, XrInstance *instance) { wine_XrInstance *wine_instance = calloc(1, sizeof(wine_XrInstance)); struct xrCreateInstance_params params = { .createInfo = createInfo, .instance = &wine_instance->host_instance, }; NTSTATUS _status; _status = UNIX_CALL(xrCreateInstance, ¶ms); assert(!_status && "xrCreateInstance"); if (params.result != XR_SUCCESS) { WARN("xrCreateInstance failed: %d\n", params.result); free(wine_instance); } else { *instance = (XrInstance)wine_instance; } return params.result; } XrResult WINAPI xrDestroyInstance(XrInstance instance) { wine_XrInstance *wine_instance = wine_instance_from_handle(instance); struct xrDestroyInstance_params params = { .instance = instance, }; NTSTATUS _status; TRACE("\n"); _status = UNIX_CALL(xrDestroyInstance, ¶ms); assert(!_status && "xrDestroyInstance"); if (params.result != XR_SUCCESS) { WARN("xrDestroyInstance failed: %d\n", params.result); return params.result; } if (wine_instance->dxvk_device) { wine_instance->dxvk_device->lpVtbl->Release(wine_instance->dxvk_device); } if (wine_instance->d3d12_device) { vkDestroyCommandPool(wine_instance->vk_device, wine_instance->vk_command_pool, NULL); wine_instance->d3d12_device->lpVtbl->Release(wine_instance->d3d12_device); wine_instance->d3d12_queue->lpVtbl->Release(wine_instance->d3d12_queue); } free(wine_instance); return XR_SUCCESS; } /* SteamVR does some internal init during these functions. */ static XrResult do_vulkan_init(wine_XrInstance *wine_instance, VkInstance vk_instance) { XrInstance instance = (XrInstance)wine_instance; char *instance_extensions, *device_extensions; XrGraphicsRequirementsVulkanKHR vk_reqs; XrResult res; uint32_t len; vk_reqs.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN_KHR; vk_reqs.next = NULL; res = xrGetVulkanGraphicsRequirementsKHR(instance, wine_instance->systemId, &vk_reqs); if (res != XR_SUCCESS) { WINE_WARN("xrGetVulkanGraphicsRequirementsKHR failed: %d\n", res); return res; } res = xrGetVulkanInstanceExtensionsKHR(instance, wine_instance->systemId, 0, &len, NULL); if (res != XR_SUCCESS) { WINE_WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); return res; } instance_extensions = malloc(len); res = xrGetVulkanInstanceExtensionsKHR(instance, wine_instance->systemId, len, &len, instance_extensions); if (res != XR_SUCCESS) { WINE_WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); free(instance_extensions); return res; } res = xrGetVulkanGraphicsDeviceKHR(instance, wine_instance->systemId, vk_instance, &wine_instance->vk_phys_dev); if (res != XR_SUCCESS) { WINE_WARN("xrGetVulkanGraphicsDeviceKHR failed: %d\n", res); return res; } res = xrGetVulkanDeviceExtensionsKHR(instance, wine_instance->systemId, 0, &len, NULL); if (res != XR_SUCCESS) { WINE_WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); return res; } device_extensions = malloc(len); res = xrGetVulkanDeviceExtensionsKHR(instance, wine_instance->systemId, len, &len, device_extensions); if (res != XR_SUCCESS) { WINE_WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); free(device_extensions); return res; } free(device_extensions); return XR_SUCCESS; } XrResult WINAPI xrCreateSession(XrInstance instance, const XrSessionCreateInfo *createInfo, XrSession *session) { wine_XrInstance *wine_instance = wine_instance_from_handle(instance); wine_XrSession *wine_session = calloc(1, sizeof(wine_XrSession)); XrSessionCreateInfo our_create_info = *createInfo; XrGraphicsBindingVulkanKHR our_vk_binding; struct xrCreateSession_params params = { .instance = instance, .createInfo = &our_create_info, .session = &wine_session->host_session, }; XrResult res; NTSTATUS _status; uint32_t session_type = 0; TRACE("%p, %p, %p\n", instance, createInfo, session); if (createInfo->next) { switch (((XrBaseInStructure *)createInfo->next)->type) { case XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR /* == XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR */: { session_type = SESSION_TYPE_VULKAN; break; } case XR_TYPE_GRAPHICS_BINDING_D3D11_KHR: { const XrGraphicsBindingD3D11KHR *their_d3d11_binding = (const XrGraphicsBindingD3D11KHR *)createInfo->next; HRESULT hr; hr = ID3D11Device_QueryInterface(their_d3d11_binding->device, &IID_IDXGIVkInteropDevice2, (void **)&wine_instance->dxvk_device); if (FAILED(hr)) { WARN("Given ID3D11Device doesn't support IDXGIVkInteropDevice. Only DXVK is supported.\n"); return XR_ERROR_VALIDATION_FAILURE; } our_vk_binding.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR; our_vk_binding.next = NULL; wine_instance->dxvk_device->lpVtbl->GetVulkanHandles(wine_instance->dxvk_device, &our_vk_binding.instance, &our_vk_binding.physicalDevice, &our_vk_binding.device); wine_instance->dxvk_device->lpVtbl->GetSubmissionQueue2( wine_instance->dxvk_device, NULL, &our_vk_binding.queueIndex, &our_vk_binding.queueFamilyIndex); if ((res = do_vulkan_init(wine_instance, our_vk_binding.instance)) != XR_SUCCESS) { return res; } our_vk_binding.physicalDevice = wine_instance->vk_phys_dev; our_create_info = *createInfo; our_create_info.next = &our_vk_binding; session_type = SESSION_TYPE_D3D11; break; } case XR_TYPE_GRAPHICS_BINDING_D3D12_KHR: { const XrGraphicsBindingD3D12KHR *their_d3d12_binding = createInfo->next; VkCommandPoolCreateInfo command_pool_create_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = 0, .pNext = NULL, }; HRESULT hr; UINT32 queue_index; VkQueueFlags queue_flags; ID3D12DeviceExt1 *device_ext; hr = ID3D12Device_QueryInterface(their_d3d12_binding->device, &IID_ID3D12DXVKInteropDevice, (void **)&wine_instance->d3d12_device); if (FAILED(hr)) { WARN("Given ID3D12Device doesn't support ID3D12DXVKInteropDevice. Only vkd3d-proton is supported.\n"); return XR_ERROR_VALIDATION_FAILURE; } hr = ID3D12Device_QueryInterface(their_d3d12_binding->device, &IID_ID3D12DeviceExt1, (void **)&device_ext); if (FAILED(hr)) { WARN("Given ID3D12Device doesn't support ID3D12DeviceExt1. Only vkd3d-proton is supported.\n"); return XR_ERROR_VALIDATION_FAILURE; } our_vk_binding.type = XR_TYPE_GRAPHICS_BINDING_VULKAN_KHR; our_vk_binding.next = NULL; wine_instance->d3d12_queue = their_d3d12_binding->queue; their_d3d12_binding->queue->lpVtbl->AddRef(their_d3d12_binding->queue); wine_instance->d3d12_device->lpVtbl->GetVulkanHandles(wine_instance->d3d12_device, &our_vk_binding.instance, &our_vk_binding.physicalDevice, &our_vk_binding.device); device_ext->lpVtbl->GetVulkanQueueInfoEx(device_ext, their_d3d12_binding->queue, &wine_instance->vk_queue, &queue_index, &queue_flags, &our_vk_binding.queueFamilyIndex); device_ext->lpVtbl->Release(device_ext); wine_instance->vk_device = our_vk_binding.device; our_vk_binding.queueIndex = queue_index; if ((res = do_vulkan_init(wine_instance, our_vk_binding.instance)) != XR_SUCCESS) { return res; } our_vk_binding.physicalDevice = wine_instance->vk_phys_dev; our_create_info = *createInfo; our_create_info.next = &our_vk_binding; command_pool_create_info.queueFamilyIndex = our_vk_binding.queueFamilyIndex; if (vkCreateCommandPool(wine_instance->vk_device, &command_pool_create_info, NULL, &wine_instance->vk_command_pool) != VK_SUCCESS) { WARN("vkCreateCommandPool failed\n"); return XR_ERROR_RUNTIME_FAILURE; } session_type = SESSION_TYPE_D3D12; break; } default: WARN("Unhandled graphics binding type: %d\n", ((XrBaseInStructure *)createInfo->next)->type); break; } } _status = UNIX_CALL(xrCreateSession, ¶ms); assert(!_status && "xrCreateSession"); if (params.result != XR_SUCCESS) { WARN("xrCreateSession failed: %d\n", res); free(wine_session); return params.result; } wine_session->instance = wine_instance; wine_session->session_type = session_type; EnterCriticalSection(&session_list_lock); list_add_tail(&session_list, &wine_session->entry); LeaveCriticalSection(&session_list_lock); *session = (XrSession)wine_session; return XR_SUCCESS; } XrResult WINAPI xrDestroySession(XrSession session) { wine_XrSession *wine_session = wine_session_from_handle(session); struct xrDestroySession_params params = {.session = session}; NTSTATUS _status; TRACE("%p\n", session); _status = UNIX_CALL(xrDestroySession, ¶ms); assert(!_status && "xrDestroySession"); if (params.result != XR_SUCCESS) { WARN("xrDestroySession failed: %d\n", params.result); return params.result; } free(wine_session); return XR_SUCCESS; } XrResult WINAPI xrPollEvent(XrInstance instance, XrEventDataBuffer *eventData) { struct xrPollEvent_params params = {.instance = instance, .eventData = eventData}; NTSTATUS _status; WINE_TRACE("%p, %p\n", instance, eventData); _status = UNIX_CALL(xrPollEvent, ¶ms); assert(!_status && "xrPollEvent"); WINE_TRACE("eventData->type %#x\n", eventData->type); if (params.result == XR_SUCCESS) { switch (eventData->type) { case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { XrEventDataInteractionProfileChanged *evt = (XrEventDataInteractionProfileChanged *)eventData; evt->session = (XrSession)get_wrapped_XrSession(evt->session); break; } case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { XrEventDataSessionStateChanged *evt = (XrEventDataSessionStateChanged *)eventData; evt->session = (XrSession)get_wrapped_XrSession(evt->session); break; } case XR_TYPE_EVENT_DATA_VISIBILITY_MASK_CHANGED_KHR: { XrEventDataVisibilityMaskChangedKHR *evt = (XrEventDataVisibilityMaskChangedKHR *)eventData; evt->session = (XrSession)get_wrapped_XrSession(evt->session); break; } case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: { XrEventDataReferenceSpaceChangePending *evt = (XrEventDataReferenceSpaceChangePending *)eventData; evt->session = (XrSession)get_wrapped_XrSession(evt->session); break; } case XR_TYPE_EVENT_DATA_USER_PRESENCE_CHANGED_EXT: { XrEventDataUserPresenceChangedEXT *evt = (XrEventDataUserPresenceChangedEXT *)eventData; evt->session = (XrSession)get_wrapped_XrSession(evt->session); break; } case XR_TYPE_EVENT_DATA_LOCALIZATION_CHANGED_ML: { XrEventDataLocalizationChangedML *evt = (XrEventDataLocalizationChangedML *)eventData; evt->session = (XrSession)get_wrapped_XrSession(evt->session); break; } default: break; } } return params.result; } XrResult WINAPI xrGetSystem(XrInstance instance, const XrSystemGetInfo *getInfo, XrSystemId *systemId) { wine_XrInstance *wine_instance = wine_instance_from_handle(instance); struct xrGetSystem_params params = {.instance = instance, .getInfo = getInfo, .systemId = systemId}; NTSTATUS _status; TRACE("%p, %p, %p\n", instance, getInfo, systemId); _status = UNIX_CALL(xrGetSystem, ¶ms); assert(!_status && "xrGetSystem"); if (params.result != XR_SUCCESS) { return params.result; } wine_instance->systemId = *systemId; return params.result; } static BOOL is_vulkan_format_depth(VkFormat format) { switch (format) { case VK_FORMAT_B8G8R8A8_SRGB: case VK_FORMAT_B8G8R8A8_UNORM: case VK_FORMAT_R8G8B8A8_SRGB: case VK_FORMAT_R8G8B8A8_UNORM: case VK_FORMAT_R32G32B32A32_SFLOAT: case VK_FORMAT_R32G32B32_SFLOAT: case VK_FORMAT_R16G16B16A16_SFLOAT: case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return FALSE; case VK_FORMAT_D32_SFLOAT: case VK_FORMAT_D16_UNORM: case VK_FORMAT_D24_UNORM_S8_UINT: case VK_FORMAT_D32_SFLOAT_S8_UINT: return TRUE; default: WARN("Unknown vulkan format %#x", format); return FALSE; } } int64_t map_format_dxgi_to_vulkan(int64_t format) { switch (format) { case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: return VK_FORMAT_B8G8R8A8_SRGB; case DXGI_FORMAT_B8G8R8A8_UNORM: return VK_FORMAT_B8G8R8A8_UNORM; case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: return VK_FORMAT_R8G8B8A8_SRGB; case DXGI_FORMAT_R8G8B8A8_UNORM: return VK_FORMAT_R8G8B8A8_UNORM; case DXGI_FORMAT_R32G32B32A32_FLOAT: return VK_FORMAT_R32G32B32A32_SFLOAT; case DXGI_FORMAT_R32G32B32_FLOAT: return VK_FORMAT_R32G32B32_SFLOAT; case DXGI_FORMAT_R16G16B16A16_FLOAT: return VK_FORMAT_R16G16B16A16_SFLOAT; case DXGI_FORMAT_R10G10B10A2_UNORM: return VK_FORMAT_A2B10G10R10_UNORM_PACK32; case DXGI_FORMAT_D32_FLOAT: return VK_FORMAT_D32_SFLOAT; case DXGI_FORMAT_D16_UNORM: return VK_FORMAT_D16_UNORM; case DXGI_FORMAT_D24_UNORM_S8_UINT: return VK_FORMAT_D24_UNORM_S8_UINT; case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: return VK_FORMAT_D32_SFLOAT_S8_UINT; default: WARN("Unable to map DXGI format (%lu) to Vulkan format\n", (long)format); return VK_FORMAT_UNDEFINED; } } int64_t map_format_vulkan_to_dxgi(int64_t format) { switch (format) { case VK_FORMAT_B8G8R8A8_SRGB: return DXGI_FORMAT_B8G8R8A8_UNORM_SRGB; case VK_FORMAT_B8G8R8A8_UNORM: return DXGI_FORMAT_B8G8R8A8_UNORM; case VK_FORMAT_R8G8B8A8_SRGB: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; case VK_FORMAT_R8G8B8A8_UNORM: return DXGI_FORMAT_R8G8B8A8_UNORM; case VK_FORMAT_R32G32B32A32_SFLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT; case VK_FORMAT_R32G32B32_SFLOAT: return DXGI_FORMAT_R32G32B32_FLOAT; case VK_FORMAT_R16G16B16A16_SFLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT; case VK_FORMAT_A2B10G10R10_UNORM_PACK32: return DXGI_FORMAT_R10G10B10A2_UNORM; case VK_FORMAT_D32_SFLOAT: return DXGI_FORMAT_D32_FLOAT; case VK_FORMAT_D16_UNORM: return DXGI_FORMAT_D16_UNORM; case VK_FORMAT_D24_UNORM_S8_UINT: return DXGI_FORMAT_D24_UNORM_S8_UINT; case VK_FORMAT_D32_SFLOAT_S8_UINT: return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; default: WARN("Unable to map Vulkan format (%lu) to DXGI format\n", (long)format); return DXGI_FORMAT_UNKNOWN; } } XrResult WINAPI xrEnumerateSwapchainFormats(XrSession session, uint32_t formatCapacityInput, uint32_t *formatCountOutput, int64_t *formats) { wine_XrSession *wine_session = wine_session_from_handle(session); uint32_t real_format_count; int64_t *real_formats; uint32_t i, o; struct xrEnumerateSwapchainFormats_params params = { .session = session, .formatCapacityInput = formatCapacityInput, .formatCountOutput = formatCountOutput, .formats = formats, }; NTSTATUS _status; TRACE("%p, %u, %p, %p\n", session, formatCapacityInput, formatCountOutput, formats); if (wine_session->session_type != SESSION_TYPE_D3D11 && wine_session->session_type != SESSION_TYPE_D3D12) { _status = UNIX_CALL(xrEnumerateSwapchainFormats, ¶ms); assert(!_status && "xrEnumerateSwapchainFormats"); return params.result; } params.formatCapacityInput = 0; params.formatCountOutput = &real_format_count; params.formats = NULL; _status = UNIX_CALL(xrEnumerateSwapchainFormats, ¶ms); assert(!_status && "xrEnumerateSwapchainFormats"); if (params.result != XR_SUCCESS) { return params.result; } real_formats = malloc(sizeof(*real_formats) * real_format_count); params.formatCapacityInput = real_format_count; params.formats = real_formats; _status = UNIX_CALL(xrEnumerateSwapchainFormats, ¶ms); assert(!_status && "xrEnumerateSwapchainFormats"); if (params.result != XR_SUCCESS) { goto done; } o = 0; for (i = 0; i < real_format_count; ++i) { int64_t mapped = map_format_vulkan_to_dxgi(real_formats[i]); if (mapped == DXGI_FORMAT_UNKNOWN) { continue; } if (formatCapacityInput && formats) { if (o < formatCapacityInput) { formats[o] = mapped; } else { params.result = XR_ERROR_SIZE_INSUFFICIENT; } } ++o; } *formatCountOutput = o; done: free(real_formats); return params.result; } XrResult WINAPI xrCreateSwapchain(XrSession session, const XrSwapchainCreateInfo *createInfo, XrSwapchain *swapchain) { wine_XrSession *wine_session = wine_session_from_handle(session); wine_XrSwapchain *wine_swapchain = calloc(1, sizeof(wine_XrSwapchain)); XrSwapchainCreateInfo our_createInfo = *createInfo; struct xrCreateSwapchain_params params = { .session = session, .createInfo = &our_createInfo, .swapchain = &wine_swapchain->host_swapchain}; NTSTATUS _status; wine_swapchain->create_info = *createInfo; TRACE("%p, %p, %p\n", session, createInfo, swapchain); if (wine_session->session_type == SESSION_TYPE_D3D11 || wine_session->session_type == SESSION_TYPE_D3D12) { BOOL format_is_depth; our_createInfo.format = map_format_dxgi_to_vulkan(createInfo->format); if (our_createInfo.format == VK_FORMAT_UNDEFINED) { ERR("unable to set Vulkan format\n"); free(wine_swapchain); return XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED; } our_createInfo.usageFlags |= XR_SWAPCHAIN_USAGE_MUTABLE_FORMAT_BIT; format_is_depth = is_vulkan_format_depth(our_createInfo.format); if ((createInfo->usageFlags & XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT) && format_is_depth) { WARN("Swapchain has a color attachment usage, but it's format is for depth\n"); } if ((createInfo->usageFlags & XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) && !format_is_depth) { WARN("Swapchain has a depth/stencil usage, but it's format is for color\n"); } } _status = UNIX_CALL(xrCreateSwapchain, ¶ms); assert(!_status && "xrCreateSwapchain"); if (params.result != XR_SUCCESS) { WARN("xrCreateSwapchain failed: %d\n", params.result); free(wine_swapchain); return params.result; } wine_swapchain->session = wine_session; *swapchain = (XrSwapchain)wine_swapchain; return XR_SUCCESS; } static void release_d3d12_resources(wine_XrSwapchain *swapchain, uint32_t image_count) { wine_XrInstance *instance = swapchain->session->instance; XrSwapchainImageD3D12KHR *d3d12_images = (XrSwapchainImageD3D12KHR *)swapchain->images; UINT i; if (!image_count) { return; } for (i = 0; i < image_count; i++) { if (d3d12_images[i].texture) { d3d12_images[i].texture->lpVtbl->Release(d3d12_images[i].texture); } } vkFreeCommandBuffers(instance->vk_device, instance->vk_command_pool, image_count, swapchain->cmd_release); vkFreeCommandBuffers(instance->vk_device, instance->vk_command_pool, image_count, swapchain->cmd_acquire); free(swapchain->cmd_release); free(swapchain->cmd_acquire); free(swapchain->acquired); free(swapchain->acquired_indices); } XrResult WINAPI xrDestroySwapchain(XrSwapchain swapchain) { wine_XrSwapchain *wine_swapchain = wine_swapchain_from_handle(swapchain); struct xrDestroySwapchain_params params = {.swapchain = swapchain}; NTSTATUS _status; TRACE("%p\n", swapchain); _status = UNIX_CALL(xrDestroySwapchain, ¶ms); assert(!_status && "xrDestroySwapchain"); if (params.result != XR_SUCCESS) { WARN("xrDestroySwapchain failed: %d\n", params.result); return params.result; } if (wine_swapchain->image_count) { UINT i; if (wine_swapchain->session->session_type == SESSION_TYPE_D3D11) { XrSwapchainImageD3D11KHR *d3d11_images = (XrSwapchainImageD3D11KHR *)wine_swapchain->images; for (i = 0; i < wine_swapchain->image_count; i++) { d3d11_images[i].texture->lpVtbl->Release(d3d11_images[i].texture); } } else if (wine_swapchain->session->session_type == SESSION_TYPE_D3D12) { release_d3d12_resources(wine_swapchain, wine_swapchain->image_count); } free(wine_swapchain->images); wine_swapchain->image_count = 0; } free(wine_swapchain); return XR_SUCCESS; } static D3D11_USAGE d3d11usage_from_XrSwapchainUsageFlags(XrSwapchainUsageFlags flags) { static const D3D11_USAGE supported_flags = XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | XR_SWAPCHAIN_USAGE_UNORDERED_ACCESS_BIT | XR_SWAPCHAIN_USAGE_SAMPLED_BIT; D3D11_USAGE ret = 0; if (flags & ~supported_flags) { WINE_FIXME("Unhandled flags %#lx.\n", (long)flags); } if (flags & XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT) { ret |= D3D11_BIND_RENDER_TARGET; } if (flags & XR_SWAPCHAIN_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT) { ret |= D3D11_BIND_DEPTH_STENCIL; } if (flags & XR_SWAPCHAIN_USAGE_UNORDERED_ACCESS_BIT) { ret |= D3D11_BIND_UNORDERED_ACCESS; } if (flags & XR_SWAPCHAIN_USAGE_SAMPLED_BIT) { ret |= D3D11_BIND_SHADER_RESOURCE; } return ret; } static VkResult record_transition_command(wine_XrInstance *instance, VkImage image, VkImageSubresourceRange subresource, VkImageLayout from, VkImageLayout to, VkCommandBuffer *out_cmd) { VkResult ret; VkCommandBufferAllocateInfo command_buffer_allocate_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandPool = instance->vk_command_pool, .commandBufferCount = 1, }; VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = 0, }; VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = from, .newLayout = to, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange = subresource, .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT, }; *out_cmd = VK_NULL_HANDLE; if (from == to) { return VK_SUCCESS; } if ((ret = vkAllocateCommandBuffers(instance->vk_device, &command_buffer_allocate_info, out_cmd)) != VK_SUCCESS) { return ret; } if ((ret = vkBeginCommandBuffer(*out_cmd, &begin_info)) != VK_SUCCESS) { return ret; } vkCmdPipelineBarrier(*out_cmd, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, NULL, 0, NULL, 1, &barrier); return vkEndCommandBuffer(*out_cmd); } XrResult WINAPI xrEnumerateSwapchainImages(XrSwapchain swapchain, uint32_t imageCapacityInput, uint32_t *imageCountOutput, XrSwapchainImageBaseHeader *images) { wine_XrSwapchain *wine_swapchain = wine_swapchain_from_handle(swapchain); wine_XrInstance *wine_instance = wine_swapchain->session->instance; XrSwapchainImageVulkanKHR *our_vk; HRESULT hr; size_t image_size = 0; uint32_t i; struct xrEnumerateSwapchainImages_params params = { .swapchain = swapchain, .imageCapacityInput = imageCapacityInput, .imageCountOutput = imageCountOutput, .images = images, }; NTSTATUS _status; TRACE("%p, %u, %p, %p\n", swapchain, imageCapacityInput, imageCountOutput, images); if (wine_swapchain->session->session_type != SESSION_TYPE_D3D11 && wine_swapchain->session->session_type != SESSION_TYPE_D3D12) { _status = UNIX_CALL(xrEnumerateSwapchainImages, ¶ms); assert(!_status && "xrEnumerateSwapchainImages"); return params.result; } if (!wine_swapchain->image_count) { uint32_t image_count; params.imageCapacityInput = 0; params.imageCountOutput = &image_count; params.images = NULL; _status = UNIX_CALL(xrEnumerateSwapchainImages, ¶ms); assert(!_status && "xrEnumerateSwapchainImages"); if (params.result != XR_SUCCESS) { return params.result; } our_vk = malloc(sizeof(*our_vk) * image_count); for (i = 0; i < image_count; ++i) { our_vk[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; our_vk[i].next = NULL; } params.imageCapacityInput = image_count; params.images = (XrSwapchainImageBaseHeader *)our_vk; _status = UNIX_CALL(xrEnumerateSwapchainImages, ¶ms); assert(!_status && "xrEnumerateSwapchainImages"); if (params.result != XR_SUCCESS) { free(our_vk); return params.result; } if (wine_swapchain->session->session_type == SESSION_TYPE_D3D11) { XrSwapchainImageD3D11KHR *our_d3d11; D3D11_TEXTURE2D_DESC1 desc; desc.Width = wine_swapchain->create_info.width; desc.Height = wine_swapchain->create_info.height; desc.MipLevels = wine_swapchain->create_info.mipCount; desc.ArraySize = wine_swapchain->create_info.arraySize; desc.Format = wine_swapchain->create_info.format; TRACE("creating dxvk texture with dxgi format %d (%x)\n", desc.Format, desc.Format); desc.SampleDesc.Count = wine_swapchain->create_info.sampleCount; desc.SampleDesc.Quality = 0; desc.Usage = D3D11_USAGE_DEFAULT; desc.BindFlags = d3d11usage_from_XrSwapchainUsageFlags(wine_swapchain->create_info.usageFlags); desc.CPUAccessFlags = 0; desc.MiscFlags = 0; desc.TextureLayout = D3D11_TEXTURE_LAYOUT_UNDEFINED; our_d3d11 = malloc(sizeof(XrSwapchainImageD3D11KHR) * image_count); for (i = 0; i < image_count; ++i) { hr = wine_instance->dxvk_device->lpVtbl->CreateTexture2DFromVkImage(wine_instance->dxvk_device, &desc, our_vk[i].image, &our_d3d11[i].texture); if (FAILED(hr)) { UINT j; for (j = 0; j < i; ++j) { our_d3d11[i].texture->lpVtbl->Release(our_d3d11[i].texture); } free(our_d3d11); free(our_vk); WARN("Failed to create DXVK texture from VkImage: %08x\n", hr); return XR_ERROR_RUNTIME_FAILURE; } TRACE("Successfully allocated texture %p\n", our_d3d11[i].texture); } wine_swapchain->images = (XrSwapchainImageBaseHeader *)our_d3d11; } else if (wine_swapchain->session->session_type == SESSION_TYPE_D3D12) { XrSwapchainImageD3D12KHR *our_d3d12; D3D12_RESOURCE_DESC1 desc; ID3D12DeviceExt1 *device_ext; HRESULT hr = wine_instance->d3d12_device->lpVtbl->QueryInterface(wine_instance->d3d12_device, &IID_ID3D12DeviceExt1, (void **)&device_ext); BOOL format_is_depth = is_vulkan_format_depth(map_format_dxgi_to_vulkan(wine_swapchain->create_info.format)); BOOL succeeded = TRUE; const D3D12_RESOURCE_STATES incoming_state = format_is_depth ? D3D12_RESOURCE_STATE_DEPTH_WRITE : D3D12_RESOURCE_STATE_RENDER_TARGET; const VkImageLayout vk_layout = format_is_depth ? VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; const VkImageSubresourceRange subresource = { .layerCount = wine_swapchain->create_info.arraySize, .baseArrayLayer = 0, .levelCount = wine_swapchain->create_info.mipCount, .baseMipLevel = 0, .aspectMask = format_is_depth ? (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT) : VK_IMAGE_ASPECT_COLOR_BIT, }; if (FAILED(hr)) { ERR("Cannot get vkd3d-proton interface: %08x\n", hr); return XR_ERROR_VALIDATION_FAILURE; } desc.Alignment = 0; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Width = wine_swapchain->create_info.width; desc.Height = wine_swapchain->create_info.height; desc.MipLevels = wine_swapchain->create_info.mipCount; desc.DepthOrArraySize = wine_swapchain->create_info.arraySize; desc.Format = wine_swapchain->create_info.format; TRACE("creating vkd3d-proton texture with dxgi format %d (%x)\n", desc.Format, desc.Format); desc.SampleDesc.Count = wine_swapchain->create_info.sampleCount; desc.SampleDesc.Quality = 0; desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN; desc.Flags = 0; if (!format_is_depth) { desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET; } else { desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL; if (!(wine_swapchain->create_info.usageFlags & XR_SWAPCHAIN_USAGE_SAMPLED_BIT)) { desc.Flags |= D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE; } } if (wine_swapchain->create_info.usageFlags & XR_SWAPCHAIN_USAGE_UNORDERED_ACCESS_BIT) { desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS; } our_d3d12 = calloc(1, sizeof(XrSwapchainImageD3D12KHR) * image_count); for (i = 0; i < image_count; ++i) { hr = device_ext->lpVtbl->CreateResourceFromBorrowedHandle(device_ext, &desc, our_vk[i].image, &our_d3d12[i].texture); if (FAILED(hr)) { ERR("Failed to create vkd3d-proton texture from VkImage: %08x\n", hr); succeeded = FALSE; break; } TRACE("Successfully allocated texture %p\n", our_d3d12[i].texture); } device_ext->lpVtbl->Release(device_ext); if (succeeded) { wine_swapchain->acquired = calloc(1, sizeof(BOOL) * image_count); wine_swapchain->acquired_indices = malloc(sizeof(uint32_t) * (image_count + 1)); wine_swapchain->acquired_count = wine_swapchain->acquired_start = 0; wine_swapchain->images = (XrSwapchainImageBaseHeader *)our_d3d12; wine_swapchain->cmd_release = calloc(1, sizeof(VkCommandBuffer) * image_count); wine_swapchain->cmd_acquire = calloc(1, sizeof(VkCommandBuffer) * image_count); for (i = 0; i < image_count; i++) { VkImageLayout d3d12_vk_layout; wine_instance->d3d12_device->lpVtbl->GetVulkanImageLayout(wine_instance->d3d12_device, our_d3d12[i].texture, incoming_state, &d3d12_vk_layout); if (record_transition_command(wine_instance, our_vk[i].image, subresource, d3d12_vk_layout, vk_layout, &wine_swapchain->cmd_release[i]) != VK_SUCCESS || record_transition_command(wine_instance, our_vk[i].image, subresource, vk_layout, d3d12_vk_layout, &wine_swapchain->cmd_acquire[i]) != VK_SUCCESS) { ERR("Failed to create command buffer for layout transition\n"); succeeded = FALSE; break; } } } if (!succeeded) { release_d3d12_resources(wine_swapchain, image_count); free(wine_swapchain->images); wine_swapchain->images = NULL; free(our_vk); return XR_ERROR_RUNTIME_FAILURE; } } free(our_vk); wine_swapchain->image_count = image_count; TRACE("image count %d\n", image_count); } *imageCountOutput = wine_swapchain->image_count; if (wine_swapchain->session->session_type == SESSION_TYPE_D3D11) { image_size = sizeof(XrSwapchainImageD3D11KHR); } else if (wine_swapchain->session->session_type == SESSION_TYPE_D3D12) { image_size = sizeof(XrSwapchainImageD3D12KHR); } memcpy(images, wine_swapchain->images, image_size * min(wine_swapchain->image_count, imageCapacityInput)); return XR_SUCCESS; } XrResult WINAPI xrAcquireSwapchainImage(XrSwapchain swapchain, const XrSwapchainImageAcquireInfo *acquireInfo, uint32_t *index) { wine_XrSwapchain *wine_swapchain = wine_swapchain_from_handle(swapchain); wine_XrInstance *wine_instance = wine_swapchain->session->instance; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device; struct xrAcquireSwapchainImage_params params = { .swapchain = swapchain, .acquireInfo = acquireInfo, .index = index, }; NTSTATUS _status; TRACE("%p, %p, %p image count %d, acquired %d\n", swapchain, acquireInfo, index, wine_swapchain->image_count, wine_swapchain->acquired_count); if ((dxvk_device = wine_instance->dxvk_device)) { dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); } else if ((d3d12_device = wine_instance->d3d12_device)) { if (wine_swapchain->acquired_count >= wine_swapchain->image_count) { WARN("Application has acquired all images but still tries to acquire more.\n"); return XR_ERROR_CALL_ORDER_INVALID; } d3d12_device->lpVtbl->LockCommandQueue(d3d12_device, wine_instance->d3d12_queue); } _status = UNIX_CALL(xrAcquireSwapchainImage, ¶ms); assert(!_status && "xrAcquireSwapchainImage"); if (dxvk_device) { dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); return params.result; } if (!d3d12_device) { return params.result; } if (params.result == XR_SUCCESS) { if (!wine_swapchain->acquired[*index] && wine_swapchain->cmd_acquire[*index] != VK_NULL_HANDLE) { VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &wine_swapchain->cmd_acquire[*index], }; vkQueueSubmit(wine_instance->vk_queue, 1, &submit_info, VK_NULL_HANDLE); } if (!wine_swapchain->acquired[*index]) { uint32_t next = (wine_swapchain->acquired_start + wine_swapchain->acquired_count) % wine_swapchain->image_count; wine_swapchain->acquired[*index] = TRUE; wine_swapchain->acquired_indices[next] = *index; wine_swapchain->acquired_count += 1; } else { WARN("the application acquired the same image (index %d) again!?", *index); } } d3d12_device->lpVtbl->UnlockCommandQueue(d3d12_device, wine_instance->d3d12_queue); return params.result; } XrResult WINAPI xrReleaseSwapchainImage(XrSwapchain swapchain, const XrSwapchainImageReleaseInfo *releaseInfo) { wine_XrSwapchain *wine_swapchain = wine_swapchain_from_handle(swapchain); wine_XrInstance *wine_instance = wine_swapchain->session->instance; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device = wine_instance->d3d12_device; uint32_t index; struct xrReleaseSwapchainImage_params params = { .swapchain = swapchain, .releaseInfo = releaseInfo, }; NTSTATUS _status; TRACE("%p, %p\n", swapchain, releaseInfo); if ((dxvk_device = wine_instance->dxvk_device)) { dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); } else if (d3d12_device) { if (wine_swapchain->acquired_count == 0) { WARN("Application tried to release a swapchain image without having acquired it first.\n"); return XR_ERROR_CALL_ORDER_INVALID; } d3d12_device->lpVtbl->LockCommandQueue(d3d12_device, wine_instance->d3d12_queue); index = wine_swapchain->acquired_indices[wine_swapchain->acquired_start]; if (wine_swapchain->cmd_release[index] != VK_NULL_HANDLE) { VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &wine_swapchain->cmd_release[index], }; vkQueueSubmit(wine_instance->vk_queue, 1, &submit_info, VK_NULL_HANDLE); } } _status = UNIX_CALL(xrReleaseSwapchainImage, ¶ms); assert(!_status && "xrReleaseSwapchainImage"); if (dxvk_device) { dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); return params.result; } if (!d3d12_device) { return params.result; } if (params.result != XR_SUCCESS && wine_swapchain->cmd_release[index] != VK_NULL_HANDLE) { VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &wine_swapchain->cmd_acquire[index], }; WARN("xrReleaseSwapchainImage failed, reverting layout transition\n"); vkQueueSubmit(wine_instance->vk_queue, 1, &submit_info, VK_NULL_HANDLE); } if (params.result == XR_SUCCESS) { wine_swapchain->acquired[index] = FALSE; wine_swapchain->acquired_start = (wine_swapchain->acquired_start + 1) % wine_swapchain->image_count; wine_swapchain->acquired_count -= 1; } d3d12_device->lpVtbl->UnlockCommandQueue(d3d12_device, wine_instance->d3d12_queue); return params.result; } XrResult WINAPI xrBeginFrame(XrSession session, const XrFrameBeginInfo *frameBeginInfo) { wine_XrSession *wine_session = wine_session_from_handle(session); struct xrBeginFrame_params params = { .session = session, .frameBeginInfo = frameBeginInfo, }; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device; NTSTATUS _status; TRACE("%p, %p\n", session, frameBeginInfo); if ((dxvk_device = wine_session->instance->dxvk_device)) { dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); } else if ((d3d12_device = wine_session->instance->d3d12_device)) { d3d12_device->lpVtbl->LockCommandQueue(d3d12_device, wine_session->instance->d3d12_queue); } _status = UNIX_CALL(xrBeginFrame, ¶ms); assert(!_status && "xrBeginFrame"); if (dxvk_device) { dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); } else if (d3d12_device) { d3d12_device->lpVtbl->UnlockCommandQueue(d3d12_device, wine_session->instance->d3d12_queue); } return params.result; } static XrCompositionLayerBaseHeader *convert_XrCompositionLayer(wine_XrSession *wine_session, const XrCompositionLayerBaseHeader *in_layer, CompositionLayer *out_layer, uint32_t *view_idx, uint32_t *view_info_idx) { uint32_t i; TRACE("Type %u, pNext %p.\n", in_layer->type, in_layer->next); switch (in_layer->type) { case XR_TYPE_COMPOSITION_LAYER_CUBE_KHR: { out_layer->cube = *(const XrCompositionLayerCubeKHR *)in_layer; out_layer->cube.swapchain = wine_swapchain_from_handle(out_layer->cube.swapchain)->host_swapchain; break; } case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: out_layer->cylinder = *(const XrCompositionLayerCylinderKHR *)in_layer; out_layer->cylinder.subImage.swapchain = wine_swapchain_from_handle(out_layer->cylinder.subImage.swapchain)->host_swapchain; break; case XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR: out_layer->depth_info = *(const XrCompositionLayerDepthInfoKHR *)in_layer; out_layer->depth_info.subImage.swapchain = wine_swapchain_from_handle(out_layer->depth_info.subImage.swapchain)->host_swapchain; break; case XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR: out_layer->equirect = *(const XrCompositionLayerEquirectKHR *)in_layer; out_layer->equirect.subImage.swapchain = wine_swapchain_from_handle(out_layer->equirect.subImage.swapchain)->host_swapchain; break; case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: out_layer->equirect2 = *(const XrCompositionLayerEquirect2KHR *)in_layer; out_layer->equirect2.subImage.swapchain = wine_swapchain_from_handle(out_layer->equirect2.subImage.swapchain)->host_swapchain; break; case XR_TYPE_COMPOSITION_LAYER_PROJECTION: { const XrCompositionLayerProjectionView *view; unsigned int view_info_count; out_layer->projection = *(const XrCompositionLayerProjection *)in_layer; view_info_count = 0; for (i = 0; i < out_layer->projection.viewCount; ++i) { view = &((XrCompositionLayerProjection *)in_layer)->views[i]; while ((view = view->next)) { ++view_info_count; } } if (out_layer->projection.viewCount + *view_idx > wine_session->projection_view_count) { wine_session->projection_view_count = out_layer->projection.viewCount + *view_idx; wine_session->projection_views = realloc(wine_session->projection_views, sizeof(XrCompositionLayerProjectionView) * wine_session->projection_view_count); } if (view_info_count + *view_info_idx > wine_session->view_info_count) { wine_session->view_info_count += view_info_count; wine_session->view_infos = realloc(wine_session->view_infos, sizeof(*wine_session->view_infos) * wine_session->view_info_count); } out_layer->projection.views = &wine_session->projection_views[*view_idx]; memcpy((void *)out_layer->projection.views, ((const XrCompositionLayerProjection *)in_layer)->views, sizeof(XrCompositionLayerProjectionView) * out_layer->projection.viewCount); view_info_count = 0; for (i = 0; i < out_layer->projection.viewCount; ++i) { view = &out_layer->projection.views[i]; if (view->type != XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW) { WARN("Unexpected view type %u.\n", view->type); } ((XrCompositionLayerProjectionView *)view)->subImage.swapchain = wine_swapchain_from_handle(view->subImage.swapchain)->host_swapchain; while (view->next) { TRACE("Projection view type %u.\n", ((XrCompositionLayerProjectionView *)view->next)->type); switch (((XrCompositionLayerProjectionView *)view->next)->type) { case XR_TYPE_COMPOSITION_LAYER_DEPTH_INFO_KHR: { XrCompositionLayerDepthInfoKHR *out_depth_info, *in_depth_info; in_depth_info = (XrCompositionLayerDepthInfoKHR *)view->next; out_depth_info = &wine_session->view_infos[*view_info_idx + view_info_count].depth_info; *out_depth_info = *in_depth_info; out_depth_info->subImage.swapchain = wine_swapchain_from_handle(out_depth_info->subImage.swapchain)->host_swapchain; ((XrCompositionLayerProjectionView *)view)->next = out_depth_info; break; } case XR_TYPE_COMPOSITION_LAYER_SPACE_WARP_INFO_FB: { XrCompositionLayerSpaceWarpInfoFB *out_warp_info, *in_warp_info; in_warp_info = (XrCompositionLayerSpaceWarpInfoFB *)view->next; out_warp_info = &wine_session->view_infos[*view_info_idx + view_info_count].space_warp_info; *out_warp_info = *in_warp_info; out_warp_info->motionVectorSubImage.swapchain = wine_swapchain_from_handle(out_warp_info->motionVectorSubImage.swapchain)->host_swapchain; out_warp_info->depthSubImage.swapchain = wine_swapchain_from_handle(out_warp_info->depthSubImage.swapchain)->host_swapchain; ((XrCompositionLayerProjectionView *)view)->next = out_warp_info; break; } default: WARN("Unknown view info type %u.\n", view->type); break; } ++view_info_count; view = view->next; } } *view_idx += out_layer->projection.viewCount; *view_info_idx += view_info_count; break; } case XR_TYPE_COMPOSITION_LAYER_QUAD: out_layer->quad = *(const XrCompositionLayerQuad *)in_layer; out_layer->quad.subImage.swapchain = wine_swapchain_from_handle(out_layer->quad.subImage.swapchain)->host_swapchain; break; default: WARN("Unknown composition in_layer type: %d\n", in_layer->type); return (XrCompositionLayerBaseHeader *)in_layer; } return (XrCompositionLayerBaseHeader *)out_layer; } XrResult WINAPI xrEndFrame(XrSession session, const XrFrameEndInfo *frameEndInfo) { wine_XrSession *wine_session = wine_session_from_handle(session); XrFrameEndInfo our_frameEndInfo; struct xrEndFrame_params params = { .session = session, .frameEndInfo = &our_frameEndInfo, }; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device; uint32_t i, view_idx = 0, view_info_idx = 0; NTSTATUS _status; TRACE("%p, %p\n", session, frameEndInfo); if (frameEndInfo->layerCount > wine_session->composition_layer_count) { free(wine_session->composition_layers); wine_session->composition_layers = malloc(frameEndInfo->layerCount * sizeof(*wine_session->composition_layers)); free(wine_session->composition_layer_ptrs); wine_session->composition_layer_ptrs = malloc(frameEndInfo->layerCount * sizeof(*wine_session->composition_layer_ptrs)); wine_session->composition_layer_count = frameEndInfo->layerCount; } for (i = 0; i < frameEndInfo->layerCount; ++i) { wine_session->composition_layer_ptrs[i] = convert_XrCompositionLayer( wine_session, frameEndInfo->layers[i], &wine_session->composition_layers[i], &view_idx, &view_info_idx); } our_frameEndInfo = *frameEndInfo; our_frameEndInfo.layers = (const XrCompositionLayerBaseHeader *const *)wine_session->composition_layer_ptrs; if ((dxvk_device = wine_session->instance->dxvk_device)) { TRACE("Locking submission queue.\n"); dxvk_device->lpVtbl->FlushRenderingCommands(dxvk_device); dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); } else if ((d3d12_device = wine_session->instance->d3d12_device)) { TRACE("Locking vkd3d-proton submission queue.\n"); d3d12_device->lpVtbl->LockCommandQueue(d3d12_device, wine_session->instance->d3d12_queue); } _status = UNIX_CALL(xrEndFrame, ¶ms); assert(!_status && "xrEndFrame"); if (dxvk_device) { dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); } else if (d3d12_device) { d3d12_device->lpVtbl->UnlockCommandQueue(d3d12_device, wine_session->instance->d3d12_queue); } return params.result; } XrResult WINAPI xrConvertTimeToWin32PerformanceCounterKHR(XrInstance instance, XrTime time, LARGE_INTEGER *performanceCounter) { FIXME("unimplemented\n"); /* FIXME */ return XR_ERROR_INITIALIZATION_FAILED; } XrResult WINAPI xrConvertWin32PerformanceCounterToTimeKHR(XrInstance instance, const LARGE_INTEGER *performanceCounter, XrTime *time) { FIXME("unimplemented\n"); /* FIXME */ return XR_ERROR_INITIALIZATION_FAILED; } XrResult WINAPI xrGetD3D11GraphicsRequirementsKHR(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D11KHR *graphicsRequirements) { IDXGIFactory1 *factory; IDXGIAdapter *adapter; DXGI_ADAPTER_DESC adapter_desc; HRESULT hr; DWORD i; TRACE("\n"); hr = CreateDXGIFactory1(&IID_IDXGIFactory1, (void **)&factory); if (FAILED(hr)) { WARN("CreateDXGIFactory1 failed: %08x\n", hr); return XR_ERROR_INITIALIZATION_FAILED; } i = 0; while ((hr = IDXGIFactory1_EnumAdapters(factory, i++, &adapter)) == S_OK) { hr = IDXGIAdapter_GetDesc(adapter, &adapter_desc); if (FAILED(hr)) { WARN("GetDesc failed: %08x\n", hr); IDXGIAdapter_Release(adapter); continue; } IDXGIAdapter_Release(adapter); /* FIXME: what if we have two of the same adapters? */ if (adapter_desc.VendorId == g_physdev_vid && adapter_desc.DeviceId == g_physdev_pid) { TRACE("Found DXGI adapter for %#04x:%#04x\n", g_physdev_vid, g_physdev_pid); break; } } if (hr == S_OK) { graphicsRequirements->adapterLuid = adapter_desc.AdapterLuid; } else { WARN("Couldn't find matching DXGI adapter for given VkPhysicalDevice! Choosing first one...\n"); hr = IDXGIFactory1_EnumAdapters(factory, 0, &adapter); if (FAILED(hr)) { WARN("EnumAdapters(0) failed: %08x\n", hr); IDXGIFactory1_Release(factory); return XR_ERROR_INITIALIZATION_FAILED; } hr = IDXGIAdapter_GetDesc(adapter, &adapter_desc); if (FAILED(hr)) { WARN("GetDesc(0) failed: %08x\n", hr); IDXGIAdapter_Release(adapter); IDXGIFactory1_Release(factory); return XR_ERROR_INITIALIZATION_FAILED; } IDXGIAdapter_Release(adapter); graphicsRequirements->adapterLuid = adapter_desc.AdapterLuid; } IDXGIFactory1_Release(factory); /* XXX */ graphicsRequirements->minFeatureLevel = D3D_FEATURE_LEVEL_10_0; return XR_SUCCESS; } XrResult WINAPI xrGetD3D12GraphicsRequirementsKHR(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D12KHR *graphicsRequirements) { XrGraphicsRequirementsD3D11KHR requirements; XrResult result = xrGetD3D11GraphicsRequirementsKHR(instance, systemId, &requirements); if (result != XR_SUCCESS) { return result; } graphicsRequirements->adapterLuid = requirements.adapterLuid; graphicsRequirements->minFeatureLevel = D3D_FEATURE_LEVEL_11_0; return XR_SUCCESS; } XrResult WINAPI xrCreateApiLayerInstance(const XrInstanceCreateInfo *info, const XrApiLayerCreateInfo *layerInfo, XrInstance *instance) { TRACE("%p, %p, %p\n", info, layerInfo, instance); return xrCreateInstance(info, instance); } XrResult WINAPI xrCreateVulkanInstanceKHR(XrInstance instance, const XrVulkanInstanceCreateInfoKHR *createInfo, VkInstance *vulkanInstance, VkResult *vulkanResult) { struct vk_create_callback_context context; VkCreateInfoWineInstanceCallback callback; VkInstanceCreateInfo vulkan_create_info; TRACE("instance %p, createInfo %p, vulkanInstance %p, vulkanResult %p.\n", instance, createInfo, vulkanInstance, vulkanResult); if (createInfo->createFlags) { WARN("Unexpected flags %#lx.\n", (long)createInfo->createFlags); } context.wine_instance = instance; context.create_info = (UINT64)createInfo; vulkan_create_info = *createInfo->vulkanCreateInfo; callback.sType = VK_STRUCTURE_TYPE_CREATE_INFO_WINE_INSTANCE_CALLBACK; callback.native_create_callback = (PFN_native_vkCreateInstance)g_vk_create_instance_callback; callback.context = &context; callback.pNext = vulkan_create_info.pNext; vulkan_create_info.pNext = &callback; *vulkanResult = vkCreateInstance(&vulkan_create_info, createInfo->vulkanAllocator, vulkanInstance); if (context.ret == XR_SUCCESS && *vulkanResult != VK_SUCCESS) { WARN("winevulkan instance creation failed after native xrCreateVulkanInstanceKHR() success.\n"); } TRACE("result %d, vk result %d.\n", context.ret, *vulkanResult); return context.ret; } XrResult WINAPI xrCreateVulkanDeviceKHR(XrInstance instance, const XrVulkanDeviceCreateInfoKHR *createInfo, VkDevice *vulkanDevice, VkResult *vulkanResult) { struct vk_create_callback_context context; VkCreateInfoWineDeviceCallback callback; VkDeviceCreateInfo vulkan_create_info; TRACE("instance %p, createInfo %p, vulkanDevice %p, vulkanResult %p.\n", instance, createInfo, vulkanDevice, vulkanResult); if (createInfo->createFlags) { WARN("Unexpected flags %#lx.\n", (long)createInfo->createFlags); } context.wine_instance = instance; context.create_info = (UINT64)createInfo; vulkan_create_info = *createInfo->vulkanCreateInfo; callback.sType = VK_STRUCTURE_TYPE_CREATE_INFO_WINE_DEVICE_CALLBACK; callback.native_create_callback = (PFN_native_vkCreateDevice)g_vk_create_device_callback; callback.context = &context; callback.pNext = vulkan_create_info.pNext; vulkan_create_info.pNext = &callback; *vulkanResult = vkCreateDevice(createInfo->vulkanPhysicalDevice, &vulkan_create_info, createInfo->vulkanAllocator, vulkanDevice); if (context.ret == XR_SUCCESS && *vulkanResult != VK_SUCCESS) { WARN("winevulkan instance creation failed after native xrCreateVulkanInstanceKHR() success.\n"); } TRACE("result %d, vk result %d.\n", context.ret, *vulkanResult); return context.ret; } XrResult WINAPI xrGetInstanceProcAddr(XrInstance instance, const char *fn_name, PFN_xrVoidFunction *out_fn) { TRACE("%s\n", fn_name); *out_fn = wine_xr_get_instance_proc_addr(fn_name); if (!*out_fn) { WARN("Unable to find requested function: %s\n", fn_name); return XR_ERROR_FUNCTION_UNSUPPORTED; } return XR_SUCCESS; } XrResult WINAPI xrNegotiateLoaderRuntimeInterface(const XrNegotiateLoaderInfo *info, XrNegotiateRuntimeRequest *request) { const XrNegotiateLoaderInfo *loaderInfo = (const XrNegotiateLoaderInfo *)info; XrNegotiateRuntimeRequest *runtimeRequest = (XrNegotiateRuntimeRequest *)request; XrResult res; TRACE("%p %p\n", loaderInfo, runtimeRequest); if (!loaderInfo || !runtimeRequest) { return XR_ERROR_INITIALIZATION_FAILED; } if ((res = wine_openxr_init_once()) != XR_SUCCESS) { TRACE("could not initialize openxr: %d\n", res); return res; } if (loaderInfo->structType != XR_LOADER_INTERFACE_STRUCT_LOADER_INFO || loaderInfo->structVersion != XR_LOADER_INFO_STRUCT_VERSION || loaderInfo->structSize != sizeof(XrNegotiateLoaderInfo)) { return XR_ERROR_VALIDATION_FAILURE; } if (loaderInfo->minInterfaceVersion > XR_CURRENT_LOADER_RUNTIME_VERSION || loaderInfo->maxInterfaceVersion < XR_CURRENT_LOADER_RUNTIME_VERSION) { return XR_ERROR_VALIDATION_FAILURE; } if (runtimeRequest->structType != XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST || runtimeRequest->structVersion != XR_RUNTIME_INFO_STRUCT_VERSION || runtimeRequest->structSize != sizeof(XrNegotiateRuntimeRequest)) { return XR_ERROR_VALIDATION_FAILURE; } runtimeRequest->runtimeInterfaceVersion = XR_CURRENT_LOADER_RUNTIME_VERSION; runtimeRequest->getInstanceProcAddr = (PFN_xrGetInstanceProcAddr)&xrGetInstanceProcAddr; runtimeRequest->runtimeApiVersion = api_version; return XR_SUCCESS; } /* wineopenxr API */ XrResult WINAPI __wineopenxr_GetVulkanInstanceExtensions(uint32_t buflen, uint32_t *outlen, char *buf) { XrResult res; TRACE("\n"); if ((res = wine_openxr_init_once()) != XR_SUCCESS) { TRACE("could not initialize openxr: %d\n", res); return res; } if (buflen < strlen(g_instance_extensions) + 1 || !buf) { *outlen = strlen(g_instance_extensions) + 1; return XR_SUCCESS; } *outlen = strlen(g_instance_extensions) + 1; strcpy(buf, g_instance_extensions); return XR_SUCCESS; } /* wineopenxr API */ XrResult WINAPI __wineopenxr_GetVulkanDeviceExtensions(uint32_t buflen, uint32_t *outlen, char *buf) { XrResult res; TRACE("\n"); if ((res = wine_openxr_init_once()) != XR_SUCCESS) { TRACE("could not initialize openxr: %d\n", res); return res; } if (buflen < strlen(WINE_VULKAN_DEVICE_EXTENSION_NAME) + 1 || !buf) { *outlen = strlen(WINE_VULKAN_DEVICE_EXTENSION_NAME) + 1; return XR_SUCCESS; } TRACE("g_device_extensions %s.\n", g_device_extensions); __wine_set_unix_env(WINE_VULKAN_DEVICE_VARIABLE, g_device_extensions); *outlen = strlen(WINE_VULKAN_DEVICE_EXTENSION_NAME) + 1; strcpy(buf, WINE_VULKAN_DEVICE_EXTENSION_NAME); return XR_SUCCESS; } BOOL CDECL wineopenxr_init_registry(void) { char *xr_inst_ext, *xr_dev_ext; uint32_t vid, pid; LSTATUS status; HKEY vr_key; if ((status = RegOpenKeyExA( HKEY_CURRENT_USER, "Software\\Wine\\VR", 0, KEY_ALL_ACCESS, &vr_key ))) { WARN( "Could not open key, status %#x.\n", status ); return FALSE; } if (!get_extensions( &xr_inst_ext, &xr_dev_ext, &vid, &pid )) { TRACE( "Got XR extensions.\n" ); if ((status = RegSetValueExA( vr_key, "openxr_vulkan_instance_extensions", 0, REG_SZ, (BYTE *)xr_inst_ext, strlen( xr_inst_ext ) + 1 ))) ERR( "Could not set openxr_vulkan_instance_extensions value, status %#x.\n", status ); if ((status = RegSetValueExA( vr_key, "openxr_vulkan_device_extensions", 0, REG_SZ, (BYTE *)xr_dev_ext, strlen( xr_dev_ext ) + 1 ))) ERR( "Could not set openxr_vulkan_device_extensions value, status %#x.\n", status ); if ((status = RegSetValueExA( vr_key, "openxr_vulkan_device_vid", 0, REG_DWORD, (BYTE *)&vid, sizeof(vid) ))) ERR( "Could not set openxr_vulkan_device_vid value, status %#x.\n", status ); if ((status = RegSetValueExA( vr_key, "openxr_vulkan_device_pid", 0, REG_DWORD, (BYTE *)&pid, sizeof(pid) ))) ERR( "Could not set openxr_vulkan_device_pid value, status %#x.\n", status ); } TRACE( "Initialized OpenXR registry entries\n" ); RegCloseKey( vr_key ); return TRUE; }