mirror of https://github.com/AxioDL/metaforce.git
Add About window & debug overlays
This commit is contained in:
parent
5ac2a298f0
commit
e46ba3ef98
|
@ -244,9 +244,12 @@ public:
|
|||
m_window->setCallback(&m_windowCallback);
|
||||
m_window->showWindow();
|
||||
|
||||
boo::IGraphicsDataFactory* gfxF = m_window->getMainContextDataFactory();
|
||||
m_window->setTitle(
|
||||
fmt::format(FMT_STRING(_SYS_STR("Metaforce {} [{}]")), METAFORCE_WC_DESCRIBE_SYS, gfxF->platformName()));
|
||||
|
||||
boo::SWindowRect rect = m_window->getWindowFrame();
|
||||
m_windowCallback.m_lastRect = rect;
|
||||
boo::IGraphicsDataFactory* gfxF = m_window->getMainContextDataFactory();
|
||||
gfxF->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) {
|
||||
m_renderTex = ctx.newRenderTexture(rect.size[0], rect.size[1], boo::TextureClampMode::ClampToEdge, 3, 3);
|
||||
return true;
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
#include "ImGuiConsole.hpp"
|
||||
|
||||
#include "CStateManager.hpp"
|
||||
#include "GameGlobalObjects.hpp"
|
||||
#include "Runtime/CStateManager.hpp"
|
||||
#include "Runtime/GameGlobalObjects.hpp"
|
||||
#include "Runtime/World/CPlayer.hpp"
|
||||
#include "MP1/MP1.hpp"
|
||||
#include "../version.h"
|
||||
|
||||
#include "imgui.h"
|
||||
#include "ImGuiEngine.hpp"
|
||||
|
||||
#include "TCastTo.hpp" // Generated file, do not modify include path
|
||||
|
||||
namespace metaforce {
|
||||
|
||||
std::array<ImGuiEntityEntry, 1024> ImGuiConsole::entities;
|
||||
std::set<TUniqueId> ImGuiConsole::inspectingEntities;
|
||||
|
||||
void ImGuiStringViewText(std::string_view text) { ImGui::TextUnformatted(text.begin(), text.end()); }
|
||||
|
||||
void ImGuiTextCenter(std::string_view text) {
|
||||
ImGui::NewLine();
|
||||
float fontSize = ImGui::GetFontSize() * text.size() / 2;
|
||||
ImGui::SameLine(ImGui::GetWindowSize().x / 2 - fontSize + fontSize / 2);
|
||||
ImGuiStringViewText(text);
|
||||
}
|
||||
|
||||
static std::unordered_map<CAssetId, std::unique_ptr<CDummyWorld>> dummyWorlds;
|
||||
static std::unordered_map<CAssetId, TCachedToken<CStringTable>> stringTables;
|
||||
|
||||
// utility wrapper to adapt locale-bound facets for wstring/wbuffer convert
|
||||
template <class Facet>
|
||||
struct deletable_facet : Facet {
|
||||
template <class... Args>
|
||||
deletable_facet(Args&&... args) : Facet(std::forward<Args>(args)...) {}
|
||||
~deletable_facet() {}
|
||||
};
|
||||
static std::string ReadUtf8String(CStringTable* tbl, int idx) { return hecl::Char16ToUTF8(tbl->GetString(idx)); }
|
||||
|
||||
static std::wstring_convert<deletable_facet<std::codecvt<char16_t, char, std::mbstate_t>>, char16_t> conv16;
|
||||
|
||||
std::string readUtf8String(CStringTable* tbl, int idx) { return conv16.to_bytes(tbl->GetString(idx)); }
|
||||
|
||||
static bool containsCaseInsensitive(std::string_view str, std::string_view val) {
|
||||
static bool ContainsCaseInsensitive(std::string_view str, std::string_view val) {
|
||||
return std::search(str.begin(), str.end(), val.begin(), val.end(),
|
||||
[](char ch1, char ch2) { return std::toupper(ch1) == std::toupper(ch2); }) != str.end();
|
||||
}
|
||||
|
||||
void ImGuiStringViewText(std::string_view text) { ImGui::TextUnformatted(text.begin(), text.end()); }
|
||||
|
||||
static const std::vector<std::pair<std::string, CAssetId>> ListWorlds() {
|
||||
std::vector<std::pair<std::string, CAssetId>> worlds;
|
||||
for (const auto& pak : g_ResFactory->GetResLoader()->GetPaks()) {
|
||||
|
@ -56,7 +56,7 @@ static const std::vector<std::pair<std::string, CAssetId>> ListWorlds() {
|
|||
if (!stringTables.contains(stringId)) {
|
||||
stringTables[stringId] = g_SimplePool->GetObj(SObjectTag{SBIG('STRG'), stringId});
|
||||
}
|
||||
worlds.emplace_back(readUtf8String(stringTables[stringId].GetObj(), 0), worldId);
|
||||
worlds.emplace_back(ReadUtf8String(stringTables[stringId].GetObj(), 0), worldId);
|
||||
}
|
||||
return worlds;
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ static const std::vector<std::pair<std::string, TAreaId>> ListAreas(CAssetId wor
|
|||
if (!stringTables.contains(stringId)) {
|
||||
stringTables[stringId] = g_SimplePool->GetObj(SObjectTag{SBIG('STRG'), stringId});
|
||||
}
|
||||
areas.emplace_back(readUtf8String(stringTables[stringId].GetObj(), 0), TAreaId{i});
|
||||
areas.emplace_back(ReadUtf8String(stringTables[stringId].GetObj(), 0), TAreaId{i});
|
||||
}
|
||||
return areas;
|
||||
}
|
||||
|
@ -97,15 +97,12 @@ static void Warp(const CAssetId worldId, TAreaId aId) {
|
|||
}
|
||||
}
|
||||
|
||||
static bool stepFrame = false;
|
||||
|
||||
static void ShowMenuGame() {
|
||||
static bool paused;
|
||||
paused = g_Main->IsPaused();
|
||||
if (ImGui::MenuItem("Paused", nullptr, &paused)) {
|
||||
g_Main->SetPaused(paused);
|
||||
void ImGuiConsole::ShowMenuGame() {
|
||||
m_paused = g_Main->IsPaused();
|
||||
if (ImGui::MenuItem("Paused", nullptr, &m_paused)) {
|
||||
g_Main->SetPaused(m_paused);
|
||||
}
|
||||
if (ImGui::MenuItem("Step", nullptr, &stepFrame, paused)) {
|
||||
if (ImGui::MenuItem("Step Frame", nullptr, &m_stepFrame, m_paused)) {
|
||||
g_Main->SetPaused(false);
|
||||
}
|
||||
if (ImGui::BeginMenu("Warp", g_StateManager != nullptr && g_ResFactory != nullptr &&
|
||||
|
@ -220,8 +217,6 @@ static void RenderEntityColumns(const ImGuiEntityEntry& entry) {
|
|||
}
|
||||
|
||||
void ImGuiConsole::ShowInspectWindow(bool* isOpen) {
|
||||
static bool activeOnly = false;
|
||||
static std::array<char, 40> filterText{};
|
||||
if (ImGui::Begin("Inspect", isOpen)) {
|
||||
CObjectList& list = g_StateManager->GetAllObjectList();
|
||||
ImGui::Text("Objects: %d / 1024", list.size());
|
||||
|
@ -230,8 +225,8 @@ void ImGuiConsole::ShowInspectWindow(bool* isOpen) {
|
|||
ent->m_debugSelected = false;
|
||||
}
|
||||
}
|
||||
ImGui::InputText("Filter", filterText.data(), filterText.size());
|
||||
ImGui::Checkbox("Active", &activeOnly);
|
||||
ImGui::InputText("Filter", m_inspectFilterText.data(), m_inspectFilterText.size());
|
||||
ImGui::Checkbox("Active", &m_inspectActiveOnly);
|
||||
|
||||
if (ImGui::BeginTable("Entities", 4,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_RowBg |
|
||||
|
@ -247,25 +242,24 @@ void ImGuiConsole::ShowInspectWindow(bool* isOpen) {
|
|||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
|
||||
bool hasSortSpec = sortSpecs != nullptr &&
|
||||
sortSpecs->SpecsCount == 1 && // no multi-sort
|
||||
bool hasSortSpec = sortSpecs != nullptr && sortSpecs->SpecsCount == 1 && // no multi-sort
|
||||
// We can skip sorting if we just want uid ascending,
|
||||
// since that's how we iterate over CObjectList
|
||||
(sortSpecs->Specs[0].ColumnUserID != 'id' ||
|
||||
sortSpecs->Specs[0].SortDirection != ImGuiSortDirection_Ascending);
|
||||
std::string_view search{filterText.data(), strlen(filterText.data())};
|
||||
if (!search.empty() || activeOnly || hasSortSpec) {
|
||||
std::string_view search{m_inspectFilterText.data(), strlen(m_inspectFilterText.data())};
|
||||
if (!search.empty() || m_inspectActiveOnly || hasSortSpec) {
|
||||
std::vector<s16> sortedList;
|
||||
sortedList.reserve(list.size());
|
||||
s16 uid = list.GetFirstObjectIndex();
|
||||
while (uid != -1) {
|
||||
ImGuiEntityEntry& entry = ImGuiConsole::entities[uid];
|
||||
if (activeOnly && !entry.active) {
|
||||
if (m_inspectActiveOnly && !entry.active) {
|
||||
uid = list.GetNextObjectIndex(uid);
|
||||
continue;
|
||||
}
|
||||
if (!search.empty() && !containsCaseInsensitive(entry.type, search) &&
|
||||
!containsCaseInsensitive(entry.name, search)) {
|
||||
if (!search.empty() && !ContainsCaseInsensitive(entry.type, search) &&
|
||||
!ContainsCaseInsensitive(entry.name, search)) {
|
||||
uid = list.GetNextObjectIndex(uid);
|
||||
continue;
|
||||
}
|
||||
|
@ -323,10 +317,209 @@ bool ImGuiConsole::ShowEntityInfoWindow(TUniqueId uid) {
|
|||
return open;
|
||||
}
|
||||
|
||||
static bool showInspectWindow = false;
|
||||
static bool showDemoWindow = false;
|
||||
void ImGuiConsole::ShowAboutWindow() {
|
||||
// Center window
|
||||
ImVec2 center = ImGui::GetMainViewport()->GetCenter();
|
||||
ImGui::SetNextWindowPos(center, ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||
|
||||
std::array<ImGuiEntityEntry, 1024> ImGuiConsole::entities;
|
||||
if (ImGui::Begin("About", &m_showAboutWindow, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse)) {
|
||||
float iconSize = 256.f;
|
||||
ImGui::SameLine(ImGui::GetWindowSize().x / 2 - iconSize + (iconSize / 2));
|
||||
ImGui::Image(ImGuiUserTextureID_MetaforceIcon, ImVec2{iconSize, iconSize});
|
||||
ImGui::PushFont(ImGuiEngine::fontLarge);
|
||||
ImGuiTextCenter("Metaforce");
|
||||
ImGui::PopFont();
|
||||
ImGuiTextCenter(METAFORCE_WC_DESCRIBE);
|
||||
const ImVec2& padding = ImGui::GetStyle().WindowPadding;
|
||||
ImGui::Dummy(padding);
|
||||
ImGui::Dummy(padding);
|
||||
ImGui::Separator();
|
||||
if (ImGui::BeginTable("Version Info", 2, ImGuiTableFlags_BordersInnerV)) {
|
||||
ImGui::TableNextRow();
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted("Branch");
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted(METAFORCE_WC_BRANCH);
|
||||
}
|
||||
ImGui::TableNextRow();
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted("Revision");
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted(METAFORCE_WC_REVISION);
|
||||
}
|
||||
ImGui::TableNextRow();
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted("Date");
|
||||
}
|
||||
if (ImGui::TableNextColumn()) {
|
||||
ImGui::TextUnformatted(METAFORCE_WC_DATE);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ImGuiConsole::ShowDebugOverlay() {
|
||||
if (!m_frameCounter && !m_frameRate && !m_inGameTime && !m_roomTimer && !m_playerInfo && !m_areaInfo &&
|
||||
!m_worldInfo && !m_randomStats && !m_resourceStats) {
|
||||
return;
|
||||
}
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize |
|
||||
ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav;
|
||||
if (m_debugOverlayCorner != -1) {
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImVec2 workPos = viewport->WorkPos; // Use work area to avoid menu-bar/task-bar, if any!
|
||||
ImVec2 workSize = viewport->WorkSize;
|
||||
ImVec2 windowPos;
|
||||
ImVec2 windowPosPivot;
|
||||
constexpr float padding = 10.0f;
|
||||
windowPos.x = (m_debugOverlayCorner & 1) ? (workPos.x + workSize.x - padding) : (workPos.x + padding);
|
||||
windowPos.y = (m_debugOverlayCorner & 2) ? (workPos.y + workSize.y - padding) : (workPos.y + padding);
|
||||
windowPosPivot.x = (m_debugOverlayCorner & 1) ? 1.0f : 0.0f;
|
||||
windowPosPivot.y = (m_debugOverlayCorner & 2) ? 1.0f : 0.0f;
|
||||
ImGui::SetNextWindowPos(windowPos, ImGuiCond_Always, windowPosPivot);
|
||||
windowFlags |= ImGuiWindowFlags_NoMove;
|
||||
}
|
||||
ImGui::SetNextWindowBgAlpha(0.65f);
|
||||
if (ImGui::Begin("Debug Overlay", nullptr, windowFlags)) {
|
||||
bool hasPrevious = false;
|
||||
if (m_frameCounter) {
|
||||
ImGuiStringViewText(fmt::format(FMT_STRING("Frame: {}\n"), g_StateManager->GetUpdateFrameIndex()));
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (m_frameRate) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {}\n"), metaforce::CGraphics::GetFPS()));
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (m_inGameTime) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
double igt = g_GameState->GetTotalPlayTime();
|
||||
u32 ms = u64(igt * 1000) % 1000;
|
||||
auto pt = std::div(igt, 3600);
|
||||
ImGuiStringViewText(
|
||||
fmt::format(FMT_STRING("Play Time: {:02d}:{:02d}:{:02d}.{:03d}\n"), pt.quot, pt.rem / 60, pt.rem % 60, ms));
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (m_roomTimer) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
double igt = g_GameState->GetTotalPlayTime();
|
||||
if (m_currentRoom != g_StateManager->GetCurrentArea()) {
|
||||
m_currentRoom = static_cast<const void*>(g_StateManager->GetCurrentArea());
|
||||
m_lastRoomTime = igt - m_currentRoomStart;
|
||||
m_currentRoomStart = igt;
|
||||
}
|
||||
double currentRoomTime = igt - m_currentRoomStart;
|
||||
u32 curFrames = u32(std::round(u32(currentRoomTime * 60)));
|
||||
u32 lastFrames = u32(std::round(u32(m_lastRoomTime * 60)));
|
||||
ImGuiStringViewText(fmt::format(FMT_STRING("Room Time: {:7.3f} / {:5d} | Last Room:{:7.3f} / {:5d}\n"),
|
||||
currentRoomTime, curFrames, m_lastRoomTime, lastFrames));
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (m_playerInfo && g_StateManager->Player() != nullptr) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
const CPlayer& pl = g_StateManager->GetPlayer();
|
||||
const zeus::CQuaternion plQ = zeus::CQuaternion(pl.GetTransform().getRotation().buildMatrix3f());
|
||||
const zeus::CTransform camXf = g_StateManager->GetCameraManager()->GetCurrentCameraTransform(*g_StateManager);
|
||||
const zeus::CQuaternion camQ = zeus::CQuaternion(camXf.getRotation().buildMatrix3f());
|
||||
ImGuiStringViewText(
|
||||
fmt::format(FMT_STRING("Player Position x: {: .2f}, y: {: .2f}, z: {: .2f}\n"
|
||||
" Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\n"
|
||||
" Momentum x: {: .2f}, y: {: .2f}, z: {: .2f}\n"
|
||||
" Velocity x: {: .2f}, y: {: .2f}, z: {: .2f}\n"
|
||||
"Camera Position x: {: .2f}, y: {: .2f}, z {: .2f}\n"
|
||||
" Roll: {: .2f}, Pitch: {: .2f}, Yaw: {: .2f}\n"),
|
||||
pl.GetTranslation().x(), pl.GetTranslation().y(), pl.GetTranslation().z(),
|
||||
zeus::radToDeg(plQ.roll()), zeus::radToDeg(plQ.pitch()), zeus::radToDeg(plQ.yaw()),
|
||||
pl.GetMomentum().x(), pl.GetMomentum().y(), pl.GetMomentum().z(), pl.GetVelocity().x(),
|
||||
pl.GetVelocity().y(), pl.GetVelocity().z(), camXf.origin.x(), camXf.origin.y(), camXf.origin.z(),
|
||||
zeus::radToDeg(camQ.roll()), zeus::radToDeg(camQ.pitch()), zeus::radToDeg(camQ.yaw())));
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (m_worldInfo) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
TLockedToken<CStringTable> tbl =
|
||||
g_SimplePool->GetObj({FOURCC('STRG'), g_StateManager->GetWorld()->IGetStringTableAssetId()});
|
||||
const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId();
|
||||
ImGuiStringViewText(fmt::format(FMT_STRING("World: 0x{}{}, Area: {}\n"), g_GameState->CurrentWorldAssetId(),
|
||||
(tbl.IsLoaded() ? (" " + hecl::Char16ToUTF8(tbl->GetString(0))).c_str() : ""),
|
||||
aId));
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (m_areaInfo) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
const metaforce::TAreaId aId = g_GameState->CurrentWorldState().GetCurrentAreaId();
|
||||
if (g_StateManager->GetWorld() != nullptr && g_StateManager->GetWorld()->DoesAreaExist(aId)) {
|
||||
const auto& layerStates = g_GameState->CurrentWorldState().GetLayerState();
|
||||
std::string layerBits;
|
||||
u32 totalActive = 0;
|
||||
for (u32 i = 0; i < layerStates->GetAreaLayerCount(aId); ++i) {
|
||||
if (layerStates->IsLayerActive(aId, i)) {
|
||||
++totalActive;
|
||||
layerBits += "1";
|
||||
} else {
|
||||
layerBits += "0";
|
||||
}
|
||||
}
|
||||
ImGuiStringViewText(fmt::format(FMT_STRING("Area AssetId: 0x{}, Total Objects: {}\n"
|
||||
"Active Layer bits: {}\n"),
|
||||
g_StateManager->GetWorld()->GetArea(aId)->GetAreaAssetId(),
|
||||
g_StateManager->GetAllObjectList().size(), layerBits));
|
||||
hasPrevious = true;
|
||||
}
|
||||
}
|
||||
if (m_randomStats) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
ImGuiStringViewText(
|
||||
fmt::format(FMT_STRING("CRandom16::Next calls: {}\n"), metaforce::CRandom16::GetNumNextCalls()));
|
||||
hasPrevious = true;
|
||||
}
|
||||
if (m_resourceStats) {
|
||||
if (hasPrevious) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
ImGuiStringViewText(fmt::format(FMT_STRING("Resource Objects: {}\n"), g_SimplePool->GetLiveObjects()));
|
||||
}
|
||||
if (ImGui::BeginPopupContextWindow()) {
|
||||
if (ImGui::MenuItem("Custom", nullptr, m_debugOverlayCorner == -1)) {
|
||||
m_debugOverlayCorner = -1;
|
||||
}
|
||||
if (ImGui::MenuItem("Top-left", nullptr, m_debugOverlayCorner == 0)) {
|
||||
m_debugOverlayCorner = 0;
|
||||
}
|
||||
if (ImGui::MenuItem("Top-right", nullptr, m_debugOverlayCorner == 1)) {
|
||||
m_debugOverlayCorner = 1;
|
||||
}
|
||||
if (ImGui::MenuItem("Bottom-left", nullptr, m_debugOverlayCorner == 2)) {
|
||||
m_debugOverlayCorner = 2;
|
||||
}
|
||||
if (ImGui::MenuItem("Bottom-right", nullptr, m_debugOverlayCorner == 3)) {
|
||||
m_debugOverlayCorner = 3;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void ImGuiConsole::ShowAppMainMenuBar(bool canInspect) {
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
|
@ -334,11 +527,54 @@ void ImGuiConsole::ShowAppMainMenuBar(bool canInspect) {
|
|||
ShowMenuGame();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (ImGui::BeginMenu("Tools")) {
|
||||
ImGui::MenuItem("Inspect", nullptr, &showInspectWindow, canInspect);
|
||||
ImGui::MenuItem("Inspect", nullptr, &m_showInspectWindow, canInspect);
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Demo", nullptr, &showDemoWindow);
|
||||
ImGui::MenuItem("Demo", nullptr, &m_showDemoWindow);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Debug")) {
|
||||
if (ImGui::MenuItem("Frame Counter", nullptr, &m_frameCounter)) {
|
||||
m_cvarCommons.m_debugOverlayShowFrameCounter->fromBoolean(m_frameCounter);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("Frame Rate", nullptr, &m_frameRate)) {
|
||||
m_cvarCommons.m_debugOverlayShowFramerate->fromBoolean(m_frameRate);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("In-Game Time", nullptr, &m_inGameTime)) {
|
||||
m_cvarCommons.m_debugOverlayShowInGameTime->fromBoolean(m_inGameTime);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("Room Timer", nullptr, &m_roomTimer)) {
|
||||
m_cvarCommons.m_debugOverlayShowRoomTimer->fromBoolean(m_roomTimer);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("Player Info", nullptr, &m_playerInfo)) {
|
||||
m_cvarCommons.m_debugOverlayPlayerInfo->fromBoolean(m_playerInfo);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("World Info", nullptr, &m_worldInfo)) {
|
||||
m_cvarCommons.m_debugOverlayWorldInfo->fromBoolean(m_worldInfo);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("Area Info", nullptr, &m_areaInfo)) {
|
||||
m_cvarCommons.m_debugOverlayAreaInfo->fromBoolean(m_areaInfo);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("Random Stats", nullptr, &m_randomStats)) {
|
||||
m_cvarCommons.m_debugOverlayShowRandomStats->fromBoolean(m_randomStats);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
if (ImGui::MenuItem("Resource Stats", nullptr, &m_resourceStats)) {
|
||||
m_cvarCommons.m_debugOverlayShowResourceStats->fromBoolean(m_resourceStats);
|
||||
m_cvarMgr.serialize();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Spacing();
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
ImGui::MenuItem("About", nullptr, &m_showAboutWindow);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
|
@ -346,16 +582,16 @@ void ImGuiConsole::ShowAppMainMenuBar(bool canInspect) {
|
|||
}
|
||||
|
||||
void ImGuiConsole::PreUpdate() {
|
||||
if (stepFrame) {
|
||||
if (m_stepFrame) {
|
||||
g_Main->SetPaused(true);
|
||||
stepFrame = false;
|
||||
m_stepFrame = false;
|
||||
}
|
||||
bool canInspect = g_StateManager != nullptr && g_StateManager->GetObjectList();
|
||||
ShowAppMainMenuBar(canInspect);
|
||||
if (canInspect && (showInspectWindow || !inspectingEntities.empty())) {
|
||||
if (canInspect && (m_showInspectWindow || !inspectingEntities.empty())) {
|
||||
UpdateEntityEntries();
|
||||
if (showInspectWindow) {
|
||||
ShowInspectWindow(&showInspectWindow);
|
||||
if (m_showInspectWindow) {
|
||||
ShowInspectWindow(&m_showInspectWindow);
|
||||
}
|
||||
auto iter = inspectingEntities.begin();
|
||||
while (iter != inspectingEntities.end()) {
|
||||
|
@ -366,9 +602,13 @@ void ImGuiConsole::PreUpdate() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (showDemoWindow) {
|
||||
ImGui::ShowDemoWindow(&showDemoWindow);
|
||||
if (m_showAboutWindow) {
|
||||
ShowAboutWindow();
|
||||
}
|
||||
if (m_showDemoWindow) {
|
||||
ImGui::ShowDemoWindow(&m_showDemoWindow);
|
||||
}
|
||||
ShowDebugOverlay();
|
||||
}
|
||||
|
||||
void ImGuiConsole::PostUpdate() {
|
||||
|
|
|
@ -4,11 +4,15 @@
|
|||
#include <string_view>
|
||||
|
||||
#include "RetroTypes.hpp"
|
||||
#include "Runtime/World/CEntity.hpp"
|
||||
#include "Runtime/World/CActor.hpp"
|
||||
#include "Runtime/World/CEntity.hpp"
|
||||
|
||||
#include "hecl/CVarCommons.hpp"
|
||||
#include "hecl/CVarManager.hpp"
|
||||
|
||||
namespace metaforce {
|
||||
void ImGuiStringViewText(std::string_view text);
|
||||
void ImGuiTextCenter(std::string_view text);
|
||||
|
||||
struct ImGuiEntityEntry {
|
||||
TUniqueId uid = kInvalidUniqueId;
|
||||
|
@ -18,11 +22,11 @@ struct ImGuiEntityEntry {
|
|||
bool active = false;
|
||||
bool isActor = false;
|
||||
|
||||
ImGuiEntityEntry() {}
|
||||
ImGuiEntityEntry() = default;
|
||||
ImGuiEntityEntry(TUniqueId uid, CEntity* ent, std::string_view type, std::string_view name, bool active)
|
||||
: uid(uid), ent(ent), type(type), name(name), active(active) {}
|
||||
|
||||
CActor* AsActor() const {
|
||||
[[nodiscard]] CActor* AsActor() const {
|
||||
if (isActor) {
|
||||
return static_cast<CActor*>(ent);
|
||||
}
|
||||
|
@ -35,6 +39,8 @@ public:
|
|||
static std::set<TUniqueId> inspectingEntities;
|
||||
static std::array<ImGuiEntityEntry, 1024> entities;
|
||||
|
||||
ImGuiConsole(hecl::CVarManager& cvarMgr, hecl::CVarCommons& cvarCommons)
|
||||
: m_cvarMgr(cvarMgr), m_cvarCommons(cvarCommons) {}
|
||||
~ImGuiConsole();
|
||||
void PreUpdate();
|
||||
void PostUpdate();
|
||||
|
@ -43,10 +49,42 @@ public:
|
|||
static void EndEntityRow(const ImGuiEntityEntry& entry);
|
||||
|
||||
private:
|
||||
static void ShowAppMainMenuBar(bool canInspect);
|
||||
static bool ShowEntityInfoWindow(TUniqueId uid);
|
||||
static void ShowInspectWindow(bool* isOpen);
|
||||
static void LerpDebugColor(CActor* act);
|
||||
static void UpdateEntityEntries();
|
||||
hecl::CVarManager& m_cvarMgr;
|
||||
hecl::CVarCommons& m_cvarCommons;
|
||||
|
||||
bool m_showInspectWindow = false;
|
||||
bool m_showDemoWindow = false;
|
||||
bool m_showAboutWindow = false;
|
||||
|
||||
bool m_paused = false;
|
||||
bool m_stepFrame = false;
|
||||
|
||||
bool m_inspectActiveOnly = false;
|
||||
std::array<char, 40> m_inspectFilterText{};
|
||||
|
||||
// Debug overlays
|
||||
bool m_frameCounter = m_cvarCommons.m_debugOverlayShowFrameCounter->toBoolean();
|
||||
bool m_frameRate = m_cvarCommons.m_debugOverlayShowFramerate->toBoolean();
|
||||
bool m_inGameTime = m_cvarCommons.m_debugOverlayShowInGameTime->toBoolean();
|
||||
bool m_roomTimer = m_cvarCommons.m_debugOverlayShowRoomTimer->toBoolean();
|
||||
bool m_playerInfo = m_cvarCommons.m_debugOverlayPlayerInfo->toBoolean();
|
||||
bool m_worldInfo = m_cvarCommons.m_debugOverlayWorldInfo->toBoolean();
|
||||
bool m_areaInfo = m_cvarCommons.m_debugOverlayAreaInfo->toBoolean();
|
||||
bool m_randomStats = m_cvarCommons.m_debugOverlayShowRandomStats->toBoolean();
|
||||
bool m_resourceStats = m_cvarCommons.m_debugOverlayShowResourceStats->toBoolean();
|
||||
|
||||
int m_debugOverlayCorner = 2; // bottom-left
|
||||
const void* m_currentRoom = nullptr;
|
||||
double m_lastRoomTime = 0.f;
|
||||
double m_currentRoomStart = 0.f;
|
||||
|
||||
void ShowAppMainMenuBar(bool canInspect);
|
||||
void ShowMenuGame();
|
||||
bool ShowEntityInfoWindow(TUniqueId uid);
|
||||
void ShowInspectWindow(bool* isOpen);
|
||||
void LerpDebugColor(CActor* act);
|
||||
void UpdateEntityEntries();
|
||||
void ShowAboutWindow();
|
||||
void ShowDebugOverlay();
|
||||
};
|
||||
} // namespace metaforce
|
||||
|
|
|
@ -769,7 +769,7 @@ void CMain::Init(const hecl::Runtime::FileStoreManager& storeMgr, hecl::CVarMana
|
|||
"Warp"sv, "Warps to a given area and world"sv, "[worldname] areaId"sv,
|
||||
[this](hecl::Console* console, const std::vector<std::string>& args) { Warp(console, args); },
|
||||
hecl::SConsoleCommand::ECommandFlags::Normal);
|
||||
m_imGuiConsole = std::make_unique<ImGuiConsole>();
|
||||
m_imGuiConsole = std::make_unique<ImGuiConsole>(*m_cvarMgr, *m_cvarCommons);
|
||||
|
||||
bool loadedVersion = false;
|
||||
if (CDvdFile::FileExists("version.yaml")) {
|
||||
|
|
|
@ -7,12 +7,14 @@ add_library(imgui
|
|||
ImGuiEngine.cpp
|
||||
ImGuiEngine.hpp
|
||||
NotoMono.cpp
|
||||
MetaforceIcon.cpp
|
||||
)
|
||||
target_include_directories(imgui PUBLIC ${CMAKE_SOURCE_DIR}/extern/imgui ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="imconfig_user.h")
|
||||
target_link_libraries(imgui PRIVATE boo hecl-light RetroDataSpec)
|
||||
|
||||
bintoc_compress(NotoMono.cpp NotoMono-Regular.ttf NOTO_MONO_FONT)
|
||||
bintoc(MetaforceIcon.cpp ../Runtime/platforms/freedesktop/256x256/apps/metaforce.png METAFORCE_ICON)
|
||||
|
||||
add_shader(ImGuiShader)
|
||||
target_link_libraries(shader_ImGuiShader PRIVATE hecl-light)
|
||||
|
|
|
@ -4,11 +4,19 @@
|
|||
#include "hecl/Pipeline.hpp"
|
||||
#include "hecl/VertexBufferPool.hpp"
|
||||
|
||||
#define STBI_NO_STDIO
|
||||
#define STB_IMAGE_STATIC
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_ONLY_PNG
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <zeus/CMatrix4f.hpp>
|
||||
|
||||
extern "C" const uint8_t NOTO_MONO_FONT[];
|
||||
extern "C" const size_t NOTO_MONO_FONT_SZ;
|
||||
extern "C" const size_t NOTO_MONO_FONT_DECOMPRESSED_SZ;
|
||||
extern "C" const uint8_t METAFORCE_ICON[];
|
||||
extern "C" const size_t METAFORCE_ICON_SZ;
|
||||
|
||||
namespace metaforce {
|
||||
static logvisor::Module Log{"ImGuiEngine"};
|
||||
|
@ -21,8 +29,8 @@ static boo::ObjToken<boo::IShaderPipeline> ShaderPipeline;
|
|||
static boo::ObjToken<boo::IGraphicsBufferD> VertexBuffer;
|
||||
static boo::ObjToken<boo::IGraphicsBufferD> IndexBuffer;
|
||||
static boo::ObjToken<boo::IGraphicsBufferD> UniformBuffer;
|
||||
static boo::ObjToken<boo::IShaderDataBinding> ShaderDataBinding;
|
||||
static boo::ObjToken<boo::ITextureS> ImGuiAtlas;
|
||||
static std::array<boo::ObjToken<boo::IShaderDataBinding>, ImGuiUserTextureID_MAX> ShaderDataBindings;
|
||||
static std::array<boo::ObjToken<boo::ITextureS>, ImGuiUserTextureID_MAX> Textures;
|
||||
static boo::SWindowRect WindowRect;
|
||||
|
||||
struct Uniform {
|
||||
|
@ -46,6 +54,9 @@ void setClipboardText(void* userData, const char* text) {
|
|||
static_cast<boo::IWindow*>(userData)->clipboardCopy(boo::EClipboardType::String, data, strlen(text));
|
||||
}
|
||||
|
||||
ImFont* ImGuiEngine::fontNormal;
|
||||
ImFont* ImGuiEngine::fontLarge;
|
||||
|
||||
void ImGuiEngine::Initialize(boo::IGraphicsDataFactory* factory, boo::IWindow* window, float scale) {
|
||||
m_factory = factory;
|
||||
m_window = window;
|
||||
|
@ -82,8 +93,10 @@ void ImGuiEngine::Initialize(boo::IGraphicsDataFactory* factory, boo::IWindow* w
|
|||
io.KeyMap[ImGuiKey_Z] = 'z'; // for text edit CTRL+Z: undo
|
||||
|
||||
auto* fontData = new uint8_t[NOTO_MONO_FONT_DECOMPRESSED_SZ];
|
||||
athena::io::Compression::decompressZlib(NOTO_MONO_FONT, NOTO_MONO_FONT_SZ, fontData,
|
||||
NOTO_MONO_FONT_DECOMPRESSED_SZ);
|
||||
athena::io::Compression::decompressZlib(NOTO_MONO_FONT, NOTO_MONO_FONT_SZ, fontData, NOTO_MONO_FONT_DECOMPRESSED_SZ);
|
||||
|
||||
int iconWidth = 0, iconHeight = 0;
|
||||
auto* metaforceIcon = stbi_load_from_memory(METAFORCE_ICON, METAFORCE_ICON_SZ, &iconWidth, &iconHeight, nullptr, 4);
|
||||
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
|
@ -92,29 +105,38 @@ void ImGuiEngine::Initialize(boo::IGraphicsDataFactory* factory, boo::IWindow* w
|
|||
fontConfig.FontData = fontData;
|
||||
fontConfig.FontDataSize = NOTO_MONO_FONT_DECOMPRESSED_SZ;
|
||||
fontConfig.SizePixels = std::floor(14.f * scale);
|
||||
fontConfig.OversampleH = 2;
|
||||
snprintf(fontConfig.Name, sizeof(fontConfig.Name), "Noto Mono Regular, %dpx",
|
||||
static_cast<int>(fontConfig.SizePixels));
|
||||
io.Fonts->AddFont(&fontConfig);
|
||||
fontNormal = io.Fonts->AddFont(&fontConfig);
|
||||
fontConfig.SizePixels = std::floor(24.f * scale);
|
||||
snprintf(fontConfig.Name, sizeof(fontConfig.Name), "Noto Mono Regular, %dpx",
|
||||
static_cast<int>(fontConfig.SizePixels));
|
||||
fontLarge = io.Fonts->AddFont(&fontConfig);
|
||||
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
factory->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) {
|
||||
ShaderPipeline = hecl::conv->convert(Shader_ImGuiShader{});
|
||||
ImGuiAtlas = ctx.newStaticTexture(width, height, 1, boo::TextureFormat::RGBA8, boo::TextureClampMode::ClampToEdge,
|
||||
pixels, width * height * 4);
|
||||
Textures[ImGuiUserTextureID_Atlas] = ctx.newStaticTexture(
|
||||
width, height, 1, boo::TextureFormat::RGBA8, boo::TextureClampMode::ClampToEdge, pixels, width * height * 4);
|
||||
Textures[ImGuiUserTextureID_MetaforceIcon] =
|
||||
ctx.newStaticTexture(iconWidth, iconHeight, 1, boo::TextureFormat::RGBA8, boo::TextureClampMode::ClampToEdge,
|
||||
metaforceIcon, iconWidth * iconHeight * 4);
|
||||
VertexBuffer = ctx.newDynamicBuffer(boo::BufferUse::Vertex, sizeof(ImDrawVert), VertexBufferSize);
|
||||
IndexBuffer = ctx.newDynamicBuffer(boo::BufferUse::Index, sizeof(ImDrawIdx), IndexBufferSize);
|
||||
UniformBuffer = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(Uniform), 1);
|
||||
BuildShaderDataBinding(ctx);
|
||||
BuildShaderDataBindings(ctx);
|
||||
return true;
|
||||
} BooTrace);
|
||||
io.Fonts->SetTexID(ImGuiAtlas.get());
|
||||
io.Fonts->SetTexID(ImGuiUserTextureID_Atlas);
|
||||
|
||||
ImGui::GetStyle().ScaleAllSizes(scale);
|
||||
}
|
||||
|
||||
void ImGuiEngine::Shutdown() {
|
||||
ImGui::DestroyContext();
|
||||
ShaderDataBinding.reset();
|
||||
for (auto& item : ShaderDataBindings) {
|
||||
item.reset();
|
||||
}
|
||||
ShaderPipeline.reset();
|
||||
}
|
||||
|
||||
|
@ -219,7 +241,7 @@ void ImGuiEngine::End() {
|
|||
rebind = true;
|
||||
}
|
||||
if (rebind) {
|
||||
BuildShaderDataBinding(ctx);
|
||||
BuildShaderDataBindings(ctx);
|
||||
}
|
||||
|
||||
UniformBuffer->load(&projXf, sizeof(Uniform));
|
||||
|
@ -245,8 +267,6 @@ void ImGuiEngine::End() {
|
|||
void ImGuiEngine::Draw(boo::IGraphicsCommandQueue* gfxQ) {
|
||||
ImDrawData* drawData = ImGui::GetDrawData();
|
||||
|
||||
gfxQ->setShaderDataBinding(ShaderDataBinding);
|
||||
|
||||
boo::SWindowRect viewportRect = WindowRect;
|
||||
int viewportHeight = viewportRect.size[1];
|
||||
viewportRect.location = {0, 0};
|
||||
|
@ -258,9 +278,14 @@ void ImGuiEngine::Draw(boo::IGraphicsCommandQueue* gfxQ) {
|
|||
|
||||
size_t idxOffset = 0;
|
||||
size_t vtxOffset = 0;
|
||||
size_t currentTextureID = ImGuiUserTextureID_MAX;
|
||||
for (int i = 0; i < drawData->CmdListsCount; ++i) {
|
||||
const auto* cmdList = drawData->CmdLists[i];
|
||||
for (const auto& drawCmd : cmdList->CmdBuffer) {
|
||||
if (currentTextureID != drawCmd.TextureId) {
|
||||
currentTextureID = drawCmd.TextureId;
|
||||
gfxQ->setShaderDataBinding(ShaderDataBindings[currentTextureID]);
|
||||
}
|
||||
if (drawCmd.UserCallback != nullptr) {
|
||||
drawCmd.UserCallback(cmdList, &drawCmd);
|
||||
continue;
|
||||
|
@ -271,8 +296,8 @@ void ImGuiEngine::Draw(boo::IGraphicsCommandQueue* gfxQ) {
|
|||
int clipW = static_cast<int>(drawCmd.ClipRect.z - pos.x) - clipX;
|
||||
int clipH = static_cast<int>(drawCmd.ClipRect.w - pos.y) - clipY;
|
||||
boo::SWindowRect clipRect{clipX, clipY, clipW, clipH};
|
||||
if (m_factory->platform() == boo::IGraphicsDataFactory::Platform::Vulkan
|
||||
|| m_factory->platform() == boo::IGraphicsDataFactory::Platform::Metal) {
|
||||
if (m_factory->platform() == boo::IGraphicsDataFactory::Platform::Vulkan ||
|
||||
m_factory->platform() == boo::IGraphicsDataFactory::Platform::Metal) {
|
||||
clipRect.location[1] = viewportHeight - clipRect.location[1] - clipRect.size[1];
|
||||
}
|
||||
gfxQ->setScissor(clipRect);
|
||||
|
@ -283,15 +308,17 @@ void ImGuiEngine::Draw(boo::IGraphicsCommandQueue* gfxQ) {
|
|||
}
|
||||
}
|
||||
|
||||
void ImGuiEngine::BuildShaderDataBinding(boo::IGraphicsDataFactory::Context& ctx) {
|
||||
void ImGuiEngine::BuildShaderDataBindings(boo::IGraphicsDataFactory::Context& ctx) {
|
||||
boo::ObjToken<boo::IGraphicsBuffer> uniforms[] = {UniformBuffer.get()};
|
||||
boo::PipelineStage unistages[] = {boo::PipelineStage::Vertex};
|
||||
size_t unioffs[] = {0};
|
||||
size_t unisizes[] = {sizeof(Uniform)};
|
||||
boo::ObjToken<boo::ITexture> texs[] = {ImGuiAtlas.get()};
|
||||
ShaderDataBinding = ctx.newShaderDataBinding(ShaderPipeline, VertexBuffer.get(), nullptr, IndexBuffer.get(), 1,
|
||||
for (int i = 0; i < ImGuiUserTextureID_MAX; ++i) {
|
||||
boo::ObjToken<boo::ITexture> texs[] = {Textures[i].get()};
|
||||
ShaderDataBindings[i] = ctx.newShaderDataBinding(ShaderPipeline, VertexBuffer.get(), nullptr, IndexBuffer.get(), 1,
|
||||
uniforms, unistages, unioffs, unisizes, 1, texs, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool ImGuiWindowCallback::m_mouseCaptured = false;
|
||||
bool ImGuiWindowCallback::m_keyboardCaptured = false;
|
||||
|
|
|
@ -17,6 +17,8 @@ public:
|
|||
std::vector<unsigned long> m_charCodes{};
|
||||
bool m_mouseIn = true;
|
||||
} Input;
|
||||
static ImFont* fontNormal;
|
||||
static ImFont* fontLarge;
|
||||
|
||||
static void Initialize(boo::IGraphicsDataFactory* factory, boo::IWindow* window, float scale);
|
||||
static void Shutdown();
|
||||
|
@ -26,7 +28,7 @@ public:
|
|||
static void Draw(boo::IGraphicsCommandQueue* gfxQ);
|
||||
|
||||
private:
|
||||
static void BuildShaderDataBinding(boo::IGraphicsDataFactory::Context& ctx);
|
||||
static void BuildShaderDataBindings(boo::IGraphicsDataFactory::Context& ctx);
|
||||
};
|
||||
|
||||
struct ImGuiWindowCallback : boo::IWindowCallback {
|
||||
|
|
|
@ -4,3 +4,10 @@
|
|||
|
||||
// Use 32-bit index type for boo
|
||||
#define ImDrawIdx uint32_t
|
||||
|
||||
enum ImUserTextureID {
|
||||
ImGuiUserTextureID_Atlas,
|
||||
ImGuiUserTextureID_MetaforceIcon,
|
||||
ImGuiUserTextureID_MAX,
|
||||
};
|
||||
#define ImTextureID ImUserTextureID
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue