Skip to content

Commit 57968aa

Browse files
authored
Support VK_EXT_frame_boundary (#160)
This PR adds infrastructure for layers to expose and emulate Vulkan extensions not supported by the underlying driver, specifically implementing VK_EXT_frame_boundary in the layer_gpu_timeline layer. The changes introduce a standardized mechanism for extension injection and provide an example implementation of frame boundary tracking. Key changes: Introduces injectedInstanceExtensions and injectedDeviceExtensions properties in the Instance class to allow layers to declare additional extension support. Layers rebased on top of this change will need to provide definitions of these, even if empty. Refactors extension enumeration functions to support both driver-native and layer-injected extensions. Implements VK_EXT_frame_boundary emulation in layer_gpu_timeline to mark frame boundaries in metadata.
1 parent 38d40b5 commit 57968aa

23 files changed

+839
-80
lines changed

docs/extension_support.md

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# Extension support in a layer
2+
3+
It might be useful for some layers to implement an extension, such as
4+
`VK_EXT_frame_boundary`, even if the underlying driver does not support it.
5+
This page explains the general approach that needs to be taken, and the
6+
specific API modifications that need to be applied for specific extensions.
7+
8+
The core libGPULayers framework allows you to expose additional extensions via
9+
the default `vkEnumerate*ExtensionProperties()` implementation, but per-layer
10+
code must implement the API modifications in any other functions as needed.
11+
12+
## Exposing a new extension
13+
14+
New extensions are advertised to applications by adding the extension string to
15+
the list returned by `vkEnumerate*ExtensionProperties()`. This functionality
16+
is provided in the common framework default functions. Layer implementations
17+
add the new extension information that they want to expose to either:
18+
19+
* `Instance::injectedInstanceExtensions` for instance extensions.
20+
* `Instance::injectedDeviceExtensions` for device extensions.
21+
22+
Device extensions will be removed from this list if we can detect that the
23+
underlying device already supports them, which means we can just pass through
24+
rather than emulating support.
25+
26+
### Handling extended API entry points
27+
28+
All entrypoints that are touched by an extension need to be intercepted with a
29+
`user_tag` version of that function, which will implement the functionality
30+
that the layer requires.
31+
32+
If the driver beneath the layer actually supports the extension, the extended
33+
API parameters can be passed down to the driver without modification. This
34+
scenario can be detected by checking that the extension name is no longer in
35+
the `injectedExtensions` list, although the layer will probably want to cache
36+
this check to reduce performance overhead.
37+
38+
If the driver beneath the layer does not support the extension, the extended
39+
API parameters should be rewritten to remove the extension before passing down
40+
to the driver. User structure inputs to the Vulkan API are usually marked as
41+
`const`, so we must take a safe-struct copy which we can modify and then pass
42+
that copy to the driver.
43+
44+
Note that Vulkan specifies that components must ignore structures in the
45+
`pNext` chain that they do not understand:
46+
47+
> Any component of the implementation (the loader, any enabled layers, and
48+
> drivers) must skip over, without processing (other than reading the `sType`
49+
> and `pNext` members) any extending structures in the chain not defined by
50+
> core versions or extensions supported by that component.
51+
52+
Any extension structures can therefore be left in-situ when being emulated, but
53+
any other API parameter modifications must be unpicked to hide the emulation.
54+
55+
## Common extension notes
56+
57+
This section is a set of brief notes about extensions that we have implemented,
58+
summarizing the changes needed and referencing where you can find an example
59+
of the changes if you need something similar.
60+
61+
### VK_EXT_frame_boundary
62+
63+
This extension allows applications to annotate arbitrary submit calls to
64+
indicate which frame the submitted work belongs to, instead of relying on
65+
`vkQueuePresent()`. This can be useful for multi-threaded applications,
66+
where CPU processing for frames can overlap, and for applications which
67+
do not have frames, but that want to use tools such as RenderDoc that
68+
require them.
69+
70+
The `layer_gpu_timeline` layer is an example of a layer exposing this
71+
extension using emulation on devices that do not support it.
72+
73+
#### Exposing extension
74+
75+
Adding exposure handling:
76+
77+
* Add `VK_EXT_frame_boundary` to device extension list.
78+
* Populate the `VkPhysicalDeviceFrameBoundary` in the
79+
`VkPhysicalDeviceFeatures2.pNext` list returned by
80+
`vkGetPhysicalDeviceFeatures2()`, forcing the value to `VK_TRUE`, if the
81+
extension is "supported" but feature-disabled by the driver.
82+
* Query `VkPhysicalDeviceFrameBoundary` in `VkDeviceCreateInfo.pNext` to see if
83+
application enabled the extension.
84+
85+
#### Implementing extension
86+
87+
Adding implementation handling:
88+
89+
* Add `VkFrameBoundaryEXT` extension struct handling to:
90+
* `vkQueueSubmit()`
91+
* `vkQueueSubmit2()`
92+
* `vkQueuePresent()`
93+
* `vkQueueBindSparse()`
94+
95+
#### Implementation notes
96+
97+
Most applications using this that I have seen are using it to demarcate frames
98+
when using a single submitting render thread for off-screen rendering or
99+
compute use cases that do not use `vkQueuePresent()`. In these systems just
100+
detecting the frame boundary flag in the extension structure passed to a queue
101+
submit is enough, and how we would use `vkQueuePresent()` to do the same
102+
without this extension.
103+
104+
It is possible for applications to have multiple concurrent frames being
105+
submitted in an overlapping manner, which can be handled by tagging work with
106+
the frame ID found in the extension structure for each `vkQueue*()` call. This
107+
will require downstream data handling to cope with overlapping frame
108+
submissions, which most of our layers do not handle, as it is rarely
109+
encountered.
110+
111+
- - -
112+
113+
_Copyright © 2025, Arm Limited and contributors._

generator/vk_layer/source/instance.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,16 @@ static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;
3838
const APIVersion Instance::minAPIVersion { 1, 1 };
3939

4040
/* See header for documentation. */
41-
const std::vector<std::string> Instance::extraExtensions {
41+
const std::vector<std::string> Instance::requiredDriverExtensions {
4242
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
4343
};
4444

45+
/* See header for documentation. */
46+
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};
47+
48+
/* See header for documentation. */
49+
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};
50+
4551
/* See header for documentation. */
4652
void Instance::store(
4753
VkInstance handle,

generator/vk_layer/source/instance.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,24 @@ class Instance
143143
static const APIVersion minAPIVersion;
144144

145145
/**
146-
* @brief The minimum set of instance extensions needed by this layer.
146+
* @brief Required extensions from the driver.
147+
*
148+
* The layer will attempt to enable these even if the application does not.
149+
*/
150+
static const std::vector<std::string> requiredDriverExtensions;
151+
152+
/**
153+
* @brief Additional instance extensions injected by the layer.
154+
*
155+
* The layer will expose these even if the driver does not.
156+
*/
157+
static const std::vector<std::pair<std::string, uint32_t>> injectedInstanceExtensions;
158+
159+
/**
160+
* @brief Additional device extensions injected by the layer.
161+
*
162+
* The layer will expose these even if the driver does not. Items are
163+
* removed from the list if the driver already exposes the extension.
147164
*/
148-
static const std::vector<std::string> extraExtensions;
165+
static std::vector<std::pair<std::string, uint32_t>> injectedDeviceExtensions;
149166
};

