/* Wine Vulkan ICD implementation * * Copyright 2017 Roderick Colenbrander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include "windef.h" #include "winbase.h" #include "winreg.h" #include "winuser.h" #include "vulkan_private.h" WINE_DEFAULT_DEBUG_CHANNEL(vulkan); /* For now default to 4 as it felt like a reasonable version feature wise to support. * Version 5 adds more extensive version checks. Something to tackle later. */ #define WINE_VULKAN_ICD_VERSION 4 static HINSTANCE hinstance; static void *wine_vk_get_global_proc_addr(const char *name); VkResult WINAPI wine_vkEnumerateInstanceLayerProperties(uint32_t *count, VkLayerProperties *properties) { TRACE("%p, %p\n", count, properties); *count = 0; return VK_SUCCESS; } static const struct vulkan_func vk_global_dispatch_table[] = { /* These functions must call wine_vk_init_once() before accessing vk_funcs. */ {"vkCreateInstance", &wine_vkCreateInstance}, {"vkEnumerateInstanceExtensionProperties", &wine_vkEnumerateInstanceExtensionProperties}, {"vkEnumerateInstanceLayerProperties", &wine_vkEnumerateInstanceLayerProperties}, {"vkEnumerateInstanceVersion", &wine_vkEnumerateInstanceVersion}, {"vkGetInstanceProcAddr", &wine_vkGetInstanceProcAddr}, }; static void *wine_vk_get_global_proc_addr(const char *name) { unsigned int i; for (i = 0; i < ARRAY_SIZE(vk_global_dispatch_table); i++) { if (strcmp(name, vk_global_dispatch_table[i].name) == 0) { TRACE("Found name=%s in global table\n", debugstr_a(name)); return vk_global_dispatch_table[i].func; } } return NULL; } PFN_vkVoidFunction WINAPI wine_vkGetInstanceProcAddr(VkInstance instance, const char *name) { void *func; TRACE("%p, %s\n", instance, debugstr_a(name)); if (!name) return NULL; /* vkGetInstanceProcAddr can load most Vulkan functions when an instance is passed in, however * for a NULL instance it can only load global functions. */ func = wine_vk_get_global_proc_addr(name); if (func) { return func; } if (!instance) { WARN("Global function %s not found.\n", debugstr_a(name)); return NULL; } func = wine_vk_get_instance_proc_addr(name); if (func) return func; func = wine_vk_get_phys_dev_proc_addr(name); if (func) return func; /* vkGetInstanceProcAddr also loads any children of instance, so device functions as well. */ func = wine_vk_get_device_proc_addr(name); if (func) return func; WARN("Unsupported device or instance function: %s.\n", debugstr_a(name)); return NULL; } PFN_vkVoidFunction WINAPI wine_vkGetDeviceProcAddr(VkDevice device, const char *name) { void *func; TRACE("%p, %s\n", device, debugstr_a(name)); /* The spec leaves return value undefined for a NULL device, let's just return NULL. */ if (!device || !name) return NULL; /* Per the spec, we are only supposed to return device functions as in functions * for which the first parameter is vkDevice or a child of vkDevice like a * vkCommandBuffer or vkQueue. * Loader takes care of filtering of extensions which are enabled or not. */ func = wine_vk_get_device_proc_addr(name); if (func) return func; /* vkGetDeviceProcAddr was intended for loading device and subdevice functions. * idTech 6 titles such as Doom and Wolfenstein II, however use it also for * loading of instance functions. This is undefined behavior as the specification * disallows using any of the returned function pointers outside of device / * subdevice objects. The games don't actually use the function pointers and if they * did, they would crash as VkInstance / VkPhysicalDevice parameters need unwrapping. * Khronos clarified behavior in the Vulkan spec and expects drivers to get updated, * however it would require both driver and game fixes. * https://github.com/KhronosGroup/Vulkan-LoaderAndValidationLayers/issues/2323 * https://github.com/KhronosGroup/Vulkan-Docs/issues/655 */ if (device->quirks & WINEVULKAN_QUIRK_GET_DEVICE_PROC_ADDR && ((func = wine_vk_get_instance_proc_addr(name)) || (func = wine_vk_get_phys_dev_proc_addr(name)))) { WARN("Returning instance function %s.\n", debugstr_a(name)); return func; } WARN("Unsupported device function: %s.\n", debugstr_a(name)); return NULL; } void * WINAPI wine_vk_icdGetPhysicalDeviceProcAddr(VkInstance instance, const char *name) { TRACE("%p, %s\n", instance, debugstr_a(name)); return wine_vk_get_phys_dev_proc_addr(name); } void * WINAPI wine_vk_icdGetInstanceProcAddr(VkInstance instance, const char *name) { TRACE("%p, %s\n", instance, debugstr_a(name)); /* Initial version of the Vulkan ICD spec required vkGetInstanceProcAddr to be * exported. vk_icdGetInstanceProcAddr was added later to separate ICD calls from * Vulkan API. One of them in our case should forward to the other, so just forward * to the older vkGetInstanceProcAddr. */ return wine_vkGetInstanceProcAddr(instance, name); } VkResult WINAPI wine_vk_icdNegotiateLoaderICDInterfaceVersion(uint32_t *supported_version) { uint32_t req_version; TRACE("%p\n", supported_version); /* The spec is not clear how to handle this. Mesa drivers don't check, but it * is probably best to not explode. VK_INCOMPLETE seems to be the closest value. */ if (!supported_version) return VK_INCOMPLETE; req_version = *supported_version; *supported_version = min(req_version, WINE_VULKAN_ICD_VERSION); TRACE("Loader requested ICD version %u, returning %u\n", req_version, *supported_version); return VK_SUCCESS; } BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, void *reserved) { TRACE("%p, %u, %p\n", hinst, reason, reserved); switch (reason) { case DLL_PROCESS_ATTACH: hinstance = hinst; DisableThreadLibraryCalls(hinst); break; } return TRUE; } static const WCHAR winevulkan_json_resW[] = {'w','i','n','e','v','u','l','k','a','n','_','j','s','o','n',0}; static const WCHAR winevulkan_json_pathW[] = {'\\','w','i','n','e','v','u','l','k','a','n','.','j','s','o','n',0}; static const WCHAR vulkan_driversW[] = {'S','o','f','t','w','a','r','e','\\','K','h','r','o','n','o','s','\\', 'V','u','l','k','a','n','\\','D','r','i','v','e','r','s',0}; HRESULT WINAPI DllRegisterServer(void) { WCHAR json_path[MAX_PATH]; HRSRC rsrc; const char *data; DWORD datalen, written, zero = 0; HANDLE file; HKEY key; /* Create the JSON manifest and registry key to register this ICD with the official Vulkan loader. */ TRACE("\n"); rsrc = FindResourceW(hinstance, winevulkan_json_resW, (const WCHAR *)RT_RCDATA); data = LockResource(LoadResource(hinstance, rsrc)); datalen = SizeofResource(hinstance, rsrc); GetSystemDirectoryW(json_path, ARRAY_SIZE(json_path)); lstrcatW(json_path, winevulkan_json_pathW); file = CreateFileW(json_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (file == INVALID_HANDLE_VALUE) { ERR("Unable to create JSON manifest.\n"); return E_UNEXPECTED; } WriteFile(file, data, datalen, &written, NULL); CloseHandle(file); if (!RegCreateKeyExW(HKEY_LOCAL_MACHINE, vulkan_driversW, 0, NULL, 0, KEY_SET_VALUE, NULL, &key, NULL)) { RegSetValueExW(key, json_path, 0, REG_DWORD, (const BYTE *)&zero, sizeof(zero)); RegCloseKey(key); } return S_OK; } HRESULT WINAPI DllUnregisterServer(void) { WCHAR json_path[MAX_PATH]; HKEY key; /* Remove the JSON manifest and registry key */ TRACE("\n"); GetSystemDirectoryW(json_path, ARRAY_SIZE(json_path)); lstrcatW(json_path, winevulkan_json_pathW); DeleteFileW(json_path); if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, vulkan_driversW, 0, KEY_SET_VALUE, &key) == ERROR_SUCCESS) { RegDeleteValueW(key, json_path); RegCloseKey(key); } return S_OK; }