1Dispatch 2============= 3 4This chapter attemtps to document the Vulkan dispatch infrastructure in the 5Mesa Vulkan runtime. There are a lot of moving pieces here but the end 6result has proven quite effective for implementing all the various Vulkan 7API requirements. 8 9 10Extension tables 11---------------- 12 13The Vulkan runtime defines two extension table structures, one for instance 14extensions and one for device extensions which contain a Boolean per 15extension. The device table looks like this: 16 17.. code-block:: c 18 19 #define VK_DEVICE_EXTENSION_COUNT 238 20 21 struct vk_device_extension_table { 22 union { 23 bool extensions[VK_DEVICE_EXTENSION_COUNT]; 24 struct { 25 bool KHR_8bit_storage; 26 bool KHR_16bit_storage; 27 bool KHR_acceleration_structure; 28 bool KHR_bind_memory2; 29 ... 30 }; 31 }; 32 }; 33 34The instance extension table is similar except that it includes the 35instance level extensions. Both tables are actually unions so that you can 36access the table either by name or as an array. Accessing by name is 37typically better for human-written code which needs to query for specific 38enabled extensions or declare a table of which extensions a driver 39supports. The array form is convenient for more automatic code which wants 40to iterate over the table. 41 42These tables are are generated automatically using a bit of python code that 43parses the vk.xml from the `Vulkan-Docs repo 44<https://github.com/KhronosGroup/Vulkan-docs/>`_, enumerates the 45extensions, sorts them by instance vs. device and generates the table. 46Generating it from XML means that we never have to manually maintain any of 47these data structures; they get automatically updated when someone imports 48a new version of vk.xml. We also generates a matching pair of tables of 49``VkExtensionProperties``. This makes it easy to implement 50``vkEnumerate*ExtensionProperties()`` with a simple loop that walks a table 51of supported extensions and copies the VkExtensionProperties for each 52enabled entry. Similarly, we can have a loop in ``vkCreateInstance()`` or 53``vkCreateDevice()`` which takes the ``ppEnabledExtensionNames`` and fills 54out the table with all enabled extensions. 55 56 57Entrypoint and dispatch tables 58------------------------------ 59 60Entrypoint tables contain a function pointer for every Vulkan entrypoint 61within a particular scope. There are separate tables for instance, 62physical device, and device-level functionality. The device entrypoint 63table looks like this: 64 65.. code-block:: c 66 67 struct vk_device_entrypoint_table { 68 PFN_vkGetDeviceProcAddr GetDeviceProcAddr; 69 PFN_vkDestroyDevice DestroyDevice; 70 PFN_vkGetDeviceQueue GetDeviceQueue; 71 PFN_vkQueueSubmit QueueSubmit; 72 ... 73 #ifdef VK_USE_PLATFORM_WIN32_KHR 74 PFN_vkGetSemaphoreWin32HandleKHR GetSemaphoreWin32HandleKHR; 75 #else 76 PFN_vkVoidFunction GetSemaphoreWin32HandleKHR; 77 # endif 78 ... 79 }; 80 81Every entry that requires some sort of platform define is wrapped in an 82``#ifdef`` and declared as the actual function pointer type if the platform 83define is set and declared as a void function otherwise. This ensures that 84the layout of the structure doesn't change based on preprocessor symbols 85but anyone who has the platform defines set gets the real prototype and 86anyone who doesn't can use the table without needing to pull in all the 87platform headers. 88 89Dispatch tables are similar to entrypoint tables except that they're 90de-duplicated so that aliased entrypoints have only one entry in the table. 91The device dispatch table looks like this: 92 93.. code-block:: c 94 95 struct vk_device_dispatch_table { 96 PFN_vkGetDeviceProcAddr GetDeviceProcAddr; 97 PFN_vkDestroyDevice DestroyDevice; 98 PFN_vkGetDeviceQueue GetDeviceQueue; 99 PFN_vkQueueSubmit QueueSubmit; 100 ... 101 union { 102 PFN_vkResetQueryPool ResetQueryPool; 103 PFN_vkResetQueryPoolEXT ResetQueryPoolEXT; 104 }; 105 ... 106 }; 107 108In order to allow code to use any of the aliases for a given entrypoint, 109such entrypoints are wrapped in a union. This is important because we need 110to be able to add new aliases potentially at any Vulkan release and we want 111to do so without having to update all the driver code which uses one of the 112newly aliased entrypoints. We could require that everyone use the first 113name an entrypoint ever has but that gets weird if, for instance, it's 114introduced in an EXT extension and some driver only ever implements the KHR 115or core version of the feature. It's easier for everyone if we make all 116the entrypoint names work. 117 118An entrypoint table can be converted to a dispatch table by compacting it 119with one of the ``vk_*_dispatch_table_from_entrypoints()`` family of 120functions: 121 122.. code-block:: c 123 124 void vk_instance_dispatch_table_from_entrypoints( 125 struct vk_instance_dispatch_table *dispatch_table, 126 const struct vk_instance_entrypoint_table *entrypoint_table, 127 bool overwrite); 128 129 void vk_physical_device_dispatch_table_from_entrypoints( 130 struct vk_physical_device_dispatch_table *dispatch_table, 131 const struct vk_physical_device_entrypoint_table *entrypoint_table, 132 bool overwrite); 133 134 void vk_device_dispatch_table_from_entrypoints( 135 struct vk_device_dispatch_table *dispatch_table, 136 const struct vk_device_entrypoint_table *entrypoint_table, 137 bool overwrite); 138 139 140Generating driver dispatch tables 141--------------------------------- 142 143Entrypoint tables can be easily auto-generated for your driver. Simply put 144the following in the driver's ``meson.build``, modified as necessary: 145 146.. code-block:: 147 148 drv_entrypoints = custom_target( 149 'drv_entrypoints', 150 input : [vk_entrypoints_gen, vk_api_xml], 151 output : ['drv_entrypoints.h', 'drv_entrypoints.c'], 152 command : [ 153 prog_python, '@INPUT0@', '--xml', '@INPUT1@', '--proto', '--weak', 154 '--out-h', '@OUTPUT0@', '--out-c', '@OUTPUT1@', '--prefix', 'drv', 155 ], 156 depend_files : vk_entrypoints_gen_depend_files, 157 ) 158 159The generated ``drv_entrypoints.h`` fill will contain prototypes for every 160Vulkan entrypoint, prefixed with what you passed to ``--prefix`` above. 161For instance, if you set ``--prefix drv`` and the entrypoint name is 162``vkCreateDevice()``, the driver entrypoint will be named 163``drv_CreateDevice()``. The ``--prefix`` flag can be specified multiple 164times if you want more than one table. It also generates an entrypoint 165table for each prefix and each dispatch level (instance, physical device, 166and device) which is populated using the driver's functions. Thanks to our 167use of weak function pointers (or something roughly equivalent for MSVC), 168any entrypoints which are not implented will automatically show up as 169``NULL`` entries in the table rather than resulting in linking errors. 170 171The above generates entrypoint tables because, thanks to aliasing and the C 172rules around const struct declarations, it's not practical to generate a 173dispatch table directly. Before they can be passed into the relevant 174``vk_*_init()`` function, the entrypoint table will have to be converted to 175a dispatch table. The typical pattern for this inside a driver looks 176something like this: 177 178.. code-block:: c 179 180 struct vk_instance_dispatch_table dispatch_table; 181 vk_instance_dispatch_table_from_entrypoints( 182 &dispatch_table, &anv_instance_entrypoints, true); 183 vk_instance_dispatch_table_from_entrypoints( 184 &dispatch_table, &wsi_instance_entrypoints, false); 185 186 result = vk_instance_init(&instance->vk, &instance_extensions, 187 &dispatch_table, pCreateInfo, pAllocator); 188 if (result != VK_SUCCESS) { 189 vk_free(pAllocator, instance); 190 return result; 191 } 192 193The ``vk_*_dispatch_table_from_entrypoints()`` functions are designed so 194that they can be layered like this. In this case, it starts with the 195instance entrypoints from the Intel vulkan driver and then adds in the WSI 196entrypoints. If there are any entrypoints duplicated between the two, the 197first one to define the entrypoint wins. 198 199 200Common Vulkan entrypoints 201------------------------- 202 203For the Vulkan runtime itself, there is a dispatch table with the 204``vk_common`` prefix used to provide common implementations of various 205entrypoints. This entrypoint table is added last as part of 206``vk_*_init()`` so that the driver implementation will always be used, if 207there is one. 208 209This is used to implement a bunch of things on behalf of the driver. The 210most common case is whenever there are ``vkFoo()`` and ``vkFoo2()`` 211entrypoints. We provide wrappers for nearly all of these that implement 212``vkFoo()`` in terms of ``vkFoo2()`` so a driver can switch to the new one 213and throw the old one away. For instance, ``vk_common_BindBufferMemory()`` 214looks like this: 215 216.. code-block:: c 217 218 VKAPI_ATTR VkResult VKAPI_CALL 219 vk_common_BindBufferMemory(VkDevice _device, 220 VkBuffer buffer, 221 VkDeviceMemory memory, 222 VkDeviceSize memoryOffset) 223 { 224 VK_FROM_HANDLE(vk_device, device, _device); 225 226 VkBindBufferMemoryInfo bind = { 227 .sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO, 228 .buffer = buffer, 229 .memory = memory, 230 .memoryOffset = memoryOffset, 231 }; 232 233 return device->dispatch_table.BindBufferMemory2(_device, 1, &bind); 234 } 235 236There are, of course, far more complicated cases of implementing 237``vkFoo()`` in terms of ``vkFoo2()`` such as the 238``vk_common_QueueSubmit()`` implementation. We also implement far less 239trivial functionality as ``vk_common_*`` entrypoints. For instance, we 240have full implementations of ``VkFence``, ``VkSemaphore``, and 241``vkQueueSubmit2()``. 242 243 244Entrypoint lookup 245----------------- 246 247Implementing ``vkGet*ProcAddr()`` is quite complicated because of the 248Vulkan 1.2 rules around exactly when they have to return ``NULL``. When a 249client calls `vkGet*ProcAddr()`, we go through a three step process resolve 250the function pointer: 251 252 1. A static (generated at compile time) hash table is used to map the 253 entrypoint name to an index into the corresponding entry point table. 254 255 2. Optionally, the index is passed to an auto-generated function that 256 checks against the enabled core API version and extensions. We use an 257 index into the entrypoint table, not the dispatch table, because the 258 rules for when an entrypoint should be exposed are per-entrypoint. For 259 instance, `vkBindImageMemory2` is available on Vulkan 1.1 and later but 260 `vkBindImageMemory2KHR` is available if VK_KHR_bind_memory2 is enabled. 261 262 3. A compaction table is used to map from the entrypoint table index to 263 the dispatch table index and the function is finally fetched from the 264 dispatch table. 265 266All of this is encapsulated within the ``vk_*_dispatch_table_get()`` and 267``vk_*_dispatch_table_get_if_supported()`` families of functions. The 268``_if_supported`` versions take a core version and one or more extension 269tables. The driver has to provide ``vk_icdGet*ProcAddr()`` entrypoints 270which wrap these functions because those have to be exposed as actual 271symbols from the ``.so`` or ``.dll`` as part of the loader interface. It 272also has to provide its own ``drv_GetInstanceProcAddr()`` because it needs 273to pass the supported instance extension table to 274:cpp:func:`vk_instance_get_proc_addr`. The runtime will provide 275``vk_common_GetDeviceProcAddr()`` implementations. 276 277 278Populating layer or client dispatch tables 279------------------------------------------ 280 281The entrypoint and dispatch tables actually live in ``src/vulkan/util``, 282not ``src/vulkan/runtime`` so they can be used by layers and clients (such 283as Zink) as well as the runtime. Layers and clients may wish to populate 284dispatch tables from an underlying Vulkan implementation. This can be done 285via the ``vk_*_dispatch_table_load()`` family of functions: 286 287.. code-block:: c 288 289 void 290 vk_instance_dispatch_table_load(struct vk_instance_dispatch_table *table, 291 PFN_vkGetInstanceProcAddr gpa, 292 VkInstance instance); 293 void 294 vk_physical_device_dispatch_table_load(struct vk_physical_device_dispatch_table *table, 295 PFN_vkGetInstanceProcAddr gpa, 296 VkInstance instance); 297 void 298 vk_device_dispatch_table_load(struct vk_device_dispatch_table *table, 299 PFN_vkGetDeviceProcAddr gpa, 300 VkDevice device); 301 302These call the given ``vkGet*ProcAddr`` function to populate the dispatch 303table. For aliased entrypoints, it will try each variant in succession to 304ensure that the dispatch table entry gets populated no matter which version 305of the feature you have enabled. 306