WIP: Functions: new local allocator for better memory reuse and performance #104630

Draft
Jacques Lucke wants to merge 44 commits from JacquesLucke/blender:local-allocator into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
8 changed files with 223 additions and 71 deletions
Showing only changes of commit a105918412 - Show all commits

View File

@ -21,7 +21,7 @@ struct Global;
/**
* This is stored per thread. Align to cache line size to avoid false sharing.
*/
struct alignas(64) Local {
struct alignas(128) Local {
/**
* Retain shared ownership of #Global to make sure that it is not destructed.
*/

View File

@ -434,8 +434,10 @@ bool try_capture_field_on_geometry(GeometryComponent &component,
GMutableSpan{type, buffer, domain_size});
evaluator.evaluate();
if (GAttributeWriter attribute = attributes.lookup_for_write(attribute_id)) {
if (attribute.domain == domain && attribute.varray.type() == type) {
const std::optional<AttributeMetaData> meta_data = attributes.lookup_meta_data(attribute_id);
if (meta_data && meta_data->domain == domain && meta_data->data_type == data_type) {
if (GAttributeWriter attribute = attributes.lookup_for_write(attribute_id)) {
attribute.varray.set_all(buffer);
attribute.finish();
type.destruct_n(buffer, domain_size);
@ -443,6 +445,7 @@ bool try_capture_field_on_geometry(GeometryComponent &component,
return true;
}
}
attributes.remove(attribute_id);
if (attributes.add(attribute_id, domain, data_type, bke::AttributeInitMoveArray{buffer})) {
return true;

View File

@ -66,9 +66,11 @@ typedef struct PoseBlendData {
/* For temp-loading the Action from the pose library. */
AssetTempIDConsumer *temp_id_consumer;
/* Blend factor, interval [-1, 1] for interpolating between current and given pose.
* Positive factors will blend in `act`, whereas negative factors will blend in `act_flipped`. */
/* Blend factor for interpolating between current and given pose.
* 1.0 means "100% pose asset". Negative values and values > 1.0 will be used as-is, and can
* cause interesting effects. */
float blend_factor;
bool is_flipped;
struct PoseBackup *pose_backup;
Object *ob; /* Object to work on. */
@ -85,11 +87,11 @@ typedef struct PoseBlendData {
} PoseBlendData;
/** Return the bAction that should be blended.
* This is either pbd->act or pbd->act_flipped, depending on the sign of the blend factor.
* This is either pbd->act or pbd->act_flipped, depending on is_flipped.
*/
static bAction *poselib_action_to_blend(PoseBlendData *pbd)
{
return (pbd->blend_factor >= 0) ? pbd->act : pbd->act_flipped;
return pbd->is_flipped ? pbd->act_flipped : pbd->act;
}
/* Makes a copy of the current pose for restoration purposes - doesn't do constraints currently */
@ -177,27 +179,32 @@ static void poselib_blend_apply(bContext *C, wmOperator *op)
struct Depsgraph *depsgraph = CTX_data_depsgraph_pointer(C);
AnimationEvalContext anim_eval_context = BKE_animsys_eval_context_construct(depsgraph, 0.0f);
bAction *to_blend = poselib_action_to_blend(pbd);
BKE_pose_apply_action_blend(pbd->ob, to_blend, &anim_eval_context, fabs(pbd->blend_factor));
BKE_pose_apply_action_blend(pbd->ob, to_blend, &anim_eval_context, pbd->blend_factor);
}
/* ---------------------------- */
static void poselib_blend_set_factor(PoseBlendData *pbd, const float new_factor)
{
const bool sign_changed = signf(new_factor) != signf(pbd->blend_factor);
if (sign_changed) {
/* The zero point was crossed, meaning that the pose will be flipped. This means the pose
* backup has to change, as it only contains the bones for one side. */
BKE_pose_backup_restore(pbd->pose_backup);
BKE_pose_backup_free(pbd->pose_backup);
}
pbd->blend_factor = new_factor;
pbd->needs_redraw = true;
}
if (sign_changed) {
poselib_backup_posecopy(pbd);
static void poselib_set_flipped(PoseBlendData *pbd, const bool new_flipped)
{
if (pbd->is_flipped == new_flipped) {
return;
}
/* The pose will toggle between flipped and normal. This means the pose
* backup has to change, as it only contains the bones for one side. */
BKE_pose_backup_restore(pbd->pose_backup);
BKE_pose_backup_free(pbd->pose_backup);
pbd->is_flipped = new_flipped;
pbd->needs_redraw = true;
poselib_backup_posecopy(pbd);
}
/* Return operator return value. */
@ -220,6 +227,9 @@ static int poselib_blend_handle_event(bContext *UNUSED(C), wmOperator *op, const
return OPERATOR_RUNNING_MODAL;
}
/* Ctrl manages the 'flipped' state. */
poselib_set_flipped(pbd, event->modifier & KM_CTRL);
/* only accept 'press' event, and ignore 'release', so that we don't get double actions */
if (ELEM(event->val, KM_PRESS, KM_NOTHING) == 0) {
return OPERATOR_RUNNING_MODAL;
@ -318,14 +328,12 @@ static bool poselib_blend_init_data(bContext *C, wmOperator *op, const wmEvent *
return false;
}
/* Passing `flipped=True` is the same as flipping the sign of the blend factor. */
const bool apply_flipped = RNA_boolean_get(op->ptr, "flipped");
const float multiply_factor = apply_flipped ? -1.0f : 1.0f;
pbd->blend_factor = multiply_factor * RNA_float_get(op->ptr, "blend_factor");
pbd->is_flipped = RNA_boolean_get(op->ptr, "flipped");
pbd->blend_factor = RNA_float_get(op->ptr, "blend_factor");
/* Only construct the flipped pose if there is a chance it's actually needed. */
const bool is_interactive = (event != NULL);
if (is_interactive || pbd->blend_factor < 0) {
if (is_interactive || pbd->is_flipped) {
pbd->act_flipped = flip_pose(C, ob, pbd->act);
}
@ -352,6 +360,7 @@ static bool poselib_blend_init_data(bContext *C, wmOperator *op, const wmEvent *
ED_slider_init(pbd->slider, event);
ED_slider_factor_set(pbd->slider, pbd->blend_factor);
ED_slider_allow_overshoot_set(pbd->slider, true);
ED_slider_allow_increments_set(pbd->slider, false);
ED_slider_is_bidirectional_set(pbd->slider, true);
}
@ -394,8 +403,8 @@ static void poselib_blend_cleanup(bContext *C, wmOperator *op)
poselib_keytag_pose(C, scene, pbd);
/* Ensure the redo panel has the actually-used value, instead of the initial value. */
RNA_float_set(op->ptr, "blend_factor", fabs(pbd->blend_factor));
RNA_boolean_set(op->ptr, "flipped", pbd->blend_factor < 0);
RNA_float_set(op->ptr, "blend_factor", pbd->blend_factor);
RNA_boolean_set(op->ptr, "flipped", pbd->is_flipped);
break;
}
@ -485,7 +494,11 @@ static int poselib_blend_modal(bContext *C, wmOperator *op, const wmEvent *event
strcpy(tab_string, TIP_("[Tab] - Show blended pose"));
}
BLI_snprintf(status_string, sizeof(status_string), "%s | %s", tab_string, slider_string);
BLI_snprintf(status_string,
sizeof(status_string),
"%s | %s | [Ctrl] - Flip Pose",
tab_string,
slider_string);
ED_workspace_status_text(C, status_string);
poselib_blend_apply(C, op);
@ -572,16 +585,14 @@ void POSELIB_OT_apply_pose_asset(wmOperatorType *ot)
FLT_MAX,
"Blend Factor",
"Amount that the pose is applied on top of the existing poses. A negative "
"value will apply the pose flipped over the X-axis",
"value will subtract the pose instead of adding it",
-1.0f,
1.0f);
prop = RNA_def_boolean(
ot->srna,
"flipped",
false,
"Apply Flipped",
"When enabled, applies the pose flipped over the X-axis. This is the same as "
"passing a negative `blend_factor`");
prop = RNA_def_boolean(ot->srna,
"flipped",
false,
"Apply Flipped",
"When enabled, applies the pose flipped over the X-axis");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
@ -612,7 +623,7 @@ void POSELIB_OT_blend_pose_asset(wmOperatorType *ot)
FLT_MAX,
"Blend Factor",
"Amount that the pose is applied on top of the existing poses. A "
"negative value will apply the pose flipped over the X-axis",
"negative value will subtract the pose instead of adding it",
-1.0f,
1.0f);
/* Blending should always start at 0%, and not at whatever percentage was last used. This RNA
@ -624,8 +635,7 @@ void POSELIB_OT_blend_pose_asset(wmOperatorType *ot)
"flipped",
false,
"Apply Flipped",
"When enabled, applies the pose flipped over the X-axis. This is the "
"same as passing a negative `blend_factor`");
"When enabled, applies the pose flipped over the X-axis");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
prop = RNA_def_boolean(ot->srna,

View File

@ -4,6 +4,7 @@
* \ingroup edasset
*/
#include <ctime>
#include <fstream>
#include <iomanip>
#include <optional>
@ -413,17 +414,24 @@ static int init_indexer_entries_from_value(FileIndexerEntries &indexer_entries,
/**
* \brief References the asset library directory.
*
* The #AssetLibraryIndex instance is used to keep track of unused file indices. When reading any
* used indices are removed from the list and when reading is finished the unused
* indices are removed.
* The #AssetLibraryIndex instance collects file indices that are existing before the actual
* reading/updating starts. This way, the reading/updating can tag pre-existing files as used when
* they are still needed. Remaining ones (indices that are not tagged as used) can be removed once
* reading finishes.
*/
struct AssetLibraryIndex {
struct PreexistingFileIndexInfo {
bool is_used = false;
};
/**
* Tracks indices that haven't been used yet.
* File indices that are existing already before reading/updating performs changes. The key is
* the absolute path. The value can store information like if the index is known to be used.
*
* Contains absolute paths to the indices.
* Note that when deleting a file index (#delete_index_file()), it's also removed from here,
* since it doesn't exist and isn't relevant to keep track of anymore.
*/
Set<std::string> unused_file_indices;
Map<std::string /*path*/, PreexistingFileIndexInfo> preexisting_file_indices;
/**
* \brief Absolute path where the indices of `library` are stored.
@ -485,9 +493,10 @@ struct AssetLibraryIndex {
}
/**
* Initialize to keep track of unused file indices.
* Check for pre-existing index files to be able to track what is still used and what can be
* removed. See #AssetLibraryIndex::preexisting_file_indices.
*/
void init_unused_index_files()
void collect_preexisting_file_indices()
{
const char *index_path = indices_base_path.c_str();
if (!BLI_is_dir(index_path)) {
@ -498,7 +507,7 @@ struct AssetLibraryIndex {
for (int i = 0; i < dir_entries_num; i++) {
struct direntry *entry = &dir_entries[i];
if (BLI_str_endswith(entry->relname, ".index.json")) {
unused_file_indices.add_as(std::string(entry->path));
preexisting_file_indices.add_as(std::string(entry->path));
}
}
@ -507,17 +516,53 @@ struct AssetLibraryIndex {
void mark_as_used(const std::string &filename)
{
unused_file_indices.remove(filename);
PreexistingFileIndexInfo *preexisting = preexisting_file_indices.lookup_ptr(filename);
if (preexisting) {
preexisting->is_used = true;
}
}
int remove_unused_index_files() const
/**
* Removes the file index from disk and #preexisting_file_indices (invalidating its iterators, so
* don't call while iterating).
* \return true if deletion was successful.
*/
bool delete_file_index(const std::string &filename)
{
if (BLI_delete(filename.c_str(), false, false) == 0) {
preexisting_file_indices.remove(filename);
return true;
}
return false;
}
/**
* A bug was creating empty index files for a while (see D16665). Remove empty index files from
* this period, so they are regenerated.
*/
/* Implemented further below. */
int remove_broken_index_files();
int remove_unused_index_files()
{
int num_files_deleted = 0;
for (const std::string &unused_index : unused_file_indices) {
const char *file_path = unused_index.c_str();
CLOG_INFO(&LOG, 2, "Remove unused index file [%s].", file_path);
BLI_delete(file_path, false, false);
num_files_deleted++;
Set<StringRef> files_to_remove;
for (auto preexisting_index : preexisting_file_indices.items()) {
if (preexisting_index.value.is_used) {
continue;
}
const std::string &file_path = preexisting_index.key;
CLOG_INFO(&LOG, 2, "Remove unused index file [%s].", file_path.c_str());
files_to_remove.add(preexisting_index.key);
}
for (StringRef file_to_remove : files_to_remove) {
if (delete_file_index(file_to_remove)) {
num_files_deleted++;
}
}
return num_files_deleted;
@ -622,8 +667,13 @@ class AssetIndexFile : public AbstractFile {
const size_t MIN_FILE_SIZE_WITH_ENTRIES = 32;
std::string filename;
AssetIndexFile(AssetLibraryIndex &library_index, StringRef index_file_path)
: library_index(library_index), filename(index_file_path)
{
}
AssetIndexFile(AssetLibraryIndex &library_index, BlendFile &asset_filename)
: library_index(library_index), filename(library_index.index_file_path(asset_filename))
: AssetIndexFile(library_index, library_index.index_file_path(asset_filename))
{
}
@ -687,6 +737,56 @@ class AssetIndexFile : public AbstractFile {
}
};
/* TODO(Julian): remove this after a short while. Just necessary for people who've been using alpha
* builds from a certain period. */
int AssetLibraryIndex::remove_broken_index_files()
{
Set<StringRef> files_to_remove;
preexisting_file_indices.foreach_item(
[&](const std::string &index_path, const PreexistingFileIndexInfo &) {
AssetIndexFile index_file(*this, index_path);
/* Bug was causing empty index files, so non-empty ones can be skipped. */
if (index_file.constains_entries()) {
return;
}
/* Use the file modification time stamp to attempt to remove empty index files from a
* certain period (when the bug was in there). Starting from a day before the bug was
* introduced until a day after the fix should be enough to mitigate possible local time
* zone issues. */
std::tm tm_from{};
tm_from.tm_year = 2022 - 1900; /* 2022 */
tm_from.tm_mon = 11 - 1; /* November */
tm_from.tm_mday = 8; /* Day before bug was introduced. */
std::tm tm_to{};
tm_from.tm_year = 2022 - 1900; /* 2022 */
tm_from.tm_mon = 12 - 1; /* December */
tm_from.tm_mday = 3; /* Day after fix. */
std::time_t timestamp_from = std::mktime(&tm_from);
std::time_t timestamp_to = std::mktime(&tm_to);
BLI_stat_t stat = {};
if (BLI_stat(index_file.get_file_path(), &stat) == -1) {
return;
}
if (IN_RANGE(stat.st_mtime, timestamp_from, timestamp_to)) {
CLOG_INFO(&LOG, 2, "Remove potentially broken index file [%s].", index_path.c_str());
files_to_remove.add(index_path);
}
});
int num_files_deleted = 0;
for (StringRef files_to_remove : files_to_remove) {
if (delete_file_index(files_to_remove)) {
num_files_deleted++;
}
}
return num_files_deleted;
}
static eFileIndexerResult read_index(const char *filename,
FileIndexerEntries *entries,
int *r_read_entries_len,
@ -762,7 +862,8 @@ static void *init_user_data(const char *root_directory, size_t root_directory_ma
{
AssetLibraryIndex *library_index = MEM_new<AssetLibraryIndex>(
__func__, StringRef(root_directory, BLI_strnlen(root_directory, root_directory_maxlen)));
library_index->init_unused_index_files();
library_index->collect_preexisting_file_indices();
library_index->remove_broken_index_files();
return library_index;
}

View File

@ -98,6 +98,9 @@ void ED_slider_factor_set(struct tSlider *slider, float factor);
bool ED_slider_allow_overshoot_get(struct tSlider *slider);
void ED_slider_allow_overshoot_set(struct tSlider *slider, bool value);
bool ED_slider_allow_increments_get(struct tSlider *slider);
void ED_slider_allow_increments_set(struct tSlider *slider, bool value);
bool ED_slider_is_bidirectional_get(struct tSlider *slider);
void ED_slider_is_bidirectional_set(struct tSlider *slider, bool value);

View File

@ -89,6 +89,10 @@ typedef struct tSlider {
* This is set by the artist while using the slider. */
bool overshoot;
/** Whether keeping CTRL pressed will snap to 10% increments.
* Default is true. Set to false if the CTRL key is needed for other means. */
bool allow_increments;
/** Move factor in 10% steps. */
bool increments;
@ -381,6 +385,7 @@ tSlider *ED_slider_create(struct bContext *C)
/* Default is true, caller needs to manually set to false. */
slider->allow_overshoot = true;
slider->allow_increments = true;
/* Set initial factor. */
slider->raw_factor = 0.5f;
@ -425,7 +430,7 @@ bool ED_slider_modal(tSlider *slider, const wmEvent *event)
break;
case EVT_LEFTCTRLKEY:
case EVT_RIGHTCTRLKEY:
slider->increments = event->val == KM_PRESS;
slider->increments = slider->allow_increments && event->val == KM_PRESS;
break;
case MOUSEMOVE:;
/* Update factor. */
@ -469,16 +474,21 @@ void ED_slider_status_string_get(const struct tSlider *slider,
STRNCPY(precision_str, TIP_("Shift - Hold for precision"));
}
if (slider->increments) {
STRNCPY(increments_str, TIP_("[Ctrl] - Increments active"));
if (slider->allow_increments) {
if (slider->increments) {
STRNCPY(increments_str, TIP_(" | [Ctrl] - Increments active"));
}
else {
STRNCPY(increments_str, TIP_(" | Ctrl - Hold for 10% increments"));
}
}
else {
STRNCPY(increments_str, TIP_("Ctrl - Hold for 10% increments"));
increments_str[0] = '\0';
}
BLI_snprintf(status_string,
size_of_status_string,
"%s | %s | %s",
"%s | %s%s",
overshoot_str,
precision_str,
increments_str);
@ -521,6 +531,16 @@ void ED_slider_allow_overshoot_set(struct tSlider *slider, const bool value)
slider->allow_overshoot = value;
}
bool ED_slider_allow_increments_get(struct tSlider *slider)
{
return slider->allow_increments;
}
void ED_slider_allow_increments_set(struct tSlider *slider, const bool value)
{
slider->allow_increments = value;
}
bool ED_slider_is_bidirectional_get(struct tSlider *slider)
{
return slider->is_bidirectional;

View File

@ -241,7 +241,7 @@ class TestEnvironment:
f'args = pickle.loads(base64.b64decode({args}))\n'
f'result = {modulename}.{functionname}(args)\n'
f'result = base64.b64encode(pickle.dumps(result))\n'
f'print("{output_prefix}" + result.decode())\n')
f'print("\\n{output_prefix}" + result.decode() + "\\n")\n')
expr_args = blender_args + ['--python-expr', expression]
lines = self.call_blender(expr_args, foreground=foreground)

View File

@ -11,18 +11,33 @@ def _run(args):
# Evaluate objects once first, to avoid any possible lazy evaluation later.
bpy.context.view_layer.update()
# Tag all objects with geometry nodes modifiers to be recalculated.
for ob in bpy.context.view_layer.objects:
for modifier in ob.modifiers:
if modifier.type == 'NODES':
ob.update_tag()
break
test_time_start = time.time()
measured_times = []
start_time = time.time()
bpy.context.view_layer.update()
elapsed_time = time.time() - start_time
min_measurements = 5
max_measurements = 100
timeout = 5
result = {'time': elapsed_time}
while True:
# Tag all objects with geometry nodes modifiers to be recalculated.
for ob in bpy.context.view_layer.objects:
for modifier in ob.modifiers:
if modifier.type == 'NODES':
ob.update_tag()
break
start_time = time.time()
bpy.context.view_layer.update()
elapsed_time = time.time() - start_time
measured_times.append(elapsed_time)
if len(measured_times) >= min_measurements and test_time_start + timeout < time.time():
break
if len(measured_times) >= max_measurements:
break
average_time = sum(measured_times) / len(measured_times)
result = {'time': average_time}
return result