diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index 1fa718b3..07bf120b 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -8,6 +8,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/../cmake) option(TINYUSDZ_WASM64 "Use wasm64(memory64). Available only for Chrome and Firefox as of 2025 Jul" OFF) +option(TINYUSDZ_WASM_DEBUG "Enable WASM debug mode with source maps and assertions" OFF) if (TINYUSDZ_WASM64) set(BUILD_TARGET "tinyusdz_64") @@ -25,12 +26,24 @@ set(CMAKE_CXX_EXTENSIONS OFF) if (CMAKE_BUILD_TYPE STREQUAL "Debug") - if (TINYUSDZ_WASM64) - set(CMAKE_CXX_FLAGS_DEBUG "-g4 -sMEMORY64") - set(CMAKE_C_FLAGS_DEBUG "-g4 -sMEMORY64") + if (TINYUSDZ_WASM_DEBUG) + # Enhanced debug mode with source maps + if (TINYUSDZ_WASM64) + set(CMAKE_CXX_FLAGS_DEBUG "-g -gsource-map -sMEMORY64") + set(CMAKE_C_FLAGS_DEBUG "-g -gsource-map -sMEMORY64") + else() + set(CMAKE_CXX_FLAGS_DEBUG "-g -gsource-map") + set(CMAKE_C_FLAGS_DEBUG "-g -gsource-map") + endif() else() - set(CMAKE_CXX_FLAGS_DEBUG "-g4") # -sMEMORY64" - set(CMAKE_C_FLAGS_DEBUG "-g4") # -sMEMORY64" + # Standard debug mode + if (TINYUSDZ_WASM64) + set(CMAKE_CXX_FLAGS_DEBUG "-g4 -sMEMORY64") + set(CMAKE_C_FLAGS_DEBUG "-g4 -sMEMORY64") + else() + set(CMAKE_CXX_FLAGS_DEBUG "-g4") + set(CMAKE_C_FLAGS_DEBUG "-g4") + endif() endif() elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") @@ -109,18 +122,37 @@ endif() # flags = try to lower heap memory use if (CMAKE_BUILD_TYPE STREQUAL "Debug") - set(TINYUSDZ_EMCC_LINK_FLAGS "-g4 -sENVIRONMENT='web,worker,node' \ - -sMALLOC=emmalloc \ - -sSTACK_SIZE=1024000 \ - -sALLOW_MEMORY_GROWTH=1 \ - -sFILESYSTEM=0 \ - -sSAFE_HEAP=1 \ - -sWASM_BIGINT=1 \ - -sMODULARIZE=1 -sEXPORT_ES6 \ - -sINVOKE_RUN=0 --bind \ - -sDISABLE_EXCEPTION_CATCHING=0 \ - -sNO_DISABLE_EXCEPTION_CATCHING \ - --source-map-base http://localhost:5173/ ") + if (TINYUSDZ_WASM_DEBUG) + # Enhanced debug mode with source maps, stack traces, and runtime checks + set(TINYUSDZ_EMCC_LINK_FLAGS "-g -gsource-map -sENVIRONMENT='web,worker,node' \ + -sMALLOC=emmalloc \ + -sSTACK_SIZE=1024000 \ + -sALLOW_MEMORY_GROWTH=1 \ + -sFILESYSTEM=0 \ + -sSAFE_HEAP=1 \ + -sSTACK_OVERFLOW_CHECK=2 \ + -sASSERTIONS=2 \ + -sWASM_BIGINT=1 \ + -sMODULARIZE=1 -sEXPORT_ES6 \ + -sINVOKE_RUN=0 --bind \ + -sDISABLE_EXCEPTION_CATCHING=0 \ + -sNO_DISABLE_EXCEPTION_CATCHING \ + --source-map-base http://localhost:5173/ ") + else() + # Standard debug mode + set(TINYUSDZ_EMCC_LINK_FLAGS "-g4 -sENVIRONMENT='web,worker,node' \ + -sMALLOC=emmalloc \ + -sSTACK_SIZE=1024000 \ + -sALLOW_MEMORY_GROWTH=1 \ + -sFILESYSTEM=0 \ + -sSAFE_HEAP=1 \ + -sWASM_BIGINT=1 \ + -sMODULARIZE=1 -sEXPORT_ES6 \ + -sINVOKE_RUN=0 --bind \ + -sDISABLE_EXCEPTION_CATCHING=0 \ + -sNO_DISABLE_EXCEPTION_CATCHING \ + --source-map-base http://localhost:5173/ ") + endif() else() set(TINYUSDZ_EMCC_LINK_FLAGS "-Oz -sENVIRONMENT='web,worker,node' \ -sMALLOC=emmalloc \ diff --git a/web/DEBUG_SOURCEMAP.md b/web/DEBUG_SOURCEMAP.md new file mode 100644 index 00000000..5fdb67b7 --- /dev/null +++ b/web/DEBUG_SOURCEMAP.md @@ -0,0 +1,123 @@ +# WASM Debug Mode with Source Maps + +This document explains how to build TinyUSDZ WASM with source maps for better debugging. + +## Quick Start + +```bash +# Build with source maps +./bootstrap-linux-debug-sourcemap.sh +cd build_debug_sourcemap +make -j8 + +# The output will include: +# - js/src/tinyusdz/tinyusdz.js +# - js/src/tinyusdz/tinyusdz.wasm +# - js/src/tinyusdz/tinyusdz.wasm.map (source map) +``` + +## What You Get + +When building with `-DTINYUSDZ_WASM_DEBUG=ON`, you get: + +1. **Source Maps** (`-gsource-map`) + - Maps WASM code back to C++ source files + - Enables viewing C++ source in browser DevTools + +2. **Enhanced Stack Traces** (`-sDEMANGLE_SUPPORT=1`) + - C++ function names are demangled + - Shows actual function names instead of mangled symbols + +3. **Runtime Checks** + - `-sASSERTIONS=2` - Runtime assertion checks + - `-sSTACK_OVERFLOW_CHECK=2` - Detect stack overflows + - `-sSAFE_HEAP=1` - Detect memory access errors + +## Browser Setup + +1. Open Chrome DevTools (F12) +2. Go to Settings (⚙️) → Preferences +3. Enable "Enable JavaScript source maps" +4. When an abort/crash occurs, you'll see C++ source lines in the stack trace + +## Example Output + +### Without Source Maps: +``` +RuntimeError: abort(Error) at Error + at abort (wasm-function[1234]:0x5678) +``` + +### With Source Maps: +``` +RuntimeError: abort(MaterialX conversion failed) at Error + at abort (src/tydra/materialx.cc:142:5) + at convertOpenPBR (src/tydra/materialx.cc:89:12) + at setupMesh (binding.cc:345:3) +``` + +## Manual Build + +If you prefer to configure manually: + +```bash +mkdir build_debug_sourcemap +cd build_debug_sourcemap + +emcmake cmake .. \ + -DCMAKE_BUILD_TYPE=Debug \ + -DTINYUSDZ_WASM_DEBUG=ON + +make -j8 +``` + +## CMake Options + +- `TINYUSDZ_WASM_DEBUG=ON` - Enable source maps and enhanced debugging +- `TINYUSDZ_WASM64=ON` - Use WASM64/Memory64 (optional, requires newer browsers) + +## File Size Impact + +Source maps increase file sizes: + +| Build Type | .wasm Size | .wasm.map Size | +|------------|------------|----------------| +| Release | ~2-5 MB | N/A | +| Debug | ~5-10 MB | N/A | +| Debug + SourceMap | ~5-10 MB | ~15-30 MB | + +**Note:** The `.wasm.map` file is only loaded when DevTools is open, so it doesn't affect production performance. + +## Troubleshooting + +### Source maps not loading + +1. Check that `.wasm.map` file exists next to `.wasm` file +2. Verify the web server serves `.map` files (check MIME type) +3. Check browser console for CORS errors +4. Verify `--source-map-base http://localhost:5173/` matches your server URL + +### Source files not showing + +1. The source map contains paths relative to the build directory +2. Ensure source files haven't moved since build +3. Check browser DevTools → Sources tab for file tree + +### Different dev server port + +Edit `web/CMakeLists.txt` line 141: +```cmake +--source-map-base http://localhost:YOUR_PORT/ +``` + +## Production Builds + +For production, use the standard build without source maps: + +```bash +./bootstrap-linux.sh +cd build +make -j8 +``` + +This produces optimized, small WASM files without debug overhead. diff --git a/web/bootstrap-linux-debug-sourcemap.sh b/web/bootstrap-linux-debug-sourcemap.sh new file mode 100755 index 00000000..0c51525b --- /dev/null +++ b/web/bootstrap-linux-debug-sourcemap.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Build TinyUSDZ WASM with source maps and enhanced debugging +# This enables C++ source line visibility in browser DevTools on abort/crash + +rm -rf build_debug_sourcemap +mkdir build_debug_sourcemap + +emcmake cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DTINYUSDZ_WASM_DEBUG=ON \ + -DCMAKE_VERBOSE_MAKEFILE=1 \ + -Bbuild_debug_sourcemap + +echo "" +echo "Build configured with source maps enabled." +echo "To build, run:" +echo " cd build_debug_sourcemap && make -j$(nproc)" +echo "" +echo "Output will include:" +echo " - tinyusdz.wasm.map (source map file)" +echo " - Enhanced stack traces with C++ source lines" +echo " - Additional runtime checks (SAFE_HEAP, STACK_OVERFLOW_CHECK)" +echo "" diff --git a/web/bootstrap-linux-debug-wasm64-sourcemap.sh b/web/bootstrap-linux-debug-wasm64-sourcemap.sh new file mode 100755 index 00000000..f2507ede --- /dev/null +++ b/web/bootstrap-linux-debug-wasm64-sourcemap.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# Build TinyUSDZ WASM with source maps and enhanced debugging +# This enables C++ source line visibility in browser DevTools on abort/crash + +rm -rf build_debug_sourcemap_64 +mkdir build_debug_sourcemap_64 + +emcmake cmake \ + -DCMAKE_BUILD_TYPE=Debug \ + -DTINYUSDZ_WASM_DEBUG=ON \ + -DTINYUSDZ_WASM64=1 \ + -DCMAKE_VERBOSE_MAKEFILE=1 \ + -Bbuild_debug_sourcemap_64 + +echo "" +echo "Build configured with source maps enabled." +echo "To build, run:" +echo " cd build_debug_sourcemap_64 && make -j$(nproc)" +echo "" +echo "Output will include:" +echo " - tinyusdz.wasm.map (source map file)" +echo " - Enhanced stack traces with C++ source lines" +echo " - Additional runtime checks (SAFE_HEAP, STACK_OVERFLOW_CHECK)" +echo "" diff --git a/web/js/animation.js b/web/js/animation.js index d14effd7..34291f45 100644 --- a/web/js/animation.js +++ b/web/js/animation.js @@ -759,7 +759,7 @@ async function loadUSDModel() { }; // Build Three.js node from USD with MaterialX/OpenPBR support - const threeNode = TinyUSDZLoaderUtils.buildThreeNode(usdRootNode, defaultMtl, usd_scene, options); + const threeNode = await TinyUSDZLoaderUtils.buildThreeNode(usdRootNode, defaultMtl, usd_scene, options); // Store USD scene reference for material reloading threeNode.traverse((child) => { @@ -2277,7 +2277,7 @@ async function loadUSDFromArrayBuffer(arrayBuffer, filename) { }; // Build Three.js node from USD with MaterialX/OpenPBR support - const threeNode = TinyUSDZLoaderUtils.buildThreeNode(usdRootNode, defaultMtl, usd_scene, options); + const threeNode = await TinyUSDZLoaderUtils.buildThreeNode(usdRootNode, defaultMtl, usd_scene, options); // Store USD scene reference for material reloading threeNode.traverse((child) => { diff --git a/web/js/src/tinyusdz/TinyUSDZLoaderUtils.js b/web/js/src/tinyusdz/TinyUSDZLoaderUtils.js index 4418b126..55ea8a1b 100644 --- a/web/js/src/tinyusdz/TinyUSDZLoaderUtils.js +++ b/web/js/src/tinyusdz/TinyUSDZLoaderUtils.js @@ -641,7 +641,7 @@ class TinyUSDZLoaderUtils extends LoaderUtils { return geometry; } - static setupMesh(mesh /* TinyUSDZLoaderNative::RenderMesh */, defaultMtl, usdScene, options) { + static async setupMesh(mesh /* TinyUSDZLoaderNative::RenderMesh */, defaultMtl, usdScene, options) { const geometry = this.convertUsdMeshToThreeMesh(mesh); @@ -654,13 +654,47 @@ class TinyUSDZLoaderUtils extends LoaderUtils { mtl = defaultMtl || normalMtl } else { - const usdMaterial = usdScene.getMaterial(mesh.materialId); - //console.log("usdMaterial:", usdMaterial); - + // Validate materialId before attempting to get material + // materialId can be undefined, -1 (no material), or out of range + const hasMaterial = mesh.materialId !== undefined && mesh.materialId >= 0; - const pbrMaterial = this.convertUsdMaterialToMeshPhysicalMaterial(usdMaterial, usdScene); - //console.log("pbrMaterial:", pbrMaterial); + // Get material data in JSON format to access OpenPBR/MaterialX data + // Using getMaterialWithFormat ensures we get the full material structure including OpenPBR + let usdMaterialData = null; + if (hasMaterial) { + if (typeof usdScene.getMaterialWithFormat === 'function') { + const result = usdScene.getMaterialWithFormat(mesh.materialId, 'json'); + if (!result.error) { + usdMaterialData = JSON.parse(result.data); + } else { + console.warn(`Failed to get material ${mesh.materialId} with format: ${result.error}`); + } + } else { + // Fallback to getMaterial if getMaterialWithFormat is not available + usdMaterialData = usdScene.getMaterial(mesh.materialId); + } + } + console.log(`Mesh materialId: ${mesh.materialId}, hasMaterial: ${hasMaterial}, usdMaterial: ${usdMaterialData ? 'valid' : 'null'}`); + + let pbrMaterial; + if (usdMaterialData) { + // Use smart convertMaterial to handle both OpenPBR and UsdPreviewSurface + pbrMaterial = await this.convertMaterial(usdMaterialData, usdScene, { + preferredMaterialType: options.preferredMaterialType || 'auto', + envMap: options.envMap || null, + envMapIntensity: options.envMapIntensity || 1.0, + textureCache: options.textureCache || new Map(), + doubleSided: geometry.userData['doubleSided'] + }); + } else { + // No valid material - create default material + pbrMaterial = defaultMtl || new THREE.MeshPhysicalMaterial({ + color: 0x888888, + roughness: 0.5, + metalness: 0.0 + }); + } // Setting envmap is required for PBR materials to work correctly(e.g. clearcoat) pbrMaterial.envMap = options.envMap || null; @@ -672,7 +706,6 @@ class TinyUSDZLoaderUtils extends LoaderUtils { if (Object.prototype.hasOwnProperty.call(geometry.userData, 'doubleSided')) { if (geometry.userData.doubleSided) { console.log(` Setting material to DoubleSide (from USD doubleSided=true)`); - usdMaterial.side = THREE.DoubleSide; pbrMaterial.side = THREE.DoubleSide; } else { console.log(` Setting material to FrontSide (from USD doubleSided=false)`); @@ -712,7 +745,7 @@ class TinyUSDZLoaderUtils extends LoaderUtils { // Supported options // 'overrideMaterial' : Override usd material with defaultMtl. - static buildThreeNode(usdNode /* TinyUSDZLoader.Node */, defaultMtl = null, usdScene /* TinyUSDZLoader.Scene */ = null, options = {}) + static async buildThreeNode(usdNode /* TinyUSDZLoader.Node */, defaultMtl = null, usdScene /* TinyUSDZLoader.Scene */ = null, options = {}) /* => THREE.Object3D */ { var node = new THREE.Group(); @@ -742,7 +775,7 @@ class TinyUSDZLoaderUtils extends LoaderUtils { // contentId is the mesh ID in the USD scene. const mesh = usdScene.getMesh(usdNode.contentId); - const threeMesh = this.setupMesh(mesh, defaultMtl, usdScene, options); + const threeMesh = await this.setupMesh(mesh, defaultMtl, usdScene, options); node = threeMesh; // Apply transform to mesh nodes as well @@ -774,7 +807,7 @@ class TinyUSDZLoaderUtils extends LoaderUtils { // traverse children for (const child of usdNode.children) { - const childNode = this.buildThreeNode(child, defaultMtl, usdScene, options); + const childNode = await this.buildThreeNode(child, defaultMtl, usdScene, options); node.add(childNode); } }