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
This commit is contained in:
committed by
Copybara-Service
parent
14e90f3897
commit
b9fa8ee6e9
@@ -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})
|
||||
|
||||
@@ -14,14 +14,34 @@
|
||||
|
||||
#include "experimental/platform/renderer.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#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 <imgui.h>
|
||||
#include <backends/imgui_impl_opengl3.h>
|
||||
#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<double>(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<Renderer*>(user_data);
|
||||
std::vector<std::byte> bytes = (renderer->load_asset_fn_)(path);
|
||||
if (bytes.empty()) {
|
||||
*out_size = 0;
|
||||
return 0; // Empty file
|
||||
}
|
||||
|
||||
*out_size = bytes.size();
|
||||
*out = reinterpret_cast<unsigned char*>(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
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_PLATFORM_RENDERER_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_RENDERER_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <cstdint>
|
||||
#include <ratio>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#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<void(const mjModel* m, mjrContext* con)>;
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using TimePoint = std::chrono::time_point<Clock>;
|
||||
using Seconds = std::chrono::duration<double>;
|
||||
using Milliseconds = std::chrono::duration<double, std::milli>;
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,16 +17,11 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cfloat>
|
||||
#if defined(USE_CLASSIC_OPENGL)
|
||||
#include <chrono>
|
||||
#endif
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string>
|
||||
@@ -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 <backends/imgui_impl_opengl3.h>
|
||||
#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<platform::Window>("MuJoCo Studio", width, height,
|
||||
kWindowConfig, load_asset_fn);
|
||||
renderer_ = std::make_unique<platform::Renderer>(
|
||||
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<platform::Renderer>(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<double>(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<const char*> App::GetCameraNames() {
|
||||
|
||||
App::UiState::Dict App::UiState::ToDict() const {
|
||||
return {
|
||||
{"theme", std::to_string(static_cast<int>(theme))},
|
||||
{"theme", std::to_string(static_cast<int>(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<App*>(user_data);
|
||||
std::vector<std::byte> bytes = (app->load_asset_fn_)(path);
|
||||
if (bytes.empty()) {
|
||||
*out_size = 0;
|
||||
return 0; // Empty file
|
||||
}
|
||||
|
||||
*out_size = bytes.size();
|
||||
*out = reinterpret_cast<unsigned char*>(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
|
||||
|
||||
@@ -15,13 +15,9 @@
|
||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_STUDIO_APP_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_STUDIO_APP_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <ratio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
@@ -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<Clock>;
|
||||
using Seconds = std::chrono::duration<double>;
|
||||
using Milliseconds = std::chrono::duration<double, std::milli>;
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user