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
# "()" and capturing the identifier as a valid function name.
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(
mujoco
VERSION 3.7.1
VERSION 3.8.1
DESCRIPTION "MuJoCo Physics Simulator"
HOMEPAGE_URL "https://mujoco.org"
)
@@ -53,12 +53,49 @@ endif()
if(EMSCRIPTEN)
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)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -O3 -fexceptions")
if(MUJOCO_WASM_THREADS)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
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()
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/ui)
endif()
add_subdirectory(src/render/noop)
if(MUJOCO_USE_FILAMENT AND NOT EMSCRIPTEN)
if(MUJOCO_USE_FILAMENT)
add_subdirectory(src/experimental/filament)
endif()
+5 -1
View File
@@ -18,7 +18,11 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
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
if(NOT CMAKE_CONFIGURATION_TYPES)
+11 -1
View File
@@ -13,7 +13,7 @@
# limitations under the License.
set(MUJOCO_DEP_VERSION_filament
a4945939de514d049baeed654efbbdd06bc5bdbf
06793c4a80dd467025b2db1b3b7ea63bf1a865bb
CACHE STRING "Tag/version of `filament` to be fetched."
)
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 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_SKIP_SDL2 ON)
set(FILAMENT_USE_EXTERNAL_ABSL ON)
@@ -39,3 +48,4 @@ fetchpackage(
)
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
FILEVERSION 3,7,1,0
PRODUCTVERSION 3,7,1,0
FILEVERSION 3,8,1,0
PRODUCTVERSION 3,8,1,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -9,9 +9,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.7.1"
VALUE "ProductVersion", "3.8.1"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.7.1"
VALUE "FileVersion", "3.8.1"
VALUE "InternalName", "mujoco.dll"
VALUE "OriginalFilename", "mujoco.dll"
VALUE "CompanyName", "Google DeepMind"
+4 -4
View File
@@ -1,8 +1,8 @@
MUJOCO ICON "mujoco.ico"
1 VERSIONINFO
FILEVERSION 3,7,1,0
PRODUCTVERSION 3,7,1,0
FILEVERSION 3,8,1,0
PRODUCTVERSION 3,8,1,0
FILEOS 0x4
FILETYPE 0x1
{
@@ -11,9 +11,9 @@ FILETYPE 0x1
BLOCK "040904b0"
{
VALUE "ProductName", "MuJoCo"
VALUE "ProductVersion", "3.7.1"
VALUE "ProductVersion", "3.8.1"
VALUE "FileDescription", "MuJoCo"
VALUE "FileVersion", "3.7.1"
VALUE "FileVersion", "3.8.1"
VALUE "InternalName", "simulate.exe"
VALUE "OriginalFilename", "simulate.exe"
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
- description
* - ``mjVERSION_HEADER``
- 3007001
- 3008001
- 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.
The API function :ref:`mj_version` returns a number with the same meaning
+24 -17
View File
@@ -2,34 +2,41 @@
Changelog
=========
Upcoming version (not yet released)
-----------------------------------
Version 3.8.0 (April 24, 2026)
------------------------------
General
^^^^^^^
- Added new :ref:`mj_maxContact<mj_maxContact>` function to get the maximum number of possible contacts returned by
two geoms.
- Added ``mj_containsBufferVFS`` and ``mj_containsFileVFS`` to check for existence of buffers and files in VFS.
- Added :ref:`multi-cell support<body-flexcomp-cellnum>` for trilinear and quadratic flexes. Note that the implicit
integrator uses a dense solver for the flex degrees of freedom, which can be slow for multi-cell flexes.
- Refactored ``flexstrain`` equality constraints to be instantiated per cell instead of per flex object, reducing the
number of degrees of freedom per constraint row. The equality can be associated with a specific cell with the new
attribute ":ref:`cell <equality-flexstrain-cell>`
1. Added support for Python 3.14.
2. Added :ref:`multi-cell support<body-flexcomp-cellcount>` for trilinear and quadratic flexes. Note that the implicit
integrator uses a dense solver for the flex degrees of freedom, which can be slow for multi-cell flexes.
3. Refactored ``strain`` flex :ref:`equality constraints<flexcomp-edge-equality>` to be instantiated per cell instead of
per flex object, reducing the number of degrees of freedom per constraint row. The equality can be associated with a
specific cell with the new attribute :ref:`cell <equality-flexstrain-cell>`
4. Added new :ref:`mj_maxContact<mj_maxContact>` function to get the maximum number of possible contacts returned by
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
- The feature :ref:`multiccd<coMultiCCD>` is now enabled by default. This feature has little performance overhead
and gives better contact behavior for stability.
6. The :ref:`multiccd<coMultiCCD>` option (multiple contacts returned from the convex collision detection pipeline)
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
^^^^^^^^^
- 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
spec.
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 spec.
Version 3.7.0 (April 14, 2026)
------------------------------
+135 -2
View File
@@ -3,8 +3,8 @@
Extensions
----------
This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided
via :ref:`engine plugins<exPlugin>` and :ref:`resource providers<exProvider>`.
This section describes MuJoCo's mechanisms for user-authored extensions. At present, extensibility is provided by
via :ref:`engine plugins<exPlugin>`, :ref:`decoders<exDecoder>`, and :ref:`resource providers<exProvider>`.
.. _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
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:
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
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``.
Linux
_____
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
_______
+1 -1
View File
@@ -16,7 +16,7 @@
#define MUJOCO_MUJOCO_H_
// 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
#include <stdlib.h>
+19 -15
View File
@@ -1,21 +1,25 @@
jax-cuda12-plugin==0.5.3; python_version >= '3.10' \
--hash=sha256:1862595b2b6d815679d11e0e889e523185ee54a46d46e022689f70fc4554dd91 \
--hash=sha256:21fec1b56c98783ea0569b747a56751f1f9ff2187b48acc11c700d3bfc5e1a31 \
jax-cuda12-plugin==0.8.3; python_version >= '3.13' \
--hash=sha256:11d7cb222cfd4d5f6b7691df61516a5edc9298b7879dd6caaccc6dfc64678c70 \
--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:c2517a7c2186f8708894696e26cf96ebd60b7879ceca398b2c46abb28d2c96c8 \
--hash=sha256:aaa704a5ef547595d022db1c1e4878a0677116412a9360c115d67ff4b64e1596 \
--hash=sha256:298d2d768f1029b74a0b1d01270e549349d2c37dc07658796542cda967eb7bd3 \
--hash=sha256:6171aed2f4b3bdd5fc13782de1072c6a634fce13731b75d0cb0a6ab8f4e6e650 \
--hash=sha256:aaa704a5ef547595d022db1c1e4878a0677116412a9360c115d67ff4b64e1596 \
--hash=sha256:ba2555967f9b6c381c8b4ef9fb03d05bc55ec25ecfee5cfe45c5ace34f7d4152 \
--hash=sha256:6171aed2f4b3bdd5fc13782de1072c6a634fce13731b75d0cb0a6ab8f4e6e650
jax-cuda12-plugin==0.4.30; python_version == '3.9' \
--hash=sha256:d8d196241b9253ecb1144a4409b5deacbb9771624f097b2bbf025da3c7d8f4f8 \
--hash=sha256:cb8edccdce358451205f689e3536272200761c625c8e8059ab10523984cf8b61
jax-cuda12-pjrt==0.5.3; python_version >= '3.10' \
--hash=sha256:c5378306568ba0c81b230a779dd3194c9dd10339ab6360ae80928108d37e7f75 \
--hash=sha256:04ee111eaf5fc2692978ad4a5c84d5925e42eb05c1701849ba3a53f6515400cc
jax-cuda12-pjrt==0.4.30; python_version == '3.9' \
--hash=sha256:895d0198ad99638fcaf976c47592e2a543eef79ea15fabd24a402d055390c328 \
--hash=sha256:c36fb1e0c236563bf3a87e70f4d1ab28a31d7cf5d722c9ede30c4172116e8bcb
--hash=sha256:c2517a7c2186f8708894696e26cf96ebd60b7879ceca398b2c46abb28d2c96c8
jax-cuda12-pjrt==0.8.3; python_version >= '3.13' \
--hash=sha256:f6d085fa7b2836cd79b14cabf1058ddb50c5161bfca9ede407993fa4f7547b7b \
--hash=sha256:f740c661dd4064ff45dedf170fd0c4ff1a25d077636ad293307e8d28c78e65d7
jax-cuda12-pjrt==0.5.3; python_version < '3.13' \
--hash=sha256:04ee111eaf5fc2692978ad4a5c84d5925e42eb05c1701849ba3a53f6515400cc \
--hash=sha256:c5378306568ba0c81b230a779dd3194c9dd10339ab6360ae80928108d37e7f75
warp-lang==1.12.1 \
--hash=sha256:98df3533a6c40a33cce961f8efa991006b30c9d286356e4cd77ea8ce86928f1d \
--hash=sha256:6bf01f10509488ba8eacaf4ec7fcf7cfbd503118b22e002ecba407b40a17424e \
+13 -17
View File
@@ -96,16 +96,15 @@ def _resolve_device(
return cpu_0
if impl == types.Impl.WARP:
# WARP implementation requires a CUDA GPU.
cuda_gpus = [d for d in jax.devices('cuda')]
if not cuda_gpus:
raise AssertionError(
'No CUDA GPU devices found in'
f' jax.devices("cuda")={jax.devices("cuda")}.'
)
# WARP implementation requires a CUDA GPU or CPU.
if has_cuda_gpu_device():
cuda_gpus = [d for d in jax.devices('cuda')]
if cuda_gpus:
logging.debug('Picking default device: %s', cuda_gpus[0])
return cuda_gpus[0]
logging.debug('Picking default device: %s', cuda_gpus[0])
return cuda_gpus[0]
logging.debug('Picking default device for Warp: CPU')
return jax.devices('cpu')[0]
raise ValueError(f'Unsupported implementation: {impl}')
@@ -121,9 +120,12 @@ def _check_impl_device_compatibility(
impl = types.Impl(impl)
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(
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()
@@ -425,8 +427,6 @@ def _put_model_jax(
return _strip_weak_type(model)
def _put_model_warp(
m: mujoco.MjModel,
graph_mode: mjxw.types.GraphMode,
@@ -719,8 +719,6 @@ def _make_data_jax(
return d
def _get_nested_attr(obj: Any, attr_name: str, split: str) -> Any:
"""Returns the nested attribute from an object."""
for part in attr_name.split(split):
@@ -1084,8 +1082,6 @@ def _put_data_jax(
return _strip_weak_type(data)
# TODO(josechenf): Iterate on the keepalive implementation to make it easier to
# use before OSS.
def _put_data_cpp(
+5 -14
View File
@@ -934,8 +934,8 @@ _DEVICE_TEST_CASES = [
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend specified.
('cpu', 'warp', ('cpu', 'error')),
('gpu-notnvidia', 'warp', ('cpu', 'error')),
('cpu', 'warp', ('cpu', Impl.WARP)),
('gpu-notnvidia', 'warp', ('gpu', 'error')),
('gpu-nvidia', 'warp', ('gpu', Impl.WARP)),
('tpu', 'warp', ('tpu', 'error')),
# CPP backend specified.
@@ -962,10 +962,10 @@ _DEFAULT_DEVICE_TEST_CASES = [
('gpu-nvidia', 'jax', ('gpu', Impl.JAX)),
('tpu', 'jax', ('tpu', Impl.JAX)),
# WARP backend impl specified.
('cpu', 'warp', ('cpu', 'error')),
('gpu-notnvidia', 'warp', ('cpu', 'error')),
('cpu', 'warp', ('cpu', Impl.WARP)),
('gpu-notnvidia', 'warp', ('cpu', 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.
('cpu', '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
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':
with self.assertRaises(AssertionError):
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_vertnum: 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_nrow: np.ndarray
hfield_ncol: np.ndarray
+111 -48
View File
@@ -210,11 +210,19 @@ class FfiKernel:
self.input_output_aliases = input_output_aliases
# register the callback
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)
ffi_capsule = jax.ffi.pycapsule(ffi_ccall_address.value)
jax.ffi.register_ffi_target(self.name, ffi_capsule, platform="CUDA")
FFI_CCALLFUNC = ctypes.CFUNCTYPE(
ctypes.c_void_p, ctypes.POINTER(XLA_FFI_CallFrame)
)
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):
num_inputs = len(args)
@@ -241,18 +249,19 @@ class FfiKernel:
# check dtype
if input_value.dtype != input_arg.jax_scalar_type:
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
if input_value.ndim != input_arg.jax_ndim:
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
for d in range(input_arg.dtype_ndim):
if input_value.shape[input_arg.type.ndim + d] != input_arg.dtype_shape[d]:
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:
# make sure scalar is not a traced variable, should be static
@@ -328,7 +337,7 @@ class FfiKernel:
return call(*args, launch_id=launch_id)
def ffi_callback(self, call_frame):
def ffi_callback(self, call_frame, platform="CUDA"):
try:
# On the first call, XLA runtime will query the API version and traits
# 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.contents.metadata.contents.api_version.major_version = 0
metadata_ext.contents.metadata.contents.api_version.minor_version = 1
# Turn on CUDA graphs for this handler.
metadata_ext.contents.metadata.contents.traits = (
XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE
)
# Turn on CUDA graphs for this handler if on CUDA platform.
if platform == "CUDA":
metadata_ext.contents.metadata.contents.traits = (
XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE
)
return None
# Lock is required to prevent race conditions when callback is invoked
@@ -423,29 +433,43 @@ class FfiKernel:
kernel_params[0] = ctypes.addressof(launch_bounds)
# get device and stream
device = wp.get_cuda_device(get_device_ordinal_from_callframe(call_frame.contents))
stream = get_stream_from_callframe(call_frame.contents)
if platform == "CUDA":
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
hooks = self.kernel.module.get_kernel_hooks(self.kernel, device)
assert hooks.forward, "Failed to find kernel entry point"
# launch the kernel
wp._src.context.runtime.core.wp_cuda_launch_kernel(
device.context,
hooks.forward,
launch_bounds.size,
0,
256,
hooks.forward_smem_bytes,
kernel_params,
stream,
)
if device.is_cuda:
wp._src.context.runtime.core.wp_cuda_launch_kernel(
device.context,
hooks.forward,
launch_bounds.size,
0,
256,
hooks.forward_smem_bytes,
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:
print(traceback.format_exc())
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
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)
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, vmap_method=None):
num_inputs = len(args)
@@ -688,8 +718,7 @@ class FfiCallable:
except Exception:
# ignore unsupported devices like TPUs
pass
# we only support CUDA devices for now
if dev.is_cuda:
if dev.is_cuda or dev.is_cpu:
module.load(dev)
# save call data to be retrieved by callback
@@ -698,7 +727,7 @@ class FfiCallable:
self.call_id += 1
return call(*args, call_id=call_id)
def ffi_callback(self, call_frame):
def ffi_callback(self, call_frame, platform="CUDA"):
try:
# On the first call, XLA runtime will query the API version and traits
# 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.contents.metadata.contents.api_version.major_version = 0
metadata_ext.contents.metadata.contents.api_version.minor_version = 1
# Turn on CUDA graphs for this handler.
if self.graph_mode is GraphMode.JAX:
# Turn on CUDA graphs for this handler if on CUDA platform.
if self.graph_mode is GraphMode.JAX and platform == "CUDA":
metadata_ext.contents.metadata.contents.traits = (
XLA_FFI_Handler_TraitsBits.COMMAND_BUFFER_COMPATIBLE
)
@@ -738,6 +767,35 @@ class FfiCallable:
assert num_inputs == self.num_inputs
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)
device_ordinal = get_device_ordinal_from_callframe(call_frame.contents)
@@ -870,8 +928,8 @@ class FfiCallable:
arg_list.append(arr)
# call the Python function with reconstructed arguments
with wp.ScopedStream(stream, sync_enter=False):
if stream.is_capturing:
with wp.ScopedStream(stream, sync_enter=False) if stream else wp.ScopedDevice(device):
if stream and stream.is_capturing:
# capturing with JAX
with wp.ScopedCapture(external=True) as capture:
self.func(*arg_list)
@@ -879,7 +937,7 @@ class FfiCallable:
# keep a reference to the capture object to prevent required modules getting unloaded
call_desc.capture = capture
elif self.graph_mode == GraphMode.WARP:
elif self.graph_mode == GraphMode.WARP and device.is_cuda:
# capturing with WARP
with wp.ScopedCapture() as capture:
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:
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
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
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
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
else:
# not capturing
# not capturing or on CPU
self.func(*arg_list)
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
def ffi_callback(call_frame):
def ffi_callback(call_frame, platform="CUDA"):
try:
extension = call_frame.contents.extension_start
# 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.contents.metadata.contents.api_version.major_version = 0
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.
metadata_ext.contents.metadata.contents.traits = (
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
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:
_FFI_CALLBACK_REGISTRY[name] = callback_func
ffi_ccall_address = ctypes.cast(callback_func, ctypes.c_void_p)
ffi_capsule = jax.ffi.pycapsule(ffi_ccall_address.value)
jax.ffi.register_ffi_target(name, ffi_capsule, platform="CUDA")
_FFI_CALLBACK_REGISTRY[f"{name}_cuda"] = callback_func_cuda
_FFI_CALLBACK_REGISTRY[f"{name}_host"] = callback_func_host
ffi_ccall_address_cuda = ctypes.cast(callback_func_cuda, ctypes.c_void_p)
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
import numpy as np
try:
from mujoco.mjx.warp import forward # pylint: disable=g-import-not-at-top
except ImportError:
@@ -300,6 +301,56 @@ class StepTest(parameterized.TestCase):
tu.assert_attr_eq(dx, d, 'mocap_quat')
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):
if not _FORCE_TEST:
if not mjxw.WARP_INSTALLED:
+5 -4
View File
@@ -133,10 +133,11 @@ class SmoothTest(parameterized.TestCase):
def test_kinematics_vmap(self):
"""Tests kinematics with batched data."""
if not mjxw.WARP_INSTALLED:
self.skipTest('Warp not installed.')
if not io.has_cuda_gpu_device():
self.skipTest('No CUDA GPU device available.')
if not _FORCE_TEST:
if not mjxw.WARP_INSTALLED:
self.skipTest('Warp not installed.')
if not io.has_cuda_gpu_device():
self.skipTest('No CUDA GPU device available.')
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_type = select(dx._impl.efc__type)[: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]
nefc = len(keys)
@@ -180,7 +182,9 @@ def _mj_efc(d):
else:
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]
pos = d.efc_pos[keys]
efc_j = efc_j[keys]
+5 -4
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name="mujoco-mjx"
version = "3.7.1"
version = "3.8.1"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -21,6 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering",
]
requires-python = ">=3.10"
@@ -29,7 +30,7 @@ dependencies = [
"etils[epath]",
"jax",
"jaxlib",
"mujoco>=3.7.1.dev0",
"mujoco>=3.8.1.dev0",
"scipy",
"trimesh",
]
@@ -45,9 +46,9 @@ mjx-viewer = "mujoco.mjx.viewer:main"
[project.urls]
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"
Changelog = "https://mujoco.readthedocs.io/en/3.7.1/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.8.1/changelog.html"
[tool.isort]
force_single_line = true
+177 -78
View File
@@ -2,29 +2,48 @@ absl-py==2.1.0 \
--hash=sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308
etils[epath]==1.10.0; python_version >= '3.10' \
--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: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
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:972400db4af6e85270d81db5e6e620d31395f0472e510c50dfcd4cb3f72b7220 \
--hash=sha256:520665929649f29f7d948d4070dbaf3e032a4c1f7c11f2863eac73320fcee784 \
--hash=sha256:52be6c9775aff738a61170d8c047505c75bb799a45518e66a7a0908127b11785 \
--hash=sha256:5a5e88ab1cd6fdf78d69abe3544e8f09cce200dd339bb85fbe3c2ea67f2a5e68 \
--hash=sha256:8eb54e38d789557579f900ea3d70f104a440f8555a9681ed45f4a122dcbfd92e \
--hash=sha256:972400db4af6e85270d81db5e6e620d31395f0472e510c50dfcd4cb3f72b7220 \
--hash=sha256:a4666f81d72c060ed3e581ded116a9caa9b0a70a148a54cb12a1d3afca3624b5 \
--hash=sha256:b41a6fcaeb374fabc4ee7e74cfed60843bdab607cd54f60a68b7f7655cde2b66 \
--hash=sha256:b62bd8b29e5a4f9bfaa57c8daf6e04820b2c994f448f3dec602d64255545e9f2 \
--hash=sha256:a4666f81d72c060ed3e581ded116a9caa9b0a70a148a54cb12a1d3afca3624b5 \
--hash=sha256:29e1530fc81833216f1e28b578d0c59697654f72ee31c7a44ed7753baf5ac466 \
--hash=sha256:8eb54e38d789557579f900ea3d70f104a440f8555a9681ed45f4a122dcbfd92e \
--hash=sha256:d394dbde4a1c6bd67501cfb29d3819a10b900cb534cc0fc603319f7092f24cfa \
--hash=sha256:bddf6360377aa1c792e47fd87f307c342e331e5ff3582f940b1bca00f6b4bc73 \
--hash=sha256:5a5e88ab1cd6fdf78d69abe3544e8f09cce200dd339bb85fbe3c2ea67f2a5e68 \
--hash=sha256:520665929649f29f7d948d4070dbaf3e032a4c1f7c11f2863eac73320fcee784 \
--hash=sha256:31321c25282a06a6dfc940507bc14d0a0ac838d8ced6c07aa00a7fae34ce7b3f \
--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:d394dbde4a1c6bd67501cfb29d3819a10b900cb534cc0fc603319f7092f24cfa
# See note above about macOS x86-64 support.
jaxlib==0.4.38; python_version >= '3.10' and python_version <= '3.13' and sys_platform == 'darwin' and platform_machine == 'x86_64' \
--hash=sha256:55c19b9d3f33a6fc59f644aa5a21fba02639ccdd776cb4a9b5526625f57839ff \
--hash=sha256:30b2f52cb50d74734af2f477c2533a7a583e3bb7b2c8acdeb361ee77d940577a \
--hash=sha256:ee19c163a8fdf0839d4c18b88a5fbfb4e731ba7c437416d3e5483e570bb764e4 \
@@ -52,35 +71,68 @@ pytest==8.3.3 \
--hash=sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2
pytest-xdist==3.6.1 \
--hash=sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7
scipy==1.14.1; python_version >= '3.10' \
--hash=sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84 \
--hash=sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e \
--hash=sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d \
--hash=sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e \
--hash=sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73 \
--hash=sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e \
--hash=sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79 \
--hash=sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f \
--hash=sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066 \
--hash=sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310 \
--hash=sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc \
scipy==1.17.0; python_version >= '3.13' \
--hash=sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73 \
--hash=sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00 \
--hash=sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209 \
--hash=sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1 \
--hash=sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269 \
--hash=sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088 \
--hash=sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1 \
--hash=sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf \
--hash=sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061 \
--hash=sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e \
--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:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07 \
--hash=sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675 \
--hash=sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d \
--hash=sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f \
--hash=sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310 \
--hash=sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617 \
--hash=sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d \
--hash=sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94 \
--hash=sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2 \
--hash=sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37 \
--hash=sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad \
--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: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:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389
--hash=sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc \
--hash=sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1 \
--hash=sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2
setuptools==78.1.1 \
--hash=sha256:c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561 \
--hash=sha256:fcc17fd9cd898242f6b4adfaca46137a9edef687f43e6f78469692a5e70d851d
@@ -100,56 +152,103 @@ zipp==3.21.0 \
--hash=sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931
# Transitive dependencies of jax and jaxlib
ml-dtypes==0.5.0 \
--hash=sha256:cb5cc7b25acabd384f75bbd78892d0c724943f3e2e1986254665a1aa10982e07 \
--hash=sha256:54415257f00eb44fbcc807454efac3356f75644f1cbfc2d4e5522a72ae1dacab \
--hash=sha256:e04fde367b2fe901b1d47234426fe8819909bd1dd862a5adb630f27789c20599 \
--hash=sha256:d3b3db9990c3840986a0e70524e122cfa32b91139c3653df76121ba7776e015f \
--hash=sha256:afa08343069874a30812871d639f9c02b4158ace065601406a493a8511180c02 \
ml-dtypes==0.5.4; python_version >= '3.13' \
--hash=sha256:0d2ffd05a2575b1519dc928c0b93c06339eb67173ff53acb00724502cda231cf \
--hash=sha256:14a4fd3228af936461db66faccef6e4f41c1d82fcc30e9f8d58a08916b1d811f \
--hash=sha256:2314892cdc3fcf05e373d76d72aaa15fda9fb98625effa73c1d646f331fcecb7 \
--hash=sha256:2b857d3af6ac0d39db1de7c706e69c7f9791627209c3d6dedbfca8c7e5faec22 \
--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: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:8c32138975797e681eb175996d64356bcfa124bdbb6a70460b9768c2b35a6fa4 \
--hash=sha256:7ee9c320bb0f9ffdf9f6fa6a696ef2e005d1f66438d6f1c1457338e00a02e8cf \
--hash=sha256:a03fc861b86cc586728e3d093ba37f0cc05e65330c3ebd7688e7bae8290f8859 \
--hash=sha256:099e09edd54e676903b4538f3815b5ab96f5b119690514602d96bfdb67172cbe \
--hash=sha256:5f2b59233a0dbb6a560b3137ed6125433289ccba2f8d9c3695a52423a369ed15
numpy==2.1.3; python_version >= '3.10' \
--hash=sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed \
--hash=sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56 \
--hash=sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43 \
--hash=sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe \
--hash=sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57 \
--hash=sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598 \
--hash=sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f \
--hash=sha256:afa08343069874a30812871d639f9c02b4158ace065601406a493a8511180c02 \
--hash=sha256:c7a9152f5876fef565516aa5dd1dccd6fc298a5891b2467973905103eb5c7856 \
--hash=sha256:d4b1a70a3e5219790d6b55b9507606fc4e02911d1497d16c18dd721eb7efe7d0 \
--hash=sha256:dc74fd9995513d33eac63d64e436240f5494ec74d522a9f0920194942fc3d2d7
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:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0 \
--hash=sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a \
--hash=sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b \
--hash=sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512 \
--hash=sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564 \
--hash=sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8 \
--hash=sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958 \
--hash=sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e \
--hash=sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2 \
--hash=sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b \
--hash=sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a \
--hash=sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09 \
--hash=sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9 \
--hash=sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41 \
--hash=sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0 \
--hash=sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee \
--hash=sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b \
--hash=sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4 \
--hash=sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d \
--hash=sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0 \
--hash=sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098 \
--hash=sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3 \
--hash=sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f \
--hash=sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9 \
--hash=sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd \
--hash=sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a \
--hash=sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098 \
--hash=sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1 \
--hash=sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512 \
--hash=sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09 \
--hash=sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc \
--hash=sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8 \
--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 \
--hash=sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd
+36 -1
View File
@@ -15,7 +15,42 @@ glfw==2.9.0 \
--hash=sha256:fcc430cb21984afba74945b7df38a5e1a02b36c0b4a2a2bab42b4a26d7cc51d6 \
--hash=sha256:aef5b555673b9555216e4cd7bc0bdbbb9983f66c620a85ba7310cfcfda5cd38c \
--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:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56 \
--hash=sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43 \
+92 -36
View File
@@ -1,39 +1,95 @@
pillow==10.4.0 \
--hash=sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea \
--hash=sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc \
--hash=sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0 \
--hash=sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be \
--hash=sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70 \
--hash=sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb \
--hash=sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3 \
--hash=sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a \
--hash=sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a \
--hash=sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef \
--hash=sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca \
--hash=sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80 \
--hash=sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597 \
--hash=sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94 \
--hash=sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91 \
--hash=sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319 \
--hash=sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe \
--hash=sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6 \
--hash=sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3 \
--hash=sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be \
--hash=sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c \
--hash=sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141 \
--hash=sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc \
--hash=sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b \
--hash=sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f \
--hash=sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856 \
--hash=sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d \
--hash=sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e \
--hash=sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5 \
--hash=sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c \
--hash=sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b \
--hash=sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126 \
--hash=sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd \
--hash=sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b \
--hash=sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d
pillow==12.1.0 \
--hash=sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d \
--hash=sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc \
--hash=sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84 \
--hash=sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de \
--hash=sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0 \
--hash=sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef \
--hash=sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4 \
--hash=sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82 \
--hash=sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9 \
--hash=sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030 \
--hash=sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0 \
--hash=sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18 \
--hash=sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a \
--hash=sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef \
--hash=sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b \
--hash=sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6 \
--hash=sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179 \
--hash=sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e \
--hash=sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72 \
--hash=sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64 \
--hash=sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451 \
--hash=sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd \
--hash=sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924 \
--hash=sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616 \
--hash=sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a \
--hash=sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94 \
--hash=sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc \
--hash=sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8 \
--hash=sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9 \
--hash=sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91 \
--hash=sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a \
--hash=sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c \
--hash=sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670 \
--hash=sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea \
--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') \
--hash=sha256:b25bde521bb65497b8bb882e4dd0de03d111dab4937c941ff4ceea6238933d5b \
--hash=sha256:a0416e3f5bc120977028d82dda38bd652478042c228d9a7d053f736bb79cde96 \
+3 -3
View File
@@ -86,7 +86,7 @@ if(NOT TARGET mujoco)
if(MUJOCO_FRAMEWORK)
message("MuJoCo framework is at ${MUJOCO_FRAMEWORK}/mujoco.framework")
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})
endif()
@@ -94,7 +94,7 @@ if(NOT TARGET mujoco)
if(NOT MUJOCO_FRAMEWORK)
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)
message("MuJoCo is at ${MUJOCO_LIBRARY}")
@@ -193,7 +193,7 @@ findorfetch(
GIT_REPO
https://github.com/pybind/pybind11
GIT_TAG
d0f1a2168f3335426f544171a3463c36edbd5cc3 # v3.0.3
c7fb32eea8c92bebeea9f0735041a72aa20c75f5 # v3.0.4
TARGETS
pybind11::pybind11_headers
EXCLUDE_FROM_ALL
+4 -4
View File
@@ -7,13 +7,13 @@
<key>CFBundleIdentifier</key>
<string>org.mujoco.mjpython</string>
<key>CFBundleVersion</key>
<string>3.7.1</string>
<string>3.8.1</string>
<key>CFBundleGetInfoString</key>
<string>3.7.1</string>
<string>3.8.1</string>
<key>CFBundleLongVersionString</key>
<string>3.7.1</string>
<string>3.8.1</string>
<key>CFBundleShortVersionString</key>
<string>3.7.1</string>
<string>3.8.1</string>
<key>CFBundleExecutable</key>
<string>mjpython</string>
<key>CFBundleIconFile</key>
+4 -3
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "mujoco"
version = "3.7.1"
version = "3.8.1"
authors = [
{name = "Google DeepMind", email = "mujoco@deepmind.com"},
]
@@ -21,6 +21,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering",
]
dependencies = [
@@ -34,9 +35,9 @@ dynamic = ["readme", "scripts"]
[project.urls]
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"
Changelog = "https://mujoco.readthedocs.io/en/3.7.1/changelog.html"
Changelog = "https://mujoco.readthedocs.io/en/3.8.1/changelog.html"
[tool.setuptools]
include-package-data = false
+1 -1
View File
@@ -24,7 +24,7 @@ set(MSVC_INCREMENTAL_DEFAULT ON)
project(
mujoco_samples
VERSION 3.7.1
VERSION 3.8.1
DESCRIPTION "MuJoCo samples binaries"
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_REQUIRED ON)
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
if(NOT CMAKE_CONFIGURATION_TYPES)
+1 -1
View File
@@ -29,7 +29,7 @@ set(MUJOCO_DEP_VERSION_lodepng
project(
mujoco_simulate
VERSION 3.7.1
VERSION 3.8.1
DESCRIPTION "MuJoCo simulate binaries"
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_REQUIRED ON)
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
if(NOT CMAKE_CONFIGURATION_TYPES)
+2 -2
View File
@@ -43,8 +43,8 @@
//-------------------------- Constants -------------------------------------------------------------
#define mjVERSION 3007001
#define mjVERSIONSTRING "3.7.1"
#define mjVERSION 3008001
#define mjVERSIONSTRING "3.8.1"
// names of disable flags
const char* mjDISABLESTRING[mjNDISABLE] = {
+38 -24
View File
@@ -32,10 +32,6 @@ target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
filament/filament_context.h
filament/filament_platform_factory.cc
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.h
filament/material.cc
@@ -44,8 +40,6 @@ target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
filament/math_util.h
filament/mesh.cc
filament/mesh.h
filament/model_objects.cc
filament/model_objects.h
filament/model_util.h
filament/object_manager.cc
filament/object_manager.h
@@ -53,14 +47,22 @@ target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
filament/render_target.h
filament/renderable.cc
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.h
filament/texture.cc
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)
target_sources(${MUJOCO_FILAMENT_TARGET_NAME}
@@ -127,20 +129,32 @@ foreach(MATERIAL_FILE ${MATERIAL_FILES})
set(INPUT_FILE "${ASSETS_DIR}/${MATERIAL_FILE}")
set(OUTPUT_FILE "${OUTPUT_ASSETS_DIR}/${MATERIAL_NAME}.filamat")
add_custom_command(
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}"
)
if(CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
set(PRECOMPILED_FILE "${MUJOCO_NATIVE_BUILD_DIR}/src/experimental/filament/assets/${MATERIAL_NAME}.filamat")
add_custom_command(
OUTPUT ${OUTPUT_FILE}
COMMAND ${CMAKE_COMMAND} -E copy
${PRECOMPILED_FILE}
${OUTPUT_FILE}
DEPENDS ${PRECOMPILED_FILE}
COMMENT "Copying precompiled material ${MATERIAL_NAME}.filamat"
)
else()
add_custom_command(
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})
endforeach()
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "experimental/filament/filament/imgui_bridge.h"
#include "experimental/filament/compat/imgui_bridge.h"
#include <cstddef>
#include <cstdint>
@@ -23,12 +23,11 @@
#include <imgui.h>
#include <math/mat3.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/mesh.h"
#include "experimental/filament/filament/renderable.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/texture.h"
@@ -37,10 +36,25 @@ namespace mujoco {
using filament::math::float3;
using filament::math::mat3f;
ImguiBridge::ImguiBridge(ObjectManager* object_mgr, SceneView* scene_view)
: object_mgr_(object_mgr), scene_view_(scene_view) {}
ImguiBridge::ImguiBridge(ObjectManager* object_mgr) : object_mgr_(object_mgr) {
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,
int width, int height, int bpp) {
@@ -67,8 +81,8 @@ uintptr_t ImguiBridge::UploadImage(uintptr_t tex_id, const uint8_t* pixels,
// new texture.
if (texture == nullptr || texture->GetWidth() != width ||
texture->GetHeight() != height) {
TextureConfig config;
DefaultTextureConfig(&config);
mjrTextureConfig config;
mjr_defaultTextureConfig(&config);
config.width = width;
config.height = height;
config.target = mjTEXTURE_2D;
@@ -84,8 +98,8 @@ uintptr_t ImguiBridge::UploadImage(uintptr_t tex_id, const uint8_t* pixels,
const auto callback =
+[](void* user) { delete[] reinterpret_cast<std::byte*>(user); };
TextureData texture_data;
DefaultTextureData(&texture_data);
mjrTextureData texture_data;
mjr_defaultTextureData(&texture_data);
texture_data.bytes = bytes;
texture_data.nbytes = num_bytes;
texture_data.user_data = bytes;
@@ -101,8 +115,8 @@ void ImguiBridge::CreateTexture(ImTextureData* data) {
mju_error("Unsupported texture format.");
}
TextureConfig config;
DefaultTextureConfig(&config);
mjrTextureConfig config;
mjr_defaultTextureConfig(&config);
config.width = data->Width;
config.height = data->Height;
config.target = mjTEXTURE_2D;
@@ -122,8 +136,8 @@ void ImguiBridge::UpdateTexture(ImTextureData* data) {
mju_error("Texture not found: %llu", data->TexID);
}
TextureData texture_data;
DefaultTextureData(&texture_data);
mjrTextureData texture_data;
mjr_defaultTextureData(&texture_data);
texture_data.bytes = data->GetPixels();
texture_data.nbytes = data->Width * data->Height * 4;
texture_data.user_data = nullptr;
@@ -179,22 +193,10 @@ void ImguiBridge::Update() {
if (commands->Textures != nullptr) {
for (ImTextureData* tex : *commands->Textures) {
if (tex->Status == ImTextureStatus_OK) {
// 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) {
if (tex->Status == ImTextureStatus_WantCreate) {
CreateTexture(tex);
} else if (tex->Status == ImTextureStatus_WantUpdates) {
if (textures_.find(tex->TexID) == textures_.end()) {
CreateTexture(tex);
} else {
UpdateTexture(tex);
}
UpdateTexture(tex);
} else if (tex->Status == ImTextureStatus_WantDestroy &&
tex->UnusedFrames > 0) {
DestroyTexture(tex);
@@ -212,24 +214,24 @@ void ImguiBridge::Update() {
for (int n = 0; n < commands->CmdListsCount; ++n) {
const ImDrawList* cmds = commands->CmdLists[n];
MeshData data;
DefaultMeshData(&data);
mjrMeshData data;
mjr_defaultMeshData(&data);
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].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].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].bytes = cmds->VtxBuffer.Data + sizeof(float) * 4;
data.interleaved = true;
data.nvertices = cmds->VtxBuffer.Size;
data.nindices = cmds->IdxBuffer.Size;
data.indices = cmds->IdxBuffer.Data;
data.index_type = mjINDEX_TYPE_USHORT;
data.primitive_type = mjPRIM_TYPE_TRIANGLES;
data.index_type = mjINDEX_TYPE_U16;
data.primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
meshes_.push_back(std::make_unique<Mesh>(scene_view_->GetEngine(), data));
const Mesh* mesh = meshes_.back().get();
@@ -242,10 +244,12 @@ void ImguiBridge::Update() {
auto& renderable = renderables_[renderable_index];
renderable->SetMesh(mesh, index_offset, command.ElemCount);
MaterialTextures textures;
mjrMaterialTextures textures;
mjr_defaultMaterialTextures(&textures);
textures.color = textures_[command.GetTexID()].get();
MaterialParams properties;
mjrMaterialParams properties;
mjr_defaultMaterialParams(&properties);
properties.scissor[0] = command.ClipRect.x;
properties.scissor[1] = height - command.ClipRect.w;
properties.scissor[2] = command.ClipRect.z - command.ClipRect.x;
@@ -271,18 +275,18 @@ void ImguiBridge::Update() {
void ImguiBridge::PrepareRenderables(int count) {
while (renderables_.size() < count) {
RenderableParams config;
DefaultRenderableParams(&config);
config.shading_model = ShadingModel::Ux;
mjrRenderableParams params;
mjr_defaultRenderableParams(&params);
params.shading_model = mjSHADING_MODEL_UX;
auto& r = renderables_.emplace_back(
std::make_unique<Renderable>(object_mgr_, config));
std::make_unique<Renderable>(object_mgr_, params));
r->SetCastShadows(false);
r->SetReceiveShadows(false);
r->SetBlendOrder(static_cast<std::uint16_t>(renderables_.size()));
scene_view_->AddToUxScene(r.get());
scene_view_->AddToScene(r.get());
}
while (renderables_.size() > count) {
scene_view_->RemoveFromUxScene(renderables_.back().get());
scene_view_->RemoveFromScene(renderables_.back().get());
renderables_.pop_back();
}
}
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_BRIDGE_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_BRIDGE_H_
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_BRIDGE_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_IMGUI_BRIDGE_H_
#include <cstdint>
#include <memory>
@@ -29,10 +29,10 @@
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 {
public:
ImguiBridge(ObjectManager* object_mgr, SceneView* scene_view);
explicit ImguiBridge(ObjectManager* object_mgr);
~ImguiBridge();
// Prepares the Renderables using data from the current ImGui state. This
@@ -40,6 +40,9 @@ class ImguiBridge {
// synced.
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.
uintptr_t UploadImage(uintptr_t tex_id, const uint8_t* pixels, int width,
int height, int bpp);
@@ -57,7 +60,7 @@ class ImguiBridge {
void DestroyTexture(ImTextureData* data);
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<Mesh>> meshes_;
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
#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
// limitations under the License.
#include "experimental/filament/filament/imgui_editor.h"
#include "experimental/filament/compat/imgui_editor.h"
#include <algorithm>
#include <any>
@@ -34,8 +34,8 @@
#include <math/mathfwd.h>
#include <math/scalar.h>
#include <math/vec3.h>
#include "experimental/filament/compat/scene_bridge.h"
#include "experimental/filament/filament/color_grading_options.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
namespace mujoco {
@@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_IMGUI_EDITOR_H_
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_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 {
@@ -24,4 +24,4 @@ void DrawGui(SceneBridge* scene_bridge);
} // 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
// limitations under the License.
#include "experimental/filament/filament/model_objects.h"
#include "experimental/filament/compat/model_objects.h"
#include <array>
#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) {
if (!IsValidIndex(model, 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;
}
data->primitive_type = mjPRIM_TYPE_TRIANGLES;
data->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
data->nvertices = num_vertices;
data->nindices = data->nvertices;
data->indices = nullptr;
data->index_type = data->nvertices >= std::numeric_limits<uint16_t>::max()
? mjINDEX_TYPE_UINT
: mjINDEX_TYPE_USHORT;
? mjINDEX_TYPE_U32
: mjINDEX_TYPE_U16;
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].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].bytes = builder->orientations.data();
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].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;
}
void UpdateSkinFlexMeshData(MeshData* data, const mjModel* model,
void UpdateSkinFlexmjrMeshData(mjrMeshData* data, const mjModel* model,
const mjvScene* scene, const mjvGeom& geom) {
auto positions = GetPositions(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->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].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].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].bytes = uvs.data();
data->nvertices = positions.size() / 3;
data->nindices = num_indices;
data->indices = indices.data();
data->index_type = mjINDEX_TYPE_UINT;
data->primitive_type = mjPRIM_TYPE_TRIANGLES;
data->index_type = mjINDEX_TYPE_U32;
data->primitive_type = mjMESH_PRIMITIVE_TYPE_TRIANGLES;
data->compute_bounds = true;
data->release_callback = nullptr;
data->user_data = nullptr;
@@ -554,15 +554,15 @@ void ModelObjects::UploadMesh(const mjModel* model, int id) {
meshes_.erase(id);
convex_hulls_.erase(id);
MeshData data;
DefaultMeshData(&data);
UpdateMeshData(&data, model, id, MeshType::kNormal);
mjrMeshData data;
mjr_defaultMeshData(&data);
UpdatemjrMeshData(&data, model, id, MeshType::kNormal);
meshes_[id] = std::make_unique<Mesh>(engine_, data);
if (model->mesh_graphadr[id] >= 0) {
MeshData convex_hull_data;
DefaultMeshData(&convex_hull_data);
UpdateMeshData(&convex_hull_data, model, id, MeshType::kConvexHull);
mjrMeshData convex_hull_data;
mjr_defaultMeshData(&convex_hull_data);
UpdatemjrMeshData(&convex_hull_data, model, id, MeshType::kConvexHull);
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);
}
TextureConfig config;
DefaultTextureConfig(&config);
mjrTextureConfig config;
mjr_defaultTextureConfig(&config);
config.width = model->tex_width[id];
config.height = model->tex_height[id];
config.target = (mjtTexture)model->tex_type[id];
@@ -600,8 +600,8 @@ void ModelObjects::UploadTexture(const mjModel* model, int id) {
}
TextureData payload;
DefaultTextureData(&payload);
mjrTextureData payload;
mjr_defaultTextureData(&payload);
payload.bytes = model->tex_data + model->tex_adr[id];
payload.nbytes =
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);
MeshData data;
DefaultMeshData(&data);
UpdateMeshData(&data, model, id, MeshType::kHeightField);
mjrMeshData data;
mjr_defaultMeshData(&data);
UpdatemjrMeshData(&data, model, id, MeshType::kHeightField);
height_fields_[id] = std::make_unique<Mesh>(engine_, data);
}
void ModelObjects::CreateSkinFlexMesh(const mjvScene* scene, const mjvGeom& geom) {
MeshData data;
DefaultMeshData(&data);
UpdateSkinFlexMeshData(&data, model_, scene, geom);
mjrMeshData data;
mjr_defaultMeshData(&data);
UpdateSkinFlexmjrMeshData(&data, model_, scene, geom);
dynamic_meshes_[geom.objid] = std::make_unique<Mesh>(engine_, data);
}
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_OBJECTS_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MODEL_OBJECTS_H_
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MODEL_OBJECTS_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_MODEL_OBJECTS_H_
#include <array>
#include <memory>
@@ -100,4 +100,4 @@ class ModelObjects {
} // 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
// limitations under the License.
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/compat/scene_bridge.h"
#include <memory>
#include <optional>
@@ -36,16 +36,15 @@
#include <math/vec4.h>
#include <math/TVecHelpers.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/imgui_bridge.h"
#include "experimental/filament/filament/light.h"
#include "experimental/filament/filament/material.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/object_manager.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/texture.h"
@@ -64,8 +63,8 @@ static std::unique_ptr<Texture> CreateFallbackIndirectLightTexture(
std::unique_ptr<ObjectManager::Asset> asset = object_mgr->LoadAsset(filename);
TextureConfig config;
DefaultTextureConfig(&config);
mjrTextureConfig config;
mjr_defaultTextureConfig(&config);
config.width = 1;
config.height = 1;
config.target = mjTEXTURE_CUBE;
@@ -74,9 +73,9 @@ static std::unique_ptr<Texture> CreateFallbackIndirectLightTexture(
auto texture = std::make_unique<Texture>(object_mgr->GetEngine(), config);
TextureData payload;
DefaultTextureData(&payload);
payload.bytes = (void*)asset->GetBytes().data();
mjrTextureData payload;
mjr_defaultTextureData(&payload);
payload.bytes = asset->GetBytes().data();
payload.nbytes = asset->GetBytes().size();
payload.release_callback = +[](void* user_data) {
delete static_cast<ObjectManager::Asset*>(user_data);
@@ -87,9 +86,9 @@ static std::unique_ptr<Texture> CreateFallbackIndirectLightTexture(
return texture;
}
SceneBridge::SceneBridge(ObjectManager* object_mgr, SceneView* scene_view,
const mjModel* model)
: scene_view_(scene_view), object_mgr_(object_mgr) {
SceneBridge::SceneBridge(ObjectManager* object_mgr, const mjModel* model)
: object_mgr_(object_mgr) {
scene_view_ = std::make_unique<SceneView>(object_mgr_->GetEngine());
model_objects_ =
std::make_unique<ModelObjects>(model, object_mgr_->GetEngine());
@@ -180,7 +179,10 @@ SceneBridge::~SceneBridge() {
scene_view_->RemoveFromScene(iter.get());
}
lights_.clear();
if (fallback_ibl_) {
scene_view_->RemoveFromScene(fallback_ibl_.get());
}
fallback_ibl_.reset();
for (auto& iter : renderables_) {
scene_view_->RemoveFromScene(iter.get());
}
@@ -204,7 +206,8 @@ void SceneBridge::SetEnvironmentLight(std::string_view filename,
fallback_ibl_texture_ =
CreateFallbackIndirectLightTexture(object_mgr_, filename);
Light::Params params;
mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE;
params.texture = fallback_ibl_texture_.get();
params.intensity = intensity;
@@ -230,7 +233,8 @@ void SceneBridge::PrepareLights() {
total_light_intensity += model->light_intensity[i];
if (model->light_type[i] == mjLIGHT_IMAGE) {
Light::Params params;
mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE;
params.texture = model_objects_->GetTexture(model->light_texid[i]);
params.intensity = model->light_intensity[i];
@@ -239,11 +243,14 @@ void SceneBridge::PrepareLights() {
lights_.emplace_back(std::move(light_obj));
has_image_based_light = true;
} else {
Light::Params params;
params.color = ReadFloat3(model->light_diffuse);
mjrLightParams params;
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.castshadow = model->light_castshadow[i];
params.bulbradius = model->light_bulbradius[i];
params.cast_shadows = model->light_castshadow[i];
params.bulb_radius = model->light_bulbradius[i];
params.range = model->light_range[i];
params.intensity = model->light_intensity[i];
params.shadow_map_size = default_shadow_map_size_;
@@ -253,10 +260,7 @@ void SceneBridge::PrepareLights() {
}
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());
#endif
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
// assume lights_.back() is always the headlight.
{
Light::Params params;
params.color = float3(0, 0, 0);
mjrLightParams params;
mjr_defaultLightParams(&params);
// 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
// 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
// directional light to move with the camera.
params.type = mjLIGHT_SPOT;
params.castshadow = 0;
params.cast_shadows = 0;
params.intensity = 0.0f;
params.spot_cone_angle = 90.0f;
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());
#endif
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
// oriented to respect mujoco's Z-up convention.
filament::Engine* engine = object_mgr_->GetEngine();
Light::Params params;
mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE;
params.intensity = 10.0f;
fallback_ibl_ = std::make_unique<Light>(engine, params);
@@ -301,7 +303,8 @@ void SceneBridge::PrepareLights() {
// Create a fallback environment light.
fallback_ibl_texture_ = CreateFallbackIndirectLightTexture(object_mgr_);
Light::Params params;
mjrLightParams params;
mjr_defaultLightParams(&params);
params.type = mjLIGHT_IMAGE;
params.texture = fallback_ibl_texture_.get();
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) {
filament::View* view = scene_view_->GetDefaultRenderView();
view->setShadowingEnabled(scene->flags[mjRND_SHADOW] ? true : false);
if (scene->flags[mjRND_SHADOW]) {
scene_view_->EnableShadows();
} else {
scene_view_->DisableShadows();
}
if (scene->flags[mjRND_REFLECTION]) {
scene_view_->EnableReflections();
} else {
scene_view_->DisableReflections();
}
mjtNum hpos[3], hfwd[3];
float headpos[3], gazedir[3];
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_BRIDGE_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_BRIDGE_H_
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_BRIDGE_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_BRIDGE_H_
#include <memory>
#include <optional>
@@ -24,9 +24,8 @@
#include <math/vec3.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/compat/model_objects.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/renderable.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.
class SceneBridge {
public:
SceneBridge(ObjectManager* object_mgr, SceneView* scene_view,
const mjModel* model);
SceneBridge(ObjectManager* object_mgr, const mjModel* model);
~SceneBridge();
// 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 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& operator=(const SceneBridge&) = delete;
@@ -69,7 +68,7 @@ class SceneBridge {
std::optional<filament::math::float3> ClipFromWorld(
const filament::math::float3& pos) const;
SceneView* scene_view_ = nullptr;
std::unique_ptr<SceneView> scene_view_;
ObjectManager* object_mgr_ = nullptr;
std::unique_ptr<ModelObjects> model_objects_;
std::unique_ptr<Light> fallback_ibl_;
@@ -86,4 +85,4 @@ class SceneBridge {
} // 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
// limitations under the License.
#include "experimental/filament/filament/scene_geom_util.h"
#include "experimental/filament/compat/scene_geom_util.h"
#include <cmath>
#include <cstdint>
@@ -22,20 +22,17 @@
#include <filament/Material.h>
#include <filament/RenderableManager.h>
#include <filament/Scene.h>
#include <filament/Texture.h>
#include <filament/TransformManager.h>
#include <math/mat4.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <utils/Entity.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/compat/model_objects.h"
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/math_util.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/renderable.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 bool use_segid_color = scene->flags[mjRND_IDCOLOR];
const bool enable_reflection = scene->flags[mjRND_REFLECTION];
MaterialParams params;
params.color = ReadFloat4(geom.rgba);
mjrMaterialParams params;
mjr_defaultMaterialParams(&params);
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 (IsBehind(headpos, geom.pos, geom.mat)) {
params.color[3] *= 0.3;
@@ -361,8 +361,7 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
params.reflective = false;
} else {
renderable.SetReceiveShadows(true);
params.reflective =
enable_reflection && geom.reflectance > 0 && params.color.a == 1.0f;
params.reflective = geom.reflectance > 0 && params.color[3] == 1.0f;
}
}
renderable.SetLayerMask(geom.category);
@@ -373,7 +372,8 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
renderable.SetWireframe(scene->flags[mjRND_WIREFRAME]);
}
MaterialTextures textures;
mjrMaterialTextures textures;
mjr_defaultMaterialTextures(&textures);
if (geom.matid >= 0) {
textures.color = model_objs->GetTexture(geom.matid, mjTEXROLE_RGB);
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.roughness = model->mat_roughness[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) {
@@ -410,9 +411,9 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
const uint8_t red = (segmentation_color >> 0) & 0xff;
const uint8_t green = (segmentation_color >> 8) & 0xff;
const uint8_t blue = (segmentation_color >> 16) & 0xff;
params.segmentation_color.x = static_cast<float>(red) / 255.0f;
params.segmentation_color.y = static_cast<float>(green) / 255.0f;
params.segmentation_color.z = static_cast<float>(blue) / 255.0f;
params.segmentation_color[0] = static_cast<float>(red) / 255.0f;
params.segmentation_color[1] = static_cast<float>(green) / 255.0f;
params.segmentation_color[2] = static_cast<float>(blue) / 255.0f;
}
// 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
// image is repeated. The `tex_uniform` flag determines if the repetition
// is applied at in object space (false) or in world space (true).
params.uv_scale.x = params.tex_repeat.x;
params.uv_scale.y = params.tex_repeat.y;
params.uv_scale[0] = params.tex_repeat[0];
params.uv_scale[1] = params.tex_repeat[1];
if (geom.dataid >= 0 && geom.type != mjGEOM_PLANE) {
if (geom.size[0] > mjMINVAL) {
params.uv_scale.x /= geom.size[0];
params.uv_scale[0] /= geom.size[0];
}
if (geom.size[1] > mjMINVAL) {
params.uv_scale.y /= geom.size[1];
params.uv_scale[1] /= geom.size[1];
}
}
if (params.tex_uniform) {
if (geom.size[0] > 0) {
params.uv_scale.x *= geom.size[0];
params.uv_scale[0] *= geom.size[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 =
@@ -454,11 +455,11 @@ static void UpdateGeomMaterial(Renderable& renderable, const mjvGeom& geom,
// re-centering in engine_vis_visualize.c.
const float plane_scale = static_cast<float>(mjMAXPLANEGRID) / 2.0f;
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 =
GetPlaneTileSize(model, geom.matid, params.tex_repeat.y);
params.uv_scale.x = 2.0f * plane_scale / tile_size_x;
params.uv_scale.y = 2.0f * plane_scale / tile_size_y;
GetPlaneTileSize(model, geom.matid, params.tex_repeat[1]);
params.uv_scale[0] = 2.0f * plane_scale / tile_size_x;
params.uv_scale[1] = 2.0f * plane_scale / tile_size_y;
}
// 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);
// glTexGenfv(GL_S, GL_OBJECT_PLANE, splane);
// glTexGenfv(GL_T, GL_OBJECT_PLANE, tplane);
params.uv_scale.x = 0.5f * params.uv_scale.x;
params.uv_scale.y = -0.5f * params.uv_scale.y;
params.uv_offset.x = -0.5f;
params.uv_offset.y = -0.5f;
params.uv_scale[0] = 0.5f * params.uv_scale[0];
params.uv_scale[1] = -0.5f * params.uv_scale[1];
params.uv_offset[0] = -0.5f;
params.uv_offset[1] = -0.5f;
} else {
// 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.
if (params.tex_uniform) {
params.uv_scale.x = 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.z = 1.0f / (geom.size[2] ? geom.size[2] : 1.0f);
params.uv_scale[0] = 1.0f / (geom.size[0] ? geom.size[0] : 1.0f);
params.uv_scale[1] = 1.0f / (geom.size[1] ? geom.size[1] : 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(
const mjvGeom& geom, const mjvScene* scene, ObjectManager* object_mgr,
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) {
shading_model = ShadingModel::DecorLines;
shading_model = mjSHADING_MODEL_DECOR_LINES;
} else if (geom.category == mjCAT_DECOR) {
shading_model = ShadingModel::Decor;
shading_model = mjSHADING_MODEL_DECOR;
}
RenderableParams config;
DefaultRenderableParams(&config);
config.shading_model = shading_model;
auto renderable = std::make_unique<Renderable>(object_mgr, config);
mjrRenderableParams params;
mjr_defaultRenderableParams(&params);
params.shading_model = shading_model;
auto renderable = std::make_unique<Renderable>(object_mgr, params);
PrepareGeomMeshes(*renderable, geom, scene, model_objs);
UpdateGeomMaterial(*renderable, geom, scene, model_objs, object_mgr, headpos);
@@ -12,14 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_GEOM_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_SCENE_GEOM_UTIL_H_
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_GEOM_UTIL_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_COMPAT_SCENE_GEOM_UTIL_H_
#include <memory>
#include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/material.h"
#include "experimental/filament/filament/model_objects.h"
#include "experimental/filament/compat/model_objects.h"
#include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/renderable.h"
@@ -32,4 +31,4 @@ std::unique_ptr<Renderable> CreateGeomRenderable(
} // 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;
}
class BuiltinBuilder : MeshData {
class BuiltinBuilder : mjrMeshData {
public:
BuiltinBuilder() { DefaultMeshData(this); }
BuiltinBuilder() { mjr_defaultMeshData(this); }
virtual ~BuiltinBuilder() = default;
template <typename T, typename... Args>
static std::unique_ptr<Mesh> Create(filament::Engine* engine,
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) {
delete static_cast<BuiltinBuilder*>(user_data);
};
@@ -76,13 +76,13 @@ class BuiltinBuilder : MeshData {
return std::make_unique<Mesh>(engine, *mesh_data);
}
MeshData* PrepareMeshData() {
// Update the `MeshData` fields.
mjrMeshData* PrepareMeshData() {
// Update the `mjrMeshData` fields.
nattributes = 2;
attributes[0].usage = mjVERTEX_ATTRIBUTE_POSITION;
attributes[0].usage = mjVERTEX_ATTRIBUTE_USAGE_POSITION;
attributes[0].type = mjVERTEX_ATTRIBUTE_TYPE_FLOAT3;
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].bytes = reinterpret_cast<const void*>(orientations_.data());
nvertices = positions_.size();
@@ -91,9 +91,9 @@ class BuiltinBuilder : MeshData {
nindices = indices_.size();
primitive_type =
primitive_type_ == filament::backend::PrimitiveType::TRIANGLES
? mjPRIM_TYPE_TRIANGLES
: mjPRIM_TYPE_LINES;
index_type = mjINDEX_TYPE_USHORT;
? mjMESH_PRIMITIVE_TYPE_TRIANGLES
: mjMESH_PRIMITIVE_TYPE_LINES;
index_type = mjINDEX_TYPE_U16;
bounds_min[0] = bounds_.getMin().x;
bounds_min[1] = bounds_.getMin().y;
bounds_min[2] = bounds_.getMin().z;
@@ -14,13 +14,11 @@
#include "experimental/filament/filament/filament_context.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <span>
#include <utility>
#include <backend/DriverEnums.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/IndirectLight.h>
@@ -35,19 +33,11 @@
#include <math/vec4.h>
#include <utils/FixedCapacityVector.h>
#include <utils/compiler.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/draw_mode.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/render_target.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
@@ -82,10 +72,6 @@ FilamentContext::FilamentContext(const mjrFilamentConfig* config)
}
FilamentContext::~FilamentContext() {
DestroyRenderTargets();
imgui_bridge_.reset();
scene_bridge_.reset();
scene_view_.reset();
object_manager_.reset();
engine_->destroy(renderer_);
engine_->destroy(window_swap_chain_);
@@ -93,209 +79,116 @@ FilamentContext::~FilamentContext() {
filament::Engine::destroy(engine_);
}
void FilamentContext::Init(const mjModel* model) {
scene_view_ = std::make_unique<SceneView>(engine_);
scene_bridge_ = std::make_unique<SceneBridge>(object_manager_.get(),
scene_view_.get(), model);
imgui_bridge_ =
std::make_unique<ImguiBridge>(object_manager_.get(), scene_view_.get());
FilamentContext::FrameHandle FilamentContext::Render(
std::span<const RenderRequest> requests,
std::span<const ReadPixelsRequest> read_requests) {
if (read_requests.size() > 1) {
mju_error("Only one read request is supported for now.");
}
// 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;
opts.clear = true;
opts.discard = true;
opts.clearColor = ReadElement(model, "filament.clearColor",
filament::math::float4(0, 0, 0, 1));
opts.clearColor = color;
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 {
utils::FixedCapacityVector<filament::Renderer::FrameInfo> frame_info =
renderer_->getFrameInfoHistory(1);
@@ -306,6 +199,4 @@ double FilamentContext::GetFrameRate() const {
return 1.0e9 / static_cast<double>(ns);
}
void FilamentContext::UpdateGui() { DrawGui(scene_bridge_.get()); }
} // namespace mujoco
@@ -15,64 +15,102 @@
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#include <cstddef>
#include <cstdint>
#include <span>
#include <memory>
#include <backend/Platform.h>
#include <filament/Engine.h>
#include <filament/Renderer.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <filament/SwapChain.h>
#include <math/vec4.h>
#include <mujoco/mjvisualize.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/render_target.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/render_context_filament.h"
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 {
public:
explicit FilamentContext(const mjrFilamentConfig* config);
~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,
int height, int bpp);
// The number of bytes in the output buffer. This should match the size of
// 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;
void UpdateGui();
filament::Engine* GetEngine() const { return engine_; }
ObjectManager* GetObjectManager() const { return object_manager_.get(); }
FilamentContext(const FilamentContext&) = delete;
FilamentContext& operator=(const FilamentContext&) = delete;
private:
enum SwapChainType {
kWindowSwapChain,
kOffscreenSwapChain,
};
void PrepareRenderTargets(int width, int height);
void DestroyRenderTargets();
mjrFilamentConfig config_;
filament::Engine* engine_ = nullptr;
@@ -80,19 +118,10 @@ class FilamentContext {
filament::SwapChain* window_swap_chain_ = nullptr;
filament::SwapChain* offscreen_swap_chain_ = nullptr;
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<SceneView> scene_view_;
std::unique_ptr<SceneBridge> scene_bridge_;
std::unique_ptr<ImguiBridge> imgui_bridge_;
int window_width_ = 0;
int window_height_ = 0;
std::uint64_t frame_counter_ = 0;
};
} // namespace mujoco
@@ -31,13 +31,13 @@ static filament::Engine::Backend ResolveBackend(int graphics_api) {
#endif
switch (graphics_api) {
case mjGFX_DEFAULT:
case mjGRAPHICS_API_DEFAULT:
// Use the default based on the platform above.
break;
case mjGFX_OPENGL:
case mjGRAPHICS_API_OPENGL:
backend = filament::Engine::Backend::OPENGL;
break;
case mjGFX_VULKAN:
case mjGRAPHICS_API_VULKAN:
backend = filament::Engine::Backend::VULKAN;
break;
default:
+24 -5
View File
@@ -25,6 +25,7 @@
#include <utils/Entity.h>
#include <utils/EntityManager.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/texture.h"
namespace mujoco {
@@ -32,7 +33,22 @@ namespace mujoco {
using filament::math::float3;
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) {
// Filament treats image-based lights (IBLs) as separate objects (i.e.
// 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);
builder.color(params.color);
builder.color(ReadFloat3(params.color));
builder.intensityCandela(params.intensity);
builder.castShadows(params.castshadow);
builder.castShadows(params.cast_shadows);
if (type == filament::LightManager::Type::FOCUSED_SPOT) {
builder.spotLightCone(0,
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.shadowCascades =
type == filament::LightManager::Type::DIRECTIONAL ? 4 : 1;
opts.shadowBulbRadius = params.bulbradius;
opts.shadowBulbRadius = params.bulb_radius;
opts.mapSize = params.shadow_map_size;
if (params.vsm_blur_width > 0.0f) {
opts.vsm.elvsm = true;
@@ -141,7 +157,10 @@ void Light::SetTransform(filament::math::float3 position,
void Light::SetColor(const filament::math::float3& color) {
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();
const filament::LightManager::Instance li = lm.getInstance(entity_);
lm.setColor(li, color);
+30 -26
View File
@@ -24,34 +24,38 @@
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.
class Light {
public:
// Configuration parameters for a light.
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(filament::Engine* engine, const mjrLightParams& params);
~Light() noexcept;
Light(const Light&) = delete;
@@ -85,7 +89,7 @@ class Light {
filament::IndirectLight* ibl_ = nullptr;
utils::Entity entity_;
bool enabled_ = true;
Params params_;
mjrLightParams params_;
};
} // namespace mujoco
+45 -6
View File
@@ -14,20 +14,59 @@
#include "experimental/filament/filament/material.h"
#include <array>
#include <filament/Color.h>
#include <filament/Material.h>
#include <filament/MaterialInstance.h>
#include <filament/RenderableManager.h>
#include <filament/TextureSampler.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/math_util.h"
#include "experimental/filament/filament/texture.h"
#include "experimental/filament/filament/object_manager.h"
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,
const MaterialParams& params,
const MaterialTextures& textures,
const mjrMaterialParams& params,
const mjrMaterialTextures& textures,
ObjectManager* object_mgr) {
if (params.scissor[2] != 0 && params.scissor[3] != 0) {
instance->setScissor(params.scissor[0], params.scissor[1],
@@ -37,11 +76,11 @@ void UpdateMaterialInstance(filament::MaterialInstance* instance,
const filament::Material* material = instance->getMaterial();
if (material->hasParameter("BaseColorFactor")) {
instance->setParameter("BaseColorFactor", filament::RgbaType::sRGB,
params.color);
ReadFloat4(params.color));
}
if (material->hasParameter("SegmentationColor")) {
instance->setParameter("SegmentationColor", filament::RgbaType::LINEAR,
params.segmentation_color);
ReadFloat4(params.segmentation_color));
}
if (material->hasParameter("EmissiveFactor")) {
instance->setParameter("EmissiveFactor", params.emissive);
@@ -61,10 +100,10 @@ void UpdateMaterialInstance(filament::MaterialInstance* instance,
params.roughness >= 0 ? params.roughness : 1.0f);
}
if (material->hasParameter("UvScale")) {
instance->setParameter("UvScale", params.uv_scale);
instance->setParameter("UvScale", ReadFloat3(params.uv_scale));
}
if (material->hasParameter("UvOffset")) {
instance->setParameter("UvOffset", params.uv_offset);
instance->setParameter("UvOffset", ReadFloat3(params.uv_offset));
}
if (material->hasParameter("Reflectance")) {
instance->setParameter("Reflectance", params.reflectance);
+31 -29
View File
@@ -17,49 +17,51 @@
#include <filament/Engine.h>
#include <filament/MaterialInstance.h>
#include <math/vec2.h>
#include <math/vec3.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/texture.h"
#include "experimental/filament/filament/object_manager.h"
namespace mujoco {
// The textures that can be assigned to the drawable's material.
struct MaterialTextures {
const Texture* color = nullptr;
const Texture* normal = nullptr;
const Texture* metallic = nullptr;
const Texture* roughness = nullptr;
const Texture* occlusion = nullptr;
const Texture* orm = nullptr;
const Texture* emissive = nullptr;
const Texture* reflection = nullptr;
struct mjrMaterialTextures {
const Texture* color;
const Texture* normal;
const Texture* metallic;
const Texture* roughness;
const Texture* occlusion;
const Texture* orm;
const Texture* emissive;
const Texture* reflection;
};
void mjr_defaultMaterialTextures(mjrMaterialTextures* textures);
// The parameters that can be applied to the drawable's material.
struct MaterialParams {
filament::math::float4 color = {1, 1, 1, 1};
filament::math::float4 segmentation_color = {1, 1, 1, 1};
filament::math::float2 tex_repeat = {1, 1};
filament::math::float3 uv_scale = {1, 1, 1};
filament::math::float3 uv_offset = {0, 0, 0};
filament::math::float4 scissor = {0, 0, 0, 0};
float specular = -1.0f;
float glossiness = -1.0f;
float metallic = -1.0f;
float roughness = -1.0f;
float emissive = -1.0f;
float reflectance = 0.0f;
bool tex_uniform = false;
bool reflective = false;
struct mjrMaterialParams {
float color[4];
float segmentation_color[4];
float tex_repeat[2];
float uv_scale[3];
float uv_offset[3];
float scissor[4];
float specular;
float glossiness;
float metallic;
float roughness;
float emissive;
float reflectance;
mjtByte tex_uniform;
mjtByte reflective;
};
void mjr_defaultMaterialParams(mjrMaterialParams* params);
// Updates the material instances based on the currently set parameters and
// textures.
void UpdateMaterialInstance(filament::MaterialInstance* instance,
const MaterialParams& params,
const MaterialTextures& textures,
const mjrMaterialParams& params,
const mjrMaterialTextures& textures,
ObjectManager* object_mgr);
} // namespace mujoco
+69 -50
View File
@@ -18,6 +18,8 @@
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <mutex>
#include <span>
#include <utility>
@@ -37,17 +39,17 @@ namespace mujoco {
using filament::math::float3;
using filament::math::float4;
static filament::VertexAttribute GetUsage(const VertexAttribute& attrib) {
static filament::VertexAttribute GetUsage(const mjrVertexAttribute& attrib) {
switch (attrib.usage) {
case mjVERTEX_ATTRIBUTE_POSITION:
case mjVERTEX_ATTRIBUTE_USAGE_POSITION:
return filament::VertexAttribute::POSITION;
case mjVERTEX_ATTRIBUTE_NORMAL:
case mjVERTEX_ATTRIBUTE_USAGE_NORMAL:
return filament::VertexAttribute::TANGENTS;
case mjVERTEX_ATTRIBUTE_TANGENTS:
case mjVERTEX_ATTRIBUTE_USAGE_TANGENTS:
return filament::VertexAttribute::TANGENTS;
case mjVERTEX_ATTRIBUTE_UV:
case mjVERTEX_ATTRIBUTE_USAGE_UV:
return filament::VertexAttribute::UV0;
case mjVERTEX_ATTRIBUTE_COLOR:
case mjVERTEX_ATTRIBUTE_USAGE_COLOR:
return filament::VertexAttribute::COLOR;
default:
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(
const VertexAttribute& attrib) {
const mjrVertexAttribute& attrib) {
switch (attrib.type) {
case mjVERTEX_ATTRIBUTE_TYPE_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) {
case mjVERTEX_ATTRIBUTE_TYPE_FLOAT2:
return sizeof(float) * 2;
@@ -99,21 +101,21 @@ int FillSequence(std::byte* buffer, std::size_t num_bytes) {
return num;
}
// Initializes the MeshData to default values.
void DefaultMeshData(MeshData* data) {
std::memset(data, 0, sizeof(MeshData));
// Initializes the mjrMeshData to default values.
void mjr_defaultMeshData(mjrMeshData* data) {
std::memset(data, 0, sizeof(mjrMeshData));
}
Mesh::Mesh(filament::Engine* engine, const MeshData& data)
: engine_(engine) {
type_ = data.primitive_type == mjPRIM_TYPE_TRIANGLES
Mesh::Mesh(filament::Engine* engine, const mjrMeshData& data)
: engine_(engine), shared_state_(std::make_shared<SharedState>()) {
type_ = data.primitive_type == mjMESH_PRIMITIVE_TYPE_TRIANGLES
? filament::RenderableManager::PrimitiveType::TRIANGLES
: filament::RenderableManager::PrimitiveType::LINES;
// If the user has provided a release callback, then we need to ensure we
// call is when filament is done with the mesh data.
if (data.release_callback) {
release_callbacks_.push_back([=]() {
shared_state_->callbacks.push_back([=]() {
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) {
mju_error("MeshData has no vertices.");
mju_error("mjrMeshData has no vertices.");
}
// The filament BufferDescriptor callback for releasing the memory. We assume
// that ReleaseResources() can be called multiple times, so we assign this
// callback to each buffer descriptor.
// The filament BufferDescriptor callback for releasing the memory.
// We pass a heap-allocated shared_ptr to the shared state as the user data.
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
// validation and processing.
const VertexAttribute* positions = nullptr;
const VertexAttribute* normals = nullptr;
const VertexAttribute* tangents = nullptr;
const mjrVertexAttribute* positions = nullptr;
const mjrVertexAttribute* normals = nullptr;
const mjrVertexAttribute* tangents = nullptr;
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];
} else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_NORMAL) {
} else if (data.attributes[i].usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
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];
}
}
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.");
}
if (normals && tangents) {
mju_error("MeshData has both normals and tangents.");
mju_error("mjrMeshData has both normals and tangents.");
}
if (normals && data.interleaved) {
// 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.
int offset = 0;
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);
filament::VertexBuffer::AttributeType type = GetType(attrib);
vb_builder.attribute(usage, 0, type, offset, total_vertex_size);
@@ -206,16 +218,17 @@ void Mesh::BuildVertexBuffer(const MeshData& data) {
attributes_[i] = usage;
}
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 {
// For a non-interleaved vertex buffer, we assign a separate buffer to each
// attribute.
vb_builder.bufferCount(data.nattributes);
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);
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.
type = filament::VertexBuffer::AttributeType::FLOAT4;
}
@@ -230,25 +243,26 @@ void Mesh::BuildVertexBuffer(const MeshData& data) {
// Assign the individual data buffers.
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;
size_t nbytes = data.nvertices * VertexAttributeTypeSize(attrib);
if (attrib.usage == mjVERTEX_ATTRIBUTE_NORMAL) {
if (attrib.usage == mjVERTEX_ATTRIBUTE_USAGE_NORMAL) {
// Replace normals with orientations.
nbytes = data.nvertices * sizeof(float4);
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) {
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(uint32_t);
const int num_bytes = data.nindices * element_size;
@@ -259,11 +273,11 @@ void Mesh::BuildIndexBuffer(const MeshData& data) {
const void* indices = data.indices;
if (indices == nullptr) {
std::byte* sequence = new std::byte[num_bytes];
release_callbacks_.push_back([=]() {
shared_state_->callbacks.push_back([=]() {
delete[] sequence;
});
if (data.index_type == mjINDEX_TYPE_USHORT) {
if (data.index_type == mjINDEX_TYPE_U16) {
FillSequence<uint16_t>(sequence, num_bytes);
} else {
FillSequence<uint32_t>(sequence, num_bytes);
@@ -273,7 +287,7 @@ void Mesh::BuildIndexBuffer(const MeshData& data) {
filament::IndexBuffer::Builder ib_builder;
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::UINT);
index_buffer_ = ib_builder.build(*engine_);
@@ -283,9 +297,10 @@ void Mesh::BuildIndexBuffer(const MeshData& data) {
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];
release_callbacks_.push_back([=]() {
shared_state_->callbacks.push_back([=]() {
delete[] orientations;
});
const float* normals_ptr = reinterpret_cast<const float*>(normals.bytes);
@@ -295,7 +310,7 @@ float4* Mesh::BuildOrientationsFromNormals(int nvertices, const VertexAttribute&
return orientations;
}
void Mesh::UpdateBounds(const MeshData& data) {
void Mesh::UpdateBounds(const mjrMeshData& data) {
float3 bounds_min = ReadFloat3(data.bounds_min);
float3 bounds_max = ReadFloat3(data.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_max = float3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_POSITION) {
mju_error("MeshData has no positions.");
if (data.attributes[0].usage != mjVERTEX_ATTRIBUTE_USAGE_POSITION) {
mju_error("mjrMeshData has no positions.");
}
const float* positions =
reinterpret_cast<const float*>(data.attributes[0].bytes);
@@ -320,10 +335,14 @@ void Mesh::UpdateBounds(const MeshData& data) {
}
void Mesh::ReleaseResources() {
for (const auto& callback : release_callbacks_) {
callback();
std::lock_guard<std::mutex> lock(shared_state_->mutex);
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 {
+46 -39
View File
@@ -19,6 +19,7 @@
#include <cstddef>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <vector>
@@ -29,66 +30,67 @@
#include <filament/RenderableManager.h>
#include <filament/VertexBuffer.h>
#include <math/vec4.h>
#include <mujoco/mujoco.h>
// Functions for creating filament vertex and index buffers.
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.
typedef enum mjtIndexType_ {
mjINDEX_TYPE_USHORT = 0,
mjINDEX_TYPE_UINT = 1,
} mjtIndexType;
typedef enum mjrIndexType_ {
mjINDEX_TYPE_U16 = 0,
mjINDEX_TYPE_U32 = 1,
} mjrIndexType;
// The type of primitive to be drawn by vertex data.
typedef enum mjtMeshPrimitiveType_ {
mjPRIM_TYPE_TRIANGLES = 0,
mjPRIM_TYPE_LINES = 1,
} mjtMeshPrimitiveType;
typedef enum mjrMeshPrimitiveType_ {
mjMESH_PRIMITIVE_TYPE_TRIANGLES = 0,
mjMESH_PRIMITIVE_TYPE_LINES = 1,
} mjrMeshPrimitiveType;
// The usage/purpose of an attribute of a vertex.
typedef enum mjtVertexAttributeUsage_ {
mjVERTEX_ATTRIBUTE_POSITION = 0,
mjVERTEX_ATTRIBUTE_NORMAL = 1,
mjVERTEX_ATTRIBUTE_TANGENTS = 2,
mjVERTEX_ATTRIBUTE_UV = 3,
mjVERTEX_ATTRIBUTE_COLOR = 4,
} mjtVertexAttributeUsage;
typedef enum mjrVertexAttributeUsage_ {
mjVERTEX_ATTRIBUTE_USAGE_POSITION = 0,
mjVERTEX_ATTRIBUTE_USAGE_NORMAL = 1,
mjVERTEX_ATTRIBUTE_USAGE_TANGENTS = 2,
mjVERTEX_ATTRIBUTE_USAGE_UV = 3,
mjVERTEX_ATTRIBUTE_USAGE_COLOR = 4,
} mjrVertexAttributeUsage;
// The data format of an attribute of a vertex.
typedef enum mjtVertexAttributeType_ {
typedef enum mjrVertexAttributeType_ {
mjVERTEX_ATTRIBUTE_TYPE_FLOAT2 = 0,
mjVERTEX_ATTRIBUTE_TYPE_FLOAT3 = 1,
mjVERTEX_ATTRIBUTE_TYPE_FLOAT4 = 2,
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.
struct VertexAttribute {
struct mjrVertexAttribute {
// The data for the attribute.
const void* bytes;
// The usage/purpose of the attribute.
mjtVertexAttributeUsage usage;
mjrVertexAttributeUsage usage;
// The data format of the attribute.
mjtVertexAttributeType type;
mjrVertexAttributeType type;
};
// The binary contents of a mesh.
struct MeshData {
struct mjrMeshData {
// The number of vertices in the mesh. Each of the vertex arrays below is
// assumed to have this number of elements.
size_t nvertices;
mjtSize nvertices;
// The number of attributes for each vertex in the mesh.
int nattributes;
// Information about each attribute of a vertex in the mesh. See `interleaved`
// for more details.
VertexAttribute attributes[kMaxVertexAttributes];
mjrVertexAttribute attributes[mjMAX_VERTEX_ATTRIBUTES];
// 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
// 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
// this number of elements.
size_t nindices;
mjtSize nindices;
// The indices of the mesh, stored as either ushort or uint depending on the
// index type.
const void* indices;
// 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.
mjtMeshPrimitiveType primitive_type;
mjrMeshPrimitiveType primitive_type;
// 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
// that the bounds are not set (i.e. the bounds is empty).
@@ -133,13 +135,13 @@ struct MeshData {
};
// 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.
class Mesh {
public:
// Creates a Mesh from the given MeshData.
Mesh(filament::Engine* engine, const MeshData& data);
Mesh(filament::Engine* engine, const mjrMeshData& data);
~Mesh();
@@ -165,12 +167,12 @@ class Mesh {
Mesh& operator=(const Mesh&) = delete;
private:
void BuildVertexBuffer(const MeshData& data);
void BuildIndexBuffer(const MeshData& data);
void UpdateBounds(const MeshData& data);
void BuildVertexBuffer(const mjrMeshData& data);
void BuildIndexBuffer(const mjrMeshData& data);
void UpdateBounds(const mjrMeshData& data);
filament::math::float4* BuildOrientationsFromNormals(
int nvertices, const VertexAttribute& normals);
int nvertices, const mjrVertexAttribute& normals);
void ReleaseResources();
@@ -180,8 +182,13 @@ class Mesh {
filament::RenderableManager::PrimitiveType type_ =
filament::RenderableManager::PrimitiveType::TRIANGLES;
std::optional<filament::Box> bounds_;
std::vector<std::function<void()>> release_callbacks_;
std::array<filament::VertexAttribute, kMaxVertexAttributes> attributes_;
struct SharedState {
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;
};
@@ -30,13 +30,13 @@
namespace mujoco {
void DefaultRenderTargetConfig(RenderTargetConfig* config) {
void mjr_defaultRenderTargetConfig(mjrRenderTargetConfig* config) {
config->color_format = mjPIXEL_FORMAT_RGBA8;
config->depth_format = mjPIXEL_FORMAT_DEPTH32F;
}
RenderTarget::RenderTarget(filament::Engine* engine,
const RenderTargetConfig& config)
const mjrRenderTargetConfig& config)
: engine_(engine), config_(config) {}
RenderTarget::~RenderTarget() noexcept {
@@ -51,8 +51,8 @@ void RenderTarget::Prepare(int width, int height) {
width_ = width;
height_ = height;
TextureConfig color_config;
DefaultTextureConfig(&color_config);
mjrTextureConfig color_config;
mjr_defaultTextureConfig(&color_config);
Texture::InternalFlags color_flags;
color_config.width = width;
color_config.height = height;
@@ -63,8 +63,8 @@ void RenderTarget::Prepare(int width, int height) {
color_flags.color_attachment = true;
color_texture_ = std::make_unique<Texture>(engine_, color_config, color_flags);
TextureConfig depth_config;
DefaultTextureConfig(&depth_config);
mjrTextureConfig depth_config;
mjr_defaultTextureConfig(&depth_config);
Texture::InternalFlags depth_flags;
depth_config.width = width;
depth_config.height = height;
@@ -26,20 +26,20 @@
namespace mujoco {
// Defines the basic properties of a render target.
struct RenderTargetConfig {
mjtPixelFormat color_format;
mjtPixelFormat depth_format;
struct mjrRenderTargetConfig {
mjrPixelFormat color_format;
mjrPixelFormat depth_format;
};
// 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.
class RenderTarget {
public:
// Defines the types of textures to create for the color and depth
// attachments.
RenderTarget(filament::Engine* engine, const RenderTargetConfig& config);
RenderTarget(filament::Engine* engine, const mjrRenderTargetConfig& config);
~RenderTarget() noexcept;
RenderTarget(const RenderTarget&) = delete;
@@ -66,7 +66,7 @@ class RenderTarget {
void Destroy();
filament::Engine* engine_ = nullptr;
RenderTargetConfig config_;
mjrRenderTargetConfig config_;
filament::RenderTarget* render_target_ = nullptr;
std::unique_ptr<Texture> color_texture_ = nullptr;
std::unique_ptr<Texture> depth_texture_ = nullptr;
@@ -36,12 +36,15 @@ namespace mujoco {
using filament::math::mat4f;
void DefaultRenderableParams(RenderableParams* params) {
params->shading_model = ShadingModel::SceneObject;
void mjr_defaultRenderableParams(mjrRenderableParams* params) {
params->shading_model = mjSHADING_MODEL_SCENE_OBJECT;
}
Renderable::Renderable(ObjectManager* object_mgr, const RenderableParams& params)
: object_mgr_(object_mgr), params_(params) {}
Renderable::Renderable(ObjectManager* object_mgr, const mjrRenderableParams& params)
: object_mgr_(object_mgr), params_(params) {
mjr_defaultMaterialParams(&material_params_);
mjr_defaultMaterialTextures(&material_textures_);
}
Renderable::~Renderable() noexcept {
filament::Engine* engine = GetEngine();
@@ -198,13 +201,13 @@ void Renderable::RemoveFromScene(filament::Scene* scene) {
assigned_scene_ = nullptr;
}
void Renderable::UpdateMaterial(const MaterialParams& params,
const MaterialTextures& textures) {
void Renderable::UpdateMaterial(const mjrMaterialParams& params,
const mjrMaterialTextures& textures) {
material_params_ = params;
material_textures_ = textures;
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::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_;
}
const MaterialTextures& Renderable::GetMaterialTextures() const {
const mjrMaterialTextures& Renderable::GetMaterialTextures() const {
return material_textures_;
}
void Renderable::SetDrawMode(DrawMode mode) {
// Only SceneObjects support non-color draw modes.
if (params_.shading_model != ShadingModel::SceneObject) {
if (params_.shading_model != mjSHADING_MODEL_SCENE_OBJECT) {
mode = DrawMode::Color;
}
@@ -343,11 +346,11 @@ void Renderable::SetWireframe(bool wireframe) {
}
ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
if (params_.shading_model == ShadingModel::DecorLines) {
if (params_.shading_model == mjSHADING_MODEL_DECOR_LINES) {
return ObjectManager::kUnlitLine;
} else if (params_.shading_model == ShadingModel::Decor) {
} else if (params_.shading_model == mjSHADING_MODEL_DECOR) {
return ObjectManager::kUnlitDecor;
} else if (params_.shading_model == ShadingModel::Ux) {
} else if (params_.shading_model == mjSHADING_MODEL_UX) {
return ObjectManager::kUnlitUi;
} else if (material_textures_.orm) {
return ObjectManager::kPbrPacked;
@@ -374,7 +377,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
}
if (material_textures_.color == nullptr) {
if (material_params_.color.a < 1.0f) {
if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhongColorFade;
} else if (material_params_.reflective) {
return ObjectManager::kPhongColorReflect;
@@ -383,7 +386,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
}
} else if (material_textures_.color->GetFilamentTexture()->getTarget() ==
filament::Texture::Sampler::SAMPLER_CUBEMAP) {
if (material_params_.color.a < 1.0f) {
if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhongCubeFade;
} else if (material_params_.reflective) {
return ObjectManager::kPhongCubeReflect;
@@ -391,7 +394,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
return ObjectManager::kPhongCube;
}
} else if (has_texcoords) {
if (material_params_.color.a < 1.0f) {
if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhong2dUvFade;
} else if (material_params_.reflective) {
return ObjectManager::kPhong2dUvReflect;
@@ -399,7 +402,7 @@ ObjectManager::MaterialType Renderable::GetColorMaterialType() const {
return ObjectManager::kPhong2dUv;
}
} else {
if (material_params_.color.a < 1.0f) {
if (material_params_.color[3] < 1.0f) {
return ObjectManager::kPhong2dFade;
} else if (material_params_.reflective) {
return ObjectManager::kPhong2dReflect;
+17 -17
View File
@@ -33,19 +33,19 @@
namespace mujoco {
// The shading model (material) for a Renderable.
enum class ShadingModel {
SceneObject,
Decor,
DecorLines,
Ux,
};
typedef enum mjrShadingModel_ {
mjSHADING_MODEL_SCENE_OBJECT,
mjSHADING_MODEL_DECOR,
mjSHADING_MODEL_DECOR_LINES,
mjSHADING_MODEL_UX,
} mjrShadingModel;
// Configuration parameters for a Renderable.
struct RenderableParams {
ShadingModel shading_model;
struct mjrRenderableParams {
mjrShadingModel shading_model;
};
void DefaultRenderableParams(RenderableParams* params);
void mjr_defaultRenderableParams(mjrRenderableParams* params);
// 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 kDefaultLayerMask = 0x01;
Renderable(ObjectManager* object_mgr, const RenderableParams& params);
Renderable(ObjectManager* object_mgr, const mjrRenderableParams& params);
~Renderable() noexcept;
Renderable(const Renderable&) = delete;
@@ -127,14 +127,14 @@ class Renderable {
void SetDrawMode(DrawMode mode);
// Updates the parameters for the material.
void UpdateMaterial(const MaterialParams& params,
const MaterialTextures& textures);
void UpdateMaterial(const mjrMaterialParams& params,
const mjrMaterialTextures& textures);
// Returns the current material parameters.
const MaterialParams& GetMaterialParams() const;
const mjrMaterialParams& GetMaterialParams() const;
// Returns the current material textures.
const MaterialTextures& GetMaterialTextures() const;
const mjrMaterialTextures& GetMaterialTextures() const;
// Returns the filament Engine managing the renderables.
filament::Engine* GetEngine();
@@ -154,10 +154,10 @@ class Renderable {
ObjectManager::MaterialType GetColorMaterialType() const;
ObjectManager* object_mgr_;
RenderableParams params_;
mjrRenderableParams params_;
filament::MaterialInstance* instances_[kNumDrawModes] = {nullptr};
MaterialParams material_params_;
MaterialTextures material_textures_;
mjrMaterialParams material_params_;
mjrMaterialTextures material_textures_;
DrawMode draw_mode_ = DrawMode::Color;
filament::Scene* assigned_scene_ = nullptr;
std::vector<Part> parts_;
@@ -127,9 +127,7 @@ static void SetupReflectionCamera(const mat4& surface_xform,
SceneView::SceneView(filament::Engine* engine) : engine_(engine) {
scene_ = engine->createScene();
ux_scene_ = engine->createScene();
camera_ = engine->createCamera(utils::EntityManager::get().create());
ux_camera_ = engine->createCamera(utils::EntityManager::get().create());
reflect_camera_ = engine->createCamera(utils::EntityManager::get().create());
for (auto& view : views_) {
@@ -139,12 +137,6 @@ SceneView::SceneView(filament::Engine* engine) : engine_(engine) {
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_->setScene(scene_);
reflect_view_->setCamera(reflect_camera_);
@@ -172,22 +164,16 @@ SceneView::~SceneView() {
for (auto& renderable : renderables_) {
renderable->RemoveFromScene(scene_);
}
for (auto& renderable : ux_renderables_) {
renderable->RemoveFromScene(ux_scene_);
}
lights_.clear();
renderables_.clear();
reflect_targets_.clear();
engine_->destroyCameraComponent(reflect_camera_->getEntity());
engine_->destroy(reflect_view_);
engine_->destroyCameraComponent(ux_camera_->getEntity());
engine_->destroy(ux_view_);
engine_->destroyCameraComponent(camera_->getEntity());
if (color_grading_) {
engine_->destroy(color_grading_);
}
engine_->destroy(scene_);
engine_->destroy(ux_scene_);
for (auto& view : views_) {
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) {
skybox_ = skybox;
scene_->setSkybox(skybox);
@@ -255,7 +229,6 @@ void SceneView::Render(filament::Renderer* renderer,
for (auto& view : views_) {
view->setViewport(viewport);
}
ux_view_->setViewport(viewport);
reflect_view_->setViewport(viewport);
SetupCamera(request.camera, viewport, camera_);
@@ -276,7 +249,7 @@ void SceneView::Render(filament::Renderer* renderer,
}
// 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) {
Renderable* renderable = reflectives_[i];
@@ -301,15 +274,6 @@ void SceneView::Render(filament::Renderer* renderer,
renderer->render(view);
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) {
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
// renderables.
while (reflect_targets_.size() < reflectives_.size()) {
RenderTargetConfig config;
DefaultRenderTargetConfig(&config);
mjrRenderTargetConfig config;
mjr_defaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGBA8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
@@ -335,9 +299,11 @@ void SceneView::AddReflectiveRenderable(Renderable* renderable) {
auto& target = reflect_targets_[index];
target->Prepare(viewport.width, viewport.height);
MaterialTextures textures = renderable->GetMaterialTextures();
textures.reflection = target->GetColorTexture();
renderable->UpdateMaterial(renderable->GetMaterialParams(), textures);
if (reflections_enabled_) {
mjrMaterialTextures textures = renderable->GetMaterialTextures();
textures.reflection = target->GetColorTexture();
renderable->UpdateMaterial(renderable->GetMaterialParams(), textures);
}
}
void SceneView::SetColorGradingOptions(const ColorGradingOptions& opts) {
@@ -353,6 +319,43 @@ void SceneView::SetColorGradingOptions(const ColorGradingOptions& 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() {
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,
// 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
// also manages a separate scene and view for UX rendering.
// (e.g. normal, depth, segmentation, etc.) as well as reflective surfaces.
class SceneView {
public:
SceneView(filament::Engine* engine);
@@ -53,10 +52,6 @@ class SceneView {
void AddToScene(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.
struct RenderRequest {
// The draw mode (e.g. normal, depth, segmentation) to render.
@@ -67,8 +62,6 @@ class SceneView {
mjvGLCamera camera;
// An optional render target into which the scene will be rendered.
RenderTarget* target = nullptr;
// Whether or not to render the UX as a separate pass.
bool enable_ux = false;
};
// Renders the scene.
@@ -77,6 +70,18 @@ class SceneView {
// Returns the filament Engine managing the scene.
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.
// Callers can update rendering settings (e.g. post processing) directly.
filament::View* GetDefaultRenderView();
@@ -95,7 +100,6 @@ class SceneView {
filament::Engine* engine_ = nullptr;
filament::Scene* scene_ = nullptr;
filament::Scene* ux_scene_ = nullptr;
filament::Camera* camera_ = nullptr;
filament::ColorGrading* color_grading_ = nullptr;
ColorGradingOptions color_grading_options_;
@@ -106,16 +110,12 @@ class SceneView {
std::unordered_set<Renderable*> renderables_;
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.
filament::View* reflect_view_ = nullptr;
filament::Camera* reflect_camera_ = nullptr;
// The list of reflective renderables and their corresponding render targets.
bool reflections_enabled_ = true;
std::vector<Renderable*> reflectives_;
std::vector<std::unique_ptr<RenderTarget>> reflect_targets_;
};
+12 -12
View File
@@ -29,15 +29,15 @@ namespace mujoco {
static constexpr int kNumFacesPerCube = 6;
static bool IsCompressed(const TextureConfig& config) {
static bool IsCompressed(const mjrTextureConfig& config) {
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;
}
static int GetFaceHeight(const TextureConfig& config) {
static int GetFaceHeight(const mjrTextureConfig& config) {
int face_height = config.height;
if (config.width != config.height) {
if (config.width * kNumFacesPerCube != config.height) {
@@ -51,7 +51,7 @@ static int GetFaceHeight(const TextureConfig& config) {
return face_height;
}
static int GetNumChannels(const TextureConfig& config) {
static int GetNumChannels(const mjrTextureConfig& config) {
switch (config.format) {
case mjPIXEL_FORMAT_R8:
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) {
case mjPIXEL_FORMAT_R8:
return filament::Texture::Format::R;
@@ -80,7 +80,7 @@ static filament::Texture::Format GetTextureFormat(const TextureConfig& config) {
}
static filament::Texture::InternalFormat GetTextureInternalFormat(
const TextureConfig& config) {
const mjrTextureConfig& config) {
if (config.color_space == mjCOLORSPACE_SRGB) {
switch (config.format) {
case mjPIXEL_FORMAT_RGB8:
@@ -110,15 +110,15 @@ static filament::Texture::InternalFormat GetTextureInternalFormat(
}
}
void DefaultTextureData(TextureData* data) {
std::memset(data, 0, sizeof(TextureData));
void mjr_defaultTextureData(mjrTextureData* data) {
std::memset(data, 0, sizeof(mjrTextureData));
}
void DefaultTextureConfig(TextureConfig* config) {
std::memset(config, 0, sizeof(TextureConfig));
void mjr_defaultTextureConfig(mjrTextureConfig* config) {
std::memset(config, 0, sizeof(mjrTextureConfig));
}
Texture::Texture(filament::Engine* engine, const TextureConfig& config,
Texture::Texture(filament::Engine* engine, const mjrTextureConfig& config,
InternalFlags flags)
: engine_(engine), config_(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;
release_callback_ = data.release_callback;
+18 -14
View File
@@ -21,12 +21,13 @@
#include <filament/Texture.h>
#include <math/vec3.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mujoco.h>
// Functions for creating filament textures.
namespace mujoco {
// Pixel formats for textures.
typedef enum mjtPixelFormat_ {
typedef enum mjrPixelFormat_ {
mjPIXEL_FORMAT_UNKNOWN = 0,
mjPIXEL_FORMAT_R8,
mjPIXEL_FORMAT_RGB8,
@@ -34,15 +35,18 @@ typedef enum mjtPixelFormat_ {
mjPIXEL_FORMAT_R32F,
mjPIXEL_FORMAT_DEPTH32F,
mjPIXEL_FORMAT_KTX,
} mjtPixelFormat;
} mjrPixelFormat;
typedef mjtTexture mjrTextureTarget;
typedef mjtColorSpace mjrColorSpace;
// The binary contents of a texture.
struct TextureData {
struct mjrTextureData {
// 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.
size_t nbytes;
mjtSize nbytes;
// 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
@@ -54,10 +58,10 @@ struct TextureData {
};
// Initializes the TextureData to default values.
void DefaultTextureData(TextureData* data);
void mjr_defaultTextureData(mjrTextureData* data);
// 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
// number of bytes in the compressed data.
int width;
@@ -67,17 +71,17 @@ struct TextureConfig {
int height;
// 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.)
mjtPixelFormat format;
mjrPixelFormat format;
// The color space of the texture (e.g. LINEAR, sRGB, etc.)
mjtColorSpace color_space;
mjrColorSpace color_space;
};
// Initializes the TextureConfig to default values.
void DefaultTextureConfig(TextureConfig* config);
void mjr_defaultTextureConfig(mjrTextureConfig* config);
// Wrapper around a filament::Texture.
class Texture {
@@ -90,13 +94,13 @@ class Texture {
};
// Creates a texture with the given data.
Texture(filament::Engine* engine, const TextureConfig& config,
Texture(filament::Engine* engine, const mjrTextureConfig& config,
InternalFlags flags = InternalFlags());
~Texture();
// Uploads the given data to the texture.
void Upload(const TextureData& data);
void Upload(const mjrTextureData& data);
// Returns the width of the texture.
int GetWidth() const { return config_.width; }
@@ -121,7 +125,7 @@ class Texture {
filament::Engine* engine_ = nullptr;
filament::Texture* texture_ = nullptr;
TextureConfig config_;
mjrTextureConfig config_;
SphericalHarmonics spherical_harmonics_;
bool has_spherical_harmonics_ = false;
@@ -21,13 +21,13 @@
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.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)
static thread_local mujoco::FilamentContext* g_filament_context = nullptr;
static thread_local mujoco::MjrFilamentRenderer* g_filament_context = nullptr;
#else
static mujoco::FilamentContext* g_filament_context = nullptr;
static mujoco::MjrFilamentRenderer* g_filament_context = nullptr;
#endif
static void CheckFilamentContext() {
@@ -43,13 +43,13 @@ void mjrf_defaultFilamentConfig(mjrFilamentConfig* config) {
}
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
// assume a single, global context.
if (g_filament_context != nullptr) {
mju_error("Context already exists!");
}
g_filament_context = new mujoco::FilamentContext(config);
g_filament_context = new mujoco::MjrFilamentRenderer(config);
g_filament_context->Init(m);
}
@@ -29,11 +29,11 @@ extern "C" {
// IMPORTANT: This API should still be considered experimental and is likely
// change frequently.
typedef enum mjtGraphicsApi_ { // backend graphics API to use
mjGFX_DEFAULT = 0, // default based on platform
mjGFX_OPENGL, // OpenGL (desktop)
mjGFX_VULKAN // Vulkan
} mjtGraphicsApi;
typedef enum mjrGraphicsApi_ { // backend graphics API to use
mjGRAPHICS_API_DEFAULT = 0, // default based on platform
mjGRAPHICS_API_OPENGL, // OpenGL (desktop) / WebGL
mjGRAPHICS_API_VULKAN // Vulkan
} mjrGraphicsApi;
struct mjrFilamentConfig {
// 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.height = model->vis.global.offheight;
render_config.force_software_rendering = IsSoftware(gfx_);
render_config.graphics_api =
IsOpenGl(gfx_) || IsWebGl(gfx_) ? mjGFX_OPENGL : mjGFX_VULKAN;
render_config.graphics_api = IsOpenGl(gfx_) || IsWebGl(gfx_)
? mjGRAPHICS_API_OPENGL
: mjGRAPHICS_API_VULKAN;
mjrf_makeFilamentContext(model, &render_context_, &render_config);
render_ = [&](mjrRect rect, mjvScene* scene) {
mjrf_render(rect, scene, &render_context_);
+4 -9
View File
@@ -16,6 +16,7 @@
#include <algorithm>
#include <cstddef>
#include <cstdlib>
#include <span>
#include <string>
#include <string_view>
@@ -46,8 +47,8 @@ extern void* GetNativeWindowOsx(void* window);
namespace mujoco::platform {
static void InitImGui(SDL_Window* window, float content_scale, bool load_fonts,
bool build_fonts) {
static void InitImGui(SDL_Window* window, float content_scale,
bool load_fonts) {
ImGui::CreateContext();
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};
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
// 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());
}
InitImGui(sdl_window_, content_scale, config.load_fonts,
(config_.gfx_mode != GraphicsMode::ClassicOpenGl &&
config_.gfx_mode != GraphicsMode::ClassicOpenGlHeadless));
InitImGui(sdl_window_, content_scale, config.load_fonts);
// 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.
+105 -68
View File
@@ -14,87 +14,124 @@
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/implot)
include(third_party_deps/opensans)
include(third_party_deps/font_awesome)
target_link_libraries(${MUJOCO_STUDIO_TARGET_NAME}
PRIVATE
absl::flags
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}
# Common configuration shared between mujoco_studio and mujoco_live.
function(configure_studio_target TARGET_NAME)
target_sources(${TARGET_NAME}
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(
TARGET ${MUJOCO_STUDIO_TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND}
-E copy
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../_deps/opensans-src/fonts/ttf/OpenSans-Regular.ttf
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/assets
COMMENT "Copying OpenSans-Regular.ttf assets to build directory"
)
add_custom_command(
TARGET ${MUJOCO_STUDIO_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"
)
if (WIN32)
target_compile_definitions(${TARGET_NAME}
PRIVATE
-D_USE_MATH_DEFINES
)
set_target_properties(${TARGET_NAME}
PROPERTIES
VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}"
)
endif()
target_link_libraries(${TARGET_NAME}
PRIVATE
absl::flags
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(${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(
TARGET ${MUJOCO_STUDIO_TARGET_NAME}
TARGET ${TARGET_NAME}
POST_BUILD
COMMAND ${CMAKE_COMMAND}
-E copy_directory
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../src/experimental/filament/assets
-E copy
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../_deps/opensans-src/fonts/ttf/OpenSans-Regular.ttf
${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()
+143 -18
View File
@@ -1,8 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>MuJoCo Studio!</title>
<title>MuJoCo Live</title>
</head>
<body style="margin: 0; overflow: hidden">
<div style="position: absolute; top: 3px; right: 3px; z-index: 1;">
@@ -16,6 +16,28 @@
style="width: 100vw; height: 100vh; display: block"
></canvas>
<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 = {
preRun: [],
postRun: [],
@@ -46,8 +68,30 @@
totalDependencies: 0,
monitorRunDependencies(left) {},
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 = [
"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) => {
@@ -71,7 +115,31 @@
.then(() => {
try {
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) {
console.error('Failed to initialize app.', error);
}
@@ -95,6 +163,31 @@
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', () => {
const uploadButton = document.getElementById('uploadButton');
const fileInput = document.getElementById('fileInput');
@@ -108,23 +201,55 @@
if (!file) {
return;
}
handleFile(file);
});
const reader = new FileReader();
reader.onload = (e) => {
const buffer = e.target.result;
try {
Module.loadFile(file.name, buffer);
} catch (error) {
console.error('Failed to load model from file:', error);
}
};
reader.onerror = (e) => {
console.error('Error reading file:', e);
};
reader.readAsArrayBuffer(file);
// --- Drag-and-drop support ---
const dropOverlay = document.createElement('div');
dropOverlay.id = 'dropOverlay';
dropOverlay.style.cssText =
'position:fixed;top:0;left:0;width:100%;height:100%;' +
'background:rgba(0,0,0,0.5);z-index:9999;display:none;' +
'pointer-events:none;align-items:center;justify-content:center;';
dropOverlay.innerHTML =
'<div style="color:#fff;font-size:24px;font-family:sans-serif;' +
'padding:32px 48px;border:3px dashed #fff;border-radius:16px;">' +
'Drop model file here</div>';
document.body.appendChild(dropOverlay);
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 async src="/bin/wasm.js"></script>
<script async src="bin/mujoco_live.js"></script>
</body>
</html>
+15 -1
View File
@@ -75,7 +75,7 @@ class FileResource {
int main(int argc, char** argv, char** envp) {
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";
mjpResourceProvider resource_provider;
@@ -103,6 +103,20 @@ int main(int argc, char** argv, char** envp) {
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::GraphicsModeFromString(
gfx, mujoco::platform::GraphicsMode::FilamentOpenGl);
+115
View File
@@ -18,7 +18,11 @@
#include <emscripten/bind.h>
#include <emscripten/val.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
#include <unordered_map>
@@ -59,6 +63,84 @@ class AssetRegistry {
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.
void RegisterAsset(std::string filename, std::string contents) {
AssetRegistry::Instance().RegisterAsset(std::move(filename),
@@ -92,6 +174,27 @@ void Init() {
resource_provider.prefix = "filament";
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({
.width = width,
.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);
}
// 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.
void RenderFrame() {
if (g_app) {
@@ -144,6 +258,7 @@ EMSCRIPTEN_BINDINGS(studio_bindings) {
emscripten::function("registerAsset", &RegisterAsset);
emscripten::function("init", &Init);
emscripten::function("loadFile", &LoadFile);
emscripten::function("loadUrl", &LoadUrl);
emscripten::function("renderFrame", &RenderFrame);
emscripten::function("deinit", &Deinit);
}
+3 -1
View File
@@ -12,4 +12,6 @@
# See the License for the specific language governing permissions and
# 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) {
File.Copy(
"/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");
AssetDatabase.Refresh();
}
@@ -45,7 +45,7 @@ public class MujocoBinaryRetriever {
if (AssetDatabase.LoadMainAssetAtPath(mujocoPath + "/libmujoco.so") == null) {
File.Copy(
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");
AssetDatabase.Refresh();
}
+1 -1
View File
@@ -113,7 +113,7 @@ public const int mjMAXLINEPNT = 1001;
public const int mjMAXPLANEGRID = 200;
public const bool THIRD_PARTY_MUJOCO_MJXMACRO_H_ = true;
public const bool THIRD_PARTY_MUJOCO_MUJOCO_H_ = true;
public const int mjVERSION_HEADER = 3007001;
public const int mjVERSION_HEADER = 3008001;
// ------------------------------------Enums------------------------------------
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "org.mujoco",
"displayName": "MuJoCo",
"version": "3.7.1",
"version": "3.8.1",
"description": "MuJoCo importer and runtime plug-in",
"dependencies": {},
"author": {