From 1c320501b90f698647269471a2716339e828ce3e Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Mon, 30 Sep 2024 07:28:24 +0200 Subject: [PATCH] Added HelloImGui::Renderer // HelloImGui::Renderer is an alternative to HelloImGui::Run, allowing fine-grained control over the rendering process. // - It is customizable like HelloImGui::Run: construct it with `RunnerParams` or `SimpleRunnerParams` // - `Render()` will render the application for one frame: // Ensure that `Render()` is triggered regularly (e.g., through a loop or other mechanism) to maintain responsiveness. // This method must be called on the main thread. // // A typical use case is: // ```cpp // HelloImGui::RunnerParams runnerParams; // runnerParams.callbacks.ShowGui = ...; // your GUI function // // Optionally, choose between Sleep, EarlyReturn, or Auto for fps idling mode: // // runnerParams.fpsIdling.fpsIdlingMode = HelloImGui::FpsIdlingMode::Sleep; // or EarlyReturn, Auto // Renderer renderer(runnerParams); // note: a distinct copy of the `RunnerParams` will be stored inside the HelloImGui::GetRunnerParams() // while (! HelloImGui::GetRunnerParams()->appShallExit) // { // renderer.Render(); // } // ``` --- src/hello_imgui/doc_api.md | 6 + src/hello_imgui/hello_imgui.h | 64 ++++++++- src/hello_imgui/impl/hello_imgui.cpp | 123 ++++++++++++++++-- .../backend_impls/abstract_runner.cpp | 8 +- .../internal/backend_impls/abstract_runner.h | 5 +- 5 files changed, 183 insertions(+), 23 deletions(-) diff --git a/src/hello_imgui/doc_api.md b/src/hello_imgui/doc_api.md index dff2cc8a..d6eb8f4a 100644 --- a/src/hello_imgui/doc_api.md +++ b/src/hello_imgui/doc_api.md @@ -279,6 +279,12 @@ ImVec2 ImageProportionalSize(const ImVec2& askedSize, const ImVec2& imageSize); ```cpp +// `GetRunnerParams()`: a convenience function that will return the runnerParams +// of the current application + RunnerParams* GetRunnerParams(); + +// `IsUsingHelloImGui()`: returns true if the application is using HelloImGui + bool IsUsingHelloImGui(); // `FrameRate(durationForMean = 0.5)`: Returns the current FrameRate. // May differ from ImGui::GetIO().FrameRate, since one can choose the duration diff --git a/src/hello_imgui/hello_imgui.h b/src/hello_imgui/hello_imgui.h index 826545ba..0ee7be43 100644 --- a/src/hello_imgui/hello_imgui.h +++ b/src/hello_imgui/hello_imgui.h @@ -82,18 +82,72 @@ void Run( float fpsIdle = 10.f ); -// `GetRunnerParams()`: a convenience function that will return the runnerParams -// of the current application -RunnerParams* GetRunnerParams(); -// `IsUsingHelloImGui()`: returns true if the application is using HelloImGui -bool IsUsingHelloImGui(); +// =========================== HelloImGui::Renderer ================================== +// @@md#HelloImGui::Renderer + +// HelloImGui::Renderer is an alternative to HelloImGui::Run, allowing fine-grained control over the rendering process. +// - It is customizable like HelloImGui::Run: construct it with `RunnerParams` or `SimpleRunnerParams` +// - `Render()` will render the application for one frame: +// Ensure that `Render()` is triggered regularly (e.g., through a loop or other mechanism) to maintain responsiveness. +// This method must be called on the main thread. +// +// A typical use case is: +// ```cpp +// Renderer renderer(runnerParams); // note: a distinct copy of the `RunnerParams` will be stored inside the HelloImGui::GetRunnerParams() +// while (! HelloImGui::GetRunnerParams()->appShallExit) +// { +// renderer.Render(); +// } +// ``` +// +// **Notes:** +// 1. Depending on the configuration (such as `fpsIdle`), `HelloImGui` may enter an idle state to reduce CPU usage, +// if no events are received (e.g., no input or interaction). +// In this case, `Render()` will either sleep or return immediately: +// - On Emscripten, `Render()` will return immediately to avoid blocking the main thread. +// - On other platforms, it will sleep +// 2. Only one instance of `Renderer` can exist at a time. +// 3. If constructed with `RunnerParams`, a copy of the `RunnerParams` will be made (which you can access with `GetRunnerParams())`. +class Renderer +{ +public: + // Initializes with the full customizable `RunnerParams` to set up the application. + // Nb: a distinct copy of the `RunnerParams` will be made, and you can access it with `GetRunnerParams()`. + Renderer(const RunnerParams& runnerParams); + + // Initializes with SimpleRunnerParams. + Renderer(const SimpleRunnerParams& simpleParams); + + // Initializes with a simple GUI function and additional parameters. + Renderer( + const VoidFunction &guiFunction, + const std::string &windowTitle = "", + bool windowSizeAuto = false, + bool windowRestorePreviousGeometry = false, + const ScreenSize &windowSize = DefaultWindowSize, + float fpsIdle = 10.f + ); + + // Render the current frame (or return immediately if in idle state). + void Render(); + + // Destructor (automatically tears down HelloImGui). + ~Renderer(); +}; +// @@md // ============================== Utility functions =============================== // @@md#UtilityFunctions +// `GetRunnerParams()`: a convenience function that will return the runnerParams +// of the current application + RunnerParams* GetRunnerParams(); + +// `IsUsingHelloImGui()`: returns true if the application is using HelloImGui + bool IsUsingHelloImGui(); // `FrameRate(durationForMean = 0.5)`: Returns the current FrameRate. // May differ from ImGui::GetIO().FrameRate, since one can choose the duration diff --git a/src/hello_imgui/impl/hello_imgui.cpp b/src/hello_imgui/impl/hello_imgui.cpp index 28d5a0a2..5ad0fc64 100644 --- a/src/hello_imgui/impl/hello_imgui.cpp +++ b/src/hello_imgui/impl/hello_imgui.cpp @@ -7,13 +7,11 @@ #include #include #include +#include namespace HelloImGui { -RunnerParams* gLastRunnerParams = nullptr; -std::unique_ptr gLastRunner; - std::string gMissingBackendErrorMessage = R"( When running CMake, you should specify @@ -33,7 +31,7 @@ When running CMake, you should specify )"; -bool _CheckAdditionLayoutNamesUniqueness(RunnerParams &runnerParams) +bool _CheckAdditionLayoutNamesUniqueness(const RunnerParams &runnerParams) { std::set names_set; names_set.insert(runnerParams.dockingParams.layoutName); @@ -49,18 +47,112 @@ bool _CheckAdditionLayoutNamesUniqueness(RunnerParams &runnerParams) return areNamesUnique; } -void Run(RunnerParams& runnerParams) + +// =========================== HelloImGui::Renderer ================================== + +// +// Static instances to store the last runner and its parameters +// ------------------------------------------------------------- +// gLastRunnerParamsOpt may contain a valid value if Renderer(const RunnerParams& ) was used +std::optional gLastRunnerParamsOpt; +// gLastRunnerParamsUserPointer may contain a valid value if Run(RunnerParams& ) was used +// (we may modify the user's runnerParams in this case) +static RunnerParams* gLastRunnerParamsUserPointer = nullptr; +// a pointer to the current AbstractRunner +static std::unique_ptr gLastRunner; + +static RunnerParams* Priv_CurrentRunnerParamsPtr() +{ + if (gLastRunnerParamsOpt.has_value()) + return &gLastRunnerParamsOpt.value(); + if (gLastRunnerParamsUserPointer != nullptr) + return gLastRunnerParamsUserPointer; + return nullptr; +} + + +// Only one instance of `Renderer` can exist at a time +// --------------------------------------------------- +static int gRendererInstanceCount = 0; + +static void Priv_SetupRunner(RunnerParams &passedUserParams, bool isUserPointer) { + if (gRendererInstanceCount > 0) + throw std::runtime_error("Only one instance of `HelloImGui::Renderer` can exist at a time."); + if (!isUserPointer) + gLastRunnerParamsOpt = passedUserParams; // Store a copy of the user's runnerParams + else + gLastRunnerParamsUserPointer = &passedUserParams; // Store a pointer to the user's runnerParams + + IM_ASSERT(Priv_CurrentRunnerParamsPtr() != nullptr); + RunnerParams &runnerParams = *Priv_CurrentRunnerParamsPtr(); IM_ASSERT(_CheckAdditionLayoutNamesUniqueness(runnerParams)); + gLastRunner = FactorRunner(runnerParams); if (gLastRunner == nullptr) { - fprintf(stderr, "HelloImGui::Run() failed to factor a runner!\n %s", gMissingBackendErrorMessage.c_str()); - IM_ASSERT(false && "HelloImGui::Run() failed to factor a runner!"); + fprintf(stderr, "HelloImGui::Renderer() failed to factor a runner!\n %s", gMissingBackendErrorMessage.c_str()); + IM_ASSERT(false && "HelloImGui::Renderer() failed to factor a runner!"); } - gLastRunnerParams = &runnerParams; + gLastRunner->Setup(); + gRendererInstanceCount++; +} + +static void Priv_TearDown() +{ + IM_ASSERT(gLastRunner != nullptr && "HelloImGui::Renderer::~Renderer() called without a valid runner"); + if (!gLastRunner->WasTearedDown()) + gLastRunner->TearDown(false); + gLastRunner = nullptr; + gRendererInstanceCount = 0; + gLastRunnerParamsOpt.reset(); + gLastRunnerParamsUserPointer = nullptr; +} + +Renderer::Renderer(const HelloImGui::RunnerParams &runnerParams) +{ + HelloImGui::RunnerParams runnerParamsCopy = runnerParams; + Priv_SetupRunner(runnerParamsCopy, false); +} + +Renderer::Renderer(const HelloImGui::SimpleRunnerParams &simpleParams) +{ + RunnerParams fullParams = simpleParams.ToRunnerParams(); + Priv_SetupRunner(fullParams, false); +} + +Renderer::Renderer(const HelloImGui::VoidFunction &guiFunction, const std::string &windowTitle, bool windowSizeAuto, + bool windowRestorePreviousGeometry, const HelloImGui::ScreenSize &windowSize, float fpsIdle) +{ + SimpleRunnerParams params; + params.guiFunction = guiFunction; + params.windowTitle = windowTitle; + params.windowSizeAuto = windowSizeAuto; + params.windowRestorePreviousGeometry = windowRestorePreviousGeometry; + params.windowSize = windowSize; + params.fpsIdle = fpsIdle; + RunnerParams fullParams = params.ToRunnerParams(); + Priv_SetupRunner(fullParams, false); +} + +void Renderer::Render() +{ + IM_ASSERT(gLastRunner != nullptr && "HelloImGui::Renderer::Render() called without a valid runner"); + gLastRunner->CreateFramesAndRender(); +} + +Renderer::~Renderer() +{ + Priv_TearDown(); +} + +// =========================== HelloImGui::Run ================================== + +void Run(RunnerParams& runnerParams) +{ + Priv_SetupRunner(runnerParams, true); gLastRunner->Run(); - gLastRunnerParams = nullptr; + Priv_TearDown(); } void Run(const SimpleRunnerParams& simpleRunnerParams) @@ -88,19 +180,22 @@ void Run( Run(params); } + +// ============================== Utility functions =============================== + RunnerParams* GetRunnerParams() { - if (gLastRunnerParams == nullptr) + auto ptr = Priv_CurrentRunnerParamsPtr(); + if (ptr == nullptr) throw std::runtime_error("HelloImGui::GetRunnerParams() would return null. Did you call HelloImGui::Run()?"); - return gLastRunnerParams; + return ptr; } bool IsUsingHelloImGui() { - return gLastRunnerParams != nullptr; + return Priv_CurrentRunnerParamsPtr() != nullptr; } - void SwitchLayout(const std::string& layoutName) { gLastRunner->LayoutSettings_SwitchLayout(layoutName); @@ -108,7 +203,7 @@ void SwitchLayout(const std::string& layoutName) std::string CurrentLayoutName() { - return gLastRunnerParams->dockingParams.layoutName; + return GetRunnerParams()->dockingParams.layoutName; } diff --git a/src/hello_imgui/internal/backend_impls/abstract_runner.cpp b/src/hello_imgui/internal/backend_impls/abstract_runner.cpp index 13265156..b1fa7161 100644 --- a/src/hello_imgui/internal/backend_impls/abstract_runner.cpp +++ b/src/hello_imgui/internal/backend_impls/abstract_runner.cpp @@ -120,12 +120,10 @@ AbstractRunner::~AbstractRunner() } -#ifndef USEHACK void AbstractRunner::Run() { Setup(); - mIdxFrame = 0; #ifdef HELLOIMGUI_MOBILEDEVICE while (true) CreateFramesAndRender(); @@ -145,7 +143,7 @@ void AbstractRunner::Run() } #endif } -#endif + #ifdef HELLO_IMGUI_IMGUI_SHARED static void* MyMallocWrapper(size_t size, void* user_data) { IM_UNUSED(user_data); return malloc(size); } @@ -783,6 +781,8 @@ void AbstractRunner::Setup() // Create a remote display handler if needed mRemoteDisplayHandler.Create(); mRemoteDisplayHandler.SendFonts(); + + mIdxFrame = 0; } @@ -1421,6 +1421,8 @@ void AbstractRunner::OnLowMemory() void AbstractRunner::TearDown(bool gotException) { + IM_ASSERT(!mWasTearedDown && "TearDown() called twice!"); + mWasTearedDown = true; if (! gotException) { // Store screenshot before exiting diff --git a/src/hello_imgui/internal/backend_impls/abstract_runner.h b/src/hello_imgui/internal/backend_impls/abstract_runner.h index 20d804a3..97135657 100644 --- a/src/hello_imgui/internal/backend_impls/abstract_runner.h +++ b/src/hello_imgui/internal/backend_impls/abstract_runner.h @@ -26,9 +26,10 @@ class AbstractRunner void Setup(); void CreateFramesAndRender(bool insideReentrantCall = false); - void RenderGui(); void TearDown(bool gotException); + bool WasTearedDown() { return mWasTearedDown; } + // Events for mobile devices void OnPause(); void OnResume(); @@ -82,6 +83,7 @@ class AbstractRunner #endif private: + void RenderGui(); void InitImGuiContext(); void SetImGuiPrefs(); void InitRenderBackendCallbacks(); @@ -110,6 +112,7 @@ class AbstractRunner bool mPotentialFontLoadingError = false; int mIdxFrame = 0; bool mWasWindowAutoResizedOnPreviousFrame = false; + bool mWasTearedDown = false; // Differentiate between cases where the window was resized by code // and cases where the window was resized by the user