feat: add imgui texture inspector (Thanks claude)
This commit is contained in:
+51
-6
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user