layer_example/source/instance.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,18 @@
3535
static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;
3636

3737
/* See header for documentation. */
38-
const APIVersion Instance::minAPIVersion {1, 1};
38+
const APIVersion Instance::minAPIVersion { 1, 1 };
3939

4040
/* See header for documentation. */
41-
const std::vector<std::string> Instance::extraExtensions {VK_EXT_DEBUG_UTILS_EXTENSION_NAME};
41+
const std::vector<std::string> Instance::requiredDriverExtensions {
42+
VK_EXT_DEBUG_UTILS_EXTENSION_NAME
43+
};
44+
45+
/* See header for documentation. */
46+
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};
47+
48+
/* See header for documentation. */
49+
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};
4250

4351
/* See header for documentation. */
4452
void Instance::store(VkInstance handle, std::unique_ptr<Instance>& instance)

layer_example/source/instance.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,24 @@ class Instance
137137
static const APIVersion minAPIVersion;
138138

139139
/**
140-
* @brief The minimum set of instance extensions needed by this layer.
140+
* @brief Required extensions from the driver.
141+
*
142+
* The layer will attempt to enable these even if the application does not.
143+
*/
144+
static const std::vector<std::string> requiredDriverExtensions;
145+
146+
/**
147+
* @brief Additional instance extensions injected by the layer.
148+
*
149+
* The layer will expose these even if the driver does not.
150+
*/
151+
static const std::vector<std::pair<std::string, uint32_t>> injectedInstanceExtensions;
152+
153+
/**
154+
* @brief Additional device extensions injected by the layer.
155+
*
156+
* The layer will expose these even if the driver does not. Items are
157+
* removed from the list if the driver already exposes the extension.
141158
*/
142-
static const std::vector<std::string> extraExtensions;
159+
static std::vector<std::pair<std::string, uint32_t>> injectedDeviceExtensions;
143160
};

