RFC winevulkan design

Roderick Colenbrander thunderbird2k at gmail.com
Fri Nov 3 02:20:36 CDT 2017


Hi all,

Based on conversations on IRC, I reconsidered the design of the ICD.
The idea is to have a thin winevulkan proxy library. It hosts the thunks
and loads the appropriate ICD based the HWND similar how opengl32 operates.

In general the driver (e.g. winex11) is responsible for loading the required
function pointers used by the thunks. These function pointers are stored
in the Vulkan objects (Vulkan operates exactly the same way).

The driver can decide by itself for which APIs it needs to provide a custom
implementation. Usually this are at least vkCreateInstance, vkCreateDevice
and others.

I wrote some pseudo code into this RFC patch to illustrate the design.
Here and there I added some detailed comments and questions. 
The code obviously does not compile and variable/function names are just
for illustration purposes.

Let me know your thoughts.

Thanks,
Roderick

---
 dlls/winevulkan/vulkan.c          | 100 ++++++++++++++++++++++++++++++++++++++
 dlls/winevulkan/vulkan_thunks.c   |  80 ++++++++++++++++++++++++++++++
 dlls/winex11.drv/vulkan.c         |  77 +++++++++++++++++++++++++++++
 dlls/winex11.drv/winex11.drv.spec |   4 ++
 include/wine/vulkan_driver.h      |  78 +++++++++++++++++++++++++++++
 5 files changed, 339 insertions(+)
 create mode 100644 dlls/winevulkan/vulkan.c
 create mode 100644 dlls/winevulkan/vulkan_thunks.c
 create mode 100644 dlls/winex11.drv/vulkan.c
 create mode 100644 include/wine/vulkan_driver.h

