diff options
Diffstat (limited to 'source')
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); } |