diff --git a/src/experimental/filament/filament/filament_context.cc b/src/experimental/filament/filament/filament_context.cc index 427306b8..39e3dc0a 100644 --- a/src/experimental/filament/filament/filament_context.cc +++ b/src/experimental/filament/filament/filament_context.cc @@ -14,13 +14,11 @@ #include "experimental/filament/filament/filament_context.h" -#include #include -#include #include +#include #include -#include #include #include #include @@ -35,19 +33,10 @@ #include #include #include -#include -#include #include -#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(engine_); - scene_bridge_ = std::make_unique(object_manager_.get(), - scene_view_.get(), model); - imgui_bridge_ = - std::make_unique(object_manager_.get(), scene_view_.get()); +FilamentContext::FrameHandle FilamentContext::Render( + std::span requests, + std::span 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(engine_, config); - color_target_->Prepare(width, height); - - config.color_format = mjPIXEL_FORMAT_R32F; - config.depth_format = mjPIXEL_FORMAT_DEPTH32F; - depth_target_ = std::make_unique(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(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(ns); } -void FilamentContext::UpdateGui() { DrawGui(scene_bridge_.get()); } - } // namespace mujoco diff --git a/src/experimental/filament/filament/filament_context.h b/src/experimental/filament/filament/filament_context.h index 3f4eeb7f..7bb0dd62 100644 --- a/src/experimental/filament/filament/filament_context.h +++ b/src/experimental/filament/filament/filament_context.h @@ -15,64 +15,106 @@ #ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_ #define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_FILAMENT_CONTEXT_H_ +#include #include +#include #include #include #include #include -#include -#include +#include +#include #include #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 render_requests, + std::span 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 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 color_target_; - std::unique_ptr depth_target_; std::unique_ptr object_manager_; - std::unique_ptr scene_view_; - std::unique_ptr scene_bridge_; - std::unique_ptr imgui_bridge_; int window_width_ = 0; int window_height_ = 0; + std::uint64_t frame_counter_ = 0; }; } // namespace mujoco diff --git a/src/experimental/filament/filament/mjr_filament_renderer.cc b/src/experimental/filament/filament/mjr_filament_renderer.cc new file mode 100644 index 00000000..36908518 --- /dev/null +++ b/src/experimental/filament/filament/mjr_filament_renderer.cc @@ -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 +#include + +#include +#include +#include +#include +#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(GetEngine()); + scene_bridge_ = std::make_unique(GetObjectManager(), + scene_view_.get(), model); + imgui_bridge_ = + std::make_unique(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(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(GetEngine(), config); + target->Prepare(request.width, request.height); + request.target = target.get(); + + ReadPixelsRequest read_request; + read_request.output = reinterpret_cast(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 diff --git a/src/experimental/filament/filament/mjr_filament_renderer.h b/src/experimental/filament/filament/mjr_filament_renderer.h new file mode 100644 index 00000000..929e3378 --- /dev/null +++ b/src/experimental/filament/filament/mjr_filament_renderer.h @@ -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 +#include + +#include +#include +#include +#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 scene_view_; + std::unique_ptr scene_bridge_; + std::unique_ptr imgui_bridge_; +}; + +} // namespace mujoco + +#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MJR_FILAMENT_RENDERER_H_ diff --git a/src/experimental/filament/render_context_filament.cc b/src/experimental/filament/render_context_filament.cc index d49069f5..10f0aa2b 100644 --- a/src/experimental/filament/render_context_filament.cc +++ b/src/experimental/filament/render_context_filament.cc @@ -21,13 +21,13 @@ #include #include #include -#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); }