Support VK_KHR_swapchain_mutable_format extension

- Add support for VK_KHR_swapchain_mutable_format extension to the Layer.
- Expose support for the VK_KHR_swapchain_mutable_format extension.
- Improve find_extension helper API to be spec-correct, const-correct,
  and clearer by using the right base type (VkBaseInStructure for input,
  VkBaseOutStructure for output),
  and concise docs explaining it returns the first matching struct or nullptr.
- Correctly use image_create_info instead of m_image_create_info in
  create_swapchain_image function for Wayland.

Signed-off-by: Maged Elnaggar <maged.elnaggar@arm.com>
Change-Id: Id40b28977e63ba76012d3a8f693327f757d75dcd
This commit is contained in:
Maged Elnaggar 2025-09-11 08:28:03 +00:00 committed by Rosen Zhelev
parent edcd2c2629
commit fb23a9b14e
12 changed files with 291 additions and 21 deletions

View file

@ -327,7 +327,8 @@ add_library(${PROJECT_NAME} SHARED
wsi/synchronization.cpp
wsi/wsi_factory.cpp
wsi/swapchain_image_creator.cpp
wsi/swapchain_image_create_extensions/image_compression_control.cpp)
wsi/swapchain_image_create_extensions/image_compression_control.cpp
wsi/swapchain_image_create_extensions/mutable_format_extension.cpp)
if (VULKAN_WSI_LAYER_EXPERIMENTAL)
target_sources(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/layer/present_timing_api.cpp)
target_sources(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/wsi/extensions/present_timing.cpp)

View file

@ -30,6 +30,7 @@ implements the following extensions:
* VK_EXT_swapchain_maintenance1
* VK_EXT_present_mode_fifo_latest_ready (For Headless and Wayland only)
* VK_KHR_present_id2
* VK_KHR_swapchain_mutable_format (For Headless and Wayland only)
## Building

View file

