2019-05-24 15:57:50 +01:00
|
|
|
|
/*
|
2024-12-04 13:02:34 +00:00
|
|
|
|
* Copyright (c) 2016-2025 Arm Limited.
|
2019-05-24 15:57:50 +01:00
|
|
|
|
*
|
|
|
|
|
|
* 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.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <cassert>
|
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
|
#include <cstring>
|
2022-05-17 15:18:00 +01:00
|
|
|
|
#include <array>
|
2020-06-24 09:36:21 +01:00
|
|
|
|
|
2019-05-24 15:57:50 +01:00
|
|
|
|
#include <vulkan/vk_layer.h>
|
2022-03-18 12:05:46 +00:00
|
|
|
|
#include <vulkan/vulkan.h>
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2025-03-17 11:48:51 +00:00
|
|
|
|
#include "layer/calibrated_timestamps_api.hpp"
|
2025-05-09 16:04:39 +00:00
|
|
|
|
#include "present_wait_api.hpp"
|
2024-11-28 06:36:17 +00:00
|
|
|
|
#include "wsi_layer_experimental.hpp"
|
2019-05-24 15:57:50 +01:00
|
|
|
|
#include "private_data.hpp"
|
|
|
|
|
|
#include "surface_api.hpp"
|
|
|
|
|
|
#include "swapchain_api.hpp"
|
2024-01-23 18:19:27 +00:00
|
|
|
|
#include "swapchain_maintenance_api.hpp"
|
2020-06-24 09:36:21 +01:00
|
|
|
|
#include "util/extension_list.hpp"
|
|
|
|
|
|
#include "util/custom_allocator.hpp"
|
|
|
|
|
|
#include "wsi/wsi_factory.hpp"
|
2025-10-06 10:27:34 +00:00
|
|
|
|
#include "wsi/extensions/present_timing.hpp"
|
2021-05-27 13:53:07 +01:00
|
|
|
|
#include "util/log.hpp"
|
2021-11-03 11:25:48 +00:00
|
|
|
|
#include "util/macros.hpp"
|
2022-03-18 12:05:46 +00:00
|
|
|
|
#include "util/helpers.hpp"
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2021-09-30 17:24:42 +01:00
|
|
|
|
#define VK_LAYER_API_VERSION VK_MAKE_VERSION(1, 2, VK_HEADER_VERSION)
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
|
|
|
|
|
namespace layer
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
|
|
VKAPI_ATTR VkLayerInstanceCreateInfo *get_chain_info(const VkInstanceCreateInfo *pCreateInfo, VkLayerFunction func)
|
|
|
|
|
|
{
|
2021-10-07 14:48:44 +01:00
|
|
|
|
auto *chain_info = reinterpret_cast<const VkLayerInstanceCreateInfo *>(pCreateInfo->pNext);
|
2019-05-24 15:57:50 +01:00
|
|
|
|
while (chain_info &&
|
|
|
|
|
|
!(chain_info->sType == VK_STRUCTURE_TYPE_LOADER_INSTANCE_CREATE_INFO && chain_info->function == func))
|
|
|
|
|
|
{
|
2021-10-07 14:48:44 +01:00
|
|
|
|
chain_info = reinterpret_cast<const VkLayerInstanceCreateInfo *>(chain_info->pNext);
|
2019-05-24 15:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-07 14:48:44 +01:00
|
|
|
|
return const_cast<VkLayerInstanceCreateInfo *>(chain_info);
|
2019-05-24 15:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
VKAPI_ATTR VkLayerDeviceCreateInfo *get_chain_info(const VkDeviceCreateInfo *pCreateInfo, VkLayerFunction func)
|
|
|
|
|
|
{
|
2021-10-07 14:48:44 +01:00
|
|
|
|
auto *chain_info = reinterpret_cast<const VkLayerDeviceCreateInfo *>(pCreateInfo->pNext);
|
2019-05-24 15:57:50 +01:00
|
|
|
|
while (chain_info &&
|
|
|
|
|
|
!(chain_info->sType == VK_STRUCTURE_TYPE_LOADER_DEVICE_CREATE_INFO && chain_info->function == func))
|
|
|
|
|
|
{
|
2021-10-07 14:48:44 +01:00
|
|
|
|
chain_info = reinterpret_cast<const VkLayerDeviceCreateInfo *>(chain_info->pNext);
|
2019-05-24 15:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-10-07 14:48:44 +01:00
|
|
|
|
return const_cast<VkLayerDeviceCreateInfo *>(chain_info);
|
2019-05-24 15:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-05-27 13:53:07 +01:00
|
|
|
|
template <typename T>
|
|
|
|
|
|
static T get_instance_proc_addr(PFN_vkGetInstanceProcAddr fp_get_instance_proc_addr, const char *name,
|
|
|
|
|
|
VkInstance instance = VK_NULL_HANDLE)
|
|
|
|
|
|
{
|
|
|
|
|
|
T func = reinterpret_cast<T>(fp_get_instance_proc_addr(instance, name));
|
|
|
|
|
|
if (func == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
WSI_LOG_WARNING("Failed to get address of %s", name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return func;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-10 18:30:21 +01:00
|
|
|
|
template <typename T>
|
|
|
|
|
|
static T get_device_proc_addr(PFN_vkGetDeviceProcAddr fp_get_device_proc_addr, const char *name,
|
|
|
|
|
|
VkDevice device = VK_NULL_HANDLE)
|
|
|
|
|
|
{
|
|
|
|
|
|
T func = reinterpret_cast<T>(fp_get_device_proc_addr(device, name));
|
|
|
|
|
|
if (func == nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
WSI_LOG_WARNING("Failed to get address of %s", name);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return func;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-24 09:36:21 +01:00
|
|
|
|
/* This is where the layer is initialised and the instance dispatch table is constructed. */
|
2019-05-24 15:57:50 +01:00
|
|
|
|
VKAPI_ATTR VkResult create_instance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator,
|
|
|
|
|
|
VkInstance *pInstance)
|
|
|
|
|
|
{
|
2022-07-04 11:11:51 +01:00
|
|
|
|
VkLayerInstanceCreateInfo *layer_link_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO);
|
|
|
|
|
|
VkLayerInstanceCreateInfo *loader_data_callback = get_chain_info(pCreateInfo, VK_LOADER_DATA_CALLBACK);
|
|
|
|
|
|
if (nullptr == layer_link_info || nullptr == layer_link_info->u.pLayerInfo || nullptr == loader_data_callback)
|
2019-05-24 15:57:50 +01:00
|
|
|
|
{
|
2022-07-04 11:11:51 +01:00
|
|
|
|
WSI_LOG_ERROR("Unexpected NULL pointer in layer initialization structures during vkCreateInstance");
|
2019-05-24 15:57:50 +01:00
|
|
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-07-04 11:11:51 +01:00
|
|
|
|
PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = layer_link_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
|
|
|
|
|
|
PFN_vkSetInstanceLoaderData loader_callback = loader_data_callback->u.pfnSetInstanceLoaderData;
|
|
|
|
|
|
if (nullptr == fpGetInstanceProcAddr || nullptr == loader_callback)
|
|
|
|
|
|
{
|
|
|
|
|
|
WSI_LOG_ERROR("Unexpected NULL pointer for loader callback functions during vkCreateInstance");
|
|
|
|
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
|
|
|
|
}
|
2020-06-24 09:36:21 +01:00
|
|
|
|
|
2021-05-27 13:53:07 +01:00
|
|
|
|
auto fpCreateInstance = get_instance_proc_addr<PFN_vkCreateInstance>(fpGetInstanceProcAddr, "vkCreateInstance");
|
2019-05-24 15:57:50 +01:00
|
|
|
|
if (nullptr == fpCreateInstance)
|
|
|
|
|
|
{
|
2022-07-04 11:11:51 +01:00
|
|
|
|
WSI_LOG_ERROR("Unexpected NULL return value from pfnNextGetInstanceProcAddr");
|
2019-05-24 15:57:50 +01:00
|
|
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-17 15:18:00 +01:00
|
|
|
|
/* For instances handled by the layer, we need to enable extra extensions, therefore take a copy of pCreateInfo. */
|
|
|
|
|
|
VkInstanceCreateInfo modified_info = *pCreateInfo;
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2022-05-17 15:18:00 +01:00
|
|
|
|
/* Create a util::vector in case we need to modify the modified_info.ppEnabledExtensionNames list.
|
|
|
|
|
|
* This object and the extension_list object need to be in the global scope so they can be alive by the time
|
|
|
|
|
|
* vkCreateInstance is called.
|
2020-06-24 09:36:21 +01:00
|
|
|
|
*/
|
2022-11-24 11:24:34 +00:00
|
|
|
|
util::allocator allocator{ VK_SYSTEM_ALLOCATION_SCOPE_COMMAND, pAllocator };
|
|
|
|
|
|
util::vector<const char *> modified_enabled_extensions{ allocator };
|
|
|
|
|
|
util::extension_list extensions{ allocator };
|
2022-05-17 15:18:00 +01:00
|
|
|
|
|
|
|
|
|
|
/* Find all the platforms that the layer can handle based on pCreateInfo->ppEnabledExtensionNames. */
|
|
|
|
|
|
auto layer_platforms_to_enable = wsi::find_enabled_layer_platforms(pCreateInfo);
|
2025-03-12 10:25:28 +00:00
|
|
|
|
|
|
|
|
|
|
/* Create a list of extensions to enable, including the provided extensions and those required by the layer. */
|
|
|
|
|
|
TRY_LOG_CALL(extensions.add(pCreateInfo->ppEnabledExtensionNames, pCreateInfo->enabledExtensionCount));
|
|
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
uint32_t api_version =
|
|
|
|
|
|
pCreateInfo->pApplicationInfo != nullptr ? pCreateInfo->pApplicationInfo->apiVersion : VK_API_VERSION_1_3;
|
|
|
|
|
|
|
2022-05-17 15:18:00 +01:00
|
|
|
|
if (!layer_platforms_to_enable.empty())
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
2022-05-17 15:18:00 +01:00
|
|
|
|
if (!extensions.contains(VK_KHR_SURFACE_EXTENSION_NAME))
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
2022-05-17 15:18:00 +01:00
|
|
|
|
return VK_ERROR_EXTENSION_NOT_PRESENT;
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|
2025-06-27 15:21:58 +00:00
|
|
|
|
TRY_LOG_CALL(wsi::add_instance_extensions_required_by_layer(layer_platforms_to_enable, extensions, api_version));
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2025-03-12 10:25:28 +00:00
|
|
|
|
TRY_LOG_CALL(extensions.get_extension_strings(modified_enabled_extensions));
|
|
|
|
|
|
modified_info.ppEnabledExtensionNames = modified_enabled_extensions.data();
|
|
|
|
|
|
modified_info.enabledExtensionCount = modified_enabled_extensions.size();
|
|
|
|
|
|
|
2022-05-17 15:18:00 +01:00
|
|
|
|
/* Advance the link info for the next element on the chain. */
|
2022-07-04 11:11:51 +01:00
|
|
|
|
layer_link_info->u.pLayerInfo = layer_link_info->u.pLayerInfo->pNext;
|
2020-06-24 09:36:21 +01:00
|
|
|
|
|
|
|
|
|
|
/* Now call create instance on the chain further down the list.
|
|
|
|
|
|
* Note that we do not remove the extensions that the layer supports from modified_info.ppEnabledExtensionNames.
|
|
|
|
|
|
* Layers have to abide the rule that vkCreateInstance must not generate an error for unrecognized extension names.
|
|
|
|
|
|
* Also, the loader filters the extension list to ensure that ICDs do not see extensions that they do not support.
|
|
|
|
|
|
*/
|
2022-11-24 11:24:34 +00:00
|
|
|
|
TRY_LOG(fpCreateInstance(&modified_info, pAllocator, pInstance), "Failed to create the instance");
|
2024-04-10 18:30:21 +01:00
|
|
|
|
/* Note: If the call to vkCreateInstance succeeded, the loader will do the clean-up for us
|
|
|
|
|
|
* after this function returns with an error code. We can't call vkDestroyInstance
|
|
|
|
|
|
* ourselves as this will cause double-free from the loader attempting to clean up after us.
|
|
|
|
|
|
* Any failing calls below this point should NOT call vkDestroyInstance and rather just
|
|
|
|
|
|
* return the error code. */
|
2020-06-24 09:36:21 +01:00
|
|
|
|
|
2024-01-24 09:21:00 +01:00
|
|
|
|
/* Following the spec: use the callbacks provided to vkCreateInstance() if not nullptr,
|
|
|
|
|
|
* otherwise use the default callbacks.
|
|
|
|
|
|
*/
|
|
|
|
|
|
util::allocator instance_allocator{ VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE, pAllocator };
|
2024-04-10 18:30:21 +01:00
|
|
|
|
std::optional<instance_dispatch_table> table = instance_dispatch_table::create(instance_allocator);
|
|
|
|
|
|
if (!table.has_value())
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
2024-04-10 18:30:21 +01:00
|
|
|
|
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|
2024-04-10 18:30:21 +01:00
|
|
|
|
|
2025-03-27 10:51:54 +00:00
|
|
|
|
TRY_LOG_CALL(table->populate(*pInstance, fpGetInstanceProcAddr, api_version));
|
|
|
|
|
|
table->set_user_enabled_extensions(pCreateInfo->ppEnabledExtensionNames, pCreateInfo->enabledExtensionCount);
|
|
|
|
|
|
|
2024-04-10 18:30:21 +01:00
|
|
|
|
TRY_LOG_CALL(instance_private_data::associate(*pInstance, std::move(*table), loader_callback,
|
|
|
|
|
|
layer_platforms_to_enable, api_version, instance_allocator));
|
2021-05-27 13:53:07 +01:00
|
|
|
|
|
2021-11-08 13:32:12 +00:00
|
|
|
|
/*
|
|
|
|
|
|
* Store the enabled instance extensions in order to return nullptr in
|
|
|
|
|
|
* vkGetInstanceProcAddr for functions of disabled extensions.
|
|
|
|
|
|
*/
|
2024-04-10 18:30:21 +01:00
|
|
|
|
VkResult result =
|
2021-11-08 13:32:12 +00:00
|
|
|
|
instance_private_data::get(*pInstance)
|
2025-06-27 15:21:58 +00:00
|
|
|
|
.set_instance_enabled_extensions(pCreateInfo->ppEnabledExtensionNames, pCreateInfo->enabledExtensionCount);
|
2021-11-08 13:32:12 +00:00
|
|
|
|
if (result != VK_SUCCESS)
|
|
|
|
|
|
{
|
2022-03-18 12:05:46 +00:00
|
|
|
|
instance_private_data::disassociate(*pInstance);
|
2021-11-08 13:32:12 +00:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return VK_SUCCESS;
|
2019-05-24 15:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
VKAPI_ATTR VkResult create_device(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo,
|
|
|
|
|
|
const VkAllocationCallbacks *pAllocator, VkDevice *pDevice)
|
|
|
|
|
|
{
|
2022-07-04 11:11:51 +01:00
|
|
|
|
VkLayerDeviceCreateInfo *layer_link_info = get_chain_info(pCreateInfo, VK_LAYER_LINK_INFO);
|
|
|
|
|
|
VkLayerDeviceCreateInfo *loader_data_callback = get_chain_info(pCreateInfo, VK_LOADER_DATA_CALLBACK);
|
|
|
|
|
|
if (nullptr == layer_link_info || nullptr == layer_link_info->u.pLayerInfo || nullptr == loader_data_callback)
|
2019-05-24 15:57:50 +01:00
|
|
|
|
{
|
2022-07-04 11:11:51 +01:00
|
|
|
|
WSI_LOG_ERROR("Unexpected NULL pointer in layer initialization structures during vkCreateDevice");
|
2019-05-24 15:57:50 +01:00
|
|
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Retrieve the vkGetDeviceProcAddr and the vkCreateDevice function pointers for the next layer in the chain. */
|
2022-07-04 11:11:51 +01:00
|
|
|
|
PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = layer_link_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
|
|
|
|
|
|
PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = layer_link_info->u.pLayerInfo->pfnNextGetDeviceProcAddr;
|
|
|
|
|
|
PFN_vkSetDeviceLoaderData loader_callback = loader_data_callback->u.pfnSetDeviceLoaderData;
|
|
|
|
|
|
if (nullptr == fpGetInstanceProcAddr || nullptr == fpGetDeviceProcAddr || nullptr == loader_callback)
|
|
|
|
|
|
{
|
|
|
|
|
|
WSI_LOG_ERROR("Unexpected NULL pointer for loader callback functions during vkCreateDevice");
|
|
|
|
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
|
|
|
|
}
|
2021-05-27 13:53:07 +01:00
|
|
|
|
|
|
|
|
|
|
auto fpCreateDevice = get_instance_proc_addr<PFN_vkCreateDevice>(fpGetInstanceProcAddr, "vkCreateDevice");
|
2019-05-24 15:57:50 +01:00
|
|
|
|
if (nullptr == fpCreateDevice)
|
|
|
|
|
|
{
|
2022-07-04 11:11:51 +01:00
|
|
|
|
WSI_LOG_ERROR("Unexpected NULL return value from pfnNextGetInstanceProcAddr");
|
2019-05-24 15:57:50 +01:00
|
|
|
|
return VK_ERROR_INITIALIZATION_FAILED;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Advance the link info for the next element on the chain. */
|
2022-07-04 11:11:51 +01:00
|
|
|
|
layer_link_info->u.pLayerInfo = layer_link_info->u.pLayerInfo->pNext;
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2022-05-17 15:18:00 +01:00
|
|
|
|
/* Enable extra extensions if needed by the layer, similarly to what done in vkCreateInstance. */
|
|
|
|
|
|
VkDeviceCreateInfo modified_info = *pCreateInfo;
|
2021-05-27 13:53:07 +01:00
|
|
|
|
|
2022-05-17 15:18:00 +01:00
|
|
|
|
auto &inst_data = instance_private_data::get(physicalDevice);
|
2022-11-24 11:24:34 +00:00
|
|
|
|
util::allocator allocator{ inst_data.get_allocator(), VK_SYSTEM_ALLOCATION_SCOPE_COMMAND, pAllocator };
|
|
|
|
|
|
util::vector<const char *> modified_enabled_extensions{ allocator };
|
2024-10-01 15:18:22 +00:00
|
|
|
|
util::extension_list enabled_extensions{ allocator };
|
2024-11-13 17:09:48 +00:00
|
|
|
|
|
2025-10-17 15:02:40 +00:00
|
|
|
|
#if VULKAN_WSI_LAYER_EXPERIMENTAL
|
|
|
|
|
|
VkPhysicalDeviceMaintenance9FeaturesKHR maintenance9_features = {};
|
|
|
|
|
|
#endif /* VULKAN_WSI_LAYER_EXPERIMENTAL */
|
2022-11-24 11:24:34 +00:00
|
|
|
|
const util::wsi_platform_set &enabled_platforms = inst_data.get_enabled_platforms();
|
2022-05-17 15:18:00 +01:00
|
|
|
|
if (!enabled_platforms.empty())
|
2019-05-24 15:57:50 +01:00
|
|
|
|
{
|
2024-11-13 17:09:48 +00:00
|
|
|
|
TRY_LOG_CALL(enabled_extensions.add(pCreateInfo->ppEnabledExtensionNames, pCreateInfo->enabledExtensionCount));
|
2025-06-27 15:21:58 +00:00
|
|
|
|
TRY_LOG_CALL(wsi::add_device_extensions_required_by_layer(physicalDevice, enabled_platforms, enabled_extensions,
|
|
|
|
|
|
inst_data.api_version));
|
2025-10-17 15:02:40 +00:00
|
|
|
|
#if VULKAN_WSI_LAYER_EXPERIMENTAL
|
|
|
|
|
|
auto present_timing_supported = wsi::present_timing_dependencies_supported(physicalDevice);
|
|
|
|
|
|
if (std::holds_alternative<VkResult>(present_timing_supported))
|
|
|
|
|
|
{
|
|
|
|
|
|
return std::get<VkResult>(present_timing_supported);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (std::get<bool>(present_timing_supported))
|
|
|
|
|
|
{
|
|
|
|
|
|
TRY_LOG_CALL(enabled_extensions.add(VK_KHR_MAINTENANCE_9_EXTENSION_NAME));
|
|
|
|
|
|
|
|
|
|
|
|
const auto *device_maintenance9_features = util::find_extension<VkPhysicalDeviceMaintenance9FeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_9_FEATURES_KHR, pCreateInfo->pNext);
|
|
|
|
|
|
if (device_maintenance9_features)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (device_maintenance9_features->maintenance9 == VK_FALSE)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* We are taking the same risk with the frame boundary features below. */
|
|
|
|
|
|
auto *maintenance9_features_non_const =
|
|
|
|
|
|
const_cast<VkPhysicalDeviceMaintenance9FeaturesKHR *>(device_maintenance9_features);
|
|
|
|
|
|
maintenance9_features_non_const->maintenance9 = VK_TRUE;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
maintenance9_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_9_FEATURES_KHR;
|
|
|
|
|
|
maintenance9_features.pNext = const_cast<void *>(modified_info.pNext);
|
|
|
|
|
|
maintenance9_features.maintenance9 = VK_TRUE;
|
|
|
|
|
|
|
|
|
|
|
|
modified_info.pNext = &maintenance9_features;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif /* VULKAN_WSI_LAYER_EXPERIMENTAL */
|
|
|
|
|
|
|
2022-11-24 11:24:34 +00:00
|
|
|
|
TRY_LOG_CALL(enabled_extensions.get_extension_strings(modified_enabled_extensions));
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2022-05-17 15:18:00 +01:00
|
|
|
|
modified_info.ppEnabledExtensionNames = modified_enabled_extensions.data();
|
|
|
|
|
|
modified_info.enabledExtensionCount = modified_enabled_extensions.size();
|
2019-05-24 15:57:50 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-10-01 15:18:22 +00:00
|
|
|
|
bool should_layer_handle_frame_boundary_events = false;
|
|
|
|
|
|
VkPhysicalDeviceFrameBoundaryFeaturesEXT frame_boundary;
|
|
|
|
|
|
|
2024-11-13 17:09:48 +00:00
|
|
|
|
if (ENABLE_INSTRUMENTATION)
|
2024-10-01 15:18:22 +00:00
|
|
|
|
{
|
|
|
|
|
|
if (enabled_extensions.contains(VK_EXT_FRAME_BOUNDARY_EXTENSION_NAME))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (inst_data.has_frame_boundary_support(physicalDevice))
|
|
|
|
|
|
{
|
2024-11-13 17:09:48 +00:00
|
|
|
|
const auto *application_frame_boundary_features =
|
|
|
|
|
|
util::find_extension<VkPhysicalDeviceFrameBoundaryFeaturesEXT>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAME_BOUNDARY_FEATURES_EXT, pCreateInfo->pNext);
|
|
|
|
|
|
|
|
|
|
|
|
if (application_frame_boundary_features)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (application_frame_boundary_features->frameBoundary == VK_FALSE)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* The original features cannot be modified as they are marked as constant.
|
|
|
|
|
|
* Additionally, it is not possible to unlink this extension from the pNext
|
|
|
|
|
|
* chain as all other passed structures are also marked as const. We'll take
|
|
|
|
|
|
* the risk to modify the original structure as there is no trivial way to
|
|
|
|
|
|
* re-enable frame boundary feature or swap out the original structure. */
|
|
|
|
|
|
auto *frame_boundary_features_non_const =
|
|
|
|
|
|
const_cast<VkPhysicalDeviceFrameBoundaryFeaturesEXT *>(application_frame_boundary_features);
|
|
|
|
|
|
frame_boundary_features_non_const->frameBoundary = VK_TRUE;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
frame_boundary.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAME_BOUNDARY_FEATURES_EXT;
|
|
|
|
|
|
frame_boundary.pNext = const_cast<void *>(modified_info.pNext);
|
|
|
|
|
|
frame_boundary.frameBoundary = VK_TRUE;
|
|
|
|
|
|
|
|
|
|
|
|
modified_info.pNext = &frame_boundary;
|
|
|
|
|
|
}
|
2024-10-01 15:18:22 +00:00
|
|
|
|
|
|
|
|
|
|
should_layer_handle_frame_boundary_events = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-24 09:36:21 +01:00
|
|
|
|
/* Now call create device on the chain further down the list. */
|
2022-11-24 11:24:34 +00:00
|
|
|
|
TRY_LOG(fpCreateDevice(physicalDevice, &modified_info, pAllocator, pDevice), "Failed to create the device");
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2024-04-10 18:30:21 +01:00
|
|
|
|
auto fn_destroy_device = get_device_proc_addr<PFN_vkDestroyDevice>(fpGetDeviceProcAddr, "vkDestroyDevice", *pDevice);
|
|
|
|
|
|
/* This should never be nullptr */
|
|
|
|
|
|
assert(fn_destroy_device != nullptr);
|
|
|
|
|
|
|
2024-01-24 09:21:00 +01:00
|
|
|
|
/* Following the spec: use the callbacks provided to vkCreateDevice() if not nullptr, otherwise use the callbacks
|
|
|
|
|
|
* provided to the instance (if no allocator callbacks was provided to the instance, it will use default ones).
|
|
|
|
|
|
*/
|
|
|
|
|
|
util::allocator device_allocator{ inst_data.get_allocator(), VK_SYSTEM_ALLOCATION_SCOPE_DEVICE, pAllocator };
|
2024-04-10 18:30:21 +01:00
|
|
|
|
std::optional<device_dispatch_table> table = device_dispatch_table::create(device_allocator);
|
|
|
|
|
|
if (!table.has_value())
|
2019-05-24 15:57:50 +01:00
|
|
|
|
{
|
2024-04-10 18:30:21 +01:00
|
|
|
|
fn_destroy_device(*pDevice, pAllocator);
|
|
|
|
|
|
return VK_ERROR_OUT_OF_HOST_MEMORY;
|
|
|
|
|
|
}
|
2024-01-24 09:21:00 +01:00
|
|
|
|
|
2025-03-27 10:51:54 +00:00
|
|
|
|
VkResult result = table->populate(*pDevice, fpGetDeviceProcAddr, inst_data.api_version);
|
2024-04-10 18:30:21 +01:00
|
|
|
|
if (result != VK_SUCCESS)
|
|
|
|
|
|
{
|
|
|
|
|
|
fn_destroy_device(*pDevice, pAllocator);
|
2020-06-24 09:36:21 +01:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2024-04-10 18:30:21 +01:00
|
|
|
|
table->set_user_enabled_extensions(pCreateInfo->ppEnabledExtensionNames, pCreateInfo->enabledExtensionCount);
|
2024-01-24 09:21:00 +01:00
|
|
|
|
|
2024-04-10 18:30:21 +01:00
|
|
|
|
result = device_private_data::associate(*pDevice, inst_data, physicalDevice, std::move(*table), loader_callback,
|
|
|
|
|
|
device_allocator);
|
2021-11-08 13:32:12 +00:00
|
|
|
|
if (result != VK_SUCCESS)
|
|
|
|
|
|
{
|
2024-04-10 18:30:21 +01:00
|
|
|
|
fn_destroy_device(*pDevice, pAllocator);
|
2021-11-08 13:32:12 +00:00
|
|
|
|
return result;
|
|
|
|
|
|
}
|
2021-05-27 13:53:07 +01:00
|
|
|
|
|
2021-11-08 13:32:12 +00:00
|
|
|
|
/*
|
|
|
|
|
|
* Store the enabled device extensions in order to return nullptr in
|
|
|
|
|
|
* vkGetDeviceProcAddr for functions of disabled extensions.
|
|
|
|
|
|
*/
|
2024-10-01 15:18:22 +00:00
|
|
|
|
auto &device_data = layer::device_private_data::get(*pDevice);
|
|
|
|
|
|
device_data.set_layer_frame_boundary_handling_enabled(should_layer_handle_frame_boundary_events);
|
|
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
result = device_data.set_device_enabled_extensions(pCreateInfo->ppEnabledExtensionNames,
|
|
|
|
|
|
pCreateInfo->enabledExtensionCount);
|
2021-05-27 13:53:07 +01:00
|
|
|
|
if (result != VK_SUCCESS)
|
|
|
|
|
|
{
|
2022-03-18 12:05:46 +00:00
|
|
|
|
layer::device_private_data::disassociate(*pDevice);
|
2024-04-10 18:30:21 +01:00
|
|
|
|
fn_destroy_device(*pDevice, pAllocator);
|
2021-11-08 13:32:12 +00:00
|
|
|
|
return result;
|
2021-05-27 13:53:07 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-18 12:05:46 +00:00
|
|
|
|
const auto *swapchain_compression_feature =
|
2022-11-24 11:24:34 +00:00
|
|
|
|
util::find_extension<VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT>(
|
2022-03-18 12:05:46 +00:00
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT, pCreateInfo->pNext);
|
|
|
|
|
|
if (swapchain_compression_feature != nullptr)
|
|
|
|
|
|
{
|
2024-10-01 15:18:22 +00:00
|
|
|
|
device_data.set_swapchain_compression_control_enabled(
|
2022-03-18 12:05:46 +00:00
|
|
|
|
swapchain_compression_feature->imageCompressionControlSwapchain);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-20 11:46:07 +00:00
|
|
|
|
const auto present_id_features = util::find_extension<VkPhysicalDevicePresentIdFeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, pCreateInfo->pNext);
|
|
|
|
|
|
if (present_id_features != nullptr)
|
|
|
|
|
|
{
|
2024-10-01 15:18:22 +00:00
|
|
|
|
device_data.set_present_id_feature_enabled(present_id_features->presentId);
|
2024-08-20 11:46:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-13 08:41:21 +00:00
|
|
|
|
const auto present_id2_features = util::find_extension<VkPhysicalDevicePresentId2FeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_2_FEATURES_KHR, pCreateInfo->pNext);
|
|
|
|
|
|
if (present_id2_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
device_data.set_present_id2_feature_enabled(present_id2_features->presentId2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-04 08:39:03 +00:00
|
|
|
|
const auto present_mode_fifo_latest_ready_features =
|
|
|
|
|
|
util::find_extension<VkPhysicalDevicePresentModeFifoLatestReadyFeaturesEXT>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_EXT, pCreateInfo->pNext);
|
|
|
|
|
|
if (present_mode_fifo_latest_ready_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
device_data.set_present_mode_fifo_latest_ready_enabled(
|
|
|
|
|
|
present_mode_fifo_latest_ready_features->presentModeFifoLatestReady);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-16 11:22:29 +00:00
|
|
|
|
auto *physical_device_swapchain_maintenance1_features =
|
|
|
|
|
|
util::find_extension<VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, pCreateInfo->pNext);
|
|
|
|
|
|
if (physical_device_swapchain_maintenance1_features != nullptr)
|
|
|
|
|
|
{
|
2024-10-01 15:18:22 +00:00
|
|
|
|
device_data.set_swapchain_maintenance1_enabled(
|
2024-01-16 11:22:29 +00:00
|
|
|
|
physical_device_swapchain_maintenance1_features->swapchainMaintenance1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-09 16:04:39 +00:00
|
|
|
|
auto *present_wait_features = util::find_extension<VkPhysicalDevicePresentWaitFeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR, pCreateInfo->pNext);
|
|
|
|
|
|
if (present_wait_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
device_data.set_present_wait_enabled(present_wait_features->presentWait);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-26 12:42:14 +00:00
|
|
|
|
auto *present_wait2_features = util::find_extension<VkPhysicalDevicePresentWait2FeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_2_FEATURES_KHR, pCreateInfo->pNext);
|
|
|
|
|
|
if (present_wait2_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
device_data.set_present_wait2_enabled(present_wait2_features->presentWait2);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-08 13:32:12 +00:00
|
|
|
|
return VK_SUCCESS;
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2020-06-24 09:36:21 +01:00
|
|
|
|
} /* namespace layer */
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(PFN_vkVoidFunction)
|
|
|
|
|
|
wsi_layer_vkGetDeviceProcAddr(VkDevice device, const char *funcName) VWL_API_POST;
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(PFN_vkVoidFunction)
|
|
|
|
|
|
wsi_layer_vkGetInstanceProcAddr(VkInstance instance, const char *funcName) VWL_API_POST;
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2020-06-24 09:36:21 +01:00
|
|
|
|
/* Clean up the dispatch table for this instance. */
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(void)
|
|
|
|
|
|
wsi_layer_vkDestroyInstance(VkInstance instance, const VkAllocationCallbacks *pAllocator) VWL_API_POST
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
2021-05-27 13:53:07 +01:00
|
|
|
|
if (instance == VK_NULL_HANDLE)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-24 09:21:00 +01:00
|
|
|
|
auto fn_destroy_instance =
|
|
|
|
|
|
layer::instance_private_data::get(instance).disp.get_fn<PFN_vkDestroyInstance>("vkDestroyInstance");
|
2021-05-27 13:53:07 +01:00
|
|
|
|
|
|
|
|
|
|
/* Call disassociate() before doing vkDestroyInstance as an instance may be created by a different thread
|
|
|
|
|
|
* just after we call vkDestroyInstance() and it could get the same address if we are unlucky.
|
|
|
|
|
|
*/
|
|
|
|
|
|
layer::instance_private_data::disassociate(instance);
|
|
|
|
|
|
|
2024-01-24 09:21:00 +01:00
|
|
|
|
assert(fn_destroy_instance.has_value());
|
|
|
|
|
|
(*fn_destroy_instance)(instance, pAllocator);
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(void)
|
|
|
|
|
|
wsi_layer_vkDestroyDevice(VkDevice device, const VkAllocationCallbacks *pAllocator) VWL_API_POST
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
2021-05-27 13:53:07 +01:00
|
|
|
|
if (device == VK_NULL_HANDLE)
|
|
|
|
|
|
{
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-24 09:21:00 +01:00
|
|
|
|
auto fn_destroy_device = layer::device_private_data::get(device).disp.get_fn<PFN_vkDestroyDevice>("vkDestroyDevice");
|
2021-05-27 13:53:07 +01:00
|
|
|
|
|
|
|
|
|
|
/* Call disassociate() before doing vkDestroyDevice as a device may be created by a different thread
|
|
|
|
|
|
* just after we call vkDestroyDevice().
|
|
|
|
|
|
*/
|
|
|
|
|
|
layer::device_private_data::disassociate(device);
|
|
|
|
|
|
|
2024-01-24 09:21:00 +01:00
|
|
|
|
assert(fn_destroy_device.has_value());
|
|
|
|
|
|
(*fn_destroy_device)(device, pAllocator);
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(VkResult)
|
|
|
|
|
|
wsi_layer_vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator,
|
|
|
|
|
|
VkInstance *pInstance) VWL_API_POST
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
|
|
|
|
|
return layer::create_instance(pCreateInfo, pAllocator, pInstance);
|
|
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(VkResult)
|
|
|
|
|
|
wsi_layer_vkCreateDevice(VkPhysicalDevice physicalDevice, const VkDeviceCreateInfo *pCreateInfo,
|
|
|
|
|
|
const VkAllocationCallbacks *pAllocator, VkDevice *pDevice) VWL_API_POST
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
|
|
|
|
|
return layer::create_device(physicalDevice, pCreateInfo, pAllocator, pDevice);
|
|
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(VkResult)
|
2023-04-17 10:59:19 +00:00
|
|
|
|
VWL_VKAPI_EXPORT wsi_layer_vkNegotiateLoaderLayerInterfaceVersion(VkNegotiateLayerInterface *pVersionStruct)
|
|
|
|
|
|
VWL_API_POST
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
|
|
|
|
|
assert(pVersionStruct);
|
|
|
|
|
|
assert(pVersionStruct->sType == LAYER_NEGOTIATE_INTERFACE_STRUCT);
|
|
|
|
|
|
|
|
|
|
|
|
/* 2 is the minimum interface version which would utilize this function. */
|
|
|
|
|
|
assert(pVersionStruct->loaderLayerInterfaceVersion >= 2);
|
|
|
|
|
|
|
|
|
|
|
|
/* Set our requested interface version. Set to 2 for now to separate us from newer versions. */
|
|
|
|
|
|
pVersionStruct->loaderLayerInterfaceVersion = 2;
|
|
|
|
|
|
|
|
|
|
|
|
/* Fill in struct values. */
|
|
|
|
|
|
pVersionStruct->pfnGetInstanceProcAddr = &wsi_layer_vkGetInstanceProcAddr;
|
|
|
|
|
|
pVersionStruct->pfnGetDeviceProcAddr = &wsi_layer_vkGetDeviceProcAddr;
|
|
|
|
|
|
pVersionStruct->pfnGetPhysicalDeviceProcAddr = nullptr;
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2020-06-24 09:36:21 +01:00
|
|
|
|
return VK_SUCCESS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-18 12:05:46 +00:00
|
|
|
|
VWL_VKAPI_CALL(void)
|
2025-06-27 15:21:58 +00:00
|
|
|
|
wsi_layer_vkGetPhysicalDeviceFeatures2(VkPhysicalDevice physical_device,
|
|
|
|
|
|
VkPhysicalDeviceFeatures2 *pFeatures) VWL_API_POST
|
2022-03-18 12:05:46 +00:00
|
|
|
|
{
|
2025-05-15 12:36:48 +00:00
|
|
|
|
auto &instance = layer::instance_private_data::get(physical_device);
|
2025-04-01 09:29:51 +00:00
|
|
|
|
|
|
|
|
|
|
auto *swapchain_maintenance1_features = util::find_extension<VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT, pFeatures->pNext);
|
|
|
|
|
|
if (swapchain_maintenance1_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
swapchain_maintenance1_features->swapchainMaintenance1 = VK_FALSE;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
auto *present_wait_features = util::find_extension<VkPhysicalDevicePresentWaitFeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_FEATURES_KHR, pFeatures->pNext);
|
|
|
|
|
|
if (present_wait_features != nullptr)
|
2024-12-04 13:02:34 +00:00
|
|
|
|
{
|
2025-04-01 09:29:51 +00:00
|
|
|
|
present_wait_features->presentWait = VK_FALSE;
|
2024-12-04 13:02:34 +00:00
|
|
|
|
}
|
2025-04-01 09:29:51 +00:00
|
|
|
|
|
2025-09-17 09:20:46 +00:00
|
|
|
|
instance.disp.GetPhysicalDeviceFeatures2KHR(physical_device, pFeatures);
|
|
|
|
|
|
|
2025-08-26 12:42:14 +00:00
|
|
|
|
auto *present_wait2_features = util::find_extension<VkPhysicalDevicePresentWait2FeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_WAIT_2_FEATURES_KHR, pFeatures->pNext);
|
|
|
|
|
|
if (present_wait2_features != nullptr)
|
|
|
|
|
|
{
|
2025-12-16 14:26:53 +00:00
|
|
|
|
present_wait2_features->presentWait2 = VK_FALSE;
|
2025-08-26 12:42:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2022-03-18 12:05:46 +00:00
|
|
|
|
auto *image_compression_control_swapchain_features =
|
|
|
|
|
|
util::find_extension<VkPhysicalDeviceImageCompressionControlSwapchainFeaturesEXT>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_FEATURES_EXT, pFeatures->pNext);
|
|
|
|
|
|
if (image_compression_control_swapchain_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
image_compression_control_swapchain_features->imageCompressionControlSwapchain =
|
2025-05-15 12:36:48 +00:00
|
|
|
|
instance.has_image_compression_support(physical_device);
|
2022-03-18 12:05:46 +00:00
|
|
|
|
}
|
2024-01-16 11:22:29 +00:00
|
|
|
|
|
2024-08-20 11:46:07 +00:00
|
|
|
|
auto *present_id_features = util::find_extension<VkPhysicalDevicePresentIdFeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_FEATURES_KHR, pFeatures->pNext);
|
|
|
|
|
|
if (present_id_features != nullptr)
|
|
|
|
|
|
{
|
2025-04-01 09:29:51 +00:00
|
|
|
|
present_id_features->presentId = VK_TRUE;
|
2024-08-20 11:46:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-13 08:41:21 +00:00
|
|
|
|
auto *present_id2_features = util::find_extension<VkPhysicalDevicePresentId2FeaturesKHR>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_ID_2_FEATURES_KHR, pFeatures->pNext);
|
|
|
|
|
|
if (present_id2_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
present_id2_features->presentId2 = VK_TRUE;
|
|
|
|
|
|
}
|
2025-05-15 12:36:48 +00:00
|
|
|
|
wsi::set_swapchain_maintenance1_state(physical_device, swapchain_maintenance1_features);
|
2024-08-21 12:59:49 +01:00
|
|
|
|
|
2025-04-01 09:29:51 +00:00
|
|
|
|
if (present_wait_features != nullptr)
|
|
|
|
|
|
{
|
2025-12-16 14:26:53 +00:00
|
|
|
|
/* If there is a surface extension in use that is unsupported by the layer, defer to the ICD */
|
2025-04-01 09:29:51 +00:00
|
|
|
|
if (!instance.is_unsupported_surface_extension_enabled())
|
|
|
|
|
|
{
|
|
|
|
|
|
present_wait_features->presentWait = VK_TRUE;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-16 14:26:53 +00:00
|
|
|
|
if (present_wait2_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
/* If there is a surface extension in use that is unsupported by the layer, defer to the ICD */
|
|
|
|
|
|
if (!instance.is_unsupported_surface_extension_enabled())
|
|
|
|
|
|
{
|
|
|
|
|
|
present_wait2_features->presentWait2 = VK_TRUE;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-06 16:41:12 +00:00
|
|
|
|
#if VULKAN_WSI_LAYER_EXPERIMENTAL
|
2024-08-21 12:59:49 +01:00
|
|
|
|
auto *present_timing_features = util::find_extension<VkPhysicalDevicePresentTimingFeaturesEXT>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_TIMING_FEATURES_EXT, pFeatures->pNext);
|
|
|
|
|
|
if (present_timing_features != nullptr)
|
|
|
|
|
|
{
|
2025-10-06 10:27:34 +00:00
|
|
|
|
bool support;
|
|
|
|
|
|
if (wsi::wsi_ext_present_timing::physical_device_has_supported_queue_family(physical_device, support) !=
|
|
|
|
|
|
VK_SUCCESS)
|
|
|
|
|
|
{
|
|
|
|
|
|
WSI_LOG_ERROR("Failed to query physical device for present timing support");
|
|
|
|
|
|
support = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
present_timing_features->presentTiming = support ? VK_TRUE : VK_FALSE;
|
2024-08-21 12:59:49 +01:00
|
|
|
|
present_timing_features->presentAtAbsoluteTime = VK_TRUE;
|
|
|
|
|
|
present_timing_features->presentAtRelativeTime = VK_TRUE;
|
|
|
|
|
|
}
|
2025-06-08 15:32:22 +03:00
|
|
|
|
#endif
|
2025-06-04 08:39:03 +00:00
|
|
|
|
|
|
|
|
|
|
auto *present_mode_fifo_latest_ready_features =
|
|
|
|
|
|
util::find_extension<VkPhysicalDevicePresentModeFifoLatestReadyFeaturesEXT>(
|
|
|
|
|
|
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_EXT, pFeatures->pNext);
|
|
|
|
|
|
if (present_mode_fifo_latest_ready_features != nullptr)
|
|
|
|
|
|
{
|
|
|
|
|
|
present_mode_fifo_latest_ready_features->presentModeFifoLatestReady = VK_TRUE;
|
|
|
|
|
|
}
|
2022-03-18 12:05:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2020-06-24 09:36:21 +01:00
|
|
|
|
#define GET_PROC_ADDR(func) \
|
2019-05-24 15:57:50 +01:00
|
|
|
|
if (!strcmp(funcName, #func)) \
|
|
|
|
|
|
return (PFN_vkVoidFunction)&wsi_layer_##func;
|
|
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief Trampoline for **vkGetDeviceProcAddr** inside the WSI layer.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Workflow:
|
|
|
|
|
|
* 1. Retrieve the device’s private state (enabled extensions and downstream dispatch table).
|
|
|
|
|
|
* 2. If the function is one that this layer intercepts, return the layer’s handler.
|
|
|
|
|
|
* 3. Otherwise, forward to the downstream dispatch table (next layer or ICD), or return nullptr if unavailable.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This layer never exposes entrypoints for disabled extensions, preserving the Vulkan dispatch-chain contract.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param device The VkDevice being queried.
|
|
|
|
|
|
* @param funcName Name of the device-level command to resolve.
|
|
|
|
|
|
* @return Pointer to the layer’s implementation, the next-layer/ICD function, or nullptr.
|
|
|
|
|
|
*/
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(PFN_vkVoidFunction)
|
|
|
|
|
|
wsi_layer_vkGetDeviceProcAddr(VkDevice device, const char *funcName) VWL_API_POST
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
2025-06-27 15:21:58 +00:00
|
|
|
|
auto &device_data = layer::device_private_data::get(device);
|
|
|
|
|
|
const uint64_t api_version = device_data.instance_data.api_version;
|
|
|
|
|
|
const bool core_1_1 = api_version >= VK_API_VERSION_1_1;
|
|
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_SWAPCHAIN_EXTENSION_NAME))
|
2021-11-08 13:32:12 +00:00
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkCreateSwapchainKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkDestroySwapchainKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetSwapchainImagesKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkAcquireNextImageKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkQueuePresentKHR);
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_DEVICE_GROUP_EXTENSION_NAME) || core_1_1)
|
|
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkAcquireNextImage2KHR);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (core_1_1)
|
|
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkGetDeviceGroupSurfacePresentModesKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetDeviceGroupPresentCapabilitiesKHR);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_DEVICE_GROUP_EXTENSION_NAME) &&
|
|
|
|
|
|
device_data.is_device_extension_enabled(VK_KHR_SURFACE_EXTENSION_NAME))
|
|
|
|
|
|
{
|
2021-11-08 13:32:12 +00:00
|
|
|
|
GET_PROC_ADDR(vkGetDeviceGroupSurfacePresentModesKHR);
|
2025-06-27 15:21:58 +00:00
|
|
|
|
GET_PROC_ADDR(vkGetDeviceGroupPresentCapabilitiesKHR);
|
2021-11-08 13:32:12 +00:00
|
|
|
|
}
|
2025-06-27 15:21:58 +00:00
|
|
|
|
|
|
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_SHARED_PRESENTABLE_IMAGE_EXTENSION_NAME))
|
2024-04-12 14:52:39 +02:00
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkGetSwapchainStatusKHR);
|
|
|
|
|
|
}
|
2024-08-02 16:56:30 +01:00
|
|
|
|
#if VULKAN_WSI_LAYER_EXPERIMENTAL
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (device_data.is_device_extension_enabled(VK_EXT_PRESENT_TIMING_EXTENSION_NAME))
|
2024-08-02 16:56:30 +01:00
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkSetSwapchainPresentTimingQueueSizeEXT);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetSwapchainTimingPropertiesEXT);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetSwapchainTimeDomainPropertiesEXT);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPastPresentationTimingEXT);
|
2025-05-14 15:32:07 +00:00
|
|
|
|
GET_PROC_ADDR(vkGetCalibratedTimestampsKHR);
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (device_data.is_device_extension_enabled(VK_EXT_CALIBRATED_TIMESTAMPS_EXTENSION_NAME))
|
2025-05-14 15:32:07 +00:00
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkGetCalibratedTimestampsEXT);
|
|
|
|
|
|
}
|
2024-08-02 16:56:30 +01:00
|
|
|
|
}
|
|
|
|
|
|
#endif
|
2021-09-13 15:30:06 +01:00
|
|
|
|
GET_PROC_ADDR(vkDestroyDevice);
|
2021-09-23 15:35:51 +01:00
|
|
|
|
GET_PROC_ADDR(vkCreateImage);
|
2021-06-25 11:35:11 +01:00
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME))
|
2025-03-25 12:24:01 +00:00
|
|
|
|
{
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (!strcmp(funcName, "vkBindImageMemory2KHR"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return (PFN_vkVoidFunction)&wsi_layer_vkBindImageMemory2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (core_1_1)
|
|
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkBindImageMemory2);
|
2025-03-25 12:24:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-01-23 18:19:27 +00:00
|
|
|
|
/* VK_EXT_swapchain_maintenance1 */
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (device_data.is_device_extension_enabled(VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME))
|
2024-01-23 18:19:27 +00:00
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkReleaseSwapchainImagesEXT);
|
|
|
|
|
|
}
|
2025-08-28 14:46:50 +00:00
|
|
|
|
|
2025-10-16 15:02:36 +00:00
|
|
|
|
/* VK_KHR_swapchain_maintenance1 */
|
|
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!strcmp(funcName, "vkReleaseSwapchainImagesKHR"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return (PFN_vkVoidFunction)&wsi_layer_vkReleaseSwapchainImagesEXT;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-28 14:46:50 +00:00
|
|
|
|
/* VK_KHR_present_wait */
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_PRESENT_WAIT_EXTENSION_NAME))
|
2025-05-09 16:04:39 +00:00
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkWaitForPresentKHR);
|
|
|
|
|
|
}
|
2025-09-18 09:20:43 +00:00
|
|
|
|
|
2025-08-28 14:46:50 +00:00
|
|
|
|
/* VK_KHR_present_wait2 */
|
|
|
|
|
|
if (device_data.is_device_extension_enabled(VK_KHR_PRESENT_WAIT_2_EXTENSION_NAME))
|
|
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkWaitForPresent2KHR);
|
|
|
|
|
|
}
|
2024-01-23 18:19:27 +00:00
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
return device_data.disp.get_user_enabled_entrypoint(device, funcName);
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|
2019-05-24 15:57:50 +01:00
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @brief Trampoline for **vkGetInstanceProcAddr** inside the WSI layer.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Workflow:
|
|
|
|
|
|
* 1. Publish loader-critical symbols (i.e. `vkGetDeviceProcAddr`, `vkGetInstanceProcAddr`).
|
|
|
|
|
|
* 2. Retrieve the instance’s private state (API version and enabled extensions)
|
|
|
|
|
|
* and intercept layer-handled commands.
|
|
|
|
|
|
* 3. Forward all other commands to the downstream instance dispatch table,
|
|
|
|
|
|
* or return nullptr if unavailable.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This layer only exposes core commands and enabled-extension entrypoints,
|
|
|
|
|
|
* preserving the Vulkan dispatch-chain contract.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param instance The VkInstance being queried (may be VK_NULL_HANDLE).
|
|
|
|
|
|
* @param funcName Name of the instance-level command to resolve.
|
|
|
|
|
|
* @return Pointer to this layer’s handler, the next-layer/ICD function, or nullptr.
|
|
|
|
|
|
*/
|
2021-11-03 11:25:48 +00:00
|
|
|
|
VWL_VKAPI_CALL(PFN_vkVoidFunction)
|
|
|
|
|
|
wsi_layer_vkGetInstanceProcAddr(VkInstance instance, const char *funcName) VWL_API_POST
|
2020-06-24 09:36:21 +01:00
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkGetDeviceProcAddr);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetInstanceProcAddr);
|
|
|
|
|
|
GET_PROC_ADDR(vkCreateInstance);
|
|
|
|
|
|
GET_PROC_ADDR(vkDestroyInstance);
|
|
|
|
|
|
GET_PROC_ADDR(vkCreateDevice);
|
2021-06-25 11:35:11 +01:00
|
|
|
|
|
2025-11-26 12:37:12 +00:00
|
|
|
|
if (instance == VK_NULL_HANDLE)
|
|
|
|
|
|
{
|
|
|
|
|
|
return nullptr;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
auto &instance_data = layer::instance_private_data::get(instance);
|
|
|
|
|
|
const bool core_1_1 = instance_data.api_version >= VK_API_VERSION_1_1;
|
|
|
|
|
|
if ((instance_data.is_instance_extension_enabled(VK_KHR_DEVICE_GROUP_EXTENSION_NAME) &&
|
|
|
|
|
|
instance_data.is_instance_extension_enabled(VK_KHR_SURFACE_EXTENSION_NAME)) ||
|
|
|
|
|
|
core_1_1)
|
2022-03-18 12:05:46 +00:00
|
|
|
|
{
|
2025-06-27 15:21:58 +00:00
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDevicePresentRectanglesKHR);
|
2022-03-18 12:05:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (instance_data.is_instance_extension_enabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
|
|
|
|
|
|
{
|
2025-06-27 15:21:58 +00:00
|
|
|
|
if (!strcmp(funcName, "vkGetPhysicalDeviceFeatures2KHR"))
|
|
|
|
|
|
{
|
|
|
|
|
|
return (PFN_vkVoidFunction)&wsi_layer_vkGetPhysicalDeviceFeatures2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (core_1_1)
|
|
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceFeatures2);
|
2022-03-18 12:05:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2021-11-08 13:32:12 +00:00
|
|
|
|
if (instance_data.is_instance_extension_enabled(VK_KHR_SURFACE_EXTENSION_NAME))
|
|
|
|
|
|
{
|
|
|
|
|
|
PFN_vkVoidFunction wsi_func = wsi::get_proc_addr(funcName, instance_data);
|
|
|
|
|
|
if (wsi_func)
|
|
|
|
|
|
{
|
|
|
|
|
|
return wsi_func;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceSupportKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceCapabilitiesKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceFormatsKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceSurfacePresentModesKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkDestroySurfaceKHR);
|
2022-03-03 11:21:07 +00:00
|
|
|
|
|
|
|
|
|
|
if (instance_data.is_instance_extension_enabled(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME))
|
|
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceCapabilities2KHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceFormats2KHR);
|
|
|
|
|
|
}
|
2025-11-25 10:12:33 +00:00
|
|
|
|
#if VULKAN_WSI_LAYER_EXPERIMENTAL
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceCalibrateableTimeDomainsKHR);
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceCalibrateableTimeDomainsEXT);
|
|
|
|
|
|
#endif
|
2025-09-15 19:31:02 +08:00
|
|
|
|
|
|
|
|
|
|
if (instance_data.is_instance_extension_enabled(VK_EXT_DISPLAY_SURFACE_COUNTER_EXTENSION_NAME))
|
|
|
|
|
|
{
|
|
|
|
|
|
GET_PROC_ADDR(vkGetPhysicalDeviceSurfaceCapabilities2EXT);
|
|
|
|
|
|
}
|
2021-11-08 13:32:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-27 15:21:58 +00:00
|
|
|
|
return instance_data.disp.get_user_enabled_entrypoint(instance, funcName);
|
2020-06-24 09:36:21 +01:00
|
|
|
|
}
|