#include #define __USE_GNU #include #define COBJMACROS #include "initguid.h" #include "windef.h" #include "winbase.h" #include "winnls.h" #include "windows.h" #include "winternl.h" #include "wine/debug.h" #include "dxgi.h" #include "d3d11.h" #include "d3d11_3.h" #include "d3d12.h" #include "wine/rbtree.h" /* we want to use the native linux header */ #pragma push_macro("_WIN32") #pragma push_macro("__cdecl") #undef _WIN32 #undef __cdecl #define XR_USE_GRAPHICS_API_OPENGL 1 #define XR_USE_GRAPHICS_API_VULKAN 1 #define WINE_VK_HOST #include "wine/vulkan.h" #define VULKAN_H_ 1// tell dxvk-interop not to include vulkan.h #include "dxvk-interop.h" #include "vkd3d-proton-interop.h" #undef WINE_VK_HOST #define XR_USE_GRAPHICS_API_D3D11 1 #define XR_USE_GRAPHICS_API_D3D12 1 #include #pragma pop_macro("_WIN32") #pragma pop_macro("__cdecl") typedef struct XrApiLayerCreateInfo XrApiLayerCreateInfo; typedef struct XrNegotiateLoaderInfo XrNegotiateLoaderInfo; typedef struct XrNegotiateApiLayerRequest XrNegotiateApiLayerRequest; typedef struct XrNegotiateRuntimeRequest XrNegotiateRuntimeRequest; #include "openxr_private.h" #include "wine/vulkan_driver.h" #include "wine/unixlib.h" WINE_DEFAULT_DEBUG_CHANNEL(openxr); union CompositionLayer { XrCompositionLayerProjection projection; XrCompositionLayerQuad quad; XrCompositionLayerCubeKHR cube; XrCompositionLayerDepthInfoKHR depth_info; XrCompositionLayerCylinderKHR cylinder; XrCompositionLayerEquirectKHR equirect; XrCompositionLayerEquirect2KHR equirect2; }; #define heap_alloc(s) HeapAlloc(GetProcessHeap(), 0, s) #define heap_alloc_zero(s) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, s) #define heap_free(p) HeapFree(GetProcessHeap(), 0, p) static void *heap_realloc(void *p, size_t s) { if(!p) return heap_alloc(s); return HeapReAlloc(GetProcessHeap(), 0, p, s); } static const char WINE_VULKAN_DEVICE_EXTENSION_NAME[] = "VK_WINE_openxr_device_extensions"; static const char WINE_VULKAN_DEVICE_VARIABLE[] = "__WINE_OPENXR_VK_DEVICE_EXTENSIONS"; static struct { const char *win32_ext, *linux_ext; BOOL remove_original; BOOL force_enable; } substitute_extensions[] = { {"XR_KHR_D3D11_enable", "XR_KHR_vulkan_enable"}, {"XR_KHR_D3D12_enable", "XR_KHR_vulkan_enable"}, {"XR_KHR_win32_convert_performance_counter_time", "XR_KHR_convert_timespec_time", TRUE, TRUE}, }; static char *wineopenxr_strdup(const char *src) { const size_t l = strlen(src) + 1; char *r = heap_alloc(l); strcpy(r, src); return r; } struct handle_instance_lookup_entry { struct rb_entry entry; uint64_t handle; struct openxr_instance_funcs *funcs; }; static struct rb_tree handle_instance_lookup; static SRWLOCK handle_instance_lookup_lock = SRWLOCK_INIT; static struct wine_XrInstance *last_instance; static int wrapper_entry_compare(const void *key, const struct rb_entry *entry) { struct handle_instance_lookup_entry *wrapper = RB_ENTRY_VALUE(entry, struct handle_instance_lookup_entry, entry); const uint64_t *handle = key; if (*handle < wrapper->handle) return -1; if (*handle > wrapper->handle) return 1; return 0; } VkDevice(*get_native_VkDevice)(VkDevice); VkInstance(*get_native_VkInstance)(VkInstance); VkPhysicalDevice(*get_native_VkPhysicalDevice)(VkPhysicalDevice); VkPhysicalDevice(*get_wrapped_VkPhysicalDevice)(VkInstance, VkPhysicalDevice); VkQueue(*get_native_VkQueue)(VkQueue); VkResult (WINAPI *p_wine_vkCreateInstance)(const VkInstanceCreateInfo *create_info, const VkAllocationCallbacks *allocator, VkInstance *ret); VkResult (WINAPI *p_wine_vkCreateDevice)(VkPhysicalDevice phys_dev, const VkDeviceCreateInfo *create_info, const VkAllocationCallbacks *allocator, VkDevice *ret); static void *get_winevulkan_unix_lib_handle(HMODULE hvulkan) { unixlib_handle_t unix_funcs; NTSTATUS status; Dl_info info; status = NtQueryVirtualMemory(GetCurrentProcess(), hvulkan, (MEMORY_INFORMATION_CLASS)1000 /*MemoryWineUnixFuncs*/, &unix_funcs, sizeof(unix_funcs), NULL); if (status) { WINE_ERR("NtQueryVirtualMemory status %#x.\n", (int)status); return NULL; } if (!dladdr((void *)(ULONG_PTR)unix_funcs, &info)) { WINE_ERR("dladdr failed.\n"); return NULL; } WINE_TRACE("path %s.\n", info.dli_fname); return dlopen(info.dli_fname, RTLD_NOW); } static void load_vk_unwrappers(void) { static HMODULE h = NULL; void *unix_handle; if(h) /* already loaded */ return; h = LoadLibraryA("winevulkan"); if(!h){ WINE_ERR("unable to load winevulkan\n"); return; } p_wine_vkCreateInstance = (void *)GetProcAddress(h, "vkCreateInstance"); p_wine_vkCreateDevice = (void *)GetProcAddress(h, "vkCreateDevice"); if (!(unix_handle = get_winevulkan_unix_lib_handle(h))) { WINE_ERR("Unable to open winevulkan.so.\n"); return; } #define L(name) \ if (!(name = dlsym(unix_handle, "__wine_"#name))) \ {\ WINE_ERR("%s not found.\n", #name);\ dlclose(unix_handle);\ return;\ } L(get_native_VkDevice); L(get_native_VkInstance); L(get_native_VkPhysicalDevice); L(get_wrapped_VkPhysicalDevice); L(get_native_VkQueue); #undef L dlclose(unix_handle); } #define XR_CURRENT_LOADER_API_LAYER_VERSION 1 #define XR_CURRENT_LOADER_RUNTIME_VERSION 1 #define XR_LOADER_INFO_STRUCT_VERSION 1 #define XR_RUNTIME_INFO_STRUCT_VERSION 1 typedef enum XrLoaderInterfaceStructs { XR_LOADER_INTERFACE_STRUCT_UNINTIALIZED = 0, XR_LOADER_INTERFACE_STRUCT_LOADER_INFO, XR_LOADER_INTERFACE_STRUCT_API_LAYER_REQUEST, XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST, XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO, XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO, } XrLoaderInterfaceStructs; typedef struct XrApiLayerNextInfo XrApiLayerNextInfo; #define XR_API_LAYER_MAX_SETTINGS_PATH_SIZE 512 #define XR_API_LAYER_CREATE_INFO_STRUCT_VERSION 1 typedef struct XrApiLayerCreateInfo { XrLoaderInterfaceStructs structType; uint32_t structVersion; size_t structSize; void *loaderInstance; char settings_file_location[XR_API_LAYER_MAX_SETTINGS_PATH_SIZE]; XrApiLayerNextInfo *nextInfo; } XrApiLayerCreateInfo; typedef XrResult(__stdcall *PFN_xrCreateApiLayerInstance)(const XrInstanceCreateInfo *info, const XrApiLayerCreateInfo *apiLayerInfo, XrInstance *instance); #define XR_API_LAYER_NEXT_INFO_STRUCT_VERSION 1 struct XrApiLayerNextInfo { XrLoaderInterfaceStructs structType; uint32_t structVersion; size_t structSize; char layerName[XR_MAX_API_LAYER_NAME_SIZE]; PFN_xrGetInstanceProcAddr nextGetInstanceProcAddr; PFN_xrCreateApiLayerInstance nextCreateApiLayerInstance; XrApiLayerNextInfo *next; }; #define WINE_XR_STRUCT_NAME(x) x##_win #define WINE_XR_STRUCT_ATTR __attribute__((ms_struct)) #include "loader_structs.h" #undef WINE_XR_STRUCT_ATTR #undef WINE_XR_STRUCT_NAME #define WINE_XR_STRUCT_NAME(x) x##_host #define WINE_XR_STRUCT_ATTR #include "loader_structs.h" #undef WINE_XR_STRUCT_ATTR #undef WINE_XR_STRUCT_NAME static char *g_instance_extensions, *g_device_extensions; static uint32_t g_physdev_vid, g_physdev_pid; static XrVersion api_version = XR_CURRENT_API_VERSION; static char *strdupA(const char *s) { size_t l = strlen(s) + 1; char *r = heap_alloc(l); memcpy(r, s, l); return r; } 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->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 = strdupA(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 = heap_alloc(extension_count * sizeof(char *)); start = iter = str; do{ if(*iter == ' '){ *iter = 0; list[o++] = strdupA(start); WINE_TRACE("added %s to list\n", list[o-1]); iter++; start = iter; }else if(*iter == 0){ list[o++] = strdupA(start); WINE_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))) { WINE_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))) { WINE_ERR("Could not query value, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } if (type != REG_DWORD) { WINE_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)) { WINE_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))) { WINE_ERR("Could not query value, status %#x.\n", status); CloseHandle(event); goto done; } if (value) break; while ((wait_status = WaitForSingleObject(event, 1000)) == WAIT_TIMEOUT) WINE_ERR("VR state wait timeout.\n"); if (wait_status != WAIT_OBJECT_0) { WINE_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))) { WINE_ERR("Error getting openxr_vulkan_instance_extensions, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } g_instance_extensions = heap_alloc(size); if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_instance_extensions", NULL, &type, (BYTE *)g_instance_extensions, &size))) { WINE_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))) { WINE_ERR("Error getting openxr_vulkan_device_extensions, status %#x.\n", status); RegCloseKey(vr_key); return FALSE; } g_device_extensions = heap_alloc(size); if ((status = RegQueryValueExA(vr_key, "openxr_vulkan_device_extensions", NULL, &type, (BYTE *)g_device_extensions, &size))) { WINE_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))) { WINE_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))) { WINE_ERR("Error getting openxr_vulkan_device_pid, status: %#x.\n", status); RegCloseKey(vr_key); return FALSE; } } RegCloseKey(vr_key); return value == 1; } int WINAPI __wineopenxr_get_extensions_internal(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; 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) { WINE_WARN("version %#llx unsupported.\n", (long long)api_version); api_version = XR_MAKE_VERSION(1, 0, 0); xrCreateInfo.applicationInfo.apiVersion = api_version; res = xrCreateInstance(&xrCreateInfo, &instance); } if(res != XR_SUCCESS){ WINE_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) WINE_WARN("xrGetInstanceProperties failed: %d\n", res); res = pxrGetSystem(instance, &system_info, &system); if(res != XR_SUCCESS){ WINE_WARN("xrGetSystem failed: %d\n", res); xrDestroyInstance(instance); return res; } res = pxrEnumerateViewConfigurations(instance, system, 0, &len, NULL); if(res != XR_SUCCESS) WINE_WARN("xrEnumerateViewConfigurations failed: %d\n", res); configs = heap_alloc(len * sizeof(*configs)); res = pxrEnumerateViewConfigurations(instance, system, len, &len, configs); if(res != XR_SUCCESS) WINE_WARN("xrEnumerateViewConfigurations failed: %d\n", res); heap_free(configs); res = pxrGetVulkanGraphicsRequirementsKHR(instance, system, &reqs); if(res != XR_SUCCESS) WINE_WARN("xrGetVulkanGraphicsRequirementsKHR failed: %d\n", res); res = pxrGetVulkanInstanceExtensionsKHR(instance, system, 0, &len, NULL); if(res != XR_SUCCESS){ WINE_WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); xrDestroyInstance(instance); return res; } instance_extensions = heap_alloc(len); res = pxrGetVulkanInstanceExtensionsKHR(instance, system, len, &len, instance_extensions); if(res != XR_SUCCESS){ WINE_WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); xrDestroyInstance(instance); heap_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){ WINE_WARN("vkCreateInstance failed: %d\n", vk_res); for(i = 0; i < vk_createinfo.enabledExtensionCount; ++i) heap_free((void*)vk_createinfo.ppEnabledExtensionNames[i]); heap_free((void*)vk_createinfo.ppEnabledExtensionNames); xrDestroyInstance(instance); heap_free(instance_extensions); return XR_ERROR_INITIALIZATION_FAILED; } for(i = 0; i < vk_createinfo.enabledExtensionCount; ++i) heap_free((void*)vk_createinfo.ppEnabledExtensionNames[i]); heap_free((void*)vk_createinfo.ppEnabledExtensionNames); res = pxrGetVulkanGraphicsDeviceKHR(instance, system, vk_instance, &vk_physdev); if(res != XR_SUCCESS){ WINE_WARN("xrGetVulkanGraphicsDeviceKHR failed: %d\n", res); vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); heap_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){ WINE_WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); heap_free(instance_extensions); return res; } device_extensions = heap_alloc(len); res = pxrGetVulkanDeviceExtensionsKHR(instance, system, len, &len, device_extensions); if(res != XR_SUCCESS){ WINE_WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); heap_free(instance_extensions); heap_free(device_extensions); return res; } vkDestroyInstance(vk_instance, NULL); xrDestroyInstance(instance); WINE_TRACE("Got required instance extensions: %s\n", instance_extensions); WINE_TRACE("Got required device extensions: %s\n", device_extensions); *ret_instance_extensions = instance_extensions; *ret_device_extensions = device_extensions; return XR_SUCCESS; } XrResult load_host_openxr_loader(void) { if(g_instance_extensions || g_device_extensions) /* already done */ return XR_SUCCESS; if(!get_vulkan_extensions()){ return XR_ERROR_INITIALIZATION_FAILED; } load_vk_unwrappers(); rb_init(&handle_instance_lookup, wrapper_entry_compare); return XR_SUCCESS; } XrResult WINAPI wine_xrEnumerateInstanceExtensionProperties(const char *layerName, uint32_t propertyCapacityInput, uint32_t *propertyCountOutput, XrExtensionProperties *properties) { uint32_t i, j, dst, count, extra_extensions_count; XrResult res; WINE_TRACE("\n"); res = xrEnumerateInstanceExtensionProperties(layerName, propertyCapacityInput, propertyCountOutput, properties); if (res != XR_SUCCESS) return res; if (!properties) { extra_extensions_count = 0; for (i = 0; i < ARRAY_SIZE(substitute_extensions); ++i) if (!substitute_extensions[i].remove_original || substitute_extensions[i].force_enable) ++extra_extensions_count; *propertyCountOutput += extra_extensions_count; WINE_TRACE("%u extensions.\n", *propertyCountOutput); return XR_SUCCESS; } count = *propertyCountOutput; for (i = 0; i < count; ++i) { for (j = 0; j < ARRAY_SIZE(substitute_extensions); ++j) { if (!strcmp(properties[i].extensionName, substitute_extensions[j].linux_ext)) { if (substitute_extensions[j].force_enable) { WINE_FIXME("Force enabled extension %s already supported by the runtime.\n", substitute_extensions[j].linux_ext); substitute_extensions[j].force_enable = FALSE; } if (substitute_extensions[j].remove_original) dst = i; else dst = (*propertyCountOutput)++; strcpy(properties[dst].extensionName, substitute_extensions[j].win32_ext); } } } for (j = 0; j < ARRAY_SIZE(substitute_extensions); ++j) if (substitute_extensions[j].force_enable) { strcpy(properties[*propertyCountOutput].extensionName, substitute_extensions[j].win32_ext); ++*propertyCountOutput; } WINE_TRACE("Enumerated extensions:\n"); for(i = 0; i < *propertyCountOutput; ++i) WINE_TRACE(" -%s\n", properties[i].extensionName); return XR_SUCCESS; } XrResult WINAPI wine_xrConvertTimeToWin32PerformanceCounterKHR(XrInstance instance, XrTime time, LARGE_INTEGER *performanceCounter) { WINE_FIXME("unimplemented\n"); /* FIXME */ return XR_ERROR_INITIALIZATION_FAILED; } XrResult WINAPI wine_xrConvertWin32PerformanceCounterToTimeKHR(XrInstance instance, const LARGE_INTEGER *performanceCounter, XrTime *time) { WINE_FIXME("unimplemented\n"); /* FIXME */ return XR_ERROR_INITIALIZATION_FAILED; } XrResult WINAPI wine_xrGetD3D11GraphicsRequirementsKHR(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D11KHR *graphicsRequirements) { IDXGIFactory1 *factory; IDXGIAdapter *adapter; DXGI_ADAPTER_DESC adapter_desc; HRESULT hr; DWORD i; WINE_TRACE("\n"); hr = CreateDXGIFactory1(&IID_IDXGIFactory1, (void**)&factory); if(FAILED(hr)){ WINE_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)){ WINE_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){ WINE_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{ WINE_WARN("Couldn't find matching DXGI adapter for given VkPhysicalDevice! Choosing first one...\n"); hr = IDXGIFactory1_EnumAdapters(factory, 0, &adapter); if(FAILED(hr)){ WINE_WARN("EnumAdapters(0) failed: %08x\n", hr); IDXGIFactory1_Release(factory); return XR_ERROR_INITIALIZATION_FAILED; } hr = IDXGIAdapter_GetDesc(adapter, &adapter_desc); if(FAILED(hr)){ WINE_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 wine_xrGetD3D12GraphicsRequirementsKHR(XrInstance instance, XrSystemId systemId, XrGraphicsRequirementsD3D12KHR *graphicsRequirements) { XrGraphicsRequirementsD3D11KHR requirements; XrResult result = wine_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 wine_xrGetInstanceProcAddr(XrInstance instance, const char *fn_name, PFN_xrVoidFunction *out_fn) { WINE_TRACE("%s\n", fn_name); *out_fn = wine_xr_proc_addr(fn_name); if(!*out_fn){ WINE_WARN("Unable to find requested function: %s\n", fn_name); return XR_ERROR_FUNCTION_UNSUPPORTED; } return XR_SUCCESS; } XrResult WINAPI wine_xrCreateInstance(const XrInstanceCreateInfo *createInfo, XrInstance *instance) { XrResult res; struct wine_XrInstance *wine_instance; uint32_t i, j, count = 0; XrInstanceCreateInfo our_createInfo; const char *ext_name; char **new_list; WINE_TRACE("%p, %p\n", createInfo, instance); WINE_TRACE("Incoming extensions:\n"); for(i = 0; i < createInfo->enabledExtensionCount; ++i){ WINE_TRACE(" -%s\n", createInfo->enabledExtensionNames[i]); } new_list = heap_alloc(createInfo->enabledExtensionCount * sizeof(*new_list)); count = 0; /* remove win32 extensions */ for(i = 0; i < createInfo->enabledExtensionCount; ++i) { ext_name = createInfo->enabledExtensionNames[i]; for (j = 0; j < ARRAY_SIZE(substitute_extensions); ++j) { if (!strcmp(ext_name, substitute_extensions[j].win32_ext)) { if (substitute_extensions[j].force_enable) ext_name = NULL; else ext_name = substitute_extensions[j].linux_ext; break; } } if (ext_name) new_list[count++] = wineopenxr_strdup(ext_name); } our_createInfo = *createInfo; our_createInfo.enabledExtensionNames = (const char * const*)new_list; our_createInfo.enabledExtensionCount = count; createInfo = &our_createInfo; WINE_TRACE("Enabled extensions:\n"); for(i = 0; i < createInfo->enabledExtensionCount; ++i){ WINE_TRACE(" -%s\n", createInfo->enabledExtensionNames[i]); } wine_instance = heap_alloc_zero(sizeof(wine_XrInstance)); res = xrCreateInstance(createInfo, &wine_instance->instance); if(res != XR_SUCCESS){ WINE_WARN("xrCreateInstance failed: %d\n", res); heap_free(wine_instance); goto cleanup; } WINE_TRACE("allocated wine instance %p for native instance %p\n", wine_instance, wine_instance->instance); #define USE_XR_FUNC(x) \ xrGetInstanceProcAddr(wine_instance->instance, #x, (PFN_xrVoidFunction *)&wine_instance->funcs.p_##x); ALL_XR_INSTANCE_FUNCS() #undef USE_XR_FUNC *instance = (XrInstance)wine_instance; last_instance = wine_instance; cleanup: for(i = 0; i < our_createInfo.enabledExtensionCount; ++i) heap_free((void*)our_createInfo.enabledExtensionNames[i]); heap_free((void*)our_createInfo.enabledExtensionNames); return res; } XrResult WINAPI wine_xrCreateApiLayerInstance(const XrInstanceCreateInfo *info, const XrApiLayerCreateInfo *layerInfo, XrInstance *instance) { WINE_TRACE("%p, %p, %p\n", info, layerInfo, instance); return wine_xrCreateInstance(info, instance); } XrResult WINAPI wine_xrDestroyInstance(XrInstance instance) { wine_XrInstance *wine_instance = (wine_XrInstance *)instance; XrResult res; WINE_TRACE("\n"); res = xrDestroyInstance(wine_instance->instance); if(res != XR_SUCCESS){ WINE_WARN("xrDestroyInstance failed: %d\n", res); return res; } if(wine_instance->dxvk_device) wine_instance->dxvk_device->lpVtbl->Release(wine_instance->dxvk_device); if (wine_instance->d3d12_device) { wine_instance->p_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); } heap_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) { 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 = wine_instance->funcs.p_xrGetVulkanGraphicsRequirementsKHR(wine_instance->instance, wine_instance->systemId, &vk_reqs); if(res != XR_SUCCESS) { WINE_WARN("xrGetVulkanGraphicsRequirementsKHR failed: %d\n", res); return res; } res = wine_instance->funcs.p_xrGetVulkanInstanceExtensionsKHR(wine_instance->instance, wine_instance->systemId, 0, &len, NULL); if(res != XR_SUCCESS) { WINE_WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); return res; } instance_extensions = heap_alloc(len); res = wine_instance->funcs.p_xrGetVulkanInstanceExtensionsKHR(wine_instance->instance, wine_instance->systemId, len, &len, instance_extensions); if(res != XR_SUCCESS) { WINE_WARN("xrGetVulkanInstanceExtensionsKHR failed: %d\n", res); heap_free(instance_extensions); return res; } res = wine_instance->funcs.p_xrGetVulkanGraphicsDeviceKHR(wine_instance->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 = wine_instance->funcs.p_xrGetVulkanDeviceExtensionsKHR(wine_instance->instance, wine_instance->systemId, 0, &len, NULL); if(res != XR_SUCCESS) { WINE_WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); return res; } device_extensions = heap_alloc(len); res = wine_instance->funcs.p_xrGetVulkanDeviceExtensionsKHR(wine_instance->instance, wine_instance->systemId, len, &len, device_extensions); if(res != XR_SUCCESS) { WINE_WARN("pxrGetVulkanDeviceExtensionsKHR fail: %d\n", res); heap_free(device_extensions); return res; } heap_free(device_extensions); return XR_SUCCESS; } XrResult WINAPI wine_xrCreateSession(XrInstance instance, const XrSessionCreateInfo *createInfo, XrSession *session) { wine_XrInstance *wine_instance = (wine_XrInstance *)instance; wine_XrSession *wine_session; XrResult res; XrSessionCreateInfo our_create_info; XrGraphicsBindingVulkanKHR our_vk_binding; uint32_t session_type = 0; WINE_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 */: { const XrGraphicsBindingVulkanKHR *their_vk_binding = (const XrGraphicsBindingVulkanKHR *)createInfo->next; our_vk_binding = *their_vk_binding; our_vk_binding.instance = get_native_VkInstance(their_vk_binding->instance); our_vk_binding.physicalDevice = get_native_VkPhysicalDevice(their_vk_binding->physicalDevice); our_vk_binding.device = get_native_VkDevice(their_vk_binding->device); our_create_info = *createInfo; our_create_info.next = &our_vk_binding; createInfo = &our_create_info; 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)){ WINE_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); our_vk_binding.instance = get_native_VkInstance(our_vk_binding.instance); if ((res = do_vulkan_init(wine_instance, our_vk_binding.instance)) != XR_SUCCESS) return res; if (wine_instance->vk_phys_dev != get_native_VkPhysicalDevice(our_vk_binding.physicalDevice)) WINE_WARN("VK physical device does not match that from xrGetVulkanGraphicsDeviceKHR.\n"); our_vk_binding.physicalDevice = wine_instance->vk_phys_dev; our_vk_binding.device = get_native_VkDevice(our_vk_binding.device); our_create_info = *createInfo; our_create_info.next = &our_vk_binding; createInfo = &our_create_info; 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)) { WINE_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)) { WINE_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 = get_native_VkDevice(our_vk_binding.device); wine_instance->vk_queue = get_native_VkQueue(wine_instance->vk_queue); our_vk_binding.queueIndex = queue_index; our_vk_binding.instance = get_native_VkInstance(our_vk_binding.instance); if ((res = do_vulkan_init(wine_instance, our_vk_binding.instance)) != XR_SUCCESS) return res; if (wine_instance->vk_phys_dev != get_native_VkPhysicalDevice(our_vk_binding.physicalDevice)) WINE_WARN("VK physical device does not match that from xrGetVulkanGraphicsDeviceKHR.\n"); our_vk_binding.physicalDevice = wine_instance->vk_phys_dev; our_create_info = *createInfo; our_create_info.next = &our_vk_binding; createInfo = &our_create_info; #define X(proc) wine_instance->p_##proc = (void *)vkGetDeviceProcAddr(our_vk_binding.device, #proc); VK_PROCS #undef X command_pool_create_info.queueFamilyIndex = our_vk_binding.queueFamilyIndex; if (wine_instance->p_vkCreateCommandPool(wine_instance->vk_device, &command_pool_create_info, NULL, &wine_instance->vk_command_pool) != VK_SUCCESS) { WINE_WARN("vkCreateCommandPool failed\n"); return XR_ERROR_RUNTIME_FAILURE; } session_type = SESSION_TYPE_D3D12; break; } default: WINE_WARN("Unhandled graphics binding type: %d\n", ((XrBaseInStructure *)createInfo->next)->type); break; } } wine_session = heap_alloc_zero(sizeof(*wine_session)); res = xrCreateSession(wine_instance->instance, createInfo, &wine_session->session); if(res != XR_SUCCESS){ WINE_WARN("xrCreateSession failed: %d\n", res); heap_free(wine_session); return res; } wine_session->wine_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; WINE_TRACE("allocated wine session %p for native session %p\n", wine_session, wine_session->session); return XR_SUCCESS; } XrResult WINAPI wine_xrDestroySession(XrSession session) { wine_XrSession *wine_session = (wine_XrSession *)session; XrResult res; WINE_TRACE("%p\n", session); res = xrDestroySession(wine_session->session); if(res != XR_SUCCESS){ WINE_WARN("xrDestroySession failed: %d\n", res); return res; } heap_free(wine_session->projection_views); heap_free(wine_session->view_infos); heap_free(wine_session->composition_layers); heap_free(wine_session); return XR_SUCCESS; } XrResult WINAPI wine_xrNegotiateLoaderRuntimeInterface(const XrNegotiateLoaderInfo *info, XrNegotiateRuntimeRequest *request) { const XrNegotiateLoaderInfo_win *loaderInfo = (const XrNegotiateLoaderInfo_win *)info; XrNegotiateRuntimeRequest_win *runtimeRequest = (XrNegotiateRuntimeRequest_win *)request; XrResult res; WINE_TRACE("%p %p\n", loaderInfo, runtimeRequest); if(!loaderInfo || !runtimeRequest) return XR_ERROR_INITIALIZATION_FAILED; if((res = load_host_openxr_loader()) != XR_SUCCESS){ WINE_TRACE("host openxr loader failed to load runtime: %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_win)){ 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_win)){ return XR_ERROR_VALIDATION_FAILURE; } runtimeRequest->runtimeInterfaceVersion = XR_CURRENT_LOADER_RUNTIME_VERSION; runtimeRequest->getInstanceProcAddr = (PFN_xrGetInstanceProcAddr)&wine_xrGetInstanceProcAddr; runtimeRequest->runtimeApiVersion = api_version; return XR_SUCCESS; } XrResult WINAPI wine_xrGetVulkanGraphicsDeviceKHR(XrInstance instance, XrSystemId systemId, VkInstance vkInstance, VkPhysicalDevice *vkPhysicalDevice) { XrResult res; WINE_TRACE("%p, 0x%s, %p, %p\n", instance, wine_dbgstr_longlong(systemId), vkInstance, vkPhysicalDevice); res = ((wine_XrInstance *)instance)->funcs.p_xrGetVulkanGraphicsDeviceKHR(((wine_XrInstance *)instance)->instance, systemId, get_native_VkInstance(vkInstance), vkPhysicalDevice); *vkPhysicalDevice = get_wrapped_VkPhysicalDevice(vkInstance, *vkPhysicalDevice); return res; } XrResult WINAPI wine_xrGetVulkanGraphicsDevice2KHR(XrInstance instance, const XrVulkanGraphicsDeviceGetInfoKHR *getInfo, VkPhysicalDevice *vulkanPhysicalDevice) { XrVulkanGraphicsDeviceGetInfoKHR our_getinfo; XrResult res; WINE_TRACE("instance %p, getInfo %p, vulkanPhysicalDevice %p.\n", instance, getInfo, vulkanPhysicalDevice); if (getInfo->next) WINE_WARN("Unsupported chained structure %p.\n", getInfo->next); our_getinfo = *getInfo; our_getinfo.vulkanInstance = get_native_VkInstance(our_getinfo.vulkanInstance); res = ((wine_XrInstance *)instance)->funcs.p_xrGetVulkanGraphicsDevice2KHR(((wine_XrInstance *)instance)->instance, &our_getinfo, vulkanPhysicalDevice); if (res == XR_SUCCESS) *vulkanPhysicalDevice = get_wrapped_VkPhysicalDevice(getInfo->vulkanInstance, *vulkanPhysicalDevice); return res; } XrResult WINAPI wine_xrGetVulkanDeviceExtensionsKHR(XrInstance instance, XrSystemId systemId, uint32_t bufferCapacityInput, uint32_t *bufferCountOutput, char *buffer) { XrResult res; uint32_t buf_len = 0; char *buf; WINE_TRACE("%p, 0x%s, %u, %p, %p\n", instance, wine_dbgstr_longlong(systemId), bufferCapacityInput, bufferCountOutput, buffer); if(bufferCapacityInput == 0){ *bufferCountOutput = sizeof(WINE_VULKAN_DEVICE_EXTENSION_NAME); return XR_SUCCESS; } if(bufferCapacityInput < sizeof(WINE_VULKAN_DEVICE_EXTENSION_NAME)){ *bufferCountOutput = sizeof(WINE_VULKAN_DEVICE_EXTENSION_NAME); return XR_ERROR_SIZE_INSUFFICIENT; } res = ((wine_XrInstance *)instance)->funcs.p_xrGetVulkanDeviceExtensionsKHR( ((wine_XrInstance *)instance)->instance, systemId, 0, &buf_len, NULL); if(res != XR_SUCCESS){ WINE_WARN("xrGetVulkanDeviceExtensionsKHR failed: %d\n", res); return res; } buf = heap_alloc(buf_len); res = ((wine_XrInstance *)instance)->funcs.p_xrGetVulkanDeviceExtensionsKHR( ((wine_XrInstance *)instance)->instance, systemId, buf_len, &buf_len, buf); if(res != XR_SUCCESS){ WINE_WARN("xrGetVulkanDeviceExtensionsKHR failed: %d\n", res); heap_free(buf); return res; } WINE_TRACE("got device extensions: %s\n", buf); setenv(WINE_VULKAN_DEVICE_VARIABLE, buf, 1); heap_free(buf); memcpy(buffer, WINE_VULKAN_DEVICE_EXTENSION_NAME, sizeof(WINE_VULKAN_DEVICE_EXTENSION_NAME)); *bufferCountOutput = sizeof(WINE_VULKAN_DEVICE_EXTENSION_NAME); return XR_SUCCESS; } XrResult WINAPI wine_xrGetVulkanInstanceExtensionsKHR(XrInstance instance, XrSystemId systemId, uint32_t bufferCapacityInput, uint32_t *bufferCountOutput, char *buffer) { static const char win32_surface[] = "VK_KHR_surface VK_KHR_win32_surface"; XrResult res; uint32_t lin_len; WINE_TRACE("%p, 0x%s, %u, %p, %p\n", instance, wine_dbgstr_longlong(systemId), bufferCapacityInput, bufferCountOutput, buffer); /* Linux SteamVR does not return xlib_surface, but Windows SteamVR _does_ * return win32_surface. Some games (including hello_xr) depend on that, so * add it here. */ res = ((wine_XrInstance *)instance)->funcs.p_xrGetVulkanInstanceExtensionsKHR( ((wine_XrInstance *)instance)->instance, systemId, bufferCapacityInput, bufferCountOutput, buffer); if(res == XR_SUCCESS){ if(bufferCapacityInput > 0){ /* *bufferCountOutput is not required to (and sometimes does not) contain the offset to the NUL byte */ lin_len = strlen(buffer) + 1; if(bufferCapacityInput < lin_len + sizeof(win32_surface)) return XR_ERROR_SIZE_INSUFFICIENT; buffer[lin_len - 1] = ' '; memcpy(&buffer[lin_len], win32_surface, sizeof(win32_surface)); WINE_TRACE("returning: %s\n", buffer); *bufferCountOutput = lin_len + sizeof(win32_surface); }else{ *bufferCountOutput += sizeof(win32_surface) /* NUL byte included for required ' ' */; } } return res; } struct vk_create_instance_callback_context { wine_XrInstance *wine_instance; const XrVulkanInstanceCreateInfoKHR *xr_create_info; XrResult ret; }; static VkResult WINAPI vk_create_instance_callback(const VkInstanceCreateInfo *create_info, const VkAllocationCallbacks *allocator, VkInstance *vk_instance, void * (*pfnGetInstanceProcAddr)(VkInstance, const char *), void *context) { /* Only Unix calls here, called from the Unix side. */ struct vk_create_instance_callback_context *c = context; XrVulkanInstanceCreateInfoKHR our_create_info; VkInstanceCreateInfo our_vulkan_create_info; const char **enabled_extensions = NULL; unsigned int i; VkResult ret; our_create_info = *c->xr_create_info; our_create_info.pfnGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)pfnGetInstanceProcAddr; our_create_info.vulkanCreateInfo = create_info; our_create_info.vulkanAllocator = allocator; for (i = 0; i < create_info->enabledExtensionCount; ++i) if (!strcmp(create_info->ppEnabledExtensionNames[i], "VK_KHR_surface")) break; if (i == create_info->enabledExtensionCount) { our_vulkan_create_info = *create_info; our_create_info.vulkanCreateInfo = &our_vulkan_create_info; enabled_extensions = malloc((create_info->enabledExtensionCount + 2) * sizeof(*enabled_extensions)); memcpy(enabled_extensions, create_info->ppEnabledExtensionNames, create_info->enabledExtensionCount * sizeof(*enabled_extensions)); enabled_extensions[our_vulkan_create_info.enabledExtensionCount++] = "VK_KHR_surface"; enabled_extensions[our_vulkan_create_info.enabledExtensionCount++] = "VK_KHR_xlib_surface"; our_vulkan_create_info.ppEnabledExtensionNames = enabled_extensions; } c->ret = c->wine_instance->funcs.p_xrCreateVulkanInstanceKHR(c->wine_instance->instance, &our_create_info, vk_instance, &ret); free(enabled_extensions); return ret; } XrResult WINAPI wine_xrCreateVulkanInstanceKHR(XrInstance instance, const XrVulkanInstanceCreateInfoKHR *createInfo, VkInstance *vulkanInstance, VkResult *vulkanResult) { struct vk_create_instance_callback_context context; VkCreateInfoWineInstanceCallback callback; VkInstanceCreateInfo vulkan_create_info; WINE_TRACE("instance %p, createInfo %p, vulkanInstance %p, vulkanResult %p.\n", instance, createInfo, vulkanInstance, vulkanResult); if (createInfo->createFlags) WINE_WARN("Unexpected flags %#lx.\n", createInfo->createFlags); context.wine_instance = (wine_XrInstance *)instance; context.xr_create_info = createInfo; vulkan_create_info = *createInfo->vulkanCreateInfo; callback.sType = VK_STRUCTURE_TYPE_CREATE_INFO_WINE_INSTANCE_CALLBACK; callback.native_create_callback = vk_create_instance_callback; callback.context = &context; callback.pNext = vulkan_create_info.pNext; vulkan_create_info.pNext = &callback; *vulkanResult = p_wine_vkCreateInstance(&vulkan_create_info, createInfo->vulkanAllocator, vulkanInstance); if (context.ret == XR_SUCCESS && *vulkanResult != VK_SUCCESS) WINE_WARN("winevulkan instance creation failed after native xrCreateVulkanInstanceKHR() success.\n"); WINE_TRACE("result %d, vk result %d.\n", context.ret, *vulkanResult); return context.ret; } struct vk_create_device_callback_context { wine_XrInstance *wine_instance; const XrVulkanDeviceCreateInfoKHR *xr_create_info; XrResult ret; }; static VkResult WINAPI vk_create_device_callback(VkPhysicalDevice phys_dev, const VkDeviceCreateInfo *create_info, const VkAllocationCallbacks *allocator, VkDevice *vk_device, void * (*pfnGetInstanceProcAddr)(VkInstance, const char *), void *context) { /* Only Unix calls here, called from the Unix side. */ struct vk_create_device_callback_context *c = context; XrVulkanDeviceCreateInfoKHR our_create_info; VkResult ret; our_create_info = *c->xr_create_info; our_create_info.pfnGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)pfnGetInstanceProcAddr; our_create_info.vulkanPhysicalDevice = phys_dev; our_create_info.vulkanCreateInfo = create_info; our_create_info.vulkanAllocator = allocator; c->ret = c->wine_instance->funcs.p_xrCreateVulkanDeviceKHR(c->wine_instance->instance, &our_create_info, vk_device, &ret); return ret; } XrResult WINAPI wine_xrCreateVulkanDeviceKHR(XrInstance instance, const XrVulkanDeviceCreateInfoKHR *createInfo, VkDevice *vulkanDevice, VkResult *vulkanResult) { struct vk_create_device_callback_context context; VkCreateInfoWineDeviceCallback callback; VkDeviceCreateInfo vulkan_create_info; WINE_TRACE("instance %p, createInfo %p, vulkanDevice %p, vulkanResult %p.\n", instance, createInfo, vulkanDevice, vulkanResult); if (createInfo->createFlags) WINE_WARN("Unexpected flags %#lx.\n", createInfo->createFlags); context.wine_instance = (wine_XrInstance *)instance; context.xr_create_info = createInfo; vulkan_create_info = *createInfo->vulkanCreateInfo; callback.sType = VK_STRUCTURE_TYPE_CREATE_INFO_WINE_DEVICE_CALLBACK; callback.native_create_callback = vk_create_device_callback; callback.context = &context; callback.pNext = vulkan_create_info.pNext; vulkan_create_info.pNext = &callback; *vulkanResult = p_wine_vkCreateDevice(createInfo->vulkanPhysicalDevice, &vulkan_create_info, createInfo->vulkanAllocator, vulkanDevice); if (context.ret == XR_SUCCESS && *vulkanResult != VK_SUCCESS) WINE_WARN("winevulkan instance creation failed after native xrCreateVulkanInstanceKHR() success.\n"); WINE_TRACE("result %d, vk result %d.\n", context.ret, *vulkanResult); return context.ret; } XrResult WINAPI wine_xrPollEvent(XrInstance instance, XrEventDataBuffer *eventData) { XrResult res; WINE_TRACE("%p, %p\n", instance, eventData); res = xrPollEvent(((wine_XrInstance *)instance)->instance, eventData); WINE_TRACE("eventData->type %#x\n", eventData->type); if(res == 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 res; } XrResult WINAPI wine_xrGetSystem(XrInstance instance, const XrSystemGetInfo *getInfo, XrSystemId *systemId) { wine_XrInstance *wine_instance = (wine_XrInstance *)instance; XrResult res; WINE_TRACE("%p, %p, %p\n", instance, getInfo, systemId); res = wine_instance->funcs.p_xrGetSystem(wine_instance->instance, getInfo, systemId); if(res != XR_SUCCESS) return res; wine_instance->systemId = *systemId; return res; } 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: 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: WINE_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_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: WINE_WARN("Unable to map DXGI format (%lu) to Vulkan format\n", 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_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: WINE_WARN("Unable to map Vulkan format (%lu) to DXGI format\n", format); return DXGI_FORMAT_UNKNOWN; } } XrResult WINAPI wine_xrEnumerateSwapchainFormats(XrSession session, uint32_t formatCapacityInput, uint32_t *formatCountOutput, int64_t *formats) { wine_XrSession *wine_session = (wine_XrSession *)session; uint32_t real_format_count; int64_t *real_formats; uint32_t i, o; XrResult res; WINE_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) return xrEnumerateSwapchainFormats(wine_session->session, formatCapacityInput, formatCountOutput, formats); res = xrEnumerateSwapchainFormats(wine_session->session, 0, &real_format_count, NULL); if (res != XR_SUCCESS) return res; real_formats = heap_alloc(sizeof(*real_formats) * real_format_count); res = xrEnumerateSwapchainFormats(wine_session->session, real_format_count, formatCountOutput, real_formats); if (res != 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 res = XR_ERROR_SIZE_INSUFFICIENT; } ++o; } *formatCountOutput = o; done: heap_free(real_formats); return res; } XrResult WINAPI wine_xrCreateSwapchain(XrSession session, const XrSwapchainCreateInfo *createInfo, XrSwapchain *swapchain) { wine_XrSession *wine_session = (wine_XrSession *)session; wine_XrSwapchain *wine_swapchain; XrSwapchainCreateInfo our_createInfo; XrResult res; WINE_TRACE("%p, %p, %p\n", session, createInfo, swapchain); wine_swapchain = heap_alloc_zero(sizeof(*wine_swapchain)); wine_swapchain->create_info = *createInfo; if(wine_session->session_type == SESSION_TYPE_D3D11 || wine_session->session_type == SESSION_TYPE_D3D12){ BOOL format_is_depth; our_createInfo = *createInfo; our_createInfo.format = map_format_dxgi_to_vulkan(createInfo->format); if(our_createInfo.format == VK_FORMAT_UNDEFINED){ WINE_ERR("unable to set Vulkan format\n"); heap_free(wine_swapchain); return XR_ERROR_SWAPCHAIN_FORMAT_UNSUPPORTED; } createInfo = &our_createInfo; format_is_depth = is_vulkan_format_depth(our_createInfo.format); if ((createInfo->usageFlags & XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT) && format_is_depth){ WINE_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){ WINE_WARN("Swapchain has a depth/stencil usage, but it's format is for color\n"); } } res = xrCreateSwapchain(((wine_XrSession *)session)->session, createInfo, &wine_swapchain->swapchain); if(res != XR_SUCCESS){ WINE_WARN("xrCreateSwapchain failed: %d\n", res); heap_free(wine_swapchain); return res; } wine_swapchain->wine_session = wine_session; *swapchain = (XrSwapchain)wine_swapchain; WINE_TRACE("allocated wine swapchain %p for native swapchain %p\n", wine_swapchain, wine_swapchain->swapchain); return XR_SUCCESS; } static void release_d3d12_resources(wine_XrSwapchain *wine_swapchain, uint32_t image_count) { wine_XrInstance *wine_instance = wine_swapchain->wine_session->wine_instance; XrSwapchainImageD3D12KHR *d3d12_images = (XrSwapchainImageD3D12KHR *)wine_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); wine_instance->p_vkFreeCommandBuffers(wine_instance->vk_device, wine_instance->vk_command_pool, image_count, wine_swapchain->cmd_release); wine_instance->p_vkFreeCommandBuffers(wine_instance->vk_device, wine_instance->vk_command_pool, image_count, wine_swapchain->cmd_acquire); heap_free(wine_swapchain->cmd_release); heap_free(wine_swapchain->cmd_acquire); heap_free(wine_swapchain->acquired); heap_free(wine_swapchain->acquired_indices); } XrResult WINAPI wine_xrDestroySwapchain(XrSwapchain swapchain) { wine_XrSwapchain *wine_swapchain = (wine_XrSwapchain *)swapchain; XrResult res; WINE_TRACE("%p\n", swapchain); if (wine_swapchain->image_count){ UINT i; if (wine_swapchain->wine_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->wine_session->session_type == SESSION_TYPE_D3D12) release_d3d12_resources(wine_swapchain, wine_swapchain->image_count); heap_free(wine_swapchain->images); wine_swapchain->image_count = 0; } res = xrDestroySwapchain(wine_swapchain->swapchain); if(res != XR_SUCCESS){ WINE_WARN("xrDestroySwapchain failed: %d\n", res); return res; } heap_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", 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 = instance->p_vkAllocateCommandBuffers(instance->vk_device, &command_buffer_allocate_info, out_cmd)) != VK_SUCCESS) return ret; if ((ret = instance->p_vkBeginCommandBuffer(*out_cmd, &begin_info)) != VK_SUCCESS) return ret; instance->p_vkCmdPipelineBarrier(*out_cmd, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, 0, 0, NULL, 0, NULL, 1, &barrier); return instance->p_vkEndCommandBuffer(*out_cmd); } XrResult WINAPI wine_xrEnumerateSwapchainImages(XrSwapchain swapchain, uint32_t imageCapacityInput, uint32_t *imageCountOutput, XrSwapchainImageBaseHeader *images) { wine_XrSwapchain *wine_swapchain = (wine_XrSwapchain *)swapchain; wine_XrInstance *wine_instance = wine_swapchain->wine_session->wine_instance; XrResult res; XrSwapchainImageVulkanKHR *our_vk = NULL; HRESULT hr; size_t image_size = 0; uint32_t i; WINE_TRACE("%p, %u, %p, %p\n", swapchain, imageCapacityInput, imageCountOutput, images); if (wine_swapchain->wine_session->session_type != SESSION_TYPE_D3D11 && wine_swapchain->wine_session->session_type != SESSION_TYPE_D3D12) return xrEnumerateSwapchainImages(wine_swapchain->swapchain, imageCapacityInput, imageCountOutput, images); if (!wine_swapchain->image_count) { uint32_t image_count; res = xrEnumerateSwapchainImages(wine_swapchain->swapchain, 0, &image_count, NULL); if (res != XR_SUCCESS) return res; our_vk = heap_alloc(sizeof(*our_vk) * image_count); for(i = 0; i < image_count; ++i){ our_vk[i].type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; } res = xrEnumerateSwapchainImages(wine_swapchain->swapchain, image_count, &image_count, (XrSwapchainImageBaseHeader *)our_vk); if (res != XR_SUCCESS){ heap_free(our_vk); return res; } if(wine_swapchain->wine_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; WINE_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 = heap_alloc(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); } heap_free(our_d3d11); heap_free(our_vk); WINE_WARN("Failed to create DXVK texture from VkImage: %08x\n", hr); return XR_ERROR_RUNTIME_FAILURE; } WINE_TRACE("Successfully allocated texture %p\n", our_d3d11[i].texture); } wine_swapchain->images = (XrSwapchainImageBaseHeader *)our_d3d11; } else if(wine_swapchain->wine_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)) { WINE_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; WINE_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 = heap_alloc_zero(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)) { WINE_ERR("Failed to create vkd3d-proton texture from VkImage: %08x\n", hr); succeeded = FALSE; break; } WINE_TRACE("Successfully allocated texture %p\n", our_d3d12[i].texture); } device_ext->lpVtbl->Release(device_ext); if (succeeded) { wine_swapchain->acquired = heap_alloc_zero(sizeof(BOOL) * image_count); wine_swapchain->acquired_indices = heap_alloc(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 = heap_alloc_zero(sizeof(VkCommandBuffer) * image_count); wine_swapchain->cmd_acquire = heap_alloc_zero(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) { WINE_ERR("Failed to create command buffer for layout transition\n"); succeeded = FALSE; break; } } } if (!succeeded) { release_d3d12_resources(wine_swapchain, image_count); heap_free(wine_swapchain->images); wine_swapchain->images = NULL; heap_free(our_vk); return XR_ERROR_RUNTIME_FAILURE; } } heap_free(our_vk); wine_swapchain->image_count = image_count; WINE_TRACE("image count %d\n", image_count); } *imageCountOutput = wine_swapchain->image_count; if (wine_swapchain->wine_session->session_type == SESSION_TYPE_D3D11){ image_size = sizeof(XrSwapchainImageD3D11KHR); } else if (wine_swapchain->wine_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; } 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; WINE_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_XrSwapchain *)out_layer->cube.swapchain)->swapchain; break; } case XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR: out_layer->cylinder = *(const XrCompositionLayerCylinderKHR *)in_layer; out_layer->cylinder.subImage.swapchain = ((wine_XrSwapchain *)out_layer->cylinder.subImage.swapchain)->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_XrSwapchain *)out_layer->depth_info.subImage.swapchain)->swapchain; break; case XR_TYPE_COMPOSITION_LAYER_EQUIRECT_KHR: out_layer->equirect = *(const XrCompositionLayerEquirectKHR *)in_layer; out_layer->equirect.subImage.swapchain = ((wine_XrSwapchain *)out_layer->equirect.subImage.swapchain)->swapchain; break; case XR_TYPE_COMPOSITION_LAYER_EQUIRECT2_KHR: out_layer->equirect2 = *(const XrCompositionLayerEquirect2KHR *)in_layer; out_layer->equirect2.subImage.swapchain = ((wine_XrSwapchain *)out_layer->equirect2.subImage.swapchain)->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 = heap_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 = heap_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) WINE_WARN("Unexpected view type %u.\n", view->type); ((XrCompositionLayerProjectionView *)view)->subImage.swapchain = ((wine_XrSwapchain *)view->subImage.swapchain)->swapchain; while (view->next) { WINE_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_XrSwapchain *)out_depth_info->subImage.swapchain)->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_XrSwapchain *)out_warp_info->motionVectorSubImage.swapchain)->swapchain; out_warp_info->depthSubImage.swapchain = ((wine_XrSwapchain *)out_warp_info->depthSubImage.swapchain)->swapchain; ((XrCompositionLayerProjectionView *)view)->next = out_warp_info; break; } default: WINE_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_XrSwapchain *)out_layer->quad.subImage.swapchain)->swapchain; break; default: WINE_WARN("Unknown composition in_layer type: %d\n", in_layer->type); return (XrCompositionLayerBaseHeader *)in_layer; } return (XrCompositionLayerBaseHeader *)out_layer; } XrResult WINAPI wine_xrEndFrame(XrSession session, const XrFrameEndInfo *frameEndInfo) { wine_XrSession *wine_session = (wine_XrSession *)session; uint32_t i, view_idx = 0, view_info_idx = 0; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device; XrFrameEndInfo our_frameEndInfo; XrResult res; WINE_TRACE("%p, %p\n", session, frameEndInfo); if(frameEndInfo->layerCount > wine_session->composition_layer_count){ heap_free(wine_session->composition_layers); wine_session->composition_layers = heap_alloc(frameEndInfo->layerCount * sizeof(*wine_session->composition_layers)); heap_free(wine_session->composition_layer_ptrs); wine_session->composition_layer_ptrs = heap_alloc(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->wine_instance->dxvk_device)) { WINE_TRACE("Locking submission queue.\n"); dxvk_device->lpVtbl->FlushRenderingCommands(dxvk_device); dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); } else if ((d3d12_device = wine_session->wine_instance->d3d12_device)) { WINE_TRACE("Locking vkd3d-proton submission queue.\n"); d3d12_device->lpVtbl->LockCommandQueue(d3d12_device, wine_session->wine_instance->d3d12_queue); } res = xrEndFrame(((wine_XrSession *)session)->session, &our_frameEndInfo); if (dxvk_device) dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); else if (d3d12_device) d3d12_device->lpVtbl->UnlockCommandQueue(d3d12_device, wine_session->wine_instance->d3d12_queue); return res; } XrResult WINAPI wine_xrBeginFrame(XrSession session, const XrFrameBeginInfo *frameBeginInfo) { wine_XrSession *wine_session = (wine_XrSession *)session; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device; XrResult ret; WINE_TRACE("%p, %p\n", session, frameBeginInfo); if ((dxvk_device = wine_session->wine_instance->dxvk_device)) dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); else if ((d3d12_device = wine_session->wine_instance->d3d12_device)) d3d12_device->lpVtbl->LockCommandQueue(d3d12_device, wine_session->wine_instance->d3d12_queue); ret = xrBeginFrame(((wine_XrSession *)session)->session, frameBeginInfo); if (dxvk_device) dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); else if (d3d12_device) d3d12_device->lpVtbl->UnlockCommandQueue(d3d12_device, wine_session->wine_instance->d3d12_queue); return ret; } XrResult WINAPI wine_xrAcquireSwapchainImage(XrSwapchain swapchain, const XrSwapchainImageAcquireInfo *acquireInfo, uint32_t *index) { wine_XrSwapchain *wine_swapchain = (wine_XrSwapchain *)swapchain; wine_XrSession *wine_session = wine_swapchain->wine_session; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device; XrResult ret; WINE_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_session->wine_instance->dxvk_device)) dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); else if ((d3d12_device = wine_session->wine_instance->d3d12_device)) { if (wine_swapchain->acquired_count >= wine_swapchain->image_count) { WINE_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_session->wine_instance->d3d12_queue); } ret = xrAcquireSwapchainImage(((wine_XrSwapchain *)swapchain)->swapchain, acquireInfo, index); if (dxvk_device) { dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); return ret; } if (!d3d12_device) return ret; if (ret == 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], }; wine_session->wine_instance->p_vkQueueSubmit(wine_session->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 WINE_WARN("the application acquired the same image (index %d) again!?", *index); } d3d12_device->lpVtbl->UnlockCommandQueue(d3d12_device, wine_session->wine_instance->d3d12_queue); return ret; } XrResult WINAPI wine_xrReleaseSwapchainImage(XrSwapchain swapchain, const XrSwapchainImageReleaseInfo *releaseInfo) { wine_XrSwapchain *wine_swapchain = (wine_XrSwapchain *)swapchain; wine_XrSession *wine_session = wine_swapchain->wine_session; IDXGIVkInteropDevice2 *dxvk_device; ID3D12DXVKInteropDevice *d3d12_device = wine_session->wine_instance->d3d12_device; XrResult ret; uint32_t index; WINE_TRACE("%p, %p\n", swapchain, releaseInfo); if ((dxvk_device = wine_session->wine_instance->dxvk_device)) dxvk_device->lpVtbl->LockSubmissionQueue(dxvk_device); else if (d3d12_device) { if (wine_swapchain->acquired_count == 0) { WINE_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_session->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], }; wine_session->wine_instance->p_vkQueueSubmit(wine_session->wine_instance->vk_queue, 1, &submit_info, VK_NULL_HANDLE); } } ret = xrReleaseSwapchainImage(((wine_XrSwapchain *)swapchain)->swapchain, releaseInfo); if (dxvk_device) { dxvk_device->lpVtbl->ReleaseSubmissionQueue(dxvk_device); return ret; } if (!d3d12_device) return ret; if (ret != 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], }; WINE_WARN("xrReleaseSwapchainImage failed, reverting layout transition\n"); wine_session->wine_instance->p_vkQueueSubmit(wine_session->wine_instance->vk_queue, 1, &submit_info, VK_NULL_HANDLE); } if (ret == 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_session->wine_instance->d3d12_queue); return ret; } /* wineopenxr API */ XrResult WINAPI __wineopenxr_GetVulkanInstanceExtensions(uint32_t buflen, uint32_t *outlen, char *buf) { XrResult res; WINE_TRACE("\n"); if((res = load_host_openxr_loader()) != XR_SUCCESS){ WINE_TRACE("host openxr loader failed to load runtime: %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; WINE_TRACE("\n"); if((res = load_host_openxr_loader()) != XR_SUCCESS){ WINE_TRACE("host openxr loader failed to load runtime: %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; } WINE_TRACE("g_device_extensions %s.\n", g_device_extensions); setenv(WINE_VULKAN_DEVICE_VARIABLE, g_device_extensions, 1); *outlen = strlen(WINE_VULKAN_DEVICE_EXTENSION_NAME) + 1; strcpy(buf, WINE_VULKAN_DEVICE_EXTENSION_NAME); return XR_SUCCESS; } void register_dispatchable_handle(uint64_t handle, struct openxr_instance_funcs *funcs) { struct handle_instance_lookup_entry *entry; entry = heap_alloc_zero(sizeof(*entry)); entry->handle = handle; entry->funcs = funcs; AcquireSRWLockExclusive(&handle_instance_lookup_lock); rb_put(&handle_instance_lookup, &handle, &entry->entry); ReleaseSRWLockExclusive(&handle_instance_lookup_lock); } void unregister_dispatchable_handle(uint64_t handle) { struct rb_entry *entry; AcquireSRWLockExclusive(&handle_instance_lookup_lock); if ((entry = rb_get(&handle_instance_lookup, &handle))) rb_remove(&handle_instance_lookup, entry); ReleaseSRWLockExclusive(&handle_instance_lookup_lock); if (entry) heap_free(entry); else WINE_ERR("handle %s not found.\n", wine_dbgstr_longlong(handle)); } struct openxr_instance_funcs *get_dispatch_table(uint64_t handle) { struct openxr_instance_funcs *funcs = NULL; struct rb_entry *entry; AcquireSRWLockExclusive(&handle_instance_lookup_lock); if ((entry = rb_get(&handle_instance_lookup, &handle))) funcs = RB_ENTRY_VALUE(entry, struct handle_instance_lookup_entry, entry)->funcs; ReleaseSRWLockExclusive(&handle_instance_lookup_lock); if (!funcs) { WINE_ERR("handle %s not found.\n", wine_dbgstr_longlong(handle)); funcs = &last_instance->funcs; } return funcs; }