diff --git a/engine/include/nova/render/render_driver.h b/engine/include/nova/render/render_driver.h index 8adbb96..1c69146 100644 --- a/engine/include/nova/render/render_driver.h +++ b/engine/include/nova/render/render_driver.h @@ -30,7 +30,9 @@ namespace Nova { [[nodiscard]] virtual bool get_device_supports_surface(u32 index, SurfaceID surface) const = 0; virtual void select_device(u32 index) = 0; - [[nodiscard]] virtual QueueID get_queue() = 0; + [[nodiscard]] virtual u32 choose_queue_family(QueueType type, SurfaceID surface) = 0; + [[nodiscard]] virtual QueueID get_queue(u32 queue_family) = 0; + virtual void free_queue(QueueID queue) = 0; [[nodiscard]] virtual SurfaceID create_surface(WindowID window) = 0; virtual void destroy_surface(SurfaceID surface) = 0; diff --git a/engine/include/nova/render/render_fwd.h b/engine/include/nova/render/render_fwd.h index 9e74732..2fccdff 100644 --- a/engine/include/nova/render/render_fwd.h +++ b/engine/include/nova/render/render_fwd.h @@ -12,6 +12,7 @@ namespace Nova { enum class InputRate { VERTEX, INSTANCE }; enum class PipelineType { GRAPHICS, COMPUTE }; enum class PrimitiveTopology { POINT_LIST, LINE_LIST, LINE_STRIP, TRIANGLE_LIST, TRIANGLE_STRIP }; + enum class QueueType { UNDEFINED, GRAPHICS, COMPUTE, TRANSFER }; enum class RenderAPI { DX12, VULKAN }; enum class ShaderStage { VERTEX, FRAGMENT, GEOMETRY, TESS_CONTROL, TESS_EVAL, COMPUTE, MESH, TASK }; diff --git a/engine/src/drivers/vulkan/render_driver.cpp b/engine/src/drivers/vulkan/render_driver.cpp index 2e40e3b..24bda1e 100644 --- a/engine/src/drivers/vulkan/render_driver.cpp +++ b/engine/src/drivers/vulkan/render_driver.cpp @@ -16,11 +16,12 @@ #include #include +#include #include #include -#include #define VALIDATION_LAYER "VK_LAYER_KHRONOS_validation" +#define MAX_QUEUES_PER_FAMILY 2U using namespace Nova; @@ -61,6 +62,13 @@ static constexpr VkVertexInputRate VK_VERTEX_INPUT_RATE_MAP[] = { VK_VERTEX_INPUT_RATE_INSTANCE }; +static constexpr VkQueueFlagBits VK_QUEUE_FLAGS_MAP[] = { + static_cast(0), + VK_QUEUE_GRAPHICS_BIT, + VK_QUEUE_COMPUTE_BIT, + VK_QUEUE_TRANSFER_BIT +}; + // clang-format on static constexpr VkFormat VK_FORMAT_MAP[] = { @@ -375,13 +383,64 @@ void VulkanRenderDriver::select_device(const u32 p_index) { _init_device(queues); } -QueueID VulkanRenderDriver::get_queue() { +u32 VulkanRenderDriver::choose_queue_family(QueueType p_type, SurfaceID p_surface) { NOVA_AUTO_TRACE(); - NOVA_ASSERT(m_device); - // TODO: Actually create/get a queue - Queue* queue = new Queue(); - queue->family_index = 0; - return queue; + NOVA_ASSERT(!m_queue_families.empty()); + + const VkQueueFlags mask = VK_QUEUE_FLAGS_MAP[static_cast(p_type)]; + + u32 best_index = std::numeric_limits::max(); + u32 best_score = std::numeric_limits::max(); + + for (const auto [index, flags] : m_queue_families) { + if ((flags & mask) != mask) { + continue; + } + if (p_surface) { + VkBool32 supports_surface; + vkGetPhysicalDeviceSurfaceSupportKHR(m_physical_device, index, p_surface->handle, &supports_surface); + if (!supports_surface) { + continue; + } + } + + u32 score = std::popcount(flags); + if (score < best_score) { + best_index = index; + best_score = score; + } + } + + return best_index; +} + +QueueID VulkanRenderDriver::get_queue(u32 p_queue_family) { + NOVA_AUTO_TRACE(); + NOVA_ASSERT(m_queue_families.contains(p_queue_family)); + + QueueID best_queue = nullptr; + u32 best_usage = std::numeric_limits::max(); + + for (Queue& queue : m_queues) { + if (queue.family_index != p_queue_family) { + continue; + } + if (queue.usage_count < best_usage) { + best_queue = &queue; + best_usage = queue.usage_count; + } + } + + if (best_queue) { + best_queue->usage_count++; + } + return best_queue; +} + +void VulkanRenderDriver::free_queue(QueueID p_queue) { + NOVA_AUTO_TRACE(); + NOVA_ASSERT(p_queue); + p_queue->usage_count--; } SurfaceID VulkanRenderDriver::create_surface(WindowID p_window) { @@ -1113,7 +1172,7 @@ void VulkanRenderDriver::_check_device_capabilities() { // TODO: Check device capabilities } -void VulkanRenderDriver::_init_queues(std::vector& p_queues) const { +void VulkanRenderDriver::_init_queues(std::vector& p_queues) { NOVA_AUTO_TRACE(); u32 count; @@ -1136,13 +1195,21 @@ void VulkanRenderDriver::_init_queues(std::vector& p_qu NOVA_LOG("Using queue family: {}", i); found |= available[i].queueFlags; - VkDeviceQueueCreateInfo queue {}; - queue.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; - queue.queueFamilyIndex = i; - queue.queueCount = 1; - queue.pQueuePriorities = &s_priority; + VkDeviceQueueCreateInfo create {}; + create.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + create.queueFamilyIndex = i; + create.queueCount = std::min(MAX_QUEUES_PER_FAMILY, available[i].queueCount); + create.pQueuePriorities = &s_priority; - p_queues.push_back(queue); + p_queues.push_back(create); + m_queue_families[i] = available[i].queueFlags; + + for (u32 j = 0; j < create.queueCount; j++) { + Queue queue; + queue.family_index = i; + queue.queue_index = j; + m_queues.push_back(queue); + } } if ((found & QUEUE_MASK) != QUEUE_MASK) { @@ -1162,12 +1229,15 @@ void VulkanRenderDriver::_init_device(const std::vector create.queueCreateInfoCount = static_cast(p_queues.size()); create.pQueueCreateInfos = p_queues.data(); create.pEnabledFeatures = &m_features; - // TODO: pNext for additional features if (vkCreateDevice(m_physical_device, &create, get_allocator(VK_OBJECT_TYPE_DEVICE), &m_device) != VK_SUCCESS) { throw std::runtime_error("Failed to create VkDevice"); } + for (Queue& queue : m_queues) { + vkGetDeviceQueue(m_device, queue.family_index, queue.queue_index, &queue.handle); + } + NOVA_LOG("VkDevice created"); } diff --git a/engine/src/drivers/vulkan/render_driver.h b/engine/src/drivers/vulkan/render_driver.h index ea35063..b9217b6 100644 --- a/engine/src/drivers/vulkan/render_driver.h +++ b/engine/src/drivers/vulkan/render_driver.h @@ -11,6 +11,7 @@ #include #include +#include #include namespace Nova { @@ -30,7 +31,10 @@ namespace Nova { }; struct Queue { + VkQueue handle = VK_NULL_HANDLE; u32 family_index; + u32 queue_index; + u32 usage_count = 0; }; struct RenderPass { @@ -76,7 +80,9 @@ namespace Nova { [[nodiscard]] bool get_device_supports_surface(u32 index, SurfaceID surface) const override; void select_device(u32 index) override; - [[nodiscard]] QueueID get_queue() override; + [[nodiscard]] u32 choose_queue_family(QueueType type, SurfaceID surface) override; + [[nodiscard]] QueueID get_queue(u32 queue_family) override; + void free_queue(QueueID queue) override; [[nodiscard]] SurfaceID create_surface(WindowID window) override; void destroy_surface(SurfaceID surface) override; @@ -117,6 +123,8 @@ namespace Nova { std::vector m_layers; std::vector m_device_extensions; std::vector m_devices; + std::vector m_queues; + std::unordered_map m_queue_families; void _check_version() const; void _check_extensions(); @@ -127,7 +135,7 @@ namespace Nova { void _check_device_extensions(); void _check_device_features(); void _check_device_capabilities(); - void _init_queues(std::vector& queues) const; + void _init_queues(std::vector& queues); void _init_device(const std::vector& queues); }; } // namespace Nova