diff --git a/Runtime/CPakFile.hpp b/Runtime/CPakFile.hpp index b43ce88e5..fa3a61ccd 100644 --- a/Runtime/CPakFile.hpp +++ b/Runtime/CPakFile.hpp @@ -76,6 +76,7 @@ public: u32 GetFakeStaticSize() const { return 0; } void AsyncIdle(); CAssetId GetMLVLId() const { return m_mlvlId; } + const std::vector& GetResList() const { return x74_resList; } }; } // namespace metaforce diff --git a/Runtime/ImGuiConsole.cpp b/Runtime/ImGuiConsole.cpp index e28796453..40e58d9cb 100644 --- a/Runtime/ImGuiConsole.cpp +++ b/Runtime/ImGuiConsole.cpp @@ -37,7 +37,22 @@ void ImGuiTextCenter(std::string_view text) { static std::unordered_map> dummyWorlds; static std::unordered_map> stringTables; -std::string ImGuiLoadStringTable(CAssetId stringId, int idx) { +std::vector ImGuiLoadStringTable(CAssetId stringId) { + if (!stringId.IsValid()) { + return {}; + } + if (!stringTables.contains(stringId)) { + stringTables[stringId] = g_SimplePool->GetObj(SObjectTag{SBIG('STRG'), stringId}); + } + std::vector result; + CStringTable* pTable = stringTables[stringId].GetObj(); + for (u32 i = 0; i < pTable->GetStringCount(); ++i) { + result.push_back(hecl::Char16ToUTF8(pTable->GetString(s32(i)))); + } + return result; +} + +std::string ImGuiLoadStringTableIndex(CAssetId stringId, int idx) { if (!stringId.IsValid()) { return ""s; } @@ -71,7 +86,7 @@ static std::vector> ListWorlds() { if (!stringId.IsValid()) { continue; } - worlds.emplace_back(ImGuiLoadStringTable(stringId, 0), worldId); + worlds.emplace_back(ImGuiLoadStringTableIndex(stringId, 0), worldId); } return worlds; } @@ -88,7 +103,7 @@ static std::vector> ListAreas(CAssetId worldId) if (!stringId.IsValid()) { continue; } - areas.emplace_back(ImGuiLoadStringTable(stringId, 0), i); + areas.emplace_back(ImGuiLoadStringTableIndex(stringId, 0), i); } return areas; } @@ -783,7 +798,7 @@ void ImGuiConsole::ShowDebugOverlay() { } hasPrevious = true; - const std::string name = ImGuiLoadStringTable(g_StateManager->GetWorld()->IGetStringTableAssetId(), 0); + const std::string name = ImGuiLoadStringTableIndex(g_StateManager->GetWorld()->IGetStringTableAssetId(), 0); ImGuiStringViewText( fmt::format(FMT_STRING("World Asset ID: 0x{}, Name: {}\n"), g_GameState->CurrentWorldAssetId(), name)); } @@ -810,7 +825,7 @@ void ImGuiConsole::ShowDebugOverlay() { CAssetId stringId = pArea->IGetStringTableAssetId(); ImGuiStringViewText( fmt::format(FMT_STRING("Area Asset ID: 0x{}, Name: {}\nArea ID: {}, Active Layer bits: {}\n"), - pArea->GetAreaAssetId(), ImGuiLoadStringTable(stringId, 0), pArea->GetAreaId(), layerBits)); + pArea->GetAreaAssetId(), ImGuiLoadStringTableIndex(stringId, 0), pArea->GetAreaId(), layerBits)); } } if (m_layerInfo && g_StateManager != nullptr) { @@ -1031,6 +1046,7 @@ void ImGuiConsole::ShowAppMainMenuBar(bool canInspect) { ImGui::MenuItem("Items", nullptr, &m_showItemsWindow, canInspect && m_developer && m_cheats); ImGui::MenuItem("Layers", nullptr, &m_showLayersWindow, canInspect && m_developer); ImGui::MenuItem("Console Variables", nullptr, &m_showConsoleVariablesWindow); + ImGui::MenuItem("Assets", nullptr, &m_showAssetsWindow, canInspect); ImGui::EndMenu(); } if (ImGui::BeginMenu("Debug")) { @@ -1152,6 +1168,9 @@ void ImGuiConsole::PreUpdate() { if (canInspect && m_showLayersWindow) { ShowLayersWindow(); } + if (canInspect && m_showAssetsWindow) { + ShowAssetsWindow(); + } if (m_showAboutWindow) { ShowAboutWindow(true); } @@ -1479,6 +1498,52 @@ void ImGuiConsole::ShowLayersWindow() { ImGui::End(); } +void ImGuiConsole::ShowAssetsWindow() { + if (ImGui::Begin("Assets", &m_showAssetsWindow)) { + for (const auto& pak : g_ResFactory->GetResLoader()->GetPaks()) { + const std::vector>& vector = pak->GetNameList(); + const std::string_view pakName = pak->GetPath(); + if (ImGui::TreeNode(pakName.data())) { + for (const auto& item : pak->GetResList()) { + // TODO(encounter): store reverse lookup map + const auto found = std::find_if(vector.begin(), vector.end(), [&](const std::pair& v) { + return v.second.id == item.GetId(); + }); + std::string label; + if (found != vector.end()) { + label = fmt::format(FMT_STRING("0x{:08X}: {} {}"), item.GetId().Value(), item.GetType().toString().c_str(), found->first.c_str()); + } else { + label = fmt::format(FMT_STRING("0x{:08X}: {}"), item.GetId().Value(), item.GetType().toString().c_str()); + } + ImGui::Selectable(label.c_str()); // TODO(encounter): open separate asset viewer window on click + if (ImGui::IsItemHovered()) { + if (item.GetType() == SBIG('TXTR')) { + ImGui::BeginTooltip(); + float imgSize = 128.f * ImGui::GetIO().DisplayFramebufferScale.x; + // TODO(encounter): size based on CTexture dimensions + ImGui::Image(SObjectTag{item.GetType(), item.GetId()}, ImVec2{imgSize, imgSize}, ImVec2{0, 1}, + ImVec2{1, 0}); + // TODO(encounter): show CTexture information + ImGui::EndTooltip(); + } else if (item.GetType() == SBIG('STRG')) { + ImGui::BeginTooltip(); + auto vec = ImGuiLoadStringTable(item.GetId()); + for (int i = 0; i < vec.size(); ++i) { + // TODO(encounter): parse format strings or use CGuiTextSupport + ImGuiStringViewText(fmt::format(FMT_STRING("{}: {}"), i, vec[i])); + } + ImGui::EndTooltip(); + } + // TODO(encounter): preview more types + } + } + ImGui::TreePop(); + } + } + } + ImGui::End(); +} + void ImGuiConsole::ShowMenuHint() { if (m_menuHintTime <= 0.f) { return; diff --git a/Runtime/ImGuiConsole.hpp b/Runtime/ImGuiConsole.hpp index 8d5aa50f8..b944ee64f 100644 --- a/Runtime/ImGuiConsole.hpp +++ b/Runtime/ImGuiConsole.hpp @@ -14,7 +14,8 @@ namespace metaforce { void ImGuiStringViewText(std::string_view text); void ImGuiTextCenter(std::string_view text); -std::string ImGuiLoadStringTable(CAssetId stringId, int idx); +std::vector ImGuiLoadStringTable(CAssetId stringId); +std::string ImGuiLoadStringTableIndex(CAssetId stringId, int idx); struct ImGuiEntityEntry { TUniqueId uid = kInvalidUniqueId; @@ -57,6 +58,7 @@ private: bool m_showItemsWindow = false; bool m_showLayersWindow = false; bool m_showConsoleVariablesWindow = false; + bool m_showAssetsWindow = false; bool m_paused = false; bool m_stepFrame = false; @@ -102,5 +104,6 @@ private: void ShowLayersWindow(); void ShowConsoleVariablesWindow(); void ShowMenuHint(); + void ShowAssetsWindow(); }; } // namespace metaforce diff --git a/Runtime/ImGuiEntitySupport.cpp b/Runtime/ImGuiEntitySupport.cpp index 84d253c40..310be880d 100644 --- a/Runtime/ImGuiEntitySupport.cpp +++ b/Runtime/ImGuiEntitySupport.cpp @@ -767,7 +767,7 @@ IMGUI_ENTITY_INSPECT(CScriptDock, CPhysicsActor, ScriptDock, { auto areaId = dock->GetConnectedAreaId(dock->GetReferenceCount()); if (areaId != kInvalidAreaId) { CAssetId stringId = g_StateManager->GetWorld()->GetArea(areaId)->IGetStringTableAssetId(); - ImGuiStringViewText(fmt::format(FMT_STRING("Connected Area: {}"), ImGuiLoadStringTable(stringId, 0))); + ImGuiStringViewText(fmt::format(FMT_STRING("Connected Area: {}"), ImGuiLoadStringTableIndex(stringId, 0))); } } } diff --git a/imgui/ImGuiEngine.cpp b/imgui/ImGuiEngine.cpp index fde1bf3de..05e476f9b 100644 --- a/imgui/ImGuiEngine.cpp +++ b/imgui/ImGuiEngine.cpp @@ -12,6 +12,8 @@ #include "Runtime/GameGlobalObjects.hpp" #include "Runtime/Input/CInputGenerator.hpp" +#include "Runtime/CSimplePool.hpp" +#include "Runtime/CToken.hpp" #include extern "C" const uint8_t NOTO_MONO_FONT[]; @@ -33,6 +35,7 @@ static boo::ObjToken IndexBuffer; static boo::ObjToken UniformBuffer; static std::array, ImGuiUserTextureID_MAX> ShaderDataBindings; static std::array, ImGuiUserTextureID_MAX> Textures; +static std::unordered_map> ResourceBindings; static boo::SWindowRect WindowRect; static std::string IniFilePath; static std::string LogFilePath; @@ -157,6 +160,7 @@ void ImGuiEngine::Shutdown() { for (auto& item : Textures) { item.reset(); } + ResourceBindings.clear(); VertexBuffer.reset(); IndexBuffer.reset(); UniformBuffer.reset(); @@ -296,6 +300,22 @@ void ImGuiEngine::End() { for (int i = 0; i < drawData->CmdListsCount; ++i) { const auto* cmdList = drawData->CmdLists[i]; + for (const auto& drawCmd : cmdList->CmdBuffer) { + const auto& textureId = drawCmd.TextureId; + if (textureId.textureId == ImGuiUserTextureID_MAX && textureId.objectTag.id.IsValid()) { + if (ResourceBindings.contains(textureId.objectTag.id)) { + continue; + } + if (textureId.objectTag.type == SBIG('TXTR')) { + TLockedToken tex = g_SimplePool->GetObj(textureId.objectTag); + // TODO(encounter): store token until loaded, but right now we force it to load immediately + // if (tex.IsLoaded()) + ResourceBindings[textureId.objectTag.id] = BuildShaderDataBinding(ctx, tex->GetBooTexture()); + } else { + Log.report(logvisor::Fatal, FMT_STRING("Unknown type to render: {}"), textureId.objectTag.type.toString()); + } + } + } int vtxBufferSz = cmdList->VtxBuffer.size(); int idxBufferSz = cmdList->IdxBuffer.size(); memcpy(vtxBuf, cmdList->VtxBuffer.begin(), vtxBufferSz * sizeof(ImDrawVert)); @@ -304,6 +324,8 @@ void ImGuiEngine::End() { idxBuf += idxBufferSz; } + // TODO(encounter): clean up unreferenced ResourceBindings + VertexBuffer->unmap(); IndexBuffer->unmap(); return true; @@ -324,17 +346,31 @@ void ImGuiEngine::Draw(boo::IGraphicsCommandQueue* gfxQ) { size_t idxOffset = 0; size_t vtxOffset = 0; - size_t currentTextureID = ImGuiUserTextureID_MAX; + ImTextureID currentTextureID = ImGuiUserTextureID_MAX; for (int i = 0; i < drawData->CmdListsCount; ++i) { - const auto* cmdList = drawData->CmdLists[i]; - for (const auto& drawCmd : cmdList->CmdBuffer) { + auto* cmdList = drawData->CmdLists[i]; + for (auto& drawCmd : cmdList->CmdBuffer) { + if (drawCmd.UserCallback != nullptr) { + if (drawCmd.UserCallback == ImDrawCallback_ResetRenderState) { + gfxQ->setViewport(viewportRect); + } else { + if (drawCmd.UserCallbackData == nullptr) { + drawCmd.UserCallbackData = gfxQ; + } + drawCmd.UserCallback(cmdList, &drawCmd); + } + continue; + } if (currentTextureID != drawCmd.TextureId) { currentTextureID = drawCmd.TextureId; - gfxQ->setShaderDataBinding(ShaderDataBindings[currentTextureID]); - } - if (drawCmd.UserCallback != nullptr) { - drawCmd.UserCallback(cmdList, &drawCmd); - continue; + if (currentTextureID.textureId == ImGuiUserTextureID_MAX) { + if (!ResourceBindings.contains(currentTextureID.objectTag.id)) { + continue; + } + gfxQ->setShaderDataBinding(ResourceBindings[currentTextureID.objectTag.id]); + } else { + gfxQ->setShaderDataBinding(ShaderDataBindings[currentTextureID.textureId]); + } } ImVec2 pos = drawData->DisplayPos; int clipX = static_cast(drawCmd.ClipRect.x - pos.x); @@ -354,17 +390,23 @@ void ImGuiEngine::Draw(boo::IGraphicsCommandQueue* gfxQ) { } } -void ImGuiEngine::BuildShaderDataBindings(boo::IGraphicsDataFactory::Context& ctx) { +boo::ObjToken ImGuiEngine::BuildShaderDataBinding(boo::IGraphicsDataFactory::Context& ctx, + boo::ObjToken texture) { std::array, 1> uniforms = {UniformBuffer.get()}; std::array unistages = {boo::PipelineStage::Vertex}; std::array unioffs{0}; std::array unisizes{sizeof(Uniform)}; + std::array, 1> texs{texture}; + return ctx.newShaderDataBinding(ShaderPipeline, VertexBuffer.get(), nullptr, IndexBuffer.get(), uniforms.size(), + uniforms.data(), unistages.data(), unioffs.data(), unisizes.data(), texs.size(), + texs.data(), nullptr, nullptr); +} + +void ImGuiEngine::BuildShaderDataBindings(boo::IGraphicsDataFactory::Context& ctx) { for (int i = 0; i < ImGuiUserTextureID_MAX; ++i) { - std::array, 1> texs{Textures[i].get()}; - ShaderDataBindings[i] = ctx.newShaderDataBinding(ShaderPipeline, VertexBuffer.get(), nullptr, IndexBuffer.get(), - uniforms.size(), uniforms.data(), unistages.data(), unioffs.data(), - unisizes.data(), texs.size(), texs.data(), nullptr, nullptr); + ShaderDataBindings[i] = BuildShaderDataBinding(ctx, Textures[i].get()); } + ResourceBindings.clear(); } bool ImGuiWindowCallback::m_mouseCaptured = false; diff --git a/imgui/ImGuiEngine.hpp b/imgui/ImGuiEngine.hpp index 8eb2295a1..3e6dd452d 100644 --- a/imgui/ImGuiEngine.hpp +++ b/imgui/ImGuiEngine.hpp @@ -29,6 +29,9 @@ public: static void End(); static void Draw(boo::IGraphicsCommandQueue* gfxQ); + static boo::ObjToken BuildShaderDataBinding(boo::IGraphicsDataFactory::Context& ctx, + boo::ObjToken texture); + private: static void BuildShaderDataBindings(boo::IGraphicsDataFactory::Context& ctx); }; diff --git a/imgui/imconfig_user.h b/imgui/imconfig_user.h index 4d9f4bfc8..1c09cd702 100644 --- a/imgui/imconfig_user.h +++ b/imgui/imconfig_user.h @@ -1,5 +1,6 @@ #include +#include "Runtime/RetroTypes.hpp" #include #define IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -7,12 +8,23 @@ // Use 32-bit index type for boo #define ImDrawIdx uint32_t -enum ImUserTextureID { +enum ImUserTextureID : uint8_t { ImGuiUserTextureID_Atlas, ImGuiUserTextureID_MetaforceIcon, ImGuiUserTextureID_MAX, }; -#define ImTextureID ImUserTextureID +struct ImUserTexture { + ImUserTextureID textureId = ImGuiUserTextureID_MAX; + metaforce::SObjectTag objectTag{}; + + ImUserTexture() noexcept = default; + ImUserTexture(long /* null */) noexcept {} + ImUserTexture(ImUserTextureID textureId) noexcept : textureId(textureId) {} + ImUserTexture(metaforce::SObjectTag objectTag) noexcept : objectTag(objectTag) {} + bool operator==(const ImUserTexture& rhs) const { return textureId == rhs.textureId && objectTag == rhs.objectTag; } + operator intptr_t() const { return (intptr_t(textureId) << 56) | objectTag.id.Value(); } +}; +#define ImTextureID ImUserTexture #define IM_VEC2_CLASS_EXTRA \ ImVec2(const zeus::CVector2f& v) { \