diff options
author | Jacques Lucke <jacques@blender.org> | 2022-03-01 13:36:46 +0300 |
---|---|---|
committer | Jacques Lucke <jacques@blender.org> | 2022-03-01 13:36:46 +0300 |
commit | 4b9c77a19aa359b5ab4b86e05cff1c8627d05a1e (patch) | |
tree | 8a5b74ef6f782e269883a1a38896cea51d4e953c | |
parent | f0bfceb96d86062245c673938a8272276c1fcacf (diff) | |
parent | 9216cf9cb589f4d8ec0e79f0f0a160ce2c1dfb07 (diff) |
Merge branch 'blender-v3.1-release'
-rw-r--r-- | source/blender/blenkernel/intern/image.c | 1 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/image_gpu.cc | 26 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/image_partial_update.cc | 4 | ||||
-rw-r--r-- | source/blender/blenkernel/intern/node.cc | 2 | ||||
-rw-r--r-- | source/blender/draw/engines/image/image_buffer_cache.hh | 131 | ||||
-rw-r--r-- | source/blender/draw/engines/image/image_drawing_mode.hh | 65 | ||||
-rw-r--r-- | source/blender/draw/engines/image/image_instance_data.hh | 9 | ||||
-rw-r--r-- | source/blender/draw/engines/image/image_usage.hh | 3 | ||||
-rw-r--r-- | source/blender/editors/object/object_bake_api.c | 1 | ||||
-rw-r--r-- | source/blender/imbuf/IMB_imbuf.h | 3 | ||||
-rw-r--r-- | source/blender/imbuf/intern/divers.c | 83 | ||||
-rw-r--r-- | source/blender/makesrna/intern/rna_image.c | 2 |
12 files changed, 275 insertions, 55 deletions
diff --git a/source/blender/blenkernel/intern/image.c b/source/blender/blenkernel/intern/image.c index fa63f99d3f1..817e7bb9685 100644 --- a/source/blender/blenkernel/intern/image.c +++ b/source/blender/blenkernel/intern/image.c @@ -3569,6 +3569,7 @@ static void image_tag_reload(Image *ima, ID *iuser_id, ImageUser *iuser, void *c /* Must copy image user changes to CoW data-block. */ DEG_id_tag_update(iuser_id, ID_RECALC_COPY_ON_WRITE); } + 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 444cbbe4bf9..c4a43d8b023 100644 --- a/source/blender/blenkernel/intern/image_gpu.cc +++ b/source/blender/blenkernel/intern/image_gpu.cc @@ -322,19 +322,25 @@ static void image_gpu_texture_partial_update_changes_available( Image *image, PartialUpdateChecker<ImageTileData>::CollectResult &changes) { while (changes.get_next_change() == ePartialUpdateIterResult::ChangeAvailable) { - const int tile_offset_x = changes.changed_region.region.xmin; - const int tile_offset_y = changes.changed_region.region.ymin; - const int tile_width = min_ii(changes.tile_data.tile_buffer->x, - BLI_rcti_size_x(&changes.changed_region.region)); - const int tile_height = min_ii(changes.tile_data.tile_buffer->y, - BLI_rcti_size_y(&changes.changed_region.region)); + /* Calculate the clipping region with the tile buffer. + * TODO(jbakker): should become part of ImageTileData to deduplicate with image engine. */ + rcti buffer_rect; + BLI_rcti_init( + &buffer_rect, 0, changes.tile_data.tile_buffer->x, 0, changes.tile_data.tile_buffer->y); + rcti clipped_update_region; + const bool has_overlap = BLI_rcti_isect( + &buffer_rect, &changes.changed_region.region, &clipped_update_region); + if (!has_overlap) { + continue; + } + image_update_gputexture_ex(image, changes.tile_data.tile, changes.tile_data.tile_buffer, - tile_offset_x, - tile_offset_y, - tile_width, - tile_height); + clipped_update_region.xmin, + clipped_update_region.ymin, + BLI_rcti_size_x(&clipped_update_region), + BLI_rcti_size_y(&clipped_update_region)); } } diff --git a/source/blender/blenkernel/intern/image_partial_update.cc b/source/blender/blenkernel/intern/image_partial_update.cc index 9d5635f49ab..4606a14ab69 100644 --- a/source/blender/blenkernel/intern/image_partial_update.cc +++ b/source/blender/blenkernel/intern/image_partial_update.cc @@ -198,8 +198,8 @@ struct TileChangeset { tile_width = image_buffer->x; tile_height = image_buffer->y; - int chunk_x_len = tile_width / CHUNK_SIZE; - int chunk_y_len = tile_height / CHUNK_SIZE; + int chunk_x_len = (tile_width + CHUNK_SIZE - 1) / CHUNK_SIZE; + int chunk_y_len = (tile_height + CHUNK_SIZE - 1) / CHUNK_SIZE; init_chunks(chunk_x_len, chunk_y_len); return true; } diff --git a/source/blender/blenkernel/intern/node.cc b/source/blender/blenkernel/intern/node.cc index 876ccc5351c..96bfcb0311b 100644 --- a/source/blender/blenkernel/intern/node.cc +++ b/source/blender/blenkernel/intern/node.cc @@ -1952,6 +1952,8 @@ void nodeRemoveAllSockets(bNodeTree *ntree, bNode *node) } } + BLI_freelistN(&node->internal_links); + LISTBASE_FOREACH_MUTABLE (bNodeSocket *, sock, &node->inputs) { node_socket_free(sock, true); MEM_freeN(sock); diff --git a/source/blender/draw/engines/image/image_buffer_cache.hh b/source/blender/draw/engines/image/image_buffer_cache.hh new file mode 100644 index 00000000000..ef11551c879 --- /dev/null +++ b/source/blender/draw/engines/image/image_buffer_cache.hh @@ -0,0 +1,131 @@ +/* + * 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. + * + * Copyright 2022, Blender Foundation. + */ + +/** \file + * \ingroup draw_engine + */ + +#pragma once + +#include "BLI_vector.hh" + +#include "IMB_imbuf.h" +#include "IMB_imbuf_types.h" + +struct FloatImageBuffer { + ImBuf *source_buffer = nullptr; + ImBuf *float_buffer = nullptr; + bool is_used = true; + + FloatImageBuffer(ImBuf *source_buffer, ImBuf *float_buffer) + : source_buffer(source_buffer), float_buffer(float_buffer) + { + } + + FloatImageBuffer(FloatImageBuffer &&other) noexcept + { + source_buffer = other.source_buffer; + float_buffer = other.float_buffer; + is_used = other.is_used; + other.source_buffer = nullptr; + other.float_buffer = nullptr; + } + + virtual ~FloatImageBuffer() + { + IMB_freeImBuf(float_buffer); + float_buffer = nullptr; + source_buffer = nullptr; + } + + FloatImageBuffer &operator=(FloatImageBuffer &&other) noexcept + { + this->source_buffer = other.source_buffer; + this->float_buffer = other.float_buffer; + is_used = other.is_used; + other.source_buffer = nullptr; + other.float_buffer = nullptr; + return *this; + } +}; + +struct FloatBufferCache { + private: + blender::Vector<FloatImageBuffer> cache_; + + public: + ImBuf *ensure_float_buffer(ImBuf *image_buffer) + { + /* Check if we can use the float buffer of the given image_buffer. */ + if (image_buffer->rect_float != nullptr) { + return image_buffer; + } + + /* Do we have a cached float buffer. */ + for (FloatImageBuffer &item : cache_) { + if (item.source_buffer == image_buffer) { + item.is_used = true; + return item.float_buffer; + } + } + + /* Generate a new float buffer. */ + IMB_float_from_rect(image_buffer); + ImBuf *new_imbuf = IMB_allocImBuf(image_buffer->x, image_buffer->y, image_buffer->planes, 0); + new_imbuf->rect_float = image_buffer->rect_float; + new_imbuf->flags |= IB_rectfloat; + new_imbuf->mall |= IB_rectfloat; + image_buffer->rect_float = nullptr; + image_buffer->flags &= ~IB_rectfloat; + image_buffer->mall &= ~IB_rectfloat; + + cache_.append(FloatImageBuffer(image_buffer, new_imbuf)); + return new_imbuf; + } + + void reset_usage_flags() + { + for (FloatImageBuffer &buffer : cache_) { + buffer.is_used = false; + } + } + + void mark_used(const ImBuf *image_buffer) + { + for (FloatImageBuffer &item : cache_) { + if (item.source_buffer == image_buffer) { + item.is_used = true; + return; + } + } + } + + void remove_unused_buffers() + { + for (int64_t i = cache_.size() - 1; i >= 0; i--) { + if (!cache_[i].is_used) { + cache_.remove_and_reorder(i); + } + } + } + + void clear() + { + cache_.clear(); + } +}; diff --git a/source/blender/draw/engines/image/image_drawing_mode.hh b/source/blender/draw/engines/image/image_drawing_mode.hh index b9de0838fef..c727fbcd98f 100644 --- a/source/blender/draw/engines/image/image_drawing_mode.hh +++ b/source/blender/draw/engines/image/image_drawing_mode.hh @@ -157,6 +157,7 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD if (tile_buffer == nullptr) { continue; } + instance_data.float_buffers.mark_used(tile_buffer); BKE_image_release_ibuf(image, tile_buffer, lock); DRWShadingGroup *shsub = DRW_shgroup_create_sub(shgrp); @@ -184,12 +185,14 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD switch (changes.get_result_code()) { case ePartialUpdateCollectResult::FullUpdateNeeded: instance_data.mark_all_texture_slots_dirty(); + instance_data.float_buffers.clear(); break; case ePartialUpdateCollectResult::NoChangesDetected: break; case ePartialUpdateCollectResult::PartialChangesDetected: /* Partial update when wrap repeat is enabled is not supported. */ if (instance_data.flags.do_tile_drawing) { + instance_data.float_buffers.clear(); instance_data.mark_all_texture_slots_dirty(); } else { @@ -200,6 +203,34 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD do_full_update_for_dirty_textures(instance_data, image_user); } + /** + * Update the float buffer in the region given by the partial update checker. + */ + void do_partial_update_float_buffer( + ImBuf *float_buffer, PartialUpdateChecker<ImageTileData>::CollectResult &iterator) const + { + ImBuf *src = iterator.tile_data.tile_buffer; + BLI_assert(float_buffer->rect_float != nullptr); + BLI_assert(float_buffer->rect == nullptr); + BLI_assert(src->rect_float == nullptr); + BLI_assert(src->rect != nullptr); + + /* Calculate the overlap between the updated region and the buffer size. Partial Update Checker + * always returns a tile (256x256). Which could lay partially outside the buffer when using + * different resolutions. + */ + rcti buffer_rect; + BLI_rcti_init(&buffer_rect, 0, float_buffer->x, 0, float_buffer->y); + rcti clipped_update_region; + const bool has_overlap = BLI_rcti_isect( + &buffer_rect, &iterator.changed_region.region, &clipped_update_region); + if (!has_overlap) { + return; + } + + IMB_float_from_rect_ex(float_buffer, src, &clipped_update_region); + } + void do_partial_update(PartialUpdateChecker<ImageTileData>::CollectResult &iterator, IMAGE_InstanceData &instance_data) const { @@ -208,7 +239,11 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD if (iterator.tile_data.tile_buffer == nullptr) { continue; } - const bool do_free_float_buffer = ensure_float_buffer(*iterator.tile_data.tile_buffer); + ImBuf *tile_buffer = ensure_float_buffer(instance_data, iterator.tile_data.tile_buffer); + if (tile_buffer != iterator.tile_data.tile_buffer) { + do_partial_update_float_buffer(tile_buffer, iterator); + } + const float tile_width = static_cast<float>(iterator.tile_data.tile_buffer->x); const float tile_height = static_cast<float>(iterator.tile_data.tile_buffer->y); @@ -283,7 +318,6 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD &extracted_buffer, texture_region_width, texture_region_height, 32, IB_rectfloat); int offset = 0; - ImBuf *tile_buffer = iterator.tile_data.tile_buffer; for (int y = gpu_texture_region_to_update.ymin; y < gpu_texture_region_to_update.ymax; y++) { float yf = y / (float)texture_height; @@ -314,10 +348,6 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD 0); imb_freerectImbuf_all(&extracted_buffer); } - - if (do_free_float_buffer) { - imb_freerectfloatImBuf(iterator.tile_data.tile_buffer); - } } } @@ -376,16 +406,12 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD * rect_float as the reference-counter isn't 0. To work around this we destruct any created local * buffers ourself. */ - bool ensure_float_buffer(ImBuf &image_buffer) const + ImBuf *ensure_float_buffer(IMAGE_InstanceData &instance_data, ImBuf *image_buffer) const { - if (image_buffer.rect_float == nullptr) { - IMB_float_from_rect(&image_buffer); - return true; - } - return false; + return instance_data.float_buffers.ensure_float_buffer(image_buffer); } - void do_full_update_texture_slot(const IMAGE_InstanceData &instance_data, + void do_full_update_texture_slot(IMAGE_InstanceData &instance_data, const TextureInfo &texture_info, ImBuf &texture_buffer, ImBuf &tile_buffer, @@ -393,7 +419,7 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD { const int texture_width = texture_buffer.x; const int texture_height = texture_buffer.y; - const bool do_free_float_buffer = ensure_float_buffer(tile_buffer); + ImBuf *float_tile_buffer = ensure_float_buffer(instance_data, &tile_buffer); /* IMB_transform works in a non-consistent space. This should be documented or fixed!. * Construct a variant of the info_uv_to_texture that adds the texel space @@ -424,16 +450,12 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD transform_mode = IMB_TRANSFORM_MODE_CROP_SRC; } - IMB_transform(&tile_buffer, + IMB_transform(float_tile_buffer, &texture_buffer, transform_mode, IMB_FILTER_NEAREST, uv_to_texel, crop_rect_ptr); - - if (do_free_float_buffer) { - imb_freerectfloatImBuf(&tile_buffer); - } } public: @@ -452,6 +474,7 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD instance_data->partial_update.ensure_image(image); instance_data->clear_dirty_flag(); + instance_data->float_buffers.reset_usage_flags(); /* Step: Find out which screen space textures are needed to draw on the screen. Remove the * screen space textures that aren't needed. */ @@ -472,8 +495,10 @@ template<typename TextureMethod> class ScreenSpaceDrawingMode : public AbstractD add_shgroups(instance_data); } - void draw_finish(IMAGE_Data *UNUSED(vedata)) const override + void draw_finish(IMAGE_Data *vedata) const override { + IMAGE_InstanceData *instance_data = vedata->instance_data; + instance_data->float_buffers.remove_unused_buffers(); } void draw_scene(IMAGE_Data *vedata) const override diff --git a/source/blender/draw/engines/image/image_instance_data.hh b/source/blender/draw/engines/image/image_instance_data.hh index be846799293..a7ae8666968 100644 --- a/source/blender/draw/engines/image/image_instance_data.hh +++ b/source/blender/draw/engines/image/image_instance_data.hh @@ -8,6 +8,7 @@ #pragma once #include "image_batches.hh" +#include "image_buffer_cache.hh" #include "image_partial_updater.hh" #include "image_private.hh" #include "image_shader_params.hh" @@ -48,11 +49,18 @@ struct IMAGE_InstanceData { DRWPass *depth_pass; } passes; + /** + * Cache containing the float buffers when drawing byte images. + */ + FloatBufferCache float_buffers; + /** \brief Transform matrix to convert a normalized screen space coordinates to texture space. */ float ss_to_texture[4][4]; TextureInfo texture_infos[SCREEN_SPACE_DRAWING_MODE_TEXTURE_LEN]; public: + virtual ~IMAGE_InstanceData() = default; + void clear_dirty_flag() { reset_dirty_flag(false); @@ -102,6 +110,7 @@ struct IMAGE_InstanceData { if (last_usage != usage) { last_usage = usage; reset_dirty_flag(true); + float_buffers.clear(); } } diff --git a/source/blender/draw/engines/image/image_usage.hh b/source/blender/draw/engines/image/image_usage.hh index 2f3f0d23b6a..0643cc87304 100644 --- a/source/blender/draw/engines/image/image_usage.hh +++ b/source/blender/draw/engines/image/image_usage.hh @@ -23,6 +23,8 @@ struct ImageUsage { /** IMA_ALPHA_* */ char alpha_mode; + const void *last_image = nullptr; + ImageUsage() = default; ImageUsage(const struct Image *image, const struct ImageUser *image_user) { @@ -31,6 +33,7 @@ struct ImageUsage { view = image_user ? image_user->multi_index : 0; colorspace_settings = image->colorspace_settings; alpha_mode = image->alpha_mode; + last_image = static_cast<const void *>(image); } bool operator==(const ImageUsage &other) const diff --git a/source/blender/editors/object/object_bake_api.c b/source/blender/editors/object/object_bake_api.c index 7f4af43eb30..3b40a10eb2a 100644 --- a/source/blender/editors/object/object_bake_api.c +++ b/source/blender/editors/object/object_bake_api.c @@ -301,6 +301,7 @@ static void bake_targets_refresh(BakeTargets *targets) Image *ima = targets->images[i].image; if (ima) { + BKE_image_partial_update_mark_full_update(ima); LISTBASE_FOREACH (ImageTile *, tile, &ima->tiles) { BKE_image_free_gputextures(ima); DEG_id_tag_update(&ima->id, 0); diff --git a/source/blender/imbuf/IMB_imbuf.h b/source/blender/imbuf/IMB_imbuf.h index 23b9c85bd5b..6b35e84ca51 100644 --- a/source/blender/imbuf/IMB_imbuf.h +++ b/source/blender/imbuf/IMB_imbuf.h @@ -560,6 +560,9 @@ bool IMB_alpha_affects_rgb(const struct ImBuf *ibuf); * Create char buffer, color corrected if necessary, for ImBufs that lack one. */ void IMB_rect_from_float(struct ImBuf *ibuf); +void IMB_float_from_rect_ex(struct ImBuf *dst, + const struct ImBuf *src, + const struct rcti *region_to_update); void IMB_float_from_rect(struct ImBuf *ibuf); /** * No profile conversion. diff --git a/source/blender/imbuf/intern/divers.c b/source/blender/imbuf/intern/divers.c index 0bf50937674..588c92d748d 100644 --- a/source/blender/imbuf/intern/divers.c +++ b/source/blender/imbuf/intern/divers.c @@ -6,6 +6,7 @@ */ #include "BLI_math.h" +#include "BLI_rect.h" #include "BLI_utildefines.h" #include "IMB_filter.h" @@ -752,6 +753,61 @@ void IMB_rect_from_float(ImBuf *ibuf) ibuf->userflags &= ~IB_RECT_INVALID; } +void IMB_float_from_rect_ex(struct ImBuf *dst, + const struct ImBuf *src, + const rcti *region_to_update) +{ + BLI_assert_msg(dst->rect_float != NULL, + "Destination buffer should have a float buffer assigned."); + BLI_assert_msg(src->rect != NULL, "Source buffer should have a byte buffer assigned."); + BLI_assert_msg(dst->x == src->x, "Source and destination buffer should have the same dimension"); + BLI_assert_msg(dst->y == src->y, "Source and destination buffer should have the same dimension"); + BLI_assert_msg(dst->channels = 4, "Destination buffer should have 4 channels."); + BLI_assert_msg(region_to_update->xmin >= 0, + "Region to update should be clipped to the given buffers."); + BLI_assert_msg(region_to_update->ymin >= 0, + "Region to update should be clipped to the given buffers."); + BLI_assert_msg(region_to_update->xmax <= dst->x, + "Region to update should be clipped to the given buffers."); + BLI_assert_msg(region_to_update->ymax <= dst->y, + "Region to update should be clipped to the given buffers."); + + float *rect_float = dst->rect_float; + rect_float += (region_to_update->xmin + region_to_update->ymin * dst->x) * 4; + unsigned char *rect = (unsigned char *)src->rect; + rect += (region_to_update->xmin + region_to_update->ymin * dst->x) * 4; + const int region_width = BLI_rcti_size_x(region_to_update); + const int region_height = BLI_rcti_size_y(region_to_update); + + /* Convert byte buffer to float buffer without color or alpha conversion. */ + IMB_buffer_float_from_byte(rect_float, + rect, + IB_PROFILE_SRGB, + IB_PROFILE_SRGB, + false, + region_width, + region_height, + src->x, + dst->x); + + /* Perform color space conversion from rect color space to linear. */ + float *float_ptr = rect_float; + for (int i = 0; i < region_height; i++) { + IMB_colormanagement_colorspace_to_scene_linear( + float_ptr, region_width, 1, dst->channels, src->rect_colorspace, false); + float_ptr += 4 * dst->x; + } + + /* Perform alpha conversion. */ + if (IMB_alpha_affects_rgb(src)) { + float_ptr = rect_float; + for (int i = 0; i < region_height; i++) { + IMB_premultiply_rect_float(float_ptr, dst->channels, region_width, 1); + float_ptr += 4 * dst->x; + } + } +} + void IMB_float_from_rect(ImBuf *ibuf) { float *rect_float; @@ -775,33 +831,14 @@ void IMB_float_from_rect(ImBuf *ibuf) } ibuf->channels = 4; - } - - /* first, create float buffer in non-linear space */ - IMB_buffer_float_from_byte(rect_float, - (unsigned char *)ibuf->rect, - IB_PROFILE_SRGB, - IB_PROFILE_SRGB, - false, - ibuf->x, - ibuf->y, - ibuf->x, - ibuf->x); - - /* then make float be in linear space */ - IMB_colormanagement_colorspace_to_scene_linear( - rect_float, ibuf->x, ibuf->y, ibuf->channels, ibuf->rect_colorspace, false); - - /* byte buffer is straight alpha, float should always be premul */ - if (IMB_alpha_affects_rgb(ibuf)) { - IMB_premultiply_rect_float(rect_float, ibuf->channels, ibuf->x, ibuf->y); - } - - if (ibuf->rect_float == NULL) { ibuf->rect_float = rect_float; ibuf->mall |= IB_rectfloat; ibuf->flags |= IB_rectfloat; } + + rcti region_to_update; + BLI_rcti_init(®ion_to_update, 0, ibuf->x, 0, ibuf->y); + IMB_float_from_rect_ex(ibuf, ibuf, ®ion_to_update); } /** \} */ diff --git a/source/blender/makesrna/intern/rna_image.c b/source/blender/makesrna/intern/rna_image.c index e40fafd2069..56e23278176 100644 --- a/source/blender/makesrna/intern/rna_image.c +++ b/source/blender/makesrna/intern/rna_image.c @@ -103,6 +103,7 @@ static void rna_Image_generated_update(Main *bmain, Scene *UNUSED(scene), Pointe { Image *ima = (Image *)ptr->owner_id; BKE_image_signal(bmain, ima, NULL, IMA_SIGNAL_FREE); + BKE_image_partial_update_mark_full_update(ima); } static void rna_Image_colormanage_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr) @@ -141,6 +142,7 @@ static void rna_Image_views_format_update(Main *bmain, Scene *scene, PointerRNA } BKE_image_release_ibuf(ima, ibuf, lock); + BKE_image_partial_update_mark_full_update(ima); } static void rna_ImageUser_update(Main *bmain, Scene *scene, PointerRNA *ptr) |