Introduce MaterialManager to allow sharing of material instances.

PiperOrigin-RevId: 927139826
Change-Id: I923a24f28ab7af3a8c854286f064e851b9fd494e
This commit is contained in:
Haroon Qureshi
2026-06-05 01:17:01 -07:00
committed by Copybara-Service
parent 66156c7d9a
commit 8fbacf2883
9 changed files with 219 additions and 125 deletions
+2 -2
View File
@@ -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
@@ -38,6 +38,7 @@
#include <utils/compiler.h>
#include <mujoco/mujoco.h>
#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<ObjectManager>(engine_);
material_manager_ = std::make_unique<MaterialManager>(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<mjrScene*, std::vector<const mjrRenderRequest*>> 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();
@@ -24,6 +24,7 @@
#include <filament/Renderer.h>
#include <filament/SwapChain.h>
#include <math/vec4.h>
#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<FilamentContext*>(context);
}
@@ -77,6 +80,7 @@ class FilamentContext : public mjrfContext {
filament::SwapChain* offscreen_swap_chain_ = nullptr;
std::unique_ptr<filament::backend::Platform> platform_;
std::unique_ptr<ObjectManager> object_manager_;
std::unique_ptr<MaterialManager> material_manager_;
int window_width_ = 0;
int window_height_ = 0;
std::uint64_t frame_counter_ = 0;
@@ -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 <cstdint>
#include <filament/Engine.h>
#include <filament/MaterialInstance.h>
#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_
@@ -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 <cstddef>
#include <cstdint>
#include <functional>
@@ -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
@@ -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 <cstdint>
#include <unordered_map>
#include <unordered_set>
#include <filament/Engine.h>
#include <filament/MaterialInstance.h>
#include <mujoco/mujoco.h>
#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<MaterialKey, filament::MaterialInstance*> instances_;
std::unordered_set<MaterialKey> used_keys_;
};
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_FILAMENT_FILAMENT_MATERIAL_MANAGER_H_
@@ -35,9 +35,8 @@
#include <mujoco/mujoco.h>
#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<const mjrRenderRequest*> 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<const mjrRenderRequest*> 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
@@ -19,7 +19,6 @@
#include <deque>
#include <functional>
#include <span>
#include <unordered_map>
#include <vector>
#include <filament/Engine.h>
@@ -29,9 +28,8 @@
#include <math/vec3.h>
#include <utils/Entity.h>
#include <mujoco/mujoco.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"
@@ -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<MaterialKey, filament::MaterialInstance*> instances_;
std::deque<DrawState> 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
@@ -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) {