From 59b991a61d1c124e4112a7a042f8cbfd9d27e089 Mon Sep 17 00:00:00 2001 From: Hayk Martiros Date: Tue, 3 May 2022 16:57:43 -0700 Subject: [PATCH] Add Rachel's proof of concept for Web Assembly with SymForce --- .gitignore | 1 + symforce/wasm/README.md | 51 ++++++++++++++++++++++++ symforce/wasm/index.html | 58 ++++++++++++++++++++++++++++ symforce/wasm/symforce_bindings.js | 1 + symforce/wasm/symforce_bindings.wasm | 1 + symforce/wasm/wasm_bindings.cc | 57 +++++++++++++++++++++++++++ 6 files changed, 169 insertions(+) create mode 100644 symforce/wasm/README.md create mode 100644 symforce/wasm/index.html create mode 120000 symforce/wasm/symforce_bindings.js create mode 120000 symforce/wasm/symforce_bindings.wasm create mode 100644 symforce/wasm/wasm_bindings.cc diff --git a/.gitignore b/.gitignore index 7c4e6f7c5..6499c2815 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __pycache__/ # Distribution / packaging .Python build/ +build_wasm/ develop-eggs/ dist/ downloads/ diff --git a/symforce/wasm/README.md b/symforce/wasm/README.md new file mode 100644 index 000000000..d61bb1a8e --- /dev/null +++ b/symforce/wasm/README.md @@ -0,0 +1,51 @@ +# Compiling SymForce to Web Assembly + +The goal is to create Javascript (ideally Typescript) bindings +for select C++ implementations within [SymForce](https://symforce.org/) +using [Embind](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html) +so that symforce can be imported and used in JS like any other package. The strategy is similar to how we use pybind for Python. + +Step 1: Source the emscripten SDK: +``` +source ~/projects/wasymforce/.venv/bin/activate +source ~/projects/emsdk/emsdk_env.sh +``` + +Step 2: Run emcmake: +``` +mkdir build_wasm +cd build_wasm + +emcmake cmake .. \ +-DSYMFORCE_BUILD_STATIC_LIBRARIES=ON \ +-DSYMFORCE_BUILD_OPT=ON \ +-DSYMFORCE_BUILD_CC_SYM=OFF \ +-DSYMFORCE_BUILD_EXAMPLES=OFF \ +-DSYMFORCE_BUILD_TESTS=OFF \ +-DSYMFORCE_BUILD_SYMENGINE=OFF \ +-DSYMFORCE_GENERATE_MANIFEST=OFF \ +-DSYMFORCE_BUILD_BENCHMARKS=OFF +``` + +Step 3: Run emmake: +``` +emmake make -j 7 +``` + +Step 4: Run embind: +``` +emcc -lembind -o symforce_bindings.js ../symforce/wasm/wasm_bindings.cc \ +-s LLD_REPORT_UNDEFINED --no-entry \ +-Wl,--whole-archive \ +./_deps/spdlog-build/libspdlog.a \ +./_deps/metis-build/libmetis/libmetis.a \ +./_deps/fmtlib-build/libfmt.a \ +./libsymforce_gen.a \ +./symforce/opt/libsymforce_opt.a \ +-Wl,--no-whole-archive \ +-I ../gen/cpp \ +-I ./_deps/eigen3-src \ +-I ./lcmtypes/cpp \ +-I ../third_party/skymarshal/include +``` + diff --git a/symforce/wasm/index.html b/symforce/wasm/index.html new file mode 100644 index 000000000..a9afd36aa --- /dev/null +++ b/symforce/wasm/index.html @@ -0,0 +1,58 @@ + + + + SymForce WASM Demo + + + + +
+

SymForce Web Assembly Demo

+

Output:

+
+
+ + + + + diff --git a/symforce/wasm/symforce_bindings.js b/symforce/wasm/symforce_bindings.js new file mode 120000 index 000000000..adf7ffdf7 --- /dev/null +++ b/symforce/wasm/symforce_bindings.js @@ -0,0 +1 @@ +../../build_wasm/symforce_bindings.js \ No newline at end of file diff --git a/symforce/wasm/symforce_bindings.wasm b/symforce/wasm/symforce_bindings.wasm new file mode 120000 index 000000000..aec0f49f2 --- /dev/null +++ b/symforce/wasm/symforce_bindings.wasm @@ -0,0 +1 @@ +../../build_wasm/symforce_bindings.wasm \ No newline at end of file diff --git a/symforce/wasm/wasm_bindings.cc b/symforce/wasm/wasm_bindings.cc new file mode 100644 index 000000000..8e0b583aa --- /dev/null +++ b/symforce/wasm/wasm_bindings.cc @@ -0,0 +1,57 @@ +#include +#include + +#include + +using namespace emscripten; + +// TODO(rachel): Use emscripten::val for template params. See: +// https://github.com/emscripten-core/emscripten/issues/4887#issuecomment-283285974 +EMSCRIPTEN_BINDINGS(symforce) +{ + class_("Rot3") + .constructor<>() + .function("data", &sym::Rot3d::Data) + .function("inverse", &sym::Rot3d::Inverse) + .function("toRotationMatrix", &sym::Rot3d::ToRotationMatrix) + .function("toYawPitchRoll", &sym::Rot3d::ToYawPitchRoll) + .class_function("identity", &sym::Rot3d::Identity) + // TODO(hayk): Figure this out. + // .class_function("fromYawPitchRoll", static_cast(&sym::Rot3d::FromYawPitchRoll)) + // Added print for testing. + .function("toString", optional_override([](const sym::Rot3d &self) { + std::stringstream buf; + buf << self.Data().transpose() << std::endl; + return buf.str(); + })); + + // Need Eigen return values. + + // TODO(rachel): How to handle overloads? Errors: + // JS: Cannot register public name 'Matrix' twice. + // emcc: template argument for non-type template parameter must be an expression + class_>("Matrix33") + .constructor<>() + .function("toString", optional_override([](const Eigen::Matrix &self) { + std::stringstream buf; + buf << self << std::endl; + return buf.str(); + })); + + class_>("Matrix31") + .constructor<>() + .function("toString", optional_override([](const Eigen::Matrix &self) { + std::stringstream buf; + buf << self << std::endl; + return buf.str(); + })); + + class_>("Matrix41") + .constructor<>() + // Added print for testing. + .function("toString", optional_override([](const Eigen::Matrix &self) { + std::stringstream buf; + buf << self << std::endl; + return buf.str(); + })); +}