Blender V5.0
volume_grid_file_cache.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Foundation
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#ifdef WITH_OPENVDB
6
8# include "BKE_volume_openvdb.hh"
9
10# include "BLI_map.hh"
11# include "BLI_memory_cache.hh"
12# include "BLI_memory_counter.hh"
13
14# include <openvdb/openvdb.h>
15
16namespace blender::bke::volume_grid::file_cache {
17
21struct GridCache {
26 openvdb::GridBase::Ptr meta_data_grid;
30 Map<int, GVolumeGrid> grid_by_simplify_level;
31};
32
36struct FileCache {
40 std::string error_message;
44 openvdb::MetaMap meta_data;
48 Vector<GridCache> grids;
49
50 GridCache *grid_cache_by_name(const StringRef name)
51 {
52 for (GridCache &grid_cache : this->grids) {
53 if (grid_cache.meta_data_grid->getName() == name) {
54 return &grid_cache;
55 }
56 }
57 return nullptr;
58 }
59};
60
64struct GlobalCache {
66 Map<std::string, FileCache> file_map;
67};
68
72static GlobalCache &get_global_cache()
73{
74 static GlobalCache global_cache;
75 return global_cache;
76}
77
82static FileCache create_file_cache(const StringRef file_path)
83{
84 FileCache file_cache;
85
86 openvdb::io::File file(file_path);
87 openvdb::GridPtrVec vdb_grids;
88 try {
89 /* Disable delay loading and file copying, this has poor performance
90 * on network drives. */
91 const bool delay_load = false;
92# ifdef OPENVDB_USE_DELAYED_LOADING
93 file.setCopyMaxBytes(0);
94# endif
95 file.open(delay_load);
96 vdb_grids = *(file.readAllGridMetadata());
97 file_cache.meta_data = *file.getMetadata();
98 }
99 catch (const openvdb::IoError &e) {
100 file_cache.error_message = e.what();
101 }
102 catch (...) {
103 file_cache.error_message = "Unknown error reading VDB file";
104 }
105 if (!file_cache.error_message.empty()) {
106 return file_cache;
107 }
108
109 for (openvdb::GridBase::Ptr &vdb_grid : vdb_grids) {
110 if (!vdb_grid) {
111 continue;
112 }
113 GridCache grid_cache;
114 grid_cache.meta_data_grid = vdb_grid;
115 file_cache.grids.append(std::move(grid_cache));
116 }
117
118 return file_cache;
119}
120
121static FileCache &get_file_cache(const StringRef file_path)
122{
123 GlobalCache &global_cache = get_global_cache();
124 /* Assumes that the cache is locked already. */
125 BLI_assert(!global_cache.mutex.try_lock());
126 return global_cache.file_map.lookup_or_add_cb_as(file_path,
127 [&]() { return create_file_cache(file_path); });
128}
129
133class GridReadKey : public GenericKey {
134 public:
135 std::string file_path;
136 std::string grid_name;
137 int simplify_level;
138
139 uint64_t hash() const override
140 {
141 return get_default_hash(this->file_path, this->grid_name, this->simplify_level);
142 }
143
144 BLI_STRUCT_EQUALITY_OPERATORS_3(GridReadKey, file_path, grid_name, simplify_level)
145
146 bool equal_to(const GenericKey &other) const override
147 {
148 if (const auto *other_typed = dynamic_cast<const GridReadKey *>(&other)) {
149 return *this == *other_typed;
150 }
151 return false;
152 }
153
154 std::unique_ptr<GenericKey> to_storable() const override
155 {
156 return std::make_unique<GridReadKey>(*this);
157 }
158};
159
160class GridReadValue : public memory_cache::CachedValue {
161 private:
162 mutable std::atomic<int64_t> bytes_ = 0;
163
164 public:
165 ImplicitSharingPtr<> tree_sharing_info;
166 openvdb::GridBase::Ptr grid;
167
168 void count_memory(MemoryCounter &memory) const override
169 {
170 /* Avoid computing the amount of memory from scratch every time. */
171 if (bytes_ == 0) {
172 this->bytes_ = grid->baseTree().memUsage();
173 }
174 memory.add(bytes_);
175 }
176};
177
182static openvdb::GridBase::Ptr load_single_grid_from_disk(const StringRef file_path,
183 const StringRef grid_name)
184{
185 /* Disable delay loading and file copying, this has poor performance
186 * on network drivers. */
187 const bool delay_load = false;
188
189 openvdb::io::File file(file_path);
190# ifdef OPENVDB_USE_DELAYED_LOADING
191 file.setCopyMaxBytes(0);
192# endif
193 file.open(delay_load);
194 return file.readGrid(grid_name);
195}
196
201static LazyLoadedGrid load_single_grid_from_disk_cached(const StringRef file_path,
202 const StringRef grid_name,
203 const int simplify_level)
204{
205 GridReadKey key;
206 key.file_path = file_path;
207 key.grid_name = grid_name;
208 key.simplify_level = simplify_level;
209
210 std::shared_ptr<const GridReadValue> value = memory_cache::get<GridReadValue>(key, [&key]() {
211 openvdb::GridBase::Ptr grid;
212 if (key.simplify_level == 0) {
213 grid = load_single_grid_from_disk(key.file_path, key.grid_name);
214 }
215 else {
216 /* Build the simplified grid from the main grid. */
217 const GVolumeGrid main_grid = get_grid_from_file(key.file_path, key.grid_name, 0);
218 const VolumeGridType grid_type = main_grid->grid_type();
219 const float resolution_factor = 1.0f / (1 << key.simplify_level);
220 VolumeTreeAccessToken tree_token;
221 grid = BKE_volume_grid_create_with_changed_resolution(
222 grid_type, main_grid->grid(tree_token), resolution_factor);
223 }
224 auto value = std::make_unique<GridReadValue>();
225 value->grid = std::move(grid);
226 value->tree_sharing_info = OpenvdbTreeSharingInfo::make(value->grid->baseTreePtr());
227 return value;
228 });
229 if (!value) {
230 return {};
231 }
232
233 /* Copy the grid so that it has a single owner. Note that the tree is still shared. */
234 openvdb::GridBase::Ptr grid = value->grid->copyGrid();
235 grid->setTransform(grid->transform().copy());
236 return {grid, value->tree_sharing_info};
237}
238
243static GVolumeGrid get_cached_grid(const StringRef file_path,
244 GridCache &grid_cache,
245 const int simplify_level)
246{
247 if (GVolumeGrid *grid = grid_cache.grid_by_simplify_level.lookup_ptr(simplify_level)) {
248 return *grid;
249 }
250 /* A callback that actually loads the full grid including the tree when it's accessed. */
251 auto load_grid_fn = [file_path = std::string(file_path),
252 grid_name = std::string(grid_cache.meta_data_grid->getName()),
253 simplify_level]() -> LazyLoadedGrid {
254 return load_single_grid_from_disk_cached(file_path, grid_name, simplify_level);
255 };
256 /* This allows the returned grid to already contain meta-data and transforms, even if the tree is
257 * not loaded yet. */
258 openvdb::GridBase::Ptr meta_data_and_transform_grid;
259 if (simplify_level == 0) {
260 /* Only pass the meta-data grid when there is no simplification for now. For simplified grids,
261 * the transform would have to be updated here already. */
262 meta_data_and_transform_grid = grid_cache.meta_data_grid->copyGrid();
263 }
264 VolumeGridData *grid_data = MEM_new<VolumeGridData>(
265 __func__, load_grid_fn, meta_data_and_transform_grid);
266 GVolumeGrid grid{grid_data};
267 grid_cache.grid_by_simplify_level.add(simplify_level, grid);
268 return grid;
269}
270
271GVolumeGrid get_grid_from_file(const StringRef file_path,
272 const StringRef grid_name,
273 const int simplify_level)
274{
275 GlobalCache &global_cache = get_global_cache();
276 std::lock_guard lock{global_cache.mutex};
277 FileCache &file_cache = get_file_cache(file_path);
278 if (GridCache *grid_cache = file_cache.grid_cache_by_name(grid_name)) {
279 return get_cached_grid(file_path, *grid_cache, simplify_level);
280 }
281 return {};
282}
283
284GridsFromFile get_all_grids_from_file(const StringRef file_path, const int simplify_level)
285{
286 GridsFromFile result;
287 GlobalCache &global_cache = get_global_cache();
288 std::lock_guard lock{global_cache.mutex};
289 FileCache &file_cache = get_file_cache(file_path);
290
291 if (!file_cache.error_message.empty()) {
292 result.error_message = file_cache.error_message;
293 return result;
294 }
295 result.file_meta_data = std::make_shared<openvdb::MetaMap>(file_cache.meta_data);
296 for (GridCache &grid_cache : file_cache.grids) {
297 result.grids.append(get_cached_grid(file_path, grid_cache, simplify_level));
298 }
299 return result;
300}
301
302void unload_unused()
303{
304 GlobalCache &global_cache = get_global_cache();
305 std::lock_guard lock{global_cache.mutex};
306 for (FileCache &file_cache : global_cache.file_map.values()) {
307 for (GridCache &grid_cache : file_cache.grids) {
308 grid_cache.grid_by_simplify_level.remove_if(
309 [&](const auto &item) { return item.value->is_mutable(); });
310 }
311 }
312}
313
314} // namespace blender::bke::volume_grid::file_cache
315
316#endif /* WITH_OPENVDB */
VolumeGridType
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_STRUCT_EQUALITY_OPERATORS_3(Type, m1, m2, m3)
volatile int lock
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
unsigned long long int uint64_t
ThreadMutex mutex
void count_memory(const VolumeGridData &grid, MemoryCounter &memory)
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
std::mutex Mutex
Definition BLI_mutex.hh:47
#define hash
Definition noise_c.cc:154
const char * name