Blender V5.0
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#include <string>
8
9#include "BLI_hash.hh"
10#include "BLI_listbase.h"
11#include "BLI_string.h"
12#include "BLI_string_ref.hh"
13
14#include "RE_pipeline.h"
15
16#include "GPU_texture.hh"
17
19#include "IMB_imbuf.hh"
20#include "IMB_imbuf_types.hh"
21
22#include "BKE_cryptomatte.hh"
23#include "BKE_image.hh"
24#include "BKE_lib_id.hh"
25
26#include "DNA_ID.h"
27#include "DNA_image_types.h"
28
29#include "COM_cached_image.hh"
30#include "COM_context.hh"
31#include "COM_result.hh"
32#include "COM_utilities.hh"
33
34namespace blender::compositor {
35
36/* --------------------------------------------------------------------
37 * Cached Image Key.
38 */
39
44
49
51{
52 return a.image_user.framenr == b.image_user.framenr &&
53 a.image_user.layer == b.image_user.layer && a.image_user.view == b.image_user.view &&
54 a.pass_name == b.pass_name;
55}
56
57/* --------------------------------------------------------------------
58 * Cached Image.
59 */
60
61/* Get the render layer in the given render result specified by the given image user. */
62static RenderLayer *get_render_layer(const RenderResult *render_result,
63 const ImageUser &image_user)
64{
65 const ListBase *layers = &render_result->layers;
66 return static_cast<RenderLayer *>(BLI_findlink(layers, image_user.layer));
67}
68
69/* Get the index of the pass with the given name in the render layer specified by the given image
70 * user in the given render result. */
71static int get_pass_index(const RenderResult *render_result,
72 const ImageUser &image_user,
73 const char *name)
74{
75 const RenderLayer *render_layer = get_render_layer(render_result, image_user);
76 return BLI_findstringindex(&render_layer->passes, name, offsetof(RenderPass, name));
77}
78
79/* Get the render pass in the given render layer specified by the given image user. */
80static RenderPass *get_render_pass(const RenderLayer *render_layer, const ImageUser &image_user)
81{
82 return static_cast<RenderPass *>(BLI_findlink(&render_layer->passes, image_user.pass));
83}
84
85/* Get the index of the view selected in the image user. If the image is not a multi-view image
86 * or only has a single view, then zero is returned. Otherwise, if the image is a multi-view
87 * image, the index of the selected view is returned. However, note that the value of the view
88 * member of the image user is not the actual index of the view. More specifically, the index 0
89 * is reserved to denote the special mode of operation "All", which dynamically selects the view
90 * whose name matches the view currently being rendered. It follows that the views are then
91 * indexed starting from 1. So for non zero view values, the actual index of the view is the
92 * value of the view member of the image user minus 1. */
93static int get_view_index(const Context &context,
94 const RenderResult *render_result,
95 const ImageUser &image_user)
96{
97 /* The image is not a multi-view image, so just return zero. */
98 if (!render_result) {
99 return 0;
100 }
101
102 const ListBase *views = &render_result->views;
103 /* There is only one view and its index is 0. */
104 if (BLI_listbase_count_at_most(views, 2) < 2) {
105 return 0;
106 }
107
108 const int view = image_user.view;
109 /* The view is not zero, which means it is manually specified and the actual index is then the
110 * view value minus 1. */
111 if (view != 0) {
112 return view - 1;
113 }
114
115 /* Otherwise, the view value is zero, denoting the special mode of operation "All", which finds
116 * the index of the view whose name matches the view currently being rendered. */
117 const char *view_name = context.get_view_name().data();
118 const int matched_view = BLI_findstringindex(views, view_name, offsetof(RenderView, name));
119
120 /* No view matches the view currently being rendered, so fallback to the first view. */
121 if (matched_view == -1) {
122 return 0;
123 }
124
125 return matched_view;
126}
127
128/* Get a copy of the image user that is appropriate to retrieve the needed image buffer from the
129 * image. This essentially sets the appropriate frame, pass, and view that corresponds to the
130 * given context and pass name. If the image is a multi-layer image, then the render_result
131 * argument should be set, otherwise, it is ignored. */
133 const Image *image,
134 const RenderResult *render_result,
135 const ImageUser *image_user,
136 const char *pass_name)
137{
138 ImageUser image_user_for_pass = *image_user;
139
140 /* Set the needed view. */
141 image_user_for_pass.view = get_view_index(context, render_result, image_user_for_pass);
142
143 /* Set the needed pass. */
144 if (BKE_image_is_multilayer(image)) {
145 image_user_for_pass.pass = get_pass_index(render_result, image_user_for_pass, pass_name);
146 BKE_image_multilayer_index(const_cast<RenderResult *>(render_result), &image_user_for_pass);
147 }
148 else {
149 BKE_image_multiview_index(image, &image_user_for_pass);
150 }
151
152 return image_user_for_pass;
153}
154
155/* The image buffer might be stored as an sRGB 8-bit image, while the compositor expects linear
156 * float images, so compute a linear float buffer for the image buffer. This will also do linear
157 * space conversion and alpha pre-multiplication as needed. We could store those images in sRGB GPU
158 * textures and let the GPU do the linear space conversion, but the issues is that we don't control
159 * how the GPU does the conversion and so we get tiny differences across CPU and GPU compositing,
160 * and potentially even across GPUs/Drivers. Further, if alpha pre-multiplication is needed, we
161 * would need to do it ourself, which means alpha pre-multiplication will happen before linear
162 * space conversion, which would produce yet another difference. So we just do everything on the
163 * CPU, since this is already a cached resource.
164 *
165 * To avoid conflicts with other threads, create a new image buffer and assign all the necessary
166 * information to it, with IB_DO_NOT_TAKE_OWNERSHIP for buffers since a deep copy is not needed.
167 *
168 * The caller should free the returned image buffer. */
169static ImBuf *compute_linear_buffer(ImBuf *image_buffer)
170{
171 /* Do not pass the flags to the allocation function to avoid buffer allocation, but assign them
172 * after to retain important information like precision and alpha mode. */
173 ImBuf *linear_image_buffer = IMB_allocImBuf(
174 image_buffer->x, image_buffer->y, image_buffer->planes, 0);
175 linear_image_buffer->flags = image_buffer->flags;
176
177 /* Assign the float buffer if it exists, as well as its number of channels. */
179 linear_image_buffer, image_buffer->float_buffer, IB_DO_NOT_TAKE_OWNERSHIP);
180 linear_image_buffer->channels = image_buffer->channels;
181
182 /* If no float buffer exists, assign it then compute a float buffer from it. This is the main
183 * call of this function. */
184 if (!linear_image_buffer->float_buffer.data) {
186 linear_image_buffer, image_buffer->byte_buffer, IB_DO_NOT_TAKE_OWNERSHIP);
187 IMB_float_from_byte(linear_image_buffer);
188 }
189
190 /* If the image buffer contained compressed data, assign them as well, but only if the color
191 * space of the buffer is linear or data, since we need linear data and can't preprocess the
192 * compressed buffer. If not, we fallback to the float buffer already assigned, which is
193 * guaranteed to exist as a fallback for compressed textures. */
194 const bool is_suitable_compressed_color_space =
197 if (image_buffer->ftype == IMB_FTYPE_DDS && is_suitable_compressed_color_space) {
198 linear_image_buffer->ftype = IMB_FTYPE_DDS;
199 IMB_assign_dds_data(linear_image_buffer, image_buffer->dds_data, IB_DO_NOT_TAKE_OWNERSHIP);
200 }
201
202 return linear_image_buffer;
203}
204
205/* Returns the appropriate result type for the given image buffer, which represents the pass in the
206 * given render result with the given image user. The type is determined based on the channels
207 * count of the buffer for simple images, while channel IDs are also considered for multi-layer
208 * images since 3-channel passes can be RGB without alpha and 4-channel passes can be XYZW 4D
209 * vectors. */
210static ResultType get_result_type(const RenderResult *render_result,
211 const ImageUser &image_user,
212 const ImBuf *image_buffer)
213{
214 if (!render_result) {
215 return Result::float_type(image_buffer->channels);
216 }
217
218 const RenderLayer *render_layer = get_render_layer(render_result, image_user);
219 if (!render_layer) {
220 return Result::float_type(image_buffer->channels);
221 }
222
223 const RenderPass *render_pass = get_render_pass(render_layer, image_user);
224 if (!render_pass) {
225 return Result::float_type(image_buffer->channels);
226 }
227
228 switch (render_pass->channels) {
229 case 1:
230 return ResultType::Float;
231 case 2:
232 return ResultType::Float2;
233 case 3:
234 if (STR_ELEM(render_pass->chan_id, "RGB", "rgb")) {
235 return ResultType::Color;
236 }
237 else {
238 return ResultType::Float3;
239 }
240 case 4:
241 if (STR_ELEM(render_pass->chan_id, "RGBA", "rgba")) {
242 return ResultType::Color;
243 }
244 else {
245 return ResultType::Float4;
246 }
247 default:
248 break;
249 }
250
252 return ResultType::Float;
253}
254
256 Image *image,
257 ImageUser *image_user,
258 const char *pass_name)
259 : result(context)
260{
261 /* We can't retrieve the needed image buffer yet, because we still need to assign the pass index
262 * to the image user in order to acquire the image buffer corresponding to the given pass name.
263 * However, in order to compute the pass index, we need the render result structure of the image
264 * to be initialized. So we first acquire a dummy image buffer since it initializes the image
265 * render result as a side effect. We also use that as a mean of validation, since we can early
266 * exit if the returned image buffer is nullptr. This image buffer can be immediately released.
267 * Since it carries no important information. */
268 ImBuf *initial_image_buffer = BKE_image_acquire_ibuf(image, image_user, nullptr);
269 BKE_image_release_ibuf(image, initial_image_buffer, nullptr);
270 if (!initial_image_buffer) {
271 return;
272 }
273
274 RenderResult *render_result = BKE_image_acquire_renderresult(nullptr, image);
275
276 ImageUser image_user_for_pass = compute_image_user_for_pass(
277 context, image, render_result, image_user, pass_name);
278
279 this->populate_meta_data(render_result, image_user_for_pass);
280
281 BKE_image_release_renderresult(nullptr, image, render_result);
282
283 ImBuf *image_buffer = BKE_image_acquire_ibuf(image, &image_user_for_pass, nullptr);
284 ImBuf *linear_image_buffer = compute_linear_buffer(image_buffer);
285
286 const bool use_half_float = linear_image_buffer->foptions.flag & OPENEXR_HALF;
287 this->result.set_precision(use_half_float ? ResultPrecision::Half : ResultPrecision::Full);
288
289 this->result.set_type(get_result_type(render_result, image_user_for_pass, linear_image_buffer));
290
291 /* For GPU, we wrap the texture returned by IMB module and free it ourselves in destructor. For
292 * CPU, we allocate the result and copy to it from the image buffer. */
293 if (context.use_gpu()) {
294 texture_ = IMB_create_gpu_texture("Image Texture", linear_image_buffer, true, true);
296 this->result.wrap_external(texture_);
297 }
298 else {
299 const int2 size = int2(image_buffer->x, image_buffer->y);
300 Result buffer_result(
301 context, Result::float_type(image_buffer->channels), ResultPrecision::Full);
302 buffer_result.wrap_external(linear_image_buffer->float_buffer.data, size);
303 this->result.allocate_texture(size, false);
304 parallel_for(size, [&](const int2 texel) {
305 this->result.store_pixel_generic_type(texel, buffer_result.load_pixel_generic_type(texel));
306 });
307 }
308
309 IMB_freeImBuf(linear_image_buffer);
310 BKE_image_release_ibuf(image, image_buffer, nullptr);
311}
312
313void CachedImage::populate_meta_data(const RenderResult *render_result,
314 const ImageUser &image_user)
315{
316 if (!render_result) {
317 return;
318 }
319
320 const RenderLayer *render_layer = get_render_layer(render_result, image_user);
321 if (!render_layer) {
322 return;
323 }
324
325 const RenderPass *render_pass = get_render_pass(render_layer, image_user);
326 if (!render_pass) {
327 return;
328 }
329
330 /* We assume the given pass is a Cryptomatte pass and retrieve its full name. If it wasn't a
331 * Cryptomatte pass, the checks below will fail anyways. */
332 const bool is_named_layer = render_layer->name[0] != '\0';
333 const std::string layer_prefix = is_named_layer ? std::string(render_layer->name) + "." : "";
334 const std::string combined_pass_name = layer_prefix + render_pass->name;
335 StringRef cryptomatte_layer_name = bke::cryptomatte::BKE_cryptomatte_extract_layer_name(
336 combined_pass_name);
337
338 struct StampCallbackData {
339 std::string cryptomatte_layer_name;
340 compositor::MetaData *meta_data;
341 };
342
343 /* Go over the stamp data and add any Cryptomatte related meta data. */
344 StampCallbackData callback_data = {cryptomatte_layer_name, &this->result.meta_data};
346 &callback_data,
347 render_result->stamp_data,
348 [](void *user_data, const char *key, char *value, int /*value_length*/) {
349 StampCallbackData *data = static_cast<StampCallbackData *>(user_data);
350
351 const std::string manifest_key = bke::cryptomatte::BKE_cryptomatte_meta_data_key(
352 data->cryptomatte_layer_name, "manifest");
353 if (key == manifest_key) {
354 data->meta_data->cryptomatte.manifest = value;
355 }
356
358 data->cryptomatte_layer_name, "hash");
359 if (key == hash_key) {
360 data->meta_data->cryptomatte.hash = value;
361 }
362
363 const std::string conversion_key = bke::cryptomatte::BKE_cryptomatte_meta_data_key(
364 data->cryptomatte_layer_name, "conversion");
365 if (key == conversion_key) {
366 data->meta_data->cryptomatte.conversion = value;
367 }
368 },
369 false);
370}
371
373{
374 this->result.release();
375 GPU_TEXTURE_FREE_SAFE(texture_);
376}
377
378/* --------------------------------------------------------------------
379 * Cached Image Container.
380 */
381
383{
384 /* First, delete all cached images that are no longer needed. */
385 for (auto &cached_images_for_id : map_.values()) {
386 cached_images_for_id.remove_if([](auto item) { return !item.value->needed; });
387 }
388 map_.remove_if([](auto item) { return item.value.is_empty(); });
389 update_counts_.remove_if([&](auto item) { return !map_.contains(item.key); });
390
391 /* Second, reset the needed status of the remaining cached images to false to ready them to
392 * track their needed status for the next evaluation. */
393 for (auto &cached_images_for_id : map_.values()) {
394 for (auto &value : cached_images_for_id.values()) {
395 value->needed = false;
396 }
397 }
398}
399
401 Image *image,
402 const ImageUser *image_user,
403 const char *pass_name)
404{
405 if (!image || !image_user) {
406 return Result(context);
407 }
408
409 /* Compute the effective frame number of the image if it was animated. */
410 ImageUser image_user_for_frame = *image_user;
411 BKE_image_user_frame_calc(image, &image_user_for_frame, context.get_frame_number());
412
413 const CachedImageKey key(image_user_for_frame, pass_name);
414
415 const std::string library_key = image->id.lib ? image->id.lib->id.name : "";
416 const std::string id_key = std::string(image->id.name) + library_key;
417 auto &cached_images_for_id = map_.lookup_or_add_default(id_key);
418
419 /* Invalidate the cache for that image if it was changed since it was cached. */
420 if (!cached_images_for_id.is_empty() &&
421 image->runtime->update_count != update_counts_.lookup(id_key))
422 {
423 cached_images_for_id.clear();
424 }
425
426 auto &cached_image = *cached_images_for_id.lookup_or_add_cb(key, [&]() {
427 return std::make_unique<CachedImage>(context, image, &image_user_for_frame, pass_name);
428 });
429
430 /* Store the current update count to later compare to and check if the image changed. */
431 update_counts_.add_overwrite(id_key, image->runtime->update_count);
432
433 cached_image.needed = true;
434 return cached_image.result;
435}
436
437} // namespace blender::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)
void BKE_image_release_renderresult(Scene *scene, Image *ima, RenderResult *render_result)
void BKE_stamp_info_callback(void *data, StampData *stamp_data, StampCallback callback, bool noskip)
RenderResult * BKE_image_acquire_renderresult(Scene *scene, Image *ima)
void BKE_image_multiview_index(const Image *ima, ImageUser *iuser)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
void * BLI_findlink(const ListBase *listbase, int number) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:534
int BLI_listbase_count_at_most(const ListBase *listbase, int count_max) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:511
int BLI_findstringindex(const ListBase *listbase, const char *id, int offset) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:780
#define STR_ELEM(...)
Definition BLI_string.h:661
ID and Library types, which are fundamental for SDNA.
static AppView * view
void GPU_texture_update_mipmap_chain(blender::gpu::Texture *texture)
#define GPU_TEXTURE_FREE_SAFE(texture)
bool IMB_colormanagement_space_is_scene_linear(const ColorSpace *colorspace)
bool IMB_colormanagement_space_is_data(const ColorSpace *colorspace)
blender::gpu::Texture * IMB_create_gpu_texture(const char *name, ImBuf *ibuf, bool use_high_bitdepth, bool use_premult)
Definition util_gpu.cc:327
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_freeImBuf(ImBuf *ibuf)
ImBuf * IMB_allocImBuf(unsigned int x, unsigned int y, unsigned char planes, unsigned int flags)
void IMB_assign_byte_buffer(ImBuf *ibuf, uint8_t *buffer_data, ImBufOwnership ownership)
void IMB_float_from_byte(ImBuf *ibuf)
@ IMB_FTYPE_DDS
@ IB_DO_NOT_TAKE_OWNERSHIP
uint32_t hash_key
BMesh const char void * data
unsigned long long int uint64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
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)
void wrap_external(blender::gpu::Texture *texture)
Definition result.cc:584
static ResultType float_type(const int channels_count)
Definition result.cc:292
float4 load_pixel_generic_type(const int2 &texel) const
#define offsetof(t, d)
#define OPENEXR_HALF
if(state< num_states)
StringRef BKE_cryptomatte_extract_layer_name(StringRef render_pass_name)
std::string BKE_cryptomatte_meta_data_key(StringRef layer_name, StringRefNull key_name)
bool operator==(const BokehKernelKey &a, const BokehKernelKey &b)
static ImBuf * compute_linear_buffer(ImBuf *image_buffer)
static int get_pass_index(const RenderResult *render_result, const ImageUser &image_user, const char *name)
static RenderPass * get_render_pass(const RenderLayer *render_layer, const ImageUser &image_user)
static int get_view_index(const Context &context, const RenderResult *render_result, const ImageUser &image_user)
static ImageUser compute_image_user_for_pass(const Context &context, const Image *image, const RenderResult *render_result, const ImageUser *image_user, const char *pass_name)
static RenderLayer * get_render_layer(const RenderResult *render_result, const ImageUser &image_user)
static ResultType get_result_type(const RenderResult *render_result, const ImageUser &image_user, const ImBuf *image_buffer)
void parallel_for(const int2 range, const Function &function)
uint64_t get_default_hash(const T &v, const Args &...args)
Definition BLI_hash.hh:233
VecBase< int32_t, 2 > int2
const char * name
struct Library * lib
Definition DNA_ID.h:420
char name[258]
Definition DNA_ID.h:432
const ColorSpace * colorspace
DDSData dds_data
ImBufFloatBuffer float_buffer
ImbFormatOptions foptions
ImBufByteBuffer byte_buffer
unsigned char planes
enum eImbFileType ftype
ImageRuntimeHandle * runtime
ID id
Definition DNA_ID.h:550
ListBase passes
Definition RE_pipeline.h:94
char name[RE_MAXNAME]
Definition RE_pipeline.h:89
char chan_id[24]
Definition RE_pipeline.h:56
char name[64]
Definition RE_pipeline.h:55
ListBase views
ListBase layers
struct StampData * stamp_data