VK_EXT_swapchain_maintenance1.proposal
This proposal investigates and addresses a number of practical issues with the
VK_KHR_swapchain
specification.
Problem Statement
The following is a list of issues considered in this proposal:
- It is not directly known when a semaphore used for presentation can be recycled.
- Switching present modes requires a swapchain recreation
- Upfront memory allocation is costly in startup time and memory
- Unknown behavior when presenting a swapchain image to a surface with different dimensions
- Impossible to release acquired images without presenting them
Recycling Present Semaphores
With VK_KHR_swapchain
, there is no way to provide feedback as to when a
semaphore used for presentation can be freed or recycled.
The application would have to infer this information from when the fence of the
next call to vkAcquireNextImageKHR
that returns the presented image’s index
has been signaled.
This is needlessly complicated.
Swapchain Recreation on Present Mode Change
Currently, when changing present modes, the Vulkan swapchain is required to be recreated. This is unnecessary if otherwise the swapchain is not changed, as the present mode does not affect the actual swapchain images and their associated memory. This is realistically affecting apps that switch between VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR and VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR (in single-buffer applications). In that case, a flicker is noticed as the swapchain is recreated and the old contents of the swapchain is discarded.
Upfront Memory Allocation
Currently, Vulkan allows the application to record commands using any image
from the swapchain.
Additionally, the application may use VkBindImageMemorySwapchainInfoKHR
at
any time to bind an image to swapchain memory.
This requires swapchain implementations to allocate memory for all images
upfront.
This is costly both in startup time, and potentially memory if all images are
not eventually acquired.
Scaling Behavior
It is desirable for the application to specify a scaling behavior for when the
swapchain extents do not match the surface’s, such as during window resizing,
instead of receiving VK_ERROR_OUT_OF_DATE_KHR
.
See the proposal document for VK_EXT_surface_maintenance1
for details.
Releasing Acquired Images
When the swapchain needs to be recreated, it may be possible that an application has acquired images from the old swapchain that it is no longer interested in presenting to. However, it is unable to release those images before recreating the swapchain. As a result, the application is forced to present to an old swapchain that may no longer match the surface. Additionally, the memory allocated for the old and new swapchains coexist, increasing peak memory usage.
Solution Space
There are generally two possible approaches to resolving these problems:
- Have the application directly interface with the window system, bypassing
VK_KHR_swapchain
. - Improve upon
VK_KHR_swapchain
The benefits of the first approach are:
- Total control of the swapchain implementation by the application, which would allow for prompt fixes of future issue
- Works on existing drivers, removing any need for fallbacks
However, this approach results in duplicated effort among applications. A potential library with a swapchain implementation can be conceived which could be updated and shipped with applications independently from driver updates, though it lacks the benefit of driver updates bringing fixes and optimizations to all applications.
This proposal considers the second approach with the goal of improving the larger Vulkan ecosystem while leveraging existing swapchain implementations which most applications have come to rely on.
Recycling Present Semaphores
There are three potential solutions for this:
- A status query for present operations, potentially identified through ids
assigned with
VK_KHR_present_id
. - A wait function similar to
vkWaitForPresentKHR
- A fence passed to
vkQueuePresentKHR
that signals when the presentation engine no longer accesses the present semaphores
The last solution is adopted as it provides more power to the application (wait, in addition to query), as well as fitting better with event loops, other Vulkan APIs and future work to enable timeline semaphores.
Swapchain Recreation on Present Mode Change
Each present mode may require a different number of images.
Additionally, the VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR
and
VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR
present modes may require a
different image memory layout compared with the rest of the present modes.
Switching between non-shared present modes may or may not be possible on some
implementations.
Regarding image count, potential solutions are:
- Specify all potential present modes at swapchain creation time
- The swapchain would then allocate as many images to satisfy all present modes
- Increase the number of swapchain images as necessary when present mode is
changed
- This requires the application to use
vkGetSwapchainImagesKHR
after each change in present mode and adjust accordingly
- This requires the application to use
Regarding the subsets of switchable present modes that the implementation supports, potential solutions are:
- Specify a particular behavior for
VkSwapchainCreateInfoKHR::oldSwapchain
when the only change is in the present mode- This is not straightforward and is error-prone
- Allow modifying present modes as needed, such as during a
vkQueuePresentKHR
call- Implementations may support switching between present modes only in disjoint subsets. This subsets can be queried. Alternatively, Vulkan could divide the present modes into shared and non-shared, with the latter conditional to a feature flag.
The adopted solution in this proposal is to allow applications to specify a
potential set of present modes at swapchain creation time.
A query must be used to determine whether these present modes are compatible
for dynamic switching.
Then, the application would pass the desired present mode to
vkQueuePresentKHR
.
With the solution to the "Upfront Memory Allocation" problem, the application can avoid paying any extra memory cost due to higher image counts until a present mode that uses that many images is used.
Upfront Memory Allocation
This can be explicitly opted in with a new swapchain create flag.
Using this flag would prohibit problematic scenarios, such as using
VkBindImageMemorySwapchainInfoKHR
with an image index that has never been
acquired, or recording command buffers using those swapchain images.
This amortizes the cost of memory allocation between the first few frames, using CPU time that may otherwise have gone idle waiting for the GPU. This will additionally resolve a number of memory issues:
- If a present mode is specified at swapchain creation time which requires a larger number of images, but that is never actually used, the extra images would not have to consume memory.
- When resizing the swapchain, peak memory increase is avoided by not actually allocating memory for the new swapchain. First memory allocation for the new swapchain could happen after some of the images from the old swapchain have been destroyed.
Scaling Behavior
The VK_EXT_surface_maintenance1
extension introduces scaling and gravity
behavior enums whose support can be queried from the surface.
At swapchain creation time, one of the supported behavior can be specified by
the application.
Releasing Acquired Images
There are a number of ways the applications could partially work around this issue, such as by deferring image acquisition, or recreating the swapchain only once all images from the old swapchain are presented.
However, in all implementations, releasing an acquired image without presenting it is no more complex than presenting it. The adopted solution is to expose this functionality in this extension.
Proposal
Introduced by this API are:
Feature
Advertising whether the implementation supports the functionality in this extension:
typedef struct VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT {
VkStructureType sType;
void* pNext;
VkBool32 swapchainMaintenance1;
} VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT;
Present Fence
To associate fences with the present operations for each swapchain, chain the
following to VkPresentInfoKHR
:
typedef struct VkSwapchainPresentFenceInfoEXT {
VkStructureType sType;
void* pNext;
uint32_t swapchainCount;
VkFence* pFences;
} VkSwapchainPresentFenceInfoEXT;
With swapchainCount
matching
VkSwapchainPresentFenceInfoEXT::swapchainCount
, each swapchain being
presented to will signal the fence once the application is allowed to destroy
or recycle the semaphores passed to vkPresentInfoKHR::pWaitSemaphores
.
Switching Present Modes
During creation of the swapchain, all potential present modes are specified by
chaining the following to VkSwapchainCreateInfoKHR
:
typedef struct VkSwapchainPresentModesCreateInfoEXT {
VkStructureType sType;
void* pNext;
uint32_t presentModeCount;
VkPresentModeKHR* pPresentModes;
} VkSwapchainPresentModesCreateInfoEXT;
The present modes given in pPresentModes
must be compatible for mode
switching.
This can be queried by use of VkSurfacePresentModeCompatibilityEXT
from the
VK_EXT_surface_maintenance1
extension.
The present mode can be changed by chaining the following to
VkPresentInfoKHR
:
typedef struct VkSwapchainPresentModeInfoEXT {
VkStructureType sType;
void* pNext;
uint32_t swapchainCount;
VkPresentModeKHR* pPresentModes;
} VkSwapchainPresentModeInfoEXT;
Where the elements of pPresentModes
can take any present mode specified in
VkSwapchainPresentModesCreateInfoEXT
during the creation of the respective
swapchain.
If not specified, the swapchain will continue to operate according to the last
specified present mode.
Swapchain Memory Allocation
To allow the swapchain to defer memory allocation for each image until it is
acquired through vkAcquireNextImageKHR
, specify the
VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT
flag in
VkSwapchainCreateInfoKHR::flags
.
In that case, the application may still use vkGetSwapchainImagesKHR
to
retrieve the image handles, but it may not use any image whose index has never
been returned by vkAcquireNextImageKHR
; this includes recording commands
using such an image, or binding another image to its memory through
VkBindImageMemorySwapchainInfoKHR
.
As memory allocation for each image is deferred, so should the application defer the above operations for each image until it is first acquired.
Scaling Behavior
To specify the scaling behavior of the swapchain, chain the following to
VkSwapchainCreateInfoKHR
:
typedef struct VkSwapchainPresentScalingCreateInfoEXT {
VkStructureType sType;
void* pNext;
VkPresentScalingFlagsEXT scalingBehavior;
VkPresentGravityFlagsEXT presentGravityX;
VkPresentGravityFlagsEXT presentGravityY;
} VkSwapchainPresentScalingCreateInfoEXT;
The values specified in scalingBehavior
, presentGravityX
and
presentGravityY
must be supported by the surface.
This can be queried by use of VkSurfacePresentScalingCapabilitiesEXT
from the
VK_EXT_surface_maintenance1
extension.
Releasing Acquired Images
To release previously acquired images back to the swapchain, call
vkReleaseSwapchainImagesEXT
:
VKAPI_ATTR VkResult VKAPI_CALL vkReleaseSwapchainImagesEXT(
VkDevice device,
const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo);
VkReleaseSwapchainImagesInfoEXT
is defined as:
typedef struct VkReleaseSwapchainImagesInfoEXT {
VkStructureType sType;
const void* pNext;
VkSwapchainKHR swapchain;
deUint32 imageIndexCount;
const deUint32* pImageIndices;
} VkReleaseSwapchainImagesInfoEXT;
Issues
RESOLVED: Should there be a surface capability bit to advertise the present fence functionality?
No. Present fences fix a critical hole in the swapchain programming model and hence are a required feature of this maintenance extension.