Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emscripten crossbuild support #359

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 82 additions & 9 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,39 @@ option(ENABLE_AUDIO "Enable Audio" ON)
option(ENABLE_MICROPROFILE "Enable microprofile" OFF)
option(ENABLE_ANGELSCRIPT "Enable AngelScript" ON)
option(ENABLE_MOFILEREADER "Enable MofileReader" ON)
option(CROSSBUILD_EMSCRIPTEN "Build for the EmScripten Platform" OFF)

if (CROSSBUILD_EMSCRIPTEN)
# Turn off not features that are not compatible with emscripten

include(~/.conan/data/emsdk_installer/1.39.6/bincrafters/stable/package/1492a59deb6efdf29776a5734de63fc5f5c58650/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is good practice...

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hash is likely to change... We should use the proper conan way to do this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right about both, but i don't know how to do this with conan. Maybe @AnotherFoxGuy could help us out here?

endif()

if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten")
find_package(OpenGL REQUIRED)
include_directories(${OPENGL_INCLUDE_DIR})

list( APPEND conan_cmake_run_params SETTINGS os.threads=true )

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --no-check-features")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --no-check-features")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --no-check-features")

#Debug information
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profiling-funcs -Oz -g4 -s PTHREAD_POOL_SIZE=8 -s DEMANGLE_SUPPORT=1 -pthread -s DISABLE_EXCEPTION_CATCHING=0 -s ASSERTIONS=1 -s USE_PTHREADS=1 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s USE_ZLIB=1" )
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably remove this comment

Suggested change
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profiling-funcs -Oz -g4 -s PTHREAD_POOL_SIZE=8 -s DEMANGLE_SUPPORT=1 -pthread -s DISABLE_EXCEPTION_CATCHING=0 -s ASSERTIONS=1 -s USE_PTHREADS=1 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s USE_ZLIB=1" )

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this in because those are the flags that can be used to debug emscripten. Instead of removing the command, we should check for CMAKE_BUILD_TYPE=Debug and use the aproprate flags instead.


set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --profiling-funcs -O3 -g0 --llvm-lto 1 --llvm-opts 3 -s PTHREAD_POOL_SIZE=8 -pthread -s DISABLE_EXCEPTION_CATCHING=1 -s USE_PTHREADS=1 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s USE_ZLIB=1" )


