changes for ImGui
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
#include "imgui_renderer.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_vulkan.h>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
ImGuiRenderer::ImGuiRenderer(Window& window, Device& device,
|
||||
vk::Format colorAttachmentFormat,
|
||||
u32 minImageCount) {
|
||||
// 1. Construct ImGui context
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
|
||||
ImGui_ImplGlfw_InitForVulkan(window.getGLFWwindow(), true);
|
||||
|
||||
ImGui_ImplVulkan_InitInfo initInfo = {};
|
||||
device.populateImGuiInitInfo(initInfo);
|
||||
initInfo.DescriptorPoolSize =
|
||||
IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE + 5;
|
||||
initInfo.MinImageCount = minImageCount;
|
||||
initInfo.ImageCount = minImageCount;
|
||||
|
||||
VkFormat rawColorAttachmentFormat =
|
||||
static_cast<VkFormat>(colorAttachmentFormat);
|
||||
initInfo.UseDynamicRendering = true;
|
||||
initInfo.PipelineInfoMain.PipelineRenderingCreateInfo.sType =
|
||||
VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR;
|
||||
initInfo.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount =
|
||||
1;
|
||||
initInfo.PipelineInfoMain.PipelineRenderingCreateInfo
|
||||
.pColorAttachmentFormats = &rawColorAttachmentFormat;
|
||||
|
||||
ImGui_ImplVulkan_Init(&initInfo);
|
||||
|
||||
// 2. Enable dark theme
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
// 3. Enable docking / keyboard
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
|
||||
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
|
||||
|
||||
/**
|
||||
* When viewports are enabled we tweak WindowRounding/WindowBg so
|
||||
* platform windows can look identical to regular ones.
|
||||
*/
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
||||
style.WindowRounding = 0.0f;
|
||||
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiRenderer::~ImGuiRenderer() {
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
void ImGuiRenderer::beginFrame() {
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui_ImplGlfw_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
|
||||
ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(),
|
||||
ImGuiDockNodeFlags_PassthruCentralNode);
|
||||
}
|
||||
|
||||
void ImGuiRenderer::endFrame(vk::CommandBuffer commandBuffer) {
|
||||
ImGui::Render();
|
||||
ImDrawData* mainDrawData = ImGui::GetDrawData();
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
||||
ImGui::UpdatePlatformWindows();
|
||||
ImGui::RenderPlatformWindowsDefault();
|
||||
}
|
||||
|
||||
ImGui_ImplVulkan_RenderDrawData(mainDrawData, commandBuffer);
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <sophia/types.hpp>
|
||||
|
||||
#include "device.hpp"
|
||||
#include "window.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class ImGuiRenderer {
|
||||
public:
|
||||
ImGuiRenderer(const ImGuiRenderer&) = delete;
|
||||
ImGuiRenderer& operator=(const ImGuiRenderer&) = delete;
|
||||
|
||||
ImGuiRenderer(Window& window, Device& device,
|
||||
vk::Format colorAttachmentFormat, u32 minImageCount);
|
||||
~ImGuiRenderer();
|
||||
|
||||
void beginFrame();
|
||||
void endFrame(vk::CommandBuffer commandBuffer);
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -45,6 +45,9 @@ class RenderContext {
|
||||
vk::Format getImageFormat() const {
|
||||
return this->swapchain->getImageFormat();
|
||||
}
|
||||
u32 getImageCount() const {
|
||||
return static_cast<u32>(this->swapchain->imageCount());
|
||||
}
|
||||
|
||||
private:
|
||||
void recreateSwapchain();
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <sophia/ui/debug_ui.hpp>
|
||||
|
||||
#include "panels/frame_stats_panel.hpp"
|
||||
#include "panels/panel_registry_panel.hpp"
|
||||
|
||||
namespace sophia {
|
||||
|
||||
DebugUI::DebugUI(PanelRegistry& registry) : registry{registry} {
|
||||
// #ifndef NDEBUG
|
||||
this->panelRegistryHandle = registry.emplace<PanelRegistryPanel>(registry);
|
||||
this->frameStatsHandle = registry.emplace<FrameStatsPanel>();
|
||||
// #endif // !NDEBUG
|
||||
}
|
||||
|
||||
void DebugUI::show() {
|
||||
showPanelRegistry(true);
|
||||
showFrameStats(true);
|
||||
}
|
||||
|
||||
void DebugUI::hide() {
|
||||
showPanelRegistry(false);
|
||||
showFrameStats(false);
|
||||
}
|
||||
|
||||
void DebugUI::showPanelRegistry(bool show) {
|
||||
if (registry.isValid(panelRegistryHandle)) {
|
||||
registry.get(panelRegistryHandle).setVisible(show);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugUI::showFrameStats(bool show) {
|
||||
if (registry.isValid(frameStatsHandle)) {
|
||||
registry.get(frameStatsHandle).setVisible(show);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,88 @@
|
||||
#include <sophia/ui/panel_registry.hpp>
|
||||
#include <sophia/util/assert.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
namespace {
|
||||
constexpr u32 GENERATION_MASK = (1u << 12) - 1;
|
||||
} // namespace
|
||||
|
||||
PanelHandle PanelRegistry::add(std::unique_ptr<Panel> panel) {
|
||||
if (!freeList.empty()) {
|
||||
u32 index = freeList.back();
|
||||
freeList.pop_back();
|
||||
slots[index].panel = std::move(panel);
|
||||
return PanelHandle{index, slots[index].generation};
|
||||
}
|
||||
|
||||
slots.push_back(Slot{std::move(panel), 0});
|
||||
return PanelHandle{static_cast<u32>(slots.size() - 1), 0};
|
||||
}
|
||||
|
||||
const Panel& PanelRegistry::get(PanelHandle handle) const {
|
||||
SOPHIA_ASSERT(handle.isValid(), "invalid PanelHandle");
|
||||
SOPHIA_ASSERT(handle.index < slots.size(),
|
||||
"PanelHandle index out of range:", handle.index);
|
||||
|
||||
const Slot& slot = slots[handle.index];
|
||||
SOPHIA_ASSERT(slot.generation == handle.generation,
|
||||
"stale PanelHandle (generation mismatch):", handle.index);
|
||||
SOPHIA_ASSERT(slot.panel != nullptr,
|
||||
"PanelHandle refers to a removed panel:", handle.index);
|
||||
return *slot.panel;
|
||||
}
|
||||
|
||||
void PanelRegistry::remove(PanelHandle handle) {
|
||||
SOPHIA_ASSERT(handle.isValid(), "invalid PanelHandle");
|
||||
SOPHIA_ASSERT(handle.index < slots.size(),
|
||||
"PanelHandle index out of range:", handle.index);
|
||||
|
||||
Slot& slot = slots[handle.index];
|
||||
SOPHIA_ASSERT(slot.generation == handle.generation,
|
||||
"stale PanelHandle (generation mismatch):", handle.index);
|
||||
SOPHIA_ASSERT(slot.panel != nullptr,
|
||||
"PanelHandle already removed:", handle.index);
|
||||
|
||||
slot.panel.reset();
|
||||
slot.generation = (slot.generation + 1) & GENERATION_MASK;
|
||||
freeList.push_back(handle.index);
|
||||
}
|
||||
|
||||
bool PanelRegistry::isValid(PanelHandle handle) const {
|
||||
if (!handle.isValid() || handle.index >= slots.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Slot& slot = slots[handle.index];
|
||||
return slot.generation == handle.generation && slot.panel != nullptr;
|
||||
}
|
||||
|
||||
u32 PanelRegistry::size() const {
|
||||
u32 count = 0;
|
||||
for (const Slot& slot : slots) {
|
||||
if (slot.panel) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
u32 PanelRegistry::visibleCount() const {
|
||||
u32 count = 0;
|
||||
for (const Slot& slot : slots) {
|
||||
if (slot.panel && slot.panel->isVisible()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void PanelRegistry::update(const FrameInfo& frameInfo) {
|
||||
for (auto& slot : slots) {
|
||||
if (slot.panel && slot.panel->isVisible()) {
|
||||
slot.panel->onUpdate(frameInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,57 @@
|
||||
#include "ecs_inspector_panel.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sophia/components.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
void EcsInspectorPanel::onUpdate(const FrameInfo&) {
|
||||
ImGui::Begin("ECS Inspector");
|
||||
|
||||
entityScratch.clear();
|
||||
for (entt::entity entity : registry.storage<entt::entity>()) {
|
||||
entityScratch.push_back(entity);
|
||||
}
|
||||
|
||||
ImGui::BeginChild("EntityList", ImVec2(0, 200), true);
|
||||
ImGuiListClipper clipper;
|
||||
clipper.Begin(static_cast<int>(entityScratch.size()));
|
||||
while (clipper.Step()) {
|
||||
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
|
||||
entt::entity entity = entityScratch[static_cast<size_t>(i)];
|
||||
const TagComponent* tag = registry.try_get<TagComponent>(entity);
|
||||
std::string label = tag ? tag->tag : "Entity";
|
||||
|
||||
ImGui::PushID(static_cast<int>(entt::to_integral(entity)));
|
||||
if (ImGui::Selectable(label.c_str(), selected == entity)) {
|
||||
selected = entity;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (registry.valid(selected)) {
|
||||
if (auto* transform = registry.try_get<TransformComponent>(selected)) {
|
||||
ImGui::DragFloat3("Position", &transform->position.x, 0.1f);
|
||||
ImGui::DragFloat3("Scale", &transform->scale.x, 0.1f);
|
||||
ImGui::Text("Rotation: %.2f, %.2f, %.2f, %.2f", transform->rotation.w,
|
||||
transform->rotation.x, transform->rotation.y,
|
||||
transform->rotation.z);
|
||||
}
|
||||
|
||||
if (auto* mesh = registry.try_get<MeshComponent>(selected)) {
|
||||
ImGui::Text("Mesh: index=%u generation=%u", mesh->handle.index,
|
||||
mesh->handle.generation);
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("No entity selected");
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <entt/entt.hpp>
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class EcsInspectorPanel : public Panel {
|
||||
public:
|
||||
explicit EcsInspectorPanel(entt::registry& registry)
|
||||
: Panel{"ECS Inspector"}, registry{registry} {}
|
||||
|
||||
void onUpdate(const FrameInfo& frameInfo) override;
|
||||
|
||||
private:
|
||||
entt::registry& registry;
|
||||
entt::entity selected = entt::null;
|
||||
std::vector<entt::entity> entityScratch;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class FrameStatsPanel : public Panel {
|
||||
public:
|
||||
FrameStatsPanel() : Panel{"Frame Stats"} {}
|
||||
|
||||
void onUpdate(const FrameInfo& frameInfo) override {
|
||||
frameCount++;
|
||||
accumulatedTime += frameInfo.deltaTime;
|
||||
|
||||
if (frameInfo.elapsedTime >= nextRefreshTime) {
|
||||
displayedFps = static_cast<double>(frameCount) / accumulatedTime;
|
||||
displayedFrameTimeMs =
|
||||
(accumulatedTime / static_cast<double>(frameCount)) * 1000.0;
|
||||
|
||||
frameCount = 0;
|
||||
accumulatedTime = 0.0;
|
||||
nextRefreshTime = frameInfo.elapsedTime + REFRESH_INTERVAL;
|
||||
}
|
||||
|
||||
ImGui::Begin("Frame Stats");
|
||||
ImGui::Text("FPS: %.1f", displayedFps);
|
||||
ImGui::Text("Frame time: %.3f ms", displayedFrameTimeMs);
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr double REFRESH_INTERVAL = 1.0;
|
||||
|
||||
double accumulatedTime = 0.0;
|
||||
u32 frameCount = 0;
|
||||
double nextRefreshTime = 0.0;
|
||||
double displayedFps = 0.0;
|
||||
double displayedFrameTimeMs = 0.0;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "panel_registry_panel.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
void PanelRegistryPanel::onUpdate(const FrameInfo&) {
|
||||
ImGui::Begin("PanelRegistry");
|
||||
ImGui::Text("%u panels (%u visible)", registry.size(),
|
||||
registry.visibleCount());
|
||||
ImGui::Separator();
|
||||
|
||||
for (std::string_view name : registry.names()) {
|
||||
Panel* panel = registry.find(name);
|
||||
if (!panel || panel == this) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool visible = panel->isVisible();
|
||||
if (ImGui::Checkbox(name.data(), &visible)) {
|
||||
panel->setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace sophia
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <sophia/frame_info.hpp>
|
||||
#include <sophia/ui/panel.hpp>
|
||||
#include <sophia/ui/panel_registry.hpp>
|
||||
|
||||
namespace sophia {
|
||||
|
||||
class PanelRegistryPanel : public Panel {
|
||||
public:
|
||||
explicit PanelRegistryPanel(PanelRegistry& registry)
|
||||
: Panel{"PanelRegistry"}, registry{registry} {}
|
||||
|
||||
void onUpdate(const FrameInfo& frameInfo) override;
|
||||
|
||||
private:
|
||||
PanelRegistry& registry;
|
||||
};
|
||||
|
||||
} // namespace sophia
|
||||
Reference in New Issue
Block a user