Grease Pencil: Initialize runtime drawing user counts on read

This adds a function `GreasePencil::count_frame_users_for_drawings()`
that computes the drawing user counts from the layer frames. This is used
to correctly initialize the runtime drawing user counts when reading the ID.

Note that we don't expose any functionality to instance drawings currently.
This change is also in preparation for when this will be possible.

Also adds a function `GreasePencil::validate_drawing_user_counts()` that
compares the actual user counts with the runtime user counts that are
stored. Only runs these checks in debug builds.

Pull Request: https://projects.blender.org/blender/blender/pulls/141458
This commit is contained in:
Falk David
2025-07-14 11:44:57 +02:00
committed by Falk David
parent 13f0d6a859
commit 45ab790e80
4 changed files with 93 additions and 13 deletions

View File

@@ -76,6 +76,12 @@ class DrawingRuntime {
* and remove a drawing if it has zero users.
*/
mutable std::atomic<int> user_count = 1;
/**
* Ensures that the drawing is not deleted and can be used temporarily (e.g. by the transform
* code).
*/
mutable bool fake_user = false;
};
class Drawing : public ::GreasePencilDrawing {

View File

@@ -173,6 +173,21 @@ static void grease_pencil_set_runtime_visibilities(ID &id_dst, GreasePencil &gre
}
}
static void grease_pencil_initialize_drawing_user_counts_after_read(GreasePencil &grease_pencil)
{
using namespace blender;
using namespace blender::bke::greasepencil;
const Array<int> user_counts = grease_pencil.count_frame_users_for_drawings();
BLI_assert(user_counts.size() == grease_pencil.drawings().size());
for (const int drawing_i : grease_pencil.drawings().index_range()) {
GreasePencilDrawingBase *drawing_base = grease_pencil.drawing(drawing_i);
if (drawing_base->type != GP_DRAWING_REFERENCE) {
Drawing &drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base)->wrap();
drawing.runtime->user_count.store(user_counts[drawing_i]);
}
}
}
static void grease_pencil_copy_data(Main * /*bmain*/,
std::optional<Library *> /*owner_library*/,
ID *id_dst,
@@ -300,6 +315,8 @@ static void grease_pencil_blend_read_data(BlendDataReader *reader, ID *id)
read_drawing_array(*grease_pencil, reader);
/* Read layer tree. */
read_layer_tree(*grease_pencil, reader);
/* Initialize drawing user counts */
grease_pencil_initialize_drawing_user_counts_after_read(*grease_pencil);
CustomData_blend_read(
reader, &grease_pencil->layers_data_legacy, grease_pencil->layers().size());
@@ -3029,6 +3046,9 @@ bool GreasePencil::insert_duplicate_frame(blender::bke::greasepencil::Layer &lay
layer.remove_frame(dst_frame_number);
return false;
}
#ifndef NDEBUG
this->validate_drawing_user_counts();
#endif
return true;
}
@@ -3065,6 +3085,11 @@ bool GreasePencil::remove_frames(blender::bke::greasepencil::Layer &layer,
this->remove_drawings_with_no_users();
return true;
}
#ifndef NDEBUG
else {
this->validate_drawing_user_counts();
}
#endif
return false;
}
@@ -3135,6 +3160,10 @@ void GreasePencil::remove_drawings_with_no_users()
using namespace blender;
using namespace blender::bke::greasepencil;
#ifndef NDEBUG
this->validate_drawing_user_counts();
#endif
/* Compress the drawings array by finding unused drawings.
* In every step two indices are found:
* - The next unused drawing from the start
@@ -3156,7 +3185,7 @@ void GreasePencil::remove_drawings_with_no_users()
return false;
}
GreasePencilDrawing *drawing = reinterpret_cast<GreasePencilDrawing *>(drawing_base);
return drawing->wrap().has_users();
return drawing->wrap().has_users() || drawing->runtime->fake_user;
};
/* Index map to remap drawing indices in frame data.
@@ -3239,12 +3268,16 @@ void GreasePencil::remove_drawings_with_no_users()
}
}
}
#ifndef NDEBUG
this->validate_drawing_user_counts();
#endif
}
void GreasePencil::update_drawing_users_for_layer(const blender::bke::greasepencil::Layer &layer)
{
using namespace blender;
for (auto [key, value] : layer.frames().items()) {
for (const auto &[key, value] : layer.frames().items()) {
BLI_assert(this->drawings().index_range().contains(value.drawing_index));
GreasePencilDrawingBase *drawing_base = this->drawing(value.drawing_index);
if (drawing_base->type != GP_DRAWING) {
@@ -3256,6 +3289,10 @@ void GreasePencil::update_drawing_users_for_layer(const blender::bke::greasepenc
drawing.add_user();
}
}
#ifndef NDEBUG
this->validate_drawing_user_counts();
#endif
}
void GreasePencil::move_frames(blender::bke::greasepencil::Layer &layer,
@@ -4260,6 +4297,36 @@ void GreasePencil::print_layer_tree()
this->root_group().print_nodes("Layer Tree:");
}
blender::Array<int> GreasePencil::count_frame_users_for_drawings() const
{
using namespace blender;
using namespace blender::bke::greasepencil;
Array<int> user_counts(this->drawings().size(), 0);
for (const Layer *layer : this->layers()) {
for (const auto &[frame, value] : layer->frames().items()) {
BLI_assert(this->drawings().index_range().contains(value.drawing_index));
user_counts[value.drawing_index]++;
}
}
return user_counts;
}
void GreasePencil::validate_drawing_user_counts()
{
#ifndef NDEBUG
using namespace blender::bke::greasepencil;
blender::Array<int> actual_user_counts = this->count_frame_users_for_drawings();
for (const int drawing_i : this->drawings().index_range()) {
const GreasePencilDrawingBase *drawing_base = this->drawing(drawing_i);
if (drawing_base->type != GP_DRAWING_REFERENCE) {
const Drawing &drawing = reinterpret_cast<const GreasePencilDrawing *>(drawing_base)->wrap();
/* Ignore `fake_user` flag. */
BLI_assert(drawing.user_count() == actual_user_counts[drawing_i]);
}
}
#endif
}
blender::bke::AttributeAccessor GreasePencil::attributes() const
{
return blender::bke::AttributeAccessor(

View File

@@ -53,9 +53,9 @@ static bool is_td2d_int(TransData2D *td2d)
/** \name Grease Pencil Transform helpers
* \{ */
/* Add a user to ensure drawings are not deleted during transform when a frame is overwritten
/* Add a fake user to ensure drawings are not deleted during transform when a frame is overwritten
* temporarily. The drawing_index of any existing frame will also remain valid. */
static void grease_pencil_transdata_add_drawing_users(const GreasePencil &grease_pencil)
static void grease_pencil_transdata_add_fake_drawing_users(const GreasePencil &grease_pencil)
{
using namespace bke::greasepencil;
@@ -66,13 +66,13 @@ static void grease_pencil_transdata_add_drawing_users(const GreasePencil &grease
}
const Drawing &drawing = reinterpret_cast<const GreasePencilDrawing *>(drawing_base)->wrap();
drawing.add_user();
drawing.runtime->fake_user = true;
}
}
/* Remove users from drawings after frame data has been restored. After this drawing data can be
* freed and drawing indices may become invalid. */
static void grease_pencil_transdata_remove_drawing_users(const GreasePencil &grease_pencil)
/* Remove fake users from drawings after frame data has been restored. After this drawing data can
* be freed and drawing indices may become invalid. */
static void grease_pencil_transdata_remove_fake_drawing_users(const GreasePencil &grease_pencil)
{
using namespace bke::greasepencil;
@@ -83,7 +83,7 @@ static void grease_pencil_transdata_remove_drawing_users(const GreasePencil &gre
}
const Drawing &drawing = reinterpret_cast<const GreasePencilDrawing *>(drawing_base)->wrap();
drawing.remove_user();
drawing.runtime->fake_user = false;
}
}
@@ -99,10 +99,10 @@ static bool grease_pencil_layer_initialize_trans_data(const GreasePencil &grease
return false;
}
/* "Freeze" drawing indices by adding a user to each drawing. This ensures the draw_index in
* frame data remains valid and no data is lost if the drawing is temporarily unused during
/* "Freeze" drawing indices by adding a fake user to each drawing. This ensures the drawing_index
* in frame data remains valid and no data is lost if the drawing is temporarily unused during
* transform. */
grease_pencil_transdata_add_drawing_users(grease_pencil);
grease_pencil_transdata_add_fake_drawing_users(grease_pencil);
/* Initialize the transformation data structure, by storing in separate maps frames that will
* remain static during the transformation, and frames that are affected by the
@@ -247,7 +247,7 @@ static bool grease_pencil_layer_apply_trans_data(GreasePencil &grease_pencil,
}
/* All frame data is updated, safe to remove the fake user and remove unused drawings. */
grease_pencil_transdata_remove_drawing_users(grease_pencil);
grease_pencil_transdata_remove_fake_drawing_users(grease_pencil);
grease_pencil.remove_drawings_with_no_users();
/* Clear the frames copy. */

View File

@@ -743,7 +743,14 @@ typedef struct GreasePencil {
void count_memory(blender::MemoryCounter &memory) const;
/**
* Compute the user counts of the drawings by iterating through the keyframes of all the layers
* and counting the number of references to each drawing.
*/
blender::Array<int> count_frame_users_for_drawings() const;
/* For debugging purposes. */
void print_layer_tree();
void validate_drawing_user_counts();
#endif
} GreasePencil;