studio: replace separate Solver and Performance charts with a unified Profiler, bound to F3

Combines the "Solver" and "Performance" chart windows into a single "Profiler" window for better ergonomics and visual layout:
- Unified Dashboard: Groups the Solver block (Counts & Convergence) and the Performance block (Dimensions & CPU Time) in a single viewable window that scales cleanly without vertical scrollbars.
- Adaptive Stacking: Uses ImGui grouping so each block internally stacks its charts vertically, but tiles side-by-side horizontally when the window is stretched wide.
- Default Docking & Width: Integrates the Profiler into the default dockspace layout attached to the right of the main viewable area, with an optimized initial width scaling factor of 0.42.
- Shortcut and Menus: Adds a toggle shortcut bound to `F3`, updates the Help Shortcuts overlay, and replaces old separate entries in the Charts menu.

PiperOrigin-RevId: 912491435
Change-Id: I1f9215e5498866a01ce48b6aeb4a7461191fa01d
This commit is contained in:
Yuval Tassa
2026-05-08 06:24:53 -07:00
committed by Copybara-Service
parent 395a002801
commit 531a571dda
5 changed files with 77 additions and 44 deletions
+57
View File
@@ -28,6 +28,7 @@
#include <implot.h>
#include <mujoco/mujoco.h>
#include "experimental/platform/helpers.h"
#include "experimental/platform/sim/sim_profiler.h"
#include "experimental/platform/sim/step_control.h"
#include "experimental/platform/ux/imgui_widgets.h"
#include "experimental/platform/ux/interaction.h"
@@ -275,6 +276,9 @@ ImVec4 ConfigureDockingLayout() {
ImGui::DockBuilderSplitNode(inspector, ImGuiDir_Down, kStatsRelHeight,
&properties, &inspector);
ImGuiID profiler = 0;
ImGui::DockBuilderSplitNode(main, ImGuiDir_Right, 0.42f, &profiler, &main);
ImGui::DockBuilderDockWindow("Dockspace", main);
ImGui::DockBuilderDockWindow("Options", options);
ImGui::DockBuilderDockWindow("Explorer", inspector);
@@ -282,6 +286,7 @@ ImVec4 ConfigureDockingLayout() {
ImGui::DockBuilderDockWindow("Inspector", inspector);
ImGui::DockBuilderDockWindow("Properties", properties);
ImGui::DockBuilderDockWindow("Stats", stats);
ImGui::DockBuilderDockWindow("Profiler", profiler);
ImGui::DockBuilderFinish(root);
}
@@ -1392,4 +1397,56 @@ void StatsGui(const mjModel* model, const mjData* data, bool paused,
ImGui::Columns();
}
void ProfilerGui(const mjModel* model, mjData* data, SimProfiler* profiler) {
ImGui::SetWindowFontScale(0.8f);
ImVec2 avail = ImGui::GetContentRegionAvail();
const float pad = ImGui::GetStyle().ItemSpacing.x;
const float aspect = avail.y > 0 ? avail.x / avail.y : 1.0f;
ImVec2 plot_size;
int cols;
if (aspect < 0.8f) {
plot_size.x = avail.x;
plot_size.y = (avail.y - pad * 3.0f) * 0.25f;
cols = 1;
} else if (aspect < 1.8f) {
plot_size.x = (avail.x - pad) * 0.5f;
plot_size.y = (avail.y - pad) * 0.5f;
cols = 2;
} else {
plot_size.x = (avail.x - pad * 3.0f) * 0.25f;
plot_size.y = avail.y;
cols = 4;
}
int current_col = 0;
auto advance = [&]() {
current_col++;
if (current_col < cols) {
ImGui::SameLine();
} else {
current_col = 0;
}
};
if (cols == 2) {
// In 2x2 layout, vertically stack charts with the same x-axis.
CountsGui(model, data, plot_size);
advance();
profiler->DimensionsGraph(plot_size);
advance();
ConvergenceGui(model, data, plot_size);
advance();
profiler->CpuTimeGraph(plot_size);
} else {
CountsGui(model, data, plot_size);
advance();
ConvergenceGui(model, data, plot_size);
advance();
profiler->DimensionsGraph(plot_size);
advance();
profiler->CpuTimeGraph(plot_size);
}
}
} // namespace mujoco::platform
+4
View File
@@ -28,6 +28,7 @@
#include <imgui.h>
#include <mujoco/mujoco.h>
#include "experimental/platform/sim/sim_profiler.h"
#include "experimental/platform/sim/step_control.h"
namespace mujoco::platform {
@@ -148,6 +149,9 @@ void ConvergenceGui(const mjModel* model, mjData* data,
void CountsGui(const mjModel* model, mjData* data,
ImVec2 plot_size = ImVec2(-1, 0));
// UX for Profiler panel combining Solver and Performance metrics.
void ProfilerGui(const mjModel* model, mjData* data, SimProfiler* profiler);
// UX for displaying basic simulation information. Note that the pause state and
// FPS needs to be tracked by the caller and passed here to be displayed.
void StatsGui(const mjModel* model, const mjData* data, bool paused, float fps);
@@ -382,10 +382,10 @@ ImPlotFlags ImPlot_SetupPlotFlags(ImVec2 plot_size) {
ImPlotFlags flags = ImPlotFlags_None;
if (plot_size.x > 0 && plot_size.y > 0) {
const float min_dim = std::min(plot_size.x, plot_size.y);
if (min_dim < 300) {
if (min_dim < 150) {
flags |= ImPlotFlags_NoTitle;
}
if (min_dim < 200) {
if (min_dim < 140) {
flags |= ImPlotFlags_NoLegend;
}
}
@@ -395,7 +395,7 @@ ImPlotFlags ImPlot_SetupPlotFlags(ImVec2 plot_size) {
void ImPlot_SetupTimeAxis(ImVec2 plot_size, const char* label,
ImPlotAxisFlags extra_flags) {
ImPlotAxisFlags flags = extra_flags;
if (plot_size.x > 0 && plot_size.x < 300) {
if (plot_size.x > 0 && plot_size.x < 180) {
flags |= ImPlotAxisFlags_NoTickLabels;
}
ImPlot::SetupAxis(ImAxis_X1, label, flags);
@@ -404,7 +404,7 @@ void ImPlot_SetupTimeAxis(ImVec2 plot_size, const char* label,
void ImPlot_SetupValueAxis(ImVec2 plot_size, const char* label,
const char* format, ImPlotAxisFlags extra_flags) {
ImPlotAxisFlags flags = extra_flags;
if (plot_size.y > 0 && plot_size.y < 150) {
if (plot_size.y > 0 && plot_size.y < 90) {
flags |= ImPlotAxisFlags_NoTickLabels;
}
ImPlot::SetupAxis(ImAxis_Y1, label, flags);
@@ -418,7 +418,7 @@ void ImPlot_SetupFixedAxis(ImVec2 plot_size, double y_min, double y_max,
const double* tick_values,
const char* const* tick_labels, int n_ticks) {
ImPlotAxisFlags flags = ImPlotAxisFlags_None;
if (plot_size.y > 0 && plot_size.y < 150) {
if (plot_size.y > 0 && plot_size.y < 90) {
flags |= ImPlotAxisFlags_NoTickLabels;
}
ImPlot::SetupAxis(ImAxis_Y1, label, flags);
+10 -37
View File
@@ -625,14 +625,12 @@ void App::HandleKeyboardEvents() {
ToggleWindow(tmp_.help);
} else if (ImGui_IsChordJustPressed(ImGuiKey_F2)) {
ToggleWindow(tmp_.stats);
} else if (ImGui_IsChordJustPressed(ImGuiKey_F3)) {
ToggleWindow(tmp_.profiler);
} else if (ImGui_IsChordJustPressed(ImGuiKey_F6)) {
vis_options_.frame = (vis_options_.frame + 1) % mjNFRAME;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F7)) {
vis_options_.label = (vis_options_.label + 1) % mjNLABEL;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F9)) {
tmp_.chart_solver = !tmp_.chart_solver;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F10)) {
tmp_.chart_performance = !tmp_.chart_performance;
} else if (ImGui_IsChordJustPressed(ImGuiKey_F11)) {
tmp_.full_screen = !tmp_.full_screen;
} else if (ImGui_IsChordJustPressed(ImGuiKey_H)) {
@@ -887,30 +885,10 @@ void App::BuildGui() {
ImGui::End();
}
if (tmp_.chart_performance) {
ImGui::SetNextWindowPos(chart_pos, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(chart_size, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Performance", &tmp_.chart_performance)) {
auto layout = platform::ImPlot_ComputePairLayout();
profiler_.CpuTimeGraph(layout.plot_size);
if (layout.direction == platform::ImPlotLayoutDirection::kHorizontal) {
ImGui::SameLine();
}
profiler_.DimensionsGraph(layout.plot_size);
}
ImGui::End();
}
if (tmp_.chart_solver) {
ImGui::SetNextWindowPos(chart_pos, ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(chart_size, ImGuiCond_FirstUseEver);
if (ImGui::Begin("Solver", &tmp_.chart_solver)) {
auto layout = platform::ImPlot_ComputePairLayout();
platform::CountsGui(model(), data(), layout.plot_size);
if (layout.direction == platform::ImPlotLayoutDirection::kHorizontal) {
ImGui::SameLine();
}
platform::ConvergenceGui(model(), data(), layout.plot_size);
if (tmp_.profiler) {
if (ImGui::Begin("Profiler", &tmp_.profiler,
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
platform::ProfilerGui(model(), data(), &profiler_);
}
ImGui::End();
}
@@ -1311,10 +1289,9 @@ void App::HelpGui() {
ImGui::Text("Help");
ImGui::Text("Stats");
ImGui::Text("Profiler");
ImGui::Text("Cycle Frames");
ImGui::Text("Cycle Labels");
ImGui::Text("Solver Charts");
ImGui::Text("Perf. Charts");
ImGui::Text("Toggle Fullscreen");
ImGui::Text("Free Camera");
ImGui::Text("Toggle Pause");
@@ -1338,10 +1315,9 @@ void App::HelpGui() {
ImGui::Indent(indent);
ImGui::Text("F1");
ImGui::Text("F2");
ImGui::Text("F3");
ImGui::Text("F6");
ImGui::Text("F7");
ImGui::Text("F9");
ImGui::Text("F10");
ImGui::Text("F11");
ImGui::Text("Esc");
ImGui::Text("Spc");
@@ -1733,11 +1709,8 @@ void App::MainMenuGui() {
}
if (ImGui::BeginMenu("Charts")) {
if (ImGui::MenuItem("Solver", "F9")) {
tmp_.chart_solver = !tmp_.chart_solver;
}
if (ImGui::MenuItem("Performance", "F10")) {
tmp_.chart_performance = !tmp_.chart_performance;
if (ImGui::MenuItem("Profiler", "F3")) {
ToggleWindow(tmp_.profiler);
}
ImGui::EndMenu();
}
+1 -2
View File
@@ -120,8 +120,7 @@ class App {
// Windows.
bool help = false;
bool stats = false;
bool chart_solver = false;
bool chart_performance = false;
bool profiler = false;
bool picture_in_picture = false;
bool options_panel = true;
bool inspector_panel = true;