layer_gpu_profile/source/instance.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,19 @@
3535
static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;
3636

3737
/* See header for documentation. */
38-
const APIVersion Instance::minAPIVersion {1, 1};
38+
const APIVersion Instance::minAPIVersion { 1, 1 };
3939

4040
/* See header for documentation. */
41-
const std::vector<std::string> Instance::extraExtensions {
41+
const std::vector<std::string> Instance::requiredDriverExtensions {
4242
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
4343
};
4444

45+
/* See header for documentation. */
46+
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};
47+
48+
/* See header for documentation. */
49+
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};
50+
4551
/* See header for documentation. */
4652
void Instance::store(VkInstance handle, std::unique_ptr<Instance>& instance)
4753
{

layer_gpu_profile/source/instance.hpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,24 @@ class Instance
143143
static const APIVersion minAPIVersion;
144144

145145
/**
146-
* @brief The minimum set of instance extensions needed by this layer.
146+
* @brief Required extensions from the driver.
147+
*
148+
* The layer will attempt to enable these even if the application does not.
149+
*/
150+
static const std::vector<std::string> requiredDriverExtensions;
151+
152+
/**
153+
* @brief Additional instance extensions injected by the layer.
154+
*
155+
* The layer will expose these even if the driver does not.
156+
*/
157+
static const std::vector<std::pair<std::string, uint32_t>> injectedInstanceExtensions;
158+
159+
/**
160+
* @brief Additional device extensions injected by the layer.
161+
*
162+
* The layer will expose these even if the driver does not. Items are
163+
* removed from the list if the driver already exposes the extension.
147164
*/
148-
static const std::vector<std::string> extraExtensions;
165+
static std::vector<std::pair<std::string, uint32_t>> injectedDeviceExtensions;
149166
};

layer_gpu_profile/source/layer_device_functions_debug.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ VKAPI_ATTR void VKAPI_CALL layer_vkCmdDebugMarkerBeginEXT<user_tag>(VkCommandBuf
4242
auto* layer = Device::retrieve(commandBuffer);
4343

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

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

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

120120
// Only instrument inside active frame of interest
121-
if(layer->isFrameOfInterest)
121+
if (layer->isFrameOfInterest)
122122
{
123123
auto& tracker = layer->getStateTracker();
124124
auto& cb = tracker.getCommandBuffer(commandBuffer);

layer_gpu_support/source/device.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ static void modifyDeviceRobustBufferAccess(Instance& instance,
180180
{
181181
if (enableRobustness)
182182
{
183-
if(config->robustBufferAccess)
183+
if (config->robustBufferAccess)
184184
{
185185
LAYER_LOG("Device feature already enabled: robustBufferAccess");
186186
}
@@ -190,9 +190,10 @@ static void modifyDeviceRobustBufferAccess(Instance& instance,
190190
config->robustBufferAccess = VK_TRUE;
191191
}
192192
}
193+
193194
if (disableRobustness)
194195
{
195-
if(!config->robustBufferAccess)
196+
if (!config->robustBufferAccess)
196197
{
197198
LAYER_LOG("Device feature already disabled: robustBufferAccess");
198199
}

layer_gpu_support/source/instance.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,19 @@
3535
static std::unordered_map<void*, std::unique_ptr<Instance>> g_instances;
3636

3737
/* See header for documentation. */
38-
const APIVersion Instance::minAPIVersion {1, 1};
38+
const APIVersion Instance::minAPIVersion { 1, 1 };
3939

4040
/* See header for documentation. */
41-
const std::vector<std::string> Instance::extraExtensions {
41+
const std::vector<std::string> Instance::requiredDriverExtensions {
4242
VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
4343
};
4444

45+
/* See header for documentation. */
46+
const std::vector<std::pair<std::string, uint32_t>> Instance::injectedInstanceExtensions {};
47+
48+
/* See header for documentation. */
49+
std::vector<std::pair<std::string, uint32_t>> Instance::injectedDeviceExtensions {};
50+
4551
/* See header for documentation. */
4652
void Instance::store(VkInstance handle, std::unique_ptr<Instance>& instance)
4753
{

0 commit comments

Comments
 (0)