Split Renderer class into explicit "classic" and "filament" classes.
PiperOrigin-RevId: 960272089 Change-Id: I9494472b7b8e838a75beaf0cd68e5ce4b42a80a4
This commit is contained in:
committed by
Copybara-Service
parent
7f305abd71
commit
63b779042c
@@ -26,8 +26,8 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <implot.h>
|
#include <implot.h>
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
|
#include <mujoco/experimental/platform/hal/filament_renderer.h>
|
||||||
#include <mujoco/experimental/platform/hal/graphics_mode.h>
|
#include <mujoco/experimental/platform/hal/graphics_mode.h>
|
||||||
#include <mujoco/experimental/platform/hal/renderer.h>
|
|
||||||
#include <mujoco/experimental/platform/hal/window.h>
|
#include <mujoco/experimental/platform/hal/window.h>
|
||||||
#include <mujoco/experimental/platform/sys_utils.h>
|
#include <mujoco/experimental/platform/sys_utils.h>
|
||||||
#include "structs.h"
|
#include "structs.h"
|
||||||
@@ -119,7 +119,7 @@ class Viewer {
|
|||||||
width, height, config);
|
width, height, config);
|
||||||
ImPlot::CreateContext();
|
ImPlot::CreateContext();
|
||||||
|
|
||||||
renderer_ = std::make_unique<mujoco::platform::Renderer>(
|
renderer_ = std::make_unique<mujoco::platform::FilamentRenderer>(
|
||||||
window_->GetNativeWindowHandle(), config.gfx_mode);
|
window_->GetNativeWindowHandle(), config.gfx_mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
#include <mujoco/experimental/platform/hal/renderer.h>
|
#include <mujoco/experimental/platform/hal/filament_renderer.h>
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -32,7 +32,7 @@ namespace mujoco::python {
|
|||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
using RendererImpl = mujoco::platform::Renderer;
|
using RendererImpl = mujoco::platform::FilamentRenderer;
|
||||||
using GraphicsMode = mujoco::platform::GraphicsMode;
|
using GraphicsMode = mujoco::platform::GraphicsMode;
|
||||||
|
|
||||||
Renderer(const std::string& graphics_mode_str) {
|
Renderer(const std::string& graphics_mode_str) {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <implot.h>
|
#include <implot.h>
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
#include "experimental/platform/hal/renderer.h"
|
#include "experimental/platform/hal/filament_renderer.h"
|
||||||
#include "experimental/platform/hal/window.h"
|
#include "experimental/platform/hal/window.h"
|
||||||
#include "experimental/platform/sim/model_holder.h"
|
#include "experimental/platform/sim/model_holder.h"
|
||||||
#include "experimental/platform/ux/interaction.h"
|
#include "experimental/platform/ux/interaction.h"
|
||||||
@@ -757,7 +757,7 @@ void StartApp() {
|
|||||||
1400, 720, config);
|
1400, 720, config);
|
||||||
ImPlot::CreateContext(); // Needed if the server app uses ImPlot.
|
ImPlot::CreateContext(); // Needed if the server app uses ImPlot.
|
||||||
|
|
||||||
g_app.renderer = new mujoco::platform::Renderer(
|
g_app.renderer = new mujoco::platform::FilamentRenderer(
|
||||||
g_app.window->GetNativeWindowHandle(), config.gfx_mode);
|
g_app.window->GetNativeWindowHandle(), config.gfx_mode);
|
||||||
|
|
||||||
// Initialize an empty dummy scene so Filament and ImGui are ready to render
|
// Initialize an empty dummy scene so Filament and ImGui are ready to render
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ target_sources(${MUJOCO_PLATFORM_TARGET_NAME}
|
|||||||
resources.h
|
resources.h
|
||||||
sys_utils.cc
|
sys_utils.cc
|
||||||
sys_utils.h
|
sys_utils.h
|
||||||
|
hal/classic_renderer.cc
|
||||||
|
hal/classic_renderer.h
|
||||||
hal/egl_utils.cc
|
hal/egl_utils.cc
|
||||||
hal/egl_utils.h
|
hal/egl_utils.h
|
||||||
|
hal/filament_renderer.cc
|
||||||
|
hal/filament_renderer.h
|
||||||
hal/graphics_mode.cc
|
hal/graphics_mode.cc
|
||||||
hal/graphics_mode.h
|
hal/graphics_mode.h
|
||||||
hal/renderer.cc
|
|
||||||
hal/renderer.h
|
hal/renderer.h
|
||||||
hal/window.cc
|
hal/window.cc
|
||||||
hal/window.h
|
hal/window.h
|
||||||
|
|||||||
@@ -0,0 +1,202 @@
|
|||||||
|
// Copyright 2025 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 "experimental/platform/hal/classic_renderer.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include <backends/imgui_impl_opengl3.h>
|
||||||
|
#include <imgui.h>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
|
#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
|
||||||
|
#include "experimental/platform/hal/egl_utils.h"
|
||||||
|
#endif
|
||||||
|
#include "experimental/platform/hal/graphics_mode.h"
|
||||||
|
|
||||||
|
namespace mujoco::platform {
|
||||||
|
|
||||||
|
static void FlipImage(unsigned char* pixels, int width, int height, int bpp) {
|
||||||
|
const int row_size = width * bpp;
|
||||||
|
for (int i = 0; i < height / 2; ++i) {
|
||||||
|
unsigned char* top_row = pixels + i * row_size;
|
||||||
|
unsigned char* bottom_row = pixels + (height - 1 - i) * row_size;
|
||||||
|
for (int j = 0; j < row_size; ++j) {
|
||||||
|
std::swap(top_row[j], bottom_row[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassicRenderer::ClassicRenderer(void* native_window, GraphicsMode gfx)
|
||||||
|
: gfx_(gfx) {
|
||||||
|
if (native_window == nullptr) {
|
||||||
|
#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
|
||||||
|
graphics_api_context_ = CreateEglContext();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (ImGui::GetCurrentContext()) {
|
||||||
|
ImGui_ImplOpenGL3_Init();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ClassicRenderer::~ClassicRenderer() {
|
||||||
|
if (ImGui::GetCurrentContext()) {
|
||||||
|
ImGui_ImplOpenGL3_Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
Deinit();
|
||||||
|
graphics_api_context_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassicRenderer::Init(const mjModel* model) {
|
||||||
|
Deinit();
|
||||||
|
if (model) {
|
||||||
|
mjr_defaultContext(&render_context_);
|
||||||
|
mjr_makeContext(model, &render_context_, mjFONTSCALE_150);
|
||||||
|
mjv_defaultScene(&scene_);
|
||||||
|
mjv_makeScene(model, &scene_, 2000);
|
||||||
|
initialized_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassicRenderer::Deinit() {
|
||||||
|
if (initialized_) {
|
||||||
|
mjv_freeScene(&scene_);
|
||||||
|
mjr_freeContext(&render_context_);
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassicRenderer::Render(const mjModel* model, mjData* data,
|
||||||
|
const mjvPerturb* perturb, mjvCamera* camera,
|
||||||
|
const mjvOption* vis_option, int width, int height,
|
||||||
|
std::span<std::byte> pixels,
|
||||||
|
std::span<mjvGeom> extra_geoms) {
|
||||||
|
if (!initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjvCamera default_cam;
|
||||||
|
if (camera == nullptr) {
|
||||||
|
if (model) {
|
||||||
|
mjv_defaultFreeCamera(model, &default_cam);
|
||||||
|
} else {
|
||||||
|
mjv_defaultCamera(&default_cam);
|
||||||
|
}
|
||||||
|
camera = &default_cam;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjvOption default_opt;
|
||||||
|
if (vis_option == nullptr) {
|
||||||
|
mjv_defaultOption(&default_opt);
|
||||||
|
vis_option = &default_opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjv_updateScene(model, data, vis_option, perturb, camera, mjCAT_ALL, &scene_);
|
||||||
|
const int nextra_geoms =
|
||||||
|
std::min<int>(extra_geoms.size(), scene_.maxgeom - scene_.ngeom);
|
||||||
|
for (int i = 0; i < nextra_geoms; ++i) {
|
||||||
|
scene_.geoms[scene_.ngeom++] = extra_geoms[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool render_to_texture = !pixels.empty();
|
||||||
|
if (render_to_texture) {
|
||||||
|
// mjr_readPixels reads to a RGB buffer (i.e. 3 bytes per pixel).
|
||||||
|
if (pixels.size() != width * height * 3) {
|
||||||
|
mju_error("Offscreen mode requires a pixel buffer of size %d.",
|
||||||
|
width * height * 3);
|
||||||
|
}
|
||||||
|
mjr_resizeOffscreen(width, height, &render_context_);
|
||||||
|
|
||||||
|
framebuffer_mode_ = mjFB_OFFSCREEN;
|
||||||
|
mjr_setBuffer(mjFB_OFFSCREEN, &render_context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mjrRect viewport = {0, 0, width, height};
|
||||||
|
mjr_render(viewport, &scene_, &render_context_);
|
||||||
|
|
||||||
|
// The filament backend knows how to renders the ImGui draw data. For the
|
||||||
|
// classic backend, we need to render the ImGui draw data ourselves.
|
||||||
|
if (ImGui::GetCurrentContext()) {
|
||||||
|
ImGui_ImplOpenGL3_NewFrame();
|
||||||
|
ImGui::Render();
|
||||||
|
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render_to_texture) {
|
||||||
|
unsigned char* ptr = reinterpret_cast<unsigned char*>(pixels.data());
|
||||||
|
DoReadPixels(width, height, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateFps();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassicRenderer::RenderToTexture(const mjModel* model, mjData* data,
|
||||||
|
mjvCamera* camera, int width, int height,
|
||||||
|
std::byte* output) {
|
||||||
|
if (!initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjv_updateCamera(model, data, camera, &scene_);
|
||||||
|
unsigned char* ptr = reinterpret_cast<unsigned char*>(output);
|
||||||
|
|
||||||
|
framebuffer_mode_ = mjFB_OFFSCREEN;
|
||||||
|
mjr_setBuffer(mjFB_OFFSCREEN, &render_context_);
|
||||||
|
const mjrRect viewport = {0, 0, width, height};
|
||||||
|
mjr_render(viewport, &scene_, &render_context_);
|
||||||
|
DoReadPixels(width, height, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ClassicRenderer::UploadImage(int texture_id, const std::byte* pixels,
|
||||||
|
int width, int height, int bpp) {
|
||||||
|
// unsupported
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClassicRenderer::DoReadPixels(int width, int height, unsigned char* rgb) {
|
||||||
|
if (!rgb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (framebuffer_mode_ == mjFB_WINDOW) {
|
||||||
|
mju_warning("ReadPixels is only supported for offscreen rendering.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mjrRect viewport = {0, 0, width, height};
|
||||||
|
mjr_readPixels(rgb, nullptr, viewport, &render_context_);
|
||||||
|
mjr_setBuffer(mjFB_WINDOW, &render_context_);
|
||||||
|
FlipImage(rgb, viewport.width, viewport.height, 3);
|
||||||
|
framebuffer_mode_ = mjFB_WINDOW;
|
||||||
|
mjr_setBuffer(mjFB_WINDOW, &render_context_);
|
||||||
|
}
|
||||||
|
|
||||||
|
double ClassicRenderer::GetFps() { return fps_; }
|
||||||
|
|
||||||
|
void ClassicRenderer::UpdateFps() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace mujoco::platform
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright 2025 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_HAL_CLASSIC_RENDERER_H_
|
||||||
|
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_CLASSIC_RENDERER_H_
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
|
#include "experimental/platform/hal/graphics_mode.h"
|
||||||
|
#include "experimental/platform/hal/renderer.h"
|
||||||
|
|
||||||
|
namespace mujoco::platform {
|
||||||
|
|
||||||
|
// Renders the mujoco simulation and the imgui state using the Classic MuJoCo
|
||||||
|
// OpenGL renderer.
|
||||||
|
class ClassicRenderer : public Renderer {
|
||||||
|
public:
|
||||||
|
ClassicRenderer(void* native_window, GraphicsMode gfx_mode);
|
||||||
|
~ClassicRenderer();
|
||||||
|
|
||||||
|
ClassicRenderer(const ClassicRenderer&) = delete;
|
||||||
|
ClassicRenderer& operator=(const ClassicRenderer&) = delete;
|
||||||
|
|
||||||
|
// Initializes the renderer with the given mjModel.
|
||||||
|
void Init(const mjModel* model) override;
|
||||||
|
|
||||||
|
// Renders the simulation and ux state. Renders into `pixels` if provided,
|
||||||
|
// otherwise renders to the `native_window` provided at construction.
|
||||||
|
void Render(const mjModel* model, mjData* data, const mjvPerturb* perturb,
|
||||||
|
mjvCamera* camera, const mjvOption* vis_option, int width,
|
||||||
|
int height, std::span<std::byte> pixels = {},
|
||||||
|
std::span<mjvGeom> extra_geoms = {}) override;
|
||||||
|
|
||||||
|
// Populates the given output buffer with RGB888 pixel data. The size of the
|
||||||
|
// output buffer must be at least width * height * 3.
|
||||||
|
void RenderToTexture(const mjModel* model, mjData* data, mjvCamera* camera,
|
||||||
|
int width, int height, std::byte* output) override;
|
||||||
|
|
||||||
|
// Uploads an image to the backend for GUI rendering, returning the texture
|
||||||
|
// ID for the texture. The ID can be used in subsequent calls to update the
|
||||||
|
// texture data. A nullptr pixels argument will free the texture if it exists.
|
||||||
|
// A texture ID of 0 will create a new texture.
|
||||||
|
int UploadImage(int texture_id, const std::byte* pixels, int width,
|
||||||
|
int height, int bpp) override;
|
||||||
|
|
||||||
|
// Rendering flags.
|
||||||
|
mjtByte* GetRenderFlags() override { return scene_.flags; }
|
||||||
|
|
||||||
|
// Returns the current frame rate.
|
||||||
|
double GetFps() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using Clock = std::chrono::steady_clock;
|
||||||
|
using TimePoint = std::chrono::time_point<Clock>;
|
||||||
|
|
||||||
|
// Resets the renderer; no rendering will occur until Init() is called again.
|
||||||
|
void Deinit();
|
||||||
|
|
||||||
|
void UpdateFps();
|
||||||
|
|
||||||
|
void DoReadPixels(int width, int height, unsigned char* rgb);
|
||||||
|
|
||||||
|
GraphicsMode gfx_ = GraphicsMode::ClassicOpenGl;
|
||||||
|
std::shared_ptr<void> graphics_api_context_ = nullptr;
|
||||||
|
mjrContext render_context_;
|
||||||
|
mjvScene scene_;
|
||||||
|
TimePoint last_fps_update_;
|
||||||
|
int framebuffer_mode_ = 0;
|
||||||
|
int frames_ = 0;
|
||||||
|
double fps_ = 0;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mujoco::platform
|
||||||
|
|
||||||
|
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_CLASSIC_RENDERER_H_
|
||||||
@@ -0,0 +1,289 @@
|
|||||||
|
// Copyright 2025 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 "experimental/platform/hal/filament_renderer.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include <mujoco/mjrfilament.h>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
|
#include "experimental/filament/compat/scene_bridge.h"
|
||||||
|
#include "experimental/platform/hal/graphics_mode.h"
|
||||||
|
#include "experimental/platform/ux/imgui_bridge.h"
|
||||||
|
#include "experimental/platform/ux/imgui_widgets.h"
|
||||||
|
#include "experimental/platform/ux/plugin.h"
|
||||||
|
#include "render/filament/mjrfilament_cpp.h"
|
||||||
|
|
||||||
|
namespace mujoco::platform {
|
||||||
|
|
||||||
|
static std::function<void()> g_update_gui_callback = nullptr;
|
||||||
|
static void PluginUpdate(GuiPlugin* plugin) {
|
||||||
|
if (g_update_gui_callback) {
|
||||||
|
g_update_gui_callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FilamentRenderer::FilamentRenderer(void* native_window, GraphicsMode gfx)
|
||||||
|
: native_window_(native_window), gfx_(gfx) {
|
||||||
|
g_update_gui_callback = [this]() {
|
||||||
|
if (main_scene_) {
|
||||||
|
mjrf_DEBUG_drawImguiEditor(main_scene_.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
FilamentRenderer::~FilamentRenderer() {
|
||||||
|
g_update_gui_callback = nullptr;
|
||||||
|
Deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentRenderer::Init(const mjModel* model) {
|
||||||
|
Deinit();
|
||||||
|
if (model) {
|
||||||
|
mjrfContextConfig cfg;
|
||||||
|
mjrf_defaultContextConfig(&cfg);
|
||||||
|
cfg.native_window = native_window_;
|
||||||
|
cfg.force_software_rendering = IsSoftware(gfx_);
|
||||||
|
cfg.graphics_api = IsOpenGl(gfx_) || IsWebGl(gfx_) ? mjGRAPHICS_API_OPENGL
|
||||||
|
: mjGRAPHICS_API_VULKAN;
|
||||||
|
filament_context_ = CreateContext(cfg);
|
||||||
|
|
||||||
|
main_scene_ = CreateScene(filament_context_.get(), {});
|
||||||
|
ux_scene_ = CreateScene(filament_context_.get(), {});
|
||||||
|
scene_bridge_ = std::make_unique<SceneBridge>(filament_context_.get(),
|
||||||
|
main_scene_.get(), model);
|
||||||
|
imgui_bridge_ =
|
||||||
|
std::make_unique<ImguiBridge>(filament_context_.get(), ux_scene_.get());
|
||||||
|
scene_bridge_->SetDrawTextFunction(DrawTextAt);
|
||||||
|
|
||||||
|
mjv_defaultScene(&scene_);
|
||||||
|
mjv_makeScene(model, &scene_, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentRenderer::Deinit() {
|
||||||
|
if (filament_context_) {
|
||||||
|
mjv_freeScene(&scene_);
|
||||||
|
scene_bridge_.reset();
|
||||||
|
imgui_bridge_.reset();
|
||||||
|
ux_scene_.reset();
|
||||||
|
main_scene_.reset();
|
||||||
|
filament_context_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentRenderer::Render(const mjModel* model, mjData* data,
|
||||||
|
const mjvPerturb* perturb, mjvCamera* camera,
|
||||||
|
const mjvOption* vis_option, int width,
|
||||||
|
int height, std::span<std::byte> pixels,
|
||||||
|
std::span<mjvGeom> extra_geoms) {
|
||||||
|
if (!filament_context_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjvCamera default_cam;
|
||||||
|
if (camera == nullptr) {
|
||||||
|
if (model) {
|
||||||
|
mjv_defaultFreeCamera(model, &default_cam);
|
||||||
|
} else {
|
||||||
|
mjv_defaultCamera(&default_cam);
|
||||||
|
}
|
||||||
|
camera = &default_cam;
|
||||||
|
}
|
||||||
|
mjvOption default_opt;
|
||||||
|
if (vis_option == nullptr) {
|
||||||
|
mjv_defaultOption(&default_opt);
|
||||||
|
vis_option = &default_opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjv_updateScene(model, data, vis_option, perturb, camera, mjCAT_ALL, &scene_);
|
||||||
|
const int nextra_geoms =
|
||||||
|
std::min<int>(extra_geoms.size(), scene_.maxgeom - scene_.ngeom);
|
||||||
|
for (int i = 0; i < nextra_geoms; ++i) {
|
||||||
|
scene_.geoms[scene_.ngeom++] = extra_geoms[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool render_to_texture = !pixels.empty();
|
||||||
|
if (render_to_texture) {
|
||||||
|
if (pixels.size() != width * height * 3) {
|
||||||
|
mju_error("Offscreen mode requires a pixel buffer of size %d.",
|
||||||
|
width * height * 3);
|
||||||
|
}
|
||||||
|
framebuffer_mode_ = mjFB_OFFSCREEN + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DoRender(width, height);
|
||||||
|
|
||||||
|
if (render_to_texture) {
|
||||||
|
unsigned char* ptr = reinterpret_cast<unsigned char*>(pixels.data());
|
||||||
|
DoReadPixels(width, height, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateFps();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentRenderer::RenderToTexture(const mjModel* model, mjData* data,
|
||||||
|
mjvCamera* camera, int width, int height,
|
||||||
|
std::byte* output) {
|
||||||
|
if (!filament_context_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjv_updateCamera(model, data, camera, &scene_);
|
||||||
|
unsigned char* ptr = reinterpret_cast<unsigned char*>(output);
|
||||||
|
|
||||||
|
framebuffer_mode_ = mjFB_OFFSCREEN;
|
||||||
|
DoRender(width, height);
|
||||||
|
DoReadPixels(width, height, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
int FilamentRenderer::UploadImage(int texture_id, const std::byte* pixels,
|
||||||
|
int width, int height, int bpp) {
|
||||||
|
return imgui_bridge_->UploadImage(
|
||||||
|
texture_id, reinterpret_cast<const unsigned char*>(pixels), width, height,
|
||||||
|
bpp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentRenderer::DoRender(int width, int height) {
|
||||||
|
const mjrRect viewport = {0, 0, width, height};
|
||||||
|
scene_bridge_->Update(viewport, &scene_);
|
||||||
|
// Update the UX renderable entity after processing the scene in case there
|
||||||
|
// are any elements in the scene which generate UX draw calls (e.g. labels).
|
||||||
|
if (framebuffer_mode_ != 1) {
|
||||||
|
imgui_bridge_->Update();
|
||||||
|
}
|
||||||
|
if (framebuffer_mode_ == 0) {
|
||||||
|
mjrDrawMode draw_mode = mjDRAW_MODE_DEFAULT;
|
||||||
|
if (scene_.flags[mjRND_SEGMENT]) {
|
||||||
|
if (scene_.flags[mjRND_IDCOLOR]) {
|
||||||
|
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_ID;
|
||||||
|
} else {
|
||||||
|
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_COLOR;
|
||||||
|
}
|
||||||
|
} else if (scene_.flags[mjRND_DEPTH]) {
|
||||||
|
draw_mode = mjDRAW_MODE_DEPTH;
|
||||||
|
} else if (scene_.flags[mjRND_WIREFRAME]) {
|
||||||
|
draw_mode = mjDRAW_MODE_WIREFRAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjrfRenderRequest reqs[2];
|
||||||
|
|
||||||
|
mjrf_defaultRenderRequest(&reqs[0]);
|
||||||
|
reqs[0].scene = main_scene_.get();
|
||||||
|
reqs[0].draw_mode = draw_mode;
|
||||||
|
reqs[0].camera = scene_bridge_->GetCamera();
|
||||||
|
reqs[0].viewport = viewport;
|
||||||
|
reqs[0].enable_shadows = scene_.flags[mjRND_SHADOW];
|
||||||
|
reqs[0].enable_reflections = scene_.flags[mjRND_REFLECTION];
|
||||||
|
|
||||||
|
mjrf_defaultRenderRequest(&reqs[1]);
|
||||||
|
reqs[1].scene = ux_scene_.get();
|
||||||
|
reqs[1].draw_mode = mjDRAW_MODE_DEFAULT;
|
||||||
|
reqs[1].camera = imgui_bridge_->GetCamera(viewport.width, viewport.height);
|
||||||
|
reqs[1].viewport = viewport;
|
||||||
|
reqs[1].enable_shadows = false;
|
||||||
|
reqs[1].enable_reflections = false;
|
||||||
|
reqs[1].enable_post_processing = false;
|
||||||
|
|
||||||
|
mjrf_render(filament_context_.get(), &reqs[0], 2, nullptr, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FilamentRenderer::DoReadPixels(int width, int height, unsigned char* rgb) {
|
||||||
|
if (!rgb) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (framebuffer_mode_ == 0) {
|
||||||
|
mju_warning("ReadPixels is only supported for offscreen rendering.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mjrRect viewport = {0, 0, width, height};
|
||||||
|
mjrDrawMode draw_mode = mjDRAW_MODE_DEFAULT;
|
||||||
|
if (scene_.flags[mjRND_SEGMENT]) {
|
||||||
|
if (scene_.flags[mjRND_IDCOLOR]) {
|
||||||
|
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_ID;
|
||||||
|
} else {
|
||||||
|
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_COLOR;
|
||||||
|
}
|
||||||
|
} else if (scene_.flags[mjRND_DEPTH]) {
|
||||||
|
draw_mode = mjDRAW_MODE_DEPTH;
|
||||||
|
} else if (scene_.flags[mjRND_WIREFRAME]) {
|
||||||
|
draw_mode = mjDRAW_MODE_WIREFRAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
mjrfRenderTargetConfig config;
|
||||||
|
mjrf_defaultRenderTargetConfig(&config);
|
||||||
|
config.width = viewport.width;
|
||||||
|
config.height = viewport.height;
|
||||||
|
config.color_format = mjPIXEL_FORMAT_RGB8;
|
||||||
|
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
|
||||||
|
auto target = CreateRenderTarget(filament_context_.get(), config);
|
||||||
|
|
||||||
|
mjrfRenderRequest reqs[2];
|
||||||
|
mjrf_defaultRenderRequest(&reqs[0]);
|
||||||
|
reqs[0].scene = main_scene_.get();
|
||||||
|
reqs[0].draw_mode = draw_mode;
|
||||||
|
reqs[0].camera = scene_bridge_->GetCamera();
|
||||||
|
reqs[0].target = target.get();
|
||||||
|
reqs[0].viewport = viewport;
|
||||||
|
reqs[0].enable_shadows = scene_.flags[mjRND_SHADOW];
|
||||||
|
reqs[0].enable_reflections = scene_.flags[mjRND_REFLECTION];
|
||||||
|
|
||||||
|
mjrf_defaultRenderRequest(&reqs[1]);
|
||||||
|
reqs[1].scene = ux_scene_.get();
|
||||||
|
reqs[1].draw_mode = mjDRAW_MODE_DEFAULT;
|
||||||
|
reqs[1].camera = imgui_bridge_->GetCamera(viewport.width, viewport.height);
|
||||||
|
reqs[1].target = target.get();
|
||||||
|
reqs[1].viewport = viewport;
|
||||||
|
reqs[1].enable_shadows = false;
|
||||||
|
reqs[1].enable_reflections = false;
|
||||||
|
reqs[1].enable_post_processing = false;
|
||||||
|
|
||||||
|
mjrfReadPixelsRequest read_request;
|
||||||
|
mjrf_defaultReadPixelsRequest(&read_request);
|
||||||
|
read_request.target = target.get();
|
||||||
|
read_request.output = rgb;
|
||||||
|
read_request.num_bytes = viewport.width * viewport.height * 3;
|
||||||
|
|
||||||
|
const int num_requests = (framebuffer_mode_ == 2) ? 2 : 1;
|
||||||
|
const mjrfFrameHandle frame = mjrf_render(filament_context_.get(), &reqs[0],
|
||||||
|
num_requests, &read_request, 1);
|
||||||
|
mjrf_waitForFrame(filament_context_.get(), frame);
|
||||||
|
framebuffer_mode_ = mjFB_WINDOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
double FilamentRenderer::GetFps() { return fps_; }
|
||||||
|
|
||||||
|
void FilamentRenderer::UpdateFps() {
|
||||||
|
mjrfFrameStats stats;
|
||||||
|
mjrf_defaultFrameStats(&stats);
|
||||||
|
mjrf_getFrameStats(filament_context_.get(), 0, &stats);
|
||||||
|
fps_ = stats.frame_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mujoco::platform
|
||||||
|
|
||||||
|
mjPLUGIN_LIB_INIT(renderer) {
|
||||||
|
mujoco::platform::GuiPlugin plugin;
|
||||||
|
plugin.name = "Filament";
|
||||||
|
plugin.update = [](mujoco::platform::GuiPlugin* self) {
|
||||||
|
mujoco::platform::PluginUpdate(self);
|
||||||
|
};
|
||||||
|
mujoco::platform::RegisterPlugin(plugin);
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
// Copyright 2025 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_HAL_FILAMENT_RENDERER_H_
|
||||||
|
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_FILAMENT_RENDERER_H_
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <memory>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
#include <mujoco/mjrfilament.h>
|
||||||
|
#include <mujoco/mujoco.h>
|
||||||
|
#include "experimental/filament/compat/scene_bridge.h"
|
||||||
|
#include "experimental/platform/hal/graphics_mode.h"
|
||||||
|
#include "experimental/platform/hal/renderer.h"
|
||||||
|
#include "experimental/platform/ux/imgui_bridge.h"
|
||||||
|
#include "render/filament/mjrfilament_cpp.h"
|
||||||
|
|
||||||
|
namespace mujoco::platform {
|
||||||
|
|
||||||
|
// Renders the mujoco simulation and the imgui state using the Filament-based
|
||||||
|
// MuJoCo renderer.
|
||||||
|
//
|
||||||
|
// 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. If rendering to a window surface (e.g.
|
||||||
|
// x11), it requires a pointer to the native window to do so.
|
||||||
|
class FilamentRenderer : public Renderer {
|
||||||
|
public:
|
||||||
|
FilamentRenderer(void* native_window, GraphicsMode gfx_mode);
|
||||||
|
~FilamentRenderer();
|
||||||
|
|
||||||
|
FilamentRenderer(const FilamentRenderer&) = delete;
|
||||||
|
FilamentRenderer& operator=(const FilamentRenderer&) = delete;
|
||||||
|
|
||||||
|
// Initializes the renderer with the given mjModel.
|
||||||
|
void Init(const mjModel* model) override;
|
||||||
|
|
||||||
|
// Renders the simulation and ux state. Renders into `pixels` if provided,
|
||||||
|
// otherwise renders to the `native_window` provided at construction.
|
||||||
|
void Render(const mjModel* model, mjData* data, const mjvPerturb* perturb,
|
||||||
|
mjvCamera* camera, const mjvOption* vis_option, int width,
|
||||||
|
int height, std::span<std::byte> pixels = {},
|
||||||
|
std::span<mjvGeom> extra_geoms = {}) override;
|
||||||
|
|
||||||
|
// Populates the given output buffer with RGB888 pixel data. The size of the
|
||||||
|
// output buffer must be at least width * height * 3.
|
||||||
|
void RenderToTexture(const mjModel* model, mjData* data, mjvCamera* camera,
|
||||||
|
int width, int height, std::byte* output) override;
|
||||||
|
|
||||||
|
// Uploads an image to the backend for GUI rendering, returning the texture
|
||||||
|
// ID for the texture. The ID can be used in subsequent calls to update the
|
||||||
|
// texture data. A nullptr pixels argument will free the texture if it exists.
|
||||||
|
// A texture ID of 0 will create a new texture.
|
||||||
|
int UploadImage(int texture_id, const std::byte* pixels, int width,
|
||||||
|
int height, int bpp) override;
|
||||||
|
|
||||||
|
// Rendering flags.
|
||||||
|
mjtByte* GetRenderFlags() override { return scene_.flags; }
|
||||||
|
|
||||||
|
// Returns the current frame rate.
|
||||||
|
double GetFps() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Resets the renderer; no rendering will occur until Init() is called again.
|
||||||
|
void Deinit();
|
||||||
|
|
||||||
|
void UpdateFps();
|
||||||
|
|
||||||
|
void DoRender(int width, int height);
|
||||||
|
void DoReadPixels(int width, int height, unsigned char* rgb);
|
||||||
|
|
||||||
|
void* native_window_ = nullptr;
|
||||||
|
GraphicsMode gfx_ = GraphicsMode::FilamentVulkan;
|
||||||
|
UniquePtr<mjrfContext> filament_context_{nullptr, nullptr};
|
||||||
|
UniquePtr<mjrfScene> main_scene_{nullptr, nullptr};
|
||||||
|
UniquePtr<mjrfScene> ux_scene_{nullptr, nullptr};
|
||||||
|
std::unique_ptr<SceneBridge> scene_bridge_;
|
||||||
|
std::unique_ptr<ImguiBridge> imgui_bridge_;
|
||||||
|
int framebuffer_mode_ = 0;
|
||||||
|
mjvScene scene_;
|
||||||
|
double fps_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mujoco::platform
|
||||||
|
|
||||||
|
#endif // MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_FILAMENT_RENDERER_H_
|
||||||
@@ -1,385 +0,0 @@
|
|||||||
// Copyright 2025 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 "experimental/platform/hal/renderer.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
|
||||||
#include <functional>
|
|
||||||
#include <memory>
|
|
||||||
#include <span>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <backends/imgui_impl_opengl3.h>
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <mujoco/mujoco.h>
|
|
||||||
#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
|
|
||||||
#include "experimental/platform/hal/egl_utils.h"
|
|
||||||
#endif
|
|
||||||
#include <mujoco/mjrfilament.h>
|
|
||||||
#include "experimental/filament/compat/scene_bridge.h"
|
|
||||||
#include "experimental/platform/hal/graphics_mode.h"
|
|
||||||
#include "experimental/platform/ux/imgui_bridge.h"
|
|
||||||
#include "experimental/platform/ux/imgui_widgets.h"
|
|
||||||
#include "experimental/platform/ux/plugin.h"
|
|
||||||
#include "render/filament/mjrfilament_cpp.h"
|
|
||||||
|
|
||||||
namespace mujoco::platform {
|
|
||||||
|
|
||||||
static std::function<void()> g_update_gui_callback = nullptr;
|
|
||||||
static void PluginUpdate(GuiPlugin* plugin) {
|
|
||||||
if (g_update_gui_callback) {
|
|
||||||
g_update_gui_callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void FlipImage(unsigned char* pixels, int width, int height, int bpp) {
|
|
||||||
const int row_size = width * bpp;
|
|
||||||
for (int i = 0; i < height / 2; ++i) {
|
|
||||||
unsigned char* top_row = pixels + i * row_size;
|
|
||||||
unsigned char* bottom_row = pixels + (height - 1 - i) * row_size;
|
|
||||||
for (int j = 0; j < row_size; ++j) {
|
|
||||||
std::swap(top_row[j], bottom_row[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Renderer::Renderer(void* native_window, GraphicsMode gfx)
|
|
||||||
: native_window_(native_window), gfx_(gfx) {
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
if (native_window == nullptr) {
|
|
||||||
#if !defined(__EMSCRIPTEN__) && !defined(__APPLE__)
|
|
||||||
graphics_api_context_ = CreateEglContext();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
if (ImGui::GetCurrentContext()) {
|
|
||||||
ImGui_ImplOpenGL3_Init();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_update_gui_callback = [this]() {
|
|
||||||
if (main_scene_) {
|
|
||||||
mjrf_DEBUG_drawImguiEditor(main_scene_.get());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Renderer::~Renderer() {
|
|
||||||
g_update_gui_callback = nullptr;
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
if (ImGui::GetCurrentContext()) {
|
|
||||||
ImGui_ImplOpenGL3_Shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Deinit();
|
|
||||||
graphics_api_context_.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::Init(const mjModel* model) {
|
|
||||||
Deinit();
|
|
||||||
if (model) {
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
mjr_defaultContext(&render_context_);
|
|
||||||
mjr_makeContext(model, &render_context_, mjFONTSCALE_150);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
mjrfContextConfig cfg;
|
|
||||||
mjrf_defaultContextConfig(&cfg);
|
|
||||||
cfg.native_window = native_window_;
|
|
||||||
cfg.force_software_rendering = IsSoftware(gfx_);
|
|
||||||
cfg.graphics_api = IsOpenGl(gfx_) || IsWebGl(gfx_)
|
|
||||||
? mjGRAPHICS_API_OPENGL
|
|
||||||
: mjGRAPHICS_API_VULKAN;
|
|
||||||
filament_context_ = CreateContext(cfg);
|
|
||||||
main_scene_ = CreateScene(filament_context_.get(), {});
|
|
||||||
ux_scene_ = CreateScene(filament_context_.get(), {});
|
|
||||||
scene_bridge_ = std::make_unique<SceneBridge>(filament_context_.get(),
|
|
||||||
main_scene_.get(), model);
|
|
||||||
imgui_bridge_ = std::make_unique<ImguiBridge>(filament_context_.get(),
|
|
||||||
ux_scene_.get());
|
|
||||||
scene_bridge_->SetDrawTextFunction(DrawTextAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
mjv_defaultScene(&scene_);
|
|
||||||
mjv_makeScene(model, &scene_, 2000);
|
|
||||||
initialized_ = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::Deinit() {
|
|
||||||
if (initialized_) {
|
|
||||||
mjv_freeScene(&scene_);
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
mjr_freeContext(&render_context_);
|
|
||||||
} else {
|
|
||||||
scene_bridge_.reset();
|
|
||||||
imgui_bridge_.reset();
|
|
||||||
ux_scene_.reset();
|
|
||||||
main_scene_.reset();
|
|
||||||
filament_context_.reset();
|
|
||||||
}
|
|
||||||
initialized_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::Render(const mjModel* model, mjData* data,
|
|
||||||
const mjvPerturb* perturb, mjvCamera* camera,
|
|
||||||
const mjvOption* vis_option, int width, int height,
|
|
||||||
std::span<std::byte> pixels,
|
|
||||||
std::span<mjvGeom> extra_geoms) {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mjvCamera default_cam;
|
|
||||||
if (camera == nullptr) {
|
|
||||||
if (model) {
|
|
||||||
mjv_defaultFreeCamera(model, &default_cam);
|
|
||||||
} else {
|
|
||||||
mjv_defaultCamera(&default_cam);
|
|
||||||
}
|
|
||||||
camera = &default_cam;
|
|
||||||
}
|
|
||||||
mjvOption default_opt;
|
|
||||||
if (vis_option == nullptr) {
|
|
||||||
mjv_defaultOption(&default_opt);
|
|
||||||
vis_option = &default_opt;
|
|
||||||
}
|
|
||||||
|
|
||||||
mjv_updateScene(model, data, vis_option, perturb, camera, mjCAT_ALL, &scene_);
|
|
||||||
const int nextra_geoms =
|
|
||||||
std::min<int>(extra_geoms.size(), scene_.maxgeom - scene_.ngeom);
|
|
||||||
for (int i = 0; i < nextra_geoms; ++i) {
|
|
||||||
scene_.geoms[scene_.ngeom++] = extra_geoms[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool render_to_texture = !pixels.empty();
|
|
||||||
if (render_to_texture) {
|
|
||||||
// mjr_readPixels reads to a RGB buffer (i.e. 3 bytes per pixel).
|
|
||||||
if (pixels.size() != width * height * 3) {
|
|
||||||
mju_error("Offscreen mode requires a pixel buffer of size %d.",
|
|
||||||
width * height * 3);
|
|
||||||
}
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
mjr_resizeOffscreen(width, height, &render_context_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// The filament backend supports two offscreen framebuffers.
|
|
||||||
// mjFB_OFFSCREEN renders just the mjvScene data. +1 also includes the
|
|
||||||
// ImGui draw data.
|
|
||||||
DoSetBuffer(IsClassic(gfx_) ? mjFB_OFFSCREEN : mjFB_OFFSCREEN + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
DoRender(width, height);
|
|
||||||
|
|
||||||
// The filament backend knows how to renders the ImGui draw data. For the
|
|
||||||
// classic backend, we need to render the ImGui draw data ourselves.
|
|
||||||
if (IsClassic(gfx_) && ImGui::GetCurrentContext()) {
|
|
||||||
ImGui_ImplOpenGL3_NewFrame();
|
|
||||||
ImGui::Render();
|
|
||||||
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (render_to_texture) {
|
|
||||||
unsigned char* ptr = reinterpret_cast<unsigned char*>(pixels.data());
|
|
||||||
DoReadPixels(width, height, ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateFps();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::RenderToTexture(const mjModel* model, mjData* data,
|
|
||||||
mjvCamera* camera, int width, int height,
|
|
||||||
std::byte* output) {
|
|
||||||
if (!initialized_) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mjv_updateCamera(model, data, camera, &scene_);
|
|
||||||
unsigned char* ptr = reinterpret_cast<unsigned char*>(output);
|
|
||||||
|
|
||||||
DoSetBuffer(mjFB_OFFSCREEN);
|
|
||||||
DoRender(width, height);
|
|
||||||
DoReadPixels(width, height, ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Renderer::UploadImage(int texture_id, const std::byte* pixels, int width,
|
|
||||||
int height, int bpp) {
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return imgui_bridge_->UploadImage(
|
|
||||||
texture_id, reinterpret_cast<const unsigned char*>(pixels), width,
|
|
||||||
height, bpp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::DoRender(int width, int height) {
|
|
||||||
const mjrRect viewport = {0, 0, width, height};
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
mjr_render(viewport, &scene_, &render_context_);
|
|
||||||
} else {
|
|
||||||
scene_bridge_->Update(viewport, &scene_);
|
|
||||||
// Update the UX renderable entity after processing the scene in case there
|
|
||||||
// are any elements in the scene which generate UX draw calls (e.g. labels).
|
|
||||||
if (framebuffer_mode_ != 1) {
|
|
||||||
imgui_bridge_->Update();
|
|
||||||
}
|
|
||||||
if (framebuffer_mode_ == 0) {
|
|
||||||
mjrDrawMode draw_mode = mjDRAW_MODE_DEFAULT;
|
|
||||||
if (scene_.flags[mjRND_SEGMENT]) {
|
|
||||||
if (scene_.flags[mjRND_IDCOLOR]) {
|
|
||||||
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_ID;
|
|
||||||
} else {
|
|
||||||
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_COLOR;
|
|
||||||
}
|
|
||||||
} else if (scene_.flags[mjRND_DEPTH]) {
|
|
||||||
draw_mode = mjDRAW_MODE_DEPTH;
|
|
||||||
} else if (scene_.flags[mjRND_WIREFRAME]) {
|
|
||||||
draw_mode = mjDRAW_MODE_WIREFRAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
mjrfRenderRequest reqs[2];
|
|
||||||
|
|
||||||
mjrf_defaultRenderRequest(&reqs[0]);
|
|
||||||
reqs[0].scene = main_scene_.get();
|
|
||||||
reqs[0].draw_mode = draw_mode;
|
|
||||||
reqs[0].camera = scene_bridge_->GetCamera();
|
|
||||||
reqs[0].viewport = viewport;
|
|
||||||
reqs[0].enable_shadows = scene_.flags[mjRND_SHADOW];
|
|
||||||
reqs[0].enable_reflections = scene_.flags[mjRND_REFLECTION];
|
|
||||||
|
|
||||||
mjrf_defaultRenderRequest(&reqs[1]);
|
|
||||||
reqs[1].scene = ux_scene_.get();
|
|
||||||
reqs[1].draw_mode = mjDRAW_MODE_DEFAULT;
|
|
||||||
reqs[1].camera = imgui_bridge_->GetCamera(viewport.width, viewport.height);
|
|
||||||
reqs[1].viewport = viewport;
|
|
||||||
reqs[1].enable_shadows = false;
|
|
||||||
reqs[1].enable_reflections = false;
|
|
||||||
reqs[1].enable_post_processing = false;
|
|
||||||
|
|
||||||
mjrf_render(filament_context_.get(), &reqs[0], 2, nullptr, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::DoSetBuffer(int framebuffer) {
|
|
||||||
framebuffer_mode_ = framebuffer;
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
mjr_setBuffer(framebuffer, &render_context_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::DoReadPixels(int width, int height, unsigned char* rgb) {
|
|
||||||
if (!rgb) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (framebuffer_mode_ == 0) {
|
|
||||||
mju_warning("ReadPixels is only supported for offscreen rendering.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mjrRect viewport = {0, 0, width, height};
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
mjr_readPixels(rgb, nullptr, viewport, &render_context_);
|
|
||||||
mjr_setBuffer(mjFB_WINDOW, &render_context_);
|
|
||||||
FlipImage(rgb, viewport.width, viewport.height, 3);
|
|
||||||
} else {
|
|
||||||
mjrDrawMode draw_mode = mjDRAW_MODE_DEFAULT;
|
|
||||||
if (scene_.flags[mjRND_SEGMENT]) {
|
|
||||||
if (scene_.flags[mjRND_IDCOLOR]) {
|
|
||||||
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_ID;
|
|
||||||
} else {
|
|
||||||
draw_mode = mjDRAW_MODE_SEGMENTATION_BY_COLOR;
|
|
||||||
}
|
|
||||||
} else if (scene_.flags[mjRND_DEPTH]) {
|
|
||||||
draw_mode = mjDRAW_MODE_DEPTH;
|
|
||||||
} else if (scene_.flags[mjRND_WIREFRAME]) {
|
|
||||||
draw_mode = mjDRAW_MODE_WIREFRAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
mjrfRenderTargetConfig config;
|
|
||||||
mjrf_defaultRenderTargetConfig(&config);
|
|
||||||
config.width = viewport.width;
|
|
||||||
config.height = viewport.height;
|
|
||||||
config.color_format = mjPIXEL_FORMAT_RGB8;
|
|
||||||
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
|
|
||||||
auto target = CreateRenderTarget(filament_context_.get(), config);
|
|
||||||
|
|
||||||
mjrfRenderRequest reqs[2];
|
|
||||||
mjrf_defaultRenderRequest(&reqs[0]);
|
|
||||||
reqs[0].scene = main_scene_.get();
|
|
||||||
reqs[0].draw_mode = draw_mode;
|
|
||||||
reqs[0].camera = scene_bridge_->GetCamera();
|
|
||||||
reqs[0].target = target.get();
|
|
||||||
reqs[0].viewport = viewport;
|
|
||||||
reqs[0].enable_shadows = scene_.flags[mjRND_SHADOW];
|
|
||||||
reqs[0].enable_reflections = scene_.flags[mjRND_REFLECTION];
|
|
||||||
|
|
||||||
mjrf_defaultRenderRequest(&reqs[1]);
|
|
||||||
reqs[1].scene = ux_scene_.get();
|
|
||||||
reqs[1].draw_mode = mjDRAW_MODE_DEFAULT;
|
|
||||||
reqs[1].camera = imgui_bridge_->GetCamera(viewport.width, viewport.height);
|
|
||||||
reqs[1].target = target.get();
|
|
||||||
reqs[1].viewport = viewport;
|
|
||||||
reqs[1].enable_shadows = false;
|
|
||||||
reqs[1].enable_reflections = false;
|
|
||||||
reqs[1].enable_post_processing = false;
|
|
||||||
|
|
||||||
mjrfReadPixelsRequest read_request;
|
|
||||||
mjrf_defaultReadPixelsRequest(&read_request);
|
|
||||||
read_request.target = target.get();
|
|
||||||
read_request.output = rgb;
|
|
||||||
read_request.num_bytes = viewport.width * viewport.height * 3;
|
|
||||||
|
|
||||||
const int num_requests = (framebuffer_mode_ == 2) ? 2 : 1;
|
|
||||||
const mjrfFrameHandle frame = mjrf_render(
|
|
||||||
filament_context_.get(), &reqs[0], num_requests, &read_request, 1);
|
|
||||||
mjrf_waitForFrame(filament_context_.get(), frame);
|
|
||||||
}
|
|
||||||
DoSetBuffer(mjFB_WINDOW);
|
|
||||||
}
|
|
||||||
|
|
||||||
double Renderer::GetFps() { return fps_; }
|
|
||||||
|
|
||||||
void Renderer::UpdateFps() {
|
|
||||||
if (IsClassic(gfx_)) {
|
|
||||||
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 {
|
|
||||||
mjrfFrameStats stats;
|
|
||||||
mjrf_defaultFrameStats(&stats);
|
|
||||||
mjrf_getFrameStats(filament_context_.get(), 0, &stats);
|
|
||||||
fps_ = stats.frame_rate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace mujoco::platform
|
|
||||||
|
|
||||||
mjPLUGIN_LIB_INIT(renderer) {
|
|
||||||
mujoco::platform::GuiPlugin plugin;
|
|
||||||
plugin.name = "Filament";
|
|
||||||
plugin.update = [](mujoco::platform::GuiPlugin* self) {
|
|
||||||
mujoco::platform::PluginUpdate(self);
|
|
||||||
};
|
|
||||||
mujoco::platform::RegisterPlugin(plugin);
|
|
||||||
}
|
|
||||||
@@ -15,117 +15,51 @@
|
|||||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_RENDERER_H_
|
#ifndef MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_RENDERER_H_
|
||||||
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_RENDERER_H_
|
#define MUJOCO_SRC_EXPERIMENTAL_PLATFORM_HAL_RENDERER_H_
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
|
||||||
#include <ratio>
|
|
||||||
#include <span>
|
#include <span>
|
||||||
|
|
||||||
#include <mujoco/mjrfilament.h>
|
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
#include "experimental/filament/compat/scene_bridge.h"
|
|
||||||
#include "experimental/platform/hal/graphics_mode.h"
|
|
||||||
#include "experimental/platform/ux/imgui_bridge.h"
|
|
||||||
#include "render/filament/mjrfilament_cpp.h"
|
|
||||||
|
|
||||||
namespace mujoco::platform {
|
namespace mujoco::platform {
|
||||||
|
|
||||||
// Renders the mujoco simulation and the imgui state.
|
// 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 {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
using Clock = std::chrono::steady_clock;
|
Renderer() = default;
|
||||||
using TimePoint = std::chrono::time_point<Clock>;
|
virtual ~Renderer() = default;
|
||||||
using Seconds = std::chrono::duration<double>;
|
|
||||||
using Milliseconds = std::chrono::duration<double, std::milli>;
|
|
||||||
|
|
||||||
Renderer(void* native_window, GraphicsMode gfx_mode);
|
|
||||||
~Renderer();
|
|
||||||
|
|
||||||
Renderer(const Renderer&) = delete;
|
Renderer(const Renderer&) = delete;
|
||||||
Renderer& operator=(const Renderer&) = delete;
|
Renderer& operator=(const Renderer&) = delete;
|
||||||
|
|
||||||
// Initializes the renderer with the given mjModel.
|
// Initializes the renderer with the given mjModel.
|
||||||
void Init(const mjModel* model);
|
virtual void Init(const mjModel* model) = 0;
|
||||||
|
|
||||||
// Renders the simulation and ux state. Renders into `pixels` if provided,
|
// Renders the simulation and ux state. Renders into `pixels` if provided,
|
||||||
// otherwise renders to the `native_window` provided at construction.
|
// otherwise renders to the `native_window` provided at construction.
|
||||||
void Render(const mjModel* model, mjData* data, const mjvPerturb* perturb,
|
virtual void Render(const mjModel* model, mjData* data,
|
||||||
mjvCamera* camera, const mjvOption* vis_option, int width,
|
const mjvPerturb* perturb, mjvCamera* camera,
|
||||||
int height, std::span<std::byte> pixels = {},
|
const mjvOption* vis_option, int width, int height,
|
||||||
std::span<mjvGeom> extra_geoms = {});
|
std::span<std::byte> pixels = {},
|
||||||
|
std::span<mjvGeom> extra_geoms = {}) = 0;
|
||||||
|
|
||||||
// Populates the given output buffer with RGB888 pixel data. The size of the
|
// Populates the given output buffer with RGB888 pixel data. The size of the
|
||||||
// output buffer must be at least width * height * 3.
|
// output buffer must be at least width * height * 3.
|
||||||
void RenderToTexture(const mjModel* model, mjData* data, mjvCamera* camera,
|
virtual void RenderToTexture(const mjModel* model, mjData* data,
|
||||||
int width, int height, std::byte* output);
|
mjvCamera* camera, int width, int height,
|
||||||
|
std::byte* output) = 0;
|
||||||
|
|
||||||
// Uploads an image to the backend for GUI rendering, returning the texture
|
// Uploads an image to the backend for GUI rendering, returning the texture
|
||||||
// ID for the texture. The ID can be used in subsequent calls to update the
|
// ID for the texture. The ID can be used in subsequent calls to update the
|
||||||
// texture data. A nullptr pixels argument will free the texture if it exists.
|
// texture data. A nullptr pixels argument will free the texture if it exists.
|
||||||
// A texture ID of 0 will create a new texture.
|
// A texture ID of 0 will create a new texture.
|
||||||
int UploadImage(int texture_id, const std::byte* pixels, int width,
|
virtual int UploadImage(int texture_id, const std::byte* pixels, int width,
|
||||||
int height, int bpp);
|
int height, int bpp) = 0;
|
||||||
|
|
||||||
// Rendering flags.
|
// Rendering flags.
|
||||||
mjtByte* GetRenderFlags() { return scene_.flags; }
|
virtual mjtByte* GetRenderFlags() = 0;
|
||||||
|
|
||||||
// Returns the current frame rate.
|
// Returns the current frame rate.
|
||||||
double GetFps();
|
virtual double GetFps() = 0;
|
||||||
|
|
||||||
private:
|
|
||||||
// Resets the renderer; no rendering will occur until Init() is called again.
|
|
||||||
void Deinit();
|
|
||||||
|
|
||||||
void UpdateFps();
|
|
||||||
|
|
||||||
void DoRender(int width, int height);
|
|
||||||
void DoSetBuffer(int framebuffer);
|
|
||||||
void DoReadPixels(int width, int height, unsigned char* rgb);
|
|
||||||
|
|
||||||
void* native_window_ = nullptr;
|
|
||||||
GraphicsMode gfx_ = GraphicsMode::FilamentVulkan;
|
|
||||||
|
|
||||||
// State used by the classic renderer.
|
|
||||||
std::shared_ptr<void> graphics_api_context_ = nullptr;
|
|
||||||
mjrContext render_context_;
|
|
||||||
|
|
||||||
// State used by the filament renderer.
|
|
||||||
UniquePtr<mjrfContext> filament_context_{nullptr, nullptr};
|
|
||||||
UniquePtr<mjrfScene> main_scene_{nullptr, nullptr};
|
|
||||||
UniquePtr<mjrfScene> ux_scene_{nullptr, nullptr};
|
|
||||||
std::unique_ptr<SceneBridge> scene_bridge_;
|
|
||||||
std::unique_ptr<ImguiBridge> imgui_bridge_;
|
|
||||||
|
|
||||||
// Common state.
|
|
||||||
int framebuffer_mode_ = 0;
|
|
||||||
mjvScene scene_;
|
|
||||||
bool initialized_ = false;
|
|
||||||
mjtNum last_update_time_ = -1;
|
|
||||||
int frames_ = 0;
|
|
||||||
TimePoint last_fps_update_;
|
|
||||||
double fps_ = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace mujoco::platform
|
} // namespace mujoco::platform
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
#include "experimental/platform/hal/renderer.h"
|
#include "experimental/platform/hal/filament_renderer.h"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -50,7 +50,7 @@ class RendererTest : public ::testing::Test {
|
|||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(RendererTest, OpengGlSoftware) {
|
TEST_F(RendererTest, OpengGlSoftware) {
|
||||||
Renderer renderer(nullptr, GraphicsMode::FilamentOpenGlSoftware);
|
FilamentRenderer renderer(nullptr, GraphicsMode::FilamentOpenGlSoftware);
|
||||||
renderer.Init(holder_->model());
|
renderer.Init(holder_->model());
|
||||||
|
|
||||||
gl::DriverType driver_type = gl::GetLoadedDriverType();
|
gl::DriverType driver_type = gl::GetLoadedDriverType();
|
||||||
@@ -69,7 +69,7 @@ TEST_F(RendererTest, OpengGlSoftware) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(RendererTest, OpengGlHeadless) {
|
TEST_F(RendererTest, OpengGlHeadless) {
|
||||||
Renderer renderer(nullptr, GraphicsMode::FilamentOpenGlHeadless);
|
FilamentRenderer renderer(nullptr, GraphicsMode::FilamentOpenGlHeadless);
|
||||||
renderer.Init(holder_->model());
|
renderer.Init(holder_->model());
|
||||||
|
|
||||||
gl::DriverType driver_type = gl::GetLoadedDriverType();
|
gl::DriverType driver_type = gl::GetLoadedDriverType();
|
||||||
@@ -92,7 +92,7 @@ TEST_F(RendererTest, OpengGlHeadless) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(RendererTest, VulkanSoftware) {
|
TEST_F(RendererTest, VulkanSoftware) {
|
||||||
Renderer renderer(nullptr, GraphicsMode::FilamentVulkanSoftware);
|
FilamentRenderer renderer(nullptr, GraphicsMode::FilamentVulkanSoftware);
|
||||||
renderer.Init(holder_->model());
|
renderer.Init(holder_->model());
|
||||||
|
|
||||||
std::vector<std::byte> pixels(width_ * height_ * 3);
|
std::vector<std::byte> pixels(width_ * height_ * 3);
|
||||||
@@ -108,7 +108,7 @@ TEST_F(RendererTest, VulkanSoftware) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(RendererTest, VulkanHeadless) {
|
TEST_F(RendererTest, VulkanHeadless) {
|
||||||
Renderer renderer(nullptr, GraphicsMode::FilamentVulkan);
|
FilamentRenderer renderer(nullptr, GraphicsMode::FilamentVulkan);
|
||||||
renderer.Init(holder_->model());
|
renderer.Init(holder_->model());
|
||||||
|
|
||||||
std::vector<std::byte> pixels(width_ * height_ * 3);
|
std::vector<std::byte> pixels(width_ * height_ * 3);
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <implot.h>
|
#include <implot.h>
|
||||||
#include <mujoco/mujoco.h>
|
#include <mujoco/mujoco.h>
|
||||||
|
#include "experimental/platform/hal/classic_renderer.h"
|
||||||
|
#include "experimental/platform/hal/filament_renderer.h"
|
||||||
#include "experimental/platform/hal/graphics_mode.h"
|
#include "experimental/platform/hal/graphics_mode.h"
|
||||||
#include "experimental/platform/hal/renderer.h"
|
#include "experimental/platform/hal/renderer.h"
|
||||||
#include "experimental/platform/hal/window.h"
|
#include "experimental/platform/hal/window.h"
|
||||||
@@ -119,8 +121,14 @@ void App::SwitchGraphicsMode(int width, int height,
|
|||||||
window_config.gfx_mode = gfx_mode_;
|
window_config.gfx_mode = gfx_mode_;
|
||||||
window_ = std::make_unique<platform::Window>(app_title_, width, height,
|
window_ = std::make_unique<platform::Window>(app_title_, width, height,
|
||||||
window_config);
|
window_config);
|
||||||
renderer_ = std::make_unique<platform::Renderer>(
|
if (platform::IsClassic(gfx_mode_)) {
|
||||||
window_->GetNativeWindowHandle(), gfx_mode_);
|
renderer_ = std::make_unique<platform::ClassicRenderer>(
|
||||||
|
window_->GetNativeWindowHandle(), gfx_mode_);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
renderer_ = std::make_unique<platform::FilamentRenderer>(
|
||||||
|
window_->GetNativeWindowHandle(), gfx_mode_);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Figure out why this breaks on some platforms.
|
// TODO: Figure out why this breaks on some platforms.
|
||||||
// LoadSettings();
|
// LoadSettings();
|
||||||
|
|||||||
Reference in New Issue
Block a user