Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFalk David <falkdavid@gmx.de>2022-02-10 13:34:12 +0300
committerFalk David <falkdavid@gmx.de>2022-02-10 13:35:56 +0300
commite2befa425a84c9e4ec715442e85624a5d3669a4f (patch)
tree0965c6eb7d30eceeab8300288197f4e77b10a097
parent84f30ac3a2e8b3017ef7dda34643e573a1f476b7 (diff)
GPencil: Update-on-write using update cache
This implements the update cache described in T95401. The cache is currently only used for drawing strokes and sculpting (using the push brush). **Note: Making use of the cache throughout grease pencil will have to be done incrementally in other patches. ** The update cache stores what elements have changed in the original data-block since the last time the eval object was updated. Additionally, the update cache can store multiple updates to the data and minimizes the number of elements that need to be copied. Elements can be tagged using `BKE_gpencil_tag_full_update` and `BKE_gpencil_tag_light_update`. A full update means that the element itself will be copied but also all of the content inside. E.g. when a layer is tagged for a full update, the layer, all the frames inside the layer and all the strokes inside the frames will be copied. A light update means that only the properties of the element are copied without any of the content. E.g. if a layer is tagged with a light update, it will copy the layer name, opacity, transform, etc. When the update cache is in use (e.g. elements have been tagged) then the depsgraph will not trigger a copy-on-write, but an update-on-write. This means that the update cache will be used to determine what elements have changed and then only those elements will be copied over to the eval object. If the update cache is empty or the data block was tagged with a full update, we always fall back to a copy-on-write. Currently, the update cache is only used by the active depsgraph. This is because we need to free the update cache after an update-on-write so it's reset and we need to make sure it is not freed or read by other depsgraphs. Co-authored-by: @yann-lty This patch was contributed by The SPA Studios. Reviewed By: sergey, antoniov, #dependency_graph, pepeland, mendio Maniphest Tasks: T95401 Differential Revision: https://developer.blender.org/D13984
-rw-r--r--source/blender/blenkernel/BKE_gpencil.h34
-rw-r--r--source/blender/blenkernel/BKE_gpencil_update_cache.h152
-rw-r--r--source/blender/blenkernel/CMakeLists.txt2
-rw-r--r--source/blender/blenkernel/intern/gpencil.c292
-rw-r--r--source/blender/blenkernel/intern/gpencil_update_cache.c274
-rw-r--r--source/blender/blenlib/BLI_dlrbTree.h8
-rw-r--r--source/blender/blenlib/BLI_listbase.h6
-rw-r--r--source/blender/blenlib/intern/DLRB_tree.c28
-rw-r--r--source/blender/blenlib/intern/listbase.c15
-rw-r--r--source/blender/blenlib/tests/BLI_listbase_test.cc8
-rw-r--r--source/blender/depsgraph/CMakeLists.txt2
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc11
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.cc7
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h2
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.cc59
-rw-r--r--source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h45
-rw-r--r--source/blender/editors/gpencil/gpencil_paint.c13
-rw-r--r--source/blender/editors/gpencil/gpencil_sculpt_paint.c17
-rw-r--r--source/blender/makesdna/DNA_gpencil_types.h11
-rw-r--r--source/blender/makesrna/intern/rna_gpencil.c7
20 files changed, 961 insertions, 32 deletions
diff --git a/source/blender/blenkernel/BKE_gpencil.h b/source/blender/blenkernel/BKE_gpencil.h
index 885d0c2fd90..fd8996993c0 100644
--- a/source/blender/blenkernel/BKE_gpencil.h
+++ b/source/blender/blenkernel/BKE_gpencil.h
@@ -48,6 +48,7 @@ struct bGPDlayer;
struct bGPDlayer_Mask;
struct bGPDstroke;
struct bGPdata;
+struct GPencilUpdateCache;
#define GPENCIL_SIMPLIFY(scene) (scene->r.simplify_gpencil & SIMPLIFY_GPENCIL_ENABLE)
#define GPENCIL_SIMPLIFY_ONPLAY(playing) \
@@ -175,10 +176,28 @@ struct bGPDframe *BKE_gpencil_frame_duplicate(const struct bGPDframe *gpf_src, b
struct bGPDlayer *BKE_gpencil_layer_duplicate(const struct bGPDlayer *gpl_src,
bool dup_frames,
bool dup_strokes);
+
+/**
+ * Make a copy of a given gpencil data settings.
+ */
+void BKE_gpencil_data_copy_settings(const struct bGPdata *gpd_src, struct bGPdata *gpd_dst);
+
/**
* Make a copy of a given gpencil layer settings.
*/
void BKE_gpencil_layer_copy_settings(const struct bGPDlayer *gpl_src, struct bGPDlayer *gpl_dst);
+
+/**
+ * Make a copy of a given gpencil frame settings.
+ */
+void BKE_gpencil_frame_copy_settings(const struct bGPDframe *gpf_src, struct bGPDframe *gpf_dst);
+
+/**
+ * Make a copy of a given gpencil stroke settings.
+ */
+void BKE_gpencil_stroke_copy_settings(const struct bGPDstroke *gpf_src,
+ struct bGPDstroke *gpf_dst);
+
/**
* Make a copy of strokes between gpencil frames.
* \param gpf_src: Source grease pencil frame
@@ -675,6 +694,9 @@ extern void (*BKE_gpencil_batch_cache_free_cb)(struct bGPdata *gpd);
*/
void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig,
const struct bGPDframe *gpf_eval);
+
+void BKE_gpencil_layer_original_pointers_update(const struct bGPDlayer *gpl_orig,
+ const struct bGPDlayer *gpl_eval);
/**
* Update pointers of eval data to original data to keep references.
* \param ob_orig: Original grease pencil object
@@ -683,6 +705,14 @@ void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig
void BKE_gpencil_update_orig_pointers(const struct Object *ob_orig, const struct Object *ob_eval);
/**
+ * Update pointers of eval data to original data to keep references.
+ * \param gpd_orig: Original grease pencil data
+ * \param gpd_eval: Evaluated grease pencil data
+ */
+void BKE_gpencil_data_update_orig_pointers(const struct bGPdata *gpd_orig,
+ const struct bGPdata *gpd_eval);
+
+/**
* Get parent matrix, including layer parenting.
* \param depsgraph: Depsgraph
* \param obact: Grease pencil object
@@ -711,6 +741,10 @@ int BKE_gpencil_material_find_index_by_name_prefix(struct Object *ob, const char
void BKE_gpencil_blend_read_data(struct BlendDataReader *reader, struct bGPdata *gpd);
+bool BKE_gpencil_can_avoid_full_copy_on_write(const struct Depsgraph *depsgraph, struct bGPdata *gpd);
+
+void BKE_gpencil_update_on_write(struct bGPdata *gpd_orig, struct bGPdata *gpd_eval);
+
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/BKE_gpencil_update_cache.h b/source/blender/blenkernel/BKE_gpencil_update_cache.h
new file mode 100644
index 00000000000..3ac78267922
--- /dev/null
+++ b/source/blender/blenkernel/BKE_gpencil_update_cache.h
@@ -0,0 +1,152 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2022, Blender Foundation
+ * This is a new part of Blender
+ */
+
+#pragma once
+
+/** \file
+ * \ingroup bke
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "BLI_sys_types.h" /* for bool */
+
+struct DLRBT_Tree;
+struct bGPdata;
+struct bGPDlayer;
+struct bGPDframe;
+struct bGPDstroke;
+struct GPencilUpdateCache;
+
+/* GPencilUpdateCache.flag */
+typedef enum eGPUpdateCacheNodeFlag {
+ /* Node is a placeholder (e.g. when only an index is needed). */
+ GP_UPDATE_NODE_NO_COPY = 0,
+ /* Copy only element, not the content. */
+ GP_UPDATE_NODE_LIGHT_COPY = 1,
+ /* Copy the element as well as all of its content. */
+ GP_UPDATE_NODE_FULL_COPY = 2,
+} eGPUpdateCacheNodeFlag;
+
+/**
+ * Cache for what needs to be updated after bGPdata was modified.
+ *
+ * Every node holds information about one element that was changed:
+ * - the index of where that element is in the linked-list
+ * - the pointer to the original element in bGPdata
+ * Additionally, nodes also hold other nodes that are one "level" below them.
+ * E.g. a node that represents a change on a bGPDframe could contain a set of
+ * nodes that represent a change on bGPDstrokes.
+ * These nodes are stored in a red-black tree so that they are sorted by their
+ * index to make sure they can be processed in the correct order.
+ */
+typedef struct GPencilUpdateCache {
+ /* Mapping from index to a GPencilUpdateCache struct. */
+ struct DLRBT_Tree *children;
+ /* eGPUpdateCacheNodeFlag */
+ int flag;
+ /* Index of the element in the linked-list. */
+ int index;
+ /* Pointer to one of bGPdata, bGPDLayer, bGPDFrame, bGPDStroke. */
+ void *data;
+} GPencilUpdateCache;
+
+/* Node structure in the DLRBT_Tree for GPencilUpdateCache mapping. */
+typedef struct GPencilUpdateCacheNode {
+ /* DLRB tree capabilities. */
+ struct GPencilUpdateCacheNode *next, *prev;
+ struct GPencilUpdateCacheNode *left, *right;
+ struct GPencilUpdateCacheNode *parent;
+ char tree_col;
+
+ char _pad[7];
+ /* Content of DLRB tree node. */
+ GPencilUpdateCache *cache;
+} GPencilUpdateCacheNode;
+
+/**
+ * Callback that is called in BKE_gpencil_traverse_update_cache at each level. If the callback
+ * returns true, then the children will not be iterated over and instead continue.
+ * \param cache: The cache at this level.
+ * \param user_data: Pointer to the user_data passed to BKE_gpencil_traverse_update_cache.
+ * \returns true, if iterating over the children of \a cache should be skipped, false if not.
+ */
+typedef bool (*GPencilUpdateCacheIter_Cb)(GPencilUpdateCache *cache, void *user_data);
+
+typedef struct GPencilUpdateCacheTraverseSettings {
+ /* Callbacks for the update cache traversal. Callback with index 0 is for layers, 1 for frames
+ * and 2 for strokes. */
+ GPencilUpdateCacheIter_Cb update_cache_cb[3];
+} GPencilUpdateCacheTraverseSettings;
+
+/**
+ * Allocates a new GPencilUpdateCache and populates it.
+ * \param data: A data pointer to populate the initial cache with.
+ * \param full_copy: If true, will mark this update cache as a full copy
+ * (GP_UPDATE_NODE_FULL_COPY). If false, it will be marked as a struct copy
+ * (GP_UPDATE_NODE_LIGHT_COPY).
+ */
+GPencilUpdateCache *BKE_gpencil_create_update_cache(void *data, bool full_copy);
+
+/**
+ * Traverses an update cache and executes callbacks at each level.
+ * \param cache: The update cache to traverse.
+ * \param ts: The traversal settings. This stores the callbacks that are called at each level.
+ * \param user_data: Custom data passed to each callback.
+ */
+void BKE_gpencil_traverse_update_cache(GPencilUpdateCache *cache,
+ GPencilUpdateCacheTraverseSettings *ts,
+ void *user_data);
+
+/**
+ * Tags an element (bGPdata, bGPDlayer, bGPDframe, or bGPDstroke) and all of its containing data to
+ * be updated in the next update-on-write operation.
+ *
+ * The function assumes that when a parameter is NULL all of the following parameters are NULL too.
+ * E.g. in order to tag a layer (gpl), the parameters would *have* to be (gpd, gpl, NULL, NULL).
+ */
+void BKE_gpencil_tag_full_update(struct bGPdata *gpd,
+ struct bGPDlayer *gpl,
+ struct bGPDframe *gpf,
+ struct bGPDstroke *gps);
+
+/**
+ * Tags an element (bGPdata, bGPDlayer, bGPDframe, or bGPDstroke) to be updated in the next
+ * update-on-write operation. This function will not update any of the containing data, only the
+ * struct itself.
+ *
+ * The function assumes that when a parameter is NULL all of the following parameters are NULL too.
+ * E.g. in order to tag a layer (gpl), the parameters would *have* to be (gpd, gpl, NULL, NULL).
+ */
+void BKE_gpencil_tag_light_update(struct bGPdata *gpd,
+ struct bGPDlayer *gpl,
+ struct bGPDframe *gpf,
+ struct bGPDstroke *gps);
+
+/**
+ * Frees the GPencilUpdateCache on the gpd->runtime. This will not free the data that the cache
+ * node might point to. It assumes that the cache does not own the data.
+ */
+void BKE_gpencil_free_update_cache(struct bGPdata *gpd);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/blenkernel/CMakeLists.txt b/source/blender/blenkernel/CMakeLists.txt
index efc9cd6e99f..1483466061f 100644
--- a/source/blender/blenkernel/CMakeLists.txt
+++ b/source/blender/blenkernel/CMakeLists.txt
@@ -157,6 +157,7 @@ set(SRC
intern/gpencil_curve.c
intern/gpencil_geom.cc
intern/gpencil_modifier.c
+ intern/gpencil_update_cache.c
intern/icons.cc
intern/icons_rasterize.c
intern/idprop.c
@@ -385,6 +386,7 @@ set(SRC
BKE_gpencil_curve.h
BKE_gpencil_geom.h
BKE_gpencil_modifier.h
+ BKE_gpencil_update_cache.h
BKE_icons.h
BKE_idprop.h
BKE_idprop.hh
diff --git a/source/blender/blenkernel/intern/gpencil.c b/source/blender/blenkernel/intern/gpencil.c
index 13338f33bd6..6e39125b252 100644
--- a/source/blender/blenkernel/intern/gpencil.c
+++ b/source/blender/blenkernel/intern/gpencil.c
@@ -55,6 +55,7 @@
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
+#include "BKE_gpencil_update_cache.h"
#include "BKE_icons.h"
#include "BKE_idtype.h"
#include "BKE_image.h"
@@ -158,6 +159,7 @@ static void greasepencil_blend_write(BlendWriter *writer, ID *id, const void *id
gpd->runtime.sbuffer_used = 0;
gpd->runtime.sbuffer_size = 0;
gpd->runtime.tot_cp_points = 0;
+ gpd->runtime.update_cache = NULL;
/* write gpd data block to file */
BLO_write_id_struct(writer, bGPdata, id_address, &gpd->id);
@@ -221,6 +223,7 @@ void BKE_gpencil_blend_read_data(BlendDataReader *reader, bGPdata *gpd)
gpd->runtime.sbuffer_used = 0;
gpd->runtime.sbuffer_size = 0;
gpd->runtime.tot_cp_points = 0;
+ gpd->runtime.update_cache = NULL;
/* Relink palettes (old palettes deprecated, only to convert old files). */
BLO_read_list(reader, &gpd->palettes);
@@ -501,6 +504,8 @@ void BKE_gpencil_free_data(bGPdata *gpd, bool free_all)
BLI_freelistN(&gpd->vertex_group_names);
+ BKE_gpencil_free_update_cache(gpd);
+
/* free all data */
if (free_all) {
/* clear cache */
@@ -985,6 +990,43 @@ bGPDlayer *BKE_gpencil_layer_duplicate(const bGPDlayer *gpl_src,
return gpl_dst;
}
+void BKE_gpencil_data_copy_settings(const bGPdata *gpd_src, bGPdata *gpd_dst)
+{
+ gpd_dst->flag = gpd_src->flag;
+ gpd_dst->curve_edit_resolution = gpd_src->curve_edit_resolution;
+ gpd_dst->curve_edit_threshold = gpd_src->curve_edit_threshold;
+ gpd_dst->curve_edit_corner_angle = gpd_src->curve_edit_corner_angle;
+ gpd_dst->pixfactor = gpd_src->pixfactor;
+ copy_v4_v4(gpd_dst->line_color, gpd_src->line_color);
+
+ gpd_dst->onion_factor = gpd_src->onion_factor;
+ gpd_dst->onion_mode = gpd_src->onion_mode;
+ gpd_dst->onion_flag = gpd_src->onion_flag;
+ gpd_dst->gstep = gpd_src->gstep;
+ gpd_dst->gstep_next = gpd_src->gstep_next;
+
+ copy_v3_v3(gpd_dst->gcolor_prev, gpd_src->gcolor_prev);
+ copy_v3_v3(gpd_dst->gcolor_next, gpd_src->gcolor_next);
+
+ gpd_dst->zdepth_offset = gpd_src->zdepth_offset;
+
+ gpd_dst->totlayer = gpd_src->totlayer;
+ gpd_dst->totframe = gpd_src->totframe;
+ gpd_dst->totstroke = gpd_src->totstroke;
+ gpd_dst->totpoint = gpd_src->totpoint;
+
+ gpd_dst->draw_mode = gpd_src->draw_mode;
+ gpd_dst->onion_keytype = gpd_src->onion_keytype;
+
+ gpd_dst->select_last_index = gpd_src->select_last_index;
+ gpd_dst->vertex_group_active_index = gpd_src->vertex_group_active_index;
+
+ copy_v3_v3(gpd_dst->grid.color, gpd_src->grid.color);
+ copy_v2_v2(gpd_dst->grid.scale, gpd_src->grid.scale);
+ copy_v2_v2(gpd_dst->grid.offset, gpd_src->grid.offset);
+ gpd_dst->grid.lines = gpd_src->grid.lines;
+}
+
void BKE_gpencil_layer_copy_settings(const bGPDlayer *gpl_src, bGPDlayer *gpl_dst)
{
gpl_dst->line_change = gpl_src->line_change;
@@ -1004,6 +1046,33 @@ void BKE_gpencil_layer_copy_settings(const bGPDlayer *gpl_src, bGPDlayer *gpl_ds
copy_m4_m4(gpl_dst->layer_invmat, gpl_src->layer_invmat);
gpl_dst->blend_mode = gpl_src->blend_mode;
gpl_dst->flag = gpl_src->flag;
+ gpl_dst->onion_flag = gpl_src->onion_flag;
+}
+
+void BKE_gpencil_frame_copy_settings(const bGPDframe *gpf_src, bGPDframe *gpf_dst)
+{
+ gpf_dst->flag = gpf_src->flag;
+ gpf_dst->key_type = gpf_src->key_type;
+ gpf_dst->framenum = gpf_src->framenum;
+}
+
+void BKE_gpencil_stroke_copy_settings(const bGPDstroke *gps_src, bGPDstroke *gps_dst)
+{
+ gps_dst->thickness = gps_src->thickness;
+ gps_dst->flag = gps_src->flag;
+ gps_dst->inittime = gps_src->inittime;
+ gps_dst->mat_nr = gps_src->mat_nr;
+ copy_v2_v2_short(gps_dst->caps, gps_src->caps);
+ gps_dst->hardeness = gps_src->hardeness;
+ copy_v2_v2(gps_dst->aspect_ratio, gps_src->aspect_ratio);
+ gps_dst->fill_opacity_fac = gps_dst->fill_opacity_fac;
+ copy_v3_v3(gps_dst->boundbox_min, gps_src->boundbox_min);
+ copy_v3_v3(gps_dst->boundbox_max, gps_src->boundbox_max);
+ gps_dst->uv_rotation = gps_src->uv_rotation;
+ copy_v2_v2(gps_dst->uv_translation, gps_src->uv_translation);
+ gps_dst->uv_scale = gps_src->uv_scale;
+ gps_dst->select_index = gps_src->select_index;
+ copy_v4_v4(gps_dst->vert_color_fill, gps_src->vert_color_fill);
}
bGPdata *BKE_gpencil_data_duplicate(Main *bmain, const bGPdata *gpd_src, bool internal_copy)
@@ -2579,36 +2648,53 @@ void BKE_gpencil_frame_original_pointers_update(const struct bGPDframe *gpf_orig
}
}
-void BKE_gpencil_update_orig_pointers(const Object *ob_orig, const Object *ob_eval)
+/**
+ * Update original pointers in evaluated layer.
+ * \param gpl_orig: Original grease-pencil layer.
+ * \param gpl_eval: Evaluated grease pencil layer.
+ */
+void BKE_gpencil_layer_original_pointers_update(const struct bGPDlayer *gpl_orig,
+ const struct bGPDlayer *gpl_eval)
{
- bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
- bGPdata *gpd_orig = (bGPdata *)ob_orig->data;
+ bGPDframe *gpf_eval = gpl_eval->frames.first;
+ LISTBASE_FOREACH (bGPDframe *, gpf_orig, &gpl_orig->frames) {
+ if (gpf_eval != NULL) {
+ /* Update frame reference pointers. */
+ gpf_eval->runtime.gpf_orig = (bGPDframe *)gpf_orig;
+ BKE_gpencil_frame_original_pointers_update(gpf_orig, gpf_eval);
+ gpf_eval = gpf_eval->next;
+ }
+ }
+}
+void BKE_gpencil_data_update_orig_pointers(const bGPdata *gpd_orig, const bGPdata *gpd_eval)
+{
/* Assign pointers to the original stroke and points to the evaluated data. This must
* be done before applying any modifier because at this moment the structure is equals,
* so we can assume the layer index is the same in both data-blocks.
* This data will be used by operators. */
bGPDlayer *gpl_eval = gpd_eval->layers.first;
- LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_orig->layers) {
+ LISTBASE_FOREACH (bGPDlayer *, gpl_orig, &gpd_orig->layers) {
if (gpl_eval != NULL) {
/* Update layer reference pointers. */
- gpl_eval->runtime.gpl_orig = (bGPDlayer *)gpl;
-
- bGPDframe *gpf_eval = gpl_eval->frames.first;
- LISTBASE_FOREACH (bGPDframe *, gpf_orig, &gpl->frames) {
- if (gpf_eval != NULL) {
- /* Update frame reference pointers. */
- gpf_eval->runtime.gpf_orig = (bGPDframe *)gpf_orig;
- BKE_gpencil_frame_original_pointers_update(gpf_orig, gpf_eval);
- gpf_eval = gpf_eval->next;
- }
- }
+ gpl_eval->runtime.gpl_orig = gpl_orig;
+ BKE_gpencil_layer_original_pointers_update(gpl_orig, gpl_eval);
gpl_eval = gpl_eval->next;
}
}
}
+/**
+ * Update pointers of eval data to original data to keep references.
+ * \param ob_orig: Original grease pencil object
+ * \param ob_eval: Evaluated grease pencil object
+ */
+void BKE_gpencil_update_orig_pointers(const Object *ob_orig, const Object *ob_eval)
+{
+ BKE_gpencil_data_update_orig_pointers((bGPdata *)ob_orig->data, (bGPdata *)ob_eval->data);
+}
+
void BKE_gpencil_layer_transform_matrix_get(const Depsgraph *depsgraph,
Object *obact,
bGPDlayer *gpl,
@@ -2751,4 +2837,180 @@ void BKE_gpencil_frame_selected_hash(bGPdata *gpd, struct GHash *r_list)
}
}
+bool BKE_gpencil_can_avoid_full_copy_on_write(const Depsgraph *depsgraph, bGPdata *gpd)
+{
+ /* For now, we only use the update cache in the active depsgraph. Othwerwise we might access the
+ * cache while another depsgraph frees it. */
+ if (!DEG_is_active(depsgraph)) {
+ return false;
+ }
+
+ GPencilUpdateCache *update_cache = gpd->runtime.update_cache;
+ return update_cache != NULL && update_cache->flag != GP_UPDATE_NODE_FULL_COPY;
+}
+
+typedef struct tGPencilUpdateOnWriteTraverseData {
+ bGPdata *gpd_eval;
+ bGPDlayer *gpl_eval;
+ bGPDframe *gpf_eval;
+ bGPDstroke *gps_eval;
+ int gpl_index;
+ int gpf_index;
+ int gps_index;
+} tGPencilUpdateOnWriteTraverseData;
+
+static bool gpencil_update_on_write_layer_cb(GPencilUpdateCache *gpl_cache, void *user_data)
+{
+ tGPencilUpdateOnWriteTraverseData *td = (tGPencilUpdateOnWriteTraverseData *)user_data;
+ td->gpl_eval = BLI_findlinkfrom((Link *)td->gpl_eval, gpl_cache->index - td->gpl_index);
+ td->gpl_index = gpl_cache->index;
+ bGPDlayer *gpl = (bGPDlayer *)gpl_cache->data;
+
+ if (gpl_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
+ bGPDlayer *gpl_eval_next = td->gpl_eval->next;
+ BLI_assert(gpl != NULL);
+
+ BKE_gpencil_layer_delete(td->gpd_eval, td->gpl_eval);
+
+ td->gpl_eval = BKE_gpencil_layer_duplicate(gpl, true, true);
+ BLI_insertlinkbefore(&td->gpd_eval->layers, gpl_eval_next, td->gpl_eval);
+
+ BKE_gpencil_layer_original_pointers_update(gpl, td->gpl_eval);
+ td->gpl_eval->runtime.gpl_orig = gpl;
+ return true;
+ }
+ else if (gpl_cache->flag == GP_UPDATE_NODE_LIGHT_COPY) {
+ BLI_assert(gpl != NULL);
+ BKE_gpencil_layer_copy_settings(gpl, td->gpl_eval);
+ td->gpl_eval->runtime.gpl_orig = gpl;
+ }
+
+ td->gpf_eval = td->gpl_eval->frames.first;
+ td->gpf_index = 0;
+ return false;
+}
+
+static bool gpencil_update_on_write_frame_cb(GPencilUpdateCache *gpf_cache, void *user_data)
+{
+ tGPencilUpdateOnWriteTraverseData *td = (tGPencilUpdateOnWriteTraverseData *)user_data;
+ td->gpf_eval = BLI_findlinkfrom((Link *)td->gpf_eval, gpf_cache->index - td->gpf_index);
+ td->gpf_index = gpf_cache->index;
+
+ bGPDframe *gpf = (bGPDframe *)gpf_cache->data;
+
+ if (gpf_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
+ /* Do a full copy of the frame. */
+ bGPDframe *gpf_eval_next = td->gpf_eval->next;
+ BLI_assert(gpf != NULL);
+
+ bool update_actframe = (td->gpl_eval->actframe == td->gpf_eval) ? true : false;
+ BKE_gpencil_free_strokes(td->gpf_eval);
+ BLI_freelinkN(&td->gpl_eval->frames, td->gpf_eval);
+
+ td->gpf_eval = BKE_gpencil_frame_duplicate(gpf, true);
+ BLI_insertlinkbefore(&td->gpl_eval->frames, gpf_eval_next, td->gpf_eval);
+
+ BKE_gpencil_frame_original_pointers_update(gpf, td->gpf_eval);
+ td->gpf_eval->runtime.gpf_orig = gpf;
+
+ if (update_actframe) {
+ td->gpl_eval->actframe = td->gpf_eval;
+ }
+
+ return true;
+ }
+ else if (gpf_cache->flag == GP_UPDATE_NODE_LIGHT_COPY) {
+ BLI_assert(gpf != NULL);
+ BKE_gpencil_frame_copy_settings(gpf, td->gpf_eval);
+ td->gpf_eval->runtime.gpf_orig = gpf;
+ }
+
+ td->gps_eval = td->gpf_eval->strokes.first;
+ td->gps_index = 0;
+ return false;
+}
+
+static bool gpencil_update_on_write_stroke_cb(GPencilUpdateCache *gps_cache, void *user_data)
+{
+ tGPencilUpdateOnWriteTraverseData *td = (tGPencilUpdateOnWriteTraverseData *)user_data;
+ td->gps_eval = BLI_findlinkfrom((Link *)td->gps_eval, gps_cache->index - td->gps_index);
+ td->gps_index = gps_cache->index;
+
+ bGPDstroke *gps = (bGPDstroke *)gps_cache->data;
+
+ if (gps_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
+ /* Do a full copy of the stroke. */
+ bGPDstroke *gps_eval_next = td->gps_eval->next;
+ BLI_assert(gps != NULL);
+
+ BLI_remlink(&td->gpf_eval->strokes, td->gps_eval);
+ BKE_gpencil_free_stroke(td->gps_eval);
+
+ td->gps_eval = BKE_gpencil_stroke_duplicate(gps, true, true);
+ BLI_insertlinkbefore(&td->gpf_eval->strokes, gps_eval_next, td->gps_eval);
+
+ td->gps_eval->runtime.gps_orig = gps;
+
+ /* Assign original pt pointers. */
+ for (int i = 0; i < gps->totpoints; i++) {
+ bGPDspoint *pt_orig = &gps->points[i];
+ bGPDspoint *pt_eval = &td->gps_eval->points[i];
+ pt_orig->runtime.pt_orig = NULL;
+ pt_orig->runtime.idx_orig = i;
+ pt_eval->runtime.pt_orig = pt_orig;
+ pt_eval->runtime.idx_orig = i;
+ }
+ }
+ else if (gps_cache->flag == GP_UPDATE_NODE_LIGHT_COPY) {
+ BLI_assert(gps != NULL);
+ BKE_gpencil_stroke_copy_settings(gps, td->gps_eval);
+ td->gps_eval->runtime.gps_orig = gps;
+ }
+
+ return false;
+}
+
+/**
+ * Update the geometry of the evaluated bGPdata.
+ * This function will:
+ * 1) Copy the original data over to the evaluated object.
+ * 2) Update the original pointers in the runtime structs.
+ */
+void BKE_gpencil_update_on_write(bGPdata *gpd_orig, bGPdata *gpd_eval)
+{
+ GPencilUpdateCache *update_cache = gpd_orig->runtime.update_cache;
+
+ /* We assume that a full copy is not needed and the update cache is populated. */
+ if (update_cache == NULL || update_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
+ return;
+ }
+
+ if (update_cache->flag == GP_UPDATE_NODE_LIGHT_COPY) {
+ BKE_gpencil_data_copy_settings(gpd_orig, gpd_eval);
+ }
+
+ GPencilUpdateCacheTraverseSettings ts = {{
+ gpencil_update_on_write_layer_cb,
+ gpencil_update_on_write_frame_cb,
+ gpencil_update_on_write_stroke_cb,
+ }};
+
+ tGPencilUpdateOnWriteTraverseData data = {
+ .gpd_eval = gpd_eval,
+ .gpl_eval = gpd_eval->layers.first,
+ .gpf_eval = NULL,
+ .gps_eval = NULL,
+ .gpl_index = 0,
+ .gpf_index = 0,
+ .gps_index = 0,
+ };
+
+ BKE_gpencil_traverse_update_cache(update_cache, &ts, &data);
+
+ gpd_eval->flag |= GP_DATA_CACHE_IS_DIRTY;
+
+ /* TODO: This might cause issues when we have multiple depsgraphs? */
+ BKE_gpencil_free_update_cache(gpd_orig);
+}
+
/** \} */
diff --git a/source/blender/blenkernel/intern/gpencil_update_cache.c b/source/blender/blenkernel/intern/gpencil_update_cache.c
new file mode 100644
index 00000000000..323c3a9f2a2
--- /dev/null
+++ b/source/blender/blenkernel/intern/gpencil_update_cache.c
@@ -0,0 +1,274 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2022, Blender Foundation
+ * This is a new part of Blender
+ */
+
+/** \file
+ * \ingroup bke
+ */
+
+#include <stdio.h>
+
+#include "BKE_gpencil_update_cache.h"
+
+#include "BLI_dlrbTree.h"
+#include "BLI_listbase.h"
+
+#include "BKE_gpencil.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_userdef_types.h"
+
+#include "MEM_guardedalloc.h"
+
+static GPencilUpdateCache *update_cache_alloc(int index, int flag, void *data)
+{
+ GPencilUpdateCache *new_cache = MEM_callocN(sizeof(GPencilUpdateCache), __func__);
+ new_cache->children = BLI_dlrbTree_new();
+ new_cache->flag = flag;
+ new_cache->index = index;
+ new_cache->data = data;
+
+ return new_cache;
+}
+
+static short cache_node_compare(void *node, void *data)
+{
+ int index_a = ((GPencilUpdateCacheNode *)node)->cache->index;
+ int index_b = ((GPencilUpdateCache *)data)->index;
+ if (index_a == index_b) {
+ return 0;
+ }
+ return index_a < index_b ? 1 : -1;
+}
+
+static DLRBT_Node *cache_node_alloc(void *data)
+{
+ GPencilUpdateCacheNode *new_node = MEM_callocN(sizeof(GPencilUpdateCacheNode), __func__);
+ new_node->cache = ((GPencilUpdateCache *)data);
+ return (DLRBT_Node *)new_node;
+}
+
+static void cache_node_free(void *node);
+
+static void update_cache_free(GPencilUpdateCache *cache)
+{
+ if (cache->children != NULL) {
+ BLI_dlrbTree_free(cache->children, cache_node_free);
+ MEM_freeN(cache->children);
+ }
+ MEM_freeN(cache);
+}
+
+static void cache_node_free(void *node)
+{
+ GPencilUpdateCache *cache = ((GPencilUpdateCacheNode *)node)->cache;
+ if (cache != NULL) {
+ update_cache_free(cache);
+ }
+ MEM_freeN(node);
+}
+
+static void cache_node_update(void *node, void *data)
+{
+ GPencilUpdateCache *update_cache = ((GPencilUpdateCacheNode *)node)->cache;
+ GPencilUpdateCache *new_update_cache = (GPencilUpdateCache *)data;
+
+ /* If the new cache is already "covered" by the current cache, just free it and return. */
+ if (new_update_cache->flag <= update_cache->flag) {
+ update_cache_free(new_update_cache);
+ return;
+ }
+
+ update_cache->data = new_update_cache->data;
+ update_cache->flag = new_update_cache->flag;
+
+ /* In case the new cache does a full update, remove its children since they will be all
+ * updated by this cache. */
+ if (new_update_cache->flag == GP_UPDATE_NODE_FULL_COPY && update_cache->children != NULL) {
+ BLI_dlrbTree_free(update_cache->children, cache_node_free);
+ MEM_freeN(update_cache->children);
+ }
+
+ update_cache_free(new_update_cache);
+}
+
+static void update_cache_node_create_ex(GPencilUpdateCache *root_cache,
+ void *data,
+ int gpl_index,
+ int gpf_index,
+ int gps_index,
+ bool full_copy)
+{
+ if (root_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
+ /* Entire data-block has to be recaculated, e.g. nothing else needs to be added to the cache.
+ */
+ return;
+ }
+
+ const int node_flag = full_copy ? GP_UPDATE_NODE_FULL_COPY : GP_UPDATE_NODE_LIGHT_COPY;
+
+ if (gpl_index == -1) {
+ root_cache->data = (bGPdata *)data;
+ root_cache->flag = node_flag;
+ if (full_copy) {
+ /* Entire data-block has to be recaculated, remove all caches of "lower" elements. */
+ BLI_dlrbTree_free(root_cache->children, cache_node_free);
+ }
+ return;
+ }
+
+ const bool is_layer_update_node = (gpf_index == -1);
+ /* If the data pointer in GPencilUpdateCache is NULL, this element is not actually cached
+ * and does not need to be updated, but we do need the index to find elements that are in
+ * levels below. E.g. if a stroke needs to be updated, the frame it is in would not hold a
+ * pointer to it's data. */
+ GPencilUpdateCache *gpl_cache = update_cache_alloc(
+ gpl_index,
+ is_layer_update_node ? node_flag : GP_UPDATE_NODE_NO_COPY,
+ is_layer_update_node ? (bGPDlayer *)data : NULL);
+ GPencilUpdateCacheNode *gpl_node = (GPencilUpdateCacheNode *)BLI_dlrbTree_add(
+ root_cache->children, cache_node_compare, cache_node_alloc, cache_node_update, gpl_cache);
+
+ BLI_dlrbTree_linkedlist_sync(root_cache->children);
+ if (gpl_node->cache->flag == GP_UPDATE_NODE_FULL_COPY || is_layer_update_node) {
+ return;
+ }
+
+ const bool is_frame_update_node = (gps_index == -1);
+ GPencilUpdateCache *gpf_cache = update_cache_alloc(
+ gpf_index,
+ is_frame_update_node ? node_flag : GP_UPDATE_NODE_NO_COPY,
+ is_frame_update_node ? (bGPDframe *)data : NULL);
+ GPencilUpdateCacheNode *gpf_node = (GPencilUpdateCacheNode *)BLI_dlrbTree_add(
+ gpl_node->cache->children,
+ cache_node_compare,
+ cache_node_alloc,
+ cache_node_update,
+ gpf_cache);
+
+ BLI_dlrbTree_linkedlist_sync(gpl_node->cache->children);
+ if (gpf_node->cache->flag == GP_UPDATE_NODE_FULL_COPY || is_frame_update_node) {
+ return;
+ }
+
+ GPencilUpdateCache *gps_cache = update_cache_alloc(gps_index, node_flag, (bGPDstroke *)data);
+ BLI_dlrbTree_add(
+ gpf_node->cache->children, cache_node_compare, cache_node_alloc, cache_node_update, gps_cache);
+
+ BLI_dlrbTree_linkedlist_sync(gpf_node->cache->children);
+}
+
+static void update_cache_node_create(
+ bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps, bool full_copy)
+{
+ if (gpd == NULL) {
+ return;
+ }
+
+ GPencilUpdateCache *root_cache = gpd->runtime.update_cache;
+ if (root_cache == NULL) {
+ gpd->runtime.update_cache = update_cache_alloc(0, GP_UPDATE_NODE_NO_COPY, NULL);
+ root_cache = gpd->runtime.update_cache;
+ }
+
+ if (root_cache->flag == GP_UPDATE_NODE_FULL_COPY) {
+ /* Entire data-block has to be recaculated, e.g. nothing else needs to be added to the cache.
+ */
+ return;
+ }
+
+ const int gpl_index = (gpl != NULL) ? BLI_findindex(&gpd->layers, gpl) : -1;
+ const int gpf_index = (gpl != NULL && gpf != NULL) ? BLI_findindex(&gpl->frames, gpf) : -1;
+ const int gps_index = (gpf != NULL && gps != NULL) ? BLI_findindex(&gpf->strokes, gps) : -1;
+
+ void *data = gps;
+ if (!data) {
+ data = gpf;
+ }
+ if (!data) {
+ data = gpl;
+ }
+ if (!data) {
+ data = gpd;
+ }
+
+ update_cache_node_create_ex(root_cache, data, gpl_index, gpf_index, gps_index, full_copy);
+}
+
+static void gpencil_traverse_update_cache_ex(GPencilUpdateCache *parent_cache,
+ GPencilUpdateCacheTraverseSettings *ts,
+ int depth,
+ void *user_data)
+{
+ if (BLI_listbase_is_empty((ListBase *)parent_cache->children)) {
+ return;
+ }
+
+ LISTBASE_FOREACH (GPencilUpdateCacheNode *, cache_node, parent_cache->children) {
+ GPencilUpdateCache *cache = cache_node->cache;
+
+ GPencilUpdateCacheIter_Cb cb = ts->update_cache_cb[depth];
+ if (cb != NULL) {
+ bool skip = cb(cache, user_data);
+ if (skip) {
+ continue;
+ }
+ }
+
+ gpencil_traverse_update_cache_ex(cache, ts, depth + 1, user_data);
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/** \name Update Cache API
+ *
+ * \{ */
+
+GPencilUpdateCache *BKE_gpencil_create_update_cache(void *data, bool full_copy)
+{
+ return update_cache_alloc(
+ 0, full_copy ? GP_UPDATE_NODE_FULL_COPY : GP_UPDATE_NODE_LIGHT_COPY, data);
+}
+
+void BKE_gpencil_traverse_update_cache(GPencilUpdateCache *cache,
+ GPencilUpdateCacheTraverseSettings *ts,
+ void *user_data)
+{
+ gpencil_traverse_update_cache_ex(cache, ts, 0, user_data);
+}
+
+void BKE_gpencil_tag_full_update(bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps)
+{
+ update_cache_node_create(gpd, gpl, gpf, gps, true);
+}
+
+void BKE_gpencil_tag_light_update(bGPdata *gpd, bGPDlayer *gpl, bGPDframe *gpf, bGPDstroke *gps)
+{
+ update_cache_node_create(gpd, gpl, gpf, gps, false);
+}
+
+void BKE_gpencil_free_update_cache(bGPdata *gpd)
+{
+ GPencilUpdateCache *gpd_cache = gpd->runtime.update_cache;
+ if (gpd_cache) {
+ update_cache_free(gpd_cache);
+ gpd->runtime.update_cache = NULL;
+ }
+}
+
+/** \} */
diff --git a/source/blender/blenlib/BLI_dlrbTree.h b/source/blender/blenlib/BLI_dlrbTree.h
index 3cf849efaef..ad6e350ba2c 100644
--- a/source/blender/blenlib/BLI_dlrbTree.h
+++ b/source/blender/blenlib/BLI_dlrbTree.h
@@ -99,6 +99,12 @@ typedef DLRBT_Node *(*DLRBT_NAlloc_FP)(void *data);
*/
typedef void (*DLRBT_NUpdate_FP)(void *node, void *data);
+/**
+ * Free a node and the wrapped data.
+ * \param node: <DLRBT_Node> the node to free.
+ */
+typedef void (*DLRBT_NFree_FP)(void *node);
+
/* ********************************************** */
/* Public API */
@@ -122,7 +128,7 @@ void BLI_dlrbTree_init(DLRBT_Tree *tree);
/**
* Free the given tree's data but not the tree itself.
*/
-void BLI_dlrbTree_free(DLRBT_Tree *tree);
+void BLI_dlrbTree_free(DLRBT_Tree *tree, DLRBT_NFree_FP free_cb);
/**
* Make sure the tree's Double-Linked list representation is valid.
diff --git a/source/blender/blenlib/BLI_listbase.h b/source/blender/blenlib/BLI_listbase.h
index f73d1f22502..f970563d2a6 100644
--- a/source/blender/blenlib/BLI_listbase.h
+++ b/source/blender/blenlib/BLI_listbase.h
@@ -58,6 +58,12 @@ ListBase BLI_listbase_from_link(struct Link *some_link);
*/
void *BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1);
+
+/**
+ * Returns the nth element after \a link, numbering from 0.
+ */
+void *BLI_findlinkfrom(struct Link *start, int number) ATTR_WARN_UNUSED_RESULT;
+
/**
* Finds the first element of \a listbase which contains the null-terminated
* string \a id at the specified offset, returning NULL if not found.
diff --git a/source/blender/blenlib/intern/DLRB_tree.c b/source/blender/blenlib/intern/DLRB_tree.c
index 66c394f5eb2..9c22afeb893 100644
--- a/source/blender/blenlib/intern/DLRB_tree.c
+++ b/source/blender/blenlib/intern/DLRB_tree.c
@@ -45,7 +45,7 @@ void BLI_dlrbTree_init(DLRBT_Tree *tree)
}
/* Helper for traversing tree and freeing sub-nodes */
-static void recursive_tree_free_nodes(DLRBT_Node *node)
+static void recursive_tree_free_nodes(DLRBT_Node *node, DLRBT_NFree_FP free_cb)
{
/* sanity check */
if (node == NULL) {
@@ -53,14 +53,16 @@ static void recursive_tree_free_nodes(DLRBT_Node *node)
}
/* free child nodes + subtrees */
- recursive_tree_free_nodes(node->left);
- recursive_tree_free_nodes(node->right);
+ recursive_tree_free_nodes(node->left, free_cb);
+ recursive_tree_free_nodes(node->right, free_cb);
/* free self */
- MEM_freeN(node);
+ if (free_cb) {
+ free_cb(node);
+ }
}
-void BLI_dlrbTree_free(DLRBT_Tree *tree)
+void BLI_dlrbTree_free(DLRBT_Tree *tree, DLRBT_NFree_FP free_cb)
{
if (tree == NULL) {
return;
@@ -71,11 +73,19 @@ void BLI_dlrbTree_free(DLRBT_Tree *tree)
*/
if (tree->first) {
/* free list */
- BLI_freelistN((ListBase *)tree);
+ if (free_cb) {
+ LISTBASE_FOREACH_MUTABLE(DLRBT_Node *, node, tree) {
+ free_cb(node);
+ }
+ BLI_listbase_clear((ListBase *)tree);
+ }
+ else {
+ BLI_freelistN((ListBase *)tree);
+ }
}
else {
/* traverse tree, freeing sub-nodes */
- recursive_tree_free_nodes(tree->root);
+ recursive_tree_free_nodes(tree->root, free_cb);
}
/* clear pointers */
@@ -584,8 +594,10 @@ DLRBT_Node *BLI_dlrbTree_add(DLRBT_Tree *tree,
}
default: /* update the duplicate node as appropriate */
{
+ /* Return the updated node after calling the callback. */
+ node = parNode;
if (update_cb) {
- update_cb(parNode, data);
+ update_cb(node, data);
}
break;
}
diff --git a/source/blender/blenlib/intern/listbase.c b/source/blender/blenlib/intern/listbase.c
index 513b08a589d..c21b448e505 100644
--- a/source/blender/blenlib/intern/listbase.c
+++ b/source/blender/blenlib/intern/listbase.c
@@ -537,6 +537,21 @@ void *BLI_rfindlink(const ListBase *listbase, int number)
return link;
}
+void *BLI_findlinkfrom(Link *start, int number)
+{
+ Link *link = NULL;
+
+ if (number >= 0) {
+ link = start;
+ while (link != NULL && number != 0) {
+ number--;
+ link = link->next;
+ }
+ }
+
+ return link;
+}
+
int BLI_findindex(const ListBase *listbase, const void *vlink)
{
Link *link = NULL;
diff --git a/source/blender/blenlib/tests/BLI_listbase_test.cc b/source/blender/blenlib/tests/BLI_listbase_test.cc
index 9e4d7c7dd36..88f597cdf67 100644
--- a/source/blender/blenlib/tests/BLI_listbase_test.cc
+++ b/source/blender/blenlib/tests/BLI_listbase_test.cc
@@ -80,18 +80,26 @@ TEST(listbase, FindLinkOrIndex)
EXPECT_EQ(BLI_rfindlink(&lb, 0), (void *)nullptr);
EXPECT_EQ(BLI_rfindlink(&lb, 1), (void *)nullptr);
EXPECT_EQ(BLI_findindex(&lb, link1), -1);
+ EXPECT_EQ(BLI_findlinkfrom((Link *)lb.first, -1), (void *)nullptr);
+ EXPECT_EQ(BLI_findlinkfrom((Link *)lb.first, 0), (void *)nullptr);
+ EXPECT_EQ(BLI_findlinkfrom((Link *)lb.first, 1), (void *)nullptr);
/* One link */
BLI_addtail(&lb, link1);
EXPECT_EQ(BLI_findlink(&lb, 0), link1);
EXPECT_EQ(BLI_rfindlink(&lb, 0), link1);
EXPECT_EQ(BLI_findindex(&lb, link1), 0);
+ EXPECT_EQ(BLI_findlinkfrom((Link *)lb.first, 0), link1);
/* Two links */
BLI_addtail(&lb, link2);
EXPECT_EQ(BLI_findlink(&lb, 1), link2);
EXPECT_EQ(BLI_rfindlink(&lb, 0), link2);
EXPECT_EQ(BLI_findindex(&lb, link2), 1);
+ EXPECT_EQ(BLI_findlinkfrom((Link *)lb.first, 1), link2);
+
+ /* After end of list */
+ EXPECT_EQ(BLI_findlinkfrom((Link *)lb.first, 2), (void *)nullptr);
BLI_freelistN(&lb);
}
diff --git a/source/blender/depsgraph/CMakeLists.txt b/source/blender/depsgraph/CMakeLists.txt
index 41253117096..91c7fac68de 100644
--- a/source/blender/depsgraph/CMakeLists.txt
+++ b/source/blender/depsgraph/CMakeLists.txt
@@ -70,6 +70,7 @@ set(SRC
intern/eval/deg_eval_flush.cc
intern/eval/deg_eval_runtime_backup.cc
intern/eval/deg_eval_runtime_backup_animation.cc
+ intern/eval/deg_eval_runtime_backup_gpencil.cc
intern/eval/deg_eval_runtime_backup_modifier.cc
intern/eval/deg_eval_runtime_backup_movieclip.cc
intern/eval/deg_eval_runtime_backup_object.cc
@@ -131,6 +132,7 @@ set(SRC
intern/eval/deg_eval_flush.h
intern/eval/deg_eval_runtime_backup.h
intern/eval/deg_eval_runtime_backup_animation.h
+ intern/eval/deg_eval_runtime_backup_gpencil.h
intern/eval/deg_eval_runtime_backup_modifier.h
intern/eval/deg_eval_runtime_backup_movieclip.h
intern/eval/deg_eval_runtime_backup_object.h
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
index b64f94568f6..48166567354 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval_copy_on_write.cc
@@ -43,6 +43,7 @@
#include "BKE_curve.h"
#include "BKE_global.h"
#include "BKE_gpencil.h"
+#include "BKE_gpencil_update_cache.h"
#include "BKE_idprop.h"
#include "BKE_layer.h"
#include "BKE_lib_id.h"
@@ -737,9 +738,6 @@ void update_id_after_copy(const Depsgraph *depsgraph,
}
BKE_pose_pchan_index_rebuild(object_cow->pose);
}
- if (object_cow->type == OB_GPENCIL) {
- BKE_gpencil_update_orig_pointers(object_orig, object_cow);
- }
update_particles_after_copy(depsgraph, object_orig, object_cow);
break;
}
@@ -892,6 +890,13 @@ ID *deg_update_copy_on_write_datablock(const Depsgraph *depsgraph, const IDNode
update_edit_mode_pointers(depsgraph, id_orig, id_cow);
return id_cow;
}
+ /* In case we don't need to do a copy-on-write, we can use the update cache of the grease
+ * pencil data to do an update-on-write.*/
+ if (id_type == ID_GD && BKE_gpencil_can_avoid_full_copy_on_write(
+ (const ::Depsgraph *)depsgraph, (bGPdata *)id_orig)) {
+ BKE_gpencil_update_on_write((bGPdata *)id_orig, (bGPdata *)id_cow);
+ return id_cow;
+ }
}
RuntimeBackup backup(depsgraph);
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.cc
index 8bf64af7d5d..85ac8b509c2 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.cc
+++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.cc
@@ -40,7 +40,8 @@ RuntimeBackup::RuntimeBackup(const Depsgraph *depsgraph)
object_backup(depsgraph),
drawdata_ptr(nullptr),
movieclip_backup(depsgraph),
- volume_backup(depsgraph)
+ volume_backup(depsgraph),
+ gpencil_backup(depsgraph)
{
drawdata_backup.first = drawdata_backup.last = nullptr;
}
@@ -75,6 +76,8 @@ void RuntimeBackup::init_from_id(ID *id)
case ID_VO:
volume_backup.init_from_volume(reinterpret_cast<Volume *>(id));
break;
+ case ID_GD:
+ gpencil_backup.init_from_gpencil(reinterpret_cast<bGPdata *>(id));
default:
break;
}
@@ -115,6 +118,8 @@ void RuntimeBackup::restore_to_id(ID *id)
case ID_VO:
volume_backup.restore_to_volume(reinterpret_cast<Volume *>(id));
break;
+ case ID_GD:
+ gpencil_backup.restore_to_gpencil(reinterpret_cast<bGPdata *>(id));
default:
break;
}
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h
index 0629dbe62b4..c4f6bd954c0 100644
--- a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h
+++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup.h
@@ -31,6 +31,7 @@
#include "intern/eval/deg_eval_runtime_backup_scene.h"
#include "intern/eval/deg_eval_runtime_backup_sound.h"
#include "intern/eval/deg_eval_runtime_backup_volume.h"
+#include "intern/eval/deg_eval_runtime_backup_gpencil.h"
namespace blender {
namespace deg {
@@ -71,6 +72,7 @@ class RuntimeBackup {
DrawDataList *drawdata_ptr;
MovieClipBackup movieclip_backup;
VolumeBackup volume_backup;
+ GPencilBackup gpencil_backup;
};
} // namespace deg
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.cc b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.cc
new file mode 100644
index 00000000000..63d1eb9f711
--- /dev/null
+++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.cc
@@ -0,0 +1,59 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2022 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup depsgraph
+ */
+
+#include "intern/eval/deg_eval_runtime_backup_gpencil.h"
+#include "intern/depsgraph.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_update_cache.h"
+
+#include "DNA_gpencil_types.h"
+
+namespace blender::deg {
+
+GPencilBackup::GPencilBackup(const Depsgraph *depsgraph) : depsgraph(depsgraph)
+{
+}
+
+void GPencilBackup::init_from_gpencil(bGPdata *UNUSED(gpd))
+{
+}
+
+void GPencilBackup::restore_to_gpencil(bGPdata *gpd)
+{
+ bGPdata *gpd_orig = reinterpret_cast<bGPdata *>(gpd->id.orig_id);
+
+ /* We check for the active depsgraph here to avoid freeing the cache on the original object
+ * multiple times. This free is only needed for the case where we tagged a full update in the
+ * update cache and did not do an update-on-write. */
+ if (depsgraph->is_active) {
+ BKE_gpencil_free_update_cache(gpd_orig);
+ }
+ /* Doing a copy-on-write copies the update cache pointer. Make sure to reset it
+ * to NULL as we should never use the update cache from eval data. */
+ gpd->runtime.update_cache = NULL;
+ /* Make sure to update the original runtime pointers in the eval data. */
+ BKE_gpencil_data_update_orig_pointers(gpd_orig, gpd);
+}
+
+} // namespace blender::deg
diff --git a/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h
new file mode 100644
index 00000000000..baf0f6a6945
--- /dev/null
+++ b/source/blender/depsgraph/intern/eval/deg_eval_runtime_backup_gpencil.h
@@ -0,0 +1,45 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2022 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup depsgraph
+ */
+
+#pragma once
+
+struct bGPdata;
+
+namespace blender {
+namespace deg {
+
+struct Depsgraph;
+
+/* Backup of volume datablocks runtime data. */
+class GPencilBackup {
+ public:
+ GPencilBackup(const Depsgraph *depsgraph);
+
+ void init_from_gpencil(bGPdata *gpd);
+ void restore_to_gpencil(bGPdata *gpd);
+
+ const Depsgraph* depsgraph;
+};
+
+} // namespace deg
+} // namespace blender
diff --git a/source/blender/editors/gpencil/gpencil_paint.c b/source/blender/editors/gpencil/gpencil_paint.c
index 79dda480a0a..082deab823b 100644
--- a/source/blender/editors/gpencil/gpencil_paint.c
+++ b/source/blender/editors/gpencil/gpencil_paint.c
@@ -56,6 +56,7 @@
#include "BKE_gpencil.h"
#include "BKE_gpencil_curve.h"
#include "BKE_gpencil_geom.h"
+#include "BKE_gpencil_update_cache.h"
#include "BKE_layer.h"
#include "BKE_main.h"
#include "BKE_material.h"
@@ -1341,6 +1342,7 @@ static void gpencil_stroke_newfrombuffer(tGPsdata *p)
}
gpencil_update_cache(p->gpd);
+ BKE_gpencil_tag_full_update(p->gpd, gpl, p->gpf, NULL);
}
/* --- 'Eraser' for 'Paint' Tool ------ */
@@ -2108,6 +2110,9 @@ static void gpencil_session_cleanup(tGPsdata *p)
gpd->runtime.sbuffer_used = 0;
gpd->runtime.sbuffer_size = 0;
gpd->runtime.sbuffer_sflag = 0;
+ /* This update is required for update-on-write because the sbuffer data is not longer overwritten
+ * by a copy-on-write. */
+ ED_gpencil_sbuffer_update_eval(gpd, p->ob_eval);
p->inittime = 0.0;
}
@@ -2136,6 +2141,7 @@ static void gpencil_paint_initstroke(tGPsdata *p,
p->gpl = BKE_gpencil_layer_active_get(p->gpd);
if (p->gpl == NULL) {
p->gpl = BKE_gpencil_layer_addnew(p->gpd, DATA_("GP_Layer"), true, false);
+ BKE_gpencil_tag_full_update(p->gpd, NULL, NULL, NULL);
changed = true;
if (p->custom_color[3]) {
copy_v3_v3(p->gpl->color, p->custom_color);
@@ -2218,10 +2224,15 @@ static void gpencil_paint_initstroke(tGPsdata *p,
}
bool need_tag = p->gpl->actframe == NULL;
+ bGPDframe *actframe = p->gpl->actframe;
+
p->gpf = BKE_gpencil_layer_frame_get(p->gpl, CFRA, add_frame_mode);
/* Only if there wasn't an active frame, need update. */
if (need_tag) {
- DEG_id_tag_update(&p->gpd->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ DEG_id_tag_update(&p->gpd->id, ID_RECALC_GEOMETRY);
+ }
+ if (actframe != p->gpl->actframe) {
+ BKE_gpencil_tag_full_update(p->gpd, p->gpl, NULL, NULL);
}
if (p->gpf == NULL) {
diff --git a/source/blender/editors/gpencil/gpencil_sculpt_paint.c b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
index 0cd4efb10d6..66b7f260f28 100644
--- a/source/blender/editors/gpencil/gpencil_sculpt_paint.c
+++ b/source/blender/editors/gpencil/gpencil_sculpt_paint.c
@@ -56,6 +56,7 @@
#include "BKE_gpencil.h"
#include "BKE_gpencil_geom.h"
#include "BKE_gpencil_modifier.h"
+#include "BKE_gpencil_update_cache.h"
#include "BKE_main.h"
#include "BKE_material.h"
#include "BKE_object_deform.h"
@@ -297,6 +298,8 @@ static void gpencil_update_geometry(bGPdata *gpd)
return;
}
+ bool changed = false;
+
LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd->layers) {
LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
if ((gpl->actframe != gpf) && ((gpf->flag & GP_FRAME_SELECT) == 0)) {
@@ -306,13 +309,17 @@ static void gpencil_update_geometry(bGPdata *gpd)
LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
if (gps->flag & GP_STROKE_TAG) {
BKE_gpencil_stroke_geometry_update(gpd, gps);
+ BKE_gpencil_tag_full_update(gpd, gpl, gpf, gps);
gps->flag &= ~GP_STROKE_TAG;
+ changed = true;
}
}
}
}
- DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
- WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+ if (changed) {
+ DEG_id_tag_update(&gpd->id, ID_RECALC_GEOMETRY);
+ WM_main_add_notifier(NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
+ }
}
/* ************************************************ */
@@ -1351,8 +1358,9 @@ static void gpencil_sculpt_brush_init_stroke(bContext *C, tGP_BrushEditData *gso
*/
if (IS_AUTOKEY_ON(scene) && (gpf->framenum != cfra)) {
BKE_gpencil_frame_addcopy(gpl, cfra);
+ BKE_gpencil_tag_full_update(gpd, gpl, NULL, NULL);
/* Need tag to recalculate evaluated data to avoid crashes. */
- DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY | ID_RECALC_COPY_ON_WRITE);
+ DEG_id_tag_update(&gso->gpd->id, ID_RECALC_GEOMETRY);
WM_event_add_notifier(C, NC_GPENCIL | ND_DATA | NA_EDITED, NULL);
}
}
@@ -1696,6 +1704,9 @@ static bool gpencil_sculpt_brush_do_frame(bContext *C,
/* Delay a full recalculation for other frames. */
gpencil_recalc_geometry_tag(gps_active);
}
+ bGPDlayer *gpl_active = (gpl->runtime.gpl_orig) ? gpl->runtime.gpl_orig : gpl;
+ bGPDframe *gpf_active = (gpf->runtime.gpf_orig) ? gpf->runtime.gpf_orig : gpf;
+ BKE_gpencil_tag_full_update(gpd, gpl_active, gpf_active, gps_active);
}
}
diff --git a/source/blender/makesdna/DNA_gpencil_types.h b/source/blender/makesdna/DNA_gpencil_types.h
index 0f570f8603d..3340782d64a 100644
--- a/source/blender/makesdna/DNA_gpencil_types.h
+++ b/source/blender/makesdna/DNA_gpencil_types.h
@@ -35,6 +35,7 @@ struct AnimData;
struct Curve;
struct Curve;
struct MDeformVert;
+struct GPencilUpdateCache;
#define GP_DEFAULT_PIX_FACTOR 1.0f
#define GP_DEFAULT_GRID_LINES 4
@@ -325,6 +326,8 @@ typedef struct bGPDstroke {
/** Curve used to edit the stroke using Bezier handlers. */
struct bGPDcurve *editcurve;
+ /* NOTE: When adding new members, make sure to add them to BKE_gpencil_stroke_copy_settings as well! */
+
bGPDstroke_Runtime runtime;
void *_pad5;
} bGPDstroke;
@@ -409,6 +412,8 @@ typedef struct bGPDframe {
/** Keyframe type (eBezTriple_KeyframeType). */
short key_type;
+ /* NOTE: When adding new members, make sure to add them to BKE_gpencil_frame_copy_settings as well! */
+
bGPDframe_Runtime runtime;
} bGPDframe;
@@ -532,6 +537,8 @@ typedef struct bGPDlayer {
float layer_mat[4][4], layer_invmat[4][4];
char _pad3[4];
+ /* NOTE: When adding new members, make sure to add them to BKE_gpencil_layer_copy_settings as well! */
+
bGPDlayer_Runtime runtime;
} bGPDlayer;
@@ -633,6 +640,8 @@ typedef struct bGPdata_Runtime {
Brush *sbuffer_brush;
struct GpencilBatchCache *gpencil_cache;
struct LineartCache *lineart_cache;
+
+ struct GPencilUpdateCache *update_cache;
} bGPdata_Runtime;
/* grid configuration */
@@ -726,6 +735,8 @@ typedef struct bGPdata {
bGPgrid grid;
+ /* NOTE: When adding new members, make sure to add them to BKE_gpencil_data_copy_settings as well! */
+
bGPdata_Runtime runtime;
} bGPdata;
diff --git a/source/blender/makesrna/intern/rna_gpencil.c b/source/blender/makesrna/intern/rna_gpencil.c
index 7ef2f757cd8..ff194e2c52a 100644
--- a/source/blender/makesrna/intern/rna_gpencil.c
+++ b/source/blender/makesrna/intern/rna_gpencil.c
@@ -172,6 +172,7 @@ static const EnumPropertyItem rna_enum_gpencil_caps_modes_items[] = {
# include "BKE_gpencil.h"
# include "BKE_gpencil_curve.h"
# include "BKE_gpencil_geom.h"
+# include "BKE_gpencil_update_cache.h"
# include "BKE_icons.h"
# include "DEG_depsgraph.h"
@@ -179,6 +180,12 @@ static const EnumPropertyItem rna_enum_gpencil_caps_modes_items[] = {
static void rna_GPencil_update(Main *UNUSED(bmain), Scene *UNUSED(scene), PointerRNA *ptr)
{
+#if 0
+ /* In case a property on a layer changed, tag it with a light update. */
+ if (ptr->type == &RNA_GPencilLayer) {
+ BKE_gpencil_tag_light_update((bGPdata *)(ptr->owner_id), (bGPDlayer *)(ptr->data), NULL, NULL);
+ }
+#endif
DEG_id_tag_update(ptr->owner_id, ID_RECALC_GEOMETRY);
WM_main_add_notifier(NC_GPENCIL | NA_EDITED, NULL);
}