From 3ab1ca2347bd5264011ae849560508459eba4e5e Mon Sep 17 00:00:00 2001 From: halx99 Date: Wed, 12 Feb 2025 01:12:54 +0800 Subject: [PATCH] Improve webgl render fps (#2379) Improve webgl fps --- cmake/Modules/AXBuildHelpers.cmake | 2 +- core/base/JobSystem.cpp | 15 +- core/platform/android/Application-android.cpp | 2 +- core/platform/wasm/Application-wasm.cpp | 152 +++++++++++------- core/platform/wasm/Application-wasm.h | 1 - core/platform/wasm/shell_minimal.html | 4 +- templates/common/proj.wasm/main.cpp | 17 +- tests/cpp-tests/Source/AppDelegate.cpp | 2 + tests/cpp-tests/proj.wasm/main.cpp | 12 +- tests/fairygui-tests/proj.wasm/main.cpp | 12 +- tests/live2d-tests/proj.wasm/main.cpp | 12 +- tests/lua-tests/proj.wasm/main.cpp | 12 +- tests/unit-tests/proj.wasm/main.cpp | 12 +- 13 files changed, 174 insertions(+), 81 deletions(-) diff --git a/cmake/Modules/AXBuildHelpers.cmake b/cmake/Modules/AXBuildHelpers.cmake index 4904d42528e8..964977842bfd 100644 --- a/cmake/Modules/AXBuildHelpers.cmake +++ b/cmake/Modules/AXBuildHelpers.cmake @@ -533,7 +533,7 @@ set(AX_WASM_SHELL_FILE "${_AX_ROOT}/core/platform/wasm/shell_minimal.html" CACHE option(AX_WASM_ENABLE_DEVTOOLS "Enable wasm devtools" ON) -set(_AX_WASM_EXPORTS "_main,_axmol_wgl_context_lost,_axmol_wgl_context_restored,_axmol_hdoc_visibilitychange") +set(_AX_WASM_EXPORTS "_main,_axmol_webglcontextlost,_axmol_webglcontextrestored,_axmol_hdoc_visibilitychange") if(AX_WASM_ENABLE_DEVTOOLS) string(APPEND _AX_WASM_EXPORTS ",_axmol_dev_pause,_axmol_dev_resume,_axmol_dev_step") endif() diff --git a/core/base/JobSystem.cpp b/core/base/JobSystem.cpp index 9fde7b7567c2..f55f87d30862 100644 --- a/core/base/JobSystem.cpp +++ b/core/base/JobSystem.cpp @@ -35,6 +35,10 @@ #include #include +#if defined(__EMSCRIPTEN__) +# include +#endif + namespace ax { @@ -135,15 +139,20 @@ static int clampThreads(int nThreads) { if (nThreads <= 0) { -#if !defined(__EMSCRIPTEN__) || defined(__EMSCRIPTEN_PTHREADS__) +#if !defined(__EMSCRIPTEN__) # if defined(AX_PLATFORM_PC) nThreads = (std::max)(static_cast(std::thread::hardware_concurrency() * 3 / 2), 2); # else nThreads = (std::clamp)(static_cast(std::thread::hardware_concurrency()) - 2, 2, 8); # endif #else - AXLOGW("The emscripten pthread not enabled, JobSystem not working"); +# if defined(__EMSCRIPTEN_PTHREADS__) + nThreads = EM_ASM_INT(return PThread.unusedWorkers.length); + AXLOGI("The emscripten pthread enabled, unused workers count:{}", nThreads); +# else nThreads = 0; + AXLOGW("The emscripten pthread not enabled, JobSystem not working"); +# endif #endif } @@ -235,4 +244,4 @@ void JobSystem::enqueue(std::function task, std::function done) #pragma endregion -} +} // namespace ax diff --git a/core/platform/android/Application-android.cpp b/core/platform/android/Application-android.cpp index 1be12021d796..813ef6123ba8 100644 --- a/core/platform/android/Application-android.cpp +++ b/core/platform/android/Application-android.cpp @@ -65,7 +65,7 @@ Application::~Application() int Application::run() { - // Initialize instance and cocos2d. + // Initialize instance and axmol. if (!applicationDidFinishLaunching()) { return 0; diff --git a/core/platform/wasm/Application-wasm.cpp b/core/platform/wasm/Application-wasm.cpp index 0a746fae717e..eca761430ae2 100644 --- a/core/platform/wasm/Application-wasm.cpp +++ b/core/platform/wasm/Application-wasm.cpp @@ -36,8 +36,11 @@ THE SOFTWARE. # include "base/Director.h" # include "base/Utils.h" # include "platform/FileUtils.h" +# include "yasio/utils.hpp" # include +extern void axmol_wasm_app_exit(); + extern "C" { // void axmol_hdoc_visibilitychange(bool hidden) @@ -47,13 +50,13 @@ void axmol_hdoc_visibilitychange(bool hidden) } // webglcontextlost -void axmol_wgl_context_lost() +void axmol_webglcontextlost() { AXLOGI("receive event: webglcontextlost"); } // webglcontextrestored -void axmol_wgl_context_restored() +void axmol_webglcontextrestored() { AXLOGI("receive event: webglcontextrestored"); @@ -90,7 +93,76 @@ namespace ax // sharedApplication pointer Application* Application::sm_pSharedApplication = nullptr; -Application::Application() : _animationSpeed(60) +static int64_t NANOSECONDSPERSECOND = 1000000000LL; +static int64_t NANOSECONDSPERMICROSECOND = 1000000LL; +static int64_t FPS_CONTROL_THRESHOLD = static_cast(1.0f / 1200.0f * NANOSECONDSPERSECOND); + +static int64_t s_animationInterval = static_cast(1/60.0 * NANOSECONDSPERSECOND); + +static Director* __director; +static int64_t mLastTickInNanoSeconds = 0; + +static void renderFrame() { + auto director = __director; + auto glview = director->getGLView(); + + director->mainLoop(); + glview->pollEvents(); + + if (glview->windowShouldClose()) + { + AXLOGI("shuting down axmol wasm app ..."); + emscripten_cancel_main_loop(); // Cancel current loop and set the cleanup one. + + if (glview->isOpenGLReady()) + { + director->end(); + director->mainLoop(); + } + glview->release(); + + axmol_wasm_app_exit(); + } +} + +static void updateFrame(void) +{ + renderFrame(); + + /* + * No need to use algorithm in default(60,90,120... FPS) situation, + * since onDrawFrame() was called by system 60 times per second by default. + */ + if (s_animationInterval > FPS_CONTROL_THRESHOLD) { + auto interval = yasio::xhighp_clock() - mLastTickInNanoSeconds; + + if (interval < s_animationInterval) { + std::this_thread::sleep_for(std::chrono::nanoseconds(s_animationInterval - interval)); + } + + mLastTickInNanoSeconds = yasio::xhighp_clock(); + } +} + +static void getCurrentLangISO2(char buf[16]) { + // clang-format off + EM_ASM_ARGS( + { + var lang = localStorage.getItem('localization_language'); + if (lang == null) + { + stringToUTF8(window.navigator.language.replace(/-.*/, ""), $0, 16); + } + else + { + stringToUTF8(lang, $0, 16); + } + }, + buf); + // clang-format on +} + +Application::Application() { AX_ASSERT(!sm_pSharedApplication); sm_pSharedApplication = this; @@ -102,15 +174,6 @@ Application::~Application() sm_pSharedApplication = nullptr; } -extern "C" void mainLoopIter(void) -{ - auto director = Director::getInstance(); - auto glview = director->getGLView(); - - director->mainLoop(); - glview->pollEvents(); -} - int Application::run() { initGLContextAttrs(); @@ -120,33 +183,27 @@ int Application::run() return 1; } - auto director = Director::getInstance(); - auto glview = director->getGLView(); + __director = Director::getInstance(); // Retain glview to avoid glview being released in the while loop - glview->retain(); - - // emscripten_set_main_loop(&mainLoopIter, 0, 1); - emscripten_set_main_loop(&mainLoopIter, _animationSpeed, 1); - // TODO: ? does these cleanup really run? - /* Only work on Desktop - * Director::mainLoop is really one frame logic - * when we want to close the window, we should call Director::end(); - * then call Director::mainLoop to do release of internal resources - */ - if (glview->isOpenGLReady()) - { - director->end(); - director->mainLoop(); - director = nullptr; - } - glview->release(); + __director->getGLView()->retain(); + + /* + The JavaScript environment will call that function at a specified number + of frames per second. If called on the main browser thread, setting 0 or + a negative value as the fps will use the browser’s requestAnimationFrame mechanism + o call the main loop function. This is HIGHLY recommended if you are doing rendering, + as the browser’s requestAnimationFrame will make sure you render at a proper smooth rate + that lines up properly with the browser and monitor. + */ + emscripten_set_main_loop(updateFrame, -1, false); + return 0; } void Application::setAnimationInterval(float interval) { - _animationSpeed = 1.0f / interval; + s_animationInterval = static_cast(interval * NANOSECONDSPERSECOND); } void Application::setResourceRootPath(const std::string& rootResDir) @@ -203,20 +260,7 @@ const char* Application::getCurrentLanguageCode() { static char code[3] = {0}; char pLanguageName[16]; - - EM_ASM_ARGS( - { - var lang = localStorage.getItem('localization_language'); - if (lang == null) - { - stringToUTF8(window.navigator.language.replace(/ - .*/, ""), $0, 16); - } - else - { - stringToUTF8(lang, $0, 16); - } - }, - pLanguageName); + getCurrentLangISO2(pLanguageName); strncpy(code, pLanguageName, 2); code[2] = '\0'; return code; @@ -225,21 +269,7 @@ const char* Application::getCurrentLanguageCode() LanguageType Application::getCurrentLanguage() { char pLanguageName[16]; - - EM_ASM_ARGS( - { - var lang = localStorage.getItem('localization_language'); - if (lang == null) - { - stringToUTF8(window.navigator.language.replace(/ - .*/, ""), $0, 16); - } - else - { - stringToUTF8(lang, $0, 16); - } - }, - pLanguageName); - + getCurrentLangISO2(pLanguageName); return utils::getLanguageTypeByISO2(pLanguageName); } diff --git a/core/platform/wasm/Application-wasm.h b/core/platform/wasm/Application-wasm.h index 61868fabbd9c..a8e0f0603933 100644 --- a/core/platform/wasm/Application-wasm.h +++ b/core/platform/wasm/Application-wasm.h @@ -112,7 +112,6 @@ class Application : public ApplicationBase */ virtual Platform getTargetPlatform() override; protected: - long _animationSpeed; // micro second std::string _resourceRootPath; static Application * sm_pSharedApplication; diff --git a/core/platform/wasm/shell_minimal.html b/core/platform/wasm/shell_minimal.html index 47e952152634..2ee33cd7de1d 100644 --- a/core/platform/wasm/shell_minimal.html +++ b/core/platform/wasm/shell_minimal.html @@ -140,8 +140,8 @@ // As a default initial behavior, pop up an alert when webgl context is lost. To make your // application robust, you may want to override this behavior before shipping! // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2 - canvas.addEventListener("webglcontextlost", function(e) { Module.ccall('axmol_wgl_context_lost'); alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); - canvas.addEventListener("webglcontextrestored", function(e) { Module.ccall('axmol_wgl_context_restored'); e.preventDefault(); }, false); + canvas.addEventListener("webglcontextlost", function(e) { Module.ccall('axmol_webglcontextlost'); alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false); + canvas.addEventListener("webglcontextrestored", function(e) { Module.ccall('axmol_webglcontextrestored'); e.preventDefault(); }, false); document.addEventListener("visibilitychange", () => { Module.ccall('axmol_hdoc_visibilitychange', document.hidden); }); diff --git a/templates/common/proj.wasm/main.cpp b/templates/common/proj.wasm/main.cpp index 5256b0e10d13..0024f86c5ca0 100644 --- a/templates/common/proj.wasm/main.cpp +++ b/templates/common/proj.wasm/main.cpp @@ -33,20 +33,23 @@ using namespace ax; -int axmol_main() +namespace { - // create the application instance - AppDelegate app; - return Application::getInstance()->run(); +std::unique_ptr appDelegate; } -int main(int argc, char** argv) +void axmol_wasm_app_exit() { - auto result = axmol_main(); + appDelegate = nullptr; #if AX_OBJECT_LEAK_DETECTION Object::printLeaks(); #endif +} - return result; +int main(int argc, char** argv) +{ + // create the application instance + appDelegate.reset(new AppDelegate()); + return Application::getInstance()->run(); } diff --git a/tests/cpp-tests/Source/AppDelegate.cpp b/tests/cpp-tests/Source/AppDelegate.cpp index 09666d42b02f..c3e6089209a7 100644 --- a/tests/cpp-tests/Source/AppDelegate.cpp +++ b/tests/cpp-tests/Source/AppDelegate.cpp @@ -38,6 +38,8 @@ AppDelegate::AppDelegate() : _testController(nullptr) {} AppDelegate::~AppDelegate() { + + AXLOGI("AppDelegate::~AppDelegate"); // SimpleAudioEngine::end(); // TODO: minggo // cocostudio::ArmatureDataManager::destroyInstance(); diff --git a/tests/cpp-tests/proj.wasm/main.cpp b/tests/cpp-tests/proj.wasm/main.cpp index 03cc24bf1e8d..a274751783dd 100644 --- a/tests/cpp-tests/proj.wasm/main.cpp +++ b/tests/cpp-tests/proj.wasm/main.cpp @@ -32,9 +32,19 @@ using namespace ax; +namespace +{ +std::unique_ptr appDelegate; +} + +void axmol_wasm_app_exit() +{ + appDelegate = nullptr; +} + int main(int argc, char** argv) { // create the application instance - AppDelegate app; + appDelegate.reset(new AppDelegate()); return Application::getInstance()->run(); } diff --git a/tests/fairygui-tests/proj.wasm/main.cpp b/tests/fairygui-tests/proj.wasm/main.cpp index 03cc24bf1e8d..a274751783dd 100644 --- a/tests/fairygui-tests/proj.wasm/main.cpp +++ b/tests/fairygui-tests/proj.wasm/main.cpp @@ -32,9 +32,19 @@ using namespace ax; +namespace +{ +std::unique_ptr appDelegate; +} + +void axmol_wasm_app_exit() +{ + appDelegate = nullptr; +} + int main(int argc, char** argv) { // create the application instance - AppDelegate app; + appDelegate.reset(new AppDelegate()); return Application::getInstance()->run(); } diff --git a/tests/live2d-tests/proj.wasm/main.cpp b/tests/live2d-tests/proj.wasm/main.cpp index 03cc24bf1e8d..a274751783dd 100644 --- a/tests/live2d-tests/proj.wasm/main.cpp +++ b/tests/live2d-tests/proj.wasm/main.cpp @@ -32,9 +32,19 @@ using namespace ax; +namespace +{ +std::unique_ptr appDelegate; +} + +void axmol_wasm_app_exit() +{ + appDelegate = nullptr; +} + int main(int argc, char** argv) { // create the application instance - AppDelegate app; + appDelegate.reset(new AppDelegate()); return Application::getInstance()->run(); } diff --git a/tests/lua-tests/proj.wasm/main.cpp b/tests/lua-tests/proj.wasm/main.cpp index 03cc24bf1e8d..a274751783dd 100644 --- a/tests/lua-tests/proj.wasm/main.cpp +++ b/tests/lua-tests/proj.wasm/main.cpp @@ -32,9 +32,19 @@ using namespace ax; +namespace +{ +std::unique_ptr appDelegate; +} + +void axmol_wasm_app_exit() +{ + appDelegate = nullptr; +} + int main(int argc, char** argv) { // create the application instance - AppDelegate app; + appDelegate.reset(new AppDelegate()); return Application::getInstance()->run(); } diff --git a/tests/unit-tests/proj.wasm/main.cpp b/tests/unit-tests/proj.wasm/main.cpp index 03cc24bf1e8d..a274751783dd 100644 --- a/tests/unit-tests/proj.wasm/main.cpp +++ b/tests/unit-tests/proj.wasm/main.cpp @@ -32,9 +32,19 @@ using namespace ax; +namespace +{ +std::unique_ptr appDelegate; +} + +void axmol_wasm_app_exit() +{ + appDelegate = nullptr; +} + int main(int argc, char** argv) { // create the application instance - AppDelegate app; + appDelegate.reset(new AppDelegate()); return Application::getInstance()->run(); }