Split the core filament context from the mjr_ API compatibility layer.
PiperOrigin-RevId: 904305762 Change-Id: I462068a847a21be8ad7f3f1a8889f6b5b2aa2d5f
This commit is contained in:
committed by
Copybara-Service
parent
e8ebc994d2
commit
c41ed42407
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user