Similar to 7eee378ecc, this change decreases memory usage and
improves performance when copying curves and meshes without changing
their topology. The same change used for custom data layers is applied
to face and curve offset indices, which aren't stored as a custom data
layer.
The implicit sharing info for the offsets is stored in the mesh and
curve runtime structs, since it doesn't need to be written to files
directly. When changing the offsets pointer directly, the sharing info
must be updated accordingly. To make that easier, a few utility
functions take care of common operations like making an array mutable,
resizing an array, and creating sharing info for allocated data.
This commit also clarifies the intention to not allocate the offsets
at all when there are no curves/faces. That slightly complicates some
of the logic, but there's no reason for the single `0` integer to be
allocated.
Pull Request: https://projects.blender.org/blender/blender/pulls/106907
101 lines
2.7 KiB
C++
101 lines
2.7 KiB
C++
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#include "BLI_implicit_sharing.hh"
|
|
|
|
namespace blender::implicit_sharing {
|
|
|
|
class MEMFreeImplicitSharing : public ImplicitSharingInfo {
|
|
public:
|
|
void *data;
|
|
|
|
MEMFreeImplicitSharing(void *data) : ImplicitSharingInfo(1), data(data)
|
|
{
|
|
BLI_assert(data != nullptr);
|
|
}
|
|
|
|
private:
|
|
void delete_self_with_data() override
|
|
{
|
|
MEM_freeN(data);
|
|
MEM_delete(this);
|
|
}
|
|
};
|
|
|
|
ImplicitSharingInfo *info_for_mem_free(void *data)
|
|
{
|
|
return MEM_new<MEMFreeImplicitSharing>(__func__, data);
|
|
}
|
|
|
|
namespace detail {
|
|
|
|
void *make_trivial_data_mutable_impl(void *old_data,
|
|
const int64_t size,
|
|
const int64_t alignment,
|
|
ImplicitSharingInfo **sharing_info)
|
|
{
|
|
if (!old_data) {
|
|
BLI_assert(size == 0);
|
|
return nullptr;
|
|
}
|
|
|
|
BLI_assert(*sharing_info != nullptr);
|
|
if ((*sharing_info)->is_shared()) {
|
|
void *new_data = MEM_mallocN_aligned(size, alignment, __func__);
|
|
memcpy(new_data, old_data, size);
|
|
(*sharing_info)->remove_user_and_delete_if_last();
|
|
*sharing_info = info_for_mem_free(new_data);
|
|
return new_data;
|
|
}
|
|
|
|
return old_data;
|
|
}
|
|
|
|
void *resize_trivial_array_impl(void *old_data,
|
|
const int64_t old_size,
|
|
const int64_t new_size,
|
|
const int64_t alignment,
|
|
ImplicitSharingInfo **sharing_info)
|
|
{
|
|
if (new_size == 0) {
|
|
if (*sharing_info) {
|
|
(*sharing_info)->remove_user_and_delete_if_last();
|
|
*sharing_info = nullptr;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
if (!old_data) {
|
|
BLI_assert(old_size == 0);
|
|
BLI_assert(*sharing_info == nullptr);
|
|
void *new_data = MEM_mallocN_aligned(new_size, alignment, __func__);
|
|
*sharing_info = info_for_mem_free(new_data);
|
|
return new_data;
|
|
}
|
|
|
|
BLI_assert(old_size != 0);
|
|
if ((*sharing_info)->is_mutable()) {
|
|
if (auto *info = dynamic_cast<MEMFreeImplicitSharing *>(*sharing_info)) {
|
|
/* If the array was allocated with the MEM allocator, we can use realloc directly, which
|
|
* could theoretically give better performance if the data can be reused in place. */
|
|
void *new_data = static_cast<int *>(MEM_reallocN(old_data, new_size));
|
|
info->data = new_data;
|
|
return new_data;
|
|
}
|
|
}
|
|
|
|
void *new_data = MEM_mallocN_aligned(new_size, alignment, __func__);
|
|
memcpy(new_data, old_data, std::min(old_size, new_size));
|
|
(*sharing_info)->remove_user_and_delete_if_last();
|
|
*sharing_info = info_for_mem_free(new_data);
|
|
return new_data;
|
|
}
|
|
|
|
} // namespace detail
|
|
|
|
} // namespace blender::implicit_sharing
|