Blender V5.0
source_image_cache.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BLI_map.hh"
10#include "BLI_mutex.hh"
11#include "BLI_vector.hh"
12
13#include "DNA_scene_types.h"
14#include "DNA_sequence_types.h"
15
16#include "IMB_imbuf.hh"
17
18#include "SEQ_relations.hh"
19#include "SEQ_render.hh"
20#include "SEQ_time.hh"
21
22#include "prefetch.hh"
23#include "source_image_cache.hh"
24
25namespace blender::seq {
26
28
30 struct FrameEntry {
31 ImBuf *image = nullptr;
38 float strip_frame = 0;
39 };
40
45
47
49 {
50 clear();
51 }
52
53 void clear()
54 {
55 for (const auto &item : map_.items()) {
56 for (const auto &frame : item.value.frames.values()) {
57 IMB_freeImBuf(frame.image);
58 }
59 }
60 map_.clear();
61 }
62
63 void remove_entry(const Strip *strip)
64 {
65 StripEntry *entry = map_.lookup_ptr(strip);
66 if (entry == nullptr) {
67 return;
68 }
69 for (const auto &frame : entry->frames.values()) {
70 IMB_freeImBuf(frame.image);
71 }
72 map_.remove_contained(strip);
73 }
74};
75
77{
79 if (*cache == nullptr) {
80 *cache = MEM_new<SourceImageCache>(__func__);
81 }
82 return *cache;
83}
84
86{
87 if (scene == nullptr || scene->ed == nullptr) {
88 return nullptr;
89 }
90 return scene->ed->runtime.source_image_cache;
91}
92
93static float give_cache_frame_index(const Scene *scene, const Strip *strip, float timeline_frame)
94{
95 float frame_index = give_frame_index(scene, strip, timeline_frame);
96 if (strip->type != STRIP_TYPE_SCENE) {
97 /* Scene strips that are slowed down need fractional frame index for animation interpolation;
98 * for others use integer index for better cache hit rates. */
99 frame_index = std::trunc(frame_index);
100 }
101 if (strip->type == STRIP_TYPE_MOVIE) {
102 frame_index += strip->anim_startofs;
103 }
104 return frame_index;
105}
106
107ImBuf *source_image_cache_get(const RenderData *context, const Strip *strip, float timeline_frame)
108{
109 if (context->skip_cache || context->is_proxy_render || strip == nullptr) {
110 return nullptr;
111 }
112
113 Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
114 timeline_frame = math::round(timeline_frame);
115 const float frame_index = give_cache_frame_index(scene, strip, timeline_frame);
116 const int view_id = context->view_id;
117
118 ImBuf *res = nullptr;
119 {
120 std::lock_guard lock(source_image_cache_mutex);
122 if (cache == nullptr) {
123 return nullptr;
124 }
125
126 SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
127 if (val == nullptr) {
128 /* Nothing in cache for this strip yet. */
129 return nullptr;
130 }
131 /* Search entries for the frame we want. */
132 SourceImageCache::FrameEntry *frame = val->frames.lookup_ptr({frame_index, view_id});
133 if (frame != nullptr) {
134 res = frame->image;
135 }
136
137 /* For effect and scene strips, check if the cached result matches our current
138 * render resolution. If it does not, remove stale source entries for this strip. */
139 if (res != nullptr && (strip->is_effect() || strip->type == STRIP_TYPE_SCENE)) {
140 if (res->x != context->rectx || res->y != context->recty) {
141 cache->remove_entry(strip);
142 return nullptr;
143 }
144 }
145 }
146
147 if (res) {
148 IMB_refImBuf(res);
149 }
150 return res;
151}
152
154 const Strip *strip,
155 float timeline_frame,
156 ImBuf *image)
157{
158 if (context->skip_cache || context->is_proxy_render || strip == nullptr || image == nullptr) {
159 return;
160 }
161
162 Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
163 timeline_frame = math::round(timeline_frame);
164
165 const float frame_index = give_cache_frame_index(scene, strip, timeline_frame);
166 const int view_id = context->view_id;
167
168 IMB_refImBuf(image);
169
170 std::lock_guard lock(source_image_cache_mutex);
172
173 SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
174
175 if (val == nullptr) {
176 /* Nothing in cache for this strip yet. */
177 cache->map_.add_new(strip, {});
178 val = cache->map_.lookup_ptr(strip);
179 }
180 BLI_assert_msg(val != nullptr, "Source image cache value should never be null here");
181
182 SourceImageCache::FrameEntry &frame = val->frames.lookup_or_add_default({frame_index, view_id});
183 if (frame.image != nullptr) {
184 IMB_freeImBuf(frame.image);
185 }
186 frame.strip_frame = timeline_frame - strip->start;
187 frame.image = image;
188}
189
191{
192 std::lock_guard lock(source_image_cache_mutex);
194 if (cache != nullptr) {
195 cache->remove_entry(strip);
196 }
197}
198
200{
201 std::lock_guard lock(source_image_cache_mutex);
203 if (cache != nullptr) {
205 }
206}
207
209{
210 std::lock_guard lock(source_image_cache_mutex);
212 if (cache != nullptr) {
213 BLI_assert(cache == scene->ed->runtime.source_image_cache);
214 MEM_delete(scene->ed->runtime.source_image_cache);
215 scene->ed->runtime.source_image_cache = nullptr;
216 }
217}
218
220 void *userdata,
221 void callback_iter(void *userdata,
222 const Strip *strip,
223 int timeline_frame))
224{
225 std::lock_guard lock(source_image_cache_mutex);
227 if (cache == nullptr) {
228 return;
229 }
230
231 for (const auto &[strip, frames] : cache->map_.items()) {
232 for (const auto &[frame_key, frame] : frames.frames.items()) {
233 const float timeline_frame = strip->start + frame.strip_frame;
234 callback_iter(userdata, strip, int(timeline_frame));
235 }
236 }
237}
238
240{
241 std::lock_guard lock(source_image_cache_mutex);
243 if (cache == nullptr) {
244 return 0;
245 }
246 size_t size = 0;
247 for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
248 for (const SourceImageCache::FrameEntry &frame : entry.frames.values()) {
249 size += IMB_get_size_in_memory(frame.image);
250 }
251 }
252 return size;
253}
254
256{
257 std::lock_guard lock(source_image_cache_mutex);
259 if (cache == nullptr) {
260 return 0;
261 }
262 size_t count = 0;
263 for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
264 count += entry.frames.size();
265 }
266 return count;
267}
268
270{
271 std::lock_guard lock(source_image_cache_mutex);
273 if (cache == nullptr) {
274 return false;
275 }
276 /* Find which entry to remove -- we pick the one that is furthest from the current frame,
277 * biasing the ones that are behind the current frame.
278 *
279 * However, do not try to evict entries from the current prefetch job range -- we need to
280 * be able to fully fill the cache from prefetching, and then actually stop the job when it
281 * is full and no longer can evict anything. */
282 int cur_prefetch_start = std::numeric_limits<int>::min();
283 int cur_prefetch_end = std::numeric_limits<int>::min();
284 if (scene->ed->cache_flag & SEQ_CACHE_STORE_RAW) {
285 /* Only activate the prefetch guards if the cache is active. */
286 seq_prefetch_get_time_range(scene, &cur_prefetch_start, &cur_prefetch_end);
287 }
288 const bool prefetch_loops_around = cur_prefetch_start > cur_prefetch_end;
289
290 const int timeline_start = PSFRA;
291 const int timeline_end = PEFRA;
292 /* If we wrap around, treat the timeline start as the playback head position.
293 * This is to try to mitigate un-needed cache evictions. */
294 const int cur_frame = prefetch_loops_around ? timeline_start : scene->r.cfra;
295
296 SourceImageCache::StripEntry *best_strip = nullptr;
297 std::pair<float, int> best_key = {};
298 int best_score = 0;
299 for (const auto &strip : cache->map_.items()) {
300 for (const auto &entry : strip.value.frames.items()) {
301 const int item_frame = int(strip.key->start + entry.value.strip_frame);
302 if (prefetch_loops_around) {
303 if (item_frame >= timeline_start && item_frame <= cur_prefetch_end) {
304 continue; /* Within active prefetch range, do not try to remove it. */
305 }
306 if (item_frame >= cur_prefetch_start && item_frame <= timeline_end) {
307 continue; /* Within active prefetch range, do not try to remove it. */
308 }
309 }
310 else if (item_frame >= cur_prefetch_start && item_frame <= cur_prefetch_end) {
311 continue; /* Within active prefetch range, do not try to remove it. */
312 }
313
314 /* Score for removal is distance to current frame; 2x that if behind current frame. */
315 int score = 0;
316 if (item_frame < cur_frame) {
317 score = (cur_frame - item_frame) * 2;
318 }
319 else if (item_frame > cur_frame) {
320 score = item_frame - cur_frame;
321 }
322 if (score > best_score) {
323 best_strip = &strip.value;
324 best_key = entry.key;
325 best_score = score;
326 }
327 }
328 }
329
330 /* Remove if we found one. */
331 if (best_strip != nullptr) {
332 IMB_freeImBuf(best_strip->frames.lookup(best_key).image);
333 best_strip->frames.remove(best_key);
334 return true;
335 }
336
337 return false;
338}
339
340} // namespace blender::seq
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define PSFRA
#define PEFRA
@ SEQ_CACHE_STORE_RAW
@ STRIP_TYPE_SCENE
@ STRIP_TYPE_MOVIE
void IMB_freeImBuf(ImBuf *ibuf)
void IMB_refImBuf(ImBuf *ibuf)
size_t IMB_get_size_in_memory(const ImBuf *ibuf)
volatile int lock
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
int count
T round(const T &a)
void source_image_cache_put(const RenderData *context, const Strip *strip, float timeline_frame, ImBuf *image)
static float give_cache_frame_index(const Scene *scene, const Strip *strip, float timeline_frame)
void source_image_cache_iterate(Scene *scene, void *userdata, void callback_iter(void *userdata, const Strip *strip, int timeline_frame))
float give_frame_index(const Scene *scene, const Strip *strip, float timeline_frame)
Definition strip_time.cc:52
bool source_image_cache_evict(Scene *scene)
void source_image_cache_clear(Scene *scene)
void source_image_cache_invalidate_strip(Scene *scene, const Strip *strip)
size_t source_image_cache_get_image_count(const Scene *scene)
static SourceImageCache * query_source_image_cache(const Scene *scene)
ImBuf * source_image_cache_get(const RenderData *context, const Strip *strip, float timeline_frame)
Scene * prefetch_get_original_scene_and_strip(const RenderData *context, const Strip *&strip)
Definition prefetch.cc:173
void source_image_cache_destroy(Scene *scene)
static Mutex source_image_cache_mutex
size_t source_image_cache_calc_memory_size(const Scene *scene)
static SourceImageCache * ensure_source_image_cache(Scene *scene)
void seq_prefetch_get_time_range(Scene *scene, int *r_start, int *r_end)
Definition prefetch.cc:209
std::mutex Mutex
Definition BLI_mutex.hh:47
SourceImageCache * source_image_cache
EditingRuntime runtime
struct Editing * ed
struct RenderData r
Map< std::pair< float, int >, FrameEntry > frames
void remove_entry(const Strip *strip)
Map< const Strip *, StripEntry > map_