#ifdef ENABLE_EDITOR
#define IMGUI_DEFINE_MATH_OPERATORS
#include "editor.h"
#include <imgui.h>
#include <cstring>
#include <string>
#include <vector>
#include "quick_submit.h"
#include "renderer.h"
namespace editor {
const char *format_name(rhi::ImageFormat fmt) {
switch (fmt) {
case rhi::ImageFormat::R8_UNORM:
return "R8_UNORM";
case rhi::ImageFormat::RGBA8_UNORM:
return "RGBA8_UNORM";
case rhi::ImageFormat::RGBA8_SRGB:
return "RGBA8_SRGB";
case rhi::ImageFormat::BGRA8_UNORM:
return "BGRA8_UNORM";
case rhi::ImageFormat::D32_FLOAT:
return "D32_FLOAT";
case rhi::ImageFormat::RGBA16_FLOAT:
return "RGBA16_FLOAT";
case rhi::ImageFormat::RGBA32_FLOAT:
return "RGBA32_FLOAT";
case rhi::ImageFormat::BC7_UNORM:
return "BC7_UNORM";
case rhi::ImageFormat::BC7_SRGB:
return "BC7_SRGB";
case rhi::ImageFormat::ASTC6X6_UNORM:
return "ASTC6X6_UNORM";
case rhi::ImageFormat::ASTC6X6_SRGB:
return "ASTC6X6_SRGB";
case rhi::ImageFormat::R16_FLOAT:
return "R16_FLOAT";
case rhi::ImageFormat::R32_FLOAT:
return "R32_FLOAT";
case rhi::ImageFormat::R10G10B10A2_UNORM:
return "R10G10B10A2_UNORM";
case rhi::ImageFormat::RG16_FLOAT:
return "RG16_FLOAT";
default:
return "Other";
}
}
struct SourceEntry {
std::string name;
std::string format_name;
rhi::Image *img = nullptr;
// false = render target (display / ddgi / cascades), true = scene texture.
bool is_texture = false;
};
// persists across frames; re-resolved against the catalog each frame so
// pointers stay valid across scene reloads.
std::vector<SourceEntry> PINNED;
bool AUTO_PINNED = false;
bool is_pinned(const std::string &name) {
for (const auto &p : PINNED) {
if (p.name == name) {
return true;
}
}
return false;
}
void build_catalog(RenderData &rd, std::vector<SourceEntry> &out) {
out.clear();
out.push_back({"Main Color", format_name(rd.display.desc.format), &rd.display, false});
out.push_back({"DDGI Irradiance", format_name(rd.irr_atlas.desc.format), &rd.irr_atlas, false});
out.push_back({"DDGI Distance", format_name(rd.dis_atlas.desc.format), &rd.dis_atlas, false});
for (u32 i = 0; i < Light::CASCADE_COUNT; ++i) {
std::string name = "Shadow Cascade " + std::to_string(i);
out.push_back({name, format_name(rd.cascade[i].desc.format), &rd.cascade[i], false});
}
// Scene textures (deduped by GPU handle; multiple materials may share a load).
u32 tex_idx = 0;
for (auto &[img, view] : rd.loaded_textures) {
(void)view;
if (!rhi::valid(img)) {
continue;
}
out.push_back({"Tex " + std::to_string(tex_idx), format_name(img.desc.format), &img, true});
++tex_idx;
}
}
void draw_texture_viewer_window(rhi::Device &device, Editor &editor, RenderData &rd) {
if (!ImGui::Begin("Texture Viewer", nullptr, PANEL_FLAGS)) {
ImGui::End();
return;
}
std::vector<SourceEntry> sources;
build_catalog(rd, sources);
if (!AUTO_PINNED && !sources.empty()) {
AUTO_PINNED = true;
PINNED.push_back(sources[0]);
}
for (auto &p : PINNED) {
const SourceEntry *match = nullptr;
for (const auto &e : sources) {
if (e.name == p.name) {
match = &e;
break;
}
}
p.img = match ? match->img : nullptr;
p.format_name = match ? match->format_name : std::string();
}
ImGui::TextUnformatted("Sources:");
ImGui::SameLine();
ImGui::SetNextItemWidth(150);
if (ImGui::BeginCombo("##add_source", "Add...")) {
const char *last_section = nullptr;
for (const auto &e : sources) {
if (is_pinned(e.name)) {
continue;
}
const char *section = e.is_texture ? "Scene Textures" : "Render Targets";
if (last_section == nullptr || strcmp(last_section, section) != 0) {
last_section = section;
ImGui::TextUnformatted(section);
}
if (ImGui::Selectable(e.name.c_str())) {
PINNED.push_back(e);
}
}
ImGui::EndCombo();
}
std::vector<usize> to_remove;
if (ImGui::BeginTabBar("##tv_tabs", ImGuiTabBarFlags_None)) {
for (usize i = 0; i < PINNED.size(); ++i) {
SourceEntry &pin = PINNED[i];
bool open_tab = true;
if (ImGui::BeginTabItem(pin.name.c_str(), &open_tab)) {
if (pin.img && rhi::valid(*pin.img)) {
TextureViewerDesc tv{};
tv.image = pin.img;
tv.sampler = &editor.viewport_sampler;
tv.name = pin.name.c_str();
tv.format_name = pin.format_name.c_str();
draw_texture_inspector(device, *editor.qs, pin.name.c_str(), tv);
} else {
ImGui::TextUnformatted("Source unavailable");
}
ImGui::EndTabItem();
}
if (!open_tab) {
to_remove.push_back(i);
}
}
ImGui::EndTabBar();
}
for (usize i = to_remove.size(); i-- > 0;) {
PINNED.erase(PINNED.begin() + (i32)to_remove[i]);
}
ImGui::End();
}
} // namespace editor
#endif // ENABLE_EDITOR