From 25e094e81dbbc6fe5b61438e9fd46cc0004070e5 Mon Sep 17 00:00:00 2001 From: Ben Polinsky <78756012+ben-polinsky@users.noreply.github.com> Date: Mon, 19 Oct 2026 09:51:05 -0400 Subject: [PATCH 1/2] Fix missing cPackUASTC* constants in the WebGL encoder The cPackUASTC* values are members of an anonymous enum. embind's constant() registers a constant against the type of its value, and no binding exists for an anonymous enum type. embind therefore waits for that type forever, and the constants never appear on the module. Module.cPackUASTCLevelDefault and the others are undefined in the shipped basis_encoder.js. Cast the values to uint32_t so that they bind as plain numbers. EMBIND_AOT=1 makes this a link error ("Missing binding for type"), so this change is also necessary to build the encoder with that flag. --- webgl/transcoder/basis_wrappers.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/webgl/transcoder/basis_wrappers.cpp b/webgl/transcoder/basis_wrappers.cpp index c80d4df..ac8ec88 100644 --- a/webgl/transcoder/basis_wrappers.cpp +++ b/webgl/transcoder/basis_wrappers.cpp @@ -3347,17 +3347,17 @@ EMSCRIPTEN_BINDINGS(basis_codec) { constant("ASTC_HDR_MAX_VAL", basist::ASTC_HDR_MAX_VAL); // UASTC LDR/HDR flags/options - constant("cPackUASTCLevelFastest", cPackUASTCLevelFastest); - constant("cPackUASTCLevelFaster", cPackUASTCLevelFaster); - constant("cPackUASTCLevelDefault", cPackUASTCLevelDefault); - constant("cPackUASTCLevelSlower", cPackUASTCLevelSlower); - constant("cPackUASTCLevelVerySlow", cPackUASTCLevelVerySlow); - constant("cPackUASTCLevelMask", cPackUASTCLevelMask); - constant("cPackUASTCFavorUASTCError", cPackUASTCFavorUASTCError); - constant("cPackUASTCFavorBC7Error", cPackUASTCFavorBC7Error); - constant("cPackUASTCETC1FasterHints", cPackUASTCETC1FasterHints); - constant("cPackUASTCETC1FastestHints", cPackUASTCETC1FastestHints); - constant("cPackUASTCETC1DisableFlipAndIndividual", cPackUASTCETC1DisableFlipAndIndividual); + constant("cPackUASTCLevelFastest", static_cast(cPackUASTCLevelFastest)); + constant("cPackUASTCLevelFaster", static_cast(cPackUASTCLevelFaster)); + constant("cPackUASTCLevelDefault", static_cast(cPackUASTCLevelDefault)); + constant("cPackUASTCLevelSlower", static_cast(cPackUASTCLevelSlower)); + constant("cPackUASTCLevelVerySlow", static_cast(cPackUASTCLevelVerySlow)); + constant("cPackUASTCLevelMask", static_cast(cPackUASTCLevelMask)); + constant("cPackUASTCFavorUASTCError", static_cast(cPackUASTCFavorUASTCError)); + constant("cPackUASTCFavorBC7Error", static_cast(cPackUASTCFavorBC7Error)); + constant("cPackUASTCETC1FasterHints", static_cast(cPackUASTCETC1FasterHints)); + constant("cPackUASTCETC1FastestHints", static_cast(cPackUASTCETC1FastestHints)); + constant("cPackUASTCETC1DisableFlipAndIndividual", static_cast(cPackUASTCETC1DisableFlipAndIndividual)); constant("UASTC_RDO_DEFAULT_MAX_ALLOWED_RMS_INCREASE_RATIO", UASTC_RDO_DEFAULT_MAX_ALLOWED_RMS_INCREASE_RATIO); constant("UASTC_RDO_DEFAULT_SKIP_BLOCK_RMS_THRESH", UASTC_RDO_DEFAULT_SKIP_BLOCK_RMS_THRESH); From b15211474da1cf7c3a408a40dd22f18b15514b22 Mon Sep 17 00:00:00 2001 From: Ben Polinsky <78756012+ben-polinsky@users.noreply.github.com> Date: Mon, 19 Oct 2026 09:51:05 -0400 Subject: [PATCH 2/2] Add opt-in WebGL builds that do not use eval()/new Function() With the default DYNAMIC_EXECUTION=1, embind generates its invokers at runtime with new Function(). A Content-Security-Policy without 'unsafe-eval' blocks this, so the transcoder and encoder fail to start with an EvalError. Add two CMake options to webgl/transcoder and webgl/encoder: - NO_DYNAMIC_EXECUTION (default OFF) adds -s DYNAMIC_EXECUTION=0. - EMBIND_AOT (default ON, only used with NO_DYNAMIC_EXECUTION) adds -s EMBIND_AOT=1. This generates the invokers at build time, so embind calls stay as fast as with the default build. The default build does not change. The .wasm output is byte-identical in all configurations; only the JS glue changes. Fixes #464 --- webgl/build.sh | 3 ++- webgl/encoder/CMakeLists.txt | 17 +++++++++++++++++ webgl/encoder/README.md | 19 +++++++++++++++++++ webgl/transcoder/CMakeLists.txt | 21 ++++++++++++++++++++- webgl/transcoder/README.md | 19 +++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) diff --git a/webgl/build.sh b/webgl/build.sh index d3b38ff..92fd036 100755 --- a/webgl/build.sh +++ b/webgl/build.sh @@ -11,6 +11,7 @@ # ./build.sh --clean # make clean + wipe CMake cache first (full rebuild) # ./build.sh --slow # serial build (plain make, one file at a time) # ./build.sh encoder -DKTX2_ZSTANDARD=OFF -DCMAKE_BUILD_TYPE=Debug +# ./build.sh -DNO_DYNAMIC_EXECUTION=ON # no eval/new Function (strict CSP) # # Any -D... argument is forwarded to cmake. Runnable from any directory. # @@ -35,7 +36,7 @@ for a in "$@"; do --slow) SLOW=1 ;; transcoder|encoder) TARGETS+=("$a") ;; -D*) CMAKE_ARGS+=("$a") ;; - -h|--help) sed -n '3,15p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + -h|--help) sed -n '3,16p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; *) die "unknown argument: '$a' (try --help)" ;; esac done diff --git a/webgl/encoder/CMakeLists.txt b/webgl/encoder/CMakeLists.txt index 846cdea..478c0cd 100644 --- a/webgl/encoder/CMakeLists.txt +++ b/webgl/encoder/CMakeLists.txt @@ -5,8 +5,18 @@ project(basisu_encoder_js) option(KTX2_ZSTANDARD "Enable KTX2 Zstandard support" TRUE) option(SUPPORT_ASTCENC "Enable astcenc support" FALSE) +# Build JavaScript glue that never calls eval() or new Function(), so the module +# loads under a Content-Security-Policy without 'unsafe-eval' (OFF by default). +option(NO_DYNAMIC_EXECUTION "Disallow eval()/new Function() in the JS glue" FALSE) + +# With NO_DYNAMIC_EXECUTION, generate the embind call wrappers at build time (fast) +# instead of using the slower generic wrappers. No effect unless NO_DYNAMIC_EXECUTION is ON. +option(EMBIND_AOT "Generate embind wrappers at build time" TRUE) + message("KTX2_ZSTANDARD=${KTX2_ZSTANDARD}") message("SUPPORT_ASTCENC=${SUPPORT_ASTCENC}") +message("NO_DYNAMIC_EXECUTION=${NO_DYNAMIC_EXECUTION}") +message("EMBIND_AOT=${EMBIND_AOT}") # Only for Emscripten builds. if(EMSCRIPTEN) @@ -132,6 +142,13 @@ if(EMSCRIPTEN) # Base link flags set(LINK_BASE "--bind -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=134217728 -s STACK_SIZE=2097152 -s MODULARIZE=1 -s EXPORT_NAME=BASIS -s EXPORTED_RUNTIME_METHODS=['HEAP8']") + if(NO_DYNAMIC_EXECUTION) + set(LINK_BASE "${LINK_BASE} -s DYNAMIC_EXECUTION=0") + if(EMBIND_AOT) + set(LINK_BASE "${LINK_BASE} -s EMBIND_AOT=1") + endif() + endif() + set(LINK_THREADS "-s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=32 -s ENVIRONMENT=web,worker") set(LINK_WASM64 "-s MEMORY64=1 -sWASM_BIGINT=1 --profiling-funcs") diff --git a/webgl/encoder/README.md b/webgl/encoder/README.md index 2055d02..f884434 100644 --- a/webgl/encoder/README.md +++ b/webgl/encoder/README.md @@ -5,3 +5,22 @@ cd webgl/encoder/build/ emcmake cmake ../ make ``` + +### Strict Content-Security-Policy (no `unsafe-eval`) + +By default, the generated JavaScript glue uses `new Function()` to build its +embind bindings at runtime. A Content-Security-Policy without `'unsafe-eval'` +blocks this, and module startup fails with an `EvalError`. To build glue that +never calls `eval()` or `new Function()`, add `-DNO_DYNAMIC_EXECUTION=ON`: + +```shell +emcmake cmake ../ -DNO_DYNAMIC_EXECUTION=ON +make +``` + +This adds `-s DYNAMIC_EXECUTION=0 -s EMBIND_AOT=1` to the link flags. The +`.wasm` file does not change. `EMBIND_AOT` generates the bindings at build +time, so JavaScript-to-WASM calls stay as fast as in the default build. The +page still needs `'wasm-unsafe-eval'` in `script-src` to compile the +WebAssembly module. Use `-DEMBIND_AOT=OFF` to get the smaller, generic +bindings, which are slower per call. diff --git a/webgl/transcoder/CMakeLists.txt b/webgl/transcoder/CMakeLists.txt index 159f87a..8703250 100644 --- a/webgl/transcoder/CMakeLists.txt +++ b/webgl/transcoder/CMakeLists.txt @@ -10,8 +10,18 @@ option(KTX2 "KTX2" TRUE) # If you know you'll never be transcoding KTX2 files using this feature you can set KTX2_ZSTANDARD to 0. option(KTX2_ZSTANDARD "KTX2_ZSTANDARD" TRUE) +# Use "cmake -DNO_DYNAMIC_EXECUTION=TRUE" to build JavaScript glue that never calls eval() or new Function(). +# This lets the module load under a Content-Security-Policy without 'unsafe-eval'. +option(NO_DYNAMIC_EXECUTION "NO_DYNAMIC_EXECUTION" FALSE) + +# With NO_DYNAMIC_EXECUTION, generate the embind call wrappers at build time (fast) instead of +# using the slower generic wrappers. Only has an effect when NO_DYNAMIC_EXECUTION is TRUE. +option(EMBIND_AOT "EMBIND_AOT" TRUE) + message("KTX2=${KTX2}") message("KTX2_ZSTANDARD=${KTX2_ZSTANDARD}") +message("NO_DYNAMIC_EXECUTION=${NO_DYNAMIC_EXECUTION}") +message("EMBIND_AOT=${EMBIND_AOT}") if (EMSCRIPTEN) set(CMAKE_CXX_STANDARD 17) @@ -61,8 +71,17 @@ if (EMSCRIPTEN) target_include_directories(basis_transcoder.js PRIVATE ../../transcoder) + set(TRANSCODER_LINK_FLAGS "--bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS -s ASSERTIONS=0 -s EXPORTED_RUNTIME_METHODS=['HEAP8']") + + if (NO_DYNAMIC_EXECUTION) + set(TRANSCODER_LINK_FLAGS "${TRANSCODER_LINK_FLAGS} -s DYNAMIC_EXECUTION=0") + if (EMBIND_AOT) + set(TRANSCODER_LINK_FLAGS "${TRANSCODER_LINK_FLAGS} -s EMBIND_AOT=1") + endif() + endif() + set_target_properties(basis_transcoder.js PROPERTIES OUTPUT_NAME "basis_transcoder" SUFFIX ".js" - LINK_FLAGS "--bind -s ALLOW_MEMORY_GROWTH=1 -O3 -s MALLOC=emmalloc -s MODULARIZE=1 -s EXPORT_NAME=BASIS -s ASSERTIONS=0 -s EXPORTED_RUNTIME_METHODS=['HEAP8']") + LINK_FLAGS "${TRANSCODER_LINK_FLAGS}") endif() diff --git a/webgl/transcoder/README.md b/webgl/transcoder/README.md index 1553a3a..551c43a 100644 --- a/webgl/transcoder/README.md +++ b/webgl/transcoder/README.md @@ -7,3 +7,22 @@ cd webgl/transcoder/build/ emcmake cmake ../ make ``` + +### Strict Content-Security-Policy (no `unsafe-eval`) + +By default, the generated JavaScript glue uses `new Function()` to build its +embind bindings at runtime. A Content-Security-Policy without `'unsafe-eval'` +blocks this, and module startup fails with an `EvalError`. To build glue that +never calls `eval()` or `new Function()`, add `-DNO_DYNAMIC_EXECUTION=ON`: + +```shell +emcmake cmake ../ -DNO_DYNAMIC_EXECUTION=ON +make +``` + +This adds `-s DYNAMIC_EXECUTION=0 -s EMBIND_AOT=1` to the link flags. The +`.wasm` file does not change. `EMBIND_AOT` generates the bindings at build +time, so JavaScript-to-WASM calls stay as fast as in the default build. The +page still needs `'wasm-unsafe-eval'` in `script-src` to compile the +WebAssembly module. Use `-DEMBIND_AOT=OFF` to get the smaller, generic +bindings, which are slower per call.