Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions docs/extension_support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Extension support in a layer

It might be useful for some layers to implement an extension, such as
`VK_EXT_frame_boundary`, even if the underlying driver does not support it.
This page explains the general approach that needs to be taken, and the
specific API modifications that need to be applied for specific extensions.

The core libGPULayers framework allows you to expose additional extensions via
the default `vkEnumerate*ExtensionProperties()` implementation, but per-layer
code must implement the API modifications in any other functions as needed.

## Exposing a new extension

New extensions are advertised to applications by adding the extension string to
the list returned by `vkEnumerate*ExtensionProperties()`. This functionality
is provided in the common framework default functions. Layer implementations
add the new extension information that they want to expose to either:

* `Instance::injectedInstanceExtensions` for instance extensions.
* `Instance::injectedDeviceExtensions` for device extensions.

Device extensions will be removed from this list if we can detect that the
underlying device already supports them, which means we can just pass through
rather than emulating support.

### Handling extended API entry points

All entrypoints that are touched by an extension need to be intercepted with a
`user_tag` version of that function, which will implement the functionality
that the layer requires.

If the driver beneath the layer actually supports the extension, the extended
API parameters can be passed down to the driver without modification. This
scenario can be detected by checking that the extension name is no longer in
the `injectedExtensions` list, although the layer will probably want to cache
this check to reduce performance overhead.

If the driver beneath the layer does not support the extension, the extended
API parameters should be rewritten to remove the extension before passing down
to the driver. User structure inputs to the Vulkan API are usually marked as
`const`, so we must take a safe-struct copy which we can modify and then pass
that copy to the driver.

Note that Vulkan specifies that components must ignore structures in the
`pNext` chain that they do not understand:

> Any component of the implementation (the loader, any enabled layers, and
> drivers) must skip over, without processing (other than reading the `sType`
> and `pNext` members) any extending structures in the chain not defined by
> core versions or extensions supported by that component.

Any extension structures can therefore be left in-situ when being emulated, but
any other API parameter modifications must be unpicked to hide the emulation.

## Common extension notes

This section is a set of brief notes about extensions that we have implemented,
summarizing the changes needed and referencing where you can find an example
of the changes if you need something similar.

### VK_EXT_frame_boundary

This extension allows applications to annotate arbitrary submit calls to
indicate which frame the submitted work belongs to, instead of relying on
`vkQueuePresent()`. This can be useful for multi-threaded applications,
where CPU processing for frames can overlap, and for applications which
do not have frames, but that want to use tools such as RenderDoc that
require them.

The `layer_gpu_timeline` layer is an example of a layer exposing this
extension using emulation on devices that do not support it.

#### Exposing extension

Adding exposure handling:

* Add `VK_EXT_frame_boundary` to device extension list.
* Populate the `VkPhysicalDeviceFrameBoundary` in the
`VkPhysicalDeviceFeatures2.pNext` list returned by
`vkGetPhysicalDeviceFeatures2()`, forcing the value to `VK_TRUE`, if the
extension is "supported" but feature-disabled by the driver.
* Query `VkPhysicalDeviceFrameBoundary` in `VkDeviceCreateInfo.pNext` to see if
application enabled the extension.

#### Implementing extension

Adding implementation handling:

* Add `VkFrameBoundaryEXT` extension struct handling to:
* `vkQueueSubmit()`
* `vkQueueSubmit2()`
* `vkQueuePresent()`
* `vkQueueBindSparse()`

#### Implementation notes

Most applications using this that I have seen are using it to demarcate frames
when using a single submitting render thread for off-screen rendering or
compute use cases that do not use `vkQueuePresent()`. In these systems just
detecting the frame boundary flag in the extension structure passed to a queue
submit is enough, and how we would use `vkQueuePresent()` to do the same
without this extension.

It is possible for applications to have multiple concurrent frames being
submitted in an overlapping manner, which can be handled by tagging work with
the frame ID found in the extension structure for each `vkQueue*()` call. This
will require downstream data handling to cope with overlapping frame
submissions, which most of our layers do not handle, as it is rarely
encountered.

- - -

_Copyright © 2025, Arm Limited and contributors._
8 changes: 7 additions & 1 deletion generator/vk_layer/source/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,16 @@ static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;
const APIVersion Instance::minAPIVersion { 1, 1 };

/* See header for documentation. */
const std::vector<std::string> Instance::extraExtensions {
const std::vector<std::string> Instance::requiredDriverExtensions {
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
};

/* See header for documentation. */
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};

/* See header for documentation. */
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};

