From f8c9dc9c8bd52c84ddb5b62fb3b986007b208e55 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Thu, 28 Nov 2024 00:41:40 +0800 Subject: [PATCH] Enable emscripten build with parallelization (#1045) * port tbb changes * format --- .github/workflows/manifold.yml | 2 +- CMakeLists.txt | 4 ---- bindings/wasm/CMakeLists.txt | 4 ++++ bindings/wasm/bindings.cpp | 26 ++++++++++++++++++++++++++ bindings/wasm/bindings.js | 3 +++ bindings/wasm/examples/vite.config.js | 6 ++++++ bindings/wasm/fixup.py | 19 +++++++++++++++++++ bindings/wasm/helpers.cpp | 2 +- cmake/manifoldDeps.cmake | 4 ++++ flake.nix | 6 +++++- test/test_main.cpp | 21 +++++++++++++++++++++ 11 files changed, 90 insertions(+), 7 deletions(-) create mode 100644 bindings/wasm/fixup.py diff --git a/.github/workflows/manifold.yml b/.github/workflows/manifold.yml index 61cc8d7c0..9a81b41ef 100644 --- a/.github/workflows/manifold.yml +++ b/.github/workflows/manifold.yml @@ -286,7 +286,7 @@ jobs: timeout-minutes: 30 strategy: matrix: - variant: [manifold-none, manifold-tbb, manifold-js, manifold3d] + variant: [manifold-none, manifold-tbb, manifold-js, manifold-js-tbb, manifold3d] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 90b688cc3..7dc2af0af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,10 +121,6 @@ if(EMSCRIPTEN) if(MANIFOLD_PAR) set(CMAKE_THREAD_LIBS_INIT "-pthread") add_compile_options(-pthread) - # PTHREAD_POOL_SIZE is set to 4 for OK-ish performance, in general we are - # not getting too much speedup for very large number of cores, and a lot of - # CPUs now have 4 cores... - add_link_options(-sPTHREAD_POOL_SIZE=4) # mimalloc is needed for good performance add_link_options(-sMALLOC=mimalloc) # The default stack size apparently causes problem when parallelization is diff --git a/bindings/wasm/CMakeLists.txt b/bindings/wasm/CMakeLists.txt index 190e6142a..2c28acbc0 100644 --- a/bindings/wasm/CMakeLists.txt +++ b/bindings/wasm/CMakeLists.txt @@ -49,6 +49,10 @@ add_custom_target( add_custom_command( TARGET js_deps POST_BUILD + # fix js file + COMMAND + ${Python_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/fixup.py + ${CMAKE_CURRENT_BINARY_DIR}/manifold.js # copy WASM build back here for publishing to npm COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/manifold.* diff --git a/bindings/wasm/bindings.cpp b/bindings/wasm/bindings.cpp index c48fcfd30..c396b4e42 100644 --- a/bindings/wasm/bindings.cpp +++ b/bindings/wasm/bindings.cpp @@ -22,6 +22,30 @@ #include "manifold/manifold.h" #include "manifold/polygon.h" +#if (MANIFOLD_PAR == 1) +#include + +#include +#endif + +// https://github.com/oneapi-src/oneTBB/blob/master/WASM_Support.md#limitations +void initTBB() { +#if (MANIFOLD_PAR == 1) + int num_threads = tbb::this_task_arena::max_concurrency(); + std::atomic barrier{num_threads}; + tbb::parallel_for( + 0, num_threads, + [&barrier](int) { + barrier--; + while (barrier > 0) { + // Send browser thread to event loop + std::this_thread::yield(); + } + }, + tbb::static_partitioner{}); +#endif +} + using namespace emscripten; using namespace manifold; @@ -195,4 +219,6 @@ EMSCRIPTEN_BINDINGS(whatever) { function("setCircularSegments", &Quality::SetCircularSegments); function("getCircularSegments", &Quality::GetCircularSegments); function("resetToCircularDefaults", &Quality::ResetToDefaults); + + function("initTBB", &initTBB); } diff --git a/bindings/wasm/bindings.js b/bindings/wasm/bindings.js index 2253fe735..e0daa3124 100644 --- a/bindings/wasm/bindings.js +++ b/bindings/wasm/bindings.js @@ -17,6 +17,9 @@ Module.setup = function() { if (_ManifoldInitialized) return; _ManifoldInitialized = true; + // warmup tbb for emscripten, according to + // https://github.com/oneapi-src/oneTBB/blob/master/WASM_Support.md#limitations + Module.initTBB(); // conversion utilities function toVec(vec, list, f = x => x) { diff --git a/bindings/wasm/examples/vite.config.js b/bindings/wasm/examples/vite.config.js index b5e5fadd1..9676c2d75 100644 --- a/bindings/wasm/examples/vite.config.js +++ b/bindings/wasm/examples/vite.config.js @@ -7,6 +7,12 @@ export default defineConfig({ worker: { format: 'es', }, + server: { + headers: { + 'Cross-Origin-Embedder-Policy': 'require-corp', + 'Cross-Origin-Opener-Policy': 'same-origin', + }, + }, build: { target: 'esnext', sourcemap: true, diff --git a/bindings/wasm/fixup.py b/bindings/wasm/fixup.py new file mode 100644 index 000000000..01d17ffac --- /dev/null +++ b/bindings/wasm/fixup.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +import sys + +if len(sys.argv) != 2: + print("Usage: python fixup.py ") + +filename = sys.argv[1] +with open(filename, "r") as file: + data = file.read() + +data = data.replace( + 'var workerOptions={type:"module",workerData:"em-pthread",name:"em-pthread"};', "" +) +data = data.replace( + "workerOptions", '{type:"module",workerData:"em-pthread",name:"em-pthread"}' +) + +with open(filename, "w") as file: + file.write(data) diff --git a/bindings/wasm/helpers.cpp b/bindings/wasm/helpers.cpp index 66bf70b9e..c9104017a 100644 --- a/bindings/wasm/helpers.cpp +++ b/bindings/wasm/helpers.cpp @@ -208,7 +208,7 @@ Manifold SetProperties(Manifold& manifold, int numProp, uintptr_t funcPtr) { Manifold LevelSet(uintptr_t funcPtr, Box bounds, double edgeLength, double level, double tolerance) { double (*f)(const vec3&) = reinterpret_cast(funcPtr); - return Manifold::LevelSet(f, bounds, edgeLength, level, tolerance); + return Manifold::LevelSet(f, bounds, edgeLength, level, tolerance, false); } std::vector Split(Manifold& a, Manifold& b) { diff --git a/cmake/manifoldDeps.cmake b/cmake/manifoldDeps.cmake index a0e1af0ec..552b61638 100644 --- a/cmake/manifoldDeps.cmake +++ b/cmake/manifoldDeps.cmake @@ -218,6 +218,10 @@ if(MANIFOLD_TEST) endif() endif() +if(EMSCRIPTEN) + find_package(Python REQUIRED) +endif() + if(MANIFOLD_FUZZ) logmissingdep("fuzztest" , "MANIFOLD_FUZZ") FetchContent_Declare( diff --git a/flake.nix b/flake.nix index 8ca07af06..e24baafb8 100644 --- a/flake.nix +++ b/flake.nix @@ -112,7 +112,7 @@ cd ../ ''; }; - manifold-emscripten = { doCheck ? true }: pkgs.buildEmscriptenPackage { + manifold-emscripten = { doCheck ? true, parallel ? false }: pkgs.buildEmscriptenPackage { name = "manifold-js"; version = manifold-version; src = self; @@ -126,7 +126,9 @@ mkdir build cd build emcmake cmake -DCMAKE_BUILD_TYPE=Release \ + -DMANIFOLD_PAR=${if parallel then "ON" else "OFF"} \ -DFETCHCONTENT_SOURCE_DIR_GOOGLETEST=${gtest-src} \ + -DFETCHCONTENT_SOURCE_DIR_TBB=${onetbb-src} \ -DFETCHCONTENT_SOURCE_DIR_CLIPPER2=../clipper2 .. ''; buildPhase = '' @@ -148,6 +150,7 @@ manifold-tbb = manifold { }; manifold-none = manifold { parallel = false; }; manifold-js = manifold-emscripten { }; + manifold-js-tbb = manifold-emscripten { parallel = true; }; # but how should we make it work with other python versions? manifold3d = with pkgs.python3Packages; buildPythonPackage { pname = "manifold3d"; @@ -197,6 +200,7 @@ # useful tools clang-tools_18 clang_18 + llvmPackages_18.bintools tracy ]; }; diff --git a/test/test_main.cpp b/test/test_main.cpp index b3e51ac82..dfa78e5f9 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -17,6 +17,10 @@ #include "manifold/polygon.h" #include "test.h" +#if (MANIFOLD_PAR == 1) +#include +#endif + // we need to call some tracy API to establish the connection #if __has_include() #include @@ -45,6 +49,23 @@ int main(int argc, char** argv) { const char* name = "test setup"; FrameMarkStart(name); + // warmup tbb for emscripten, according to + // https://github.com/oneapi-src/oneTBB/blob/master/WASM_Support.md#limitations +#if defined(__EMSCRIPTEN__) && (MANIFOLD_PAR == 1) + int num_threads = tbb::this_task_arena::max_concurrency(); + std::atomic barrier{num_threads}; + tbb::parallel_for( + 0, num_threads, + [&barrier](int) { + barrier--; + while (barrier > 0) { + // Send browser thread to event loop + std::this_thread::yield(); + } + }, + tbb::static_partitioner{}); +#endif + for (int i = 1; i < argc; i++) { if (argv[i][0] != '-') { fprintf(stderr, "Unknown option: %s\n", argv[i]);