Add About window & debug overlays

This commit is contained in:
Luke Street 2021-05-27 00:56:25 -04:00
parent 5ac2a298f0
commit e46ba3ef98
9 changed files with 8163 additions and 82 deletions

View File

@ -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;

View File

@ -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() {

View File

@ -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

View File

@ -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")) {

View File

@ -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)

View File

@ -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,14 +308,16 @@ 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,
uniforms, unistages, unioffs, unisizes, 1, texs, nullptr, nullptr);
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;

View File

@ -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 {

View File

@ -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

7762
imgui/stb_image.h Normal file

File diff suppressed because it is too large Load Diff