feat: add imgui texture inspector (Thanks claude)

This commit is contained in:
2026-10-11 01:34:49 +02:00
parent 95d1497169
commit 170af2a4e9
18 changed files with 692 additions and 18 deletions
+1
View File
@@ -33,6 +33,7 @@ set(SRC_FILES
"src/Graphics/Swapchain.cpp"
"src/Graphics/Util.cpp"
"src/Graphics/RenderResources.cpp"
"src/Graphics/ResourcesInspector.cpp"
"src/Graphics/Managers/VulkanInstanceManager.cpp"
"src/Graphics/Managers/MemoryManager.cpp"
+15
View File
@@ -11,6 +11,7 @@
#include <destrum/Graphics/GfxDevice.h>
#include <destrum/Graphics/Renderer.h>
#include <destrum/Graphics/Pipelines/ImguiPass.h>
#include <destrum/Graphics/ResourcesInspector.h>
#include <destrum/Input/InputManager.h>
@@ -41,6 +42,9 @@ public:
virtual void onWindowResize(int, int) {};
// Called inside the "Windows" menu of the main menu bar, add toggles for the game's own windows here
virtual void customWindowsMenu() {}
protected:
SDL_Window* window{nullptr};
AppParams m_params{};
@@ -72,6 +76,17 @@ protected:
float m_PhysicsTimeScale = 1.0f;
int m_PhysicsStepsLastFrame = 0;
struct DebugWindows {
bool stats{true};
bool physics{true};
bool inspector{true};
bool imageCache{false};
};
DebugWindows m_DebugWindows{};
ResourcesInspector m_ResourcesInspector;
void drawWindowsMenu();
void drawPhysicsPanel(float dt, float fixedDt, float accumulator);
};
@@ -1,8 +1,15 @@
#ifndef DESTRUM_IMGUI_PASS_H
#define DESTRUM_IMGUI_PASS_H
#include <array>
#include <utility>
#include <vector>
#include <vulkan/vulkan.h>
#include <SDL.h>
#include <imgui.h>
#include <destrum/Graphics/Swapchain.h>
class GfxDevice;
@@ -33,9 +40,19 @@ public:
[[nodiscard]] bool wantsMouse() const;
[[nodiscard]] bool wantsKeyboard() const;
// Wraps an image view for ImGui::Image in the frame that's being built. Call it every frame
// you draw the image, the descriptor set is freed again once the GPU is done with that frame.
// The image has to be in imageLayout by the time the UI gets rendered at the end of the frame.
[[nodiscard]] ImTextureID getFrameTexture(
VkImageView imageView,
VkImageLayout imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
private:
void initVulkanBackend();
void releaseTextures(std::vector<VkDescriptorSet>& textures);
void releaseAllTextures();
void transitionToColorAttachment(
VkCommandBuffer cmd,
VkImage image,
@@ -50,6 +67,17 @@ private:
VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
VkFormat colorFormat = VK_FORMAT_UNDEFINED;
struct FrameTexture {
VkImageView imageView{VK_NULL_HANDLE};
VkImageLayout imageLayout{VK_IMAGE_LAYOUT_UNDEFINED};
VkDescriptorSet descriptorSet{VK_NULL_HANDLE};
};
// created while building the current UI, not submitted yet
std::vector<FrameTexture> pendingTextures;
// submitted with a frame, freed once that frame's fence has been waited on
std::array<std::vector<VkDescriptorSet>, FRAMES_IN_FLIGHT> inFlightTextures;
bool initialized = false;
bool frameBegun = false;
bool contextCreated = false;
@@ -53,6 +53,7 @@ public:
[[nodiscard]] ImageID getWhiteTextureID() const { return whiteImageId; }
[[nodiscard]] ImageID getErrorTextureID() const { return errorImageId; }
[[nodiscard]] ImageID getDefaultNormalTextureID() const { return defaultNormalImageId; }
BindlessSetManager& getBindlessSetManager();
VkDescriptorSetLayout getBindlessDescSetLayout() const;
@@ -62,6 +63,10 @@ public:
VkPipelineLayout layout,
VkPipelineBindPoint bindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS) const;
[[nodiscard]] bool isInitialized() const { return imageCache != nullptr && materialCache != nullptr; }
const ImageCache& images() const { return *imageCache; }
MeshCache& meshes() { return *meshCache; }
const MeshCache& meshes() const { return *meshCache; }
@@ -0,0 +1,35 @@
#ifndef RESOURCESINSPECTOR_H
#define RESOURCESINSPECTOR_H
#include <destrum/Graphics/ids.h>
struct GPUImage;
class ImguiPass;
class RenderResources;
// ImGui windows for looking into the render resource caches
class ResourcesInspector final
{
public:
void drawImageCacheWindow(bool* open, const RenderResources& resources, ImguiPass& imguiPass);
private:
struct PreviewInfo
{
bool previewable{false};
const char* reason{""};
};
[[nodiscard]] static PreviewInfo getPreviewInfo(const GPUImage& image);
void drawImagePreview(const GPUImage& image, float maxSize, ImguiPass& imguiPass) const;
void drawImageDetails(ImageID id, const RenderResources& resources) const;
[[nodiscard]] bool matchesFilter(ImageID id, const RenderResources& resources) const;
char filter[128]{};
float thumbnailSize{48.0f};
bool onlyPreviewable{false};
ImageID selectedImageId{NULL_IMAGE_ID};
};
#endif //RESOURCESINSPECTOR_H
+1 -1
View File
@@ -33,7 +33,7 @@ public:
void FixedUpdate(float dt);
void LateUpdate(float dt);
void Render(const RenderContext& ctx);
void RenderImgui();
void RenderImgui(bool* open = nullptr);
void CleanupDestroyedGameObjects();
void Unload();
+1 -1
View File
@@ -23,7 +23,7 @@ public:
void LateUpdate(float dt);
void Render(const RenderContext& ctx);
void RenderImgui();
void RenderImgui(bool* open = nullptr);
void HandleGameObjectDestroy();
void DestroyGameObjects();
+2 -1
View File
@@ -21,7 +21,8 @@ namespace ImGuiUtils {
std::string_view sceneName,
const std::vector<std::shared_ptr<GameObject>>& objects,
const std::vector<std::shared_ptr<GameObject>>& pendingAdditions,
ObjectId& selectedObjectId);
ObjectId& selectedObjectId,
bool* open = nullptr);
}
#endif // DESTRUM_IMGUIUTILS_H
+51 -6
View File
@@ -195,13 +195,31 @@ if (!isRunning) break;
if (m_params.showDebugUi)
{
ZoneScopedN("Debug ImGui");
SceneManager::GetInstance().RenderImgui();
drawWindowsMenu();
ImGui::Begin("Debug");
ImGui::Text("FPS: %.2f", avgFPS);
ImGui::End();
if (m_DebugWindows.inspector)
{
SceneManager::GetInstance().RenderImgui(&m_DebugWindows.inspector);
}
drawPhysicsPanel(dt, fixedDt, accumulator);
if (m_DebugWindows.stats)
{
if (ImGui::Begin("Debug", &m_DebugWindows.stats))
{
ImGui::Text("FPS: %.2f", avgFPS);
}
ImGui::End();
}
if (m_DebugWindows.physics)
{
drawPhysicsPanel(dt, fixedDt, accumulator);
}
if (m_DebugWindows.imageCache)
{
m_ResourcesInspector.drawImageCacheWindow(&m_DebugWindows.imageCache, resources, imguiPass);
}
TracyPlot("FPS", avgFPS);
TracyPlot("Frame Time ms", dt * 1000.0f);
@@ -359,9 +377,36 @@ void App::cleanup()
}
}
void App::drawWindowsMenu()
{
// ImGui appends to the main menu bar, so this ends up next to whatever menus the game added
if (!ImGui::BeginMainMenuBar())
{
return;
}
if (ImGui::BeginMenu("Windows"))
{
ImGui::MenuItem("Debug", nullptr, &m_DebugWindows.stats);
ImGui::MenuItem("Physics", nullptr, &m_DebugWindows.physics);
ImGui::MenuItem("Inspector", nullptr, &m_DebugWindows.inspector);
ImGui::MenuItem("Image Cache", nullptr, &m_DebugWindows.imageCache);
customWindowsMenu();
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
void App::drawPhysicsPanel(float dt, float fixedDt, float accumulator)
{
ImGui::Begin("Physics");
if (!ImGui::Begin("Physics", &m_DebugWindows.physics))
{
ImGui::End();
return;
}
ImGui::Text("Frame dt: %.3f ms", dt * 1000.0f);
ImGui::Text("Fixed dt: %.3f ms", fixedDt * 1000.0f);
@@ -122,6 +122,15 @@ void ImguiPass::beginFrame() {
return;
}
// the previous UI was built but never rendered (e.g. the frame got skipped while resizing),
// so the GPU never saw these
std::vector<VkDescriptorSet> unusedTextures;
for (const FrameTexture& texture : pendingTextures) {
unusedTextures.push_back(texture.descriptorSet);
}
pendingTextures.clear();
releaseTextures(unusedTextures);
ImGui_ImplVulkan_NewFrame();
ImGui_ImplSDL2_NewFrame();
@@ -155,6 +164,18 @@ void ImguiPass::render(
return;
}
{
// beginFrame on the gfx device already waited for this frame index' fence,
// so the textures it used last time around can go
auto& inFlight = inFlightTextures[gfx->getCurrentFrameIndex() % FRAMES_IN_FLIGHT];
releaseTextures(inFlight);
for (const FrameTexture& texture : pendingTextures) {
inFlight.push_back(texture.descriptorSet);
}
pendingTextures.clear();
}
ImDrawData* drawData = ImGui::GetDrawData();
if (drawData == nullptr || drawData->CmdListsCount == 0) {
return;
@@ -200,6 +221,8 @@ void ImguiPass::onSwapchainRecreated() {
const auto newFormat = gfx->getSwapchainFormat();
if (newFormat != colorFormat) {
if (vulkanInitialized) {
// the swapchain recreate waited for the device, nothing is in flight anymore
releaseAllTextures();
ImGui_ImplVulkan_Shutdown();
vulkanInitialized = false;
}
@@ -224,6 +247,7 @@ void ImguiPass::cleanup() {
}
if (vulkanInitialized) {
releaseAllTextures();
ImGui_ImplVulkan_Shutdown();
vulkanInitialized = false;
}
@@ -293,3 +317,49 @@ void ImguiPass::transitionToColorAttachment(
vkCmdPipelineBarrier2(cmd, &dependencyInfo);
}
ImTextureID ImguiPass::getFrameTexture(VkImageView imageView, VkImageLayout imageLayout) {
if (!vulkanInitialized || imageView == VK_NULL_HANDLE) {
return ImTextureID{};
}
for (const FrameTexture& texture : pendingTextures) {
if (texture.imageView == imageView && texture.imageLayout == imageLayout) {
return reinterpret_cast<ImTextureID>(texture.descriptorSet);
}
}
const VkDescriptorSet descriptorSet = ImGui_ImplVulkan_AddTexture(imageView, imageLayout);
if (descriptorSet == VK_NULL_HANDLE) {
return ImTextureID{};
}
pendingTextures.push_back(FrameTexture{
.imageView = imageView,
.imageLayout = imageLayout,
.descriptorSet = descriptorSet,
});
return reinterpret_cast<ImTextureID>(descriptorSet);
}
void ImguiPass::releaseTextures(std::vector<VkDescriptorSet>& textures) {
if (vulkanInitialized) {
for (VkDescriptorSet descriptorSet : textures) {
ImGui_ImplVulkan_RemoveTexture(descriptorSet);
}
}
textures.clear();
}
void ImguiPass::releaseAllTextures() {
std::vector<VkDescriptorSet> textures;
for (const FrameTexture& texture : pendingTextures) {
textures.push_back(texture.descriptorSet);
}
pendingTextures.clear();
releaseTextures(textures);
for (auto& inFlight : inFlightTextures) {
releaseTextures(inFlight);
}
}
+2 -1
View File
@@ -205,7 +205,8 @@ void CubeMap::CreateCubeMap(
.mipMap = false,
.isCubemap = true
});
vkutil::addDebugLabel(gfxDevice.getDevice(), cubeMapImage.image, "skybox cubemap");
cubeMapImage.debugName = "skybox cubemap";
vkutil::addDebugLabel(gfxDevice.getDevice(), cubeMapImage.image, cubeMapImage.debugName.c_str());
std::array<VkImageView, 6> faceViews{};
+439
View File
@@ -0,0 +1,439 @@
#include <destrum/Graphics/ResourcesInspector.h>
#include <algorithm>
#include <cctype>
#include <charconv>
#include <system_error>
#include <cstring>
#include <string>
#include <string_view>
#include <vector>
#include <imgui.h>
#include <vulkan/vk_enum_string_helper.h>
#include <destrum/Graphics/Caches/ImageCache.h>
#include <destrum/Graphics/Caches/MaterialCache.h>
#include <destrum/Graphics/Pipelines/ImguiPass.h>
#include <destrum/Graphics/RenderResources.h>
namespace
{
[[nodiscard]] const char* StripPrefix(const char* name, const char* prefix)
{
const std::size_t length = std::strlen(prefix);
return std::strncmp(name, prefix, length) == 0 ? name + length : name;
}
[[nodiscard]] const char* FormatName(VkFormat format)
{
return StripPrefix(string_VkFormat(format), "VK_FORMAT_");
}
[[nodiscard]] const char* LayoutName(VkImageLayout layout)
{
return StripPrefix(string_VkImageLayout(layout), "VK_IMAGE_LAYOUT_");
}
[[nodiscard]] std::string UsageNames(VkImageUsageFlags usage)
{
struct UsageName
{
VkImageUsageFlagBits bit;
const char* name;
};
constexpr UsageName usageNames[] = {
{VK_IMAGE_USAGE_SAMPLED_BIT, "sampled"},
{VK_IMAGE_USAGE_STORAGE_BIT, "storage"},
{VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, "color attachment"},
{VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, "depth attachment"},
{VK_IMAGE_USAGE_TRANSFER_SRC_BIT, "transfer src"},
{VK_IMAGE_USAGE_TRANSFER_DST_BIT, "transfer dst"},
};
std::string result;
for (const UsageName& usageName : usageNames)
{
if ((usage & usageName.bit) == 0)
{
continue;
}
if (!result.empty())
{
result += ", ";
}
result += usageName.name;
}
return result.empty() ? "none" : result;
}
[[nodiscard]] bool IsDepthFormat(VkFormat format)
{
switch (format)
{
case VK_FORMAT_D16_UNORM:
case VK_FORMAT_X8_D24_UNORM_PACK32:
case VK_FORMAT_D32_SFLOAT:
case VK_FORMAT_D16_UNORM_S8_UINT:
case VK_FORMAT_D24_UNORM_S8_UINT:
case VK_FORMAT_D32_SFLOAT_S8_UINT:
return true;
default:
return false;
}
}
[[nodiscard]] bool ContainsCaseInsensitive(std::string_view haystack, std::string_view needle)
{
const auto it = std::search(
haystack.begin(), haystack.end(),
needle.begin(), needle.end(),
[](char a, char b)
{
return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b));
});
return it != haystack.end();
}
[[nodiscard]] const char* TextureIntentName(TextureIntent intent)
{
switch (intent)
{
case TextureIntent::ColorSrgb: return "color (sRGB)";
case TextureIntent::DataLinear: return "data (linear)";
}
return "unknown";
}
}
ResourcesInspector::PreviewInfo ResourcesInspector::getPreviewInfo(const GPUImage& image)
{
if (image.image == VK_NULL_HANDLE || image.imageView == VK_NULL_HANDLE)
{
return {false, "no image view"};
}
if ((image.usage & VK_IMAGE_USAGE_SAMPLED_BIT) == 0)
{
return {false, "not sampleable"};
}
if (image.isCubemap)
{
return {false, "cubemap"};
}
if (image.numLayers > 1)
{
return {false, "image array"};
}
if (image.getLayout() == VK_IMAGE_LAYOUT_UNDEFINED)
{
return {false, "never written"};
}
// ImGui reads the image when the UI gets rendered at the very end of the frame. Render targets
// like the draw / depth image are in an attachment or transfer layout by then.
if (image.getLayout() != VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
{
return {false, "render target"};
}
return {true, ""};
}
void ResourcesInspector::drawImagePreview(const GPUImage& image, float maxSize, ImguiPass& imguiPass) const
{
const float width = static_cast<float>(image.extent.width);
const float height = static_cast<float>(image.extent.height);
const float scale = maxSize / std::max({width, height, 1.0f});
const ImVec2 size{std::max(width * scale, 1.0f), std::max(height * scale, 1.0f)};
const PreviewInfo preview = getPreviewInfo(image);
if (preview.previewable)
{
const ImTextureID textureId = imguiPass.getFrameTexture(image.imageView);
if (textureId != ImTextureID{})
{
ImGui::ImageWithBg(textureId, size, ImVec2{0.0f, 0.0f}, ImVec2{1.0f, 1.0f}, ImVec4{0.1f, 0.1f, 0.1f, 1.0f});
return;
}
}
// placeholder box with the reason it can't be shown
const ImVec2 boxSize{maxSize, std::min(maxSize, std::max(size.y, ImGui::GetTextLineHeight() * 2.0f))};
const ImVec2 min = ImGui::GetCursorScreenPos();
const ImVec2 max{min.x + boxSize.x, min.y + boxSize.y};
ImDrawList* drawList = ImGui::GetWindowDrawList();
drawList->AddRectFilled(min, max, ImGui::GetColorU32(ImGuiCol_FrameBg));
drawList->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border));
// small thumbnails just get a dash, the reason is in the tooltip
const char* text = ImGui::CalcTextSize(preview.reason).x <= boxSize.x - 8.0f ? preview.reason : "-";
const ImVec2 textSize = ImGui::CalcTextSize(text);
drawList->AddText(
ImVec2{min.x + (boxSize.x - textSize.x) * 0.5f, min.y + (boxSize.y - textSize.y) * 0.5f},
ImGui::GetColorU32(ImGuiCol_TextDisabled),
text);
ImGui::Dummy(boxSize);
}
void ResourcesInspector::drawImageDetails(ImageID id, const RenderResources& resources) const
{
const ImageCache& cache = resources.images();
const GPUImage& image = cache.images[id];
ImGui::Text("Image ID: %u", static_cast<unsigned>(id));
ImGui::TextWrapped("Name: %s", image.debugName.empty() ? "<unnamed>" : image.debugName.c_str());
if (id == resources.getWhiteTextureID())
{
ImGui::TextColored(ImVec4{0.6f, 0.8f, 1.0f, 1.0f}, "Default white (also metallic/roughness + emissive default)");
}
if (id == resources.getErrorTextureID())
{
ImGui::TextColored(ImVec4{1.0f, 0.4f, 0.8f, 1.0f}, "Error texture (used when a load fails)");
}
if (id == resources.getDefaultNormalTextureID())
{
ImGui::TextColored(ImVec4{0.6f, 0.8f, 1.0f, 1.0f}, "Default normal map");
}
ImGui::Separator();
ImGui::Text("Size: %u x %u", image.extent.width, image.extent.height);
ImGui::Text("Format: %s%s", FormatName(image.format), IsDepthFormat(image.format) ? " (shown as red)" : "");
ImGui::Text("Mips: %u Layers: %u%s", image.mipLevels, image.numLayers, image.isCubemap ? " cubemap" : "");
ImGui::Text("Layout: %s", LayoutName(image.getLayout()));
if (const PreviewInfo preview = getPreviewInfo(image); !preview.previewable)
{
ImGui::TextDisabled("No preview: %s", preview.reason);
}
ImGui::TextWrapped("Usage: %s", UsageNames(image.usage).c_str());
if (const auto it = cache.loadedImagesInfo.find(id); it != cache.loadedImagesInfo.end())
{
ImGui::Separator();
ImGui::TextWrapped("File: %s", it->second.path.generic_string().c_str());
ImGui::Text("Intent: %s%s", TextureIntentName(it->second.intent), it->second.mipMap ? ", mipmapped" : "");
}
const MaterialCache& materials = resources.materials();
std::vector<std::size_t> users;
for (std::size_t materialId = 0; materialId < materials.materials.size(); ++materialId)
{
if (materials.materials[materialId].diffuseTexture == id)
{
users.push_back(materialId);
}
}
if (!users.empty())
{
ImGui::Separator();
ImGui::Text("Diffuse texture of %zu material(s):", users.size());
constexpr std::size_t maxListed = 8;
for (std::size_t i = 0; i < std::min(users.size(), maxListed); ++i)
{
const Material& material = materials.materials[users[i]];
ImGui::BulletText(
"#%zu %s",
users[i],
material.name.empty() ? "<unnamed>" : material.name.c_str());
}
if (users.size() > maxListed)
{
ImGui::TextDisabled("... and %zu more", users.size() - maxListed);
}
}
}
bool ResourcesInspector::matchesFilter(ImageID id, const RenderResources& resources) const
{
const std::string_view filterText{filter};
if (filterText.empty())
{
return true;
}
// a plain number matches the image id exactly
unsigned filterId = 0;
const auto [end, error] = std::from_chars(filterText.data(), filterText.data() + filterText.size(), filterId);
if (error == std::errc{} && end == filterText.data() + filterText.size())
{
return filterId == id;
}
const ImageCache& cache = resources.images();
if (ContainsCaseInsensitive(cache.images[id].debugName, filterText))
{
return true;
}
if (const auto it = cache.loadedImagesInfo.find(id); it != cache.loadedImagesInfo.end())
{
return ContainsCaseInsensitive(it->second.path.generic_string(), filterText);
}
return false;
}
void ResourcesInspector::drawImageCacheWindow(bool* open, const RenderResources& resources, ImguiPass& imguiPass)
{
if (!ImGui::Begin("Image Cache", open))
{
ImGui::End();
return;
}
if (!resources.isInitialized())
{
ImGui::TextDisabled("Render resources aren't initialized yet");
ImGui::End();
return;
}
const ImageCache& cache = resources.images();
std::vector<ImageID> visibleIds;
std::size_t imageCount = 0;
for (std::size_t i = 0; i < cache.images.size(); ++i)
{
const auto id = static_cast<ImageID>(i);
const GPUImage& image = cache.images[i];
if (image.image == VK_NULL_HANDLE)
{
continue;
}
++imageCount;
if (!matchesFilter(id, resources))
{
continue;
}
if (onlyPreviewable && !getPreviewInfo(image).previewable)
{
continue;
}
visibleIds.push_back(id);
}
ImGui::Text("%zu images (bindless capacity %u)", imageCount, cache.getMaxImageCount());
ImGui::SetNextItemWidth(220.0f);
ImGui::InputTextWithHint("##filter", "Filter by name, path or ID", filter, sizeof(filter));
ImGui::SameLine();
ImGui::SetNextItemWidth(140.0f);
ImGui::SliderFloat("Thumbnail", &thumbnailSize, 24.0f, 256.0f, "%.0f px");
ImGui::SameLine();
ImGui::Checkbox("Previewable only", &onlyPreviewable);
if (selectedImageId != NULL_IMAGE_ID &&
(selectedImageId >= cache.images.size() || cache.images[selectedImageId].image == VK_NULL_HANDLE))
{
selectedImageId = NULL_IMAGE_ID;
}
if (selectedImageId != NULL_IMAGE_ID)
{
if (ImGui::CollapsingHeader("Selected Image", ImGuiTreeNodeFlags_DefaultOpen))
{
drawImagePreview(cache.images[selectedImageId], 256.0f, imguiPass);
ImGui::SameLine();
ImGui::BeginGroup();
drawImageDetails(selectedImageId, resources);
if (ImGui::SmallButton("Clear selection"))
{
selectedImageId = NULL_IMAGE_ID;
}
ImGui::EndGroup();
}
}
constexpr ImGuiTableFlags tableFlags =
ImGuiTableFlags_Borders |
ImGuiTableFlags_RowBg |
ImGuiTableFlags_ScrollY |
ImGuiTableFlags_Resizable |
ImGuiTableFlags_SizingStretchProp;
if (ImGui::BeginTable("##images", 6, tableFlags))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn("ID", ImGuiTableColumnFlags_WidthFixed, 40.0f);
ImGui::TableSetupColumn("Preview", ImGuiTableColumnFlags_WidthFixed, thumbnailSize + 4.0f);
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 3.0f);
ImGui::TableSetupColumn("Size", ImGuiTableColumnFlags_WidthFixed, 90.0f);
ImGui::TableSetupColumn("Format", ImGuiTableColumnFlags_WidthStretch, 1.5f);
ImGui::TableSetupColumn("Layout", ImGuiTableColumnFlags_WidthStretch, 1.5f);
ImGui::TableHeadersRow();
// only the visible rows get a texture descriptor
const float rowHeight = thumbnailSize + ImGui::GetStyle().CellPadding.y * 2.0f;
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(visibleIds.size()), rowHeight);
while (clipper.Step())
{
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
{
const ImageID id = visibleIds[static_cast<std::size_t>(row)];
const GPUImage& image = cache.images[id];
ImGui::PushID(static_cast<int>(id));
ImGui::TableNextRow(ImGuiTableRowFlags_None, thumbnailSize);
ImGui::TableNextColumn();
const std::string idLabel = std::to_string(id);
if (ImGui::Selectable(
idLabel.c_str(),
selectedImageId == id,
ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap,
ImVec2{0.0f, thumbnailSize}))
{
selectedImageId = selectedImageId == id ? NULL_IMAGE_ID : id;
}
if (ImGui::BeginItemTooltip())
{
drawImagePreview(image, 256.0f, imguiPass);
drawImageDetails(id, resources);
ImGui::EndTooltip();
}
ImGui::TableNextColumn();
drawImagePreview(image, thumbnailSize, imguiPass);
ImGui::TableNextColumn();
if (const auto it = cache.loadedImagesInfo.find(id); it != cache.loadedImagesInfo.end())
{
// loaded images are named after their full path, the file name is enough here
ImGui::TextWrapped("%s", it->second.path.filename().generic_string().c_str());
}
else
{
ImGui::TextWrapped("%s", image.debugName.empty() ? "<unnamed>" : image.debugName.c_str());
}
ImGui::TableNextColumn();
ImGui::Text("%u x %u", image.extent.width, image.extent.height);
if (image.mipLevels > 1)
{
ImGui::TextDisabled("%u mips", image.mipLevels);
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(FormatName(image.format));
ImGui::TableNextColumn();
ImGui::TextUnformatted(LayoutName(image.getLayout()));
ImGui::PopID();
}
}
ImGui::EndTable();
}
ImGui::End();
}
+3 -2
View File
@@ -164,12 +164,13 @@ void Scene::Render(const RenderContext& ctx) {
}
void Scene::RenderImgui() {
void Scene::RenderImgui(bool* open) {
ImGuiUtils::RenderSceneInspector(
m_name,
m_objects,
m_pendingAdditions,
m_SelectedObjectId);
m_SelectedObjectId,
open);
}
void Scene::CleanupDestroyedGameObjects() {
+2 -2
View File
@@ -38,9 +38,9 @@ void SceneManager::Render(const RenderContext& ctx) {
}
}
void SceneManager::RenderImgui() {
void SceneManager::RenderImgui(bool* open) {
for (const auto& scene : m_activeScenes) {
scene->RenderImgui();
scene->RenderImgui(open);
}
}
+3 -2
View File
@@ -123,7 +123,8 @@ namespace ImGuiUtils {
std::string_view sceneName,
const std::vector<std::shared_ptr<GameObject>>& objectsSource,
const std::vector<std::shared_ptr<GameObject>>& pendingAdditions,
ObjectId& selectedObjectId) {
ObjectId& selectedObjectId,
bool* open) {
RegisterEngineComponents();
std::vector<GameObject*> objects;
@@ -154,7 +155,7 @@ namespace ImGuiUtils {
}
ImGui::SetNextWindowSize(ImVec2(640.0f, 520.0f), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Inspector")) {
if (!ImGui::Begin("Inspector", open)) {
ImGui::End();
return;
}
+24
View File
@@ -64,8 +64,32 @@ set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(ASSIMP_INJECT_DEBUG_POSTFIX OFF CACHE BOOL "" FORCE)
set(ASSIMP_INSTALL OFF CACHE BOOL "" FORCE)
set(ASSIMP_WARNINGS_AS_ERRORS OFF CACHE BOOL "" FORCE)
# assimp is unbearably slow unoptimized (a big FBX takes ~10s), so build it optimized even in Debug.
# This only affects targets created inside the assimp directory (including its bundled zlib).
set(DESTRUM_SAVED_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}")
set(DESTRUM_SAVED_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}")
if (MSVC)
foreach (flags_var CMAKE_C_FLAGS_DEBUG CMAKE_CXX_FLAGS_DEBUG)
string(REPLACE "/RTC1" "" ${flags_var} "${${flags_var}}")
string(REPLACE "/Od" "/O2" ${flags_var} "${${flags_var}}")
string(REPLACE "/Ob0" "/Ob2" ${flags_var} "${${flags_var}}")
endforeach ()
else ()
string(APPEND CMAKE_C_FLAGS_DEBUG " -O2")
string(APPEND CMAKE_CXX_FLAGS_DEBUG " -O2")
endif ()
add_subdirectory(assimp)
# assimp's own CMakeLists appends "/Od" to the Debug flags again, target options come later and win
if (MSVC)
target_compile_options(assimp PRIVATE $<$<CONFIG:Debug>:/O2>)
endif ()
set(CMAKE_C_FLAGS_DEBUG "${DESTRUM_SAVED_C_FLAGS_DEBUG}")
set(CMAKE_CXX_FLAGS_DEBUG "${DESTRUM_SAVED_CXX_FLAGS_DEBUG}")
set(IMGUI_DIR "${CMAKE_CURRENT_LIST_DIR}/imgui")
add_library(imgui STATIC
+2
View File
@@ -22,6 +22,7 @@ public:
void customFixedUpdate(float dt) override;
void onWindowResize(int newWidth, int newHeight) override;
void customWindowsMenu() override;
private:
void drawDebugMenuBar();
@@ -49,6 +50,7 @@ private:
bool renderBoxColliders{false};
bool renderQuads{true};
bool renderDebugLines{true};
bool showTestWindow{true};
};
#endif //LIGHTKEEPER_H
+8 -2
View File
@@ -539,8 +539,8 @@ void LightKeeper::customUpdate(float dt)
renderer.setRenderWireframe(!renderer.getRenderWireframe());
}
if (m_params.showDebugUi) {
ImGui::Begin("Test");
if (m_params.showDebugUi && showTestWindow) {
ImGui::Begin("Test", &showTestWindow);
if (ImGui::Button("SPawn ball"))
{
auto sphere = SceneManager::GetInstance().GetCurrentScene().CreateGameObject("Sphere");
@@ -563,6 +563,12 @@ void LightKeeper::customUpdate(float dt)
}
void LightKeeper::customWindowsMenu()
{
ImGui::Separator();
ImGui::MenuItem("Test", nullptr, &showTestWindow);
}
void LightKeeper::drawDebugMenuBar()
{
if (!ImGui::BeginMainMenuBar()) {