diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 67cae553..5d838655 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,3 +1,20 @@ +if(CMAKE_SYSTEM_NAME STREQUAL Emscripten) + set(BOOST_INCLUDE_LIBRARIES random stacktrace math) + set(BOOST_ENABLE_COMPATIBILITY_TARGETS 1) + add_compile_options(-fdeclspec) # math doesn't build otherwise + + # Use out-of-tree sources for boost, since we can't use pre-built + # libraries for the web build + if(BOOST_SOURCE_ROOT) + add_subdirectory(${BOOST_SOURCE_ROOT} boost EXCLUDE_FROM_ALL) + else() + message(FATAL_ERROR "For web builds BOOST_SOURCE_ROOT must be provided") + endif() + + # TODO: make this available via find_package so we wouldn't have to + # special-case it in every library +endif() + file(GLOB_RECURSE RAPIDJSON_SOURCES "${CMAKE_CURRENT_LIST_DIR}/rapidjson/include/*") add_library(rapidjson INTERFACE EXCLUDE_FROM_ALL "${RAPIDJSON_SOURCES}") target_include_directories(rapidjson INTERFACE "${CMAKE_CURRENT_LIST_DIR}/rapidjson/include") diff --git a/CMakeLists.txt b/CMakeLists.txt index 029bf0e4..906298fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,18 @@ if(PSEMEK_BACKEND STREQUAL "ANDROID") set(PSEMEK_GL_LIBRARIES GLESv3 EGL) set(PSEMEK_PACKAGE_AS_LIBRARY ON CACHE INTERNAL "Build applications as shared libraries") list(APPEND PSEMEK_CXX_FLAGS -fPIC) +elseif(CMAKE_SYSTEM_NAME STREQUAL Emscripten) + set(PSEMEK_GL_API gles32) + set(PSEMEK_GL_LIBRARIES EGL) + set(PSEMEK_PACKAGE_AS_LIBRARY OFF CACHE INTERNAL "Build applications as shared libraries") + list(APPEND PSEMEK_DEFINITIONS "-DPSEMEK_GLES=1") + list(APPEND PSEMEK_CXX_FLAGS -sUSE_SDL=2 -fexperimental-library) + # by default emcc doesn't produce catch clauses, but psemek is designed around exceptions + list(APPEND PSEMEK_CXX_FLAGS -sDISABLE_EXCEPTION_CATCHING=0) + if(CMAKE_BUILD_TYPE STREQUAL Debug) + list(APPEND PSEMEK_CXX_FLAGS -g) + endif() + set(CMAKE_EXECUTABLE_SUFFIX ".html" PARENT_SCOPE) # we want to build html, not js else() find_package(Threads REQUIRED) set(OpenGL_GL_PREFERENCE LEGACY) diff --git a/libs/gfx/api/gles32/source/gl.cpp b/libs/gfx/api/gles32/source/gl.cpp index b2561305..8d5f7cea 100644 --- a/libs/gfx/api/gles32/source/gl.cpp +++ b/libs/gfx/api/gles32/source/gl.cpp @@ -11,7 +11,7 @@ #include #elif defined(_WIN32) #include -#elif defined(__ANDROID__) +#elif defined(__ANDROID__) || defined(__EMSCRIPTEN__) #include #else #include @@ -80,7 +80,7 @@ namespace gl return reinterpret_cast(GetProcAddress(image, reinterpret_cast(name))); } - #elif defined(__ANDROID__) + #elif defined(__ANDROID__) || defined(__EMSCRIPTEN__) static void * get_proc_address(const char *func) { diff --git a/libs/journal/source/event.cpp b/libs/journal/source/event.cpp index 62d43b17..538578dd 100644 --- a/libs/journal/source/event.cpp +++ b/libs/journal/source/event.cpp @@ -8,8 +8,12 @@ namespace psemek::journal std::string current_time() { +#ifndef __EMSCRIPTEN__ const auto now = std::chrono::system_clock::now(); return std::format("{:%FT%TZ}", now); +#else + return ""; +#endif } } diff --git a/libs/log/source/log.cpp b/libs/log/source/log.cpp index 9b271072..4f070455 100644 --- a/libs/log/source/log.cpp +++ b/libs/log/source/log.cpp @@ -148,9 +148,11 @@ namespace psemek::log #if __APPLE__ ::pthread_setname_np(name.data()); #else +#if !(defined __EMSCRIPTEN__) || (defined __EMSCRIPTEN_PTHREADS__) ::pthread_setname_np(::pthread_self(), name.data()); #endif #endif +#endif #endif auto id = std::this_thread::get_id(); diff --git a/libs/random/CMakeLists.txt b/libs/random/CMakeLists.txt index 85454de7..e8fa94c3 100644 --- a/libs/random/CMakeLists.txt +++ b/libs/random/CMakeLists.txt @@ -1,4 +1,6 @@ -find_package(Boost COMPONENTS random REQUIRED CONFIG) +if(NOT (CMAKE_SYSTEM_NAME STREQUAL Emscripten)) + find_package(Boost COMPONENTS random REQUIRED CONFIG) +endif() file(GLOB_RECURSE PSEMEK_RANDOM_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp") file(GLOB_RECURSE PSEMEK_RANDOM_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp") diff --git a/libs/sdl2/CMakeLists.txt b/libs/sdl2/CMakeLists.txt index 66bebaa7..de3d8ba3 100644 --- a/libs/sdl2/CMakeLists.txt +++ b/libs/sdl2/CMakeLists.txt @@ -1,4 +1,7 @@ +# On web SDL is provided by emsdk itself +if(NOT CMAKE_SYSTEM_NAME STREQUAL Emscripten) find_package(SDL2 REQUIRED) +endif() file(GLOB_RECURSE PSEMEK_SDL2_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp") file(GLOB_RECURSE PSEMEK_SDL2_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp") diff --git a/libs/util/CMakeLists.txt b/libs/util/CMakeLists.txt index 5a75457b..b308bb9b 100644 --- a/libs/util/CMakeLists.txt +++ b/libs/util/CMakeLists.txt @@ -1,6 +1,8 @@ -# Boost.Math is header-only -# Required for statistics.hpp -find_package(Boost REQUIRED CONFIG) +if(NOT (CMAKE_SYSTEM_NAME STREQUAL Emscripten)) + # Boost.Math is header-only + # Required for statistics.hpp + find_package(Boost REQUIRED CONFIG) +endif() file(GLOB_RECURSE PSEMEK_UTIL_HEADERS RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "include/*.hpp") file(GLOB_RECURSE PSEMEK_UTIL_SOURCES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "source/*.cpp") diff --git a/package/CMakeLists.txt b/package/CMakeLists.txt index 940fc5a3..862baf91 100644 --- a/package/CMakeLists.txt +++ b/package/CMakeLists.txt @@ -10,14 +10,24 @@ if(PSEMEK_PACKAGE_MODE) elseif(ANDROID) set(PSEMEK_PACKAGE_SUFFIX_RAW android) elseif(UNIX) - set(PSEMEK_PACKAGE_SUFFIX_RAW linux) + if (CMAKE_SYSTEM_NAME STREQUAL Emscripten) + set(PSEMEK_PACKAGE_SUFFIX_RAW web) + else() + set(PSEMEK_PACKAGE_SUFFIX_RAW linux) + endif() else() message(FATAL "Uknown system for packaging") endif() set(PSEMEK_PACKAGE_SUFFIX ${PSEMEK_PACKAGE_SUFFIX_RAW} CACHE INTERNAL "Packaging suffix" FORCE) set(PSEMEK_PACKAGE_LINK_FLAGS_RAW) - if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) + + if(CMAKE_SYSTEM_NAME STREQUAL Emscripten) + list(APPEND PSEMEK_PACKAGE_LINK_FLAGS_RAW "-sMIN_WEBGL_VERSION=2" "-sNO_DISABLE_EXCEPTION_CATCHING") + if(CMAKE_BUILD_TYPE STREQUAL Debug) + list(APPEND PSEMEK_PACKAGE_LINK_FLAGS_RAW "-g") + endif() + elseif((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")) list(APPEND PSEMEK_PACKAGE_LINK_FLAGS_RAW "-static-libgcc" "-static-libstdc++") endif() set(PSEMEK_PACKAGE_LINK_FLAGS ${PSEMEK_PACKAGE_LINK_FLAGS_RAW} CACHE INTERNAL "Packaging CXX flags" FORCE) @@ -71,7 +81,7 @@ function(psemek_add_executable_impl target is_application) ) endif() - if(UNIX AND (NOT APPLE) AND (NOT ANDROID)) + if(UNIX AND (NOT APPLE) AND (NOT ANDROID) AND (NOT (CMAKE_SYSTEM_NAME STREQUAL Emscripten))) add_custom_command(TARGET ${target} POST_BUILD COMMAND chrpath -r . $ ) @@ -82,7 +92,19 @@ function(psemek_add_executable_impl target is_application) if(NOT ANDROID) get_filename_component(_OUTPUT_DIRECTORY "${_OUTPUT_PATH}" DIRECTORY) - set(_COPY_FILES $ ${PSEMEK_PACKAGE_COPY_FILES}) + set(_TARGET_FILE $) + + set(_TARGET_RUNTIME) + if(CMAKE_SYSTEM_NAME STREQUAL Emscripten) + set(_TARGET_PARENT $) + set(_TARGET_STEM $) + set(_TARGET_NOEXT "${_TARGET_PARENT}/${_TARGET_STEM}") + + list(APPEND _TARGET_RUNTIME ${_TARGET_NOEXT}.js) + list(APPEND _TARGET_RUNTIME ${_TARGET_NOEXT}.wasm) + endif() + + set(_COPY_FILES ${_TARGET_FILE} ${_TARGET_RUNTIME} ${PSEMEK_PACKAGE_COPY_FILES}) if(PSEMEK_GRAPHICS_API STREQUAL WEBGPU) list(APPEND _COPY_FILES ${PSEMEK_PACKAGE_COPY_FILES_WGPU}) diff --git a/package/bin/psemek-package b/package/bin/psemek-package index 296993a9..e5956654 100755 --- a/package/bin/psemek-package +++ b/package/bin/psemek-package @@ -8,6 +8,7 @@ if [ "$#" -ne 2 ]; then echo " linux" echo " windows" echo " android" + echo " web" exit 0 fi @@ -15,6 +16,7 @@ case ${1} in linux) ;; windows) ;; android) ;; + web) ;; *) echo "Unknown platform: ${1}" exit -1 diff --git a/package/web/Dockerfile b/package/web/Dockerfile new file mode 100644 index 00000000..57307126 --- /dev/null +++ b/package/web/Dockerfile @@ -0,0 +1,20 @@ +FROM lisyarus/psemek:package-linux + +# Install emsdk - the version the build was tested at +WORKDIR /home +RUN git clone --branch 3.1.46 https://github.com/emscripten-core/emsdk.git +WORKDIR /home/emsdk +RUN ./emsdk install 3.1.46 +RUN ./emsdk activate 3.1.46 +WORKDIR /home +COPY env-helper.sh . +# Source emsdk env variables when running the container +ENTRYPOINT ["/home/env-helper.sh"] + +# Install Boost sources +WORKDIR /home +RUN git clone --recurse-submodules --branch boost-1.87.0 https://github.com/boostorg/boost.git + +# Install toolchain file & main packaging script +COPY toolchain-host.cmake . +COPY package.sh . diff --git a/package/web/build.sh b/package/web/build.sh new file mode 100755 index 00000000..2bb8a43d --- /dev/null +++ b/package/web/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")" + +docker build -t lisyarus/psemek:package-web . diff --git a/package/web/env-helper.sh b/package/web/env-helper.sh new file mode 100755 index 00000000..d99df790 --- /dev/null +++ b/package/web/env-helper.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# emsdk provides some environment variables that need to be sourced from a file +source /home/emsdk/emsdk_env.sh + +exec "$@" diff --git a/package/web/package.sh b/package/web/package.sh new file mode 100755 index 00000000..695fe547 --- /dev/null +++ b/package/web/package.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -e + +mkdir -p /home/build-host +cd /home/build-host +cmake ../source -DCMAKE_TOOLCHAIN_FILE=/home/toolchain-host.cmake -DCMAKE_INSTALL_PREFIX=/home/tools +cmake --build . -t install -j + +mkdir -p /home/build-target +cd /home/build-target +rm -rf ./* +# Use emsdk wrapper for cmake - it sets some env variables to select the +# right toolchain. +emcmake cmake \ + -DBOOST_SOURCE_ROOT=$(realpath ../source)/psemek/3rdparty/boost \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DPSEMEK_PACKAGE_MODE=ON \ + -DPSEMEK_PACKAGE_HOST=OFF \ + -DPSEMEK_PACKAGE_TARGET=ON \ + -DPSEMEK_STACKTRACE=OFF \ + -DBOOST_SOURCE_ROOT=/home/boost \ + -DPSEMEK_PACKAGE_TOOLS_PATH=/home/tools \ + -DPSEMEK_PACKAGE_COPY_FILES= \ + -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH \ + ../source +cmake --build . -j diff --git a/package/web/toolchain-host.cmake b/package/web/toolchain-host.cmake new file mode 100644 index 00000000..b7ffd05c --- /dev/null +++ b/package/web/toolchain-host.cmake @@ -0,0 +1,26 @@ +set(CMAKE_C_COMPILER gcc) +set(CMAKE_CXX_COMPILER g++) +set(CMAKE_BUILD_TYPE Release) + +set(PSEMEK_PACKAGE_MODE ON) +set(PSEMEK_PACKAGE_ROOT "${CMAKE_CURRENT_LIST_DIR}") +set(PSEMEK_PACKAGE_HOST ON) +set(PSEMEK_PACKAGE_TARGET OFF) +set(PSEMEK_PACKAGE_TOOLS_PATH "${PSEMEK_PACKAGE_ROOT}/tools") +set(PSEMEK_STACKTRACE OFF) + +set(PSEMEK_PACKAGE_COPY_FILES + ${PSEMEK_PACKAGE_ROOT}/sdl2/install/lib/libSDL2-2.0.so.0 +) + +set(PSEMEK_PACKAGE_COPY_FILES_FREETYPE + ${PSEMEK_PACKAGE_ROOT}/libfreetype.so.6 +) + +list(APPEND CMAKE_PREFIX_PATH + "${PSEMEK_PACKAGE_ROOT}/sdl2/install" +) + +set(WGPU_NATIVE_ROOT + "${PSEMEK_PACKAGE_ROOT}/wgpu-native/install" +)