/* See header for documentation. */
void Instance::store(
VkInstance handle,
Expand Down
21 changes: 19 additions & 2 deletions generator/vk_layer/source/instance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,24 @@ class Instance
static const APIVersion minAPIVersion;

/**
* @brief The minimum set of instance extensions needed by this layer.
* @brief Required extensions from the driver.
*
* The layer will attempt to enable these even if the application does not.
*/
static const std::vector<std::string> requiredDriverExtensions;

/**
* @brief Additional instance extensions injected by the layer.
*
* The layer will expose these even if the driver does not.
*/
static const std::vector<std::pair<std::string, uint32_t>> injectedInstanceExtensions;

/**
* @brief Additional device extensions injected by the layer.
*
* The layer will expose these even if the driver does not. Items are
* removed from the list if the driver already exposes the extension.
*/
static const std::vector<std::string> extraExtensions;
static std::vector<std::pair<std::string, uint32_t>> injectedDeviceExtensions;
};
12 changes: 10 additions & 2 deletions layer_example/source/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,18 @@
static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;

/* See header for documentation. */
const APIVersion Instance::minAPIVersion {1, 1};
const APIVersion Instance::minAPIVersion { 1, 1 };

/* See header for documentation. */
const std::vector<std::string> Instance::extraExtensions {VK_EXT_DEBUG_UTILS_EXTENSION_NAME};
const std::vector<std::string> Instance::requiredDriverExtensions {
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
};

/* See header for documentation. */
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};

/* See header for documentation. */
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};

/* See header for documentation. */
void Instance::store(VkInstance handle, std::unique_ptr<Instance>& instance)
Expand Down
21 changes: 19 additions & 2 deletions layer_example/source/instance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,24 @@ class Instance
static const APIVersion minAPIVersion;

/**
* @brief The minimum set of instance extensions needed by this layer.
* @brief Required extensions from the driver.
*
* The layer will attempt to enable these even if the application does not.
*/
static const std::vector<std::string> requiredDriverExtensions;

/**
* @brief Additional instance extensions injected by the layer.
*
* The layer will expose these even if the driver does not.
*/
static const std::vector<std::pair<std::string, uint32_t>> injectedInstanceExtensions;

/**
* @brief Additional device extensions injected by the layer.
*
* The layer will expose these even if the driver does not. Items are
* removed from the list if the driver already exposes the extension.
*/
static const std::vector<std::string> extraExtensions;
static std::vector<std::pair<std::string, uint32_t>> injectedDeviceExtensions;
};
10 changes: 8 additions & 2 deletions layer_gpu_profile/source/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,19 @@
static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;

/* See header for documentation. */
const APIVersion Instance::minAPIVersion {1, 1};
const APIVersion Instance::minAPIVersion { 1, 1 };

/* See header for documentation. */
const std::vector<std::string> Instance::extraExtensions {
const std::vector<std::string> Instance::requiredDriverExtensions {
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
};

/* See header for documentation. */
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};

/* See header for documentation. */
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};

