/* Copyright (c) 2022-2025, NVIDIA CORPORATION. All rights reserved. * * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 the "License"; * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "core/hpp_swapchain.h" #include "core/device.h" #include "core/hpp_physical_device.h" namespace vkb { namespace { template constexpr const T &clamp(const T &v, const T &lo, const T &hi) { return (v < lo) ? lo : ((hi < v) ? hi : v); } vk::Extent2D choose_extent(vk::Extent2D request_extent, const vk::Extent2D &min_image_extent, const vk::Extent2D &max_image_extent, const vk::Extent2D ¤t_extent) { if (current_extent.width == 0xFFFFFFFF) { return request_extent; } if (request_extent.width < 1 || request_extent.height < 1) { LOGW("(HPPSwapchain) Image extent ({}, {}) not supported. Selecting ({}, {}).", request_extent.width, request_extent.height, current_extent.width, current_extent.height); return current_extent; } request_extent.width = clamp(request_extent.width, min_image_extent.width, max_image_extent.width); request_extent.height = clamp(request_extent.height, min_image_extent.height, max_image_extent.height); return request_extent; } vk::PresentModeKHR choose_present_mode(vk::PresentModeKHR request_present_mode, const std::vector &available_present_modes, const std::vector &present_mode_priority_list) { // Try to find the requested present mode in the available present modes auto const present_mode_it = std::ranges::find(available_present_modes, request_present_mode); if (present_mode_it == available_present_modes.end()) { // If the requested present mode isn't found, then try to find a mode from the priority list auto const chosen_present_mode_it = std::ranges::find_if(present_mode_priority_list, [&available_present_modes](vk::PresentModeKHR present_mode) { return std::ranges::find(available_present_modes, present_mode) != available_present_modes.end(); }); // If nothing found, always default to FIFO vk::PresentModeKHR const chosen_present_mode = (chosen_present_mode_it != present_mode_priority_list.end()) ? *chosen_present_mode_it : vk::PresentModeKHR::eFifo; LOGW("(HPPSwapchain) Present mode '{}' not supported. Selecting '{}'.", vk::to_string(request_present_mode), vk::to_string(chosen_present_mode)); return chosen_present_mode; } else { LOGI("(HPPSwapchain) Present mode selected: {}", to_string(request_present_mode)); return request_present_mode; } } vk::SurfaceFormatKHR choose_surface_format(const vk::SurfaceFormatKHR requested_surface_format, const std::vector &available_surface_formats, const std::vector &surface_format_priority_list) { // Try to find the requested surface format in the available surface formats auto const surface_format_it = std::ranges::find(available_surface_formats, requested_surface_format); // If the requested surface format isn't found, then try to request a format from the priority list if (surface_format_it == available_surface_formats.end()) { auto const chosen_surface_format_it = std::ranges::find_if(surface_format_priority_list, [&available_surface_formats](vk::SurfaceFormatKHR surface_format) { return std::ranges::find(available_surface_formats, surface_format) != available_surface_formats.end(); }); // If nothing found, default to the first available format vk::SurfaceFormatKHR const &chosen_surface_format = (chosen_surface_format_it != surface_format_priority_list.end()) ? *chosen_surface_format_it : available_surface_formats[0]; LOGW("(HPPSwapchain) Surface format ({}) not supported. Selecting ({}).", vk::to_string(requested_surface_format.format) + ", " + vk::to_string(requested_surface_format.colorSpace), vk::to_string(chosen_surface_format.format) + ", " + vk::to_string(chosen_surface_format.colorSpace)); return chosen_surface_format; } else { LOGI("(HPPSwapchain) Surface format selected: {}", vk::to_string(requested_surface_format.format) + ", " + vk::to_string(requested_surface_format.colorSpace)); return requested_surface_format; } } vk::SurfaceTransformFlagBitsKHR choose_transform(vk::SurfaceTransformFlagBitsKHR request_transform, vk::SurfaceTransformFlagsKHR supported_transform, vk::SurfaceTransformFlagBitsKHR current_transform) { if (request_transform & supported_transform) { return request_transform; } LOGW("(HPPSwapchain) Surface transform '{}' not supported. Selecting '{}'.", vk::to_string(request_transform), vk::to_string(current_transform)); return current_transform; } vk::CompositeAlphaFlagBitsKHR choose_composite_alpha(vk::CompositeAlphaFlagBitsKHR request_composite_alpha, vk::CompositeAlphaFlagsKHR supported_composite_alpha) { if (request_composite_alpha & supported_composite_alpha) { return request_composite_alpha; } static const std::vector composite_alpha_priority_list = {vk::CompositeAlphaFlagBitsKHR::eOpaque, vk::CompositeAlphaFlagBitsKHR::ePreMultiplied, vk::CompositeAlphaFlagBitsKHR::ePostMultiplied, vk::CompositeAlphaFlagBitsKHR::eInherit}; auto const chosen_composite_alpha_it = std::find_if(composite_alpha_priority_list.begin(), composite_alpha_priority_list.end(), [&supported_composite_alpha](vk::CompositeAlphaFlagBitsKHR composite_alpha) { return composite_alpha & supported_composite_alpha; }); if (chosen_composite_alpha_it == composite_alpha_priority_list.end()) { throw std::runtime_error("No compatible composite alpha found."); } else { LOGW("(HPPSwapchain) Composite alpha '{}' not supported. Selecting '{}.", vk::to_string(request_composite_alpha), vk::to_string(*chosen_composite_alpha_it)); return *chosen_composite_alpha_it; } } bool validate_format_feature(vk::ImageUsageFlagBits image_usage, vk::FormatFeatureFlags supported_features) { return (image_usage != vk::ImageUsageFlagBits::eStorage) || (supported_features & vk::FormatFeatureFlagBits::eStorageImage); } std::set choose_image_usage(const std::set &requested_image_usage_flags, vk::ImageUsageFlags supported_image_usage, vk::FormatFeatureFlags supported_features) { std::set validated_image_usage_flags; for (auto flag : requested_image_usage_flags) { if ((flag & supported_image_usage) && validate_format_feature(flag, supported_features)) { validated_image_usage_flags.insert(flag); } else { LOGW("(HPPSwapchain) Image usage ({}) requested but not supported.", vk::to_string(flag)); } } if (validated_image_usage_flags.empty()) { // Pick the first format from list of defaults, if supported static const std::vector image_usage_priority_list = { vk::ImageUsageFlagBits::eColorAttachment, vk::ImageUsageFlagBits::eStorage, vk::ImageUsageFlagBits::eSampled, vk::ImageUsageFlagBits::eTransferDst}; auto const priority_list_it = std::ranges::find_if(image_usage_priority_list, [&supported_image_usage, &supported_features](auto const image_usage) { return ((image_usage & supported_image_usage) && validate_format_feature(image_usage, supported_features)); }); if (priority_list_it != image_usage_priority_list.end()) { validated_image_usage_flags.insert(*priority_list_it); } } if (validated_image_usage_flags.empty()) { throw std::runtime_error("No compatible image usage found."); } else { // Log image usage flags used std::string usage_list; for (vk::ImageUsageFlagBits image_usage : validated_image_usage_flags) { usage_list += to_string(image_usage) + " "; } LOGI("(HPPSwapchain) Image usage flags: {}", usage_list); } return validated_image_usage_flags; } vk::ImageUsageFlags composite_image_flags(std::set &image_usage_flags) { vk::ImageUsageFlags image_usage; for (auto flag : image_usage_flags) { image_usage |= flag; } return image_usage; } } // namespace namespace core { HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const vk::Extent2D &extent) : HPPSwapchain{old_swapchain.device, old_swapchain.surface, old_swapchain.properties.present_mode, old_swapchain.present_mode_priority_list, old_swapchain.surface_format_priority_list, extent, old_swapchain.properties.image_count, old_swapchain.properties.pre_transform, old_swapchain.image_usage_flags, old_swapchain.get_handle()} {} HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const uint32_t image_count) : HPPSwapchain{old_swapchain.device, old_swapchain.surface, old_swapchain.properties.present_mode, old_swapchain.present_mode_priority_list, old_swapchain.surface_format_priority_list, old_swapchain.properties.extent, image_count, old_swapchain.properties.pre_transform, old_swapchain.image_usage_flags, old_swapchain.get_handle()} {} HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const std::set &image_usage_flags) : HPPSwapchain{old_swapchain.device, old_swapchain.surface, old_swapchain.properties.present_mode, old_swapchain.present_mode_priority_list, old_swapchain.surface_format_priority_list, old_swapchain.properties.extent, old_swapchain.properties.image_count, old_swapchain.properties.pre_transform, image_usage_flags, old_swapchain.get_handle()} {} HPPSwapchain::HPPSwapchain(HPPSwapchain &old_swapchain, const vk::Extent2D &extent, const vk::SurfaceTransformFlagBitsKHR transform) : HPPSwapchain{old_swapchain.device, old_swapchain.surface, old_swapchain.properties.present_mode, old_swapchain.present_mode_priority_list, old_swapchain.surface_format_priority_list, extent, old_swapchain.properties.image_count, transform, old_swapchain.image_usage_flags, old_swapchain.get_handle()} {} HPPSwapchain::HPPSwapchain(vkb::core::DeviceCpp &device, vk::SurfaceKHR surface, const vk::PresentModeKHR present_mode, const std::vector &present_mode_priority_list, const std::vector &surface_format_priority_list, const vk::Extent2D &extent, const uint32_t image_count, const vk::SurfaceTransformFlagBitsKHR transform, const std::set &image_usage_flags, vk::SwapchainKHR old_swapchain) : device{device}, surface{surface} { this->present_mode_priority_list = present_mode_priority_list; this->surface_format_priority_list = surface_format_priority_list; std::vector surface_formats = device.get_gpu().get_handle().getSurfaceFormatsKHR(surface); LOGI("Surface supports the following surface formats:"); for (auto &surface_format : surface_formats) { LOGI(" \t{}", vk::to_string(surface_format.format) + ", " + vk::to_string(surface_format.colorSpace)); } std::vector present_modes = device.get_gpu().get_handle().getSurfacePresentModesKHR(surface); LOGI("Surface supports the following present modes:"); for (auto &present_mode : present_modes) { LOGI(" \t{}", to_string(present_mode)); } // Choose best properties based on surface capabilities vk::SurfaceCapabilitiesKHR const surface_capabilities = device.get_gpu().get_handle().getSurfaceCapabilitiesKHR(surface); properties.old_swapchain = old_swapchain; properties.image_count = clamp(image_count, surface_capabilities.minImageCount, surface_capabilities.maxImageCount ? surface_capabilities.maxImageCount : std::numeric_limits::max()); properties.extent = choose_extent(extent, surface_capabilities.minImageExtent, surface_capabilities.maxImageExtent, surface_capabilities.currentExtent); properties.surface_format = choose_surface_format(properties.surface_format, surface_formats, surface_format_priority_list); properties.array_layers = 1; vk::FormatProperties const format_properties = device.get_gpu().get_handle().getFormatProperties(properties.surface_format.format); this->image_usage_flags = choose_image_usage(image_usage_flags, surface_capabilities.supportedUsageFlags, format_properties.optimalTilingFeatures); properties.image_usage = composite_image_flags(this->image_usage_flags); properties.pre_transform = choose_transform(transform, surface_capabilities.supportedTransforms, surface_capabilities.currentTransform); properties.composite_alpha = choose_composite_alpha(vk::CompositeAlphaFlagBitsKHR::eInherit, surface_capabilities.supportedCompositeAlpha); properties.present_mode = choose_present_mode(present_mode, present_modes, present_mode_priority_list); vk::SwapchainCreateInfoKHR const create_info{.surface = surface, .minImageCount = properties.image_count, .imageFormat = properties.surface_format.format, .imageColorSpace = properties.surface_format.colorSpace, .imageExtent = properties.extent, .imageArrayLayers = properties.array_layers, .imageUsage = properties.image_usage, .preTransform = properties.pre_transform, .compositeAlpha = properties.composite_alpha, .presentMode = properties.present_mode, .oldSwapchain = properties.old_swapchain}; handle = device.get_handle().createSwapchainKHR(create_info); images = device.get_handle().getSwapchainImagesKHR(handle); } HPPSwapchain::~HPPSwapchain() { if (handle) { device.get_handle().destroySwapchainKHR(handle); } } HPPSwapchain::HPPSwapchain(HPPSwapchain &&other) : device{other.device}, surface{std::exchange(other.surface, nullptr)}, handle{std::exchange(other.handle, nullptr)}, images{std::exchange(other.images, {})}, properties{std::exchange(other.properties, {})}, present_mode_priority_list{std::exchange(other.present_mode_priority_list, {})}, surface_format_priority_list{std::exchange(other.surface_format_priority_list, {})}, image_usage_flags{std::move(other.image_usage_flags)} {} bool HPPSwapchain::is_valid() const { return !!handle; } vkb::core::DeviceCpp const &HPPSwapchain::get_device() const { return device; } vk::SwapchainKHR HPPSwapchain::get_handle() const { return handle; } std::pair HPPSwapchain::acquire_next_image(vk::Semaphore image_acquired_semaphore, vk::Fence fence) const { vk::ResultValue rv = device.get_handle().acquireNextImageKHR(handle, std::numeric_limits::max(), image_acquired_semaphore, fence); return std::make_pair(rv.result, rv.value); } const vk::Extent2D &HPPSwapchain::get_extent() const { return properties.extent; } vk::Format HPPSwapchain::get_format() const { return properties.surface_format.format; } const std::vector &HPPSwapchain::get_images() const { return images; } vk::SurfaceTransformFlagBitsKHR HPPSwapchain::get_transform() const { return properties.pre_transform; } vk::SurfaceKHR HPPSwapchain::get_surface() const { return surface; } vk::ImageUsageFlags HPPSwapchain::get_usage() const { return properties.image_usage; } vk::PresentModeKHR HPPSwapchain::get_present_mode() const { return properties.present_mode; } } // namespace core } // namespace vkb