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
This commit is contained in:
Falk David 2022-02-10 11:34:12 +01:00
parent 84f30ac3a2
commit e2befa425a
Notes: blender-bot 2023-11-02 19:04:06 +01:00
Referenced by issue #95401, GPencil: Update-on-write using an update cache
Referenced by issue #110038, Regression: Crash when Sculpting on a mesh from a  GP Lineart Modifier
Referenced by pull request #114410, Fix #110038: Crash when sculpting on GP object with modifier
Referenced by commit 05d6818aa2, Fix #110038: Crash when sculpting on GP object with modifier
20 changed files with 961 additions and 32 deletions

View File

@ -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
@ -682,6 +704,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
@ -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

View File

@ -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

View File

@ -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

View File

@ -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);
}
/** \} */

View File

@ -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;
}
}
/** \} */

View File

@ -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.

View File

@ -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.

View File

@ -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;
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}