From d0b52eb0ebdc69540bd935ca20e6359326d853fe Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Sun, 6 Oct 2024 18:01:13 +0200 Subject: [PATCH] Add HelloImGui::ManualRender namespace --- src/hello_imgui/doc_api.md | 110 ++++++++++------- src/hello_imgui/doc_api.src.md | 4 +- src/hello_imgui/hello_imgui.h | 113 +++++++++-------- src/hello_imgui/impl/hello_imgui.cpp | 115 +++++++++++++----- src/hello_imgui_demos/CMakeLists.txt | 1 + .../hello_manual_render/CMakeLists.txt | 2 + .../hello_manual_render.main.cpp | 24 ++++ 7 files changed, 238 insertions(+), 131 deletions(-) create mode 100644 src/hello_imgui_demos/hello_manual_render/CMakeLists.txt create mode 100644 src/hello_imgui_demos/hello_manual_render/hello_manual_render.main.cpp diff --git a/src/hello_imgui/doc_api.md b/src/hello_imgui/doc_api.md index e997d3d7..0b82e104 100644 --- a/src/hello_imgui/doc_api.md +++ b/src/hello_imgui/doc_api.md @@ -25,64 +25,80 @@ __HelloImGui::GetRunnerParams()__ will return the runnerParams of the current a # Run Application while handling the rendering loop -If you want to be in control of the rendering loop, you may use the class `HelloImGui::Renderer` (available since September 2024) +If you want to be in control of the rendering loop, you may use the namespace `HelloImGui::ManualRender` (available since September 2024) ```cpp -// 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(); -// } -// ``` -// -// **Notes:** -// 1. Depending on the configuration (`runnerParams.fpsIdling.fpsIdlingMode`), `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. -// By default, -// - 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 +namespace ManualRender { -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 = "", + // HelloImGui::ManualRender is a namespace that groups functions, allowing fine-grained control over the rendering process: + // - It is customizable like HelloImGui::Run: initialize it with `RunnerParams` or `SimpleRunnerParams` + // - `ManualRender::Render()` will render the application for one frame: + // - Ensure that `ManualRender::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: + // C++ + // ```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 + // HelloImGui::ManualRender::SetupFromRunnerParams(runnerParams); + // while (!HelloImGui::GetRunnerParams()->appShallExit) + // { + // HelloImGui::ManualRender::Render(); + // } + // HelloImGui::ManualRender::TearDown(); + // ``` + // Python: + // ```python + // runnerParams = HelloImGui.RunnerParams() + // runnerParams.callbacks.show_gui = ... # your GUI function + // while not hello_imgui.get_runner_params().app_shall_exit: + // hello_imgui.manual_render.render() + // hello_imgui.manual_render.tear_down() + // ``` + // + // **Notes:** + // 1. Depending on the configuration (`runnerParams.fpsIdling.fpsIdlingMode`), `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. + // By default, + // - On Emscripten, `ManualRender::Render()` will return immediately to avoid blocking the main thread. + // - On other platforms, it will sleep + // 2. If initialized with `RunnerParams`, a copy of the `RunnerParams` will be made + // (which can be accessed with `HelloImGui::GetRunnerParams()`). + + // Initializes the rendering with the full customizable `RunnerParams`. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + // A distinct copy of `RunnerParams` is stored internally. + void SetupFromRunnerParams(const RunnerParams& runnerParams); + + // Initializes the rendering with `SimpleRunnerParams`. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + void SetupFromSimpleRunnerParams(const SimpleRunnerParams& simpleParams); + + // Initializes the renderer with a simple GUI function and additional parameters. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + void SetupFromGuiFunction( + const VoidFunction& guiFunction, + const std::string& windowTitle = "", bool windowSizeAuto = false, bool windowRestorePreviousGeometry = false, - const ScreenSize &windowSize = DefaultWindowSize, + const ScreenSize& windowSize = DefaultWindowSize, float fpsIdle = 10.f ); - // Render the current frame (or return immediately if in idle state). + // Renders the current frame. Should be called regularly to maintain the application's responsiveness. void Render(); - // Destructor (automatically tears down HelloImGui). - ~Renderer(); -}; + // Tears down the renderer and releases all associated resources. + // This will release the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + // After calling `TearDown()`, the InitFromXXX can be called with new parameters. + void TearDown(); +} // namespace ManualRender + ``` ---- diff --git a/src/hello_imgui/doc_api.src.md b/src/hello_imgui/doc_api.src.md index 66ea7e5a..74066768 100644 --- a/src/hello_imgui/doc_api.src.md +++ b/src/hello_imgui/doc_api.src.md @@ -7,10 +7,10 @@ HelloImGui is extremely easy to use: there is **one** main function in the API, @import "hello_imgui.h" {md_id=HelloImGui::Run} # Run Application while handling the rendering loop -If you want to be in control of the rendering loop, you may use the class `HelloImGui::Renderer` (available since September 2024) +If you want to be in control of the rendering loop, you may use the namespace `HelloImGui::ManualRender` (available since September 2024) ```cpp -@import "hello_imgui.h" {md_id=HelloImGui::Renderer} +@import "hello_imgui.h" {md_id=HelloImGui::ManualRender} ``` ---- diff --git a/src/hello_imgui/hello_imgui.h b/src/hello_imgui/hello_imgui.h index ba1bff62..c079a7a9 100644 --- a/src/hello_imgui/hello_imgui.h +++ b/src/hello_imgui/hello_imgui.h @@ -82,64 +82,79 @@ void Run( float fpsIdle = 10.f ); +// =========================== HelloImGui::ManualRender ================================== +// @@md#HelloImGui::ManualRender -// =========================== 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 -// 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(); -// } -// ``` -// -// **Notes:** -// 1. Depending on the configuration (`runnerParams.fpsIdling.fpsIdlingMode`), `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. -// By default, -// - 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 +namespace ManualRender { -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 = "", + // HelloImGui::ManualRender is a namespace that groups functions, allowing fine-grained control over the rendering process: + // - It is customizable like HelloImGui::Run: initialize it with `RunnerParams` or `SimpleRunnerParams` + // - `ManualRender::Render()` will render the application for one frame: + // - Ensure that `ManualRender::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: + // C++ + // ```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 + // HelloImGui::ManualRender::SetupFromRunnerParams(runnerParams); + // while (!HelloImGui::GetRunnerParams()->appShallExit) + // { + // HelloImGui::ManualRender::Render(); + // } + // HelloImGui::ManualRender::TearDown(); + // ``` + // Python: + // ```python + // runnerParams = HelloImGui.RunnerParams() + // runnerParams.callbacks.show_gui = ... # your GUI function + // while not hello_imgui.get_runner_params().app_shall_exit: + // hello_imgui.manual_render.render() + // hello_imgui.manual_render.tear_down() + // ``` + // + // **Notes:** + // 1. Depending on the configuration (`runnerParams.fpsIdling.fpsIdlingMode`), `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. + // By default, + // - On Emscripten, `ManualRender::Render()` will return immediately to avoid blocking the main thread. + // - On other platforms, it will sleep + // 2. If initialized with `RunnerParams`, a copy of the `RunnerParams` will be made + // (which can be accessed with `HelloImGui::GetRunnerParams()`). + + // Initializes the rendering with the full customizable `RunnerParams`. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + // A distinct copy of `RunnerParams` is stored internally. + void SetupFromRunnerParams(const RunnerParams& runnerParams); + + // Initializes the rendering with `SimpleRunnerParams`. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + void SetupFromSimpleRunnerParams(const SimpleRunnerParams& simpleParams); + + // Initializes the renderer with a simple GUI function and additional parameters. + // This will initialize the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + void SetupFromGuiFunction( + const VoidFunction& guiFunction, + const std::string& windowTitle = "", bool windowSizeAuto = false, bool windowRestorePreviousGeometry = false, - const ScreenSize &windowSize = DefaultWindowSize, + const ScreenSize& windowSize = DefaultWindowSize, float fpsIdle = 10.f ); - // Render the current frame (or return immediately if in idle state). + // Renders the current frame. Should be called regularly to maintain the application's responsiveness. void Render(); - // Destructor (automatically tears down HelloImGui). - ~Renderer(); -}; + // Tears down the renderer and releases all associated resources. + // This will release the platform backend (SDL, Glfw, etc.) and the rendering backend (OpenGL, Vulkan, etc.). + // After calling `TearDown()`, the InitFromXXX can be called with new parameters. + void TearDown(); +} // namespace ManualRender + // @@md diff --git a/src/hello_imgui/impl/hello_imgui.cpp b/src/hello_imgui/impl/hello_imgui.cpp index cdb084bb..90831d9f 100644 --- a/src/hello_imgui/impl/hello_imgui.cpp +++ b/src/hello_imgui/impl/hello_imgui.cpp @@ -48,8 +48,7 @@ bool _CheckAdditionLayoutNamesUniqueness(const RunnerParams &runnerParams) } -// =========================== HelloImGui::Renderer ================================== - +// =========================== Priv_SetupRunner ================================== // // Static instances to store the last runner and its parameters // ------------------------------------------------------------- @@ -120,42 +119,92 @@ static void Priv_TearDown(SetupMode setupMode) gLastRunnerParamsUserPointer = nullptr; } -Renderer::Renderer(const HelloImGui::RunnerParams &runnerParams) -{ - HelloImGui::RunnerParams runnerParamsCopy = runnerParams; - Priv_SetupRunner(runnerParamsCopy, SetupMode::Renderer); -} -Renderer::Renderer(const HelloImGui::SimpleRunnerParams &simpleParams) +// =========================== ManualRender ================================== +namespace ManualRender { - RunnerParams fullParams = simpleParams.ToRunnerParams(); - Priv_SetupRunner(fullParams, SetupMode::Renderer); -} + // Enumeration to track the current state of the ManualRenderer + enum class RendererStatus + { + NotInitialized, + Initialized, + }; + RendererStatus sCurrentStatus = RendererStatus::NotInitialized; -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, SetupMode::Renderer); -} + // Changes the current status to Initialized if it was NotInitialized, + // otherwise raises an error (assert or exception) + void TrySwitchToInitialized() + { + if (sCurrentStatus == RendererStatus::Initialized) + IM_ASSERT(false && "HelloImGui::ManualRender::SetupFromXXX() cannot be called while already initialized. Call TearDown() first."); + sCurrentStatus = RendererStatus::Initialized; + } -void Renderer::Render() -{ - IM_ASSERT(gLastRunner != nullptr && "HelloImGui::Renderer::Render() called without a valid runner"); - gLastRunner->CreateFramesAndRender(); -} + // Changes the current status to NotInitialized if it was Initialized, + // otherwise raises an error (assert or exception) + void TrySwitchToNotInitialized() + { + if (sCurrentStatus == RendererStatus::NotInitialized) + IM_ASSERT(false && "HelloImGui::ManualRender::TearDown() cannot be called while not initialized."); + sCurrentStatus = RendererStatus::NotInitialized; + } + + // Initializes the renderer with the full customizable `RunnerParams`. + // A distinct copy of `RunnerParams` is stored internally. + void SetupFromRunnerParams(const RunnerParams& runnerParams) + { + TrySwitchToInitialized(); + RunnerParams runnerParamsCopy = runnerParams; + Priv_SetupRunner(runnerParamsCopy, SetupMode::Renderer); + } + + // Initializes the renderer with `SimpleRunnerParams`. + void SetupFromSimpleRunnerParams(const SimpleRunnerParams& simpleParams) + { + TrySwitchToInitialized(); + RunnerParams fullParams = simpleParams.ToRunnerParams(); + Priv_SetupRunner(fullParams, SetupMode::Renderer); + } + + // Initializes the renderer with a simple GUI function and additional parameters. + void SetupFromGuiFunction( + const VoidFunction& guiFunction, + const std::string& windowTitle, + bool windowSizeAuto, + bool windowRestorePreviousGeometry, + const ScreenSize& windowSize, + float fpsIdle + ) + { + TrySwitchToInitialized(); + 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, SetupMode::Renderer); + } + + // Renders the current frame. Should be called regularly to maintain the application's responsiveness. + void Render() + { + IM_ASSERT(gLastRunner != nullptr && "HelloImGui::Renderer::Render() called without a valid runner"); + gLastRunner->CreateFramesAndRender(); + } + + // Tears down the renderer and releases all associated resources. + // After calling `TearDown()`, the InitFromXXX can be called with new parameters. + void TearDown() + { + TrySwitchToNotInitialized(); + Priv_TearDown(SetupMode::Renderer); + } + +} // namespace ManualRender -Renderer::~Renderer() -{ - Priv_TearDown(SetupMode::Renderer); -} // =========================== HelloImGui::Run ================================== diff --git a/src/hello_imgui_demos/CMakeLists.txt b/src/hello_imgui_demos/CMakeLists.txt index e3621aba..3b4deeb1 100644 --- a/src/hello_imgui_demos/CMakeLists.txt +++ b/src/hello_imgui_demos/CMakeLists.txt @@ -8,6 +8,7 @@ set(subdirs hello_idbfs hello_imgui_demo_test_engine hello_custom_background + hello_manual_render ) if(HELLOIMGUI_HAS_METAL) # Demo of EDR (Extended Dynamic Range) support, only for Metal diff --git a/src/hello_imgui_demos/hello_manual_render/CMakeLists.txt b/src/hello_imgui_demos/hello_manual_render/CMakeLists.txt new file mode 100644 index 00000000..a7f881f9 --- /dev/null +++ b/src/hello_imgui_demos/hello_manual_render/CMakeLists.txt @@ -0,0 +1,2 @@ +include(hello_imgui_add_app) +hello_imgui_add_app(hello_manual_render hello_manual_render.main.cpp) diff --git a/src/hello_imgui_demos/hello_manual_render/hello_manual_render.main.cpp b/src/hello_imgui_demos/hello_manual_render/hello_manual_render.main.cpp new file mode 100644 index 00000000..e9eceb15 --- /dev/null +++ b/src/hello_imgui_demos/hello_manual_render/hello_manual_render.main.cpp @@ -0,0 +1,24 @@ +#include "hello_imgui/hello_imgui.h" + +void Gui() +{ + ImGui::Text("Hello..."); + if (ImGui::Button("Goodbye!")) + HelloImGui::GetRunnerParams()->appShallExit = true; +}; + + +int main(int , char *[]) +{ + // Run 2 applications, one after the other + // to make sure that the manual render is working correctly and releasing resources + for (int i = 0; i < 2; i++) + { + HelloImGui::ManualRender::SetupFromGuiFunction(Gui); + while (! HelloImGui::GetRunnerParams()->appShallExit) + { + HelloImGui::ManualRender::Render(); + } + HelloImGui::ManualRender::TearDown(); + } +}