Blender V4.3
thumbnail_cache.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BLI_map.hh"
10#include "BLI_math_base.h"
11#include "BLI_path_utils.hh"
12#include "BLI_set.hh"
13#include "BLI_task.hh"
14#include "BLI_vector.hh"
15
16#include "BKE_context.hh"
17#include "BKE_main.hh"
18
19#include "DNA_scene_types.h"
20#include "DNA_sequence_types.h"
21
22#include "IMB_imbuf.hh"
23
24#include "SEQ_render.hh"
26#include "SEQ_time.hh"
27
28#include "WM_api.hh"
29
30#include "render.hh"
31
32namespace blender::seq {
33
34static constexpr int MAX_THUMBNAILS = 5000;
35
36// #define DEBUG_PRINT_THUMB_JOB_TIMES
37
38static std::mutex thumb_cache_mutex;
39
40/* Thumbnail cache is a map keyed by media file path, with values being
41 * the various thumbnails that are loaded for it (mostly images would contain just
42 * one thumbnail frame, but movies can contain multiple).
43 *
44 * File entries and individual frame entries also record the timestamp when they were
45 * last accessed, so that when the cache is full, some of the old entries can be removed.
46 *
47 * Thumbnails that are requested but do not have an exact match in the cache, are added
48 * to the "requests" set. The requests are processed in the background by a WM job. */
50 struct FrameEntry {
51 int frame_index = 0; /* Frame index (for movies) or image index (for image sequences). */
52 int stream_index = 0; /* Stream index (only for multi-stream movies). */
53 ImBuf *thumb = nullptr;
55 };
56
61
62 struct Request {
63 explicit Request(const std::string &path,
64 int frame,
65 int stream,
66 SequenceType type,
67 int64_t logical_time,
68 float time_frame,
69 int ch,
70 int width,
71 int height)
72 : file_path(path),
73 frame_index(frame),
74 stream_index(stream),
75 seq_type(type),
76 requested_at(logical_time),
77 timeline_frame(time_frame),
78 channel(ch),
79 full_width(width),
80 full_height(height)
81 {
82 }
83 /* These determine request uniqueness (for equality/hash in a Set). */
84 std::string file_path;
85 int frame_index = 0; /* Frame index (for movies) or image index (for image sequences). */
86 int stream_index = 0; /* Stream index (only for multi-stream movies). */
88
89 /* The following members are payload and do not contribute to uniqueness. */
91 float timeline_frame = 0;
92 int channel = 0;
93 int full_width = 0;
94 int full_height = 0;
95
96 uint64_t hash() const
97 {
98 return get_default_hash(file_path, frame_index, stream_index, seq_type);
99 }
100 bool operator==(const Request &o) const
101 {
102 return frame_index == o.frame_index && stream_index == o.stream_index &&
103 seq_type == o.seq_type && file_path == o.file_path;
104 }
105 };
106
110
112 {
113 clear();
114 }
115
116 void clear()
117 {
118 for (const auto &item : map_.items()) {
119 for (const auto &thumb : item.value.frames) {
120 IMB_freeImBuf(thumb.thumb);
121 }
122 }
123 map_.clear_and_shrink();
124 requests_.clear_and_shrink();
125 logical_time_ = 0;
126 }
127
128 void remove_entry(const std::string &path)
129 {
130 FileEntry *entry = map_.lookup_ptr(path);
131 if (entry == nullptr) {
132 return;
133 }
134 for (const auto &thumb : entry->frames) {
135 IMB_freeImBuf(thumb.thumb);
136 }
137 map_.remove_contained(path);
138 }
139};
140
142{
143 ThumbnailCache **cache = &scene->ed->runtime.thumbnail_cache;
144 if (*cache == nullptr) {
145 *cache = MEM_new<ThumbnailCache>(__func__);
146 }
147 return *cache;
148}
149
151{
152 if (scene == nullptr || scene->ed == nullptr) {
153 return nullptr;
154 }
155 return scene->ed->runtime.thumbnail_cache;
156}
157
158bool strip_can_have_thumbnail(const Scene *scene, const Sequence *seq)
159{
160 if (scene == nullptr || scene->ed == nullptr || seq == nullptr) {
161 return false;
162 }
163 if (!ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) {
164 return false;
165 }
166 const StripElem *se = seq->strip->stripdata;
167 if (se->orig_height == 0 || se->orig_width == 0) {
168 return false;
169 }
170 return true;
171}
172
173static std::string get_path_from_seq(Scene *scene, const Sequence *seq, float timeline_frame)
174{
175 char filepath[FILE_MAX];
176 filepath[0] = 0;
177 switch (seq->type) {
178 case SEQ_TYPE_IMAGE: {
179 const StripElem *s_elem = SEQ_render_give_stripelem(scene, seq, timeline_frame);
180 if (s_elem != nullptr) {
181 BLI_path_join(filepath, sizeof(filepath), seq->strip->dirpath, s_elem->filename);
182 BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
183 }
184 } break;
185 case SEQ_TYPE_MOVIE:
187 filepath, sizeof(filepath), seq->strip->dirpath, seq->strip->stripdata->filename);
188 BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
189 break;
190 }
191 return filepath;
192}
193
194static void image_size_to_thumb_size(int &r_width, int &r_height)
195{
196 float aspect = float(r_width) / float(r_height);
197 if (r_width > r_height) {
198 r_width = SEQ_THUMB_SIZE;
199 r_height = round_fl_to_int(SEQ_THUMB_SIZE / aspect);
200 }
201 else {
202 r_height = SEQ_THUMB_SIZE;
203 r_width = round_fl_to_int(SEQ_THUMB_SIZE * aspect);
204 }
205}
206
207static ImBuf *make_thumb_for_image(const Scene *scene, const ThumbnailCache::Request &request)
208{
210 request.file_path.c_str(), SEQ_THUMB_SIZE, nullptr, IMBThumbLoadFlags::LoadLargeFiles);
211 if (ibuf == nullptr) {
212 return nullptr;
213 }
214 /* Keep only float buffer if we have both byte & float. */
215 if (ibuf->float_buffer.data != nullptr && ibuf->byte_buffer.data != nullptr) {
216 imb_freerectImBuf(ibuf);
217 }
218
219 seq_imbuf_to_sequencer_space(scene, ibuf, false);
220 seq_imbuf_assign_spaces(scene, ibuf);
221 return ibuf;
222}
223
225{
226 if (ibuf == nullptr) {
227 return;
228 }
229 int width = ibuf->x;
230 int height = ibuf->y;
231 image_size_to_thumb_size(width, height);
232 IMB_scale(ibuf, width, height, IMBScaleFilter::Nearest, false);
233}
234
235/* Background job that processes in-flight thumbnail requests. */
237 Scene *scene_ = nullptr;
238 ThumbnailCache *cache_ = nullptr;
239
240 public:
241 ThumbGenerationJob(Scene *scene, ThumbnailCache *cache) : scene_(scene), cache_(cache) {}
242
243 static void ensure_job(const bContext *C, ThumbnailCache *cache);
244
245 private:
246 static void run_fn(void *customdata, wmJobWorkerStatus *worker_status);
247 static void end_fn(void *customdata);
248 static void free_fn(void *customdata);
249};
250
252{
254 wmWindow *win = CTX_wm_window(C);
255 Scene *scene = CTX_data_scene(C);
256 wmJob *wm_job = WM_jobs_get(
257 wm, win, scene, "Strip Thumbnails", eWM_JobFlag(0), WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
258 if (!WM_jobs_is_running(wm_job)) {
259 ThumbGenerationJob *tj = MEM_new<ThumbGenerationJob>("ThumbGenerationJob", scene, cache);
260 WM_jobs_customdata_set(wm_job, tj, free_fn);
262 WM_jobs_callbacks(wm_job, run_fn, nullptr, nullptr, end_fn);
263
264 WM_jobs_start(wm, wm_job);
265 }
266}
267
268void ThumbGenerationJob::free_fn(void *customdata)
269{
270 ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
271 MEM_delete(job);
272}
273
274void ThumbGenerationJob::run_fn(void *customdata, wmJobWorkerStatus *worker_status)
275{
276#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
277 clock_t t0 = clock();
278 std::atomic<int> total_thumbs = 0, total_images = 0, total_movies = 0;
279#endif
280
281 ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
283 while (!worker_status->stop) {
284 /* Under cache mutex lock: copy all current requests into a vector for processing.
285 * NOTE: keep the requests set intact! We don't want to add new requests for same
286 * items while we are processing them. They will be removed from the set once
287 * they are finished, one by one. */
288 {
289 std::scoped_lock lock(thumb_cache_mutex);
290 requests.clear();
291 requests.reserve(job->cache_->requests_.size());
292 for (const auto &request : job->cache_->requests_) {
293 requests.append(request);
294 }
295 }
296
297 if (requests.is_empty()) {
298 break;
299 }
300
301 /* Sort requests by file, stream and increasing frame index. */
302 std::sort(requests.begin(),
303 requests.end(),
304 [](const ThumbnailCache::Request &a, const ThumbnailCache::Request &b) {
305 if (a.file_path != b.file_path) {
306 return a.file_path < b.file_path;
307 }
308 if (a.stream_index != b.stream_index) {
309 return a.stream_index < b.stream_index;
310 }
311 return a.frame_index < b.frame_index;
312 });
313
314 /* Process the requests in parallel. Split requests into approximately 4 groups:
315 * we don't want to go too wide since that would potentially mean that a single input
316 * movie gets assigned to more than one thread, and the thumbnail loading itself
317 * is somewhat-threaded already. */
318 int64_t grain_size = math::max<int64_t>(8, requests.size() / 4);
319 threading::parallel_for(requests.index_range(), grain_size, [&](IndexRange range) {
320 /* Often the same movie file is chopped into multiple strips next to each other.
321 * Since the requests are sorted by file path and frame index, we can reuse ImBufAnim
322 * objects between them for performance. */
323 ImBufAnim *cur_anim = nullptr;
324 std::string cur_anim_path;
325 int cur_stream = 0;
326 for (const int i : range) {
327 const ThumbnailCache::Request &request = requests[i];
328 if (worker_status->stop) {
329 break;
330 }
331
332#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
333 ++total_thumbs;
334#endif
335 ImBuf *thumb = nullptr;
336 if (request.seq_type == SEQ_TYPE_IMAGE) {
337 /* Load thumbnail for an image. */
338#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
339 ++total_images;
340#endif
341 thumb = make_thumb_for_image(job->scene_, request);
342 }
343 else if (request.seq_type == SEQ_TYPE_MOVIE) {
344 /* Load thumbnail for an movie. */
345#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
346 ++total_movies;
347#endif
348
349 /* Are we switching to a different movie file / stream? */
350 if (request.file_path != cur_anim_path || request.stream_index != cur_stream) {
351 if (cur_anim != nullptr) {
352 IMB_free_anim(cur_anim);
353 cur_anim = nullptr;
354 }
355
356 cur_anim_path = request.file_path;
357 cur_stream = request.stream_index;
358 cur_anim = IMB_open_anim(cur_anim_path.c_str(), IB_rect, cur_stream, nullptr);
359 }
360
361 /* Decode the movie frame. */
362 if (cur_anim != nullptr) {
363 thumb = IMB_anim_absolute(cur_anim, request.frame_index, IMB_TC_NONE, IMB_PROXY_NONE);
364 if (thumb != nullptr) {
365 seq_imbuf_assign_spaces(job->scene_, thumb);
366 }
367 }
368 }
369 else {
370 BLI_assert_unreachable();
371 }
372
373 scale_to_thumbnail_size(thumb);
374
375 /* Add result into the cache (under cache mutex lock). */
376 {
377 std::scoped_lock lock(thumb_cache_mutex);
378 ThumbnailCache::FileEntry *val = job->cache_->map_.lookup_ptr(request.file_path);
379 if (val != nullptr) {
380 val->used_at = math::max(val->used_at, request.requested_at);
381 val->frames.append(
382 {request.frame_index, request.stream_index, thumb, request.requested_at});
383 }
384 else {
385 IMB_freeImBuf(thumb);
386 }
387 /* Remove the request from original set. */
388 job->cache_->requests_.remove(request);
389 }
390
391 if (thumb) {
392 worker_status->do_update = true;
393 }
394 }
395 if (cur_anim != nullptr) {
396 IMB_free_anim(cur_anim);
397 cur_anim = nullptr;
398 }
399 });
400 }
401
402#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
403 clock_t t1 = clock();
404 printf("VSE thumb job: %i thumbs (%i img, %i movie) in %.3f sec\n",
405 total_thumbs.load(),
406 total_images.load(),
407 total_movies.load(),
408 double(t1 - t0) / CLOCKS_PER_SEC);
409#endif
410}
411
412void ThumbGenerationJob::end_fn(void *customdata)
413{
414 ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
416}
417
419 const std::string &key,
420 int frame_index,
421 float timeline_frame,
422 const bContext *C,
423 const Sequence *seq)
424{
425 int64_t cur_time = cache.logical_time_;
426 ThumbnailCache::FileEntry *val = cache.map_.lookup_ptr(key);
427
428 if (val == nullptr) {
429 /* Nothing in cache for this path yet. */
430 ThumbnailCache::FileEntry value;
431 value.used_at = cur_time;
432 cache.map_.add_new(key, value);
433 val = cache.map_.lookup_ptr(key);
434 }
435 BLI_assert_msg(val != nullptr, "Thumbnail cache value should never be null here");
436
437 /* Search thumbnail entries of this file for closest match to the frame we want. */
438 int64_t best_index = -1;
439 int best_score = INT_MAX;
440 for (int64_t index = 0; index < val->frames.size(); index++) {
441 if (seq->streamindex != val->frames[index].stream_index) {
442 continue; /* Different video stream than what we need, ignore. */
443 }
444 int score = math::abs(frame_index - val->frames[index].frame_index);
445 if (score < best_score) {
446 best_score = score;
447 best_index = index;
448 if (score == 0) {
449 break;
450 }
451 }
452 }
453
454 if (best_score > 0) {
455 /* We do not have an exact frame match, add a thumb generation request. */
456 const StripElem *se = seq->strip->stripdata;
457 int img_width = se->orig_width;
458 int img_height = se->orig_height;
459 ThumbnailCache::Request request(key,
460 frame_index,
461 seq->streamindex,
462 SequenceType(seq->type),
463 cur_time,
464 timeline_frame,
465 seq->machine,
466 img_width,
467 img_height);
468 cache.requests_.add(request);
469 ThumbGenerationJob::ensure_job(C, &cache);
470 }
471
472 if (best_index < 0) {
473 return nullptr;
474 }
475
476 /* Return the closest thumbnail fit we have so far. */
477 val->used_at = math::max(val->used_at, cur_time);
478 val->frames[best_index].used_at = math::max(val->frames[best_index].used_at, cur_time);
479 return val->frames[best_index].thumb;
480}
481
483 Scene *scene,
484 const Sequence *seq,
485 float timeline_frame)
486{
487 if (!strip_can_have_thumbnail(scene, seq)) {
488 return nullptr;
489 }
490
491 timeline_frame = math::round(timeline_frame);
492
493 const std::string key = get_path_from_seq(scene, seq, timeline_frame);
494 int frame_index = SEQ_give_frame_index(scene, seq, timeline_frame);
495 if (seq->type == SEQ_TYPE_MOVIE) {
496 frame_index += seq->anim_startofs;
497 }
498
499 ImBuf *res = nullptr;
500 {
501 std::scoped_lock lock(thumb_cache_mutex);
503 res = query_thumbnail(*cache, key, frame_index, timeline_frame, C, seq);
504 }
505
506 if (res) {
507 IMB_refImBuf(res);
508 }
509 return res;
510}
511
513{
514 if (!strip_can_have_thumbnail(scene, seq)) {
515 return;
516 }
517
518 std::scoped_lock lock(thumb_cache_mutex);
520 if (cache != nullptr) {
521 if (ELEM((seq)->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) {
522 const StripElem *elem = seq->strip->stripdata;
523 if (elem != nullptr) {
524 int paths_count = 1;
525 if (seq->type == SEQ_TYPE_IMAGE) {
526 /* Image strip has array of file names. */
527 paths_count = int(MEM_allocN_len(elem) / sizeof(*elem));
528 }
529 char filepath[FILE_MAX];
530 const char *basepath = seq->scene ? ID_BLEND_PATH_FROM_GLOBAL(&seq->scene->id) :
532 for (int i = 0; i < paths_count; i++, elem++) {
533 BLI_path_join(filepath, sizeof(filepath), seq->strip->dirpath, elem->filename);
534 BLI_path_abs(filepath, basepath);
535 cache->remove_entry(filepath);
536 }
537 }
538 }
539 }
540}
541
543{
544 std::scoped_lock lock(thumb_cache_mutex);
546 if (cache != nullptr) {
547 cache->logical_time_++;
548
549 /* Count total number of thumbnails, and track which one is the least recently used file. */
550 int64_t entries = 0;
551 std::string oldest_file;
552 /* Do not remove thumbnails for files used within last 10 updates. */
553 int64_t oldest_time = cache->logical_time_ - 10;
554 int64_t oldest_entries = 0;
555 for (const auto &item : cache->map_.items()) {
556 entries += item.value.frames.size();
557 if (item.value.used_at < oldest_time) {
558 oldest_file = item.key;
559 oldest_time = item.value.used_at;
560 oldest_entries = item.value.frames.size();
561 }
562 }
563
564 /* If we're beyond capacity and have a long-unused file, remove that. */
565 if (entries > MAX_THUMBNAILS && !oldest_file.empty()) {
566 cache->remove_entry(oldest_file);
567 entries -= oldest_entries;
568 }
569
570 /* If we're still beyond capacity, remove individual long-unused (but not within
571 * last 100 updates) individual frames. */
572 if (entries > MAX_THUMBNAILS) {
573 for (const auto &item : cache->map_.items()) {
574 for (int64_t i = 0; i < item.value.frames.size(); i++) {
575 if (item.value.frames[i].used_at < cache->logical_time_ - 100) {
576 IMB_freeImBuf(item.value.frames[i].thumb);
577 item.value.frames.remove_and_reorder(i);
578 }
579 }
580 }
581 }
582 }
583}
584
586{
587 std::scoped_lock lock(thumb_cache_mutex);
589 if (cache != nullptr) {
590 cache->requests_.remove_if([&](const ThumbnailCache::Request &request) {
591 return request.timeline_frame < rect.xmin || request.timeline_frame > rect.xmax ||
592 request.channel < rect.ymin || request.channel > rect.ymax;
593 });
594 }
595}
596
598{
599 std::scoped_lock lock(thumb_cache_mutex);
601 if (cache != nullptr) {
602 scene->ed->runtime.thumbnail_cache->clear();
603 }
604}
605
607{
608 std::scoped_lock lock(thumb_cache_mutex);
610 if (cache != nullptr) {
611 BLI_assert(cache == scene->ed->runtime.thumbnail_cache);
612 MEM_delete(scene->ed->runtime.thumbnail_cache);
613 scene->ed->runtime.thumbnail_cache = nullptr;
614 }
615}
616
617} // namespace blender::seq
wmWindow * CTX_wm_window(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
const char * BKE_main_blendfile_path_from_global()
Definition main.cc:837
#define BLI_assert(a)
Definition BLI_assert.h:50
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:57
MINLINE int round_fl_to_int(float a)
bool BLI_path_abs(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1
#define FILE_MAX
#define BLI_path_join(...)
#define ELEM(...)
#define ID_BLEND_PATH_FROM_GLOBAL(_id)
Definition DNA_ID.h:649
@ SEQ_TYPE_IMAGE
@ SEQ_TYPE_MOVIE
void imb_freerectImBuf(ImBuf *ibuf)
void IMB_free_anim(ImBufAnim *anim)
Definition anim_movie.cc:60
void IMB_refImBuf(ImBuf *ibuf)
ImBuf * IMB_thumb_load_image(const char *filepath, const size_t max_thumb_size, char colorspace[IM_MAX_SPACE], IMBThumbLoadFlags load_flags=IMBThumbLoadFlags::Zero)
Definition readimage.cc:169
bool IMB_scale(ImBuf *ibuf, unsigned int newx, unsigned int newy, IMBScaleFilter filter, bool threaded=true)
Definition scaling.cc:779
@ WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL
Definition WM_api.hh:1607
eWM_JobFlag
Definition WM_api.hh:1559
#define ND_SEQUENCER
Definition WM_types.hh:404
#define NC_SCENE
Definition WM_types.hh:345
volatile int lock
int64_t size() const
void append(const T &value)
bool is_empty() const
IndexRange index_range() const
void reserve(const int64_t min_capacity)
static void ensure_job(const bContext *C, ThumbnailCache *cache)
ThumbGenerationJob(Scene *scene, ThumbnailCache *cache)
local_group_size(16, 16) .push_constant(Type b
#define printf
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
void IMB_freeImBuf(ImBuf *)
size_t(* MEM_allocN_len)(const void *vmemh)
Definition mallocn.cc:36
T max(const T &a, const T &b)
T abs(const T &a)
T round(const T &a)
static std::string get_path_from_seq(Scene *scene, const Sequence *seq, float timeline_frame)
void thumbnail_cache_invalidate_strip(Scene *scene, const Sequence *seq)
static constexpr int SEQ_THUMB_SIZE
static ImBuf * query_thumbnail(ThumbnailCache &cache, const std::string &key, int frame_index, float timeline_frame, const bContext *C, const Sequence *seq)
ImBuf * thumbnail_cache_get(const bContext *C, Scene *scene, const Sequence *seq, float timeline_frame)
void thumbnail_cache_clear(Scene *scene)
static ImBuf * make_thumb_for_image(const Scene *scene, const ThumbnailCache::Request &request)
static constexpr int MAX_THUMBNAILS
bool strip_can_have_thumbnail(const Scene *scene, const Sequence *seq)
static void image_size_to_thumb_size(int &r_width, int &r_height)
static void scale_to_thumbnail_size(ImBuf *ibuf)
void thumbnail_cache_discard_requests_outside(Scene *scene, const rctf &rect)
static ThumbnailCache * query_thumbnail_cache(Scene *scene)
static ThumbnailCache * ensure_thumbnail_cache(Scene *scene)
void thumbnail_cache_destroy(Scene *scene)
static std::mutex thumb_cache_mutex
void thumbnail_cache_maintain_capacity(Scene *scene)
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:95
uint64_t get_default_hash(const T &v)
Definition BLI_hash.hh:219
void seq_imbuf_to_sequencer_space(const Scene *scene, ImBuf *ibuf, bool make_float)
Definition render.cc:106
void seq_imbuf_assign_spaces(const Scene *scene, ImBuf *ibuf)
Definition render.cc:93
StripElem * SEQ_render_give_stripelem(const Scene *scene, const Sequence *seq, int timeline_frame)
Definition render.cc:248
__int64 int64_t
Definition stdint.h:89
unsigned __int64 uint64_t
Definition stdint.h:90
float SEQ_give_frame_index(const Scene *scene, const Sequence *seq, float timeline_frame)
Definition strip_time.cc:60
ImBufFloatBuffer float_buffer
ImBufByteBuffer byte_buffer
struct Scene * scene
char filename[256]
char dirpath[768]
StripElem * stripdata
bool operator==(const Request &o) const
Request(const std::string &path, int frame, int stream, SequenceType type, int64_t logical_time, float time_frame, int ch, int width, int height)
void remove_entry(const std::string &path)
Map< std::string, FileEntry > map_
float xmax
float xmin
float ymax
float ymin
void WM_main_add_notifier(uint type, void *reference)
bool WM_jobs_is_running(const wmJob *wm_job)
Definition wm_jobs.cc:317
void WM_jobs_timer(wmJob *wm_job, double time_step, uint note, uint endnote)
Definition wm_jobs.cc:352
void WM_jobs_start(wmWindowManager *wm, wmJob *wm_job)
Definition wm_jobs.cc:455
wmJob * WM_jobs_get(wmWindowManager *wm, wmWindow *win, const void *owner, const char *name, const eWM_JobFlag flag, const eWM_JobType job_type)
Definition wm_jobs.cc:189
void WM_jobs_callbacks(wmJob *wm_job, wm_jobs_start_callback startjob, void(*initjob)(void *), void(*update)(void *), void(*endjob)(void *))
Definition wm_jobs.cc:364
void WM_jobs_customdata_set(wmJob *wm_job, void *customdata, void(*free)(void *customdata))
Definition wm_jobs.cc:336