diff options
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r-- | source/blender/blenlib/BLI_cache_mutex.hh | 106 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_lazy_threading.hh | 11 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_string.h | 8 | ||||
-rw-r--r-- | source/blender/blenlib/BLI_task.hh | 1 | ||||
-rw-r--r-- | source/blender/blenlib/CMakeLists.txt | 2 | ||||
-rw-r--r-- | source/blender/blenlib/intern/cache_mutex.cc | 25 | ||||
-rw-r--r-- | source/blender/blenlib/intern/lazy_threading.cc | 30 | ||||
-rw-r--r-- | source/blender/blenlib/intern/math_rotation.c | 101 | ||||
-rw-r--r-- | source/blender/blenlib/intern/path_util.c | 4 | ||||
-rw-r--r-- | source/blender/blenlib/intern/string.c | 15 | ||||
-rw-r--r-- | source/blender/blenlib/intern/task_scheduler.cc | 2 | ||||
-rw-r--r-- | source/blender/blenlib/intern/uuid.cc | 27 | ||||
-rw-r--r-- | source/blender/blenlib/intern/winstuff.c | 14 | ||||
-rw-r--r-- | source/blender/blenlib/tests/BLI_math_rotation_test.cc | 16 |
14 files changed, 293 insertions, 69 deletions
diff --git a/source/blender/blenlib/BLI_cache_mutex.hh b/source/blender/blenlib/BLI_cache_mutex.hh new file mode 100644 index 00000000000..8e2a0d1b1a5 --- /dev/null +++ b/source/blender/blenlib/BLI_cache_mutex.hh @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#pragma once + +/** + * A #CacheMutex is used to protect a lazily computed cache from being computed more than once. + * Using #CacheMutex instead of a "raw mutex" to protect a cache has some benefits: + * - Avoid common pitfalls like forgetting to use task isolation or a double checked lock. + * - Cleaner and less redundant code because the same locking patterns don't have to be repeated + * everywhere. + * - One can benefit from potential future improvements to #CacheMutex of which there are a few + * mentioned below. + * + * The data protected by #CacheMutex is not part of #CacheMutex. Instead, the #CacheMutex and its + * protected data should generally be placed next to each other. + * + * Each #CacheMutex protects exactly one cache, so multiple cache mutexes have to be used when a + * class has multiple caches. That is contrary to a "custom" solution using `std::mutex` where one + * mutex could protect multiple caches at the cost of higher lock contention. + * + * To make sure the cache is up to date, call `CacheMutex::ensure` and pass in the function that + * computes the cache. + * + * To tell the #CacheMutex that the cache is invalidated and to be re-evaluated upon next access + * use `CacheMutex::tag_dirty`. + * + * This example shows how one could implement a lazily computed average vertex position in an + * imaginary `Mesh` data structure: + * + * \code{.cpp} + * class Mesh { + * private: + * mutable CacheMutex average_position_cache_mutex_; + * mutable float3 average_position_cache_; + * + * public: + * const float3 &average_position() const + * { + * average_position_cache_mutex_.ensure([&]() { + * average_position_cache_ = actually_compute_average_position(); + * }); + * return average_position_cache_; + * } + * + * void tag_positions_changed() + * { + * average_position_cache_mutex_.tag_dirty(); + * } + * }; + * \endcode + * + * Possible future improvements: + * - Avoid task isolation when we know that the cache computation does not use threading. + * - Try to use a smaller mutex. The mutex does not have to be fair for this use case. + * - Try to join the cache computation instead of blocking if another thread is computing the cache + * already. + */ + +#include <atomic> +#include <mutex> + +#include "BLI_function_ref.hh" + +namespace blender { + +class CacheMutex { + private: + std::mutex mutex_; + std::atomic<bool> cache_valid_ = false; + + public: + /** + * Make sure the cache exists and is up to date. This calls `compute_cache` once to update the + * cache (which is stored outside of this class) if it is dirty, otherwise it does nothing. + * + * This function is thread-safe under the assumption that the same parameters are passed from + * every thread. + */ + void ensure(FunctionRef<void()> compute_cache); + + /** + * Reset the cache. The next time #ensure is called, it will recompute that code. + */ + void tag_dirty() + { + cache_valid_.store(false); + } + + /** + * Return true if the cache currently does not exist or has been invalidated. + */ + bool is_dirty() const + { + return !this->is_cached(); + } + + /** + * Return true if the cache exists and is valid. + */ + bool is_cached() const + { + return cache_valid_.load(std::memory_order_relaxed); + } +}; + +} // namespace blender diff --git a/source/blender/blenlib/BLI_lazy_threading.hh b/source/blender/blenlib/BLI_lazy_threading.hh index b5a15919c89..4d04fe9e908 100644 --- a/source/blender/blenlib/BLI_lazy_threading.hh +++ b/source/blender/blenlib/BLI_lazy_threading.hh @@ -80,4 +80,15 @@ class HintReceiver { ~HintReceiver(); }; +/** + * Used to make sure that lazy-threading hints don't propagate through task isolation. This is + * necessary to avoid deadlocks when isolated regions are used together with e.g. task pools. For + * more info see the comment on #BLI_task_isolate. + */ +class ReceiverIsolation { + public: + ReceiverIsolation(); + ~ReceiverIsolation(); +}; + } // namespace blender::lazy_threading diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h index 17abcf52ecc..ed15e0871b9 100644 --- a/source/blender/blenlib/BLI_string.h +++ b/source/blender/blenlib/BLI_string.h @@ -206,6 +206,14 @@ char *BLI_sprintfN(const char *__restrict format, ...) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC ATTR_PRINTF_FORMAT(1, 2); /** + * A wrapper around `::sprintf()` which does not generate security warnings. + * + * \note Use #BLI_snprintf for cases when the string size is known. + */ +int BLI_sprintf(char *__restrict str, const char *__restrict format, ...) ATTR_NONNULL(1, 2) + ATTR_PRINTF_FORMAT(2, 3); + +/** * This roughly matches C and Python's string escaping with double quotes - `"`. * * Since every character may need escaping, diff --git a/source/blender/blenlib/BLI_task.hh b/source/blender/blenlib/BLI_task.hh index 9f9a57be634..e7d9a21439a 100644 --- a/source/blender/blenlib/BLI_task.hh +++ b/source/blender/blenlib/BLI_task.hh @@ -129,6 +129,7 @@ void parallel_invoke(const bool use_threading, Functions &&...functions) template<typename Function> void isolate_task(const Function &function) { #ifdef WITH_TBB + lazy_threading::ReceiverIsolation isolation; tbb::this_task_arena::isolate(function); #else function(); diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt index 2ac77f000e9..693a4d98675 100644 --- a/source/blender/blenlib/CMakeLists.txt +++ b/source/blender/blenlib/CMakeLists.txt @@ -54,6 +54,7 @@ set(SRC intern/bitmap_draw_2d.c intern/boxpack_2d.c intern/buffer.c + intern/cache_mutex.cc intern/compute_context.cc intern/convexhull_2d.c intern/cpp_type.cc @@ -178,6 +179,7 @@ set(SRC BLI_bounds.hh BLI_boxpack_2d.h BLI_buffer.h + BLI_cache_mutex.hh BLI_color.hh BLI_color_mix.hh BLI_compiler_attrs.h diff --git a/source/blender/blenlib/intern/cache_mutex.cc b/source/blender/blenlib/intern/cache_mutex.cc new file mode 100644 index 00000000000..db474b1ef87 --- /dev/null +++ b/source/blender/blenlib/intern/cache_mutex.cc @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#include "BLI_cache_mutex.hh" +#include "BLI_task.hh" + +namespace blender { + +void CacheMutex::ensure(const FunctionRef<void()> compute_cache) +{ + if (cache_valid_.load(std::memory_order_acquire)) { + return; + } + std::scoped_lock lock{mutex_}; + /* Double checked lock. */ + if (cache_valid_.load(std::memory_order_relaxed)) { + return; + } + /* Use task isolation because a mutex is locked and the cache computation might use + * multi-threading. */ + threading::isolate_task(compute_cache); + + cache_valid_.store(true, std::memory_order_release); +} + +} // namespace blender diff --git a/source/blender/blenlib/intern/lazy_threading.cc b/source/blender/blenlib/intern/lazy_threading.cc index 803fd81a96d..4f6d3a75ecc 100644 --- a/source/blender/blenlib/intern/lazy_threading.cc +++ b/source/blender/blenlib/intern/lazy_threading.cc @@ -1,30 +1,50 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ #include "BLI_lazy_threading.hh" +#include "BLI_stack.hh" #include "BLI_vector.hh" namespace blender::lazy_threading { /** - * This is a #RawVector so that it can be destructed after Blender checks for memory leaks. + * This uses a "raw" stack and vector so that it can be destructed after Blender checks for memory + * leaks. A new list of receivers is created whenever an isolated region is entered to avoid + * deadlocks. */ -thread_local RawVector<FunctionRef<void()>, 0> hint_receivers; +using HintReceivers = RawStack<RawVector<FunctionRef<void()>, 0>, 0>; +thread_local HintReceivers hint_receivers = []() { + HintReceivers receivers; + /* Make sure there is always at least one vector. */ + receivers.push_as(); + return receivers; +}(); void send_hint() { - for (const FunctionRef<void()> &fn : hint_receivers) { + for (const FunctionRef<void()> &fn : hint_receivers.peek()) { fn(); } } HintReceiver::HintReceiver(const FunctionRef<void()> fn) { - hint_receivers.append(fn); + hint_receivers.peek().append(fn); } HintReceiver::~HintReceiver() { - hint_receivers.pop_last(); + hint_receivers.peek().pop_last(); +} + +ReceiverIsolation::ReceiverIsolation() +{ + hint_receivers.push_as(); +} + +ReceiverIsolation::~ReceiverIsolation() +{ + BLI_assert(hint_receivers.peek().is_empty()); + hint_receivers.pop(); } } // namespace blender::lazy_threading diff --git a/source/blender/blenlib/intern/math_rotation.c b/source/blender/blenlib/intern/math_rotation.c index ff45bbee5c9..180412c4a14 100644 --- a/source/blender/blenlib/intern/math_rotation.c +++ b/source/blender/blenlib/intern/math_rotation.c @@ -275,64 +275,83 @@ void mat3_normalized_to_quat_fast(float q[4], const float mat[3][3]) /* Caller must ensure matrices aren't negative for valid results, see: T24291, T94231. */ BLI_assert(!is_negative_m3(mat)); - /* Check the trace of the matrix - bad precision if close to -1. */ - const float trace = mat[0][0] + mat[1][1] + mat[2][2]; - - if (trace > 0) { - float s = 2.0f * sqrtf(1.0f + trace); - - q[0] = 0.25f * s; - - s = 1.0f / s; - - q[1] = (mat[1][2] - mat[2][1]) * s; - q[2] = (mat[2][0] - mat[0][2]) * s; - q[3] = (mat[0][1] - mat[1][0]) * s; - } - else { - /* Find the biggest diagonal element to choose the best formula. - * Here trace should also be always >= 0, avoiding bad precision. */ - if (mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2]) { - float s = 2.0f * sqrtf(1.0f + mat[0][0] - mat[1][1] - mat[2][2]); - + /* Method outlined by Mike Day, ref: https://math.stackexchange.com/a/3183435/220949 + * with an additional `sqrtf(..)` for higher precision result. + * Removing the `sqrt` causes tests to fail unless the precision is set to 1e-6 or larger. */ + + if (mat[2][2] < 0.0f) { + if (mat[0][0] > mat[1][1]) { + const float trace = 1.0f + mat[0][0] - mat[1][1] - mat[2][2]; + float s = 2.0f * sqrtf(trace); + if (mat[1][2] < mat[2][1]) { + /* Ensure W is non-negative for a canonical result. */ + s = -s; + } q[1] = 0.25f * s; - s = 1.0f / s; - q[0] = (mat[1][2] - mat[2][1]) * s; - q[2] = (mat[1][0] + mat[0][1]) * s; + q[2] = (mat[0][1] + mat[1][0]) * s; q[3] = (mat[2][0] + mat[0][2]) * s; + if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[2] == 0.0f && q[3] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[1] = 1.0f; + } } - else if (mat[1][1] > mat[2][2]) { - float s = 2.0f * sqrtf(1.0f + mat[1][1] - mat[0][0] - mat[2][2]); - + else { + const float trace = 1.0f - mat[0][0] + mat[1][1] - mat[2][2]; + float s = 2.0f * sqrtf(trace); + if (mat[2][0] < mat[0][2]) { + /* Ensure W is non-negative for a canonical result. */ + s = -s; + } q[2] = 0.25f * s; - s = 1.0f / s; - q[0] = (mat[2][0] - mat[0][2]) * s; - q[1] = (mat[1][0] + mat[0][1]) * s; - q[3] = (mat[2][1] + mat[1][2]) * s; + q[1] = (mat[0][1] + mat[1][0]) * s; + q[3] = (mat[1][2] + mat[2][1]) * s; + if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[1] == 0.0f && q[3] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[2] = 1.0f; + } } - else { - float s = 2.0f * sqrtf(1.0f + mat[2][2] - mat[0][0] - mat[1][1]); - + } + else { + if (mat[0][0] < -mat[1][1]) { + const float trace = 1.0f - mat[0][0] - mat[1][1] + mat[2][2]; + float s = 2.0f * sqrtf(trace); + if (mat[0][1] < mat[1][0]) { + /* Ensure W is non-negative for a canonical result. */ + s = -s; + } q[3] = 0.25f * s; - s = 1.0f / s; - q[0] = (mat[0][1] - mat[1][0]) * s; q[1] = (mat[2][0] + mat[0][2]) * s; - q[2] = (mat[2][1] + mat[1][2]) * s; + q[2] = (mat[1][2] + mat[2][1]) * s; + if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[1] == 0.0f && q[2] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[3] = 1.0f; + } } - - /* Make sure W is non-negative for a canonical result. */ - if (q[0] < 0) { - negate_v4(q); + else { + /* NOTE(@campbellbarton): A zero matrix will fall through to this block, + * needed so a zero scaled matrices to return a quaternion without rotation, see: T101848. */ + const float trace = 1.0f + mat[0][0] + mat[1][1] + mat[2][2]; + float s = 2.0f * sqrtf(trace); + q[0] = 0.25f * s; + s = 1.0f / s; + q[1] = (mat[1][2] - mat[2][1]) * s; + q[2] = (mat[2][0] - mat[0][2]) * s; + q[3] = (mat[0][1] - mat[1][0]) * s; + if (UNLIKELY((trace == 1.0f) && (q[1] == 0.0f && q[2] == 0.0f && q[3] == 0.0f))) { + /* Avoids the need to normalize the degenerate case. */ + q[0] = 1.0f; + } } } - normalize_qt(q); + BLI_assert(!(q[0] < 0.0f)); + BLI_ASSERT_UNIT_QUAT(q); } static void mat3_normalized_to_quat_with_checks(float q[4], float mat[3][3]) diff --git a/source/blender/blenlib/intern/path_util.c b/source/blender/blenlib/intern/path_util.c index d13f3fe5ced..2376bd82b69 100644 --- a/source/blender/blenlib/intern/path_util.c +++ b/source/blender/blenlib/intern/path_util.c @@ -123,7 +123,7 @@ int BLI_path_sequence_decode(const char *string, char *head, char *tail, ushort void BLI_path_sequence_encode( char *string, const char *head, const char *tail, ushort numlen, int pic) { - sprintf(string, "%s%.*d%s", head, numlen, MAX2(0, pic), tail); + BLI_sprintf(string, "%s%.*d%s", head, numlen, MAX2(0, pic), tail); } static int BLI_path_unc_prefix_len(const char *path); /* defined below in same file */ @@ -620,7 +620,7 @@ bool BLI_path_suffix(char *string, size_t maxlen, const char *suffix, const char } BLI_strncpy(extension, string + a, sizeof(extension)); - sprintf(string + a, "%s%s%s", sep, suffix, extension); + BLI_sprintf(string + a, "%s%s%s", sep, suffix, extension); return true; } diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c index 755d2dbd55d..3c3dcaf90f4 100644 --- a/source/blender/blenlib/intern/string.c +++ b/source/blender/blenlib/intern/string.c @@ -241,6 +241,17 @@ char *BLI_sprintfN(const char *__restrict format, ...) return n; } +int BLI_sprintf(char *__restrict str, const char *__restrict format, ...) +{ + va_list arg; + + va_start(arg, format); + const int result = vsprintf(str, format, arg); + va_end(arg); + + return result; +} + /** \} */ /* -------------------------------------------------------------------- */ @@ -1114,7 +1125,7 @@ static size_t BLI_str_format_int_grouped_ex(char src[16], char dst[16], int num_ size_t BLI_str_format_int_grouped(char dst[16], int num) { char src[16]; - int num_len = sprintf(src, "%d", num); + const int num_len = BLI_snprintf(src, sizeof(src), "%d", num); return BLI_str_format_int_grouped_ex(src, dst, num_len); } @@ -1124,7 +1135,7 @@ size_t BLI_str_format_uint64_grouped(char dst[16], uint64_t num) /* NOTE: Buffer to hold maximum `uint64`, which is 1.8e+19. but * we also need space for commas and null-terminator. */ char src[27]; - int num_len = sprintf(src, "%" PRIu64 "", num); + const int num_len = BLI_snprintf(src, sizeof(src), "%" PRIu64 "", num); return BLI_str_format_int_grouped_ex(src, dst, num_len); } diff --git a/source/blender/blenlib/intern/task_scheduler.cc b/source/blender/blenlib/intern/task_scheduler.cc index 1f7747453c1..5b056df78b4 100644 --- a/source/blender/blenlib/intern/task_scheduler.cc +++ b/source/blender/blenlib/intern/task_scheduler.cc @@ -8,6 +8,7 @@ #include "MEM_guardedalloc.h" +#include "BLI_lazy_threading.hh" #include "BLI_task.h" #include "BLI_threads.h" @@ -67,6 +68,7 @@ int BLI_task_scheduler_num_threads() void BLI_task_isolate(void (*func)(void *userdata), void *userdata) { #ifdef WITH_TBB + blender::lazy_threading::ReceiverIsolation isolation; tbb::this_task_arena::isolate([&] { func(userdata); }); #else func(userdata); diff --git a/source/blender/blenlib/intern/uuid.cc b/source/blender/blenlib/intern/uuid.cc index 023dd1ec409..b845208f0da 100644 --- a/source/blender/blenlib/intern/uuid.cc +++ b/source/blender/blenlib/intern/uuid.cc @@ -5,6 +5,7 @@ */ #include "BLI_assert.h" +#include "BLI_string.h" #include "BLI_uuid.h" #include <cstdio> @@ -85,19 +86,19 @@ bool BLI_uuid_equal(const bUUID uuid1, const bUUID uuid2) void BLI_uuid_format(char *buffer, const bUUID uuid) { - std::sprintf(buffer, - "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", - uuid.time_low, - uuid.time_mid, - uuid.time_hi_and_version, - uuid.clock_seq_hi_and_reserved, - uuid.clock_seq_low, - uuid.node[0], - uuid.node[1], - uuid.node[2], - uuid.node[3], - uuid.node[4], - uuid.node[5]); + BLI_sprintf(buffer, + "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.time_low, + uuid.time_mid, + uuid.time_hi_and_version, + uuid.clock_seq_hi_and_reserved, + uuid.clock_seq_low, + uuid.node[0], + uuid.node[1], + uuid.node[2], + uuid.node[3], + uuid.node[4], + uuid.node[5]); } bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer) diff --git a/source/blender/blenlib/intern/winstuff.c b/source/blender/blenlib/intern/winstuff.c index 7e2c5e8f1dd..3a574b60ae2 100644 --- a/source/blender/blenlib/intern/winstuff.c +++ b/source/blender/blenlib/intern/winstuff.c @@ -110,7 +110,7 @@ bool BLI_windows_register_blend_extension(const bool background) &hkey, &dwd); if (lresult == ERROR_SUCCESS) { - sprintf(buffer, "\"%s\" \"%%1\"", BlPath); + BLI_snprintf(buffer, sizeof(buffer), "\"%s\" \"%%1\"", BlPath); lresult = RegSetValueEx(hkey, NULL, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1); RegCloseKey(hkey); } @@ -129,7 +129,7 @@ bool BLI_windows_register_blend_extension(const bool background) &hkey, &dwd); if (lresult == ERROR_SUCCESS) { - sprintf(buffer, "\"%s\", 1", BlPath); + BLI_snprintf(buffer, sizeof(buffer), "\"%s\", 1", BlPath); lresult = RegSetValueEx(hkey, NULL, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1); RegCloseKey(hkey); } @@ -167,10 +167,12 @@ bool BLI_windows_register_blend_extension(const bool background) RegCloseKey(root); printf("success (%s)\n", usr_mode ? "user" : "system"); if (!background) { - sprintf(MBox, - "File extension registered for %s.", - usr_mode ? "the current user. To register for all users, run as an administrator" : - "all users"); + BLI_snprintf(MBox, + sizeof(MBox), + "File extension registered for %s.", + usr_mode ? + "the current user. To register for all users, run as an administrator" : + "all users"); MessageBox(0, MBox, "Blender", MB_OK | MB_ICONINFORMATION); } return true; diff --git a/source/blender/blenlib/tests/BLI_math_rotation_test.cc b/source/blender/blenlib/tests/BLI_math_rotation_test.cc index e37b212e1df..0c8ae38c386 100644 --- a/source/blender/blenlib/tests/BLI_math_rotation_test.cc +++ b/source/blender/blenlib/tests/BLI_math_rotation_test.cc @@ -3,6 +3,7 @@ #include "testing/testing.h" #include "BLI_math_base.h" +#include "BLI_math_matrix.h" #include "BLI_math_rotation.h" #include "BLI_math_rotation.hh" #include "BLI_math_vector.hh" @@ -138,6 +139,21 @@ TEST(math_rotation, quat_to_mat_to_quat_near_0001) test_quat_to_mat_to_quat(0.30f, -0.030f, -0.30f, 0.95f); } +/* A zeroed matrix converted to a quaternion and back should not add rotation, see: T101848 */ +TEST(math_rotation, quat_to_mat_to_quat_zeroed_matrix) +{ + float matrix_zeroed[3][3] = {{0.0f}}; + float matrix_result[3][3]; + float matrix_unit[3][3]; + float out_quat[4]; + + unit_m3(matrix_unit); + mat3_normalized_to_quat(out_quat, matrix_zeroed); + quat_to_mat3(matrix_result, out_quat); + + EXPECT_M3_NEAR(matrix_unit, matrix_result, FLT_EPSILON); +} + TEST(math_rotation, quat_split_swing_and_twist_negative) { const float input[4] = {-0.5f, 0, sqrtf(3) / 2, 0}; |