From 1799ff01c4b5f307e79c0830cd89037c92c22317 Mon Sep 17 00:00:00 2001 From: Romain BOULLARD Date: Sun, 13 Sep 2026 07:58:29 +0200 Subject: [PATCH] Graphics device --- .../Graphics/GraphicsCore/GraphicsContext.cpp | 57 +++ .../Include/GraphicsCore/GraphicsContext.hpp | 108 ++++++ .../GraphicsInterface/GraphicsDevice.hpp | 75 ++++ .../GraphicsInterface/GraphicsInstance.hpp | 5 + .../GraphicsInterface/GraphicsQueue.hpp | 12 +- .../GraphicsInterface/GraphicsQueueType.fbs | 9 + .../GraphicsQueueType_generated.hpp | 83 +++++ .../GraphicsInterface/GraphicsSurface.hpp | 5 + .../Include/GraphicsVulkan/VulkanDevice.hpp | 80 ++++ .../Include/GraphicsVulkan/VulkanQueue.hpp | 6 + .../Graphics/GraphicsVulkan/VulkanDevice.cpp | 352 ++++++++++++++++++ .../GraphicsVulkan/VulkanInstance.cpp | 23 ++ .../Graphics/GraphicsVulkan/VulkanQueue.cpp | 20 + .../Graphics/GraphicsVulkan/VulkanSurface.cpp | 23 ++ .../Graphics/GraphicsCore/CMakeLists.txt | 3 +- .../Graphics/GraphicsCore/GraphicsContext.cpp | 62 +++ .../Graphics/GraphicsVulkan/VulkanDevice.cpp | 82 ++++ 17 files changed, 993 insertions(+), 12 deletions(-) create mode 100644 Bigfoot/Libs/Graphics/GraphicsCore/GraphicsContext.cpp create mode 100644 Bigfoot/Libs/Graphics/GraphicsCore/Include/GraphicsCore/GraphicsContext.hpp create mode 100644 Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsDevice.hpp create mode 100644 Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType.fbs create mode 100644 Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType_generated.hpp create mode 100644 Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanDevice.hpp create mode 100644 Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanDevice.cpp create mode 100644 Bigfoot/Tests/Graphics/GraphicsCore/GraphicsContext.cpp create mode 100644 Bigfoot/Tests/Graphics/GraphicsVulkan/VulkanDevice.cpp diff --git a/Bigfoot/Libs/Graphics/GraphicsCore/GraphicsContext.cpp b/Bigfoot/Libs/Graphics/GraphicsCore/GraphicsContext.cpp new file mode 100644 index 0000000..ed634af --- /dev/null +++ b/Bigfoot/Libs/Graphics/GraphicsCore/GraphicsContext.cpp @@ -0,0 +1,57 @@ +/********************************************************************* + * \file GraphicsContext.cpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#include + +#include + +namespace Bigfoot +{ +GraphicsContext::GraphicsContext(const GraphicsContextCreateInfo& p_createInfo) +{ + m_instance = g_graphicsInstanceFactory->Create(p_createInfo.m_api, + p_createInfo.m_applicationName, + p_createInfo.m_applicationVersion); + CRITICAL_ASSERT(GraphicsCoreAssertHandler, + m_instance != nullptr, + "No graphics instance registered for GraphicsAPI '{}'", + EnumNameGraphicsAPI(p_createInfo.m_api)); + + m_surface = g_graphicsSurfaceFactory->Create(p_createInfo.m_api, *m_instance, p_createInfo.m_surfaceCreateInfo); + CRITICAL_ASSERT(GraphicsCoreAssertHandler, + m_surface != nullptr, + "No graphics surface registered for GraphicsAPI '{}'", + EnumNameGraphicsAPI(p_createInfo.m_api)); + + const DeviceCreateInfo deviceCreateInfo {p_createInfo.m_deviceNameHint, m_surface.get()}; + m_device = g_graphicsDeviceFactory->Create(p_createInfo.m_api, *m_instance, deviceCreateInfo); + CRITICAL_ASSERT(GraphicsCoreAssertHandler, + m_device != nullptr, + "No graphics device registered for GraphicsAPI '{}'", + EnumNameGraphicsAPI(p_createInfo.m_api)); +} + +/****************************************************************************************/ + +GraphicsInstance& GraphicsContext::GetInstance() const +{ + return *m_instance; +} + +/****************************************************************************************/ + +GraphicsSurface& GraphicsContext::GetSurface() const +{ + return *m_surface; +} + +/****************************************************************************************/ + +GraphicsDevice& GraphicsContext::GetDevice() const +{ + return *m_device; +} +} // namespace Bigfoot diff --git a/Bigfoot/Libs/Graphics/GraphicsCore/Include/GraphicsCore/GraphicsContext.hpp b/Bigfoot/Libs/Graphics/GraphicsCore/Include/GraphicsCore/GraphicsContext.hpp new file mode 100644 index 0000000..861f33d --- /dev/null +++ b/Bigfoot/Libs/Graphics/GraphicsCore/Include/GraphicsCore/GraphicsContext.hpp @@ -0,0 +1,108 @@ +/********************************************************************* + * \file GraphicsContext.hpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#ifndef BIGFOOT_GRAPHICSCORE_GRAPHICSCONTEXT_HPP +#define BIGFOOT_GRAPHICSCORE_GRAPHICSCONTEXT_HPP +#include +#include +#include + +#include + +#include + +#include +#include + +namespace Bigfoot +{ +struct GraphicsContextCreateInfo +{ + /** + * The graphics API to use + */ + GraphicsAPI m_api = GraphicsAPI::Vulkan; + + /** + * The name of the application + */ + eastl::string_view m_applicationName; + + /** + * The version of the application + */ + Version m_applicationVersion; + + /** + * The native window handles + */ + SurfaceCreateInfo m_surfaceCreateInfo; + + /** + * Pick a specific physical device by name. + */ + eastl::string_view m_deviceNameHint; +}; + +class GraphicsContext +{ + public: + /** + * Constructor. + * + * \param p_createInfo The create info + */ + explicit GraphicsContext(const GraphicsContextCreateInfo& p_createInfo); + + GraphicsContext(const GraphicsContext& p_context) = delete; + GraphicsContext(GraphicsContext&& p_context) = delete; + + GraphicsContext& operator=(const GraphicsContext& p_context) = delete; + GraphicsContext& operator=(GraphicsContext&& p_context) = delete; + + /** + * Get the graphics instance + * + * \return The graphics instance + */ + [[nodiscard]] + GraphicsInstance& GetInstance() const; + + /** + * Get the graphics surface + * + * \return The graphics surface + */ + [[nodiscard]] + GraphicsSurface& GetSurface() const; + + /** + * Get the graphics device + * + * \return The graphics device + */ + [[nodiscard]] + GraphicsDevice& GetDevice() const; + + private: + /** + * The graphics instance. + */ + eastl::unique_ptr m_instance; + + /** + * The graphics surface. + */ + eastl::unique_ptr m_surface; + + /** + * The graphics device. + */ + eastl::unique_ptr m_device; +}; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsDevice.hpp b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsDevice.hpp new file mode 100644 index 0000000..4620098 --- /dev/null +++ b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsDevice.hpp @@ -0,0 +1,75 @@ +/********************************************************************* + * \file GraphicsDevice.hpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#ifndef BIGFOOT_GRAPHICSINTERFACE_GRAPHICSDEVICE_HPP +#define BIGFOOT_GRAPHICSINTERFACE_GRAPHICSDEVICE_HPP +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +namespace Bigfoot +{ + +struct DeviceCreateInfo +{ + /** + * When empty, the most suitable discrete GPU is preferred. + */ + eastl::string_view m_deviceNameHint; + + /** + * The surface the device must be able to present to. + */ + const GraphicsSurface* m_surface = nullptr; +}; + +class GraphicsDevice +{ + public: + virtual ~GraphicsDevice() = default; + + /** + * Get a queue of the given type + * + * \param p_type The kind of work the queue should support + * \return The queue, or nullptr if the device has no queue of this type + */ + [[nodiscard]] + virtual const GraphicsQueue* GetQueue(GraphicsQueueType p_type) const = 0; + + GraphicsDevice(const GraphicsDevice& p_device) = delete; + GraphicsDevice(GraphicsDevice&& p_device) = delete; + + GraphicsDevice& operator=(const GraphicsDevice& p_device) = delete; + GraphicsDevice& operator=(GraphicsDevice&& p_device) = delete; + + protected: + GraphicsDevice() = default; +}; + +/****************************************************************************************/ + +using GraphicsDeviceFactory = + Factory(const GraphicsInstance&, const DeviceCreateInfo&)>>; + +/** + * The global factory graphics libraries register themselves into, keyed by GraphicsAPI + */ +inline LazyGlobal g_graphicsDeviceFactory; +} // namespace Bigfoot + +#endif diff --git a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsInstance.hpp b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsInstance.hpp index 82b0420..67c60fe 100644 --- a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsInstance.hpp +++ b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsInstance.hpp @@ -7,6 +7,7 @@ #ifndef BIGFOOT_GRAPHICSINTERFACE_GRAPHICSINSTANCE_HPP #define BIGFOOT_GRAPHICSINTERFACE_GRAPHICSINSTANCE_HPP #include +#include #include #include @@ -38,6 +39,10 @@ using GraphicsInstanceFactory = Factory(eastl::string_view, Version)>>; + +/****************************************************************************************/ + +inline LazyGlobal g_graphicsInstanceFactory; } // namespace Bigfoot #endif diff --git a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueue.hpp b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueue.hpp index 1d6775f..701bdf0 100644 --- a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueue.hpp +++ b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueue.hpp @@ -6,20 +6,10 @@ *********************************************************************/ #ifndef BIGFOOT_GRAPHICSINTERFACE_GRAPHICSQUEUE_HPP #define BIGFOOT_GRAPHICSINTERFACE_GRAPHICSQUEUE_HPP +#include namespace Bigfoot { -/** - * The kind of work a GraphicsQueue submit - */ -enum class GraphicsQueueType -{ - Graphics, - Compute, - Transfer, - Present -}; - class GraphicsQueue { public: diff --git a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType.fbs b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType.fbs new file mode 100644 index 0000000..0308f4a --- /dev/null +++ b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType.fbs @@ -0,0 +1,9 @@ +namespace Bigfoot; + +enum GraphicsQueueType: byte +{ + Graphics, + Compute, + Transfer, + Present +} diff --git a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType_generated.hpp b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType_generated.hpp new file mode 100644 index 0000000..b6d172b --- /dev/null +++ b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsQueueType_generated.hpp @@ -0,0 +1,83 @@ +// automatically generated by the FlatBuffers compiler, do not modify + + +#ifndef FLATBUFFERS_GENERATED_GRAPHICSQUEUETYPE_BIGFOOT_H_ +#define FLATBUFFERS_GENERATED_GRAPHICSQUEUETYPE_BIGFOOT_H_ + +#include "flatbuffers/flatbuffers.h" + +// Ensure the included flatbuffers.h is the same version as when this file was +// generated, otherwise it may not be compatible. +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 12 && + FLATBUFFERS_VERSION_REVISION == 19, + "Non-compatible flatbuffers version included"); +// Ensure the included flatbuffers.h is Bigfoot's fork - stock +// flatbuffers (or a differently versioned Bigfoot fork) is not compatible. +#ifndef FLATBUFFERS_BIGFOOT_VERSION +#error "This file requires Bigfoot's flatbuffers fork - stock flatbuffers is not compatible" +#endif +static_assert(FLATBUFFERS_BIGFOOT_VERSION == 1, + "Non-compatible Bigfoot flatbuffers fork version included"); + +#include "EASTL/unique_ptr.h" +#include "EASTL/string.h" +#include "EASTL/vector.h" + +namespace Bigfoot { + +enum class GraphicsQueueType : int8_t { + Graphics = 0, + Compute = 1, + Transfer = 2, + Present = 3, + MIN = Graphics, + MAX = Present +}; + +inline const GraphicsQueueType (&EnumValuesGraphicsQueueType())[4] { + static const GraphicsQueueType values[] = { + GraphicsQueueType::Graphics, + GraphicsQueueType::Compute, + GraphicsQueueType::Transfer, + GraphicsQueueType::Present + }; + return values; +} + +inline const char * const *EnumNamesGraphicsQueueType() { + static const char * const names[5] = { + "Graphics", + "Compute", + "Transfer", + "Present", + nullptr + }; + return names; +} + +inline const char *EnumNameGraphicsQueueType(GraphicsQueueType e) { + if (::flatbuffers::IsOutRange(e, GraphicsQueueType::Graphics, GraphicsQueueType::Present)) return ""; + const size_t index = static_cast(e); + return EnumNamesGraphicsQueueType()[index]; +} + +inline const ::flatbuffers::TypeTable *GraphicsQueueTypeTypeTable() { + static const ::flatbuffers::TypeCode type_codes[] = { + { ::flatbuffers::ET_CHAR, 0, 0 }, + { ::flatbuffers::ET_CHAR, 0, 0 }, + { ::flatbuffers::ET_CHAR, 0, 0 }, + { ::flatbuffers::ET_CHAR, 0, 0 } + }; + static const ::flatbuffers::TypeFunction type_refs[] = { + Bigfoot::GraphicsQueueTypeTypeTable + }; + static const ::flatbuffers::TypeTable tt = { + ::flatbuffers::ST_ENUM, 4, type_codes, type_refs, nullptr, nullptr, nullptr + }; + return &tt; +} + +} // namespace Bigfoot + +#endif // FLATBUFFERS_GENERATED_GRAPHICSQUEUETYPE_BIGFOOT_H_ diff --git a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsSurface.hpp b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsSurface.hpp index b757f2f..1ffe8d3 100644 --- a/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsSurface.hpp +++ b/Bigfoot/Libs/Graphics/GraphicsInterface/Include/GraphicsInterface/GraphicsSurface.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -60,6 +61,10 @@ using GraphicsSurfaceFactory = Factory(const GraphicsInstance&, const SurfaceCreateInfo&)>>; + +/****************************************************************************************/ + +inline LazyGlobal g_graphicsSurfaceFactory; } // namespace Bigfoot #endif diff --git a/Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanDevice.hpp b/Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanDevice.hpp new file mode 100644 index 0000000..987e36f --- /dev/null +++ b/Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanDevice.hpp @@ -0,0 +1,80 @@ +/********************************************************************* + * \file VulkanDevice.hpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#ifndef BIGFOOT_GRAPHICSVULKAN_VULKANDEVICE_HPP +#define BIGFOOT_GRAPHICSVULKAN_VULKANDEVICE_HPP +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace Bigfoot::Vulkan +{ +class VulkanDevice final: public GraphicsDevice +{ + public: + /** + * Constructor. + * + * \param p_instance The instance to create the device from + * \param p_surface The surface the device must be able to present to. Can be null to create a device with no + * present queue + * \param p_deviceNameHint Pick a specific physical device by name. + */ + VulkanDevice(const VulkanInstance& p_instance, const VulkanSurface* p_surface, eastl::string_view p_deviceNameHint); + + ~VulkanDevice() override; + + /** + * Get a queue of the given type + * + * \param p_type The kind of work the queue should support + * \return The queue, or nullptr if the device has no queue of this type + */ + [[nodiscard]] + const GraphicsQueue* GetQueue(GraphicsQueueType p_type) const override; + + /** + * Get the underlying Vulkan physical device handle + * + * \return The vk::PhysicalDevice handle + */ + [[nodiscard]] + vk::PhysicalDevice GetPhysicalDeviceHandle() const; + + /** + * Get the underlying Vulkan device handle + * + * \return The vk::Device handle + */ + [[nodiscard]] + vk::Device GetHandle() const; + + private: + /** + * The physical device this device was created from + */ + vk::PhysicalDevice m_physicalDevice; + + /** + * The logical device + */ + vk::Device m_device; + + /** + * The queues. A slot is empty if the device has no queue of that type + */ + eastl::array, static_cast(GraphicsQueueType::MAX) + 1> m_queues; +}; +} // namespace Bigfoot::Vulkan + +#endif diff --git a/Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanQueue.hpp b/Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanQueue.hpp index 37f5ec4..c5f56b0 100644 --- a/Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanQueue.hpp +++ b/Bigfoot/Libs/Graphics/GraphicsVulkan/Include/GraphicsVulkan/VulkanQueue.hpp @@ -26,6 +26,12 @@ class VulkanQueue final: public GraphicsQueue */ VulkanQueue(vk::Queue p_queue, std::uint32_t p_familyIndex, GraphicsQueueType p_type); + VulkanQueue(const VulkanQueue& p_queue) = delete; + VulkanQueue(VulkanQueue&& p_queue) noexcept; + + VulkanQueue& operator=(const VulkanQueue& p_queue) = delete; + VulkanQueue& operator=(VulkanQueue&& p_queue) noexcept; + /** * Get the kind of work this queue can be submitted * diff --git a/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanDevice.cpp b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanDevice.cpp new file mode 100644 index 0000000..c1cbf4d --- /dev/null +++ b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanDevice.cpp @@ -0,0 +1,352 @@ +/********************************************************************* + * \file VulkanDevice.cpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace Bigfoot::Vulkan +{ +[[nodiscard]] +std::uint32_t GetPhysicalDeviceTypeRank(const vk::PhysicalDeviceType p_type) +{ + switch (p_type) + { + case vk::PhysicalDeviceType::eDiscreteGpu: + return 0; + case vk::PhysicalDeviceType::eIntegratedGpu: + return 1; + case vk::PhysicalDeviceType::eVirtualGpu: + return 2; + case vk::PhysicalDeviceType::eCpu: + return 3; + case vk::PhysicalDeviceType::eOther: + default: + return 4; + } +} + +/****************************************************************************************/ + +struct SelectedQueueFamilies +{ + eastl::optional m_graphics; + eastl::optional m_compute; + eastl::optional m_transfer; + eastl::optional m_present; +}; + +/****************************************************************************************/ + +[[nodiscard]] +eastl::optional FindQueueFamily(const std::span p_families, + const vk::QueueFlagBits p_required) +{ + eastl::optional best; + int bestBitCount = 0; + + for (std::uint32_t index = 0; index < p_families.size(); ++index) + { + const vk::QueueFlags flags = p_families[index].queueFlags; + if (!(flags & p_required)) + { + continue; + } + + const int bitCount = std::popcount(static_cast(flags)); + if (!best.has_value() || bitCount < bestBitCount) + { + best = index; + bestBitCount = bitCount; + } + } + + return best; +} + +/****************************************************************************************/ + +[[nodiscard]] +eastl::optional FindPresentQueueFamily(const vk::PhysicalDevice p_physicalDevice, + const vk::SurfaceKHR p_surface, + const std::uint32_t p_familyCount, + const eastl::optional p_graphicsFamily) +{ + if (p_graphicsFamily.has_value()) + { + const vk::ResultValue support = p_physicalDevice.getSurfaceSupportKHR(*p_graphicsFamily, p_surface); + if (support.result == vk::Result::eSuccess && support.value) + { + return *p_graphicsFamily; + } + } + + for (std::uint32_t index = 0; index < p_familyCount; ++index) + { + const vk::ResultValue support = p_physicalDevice.getSurfaceSupportKHR(index, p_surface); + if (support.result == vk::Result::eSuccess && support.value) + { + return index; + } + } + + return eastl::nullopt; +} + +/****************************************************************************************/ + +[[nodiscard]] +SelectedQueueFamilies SelectQueueFamilies(const vk::PhysicalDevice p_physicalDevice, const vk::SurfaceKHR p_surface) +{ + const std::vector families = p_physicalDevice.getQueueFamilyProperties(); + + SelectedQueueFamilies selected; + selected.m_graphics = FindQueueFamily(families, vk::QueueFlagBits::eGraphics); + selected.m_compute = FindQueueFamily(families, vk::QueueFlagBits::eCompute); + selected.m_transfer = FindQueueFamily(families, vk::QueueFlagBits::eTransfer); + if (!selected.m_transfer.has_value()) + { + // Per the Vulkan spec, any queue family supporting Graphics or Compute implicitly supports + // Transfer even when it doesn't explicitly report the bit, so the graphics family always works. + selected.m_transfer = selected.m_graphics; + } + + if (p_surface) + { + selected.m_present = FindPresentQueueFamily(p_physicalDevice, + p_surface, + static_cast(families.size()), + selected.m_graphics); + } + + return selected; +} + +/****************************************************************************************/ + +[[nodiscard]] +bool IsPhysicalDeviceSuitable(const SelectedQueueFamilies& p_families, const bool p_needsPresent) +{ + return p_families.m_graphics.has_value() && p_families.m_compute.has_value() && + (!p_needsPresent || p_families.m_present.has_value()); +} + +/****************************************************************************************/ + +struct PhysicalDeviceCandidate +{ + vk::PhysicalDevice m_handle; + vk::PhysicalDeviceProperties m_properties; + SelectedQueueFamilies m_queueFamilies; +}; + +/****************************************************************************************/ + +[[nodiscard]] +const PhysicalDeviceCandidate& SelectPhysicalDevice(const eastl::span p_candidates, + const eastl::string_view p_deviceNameHint) +{ + if (!p_deviceNameHint.empty()) + { + const auto it = eastl::find_if(p_candidates.begin(), + p_candidates.end(), + [p_deviceNameHint](const PhysicalDeviceCandidate& p_candidate) + { + return EqualNoCase(p_candidate.m_properties.deviceName.data(), + p_deviceNameHint); + }); + if (it != p_candidates.end()) + { + return *it; + } + + BIGFOOT_LOG_WARN(GRAPHICSVULKAN_LOGGER, + "No Vulkan device matching hint '{}' found, falling back to automatic selection", + p_deviceNameHint); + } + + return *eastl::min_element(p_candidates.begin(), + p_candidates.end(), + [](const PhysicalDeviceCandidate& p_lhs, const PhysicalDeviceCandidate& p_rhs) + { + return GetPhysicalDeviceTypeRank(p_lhs.m_properties.deviceType) < + GetPhysicalDeviceTypeRank(p_rhs.m_properties.deviceType); + }); +} + +/****************************************************************************************/ + +[[nodiscard]] +eastl::optional CreateQueue(const vk::Device p_device, + const eastl::optional p_familyIndex, + const GraphicsQueueType p_type) +{ + if (!p_familyIndex.has_value()) + { + return eastl::nullopt; + } + + return VulkanQueue {p_device.getQueue(*p_familyIndex, 0), *p_familyIndex, p_type}; +} + +/****************************************************************************************/ + +VulkanDevice::VulkanDevice(const VulkanInstance& p_instance, + const VulkanSurface* p_surface, + const eastl::string_view p_deviceNameHint) +{ + const vk::SurfaceKHR surfaceHandle = p_surface != nullptr ? p_surface->GetHandle() : vk::SurfaceKHR {}; + + const vk::ResultValue> physicalDevices = p_instance.GetHandle() + .enumeratePhysicalDevices(); + CRITICAL_ASSERT(GraphicsVulkanAssertHandler, + physicalDevices.result == vk::Result::eSuccess, + "({}) Failed to enumerate Vulkan physical devices", + physicalDevices.result); + + eastl::vector candidates; + for (const vk::PhysicalDevice physicalDevice: physicalDevices.value) + { + SelectedQueueFamilies queueFamilies = SelectQueueFamilies(physicalDevice, surfaceHandle); + if (!IsPhysicalDeviceSuitable(queueFamilies, static_cast(surfaceHandle))) + { + continue; + } + + candidates.push_back({physicalDevice, physicalDevice.getProperties(), eastl::move(queueFamilies)}); + } + CRITICAL_ASSERT(GraphicsVulkanAssertHandler, !candidates.empty(), "No suitable Vulkan physical device found"); + + const PhysicalDeviceCandidate& selected = SelectPhysicalDevice(candidates, p_deviceNameHint); + m_physicalDevice = selected.m_handle; + + BIGFOOT_LOG_INFO(GRAPHICSVULKAN_LOGGER, + "Selected Vulkan physical device '{}' ({})", + selected.m_properties.deviceName.data(), + vk::to_string(selected.m_properties.deviceType)); + + eastl::vector uniqueFamilies; + for (const eastl::optional family: {selected.m_queueFamilies.m_graphics, + selected.m_queueFamilies.m_compute, + selected.m_queueFamilies.m_transfer, + selected.m_queueFamilies.m_present}) + { + if (family.has_value() && + eastl::find(uniqueFamilies.begin(), uniqueFamilies.end(), *family) == uniqueFamilies.end()) + { + uniqueFamilies.push_back(*family); + } + } + + constexpr float queuePriority = 1.0f; + eastl::vector queueCreateInfos; + queueCreateInfos.reserve(uniqueFamilies.size()); + for (const std::uint32_t family: uniqueFamilies) + { + queueCreateInfos.push_back({{}, family, 1u, &queuePriority}); + } + + eastl::vector enabledExtensions; + if (surfaceHandle) + { + enabledExtensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + } + + const vk::DeviceCreateInfo deviceCreateInfo {{}, queueCreateInfos, {}, enabledExtensions}; + + vk::ResultValue device {vk::Result::eSuccess, vk::Device {}}; + { + BIGFOOT_LSAN_ONLY(const __lsan::ScopedDisabler lsanDisabler;) + device = m_physicalDevice.createDevice(deviceCreateInfo); + } + CRITICAL_ASSERT(GraphicsVulkanAssertHandler, + device.result == vk::Result::eSuccess, + "({}) Failed to create Vulkan device", + device.result); + m_device = device.value; + + m_queues[static_cast(GraphicsQueueType::Graphics)] = CreateQueue(m_device, + selected.m_queueFamilies.m_graphics, + GraphicsQueueType::Graphics); + m_queues[static_cast(GraphicsQueueType::Compute)] = CreateQueue(m_device, + selected.m_queueFamilies.m_compute, + GraphicsQueueType::Compute); + m_queues[static_cast(GraphicsQueueType::Transfer)] = CreateQueue(m_device, + selected.m_queueFamilies.m_transfer, + GraphicsQueueType::Transfer); + m_queues[static_cast(GraphicsQueueType::Present)] = CreateQueue(m_device, + selected.m_queueFamilies.m_present, + GraphicsQueueType::Present); +} + +/****************************************************************************************/ + +VulkanDevice::~VulkanDevice() +{ + m_device.destroy(); +} + +/****************************************************************************************/ + +const GraphicsQueue* VulkanDevice::GetQueue(const GraphicsQueueType p_type) const +{ + const eastl::optional& queue = m_queues[static_cast(p_type)]; + return queue.has_value() ? &*queue : nullptr; +} + +/****************************************************************************************/ + +vk::PhysicalDevice VulkanDevice::GetPhysicalDeviceHandle() const +{ + return m_physicalDevice; +} + +/****************************************************************************************/ + +vk::Device VulkanDevice::GetHandle() const +{ + return m_device; +} + +/****************************************************************************************/ + +[[nodiscard]] +eastl::unique_ptr CreateVulkanDevice(const GraphicsInstance& p_instance, + const DeviceCreateInfo& p_createInfo) +{ + return eastl::make_unique(static_cast(p_instance), + static_cast(p_createInfo.m_surface), + p_createInfo.m_deviceNameHint); +} + +/****************************************************************************************/ + +[[nodiscard]] +bool RegisterVulkanDeviceFactory() +{ + const bool inserted = g_graphicsDeviceFactory->Register(GraphicsAPI::Vulkan, &CreateVulkanDevice); + CRITICAL_ASSERT(GraphicsVulkanAssertHandler, inserted, "Failed to register the Vulkan graphics device factory"); + return inserted; +} + +/****************************************************************************************/ + +const bool g_vulkanDeviceFactoryRegistered = RegisterVulkanDeviceFactory(); +} // namespace Bigfoot::Vulkan diff --git a/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanInstance.cpp b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanInstance.cpp index 0879020..7dda353 100644 --- a/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanInstance.cpp +++ b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanInstance.cpp @@ -163,4 +163,27 @@ vk::Instance VulkanInstance::GetHandle() const { return m_instance; } + +/****************************************************************************************/ + +[[nodiscard]] +eastl::unique_ptr CreateVulkanInstance(const eastl::string_view p_applicationName, + const Version p_applicationVersion) +{ + return eastl::make_unique(p_applicationName, p_applicationVersion); +} + +/****************************************************************************************/ + +[[nodiscard]] +bool RegisterVulkanInstanceFactory() +{ + const bool inserted = g_graphicsInstanceFactory->Register(GraphicsAPI::Vulkan, &CreateVulkanInstance); + CRITICAL_ASSERT(GraphicsVulkanAssertHandler, inserted, "Failed to register the Vulkan graphics instance factory"); + return inserted; +} + +/****************************************************************************************/ + +const bool g_vulkanInstanceFactoryRegistered = RegisterVulkanInstanceFactory(); } // namespace Bigfoot::Vulkan diff --git a/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanQueue.cpp b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanQueue.cpp index 649d03a..69b4723 100644 --- a/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanQueue.cpp +++ b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanQueue.cpp @@ -17,6 +17,26 @@ VulkanQueue::VulkanQueue(const vk::Queue p_queue, const std::uint32_t p_familyIn /****************************************************************************************/ +VulkanQueue::VulkanQueue(VulkanQueue&& p_queue) noexcept: + GraphicsQueue(), + m_queue(p_queue.m_queue), + m_familyIndex(p_queue.m_familyIndex), + m_type(p_queue.m_type) +{ +} + +/****************************************************************************************/ + +VulkanQueue& VulkanQueue::operator=(VulkanQueue&& p_queue) noexcept +{ + m_queue = p_queue.m_queue; + m_familyIndex = p_queue.m_familyIndex; + m_type = p_queue.m_type; + return *this; +} + +/****************************************************************************************/ + GraphicsQueueType VulkanQueue::GetType() const { return m_type; diff --git a/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanSurface.cpp b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanSurface.cpp index 725de49..79691a3 100644 --- a/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanSurface.cpp +++ b/Bigfoot/Libs/Graphics/GraphicsVulkan/VulkanSurface.cpp @@ -44,4 +44,27 @@ vk::SurfaceKHR VulkanSurface::GetHandle() const { return m_surface; } + +/****************************************************************************************/ + +[[nodiscard]] +eastl::unique_ptr CreateVulkanSurface(const GraphicsInstance& p_instance, + const SurfaceCreateInfo& p_surfaceCreateInfo) +{ + return eastl::make_unique(static_cast(p_instance), p_surfaceCreateInfo); +} + +/****************************************************************************************/ + +[[nodiscard]] +bool RegisterVulkanSurfaceFactory() +{ + const bool inserted = g_graphicsSurfaceFactory->Register(GraphicsAPI::Vulkan, &CreateVulkanSurface); + CRITICAL_ASSERT(GraphicsVulkanAssertHandler, inserted, "Failed to register the Vulkan graphics surface factory"); + return inserted; +} + +/****************************************************************************************/ + +const bool g_vulkanSurfaceFactoryRegistered = RegisterVulkanSurfaceFactory(); } // namespace Bigfoot::Vulkan diff --git a/Bigfoot/Tests/Graphics/GraphicsCore/CMakeLists.txt b/Bigfoot/Tests/Graphics/GraphicsCore/CMakeLists.txt index 5a2714f..3bda171 100644 --- a/Bigfoot/Tests/Graphics/GraphicsCore/CMakeLists.txt +++ b/Bigfoot/Tests/Graphics/GraphicsCore/CMakeLists.txt @@ -1,7 +1,8 @@ get_filename_component(PackageName ${CMAKE_CURRENT_SOURCE_DIR} NAME) project(${PackageName}Tests) -set(Dependencies) +set(Dependencies + SDL3::SDL3) set(BigfootDependencies) bigfoot_create_package_tests( diff --git a/Bigfoot/Tests/Graphics/GraphicsCore/GraphicsContext.cpp b/Bigfoot/Tests/Graphics/GraphicsCore/GraphicsContext.cpp new file mode 100644 index 0000000..f6b7172 --- /dev/null +++ b/Bigfoot/Tests/Graphics/GraphicsCore/GraphicsContext.cpp @@ -0,0 +1,62 @@ +/********************************************************************* + * \file GraphicsContext.cpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#include + +#include + +#include +#include + +namespace Bigfoot +{ +class GraphicsContextFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + SDL_Init(SDL_INIT_VIDEO); + + m_window = SDL_CreateWindow("GraphicsContextTests", 64, 64, SDL_WINDOW_HIDDEN | SDL_WINDOW_VULKAN); + + const SDL_PropertiesID windowProperties = SDL_GetWindowProperties(m_window); + + m_createInfo.m_applicationName = "GraphicsContextTests"; + m_createInfo.m_applicationVersion = Version {1, 0, 0}; +#ifdef BIGFOOT_WINDOWS + m_createInfo.m_surfaceCreateInfo.m_windowHandle = static_cast( + SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr)); + m_createInfo.m_surfaceCreateInfo.m_windowInstance = GetModuleHandle(nullptr); +#elif defined BIGFOOT_LINUX + m_createInfo.m_surfaceCreateInfo.m_display = static_cast( + SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr)); + m_createInfo.m_surfaceCreateInfo.m_surface = static_cast( + SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr)); +#endif + } + + void TearDown() override + { + SDL_DestroyWindow(m_window); + SDL_Quit(); + } + + SDL_Window* m_window = nullptr; + GraphicsContextCreateInfo m_createInfo; +}; + +/****************************************************************************************/ + +TEST_F(GraphicsContextFixture, Constructor_ShouldCreateAValidInstanceSurfaceAndDevice) +{ + const GraphicsContext context {m_createInfo}; + + EXPECT_NE(context.GetDevice().GetQueue(GraphicsQueueType::Graphics), nullptr); + EXPECT_NE(context.GetDevice().GetQueue(GraphicsQueueType::Compute), nullptr); + EXPECT_NE(context.GetDevice().GetQueue(GraphicsQueueType::Transfer), nullptr); + EXPECT_NE(context.GetDevice().GetQueue(GraphicsQueueType::Present), nullptr); +} +} // namespace Bigfoot diff --git a/Bigfoot/Tests/Graphics/GraphicsVulkan/VulkanDevice.cpp b/Bigfoot/Tests/Graphics/GraphicsVulkan/VulkanDevice.cpp new file mode 100644 index 0000000..81bbe26 --- /dev/null +++ b/Bigfoot/Tests/Graphics/GraphicsVulkan/VulkanDevice.cpp @@ -0,0 +1,82 @@ +/********************************************************************* + * \file VulkanDevice.cpp + * + * \author Romain BOULLARD + * \date September 2026 + *********************************************************************/ +#include + +#include +#include + +#include + +#include +#include + +namespace Bigfoot::Vulkan +{ +class VulkanDeviceFixture: public ::testing::Test +{ + protected: + void SetUp() override + { + SDL_Init(SDL_INIT_VIDEO); + + m_window = SDL_CreateWindow("VulkanDeviceTests", 64, 64, SDL_WINDOW_HIDDEN | SDL_WINDOW_VULKAN); + + const SDL_PropertiesID windowProperties = SDL_GetWindowProperties(m_window); + +#ifdef BIGFOOT_WINDOWS + m_surfaceCreateInfo.m_windowHandle = static_cast( + SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr)); + m_surfaceCreateInfo.m_windowInstance = GetModuleHandle(nullptr); +#elif defined BIGFOOT_LINUX + m_surfaceCreateInfo.m_display = static_cast( + SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, nullptr)); + m_surfaceCreateInfo.m_surface = static_cast( + SDL_GetPointerProperty(windowProperties, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, nullptr)); +#endif + } + + void TearDown() override + { + SDL_DestroyWindow(m_window); + SDL_Quit(); + } + + VulkanInstance m_instance {"VulkanDeviceTests", Version {1, 0, 0}}; + SDL_Window* m_window = nullptr; + SurfaceCreateInfo m_surfaceCreateInfo; +}; + +/****************************************************************************************/ + +TEST_F(VulkanDeviceFixture, WithoutSurface_ShouldCreateAValidDeviceWithoutAPresentQueue) +{ + const VulkanDevice device {m_instance, nullptr, {}}; + + EXPECT_TRUE(device.GetPhysicalDeviceHandle()); + EXPECT_TRUE(device.GetHandle()); + EXPECT_NE(device.GetQueue(GraphicsQueueType::Graphics), nullptr); + EXPECT_NE(device.GetQueue(GraphicsQueueType::Compute), nullptr); + EXPECT_NE(device.GetQueue(GraphicsQueueType::Transfer), nullptr); + EXPECT_EQ(device.GetQueue(GraphicsQueueType::Present), nullptr); +} + +/****************************************************************************************/ + +TEST_F(VulkanDeviceFixture, WithSurface_ShouldCreateAValidDeviceWithAPresentQueue) +{ + const VulkanSurface surface {m_instance, m_surfaceCreateInfo}; + + const VulkanDevice device {m_instance, &surface, {}}; + + EXPECT_TRUE(device.GetPhysicalDeviceHandle()); + EXPECT_TRUE(device.GetHandle()); + EXPECT_NE(device.GetQueue(GraphicsQueueType::Graphics), nullptr); + EXPECT_NE(device.GetQueue(GraphicsQueueType::Compute), nullptr); + EXPECT_NE(device.GetQueue(GraphicsQueueType::Transfer), nullptr); + EXPECT_NE(device.GetQueue(GraphicsQueueType::Present), nullptr); +} +} // namespace Bigfoot::Vulkan