Enable Emscripten threading for MuJoCo WASM builds.

PiperOrigin-RevId: 863309872
Change-Id: I2edabb386f3abc74e507d0419b136b1586d01241
This commit is contained in:
Google DeepMind
2026-01-30 10:57:12 -08:00
committed by Copybara-Service
parent b073cd2ca4
commit 7418d86eda
6 changed files with 80 additions and 9 deletions
+2
View File
@@ -52,6 +52,8 @@ endif()
if(EMSCRIPTEN)
option(MUJOCO_BUILD_TESTS_WASM "Build tests for WASM bindings" ON)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -O3 -pthread -fexceptions")
endif()
if(APPLE AND (MUJOCO_BUILD_EXAMPLES OR MUJOCO_BUILD_SIMULATE))
+2 -5
View File
@@ -14,8 +14,6 @@
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/wasm/dist")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -O3 -fexceptions")
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/wasm)
include_directories(${PROJECT_SOURCE_DIR}/include)
@@ -33,12 +31,11 @@ if(NOT MUJOCO_WASM_FILES)
message(FATAL_ERROR "No source files found in codegen/generated/")
endif()
add_compile_options(-pthread)
add_compile_options(-fexceptions)
# Set Emscripten linker flags
set(EMCC_LINKER_FLAGS
"--bind"
"-pthread"
"-s PTHREAD_POOL_SIZE=navigator.hardwareConcurrency"
"-s ASSERTIONS=1"
"-s ALLOW_MEMORY_GROWTH=1"
"-s EXPORT_ES6=1"
+2 -4
View File
@@ -14,8 +14,6 @@
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/wasm/dist")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -O3")
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/wasm)
include_directories(${PROJECT_SOURCE_DIR}/include)
@@ -33,11 +31,11 @@ if(NOT MUJOCO_WASM_FILES)
message(FATAL_ERROR "No source files found")
endif()
add_compile_options(-pthread)
# Set Emscripten linker flags
set(EMCC_LINKER_FLAGS
"--bind"
"-pthread"
"-s PTHREAD_POOL_SIZE=navigator.hardwareConcurrency"
"-s ASSERTIONS=1"
"-s ALLOW_MEMORY_GROWTH=1"
"-s EXPORT_ES6=1"
+60
View File
@@ -1873,6 +1873,66 @@ describe('MuJoCo WASM Bindings', () => {
}
});
it('should compile a spec from XML with 10 meshes and 10 textures', () => {
const assetsCount = 10;
let xml = `<mujoco>
<asset>`;
const cube1 = `
v -1 -1 1
v 1 -1 1
v -1 1 1
v 1 1 1
v -1 1 -1
v 1 1 -1
v -1 -1 -1
v 1 -1 -1`;
const dummyTex = 'dummy texture data';
for (let i = 0; i < assetsCount; i++) {
xml += `<mesh name="cube${i}" file="cube${i}.obj"/>`;
xml += `<texture name="tex${
i}" type="2d" builtin="checker" width="16" height="16"/>`;
}
xml += `</asset><worldbody>`;
for (let i = 0; i < assetsCount; i++) {
xml += `<geom type="mesh" mesh="cube${i}"/>`;
}
xml += `</worldbody></mujoco>`;
let spec: MjSpec|null = null;
let model: MjModel|null = null;
let vfs: MjVFS|null = null;
try {
spec = mujoco.parseXMLString(xml);
assertExists(spec);
vfs = new mujoco.MjVFS();
assertExists(vfs);
for (let i = 0; i < assetsCount; i++) {
vfs.addBuffer(`cube${i}.obj`, new TextEncoder().encode(cube1));
vfs.addBuffer(`tex${i}.txt`, new TextEncoder().encode(dummyTex));
}
model = mujoco.mj_compile(spec, vfs);
assertExists(model);
expect(model.nmesh).toBe(assetsCount);
expect(model.ntex).toBe(assetsCount);
for (let i = 0; i < assetsCount; i++) {
const meshId = mujoco.mj_name2id(
model, mujoco.mjtObj.mjOBJ_MESH.value, `cube${i}`);
expect(meshId).toBe(i);
const tex = model.tex(`tex${i}`);
expect(tex).toBeDefined();
expect(tex!.name).toBe(`tex${i}`);
}
} finally {
spec?.delete();
vfs?.delete();
model?.delete();
}
});
describe('MjModel named access', () => {
// Corresponds to
// bindings_test.py:test_named_indexing_invalid_names_in_model
+7
View File
@@ -13,6 +13,13 @@
// limitations under the License.
import Jasmine from 'jasmine';
import os from 'os';
if (typeof navigator === 'undefined') {
globalThis.navigator = {
hardwareConcurrency: os.cpus().length,
};
}
const jasmine = new Jasmine();
+7
View File
@@ -13,6 +13,13 @@
// limitations under the License.
import Jasmine from 'jasmine';
import os from 'os';
if (typeof navigator === 'undefined') {
globalThis.navigator = {
hardwareConcurrency: os.cpus().length,
};
}
const jasmine = new Jasmine();