Merge branch 'google-deepmind:main' into mjx-warp-segmentation

This commit is contained in:
Tarik Kelestemur
2026-04-26 21:36:32 -04:00
committed by GitHub
75 changed files with 2344 additions and 1124 deletions
+26
View File
@@ -295,6 +295,32 @@ EOF
} }
build_mujoco_live() {
echo "Setting up Emscripten SDK..."
source emsdk/emsdk_env.sh
echo "Building Filament tools, targeting host platform..."
cmake -S . -B build_host -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DUSE_STATIC_LIBCXX=OFF \
-DMUJOCO_BUILD_STUDIO=ON \
-DMUJOCO_USE_FILAMENT=ON \
-DMUJOCO_BUILD_TESTS=OFF \
-DMUJOCO_BUILD_EXAMPLES=OFF \
-DMUJOCO_BUILD_SIMULATE=OFF
cmake --build build_host --target matc resgen cmgen mujoco_filament_assets -j$(nproc)
echo "Building WASM app..."
emcmake cmake -S . -B build_wasm -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DMUJOCO_BUILD_STUDIO=ON \
-DMUJOCO_USE_FILAMENT=ON \
-DMUJOCO_BUILD_TESTS_WASM=OFF \
-DMUJOCO_NATIVE_BUILD_DIR=$(pwd)/build_host
cmake --build build_wasm --target mujoco_live -j$(nproc)
}
# Discover functions defined in this script by finding identifiers followed by # Discover functions defined in this script by finding identifiers followed by
# "()" and capturing the identifier as a valid function name. # "()" and capturing the identifier as a valid function name.
VALID_FUNCTIONS=() VALID_FUNCTIONS=()
+51
View File
@@ -0,0 +1,51 @@
name: live
on:
push:
branches:
- live
permissions:
contents: read
pages: write
id-token: write
jobs:
build-and-upload-artifacts:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Prepare Linux
run: bash ./.github/workflows/build_steps.sh prepare_linux
- name: Setup Emscripten
run: bash ./.github/workflows/build_steps.sh setup_emsdk
- name: Build MuJoCo Live
env:
CC: clang-18
CXX: clang++-18
run: bash ./.github/workflows/build_steps.sh build_mujoco_live
- name: Prepare files for GitHub Pages
run: |
mkdir -p dist/bin
cp -r build_wasm/bin/* dist/bin/
cp src/experimental/studio/index.html dist/index.html
- name: Upload GitHub Pages artifacts
uses: actions/upload-pages-artifact@v3
with:
path: dist
deploy-pages:
needs: build-and-upload-artifacts
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
+40 -2
View File
@@ -29,7 +29,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project( project(
mujoco mujoco
VERSION 3.7.1 VERSION 3.8.1
DESCRIPTION "MuJoCo Physics Simulator" DESCRIPTION "MuJoCo Physics Simulator"
HOMEPAGE_URL "https://mujoco.org" HOMEPAGE_URL "https://mujoco.org"
) )
@@ -53,12 +53,49 @@ endif()
if(EMSCRIPTEN) if(EMSCRIPTEN)
option(MUJOCO_BUILD_TESTS_WASM "Build tests for WASM bindings" ON) option(MUJOCO_BUILD_TESTS_WASM "Build tests for WASM bindings" ON)
option(MUJOCO_BUILD_STUDIO "Build studio for MuJoCo (WASM)" OFF)
option(MUJOCO_USE_FILAMENT "Use filament rendering" OFF)
option(MUJOCO_WASM_THREADS "Build with multi-threading support" ON) option(MUJOCO_WASM_THREADS "Build with multi-threading support" ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -O3 -fexceptions") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -O3 -fexceptions")
if(MUJOCO_WASM_THREADS) if(MUJOCO_WASM_THREADS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif() endif()
# Filament uses the WEBGL variable (not EMSCRIPTEN) to identify web builds.
# Without this, it falls into the LINUX path and tries to compile with
# futex, X11, Vulkan, etc.
if(MUJOCO_USE_FILAMENT)
set(WEBGL ON CACHE BOOL "Filament WebGL mode" FORCE)
endif()
# Automatically generate host tool imports for Filament cross-compilation.
# When WEBGL is true, Filament hardcodes the path to:
# ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-Release.cmake
# So we write our file with that exact name and set IMPORT_EXECUTABLES_DIR
# to point at CMAKE_BINARY_DIR (which is ../../ relative to filament-src).
set(MUJOCO_NATIVE_BUILD_DIR "${PROJECT_SOURCE_DIR}/build-host" CACHE PATH "Path to native build directory containing host tools")
find_program(MATC_EXE matc PATHS "${MUJOCO_NATIVE_BUILD_DIR}/bin" NO_DEFAULT_PATH)
find_program(RESGEN_EXE resgen PATHS "${MUJOCO_NATIVE_BUILD_DIR}/bin" NO_DEFAULT_PATH)
find_program(CMGEN_EXE cmgen PATHS "${MUJOCO_NATIVE_BUILD_DIR}/bin" NO_DEFAULT_PATH)
if(MATC_EXE AND RESGEN_EXE AND CMGEN_EXE)
message(STATUS "Found host tools in ${MUJOCO_NATIVE_BUILD_DIR}/bin")
set(IMPORT_EXECUTABLES_FILE "${CMAKE_BINARY_DIR}/ImportExecutables-Release.cmake")
file(WRITE "${IMPORT_EXECUTABLES_FILE}"
"add_executable(matc IMPORTED)\n"
"set_property(TARGET matc PROPERTY IMPORTED_LOCATION \"${MATC_EXE}\")\n"
"add_executable(resgen IMPORTED)\n"
"set_property(TARGET resgen PROPERTY IMPORTED_LOCATION \"${RESGEN_EXE}\")\n"
"add_executable(cmgen IMPORTED)\n"
"set_property(TARGET cmgen PROPERTY IMPORTED_LOCATION \"${CMGEN_EXE}\")\n"
)
# Filament's WEBGL path resolves: ${FILAMENT}/${IMPORT_EXECUTABLES_DIR}/ImportExecutables-Release.cmake
# FILAMENT = _deps/filament-src, so ../../ resolves to CMAKE_BINARY_DIR.
set(IMPORT_EXECUTABLES_DIR "../../" CACHE PATH "" FORCE)
else()
message(WARNING "Host tools (matc, resgen, cmgen) not found in ${MUJOCO_NATIVE_BUILD_DIR}/bin. WASM build of Studio might fail.")
endif()
endif() endif()
if(APPLE AND (MUJOCO_BUILD_EXAMPLES OR MUJOCO_BUILD_SIMULATE)) if(APPLE AND (MUJOCO_BUILD_EXAMPLES OR MUJOCO_BUILD_SIMULATE))
@@ -125,8 +162,9 @@ if(NOT EMSCRIPTEN AND NOT MUJOCO_USE_FILAMENT_MJR_COMPAT)
add_subdirectory(src/render/classic) add_subdirectory(src/render/classic)
add_subdirectory(src/ui) add_subdirectory(src/ui)
endif() endif()
add_subdirectory(src/render/noop)
if(MUJOCO_USE_FILAMENT AND NOT EMSCRIPTEN) if(MUJOCO_USE_FILAMENT)
add_subdirectory(src/experimental/filament) add_subdirectory(src/experimental/filament)
endif() endif()
+5 -1
View File
@@ -18,7 +18,11 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_EXTENSIONS OFF) if(EMSCRIPTEN)
set(CMAKE_C_EXTENSIONS ON)
else()
set(CMAKE_C_EXTENSIONS OFF)
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For LLVM tooling set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For LLVM tooling
if(NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_CONFIGURATION_TYPES)
+11 -1
View File
@@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
set(MUJOCO_DEP_VERSION_filament set(MUJOCO_DEP_VERSION_filament
a4945939de514d049baeed654efbbdd06bc5bdbf 06793c4a80dd467025b2db1b3b7ea63bf1a865bb
CACHE STRING "Tag/version of `filament` to be fetched." CACHE STRING "Tag/version of `filament` to be fetched."
) )
mark_as_advanced(MUJOCO_DEP_VERSION_filament) mark_as_advanced(MUJOCO_DEP_VERSION_filament)
@@ -23,6 +23,15 @@ include(FindOrFetch)
set(BUILD_SHARED_LIBS_OLD ${BUILD_SHARED_LIBS}) set(BUILD_SHARED_LIBS_OLD ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF)
# Filament's ShaderMinifier.cpp uses strlen without including <cstring>, and
# PostProcessManager.h uses std::optional without including <optional>.
set(CMAKE_CXX_FLAGS_OLD "${CMAKE_CXX_FLAGS}")
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FI cstring /FI optional")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include cstring -include optional")
endif()
set(FILAMENT_ENABLE_EXPERIMENTAL_GCC_SUPPORT ON) set(FILAMENT_ENABLE_EXPERIMENTAL_GCC_SUPPORT ON)
set(FILAMENT_SKIP_SDL2 ON) set(FILAMENT_SKIP_SDL2 ON)
set(FILAMENT_USE_EXTERNAL_ABSL ON) set(FILAMENT_USE_EXTERNAL_ABSL ON)
@@ -39,3 +48,4 @@ fetchpackage(
) )
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_OLD}) set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_OLD})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_OLD}")
+4 -4
View File
@@ -1,6 +1,6 @@
1 VERSIONINFO 1 VERSIONINFO
FILEVERSION 3,7,1,0 FILEVERSION 3,8,1,0
PRODUCTVERSION 3,7,1,0 PRODUCTVERSION 3,8,1,0
FILEOS 0x4 FILEOS 0x4
FILETYPE 0x1 FILETYPE 0x1
{ {
@@ -9,9 +9,9 @@ FILETYPE 0x1
BLOCK "040904b0" BLOCK "040904b0"
{ {
VALUE "ProductName", "MuJoCo" VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.7.1" VALUE "ProductVersion", "3.8.1"
VALUE "FileDescription", "MuJoCo" VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.7.1" VALUE "FileVersion", "3.8.1"
VALUE "InternalName", "mujoco.dll" VALUE "InternalName", "mujoco.dll"
VALUE "OriginalFilename", "mujoco.dll" VALUE "OriginalFilename", "mujoco.dll"
VALUE "CompanyName", "Google DeepMind" VALUE "CompanyName", "Google DeepMind"
+4 -4
View File
@@ -1,8 +1,8 @@
MUJOCO ICON "mujoco.ico" MUJOCO ICON "mujoco.ico"
1 VERSIONINFO 1 VERSIONINFO
FILEVERSION 3,7,1,0 FILEVERSION 3,8,1,0
PRODUCTVERSION 3,7,1,0 PRODUCTVERSION 3,8,1,0
FILEOS 0x4 FILEOS 0x4
FILETYPE 0x1 FILETYPE 0x1
{ {
@@ -11,9 +11,9 @@ FILETYPE 0x1
BLOCK "040904b0" BLOCK "040904b0"
{ {
VALUE "ProductName", "MuJoCo" VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.7.1" VALUE "ProductVersion", "3.8.1"
VALUE "FileDescription", "MuJoCo" VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.7.1" VALUE "FileVersion", "3.8.1"
VALUE "InternalName", "simulate.exe" VALUE "InternalName", "simulate.exe"
VALUE "OriginalFilename", "simulate.exe" VALUE "OriginalFilename", "simulate.exe"
VALUE "CompanyName", "Google DeepMind" VALUE "CompanyName", "Google DeepMind"
+1 -1
View File
@@ -388,7 +388,7 @@ Defined in `mujoco.h <https://github.com/google-deepmind/mujoco/blob/main/includ
- value - value
- description - description
* - ``mjVERSION_HEADER`` * - ``mjVERSION_HEADER``
- 3007001 - 3008001
- The version of the MuJoCo headers. This is an integer calculated from the version string "S.M.P" - The version of the MuJoCo headers. This is an integer calculated from the version string "S.M.P"
using the formula ``(S * 1e6) + (M * 1e3) + P``. For example, version 4.2.1 is represented as 4002001. using the formula ``(S * 1e6) + (M * 1e3) + P``. For example, version 4.2.1 is represented as 4002001.
The API function :ref:`mj_version` returns a number with the same meaning The API function :ref:`mj_version` returns a number with the same meaning
+24 -17
View File
@@ -2,34 +2,41 @@
Changelog Changelog
========= =========
Upcoming version (not yet released) Version 3.8.0 (April 24, 2026)
----------------------------------- ------------------------------
General General
^^^^^^^ ^^^^^^^
- Added new :ref:`mj_maxContact<mj_maxContact>` function to get the maximum number of possible contacts returned by 1. Added support for Python 3.14.
two geoms. 2. Added :ref:`multi-cell support<body-flexcomp-cellcount>` for trilinear and quadratic flexes. Note that the implicit
- Added ``mj_containsBufferVFS`` and ``mj_containsFileVFS`` to check for existence of buffers and files in VFS. integrator uses a dense solver for the flex degrees of freedom, which can be slow for multi-cell flexes.
- Added :ref:`multi-cell support<body-flexcomp-cellnum>` for trilinear and quadratic flexes. Note that the implicit 3. Refactored ``strain`` flex :ref:`equality constraints<flexcomp-edge-equality>` to be instantiated per cell instead of
integrator uses a dense solver for the flex degrees of freedom, which can be slow for multi-cell flexes. per flex object, reducing the number of degrees of freedom per constraint row. The equality can be associated with a
- Refactored ``flexstrain`` equality constraints to be instantiated per cell instead of per flex object, reducing the specific cell with the new attribute :ref:`cell <equality-flexstrain-cell>`
number of degrees of freedom per constraint row. The equality can be associated with a specific cell with the new 4. Added new :ref:`mj_maxContact<mj_maxContact>` function to get the maximum number of possible contacts returned by
attribute ":ref:`cell <equality-flexstrain-cell>` colliding two geoms.
5. Added ``mj_containsBufferVFS`` and ``mj_containsFileVFS`` to check for existence of buffers and files in VFS.
.. admonition:: Breaking API changes .. admonition:: Breaking API changes
:class: attention :class: attention
- The feature :ref:`multiccd<coMultiCCD>` is now enabled by default. This feature has little performance overhead 6. The :ref:`multiccd<coMultiCCD>` option (multiple contacts returned from the convex collision detection pipeline)
and gives better contact behavior for stability. is now enabled by default. The new implementation (as opposed to the legacy pipeline) has little performance
overhead and improves stability.
**Migration:** The flag :ref:`multiccd<option-flag-multiccd>` must be explicitly disabled. **Migration:** Disable :ref:`multiccd<option-flag-multiccd>` to recover the previous behavior.
Documentation
^^^^^^^^^^^^^
7. Added :ref:`documentation<exDecoder>` for :ref:`mjpDecoder` plugins.
Bug fixes Bug fixes
^^^^^^^^^ ^^^^^^^^^
- Asset paths in attached child specs are now resolved relative to the model file directory of the child spec, rather 8. Asset paths in attached child specs are now resolved relative to the model file directory of the child spec, rather
than the parent spec. This prevents the origin of the parent spec to affect the resolution of asset paths in the child than the parent spec. This prevents the origin of the parent spec to affect the resolution of asset paths in the
spec. child spec.
Version 3.7.0 (April 14, 2026) Version 3.7.0 (April 14, 2026)
------------------------------ ------------------------------
+135 -2
View File
@@ -3,8 +3,8 @@
Extensions Extensions
---------- ----------
This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided by
via :ref:`engine plugins<exPlugin>` and :ref:`resource providers<exProvider>`. via :ref:`engine plugins<exPlugin>`, :ref:`decoders<exDecoder>`, and :ref:`resource providers<exProvider>`.
.. _exPlugin: .. _exPlugin:
@@ -337,6 +337,139 @@ For the sdf plugin, the following methods need to be specified
Computes the axis-aligned bounding box in local coordinates. This volume is voxelized uniformly before the call to Computes the axis-aligned bounding box in local coordinates. This volume is voxelized uniformly before the call to
the marching cubes algorithm. the marching cubes algorithm.
.. _exDecoder:
Decoders
~~~~~~~~
Decoder plugins extend asset loading capabilities beyond MJCF and URDF. They are :ref:`registered<mjPLUGIN_LIB_INIT>`
similarly to other MuJoCo plugins.
MuJoCo ships with two built-in decoders for common mesh formats:
- **OBJ decoder** (``plugin/obj_decoder``) -- `Wavefront OBJ <https://en.wikipedia.org/wiki/Wavefront_.obj_file>`_.
- **STL decoder** (``plugin/stl_decoder``) -- `STL <https://en.wikipedia.org/wiki/STL_(file_format)>`_.
Additionally, we provide the following optional decoder plugins:
- **USD decoder** (``plugin/usd_decoder``) -- `Universal Scene Description <https://openusd.org/release/index.html>`_.
These plugins also serve as examples for how to write custom decoders. The obj decoder is perhaps the simplest to
understand, while the USD decoder is more complex due to its support for entire scenes.
.. _exDecoderInterface:
Decoder interface
^^^^^^^^^^^^^^^^^
A decoder is described by the :ref:`mjpDecoder` struct, which has the following fields:
``content_type``
A MIME-like content type string identifying the format. For example, ``"model/obj"``, or ``"model/stl"``.
When a mesh asset specifies a ``content-type`` attribute in MJCF, this string is used
to find the appropriate decoder.
``extension``
A file extension string (including the dot) used for matching when no content type is specified. Multiple
extensions can be separated by pipes (`|`) for formats with multiple extensions such as ``.usd|.usda|.usdc|.usdz``.
``can_decode``
A callback of type :ref:`mjfCanDecode` that determines whether the decoder can handle a given resource. This is
typically implemented by checking the file extension but may also check the file contents to differentiate between
formats. For example, URDF and MJCF files both have a ``.xml`` extension. Returns nonzero if the decoder can handle
the resource.
``decode``
A callback of type :ref:`mjfDecode` that performs the actual decoding. It receives an :ref:`mjResource` and
returns a newly allocated :ref:`mjSpec` containing the decoded asset data. The caller takes
ownership of the returned spec and is responsible for freeing it with :ref:`mj_deleteSpec`. Returns ``NULL`` on
failure.
When a decoder is invoked for a mesh asset, the compiler will reference the first mesh element in the spec returned
by the ``decode`` callback.
When a decoder is invoked for a model asset, the spec returned by the ``decode`` callback may contain any number of
elements of any type.
.. _exDecoderRegistration:
Registration
^^^^^^^^^^^^
Decoders must be registered before they can be used. Registration is performed via
:ref:`mjp_registerDecoder`. The :ref:`mjp_defaultDecoder` function initializes an :ref:`mjpDecoder` struct with
default values. The :ref:`mjPLUGIN_LIB_INIT` macro is used to define the initialization function that registers the
decoder when the library is loaded.
.. code-block:: C
mjPLUGIN_LIB_INIT(my_format_decoder) {
mjpDecoder decoder;
mjp_defaultDecoder(&decoder);
decoder.content_type = "model/my-format";
decoder.extension = ".myf|.myfa|.myfc";
decoder.decode = MyDecode;
decoder.can_decode = MyCanDecode;
mjp_registerDecoder(&decoder);
}
.. _exDecoderExample:
Example
^^^^^^^
Below is a minimal decoder that reads a hypothetical binary mesh format:
.. code-block:: C
#include <mujoco.h>
static mjSpec* MyDecode(mjResource* resource, const mjVFS* vfs) {
const void* bytes = NULL;
int nbytes = mju_readResource(resource, &bytes);
if (nbytes < 0) {
mju_warning("failed to read resource '%s'", resource->name);
return NULL;
}
/* ... parse bytes into vertex/face arrays ... */
mjSpec* spec = mj_makeSpec();
mjsMesh* mesh = mjs_addMesh(spec, NULL);
mjs_setString(mesh->file, resource->name);
mjs_setFloat(mesh->uservert, vertices, nvert * 3);
mjs_setInt(mesh->userface, faces, nface * 3);
return spec;
}
static int MyCanDecode(const mjResource* resource) {
/* check file extension */
const char* name = resource->name;
int len = strlen(name);
return len > 4 && strcmp(name + len - 4, ".myf") == 0;
}
mjPLUGIN_LIB_INIT(my_format_decoder) {
mjpDecoder decoder;
mjp_defaultDecoder(&decoder);
decoder.content_type = "model/my-format";
decoder.extension = ".myf";
decoder.decode = MyDecode;
decoder.can_decode = MyCanDecode;
mjp_registerDecoder(&decoder);
}
Once registered, the decoder is used automatically when MuJoCo encounters an asset with a matching file extension
or content type:
.. code-block:: xml
<asset>
<mesh file="my_mesh.myf"/>
</asset>
.. _exProvider: .. _exProvider:
Resource providers Resource providers
+2 -2
View File
@@ -37,14 +37,14 @@ _____
The MuJoCo app needs to be run at least once before the native library can be used, in order to register the library as The MuJoCo app needs to be run at least once before the native library can be used, in order to register the library as
a trusted binary. Then, copy the dynamic library file from a trusted binary. Then, copy the dynamic library file from
``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.7.1.dylib`` (it can be ``/Applications/MuJoCo.app/Contents/Frameworks/mujoco.framework/Versions/Current/libmujoco.3.8.1.dylib`` (it can be
found by browsing the contents of ``MuJoCo.app``) and rename it as ``mujoco.dylib``. found by browsing the contents of ``MuJoCo.app``) and rename it as ``mujoco.dylib``.
Linux Linux
_____ _____
Expand the ``tar.gz`` archive to ``~/.mujoco``. Then copy the dynamic library from Expand the ``tar.gz`` archive to ``~/.mujoco``. Then copy the dynamic library from
``~/.mujoco/mujoco-3.7.1/lib/libmujoco.so.3.7.1`` and rename it as ``libmujoco.so``. ``~/.mujoco/mujoco-3.8.1/lib/libmujoco.so.3.8.1`` and rename it as ``libmujoco.so``.
Windows Windows
_______ _______
+1 -1
View File
@@ -16,7 +16,7 @@
#define MUJOCO_MUJOCO_H_ #define MUJOCO_MUJOCO_H_
// header version; should match the library version as returned by mj_version() // header version; should match the library version as returned by mj_version()
#define mjVERSION_HEADER 3007001 #define mjVERSION_HEADER 3008001
// needed to define size_t, fabs and log10 // needed to define size_t, fabs and log10
#include <stdlib.h> #include <stdlib.h>
+19 -15
View File
@@ -1,21 +1,25 @@
jax-cuda12-plugin==0.5.3; python_version >= '3.10' \ jax-cuda12-plugin==0.8.3; python_version >= '3.13' \
--hash=sha256:1862595b2b6d815679d11e0e889e523185ee54a46d46e022689f70fc4554dd91 \ --hash=sha256:11d7cb222cfd4d5f6b7691df61516a5edc9298b7879dd6caaccc6dfc64678c70 \
--hash=sha256:21fec1b56c98783ea0569b747a56751f1f9ff2187b48acc11c700d3bfc5e1a31 \ --hash=sha256:7bb68f693e16038cad77197aba914375795e5bb7e99f4a4f023b67bb50cad8c2 \
--hash=sha256:9e3f1335a6812f5b28ace211a17d9ede46b8b46ceb4b8d9c8c697d3b4ada513f \
--hash=sha256:b38c4bd34062c01d22a8c6a2225254dcd429771e97e024f3a2ccc1d6d24fd856 \
--hash=sha256:b3f64b5059a39c18ec006c136475f46d088a11164e7e6ee2444e76305da87a1b \
--hash=sha256:bb20d8b794ce52d644967c7229c5e0a01d3b1fac7efc60fbbfcd046a745a66c6 \
--hash=sha256:f9a722128e2b423469a5dab8f9f96d77257d153ca7d96850b25a475191efaacc \
--hash=sha256:fb9d49d43c4447793630079632e6fbb4a5ce30c388955131fe3ba96efec91817
jax-cuda12-plugin==0.5.3; python_version < '3.13' \
--hash=sha256:2030cf1208ce4ea70ee56cac61ddd239f9798695fc39bb7739c50a25d6e9da44 \ --hash=sha256:2030cf1208ce4ea70ee56cac61ddd239f9798695fc39bb7739c50a25d6e9da44 \
--hash=sha256:c2517a7c2186f8708894696e26cf96ebd60b7879ceca398b2c46abb28d2c96c8 \
--hash=sha256:aaa704a5ef547595d022db1c1e4878a0677116412a9360c115d67ff4b64e1596 \
--hash=sha256:298d2d768f1029b74a0b1d01270e549349d2c37dc07658796542cda967eb7bd3 \ --hash=sha256:298d2d768f1029b74a0b1d01270e549349d2c37dc07658796542cda967eb7bd3 \
--hash=sha256:6171aed2f4b3bdd5fc13782de1072c6a634fce13731b75d0cb0a6ab8f4e6e650 \
--hash=sha256:aaa704a5ef547595d022db1c1e4878a0677116412a9360c115d67ff4b64e1596 \
--hash=sha256:ba2555967f9b6c381c8b4ef9fb03d05bc55ec25ecfee5cfe45c5ace34f7d4152 \ --hash=sha256:ba2555967f9b6c381c8b4ef9fb03d05bc55ec25ecfee5cfe45c5ace34f7d4152 \
--hash=sha256:6171aed2f4b3bdd5fc13782de1072c6a634fce13731b75d0cb0a6ab8f4e6e650 --hash=sha256:c2517a7c2186f8708894696e26cf96ebd60b7879ceca398b2c46abb28d2c96c8
jax-cuda12-plugin==0.4.30; python_version == '3.9' \ jax-cuda12-pjrt==0.8.3; python_version >= '3.13' \
--hash=sha256:d8d196241b9253ecb1144a4409b5deacbb9771624f097b2bbf025da3c7d8f4f8 \ --hash=sha256:f6d085fa7b2836cd79b14cabf1058ddb50c5161bfca9ede407993fa4f7547b7b \
--hash=sha256:cb8edccdce358451205f689e3536272200761c625c8e8059ab10523984cf8b61 --hash=sha256:f740c661dd4064ff45dedf170fd0c4ff1a25d077636ad293307e8d28c78e65d7
jax-cuda12-pjrt==0.5.3; python_version >= '3.10' \ jax-cuda12-pjrt==0.5.3; python_version < '3.13' \
--hash=sha256:c5378306568ba0c81b230a779dd3194c9dd10339ab6360ae80928108d37e7f75 \ --hash=sha256:04ee111eaf5fc2692978ad4a5c84d5925e42eb05c1701849ba3a53f6515400cc \
--hash=sha256:04ee111eaf5fc2692978ad4a5c84d5925e42eb05c1701849ba3a53f6515400cc --hash=sha256:c5378306568ba0c81b230a779dd3194c9dd10339ab6360ae80928108d37e7f75
jax-cuda12-pjrt==0.4.30; python_version == '3.9' \
--hash=sha256:895d0198ad99638fcaf976c47592e2a543eef79ea15fabd24a402d055390c328 \
--hash=sha256:c36fb1e0c236563bf3a87e70f4d1ab28a31d7cf5d722c9ede30c4172116e8bcb
warp-lang==1.12.1 \ warp-lang==1.12.1 \
--hash=sha256:98df3533a6c40a33cce961f8efa991006b30c9d286356e4cd77ea8ce86928f1d \ --hash=sha256:98df3533a6c40a33cce961f8efa991006b30c9d286356e4cd77ea8ce86928f1d \
--hash=sha256:6bf01f10509488ba8eacaf4ec7fcf7cfbd503118b22e002ecba407b40a17424e \ --hash=sha256:6bf01f10509488ba8eacaf4ec7fcf7cfbd503118b22e002ecba407b40a17424e \
+13 -17
View File
@@ -96,16 +96,15 @@ def _resolve_device(
return cpu_0 return cpu_0
if impl == types.Impl.WARP: if impl == types.Impl.WARP:
# WARP implementation requires a CUDA GPU. # WARP implementation requires a CUDA GPU or CPU.
cuda_gpus = [d for d in jax.devices('cuda')] if has_cuda_gpu_device():
if not cuda_gpus: cuda_gpus = [d for d in jax.devices('cuda')]
raise AssertionError( if cuda_gpus:
'No CUDA GPU devices found in' logging.debug('Picking default device: %s', cuda_gpus[0])
f' jax.devices("cuda")={jax.devices("cuda")}.' return cuda_gpus[0]
)
logging.debug('Picking default device: %s', cuda_gpus[0]) logging.debug('Picking default device for Warp: CPU')
return cuda_gpus[0] return jax.devices('cpu')[0]
raise ValueError(f'Unsupported implementation: {impl}') raise ValueError(f'Unsupported implementation: {impl}')
@@ -121,9 +120,12 @@ def _check_impl_device_compatibility(
impl = types.Impl(impl) impl = types.Impl(impl)
if impl == types.Impl.WARP: if impl == types.Impl.WARP:
if not _is_cuda_gpu_device(device): is_cuda_device = _is_cuda_gpu_device(device)
is_cpu_device = device.platform == 'cpu'
if not (is_cuda_device or is_cpu_device):
raise AssertionError( raise AssertionError(
f'Warp implementation requires a CUDA GPU device, got {device}.' 'Warp implementation requires a CUDA GPU or CPU device, got '
f'{device}.'
) )
_check_warp_installed() _check_warp_installed()
@@ -425,8 +427,6 @@ def _put_model_jax(
return _strip_weak_type(model) return _strip_weak_type(model)
def _put_model_warp( def _put_model_warp(
m: mujoco.MjModel, m: mujoco.MjModel,
graph_mode: mjxw.types.GraphMode, graph_mode: mjxw.types.GraphMode,
@@ -719,8 +719,6 @@ def _make_data_jax(
return d return d
def _get_nested_attr(obj: Any, attr_name: str, split: str) -> Any: def _get_nested_attr(obj: Any, attr_name: str, split: str) -> Any:
"""Returns the nested attribute from an object.""" """Returns the nested attribute from an object."""
for part in attr_name.split(split): for part in attr_name.split(split):
@@ -1084,8 +1082,6 @@ def _put_data_jax(
return _strip_weak_type(data) return _strip_weak_type(data)
# TODO(josechenf): Iterate on the keepalive implementation to make it easier to # TODO(josechenf): Iterate on the keepalive implementation to make it easier to
# use before OSS. # use before OSS.
def _put_data_cpp( def _put_data_cpp(
+5 -14
View File
@@ -934,8 +934,8 @@ _DEVICE_TEST_CASES = [
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)), ('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)), ('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend specified. # WARP backend specified.
('cpu', 'warp', ('cpu', 'error')), ('cpu', 'warp', ('cpu', Impl.WARP)),
('gpu-notnvidia', 'warp', ('cpu', 'error')), ('gpu-notnvidia', 'warp', ('gpu', 'error')),
('gpu-nvidia', 'warp', ('gpu', Impl.WARP)), ('gpu-nvidia', 'warp', ('gpu', Impl.WARP)),
('tpu', 'warp', ('tpu', 'error')), ('tpu', 'warp', ('tpu', 'error')),
# CPP backend specified. # CPP backend specified.
@@ -962,10 +962,10 @@ _DEFAULT_DEVICE_TEST_CASES = [
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)), ('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)), ('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend impl specified. # WARP backend impl specified.
('cpu', 'warp', ('cpu', 'error')), ('cpu', 'warp', ('cpu', Impl.WARP)),
('gpu-notnvidia', 'warp', ('cpu', 'error')), ('gpu-notnvidia', 'warp', ('cpu', Impl.WARP)),
('gpu-nvidia', 'warp', ('gpu', Impl.WARP)), ('gpu-nvidia', 'warp', ('gpu', Impl.WARP)),
('tpu', 'warp', ('tpu', 'error')), ('tpu', 'warp', ('cpu', Impl.WARP)),
# CPP backend impl specified, CPU should always be available. # CPP backend impl specified, CPU should always be available.
('cpu', 'cpp', ('cpu', Impl.CPP)), ('cpu', 'cpp', ('cpu', Impl.CPP)),
('gpu-notnvidia', 'cpp', ('cpu', Impl.CPP)), ('gpu-notnvidia', 'cpp', ('cpu', Impl.CPP)),
@@ -1140,15 +1140,6 @@ class ResolveImplAndDeviceTest(parameterized.TestCase):
self.mock_jax_backends.side_effect = backends_side_effect self.mock_jax_backends.side_effect = backends_side_effect
expected_device, expected_impl = expected expected_device, expected_impl = expected
if (
expected_impl == 'error'
and default_device_str != 'gpu-nvidia'
and impl_str == 'warp'
):
with self.assertRaisesRegex(RuntimeError, 'cuda backend not supported'):
mjx_io._resolve_impl_and_device(impl=impl_str, device=None)
return
if expected_impl == 'error': if expected_impl == 'error':
with self.assertRaises(AssertionError): with self.assertRaises(AssertionError):
mjx_io._resolve_impl_and_device(impl=impl_str, device=None) mjx_io._resolve_impl_and_device(impl=impl_str, device=None)
+4
View File
@@ -776,6 +776,10 @@ class Model(PyTreeNode):
flex_vertadr: np.ndarray flex_vertadr: np.ndarray
flex_vertnum: np.ndarray flex_vertnum: np.ndarray
flex_vert0: np.ndarray flex_vert0: np.ndarray
flex_nodeadr: np.ndarray
flex_nodenum: np.ndarray
flex_nodebodyid: np.ndarray
flex_node0: np.ndarray
hfield_size: np.ndarray hfield_size: np.ndarray
hfield_nrow: np.ndarray hfield_nrow: np.ndarray
hfield_ncol: np.ndarray hfield_ncol: np.ndarray
+111 -48
View File
@@ -210,11 +210,19 @@ class FfiKernel:
self.input_output_aliases = input_output_aliases self.input_output_aliases = input_output_aliases
# register the callback # register the callback
FFI_CCALLFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.POINTER(XLA_FFI_CallFrame)) FFI_CCALLFUNC = ctypes.CFUNCTYPE(
self.callback_func = FFI_CCALLFUNC(self.ffi_callback) ctypes.c_void_p, ctypes.POINTER(XLA_FFI_CallFrame)
ffi_ccall_address = ctypes.cast(self.callback_func, ctypes.c_void_p) )
ffi_capsule = jax.ffi.pycapsule(ffi_ccall_address.value)
jax.ffi.register_ffi_target(self.name, ffi_capsule, platform="CUDA") self.callback_func_cuda = FFI_CCALLFUNC(lambda call_frame: self.ffi_callback(call_frame, platform="CUDA"))
ffi_ccall_address_cuda = ctypes.cast(self.callback_func_cuda, ctypes.c_void_p)
ffi_capsule_cuda = jax.ffi.pycapsule(ffi_ccall_address_cuda.value)
jax.ffi.register_ffi_target(self.name, ffi_capsule_cuda, platform="CUDA")
self.callback_func_host = FFI_CCALLFUNC(lambda call_frame: self.ffi_callback(call_frame, platform="Host"))
ffi_ccall_address_host = ctypes.cast(self.callback_func_host, ctypes.c_void_p)
ffi_capsule_host = jax.ffi.pycapsule(ffi_ccall_address_host.value)
jax.ffi.register_ffi_target(self.name, ffi_capsule_host, platform="Host")
def __call__(self, *args, output_dims=None, launch_dims=None, vmap_method=None): def __call__(self, *args, output_dims=None, launch_dims=None, vmap_method=None):
num_inputs = len(args) num_inputs = len(args)
@@ -241,18 +249,19 @@ class FfiKernel:
# check dtype # check dtype
if input_value.dtype != input_arg.jax_scalar_type: if input_value.dtype != input_arg.jax_scalar_type:
raise TypeError( raise TypeError(
f"Invalid data type for array argument '{input_arg.name}', expected {input_arg.jax_scalar_type}, got {input_value.dtype}" f"Invalid data type for array argument '{input_arg.name}',"
f" expected {input_arg.jax_scalar_type}, got {input_value.dtype}"
) )
# check ndim # check ndim
if input_value.ndim != input_arg.jax_ndim: if input_value.ndim != input_arg.jax_ndim:
raise TypeError( raise TypeError(
f"Invalid dimensionality for array argument '{input_arg.name}', expected {input_arg.jax_ndim} dimensions, got {input_value.ndim}" f"Invalid dimensionality for array argument '{input_arg.name}', expected {input_arg.jax_ndim} dimensions, got {input_value.ndim}"
) )
# check inner dims # check inner dims
for d in range(input_arg.dtype_ndim): for d in range(input_arg.dtype_ndim):
if input_value.shape[input_arg.type.ndim + d] != input_arg.dtype_shape[d]: if input_value.shape[input_arg.type.ndim + d] != input_arg.dtype_shape[d]:
raise TypeError( raise TypeError(
f"Invalid inner dimensions for array argument '{input_arg.name}', expected {input_arg.dtype_shape}, got {input_value.shape[-input_arg.dtype_ndim :]}" f"Invalid inner dimensions for array argument '{input_arg.name}', expected {input_arg.dtype_shape}, got {input_value.shape[-input_arg.dtype_ndim :]}"
) )
else: else:
# make sure scalar is not a traced variable, should be static # make sure scalar is not a traced variable, should be static
@@ -328,7 +337,7 @@ class FfiKernel:
return call(*args, launch_id=launch_id) return call(*args, launch_id=launch_id)
def ffi_callback(self, call_frame): def ffi_callback(self, call_frame, platform="CUDA"):
try: try:
# On the first call, XLA runtime will query the API version and traits # On the first call, XLA runtime will query the API version and traits
# metadata using the |extension| field. Let us respond to that query # metadata using the |extension| field. Let us respond to that query
@@ -340,10 +349,11 @@ class FfiKernel:
metadata_ext = ctypes.cast(extension, ctypes.POINTER(XLA_FFI_Metadata_Extension)) metadata_ext = ctypes.cast(extension, ctypes.POINTER(XLA_FFI_Metadata_Extension))
metadata_ext.contents.metadata.contents.api_version.major_version = 0 metadata_ext.contents.metadata.contents.api_version.major_version = 0
metadata_ext.contents.metadata.contents.api_version.minor_version = 1 metadata_ext.contents.metadata.contents.api_version.minor_version = 1
# Turn on CUDA graphs for this handler. # Turn on CUDA graphs for this handler if on CUDA platform.
metadata_ext.contents.metadata.contents.traits = ( if platform == "CUDA":
XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE metadata_ext.contents.metadata.contents.traits = (
) XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE
)
return None return None
# Lock is required to prevent race conditions when callback is invoked # Lock is required to prevent race conditions when callback is invoked
@@ -423,29 +433,43 @@ class FfiKernel:
kernel_params[0] = ctypes.addressof(launch_bounds) kernel_params[0] = ctypes.addressof(launch_bounds)
# get device and stream # get device and stream
device = wp.get_cuda_device(get_device_ordinal_from_callframe(call_frame.contents)) if platform == "CUDA":
stream = get_stream_from_callframe(call_frame.contents) device = wp.get_cuda_device(get_device_ordinal_from_callframe(call_frame.contents))
stream = get_stream_from_callframe(call_frame.contents)
else:
device = wp.get_device("cpu")
stream = None
# get kernel hooks # get kernel hooks
hooks = self.kernel.module.get_kernel_hooks(self.kernel, device) hooks = self.kernel.module.get_kernel_hooks(self.kernel, device)
assert hooks.forward, "Failed to find kernel entry point" assert hooks.forward, "Failed to find kernel entry point"
# launch the kernel # launch the kernel
wp._src.context.runtime.core.wp_cuda_launch_kernel( if device.is_cuda:
device.context, wp._src.context.runtime.core.wp_cuda_launch_kernel(
hooks.forward, device.context,
launch_bounds.size, hooks.forward,
0, launch_bounds.size,
256, 0,
hooks.forward_smem_bytes, 256,
kernel_params, hooks.forward_smem_bytes,
stream, kernel_params,
) stream,
)
else:
wp._src.context.runtime.core.wp_cpu_launch_kernel(
device.context,
hooks.forward,
launch_bounds.size,
kernel_params,
)
except Exception as e: except Exception as e:
print(traceback.format_exc()) print(traceback.format_exc())
return create_ffi_error( return create_ffi_error(
call_frame.contents.api, XLA_FFI_Error_Code.UNKNOWN, f"FFI callback error: {type(e).__name__}: {e}" call_frame.contents.api,
XLA_FFI_Error_Code.UNKNOWN,
f"FFI callback error: {type(e).__name__}: {e}",
) )
@@ -594,10 +618,16 @@ class FfiCallable:
# register the callback # register the callback
FFI_CCALLFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.POINTER(XLA_FFI_CallFrame)) FFI_CCALLFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.POINTER(XLA_FFI_CallFrame))
self.callback_func = FFI_CCALLFUNC(self.ffi_callback)
ffi_ccall_address = ctypes.cast(self.callback_func, ctypes.c_void_p) self.callback_func_cuda = FFI_CCALLFUNC(lambda call_frame: self.ffi_callback(call_frame, platform="CUDA"))
ffi_capsule = jax.ffi.pycapsule(ffi_ccall_address.value) ffi_ccall_address_cuda = ctypes.cast(self.callback_func_cuda, ctypes.c_void_p)
jax.ffi.register_ffi_target(self.name, ffi_capsule, platform="CUDA") ffi_capsule_cuda = jax.ffi.pycapsule(ffi_ccall_address_cuda.value)
jax.ffi.register_ffi_target(self.name, ffi_capsule_cuda, platform="CUDA")
self.callback_func_host = FFI_CCALLFUNC(lambda call_frame: self.ffi_callback(call_frame, platform="Host"))
ffi_ccall_address_host = ctypes.cast(self.callback_func_host, ctypes.c_void_p)
ffi_capsule_host = jax.ffi.pycapsule(ffi_ccall_address_host.value)
jax.ffi.register_ffi_target(self.name, ffi_capsule_host, platform="Host")
def __call__(self, *args, output_dims=None, vmap_method=None): def __call__(self, *args, output_dims=None, vmap_method=None):
num_inputs = len(args) num_inputs = len(args)
@@ -688,8 +718,7 @@ class FfiCallable:
except Exception: except Exception:
# ignore unsupported devices like TPUs # ignore unsupported devices like TPUs
pass pass
# we only support CUDA devices for now if dev.is_cuda or dev.is_cpu:
if dev.is_cuda:
module.load(dev) module.load(dev)
# save call data to be retrieved by callback # save call data to be retrieved by callback
@@ -698,7 +727,7 @@ class FfiCallable:
self.call_id += 1 self.call_id += 1
return call(*args, call_id=call_id) return call(*args, call_id=call_id)
def ffi_callback(self, call_frame): def ffi_callback(self, call_frame, platform="CUDA"):
try: try:
# On the first call, XLA runtime will query the API version and traits # On the first call, XLA runtime will query the API version and traits
# metadata using the |extension| field. Let us respond to that query # metadata using the |extension| field. Let us respond to that query
@@ -710,8 +739,8 @@ class FfiCallable:
metadata_ext = ctypes.cast(extension, ctypes.POINTER(XLA_FFI_Metadata_Extension)) metadata_ext = ctypes.cast(extension, ctypes.POINTER(XLA_FFI_Metadata_Extension))
metadata_ext.contents.metadata.contents.api_version.major_version = 0 metadata_ext.contents.metadata.contents.api_version.major_version = 0
metadata_ext.contents.metadata.contents.api_version.minor_version = 1 metadata_ext.contents.metadata.contents.api_version.minor_version = 1
# Turn on CUDA graphs for this handler. # Turn on CUDA graphs for this handler if on CUDA platform.
if self.graph_mode is GraphMode.JAX: if self.graph_mode is GraphMode.JAX and platform == "CUDA":
metadata_ext.contents.metadata.contents.traits = ( metadata_ext.contents.metadata.contents.traits = (
XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE
) )
@@ -738,6 +767,35 @@ class FfiCallable:
assert num_inputs == self.num_inputs assert num_inputs == self.num_inputs
assert num_outputs == self.num_outputs assert num_outputs == self.num_outputs
if platform == "Host":
device = wp.get_device("cpu")
# reconstruct the argument list
arg_list = []
# input and in-out args
for i, arg in enumerate(self.input_args):
if arg.is_array:
buffer = inputs[i].contents
shape = collapse_batch_dims(buffer.dims[: buffer.rank - arg.dtype_ndim], arg.type.ndim)
arr = wp.array(ptr=buffer.data, dtype=arg.type.dtype, shape=shape, device=device)
arg_list.append(arr)
else:
# scalar argument, get stashed value
value = call_desc.static_inputs[arg.name]
arg_list.append(value)
# pure output args (skip in-out FFI buffers)
for i, arg in enumerate(self.output_args):
buffer = outputs[i + self.num_in_out].contents
shape = collapse_batch_dims(buffer.dims[: buffer.rank - arg.dtype_ndim], arg.type.ndim)
arr = wp.array(ptr=buffer.data, dtype=arg.type.dtype, shape=shape, device=device)
arg_list.append(arr)
# call the Python function with reconstructed arguments
with wp.ScopedDevice(device):
self.func(*arg_list)
return
cuda_stream = get_stream_from_callframe(call_frame.contents) cuda_stream = get_stream_from_callframe(call_frame.contents)
device_ordinal = get_device_ordinal_from_callframe(call_frame.contents) device_ordinal = get_device_ordinal_from_callframe(call_frame.contents)
@@ -870,8 +928,8 @@ class FfiCallable:
arg_list.append(arr) arg_list.append(arr)
# call the Python function with reconstructed arguments # call the Python function with reconstructed arguments
with wp.ScopedStream(stream, sync_enter=False): with wp.ScopedStream(stream, sync_enter=False) if stream else wp.ScopedDevice(device):
if stream.is_capturing: if stream and stream.is_capturing:
# capturing with JAX # capturing with JAX
with wp.ScopedCapture(external=True) as capture: with wp.ScopedCapture(external=True) as capture:
self.func(*arg_list) self.func(*arg_list)
@@ -879,7 +937,7 @@ class FfiCallable:
# keep a reference to the capture object to prevent required modules getting unloaded # keep a reference to the capture object to prevent required modules getting unloaded
call_desc.capture = capture call_desc.capture = capture
elif self.graph_mode == GraphMode.WARP: elif self.graph_mode == GraphMode.WARP and device.is_cuda:
# capturing with WARP # capturing with WARP
with wp.ScopedCapture() as capture: with wp.ScopedCapture() as capture:
self.func(*arg_list) self.func(*arg_list)
@@ -892,7 +950,7 @@ class FfiCallable:
if self._graph_cache_max is not None and len(self.captures) > self._graph_cache_max: if self._graph_cache_max is not None and len(self.captures) > self._graph_cache_max:
self.captures.popitem(last=False) self.captures.popitem(last=False)
elif self.graph_mode == GraphMode.WARP_STAGED_EX: elif self.graph_mode == GraphMode.WARP_STAGED_EX and device.is_cuda:
# capturing with WARP using staging buffers and memcopies done outside of the graph # capturing with WARP using staging buffers and memcopies done outside of the graph
wp_memcpy_batch = wp._src.context.runtime.core.wp_memcpy_batch wp_memcpy_batch = wp._src.context.runtime.core.wp_memcpy_batch
@@ -935,7 +993,7 @@ class FfiCallable:
# TODO: we should have a way of freeing this # TODO: we should have a way of freeing this
call_desc.capture = capture call_desc.capture = capture
elif self.graph_mode == GraphMode.WARP_STAGED: elif self.graph_mode == GraphMode.WARP_STAGED and device.is_cuda:
# capturing with WARP using staging buffers and memcopies done inside of the graph # capturing with WARP using staging buffers and memcopies done inside of the graph
wp_cuda_graph_insert_memcpy_batch = ( wp_cuda_graph_insert_memcpy_batch = (
wp._src.context.runtime.core.wp_cuda_graph_insert_memcpy_batch wp._src.context.runtime.core.wp_cuda_graph_insert_memcpy_batch
@@ -1013,7 +1071,7 @@ class FfiCallable:
call_desc.capture = capture call_desc.capture = capture
else: else:
# not capturing # not capturing or on CPU
self.func(*arg_list) self.func(*arg_list)
except Exception as e: except Exception as e:
@@ -1621,7 +1679,7 @@ def register_ffi_callback(name: str, func: Callable, graph_compatible: bool = Tr
# TODO check that the name is not already registered # TODO check that the name is not already registered
def ffi_callback(call_frame): def ffi_callback(call_frame, platform="CUDA"):
try: try:
extension = call_frame.contents.extension_start extension = call_frame.contents.extension_start
# On the first call, XLA runtime will query the API version and traits # On the first call, XLA runtime will query the API version and traits
@@ -1633,7 +1691,7 @@ def register_ffi_callback(name: str, func: Callable, graph_compatible: bool = Tr
metadata_ext = ctypes.cast(extension, ctypes.POINTER(XLA_FFI_Metadata_Extension)) metadata_ext = ctypes.cast(extension, ctypes.POINTER(XLA_FFI_Metadata_Extension))
metadata_ext.contents.metadata.contents.api_version.major_version = 0 metadata_ext.contents.metadata.contents.api_version.major_version = 0
metadata_ext.contents.metadata.contents.api_version.minor_version = 1 metadata_ext.contents.metadata.contents.api_version.minor_version = 1
if graph_compatible: if graph_compatible and platform == "CUDA":
# Turn on CUDA graphs for this handler. # Turn on CUDA graphs for this handler.
metadata_ext.contents.metadata.contents.traits = ( metadata_ext.contents.metadata.contents.traits = (
XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE
@@ -1666,12 +1724,17 @@ def register_ffi_callback(name: str, func: Callable, graph_compatible: bool = Tr
return None return None
FFI_CCALLFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.POINTER(XLA_FFI_CallFrame)) FFI_CCALLFUNC = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.POINTER(XLA_FFI_CallFrame))
callback_func = FFI_CCALLFUNC(ffi_callback) callback_func_cuda = FFI_CCALLFUNC(lambda call_frame: ffi_callback(call_frame, platform="CUDA"))
callback_func_host = FFI_CCALLFUNC(lambda call_frame: ffi_callback(call_frame, platform="Host"))
with _FFI_REGISTRY_LOCK: with _FFI_REGISTRY_LOCK:
_FFI_CALLBACK_REGISTRY[name] = callback_func _FFI_CALLBACK_REGISTRY[f"{name}_cuda"] = callback_func_cuda
ffi_ccall_address = ctypes.cast(callback_func, ctypes.c_void_p) _FFI_CALLBACK_REGISTRY[f"{name}_host"] = callback_func_host
ffi_capsule = jax.ffi.pycapsule(ffi_ccall_address.value) ffi_ccall_address_cuda = ctypes.cast(callback_func_cuda, ctypes.c_void_p)
jax.ffi.register_ffi_target(name, ffi_capsule, platform="CUDA") ffi_capsule_cuda = jax.ffi.pycapsule(ffi_ccall_address_cuda.value)
jax.ffi.register_ffi_target(name, ffi_capsule_cuda, platform="CUDA")
ffi_ccall_address_host = ctypes.cast(callback_func_host, ctypes.c_void_p)
ffi_capsule_host = jax.ffi.pycapsule(ffi_ccall_address_host.value)
jax.ffi.register_ffi_target(name, ffi_capsule_host, platform="Host")
############################################################################### ###############################################################################
+51
View File
@@ -31,6 +31,7 @@ from mujoco.mjx.warp import test_util as tu
from mujoco.mjx.warp import warp as wp # pylint: disable=g-importing-member from mujoco.mjx.warp import warp as wp # pylint: disable=g-importing-member
import numpy as np import numpy as np
try: try:
from mujoco.mjx.warp import forward # pylint: disable=g-import-not-at-top from mujoco.mjx.warp import forward # pylint: disable=g-import-not-at-top
except ImportError: except ImportError:
@@ -300,6 +301,56 @@ class StepTest(parameterized.TestCase):
tu.assert_attr_eq(dx, d, 'mocap_quat') tu.assert_attr_eq(dx, d, 'mocap_quat')
tu.assert_attr_eq(dx, d, 'sensordata') tu.assert_attr_eq(dx, d, 'sensordata')
@parameterized.parameters(
'humanoid/humanoid.xml',
'pendula.xml',
)
def test_step_cpu(self, xml: str):
"""Tests step on the CPU device."""
if not _FORCE_TEST:
if not mjxw.WARP_INSTALLED:
self.skipTest('Warp not installed.')
batch_size = 1
m = test_util.load_test_file(xml)
m.opt.iterations = 10
m.opt.ls_iterations = 10
cpu_device = jax.devices('cpu')[0]
mx = mjx.put_model(m, impl='warp', device=cpu_device)
d = mujoco.MjData(m)
worldids = jp.arange(batch_size)
dx_batch = jax.vmap(functools.partial(tu.make_data, m))(worldids)
dx_batch = jax.device_put(dx_batch, cpu_device)
dx_batch_orig = dx_batch
for _ in range(10):
dx_batch = jax.vmap(forward.step, in_axes=(None, 0))(
mx, dx_batch
)
for i in range(batch_size):
dx = dx_batch[i]
dx_orig = dx_batch_orig[i]
d.qpos[:] = dx_orig.qpos
d.qvel[:] = dx_orig.qvel
d.ctrl[:] = dx_orig.ctrl
d.mocap_pos[:] = dx_orig.mocap_pos
d.mocap_quat[:] = dx_orig.mocap_quat
d.time = dx_orig.time
mujoco.mj_step(m, d, 10)
tu.assert_attr_eq(dx, d, 'qpos')
tu.assert_attr_eq(dx, d, 'qvel')
tu.assert_attr_eq(dx, d, 'time')
tu.assert_attr_eq(dx, d, 'ctrl')
tu.assert_attr_eq(dx, d, 'act')
tu.assert_attr_eq(dx, d, 'mocap_pos')
tu.assert_attr_eq(dx, d, 'mocap_quat')
tu.assert_attr_eq(dx, d, 'sensordata')
def test_step_leading_dim_mismatch(self): def test_step_leading_dim_mismatch(self):
if not _FORCE_TEST: if not _FORCE_TEST:
if not mjxw.WARP_INSTALLED: if not mjxw.WARP_INSTALLED:
+5 -4
View File
@@ -133,10 +133,11 @@ class SmoothTest(parameterized.TestCase):
def test_kinematics_vmap(self): def test_kinematics_vmap(self):
"""Tests kinematics with batched data.""" """Tests kinematics with batched data."""
if not mjxw.WARP_INSTALLED: if not _FORCE_TEST:
self.skipTest('Warp not installed.') if not mjxw.WARP_INSTALLED:
if not io.has_cuda_gpu_device(): self.skipTest('Warp not installed.')
self.skipTest('No CUDA GPU device available.') if not io.has_cuda_gpu_device():
self.skipTest('No CUDA GPU device available.')
m = tu.load_test_file('pendula.xml') m = tu.load_test_file('pendula.xml')
+6 -2
View File
@@ -153,7 +153,9 @@ def _mjx_efc(dx, worldid: int):
efc_pos = select(dx._impl.efc__pos)[:nefc] efc_pos = select(dx._impl.efc__pos)[:nefc]
efc_type = select(dx._impl.efc__type)[:nefc] efc_type = select(dx._impl.efc__type)[:nefc]
efc_d = select(dx._impl.efc__D)[:nefc] efc_d = select(dx._impl.efc__D)[:nefc]
keys_sorted = np.lexsort((-efc_pos, efc_type, efc_d)) keys_sorted = np.lexsort(
(-np.round(efc_pos, 12), efc_type, np.round(efc_d, 12))
)
keys = keys[keys_sorted] keys = keys[keys_sorted]
nefc = len(keys) nefc = len(keys)
@@ -180,7 +182,9 @@ def _mj_efc(d):
else: else:
efc_j = d.efc_J.reshape((-1, d.qvel.shape[0])) efc_j = d.efc_J.reshape((-1, d.qvel.shape[0]))
keys = np.lexsort((-d.efc_pos, d.efc_type, d.efc_D)) keys = np.lexsort(
(-np.round(d.efc_pos, 12), d.efc_type, np.round(d.efc_D, 12))
)
type_ = d.efc_type[keys] type_ = d.efc_type[keys]
pos = d.efc_pos[keys] pos = d.efc_pos[keys]
efc_j = efc_j[keys] efc_j = efc_j[keys]
+5 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name="mujoco-mjx" name="mujoco-mjx"
version = "3.7.1" version = "3.8.1"
authors = [ authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"}, {name = "Google DeepMind", email = "mujoco@deepmind.com"},
] ]
@@ -21,6 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering",
] ]
requires-python = ">=3.10" requires-python = ">=3.10"
@@ -29,7 +30,7 @@ dependencies = [
"etils[epath]", "etils[epath]",
"jax", "jax",
"jaxlib", "jaxlib",
"mujoco>=3.7.1.dev0", "mujoco>=3.8.1.dev0",
"scipy", "scipy",
"trimesh", "trimesh",
] ]
@@ -45,9 +46,9 @@ mjx-viewer = "mujoco.mjx.viewer:main"
[project.urls] [project.urls]
Homepage = "https://github.com/google-deepmind/mujoco/tree/main/mjx" Homepage = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Documentation = "https://mujoco.readthedocs.io/en/3.7.1" Documentation = "https://mujoco.readthedocs.io/en/3.8.1"
Repository = "https://github.com/google-deepmind/mujoco/tree/main/mjx" Repository = "https://github.com/google-deepmind/mujoco/tree/main/mjx"
Changelog = "https://mujoco.readthedocs.io/en/3.7.1/changelog.html" Changelog = "https://mujoco.readthedocs.io/en/3.8.1/changelog.html"
[tool.isort] [tool.isort]
force_single_line = true force_single_line = true
+177 -78
View File
@@ -2,29 +2,48 @@ absl-py==2.1.0 \
--hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308 --hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308
etils[epath]==1.10.0; python_version >= '3.10' \ etils[epath]==1.10.0; python_version >= '3.10' \
--hash=sha256:0777fe60a234b4c65ca53470fc64f2dd2d0c6bca7fcc623fdaa8d7fa5a317098 --hash=sha256:0777fe60a234b4c65ca53470fc64f2dd2d0c6bca7fcc623fdaa8d7fa5a317098
jax==0.5.3; python_version >= '3.10' and (sys_platform != 'darwin' or platform_machine != 'x86_64') \ jax==0.8.3; python_version >= '3.13' and (sys_platform != 'darwin' or platform_machine != 'x86_64') \
--hash=sha256:fb75f4cfee64e990ce6d7a0424cb11eb0520e4de19d7a52d0ca3498bff78261a
jax==0.5.3; python_version >= '3.10' and python_version < '3.13' and (sys_platform != 'darwin' or platform_machine != 'x86_64') \
--hash=sha256:1483dc237b4f47e41755d69429e8c3c138736716147cd43bb2b99b259d4e3c41 \ --hash=sha256:1483dc237b4f47e41755d69429e8c3c138736716147cd43bb2b99b259d4e3c41 \
--hash=sha256:f17fcb0fd61dc289394af6ce4de2dada2312f2689bb0d73642c6f026a95fbb2c --hash=sha256:f17fcb0fd61dc289394af6ce4de2dada2312f2689bb0d73642c6f026a95fbb2c
jax==0.4.38; python_version >= '3.10' and sys_platform == 'darwin' and platform_machine == 'x86_64' \ # 0.4.38 is the last Jax release that provides macOS x86-64 wheels, and only up
# to Python 3.13. Unfortunately, later releases can't readily be built from
# sources because some dependencies, like jaxlib, don't provide tarballs.
# Thus, building with Python 3.14+ on macOS x86-64 is disabled in the build
# scripts; if it's still needed, we'll have to come up with another method.
jax==0.4.38; python_version >= '3.10' and python_version <= '3.13' and sys_platform == 'darwin' and platform_machine == 'x86_64' \
--hash=sha256:78987306f7041ea8500d99df1a17c33ed92620c2268c4c3677fb24e06712be64 --hash=sha256:78987306f7041ea8500d99df1a17c33ed92620c2268c4c3677fb24e06712be64
jaxlib==0.5.3; python_version >= '3.10' and (sys_platform != 'darwin' or platform_machine != 'x86_64') \ jaxlib==0.8.3; python_version >= '3.13' and (sys_platform != 'darwin' or platform_machine != 'x86_64') \
--hash=sha256:1b3acadba65863254cc5482455d31a41f1fc8d38a449701f13cdbbc45beb240d \
--hash=sha256:1e5481f9d7df9bfc0ae053d9f9d4f97ee77639a0169b45beb1cced070dc26da6 \
--hash=sha256:3233198422c7ef49e5340785fc23f61c25ee61a5d767aa6f9183b8015e6ac6e2 \
--hash=sha256:41067d2ec14a140e1d692fc84bc714f3372e208966b5f29cc309f4fca63c081b \
--hash=sha256:4be4273edeb2cc75c409e446fd4f59a63d2e14f1311e714a978b5fa67ac16b70 \
--hash=sha256:4d80575513f351eef4582908039c6e456d4e2ca028a043d2cdcbd059fa55e56c \
--hash=sha256:56adca7fc1e24972633e2f33c758a28d924e116ba54b2a4e73f8ae4647657cf1 \
--hash=sha256:831d817fa04218cc91b813920229c8ee0bec076c03ea744bec398e570b699d1c \
--hash=sha256:92e755030e7862d3ba15929f25c0d7647baa95b083c87ed0c2da297aeb50c48c \
--hash=sha256:9e76868758330eb63c5e4dd31e4d7c500a7e5523c7f09b34f64780c4a0503c7c \
--hash=sha256:be237754eead89788264e112b2b5c722ab2a941406d19f1a8da6bf48bfde0bf2 \
--hash=sha256:c96e562ad771fc81dfbb9a696519caa7e214f9b6dab1ce1df8bcd4759597d4a3 \
--hash=sha256:ef3376145cc6c768f7847b688b004f37382e952bdc34b9a9404eb28b15ab50f8 \
--hash=sha256:fe490fe3d81b02d21aaf936475fc4f47171473599e9d3907f6be0823ec4321bb
jaxlib==0.5.3; python_version >= '3.10' and python_version < '3.13' and (sys_platform != 'darwin' or platform_machine != 'x86_64') \
--hash=sha256:29e1530fc81833216f1e28b578d0c59697654f72ee31c7a44ed7753baf5ac466 \
--hash=sha256:48ff5c89fb8a0fe04d475e9ddc074b4879a91d7ab68a51cec5cd1e87f81e6c47 \ --hash=sha256:48ff5c89fb8a0fe04d475e9ddc074b4879a91d7ab68a51cec5cd1e87f81e6c47 \
--hash=sha256:972400db4af6e85270d81db5e6e620d31395f0472e510c50dfcd4cb3f72b7220 \ --hash=sha256:520665929649f29f7d948d4070dbaf3e032a4c1f7c11f2863eac73320fcee784 \
--hash=sha256:52be6c9775aff738a61170d8c047505c75bb799a45518e66a7a0908127b11785 \ --hash=sha256:52be6c9775aff738a61170d8c047505c75bb799a45518e66a7a0908127b11785 \
--hash=sha256:5a5e88ab1cd6fdf78d69abe3544e8f09cce200dd339bb85fbe3c2ea67f2a5e68 \
--hash=sha256:8eb54e38d789557579f900ea3d70f104a440f8555a9681ed45f4a122dcbfd92e \
--hash=sha256:972400db4af6e85270d81db5e6e620d31395f0472e510c50dfcd4cb3f72b7220 \
--hash=sha256:a4666f81d72c060ed3e581ded116a9caa9b0a70a148a54cb12a1d3afca3624b5 \
--hash=sha256:b41a6fcaeb374fabc4ee7e74cfed60843bdab607cd54f60a68b7f7655cde2b66 \ --hash=sha256:b41a6fcaeb374fabc4ee7e74cfed60843bdab607cd54f60a68b7f7655cde2b66 \
--hash=sha256:b62bd8b29e5a4f9bfaa57c8daf6e04820b2c994f448f3dec602d64255545e9f2 \ --hash=sha256:b62bd8b29e5a4f9bfaa57c8daf6e04820b2c994f448f3dec602d64255545e9f2 \
--hash=sha256:a4666f81d72c060ed3e581ded116a9caa9b0a70a148a54cb12a1d3afca3624b5 \
--hash=sha256:29e1530fc81833216f1e28b578d0c59697654f72ee31c7a44ed7753baf5ac466 \
--hash=sha256:8eb54e38d789557579f900ea3d70f104a440f8555a9681ed45f4a122dcbfd92e \
--hash=sha256:d394dbde4a1c6bd67501cfb29d3819a10b900cb534cc0fc603319f7092f24cfa \
--hash=sha256:bddf6360377aa1c792e47fd87f307c342e331e5ff3582f940b1bca00f6b4bc73 \ --hash=sha256:bddf6360377aa1c792e47fd87f307c342e331e5ff3582f940b1bca00f6b4bc73 \
--hash=sha256:5a5e88ab1cd6fdf78d69abe3544e8f09cce200dd339bb85fbe3c2ea67f2a5e68 \ --hash=sha256:d394dbde4a1c6bd67501cfb29d3819a10b900cb534cc0fc603319f7092f24cfa
--hash=sha256:520665929649f29f7d948d4070dbaf3e032a4c1f7c11f2863eac73320fcee784 \ # See note above about macOS x86-64 support.
--hash=sha256:31321c25282a06a6dfc940507bc14d0a0ac838d8ced6c07aa00a7fae34ce7b3f \ jaxlib==0.4.38; python_version >= '3.10' and python_version <= '3.13' and sys_platform == 'darwin' and platform_machine == 'x86_64' \
--hash=sha256:e904b92dedfbc7e545725a8d7676987030ae9c069001d94701bc109c6dab4100 \
--hash=sha256:bb7593cb7fffcb13963f22fa5229ed960b8fb4ae5ec3b0820048cbd67f1e8e31 \
--hash=sha256:8019f73a10b1290f988dd3768c684f3a8a147239091c3b790ce7e47e3bbc00bd
jaxlib==0.4.38; python_version >= '3.10' and sys_platform == 'darwin' and platform_machine == 'x86_64' \
--hash=sha256:55c19b9d3f33a6fc59f644aa5a21fba02639ccdd776cb4a9b5526625f57839ff \ --hash=sha256:55c19b9d3f33a6fc59f644aa5a21fba02639ccdd776cb4a9b5526625f57839ff \
--hash=sha256:30b2f52cb50d74734af2f477c2533a7a583e3bb7b2c8acdeb361ee77d940577a \ --hash=sha256:30b2f52cb50d74734af2f477c2533a7a583e3bb7b2c8acdeb361ee77d940577a \
--hash=sha256:ee19c163a8fdf0839d4c18b88a5fbfb4e731ba7c437416d3e5483e570bb764e4 \ --hash=sha256:ee19c163a8fdf0839d4c18b88a5fbfb4e731ba7c437416d3e5483e570bb764e4 \
@@ -52,35 +71,68 @@ pytest==8.3.3 \
--hash=sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2 --hash=sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2
pytest-xdist==3.6.1 \ pytest-xdist==3.6.1 \
--hash=sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7 --hash=sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7
scipy==1.14.1; python_version >= '3.10' \ scipy==1.17.0; python_version >= '3.13' \
--hash=sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84 \ --hash=sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73 \
--hash=sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e \ --hash=sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00 \
--hash=sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d \ --hash=sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209 \
--hash=sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e \ --hash=sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1 \
--hash=sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73 \ --hash=sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269 \
--hash=sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e \ --hash=sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088 \
--hash=sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79 \ --hash=sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1 \
--hash=sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f \ --hash=sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf \
--hash=sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066 \ --hash=sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061 \
--hash=sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310 \ --hash=sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e \
--hash=sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc \ --hash=sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61 \
--hash=sha256:4e00562e519c09da34c31685f6acc3aa384d4d50604db0f245c14e1b4488bfa2 \
--hash=sha256:5fb10d17e649e1446410895639f3385fd2bf4c3c7dfc9bea937bddcbc3d7b9ba \
--hash=sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6 \
--hash=sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752 \
--hash=sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45 \
--hash=sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97 \
--hash=sha256:8547e7c57f932e7354a2319fab613981cde910631979f74c9b542bb167a8b9db \
--hash=sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812 \
--hash=sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07 \
--hash=sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72 \
--hash=sha256:a38c3337e00be6fd8a95b4ed66b5d988bac4ec888fd922c2ea9fe5fb1603dd67 \
--hash=sha256:aabf057c632798832f071a8dde013c2e26284043934f53b00489f1773b33527e \
--hash=sha256:c17514d11b78be8f7e6331b983a65a7f5ca1fd037b95e27b280921fe5606286a \
--hash=sha256:c5e8647f60679790c2f5c76be17e2e9247dc6b98ad0d3b065861e082c56e078d \
--hash=sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04 \
--hash=sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea \
--hash=sha256:dbf133ced83889583156566d2bdf7a07ff89228fe0c0cb727f777de92092ec6b \
--hash=sha256:eb2651271135154aa24f6481cbae5cc8af1f0dd46e6533fb7b56aa9727b6a232 \
--hash=sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3 \
--hash=sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0 \
--hash=sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d \
--hash=sha256:f2a4942b0f5f7c23c7cd641a0ca1955e2ae83dedcff537e3a0259096635e186b \
--hash=sha256:f7df7941d71314e60a481e02d5ebcb3f0185b8d799c70d03d8258f6c80f3d467 \
--hash=sha256:f9eb55bb97d00f8b7ab95cb64f873eb0bf54d9446264d9f3609130381233483f \
--hash=sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6
scipy==1.14.1; python_version >= '3.10' and python_version < '3.13' \
--hash=sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37 \
--hash=sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5 \ --hash=sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5 \
--hash=sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07 \ --hash=sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675 \
--hash=sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d \
--hash=sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f \
--hash=sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310 \
--hash=sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617 \
--hash=sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d \ --hash=sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d \
--hash=sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94 \ --hash=sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94 \
--hash=sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2 \ --hash=sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad \
--hash=sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37 \
--hash=sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8 \ --hash=sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8 \
--hash=sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617 \
--hash=sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2 \
--hash=sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675 \
--hash=sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5 \
--hash=sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69 \
--hash=sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d \
--hash=sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3 \
--hash=sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0 \ --hash=sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0 \
--hash=sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69 \
--hash=sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066 \
--hash=sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3 \
--hash=sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5 \
--hash=sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07 \
--hash=sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2 \
--hash=sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389 \
--hash=sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2 \
--hash=sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3 \ --hash=sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3 \
--hash=sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389 --hash=sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc \
--hash=sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1 \
--hash=sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2
setuptools==78.1.1 \ setuptools==78.1.1 \
--hash=sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561 \ --hash=sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561 \
--hash=sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d --hash=sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d
@@ -100,56 +152,103 @@ zipp==3.21.0 \
--hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931 --hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931
# Transitive dependencies of jax and jaxlib # Transitive dependencies of jax and jaxlib
ml-dtypes==0.5.0 \ ml-dtypes==0.5.4; python_version >= '3.13' \
--hash=sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07 \ --hash=sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf \
--hash=sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab \ --hash=sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f \
--hash=sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599 \ --hash=sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7 \
--hash=sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f \ --hash=sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22 \
--hash=sha256:afa08343069874a30812871d639f9c02b4158ace065601406a493a8511180c02 \ --hash=sha256:304ad47faa395415b9ccbcc06a0350800bc50eda70f0e45326796e27c62f18b6 \
--hash=sha256:4381fe2f2452a2d7589689693d3162e876b3ddb0a832cde7a414f8e1adf7eab1 \
--hash=sha256:531eff30e4d368cb6255bc2328d070e35836aa4f282a0fb5f3a0cd7260257298 \
--hash=sha256:533ce891ba774eabf607172254f2e7260ba5f57bdd64030c9a4fcfbd99815d0d \
--hash=sha256:6a0df4223b514d799b8a1629c65ddc351b3efa833ccf7f8ea0cf654a61d1e35d \
--hash=sha256:805cef3a38f4eafae3a5bf9ebdcdb741d0bcfd9e1bd90eb54abd24f928cd2465 \
--hash=sha256:8c6a2dcebd6f3903e05d51960a8058d6e131fe69f952a5397e5dbabc841b6d56 \
--hash=sha256:8c760d85a2f82e2bed75867079188c9d18dae2ee77c25a54d60e9cc79be1bc48 \
--hash=sha256:bfc534409c5d4b0bf945af29e5d0ab075eae9eecbb549ff8a29280db822f34f9 \
--hash=sha256:cb73dccfc991691c444acc8c0012bee8f2470da826a92e3a20bb333b1a7894e6 \
--hash=sha256:ce756d3a10d0c4067172804c9cc276ba9cc0ff47af9078ad439b075d1abdc29b \
--hash=sha256:f21c9219ef48ca5ee78402d5cc831bd58ea27ce89beda894428bc67a52da5328
ml-dtypes==0.5.0; python_version < '3.13' \
--hash=sha256:2e7534392682c3098bc7341648c650864207169c654aed83143d7a19c67ae06f \
--hash=sha256:60275f2b51b56834e840c4809fca840565f9bf8e9a73f6d8c94f5b5935701215 \
--hash=sha256:76942f6aeb5c40766d5ea62386daa4148e6a54322aaf5b53eae9e7553240222f \
--hash=sha256:8c32138975797e681eb175996d64356bcfa124bdbb6a70460b9768c2b35a6fa4 \
--hash=sha256:968fede07d1f9b926a63df97d25ac656cac1a57ebd33701734eaf704bc55d8d8 \
--hash=sha256:a38df8df61194aeaae1ab7579075779b4ad32cd1cffd012c28be227fa7f2a70a \ --hash=sha256:a38df8df61194aeaae1ab7579075779b4ad32cd1cffd012c28be227fa7f2a70a \
--hash=sha256:a988bac6572630e1e9c2edd9b1277b4eefd1c86209e52b0d061b775ac33902ff \ --hash=sha256:a988bac6572630e1e9c2edd9b1277b4eefd1c86209e52b0d061b775ac33902ff \
--hash=sha256:d4b1a70a3e5219790d6b55b9507606fc4e02911d1497d16c18dd721eb7efe7d0 \
--hash=sha256:dc74fd9995513d33eac63d64e436240f5494ec74d522a9f0920194942fc3d2d7 \
--hash=sha256:2e7534392682c3098bc7341648c650864207169c654aed83143d7a19c67ae06f \
--hash=sha256:76942f6aeb5c40766d5ea62386daa4148e6a54322aaf5b53eae9e7553240222f \
--hash=sha256:60275f2b51b56834e840c4809fca840565f9bf8e9a73f6d8c94f5b5935701215 \
--hash=sha256:968fede07d1f9b926a63df97d25ac656cac1a57ebd33701734eaf704bc55d8d8 \
--hash=sha256:c7a9152f5876fef565516aa5dd1dccd6fc298a5891b2467973905103eb5c7856 \
--hash=sha256:ab046f2ff789b1f11b2491909682c5d089934835f9a760fafc180e47dcb676b8 \ --hash=sha256:ab046f2ff789b1f11b2491909682c5d089934835f9a760fafc180e47dcb676b8 \
--hash=sha256:8c32138975797e681eb175996d64356bcfa124bdbb6a70460b9768c2b35a6fa4 \ --hash=sha256:afa08343069874a30812871d639f9c02b4158ace065601406a493a8511180c02 \
--hash=sha256:7ee9c320bb0f9ffdf9f6fa6a696ef2e005d1f66438d6f1c1457338e00a02e8cf \ --hash=sha256:c7a9152f5876fef565516aa5dd1dccd6fc298a5891b2467973905103eb5c7856 \
--hash=sha256:a03fc861b86cc586728e3d093ba37f0cc05e65330c3ebd7688e7bae8290f8859 \ --hash=sha256:d4b1a70a3e5219790d6b55b9507606fc4e02911d1497d16c18dd721eb7efe7d0 \
--hash=sha256:099e09edd54e676903b4538f3815b5ab96f5b119690514602d96bfdb67172cbe \ --hash=sha256:dc74fd9995513d33eac63d64e436240f5494ec74d522a9f0920194942fc3d2d7
--hash=sha256:5f2b59233a0dbb6a560b3137ed6125433289ccba2f8d9c3695a52423a369ed15 numpy==2.4.4; python_version >= '3.13' \
numpy==2.1.3; python_version >= '3.10' \ --hash=sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50 \
--hash=sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed \ --hash=sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959 \
--hash=sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56 \ --hash=sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a \
--hash=sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43 \ --hash=sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d \
--hash=sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe \ --hash=sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e \
--hash=sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57 \ --hash=sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a \
--hash=sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598 \ --hash=sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113 \
--hash=sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f \ --hash=sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103 \
--hash=sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93 \
--hash=sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af \
--hash=sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5 \
--hash=sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7 \
--hash=sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392 \
--hash=sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40 \
--hash=sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf \
--hash=sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b \
--hash=sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74 \
--hash=sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d \
--hash=sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150 \
--hash=sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8 \
--hash=sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a \
--hash=sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed \
--hash=sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e \
--hash=sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0 \
--hash=sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e \
--hash=sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f \
--hash=sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83 \
--hash=sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d \
--hash=sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c \
--hash=sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252 \
--hash=sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115 \
--hash=sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f \
--hash=sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0 \
--hash=sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e
numpy==2.1.3; python_version >= '3.10' and python_version < '3.13' \
--hash=sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0 \
--hash=sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a \ --hash=sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a \
--hash=sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b \
--hash=sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512 \
--hash=sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564 \ --hash=sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564 \
--hash=sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8 \
--hash=sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958 \ --hash=sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958 \
--hash=sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e \ --hash=sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0 \
--hash=sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2 \ --hash=sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee \
--hash=sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b \ --hash=sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b \
--hash=sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a \ --hash=sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4 \
--hash=sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09 \
--hash=sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9 \
--hash=sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41 \
--hash=sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d \ --hash=sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d \
--hash=sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0 \ --hash=sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f \
--hash=sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098 \ --hash=sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9 \
--hash=sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3 \
--hash=sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd \ --hash=sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd \
--hash=sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a \
--hash=sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098 \
--hash=sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1 \ --hash=sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1 \
--hash=sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512 \
--hash=sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09 \
--hash=sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc \
--hash=sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8 \
--hash=sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5 \ --hash=sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5 \
--hash=sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff --hash=sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b \
--hash=sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d \
--hash=sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c \
--hash=sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41 \
--hash=sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff \
--hash=sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2 \
--hash=sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb \
--hash=sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3 \
--hash=sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0 \
--hash=sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e \
--hash=sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4
opt-einsum==3.4.0 \ opt-einsum==3.4.0 \
--hash=sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd --hash=sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd
+36 -1
View File
@@ -15,7 +15,42 @@ glfw==2.9.0 \
--hash=sha256:fcc430cb21984afba74945b7df38a5e1a02b36c0b4a2a2bab42b4a26d7cc51d6 \ --hash=sha256:fcc430cb21984afba74945b7df38a5e1a02b36c0b4a2a2bab42b4a26d7cc51d6 \
--hash=sha256:aef5b555673b9555216e4cd7bc0bdbbb9983f66c620a85ba7310cfcfda5cd38c \ --hash=sha256:aef5b555673b9555216e4cd7bc0bdbbb9983f66c620a85ba7310cfcfda5cd38c \
--hash=sha256:183da99152f63469e9263146db2eb1b6cc4ee0c4082b280743e57bd1b0a3bd70 --hash=sha256:183da99152f63469e9263146db2eb1b6cc4ee0c4082b280743e57bd1b0a3bd70
numpy==2.1.3; python_version >= '3.10' \ numpy==2.4.4; python_version >= '3.13' \
--hash=sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50 \
--hash=sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959 \
--hash=sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a \
--hash=sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d \
--hash=sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e \
--hash=sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a \
--hash=sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113 \
--hash=sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103 \
--hash=sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93 \
--hash=sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af \
--hash=sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5 \
--hash=sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7 \
--hash=sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392 \
--hash=sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40 \
--hash=sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf \
--hash=sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b \
--hash=sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74 \
--hash=sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d \
--hash=sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150 \
--hash=sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8 \
--hash=sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a \
--hash=sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed \
--hash=sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e \
--hash=sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0 \
--hash=sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e \
--hash=sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f \
--hash=sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83 \
--hash=sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d \
--hash=sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c \
--hash=sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252 \
--hash=sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115 \
--hash=sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f \
--hash=sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0 \
--hash=sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e
numpy==2.1.3; python_version >= '3.10' and python_version < '3.13' \
--hash=sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed \ --hash=sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed \
--hash=sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56 \ --hash=sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56 \
--hash=sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43 \ --hash=sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43 \
+92 -36
View File
@@ -1,39 +1,95 @@
pillow==10.4.0 \ pillow==12.1.0 \
--hash=sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea \ --hash=sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d \
--hash=sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc \ --hash=sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc \
--hash=sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0 \ --hash=sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84 \
--hash=sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be \ --hash=sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de \
--hash=sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70 \ --hash=sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0 \
--hash=sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb \ --hash=sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef \
--hash=sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3 \ --hash=sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4 \
--hash=sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a \ --hash=sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82 \
--hash=sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a \ --hash=sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9 \
--hash=sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef \ --hash=sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030 \
--hash=sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca \ --hash=sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0 \
--hash=sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80 \ --hash=sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18 \
--hash=sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597 \ --hash=sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a \
--hash=sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94 \ --hash=sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef \
--hash=sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91 \ --hash=sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b \
--hash=sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319 \ --hash=sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6 \
--hash=sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe \ --hash=sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179 \
--hash=sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6 \ --hash=sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e \
--hash=sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3 \ --hash=sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72 \
--hash=sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be \ --hash=sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64 \
--hash=sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c \ --hash=sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451 \
--hash=sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141 \ --hash=sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd \
--hash=sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc \ --hash=sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924 \
--hash=sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b \ --hash=sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616 \
--hash=sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f \ --hash=sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a \
--hash=sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856 \ --hash=sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94 \
--hash=sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d \ --hash=sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc \
--hash=sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e \ --hash=sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8 \
--hash=sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5 \ --hash=sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9 \
--hash=sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c \ --hash=sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91 \
--hash=sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b \ --hash=sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a \
--hash=sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126 \ --hash=sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c \
--hash=sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd \ --hash=sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670 \
--hash=sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b \ --hash=sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea \
--hash=sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d --hash=sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91 \
--hash=sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c \
--hash=sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc \
--hash=sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0 \
--hash=sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b \
--hash=sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65 \
--hash=sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661 \
--hash=sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19 \
--hash=sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1 \
--hash=sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0 \
--hash=sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e \
--hash=sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75 \
--hash=sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4 \
--hash=sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8 \
--hash=sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd \
--hash=sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7 \
--hash=sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61 \
--hash=sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51 \
--hash=sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551 \
--hash=sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45 \
--hash=sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1 \
--hash=sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644 \
--hash=sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796 \
--hash=sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587 \
--hash=sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304 \
--hash=sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b \
--hash=sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8 \
--hash=sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17 \
--hash=sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171 \
--hash=sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3 \
--hash=sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7 \
--hash=sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988 \
--hash=sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a \
--hash=sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0 \
--hash=sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c \
--hash=sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2 \
--hash=sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14 \
--hash=sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5 \
--hash=sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a \
--hash=sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377 \
--hash=sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0 \
--hash=sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5 \
--hash=sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b \
--hash=sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d \
--hash=sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac \
--hash=sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c \
--hash=sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554 \
--hash=sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643 \
--hash=sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13 \
--hash=sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09 \
--hash=sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208 \
--hash=sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda \
--hash=sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea \
--hash=sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e \
--hash=sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0 \
--hash=sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831 \
--hash=sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd
usd-core==24.11; python_version<='3.11' and (platform_machine=='x86_64' or platform_system=='Darwin') \ usd-core==24.11; python_version<='3.11' and (platform_machine=='x86_64' or platform_system=='Darwin') \
--hash=sha256:b25bde521bb65497b8bb882e4dd0de03d111dab4937c941ff4ceea6238933d5b \ --hash=sha256:b25bde521bb65497b8bb882e4dd0de03d111dab4937c941ff4ceea6238933d5b \
--hash=sha256:a0416e3f5bc120977028d82dda38bd652478042c228d9a7d053f736bb79cde96 \ --hash=sha256:a0416e3f5bc120977028d82dda38bd652478042c228d9a7d053f736bb79cde96 \
+3 -3
View File
@@ -86,7 +86,7 @@ if(NOT TARGET mujoco)
if(MUJOCO_FRAMEWORK) if(MUJOCO_FRAMEWORK)
message("MuJoCo framework is at ${MUJOCO_FRAMEWORK}/mujoco.framework") message("MuJoCo framework is at ${MUJOCO_FRAMEWORK}/mujoco.framework")
set(MUJOCO_LIBRARY set(MUJOCO_LIBRARY
${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.7.1.dylib ${MUJOCO_FRAMEWORK}/mujoco.framework/Versions/A/libmujoco.3.8.1.dylib
) )
target_compile_options(mujoco INTERFACE -F${MUJOCO_FRAMEWORK}) target_compile_options(mujoco INTERFACE -F${MUJOCO_FRAMEWORK})
endif() endif()
@@ -94,7 +94,7 @@ if(NOT TARGET mujoco)
if(NOT MUJOCO_FRAMEWORK) if(NOT MUJOCO_FRAMEWORK)
find_library( find_library(
MUJOCO_LIBRARY mujoco mujoco.3.7.1 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED MUJOCO_LIBRARY mujoco mujoco.3.8.1 HINTS ${MUJOCO_LIBRARY_DIR} REQUIRED
) )
find_path(MUJOCO_INCLUDE mujoco/mujoco.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED) find_path(MUJOCO_INCLUDE mujoco/mujoco.h HINTS ${MUJOCO_INCLUDE_DIR} REQUIRED)
message("MuJoCo is at ${MUJOCO_LIBRARY}") message("MuJoCo is at ${MUJOCO_LIBRARY}")
@@ -193,7 +193,7 @@ findorfetch(
GIT_REPO GIT_REPO
https://github.com/pybind/pybind11 https://github.com/pybind/pybind11
GIT_TAG GIT_TAG
d0f1a2168f3335426f544171a3463c36edbd5cc3 # v3.0.3 c7fb32eea8c92bebeea9f0735041a72aa20c75f5 # v3.0.4
TARGETS TARGETS
pybind11::pybind11_headers pybind11::pybind11_headers
EXCLUDE_FROM_ALL EXCLUDE_FROM_ALL
+4 -4
View File
@@ -7,13 +7,13 @@
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>org.mujoco.mjpython</string> <string>org.mujoco.mjpython</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>3.7.1</string> <string>3.8.1</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>3.7.1</string> <string>3.8.1</string>
<key>CFBundleLongVersionString</key> <key>CFBundleLongVersionString</key>
<string>3.7.1</string> <string>3.8.1</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.7.1</string> <string>3.8.1</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>mjpython</string> <string>mjpython</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
+4 -3
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "mujoco" name = "mujoco"
version = "3.7.1" version = "3.8.1"
authors = [ authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"}, {name = "Google DeepMind", email = "mujoco@deepmind.com"},
] ]
@@ -21,6 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering",
] ]
dependencies = [ dependencies = [
@@ -34,9 +35,9 @@ dynamic = ["readme", "scripts"]
[project.urls] [project.urls]
Homepage = "https://github.com/google-deepmind/mujoco" Homepage = "https://github.com/google-deepmind/mujoco"
Documentation = "https://mujoco.readthedocs.io/en/3.7.1" Documentation = "https://mujoco.readthedocs.io/en/3.8.1"
Repository = "https://github.com/google-deepmind/mujoco" Repository = "https://github.com/google-deepmind/mujoco"
Changelog = "https://mujoco.readthedocs.io/en/3.7.1/changelog.html" Changelog = "https://mujoco.readthedocs.io/en/3.8.1/changelog.html"
[tool.setuptools] [tool.setuptools]
include-package-data = false include-package-data = false
+1 -1
View File
@@ -24,7 +24,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project( project(
mujoco_samples mujoco_samples
VERSION 3.7.1 VERSION 3.8.1
DESCRIPTION "MuJoCo samples binaries" DESCRIPTION "MuJoCo samples binaries"
HOMEPAGE_URL "https://mujoco.org" HOMEPAGE_URL "https://mujoco.org"
) )
+5 -1
View File
@@ -18,7 +18,11 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_EXTENSIONS OFF) if(EMSCRIPTEN)
set(CMAKE_C_EXTENSIONS ON)
else()
set(CMAKE_C_EXTENSIONS OFF)
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For LLVM tooling set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For LLVM tooling
if(NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_CONFIGURATION_TYPES)
+1 -1
View File
@@ -29,7 +29,7 @@ set(MUJOCO_DEP_VERSION_lodepng
project( project(
mujoco_simulate mujoco_simulate
VERSION 3.7.1 VERSION 3.8.1
DESCRIPTION "MuJoCo simulate binaries" DESCRIPTION "MuJoCo simulate binaries"
HOMEPAGE_URL "https://mujoco.org" HOMEPAGE_URL "https://mujoco.org"
) )
+5 -1
View File
@@ -18,7 +18,11 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_C_EXTENSIONS OFF) if(EMSCRIPTEN)
set(CMAKE_C_EXTENSIONS ON)
else()
set(CMAKE_C_EXTENSIONS OFF)
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For LLVM tooling set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # For LLVM tooling
if(NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_CONFIGURATION_TYPES)
+2 -2
View File
@@ -43,8 +43,8 @@
//-------------------------- Constants ------------------------------------------------------------- //-------------------------- Constants -------------------------------------------------------------
#define mjVERSION 3007001 #define mjVERSION 3008001
#define mjVERSIONSTRING "3.7.1" #define mjVERSIONSTRING "3.8.1"
// names of disable flags // names of disable flags
const char* mjDISABLESTRING[mjNDISABLE] = { const char* mjDISABLESTRING[mjNDISABLE] = {
+38 -24
View File
@@ -32,10 +32,6 @@ target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
filament/filament_context.h filament/filament_context.h
filament/filament_platform_factory.cc filament/filament_platform_factory.cc
filament/filament_platform_factory.h filament/filament_platform_factory.h
filament/imgui_bridge.cc
filament/imgui_bridge.h
filament/imgui_editor.cc
filament/imgui_editor.h
filament/light.cc filament/light.cc
filament/light.h filament/light.h
filament/material.cc filament/material.cc
@@ -44,8 +40,6 @@ target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
filament/math_util.h filament/math_util.h
filament/mesh.cc filament/mesh.cc
filament/mesh.h filament/mesh.h
filament/model_objects.cc
filament/model_objects.h
filament/model_util.h filament/model_util.h
filament/object_manager.cc filament/object_manager.cc
filament/object_manager.h filament/object_manager.h
@@ -53,14 +47,22 @@ target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
filament/render_target.h filament/render_target.h
filament/renderable.cc filament/renderable.cc
filament/renderable.h filament/renderable.h
filament/scene_bridge.cc
filament/scene_bridge.h
filament/scene_geom_util.cc
filament/scene_geom_util.h
filament/scene_view.cc filament/scene_view.cc
filament/scene_view.h filament/scene_view.h
filament/texture.cc filament/texture.cc
filament/texture.h filament/texture.h
compat/imgui_bridge.cc
compat/imgui_bridge.h
compat/imgui_editor.cc
compat/imgui_editor.h
compat/mjr_filament_renderer.cc
compat/mjr_filament_renderer.h
compat/model_objects.cc
compat/model_objects.h
compat/scene_bridge.cc
compat/scene_bridge.h
compat/scene_geom_util.cc
compat/scene_geom_util.h
) )
if(MUJOCO_USE_FILAMENT_MJR_COMPAT) if(MUJOCO_USE_FILAMENT_MJR_COMPAT)
target_sources(${MUJOCO_FILAMENT_TARGET_NAME} target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
@@ -127,20 +129,32 @@ foreach(MATERIAL_FILE ${MATERIAL_FILES})
set(INPUT_FILE "${ASSETS_DIR}/${MATERIAL_FILE}") set(INPUT_FILE "${ASSETS_DIR}/${MATERIAL_FILE}")
set(OUTPUT_FILE "${OUTPUT_ASSETS_DIR}/${MATERIAL_NAME}.filamat") set(OUTPUT_FILE "${OUTPUT_ASSETS_DIR}/${MATERIAL_NAME}.filamat")
add_custom_command( if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
OUTPUT ${OUTPUT_FILE} set(PRECOMPILED_FILE "${MUJOCO_NATIVE_BUILD_DIR}/src/experimental/filament/assets/${MATERIAL_NAME}.filamat")
COMMAND ${MATC_EXECUTABLE} add_custom_command(
--platform=all OUTPUT ${OUTPUT_FILE}
--api=vulkan COMMAND ${CMAKE_COMMAND} -E copy
--api=opengl ${PRECOMPILED_FILE}
--variant-filter skinning ${OUTPUT_FILE}
--optimize-size DEPENDS ${PRECOMPILED_FILE}
--output ${OUTPUT_FILE} COMMENT "Copying precompiled material ${MATERIAL_NAME}.filamat"
${INPUT_FILE} )
DEPENDS ${INPUT_FILE} else()
DEPENDS matc add_custom_command(
COMMENT "Compiling ${MATERIAL_FILE}" OUTPUT ${OUTPUT_FILE}
) COMMAND ${MATC_EXECUTABLE}
--platform=all
--api=vulkan
--api=opengl
--variant-filter skinning
--optimize-size
--output ${OUTPUT_FILE}
${INPUT_FILE}
DEPENDS ${INPUT_FILE}
DEPENDS matc
COMMENT "Compiling ${MATERIAL_FILE}"
)
endif()
list(APPEND MUJOCO_FILAMENT_ASSET_FILES ${OUTPUT_FILE}) list(APPEND MUJOCO_FILAMENT_ASSET_FILES ${OUTPUT_FILE})
endforeach() endforeach()
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "experimental/filament/filament/imgui_bridge.h" #include "experimental/filament/compat/imgui_bridge.h"
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@@ -23,12 +23,11 @@
#include <imgui.h> #include <imgui.h>
#include <math/mat3.h> #include <math/mat3.h>
#include <math/vec3.h> #include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/filament/material.h" #include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/mesh.h" #include "experimental/filament/filament/mesh.h"
#include "experimental/filament/filament/renderable.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/renderable.h"
#include "experimental/filament/filament/scene_view.h" #include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture.h" #include "experimental/filament/filament/texture.h"
@@ -37,10 +36,25 @@ namespace mujoco {
using filament::math::float3; using filament::math::float3;
using filament::math::mat3f; using filament::math::mat3f;
ImguiBridge::ImguiBridge(ObjectManager* object_mgr, SceneView* scene_view) ImguiBridge::ImguiBridge(ObjectManager* object_mgr) : object_mgr_(object_mgr) {
: object_mgr_(object_mgr), scene_view_(scene_view) {} scene_view_ = std::make_unique<SceneView>(object_mgr_->GetEngine());
scene_view_->DisableShadows();
scene_view_->DisableReflections();
scene_view_->DisablePostProcessing();
}
ImguiBridge::~ImguiBridge() { PrepareRenderables(0); } ImguiBridge::~ImguiBridge() {
PrepareRenderables(0);
// Destroy all textures tracked by ImGui.
if (ImGui::GetCurrentContext()) {
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) {
if (tex->Status != ImTextureStatus_Destroyed) {
DestroyTexture(tex);
}
}
}
}
uintptr_t ImguiBridge::UploadImage(uintptr_t tex_id, const uint8_t* pixels, uintptr_t ImguiBridge::UploadImage(uintptr_t tex_id, const uint8_t* pixels,
int width, int height, int bpp) { int width, int height, int bpp) {
@@ -67,8 +81,8 @@ uintptr_t ImguiBridge::UploadImage(uintptr_t tex_id, const uint8_t* pixels,
// new texture. // new texture.
if (texture == nullptr || texture->GetWidth() != width || if (texture == nullptr || texture->GetWidth() != width ||
texture->GetHeight() != height) { texture->GetHeight() != height) {
TextureConfig config; mjrTextureConfig config;
DefaultTextureConfig(&config); mjr_defaultTextureConfig(&config);
config.width = width; config.width = width;
config.height = height; config.height = height;
config.target = mjTEXTURE_2D; config.target = mjTEXTURE_2D;
@@ -84,8 +98,8 @@ uintptr_t ImguiBridge::UploadImage(uintptr_t tex_id, const uint8_t* pixels,
const auto callback = const auto callback =
+[](void* user) { delete[] reinterpret_cast<std::byte*>(user); }; +[](void* user) { delete[] reinterpret_cast<std::byte*>(user); };
TextureData texture_data; mjrTextureData texture_data;
DefaultTextureData(&texture_data); mjr_defaultTextureData(&texture_data);
texture_data.bytes = bytes; texture_data.bytes = bytes;
texture_data.nbytes = num_bytes; texture_data.nbytes = num_bytes;
texture_data.user_data = bytes; texture_data.user_data = bytes;
@@ -101,8 +115,8 @@ void ImguiBridge::CreateTexture(ImTextureData* data) {
mju_error("Unsupported texture format."); mju_error("Unsupported texture format.");
} }
TextureConfig config; mjrTextureConfig config;
DefaultTextureConfig(&config); mjr_defaultTextureConfig(&config);
config.width = data->Width; config.width = data->Width;
config.height = data->Height; config.height = data->Height;
config.target = mjTEXTURE_2D; config.target = mjTEXTURE_2D;
@@ -122,8 +136,8 @@ void ImguiBridge::UpdateTexture(ImTextureData* data) {
mju_error("Texture not found: %llu", data->TexID); mju_error("Texture not found: %llu", data->TexID);
} }
TextureData texture_data; mjrTextureData texture_data;
DefaultTextureData(&texture_data); mjr_defaultTextureData(&texture_data);
texture_data.bytes = data->GetPixels(); texture_data.bytes = data->GetPixels();
texture_data.nbytes = data->Width * data->Height * 4; texture_data.nbytes = data->Width * data->Height * 4;
texture_data.user_data = nullptr; texture_data.user_data = nullptr;
@@ -179,22 +193,10 @@ void ImguiBridge::Update() {
if (commands->Textures != nullptr) { if (commands->Textures != nullptr) {
for (ImTextureData* tex : *commands->Textures) { for (ImTextureData* tex : *commands->Textures) {
if (tex->Status == ImTextureStatus_OK) { if (tex->Status == ImTextureStatus_WantCreate) {
// ImGui's lifecycle is independent of the filament context lifecycle.
// As such, it is possible to destroy and create a new filament context
// while ImGui is still expecting the "OK" textures to work. In this
// case, we simply recreate the texture.
if (textures_.find(tex->TexID) == textures_.end()) {
CreateTexture(tex);
}
} else if (tex->Status == ImTextureStatus_WantCreate) {
CreateTexture(tex); CreateTexture(tex);
} else if (tex->Status == ImTextureStatus_WantUpdates) { } else if (tex->Status == ImTextureStatus_WantUpdates) {
if (textures_.find(tex->TexID) == textures_.end()) { UpdateTexture(tex);
CreateTexture(tex);
} else {
UpdateTexture(tex);
}
} else if (tex->Status == ImTextureStatus_WantDestroy && } else if (tex->Status == ImTextureStatus_WantDestroy &&
tex->UnusedFrames > 0) { tex->UnusedFrames > 0) {
DestroyTexture(tex); DestroyTexture(tex);
@@ -212,24 +214,24 @@ void ImguiBridge::Update() {
for (int n = 0; n < commands->CmdListsCount; ++n) { for (int n = 0; n < commands->CmdListsCount; ++n) {
const ImDrawList* cmds = commands->CmdLists[n]; const ImDrawList* cmds = commands->CmdLists[n];
MeshData data; mjrMeshData data;
DefaultMeshData(&data); mjr_defaultMeshData(&data);
data.nattributes = 3; data.nattributes = 3;
data.attributes[0].usage = mjVERTEX_ATTRIBUTE_POSITION; data.attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION;
data.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; data.attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2;
data.attributes[0].bytes = cmds->VtxBuffer.Data; data.attributes[0].bytes = cmds->VtxBuffer.Data;
data.attributes[1].usage = mjVERTEX_ATTRIBUTE_UV; data.attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_UV;
data.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; data.attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2;
data.attributes[1].bytes = cmds->VtxBuffer.Data + sizeof(float) * 2; data.attributes[1].bytes = cmds->VtxBuffer.Data + sizeof(float) * 2;
data.attributes[2].usage = mjVERTEX_ATTRIBUTE_COLOR; data.attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_COLOR;
data.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_UBYTE4; data.attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_UBYTE4;
data.attributes[2].bytes = cmds->VtxBuffer.Data + sizeof(float) * 4; data.attributes[2].bytes = cmds->VtxBuffer.Data + sizeof(float) * 4;
data.interleaved = true; data.interleaved = true;
data.nvertices = cmds->VtxBuffer.Size; data.nvertices = cmds->VtxBuffer.Size;
data.nindices = cmds->IdxBuffer.Size; data.nindices = cmds->IdxBuffer.Size;
data.indices = cmds->IdxBuffer.Data; data.indices = cmds->IdxBuffer.Data;
data.index_type = mjINDEX_TYPE_USHORT; data.index_type = mjINDEX_TYPE_U16;
data.primitive_type = mjPRIM_TYPE_TRIANGLES; data.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
meshes_.push_back(std::make_unique<Mesh>(scene_view_->GetEngine(), data)); meshes_.push_back(std::make_unique<Mesh>(scene_view_->GetEngine(), data));
const Mesh* mesh = meshes_.back().get(); const Mesh* mesh = meshes_.back().get();
@@ -242,10 +244,12 @@ void ImguiBridge::Update() {
auto& renderable = renderables_[renderable_index]; auto& renderable = renderables_[renderable_index];
renderable->SetMesh(mesh, index_offset, command.ElemCount); renderable->SetMesh(mesh, index_offset, command.ElemCount);
MaterialTextures textures; mjrMaterialTextures textures;
mjr_defaultMaterialTextures(&textures);
textures.color = textures_[command.GetTexID()].get(); textures.color = textures_[command.GetTexID()].get();
MaterialParams properties; mjrMaterialParams properties;
mjr_defaultMaterialParams(&properties);
properties.scissor[0] = command.ClipRect.x; properties.scissor[0] = command.ClipRect.x;
properties.scissor[1] = height - command.ClipRect.w; properties.scissor[1] = height - command.ClipRect.w;
properties.scissor[2] = command.ClipRect.z - command.ClipRect.x; properties.scissor[2] = command.ClipRect.z - command.ClipRect.x;
@@ -271,18 +275,18 @@ void ImguiBridge::Update() {
void ImguiBridge::PrepareRenderables(int count) { void ImguiBridge::PrepareRenderables(int count) {
while (renderables_.size() < count) { while (renderables_.size() < count) {
RenderableParams config; mjrRenderableParams params;
DefaultRenderableParams(&config); mjr_defaultRenderableParams(&params);
config.shading_model = ShadingModel::Ux; params.shading_model = mjSHADING_MODEL_UX;
auto& r = renderables_.emplace_back( auto& r = renderables_.emplace_back(
std::make_unique<Renderable>(object_mgr_, config)); std::make_unique<Renderable>(object_mgr_, params));
r->SetCastShadows(false); r->SetCastShadows(false);
r->SetReceiveShadows(false); r->SetReceiveShadows(false);
r->SetBlendOrder(static_cast<std::uint16_t>(renderables_.size())); r->SetBlendOrder(static_cast<std::uint16_t>(renderables_.size()));
scene_view_->AddToUxScene(r.get()); scene_view_->AddToScene(r.get());
} }
while (renderables_.size() > count) { while (renderables_.size() > count) {
scene_view_->RemoveFromUxScene(renderables_.back().get()); scene_view_->RemoveFromScene(renderables_.back().get());
renderables_.pop_back(); renderables_.pop_back();
} }
} }
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_BRIDGE_H_ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_BRIDGE_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_BRIDGE_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_BRIDGE_H_
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
@@ -29,10 +29,10 @@
namespace mujoco { namespace mujoco {
// Manages Renderables that will be added a SceneView's UX scene. // Creates and manages a SceneView using data read from ImGui.
class ImguiBridge { class ImguiBridge {
public: public:
ImguiBridge(ObjectManager* object_mgr, SceneView* scene_view); explicit ImguiBridge(ObjectManager* object_mgr);
~ImguiBridge(); ~ImguiBridge();
// Prepares the Renderables using data from the current ImGui state. This // Prepares the Renderables using data from the current ImGui state. This
@@ -40,6 +40,9 @@ class ImguiBridge {
// synced. // synced.
void Update(); void Update();
// Returns the managed UX scene.
SceneView* GetSceneView() const { return scene_view_.get(); }
// Uploads texture to be used with ImGui's Image and ImageButton functions. // Uploads texture to be used with ImGui's Image and ImageButton functions.
uintptr_t UploadImage(uintptr_t tex_id, const uint8_t* pixels, int width, uintptr_t UploadImage(uintptr_t tex_id, const uint8_t* pixels, int width,
int height, int bpp); int height, int bpp);
@@ -57,7 +60,7 @@ class ImguiBridge {
void DestroyTexture(ImTextureData* data); void DestroyTexture(ImTextureData* data);
ObjectManager* object_mgr_ = nullptr; ObjectManager* object_mgr_ = nullptr;
SceneView* scene_view_ = nullptr; std::unique_ptr<SceneView> scene_view_;
std::vector<std::unique_ptr<Renderable>> renderables_; std::vector<std::unique_ptr<Renderable>> renderables_;
std::vector<std::unique_ptr<Mesh>> meshes_; std::vector<std::unique_ptr<Mesh>> meshes_;
std::unordered_map<uintptr_t, std::unique_ptr<Texture>> textures_; std::unordered_map<uintptr_t, std::unique_ptr<Texture>> textures_;
@@ -69,4 +72,4 @@ void DrawTextAt(const char* text, float x, float y, float z);
} // namespace mujoco } // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_BRIDGE_H_ #endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_BRIDGE_H_
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "experimental/filament/filament/imgui_editor.h" #include "experimental/filament/compat/imgui_editor.h"
#include <algorithm> #include <algorithm>
#include <any> #include <any>
@@ -34,8 +34,8 @@
#include <math/mathfwd.h> #include <math/mathfwd.h>
#include <math/scalar.h> #include <math/scalar.h>
#include <math/vec3.h> #include <math/vec3.h>
#include "experimental/filament/compat/scene_bridge.h"
#include "experimental/filament/filament/color_grading_options.h" #include "experimental/filament/filament/color_grading_options.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h" #include "experimental/filament/filament/scene_view.h"
namespace mujoco { namespace mujoco {
@@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_EDITOR_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_EDITOR_H_
#include "experimental/filament/filament/scene_bridge.h" #include "experimental/filament/compat/scene_bridge.h"
namespace mujoco { namespace mujoco {
@@ -24,4 +24,4 @@ void DrawGui(SceneBridge* scene_bridge);
} // namespace mujoco } // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_ #endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_EDITOR_H_
@@ -0,0 +1,211 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "experimental/filament/compat/mjr_filament_renderer.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include <math/vec4.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/compat/imgui_bridge.h"
#include "experimental/filament/compat/imgui_editor.h"
#include "experimental/filament/compat/scene_bridge.h"
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/filament_context.h"
#include "experimental/filament/filament/model_util.h"
#include "experimental/filament/filament/render_target.h"
#include "experimental/filament/filament/texture.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
MjrFilamentRenderer::MjrFilamentRenderer(const mjrFilamentConfig* config)
: FilamentContext(config) {
}
void MjrFilamentRenderer::Init(const mjModel* model) {
scene_bridge_ = std::make_unique<SceneBridge>(GetObjectManager(), model);
imgui_bridge_ = std::make_unique<ImguiBridge>(GetObjectManager());
render_requests_[0].scene = scene_bridge_->GetSceneView();
render_requests_[0].draw_mode = DrawMode::Color;
render_requests_[1].scene = imgui_bridge_->GetSceneView();
render_requests_[1].draw_mode = DrawMode::Color;
// The UX camera is a fixed orthographic camera. We only need to change the
// width/height based on the viewport per frame.
render_requests_[1].camera.orthographic = true;
render_requests_[1].camera.pos[0] = 0.0f;
render_requests_[1].camera.pos[1] = 0.0f;
render_requests_[1].camera.pos[2] = 1.0f;
render_requests_[1].camera.forward[0] = 0.0f;
render_requests_[1].camera.forward[1] = 0.0f;
render_requests_[1].camera.forward[2] = -1.0f;
render_requests_[1].camera.up[0] = 0.0f;
render_requests_[1].camera.up[1] = 1.0f;
render_requests_[1].camera.up[2] = 0.0f;
render_requests_[1].camera.frustum_top = 0.0f;
render_requests_[1].camera.frustum_near = 0.0f;
render_requests_[1].camera.frustum_far = 1.0f;
SetClearColor(ReadElement(model, "filament.clearColor",
filament::math::float4(0, 0, 0, 1)));
}
void MjrFilamentRenderer::Render(const mjrRect& viewport, const mjvScene* scene) {
scene_bridge_->Update(viewport, scene);
// Update the UX renderable entity after processing the scene in case there
// are any elements in the scene which generate UX draw calls (e.g. labels).
if (mode_ != FrameBufferMode::OffScreen) {
imgui_bridge_->Update();
}
if (scene->flags[mjRND_SEGMENT]) {
render_requests_[0].draw_mode = DrawMode::Segmentation;
} else if (scene->flags[mjRND_DEPTH]) {
render_requests_[0].draw_mode = DrawMode::Depth;
} else {
render_requests_[0].draw_mode = DrawMode::Color;
}
render_requests_[0].width = viewport.width;
render_requests_[0].height = viewport.height;
render_requests_[1].width = viewport.width;
render_requests_[1].height = viewport.height;
render_requests_[0].camera = mjv_averageCamera(scene->camera, scene->camera + 1);
render_requests_[1].camera.frustum_center = viewport.width / 2.0f;
render_requests_[1].camera.frustum_width = viewport.width / 2.0f;
render_requests_[1].camera.frustum_bottom = viewport.height;
if (mode_ == FrameBufferMode::Window) {
render_requests_[0].target = nullptr;
render_requests_[1].target = nullptr;
FilamentContext::Render(render_requests_);
}
}
void MjrFilamentRenderer::SetFrameBuffer(int framebuffer) {
switch (framebuffer) {
case mjFB_WINDOW:
mode_ = FrameBufferMode::Window;
break;
case mjFB_OFFSCREEN:
mode_ = FrameBufferMode::OffScreen;
break;
case 2: // No official constant fo this.
mode_ = FrameBufferMode::OffScreenWithGui;
break;
default:
mju_error("Invalid framebuffer mode: %d", framebuffer);
}
}
void MjrFilamentRenderer::ReadPixels(mjrRect viewport, unsigned char* rgb,
float* depth) {
if (mode_ == FrameBufferMode::Window) {
mju_error("ReadPixels is only supported for offscreen rendering.");
}
render_requests_[0].width = viewport.width;
render_requests_[0].height = viewport.height;
render_requests_[1].width = viewport.width;
render_requests_[1].height = viewport.height;
if (rgb) {
mjrRenderTargetConfig config;
mjr_defaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGB8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
auto target = std::make_unique<RenderTarget>(GetEngine(), config);
target->Prepare(viewport.width, viewport.height);
render_requests_[0].target = target.get();
render_requests_[1].target = target.get();
const size_t num_requests =
(mode_ == FrameBufferMode::OffScreenWithGui) ? 2 : 1;
ReadPixelsRequest read_request;
read_request.output = rgb;
read_request.num_bytes = viewport.width * viewport.height * 3;
const FrameHandle frame = FilamentContext::Render(
{&render_requests_[0], num_requests}, {&read_request, 1});
FilamentContext::WaitForFrame(frame);
render_requests_[0].target = nullptr;
render_requests_[1].target = nullptr;
}
if (depth) {
mjrRenderTargetConfig config;
mjr_defaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_R32F;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
auto target = std::make_unique<RenderTarget>(GetEngine(), config);
target->Prepare(viewport.width, viewport.height);
render_requests_[0].target = target.get();
render_requests_[1].target = target.get();
DrawMode last_draw_mode = render_requests_[0].draw_mode;
render_requests_[0].draw_mode = DrawMode::Depth;
ReadPixelsRequest read_request;
read_request.output = reinterpret_cast<uint8_t*>(depth);
read_request.num_bytes = viewport.width * viewport.height * sizeof(float);
const FrameHandle frame =
FilamentContext::Render({&render_requests_[0], 1}, {&read_request, 1});
FilamentContext::WaitForFrame(frame);
render_requests_[0].target = nullptr;
render_requests_[1].target = nullptr;
render_requests_[0].draw_mode = last_draw_mode;
}
}
void MjrFilamentRenderer::UploadMesh(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadMesh(model, id);
}
void MjrFilamentRenderer::UploadTexture(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadTexture(model, id);
}
void MjrFilamentRenderer::UploadHeightField(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadHeightField(model, id);
}
uintptr_t MjrFilamentRenderer::UploadGuiImage(uintptr_t tex_id,
const uint8_t* pixels, int width,
int height, int bpp) {
return imgui_bridge_->UploadImage(tex_id, pixels, width, height, bpp);
}
void MjrFilamentRenderer::UpdateGui() { DrawGui(scene_bridge_.get()); }
} // namespace mujoco
@@ -0,0 +1,86 @@
// Copyright 2025 DeepMind Technologies Limited
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MJR_FILAMENT_RENDERER_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MJR_FILAMENT_RENDERER_H_
#include <cstdint>
#include <memory>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h>
#include "experimental/filament/compat/imgui_bridge.h"
#include "experimental/filament/compat/scene_bridge.h"
#include "experimental/filament/filament/filament_context.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
// Subclass of the FilamentContext that implements the legacy mjr API.
class MjrFilamentRenderer : public FilamentContext {
public:
explicit MjrFilamentRenderer(const mjrFilamentConfig* config);
~MjrFilamentRenderer() = default;
// Initializes the renderer with the given model.
void Init(const mjModel* model);
// Renders the given mjvScene to the viewport.
void Render(const mjrRect& viewport, const mjvScene* scene);
// Configures the renderer to render to the window (0) or an offscreen
// texture (1 or 2). Rendering to the window always includes UX data from
// ImGui. A value of 1 indicates the UX should not be included in the
// offscreen render, whereas 2 indicates that it should.
void SetFrameBuffer(int framebuffer);
// Renders the scene to a texture if the framebuffer is not 0.
void ReadPixels(mjrRect viewport, unsigned char* rgb, float* depth);
// Uploads the mesh data from the model to the GPU.
void UploadMesh(const mjModel* model, int id);
// Uploads the texture data from the model to the GPU.
void UploadTexture(const mjModel* model, int id);
// Uploads the height field data from the model to the GPU.
void UploadHeightField(const mjModel* model, int id);
// Uploads a texture that can be used with ImGui to the GPU.
uintptr_t UploadGuiImage(uintptr_t tex_id, const uint8_t* pixels, int width,
int height, int bpp);
// Renders an ImGui window containing Filament-specific editor UI.
void UpdateGui();
MjrFilamentRenderer(const MjrFilamentRenderer&) = delete;
MjrFilamentRenderer& operator=(const MjrFilamentRenderer&) = delete;
private:
enum class FrameBufferMode {
Window,
OffScreen,
OffScreenWithGui,
};
FrameBufferMode mode_ = FrameBufferMode::Window;
RenderRequest render_requests_[2];
std::unique_ptr<SceneBridge> scene_bridge_;
std::unique_ptr<ImguiBridge> imgui_bridge_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MJR_FILAMENT_RENDERER_H_
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "experimental/filament/filament/model_objects.h" #include "experimental/filament/compat/model_objects.h"
#include <array> #include <array>
#include <algorithm> #include <algorithm>
@@ -412,7 +412,7 @@ static std::span<const int> GetIndices(const mjModel* model,
} }
} }
static void UpdateMeshData(MeshData* data, const mjModel* model, int id, static void UpdatemjrMeshData(mjrMeshData* data, const mjModel* model, int id,
MeshType mesh_type) { MeshType mesh_type) {
if (!IsValidIndex(model, id, mesh_type)) { if (!IsValidIndex(model, id, mesh_type)) {
mju_error("Invalid index %d for type %d", id, mesh_type); mju_error("Invalid index %d for type %d", id, mesh_type);
@@ -440,22 +440,22 @@ static void UpdateMeshData(MeshData* data, const mjModel* model, int id,
break; break;
} }
data->primitive_type = mjPRIM_TYPE_TRIANGLES; data->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
data->nvertices = num_vertices; data->nvertices = num_vertices;
data->nindices = data->nvertices; data->nindices = data->nvertices;
data->indices = nullptr; data->indices = nullptr;
data->index_type = data->nvertices >= std::numeric_limits<uint16_t>::max() data->index_type = data->nvertices >= std::numeric_limits<uint16_t>::max()
? mjINDEX_TYPE_UINT ? mjINDEX_TYPE_U32
: mjINDEX_TYPE_USHORT; : mjINDEX_TYPE_U16;
data->nattributes = has_uvs ? 3 : 2; data->nattributes = has_uvs ? 3 : 2;
data->attributes[0].usage = mjVERTEX_ATTRIBUTE_POSITION; data->attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION;
data->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; data->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3;
data->attributes[0].bytes = builder->positions.data(); data->attributes[0].bytes = builder->positions.data();
data->attributes[1].usage = mjVERTEX_ATTRIBUTE_TANGENTS; data->attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS;
data->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; data->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4;
data->attributes[1].bytes = builder->orientations.data(); data->attributes[1].bytes = builder->orientations.data();
if (has_uvs) { if (has_uvs) {
data->attributes[2].usage = mjVERTEX_ATTRIBUTE_UV; data->attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV;
data->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; data->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2;
data->attributes[2].bytes = builder->uvs.data(); data->attributes[2].bytes = builder->uvs.data();
} }
@@ -467,7 +467,7 @@ static void UpdateMeshData(MeshData* data, const mjModel* model, int id,
data->bounds_max[2] = builder->bounds_max.z; data->bounds_max[2] = builder->bounds_max.z;
} }
void UpdateSkinFlexMeshData(MeshData* data, const mjModel* model, void UpdateSkinFlexmjrMeshData(mjrMeshData* data, const mjModel* model,
const mjvScene* scene, const mjvGeom& geom) { const mjvScene* scene, const mjvGeom& geom) {
auto positions = GetPositions(model, scene, geom); auto positions = GetPositions(model, scene, geom);
auto normals = GetNormals(model, scene, geom); auto normals = GetNormals(model, scene, geom);
@@ -480,20 +480,20 @@ void UpdateSkinFlexMeshData(MeshData* data, const mjModel* model,
} }
data->nattributes = uvs.data() ? 3 : 2; data->nattributes = uvs.data() ? 3 : 2;
data->attributes[0].usage = mjVERTEX_ATTRIBUTE_POSITION; data->attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION;
data->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; data->attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3;
data->attributes[0].bytes = positions.data(); data->attributes[0].bytes = positions.data();
data->attributes[1].usage = mjVERTEX_ATTRIBUTE_NORMAL; data->attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_NORMAL;
data->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; data->attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3;
data->attributes[1].bytes = normals.data(); data->attributes[1].bytes = normals.data();
data->attributes[2].usage = mjVERTEX_ATTRIBUTE_UV; data->attributes[2].usage = mjVERTEX_ATTRIBUTE_USAGE_UV;
data->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2; data->attributes[2].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT2;
data->attributes[2].bytes = uvs.data(); data->attributes[2].bytes = uvs.data();
data->nvertices = positions.size() / 3; data->nvertices = positions.size() / 3;
data->nindices = num_indices; data->nindices = num_indices;
data->indices = indices.data(); data->indices = indices.data();
data->index_type = mjINDEX_TYPE_UINT; data->index_type = mjINDEX_TYPE_U32;
data->primitive_type = mjPRIM_TYPE_TRIANGLES; data->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
data->compute_bounds = true; data->compute_bounds = true;
data->release_callback = nullptr; data->release_callback = nullptr;
data->user_data = nullptr; data->user_data = nullptr;
@@ -554,15 +554,15 @@ void ModelObjects::UploadMesh(const mjModel* model, int id) {
meshes_.erase(id); meshes_.erase(id);
convex_hulls_.erase(id); convex_hulls_.erase(id);
MeshData data; mjrMeshData data;
DefaultMeshData(&data); mjr_defaultMeshData(&data);
UpdateMeshData(&data, model, id, MeshType::kNormal); UpdatemjrMeshData(&data, model, id, MeshType::kNormal);
meshes_[id] = std::make_unique<Mesh>(engine_, data); meshes_[id] = std::make_unique<Mesh>(engine_, data);
if (model->mesh_graphadr[id] >= 0) { if (model->mesh_graphadr[id] >= 0) {
MeshData convex_hull_data; mjrMeshData convex_hull_data;
DefaultMeshData(&convex_hull_data); mjr_defaultMeshData(&convex_hull_data);
UpdateMeshData(&convex_hull_data, model, id, MeshType::kConvexHull); UpdatemjrMeshData(&convex_hull_data, model, id, MeshType::kConvexHull);
convex_hulls_[id] = std::make_unique<Mesh>(engine_, convex_hull_data); convex_hulls_[id] = std::make_unique<Mesh>(engine_, convex_hull_data);
} }
} }
@@ -575,8 +575,8 @@ void ModelObjects::UploadTexture(const mjModel* model, int id) {
mju_error("Invalid texture index: %d", id); mju_error("Invalid texture index: %d", id);
} }
TextureConfig config; mjrTextureConfig config;
DefaultTextureConfig(&config); mjr_defaultTextureConfig(&config);
config.width = model->tex_width[id]; config.width = model->tex_width[id];
config.height = model->tex_height[id]; config.height = model->tex_height[id];
config.target = (mjtTexture)model->tex_type[id]; config.target = (mjtTexture)model->tex_type[id];
@@ -600,8 +600,8 @@ void ModelObjects::UploadTexture(const mjModel* model, int id) {
} }
TextureData payload; mjrTextureData payload;
DefaultTextureData(&payload); mjr_defaultTextureData(&payload);
payload.bytes = model->tex_data + model->tex_adr[id]; payload.bytes = model->tex_data + model->tex_adr[id];
payload.nbytes = payload.nbytes =
model->tex_width[id] * model->tex_height[id] * model->tex_nchannel[id]; model->tex_width[id] * model->tex_height[id] * model->tex_nchannel[id];
@@ -624,16 +624,16 @@ void ModelObjects::UploadHeightField(const mjModel* model, int id) {
height_fields_.erase(id); height_fields_.erase(id);
MeshData data; mjrMeshData data;
DefaultMeshData(&data); mjr_defaultMeshData(&data);
UpdateMeshData(&data, model, id, MeshType::kHeightField); UpdatemjrMeshData(&data, model, id, MeshType::kHeightField);
height_fields_[id] = std::make_unique<Mesh>(engine_, data); height_fields_[id] = std::make_unique<Mesh>(engine_, data);
} }
void ModelObjects::CreateSkinFlexMesh(const mjvScene* scene, const mjvGeom& geom) { void ModelObjects::CreateSkinFlexMesh(const mjvScene* scene, const mjvGeom& geom) {
MeshData data; mjrMeshData data;
DefaultMeshData(&data); mjr_defaultMeshData(&data);
UpdateSkinFlexMeshData(&data, model_, scene, geom); UpdateSkinFlexmjrMeshData(&data, model_, scene, geom);
dynamic_meshes_[geom.objid] = std::make_unique<Mesh>(engine_, data); dynamic_meshes_[geom.objid] = std::make_unique<Mesh>(engine_, data);
} }
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_OBJECTS_H_ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MODEL_OBJECTS_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_OBJECTS_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MODEL_OBJECTS_H_
#include <array> #include <array>
#include <memory> #include <memory>
@@ -100,4 +100,4 @@ class ModelObjects {
} // namespace mujoco } // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_OBJECTS_H_ #endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MODEL_OBJECTS_H_
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "experimental/filament/filament/scene_bridge.h" #include "experimental/filament/compat/scene_bridge.h"
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -36,16 +36,15 @@
#include <math/vec4.h> #include <math/vec4.h>
#include <math/TVecHelpers.h> #include <math/TVecHelpers.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/compat/imgui_bridge.h"
#include "experimental/filament/compat/model_objects.h"
#include "experimental/filament/compat/scene_geom_util.h"
#include "experimental/filament/filament/color_grading_options.h" #include "experimental/filament/filament/color_grading_options.h"
#include "experimental/filament/filament/imgui_bridge.h"
#include "experimental/filament/filament/light.h" #include "experimental/filament/filament/light.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/math_util.h" #include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/model_objects.h"
#include "experimental/filament/filament/model_util.h" #include "experimental/filament/filament/model_util.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/renderable.h" #include "experimental/filament/filament/renderable.h"
#include "experimental/filament/filament/scene_geom_util.h"
#include "experimental/filament/filament/scene_view.h" #include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture.h" #include "experimental/filament/filament/texture.h"
@@ -64,8 +63,8 @@ static std::unique_ptr<Texture> CreateFallbackIndirectLightTexture(
std::unique_ptr<ObjectManager::Asset> asset = object_mgr->LoadAsset(filename); std::unique_ptr<ObjectManager::Asset> asset = object_mgr->LoadAsset(filename);
TextureConfig config; mjrTextureConfig config;
DefaultTextureConfig(&config); mjr_defaultTextureConfig(&config);
config.width = 1; config.width = 1;
config.height = 1; config.height = 1;
config.target = mjTEXTURE_CUBE; config.target = mjTEXTURE_CUBE;
@@ -74,9 +73,9 @@ static std::unique_ptr<Texture> CreateFallbackIndirectLightTexture(
auto texture = std::make_unique<Texture>(object_mgr->GetEngine(), config); auto texture = std::make_unique<Texture>(object_mgr->GetEngine(), config);
TextureData payload; mjrTextureData payload;
DefaultTextureData(&payload); mjr_defaultTextureData(&payload);
payload.bytes = (void*)asset->GetBytes().data(); payload.bytes = asset->GetBytes().data();
payload.nbytes = asset->GetBytes().size(); payload.nbytes = asset->GetBytes().size();
payload.release_callback = +[](void* user_data) { payload.release_callback = +[](void* user_data) {
delete static_cast<ObjectManager::Asset*>(user_data); delete static_cast<ObjectManager::Asset*>(user_data);
@@ -87,9 +86,9 @@ static std::unique_ptr<Texture> CreateFallbackIndirectLightTexture(
return texture; return texture;
} }
SceneBridge::SceneBridge(ObjectManager* object_mgr, SceneView* scene_view, SceneBridge::SceneBridge(ObjectManager* object_mgr, const mjModel* model)
const mjModel* model) : object_mgr_(object_mgr) {
: scene_view_(scene_view), object_mgr_(object_mgr) { scene_view_ = std::make_unique<SceneView>(object_mgr_->GetEngine());
model_objects_ = model_objects_ =
std::make_unique<ModelObjects>(model, object_mgr_->GetEngine()); std::make_unique<ModelObjects>(model, object_mgr_->GetEngine());
@@ -180,7 +179,10 @@ SceneBridge::~SceneBridge() {
scene_view_->RemoveFromScene(iter.get()); scene_view_->RemoveFromScene(iter.get());
} }
lights_.clear(); lights_.clear();
if (fallback_ibl_) {
scene_view_->RemoveFromScene(fallback_ibl_.get());
}
fallback_ibl_.reset();
for (auto& iter : renderables_) { for (auto& iter : renderables_) {
scene_view_->RemoveFromScene(iter.get()); scene_view_->RemoveFromScene(iter.get());
} }
@@ -204,7 +206,8 @@ void SceneBridge::SetEnvironmentLight(std::string_view filename,
fallback_ibl_texture_ = fallback_ibl_texture_ =
CreateFallbackIndirectLightTexture(object_mgr_, filename); CreateFallbackIndirectLightTexture(object_mgr_, filename);
Light::Params params; mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE; params.type = mjLIGHT_IMAGE;
params.texture = fallback_ibl_texture_.get(); params.texture = fallback_ibl_texture_.get();
params.intensity = intensity; params.intensity = intensity;
@@ -230,7 +233,8 @@ void SceneBridge::PrepareLights() {
total_light_intensity += model->light_intensity[i]; total_light_intensity += model->light_intensity[i];
if (model->light_type[i] == mjLIGHT_IMAGE) { if (model->light_type[i] == mjLIGHT_IMAGE) {
Light::Params params; mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE; params.type = mjLIGHT_IMAGE;
params.texture = model_objects_->GetTexture(model->light_texid[i]); params.texture = model_objects_->GetTexture(model->light_texid[i]);
params.intensity = model->light_intensity[i]; params.intensity = model->light_intensity[i];
@@ -239,11 +243,14 @@ void SceneBridge::PrepareLights() {
lights_.emplace_back(std::move(light_obj)); lights_.emplace_back(std::move(light_obj));
has_image_based_light = true; has_image_based_light = true;
} else { } else {
Light::Params params; mjrLightParams params;
params.color = ReadFloat3(model->light_diffuse); mjr_defaultLightParams(&params);
params.color[0] = model->light_diffuse[0];
params.color[1] = model->light_diffuse[1];
params.color[2] = model->light_diffuse[2];
params.type = (mjtLightType)model->light_type[i]; params.type = (mjtLightType)model->light_type[i];
params.castshadow = model->light_castshadow[i]; params.cast_shadows = model->light_castshadow[i];
params.bulbradius = model->light_bulbradius[i]; params.bulb_radius = model->light_bulbradius[i];
params.range = model->light_range[i]; params.range = model->light_range[i];
params.intensity = model->light_intensity[i]; params.intensity = model->light_intensity[i];
params.shadow_map_size = default_shadow_map_size_; params.shadow_map_size = default_shadow_map_size_;
@@ -253,10 +260,7 @@ void SceneBridge::PrepareLights() {
} }
auto light_obj = std::make_unique<Light>(engine, params); auto light_obj = std::make_unique<Light>(engine, params);
#ifndef __EMSCRIPTEN__
// TODO(b/458045799): Re-enable when lights work on glinux and chromebook.
scene_view_->AddToScene(light_obj.get()); scene_view_->AddToScene(light_obj.get());
#endif
lights_.emplace_back(std::move(light_obj)); lights_.emplace_back(std::move(light_obj));
} }
} }
@@ -264,22 +268,19 @@ void SceneBridge::PrepareLights() {
// Add a placeholder (black) headlight as our last light. Going forward, we'll // Add a placeholder (black) headlight as our last light. Going forward, we'll
// assume lights_.back() is always the headlight. // assume lights_.back() is always the headlight.
{ {
Light::Params params; mjrLightParams params;
params.color = float3(0, 0, 0); mjr_defaultLightParams(&params);
// We break with the spec here slightly and use a spot light for the head // We break with the spec here slightly and use a spot light for the head
// light instead of a directional params. This is because filament only // light instead of a directional params. This is because filament only
// supports a single directional light, and we'd rather allow a scene // supports a single directional light, and we'd rather allow a scene
// light to be that directional params. It's also a bit odd for a // light to be that directional params. It's also a bit odd for a
// directional light to move with the camera. // directional light to move with the camera.
params.type = mjLIGHT_SPOT; params.type = mjLIGHT_SPOT;
params.castshadow = 0; params.cast_shadows = 0;
params.intensity = 0.0f; params.intensity = 0.0f;
params.spot_cone_angle = 90.0f; params.spot_cone_angle = 90.0f;
auto light_obj = std::make_unique<Light>(engine, params); auto light_obj = std::make_unique<Light>(engine, params);
#ifndef __EMSCRIPTEN__
// TODO(b/458045799): Re-enable when lights work on glinux and chromebook.
scene_view_->AddToScene(light_obj.get()); scene_view_->AddToScene(light_obj.get());
#endif
lights_.emplace_back(std::move(light_obj)); lights_.emplace_back(std::move(light_obj));
} }
@@ -287,7 +288,8 @@ void SceneBridge::PrepareLights() {
// Create a black indirect light to ensure that the skybox is // Create a black indirect light to ensure that the skybox is
// oriented to respect mujoco's Z-up convention. // oriented to respect mujoco's Z-up convention.
filament::Engine* engine = object_mgr_->GetEngine(); filament::Engine* engine = object_mgr_->GetEngine();
Light::Params params; mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE; params.type = mjLIGHT_IMAGE;
params.intensity = 10.0f; params.intensity = 10.0f;
fallback_ibl_ = std::make_unique<Light>(engine, params); fallback_ibl_ = std::make_unique<Light>(engine, params);
@@ -301,7 +303,8 @@ void SceneBridge::PrepareLights() {
// Create a fallback environment light. // Create a fallback environment light.
fallback_ibl_texture_ = CreateFallbackIndirectLightTexture(object_mgr_); fallback_ibl_texture_ = CreateFallbackIndirectLightTexture(object_mgr_);
Light::Params params; mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE; params.type = mjLIGHT_IMAGE;
params.texture = fallback_ibl_texture_.get(); params.texture = fallback_ibl_texture_.get();
params.intensity = fallback_environment_light_intensity_; params.intensity = fallback_environment_light_intensity_;
@@ -354,8 +357,16 @@ filament::math::mat4 CalculateClipFromWorld(const mjrRect& viewport,
} }
void SceneBridge::Update(const mjrRect& viewport, const mjvScene* scene) { void SceneBridge::Update(const mjrRect& viewport, const mjvScene* scene) {
filament::View* view = scene_view_->GetDefaultRenderView(); if (scene->flags[mjRND_SHADOW]) {
view->setShadowingEnabled(scene->flags[mjRND_SHADOW] ? true : false); scene_view_->EnableShadows();
} else {
scene_view_->DisableShadows();
}
if (scene->flags[mjRND_REFLECTION]) {
scene_view_->EnableReflections();
} else {
scene_view_->DisableReflections();
}
mjtNum hpos[3], hfwd[3]; mjtNum hpos[3], hfwd[3];
float headpos[3], gazedir[3]; float headpos[3], gazedir[3];
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_BRIDGE_H_ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_BRIDGE_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_BRIDGE_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_BRIDGE_H_
#include <memory> #include <memory>
#include <optional> #include <optional>
@@ -24,9 +24,8 @@
#include <math/vec3.h> #include <math/vec3.h>
#include <mujoco/mjvisualize.h> #include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/compat/model_objects.h"
#include "experimental/filament/filament/light.h" #include "experimental/filament/filament/light.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/model_objects.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/renderable.h" #include "experimental/filament/filament/renderable.h"
#include "experimental/filament/filament/scene_view.h" #include "experimental/filament/filament/scene_view.h"
@@ -37,8 +36,7 @@ namespace mujoco {
// Manages all mjModel data and updates a SceneView using an mjvScene. // Manages all mjModel data and updates a SceneView using an mjvScene.
class SceneBridge { class SceneBridge {
public: public:
SceneBridge(ObjectManager* object_mgr, SceneView* scene_view, SceneBridge(ObjectManager* object_mgr, const mjModel* model);
const mjModel* model);
~SceneBridge(); ~SceneBridge();
// Updates the environment light using the KTX image at the given path. // Updates the environment light using the KTX image at the given path.
@@ -56,7 +54,8 @@ class SceneBridge {
void UploadTexture(const mjModel* model, int id); void UploadTexture(const mjModel* model, int id);
void UploadHeightField(const mjModel* model, int id); void UploadHeightField(const mjModel* model, int id);
SceneView* GetSceneView() const { return scene_view_; } // Returns the managed scene.
SceneView* GetSceneView() const { return scene_view_.get(); }
SceneBridge(const SceneBridge&) = delete; SceneBridge(const SceneBridge&) = delete;
SceneBridge& operator=(const SceneBridge&) = delete; SceneBridge& operator=(const SceneBridge&) = delete;
@@ -69,7 +68,7 @@ class SceneBridge {
std::optional<filament::math::float3> ClipFromWorld( std::optional<filament::math::float3> ClipFromWorld(
const filament::math::float3& pos) const; const filament::math::float3& pos) const;
SceneView* scene_view_ = nullptr; std::unique_ptr<SceneView> scene_view_;
ObjectManager* object_mgr_ = nullptr; ObjectManager* object_mgr_ = nullptr;
std::unique_ptr<ModelObjects> model_objects_; std::unique_ptr<ModelObjects> model_objects_;
std::unique_ptr<Light> fallback_ibl_; std::unique_ptr<Light> fallback_ibl_;
@@ -86,4 +85,4 @@ class SceneBridge {
} // namespace mujoco } // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_BRIDGE_H_ #endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_BRIDGE_H_
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#include "experimental/filament/filament/scene_geom_util.h" #include "experimental/filament/compat/scene_geom_util.h"
#include <cmath> #include <cmath>
#include <cstdint> #include <cstdint>
@@ -22,20 +22,17 @@
#include <filament/Material.h> #include <filament/Material.h>
#include <filament/RenderableManager.h> #include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/Texture.h> #include <filament/Texture.h>
#include <filament/TransformManager.h>
#include <math/mat4.h> #include <math/mat4.h>
#include <math/vec2.h> #include <math/vec2.h>
#include <math/vec3.h> #include <math/vec3.h>
#include <math/vec4.h> #include <math/vec4.h>
#include <utils/Entity.h>
#include <mujoco/mjvisualize.h> #include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/compat/model_objects.h"
#include "experimental/filament/filament/material.h" #include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/math_util.h" #include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/mesh.h" #include "experimental/filament/filament/mesh.h"
#include "experimental/filament/filament/model_objects.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/renderable.h" #include "experimental/filament/filament/renderable.h"
#include "experimental/filament/filament/texture.h" #include "experimental/filament/filament/texture.h"
@@ -351,9 +348,12 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
const mjModel* model = model_objs->GetModel(); const mjModel* model = model_objs->GetModel();
const bool use_segid_color = scene->flags[mjRND_IDCOLOR]; const bool use_segid_color = scene->flags[mjRND_IDCOLOR];
const bool enable_reflection = scene->flags[mjRND_REFLECTION]; mjrMaterialParams params;
MaterialParams params; mjr_defaultMaterialParams(&params);
params.color = ReadFloat4(geom.rgba); params.color[0] = geom.rgba[0];
params.color[1] = geom.rgba[1];
params.color[2] = geom.rgba[2];
params.color[3] = geom.rgba[3];
if (geom.type == mjGEOM_PLANE) { if (geom.type == mjGEOM_PLANE) {
if (IsBehind(headpos, geom.pos, geom.mat)) { if (IsBehind(headpos, geom.pos, geom.mat)) {
params.color[3] *= 0.3; params.color[3] *= 0.3;
@@ -361,8 +361,7 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
params.reflective = false; params.reflective = false;
} else { } else {
renderable.SetReceiveShadows(true); renderable.SetReceiveShadows(true);
params.reflective = params.reflective = geom.reflectance > 0 && params.color[3] == 1.0f;
enable_reflection && geom.reflectance > 0 && params.color.a == 1.0f;
} }
} }
renderable.SetLayerMask(geom.category); renderable.SetLayerMask(geom.category);
@@ -373,7 +372,8 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
renderable.SetWireframe(scene->flags[mjRND_WIREFRAME]); renderable.SetWireframe(scene->flags[mjRND_WIREFRAME]);
} }
MaterialTextures textures; mjrMaterialTextures textures;
mjr_defaultMaterialTextures(&textures);
if (geom.matid >= 0) { if (geom.matid >= 0) {
textures.color = model_objs->GetTexture(geom.matid, mjTEXROLE_RGB); textures.color = model_objs->GetTexture(geom.matid, mjTEXROLE_RGB);
textures.normal = model_objs->GetTexture(geom.matid, mjTEXROLE_NORMAL); textures.normal = model_objs->GetTexture(geom.matid, mjTEXROLE_NORMAL);
@@ -394,7 +394,8 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
params.metallic = model->mat_metallic[geom.matid]; params.metallic = model->mat_metallic[geom.matid];
params.roughness = model->mat_roughness[geom.matid]; params.roughness = model->mat_roughness[geom.matid];
params.tex_uniform = model->mat_texuniform[geom.matid]; params.tex_uniform = model->mat_texuniform[geom.matid];
params.tex_repeat = ReadFloat2(model->mat_texrepeat, geom.matid); params.tex_repeat[0] = model->mat_texrepeat[(geom.matid * 2) + 0];
params.tex_repeat[1] = model->mat_texrepeat[(geom.matid * 2) + 1];
} }
if (geom.segid >= 0) { if (geom.segid >= 0) {
@@ -410,9 +411,9 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
const uint8_t red = (segmentation_color >> 0) & 0xff; const uint8_t red = (segmentation_color >> 0) & 0xff;
const uint8_t green = (segmentation_color >> 8) & 0xff; const uint8_t green = (segmentation_color >> 8) & 0xff;
const uint8_t blue = (segmentation_color >> 16) & 0xff; const uint8_t blue = (segmentation_color >> 16) & 0xff;
params.segmentation_color.x = static_cast<float>(red) / 255.0f; params.segmentation_color[0] = static_cast<float>(red) / 255.0f;
params.segmentation_color.y = static_cast<float>(green) / 255.0f; params.segmentation_color[1] = static_cast<float>(green) / 255.0f;
params.segmentation_color.z = static_cast<float>(blue) / 255.0f; params.segmentation_color[2] = static_cast<float>(blue) / 255.0f;
} }
// UvScale only applies to objects that don't have explicit UV coordinates // UvScale only applies to objects that don't have explicit UV coordinates
@@ -428,23 +429,23 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
// For 2D textures, `tex_repeat` specifies how many times the texture // For 2D textures, `tex_repeat` specifies how many times the texture
// image is repeated. The `tex_uniform` flag determines if the repetition // image is repeated. The `tex_uniform` flag determines if the repetition
// is applied at in object space (false) or in world space (true). // is applied at in object space (false) or in world space (true).
params.uv_scale.x = params.tex_repeat.x; params.uv_scale[0] = params.tex_repeat[0];
params.uv_scale.y = params.tex_repeat.y; params.uv_scale[1] = params.tex_repeat[1];
if (geom.dataid >= 0 && geom.type != mjGEOM_PLANE) { if (geom.dataid >= 0 && geom.type != mjGEOM_PLANE) {
if (geom.size[0] > mjMINVAL) { if (geom.size[0] > mjMINVAL) {
params.uv_scale.x /= geom.size[0]; params.uv_scale[0] /= geom.size[0];
} }
if (geom.size[1] > mjMINVAL) { if (geom.size[1] > mjMINVAL) {
params.uv_scale.y /= geom.size[1]; params.uv_scale[1] /= geom.size[1];
} }
} }
if (params.tex_uniform) { if (params.tex_uniform) {
if (geom.size[0] > 0) { if (geom.size[0] > 0) {
params.uv_scale.x *= geom.size[0]; params.uv_scale[0] *= geom.size[0];
} }
if (geom.size[1] > 0) { if (geom.size[1] > 0) {
params.uv_scale.y *= geom.size[1]; params.uv_scale[1] *= geom.size[1];
} }
} }
const bool is_infinite_plane = const bool is_infinite_plane =
@@ -454,11 +455,11 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
// re-centering in engine_vis_visualize.c. // re-centering in engine_vis_visualize.c.
const float plane_scale = static_cast<float>(mjMAXPLANEGRID) / 2.0f; const float plane_scale = static_cast<float>(mjMAXPLANEGRID) / 2.0f;
const float tile_size_x = const float tile_size_x =
GetPlaneTileSize(model, geom.matid, params.tex_repeat.x); GetPlaneTileSize(model, geom.matid, params.tex_repeat[0]);
const float tile_size_y = const float tile_size_y =
GetPlaneTileSize(model, geom.matid, params.tex_repeat.y); GetPlaneTileSize(model, geom.matid, params.tex_repeat[1]);
params.uv_scale.x = 2.0f * plane_scale / tile_size_x; params.uv_scale[0] = 2.0f * plane_scale / tile_size_x;
params.uv_scale.y = 2.0f * plane_scale / tile_size_y; params.uv_scale[1] = 2.0f * plane_scale / tile_size_y;
} }
// We want to do the equivalent of: // We want to do the equivalent of:
@@ -466,17 +467,17 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
// mjr_setf4(tplane, 0, -0.5 * scl.y, 0, -0.5); // mjr_setf4(tplane, 0, -0.5 * scl.y, 0, -0.5);
// glTexGenfv(GL_S, GL_OBJECT_PLANE, splane); // glTexGenfv(GL_S, GL_OBJECT_PLANE, splane);
// glTexGenfv(GL_T, GL_OBJECT_PLANE, tplane); // glTexGenfv(GL_T, GL_OBJECT_PLANE, tplane);
params.uv_scale.x = 0.5f * params.uv_scale.x; params.uv_scale[0] = 0.5f * params.uv_scale[0];
params.uv_scale.y = -0.5f * params.uv_scale.y; params.uv_scale[1] = -0.5f * params.uv_scale[1];
params.uv_offset.x = -0.5f; params.uv_offset[0] = -0.5f;
params.uv_offset.y = -0.5f; params.uv_offset[1] = -0.5f;
} else { } else {
// For cube maps, if `tex_uniform` is true, then scale the texture so that // For cube maps, if `tex_uniform` is true, then scale the texture so that
// it covers a 1x1 area of world space rather than the area of the object. // it covers a 1x1 area of world space rather than the area of the object.
if (params.tex_uniform) { if (params.tex_uniform) {
params.uv_scale.x = 1.0f / (geom.size[0] ? geom.size[0] : 1.0f); params.uv_scale[0] = 1.0f / (geom.size[0] ? geom.size[0] : 1.0f);
params.uv_scale.y = 1.0f / (geom.size[1] ? geom.size[1] : 1.0f); params.uv_scale[1] = 1.0f / (geom.size[1] ? geom.size[1] : 1.0f);
params.uv_scale.z = 1.0f / (geom.size[2] ? geom.size[2] : 1.0f); params.uv_scale[2] = 1.0f / (geom.size[2] ? geom.size[2] : 1.0f);
} }
} }
} }
@@ -492,17 +493,17 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
std::unique_ptr<Renderable> CreateGeomRenderable( std::unique_ptr<Renderable> CreateGeomRenderable(
const mjvGeom& geom, const mjvScene* scene, ObjectManager* object_mgr, const mjvGeom& geom, const mjvScene* scene, ObjectManager* object_mgr,
ModelObjects* model_objs, const float headpos[3]) { ModelObjects* model_objs, const float headpos[3]) {
ShadingModel shading_model = ShadingModel::SceneObject; mjrShadingModel shading_model = mjSHADING_MODEL_SCENE_OBJECT;
if (geom.type == mjGEOM_LINE || geom.type == mjGEOM_LINEBOX) { if (geom.type == mjGEOM_LINE || geom.type == mjGEOM_LINEBOX) {
shading_model = ShadingModel::DecorLines; shading_model = mjSHADING_MODEL_DECOR_LINES;
} else if (geom.category == mjCAT_DECOR) { } else if (geom.category == mjCAT_DECOR) {
shading_model = ShadingModel::Decor; shading_model = mjSHADING_MODEL_DECOR;
} }
RenderableParams config; mjrRenderableParams params;
DefaultRenderableParams(&config); mjr_defaultRenderableParams(&params);
config.shading_model = shading_model; params.shading_model = shading_model;
auto renderable = std::make_unique<Renderable>(object_mgr, config); auto renderable = std::make_unique<Renderable>(object_mgr, params);
PrepareGeomMeshes(*renderable, geom, scene, model_objs); PrepareGeomMeshes(*renderable, geom, scene, model_objs);
UpdateGeomMaterial(*renderable, geom, scene, model_objs, object_mgr, headpos); UpdateGeomMaterial(*renderable, geom, scene, model_objs, object_mgr, headpos);
@@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_GEOM_UTIL_H_ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_GEOM_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_GEOM_UTIL_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_GEOM_UTIL_H_
#include <memory> #include <memory>
#include <mujoco/mjvisualize.h> #include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/material.h" #include "experimental/filament/compat/model_objects.h"
#include "experimental/filament/filament/model_objects.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/renderable.h" #include "experimental/filament/filament/renderable.h"
@@ -32,4 +31,4 @@ std::unique_ptr<Renderable> CreateGeomRenderable(
} // namespace mujoco } // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_GEOM_UTIL_H_ #endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_GEOM_UTIL_H_
+10 -10
View File
@@ -59,16 +59,16 @@ static std::size_t NumIndicesPerSide(int num_quads_per_axis) {
return kNumIndicesPerQuad * num_quads_per_axis * num_quads_per_axis; return kNumIndicesPerQuad * num_quads_per_axis * num_quads_per_axis;
} }
class BuiltinBuilder : MeshData { class BuiltinBuilder : mjrMeshData {
public: public:
BuiltinBuilder() { DefaultMeshData(this); } BuiltinBuilder() { mjr_defaultMeshData(this); }
virtual ~BuiltinBuilder() = default; virtual ~BuiltinBuilder() = default;
template <typename T, typename... Args> template <typename T, typename... Args>
static std::unique_ptr<Mesh> Create(filament::Engine* engine, static std::unique_ptr<Mesh> Create(filament::Engine* engine,
Args&&... args) { Args&&... args) {
auto builder = new T(std::forward<Args>(args)...); auto builder = new T(std::forward<Args>(args)...);
MeshData* mesh_data = builder->PrepareMeshData(); mjrMeshData* mesh_data = builder->PrepareMeshData();
mesh_data->release_callback = +[](void* user_data) { mesh_data->release_callback = +[](void* user_data) {
delete static_cast<BuiltinBuilder*>(user_data); delete static_cast<BuiltinBuilder*>(user_data);
}; };
@@ -76,13 +76,13 @@ class BuiltinBuilder : MeshData {
return std::make_unique<Mesh>(engine, *mesh_data); return std::make_unique<Mesh>(engine, *mesh_data);
} }
MeshData* PrepareMeshData() { mjrMeshData* PrepareMeshData() {
// Update the `MeshData` fields. // Update the `mjrMeshData` fields.
nattributes = 2; nattributes = 2;
attributes[0].usage = mjVERTEX_ATTRIBUTE_POSITION; attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION;
attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3; attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3;
attributes[0].bytes = reinterpret_cast<const void*>(positions_.data()); attributes[0].bytes = reinterpret_cast<const void*>(positions_.data());
attributes[1].usage = mjVERTEX_ATTRIBUTE_TANGENTS; attributes[1].usage = mjVERTEX_ATTRIBUTE_USAGE_TANGENTS;
attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4; attributes[1].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT4;
attributes[1].bytes = reinterpret_cast<const void*>(orientations_.data()); attributes[1].bytes = reinterpret_cast<const void*>(orientations_.data());
nvertices = positions_.size(); nvertices = positions_.size();
@@ -91,9 +91,9 @@ class BuiltinBuilder : MeshData {
nindices = indices_.size(); nindices = indices_.size();
primitive_type = primitive_type =
primitive_type_ == filament::backend::PrimitiveType::TRIANGLES primitive_type_ == filament::backend::PrimitiveType::TRIANGLES
? mjPRIM_TYPE_TRIANGLES ? mjMESH_PRIMITIVE_TYPE_TRIANGLES
: mjPRIM_TYPE_LINES; : mjMESH_PRIMITIVE_TYPE_LINES;
index_type = mjINDEX_TYPE_USHORT; index_type = mjINDEX_TYPE_U16;
bounds_min[0] = bounds_.getMin().x; bounds_min[0] = bounds_.getMin().x;
bounds_min[1] = bounds_.getMin().y; bounds_min[1] = bounds_.getMin().y;
bounds_min[2] = bounds_.getMin().z; bounds_min[2] = bounds_.getMin().z;
@@ -14,13 +14,11 @@
#include "experimental/filament/filament/filament_context.h" #include "experimental/filament/filament/filament_context.h"
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstdlib>
#include <memory> #include <memory>
#include <span>
#include <utility> #include <utility>
#include <backend/DriverEnums.h>
#include <filament/Engine.h> #include <filament/Engine.h>
#include <filament/IndexBuffer.h> #include <filament/IndexBuffer.h>
#include <filament/IndirectLight.h> #include <filament/IndirectLight.h>
@@ -35,19 +33,11 @@
#include <math/vec4.h> #include <math/vec4.h>
#include <utils/FixedCapacityVector.h> #include <utils/FixedCapacityVector.h>
#include <utils/compiler.h> #include <utils/compiler.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/filament_platform_factory.h" #include "experimental/filament/filament/filament_platform_factory.h"
#include "experimental/filament/filament/imgui_bridge.h"
#include "experimental/filament/filament/imgui_editor.h"
#include "experimental/filament/filament/model_util.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/render_target.h" #include "experimental/filament/filament/render_target.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h" #include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture.h"
#include "experimental/filament/render_context_filament.h" #include "experimental/filament/render_context_filament.h"
namespace mujoco { namespace mujoco {
@@ -82,10 +72,6 @@ FilamentContext::FilamentContext(const mjrFilamentConfig* config)
} }
FilamentContext::~FilamentContext() { FilamentContext::~FilamentContext() {
DestroyRenderTargets();
imgui_bridge_.reset();
scene_bridge_.reset();
scene_view_.reset();
object_manager_.reset(); object_manager_.reset();
engine_->destroy(renderer_); engine_->destroy(renderer_);
engine_->destroy(window_swap_chain_); engine_->destroy(window_swap_chain_);
@@ -93,209 +79,116 @@ FilamentContext::~FilamentContext() {
filament::Engine::destroy(engine_); filament::Engine::destroy(engine_);
} }
void FilamentContext::Init(const mjModel* model) { FilamentContext::FrameHandle FilamentContext::Render(
scene_view_ = std::make_unique<SceneView>(engine_); std::span<const RenderRequest> requests,
scene_bridge_ = std::make_unique<SceneBridge>(object_manager_.get(), std::span<const ReadPixelsRequest> read_requests) {
scene_view_.get(), model); if (read_requests.size() > 1) {
imgui_bridge_ = mju_error("Only one read request is supported for now.");
std::make_unique<ImguiBridge>(object_manager_.get(), scene_view_.get()); }
// Set clear options. bool render_began = false;
RenderTarget* current_target = nullptr;
for (const RenderRequest& request : requests) {
if (request.target != current_target && render_began) {
renderer_->endFrame();
render_began = false;
}
current_target = request.target;
if (current_target == nullptr) {
if (!read_requests.empty()) {
mju_error("Cannot read pixels from the window.");
}
if constexpr (UTILS_HAS_THREADING) {
// Wait until previous frame is completed before requesting a new frame.
engine_->flushAndWait();
}
// If the window size has changed, we need to reacquire the swap chain.
if (request.width != window_width_ || request.height != window_height_) {
if (window_width_ != 0 && window_height_ != 0) {
engine_->destroy(window_swap_chain_);
window_swap_chain_ = engine_->createSwapChain(config_.native_window);
}
window_width_ = request.width;
window_height_ = request.height;
}
if (!render_began) {
render_began = renderer_->beginFrame(window_swap_chain_);
}
if (!render_began) {
break;
}
if (render_began) {
SceneView::RenderRequest scene_view_request;
scene_view_request.draw_mode = request.draw_mode;
scene_view_request.viewport = {0, 0, request.width, request.height};
scene_view_request.camera = request.camera;
request.scene->Render(renderer_, scene_view_request);
}
} else {
if (read_requests.empty()) {
mju_error(
"Rendering to a render target without a read request is pointless.");
}
const ReadPixelsRequest& read_request = read_requests[0];
if (read_request.num_bytes == 0) {
mju_error("Output buffer size is zero.");
}
if (!render_began) {
render_began = renderer_->beginFrame(offscreen_swap_chain_);
}
if (!render_began) {
break;
}
if (render_began) {
SceneView::RenderRequest scene_view_request;
scene_view_request.draw_mode = request.draw_mode;
scene_view_request.viewport = {0, 0, request.width, request.height};
scene_view_request.camera = request.camera;
scene_view_request.target = request.target;
request.scene->Render(renderer_, scene_view_request);
request.target->ReadColorPixels(renderer_, read_request.output,
read_request.num_bytes);
}
}
}
if (render_began) {
renderer_->endFrame();
}
if constexpr (!UTILS_HAS_THREADING) {
engine_->execute();
}
if (!read_requests.empty()) {
engine_->flushAndWait();
if (read_requests[0].read_completed_callback) {
read_requests[0].read_completed_callback(read_requests[0].user_data);
}
}
return ++frame_counter_;
}
void FilamentContext::WaitForFrame(FrameHandle frame_handle) {
if (frame_counter_ < frame_handle) {
engine_->flushAndWait();
}
}
void FilamentContext::SetClearColor(const filament::math::float4& color) {
filament::Renderer::ClearOptions opts; filament::Renderer::ClearOptions opts;
opts.clear = true; opts.clear = true;
opts.discard = true; opts.discard = true;
opts.clearColor = ReadElement(model, "filament.clearColor", opts.clearColor = color;
filament::math::float4(0, 0, 0, 1));
renderer_->setClearOptions(opts); renderer_->setClearOptions(opts);
} }
void FilamentContext::Render(const mjrRect& viewport, const mjvScene* scene) {
// If we're rendering to the window, and the window size has changed, we need
// to reacquire the swap chain.
if (scene_swap_chain_target_ == kWindowSwapChain &&
(viewport.width != window_width_ || viewport.height != window_height_)) {
if (window_width_ != 0 && window_height_ != 0) {
if constexpr (UTILS_HAS_THREADING) {
engine_->flushAndWait();
}
engine_->destroy(window_swap_chain_);
window_swap_chain_ = engine_->createSwapChain(config_.native_window);
}
window_width_ = viewport.width;
window_height_ = viewport.height;
}
scene_bridge_->Update(viewport, scene);
// Update the UX renderable entity after processing the scene in case there
// are any elements in the scene which generate UX draw calls (e.g. labels).
if (imgui_bridge_ && gui_swap_chain_target_ == scene_swap_chain_target_) {
// Prepare the filament Renderable that contains the GUI draw commands. We
// must call this function even if we do not plan on rendering the GUI to
// ensure the ImGui state is updated.
imgui_bridge_->Update();
}
last_render_mode_ = DrawMode::Color;
if (scene->flags[mjRND_SEGMENT]) {
last_render_mode_ = DrawMode::Segmentation;
} else if (scene->flags[mjRND_DEPTH]) {
last_render_mode_ = DrawMode::Depth;
}
last_camera_ = mjv_averageCamera(scene->camera, scene->camera + 1);
// Render the frame if we're not rendering to a texture.
if (scene_swap_chain_target_ == kWindowSwapChain) {
if constexpr (UTILS_HAS_THREADING) {
// Wait until previous frame is completed before requesting a new frame.
engine_->flushAndWait();
}
if (renderer_->beginFrame(window_swap_chain_)) {
SceneView::RenderRequest request;
request.draw_mode = last_render_mode_;
request.viewport = viewport;
request.camera = last_camera_;
request.enable_ux = (gui_swap_chain_target_ == kWindowSwapChain);
scene_view_->Render(renderer_, request);
renderer_->endFrame();
}
if constexpr (!UTILS_HAS_THREADING) {
engine_->execute();
}
}
}
void FilamentContext::SetFrameBuffer(int framebuffer) {
switch (framebuffer) {
case mjFB_WINDOW:
scene_swap_chain_target_ = kWindowSwapChain;
gui_swap_chain_target_ = kWindowSwapChain;
break;
case mjFB_OFFSCREEN:
scene_swap_chain_target_ = kOffscreenSwapChain;
gui_swap_chain_target_ = kWindowSwapChain;
break;
case 2: // No official constant fo this.
scene_swap_chain_target_ = kOffscreenSwapChain;
gui_swap_chain_target_ = kOffscreenSwapChain;
break;
default:
mju_error("Invalid framebuffer mode: %d", framebuffer);
}
if (framebuffer == 0) {
DestroyRenderTargets();
}
}
void FilamentContext::PrepareRenderTargets(int width, int height) {
RenderTargetConfig config;
DefaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGB8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
color_target_ = std::make_unique<RenderTarget>(engine_, config);
color_target_->Prepare(width, height);
config.color_format = mjPIXEL_FORMAT_R32F;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
depth_target_ = std::make_unique<RenderTarget>(engine_, config);
depth_target_->Prepare(width, height);
}
void FilamentContext::DestroyRenderTargets() {
depth_target_.reset();
color_target_.reset();
}
void FilamentContext::ReadPixels(mjrRect viewport, unsigned char* rgb,
float* depth) {
if (scene_swap_chain_target_ != kOffscreenSwapChain) {
mju_error("Cannot read pixels unless framebuffer is set.");
}
if (color_target_ == nullptr || depth_target_ == nullptr) {
if (viewport.left != 0) {
mju_error("Reading subpixels not supported.");
}
if (viewport.bottom != 0) {
mju_error("Reading subpixels not supported.");
}
PrepareRenderTargets(viewport.width, viewport.height);
}
if (rgb) {
if (renderer_->beginFrame(offscreen_swap_chain_)) {
SceneView::RenderRequest request;
request.draw_mode = last_render_mode_;
request.viewport = viewport;
request.target = color_target_.get();
request.camera = last_camera_;
request.enable_ux = (gui_swap_chain_target_ == kOffscreenSwapChain);
scene_view_->Render(renderer_, request);
const size_t num_bytes = viewport.width * viewport.height * 3;
color_target_->ReadColorPixels(renderer_, rgb, num_bytes);
renderer_->endFrame();
}
}
if (depth) {
if (renderer_->beginFrame(offscreen_swap_chain_)) {
SceneView::RenderRequest request;
request.draw_mode = DrawMode::Depth;
request.viewport = viewport;
request.target = depth_target_.get();
request.camera = last_camera_;
scene_view_->Render(renderer_, request);
const size_t num_bytes = viewport.width * viewport.height * sizeof(float);
depth_target_->ReadColorPixels(
renderer_, reinterpret_cast<uint8_t*>(depth), num_bytes);
renderer_->endFrame();
}
}
if (rgb || depth) {
if constexpr (UTILS_HAS_THREADING) {
// Wait for rendering to copy back to buffer to complete.
engine_->flushAndWait();
}
}
}
void FilamentContext::UploadMesh(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadMesh(model, id);
}
void FilamentContext::UploadTexture(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadTexture(model, id);
}
void FilamentContext::UploadHeightField(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadHeightField(model, id);
}
uintptr_t FilamentContext::UploadGuiImage(uintptr_t tex_id,
const uint8_t* pixels, int width,
int height, int bpp) {
if (imgui_bridge_) {
return imgui_bridge_->UploadImage(tex_id, pixels, width, height, bpp);
}
return 0;
}
double FilamentContext::GetFrameRate() const { double FilamentContext::GetFrameRate() const {
utils::FixedCapacityVector<filament::Renderer::FrameInfo> frame_info = utils::FixedCapacityVector<filament::Renderer::FrameInfo> frame_info =
renderer_->getFrameInfoHistory(1); renderer_->getFrameInfoHistory(1);
@@ -306,6 +199,4 @@ double FilamentContext::GetFrameRate() const {
return 1.0e9 / static_cast<double>(ns); return 1.0e9 / static_cast<double>(ns);
} }
void FilamentContext::UpdateGui() { DrawGui(scene_bridge_.get()); }
} // namespace mujoco } // namespace mujoco
@@ -15,64 +15,102 @@
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#include <cstddef>
#include <cstdint> #include <cstdint>
#include <span>
#include <memory> #include <memory>
#include <backend/Platform.h> #include <backend/Platform.h>
#include <filament/Engine.h> #include <filament/Engine.h>
#include <filament/Renderer.h> #include <filament/Renderer.h>
#include <mujoco/mjmodel.h> #include <filament/SwapChain.h>
#include <mujoco/mjrender.h> #include <math/vec4.h>
#include <mujoco/mjvisualize.h> #include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/draw_mode.h" #include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/imgui_bridge.h" #include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/render_target.h" #include "experimental/filament/filament/render_target.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/render_context_filament.h" #include "experimental/filament/render_context_filament.h"
namespace mujoco { namespace mujoco {
// Manages the filament renderer that is exposed via the mjr functions. // Manages the filament renderer and provides APIs for rendering scenes.
class FilamentContext { class FilamentContext {
public: public:
explicit FilamentContext(const mjrFilamentConfig* config); explicit FilamentContext(const mjrFilamentConfig* config);
~FilamentContext(); ~FilamentContext();
void Init(const mjModel* model); // Information needed to render a single image of a scene.
struct RenderRequest {
// The scene to render.
SceneView* scene = nullptr;
void Render(const mjrRect& viewport, const mjvScene* scene); // The method (e.g. Color, Depth, Segmentation, etc.) to use for rendering.
DrawMode draw_mode = DrawMode::Color;
void SetFrameBuffer(int framebuffer); // The camera from which to render the scene.
mjvGLCamera camera;
void ReadPixels(mjrRect viewport, unsigned char* rgb, float* depth); // The dimensions of the output image.
int width = 0;
int height = 0;
void UploadMesh(const mjModel* model, int id); // The render target into which to render the image. If nullptr, the image
// will be rendered to the window (as previously configured in
// mjrFilamentConfig::native_window).
RenderTarget* target = nullptr;
};
void UploadTexture(const mjModel* model, int id); // Information needed to read pixels from a render target.
struct ReadPixelsRequest {
RenderTarget* target = nullptr;
void UploadHeightField(const mjModel* model, int id); // The buffer into which the read pixels will be written.
uint8_t* output = nullptr;
uintptr_t UploadGuiImage(uintptr_t tex_id, const uint8_t* pixels, int width, // The number of bytes in the output buffer. This should match the size of
int height, int bpp); // the render target texture.
std::size_t num_bytes = 0;
// Callback when the read pixels operation is complete. This will be called
// during WaitForFrame() or in a subsequent call to Render(). This function
// can optionally be used to free the output buffer if needed.
void (*read_completed_callback)(void* user_data) = nullptr;
// User data to pass to the completion callback.
void* user_data = nullptr;
};
// Rendering is asynchronous by nature. Each render request is assigned a
// unique Handle which can be used to query the status of the request. The
// Handle can also be used to block until the request is completed.
using FrameHandle = std::uint64_t;
// Queues the given render requests for rendering. This function copies the
// necessary data from the requests into the renderer thread and returns
// immediately afterwards. The renderer thread will then perform the actual
// rendering on the GPU. Callers can use WaitForFrame to block until the
// rendering is complete.
FrameHandle Render(std::span<const RenderRequest> render_requests,
std::span<const ReadPixelsRequest> read_requests = {});
// Blocks until the given frame has completed rendering.
void WaitForFrame(FrameHandle frame_handle);
// Sets the clear color for the renderer.
void SetClearColor(const filament::math::float4& color);
// Returns the current frame rate of the renderer.
double GetFrameRate() const; double GetFrameRate() const;
void UpdateGui(); filament::Engine* GetEngine() const { return engine_; }
ObjectManager* GetObjectManager() const { return object_manager_.get(); }
FilamentContext(const FilamentContext&) = delete; FilamentContext(const FilamentContext&) = delete;
FilamentContext& operator=(const FilamentContext&) = delete; FilamentContext& operator=(const FilamentContext&) = delete;
private: private:
enum SwapChainType {
kWindowSwapChain,
kOffscreenSwapChain,
};
void PrepareRenderTargets(int width, int height);
void DestroyRenderTargets();
mjrFilamentConfig config_; mjrFilamentConfig config_;
filament::Engine* engine_ = nullptr; filament::Engine* engine_ = nullptr;
@@ -80,19 +118,10 @@ class FilamentContext {
filament::SwapChain* window_swap_chain_ = nullptr; filament::SwapChain* window_swap_chain_ = nullptr;
filament::SwapChain* offscreen_swap_chain_ = nullptr; filament::SwapChain* offscreen_swap_chain_ = nullptr;
std::unique_ptr<filament::backend::Platform> platform_; std::unique_ptr<filament::backend::Platform> platform_;
DrawMode last_render_mode_ = DrawMode::Color;
mjvGLCamera last_camera_;
SwapChainType scene_swap_chain_target_ = kWindowSwapChain;
SwapChainType gui_swap_chain_target_ = kWindowSwapChain;
std::unique_ptr<RenderTarget> color_target_;
std::unique_ptr<RenderTarget> depth_target_;
std::unique_ptr<ObjectManager> object_manager_; std::unique_ptr<ObjectManager> object_manager_;
std::unique_ptr<SceneView> scene_view_;
std::unique_ptr<SceneBridge> scene_bridge_;
std::unique_ptr<ImguiBridge> imgui_bridge_;
int window_width_ = 0; int window_width_ = 0;
int window_height_ = 0; int window_height_ = 0;
std::uint64_t frame_counter_ = 0;
}; };
} // namespace mujoco } // namespace mujoco
@@ -31,13 +31,13 @@ static filament::Engine::Backend ResolveBackend(int graphics_api) {
#endif #endif
switch (graphics_api) { switch (graphics_api) {
case mjGFX_DEFAULT: case mjGRAPHICS_API_DEFAULT:
// Use the default based on the platform above. // Use the default based on the platform above.
break; break;
case mjGFX_OPENGL: case mjGRAPHICS_API_OPENGL:
backend = filament::Engine::Backend::OPENGL; backend = filament::Engine::Backend::OPENGL;
break; break;
case mjGFX_VULKAN: case mjGRAPHICS_API_VULKAN:
backend = filament::Engine::Backend::VULKAN; backend = filament::Engine::Backend::VULKAN;
break; break;
default: default:
+24 -5
View File
@@ -25,6 +25,7 @@
#include <utils/Entity.h> #include <utils/Entity.h>
#include <utils/EntityManager.h> #include <utils/EntityManager.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/texture.h" #include "experimental/filament/filament/texture.h"
namespace mujoco { namespace mujoco {
@@ -32,7 +33,22 @@ namespace mujoco {
using filament::math::float3; using filament::math::float3;
using filament::math::mat3f; using filament::math::mat3f;
Light::Light(filament::Engine* engine, const Params& params) void mjr_defaultLightParams(mjrLightParams* params) {
params->type = mjLIGHT_POINT;
params->texture = nullptr;
params->color[0] = 0;
params->color[1] = 0;
params->color[2] = 0;
params->intensity = 0.0f;
params->cast_shadows = true;
params->range = 10.0f;
params->spot_cone_angle = 180.f;
params->bulb_radius = 0.0f;
params->shadow_map_size = 2048;
params->vsm_blur_width = 0.0f;
}
Light::Light(filament::Engine* engine, const mjrLightParams& params)
: engine_(engine), params_(params) { : engine_(engine), params_(params) {
// Filament treats image-based lights (IBLs) as separate objects (i.e. // Filament treats image-based lights (IBLs) as separate objects (i.e.
// filament::IndirectLight) and so we need to handle IBLs specially. // filament::IndirectLight) and so we need to handle IBLs specially.
@@ -71,9 +87,9 @@ Light::Light(filament::Engine* engine, const Params& params)
} }
filament::LightManager::Builder builder(type); filament::LightManager::Builder builder(type);
builder.color(params.color); builder.color(ReadFloat3(params.color));
builder.intensityCandela(params.intensity); builder.intensityCandela(params.intensity);
builder.castShadows(params.castshadow); builder.castShadows(params.cast_shadows);
if (type == filament::LightManager::Type::FOCUSED_SPOT) { if (type == filament::LightManager::Type::FOCUSED_SPOT) {
builder.spotLightCone(0, builder.spotLightCone(0,
params.spot_cone_angle * std::numbers::pi / 180.0f); params.spot_cone_angle * std::numbers::pi / 180.0f);
@@ -85,7 +101,7 @@ Light::Light(filament::Engine* engine, const Params& params)
opts.mapSize = 4096; opts.mapSize = 4096;
opts.shadowCascades = opts.shadowCascades =
type == filament::LightManager::Type::DIRECTIONAL ? 4 : 1; type == filament::LightManager::Type::DIRECTIONAL ? 4 : 1;
opts.shadowBulbRadius = params.bulbradius; opts.shadowBulbRadius = params.bulb_radius;
opts.mapSize = params.shadow_map_size; opts.mapSize = params.shadow_map_size;
if (params.vsm_blur_width > 0.0f) { if (params.vsm_blur_width > 0.0f) {
opts.vsm.elvsm = true; opts.vsm.elvsm = true;
@@ -141,7 +157,10 @@ void Light::SetTransform(filament::math::float3 position,
void Light::SetColor(const filament::math::float3& color) { void Light::SetColor(const filament::math::float3& color) {
if (!ibl_) { if (!ibl_) {
params_.color = color; params_.color[0] = color.r;
params_.color[1] = color.g;
params_.color[2] = color.b;
filament::LightManager& lm = engine_->getLightManager(); filament::LightManager& lm = engine_->getLightManager();
const filament::LightManager::Instance li = lm.getInstance(entity_); const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setColor(li, color); lm.setColor(li, color);
+30 -26
View File
@@ -24,34 +24,38 @@
namespace mujoco { namespace mujoco {
typedef mjtLightType mjrLightType;
// Configuration parameters for a light.
struct mjrLightParams {
// The type of light (e.g. spot, point, directional, etc.)
mjrLightType type;
// The texture to use for image lights.
const Texture* texture;
// The color of the light.
float color[3];
// The intensity of the light, in candela.
float intensity;
// Whether or not the light casts shadows.
mjtByte cast_shadows;
// The range/distance in which the light is effective, in meters.
float range;
// The angle of the spot light cone, in degrees.
float spot_cone_angle;
// The radius of the bulb used for soft shadows.
float bulb_radius;
// The size of the shadow map.
int shadow_map_size;
// Blur width for EL VSM.
float vsm_blur_width;
};
void mjr_defaultLightParams(mjrLightParams* params);
// Manages the filament Entities for a single mjvLight. // Manages the filament Entities for a single mjvLight.
class Light { class Light {
public: public:
// Configuration parameters for a light. Light(filament::Engine* engine, const mjrLightParams& params);
struct Params {
// The type of light (e.g. spot, point, directional, etc.)
mjtLightType type;
// The texture to use for image lights.
const Texture* texture = nullptr;
// The color of the light.
filament::math::float3 color = {0, 0, 0};
// The intensity of the light, in candela.
float intensity = 0.0f;
// Whether or not the light casts shadows.
bool castshadow = true;
// The range/distance in which the light is effective, in meters.
float range = 10.0f;
// The angle of the spot light cone, in degrees.
float spot_cone_angle = 180.f;
// The radius of the bulb used for soft shadows.
float bulbradius = 0.0f;
// The size of the shadow map.
int shadow_map_size = 2048;
// Blur width for EL VSM.
float vsm_blur_width = 0.0f;
};
Light(filament::Engine* engine, const Params& params);
~Light() noexcept; ~Light() noexcept;
Light(const Light&) = delete; Light(const Light&) = delete;
@@ -85,7 +89,7 @@ class Light {
filament::IndirectLight* ibl_ = nullptr; filament::IndirectLight* ibl_ = nullptr;
utils::Entity entity_; utils::Entity entity_;
bool enabled_ = true; bool enabled_ = true;
Params params_; mjrLightParams params_;
}; };
} // namespace mujoco } // namespace mujoco
+45 -6
View File
@@ -14,20 +14,59 @@
#include "experimental/filament/filament/material.h" #include "experimental/filament/filament/material.h"
#include <array>
#include <filament/Color.h> #include <filament/Color.h>
#include <filament/Material.h> #include <filament/Material.h>
#include <filament/MaterialInstance.h> #include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h> #include <filament/RenderableManager.h>
#include <filament/TextureSampler.h> #include <filament/TextureSampler.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/texture.h" #include "experimental/filament/filament/texture.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
namespace mujoco { namespace mujoco {
template <int N>
static void setf(float (&arr)[N], const std::array<float, N>& values) {
for (int i = 0; i < N; ++i) {
arr[i] = values[i];
}
}
void mjr_defaultMaterialTextures(mjrMaterialTextures* textures) {
textures->color = nullptr;
textures->normal = nullptr;
textures->metallic = nullptr;
textures->roughness = nullptr;
textures->occlusion = nullptr;
textures->orm = nullptr;
textures->emissive = nullptr;
textures->reflection = nullptr;
}
void mjr_defaultMaterialParams(mjrMaterialParams* params) {
setf(params->color, {1.f, 1.f, 1.f, 1.f});
setf(params->segmentation_color, {1, 1, 1, 1});
setf(params->uv_scale, {1, 1, 1});
setf(params->uv_offset, {0, 0, 0});
setf(params->scissor, {0, 0, 0, 0});
params->emissive = -1.0f;
params->specular = -1.0f;
params->glossiness = -1.0f;
params->metallic = -1.0f;
params->roughness = -1.0f;
params->reflectance = 0.0f;
params->tex_uniform = false;
params->reflective = false;
}
void UpdateMaterialInstance(filament::MaterialInstance* instance, void UpdateMaterialInstance(filament::MaterialInstance* instance,
const MaterialParams& params, const mjrMaterialParams& params,
const MaterialTextures& textures, const mjrMaterialTextures& textures,
ObjectManager* object_mgr) { ObjectManager* object_mgr) {
if (params.scissor[2] != 0 && params.scissor[3] != 0) { if (params.scissor[2] != 0 && params.scissor[3] != 0) {
instance->setScissor(params.scissor[0], params.scissor[1], instance->setScissor(params.scissor[0], params.scissor[1],
@@ -37,11 +76,11 @@ void UpdateMaterialInstance(filament::MaterialInstance* instance,
const filament::Material* material = instance->getMaterial(); const filament::Material* material = instance->getMaterial();
if (material->hasParameter("BaseColorFactor")) { if (material->hasParameter("BaseColorFactor")) {
instance->setParameter("BaseColorFactor", filament::RgbaType::sRGB, instance->setParameter("BaseColorFactor", filament::RgbaType::sRGB,
params.color); ReadFloat4(params.color));
} }
if (material->hasParameter("SegmentationColor")) { if (material->hasParameter("SegmentationColor")) {
instance->setParameter("SegmentationColor", filament::RgbaType::LINEAR, instance->setParameter("SegmentationColor", filament::RgbaType::LINEAR,
params.segmentation_color); ReadFloat4(params.segmentation_color));
} }
if (material->hasParameter("EmissiveFactor")) { if (material->hasParameter("EmissiveFactor")) {
instance->setParameter("EmissiveFactor", params.emissive); instance->setParameter("EmissiveFactor", params.emissive);
@@ -61,10 +100,10 @@ void UpdateMaterialInstance(filament::MaterialInstance* instance,
params.roughness >= 0 ? params.roughness : 1.0f); params.roughness >= 0 ? params.roughness : 1.0f);
} }
if (material->hasParameter("UvScale")) { if (material->hasParameter("UvScale")) {
instance->setParameter("UvScale", params.uv_scale); instance->setParameter("UvScale", ReadFloat3(params.uv_scale));
} }
if (material->hasParameter("UvOffset")) { if (material->hasParameter("UvOffset")) {
instance->setParameter("UvOffset", params.uv_offset); instance->setParameter("UvOffset", ReadFloat3(params.uv_offset));
} }
if (material->hasParameter("Reflectance")) { if (material->hasParameter("Reflectance")) {
instance->setParameter("Reflectance", params.reflectance); instance->setParameter("Reflectance", params.reflectance);
+31 -29
View File
@@ -17,49 +17,51 @@
#include <filament/Engine.h> #include <filament/Engine.h>
#include <filament/MaterialInstance.h> #include <filament/MaterialInstance.h>
#include <math/vec2.h> #include <mujoco/mujoco.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include "experimental/filament/filament/texture.h" #include "experimental/filament/filament/texture.h"
#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/object_manager.h"
namespace mujoco { namespace mujoco {
// The textures that can be assigned to the drawable's material. // The textures that can be assigned to the drawable's material.
struct MaterialTextures { struct mjrMaterialTextures {
const Texture* color = nullptr; const Texture* color;
const Texture* normal = nullptr; const Texture* normal;
const Texture* metallic = nullptr; const Texture* metallic;
const Texture* roughness = nullptr; const Texture* roughness;
const Texture* occlusion = nullptr; const Texture* occlusion;
const Texture* orm = nullptr; const Texture* orm;
const Texture* emissive = nullptr; const Texture* emissive;
const Texture* reflection = nullptr; const Texture* reflection;
}; };
void mjr_defaultMaterialTextures(mjrMaterialTextures* textures);
// The parameters that can be applied to the drawable's material. // The parameters that can be applied to the drawable's material.
struct MaterialParams { struct mjrMaterialParams {
filament::math::float4 color = {1, 1, 1, 1}; float color[4];
filament::math::float4 segmentation_color = {1, 1, 1, 1}; float segmentation_color[4];
filament::math::float2 tex_repeat = {1, 1}; float tex_repeat[2];
filament::math::float3 uv_scale = {1, 1, 1}; float uv_scale[3];
filament::math::float3 uv_offset = {0, 0, 0}; float uv_offset[3];
filament::math::float4 scissor = {0, 0, 0, 0}; float scissor[4];
float specular = -1.0f; float specular;
float glossiness = -1.0f; float glossiness;
float metallic = -1.0f; float metallic;
float roughness = -1.0f; float roughness;
float emissive = -1.0f; float emissive;
float reflectance = 0.0f; float reflectance;
bool tex_uniform = false; mjtByte tex_uniform;
bool reflective = false; mjtByte reflective;
}; };
void mjr_defaultMaterialParams(mjrMaterialParams* params);
// Updates the material instances based on the currently set parameters and // Updates the material instances based on the currently set parameters and
// textures. // textures.
void UpdateMaterialInstance(filament::MaterialInstance* instance, void UpdateMaterialInstance(filament::MaterialInstance* instance,
const MaterialParams& params, const mjrMaterialParams& params,
const MaterialTextures& textures, const mjrMaterialTextures& textures,
ObjectManager* object_mgr); ObjectManager* object_mgr);
} // namespace mujoco } // namespace mujoco
+69 -50
View File
@@ -18,6 +18,8 @@
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
#include <memory>
#include <mutex>
#include <span> #include <span>
#include <utility> #include <utility>
@@ -37,17 +39,17 @@ namespace mujoco {
using filament::math::float3; using filament::math::float3;
using filament::math::float4; using filament::math::float4;
static filament::VertexAttribute GetUsage(const VertexAttribute& attrib) { static filament::VertexAttribute GetUsage(const mjrVertexAttribute& attrib) {
switch (attrib.usage) { switch (attrib.usage) {
case mjVERTEX_ATTRIBUTE_POSITION: case mjVERTEX_ATTRIBUTE_USAGE_POSITION:
return filament::VertexAttribute::POSITION; return filament::VertexAttribute::POSITION;
case mjVERTEX_ATTRIBUTE_NORMAL: case mjVERTEX_ATTRIBUTE_USAGE_NORMAL:
return filament::VertexAttribute::TANGENTS; return filament::VertexAttribute::TANGENTS;
case mjVERTEX_ATTRIBUTE_TANGENTS: case mjVERTEX_ATTRIBUTE_USAGE_TANGENTS:
return filament::VertexAttribute::TANGENTS; return filament::VertexAttribute::TANGENTS;
case mjVERTEX_ATTRIBUTE_UV: case mjVERTEX_ATTRIBUTE_USAGE_UV:
return filament::VertexAttribute::UV0; return filament::VertexAttribute::UV0;
case mjVERTEX_ATTRIBUTE_COLOR: case mjVERTEX_ATTRIBUTE_USAGE_COLOR:
return filament::VertexAttribute::COLOR; return filament::VertexAttribute::COLOR;
default: default:
mju_error("Unsupported vertex attribute usage: %d", attrib.usage); mju_error("Unsupported vertex attribute usage: %d", attrib.usage);
@@ -56,7 +58,7 @@ static filament::VertexAttribute GetUsage(const VertexAttribute& attrib) {
} }
static filament::VertexBuffer::AttributeType GetType( static filament::VertexBuffer::AttributeType GetType(
const VertexAttribute& attrib) { const mjrVertexAttribute& attrib) {
switch (attrib.type) { switch (attrib.type) {
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT2: case mjVERTEX_ATTRIBUTE_TYPE_FLOAT2:
return filament::VertexBuffer::AttributeType::FLOAT2; return filament::VertexBuffer::AttributeType::FLOAT2;
@@ -72,7 +74,7 @@ static filament::VertexBuffer::AttributeType GetType(
} }
} }
int VertexAttributeTypeSize(const VertexAttribute& attrib) { int VertexAttributeTypeSize(const mjrVertexAttribute& attrib) {
switch (attrib.type) { switch (attrib.type) {
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT2: case mjVERTEX_ATTRIBUTE_TYPE_FLOAT2:
return sizeof(float) * 2; return sizeof(float) * 2;
@@ -99,21 +101,21 @@ int FillSequence(std::byte* buffer, std::size_t num_bytes) {
return num; return num;
} }
// Initializes the MeshData to default values. // Initializes the mjrMeshData to default values.
void DefaultMeshData(MeshData* data) { void mjr_defaultMeshData(mjrMeshData* data) {
std::memset(data, 0, sizeof(MeshData)); std::memset(data, 0, sizeof(mjrMeshData));
} }
Mesh::Mesh(filament::Engine* engine, const MeshData& data) Mesh::Mesh(filament::Engine* engine, const mjrMeshData& data)
: engine_(engine) { : engine_(engine), shared_state_(std::make_shared<SharedState>()) {
type_ = data.primitive_type == mjPRIM_TYPE_TRIANGLES type_ = data.primitive_type == mjMESH_PRIMITIVE_TYPE_TRIANGLES
? filament::RenderableManager::PrimitiveType::TRIANGLES ? filament::RenderableManager::PrimitiveType::TRIANGLES
: filament::RenderableManager::PrimitiveType::LINES; : filament::RenderableManager::PrimitiveType::LINES;
// If the user has provided a release callback, then we need to ensure we // If the user has provided a release callback, then we need to ensure we
// call is when filament is done with the mesh data. // call is when filament is done with the mesh data.
if (data.release_callback) { if (data.release_callback) {
release_callbacks_.push_back([=]() { shared_state_->callbacks.push_back([=]() {
data.release_callback(data.user_data); data.release_callback(data.user_data);
}); });
} }
@@ -133,40 +135,50 @@ Mesh::~Mesh() {
} }
} }
void Mesh::BuildVertexBuffer(const MeshData& data) { void Mesh::BuildVertexBuffer(const mjrMeshData& data) {
if (data.nvertices == 0) { if (data.nvertices == 0) {
mju_error("MeshData has no vertices."); mju_error("mjrMeshData has no vertices.");
} }
// The filament BufferDescriptor callback for releasing the memory. We assume // The filament BufferDescriptor callback for releasing the memory.
// that ReleaseResources() can be called multiple times, so we assign this // We pass a heap-allocated shared_ptr to the shared state as the user data.
// callback to each buffer descriptor.
auto callback = +[](void* buffer, size_t size, void* user) { auto callback = +[](void* buffer, size_t size, void* user) {
static_cast<Mesh*>(user)->ReleaseResources(); auto* state_ptr = static_cast<std::shared_ptr<SharedState>*>(user);
auto state = *state_ptr;
delete state_ptr;
std::lock_guard<std::mutex> lock(state->mutex);
if (!state->called) {
for (const auto& cb : state->callbacks) {
cb();
}
state->callbacks.clear();
state->called = true;
}
}; };
// Pointers to specific attributes in the mesh data, used for additional // Pointers to specific attributes in the mesh data, used for additional
// validation and processing. // validation and processing.
const VertexAttribute* positions = nullptr; const mjrVertexAttribute* positions = nullptr;
const VertexAttribute* normals = nullptr; const mjrVertexAttribute* normals = nullptr;
const VertexAttribute* tangents = nullptr; const mjrVertexAttribute* tangents = nullptr;
for (int i = 0; i < data.nattributes; ++i) { for (int i = 0; i < data.nattributes; ++i) {
if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_POSITION) { if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_POSITION) {
positions = &data.attributes[i]; positions = &data.attributes[i];
} else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_NORMAL) { } else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
normals = &data.attributes[i]; normals = &data.attributes[i];
} else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_TANGENTS) { } else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_TANGENTS) {
tangents = &data.attributes[i]; tangents = &data.attributes[i];
} }
} }
if (!positions) { if (!positions) {
mju_error("MeshData has no positions."); mju_error("mjrMeshData has no positions.");
} }
if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_POSITION) { if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) {
mju_error("Positions must be the first attribute."); mju_error("Positions must be the first attribute.");
} }
if (normals && tangents) { if (normals && tangents) {
mju_error("MeshData has both normals and tangents."); mju_error("mjrMeshData has both normals and tangents.");
} }
if (normals && data.interleaved) { if (normals && data.interleaved) {
// We need to build orientations from normals and so we require each // We need to build orientations from normals and so we require each
@@ -195,7 +207,7 @@ void Mesh::BuildVertexBuffer(const MeshData& data) {
// each offset is the sum of the sizes of the preceding attributes. // each offset is the sum of the sizes of the preceding attributes.
int offset = 0; int offset = 0;
for (int i = 0; i < data.nattributes; ++i) { for (int i = 0; i < data.nattributes; ++i) {
const VertexAttribute& attrib = data.attributes[i]; const mjrVertexAttribute& attrib = data.attributes[i];
const filament::VertexAttribute usage = GetUsage(attrib); const filament::VertexAttribute usage = GetUsage(attrib);
filament::VertexBuffer::AttributeType type = GetType(attrib); filament::VertexBuffer::AttributeType type = GetType(attrib);
vb_builder.attribute(usage, 0, type, offset, total_vertex_size); vb_builder.attribute(usage, 0, type, offset, total_vertex_size);
@@ -206,16 +218,17 @@ void Mesh::BuildVertexBuffer(const MeshData& data) {
attributes_[i] = usage; attributes_[i] = usage;
} }
vertex_buffer_ = vb_builder.build(*engine_); vertex_buffer_ = vb_builder.build(*engine_);
vertex_buffer_->setBufferAt(*engine_, 0, {bytes, nbytes, callback, this}); auto* user_data = new std::shared_ptr<SharedState>(shared_state_);
vertex_buffer_->setBufferAt(*engine_, 0, {bytes, nbytes, callback, user_data});
} else { } else {
// For a non-interleaved vertex buffer, we assign a separate buffer to each // For a non-interleaved vertex buffer, we assign a separate buffer to each
// attribute. // attribute.
vb_builder.bufferCount(data.nattributes); vb_builder.bufferCount(data.nattributes);
for (int i = 0; i < data.nattributes; ++i) { for (int i = 0; i < data.nattributes; ++i) {
const VertexAttribute& attrib = data.attributes[i]; const mjrVertexAttribute& attrib = data.attributes[i];
const filament::VertexAttribute usage = GetUsage(attrib); const filament::VertexAttribute usage = GetUsage(attrib);
filament::VertexBuffer::AttributeType type = GetType(attrib); filament::VertexBuffer::AttributeType type = GetType(attrib);
if (attrib.usage == mjVERTEX_ATTRIBUTE_NORMAL) { if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
// We will replace normals with orientations. // We will replace normals with orientations.
type = filament::VertexBuffer::AttributeType::FLOAT4; type = filament::VertexBuffer::AttributeType::FLOAT4;
} }
@@ -230,25 +243,26 @@ void Mesh::BuildVertexBuffer(const MeshData& data) {
// Assign the individual data buffers. // Assign the individual data buffers.
for (int i = 0; i < data.nattributes; ++i) { for (int i = 0; i < data.nattributes; ++i) {
const VertexAttribute& attrib = data.attributes[i]; const mjrVertexAttribute& attrib = data.attributes[i];
const void* bytes = attrib.bytes; const void* bytes = attrib.bytes;
size_t nbytes = data.nvertices * VertexAttributeTypeSize(attrib); size_t nbytes = data.nvertices * VertexAttributeTypeSize(attrib);
if (attrib.usage == mjVERTEX_ATTRIBUTE_NORMAL) { if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
// Replace normals with orientations. // Replace normals with orientations.
nbytes = data.nvertices * sizeof(float4); nbytes = data.nvertices * sizeof(float4);
bytes = BuildOrientationsFromNormals(data.nvertices, attrib); bytes = BuildOrientationsFromNormals(data.nvertices, attrib);
} }
vertex_buffer_->setBufferAt(*engine_, i, {bytes, nbytes, callback, this}); auto* user_data = new std::shared_ptr<SharedState>(shared_state_);
vertex_buffer_->setBufferAt(*engine_, i, {bytes, nbytes, callback, user_data});
} }
} }
} }
void Mesh::BuildIndexBuffer(const MeshData& data) { void Mesh::BuildIndexBuffer(const mjrMeshData& data) {
if (data.nindices == 0) { if (data.nindices == 0) {
return; return;
} }
const int element_size = data.index_type == mjINDEX_TYPE_USHORT const int element_size = data.index_type == mjINDEX_TYPE_U16
? sizeof(uint16_t) ? sizeof(uint16_t)
: sizeof(uint32_t); : sizeof(uint32_t);
const int num_bytes = data.nindices * element_size; const int num_bytes = data.nindices * element_size;
@@ -259,11 +273,11 @@ void Mesh::BuildIndexBuffer(const MeshData& data) {
const void* indices = data.indices; const void* indices = data.indices;
if (indices == nullptr) { if (indices == nullptr) {
std::byte* sequence = new std::byte[num_bytes]; std::byte* sequence = new std::byte[num_bytes];
release_callbacks_.push_back([=]() { shared_state_->callbacks.push_back([=]() {
delete[] sequence; delete[] sequence;
}); });
if (data.index_type == mjINDEX_TYPE_USHORT) { if (data.index_type == mjINDEX_TYPE_U16) {
FillSequence<uint16_t>(sequence, num_bytes); FillSequence<uint16_t>(sequence, num_bytes);
} else { } else {
FillSequence<uint32_t>(sequence, num_bytes); FillSequence<uint32_t>(sequence, num_bytes);
@@ -273,7 +287,7 @@ void Mesh::BuildIndexBuffer(const MeshData& data) {
filament::IndexBuffer::Builder ib_builder; filament::IndexBuffer::Builder ib_builder;
ib_builder.indexCount(data.nindices); ib_builder.indexCount(data.nindices);
ib_builder.bufferType(data.index_type == mjINDEX_TYPE_USHORT ib_builder.bufferType(data.index_type == mjINDEX_TYPE_U16
? filament::IndexBuffer::IndexType::USHORT ? filament::IndexBuffer::IndexType::USHORT
: filament::IndexBuffer::IndexType::UINT); : filament::IndexBuffer::IndexType::UINT);
index_buffer_ = ib_builder.build(*engine_); index_buffer_ = ib_builder.build(*engine_);
@@ -283,9 +297,10 @@ void Mesh::BuildIndexBuffer(const MeshData& data) {
index_buffer_->setBuffer(*engine_, std::move(desc)); index_buffer_->setBuffer(*engine_, std::move(desc));
} }
float4* Mesh::BuildOrientationsFromNormals(int nvertices, const VertexAttribute& normals) { float4* Mesh::BuildOrientationsFromNormals(int nvertices,
const mjrVertexAttribute& normals) {
float4* orientations = new float4[nvertices]; float4* orientations = new float4[nvertices];
release_callbacks_.push_back([=]() { shared_state_->callbacks.push_back([=]() {
delete[] orientations; delete[] orientations;
}); });
const float* normals_ptr = reinterpret_cast<const float*>(normals.bytes); const float* normals_ptr = reinterpret_cast<const float*>(normals.bytes);
@@ -295,7 +310,7 @@ float4* Mesh::BuildOrientationsFromNormals(int nvertices, const VertexAttribute&
return orientations; return orientations;
} }
void Mesh::UpdateBounds(const MeshData& data) { void Mesh::UpdateBounds(const mjrMeshData& data) {
float3 bounds_min = ReadFloat3(data.bounds_min); float3 bounds_min = ReadFloat3(data.bounds_min);
float3 bounds_max = ReadFloat3(data.bounds_max); float3 bounds_max = ReadFloat3(data.bounds_max);
if (bounds_min != bounds_max) { if (bounds_min != bounds_max) {
@@ -304,8 +319,8 @@ void Mesh::UpdateBounds(const MeshData& data) {
bounds_min = float3(FLT_MAX, FLT_MAX, FLT_MAX); bounds_min = float3(FLT_MAX, FLT_MAX, FLT_MAX);
bounds_max = float3(-FLT_MAX, -FLT_MAX, -FLT_MAX); bounds_max = float3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_POSITION) { if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) {
mju_error("MeshData has no positions."); mju_error("mjrMeshData has no positions.");
} }
const float* positions = const float* positions =
reinterpret_cast<const float*>(data.attributes[0].bytes); reinterpret_cast<const float*>(data.attributes[0].bytes);
@@ -320,10 +335,14 @@ void Mesh::UpdateBounds(const MeshData& data) {
} }
void Mesh::ReleaseResources() { void Mesh::ReleaseResources() {
for (const auto& callback : release_callbacks_) { std::lock_guard<std::mutex> lock(shared_state_->mutex);
callback(); if (!shared_state_->called) {
for (const auto& callback : shared_state_->callbacks) {
callback();
}
shared_state_->callbacks.clear();
shared_state_->called = true;
} }
release_callbacks_.clear();
} }
filament::IndexBuffer* Mesh::GetFilamentIndexBuffer() const { filament::IndexBuffer* Mesh::GetFilamentIndexBuffer() const {
+46 -39
View File
@@ -19,6 +19,7 @@
#include <cstddef> #include <cstddef>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <mutex>
#include <optional> #include <optional>
#include <span> #include <span>
#include <vector> #include <vector>
@@ -29,66 +30,67 @@
#include <filament/RenderableManager.h> #include <filament/RenderableManager.h>
#include <filament/VertexBuffer.h> #include <filament/VertexBuffer.h>
#include <math/vec4.h> #include <math/vec4.h>
#include <mujoco/mujoco.h>
// Functions for creating filament vertex and index buffers. // Functions for creating filament vertex and index buffers.
namespace mujoco { namespace mujoco {
// Maximum number of vertex attributes that can be used by a mesh.
static constexpr int kMaxVertexAttributes = 16;
// The type of data stored in an index buffer. // The type of data stored in an index buffer.
typedef enum mjtIndexType_ { typedef enum mjrIndexType_ {
mjINDEX_TYPE_USHORT = 0, mjINDEX_TYPE_U16 = 0,
mjINDEX_TYPE_UINT = 1, mjINDEX_TYPE_U32 = 1,
} mjtIndexType; } mjrIndexType;
// The type of primitive to be drawn by vertex data. // The type of primitive to be drawn by vertex data.
typedef enum mjtMeshPrimitiveType_ { typedef enum mjrMeshPrimitiveType_ {
mjPRIM_TYPE_TRIANGLES = 0, mjMESH_PRIMITIVE_TYPE_TRIANGLES = 0,
mjPRIM_TYPE_LINES = 1, mjMESH_PRIMITIVE_TYPE_LINES = 1,
} mjtMeshPrimitiveType; } mjrMeshPrimitiveType;
// The usage/purpose of an attribute of a vertex. // The usage/purpose of an attribute of a vertex.
typedef enum mjtVertexAttributeUsage_ { typedef enum mjrVertexAttributeUsage_ {
mjVERTEX_ATTRIBUTE_POSITION = 0, mjVERTEX_ATTRIBUTE_USAGE_POSITION = 0,
mjVERTEX_ATTRIBUTE_NORMAL = 1, mjVERTEX_ATTRIBUTE_USAGE_NORMAL = 1,
mjVERTEX_ATTRIBUTE_TANGENTS = 2, mjVERTEX_ATTRIBUTE_USAGE_TANGENTS = 2,
mjVERTEX_ATTRIBUTE_UV = 3, mjVERTEX_ATTRIBUTE_USAGE_UV = 3,
mjVERTEX_ATTRIBUTE_COLOR = 4, mjVERTEX_ATTRIBUTE_USAGE_COLOR = 4,
} mjtVertexAttributeUsage; } mjrVertexAttributeUsage;
// The data format of an attribute of a vertex. // The data format of an attribute of a vertex.
typedef enum mjtVertexAttributeType_ { typedef enum mjrVertexAttributeType_ {
mjVERTEX_ATTRIBUTE_TYPE_FLOAT2 = 0, mjVERTEX_ATTRIBUTE_TYPE_FLOAT2 = 0,
mjVERTEX_ATTRIBUTE_TYPE_FLOAT3 = 1, mjVERTEX_ATTRIBUTE_TYPE_FLOAT3 = 1,
mjVERTEX_ATTRIBUTE_TYPE_FLOAT4 = 2, mjVERTEX_ATTRIBUTE_TYPE_FLOAT4 = 2,
mjVERTEX_ATTRIBUTE_TYPE_UBYTE4 = 3, mjVERTEX_ATTRIBUTE_TYPE_UBYTE4 = 3,
} mjtVertexAttributeType; } mjrVertexAttributeType;
// Maximum number of vertex attributes that can be used by a mesh.
enum { mjMAX_VERTEX_ATTRIBUTES = 16 };
// Information about a single attribute of a vertex. // Information about a single attribute of a vertex.
struct VertexAttribute { struct mjrVertexAttribute {
// The data for the attribute. // The data for the attribute.
const void* bytes; const void* bytes;
// The usage/purpose of the attribute. // The usage/purpose of the attribute.
mjtVertexAttributeUsage usage; mjrVertexAttributeUsage usage;
// The data format of the attribute. // The data format of the attribute.
mjtVertexAttributeType type; mjrVertexAttributeType type;
}; };
// The binary contents of a mesh. // The binary contents of a mesh.
struct MeshData { struct mjrMeshData {
// The number of vertices in the mesh. Each of the vertex arrays below is // The number of vertices in the mesh. Each of the vertex arrays below is
// assumed to have this number of elements. // assumed to have this number of elements.
size_t nvertices; mjtSize nvertices;
// The number of attributes for each vertex in the mesh. // The number of attributes for each vertex in the mesh.
int nattributes; int nattributes;
// Information about each attribute of a vertex in the mesh. See `interleaved` // Information about each attribute of a vertex in the mesh. See `interleaved`
// for more details. // for more details.
VertexAttribute attributes[kMaxVertexAttributes]; mjrVertexAttribute attributes[mjMAX_VERTEX_ATTRIBUTES];
// Whether the vertex attributes are interleaved or not. // Whether the vertex attributes are interleaved or not.
// //
@@ -99,24 +101,24 @@ struct MeshData {
// //
// If false, assume each attribute is stored in a separate array as defined // If false, assume each attribute is stored in a separate array as defined
// by the `data` field of the attribute. // by the `data` field of the attribute.
bool interleaved; mjtByte interleaved;
// The number of indices in the mesh. The indices array is assumed to have // The number of indices in the mesh. The indices array is assumed to have
// this number of elements. // this number of elements.
size_t nindices; mjtSize nindices;
// The indices of the mesh, stored as either ushort or uint depending on the // The indices of the mesh, stored as either ushort or uint depending on the
// index type. // index type.
const void* indices; const void* indices;
// The type of data stored in the indices array. // The type of data stored in the indices array.
mjtIndexType index_type; mjrIndexType index_type;
// The type of primitive to be drawn by vertex data. // The type of primitive to be drawn by vertex data.
mjtMeshPrimitiveType primitive_type; mjrMeshPrimitiveType primitive_type;
// Whether to compute the bounds of the mesh using the vertex positions. // Whether to compute the bounds of the mesh using the vertex positions.
bool compute_bounds; mjtByte compute_bounds;
// The bounds of the mesh. If bounds_min == bounds_max, then we assume that // The bounds of the mesh. If bounds_min == bounds_max, then we assume that
// that the bounds are not set (i.e. the bounds is empty). // that the bounds are not set (i.e. the bounds is empty).
@@ -133,13 +135,13 @@ struct MeshData {
}; };
// Initializes the MeshData to default values. // Initializes the MeshData to default values.
void DefaultMeshData(MeshData* data); void mjr_defaultMeshData(mjrMeshData* data);
// Owns a Vertex and Index buffer representing a geometry mesh. // Owns a Vertex and Index buffer representing a geometry mesh.
class Mesh { class Mesh {
public: public:
// Creates a Mesh from the given MeshData. // Creates a Mesh from the given MeshData.
Mesh(filament::Engine* engine, const MeshData& data); Mesh(filament::Engine* engine, const mjrMeshData& data);
~Mesh(); ~Mesh();
@@ -165,12 +167,12 @@ class Mesh {
Mesh& operator=(const Mesh&) = delete; Mesh& operator=(const Mesh&) = delete;
private: private:
void BuildVertexBuffer(const MeshData& data); void BuildVertexBuffer(const mjrMeshData& data);
void BuildIndexBuffer(const MeshData& data); void BuildIndexBuffer(const mjrMeshData& data);
void UpdateBounds(const MeshData& data); void UpdateBounds(const mjrMeshData& data);
filament::math::float4* BuildOrientationsFromNormals( filament::math::float4* BuildOrientationsFromNormals(
int nvertices, const VertexAttribute& normals); int nvertices, const mjrVertexAttribute& normals);
void ReleaseResources(); void ReleaseResources();
@@ -180,8 +182,13 @@ class Mesh {
filament::RenderableManager::PrimitiveType type_ = filament::RenderableManager::PrimitiveType type_ =
filament::RenderableManager::PrimitiveType::TRIANGLES; filament::RenderableManager::PrimitiveType::TRIANGLES;
std::optional<filament::Box> bounds_; std::optional<filament::Box> bounds_;
std::vector<std::function<void()>> release_callbacks_; struct SharedState {
std::array<filament::VertexAttribute, kMaxVertexAttributes> attributes_; std::vector<std::function<void()>> callbacks;
std::mutex mutex;
bool called = false;
};
std::shared_ptr<SharedState> shared_state_;
std::array<filament::VertexAttribute, mjMAX_VERTEX_ATTRIBUTES> attributes_;
int num_attributes_ = 0; int num_attributes_ = 0;
}; };
@@ -30,13 +30,13 @@
namespace mujoco { namespace mujoco {
void DefaultRenderTargetConfig(RenderTargetConfig* config) { void mjr_defaultRenderTargetConfig(mjrRenderTargetConfig* config) {
config->color_format = mjPIXEL_FORMAT_RGBA8; config->color_format = mjPIXEL_FORMAT_RGBA8;
config->depth_format = mjPIXEL_FORMAT_DEPTH32F; config->depth_format = mjPIXEL_FORMAT_DEPTH32F;
} }
RenderTarget::RenderTarget(filament::Engine* engine, RenderTarget::RenderTarget(filament::Engine* engine,
const RenderTargetConfig& config) const mjrRenderTargetConfig& config)
: engine_(engine), config_(config) {} : engine_(engine), config_(config) {}
RenderTarget::~RenderTarget() noexcept { RenderTarget::~RenderTarget() noexcept {
@@ -51,8 +51,8 @@ void RenderTarget::Prepare(int width, int height) {
width_ = width; width_ = width;
height_ = height; height_ = height;
TextureConfig color_config; mjrTextureConfig color_config;
DefaultTextureConfig(&color_config); mjr_defaultTextureConfig(&color_config);
Texture::InternalFlags color_flags; Texture::InternalFlags color_flags;
color_config.width = width; color_config.width = width;
color_config.height = height; color_config.height = height;
@@ -63,8 +63,8 @@ void RenderTarget::Prepare(int width, int height) {
color_flags.color_attachment = true; color_flags.color_attachment = true;
color_texture_ = std::make_unique<Texture>(engine_, color_config, color_flags); color_texture_ = std::make_unique<Texture>(engine_, color_config, color_flags);
TextureConfig depth_config; mjrTextureConfig depth_config;
DefaultTextureConfig(&depth_config); mjr_defaultTextureConfig(&depth_config);
Texture::InternalFlags depth_flags; Texture::InternalFlags depth_flags;
depth_config.width = width; depth_config.width = width;
depth_config.height = height; depth_config.height = height;
@@ -26,20 +26,20 @@
namespace mujoco { namespace mujoco {
// Defines the basic properties of a render target. // Defines the basic properties of a render target.
struct RenderTargetConfig { struct mjrRenderTargetConfig {
mjtPixelFormat color_format; mjrPixelFormat color_format;
mjtPixelFormat depth_format; mjrPixelFormat depth_format;
}; };
// Initializes the RenderTargetConfig to default values. // Initializes the RenderTargetConfig to default values.
void DefaultRenderTargetConfig(RenderTargetConfig* config); void mjr_defaultRenderTargetConfig(mjrRenderTargetConfig* config);
// Manages a filament RenderTarget and the textures which are bound to it. // Manages a filament RenderTarget and the textures which are bound to it.
class RenderTarget { class RenderTarget {
public: public:
// Defines the types of textures to create for the color and depth // Defines the types of textures to create for the color and depth
// attachments. // attachments.
RenderTarget(filament::Engine* engine, const RenderTargetConfig& config); RenderTarget(filament::Engine* engine, const mjrRenderTargetConfig& config);
~RenderTarget() noexcept; ~RenderTarget() noexcept;
RenderTarget(const RenderTarget&) = delete; RenderTarget(const RenderTarget&) = delete;
@@ -66,7 +66,7 @@ class RenderTarget {
void Destroy(); void Destroy();
filament::Engine* engine_ = nullptr; filament::Engine* engine_ = nullptr;
RenderTargetConfig config_; mjrRenderTargetConfig config_;
filament::RenderTarget* render_target_ = nullptr; filament::RenderTarget* render_target_ = nullptr;
std::unique_ptr<Texture> color_texture_ = nullptr; std::unique_ptr<Texture> color_texture_ = nullptr;
std::unique_ptr<Texture> depth_texture_ = nullptr; std::unique_ptr<Texture> depth_texture_ = nullptr;
@@ -36,12 +36,15 @@ namespace mujoco {
using filament::math::mat4f; using filament::math::mat4f;
void DefaultRenderableParams(RenderableParams* params) { void mjr_defaultRenderableParams(mjrRenderableParams* params) {
params->shading_model = ShadingModel::SceneObject; params->shading_model = mjSHADING_MODEL_SCENE_OBJECT;
} }
Renderable::Renderable(ObjectManager* object_mgr, const RenderableParams& params) Renderable::Renderable(ObjectManager* object_mgr, const mjrRenderableParams& params)
: object_mgr_(object_mgr), params_(params) {} : object_mgr_(object_mgr), params_(params) {
mjr_defaultMaterialParams(&material_params_);
mjr_defaultMaterialTextures(&material_textures_);
}
Renderable::~Renderable() noexcept { Renderable::~Renderable() noexcept {
filament::Engine* engine = GetEngine(); filament::Engine* engine = GetEngine();
@@ -198,13 +201,13 @@ void Renderable::RemoveFromScene(filament::Scene* scene) {
assigned_scene_ = nullptr; assigned_scene_ = nullptr;
} }
void Renderable::UpdateMaterial(const MaterialParams& params, void Renderable::UpdateMaterial(const mjrMaterialParams& params,
const MaterialTextures& textures) { const mjrMaterialTextures& textures) {
material_params_ = params; material_params_ = params;
material_textures_ = textures; material_textures_ = textures;
AssignMaterial(DrawMode::Color, GetColorMaterialType()); AssignMaterial(DrawMode::Color, GetColorMaterialType());
if (params_.shading_model == ShadingModel::SceneObject) { if (params_.shading_model == mjSHADING_MODEL_SCENE_OBJECT) {
AssignMaterial(DrawMode::Depth, ObjectManager::kUnlitDepth); AssignMaterial(DrawMode::Depth, ObjectManager::kUnlitDepth);
AssignMaterial(DrawMode::Segmentation, ObjectManager::kUnlitSegmentation); AssignMaterial(DrawMode::Segmentation, ObjectManager::kUnlitSegmentation);
} }
@@ -237,17 +240,17 @@ void Renderable::AssignMaterial(DrawMode mode,
} }
} }
const MaterialParams& Renderable::GetMaterialParams() const { const mjrMaterialParams& Renderable::GetMaterialParams() const {
return material_params_; return material_params_;
} }
const MaterialTextures& Renderable::GetMaterialTextures() const { const mjrMaterialTextures& Renderable::GetMaterialTextures() const {
return material_textures_; return material_textures_;
} }
void Renderable::SetDrawMode(DrawMode mode) { void Renderable::SetDrawMode(DrawMode mode) {
// Only SceneObjects support non-color draw modes. // Only SceneObjects support non-color draw modes.
if (params_.shading_model != ShadingModel::SceneObject) { if (params_.shading_model != mjSHADING_MODEL_SCENE_OBJECT) {
mode = DrawMode::Color; mode = DrawMode::Color;
} }
@@ -343,11 +346,11 @@ void Renderable::SetWireframe(bool wireframe) {
} }
ObjectManager::MaterialType Renderable::GetColorMaterialType() const { ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
if (params_.shading_model == ShadingModel::DecorLines) { if (params_.shading_model == mjSHADING_MODEL_DECOR_LINES) {
return ObjectManager::kUnlitLine; return ObjectManager::kUnlitLine;
} else if (params_.shading_model == ShadingModel::Decor) { } else if (params_.shading_model == mjSHADING_MODEL_DECOR) {
return ObjectManager::kUnlitDecor; return ObjectManager::kUnlitDecor;
} else if (params_.shading_model == ShadingModel::Ux) { } else if (params_.shading_model == mjSHADING_MODEL_UX) {
return ObjectManager::kUnlitUi; return ObjectManager::kUnlitUi;
} else if (material_textures_.orm) { } else if (material_textures_.orm) {
return ObjectManager::kPbrPacked; return ObjectManager::kPbrPacked;
@@ -374,7 +377,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
} }
if (material_textures_.color == nullptr) { if (material_textures_.color == nullptr) {
if (material_params_.color.a < 1.0f) { if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhongColorFade; return ObjectManager::kPhongColorFade;
} else if (material_params_.reflective) { } else if (material_params_.reflective) {
return ObjectManager::kPhongColorReflect; return ObjectManager::kPhongColorReflect;
@@ -383,7 +386,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
} }
} else if (material_textures_.color->GetFilamentTexture()->getTarget() == } else if (material_textures_.color->GetFilamentTexture()->getTarget() ==
filament::Texture::Sampler::SAMPLER_CUBEMAP) { filament::Texture::Sampler::SAMPLER_CUBEMAP) {
if (material_params_.color.a < 1.0f) { if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhongCubeFade; return ObjectManager::kPhongCubeFade;
} else if (material_params_.reflective) { } else if (material_params_.reflective) {
return ObjectManager::kPhongCubeReflect; return ObjectManager::kPhongCubeReflect;
@@ -391,7 +394,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
return ObjectManager::kPhongCube; return ObjectManager::kPhongCube;
} }
} else if (has_texcoords) { } else if (has_texcoords) {
if (material_params_.color.a < 1.0f) { if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhong2dUvFade; return ObjectManager::kPhong2dUvFade;
} else if (material_params_.reflective) { } else if (material_params_.reflective) {
return ObjectManager::kPhong2dUvReflect; return ObjectManager::kPhong2dUvReflect;
@@ -399,7 +402,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
return ObjectManager::kPhong2dUv; return ObjectManager::kPhong2dUv;
} }
} else { } else {
if (material_params_.color.a < 1.0f) { if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhong2dFade; return ObjectManager::kPhong2dFade;
} else if (material_params_.reflective) { } else if (material_params_.reflective) {
return ObjectManager::kPhong2dReflect; return ObjectManager::kPhong2dReflect;
+17 -17
View File
@@ -33,19 +33,19 @@
namespace mujoco { namespace mujoco {
// The shading model (material) for a Renderable. // The shading model (material) for a Renderable.
enum class ShadingModel { typedef enum mjrShadingModel_ {
SceneObject, mjSHADING_MODEL_SCENE_OBJECT,
Decor, mjSHADING_MODEL_DECOR,
DecorLines, mjSHADING_MODEL_DECOR_LINES,
Ux, mjSHADING_MODEL_UX,
}; } mjrShadingModel;
// Configuration parameters for a Renderable. // Configuration parameters for a Renderable.
struct RenderableParams { struct mjrRenderableParams {
ShadingModel shading_model; mjrShadingModel shading_model;
}; };
void DefaultRenderableParams(RenderableParams* params); void mjr_defaultRenderableParams(mjrRenderableParams* params);
// A Renderable is effectively two things: a mesh and a material. // A Renderable is effectively two things: a mesh and a material.
// //
@@ -67,7 +67,7 @@ class Renderable {
static constexpr std::uint8_t kDefaultPriority = 4; static constexpr std::uint8_t kDefaultPriority = 4;
static constexpr std::uint8_t kDefaultLayerMask = 0x01; static constexpr std::uint8_t kDefaultLayerMask = 0x01;
Renderable(ObjectManager* object_mgr, const RenderableParams& params); Renderable(ObjectManager* object_mgr, const mjrRenderableParams& params);
~Renderable() noexcept; ~Renderable() noexcept;
Renderable(const Renderable&) = delete; Renderable(const Renderable&) = delete;
@@ -127,14 +127,14 @@ class Renderable {
void SetDrawMode(DrawMode mode); void SetDrawMode(DrawMode mode);
// Updates the parameters for the material. // Updates the parameters for the material.
void UpdateMaterial(const MaterialParams& params, void UpdateMaterial(const mjrMaterialParams& params,
const MaterialTextures& textures); const mjrMaterialTextures& textures);
// Returns the current material parameters. // Returns the current material parameters.
const MaterialParams& GetMaterialParams() const; const mjrMaterialParams& GetMaterialParams() const;
// Returns the current material textures. // Returns the current material textures.
const MaterialTextures& GetMaterialTextures() const; const mjrMaterialTextures& GetMaterialTextures() const;
// Returns the filament Engine managing the renderables. // Returns the filament Engine managing the renderables.
filament::Engine* GetEngine(); filament::Engine* GetEngine();
@@ -154,10 +154,10 @@ class Renderable {
ObjectManager::MaterialType GetColorMaterialType() const; ObjectManager::MaterialType GetColorMaterialType() const;
ObjectManager* object_mgr_; ObjectManager* object_mgr_;
RenderableParams params_; mjrRenderableParams params_;
filament::MaterialInstance* instances_[kNumDrawModes] = {nullptr}; filament::MaterialInstance* instances_[kNumDrawModes] = {nullptr};
MaterialParams material_params_; mjrMaterialParams material_params_;
MaterialTextures material_textures_; mjrMaterialTextures material_textures_;
DrawMode draw_mode_ = DrawMode::Color; DrawMode draw_mode_ = DrawMode::Color;
filament::Scene* assigned_scene_ = nullptr; filament::Scene* assigned_scene_ = nullptr;
std::vector<Part> parts_; std::vector<Part> parts_;
@@ -127,9 +127,7 @@ static void SetupReflectionCamera(const mat4& surface_xform,
SceneView::SceneView(filament::Engine* engine) : engine_(engine) { SceneView::SceneView(filament::Engine* engine) : engine_(engine) {
scene_ = engine->createScene(); scene_ = engine->createScene();
ux_scene_ = engine->createScene();
camera_ = engine->createCamera(utils::EntityManager::get().create()); camera_ = engine->createCamera(utils::EntityManager::get().create());
ux_camera_ = engine->createCamera(utils::EntityManager::get().create());
reflect_camera_ = engine->createCamera(utils::EntityManager::get().create()); reflect_camera_ = engine->createCamera(utils::EntityManager::get().create());
for (auto& view : views_) { for (auto& view : views_) {
@@ -139,12 +137,6 @@ SceneView::SceneView(filament::Engine* engine) : engine_(engine) {
view->setVisibleLayers(0xff, mjCAT_ALL); view->setVisibleLayers(0xff, mjCAT_ALL);
} }
ux_view_ = engine->createView();
ux_view_->setScene(ux_scene_);
ux_view_->setCamera(ux_camera_);
ux_view_->setPostProcessingEnabled(false);
ux_view_->setShadowingEnabled(false);
reflect_view_ = engine->createView(); reflect_view_ = engine->createView();
reflect_view_->setScene(scene_); reflect_view_->setScene(scene_);
reflect_view_->setCamera(reflect_camera_); reflect_view_->setCamera(reflect_camera_);
@@ -172,22 +164,16 @@ SceneView::~SceneView() {
for (auto& renderable : renderables_) { for (auto& renderable : renderables_) {
renderable->RemoveFromScene(scene_); renderable->RemoveFromScene(scene_);
} }
for (auto& renderable : ux_renderables_) {
renderable->RemoveFromScene(ux_scene_);
}
lights_.clear(); lights_.clear();
renderables_.clear(); renderables_.clear();
reflect_targets_.clear(); reflect_targets_.clear();
engine_->destroyCameraComponent(reflect_camera_->getEntity()); engine_->destroyCameraComponent(reflect_camera_->getEntity());
engine_->destroy(reflect_view_); engine_->destroy(reflect_view_);
engine_->destroyCameraComponent(ux_camera_->getEntity());
engine_->destroy(ux_view_);
engine_->destroyCameraComponent(camera_->getEntity()); engine_->destroyCameraComponent(camera_->getEntity());
if (color_grading_) { if (color_grading_) {
engine_->destroy(color_grading_); engine_->destroy(color_grading_);
} }
engine_->destroy(scene_); engine_->destroy(scene_);
engine_->destroy(ux_scene_);
for (auto& view : views_) { for (auto& view : views_) {
engine_->destroy(view); engine_->destroy(view);
} }
@@ -224,18 +210,6 @@ void SceneView::RemoveFromScene(Renderable* renderable) {
} }
} }
void SceneView::AddToUxScene(Renderable* renderable) {
if (ux_renderables_.insert(renderable).second) {
renderable->AddToScene(ux_scene_);
}
}
void SceneView::RemoveFromUxScene(Renderable* renderable) {
if (ux_renderables_.erase(renderable)) {
renderable->RemoveFromScene(ux_scene_);
}
}
void SceneView::AddToScene(filament::Skybox* skybox) { void SceneView::AddToScene(filament::Skybox* skybox) {
skybox_ = skybox; skybox_ = skybox;
scene_->setSkybox(skybox); scene_->setSkybox(skybox);
@@ -255,7 +229,6 @@ void SceneView::Render(filament::Renderer* renderer,
for (auto& view : views_) { for (auto& view : views_) {
view->setViewport(viewport); view->setViewport(viewport);
} }
ux_view_->setViewport(viewport);
reflect_view_->setViewport(viewport); reflect_view_->setViewport(viewport);
SetupCamera(request.camera, viewport, camera_); SetupCamera(request.camera, viewport, camera_);
@@ -276,7 +249,7 @@ void SceneView::Render(filament::Renderer* renderer,
} }
// Render reflection passes. // Render reflection passes.
if (request.draw_mode == DrawMode::Color) { if (request.draw_mode == DrawMode::Color && reflections_enabled_) {
for (size_t i = 0; i < reflectives_.size(); ++i) { for (size_t i = 0; i < reflectives_.size(); ++i) {
Renderable* renderable = reflectives_[i]; Renderable* renderable = reflectives_[i];
@@ -301,15 +274,6 @@ void SceneView::Render(filament::Renderer* renderer,
renderer->render(view); renderer->render(view);
view->setRenderTarget(nullptr); view->setRenderTarget(nullptr);
if (request.enable_ux) {
ux_camera_->setProjection(filament::Camera::Projection::ORTHO, 0.0f,
viewport.width, viewport.height, 0.0f, 0.0f,
1.0f);
ux_view_->setRenderTarget(render_target);
renderer->render(ux_view_);
ux_view_->setRenderTarget(nullptr);
}
if (request.target) { if (request.target) {
view->setMultiSampleAntiAliasingOptions(options); view->setMultiSampleAntiAliasingOptions(options);
} }
@@ -322,8 +286,8 @@ void SceneView::AddReflectiveRenderable(Renderable* renderable) {
// Ensure we have the same number of render targets as we do reflective // Ensure we have the same number of render targets as we do reflective
// renderables. // renderables.
while (reflect_targets_.size() < reflectives_.size()) { while (reflect_targets_.size() < reflectives_.size()) {
RenderTargetConfig config; mjrRenderTargetConfig config;
DefaultRenderTargetConfig(&config); mjr_defaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGBA8; config.color_format = mjPIXEL_FORMAT_RGBA8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F; config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
@@ -335,9 +299,11 @@ void SceneView::AddReflectiveRenderable(Renderable* renderable) {
auto& target = reflect_targets_[index]; auto& target = reflect_targets_[index];
target->Prepare(viewport.width, viewport.height); target->Prepare(viewport.width, viewport.height);
MaterialTextures textures = renderable->GetMaterialTextures(); if (reflections_enabled_) {
textures.reflection = target->GetColorTexture(); mjrMaterialTextures textures = renderable->GetMaterialTextures();
renderable->UpdateMaterial(renderable->GetMaterialParams(), textures); textures.reflection = target->GetColorTexture();
renderable->UpdateMaterial(renderable->GetMaterialParams(), textures);
}
} }
void SceneView::SetColorGradingOptions(const ColorGradingOptions& opts) { void SceneView::SetColorGradingOptions(const ColorGradingOptions& opts) {
@@ -353,6 +319,43 @@ void SceneView::SetColorGradingOptions(const ColorGradingOptions& opts) {
color_grading_options_ = opts; color_grading_options_ = opts;
} }
void SceneView::EnableShadows() {
views_[kNormalIndex]->setShadowingEnabled(true);
}
void SceneView::DisableShadows() {
views_[kNormalIndex]->setShadowingEnabled(false);
}
void SceneView::EnableReflections() {
reflections_enabled_ = true;
for (int i = 0; i < reflectives_.size(); ++i) {
Renderable* renderable = reflectives_[i];
mjrMaterialTextures textures = renderable->GetMaterialTextures();
textures.reflection = reflect_targets_[i]->GetColorTexture();
renderable->UpdateMaterial(renderable->GetMaterialParams(), textures);
}
}
void SceneView::DisableReflections() {
reflections_enabled_ = false;
for (Renderable* renderable : reflectives_) {
mjrMaterialTextures textures = renderable->GetMaterialTextures();
textures.reflection = nullptr;
renderable->UpdateMaterial(renderable->GetMaterialParams(), textures);
}
}
void SceneView::EnablePostProcessing() {
views_[kNormalIndex]->setPostProcessingEnabled(true);
}
void SceneView::DisablePostProcessing() {
views_[kNormalIndex]->setPostProcessingEnabled(false);
}
filament::View* SceneView::GetDefaultRenderView() { filament::View* SceneView::GetDefaultRenderView() {
return views_[kNormalIndex]; return views_[kNormalIndex];
} }
+14 -14
View File
@@ -38,8 +38,7 @@ namespace mujoco {
// //
// The filament Scene is populated with the objects (e.g. lights, renderables, // The filament Scene is populated with the objects (e.g. lights, renderables,
// skybox, etc.). It manages multiple views to support a variety of draw modes // skybox, etc.). It manages multiple views to support a variety of draw modes
// (e.g. normal, depth, segmentation, etc.) as well as reflective surfaces. It // (e.g. normal, depth, segmentation, etc.) as well as reflective surfaces.
// also manages a separate scene and view for UX rendering.
class SceneView { class SceneView {
public: public:
SceneView(filament::Engine* engine); SceneView(filament::Engine* engine);
@@ -53,10 +52,6 @@ class SceneView {
void AddToScene(filament::Skybox* skybox); void AddToScene(filament::Skybox* skybox);
void RemoveFromScene(filament::Skybox* skybox); void RemoveFromScene(filament::Skybox* skybox);
// Adds/removes entities from the UX scene, which is rendered separately.
void AddToUxScene(Renderable* renderable);
void RemoveFromUxScene(Renderable* renderable);
// Parameters for rendering the scene. // Parameters for rendering the scene.
struct RenderRequest { struct RenderRequest {
// The draw mode (e.g. normal, depth, segmentation) to render. // The draw mode (e.g. normal, depth, segmentation) to render.
@@ -67,8 +62,6 @@ class SceneView {
mjvGLCamera camera; mjvGLCamera camera;
// An optional render target into which the scene will be rendered. // An optional render target into which the scene will be rendered.
RenderTarget* target = nullptr; RenderTarget* target = nullptr;
// Whether or not to render the UX as a separate pass.
bool enable_ux = false;
}; };
// Renders the scene. // Renders the scene.
@@ -77,6 +70,18 @@ class SceneView {
// Returns the filament Engine managing the scene. // Returns the filament Engine managing the scene.
filament::Engine* GetEngine() const { return engine_; } filament::Engine* GetEngine() const { return engine_; }
// Enables/disables shadows for the default render view.
void EnableShadows();
void DisableShadows();
// Enables/disables reflections for the default render view.
void EnableReflections();
void DisableReflections();
// Enables/disables post processing for the default render view.
void EnablePostProcessing();
void DisablePostProcessing();
// Returns the underlying filament View that is used for normal rendering. // Returns the underlying filament View that is used for normal rendering.
// Callers can update rendering settings (e.g. post processing) directly. // Callers can update rendering settings (e.g. post processing) directly.
filament::View* GetDefaultRenderView(); filament::View* GetDefaultRenderView();
@@ -95,7 +100,6 @@ class SceneView {
filament::Engine* engine_ = nullptr; filament::Engine* engine_ = nullptr;
filament::Scene* scene_ = nullptr; filament::Scene* scene_ = nullptr;
filament::Scene* ux_scene_ = nullptr;
filament::Camera* camera_ = nullptr; filament::Camera* camera_ = nullptr;
filament::ColorGrading* color_grading_ = nullptr; filament::ColorGrading* color_grading_ = nullptr;
ColorGradingOptions color_grading_options_; ColorGradingOptions color_grading_options_;
@@ -106,16 +110,12 @@ class SceneView {
std::unordered_set<Renderable*> renderables_; std::unordered_set<Renderable*> renderables_;
filament::Skybox* skybox_ = nullptr; filament::Skybox* skybox_ = nullptr;
// Custom view for UX.
filament::View* ux_view_ = nullptr;
filament::Camera* ux_camera_ = nullptr;
std::unordered_set<Renderable*> ux_renderables_;
// Custom view and camera for reflective surfaces. // Custom view and camera for reflective surfaces.
filament::View* reflect_view_ = nullptr; filament::View* reflect_view_ = nullptr;
filament::Camera* reflect_camera_ = nullptr; filament::Camera* reflect_camera_ = nullptr;
// The list of reflective renderables and their corresponding render targets. // The list of reflective renderables and their corresponding render targets.
bool reflections_enabled_ = true;
std::vector<Renderable*> reflectives_; std::vector<Renderable*> reflectives_;
std::vector<std::unique_ptr<RenderTarget>> reflect_targets_; std::vector<std::unique_ptr<RenderTarget>> reflect_targets_;
}; };
+12 -12
View File
@@ -29,15 +29,15 @@ namespace mujoco {
static constexpr int kNumFacesPerCube = 6; static constexpr int kNumFacesPerCube = 6;
static bool IsCompressed(const TextureConfig& config) { static bool IsCompressed(const mjrTextureConfig& config) {
return config.format == mjPIXEL_FORMAT_KTX; return config.format == mjPIXEL_FORMAT_KTX;
} }
static bool IsCubeMap(const TextureConfig& config) { static bool IsCubeMap(const mjrTextureConfig& config) {
return config.target == mjTEXTURE_CUBE || config.target == mjTEXTURE_SKYBOX; return config.target == mjTEXTURE_CUBE || config.target == mjTEXTURE_SKYBOX;
} }
static int GetFaceHeight(const TextureConfig& config) { static int GetFaceHeight(const mjrTextureConfig& config) {
int face_height = config.height; int face_height = config.height;
if (config.width != config.height) { if (config.width != config.height) {
if (config.width * kNumFacesPerCube != config.height) { if (config.width * kNumFacesPerCube != config.height) {
@@ -51,7 +51,7 @@ static int GetFaceHeight(const TextureConfig& config) {
return face_height; return face_height;
} }
static int GetNumChannels(const TextureConfig& config) { static int GetNumChannels(const mjrTextureConfig& config) {
switch (config.format) { switch (config.format) {
case mjPIXEL_FORMAT_R8: case mjPIXEL_FORMAT_R8:
return 1; return 1;
@@ -65,7 +65,7 @@ static int GetNumChannels(const TextureConfig& config) {
} }
} }
static filament::Texture::Format GetTextureFormat(const TextureConfig& config) { static filament::Texture::Format GetTextureFormat(const mjrTextureConfig& config) {
switch (config.format) { switch (config.format) {
case mjPIXEL_FORMAT_R8: case mjPIXEL_FORMAT_R8:
return filament::Texture::Format::R; return filament::Texture::Format::R;
@@ -80,7 +80,7 @@ static filament::Texture::Format GetTextureFormat(const TextureConfig& config) {
} }
static filament::Texture::InternalFormat GetTextureInternalFormat( static filament::Texture::InternalFormat GetTextureInternalFormat(
const TextureConfig& config) { const mjrTextureConfig& config) {
if (config.color_space == mjCOLORSPACE_SRGB) { if (config.color_space == mjCOLORSPACE_SRGB) {
switch (config.format) { switch (config.format) {
case mjPIXEL_FORMAT_RGB8: case mjPIXEL_FORMAT_RGB8:
@@ -110,15 +110,15 @@ static filament::Texture::InternalFormat GetTextureInternalFormat(
} }
} }
void DefaultTextureData(TextureData* data) { void mjr_defaultTextureData(mjrTextureData* data) {
std::memset(data, 0, sizeof(TextureData)); std::memset(data, 0, sizeof(mjrTextureData));
} }
void DefaultTextureConfig(TextureConfig* config) { void mjr_defaultTextureConfig(mjrTextureConfig* config) {
std::memset(config, 0, sizeof(TextureConfig)); std::memset(config, 0, sizeof(mjrTextureConfig));
} }
Texture::Texture(filament::Engine* engine, const TextureConfig& config, Texture::Texture(filament::Engine* engine, const mjrTextureConfig& config,
InternalFlags flags) InternalFlags flags)
: engine_(engine), config_(config) { : engine_(engine), config_(config) {
if (IsCompressed(config_)) { if (IsCompressed(config_)) {
@@ -166,7 +166,7 @@ Texture::~Texture() {
} }
} }
void Texture::Upload(const TextureData& data) { void Texture::Upload(const mjrTextureData& data) {
user_data_ = data.user_data; user_data_ = data.user_data;
release_callback_ = data.release_callback; release_callback_ = data.release_callback;
+18 -14
View File
@@ -21,12 +21,13 @@
#include <filament/Texture.h> #include <filament/Texture.h>
#include <math/vec3.h> #include <math/vec3.h>
#include <mujoco/mjmodel.h> #include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
// Functions for creating filament textures. // Functions for creating filament textures.
namespace mujoco { namespace mujoco {
// Pixel formats for textures. // Pixel formats for textures.
typedef enum mjtPixelFormat_ { typedef enum mjrPixelFormat_ {
mjPIXEL_FORMAT_UNKNOWN = 0, mjPIXEL_FORMAT_UNKNOWN = 0,
mjPIXEL_FORMAT_R8, mjPIXEL_FORMAT_R8,
mjPIXEL_FORMAT_RGB8, mjPIXEL_FORMAT_RGB8,
@@ -34,15 +35,18 @@ typedef enum mjtPixelFormat_ {
mjPIXEL_FORMAT_R32F, mjPIXEL_FORMAT_R32F,
mjPIXEL_FORMAT_DEPTH32F, mjPIXEL_FORMAT_DEPTH32F,
mjPIXEL_FORMAT_KTX, mjPIXEL_FORMAT_KTX,
} mjtPixelFormat; } mjrPixelFormat;
typedef mjtTexture mjrTextureTarget;
typedef mjtColorSpace mjrColorSpace;
// The binary contents of a texture. // The binary contents of a texture.
struct TextureData { struct mjrTextureData {
// Pointer to the image data. If null, an empty texture will be created. // Pointer to the image data. If null, an empty texture will be created.
void* bytes; const void* bytes;
// The number of bytes in the image data. // The number of bytes in the image data.
size_t nbytes; mjtSize nbytes;
// Because rendering may be multithreaded, we cannot make assumptions about // Because rendering may be multithreaded, we cannot make assumptions about
// when the image data will finish uploading to the GPU. As such, we will use // when the image data will finish uploading to the GPU. As such, we will use
@@ -54,10 +58,10 @@ struct TextureData {
}; };
// Initializes the TextureData to default values. // Initializes the TextureData to default values.
void DefaultTextureData(TextureData* data); void mjr_defaultTextureData(mjrTextureData* data);
// Defines the basic properties of a texture. // Defines the basic properties of a texture.
struct TextureConfig { struct mjrTextureConfig {
// The width of the texture. For compressed textures (e.g. KTX), this is the // The width of the texture. For compressed textures (e.g. KTX), this is the
// number of bytes in the compressed data. // number of bytes in the compressed data.
int width; int width;
@@ -67,17 +71,17 @@ struct TextureConfig {
int height; int height;
// The target of the texture (e.g. 2D, cube, etc.) // The target of the texture (e.g. 2D, cube, etc.)
mjtTexture target; mjrTextureTarget target;
// The format of the pixels in the texture (e.g. RGB8, RGBA8, KTX, etc.) // The format of the pixels in the texture (e.g. RGB8, RGBA8, KTX, etc.)
mjtPixelFormat format; mjrPixelFormat format;
// The color space of the texture (e.g. LINEAR, sRGB, etc.) // The color space of the texture (e.g. LINEAR, sRGB, etc.)
mjtColorSpace color_space; mjrColorSpace color_space;
}; };
// Initializes the TextureConfig to default values. // Initializes the TextureConfig to default values.
void DefaultTextureConfig(TextureConfig* config); void mjr_defaultTextureConfig(mjrTextureConfig* config);
// Wrapper around a filament::Texture. // Wrapper around a filament::Texture.
class Texture { class Texture {
@@ -90,13 +94,13 @@ class Texture {
}; };
// Creates a texture with the given data. // Creates a texture with the given data.
Texture(filament::Engine* engine, const TextureConfig& config, Texture(filament::Engine* engine, const mjrTextureConfig& config,
InternalFlags flags = InternalFlags()); InternalFlags flags = InternalFlags());
~Texture(); ~Texture();
// Uploads the given data to the texture. // Uploads the given data to the texture.
void Upload(const TextureData& data); void Upload(const mjrTextureData& data);
// Returns the width of the texture. // Returns the width of the texture.
int GetWidth() const { return config_.width; } int GetWidth() const { return config_.width; }
@@ -121,7 +125,7 @@ class Texture {
filament::Engine* engine_ = nullptr; filament::Engine* engine_ = nullptr;
filament::Texture* texture_ = nullptr; filament::Texture* texture_ = nullptr;
TextureConfig config_; mjrTextureConfig config_;
SphericalHarmonics spherical_harmonics_; SphericalHarmonics spherical_harmonics_;
bool has_spherical_harmonics_ = false; bool has_spherical_harmonics_ = false;
@@ -21,13 +21,13 @@
#include <mujoco/mjrender.h> #include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h> #include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h> #include <mujoco/mujoco.h>
#include "experimental/filament/filament/filament_context.h" #include "experimental/filament/compat/mjr_filament_renderer.h"
#if defined(TLS_FILAMENT_CONTEXT) #if defined(TLS_FILAMENT_CONTEXT)
static thread_local mujoco::FilamentContext* g_filament_context = nullptr; static thread_local mujoco::MjrFilamentRenderer* g_filament_context = nullptr;
#else #else
static mujoco::FilamentContext* g_filament_context = nullptr; static mujoco::MjrFilamentRenderer* g_filament_context = nullptr;
#endif #endif
static void CheckFilamentContext() { static void CheckFilamentContext() {
@@ -43,13 +43,13 @@ void mjrf_defaultFilamentConfig(mjrFilamentConfig* config) {
} }
void mjrf_makeFilamentContext(const mjModel* m, mjrContext* con, void mjrf_makeFilamentContext(const mjModel* m, mjrContext* con,
const mjrFilamentConfig* config) { const mjrFilamentConfig* config) {
// TODO: Support multiple contexts and multiple threads. For now, we'll just // TODO: Support multiple contexts and multiple threads. For now, we'll just
// assume a single, global context. // assume a single, global context.
if (g_filament_context != nullptr) { if (g_filament_context != nullptr) {
mju_error("Context already exists!"); mju_error("Context already exists!");
} }
g_filament_context = new mujoco::FilamentContext(config); g_filament_context = new mujoco::MjrFilamentRenderer(config);
g_filament_context->Init(m); g_filament_context->Init(m);
} }
@@ -29,11 +29,11 @@ extern "C" {
// IMPORTANT: This API should still be considered experimental and is likely // IMPORTANT: This API should still be considered experimental and is likely
// change frequently. // change frequently.
typedef enum mjtGraphicsApi_ { // backend graphics API to use typedef enum mjrGraphicsApi_ { // backend graphics API to use
mjGFX_DEFAULT = 0, // default based on platform mjGRAPHICS_API_DEFAULT = 0, // default based on platform
mjGFX_OPENGL, // OpenGL (desktop) mjGRAPHICS_API_OPENGL, // OpenGL (desktop) / WebGL
mjGFX_VULKAN // Vulkan mjGRAPHICS_API_VULKAN // Vulkan
} mjtGraphicsApi; } mjrGraphicsApi;
struct mjrFilamentConfig { struct mjrFilamentConfig {
// The native window handle into which we can render directly. // The native window handle into which we can render directly.
+3 -2
View File
@@ -90,8 +90,9 @@ void Renderer::Init(const mjModel* model) {
render_config.width = model->vis.global.offwidth; render_config.width = model->vis.global.offwidth;
render_config.height = model->vis.global.offheight; render_config.height = model->vis.global.offheight;
render_config.force_software_rendering = IsSoftware(gfx_); render_config.force_software_rendering = IsSoftware(gfx_);
render_config.graphics_api = render_config.graphics_api = IsOpenGl(gfx_) || IsWebGl(gfx_)
IsOpenGl(gfx_) || IsWebGl(gfx_) ? mjGFX_OPENGL : mjGFX_VULKAN; ? mjGRAPHICS_API_OPENGL
: mjGRAPHICS_API_VULKAN;
mjrf_makeFilamentContext(model, &render_context_, &render_config); mjrf_makeFilamentContext(model, &render_context_, &render_config);
render_ = [&](mjrRect rect, mjvScene* scene) { render_ = [&](mjrRect rect, mjvScene* scene) {
mjrf_render(rect, scene, &render_context_); mjrf_render(rect, scene, &render_context_);
+4 -9
View File
@@ -16,6 +16,7 @@
#include <algorithm> #include <algorithm>
#include <cstddef> #include <cstddef>
#include <cstdlib>
#include <span> #include <span>
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -46,8 +47,8 @@ extern void* GetNativeWindowOsx(void* window);
namespace mujoco::platform { namespace mujoco::platform {
static void InitImGui(SDL_Window* window, float content_scale, bool load_fonts, static void InitImGui(SDL_Window* window, float content_scale,
bool build_fonts) { bool load_fonts) {
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@@ -84,10 +85,6 @@ static void InitImGui(SDL_Window* window, float content_scale, bool load_fonts,
constexpr ImWchar icon_ranges[] = {0xf000, 0xf3ff, 0x000}; constexpr ImWchar icon_ranges[] = {0xf000, 0xf3ff, 0x000};
io.Fonts->AddFontFromMemoryTTF(data, size, 14.f, &icon_cfg, icon_ranges); io.Fonts->AddFontFromMemoryTTF(data, size, 14.f, &icon_cfg, icon_ranges);
if (build_fonts) {
io.Fonts->Build();
}
// Note: we purposefully do not "close" the font resources as ImGui may // Note: we purposefully do not "close" the font resources as ImGui may
// need them again to resize fonts. // need them again to resize fonts.
} }
@@ -132,9 +129,7 @@ Window::Window(std::string_view title, int width, int height, Config config)
mju_error("Error creating window: %s", SDL_GetError()); mju_error("Error creating window: %s", SDL_GetError());
} }
InitImGui(sdl_window_, content_scale, config.load_fonts, InitImGui(sdl_window_, content_scale, config.load_fonts);
(config_.gfx_mode != GraphicsMode::ClassicOpenGl &&
config_.gfx_mode != GraphicsMode::ClassicOpenGlHeadless));
// Filament (except WebGL) manages its own swap chain including when to swap. // Filament (except WebGL) manages its own swap chain including when to swap.
// In all other cases, we'll use SDL to manage the swap chain. // In all other cases, we'll use SDL to manage the swap chain.
+105 -68
View File
@@ -14,87 +14,124 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
set(MUJOCO_STUDIO_TARGET_NAME mujoco_studio)
add_executable(${MUJOCO_STUDIO_TARGET_NAME})
target_sources(${MUJOCO_STUDIO_TARGET_NAME}
PRIVATE
app.cc
app.h
main.cc
)
target_include_directories(${MUJOCO_STUDIO_TARGET_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
)
if (WIN32)
target_compile_definitions(${MUJOCO_STUDIO_TARGET_NAME}
PRIVATE
-D_USE_MATH_DEFINES
)
set_target_properties(${MUJOCO_STUDIO_TARGET_NAME}
PROPERTIES
VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
)
endif()
include(third_party_deps/dear_imgui) include(third_party_deps/dear_imgui)
include(third_party_deps/implot) include(third_party_deps/implot)
include(third_party_deps/opensans) include(third_party_deps/opensans)
include(third_party_deps/font_awesome) include(third_party_deps/font_awesome)
target_link_libraries(${MUJOCO_STUDIO_TARGET_NAME} # Common configuration shared between mujoco_studio and mujoco_live.
PRIVATE function(configure_studio_target TARGET_NAME)
absl::flags target_sources(${TARGET_NAME}
absl::flags_parse
dear_imgui
implot
mujoco::mujoco
mujoco::platform
)
# TODO: re-enable mjz support on Windows builds once DllMain issue is resolved.
if (NOT WIN32)
target_link_libraries(${MUJOCO_STUDIO_TARGET_NAME}
PRIVATE PRIVATE
mujoco::mjz app.cc
app.h
) )
endif()
file(MAKE_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets) target_include_directories(${TARGET_NAME}
PUBLIC
${PROJECT_SOURCE_DIR}/include
${PROJECT_SOURCE_DIR}/src
)
add_custom_command( if (WIN32)
TARGET ${MUJOCO_STUDIO_TARGET_NAME} target_compile_definitions(${TARGET_NAME}
POST_BUILD PRIVATE
COMMAND ${CMAKE_COMMAND} -D_USE_MATH_DEFINES
-E copy )
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../_deps/opensans-src/fonts/ttf/OpenSans-Regular.ttf set_target_properties(${TARGET_NAME}
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets PROPERTIES
COMMENT "Copying OpenSans-Regular.ttf assets to build directory" VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
) )
add_custom_command( endif()
TARGET ${MUJOCO_STUDIO_TARGET_NAME}
POST_BUILD target_link_libraries(${TARGET_NAME}
COMMAND ${CMAKE_COMMAND} PRIVATE
-E copy absl::flags
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../_deps/font_awesome-src/fonts/fontawesome-webfont.ttf absl::flags_parse
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets dear_imgui
COMMENT "Copying fontawesome-webfont.ttf to build directory" implot
) mujoco::mujoco
mujoco::platform
)
# TODO: re-enable mjz support on Windows builds once DllMain issue is resolved.
if (NOT WIN32)
target_link_libraries(${TARGET_NAME}
PRIVATE
mujoco::mjz
)
endif()
file(MAKE_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets)
# Filament backend requires additional files to be copied into an "assets" folder.
if(MUJOCO_USE_FILAMENT)
add_custom_command( add_custom_command(
TARGET ${MUJOCO_STUDIO_TARGET_NAME} TARGET ${TARGET_NAME}
POST_BUILD POST_BUILD
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
-E copy_directory -E copy
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../src/experimental/filament/assets ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../_deps/opensans-src/fonts/ttf/OpenSans-Regular.ttf
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets
COMMENT "Copying Filament assets to build directory" COMMENT "Copying OpenSans-Regular.ttf assets to build directory"
)
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND}
-E copy
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../_deps/font_awesome-src/fonts/fontawesome-webfont.ttf
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets
COMMENT "Copying fontawesome-webfont.ttf to build directory"
)
# Filament backend requires additional files to be copied into an "assets" folder.
if(MUJOCO_USE_FILAMENT)
add_custom_command(
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND}
-E copy_directory
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../src/experimental/filament/assets
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets
COMMENT "Copying Filament assets to build directory"
)
endif()
endfunction()
# Desktop Studio target.
if(NOT EMSCRIPTEN)
add_executable(mujoco_studio)
target_sources(mujoco_studio PRIVATE main.cc)
configure_studio_target(mujoco_studio)
endif()
# WASM Live target.
if(EMSCRIPTEN)
add_executable(mujoco_live)
target_sources(mujoco_live PRIVATE wasm.cc)
target_compile_options(mujoco_live PRIVATE -g)
configure_studio_target(mujoco_live)
target_link_libraries(mujoco_live PRIVATE mujoco::render_noop)
# Ensure resource decoder plugins are linked into the WASM binary.
target_link_options(mujoco_live PRIVATE
-Wl,--whole-archive $<TARGET_FILE:mujoco> -Wl,--no-whole-archive
)
# Filament's OpenGL backend requires WebGL2 / full ES3 bindings.
target_link_options(mujoco_live PRIVATE
--bind
-sUSE_WEBGL2=1
-sFULL_ES3
-sMIN_WEBGL_VERSION=2
-sMAX_WEBGL_VERSION=2
-sALLOW_MEMORY_GROWTH=1
-sASYNCIFY=1
-sFETCH=1
-sGL_PREINITIALIZED_CONTEXT=1
-sSTACK_SIZE=512mb
-sINITIAL_MEMORY=1024mb
-sASSERTIONS=1
-fexceptions
-g
) )
endif() endif()
+143 -18
View File
@@ -1,8 +1,8 @@
<!doctype html> <!doctype html>
<html> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>MuJoCo Studio!</title> <title>MuJoCo Live</title>
</head> </head>
<body style="margin: 0; overflow: hidden"> <body style="margin: 0; overflow: hidden">
<div style="position: absolute; top: 3px; right: 3px; z-index: 1;"> <div style="position: absolute; top: 3px; right: 3px; z-index: 1;">
@@ -16,6 +16,28 @@
style="width: 100vw; height: 100vh; display: block" style="width: 100vw; height: 100vh; display: block"
></canvas> ></canvas>
<script> <script>
// --- Loading overlay (shown while model is compiling) ---
var loadingOverlay = document.createElement('div');
loadingOverlay.id = 'loadingOverlay';
loadingOverlay.style.cssText =
'position:fixed;top:0;left:0;width:100%;height:100%;' +
'background:rgba(0,0,0,0.6);z-index:10000;display:none;' +
'align-items:center;justify-content:center;';
loadingOverlay.innerHTML =
'<div style="color:#fff;font-size:24px;font-family:sans-serif;' +
'text-align:center;">' +
'<div style="margin-bottom:16px;">Loading model\u2026</div>' +
'<div style="font-size:14px;opacity:0.7;">This may take a moment ' +
'for large models.</div></div>';
document.body.appendChild(loadingOverlay);
function showLoading() {
loadingOverlay.style.display = 'flex';
}
function hideLoading() {
loadingOverlay.style.display = 'none';
}
var Module = { var Module = {
preRun: [], preRun: [],
postRun: [], postRun: [],
@@ -46,8 +68,30 @@
totalDependencies: 0, totalDependencies: 0,
monitorRunDependencies(left) {}, monitorRunDependencies(left) {},
onRuntimeInitialized: () => { onRuntimeInitialized: () => {
// Define assets to prefetch. These paths are relative to the wasm_binary/ directory. // Define assets to prefetch. These paths are relative to the mujoco_live_wasm/ directory.
const assetsToPrefetch = [ const assetsToPrefetch = [
"assets/fontawesome-webfont.ttf",
"assets/ibl.ktx",
"assets/OpenSans-Regular.ttf",
"assets/pbr.filamat",
"assets/pbr_packed.filamat",
"assets/phong_2d_fade.filamat",
"assets/phong_2d.filamat",
"assets/phong_2d_reflect.filamat",
"assets/phong_2d_uv_fade.filamat",
"assets/phong_2d_uv.filamat",
"assets/phong_2d_uv_reflect.filamat",
"assets/phong_color_fade.filamat",
"assets/phong_color.filamat",
"assets/phong_color_reflect.filamat",
"assets/phong_cube_fade.filamat",
"assets/phong_cube.filamat",
"assets/phong_cube_reflect.filamat",
"assets/unlit_decor.filamat",
"assets/unlit_depth.filamat",
"assets/unlit_line.filamat",
"assets/unlit_segmentation.filamat",
"assets/unlit_ui.filamat"
]; ];
const assetPromises = assetsToPrefetch.map(async (relativePath) => { const assetPromises = assetsToPrefetch.map(async (relativePath) => {
@@ -71,7 +115,31 @@
.then(() => { .then(() => {
try { try {
Module.init(); Module.init();
requestAnimationFrame(Module.animate);
// Check for a ?model= URL parameter and load from URL.
const params = new URLSearchParams(window.location.search);
const modelUrl = params.get('model');
if (modelUrl) {
// loadUrl uses ASYNCIFY (via EM_ASYNC_JS fetch), which
// suspends the WASM module. We must not start the animation
// loop until the load completes, otherwise renderFrame will
// hit "Cannot have multiple async operations in flight".
showLoading();
requestAnimationFrame(() => {
requestAnimationFrame(async () => {
try {
await Module.loadUrl(modelUrl);
} catch (error) {
console.error('Failed to load model from URL:', error);
} finally {
hideLoading();
requestAnimationFrame(Module.animate);
}
});
});
} else {
requestAnimationFrame(Module.animate);
}
} catch (error) { } catch (error) {
console.error('Failed to initialize app.', error); console.error('Failed to initialize app.', error);
} }
@@ -95,6 +163,31 @@
Module.canvas.style.height = window.innerHeight + "px"; Module.canvas.style.height = window.innerHeight + "px";
}); });
function handleFile(file) {
const reader = new FileReader();
reader.onload = (e) => {
const buffer = e.target.result;
// Show the loading overlay, then defer the blocking WASM call by two
// animation frames so the browser has a chance to paint the overlay.
showLoading();
requestAnimationFrame(() => {
requestAnimationFrame(() => {
try {
Module.loadFile(file.name, buffer);
} catch (error) {
console.error('Failed to load model from file:', error);
} finally {
hideLoading();
}
});
});
};
reader.onerror = (e) => {
console.error('Error reading file:', e);
};
reader.readAsArrayBuffer(file);
}
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
const uploadButton = document.getElementById('uploadButton'); const uploadButton = document.getElementById('uploadButton');
const fileInput = document.getElementById('fileInput'); const fileInput = document.getElementById('fileInput');
@@ -108,23 +201,55 @@
if (!file) { if (!file) {
return; return;
} }
handleFile(file);
});
const reader = new FileReader(); // --- Drag-and-drop support ---
reader.onload = (e) => { const dropOverlay = document.createElement('div');
const buffer = e.target.result; dropOverlay.id = 'dropOverlay';
try { dropOverlay.style.cssText =
Module.loadFile(file.name, buffer); 'position:fixed;top:0;left:0;width:100%;height:100%;' +
} catch (error) { 'background:rgba(0,0,0,0.5);z-index:9999;display:none;' +
console.error('Failed to load model from file:', error); 'pointer-events:none;align-items:center;justify-content:center;';
} dropOverlay.innerHTML =
}; '<div style="color:#fff;font-size:24px;font-family:sans-serif;' +
reader.onerror = (e) => { 'padding:32px 48px;border:3px dashed #fff;border-radius:16px;">' +
console.error('Error reading file:', e); 'Drop model file here</div>';
}; document.body.appendChild(dropOverlay);
reader.readAsArrayBuffer(file);
let dragCounter = 0;
document.addEventListener('dragenter', (e) => {
e.preventDefault();
dragCounter++;
if (dragCounter === 1) {
dropOverlay.style.display = 'flex';
}
});
document.addEventListener('dragleave', (e) => {
e.preventDefault();
dragCounter--;
if (dragCounter === 0) {
dropOverlay.style.display = 'none';
}
});
document.addEventListener('dragover', (e) => {
e.preventDefault();
});
document.addEventListener('drop', (e) => {
e.preventDefault();
dragCounter = 0;
dropOverlay.style.display = 'none';
const files = e.dataTransfer.files;
for (let i = 0; i < files.length; i++) {
handleFile(files[i]);
}
}); });
}); });
</script> </script>
<script async src="/bin/wasm.js"></script> <script async src="bin/mujoco_live.js"></script>
</body> </body>
</html> </html>
+15 -1
View File
@@ -75,7 +75,7 @@ class FileResource {
int main(int argc, char** argv, char** envp) { int main(int argc, char** argv, char** envp) {
absl::ParseCommandLine(argc, argv); absl::ParseCommandLine(argc, argv);
const char* home = getenv("HOME"); const char* home = std::getenv("HOME");
const std::string ini_path = std::string(home ? home : ".") + "/.mujoco.ini"; const std::string ini_path = std::string(home ? home : ".") + "/.mujoco.ini";
mjpResourceProvider resource_provider; mjpResourceProvider resource_provider;
@@ -103,6 +103,20 @@ int main(int argc, char** argv, char** envp) {
std::string gfx = absl::GetFlag(FLAGS_gfx); std::string gfx = absl::GetFlag(FLAGS_gfx);
const char* session_type = std::getenv("XDG_SESSION_TYPE");
const char* wayland_display = std::getenv("WAYLAND_DISPLAY");
if ((session_type && std::string_view(session_type) == "wayland") ||
wayland_display) {
if (gfx.empty()) {
gfx = "opengl_headless";
} else if (gfx == "classic" || gfx == "opengl") {
mju_error(
"Wayland does not support '%s' graphics mode. "
"Restart with a different graphics mode, or login using X11.",
gfx.c_str());
}
}
mujoco::platform::GraphicsMode gfx_mode = mujoco::platform::GraphicsMode gfx_mode =
mujoco::platform::GraphicsModeFromString( mujoco::platform::GraphicsModeFromString(
gfx, mujoco::platform::GraphicsMode::FilamentOpenGl); gfx, mujoco::platform::GraphicsMode::FilamentOpenGl);
+115
View File
@@ -18,7 +18,11 @@
#include <emscripten/bind.h> #include <emscripten/bind.h>
#include <emscripten/val.h> #include <emscripten/val.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem> #include <filesystem>
#include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_map> #include <unordered_map>
@@ -59,6 +63,84 @@ class AssetRegistry {
std::unordered_map<std::string, std::string> assets_; std::unordered_map<std::string, std::string> assets_;
}; };
// ---------------------------------------------------------------------------
// HTTP/HTTPS resource fetching via the JS fetch API (uses ASYNCIFY to yield).
// ---------------------------------------------------------------------------
// Fetches a URL using the JS fetch API. Returns a malloc'd buffer and its size.
// The caller is responsible for freeing the buffer. Returns 0 on failure.
EM_ASYNC_JS(int, FetchUrl,
(const char* url, char** out_data, std::int32_t* out_size), {
try {
const urlStr = UTF8ToString(url);
const response = await fetch(urlStr);
if (!response.ok) {
console.error('Fetch failed: ' + response.status + ' ' +
urlStr);
return 0;
}
const buffer = await response.arrayBuffer();
const bytes = new Uint8Array(buffer);
const ptr = _malloc(bytes.length);
HEAPU8.set(bytes, ptr);
setValue(out_data, ptr, '*');
setValue(out_size, bytes.length, 'i32');
return 1;
} catch (e) {
console.error('Fetch error:', e);
return 0;
}
});
// Cache for data fetched via HTTP/HTTPS. Stores the downloaded bytes keyed by
// the resource name (URL) so that read() can return a pointer to the data.
class FetchCache {
public:
static FetchCache& Instance() {
static FetchCache instance;
return instance;
}
// Fetches the URL and stores the result. Returns the size (>0) on success.
int Fetch(const char* url) {
char* data = nullptr;
std::int32_t size = 0;
if (!FetchUrl(url, &data, &size)) {
return 0;
}
entries_[url] = Entry{UniquePtrWasm<char[]>(data), size};
return size;
}
// Returns pointer and size for a previously fetched URL.
int Read(const char* url, const void** buffer) {
auto it = entries_.find(url);
if (it == entries_.end()) {
return -1;
}
*buffer = it->second.data.get();
return it->second.size;
}
// Frees the data for a URL.
void Close(const char* url) { entries_.erase(url); }
private:
struct FreeDeleter {
void operator()(void* p) const { std::free(p); }
};
template <typename T>
using UniquePtrWasm = std::unique_ptr<T, FreeDeleter>;
struct Entry {
UniquePtrWasm<char[]> data;
int size;
};
std::unordered_map<std::string, Entry> entries_;
};
// ---------------------------------------------------------------------------
// Javascript-facing function to register an asset. // Javascript-facing function to register an asset.
void RegisterAsset(std::string filename, std::string contents) { void RegisterAsset(std::string filename, std::string contents) {
AssetRegistry::Instance().RegisterAsset(std::move(filename), AssetRegistry::Instance().RegisterAsset(std::move(filename),
@@ -92,6 +174,27 @@ void Init() {
resource_provider.prefix = "filament"; resource_provider.prefix = "filament";
mjp_registerResourceProvider(&resource_provider); mjp_registerResourceProvider(&resource_provider);
// Register HTTP/HTTPS resource providers so that models loaded from URLs
// can automatically fetch referenced assets (meshes, textures, etc.) over
// the network.
mjpResourceProvider http_provider;
mjp_defaultResourceProvider(&http_provider);
http_provider.open = [](mjResource* resource) {
return FetchCache::Instance().Fetch(resource->name);
};
http_provider.read = [](mjResource* resource, const void** buffer) {
return FetchCache::Instance().Read(resource->name, buffer);
};
http_provider.close = [](mjResource* resource) {
FetchCache::Instance().Close(resource->name);
};
http_provider.prefix = "http";
mjp_registerResourceProvider(&http_provider);
http_provider.prefix = "https";
mjp_registerResourceProvider(&http_provider);
g_app = new mujoco::studio::App({ g_app = new mujoco::studio::App({
.width = width, .width = width,
.height = height, .height = height,
@@ -124,6 +227,17 @@ void LoadFile(const std::string& filename, const std::string& data) {
g_app->LoadModelFromBuffer({ptr, ptr + data.size()}, content_type, filename); g_app->LoadModelFromBuffer({ptr, ptr + data.size()}, content_type, filename);
} }
// Javascript-facing function to load a model from a URL.
// The URL is passed directly to LoadModelFromFile, which will use the
// registered HTTP/HTTPS resource providers to fetch the model and any
// referenced assets.
void LoadUrl(const std::string& url) {
if (!g_app) {
return;
}
g_app->LoadModelFromFile(url);
}
// Javascript-facing function to render a single frame. // Javascript-facing function to render a single frame.
void RenderFrame() { void RenderFrame() {
if (g_app) { if (g_app) {
@@ -144,6 +258,7 @@ EMSCRIPTEN_BINDINGS(studio_bindings) {
emscripten::function("registerAsset", &RegisterAsset); emscripten::function("registerAsset", &RegisterAsset);
emscripten::function("init", &Init); emscripten::function("init", &Init);
emscripten::function("loadFile", &LoadFile); emscripten::function("loadFile", &LoadFile);
emscripten::function("loadUrl", &LoadUrl);
emscripten::function("renderFrame", &RenderFrame); emscripten::function("renderFrame", &RenderFrame);
emscripten::function("deinit", &Deinit); emscripten::function("deinit", &Deinit);
} }
+3 -1
View File
@@ -12,4 +12,6 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
target_sources(mujoco PRIVATE render_noop.c) add_library(render_noop STATIC render_noop.c)
target_link_libraries(render_noop PUBLIC mujoco::mujoco)
add_library(mujoco::render_noop ALIAS render_noop)
@@ -37,7 +37,7 @@ public class MujocoBinaryRetriever {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/mujoco.dylib") == null) { if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/mujoco.dylib") == null) {
File.Copy( File.Copy(
"/Applications/MuJoCo.app/Contents/Frameworks" + "/Applications/MuJoCo.app/Contents/Frameworks" +
"/mujoco.framework/Versions/Current/libmujoco.3.7.1.dylib", "/mujoco.framework/Versions/Current/libmujoco.3.8.1.dylib",
mujocoPath + "/mujoco.dylib"); mujocoPath + "/mujoco.dylib");
AssetDatabase.Refresh(); AssetDatabase.Refresh();
} }
@@ -45,7 +45,7 @@ public class MujocoBinaryRetriever {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/libmujoco.so") == null) { if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/libmujoco.so") == null) {
File.Copy( File.Copy(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) +
"/.mujoco/mujoco-3.7.1/lib/libmujoco.so.3.7.1", "/.mujoco/mujoco-3.8.1/lib/libmujoco.so.3.8.1",
mujocoPath + "/libmujoco.so"); mujocoPath + "/libmujoco.so");
AssetDatabase.Refresh(); AssetDatabase.Refresh();
} }
+1 -1
View File
@@ -113,7 +113,7 @@ public const int mjMAXLINEPNT = 1001;
public const int mjMAXPLANEGRID = 200; public const int mjMAXPLANEGRID = 200;
public const bool THIRD_PARTY_MUJOCO_MJXMACRO_H_ = true; public const bool THIRD_PARTY_MUJOCO_MJXMACRO_H_ = true;
public const bool THIRD_PARTY_MUJOCO_MUJOCO_H_ = true; public const bool THIRD_PARTY_MUJOCO_MUJOCO_H_ = true;
public const int mjVERSION_HEADER = 3007001; public const int mjVERSION_HEADER = 3008001;
// ------------------------------------Enums------------------------------------ // ------------------------------------Enums------------------------------------
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "org.mujoco", "name": "org.mujoco",
"displayName": "MuJoCo", "displayName": "MuJoCo",
"version": "3.7.1", "version": "3.8.1",
"description": "MuJoCo importer and runtime plug-in", "description": "MuJoCo importer and runtime plug-in",
"dependencies": {}, "dependencies": {},
"author": { "author": {