Files
test2/source/blender/blenkernel/intern/cachefile.cc
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

453 lines
13 KiB
C++

/* SPDX-FileCopyrightText: 2016 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <cstring>
#include <optional>
#include "DNA_cachefile_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "BLI_fileops.h"
#include "BLI_ghash.h"
#include "BLI_listbase.h"
#include "BLI_mutex.hh"
#include "BLI_path_utils.hh"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "BLT_translation.hh"
#include "BKE_bpath.hh"
#include "BKE_cachefile.hh"
#include "BKE_idtype.hh"
#include "BKE_lib_id.hh"
#include "BKE_library.hh"
#include "BKE_main.hh"
#include "BKE_scene.hh"
#include "DEG_depsgraph_query.hh"
#include "RE_engine.h"
#include "BLO_read_write.hh"
#include "MEM_guardedalloc.h"
#ifdef WITH_ALEMBIC
# include "ABC_alembic.h"
#endif
#ifdef WITH_USD
# include "usd.hh"
#endif
static void cachefile_handle_free(CacheFile *cache_file);
static void cache_file_init_data(ID *id)
{
CacheFile *cache_file = (CacheFile *)id;
BLI_assert(MEMCMP_STRUCT_AFTER_IS_ZERO(cache_file, id));
cache_file->scale = 1.0f;
cache_file->velocity_unit = CACHEFILE_VELOCITY_UNIT_SECOND;
STRNCPY(cache_file->velocity_name, ".velocities");
}
static void cache_file_copy_data(Main * /*bmain*/,
std::optional<Library *> /*owner_library*/,
ID *id_dst,
const ID *id_src,
const int /*flag*/)
{
CacheFile *cache_file_dst = (CacheFile *)id_dst;
const CacheFile *cache_file_src = (const CacheFile *)id_src;
cache_file_dst->handle = nullptr;
cache_file_dst->handle_readers = nullptr;
BLI_duplicatelist(&cache_file_dst->object_paths, &cache_file_src->object_paths);
BLI_duplicatelist(&cache_file_dst->layers, &cache_file_src->layers);
}
static void cache_file_free_data(ID *id)
{
CacheFile *cache_file = (CacheFile *)id;
cachefile_handle_free(cache_file);
BLI_freelistN(&cache_file->object_paths);
BLI_freelistN(&cache_file->layers);
}
static void cache_file_foreach_path(ID *id, BPathForeachPathData *bpath_data)
{
CacheFile *cache_file = (CacheFile *)id;
BKE_bpath_foreach_path_fixed_process(
bpath_data, cache_file->filepath, sizeof(cache_file->filepath));
}
static void cache_file_blend_write(BlendWriter *writer, ID *id, const void *id_address)
{
CacheFile *cache_file = (CacheFile *)id;
/* Clean up, important in undo case to reduce false detection of changed datablocks. */
BLI_listbase_clear(&cache_file->object_paths);
cache_file->handle = nullptr;
memset(cache_file->handle_filepath, 0, sizeof(cache_file->handle_filepath));
cache_file->handle_readers = nullptr;
BLO_write_id_struct(writer, CacheFile, id_address, &cache_file->id);
BKE_id_blend_write(writer, &cache_file->id);
/* write layers */
LISTBASE_FOREACH (CacheFileLayer *, layer, &cache_file->layers) {
BLO_write_struct(writer, CacheFileLayer, layer);
}
}
static void cache_file_blend_read_data(BlendDataReader *reader, ID *id)
{
CacheFile *cache_file = (CacheFile *)id;
BLI_listbase_clear(&cache_file->object_paths);
cache_file->handle = nullptr;
cache_file->handle_filepath[0] = '\0';
cache_file->handle_readers = nullptr;
/* relink layers */
BLO_read_struct_list(reader, CacheFileLayer, &cache_file->layers);
}
IDTypeInfo IDType_ID_CF = {
/*id_code*/ CacheFile::id_type,
/*id_filter*/ FILTER_ID_CF,
/*dependencies_id_types*/ 0,
/*main_listbase_index*/ INDEX_ID_CF,
/*struct_size*/ sizeof(CacheFile),
/*name*/ "CacheFile",
/*name_plural*/ N_("cache_files"),
/*translation_context*/ BLT_I18NCONTEXT_ID_CACHEFILE,
/*flags*/ IDTYPE_FLAGS_APPEND_IS_REUSABLE,
/*asset_type_info*/ nullptr,
/*init_data*/ cache_file_init_data,
/*copy_data*/ cache_file_copy_data,
/*free_data*/ cache_file_free_data,
/*make_local*/ nullptr,
/*foreach_id*/ nullptr,
/*foreach_cache*/ nullptr,
/*foreach_path*/ cache_file_foreach_path,
/*owner_pointer_get*/ nullptr,
/*blend_write*/ cache_file_blend_write,
/*blend_read_data*/ cache_file_blend_read_data,
/*blend_read_after_liblink*/ nullptr,
/*blend_read_undo_preserve*/ nullptr,
/*lib_override_apply_post*/ nullptr,
};
/* TODO: make this per cache file to avoid global locks. */
static blender::Mutex cache_mutex;
void BKE_cachefile_reader_open(CacheFile *cache_file,
CacheReader **reader,
Object *object,
const char *object_path)
{
#if defined(WITH_ALEMBIC) || defined(WITH_USD)
BLI_assert(cache_file->id.tag & ID_TAG_COPIED_ON_EVAL);
if (cache_file->handle == nullptr) {
return;
}
switch (cache_file->type) {
case CACHEFILE_TYPE_ALEMBIC:
# ifdef WITH_ALEMBIC
/* Open Alembic cache reader. */
*reader = CacheReader_open_alembic_object(
cache_file->handle, *reader, object, object_path, cache_file->is_sequence);
# endif
break;
case CACHEFILE_TYPE_USD:
# ifdef WITH_USD
/* Open USD cache reader. */
*reader = blender::io::usd::CacheReader_open_usd_object(
cache_file->handle, *reader, object, object_path);
# endif
break;
case CACHE_FILE_TYPE_INVALID:
break;
}
/* Multiple modifiers and constraints can call this function concurrently. */
std::lock_guard lock(cache_mutex);
if (*reader) {
/* Register in set so we can free it when the cache file changes. */
if (cache_file->handle_readers == nullptr) {
cache_file->handle_readers = BLI_gset_ptr_new("CacheFile.handle_readers");
}
BLI_gset_reinsert(cache_file->handle_readers, reader, nullptr);
}
else if (cache_file->handle_readers) {
/* Remove in case CacheReader_open_alembic_object free the existing reader. */
BLI_gset_remove(cache_file->handle_readers, reader, nullptr);
}
#else
UNUSED_VARS(cache_file, reader, object, object_path);
#endif
}
void BKE_cachefile_reader_free(CacheFile *cache_file, CacheReader **reader)
{
#if defined(WITH_ALEMBIC) || defined(WITH_USD)
/* Multiple modifiers and constraints can call this function concurrently, and
* cachefile_handle_free() can also be called at the same time. */
std::lock_guard lock(cache_mutex);
if (*reader != nullptr) {
if (cache_file) {
BLI_assert(cache_file->id.tag & ID_TAG_COPIED_ON_EVAL);
switch (cache_file->type) {
case CACHEFILE_TYPE_ALEMBIC:
# ifdef WITH_ALEMBIC
ABC_CacheReader_free(*reader);
# endif
break;
case CACHEFILE_TYPE_USD:
# ifdef WITH_USD
blender::io::usd::USD_CacheReader_free(*reader);
# endif
break;
case CACHE_FILE_TYPE_INVALID:
break;
}
}
*reader = nullptr;
if (cache_file && cache_file->handle_readers) {
BLI_gset_remove(cache_file->handle_readers, reader, nullptr);
}
}
#else
UNUSED_VARS(cache_file, reader);
#endif
}
static void cachefile_handle_free(CacheFile *cache_file)
{
#if defined(WITH_ALEMBIC) || defined(WITH_USD)
/* Free readers in all modifiers and constraints that use the handle, before
* we free the handle itself. */
{
std::lock_guard lock(cache_mutex);
if (cache_file->handle_readers) {
GSetIterator gs_iter;
GSET_ITER (gs_iter, cache_file->handle_readers) {
CacheReader **reader = static_cast<CacheReader **>(BLI_gsetIterator_getKey(&gs_iter));
if (*reader != nullptr) {
switch (cache_file->type) {
case CACHEFILE_TYPE_ALEMBIC:
# ifdef WITH_ALEMBIC
ABC_CacheReader_free(*reader);
# endif
break;
case CACHEFILE_TYPE_USD:
# ifdef WITH_USD
blender::io::usd::USD_CacheReader_free(*reader);
# endif
break;
case CACHE_FILE_TYPE_INVALID:
break;
}
*reader = nullptr;
}
}
BLI_gset_free(cache_file->handle_readers, nullptr);
cache_file->handle_readers = nullptr;
}
}
/* Free handle. */
if (cache_file->handle) {
switch (cache_file->type) {
case CACHEFILE_TYPE_ALEMBIC:
# ifdef WITH_ALEMBIC
ABC_free_handle(cache_file->handle);
# endif
break;
case CACHEFILE_TYPE_USD:
# ifdef WITH_USD
blender::io::usd::USD_free_handle(cache_file->handle);
# endif
break;
case CACHE_FILE_TYPE_INVALID:
break;
}
cache_file->handle = nullptr;
}
cache_file->handle_filepath[0] = '\0';
#else
UNUSED_VARS(cache_file);
#endif
}
void *BKE_cachefile_add(Main *bmain, const char *name)
{
CacheFile *cache_file = static_cast<CacheFile *>(BKE_id_new(bmain, ID_CF, name));
return cache_file;
}
void BKE_cachefile_reload(Depsgraph *depsgraph, CacheFile *cache_file)
{
/* To force reload, free the handle and tag depsgraph to load it again. */
CacheFile *cache_file_eval = DEG_get_evaluated(depsgraph, cache_file);
if (cache_file_eval) {
cachefile_handle_free(cache_file_eval);
}
DEG_id_tag_update(&cache_file->id, ID_RECALC_SYNC_TO_EVAL);
}
void BKE_cachefile_eval(Main *bmain, Depsgraph *depsgraph, CacheFile *cache_file)
{
BLI_assert(cache_file->id.tag & ID_TAG_COPIED_ON_EVAL);
/* Compute filepath. */
char filepath[FILE_MAX];
if (!BKE_cachefile_filepath_get(bmain, depsgraph, cache_file, filepath)) {
return;
}
/* Test if filepath change or if we can keep the existing handle. */
if (STREQ(cache_file->handle_filepath, filepath)) {
return;
}
cachefile_handle_free(cache_file);
BLI_freelistN(&cache_file->object_paths);
#ifdef WITH_ALEMBIC
if (BLI_path_extension_check_glob(filepath, "*.abc")) {
cache_file->type = CACHEFILE_TYPE_ALEMBIC;
cache_file->handle = ABC_create_handle(
bmain,
filepath,
static_cast<const CacheFileLayer *>(cache_file->layers.first),
&cache_file->object_paths);
STRNCPY(cache_file->handle_filepath, filepath);
}
#endif
#ifdef WITH_USD
if (BLI_path_extension_check_glob(filepath, "*.usd;*.usda;*.usdc;*.usdz")) {
cache_file->type = CACHEFILE_TYPE_USD;
cache_file->handle = blender::io::usd::USD_create_handle(
bmain, filepath, &cache_file->object_paths);
STRNCPY(cache_file->handle_filepath, filepath);
}
#endif
if (DEG_is_active(depsgraph)) {
/* Flush object paths back to original data-block for UI. */
CacheFile *cache_file_orig = DEG_get_original(cache_file);
BLI_freelistN(&cache_file_orig->object_paths);
BLI_duplicatelist(&cache_file_orig->object_paths, &cache_file->object_paths);
}
}
bool BKE_cachefile_filepath_get(const Main *bmain,
const Depsgraph *depsgraph,
const CacheFile *cache_file,
char r_filepath[FILE_MAX])
{
BLI_strncpy(r_filepath, cache_file->filepath, FILE_MAX);
BLI_path_abs(r_filepath, ID_BLEND_PATH(bmain, &cache_file->id));
int fframe;
int frame_len;
if (cache_file->is_sequence && BLI_path_frame_get(r_filepath, &fframe, &frame_len)) {
Scene *scene = DEG_get_evaluated_scene(depsgraph);
const float ctime = BKE_scene_ctime_get(scene);
const double fps = double(scene->r.frs_sec) / double(scene->r.frs_sec_base);
const int frame = int(BKE_cachefile_time_offset(cache_file, double(ctime), fps));
char ext[32];
BLI_path_frame_strip(r_filepath, ext, sizeof(ext));
BLI_path_frame(r_filepath, FILE_MAX, frame, frame_len);
BLI_path_extension_ensure(r_filepath, FILE_MAX, ext);
/* TODO(kevin): store sequence range? */
return BLI_exists(r_filepath);
}
return true;
}
double BKE_cachefile_time_offset(const CacheFile *cache_file, const double time, const double fps)
{
const double time_offset = double(cache_file->frame_offset) / fps;
const double frame = (cache_file->override_frame ? double(cache_file->frame) : time);
return cache_file->is_sequence ? frame : frame / fps - time_offset;
}
bool BKE_cache_file_uses_render_procedural(const CacheFile *cache_file, Scene *scene)
{
RenderEngineType *render_engine_type = RE_engines_find(scene->r.engine);
if (cache_file->type != CACHEFILE_TYPE_ALEMBIC ||
!RE_engine_supports_alembic_procedural(render_engine_type, scene))
{
return false;
}
return cache_file->use_render_procedural;
}
CacheFileLayer *BKE_cachefile_add_layer(CacheFile *cache_file, const char filepath[1024])
{
LISTBASE_FOREACH (CacheFileLayer *, layer, &cache_file->layers) {
if (STREQ(layer->filepath, filepath)) {
return nullptr;
}
}
const int num_layers = BLI_listbase_count(&cache_file->layers);
CacheFileLayer *layer = MEM_callocN<CacheFileLayer>("CacheFileLayer");
STRNCPY(layer->filepath, filepath);
BLI_addtail(&cache_file->layers, layer);
cache_file->active_layer = char(num_layers + 1);
return layer;
}
CacheFileLayer *BKE_cachefile_get_active_layer(CacheFile *cache_file)
{
return static_cast<CacheFileLayer *>(
BLI_findlink(&cache_file->layers, cache_file->active_layer - 1));
}
void BKE_cachefile_remove_layer(CacheFile *cache_file, CacheFileLayer *layer)
{
cache_file->active_layer = 0;
BLI_remlink(&cache_file->layers, layer);
MEM_freeN(layer);
}