/* Copyright (c) 2021-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. */ #pragma once #include "common/vk_common.h" #include "core/util/logging.hpp" #include "vulkan/vulkan.hpp" #include "vulkan/vulkan_format_traits.hpp" namespace vkb { namespace common { /** * @brief facade helper functions and structs around the functions and structs in common/vk_common, providing a vulkan.hpp-based interface */ struct HPPBufferMemoryBarrier { vk::PipelineStageFlags src_stage_mask = vk::PipelineStageFlagBits::eBottomOfPipe; vk::PipelineStageFlags dst_stage_mask = vk::PipelineStageFlagBits::eTopOfPipe; vk::AccessFlags src_access_mask = {}; vk::AccessFlags dst_access_mask = {}; }; struct HPPImageMemoryBarrier { vk::PipelineStageFlags src_stage_mask = vk::PipelineStageFlagBits::eBottomOfPipe; vk::PipelineStageFlags dst_stage_mask = vk::PipelineStageFlagBits::eTopOfPipe; vk::AccessFlags src_access_mask; vk::AccessFlags dst_access_mask; vk::ImageLayout old_layout = vk::ImageLayout::eUndefined; vk::ImageLayout new_layout = vk::ImageLayout::eUndefined; uint32_t src_queue_family = VK_QUEUE_FAMILY_IGNORED; uint32_t dst_queue_family = VK_QUEUE_FAMILY_IGNORED; }; struct HPPLoadStoreInfo { vk::AttachmentLoadOp load_op = vk::AttachmentLoadOp::eClear; vk::AttachmentStoreOp store_op = vk::AttachmentStoreOp::eStore; }; inline int32_t get_bits_per_pixel(vk::Format format) { return vkb::get_bits_per_pixel(static_cast(format)); } inline vk::Format get_suitable_depth_format(vk::PhysicalDevice physical_device, bool depth_only = false, const std::vector &depth_format_priority_list = { vk::Format::eD32Sfloat, vk::Format::eD24UnormS8Uint, vk::Format::eD16Unorm}) { return static_cast( vkb::get_suitable_depth_format(physical_device, depth_only, reinterpret_cast const &>(depth_format_priority_list))); } inline bool is_buffer_descriptor_type(vk::DescriptorType descriptor_type) { return vkb::is_buffer_descriptor_type(static_cast(descriptor_type)); } inline bool is_depth_only_format(vk::Format format) { assert(vkb::is_depth_only_format(static_cast(format)) == ((vk::componentCount(format) == 1) && (std::string(vk::componentName(format, 0)) == "D"))); return vkb::is_depth_only_format(static_cast(format)); } inline bool is_depth_stencil_format(vk::Format format) { assert(vkb::is_depth_stencil_format(static_cast(format)) == ((vk::componentCount(format) == 2) && (std::string(vk::componentName(format, 0)) == "D") && (std::string(vk::componentName(format, 1)) == "S"))); return vkb::is_depth_stencil_format(static_cast(format)); } inline bool is_depth_format(vk::Format format) { assert(vkb::is_depth_format(static_cast(format)) == (std::string(vk::componentName(format, 0)) == "D")); return vkb::is_depth_format(static_cast(format)); } inline bool is_dynamic_buffer_descriptor_type(vk::DescriptorType descriptor_type) { return vkb::is_dynamic_buffer_descriptor_type(static_cast(descriptor_type)); } inline vk::ShaderModule load_shader(const std::string &filename, vk::Device device, vk::ShaderStageFlagBits stage) { return static_cast(vkb::load_shader(filename, device, static_cast(stage))); } inline void image_layout_transition(vk::CommandBuffer command_buffer, vk::Image image, vk::ImageLayout old_layout, vk::ImageLayout new_layout) { vkb::image_layout_transition(static_cast(command_buffer), static_cast(image), static_cast(old_layout), static_cast(new_layout)); } inline void image_layout_transition(vk::CommandBuffer command_buffer, vk::Image image, vk::ImageLayout old_layout, vk::ImageLayout new_layout, vk::ImageSubresourceRange subresource_range) { vkb::image_layout_transition(static_cast(command_buffer), static_cast(image), static_cast(old_layout), static_cast(new_layout), static_cast(subresource_range)); } inline void image_layout_transition(vk::CommandBuffer command_buffer, vk::Image image, vk::PipelineStageFlags src_stage_mask, vk::PipelineStageFlags dst_stage_mask, vk::AccessFlags src_access_mask, vk::AccessFlags dst_access_mask, vk::ImageLayout old_layout, vk::ImageLayout new_layout, vk::ImageSubresourceRange const &subresource_range) { vkb::image_layout_transition(static_cast(command_buffer), static_cast(image), static_cast(src_stage_mask), static_cast(dst_stage_mask), static_cast(src_access_mask), static_cast(dst_access_mask), static_cast(old_layout), static_cast(new_layout), static_cast(subresource_range)); } inline void make_filters_valid(vk::PhysicalDevice physical_device, vk::Format format, vk::Filter *filter, vk::SamplerMipmapMode *mipmapMode = nullptr) { // Not all formats support linear filtering, so we need to adjust them if they don't if (*filter == vk::Filter::eNearest && (mipmapMode == nullptr || *mipmapMode == vk::SamplerMipmapMode::eNearest)) { return; // These must already be valid } vk::FormatProperties properties = physical_device.getFormatProperties(format); if (!(properties.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear)) { *filter = vk::Filter::eNearest; if (mipmapMode) { *mipmapMode = vk::SamplerMipmapMode::eNearest; } } } inline vk::SurfaceFormatKHR select_surface_format(vk::PhysicalDevice gpu, vk::SurfaceKHR surface, std::vector const &preferred_formats = { vk::Format::eR8G8B8A8Srgb, vk::Format::eB8G8R8A8Srgb, vk::Format::eA8B8G8R8SrgbPack32}) { std::vector supported_surface_formats = gpu.getSurfaceFormatsKHR(surface); assert(!supported_surface_formats.empty()); auto it = std::ranges::find_if(supported_surface_formats, [&preferred_formats](vk::SurfaceFormatKHR surface_format) { return std::ranges::any_of(preferred_formats, [&surface_format](vk::Format format) { return format == surface_format.format; }); }); // We use the first supported format as a fallback in case none of the preferred formats is available return it != supported_surface_formats.end() ? *it : supported_surface_formats[0]; } inline vk::Format choose_blendable_format(vk::PhysicalDevice gpu, const std::vector &format_priority_list) { for (const auto &format : format_priority_list) { vk::FormatProperties fmt_props = gpu.getFormatProperties(format); if (fmt_props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eColorAttachmentBlend) return format; } throw std::runtime_error("No suitable blendable format could be determined"); } // helper functions not backed by vk_common.h inline vk::CommandBuffer allocate_command_buffer(vk::Device device, vk::CommandPool command_pool, vk::CommandBufferLevel level = vk::CommandBufferLevel::ePrimary) { vk::CommandBufferAllocateInfo command_buffer_allocate_info{.commandPool = command_pool, .level = level, .commandBufferCount = 1}; return device.allocateCommandBuffers(command_buffer_allocate_info).front(); } inline vk::DescriptorSet allocate_descriptor_set(vk::Device device, vk::DescriptorPool descriptor_pool, vk::DescriptorSetLayout descriptor_set_layout) { vk::DescriptorSetAllocateInfo descriptor_set_allocate_info{.descriptorPool = descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = &descriptor_set_layout}; return device.allocateDescriptorSets(descriptor_set_allocate_info).front(); } inline vk::Framebuffer create_framebuffer(vk::Device device, vk::RenderPass render_pass, std::vector const &attachments, vk::Extent2D const &extent) { vk::FramebufferCreateInfo framebuffer_create_info{.renderPass = render_pass, .attachmentCount = static_cast(attachments.size()), .pAttachments = attachments.data(), .width = extent.width, .height = extent.height, .layers = 1}; return device.createFramebuffer(framebuffer_create_info); } inline vk::Pipeline create_graphics_pipeline(vk::Device device, vk::PipelineCache pipeline_cache, std::vector const &shader_stages, vk::PipelineVertexInputStateCreateInfo const &vertex_input_state, vk::PrimitiveTopology primitive_topology, uint32_t patch_control_points, vk::PolygonMode polygon_mode, vk::CullModeFlags cull_mode, vk::FrontFace front_face, std::vector const &blend_attachment_states, vk::PipelineDepthStencilStateCreateInfo const &depth_stencil_state, vk::PipelineLayout pipeline_layout, vk::RenderPass render_pass) { vk::PipelineInputAssemblyStateCreateInfo input_assembly_state{.topology = primitive_topology}; vk::PipelineTessellationStateCreateInfo tessellation_state{.patchControlPoints = patch_control_points}; vk::PipelineViewportStateCreateInfo viewport_state{.viewportCount = 1, .scissorCount = 1}; vk::PipelineRasterizationStateCreateInfo rasterization_state{ .polygonMode = polygon_mode, .cullMode = cull_mode, .frontFace = front_face, .lineWidth = 1.0f}; vk::PipelineMultisampleStateCreateInfo multisample_state{.rasterizationSamples = vk::SampleCountFlagBits::e1}; vk::PipelineColorBlendStateCreateInfo color_blend_state{.attachmentCount = static_cast(blend_attachment_states.size()), .pAttachments = blend_attachment_states.data()}; std::array dynamic_state_enables = {vk::DynamicState::eViewport, vk::DynamicState::eScissor}; vk::PipelineDynamicStateCreateInfo dynamic_state{.dynamicStateCount = static_cast(dynamic_state_enables.size()), .pDynamicStates = dynamic_state_enables.data()}; // Final fullscreen composition pass pipeline vk::GraphicsPipelineCreateInfo pipeline_create_info{.stageCount = static_cast(shader_stages.size()), .pStages = shader_stages.data(), .pVertexInputState = &vertex_input_state, .pInputAssemblyState = &input_assembly_state, .pTessellationState = &tessellation_state, .pViewportState = &viewport_state, .pRasterizationState = &rasterization_state, .pMultisampleState = &multisample_state, .pDepthStencilState = &depth_stencil_state, .pColorBlendState = &color_blend_state, .pDynamicState = &dynamic_state, .layout = pipeline_layout, .renderPass = render_pass, .basePipelineIndex = -1}; vk::Result result; vk::Pipeline pipeline; std::tie(result, pipeline) = device.createGraphicsPipeline(pipeline_cache, pipeline_create_info); assert(result == vk::Result::eSuccess); return pipeline; } inline vk::ImageView create_image_view(vk::Device device, vk::Image image, vk::ImageViewType view_type, vk::Format format, vk::ImageAspectFlags aspect_mask = vk::ImageAspectFlagBits::eColor, uint32_t base_mip_level = 0, uint32_t level_count = 1, uint32_t base_array_layer = 0, uint32_t layer_count = 1) { vk::ImageViewCreateInfo image_view_create_info{.image = image, .viewType = view_type, .format = format, .subresourceRange = {.aspectMask = aspect_mask, .baseMipLevel = base_mip_level, .levelCount = level_count, .baseArrayLayer = base_array_layer, .layerCount = layer_count}}; return device.createImageView(image_view_create_info); } inline vk::QueryPool create_query_pool(vk::Device device, vk::QueryType query_type, uint32_t query_count, vk::QueryPipelineStatisticFlags pipeline_statistics = {}) { vk::QueryPoolCreateInfo query_pool_create_info{.queryType = query_type, .queryCount = query_count, .pipelineStatistics = pipeline_statistics}; return device.createQueryPool(query_pool_create_info); } inline vk::Sampler create_sampler(vk::Device device, vk::Filter mag_filter, vk::Filter min_filter, vk::SamplerMipmapMode mipmap_mode, vk::SamplerAddressMode sampler_address_mode, float max_anisotropy, float max_LOD) { vk::SamplerCreateInfo sampler_create_info{.magFilter = mag_filter, .minFilter = min_filter, .mipmapMode = mipmap_mode, .addressModeU = sampler_address_mode, .addressModeV = sampler_address_mode, .addressModeW = sampler_address_mode, .anisotropyEnable = (1.0f < max_anisotropy), .maxAnisotropy = max_anisotropy, .compareOp = vk::CompareOp::eNever, .minLod = 0.0f, .maxLod = max_LOD, .borderColor = vk::BorderColor::eFloatOpaqueWhite}; return device.createSampler(sampler_create_info); } inline vk::Sampler create_sampler(vk::PhysicalDevice gpu, vk::Device device, vk::Format format, vk::Filter filter, vk::SamplerAddressMode sampler_address_mode, float max_anisotropy, float max_LOD) { const vk::FormatProperties fmt_props = gpu.getFormatProperties(format); bool has_linear_filter = !!(fmt_props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eSampledImageFilterLinear); return create_sampler(device, has_linear_filter ? filter : vk::Filter::eNearest, has_linear_filter ? filter : vk::Filter::eNearest, has_linear_filter ? vk::SamplerMipmapMode::eLinear : vk::SamplerMipmapMode::eNearest, sampler_address_mode, max_anisotropy, max_LOD); } inline vk::ImageAspectFlags get_image_aspect_flags(vk::ImageUsageFlagBits usage, vk::Format format) { vk::ImageAspectFlags image_aspect_flags; switch (usage) { case vk::ImageUsageFlagBits::eColorAttachment: assert(!vkb::common::is_depth_format(format)); image_aspect_flags = vk::ImageAspectFlagBits::eColor; break; case vk::ImageUsageFlagBits::eDepthStencilAttachment: assert(vkb::common::is_depth_format(format)); image_aspect_flags = vk::ImageAspectFlagBits::eDepth; // Stencil aspect should only be set on depth + stencil formats if (vkb::common::is_depth_stencil_format(format)) { image_aspect_flags |= vk::ImageAspectFlagBits::eStencil; } break; default: assert(false); } return image_aspect_flags; } inline void submit_and_wait(vk::Device device, vk::Queue queue, std::vector command_buffers, std::vector semaphores = {}) { // Submit command_buffer vk::SubmitInfo submit_info{.commandBufferCount = static_cast(command_buffers.size()), .pCommandBuffers = command_buffers.data(), .signalSemaphoreCount = static_cast(semaphores.size()), .pSignalSemaphores = semaphores.data()}; // Create fence to ensure that command_buffer has finished executing vk::Fence fence = device.createFence({}); // Submit to the queue queue.submit(submit_info, fence); // Wait for the fence to signal that command_buffer has finished executing vk::Result result = device.waitForFences(fence, true, DEFAULT_FENCE_TIMEOUT); if (result != vk::Result::eSuccess) { LOGE("Vulkan error on waitForFences: {}", vk::to_string(result)); abort(); } // Destroy the fence device.destroyFence(fence); } inline uint32_t get_queue_family_index(std::vector const &queue_family_properties, vk::QueueFlagBits queue_flag) { // Dedicated queue for compute // Try to find a queue family index that supports compute but not graphics if (queue_flag & vk::QueueFlagBits::eCompute) { auto propertyIt = std::ranges::find_if(queue_family_properties, [queue_flag](const vk::QueueFamilyProperties &property) { return (property.queueFlags & queue_flag) && !(property.queueFlags & vk::QueueFlagBits::eGraphics); }); if (propertyIt != queue_family_properties.end()) { return static_cast(std::distance(queue_family_properties.begin(), propertyIt)); } } // Dedicated queue for transfer // Try to find a queue family index that supports transfer but not graphics and compute if (queue_flag & vk::QueueFlagBits::eTransfer) { auto propertyIt = std::ranges::find_if(queue_family_properties, [queue_flag](const vk::QueueFamilyProperties &property) { return (property.queueFlags & queue_flag) && !(property.queueFlags & vk::QueueFlagBits::eGraphics) && !(property.queueFlags & vk::QueueFlagBits::eCompute); }); if (propertyIt != queue_family_properties.end()) { return static_cast(std::distance(queue_family_properties.begin(), propertyIt)); } } // For other queue types or if no separate compute queue is present, return the first one to support the requested flags auto propertyIt = std::ranges::find_if( queue_family_properties, [queue_flag](const vk::QueueFamilyProperties &property) { return (property.queueFlags & queue_flag) == queue_flag; }); if (propertyIt != queue_family_properties.end()) { return static_cast(std::distance(queue_family_properties.begin(), propertyIt)); } throw std::runtime_error("Could not find a matching queue family index"); } } // namespace common } // namespace vkb