set(CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -pthread -s USE_PTHREADS=1 -s USE_SDL_IMAGE=2 -s USE_SDL_TTF=2 -s USE_ZLIB=1" )
set(SDL2_LIBRARIES "-s USE_SDL=2")
set(SDL2_IMAGE_LIBRARIES "-s USE_PTHREADS=1 -s USE_SDL_IMAGE=2")
#set(SDL2_MIXER_LIBRARIES "-s USE_SDL_MIXER=2")
set(SDL2_TTF_LIBRARIES "--no-check-features -s USE_SDL_TTF=2")
set(SDL2_ZLIB_LIBRARIES "-s USE_ZLIB=1")

endif()


# Comment-out uneeded libs
if (NOT ENABLE_AUDIO)
Expand Down Expand Up @@ -72,14 +105,27 @@ if (USE_PACKAGE_MANAGER)

include(pmm)

pmm(CONAN
REMOTES
AFG https://api.bintray.com/conan/anotherfoxguy/conan-packages
catchorg https://api.bintray.com/conan/catchorg/Catch2
ror-dependencies https://api.bintray.com/conan/anotherfoxguy/ror-dependencies
BINCRAFTERS
CMakeCM ROLLING
)
if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten")

pmm(CONAN
PROFILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/conan_profiles/emscripten_profile"
REMOTES
AFG https://api.bintray.com/conan/anotherfoxguy/conan-packages
catchorg https://api.bintray.com/conan/catchorg/Catch2
ror-dependencies https://api.bintray.com/conan/anotherfoxguy/ror-dependencies
BINCRAFTERS
CMakeCM ROLLING
)
else()
pmm(CONAN
REMOTES
AFG https://api.bintray.com/conan/anotherfoxguy/conan-packages
catchorg https://api.bintray.com/conan/catchorg/Catch2
ror-dependencies https://api.bintray.com/conan/anotherfoxguy/ror-dependencies
BINCRAFTERS
CMakeCM ROLLING
)
endif()

if (DEFINED ENV{CI})
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
Expand All @@ -95,6 +141,32 @@ if (USE_PACKAGE_MANAGER)
list(APPEND CMAKE_MODULE_PATH "${CONAN_LIB_DIRS_CATCH2}/cmake/Catch2")
endif ()

# these compiler options are mostly for debugging and trying to get it to work
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should go in CompileOptions.cmake

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

if("${CMAKE_SYSTEM_NAME}" STREQUAL "Emscripten")
list(APPEND _link_libraries
#"-s ERROR_ON_UNDEFINED_SYMBOLS=0"
"--preload-file ${CMAKE_CURRENT_BINARY_DIR}/resources@/resources" #include resources dir in .data file
"-s USE_PTHREADS=1"
"--use-preload-plugins"
# "--source-map-base"
"-s WASM=1"
"-s WASM_MEM_MAX=512MB" # ALLOW_MEMORY_GROWTH demands MEM_MAX to be set
"-s ALLOW_MEMORY_GROWTH=1" #seems necessary for pthreads and dynamic memory allocation
"-o index.html"
# "-s DISABLE_EXCEPTION_CATCHING=0" # 0 -> catch exceptions! 1-> is disabled
"-s TOTAL_MEMORY=33554432"
# "-s SAFE_HEAP=1"
#demangle C++ functions
# "-s ASSERTIONS=0" # use assertions
"-O3" # no optimization
"-g0" # most debug info
CONAN_PKG::libnoise
${SDL2_LIBRARIES}
${SDL2_IMAGE_LIBRARIES}
${SDL2TTF_LIBRARIES}
${SDL2_ZLIB_LIBRARIES}
)
else()
list(APPEND _link_libraries
CONAN_PKG::sdl2
CONAN_PKG::sdl2_image
Expand All @@ -103,6 +175,7 @@ if (USE_PACKAGE_MANAGER)
CONAN_PKG::libnoise
CONAN_PKG::zlib
)
endif()

if (ENABLE_AUDIO)
list(APPEND _link_libraries CONAN_PKG::sdl2_mixer)
Expand Down Expand Up @@ -197,7 +270,7 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
link_libraries(dbghelp.lib)
else()
# Needed for filesystem library
if(NOT APPLE)
if(NOT APPLE AND NOT EMSCRIPTEN)
# TODO: Link with stdc++fs on Apple with XCode11
list(APPEND _link_libraries "stdc++fs")
endif()
Expand Down
17 changes: 17 additions & 0 deletions cmake/conan_profiles/emscripten_profile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[settings]
os=Emscripten
arch=x86
#arch=wasm
os_build=Linux
arch_build=x86_64
compiler=clang
compiler.version=10
#compiler.version=6.0
compiler.libcxx=libc++
[options]
threads=true
[build_requires]
emsdk_installer/1.39.6@bincrafters/stable
[options]
libjpeg-turbo:SIMD = False
sdl2_image:tif = False
129 changes: 80 additions & 49 deletions src/Game.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@
#include <SDL.h>
#include <SDL_ttf.h>

#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif

void gameLoop(void* arg_);

#ifdef USE_ANGELSCRIPT
#include "Scripting/ScriptEngine.hxx"
#endif
Expand All @@ -26,6 +32,17 @@
template void Game::LoopMain<GameLoopMQ, Game::GameVisitor>(Game::GameContext &, Game::GameVisitor);
template void Game::LoopMain<UILoopMQ, Game::UIVisitor>(Game::GameContext &, Game::UIVisitor);

typedef struct loop_arg {
Engine *engine;
EventManager *evManager;
UIManager *uiManager;
} loop_arg;

// FPS Counter variables
const float fpsIntervall = 1.0; // interval the fps counter is refreshed in seconds.
Uint32 fpsLastTime = SDL_GetTicks();
Uint32 fpsFrames = 0;

Game::Game()
: m_GameContext(&m_UILoopMQ, &m_GameLoopMQ,
#ifdef USE_AUDIO
Expand Down Expand Up @@ -240,24 +257,32 @@ void Game::mainMenu()

void Game::run(bool SkipMenu)
{
loop_arg* arg = new loop_arg;
Timer benchmarkTimer;
LOG(LOG_INFO) << VERSION;

Engine &engine = Engine::instance();
EventManager &evManager = EventManager::instance();
UIManager &uiManager = UIManager::instance();


if (SkipMenu)
{
Engine::instance().newGame();
}

benchmarkTimer.start();
Engine &engine = Engine::instance();

LOG(LOG_DEBUG) << "Map initialized in " << benchmarkTimer.getElapsedTime() << "ms";
// SDL_Event event;

arg->engine = &engine;
arg->evManager = &evManager;
arg->uiManager = &uiManager;

LOG(LOG_DEBUG) << "Map initialized in " << benchmarkTimer.getElapsedTime() << "ms";
Camera::centerScreenOnMapCenter();

SDL_Event event;
EventManager &evManager = EventManager::instance();

UIManager &uiManager = UIManager::instance();
uiManager.init();

#ifdef USE_ANGELSCRIPT
Expand All @@ -278,55 +303,18 @@ void Game::run(bool SkipMenu)
}
#endif // USE_AUDIO

// FPS Counter variables
const float fpsIntervall = 1.0; // interval the fps counter is refreshed in seconds.
Uint32 fpsLastTime = SDL_GetTicks();
Uint32 fpsFrames = 0;
#ifdef __EMSCRIPTEN__
emscripten_set_main_loop_arg(&gameLoop, arg, 0, 1);
#else

// GameLoop
while (engine.isGameRunning())
{
#ifdef MICROPROFILE_ENABLED
MICROPROFILE_SCOPEI("Map", "Gameloop", MP_GREEN);
#endif
SDL_RenderClear(WindowManager::instance().getRenderer());

evManager.checkEvents(event, engine);

// render the tileMap
if (engine.map != nullptr)
{
engine.map->renderMap();
}

// render the ui
// TODO: This is only temporary until the new UI is ready. Remove this afterwards
if (Settings::instance().drawUI)
{
uiManager.drawUI();
}

// reset renderer color back to black
SDL_SetRenderDrawColor(WindowManager::instance().getRenderer(), 0, 0, 0, SDL_ALPHA_OPAQUE);

// Render the Frame
SDL_RenderPresent(WindowManager::instance().getRenderer());

fpsFrames++;

if (fpsLastTime < SDL_GetTicks() - fpsIntervall * 1000)
{
fpsLastTime = SDL_GetTicks();
uiManager.setFPSCounterText(std::to_string(fpsFrames) + " FPS");
fpsFrames = 0;
}

SDL_Delay(1);

#ifdef MICROPROFILE_ENABLED
MicroProfileFlip(nullptr);
#endif
gameLoop( arg);
}
#endif

delete arg;
}

void Game::shutdown()
Expand Down Expand Up @@ -370,3 +358,46 @@ template <typename MQType, typename Visitor> void Game::LoopMain(GameContext &co
// @todo: Call shutdown() here in a safe way
}
}

