First-pass of a plugin system for GUI elements.
Refactor filament's debug drawing to be one such plugin. PiperOrigin-RevId: 859002002 Change-Id: Ibe9000dec19fce648eef6d57170ce435b9a8c6ff
This commit is contained in:
committed by
Copybara-Service
parent
bf05bb1885
commit
e2180ff744
@@ -146,8 +146,6 @@ void FilamentContext::Render(const mjrRect& viewport, const mjvScene* scene,
|
||||
// Draw the GUI. We do this after processing the scene in case there are any
|
||||
// label elements in the scene.
|
||||
if (gui_view_ && !render_to_texture_) {
|
||||
DrawGui(scene_view_.get());
|
||||
|
||||
// Prepare the filament Renderable that contains the GUI draw commands. We
|
||||
// must call this function even if we do not plan on rendering the GUI to
|
||||
// ensure the ImGui state is updated.
|
||||
@@ -339,4 +337,8 @@ double FilamentContext::GetFrameRate() const {
|
||||
return 1.0e9 / static_cast<double>(ns);
|
||||
}
|
||||
|
||||
void FilamentContext::UpdateGui() {
|
||||
DrawGui(scene_view_.get());
|
||||
}
|
||||
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -56,6 +56,8 @@ class FilamentContext {
|
||||
|
||||
double GetFrameRate() const;
|
||||
|
||||
void UpdateGui();
|
||||
|
||||
FilamentContext(const FilamentContext&) = delete;
|
||||
FilamentContext& operator=(const FilamentContext&) = delete;
|
||||
|
||||
@@ -63,8 +65,6 @@ class FilamentContext {
|
||||
void PrepareRenderTargets(int width, int height);
|
||||
void DestroyRenderTargets();
|
||||
|
||||
SceneView* GetSceneView(const mjvScene* scene);
|
||||
|
||||
mjrFilamentConfig config_;
|
||||
mjrContext* context_ = nullptr;
|
||||
const mjModel* model_ = nullptr;
|
||||
|
||||
@@ -581,7 +581,7 @@ void DrawLightGui(filament::LightManager& lm,
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSceneViewGui(SceneView* scene_view) {
|
||||
void DrawGui(SceneView* scene_view) {
|
||||
filament::View* view = scene_view->GetDefaultRenderView();
|
||||
filament::Engine* engine = scene_view->GetEngine();
|
||||
filament::LightManager& lm = engine->getLightManager();
|
||||
@@ -669,28 +669,4 @@ void DrawSceneViewGui(SceneView* scene_view) {
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
void DrawGui(SceneView* scene_view) {
|
||||
static bool display = false;
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Filament", "", display)) {
|
||||
display = !display;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
if (!display) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowSize(ImVec2(400, 300), ImGuiCond_Appearing);
|
||||
ImGui::Begin("Filament");
|
||||
DrawSceneViewGui(scene_view);
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -113,4 +113,10 @@ double mjr_getFrameRate(const mjrContext* con) {
|
||||
return g_filament_context->GetFrameRate();
|
||||
}
|
||||
|
||||
void mjr_updateGui(const mjrContext* con) {
|
||||
if (g_filament_context != nullptr) {
|
||||
g_filament_context->UpdateGui();
|
||||
}
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
@@ -71,6 +71,8 @@ uintptr_t mjr_uploadGuiImage(uintptr_t tex_id, const unsigned char* pixels,
|
||||
int width, int height, int bpp,
|
||||
const mjrContext* con);
|
||||
|
||||
void mjr_updateGui(const mjrContext* con);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
@@ -47,6 +47,8 @@ target_sources(${MUJOCO_PLATFORM_TARGET_NAME}
|
||||
interaction.h
|
||||
picture_gui.h
|
||||
picture_gui.cc
|
||||
plugin.cc
|
||||
plugin.h
|
||||
renderer.cc
|
||||
renderer.h
|
||||
sim_history.cc
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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.
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "experimental/platform/plugin.h"
|
||||
#include "engine/engine_global_table.h"
|
||||
|
||||
namespace mujoco::platform {
|
||||
|
||||
void RegisterGuiPlugin(const GuiPlugin* plugin) {
|
||||
if (plugin->name == nullptr || plugin->name[0] == '\0') {
|
||||
mju_error("Plugin name must not be empty or null.");
|
||||
}
|
||||
GlobalTable<GuiPlugin>::GetSingleton().AppendIfUnique(*plugin);
|
||||
}
|
||||
|
||||
void ForEachGuiPlugin(const std::function<void(GuiPlugin*)>& fn) {
|
||||
auto& table = GlobalTable<GuiPlugin>::GetSingleton();
|
||||
for (int i = 0; i < table.count(); ++i) {
|
||||
const GuiPlugin* plugin = table.GetAtSlot(i);
|
||||
fn(const_cast<GuiPlugin*>(plugin));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
using mujoco::GlobalTable;
|
||||
using GuiPlugin = mujoco::platform::GuiPlugin;
|
||||
|
||||
template <>
|
||||
const char* GlobalTable<GuiPlugin>::HumanReadableTypeName() {
|
||||
return "gui plugin";
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string_view GlobalTable<GuiPlugin>::ObjectKey(const GuiPlugin& plugin) {
|
||||
return std::string_view(plugin.name);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool GlobalTable<GuiPlugin>::ObjectEqual(const GuiPlugin& p1, const GuiPlugin& p2) {
|
||||
return CaseInsensitiveEqual(p1.name, p2.name);
|
||||
}
|
||||
|
||||
template <>
|
||||
bool GlobalTable<GuiPlugin>::CopyObject(GuiPlugin& dst, const GuiPlugin& src, ErrorMessage& err) {
|
||||
dst = src;
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// 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_PLUGIN_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_PLUGIN_H_
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace mujoco::platform {
|
||||
|
||||
// Important: Do not inherit from these plugin structs. They are copied by value
|
||||
// and therefore any derived classes will be sliced. We assume plugins are
|
||||
// effectively globals and so any pointers will be valid for the lifetime
|
||||
// of the process.
|
||||
|
||||
// Plugin for processing custom UI windows. The plugin will be listed in the
|
||||
// "Plugins" main menu and, when selected, an ImGui window will be opened with
|
||||
// the name of the plugin as the title. The `update` function can then be used
|
||||
// to process the GUI. All functions will be called by passing `this` as the
|
||||
// first argument.
|
||||
struct GuiPlugin final {
|
||||
using UpdateFn = void (*)(GuiPlugin* self);
|
||||
|
||||
// Whether or not to display the plugin window.
|
||||
bool active = false;
|
||||
|
||||
// The name of the plugin; must be unique.
|
||||
const char* name = "";
|
||||
|
||||
// The function that will update the plugin's window. Plugin GUI updates
|
||||
// happen when the window is `active` and after all other Studio GUI updates.
|
||||
UpdateFn update = nullptr;
|
||||
|
||||
// Optional data pointer.
|
||||
void* data = nullptr;
|
||||
};
|
||||
|
||||
// Registers a plugin with a global registry. The plugin must have a
|
||||
// case-insensitive unique name.
|
||||
void RegisterGuiPlugin(const GuiPlugin* plugin);
|
||||
|
||||
// Executes the given function for each registered plugin.
|
||||
void ForEachGuiPlugin(const std::function<void(GuiPlugin*)>& fn);
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_PLUGIN_H_
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#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)
|
||||
#include <imgui.h>
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
@@ -136,3 +137,14 @@ int Renderer::UploadImage(int texture_id, const std::byte* pixels, int width,
|
||||
double Renderer::GetFps() { return fps_; }
|
||||
|
||||
} // namespace mujoco::platform
|
||||
|
||||
#if !defined(USE_CLASSIC_OPENGL)
|
||||
mjPLUGIN_LIB_INIT {
|
||||
mujoco::platform::GuiPlugin plugin;
|
||||
plugin.name = "Filament";
|
||||
plugin.update = [](mujoco::platform::GuiPlugin* self) {
|
||||
mjr_updateGui(nullptr);
|
||||
};
|
||||
mujoco::platform::RegisterGuiPlugin(&plugin);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "experimental/platform/imgui_widgets.h"
|
||||
#include "experimental/platform/interaction.h"
|
||||
#include "experimental/platform/picture_gui.h"
|
||||
#include "experimental/platform/plugin.h"
|
||||
#include "experimental/platform/renderer.h"
|
||||
#include "experimental/platform/step_control.h"
|
||||
#include "experimental/platform/window.h"
|
||||
@@ -854,6 +855,26 @@ void App::BuildGui() {
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
platform::ForEachGuiPlugin([](platform::GuiPlugin* plugin) {
|
||||
if (!plugin->update) {
|
||||
return;
|
||||
}
|
||||
if (ImGui::BeginMainMenuBar()) {
|
||||
if (ImGui::BeginMenu("Plugins")) {
|
||||
if (ImGui::MenuItem(plugin->name, "", plugin->active)) {
|
||||
plugin->active = !plugin->active;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
if (plugin->active) {
|
||||
ImGui::Begin(plugin->name);
|
||||
plugin->update(plugin);
|
||||
ImGui::End();
|
||||
}
|
||||
});
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (tmp_.first_frame) {
|
||||
LoadSettings();
|
||||
@@ -1534,6 +1555,11 @@ void App::MainMenuGui() {
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Plugins")) {
|
||||
// Placeholder menu item that will be populated by plugins later on. We
|
||||
// do this now in so that the menu is present at the right place.
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("Help", "F1", tmp_.help)) {
|
||||
ToggleWindow(tmp_.help);
|
||||
|
||||
Reference in New Issue
Block a user