mesa-clc: vendor as full-fork recipe (path=source, patches baked)

This commit is contained in:
2026-08-01 04:44:05 +03:00
parent f8e4789a2b
commit 406ee9fbeb
12522 changed files with 6035591 additions and 3 deletions
@@ -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: