Blender V4.3
cached_image.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
5#include <cstdint>
6#include <memory>
7
8#include "BLI_array.hh"
9#include "BLI_assert.h"
10#include "BLI_hash.hh"
11#include "BLI_listbase.h"
12
13#include "RE_pipeline.h"
14
15#include "GPU_shader.hh"
16#include "GPU_texture.hh"
17
19#include "IMB_imbuf.hh"
20#include "IMB_imbuf_types.hh"
21
22#include "BKE_image.hh"
23#include "BKE_lib_id.hh"
24
25#include "DNA_ID.h"
26#include "DNA_image_types.h"
27
28#include "COM_cached_image.hh"
29#include "COM_context.hh"
30#include "COM_result.hh"
31#include "COM_utilities.hh"
32
34
35/* --------------------------------------------------------------------
36 * Cached Image Key.
37 */
38
39CachedImageKey::CachedImageKey(ImageUser image_user, std::string pass_name)
40 : image_user(image_user), pass_name(pass_name)
41{
42}
43
48
50{
51 return a.image_user.framenr == b.image_user.framenr &&
52 a.image_user.layer == b.image_user.layer && a.image_user.view == b.image_user.view &&
53 a.pass_name == b.pass_name;
54}
55
56/* --------------------------------------------------------------------
57 * Cached Image.
58 */
59
60/* Get the selected render layer selected assuming the image is a multilayer image. */
61static RenderLayer *get_render_layer(Image *image, ImageUser &image_user)
62{
63 const ListBase *layers = &image->rr->layers;
64 return static_cast<RenderLayer *>(BLI_findlink(layers, image_user.layer));
65}
66
67/* Get the index of the pass with the given name in the selected render layer's passes list
68 * assuming the image is a multilayer image. */
69static int get_pass_index(Image *image, ImageUser &image_user, const char *name)
70{
71 const RenderLayer *render_layer = get_render_layer(image, image_user);
72 return BLI_findstringindex(&render_layer->passes, name, offsetof(RenderPass, name));
73}
74
75/* Get the index of the view selected in the image user. If the image is not a multi-view image
76 * or only has a single view, then zero is returned. Otherwise, if the image is a multi-view
77 * image, the index of the selected view is returned. However, note that the value of the view
78 * member of the image user is not the actual index of the view. More specifically, the index 0
79 * is reserved to denote the special mode of operation "All", which dynamically selects the view
80 * whose name matches the view currently being rendered. It follows that the views are then
81 * indexed starting from 1. So for non zero view values, the actual index of the view is the
82 * value of the view member of the image user minus 1. */
83static int get_view_index(Context &context, Image *image, ImageUser &image_user)
84{
85 /* The image is not a multi-view image, so just return zero. */
86 if (!BKE_image_is_multiview(image)) {
87 return 0;
88 }
89
90 const ListBase *views = &image->rr->views;
91 /* There is only one view and its index is 0. */
92 if (BLI_listbase_count_at_most(views, 2) < 2) {
93 return 0;
94 }
95
96 const int view = image_user.view;
97 /* The view is not zero, which means it is manually specified and the actual index is then the
98 * view value minus 1. */
99 if (view != 0) {
100 return view - 1;
101 }
102
103 /* Otherwise, the view value is zero, denoting the special mode of operation "All", which finds
104 * the index of the view whose name matches the view currently being rendered. */
105 const char *view_name = context.get_view_name().data();
106 const int matched_view = BLI_findstringindex(views, view_name, offsetof(RenderView, name));
107
108 /* No view matches the view currently being rendered, so fallback to the first view. */
109 if (matched_view == -1) {
110 return 0;
111 }
112
113 return matched_view;
114}
115
116/* Get a copy of the image user that is appropriate to retrieve the needed image buffer from the
117 * image. This essentially sets the appropriate frame, pass, and view that corresponds to the
118 * given context and pass name. */
120 Image *image,
121 const ImageUser *image_user,
122 const char *pass_name)
123{
124 ImageUser image_user_for_pass = *image_user;
125
126 /* Set the needed view. */
127 image_user_for_pass.view = get_view_index(context, image, image_user_for_pass);
128
129 /* Set the needed pass. */
130 if (BKE_image_is_multilayer(image)) {
131 image_user_for_pass.pass = get_pass_index(image, image_user_for_pass, pass_name);
132 BKE_image_multilayer_index(image->rr, &image_user_for_pass);
133 }
134 else {
135 BKE_image_multiview_index(image, &image_user_for_pass);
136 }
137
138 return image_user_for_pass;
139}
140
141/* The image buffer might be stored as an sRGB 8-bit image, while the compositor expects linear
142 * float images, so compute a linear float buffer for the image buffer. This will also do linear
143 * space conversion and alpha pre-multiplication as needed. We could store those images in sRGB GPU
144 * textures and let the GPU do the linear space conversion, but the issues is that we don't control
145 * how the GPU does the conversion and so we get tiny differences across CPU and GPU compositing,
146 * and potentially even across GPUs/Drivers. Further, if alpha pre-multiplication is needed, we
147 * would need to do it ourself, which means alpha pre-multiplication will happen before linear
148 * space conversion, which would produce yet another difference. So we just do everything on the
149 * CPU, since this is already a cached resource.
150 *
151 * To avoid conflicts with other threads, create a new image buffer and assign all the necessary
152 * information to it, with IB_DO_NOT_TAKE_OWNERSHIP for buffers since a deep copy is not needed.
153 *
154 * The caller should free the returned image buffer. */
155static ImBuf *compute_linear_buffer(ImBuf *image_buffer)
156{
157 /* Do not pass the flags to the allocation function to avoid buffer allocation, but assign them
158 * after to retain important information like precision and alpha mode. */
159 ImBuf *linear_image_buffer = IMB_allocImBuf(
160 image_buffer->x, image_buffer->y, image_buffer->planes, 0);
161 linear_image_buffer->flags = image_buffer->flags;
162
163 /* Assign the float buffer if it exists, as well as its number of channels. */
165 linear_image_buffer, image_buffer->float_buffer, IB_DO_NOT_TAKE_OWNERSHIP);
166 linear_image_buffer->channels = image_buffer->channels;
167
168 /* If no float buffer exists, assign it then compute a float buffer from it. This is the main
169 * call of this function. */
170 if (!linear_image_buffer->float_buffer.data) {
172 linear_image_buffer, image_buffer->byte_buffer, IB_DO_NOT_TAKE_OWNERSHIP);
173 IMB_float_from_rect(linear_image_buffer);
174 }
175
176 /* If the image buffer contained compressed data, assign them as well, but only if the color
177 * space of the buffer is linear or data, since we need linear data and can't preprocess the
178 * compressed buffer. If not, we fallback to the float buffer already assigned, which is
179 * guaranteed to exist as a fallback for compressed textures. */
180 const bool is_suitable_compressed_color_space =
183 if (image_buffer->ftype == IMB_FTYPE_DDS && is_suitable_compressed_color_space) {
184 linear_image_buffer->ftype = IMB_FTYPE_DDS;
185 IMB_assign_dds_data(linear_image_buffer, image_buffer->dds_data, IB_DO_NOT_TAKE_OWNERSHIP);
186 }
187
188 return linear_image_buffer;
189}
190
192 Image *image,
193 ImageUser *image_user,
194 const char *pass_name)
195 : result(context)
196{
197 /* We can't retrieve the needed image buffer yet, because we still need to assign the pass index
198 * to the image user in order to acquire the image buffer corresponding to the given pass name.
199 * However, in order to compute the pass index, we need the render result structure of the image
200 * to be initialized. So we first acquire a dummy image buffer since it initializes the image
201 * render result as a side effect. We also use that as a mean of validation, since we can early
202 * exit if the returned image buffer is nullptr. This image buffer can be immediately released.
203 * Since it carries no important information. */
204 ImBuf *initial_image_buffer = BKE_image_acquire_ibuf(image, image_user, nullptr);
205 BKE_image_release_ibuf(image, initial_image_buffer, nullptr);
206 if (!initial_image_buffer) {
207 return;
208 }
209
210 ImageUser image_user_for_pass = compute_image_user_for_pass(
211 context, image, image_user, pass_name);
212
213 ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user_for_pass, nullptr);
214 ImBuf *linear_image_buffer = compute_linear_buffer(image_buffer);
215
216 const bool use_half_float = linear_image_buffer->flags & IB_halffloat;
217 this->result.set_precision(use_half_float ? ResultPrecision::Half : ResultPrecision::Full);
218
219 /* At the user level, vector images are always treated as color, so there are only two possible
220 * options, float images and color images. 3-channel images should then be converted to 4-channel
221 * images below. */
222 const bool is_single_channel = linear_image_buffer->channels == 1;
223 this->result.set_type(is_single_channel ? ResultType::Float : ResultType::Color);
224
225 /* For GPU, we wrap the texture returned by IMB module and free it ourselves in destructor. For
226 * CPU, we allocate the result and copy to it from the image buffer. */
227 if (context.use_gpu()) {
228 texture_ = IMB_create_gpu_texture("Image Texture", linear_image_buffer, true, true);
230 this->result.wrap_external(texture_);
231 }
232 else {
233 const int2 size = int2(image_buffer->x, image_buffer->y);
234 const int channels_count = linear_image_buffer->channels;
235 Result buffer_result(context, Result::float_type(channels_count), ResultPrecision::Full);
236 buffer_result.wrap_external(linear_image_buffer->float_buffer.data, size);
237 this->result.allocate_texture(size, false);
238 parallel_for(size, [&](const int2 texel) {
239 this->result.store_pixel(texel, buffer_result.load_pixel(texel));
240 });
241 }
242
243 IMB_freeImBuf(linear_image_buffer);
244 BKE_image_release_ibuf(image, image_buffer, nullptr);
245}
246
248{
249 this->result.release();
250 GPU_TEXTURE_FREE_SAFE(texture_);
251}
252
253/* --------------------------------------------------------------------
254 * Cached Image Container.
255 */
256
258{
259 /* First, delete all cached images that are no longer needed. */
260 for (auto &cached_images_for_id : map_.values()) {
261 cached_images_for_id.remove_if([](auto item) { return !item.value->needed; });
262 }
263 map_.remove_if([](auto item) { return item.value.is_empty(); });
264
265 /* Second, reset the needed status of the remaining cached images to false to ready them to
266 * track their needed status for the next evaluation. */
267 for (auto &cached_images_for_id : map_.values()) {
268 for (auto &value : cached_images_for_id.values()) {
269 value->needed = false;
270 }
271 }
272}
273
275 Image *image,
276 const ImageUser *image_user,
277 const char *pass_name)
278{
279 if (!image || !image_user) {
280 return nullptr;
281 }
282
283 /* Compute the effective frame number of the image if it was animated. */
284 ImageUser image_user_for_frame = *image_user;
285 BKE_image_user_frame_calc(image, &image_user_for_frame, context.get_frame_number());
286
287 const CachedImageKey key(image_user_for_frame, pass_name);
288
289 const std::string library_key = image->id.lib ? image->id.lib->id.name : "";
290 const std::string id_key = std::string(image->id.name) + library_key;
291 auto &cached_images_for_id = map_.lookup_or_add_default(id_key);
292
293 /* Invalidate the cache for that image ID if it was changed and reset the recalculate flag. */
294 if (context.query_id_recalc_flag(reinterpret_cast<ID *>(image)) & ID_RECALC_ALL) {
295 cached_images_for_id.clear();
296 }
297
298 auto &cached_image = *cached_images_for_id.lookup_or_add_cb(key, [&]() {
299 return std::make_unique<CachedImage>(context, image, &image_user_for_frame, pass_name);
300 });
301
302 cached_image.needed = true;
303 return &cached_image.result;
304}
305
306} // namespace blender::realtime_compositor
ImBuf * BKE_image_acquire_ibuf(Image *ima, ImageUser *iuser, void **r_lock)
bool BKE_image_is_multilayer(const Image *ima)
void BKE_image_user_frame_calc(Image *ima, ImageUser *iuser, int cfra)
RenderPass * BKE_image_multilayer_index(RenderResult *rr, ImageUser *iuser)
void BKE_image_release_ibuf(Image *ima, ImBuf *ibuf, void *lock)
bool BKE_image_is_multiview(const Image *ima)
void BKE_image_multiview_index(const Image *ima, ImageUser *iuser)
void * BLI_findlink(const struct ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_listbase_count_at_most(const struct ListBase *listbase, int count_max) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
int BLI_findstringindex(const struct ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
ID and Library types, which are fundamental for SDNA.
@ ID_RECALC_ALL
Definition DNA_ID.h:1155
#define GPU_TEXTURE_FREE_SAFE(texture)
void GPU_texture_update_mipmap_chain(GPUTexture *texture)
bool IMB_colormanagement_space_is_data(ColorSpace *colorspace)
bool IMB_colormanagement_space_is_scene_linear(ColorSpace *colorspace)
void IMB_assign_dds_data(ImBuf *ibuf, const DDSData &data, ImBufOwnership ownership)
void IMB_assign_float_buffer(ImBuf *ibuf, float *buffer_data, ImBufOwnership ownership)
void IMB_assign_byte_buffer(ImBuf *ibuf, uint8_t *buffer_data, ImBufOwnership ownership)
GPUTexture * IMB_create_gpu_texture(const char *name, ImBuf *ibuf, bool use_high_bitdepth, bool use_premult)
Definition util_gpu.cc:312
void IMB_float_from_rect(ImBuf *ibuf)
Definition divers.cc:802
@ IMB_FTYPE_DDS
Contains defines and structs used throughout the imbuf module.
@ IB_DO_NOT_TAKE_OWNERSHIP
@ IB_halffloat
Result * get(Context &context, Image *image, const ImageUser *image_user, const char *pass_name)
CachedImageKey(ImageUser image_user, std::string pass_name)
CachedImage(Context &context, Image *image, ImageUser *image_user, const char *pass_name)
float4 load_pixel(const int2 &texel) const
Definition result.cc:723
void wrap_external(GPUTexture *texture)
Definition result.cc:321
static ResultType float_type(const int channels_count)
Definition result.cc:174
local_group_size(16, 16) .push_constant(Type b
#define offsetof(t, d)
struct ImBuf * IMB_allocImBuf(unsigned int, unsigned int, unsigned char, unsigned int)
void IMB_freeImBuf(ImBuf *)
static ImBuf * compute_linear_buffer(ImBuf *image_buffer)
static int get_pass_index(Image *image, ImageUser &image_user, const char *name)
static ImageUser compute_image_user_for_pass(Context &context, Image *image, const ImageUser *image_user, const char *pass_name)
static int get_view_index(Context &context, Image *image, ImageUser &image_user)
void parallel_for(const int2 range, FunctionRef< void(int2)> function)
Definition utilities.cc:252
static RenderLayer * get_render_layer(Image *image, ImageUser &image_user)
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
VecBase< int32_t, 2 > int2
uint64_t get_default_hash(const T &v)
Definition BLI_hash.hh:219
unsigned __int64 uint64_t
Definition stdint.h:90
Definition DNA_ID.h:413
ColorSpace * colorspace
DDSData dds_data
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
unsigned char planes
enum eImbFileType ftype
ListBase passes
Definition RE_pipeline.h:97