@ -73,6 +73,10 @@
{
"name": "VK_EXT_present_mode_fifo_latest_ready",
"spec_version": "1"
},
{
"name": "VK_KHR_swapchain_mutable_format",
"spec_version": "1"
}
],
"disable_environment": {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021-2022, 2024 Arm Limited.
* Copyright (c) 2021-2022, 2024-2025 Arm Limited.
*
* SPDX-License-Identifier: MIT
*
@ -77,26 +77,60 @@
namespace util
{
/**
* @brief Search a Vulkan pNext chain (read-only) for a specific extension struct.
*
* Use for input pNext chains (application driver), e.g. VkSwapchainCreateInfoKHR::pNext.
* Walks the chain via VkBaseInStructure and returns the first match.
*
* @tparam T Vulkan struct type to find (e.g., VkImageFormatListCreateInfo)
* @param sType VkStructureType corresponding to T
* @param pNext Head of the pNext chain (may be nullptr)
* @return const T* to the first matching node; nullptr if not found
*
* @note Unknown sTypes are skipped per Vulkan forward-compat rules.
*/
template <typename T>
const T *find_extension(VkStructureType sType, const void *pNext)
inline const T *find_extension(VkStructureType sType, const void *pNext) noexcept
{
auto entry = reinterpret_cast<const VkBaseOutStructure *>(pNext);
while (entry && entry->sType != sType)
auto p = reinterpret_cast<const VkBaseInStructure *>(pNext);
while (p)
{
entry = entry->pNext;
if (p->sType == sType)
{
return reinterpret_cast<const T *>(p);
}
p = p->pNext;
}
return reinterpret_cast<const T *>(entry);
return nullptr;
}
/**
* @brief Search a Vulkan pNext chain (writable) for a specific extension struct.
*
* Use for output pNext chains (driver application), e.g. VkPhysicalDeviceFeatures2::pNext.
* Walks the chain via VkBaseOutStructure and returns the first match.
*
* @tparam T Vulkan struct type to find (e.g., VkPhysicalDeviceVulkan11Features)
* @param sType VkStructureType corresponding to T
* @param pNext Head of the pNext chain (may be nullptr)
* @return T* to the first matching node; nullptr if not found
*
* @note Unknown sTypes are skipped per Vulkan forward-compat rules.
*/
template <typename T>
T *find_extension(VkStructureType sType, void *pNext)
inline T *find_extension(VkStructureType sType, void *pNext) noexcept
{
auto entry = reinterpret_cast<VkBaseOutStructure *>(pNext);
while (entry && entry->sType != sType)
auto p = reinterpret_cast<VkBaseOutStructure *>(pNext);
while (p)
{
entry = entry->pNext;
if (p->sType == sType)
{
return reinterpret_cast<T *>(p);
}
p = p->pNext;
}
return reinterpret_cast<T *>(entry);
return nullptr;
}
template <typename T>

View file

@ -142,6 +142,12 @@ VkResult swapchain::init_platform(VkDevice device, const VkSwapchainCreateInfoKH
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
if (is_mutable_format_enabled())
{
WSI_LOG_ERROR("Mutable format swapchain is not supported for display backend");
return VK_ERROR_INITIALIZATION_FAILED;
}
return VK_SUCCESS;
}

View file

@ -45,6 +45,7 @@
#include "util/helpers.hpp"
#include "swapchain_base.hpp"
#include "swapchain_image_create_extensions/mutable_format_extension.hpp"
#include "wsi_factory.hpp"
#include "extensions/present_timing.hpp"
@ -274,6 +275,14 @@ VkResult swapchain_base::init(VkDevice device, const VkSwapchainCreateInfoKHR *s
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
const bool want_mutable_format = (swapchain_create_info->flags & VK_SWAPCHAIN_CREATE_MUTABLE_FORMAT_BIT_KHR) != 0;
/* Enable mutable-format early so backends can react in init_platform. */
if (want_mutable_format)
{
enable_mutable_format();
}
/* We have allocated images, we can call the platform init function if something needs to be done. */
bool use_presentation_thread = true;
TRY_LOG_CALL(init_platform(device, swapchain_create_info, use_presentation_thread));
@ -283,11 +292,11 @@ VkResult swapchain_base::init(VkDevice device, const VkSwapchainCreateInfoKHR *s
TRY_LOG_CALL(init_page_flip_thread());
}
/* Initialize the image creator, and attach any required extensions */
TRY_LOG_CALL(image_creator_init(*swapchain_create_info));
m_image_create_info = m_image_creator.get_image_create_info();
VkResult result = m_free_image_semaphore.init(m_swapchain_images.size());
if (result != VK_SUCCESS)
if (VkResult result = m_free_image_semaphore.init(m_swapchain_images.size()); result != VK_SUCCESS)
{
assert(result == VK_ERROR_OUT_OF_HOST_MEMORY);
return result;
@ -566,7 +575,11 @@ VkResult swapchain_base::get_swapchain_images(uint32_t *swapchain_image_count, V
VkResult swapchain_base::create_aliased_image_handle(VkImage *image)
{
return m_device_data.disp.CreateImage(m_device, &m_image_create_info, get_allocation_callbacks(), image);
/* Build an alias VkImageCreateInfo compatible with swapchain memory binding. */
VkImageCreateInfo alias_info = m_image_create_info;
alias_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
return m_device_data.disp.CreateImage(m_device, &alias_info, get_allocation_callbacks(), image);
}
VkResult swapchain_base::get_swapchain_status()
@ -832,8 +845,26 @@ VkResult swapchain_base::image_creator_init(const VkSwapchainCreateInfoKHR &swap
{
m_image_creator.init(swapchain_create_info);
util::vector<util::unique_ptr<swapchain_image_create_info_extension>> extensions(m_allocator);
util::vector<util::unique_ptr<swapchain_image_create_info_extension>> extensions(
util::allocator(m_allocator, VK_SYSTEM_ALLOCATION_SCOPE_OBJECT));
TRY_LOG_CALL(get_required_image_creator_extensions(swapchain_create_info, &extensions));
if (is_mutable_format_enabled())
{
const auto *image_format_list = util::find_extension<VkImageFormatListCreateInfo>(
VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, swapchain_create_info.pNext);
auto mutable_format_uptr = swapchain_image_create_mutable_format::create_unique(image_format_list, m_allocator);
if (!mutable_format_uptr)
{
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
if (!extensions.try_push_back(std::move(mutable_format_uptr)))
{
return VK_ERROR_OUT_OF_HOST_MEMORY;
}
}
TRY_LOG_CALL(m_image_creator.add_extensions(extensions));
return VK_SUCCESS;

View file

@ -316,6 +316,24 @@ public:
bool add_swapchain_extension(util::unique_ptr<wsi_ext> extension);
/**
* @brief Enable mutable format swapchain support.
*/
void enable_mutable_format()
{
m_mutable_format_enabled = true;
}
/**
* @brief Check if mutable format swapchain support is enabled.
*
* @return true if mutable format swapchain support is enabled, false otherwise.
*/
bool is_mutable_format_enabled() const
{
return m_mutable_format_enabled;
}
protected:
layer::device_private_data &m_device_data;
@ -677,6 +695,11 @@ private:
*/
swapchain_image_creator m_image_creator;
/**
* @brief Flag indicating if mutable format swapchain support is enabled.
*/
bool m_mutable_format_enabled{ false };
/** @brief Signal semaphores for queue submit.
*/
std::array<VkSemaphore, SIGNAL_SEMAPHORE_MAX_NUM> signal_semaphores;

View file

@ -232,4 +232,5 @@ VkResult swapchain_image_create_external_memory::get_surface_compatible_formats(
return VK_SUCCESS;
}
};
} /* namespace wsi */

View file

@ -85,4 +85,4 @@ VkResult swapchain_image_create_compression_control::extend_image_create_info(Vk
return VK_SUCCESS;
}
};
} /* namespace wsi */

View file

@ -0,0 +1,75 @@
/*
* Copyright (c) 2025 Arm Limited.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file mutable_format_extension.cpp
*/
#include "mutable_format_extension.hpp"
namespace wsi
{
util::unique_ptr<swapchain_image_create_mutable_format> swapchain_image_create_mutable_format::create_unique(
const VkImageFormatListCreateInfo *image_format_list, util::allocator allocator)
{
/** Allocate the object directly using the allocator so we don't need to move. */
auto ptr = allocator.make_unique<swapchain_image_create_mutable_format>(allocator);
if (!ptr)
{
return nullptr;
}
ptr->m_format_list.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO;
ptr->m_format_list.pNext = nullptr;
ptr->m_format_list.viewFormatCount = 0;
ptr->m_format_list.pViewFormats = nullptr;
if (image_format_list && image_format_list->viewFormatCount > 0)
{
if (!ptr->m_view_formats.try_resize(image_format_list->viewFormatCount))
{
return nullptr;
}
for (uint32_t i = 0; i < image_format_list->viewFormatCount; ++i)
{
ptr->m_view_formats[i] = image_format_list->pViewFormats[i];
}
ptr->m_format_list.viewFormatCount = image_format_list->viewFormatCount;
ptr->m_format_list.pViewFormats = ptr->m_view_formats.data();
}
return ptr;
}
VkResult swapchain_image_create_mutable_format::extend_image_create_info(VkImageCreateInfo *image_create_info)
{
/* If this extension is present, mutable format support is requested. */
image_create_info->flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT | VK_IMAGE_CREATE_EXTENDED_USAGE_BIT_KHR;
m_format_list.pNext = image_create_info->pNext;
image_create_info->pNext = &m_format_list;
return VK_SUCCESS;
}
} /* namespace wsi */

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2025 Arm Limited.
*
* SPDX-License-Identifier: MIT
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* @file mutable_format_extension.hpp
*
* @brief Swapchain image create info extension for VK_KHR_swapchain_mutable_format handling.
*/
#pragma once
#include <vulkan/vulkan.h>
#include <util/custom_allocator.hpp>
#include "swapchain_image_create_info_extension.hpp"
namespace wsi
{
class swapchain_image_create_mutable_format : public swapchain_image_create_info_extension
{
public:
swapchain_image_create_mutable_format() = delete;
swapchain_image_create_mutable_format(const swapchain_image_create_mutable_format &) = delete;
swapchain_image_create_mutable_format &operator=(const swapchain_image_create_mutable_format &) = delete;
/**
* @brief Create and return a unique_ptr-managed instance using allocator.
*
* Allocates the object with the provided allocator and initializes its
* internal state from the optional VkImageFormatListCreateInfo.
*
* @return util::unique_ptr owning the instance, or nullptr on failure.
*/
static util::unique_ptr<swapchain_image_create_mutable_format> create_unique(
const VkImageFormatListCreateInfo *image_format_list, util::allocator allocator);
/**
* @brief Extend image_create_info pNext with extension specific data.
* A swapchain image create info extension will use this function to add its
* extension specific data to pNext of image_create_info.
* @param[in, out] image_create_info VkImageCreateInfo for creating swapchain images
* @return VkResult indicating success or failure
*/
VkResult extend_image_create_info(VkImageCreateInfo *image_create_info) override;
private:
friend class util::allocator;
/**
* @brief Construct the extension.
* @param allocator Allocator used to own copied view formats.
*/
swapchain_image_create_mutable_format(util::allocator &allocator)
: m_view_formats(allocator)
{
}
util::vector<VkFormat> m_view_formats;
VkImageFormatListCreateInfo m_format_list{};
};
} /* namespace wsi */

View file

@ -285,8 +285,10 @@ VkResult swapchain::get_surface_compatible_formats(const VkImageCreateInfo &info
VkResult result = VK_SUCCESS;
{
/* Build pNext chain for the format query. */
VkPhysicalDeviceExternalImageFormatInfoKHR external_info = {};
external_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO_KHR;
external_info.pNext = nullptr;
external_info.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_DMA_BUF_BIT_EXT;
VkPhysicalDeviceImageDrmFormatModifierInfoEXT drm_mod_info = {};
@ -306,8 +308,20 @@ VkResult swapchain::get_surface_compatible_formats(const VkImageCreateInfo &info
image_info.usage = info.usage;
image_info.flags = info.flags;
VkImageCompressionControlEXT compression_control = {};
/* Attach view format list (if any) from the swapchain image creator
* to the image_info chain, as required by the spec.
*/
const auto *image_format_list = util::find_extension<VkImageFormatListCreateInfo>(
VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO, m_image_create_info.pNext);
VkImageFormatListCreateInfo image_format_list_copy = {};
if (image_format_list)
{
image_format_list_copy = util::shallow_copy_extension(image_format_list);
image_format_list_copy.pNext = image_info.pNext;
image_info.pNext = &image_format_list_copy;
}
VkImageCompressionControlEXT compression_control = {};
if (m_device_data.is_swapchain_compression_control_enabled())
{
auto *ext = get_swapchain_extension<wsi_ext_image_compression_control>();
@ -544,7 +558,7 @@ VkResult swapchain::create_swapchain_image(VkImageCreateInfo image_create_info,
m_image_creation_parameters.m_allocated_format = allocated_format;
}
return m_device_data.disp.CreateImage(m_device, &m_image_create_info, get_allocation_callbacks(), &image.image);
return m_device_data.disp.CreateImage(m_device, &image_create_info, get_allocation_callbacks(), &image.image);
}
void swapchain::present_image(const pending_present_request &pending_present)