diff --git a/nvfetcher.toml b/nvfetcher.toml index 94899bc..2a9fc5e 100644 --- a/nvfetcher.toml +++ b/nvfetcher.toml @@ -1,3 +1,8 @@ [fmt] src.github = "fmtlib/fmt" fetch.github = "fmtlib/fmt" + +[imgui] +src.git = "https://github.com/ocornut/imgui.git" +src.branch = "docking" +fetch.github = "ocornut/imgui" diff --git a/src/main.cpp b/src/main.cpp index 8f8dd55..2dc7720 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,7 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE // Include custom utility headers +#include "util/constants.hpp" // Constants definitions #include "util/crosshair.hpp" // Crosshair definitions #include "util/shaders.hpp" // Compiled shader code #include "util/types.hpp" // Custom type definitions @@ -38,34 +39,7 @@ VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE #define VKFW_NO_STRUCT_CONSTRUCTORS // Use aggregate initialization for GLFW structs #include "vkfw.hpp" // Include GLFW C++ bindings -// Constants for window dimensions -constexpr i32 WIDTH = 1920; -constexpr i32 HEIGHT = 1080; - -// CAMERA_SPEED of camera movement -constexpr f64 CAMERA_SPEED = 1.0; - -// Maximum number of frames that can be processed concurrently -constexpr i32 MAX_FRAMES_IN_FLIGHT = 2; - -// Shader file paths -constexpr const char* VERTEX_SHADER_PATH = "shaders/vertex.glsl"; -constexpr const char* FRAGMENT_SHADER_PATH = "shaders/fragment.glsl"; -constexpr const char* CROSSHAIR_VERTEX_SHADER_PATH = "shaders/crosshair_vertex.glsl"; -constexpr const char* CROSSHAIR_FRAGMENT_SHADER_PATH = "shaders/crosshair_fragment.glsl"; - -// Validation layers for debug builds -#ifndef NDEBUG -constexpr std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; -#endif - -// Required device extensions (platform-specific) -#ifdef __APPLE__ -constexpr std::array deviceExtensions = { vk::KHRSwapchainExtensionName, - vk::KHRPortabilitySubsetExtensionName }; -#else -constexpr std::array deviceExtensions = { vk::KHRSwapchainExtensionName }; -#endif +using namespace constants; /** * @brief The Vulkan application class. @@ -204,9 +178,12 @@ class VulkanApp { bool mCursorCaptured = true; ///< Flag indicating if cursor is captured // ImGui-related state - f32 mCameraSpeed = CAMERA_SPEED; ///< Current camera speed - f32 mFieldOfView = 90.0F; ///< Current field of view - bool mWireframeMode = false; ///< Wireframe rendering mode + f32 mCameraSpeed = CAMERA_SPEED; ///< Current camera speed + f32 mFieldOfView = 90.0F; ///< Current field of view + bool mWireframeMode = false; ///< Wireframe rendering mode + f32 mLineWidth = 1.0F; ///< Line width for wireframe mode + f32 mMaxLineWidth = 1.0F; ///< Maximum supported line width + bool mWideLineSupport = false; ///< Whether wide lines are supported /** * @brief Struct to store queue family indices. @@ -551,6 +528,10 @@ class VulkanApp { // 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; @@ -617,116 +598,12 @@ class VulkanApp { processInput(mWindow.get(), mCamera, static_cast(deltaTime), mCameraSpeed); mView = mCamera.getViewMatrix(); - if (currentFrame - lastFpsUpdate > 1.0) { - mWindow->setTitle( - fmt::format("Vulkan - {:.0f}FPS", static_cast(frameCounter / (currentFrame - lastFpsUpdate))) - ); - lastFpsUpdate = currentFrame; - frameCounter = 0; - } - ++frameCounter; - + updateFrameStats(lastFpsUpdate, frameCounter); vkfw::pollEvents(); - // Start the ImGui frame - ImGui_ImplVulkan_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - // Create ImGui window with controls - ImGui::Begin("Controls"); - - // Camera Position - auto pos = mCamera.getPosition(); - ImGui::Text("Camera Position: (%.2f, %.2f, %.2f)", pos.x, pos.y, pos.z); - - // 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(); // Reset to default position - } - } - - // Rendering Settings - if (ImGui::CollapsingHeader("Rendering Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - // Wireframe Mode - if (ImGui::Checkbox("Wireframe Mode", &mWireframeMode)) { - recreateSwapChain(); - } - - // Line Width (only in wireframe mode) - static float LineWidth = 1.0F; - if (mWireframeMode) { - if (ImGui::SliderFloat("Line Width", &LineWidth, 1.0F, 5.0F, "%.1f")) { - // TODO: Update line width in pipeline - } - } - } - - // Controls Help - 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"); - ImGui::BulletText("Tab to toggle this menu"); - } - - // Performance Metrics - 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) - ); - - // Memory Usage - 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) - ); - - // Camera Information - 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()); - - // Light Controls - 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); - } - - ImGui::End(); - - // Render ImGui - ImGui::Render(); + setupImGuiFrame(); + renderImGuiControls(); + finalizeImGuiFrame(); drawFrame(); } @@ -734,6 +611,138 @@ 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"); + ImGui::BulletText("Tab to toggle this menu"); + } + } + + 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) { + mWindow->setTitle( + fmt::format("Vulkan - {:.0f}FPS", static_cast(frameCounter / (currentFrame - lastFpsUpdate))) + ); + lastFpsUpdate = currentFrame; + frameCounter = 0; + } + ++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. * @@ -912,6 +921,16 @@ class VulkanApp { if (isDeviceSuitable(device)) { mPhysicalDevice = device; mMsaaSamples = getMaxUsableSampleCount(); + + // Get the device properties for line width limits + vk::PhysicalDeviceProperties deviceProperties = device.getProperties(); + mMaxLineWidth = deviceProperties.limits.lineWidthRange[1]; + mWideLineSupport = deviceProperties.limits.lineWidthRange[1] > 1.0F; + +#ifndef NDEBUG + fmt::println("Maximum supported line width: {}", mMaxLineWidth); + fmt::println("Wide lines supported: {}", mWideLineSupport ? "yes" : "no"); +#endif break; } } @@ -1249,7 +1268,7 @@ class VulkanApp { .cullMode = vk::CullModeFlagBits::eBack, .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, - .lineWidth = mWireframeMode ? 2.0F : 1.0F, // Thicker lines in wireframe mode + .lineWidth = mWireframeMode ? mLineWidth : 1.0F, // Thicker lines in wireframe mode }; vk::PipelineMultisampleStateCreateInfo multisampling { @@ -2045,7 +2064,7 @@ class VulkanApp { attrib.vertices[static_cast((3 * index.vertex_index) + 2)], }, .color = { 1.0F, 1.0F, 1.0F }, - .texCoord = { + .tex_coord = { attrib.texcoords[static_cast((2 * index.texcoord_index) + 0)], 1.0F - attrib.texcoords[static_cast((2 * index.texcoord_index) + 1)], }, diff --git a/src/util/constants.hpp b/src/util/constants.hpp new file mode 100644 index 0000000..ef907c1 --- /dev/null +++ b/src/util/constants.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +#include "types.hpp" + +namespace constants { + // Window dimensions + constexpr i32 WIDTH = 1920; + constexpr i32 HEIGHT = 1080; + + // Camera settings + constexpr f64 CAMERA_SPEED = 1.0; + + // Frame processing + constexpr i32 MAX_FRAMES_IN_FLIGHT = 2; + + // Shader paths + constexpr const char* VERTEX_SHADER_PATH = "shaders/vertex.glsl"; + constexpr const char* FRAGMENT_SHADER_PATH = "shaders/fragment.glsl"; + constexpr const char* CROSSHAIR_VERTEX_SHADER_PATH = "shaders/crosshair_vertex.glsl"; + constexpr const char* CROSSHAIR_FRAGMENT_SHADER_PATH = "shaders/crosshair_fragment.glsl"; + +// Validation layers +#ifndef NDEBUG + constexpr std::array validationLayers = { "VK_LAYER_KHRONOS_validation" }; +#endif + +// Device extensions +#ifdef __APPLE__ + constexpr std::array deviceExtensions = { vk::KHRSwapchainExtensionName, + vk::KHRPortabilitySubsetExtensionName }; +#else + constexpr std::array deviceExtensions = { vk::KHRSwapchainExtensionName }; +#endif +} diff --git a/src/util/vertex.hpp b/src/util/vertex.hpp index dfead00..882754a 100644 --- a/src/util/vertex.hpp +++ b/src/util/vertex.hpp @@ -27,7 +27,7 @@ struct Vertex { glm::vec3 pos; ///< Position of the vertex in 3D space (x, y, z) glm::vec3 color; ///< RGB color values, each component in range [0, 1] - glm::vec2 texCoord; ///< Texture coordinates (u, v) for texture mapping + glm::vec2 tex_coord; ///< Texture coordinates (u, v) for texture mapping glm::vec3 normal; ///< Normal vector of the vertex /** @@ -51,7 +51,8 @@ struct Vertex { /** * @brief Provides attribute descriptions for vertex data interpretation. * - * @return std::array Array of descriptions for position, color, texture coordinates, and normal. + * @return std::array Array of descriptions for position, color, + * texture coordinates, and normal. * * The attribute descriptions specify: * - Location indices (0 for position, 1 for color, 2 for texture coordinates, 3 for normal) @@ -62,29 +63,29 @@ struct Vertex { static fn getAttributeDescriptions() -> std::array { return { vk::VertexInputAttributeDescription { - .location = 0, - .binding = 0, - .format = vk::Format::eR32G32B32Sfloat, - .offset = offsetof(Vertex, pos), - }, + .location = 0, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, pos), + }, vk::VertexInputAttributeDescription { - .location = 1, - .binding = 0, - .format = vk::Format::eR32G32B32Sfloat, - .offset = offsetof(Vertex, color), - }, + .location = 1, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, color), + }, vk::VertexInputAttributeDescription { - .location = 2, - .binding = 0, - .format = vk::Format::eR32G32Sfloat, - .offset = offsetof(Vertex, texCoord), - }, + .location = 2, + .binding = 0, + .format = vk::Format::eR32G32Sfloat, + .offset = offsetof(Vertex, tex_coord), + }, vk::VertexInputAttributeDescription { - .location = 3, - .binding = 0, - .format = vk::Format::eR32G32B32Sfloat, - .offset = offsetof(Vertex, normal), - }, + .location = 3, + .binding = 0, + .format = vk::Format::eR32G32B32Sfloat, + .offset = offsetof(Vertex, normal), + }, }; } @@ -95,7 +96,7 @@ struct Vertex { * @return bool True if vertices are identical in position, color, texture coordinates, and normal. */ fn operator==(const Vertex& other) const->bool { - return pos == other.pos && color == other.color && texCoord == other.texCoord && normal == other.normal; + return pos == other.pos && color == other.color && tex_coord == other.tex_coord && normal == other.normal; } }; @@ -117,8 +118,7 @@ namespace std { */ fn operator()(Vertex const& vertex) const->size_t { return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ - (hash()(vertex.texCoord) << 1) ^ - (hash()(vertex.normal) << 2); + (hash()(vertex.tex_coord) << 1) ^ (hash()(vertex.normal) << 2); } }; }