* 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
228 lines
6.2 KiB
C++
228 lines
6.2 KiB
C++
/*
|
|
* Copyright (C) 2025 The Android Open Source Project
|
|
*
|
|
* 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 <gtest/gtest.h>
|
|
|
|
#include <utils/RefCountedMap.h>
|
|
|
|
using namespace utils;
|
|
|
|
using KeyType = size_t;
|
|
using ValueType = size_t;
|
|
using PlainPointerType = size_t*;
|
|
using SmartPointerType = std::unique_ptr<size_t>;
|
|
|
|
/* Value types */
|
|
|
|
TEST(RefCountedMapTest, ValueType_AcquireAndRelease) {
|
|
RefCountedMap<KeyType, ValueType> map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return 1; });
|
|
EXPECT_NE(a1, nullptr);
|
|
EXPECT_EQ(*a1, 1);
|
|
EXPECT_FALSE(map.empty());
|
|
|
|
map.release(1);
|
|
EXPECT_TRUE(map.empty());
|
|
}
|
|
|
|
TEST(RefCountedMapTest, ValueType_AcquireAndReleaseMany) {
|
|
RefCountedMap<KeyType, ValueType> map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return 1; });
|
|
EXPECT_NE(a1, nullptr);
|
|
EXPECT_EQ(*a1, 1);
|
|
ValueType* a2 = map.acquire(1, []() {
|
|
ADD_FAILURE();
|
|
return 1;
|
|
});
|
|
EXPECT_EQ(a1, a2);
|
|
ValueType* a3 = map.acquire(1, []() {
|
|
ADD_FAILURE();
|
|
return 1;
|
|
});
|
|
EXPECT_EQ(a1, a3);
|
|
EXPECT_FALSE(map.empty());
|
|
|
|
map.release(1, [](ValueType& it) { ADD_FAILURE(); });
|
|
EXPECT_FALSE(map.empty());
|
|
map.release(1, [](ValueType& it) { ADD_FAILURE(); });
|
|
EXPECT_FALSE(map.empty());
|
|
map.release(1);
|
|
EXPECT_TRUE(map.empty());
|
|
}
|
|
|
|
TEST(RefCountedMapTest, ValueType_GetsValue) {
|
|
RefCountedMap<KeyType, ValueType> map;
|
|
RefCountedMap<KeyType, ValueType> const& constMap = map;
|
|
|
|
map.acquire(1, []() { return 1; });
|
|
ValueType& v1 = map.get(1);
|
|
EXPECT_EQ(v1, 1);
|
|
|
|
ValueType const& v1const = constMap.get(1);
|
|
EXPECT_EQ(v1const, 1);
|
|
}
|
|
|
|
#ifdef GTEST_HAS_DEATH_TEST
|
|
TEST(RefCountedMapTest, ValueType_PanicsIfReleaseMissing) {
|
|
RefCountedMap<KeyType, ValueType> map;
|
|
ASSERT_DEATH(map.release(1), "");
|
|
}
|
|
|
|
TEST(RefCountedMapTest, ValueType_PanicsIfGetsMissing) {
|
|
RefCountedMap<KeyType, ValueType> map;
|
|
ASSERT_DEATH(map.get(1), "");
|
|
}
|
|
#endif // GTEST_HAS_DEATH_TEST
|
|
|
|
/* Plain pointer types */
|
|
|
|
TEST(RefCountedMapTest, PlainPointerType_AcquireAndRelease) {
|
|
RefCountedMap<KeyType, PlainPointerType> map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return new size_t(1); });
|
|
EXPECT_NE(a1, nullptr);
|
|
EXPECT_EQ(*a1, 1);
|
|
EXPECT_FALSE(map.empty());
|
|
|
|
map.release(1, [](ValueType& it) { delete ⁢ });
|
|
EXPECT_TRUE(map.empty());
|
|
}
|
|
|
|
TEST(RefCountedMapTest, PlainPointerType_AcquireAndReleaseMany) {
|
|
RefCountedMap<KeyType, PlainPointerType> map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return new size_t(1); });
|
|
EXPECT_NE(a1, nullptr);
|
|
EXPECT_EQ(*a1, 1);
|
|
ValueType* a2 = map.acquire(1, []() {
|
|
ADD_FAILURE();
|
|
return new size_t(1);
|
|
});
|
|
EXPECT_EQ(a1, a2);
|
|
ValueType* a3 = map.acquire(1, []() {
|
|
ADD_FAILURE();
|
|
return new size_t(1);
|
|
});
|
|
EXPECT_EQ(a1, a3);
|
|
EXPECT_FALSE(map.empty());
|
|
|
|
map.release(1, [](ValueType& it) {
|
|
ADD_FAILURE();
|
|
delete ⁢
|
|
});
|
|
EXPECT_FALSE(map.empty());
|
|
map.release(1, [](ValueType& it) {
|
|
ADD_FAILURE();
|
|
delete ⁢
|
|
});
|
|
EXPECT_FALSE(map.empty());
|
|
map.release(1, [](ValueType& it) { delete ⁢ });
|
|
EXPECT_TRUE(map.empty());
|
|
}
|
|
|
|
TEST(RefCountedMapTest, PlainPointerType_GetsValue) {
|
|
RefCountedMap<KeyType, PlainPointerType> map;
|
|
RefCountedMap<KeyType, PlainPointerType> const& constMap = map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return new size_t(1); });
|
|
ValueType& v1 = map.get(1);
|
|
EXPECT_EQ(v1, 1);
|
|
|
|
ValueType const& v1const = constMap.get(1);
|
|
EXPECT_EQ(v1const, 1);
|
|
|
|
delete a1;
|
|
}
|
|
|
|
#ifdef GTEST_HAS_DEATH_TEST
|
|
TEST(RefCountedMapTest, PlainPointerType_PanicsIfReleaseMissing) {
|
|
RefCountedMap<KeyType, PlainPointerType> map;
|
|
ASSERT_DEATH(map.release(1), "");
|
|
}
|
|
|
|
TEST(RefCountedMapTest, PlainPointerType_PanicsIfGetsMissing) {
|
|
RefCountedMap<KeyType, PlainPointerType> map;
|
|
ASSERT_DEATH(map.get(1), "");
|
|
}
|
|
#endif // GTEST_HAS_DEATH_TEST
|
|
|
|
/* Smart pointer types */
|
|
|
|
TEST(RefCountedMapTest, SmartPointerType_AcquireAndRelease) {
|
|
RefCountedMap<KeyType, SmartPointerType> map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return std::make_unique<size_t>(1); });
|
|
EXPECT_NE(a1, nullptr);
|
|
EXPECT_EQ(*a1, 1);
|
|
EXPECT_FALSE(map.empty());
|
|
|
|
map.release(1);
|
|
EXPECT_TRUE(map.empty());
|
|
}
|
|
|
|
TEST(RefCountedMapTest, SmartPointerType_AcquireAndReleaseMany) {
|
|
RefCountedMap<KeyType, SmartPointerType> map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return std::make_unique<size_t>(1); });
|
|
EXPECT_NE(a1, nullptr);
|
|
EXPECT_EQ(*a1, 1);
|
|
ValueType* a2 = map.acquire(1, []() {
|
|
ADD_FAILURE();
|
|
return std::make_unique<size_t>(1);
|
|
});
|
|
EXPECT_EQ(a1, a2);
|
|
ValueType* a3 = map.acquire(1, []() {
|
|
ADD_FAILURE();
|
|
return std::make_unique<size_t>(1);
|
|
});
|
|
EXPECT_EQ(a1, a3);
|
|
EXPECT_FALSE(map.empty());
|
|
|
|
map.release(1);
|
|
EXPECT_FALSE(map.empty());
|
|
map.release(1);
|
|
EXPECT_FALSE(map.empty());
|
|
map.release(1);
|
|
EXPECT_TRUE(map.empty());
|
|
}
|
|
|
|
TEST(RefCountedMapTest, SmartPointerType_GetsValue) {
|
|
RefCountedMap<KeyType, SmartPointerType> map;
|
|
RefCountedMap<KeyType, SmartPointerType> const& constMap = map;
|
|
|
|
ValueType* a1 = map.acquire(1, []() { return std::make_unique<size_t>(1); });
|
|
|
|
ValueType& v1 = map.get(1);
|
|
EXPECT_EQ(v1, 1);
|
|
|
|
ValueType const& v1const = constMap.get(1);
|
|
EXPECT_EQ(v1const, 1);
|
|
}
|
|
|
|
#ifdef GTEST_HAS_DEATH_TEST
|
|
TEST(RefCountedMapTest, SmartPointerType_PanicsIfReleaseMissing) {
|
|
RefCountedMap<KeyType, SmartPointerType> map;
|
|
ASSERT_DEATH(map.release(1), "");
|
|
}
|
|
|
|
TEST(RefCountedMapTest, SmartPointerType_PanicsIfGetsMissing) {
|
|
RefCountedMap<KeyType, SmartPointerType> map;
|
|
ASSERT_DEATH(map.get(1), "");
|
|
}
|
|
#endif // GTEST_HAS_DEATH_TEST
|