From 9d06cd9e53bd69b0453f2ee1e1140e42196c851d Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Tue, 20 May 2025 23:36:51 +0200 Subject: [PATCH] Show imgui window when starting app with --debug (#138) * Show imgui window when starting app with --debug * g_closed test in SDL_AppIterate is still needed * Allow disabling isleapp debugging by configuring with -DISLE_DEBUG=OFF * clang-format --- .gitmodules | 3 + 3rdparty/CMakeLists.txt | 28 +++- 3rdparty/imgui | 1 + CMakeLists.txt | 10 ++ ISLE/isleapp.cpp | 29 +++- ISLE/isleapp.h | 5 +- ISLE/isledebug.cpp | 155 ++++++++++++++++++ ISLE/isledebug.h | 32 ++++ LEGO1/lego/legoomni/include/legogamestate.h | 2 +- .../lego/legoomni/include/legoplantmanager.h | 2 +- miniwin/miniwin/include/miniwin.h | 4 +- miniwin/miniwin/src/miniwin.cpp | 2 +- 12 files changed, 258 insertions(+), 15 deletions(-) create mode 160000 3rdparty/imgui create mode 100644 ISLE/isledebug.cpp create mode 100644 ISLE/isledebug.h diff --git a/.gitmodules b/.gitmodules index 957a7f79..931b1f4f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "3rdparty/miniaudio"] path = 3rdparty/miniaudio url = https://github.com/mackron/miniaudio +[submodule "imgui"] + path = 3rdparty/imgui + url = https://github.com/ocornut/imgui diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 8df40548..eea078ca 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -15,7 +15,6 @@ else() target_include_directories(miniaudio PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/miniaudio/extras/miniaudio_split") endif() -set_property(TARGET miniaudio PROPERTY ARCHIVE_OUTPUT_NAME "miniaudio$<$:d>") # Disable most features since we don't need them. target_compile_definitions(miniaudio PUBLIC MA_ENABLE_ONLY_SPECIFIC_BACKENDS @@ -45,5 +44,30 @@ endif() add_library(libsmacker STATIC ${libsmacker_SOURCE_DIR}/smacker.c ) -set_property(TARGET libsmacker PROPERTY ARCHIVE_OUTPUT_NAME "libsmacker$<$:d>") target_include_directories(libsmacker PUBLIC ${libsmacker_SOURCE_DIR}) + +if(DOWNLOAD_DEPENDENCIES) + include(FetchContent) + FetchContent_Declare( + imgui + GIT_REPOSITORY "https://github.com/ocornut/imgui" + GIT_TAG f5befd2d29e66809cd1110a152e375a7f1981f06 + ) + FetchContent_MakeAvailable(imgui) +else() + set(imgui_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/imgui") +endif() + +add_library(imgui STATIC + ${imgui_SOURCE_DIR}/imgui.cpp + ${imgui_SOURCE_DIR}/imgui_draw.cpp + ${imgui_SOURCE_DIR}/imgui_tables.cpp + ${imgui_SOURCE_DIR}/imgui_widgets.cpp + ${imgui_SOURCE_DIR}/imgui_demo.cpp + ${imgui_SOURCE_DIR}/backends/imgui_impl_sdl3.cpp + ${imgui_SOURCE_DIR}/backends/imgui_impl_sdlrenderer3.cpp +) +target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR}) +target_link_libraries(imgui PUBLIC SDL3::Headers) +target_link_libraries(imgui PRIVATE SDL3::SDL3) +set_property(TARGET imgui PROPERTY CXX_CLANG_TIDY "") diff --git a/3rdparty/imgui b/3rdparty/imgui new file mode 160000 index 00000000..f5befd2d --- /dev/null +++ b/3rdparty/imgui @@ -0,0 +1 @@ +Subproject commit f5befd2d29e66809cd1110a152e375a7f1981f06 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8edd07da..e29cc4db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ option(ISLE_BUILD_APP "Build isle application" ON) option(ISLE_ASAN "Enable Address Sanitizer" OFF) option(ISLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF) option(ISLE_WERROR "Treat warnings as errors" OFF) +option(ISLE_DEBUG "Enable imgui debug" ON) cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF) cmake_dependent_option(ISLE_MINIWIN "Use miniwin and minimfc" ON "NOT ISLE_USE_DX5" OFF) cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN" OFF) @@ -34,6 +35,7 @@ message(STATUS "Isle app: ${ISLE_BUILD_APP}") message(STATUS "Config app: ${ISLE_BUILD_CONFIG}") message(STATUS "Internal DirectX5 SDK: ${ISLE_USE_DX5}") message(STATUS "Internal miniwin: ${ISLE_MINIWIN}") +message(STATUS "Isle debugging: ${ISLE_DEBUG}") if (DOWNLOAD_DEPENDENCIES) # FetchContent downloads and configures dependencies @@ -344,6 +346,7 @@ if (WIN32) target_link_libraries(lego1 INTERFACE winmm) endif() target_link_libraries(lego1 PRIVATE libsmacker miniaudio) +target_include_directories(lego1 PUBLIC $>) # lego1_impl sources target_sources(lego1 PRIVATE @@ -490,6 +493,13 @@ if (ISLE_BUILD_APP) endif() # Link LEGO1 target_link_libraries(isle PRIVATE lego1) + if(ISLE_DEBUG) + target_sources(isle PRIVATE + ISLE/isledebug.cpp + ) + target_compile_definitions(isle PRIVATE ISLE_DEBUG) + target_link_libraries(isle PRIVATE imgui) + endif() endif() if (ISLE_BUILD_CONFIG) diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index a42e4c14..6d3fd428 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -2,6 +2,7 @@ #include "3dmanager/lego3dmanager.h" #include "decomp.h" +#include "isledebug.h" #include "legoanimationmanager.h" #include "legobuildingmanager.h" #include "legogamestate.h" @@ -30,8 +31,6 @@ #include "roi/legoroi.h" #include "viewmanager/viewmanager.h" -#include - #define SDL_MAIN_USE_CALLBACKS #include #include @@ -303,6 +302,8 @@ SDL_AppResult SDL_AppIterate(void* appstate) } if (!g_closed) { + IsleDebug_Render(); + if (g_reqEnableRMDevice) { g_reqEnableRMDevice = FALSE; VideoManager()->EnableRMDevice(); @@ -341,6 +342,10 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) return SDL_APP_CONTINUE; } + if (IsleDebug_Event(event)) { + return SDL_APP_CONTINUE; + } + // [library:window] // Remaining functionality to be implemented: // Full screen - crashes when minimizing/maximizing, but this will probably be fixed once DirectDraw is replaced @@ -348,12 +353,16 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) switch (event->type) { case SDL_EVENT_WINDOW_FOCUS_GAINED: - g_isle->SetWindowActive(TRUE); - Lego()->Resume(); + if (!g_debugEnabled) { + g_isle->SetWindowActive(TRUE); + Lego()->Resume(); + } break; case SDL_EVENT_WINDOW_FOCUS_LOST: - g_isle->SetWindowActive(FALSE); - Lego()->Pause(); + if (!g_debugEnabled) { + g_isle->SetWindowActive(FALSE); + Lego()->Pause(); + } break; case SDL_EVENT_WINDOW_CLOSE_REQUESTED: if (!g_closed) { @@ -566,6 +575,8 @@ MxResult IsleApp::SetupWindow() } } + IsleDebug_Init(); + return SUCCESS; } @@ -832,6 +843,12 @@ MxResult IsleApp::ParseArguments(int argc, char** argv) m_iniPath = argv[i + 1]; consumed = 2; } +#ifdef ISLE_DEBUG + else if (strcmp(argv[i], "--debug") == 0) { + g_debugEnabled = true; + consumed = 1; + } +#endif if (consumed <= 0) { SDL_Log("Invalid argument(s): %s", argv[i]); return FAILURE; diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 91248d35..b3885e6d 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -6,8 +6,7 @@ #include "mxtypes.h" #include "mxvideoparam.h" -#include -#include +#include #ifdef MINIWIN #include "miniwin.h" #else @@ -87,4 +86,6 @@ private: char* m_iniPath; }; +extern IsleApp* g_isle; + #endif // ISLEAPP_H diff --git a/ISLE/isledebug.cpp b/ISLE/isledebug.cpp new file mode 100644 index 00000000..b2b52752 --- /dev/null +++ b/ISLE/isledebug.cpp @@ -0,0 +1,155 @@ +#include "isledebug.h" + +#include "isleapp.h" +#include "legobuildingmanager.h" +#include "legogamestate.h" +#include "legoplantmanager.h" +#include "legosoundmanager.h" +#include "legovideomanager.h" +#include "misc.h" + +#include +#include +#include +#include + +bool g_debugEnabled; +bool g_debugPaused; +SDL_Window* g_debugWindow; +SDL_Renderer* g_debugRenderer; + +void IsleDebug_Init() +{ + do { + if (!g_debugEnabled) { + break; + } + if (!SDL_CreateWindowAndRenderer( + "Debug ISLE", + 640, + 480, + SDL_WINDOW_RESIZABLE, + &g_debugWindow, + &g_debugRenderer + )) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create debug window"); + break; + } + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + if (!ImGui_ImplSDL3_InitForSDLRenderer(g_debugWindow, g_debugRenderer)) { + SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ImGui_ImplSDL3_InitForSDLRenderer failed"); + g_debugEnabled = false; + break; + } + if (!ImGui_ImplSDLRenderer3_Init(g_debugRenderer)) { + g_debugEnabled = false; + break; + } + } while (0); + if (!g_debugEnabled) { + if (g_debugRenderer) { + SDL_DestroyRenderer(g_debugRenderer); + g_debugRenderer = nullptr; + } + if (g_debugWindow) { + SDL_DestroyWindow(g_debugWindow); + g_debugWindow = nullptr; + } + } +} + +bool IsleDebug_Event(SDL_Event* event) +{ + if (!g_debugEnabled) { + return false; + } + if (SDL_GetWindowFromEvent(event) != g_debugWindow) { + return false; + } + ImGui_ImplSDL3_ProcessEvent(event); + return true; +} + +void IsleDebug_Render() +{ + if (!g_debugEnabled) { + return; + } + const ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + ImGui_ImplSDLRenderer3_NewFrame(); + ImGui_ImplSDL3_NewFrame(); + ImGui::NewFrame(); + ImGuiIO& io = ImGui::GetIO(); + { + ImGui::BeginMainMenuBar(); + if (ImGui::MenuItem(g_debugPaused ? "Resume" : "Pause")) { + g_debugPaused = !g_debugPaused; + if (g_debugPaused) { + g_isle->SetWindowActive(false); + Lego()->Pause(); + } + else { + g_isle->SetWindowActive(true); + Lego()->Resume(); + } + } + ImGui::EndMainMenuBar(); + LegoOmni* lego = Lego(); + if (ImGui::Begin("LEGO")) { + if (ImGui::TreeNode("Game State")) { + LegoGameState* gameState = lego->GetGameState(); + ImGui::Value("Actor Id", gameState->GetActorId()); + ImGui::Text("Actor Name: %s", gameState->GetActorName()); + ImGui::Text("Current act: %d", gameState->GetCurrentAct()); + ImGui::Text("Loaded act: %d", gameState->GetLoadedAct()); + ImGui::Text("Previous area: %d", gameState->GetPreviousArea()); + ImGui::Text("Unknown 0x42c: %d", gameState->GetUnknown0x42c()); + ImGui::Value("Player count", gameState->GetPlayerCount()); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Sound Manager")) { + LegoSoundManager* soundManager = lego->GetSoundManager(); + Sint32 oldVolume = soundManager->GetVolume(); + Sint32 volume = oldVolume; + ImGui::SliderInt("volume", &volume, 0, 100); + if (volume != oldVolume) { + soundManager->SetVolume(volume); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Video Manager")) { + LegoVideoManager* videoManager = lego->GetVideoManager(); + ImGui::Text("Elapsed: %g", static_cast(videoManager->GetElapsedSeconds())); + ImGui::Value("Render3D", videoManager->GetRender3D()); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Plant Manager")) { + LegoPlantManager* plantManager = lego->GetPlantManager(); + ImGui::Value("#plants", plantManager->GetNumPlants()); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Building Manager")) { + LegoBuildingManager* buildingManager = lego->GetBuildingManager(); + ImGui::TreePop(); + } + } + ImGui::End(); + } + ImGui::ShowDemoWindow(nullptr); + + ImGui::Render(); + SDL_RenderClear(g_debugRenderer); + SDL_SetRenderScale(g_debugRenderer, io.DisplayFramebufferScale.x, io.DisplayFramebufferScale.y); + SDL_SetRenderDrawColor( + g_debugRenderer, + (Uint8) (clear_color.x * 255), + (Uint8) (clear_color.y * 255), + (Uint8) (clear_color.z * 255), + (Uint8) (clear_color.w * 255) + ); + ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), g_debugRenderer); + SDL_RenderPresent(g_debugRenderer); +} diff --git a/ISLE/isledebug.h b/ISLE/isledebug.h new file mode 100644 index 00000000..e3f2bfbb --- /dev/null +++ b/ISLE/isledebug.h @@ -0,0 +1,32 @@ +#ifndef ISLEDEBUG_H +#define ISLEDEBUG_H + +#if defined(ISLE_DEBUG) + +extern bool g_debugEnabled; + +typedef union SDL_Event SDL_Event; + +extern void IsleDebug_Init(); + +extern bool IsleDebug_Event(SDL_Event* event); + +extern void IsleDebug_Render(); + +#else + +#define IsleDebug_Init() \ + do { \ + } while (0) + +#define IsleDebug_Event(EVENT) false + +#define IsleDebug_Render() \ + do { \ + } while (0) + +#define g_debugEnabled false + +#endif + +#endif diff --git a/LEGO1/lego/legoomni/include/legogamestate.h b/LEGO1/lego/legoomni/include/legogamestate.h index d769c173..1191a867 100644 --- a/LEGO1/lego/legoomni/include/legogamestate.h +++ b/LEGO1/lego/legoomni/include/legogamestate.h @@ -15,7 +15,7 @@ class LegoStorage; class MxVariableTable; class MxString; -extern const char* g_actorNames[7]; +LEGO1_EXPORT extern const char* g_actorNames[7]; // SIZE 0x08 struct ColorStringStruct { diff --git a/LEGO1/lego/legoomni/include/legoplantmanager.h b/LEGO1/lego/legoomni/include/legoplantmanager.h index a7a821d1..a494b5a2 100644 --- a/LEGO1/lego/legoomni/include/legoplantmanager.h +++ b/LEGO1/lego/legoomni/include/legoplantmanager.h @@ -14,7 +14,7 @@ class LegoWorld; // VTABLE: LEGO1 0x100d6758 // SIZE 0x2c -class LegoPlantManager : public MxCore { +class LEGO1_EXPORT LegoPlantManager : public MxCore { public: // SIZE 0x0c struct AnimEntry { diff --git a/miniwin/miniwin/include/miniwin.h b/miniwin/miniwin/include/miniwin.h index 496ed1a2..cca37450 100644 --- a/miniwin/miniwin/include/miniwin.h +++ b/miniwin/miniwin/include/miniwin.h @@ -16,7 +16,7 @@ #define CALLBACK #define FAR #define WINAPI -#define HWND_NOTOPMOST -2 +#define HWND_NOTOPMOST ((HWND) -2) #define RGB(r, g, b) (((BYTE) (r) | ((BYTE) (g) << 8) | ((BYTE) (b) << 16))) #define S_OK ((HRESULT) 0) #define E_NOINTERFACE (0x80004002) @@ -154,7 +154,7 @@ protected: int m_refCount; }; -BOOL SetWindowPos(HWND hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); +BOOL SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags); inline HDC WINAPI GetDC(HWND hWnd) { diff --git a/miniwin/miniwin/src/miniwin.cpp b/miniwin/miniwin/src/miniwin.cpp index e21b5a70..20e6b4be 100644 --- a/miniwin/miniwin/src/miniwin.cpp +++ b/miniwin/miniwin/src/miniwin.cpp @@ -27,7 +27,7 @@ HRESULT IUnknown::QueryInterface(const GUID& riid, void** ppvObject) return E_NOINTERFACE; } -BOOL SetWindowPos(HWND hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags) +BOOL SetWindowPos(HWND hWnd, HWND hWndInsertAfter, int X, int Y, int cx, int cy, UINT uFlags) { if (!hWnd) { return FALSE;