Skip to content

Commit 5516cd9

Browse files
authored
materials: introduce MaterialCache (#9205)
* materials: introduce MaterialCache Presently, Filament Materials are instantiated by first parsing a bunch of read-only data from a material file, then applying a bunch of options from its Builder before settling on a final, immutable Material object. If two different Material instances need to be parameterized differently, e.g. setting their spec constants independently, each has to do all of these steps independently for each variation. This change introduces two new concepts: MaterialDefinition, representing the deserialized, read-only state of a material file, and MaterialCache, a reference-counted system responsible for managing the lifetimes of MaterialDefinitions. Now, each Material asks the cache if a MaterialDefinition exists for the particular UUID of the data it's trying to read; if not, MaterialCache creates a new entry transparently. If a hundred different Materials all try to load the same material data, only one MaterialDefinition (and its associated GPU resources) will be created. This first PR is the least possible invasive implementation of this feature. There are a lot of room for improvements (and more planned). For example, each Material still manages its own compiled shader program cache, but we can easily move this to the MaterialCache in future PRs, further enabling the planned mutable spec constants feature. Additionally, there's room here to add a Material::toBuilder() method, which could take an extant material and create a Builder object from it already parameterized with all of the same options, a la the prototype design pattern. * material cache: key on crc32 * material cache: make materialParser private * move RefCountedMap to utils and add unit tests * material cache: make create functions private * material cache: fix broken tests on iOS/web * material cache: address more comments
1 parent 77d6092 commit 5516cd9

File tree

17 files changed

+1286
-565
lines changed

17 files changed

+1286
-565
lines changed

filament/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ set(SRCS
8484
src/InstanceBuffer.cpp
8585
src/LightManager.cpp
8686
src/Material.cpp
87+
src/MaterialCache.cpp
88+
src/MaterialDefinition.cpp
8789
src/MaterialInstance.cpp
8890
src/MaterialInstanceManager.cpp
8991
src/MaterialParser.cpp
@@ -175,6 +177,8 @@ set(PRIVATE_HDRS
175177
src/HwRenderPrimitiveFactory.h
176178
src/HwVertexBufferInfoFactory.h
177179
src/Intersections.h
180+
src/MaterialCache.h
181+
src/MaterialDefinition.h
178182
src/MaterialParser.h
179183
src/MaterialInstanceManager.h
180184
src/PIDController.h

filament/backend/include/backend/DriverEnums.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -636,6 +636,15 @@ enum class ShaderModel : uint8_t {
636636
};
637637
static constexpr size_t SHADER_MODEL_COUNT = 2;
638638

639+
constexpr std::string_view to_string(ShaderModel model) noexcept {
640+
switch (model) {
641+
case ShaderModel::MOBILE:
642+
return "mobile";
643+
case ShaderModel::DESKTOP:
644+
return "desktop";
645+
}
646+
}
647+
639648
/**
640649
* Primitive types
641650
*/

filament/src/MaterialCache.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "MaterialCache.h"
18+
#include "MaterialParser.h"
19+
20+
#include <backend/DriverEnums.h>
21+
22+
#include <details/Engine.h>
23+
#include <details/Material.h>
24+
25+
#include <utils/Logger.h>
26+
27+
namespace filament {
28+
29+
size_t MaterialCache::Key::Hash::operator()(
30+
filament::MaterialCache::Key const& key) const noexcept {
31+
uint32_t crc;
32+
if (key.parser->getMaterialCrc32(&crc)) {
33+
return size_t(crc);
34+
}
35+
return size_t(key.parser->computeCrc32());
36+
}
37+
38+
bool MaterialCache::Key::operator==(Key const& rhs) const noexcept {
39+
return parser == rhs.parser;
40+
}
41+
42+
MaterialCache::~MaterialCache() {
43+
if (!mDefinitions.empty()) {
44+
LOG(WARNING) << "MaterialCache was destroyed but wasn't empty";
45+
}
46+
}
47+
48+
MaterialDefinition* UTILS_NULLABLE MaterialCache::acquire(FEngine& engine,
49+
const void* UTILS_NONNULL data, size_t size) noexcept {
50+
std::unique_ptr<MaterialParser> parser = MaterialDefinition::createParser(engine.getBackend(),
51+
engine.getShaderLanguage(), data, size);
52+
assert_invariant(parser);
53+
54+
return mDefinitions.acquire(Key{parser.get()}, [&engine, parser = std::move(parser)]() mutable {
55+
return MaterialDefinition::create(engine, std::move(parser));
56+
});
57+
}
58+
59+
void MaterialCache::release(FEngine& engine, MaterialParser const& parser) noexcept {
60+
mDefinitions.release(Key{&parser}, [&engine](MaterialDefinition& definition) {
61+
definition.terminate(engine);
62+
});
63+
}
64+
65+
} // namespace filament

filament/src/MaterialCache.h

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (C) 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
#ifndef TNT_FILAMENT_MATERIALCACHE_H
17+
#define TNT_FILAMENT_MATERIALCACHE_H
18+
19+
#include "MaterialDefinition.h"
20+
21+
#include <private/filament/Variant.h>
22+
23+
#include <backend/CallbackHandler.h>
24+
#include <backend/DriverEnums.h>
25+
#include <backend/Handle.h>
26+
#include <backend/Program.h>
27+
28+
#include <utils/Invocable.h>
29+
#include <utils/RefCountedMap.h>
30+
31+
namespace filament {
32+
33+
class Material;
34+
35+
class MaterialCache {
36+
// A newtype around a material parser used as a key for the material cache. The material file's
37+
// CRC32 is used as the hash function.
38+
struct Key {
39+
struct Hash {
40+
size_t operator()(Key const& x) const noexcept;
41+
};
42+
bool operator==(Key const& rhs) const noexcept;
43+
44+
MaterialParser const* UTILS_NONNULL parser;
45+
};
46+
47+
public:
48+
~MaterialCache();
49+
50+
/** Acquire or create a new entry in the cache for the given material data. */
51+
MaterialDefinition* UTILS_NULLABLE acquire(FEngine& engine, const void* UTILS_NONNULL data,
52+
size_t size) noexcept;
53+
54+
/** Release an entry in the cache, potentially freeing its GPU resources. */
55+
void release(FEngine& engine, MaterialParser const& parser) noexcept;
56+
57+
private:
58+
// We use unique_ptr here because we need these pointers to be stable.
59+
// TODO: investigate using a custom allocator here?
60+
utils::RefCountedMap<Key, std::unique_ptr<MaterialDefinition>, Key::Hash> mDefinitions;
61+
};
62+
63+
} // namespace filament
64+
65+
#endif // TNT_FILAMENT_MATERIALCACHE_H

0 commit comments

Comments
 (0)