Changeset View
Changeset View
Standalone View
Standalone View
source/blender/editors/sculpt_paint/paint_image_undo.c
| Show All 9 Lines | |||||
| * GNU General Public License for more details. | * GNU General Public License for more details. | ||||
| * | * | ||||
| * along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
| */ | */ | ||||
| /** \file | /** \file | ||||
| * \ingroup edsculpt | * \ingroup edsculpt | ||||
| * | |||||
| * Overview | |||||
| * ======== | |||||
| * | |||||
| * - Each undo step is a #ImageUndoStep | |||||
| * - Each #ImageUndoStep stores a list of #UndoImageHandle | |||||
| * - Each #UndoImageHandle stores a list of #UndoImageBuf | |||||
| * (this is the undo systems equivalent of an #ImBuf). | |||||
| * - Each #UndoImageBuf stores an array of #UndoImageTile | |||||
| * The tiles are shared between #UndoImageBuf's to avoid duplication. | |||||
| * | |||||
| * When the undo system manages an image, there will always be a full copy (as a #UndoImageBuf) | |||||
| * each new undo step only stores modified tiles. | |||||
| */ | */ | ||||
| #include "CLG_log.h" | |||||
| #include "MEM_guardedalloc.h" | #include "MEM_guardedalloc.h" | ||||
| #include "BLI_math.h" | #include "BLI_math.h" | ||||
| #include "BLI_blenlib.h" | #include "BLI_blenlib.h" | ||||
| #include "BLI_utildefines.h" | #include "BLI_utildefines.h" | ||||
| #include "BLI_threads.h" | #include "BLI_threads.h" | ||||
| #include "DNA_image_types.h" | #include "DNA_image_types.h" | ||||
| Show All 18 Lines | |||||
| #include "ED_object.h" | #include "ED_object.h" | ||||
| #include "GPU_draw.h" | #include "GPU_draw.h" | ||||
| #include "WM_api.h" | #include "WM_api.h" | ||||
| #include "paint_intern.h" | #include "paint_intern.h" | ||||
| static CLG_LogRef LOG = {"ed.image.undo"}; | |||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Undo Conversion | /** \name Thread Locking | ||||
| * \{ */ | * \{ */ | ||||
| typedef struct UndoImageTile { | |||||
| struct UndoImageTile *next, *prev; | |||||
| char ibufname[IMB_FILENAME_SIZE]; | |||||
| union { | |||||
| float *fp; | |||||
| unsigned int *uint; | |||||
| void *pt; | |||||
| } rect; | |||||
| unsigned short *mask; | |||||
| int x, y; | |||||
| /* TODO(campbell): avoid storing the ID per tile, | |||||
| * adds unnecessary overhead restoring undo steps when most tiles share the same image. */ | |||||
| UndoRefID_Image image_ref; | |||||
| short source; | |||||
| bool use_float; | |||||
| char gen_type; | |||||
| bool valid; | |||||
| size_t undo_size; | |||||
| } UndoImageTile; | |||||
| /* this is a static resource for non-globality, | /* this is a static resource for non-globality, | ||||
| * Maybe it should be exposed as part of the | * Maybe it should be exposed as part of the | ||||
| * paint operation, but for now just give a public interface */ | * paint operation, but for now just give a public interface */ | ||||
| static SpinLock undolock; | static SpinLock paint_tiles_lock; | ||||
| void image_undo_init_locks(void) | void image_undo_init_locks(void) | ||||
| { | { | ||||
| BLI_spin_init(&undolock); | BLI_spin_init(&paint_tiles_lock); | ||||
| } | } | ||||
| void image_undo_end_locks(void) | void image_undo_end_locks(void) | ||||
| { | { | ||||
| BLI_spin_end(&undolock); | BLI_spin_end(&paint_tiles_lock); | ||||
| } | } | ||||
| /* UNDO */ | /** \} */ | ||||
| typedef enum { | |||||
| COPY = 0, | |||||
| RESTORE = 1, | |||||
| RESTORE_COPY = 2, | |||||
| } CopyMode; | |||||
| static void undo_copy_tile(UndoImageTile *tile, ImBuf *tmpibuf, ImBuf *ibuf, CopyMode mode) | /* -------------------------------------------------------------------- */ | ||||
| { | /** \name Paint Tiles | ||||
| if (mode == COPY) { | * | ||||
| /* copy or swap contents of tile->rect and region in ibuf->rect */ | * Created on demand while painting, | ||||
| IMB_rectcpy(tmpibuf, | * use to access the previous state for some paint operations. | ||||
| ibuf, | * | ||||
| 0, | * These buffers are also used for undo when available. | ||||
| 0, | * | ||||
| tile->x * IMAPAINT_TILE_SIZE, | * \{ */ | ||||
| tile->y * IMAPAINT_TILE_SIZE, | |||||
| IMAPAINT_TILE_SIZE, | |||||
| IMAPAINT_TILE_SIZE); | |||||
| if (ibuf->rect_float) { | static ImBuf *imbuf_alloc_temp_tile(void) | ||||
| SWAP(float *, tmpibuf->rect_float, tile->rect.fp); | { | ||||
| } | return IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, IB_rectfloat | IB_rect); | ||||
| else { | |||||
| SWAP(unsigned int *, tmpibuf->rect, tile->rect.uint); | |||||
| } | |||||
| } | |||||
| else { | |||||
| if (mode == RESTORE_COPY) { | |||||
| IMB_rectcpy(tmpibuf, | |||||
| ibuf, | |||||
| 0, | |||||
| 0, | |||||
| tile->x * IMAPAINT_TILE_SIZE, | |||||
| tile->y * IMAPAINT_TILE_SIZE, | |||||
| IMAPAINT_TILE_SIZE, | |||||
| IMAPAINT_TILE_SIZE); | |||||
| } | |||||
| /* swap to the tmpbuf for easy copying */ | |||||
| if (ibuf->rect_float) { | |||||
| SWAP(float *, tmpibuf->rect_float, tile->rect.fp); | |||||
| } | |||||
| else { | |||||
| SWAP(unsigned int *, tmpibuf->rect, tile->rect.uint); | |||||
| } | } | ||||
| IMB_rectcpy(ibuf, | typedef struct PaintTile { | ||||
| tmpibuf, | struct PaintTile *next, *prev; | ||||
| tile->x * IMAPAINT_TILE_SIZE, | Image *image; | ||||
| tile->y * IMAPAINT_TILE_SIZE, | ImBuf *ibuf; | ||||
| 0, | union { | ||||
| 0, | float *fp; | ||||
| IMAPAINT_TILE_SIZE, | uint *uint; | ||||
| IMAPAINT_TILE_SIZE); | void *pt; | ||||
| } rect; | |||||
| ushort *mask; | |||||
| bool valid; | |||||
| bool use_float; | |||||
| int x, y; | |||||
| } PaintTile; | |||||
| if (mode == RESTORE) { | static void ptile_free(PaintTile *ptile) | ||||
| if (ibuf->rect_float) { | { | ||||
| SWAP(float *, tmpibuf->rect_float, tile->rect.fp); | if (ptile->rect.pt) { | ||||
| MEM_freeN(ptile->rect.pt); | |||||
| } | } | ||||
| else { | MEM_freeN(ptile); | ||||
| SWAP(unsigned int *, tmpibuf->rect, tile->rect.uint); | |||||
| } | } | ||||
| static void ptile_free_list(ListBase *paint_tiles) | |||||
| { | |||||
| for (PaintTile *ptile = paint_tiles->first, *ptile_next; ptile; ptile = ptile_next) { | |||||
| ptile_next = ptile->next; | |||||
| ptile_free(ptile); | |||||
| } | } | ||||
| BLI_listbase_clear(paint_tiles); | |||||
| } | |||||
| static void ptile_invalidate_list(ListBase *paint_tiles) | |||||
| { | |||||
| for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) { | |||||
| ptile->valid = false; | |||||
| } | } | ||||
| } | } | ||||
| void *image_undo_find_tile(ListBase *undo_tiles, | void *image_undo_find_tile(ListBase *paint_tiles, | ||||
| Image *ima, | Image *image, | ||||
| ImBuf *ibuf, | ImBuf *ibuf, | ||||
| int x_tile, | int x_tile, | ||||
| int y_tile, | int y_tile, | ||||
| unsigned short **mask, | ushort **r_mask, | ||||
| bool validate) | bool validate) | ||||
| { | { | ||||
| UndoImageTile *tile; | for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) { | ||||
| const bool use_float = (ibuf->rect_float != NULL); | if (ptile->x == x_tile && ptile->y == y_tile) { | ||||
| if (ptile->image == image && ptile->ibuf == ibuf) { | |||||
| for (tile = undo_tiles->first; tile; tile = tile->next) { | if (r_mask) { | ||||
| if (tile->x == x_tile && tile->y == y_tile && ima->gen_type == tile->gen_type && | /* allocate mask if requested. */ | ||||
| ima->source == tile->source) { | if (!ptile->mask) { | ||||
| if (tile->use_float == use_float) { | ptile->mask = MEM_callocN(sizeof(ushort) * SQUARE(IMAPAINT_TILE_SIZE), | ||||
| if (STREQ(tile->ibufname, ibuf->name)) { | |||||
| if (mask) { | |||||
| /* allocate mask if requested */ | |||||
| if (!tile->mask) { | |||||
| tile->mask = MEM_callocN(sizeof(unsigned short) * IMAPAINT_TILE_SIZE * | |||||
| IMAPAINT_TILE_SIZE, | |||||
| "UndoImageTile.mask"); | "UndoImageTile.mask"); | ||||
| } | } | ||||
| *r_mask = ptile->mask; | |||||
| *mask = tile->mask; | |||||
| } | } | ||||
| if (validate) { | if (validate) { | ||||
| tile->valid = true; | ptile->valid = true; | ||||
| } | } | ||||
| return tile->rect.pt; | return ptile->rect.pt; | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return NULL; | |||||
| } | } | ||||
| return NULL; | void image_undo_remove_masks(void) | ||||
| { | |||||
| ListBase *paint_tiles = ED_image_undo_get_tiles(); | |||||
| for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) { | |||||
| MEM_SAFE_FREE(ptile->mask); | |||||
| } | |||||
| } | } | ||||
| void *image_undo_push_tile(ListBase *undo_tiles, | void *image_undo_push_tile(ListBase *paint_tiles, | ||||
| Image *ima, | Image *image, | ||||
| ImBuf *ibuf, | ImBuf *ibuf, | ||||
| ImBuf **tmpibuf, | ImBuf **tmpibuf, | ||||
| int x_tile, | int x_tile, | ||||
| int y_tile, | int y_tile, | ||||
| unsigned short **mask, | ushort **r_mask, | ||||
| bool **valid, | bool **valid, | ||||
| bool proj, | bool proj, | ||||
| bool find_prev) | bool find_prev) | ||||
| { | { | ||||
| UndoImageTile *tile; | const bool has_float = (ibuf->rect_float != NULL); | ||||
| int allocsize; | |||||
| const bool use_float = (ibuf->rect_float != NULL); | |||||
| void *data; | |||||
| /* check if tile is already pushed */ | /* check if tile is already pushed */ | ||||
| /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */ | /* in projective painting we keep accounting of tiles, so if we need one pushed, just push! */ | ||||
| if (find_prev) { | if (find_prev) { | ||||
| data = image_undo_find_tile(undo_tiles, ima, ibuf, x_tile, y_tile, mask, true); | void *data = image_undo_find_tile(paint_tiles, image, ibuf, x_tile, y_tile, r_mask, true); | ||||
| if (data) { | if (data) { | ||||
| return data; | return data; | ||||
| } | } | ||||
| } | } | ||||
| if (*tmpibuf == NULL) { | if (*tmpibuf == NULL) { | ||||
| *tmpibuf = IMB_allocImBuf(IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, IB_rectfloat | IB_rect); | *tmpibuf = imbuf_alloc_temp_tile(); | ||||
| } | } | ||||
| tile = MEM_callocN(sizeof(UndoImageTile), "UndoImageTile"); | PaintTile *ptile = MEM_callocN(sizeof(PaintTile), "PaintTile"); | ||||
| tile->x = x_tile; | |||||
| tile->y = y_tile; | ptile->image = image; | ||||
| ptile->ibuf = ibuf; | |||||
| ptile->x = x_tile; | |||||
| ptile->y = y_tile; | |||||
| /* add mask explicitly here */ | /* add mask explicitly here */ | ||||
| if (mask) { | if (r_mask) { | ||||
| *mask = tile->mask = MEM_callocN( | *r_mask = ptile->mask = MEM_callocN(sizeof(ushort) * SQUARE(IMAPAINT_TILE_SIZE), | ||||
| sizeof(unsigned short) * IMAPAINT_TILE_SIZE * IMAPAINT_TILE_SIZE, "UndoImageTile.mask"); | "PaintTile.mask"); | ||||
| } | } | ||||
| allocsize = IMAPAINT_TILE_SIZE * IMAPAINT_TILE_SIZE * 4; | |||||
| allocsize *= (ibuf->rect_float) ? sizeof(float) : sizeof(char); | ptile->rect.pt = MEM_mapallocN((ibuf->rect_float ? sizeof(float[4]) : sizeof(char[4])) * | ||||
| tile->rect.pt = MEM_mapallocN(allocsize, "UndeImageTile.rect"); | SQUARE(IMAPAINT_TILE_SIZE), | ||||
| "PaintTile.rect"); | |||||
| BLI_strncpy(tile->ibufname, ibuf->name, sizeof(tile->ibufname)); | |||||
| ptile->use_float = has_float; | |||||
| tile->gen_type = ima->gen_type; | ptile->valid = true; | ||||
| tile->source = ima->source; | |||||
| tile->use_float = use_float; | |||||
| tile->valid = true; | |||||
| tile->image_ref.ptr = ima; | |||||
| if (valid) { | if (valid) { | ||||
| *valid = &tile->valid; | *valid = &ptile->valid; | ||||
| } | |||||
| IMB_rectcpy(*tmpibuf, | |||||
| ibuf, | |||||
| 0, | |||||
| 0, | |||||
| x_tile * IMAPAINT_TILE_SIZE, | |||||
| y_tile * IMAPAINT_TILE_SIZE, | |||||
| IMAPAINT_TILE_SIZE, | |||||
| IMAPAINT_TILE_SIZE); | |||||
| if (has_float) { | |||||
| SWAP(float *, ptile->rect.fp, (*tmpibuf)->rect_float); | |||||
| } | |||||
| else { | |||||
| SWAP(uint *, ptile->rect.uint, (*tmpibuf)->rect); | |||||
| } | } | ||||
| undo_copy_tile(tile, *tmpibuf, ibuf, COPY); | |||||
| if (proj) { | if (proj) { | ||||
| BLI_spin_lock(&undolock); | BLI_spin_lock(&paint_tiles_lock); | ||||
| } | } | ||||
| BLI_addtail(undo_tiles, tile); | BLI_addtail(paint_tiles, ptile); | ||||
sergey: Sounds a bit fragile. Do you think we can do some `assert()` to catch possible race conditions? | |||||
Not Done Inline ActionsNot sure why we would assert? This logic is kept unchanged from before, not against further changes but think it's safe to keep as-is and make changes here as a separate patch. campbellbarton: Not sure why we would assert?
----
This logic is kept unchanged from before, not against… | |||||
| if (proj) { | if (proj) { | ||||
| BLI_spin_unlock(&undolock); | BLI_spin_unlock(&paint_tiles_lock); | ||||
| } | } | ||||
| return tile->rect.pt; | return ptile->rect.pt; | ||||
| } | } | ||||
| void image_undo_remove_masks(void) | static void ptile_restore_runtime_list(ListBase *paint_tiles) | ||||
| { | { | ||||
| ListBase *undo_tiles = ED_image_undo_get_tiles(); | ImBuf *tmpibuf = imbuf_alloc_temp_tile(); | ||||
| for (UndoImageTile *tile = undo_tiles->first; tile; tile = tile->next) { | |||||
| MEM_SAFE_FREE(tile->mask); | for (PaintTile *ptile = paint_tiles->first; ptile; ptile = ptile->next) { | ||||
| Image *image = ptile->image; | |||||
| ImBuf *ibuf = BKE_image_acquire_ibuf(image, NULL, NULL); | |||||
| const bool has_float = (ibuf->rect_float != NULL); | |||||
| if (has_float) { | |||||
| SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); | |||||
| } | } | ||||
| else { | |||||
| SWAP(uint *, ptile->rect.uint, tmpibuf->rect); | |||||
| } | } | ||||
| static void image_undo_restore_runtime(ListBase *lb) | IMB_rectcpy(ibuf, tmpibuf, ptile->x, ptile->y, 0, 0, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE); | ||||
| { | |||||
| ImBuf *tmpibuf = IMB_allocImBuf( | |||||
| IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, IB_rectfloat | IB_rect); | |||||
| for (UndoImageTile *tile = lb->first; tile; tile = tile->next) { | |||||
| Image *ima = tile->image_ref.ptr; | |||||
| ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL); | |||||
| undo_copy_tile(tile, tmpibuf, ibuf, RESTORE); | if (has_float) { | ||||
| SWAP(float *, ptile->rect.fp, tmpibuf->rect_float); | |||||
| } | |||||
| else { | |||||
| SWAP(uint *, ptile->rect.uint, tmpibuf->rect); | |||||
| } | |||||
| GPU_free_image(ima); /* force OpenGL reload (maybe partial update will operate better?) */ | GPU_free_image(image); /* force OpenGL reload (maybe partial update will operate better?) */ | ||||
| if (ibuf->rect_float) { | if (ibuf->rect_float) { | ||||
| ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ | ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ | ||||
| } | } | ||||
| if (ibuf->mipmap[0]) { | if (ibuf->mipmap[0]) { | ||||
| ibuf->userflags |= IB_MIPMAP_INVALID; /* force mipmap recreatiom */ | ibuf->userflags |= IB_MIPMAP_INVALID; /* force mip-map recreation. */ | ||||
| } | } | ||||
| ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; | ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; | ||||
| BKE_image_release_ibuf(ima, ibuf, NULL); | BKE_image_release_ibuf(image, ibuf, NULL); | ||||
| } | } | ||||
| IMB_freeImBuf(tmpibuf); | IMB_freeImBuf(tmpibuf); | ||||
| } | } | ||||
| static void image_undo_restore_list(ListBase *lb) | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name Image Undo Tile | |||||
| * \{ */ | |||||
| static uint index_from_xy(uint tile_x, uint tile_y, const uint tiles_dims[2]) | |||||
| { | { | ||||
| ImBuf *tmpibuf = IMB_allocImBuf( | BLI_assert(tile_x < tiles_dims[0] && tile_y < tiles_dims[1]); | ||||
| IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE, 32, IB_rectfloat | IB_rect); | return (tile_y * tiles_dims[0]) + tile_x; | ||||
| } | |||||
| for (UndoImageTile *tile = lb->first; tile; tile = tile->next) { | typedef struct UndoImageTile { | ||||
| Image *ima = tile->image_ref.ptr; | union { | ||||
| ImBuf *ibuf = BKE_image_acquire_ibuf(ima, NULL, NULL); | float *fp; | ||||
| uint *uint; | |||||
| void *pt; | |||||
| } rect; | |||||
| int users; | |||||
| } UndoImageTile; | |||||
| if (ima && ibuf && !STREQ(tile->ibufname, ibuf->name)) { | static UndoImageTile *utile_alloc(bool has_float) | ||||
| /* current ImBuf filename was changed, probably current frame | { | ||||
| * was changed when painting on image sequence, rather than storing | UndoImageTile *utile = MEM_callocN(sizeof(*utile), "ImageUndoTile"); | ||||
| * full image user (which isn't so obvious, btw) try to find ImBuf with | if (has_float) { | ||||
| * matched file name in list of already loaded images */ | utile->rect.fp = MEM_mallocN(sizeof(float[4]) * SQUARE(IMAPAINT_TILE_SIZE), __func__); | ||||
| } | |||||
| else { | |||||
| utile->rect.uint = MEM_mallocN(sizeof(uint) * SQUARE(IMAPAINT_TILE_SIZE), __func__); | |||||
| } | |||||
| return utile; | |||||
| } | |||||
| BKE_image_release_ibuf(ima, ibuf, NULL); | static void utile_init_from_imbuf( | ||||
| UndoImageTile *utile, const uint x, const uint y, const ImBuf *ibuf, ImBuf *tmpibuf) | |||||
| { | |||||
| const bool has_float = ibuf->rect_float; | |||||
| ibuf = BKE_image_get_ibuf_with_name(ima, tile->ibufname); | if (has_float) { | ||||
| SWAP(float *, utile->rect.fp, tmpibuf->rect_float); | |||||
| } | |||||
| else { | |||||
| SWAP(uint *, utile->rect.uint, tmpibuf->rect); | |||||
| } | } | ||||
| if (!ima || !ibuf || !(ibuf->rect || ibuf->rect_float)) { | IMB_rectcpy(tmpibuf, ibuf, 0, 0, x, y, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE); | ||||
| BKE_image_release_ibuf(ima, ibuf, NULL); | |||||
| continue; | if (has_float) { | ||||
| SWAP(float *, utile->rect.fp, tmpibuf->rect_float); | |||||
| } | |||||
| else { | |||||
| SWAP(uint *, utile->rect.uint, tmpibuf->rect); | |||||
| } | |||||
| } | } | ||||
| if (ima->gen_type != tile->gen_type || ima->source != tile->source) { | static void utile_restore( | ||||
| BKE_image_release_ibuf(ima, ibuf, NULL); | const UndoImageTile *utile, const uint x, const uint y, ImBuf *ibuf, ImBuf *tmpibuf) | ||||
| continue; | { | ||||
| const bool has_float = ibuf->rect_float; | |||||
| float *prev_rect_float = tmpibuf->rect_float; | |||||
| uint *prev_rect = tmpibuf->rect; | |||||
| if (has_float) { | |||||
| tmpibuf->rect_float = utile->rect.fp; | |||||
| } | |||||
| else { | |||||
| tmpibuf->rect = utile->rect.uint; | |||||
| } | } | ||||
| const bool use_float = (ibuf->rect_float != NULL); | IMB_rectcpy(ibuf, tmpibuf, x, y, 0, 0, IMAPAINT_TILE_SIZE, IMAPAINT_TILE_SIZE); | ||||
| if (use_float != tile->use_float) { | tmpibuf->rect_float = prev_rect_float; | ||||
| BKE_image_release_ibuf(ima, ibuf, NULL); | tmpibuf->rect = prev_rect; | ||||
| continue; | } | ||||
| static void utile_decref(UndoImageTile *utile) | |||||
| { | |||||
| utile->users -= 1; | |||||
| BLI_assert(utile->users >= 0); | |||||
| if (utile->users == 0) { | |||||
| MEM_freeN(utile->rect.pt); | |||||
| MEM_freeN(utile); | |||||
| } | |||||
| } | |||||
| /** \} */ | |||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name Image Undo Buffer | |||||
| * \{ */ | |||||
| typedef struct UndoImageBuf { | |||||
| struct UndoImageBuf *next, *prev; | |||||
| /** | |||||
| * The buffer after the undo step has executed. | |||||
| */ | |||||
| struct UndoImageBuf *post; | |||||
| char ibuf_name[IMB_FILENAME_SIZE]; | |||||
| UndoImageTile **tiles; | |||||
| /** Can calculate these from dims, just for convenience. */ | |||||
| uint tiles_len; | |||||
| uint tiles_dims[2]; | |||||
| uint dims[2]; | |||||
Done Inline Actionsimage_dims? sergey: `image_dims`? | |||||
| /** Store variables from the image. */ | |||||
| struct { | |||||
| short source; | |||||
| bool use_float; | |||||
| char gen_type; | |||||
| } image_state; | |||||
| } UndoImageBuf; | |||||
| static UndoImageBuf *ubuf_from_image_no_tiles(Image *image, const ImBuf *ibuf) | |||||
| { | |||||
| UndoImageBuf *ubuf = MEM_callocN(sizeof(*ubuf), __func__); | |||||
| ubuf->dims[0] = ibuf->x; | |||||
| ubuf->dims[1] = ibuf->y; | |||||
| ubuf->tiles_dims[0] = IMAPAINT_TILE_NUMBER(ubuf->dims[0]); | |||||
| ubuf->tiles_dims[1] = IMAPAINT_TILE_NUMBER(ubuf->dims[1]); | |||||
| ubuf->tiles_len = ubuf->tiles_dims[0] * ubuf->tiles_dims[1]; | |||||
| ubuf->tiles = MEM_callocN(sizeof(*ubuf->tiles) * ubuf->tiles_len, __func__); | |||||
| BLI_strncpy(ubuf->ibuf_name, ibuf->name, sizeof(ubuf->ibuf_name)); | |||||
| ubuf->image_state.gen_type = image->gen_type; | |||||
| ubuf->image_state.source = image->source; | |||||
| ubuf->image_state.use_float = ibuf->rect_float != NULL; | |||||
| return ubuf; | |||||
| } | } | ||||
| undo_copy_tile(tile, tmpibuf, ibuf, RESTORE_COPY); | static void ubuf_from_image_all_tiles(UndoImageBuf *ubuf, const ImBuf *ibuf) | ||||
| { | |||||
| ImBuf *tmpibuf = imbuf_alloc_temp_tile(); | |||||
| const bool has_float = ibuf->rect_float; | |||||
| int i = 0; | |||||
| for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { | |||||
| uint y = y_tile << IMAPAINT_TILE_BITS; | |||||
| for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { | |||||
| uint x = x_tile << IMAPAINT_TILE_BITS; | |||||
| BLI_assert(ubuf->tiles[i] == NULL); | |||||
| UndoImageTile *utile = utile_alloc(has_float); | |||||
| utile->users = 1; | |||||
| utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); | |||||
| ubuf->tiles[i] = utile; | |||||
| i += 1; | |||||
| } | |||||
| } | |||||
| IMB_freeImBuf(tmpibuf); | |||||
| } | |||||
| static void ubuf_free(UndoImageBuf *ubuf) | |||||
| { | |||||
| UndoImageBuf *ubuf_post = ubuf->post; | |||||
| for (uint i = 0; i < ubuf->tiles_len; i++) { | |||||
| UndoImageTile *utile = ubuf->tiles[i]; | |||||
| utile_decref(utile); | |||||
| } | |||||
| MEM_freeN(ubuf->tiles); | |||||
| MEM_freeN(ubuf); | |||||
| if (ubuf_post) { | |||||
| ubuf_free(ubuf_post); | |||||
| } | |||||
| } | |||||
| /** \} */ | |||||
| /* -------------------------------------------------------------------- */ | |||||
| /** \name Image Undo Handle | |||||
| * \{ */ | |||||
| typedef struct UndoImageHandle { | |||||
| struct UndoImageHandle *next, *prev; | |||||
| /** Each undo handle refers to a single image which may have multiple buffers. */ | |||||
| UndoRefID_Image image_ref; | |||||
| /** | |||||
| * We may want to support multiple buffers per image, | |||||
| * may be needed for stereo painting and similar corner cases. | |||||
| * The first set of buffers | |||||
| */ | |||||
| ListBase buffers; | |||||
| BKE_image_mark_dirty(ima, ibuf); | } UndoImageHandle; | ||||
| GPU_free_image(ima); /* force OpenGL reload */ | |||||
| static void uhandle_restore_list(ListBase *undo_handles, bool use_init) | |||||
| { | |||||
| ImBuf *tmpibuf = imbuf_alloc_temp_tile(); | |||||
| for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) { | |||||
| /* Tiles only added to second set of tiles. */ | |||||
| Image *image = uh->image_ref.ptr; | |||||
| ImBuf *ibuf = BKE_image_acquire_ibuf(image, NULL, NULL); | |||||
| if (UNLIKELY(ibuf == NULL)) { | |||||
| CLOG_ERROR(&LOG, "Unable to get buffer for image '%s'", image->id.name + 2); | |||||
| continue; | |||||
| } | |||||
| bool changed = false; | |||||
| for (UndoImageBuf *ubuf_iter = uh->buffers.first; ubuf_iter; ubuf_iter = ubuf_iter->next) { | |||||
| UndoImageBuf *ubuf = use_init ? ubuf_iter : ubuf_iter->post; | |||||
| IMB_rect_size_set(ibuf, ubuf->dims); | |||||
| int i = 0; | |||||
| for (uint y_tile = 0; y_tile < ubuf->tiles_dims[1]; y_tile += 1) { | |||||
| uint y = y_tile << IMAPAINT_TILE_BITS; | |||||
| for (uint x_tile = 0; x_tile < ubuf->tiles_dims[0]; x_tile += 1) { | |||||
| uint x = x_tile << IMAPAINT_TILE_BITS; | |||||
| utile_restore(ubuf->tiles[i], x, y, ibuf, tmpibuf); | |||||
| changed = true; | |||||
| i += 1; | |||||
| } | |||||
| } | |||||
| } | |||||
| if (changed) { | |||||
| BKE_image_mark_dirty(image, ibuf); | |||||
| GPU_free_image(image); /* force OpenGL reload */ | |||||
| if (ibuf->rect_float) { | if (ibuf->rect_float) { | ||||
| ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ | ibuf->userflags |= IB_RECT_INVALID; /* force recreate of char rect */ | ||||
| } | } | ||||
| if (ibuf->mipmap[0]) { | if (ibuf->mipmap[0]) { | ||||
| ibuf->userflags |= IB_MIPMAP_INVALID; /* force mipmap recreatiom */ | ibuf->userflags |= IB_MIPMAP_INVALID; /* force mip-map recreation. */ | ||||
| } | } | ||||
| ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; | ibuf->userflags |= IB_DISPLAY_BUFFER_INVALID; | ||||
| DEG_id_tag_update(&ima->id, 0); | DEG_id_tag_update(&image->id, 0); | ||||
| } | |||||
| BKE_image_release_ibuf(ima, ibuf, NULL); | BKE_image_release_ibuf(image, ibuf, NULL); | ||||
| } | } | ||||
| IMB_freeImBuf(tmpibuf); | IMB_freeImBuf(tmpibuf); | ||||
| } | } | ||||
| static void image_undo_free_tile(UndoImageTile *tile) | static void uhandle_free_list(ListBase *undo_handles) | ||||
| { | { | ||||
| MEM_freeN(tile->rect.pt); | LISTBASE_FOREACH_MUTABLE (UndoImageHandle *, uh, undo_handles) { | ||||
| MEM_freeN(tile); | LISTBASE_FOREACH_MUTABLE (UndoImageBuf *, ubuf, &uh->buffers) { | ||||
| ubuf_free(ubuf); | |||||
| } | |||||
| MEM_freeN(uh); | |||||
| } | } | ||||
| BLI_listbase_clear(undo_handles); | |||||
| } | |||||
| /** \} */ | |||||
| static void image_undo_free_list(ListBase *lb) | /* -------------------------------------------------------------------- */ | ||||
| /** \name Image Undo Internal Utilities | |||||
| * \{ */ | |||||
| /** #UndoImageHandle utilities */ | |||||
| static UndoImageBuf *uhandle_lookup_ubuf(UndoImageHandle *uh, | |||||
| const Image *UNUSED(image), | |||||
| const char *ibuf_name) | |||||
| { | |||||
| for (UndoImageBuf *ubuf = uh->buffers.first; ubuf; ubuf = ubuf->next) { | |||||
| if (STREQ(ubuf->ibuf_name, ibuf_name)) { | |||||
| return ubuf; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| static UndoImageBuf *uhandle_add_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) | |||||
| { | |||||
| BLI_assert(uhandle_lookup_ubuf(uh, image, ibuf->name) == NULL); | |||||
| UndoImageBuf *ubuf = ubuf_from_image_no_tiles(image, ibuf); | |||||
| BLI_addtail(&uh->buffers, ubuf); | |||||
| ubuf->post = NULL; | |||||
| return ubuf; | |||||
| } | |||||
| static UndoImageBuf *uhandle_ensure_ubuf(UndoImageHandle *uh, Image *image, ImBuf *ibuf) | |||||
| { | { | ||||
| for (UndoImageTile *tile = lb->first, *tile_next; tile; tile = tile_next) { | UndoImageBuf *ubuf = uhandle_lookup_ubuf(uh, image, ibuf->name); | ||||
| tile_next = tile->next; | if (ubuf == NULL) { | ||||
| image_undo_free_tile(tile); | ubuf = uhandle_add_ubuf(uh, image, ibuf); | ||||
| } | } | ||||
| return ubuf; | |||||
| } | } | ||||
| static void image_undo_invalidate(void) | static UndoImageHandle *uhandle_lookup_by_name(ListBase *undo_handles, const Image *image) | ||||
| { | { | ||||
| ListBase *lb = ED_image_undo_get_tiles(); | for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) { | ||||
| for (UndoImageTile *tile = lb->first; tile; tile = tile->next) { | if (STREQ(image->id.name + 2, uh->image_ref.name + 2)) { | ||||
| tile->valid = false; | return uh; | ||||
| } | } | ||||
| } | } | ||||
| return NULL; | |||||
| } | |||||
| static UndoImageHandle *uhandle_lookup(ListBase *undo_handles, const Image *image) | |||||
| { | |||||
| for (UndoImageHandle *uh = undo_handles->first; uh; uh = uh->next) { | |||||
| if (image == uh->image_ref.ptr) { | |||||
| return uh; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| static UndoImageHandle *uhandle_add(ListBase *undo_handles, Image *image) | |||||
| { | |||||
| BLI_assert(uhandle_lookup(undo_handles, image) == NULL); | |||||
| UndoImageHandle *uh = MEM_callocN(sizeof(*uh), __func__); | |||||
| uh->image_ref.ptr = image; | |||||
| BLI_addtail(undo_handles, uh); | |||||
| return uh; | |||||
| } | |||||
| static UndoImageHandle *uhandle_ensure(ListBase *undo_handles, Image *image) | |||||
| { | |||||
| UndoImageHandle *uh = uhandle_lookup(undo_handles, image); | |||||
| if (uh == NULL) { | |||||
| uh = uhandle_add(undo_handles, image); | |||||
| } | |||||
| return uh; | |||||
| } | |||||
| /** \} */ | /** \} */ | ||||
| /* -------------------------------------------------------------------- */ | /* -------------------------------------------------------------------- */ | ||||
| /** \name Implements ED Undo System | /** \name Implements ED Undo System | ||||
| * \{ */ | * \{ */ | ||||
| typedef struct ImageUndoStep { | typedef struct ImageUndoStep { | ||||
| UndoStep step; | UndoStep step; | ||||
| ListBase tiles; | |||||
| /** #UndoImageHandle */ | |||||
| ListBase handles; | |||||
| /** | |||||
| * #PaintTile | |||||
| * Run-time only data (active during a paint stroke). | |||||
| */ | |||||
| ListBase paint_tiles; | |||||
| bool is_encode_init; | bool is_encode_init; | ||||
| ePaintMode paint_mode; | ePaintMode paint_mode; | ||||
| } ImageUndoStep; | } ImageUndoStep; | ||||
| /** | |||||
| * Find the previous undo buffer from this one. | |||||
| * \note We could look into undo steps even further back. | |||||
| */ | |||||
| static UndoImageBuf *ubuf_lookup_from_reference(ImageUndoStep *us_prev, | |||||
| const Image *image, | |||||
| const UndoImageBuf *ubuf) | |||||
| { | |||||
| /* Use name lookup because because the pointer is cleared for previous steps. */ | |||||
| UndoImageHandle *uh_prev = uhandle_lookup_by_name(&us_prev->handles, image); | |||||
| if (uh_prev != NULL) { | |||||
| UndoImageBuf *ubuf_reference = uhandle_lookup_ubuf(uh_prev, image, ubuf->ibuf_name); | |||||
| if (ubuf_reference) { | |||||
| ubuf_reference = ubuf_reference->post; | |||||
| if ((ubuf_reference->dims[0] == ubuf->dims[0]) && | |||||
| (ubuf_reference->dims[1] == ubuf->dims[1])) { | |||||
| return ubuf_reference; | |||||
| } | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| static bool image_undosys_poll(bContext *C) | static bool image_undosys_poll(bContext *C) | ||||
| { | { | ||||
| Object *obact = CTX_data_active_object(C); | Object *obact = CTX_data_active_object(C); | ||||
| ScrArea *sa = CTX_wm_area(C); | ScrArea *sa = CTX_wm_area(C); | ||||
| if (sa && (sa->spacetype == SPACE_IMAGE)) { | if (sa && (sa->spacetype == SPACE_IMAGE)) { | ||||
| SpaceImage *sima = (SpaceImage *)sa->spacedata.first; | SpaceImage *sima = (SpaceImage *)sa->spacedata.first; | ||||
| if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { | if ((obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) || (sima->mode == SI_MODE_PAINT)) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| else { | else { | ||||
| if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { | if (obact && (obact->mode & OB_MODE_TEXTURE_PAINT)) { | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| return false; | return false; | ||||
| } | } | ||||
| static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) | static void image_undosys_step_encode_init(struct bContext *UNUSED(C), UndoStep *us_p) | ||||
| { | { | ||||
| ImageUndoStep *us = (ImageUndoStep *)us_p; | ImageUndoStep *us = (ImageUndoStep *)us_p; | ||||
| /* dummy, memory is cleared anyway. */ | /* dummy, memory is cleared anyway. */ | ||||
| us->is_encode_init = true; | us->is_encode_init = true; | ||||
| BLI_listbase_clear(&us->tiles); | BLI_listbase_clear(&us->handles); | ||||
| BLI_listbase_clear(&us->paint_tiles); | |||||
| } | } | ||||
| static bool image_undosys_step_encode(struct bContext *C, | static bool image_undosys_step_encode(struct bContext *C, | ||||
| struct Main *UNUSED(bmain), | struct Main *UNUSED(bmain), | ||||
| UndoStep *us_p) | UndoStep *us_p) | ||||
| { | { | ||||
| /* dummy, encoding is done along the way by adding tiles | /* Encoding is done along the way by adding tiles | ||||
| * to the current 'ImageUndoStep' added by encode_init. */ | * to the current 'ImageUndoStep' added by encode_init. | ||||
| * | |||||
| * This function ensures there are previous and current states of the image in the undo buffer. | |||||
| */ | |||||
| ImageUndoStep *us = (ImageUndoStep *)us_p; | ImageUndoStep *us = (ImageUndoStep *)us_p; | ||||
| BLI_assert(us->step.data_size == 0); | BLI_assert(us->step.data_size == 0); | ||||
| int allocsize = IMAPAINT_TILE_SIZE * IMAPAINT_TILE_SIZE * 4; | |||||
| if (us->is_encode_init) { | if (us->is_encode_init) { | ||||
| /* first dispose of invalid tiles (may happen due to drag dot for instance) */ | |||||
| for (UndoImageTile *tile = us->tiles.first; tile;) { | ImBuf *tmpibuf = imbuf_alloc_temp_tile(); | ||||
| if (!tile->valid) { | |||||
| UndoImageTile *tile_next = tile->next; | ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active; | ||||
| BLI_remlink(&us->tiles, tile); | while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { | ||||
| image_undo_free_tile(tile); | us_reference = (ImageUndoStep *)us_reference->step.prev; | ||||
| tile = tile_next; | } | ||||
| /* Initialize undo tiles from ptiles (if they exist). */ | |||||
| for (PaintTile *ptile = us->paint_tiles.first, *ptile_next; ptile; ptile = ptile_next) { | |||||
| if (ptile->valid) { | |||||
| UndoImageHandle *uh = uhandle_ensure(&us->handles, ptile->image); | |||||
| UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, ptile->image, ptile->ibuf); | |||||
| UndoImageTile *utile = MEM_callocN(sizeof(*utile), "UndoImageTile"); | |||||
| utile->users = 1; | |||||
| utile->rect.pt = ptile->rect.pt; | |||||
| ptile->rect.pt = NULL; | |||||
| const uint tile_index = index_from_xy(ptile->x, ptile->y, ubuf_pre->tiles_dims); | |||||
| BLI_assert(ubuf_pre->tiles[tile_index] == NULL); | |||||
| ubuf_pre->tiles[tile_index] = utile; | |||||
| } | |||||
| ptile_next = ptile->next; | |||||
| ptile_free(ptile); | |||||
| } | |||||
| BLI_listbase_clear(&us->paint_tiles); | |||||
| for (UndoImageHandle *uh = us->handles.first; uh; uh = uh->next) { | |||||
| for (UndoImageBuf *ubuf_pre = uh->buffers.first; ubuf_pre; ubuf_pre = ubuf_pre->next) { | |||||
| ImBuf *ibuf = BKE_image_acquire_ibuf(uh->image_ref.ptr, NULL, NULL); | |||||
| const bool has_float = ibuf->rect_float; | |||||
| BLI_assert(ubuf_pre->post == NULL); | |||||
| ubuf_pre->post = ubuf_from_image_no_tiles(uh->image_ref.ptr, ibuf); | |||||
| UndoImageBuf *ubuf_post = ubuf_pre->post; | |||||
| if (ubuf_pre->dims[0] != ubuf_post->dims[0] || ubuf_pre->dims[1] != ubuf_post->dims[1]) { | |||||
| ubuf_from_image_all_tiles(ubuf_post, ibuf); | |||||
| } | |||||
| else { | |||||
| /* Search for the previous buffer. */ | |||||
| UndoImageBuf *ubuf_reference = (us_reference ? | |||||
| ubuf_lookup_from_reference( | |||||
| us_reference, uh->image_ref.ptr, ubuf_post) : | |||||
| NULL); | |||||
| int i = 0; | |||||
| for (uint y_tile = 0; y_tile < ubuf_pre->tiles_dims[1]; y_tile += 1) { | |||||
| uint y = y_tile << IMAPAINT_TILE_BITS; | |||||
| for (uint x_tile = 0; x_tile < ubuf_pre->tiles_dims[0]; x_tile += 1) { | |||||
| uint x = x_tile << IMAPAINT_TILE_BITS; | |||||
| if ((ubuf_reference != NULL) && ((ubuf_pre->tiles[i] == NULL) || | |||||
| /* In this case the paint stroke as has added a tile | |||||
| * which we have a duplicate reference available. */ | |||||
| (ubuf_pre->tiles[i]->users == 1))) { | |||||
| if (ubuf_pre->tiles[i] != NULL) { | |||||
| /* If we have a reference, re-use this single use tile for the post state. */ | |||||
| BLI_assert(ubuf_pre->tiles[i]->users == 1); | |||||
| ubuf_post->tiles[i] = ubuf_pre->tiles[i]; | |||||
| ubuf_pre->tiles[i] = NULL; | |||||
| utile_init_from_imbuf(ubuf_post->tiles[i], x, y, ibuf, tmpibuf); | |||||
| } | |||||
| else { | |||||
| BLI_assert(ubuf_post->tiles[i] == NULL); | |||||
| ubuf_post->tiles[i] = ubuf_reference->tiles[i]; | |||||
| ubuf_post->tiles[i]->users += 1; | |||||
| } | |||||
| BLI_assert(ubuf_pre->tiles[i] == NULL); | |||||
| ubuf_pre->tiles[i] = ubuf_reference->tiles[i]; | |||||
| ubuf_pre->tiles[i]->users += 1; | |||||
| BLI_assert(ubuf_pre->tiles[i] != NULL); | |||||
| BLI_assert(ubuf_post->tiles[i] != NULL); | |||||
| } | |||||
| else { | |||||
| UndoImageTile *utile = utile_alloc(has_float); | |||||
| utile_init_from_imbuf(utile, x, y, ibuf, tmpibuf); | |||||
| if (ubuf_pre->tiles[i] != NULL) { | |||||
| ubuf_post->tiles[i] = utile; | |||||
| utile->users = 1; | |||||
| } | } | ||||
| else { | else { | ||||
| us->step.data_size += allocsize * (tile->use_float ? sizeof(float) : sizeof(char)); | ubuf_pre->tiles[i] = utile; | ||||
| tile = tile->next; | ubuf_post->tiles[i] = utile; | ||||
| utile->users = 2; | |||||
| } | } | ||||
| } | } | ||||
| BLI_assert(ubuf_pre->tiles[i] != NULL); | |||||
| BLI_assert(ubuf_post->tiles[i] != NULL); | |||||
| i += 1; | |||||
| } | |||||
| } | |||||
| } | |||||
| BKE_image_release_ibuf(uh->image_ref.ptr, ibuf, NULL); | |||||
| } | |||||
| } | |||||
| IMB_freeImBuf(tmpibuf); | |||||
| /* Useful to debug tiles are stored correctly. */ | |||||
| if (false) { | |||||
| uhandle_restore_list(&us->handles, false); | |||||
| } | |||||
| } | } | ||||
| else { | else { | ||||
| /* Happens when switching modes. */ | /* Happens when switching modes. */ | ||||
| ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); | ePaintMode paint_mode = BKE_paintmode_get_active_from_context(C); | ||||
| BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); | BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); | ||||
| us->paint_mode = paint_mode; | us->paint_mode = paint_mode; | ||||
| } | } | ||||
| us_p->is_applied = true; | us_p->is_applied = true; | ||||
| return true; | return true; | ||||
| } | } | ||||
| static void image_undosys_step_decode_undo_impl(ImageUndoStep *us) | static void image_undosys_step_decode_undo_impl(ImageUndoStep *us, bool is_final) | ||||
| { | { | ||||
| BLI_assert(us->step.is_applied == true); | BLI_assert(us->step.is_applied == true); | ||||
| image_undo_restore_list(&us->tiles); | uhandle_restore_list(&us->handles, !is_final); | ||||
| us->step.is_applied = false; | us->step.is_applied = false; | ||||
| } | } | ||||
| static void image_undosys_step_decode_redo_impl(ImageUndoStep *us) | static void image_undosys_step_decode_redo_impl(ImageUndoStep *us) | ||||
| { | { | ||||
| BLI_assert(us->step.is_applied == false); | BLI_assert(us->step.is_applied == false); | ||||
| image_undo_restore_list(&us->tiles); | uhandle_restore_list(&us->handles, false); | ||||
| us->step.is_applied = true; | us->step.is_applied = true; | ||||
| } | } | ||||
| static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final) | static void image_undosys_step_decode_undo(ImageUndoStep *us, bool is_final) | ||||
| { | { | ||||
| ImageUndoStep *us_iter = us; | ImageUndoStep *us_iter = us; | ||||
| while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) { | while (us_iter->step.next && (us_iter->step.next->type == us_iter->step.type)) { | ||||
| if (us_iter->step.next->is_applied == false) { | if (us_iter->step.next->is_applied == false) { | ||||
| break; | break; | ||||
| } | } | ||||
| us_iter = (ImageUndoStep *)us_iter->step.next; | us_iter = (ImageUndoStep *)us_iter->step.next; | ||||
| } | } | ||||
| while (us_iter != us || (!is_final && us_iter == us)) { | while (us_iter != us || (!is_final && us_iter == us)) { | ||||
| image_undosys_step_decode_undo_impl(us_iter); | |||||
| image_undosys_step_decode_undo_impl(us_iter, is_final); | |||||
| if (us_iter == us) { | if (us_iter == us) { | ||||
| break; | break; | ||||
| } | } | ||||
| us_iter = (ImageUndoStep *)us_iter->step.prev; | us_iter = (ImageUndoStep *)us_iter->step.prev; | ||||
| } | } | ||||
| } | } | ||||
| static void image_undosys_step_decode_redo(ImageUndoStep *us) | static void image_undosys_step_decode_redo(ImageUndoStep *us) | ||||
| Show All 31 Lines | static void image_undosys_step_decode( | ||||
| /* Refresh texture slots. */ | /* Refresh texture slots. */ | ||||
| ED_editors_init_for_undo(bmain); | ED_editors_init_for_undo(bmain); | ||||
| } | } | ||||
| static void image_undosys_step_free(UndoStep *us_p) | static void image_undosys_step_free(UndoStep *us_p) | ||||
| { | { | ||||
| ImageUndoStep *us = (ImageUndoStep *)us_p; | ImageUndoStep *us = (ImageUndoStep *)us_p; | ||||
| image_undo_free_list(&us->tiles); | uhandle_free_list(&us->handles); | ||||
| /* Typically this list will have been cleared. */ | |||||
| ptile_free_list(&us->paint_tiles); | |||||
| } | } | ||||
| static void image_undosys_foreach_ID_ref(UndoStep *us_p, | static void image_undosys_foreach_ID_ref(UndoStep *us_p, | ||||
| UndoTypeForEachIDRefFn foreach_ID_ref_fn, | UndoTypeForEachIDRefFn foreach_ID_ref_fn, | ||||
| void *user_data) | void *user_data) | ||||
| { | { | ||||
| ImageUndoStep *us = (ImageUndoStep *)us_p; | ImageUndoStep *us = (ImageUndoStep *)us_p; | ||||
| for (UndoImageTile *tile = us->tiles.first; tile; tile = tile->next) { | for (UndoImageHandle *uh = us->handles.first; uh; uh = uh->next) { | ||||
| foreach_ID_ref_fn(user_data, ((UndoRefID *)&tile->image_ref)); | foreach_ID_ref_fn(user_data, ((UndoRefID *)&uh->image_ref)); | ||||
| } | } | ||||
| } | } | ||||
| /* Export for ED_undo_sys. */ | /* Export for ED_undo_sys. */ | ||||
| void ED_image_undosys_type(UndoType *ut) | void ED_image_undosys_type(UndoType *ut) | ||||
| { | { | ||||
| ut->name = "Image"; | ut->name = "Image"; | ||||
| ut->poll = image_undosys_poll; | ut->poll = image_undosys_poll; | ||||
| Show All 23 Lines | ListBase *ED_image_undo_get_tiles(void) | ||||
| ImageUndoStep *us = (ImageUndoStep *)us_p; | ImageUndoStep *us = (ImageUndoStep *)us_p; | ||||
| /* We should always have an undo push started when accessing tiles, | /* We should always have an undo push started when accessing tiles, | ||||
| * not doing this means we won't have paint_mode correctly set. */ | * not doing this means we won't have paint_mode correctly set. */ | ||||
| BLI_assert(us_p == us_prev); | BLI_assert(us_p == us_prev); | ||||
| if (us_p != us_prev) { | if (us_p != us_prev) { | ||||
| /* Fallback value until we can be sure this never happens. */ | /* Fallback value until we can be sure this never happens. */ | ||||
| us->paint_mode = PAINT_MODE_TEXTURE_2D; | us->paint_mode = PAINT_MODE_TEXTURE_2D; | ||||
| } | } | ||||
| return &us->tiles; | return &us->paint_tiles; | ||||
| } | } | ||||
| /* restore painting image to previous state. Used for anchored and drag-dot style brushes*/ | /* restore painting image to previous state. Used for anchored and drag-dot style brushes*/ | ||||
| void ED_image_undo_restore(UndoStep *us) | void ED_image_undo_restore(UndoStep *us) | ||||
| { | { | ||||
| ListBase *lb = &((ImageUndoStep *)us)->tiles; | ListBase *paint_tiles = &((ImageUndoStep *)us)->paint_tiles; | ||||
| image_undo_restore_runtime(lb); | ptile_restore_runtime_list(paint_tiles); | ||||
| image_undo_invalidate(); | ptile_invalidate_list(paint_tiles); | ||||
| } | } | ||||
| void ED_image_undo_push_begin(const char *name, int paint_mode) | static ImageUndoStep *image_undo_push_begin(const char *name, int paint_mode) | ||||
| { | { | ||||
| UndoStack *ustack = ED_undo_stack_get(); | UndoStack *ustack = ED_undo_stack_get(); | ||||
| bContext *C = NULL; /* special case, we never read from this. */ | bContext *C = NULL; /* special case, we never read from this. */ | ||||
| UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE); | UndoStep *us_p = BKE_undosys_step_push_init_with_type(ustack, C, name, BKE_UNDOSYS_TYPE_IMAGE); | ||||
| ImageUndoStep *us = (ImageUndoStep *)us_p; | ImageUndoStep *us = (ImageUndoStep *)us_p; | ||||
| BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); | BLI_assert(ELEM(paint_mode, PAINT_MODE_TEXTURE_2D, PAINT_MODE_TEXTURE_3D)); | ||||
| us->paint_mode = paint_mode; | us->paint_mode = paint_mode; | ||||
| return us; | |||||
| } | |||||
| void ED_image_undo_push_begin(const char *name, int paint_mode) | |||||
| { | |||||
| image_undo_push_begin(name, paint_mode); | |||||
| } | |||||
| void ED_image_undo_push_begin_with_image(const char *name, Image *image, ImBuf *ibuf) | |||||
| { | |||||
| ImageUndoStep *us = image_undo_push_begin(name, PAINT_MODE_TEXTURE_2D); | |||||
| UndoImageHandle *uh = uhandle_ensure(&us->handles, image); | |||||
| UndoImageBuf *ubuf_pre = uhandle_ensure_ubuf(uh, image, ibuf); | |||||
| BLI_assert(ubuf_pre->post == NULL); | |||||
| ImageUndoStep *us_reference = (ImageUndoStep *)ED_undo_stack_get()->step_active; | |||||
| while (us_reference && us_reference->step.type != BKE_UNDOSYS_TYPE_IMAGE) { | |||||
| us_reference = (ImageUndoStep *)us_reference->step.prev; | |||||
| } | |||||
| UndoImageBuf *ubuf_reference = (us_reference ? | |||||
| ubuf_lookup_from_reference(us_reference, image, ubuf_pre) : | |||||
| NULL); | |||||
| if (ubuf_reference) { | |||||
| memcpy(ubuf_pre->tiles, ubuf_reference->tiles, sizeof(*ubuf_pre->tiles) * ubuf_pre->tiles_len); | |||||
| for (uint i = 0; i < ubuf_pre->tiles_len; i++) { | |||||
| UndoImageTile *utile = ubuf_pre->tiles[i]; | |||||
| utile->users += 1; | |||||
| } | |||||
| } | |||||
| else { | |||||
| ubuf_from_image_all_tiles(ubuf_pre, ibuf); | |||||
| } | |||||
| } | } | ||||
| void ED_image_undo_push_end(void) | void ED_image_undo_push_end(void) | ||||
| { | { | ||||
| UndoStack *ustack = ED_undo_stack_get(); | UndoStack *ustack = ED_undo_stack_get(); | ||||
| BKE_undosys_step_push(ustack, NULL, NULL); | BKE_undosys_step_push(ustack, NULL, NULL); | ||||
| WM_file_tag_modified(); | WM_file_tag_modified(); | ||||
| } | } | ||||
| /** \} */ | /** \} */ | ||||
Sounds a bit fragile. Do you think we can do some assert() to catch possible race conditions? Or, maybe, consider lock cheap enough and always acquire it?