From 9bc8bd33d718d1d4ec302c82c1d67a0e0ef9c9f4 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 00:43:31 +0200 Subject: [PATCH 01/48] Start testing Vulkan --- engine/src/VulkanTest.cpp | 97 +++++++++++++++++++++++++++++++++++ engine/src/VulkanTest.h | 34 ++++++++++++ engine/windows/WinSandbox.cpp | 2 +- xmake.lua | 15 +++++- 4 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 engine/src/VulkanTest.cpp create mode 100644 engine/src/VulkanTest.h diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp new file mode 100644 index 0000000..0c65f19 --- /dev/null +++ b/engine/src/VulkanTest.cpp @@ -0,0 +1,97 @@ +#include "pch/enginepch.h" +#include "VulkanTest.h" + +int main() { + VulkanTest app; + + try { + app.run(); + } catch (const std::exception& e) { + std::cerr << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + +void VulkanTest::initWindow() { + glfwInit(); + + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); + + window = glfwCreateWindow(1920, 1080, "Vulkan", nullptr, nullptr); +} + +void VulkanTest::initVulkan() { + createInstance(); +} +void VulkanTest::mainLoop() { + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + } +} + +void VulkanTest::createInstance() { + if (enableValidationLayers && !checkValidationLayerSupport()) { + throw std::runtime_error("validation layers requested, but not available!"); + } + + vk::ApplicationInfo appInfo{}; + appInfo.sType = vk::StructureType::eApplicationInfo; + appInfo.pApplicationName = "Hello Triangle"; + appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.pEngineName = "No Engine"; + appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); + appInfo.apiVersion = VK_API_VERSION_1_0; + + vk::InstanceCreateInfo createInfo{}; + createInfo.sType = vk::StructureType::eInstanceCreateInfo; + createInfo.pApplicationInfo = &appInfo; + + uint32_t glfwExtensionCount = 0; + const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); + + createInfo.enabledExtensionCount = glfwExtensionCount; + createInfo.ppEnabledExtensionNames = glfwExtensions; + createInfo.enabledLayerCount = 0; + + if (vk::createInstance(&createInfo, nullptr, &instance) != vk::Result::eSuccess) { + throw std::runtime_error("failed to create instance!"); + } +} + +bool VulkanTest::checkValidationLayerSupport() const { + #ifdef VK_ADD_LAYER_PATH and DE_PLATFORM_WINDOWS + _putenv_s("VK_ADD_LAYER_PATH", VK_ADD_LAYER_PATH); + #endif + + uint32_t layerCount; + vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + + std::vector availableLayers(layerCount); + vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + + for (const char* layerName : validationLayers) { + bool layerFound = false; + + for (const auto& layerProperties : availableLayers) { + if (strcmp(layerName, layerProperties.layerName) == 0) { + layerFound = true; + break; + } + } + + if (!layerFound) { + return false; + } + } + + return true; +} + +void VulkanTest::cleanup() const { + instance.destroy(); + glfwDestroyWindow(window); + glfwTerminate(); +} diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h new file mode 100644 index 0000000..bed0005 --- /dev/null +++ b/engine/src/VulkanTest.h @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +class VulkanTest { +public: + void run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); + } + +private: + void initWindow(); + void initVulkan(); + void cleanup() const; + void mainLoop(); + + void createInstance(); + + bool checkValidationLayerSupport() const; + + GLFWwindow* window; + vk::Instance instance; + + const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; + +#ifdef DE_DEBUG + const bool enableValidationLayers = true; +#else + const bool enableValidationLayers = false; +#endif +}; diff --git a/engine/windows/WinSandbox.cpp b/engine/windows/WinSandbox.cpp index 0aba24e..83ad321 100644 --- a/engine/windows/WinSandbox.cpp +++ b/engine/windows/WinSandbox.cpp @@ -5,7 +5,7 @@ #include -int main(const int argc, char* argv[]) { +int mainOld(const int argc, char* argv[]) { const CefMainArgs mainArgs; const CefRefPtr osrApp = new OSRCefApp(); diff --git a/xmake.lua b/xmake.lua index 92e84c2..ac7346b 100644 --- a/xmake.lua +++ b/xmake.lua @@ -5,6 +5,9 @@ add_repositories("dicy-xmake-registry https://github.com/Dicy/xmake-registry") add_requires("spdlog v1.14.1") add_requires("glfw 3.4") add_requires("glm 1.0.1") +add_requires("vulkan-headers 1.4.309+0") +add_requires("vulkan-loader 1.4.309+0") +add_requires("vulkan-validationlayers 1.4.309+0") add_requires("entt v3.13.2") add_requires("assimp v5.4.3") add_requires("toml++ v3.4.0") @@ -41,12 +44,20 @@ target("engine") end end) - add_packages("spdlog", "glfw", "glm", "entt", "assimp", "toml++", "chromium-embedded-framework", "joltphysics") + add_packages("spdlog", "glfw", "glm", "vulkan-headers", "vulkan-loader", "vulkan-validationlayers", "entt", "assimp", "toml++", "chromium-embedded-framework", "joltphysics") add_deps("glad", "stb") add_defines("DE_IS_ENGINE", "GLM_ENABLE_EXPERIMENTAL", "NDEBUG") if is_mode("debug") then add_defines("DE_DEBUG") + -- Pass VK_ADD_LAYER_PATH from environment to the compiler as the runtime path is not loaded by the IDE + after_load(function (target) + if os.getenv("VK_ADD_LAYER_PATH") then + local vk_layer_path = os.getenv("VK_ADD_LAYER_PATH") + vk_layer_path = vk_layer_path:gsub("\\", "\\\\") + target:add("defines", "VK_ADD_LAYER_PATH=\"" .. vk_layer_path .. "\"") + end + end) else add_defines("DE_RELEASE") end @@ -90,7 +101,7 @@ if is_plat("macosx") then set_kind("binary") set_languages("cxx23") add_rules("xcode.application") - set_targetdir("$(buildir)/$(plat)/$(arch)/$(mode)/DicyEngine.app/Contents/Frameworks") + set_targetdir("$(builddir)/$(plat)/$(arch)/$(mode)/DicyEngine.app/Contents/Frameworks") add_frameworks("AppKit", "Cocoa", "IOSurface") add_packages("chromium-embedded-framework") From 2a7c227f11eb00c90504a669066dfb4b14282871 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 11:03:19 +0200 Subject: [PATCH 02/48] Fix compiler directive --- engine/src/VulkanTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index 0c65f19..1cd995f 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -62,7 +62,7 @@ void VulkanTest::createInstance() { } bool VulkanTest::checkValidationLayerSupport() const { - #ifdef VK_ADD_LAYER_PATH and DE_PLATFORM_WINDOWS + #if defined(VK_ADD_LAYER_PATH) && defined(DE_PLATFORM_WINDOWS) _putenv_s("VK_ADD_LAYER_PATH", VK_ADD_LAYER_PATH); #endif From e8937952b586c665b76e38301d2daef3d0571c57 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 11:13:51 +0200 Subject: [PATCH 03/48] Only enable validations layers if not on windows or the path is defined --- engine/src/VulkanTest.cpp | 12 ++++++++---- engine/src/VulkanTest.h | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index 1cd995f..0c452c5 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -57,7 +57,7 @@ void VulkanTest::createInstance() { createInfo.enabledLayerCount = 0; if (vk::createInstance(&createInfo, nullptr, &instance) != vk::Result::eSuccess) { - throw std::runtime_error("failed to create instance!"); + throw std::runtime_error("Failed to create instance!"); } } @@ -67,10 +67,14 @@ bool VulkanTest::checkValidationLayerSupport() const { #endif uint32_t layerCount; - vkEnumerateInstanceLayerProperties(&layerCount, nullptr); + if (vk::enumerateInstanceLayerProperties(&layerCount, nullptr) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to enumerate instance layer properties!"); + } - std::vector availableLayers(layerCount); - vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); + std::vector availableLayers(layerCount); + if (vk::enumerateInstanceLayerProperties(&layerCount, availableLayers.data()) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to enumerate instance layer properties!"); + } for (const char* layerName : validationLayers) { bool layerFound = false; diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h index bed0005..2d53d21 100644 --- a/engine/src/VulkanTest.h +++ b/engine/src/VulkanTest.h @@ -26,7 +26,7 @@ class VulkanTest { const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; -#ifdef DE_DEBUG +#if defined(DE_DEBUG) && (!defined(DE_PLATFORM_WINDOWS) || defined(VK_ADD_LAYER_PATH)) const bool enableValidationLayers = true; #else const bool enableValidationLayers = false; From 37a7ce7425c6265f3f5a68885f79fca27e075108 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 12:31:44 +0200 Subject: [PATCH 04/48] Update naming conventions --- vsxmake2022/DicyEngine.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/vsxmake2022/DicyEngine.sln.DotSettings b/vsxmake2022/DicyEngine.sln.DotSettings index e141d2b..f86e2c7 100644 --- a/vsxmake2022/DicyEngine.sln.DotSettings +++ b/vsxmake2022/DicyEngine.sln.DotSettings @@ -54,6 +54,7 @@ VM <NamingElement Priority="13" Title="Union members"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="union member" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></NamingElement> <NamingElement Priority="2" Title="Concepts"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="concept" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> + <NamingElement Priority="1" Title="Classes and structs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="__interface" /><type Name="class" /><type Name="struct" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> <NamingElement Priority="14" Title="Enum members"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="scoped enumerator" /><type Name="unscoped enumerator" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></NamingElement> <NamingElement Priority="4" Title="Unions"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="union" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> <NamingElement Priority="18" Title="Typedefs"><Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"><type Name="type alias" /><type Name="typedef" /></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></NamingElement> From 442f79e0dfbcd3d960d3607646e10e4d3918a0ee Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 12:33:03 +0200 Subject: [PATCH 05/48] Create surface, logical device, queues... --- engine/src/VulkanTest.cpp | 136 +++++++++++++++++++++++++++++++++++++- engine/src/VulkanTest.h | 30 +++++++-- 2 files changed, 156 insertions(+), 10 deletions(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index 0c452c5..bca8dbc 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -14,6 +14,13 @@ int main() { return EXIT_SUCCESS; } +void VulkanTest::run() { + initWindow(); + initVulkan(); + mainLoop(); + cleanup(); +} + void VulkanTest::initWindow() { glfwInit(); @@ -25,7 +32,11 @@ void VulkanTest::initWindow() { void VulkanTest::initVulkan() { createInstance(); + createSurface(); + pickPhysicalDevice(); + createLogicalDevice(); } + void VulkanTest::mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); @@ -54,7 +65,12 @@ void VulkanTest::createInstance() { createInfo.enabledExtensionCount = glfwExtensionCount; createInfo.ppEnabledExtensionNames = glfwExtensions; - createInfo.enabledLayerCount = 0; + if (enableValidationLayers) { + createInfo.enabledLayerCount = static_cast(validationLayers.size()); + createInfo.ppEnabledLayerNames = validationLayers.data(); + } else { + createInfo.enabledLayerCount = 0; + } if (vk::createInstance(&createInfo, nullptr, &instance) != vk::Result::eSuccess) { throw std::runtime_error("Failed to create instance!"); @@ -62,9 +78,9 @@ void VulkanTest::createInstance() { } bool VulkanTest::checkValidationLayerSupport() const { - #if defined(VK_ADD_LAYER_PATH) && defined(DE_PLATFORM_WINDOWS) +#if defined(VK_ADD_LAYER_PATH) && defined(DE_PLATFORM_WINDOWS) _putenv_s("VK_ADD_LAYER_PATH", VK_ADD_LAYER_PATH); - #endif +#endif uint32_t layerCount; if (vk::enumerateInstanceLayerProperties(&layerCount, nullptr) != vk::Result::eSuccess) { @@ -94,7 +110,121 @@ bool VulkanTest::checkValidationLayerSupport() const { return true; } +void VulkanTest::pickPhysicalDevice() { + uint32_t deviceCount = 0; + if (instance.enumeratePhysicalDevices(&deviceCount, nullptr) != vk::Result::eSuccess || deviceCount == 0) { + throw std::runtime_error("Failed to find GPUs with Vulkan support!"); + } + + std::vector devices(deviceCount); + if (instance.enumeratePhysicalDevices(&deviceCount, devices.data()) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to enumerate physical devices!"); + } + + std::multimap candidates; + for (const auto& device : devices) { + unsigned int score = rateDeviceSuitability(device); + candidates.insert(std::make_pair(score, device)); + } + + if (candidates.rbegin()->first > 0) { + physicalDevice = candidates.rbegin()->second; + std::cout << "Selected GPU: " << physicalDevice.getProperties().deviceName << std::endl; + } else { + throw std::runtime_error("Failed to find a suitable GPU!"); + } +} + +void VulkanTest::createLogicalDevice() { + QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + std::vector queueCreateInfos; + std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + + float queuePriority = 1.0f; + for (unsigned int queueFamily : uniqueQueueFamilies) { + vk::DeviceQueueCreateInfo queueCreateInfo{}; + queueCreateInfo.sType = vk::StructureType::eDeviceQueueCreateInfo; + queueCreateInfo.queueFamilyIndex = queueFamily; + queueCreateInfo.queueCount = 1; + queueCreateInfo.pQueuePriorities = &queuePriority; + queueCreateInfos.emplace_back(queueCreateInfo); + } + + vk::PhysicalDeviceFeatures deviceFeatures{}; + vk::DeviceCreateInfo createInfo{}; + createInfo.sType = vk::StructureType::eDeviceCreateInfo; + createInfo.pQueueCreateInfos = queueCreateInfos.data(); + createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); + createInfo.pEnabledFeatures = &deviceFeatures; + createInfo.enabledExtensionCount = 0; + if (physicalDevice.createDevice(&createInfo, nullptr, &device) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create logical device!"); + } + + graphicsQueue = device.getQueue(indices.graphicsFamily.value(), 0); + presentQueue = device.getQueue(indices.presentFamily.value(), 0); +} + +void VulkanTest::createSurface() { + if (glfwCreateWindowSurface(instance, window, nullptr, reinterpret_cast(&surface)) != VK_SUCCESS) { + throw std::runtime_error("Failed to create window surface!"); + } +} + +VulkanTest::QueueFamilyIndices VulkanTest::findQueueFamilies(vk::PhysicalDevice device) { + QueueFamilyIndices indices; + std::vector queueFamilies = device.getQueueFamilyProperties(); + int i = 0; + for (const auto& queueFamily : queueFamilies) { + if (queueFamily.queueFlags & vk::QueueFlagBits::eGraphics) { + indices.graphicsFamily = i; + } + vk::Bool32 presentSupport = false; + if (device.getSurfaceSupportKHR(i, surface, &presentSupport) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to get device surface support!"); + } + if (presentSupport) { + indices.presentFamily = i; + } + if (indices.isComplete()) { + break; + } + i++; + } + return indices; +} + +unsigned int VulkanTest::rateDeviceSuitability(const vk::PhysicalDevice device) { + const vk::PhysicalDeviceProperties properties = device.getProperties(); + const vk::PhysicalDeviceFeatures features = device.getFeatures(); + + unsigned int score = 0; + + if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { + score += 1000; + } + + score += properties.limits.maxImageDimension2D; + + if (!features.geometryShader) { + return 0; + } + + const QueueFamilyIndices indices = findQueueFamilies(device); + if (!indices.isComplete()) { + return 0; + } + + if (indices.graphicsFamily == indices.presentFamily) { + score += 1; + } + + return score; +} + void VulkanTest::cleanup() const { + device.destroy(); + instance.destroySurfaceKHR(surface); instance.destroy(); glfwDestroyWindow(window); glfwTerminate(); diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h index 2d53d21..f7bd1c9 100644 --- a/engine/src/VulkanTest.h +++ b/engine/src/VulkanTest.h @@ -4,12 +4,7 @@ class VulkanTest { public: - void run() { - initWindow(); - initVulkan(); - mainLoop(); - cleanup(); - } + void run(); private: void initWindow(); @@ -18,13 +13,34 @@ class VulkanTest { void mainLoop(); void createInstance(); - bool checkValidationLayerSupport() const; + void pickPhysicalDevice(); + void createLogicalDevice(); + void createSurface(); + + unsigned int rateDeviceSuitability(vk::PhysicalDevice device); + + struct QueueFamilyIndices { + std::optional graphicsFamily; + std::optional presentFamily; + + bool isComplete() const { + return graphicsFamily.has_value() && presentFamily.has_value(); + } + }; + + QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device); GLFWwindow* window; vk::Instance instance; + vk::PhysicalDevice physicalDevice = nullptr; + vk::Device device; + vk::Queue graphicsQueue; + vk::Queue presentQueue; + vk::SurfaceKHR surface; const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; + const std::vector deviceExtensions = {vk::KHRSwapchainExtensionName}; #if defined(DE_DEBUG) && (!defined(DE_PLATFORM_WINDOWS) || defined(VK_ADD_LAYER_PATH)) const bool enableValidationLayers = true; From cb2b90eae382f073a0fc9a8709d249b126f5ee85 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 14:18:13 +0200 Subject: [PATCH 06/48] Create swap chain and image views --- engine/src/VulkanTest.cpp | 143 +++++++++++++++++++++++++++++++++++--- engine/src/VulkanTest.h | 23 +++++- 2 files changed, 154 insertions(+), 12 deletions(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index bca8dbc..1df5635 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -35,6 +35,8 @@ void VulkanTest::initVulkan() { createSurface(); pickPhysicalDevice(); createLogicalDevice(); + createSwapChain(); + createImageViews(); } void VulkanTest::mainLoop() { @@ -156,7 +158,8 @@ void VulkanTest::createLogicalDevice() { createInfo.pQueueCreateInfos = queueCreateInfos.data(); createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pEnabledFeatures = &deviceFeatures; - createInfo.enabledExtensionCount = 0; + createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); + createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (physicalDevice.createDevice(&createInfo, nullptr, &device) != vk::Result::eSuccess) { throw std::runtime_error("Failed to create logical device!"); } @@ -171,7 +174,69 @@ void VulkanTest::createSurface() { } } -VulkanTest::QueueFamilyIndices VulkanTest::findQueueFamilies(vk::PhysicalDevice device) { +void VulkanTest::createSwapChain() { + auto [capabilities, formats, presentModes] = querySwapChainSupport(physicalDevice); + vk::SurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(formats); + vk::PresentModeKHR presentMode = chooseSwapPresentMode(presentModes); + vk::Extent2D extent = chooseSwapExtent(capabilities); + unsigned int imageCount = capabilities.minImageCount + 1; + if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) { + imageCount = capabilities.maxImageCount; + } + vk::SwapchainCreateInfoKHR createInfo{}; + createInfo.sType = vk::StructureType::eSwapchainCreateInfoKHR; + createInfo.surface = surface; + createInfo.minImageCount = imageCount; + createInfo.imageFormat = surfaceFormat.format; + createInfo.imageColorSpace = surfaceFormat.colorSpace; + createInfo.imageExtent = extent; + createInfo.imageArrayLayers = 1; + createInfo.imageUsage = vk::ImageUsageFlagBits::eColorAttachment; + const QueueFamilyIndices indices = findQueueFamilies(physicalDevice); + const unsigned int queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; + if (indices.graphicsFamily != indices.presentFamily) { + createInfo.imageSharingMode = vk::SharingMode::eConcurrent; + createInfo.queueFamilyIndexCount = 2; + createInfo.pQueueFamilyIndices = queueFamilyIndices; + } else { + createInfo.imageSharingMode = vk::SharingMode::eExclusive; + } + createInfo.preTransform = capabilities.currentTransform; + createInfo.compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque; + createInfo.presentMode = presentMode; + createInfo.clipped = true; + createInfo.oldSwapchain = nullptr; + swapChain = device.createSwapchainKHR(createInfo); + swapChainImages = device.getSwapchainImagesKHR(swapChain); + swapChainImageFormat = surfaceFormat.format; + swapChainExtent = extent; +} + +void VulkanTest::createImageViews() { + swapChainImageViews.resize(swapChainImages.size()); + for (size_t i = 0; i < swapChainImages.size(); i++) { + vk::ImageViewCreateInfo createInfo{}; + createInfo.sType = vk::StructureType::eImageViewCreateInfo; + createInfo.image = swapChainImages[i]; + createInfo.viewType = vk::ImageViewType::e2D; + createInfo.format = swapChainImageFormat; + createInfo.components.r = vk::ComponentSwizzle::eIdentity; + createInfo.components.g = vk::ComponentSwizzle::eIdentity; + createInfo.components.b = vk::ComponentSwizzle::eIdentity; + createInfo.components.a = vk::ComponentSwizzle::eIdentity; + createInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor; + createInfo.subresourceRange.baseMipLevel = 0; + createInfo.subresourceRange.levelCount = 1; + createInfo.subresourceRange.baseArrayLayer = 0; + createInfo.subresourceRange.layerCount = 1; + + if (device.createImageView(&createInfo, nullptr, &swapChainImageViews[i]) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create image views!"); + } + } +} + +VulkanTest::QueueFamilyIndices VulkanTest::findQueueFamilies(const vk::PhysicalDevice device) const { QueueFamilyIndices indices; std::vector queueFamilies = device.getQueueFamilyProperties(); int i = 0; @@ -194,19 +259,55 @@ VulkanTest::QueueFamilyIndices VulkanTest::findQueueFamilies(vk::PhysicalDevice return indices; } -unsigned int VulkanTest::rateDeviceSuitability(const vk::PhysicalDevice device) { - const vk::PhysicalDeviceProperties properties = device.getProperties(); - const vk::PhysicalDeviceFeatures features = device.getFeatures(); +bool VulkanTest::checkDeviceExtensionSupport(const vk::PhysicalDevice device) const { + const std::vector availableExtensions = device.enumerateDeviceExtensionProperties(); + std::set requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); + for (const auto& extension : availableExtensions) { + requiredExtensions.erase(extension.extensionName); + } + return requiredExtensions.empty(); +} - unsigned int score = 0; +VulkanTest::SwapChainSupportDetails VulkanTest::querySwapChainSupport(vk::PhysicalDevice device) const { + return { + .capabilities = device.getSurfaceCapabilitiesKHR(surface), + .formats = device.getSurfaceFormatsKHR(surface), + .presentModes = device.getSurfacePresentModesKHR(surface), + }; +} - if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { - score += 1000; +vk::SurfaceFormatKHR VulkanTest::chooseSwapSurfaceFormat(const std::vector& availableFormats) const { + for (const auto& availableFormat : availableFormats) { + if (availableFormat.format == vk::Format::eB8G8R8A8Srgb && availableFormat.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear) { + return availableFormat; + } } + return availableFormats[0]; +} - score += properties.limits.maxImageDimension2D; +vk::PresentModeKHR VulkanTest::chooseSwapPresentMode(const std::vector& availablePresentModes) const { + for (const auto& availablePresentMode : availablePresentModes) { + if (availablePresentMode == vk::PresentModeKHR::eMailbox) { + return availablePresentMode; + } + } + return vk::PresentModeKHR::eFifo; +} + +vk::Extent2D VulkanTest::chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const { + if (capabilities.currentExtent.width != std::numeric_limits::max()) { + return capabilities.currentExtent; + } + int width, height; + glfwGetFramebufferSize(window, &width, &height); + vk::Extent2D actualExtent; + actualExtent.width = std::clamp(static_cast(width), capabilities.minImageExtent.width, capabilities.maxImageExtent.width); + actualExtent.height = std::clamp(static_cast(height), capabilities.minImageExtent.height, capabilities.maxImageExtent.height); + return actualExtent; +} - if (!features.geometryShader) { +unsigned int VulkanTest::rateDeviceSuitability(const vk::PhysicalDevice device) { + if (!device.getFeatures().geometryShader) { return 0; } @@ -215,6 +316,24 @@ unsigned int VulkanTest::rateDeviceSuitability(const vk::PhysicalDevice device) return 0; } + if (!checkDeviceExtensionSupport(device)) { + return 0; + } + + SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); + if (swapChainSupport.formats.empty() || swapChainSupport.presentModes.empty()) { + return 0; + } + + const vk::PhysicalDeviceProperties properties = device.getProperties(); + unsigned int score = 0; + + if (properties.deviceType == vk::PhysicalDeviceType::eDiscreteGpu) { + score += 1000; + } + + score += properties.limits.maxImageDimension2D; + if (indices.graphicsFamily == indices.presentFamily) { score += 1; } @@ -223,6 +342,10 @@ unsigned int VulkanTest::rateDeviceSuitability(const vk::PhysicalDevice device) } void VulkanTest::cleanup() const { + for (const auto& imageView : swapChainImageViews) { + device.destroyImageView(imageView); + } + device.destroySwapchainKHR(swapChain); device.destroy(); instance.destroySurfaceKHR(surface); instance.destroy(); diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h index f7bd1c9..df9f509 100644 --- a/engine/src/VulkanTest.h +++ b/engine/src/VulkanTest.h @@ -17,6 +17,8 @@ class VulkanTest { void pickPhysicalDevice(); void createLogicalDevice(); void createSurface(); + void createSwapChain(); + void createImageViews(); unsigned int rateDeviceSuitability(vk::PhysicalDevice device); @@ -29,15 +31,32 @@ class VulkanTest { } }; - QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device); + QueueFamilyIndices findQueueFamilies(vk::PhysicalDevice device) const; + bool checkDeviceExtensionSupport(vk::PhysicalDevice device) const; - GLFWwindow* window; + struct SwapChainSupportDetails { + vk::SurfaceCapabilitiesKHR capabilities; + std::vector formats; + std::vector presentModes; + }; + + SwapChainSupportDetails querySwapChainSupport(vk::PhysicalDevice device) const; + vk::SurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats) const; + vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) const; + vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const; + + GLFWwindow* window = nullptr; vk::Instance instance; vk::PhysicalDevice physicalDevice = nullptr; vk::Device device; vk::Queue graphicsQueue; vk::Queue presentQueue; vk::SurfaceKHR surface; + vk::SwapchainKHR swapChain; + std::vector swapChainImages; + vk::Format swapChainImageFormat = {}; + vk::Extent2D swapChainExtent; + std::vector swapChainImageViews; const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; const std::vector deviceExtensions = {vk::KHRSwapchainExtensionName}; From d297555bd4bf3a0998fae15a11c83f3acc738a68 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 15:50:38 +0200 Subject: [PATCH 07/48] Create render pass and graphics pipeline --- assets/shaders/vulkan-test.frag | 9 ++ assets/shaders/vulkan-test.frag.spv | Bin 0 -> 572 bytes assets/shaders/vulkan-test.vert | 20 +++ assets/shaders/vulkan-test.vert.spv | Bin 0 -> 1504 bytes engine/src/VulkanTest.cpp | 204 ++++++++++++++++++++++++++-- engine/src/VulkanTest.h | 9 ++ 6 files changed, 233 insertions(+), 9 deletions(-) create mode 100644 assets/shaders/vulkan-test.frag create mode 100644 assets/shaders/vulkan-test.frag.spv create mode 100644 assets/shaders/vulkan-test.vert create mode 100644 assets/shaders/vulkan-test.vert.spv diff --git a/assets/shaders/vulkan-test.frag b/assets/shaders/vulkan-test.frag new file mode 100644 index 0000000..7c5b0e7 --- /dev/null +++ b/assets/shaders/vulkan-test.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/assets/shaders/vulkan-test.frag.spv b/assets/shaders/vulkan-test.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..da37f7ede672fe162b5322c32f5938a7836409a7 GIT binary patch literal 572 zcmYk2PfNo<6vUrx)7IAhv!FMrcod2U6+zU4NG^dYet=MtD1q3NHWj@2+5A*q1n0LV z(uK*}H#=`OEEHA71Yeo8+BI6MPEqd}+{a8uKdhAl0+aGA( z6gLqLrRPob_)qk0tMXUiuge|}IP@J=^z`Vvs`?d60nUc?u0MFnDF)EG3?WD_tXd~G(}N+zk@O66FYiu^P-@u*JZ3F?aDVc^abjOR}^*Rec7t&^?*uDZdsTY;mcUNHNK*l`ZeJhd1~N% zVfN;G@ULpjeZowB?r%b zYcjq?9UA(*E6?8CvAYBAl()eh+><8{x30*ScQxcN?q)-tnSB{^fj4Dv-1U~cFPo6D z4|rR~qW-=-Ie6;9J2glCpr4#8b{zD+OS{Tg%D)Ps4SXvn{ir=I+g-U4rsnOSK5 zit*&%2=S)4TVn2pb9*f{y*-f8*E^}{>5&XQJR17r{{#NBH0-%sb8=&DujZKfR8`FJ yM240e_srd~53@d%ah{wReSy)tzMd;~eKG5SjNakVF!!Mho_fv({7 + int main() { VulkanTest app; @@ -37,6 +39,23 @@ void VulkanTest::initVulkan() { createLogicalDevice(); createSwapChain(); createImageViews(); + createRenderPass(); + createGraphicsPipeline(); +} + +void VulkanTest::cleanup() const { + device.destroyPipeline(graphicsPipeline); + device.destroyPipelineLayout(pipelineLayout); + device.destroyRenderPass(renderPass); + for (const auto& imageView : swapChainImageViews) { + device.destroyImageView(imageView); + } + device.destroySwapchainKHR(swapChain); + device.destroy(); + instance.destroySurfaceKHR(surface); + instance.destroy(); + glfwDestroyWindow(window); + glfwTerminate(); } void VulkanTest::mainLoop() { @@ -236,6 +255,171 @@ void VulkanTest::createImageViews() { } } +void VulkanTest::createRenderPass() { + vk::AttachmentDescription colorAttachment{}; + colorAttachment.format = swapChainImageFormat; + colorAttachment.samples = vk::SampleCountFlagBits::e1; + colorAttachment.loadOp = vk::AttachmentLoadOp::eClear; + colorAttachment.storeOp = vk::AttachmentStoreOp::eStore; + colorAttachment.stencilLoadOp = vk::AttachmentLoadOp::eDontCare; + colorAttachment.stencilStoreOp = vk::AttachmentStoreOp::eDontCare; + colorAttachment.initialLayout = vk::ImageLayout::eUndefined; + colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR; + + vk::AttachmentReference colorAttachmentRef{}; + colorAttachmentRef.attachment = 0; + colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal; + + vk::SubpassDescription subpass{}; + subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics; + subpass.colorAttachmentCount = 1; + subpass.pColorAttachments = &colorAttachmentRef; + + vk::RenderPassCreateInfo renderPassInfo{}; + renderPassInfo.sType = vk::StructureType::eRenderPassCreateInfo; + renderPassInfo.attachmentCount = 1; + renderPassInfo.pAttachments = &colorAttachment; + renderPassInfo.subpassCount = 1; + renderPassInfo.pSubpasses = &subpass; + renderPass = device.createRenderPass(renderPassInfo); +} + +void VulkanTest::createGraphicsPipeline() { + auto vertShaderCode = readFile("../assets/shaders/vulkan-test.vert.spv"); + auto fragShaderCode = readFile("../assets/shaders/vulkan-test.frag.spv"); + + vk::ShaderModule vertShaderModule = createShaderModule(vertShaderCode); + vk::ShaderModule fragShaderModule = createShaderModule(fragShaderCode); + + vk::PipelineShaderStageCreateInfo vertShaderStageInfo{}; + vertShaderStageInfo.sType = vk::StructureType::ePipelineShaderStageCreateInfo; + vertShaderStageInfo.stage = vk::ShaderStageFlagBits::eVertex; + vertShaderStageInfo.module = vertShaderModule; + vertShaderStageInfo.pName = "main"; + vk::PipelineShaderStageCreateInfo fragShaderStageInfo{}; + fragShaderStageInfo.sType = vk::StructureType::ePipelineShaderStageCreateInfo; + fragShaderStageInfo.stage = vk::ShaderStageFlagBits::eFragment; + fragShaderStageInfo.module = fragShaderModule; + fragShaderStageInfo.pName = "main"; + vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; + + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; + vertexInputInfo.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo; + vertexInputInfo.vertexBindingDescriptionCount = 0; + vertexInputInfo.pVertexBindingDescriptions = nullptr; + vertexInputInfo.vertexAttributeDescriptionCount = 0; + vertexInputInfo.pVertexAttributeDescriptions = nullptr; + + vk::PipelineInputAssemblyStateCreateInfo inputAssembly{}; + inputAssembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo; + inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; + inputAssembly.primitiveRestartEnable = VK_FALSE; + + vk::Viewport viewport = {0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0}; + + vk::Rect2D scissor = {{0, 0}, swapChainExtent}; + + const std::vector dynamicStates = { + vk::DynamicState::eViewport, + vk::DynamicState::eScissor, + }; + vk::PipelineDynamicStateCreateInfo dynamicState{}; + dynamicState.sType = vk::StructureType::ePipelineDynamicStateCreateInfo; + dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); + dynamicState.pDynamicStates = dynamicStates.data(); + + vk::PipelineViewportStateCreateInfo viewportState{}; + viewportState.sType = vk::StructureType::ePipelineViewportStateCreateInfo; + viewportState.viewportCount = 1; + viewportState.pViewports = &viewport; + viewportState.scissorCount = 1; + viewportState.pScissors = &scissor; + + vk::PipelineRasterizationStateCreateInfo rasterizer{}; + rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo; + rasterizer.depthClampEnable = VK_FALSE; + rasterizer.rasterizerDiscardEnable = VK_FALSE; + rasterizer.polygonMode = vk::PolygonMode::eFill; + rasterizer.lineWidth = 1.0f; + rasterizer.cullMode = vk::CullModeFlagBits::eBack; + rasterizer.frontFace = vk::FrontFace::eClockwise; + rasterizer.depthBiasEnable = VK_FALSE; + rasterizer.depthBiasConstantFactor = 0.0f; + rasterizer.depthBiasClamp = 0.0f; + rasterizer.depthBiasSlopeFactor = 0.0f; + + vk::PipelineMultisampleStateCreateInfo multisampling{}; + multisampling.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo; + multisampling.sampleShadingEnable = VK_FALSE; + multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; + multisampling.minSampleShading = 1.0f; + multisampling.pSampleMask = nullptr; + multisampling.alphaToCoverageEnable = VK_FALSE; + multisampling.alphaToOneEnable = VK_FALSE; + + vk::PipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; + colorBlendAttachment.blendEnable = VK_FALSE; + colorBlendAttachment.srcColorBlendFactor = vk::BlendFactor::eOne; + colorBlendAttachment.dstColorBlendFactor = vk::BlendFactor::eZero; + colorBlendAttachment.colorBlendOp = vk::BlendOp::eAdd; + colorBlendAttachment.srcAlphaBlendFactor = vk::BlendFactor::eOne; + colorBlendAttachment.dstAlphaBlendFactor = vk::BlendFactor::eZero; + colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; + + vk::PipelineColorBlendStateCreateInfo colorBlending{}; + colorBlending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo; + colorBlending.logicOpEnable = VK_FALSE; + colorBlending.logicOp = vk::LogicOp::eCopy; + colorBlending.attachmentCount = 1; + colorBlending.pAttachments = &colorBlendAttachment; + colorBlending.blendConstants[0] = 0.0f; + colorBlending.blendConstants[1] = 0.0f; + colorBlending.blendConstants[2] = 0.0f; + colorBlending.blendConstants[3] = 0.0f; + + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; + pipelineLayoutInfo.sType = vk::StructureType::ePipelineLayoutCreateInfo; + pipelineLayoutInfo.setLayoutCount = 0; + pipelineLayoutInfo.pSetLayouts = nullptr; + pipelineLayoutInfo.pushConstantRangeCount = 0; + pipelineLayoutInfo.pPushConstantRanges = nullptr; + pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); + + vk::GraphicsPipelineCreateInfo pipelineInfo{}; + pipelineInfo.sType = vk::StructureType::eGraphicsPipelineCreateInfo; + pipelineInfo.stageCount = 2; + pipelineInfo.pStages = shaderStages; + pipelineInfo.pVertexInputState = &vertexInputInfo; + pipelineInfo.pInputAssemblyState = &inputAssembly; + pipelineInfo.pViewportState = &viewportState; + pipelineInfo.pRasterizationState = &rasterizer; + pipelineInfo.pMultisampleState = &multisampling; + pipelineInfo.pDepthStencilState = nullptr; + pipelineInfo.pColorBlendState = &colorBlending; + pipelineInfo.pDynamicState = &dynamicState; + pipelineInfo.layout = pipelineLayout; + pipelineInfo.renderPass = renderPass; + pipelineInfo.subpass = 0; + + const vk::ResultValue result = device.createGraphicsPipeline(nullptr, pipelineInfo); + if (result.result != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create graphics pipeline!"); + } + graphicsPipeline = result.value; + + device.destroyShaderModule(fragShaderModule); + device.destroyShaderModule(vertShaderModule); +} + +vk::ShaderModule VulkanTest::createShaderModule(const std::vector& code) const { + vk::ShaderModuleCreateInfo createInfo{}; + createInfo.sType = vk::StructureType::eShaderModuleCreateInfo; + createInfo.codeSize = code.size(); + createInfo.pCode = reinterpret_cast(code.data()); + return device.createShaderModule(createInfo); +} + VulkanTest::QueueFamilyIndices VulkanTest::findQueueFamilies(const vk::PhysicalDevice device) const { QueueFamilyIndices indices; std::vector queueFamilies = device.getQueueFamilyProperties(); @@ -341,14 +525,16 @@ unsigned int VulkanTest::rateDeviceSuitability(const vk::PhysicalDevice device) return score; } -void VulkanTest::cleanup() const { - for (const auto& imageView : swapChainImageViews) { - device.destroyImageView(imageView); +std::vector VulkanTest::readFile(const std::string& filename) { + std::ifstream file(filename, std::ios::ate | std::ios::binary); + + if (!file.is_open()) { + throw std::runtime_error("Failed to open file!"); } - device.destroySwapchainKHR(swapChain); - device.destroy(); - instance.destroySurfaceKHR(surface); - instance.destroy(); - glfwDestroyWindow(window); - glfwTerminate(); + size_t fileSize = static_cast(file.tellg()); + std::vector buffer(fileSize); + file.seekg(0); + file.read(buffer.data(), fileSize); + file.close(); + return buffer; } diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h index df9f509..94466dc 100644 --- a/engine/src/VulkanTest.h +++ b/engine/src/VulkanTest.h @@ -19,6 +19,8 @@ class VulkanTest { void createSurface(); void createSwapChain(); void createImageViews(); + void createRenderPass(); + void createGraphicsPipeline(); unsigned int rateDeviceSuitability(vk::PhysicalDevice device); @@ -45,6 +47,10 @@ class VulkanTest { vk::PresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes) const; vk::Extent2D chooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities) const; + vk::ShaderModule createShaderModule(const std::vector& code) const; + + static std::vector readFile(const std::string& filename); + GLFWwindow* window = nullptr; vk::Instance instance; vk::PhysicalDevice physicalDevice = nullptr; @@ -57,6 +63,9 @@ class VulkanTest { vk::Format swapChainImageFormat = {}; vk::Extent2D swapChainExtent; std::vector swapChainImageViews; + vk::RenderPass renderPass; + vk::PipelineLayout pipelineLayout; + vk::Pipeline graphicsPipeline; const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; const std::vector deviceExtensions = {vk::KHRSwapchainExtensionName}; From 4d6cd51d5f5469571b9c3d0da2b2988ac9c7d13d Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 17:07:18 +0200 Subject: [PATCH 08/48] We havea a colored triangle! --- engine/src/VulkanTest.cpp | 174 ++++++++++++++++++++++++++++++++++---- engine/src/VulkanTest.h | 16 +++- 2 files changed, 174 insertions(+), 16 deletions(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index a94dcef..4e705a6 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -41,9 +41,20 @@ void VulkanTest::initVulkan() { createImageViews(); createRenderPass(); createGraphicsPipeline(); + createFramebuffers(); + createCommandPool(); + createCommandBuffer(); + createSyncObjects(); } void VulkanTest::cleanup() const { + device.destroySemaphore(renderFinishedSemaphore); + device.destroySemaphore(imageAvailableSemaphore); + device.destroyFence(inFlightFence); + device.destroyCommandPool(commandPool); + for (const auto& framebuffer : swapChainFramebuffers) { + device.destroyFramebuffer(framebuffer); + } device.destroyPipeline(graphicsPipeline); device.destroyPipelineLayout(pipelineLayout); device.destroyRenderPass(renderPass); @@ -58,15 +69,59 @@ void VulkanTest::cleanup() const { glfwTerminate(); } -void VulkanTest::mainLoop() { +void VulkanTest::mainLoop() const { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); + drawFrame(); + } + device.waitIdle(); +} + +void VulkanTest::drawFrame() const { + if (device.waitForFences(1, &inFlightFence, true, std::numeric_limits::max()) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to wait for fence"); + } + if (device.resetFences(1, &inFlightFence) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to reset fence"); + } + + unsigned int imageIndex = device.acquireNextImageKHR(swapChain, std::numeric_limits::max(), imageAvailableSemaphore, nullptr).value; + commandBuffer.reset(); + recordCommandBuffer(commandBuffer, imageIndex); + + vk::SubmitInfo submitInfo{}; + vk::Semaphore waitSemaphores[] = {imageAvailableSemaphore}; + vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; + submitInfo.sType = vk::StructureType::eSubmitInfo; + submitInfo.waitSemaphoreCount = 1; + submitInfo.pWaitSemaphores = waitSemaphores; + submitInfo.pWaitDstStageMask = waitStages; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + vk::Semaphore signalSemaphores[] = {renderFinishedSemaphore}; + submitInfo.signalSemaphoreCount = 1; + submitInfo.pSignalSemaphores = signalSemaphores; + if (graphicsQueue.submit(1, &submitInfo, inFlightFence) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to submit draw command buffer"); + } + + vk::PresentInfoKHR presentInfo{}; + presentInfo.sType = vk::StructureType::ePresentInfoKHR; + presentInfo.waitSemaphoreCount = 1; + presentInfo.pWaitSemaphores = signalSemaphores; + + vk::SwapchainKHR swapChains[] = {swapChain}; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = swapChains; + presentInfo.pImageIndices = &imageIndex; + if (presentQueue.presentKHR(&presentInfo) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to present swap chain image"); } } void VulkanTest::createInstance() { if (enableValidationLayers && !checkValidationLayerSupport()) { - throw std::runtime_error("validation layers requested, but not available!"); + throw std::runtime_error("validation layers requested, but not available"); } vk::ApplicationInfo appInfo{}; @@ -94,7 +149,7 @@ void VulkanTest::createInstance() { } if (vk::createInstance(&createInfo, nullptr, &instance) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create instance!"); + throw std::runtime_error("Failed to create instance"); } } @@ -105,12 +160,12 @@ bool VulkanTest::checkValidationLayerSupport() const { uint32_t layerCount; if (vk::enumerateInstanceLayerProperties(&layerCount, nullptr) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to enumerate instance layer properties!"); + throw std::runtime_error("Failed to enumerate instance layer properties"); } std::vector availableLayers(layerCount); if (vk::enumerateInstanceLayerProperties(&layerCount, availableLayers.data()) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to enumerate instance layer properties!"); + throw std::runtime_error("Failed to enumerate instance layer properties"); } for (const char* layerName : validationLayers) { @@ -134,12 +189,12 @@ bool VulkanTest::checkValidationLayerSupport() const { void VulkanTest::pickPhysicalDevice() { uint32_t deviceCount = 0; if (instance.enumeratePhysicalDevices(&deviceCount, nullptr) != vk::Result::eSuccess || deviceCount == 0) { - throw std::runtime_error("Failed to find GPUs with Vulkan support!"); + throw std::runtime_error("Failed to find GPUs with Vulkan support"); } std::vector devices(deviceCount); if (instance.enumeratePhysicalDevices(&deviceCount, devices.data()) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to enumerate physical devices!"); + throw std::runtime_error("Failed to enumerate physical devices"); } std::multimap candidates; @@ -152,7 +207,7 @@ void VulkanTest::pickPhysicalDevice() { physicalDevice = candidates.rbegin()->second; std::cout << "Selected GPU: " << physicalDevice.getProperties().deviceName << std::endl; } else { - throw std::runtime_error("Failed to find a suitable GPU!"); + throw std::runtime_error("Failed to find a suitable GPU"); } } @@ -180,7 +235,7 @@ void VulkanTest::createLogicalDevice() { createInfo.enabledExtensionCount = static_cast(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (physicalDevice.createDevice(&createInfo, nullptr, &device) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create logical device!"); + throw std::runtime_error("Failed to create logical device"); } graphicsQueue = device.getQueue(indices.graphicsFamily.value(), 0); @@ -189,7 +244,7 @@ void VulkanTest::createLogicalDevice() { void VulkanTest::createSurface() { if (glfwCreateWindowSurface(instance, window, nullptr, reinterpret_cast(&surface)) != VK_SUCCESS) { - throw std::runtime_error("Failed to create window surface!"); + throw std::runtime_error("Failed to create window surface"); } } @@ -250,7 +305,7 @@ void VulkanTest::createImageViews() { createInfo.subresourceRange.layerCount = 1; if (device.createImageView(&createInfo, nullptr, &swapChainImageViews[i]) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create image views!"); + throw std::runtime_error("Failed to create image views"); } } } @@ -266,7 +321,7 @@ void VulkanTest::createRenderPass() { colorAttachment.initialLayout = vk::ImageLayout::eUndefined; colorAttachment.finalLayout = vk::ImageLayout::ePresentSrcKHR; - vk::AttachmentReference colorAttachmentRef{}; + vk::AttachmentReference colorAttachmentRef; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = vk::ImageLayout::eColorAttachmentOptimal; @@ -275,12 +330,22 @@ void VulkanTest::createRenderPass() { subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; + vk::SubpassDependency dependency{}; + dependency.srcSubpass = vk::SubpassExternal; + dependency.dstSubpass = 0; + dependency.srcStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dependency.srcAccessMask = vk::AccessFlags(); + dependency.dstStageMask = vk::PipelineStageFlagBits::eColorAttachmentOutput; + dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; + vk::RenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = vk::StructureType::eRenderPassCreateInfo; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; + renderPassInfo.dependencyCount = 1; + renderPassInfo.pDependencies = &dependency; renderPass = device.createRenderPass(renderPassInfo); } @@ -404,7 +469,7 @@ void VulkanTest::createGraphicsPipeline() { const vk::ResultValue result = device.createGraphicsPipeline(nullptr, pipelineInfo); if (result.result != vk::Result::eSuccess) { - throw std::runtime_error("Failed to create graphics pipeline!"); + throw std::runtime_error("Failed to create graphics pipeline"); } graphicsPipeline = result.value; @@ -412,6 +477,85 @@ void VulkanTest::createGraphicsPipeline() { device.destroyShaderModule(vertShaderModule); } +void VulkanTest::createFramebuffers() { + swapChainFramebuffers.resize(swapChainImageViews.size()); + for (size_t i = 0; i < swapChainImageViews.size(); i++) { + const vk::ImageView attachments[] = {swapChainImageViews[i]}; + + vk::FramebufferCreateInfo framebufferInfo{}; + framebufferInfo.sType = vk::StructureType::eFramebufferCreateInfo; + framebufferInfo.renderPass = renderPass; + framebufferInfo.attachmentCount = 1; + framebufferInfo.pAttachments = attachments; + framebufferInfo.width = swapChainExtent.width; + framebufferInfo.height = swapChainExtent.height; + framebufferInfo.layers = 1; + + if (device.createFramebuffer(&framebufferInfo, nullptr, &swapChainFramebuffers[i]) != vk::Result::eSuccess) { + throw std::runtime_error("failed to create framebuffer"); + } + } +} + +void VulkanTest::createCommandPool() { + QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); + + vk::CommandPoolCreateInfo poolInfo{}; + poolInfo.sType = vk::StructureType::eCommandPoolCreateInfo; + poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; + poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); + + if (device.createCommandPool(&poolInfo, nullptr, &commandPool) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to create command pool"); + } +} + +void VulkanTest::createCommandBuffer() { + vk::CommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = vk::StructureType::eCommandBufferAllocateInfo; + allocInfo.commandPool = commandPool; + allocInfo.level = vk::CommandBufferLevel::ePrimary; + allocInfo.commandBufferCount = 1; + commandBuffer = device.allocateCommandBuffers(allocInfo)[0]; +} + +void VulkanTest::createSyncObjects() { + vk::SemaphoreCreateInfo semaphoreInfo{}; + semaphoreInfo.sType = vk::StructureType::eSemaphoreCreateInfo; + imageAvailableSemaphore = device.createSemaphore(semaphoreInfo); + renderFinishedSemaphore = device.createSemaphore(semaphoreInfo); + + vk::FenceCreateInfo fenceInfo{}; + fenceInfo.sType = vk::StructureType::eFenceCreateInfo; + fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; + inFlightFence = device.createFence(fenceInfo); +} + +void VulkanTest::recordCommandBuffer(const vk::CommandBuffer commandBuffer, const unsigned int imageIndex) const { + vk::CommandBufferBeginInfo beginInfo{}; + beginInfo.sType = vk::StructureType::eCommandBufferBeginInfo; + if (commandBuffer.begin(&beginInfo) != vk::Result::eSuccess) { + throw std::runtime_error("Failed to begin recording command buffer"); + } + vk::RenderPassBeginInfo renderPassInfo{}; + renderPassInfo.sType = vk::StructureType::eRenderPassBeginInfo; + renderPassInfo.renderPass = renderPass; + renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; + renderPassInfo.renderArea.offset = vk::Offset2D(0, 0); + renderPassInfo.renderArea.extent = swapChainExtent; + constexpr vk::ClearValue clearColor = vk::ClearColorValue(std::array{0.0f, 0.0f, 0.0f, 1.0f}); + renderPassInfo.clearValueCount = 1; + renderPassInfo.pClearValues = &clearColor; + + commandBuffer.beginRenderPass(&renderPassInfo, vk::SubpassContents::eInline); + commandBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, graphicsPipeline); + commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); + commandBuffer.setScissor(0, vk::Rect2D({0, 0}, swapChainExtent)); + commandBuffer.draw(3, 1, 0, 0); + commandBuffer.endRenderPass(); + commandBuffer.end(); +} + vk::ShaderModule VulkanTest::createShaderModule(const std::vector& code) const { vk::ShaderModuleCreateInfo createInfo{}; createInfo.sType = vk::StructureType::eShaderModuleCreateInfo; @@ -430,7 +574,7 @@ VulkanTest::QueueFamilyIndices VulkanTest::findQueueFamilies(const vk::PhysicalD } vk::Bool32 presentSupport = false; if (device.getSurfaceSupportKHR(i, surface, &presentSupport) != vk::Result::eSuccess) { - throw std::runtime_error("Failed to get device surface support!"); + throw std::runtime_error("Failed to get device surface support"); } if (presentSupport) { indices.presentFamily = i; @@ -529,7 +673,7 @@ std::vector VulkanTest::readFile(const std::string& filename) { std::ifstream file(filename, std::ios::ate | std::ios::binary); if (!file.is_open()) { - throw std::runtime_error("Failed to open file!"); + throw std::runtime_error("Failed to open file"); } size_t fileSize = static_cast(file.tellg()); std::vector buffer(fileSize); diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h index 94466dc..f8af268 100644 --- a/engine/src/VulkanTest.h +++ b/engine/src/VulkanTest.h @@ -10,7 +10,8 @@ class VulkanTest { void initWindow(); void initVulkan(); void cleanup() const; - void mainLoop(); + void mainLoop() const; + void drawFrame() const; void createInstance(); bool checkValidationLayerSupport() const; @@ -21,6 +22,12 @@ class VulkanTest { void createImageViews(); void createRenderPass(); void createGraphicsPipeline(); + void createFramebuffers(); + void createCommandPool(); + void createCommandBuffer(); + void createSyncObjects(); + + void recordCommandBuffer(vk::CommandBuffer commandBuffer, unsigned int imageIndex) const; unsigned int rateDeviceSuitability(vk::PhysicalDevice device); @@ -66,6 +73,13 @@ class VulkanTest { vk::RenderPass renderPass; vk::PipelineLayout pipelineLayout; vk::Pipeline graphicsPipeline; + std::vector swapChainFramebuffers; + vk::CommandPool commandPool; + vk::CommandBuffer commandBuffer; + + vk::Semaphore imageAvailableSemaphore; + vk::Semaphore renderFinishedSemaphore; + vk::Fence inFlightFence; const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; const std::vector deviceExtensions = {vk::KHRSwapchainExtensionName}; From 3087d6786b110c60eb82ce2139090fa1f1808642 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 17:19:12 +0200 Subject: [PATCH 09/48] Little cleanup --- engine/src/VulkanTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index 4e705a6..4b64567 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -675,7 +675,7 @@ std::vector VulkanTest::readFile(const std::string& filename) { if (!file.is_open()) { throw std::runtime_error("Failed to open file"); } - size_t fileSize = static_cast(file.tellg()); + const auto fileSize = file.tellg(); std::vector buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); From 57b7841193e80c84b5a3a905560a66b11e0d0b0a Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 17:22:02 +0200 Subject: [PATCH 10/48] Remove redundant sType everywhere --- engine/src/VulkanTest.cpp | 30 +----------------------------- 1 file changed, 1 insertion(+), 29 deletions(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index 4b64567..c2caf34 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -92,7 +92,6 @@ void VulkanTest::drawFrame() const { vk::SubmitInfo submitInfo{}; vk::Semaphore waitSemaphores[] = {imageAvailableSemaphore}; vk::PipelineStageFlags waitStages[] = {vk::PipelineStageFlagBits::eColorAttachmentOutput}; - submitInfo.sType = vk::StructureType::eSubmitInfo; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; @@ -106,7 +105,6 @@ void VulkanTest::drawFrame() const { } vk::PresentInfoKHR presentInfo{}; - presentInfo.sType = vk::StructureType::ePresentInfoKHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; @@ -125,7 +123,6 @@ void VulkanTest::createInstance() { } vk::ApplicationInfo appInfo{}; - appInfo.sType = vk::StructureType::eApplicationInfo; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; @@ -133,7 +130,6 @@ void VulkanTest::createInstance() { appInfo.apiVersion = VK_API_VERSION_1_0; vk::InstanceCreateInfo createInfo{}; - createInfo.sType = vk::StructureType::eInstanceCreateInfo; createInfo.pApplicationInfo = &appInfo; uint32_t glfwExtensionCount = 0; @@ -219,7 +215,6 @@ void VulkanTest::createLogicalDevice() { float queuePriority = 1.0f; for (unsigned int queueFamily : uniqueQueueFamilies) { vk::DeviceQueueCreateInfo queueCreateInfo{}; - queueCreateInfo.sType = vk::StructureType::eDeviceQueueCreateInfo; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; queueCreateInfo.pQueuePriorities = &queuePriority; @@ -228,7 +223,6 @@ void VulkanTest::createLogicalDevice() { vk::PhysicalDeviceFeatures deviceFeatures{}; vk::DeviceCreateInfo createInfo{}; - createInfo.sType = vk::StructureType::eDeviceCreateInfo; createInfo.pQueueCreateInfos = queueCreateInfos.data(); createInfo.queueCreateInfoCount = static_cast(queueCreateInfos.size()); createInfo.pEnabledFeatures = &deviceFeatures; @@ -258,7 +252,6 @@ void VulkanTest::createSwapChain() { imageCount = capabilities.maxImageCount; } vk::SwapchainCreateInfoKHR createInfo{}; - createInfo.sType = vk::StructureType::eSwapchainCreateInfoKHR; createInfo.surface = surface; createInfo.minImageCount = imageCount; createInfo.imageFormat = surfaceFormat.format; @@ -290,7 +283,6 @@ void VulkanTest::createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for (size_t i = 0; i < swapChainImages.size(); i++) { vk::ImageViewCreateInfo createInfo{}; - createInfo.sType = vk::StructureType::eImageViewCreateInfo; createInfo.image = swapChainImages[i]; createInfo.viewType = vk::ImageViewType::e2D; createInfo.format = swapChainImageFormat; @@ -339,7 +331,6 @@ void VulkanTest::createRenderPass() { dependency.dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite; vk::RenderPassCreateInfo renderPassInfo{}; - renderPassInfo.sType = vk::StructureType::eRenderPassCreateInfo; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; @@ -357,26 +348,22 @@ void VulkanTest::createGraphicsPipeline() { vk::ShaderModule fragShaderModule = createShaderModule(fragShaderCode); vk::PipelineShaderStageCreateInfo vertShaderStageInfo{}; - vertShaderStageInfo.sType = vk::StructureType::ePipelineShaderStageCreateInfo; vertShaderStageInfo.stage = vk::ShaderStageFlagBits::eVertex; vertShaderStageInfo.module = vertShaderModule; vertShaderStageInfo.pName = "main"; vk::PipelineShaderStageCreateInfo fragShaderStageInfo{}; - fragShaderStageInfo.sType = vk::StructureType::ePipelineShaderStageCreateInfo; fragShaderStageInfo.stage = vk::ShaderStageFlagBits::eFragment; fragShaderStageInfo.module = fragShaderModule; fragShaderStageInfo.pName = "main"; vk::PipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; vk::PipelineVertexInputStateCreateInfo vertexInputInfo{}; - vertexInputInfo.sType = vk::StructureType::ePipelineVertexInputStateCreateInfo; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.pVertexBindingDescriptions = nullptr; vertexInputInfo.vertexAttributeDescriptionCount = 0; vertexInputInfo.pVertexAttributeDescriptions = nullptr; vk::PipelineInputAssemblyStateCreateInfo inputAssembly{}; - inputAssembly.sType = vk::StructureType::ePipelineInputAssemblyStateCreateInfo; inputAssembly.topology = vk::PrimitiveTopology::eTriangleList; inputAssembly.primitiveRestartEnable = VK_FALSE; @@ -389,19 +376,16 @@ void VulkanTest::createGraphicsPipeline() { vk::DynamicState::eScissor, }; vk::PipelineDynamicStateCreateInfo dynamicState{}; - dynamicState.sType = vk::StructureType::ePipelineDynamicStateCreateInfo; dynamicState.dynamicStateCount = static_cast(dynamicStates.size()); dynamicState.pDynamicStates = dynamicStates.data(); vk::PipelineViewportStateCreateInfo viewportState{}; - viewportState.sType = vk::StructureType::ePipelineViewportStateCreateInfo; viewportState.viewportCount = 1; viewportState.pViewports = &viewport; viewportState.scissorCount = 1; viewportState.pScissors = &scissor; vk::PipelineRasterizationStateCreateInfo rasterizer{}; - rasterizer.sType = vk::StructureType::ePipelineRasterizationStateCreateInfo; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; rasterizer.polygonMode = vk::PolygonMode::eFill; @@ -414,7 +398,6 @@ void VulkanTest::createGraphicsPipeline() { rasterizer.depthBiasSlopeFactor = 0.0f; vk::PipelineMultisampleStateCreateInfo multisampling{}; - multisampling.sType = vk::StructureType::ePipelineMultisampleStateCreateInfo; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = vk::SampleCountFlagBits::e1; multisampling.minSampleShading = 1.0f; @@ -433,7 +416,6 @@ void VulkanTest::createGraphicsPipeline() { colorBlendAttachment.alphaBlendOp = vk::BlendOp::eAdd; vk::PipelineColorBlendStateCreateInfo colorBlending{}; - colorBlending.sType = vk::StructureType::ePipelineColorBlendStateCreateInfo; colorBlending.logicOpEnable = VK_FALSE; colorBlending.logicOp = vk::LogicOp::eCopy; colorBlending.attachmentCount = 1; @@ -444,7 +426,6 @@ void VulkanTest::createGraphicsPipeline() { colorBlending.blendConstants[3] = 0.0f; vk::PipelineLayoutCreateInfo pipelineLayoutInfo{}; - pipelineLayoutInfo.sType = vk::StructureType::ePipelineLayoutCreateInfo; pipelineLayoutInfo.setLayoutCount = 0; pipelineLayoutInfo.pSetLayouts = nullptr; pipelineLayoutInfo.pushConstantRangeCount = 0; @@ -452,7 +433,6 @@ void VulkanTest::createGraphicsPipeline() { pipelineLayout = device.createPipelineLayout(pipelineLayoutInfo); vk::GraphicsPipelineCreateInfo pipelineInfo{}; - pipelineInfo.sType = vk::StructureType::eGraphicsPipelineCreateInfo; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; pipelineInfo.pVertexInputState = &vertexInputInfo; @@ -483,7 +463,6 @@ void VulkanTest::createFramebuffers() { const vk::ImageView attachments[] = {swapChainImageViews[i]}; vk::FramebufferCreateInfo framebufferInfo{}; - framebufferInfo.sType = vk::StructureType::eFramebufferCreateInfo; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; framebufferInfo.pAttachments = attachments; @@ -501,7 +480,6 @@ void VulkanTest::createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); vk::CommandPoolCreateInfo poolInfo{}; - poolInfo.sType = vk::StructureType::eCommandPoolCreateInfo; poolInfo.flags = vk::CommandPoolCreateFlagBits::eResetCommandBuffer; poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); @@ -512,7 +490,6 @@ void VulkanTest::createCommandPool() { void VulkanTest::createCommandBuffer() { vk::CommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = vk::StructureType::eCommandBufferAllocateInfo; allocInfo.commandPool = commandPool; allocInfo.level = vk::CommandBufferLevel::ePrimary; allocInfo.commandBufferCount = 1; @@ -521,24 +498,20 @@ void VulkanTest::createCommandBuffer() { void VulkanTest::createSyncObjects() { vk::SemaphoreCreateInfo semaphoreInfo{}; - semaphoreInfo.sType = vk::StructureType::eSemaphoreCreateInfo; imageAvailableSemaphore = device.createSemaphore(semaphoreInfo); renderFinishedSemaphore = device.createSemaphore(semaphoreInfo); vk::FenceCreateInfo fenceInfo{}; - fenceInfo.sType = vk::StructureType::eFenceCreateInfo; fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; inFlightFence = device.createFence(fenceInfo); } void VulkanTest::recordCommandBuffer(const vk::CommandBuffer commandBuffer, const unsigned int imageIndex) const { - vk::CommandBufferBeginInfo beginInfo{}; - beginInfo.sType = vk::StructureType::eCommandBufferBeginInfo; + constexpr vk::CommandBufferBeginInfo beginInfo{}; if (commandBuffer.begin(&beginInfo) != vk::Result::eSuccess) { throw std::runtime_error("Failed to begin recording command buffer"); } vk::RenderPassBeginInfo renderPassInfo{}; - renderPassInfo.sType = vk::StructureType::eRenderPassBeginInfo; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; renderPassInfo.renderArea.offset = vk::Offset2D(0, 0); @@ -558,7 +531,6 @@ void VulkanTest::recordCommandBuffer(const vk::CommandBuffer commandBuffer, cons vk::ShaderModule VulkanTest::createShaderModule(const std::vector& code) const { vk::ShaderModuleCreateInfo createInfo{}; - createInfo.sType = vk::StructureType::eShaderModuleCreateInfo; createInfo.codeSize = code.size(); createInfo.pCode = reinterpret_cast(code.data()); return device.createShaderModule(createInfo); From c27f25aa06f776c942a1c8037564c6d88bb58ac9 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 17:38:25 +0200 Subject: [PATCH 11/48] Allow 2 in-flight frames --- engine/src/VulkanTest.cpp | 40 ++++++++++++++++++++++++++------------- engine/src/VulkanTest.h | 16 +++++++++------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index c2caf34..89995df 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -43,14 +43,16 @@ void VulkanTest::initVulkan() { createGraphicsPipeline(); createFramebuffers(); createCommandPool(); - createCommandBuffer(); + createCommandBuffers(); createSyncObjects(); } void VulkanTest::cleanup() const { - device.destroySemaphore(renderFinishedSemaphore); - device.destroySemaphore(imageAvailableSemaphore); - device.destroyFence(inFlightFence); + for (size_t i = 0; i < maxFramesInFlight; i++) { + device.destroySemaphore(renderFinishedSemaphores[i]); + device.destroySemaphore(imageAvailableSemaphores[i]); + device.destroyFence(inFlightFences[i]); + } device.destroyCommandPool(commandPool); for (const auto& framebuffer : swapChainFramebuffers) { device.destroyFramebuffer(framebuffer); @@ -69,7 +71,7 @@ void VulkanTest::cleanup() const { glfwTerminate(); } -void VulkanTest::mainLoop() const { +void VulkanTest::mainLoop() { while (!glfwWindowShouldClose(window)) { glfwPollEvents(); drawFrame(); @@ -77,7 +79,12 @@ void VulkanTest::mainLoop() const { device.waitIdle(); } -void VulkanTest::drawFrame() const { +void VulkanTest::drawFrame() { + const vk::Semaphore imageAvailableSemaphore = imageAvailableSemaphores[currentFrame]; + const vk::Semaphore renderFinishedSemaphore = renderFinishedSemaphores[currentFrame]; + const vk::Fence inFlightFence = inFlightFences[currentFrame]; + const vk::CommandBuffer commandBuffer = commandBuffers[currentFrame]; + if (device.waitForFences(1, &inFlightFence, true, std::numeric_limits::max()) != vk::Result::eSuccess) { throw std::runtime_error("Failed to wait for fence"); } @@ -115,6 +122,7 @@ void VulkanTest::drawFrame() const { if (presentQueue.presentKHR(&presentInfo) != vk::Result::eSuccess) { throw std::runtime_error("Failed to present swap chain image"); } + currentFrame = (currentFrame + 1) % maxFramesInFlight; } void VulkanTest::createInstance() { @@ -488,22 +496,28 @@ void VulkanTest::createCommandPool() { } } -void VulkanTest::createCommandBuffer() { +void VulkanTest::createCommandBuffers() { vk::CommandBufferAllocateInfo allocInfo{}; allocInfo.commandPool = commandPool; allocInfo.level = vk::CommandBufferLevel::ePrimary; - allocInfo.commandBufferCount = 1; - commandBuffer = device.allocateCommandBuffers(allocInfo)[0]; + allocInfo.commandBufferCount = maxFramesInFlight; + commandBuffers = device.allocateCommandBuffers(allocInfo); } void VulkanTest::createSyncObjects() { - vk::SemaphoreCreateInfo semaphoreInfo{}; - imageAvailableSemaphore = device.createSemaphore(semaphoreInfo); - renderFinishedSemaphore = device.createSemaphore(semaphoreInfo); + imageAvailableSemaphores.resize(maxFramesInFlight); + renderFinishedSemaphores.resize(maxFramesInFlight); + inFlightFences.resize(maxFramesInFlight); + const vk::SemaphoreCreateInfo semaphoreInfo{}; vk::FenceCreateInfo fenceInfo{}; fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; - inFlightFence = device.createFence(fenceInfo); + + for (size_t i = 0; i < maxFramesInFlight; i++) { + imageAvailableSemaphores[i] = device.createSemaphore(semaphoreInfo); + renderFinishedSemaphores[i] = device.createSemaphore(semaphoreInfo); + inFlightFences[i] = device.createFence(fenceInfo); + } } void VulkanTest::recordCommandBuffer(const vk::CommandBuffer commandBuffer, const unsigned int imageIndex) const { diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h index f8af268..c279880 100644 --- a/engine/src/VulkanTest.h +++ b/engine/src/VulkanTest.h @@ -10,8 +10,8 @@ class VulkanTest { void initWindow(); void initVulkan(); void cleanup() const; - void mainLoop() const; - void drawFrame() const; + void mainLoop(); + void drawFrame(); void createInstance(); bool checkValidationLayerSupport() const; @@ -24,7 +24,7 @@ class VulkanTest { void createGraphicsPipeline(); void createFramebuffers(); void createCommandPool(); - void createCommandBuffer(); + void createCommandBuffers(); void createSyncObjects(); void recordCommandBuffer(vk::CommandBuffer commandBuffer, unsigned int imageIndex) const; @@ -75,11 +75,13 @@ class VulkanTest { vk::Pipeline graphicsPipeline; std::vector swapChainFramebuffers; vk::CommandPool commandPool; - vk::CommandBuffer commandBuffer; - vk::Semaphore imageAvailableSemaphore; - vk::Semaphore renderFinishedSemaphore; - vk::Fence inFlightFence; + static constexpr unsigned int maxFramesInFlight = 2; + std::vector commandBuffers; + std::vector imageAvailableSemaphores; + std::vector renderFinishedSemaphores; + std::vector inFlightFences; + unsigned int currentFrame = 0; const std::vector validationLayers = {"VK_LAYER_KHRONOS_validation"}; const std::vector deviceExtensions = {vk::KHRSwapchainExtensionName}; From 7a9d1d59cde2e03ab83467e51c41e0500a09910c Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 18:16:46 +0200 Subject: [PATCH 12/48] Handle resizes --- engine/src/VulkanTest.cpp | 56 ++++++++++++++++++++++++++++----------- engine/src/VulkanTest.h | 2 ++ 2 files changed, 42 insertions(+), 16 deletions(-) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index 89995df..060170c 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -27,7 +27,6 @@ void VulkanTest::initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(1920, 1080, "Vulkan", nullptr, nullptr); } @@ -48,22 +47,16 @@ void VulkanTest::initVulkan() { } void VulkanTest::cleanup() const { + cleanupSwapChain(); + device.destroyPipeline(graphicsPipeline); + device.destroyPipelineLayout(pipelineLayout); + device.destroyRenderPass(renderPass); for (size_t i = 0; i < maxFramesInFlight; i++) { device.destroySemaphore(renderFinishedSemaphores[i]); device.destroySemaphore(imageAvailableSemaphores[i]); device.destroyFence(inFlightFences[i]); } device.destroyCommandPool(commandPool); - for (const auto& framebuffer : swapChainFramebuffers) { - device.destroyFramebuffer(framebuffer); - } - device.destroyPipeline(graphicsPipeline); - device.destroyPipelineLayout(pipelineLayout); - device.destroyRenderPass(renderPass); - for (const auto& imageView : swapChainImageViews) { - device.destroyImageView(imageView); - } - device.destroySwapchainKHR(swapChain); device.destroy(); instance.destroySurfaceKHR(surface); instance.destroy(); @@ -88,13 +81,22 @@ void VulkanTest::drawFrame() { if (device.waitForFences(1, &inFlightFence, true, std::numeric_limits::max()) != vk::Result::eSuccess) { throw std::runtime_error("Failed to wait for fence"); } + + const auto acquireResult = device.acquireNextImageKHR(swapChain, std::numeric_limits::max(), imageAvailableSemaphore, nullptr); + if (acquireResult.result == vk::Result::eErrorOutOfDateKHR) { + recreateSwapChain(); + return; + } + if (acquireResult.result != vk::Result::eSuccess && acquireResult.result != vk::Result::eSuboptimalKHR) { + throw std::runtime_error("Failed to acquire swap chain image"); + } + if (device.resetFences(1, &inFlightFence) != vk::Result::eSuccess) { throw std::runtime_error("Failed to reset fence"); } - unsigned int imageIndex = device.acquireNextImageKHR(swapChain, std::numeric_limits::max(), imageAvailableSemaphore, nullptr).value; commandBuffer.reset(); - recordCommandBuffer(commandBuffer, imageIndex); + recordCommandBuffer(commandBuffer, acquireResult.value); vk::SubmitInfo submitInfo{}; vk::Semaphore waitSemaphores[] = {imageAvailableSemaphore}; @@ -118,10 +120,14 @@ void VulkanTest::drawFrame() { vk::SwapchainKHR swapChains[] = {swapChain}; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = swapChains; - presentInfo.pImageIndices = &imageIndex; - if (presentQueue.presentKHR(&presentInfo) != vk::Result::eSuccess) { + presentInfo.pImageIndices = &acquireResult.value; + const auto presentResult = presentQueue.presentKHR(&presentInfo); + if (presentResult == vk::Result::eErrorOutOfDateKHR || presentResult == vk::Result::eSuboptimalKHR) { + recreateSwapChain(); + } else if (presentResult != vk::Result::eSuccess) { throw std::runtime_error("Failed to present swap chain image"); } + currentFrame = (currentFrame + 1) % maxFramesInFlight; } @@ -287,6 +293,24 @@ void VulkanTest::createSwapChain() { swapChainExtent = extent; } +void VulkanTest::cleanupSwapChain() const { + for (const auto& framebuffer : swapChainFramebuffers) { + device.destroyFramebuffer(framebuffer); + } + for (const auto& imageView : swapChainImageViews) { + device.destroyImageView(imageView); + } + device.destroySwapchainKHR(swapChain); +} + +void VulkanTest::recreateSwapChain() { + device.waitIdle(); + cleanupSwapChain(); + createSwapChain(); + createImageViews(); + createFramebuffers(); +} + void VulkanTest::createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for (size_t i = 0; i < swapChainImages.size(); i++) { @@ -469,7 +493,7 @@ void VulkanTest::createFramebuffers() { swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { const vk::ImageView attachments[] = {swapChainImageViews[i]}; - +std::cout << "Creating framebuffer " << i << " with size " << swapChainExtent.width << "x" << swapChainExtent.height << std::endl; vk::FramebufferCreateInfo framebufferInfo{}; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; diff --git a/engine/src/VulkanTest.h b/engine/src/VulkanTest.h index c279880..6b9268d 100644 --- a/engine/src/VulkanTest.h +++ b/engine/src/VulkanTest.h @@ -19,6 +19,8 @@ class VulkanTest { void createLogicalDevice(); void createSurface(); void createSwapChain(); + void cleanupSwapChain() const; + void recreateSwapChain(); void createImageViews(); void createRenderPass(); void createGraphicsPipeline(); From b68aae1426b0dfa416243a8625f294f29cff39e8 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 18:19:23 +0200 Subject: [PATCH 13/48] Handle minimized window --- engine/src/VulkanTest.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index 060170c..ae3fe13 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -304,6 +304,13 @@ void VulkanTest::cleanupSwapChain() const { } void VulkanTest::recreateSwapChain() { + int width = 0, height = 0; + glfwGetFramebufferSize(window, &width, &height); + while (width == 0 || height == 0) { + glfwGetFramebufferSize(window, &width, &height); + glfwWaitEvents(); + } + device.waitIdle(); cleanupSwapChain(); createSwapChain(); From 83e4d857fdb88a1cf4e8d2be3ec964e0e47eaab0 Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 18:42:33 +0200 Subject: [PATCH 14/48] Fix vector size debug check --- engine/src/images/LinearImage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/images/LinearImage.cpp b/engine/src/images/LinearImage.cpp index afc7b3a..e8866a9 100644 --- a/engine/src/images/LinearImage.cpp +++ b/engine/src/images/LinearImage.cpp @@ -50,7 +50,7 @@ LinearImage::LinearImage(const std::string& path) : Image(0, 0, 3, sizeof(float) this->data = std::make_unique(width * height * this->bytesPerPixel); std::vector row; - row.reserve(width * 4); // RGBE + row.resize(width * 4); // RGBE // read the first 3 bytes of the file to check if the file is using RLE compression const std::streampos position = imageStream.tellg(); From e96b4fb6217e9e526fb3240d733d9f75bdd2b04e Mon Sep 17 00:00:00 2001 From: Lorenzo Lapucci Date: Sun, 5 Oct 2025 23:07:24 +0200 Subject: [PATCH 15/48] Start refactoring Textures to support more APIs --- engine/src/VulkanTest.cpp | 3 +- engine/src/images/ImageUtils.cpp | 35 +++++ engine/src/images/ImageUtils.h | 1 + engine/src/layers/EditorLayer.cpp | 2 +- engine/src/layers/SceneLayer.cpp | 1 + engine/src/rendering/Renderer.h | 3 +- engine/src/rendering/Texture.cpp | 14 ++ engine/src/rendering/Texture.h | 118 +++++++++++++-- engine/src/rendering/opengl/OpenGLDataType.h | 143 ++++++++++++++---- .../src/rendering/opengl/OpenGLRenderer.cpp | 9 +- engine/src/rendering/opengl/OpenGLRenderer.h | 3 +- engine/src/rendering/opengl/OpenGLTexture.cpp | 81 ++++++++++ engine/src/rendering/opengl/OpenGLTexture.h | 26 ++++ .../src/rendering/opengl/OpenGLTextureCube.h | 3 + .../rendering/opengl/OpenGLTextureCubeArray.h | 2 + .../rendering/opengl/OpenGLVertexArray.cpp | 2 +- engine/windows/WinSandbox.cpp | 2 +- 17 files changed, 401 insertions(+), 47 deletions(-) create mode 100644 engine/src/rendering/opengl/OpenGLTexture.cpp create mode 100644 engine/src/rendering/opengl/OpenGLTexture.h diff --git a/engine/src/VulkanTest.cpp b/engine/src/VulkanTest.cpp index ae3fe13..7f21928 100644 --- a/engine/src/VulkanTest.cpp +++ b/engine/src/VulkanTest.cpp @@ -3,7 +3,7 @@ #include -int main() { +int mainVulkan() { VulkanTest app; try { @@ -500,7 +500,6 @@ void VulkanTest::createFramebuffers() { swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { const vk::ImageView attachments[] = {swapChainImageViews[i]}; -std::cout << "Creating framebuffer " << i << " with size " << swapChainExtent.width << "x" << swapChainExtent.height << std::endl; vk::FramebufferCreateInfo framebufferInfo{}; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; diff --git a/engine/src/images/ImageUtils.cpp b/engine/src/images/ImageUtils.cpp index f143761..f92b5b8 100644 --- a/engine/src/images/ImageUtils.cpp +++ b/engine/src/images/ImageUtils.cpp @@ -1,6 +1,41 @@ #include "pch/enginepch.h" #include "ImageUtils.h" +#include + +Ref ImageUtils::loadTextureFromFile(const Ref& renderer, const std::string& path) { + const bool isHDR = path.ends_with(".hdr"); + int width; + int height; + int channels; + + stbi_set_flip_vertically_on_load(true); + void* data; + if (isHDR) { + data = stbi_loadf(path.c_str(), &width, &height, &channels, 0); + } else { + data = stbi_load(path.c_str(), &width, &height, &channels, 0); + } + + if (!data) { + const char* error = stbi_failure_reason(); + DE_ERROR("Failed to read texture file {0} - {1}", path, error); + } + + const Texture::Format format = !isHDR && channels > 3 ? Texture::Format::RGBA : Texture::RGB; + const Texture::InternalFormat internalFormat = isHDR ? (channels > 3 ? Texture::InternalFormat::RGBA16F : Texture::InternalFormat::RGB16F) + : (channels > 3 ? Texture::InternalFormat::RGBA8 : Texture::InternalFormat::RGB8); + Ref texture = Texture::builder() // + .setWidth(width) + .setHeight(height) + .setFormat(format) + .setInternalFormat(internalFormat) + .setSourcePath(path) + .build(renderer); + stbi_image_free(data); + return texture; +} + Ref ImageUtils::acesFilmicTonemapping(const Ref& image) { auto result = std::make_shared(image->getWidth(), image->getHeight(), nullptr); for (unsigned int x = 0; x < image->getWidth(); x++) { diff --git a/engine/src/images/ImageUtils.h b/engine/src/images/ImageUtils.h index 5bb6395..4ca580f 100644 --- a/engine/src/images/ImageUtils.h +++ b/engine/src/images/ImageUtils.h @@ -3,5 +3,6 @@ class ImageUtils { public: + static Ref loadTextureFromFile(const Ref& renderer, const std::string& path); static Ref acesFilmicTonemapping(const Ref& image); }; diff --git a/engine/src/layers/EditorLayer.cpp b/engine/src/layers/EditorLayer.cpp index f4081d3..d1144cb 100644 --- a/engine/src/layers/EditorLayer.cpp +++ b/engine/src/layers/EditorLayer.cpp @@ -5,7 +5,7 @@ EditorLayer::EditorLayer(const std::unique_ptr& ctx) : Layer(ctx) { Ref uiEntity = this->scene->createEntity("CEF Editor UI"); - const auto uiMaterial = Material(ctx->renderer->createTexture2D(4, 1, 1, 1, BGRA, std::array{0, 0, 0, 0}.data())); + const auto uiMaterial = Material(ctx->renderer->createTexture2D(4, 1, 1, 1, Texture::BGRA, std::array{0, 0, 0, 0}.data())); this->uiMesh = Plane::create(ctx->renderer, uiMaterial); uiEntity->add(uiMaterial.albedo); Script& uiScript = uiEntity->add