diff --git a/src/experimental/filament/CMakeLists.txt b/src/experimental/filament/CMakeLists.txt index f1747e07..0a514f7f 100644 --- a/src/experimental/filament/CMakeLists.txt +++ b/src/experimental/filament/CMakeLists.txt @@ -37,8 +37,8 @@ target_sources(${MUJOCO_FILAMENT_TARGET_NAME} filament/imgui_editor.cc filament/light.cc filament/light.h - filament/material.cc - filament/material.h + filament/material_manager.cc + filament/material_manager.h filament/mesh.cc filament/mesh.h filament/object_manager.cc diff --git a/src/experimental/filament/filament/filament_context.cc b/src/experimental/filament/filament/filament_context.cc index e5682486..f0c4a450 100644 --- a/src/experimental/filament/filament/filament_context.cc +++ b/src/experimental/filament/filament/filament_context.cc @@ -38,6 +38,7 @@ #include #include #include "experimental/filament/filament/filament_platform_factory.h" +#include "experimental/filament/filament/material_manager.h" #include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/render_target.h" #include "experimental/filament/filament/scene_view.h" @@ -72,9 +73,11 @@ FilamentContext::FilamentContext(const mjrFilamentConfig* config) engine_->createSwapChain(config_.width, config_.height); object_manager_ = std::make_unique(engine_); + material_manager_ = std::make_unique(object_manager_.get()); } FilamentContext::~FilamentContext() { + material_manager_.reset(); object_manager_.reset(); engine_->destroy(renderer_); engine_->destroy(window_swap_chain_); @@ -96,6 +99,8 @@ mjrFrameHandle FilamentContext::Render( ValidateSwapChains(requests); + material_manager_->BeginFrame(); + std::unordered_map> scene_to_requests; for (const mjrRenderRequest& request : requests) { scene_to_requests[request.scene].push_back(&request); @@ -157,6 +162,7 @@ mjrFrameHandle FilamentContext::Render( if (render_began) { renderer_->endFrame(); + material_manager_->EndFrame(); } if constexpr (!UTILS_HAS_THREADING) { engine_->execute(); diff --git a/src/experimental/filament/filament/filament_context.h b/src/experimental/filament/filament/filament_context.h index d1dfbf43..9d258220 100644 --- a/src/experimental/filament/filament/filament_context.h +++ b/src/experimental/filament/filament/filament_context.h @@ -24,6 +24,7 @@ #include #include #include +#include "experimental/filament/filament/material_manager.h" #include "experimental/filament/filament/object_manager.h" #include "experimental/filament/render_context_filament.h" @@ -60,6 +61,8 @@ class FilamentContext : public mjrfContext { ObjectManager* GetObjectManager() const { return object_manager_.get(); } + MaterialManager* GetMaterialManager() const { return material_manager_.get(); } + static FilamentContext* downcast(mjrfContext* context) { return static_cast(context); } @@ -77,6 +80,7 @@ class FilamentContext : public mjrfContext { filament::SwapChain* offscreen_swap_chain_ = nullptr; std::unique_ptr platform_; std::unique_ptr object_manager_; + std::unique_ptr material_manager_; int window_width_ = 0; int window_height_ = 0; std::uint64_t frame_counter_ = 0; diff --git a/src/experimental/filament/filament/material.h b/src/experimental/filament/filament/material.h deleted file mode 100644 index 9bb0db64..00000000 --- a/src/experimental/filament/filament/material.h +++ /dev/null @@ -1,46 +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. - -#ifndef MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_H_ -#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_H_ - -#include -#include -#include -#include "experimental/filament/filament/mesh.h" -#include "experimental/filament/filament/object_manager.h" -#include "experimental/filament/render_context_filament.h" - -namespace mujoco { - -// Generates a hash for the given material (type) and material parameters. -using MaterialKey = uint64_t; -MaterialKey BuildMaterialKey(ObjectManager::MaterialType type, - const mjrMaterial& material); - -// Returns a MaterialType that best matches the given material data and mesh. -ObjectManager::MaterialType GetMaterialType( - const mjrMaterial& material, const Mesh* mesh); - -// Updates the material instance using the given parameters and texture -// data. In some cases where a material needs a texture, but a specific -// texture is not provided, a default texture from the ObjectManager will be -// used instead. -void UpdateMaterialInstance(filament::MaterialInstance* instance, - const mjrMaterial& material, - ObjectManager* object_mgr); - -} // namespace mujoco - -#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_H_ diff --git a/src/experimental/filament/filament/material.cc b/src/experimental/filament/filament/material_manager.cc similarity index 76% rename from src/experimental/filament/filament/material.cc rename to src/experimental/filament/filament/material_manager.cc index 8c8b5460..a3d1966e 100644 --- a/src/experimental/filament/filament/material.cc +++ b/src/experimental/filament/filament/material_manager.cc @@ -1,4 +1,4 @@ -// Copyright 2025 DeepMind Technologies Limited +// Copyright 2026 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. @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "experimental/filament/filament/material.h" +#include "experimental/filament/filament/material_manager.h" #include #include #include @@ -67,18 +67,47 @@ static uint64_t hash(const T& obj) { return seed; } -uint64_t BuildMaterialKey(ObjectManager::MaterialType material_type, - const mjrMaterial& material) { +MaterialManager::MaterialManager(ObjectManager* object_mgr) + : object_mgr_(object_mgr) {} + +MaterialManager::~MaterialManager() { + for (auto& [key, instance] : instances_) { + object_mgr_->GetEngine()->destroy(instance); + } +} + +void MaterialManager::BeginFrame() { + used_keys_.clear(); +} + +void MaterialManager::EndFrame() { + if (instances_.size() == used_keys_.size()) { + return; + } + for (auto it = instances_.begin(); it != instances_.end();) { + if (!used_keys_.contains(it->first)) { + object_mgr_->GetEngine()->destroy(it->second); + it = instances_.erase(it); + } else { + ++it; + } + } +} + +static MaterialManager::MaterialKey BuildMaterialKey( + MaterialManager::MaterialType material_type, mjtGeom geom_type, + const mjrMaterial& material) { // Normally, hashing the struct by memory would be a problem because of // padding and other uninitialized data. However, we do a memset(0) on the // entire structure in mjr_defaultMaterial so this should be safe. uint64_t key = hash(material); + Combine(key, &geom_type); Combine(key, &material_type); return key; } -ObjectManager::MaterialType GetMaterialType(const mjrMaterial& material, - const Mesh* mesh) { +MaterialManager::MaterialType MaterialManager::GetMaterialType( + const mjrMaterial& material, const Mesh* mesh) { if (material.decor_ux) { if (material.color_texture) { return ObjectManager::kUnlitUi; @@ -160,9 +189,44 @@ ObjectManager::MaterialType GetMaterialType(const mjrMaterial& material, } } -void UpdateMaterialInstance(filament::MaterialInstance* instance, - const mjrMaterial& material, - ObjectManager* object_mgr) { +MaterialManager::MaterialKey MaterialManager::PrepareMaterialInstance( + const mjrMaterial& material, mjrDrawMode draw_mode, mjtGeom geom_type, + const Mesh* mesh) { + ObjectManager::MaterialType type; + if (draw_mode == mjDRAW_MODE_DEPTH) { + type = ObjectManager::kUnlitDepth; + } else if (draw_mode == mjDRAW_MODE_SEGMENTATION) { + type = ObjectManager::kUnlitSegmentation; + } else { + type = GetMaterialType(material, mesh); + } + + const MaterialKey key = BuildMaterialKey(type, geom_type, material); + + auto it = instances_.find(key); + if (it == instances_.end()) { + filament::MaterialInstance* instance = + object_mgr_->GetMaterial(type)->createInstance(); + UpdateMaterialInstance(instance, material); + if (geom_type == mjGEOM_PLANE || geom_type == mjGEOM_TRIANGLE) { + instance->setCullingMode(filament::MaterialInstance::CullingMode::NONE); + } + instances_[key] = instance; + } + used_keys_.insert(key); + return key; +} + +filament::MaterialInstance* MaterialManager::GetInstance(MaterialKey key) { + auto it = instances_.find(key); + if (it == instances_.end()) { + return nullptr; + } + return it->second; +} + +void MaterialManager::UpdateMaterialInstance(filament::MaterialInstance* instance, + const mjrMaterial& material) { if (material.scissor[2] != 0 && material.scissor[3] != 0) { instance->setScissor(material.scissor[0], material.scissor[1], material.scissor[2], material.scissor[3]); @@ -224,7 +288,7 @@ void UpdateMaterialInstance(filament::MaterialInstance* instance, instance->setParameter( name, Texture::downcast(texture)->GetFilamentTexture(), sampler); } else { - instance->setParameter(name, object_mgr->GetFallbackTexture(role), + instance->setParameter(name, object_mgr_->GetFallbackTexture(role), sampler); } } @@ -241,4 +305,12 @@ void UpdateMaterialInstance(filament::MaterialInstance* instance, TrySetTexture("Reflection", material.reflection_texture, mjTEXROLE_USER); } +ObjectManager* MaterialManager::GetObjectManager() const { + return object_mgr_; +} + +filament::Engine* MaterialManager::GetEngine() const { + return object_mgr_->GetEngine(); +} + } // namespace mujoco diff --git a/src/experimental/filament/filament/material_manager.h b/src/experimental/filament/filament/material_manager.h new file mode 100644 index 00000000..0fd37856 --- /dev/null +++ b/src/experimental/filament/filament/material_manager.h @@ -0,0 +1,88 @@ +// Copyright 2026 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_MATERIAL_MANAGER_H_ +#define MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_MANAGER_H_ + +#include +#include +#include +#include +#include +#include +#include "experimental/filament/filament/mesh.h" +#include "experimental/filament/filament/object_manager.h" +#include "experimental/filament/render_context_filament.h" + +namespace mujoco { + +// Manages the filament MaterialInstances used by Renderables. +// +// Centralizing the MaterialInstance management allows multiple Renderables to +// share the same MaterialInstance, which can reduce GPU overhead. +// MaterialInstance uniqueness is determined by hashing the parameters (mainly +// the mjrMaterial) used to create the instance. +// +// Any unused MaterialInstances are destroyed at the end of each frame. +class MaterialManager { + public: + using MaterialType = ObjectManager::MaterialType; + + explicit MaterialManager(ObjectManager* object_mgr); + ~MaterialManager(); + + MaterialManager(const MaterialManager&) = delete; + MaterialManager& operator=(const MaterialManager&) = delete; + + // A key used to uniquely identify a MaterialInstance. + using MaterialKey = uint64_t; + + // Marks the beginning of a new frame, allowing us to track which + // MaterialInstances are used during the frame so they can be removed at the + // end of the frame. + void BeginFrame(); + + // Removes any unused MaterialInstances. + void EndFrame(); + + // Returns a MaterialType that best matches the given material data and mesh. + MaterialType GetMaterialType(const mjrMaterial& material, const Mesh* mesh); + + // Prepares a MaterialInstance based on the given parameters if one does not + // already exist. Returns the key associated with the MaterialInstance. + MaterialKey PrepareMaterialInstance(const mjrMaterial& material, + mjrDrawMode draw_mode, mjtGeom geom_type, + const Mesh* mesh); + + // Returns the MaterialInstance associated with the given key. + filament::MaterialInstance* GetInstance(MaterialKey key); + + ObjectManager* GetObjectManager() const; + filament::Engine* GetEngine() const; + + private: + // Updates the material instance using the given parameters and texture + // data. In some cases where a material needs a texture, but a specific + // texture is not provided, a default texture from the ObjectManager will be + // used instead. + void UpdateMaterialInstance(filament::MaterialInstance* instance, + const mjrMaterial& material); + + ObjectManager* object_mgr_; + std::unordered_map instances_; + std::unordered_set used_keys_; +}; +} // namespace mujoco + +#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_MANAGER_H_ diff --git a/src/experimental/filament/filament/renderable.cc b/src/experimental/filament/filament/renderable.cc index 0d265e0d..5462fab7 100644 --- a/src/experimental/filament/filament/renderable.cc +++ b/src/experimental/filament/filament/renderable.cc @@ -35,9 +35,8 @@ #include #include "experimental/filament/filament_util.h" #include "experimental/filament/filament/builtins.h" -#include "experimental/filament/filament/material.h" +#include "experimental/filament/filament/material_manager.h" #include "experimental/filament/filament/mesh.h" -#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/reflection_manager.h" #include "experimental/filament/render_context_filament.h" @@ -55,8 +54,8 @@ static constexpr float kArrowHeadSize = 1.75f; Renderable::Renderable(filament::Engine* engine, const mjrRenderableParams& params, - ObjectManager* object_mgr) - : object_mgr_(object_mgr), params_(params) { + MaterialManager* material_mgr) + : material_mgr_(material_mgr), params_(params) { mjr_defaultMaterial(&material_); } @@ -70,9 +69,6 @@ Renderable::~Renderable() noexcept { engine->destroy(part.entity); em.destroy(part.entity); } - for (auto& instance : instances_) { - engine->destroy(instance.second); - } } void Renderable::SetMesh(const Mesh* mesh, int elem_offset, int elem_count) { @@ -261,6 +257,14 @@ void Renderable::Prepare(std::span requests, } } + if (request->draw_mode != mjDRAW_MODE_SEGMENTATION) { + // Clear out the segmentation color in order to take advantage of shared + // materials. + material.segmentation_color[0] = 0; + material.segmentation_color[1] = 0; + material.segmentation_color[2] = 0; + } + const bool reflective = request->draw_mode == mjDRAW_MODE_COLOR && request->enable_reflections && material.reflectance > 0.0; @@ -269,42 +273,13 @@ void Renderable::Prepare(std::span requests, this, request->viewport.width, request->viewport.height); } - draw_state.material_key = - PrepareMaterialInstance(material, request->draw_mode); + const Mesh* mesh = !parts_.empty() ? parts_[0].mesh : nullptr; + draw_state.material_key = material_mgr_->PrepareMaterialInstance( + material, request->draw_mode, geom_type_, mesh); draw_queue_.push_back(draw_state); } } -MaterialKey Renderable::PrepareMaterialInstance(const mjrMaterial& material, - mjrDrawMode draw_mode) { - ObjectManager::MaterialType type; - if (draw_mode == mjDRAW_MODE_COLOR || draw_mode == mjDRAW_MODE_WIREFRAME) { - const Mesh* mesh = !parts_.empty() ? parts_[0].mesh : nullptr; - type = GetMaterialType(material, mesh); - } else if (draw_mode == mjDRAW_MODE_DEPTH) { - type = ObjectManager::kUnlitDepth; - } else if (draw_mode == mjDRAW_MODE_SEGMENTATION) { - type = ObjectManager::kUnlitSegmentation; - } else { - mju_error("Invalid draw mode: %d", draw_mode); - return 0; - } - - const MaterialKey key = BuildMaterialKey(type, material); - - auto it = instances_.find(key); - if (it == instances_.end()) { - filament::MaterialInstance* instance = - object_mgr_->GetMaterial(type)->createInstance(); - UpdateMaterialInstance(instance, material, object_mgr_); - if (geom_type_ == mjGEOM_PLANE || geom_type_ == mjGEOM_TRIANGLE) { - instance->setCullingMode(filament::MaterialInstance::CullingMode::NONE); - } - instances_[key] = instance; - } - return key; -} - void Renderable::BindMaterialInstance(const mjrRenderRequest& request) { if (draw_queue_.empty()) { mju_error("No material instances to bind."); @@ -351,21 +326,23 @@ void Renderable::BindMaterialInstance(const mjrRenderRequest& request) { SetCastShadows(state.cast_shadows); SetReceiveShadows(state.receive_shadows); SetWireframe(state.wireframe); - SetMaterialInstance(state.material_key); - curr_state_ = state; - draw_queue_.pop_front(); -} -void Renderable::SetMaterialInstance(MaterialKey key) { - if (key != curr_state_.material_key) { - filament::MaterialInstance* instance = instances_[key]; + if (state.material_key != curr_state_.material_key) { + filament::MaterialInstance* instance = + material_mgr_->GetInstance(state.material_key); + if (!instance) { + mju_error("Failed to get material instance."); + return; + } filament::RenderableManager& rm = GetEngine()->getRenderableManager(); for (Part& part : parts_) { filament::RenderableManager::Instance ri = rm.getInstance(part.entity); rm.setMaterialInstanceAt(ri, 0, instance); } - curr_state_.material_key = key; } + + curr_state_ = state; + draw_queue_.pop_front(); } std::uint8_t Renderable::SetLayerMask(std::uint8_t mask) { @@ -412,8 +389,8 @@ void Renderable::SetCastShadows(bool cast_shadows) { params_.cast_shadows = cast_shadows; filament::RenderableManager& rm = GetEngine()->getRenderableManager(); - for (Part& part : parts_) { - rm.setCastShadows(rm.getInstance(part.entity), params_.cast_shadows); + for (Part& part : parts_) { + rm.setCastShadows(rm.getInstance(part.entity), params_.cast_shadows); } } } @@ -433,15 +410,15 @@ void Renderable::SetWireframe(bool wireframe) { static constexpr auto kWireframeType = filament::RenderableManager::PrimitiveType::LINES; - if (wireframe != wireframe_) { - wireframe_ = wireframe; + if (wireframe != curr_state_.wireframe) { + curr_state_.wireframe = wireframe; filament::RenderableManager& rm = GetEngine()->getRenderableManager(); for (Part& part : parts_) { filament::VertexBuffer* vertex_buffer = part.mesh->GetFilamentVertexBuffer(); filament::IndexBuffer* index_buffer = part.mesh->GetFilamentIndexBuffer(); rm.setGeometryAt(rm.getInstance(part.entity), 0, - wireframe_ ? kWireframeType : part.mesh->GetPrimitiveType(), + wireframe ? kWireframeType : part.mesh->GetPrimitiveType(), vertex_buffer, index_buffer, part.elem_offset, part.elem_count); } @@ -449,7 +426,8 @@ void Renderable::SetWireframe(bool wireframe) { } void Renderable::SetGeomMesh(mjtGeom type, int nstack, int nslice, int nquad) { - Builtins* builtins = object_mgr_->GetBuiltins(nstack, nslice, nquad); + Builtins* builtins = + material_mgr_->GetObjectManager()->GetBuiltins(nstack, nslice, nquad); geom_type_ = type; switch (type) { @@ -641,6 +619,6 @@ void Renderable::SetGeomMesh(mjtGeom type, int nstack, int nslice, int nquad) { } } -filament::Engine* Renderable::GetEngine() { return object_mgr_->GetEngine(); } +filament::Engine* Renderable::GetEngine() { return material_mgr_->GetEngine(); } } // namespace mujoco diff --git a/src/experimental/filament/filament/renderable.h b/src/experimental/filament/filament/renderable.h index 933aa59a..1a7bb8f1 100644 --- a/src/experimental/filament/filament/renderable.h +++ b/src/experimental/filament/filament/renderable.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -29,9 +28,8 @@ #include #include #include -#include "experimental/filament/filament/material.h" +#include "experimental/filament/filament/material_manager.h" #include "experimental/filament/filament/mesh.h" -#include "experimental/filament/filament/object_manager.h" #include "experimental/filament/filament/reflection_manager.h" #include "experimental/filament/render_context_filament.h" @@ -45,7 +43,7 @@ namespace mujoco { class Renderable : public mjrRenderable { public: Renderable(filament::Engine* engine, const mjrRenderableParams& params, - ObjectManager* object_mgr); + MaterialManager* material_mgr); ~Renderable() noexcept; Renderable(const Renderable&) = delete; @@ -148,7 +146,7 @@ class Renderable : public mjrRenderable { // of which instance, as well as other render state, to use with each render // request. struct DrawState { - MaterialKey material_key; + MaterialManager::MaterialKey material_key; bool cast_shadows = true; bool receive_shadows = true; bool wireframe = false; @@ -162,17 +160,12 @@ class Renderable : public mjrRenderable { void InitPartEntity(Part& part); void UpdateTransform(); - MaterialKey PrepareMaterialInstance(const mjrMaterial& material, - mjrDrawMode draw_mode); - void SetMaterialInstance(MaterialKey key); - filament::Engine* GetEngine(); - ObjectManager* object_mgr_; + MaterialManager* material_mgr_; mjrRenderableParams params_; mjtGeom geom_type_ = mjGEOM_NONE; mjrMaterial material_; - std::unordered_map instances_; std::deque draw_queue_; DrawState curr_state_; filament::Scene* assigned_scene_ = nullptr; @@ -181,7 +174,6 @@ class Renderable : public mjrRenderable { GetTransformFn get_transform_fn_; Trs trs_; bool infinite_plane_ = false; - bool wireframe_ = false; }; } // namespace mujoco diff --git a/src/experimental/filament/render_context_filament.cc b/src/experimental/filament/render_context_filament.cc index 19ccee6f..49b7e251 100644 --- a/src/experimental/filament/render_context_filament.cc +++ b/src/experimental/filament/render_context_filament.cc @@ -168,7 +168,7 @@ mjrRenderable* mjrf_createRenderable(mjrfContext* ctx, const mjrRenderableParams* params) { return new mujoco::Renderable( mujoco::FilamentContext::downcast(ctx)->GetEngine(), *params, - mujoco::FilamentContext::downcast(ctx)->GetObjectManager()); + mujoco::FilamentContext::downcast(ctx)->GetMaterialManager()); } void mjrf_destroyRenderable(mjrRenderable* renderable) {