VirtualBox

source: vbox/trunk/src/VBox/Additions/3D/mesa/mesa-24.0.2/docs/vulkan/dispatch.rst@ 105254

最後變更 在這個檔案從105254是 103996,由 vboxsync 提交於 12 月 前

Additions/3D/mesa: export mesa-24.0.2 to OSE. bugref:10606

檔案大小: 12.6 KB
 
1Dispatch
2=============
3
4This chapter attempts 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
90deduplicated 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 '--beta', with_vulkan_beta.to_string(),
156 ],
157 depend_files : vk_entrypoints_gen_depend_files,
158 )
159
160The generated ``drv_entrypoints.h`` fill will contain prototypes for every
161Vulkan entrypoint, prefixed with what you passed to ``--prefix`` above.
162For instance, if you set ``--prefix drv`` and the entrypoint name is
163``vkCreateDevice()``, the driver entrypoint will be named
164``drv_CreateDevice()``. The ``--prefix`` flag can be specified multiple
165times if you want more than one table. It also generates an entrypoint
166table for each prefix and each dispatch level (instance, physical device,
167and device) which is populated using the driver's functions. Thanks to our
168use of weak function pointers (or something roughly equivalent for MSVC),
169any entrypoints which are not implemented will automatically show up as
170``NULL`` entries in the table rather than resulting in linking errors.
171
172The above generates entrypoint tables because, thanks to aliasing and the C
173rules around const struct declarations, it's not practical to generate a
174dispatch table directly. Before they can be passed into the relevant
175``vk_*_init()`` function, the entrypoint table will have to be converted to
176a dispatch table. The typical pattern for this inside a driver looks
177something like this:
178
179.. code-block:: c
180
181 struct vk_instance_dispatch_table dispatch_table;
182 vk_instance_dispatch_table_from_entrypoints(
183 &dispatch_table, &anv_instance_entrypoints, true);
184 vk_instance_dispatch_table_from_entrypoints(
185 &dispatch_table, &wsi_instance_entrypoints, false);
186
187 result = vk_instance_init(&instance->vk, &instance_extensions,
188 &dispatch_table, pCreateInfo, pAllocator);
189 if (result != VK_SUCCESS) {
190 vk_free(pAllocator, instance);
191 return result;
192 }
193
194The ``vk_*_dispatch_table_from_entrypoints()`` functions are designed so
195that they can be layered like this. In this case, it starts with the
196instance entrypoints from the Intel Vulkan driver and then adds in the WSI
197entrypoints. If there are any entrypoints duplicated between the two, the
198first one to define the entrypoint wins.
199
200
201Common Vulkan entrypoints
202-------------------------
203
204For the Vulkan runtime itself, there is a dispatch table with the
205``vk_common`` prefix used to provide common implementations of various
206entrypoints. This entrypoint table is added last as part of
207``vk_*_init()`` so that the driver implementation will always be used, if
208there is one.
209
210This is used to implement a bunch of things on behalf of the driver. The
211most common case is whenever there are ``vkFoo()`` and ``vkFoo2()``
212entrypoints. We provide wrappers for nearly all of these that implement
213``vkFoo()`` in terms of ``vkFoo2()`` so a driver can switch to the new one
214and throw the old one away. For instance, ``vk_common_BindBufferMemory()``
215looks like this:
216
217.. code-block:: c
218
219 VKAPI_ATTR VkResult VKAPI_CALL
220 vk_common_BindBufferMemory(VkDevice _device,
221 VkBuffer buffer,
222 VkDeviceMemory memory,
223 VkDeviceSize memoryOffset)
224 {
225 VK_FROM_HANDLE(vk_device, device, _device);
226
227 VkBindBufferMemoryInfo bind = {
228 .sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO,
229 .buffer = buffer,
230 .memory = memory,
231 .memoryOffset = memoryOffset,
232 };
233
234 return device->dispatch_table.BindBufferMemory2(_device, 1, &bind);
235 }
236
237There are, of course, far more complicated cases of implementing
238``vkFoo()`` in terms of ``vkFoo2()`` such as the
239``vk_common_QueueSubmit()`` implementation. We also implement far less
240trivial functionality as ``vk_common_*`` entrypoints. For instance, we
241have full implementations of ``VkFence``, ``VkSemaphore``, and
242``vkQueueSubmit2()``.
243
244
245Entrypoint lookup
246-----------------
247
248Implementing ``vkGet*ProcAddr()`` is quite complicated because of the
249Vulkan 1.2 rules around exactly when they have to return ``NULL``. When a
250client calls ``vkGet*ProcAddr()``, we go through a three step process resolve
251the function pointer:
252
253 1. A static (generated at compile time) hash table is used to map the
254 entrypoint name to an index into the corresponding entry point table.
255
256 2. Optionally, the index is passed to an auto-generated function that
257 checks against the enabled core API version and extensions. We use an
258 index into the entrypoint table, not the dispatch table, because the
259 rules for when an entrypoint should be exposed are per-entrypoint. For
260 instance, ``vkBindImageMemory2`` is available on Vulkan 1.1 and later but
261 ``vkBindImageMemory2KHR`` is available if :ext:`VK_KHR_bind_memory2` is
262 enabled.
263
264 3. A compaction table is used to map from the entrypoint table index to
265 the dispatch table index and the function is finally fetched from the
266 dispatch table.
267
268All of this is encapsulated within the ``vk_*_dispatch_table_get()`` and
269``vk_*_dispatch_table_get_if_supported()`` families of functions. The
270``_if_supported`` versions take a core version and one or more extension
271tables. The driver has to provide ``vk_icdGet*ProcAddr()`` entrypoints
272which wrap these functions because those have to be exposed as actual
273symbols from the ``.so`` or ``.dll`` as part of the loader interface. It
274also has to provide its own ``drv_GetInstanceProcAddr()`` because it needs
275to pass the supported instance extension table to
276:c:func:`vk_instance_get_proc_addr`. The runtime will provide
277``vk_common_GetDeviceProcAddr()`` implementations.
278
279
280Populating layer or client dispatch tables
281------------------------------------------
282
283The entrypoint and dispatch tables actually live in ``src/vulkan/util``,
284not ``src/vulkan/runtime`` so they can be used by layers and clients (such
285as Zink) as well as the runtime. Layers and clients may wish to populate
286dispatch tables from an underlying Vulkan implementation. This can be done
287via the ``vk_*_dispatch_table_load()`` family of functions:
288
289.. code-block:: c
290
291 void
292 vk_instance_dispatch_table_load(struct vk_instance_dispatch_table *table,
293 PFN_vkGetInstanceProcAddr gpa,
294 VkInstance instance);
295 void
296 vk_physical_device_dispatch_table_load(struct vk_physical_device_dispatch_table *table,
297 PFN_vkGetInstanceProcAddr gpa,
298 VkInstance instance);
299 void
300 vk_device_dispatch_table_load(struct vk_device_dispatch_table *table,
301 PFN_vkGetDeviceProcAddr gpa,
302 VkDevice device);
303
304These call the given ``vkGet*ProcAddr`` function to populate the dispatch
305table. For aliased entrypoints, it will try each variant in succession to
306ensure that the dispatch table entry gets populated no matter which version
307of the feature you have enabled.
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette