mesa-clc: vendor as full-fork recipe (path=source, patches baked)
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
Base object structs
|
||||
===================
|
||||
|
||||
The Vulkan runtime code provides a set of base object structs which must be
|
||||
used if you want your driver to take advantage of any of the runtime code.
|
||||
There are other base structs for various things which are not covered here
|
||||
but those are optional. The ones covered here are the bare minimum set
|
||||
which form the core of the Vulkan runtime code:
|
||||
|
||||
.. contents::
|
||||
:local:
|
||||
|
||||
As one might expect, :c:struct:`vk_instance` is the required base struct
|
||||
for implementing ``VkInstance``, :c:struct:`vk_physical_device` is
|
||||
required for ``VkPhysicalDevice``, and :c:struct:`vk_device` for
|
||||
``VkDevice``. Everything else must derive from
|
||||
:c:struct:`vk_object_base` or from some struct that derives from
|
||||
:c:struct:`vk_object_base`.
|
||||
|
||||
|
||||
vk_object_base
|
||||
--------------
|
||||
|
||||
The root base struct for all Vulkan objects is
|
||||
:c:struct:`vk_object_base`. Every object exposed to the client through
|
||||
the Vulkan API *must* inherit from :c:struct:`vk_object_base` by having a
|
||||
:c:struct:`vk_object_base` or some struct that inherits from
|
||||
:c:struct:`vk_object_base` as the driver struct's first member. Even
|
||||
though we have ``container_of()`` and use it liberally, the
|
||||
:c:struct:`vk_object_base` should be the first member as there are a few
|
||||
places, particularly in the logging framework, where we use void pointers
|
||||
to avoid casting and this only works if the address of the driver struct is
|
||||
the same as the address of the :c:struct:`vk_object_base`.
|
||||
|
||||
The standard pattern for defining a Vulkan object inside a driver looks
|
||||
something like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct drv_sampler {
|
||||
struct vk_object_base base;
|
||||
|
||||
/* Driver fields */
|
||||
};
|
||||
|
||||
VK_DEFINE_NONDISP_HANDLE_CASTS(drv_sampler, base, VkSampler,
|
||||
VK_OBJECT_TYPE_SAMPLER);
|
||||
|
||||
Then, to the object in a Vulkan entrypoint,
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
VKAPI_ATTR void VKAPI_CALL drv_DestroySampler(
|
||||
VkDevice _device,
|
||||
VkSampler _sampler,
|
||||
const VkAllocationCallbacks* pAllocator)
|
||||
{
|
||||
VK_FROM_HANDLE(drv_device, device, _device);
|
||||
VK_FROM_HANDLE(drv_sampler, sampler, _sampler);
|
||||
|
||||
if (!sampler)
|
||||
return;
|
||||
|
||||
/* Tear down the sampler */
|
||||
|
||||
vk_object_free(&device->vk, pAllocator, sampler);
|
||||
}
|
||||
|
||||
The :c:macro:`VK_DEFINE_NONDISP_HANDLE_CASTS()` macro defines a set of
|
||||
type-safe cast functions called ``drv_sampler_from_handle()`` and
|
||||
``drv_sampler_to_handle()`` which cast a :c:type:`VkSampler` to and from a
|
||||
``struct drv_sampler *``. Because compile-time type checking with Vulkan
|
||||
handle types doesn't always work in C, the ``_from_handle()`` helper uses the
|
||||
provided :c:type:`VkObjectType` to assert at runtime that the provided
|
||||
handle is the correct type of object. Both cast helpers properly handle
|
||||
``NULL`` and ``VK_NULL_HANDLE`` as inputs. The :c:macro:`VK_FROM_HANDLE()`
|
||||
macro provides a convenient way to declare a ``drv_foo`` pointer and
|
||||
initialize it from a ``VkFoo`` handle in one smooth motion.
|
||||
|
||||
.. c:autostruct:: vk_object_base
|
||||
:file: src/vulkan/runtime/vk_object.h
|
||||
:members:
|
||||
|
||||
.. c:autofunction:: vk_object_base_init
|
||||
|
||||
.. c:autofunction:: vk_object_base_finish
|
||||
|
||||
.. c:automacro:: VK_DEFINE_HANDLE_CASTS
|
||||
|
||||
.. c:automacro:: VK_DEFINE_NONDISP_HANDLE_CASTS
|
||||
|
||||
.. c:automacro:: VK_FROM_HANDLE
|
||||
|
||||
|
||||
vk_instance
|
||||
-----------
|
||||
|
||||
.. c:autostruct:: vk_instance
|
||||
:file: src/vulkan/runtime/vk_instance.h
|
||||
:members:
|
||||
|
||||
.. c:autofunction:: vk_instance_init
|
||||
|
||||
.. c:autofunction:: vk_instance_finish
|
||||
|
||||
Once a driver has a :c:struct:`vk_instance`, implementing all the various
|
||||
instance-level ``vkGet*ProcAddr()`` entrypoints is trivial:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
||||
drv_GetInstanceProcAddr(VkInstance _instance,
|
||||
const char *pName)
|
||||
{
|
||||
VK_FROM_HANDLE(vk_instance, instance, _instance);
|
||||
return vk_instance_get_proc_addr(instance,
|
||||
&drv_instance_entrypoints,
|
||||
pName);
|
||||
}
|
||||
|
||||
PUBLIC VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL
|
||||
vk_icdGetInstanceProcAddr(VkInstance instance,
|
||||
const char *pName)
|
||||
{
|
||||
return drv_GetInstanceProcAddr(instance, pName);
|
||||
}
|
||||
|
||||
.. c:autofunction:: vk_instance_get_proc_addr
|
||||
|
||||
.. c:autofunction:: vk_instance_get_proc_addr_unchecked
|
||||
|
||||
.. c:autofunction:: vk_instance_get_physical_device_proc_addr
|
||||
|
||||
We also provide an implementation of
|
||||
``vkEnumerateInstanceExtensionProperties()`` which can be used similarly:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL
|
||||
drv_EnumerateInstanceExtensionProperties(const char *pLayerName,
|
||||
uint32_t *pPropertyCount,
|
||||
VkExtensionProperties *pProperties)
|
||||
{
|
||||
if (pLayerName)
|
||||
return vk_error(NULL, VK_ERROR_LAYER_NOT_PRESENT);
|
||||
|
||||
return vk_enumerate_instance_extension_properties(
|
||||
&instance_extensions, pPropertyCount, pProperties);
|
||||
}
|
||||
|
||||
.. c:autofunction:: vk_enumerate_instance_extension_properties
|
||||
|
||||
vk_physical_device
|
||||
------------------
|
||||
|
||||
.. c:autostruct:: vk_physical_device
|
||||
:file: src/vulkan/runtime/vk_physical_device.h
|
||||
:members:
|
||||
|
||||
.. c:autofunction:: vk_physical_device_init
|
||||
|
||||
.. c:autofunction:: vk_physical_device_finish
|
||||
|
||||
vk_device
|
||||
------------------
|
||||
|
||||
.. c:autostruct:: vk_device
|
||||
:file: src/vulkan/runtime/vk_device.h
|
||||
:members:
|
||||
|
||||
.. c:autofunction:: vk_device_init
|
||||
|
||||
.. c:autofunction:: vk_device_finish
|
||||
@@ -0,0 +1,85 @@
|
||||
Command Pools
|
||||
=============
|
||||
|
||||
The Vulkan runtime code provides a common ``VkCommandPool`` implementation
|
||||
which makes managing the lifetimes of command buffers and recycling their
|
||||
internal state easier. To use the common command pool a driver needs to
|
||||
fill out a :c:struct:`vk_command_buffer_ops` struct and set the
|
||||
``command_buffer_ops`` field of :c:struct:`vk_device`.
|
||||
|
||||
.. c:autostruct:: vk_command_buffer_ops
|
||||
:file: src/vulkan/runtime/vk_command_buffer.h
|
||||
:members:
|
||||
|
||||
By reducing the entirety of command buffer lifetime management to these
|
||||
three functions, much of the complexity of command pools can be implemented
|
||||
in common code, providing better, more consistent behavior across Mesa.
|
||||
|
||||
|
||||
Command Buffer Recycling
|
||||
------------------------
|
||||
|
||||
The common command pool provides automatic command buffer recycling as long
|
||||
as the driver uses the common ``vkAllocateCommandBuffers()`` and
|
||||
``vkFreeCommandBuffers()`` implementations. The driver must also provide the
|
||||
``reset`` function pointer in :c:struct:`vk_command_buffer_ops`.
|
||||
|
||||
With the common command buffer pool, when the client calls
|
||||
``vkFreeCommandBuffers()``, the command buffers are not immediately freed.
|
||||
Instead, they are reset with
|
||||
``VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT``, their base object is
|
||||
recycled, and they are added to a free list inside the pool. When the
|
||||
client then calls ``vkAllocateCommandBuffers()``, we check the free list
|
||||
and return a recycled command buffer, if any are available. This provides
|
||||
some basic command buffer pooling without the driver doing any additional
|
||||
work.
|
||||
|
||||
|
||||
Custom command pools
|
||||
--------------------
|
||||
|
||||
If a driver wishes to recycle at a finer granularity than whole command
|
||||
buffers, they can do so by providing their own command pool implementation
|
||||
which wraps :c:struct:`vk_command_pool`. The common use-case here is if
|
||||
the driver wants to pool command-buffer-internal objects at a finer
|
||||
granularity than whole command buffers. The command pool provides a place
|
||||
where things like GPU command buffers or upload buffers can be cached
|
||||
without having to take a lock.
|
||||
|
||||
When implementing a custom command pool, drivers need only implement three
|
||||
entrypoints:
|
||||
|
||||
- ``vkCreateCommandPool()``
|
||||
- ``vkDestroyCommandPool()``
|
||||
- ``vkTrimCommandPool()``
|
||||
|
||||
All of the other entrypoints will be handled by common code so long as the
|
||||
driver's command pool derives from :c:struct:`vk_command_pool`.
|
||||
|
||||
The driver implementation of the command buffer ``recycle()`` function
|
||||
should respect ``VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT`` and, when
|
||||
set, return any recyclable resources to the command pool. This may be set
|
||||
by the client when it calls ``vkResetCommandBuffer()``, come from a
|
||||
whole-pool reset via ``VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT``, or
|
||||
come from the common command buffer code when a command buffer is recycled.
|
||||
|
||||
The driver's implementation of ``vkTrimCommandPool()`` should free any
|
||||
resources that have been cached within the command pool back to the device
|
||||
or back to the OS. It **must** also call :c:func:`vk_command_pool_trim`
|
||||
to allow the common code to free any recycled command buffers.
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
.. c:autostruct:: vk_command_pool
|
||||
:file: src/vulkan/runtime/vk_command_pool.h
|
||||
:members:
|
||||
|
||||
.. c:autofunction:: vk_command_pool_init
|
||||
:file: src/vulkan/runtime/vk_command_pool.h
|
||||
|
||||
.. c:autofunction:: vk_command_pool_finish
|
||||
:file: src/vulkan/runtime/vk_command_pool.h
|
||||
|
||||
.. c:autofunction:: vk_command_pool_trim
|
||||
:file: src/vulkan/runtime/vk_command_pool.h
|
||||
@@ -0,0 +1,307 @@
|
||||
Dispatch
|
||||
=============
|
||||
|
||||
This chapter attempts to document the Vulkan dispatch infrastructure in the
|
||||
Mesa Vulkan runtime. There are a lot of moving pieces here but the end
|
||||
result has proven quite effective for implementing all the various Vulkan
|
||||
API requirements.
|
||||
|
||||
|
||||
Extension tables
|
||||
----------------
|
||||
|
||||
The Vulkan runtime defines two extension table structures, one for instance
|
||||
extensions and one for device extensions which contain a Boolean per
|
||||
extension. The device table looks like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#define VK_DEVICE_EXTENSION_COUNT 238
|
||||
|
||||
struct vk_device_extension_table {
|
||||
union {
|
||||
bool extensions[VK_DEVICE_EXTENSION_COUNT];
|
||||
struct {
|
||||
bool KHR_8bit_storage;
|
||||
bool KHR_16bit_storage;
|
||||
bool KHR_acceleration_structure;
|
||||
bool KHR_bind_memory2;
|
||||
...
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
The instance extension table is similar except that it includes the
|
||||
instance level extensions. Both tables are actually unions so that you can
|
||||
access the table either by name or as an array. Accessing by name is
|
||||
typically better for human-written code which needs to query for specific
|
||||
enabled extensions or declare a table of which extensions a driver
|
||||
supports. The array form is convenient for more automatic code which wants
|
||||
to iterate over the table.
|
||||
|
||||
These tables are are generated automatically using a bit of python code that
|
||||
parses the vk.xml from the `Vulkan-Docs repo
|
||||
<https://github.com/KhronosGroup/Vulkan-docs/>`__, enumerates the
|
||||
extensions, sorts them by instance vs. device and generates the table.
|
||||
Generating it from XML means that we never have to manually maintain any of
|
||||
these data structures; they get automatically updated when someone imports
|
||||
a new version of vk.xml. We also generates a matching pair of tables of
|
||||
``VkExtensionProperties``. This makes it easy to implement
|
||||
``vkEnumerate*ExtensionProperties()`` with a simple loop that walks a table
|
||||
of supported extensions and copies the VkExtensionProperties for each
|
||||
enabled entry. Similarly, we can have a loop in ``vkCreateInstance()`` or
|
||||
``vkCreateDevice()`` which takes the ``ppEnabledExtensionNames`` and fills
|
||||
out the table with all enabled extensions.
|
||||
|
||||
|
||||
Entrypoint and dispatch tables
|
||||
------------------------------
|
||||
|
||||
Entrypoint tables contain a function pointer for every Vulkan entrypoint
|
||||
within a particular scope. There are separate tables for instance,
|
||||
physical device, and device-level functionality. The device entrypoint
|
||||
table looks like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vk_device_entrypoint_table {
|
||||
PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
|
||||
PFN_vkDestroyDevice DestroyDevice;
|
||||
PFN_vkGetDeviceQueue GetDeviceQueue;
|
||||
PFN_vkQueueSubmit QueueSubmit;
|
||||
...
|
||||
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||
PFN_vkGetSemaphoreWin32HandleKHR GetSemaphoreWin32HandleKHR;
|
||||
#else
|
||||
PFN_vkVoidFunction GetSemaphoreWin32HandleKHR;
|
||||
# endif
|
||||
...
|
||||
};
|
||||
|
||||
Every entry that requires some sort of platform define is wrapped in an
|
||||
``#ifdef`` and declared as the actual function pointer type if the platform
|
||||
define is set and declared as a void function otherwise. This ensures that
|
||||
the layout of the structure doesn't change based on preprocessor symbols
|
||||
but anyone who has the platform defines set gets the real prototype and
|
||||
anyone who doesn't can use the table without needing to pull in all the
|
||||
platform headers.
|
||||
|
||||
Dispatch tables are similar to entrypoint tables except that they're
|
||||
deduplicated so that aliased entrypoints have only one entry in the table.
|
||||
The device dispatch table looks like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vk_device_dispatch_table {
|
||||
PFN_vkGetDeviceProcAddr GetDeviceProcAddr;
|
||||
PFN_vkDestroyDevice DestroyDevice;
|
||||
PFN_vkGetDeviceQueue GetDeviceQueue;
|
||||
PFN_vkQueueSubmit QueueSubmit;
|
||||
...
|
||||
union {
|
||||
PFN_vkResetQueryPool ResetQueryPool;
|
||||
PFN_vkResetQueryPoolEXT ResetQueryPoolEXT;
|
||||
};
|
||||
...
|
||||
};
|
||||
|
||||
In order to allow code to use any of the aliases for a given entrypoint,
|
||||
such entrypoints are wrapped in a union. This is important because we need
|
||||
to be able to add new aliases potentially at any Vulkan release and we want
|
||||
to do so without having to update all the driver code which uses one of the
|
||||
newly aliased entrypoints. We could require that everyone use the first
|
||||
name an entrypoint ever has but that gets weird if, for instance, it's
|
||||
introduced in an EXT extension and some driver only ever implements the KHR
|
||||
or core version of the feature. It's easier for everyone if we make all
|
||||
the entrypoint names work.
|
||||
|
||||
An entrypoint table can be converted to a dispatch table by compacting it
|
||||
with one of the ``vk_*_dispatch_table_from_entrypoints()`` family of
|
||||
functions:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void vk_instance_dispatch_table_from_entrypoints(
|
||||
struct vk_instance_dispatch_table *dispatch_table,
|
||||
const struct vk_instance_entrypoint_table *entrypoint_table,
|
||||
bool overwrite);
|
||||
|
||||
void vk_physical_device_dispatch_table_from_entrypoints(
|
||||
struct vk_physical_device_dispatch_table *dispatch_table,
|
||||
const struct vk_physical_device_entrypoint_table *entrypoint_table,
|
||||
bool overwrite);
|
||||
|
||||
void vk_device_dispatch_table_from_entrypoints(
|
||||
struct vk_device_dispatch_table *dispatch_table,
|
||||
const struct vk_device_entrypoint_table *entrypoint_table,
|
||||
bool overwrite);
|
||||
|
||||
|
||||
Generating driver dispatch tables
|
||||
---------------------------------
|
||||
|
||||
Entrypoint tables can be easily auto-generated for your driver. Simply put
|
||||
the following in the driver's ``meson.build``, modified as necessary:
|
||||
|
||||
.. code-block::
|
||||
|
||||
drv_entrypoints = custom_target(
|
||||
'drv_entrypoints',
|
||||
input : [vk_entrypoints_gen, vk_api_xml],
|
||||
output : ['drv_entrypoints.h', 'drv_entrypoints.c'],
|
||||
command : [
|
||||
prog_python, '@INPUT0@', '--xml', '@INPUT1@', '--proto', '--weak',
|
||||
'--out-h', '@OUTPUT0@', '--out-c', '@OUTPUT1@', '--prefix', 'drv',
|
||||
'--beta', with_vulkan_beta.to_string(),
|
||||
],
|
||||
depend_files : vk_entrypoints_gen_depend_files,
|
||||
)
|
||||
|
||||
The generated ``drv_entrypoints.h`` fill will contain prototypes for every
|
||||
Vulkan entrypoint, prefixed with what you passed to ``--prefix`` above.
|
||||
For instance, if you set ``--prefix drv`` and the entrypoint name is
|
||||
``vkCreateDevice()``, the driver entrypoint will be named
|
||||
``drv_CreateDevice()``. The ``--prefix`` flag can be specified multiple
|
||||
times if you want more than one table. It also generates an entrypoint
|
||||
table for each prefix and each dispatch level (instance, physical device,
|
||||
and device) which is populated using the driver's functions. Thanks to our
|
||||
use of weak function pointers (or something roughly equivalent for MSVC),
|
||||
any entrypoints which are not implemented will automatically show up as
|
||||
``NULL`` entries in the table rather than resulting in linking errors.
|
||||
|
||||
The above generates entrypoint tables because, thanks to aliasing and the C
|
||||
rules around const struct declarations, it's not practical to generate a
|
||||
dispatch table directly. Before they can be passed into the relevant
|
||||
``vk_*_init()`` function, the entrypoint table will have to be converted to
|
||||
a dispatch table. The typical pattern for this inside a driver looks
|
||||
something like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vk_instance_dispatch_table dispatch_table;
|
||||
vk_instance_dispatch_table_from_entrypoints(
|
||||
&dispatch_table, &anv_instance_entrypoints, true);
|
||||
vk_instance_dispatch_table_from_entrypoints(
|
||||
&dispatch_table, &wsi_instance_entrypoints, false);
|
||||
|
||||
result = vk_instance_init(&instance->vk, &instance_extensions,
|
||||
&dispatch_table, pCreateInfo, pAllocator);
|
||||
if (result != VK_SUCCESS) {
|
||||
vk_free(pAllocator, instance);
|
||||
return result;
|
||||
}
|
||||
|
||||
The ``vk_*_dispatch_table_from_entrypoints()`` functions are designed so
|
||||
that they can be layered like this. In this case, it starts with the
|
||||
instance entrypoints from the Intel Vulkan driver and then adds in the WSI
|
||||
entrypoints. If there are any entrypoints duplicated between the two, the
|
||||
first one to define the entrypoint wins.
|
||||
|
||||
|
||||
Common Vulkan entrypoints
|
||||
-------------------------
|
||||
|
||||
For the Vulkan runtime itself, there is a dispatch table with the
|
||||
``vk_common`` prefix used to provide common implementations of various
|
||||
entrypoints. This entrypoint table is added last as part of
|
||||
``vk_*_init()`` so that the driver implementation will always be used, if
|
||||
there is one.
|
||||
|
||||
This is used to implement a bunch of things on behalf of the driver. The
|
||||
most common case is whenever there are ``vkFoo()`` and ``vkFoo2()``
|
||||
entrypoints. We provide wrappers for nearly all of these that implement
|
||||
``vkFoo()`` in terms of ``vkFoo2()`` so a driver can switch to the new one
|
||||
and throw the old one away. For instance, ``vk_common_BindBufferMemory()``
|
||||
looks like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
VKAPI_ATTR VkResult VKAPI_CALL
|
||||
vk_common_BindBufferMemory(VkDevice _device,
|
||||
VkBuffer buffer,
|
||||
VkDeviceMemory memory,
|
||||
VkDeviceSize memoryOffset)
|
||||
{
|
||||
VK_FROM_HANDLE(vk_device, device, _device);
|
||||
|
||||
VkBindBufferMemoryInfo bind = {
|
||||
.sType = VK_STRUCTURE_TYPE_BIND_BUFFER_MEMORY_INFO,
|
||||
.buffer = buffer,
|
||||
.memory = memory,
|
||||
.memoryOffset = memoryOffset,
|
||||
};
|
||||
|
||||
return device->dispatch_table.BindBufferMemory2(_device, 1, &bind);
|
||||
}
|
||||
|
||||
There are, of course, far more complicated cases of implementing
|
||||
``vkFoo()`` in terms of ``vkFoo2()`` such as the
|
||||
``vk_common_QueueSubmit()`` implementation. We also implement far less
|
||||
trivial functionality as ``vk_common_*`` entrypoints. For instance, we
|
||||
have full implementations of ``VkFence``, ``VkSemaphore``, and
|
||||
``vkQueueSubmit2()``.
|
||||
|
||||
|
||||
Entrypoint lookup
|
||||
-----------------
|
||||
|
||||
Implementing ``vkGet*ProcAddr()`` is quite complicated because of the
|
||||
Vulkan 1.2 rules around exactly when they have to return ``NULL``. When a
|
||||
client calls ``vkGet*ProcAddr()``, we go through a three step process resolve
|
||||
the function pointer:
|
||||
|
||||
1. A static (generated at compile time) hash table is used to map the
|
||||
entrypoint name to an index into the corresponding entry point table.
|
||||
|
||||
2. Optionally, the index is passed to an auto-generated function that
|
||||
checks against the enabled core API version and extensions. We use an
|
||||
index into the entrypoint table, not the dispatch table, because the
|
||||
rules for when an entrypoint should be exposed are per-entrypoint. For
|
||||
instance, ``vkBindImageMemory2`` is available on Vulkan 1.1 and later but
|
||||
``vkBindImageMemory2KHR`` is available if :ext:`VK_KHR_bind_memory2` is
|
||||
enabled.
|
||||
|
||||
3. A compaction table is used to map from the entrypoint table index to
|
||||
the dispatch table index and the function is finally fetched from the
|
||||
dispatch table.
|
||||
|
||||
All of this is encapsulated within the ``vk_*_dispatch_table_get()`` and
|
||||
``vk_*_dispatch_table_get_if_supported()`` families of functions. The
|
||||
``_if_supported`` versions take a core version and one or more extension
|
||||
tables. The driver has to provide ``vk_icdGet*ProcAddr()`` entrypoints
|
||||
which wrap these functions because those have to be exposed as actual
|
||||
symbols from the ``.so`` or ``.dll`` as part of the loader interface. It
|
||||
also has to provide its own ``drv_GetInstanceProcAddr()`` because it needs
|
||||
to pass the supported instance extension table to
|
||||
:c:func:`vk_instance_get_proc_addr`. The runtime will provide
|
||||
``vk_common_GetDeviceProcAddr()`` implementations.
|
||||
|
||||
|
||||
Populating layer or client dispatch tables
|
||||
------------------------------------------
|
||||
|
||||
The entrypoint and dispatch tables actually live in ``src/vulkan/util``,
|
||||
not ``src/vulkan/runtime`` so they can be used by layers and clients (such
|
||||
as Zink) as well as the runtime. Layers and clients may wish to populate
|
||||
dispatch tables from an underlying Vulkan implementation. This can be done
|
||||
via the ``vk_*_dispatch_table_load()`` family of functions:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void
|
||||
vk_instance_dispatch_table_load(struct vk_instance_dispatch_table *table,
|
||||
PFN_vkGetInstanceProcAddr gpa,
|
||||
VkInstance instance);
|
||||
void
|
||||
vk_physical_device_dispatch_table_load(struct vk_physical_device_dispatch_table *table,
|
||||
PFN_vkGetInstanceProcAddr gpa,
|
||||
VkInstance instance);
|
||||
void
|
||||
vk_device_dispatch_table_load(struct vk_device_dispatch_table *table,
|
||||
PFN_vkGetDeviceProcAddr gpa,
|
||||
VkDevice device);
|
||||
|
||||
These call the given ``vkGet*ProcAddr`` function to populate the dispatch
|
||||
table. For aliased entrypoints, it will try each variant in succession to
|
||||
ensure that the dispatch table entry gets populated no matter which version
|
||||
of the feature you have enabled.
|
||||
@@ -0,0 +1,294 @@
|
||||
Graphics state
|
||||
==============
|
||||
|
||||
The Mesa Vulkan runtime provides helpers for managing the numerous pieces
|
||||
of graphics state associated with a ``VkPipeline`` or set dynamically on a
|
||||
command buffer. No such helpers are provided for compute or ray-tracing
|
||||
because they have little or no state besides the shaders themselves.
|
||||
|
||||
|
||||
Pipeline state
|
||||
--------------
|
||||
|
||||
All (possibly dynamic) Vulkan graphics pipeline state is encapsulated into
|
||||
a single :c:struct:`vk_graphics_pipeline_state` structure which contains
|
||||
pointers to sub-structures for each of the different state categories.
|
||||
Unlike :c:type:`VkGraphicsPipelineCreateInfo`, the pointers in
|
||||
:c:struct:`vk_graphics_pipeline_state` are guaranteed to be either be
|
||||
NULL or point to valid and properly populated memory.
|
||||
|
||||
When creating a pipeline, the
|
||||
:c:func:`vk_graphics_pipeline_state_fill()` function can be used to
|
||||
gather all of the state from the core structures as well as various ``pNext``
|
||||
chains into a single state structure. Whenever an extension struct is
|
||||
missing, a reasonable default value is provided whenever possible.
|
||||
|
||||
|
||||
:c:func:`vk_graphics_pipeline_state_fill()` automatically handles both
|
||||
the render pass and dynamic rendering. For drivers which use
|
||||
:c:struct:`vk_render_pass`, the :c:struct:`vk_render_pass_state`
|
||||
structure will be populated as if for dynamic rendering, regardless of
|
||||
which path is used. Drivers which use their own render pass structure
|
||||
should parse the render pass, if available, and pass a
|
||||
:c:struct:`vk_render_pass_state` to the ``driver_rp`` argument of
|
||||
:c:func:`vk_graphics_pipeline_state_fill()` with the relevant information
|
||||
from the specified subpass. If a render pass is available,
|
||||
:c:struct:`vk_render_pass_state` will be populated with the
|
||||
the information from the :c:struct:`driver_rp`. If dynamic
|
||||
rendering is used or the driver provides a ``NULL``
|
||||
:c:struct:`driver_rp`, the :c:struct:`vk_render_pass_state`
|
||||
structure will be populated for dynamic rendering, including color, depth,
|
||||
and stencil attachment formats.
|
||||
|
||||
The usual flow for creating a full graphics pipeline (not library) looks
|
||||
like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vk_graphics_pipeline_state state = { };
|
||||
struct vk_graphics_pipeline_all_state all;
|
||||
vk_graphics_pipeline_state_fill(&device->vk, &state, pCreateInfo,
|
||||
NULL, &all, NULL, 0, NULL);
|
||||
|
||||
/* Emit stuff using the state in `state` */
|
||||
|
||||
The :c:struct:`vk_graphics_pipeline_all_state` structure exists to allow
|
||||
the state to sit on the stack instead of requiring a heap allocation. This
|
||||
is useful if you intend to use the state right away and don't need to store
|
||||
it. For pipeline libraries, it's likely more useful to use the dynamically
|
||||
allocated version and store the dynamically allocated memory in the
|
||||
library pipeline.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Assuming we have a vk_graphics_pipeline_state in pipeline */
|
||||
memset(&pipeline->state, 0, sizeof(pipeline->state));
|
||||
|
||||
for (uint32_t i = 0; i < lib_info->libraryCount; i++) {
|
||||
VK_FROM_HANDLE(drv_graphics_pipeline_library, lib, lib_info->pLibraries[i]);
|
||||
vk_graphics_pipeline_state_merge(&pipeline->state, &lib->state);
|
||||
}
|
||||
|
||||
/* This assumes you have a void **state_mem in pipeline */
|
||||
result = vk_graphics_pipeline_state_fill(&device->vk, &pipeline->state,
|
||||
pCreateInfo, NULL, NULL, pAllocator,
|
||||
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT,
|
||||
&pipeline->state_mem);
|
||||
if (result != VK_SUCCESS)
|
||||
return result;
|
||||
|
||||
State from dependent libraries can be merged together using
|
||||
:c:func:`vk_graphics_pipeline_state_merge`.
|
||||
:c:func:`vk_graphics_pipeline_state_fill` will then only attempt to
|
||||
populate missing fields. You can also merge dependent pipeline libraries
|
||||
together but store the final state on the stack for immediate consumption:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct vk_graphics_pipeline_state state = { };
|
||||
|
||||
for (uint32_t i = 0; i < lib_info->libraryCount; i++) {
|
||||
VK_FROM_HANDLE(drv_graphics_pipeline_library, lib, lib_info->pLibraries[i]);
|
||||
vk_graphics_pipeline_state_merge(&state, &lib->state);
|
||||
}
|
||||
|
||||
struct vk_graphics_pipeline_all_state all;
|
||||
vk_graphics_pipeline_state_fill(&device->vk, &state, pCreateInfo,
|
||||
NULL, &all, NULL, 0, NULL);
|
||||
|
||||
.. c:autofunction:: vk_graphics_pipeline_state_fill
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
.. c:autofunction:: vk_graphics_pipeline_state_merge
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
|
||||
Dynamic state
|
||||
-------------
|
||||
|
||||
All dynamic states in Vulkan, regardless of which API version or extension
|
||||
introduced them, are represented by the
|
||||
:c:enum:`mesa_vk_dynamic_graphics_state` enum. This corresponds to the
|
||||
:c:type:`VkDynamicState` enum in the Vulkan API only it's compact (has no
|
||||
holes due to extension namespacing) and a bit better organized. Each
|
||||
enumerant is named with the name of the state group to which the dynamic
|
||||
state belongs as well as the name of the dynamic state itself. The fact
|
||||
that it's compact allows us to use to index bitsets.
|
||||
|
||||
.. c:autofunction:: vk_get_dynamic_graphics_states
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
We also provide a :c:struct:`vk_dynamic_graphics_state` structure which
|
||||
contains all the dynamic graphics states, regardless of which API version
|
||||
or extension introduced them. This structure can be populated from a
|
||||
:c:struct:`vk_graphics_pipeline_state` via
|
||||
:c:func:`vk_dynamic_graphics_state_init`.
|
||||
|
||||
.. c:autofunction:: vk_dynamic_graphics_state_init
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
.. c:autofunction:: vk_dynamic_graphics_state_copy
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
There is also a :c:struct:`vk_dynamic_graphics_state` embedded in
|
||||
:c:struct:`vk_command_buffer`. Should you choose to use them, we provide
|
||||
common implementations for all ``vkCmdSet*()`` functions. Two additional
|
||||
functions are provided for the driver to call in ``CmdBindPipeline()`` and
|
||||
``CmdBindVertexBuffers2()``:
|
||||
|
||||
.. c:autofunction:: vk_cmd_set_dynamic_graphics_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
.. c:autofunction:: vk_cmd_set_vertex_binding_strides
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
To use the dynamic state framework, you will need the following in your
|
||||
pipeline structure:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct drv_graphics_pipeline {
|
||||
....
|
||||
struct vk_vertex_input_state vi_state;
|
||||
struct vk_sample_locations_state sl_state;
|
||||
struct vk_dynamic_graphics_state dynamic;
|
||||
...
|
||||
};
|
||||
|
||||
Then, in your pipeline create function,
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
memset(&pipeline->dynamic, 0, sizeof(pipeline->dynamic));
|
||||
pipeline->dynamic->vi = &pipeline->vi_state;
|
||||
pipeline->dynamic->ms.sample_locations = &pipeline->sl_state;
|
||||
vk_dynamic_graphics_state_init(&pipeline->dynamic, &state);
|
||||
|
||||
In your implementation of ``vkCmdBindPipeline()``,
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
vk_cmd_set_dynamic_graphics_state(&cmd->vk, &pipeline->dynamic_state);
|
||||
|
||||
And, finally, at ``vkCmdDraw*()`` time, the code to emit dynamic state into
|
||||
your hardware command buffer will look something like this:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
static void
|
||||
emit_dynamic_state(struct drv_cmd_buffer *cmd)
|
||||
{
|
||||
struct vk_dynamic_graphics_state *dyn = &cmd->vk.dynamic_graphics_state;
|
||||
|
||||
if (!vk_dynamic_graphics_state_any_dirty(dyn))
|
||||
return;
|
||||
|
||||
if (BITSET_TEST(dyn->dirty, MESA_VK_DYNAMIC_VP_VIEWPORTS) |
|
||||
BITSET_TEST(dyn->dirty, MESA_VK_DYNAMIC_VP_VIEWPORT_COUNT)) {
|
||||
/* Re-emit viewports */
|
||||
}
|
||||
|
||||
if (BITSET_TEST(dyn->dirty, MESA_VK_DYNAMIC_VP_SCISSORS) |
|
||||
BITSET_TEST(dyn->dirty, MESA_VK_DYNAMIC_VP_SCISSOR_COUNT)) {
|
||||
/* Re-emit scissors */
|
||||
}
|
||||
|
||||
/* etc... */
|
||||
|
||||
vk_dynamic_graphics_state_clear_dirty(dyn);
|
||||
}
|
||||
|
||||
Any states used by the currently bound pipeline and attachments are always
|
||||
valid in ``vk_command_buffer::dynamic_graphics_state`` so you can always
|
||||
use a state even if it isn't dirty on this particular draw.
|
||||
|
||||
.. c:autofunction:: vk_dynamic_graphics_state_dirty_all
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
.. c:autofunction:: vk_dynamic_graphics_state_clear_dirty
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
.. c:autofunction:: vk_dynamic_graphics_state_any_dirty
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
|
||||
Depth stencil state optimization
|
||||
--------------------------------
|
||||
|
||||
.. c:autofunction:: vk_optimize_depth_stencil_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
|
||||
Reference
|
||||
---------
|
||||
|
||||
.. c:autostruct:: vk_graphics_pipeline_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_vertex_binding_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_vertex_attribute_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_vertex_input_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_input_assembly_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_tessellation_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_viewport_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_discard_rectangles_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_rasterization_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_fragment_shading_rate_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_sample_locations_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_multisample_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_stencil_test_face_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_depth_stencil_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_color_blend_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_render_pass_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
|
||||
.. c:autoenum:: mesa_vk_dynamic_graphics_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
|
||||
.. c:autostruct:: vk_dynamic_graphics_state
|
||||
:file: src/vulkan/runtime/vk_graphics_state.h
|
||||
:members:
|
||||
@@ -0,0 +1,16 @@
|
||||
Vulkan Runtime
|
||||
==============
|
||||
|
||||
The Vulkan runtime and utility code in Mesa provides a powerful shared core
|
||||
for building Vulkan drivers. It's a collection of base structures (think
|
||||
base classes in OOO) which allow us to implement a bunch of the annoying
|
||||
hardware-agnostic bits in common code.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
base-objs
|
||||
dispatch
|
||||
command-pools
|
||||
graphics-state
|
||||
renderpass
|
||||
@@ -0,0 +1,111 @@
|
||||
Render Passes
|
||||
=============
|
||||
|
||||
The Vulkan runtime code in Mesa provides several helpful utilities to make
|
||||
managing render passes easier.
|
||||
|
||||
|
||||
:ext:`VK_KHR_create_renderpass2`
|
||||
--------------------------------
|
||||
|
||||
It is strongly recommended that drivers implement
|
||||
:ext:`VK_KHR_create_renderpass2` directly and not bother implementing the
|
||||
old Vulkan 1.0 entrypoints. If a driver does not implement them, the
|
||||
following will be implemented in common code in terms of their
|
||||
:ext:`VK_KHR_create_renderpass2` counterparts:
|
||||
|
||||
- :c:func:`vkCreateRenderPass`
|
||||
- :c:func:`vkCmdBeginRenderPass`
|
||||
- :c:func:`vkCmdNextSubpass`
|
||||
- :c:func:`vkCmdEndRenderPass`
|
||||
|
||||
|
||||
Common VkRenderPass implementation
|
||||
----------------------------------
|
||||
|
||||
The Vulkan runtime code in Mesa provides a common implementation of
|
||||
:c:type:`VkRenderPass` called :c:struct:`vk_render_pass` which drivers
|
||||
can optionally use. Unlike most Vulkan runtime structs, it's not really
|
||||
designed to be used as a base for a driver-specific struct. It does,
|
||||
however, contain all the information passed to
|
||||
:c:func:`vkCreateRenderPass2` so it can be used in a driver so long as
|
||||
that driver doesn't need to do any additional compilation at
|
||||
:c:func:`vkCreateRenderPass2` time. If a driver chooses to use
|
||||
:c:struct:`vk_render_pass`, the Vulkan runtime provides implementations
|
||||
of :c:func:`vkCreateRenderPass2` and :c:func:`vkDestroyRenderPass`.
|
||||
|
||||
|
||||
:ext:`VK_KHR_dynamic_rendering`
|
||||
-------------------------------
|
||||
|
||||
For drivers which don't need to do subpass combining, it is recommended
|
||||
that they skip implementing render passes entirely and implement
|
||||
:ext:`VK_KHR_dynamic_rendering` instead. If they choose to do so, the runtime
|
||||
will provide the following, implemented in terms of
|
||||
:c:func:`vkCmdBeginRendering` and :c:func:`vkCmdEndRendering`:
|
||||
|
||||
- :c:func:`vkCmdBeginRenderPass2`
|
||||
- :c:func:`vkCmdNextSubpass2`
|
||||
- :c:func:`vkCmdEndRenderPass2`
|
||||
|
||||
We also provide a no-op implementation of
|
||||
:c:func:`vkGetRenderAreaGranularity` which returns a render area
|
||||
granularity of 1x1.
|
||||
|
||||
Drivers which wish to use the common render pass implementation in this way
|
||||
**must** also support a Mesa-specific pseudo-extension which optionally
|
||||
provides an initial image layout for each attachment at
|
||||
:c:func:`vkCmdBeginRendering` time. This is required for us to combine
|
||||
render pass clears with layout transitions, often from
|
||||
:c:enum:`VK_IMAGE_LAYOUT_UNDEFINED`. On at least Intel and AMD,
|
||||
combining these transitions with clears is important for performance.
|
||||
|
||||
.. c:autostruct:: VkRenderingAttachmentInitialLayoutInfoMESA
|
||||
:file: src/vulkan/util/vk_internal_exts.h
|
||||
:members:
|
||||
|
||||
Because render passes and subpass indices are also passed into
|
||||
:c:func:`vkCmdCreateGraphicsPipelines` and
|
||||
:c:func:`vkCmdExecuteCommands` which we can't implement on the driver's
|
||||
behalf, we provide a couple of helpers for getting the render pass
|
||||
information in terms of the relevant :ext:`VK_KHR_dynamic_rendering`:
|
||||
|
||||
.. c:autofunction:: vk_get_pipeline_rendering_create_info
|
||||
:file: src/vulkan/runtime/vk_render_pass.h
|
||||
|
||||
.. c:autofunction:: vk_get_command_buffer_inheritance_rendering_info
|
||||
:file: src/vulkan/runtime/vk_render_pass.h
|
||||
|
||||
Apart from handling layout transitions, the common render pass
|
||||
implementation mostly ignores input attachments. It is expected that the
|
||||
driver call :c:func:`nir_lower_input_attachments` to turn them into
|
||||
texturing operations. The driver **must** support texturing from an input
|
||||
attachment at the same time as rendering to it in order to support Vulkan
|
||||
subpass self-dependencies. ``VK_EXT_attachment_feedback_loop_layout`` provides
|
||||
information on when these self dependencies are present.
|
||||
|
||||
vk_render_pass reference
|
||||
------------------------
|
||||
|
||||
The following is a reference for the :c:struct:`vk_render_pass` structure
|
||||
and its substructures.
|
||||
|
||||
.. c:autostruct:: vk_render_pass
|
||||
:file: src/vulkan/runtime/vk_render_pass.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_render_pass_attachment
|
||||
:file: src/vulkan/runtime/vk_render_pass.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_subpass
|
||||
:file: src/vulkan/runtime/vk_render_pass.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_subpass_attachment
|
||||
:file: src/vulkan/runtime/vk_render_pass.h
|
||||
:members:
|
||||
|
||||
.. c:autostruct:: vk_subpass_dependency
|
||||
:file: src/vulkan/runtime/vk_render_pass.h
|
||||
:members:
|
||||
Reference in New Issue
Block a user