void gameLoop(void* arg_)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's good that you were able to get it to work this way. However I don't think this is the right solution. If Emscripten truly doesn't support multithreading then we can try using coroutines instead. For now, this is a good starting point

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was sure you wouldn't be happy with this solution. (Because i'm not too) According to the documentation:

https://emscripten.org/docs/porting/emscripten-runtime-environment.html#browser-main-loop

it seems to be the only way to do this.
I think we could ifdef this?

// void gameLoop(Engine &engine, EventManager &evManager, UIManager &uiManager)
{

loop_arg* arg = (loop_arg*)arg_;
#ifdef MICROPROFILE_ENABLED
MICROPROFILE_SCOPEI("Map", "Gameloop", MP_GREEN);
#endif
SDL_RenderClear(WindowManager::instance().getRenderer());
SDL_Event event;
arg->evManager->checkEvents(event, *arg->engine);

// render the tileMap
if (arg->engine->map != nullptr)
arg->engine->map->renderMap();

// render the ui
arg->uiManager->drawUI();

// reset renderer color back to black
SDL_SetRenderDrawColor(WindowManager::instance().getRenderer(), 0, 0, 0, SDL_ALPHA_OPAQUE);

// Render the Frame
SDL_RenderPresent(WindowManager::instance().getRenderer());

fpsFrames++;

if (fpsLastTime < SDL_GetTicks() - fpsIntervall * 1000)
{
fpsLastTime = SDL_GetTicks();
arg->uiManager->setFPSCounterText(std::to_string(fpsFrames) + " FPS");
fpsFrames = 0;
}

#ifndef __EMSCRIPTEN__
SDL_Delay(1);
#endif

#ifdef MICROPROFILE_ENABLED
MicroProfileFlip(nullptr);
#endif
}
2 changes: 1 addition & 1 deletion src/engine/EventManager.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ void EventManager::checkEvents(SDL_Event &event, Engine &engine)

case SDLK_0:
break;
case SDLK_F11:
case SDLK_m:
m_uiManager.toggleDebugMenu();
break;
case SDLK_1:
Expand Down
9 changes: 8 additions & 1 deletion src/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
#include "Exception.hxx"
#include "LOG.hxx"

#ifndef __EMSCRIPTEN__
#include <signal.h>
void SIG_handler(int signal);
#endif

SDL_AssertState AssertionHandler(const SDL_AssertData *, void *);

Expand All @@ -24,6 +26,10 @@ int protected_main(int argc, char **argv)
skipMenu = true;
}
}

#ifdef __EMSCRIPTEN__
skipMenu = true;
#endif

LOG(LOG_DEBUG) << "Launching Cytopia";

Expand Down Expand Up @@ -52,11 +58,12 @@ int protected_main(int argc, char **argv)
int main(int argc, char **argv)
{

#ifndef __EMSCRIPTEN__
/* Register handler for Segmentation Fault, Interrupt, Terminate */
signal(SIGSEGV, SIG_handler);
signal(SIGINT, SIG_handler);
signal(SIGTERM, SIG_handler);

#endif
/* All SDL2 Assertion failures must be handled
* by our handler */
SDL_SetAssertionHandler(AssertionHandler, 0);
Expand Down
8 changes: 5 additions & 3 deletions src/util/Exception.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ void SIG_handler(int signal)
exit(1);
}

#else
// #else
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should remove this comment

Suggested change
// #else

#elif __unix__ && !__EMSCRIPTEN__

#include <unistd.h>
#include <execinfo.h>
#include <signal.h>

#include <execinfo.h>

void SIG_handler(int signal)
{
switch (signal)
Expand Down Expand Up @@ -93,7 +95,7 @@ SDL_AssertState AssertionHandler(const SDL_AssertData *data, void *)
SYMBOL_INFO symbol;
for (int i = 0; i < size; ++i)
std::cout << "\tat " << symbol.Name << "\n";
#else
#elif __unix__ && !__EMSCRIPTEN__
/* We print the last 10 calls */
void *buffer[10];
size_t size;
Expand Down