diff --git a/doc/APIreference/APItypes.rst b/doc/APIreference/APItypes.rst index 49c48a4b..8b7e8416 100644 --- a/doc/APIreference/APItypes.rst +++ b/doc/APIreference/APItypes.rst @@ -1286,6 +1286,16 @@ This structure specifies the attributes for a single vertex. +.. _mjrRendererInfo: + +mjrRendererInfo +~~~~~~~~~~~~~~~ + +This structure contains information about the available renderer and its current context. + +.. mujoco-include:: mjrRendererInfo + + .. _mjrContext: mjrContext diff --git a/doc/APIreference/functions.rst b/doc/APIreference/functions.rst index 4c1be31d..c8a3d087 100644 --- a/doc/APIreference/functions.rst +++ b/doc/APIreference/functions.rst @@ -2797,6 +2797,24 @@ of how to use these functions. Set default mjrContext. +.. _mjr_defaultRendererInfo: + +`mjr_defaultRendererInfo <#mjr_defaultRendererInfo>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mjr_defaultRendererInfo + +Set default mjrRendererInfo. + +.. _mjr_getRendererInfo: + +`mjr_getRendererInfo <#mjr_getRendererInfo>`__ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. mujoco-include:: mjr_getRendererInfo + +Get active renderer information. + .. _mjr_makeContext: `mjr_makeContext <#mjr_makeContext>`__ diff --git a/doc/includes/references.h b/doc/includes/references.h index 94d31210..26475b4b 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -1378,6 +1378,10 @@ typedef struct mjrRect_ { // OpenGL rectangle int width; // width (usually buffer width) int height; // height (usually buffer height) } mjrRect; +typedef struct mjrRendererInfo_ { // active renderer identity + const char* renderer; // renderer family: classic, filament, noop + const char* backend; // graphics backend: opengl, vulkan; empty if uninitialized +} mjrRendererInfo; typedef struct mjrVertexAttribute_ { // vertex attribute format specification const void* bytes; // vertex data int usage; // position, normal, etc [mjrVertexAttributeUsage] @@ -3471,6 +3475,8 @@ void mjv_cameraFrame(mjtNum headpos[3], mjtNum forward[3], mjtNum up[3], mjtNum void mjv_cameraFrustum(float zver[2], float zhor[2], float zclip[2], const mjModel* m, const mjvCamera* cam); void mjr_defaultContext(mjrContext* con); +void mjr_defaultRendererInfo(mjrRendererInfo* info); +void mjr_getRendererInfo(mjrRendererInfo* info); void mjr_makeContext(const mjModel* m, mjrContext* con, int fontscale); void mjr_changeFont(int fontscale, mjrContext* con); void mjr_addAux(int index, int width, int height, int samples, mjrContext* con); diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index b3faf2d7..c683688f 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -117,6 +117,12 @@ typedef struct mjrRect_ { // OpenGL rectangle } mjrRect; +typedef struct mjrRendererInfo_ { // active renderer identity + const char* renderer; // renderer family: classic, filament, noop + const char* backend; // graphics backend: opengl, vulkan; empty if uninitialized +} mjrRendererInfo; + + typedef struct mjrVertexAttribute_ { // vertex attribute format specification const void* bytes; // vertex data int usage; // position, normal, etc [mjrVertexAttributeUsage] diff --git a/include/mujoco/mjrfilament.h b/include/mujoco/mjrfilament.h index 8758786a..79d88fda 100644 --- a/include/mujoco/mjrfilament.h +++ b/include/mujoco/mjrfilament.h @@ -104,6 +104,9 @@ mjrfContext* mjrf_createContext(const mjrfContextConfig* config); // Destroys the filament rendering context. void mjrf_destroyContext(mjrfContext* ctx); +// Gets active renderer information for the given filament context. +void mjrf_getRendererInfo(mjrfContext* ctx, mjrRendererInfo* info); + typedef enum mjrDrawMode_ { // how to draw objects in the scene mjDRAW_MODE_DEFAULT, // default colors and lighting mjDRAW_MODE_DEFAULT_NO_TEXTURES, // default, but without textures diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index 810bbd65..acb15625 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -855,6 +855,12 @@ MJAPI void mjv_cameraFrustum(float zver[2], float zhor[2], float zclip[2], cons // Set default mjrContext. MJAPI void mjr_defaultContext(mjrContext* con); +// Set default mjrRendererInfo. +MJAPI void mjr_defaultRendererInfo(mjrRendererInfo* info); + +// Get active renderer information. +MJAPI void mjr_getRendererInfo(mjrRendererInfo* info); + // Allocate resources in custom OpenGL context; fontscale is mjtFontScale. MJAPI void mjr_makeContext(const mjModel* m, mjrContext* con, int fontscale); diff --git a/python/mujoco/introspect/functions.py b/python/mujoco/introspect/functions.py index e9b7e059..c01d5efa 100644 --- a/python/mujoco/introspect/functions.py +++ b/python/mujoco/introspect/functions.py @@ -5480,6 +5480,34 @@ FUNCTIONS: Mapping[str, FunctionDecl] = dict([ ), doc='Set default mjrContext.', )), + ('mjr_defaultRendererInfo', + FunctionDecl( + name='mjr_defaultRendererInfo', + return_type=ValueType(name='void'), + parameters=( + FunctionParameterDecl( + name='info', + type=PointerType( + inner_type=ValueType(name='mjrRendererInfo'), + ), + ), + ), + doc='Set default mjrRendererInfo.', + )), + ('mjr_getRendererInfo', + FunctionDecl( + name='mjr_getRendererInfo', + return_type=ValueType(name='void'), + parameters=( + FunctionParameterDecl( + name='info', + type=PointerType( + inner_type=ValueType(name='mjrRendererInfo'), + ), + ), + ), + doc='Get active renderer information.', + )), ('mjr_makeContext', FunctionDecl( name='mjr_makeContext', diff --git a/python/mujoco/introspect/structs.py b/python/mujoco/introspect/structs.py index a2495ce4..1af344dc 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -11005,6 +11005,27 @@ STRUCTS: Mapping[str, StructDecl] = dict([ ), ), )), + ('mjrRendererInfo', + StructDecl( + name='mjrRendererInfo', + declname='struct mjrRendererInfo_', + fields=( + StructFieldDecl( + name='renderer', + type=PointerType( + inner_type=ValueType(name='char', is_const=True), + ), + doc='renderer family: classic, filament, noop', + ), + StructFieldDecl( + name='backend', + type=PointerType( + inner_type=ValueType(name='char', is_const=True), + ), + doc='graphics backend: opengl, vulkan; empty if uninitialized', + ), + ), + )), ('mjrVertexAttribute', StructDecl( name='mjrVertexAttribute', diff --git a/python/mujoco/raw.h b/python/mujoco/raw.h index 920a419c..7ff1735a 100644 --- a/python/mujoco/raw.h +++ b/python/mujoco/raw.h @@ -79,6 +79,7 @@ using MjLogMessage = ::mjLogMessage; // From mjrender.h using MjrRect = ::mjrRect; +using MjrRendererInfo = ::mjrRendererInfo; using MjrContext = ::mjrContext; using MjrVertexAttribute = ::mjrVertexAttribute; diff --git a/python/mujoco/render.cc b/python/mujoco/render.cc index 57c633ec..82418367 100644 --- a/python/mujoco/render.cc +++ b/python/mujoco/render.cc @@ -143,7 +143,6 @@ void MjrContextWrapper::Free() { // mjr_freeContext is safe to call multiple times. InterceptMjErrors(mjr_freeContext)(ptr_); } - } // namespace _impl namespace { @@ -241,10 +240,38 @@ PYBIND11_MODULE(_render, pymodule, pybind11::mod_gil_not_used()) { X(charWidthBig); #undef X + // ==================== MJRRENDERERINFO ====================================== + py::class_ mjrRendererInfo(pymodule, "MjrRendererInfo"); + mjrRendererInfo.def(py::init([]() { + raw::MjrRendererInfo info; + mjr_defaultRendererInfo(&info); + return info; + })); + mjrRendererInfo.def("__copy__", [](const raw::MjrRendererInfo& other) { + return raw::MjrRendererInfo(other); + }); + mjrRendererInfo.def("__deepcopy__", + [](const raw::MjrRendererInfo& other, py::dict) { + return raw::MjrRendererInfo(other); + }); + DefineStructFunctions(mjrRendererInfo); + mjrRendererInfo.def_property_readonly("renderer", + [](const raw::MjrRendererInfo& info) { + return info.renderer ? info.renderer + : ""; + }); + mjrRendererInfo.def_property_readonly("backend", + [](const raw::MjrRendererInfo& info) { + return info.backend ? info.backend + : ""; + }); + using EigenUnsignedCharVectorX = Eigen::Vector; using EigenFloatVectorX = Eigen::Vector; // Skipped: mjr_defaultContext (have MjrContext.__init__) + Def(pymodule); + Def(pymodule); // Skipped: mjr_makeContext (have MjrContext.__init__) Def(pymodule); Def(pymodule); diff --git a/python/mujoco/renderer_test.py b/python/mujoco/renderer_test.py index 055a9476..a7338a91 100644 --- a/python/mujoco/renderer_test.py +++ b/python/mujoco/renderer_test.py @@ -28,6 +28,13 @@ import numpy as np ) class MuJoCoRendererTest(parameterized.TestCase): + def test_renderer_info_binding(self): + info = mujoco.MjrRendererInfo() + mujoco.mjr_getRendererInfo(info) + + self.assertIn(info.renderer, ('classic', 'filament', 'noop')) + self.assertIn(info.backend, ('', 'opengl', 'vulkan', 'unknown')) + def test_renderer_unknown_camera_name(self): xml = """ diff --git a/src/experimental/filament/mjr_compat.cc b/src/experimental/filament/mjr_compat.cc index c1da2fd3..fd6ab006 100644 --- a/src/experimental/filament/mjr_compat.cc +++ b/src/experimental/filament/mjr_compat.cc @@ -57,6 +57,8 @@ class CompatContext { scene_bridge_->UploadHeightField(model, id); } + mjrfContext* Context() const { return context_.get(); } + private: mjrDrawMode draw_mode_ = mjDRAW_MODE_DEFAULT; UniquePtr context_{nullptr, nullptr}; @@ -189,6 +191,19 @@ void mjr_defaultContext(mjrContext* con) { memset(con, 0, sizeof(mjrContext)); } +void mjr_defaultRendererInfo(mjrRendererInfo* info) { + memset(info, 0, sizeof(mjrRendererInfo)); + info->renderer = "filament"; + info->backend = ""; +} + +void mjr_getRendererInfo(mjrRendererInfo* info) { + mjr_defaultRendererInfo(info); + if (g_context) { + mjrf_getRendererInfo(g_context->Context(), info); + } +} + void mjr_makeFilamentContext(const mjModel* m, const mjrfContextConfig* cfg, mjrContext* con) { if (g_context != nullptr) { diff --git a/src/render/classic/render_context.c b/src/render/classic/render_context.c index c7a5dd0b..35fcfbf0 100644 --- a/src/render/classic/render_context.c +++ b/src/render/classic/render_context.c @@ -55,6 +55,29 @@ void mjr_defaultContext(mjrContext* con) { memset(con, 0, sizeof(mjrContext)); } +static int context_count = 0; + +static int contextHasResources(const mjrContext* con) { + return con->ntexture || con->offColor || con->offDepthStencil || con->offFBO || + con->shadowTex || con->shadowFBO || con->rangePlane || con->rangeMesh || + con->rangeHField || con->rangeBuiltin || con->rangeFont || con->nskin; +} + +// set default mjrRendererInfo +void mjr_defaultRendererInfo(mjrRendererInfo* info) { + memset(info, 0, sizeof(mjrRendererInfo)); + info->renderer = "classic"; + info->backend = ""; +} + +// get active renderer information +void mjr_getRendererInfo(mjrRendererInfo* info) { + mjr_defaultRendererInfo(info); + if (context_count > 0) { + info->backend = "opengl"; + } +} + // allocate lists @@ -1608,6 +1631,7 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, // try to bind window (bind offscreen if no window) mjr_setBuffer(mjFB_WINDOW, con); + context_count++; return; } @@ -1667,6 +1691,7 @@ void mjr_makeContext_offSize(const mjModel* m, mjrContext* con, int fontscale, // set default depth mapping for mjr_readPixels con->readDepthMap = mjDEPTH_ZERONEAR; + context_count++; } @@ -1811,6 +1836,8 @@ void mjr_addAux(int index, int width, int height, int samples, mjrContext* con) // free resources in custom OpenGL context void mjr_freeContext(mjrContext* con) { + int had_resources = contextHasResources(con); + // save flags int glInitialized = con->glInitialized; int windowAvailable = con->windowAvailable; @@ -1867,6 +1894,10 @@ void mjr_freeContext(mjrContext* con) { con->windowSamples = windowSamples; con->windowStereo = windowStereo; con->windowDoublebuffer = windowDoublebuffer; + + if (had_resources && context_count > 0) { + context_count--; + } } diff --git a/src/render/filament/core/filament_context.cc b/src/render/filament/core/filament_context.cc index b3efd784..5e2921d5 100644 --- a/src/render/filament/core/filament_context.cc +++ b/src/render/filament/core/filament_context.cc @@ -49,6 +49,7 @@ namespace mujoco { FilamentContext::FilamentContext(const mjrfContextConfig* config) : config_(*config) { FilamentPlatformSetup setup = CreateFilamentPlatform(config_); + backend_ = setup.backend; platform_ = std::move(setup.platform); filament::Engine::Config engine_config; @@ -58,7 +59,7 @@ FilamentContext::FilamentContext(const mjrfContextConfig* config) filament::Engine::Builder engine_builder; engine_builder.config(&engine_config); - engine_builder.backend(setup.backend); + engine_builder.backend(backend_); engine_builder.platform(platform_.get()); engine_builder.feature("backend.disable_parallel_shader_compile", setup.disable_parallel_shader_compile); diff --git a/src/render/filament/core/filament_context.h b/src/render/filament/core/filament_context.h index fe62b967..4adf0ab8 100644 --- a/src/render/filament/core/filament_context.h +++ b/src/render/filament/core/filament_context.h @@ -59,6 +59,8 @@ class FilamentContext : public mjrfContext { filament::Engine* GetEngine() const { return engine_; } + filament::Engine::Backend GetBackend() const { return backend_; } + ObjectManager* GetObjectManager() const { return object_manager_.get(); } MaterialManager* GetMaterialManager() const { @@ -76,6 +78,7 @@ class FilamentContext : public mjrfContext { void ValidateSwapChains(std::span render_requests); mjrfContextConfig config_; + filament::Engine::Backend backend_; filament::Engine* engine_ = nullptr; filament::Renderer* renderer_ = nullptr; filament::SwapChain* window_swap_chain_ = nullptr; diff --git a/src/render/filament/mjrfilament.cc b/src/render/filament/mjrfilament.cc index d3c61304..d35b3e02 100644 --- a/src/render/filament/mjrfilament.cc +++ b/src/render/filament/mjrfilament.cc @@ -15,9 +15,9 @@ #include #include -#include #include +#include #include #include #include @@ -37,6 +37,17 @@ static void setf(float (&arr)[N], const std::array& values) { } } +static const char* BackendName(filament::Engine::Backend backend) { + switch (backend) { + case filament::Engine::Backend::OPENGL: + return "opengl"; + case filament::Engine::Backend::VULKAN: + return "vulkan"; + default: + return "unknown"; + } +} + extern "C" { void mjrf_defaultContextConfig(mjrfContextConfig* config) { @@ -124,6 +135,12 @@ void mjrf_destroyContext(mjrfContext* ctx) { delete mujoco::FilamentContext::downcast(ctx); } +void mjrf_getRendererInfo(mjrfContext* ctx, mjrRendererInfo* info) { + memset(info, 0, sizeof(mjrRendererInfo)); + info->renderer = "filament"; + info->backend = ctx ? BackendName(mujoco::FilamentContext::downcast(ctx)->GetBackend()) : ""; +} + mjrfTexture* mjrf_createTexture(mjrfContext* ctx, const mjrfTextureConfig* config) { return new mujoco::Texture( diff --git a/src/render/noop/render_noop.c b/src/render/noop/render_noop.c index a928e96d..6f7bcbf6 100644 --- a/src/render/noop/render_noop.c +++ b/src/render/noop/render_noop.c @@ -13,6 +13,7 @@ // limitations under the License. #include +#include // This library implements the entirety of mujoco's mjr API as fast-fail stubs. // You can link this library with your application (instead of standard renderer @@ -22,6 +23,14 @@ void mjr_defaultContext(mjrContext* con) { mju_error("mjr_defaultContext not implemented."); } +void mjr_defaultRendererInfo(mjrRendererInfo* info) { + memset(info, 0, sizeof(mjrRendererInfo)); + info->renderer = "noop"; + info->backend = ""; +} +void mjr_getRendererInfo(mjrRendererInfo* info) { + mjr_defaultRendererInfo(info); +} void mjr_makeContext(const mjModel* m, mjrContext* con, int fontscale) { mju_error("mjr_makeContext not implemented."); } diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index ea5c30e3..e706d415 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -6182,6 +6182,12 @@ public unsafe struct mjrRect_ { public int height; } +[StructLayout(LayoutKind.Sequential)] +public unsafe struct mjrRendererInfo_ { + public char* renderer; + public char* backend; +} + [StructLayout(LayoutKind.Sequential)] public unsafe struct mjrVertexAttribute_ { public void* bytes; @@ -7215,6 +7221,12 @@ public static unsafe extern void mjv_cameraFrustum(float* zver, float* zhor, flo [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] public static unsafe extern void mjr_defaultContext(mjrContext_* con); +[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] +public static unsafe extern void mjr_defaultRendererInfo(mjrRendererInfo_* info); + +[DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] +public static unsafe extern void mjr_getRendererInfo(mjrRendererInfo_* info); + [DllImport("mujoco", CallingConvention = CallingConvention.Cdecl)] public static unsafe extern void mjr_makeContext(mjModel_* m, mjrContext_* con, int fontscale); diff --git a/wasm/codegen/generators/constants.py b/wasm/codegen/generators/constants.py index 6bc033e8..42a6272f 100644 --- a/wasm/codegen/generators/constants.py +++ b/wasm/codegen/generators/constants.py @@ -218,6 +218,7 @@ SKIPPED_STRUCTS: tuple[str, ...] = ( "mjUI", "mjVFS", "mjrContext", + "mjrRendererInfo", "mjrRect", "mjrVertexAttribute", "mjuiDef",