diff --git a/meson.build b/meson.build index 4e243f8..166a91f 100644 --- a/meson.build +++ b/meson.build @@ -41,6 +41,7 @@ executable( sources: [ 'src/main.cpp', 'src/camera/camera.cpp', + 'src/gui/imgui_manager.cpp', 'src/init/vulkan_instance.cpp', 'src/init/debug_messenger.cpp', 'src/init/device_manager.cpp', diff --git a/src/gui/imgui_manager.cpp b/src/gui/imgui_manager.cpp new file mode 100644 index 0000000..78b592c --- /dev/null +++ b/src/gui/imgui_manager.cpp @@ -0,0 +1,204 @@ +#include + +#include "imgui_manager.hpp" + +ImGuiManager::~ImGuiManager() { cleanup(); } + +void ImGuiManager::init( + vk::Instance instance, + vk::PhysicalDevice physicalDevice, + vk::Device device, + uint32_t queueFamily, + vk::Queue queue, + vk::RenderPass renderPass, + uint32_t minImageCount, + uint32_t imageCount, + vk::SampleCountFlagBits msaaSamples, + GLFWwindow* window +) { + mDevice = device; + + // Create ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + + ImGuiIO& imGuiIO = ImGui::GetIO(); + imGuiIO.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + imGuiIO.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; + imGuiIO.IniFilename = nullptr; // Disable writing imgui.ini + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + + // Initialize ImGui for GLFW and Vulkan + ImGui_ImplGlfw_InitForVulkan(window, true); + + vk::DescriptorPoolSize descriptorPoolSize { + .type = vk::DescriptorType::eCombinedImageSampler, + .descriptorCount = 1, + }; + + vk::DescriptorPoolCreateInfo poolInfo { + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = 1, + .poolSizeCount = 1, + .pPoolSizes = &descriptorPoolSize, + }; + + mDescriptorPool = device.createDescriptorPoolUnique(poolInfo); + + ImGui_ImplVulkan_InitInfo initInfo { + .Instance = instance, + .PhysicalDevice = physicalDevice, + .Device = device, + .QueueFamily = queueFamily, + .Queue = queue, + .DescriptorPool = mDescriptorPool.get(), + .RenderPass = renderPass, + .MinImageCount = minImageCount, + .ImageCount = imageCount, + .MSAASamples = static_cast(msaaSamples), + .PipelineCache = VK_NULL_HANDLE, + .Subpass = 0, + .UseDynamicRendering = false, + .PipelineRenderingCreateInfo = {}, + .Allocator = nullptr, + .CheckVkResultFn = nullptr, + .MinAllocationSize = 0, + }; + + ImGui_ImplVulkan_Init(&initInfo); + mInitialized = true; +} + +void ImGuiManager::cleanup() { + if (mInitialized) { + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + mInitialized = false; + } +} + +void ImGuiManager::newFrame() { + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + ImGui::Begin("Controls"); +} + +void ImGuiManager::render() { + ImGui::End(); + ImGui::Render(); + + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } +} + +void ImGuiManager::renderControls( + float& cameraSpeed, + float& fieldOfView, + bool& wireframeMode, + float& lineWidth, + float maxLineWidth, + bool wideLineSupport, + Camera& camera, + LightInfo& lightSettings, + bool& needsPipelineRecreation +) { + // Camera Settings + if (ImGui::CollapsingHeader("Camera Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::SliderFloat("Camera Speed", &cameraSpeed, 0.1F, 10.0F, "%.1f"); + ImGui::SliderFloat("Field of View", &fieldOfView, 45.0F, 120.0F, "%.1f"); + if (ImGui::Button("Reset Camera")) + camera = Camera(); + } + + // Rendering Settings + if (ImGui::CollapsingHeader("Rendering Settings", ImGuiTreeNodeFlags_DefaultOpen)) { + if (ImGui::Checkbox("Wireframe Mode", &wireframeMode)) + needsPipelineRecreation = true; + + if (wireframeMode) { + if (wideLineSupport) { + if (ImGui::SliderFloat("Line Width", &lineWidth, 1.0F, maxLineWidth, "%.1f")) { + needsPipelineRecreation = true; + } + } else { + ImGui::TextColored(ImVec4(1.0F, 0.5F, 0.5F, 1.0F), "Wide lines not supported on this device"); + lineWidth = 1.0F; + } + } + } + + renderControlsHelp(); + renderFrameMetrics(); + renderCameraInfo(camera); + renderLightControls(lightSettings); +} + +void ImGuiManager::renderControlsHelp() { + if (ImGui::CollapsingHeader("Controls", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::BulletText("Use mouse to look around"); + ImGui::BulletText("WASD to move horizontally"); + ImGui::BulletText("Space/Shift to move up/down"); + ImGui::BulletText("ESC to toggle mouse capture"); + } +} + +void ImGuiManager::renderFrameMetrics() { + if (ImGui::CollapsingHeader("Performance", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Text( + "Application average %.3f ms/frame (%.1f FPS)", + static_cast(1000.0F / ImGui::GetIO().Framerate), + static_cast(ImGui::GetIO().Framerate) + ); + } +} + +void ImGuiManager::renderMemoryUsage( + size_t vertexSize, + size_t indexSize, + size_t vertexCount, + size_t indexCount +) { + ImGui::Separator(); + ImGui::Text("Memory Usage:"); + ImGui::Text( + "Vertex Buffer: %.2f MB", + (static_cast(vertexCount) * static_cast(vertexSize)) / (1024.0 * 1024.0) + ); + ImGui::Text( + "Index Buffer: %.2f MB", + (static_cast(indexCount) * static_cast(indexSize)) / (1024.0 * 1024.0) + ); +} + +void ImGuiManager::renderCameraInfo(const Camera& camera) { + ImGui::Separator(); + ImGui::Text("Camera Information:"); + ImGui::Text( + "Position: (%.2f, %.2f, %.2f)", camera.getPosition().x, camera.getPosition().y, camera.getPosition().z + ); + ImGui::Text( + "Front Vector: (%.2f, %.2f, %.2f)", camera.getFront().x, camera.getFront().y, camera.getFront().z + ); + ImGui::Text("Yaw: %.2f°, Pitch: %.2f°", camera.getYaw(), camera.getPitch()); +} + +void ImGuiManager::renderLightControls(LightInfo& lightSettings) { + ImGui::Separator(); + ImGui::Text("Light Controls:"); + + // Light Position + ImGui::DragFloat3("Light Position", &lightSettings.position.x, 0.1F, -10.0F, 10.0F); + + // Light Color + ImGui::ColorEdit3("Light Color", &lightSettings.color.x); + + // Light Strengths + ImGui::SliderFloat("Ambient Strength", &lightSettings.ambient_strength, 0.0F, 1.0F); + ImGui::SliderFloat("Specular Strength", &lightSettings.specular_strength, 0.0F, 1.0F); +} diff --git a/src/gui/imgui_manager.hpp b/src/gui/imgui_manager.hpp new file mode 100644 index 0000000..3bf9b70 --- /dev/null +++ b/src/gui/imgui_manager.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +#define VULKAN_HPP_NO_CONSTRUCTORS +#include + +#include "../camera/camera.hpp" +#include "../structs/light_info.hpp" + +class ImGuiManager { + public: + ImGuiManager() = default; + ImGuiManager(const ImGuiManager&) = delete; + ImGuiManager(ImGuiManager&&) = delete; + fn operator=(const ImGuiManager&)->ImGuiManager& = delete; + fn operator=(ImGuiManager&&)->ImGuiManager& = delete; + ~ImGuiManager(); + + void init( + vk::Instance instance, + vk::PhysicalDevice physicalDevice, + vk::Device device, + uint32_t queueFamily, + vk::Queue queue, + vk::RenderPass renderPass, + uint32_t minImageCount, + uint32_t imageCount, + vk::SampleCountFlagBits msaaSamples, + GLFWwindow* window + ); + + fn cleanup() -> void; + static fn newFrame() -> void; + static fn render() -> void; + static fn renderControls( + float& cameraSpeed, + float& fieldOfView, + bool& wireframeMode, + float& lineWidth, + float maxLineWidth, + bool wideLineSupport, + Camera& camera, + LightInfo& lightSettings, + bool& needsPipelineRecreation + ) -> void; + static fn renderMemoryUsage(size_t vertexSize, size_t indexSize, size_t vertexCount, size_t indexCount) + -> void; + + private: + vk::UniqueDescriptorPool mDescriptorPool; + vk::Device mDevice = nullptr; + bool mInitialized = false; + + static fn renderControlsHelp() -> void; + static fn renderFrameMetrics() -> void; + static fn renderCameraInfo(const Camera& camera) -> void; + static fn renderLightControls(LightInfo& lightSettings) -> void; +}; diff --git a/src/main.cpp b/src/main.cpp index f457587..0faaf70 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,9 +41,7 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE #include "window/window_manager.hpp" // Window manager // ImGui headers for GUI -#include -#include -#include +#include "gui/imgui_manager.hpp" using namespace constants; @@ -76,9 +74,7 @@ class VulkanApp { } // Shut down ImGui - ImGui_ImplVulkan_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); + mImGuiManager.cleanup(); } private: @@ -152,9 +148,8 @@ class VulkanApp { std::vector mCameraUniformBuffersMemory; ///< Memory for camera uniform buffers std::vector mCameraUniformBuffersMapped; ///< Mapped camera uniform buffers - vk::UniqueDescriptorPool mDescriptorPool; ///< Descriptor pool for the application - vk::UniqueDescriptorPool mImGuiDescriptorPool; ///< Separate descriptor pool for ImGui - std::vector mDescriptorSets; ///< Descriptor sets for binding resources + vk::UniqueDescriptorPool mDescriptorPool; ///< Descriptor pool for the application + std::vector mDescriptorSets; ///< Descriptor sets for binding resources std::vector mCommandBuffers; ///< Command buffers for drawing commands @@ -193,6 +188,8 @@ class VulkanApp { glm::mat4 mView; ///< View matrix + ImGuiManager mImGuiManager; ///< ImGui manager for GUI rendering + static fn processInput(vkfw::Window& window, Camera& camera, const f32& deltaTime, const f32& cameraSpeed) -> void { if (window.getKey(vkfw::Key::eW) == vkfw::eTrue) @@ -337,74 +334,20 @@ class VulkanApp { createCommandBuffers(); // Create command buffers for rendering commands createCrosshairBuffers(); // Create crosshair buffers createSyncObjects(); // Create synchronization objects (semaphores and fences) - initImGui(); // Initialize Dear ImGui for GUI rendering - } - /** - * @brief Initializes the ImGui library for use with GLFW and Vulkan. - * - * This function sets up ImGui for rendering with Vulkan and GLFW. It creates the ImGui context, - * sets up the style, initializes ImGui for GLFW and Vulkan, and creates a descriptor pool for ImGui. - */ - fn initImGui() -> void { - // Create ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - - // Remember to use a reference here, otherwise - // the ImGui::GetIO() function will return a - // copy and the changes won't be saved. - ImGuiIO& imGuiIO = ImGui::GetIO(); - - // Enable docking and viewports - imGuiIO.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking - imGuiIO.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport - - // Disable writing imgui.ini - imGuiIO.IniFilename = nullptr; - - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - - // Initialize ImGui for GLFW and Vulkan - ImGui_ImplGlfw_InitForVulkan(mWindow.get(), true); - - vk::DescriptorPoolSize descriptorPoolSize = { - .type = vk::DescriptorType::eCombinedImageSampler, - .descriptorCount = 1, - }; - - vk::DescriptorPoolCreateInfo poolInfo { - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = 1, - .poolSizeCount = 1, - .pPoolSizes = &descriptorPoolSize, - }; - - mImGuiDescriptorPool = mDevice->createDescriptorPoolUnique(poolInfo); - - ImGui_ImplVulkan_InitInfo initInfo = { - .Instance = mInstance.get(), - .PhysicalDevice = mPhysicalDevice, - .Device = mDevice.get(), - .QueueFamily = - DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get()).graphics_family.value(), - .Queue = mGraphicsQueue, - .DescriptorPool = mImGuiDescriptorPool.get(), - .RenderPass = mRenderPass.get(), - .MinImageCount = MAX_FRAMES_IN_FLIGHT, - .ImageCount = static_cast(mSwapChainImages.size()), - .MSAASamples = static_cast(mMsaaSamples), - .PipelineCache = VK_NULL_HANDLE, - .Subpass = 0, - .UseDynamicRendering = false, - .PipelineRenderingCreateInfo = {}, - .Allocator = nullptr, - .CheckVkResultFn = nullptr, - .MinAllocationSize = 0, - }; - - ImGui_ImplVulkan_Init(&initInfo); + // Initialize ImGui + mImGuiManager.init( + mInstance.get(), + mPhysicalDevice, + mDevice.get(), + DeviceManager::findQueueFamilies(mPhysicalDevice, mSurface.get()).graphics_family.value(), + mGraphicsQueue, + mRenderPass.get(), + MAX_FRAMES_IN_FLIGHT, + static_cast(mSwapChainImages.size()), + mMsaaSamples, + mWindow.get() + ); } /** @@ -430,9 +373,25 @@ class VulkanApp { updateFrameStats(lastFpsUpdate, frameCounter); vkfw::pollEvents(); - setupImGuiFrame(); - renderImGuiControls(); - finalizeImGuiFrame(); + mImGuiManager.newFrame(); + bool needsPipelineRecreation = false; + mImGuiManager.renderControls( + mCameraSpeed, + mFieldOfView, + mWireframeMode, + mLineWidth, + mMaxLineWidth, + mWideLineSupport, + mCamera, + mLightSettings, + needsPipelineRecreation + ); + mImGuiManager.renderMemoryUsage(sizeof(Vertex), sizeof(uint32_t), mVertices.size(), mIndices.size()); + if (needsPipelineRecreation) { + mDevice->waitIdle(); + createGraphicsPipeline(); + } + mImGuiManager.render(); drawFrame(); } @@ -440,106 +399,6 @@ class VulkanApp { mDevice->waitIdle(); } - fn renderImGuiControls() -> void { - // Camera Settings - if (ImGui::CollapsingHeader("Camera Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderFloat("Camera Speed", &mCameraSpeed, 0.1F, 10.0F, "%.1f"); - ImGui::SliderFloat("Field of View", &mFieldOfView, 45.0F, 120.0F, "%.1f"); - if (ImGui::Button("Reset Camera")) - mCamera = Camera(); - } - - // Rendering Settings - if (ImGui::CollapsingHeader("Rendering Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - if (ImGui::Checkbox("Wireframe Mode", &mWireframeMode)) - recreateSwapChain(); - - if (mWireframeMode) { - if (mWideLineSupport) { - if (ImGui::SliderFloat("Line Width", &mLineWidth, 1.0F, mMaxLineWidth, "%.1f")) { - mDevice->waitIdle(); - createGraphicsPipeline(); - } - } else { - ImGui::TextColored(ImVec4(1.0F, 0.5F, 0.5F, 1.0F), "Wide lines not supported on this device"); - mLineWidth = 1.0F; - } - } - } - - renderControlsHelp(); - renderPerformanceMetrics(); - } - - static fn renderControlsHelp() -> void { - if (ImGui::CollapsingHeader("Controls", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::BulletText("Use mouse to look around"); - ImGui::BulletText("WASD to move horizontally"); - ImGui::BulletText("Space/Shift to move up/down"); - ImGui::BulletText("ESC to toggle mouse capture"); - } - } - - fn renderPerformanceMetrics() -> void { - if (ImGui::CollapsingHeader("Performance", ImGuiTreeNodeFlags_DefaultOpen)) { - renderFrameMetrics(); - renderMemoryUsage(); - renderCameraInfo(); - renderLightControls(); - } - } - - static fn renderFrameMetrics() -> void { - ImGui::Text( - "Application average %.3f ms/frame (%.1f FPS)", - static_cast(1000.0F / ImGui::GetIO().Framerate), - static_cast(ImGui::GetIO().Framerate) - ); - } - - fn renderMemoryUsage() -> void { - ImGui::Separator(); - ImGui::Text("Memory Usage:"); - ImGui::Text( - "Vertex Buffer: %.2f MB", - (static_cast(mVertices.size()) * static_cast(sizeof(Vertex))) / (1024.0 * 1024.0) - ); - ImGui::Text( - "Index Buffer: %.2f MB", - (static_cast(mIndices.size()) * static_cast(sizeof(uint32_t))) / (1024.0 * 1024.0) - ); - } - - fn renderCameraInfo() -> void { - ImGui::Separator(); - ImGui::Text("Camera Information:"); - ImGui::Text( - "Position: (%.2f, %.2f, %.2f)", - mCamera.getPosition().x, - mCamera.getPosition().y, - mCamera.getPosition().z - ); - ImGui::Text( - "Front Vector: (%.2f, %.2f, %.2f)", mCamera.getFront().x, mCamera.getFront().y, mCamera.getFront().z - ); - ImGui::Text("Yaw: %.2f°, Pitch: %.2f°", mCamera.getYaw(), mCamera.getPitch()); - } - - fn renderLightControls() -> void { - ImGui::Separator(); - ImGui::Text("Light Controls:"); - - // Light Position - ImGui::DragFloat3("Light Position", &mLightSettings.position.x, 0.1F, -10.0F, 10.0F); - - // Light Color - ImGui::ColorEdit3("Light Color", &mLightSettings.color.x); - - // Light Strengths - ImGui::SliderFloat("Ambient Strength", &mLightSettings.ambient_strength, 0.0F, 1.0F); - ImGui::SliderFloat("Specular Strength", &mLightSettings.specular_strength, 0.0F, 1.0F); - } - fn updateFrameStats(f64& lastFpsUpdate, i32& frameCounter) -> void { f64 currentFrame = vkfw::getTime(); if (currentFrame - lastFpsUpdate > 1.0) { @@ -553,23 +412,6 @@ class VulkanApp { ++frameCounter; } - static fn setupImGuiFrame() -> void { - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - ImGui::Begin("Controls"); - } - - static fn finalizeImGuiFrame() -> void { - ImGui::End(); - ImGui::Render(); - - if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { - ImGui::UpdatePlatformWindows(); - ImGui::RenderPlatformWindowsDefault(); - } - } - /** * @brief Cleans up the swap chain resources. *