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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeroen Bakker <jeroen@blender.org>2022-05-27 15:46:05 +0300
committerJeroen Bakker <jeroen@blender.org>2022-05-27 15:46:05 +0300
commitae68c53b15912669f432b93bfd6edbe4d043a897 (patch)
tree0e3f327641c3c90b5b6409e3d4c8abd2a1490de9
parentf41c7723c93bc9e784634887da8b682787729538 (diff)
Base implementation to share textures between mutliple image data-blocks.
-rw-r--r--source/blender/blenkernel/BKE_image.h7
-rw-r--r--source/blender/blenkernel/intern/image.cc62
-rw-r--r--source/blender/blenkernel/intern/image_gpu.cc222
-rw-r--r--source/blender/editors/sculpt_paint/paint_image.cc4
-rw-r--r--source/blender/makesdna/DNA_image_types.h2
-rw-r--r--source/blender/makesrna/intern/rna_image.c2
-rw-r--r--source/blender/makesrna/intern/rna_image_api.c12
7 files changed, 176 insertions, 135 deletions
diff --git a/source/blender/blenkernel/BKE_image.h b/source/blender/blenkernel/BKE_image.h
index b6607a92c14..8f617cce7f7 100644
--- a/source/blender/blenkernel/BKE_image.h
+++ b/source/blender/blenkernel/BKE_image.h
@@ -326,11 +326,6 @@ bool BKE_image_scale(struct Image *image, int width, int height);
bool BKE_image_has_alpha(struct Image *image);
/**
- * Check if texture has GPU texture code.
- */
-bool BKE_image_has_opengl_texture(struct Image *ima);
-
-/**
* Get tile index for tiled images.
*/
void BKE_image_get_tile_label(struct Image *ima,
@@ -525,7 +520,7 @@ void BKE_image_update_gputexture_delayed(struct Image *ima,
* temporary disabling/enabling mipmapping on all images for quick texture
* updates with glTexSubImage2D. images that didn't change don't have to be re-uploaded to OpenGL.
*/
-void BKE_image_paint_set_mipmap(struct Main *bmain, bool mipmap);
+void BKE_image_paint_set_mipmap(bool mipmap);
/**
* Delayed free of OpenGL buffers by main thread.
diff --git a/source/blender/blenkernel/intern/image.cc b/source/blender/blenkernel/intern/image.cc
index bc21e706d1e..bf00eb7ab7a 100644
--- a/source/blender/blenkernel/intern/image.cc
+++ b/source/blender/blenkernel/intern/image.cc
@@ -180,12 +180,6 @@ static void image_copy_data(Main *UNUSED(bmain), ID *id_dst, const ID *id_src, c
BLI_duplicatelist(&image_dst->tiles, &image_src->tiles);
- for (int eye = 0; eye < 2; eye++) {
- for (int i = 0; i < TEXTARGET_COUNT; i++) {
- image_dst->gputexture[i][eye] = nullptr;
- }
- }
-
if ((flag & LIB_ID_COPY_NO_PREVIEW) == 0) {
BKE_previewimg_id_copy(&image_dst->id, &image_src->id);
}
@@ -234,6 +228,7 @@ static void image_foreach_cache(ID *id,
key.offset_in_ID = offsetof(Image, cache);
function_callback(id, &key, (void **)&image->cache, 0, user_data);
+#if 0
auto gputexture_offset = [image](int target, int eye) {
constexpr size_t base_offset = offsetof(Image, gputexture);
struct GPUTexture **first = &image->gputexture[0][0];
@@ -241,6 +236,7 @@ static void image_foreach_cache(ID *id,
return base_offset + array_offset;
};
+// TODO(jbakker): need to check how this is actually used not the GPU textures are shared we might need to reinvalidate this.
for (int eye = 0; eye < 2; eye++) {
for (int a = 0; a < TEXTARGET_COUNT; a++) {
GPUTexture *texture = image->gputexture[a][eye];
@@ -251,6 +247,7 @@ static void image_foreach_cache(ID *id,
function_callback(id, &key, (void **)&image->gputexture[a][eye], 0, user_data);
}
}
+#endif
key.offset_in_ID = offsetof(Image, rr);
function_callback(id, &key, (void **)&image->rr, 0, user_data);
@@ -328,11 +325,6 @@ static void image_blend_write(BlendWriter *writer, ID *id, const void *id_addres
BLI_listbase_clear(&ima->anims);
ima->runtime.partial_update_register = nullptr;
ima->runtime.partial_update_user = nullptr;
- for (int i = 0; i < 3; i++) {
- for (int j = 0; j < 2; j++) {
- ima->gputexture[i][j] = nullptr;
- }
- }
ImagePackedFile *imapf;
@@ -773,18 +765,6 @@ bool BKE_image_scale(Image *image, int width, int height)
return (ibuf != nullptr);
}
-bool BKE_image_has_opengl_texture(Image *ima)
-{
- for (int eye = 0; eye < 2; eye++) {
- for (int i = 0; i < TEXTARGET_COUNT; i++) {
- if (ima->gputexture[i][eye] != nullptr) {
- return true;
- }
- }
- }
- return false;
-}
-
static int image_get_tile_number_from_iuser(Image *ima, const ImageUser *iuser)
{
BLI_assert(ima != nullptr && ima->tiles.first);
@@ -2847,20 +2827,6 @@ void BKE_image_init_imageuser(Image *ima, ImageUser *iuser)
static void image_free_tile(Image *ima, ImageTile *tile)
{
- for (int i = 0; i < TEXTARGET_COUNT; i++) {
- /* Only two textures depends on all tiles, so if this is a secondary tile we can keep the other
- * two. */
- if (tile != ima->tiles.first && !(ELEM(i, TEXTARGET_2D_ARRAY, TEXTARGET_TILE_MAPPING))) {
- continue;
- }
-
- for (int eye = 0; eye < 2; eye++) {
- if (ima->gputexture[i][eye] != nullptr) {
- GPU_texture_free(ima->gputexture[i][eye]);
- ima->gputexture[i][eye] = nullptr;
- }
- }
- }
BKE_image_partial_update_mark_full_update(ima);
if (BKE_image_is_multiview(ima)) {
@@ -3196,17 +3162,6 @@ ImageTile *BKE_image_add_tile(struct Image *ima, int tile_number, const char *la
BLI_strncpy(tile->label, label, sizeof(tile->label));
}
- for (int eye = 0; eye < 2; eye++) {
- /* Reallocate GPU tile array. */
- if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != nullptr) {
- GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]);
- ima->gputexture[TEXTARGET_2D_ARRAY][eye] = nullptr;
- }
- if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != nullptr) {
- GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]);
- ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = nullptr;
- }
- }
BKE_image_partial_update_mark_full_update(ima);
return tile;
@@ -3259,17 +3214,6 @@ void BKE_image_reassign_tile(struct Image *ima, ImageTile *tile, int new_tile_nu
IMB_freeImBuf(ibuf);
}
- for (int eye = 0; eye < 2; eye++) {
- /* Reallocate GPU tile array. */
- if (ima->gputexture[TEXTARGET_2D_ARRAY][eye] != nullptr) {
- GPU_texture_free(ima->gputexture[TEXTARGET_2D_ARRAY][eye]);
- ima->gputexture[TEXTARGET_2D_ARRAY][eye] = nullptr;
- }
- if (ima->gputexture[TEXTARGET_TILE_MAPPING][eye] != nullptr) {
- GPU_texture_free(ima->gputexture[TEXTARGET_TILE_MAPPING][eye]);
- ima->gputexture[TEXTARGET_TILE_MAPPING][eye] = nullptr;
- }
- }
BKE_image_partial_update_mark_full_update(ima);
}
diff --git a/source/blender/blenkernel/intern/image_gpu.cc b/source/blender/blenkernel/intern/image_gpu.cc
index bed79a318e8..3e0e0414afa 100644
--- a/source/blender/blenkernel/intern/image_gpu.cc
+++ b/source/blender/blenkernel/intern/image_gpu.cc
@@ -11,7 +11,9 @@
#include "BLI_boxpack_2d.h"
#include "BLI_linklist.h"
#include "BLI_listbase.h"
+#include "BLI_map.hh"
#include "BLI_threads.h"
+#include "BLI_utility_mixins.hh"
#include "DNA_image_types.h"
#include "DNA_userdef_types.h"
@@ -33,13 +35,133 @@
using namespace blender::bke::image::partial_update;
+namespace blender::bke::image::gpu {
+struct ImageGPUTextureStore {
+ class Entry : NonCopyable {
+ public:
+ GPUTexture *gputextures[TEXTARGET_COUNT][2];
+ struct {
+ bool in_use : 1;
+ } flags;
+
+ Entry()
+ {
+ for (int target = 0; target < TEXTARGET_COUNT; target++) {
+ for (int eye = 0; eye < 2; eye++) {
+ gputextures[target][eye] = nullptr;
+ }
+ }
+ }
+
+ Entry(Entry &&other)
+ {
+ for (int target = 0; target < TEXTARGET_COUNT; target++) {
+ for (int eye = 0; eye < 2; eye++) {
+ gputextures[target][eye] = other.gputextures[target][eye];
+ other.gputextures[target][eye] = nullptr;
+ }
+ }
+ flags.in_use = other.flags.in_use;
+ };
+
+ virtual ~Entry()
+ {
+ clear();
+ }
+
+ void set_mipmap(const bool mipmap)
+ {
+ for (int eye = 0; eye < 2; eye++) {
+ if (gputextures[TEXTARGET_2D][eye]) {
+ GPU_texture_mipmap_mode(gputextures[TEXTARGET_2D][eye], mipmap, true);
+ }
+ if (gputextures[TEXTARGET_2D_ARRAY][eye]) {
+ GPU_texture_mipmap_mode(gputextures[TEXTARGET_2D_ARRAY][eye], mipmap, true);
+ }
+ }
+ }
+
+ void tag_used()
+ {
+ flags.in_use = true;
+ }
+
+ void clear()
+ {
+ for (int target = 0; target < TEXTARGET_COUNT; target++) {
+ for (int eye = 0; eye < 2; eye++) {
+ GPU_TEXTURE_FREE_SAFE(gputextures[target][eye]);
+ }
+ }
+ }
+ };
+
+ using Entries = Map<std::string, Entry>;
+
+ Entries entries;
+
+ void reset_usage()
+ {
+ for (Entry &entry : entries.values()) {
+ entry.flags.in_use = false;
+ }
+ }
+
+ void remove_unused()
+ {
+ for (auto it : entries.items()) {
+ if (it.value.flags.in_use) {
+ continue;
+ }
+ entries.remove(it.key);
+ }
+ }
+
+ void set_mipmap(const bool mipmap)
+ {
+ for (Entry &entry : entries.values()) {
+ entry.set_mipmap(mipmap);
+ }
+ }
+
+ void clear()
+ {
+ entries.clear();
+ }
+
+ std::string create_key(const Image &image) const
+ {
+ std::stringstream result;
+ result << "ID:" << image.id.name;
+ return result.str();
+ }
+
+ Entry &operator[](Image &image)
+ {
+ std::string key = create_key(image);
+ return entries.lookup_or_add_default(key);
+ }
+};
+
+static ImageGPUTextureStore g_texture_store;
+
+} // namespace blender::bke::image::gpu
+
extern "C" {
+using namespace blender::bke::image::gpu;
+
/* Prototypes. */
static void gpu_free_unused_buffers();
static void image_free_gpu(Image *ima, const bool immediate);
-static void image_update_gputexture_ex(
- Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h);
+static void image_update_gputexture_ex(Image *ima,
+ ImageGPUTextureStore::Entry &entry,
+ ImageTile *tile,
+ ImBuf *ibuf,
+ int x,
+ int y,
+ int w,
+ int h);
bool BKE_image_has_gpu_texture_premultiplied_alpha(Image *image, ImBuf *ibuf)
{
@@ -77,9 +199,11 @@ static int smaller_power_of_2_limit(int num)
return power_of_2_min_i(GPU_texture_size_with_limit(num));
}
-static GPUTexture *gpu_texture_create_tile_mapping(Image *ima, const int multiview_eye)
+static GPUTexture *gpu_texture_create_tile_mapping(Image *ima,
+ ImageGPUTextureStore::Entry &entry,
+ const int multiview_eye)
{
- GPUTexture *tilearray = ima->gputexture[TEXTARGET_2D_ARRAY][multiview_eye];
+ GPUTexture *tilearray = entry.gputextures[TEXTARGET_2D_ARRAY][multiview_eye];
if (tilearray == nullptr) {
return nullptr;
@@ -249,7 +373,7 @@ static GPUTexture *gpu_texture_create_tile_array(Image *ima, ImBuf *main_ibuf)
/** \name Regular gpu texture
* \{ */
-static GPUTexture **get_image_gpu_texture_ptr(Image *ima,
+static GPUTexture **get_image_gpu_texture_ptr(ImageGPUTextureStore::Entry &entry,
eGPUTextureTarget textarget,
const int multiview_eye)
{
@@ -258,7 +382,7 @@ static GPUTexture **get_image_gpu_texture_ptr(Image *ima,
BLI_assert(ELEM(multiview_eye, 0, 1));
if (in_range) {
- return &(ima->gputexture[textarget][multiview_eye]);
+ return &entry.gputextures[textarget][multiview_eye];
}
return nullptr;
}
@@ -278,7 +402,9 @@ static GPUTexture *image_gpu_texture_error_create(eGPUTextureTarget textarget)
}
static void image_gpu_texture_partial_update_changes_available(
- Image *image, PartialUpdateChecker<ImageTileData>::CollectResult &changes)
+ Image *image,
+ ImageGPUTextureStore::Entry &entry,
+ PartialUpdateChecker<ImageTileData>::CollectResult &changes)
{
while (changes.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) {
/* Calculate the clipping region with the tile buffer.
@@ -294,6 +420,7 @@ static void image_gpu_texture_partial_update_changes_available(
}
image_update_gputexture_ex(image,
+ entry,
changes.tile_data.tile,
changes.tile_data.tile_buffer,
clipped_update_region.xmin,
@@ -307,14 +434,15 @@ static void image_gpu_texture_try_partial_update(Image *image, ImageUser *iuser)
{
PartialUpdateChecker<ImageTileData> checker(image, iuser, image->runtime.partial_update_user);
PartialUpdateChecker<ImageTileData>::CollectResult changes = checker.collect_changes();
+ ImageGPUTextureStore::Entry &entry = g_texture_store[*image];
switch (changes.get_result_code()) {
case ePartialUpdateCollectResult::FullUpdateNeeded: {
- image_free_gpu(image, true);
+ entry.clear();
break;
}
case ePartialUpdateCollectResult::PartialChangesDetected: {
- image_gpu_texture_partial_update_changes_available(image, changes);
+ image_gpu_texture_partial_update_changes_available(image, entry, changes);
break;
}
@@ -371,7 +499,8 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
if (current_view >= 2) {
current_view = 0;
}
- GPUTexture **tex = get_image_gpu_texture_ptr(ima, textarget, current_view);
+ ImageGPUTextureStore::Entry &entry = g_texture_store[*ima];
+ GPUTexture **tex = get_image_gpu_texture_ptr(entry, textarget, current_view);
if (*tex) {
return *tex;
}
@@ -398,7 +527,7 @@ static GPUTexture *image_get_gpu_texture(Image *ima,
*tex = gpu_texture_create_tile_array(ima, ibuf_intern);
}
else if (textarget == TEXTARGET_TILE_MAPPING) {
- *tex = gpu_texture_create_tile_mapping(ima, iuser ? iuser->multiview_eye : 0);
+ *tex = gpu_texture_create_tile_mapping(ima, entry, iuser ? iuser->multiview_eye : 0);
}
else {
const bool use_high_bitdepth = (ima->flag & IMA_HIGH_BITDEPTH);
@@ -491,8 +620,10 @@ void BKE_image_free_unused_gpu_textures()
/** \name Deletion
* \{ */
-static void image_free_gpu(Image *ima, const bool immediate)
+static void image_free_gpu(Image *UNUSED(ima), const bool UNUSED(immediate))
{
+// TODO(jbakker)...
+#if 0
for (int eye = 0; eye < 2; eye++) {
for (int i = 0; i < TEXTARGET_COUNT; i++) {
if (ima->gputexture[i][eye] != nullptr) {
@@ -511,6 +642,7 @@ static void image_free_gpu(Image *ima, const bool immediate)
}
ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE;
+#endif
}
void BKE_image_free_gputextures(Image *ima)
@@ -518,13 +650,9 @@ void BKE_image_free_gputextures(Image *ima)
image_free_gpu(ima, BLI_thread_is_main());
}
-void BKE_image_free_all_gputextures(Main *bmain)
+void BKE_image_free_all_gputextures(Main *UNUSED(bmain))
{
- if (bmain) {
- LISTBASE_FOREACH (Image *, ima, &bmain->images) {
- BKE_image_free_gputextures(ima);
- }
- }
+ blender::bke::image::gpu::g_texture_store.clear();
}
void BKE_image_free_anim_gputextures(Main *bmain)
@@ -538,8 +666,9 @@ void BKE_image_free_anim_gputextures(Main *bmain)
}
}
-void BKE_image_free_old_gputextures(Main *bmain)
+void BKE_image_free_old_gputextures(Main *UNUSED(bmain))
{
+
static int lasttime = 0;
int ctime = (int)PIL_check_seconds_timer();
@@ -558,20 +687,8 @@ void BKE_image_free_old_gputextures(Main *bmain)
lasttime = ctime;
- LISTBASE_FOREACH (Image *, ima, &bmain->images) {
- if ((ima->flag & IMA_NOCOLLECT) == 0 && ctime - ima->lastused > U.textimeout) {
- /* If it's in GL memory, deallocate and set time tag to current time
- * This gives textures a "second chance" to be used before dying. */
- if (BKE_image_has_opengl_texture(ima)) {
- BKE_image_free_gputextures(ima);
- ima->lastused = ctime;
- }
- /* Otherwise, just kill the buffers */
- else {
- BKE_image_free_buffers(ima);
- }
- }
- }
+ blender::bke::image::gpu::g_texture_store.remove_unused();
+ blender::bke::image::gpu::g_texture_store.reset_usage();
}
/** \} */
@@ -802,18 +919,24 @@ static void gpu_texture_update_from_ibuf(
GPU_texture_unbind(tex);
}
-static void image_update_gputexture_ex(
- Image *ima, ImageTile *tile, ImBuf *ibuf, int x, int y, int w, int h)
+static void image_update_gputexture_ex(Image *ima,
+ ImageGPUTextureStore::Entry &entry,
+ ImageTile *tile,
+ ImBuf *ibuf,
+ int x,
+ int y,
+ int w,
+ int h)
{
const int eye = 0;
- GPUTexture *tex = ima->gputexture[TEXTARGET_2D][eye];
+ GPUTexture *tex = entry.gputextures[TEXTARGET_2D][eye];
/* Check if we need to update the main gputexture. */
if (tex != nullptr && tile == ima->tiles.first) {
gpu_texture_update_from_ibuf(tex, ima, ibuf, nullptr, x, y, w, h);
}
/* Check if we need to update the array gputexture. */
- tex = ima->gputexture[TEXTARGET_2D_ARRAY][eye];
+ tex = entry.gputextures[TEXTARGET_2D_ARRAY][eye];
if (tex != nullptr) {
gpu_texture_update_from_ibuf(tex, ima, ibuf, tile, x, y, w, h);
}
@@ -847,30 +970,9 @@ void BKE_image_update_gputexture_delayed(struct Image *ima,
}
}
-void BKE_image_paint_set_mipmap(Main *bmain, bool mipmap)
+void BKE_image_paint_set_mipmap(bool mipmap)
{
- LISTBASE_FOREACH (Image *, ima, &bmain->images) {
- if (BKE_image_has_opengl_texture(ima)) {
- if (ima->gpuflag & IMA_GPU_MIPMAP_COMPLETE) {
- for (int a = 0; a < TEXTARGET_COUNT; a++) {
- if (ELEM(a, TEXTARGET_2D, TEXTARGET_2D_ARRAY)) {
- for (int eye = 0; eye < 2; eye++) {
- GPUTexture *tex = ima->gputexture[a][eye];
- if (tex != nullptr) {
- GPU_texture_mipmap_mode(tex, mipmap, true);
- }
- }
- }
- }
- }
- else {
- BKE_image_free_gputextures(ima);
- }
- }
- else {
- ima->gpuflag &= ~IMA_GPU_MIPMAP_COMPLETE;
- }
- }
+ blender::bke::image::gpu::g_texture_store.set_mipmap(mipmap);
}
/** \} */
diff --git a/source/blender/editors/sculpt_paint/paint_image.cc b/source/blender/editors/sculpt_paint/paint_image.cc
index a313489885d..d5da6f00494 100644
--- a/source/blender/editors/sculpt_paint/paint_image.cc
+++ b/source/blender/editors/sculpt_paint/paint_image.cc
@@ -801,7 +801,7 @@ void ED_object_texture_paint_mode_enter_ex(Main *bmain, Scene *scene, Object *ob
if (U.glreslimit != 0) {
BKE_image_free_all_gputextures(bmain);
}
- BKE_image_paint_set_mipmap(bmain, false);
+ BKE_image_paint_set_mipmap(false);
toggle_paint_cursor(scene, true);
@@ -826,7 +826,7 @@ void ED_object_texture_paint_mode_exit_ex(Main *bmain, Scene *scene, Object *ob)
if (U.glreslimit != 0) {
BKE_image_free_all_gputextures(bmain);
}
- BKE_image_paint_set_mipmap(bmain, true);
+ BKE_image_paint_set_mipmap(true);
toggle_paint_cursor(scene, false);
Mesh *me = BKE_mesh_from_object(ob);
diff --git a/source/blender/makesdna/DNA_image_types.h b/source/blender/makesdna/DNA_image_types.h
index 6e4e515a0fe..beef410d362 100644
--- a/source/blender/makesdna/DNA_image_types.h
+++ b/source/blender/makesdna/DNA_image_types.h
@@ -138,7 +138,7 @@ typedef struct Image {
/** Not written in file. */
struct MovieCache *cache;
/** Not written in file 3 = TEXTARGET_COUNT, 2 = stereo eyes. */
- struct GPUTexture *gputexture[3][2];
+ // struct GPUTexture *gputexture[3][2];
/* sources from: */
ListBase anims;
diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c
index 269ebe1581f..703932f6a59 100644
--- a/source/blender/makesrna/intern/rna_image.c
+++ b/source/blender/makesrna/intern/rna_image.c
@@ -397,7 +397,7 @@ static void rna_Image_resolution_set(PointerRNA *ptr, const float *values)
static int rna_Image_bindcode_get(PointerRNA *ptr)
{
Image *ima = (Image *)ptr->data;
- GPUTexture *tex = ima->gputexture[TEXTARGET_2D][0];
+ GPUTexture *tex = BKE_image_get_gpu_texture(ima, NULL, NULL);
return (tex) ? GPU_texture_opengl_bindcode(tex) : 0;
}
diff --git a/source/blender/makesrna/intern/rna_image_api.c b/source/blender/makesrna/intern/rna_image_api.c
index bac8f214441..6f742284616 100644
--- a/source/blender/makesrna/intern/rna_image_api.c
+++ b/source/blender/makesrna/intern/rna_image_api.c
@@ -191,16 +191,16 @@ static int rna_Image_gl_load(
return 0; /* GL_NO_ERROR */
}
-static int rna_Image_gl_touch(
- Image *image, ReportList *reports, int frame, int layer_index, int pass_index)
+static int rna_Image_gl_touch(Image *image,
+ ReportList *reports,
+ int UNUSED(frame),
+ int UNUSED(layer_index),
+ int UNUSED(pass_index))
{
int error = 0; /* GL_NO_ERROR */
BKE_image_tag_time(image);
-
- if (image->gputexture[TEXTARGET_2D][0] == NULL) {
- error = rna_Image_gl_load(image, reports, frame, layer_index, pass_index);
- }
+ BKE_image_partial_update_mark_full_update(image);
return error;
}