From 7725b36d8b74ed46dffe9c46898dda0ed6b1ea43 Mon Sep 17 00:00:00 2001 From: dryan Date: Mon, 28 Aug 2023 16:14:04 +0800 Subject: [PATCH 01/19] add webgl surport for axmol 1.x --- .github/workflows/gendocs.yml | 4 +- README_webgl.md | 22 + cmake/Modules/AXConfigDefine.cmake | 7 + cmake/Modules/AXPlatform.cmake | 6 + core/2d/FontFreeType.cpp | 7 + core/CMakeLists.txt | 4 +- core/_version.h | 10 + core/audio/AudioEngine.cpp | 11 + core/audio/AudioEngine.h | 2 + core/audio/AudioEngineImpl.cpp | 2 +- core/audio/AudioEngineImpl.h | 4 +- core/audio/alconfig.h | 28 +- core/axmol.h | 7 + core/base/Config.h | 10 +- core/base/Controller.cpp | 4 +- core/base/Controller.h | 2 +- core/base/Random.cpp | 3 + core/base/Random.h | 26 +- core/base/posix_io.h | 2 +- core/network/CMakeLists.txt | 50 +- core/network/Downloader-emscripten.cpp | 265 ++++ core/network/Downloader-emscripten.h | 66 + core/network/Downloader.cpp | 5 + core/network/HttpClient-emscripten.cpp | 359 +++++ core/network/HttpClient-emscripten.h | 208 +++ core/network/HttpCookie.cpp | 3 +- core/platform/Application.h | 2 + core/platform/ApplicationProtocol.h | 1 + core/platform/CMakeLists.txt | 20 + core/platform/GL.h | 2 + core/platform/PlatformConfig.h | 11 +- core/platform/PlatformDefine.h | 2 + core/platform/desktop/GLViewImpl-desktop.cpp | 14 +- core/platform/desktop/GLViewImpl-desktop.h | 2 +- .../emscripten/Application-emscripten.cpp | 202 +++ .../emscripten/Application-emscripten.h | 122 ++ .../platform/emscripten/Common-emscripten.cpp | 51 + .../platform/emscripten/Device-emscripten.cpp | 161 ++ .../emscripten/FileUtils-emscripten.cpp | 103 ++ .../emscripten/FileUtils-emscripten.h | 67 + core/platform/emscripten/GL-emscripten.h | 170 +++ .../emscripten/GLViewImpl-emscripten.cpp | 1326 +++++++++++++++++ .../emscripten/GLViewImpl-emscripten.h | 186 +++ .../emscripten/PlatformDefine-emscripten.h | 43 + core/platform/emscripten/StdC-emscripten.h | 49 + .../emscripten/devtools-emscripten.cpp | 70 + .../platform/emscripten/devtools-emscripten.h | 34 + core/renderer/CMakeLists.txt | 2 +- core/renderer/RenderConsts.h | 11 + core/ui/CMakeLists.txt | 4 + .../auto/axlua_audioengine_auto.cpp | 2 +- .../auto/axlua_audioengine_auto.hpp | 2 +- .../manual/cocostudio/CustomGUIReader.cpp | 2 +- templates/cpp-template-default/CMakeLists.txt | 31 + templates/cpp-template-default/index.html | 150 ++ .../proj.emscripten/main.cpp | 40 + templates/lua-template-default/CMakeLists.txt | 31 + templates/lua-template-default/index.html | 150 ++ .../proj.emscripten/main.cpp | 40 + tests/cpp-tests/CMakeLists.txt | 29 + tests/cpp-tests/Source/CurlTest/CurlTest.cpp | 4 + tests/cpp-tests/Source/CurlTest/CurlTest.h | 4 + .../Source/FileUtilsTest/FileUtilsTest.cpp | 6 +- .../NewAudioEngineTest/NewAudioEngineTest.cpp | 2 +- .../CocoStudioGUITest/CocosGUIScene.cpp | 2 +- tests/cpp-tests/Source/controller.cpp | 2 + tests/cpp-tests/index.html | 150 ++ tests/cpp-tests/proj.emscripten/main.cpp | 40 + tests/fairygui-tests/CMakeLists.txt | 29 + tests/fairygui-tests/index.html | 150 ++ tests/fairygui-tests/proj.emscripten/main.cpp | 40 + tests/live2d-tests/CMakeLists.txt | 5 + tests/live2d-tests/index.html | 150 ++ tests/live2d-tests/proj.emscripten/main.cpp | 40 + tests/lua-tests/CMakeLists.txt | 29 + tests/lua-tests/index.html | 150 ++ tests/lua-tests/proj.emscripten/main.cpp | 40 + thirdparty/CMakeLists.txt | 18 +- thirdparty/astcenc/CMakeLists.txt | 5 + thirdparty/openal/CMakeLists.txt | 2 +- thirdparty/openal/core/helpers.cpp | 2 +- .../emscripten/openssl/configuration.h | 161 ++ .../openssl/include/openssl/configuration.h | 2 + .../openssl/prebuilt/emscripten/libcrypto.a | Bin 0 -> 4883160 bytes .../openssl/prebuilt/emscripten/libssl.a | Bin 0 -> 795626 bytes thirdparty/unzip/CMakeLists.txt | 7 +- thirdparty/unzip/crypt.c | 5 +- thirdparty/yasio/bindings/yasio_axlua.cpp | 8 +- thirdparty/yasio/endian_portable.hpp | 10 + tools/console/bin/axmol.bat | 27 +- 90 files changed, 5260 insertions(+), 79 deletions(-) create mode 100644 README_webgl.md create mode 100644 core/_version.h create mode 100644 core/network/Downloader-emscripten.cpp create mode 100644 core/network/Downloader-emscripten.h create mode 100644 core/network/HttpClient-emscripten.cpp create mode 100644 core/network/HttpClient-emscripten.h create mode 100644 core/platform/emscripten/Application-emscripten.cpp create mode 100644 core/platform/emscripten/Application-emscripten.h create mode 100644 core/platform/emscripten/Common-emscripten.cpp create mode 100644 core/platform/emscripten/Device-emscripten.cpp create mode 100644 core/platform/emscripten/FileUtils-emscripten.cpp create mode 100644 core/platform/emscripten/FileUtils-emscripten.h create mode 100644 core/platform/emscripten/GL-emscripten.h create mode 100644 core/platform/emscripten/GLViewImpl-emscripten.cpp create mode 100644 core/platform/emscripten/GLViewImpl-emscripten.h create mode 100644 core/platform/emscripten/PlatformDefine-emscripten.h create mode 100644 core/platform/emscripten/StdC-emscripten.h create mode 100644 core/platform/emscripten/devtools-emscripten.cpp create mode 100644 core/platform/emscripten/devtools-emscripten.h create mode 100644 core/renderer/RenderConsts.h create mode 100644 templates/cpp-template-default/index.html create mode 100644 templates/cpp-template-default/proj.emscripten/main.cpp create mode 100644 templates/lua-template-default/index.html create mode 100644 templates/lua-template-default/proj.emscripten/main.cpp create mode 100644 tests/cpp-tests/index.html create mode 100644 tests/cpp-tests/proj.emscripten/main.cpp create mode 100644 tests/fairygui-tests/index.html create mode 100644 tests/fairygui-tests/proj.emscripten/main.cpp create mode 100644 tests/live2d-tests/index.html create mode 100644 tests/live2d-tests/proj.emscripten/main.cpp create mode 100644 tests/lua-tests/index.html create mode 100644 tests/lua-tests/proj.emscripten/main.cpp create mode 100644 thirdparty/openssl/include/emscripten/openssl/configuration.h create mode 100644 thirdparty/openssl/prebuilt/emscripten/libcrypto.a create mode 100644 thirdparty/openssl/prebuilt/emscripten/libssl.a diff --git a/.github/workflows/gendocs.yml b/.github/workflows/gendocs.yml index 2db838493aac..5f5c21663d92 100644 --- a/.github/workflows/gendocs.yml +++ b/.github/workflows/gendocs.yml @@ -5,7 +5,7 @@ name: gendocs on: push: branches: - - dev + - 1.x paths: - core/**/* - docs/**/* @@ -29,7 +29,7 @@ jobs: uses: peaceiris/actions-gh-pages@v3 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./docs/out/ + publish_dir: ./docs/html/ enable_jekyll: false allow_empty_commit: false force_orphan: true diff --git a/README_webgl.md b/README_webgl.md new file mode 100644 index 000000000000..c80b624cd221 --- /dev/null +++ b/README_webgl.md @@ -0,0 +1,22 @@ +this is a fork of axmol engine. +add webgl build surport. + +build step: +1. run emsdk_env.bat + +2. emcmake cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug .. + +3. ninja + + + + + + +todo: +1. +#ifndef EMSCRIPTEN + const FT_Int spread = DistanceMapSpread; + FT_Property_Set(_FTlibrary, "sdf", "spread", &spread); + FT_Property_Set(_FTlibrary, "bsdf", "spread", &spread); +#endif diff --git a/cmake/Modules/AXConfigDefine.cmake b/cmake/Modules/AXConfigDefine.cmake index 39fcd29fa54e..59871695ed48 100644 --- a/cmake/Modules/AXConfigDefine.cmake +++ b/cmake/Modules/AXConfigDefine.cmake @@ -147,6 +147,8 @@ function(use_ax_compile_define target) target_compile_definitions(${target} PUBLIC _GNU_SOURCE) elseif(ANDROID) target_compile_definitions(${target} PUBLIC USE_FILE32API) + elseif(EMSCRIPTEN) + ax_config_pred(${target} AX_USE_ANGLE) elseif(WINDOWS) ax_config_pred(${target} AX_USE_ANGLE) ax_config_pred(${target} AX_ENABLE_VLC_MEDIA) @@ -187,6 +189,11 @@ endfunction() # endif() # endif() +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS "-s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") + set(CMAKE_CXX_FLAGS "-s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") +endif() + # Try enable asm & nasm compiler support set(can_use_assembler TRUE) enable_language(ASM) diff --git a/cmake/Modules/AXPlatform.cmake b/cmake/Modules/AXPlatform.cmake index 1c7de327d5df..eabede60b35a 100644 --- a/cmake/Modules/AXPlatform.cmake +++ b/cmake/Modules/AXPlatform.cmake @@ -38,6 +38,9 @@ elseif(${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(LINUX TRUE) set(PLATFORM_FOLDER linux) endif() +elseif(${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") + set(EMSCRIPTEN TRUE) + set(PLATFORM_FOLDER emscripten) elseif(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set(APPLE TRUE) set(MACOSX TRUE) @@ -91,6 +94,9 @@ elseif(MACOSX) elseif(LINUX) set(platform_name linux) set(platform_spec_path linux) +elseif(EMSCRIPTEN) + set(platform_name emscripten) + set(platform_spec_path emscripten) endif() set(platform_spec_path "${_path_prefix}${platform_spec_path}") diff --git a/core/2d/FontFreeType.cpp b/core/2d/FontFreeType.cpp index 466808c5f155..ac6b31544f49 100644 --- a/core/2d/FontFreeType.cpp +++ b/core/2d/FontFreeType.cpp @@ -29,7 +29,9 @@ THE SOFTWARE. #include "2d/FontAtlas.h" #include "base/Director.h" #include "base/UTF8.h" +#ifndef EMSCRIPTEN #include "freetype/ftmodapi.h" +#endif #include "platform/FileUtils.h" #include "platform/FileStream.h" @@ -124,9 +126,11 @@ bool FontFreeType::initFreeType() if (FT_Init_FreeType(&_FTlibrary)) return false; +#ifndef EMSCRIPTEN const FT_Int spread = DistanceMapSpread; FT_Property_Set(_FTlibrary, "sdf", "spread", &spread); FT_Property_Set(_FTlibrary, "bsdf", "spread", &spread); +#endif _FTInitialized = true; } @@ -413,12 +417,15 @@ unsigned char* FontFreeType::getGlyphBitmap(char32_t charCode, #endif if (FT_Load_Glyph(_fontFace, glyphIndex, FT_LOAD_RENDER | FT_LOAD_NO_AUTOHINT)) break; + +#ifndef EMSCRIPTEN if (_distanceFieldEnabled && _fontFace->glyph->bitmap.buffer) { // Require freetype version > 2.11.0, because freetype 2.11.0 sdf has memory access bug, see: // https://gitlab.freedesktop.org/freetype/freetype/-/issues/1077 FT_Render_Glyph(_fontFace->glyph, FT_Render_Mode::FT_RENDER_MODE_SDF); } +#endif auto& metrics = _fontFace->glyph->metrics; outRect.origin.x = static_cast(metrics.horiBearingX >> 6); diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 6709a3da416f..6a53eba1e221 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -76,7 +76,7 @@ endif() message(STATUS "AX_ENABLE_VLC_MEDIA=${AX_ENABLE_VLC_MEDIA}") -if(NOT APPLE) +if(NOT APPLE AND (NOT EMSCRIPTEN)) set(AX_USE_ALSOFT ON CACHE BOOL "" FORCE) else() option(AX_USE_ALSOFT "Use ALSOFT on apple" OFF) @@ -111,7 +111,7 @@ else() set(AX_ENABLE_EXT_IMGUI OFF) endif() -if (ANDROID) +if (ANDROID OR EMSCRIPTEN) set(AX_USE_GLAD TRUE CACHE BOOL "Use glad for android GLESv3 support" FORCE) add_definitions(-DAX_USE_GLAD=1) endif() diff --git a/core/_version.h b/core/_version.h new file mode 100644 index 000000000000..6c75191aded5 --- /dev/null +++ b/core/_version.h @@ -0,0 +1,10 @@ +#pragma once + +/* Define to the library build number from git commit count */ +#define AX_BUILD_NUM "40673" + +/* Define the branch being built */ +#define AX_GIT_BRANCH "dev" + +/* Define the hash of the head commit */ +#define AX_GIT_COMMIT_HASH "73b56fa" diff --git a/core/audio/AudioEngine.cpp b/core/audio/AudioEngine.cpp index 4fe7d87f757f..724b9c180783 100644 --- a/core/audio/AudioEngine.cpp +++ b/core/audio/AudioEngine.cpp @@ -55,7 +55,10 @@ AudioEngine::ProfileHelper* AudioEngine::_defaultProfileHelper = nullptr; std::unordered_map AudioEngine::_audioIDInfoMap; AudioEngineImpl* AudioEngine::_audioEngineImpl = nullptr; +#ifndef __EMSCRIPTEN__ AudioEngine::AudioEngineThreadPool* AudioEngine::s_threadPool = nullptr; +#endif + bool AudioEngine::_isEnabled = true; AudioEngine::AudioInfo::AudioInfo() @@ -64,6 +67,7 @@ AudioEngine::AudioInfo::AudioInfo() AudioEngine::AudioInfo::~AudioInfo() {} +#ifndef __EMSCRIPTEN__ class AudioEngine::AudioEngineThreadPool { public: @@ -131,6 +135,7 @@ class AudioEngine::AudioEngineThreadPool std::condition_variable _taskCondition; bool _stop; }; +#endif void AudioEngine::end() { @@ -138,11 +143,13 @@ void AudioEngine::end() // fix #127 uncacheAll(); +#ifndef __EMSCRIPTEN__ if (s_threadPool) { delete s_threadPool; s_threadPool = nullptr; } +#endif delete _audioEngineImpl; _audioEngineImpl = nullptr; @@ -164,10 +171,12 @@ bool AudioEngine::lazyInit() } } +#ifndef __EMSCRIPTEN__ if (s_threadPool == nullptr) { s_threadPool = new AudioEngineThreadPool(); } +#endif return true; } @@ -587,10 +596,12 @@ void AudioEngine::addTask(const std::function& task) { lazyInit(); +#ifndef __EMSCRIPTEN__ if (_audioEngineImpl && s_threadPool) { s_threadPool->addTask(task); } +#endif } int AudioEngine::getPlayingAudioCount() diff --git a/core/audio/AudioEngine.h b/core/audio/AudioEngine.h index df602e07c36d..b14c18170f28 100644 --- a/core/audio/AudioEngine.h +++ b/core/audio/AudioEngine.h @@ -383,8 +383,10 @@ class AX_DLL AudioEngine static AudioEngineImpl* _audioEngineImpl; +#ifndef __EMSCRIPTEN__ class AudioEngineThreadPool; static AudioEngineThreadPool* s_threadPool; +#endif static bool _isEnabled; diff --git a/core/audio/AudioEngineImpl.cpp b/core/audio/AudioEngineImpl.cpp index 310c6ac06728..6473f6e1d4ce 100644 --- a/core/audio/AudioEngineImpl.cpp +++ b/core/audio/AudioEngineImpl.cpp @@ -389,7 +389,7 @@ bool AudioEngineImpl::init() for (int i = 0; i < MAX_AUDIOINSTANCES; ++i) { _unusedSourcesPool.push(_alSources[i]); -#if !AX_USE_ALSOFT +#if defined(__APPLE__) && !AX_USE_ALSOFT alSourceAddNotificationExt(_alSources[i], AL_BUFFERS_PROCESSED, myAlSourceNotificationCallback, nullptr); #endif diff --git a/core/audio/AudioEngineImpl.h b/core/audio/AudioEngineImpl.h index c249d3d5d30c..b69b4318f9a5 100644 --- a/core/audio/AudioEngineImpl.h +++ b/core/audio/AudioEngineImpl.h @@ -72,9 +72,9 @@ class AX_DLL AudioEngineImpl : public ax::Ref void _play2d(AudioCache* cache, AUDIO_ID audioID); void _unscheduleUpdate(); ALuint findValidSource(); -# if defined(__APPLE__) +#if defined(__APPLE__) && !AX_USE_ALSOFT static ALvoid myAlSourceNotificationCallback(ALuint sid, ALuint notificationID, ALvoid* userData); -# endif +#endif ALuint _alSources[MAX_AUDIOINSTANCES]; // available sources diff --git a/core/audio/alconfig.h b/core/audio/alconfig.h index efb20da49d78..285f779c930f 100644 --- a/core/audio/alconfig.h +++ b/core/audio/alconfig.h @@ -26,20 +26,26 @@ ****************************************************************************/ #pragma once -#if !defined(__APPLE__) +#if !defined(__APPLE__) && !defined(__EMSCRIPTEN__) # if !defined(AX_USE_ALSOFT) # define AX_USE_ALSOFT 1 # endif #endif -#if !AX_USE_ALSOFT -# import -# import -# define MAX_AUDIOINSTANCES 24 +#if defined(__EMSCRIPTEN__) + #import + #import + #define MAX_AUDIOINSTANCES 24 #else -# define AL_ALEXT_PROTOTYPES 1 -# include "AL/al.h" -# include "AL/alc.h" -# include "AL/alext.h" -# define MAX_AUDIOINSTANCES 32 -#endif + #if !AX_USE_ALSOFT + # import + # import + # define MAX_AUDIOINSTANCES 24 + #else + # define AL_ALEXT_PROTOTYPES 1 + # include "AL/al.h" + # include "AL/alc.h" + # include "AL/alext.h" + # define MAX_AUDIOINSTANCES 32 + #endif +#endif \ No newline at end of file diff --git a/core/axmol.h b/core/axmol.h index cf1950d3e9aa..1891b909f406 100644 --- a/core/axmol.h +++ b/core/axmol.h @@ -226,6 +226,13 @@ THE SOFTWARE. # include "platform/linux/StdC-linux.h" #endif // AX_TARGET_PLATFORM == AX_PLATFORM_LINUX +#if (AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) + #include "platform/emscripten/Application-emscripten.h" + #include "platform/emscripten/GLViewImpl-emscripten.h" + #include "platform/emscripten/GL-emscripten.h" + #include "platform/emscripten/StdC-emscripten.h" +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + // script_support #include "base/ScriptSupport.h" diff --git a/core/base/Config.h b/core/base/Config.h index 809c59170392..fde9c89ae329 100644 --- a/core/base/Config.h +++ b/core/base/Config.h @@ -272,7 +272,7 @@ THE SOFTWARE. #ifndef AX_USE_3D_PHYSICS # if (AX_TARGET_PLATFORM == AX_PLATFORM_IOS || AX_TARGET_PLATFORM == AX_PLATFORM_MAC || \ AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 || AX_TARGET_PLATFORM == AX_PLATFORM_WINRT || \ - AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX) + AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) # define AX_USE_3D_PHYSICS 1 # endif #endif @@ -311,9 +311,11 @@ THE SOFTWARE. /** Support webp or not. If your application don't use webp format picture, you can undefine this macro to save package * size. */ -#ifndef AX_USE_WEBP -# define AX_USE_WEBP 1 -#endif // AX_USE_WEBP +#if AX_TARGET_PLATFORM != AX_PLATFORM_EMSCRIPTEN + #ifndef AX_USE_WEBP + #define AX_USE_WEBP 1 + #endif // AX_USE_WEBP +#endif /** Enable Lua Script binding */ #ifndef AX_ENABLE_SCRIPT_BINDING diff --git a/core/base/Controller.cpp b/core/base/Controller.cpp index 2cbc67943907..825ddf76770c 100644 --- a/core/base/Controller.cpp +++ b/core/base/Controller.cpp @@ -28,7 +28,7 @@ #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || \ AX_TARGET_PLATFORM == AX_PLATFORM_MAC || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || \ - AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) + AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 || AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) # include "base/EventDispatcher.h" # include "base/EventController.h" @@ -128,4 +128,4 @@ void Controller::onAxisEvent(int axisCode, float value, bool isAnalog) NS_AX_END #endif // (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || AX_TARGET_PLATFORM == - // AX_PLATFORM_MAC || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) + // AX_PLATFORM_MAC || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 || AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) diff --git a/core/base/Controller.h b/core/base/Controller.h index 15d6eb2c1e4d..e4e4302efe10 100644 --- a/core/base/Controller.h +++ b/core/base/Controller.h @@ -27,7 +27,7 @@ #define __cocos2d_libs__CCController__ #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || \ AX_TARGET_PLATFORM == AX_PLATFORM_MAC || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || \ - defined(_WIN32)) + AX_TARGET_PLATFORM == AX_PLATFORM_WIN32 || AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) # include "platform/PlatformMacros.h" # include diff --git a/core/base/Random.cpp b/core/base/Random.cpp index 350beac96170..c4b0f2811a17 100644 --- a/core/base/Random.cpp +++ b/core/base/Random.cpp @@ -26,9 +26,12 @@ THE SOFTWARE. #include "base/Random.h" +#ifdef EMSCRIPTEN +#else std::mt19937& ax::RandomHelper::getEngine() { static std::random_device seed_gen; static std::mt19937 engine(seed_gen()); return engine; } +#endif diff --git a/core/base/Random.h b/core/base/Random.h index c5a0bcf59575..2688ab592f40 100644 --- a/core/base/Random.h +++ b/core/base/Random.h @@ -30,7 +30,7 @@ THE SOFTWARE. #include #include -#include "platform/PlatformMacros.h" +#include "platform/PlatformConfig.h" /** * @addtogroup base @@ -38,10 +38,33 @@ THE SOFTWARE. */ NS_AX_BEGIN + + /** * @class RandomHelper * @brief A helper class for creating random number. */ +#ifdef EMSCRIPTEN +#include + +class AX_DLL RandomHelper +{ +public: + template + static T random_real(T min, T max) + { + T randomValue = static_cast(emscripten_random()) / static_cast(0xFFFFFFFF); + return min + randomValue * (max - min); + } + + template + static T random_int(T min, T max) + { + T randomValue = static_cast(emscripten_random()) % (max - min + 1); + return min + randomValue; + } +}; +#else class AX_DLL RandomHelper { public: @@ -64,6 +87,7 @@ class AX_DLL RandomHelper private: static std::mt19937& getEngine(); }; +#endif /** * Returns a random value between `min` and `max`. diff --git a/core/base/posix_io.h b/core/base/posix_io.h index c7d2bf0da812..3e988d1f2e78 100644 --- a/core/base/posix_io.h +++ b/core/base/posix_io.h @@ -42,7 +42,7 @@ extern "C" int _ftruncate(int fd, int64_t size); # define posix_write ::write # define posix_fd2fh(fd) (fd) # define posix_ftruncate ::ftruncate -# if defined(__APPLE__) +# if defined(__APPLE__) || defined(__EMSCRIPTEN__) # define posix_lseek64 ::lseek # define posix_ftruncate64 ::ftruncate # else diff --git a/core/network/CMakeLists.txt b/core/network/CMakeLists.txt index cdd7792965cd..933fb8f3fa40 100644 --- a/core/network/CMakeLists.txt +++ b/core/network/CMakeLists.txt @@ -1,18 +1,38 @@ -set(_AX_NETWORK_HEADER - network/Downloader-curl.h - network/IDownloaderImpl.h - network/Downloader.h - network/Uri.h - network/HttpClient.h - network/HttpResponse.h - network/HttpRequest.h - network/HttpCookie.h +if(EMSCRIPTEN) + set(_AX_NETWORK_HEADER + network/Downloader-emscripten.h + network/IDownloaderImpl.h + network/Downloader.h + network/Uri.h + network/HttpClient.h + network/HttpResponse.h + network/HttpRequest.h ) -set(_AX_NETWORK_SRC - network/HttpClient.cpp - network/Downloader.cpp - network/Downloader-curl.cpp - network/HttpCookie.cpp - network/Uri.cpp + set(_AX_NETWORK_SRC + network/HttpClient.cpp + network/Downloader.cpp + network/Downloader-emscripten.cpp + network/HttpCookie.cpp + network/Uri.cpp ) +else() + set(_AX_NETWORK_HEADER + network/Downloader-curl.h + network/IDownloaderImpl.h + network/Downloader.h + network/Uri.h + network/HttpClient.h + network/HttpResponse.h + network/HttpRequest.h + network/HttpCookie.h + ) + + set(_AX_NETWORK_SRC + network/HttpClient.cpp + network/Downloader.cpp + network/Downloader-curl.cpp + network/HttpCookie.cpp + network/Uri.cpp + ) +endif() diff --git a/core/network/Downloader-emscripten.cpp b/core/network/Downloader-emscripten.cpp new file mode 100644 index 000000000000..7cbb69938b1f --- /dev/null +++ b/core/network/Downloader-emscripten.cpp @@ -0,0 +1,265 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + + +#include "platform/FileUtils.h" +#include "network/Downloader-emscripten.h" +#include + +using namespace std; + +namespace ax { namespace network { + + static int sDownloaderCounter; + + struct DownloadTaskEmscripten : public IDownloadTask + { + DownloadTaskEmscripten(unsigned int id) + :id(id) + ,bytesReceived(0) + ,fetch(NULL) + { + DLLOG("Construct DownloadTaskEmscripten: %p", this); + } + virtual ~DownloadTaskEmscripten() + { + DLLOG("Destruct DownloadTaskEmscripten: %p", this); + } + + int bytesReceived; + unsigned int id; + emscripten_fetch_t * fetch; + shared_ptr task; // reference to DownloadTask, when task finish, release + }; + + DownloaderEmscripten::DownloaderEmscripten(const DownloaderHints& hints) + : _id(++sDownloaderCounter) + , hints(hints) + { + DLLOG("Construct DownloaderEmscripten: %p", this); + } + + DownloaderEmscripten::~DownloaderEmscripten() + { + DLLOG("Destruct DownloaderEmscripten: %p", this); + for (auto iter = _taskMap.begin(); iter != _taskMap.end(); ++iter) + { + if(iter->second->fetch != NULL) { + emscripten_fetch_close(iter->second->fetch); + } + } + } + + void DownloaderEmscripten::startTask(std::shared_ptr& task) + { + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + strcpy(attr.requestMethod, "GET"); + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY | EMSCRIPTEN_FETCH_PERSIST_FILE; + if(task->storagePath.length() == 0) { + attr.onsuccess = DownloaderEmscripten::onDataLoad; + }else{ + attr.onsuccess = DownloaderEmscripten::onLoad; + } + attr.onprogress = DownloaderEmscripten::onProgress; + attr.onerror = DownloaderEmscripten::onError; + attr.timeoutMSecs = this->hints.timeoutInSeconds * 1000; + emscripten_fetch_t *fetch = emscripten_fetch(&attr, task->requestURL.c_str()); + fetch->userData = this; + + DownloadTaskEmscripten *coTask = new DownloadTaskEmscripten(fetch->id); + coTask->task = task; + + DLLOG("DownloaderEmscripten::createCoTask id: %d", coTask->id); + _taskMap.insert(make_pair(coTask->id, coTask)); + } + + void DownloaderEmscripten::onDataLoad(emscripten_fetch_t *fetch) + { + unsigned int taskId = fetch->id; + uint64_t size = fetch->numBytes; + DLLOG("DownloaderEmscripten::onDataLoad(taskId: %d, size: %d)", taskId, size); + DownloaderEmscripten* downloader = reinterpret_cast(fetch->userData); + auto iter = downloader->_taskMap.find(taskId); + if (downloader->_taskMap.end() == iter) + { + DLLOG("DownloaderEmscripten::onDataLoad can't find task with id: %i, size: %i", taskId, size); + return; + } + DownloadTaskEmscripten *coTask = iter->second; + std::vector buf((unsigned char*)fetch->data, (unsigned char*)fetch->data + size); + emscripten_fetch_close(fetch); + coTask->fetch = fetch = NULL; + + downloader->_taskMap.erase(iter); + downloader->onTaskFinish(*coTask->task, + DownloadTask::ERROR_NO_ERROR, + 0, + "", + buf + ); + + coTask->task.reset(); + } + + void DownloaderEmscripten::onLoad(emscripten_fetch_t *fetch) + { + unsigned int taskId = fetch->id; + uint64_t size = fetch->numBytes; + DLLOG("DownloaderEmscripten::onLoad(taskId: %i, size: %i)", taskId, size); + DownloaderEmscripten* downloader = reinterpret_cast(fetch->userData); + auto iter = downloader->_taskMap.find(taskId); + if (downloader->_taskMap.end() == iter) + { + DLLOG("DownloaderEmscripten::onLoad can't find task with id: %i, size: %i", taskId, size); + return; + } + DownloadTaskEmscripten *coTask = iter->second; + vector buf; + downloader->_taskMap.erase(iter); + + string storagePath = coTask->task->storagePath; + int errCode = DownloadTask::ERROR_NO_ERROR; + int errCodeInternal = 0; + string errDescription; + do + { + auto util = FileUtils::getInstance(); + if (util->isFileExist(storagePath)) + { + if (false == util->removeFile(storagePath)) + { + errCode = DownloadTask::ERROR_REMOVE_FILE_FAILED; + errCodeInternal = 0; + errDescription = "Can't remove old file: "; + errDescription.append(storagePath); + break; + } + } + + string dir; + size_t found = storagePath.find_last_of("/\\"); + if (found == string::npos) + { + errCode = DownloadTask::ERROR_INVALID_PARAMS; + errCodeInternal = 0; + errDescription = "Can't find dirname in storagePath."; + break; + } + + // ensure directory is exist + dir = storagePath.substr(0, found + 1); + if (false == util->isDirectoryExist(dir)) + { + if (false == util->createDirectory(dir)) + { + errCode = DownloadTask::ERROR_CREATE_DIR_FAILED; + errCodeInternal = 0; + errDescription = "Can't create dir:"; + errDescription.append(dir); + break; + } + } + + // open file + auto _fs = util->openFileStream(storagePath, FileStream::Mode::READ); + if (!_fs) + { + errCode = DownloadTask::ERROR_OPEN_FILE_FAILED; + errCodeInternal = 0; + errDescription = "Can't open file:"; + errDescription.append(storagePath); + break; + } + + _fs->write(fetch->data, static_cast(size)); + + } while (0); + emscripten_fetch_close(fetch); + coTask->fetch = fetch = NULL; + + downloader->onTaskFinish(*coTask->task, + errCode, + errCodeInternal, + errDescription, + buf + ); + coTask->task.reset(); + } + + void DownloaderEmscripten::onProgress(emscripten_fetch_t *fetch) + { + uint64_t dlTotal = fetch->totalBytes; + uint64_t dlNow = fetch->dataOffset; + unsigned int taskId = fetch->id; + DLLOG("DownloaderEmscripten::onProgress(taskId: %i, dlnow: %d, dltotal: %d)", taskId, dlNow, dlTotal); + DownloaderEmscripten* downloader = reinterpret_cast(fetch->userData); + auto iter = downloader->_taskMap.find(taskId); + if (downloader->_taskMap.end() == iter) + { + DLLOG("DownloaderEmscripten::onProgress can't find task with id: %i", taskId); + return; + } + + if (dlTotal == 0) { + DLLOG("DownloaderEmscripten::onProgress dlTotal unknown, usually caused by unknown content-length header %i", taskId); + return; + } + + DownloadTaskEmscripten *coTask = iter->second; + function transferDataToBuffer; // just a placeholder + int dl = dlNow - coTask->bytesReceived; + coTask->bytesReceived = dlNow; + downloader->onTaskProgress(*coTask->task, transferDataToBuffer); + } + + void DownloaderEmscripten::onError(emscripten_fetch_t *fetch) + { + unsigned int taskId = fetch->id; + DLLOG("DownloaderEmscripten::onLoad(taskId: %i)", taskId); + DownloaderEmscripten* downloader = reinterpret_cast(fetch->userData); + auto iter = downloader->_taskMap.find(taskId); + if (downloader->_taskMap.end() == iter) + { + emscripten_fetch_close(fetch); + DLLOG("DownloaderEmscripten::onLoad can't find task with id: %i", taskId); + return; + } + DownloadTaskEmscripten *coTask = iter->second; + vector buf; + downloader->_taskMap.erase(iter); + downloader->onTaskFinish(*coTask->task, + DownloadTask::ERROR_IMPL_INTERNAL, + fetch->status, + fetch->statusText, + buf + ); + + emscripten_fetch_close(fetch); + coTask->fetch = fetch = NULL; + coTask->task.reset(); + } + } +} // namespace cocos2d::network diff --git a/core/network/Downloader-emscripten.h b/core/network/Downloader-emscripten.h new file mode 100644 index 000000000000..2388b4e14911 --- /dev/null +++ b/core/network/Downloader-emscripten.h @@ -0,0 +1,66 @@ +/**************************************************************************** + Copyright (c) 2015-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#pragma once + +#include "network/IDownloaderImpl.h" +#include "network/Downloader.h" +#include + +namespace ax { + class Scheduler; +} + +namespace ax { namespace network +{ + class DownloadTaskEmscripten; + + class DownloaderEmscripten : public IDownloaderImpl + { + public: + DownloaderEmscripten(const DownloaderHints& hints); + virtual ~DownloaderEmscripten(); + + // virtual IDownloadTask *createCoTask(std::shared_ptr& task) override; + virtual void startTask(std::shared_ptr& task) override; + + protected: + int _id; + + DownloaderHints hints; + + std::unordered_map _taskMap; + + static void onError(emscripten_fetch_t *fetch); + + static void onProgress(emscripten_fetch_t *fetch); + + static void onDataLoad(emscripten_fetch_t *fetch); + + static void onLoad(emscripten_fetch_t *fetch); + }; + +}} // namespace cocos2d::network + diff --git a/core/network/Downloader.cpp b/core/network/Downloader.cpp index 650513a1db64..c2103107bf49 100644 --- a/core/network/Downloader.cpp +++ b/core/network/Downloader.cpp @@ -26,8 +26,13 @@ #include "network/Downloader.h" +#if EMSCRIPTEN +#include "network/Downloader-emscripten.h" +#define DownloaderImpl DownloaderEmscripten +#else #include "network/Downloader-curl.h" #define DownloaderImpl DownloaderCURL +#endif NS_AX_BEGIN diff --git a/core/network/HttpClient-emscripten.cpp b/core/network/HttpClient-emscripten.cpp new file mode 100644 index 000000000000..f99e999a2f89 --- /dev/null +++ b/core/network/HttpClient-emscripten.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** + Copyright (c) 2012 greathqy + Copyright (c) 2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "network/HttpClient-emscripten.h" +#include +#include "base/Director.h" +#include "platform/FileUtils.h" + +#if EMSCRIPTEN +#include +#include +#endif + +NS_AX_BEGIN + +namespace network +{ + + struct fetchUserData + { + bool isAlone; + HttpResponse *response; + }; + + static HttpClient *_httpClient = nullptr; // pointer to singleton + + // HttpClient implementation + HttpClient *HttpClient::getInstance() + { + if (_httpClient == nullptr) + { + _httpClient = new (std::nothrow) HttpClient(); + } + + return _httpClient; + } + + HttpClient::HttpClient() + : _timeoutForConnect(30) + , _timeoutForRead(60) + , _threadCount(0) + , _cookie(nullptr) + , _clearRequestPredicate(nullptr) + , _clearResponsePredicate(nullptr) + { + AXLOG("In the constructor of HttpClient!"); + increaseThreadCount(); + } + + HttpClient::~HttpClient() + { + AXLOG("HttpClient destructor"); + } + + void HttpClient::destroyInstance() + { + if (nullptr == _httpClient) + { + AXLOG("HttpClient singleton is nullptr"); + } + + auto thiz = _httpClient; + _httpClient = nullptr; + + Vector requestQueue = thiz->_requestQueue; + for (auto it = requestQueue.begin(); it != requestQueue.end();) + { + (*it)->release(); + it = requestQueue.erase(it); + } + + thiz->decreaseThreadCountAndMayDeleteThis(); + } + + void HttpClient::enableCookies(const char *cookieFile) + { + if (cookieFile) + { + _cookieFilename = std::string(cookieFile); + } + else + { + _cookieFilename = (FileUtils::getInstance()->getWritablePath() + "cookieFile.txt"); + } + } + + void HttpClient::setSSLVerification(const std::string &caFile) + { + AXLOG("HttpClient::setSSLVerification not supported on Emscripten"); + _sslCaFilename = caFile; + } + + //Add a get task to queue + void HttpClient::send(HttpRequest *request) + { + if (!request) + { + return; + } + + request->retain(); + + if (_threadCount <= 1) + { + increaseThreadCount(); + HttpResponse *response = new (std::nothrow) HttpResponse(request); + processResponse(response, false); + } + else + { + _requestQueue.pushBack(request); + } + } + + void HttpClient::sendImmediate(HttpRequest *request) + { + if (!request) + { + return; + } + + request->retain(); + HttpResponse *response = new (std::nothrow) HttpResponse(request); + processResponse(response, true); + } + + // Process Response + void HttpClient::processResponse(HttpResponse *response, bool isAlone) + { + // copy cookie back to document.cookie in case it is changed + std::string_view cookieFilename = HttpClient::getInstance()->getCookieFilename(); + if (!cookieFilename.empty()) + { + EM_ASM_ARGS({ + document.cookie = FS.readFile(UTF8ToString($0)); + }, cookieFilename.data()); + } + + auto request = response->getHttpRequest(); + emscripten_fetch_attr_t attr; + emscripten_fetch_attr_init(&attr); + + // set http method + switch (request->getRequestType()) + { + case HttpRequest::Type::GET: + strcpy(attr.requestMethod, "GET"); + break; + + case HttpRequest::Type::POST: + strcpy(attr.requestMethod, "POST"); + break; + + case HttpRequest::Type::PUT: + strcpy(attr.requestMethod, "PUT"); + break; + + case HttpRequest::Type::DELETE: + strcpy(attr.requestMethod, "DELETE"); + break; + + default: + AXASSERT(false, "CCHttpClient: unknown request type, only GET, POST, PUT or DELETE is supported"); + break; + } + + attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY; + + auto userData = new (std::nothrow) fetchUserData(); + userData->isAlone = isAlone; + userData->response = response; + + // set header + std::vector headers; + for (auto &header : request->getHeaders()) + { + size_t pos = header.find(":"); + if (pos != std::string::npos) + { + std::string key = header.substr(0, pos); + std::string value = header.substr(pos + 1); + headers.push_back(key); + headers.push_back(value); + } + } + + std::vector headersCharptr; + headersCharptr.reserve(headers.size() + 1); + for (auto &header : headers) + { + headersCharptr.push_back(header.c_str()); + } + headersCharptr.push_back(0); + attr.requestHeaders = &headersCharptr[0]; + + // post data + if (request->getRequestDataSize()) + { + attr.requestData = request->getRequestData(); + attr.requestDataSize = request->getRequestDataSize(); + } + + attr.onsuccess = onRequestComplete; + attr.onerror = onRequestComplete; + attr.timeoutMSecs = (HttpClient::getInstance()->getTimeoutForConnect() + HttpClient::getInstance()->getTimeoutForRead()) * 1000; + std::string_view url = response->getHttpRequest()->getUrl(); + emscripten_fetch_t *fetch = emscripten_fetch(&attr, url.data()); + fetch->userData = userData; + } + + void HttpClient::onRequestComplete(emscripten_fetch_t *fetch) + { + fetchUserData *userData = reinterpret_cast(fetch->userData); + HttpResponse *response = userData->response; + HttpRequest *request = response->getHttpRequest(); + + // get response + response->setResponseCode(fetch->status); + response->setErrorBuffer(fetch->statusText); + response->getResponseData()->assign((char *)fetch->data, (char *)fetch->data + fetch->numBytes); + emscripten_fetch_close(fetch); + + // write cookie back + std::string cookieFilename = HttpClient::getInstance()->getCookieFilename(); + if (!cookieFilename.empty()) + { + EM_ASM_ARGS({ + FS.writeFile(UTF8ToString($0), document.cookie); + }, cookieFilename.c_str()); + } + + if (_httpClient) + { + // call back + const ccHttpRequestCallback &callback = request->getCallback(); + Ref *pTarget = request->getTarget(); + SEL_HttpResponse pSelector = request->getSelector(); + + if (callback != nullptr) + { + callback(HttpClient::getInstance(), response); + } + else if (pTarget && pSelector) + { + (pTarget->*pSelector)(HttpClient::getInstance(), response); + } + + // call next request + if (!userData->isAlone) + { + Vector requestQueue = _httpClient->_requestQueue; + if (!requestQueue.empty()) + { + HttpRequest *request = requestQueue.at(0); + requestQueue.erase(0); + + HttpResponse *response = new (std::nothrow) HttpResponse(request); + _httpClient->processResponse(response, false); + } + else + { + _httpClient->decreaseThreadCountAndMayDeleteThis(); + } + } + } + + response->release(); + request->release(); + delete userData; + } + + void HttpClient::clearResponseAndRequestQueue() + { + for (auto it = _requestQueue.begin(); it != _requestQueue.end();) + { + if (!_clearRequestPredicate || + _clearRequestPredicate((*it))) + { + (*it)->release(); + it = _requestQueue.erase(it); + } + else + { + it++; + } + } + } + + void HttpClient::increaseThreadCount() + { + ++_threadCount; + } + + void HttpClient::decreaseThreadCountAndMayDeleteThis() + { + --_threadCount; + if (0 == _threadCount) + { + delete this; + } + } + + void HttpClient::setTimeoutForConnect(int value) + { + _timeoutForConnect = value; + } + + int HttpClient::getTimeoutForConnect() + { + return _timeoutForConnect; + } + + void HttpClient::setTimeoutForRead(int value) + { + _timeoutForRead = value; + } + + int HttpClient::getTimeoutForRead() + { + return _timeoutForRead; + } + + const std::string &HttpClient::getCookieFilename() + { + return _cookieFilename; + } + + const std::string &HttpClient::getSSLVerification() + { + return _sslCaFilename; + } + +} // namespace network + +NS_AX_END diff --git a/core/network/HttpClient-emscripten.h b/core/network/HttpClient-emscripten.h new file mode 100644 index 000000000000..beb9402da93d --- /dev/null +++ b/core/network/HttpClient-emscripten.h @@ -0,0 +1,208 @@ +/**************************************************************************** + Copyright (c) 2012 greathqy + Copyright (c) 2012 cocos2d-x.org + Copyright (c) 2013-2016 Chukong Technologies Inc. + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#pragma once + +#include "base/Vector.h" +#include "network/HttpRequest.h" +#include "network/HttpResponse.h" +#include "network/HttpCookie.h" + +struct emscripten_fetch_t; + +/** + * @addtogroup network + * @{ + */ + +NS_AX_BEGIN + +namespace network { + + + +/** Singleton that handles asynchronous http requests. + * + * Once the request completed, a callback will issued in main thread when it provided during make request. + * + * @lua NA + */ +class AX_DLL HttpClient +{ +public: + /** + * The buffer size of _responseMessage + */ + static const int RESPONSE_BUFFER_SIZE = 256; + + /** + * Get instance of HttpClient. + * + * @return the instance of HttpClient. + */ + static HttpClient *getInstance(); + + /** + * Release the instance of HttpClient. + */ + static void destroyInstance(); + + /** + * Enable cookie support. + * + * @param cookieFile the filepath of cookie file. + */ + void enableCookies(const char* cookieFile); + + /** + * Get the cookie filename + * + * @return the cookie filename + */ + const std::string& getCookieFilename(); + + /** + * Set root certificate path for SSL verification. + * + * @param caFile a full path of root certificate.if it is empty, SSL verification is disabled. + */ + void setSSLVerification(const std::string& caFile); + + /** + * Get the ssl CA filename + * + * @return the ssl CA filename + */ + const std::string& getSSLVerification(); + + /** + * Add a get request to task queue + * + * @param request a HttpRequest object, which includes url, response callback etc. + please make sure request->_requestData is clear before calling "send" here. + */ + void send(HttpRequest* request); + + /** + * Immediate send a request + * + * @param request a HttpRequest object, which includes url, response callback etc. + please make sure request->_requestData is clear before calling "sendImmediate" here. + */ + void sendImmediate(HttpRequest* request); + + /** + * Set the timeout value for connecting. + * + * @param value the timeout value for connecting. + */ + void setTimeoutForConnect(int value); + + /** + * Get the timeout value for connecting. + * + * @return int the timeout value for connecting. + */ + int getTimeoutForConnect(); + + /** + * Set the timeout value for reading. + * + * @param value the timeout value for reading. + */ + void setTimeoutForRead(int value); + + /** + * Get the timeout value for reading. + * + * @return int the timeout value for reading. + */ + int getTimeoutForRead(); + + HttpCookie* getCookie() const {return _cookie; } + + typedef std::function ClearRequestPredicate; + typedef std::function ClearResponsePredicate; + + /** + * Clears the pending http responses and http requests + * If defined, the method uses the ClearRequestPredicate and ClearResponsePredicate + * to check for each request/response which to delete + */ + void clearResponseAndRequestQueue(); + + /** + * Sets a predicate function that is going to be called to determine if we proceed + * each of the pending requests + * + * @param predicate function that will be called + */ + void setClearRequestPredicate(ClearRequestPredicate predicate) { _clearRequestPredicate = predicate; } + + /** + Sets a predicate function that is going to be called to determine if we proceed + * each of the pending requests + * + * @param cb predicate function that will be called + */ + void setClearResponsePredicate(ClearResponsePredicate predicate) { _clearResponsePredicate = predicate; } + + +private: + HttpClient(); + virtual ~HttpClient(); + + void processResponse(HttpResponse* response, bool isAlone); + static void onRequestComplete(emscripten_fetch_t *fetch); + void increaseThreadCount(); + void decreaseThreadCountAndMayDeleteThis(); + +private: + int _timeoutForConnect; + + int _timeoutForRead; + + int _threadCount; + + Vector _requestQueue; + + std::string _cookieFilename; + + std::string _sslCaFilename; + + HttpCookie* _cookie; + + ClearRequestPredicate _clearRequestPredicate; + ClearResponsePredicate _clearResponsePredicate; +}; + +} // namespace network + +NS_AX_END + +// end group +/// @} \ No newline at end of file diff --git a/core/network/HttpCookie.cpp b/core/network/HttpCookie.cpp index 40385f076699..31ad73737368 100644 --- a/core/network/HttpCookie.cpp +++ b/core/network/HttpCookie.cpp @@ -257,8 +257,7 @@ bool HttpCookie::updateOrAddCookie(std::string_view cookie, const Uri& uri) void HttpCookie::writeFile() { - FILE* out; - out = fopen(_cookieFileName.c_str(), "wb"); + auto out = fopen(_cookieFileName.c_str(), "wb"); fputs( "# Netscape HTTP Cookie File\n" "# http://curl.haxx.se/docs/http-cookies.html\n" diff --git a/core/platform/Application.h b/core/platform/Application.h index 1bcb73a1444e..aa1cb1623a1f 100644 --- a/core/platform/Application.h +++ b/core/platform/Application.h @@ -42,6 +42,8 @@ THE SOFTWARE. # include "platform/winrt/Application-winrt.h" #elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX # include "platform/linux/Application-linux.h" +#elif AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN +# include "platform/emscripten/Application-emscripten.h" #endif /// @endcond diff --git a/core/platform/ApplicationProtocol.h b/core/platform/ApplicationProtocol.h index ad15d001ab2e..a7c12f5d4419 100644 --- a/core/platform/ApplicationProtocol.h +++ b/core/platform/ApplicationProtocol.h @@ -52,6 +52,7 @@ class AX_DLL ApplicationProtocol Linux, /**< Linux */ macOS, /**< macOS */ Android, /**< Android */ + Emscripten, /**< Emscripten */ iOS, /**< Apple iOS */ }; diff --git a/core/platform/CMakeLists.txt b/core/platform/CMakeLists.txt index 1e78aadd289a..b3c8a09b818f 100644 --- a/core/platform/CMakeLists.txt +++ b/core/platform/CMakeLists.txt @@ -145,6 +145,26 @@ elseif(LINUX) platform/linux/Device-linux.cpp platform/desktop/GLViewImpl-desktop.cpp ) +elseif(EMSCRIPTEN) + include_directories( + PUBLIC "${_AX_ROOT}/thirdparty/angle/include" + PUBLIC "${_AX_ROOT}/core" + ) + set(_AX_PLATFORM_SPECIFIC_HEADER + platform/emscripten/Application-emscripten.h + platform/emscripten/GL-emscripten.h + platform/emscripten/StdC-emscripten.h + platform/emscripten/FileUtils-emscripten.h + platform/emscripten/PlatformDefine-emscripten.h + platform/emscripten/GLViewImpl-emscripten.h + ) + set(_AX_PLATFORM_SPECIFIC_SRC + platform/emscripten/FileUtils-emscripten.cpp + platform/emscripten/Common-emscripten.cpp + platform/emscripten/Application-emscripten.cpp + platform/emscripten/Device-emscripten.cpp + platform/emscripten/GLViewImpl-emscripten.cpp + ) endif() set(_AX_PLATFORM_HEADER diff --git a/core/platform/GL.h b/core/platform/GL.h index 6b4087a6415d..1ba6c9d0367f 100644 --- a/core/platform/GL.h +++ b/core/platform/GL.h @@ -39,6 +39,8 @@ THE SOFTWARE. # include "platform/winrt/GL-winrt.h" #elif AX_TARGET_PLATFORM == AX_PLATFORM_LINUX # include "platform/linux/GL-linux.h" +#elif AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN +# include "platform/emscripten/GL-emscripten.h" #elif AX_TARGET_PLATFORM == AX_PLATFORM_IOS # if AX_USE_ANGLE # include "platform/ios/GL-ios.h" diff --git a/core/platform/PlatformConfig.h b/core/platform/PlatformConfig.h index 858ec2de8a23..aa9b007c54ac 100644 --- a/core/platform/PlatformConfig.h +++ b/core/platform/PlatformConfig.h @@ -50,6 +50,7 @@ THE SOFTWARE. #define AX_PLATFORM_WIN32 3 #define AX_PLATFORM_LINUX 5 #define AX_PLATFORM_MAC 8 +#define AX_PLATFORM_EMSCRIPTEN 10 #define AX_PLATFORM_WINRT 13 // Determine target platform by compile environment macro. @@ -87,6 +88,11 @@ THE SOFTWARE. # define AX_TARGET_PLATFORM AX_PLATFORM_LINUX #endif +#if defined(EMSCRIPTEN) + #undef AX_TARGET_PLATFORM + #define AX_TARGET_PLATFORM AX_PLATFORM_EMSCRIPTEN +#endif + // android, override linux #if defined(__ANDROID__) || defined(ANDROID) # undef AX_TARGET_PLATFORM @@ -120,7 +126,7 @@ Linux: Desktop GL/Vulkan # define AX_USE_ANGLE 0 #endif -#if ((AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) || (AX_TARGET_PLATFORM == AX_PLATFORM_IOS) || (AX_TARGET_PLATFORM == AX_PLATFORM_WINRT)) +#if ((AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) || (AX_TARGET_PLATFORM == AX_PLATFORM_IOS) || (AX_TARGET_PLATFORM == AX_PLATFORM_WINRT) || (AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN)) # define AX_PLATFORM_MOBILE #else # define AX_PLATFORM_PC @@ -153,6 +159,9 @@ Linux: Desktop GL/Vulkan # define AX_USE_ANGLE 1 # endif # define AX_USE_GLES +#elif (AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) + #define AX_USE_GL + #define AX_USE_GLES #else # define AX_USE_GL #endif diff --git a/core/platform/PlatformDefine.h b/core/platform/PlatformDefine.h index 3d6b23f1ccb6..a1bbb83a6d62 100644 --- a/core/platform/PlatformDefine.h +++ b/core/platform/PlatformDefine.h @@ -41,4 +41,6 @@ THE SOFTWARE. # include "platform/linux/PlatformDefine-linux.h" #elif AX_TARGET_PLATFORM == AX_PLATFORM_WINRT # include "platform/winrt/PlatformDefine-winrt.h" +#elif AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN +# include "platform/emscripten/PlatformDefine-emscripten.h" #endif diff --git a/core/platform/desktop/GLViewImpl-desktop.cpp b/core/platform/desktop/GLViewImpl-desktop.cpp index c01a3ed4564b..26b79cc1db1e 100644 --- a/core/platform/desktop/GLViewImpl-desktop.cpp +++ b/core/platform/desktop/GLViewImpl-desktop.cpp @@ -74,7 +74,11 @@ THE SOFTWARE. # endif #endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) -#include "glfw3native.h" +#if (AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) + +#else + #include +#endif #if defined(_WIN32) # include "glfw3ext.h" @@ -553,11 +557,12 @@ bool GLViewImpl::initWithRect(std::string_view viewName, const ax::Rect& rect, f #endif #if defined(AX_USE_GL) - glfwSwapInterval(_glContextAttrs.vsync ? 1 : 0); - // check OpenGL version at first const GLubyte* glVersion = glGetString(GL_VERSION); +#ifndef EMSCRIPTEN + glfwSwapInterval(_glContextAttrs.vsync ? 1 : 0); + if (utils::atof((const char*)glVersion) < 1.5 && nullptr == strstr((const char*)glVersion, "ANGLE")) { char strComplain[256] = {0}; @@ -568,7 +573,8 @@ bool GLViewImpl::initWithRect(std::string_view viewName, const ax::Rect& rect, f utils::killCurrentProcess(); // kill current process, don't cause crash when driver issue. return false; } - +#endif + if (GL_ARB_vertex_shader && GL_ARB_fragment_shader) ax::print("[GL:%s] Ready for GLSL", glVersion); else diff --git a/core/platform/desktop/GLViewImpl-desktop.h b/core/platform/desktop/GLViewImpl-desktop.h index 09b1fa06507c..eb490e9f4d84 100644 --- a/core/platform/desktop/GLViewImpl-desktop.h +++ b/core/platform/desktop/GLViewImpl-desktop.h @@ -28,7 +28,7 @@ THE SOFTWARE. #include "base/Ref.h" #include "platform/Common.h" #include "platform/GLView.h" -#include "glfw3.h" +#include NS_AX_BEGIN diff --git a/core/platform/emscripten/Application-emscripten.cpp b/core/platform/emscripten/Application-emscripten.cpp new file mode 100644 index 000000000000..749499a14469 --- /dev/null +++ b/core/platform/emscripten/Application-emscripten.cpp @@ -0,0 +1,202 @@ +/**************************************************************************** +Copyright (c) 2011 Laschweinski +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "platform/emscripten/Application-emscripten.h" +#include +#include +#include +#include "base/Director.h" +#include "base/Utils.h" +#include "platform/FileUtils.h" +#include + +NS_AX_BEGIN + + +// sharedApplication pointer +Application * Application::sm_pSharedApplication = nullptr; + +static long getCurrentMillSecond() { + long lLastTime; + struct timeval stCurrentTime; + + gettimeofday(&stCurrentTime,NULL); + lLastTime = stCurrentTime.tv_sec*1000+stCurrentTime.tv_usec*0.001; // milliseconds + return lLastTime; +} + +Application::Application() +: _animationInterval(1.0f/60.0f*1000.0f) +{ + AX_ASSERT(! sm_pSharedApplication); + sm_pSharedApplication = this; +} + +Application::~Application() +{ + AX_ASSERT(this == sm_pSharedApplication); + sm_pSharedApplication = nullptr; +} + +extern "C" void mainLoopIter(void) +{ + auto director = Director::getInstance(); + auto glview = director->getOpenGLView(); + + director->mainLoop(); + glview->pollEvents(); +} + +int Application::run() +{ + initGLContextAttrs(); + // Initialize instance and cocos2d. + if (! applicationDidFinishLaunching()) + { + return 1; + } + + auto director = Director::getInstance(); + auto glview = director->getOpenGLView(); + + // Retain glview to avoid glview being released in the while loop + glview->retain(); + + emscripten_set_main_loop(&mainLoopIter, 0, 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(); + return 0; +} + +void Application::setAnimationInterval(float interval) +{ + _animationInterval = interval*1000.0f; +} + +void Application::setResourceRootPath(const std::string& rootResDir) +{ + _resourceRootPath = rootResDir; + if (_resourceRootPath[_resourceRootPath.length() - 1] != '/') + { + _resourceRootPath += '/'; + } + FileUtils* pFileUtils = FileUtils::getInstance(); + std::vector searchPaths = pFileUtils->getSearchPaths(); + searchPaths.insert(searchPaths.begin(), _resourceRootPath); + pFileUtils->setSearchPaths(searchPaths); +} + +const std::string& Application::getResourceRootPath() +{ + return _resourceRootPath; +} + +Application::Platform Application::getTargetPlatform() +{ + return Platform::Emscripten; +} + +std::string Application::getVersion() +{ + return ""; +} + +bool Application::openURL(std::string_view url) +{ + EM_ASM_ARGS({ + window.open(UTF8ToString($0)); + }, url.data()); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +// static member function +////////////////////////////////////////////////////////////////////////// +Application* Application::getInstance() +{ + AX_ASSERT(sm_pSharedApplication); + return sm_pSharedApplication; +} + +// @deprecated Use getInstance() instead +Application* Application::sharedApplication() +{ + return Application::getInstance(); +} + +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); + strncpy(code,pLanguageName,2); + code[2]='\0'; + return code; +} + +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); + + return utils::getLanguageTypeByISO2(pLanguageName); +} + +NS_AX_END + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + diff --git a/core/platform/emscripten/Application-emscripten.h b/core/platform/emscripten/Application-emscripten.h new file mode 100644 index 000000000000..920c1f5e21c8 --- /dev/null +++ b/core/platform/emscripten/Application-emscripten.h @@ -0,0 +1,122 @@ +/**************************************************************************** +Copyright (c) 2011 Laschweinski +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef AXAPLICATION_H_ +#define AXAPLICATION_H_ + +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "platform/Common.h" +#include "platform/ApplicationProtocol.h" +#include + +NS_AX_BEGIN +class Rect; + +class Application : public ApplicationProtocol +{ +public: + /** + * @js ctor + */ + Application(); + /** + * @js NA + * @lua NA + */ + virtual ~Application(); + + /** + @brief Callback by Director for limit FPS. + @param interval The time, which expressed in second in second, between current frame and next. + */ + virtual void setAnimationInterval(float interval) override; + + /** + @brief Run the message loop. + */ + int run(); + + /** + @brief Get current application instance. + @return Current application instance pointer. + */ + static Application* getInstance(); + + /** @deprecated Use getInstance() instead */ + AX_DEPRECATED_ATTRIBUTE static Application* sharedApplication(); + + /* override functions */ + virtual LanguageType getCurrentLanguage() override; + + /** + @brief Get current language iso 639-1 code + @return Current language iso 639-1 code + */ + virtual const char * getCurrentLanguageCode() override; + + /** + @brief Get application version + */ + virtual std::string getVersion() override; + + /** + @brief Open url in default browser + @param String with url to open. + @return true if the resource located by the URL was successfully opened; otherwise false. + */ + virtual bool openURL(std::string_view url) override; + + + /** + * Sets the Resource root path. + * @deprecated Please use FileUtils::getInstance()->setSearchPaths() instead. + */ + AX_DEPRECATED_ATTRIBUTE void setResourceRootPath(const std::string& rootResDir); + + /** + * Gets the Resource root path. + * @deprecated Please use FileUtils::getInstance()->getSearchPaths() instead. + */ + AX_DEPRECATED_ATTRIBUTE const std::string& getResourceRootPath(); + + /** + @brief Get target platform + */ + virtual Platform getTargetPlatform() override; +protected: + long _animationInterval; //micro second + std::string _resourceRootPath; + + static Application * sm_pSharedApplication; +}; + +NS_AX_END + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#endif /* AXAPLICATION_H_ */ diff --git a/core/platform/emscripten/Common-emscripten.cpp b/core/platform/emscripten/Common-emscripten.cpp new file mode 100644 index 000000000000..7a8f2764530a --- /dev/null +++ b/core/platform/emscripten/Common-emscripten.cpp @@ -0,0 +1,51 @@ +/**************************************************************************** +Copyright (c) 2011 Laschweinski +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "platform/Common.h" +#include "platform/emscripten/StdC-emscripten.h" +#include "base/Console.h" +#include + +NS_AX_BEGIN + +void ccMessageBox(const char * msg, const char * title) +{ + EM_ASM_ARGS({ + window.alert(UTF8ToString($0) + ": " + UTF8ToString($1)); + }, title, msg); +} + +void LuaLog(const char * format) +{ + puts(format); +} + +NS_AX_END + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN diff --git a/core/platform/emscripten/Device-emscripten.cpp b/core/platform/emscripten/Device-emscripten.cpp new file mode 100644 index 000000000000..1b4e707b6607 --- /dev/null +++ b/core/platform/emscripten/Device-emscripten.cpp @@ -0,0 +1,161 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "platform/Device.h" +#include "platform/FileUtils.h" + +#include +#include + +NS_AX_BEGIN + +int Device::getDPI() +{ + return 160; +} + +void Device::setAccelerometerEnabled(bool isEnabled) +{ + // TODO: https://emscripten.org/docs/api_reference/html5.h.html? +} + +void Device::setAccelerometerInterval(float interval) +{ + // TODO: https://emscripten.org/docs/api_reference/html5.h.html? +} + +Data Device::getTextureDataForText(std::string_view text, const FontDefinition& textDefinition, TextAlign align, int &width, int &height, bool& hasPremultipliedAlpha) +{ + char color[8]; + sprintf(color, "#%02x%02x%02x", textDefinition._fontFillColor.r, textDefinition._fontFillColor.g, textDefinition._fontFillColor.b); + unsigned char* ptr = (unsigned char*)EM_ASM_INT({ + var lines = UTF8ToString($0).split("\n"); + var linesWidth = []; + var fontName = UTF8ToString($1); + var fontSize = $2; + var lineHeight = $2 * 1.2; // Nothing accurate + var color = UTF8ToString($3); + var dimWidth = $4; + var dimHeight = $5; + var align = $6; + + var canvas = Module.cocosSharedCanvas = Module.cocosSharedCanvas || document.createElement("canvas"); + var context = canvas.getContext("2d"); + context.font = fontSize + "px " + fontName; + + var canvasWidth = dimWidth; + var canvasHeight = dimHeight; + for (var i = 0; i < lines.length; i++) { + var lineWidth = context.measureText(lines[i]).width; + linesWidth[i] = lineWidth; + if (lineWidth > canvasWidth && dimWidth <= 0) { + canvasWidth = lineWidth; + } + }; + + if (dimHeight <= 0) { + canvasHeight = lineHeight * lines.length; + } + + canvas.width = canvasWidth; + canvas.height = canvasHeight; + + context.clearRect(0, 0, canvasWidth, canvasHeight); + context.font = fontSize + "px " + fontName; + context.fillStyle = color; + context.textBaseline = "top"; + + //Vertical top + var offsetY = 0; + if ((align & 0xf0) == 0x30) { + //Vertical center + offsetY = (canvasHeight - lineHeight * lines.length) / 2; + } else if ((align & 0xf0) == 0x20) { + //Vertical bottom + offsetY = canvasHeight - lineHeight * lines.length; + } + + for (var i = 0; i < lines.length; i++) { + //Horizonal left + var offsetX = 0; + if ((align & 0x0f) == 0x03) { + //Horizonal center + offsetX = (canvasWidth - linesWidth[i]) / 2; + } else if ((align & 0x0f) == 0x02) { + //Horizonal right + offsetX = canvasWidth - linesWidth[i]; + } + context.fillText(lines[i], offsetX, offsetY + lineHeight * i); + } + + var data = context.getImageData(0, 0, canvasWidth, canvasHeight).data; + var ptr = _malloc(data.byteLength); // Cocos Data object free it + var buffer= new Uint8Array(Module.HEAPU8.buffer, ptr, data.byteLength); + buffer.set(data); + return ptr; + }, text.data(), textDefinition._fontName.c_str(), textDefinition._fontSize, color, textDefinition._dimensions.width, textDefinition._dimensions.height, align); + + width = EM_ASM_INT({ + return Module.cocosSharedCanvas.width; + }); + height = EM_ASM_INT({ + return Module.cocosSharedCanvas.height; + }); + hasPremultipliedAlpha = true; + + Data ret; + ret.fastSet(ptr, width * height * 4); + return ret; +} + +void Device::setKeepScreenOn(bool value) +{ + // Impossible in browser +} + +void Device::vibrate(float duration) +{ + emscripten_vibrate(duration * 1000); +} + +void Device::prepareImpactFeedbackGenerator(ImpactFeedbackStyle style) {} + +void Device::impactOccurred(ImpactFeedbackStyle style) {} + +void Device::prepareNotificationFeedbackGenerator() {} + +void Device::notificationOccurred(NotificationFeedbackType type) {} + +void Device::prepareSelectionFeedbackGenerator() {} + +void Device::selectionChanged() {} + +NS_AX_END + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN diff --git a/core/platform/emscripten/FileUtils-emscripten.cpp b/core/platform/emscripten/FileUtils-emscripten.cpp new file mode 100644 index 000000000000..22cdd5a22ad0 --- /dev/null +++ b/core/platform/emscripten/FileUtils-emscripten.cpp @@ -0,0 +1,103 @@ +/**************************************************************************** +Copyright (c) 2011 Laschweinski +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "platform/emscripten/FileUtils-emscripten.h" +#include "platform/emscripten/Application-emscripten.h" +#include "platform/Common.h" +#include "base/Macros.h" +#include "base/UTF8.h" +#include + +using namespace std; + +NS_AX_BEGIN + +FileUtils* FileUtils::getInstance() +{ + if (s_sharedFileUtils == nullptr) + { + s_sharedFileUtils = new FileUtilsEmscripten(); + if(!s_sharedFileUtils->init()) + { + delete s_sharedFileUtils; + s_sharedFileUtils = nullptr; + AXLOG("ERROR: Could not init CCFileUtilsEmscripten"); + } + } + return s_sharedFileUtils; +} + +FileUtilsEmscripten::FileUtilsEmscripten() +{} + +bool FileUtilsEmscripten::init() +{ + _defaultResRootPath = "/"; + return FileUtils::init(); +} + +string FileUtilsEmscripten::getWritablePath() const +{ + return "/cocos2dxWritablePath/"; +} + +std::string FileUtilsEmscripten::getNativeWritableAbsolutePath() const +{ + struct stat st; + stat(_writablePath.c_str(), &st); + if (!S_ISDIR(st.st_mode)) + { + mkdir(_writablePath.c_str(), 0744); + } + + return _writablePath; +} + +bool FileUtilsEmscripten::isFileExistInternal(std::string_view path) const +{ + if (path.empty()) + { + return false; + } + + std::string strPath(path); + if (strPath[0] != '/') + { // Not absolute path, add the default root path at the beginning. + if (strPath.find(_defaultResRootPath) != 0) + {// Didn't find "assets/" at the beginning of the path, adding it. + strPath.insert(0, _defaultResRootPath); + } + } + + return access(strPath.c_str(), F_OK) != -1 ? true : false; +} + +NS_AX_END + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN diff --git a/core/platform/emscripten/FileUtils-emscripten.h b/core/platform/emscripten/FileUtils-emscripten.h new file mode 100644 index 000000000000..d33bac1f5e7c --- /dev/null +++ b/core/platform/emscripten/FileUtils-emscripten.h @@ -0,0 +1,67 @@ +/**************************************************************************** +Copyright (c) 2011 Laschweinski +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#ifndef __AX_FILEUTILS_EMSCRIPTEN_H__ +#define __AX_FILEUTILS_EMSCRIPTEN_H__ + +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "platform/FileUtils.h" +#include "platform/PlatformMacros.h" +#include "base/Types.h" +#include +#include + +NS_AX_BEGIN + +/** + * @addtogroup platform + * @{ + */ + +//! @brief Helper class to handle file operations +class AX_DLL FileUtilsEmscripten : public FileUtils +{ + friend class FileUtils; +protected: + FileUtilsEmscripten(); +public: + /* override functions */ + bool init() override; + virtual std::string getWritablePath() const override; + std::string getNativeWritableAbsolutePath() const override; +private: + virtual bool isFileExistInternal(std::string_view path) const override; +}; + +// end of platform group +/// @} + +NS_AX_END + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#endif // __AX_FILEUTILS_EMSCRIPTEN_H__ diff --git a/core/platform/emscripten/GL-emscripten.h b/core/platform/emscripten/GL-emscripten.h new file mode 100644 index 000000000000..31843d64e74b --- /dev/null +++ b/core/platform/emscripten/GL-emscripten.h @@ -0,0 +1,170 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2022 Bytedance Inc. + +https://axmolengine.github.io/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include "platform/PlatformConfig.h" + +#if AX_USE_GLAD + +# include "glad/gl.h" + +# undef GL_DEPTH_STENCIL +# undef GL_DEPTH24_STENCIL8 +# undef GL_UNSIGNED_INT_24_8 +# undef glClearDepth +# undef glMapBuffer +# undef glUnmapBuffer +# undef glBindVertexArray +# undef glDeleteVertexArrays +# undef glGenVertexArrays +# if defined(GL_VERSION_ES_CM_1_0) +# undef glIsRenderbuffer +# undef glBindRenderbuffer +# undef glDeleteRenderbuffers +# undef glGenRenderbuffers +# undef glRenderbufferStorage +# undef glIsFramebuffer +# undef glBindFramebuffer +# undef glDeleteFramebuffers +# undef glGenFramebuffers +# undef glCheckFramebufferStatus +# undef glFramebufferRenderbuffer +# undef glFramebufferTexture2D +# undef glGetFramebufferAttachmentParameteriv +# undef glGenerateMipmap +# endif + +# define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES +# define GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8_OES +# define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES +# define glClearDepth glClearDepthf +# define glMapBuffer glMapBufferOES +# define glUnmapBuffer glUnmapBufferOES +# define glBindVertexArray glBindVertexArrayOES +# define glDeleteVertexArrays glDeleteVertexArraysOES +# define glGenVertexArrays glGenVertexArraysOES +# if defined(GL_VERSION_ES_CM_1_0) +# define glIsRenderbuffer glIsRenderbufferOES +# define glBindRenderbuffer glBindRenderbufferOES +# define glDeleteRenderbuffers glDeleteRenderbuffersOES +# define glGenRenderbuffers glGenRenderbuffersOES +# define glRenderbufferStorage glRenderbufferStorageOES +# define glIsFramebuffer glIsFramebufferOES +# define glBindFramebuffer glBindFramebufferOES +# define glDeleteFramebuffers glDeleteFramebuffersOES +# define glGenFramebuffers glGenFramebuffersOES +# define glCheckFramebufferStatus glCheckFramebufferStatusOES +# define glFramebufferRenderbuffer glFramebufferRenderbufferOES +# define glFramebufferTexture2D glFramebufferTexture2DOES +# define glGetFramebufferAttachmentParameteriv glGetFramebufferAttachmentParameterivOES +# define glGenerateMipmap glGenerateMipmapOES +# endif + +#else + +# define glClearDepth glClearDepthf +# define glDeleteVertexArrays glDeleteVertexArraysOES +# define glGenVertexArrays glGenVertexArraysOES +# define glBindVertexArray glBindVertexArrayOES +# define glMapBuffer glMapBufferOES +# define glUnmapBuffer glUnmapBufferOES + +# define GL_DEPTH24_STENCIL8 GL_DEPTH24_STENCIL8_OES +# define GL_DEPTH_STENCIL GL_DEPTH_STENCIL_OES +# define GL_UNSIGNED_INT_24_8 GL_UNSIGNED_INT_24_8_OES +# define GL_WRITE_ONLY GL_WRITE_ONLY_OES + +// GL_GLEXT_PROTOTYPES isn't defined in glplatform.h on android ndk r7 +// we manually define it here +# include +# ifndef GL_GLEXT_PROTOTYPES +# define GL_GLEXT_PROTOTYPES 1 +# endif + +// normal process +# include +# include +// gl2.h doesn't define GLchar on Android +typedef char GLchar; +// android defines GL_BGRA_EXT but not GL_BRGA +# ifndef GL_BGRA +# define GL_BGRA 0x80E1 +# endif + +// declare here while define in EGLView_android.cpp +extern PFNGLGENVERTEXARRAYSOESPROC glGenVertexArraysOESEXT; +extern PFNGLBINDVERTEXARRAYOESPROC glBindVertexArrayOESEXT; +extern PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArraysOESEXT; + +# define glGenVertexArraysOES glGenVertexArraysOESEXT +# define glBindVertexArrayOES glBindVertexArrayOESEXT +# define glDeleteVertexArraysOES glDeleteVertexArraysOESEXT + +/* gles3/gl */ +# if !defined(GL_SRGB8) +# define GL_SRGB8 0x8C41 +# endif + +# if !defined(GL_SRGB8_ALPHA8) +# define GL_SRGB8_ALPHA8 0x8C43 +# endif + +# if !defined(GL_COMPRESSED_RGB8_ETC2) +# define GL_COMPRESSED_RGB8_ETC2 0x9274 +# endif + +# if !defined(GL_COMPRESSED_SRGB8_ETC2) +# define GL_COMPRESSED_SRGB8_ETC2 0x9275 +# endif + +# if !defined(GL_COMPRESSED_RGBA8_ETC2_EAC) +# define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +# endif + +# if !defined(GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC) +# define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 +# endif + +/* gles2/glext */ +# ifndef GL_EXT_texture_compression_s3tc_srgb +# define GL_EXT_texture_compression_s3tc_srgb 1 +# define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +# define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +# define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +# define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +# endif /* GL_EXT_texture_compression_s3tc_srgb */ + +// works on device which support OpenGLES 3.0 +# if !defined(GL_RG) +# define GL_RG 0x8227 +# endif + +# if !defined(GL_RG8) +# define GL_RG8 0x822B +# endif + +#endif diff --git a/core/platform/emscripten/GLViewImpl-emscripten.cpp b/core/platform/emscripten/GLViewImpl-emscripten.cpp new file mode 100644 index 000000000000..f47e028fc43c --- /dev/null +++ b/core/platform/emscripten/GLViewImpl-emscripten.cpp @@ -0,0 +1,1326 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. +Copyright (c) 2020 C4games Ltd. +Copyright (c) 2021-2022 Bytedance Inc. + +https://axmolengine.github.io/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "platform/emscripten/GLViewImpl-emscripten.h" + +#include +#include + +#include "platform/Application.h" +#include "base/Director.h" +#include "base/Touch.h" +#include "base/EventDispatcher.h" +#include "base/EventKeyboard.h" +#include "base/EventMouse.h" +#include "base/IMEDispatcher.h" +#include "base/Utils.h" +#include "base/UTF8.h" +#include "2d/Camera.h" +#if AX_ICON_SET_SUPPORT +# include "platform/Image.h" +#endif /* AX_ICON_SET_SUPPORT */ + +#include "renderer/Renderer.h" + +#if defined(AX_USE_METAL) +# include +# include "renderer/backend/metal/DeviceMTL.h" +# include "renderer/backend/metal/UtilsMTL.h" +#else +# include "renderer/backend/opengl/MacrosGL.h" +#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + +/** glfw3native.h */ +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) +# ifndef GLFW_EXPOSE_NATIVE_WIN32 +# define GLFW_EXPOSE_NATIVE_WIN32 +# endif +# ifndef GLFW_EXPOSE_NATIVE_WGL +# define GLFW_EXPOSE_NATIVE_WGL +# endif +#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) */ + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) +# ifndef GLFW_EXPOSE_NATIVE_NSGL +# define GLFW_EXPOSE_NATIVE_NSGL +# endif +# ifndef GLFW_EXPOSE_NATIVE_COCOA +# define GLFW_EXPOSE_NATIVE_COCOA +# endif +#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN) + +#else + #include +#endif + +#if defined(_WIN32) +# include "glfw3ext.h" +#endif + +NS_AX_BEGIN + +class GLFWEventHandler +{ +public: + static void onGLFWError(int errorID, const char* errorDesc) + { + if (_view) + _view->onGLFWError(errorID, errorDesc); + } + + static void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify) + { + if (_view) + _view->onGLFWMouseCallBack(window, button, action, modify); + } + + static void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y) + { + if (_view) + _view->onGLFWMouseMoveCallBack(window, x, y); + } + + static void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y) + { + if (_view) + _view->onGLFWMouseScrollCallback(window, x, y); + } + + static void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) + { + if (_view) + _view->onGLFWKeyCallback(window, key, scancode, action, mods); + } + + static void onGLFWCharCallback(GLFWwindow* window, unsigned int character) + { + if (_view) + _view->onGLFWCharCallback(window, character); + } + + static void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y) + { + if (_view) + _view->onGLFWWindowPosCallback(windows, x, y); + } + + // Notes: Unused on windows or macos Metal renderer backend + // static void onGLFWframebufferSize(GLFWwindow* window, int w, int h) + // { + // if (_view) + // _view->onGLFWframebufferSize(window, w, h); + // } + + static void onGLFWWindowSizeCallback(GLFWwindow* window, int width, int height) + { + if (_view) + _view->onGLFWWindowSizeCallback(window, width, height); + } + + static void setGLViewImpl(GLViewImpl* view) { _view = view; } + + static void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified) + { + if (_view) + { + _view->onGLFWWindowIconifyCallback(window, iconified); + } + } + + static void onGLFWWindowFocusCallback(GLFWwindow* window, int focused) + { + if (_view) + { + _view->onGLFWWindowFocusCallback(window, focused); + } + } + +private: + static GLViewImpl* _view; +}; +GLViewImpl* GLFWEventHandler::_view = nullptr; + +const std::string GLViewImpl::EVENT_WINDOW_RESIZED = "glview_window_resized"; +const std::string GLViewImpl::EVENT_WINDOW_FOCUSED = "glview_window_focused"; +const std::string GLViewImpl::EVENT_WINDOW_UNFOCUSED = "glview_window_unfocused"; + +//////////////////////////////////////////////////// + +struct keyCodeItem +{ + int glfwKeyCode; + EventKeyboard::KeyCode keyCode; +}; + +static std::unordered_map g_keyCodeMap; + +static keyCodeItem g_keyCodeStructArray[] = { + /* The unknown key */ + {GLFW_KEY_UNKNOWN, EventKeyboard::KeyCode::KEY_NONE}, + + /* Printable keys */ + {GLFW_KEY_SPACE, EventKeyboard::KeyCode::KEY_SPACE}, + {GLFW_KEY_APOSTROPHE, EventKeyboard::KeyCode::KEY_APOSTROPHE}, + {GLFW_KEY_COMMA, EventKeyboard::KeyCode::KEY_COMMA}, + {GLFW_KEY_MINUS, EventKeyboard::KeyCode::KEY_MINUS}, + {GLFW_KEY_PERIOD, EventKeyboard::KeyCode::KEY_PERIOD}, + {GLFW_KEY_SLASH, EventKeyboard::KeyCode::KEY_SLASH}, + {GLFW_KEY_0, EventKeyboard::KeyCode::KEY_0}, + {GLFW_KEY_1, EventKeyboard::KeyCode::KEY_1}, + {GLFW_KEY_2, EventKeyboard::KeyCode::KEY_2}, + {GLFW_KEY_3, EventKeyboard::KeyCode::KEY_3}, + {GLFW_KEY_4, EventKeyboard::KeyCode::KEY_4}, + {GLFW_KEY_5, EventKeyboard::KeyCode::KEY_5}, + {GLFW_KEY_6, EventKeyboard::KeyCode::KEY_6}, + {GLFW_KEY_7, EventKeyboard::KeyCode::KEY_7}, + {GLFW_KEY_8, EventKeyboard::KeyCode::KEY_8}, + {GLFW_KEY_9, EventKeyboard::KeyCode::KEY_9}, + {GLFW_KEY_SEMICOLON, EventKeyboard::KeyCode::KEY_SEMICOLON}, + {GLFW_KEY_EQUAL, EventKeyboard::KeyCode::KEY_EQUAL}, + {GLFW_KEY_A, EventKeyboard::KeyCode::KEY_A}, + {GLFW_KEY_B, EventKeyboard::KeyCode::KEY_B}, + {GLFW_KEY_C, EventKeyboard::KeyCode::KEY_C}, + {GLFW_KEY_D, EventKeyboard::KeyCode::KEY_D}, + {GLFW_KEY_E, EventKeyboard::KeyCode::KEY_E}, + {GLFW_KEY_F, EventKeyboard::KeyCode::KEY_F}, + {GLFW_KEY_G, EventKeyboard::KeyCode::KEY_G}, + {GLFW_KEY_H, EventKeyboard::KeyCode::KEY_H}, + {GLFW_KEY_I, EventKeyboard::KeyCode::KEY_I}, + {GLFW_KEY_J, EventKeyboard::KeyCode::KEY_J}, + {GLFW_KEY_K, EventKeyboard::KeyCode::KEY_K}, + {GLFW_KEY_L, EventKeyboard::KeyCode::KEY_L}, + {GLFW_KEY_M, EventKeyboard::KeyCode::KEY_M}, + {GLFW_KEY_N, EventKeyboard::KeyCode::KEY_N}, + {GLFW_KEY_O, EventKeyboard::KeyCode::KEY_O}, + {GLFW_KEY_P, EventKeyboard::KeyCode::KEY_P}, + {GLFW_KEY_Q, EventKeyboard::KeyCode::KEY_Q}, + {GLFW_KEY_R, EventKeyboard::KeyCode::KEY_R}, + {GLFW_KEY_S, EventKeyboard::KeyCode::KEY_S}, + {GLFW_KEY_T, EventKeyboard::KeyCode::KEY_T}, + {GLFW_KEY_U, EventKeyboard::KeyCode::KEY_U}, + {GLFW_KEY_V, EventKeyboard::KeyCode::KEY_V}, + {GLFW_KEY_W, EventKeyboard::KeyCode::KEY_W}, + {GLFW_KEY_X, EventKeyboard::KeyCode::KEY_X}, + {GLFW_KEY_Y, EventKeyboard::KeyCode::KEY_Y}, + {GLFW_KEY_Z, EventKeyboard::KeyCode::KEY_Z}, + {GLFW_KEY_LEFT_BRACKET, EventKeyboard::KeyCode::KEY_LEFT_BRACKET}, + {GLFW_KEY_BACKSLASH, EventKeyboard::KeyCode::KEY_BACK_SLASH}, + {GLFW_KEY_RIGHT_BRACKET, EventKeyboard::KeyCode::KEY_RIGHT_BRACKET}, + {GLFW_KEY_GRAVE_ACCENT, EventKeyboard::KeyCode::KEY_GRAVE}, + {GLFW_KEY_WORLD_1, EventKeyboard::KeyCode::KEY_GRAVE}, + {GLFW_KEY_WORLD_2, EventKeyboard::KeyCode::KEY_NONE}, + + /* Function keys */ + {GLFW_KEY_ESCAPE, EventKeyboard::KeyCode::KEY_ESCAPE}, + {GLFW_KEY_ENTER, EventKeyboard::KeyCode::KEY_ENTER}, + {GLFW_KEY_TAB, EventKeyboard::KeyCode::KEY_TAB}, + {GLFW_KEY_BACKSPACE, EventKeyboard::KeyCode::KEY_BACKSPACE}, + {GLFW_KEY_INSERT, EventKeyboard::KeyCode::KEY_INSERT}, + {GLFW_KEY_DELETE, EventKeyboard::KeyCode::KEY_DELETE}, + {GLFW_KEY_RIGHT, EventKeyboard::KeyCode::KEY_RIGHT_ARROW}, + {GLFW_KEY_LEFT, EventKeyboard::KeyCode::KEY_LEFT_ARROW}, + {GLFW_KEY_DOWN, EventKeyboard::KeyCode::KEY_DOWN_ARROW}, + {GLFW_KEY_UP, EventKeyboard::KeyCode::KEY_UP_ARROW}, + {GLFW_KEY_PAGE_UP, EventKeyboard::KeyCode::KEY_PG_UP}, + {GLFW_KEY_PAGE_DOWN, EventKeyboard::KeyCode::KEY_PG_DOWN}, + {GLFW_KEY_HOME, EventKeyboard::KeyCode::KEY_HOME}, + {GLFW_KEY_END, EventKeyboard::KeyCode::KEY_END}, + {GLFW_KEY_CAPS_LOCK, EventKeyboard::KeyCode::KEY_CAPS_LOCK}, + {GLFW_KEY_SCROLL_LOCK, EventKeyboard::KeyCode::KEY_SCROLL_LOCK}, + {GLFW_KEY_NUM_LOCK, EventKeyboard::KeyCode::KEY_NUM_LOCK}, + {GLFW_KEY_PRINT_SCREEN, EventKeyboard::KeyCode::KEY_PRINT}, + {GLFW_KEY_PAUSE, EventKeyboard::KeyCode::KEY_PAUSE}, + {GLFW_KEY_F1, EventKeyboard::KeyCode::KEY_F1}, + {GLFW_KEY_F2, EventKeyboard::KeyCode::KEY_F2}, + {GLFW_KEY_F3, EventKeyboard::KeyCode::KEY_F3}, + {GLFW_KEY_F4, EventKeyboard::KeyCode::KEY_F4}, + {GLFW_KEY_F5, EventKeyboard::KeyCode::KEY_F5}, + {GLFW_KEY_F6, EventKeyboard::KeyCode::KEY_F6}, + {GLFW_KEY_F7, EventKeyboard::KeyCode::KEY_F7}, + {GLFW_KEY_F8, EventKeyboard::KeyCode::KEY_F8}, + {GLFW_KEY_F9, EventKeyboard::KeyCode::KEY_F9}, + {GLFW_KEY_F10, EventKeyboard::KeyCode::KEY_F10}, + {GLFW_KEY_F11, EventKeyboard::KeyCode::KEY_F11}, + {GLFW_KEY_F12, EventKeyboard::KeyCode::KEY_F12}, + {GLFW_KEY_F13, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F14, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F15, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F16, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F17, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F18, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F19, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F20, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F21, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F22, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F23, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F24, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_F25, EventKeyboard::KeyCode::KEY_NONE}, + {GLFW_KEY_KP_0, EventKeyboard::KeyCode::KEY_0}, + {GLFW_KEY_KP_1, EventKeyboard::KeyCode::KEY_1}, + {GLFW_KEY_KP_2, EventKeyboard::KeyCode::KEY_2}, + {GLFW_KEY_KP_3, EventKeyboard::KeyCode::KEY_3}, + {GLFW_KEY_KP_4, EventKeyboard::KeyCode::KEY_4}, + {GLFW_KEY_KP_5, EventKeyboard::KeyCode::KEY_5}, + {GLFW_KEY_KP_6, EventKeyboard::KeyCode::KEY_6}, + {GLFW_KEY_KP_7, EventKeyboard::KeyCode::KEY_7}, + {GLFW_KEY_KP_8, EventKeyboard::KeyCode::KEY_8}, + {GLFW_KEY_KP_9, EventKeyboard::KeyCode::KEY_9}, + {GLFW_KEY_KP_DECIMAL, EventKeyboard::KeyCode::KEY_PERIOD}, + {GLFW_KEY_KP_DIVIDE, EventKeyboard::KeyCode::KEY_KP_DIVIDE}, + {GLFW_KEY_KP_MULTIPLY, EventKeyboard::KeyCode::KEY_KP_MULTIPLY}, + {GLFW_KEY_KP_SUBTRACT, EventKeyboard::KeyCode::KEY_KP_MINUS}, + {GLFW_KEY_KP_ADD, EventKeyboard::KeyCode::KEY_KP_PLUS}, + {GLFW_KEY_KP_ENTER, EventKeyboard::KeyCode::KEY_KP_ENTER}, + {GLFW_KEY_KP_EQUAL, EventKeyboard::KeyCode::KEY_EQUAL}, + {GLFW_KEY_LEFT_SHIFT, EventKeyboard::KeyCode::KEY_LEFT_SHIFT}, + {GLFW_KEY_LEFT_CONTROL, EventKeyboard::KeyCode::KEY_LEFT_CTRL}, + {GLFW_KEY_LEFT_ALT, EventKeyboard::KeyCode::KEY_LEFT_ALT}, + {GLFW_KEY_LEFT_SUPER, EventKeyboard::KeyCode::KEY_HYPER}, + {GLFW_KEY_RIGHT_SHIFT, EventKeyboard::KeyCode::KEY_RIGHT_SHIFT}, + {GLFW_KEY_RIGHT_CONTROL, EventKeyboard::KeyCode::KEY_RIGHT_CTRL}, + {GLFW_KEY_RIGHT_ALT, EventKeyboard::KeyCode::KEY_RIGHT_ALT}, + {GLFW_KEY_RIGHT_SUPER, EventKeyboard::KeyCode::KEY_HYPER}, + {GLFW_KEY_MENU, EventKeyboard::KeyCode::KEY_MENU}, + {GLFW_KEY_LAST, EventKeyboard::KeyCode::KEY_NONE}}; + +////////////////////////////////////////////////////////////////////////// +// implement GLViewImpl +////////////////////////////////////////////////////////////////////////// + +GLViewImpl::GLViewImpl(bool initglfw) + : _captured(false) + , _isInRetinaMonitor(false) + , _isRetinaEnabled(false) + , _retinaFactor(1) + , _frameZoomFactor(1.0f) + , _mainWindow(nullptr) + , _monitor(nullptr) + , _mouseX(0.0f) + , _mouseY(0.0f) +{ + _viewName = "AXMOL10"; + g_keyCodeMap.clear(); + for (auto&& item : g_keyCodeStructArray) + { + g_keyCodeMap[item.glfwKeyCode] = item.keyCode; + } + + GLFWEventHandler::setGLViewImpl(this); + if (initglfw) + { + glfwSetErrorCallback(GLFWEventHandler::onGLFWError); +#if defined(AX_USE_GLES) && GLFW_VERSION_MAJOR >= 3 && GLFW_VERSION_MINOR >= 4 + glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, GLFW_ANGLE_PLATFORM_TYPE_D3D11); // since glfw-3.4 +#endif +#if defined(_WIN32) + glfwxInit(); +#else + glfwInit(); +#endif + } +} + +GLViewImpl::~GLViewImpl() +{ + AXLOGINFO("deallocing GLViewImpl: %p", this); + GLFWEventHandler::setGLViewImpl(nullptr); +#if defined(_WIN32) + glfwxTerminate(); +#else + glfwTerminate(); +#endif +} + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) +HWND GLViewImpl::getWin32Window() +{ + return glfwGetWin32Window(_mainWindow); +} +#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) */ + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) +void* GLViewImpl::getCocoaWindow() +{ + return (void*)glfwGetCocoaWindow(_mainWindow); +} +void* GLViewImpl::getNSGLContext() +{ + return (void*)glfwGetNSGLContext(_mainWindow); +} // stevetranby: added +#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + +GLViewImpl* GLViewImpl::create(std::string_view viewName) +{ + return GLViewImpl::create(viewName, false); +} + +GLViewImpl* GLViewImpl::create(std::string_view viewName, bool resizable) +{ + auto ret = new GLViewImpl; + if (ret->initWithRect(viewName, ax::Rect(0, 0, 960, 640), 1.0f, resizable)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +GLViewImpl* GLViewImpl::createWithRect(std::string_view viewName, const ax::Rect& rect, float frameZoomFactor, bool resizable) +{ + auto ret = new GLViewImpl; + if (ret->initWithRect(viewName, rect, frameZoomFactor, resizable)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +GLViewImpl* GLViewImpl::createWithFullScreen(std::string_view viewName) +{ + auto ret = new GLViewImpl(); + if (ret->initWithFullScreen(viewName)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +GLViewImpl* GLViewImpl::createWithFullScreen(std::string_view viewName, + const GLFWvidmode& videoMode, + GLFWmonitor* monitor) +{ + auto ret = new GLViewImpl(); + if (ret->initWithFullscreen(viewName, videoMode, monitor)) + { + ret->autorelease(); + return ret; + } + AX_SAFE_DELETE(ret); + return nullptr; +} + +bool GLViewImpl::initWithRect(std::string_view viewName, const ax::Rect& rect, float frameZoomFactor, bool resizable) +{ + setViewName(viewName); + + _frameZoomFactor = frameZoomFactor; + + Vec2 frameSize = rect.size; + +#if defined(AX_USE_GLES) + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); +#endif + + glfwWindowHint(GLFW_RESIZABLE, resizable ? GL_TRUE : GL_FALSE); + glfwWindowHint(GLFW_RED_BITS, _glContextAttrs.redBits); + glfwWindowHint(GLFW_GREEN_BITS, _glContextAttrs.greenBits); + glfwWindowHint(GLFW_BLUE_BITS, _glContextAttrs.blueBits); + glfwWindowHint(GLFW_ALPHA_BITS, _glContextAttrs.alphaBits); + glfwWindowHint(GLFW_DEPTH_BITS, _glContextAttrs.depthBits); + glfwWindowHint(GLFW_STENCIL_BITS, _glContextAttrs.stencilBits); + + glfwWindowHint(GLFW_SAMPLES, _glContextAttrs.multisamplingCount); + + glfwWindowHint(GLFW_VISIBLE, _glContextAttrs.visible); + glfwWindowHint(GLFW_DECORATED, _glContextAttrs.decorated); + +#if defined(AX_USE_METAL) + // Don't create gl context. + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); +#endif + + int neededWidth = static_cast(frameSize.width * _frameZoomFactor); + int neededHeight = static_cast(frameSize.height * _frameZoomFactor); + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) + glfwxSetParent((HWND)_glContextAttrs.viewParent); +#endif + + _mainWindow = glfwCreateWindow(neededWidth, neededHeight, _viewName.c_str(), + _monitor, nullptr); + + if (_mainWindow == nullptr) + { + std::string message = "Can't create window"; + if (!_glfwError.empty()) + { + message.append("\nMore info: \n"); + message.append(_glfwError); + } + + ccMessageBox(message.c_str(), "Error launch application"); + utils::killCurrentProcess(); // kill current process, don't cause crash when driver issue. + return false; + } + +#if defined(AX_USE_METAL) + int fbWidth, fbHeight; + glfwGetFramebufferSize(_mainWindow, &fbWidth, &fbHeight); + + CGSize size; + size.width = static_cast(fbWidth); + size.height = static_cast(fbHeight); + // Initialize device. + id device = MTLCreateSystemDefaultDevice(); + if (!device) + { + AXLOG("Doesn't support metal."); + return false; + } + + NSView* contentView = [(id)getCocoaWindow() contentView]; + [contentView setWantsLayer:YES]; + CAMetalLayer* layer = [CAMetalLayer layer]; + [layer setDevice:device]; + [layer setPixelFormat:MTLPixelFormatBGRA8Unorm]; + [layer setFramebufferOnly:YES]; + [layer setDrawableSize:size]; + layer.displaySyncEnabled = _glContextAttrs.vsync; + [contentView setLayer:layer]; + backend::DeviceMTL::setCAMetalLayer(layer); +#endif + +#if defined(AX_USE_GL) + glfwMakeContextCurrent(_mainWindow); +#endif + + /* + * Note that the created window and context may differ from what you requested, + * as not all parameters and hints are + * [hard constraints](@ref window_hints_hard). This includes the size of the + * window, especially for full screen windows. To retrieve the actual + * attributes of the created window and context, use queries like @ref + * glfwGetWindowAttrib and @ref glfwGetWindowSize. + * + * see declaration glfwCreateWindow + */ + int realW = 0, realH = 0; + glfwGetWindowSize(_mainWindow, &realW, &realH); + if (realW != neededWidth) + { + frameSize.width = realW / _frameZoomFactor; + } + if (realH != neededHeight) + { + frameSize.height = realH / _frameZoomFactor; + } + + glfwSetMouseButtonCallback(_mainWindow, GLFWEventHandler::onGLFWMouseCallBack); + glfwSetCursorPosCallback(_mainWindow, GLFWEventHandler::onGLFWMouseMoveCallBack); + glfwSetScrollCallback(_mainWindow, GLFWEventHandler::onGLFWMouseScrollCallback); + glfwSetCharCallback(_mainWindow, GLFWEventHandler::onGLFWCharCallback); + glfwSetKeyCallback(_mainWindow, GLFWEventHandler::onGLFWKeyCallback); + glfwSetWindowPosCallback(_mainWindow, GLFWEventHandler::onGLFWWindowPosCallback); + glfwSetWindowSizeCallback(_mainWindow, GLFWEventHandler::onGLFWWindowSizeCallback); + glfwSetWindowIconifyCallback(_mainWindow, GLFWEventHandler::onGLFWWindowIconifyCallback); + glfwSetWindowFocusCallback(_mainWindow, GLFWEventHandler::onGLFWWindowFocusCallback); + + setFrameSize(frameSize.width, frameSize.height); + +#if (AX_TARGET_PLATFORM != AX_PLATFORM_MAC) + loadGL(); +#endif + +#if defined(AX_USE_GL) + // check OpenGL version at first + const GLubyte* glVersion = glGetString(GL_VERSION); + +#ifndef EMSCRIPTEN + glfwSwapInterval(_glContextAttrs.vsync ? 1 : 0); + + if (utils::atof((const char*)glVersion) < 1.5 && nullptr == strstr((const char*)glVersion, "ANGLE")) + { + char strComplain[256] = {0}; + sprintf(strComplain, + "OpenGL 1.5 or higher is required (your version is %s). Please upgrade the driver of your video card.", + glVersion); + ccMessageBox(strComplain, "OpenGL version too old"); + utils::killCurrentProcess(); // kill current process, don't cause crash when driver issue. + return false; + } +#endif + + if (GL_ARB_vertex_shader && GL_ARB_fragment_shader) + ax::print("[GL:%s] Ready for GLSL", glVersion); + else + ax::print("Not totally ready :("); + + // Will cause OpenGL error 0x0500 when use ANGLE-GLES on desktop +# if !defined(AX_USE_GLES) + // Enable point size by default. +# if defined(GL_VERSION_2_0) + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); +# else + glEnable(GL_VERTEX_PROGRAM_POINT_SIZE_ARB); +# endif + if (_glContextAttrs.multisamplingCount > 0) + glEnable(GL_MULTISAMPLE); +# endif + CHECK_GL_ERROR_DEBUG(); +#endif + // // GLFW v3.2 no longer emits "onGLFWWindowSizeFunCallback" at creation time. Force default viewport: + // setViewPortInPoints(0, 0, neededWidth, neededHeight); + // + return true; +} + +bool GLViewImpl::initWithFullScreen(std::string_view viewName) +{ + // Create fullscreen window on primary monitor at its current video mode. + _monitor = glfwGetPrimaryMonitor(); + if (nullptr == _monitor) + return false; + + const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); + return initWithRect(viewName, ax::Rect(0, 0, (float)videoMode->width, (float)videoMode->height), 1.0f, false); +} + +bool GLViewImpl::initWithFullscreen(std::string_view viewname, const GLFWvidmode& videoMode, GLFWmonitor* monitor) +{ + // Create fullscreen on specified monitor at the specified video mode. + _monitor = monitor; + if (nullptr == _monitor) + return false; + + // These are soft constraints. If the video mode is retrieved at runtime, the resulting window and context should + // match these exactly. If invalid attribs are passed (eg. from an outdated cache), window creation will NOT fail + // but the actual window/context may differ. + glfwWindowHint(GLFW_REFRESH_RATE, videoMode.refreshRate); + glfwWindowHint(GLFW_RED_BITS, videoMode.redBits); + glfwWindowHint(GLFW_BLUE_BITS, videoMode.blueBits); + glfwWindowHint(GLFW_GREEN_BITS, videoMode.greenBits); + + return initWithRect(viewname, ax::Rect(0, 0, (float)videoMode.width, (float)videoMode.height), 1.0f, false); +} + +bool GLViewImpl::isOpenGLReady() +{ + return nullptr != _mainWindow; +} + +void GLViewImpl::end() +{ + if (_mainWindow) + { + glfwSetWindowShouldClose(_mainWindow, 1); + _mainWindow = nullptr; + } + // Release self. Otherwise, GLViewImpl could not be freed. + release(); +} + +void GLViewImpl::swapBuffers() +{ +#if defined(AX_USE_GL) + if (_mainWindow) + glfwSwapBuffers(_mainWindow); +#endif +} + +bool GLViewImpl::windowShouldClose() +{ + if (_mainWindow) + return glfwWindowShouldClose(_mainWindow) ? true : false; + else + return true; +} + +void GLViewImpl::pollEvents() +{ + glfwPollEvents(); +} + +void GLViewImpl::enableRetina(bool enabled) +{ +#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + _isRetinaEnabled = enabled; + if (_isRetinaEnabled) + { + _retinaFactor = 1; + } + else + { + _retinaFactor = 2; + } + updateFrameSize(); +#endif +} + +void GLViewImpl::setIMEKeyboardState(bool /*bOpen*/) {} + +#if AX_ICON_SET_SUPPORT +void GLViewImpl::setIcon(std::string_view filename) const +{ + this->setIcon(std::vector{filename}); +} + +void GLViewImpl::setIcon(const std::vector& filelist) const +{ + if (filelist.empty()) + return; + std::vector icons; + for (auto const& filename : filelist) + { + Image* icon = new Image(); + if (icon->initWithImageFile(filename)) + { + icons.emplace_back(icon); + } + else + { + AX_SAFE_DELETE(icon); + } + } + + if (icons.empty()) + return; // No valid images + size_t iconsCount = icons.size(); + auto images = new GLFWimage[iconsCount]; + for (size_t i = 0; i < iconsCount; i++) + { + auto& image = images[i]; + auto& icon = icons[i]; + image.width = icon->getWidth(); + image.height = icon->getHeight(); + image.pixels = icon->getData(); + }; + + GLFWwindow* window = this->getWindow(); + glfwSetWindowIcon(window, iconsCount, images); + + AX_SAFE_DELETE_ARRAY(images); + for (auto&& icon : icons) + { + AX_SAFE_DELETE(icon); + } +} + +void GLViewImpl::setDefaultIcon() const +{ + GLFWwindow* window = this->getWindow(); + glfwSetWindowIcon(window, 0, nullptr); +} +#endif /* AX_ICON_SET_SUPPORT */ + +void GLViewImpl::setCursorVisible(bool isVisible) +{ + if (_mainWindow == NULL) + return; + + if (isVisible) + glfwSetInputMode(_mainWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL); + else + glfwSetInputMode(_mainWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); +} + +void GLViewImpl::setFrameZoomFactor(float zoomFactor) +{ + AXASSERT(zoomFactor > 0.0f, "zoomFactor must be larger than 0"); + + if (std::abs(_frameZoomFactor - zoomFactor) < FLT_EPSILON) + { + return; + } + + _frameZoomFactor = zoomFactor; + updateFrameSize(); +} + +float GLViewImpl::getFrameZoomFactor() const +{ + return _frameZoomFactor; +} + +bool GLViewImpl::isFullscreen() const +{ + return (_monitor != nullptr); +} + +void GLViewImpl::setFullscreen() +{ + setFullscreen(-1, -1, -1); +} + +void GLViewImpl::setFullscreen(int w, int h, int refreshRate) +{ + auto monitor = glfwGetPrimaryMonitor(); + if (nullptr == monitor || monitor == _monitor) + { + return; + } + this->setFullscreen(monitor, w, h, refreshRate); +} + +void GLViewImpl::setFullscreen(int monitorIndex) +{ + setFullscreen(monitorIndex, -1, -1, -1); +} + +void GLViewImpl::setFullscreen(int monitorIndex, int w, int h, int refreshRate) +{ + int count = 0; + GLFWmonitor** monitors = glfwGetMonitors(&count); + if (monitorIndex < 0 || monitorIndex >= count) + { + return; + } + GLFWmonitor* monitor = monitors[monitorIndex]; + if (nullptr == monitor || _monitor == monitor) + { + return; + } + this->setFullscreen(monitor, w, h, refreshRate); +} + +void GLViewImpl::setFullscreen(GLFWmonitor* monitor, int w, int h, int refreshRate) +{ + _monitor = monitor; + + const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); + if (w == -1) + w = videoMode->width; + if (h == -1) + h = videoMode->height; + if (refreshRate == -1) + refreshRate = videoMode->refreshRate; + + glfwSetWindowMonitor(_mainWindow, _monitor, 0, 0, w, h, refreshRate); + + updateWindowSize(); +} + +void GLViewImpl::setWindowed(int width, int height) +{ + if (!this->isFullscreen()) + { + this->setFrameSize((float)width, (float)height); + } + else + { + const GLFWvidmode* videoMode = glfwGetVideoMode(_monitor); + int xpos = 0, ypos = 0; + glfwGetMonitorPos(_monitor, &xpos, &ypos); + xpos += (int)((videoMode->width - width) * 0.5f); + ypos += (int)((videoMode->height - height) * 0.5f); + _monitor = nullptr; + glfwSetWindowMonitor(_mainWindow, nullptr, xpos, ypos, width, height, GLFW_DONT_CARE); +#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + // on mac window will sometimes lose title when windowed + glfwSetWindowTitle(_mainWindow, _viewName.c_str()); +#endif + + updateWindowSize(); + } +} + +void GLViewImpl::updateWindowSize() +{ + int w = 0, h = 0; + glfwGetFramebufferSize(_mainWindow, &w, &h); + int frameWidth = w / _frameZoomFactor; + int frameHeight = h / _frameZoomFactor; + setFrameSize(frameWidth, frameHeight); + updateDesignResolutionSize(); + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_RESIZED, nullptr); +} + +int GLViewImpl::getMonitorCount() const +{ + int count = 0; + glfwGetMonitors(&count); + return count; +} + +Vec2 GLViewImpl::getMonitorSize() const +{ + GLFWmonitor* monitor = _monitor; + if (nullptr == monitor) + { + GLFWwindow* window = this->getWindow(); + monitor = glfwGetWindowMonitor(window); + } + if (nullptr == monitor) + { + monitor = glfwGetPrimaryMonitor(); + } + if (nullptr != monitor) + { + const GLFWvidmode* videoMode = glfwGetVideoMode(monitor); + Vec2 size = Vec2((float)videoMode->width, (float)videoMode->height); + return size; + } + return Vec2::ZERO; +} + +void GLViewImpl::updateFrameSize() +{ + if (_screenSize.width > 0 && _screenSize.height > 0) + { + int w = 0, h = 0; + glfwGetWindowSize(_mainWindow, &w, &h); + + int frameBufferW = 0, frameBufferH = 0; + glfwGetFramebufferSize(_mainWindow, &frameBufferW, &frameBufferH); + + if (frameBufferW == 2 * w && frameBufferH == 2 * h) + { + if (_isRetinaEnabled) + { + _retinaFactor = 1; + } + else + { + _retinaFactor = 2; + } + glfwSetWindowSize(_mainWindow, _screenSize.width / 2 * _retinaFactor * _frameZoomFactor, + _screenSize.height / 2 * _retinaFactor * _frameZoomFactor); + + _isInRetinaMonitor = true; + } + else + { + if (_isInRetinaMonitor) + { + _retinaFactor = 1; + } + glfwSetWindowSize(_mainWindow, (int)(_screenSize.width * _retinaFactor * _frameZoomFactor), + (int)(_screenSize.height * _retinaFactor * _frameZoomFactor)); + + _isInRetinaMonitor = false; + } + } +} + +void GLViewImpl::setFrameSize(float width, float height) +{ + GLView::setFrameSize(width, height); + updateFrameSize(); +} + +void GLViewImpl::setViewPortInPoints(float x, float y, float w, float h) +{ + Viewport vp; + vp.x = (int)(x * _scaleX * _retinaFactor * _frameZoomFactor + + _viewPortRect.origin.x * _retinaFactor * _frameZoomFactor); + vp.y = (int)(y * _scaleY * _retinaFactor * _frameZoomFactor + + _viewPortRect.origin.y * _retinaFactor * _frameZoomFactor); + vp.w = (unsigned int)(w * _scaleX * _retinaFactor * _frameZoomFactor); + vp.h = (unsigned int)(h * _scaleY * _retinaFactor * _frameZoomFactor); + Camera::setDefaultViewport(vp); +} + +void GLViewImpl::setScissorInPoints(float x, float y, float w, float h) +{ + auto x1 = (int)(x * _scaleX * _retinaFactor * _frameZoomFactor + + _viewPortRect.origin.x * _retinaFactor * _frameZoomFactor); + auto y1 = (int)(y * _scaleY * _retinaFactor * _frameZoomFactor + + _viewPortRect.origin.y * _retinaFactor * _frameZoomFactor); + auto width1 = (unsigned int)(w * _scaleX * _retinaFactor * _frameZoomFactor); + auto height1 = (unsigned int)(h * _scaleY * _retinaFactor * _frameZoomFactor); + auto renderer = Director::getInstance()->getRenderer(); + renderer->setScissorRect(x1, y1, width1, height1); +} + +ax::Rect GLViewImpl::getScissorRect() const +{ + auto renderer = Director::getInstance()->getRenderer(); + auto& rect = renderer->getScissorRect(); + + float x = (rect.x - _viewPortRect.origin.x * _retinaFactor * _frameZoomFactor) / + (_scaleX * _retinaFactor * _frameZoomFactor); + float y = (rect.y - _viewPortRect.origin.y * _retinaFactor * _frameZoomFactor) / + (_scaleY * _retinaFactor * _frameZoomFactor); + float w = rect.width / (_scaleX * _retinaFactor * _frameZoomFactor); + float h = rect.height / (_scaleY * _retinaFactor * _frameZoomFactor); + return ax::Rect(x, y, w, h); +} + +void GLViewImpl::onGLFWError(int errorID, const char* errorDesc) +{ + if (_mainWindow) + { + _glfwError = StringUtils::format("GLFWError #%d Happen, %s", errorID, errorDesc); + } + else + { + _glfwError.append(StringUtils::format("GLFWError #%d Happen, %s\n", errorID, errorDesc)); + } + AXLOGERROR("%s", _glfwError.c_str()); +} + +void GLViewImpl::onGLFWMouseCallBack(GLFWwindow* /*window*/, int button, int action, int /*modify*/) +{ + if (GLFW_MOUSE_BUTTON_LEFT == button) + { + if (GLFW_PRESS == action) + { + _captured = true; + if (this->getViewPortRect().equals(ax::Rect::ZERO) || + this->getViewPortRect().containsPoint(Vec2(_mouseX, _mouseY))) + { + intptr_t id = 0; + this->handleTouchesBegin(1, &id, &_mouseX, &_mouseY); + } + } + else if (GLFW_RELEASE == action) + { + if (_captured) + { + _captured = false; + intptr_t id = 0; + this->handleTouchesEnd(1, &id, &_mouseX, &_mouseY); + } + } + } + + // Because OpenGL and cocos2d-x uses different Y axis, we need to convert the coordinate here + float cursorX = (_mouseX - _viewPortRect.origin.x) / _scaleX; + float cursorY = (_viewPortRect.origin.y + _viewPortRect.size.height - _mouseY) / _scaleY; + + if (GLFW_PRESS == action) + { + EventMouse event(EventMouse::MouseEventType::MOUSE_DOWN); + event.setCursorPosition(cursorX, cursorY); + event.setMouseButton(static_cast(button)); + Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); + } + else if (GLFW_RELEASE == action) + { + EventMouse event(EventMouse::MouseEventType::MOUSE_UP); + event.setCursorPosition(cursorX, cursorY); + event.setMouseButton(static_cast(button)); + Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); + } +} + +void GLViewImpl::onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y) +{ + _mouseX = (float)x; + _mouseY = (float)y; + + _mouseX /= this->getFrameZoomFactor(); + _mouseY /= this->getFrameZoomFactor(); + + if (_isInRetinaMonitor) + { + if (_retinaFactor == 1) + { + _mouseX *= 2; + _mouseY *= 2; + } + } + + if (_captured) + { + intptr_t id = 0; + this->handleTouchesMove(1, &id, &_mouseX, &_mouseY); + } + + // Because OpenGL and cocos2d-x uses different Y axis, we need to convert the coordinate here + float cursorX = (_mouseX - _viewPortRect.origin.x) / _scaleX; + float cursorY = (_viewPortRect.origin.y + _viewPortRect.size.height - _mouseY) / _scaleY; + + EventMouse event(EventMouse::MouseEventType::MOUSE_MOVE); + // Set current button + if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS) + { + event.setMouseButton(static_cast(GLFW_MOUSE_BUTTON_LEFT)); + } + else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS) + { + event.setMouseButton(static_cast(GLFW_MOUSE_BUTTON_RIGHT)); + } + else if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) + { + event.setMouseButton(static_cast(GLFW_MOUSE_BUTTON_MIDDLE)); + } + event.setCursorPosition(cursorX, cursorY); + Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); +} + +void GLViewImpl::onGLFWMouseScrollCallback(GLFWwindow* /*window*/, double x, double y) +{ + EventMouse event(EventMouse::MouseEventType::MOUSE_SCROLL); + // Because OpenGL and cocos2d-x uses different Y axis, we need to convert the coordinate here + float cursorX = (_mouseX - _viewPortRect.origin.x) / _scaleX; + float cursorY = (_viewPortRect.origin.y + _viewPortRect.size.height - _mouseY) / _scaleY; + event.setScrollData((float)x, -(float)y); + event.setCursorPosition(cursorX, cursorY); + Director::getInstance()->getEventDispatcher()->dispatchEvent(&event); +} + +void GLViewImpl::onGLFWKeyCallback(GLFWwindow* /*window*/, int key, int /*scancode*/, int action, int /*mods*/) +{ + // x-studio spec, for repeat press key support. + EventKeyboard event(g_keyCodeMap[key], action); + auto dispatcher = Director::getInstance()->getEventDispatcher(); + dispatcher->dispatchEvent(&event); + + if (GLFW_RELEASE != action) + { + switch (g_keyCodeMap[key]) + { + case EventKeyboard::KeyCode::KEY_BACKSPACE: + IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward(); + break; + case EventKeyboard::KeyCode::KEY_HOME: + case EventKeyboard::KeyCode::KEY_KP_HOME: + case EventKeyboard::KeyCode::KEY_DELETE: + case EventKeyboard::KeyCode::KEY_KP_DELETE: + case EventKeyboard::KeyCode::KEY_END: + case EventKeyboard::KeyCode::KEY_LEFT_ARROW: + case EventKeyboard::KeyCode::KEY_RIGHT_ARROW: + case EventKeyboard::KeyCode::KEY_ESCAPE: + IMEDispatcher::sharedDispatcher()->dispatchControlKey(g_keyCodeMap[key]); + break; + default: + break; + } + } +} + +void GLViewImpl::onGLFWCharCallback(GLFWwindow* /*window*/, unsigned int charCode) +{ + std::string utf8String; + StringUtils::UTF32ToUTF8(std::u32string_view{(char32_t*)&charCode, (size_t)1}, utf8String); + static std::unordered_set controlUnicode = { + "\xEF\x9C\x80", // up + "\xEF\x9C\x81", // down + "\xEF\x9C\x82", // left + "\xEF\x9C\x83", // right + "\xEF\x9C\xA8", // delete + "\xEF\x9C\xA9", // home + "\xEF\x9C\xAB", // end + "\xEF\x9C\xAC", // pageup + "\xEF\x9C\xAD", // pagedown + "\xEF\x9C\xB9" // clear + }; + // Check for send control key + if (controlUnicode.find(utf8String) == controlUnicode.end()) + { + IMEDispatcher::sharedDispatcher()->dispatchInsertText(utf8String.c_str(), utf8String.size()); + } +} + +void GLViewImpl::onGLFWWindowPosCallback(GLFWwindow* /*window*/, int /*x*/, int /*y*/) +{ + Director::getInstance()->setViewport(); +} + +void GLViewImpl::onGLFWWindowSizeCallback(GLFWwindow* /*window*/, int w, int h) +{ + if (w && h && _resolutionPolicy != ResolutionPolicy::UNKNOWN) + { + /* Invoke `GLView::setFrameSize` to sync screen size immediately, + this->setFrameSize will invoke `glfwSetWindowSize` which is unnecessary. + */ + GLView::setFrameSize(w, h); + + /* + x-studio spec, fix view size incorrect when window size changed. + The original code behavior: + 1. first time enter full screen: w,h=1920,1080 + 2. second or later enter full screen: will trigger 2 times WindowSizeCallback + 1). w,h=976,679 + 2). w,h=1024,768 + + @remark: + 1. we should use glfwSetWindowMonitor to control the window size in full screen mode + @see also: updateWindowSize (call after enter/exit full screen mode) + */ + updateDesignResolutionSize(); + +#if defined(AX_USE_METAL) + // update metal attachment texture size. + int fbWidth, fbHeight; + glfwGetFramebufferSize(_mainWindow, &fbWidth, &fbHeight); + backend::UtilsMTL::resizeDefaultAttachmentTexture(fbWidth, fbHeight); +#endif + + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_RESIZED, nullptr); + } +} + +void GLViewImpl::onGLFWWindowIconifyCallback(GLFWwindow* /*window*/, int iconified) +{ + if (iconified == GL_TRUE) + { + Application::getInstance()->applicationDidEnterBackground(); + } + else + { + Application::getInstance()->applicationWillEnterForeground(); + } +} + +void GLViewImpl::onGLFWWindowFocusCallback(GLFWwindow* /*window*/, int focused) +{ + if (focused == GL_TRUE) + { + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_FOCUSED, nullptr); + } + else + { + Director::getInstance()->getEventDispatcher()->dispatchCustomEvent(GLViewImpl::EVENT_WINDOW_UNFOCUSED, nullptr); + } +} + +#if (AX_TARGET_PLATFORM != AX_PLATFORM_MAC) +static bool loadFboExtensions() +{ + const char* gl_extensions = (const char*)glGetString(GL_EXTENSIONS); + + // If the current opengl driver doesn't have framebuffers methods, check if an extension exists + if (glGenFramebuffers == nullptr) + { + log("OpenGL: glGenFramebuffers is nullptr, try to detect an extension"); + if (strstr(gl_extensions, "ARB_framebuffer_object")) + { + log("OpenGL: ARB_framebuffer_object is supported"); + + glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)glfwGetProcAddress("glIsRenderbuffer"); + glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)glfwGetProcAddress("glBindRenderbuffer"); + glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)glfwGetProcAddress("glDeleteRenderbuffers"); + glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)glfwGetProcAddress("glGenRenderbuffers"); + glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)glfwGetProcAddress("glRenderbufferStorage"); + glGetRenderbufferParameteriv = + (PFNGLGETRENDERBUFFERPARAMETERIVPROC)glfwGetProcAddress("glGetRenderbufferParameteriv"); + glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)glfwGetProcAddress("glIsFramebuffer"); + glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)glfwGetProcAddress("glBindFramebuffer"); + glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)glfwGetProcAddress("glDeleteFramebuffers"); + glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)glfwGetProcAddress("glGenFramebuffers"); + glCheckFramebufferStatus = (PFNGLCHECKFRAMEBUFFERSTATUSPROC)glfwGetProcAddress("glCheckFramebufferStatus"); + glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)glfwGetProcAddress("glFramebufferTexture1D"); + glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)glfwGetProcAddress("glFramebufferTexture2D"); + glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)glfwGetProcAddress("glFramebufferTexture3D"); + glFramebufferRenderbuffer = + (PFNGLFRAMEBUFFERRENDERBUFFERPROC)glfwGetProcAddress("glFramebufferRenderbuffer"); + glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)glfwGetProcAddress( + "glGetFramebufferAttachmentParameteriv"); + glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)glfwGetProcAddress("glGenerateMipmap"); + } + else if (strstr(gl_extensions, "EXT_framebuffer_object")) + { + log("OpenGL: EXT_framebuffer_object is supported"); + glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)glfwGetProcAddress("glIsRenderbufferEXT"); + glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)glfwGetProcAddress("glBindRenderbufferEXT"); + glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)glfwGetProcAddress("glDeleteRenderbuffersEXT"); + glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)glfwGetProcAddress("glGenRenderbuffersEXT"); + glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)glfwGetProcAddress("glRenderbufferStorageEXT"); + glGetRenderbufferParameteriv = + (PFNGLGETRENDERBUFFERPARAMETERIVPROC)glfwGetProcAddress("glGetRenderbufferParameterivEXT"); + glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)glfwGetProcAddress("glIsFramebufferEXT"); + glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)glfwGetProcAddress("glBindFramebufferEXT"); + glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)glfwGetProcAddress("glDeleteFramebuffersEXT"); + glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)glfwGetProcAddress("glGenFramebuffersEXT"); + glCheckFramebufferStatus = + (PFNGLCHECKFRAMEBUFFERSTATUSPROC)glfwGetProcAddress("glCheckFramebufferStatusEXT"); + glFramebufferTexture1D = (PFNGLFRAMEBUFFERTEXTURE1DPROC)glfwGetProcAddress("glFramebufferTexture1DEXT"); + glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)glfwGetProcAddress("glFramebufferTexture2DEXT"); + glFramebufferTexture3D = (PFNGLFRAMEBUFFERTEXTURE3DPROC)glfwGetProcAddress("glFramebufferTexture3DEXT"); + glFramebufferRenderbuffer = + (PFNGLFRAMEBUFFERRENDERBUFFERPROC)glfwGetProcAddress("glFramebufferRenderbufferEXT"); + glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)glfwGetProcAddress( + "glGetFramebufferAttachmentParameterivEXT"); + glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)glfwGetProcAddress("glGenerateMipmapEXT"); + } + else if (strstr(gl_extensions, "GL_ANGLE_framebuffer_blit")) + { + log("OpenGL: GL_ANGLE_framebuffer_object is supported"); + + glIsRenderbuffer = (PFNGLISRENDERBUFFERPROC)glfwGetProcAddress("glIsRenderbufferOES"); + glBindRenderbuffer = (PFNGLBINDRENDERBUFFERPROC)glfwGetProcAddress("glBindRenderbufferOES"); + glDeleteRenderbuffers = (PFNGLDELETERENDERBUFFERSPROC)glfwGetProcAddress("glDeleteRenderbuffersOES"); + glGenRenderbuffers = (PFNGLGENRENDERBUFFERSPROC)glfwGetProcAddress("glGenRenderbuffersOES"); + glRenderbufferStorage = (PFNGLRENDERBUFFERSTORAGEPROC)glfwGetProcAddress("glRenderbufferStorageOES"); + // glGetRenderbufferParameteriv = + // (PFNGLGETRENDERBUFFERPARAMETERIVPROC)glfwGetProcAddress("glGetRenderbufferParameterivOES"); + glIsFramebuffer = (PFNGLISFRAMEBUFFERPROC)glfwGetProcAddress("glIsFramebufferOES"); + glBindFramebuffer = (PFNGLBINDFRAMEBUFFERPROC)glfwGetProcAddress("glBindFramebufferOES"); + glDeleteFramebuffers = (PFNGLDELETEFRAMEBUFFERSPROC)glfwGetProcAddress("glDeleteFramebuffersOES"); + glGenFramebuffers = (PFNGLGENFRAMEBUFFERSPROC)glfwGetProcAddress("glGenFramebuffersOES"); + glCheckFramebufferStatus = + (PFNGLCHECKFRAMEBUFFERSTATUSPROC)glfwGetProcAddress("glCheckFramebufferStatusOES"); + glFramebufferRenderbuffer = + (PFNGLFRAMEBUFFERRENDERBUFFERPROC)glfwGetProcAddress("glFramebufferRenderbufferOES"); + glFramebufferTexture2D = (PFNGLFRAMEBUFFERTEXTURE2DPROC)glfwGetProcAddress("glFramebufferTexture2DOES"); + glGetFramebufferAttachmentParameteriv = (PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC)glfwGetProcAddress( + "glGetFramebufferAttachmentParameterivOES"); + glGenerateMipmap = (PFNGLGENERATEMIPMAPPROC)glfwGetProcAddress("glGenerateMipmapOES"); + } + else + { + log("OpenGL: No framebuffers extension is supported"); + log("OpenGL: Any call to Fbo will crash!"); + return false; + } + } + return true; +} + +// helper +bool GLViewImpl::loadGL() +{ +# if (AX_TARGET_PLATFORM != AX_PLATFORM_MAC) + + // glad: load all OpenGL function pointers + // --------------------------------------- +# if !defined(AX_USE_GLES) + if (!gladLoadGL(glfwGetProcAddress)) + { + log("glad: Failed to Load GL"); + return false; + } +# else + if (!gladLoadGLES2(glfwGetProcAddress)) + { + log("glad: Failed to Load GLES2"); + return false; + } +# endif + + loadFboExtensions(); + +# endif // (AX_TARGET_PLATFORM != AX_PLATFORM_MAC) + + return true; +} + +#endif + +NS_AX_END // end of namespace ax; diff --git a/core/platform/emscripten/GLViewImpl-emscripten.h b/core/platform/emscripten/GLViewImpl-emscripten.h new file mode 100644 index 000000000000..eb490e9f4d84 --- /dev/null +++ b/core/platform/emscripten/GLViewImpl-emscripten.h @@ -0,0 +1,186 @@ +/**************************************************************************** +Copyright (c) 2010-2012 cocos2d-x.org +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +https://axmolengine.github.io/ + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#pragma once +#include "platform/GL.h" +#include "base/Ref.h" +#include "platform/Common.h" +#include "platform/GLView.h" +#include + +NS_AX_BEGIN + +class GLFWEventHandler; +class AX_DLL GLViewImpl : public GLView +{ + friend class GLFWEventHandler; + +public: + static GLViewImpl* create(std::string_view viewName); + static GLViewImpl* create(std::string_view viewName, bool resizable); + static GLViewImpl* createWithRect(std::string_view viewName, + const Rect& size, + float frameZoomFactor = 1.0f, + bool resizable = false); + static GLViewImpl* createWithFullScreen(std::string_view viewName); + static GLViewImpl* createWithFullScreen(std::string_view viewName, + const GLFWvidmode& videoMode, + GLFWmonitor* monitor); + + /* + *frameZoomFactor for frame. This method is for debugging big resolution (e.g.new ipad) app on desktop. + */ + + // void resize(int width, int height); + + float getFrameZoomFactor() const override; + // void centerWindow(); + + virtual void setViewPortInPoints(float x, float y, float w, float h) override; + virtual void setScissorInPoints(float x, float y, float w, float h) override; + virtual Rect getScissorRect() const override; + + bool windowShouldClose() override; + void pollEvents() override; + GLFWwindow* getWindow() const { return _mainWindow; } + + bool isFullscreen() const; + + /* Sets primary monitor full screen with default w*h(refresh rate) */ + void setFullscreen(); + /* Sets primary monitor full screen with w*h(refresh rate) */ + void setFullscreen(int w, int h, int refreshRate); + + /* Sets monitor full screen with default w*h(refresh rate) */ + void setFullscreen(int monitorIndex); + /// + /// Sets monitor full screen with w*h(refresh rate) + /// + /// the 0 based index of monitor + /// the width of hardware resolution in full screen, -1 use default value + /// the height of hardware resolution in full screen, -1 use default value + /// the display refresh rate, usually 60, -1 use default value + void setFullscreen(int monitorIndex, int w, int h, int refreshRate); + + /* for internal use */ + void setFullscreen(GLFWmonitor* monitor, int w, int h, int refreshRate); + void setWindowed(int width, int height); + + int getMonitorCount() const; + Vec2 getMonitorSize() const; + + /* override functions */ + virtual bool isOpenGLReady() override; + virtual void end() override; + virtual void swapBuffers() override; + virtual void setFrameSize(float width, float height) override; + virtual void setIMEKeyboardState(bool bOpen) override; + +#if AX_ICON_SET_SUPPORT + virtual void setIcon(std::string_view filename) const override; + virtual void setIcon(const std::vector& filelist) const override; + virtual void setDefaultIcon() const override; +#endif /* AX_ICON_SET_SUPPORT */ + + /* + * Set zoom factor for frame. This method is for debugging big resolution (e.g.new ipad) app on desktop. + */ + void setFrameZoomFactor(float zoomFactor) override; + /** + * Hide or Show the mouse cursor if there is one. + */ + virtual void setCursorVisible(bool isVisible) override; + /** Retina support is disabled by default + * @note This method is only available on Mac. + */ + void enableRetina(bool enabled); + /** Check whether retina display is enabled. */ + bool isRetinaEnabled() const { return _isRetinaEnabled; }; + + /** Get retina factor */ + int getRetinaFactor() const override { return _retinaFactor; } + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) + HWND getWin32Window() override; +#endif /* (AX_TARGET_PLATFORM == AX_PLATFORM_WIN32) */ + +#if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + void* getCocoaWindow() override; + void* getNSGLContext() override; // stevetranby: added +#endif // #if (AX_TARGET_PLATFORM == AX_PLATFORM_MAC) + +protected: + GLViewImpl(bool initglfw = true); + virtual ~GLViewImpl(); + + bool initWithRect(std::string_view viewName, const Rect& rect, float frameZoomFactor, bool resizable); + bool initWithFullScreen(std::string_view viewName); + bool initWithFullscreen(std::string_view viewname, const GLFWvidmode& videoMode, GLFWmonitor* monitor); +#if (AX_TARGET_PLATFORM != AX_PLATFORM_MAC) // Windows, Linux: use glad to loadGL + bool loadGL(); +#endif + /* update frame layout when enter/exit full screen mode */ + void updateWindowSize(); + + void updateFrameSize(); + + // GLFW callbacks + void onGLFWError(int errorID, const char* errorDesc); + void onGLFWMouseCallBack(GLFWwindow* window, int button, int action, int modify); + void onGLFWMouseMoveCallBack(GLFWwindow* window, double x, double y); + void onGLFWMouseScrollCallback(GLFWwindow* window, double x, double y); + void onGLFWKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); + void onGLFWCharCallback(GLFWwindow* window, unsigned int character); + void onGLFWWindowPosCallback(GLFWwindow* windows, int x, int y); + void onGLFWWindowSizeCallback(GLFWwindow* window, int width, int height); + void onGLFWWindowIconifyCallback(GLFWwindow* window, int iconified); + void onGLFWWindowFocusCallback(GLFWwindow* window, int focused); + + bool _captured; + bool _isInRetinaMonitor; + bool _isRetinaEnabled; + int _retinaFactor; // Should be 1 or 2 + + float _frameZoomFactor; + + GLFWwindow* _mainWindow; + GLFWmonitor* _monitor; + + std::string _glfwError; + + float _mouseX; + float _mouseY; + +public: + // View will trigger an event when window is resized, gains or loses focus + static const std::string EVENT_WINDOW_RESIZED; + static const std::string EVENT_WINDOW_FOCUSED; + static const std::string EVENT_WINDOW_UNFOCUSED; + +private: + AX_DISALLOW_COPY_AND_ASSIGN(GLViewImpl); +}; + +NS_AX_END // end of namespace cocos2d diff --git a/core/platform/emscripten/PlatformDefine-emscripten.h b/core/platform/emscripten/PlatformDefine-emscripten.h new file mode 100644 index 000000000000..d8b222d198f0 --- /dev/null +++ b/core/platform/emscripten/PlatformDefine-emscripten.h @@ -0,0 +1,43 @@ +/**************************************************************************** +Copyright (c) 2011 Laschweinski +Copyright (c) 2013-2016 Chukong Technologies Inc. +Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#pragma once + +#include + +#define AX_DLL + +#include +#define AX_ASSERT(cond) assert(cond) +#define AX_UNUSED_PARAM(unusedparam) (void)unusedparam + +/* Define NULL pointer value */ +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif diff --git a/core/platform/emscripten/StdC-emscripten.h b/core/platform/emscripten/StdC-emscripten.h new file mode 100644 index 000000000000..1faa113e04ed --- /dev/null +++ b/core/platform/emscripten/StdC-emscripten.h @@ -0,0 +1,49 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef __AX_STD_C_H__ +#define __AX_STD_C_H__ + +#include "platform/PlatformConfig.h" +#include "platform/PlatformMacros.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(x,y) (((x) > (y)) ? (y) : (x)) +#endif // MIN + +#ifndef MAX +#define MAX(x,y) (((x) < (y)) ? (y) : (x)) +#endif // MAX + +#endif // __AX_STD_C_H__ + diff --git a/core/platform/emscripten/devtools-emscripten.cpp b/core/platform/emscripten/devtools-emscripten.cpp new file mode 100644 index 000000000000..f12538017fc9 --- /dev/null +++ b/core/platform/emscripten/devtools-emscripten.cpp @@ -0,0 +1,70 @@ +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "devtools-emscripten.h" +#include +#include "base/UTF8.h" + +using namespace std; +using namespace cocos2d; + +DevToolsImpl::DevToolsImpl() +{ + _director = Director::getInstance(); + _scheduler = _director->getScheduler(); + _tick = 0; +} + +void DevToolsImpl::update(float /*dt*/) +{ + // tick for 2 frames becuase delta time of the 1st frame after resume is forced to 0 + _tick ++; + if (_tick >= 2) + { + _director->pause(); + _scheduler->unscheduleUpdate(this); + } +} + +void DevToolsImpl::step() +{ + _director->resume(); + _tick = 0; + _scheduler->scheduleUpdate(this, 0, false); +} + +void DevToolsImpl::pause() +{ + _director->pause(); +} + +void DevToolsImpl::resume() +{ + _director->resume(); +} + +DevToolsImpl* DevToolsImpl::getInstance() +{ + static DevToolsImpl instance; + return &instance; +} + +extern "C" +{ + void cocos_ccdirector_pause() + { + DevToolsImpl::getInstance()->pause(); + } + + void cocos_ccdirector_resume() + { + DevToolsImpl::getInstance()->resume(); + } + + void cocos_ccdirector_step() + { + DevToolsImpl::getInstance()->step(); + } +} + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN diff --git a/core/platform/emscripten/devtools-emscripten.h b/core/platform/emscripten/devtools-emscripten.h new file mode 100644 index 000000000000..0ad97453b2a3 --- /dev/null +++ b/core/platform/emscripten/devtools-emscripten.h @@ -0,0 +1,34 @@ +#ifndef __DEVTOOLS_EMSCRIPTEN_H__ +#define __DEVTOOLS_EMSCRIPTEN_H__ + +#include "platform/PlatformConfig.h" +#if AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN + +#include "base/CCScheduler.h" +#include "base/CCDirector.h" + +using namespace cocos2d; + +class DevToolsImpl +{ +public: + DevToolsImpl(); + + void update(float /*dt*/); + + void step(); + + void pause(); + + void resume(); + + static DevToolsImpl* getInstance(); + +private: + unsigned int _tick; + Scheduler* _scheduler; + Director* _director; +}; + +#endif // AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN +#endif // __DEVTOOLS_EMSCRIPTEN_H__ \ No newline at end of file diff --git a/core/renderer/CMakeLists.txt b/core/renderer/CMakeLists.txt index cac1964d0aa9..5ccd67ad3b69 100644 --- a/core/renderer/CMakeLists.txt +++ b/core/renderer/CMakeLists.txt @@ -82,7 +82,7 @@ set(_AX_RENDERER_SRC renderer/backend/RenderPassDescriptor.cpp ) -if(ANDROID OR WINDOWS OR LINUX OR AX_USE_ANGLE) +if(ANDROID OR WINDOWS OR LINUX OR EMSCRIPTEN OR AX_USE_ANGLE) list(APPEND _AX_RENDERER_HEADER renderer/backend/opengl/BufferGL.h diff --git a/core/renderer/RenderConsts.h b/core/renderer/RenderConsts.h new file mode 100644 index 000000000000..eb3c10f72b16 --- /dev/null +++ b/core/renderer/RenderConsts.h @@ -0,0 +1,11 @@ +#pragma once + +/* The max directional lights */ +#define AX_MAX_DIRECTIONAL_LIGHT 1 + +/* The max point lights */ +#define AX_MAX_POINT_LIGHT 1 + +/* The max spot lights */ +#define AX_MAX_SPOT_LIGHT 1 + diff --git a/core/ui/CMakeLists.txt b/core/ui/CMakeLists.txt index faf92ae99c63..c65f972c966b 100644 --- a/core/ui/CMakeLists.txt +++ b/core/ui/CMakeLists.txt @@ -103,6 +103,10 @@ elseif(LINUX) set(_AX_UI_SPECIFIC_HEADER ui/UIMediaPlayer.h ${_AX_UI_SPECIFIC_HEADER}) set(_AX_UI_SPECIFIC_SRC ui/UIMediaPlayer.cpp ${_AX_UI_SPECIFIC_SRC}) endif() +elseif(EMSCRIPTEN) + set(_AX_UI_SPECIFIC_SRC + ui/UIEditBox/UIEditBoxImpl-stub.cpp + ) elseif(ANDROID) set(_AX_UI_SPECIFIC_HEADER ui/UIWebView/UIWebView.h diff --git a/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.cpp b/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.cpp index ee7e8d83c831..fddf653e6e93 100644 --- a/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.cpp +++ b/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.cpp @@ -1,5 +1,5 @@ #include "scripting/lua-bindings/auto/axlua_audioengine_auto.hpp" -#if AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || AX_TARGET_PLATFORM == AX_PLATFORM_MAC || defined(_WIN32) || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX +#if AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || AX_TARGET_PLATFORM == AX_PLATFORM_MAC || defined(_WIN32) || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN #include "audio/AudioEngine.h" #include "scripting/lua-bindings/manual/tolua_fix.h" #include "scripting/lua-bindings/manual/LuaBasicConversions.h" diff --git a/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.hpp b/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.hpp index e2452f761576..472209a05315 100644 --- a/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.hpp +++ b/extensions/scripting/lua-bindings/auto/axlua_audioengine_auto.hpp @@ -1,5 +1,5 @@ #include "base/Config.h" -#if AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || AX_TARGET_PLATFORM == AX_PLATFORM_MAC || defined(_WIN32) || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX +#if AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS || AX_TARGET_PLATFORM == AX_PLATFORM_MAC || defined(_WIN32) || AX_TARGET_PLATFORM == AX_PLATFORM_LINUX || AX_TARGET_PLATFORM == AX_PLATFORM_EMSCRIPTEN #ifndef __ax_audioengine_h__ #define __ax_audioengine_h__ diff --git a/extensions/scripting/lua-bindings/manual/cocostudio/CustomGUIReader.cpp b/extensions/scripting/lua-bindings/manual/cocostudio/CustomGUIReader.cpp index 498dc7abfb15..fe79375f5bbb 100644 --- a/extensions/scripting/lua-bindings/manual/cocostudio/CustomGUIReader.cpp +++ b/extensions/scripting/lua-bindings/manual/cocostudio/CustomGUIReader.cpp @@ -22,8 +22,8 @@ THE SOFTWARE. ****************************************************************************/ -#include "scripting/lua-bindings/manual/cocostudio/CustomGUIReader.h" #include "scripting/lua-bindings/manual/LuaEngine.h" +#include "scripting/lua-bindings/manual/cocostudio/CustomGUIReader.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" diff --git a/templates/cpp-template-default/CMakeLists.txt b/templates/cpp-template-default/CMakeLists.txt index bab7390a28be..c776d667841e 100644 --- a/templates/cpp-template-default/CMakeLists.txt +++ b/templates/cpp-template-default/CMakeLists.txt @@ -94,6 +94,11 @@ elseif(LINUX) proj.linux/main.cpp ) list(APPEND GAME_SOURCE ${common_content_files}) +elseif(EMSCRIPTEN) + list(APPEND GAME_SOURCE + proj.emscripten/main.cpp + ) + list(APPEND GAME_SOURCE ${common_content_files}) elseif(WINDOWS) if(NOT WINRT) list(APPEND GAME_HEADER @@ -227,3 +232,29 @@ endif() if (NOT DEFINED BUILD_ENGINE_DONE) ax_uwp_set_all_targets_deploy_min_version() endif() + +set(GAME_RES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/Content") + +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") + set(CMAKE_CXX_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") +endif() + +if(EMSCRIPTEN) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + target_link_options(${APP_NAME} PRIVATE + "-sEXPORTED_FUNCTIONS=[_main]" + "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap]" + ) + set(EMSCRIPTEN_LINK_FLAGS "-lidbfs.js -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s INITIAL_MEMORY=512MB --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/index.html --use-preload-cache") + # Disable wasm, generate js build + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -s WASM=0") + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -s SEPARATE_DWARF_URL=https://xxx:8080/axmolwasm/axmolwasm/build/HelloLua.debug.wasm") + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -gseparate-dwarf=HelloLua.debug.wasm") + + foreach(FOLDER IN LISTS GAME_RES_FOLDER) + string(APPEND EMSCRIPTEN_LINK_FLAGS " --preload-file ${FOLDER}/@/") + endforeach() + + set_target_properties(${APP_NAME} PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS}") +endif() \ No newline at end of file diff --git a/templates/cpp-template-default/index.html b/templates/cpp-template-default/index.html new file mode 100644 index 000000000000..dc5d71b2d949 --- /dev/null +++ b/templates/cpp-template-default/index.html @@ -0,0 +1,150 @@ + + + + + + Emscripten-Generated Code + + + +
+
Downloading...
+
+ +
+ +
+
+ Resize canvas + Lock/hide mouse pointer +     + + | + + + +
+ +
+ +
+ + + + {{{ SCRIPT }}} + + diff --git a/templates/cpp-template-default/proj.emscripten/main.cpp b/templates/cpp-template-default/proj.emscripten/main.cpp new file mode 100644 index 000000000000..b265379f11bc --- /dev/null +++ b/templates/cpp-template-default/proj.emscripten/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + https://axmolengine.github.io/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "AppDelegate.h" +#include "cocos2d.h" + +#include +#include +#include +#include + +USING_NS_AX; + +int main(int argc, char** argv) +{ + // create the application instance + AppDelegate app; + return Application::getInstance()->run(); +} diff --git a/templates/lua-template-default/CMakeLists.txt b/templates/lua-template-default/CMakeLists.txt index 5c1c16e40b07..2d3cdfeb81a1 100644 --- a/templates/lua-template-default/CMakeLists.txt +++ b/templates/lua-template-default/CMakeLists.txt @@ -107,6 +107,11 @@ elseif(LINUX) proj.linux/main.cpp ) list(APPEND GAME_SOURCE ${common_content_files}) +elseif(EMSCRIPTEN) + list(APPEND GAME_SOURCE + proj.emscripten/main.cpp + ) + list(APPEND GAME_SOURCE ${common_content_files}) elseif(WINDOWS) if(NOT WINRT) list(APPEND GAME_HEADER @@ -253,3 +258,29 @@ endif() if (NOT DEFINED BUILD_ENGINE_DONE) ax_uwp_set_all_targets_deploy_min_version() endif() + +set(GAME_RES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/Content") + +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") + set(CMAKE_CXX_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") +endif() + +if(EMSCRIPTEN) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + target_link_options(${APP_NAME} PRIVATE + "-sEXPORTED_FUNCTIONS=[_main]" + "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap]" + ) + set(EMSCRIPTEN_LINK_FLAGS "-lidbfs.js -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s INITIAL_MEMORY=512MB --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/index.html --use-preload-cache") + # Disable wasm, generate js build + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -s WASM=0") + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -s SEPARATE_DWARF_URL=https://xxx:8080/axmolwasm/axmolwasm/build/HelloLua.debug.wasm") + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -gseparate-dwarf=HelloLua.debug.wasm") + + foreach(FOLDER IN LISTS GAME_RES_FOLDER) + string(APPEND EMSCRIPTEN_LINK_FLAGS " --preload-file ${FOLDER}/@/") + endforeach() + + set_target_properties(${APP_NAME} PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS}") +endif() \ No newline at end of file diff --git a/templates/lua-template-default/index.html b/templates/lua-template-default/index.html new file mode 100644 index 000000000000..dc5d71b2d949 --- /dev/null +++ b/templates/lua-template-default/index.html @@ -0,0 +1,150 @@ + + + + + + Emscripten-Generated Code + + + +
+
Downloading...
+
+ +
+ +
+
+ Resize canvas + Lock/hide mouse pointer +     + + | + + + +
+ +
+ +
+ + + + {{{ SCRIPT }}} + + diff --git a/templates/lua-template-default/proj.emscripten/main.cpp b/templates/lua-template-default/proj.emscripten/main.cpp new file mode 100644 index 000000000000..b265379f11bc --- /dev/null +++ b/templates/lua-template-default/proj.emscripten/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + https://axmolengine.github.io/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "AppDelegate.h" +#include "cocos2d.h" + +#include +#include +#include +#include + +USING_NS_AX; + +int main(int argc, char** argv) +{ + // create the application instance + AppDelegate app; + return Application::getInstance()->run(); +} diff --git a/tests/cpp-tests/CMakeLists.txt b/tests/cpp-tests/CMakeLists.txt index 68db3d55c0f2..1728b8c8da71 100644 --- a/tests/cpp-tests/CMakeLists.txt +++ b/tests/cpp-tests/CMakeLists.txt @@ -62,6 +62,11 @@ elseif(LINUX) proj.linux/main.cpp ) list(APPEND GAME_SOURCE ${common_content_files}) +elseif(EMSCRIPTEN) + list(APPEND GAME_SOURCE + proj.emscripten/main.cpp + ) + list(APPEND GAME_SOURCE ${common_content_files}) elseif(WINDOWS) if(NOT WINRT) list(APPEND GAME_HEADER @@ -610,3 +615,27 @@ endif() if (NOT DEFINED BUILD_ENGINE_DONE) ax_uwp_set_all_targets_deploy_min_version() endif() + +set(GAME_RES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/Content") + +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") + set(CMAKE_CXX_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") +endif() + +if(EMSCRIPTEN) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + target_link_options(${APP_NAME} PRIVATE + "-sEXPORTED_FUNCTIONS=[_main]" + "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap]" + ) + set(EMSCRIPTEN_LINK_FLAGS "-lidbfs.js -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s INITIAL_MEMORY=512MB --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/index.html --use-preload-cache") + # Disable wasm, generate js build + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -s WASM=0") + + foreach(FOLDER IN LISTS GAME_RES_FOLDER) + string(APPEND EMSCRIPTEN_LINK_FLAGS " --preload-file ${FOLDER}/@/") + endforeach() + + set_target_properties(${APP_NAME} PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS}") +endif() diff --git a/tests/cpp-tests/Source/CurlTest/CurlTest.cpp b/tests/cpp-tests/Source/CurlTest/CurlTest.cpp index a9c3d5bb3bcd..edee52deb84f 100644 --- a/tests/cpp-tests/Source/CurlTest/CurlTest.cpp +++ b/tests/cpp-tests/Source/CurlTest/CurlTest.cpp @@ -22,6 +22,8 @@ THE SOFTWARE. ****************************************************************************/ +#if (AX_TARGET_PLATFORM != AX_PLATFORM_EMSCRIPTEN) + #include "platform/PlatformConfig.h" #include "CurlTest.h" #include "stdio.h" @@ -125,3 +127,5 @@ CurlTest::~CurlTest() { _label->release(); } + +#endif \ No newline at end of file diff --git a/tests/cpp-tests/Source/CurlTest/CurlTest.h b/tests/cpp-tests/Source/CurlTest/CurlTest.h index dc537d85bf0e..2015bdd5fc41 100644 --- a/tests/cpp-tests/Source/CurlTest/CurlTest.h +++ b/tests/cpp-tests/Source/CurlTest/CurlTest.h @@ -25,6 +25,8 @@ #ifndef _CURL_TEST_H_ #define _CURL_TEST_H_ +#if (AX_TARGET_PLATFORM != AX_PLATFORM_EMSCRIPTEN) + #include "axmol.h" #include "../BaseTest.h" @@ -44,4 +46,6 @@ class CurlTest : public TestCase ax::Label* _label; }; +#endif + #endif // _CURL_TEST_H_ diff --git a/tests/cpp-tests/Source/FileUtilsTest/FileUtilsTest.cpp b/tests/cpp-tests/Source/FileUtilsTest/FileUtilsTest.cpp index 194b84d7fa0b..ab60928ff673 100644 --- a/tests/cpp-tests/Source/FileUtilsTest/FileUtilsTest.cpp +++ b/tests/cpp-tests/Source/FileUtilsTest/FileUtilsTest.cpp @@ -63,7 +63,7 @@ void TestSearchPath::onEnter() std::string writablePath = sharedFileUtils->getWritablePath(); std::string fileName = writablePath + "external.txt"; char szBuf[100] = "Hello Cocos2d-x!"; - FILE* fp = fopen(fileName.c_str(), "wb"); + auto fp = fopen(fileName.c_str(), "wb"); if (fp) { size_t ret = fwrite(szBuf, 1, strlen(szBuf), fp); @@ -258,7 +258,7 @@ void TestFileFuncs::onEnter() std::string content = "Test string content to put into created file"; std::string msg; - FILE* out = fopen(filepath.c_str(), "w"); + auto out = fopen(filepath.c_str(), "w"); fputs(content.c_str(), out); fclose(out); @@ -1002,7 +1002,7 @@ void TestFileFuncsAsync::onEnter() std::string content = "Test string content to put into created file"; std::string msg; - FILE* out = fopen(filepath.c_str(), "w"); + auto out = fopen(filepath.c_str(), "w"); fputs(content.c_str(), out); fclose(out); diff --git a/tests/cpp-tests/Source/NewAudioEngineTest/NewAudioEngineTest.cpp b/tests/cpp-tests/Source/NewAudioEngineTest/NewAudioEngineTest.cpp index e984ed7c4a42..b4d7e1c80fe0 100644 --- a/tests/cpp-tests/Source/NewAudioEngineTest/NewAudioEngineTest.cpp +++ b/tests/cpp-tests/Source/NewAudioEngineTest/NewAudioEngineTest.cpp @@ -1128,7 +1128,7 @@ void AudioPlayFileInWritablePath::onEnter() if (!fileUtils->isFileExist(saveFilePath)) { Data data = fileUtils->getDataFromFile(musicFile); - FILE* fp = fopen(saveFilePath.c_str(), "wb"); + auto fp = fopen(saveFilePath.c_str(), "wb"); if (fp != nullptr) { fwrite(data.getBytes(), data.getSize(), 1, fp); diff --git a/tests/cpp-tests/Source/UITest/CocoStudioGUITest/CocosGUIScene.cpp b/tests/cpp-tests/Source/UITest/CocoStudioGUITest/CocosGUIScene.cpp index 64484ffcb474..e552844daff6 100644 --- a/tests/cpp-tests/Source/UITest/CocoStudioGUITest/CocosGUIScene.cpp +++ b/tests/cpp-tests/Source/UITest/CocoStudioGUITest/CocosGUIScene.cpp @@ -62,7 +62,7 @@ GUIDynamicCreateTests::GUIDynamicCreateTests() { -#if AX_TARGET_PLATFORM != AX_PLATFORM_LINUX || defined(AX_ENABLE_VLC_MEDIA) +#if (AX_TARGET_PLATFORM != AX_PLATFORM_LINUX && AX_TARGET_PLATFORM != AX_PLATFORM_EMSCRIPTEN) || defined(AX_ENABLE_VLC_MEDIA) addTest("VideoPlayer Test", []() { return new VideoPlayerTests; }); #endif #if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID || AX_TARGET_PLATFORM == AX_PLATFORM_IOS) && \ diff --git a/tests/cpp-tests/Source/controller.cpp b/tests/cpp-tests/Source/controller.cpp index f192999b7ac9..89a1baa04737 100644 --- a/tests/cpp-tests/Source/controller.cpp +++ b/tests/cpp-tests/Source/controller.cpp @@ -70,7 +70,9 @@ class RootTests : public TestList addTest("Click and Move", []() { return new ClickAndMoveTest(); }); addTest("Configuration", []() { return new ConfigurationTests(); }); addTest("Console", []() { return new ConsoleTests(); }); +#if !defined(AX_PLATFORM_EMSCRIPTEN) addTest("Curl", []() { return new CurlTests(); }); +#endif addTest("Current Language", []() { return new CurrentLanguageTests(); }); addTest("Network Test", []() { return new NetworkTests(); }); addTest("EventDispatcher", []() { return new EventDispatcherTests(); }); diff --git a/tests/cpp-tests/index.html b/tests/cpp-tests/index.html new file mode 100644 index 000000000000..dc5d71b2d949 --- /dev/null +++ b/tests/cpp-tests/index.html @@ -0,0 +1,150 @@ + + + + + + Emscripten-Generated Code + + + +
+
Downloading...
+
+ +
+ +
+
+ Resize canvas + Lock/hide mouse pointer +     + + | + + + +
+ +
+ +
+ + + + {{{ SCRIPT }}} + + diff --git a/tests/cpp-tests/proj.emscripten/main.cpp b/tests/cpp-tests/proj.emscripten/main.cpp new file mode 100644 index 000000000000..b265379f11bc --- /dev/null +++ b/tests/cpp-tests/proj.emscripten/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + https://axmolengine.github.io/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "AppDelegate.h" +#include "cocos2d.h" + +#include +#include +#include +#include + +USING_NS_AX; + +int main(int argc, char** argv) +{ + // create the application instance + AppDelegate app; + return Application::getInstance()->run(); +} diff --git a/tests/fairygui-tests/CMakeLists.txt b/tests/fairygui-tests/CMakeLists.txt index ae85a52b7d3c..f7f8d30ddfd9 100644 --- a/tests/fairygui-tests/CMakeLists.txt +++ b/tests/fairygui-tests/CMakeLists.txt @@ -74,6 +74,11 @@ elseif(LINUX) proj.linux/main.cpp ) list(APPEND GAME_SOURCE ${common_content_files}) +elseif(EMSCRIPTEN) + list(APPEND GAME_SOURCE + proj.emscripten/main.cpp + ) + list(APPEND GAME_SOURCE ${common_content_files}) elseif(WINDOWS) if(NOT WINRT) list(APPEND GAME_HEADER @@ -190,3 +195,27 @@ endif() if (NOT DEFINED BUILD_ENGINE_DONE) ax_uwp_set_all_targets_deploy_min_version() endif() + +set(GAME_RES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/Content") + +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") + set(CMAKE_CXX_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") +endif() + +if(EMSCRIPTEN) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + target_link_options(${APP_NAME} PRIVATE + "-sEXPORTED_FUNCTIONS=[_main]" + "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap]" + ) + set(EMSCRIPTEN_LINK_FLAGS "-lidbfs.js -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s INITIAL_MEMORY=512MB --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/index.html --use-preload-cache") + # Disable wasm, generate js build + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -s WASM=0") + + foreach(FOLDER IN LISTS GAME_RES_FOLDER) + string(APPEND EMSCRIPTEN_LINK_FLAGS " --preload-file ${FOLDER}/@/") + endforeach() + + set_target_properties(${APP_NAME} PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS}") +endif() \ No newline at end of file diff --git a/tests/fairygui-tests/index.html b/tests/fairygui-tests/index.html new file mode 100644 index 000000000000..dc5d71b2d949 --- /dev/null +++ b/tests/fairygui-tests/index.html @@ -0,0 +1,150 @@ + + + + + + Emscripten-Generated Code + + + +
+
Downloading...
+
+ +
+ +
+
+ Resize canvas + Lock/hide mouse pointer +     + + | + + + +
+ +
+ +
+ + + + {{{ SCRIPT }}} + + diff --git a/tests/fairygui-tests/proj.emscripten/main.cpp b/tests/fairygui-tests/proj.emscripten/main.cpp new file mode 100644 index 000000000000..b265379f11bc --- /dev/null +++ b/tests/fairygui-tests/proj.emscripten/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + https://axmolengine.github.io/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "AppDelegate.h" +#include "cocos2d.h" + +#include +#include +#include +#include + +USING_NS_AX; + +int main(int argc, char** argv) +{ + // create the application instance + AppDelegate app; + return Application::getInstance()->run(); +} diff --git a/tests/live2d-tests/CMakeLists.txt b/tests/live2d-tests/CMakeLists.txt index b64fcb323837..bc380c232798 100644 --- a/tests/live2d-tests/CMakeLists.txt +++ b/tests/live2d-tests/CMakeLists.txt @@ -74,6 +74,11 @@ elseif(LINUX) proj.linux/main.cpp ) list(APPEND GAME_SOURCE ${common_content_files}) +elseif(EMSCRIPTEN) + list(APPEND GAME_SOURCE + proj.emscripten/main.cpp + ) + list(APPEND GAME_SOURCE ${common_content_files}) elseif(WINDOWS) if(NOT WINRT) list(APPEND GAME_HEADER diff --git a/tests/live2d-tests/index.html b/tests/live2d-tests/index.html new file mode 100644 index 000000000000..dc5d71b2d949 --- /dev/null +++ b/tests/live2d-tests/index.html @@ -0,0 +1,150 @@ + + + + + + Emscripten-Generated Code + + + +
+
Downloading...
+
+ +
+ +
+
+ Resize canvas + Lock/hide mouse pointer +     + + | + + + +
+ +
+ +
+ + + + {{{ SCRIPT }}} + + diff --git a/tests/live2d-tests/proj.emscripten/main.cpp b/tests/live2d-tests/proj.emscripten/main.cpp new file mode 100644 index 000000000000..b265379f11bc --- /dev/null +++ b/tests/live2d-tests/proj.emscripten/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + https://axmolengine.github.io/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "AppDelegate.h" +#include "cocos2d.h" + +#include +#include +#include +#include + +USING_NS_AX; + +int main(int argc, char** argv) +{ + // create the application instance + AppDelegate app; + return Application::getInstance()->run(); +} diff --git a/tests/lua-tests/CMakeLists.txt b/tests/lua-tests/CMakeLists.txt index 11fd2dbe5513..7be1ef48ff16 100644 --- a/tests/lua-tests/CMakeLists.txt +++ b/tests/lua-tests/CMakeLists.txt @@ -74,6 +74,11 @@ elseif(LINUX) proj.linux/main.cpp ) list(APPEND GAME_SOURCE ${common_content_files}) +elseif(EMSCRIPTEN) + list(APPEND GAME_SOURCE + proj.emscripten/main.cpp + ) + list(APPEND GAME_SOURCE ${common_content_files}) elseif(WINDOWS) if(NOT WINRT) list(APPEND GAME_HEADER @@ -234,3 +239,27 @@ endif() if (NOT DEFINED BUILD_ENGINE_DONE) ax_uwp_set_all_targets_deploy_min_version() endif() + +set(GAME_RES_FOLDER "${CMAKE_CURRENT_SOURCE_DIR}/Content") + +if(EMSCRIPTEN) + set(CMAKE_C_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") + set(CMAKE_CXX_FLAGS "-s FORCE_FILESYSTEM=1 -s FETCH=1 -s USE_GLFW=3 -s VERBOSE=1 -s USE_LIBJPEG=1 -s USE_LIBPNG=1 -s USE_ZLIB=1 -s USE_FREETYPE=1") +endif() + +if(EMSCRIPTEN) + set(CMAKE_EXECUTABLE_SUFFIX ".html") + target_link_options(${APP_NAME} PRIVATE + "-sEXPORTED_FUNCTIONS=[_main]" + "-sEXPORTED_RUNTIME_METHODS=[ccall,cwrap]" + ) + set(EMSCRIPTEN_LINK_FLAGS "-lidbfs.js -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2 -s INITIAL_MEMORY=512MB --shell-file ${CMAKE_CURRENT_SOURCE_DIR}/index.html --use-preload-cache") + # Disable wasm, generate js build + # string(APPEND EMSCRIPTEN_LINK_FLAGS " -s WASM=0") + + foreach(FOLDER IN LISTS GAME_RES_FOLDER) + string(APPEND EMSCRIPTEN_LINK_FLAGS " --preload-file ${FOLDER}/@/") + endforeach() + + set_target_properties(${APP_NAME} PROPERTIES LINK_FLAGS "${EMSCRIPTEN_LINK_FLAGS}") +endif() \ No newline at end of file diff --git a/tests/lua-tests/index.html b/tests/lua-tests/index.html new file mode 100644 index 000000000000..dc5d71b2d949 --- /dev/null +++ b/tests/lua-tests/index.html @@ -0,0 +1,150 @@ + + + + + + Emscripten-Generated Code + + + +
+
Downloading...
+
+ +
+ +
+
+ Resize canvas + Lock/hide mouse pointer +     + + | + + + +
+ +
+ +
+ + + + {{{ SCRIPT }}} + + diff --git a/tests/lua-tests/proj.emscripten/main.cpp b/tests/lua-tests/proj.emscripten/main.cpp new file mode 100644 index 000000000000..b265379f11bc --- /dev/null +++ b/tests/lua-tests/proj.emscripten/main.cpp @@ -0,0 +1,40 @@ +/**************************************************************************** + Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd. + + https://axmolengine.github.io/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#include "AppDelegate.h" +#include "cocos2d.h" + +#include +#include +#include +#include + +USING_NS_AX; + +int main(int argc, char** argv) +{ + // create the application instance + AppDelegate app; + return Application::getInstance()->run(); +} diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index ccff04cc15a2..3aab71746b88 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -184,6 +184,7 @@ if(ANDROID) add_subdirectory(android-specific/cpufeatures) endif() +if(NOT EMSCRIPTEN) add_subdirectory(zlib) target_link_libraries(thirdparty dep_zlib @@ -201,6 +202,7 @@ configure_target_outdir(png) set(PNG_PNG_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/png" CACHE STRING "png include dir" FORCE) set(PNG_LIBRARY "png" CACHE STRING "png include dir" FORCE) +endif(NOT EMSCRIPTEN) if(AX_WITH_BOX2D) set(BOX2D_BUILD_UNIT_TESTS OFF CACHE BOOL "Build the Box2D unit tests" FORCE) @@ -234,7 +236,7 @@ if(AX_WITH_CHIPMUNK) target_link_libraries(thirdparty chipmunk) configure_target_outdir(chipmunk) endif(AX_WITH_CHIPMUNK) -if(AX_WITH_FREETYPE) +if(AX_WITH_FREETYPE AND NOT EMSCRIPTEN) if (NOT LINUX) set(FT_WITH_ZLIB ON CACHE BOOL "Use system zlib instead of internal library." FORCE) set(DISABLE_FORCE_DEBUG_POSTFIX ON CACHE BOOL "" FORCE) @@ -250,7 +252,7 @@ if(AX_WITH_FREETYPE) endif() endif() target_include_directories(thirdparty PUBLIC "freetype/include") -endif(AX_WITH_FREETYPE) +endif(AX_WITH_FREETYPE AND NOT EMSCRIPTEN) if(AX_WITH_RECAST) add_subdirectory(recast) target_link_libraries(thirdparty recast) @@ -261,10 +263,14 @@ if(AX_WITH_BULLET) target_link_libraries(thirdparty bullet) configure_target_outdir(bullet) endif(AX_WITH_BULLET) + +if(NOT EMSCRIPTEN) if(AX_WITH_JPEG AND NOT WINRT) add_subdirectory(jpeg-turbo) target_link_libraries(thirdparty dep_jpeg-turbo) endif() +endif(NOT EMSCRIPTEN) + if(AX_WITH_OPENSSL) add_subdirectory(openssl) if(ANDROID OR LINUX) @@ -275,11 +281,13 @@ if(AX_WITH_OPENSSL) endif() target_compile_definitions(thirdparty PUBLIC OPENSSL_SUPPRESS_DEPRECATED=1) endif(AX_WITH_OPENSSL) +if(NOT EMSCRIPTEN) if(AX_WITH_WEBP) add_subdirectory(webp) target_link_libraries(thirdparty webp) configure_target_outdir(webp) endif(AX_WITH_WEBP) +endif(NOT EMSCRIPTEN) if(AX_WITH_PUGIXML) add_subdirectory(pugixml) target_link_libraries(thirdparty pugixml) @@ -338,7 +346,7 @@ if(AX_ENABLE_EXT_LUA) target_compile_definitions(lua-cjson PRIVATE USING_LUAJIT=1) endif() endif() - +if(NOT EMSCRIPTEN) if(AX_WITH_CURL) add_subdirectory(curl) if(ANDROID OR LINUX) @@ -346,7 +354,7 @@ if(AX_WITH_CURL) endif() target_link_libraries(thirdparty libcurl) endif(AX_WITH_CURL) - +endif(NOT EMSCRIPTEN) # The openal-soft(LGPL 2.1) if(AX_USE_ALSOFT) set(ALSOFT_DLOPEN OFF CACHE BOOL "Check for the dlopen API for loading optional libs" FORCE) @@ -389,7 +397,7 @@ add_subdirectory(ogg) target_link_libraries(thirdparty ogg) configure_target_outdir(ogg) -if(WINDOWS OR LINUX OR (ANDROID AND AX_USE_GLAD)) +if(WINDOWS OR LINUX OR EMSCRIPTEN OR (ANDROID AND AX_USE_GLAD)) add_subdirectory(glad) target_link_libraries(thirdparty glad) configure_target_outdir(glad) diff --git a/thirdparty/astcenc/CMakeLists.txt b/thirdparty/astcenc/CMakeLists.txt index 235979da8b56..66316617957f 100644 --- a/thirdparty/astcenc/CMakeLists.txt +++ b/thirdparty/astcenc/CMakeLists.txt @@ -81,6 +81,11 @@ if (NOT DEFINED ASTC_ISA_SIMD) set(ASTC_ISA_SIMD "none") endif() + # disable simd when emscripten + if(EMSCRIPTEN) + set(ASTC_ISA_SIMD "none") + endif(EMSCRIPTEN) + message(AUTHOR_WARNING "ASTC_ISA_SIMD=${ASTC_ISA_SIMD}") endif() diff --git a/thirdparty/openal/CMakeLists.txt b/thirdparty/openal/CMakeLists.txt index 34fd33122305..58ecf3c4c560 100644 --- a/thirdparty/openal/CMakeLists.txt +++ b/thirdparty/openal/CMakeLists.txt @@ -572,7 +572,7 @@ check_symbol_exists(posix_memalign stdlib.h HAVE_POSIX_MEMALIGN) check_symbol_exists(_aligned_malloc malloc.h HAVE__ALIGNED_MALLOC) check_symbol_exists(proc_pidpath libproc.h HAVE_PROC_PIDPATH) -if(NOT WIN32) +if(NOT WIN32 AND NOT EMSCRIPTEN) # We need pthreads outside of Windows, for semaphores. It's also used to # set the priority and name of threads, when possible. check_include_file(pthread.h HAVE_PTHREAD_H) diff --git a/thirdparty/openal/core/helpers.cpp b/thirdparty/openal/core/helpers.cpp index 2eccc50f603b..0bc586d42d6b 100644 --- a/thirdparty/openal/core/helpers.cpp +++ b/thirdparty/openal/core/helpers.cpp @@ -437,7 +437,7 @@ namespace { bool SetRTPriorityPthread(int prio [[maybe_unused]]) { int err{ENOTSUP}; -#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) +#if defined(HAVE_PTHREAD_SETSCHEDPARAM) && !defined(__OpenBSD__) && !defined(__EMSCRIPTEN__) /* Get the min and max priority for SCHED_RR. Limit the max priority to * half, for now, to ensure the thread can't take the highest priority and * go rogue. diff --git a/thirdparty/openssl/include/emscripten/openssl/configuration.h b/thirdparty/openssl/include/emscripten/openssl/configuration.h new file mode 100644 index 000000000000..e093e27eeb41 --- /dev/null +++ b/thirdparty/openssl/include/emscripten/openssl/configuration.h @@ -0,0 +1,161 @@ +/* + * WARNING: do not edit! + * Generated by configdata.pm from Configurations/common0.tmpl, Configurations/unix-Makefile.tmpl + * via Makefile.in + * + * Copyright 2016-2021 The OpenSSL Project Authors. All Rights Reserved. + * + * Licensed under the Apache License 2.0 (the "License"). You may not use + * this file except in compliance with the License. You can obtain a copy + * in the file LICENSE in the source distribution or at + * https://www.openssl.org/source/license.html + */ + +#ifndef OPENSSL_CONFIGURATION_H +# define OPENSSL_CONFIGURATION_H +# pragma once + +# ifdef __cplusplus +extern "C" { +# endif + +# ifdef OPENSSL_ALGORITHM_DEFINES +# error OPENSSL_ALGORITHM_DEFINES no longer supported +# endif + +/* + * OpenSSL was configured with the following options: + */ + +# define OPENSSL_CONFIGURED_API 30000 +# ifndef OPENSSL_RAND_SEED_OS +# define OPENSSL_RAND_SEED_OS +# endif +# ifndef OPENSSL_NO_ACVP_TESTS +# define OPENSSL_NO_ACVP_TESTS +# endif +# ifndef OPENSSL_NO_AFALGENG +# define OPENSSL_NO_AFALGENG +# endif +# ifndef OPENSSL_NO_ASAN +# define OPENSSL_NO_ASAN +# endif +# ifndef OPENSSL_NO_ASM +# define OPENSSL_NO_ASM +# endif +# ifndef OPENSSL_NO_CAPIENG +# define OPENSSL_NO_CAPIENG +# endif +# ifndef OPENSSL_NO_CRYPTO_MDEBUG +# define OPENSSL_NO_CRYPTO_MDEBUG +# endif +# ifndef OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE +# define OPENSSL_NO_CRYPTO_MDEBUG_BACKTRACE +# endif +# ifndef OPENSSL_NO_DEVCRYPTOENG +# define OPENSSL_NO_DEVCRYPTOENG +# endif +# ifndef OPENSSL_NO_DSO +# define OPENSSL_NO_DSO +# endif +# ifndef OPENSSL_NO_DTLS +# define OPENSSL_NO_DTLS +# endif +# ifndef OPENSSL_NO_DTLS1 +# define OPENSSL_NO_DTLS1 +# endif +# ifndef OPENSSL_NO_DTLS1_2 +# define OPENSSL_NO_DTLS1_2 +# endif +# ifndef OPENSSL_NO_EC_NISTP_64_GCC_128 +# define OPENSSL_NO_EC_NISTP_64_GCC_128 +# endif +# ifndef OPENSSL_NO_EGD +# define OPENSSL_NO_EGD +# endif +# ifndef OPENSSL_NO_ENGINE +# define OPENSSL_NO_ENGINE +# endif +# ifndef OPENSSL_NO_EXTERNAL_TESTS +# define OPENSSL_NO_EXTERNAL_TESTS +# endif +# ifndef OPENSSL_NO_FIPS_SECURITYCHECKS +# define OPENSSL_NO_FIPS_SECURITYCHECKS +# endif +# ifndef OPENSSL_NO_FUZZ_AFL +# define OPENSSL_NO_FUZZ_AFL +# endif +# ifndef OPENSSL_NO_FUZZ_LIBFUZZER +# define OPENSSL_NO_FUZZ_LIBFUZZER +# endif +# ifndef OPENSSL_NO_KTLS +# define OPENSSL_NO_KTLS +# endif +# ifndef OPENSSL_NO_LOADERENG +# define OPENSSL_NO_LOADERENG +# endif +# ifndef OPENSSL_NO_MD2 +# define OPENSSL_NO_MD2 +# endif +# ifndef OPENSSL_NO_MSAN +# define OPENSSL_NO_MSAN +# endif +# ifndef OPENSSL_NO_PADLOCKENG +# define OPENSSL_NO_PADLOCKENG +# endif +# ifndef OPENSSL_NO_RC5 +# define OPENSSL_NO_RC5 +# endif +# ifndef OPENSSL_NO_SCTP +# define OPENSSL_NO_SCTP +# endif +# ifndef OPENSSL_NO_SSL3 +# define OPENSSL_NO_SSL3 +# endif +# ifndef OPENSSL_NO_SSL3_METHOD +# define OPENSSL_NO_SSL3_METHOD +# endif +# ifndef OPENSSL_NO_TRACE +# define OPENSSL_NO_TRACE +# endif +# ifndef OPENSSL_NO_UBSAN +# define OPENSSL_NO_UBSAN +# endif +# ifndef OPENSSL_NO_UI_CONSOLE +# define OPENSSL_NO_UI_CONSOLE +# endif +# ifndef OPENSSL_NO_UNIT_TEST +# define OPENSSL_NO_UNIT_TEST +# endif +# ifndef OPENSSL_NO_UPLINK +# define OPENSSL_NO_UPLINK +# endif +# ifndef OPENSSL_NO_WEAK_SSL_CIPHERS +# define OPENSSL_NO_WEAK_SSL_CIPHERS +# endif +# ifndef OPENSSL_NO_DYNAMIC_ENGINE +# define OPENSSL_NO_DYNAMIC_ENGINE +# endif + + +/* Generate 80386 code? */ +# undef I386_ONLY + +/* + * The following are cipher-specific, but are part of the public API. + */ +# if !defined(OPENSSL_SYS_UEFI) +# undef BN_LLONG +/* Only one for the following should be defined */ +# undef SIXTY_FOUR_BIT_LONG +# define SIXTY_FOUR_BIT +# undef THIRTY_TWO_BIT +# endif + +# define RC4_INT unsigned int + +# ifdef __cplusplus +} +# endif + +#endif /* OPENSSL_CONFIGURATION_H */ diff --git a/thirdparty/openssl/include/openssl/configuration.h b/thirdparty/openssl/include/openssl/configuration.h index 0d84b16597bb..5a6fe1ab602d 100644 --- a/thirdparty/openssl/include/openssl/configuration.h +++ b/thirdparty/openssl/include/openssl/configuration.h @@ -39,6 +39,8 @@ #elif defined(__linux__) # include "linux/openssl/configuration.h" +#elif defined(__EMSCRIPTEN__) +# include "emscripten/openssl/configuration.h" #else # error "unsupported platform" #endif diff --git a/thirdparty/openssl/prebuilt/emscripten/libcrypto.a b/thirdparty/openssl/prebuilt/emscripten/libcrypto.a new file mode 100644 index 0000000000000000000000000000000000000000..0a91ca673a3c56c1cf52330d8f09a72f9f013d76 GIT binary patch literal 4883160 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≫w;fa!=XGWG`lC>4#{q%tLWSZ0g8JW53HY zEsB%klbnyns+l*+G*7ZpvQF*^TRcu*rc)^ezs26zP$ZfdIj@NY9N zjbiSX!7>>L^iqJj$3V}|mYgB)_6gPT5$&jlq9BhrW-*eP9Q_%(OR*sZQ?W72H5@lT(6cgIj5Cg@>fpjT#Zn*WdgcK)P>}yfkR5pZ~SBC;p!-K ziU0#=nQ$4KH}ay_c#wi*iG?*`oOJOaPlrfS{iurK0SiNXdSJGkw!P+!(iY?S8iwGj zN7G4Kyn^#S@hq1f3wcq~K~e!9W>IL=lci075Ub2(Z%%HArkPZ(QavGH&&U{iKXH3-Y zGR~;PtfIG7ys2V#cF~N5TUN`Ym8p&jRW&Xzq8EKE9p)OH58r5y*icB8jHmu zyeMXUg?F$!P$She-nr%tJ%WXMWDWn$6FpQeD`UMsl{6f?XdK`4pft;s#ZMI@ID&?I zq9`UQFL&cF6>t2kLLs6ULdSoiYM2UCesR0@&lN)eM$ zDMY3#MNCGe5SgwNF&ULYWV%wsWK;@~=}HlkQ7IT3BOOM~iY;GkS_iF*t7TY{(qxp= z6Tq)^EJELFLd*OyE%T!)#OjN(C+bo3Vx8p`y|9k(JZqUD30A^*))(_&E2i3}DMsbw zpjtkS69raG5+Oa#2s@gPWY+oCD1&i#9tF{#ytTnrispI6Al~gOu zOhFkh6kU>9LM|HU^G4(KNMe*P-Zb(CBc3IWM`P!z>-5ivUIj9+$V8T66QiR-@pj`r zsWPFPJ&a(AYRUgxk3`laQ_qdf;&E3MnTbZ8=!}UD?$t-E@;6CRW;j4Vt;~udj`Swb zHg#NVZ5i2wf1K7Gtr2fDKxPcZI>;!FX0ouyYbrdmQ)E?-itZuuadPjL9NkiOimN&tM`9JKv z37lM2nect?Qgy59c6C=TNq5qnq`H%kL?9#~2?22?7X`-+eFdHI{ro0$(ha1^M!Lhm zi~|h>WDx{JK@en7Q9w2YR78TZD1w3@n}BSh1G0$%g2?-So^$W5Th$d9oSFCcT~b8XMN84>{i(h_$F;G#kYa~-d(=*x9{@Dz13a*e`qzQU3lxOxmg$9a5W$1y6i`9 zw3>x9!P0fpSrrM6qZ1_Z#V$v@&g0=^w38L?UBG4~`$T4igDGTIcxEox6&^46QpHZ^ z%U##2Yo2en29Pv}3$m_c#jq#FXn8DCke_(B`=cLyUsgzZl@%sV_Cg}JbXE1#MVJ;T z+&S*h3yo?I##l}@kOPd!;%2Zq5PezENnFXs=>e9+g*HwPuq3X<#_0i;#FcHF9$-ma ztBunGEQxC)PL7&@15BEoZkf68THgt4r;DBjB2tp$EKxMa$1DYh7|1I4=mnPOk{b|| zlz!T4Qz<#nM6=Ltwk&9XN;f&%pbnLE1CDpuYGF$RMNGvyg(3#TFyJAm3`mqv#xzpy zdPyYnhbH+HTCyZE926bcFLVc23og>a1Wnh*%it6RO>dE|NCkvtx+D%a>B^f@#~4CE z(|vqXki1-Np$SSI3z@HJIUu7OWwZqtBAzOES2fh6oa*MiTmqSKzEa0ZLg8Yc+#QWj zW})uWLIn$W1CcCA2{>dp5ad(f&i_byx=<2qCYF#uo${ElNSPErD!868T1@%9V^#~ zSXm|s@fQ%EiID`*tWES?CPv*~n<<=)1q?X_ID?y44+0Ce_*j~QrhV--m=ITa>zCPXV( z=1$6hrIyE-dDcQqElPk#x6C^_(*3{GR74^CkZp4DR?hU~$LX0;YaHmg-*2w*{XL^5)E-Lqb6J32gP_$M zUn==0mxw*1MC>`7ke8NPgU_|(gR@)m=7yGnd9|g(|5iCP=KJMN^QG1<|GTZc{IGSB z`EzT@f1MZqm^P#UPHZcibJ|+X_u9~*YRj8@+Iq}WZ6*KtHd4IOHpQ56w6sq`P5a_X z$-e@DLzv^raj_=9`U+Bu43nvwWt0(2nZIep=os&rQz@+KMJUXdt{xGT4oH)7U zpE{Y?vnCUJ{^YW`Vsfjwnb_MT_HK!NfY_&qJ+iyxAKOjrDc!W`^zO2`pu07Az9%32 zr6+HGIHef;d`doegx~L{l@p{vtgJ`9Cbq`e zdq6CTQ4{rO(OrM_C~B@KO=y`Fdl0C&hLt^6VG-NT4V#j7zM;%*-cuuiMT0yPR-=QU zsCu>zB(5;u*51+CHOY!y_@$$u^_b!WypxiL>3(nC+tlddZVVe9&GU{~VfG&;?Rt`Z;t zugXYTR6OvG5Hlm~6W@DAEGaWO?EMXeKVMfkrTzts6e?=cvgq(CEc*_fOpHR05HN&a z+X;4E#L_E|ll4*UxpwKJ7<^@V6!ASG^CN;bT#BY++6wyC=4v!vl~0Hl6?=*Ai*WST zE2{0n;glJw6t4lnysJ3#OBk&u#^UCTH|$l!ZiF9A3S~=Ii~S*`lQ&U!3>*6S1t$wc z`k9y$(2fewtUPl_p;Assg>|aJw~Z;tWv6ab&VKU(>8|N-Nfwt-3g?13^TX1ahV2P@i0$~l*iQ%s~Qh~ zb15$6nqL#E?hLS>^wA_X4gMB^br=h;*Y)xT|CwHD6)ZsrWWoZ6Y9b?}rro+mEM;BW zS;d~1EbWt|xmXwrYD%Gn@Xrn`hiYt|ED7J&tBiYPmU&D(nGGGklq%D8Gm&I7! zLo5Z;5ziVQ)+EBUW(r#Vy!|d?q2Jac`a|N%fs%XN5q3lLZ#K?{6?jlDpM{?G0r~KH zoSNwzg~_MaJ^ePmMYM>ko@I-W_B!Nmgyv-TD#*9VUWKR(`-bqySQ3XJTm!vIHUtMA zW=&rVK2Of>`4F)3+FoITCjt->K^LH0tr%(bIjLPXQa+0CnaB2Ng!Y7>f1+2Ry9#rP z6ci#dW0u8bH^?NX8|Op}N=k4xPAEd4fl*<}E`lY{up}?D(^H7bxESt_kUa72h(1K( zzFsI_)~_&M#C3LK+k`zR1VbtLQ<;)=F?+Gn0Usf)p)6+)1M) z%MER#8Zx=HOspv}nKhv;PL-3AvZQO&=xTWo>oT^n^4RfF-fdMGMvguMSUZsOl5Yyg z7zzh%d?#ZPr*z|#yQC@K*#9Rt-XyCZXMKlpm$OB)R$?9;?G-b-qM2iBL5$J1XGWU& zbtg%#Esnp3_sB|AMeND-ir8ycf`KX(a&m0KOJmTuTd_`cK!pa`vUc^l(bfNo7WSGU zW1^)*3?5vIctNh1HMPLkIE)OKQrQwr((-I^t(HWQ#%e99XJDW|We9Q*t0vgRs?X&> zZm3c63f7M8e4I?QIH__ro+et1+g8v!A6+$WrEP5E;6cM1M~7q8!jz@OttR}%XOvCk2Eu3N-PiSIw<^5u^xDw@3;NHze&`?@9+!G6Ml<%o|EV=Bsl`t zEdKFO7R6KE|HrB=uZp=6D__?He7ONa zgSiDOU-t(E=lDXqb55bdxw6pd+*ar^_ZG^|uM3m>KNKkR)xuG%Qy4$v`-8441 za?SAe`>k2Oa&XP)wuRBv`>or4_~5Y}7QZ#W4oj9S*r}@j_Nif{DxF%6Roi29vk?om z{v1qdj9BeuGsXkI_5}=S+_bik(>bgU#(*cElN|7Vi8%bA>}}-7rsuA zV9*d49VaZrY6`{Dt4M;I;_FT`5j8H@p3AkzUO?OmVI)@(g%OE{y4KvBbFL7=!4!!E%E7^fXZu*{U-?1j%4`~VhxuEwT^KH%HCV%4)c z0F=fxc?8`qS1y}Y^hIyR_Tp$SkK7TtsC{Han3$0Z<5;taQUAJb{|pwM0LS8j^i0f@ z9%m6@?KV+4%;xGB9;q7sJW8{2Qxw7bLj^C-M|0Oi<1kB0-|VMJ+Zm@H2=|wxp8i%o zF8n}$TT+0xJUY_p_o91@-uhc2XcTSY!>abrDBZv%M+KZOuOtKENpr0hl?|#@G3jE} z+aj13jgT^2ll^BAycVn5D(I=y`8ahdRl@?R3h9@QocygPZgr4er6g$8vAi))(&Cnq z+<_}#SnfA1CbE0ED5x2ekiWrhC5w6NUIqt)Wbu)ai|vJt2ubEfr8QON$w!dtmj0+@ z_Xp?*{Sxtifol<*CT>tpTLFEWjB~`G5yqgs=p|`(AefI#ed-C5I@FM~E9`Mqd8yDQ z3KSdx#~osO;&mg=Pk<0$6T!XL%?GF$0ltI4U!dE|MOykDTZyhj`H?7Ci@%Fj2o!uM zi=PhzpWm*aPdir5O9YauBLl5*^szD4Jb)Lyy4iKS(=fZr6IPW@E!L>&_*A(6M`cmu zp%=i9js&}n$7;F*?YkZJznFSu?(fxh5#Dp9Tl7OjFz5`oc&~~jYgbo*=ykYi4~l+3 ze-bIcr(|89>i{h)#v8eiW2nTCO+Y3EsZIjuqImTHE1(9Q^a?j%XzQ^Vn3J_cV8oG_ zNenH}XlD$ai(;TL^Xh=}lV(fr6}rx5Qd@d{T_(DmU9O3H7EK;k%&bdlzAPCoz=@ng zV3BeWaY+p|(I)n=szbrX%BO|6GfNF~-Cnb75>L@Yb4lh}HMX)I`LXSHD;VekZ=?}G z_TnN3JYKEpKghF~CWo$B2a6k=SfI#KYRb`Xvs=(%Oa_oZnZ*c=+NpSRGUrfEs4MEn zjYTCyAPZ9|ODU<8CbT#v2?9@Qx_+u`@g}-e4~wFW6WGXZ&Yzab=j00uNme$3Ewv6# zB^k3ME`~>yVM@##+?5Q2f@NfucA)Bo4Mr!ugY!!+*Js0Vq%ICGqY^GJ8B?^!rAxB7_@tr2Ip?Q*&Z-||$o|G>w!je13 za!g{Dg^yse=&8sKN%G|S!L34eVRA!pLm-K-QDSfHSFW|BEf!Av2C$N8rY8%?Qv}ZG*VwQuhi~4sxKOpIX?ZD0|#QSGsn4)_&Tkt76Ak1G$D4 z-BAzi8m)A*6jk5j)Y;RQ<(nKaSOcSWX-b#6xuVfIv7cctJPK(AkgwD{k}(RBH`)?6 zQL-jW`T8`@7Wi-IvI!JqRE2;8NxdQ}5j(j?>d*~79*+19<)|+uncO)myQt!D>4;Ud z-Bm!RH!epZOF>Q6z`6Bcp{EU#P!>VDd-~z&POMJL$jBrCAgIaNZu63K`h{-Vz~XWE^&)iL}HR!f@xCGpW}34xR*9;;?(3^e50w^FcFmE z1o4RxsTyovrLy*{_*}eUF;ittvLniPu@9<_R8)re%$qDt0$s0SsJ?Q#OoVlEdBH<88XeV3gG8Jkk8P6=otj!I_fKJNR5xIS_Uk^BWTu%xK~>vv@2c4Os@$xW?)-id_z90 z1v~DHjEq?-?vp7YFq5zuVk!+HaG4Y`ewZ>z1