/* See header for documentation. */
void Instance::store(VkInstance handle, std::unique_ptr<Instance>& instance)
{
Expand Down
21 changes: 19 additions & 2 deletions layer_gpu_profile/source/instance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,24 @@ class Instance
static const APIVersion minAPIVersion;

/**
* @brief The minimum set of instance extensions needed by this layer.
* @brief Required extensions from the driver.
*
* The layer will attempt to enable these even if the application does not.
*/
static const std::vector<std::string> requiredDriverExtensions;

/**
* @brief Additional instance extensions injected by the layer.
*
* The layer will expose these even if the driver does not.
*/
static const std::vector<std::pair<std::string, uint32_t>> injectedInstanceExtensions;

/**
* @brief Additional device extensions injected by the layer.
*
* The layer will expose these even if the driver does not. Items are
* removed from the list if the driver already exposes the extension.
*/
static const std::vector<std::string> extraExtensions;
static std::vector<std::pair<std::string, uint32_t>> injectedDeviceExtensions;
};
8 changes: 4 additions & 4 deletions layer_gpu_profile/source/layer_device_functions_debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ VKAPI_ATTR void VKAPI_CALL layer_vkCmdDebugMarkerBeginEXT<user_tag>(VkCommandBuf
auto* layer = Device::retrieve(commandBuffer);

// Only instrument inside active frame of interest
if(layer->isFrameOfInterest)
if (layer->isFrameOfInterest)
{
auto& tracker = layer->getStateTracker();
auto& cb = tracker.getCommandBuffer(commandBuffer);
Expand All @@ -67,7 +67,7 @@ VKAPI_ATTR void VKAPI_CALL layer_vkCmdDebugMarkerEndEXT<user_tag>(VkCommandBuffe
auto* layer = Device::retrieve(commandBuffer);

// Only instrument inside active frame of interest
if(layer->isFrameOfInterest)
if (layer->isFrameOfInterest)
{
auto& tracker = layer->getStateTracker();
auto& cb = tracker.getCommandBuffer(commandBuffer);
Expand All @@ -93,7 +93,7 @@ VKAPI_ATTR void VKAPI_CALL layer_vkCmdBeginDebugUtilsLabelEXT<user_tag>(VkComman
auto* layer = Device::retrieve(commandBuffer);

// Only instrument inside active frame of interest
if(layer->isFrameOfInterest)
if (layer->isFrameOfInterest)
{
auto& tracker = layer->getStateTracker();
auto& cb = tracker.getCommandBuffer(commandBuffer);
Expand All @@ -118,7 +118,7 @@ VKAPI_ATTR void VKAPI_CALL layer_vkCmdEndDebugUtilsLabelEXT<user_tag>(VkCommandB
auto* layer = Device::retrieve(commandBuffer);

// Only instrument inside active frame of interest
if(layer->isFrameOfInterest)
if (layer->isFrameOfInterest)
{
auto& tracker = layer->getStateTracker();
auto& cb = tracker.getCommandBuffer(commandBuffer);
Expand Down
5 changes: 3 additions & 2 deletions layer_gpu_support/source/device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ static void modifyDeviceRobustBufferAccess(Instance& instance,
{
if (enableRobustness)
{
if(config->robustBufferAccess)
if (config->robustBufferAccess)
{
LAYER_LOG("Device feature already enabled: robustBufferAccess");
}
Expand All @@ -190,9 +190,10 @@ static void modifyDeviceRobustBufferAccess(Instance& instance,
config->robustBufferAccess = VK_TRUE;
}
}

if (disableRobustness)
{
if(!config->robustBufferAccess)
if (!config->robustBufferAccess)
{
LAYER_LOG("Device feature already disabled: robustBufferAccess");
}
Expand Down
10 changes: 8 additions & 2 deletions layer_gpu_support/source/instance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,19 @@
static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;

/* See header for documentation. */
const APIVersion Instance::minAPIVersion {1, 1};
const APIVersion Instance::minAPIVersion { 1, 1 };

/* See header for documentation. */
const std::vector<std::string> Instance::extraExtensions {
const std::vector<std::string> Instance::requiredDriverExtensions {
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
};

/* See header for documentation. */
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};

/* See header for documentation. */
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};

/* See header for documentation. */
void Instance::store(VkInstance handle, std::unique_ptr<Instance>& instance)
{
Expand Down
21 changes: 19 additions & 2 deletions layer_gpu_support/source/instance.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,24 @@ class Instance
static const APIVersion minAPIVersion;

/**
* @brief The minimum set of instance extensions needed by this layer.
* @brief Required extensions from the driver.
*
* The layer will attempt to enable these even if the application does not.
*/
static const std::vector<std::string> requiredDriverExtensions;

/**
* @brief Additional instance extensions injected by the layer.
*
* The layer will expose these even if the driver does not.
*/
static const std::vector<std::pair<std::string, uint32_t>> injectedInstanceExtensions;

/**
* @brief Additional device extensions injected by the layer.
*
* The layer will expose these even if the driver does not. Items are
* removed from the list if the driver already exposes the extension.
*/
static const std::vector<std::string> extraExtensions;
static std::vector<std::pair<std::string, uint32_t>> injectedDeviceExtensions;
};
8 changes: 4 additions & 4 deletions layer_gpu_support/source/layer_device_functions_image.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage<user_tag>(VkDevice device,
}

// Create modifiable structures we can patch
vku::safe_VkImageCreateInfo newCreateInfoSafe(pCreateInfo);
auto* newCreateInfo = reinterpret_cast<VkImageCreateInfo*>(&newCreateInfoSafe);
vku::safe_VkImageCreateInfo safeCreateInfo(pCreateInfo);
auto* newCreateInfo = reinterpret_cast<VkImageCreateInfo*>(&safeCreateInfo);
// We know we can const-cast here because this is a safe-struct clone
void* pNextBase = const_cast<void*>(newCreateInfoSafe.pNext);
void* pNextBase = const_cast<void*>(safeCreateInfo.pNext);

// Create extra structures we can patch in
VkImageCompressionControlEXT newCompressionControl = vku::InitStructHelper();
Expand Down Expand Up @@ -165,7 +165,7 @@ VKAPI_ATTR VkResult VKAPI_CALL layer_vkCreateImage<user_tag>(VkDevice device,
// Add a config if not already configured by the application
if (patchNeeded)
{
vku::AddToPnext(newCreateInfoSafe, *compressionControl);
vku::AddToPnext(safeCreateInfo, *compressionControl);
}

return layer->driver.vkCreateImage(device, newCreateInfo, pAllocator, pImage);
Expand Down
1 change: 1 addition & 0 deletions layer_gpu_timeline/source/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ add_library(
layer_device_functions_render_pass.cpp
layer_device_functions_trace_rays.cpp
layer_device_functions_transfer.cpp
layer_instance_functions.cpp
timeline_comms.cpp
timeline_protobuf_encoder.cpp)

Expand Down
Loading