From ea39841fcd5338dc0b041c24ef81613d95b066e7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 3 Feb 2023 22:23:23 +0100 Subject: [PATCH] Examples: (Again, but better) made SDL+GL and GLFW+GL examples build with Emscripten. (#2492, #2494, #3699, #3705) --- docs/CHANGELOG.txt | 3 +- examples/example_glfw_opengl3/main.cpp | 16 ++++++++ examples/example_sdl_opengl3/main.cpp | 16 ++++++++ .../emscripten/emscripten_mainloop_stub.h | 37 +++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 examples/libs/emscripten/emscripten_mainloop_stub.h diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 264660d5..60c49b4e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -98,7 +98,8 @@ All changes: - Backends: WebGPU: Fix building for latest WebGPU specs (remove implicit layout generation). (#6117, #4116, #3632) [@tonygrue, @bfierz] - Examples: refactored SDL+GL and GLFW+GL examples to compile with Emscripten. - (the dedicated example_emscripten_opengl3/ has been removed) (#2492, #2494, #3699, #3705) + (#2492, #2494, #3699, #3705) [@ocornut, @nicolasnoble] + The dedicated example_emscripten_opengl3/ has been removed. - Examples: Win32: Fixed examples using RegisterClassW() since 1.89 to also call DefWindowProcW() instead of DefWindowProc() so that title text are correctly converted when application is compiled without /DUNICODE. (#5725, #5961, #5975) [@markreidvfx] diff --git a/examples/example_glfw_opengl3/main.cpp b/examples/example_glfw_opengl3/main.cpp index 78e67500..6c0ea101 100644 --- a/examples/example_glfw_opengl3/main.cpp +++ b/examples/example_glfw_opengl3/main.cpp @@ -20,6 +20,11 @@ #pragma comment(lib, "legacy_stdio_definitions") #endif +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + static void glfw_error_callback(int error, const char* description) { fprintf(stderr, "GLFW Error %d: %s\n", error, description); @@ -85,6 +90,7 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. //io.Fonts->AddFontDefault(); //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); @@ -99,7 +105,14 @@ int main(int, char**) ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); // Main loop +#ifdef __EMSCRIPTEN__ + // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. + // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. + io.IniFilename = NULL; + EMSCRIPTEN_MAINLOOP_BEGIN +#else while (!glfwWindowShouldClose(window)) +#endif { // Poll and handle events (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. @@ -161,6 +174,9 @@ int main(int, char**) glfwSwapBuffers(window); } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif // Cleanup ImGui_ImplOpenGL3_Shutdown(); diff --git a/examples/example_sdl_opengl3/main.cpp b/examples/example_sdl_opengl3/main.cpp index 41b7aa1a..bc660a51 100644 --- a/examples/example_sdl_opengl3/main.cpp +++ b/examples/example_sdl_opengl3/main.cpp @@ -14,6 +14,11 @@ #include #endif +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + // Main code int main(int, char**) { @@ -81,6 +86,7 @@ int main(int, char**) // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. //io.Fonts->AddFontDefault(); //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f); //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f); @@ -96,7 +102,14 @@ int main(int, char**) // Main loop bool done = false; +#ifdef __EMSCRIPTEN__ + // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. + // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. + io.IniFilename = NULL; + EMSCRIPTEN_MAINLOOP_BEGIN +#else while (!done) +#endif { // Poll and handle events (inputs, window resize, etc.) // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. @@ -163,6 +176,9 @@ int main(int, char**) ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(window); } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif // Cleanup ImGui_ImplOpenGL3_Shutdown(); diff --git a/examples/libs/emscripten/emscripten_mainloop_stub.h b/examples/libs/emscripten/emscripten_mainloop_stub.h new file mode 100644 index 00000000..05cf60fe --- /dev/null +++ b/examples/libs/emscripten/emscripten_mainloop_stub.h @@ -0,0 +1,37 @@ +// What does this file solves? +// - Since Dear ImGui 1.00 we took pride that most of our examples applications had their entire +// main-loop inside the main() function. That's because: +// - It makes the examples easier to read, keeping the code sequential. +// - It permit the use of local variables, making it easier to try things and perform quick +// changes when someone needs to quickly test something (vs having to structure the example +// in order to pass data around). This is very important because people use those examples +// to craft easy-to-past repro when they want to discuss features or report issues. +// - It conveys at a glance that this is a no-BS framework, it won't take your main loop away from you. +// - It is generally nice and elegant. +// - However, comes Emscripten... it is a wonderful and magical tech but it requires a "main loop" function. +// - Only some of our examples would run on Emscripten. Typically the ones rendering with GL or WGPU ones. +// - I tried to refactor those examples but felt it was problematic that other examples didn't follow the +// same layout. Why would the SDL+GL example be structured one way and the SGL+DX11 be structured differently? +// Especially as we are trying hard to convey that using a Dear ImGui backend in an *existing application* +// should requires only a few dozens lines of code, and this should be consistent and symmetrical for all backends. +// - So the next logical step was to refactor all examples to follow that layout of using a "main loop" function. +// This worked, but it made us lose all the nice things we had... + +// Since only about 3 examples really need to run with Emscripten, here's our solution: +// - Use some weird macros and capturing lambda to turn a loop in main() into a function. +// - Hide all that crap in this file so it doesn't make our examples unusually ugly. +// As a stance and principle of Dear ImGui development we don't use C++ headers and we don't +// want to suggest to the newcomer that we would ever use C++ headers as this would affect +// the initial judgment of many of our target audience. +// - Technique is based on this idea: https://github.com/ocornut/imgui/pull/2492/ +#ifdef __EMSCRIPTEN__ +#include +#include +static std::function MainLoopForEmscriptenP; +static void MainLoopForEmscripten() { MainLoopForEmscriptenP(); } +#define EMSCRIPTEN_MAINLOOP_BEGIN MainLoopForEmscriptenP = [&]() +#define EMSCRIPTEN_MAINLOOP_END ; emscripten_set_main_loop(MainLoopForEmscripten, 0, true) +#else +#define EMSCRIPTEN_MAINLOOP_BEGIN +#define EMSCRIPTEN_MAINLOOP_END +#endif