Files
test/source/blender/blenkernel/BKE_bake_geometry_nodes_modifier.hh
Jacques Lucke b7a1325c3c BLI: use blender::Mutex by default which wraps tbb::mutex
This patch adds a new `BLI_mutex.hh` header which adds `blender::Mutex` as alias
for either `tbb::mutex` or `std::mutex` depending on whether TBB is enabled.

Description copied from the patch:
```
/**
 * blender::Mutex should be used as the default mutex in Blender. It implements a subset of the API
 * of std::mutex but has overall better guaranteed properties. It can be used with RAII helpers
 * like std::lock_guard. However, it is not compatible with e.g. std::condition_variable. So one
 * still has to use std::mutex for that case.
 *
 * The mutex provided by TBB has these properties:
 * - It's as fast as a spin-lock in the non-contended case, i.e. when no other thread is trying to
 *   lock the mutex at the same time.
 * - In the contended case, it spins a couple of times but then blocks to avoid draining system
 *   resources by spinning for a long time.
 * - It's only 1 byte large, compared to e.g. 40 bytes when using the std::mutex of GCC. This makes
 *   it more feasible to have many smaller mutexes which can improve scalability of algorithms
 *   compared to using fewer larger mutexes. Also it just reduces "memory slop" across Blender.
 * - It is *not* a fair mutex, i.e. it's not guaranteed that a thread will ever be able to lock the
 *   mutex when there are always more than one threads that try to lock it. In the majority of
 *   cases, using a fair mutex just causes extra overhead without any benefit. std::mutex is not
 *   guaranteed to be fair either.
 */
 ```

The performance benchmark suggests that the impact is negilible in almost
all cases. The only benchmarks that show interesting behavior are the once
testing foreach zones in Geometry Nodes. These tests are explicitly testing
overhead, which I still have to reduce over time. So it's not unexpected that
changing the mutex has an impact there. What's interesting is that on macos the
performance improves a lot while on linux it gets worse. Since that overhead
should eventually be removed almost entirely, I don't really consider that
blocking.

Links:
* Documentation of different mutex flavors in TBB:
  https://www.intel.com/content/www/us/en/docs/onetbb/developer-guide-api-reference/2021-12/mutex-flavors.html
* Older implementation of a similar mutex by me:
  https://archive.blender.org/developer/differential/0016/0016711/index.html
* Interesting read regarding how a mutex can be this small:
  https://webkit.org/blog/6161/locking-in-webkit/

Pull Request: https://projects.blender.org/blender/blender/pulls/138370
2025-05-07 04:53:16 +02:00

151 lines
4.6 KiB
C++

/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#pragma once
#include "BLI_mutex.hh"
#include "BLI_sub_frame.hh"
#include "BKE_bake_items.hh"
#include "BKE_bake_items_paths.hh"
#include "BKE_bake_items_serialize.hh"
#include "DNA_modifier_types.h"
struct NodesModifierData;
struct Main;
struct Object;
struct Scene;
namespace blender::bke::bake {
enum class CacheStatus {
/** The cache is up-to-date with the inputs. */
Valid,
/**
* Nodes or input values have changed since the cache was created, i.e. the output would be
* different if the simulation was run again.
*/
Invalid,
/** The cache has been baked and will not be invalidated by changing inputs. */
Baked,
};
/**
* Stores the state for a specific frame.
*/
struct FrameCache {
SubFrame frame;
BakeState state;
/**
* Used when the baked data is loaded lazily. The meta data either has to be loaded from a file
* or from an in-memory buffer.
*/
std::optional<std::variant<std::string, Span<std::byte>>> meta_data_source;
};
/**
* Stores the state after the previous simulation step. This is only used, when the frame-cache is
* not used.
*/
struct PrevCache {
BakeState state;
SubFrame frame;
};
/**
* Baked data that corresponds to either a Simulation Output or Bake node.
*/
struct NodeBakeCache {
/** All cached frames sorted by frame. */
Vector<std::unique_ptr<FrameCache>> frames;
/** Loads blob data from memory when the bake is packed. */
std::unique_ptr<MemoryBlobReader> memory_blob_reader;
/** Where to load blobs from disk when loading the baked data lazily from disk. */
std::optional<std::string> blobs_dir;
/** Used to avoid reading blobs multiple times for different frames. */
std::unique_ptr<BlobReadSharing> blob_sharing;
/** Used to avoid checking if a bake exists many times. */
bool failed_finding_bake = false;
/** Range spanning from the first to the last baked frame. */
IndexRange frame_range() const;
void reset();
};
struct SimulationNodeCache {
NodeBakeCache bake;
CacheStatus cache_status = CacheStatus::Valid;
/** Previous simulation state when only that is stored (instead of the state for every frame). */
std::optional<PrevCache> prev_cache;
void reset();
};
struct BakeNodeCache {
NodeBakeCache bake;
void reset();
};
struct ModifierCache {
mutable Mutex mutex;
/**
* Set of nested node IDs (see #bNestedNodeRef) that is expected to be baked in the next
* evaluation. This is filled and cleared by the bake operator.
*/
Set<int> requested_bakes;
Map<int, std::unique_ptr<SimulationNodeCache>> simulation_cache_by_id;
Map<int, std::unique_ptr<BakeNodeCache>> bake_cache_by_id;
SimulationNodeCache *get_simulation_node_cache(const int id);
BakeNodeCache *get_bake_node_cache(const int id);
NodeBakeCache *get_node_bake_cache(const int id);
void reset_cache(int id);
};
/**
* Reset all simulation caches in the scene, for use when some fundamental change made them
* impossible to reuse.
*/
void scene_simulation_states_reset(Scene &scene);
std::optional<NodesModifierBakeTarget> get_node_bake_target(const Object &object,
const NodesModifierData &nmd,
int node_id);
std::optional<BakePath> get_node_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd,
int node_id);
std::optional<IndexRange> get_node_bake_frame_range(const Scene &scene,
const Object &object,
const NodesModifierData &nmd,
int node_id);
std::optional<std::string> get_modifier_bake_path(const Main &bmain,
const Object &object,
const NodesModifierData &nmd);
/**
* Get default directory for baking modifier to disk.
*/
std::string get_default_modifier_bake_directory(const Main &bmain,
const Object &object,
const NodesModifierData &nmd);
std::string get_default_node_bake_directory(const Main &bmain,
const Object &object,
const NodesModifierData &nmd,
int node_id);
} // namespace blender::bke::bake