diff --git a/include/mujoco/mjrender.h b/include/mujoco/mjrender.h index b3faf2d7..d273c7d6 100644 --- a/include/mujoco/mjrender.h +++ b/include/mujoco/mjrender.h @@ -117,6 +117,13 @@ 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 + const char* backend_version; // backend version string, empty if unknown +} 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 e734aaac..83843eec 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/bindings_test.py b/python/mujoco/bindings_test.py index 6bd5d02a..1af2eaef 100644 --- a/python/mujoco/bindings_test.py +++ b/python/mujoco/bindings_test.py @@ -125,6 +125,14 @@ class MuJoCoBindingsTest(parameterized.TestCase): self.model: mujoco.MjModel = mujoco.MjModel.from_xml_string(TEST_XML) self.data = mujoco.MjData(self.model) + 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')) + self.assertIsInstance(info.backend_version, str) + def test_load_xml_can_handle_name_clash(self): xml_1 = r""" diff --git a/python/mujoco/introspect/functions.py b/python/mujoco/introspect/functions.py index 4a023ce4..6dea91bf 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 c71b461f..d246f7c4 100644 --- a/python/mujoco/introspect/structs.py +++ b/python/mujoco/introspect/structs.py @@ -10995,6 +10995,34 @@ 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', + ), + StructFieldDecl( + name='backend_version', + type=PointerType( + inner_type=ValueType(name='char', is_const=True), + ), + doc='backend version string, empty if unknown', + ), + ), + )), ('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..e58002d1 100644 --- a/python/mujoco/render.cc +++ b/python/mujoco/render.cc @@ -245,6 +245,8 @@ PYBIND11_MODULE(_render, pymodule, pybind11::mod_gil_not_used()) { 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/structs.cc b/python/mujoco/structs.cc index 359b14eb..a6d860e6 100644 --- a/python/mujoco/structs.cc +++ b/python/mujoco/structs.cc @@ -1049,6 +1049,36 @@ This is useful for example when the MJB is not available as a file on disk.)")); X(height); #undef X + // ==================== MJRRENDERERINFO ====================================== + py::class_ mjrRendererInfo(m, "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 + : ""; + }); + mjrRendererInfo.def_property_readonly( + "backend_version", [](const raw::MjrRendererInfo& info) { + return info.backend_version ? info.backend_version : ""; + }); + // ==================== MJRVERTEXATTRIBUTE =================================== py::class_ mjrVertexAttribute(m, "MjrVertexAttribute"); diff --git a/src/experimental/filament/mjr_compat.cc b/src/experimental/filament/mjr_compat.cc index c1da2fd3..e081f173 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,20 @@ 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 = ""; + info->backend_version = ""; +} + +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..c59653c4 100644 --- a/src/render/classic/render_context.c +++ b/src/render/classic/render_context.c @@ -55,6 +55,30 @@ 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 = ""; + info->backend_version = ""; +} + +// get active renderer information +void mjr_getRendererInfo(mjrRendererInfo* info) { + mjr_defaultRendererInfo(info); + if (context_count > 0) { + info->backend = "opengl"; + } +} + // allocate lists @@ -1608,6 +1632,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 +1692,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 +1837,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 +1895,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..701dc562 100644 --- a/src/render/filament/mjrfilament.cc +++ b/src/render/filament/mjrfilament.cc @@ -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,13 @@ 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()) : ""; + info->backend_version = ""; +} + 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..411eb22b 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,15 @@ 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 = ""; + info->backend_version = ""; +} +void mjr_getRendererInfo(mjrRendererInfo* info) { + mjr_defaultRendererInfo(info); +} void mjr_makeContext(const mjModel* m, mjrContext* con, int fontscale) { mju_error("mjr_makeContext not implemented."); }