/* Copyright (c) 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/hpp_vk_common.h" #include "common/vk_common.h" #include "core/buffer.h" #include "core/hpp_physical_device.h" #include "hpp_debug.h" #include "hpp_fence_pool.h" #include "hpp_queue.h" #include "hpp_resource_cache.h" #include "queue.h" #include #include namespace vkb { class FencePool; class DebugUtils; class PhysicalDevice; class ResourceCache; namespace core { template class CommandPool; using CommandPoolC = CommandPool; using CommandPoolCpp = CommandPool; template class Device : public vkb::core::VulkanResource::type> { public: using Bool32Type = typename std::conditional::type; using BufferCopyType = typename std::conditional::type; using CommandBufferLevelType = typename std::conditional::type; using CommandBufferType = typename std::conditional::type; using CommandPoolCreateFlagsType = typename std::conditional::type; using CommandPoolType = typename std::conditional::type; using DeviceMemoryType = typename std::conditional::type; using DeviceType = typename std::conditional::type; using Extent2DType = typename std::conditional::type; using FenceType = typename std::conditional::type; using FormatType = typename std::conditional::type; using ImageType = typename std::conditional::type; using ImageUsageFlagsType = typename std::conditional::type; using MemoryPropertyFlagsType = typename std::conditional::type; using QueueFamilyPropertiesType = typename std::conditional::type; using QueueFlagBitsType = typename std::conditional::type; using QueueFlagsType = typename std::conditional::type; using QueueType = typename std::conditional::type; using ResultType = typename std::conditional::type; using SemaphoreType = typename std::conditional::type; using SurfaceType = typename std::conditional::type; using DebugUtilsType = typename std::conditional::type; using FencePoolType = typename std::conditional::type; using PhysicalDeviceType = typename std::conditional::type; using CoreQueueType = typename std::conditional::type; using ResourceCacheType = typename std::conditional::type; public: /** * @brief Device constructor * @param gpu A valid Vulkan physical device and the requested gpu features * @param surface The surface * @param debug_utils The debug utils to be associated to this device * @param requested_extensions (Optional) List of required device extensions and whether support is optional or not */ Device(PhysicalDeviceType &gpu, SurfaceType surface, std::unique_ptr &&debug_utils, std::unordered_map const &requested_extensions = {}); /** * @brief Device constructor * @param gpu A valid Vulkan physical device and the requested gpu features * @param vulkan_device A valid Vulkan device * @param surface The surface */ Device(PhysicalDeviceType &gpu, DeviceType &vulkan_device, SurfaceType surface); Device(const Device &) = delete; Device(Device &&) = delete; ~Device(); Device &operator=(const Device &) = delete; Device &operator=(Device &&) = delete; void add_queue(size_t global_index, uint32_t family_index, QueueFamilyPropertiesType const &properties, Bool32Type can_present); void copy_buffer( vkb::core::Buffer const &src, vkb::core::Buffer &dst, QueueType queue, BufferCopyType const *copy_region = nullptr); CommandBufferType create_command_buffer(CommandBufferLevelType level, bool begin = false) const; CommandPoolType create_command_pool(uint32_t queue_index, CommandPoolCreateFlagsType flags = 0); std::pair create_image( FormatType format, Extent2DType const &extent, uint32_t mip_levels, ImageUsageFlagsType usage, MemoryPropertyFlagsType properties) const; void create_internal_command_pool(); void create_internal_fence_pool(); void flush_command_buffer(CommandBufferType command_buffer, QueueType queue, bool free = true, SemaphoreType signal_semaphore = VK_NULL_HANDLE) const; vkb::core::CommandPool &get_command_pool() const; DebugUtilsType const &get_debug_utils() const; FencePoolType &get_fence_pool() const; PhysicalDeviceType const &get_gpu() const; CoreQueueType const &get_queue(uint32_t queue_family_index, uint32_t queue_index) const; CoreQueueType const &get_queue_by_flags(QueueFlagsType queue_flags, uint32_t queue_index) const; CoreQueueType const &get_queue_by_present(uint32_t queue_index) const; ResourceCacheType &get_resource_cache(); bool is_extension_enabled(const char *extension) const; bool is_image_format_supported(FormatType format) const; void wait_idle() const; private: void copy_buffer_impl(vk::Device device, vkb::core::BufferCpp const &src, vkb::core::BufferCpp &dst, vk::Queue queue, vk::BufferCopy const *copy_region); vk::CommandBuffer create_command_buffer_impl(vk::Device device, vk::CommandBufferLevel level, bool begin) const; std::pair create_image_impl( vk::Device device, vk::Format format, vk::Extent2D const &extent, uint32_t mip_levels, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties) const; void flush_command_buffer_impl( vk::Device device, vk::CommandBuffer command_buffer, vk::Queue queue, bool free = true, vk::Semaphore signal_semaphore = nullptr) const; vkb::core::HPPQueue const &get_queue_by_flags_impl(vk::QueueFlags queue_flags, uint32_t queue_index) const; void init(std::unordered_map const &requested_extensions); private: std::unique_ptr command_pool; std::unique_ptr debug_utils; std::vector enabled_extensions{}; std::unique_ptr fence_pool; vkb::core::HPPPhysicalDevice &gpu; std::vector> queues; vkb::HPPResourceCache resource_cache; vk::SurfaceKHR surface = nullptr; }; using DeviceC = Device; using DeviceCpp = Device; } // namespace core } // namespace vkb #include "core/command_pool.h" namespace vkb { namespace core { template <> inline Device::Device(vkb::core::HPPPhysicalDevice &gpu, vk::SurfaceKHR surface, std::unique_ptr &&debug_utils, std::unordered_map const &requested_extensions) : vkb::core::VulkanResourceCpp{nullptr, this}, debug_utils{std::move(debug_utils)}, gpu{gpu}, resource_cache{*this}, surface(surface) { init(requested_extensions); } template <> inline Device::Device(vkb::PhysicalDevice &gpu, VkSurfaceKHR surface, std::unique_ptr &&debug_utils, std::unordered_map const &requested_extensions) : vkb::core::VulkanResourceC{VK_NULL_HANDLE, this}, debug_utils{reinterpret_cast(debug_utils.release())}, gpu{reinterpret_cast(gpu)}, resource_cache{*reinterpret_cast(this)}, surface(static_cast(surface)) { init(requested_extensions); } template <> inline Device::Device(vkb::core::HPPPhysicalDevice &gpu, vk::Device &vulkan_device, vk::SurfaceKHR surface) : VulkanResource{vulkan_device}, gpu{gpu}, surface{surface}, resource_cache{*this} { debug_utils = std::make_unique(); } template <> inline Device::Device(vkb::PhysicalDevice &gpu, VkDevice &vulkan_device, VkSurfaceKHR surface) : VulkanResource{vulkan_device}, gpu{reinterpret_cast(gpu)}, resource_cache{*reinterpret_cast(this)}, surface{static_cast(surface)} { debug_utils = std::make_unique(); } template inline Device::~Device() { resource_cache.clear(); command_pool.reset(); fence_pool.reset(); vkb::allocated::shutdown(); if (this->get_handle()) { if constexpr (bindingType == vkb::BindingType::Cpp) { this->get_handle().destroy(); } else { static_cast(this->get_handle()).destroy(); } } } template inline void Device::add_queue(size_t global_index, uint32_t family_index, QueueFamilyPropertiesType const &properties, Bool32Type can_present) { if (queues.size() <= global_index) { queues.resize(global_index + 1); } if constexpr (bindingType == vkb::BindingType::Cpp) { queues[global_index].emplace_back(*this, family_index, properties, can_present, 0); } else { queues[global_index].emplace_back(*reinterpret_cast(this), family_index, reinterpret_cast(properties), static_cast(can_present), 0); } } template inline void Device::copy_buffer(vkb::core::Buffer const &src, vkb::core::Buffer &dst, QueueType queue, BufferCopyType const *copy_region) { assert(dst.get_size() <= src.get_size()); assert(src.get_handle()); if constexpr (bindingType == vkb::BindingType::Cpp) { copy_buffer_impl(this->get_handle(), src, dst, queue, copy_region); } else { copy_buffer_impl(static_cast(this->get_handle()), reinterpret_cast(src), reinterpret_cast(dst), static_cast(queue), reinterpret_cast(copy_region)); } } template inline typename Device::CommandBufferType Device::create_command_buffer(CommandBufferLevelType level, bool begin) const { if constexpr (bindingType == vkb::BindingType::Cpp) { return create_command_buffer_impl(this->get_handle(), level, begin); } else { return static_cast( create_command_buffer_impl(static_cast(this->get_handle()), static_cast(level), begin)); } } template inline typename Device::CommandPoolType Device::create_command_pool(uint32_t queue_index, CommandPoolCreateFlagsType flags) { if constexpr (bindingType == vkb::BindingType::Cpp) { vk::CommandPoolCreateInfo command_pool_info{.flags = flags, .queueFamilyIndex = queue_index}; return this->get_handle().createCommandPool(command_pool_info); } else { vk::CommandPoolCreateInfo command_pool_info{.flags = static_cast(flags), .queueFamilyIndex = queue_index}; return static_cast(this->get_handle()).createCommandPool(command_pool_info); } } template inline std::pair::ImageType, typename Device::DeviceMemoryType> Device::create_image( FormatType format, Extent2DType const &extent, uint32_t mip_levels, ImageUsageFlagsType usage, MemoryPropertyFlagsType properties) const { if constexpr (bindingType == vkb::BindingType::Cpp) { return create_image_impl(this->get_handle(), format, extent, mip_levels, usage, properties); } else { return static_cast>(create_image_impl(static_cast(this->get_handle()), static_cast(format), static_cast(extent), mip_levels, static_cast(usage), static_cast(properties))); } } template inline void Device::create_internal_command_pool() { uint32_t familyIndex = get_queue_by_flags_impl(vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute, 0).get_family_index(); if constexpr (bindingType == vkb::BindingType::Cpp) { command_pool = std::make_unique(*this, familyIndex); } else { command_pool = std::make_unique(*reinterpret_cast(this), familyIndex); } } template inline void Device::create_internal_fence_pool() { if constexpr (bindingType == vkb::BindingType::Cpp) { fence_pool = std::make_unique(*this); } else { fence_pool = std::make_unique(*reinterpret_cast(this)); } } template inline void Device::flush_command_buffer(CommandBufferType command_buffer, QueueType queue, bool free, SemaphoreType signal_semaphore) const { if constexpr (bindingType == vkb::BindingType::Cpp) { flush_command_buffer_impl(this->get_handle(), command_buffer, queue, free, signal_semaphore); } else { flush_command_buffer_impl(static_cast(this->get_handle()), static_cast(command_buffer), static_cast(queue), free, static_cast(signal_semaphore)); } } template inline vkb::core::CommandPool &Device::get_command_pool() const { if constexpr (bindingType == vkb::BindingType::Cpp) { return *command_pool; } else { return reinterpret_cast(*command_pool); } } template inline typename Device::DebugUtilsType const &Device::get_debug_utils() const { if constexpr (bindingType == vkb::BindingType::Cpp) { return *debug_utils; } else { return reinterpret_cast(*debug_utils); } } template inline typename Device::FencePoolType &Device::get_fence_pool() const { if constexpr (bindingType == vkb::BindingType::Cpp) { return *fence_pool; } else { return reinterpret_cast(*fence_pool); } } template inline typename Device::PhysicalDeviceType const &Device::get_gpu() const { if constexpr (bindingType == vkb::BindingType::Cpp) { return gpu; } else { return reinterpret_cast(gpu); } } template inline typename Device::CoreQueueType const &Device::get_queue(uint32_t queue_family_index, uint32_t queue_index) const { assert(queue_family_index < queues.size() && "Queue family index out of bounds"); assert(queue_index < queues[queue_family_index].size() && "Queue index out of bounds"); if constexpr (bindingType == vkb::BindingType::Cpp) { return queues[queue_family_index][queue_index]; } else { return reinterpret_cast(queues[queue_family_index][queue_index]); } } template inline typename Device::CoreQueueType const &Device::get_queue_by_flags(QueueFlagsType required_queue_flags, uint32_t queue_index) const { if constexpr (bindingType == vkb::BindingType::Cpp) { return get_queue_by_flags_impl(required_queue_flags, queue_index); } else { return reinterpret_cast(get_queue_by_flags_impl(static_cast(required_queue_flags), queue_index)); } } template inline typename Device::CoreQueueType const &Device::get_queue_by_present(uint32_t queue_index) const { auto queueIt = std::ranges::find_if(queues, [queue_index](const std::vector &queue_family) { return !queue_family.empty() && queue_index < queue_family[0].get_properties().queueCount && queue_family[0].support_present(); }); if (queueIt != queues.end()) { if constexpr (bindingType == vkb::BindingType::Cpp) { return (*queueIt)[queue_index]; } else { return reinterpret_cast((*queueIt)[queue_index]); } } throw std::runtime_error("Queue not found"); } template inline typename Device::ResourceCacheType &Device::get_resource_cache() { if constexpr (bindingType == vkb::BindingType::Cpp) { return resource_cache; } else { return reinterpret_cast(resource_cache); } } template inline bool Device::is_extension_enabled(const char *extension) const { return std::ranges::find_if(enabled_extensions, [extension](const char *enabled_extension) { return strcmp(extension, enabled_extension) == 0; }) != enabled_extensions.end(); } template inline bool Device::is_image_format_supported(FormatType format) const { // as we want to check for an error (vk::Result::eErrorFormatNotSupported) we use the non-throwing version of getImageFormatProperties here vk::ImageFormatProperties format_properties; return vk::Result::eErrorFormatNotSupported != gpu.get_handle().getImageFormatProperties( static_cast(format), vk::ImageType::e2D, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eSampled, {}, &format_properties); } template inline void Device::wait_idle() const { if constexpr (bindingType == vkb::BindingType::Cpp) { this->get_handle().waitIdle(); } else { static_cast(this->get_handle()).waitIdle(); } } template inline void Device::copy_buffer_impl( vk::Device device, vkb::core::BufferCpp const &src, vkb::core::BufferCpp &dst, vk::Queue queue, vk::BufferCopy const *copy_region) { vk::CommandBuffer command_buffer = create_command_buffer_impl(device, vk::CommandBufferLevel::ePrimary, true); vk::BufferCopy buffer_copy; if (copy_region == nullptr) { buffer_copy.size = src.get_size(); } else { buffer_copy = *copy_region; } command_buffer.copyBuffer(src.get_handle(), dst.get_handle(), buffer_copy); flush_command_buffer_impl(device, command_buffer, queue); } template inline vk::CommandBuffer Device::create_command_buffer_impl(vk::Device device, vk::CommandBufferLevel level, bool begin) const { assert(command_pool && "No command pool exists in the device"); vk::CommandBufferAllocateInfo command_buffer_allocate_info{.commandPool = command_pool->get_handle(), .level = level, .commandBufferCount = 1}; vk::CommandBuffer command_buffer = device.allocateCommandBuffers(command_buffer_allocate_info).front(); // If requested, also start recording for the new command buffer if (begin) { command_buffer.begin(vk::CommandBufferBeginInfo()); } return command_buffer; } template inline std::pair Device::create_image_impl( vk::Device device, vk::Format format, vk::Extent2D const &extent, uint32_t mip_levels, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties) const { vk::ImageCreateInfo image_create_info{.imageType = vk::ImageType::e2D, .format = format, .extent = {.width = extent.width, .height = extent.height, .depth = 1}, .mipLevels = mip_levels, .arrayLayers = 1, .samples = vk::SampleCountFlagBits::e1, .tiling = vk::ImageTiling::eOptimal, .usage = usage}; vk::Image image = device.createImage(image_create_info); vk::MemoryRequirements memory_requirements = device.getImageMemoryRequirements(image); vk::MemoryAllocateInfo memory_allocation{.allocationSize = memory_requirements.size, .memoryTypeIndex = gpu.get_memory_type(memory_requirements.memoryTypeBits, properties)}; vk::DeviceMemory memory = device.allocateMemory(memory_allocation); device.bindImageMemory(image, memory, 0); return std::make_pair(image, memory); } template inline void Device::flush_command_buffer_impl( vk::Device device, vk::CommandBuffer command_buffer, vk::Queue queue, bool free, vk::Semaphore signal_semaphore) const { if (command_buffer) { command_buffer.end(); vk::SubmitInfo submit_info{.commandBufferCount = 1, .pCommandBuffers = &command_buffer}; if (signal_semaphore) { submit_info.setSignalSemaphores(signal_semaphore); } // Create fence to ensure that the 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("Detected Vulkan error: {}", vkb::to_string(result)); abort(); } device.destroyFence(fence); if (command_pool && free) { device.freeCommandBuffers(command_pool->get_handle(), command_buffer); } } } template vkb::core::HPPQueue const &Device::get_queue_by_flags_impl(vk::QueueFlags required_queue_flags, uint32_t queue_index) const { auto queueIt = std::ranges::find_if(queues, [required_queue_flags, queue_index](const std::vector &queue) { assert(!queue.empty()); vk::QueueFamilyProperties const &properties = queue[0].get_properties(); return ((properties.queueFlags & required_queue_flags) == required_queue_flags) && (queue_index < properties.queueCount); }); if (queueIt == queues.end()) { throw std::runtime_error("Queue not found"); } return (*queueIt)[queue_index]; } template inline void Device::init(std::unordered_map const &requested_extensions) { LOGI("Selected GPU: {}", *gpu.get_properties().deviceName); // Prepare the device queues std::vector queue_family_properties = gpu.get_queue_family_properties(); std::vector queue_create_infos; std::vector> queue_priorities; queue_create_infos.reserve(queue_family_properties.size()); queue_priorities.reserve(queue_family_properties.size()); for (uint32_t queue_family_index = 0U; queue_family_index < queue_family_properties.size(); ++queue_family_index) { auto const &queue_family_property = queue_family_properties[queue_family_index]; queue_priorities.push_back(std::vector(queue_family_property.queueCount, 0.5f)); if (gpu.has_high_priority_graphics_queue() && (vkb::common::get_queue_family_index(queue_family_properties, vk::QueueFlagBits::eGraphics) == queue_family_index)) { queue_priorities.back()[0] = 1.0f; } queue_create_infos.push_back({.queueFamilyIndex = queue_family_index, .queueCount = queue_family_property.queueCount, .pQueuePriorities = queue_priorities[queue_family_index].data()}); } // Check extensions to enable Vma Dedicated Allocation bool can_get_memory_requirements = gpu.is_extension_supported("VK_KHR_get_memory_requirements2"); bool has_dedicated_allocation = gpu.is_extension_supported("VK_KHR_dedicated_allocation"); if (can_get_memory_requirements && has_dedicated_allocation) { enabled_extensions.push_back("VK_KHR_get_memory_requirements2"); enabled_extensions.push_back("VK_KHR_dedicated_allocation"); LOGI("Dedicated Allocation enabled"); } // For performance queries, we also use host query reset since queryPool resets cannot // live in the same command buffer as beginQuery if (gpu.is_extension_supported("VK_KHR_performance_query") && gpu.is_extension_supported("VK_EXT_host_query_reset")) { auto perf_counter_features = gpu.get_extension_features(); auto host_query_reset_features = gpu.get_extension_features(); if (perf_counter_features.performanceCounterQueryPools && host_query_reset_features.hostQueryReset) { gpu.add_extension_features().performanceCounterQueryPools = VK_TRUE; gpu.add_extension_features().hostQueryReset = VK_TRUE; enabled_extensions.push_back("VK_KHR_performance_query"); enabled_extensions.push_back("VK_EXT_host_query_reset"); LOGI("Performance query enabled"); } } // Check that extensions are supported before trying to create the device std::vector unsupported_extensions{}; for (auto &extension : requested_extensions) { if (gpu.is_extension_supported(extension.first)) { enabled_extensions.emplace_back(extension.first); } else { unsupported_extensions.emplace_back(extension.first); } } if (enabled_extensions.size() > 0) { LOGI("Device supports the following requested extensions:"); for (auto &extension : enabled_extensions) { LOGI(" \t{}", extension); } } if (unsupported_extensions.size() > 0) { auto error = false; for (auto &extension : unsupported_extensions) { auto extIt = requested_extensions.find(extension); assert(extIt != requested_extensions.end()); if (extIt->second) { LOGW("Optional device extension {} not available, some features may be disabled", extension); } else { LOGE("Required device extension {} not available, cannot run", extension); error = true; } } if (error) { throw VulkanException(VK_ERROR_EXTENSION_NOT_PRESENT, "Extensions not present"); } } // Latest requested feature will have the pNext's all set up for device creation. vk::DeviceCreateInfo create_info{.pNext = gpu.get_extension_feature_chain(), .queueCreateInfoCount = static_cast(queue_create_infos.size()), .pQueueCreateInfos = queue_create_infos.data(), .enabledExtensionCount = static_cast(enabled_extensions.size()), .ppEnabledExtensionNames = enabled_extensions.data(), .pEnabledFeatures = &gpu.get_requested_features()}; this->set_handle(gpu.get_handle().createDevice(create_info)); queues.resize(queue_family_properties.size()); for (uint32_t queue_family_index = 0U; queue_family_index < queue_family_properties.size(); ++queue_family_index) { const vk::QueueFamilyProperties &queue_family_property = gpu.get_queue_family_properties()[queue_family_index]; vk::Bool32 present_supported = gpu.get_handle().getSurfaceSupportKHR(queue_family_index, surface); for (uint32_t queue_index = 0U; queue_index < queue_family_property.queueCount; ++queue_index) { if constexpr (bindingType == BindingType::Cpp) { queues[queue_family_index].emplace_back(*this, queue_family_index, queue_family_property, present_supported, queue_index); } else { queues[queue_family_index].emplace_back( *reinterpret_cast(this), queue_family_index, queue_family_property, present_supported, queue_index); } } } vkb::allocated::init(*this); if constexpr (bindingType == BindingType::Cpp) { command_pool = std::make_unique( *this, get_queue_by_flags_impl(vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute, 0).get_family_index()); fence_pool = std::make_unique(*this); } else { command_pool = std::make_unique( *reinterpret_cast(this), get_queue_by_flags_impl(vk::QueueFlagBits::eGraphics | vk::QueueFlagBits::eCompute, 0).get_family_index()); fence_pool = std::make_unique(*reinterpret_cast(this)); } } } // namespace core } // namespace vkb