VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
|
|
|
|
|
|
/** \file
|
|
|
|
|
* \ingroup sequencer
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "BLI_map.hh"
|
|
|
|
|
#include "BLI_math_base.h"
|
2024-09-26 21:13:39 +10:00
|
|
|
#include "BLI_path_utils.hh"
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
#include "BLI_set.hh"
|
|
|
|
|
#include "BLI_task.hh"
|
|
|
|
|
#include "BLI_vector.hh"
|
|
|
|
|
|
|
|
|
|
#include "BKE_context.hh"
|
|
|
|
|
#include "BKE_main.hh"
|
|
|
|
|
|
|
|
|
|
#include "DNA_scene_types.h"
|
|
|
|
|
#include "DNA_sequence_types.h"
|
|
|
|
|
|
|
|
|
|
#include "IMB_imbuf.hh"
|
|
|
|
|
|
|
|
|
|
#include "SEQ_render.hh"
|
|
|
|
|
#include "SEQ_thumbnail_cache.hh"
|
|
|
|
|
#include "SEQ_time.hh"
|
|
|
|
|
|
|
|
|
|
#include "WM_api.hh"
|
|
|
|
|
|
|
|
|
|
#include "render.hh"
|
|
|
|
|
|
|
|
|
|
namespace blender::seq {
|
|
|
|
|
|
|
|
|
|
static constexpr int MAX_THUMBNAILS = 5000;
|
|
|
|
|
|
|
|
|
|
// #define DEBUG_PRINT_THUMB_JOB_TIMES
|
|
|
|
|
|
|
|
|
|
static std::mutex thumb_cache_mutex;
|
|
|
|
|
|
|
|
|
|
/* Thumbnail cache is a map keyed by media file path, with values being
|
|
|
|
|
* the various thumbnails that are loaded for it (mostly images would contain just
|
|
|
|
|
* one thumbnail frame, but movies can contain multiple).
|
|
|
|
|
*
|
|
|
|
|
* File entries and individual frame entries also record the timestamp when they were
|
|
|
|
|
* last accessed, so that when the cache is full, some of the old entries can be removed.
|
|
|
|
|
*
|
|
|
|
|
* Thumbnails that are requested but do not have an exact match in the cache, are added
|
|
|
|
|
* to the "requests" set. The requests are processed in the background by a WM job. */
|
|
|
|
|
struct ThumbnailCache {
|
|
|
|
|
struct FrameEntry {
|
|
|
|
|
int frame_index = 0; /* Frame index (for movies) or image index (for image sequences). */
|
|
|
|
|
int stream_index = 0; /* Stream index (only for multi-stream movies). */
|
|
|
|
|
ImBuf *thumb = nullptr;
|
|
|
|
|
int64_t used_at = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct FileEntry {
|
|
|
|
|
Vector<FrameEntry> frames;
|
|
|
|
|
int64_t used_at = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct Request {
|
|
|
|
|
explicit Request(const std::string &path,
|
|
|
|
|
int frame,
|
|
|
|
|
int stream,
|
|
|
|
|
SequenceType type,
|
|
|
|
|
int64_t logical_time,
|
|
|
|
|
float time_frame,
|
|
|
|
|
int ch,
|
|
|
|
|
int width,
|
|
|
|
|
int height)
|
|
|
|
|
: file_path(path),
|
|
|
|
|
frame_index(frame),
|
|
|
|
|
stream_index(stream),
|
|
|
|
|
seq_type(type),
|
|
|
|
|
requested_at(logical_time),
|
|
|
|
|
timeline_frame(time_frame),
|
|
|
|
|
channel(ch),
|
|
|
|
|
full_width(width),
|
|
|
|
|
full_height(height)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
/* These determine request uniqueness (for equality/hash in a Set). */
|
|
|
|
|
std::string file_path;
|
|
|
|
|
int frame_index = 0; /* Frame index (for movies) or image index (for image sequences). */
|
|
|
|
|
int stream_index = 0; /* Stream index (only for multi-stream movies). */
|
|
|
|
|
SequenceType seq_type = SEQ_TYPE_IMAGE;
|
|
|
|
|
|
|
|
|
|
/* The following members are payload and do not contribute to uniqueness. */
|
|
|
|
|
int64_t requested_at = 0;
|
|
|
|
|
float timeline_frame = 0;
|
|
|
|
|
int channel = 0;
|
|
|
|
|
int full_width = 0;
|
|
|
|
|
int full_height = 0;
|
|
|
|
|
|
|
|
|
|
uint64_t hash() const
|
|
|
|
|
{
|
|
|
|
|
return get_default_hash(file_path, frame_index, stream_index, seq_type);
|
|
|
|
|
}
|
|
|
|
|
bool operator==(const Request &o) const
|
|
|
|
|
{
|
|
|
|
|
return frame_index == o.frame_index && stream_index == o.stream_index &&
|
|
|
|
|
seq_type == o.seq_type && file_path == o.file_path;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Map<std::string, FileEntry> map_;
|
|
|
|
|
Set<Request> requests_;
|
|
|
|
|
int64_t logical_time_ = 0;
|
|
|
|
|
|
|
|
|
|
~ThumbnailCache()
|
|
|
|
|
{
|
|
|
|
|
clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void clear()
|
|
|
|
|
{
|
|
|
|
|
for (const auto &item : map_.items()) {
|
|
|
|
|
for (const auto &thumb : item.value.frames) {
|
|
|
|
|
IMB_freeImBuf(thumb.thumb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
map_.clear_and_shrink();
|
|
|
|
|
requests_.clear_and_shrink();
|
|
|
|
|
logical_time_ = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void remove_entry(const std::string &path)
|
|
|
|
|
{
|
|
|
|
|
FileEntry *entry = map_.lookup_ptr(path);
|
|
|
|
|
if (entry == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for (const auto &thumb : entry->frames) {
|
|
|
|
|
IMB_freeImBuf(thumb.thumb);
|
|
|
|
|
}
|
|
|
|
|
map_.remove_contained(path);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static ThumbnailCache *ensure_thumbnail_cache(Scene *scene)
|
|
|
|
|
{
|
|
|
|
|
ThumbnailCache **cache = &scene->ed->runtime.thumbnail_cache;
|
|
|
|
|
if (*cache == nullptr) {
|
|
|
|
|
*cache = MEM_new<ThumbnailCache>(__func__);
|
|
|
|
|
}
|
|
|
|
|
return *cache;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ThumbnailCache *query_thumbnail_cache(Scene *scene)
|
|
|
|
|
{
|
|
|
|
|
if (scene == nullptr || scene->ed == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return scene->ed->runtime.thumbnail_cache;
|
|
|
|
|
}
|
|
|
|
|
|
VSE: Faster timeline thumbnail drawing
VSE timeline, when many (hundreds/thousands) of thumbnails were visible, was
very slow to redraw. This PR makes them 3-10x faster to redraw, by stopping
doing things that are slow :) Part of #126087 thumbnail improvements task.
- No longer do mute semitransparency or corner rounding on the CPU, do it in
shader instead.
- Stop creating a separate GPU texture for each thumbnail, on every repaint,
and drawing each thumbnail as a separate draw call. Instead, put thumbnails
into a single texture atlas (using a simple shelf packing algorithm), and
draw them in batch, passing data via UBO. The atlas is still re-created every
frame, but that does not seem to be a performance issue. Thumbnails are
cropped horizontally based on how much of their parts are visible (e.g. a
narrow strip on screen), so realistically the atlas size is kinda
proportional to screen size, and ends up being just several megabytes of data
transfer between CPU -> GPU each frame.
On this Sprite Fright edit timeline view (612 visible thumbnails), time taken
to repaint the timeline window:
- Mac (M1 Max, Metal): 68.1ms -> 4.7ms
- Windows (Ryzen 5950X, RTX 3080Ti, OpenGL): 23.7ms -> 6.8ms
This also fixes a visual issue with thumbnails, where when strips are very
tall, the "rounded corners" that were poked right into the thumbnail bitmap
on the CPU were showing up due to actual bitmap being scaled up a lot.
Pull Request: https://projects.blender.org/blender/blender/pulls/126972
2024-09-03 08:25:15 +02:00
|
|
|
bool strip_can_have_thumbnail(const Scene *scene, const Sequence *seq)
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
{
|
|
|
|
|
if (scene == nullptr || scene->ed == nullptr || seq == nullptr) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (!ELEM(seq->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const StripElem *se = seq->strip->stripdata;
|
|
|
|
|
if (se->orig_height == 0 || se->orig_width == 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string get_path_from_seq(Scene *scene, const Sequence *seq, float timeline_frame)
|
|
|
|
|
{
|
|
|
|
|
char filepath[FILE_MAX];
|
|
|
|
|
filepath[0] = 0;
|
|
|
|
|
switch (seq->type) {
|
|
|
|
|
case SEQ_TYPE_IMAGE: {
|
|
|
|
|
const StripElem *s_elem = SEQ_render_give_stripelem(scene, seq, timeline_frame);
|
|
|
|
|
if (s_elem != nullptr) {
|
|
|
|
|
BLI_path_join(filepath, sizeof(filepath), seq->strip->dirpath, s_elem->filename);
|
|
|
|
|
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
|
|
|
|
}
|
|
|
|
|
} break;
|
|
|
|
|
case SEQ_TYPE_MOVIE:
|
|
|
|
|
BLI_path_join(
|
|
|
|
|
filepath, sizeof(filepath), seq->strip->dirpath, seq->strip->stripdata->filename);
|
|
|
|
|
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return filepath;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void image_size_to_thumb_size(int &r_width, int &r_height)
|
|
|
|
|
{
|
|
|
|
|
float aspect = float(r_width) / float(r_height);
|
|
|
|
|
if (r_width > r_height) {
|
|
|
|
|
r_width = SEQ_THUMB_SIZE;
|
|
|
|
|
r_height = round_fl_to_int(SEQ_THUMB_SIZE / aspect);
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
r_height = SEQ_THUMB_SIZE;
|
|
|
|
|
r_width = round_fl_to_int(SEQ_THUMB_SIZE * aspect);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-15 23:14:09 +10:00
|
|
|
static ImBuf *make_thumb_for_image(const Scene *scene, const ThumbnailCache::Request &request)
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
{
|
|
|
|
|
ImBuf *ibuf = IMB_thumb_load_image(
|
|
|
|
|
request.file_path.c_str(), SEQ_THUMB_SIZE, nullptr, IMBThumbLoadFlags::LoadLargeFiles);
|
|
|
|
|
if (ibuf == nullptr) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
/* Keep only float buffer if we have both byte & float. */
|
|
|
|
|
if (ibuf->float_buffer.data != nullptr && ibuf->byte_buffer.data != nullptr) {
|
|
|
|
|
imb_freerectImBuf(ibuf);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
seq_imbuf_to_sequencer_space(scene, ibuf, false);
|
|
|
|
|
seq_imbuf_assign_spaces(scene, ibuf);
|
|
|
|
|
return ibuf;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void scale_to_thumbnail_size(ImBuf *ibuf)
|
|
|
|
|
{
|
|
|
|
|
if (ibuf == nullptr) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
int width = ibuf->x;
|
|
|
|
|
int height = ibuf->y;
|
|
|
|
|
image_size_to_thumb_size(width, height);
|
|
|
|
|
IMB_scale(ibuf, width, height, IMBScaleFilter::Nearest, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Background job that processes in-flight thumbnail requests. */
|
|
|
|
|
class ThumbGenerationJob {
|
|
|
|
|
Scene *scene_ = nullptr;
|
|
|
|
|
ThumbnailCache *cache_ = nullptr;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
ThumbGenerationJob(Scene *scene, ThumbnailCache *cache) : scene_(scene), cache_(cache) {}
|
|
|
|
|
|
|
|
|
|
static void ensure_job(const bContext *C, ThumbnailCache *cache);
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
static void run_fn(void *customdata, wmJobWorkerStatus *worker_status);
|
|
|
|
|
static void end_fn(void *customdata);
|
|
|
|
|
static void free_fn(void *customdata);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void ThumbGenerationJob::ensure_job(const bContext *C, ThumbnailCache *cache)
|
|
|
|
|
{
|
2024-09-12 14:48:20 +02:00
|
|
|
wmWindowManager *wm = CTX_wm_manager(C);
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
wmWindow *win = CTX_wm_window(C);
|
|
|
|
|
Scene *scene = CTX_data_scene(C);
|
2024-09-12 14:48:20 +02:00
|
|
|
wmJob *wm_job = WM_jobs_get(
|
|
|
|
|
wm, win, scene, "Strip Thumbnails", eWM_JobFlag(0), WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
if (!WM_jobs_is_running(wm_job)) {
|
|
|
|
|
ThumbGenerationJob *tj = MEM_new<ThumbGenerationJob>("ThumbGenerationJob", scene, cache);
|
|
|
|
|
WM_jobs_customdata_set(wm_job, tj, free_fn);
|
|
|
|
|
WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_SEQUENCER, NC_SCENE | ND_SEQUENCER);
|
|
|
|
|
WM_jobs_callbacks(wm_job, run_fn, nullptr, nullptr, end_fn);
|
|
|
|
|
|
2024-09-12 14:48:20 +02:00
|
|
|
WM_jobs_start(wm, wm_job);
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ThumbGenerationJob::free_fn(void *customdata)
|
|
|
|
|
{
|
|
|
|
|
ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
|
|
|
|
|
MEM_delete(job);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ThumbGenerationJob::run_fn(void *customdata, wmJobWorkerStatus *worker_status)
|
|
|
|
|
{
|
|
|
|
|
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
|
|
|
|
clock_t t0 = clock();
|
|
|
|
|
std::atomic<int> total_thumbs = 0, total_images = 0, total_movies = 0;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
|
|
|
|
|
Vector<ThumbnailCache::Request> requests;
|
|
|
|
|
while (!worker_status->stop) {
|
|
|
|
|
/* Under cache mutex lock: copy all current requests into a vector for processing.
|
2024-10-03 11:59:51 +10:00
|
|
|
* NOTE: keep the requests set intact! We don't want to add new requests for same
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
* items while we are processing them. They will be removed from the set once
|
|
|
|
|
* they are finished, one by one. */
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
requests.clear();
|
|
|
|
|
requests.reserve(job->cache_->requests_.size());
|
|
|
|
|
for (const auto &request : job->cache_->requests_) {
|
|
|
|
|
requests.append(request);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (requests.is_empty()) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Sort requests by file, stream and increasing frame index. */
|
|
|
|
|
std::sort(requests.begin(),
|
|
|
|
|
requests.end(),
|
|
|
|
|
[](const ThumbnailCache::Request &a, const ThumbnailCache::Request &b) {
|
|
|
|
|
if (a.file_path != b.file_path) {
|
|
|
|
|
return a.file_path < b.file_path;
|
|
|
|
|
}
|
|
|
|
|
if (a.stream_index != b.stream_index) {
|
|
|
|
|
return a.stream_index < b.stream_index;
|
|
|
|
|
}
|
|
|
|
|
return a.frame_index < b.frame_index;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/* Process the requests in parallel. Split requests into approximately 4 groups:
|
|
|
|
|
* we don't want to go too wide since that would potentially mean that a single input
|
|
|
|
|
* movie gets assigned to more than one thread, and the thumbnail loading itself
|
|
|
|
|
* is somewhat-threaded already. */
|
|
|
|
|
int64_t grain_size = math::max<int64_t>(8, requests.size() / 4);
|
|
|
|
|
threading::parallel_for(requests.index_range(), grain_size, [&](IndexRange range) {
|
|
|
|
|
/* Often the same movie file is chopped into multiple strips next to each other.
|
|
|
|
|
* Since the requests are sorted by file path and frame index, we can reuse ImBufAnim
|
|
|
|
|
* objects between them for performance. */
|
|
|
|
|
ImBufAnim *cur_anim = nullptr;
|
|
|
|
|
std::string cur_anim_path;
|
|
|
|
|
int cur_stream = 0;
|
|
|
|
|
for (const int i : range) {
|
|
|
|
|
const ThumbnailCache::Request &request = requests[i];
|
|
|
|
|
if (worker_status->stop) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
|
|
|
|
++total_thumbs;
|
|
|
|
|
#endif
|
|
|
|
|
ImBuf *thumb = nullptr;
|
|
|
|
|
if (request.seq_type == SEQ_TYPE_IMAGE) {
|
|
|
|
|
/* Load thumbnail for an image. */
|
|
|
|
|
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
|
|
|
|
++total_images;
|
|
|
|
|
#endif
|
|
|
|
|
thumb = make_thumb_for_image(job->scene_, request);
|
|
|
|
|
}
|
|
|
|
|
else if (request.seq_type == SEQ_TYPE_MOVIE) {
|
|
|
|
|
/* Load thumbnail for an movie. */
|
|
|
|
|
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
|
|
|
|
++total_movies;
|
|
|
|
|
#endif
|
|
|
|
|
|
2024-08-29 17:16:42 +10:00
|
|
|
/* Are we switching to a different movie file / stream? */
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
if (request.file_path != cur_anim_path || request.stream_index != cur_stream) {
|
|
|
|
|
if (cur_anim != nullptr) {
|
|
|
|
|
IMB_free_anim(cur_anim);
|
|
|
|
|
cur_anim = nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cur_anim_path = request.file_path;
|
|
|
|
|
cur_stream = request.stream_index;
|
|
|
|
|
cur_anim = IMB_open_anim(cur_anim_path.c_str(), IB_rect, cur_stream, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Decode the movie frame. */
|
|
|
|
|
if (cur_anim != nullptr) {
|
|
|
|
|
thumb = IMB_anim_absolute(cur_anim, request.frame_index, IMB_TC_NONE, IMB_PROXY_NONE);
|
|
|
|
|
if (thumb != nullptr) {
|
|
|
|
|
seq_imbuf_assign_spaces(job->scene_, thumb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
BLI_assert_unreachable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
scale_to_thumbnail_size(thumb);
|
|
|
|
|
|
|
|
|
|
/* Add result into the cache (under cache mutex lock). */
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
ThumbnailCache::FileEntry *val = job->cache_->map_.lookup_ptr(request.file_path);
|
|
|
|
|
if (val != nullptr) {
|
|
|
|
|
val->used_at = math::max(val->used_at, request.requested_at);
|
|
|
|
|
val->frames.append(
|
|
|
|
|
{request.frame_index, request.stream_index, thumb, request.requested_at});
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
IMB_freeImBuf(thumb);
|
|
|
|
|
}
|
|
|
|
|
/* Remove the request from original set. */
|
|
|
|
|
job->cache_->requests_.remove(request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (thumb) {
|
|
|
|
|
worker_status->do_update = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (cur_anim != nullptr) {
|
|
|
|
|
IMB_free_anim(cur_anim);
|
|
|
|
|
cur_anim = nullptr;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
|
|
|
|
clock_t t1 = clock();
|
|
|
|
|
printf("VSE thumb job: %i thumbs (%i img, %i movie) in %.3f sec\n",
|
|
|
|
|
total_thumbs.load(),
|
|
|
|
|
total_images.load(),
|
|
|
|
|
total_movies.load(),
|
|
|
|
|
double(t1 - t0) / CLOCKS_PER_SEC);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ThumbGenerationJob::end_fn(void *customdata)
|
|
|
|
|
{
|
|
|
|
|
ThumbGenerationJob *job = static_cast<ThumbGenerationJob *>(customdata);
|
|
|
|
|
WM_main_add_notifier(NC_SCENE | ND_SEQUENCER, job->scene_);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static ImBuf *query_thumbnail(ThumbnailCache &cache,
|
|
|
|
|
const std::string &key,
|
|
|
|
|
int frame_index,
|
|
|
|
|
float timeline_frame,
|
|
|
|
|
const bContext *C,
|
|
|
|
|
const Sequence *seq)
|
|
|
|
|
{
|
|
|
|
|
int64_t cur_time = cache.logical_time_;
|
|
|
|
|
ThumbnailCache::FileEntry *val = cache.map_.lookup_ptr(key);
|
|
|
|
|
|
|
|
|
|
if (val == nullptr) {
|
|
|
|
|
/* Nothing in cache for this path yet. */
|
|
|
|
|
ThumbnailCache::FileEntry value;
|
|
|
|
|
value.used_at = cur_time;
|
|
|
|
|
cache.map_.add_new(key, value);
|
|
|
|
|
val = cache.map_.lookup_ptr(key);
|
|
|
|
|
}
|
|
|
|
|
BLI_assert_msg(val != nullptr, "Thumbnail cache value should never be null here");
|
|
|
|
|
|
|
|
|
|
/* Search thumbnail entries of this file for closest match to the frame we want. */
|
|
|
|
|
int64_t best_index = -1;
|
|
|
|
|
int best_score = INT_MAX;
|
|
|
|
|
for (int64_t index = 0; index < val->frames.size(); index++) {
|
|
|
|
|
if (seq->streamindex != val->frames[index].stream_index) {
|
|
|
|
|
continue; /* Different video stream than what we need, ignore. */
|
|
|
|
|
}
|
|
|
|
|
int score = math::abs(frame_index - val->frames[index].frame_index);
|
|
|
|
|
if (score < best_score) {
|
|
|
|
|
best_score = score;
|
|
|
|
|
best_index = index;
|
|
|
|
|
if (score == 0) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (best_score > 0) {
|
|
|
|
|
/* We do not have an exact frame match, add a thumb generation request. */
|
|
|
|
|
const StripElem *se = seq->strip->stripdata;
|
|
|
|
|
int img_width = se->orig_width;
|
|
|
|
|
int img_height = se->orig_height;
|
|
|
|
|
ThumbnailCache::Request request(key,
|
|
|
|
|
frame_index,
|
|
|
|
|
seq->streamindex,
|
|
|
|
|
SequenceType(seq->type),
|
|
|
|
|
cur_time,
|
|
|
|
|
timeline_frame,
|
|
|
|
|
seq->machine,
|
|
|
|
|
img_width,
|
|
|
|
|
img_height);
|
|
|
|
|
cache.requests_.add(request);
|
|
|
|
|
ThumbGenerationJob::ensure_job(C, &cache);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (best_index < 0) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Return the closest thumbnail fit we have so far. */
|
|
|
|
|
val->used_at = math::max(val->used_at, cur_time);
|
|
|
|
|
val->frames[best_index].used_at = math::max(val->frames[best_index].used_at, cur_time);
|
|
|
|
|
return val->frames[best_index].thumb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImBuf *thumbnail_cache_get(const bContext *C,
|
|
|
|
|
Scene *scene,
|
|
|
|
|
const Sequence *seq,
|
|
|
|
|
float timeline_frame)
|
|
|
|
|
{
|
VSE: Faster timeline thumbnail drawing
VSE timeline, when many (hundreds/thousands) of thumbnails were visible, was
very slow to redraw. This PR makes them 3-10x faster to redraw, by stopping
doing things that are slow :) Part of #126087 thumbnail improvements task.
- No longer do mute semitransparency or corner rounding on the CPU, do it in
shader instead.
- Stop creating a separate GPU texture for each thumbnail, on every repaint,
and drawing each thumbnail as a separate draw call. Instead, put thumbnails
into a single texture atlas (using a simple shelf packing algorithm), and
draw them in batch, passing data via UBO. The atlas is still re-created every
frame, but that does not seem to be a performance issue. Thumbnails are
cropped horizontally based on how much of their parts are visible (e.g. a
narrow strip on screen), so realistically the atlas size is kinda
proportional to screen size, and ends up being just several megabytes of data
transfer between CPU -> GPU each frame.
On this Sprite Fright edit timeline view (612 visible thumbnails), time taken
to repaint the timeline window:
- Mac (M1 Max, Metal): 68.1ms -> 4.7ms
- Windows (Ryzen 5950X, RTX 3080Ti, OpenGL): 23.7ms -> 6.8ms
This also fixes a visual issue with thumbnails, where when strips are very
tall, the "rounded corners" that were poked right into the thumbnail bitmap
on the CPU were showing up due to actual bitmap being scaled up a lot.
Pull Request: https://projects.blender.org/blender/blender/pulls/126972
2024-09-03 08:25:15 +02:00
|
|
|
if (!strip_can_have_thumbnail(scene, seq)) {
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
timeline_frame = math::round(timeline_frame);
|
|
|
|
|
|
|
|
|
|
const std::string key = get_path_from_seq(scene, seq, timeline_frame);
|
|
|
|
|
int frame_index = SEQ_give_frame_index(scene, seq, timeline_frame);
|
|
|
|
|
if (seq->type == SEQ_TYPE_MOVIE) {
|
|
|
|
|
frame_index += seq->anim_startofs;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ImBuf *res = nullptr;
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
ThumbnailCache *cache = ensure_thumbnail_cache(scene);
|
|
|
|
|
res = query_thumbnail(*cache, key, frame_index, timeline_frame, C, seq);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
|
IMB_refImBuf(res);
|
|
|
|
|
}
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void thumbnail_cache_invalidate_strip(Scene *scene, const Sequence *seq)
|
|
|
|
|
{
|
VSE: Faster timeline thumbnail drawing
VSE timeline, when many (hundreds/thousands) of thumbnails were visible, was
very slow to redraw. This PR makes them 3-10x faster to redraw, by stopping
doing things that are slow :) Part of #126087 thumbnail improvements task.
- No longer do mute semitransparency or corner rounding on the CPU, do it in
shader instead.
- Stop creating a separate GPU texture for each thumbnail, on every repaint,
and drawing each thumbnail as a separate draw call. Instead, put thumbnails
into a single texture atlas (using a simple shelf packing algorithm), and
draw them in batch, passing data via UBO. The atlas is still re-created every
frame, but that does not seem to be a performance issue. Thumbnails are
cropped horizontally based on how much of their parts are visible (e.g. a
narrow strip on screen), so realistically the atlas size is kinda
proportional to screen size, and ends up being just several megabytes of data
transfer between CPU -> GPU each frame.
On this Sprite Fright edit timeline view (612 visible thumbnails), time taken
to repaint the timeline window:
- Mac (M1 Max, Metal): 68.1ms -> 4.7ms
- Windows (Ryzen 5950X, RTX 3080Ti, OpenGL): 23.7ms -> 6.8ms
This also fixes a visual issue with thumbnails, where when strips are very
tall, the "rounded corners" that were poked right into the thumbnail bitmap
on the CPU were showing up due to actual bitmap being scaled up a lot.
Pull Request: https://projects.blender.org/blender/blender/pulls/126972
2024-09-03 08:25:15 +02:00
|
|
|
if (!strip_can_have_thumbnail(scene, seq)) {
|
VSE: Faster and more consistent thumbnails
Implementing part of design outlined in #126087.
- VSE thumbnail cache has a new implementation, hopefully simpler
and easier to understand.
- Instead of cache key being a VSE strip, frame index, plus
complicated logic for cache items linking etc.,
- The cache is keyed by media file path (if multiple strips
use the same input file, they will share cache entries), frame
index within media file, and any extra data (e.g. steam index
for multi-steam videos)
- Much reduced cache flickering and strange/weird thumbnail choices.
- Likewise, thumbnails no longer disappear-and-reload on operations
like Undo, dragging new video strip into timeline, or F12 render.
- Thumbnails now load faster.
- Images use dedicated/faster thumbnail loading routines when a
format can do that (e.g. JPG and EXR can).
- Movies reuse ffmpeg decoding context for neighboring strips
that use the same file (as often happens when cutting footage)
- Thumbnail requests are processed on several threads now too.
Images and more detail in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/126405
2024-08-29 08:27:12 +02:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
|
|
|
|
if (cache != nullptr) {
|
|
|
|
|
if (ELEM((seq)->type, SEQ_TYPE_MOVIE, SEQ_TYPE_IMAGE)) {
|
|
|
|
|
const StripElem *elem = seq->strip->stripdata;
|
|
|
|
|
if (elem != nullptr) {
|
|
|
|
|
int paths_count = 1;
|
|
|
|
|
if (seq->type == SEQ_TYPE_IMAGE) {
|
|
|
|
|
/* Image strip has array of file names. */
|
|
|
|
|
paths_count = int(MEM_allocN_len(elem) / sizeof(*elem));
|
|
|
|
|
}
|
|
|
|
|
char filepath[FILE_MAX];
|
|
|
|
|
const char *basepath = seq->scene ? ID_BLEND_PATH_FROM_GLOBAL(&seq->scene->id) :
|
|
|
|
|
BKE_main_blendfile_path_from_global();
|
|
|
|
|
for (int i = 0; i < paths_count; i++, elem++) {
|
|
|
|
|
BLI_path_join(filepath, sizeof(filepath), seq->strip->dirpath, elem->filename);
|
|
|
|
|
BLI_path_abs(filepath, basepath);
|
|
|
|
|
cache->remove_entry(filepath);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void thumbnail_cache_maintain_capacity(Scene *scene)
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
|
|
|
|
if (cache != nullptr) {
|
|
|
|
|
cache->logical_time_++;
|
|
|
|
|
|
|
|
|
|
/* Count total number of thumbnails, and track which one is the least recently used file. */
|
|
|
|
|
int64_t entries = 0;
|
|
|
|
|
std::string oldest_file;
|
|
|
|
|
/* Do not remove thumbnails for files used within last 10 updates. */
|
|
|
|
|
int64_t oldest_time = cache->logical_time_ - 10;
|
|
|
|
|
int64_t oldest_entries = 0;
|
|
|
|
|
for (const auto &item : cache->map_.items()) {
|
|
|
|
|
entries += item.value.frames.size();
|
|
|
|
|
if (item.value.used_at < oldest_time) {
|
|
|
|
|
oldest_file = item.key;
|
|
|
|
|
oldest_time = item.value.used_at;
|
|
|
|
|
oldest_entries = item.value.frames.size();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we're beyond capacity and have a long-unused file, remove that. */
|
|
|
|
|
if (entries > MAX_THUMBNAILS && !oldest_file.empty()) {
|
|
|
|
|
cache->remove_entry(oldest_file);
|
|
|
|
|
entries -= oldest_entries;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we're still beyond capacity, remove individual long-unused (but not within
|
|
|
|
|
* last 100 updates) individual frames. */
|
|
|
|
|
if (entries > MAX_THUMBNAILS) {
|
|
|
|
|
for (const auto &item : cache->map_.items()) {
|
|
|
|
|
for (int64_t i = 0; i < item.value.frames.size(); i++) {
|
|
|
|
|
if (item.value.frames[i].used_at < cache->logical_time_ - 100) {
|
|
|
|
|
IMB_freeImBuf(item.value.frames[i].thumb);
|
|
|
|
|
item.value.frames.remove_and_reorder(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void thumbnail_cache_discard_requests_outside(Scene *scene, const rctf &rect)
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
|
|
|
|
if (cache != nullptr) {
|
|
|
|
|
cache->requests_.remove_if([&](const ThumbnailCache::Request &request) {
|
|
|
|
|
return request.timeline_frame < rect.xmin || request.timeline_frame > rect.xmax ||
|
|
|
|
|
request.channel < rect.ymin || request.channel > rect.ymax;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void thumbnail_cache_clear(Scene *scene)
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
|
|
|
|
if (cache != nullptr) {
|
|
|
|
|
scene->ed->runtime.thumbnail_cache->clear();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void thumbnail_cache_destroy(Scene *scene)
|
|
|
|
|
{
|
|
|
|
|
std::scoped_lock lock(thumb_cache_mutex);
|
|
|
|
|
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
|
|
|
|
if (cache != nullptr) {
|
|
|
|
|
BLI_assert(cache == scene->ed->runtime.thumbnail_cache);
|
|
|
|
|
MEM_delete(scene->ed->runtime.thumbnail_cache);
|
|
|
|
|
scene->ed->runtime.thumbnail_cache = nullptr;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace blender::seq
|