// Copyright 2022 The Khronos Group Inc.
//
// SPDX-License-Identifier: CC-BY-4.0

= VK_EXT_swapchain_maintenance1
:toc: left
:refpage: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/
:sectnums:

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

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:

[source,c]
----
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`:

[source,c]
----
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::pname:pWaitSemaphores`.

=== Switching Present Modes

During creation of the swapchain, all potential present modes are specified by
chaining the following to `VkSwapchainCreateInfoKHR`:

[source,c]
----
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`:

[source,c]
----
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`:

[source,c]
----
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`:

[source,c]
----
VKAPI_ATTR VkResult VKAPI_CALL vkReleaseSwapchainImagesEXT(
    VkDevice                                    device,
    const VkReleaseSwapchainImagesInfoEXT*      pReleaseInfo);
----

`VkReleaseSwapchainImagesInfoEXT` is defined as:

[source,c]
----
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.
