diff --git a/README.md b/README.md index 68c6acd..50b1df7 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,18 @@ A Vulkan based renderer created with Vulkan 1.3 in C++. - [X] Load Material Data - [ ] Load Animation Data - [ ] Load Camera - - [ ] Load Lights - [ ] Support Specular Materials - [X] Bindless Descriptors - [X] Simplified Descriptor Creation Pipeline - [ ] Debugging - [ ] Tracy Integration - - [ ] Dear ImGui Integration + - [X] Dear ImGui Integration - [ ] Transparency - [ ] Sorted - [ ] Order Independent - [ ] Shadows v1 - [ ] Omnidirectional Cubemap Shadows - - [ ] Spot Lights + - [ ] SpotLight Shadows - [ ] Directional Shadows - [ ] Cascaded Shadows - [ ] PCF diff --git a/aster/window.cpp b/aster/window.cpp index edc2876..3198b5c 100644 --- a/aster/window.cpp +++ b/aster/window.cpp @@ -8,6 +8,12 @@ #include "context.h" #include "logger.h" +void +Window::RequestExit() const noexcept +{ + glfwSetWindowShouldClose(m_Window, true); +} + void Window::SetWindowSize(const vk::Extent2D &extent) const noexcept { diff --git a/aster/window.h b/aster/window.h index db3262f..565b6d7 100644 --- a/aster/window.h +++ b/aster/window.h @@ -27,6 +27,7 @@ struct Window final return !glfwWindowShouldClose(m_Window); } + void RequestExit() const noexcept; void SetWindowSize(const vk::Extent2D &extent) const noexcept; void SetWindowSize(u32 width, u32 height) const noexcept; /// Actual size of the framebuffer being used for the window render. diff --git a/samples/00_util/CMakeLists.txt b/samples/00_util/CMakeLists.txt index 806117b..fdd2ffa 100644 --- a/samples/00_util/CMakeLists.txt +++ b/samples/00_util/CMakeLists.txt @@ -2,13 +2,18 @@ cmake_minimum_required(VERSION 3.13) +find_package(imgui CONFIG REQUIRED) + add_library(util_helper STATIC helpers.h helpers.cpp frame.cpp frame.h gpu_resource_manager.cpp - gpu_resource_manager.h) + gpu_resource_manager.h + gui.cpp + gui.h) target_link_libraries(util_helper PRIVATE aster_core) +target_link_libraries(util_helper PRIVATE imgui::imgui) target_include_directories(util_helper PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/samples/00_util/gui.cpp b/samples/00_util/gui.cpp new file mode 100644 index 0000000..ee8048a --- /dev/null +++ b/samples/00_util/gui.cpp @@ -0,0 +1,213 @@ +// ============================================= +// Aster: gui.cpp +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#include "gui.h" + +#include "context.h" +#include "device.h" +#include "helpers.h" +#include "image.h" +#include "window.h" + +#include +#include +#include + +namespace ImGui +{ +vk::Format g_AttachmentFormat; +vk::DescriptorPool g_DescriptorPool; + +void +VulkanAssert(VkResult result) +{ + AbortIfFailed(result); +} + +void +Init(const Context *context, const Device *device, const Window *window, vk::Format attachmentFormat, + const u32 imageCount, const u32 queueFamily, const vk::Queue queue) +{ + g_AttachmentFormat = attachmentFormat; + + eastl::vector poolSizes = { + {vk::DescriptorType::eSampler, 1000}, + {vk::DescriptorType::eCombinedImageSampler, 1000}, + {vk::DescriptorType::eSampledImage, 1000}, + {vk::DescriptorType::eStorageImage, 1000}, + {vk::DescriptorType::eUniformTexelBuffer, 1000}, + {vk::DescriptorType::eStorageTexelBuffer, 1000}, + {vk::DescriptorType::eUniformBuffer, 1000}, + {vk::DescriptorType::eStorageBuffer, 1000}, + {vk::DescriptorType::eUniformBufferDynamic, 1000}, + {vk::DescriptorType::eStorageBufferDynamic, 1000}, + {vk::DescriptorType::eInputAttachment, 1000}, + }; + + const vk::DescriptorPoolCreateInfo descriptorPoolCreateInfo = { + .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = 1000, + .poolSizeCount = Cast(poolSizes.size()), + .pPoolSizes = poolSizes.data(), + }; + + AbortIfFailed(device->m_Device.createDescriptorPool(&descriptorPoolCreateInfo, nullptr, &g_DescriptorPool)); + + IMGUI_CHECKVERSION(); + CreateContext(); + ImGuiIO &io = GetIO(); + (void)io; + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + // io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Viewports bad + + StyleColorsDark(); + + ImGui_ImplGlfw_InitForVulkan(window->m_Window, true); + + vk::PipelineRenderingCreateInfo renderingCreateInfo = { + .colorAttachmentCount = 1, + .pColorAttachmentFormats = &g_AttachmentFormat, + }; + + ImGui_ImplVulkan_InitInfo imguiVulkanInitInfo = { + .Instance = context->m_Instance, + .PhysicalDevice = device->m_PhysicalDevice, + .Device = device->m_Device, + .QueueFamily = queueFamily, + .Queue = queue, + .DescriptorPool = g_DescriptorPool, + .MinImageCount = imageCount, + .ImageCount = imageCount, + .PipelineCache = nullptr, + .UseDynamicRendering = true, + .PipelineRenderingCreateInfo = renderingCreateInfo, + .Allocator = nullptr, + .CheckVkResultFn = VulkanAssert, + }; + ImGui_ImplVulkan_Init(&imguiVulkanInitInfo); + + ImGui_ImplVulkan_CreateFontsTexture(); +} + +void +Destroy(const Device *device) +{ + + ImGui_ImplVulkan_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + DestroyContext(); + + device->m_Device.destroy(Take(g_DescriptorPool), nullptr); +} + +void +StartBuild() +{ + ImGui_ImplVulkan_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + NewFrame(); + + static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_None | ImGuiDockNodeFlags_PassthruCentralNode; + + // We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into, + // because it would be confusing to have two docking targets within each others. + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDocking; + + const ImGuiViewport *viewport = GetMainViewport(); + SetNextWindowPos(viewport->WorkPos); + SetNextWindowSize(viewport->WorkSize); + SetNextWindowViewport(viewport->ID); + PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); + PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); + windowFlags |= + ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove; + windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + windowFlags |= ImGuiWindowFlags_NoBackground; + + // Important: note that we proceed even if Begin() returns false (aka window is collapsed). + // This is because we want to keep our DockSpace() active. If a DockSpace() is inactive, + // all active windows docked into it will lose their parent and become undocked. + // We cannot preserve the docking relationship between an active window and an inactive docking, otherwise + // any change of dockspace/settings would lead to windows being stuck in limbo and never being visible. + PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + Begin("DockSpace Demo", nullptr, windowFlags); + PopStyleVar(); + + PopStyleVar(2); + + // DockSpace + if (GetIO().ConfigFlags & ImGuiConfigFlags_DockingEnable) + { + const ImGuiID dockspaceId = GetID("MyDockSpace"); + DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags); + } +} + +void +EndBuild() +{ + End(); + Render(); + + EndFrame(); + if (GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + GLFWwindow *backupCurrentContext = glfwGetCurrentContext(); + UpdatePlatformWindows(); + RenderPlatformWindowsDefault(); + glfwMakeContextCurrent(backupCurrentContext); + } +} + +void +Draw(const vk::CommandBuffer commandBuffer, const AttachmentImage *attachmentImage) +{ + // OPTICK_EVENT(); + + constexpr vk::DebugUtilsLabelEXT label = { + .pLabelName = "UI pass", + .color = std::array{0.0f, 0.0f, 1.0f, 1.0f}, + }; + commandBuffer.beginDebugUtilsLabelEXT(&label); + vk::RenderingAttachmentInfo attachmentInfo = { + .imageView = attachmentImage->m_View, + .imageLayout = vk::ImageLayout::eColorAttachmentOptimal, + .resolveMode = vk::ResolveModeFlagBits::eNone, + .loadOp = vk::AttachmentLoadOp::eLoad, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = vk::ClearColorValue{0.0f, 0.0f, 0.0f, 1.0f}, + }; + + const vk::RenderingInfo renderingInfo = { + .renderArea = {.extent = ToExtent2D(attachmentImage->m_Extent)}, + .layerCount = 1, + .colorAttachmentCount = 1, + .pColorAttachments = &attachmentInfo, + .pDepthAttachment = nullptr, + }; + + commandBuffer.beginRendering(&renderingInfo); + + ImGui_ImplVulkan_RenderDrawData(GetDrawData(), commandBuffer); + + commandBuffer.endRendering(); + + commandBuffer.endDebugUtilsLabelEXT(); +} + +void +PushDisable() +{ + PushItemFlag(ImGuiItemFlags_Disabled, true); + PushStyleVar(ImGuiStyleVar_Alpha, GetStyle().Alpha * 0.5f); +} + +void +PopDisable() +{ + PopStyleVar(); + PopItemFlag(); +} +} // namespace ImGui \ No newline at end of file diff --git a/samples/00_util/gui.h b/samples/00_util/gui.h new file mode 100644 index 0000000..82075c7 --- /dev/null +++ b/samples/00_util/gui.h @@ -0,0 +1,34 @@ +// ============================================= +// Aster: gui.h +// Copyright (c) 2020-2024 Anish Bhobe +// ============================================= + +#pragma once + +#include "global.h" + +#include + +struct AttachmentImage; +struct Device; +struct Context; +struct Window; +struct Swapchain; + +// ReSharper disable once CppInconsistentNaming +namespace ImGui +{ +void Init(const Context *context, const Device *device, const Window *window, vk::Format attachmentFormat, + u32 imageCount, u32 queueFamily, vk::Queue queue); +void Destroy(const Device *device); + +void Recreate(); +void StartBuild(); +void EndBuild(); +void Draw(vk::CommandBuffer commandBuffer, const AttachmentImage *attachmentImage); + +void PushDisable(); +void PopDisable(); +} // namespace ImGui + +namespace gui = ImGui; \ No newline at end of file diff --git a/samples/03_model_render/model_render.cpp b/samples/03_model_render/model_render.cpp index 230cf4c..051206f 100644 --- a/samples/03_model_render/model_render.cpp +++ b/samples/03_model_render/model_render.cpp @@ -19,6 +19,7 @@ #include "light_manager.h" #include "gpu_resource_manager.h" +#include "gui.h" #include "model_loader.h" #include "pipeline_utils.h" @@ -214,6 +215,10 @@ main(int, char **) } } + gui::Init(&context, &device, &window, attachmentFormat, frameManager.m_FramesInFlight, queueAllocation.m_Family, + commandQueue); + bool rotating = false; + Time::Init(); INFO("Starting loop"); @@ -221,8 +226,25 @@ main(int, char **) { Time::Update(); - model.SetModelTransform( - rotate(model.GetModelTransform(), Cast(45.0_deg * Time::m_Delta), vec3(0.0f, 1.0f, 0.0f))); + gui::StartBuild(); + + gui::Begin("Settings"); + gui::Text("Delta: %0.6f ms", 1000.0f * Time::m_Delta); + gui::Text("FPS: %0.6f", 1.0f / Time::m_Delta); + gui::Checkbox("Rotate", &rotating); + if (gui::Button("Exit")) + { + window.RequestExit(); + } + gui::End(); + + gui::EndBuild(); + + if (rotating) + { + model.SetModelTransform( + rotate(model.GetModelTransform(), Cast(45.0_deg * Time::m_Delta), vec3(0.0f, 1.0f, 0.0f))); + } model.Update(); ubo.Write(&device, 0, sizeof camera, &camera); @@ -300,18 +322,18 @@ main(int, char **) { u32 innerPcbOffset = pcbOffset; cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, innerPcbOffset, - sizeof prim.m_MaterialIdx, - &prim.m_MaterialIdx); + sizeof prim.m_MaterialIdx, &prim.m_MaterialIdx); innerPcbOffset += sizeof prim.m_MaterialIdx; cmd.pushConstants(pipeline.m_Layout, vk::ShaderStageFlagBits::eAll, innerPcbOffset, - sizeof prim.m_TransformIdx, - &prim.m_TransformIdx); + sizeof prim.m_TransformIdx, &prim.m_TransformIdx); innerPcbOffset += sizeof prim.m_TransformIdx; cmd.drawIndexed(prim.m_IndexCount, 1, prim.m_FirstIndex, Cast(prim.m_VertexOffset), 0); } cmd.endRendering(); + gui::Draw(cmd, currentAttachment); + cmd.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eAllCommands, {}, 0, nullptr, 0, nullptr, postRenderBarriers.size(), postRenderBarriers.data()); @@ -366,6 +388,8 @@ main(int, char **) AbortIfFailed(device.m_Device.waitIdle()); + gui::Destroy(&device); + for (auto &depthImage : depthImages) { depthImage.Destroy(&device); diff --git a/vcpkg.json b/vcpkg.json index 2db59ac..75046db 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -4,6 +4,14 @@ "fmt", "glfw3", "glm", + { + "name": "imgui", + "features": [ + "glfw-binding", + "vulkan-binding", + "docking-experimental" + ] + }, "scottt-debugbreak", "tinygltf", "vulkan-memory-allocator"