diff --git a/src/experimental/platform/CMakeLists.txt b/src/experimental/platform/CMakeLists.txt index c08f9a24..80035ff0 100644 --- a/src/experimental/platform/CMakeLists.txt +++ b/src/experimental/platform/CMakeLists.txt @@ -18,13 +18,13 @@ set(MUJOCO_PLATFORM_TARGET_NAME mujoco_platform) # Determine the render configuration and dependencies based options. if(MUJOCO_USE_FILAMENT AND MUJOCO_USE_FILAMENT_VULKAN) - set(MUJOCO_PLATFORM_RENDER_CONFIG "USE_FILAMENT_VULKAN") + set(MUJOCO_PLATFORM_RENDER_CONFIG "MUJOCO_RENDERER_FILAMENT_VULKAN") set(MUJOCO_PLATFORM_RENDER_DEPS "mujoco::filament") elseif(MUJOCO_USE_FILAMENT) - set(MUJOCO_PLATFORM_RENDER_CONFIG "USE_FILAMENT_OPENGL") + set(MUJOCO_PLATFORM_RENDER_CONFIG "MUJOCO_RENDERER_FILAMENT_OPENGL") set(MUJOCO_PLATFORM_RENDER_DEPS "mujoco::filament") else() - set(MUJOCO_PLATFORM_RENDER_CONFIG "USE_CLASSIC_OPENGL") + set(MUJOCO_PLATFORM_RENDER_CONFIG "MUJOCO_RENDERER_CLASSIC_OPENGL") set(MUJOCO_PLATFORM_RENDER_DEPS "dear_imgui_OpenGL3") endif() @@ -52,6 +52,7 @@ target_sources(${MUJOCO_PLATFORM_TARGET_NAME} plugin.h renderer.cc renderer.h + renderer_backend.h sim_history.cc sim_history.h sim_profiler.cc diff --git a/src/experimental/platform/renderer.cc b/src/experimental/platform/renderer.cc index af01667f..bff07e83 100644 --- a/src/experimental/platform/renderer.cc +++ b/src/experimental/platform/renderer.cc @@ -18,21 +18,20 @@ #include #include +#include "experimental/platform/renderer_backend.h" -#if defined(USE_FILAMENT_OPENGL) || defined(USE_FILAMENT_VULKAN) -#include "experimental/filament/render_context_filament.h" -#include "experimental/platform/plugin.h" -#elif defined(USE_CLASSIC_OPENGL) +#if defined(MUJOCO_RENDERER_CLASSIC_OPENGL) #include #include #else -#error No rendering mode defined. +#include "experimental/filament/render_context_filament.h" +#include "experimental/platform/plugin.h" #endif namespace mujoco::platform { Renderer::Renderer(void* native_window) : native_window_(native_window) { -#ifdef USE_CLASSIC_OPENGL +#ifdef MUJOCO_RENDERER_CLASSIC_OPENGL ImGui_ImplOpenGL3_Init(); #endif } @@ -44,16 +43,18 @@ void Renderer::Init(const mjModel* model) { if (model) { mjr_defaultContext(&render_context_); -#if defined(USE_CLASSIC_OPENGL) +#if defined(MUJOCO_RENDERER_CLASSIC_OPENGL) mjr_makeContext(model, &render_context_, mjFONTSCALE_150); #else mjrFilamentConfig render_config; mjr_defaultFilamentConfig(&render_config); render_config.native_window = native_window_; render_config.enable_gui = true; -#if defined(USE_FILAMENT_OPENGL) +#if defined(MUJOCO_RENDERER_FILAMENT_OPENGL) render_config.graphics_api = mjGFX_OPENGL; -#elif defined(USE_FILAMENT_VULKAN) +#elif defined(MUJOCO_RENDERER_FILAMENT_OPENGL_HEADLESS) + render_config.graphics_api = mjGFX_OPENGL; +#elif defined(MUJOCO_RENDERER_FILAMENT_VULKAN) render_config.graphics_api = mjGFX_VULKAN; #endif mjr_makeFilamentContext(model, &render_context_, &render_config); @@ -92,7 +93,7 @@ void Renderer::Render(const mjModel* model, mjData* data, width * height * 3); } - #ifdef USE_CLASSIC_OPENGL + #ifdef MUJOCO_RENDERER_CLASSIC_OPENGL mjr_resizeOffscreen(width, height, &render_context_); mjr_setBuffer(mjFB_OFFSCREEN, &render_context_); #else @@ -108,7 +109,7 @@ void Renderer::Render(const mjModel* model, mjData* data, // The filament backend knows how to renders the ImGui draw data. For the // classic backend, we need to render the ImGui draw data ourselves. - #ifdef USE_CLASSIC_OPENGL + #ifdef MUJOCO_RENDERER_CLASSIC_OPENGL ImGui_ImplOpenGL3_NewFrame(); ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); @@ -141,7 +142,7 @@ void Renderer::RenderToTexture(const mjModel* model, mjData* data, int Renderer::UploadImage(int texture_id, const std::byte* pixels, int width, int height, int bpp) { -#if defined(USE_CLASSIC_OPENGL) +#if defined(MUJOCO_RENDERER_CLASSIC_OPENGL) return 0; #else return mjr_uploadGuiImage(texture_id, @@ -153,7 +154,7 @@ int Renderer::UploadImage(int texture_id, const std::byte* pixels, int width, double Renderer::GetFps() { return fps_; } void Renderer::UpdateFps() { -#ifdef USE_CLASSIC_OPENGL +#ifdef MUJOCO_RENDERER_CLASSIC_OPENGL TimePoint now = std::chrono::steady_clock::now(); TimePoint::duration delta_time = now - last_fps_update_; const double interval = std::chrono::duration(delta_time).count(); @@ -168,9 +169,25 @@ void Renderer::UpdateFps() { #endif } +RendererBackend Renderer::GetBackend() { +#if defined(MUJOCO_RENDERER_FILAMENT_OPENGL_HEADLESS) + return RendererBackend::FilamentOpenGlHeadless; +#elif defined(MUJOCO_RENDERER_FILAMENT_OPENGL) + return RendererBackend::FilamentOpenGl; +#elif defined(MUJOCO_RENDERER_FILAMENT_VULKAN) + return RendererBackend::FilamentVulkan; +#elif defined(MUJOCO_RENDERER_FILAMENT_WEBGL) + return RendererBackend::FilamentWebGl; +#elif defined(MUJOCO_RENDERER_CLASSIC_OPENGL) + return RendererBackend::ClassicOpenGl; +#else + #error "Unsupported renderer backend." +#endif +} + } // namespace mujoco::platform -#if !defined(USE_CLASSIC_OPENGL) +#if !defined(MUJOCO_RENDERER_CLASSIC_OPENGL) mjPLUGIN_LIB_INIT { mujoco::platform::GuiPlugin plugin; plugin.name = "Filament"; diff --git a/src/experimental/platform/renderer.h b/src/experimental/platform/renderer.h index b3066c8d..05091c0a 100644 --- a/src/experimental/platform/renderer.h +++ b/src/experimental/platform/renderer.h @@ -21,11 +21,33 @@ #include #include +#include "experimental/platform/renderer_backend.h" namespace mujoco::platform { -// Renders the mujoco simulation and the imgui state into the active window -// using the filament rendering backend. +// Renders the mujoco simulation and the imgui state. +// +// The Renderer is built around a specific backend configuration +// (see renderer_backend.h). +// +// Currently we support two rendering engines: the Classic MuJoCo OpenGL +// renderer and a Filament-based renderer. +// +// The Classic renderer is implemented using the OpenGL 2.0 fixed function +// pipeline. It assumes that the caller has correctly initialized the OpenGL +// context (e.g. using EGL). +// +// The Filament renderer is a modern physically-based renderer that supports +// OpenGL 3.0, Vulkan, and WebGL (as well as other graphics libraries). Unlike +// the Classic renderer, the Filament engine itself will manage its graphics +// context. If rendering to a window surface (e.g. x11), it requires a pointer +// to the native window to do so. +// +// For OpenGL, two different Filament configurations are available: normal and +// headless. Normal rendering assumes that we will be rendering to an x11 +// window and therefore will use a x11-based context. Headless assumes that we +// will be rendering to a texture and will use an EGL context. Vulkan and WebGL +// have no need for such a distinction. class Renderer { public: using Clock = std::chrono::steady_clock; @@ -66,6 +88,10 @@ class Renderer { // Returns the current frame rate. double GetFps(); + // Returns the statically-defined backend for which this renderer is + // configured. + static RendererBackend GetBackend(); + private: // Resets the renderer; no rendering will occur until Init() is called again. void Deinit(); diff --git a/src/experimental/platform/renderer_backend.h b/src/experimental/platform/renderer_backend.h new file mode 100644 index 00000000..32ddaee0 --- /dev/null +++ b/src/experimental/platform/renderer_backend.h @@ -0,0 +1,47 @@ +// Copyright 2026 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_PLATFORM_RENDERER_BACKEND_H_ +#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_RENDERER_BACKEND_H_ + +namespace mujoco::platform { + +// Describes the configuration of the renderer backend. +// +// This is generally a combination of two things. The first describes the +// high-level rendering engine that sits behind the MuJoCo API (e.g. +// mjr_render). The second describes the low-level graphics engine that is +// powering the rendering (e.g. OpenGL, Vulkan, etc.). +// +// More details about the backends can be found in renderer.h. +enum class RendererBackend { + // The classic MuJoCo OpenGL renderer. + ClassicOpenGl, + + // The Filament-based renderer running on OpenGL. + FilamentOpenGl, + + // The Filament-based renderer running on Vulkan. + FilamentVulkan, + + // The Filament-based renderer running on WebGL. + FilamentWebGl, + + // Similar to FilamentOpenGl, but assumes "headless" rendering. + FilamentOpenGlHeadless, +}; + +} // namespace mujoco::platform + +#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_RENDERER_BACKEND_H_ diff --git a/src/experimental/platform/window.cc b/src/experimental/platform/window.cc index ec138a87..823b7a68 100644 --- a/src/experimental/platform/window.cc +++ b/src/experimental/platform/window.cc @@ -36,6 +36,7 @@ #include #include #include +#include "experimental/platform/renderer_backend.h" #include "user/user_resource.h" // Because X11/Xlib.h defines Status. @@ -98,7 +99,11 @@ static void InitImGui(SDL_Window* window, float content_scale, bool load_fonts, Window::Window(std::string_view title, int width, int height, Config config) : width_(width), height_(height), config_(config) { - const RenderConfig render_config = config_.render_config; + const RendererBackend renderer_backend = config_.renderer_backend; + if (renderer_backend == RendererBackend::FilamentOpenGlHeadless) { + config_.offscreen_mode = true; + } + if (config_.offscreen_mode) { SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); SDL_SetHint(SDL_HINT_FRAMEBUFFER_ACCELERATION, "0"); @@ -118,20 +123,21 @@ Window::Window(std::string_view title, int width, int height, Config config) } int window_flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; - if (render_config == kFilamentVulkan) { + if (renderer_backend == RendererBackend::FilamentVulkan) { window_flags |= SDL_WINDOW_VULKAN; - } else if (render_config == kFilamentWebGL) { + } else if (renderer_backend == RendererBackend::FilamentWebGl) { window_flags |= SDL_WINDOW_OPENGL; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - } else if (render_config == kClassicOpenGL || - render_config == kFilamentOpenGL) { + } else if (renderer_backend == RendererBackend::ClassicOpenGl || + renderer_backend == RendererBackend::FilamentOpenGl || + renderer_backend == RendererBackend::FilamentOpenGlHeadless) { window_flags |= SDL_WINDOW_OPENGL; SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); } else { - mju_error("Unsupported window config: %d", render_config); + mju_error("Unsupported window config: %d", renderer_backend); } const float content_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); @@ -143,17 +149,18 @@ Window::Window(std::string_view title, int width, int height, Config config) } InitImGui(sdl_window_, content_scale, config.load_fonts, - (render_config != kClassicOpenGL)); + (renderer_backend != RendererBackend::ClassicOpenGl)); - if (render_config == kFilamentWebGL || - (render_config == kClassicOpenGL && !config.offscreen_mode)) { + if (renderer_backend == RendererBackend::FilamentWebGl || + (renderer_backend == RendererBackend::ClassicOpenGl && + !config.offscreen_mode)) { SDL_GLContext gl_context = SDL_GL_CreateContext(sdl_window_); SDL_GL_MakeCurrent(sdl_window_, gl_context); } - if (config.offscreen_mode) { + if (config_.offscreen_mode) { sdl_renderer_ = SDL_CreateRenderer(sdl_window_, -1, SDL_RENDERER_SOFTWARE); - if (config.render_config == kClassicOpenGL) { + if (renderer_backend == RendererBackend::ClassicOpenGl) { InitOffscreenEglContext(); } } @@ -162,7 +169,7 @@ Window::Window(std::string_view title, int width, int height, Config config) SDL_VERSION(&wmi.version); SDL_GetWindowWMInfo(sdl_window_, &wmi); - if (!config.offscreen_mode) { + if (!config_.offscreen_mode) { #if defined(__linux__) native_window_ = reinterpret_cast(wmi.info.x11.window); #elif defined(__WIN32__) @@ -270,7 +277,7 @@ void Window::Present(std::span pixels) { } } - if (config_.render_config == kClassicOpenGL) { + if (config_.renderer_backend == RendererBackend::ClassicOpenGl) { dst = static_cast(surface->pixels); for (int r = 0; r < height_ / 2; ++r) { unsigned char* top_row = &dst[4 * width_ * r]; @@ -280,8 +287,8 @@ void Window::Present(std::span pixels) { } SDL_RenderPresent(sdl_renderer_); - } else if (config_.render_config != kFilamentVulkan - && config_.render_config != kFilamentOpenGL) { + } else if (config_.renderer_backend != RendererBackend::FilamentVulkan + && config_.renderer_backend != RendererBackend::FilamentOpenGl) { SDL_GL_SwapWindow(sdl_window_); } } diff --git a/src/experimental/platform/window.h b/src/experimental/platform/window.h index df1baec7..61394375 100644 --- a/src/experimental/platform/window.h +++ b/src/experimental/platform/window.h @@ -20,6 +20,8 @@ #include #include +#include "experimental/platform/renderer_backend.h" + #if __has_include("third_party/GL/gl/include/EGL/egl.h") #define MUJOCO_STUDIO_EGL_SUPPORTED 1 #include "third_party/GL/gl/include/EGL/egl.h" @@ -35,16 +37,8 @@ namespace mujoco::platform { // handles events from the window. class Window { public: - // Configures the window for the specified rendering backend. - enum RenderConfig { - kClassicOpenGL, - kFilamentVulkan, - kFilamentOpenGL, - kFilamentWebGL, - }; - struct Config { - RenderConfig render_config = kClassicOpenGL; + RendererBackend renderer_backend = RendererBackend::ClassicOpenGl; bool enable_keyboard = true; bool load_fonts = true; bool offscreen_mode = false; diff --git a/src/experimental/studio/app.cc b/src/experimental/studio/app.cc index f0068af7..bb796f2e 100644 --- a/src/experimental/studio/app.cc +++ b/src/experimental/studio/app.cc @@ -46,17 +46,6 @@ namespace mujoco::studio { -static constexpr platform::Window::RenderConfig kRenderConfig = -#if defined(__EMSCRIPTEN__) - platform::Window::RenderConfig::kFilamentWebGL; -#elif defined(USE_FILAMENT_VULKAN) - platform::Window::RenderConfig::kFilamentVulkan; -#elif defined(USE_FILAMENT_OPENGL) - platform::Window::RenderConfig::kFilamentOpenGL; -#elif defined(USE_CLASSIC_OPENGL) - platform::Window::RenderConfig::kClassicOpenGL; -#endif - static void ToggleFlag(mjtByte& flag) { flag = flag ? 0 : 1; } static void ToggleWindow(bool& window) { @@ -115,7 +104,7 @@ static constexpr std::array kPercentRealTime = { App::App(Config config) : ini_path_(std::move(config.ini_path)) { platform::Window::Config window_config; - window_config.render_config = kRenderConfig; + window_config.renderer_backend = platform::Renderer::GetBackend(); window_config.offscreen_mode = config.offscreen_mode; window_ = std::make_unique("MuJoCo Studio", config.width, config.height, window_config);