diff --git a/dlls/winevulkan/vulkan.c b/dlls/winevulkan/vulkan.c
new file mode 100644
index 0000000000..fd73acaf10
--- /dev/null
+++ b/dlls/winevulkan/vulkan.c
@@ -0,0 +1,100 @@
+#include "wine/vulkan.h"
+#include "wine/vulkan_driver.h"
+
+/* Vulkan global functions. Stateless functions of which there very few,
+ * but which we need to call directly outside of dispatch table to the driver.
+ */
+
+MAKE_FUNCPTR(vkCreateInstance)
+MAKE_FUNCPTR(vkEnumerateInstanceExtensionProperties)
+MAKE_FUNCPTR(vkGetInstanceProcAddr)
+
+const struct vulkan_func vk_global_dispatch_table[] = {
+    {"vkCreateInstance", NULL, &wine_vkCreateInstance},
+    {"vkEnumerateInstanceExtensionProperties", NULL, &wine_vkEnumerateInstanceExtensionProperties},
+    {"vkGetInstanceProcAddr", NULL, &wine_vkGetInstanceProcAddr},
+};
+const int vk_global_dispatch_table_size = 3;
+
+void* wine_vk_get_global_proc_addr(pName)
+{
+    //search global dispatch table
+}
+
+
+static BOOL vulkan_initialized = FALSE;
+static struct vulkan_icd_funcs *icd_funcs = NULL;
+
+static BOOL wine_vk_init(void)
+{
+    HDC hdc = GetDC(0)
+    vulkan_funcs =  __wine_get_vk_driver(hdc);
+    if (!icd_funcs)
+    {
+        ReleaseDC(hdc);
+        return FALSE;
+    }
+
+#define LOAD_FUNCPTR(x) icd_funcs->vk_icdGetInstanceProcAddr(x)
+    LOAD_FUNCPTR(vkCreateInstance)
+    LOAD_FUNCPTR(vkEnumerateInstanceExtensionProperties)
+    LOAD_FUNCPTR(vkGetInstanceProcAddr)
+
+    ReleaseDC(hdc)
+    return TRUE;
+}
+
+
+/* Implementation of global Vulkan functions, which are state-less.
+ * There are only a handful of these. */
+
+static PFN_vkVoidFunction WINE_VKAPI wine_vkGetInstanceProcAddr(VkInstance instance, const char* pName)
+{
+    return wine_vk_icdGetInstanceProcAddr(instance, pName)
+}
+
+static VkResult WINE_VKAPI wine_vkCreateInstance(..)
+{
+    return icd_funcs->vkCreateInstance(..)
+}
+
+static VkResult WINE_VKAPI wine_vkEnumerateInstanceExtensionProperties(const char *pLayerName, uint32_t *pPropertyCount,
+        VkExtensionProperties* pProperties)
+{
+    return icd_funcs->vkEnumerateInstanceExtensionProperties(..)
+}
+
+
+/* ICD entry points, only 2 exist these days. */
+
+void* WINAPI vk_icdGetInstanceProcAddr(VkInstance instance, const char* pName)
+{
+    if (!wine_vk_init())
+        return NULL;
+
+    if (!instance)
+    {
+        return wine_vk_get_global_proc_addr(pName);
+    }
+
+    /* Now find any instance function. */
+    func = wine_vk_get_instance_proc_addr(pName);
+    if (func)
+        return func;
+
+    func = wine_vk_get_device_proc_addr(pName);
+    if (func)
+        return func;
+
+    TRACE("Function %s not found\n", pName);
+    return NULL;
+}
+
+VkResult WINAPI vk_icdNegotiateLoaderICDInterfaceVersion(uint32_t* pSupportedVersion)
+{
+    if (!wine_vk_init())
+        return VK_ERROR_INCOMPATIBLE_DRIVER;
+
+    *pSupportedVersion = min(*pSupportedVersion, WINE_VULKAN_ICD_VERSION);
+    return VK_SUCCESS;
+}
diff --git a/dlls/winevulkan/vulkan_thunks.c b/dlls/winevulkan/vulkan_thunks.c
new file mode 100644
index 0000000000..0dfb5ac7fd
--- /dev/null
+++ b/dlls/winevulkan/vulkan_thunks.c
@@ -0,0 +1,80 @@
+/* Automatically generated from Vulkan vk.xml; DO NOT EDIT! */
+
+#include "wine/debug.h"
+#include "wine/vulkan.h"
+#include "vulkan_private.h"
+
+WINE_DEFAULT_DEBUG_CHANNEL(vulkan);
+
+static VkResult WINAPI wine_vkAllocateCommandBuffers(VkDevice device, const VkCommandBufferAllocateInfo * pAllocateInfo, VkCommandBuffer * pCommandBuffers)
+{
+    TRACE(" %p, %p, %p\n", device, pAllocateInfo, pCommandBuffers);
+    /* Vulkan excepts dispatch tables to stored in 'dispatchable' objects.
+     * Each object will have a dispatch table to either the 'instance' or 'device' table.
+     * This helps us as the graphics driver winex11 will fill this function table.
+     * Another nice benefit is that the native libvulkan-1.so as used by winex11 may
+     * load multiple ICDs behind our back in a multi-gpu system, but we don't have to bother
+     * with any of the details as the function pointers will always be right as they are per object.
+     */
+    return device->funcs->vkAllocateCommandBuffers(device, pAllocateInfo, pCommandBuffers);
+}
+..
+..
+static void WINAPI X11DRV_vkGetPhysicalDeviceFeatures(VkPhysicalDevice physicalDevice, VkPhysicalDeviceFeatures * pFeatures)
+{
+    TRACE("%p, %p\n", physicalDevice, pFeatures);
+    physicalDevice->funcs.p_vkGetPhysicalDeviceFeatures(physicalDevice->phys_dev, pFeatures);
+}
+
+
+const struct vulkan_func vk_device_dispatch_table[] = {
+    {"vkAllocateCommandBuffers", NULL, &wine_vkAllocateCommandBuffers},
+    ..
+    {"vkWaitForFences", NULL, &wine_vkWaitForFences},
+};
+const int vk_device_dispatch_table_size = 120;
+
+const struct vulkan_func vk_instance_dispatch_table[] = {
+    {"vkCreateDevice", NULL, &wine_vkCreateDevice},
+    {"vkDestroyInstance", NULL, &wine_vkDestroyInstance},
+    {"vkEnumerateDeviceExtensionProperties", NULL, &wine_vkEnumerateDeviceExtensionProperties},
+    {"vkEnumerateDeviceLayerProperties", NULL, &wine_vkEnumerateDeviceLayerProperties},
+    {"vkEnumeratePhysicalDevices", NULL, &wine_vkEnumeratePhysicalDevices},
+    {"vkGetDeviceProcAddr", NULL, &wine_vkGetDeviceProcAddr},
+    {"vkGetPhysicalDeviceFeatures", NULL, &wine_vkGetPhysicalDeviceFeatures},
+    {"vkGetPhysicalDeviceFormatProperties", NULL, &wine_vkGetPhysicalDeviceFormatProperties},
+    {"vkGetPhysicalDeviceImageFormatProperties", NULL, &wine_vkGetPhysicalDeviceImageFormatProperties},
+    {"vkGetPhysicalDeviceMemoryProperties", NULL, &wine_vkGetPhysicalDeviceMemoryProperties},
+    {"vkGetPhysicalDeviceProperties", NULL, &wine_vkGetPhysicalDeviceProperties},
+    {"vkGetPhysicalDeviceQueueFamilyProperties", NULL, &wine_vkGetPhysicalDeviceQueueFamilyProperties},
+    {"vkGetPhysicalDeviceSparseImageFormatProperties", NULL, &wine_vkGetPhysicalDeviceSparseImageFormatProperties},
+};
+const int vk_instance_dispatch_table_size = 13;
+
+void* wine_vk_get_device_proc_addr(const char* name)
+{
+    int i;
+    for (i = 0; i < vk_device_dispatch_table_size; i++)
+    {
+        if (strcmp(name, vk_device_dispatch_table[i].name) == 0)
+        {
+            TRACE("Found pName=%s in device table\n", name);
+            return vk_device_dispatch_table[i].func;
+        }
+    }
+    return NULL;
+}
+
+void* wine_vk_get_instance_proc_addr(const char* name)
+{
+    int i;
+    for (i = 0; i < vk_instance_dispatch_table_size; i++)
+    {
+        if (strcmp(name, vk_instance_dispatch_table[i].name) == 0)
+        {
+            TRACE("Found pName=%s in instance table\n", name);
+            return vk_instance_dispatch_table[i].func;
+        }
+    }
+    return NULL;
+}
diff --git a/dlls/winex11.drv/vulkan.c b/dlls/winex11.drv/vulkan.c
new file mode 100644
index 0000000000..5b83d43360
--- /dev/null
+++ b/dlls/winex11.drv/vulkan.c
@@ -0,0 +1,77 @@
+struct vk_global_functions =
+{
+    {"vkCreateInstance", X11DRV_vkCreateInstance},
+    ..
+}
+
+/* Functions creating dispatchable objects and a few others (e.g. vkGetDeviceQueue)
+ * need special implementations. */
+struct vk_overrides =
+{
+    {"vkCreateDevice", X11DRV_vkCreateDevice}
+    ..
+}
+
+struct winex11_vkinstance
+{
+    struct VkInstance_T;
+
+    /* Native instance */
+    VkInstance *instance;
+};
+
+static VkResult WINE_VKAPI X11DRV_vkCreateInstance(..)
+{
+    instance = wine_vk_alloc_dispatchable_object(..);
+
+    pvkCreateInstance(,, &instance->instance);
+
+    /* Load all instance functions we are aware of. Note the loader takes care
+     * of any filtering for extensions which were not requested, but which the
+     * ICD may support.
+     */
+#define USE_VK_FUNC(name) \
+    instance->funcs.p_##name = (void*)pvkGetInstanceProcAddr(instance->instance, #name);
+    ALL_VK_INSTANCE_FUNCS()
+#undef USE_VK_FUNC
+
+    return VK_SUCCESS;
+}
+
+
+static VkResult WINE_VKAPI X11DRV_vkCreateDevice(..)
+{
+    device = wine_vk_alloc_dispatchable_object(..);
+
+    pvkCreateDevice(,, &device->device);
+
+    /* Just load all function pointers we are aware off. The loader takes care of filtering. */
+#define USE_VK_FUNC(name) \
+    device->funcs.p_##name = (void*)pvkGetDeviceProcAddr(device->device, #name); \
+    if (device->funcs.p_##name == NULL) \
+        TRACE("Not found %s\n", #name);
+    ALL_VK_DEVICE_FUNCS()
+#undef USE_VK_FUNC
+
+    return VK_SUCCESS;
+}
+
+void* X11DRV_vk_icdGetInstanceProcAddr(VkInstance instance, const char* pName)
+{
+    if (!instance)
+    {
+        // search vk_global_functions
+    }
+
+    // search table of overrides
+
+    /* Ask the native ICD library. Since VkInstance is a dispatchable object
+     * retrieve the native instance and pass it down.
+     */
+    return native_vkGetInstanceProcAddr(instance->native_instance, pName);
+}
+
+VkResult X11DRV_vk_icdNegotiateLoaderICDInterfaceVersion(uint32_t* pSupportedVersion)
+{
+    return VK_INCOMPLETE;
+}
diff --git a/dlls/winex11.drv/winex11.drv.spec b/dlls/winex11.drv/winex11.drv.spec
index 614f0b90f4..d701488b31 100644
--- a/dlls/winex11.drv/winex11.drv.spec
+++ b/dlls/winex11.drv/winex11.drv.spec
@@ -78,3 +78,7 @@
 @ stdcall ImeProcessKey(long long long ptr)
 @ stdcall ImeGetRegisterWordStyle(long ptr)
 @ stdcall ImeGetImeMenuItems(long long long ptr ptr long)
+
+# Vulkan
+@ cdecl vk_icdGetInstanceProcAddr(ptr str) X11DRV_vk_icdGetInstanceProcAddr
+@ cdecl vk_icdNegotiateLoaderICDInterfaceVersion(ptr) X11DRV_vk_icdNegotiateLoaderICDInterfaceVersion
diff --git a/include/wine/vulkan_driver.h b/include/wine/vulkan_driver.h
new file mode 100644
index 0000000000..eb3159f098
--- /dev/null
+++ b/include/wine/vulkan_driver.h
@@ -0,0 +1,78 @@
+/* I'm thinking of using 'WINE_VKAPI' as the calling convention for all calls implemented
+ * by winex11/android. This convention should be the same as the calling convention used
+ * by the native Vulkan library.
+ */
+#define WINE_VKAPI
+
+/* Struct returned by gdi32 back to winevulkan. */
+struct vulkan_icd_funcs
+{
+    /* To use WINE_VKAPI calling convention? */
+    Function Pointer to vk_icdGetInstanceProcAddr
+    /* Technically this is an ICD export, but we may not need, will see. */
+    Function Pointer to vk_icdNegotiateLoaderICDInterfaceVersion
+};
+
+struct vulkan_device_funcs
+{
+    vkAllocateCommandBuffers;
+    ..
+    vkWaitForFences;
+};
+
+struct vulkan_instance_funcs
+{
+    vkCreateDevice;
+    ..
+    vkGetPhysicalDeviceSparseImageFormatProperties;
+};
+
+#define ALL_VK_DEVICE_FUNCS() \
+    USE_VK_FUNC(vkAllocateCommandBuffers) \
+    USE_VK_FUNC(vkAllocateDescriptorSets) \
+    USE_VK_FUNC(vkAllocateMemory) \
+    ..
+
+
+#define ALL_VK_INSTANCE_FUNCS() \
+    USE_VK_FUNC(vkCreateDevice)\
+    USE_VK_FUNC(vkDestroyInstance)\
+    USE_VK_FUNC(vkEnumerateDeviceExtensionProperties)\
+    ..
+
+
+/* Magic value defined by Vulkan ICD / Loader spec */
+#define VULKAN_ICD_MAGIC_VALUE 0x01CDC0DE
+
+/* Base 'class' for our Vulkan dispatchable objects such as VkDevice and VkInstance.
+ * This structure MUST be the first element of a dispatchable object as the ICD
+ * loader depends on it. For now only contains loader_magic, but over time more common
+ * functionality is expected.
+ */
+struct wine_vk_base
+{
+    /* Special section in each dispatchable object for use by the ICD loader for
+     * storing dispatch tables. The start contains a magical value '0x01CDC0DE'.
+     */
+    uintptr_t loader_magic;
+};
+
+/* Vulkan defines handles for objects through VK_DEFINE_HANDLE. As part of this
+ * it defines forward declarations for structs '_T', which the implementor (us)
+ * can use.
+ */
+struct VkDevice_T
+{
+    struct wine_vk_base base;
+    struct vulkan_device_funcs funcs;
+
+    /* driver private data */
+};
+
+struct VkInstance_T
+{
+    struct wine_vk_base base;
+    struct vulkan_instance_funcs funcs;
+
+    /* driver private data */
+};
-- 
2.13.6




More information about the wine-devel mailing list