From b9fa8ee6e945f27dab076d20529f15d269477c06 Mon Sep 17 00:00:00 2001 From: Haroon Qureshi Date: Fri, 2 Jan 2026 04:22:34 -0800 Subject: [PATCH] Move all specialized rendering logic into Renderer class. (We may consider splitting this class up into specialized subclasses at a later date.) PiperOrigin-RevId: 851290789 Change-Id: Iee00b8e906eeb911de5799f784475ac6916ab55b --- src/experimental/platform/CMakeLists.txt | 19 +++++ src/experimental/platform/renderer.cc | 96 ++++++++++++++++++++++-- src/experimental/platform/renderer.h | 30 +++++--- src/experimental/studio/CMakeLists.txt | 19 ----- src/experimental/studio/app.cc | 93 ++--------------------- src/experimental/studio/app.h | 16 ---- 6 files changed, 135 insertions(+), 138 deletions(-) diff --git a/src/experimental/platform/CMakeLists.txt b/src/experimental/platform/CMakeLists.txt index 527c528f..a7d4cdd3 100644 --- a/src/experimental/platform/CMakeLists.txt +++ b/src/experimental/platform/CMakeLists.txt @@ -16,8 +16,25 @@ cmake_minimum_required(VERSION 3.16) 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_DEPS "mujoco::filament") +elseif(MUJOCO_USE_FILAMENT) + set(MUJOCO_PLATFORM_RENDER_CONFIG "USE_FILAMENT_OPENGL") + set(MUJOCO_PLATFORM_RENDER_DEPS "mujoco::filament") +else() + set(MUJOCO_PLATFORM_RENDER_CONFIG "USE_CLASSIC_OPENGL") + set(MUJOCO_PLATFORM_RENDER_DEPS "dear_imgui_OpenGL3") +endif() + add_library(${MUJOCO_PLATFORM_TARGET_NAME} STATIC) +target_compile_definitions(${MUJOCO_STUDIO_TARGET_NAME} + PUBLIC + -D${MUJOCO_PLATFORM_RENDER_CONFIG} +) + target_sources(${MUJOCO_PLATFORM_TARGET_NAME} PUBLIC gui.cc @@ -61,9 +78,11 @@ include(third_party_deps/libwebp) target_link_libraries(${MUJOCO_PLATFORM_TARGET_NAME} dear_imgui + dear_imgui_SDL2 implot webp SDL2::SDL2-static + ${MUJOCO_PLATFORM_RENDER_DEPS} ) add_library(mujoco::platform ALIAS ${MUJOCO_PLATFORM_TARGET_NAME}) diff --git a/src/experimental/platform/renderer.cc b/src/experimental/platform/renderer.cc index 15de97cd..391ab421 100644 --- a/src/experimental/platform/renderer.cc +++ b/src/experimental/platform/renderer.cc @@ -14,14 +14,34 @@ #include "experimental/platform/renderer.h" +#include +#include +#include +#include +#include +#include + #include #include +#include "experimental/platform/helpers.h" + +#if defined(USE_FILAMENT_OPENGL) || defined(USE_FILAMENT_VULKAN) +#include "experimental/filament/render_context_filament.h" +#elif defined(USE_CLASSIC_OPENGL) +#include +#include +#else +#error No rendering mode defined. +#endif namespace mujoco::platform { -Renderer::Renderer(MakeContextFn make_context_fn) - : make_context_fn_(make_context_fn) { +Renderer::Renderer(void* native_window, const LoadAssetFn& load_asset_fn) + : load_asset_fn_(load_asset_fn), native_window_(native_window) { +#ifdef USE_CLASSIC_OPENGL + ImGui_ImplOpenGL3_Init(); +#endif } Renderer::~Renderer() { Deinit(); } @@ -30,7 +50,24 @@ void Renderer::Init(const mjModel* model) { Deinit(); if (model) { mjr_defaultContext(&render_context_); - make_context_fn_(model, &render_context_); + +#if defined(USE_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.load_asset = &Renderer::LoadAssetCallback; + render_config.load_asset_user_data = this; + render_config.enable_gui = true; +#if defined(USE_FILAMENT_OPENGL) + render_config.graphics_api = mjGFX_OPENGL; +#elif defined(USE_FILAMENT_VULKAN) + render_config.graphics_api = mjGFX_VULKAN; +#endif + mjr_makeFilamentContext(model, &render_context_, &render_config); +#endif + mjv_defaultScene(&scene_); mjv_makeScene(model, &scene_, 2000); initialized_ = true; @@ -52,17 +89,43 @@ void Renderer::Render(const mjModel* model, mjData* data, return; } - mjv_updateScene(model, data, vis_option, perturb, camera, mjCAT_ALL, - &scene_); + if (last_update_time_ == data->time) { + mjv_updateCamera(model, data, camera, &scene_); + } else { + mjv_updateScene(model, data, vis_option, perturb, camera, mjCAT_ALL, + &scene_); + last_update_time_ = data->time; + } const mjrRect viewport = {0, 0, width, height}; mjr_render(viewport, &scene_, &render_context_); + +#ifdef USE_CLASSIC_OPENGL + ImGui_ImplOpenGL3_NewFrame(); + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +#endif + +#ifdef USE_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(); + + ++frames_; + if (interval > 0.2) { // only update FPS stat at most 5 times per second + last_fps_update_ = now; + fps_ = frames_ / interval; + frames_ = 0; + } +#else + fps_ = mjr_getFrameRate(&render_context_); +#endif } void Renderer::RenderToTexture(const mjModel* model, mjData* data, mjvCamera* camera, int width, int height, std::byte* output) { - if (!initialized_) { + if (!initialized_ || last_update_time_ == -1) { return; } @@ -75,4 +138,25 @@ void Renderer::RenderToTexture(const mjModel* model, mjData* data, mjr_setBuffer(mjFB_WINDOW, &render_context_); } +double Renderer::GetFps() { return fps_; } + +int Renderer::LoadAssetCallback(const char* path, void* user_data, + unsigned char** out, std::uint64_t* out_size) { + Renderer* renderer = static_cast(user_data); + std::vector bytes = (renderer->load_asset_fn_)(path); + if (bytes.empty()) { + *out_size = 0; + return 0; // Empty file + } + + *out_size = bytes.size(); + *out = reinterpret_cast(malloc(*out_size)); + if (*out == nullptr) { + mju_error("Failed to allocate memory for file %s", path); + return -1; + } + + std::memcpy(*out, bytes.data(), *out_size); + return 0; +} } // namespace mujoco::platform diff --git a/src/experimental/platform/renderer.h b/src/experimental/platform/renderer.h index b89413bc..881d33ab 100644 --- a/src/experimental/platform/renderer.h +++ b/src/experimental/platform/renderer.h @@ -15,10 +15,13 @@ #ifndef MUJOCO_SRC_EXPERIMENTAL_PLATFORM_RENDERER_H_ #define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_RENDERER_H_ +#include #include -#include +#include +#include #include +#include "experimental/platform/helpers.h" namespace mujoco::platform { @@ -26,12 +29,12 @@ namespace mujoco::platform { // using the filament rendering backend. class Renderer { public: - // Function that creates a mjrContext for the given model. We use a function - // to allow different mjrContext implementations to be created without - // requiring a direct dependency on them. - using MakeContextFn = std::function; + using Clock = std::chrono::steady_clock; + using TimePoint = std::chrono::time_point; + using Seconds = std::chrono::duration; + using Milliseconds = std::chrono::duration; - explicit Renderer(MakeContextFn make_context_fn); + Renderer(void* native_window, const LoadAssetFn& load_asset_fn); ~Renderer(); Renderer(const Renderer&) = delete; @@ -54,18 +57,25 @@ class Renderer { // Rendering flags. mjtByte* GetRenderFlags() { return scene_.flags; } - // Returns the render context. - mjrContext& GetContext() { return render_context_; } - const mjrContext& GetContext() const { return render_context_; } + // Returns the current frame rate. + double GetFps(); private: // Resets the renderer; no rendering will occur until Init() is called again. void Deinit(); - MakeContextFn make_context_fn_; + static int LoadAssetCallback(const char* path, void* user_data, + unsigned char** out, std::uint64_t* out_size); + + LoadAssetFn load_asset_fn_; + void* native_window_ = nullptr; mjrContext render_context_; mjvScene scene_; bool initialized_ = false; + mjtNum last_update_time_ = -1; + int frames_ = 0; + TimePoint last_fps_update_; + double fps_ = 0; }; } // namespace mujoco::platform diff --git a/src/experimental/studio/CMakeLists.txt b/src/experimental/studio/CMakeLists.txt index 6701d5bd..f74fa54a 100644 --- a/src/experimental/studio/CMakeLists.txt +++ b/src/experimental/studio/CMakeLists.txt @@ -14,17 +14,6 @@ cmake_minimum_required(VERSION 3.16) -# Determine the render configuration and dependencies based options. -if(MUJOCO_USE_FILAMENT AND MUJOCO_USE_FILAMENT_VULKAN) - set(MUJOCO_STUDIO_RENDER_CONFIG "USE_FILAMENT_VULKAN") - set(MUJOCO_STUDIO_RENDER_DEPS "mujoco::filament") -elseif(MUJOCO_USE_FILAMENT) - set(MUJOCO_STUDIO_RENDER_CONFIG "USE_FILAMENT_OPENGL") - set(MUJOCO_STUDIO_RENDER_DEPS "mujoco::filament") -else() - set(MUJOCO_STUDIO_RENDER_CONFIG "USE_CLASSIC_OPENGL") -endif() - set(MUJOCO_STUDIO_TARGET_NAME mujoco_studio) add_executable(${MUJOCO_STUDIO_TARGET_NAME}) @@ -42,11 +31,6 @@ target_include_directories(${MUJOCO_STUDIO_TARGET_NAME} ${PROJECT_SOURCE_DIR}/src ) -target_compile_definitions(${MUJOCO_STUDIO_TARGET_NAME} - PRIVATE - -D${MUJOCO_STUDIO_RENDER_CONFIG} -) - if (WIN32) target_compile_definitions(${MUJOCO_STUDIO_TARGET_NAME} PRIVATE @@ -65,11 +49,8 @@ include(third_party_deps/font_awesome) target_link_libraries(${MUJOCO_STUDIO_TARGET_NAME} PRIVATE - ${MUJOCO_STUDIO_RENDER_DEPS} absl::flags dear_imgui - dear_imgui_OpenGL3 - dear_imgui_SDL2 implot mujoco::mujoco mujoco::platform diff --git a/src/experimental/studio/app.cc b/src/experimental/studio/app.cc index 7fa62314..30cd48c1 100644 --- a/src/experimental/studio/app.cc +++ b/src/experimental/studio/app.cc @@ -17,16 +17,11 @@ #include #include #include -#if defined(USE_CLASSIC_OPENGL) -#include -#endif #include -#include #include #include #include #include -#include #include #include #include @@ -45,14 +40,6 @@ #include "experimental/platform/step_control.h" #include "experimental/platform/window.h" -#if defined(USE_FILAMENT_OPENGL) || defined(USE_FILAMENT_VULKAN) -#include "experimental/filament/render_context_filament.h" -#elif defined(USE_CLASSIC_OPENGL) -#include -#else -#error No rendering mode defined. -#endif - namespace mujoco::studio { static constexpr platform::Window::Config kWindowConfig = { @@ -65,7 +52,6 @@ static constexpr platform::Window::Config kWindowConfig = { #elif defined(USE_CLASSIC_OPENGL) .render_config = platform::Window::RenderConfig::kClassicOpenGL, #endif - .enable_keyboard = true, }; static void ToggleFlag(mjtByte& flag) { flag = flag ? 0 : 1; } @@ -130,37 +116,15 @@ App::App(int width, int height, std::string ini_path, : ini_path_(std::move(ini_path)), load_asset_fn_(load_asset_fn) { window_ = std::make_unique("MuJoCo Studio", width, height, kWindowConfig, load_asset_fn); + renderer_ = std::make_unique( + window_->GetNativeWindowHandle(), load_asset_fn); + ImPlot::CreateContext(); - - auto make_context_fn = [&](const mjModel* m, mjrContext* con) { -#if defined(USE_CLASSIC_OPENGL) - mjr_makeContext(m, con, mjFONTSCALE_150); -#else - mjrFilamentConfig render_config; - mjr_defaultFilamentConfig(&render_config); - render_config.native_window = window_->GetNativeWindowHandle(); - render_config.load_asset = &App::LoadAssetCallback; - render_config.load_asset_user_data = this; - render_config.enable_gui = true; -#if defined(USE_FILAMENT_OPENGL) - render_config.graphics_api = mjGFX_OPENGL; -#elif defined(USE_FILAMENT_VULKAN) - render_config.graphics_api = mjGFX_VULKAN; -#endif - mjr_makeFilamentContext(m, con, &render_config); -#endif - }; - renderer_ = std::make_unique(make_context_fn); - mjv_defaultPerturb(&perturb_); mjv_defaultCamera(&camera_); mjv_defaultOption(&vis_options_); profiler_.Clear(); - -#ifdef USE_CLASSIC_OPENGL - ImGui_ImplOpenGL3_Init(); -#endif } void App::ClearModel() { @@ -361,10 +325,6 @@ void App::LoadHistory(int offset) { bool App::Update() { const platform::Window::Status status = window_->NewFrame(); -#ifdef USE_CLASSIC_OPENGL - ImGui_ImplOpenGL3_NewFrame(); -#endif - HandleMouseEvents(); HandleKeyboardEvents(); @@ -391,12 +351,6 @@ void App::Render() { renderer_->Render(model_, data_, &perturb_, &camera_, &vis_options_, width * scale, height * scale); -#ifdef USE_CLASSIC_OPENGL - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); -#endif - - // This call to EndFrame() is only needed if render_config.enable_gui is false window_->EndFrame(); window_->Present(); @@ -406,21 +360,6 @@ void App::Render() { data_->timer[i].number = 0; } } - -#ifdef USE_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(); - - ++frames_; - if (interval > 0.2) { // only update FPS stat at most 5 times per second - last_fps_update_ = now; - fps_ = frames_ / interval; - frames_ = 0; - } -#else - fps_ = mjr_getFrameRate(&renderer_->GetContext()); -#endif } void App::HandleMouseEvents() { @@ -867,7 +806,8 @@ void App::BuildGui() { platform::ScopedStyle style; style.Var(ImGuiStyleVar_Alpha, 0.6f); if (ImGui::Begin("Stats", &tmp_.stats)) { - platform::StatsGui(model_, data_, step_control_.IsPaused(), fps_); + const float fps = renderer_->GetFps(); + platform::StatsGui(model_, data_, step_control_.IsPaused(), fps); } ImGui::End(); } @@ -1761,7 +1701,7 @@ std::vector App::GetCameraNames() { App::UiState::Dict App::UiState::ToDict() const { return { - {"theme", std::to_string(static_cast(theme))}, + {"theme", std::to_string(static_cast(theme))}, }; } @@ -1769,25 +1709,4 @@ void App::UiState::FromDict(const Dict& dict) { *this = UiState(); theme = ReadIniValue(dict, "theme", theme); } - -int App::LoadAssetCallback(const char* path, void* user_data, - unsigned char** out, std::uint64_t* out_size) { - App* app = static_cast(user_data); - std::vector bytes = (app->load_asset_fn_)(path); - if (bytes.empty()) { - *out_size = 0; - return 0; // Empty file - } - - *out_size = bytes.size(); - *out = reinterpret_cast(malloc(*out_size)); - if (*out == nullptr) { - mju_error("Failed to allocate memory for file %s", path); - return -1; - } - - std::memcpy(*out, bytes.data(), *out_size); - return 0; -} - } // namespace mujoco::studio diff --git a/src/experimental/studio/app.h b/src/experimental/studio/app.h index 180abe3a..728d428c 100644 --- a/src/experimental/studio/app.h +++ b/src/experimental/studio/app.h @@ -15,13 +15,9 @@ #ifndef MUJOCO_SRC_EXPERIMENTAL_STUDIO_APP_H_ #define MUJOCO_SRC_EXPERIMENTAL_STUDIO_APP_H_ -#include -#include #include #include -#include #include -#include #include #include @@ -40,11 +36,6 @@ namespace mujoco::studio { // Owns, updates, and renders a MuJoCo simulation. class App { public: - using Clock = std::chrono::steady_clock; - using TimePoint = std::chrono::time_point; - using Seconds = std::chrono::duration; - using Milliseconds = std::chrono::duration; - App(int width, int height, std::string ini_path, const platform::LoadAssetFn& load_asset_fn); @@ -71,9 +62,6 @@ class App { void Render(); private: - static int LoadAssetCallback(const char* path, void* user_data, - unsigned char** out, std::uint64_t* out_size); - // UI state that is persisted across application runs struct UiState { char watch_field[1000] = "qpos"; @@ -197,10 +185,6 @@ class App { UiState ui_; UiTempState tmp_; - - int frames_ = 0; - TimePoint last_fps_update_; - double fps_ = 0; }; } // namespace mujoco::studio