Split the core filament context from the mjr_ API compatibility layer.

PiperOrigin-RevId: 904305762
Change-Id: I462068a847a21be8ad7f3f1a8889f6b5b2aa2d5f
This commit is contained in:
Haroon Qureshi
2026-04-23 01:37:01 -07:00
committed by Copybara-Service
parent e8ebc994d2
commit c41ed42407
5 changed files with 411 additions and 231 deletions
@@ -14,13 +14,11 @@
#include "experimental/filament/filament/filament_context.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <memory>
#include <span>
#include <utility>
#include <backend/DriverEnums.h>
#include <filament/Engine.h>
#include <filament/IndexBuffer.h>
#include <filament/IndirectLight.h>
@@ -35,19 +33,10 @@
#include <math/vec4.h>
#include <utils/FixedCapacityVector.h>
#include <utils/compiler.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/filament_platform_factory.h"
#include "experimental/filament/filament/imgui_bridge.h"
#include "experimental/filament/filament/imgui_editor.h"
#include "experimental/filament/filament/model_util.h"
#include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/render_target.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
@@ -82,10 +71,6 @@ FilamentContext::FilamentContext(const mjrFilamentConfig* config)
}
FilamentContext::~FilamentContext() {
DestroyRenderTargets();
imgui_bridge_.reset();
scene_bridge_.reset();
scene_view_.reset();
object_manager_.reset();
engine_->destroy(renderer_);
engine_->destroy(window_swap_chain_);
@@ -93,207 +78,95 @@ FilamentContext::~FilamentContext() {
filament::Engine::destroy(engine_);
}
void FilamentContext::Init(const mjModel* model) {
scene_view_ = std::make_unique<SceneView>(engine_);
scene_bridge_ = std::make_unique<SceneBridge>(object_manager_.get(),
scene_view_.get(), model);
imgui_bridge_ =
std::make_unique<ImguiBridge>(object_manager_.get(), scene_view_.get());
FilamentContext::FrameHandle FilamentContext::Render(
std::span<const RenderRequest> requests,
std::span<const ReadPixelsRequest> read_requests) {
if (requests.size() != 1) {
mju_error("Only one render request is supported for now.");
}
if (read_requests.size() > 1) {
mju_error("Only one read request is supported for now.");
}
// Set clear options.
filament::Renderer::ClearOptions opts;
opts.clear = true;
opts.discard = true;
opts.clearColor = ReadElement(model, "filament.clearColor",
filament::math::float4(0, 0, 0, 1));
renderer_->setClearOptions(opts);
}
const RenderRequest& request = requests[0];
void FilamentContext::Render(const mjrRect& viewport, const mjvScene* scene) {
// If we're rendering to the window, and the window size has changed, we need
// to reacquire the swap chain.
if (scene_swap_chain_target_ == kWindowSwapChain &&
(viewport.width != window_width_ || viewport.height != window_height_)) {
if (window_width_ != 0 && window_height_ != 0) {
if constexpr (UTILS_HAS_THREADING) {
engine_->flushAndWait();
}
engine_->destroy(window_swap_chain_);
window_swap_chain_ = engine_->createSwapChain(config_.native_window);
if (request.target == nullptr) {
if (!read_requests.empty()) {
mju_error("Cannot read pixels from the window.");
}
window_width_ = viewport.width;
window_height_ = viewport.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 (imgui_bridge_ && gui_swap_chain_target_ == scene_swap_chain_target_) {
// 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.
imgui_bridge_->Update();
}
last_render_mode_ = DrawMode::Color;
if (scene->flags[mjRND_SEGMENT]) {
last_render_mode_ = DrawMode::Segmentation;
} else if (scene->flags[mjRND_DEPTH]) {
last_render_mode_ = DrawMode::Depth;
}
last_camera_ = mjv_averageCamera(scene->camera, scene->camera + 1);
// Render the frame if we're not rendering to a texture.
if (scene_swap_chain_target_ == kWindowSwapChain) {
if constexpr (UTILS_HAS_THREADING) {
// Wait until previous frame is completed before requesting a new frame.
engine_->flushAndWait();
}
// If the window size has changed, we need to reacquire the swap chain.
if (request.width != window_width_ || request.height != window_height_) {
if (window_width_ != 0 && window_height_ != 0) {
engine_->destroy(window_swap_chain_);
window_swap_chain_ = engine_->createSwapChain(config_.native_window);
}
window_width_ = request.width;
window_height_ = request.height;
}
SceneView::RenderRequest scene_view_request;
scene_view_request.draw_mode = request.draw_mode;
scene_view_request.viewport = {0, 0, request.width, request.height};
scene_view_request.camera = request.camera;
scene_view_request.enable_ux = request.draw_ux;
if (renderer_->beginFrame(window_swap_chain_)) {
SceneView::RenderRequest request;
request.draw_mode = last_render_mode_;
request.viewport = viewport;
request.camera = last_camera_;
request.enable_ux = (gui_swap_chain_target_ == kWindowSwapChain);
scene_view_->Render(renderer_, request);
request.scene->Render(renderer_, scene_view_request);
renderer_->endFrame();
}
if constexpr (!UTILS_HAS_THREADING) {
engine_->execute();
}
}
}
void FilamentContext::SetFrameBuffer(int framebuffer) {
switch (framebuffer) {
case mjFB_WINDOW:
scene_swap_chain_target_ = kWindowSwapChain;
gui_swap_chain_target_ = kWindowSwapChain;
break;
case mjFB_OFFSCREEN:
scene_swap_chain_target_ = kOffscreenSwapChain;
gui_swap_chain_target_ = kWindowSwapChain;
break;
case 2: // No official constant fo this.
scene_swap_chain_target_ = kOffscreenSwapChain;
gui_swap_chain_target_ = kOffscreenSwapChain;
break;
default:
mju_error("Invalid framebuffer mode: %d", framebuffer);
}
if (framebuffer == 0) {
DestroyRenderTargets();
}
}
void FilamentContext::PrepareRenderTargets(int width, int height) {
RenderTargetConfig config;
DefaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGB8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
color_target_ = std::make_unique<RenderTarget>(engine_, config);
color_target_->Prepare(width, height);
config.color_format = mjPIXEL_FORMAT_R32F;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
depth_target_ = std::make_unique<RenderTarget>(engine_, config);
depth_target_->Prepare(width, height);
}
void FilamentContext::DestroyRenderTargets() {
depth_target_.reset();
color_target_.reset();
}
void FilamentContext::ReadPixels(mjrRect viewport, unsigned char* rgb,
float* depth) {
if (scene_swap_chain_target_ != kOffscreenSwapChain) {
mju_error("Cannot read pixels unless framebuffer is set.");
}
if (color_target_ == nullptr || depth_target_ == nullptr) {
if (viewport.left != 0) {
mju_error("Reading subpixels not supported.");
} else {
if (read_requests.empty()) {
mju_error(
"Rendering to a render target without a read request is pointless.");
}
const ReadPixelsRequest& read_request = read_requests[0];
if (read_request.num_bytes == 0) {
mju_error("Output buffer size is zero.");
}
if (viewport.bottom != 0) {
mju_error("Reading subpixels not supported.");
}
PrepareRenderTargets(viewport.width, viewport.height);
}
if (rgb) {
if (renderer_->beginFrame(offscreen_swap_chain_)) {
SceneView::RenderRequest request;
request.draw_mode = last_render_mode_;
request.viewport = viewport;
request.target = color_target_.get();
request.camera = last_camera_;
request.enable_ux = (gui_swap_chain_target_ == kOffscreenSwapChain);
scene_view_->Render(renderer_, request);
const size_t num_bytes = viewport.width * viewport.height * 3;
color_target_->ReadColorPixels(renderer_, rgb, num_bytes);
SceneView::RenderRequest scene_view_request;
scene_view_request.draw_mode = request.draw_mode;
scene_view_request.viewport = {0, 0, request.width, request.height};
scene_view_request.camera = request.camera;
scene_view_request.enable_ux = request.draw_ux;
scene_view_request.target = request.target;
request.scene->Render(renderer_, scene_view_request);
request.target->ReadColorPixels(renderer_, read_request.output,
read_request.num_bytes);
renderer_->endFrame();
}
}
if (depth) {
if (renderer_->beginFrame(offscreen_swap_chain_)) {
SceneView::RenderRequest request;
request.draw_mode = DrawMode::Depth;
request.viewport = viewport;
request.target = depth_target_.get();
request.camera = last_camera_;
scene_view_->Render(renderer_, request);
const size_t num_bytes = viewport.width * viewport.height * sizeof(float);
depth_target_->ReadColorPixels(
renderer_, reinterpret_cast<uint8_t*>(depth), num_bytes);
renderer_->endFrame();
engine_->flushAndWait();
if (read_request.read_completed_callback) {
read_request.read_completed_callback(read_request.user_data);
}
}
return ++frame_counter_;
}
if (rgb || depth) {
if constexpr (UTILS_HAS_THREADING) {
// Wait for rendering to copy back to buffer to complete.
engine_->flushAndWait();
}
void FilamentContext::WaitForFrame(FrameHandle frame_handle) {
if (frame_counter_ < frame_handle) {
engine_->flushAndWait();
}
}
void FilamentContext::UploadMesh(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadMesh(model, id);
}
void FilamentContext::UploadTexture(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadTexture(model, id);
}
void FilamentContext::UploadHeightField(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadHeightField(model, id);
}
uintptr_t FilamentContext::UploadGuiImage(uintptr_t tex_id,
const uint8_t* pixels, int width,
int height, int bpp) {
if (imgui_bridge_) {
return imgui_bridge_->UploadImage(tex_id, pixels, width, height, bpp);
}
return 0;
void FilamentContext::SetClearColor(const filament::math::float4& color) {
filament::Renderer::ClearOptions opts;
opts.clear = true;
opts.discard = true;
opts.clearColor = color;
renderer_->setClearOptions(opts);
}
double FilamentContext::GetFrameRate() const {
@@ -306,6 +179,4 @@ double FilamentContext::GetFrameRate() const {
return 1.0e9 / static_cast<double>(ns);
}
void FilamentContext::UpdateGui() { DrawGui(scene_bridge_.get()); }
} // namespace mujoco
@@ -15,64 +15,106 @@
#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_
#include <cstddef>
#include <cstdint>
#include <span>
#include <memory>
#include <backend/Platform.h>
#include <filament/Engine.h>
#include <filament/Renderer.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <filament/SwapChain.h>
#include <math/vec4.h>
#include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/imgui_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/object_manager.h"
#include "experimental/filament/filament/render_target.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
// Manages the filament renderer that is exposed via the mjr functions.
// Manages the filament renderer and provides APIs for rendering scenes.
class FilamentContext {
public:
explicit FilamentContext(const mjrFilamentConfig* config);
~FilamentContext();
void Init(const mjModel* model);
// Information needed to render a single image of a scene.
struct RenderRequest {
// The scene to render.
SceneView* scene = nullptr;
void Render(const mjrRect& viewport, const mjvScene* scene);
// The method (e.g. Color, Depth, Segmentation, etc.) to use for rendering.
DrawMode draw_mode = DrawMode::Color;
void SetFrameBuffer(int framebuffer);
// The camera from which to render the scene.
mjvGLCamera camera;
void ReadPixels(mjrRect viewport, unsigned char* rgb, float* depth);
// The dimensions of the output image.
int width = 0;
int height = 0;
void UploadMesh(const mjModel* model, int id);
// The render target into which to render the image. If nullptr, the image
// will be rendered to the window (as previously configured in
// mjrFilamentConfig::native_window).
RenderTarget* target = nullptr;
void UploadTexture(const mjModel* model, int id);
// Whether or not to include the UX scene in the render. (The SceneView
// stores both a main simulation scene and the UX scene.)
bool draw_ux = true;
};
void UploadHeightField(const mjModel* model, int id);
// Information needed to read pixels from a render target.
struct ReadPixelsRequest {
RenderTarget* target = nullptr;
uintptr_t UploadGuiImage(uintptr_t tex_id, const uint8_t* pixels, int width,
int height, int bpp);
// The buffer into which the read pixels will be written.
uint8_t* output = nullptr;
// The number of bytes in the output buffer. This should match the size of
// the render target texture.
std::size_t num_bytes = 0;
// Callback when the read pixels operation is complete. This will be called
// during WaitForFrame() or in a subsequent call to Render(). This function
// can optionally be used to free the output buffer if needed.
void (*read_completed_callback)(void* user_data) = nullptr;
// User data to pass to the completion callback.
void* user_data = nullptr;
};
// Rendering is asynchronous by nature. Each render request is assigned a
// unique Handle which can be used to query the status of the request. The
// Handle can also be used to block until the request is completed.
using FrameHandle = std::uint64_t;
// Queues the given render requests for rendering. This function copies the
// necessary data from the requests into the renderer thread and returns
// immediately afterwards. The renderer thread will then perform the actual
// rendering on the GPU. Callers can use WaitForFrame to block until the
// rendering is complete.
FrameHandle Render(std::span<const RenderRequest> render_requests,
std::span<const ReadPixelsRequest> read_requests = {});
// Blocks until the given frame has completed rendering.
void WaitForFrame(FrameHandle frame_handle);
// Sets the clear color for the renderer.
void SetClearColor(const filament::math::float4& color);
// Returns the current frame rate of the renderer.
double GetFrameRate() const;
void UpdateGui();
filament::Engine* GetEngine() const { return engine_; }
ObjectManager* GetObjectManager() const { return object_manager_.get(); }
FilamentContext(const FilamentContext&) = delete;
FilamentContext& operator=(const FilamentContext&) = delete;
private:
enum SwapChainType {
kWindowSwapChain,
kOffscreenSwapChain,
};
void PrepareRenderTargets(int width, int height);
void DestroyRenderTargets();
mjrFilamentConfig config_;
filament::Engine* engine_ = nullptr;
@@ -80,19 +122,10 @@ class FilamentContext {
filament::SwapChain* window_swap_chain_ = nullptr;
filament::SwapChain* offscreen_swap_chain_ = nullptr;
std::unique_ptr<filament::backend::Platform> platform_;
DrawMode last_render_mode_ = DrawMode::Color;
mjvGLCamera last_camera_;
SwapChainType scene_swap_chain_target_ = kWindowSwapChain;
SwapChainType gui_swap_chain_target_ = kWindowSwapChain;
std::unique_ptr<RenderTarget> color_target_;
std::unique_ptr<RenderTarget> depth_target_;
std::unique_ptr<ObjectManager> object_manager_;
std::unique_ptr<SceneView> scene_view_;
std::unique_ptr<SceneBridge> scene_bridge_;
std::unique_ptr<ImguiBridge> imgui_bridge_;
int window_width_ = 0;
int window_height_ = 0;
std::uint64_t frame_counter_ = 0;
};
} // namespace mujoco
@@ -0,0 +1,186 @@
// 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/filament/filament/mjr_filament_renderer.h"
#include <cstdint>
#include <memory>
#include <math/vec4.h>
#include <mujoco/mjmodel.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/filament_context.h"
#include "experimental/filament/filament/imgui_bridge.h"
#include "experimental/filament/filament/imgui_editor.h"
#include "experimental/filament/filament/model_util.h"
#include "experimental/filament/filament/render_target.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/filament/texture.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
MjrFilamentRenderer::MjrFilamentRenderer(const mjrFilamentConfig* config)
: FilamentContext(config) {
}
void MjrFilamentRenderer::Init(const mjModel* model) {
scene_view_ = std::make_unique<SceneView>(GetEngine());
scene_bridge_ = std::make_unique<SceneBridge>(GetObjectManager(),
scene_view_.get(), model);
imgui_bridge_ =
std::make_unique<ImguiBridge>(GetObjectManager(), scene_view_.get());
SetClearColor(ReadElement(model, "filament.clearColor",
filament::math::float4(0, 0, 0, 1)));
}
void MjrFilamentRenderer::Render(const mjrRect& viewport, const mjvScene* scene) {
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 (imgui_bridge_ && gui_swap_chain_target_ == scene_swap_chain_target_) {
// 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.
imgui_bridge_->Update();
}
last_render_mode_ = DrawMode::Color;
if (scene->flags[mjRND_SEGMENT]) {
last_render_mode_ = DrawMode::Segmentation;
} else if (scene->flags[mjRND_DEPTH]) {
last_render_mode_ = DrawMode::Depth;
}
last_camera_ = mjv_averageCamera(scene->camera, scene->camera + 1);
if (scene_swap_chain_target_ == kWindowSwapChain) {
RenderRequest request;
request.scene = scene_view_.get();
request.draw_mode = last_render_mode_;
request.camera = last_camera_;
request.draw_ux = (gui_swap_chain_target_ == kWindowSwapChain);
request.width = viewport.width;
request.height = viewport.height;
FilamentContext::Render({&request, 1});
}
}
void MjrFilamentRenderer::SetFrameBuffer(int framebuffer) {
switch (framebuffer) {
case mjFB_WINDOW:
scene_swap_chain_target_ = kWindowSwapChain;
gui_swap_chain_target_ = kWindowSwapChain;
break;
case mjFB_OFFSCREEN:
scene_swap_chain_target_ = kOffscreenSwapChain;
gui_swap_chain_target_ = kWindowSwapChain;
break;
case 2: // No official constant fo this.
scene_swap_chain_target_ = kOffscreenSwapChain;
gui_swap_chain_target_ = kOffscreenSwapChain;
break;
default:
mju_error("Invalid framebuffer mode: %d", framebuffer);
}
}
void MjrFilamentRenderer::ReadPixels(mjrRect viewport, unsigned char* rgb,
float* depth) {
if (scene_swap_chain_target_ != kOffscreenSwapChain) {
mju_error("ReadPixels is only supported for offscreen rendering.");
}
RenderRequest request;
request.scene = scene_view_.get();
request.camera = last_camera_;
request.draw_ux = (gui_swap_chain_target_ == kOffscreenSwapChain);
request.width = viewport.width;
request.height = viewport.height;
if (rgb) {
request.draw_mode = last_render_mode_;
RenderTargetConfig config;
DefaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_RGB8;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
auto target = std::make_unique<RenderTarget>(GetEngine(), config);
target->Prepare(request.width, request.height);
request.target = target.get();
ReadPixelsRequest read_request;
read_request.output = rgb;
read_request.num_bytes = viewport.width * viewport.height * 3;
const FrameHandle frame =
FilamentContext::Render({&request, 1}, {&read_request, 1});
FilamentContext::WaitForFrame(frame);
}
if (depth) {
request.draw_mode = DrawMode::Depth;
RenderTargetConfig config;
DefaultRenderTargetConfig(&config);
config.color_format = mjPIXEL_FORMAT_R32F;
config.depth_format = mjPIXEL_FORMAT_DEPTH32F;
auto target = std::make_unique<RenderTarget>(GetEngine(), config);
target->Prepare(request.width, request.height);
request.target = target.get();
ReadPixelsRequest read_request;
read_request.output = reinterpret_cast<uint8_t*>(depth);
read_request.num_bytes = viewport.width * viewport.height * sizeof(float);
const FrameHandle frame =
FilamentContext::Render({&request, 1}, {&read_request, 1});
FilamentContext::WaitForFrame(frame);
}
}
void MjrFilamentRenderer::UploadMesh(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadMesh(model, id);
}
void MjrFilamentRenderer::UploadTexture(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadTexture(model, id);
}
void MjrFilamentRenderer::UploadHeightField(const mjModel* model, int id) {
if (!scene_bridge_) {
mju_error("SceneBridge is not initialized.");
}
scene_bridge_->UploadHeightField(model, id);
}
uintptr_t MjrFilamentRenderer::UploadGuiImage(uintptr_t tex_id,
const uint8_t* pixels, int width,
int height, int bpp) {
if (imgui_bridge_) {
return imgui_bridge_->UploadImage(tex_id, pixels, width, height, bpp);
}
return 0;
}
void MjrFilamentRenderer::UpdateGui() { DrawGui(scene_bridge_.get()); }
} // namespace mujoco
@@ -0,0 +1,90 @@
// 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_FILAMENT_FILAMENT_MJR_FILAMENT_RENDERER_H_
#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MJR_FILAMENT_RENDERER_H_
#include <cstdint>
#include <memory>
#include <mujoco/mjmodel.h>
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h>
#include "experimental/filament/filament/draw_mode.h"
#include "experimental/filament/filament/filament_context.h"
#include "experimental/filament/filament/imgui_bridge.h"
#include "experimental/filament/filament/scene_bridge.h"
#include "experimental/filament/filament/scene_view.h"
#include "experimental/filament/render_context_filament.h"
namespace mujoco {
// Subclass of the FilamentContext that implements the legacy mjr API.
class MjrFilamentRenderer : public FilamentContext {
public:
explicit MjrFilamentRenderer(const mjrFilamentConfig* config);
~MjrFilamentRenderer() = default;
// Initializes the renderer with the given model.
void Init(const mjModel* model);
// Renders the given mjvScene to the viewport.
void Render(const mjrRect& viewport, const mjvScene* scene);
// Configures the renderer to render to the window (0) or an offscreen
// texture (1 or 2). Rendering to the window always includes UX data from
// ImGui. A value of 1 indicates the UX should not be included in the
// offscreen render, whereas 2 indicates that it should.
void SetFrameBuffer(int framebuffer);
// Renders the scene to a texture if the framebuffer is not 0.
void ReadPixels(mjrRect viewport, unsigned char* rgb, float* depth);
// Uploads the mesh data from the model to the GPU.
void UploadMesh(const mjModel* model, int id);
// Uploads the texture data from the model to the GPU.
void UploadTexture(const mjModel* model, int id);
// Uploads the height field data from the model to the GPU.
void UploadHeightField(const mjModel* model, int id);
// Uploads a texture that can be used with ImGui to the GPU.
uintptr_t UploadGuiImage(uintptr_t tex_id, const uint8_t* pixels, int width,
int height, int bpp);
// Renders an ImGui window containing Filament-specific editor UI.
void UpdateGui();
MjrFilamentRenderer(const MjrFilamentRenderer&) = delete;
MjrFilamentRenderer& operator=(const MjrFilamentRenderer&) = delete;
private:
enum SwapChainType {
kWindowSwapChain,
kOffscreenSwapChain,
};
DrawMode last_render_mode_ = DrawMode::Color;
mjvGLCamera last_camera_;
SwapChainType scene_swap_chain_target_ = kWindowSwapChain;
SwapChainType gui_swap_chain_target_ = kWindowSwapChain;
std::unique_ptr<SceneView> scene_view_;
std::unique_ptr<SceneBridge> scene_bridge_;
std::unique_ptr<ImguiBridge> imgui_bridge_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MJR_FILAMENT_RENDERER_H_
@@ -21,13 +21,13 @@
#include <mujoco/mjrender.h>
#include <mujoco/mjvisualize.h>
#include <mujoco/mujoco.h>
#include "experimental/filament/filament/filament_context.h"
#include "experimental/filament/filament/mjr_filament_renderer.h"
#if defined(TLS_FILAMENT_CONTEXT)
static thread_local mujoco::FilamentContext* g_filament_context = nullptr;
static thread_local mujoco::MjrFilamentRenderer* g_filament_context = nullptr;
#else
static mujoco::FilamentContext* g_filament_context = nullptr;
static mujoco::MjrFilamentRenderer* g_filament_context = nullptr;
#endif
static void CheckFilamentContext() {
@@ -43,13 +43,13 @@ void mjrf_defaultFilamentConfig(mjrFilamentConfig* config) {
}
void mjrf_makeFilamentContext(const mjModel* m, mjrContext* con,
const mjrFilamentConfig* config) {
const mjrFilamentConfig* config) {
// TODO: Support multiple contexts and multiple threads. For now, we'll just
// assume a single, global context.
if (g_filament_context != nullptr) {
mju_error("Context already exists!");
}
g_filament_context = new mujoco::FilamentContext(config);
g_filament_context = new mujoco::MjrFilamentRenderer(config);
g_filament_context->Init(m);
}