libjxl / libjxl

JPEG XL image format reference implementation
BSD 3-Clause "New" or "Revised" License
2.71k stars 262 forks source link

Build standalone WASM decoder #3860

Open william-silversmith opened 1 month ago

william-silversmith commented 1 month ago

Hi! I'm trying to integrate a JPEG XL WASM binary into an existing project that uses typescript and has a fairly strict style guide. To do this cleanly, I will need a standalone WASM build. Ideally, the project would provide a CMAKE flag that would generate standalone WASM modules.

I'm not that skilled at using cmake, but I managed to hack the ninja.build file it output to create what appears to be a working version. I'm still trying to get my entire pipeline working, but the WASM seems to load and execute exported functions, though I'm doing something wrong with them.

I ran the following cmake command.

cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE=/Users/ws9/code/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DBUILD_SHARED_LIBS=OFF -DJPEGXL_ENABLE_WASM_THREADS=OFF -DENABLE_JPEGLI_DEFAULT=NO

Then I edited the top level ninja.build file under the entry for building jxl_decoder.js. I changed the TARGET_FILE extension from .js to .wasm and added -s STANDALONE_WASM=1 --no-entry to the LINK_FLAGS.

This successfully generated a standalone WASM file with no accompanying JS file.

build tools/wasm_demo/jxl_decoder.js: CXX_EXECUTABLE_LINKER__jxl_decoder_Release tools/wasm_demo/CMakeFiles/jxl_decoder.dir/jxl_decoder.cc.o tools/wasm_demo/CMakeFiles/jxl_decoder.dir/jxl_decompressor.cc.o tools/wasm_demo/CMakeFiles/jxl_decoder.dir/no_png.cc.o | lib/libjxl_extras-internal.a lib/libjxl_threads.a third_party/sjpeg/libsjpeg.a lib/libjxl-internal.a third_party/brotli/libbrotlidec.a third_party/brotli/libbrotlicommon.a third_party/brotli/libbrotlienc.a lib/libjxl_cms.a lib/libjpegli-static.a third_party/highway/libhwy.a || lib/jxl_export lib/libjpegli-static.a lib/libjxl-internal.a lib/libjxl_cms.a lib/libjxl_extras-internal.a lib/libjxl_threads.a third_party/brotli/libbrotlicommon.a third_party/brotli/libbrotlidec.a third_party/brotli/libbrotlienc.a third_party/highway/libhwy.a third_party/sjpeg/libsjpeg.a
  FLAGS = -fno-rtti -O3 -DNDEBUG
  LINK_FLAGS = -s ALLOW_MEMORY_GROWTH=1   -s DISABLE_EXCEPTION_CATCHING=1   -s MODULARIZE=1    -s EXPORT_NAME="JxlDecoderModule"   -s "EXPORTED_FUNCTIONS=[_free, _malloc, _jxlCreateInstance, _jxlDestroyInstance, _jxlFlush, _jxlProcessInput, _jxlDecompress, _jxlCleanup]" -s STANDALONE_WASM=1 --no-entry -O3 -s FILESYSTEM=0 --closure 1 -mnontrapping-fptoint  -fPIE -pie
  LINK_LIBRARIES = lib/libjxl_extras-internal.a  lib/libjxl_threads.a  third_party/sjpeg/libsjpeg.a  lib/libjxl-internal.a  third_party/brotli/libbrotlidec.a  third_party/brotli/libbrotlicommon.a  third_party/brotli/libbrotlienc.a  lib/libjxl_cms.a  lib/libjpegli-static.a  third_party/highway/libhwy.a
  OBJECT_DIR = tools/wasm_demo/CMakeFiles/jxl_decoder.dir
  POST_BUILD = :
  PRE_LINK = :
  TARGET_FILE = tools/wasm_demo/jxl_decoder.wasm
  TARGET_PDB = jxl_decoder.js.dbg

Thanks so much for all the effort put into this project!

mo271 commented 1 month ago

Thanks for the report, I hope
https://github.com/libjxl/libjxl/blob/main/doc/building_wasm.md is still up-to-date...

william-silversmith commented 1 month ago

I had some issues following it exactly, though it could be my installation of emconfigure that was busted. I suspect though that it will do what running cmake naively does, which is generate a WASM module + a JS file to run it (the WASM export symbols are minified and the JS module is written to decode it.

(for example)

var _malloc = Module['_malloc'] = (a0) => (_malloc = Module['_malloc'] = wasmExports['n'])(a0);

In my use case, I want to just use the WASM module directly with no JS file generated. I am writing the user interface manually.

The reason I would like the standalone compilation to be integrated is that the project I'm writing this for won't accept my hacked wasm build for an official merge. They want to be able to do reproducible builds. I suppose I could fork the repo, hack it, and reference that, but ideally it would be officially supported.