Add Chromium-only Blender WebEngine parity work
This commit is contained in:
56
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.cc
vendored
Normal file
56
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.cc
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "BLI_threads.h"
|
||||
#include "COM_result.hh"
|
||||
#include "DRW_engine.hh"
|
||||
|
||||
#include "compositor_cache.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
CompositorCache::~CompositorCache()
|
||||
{
|
||||
bool use_main_context = false;
|
||||
if (this->last_evaluation_used_gpu) {
|
||||
/* Free resources with GPU context enabled. Cleanup may happen from the main thread, and we
|
||||
* must use the main context there. */
|
||||
BLI_assert(BLI_thread_is_main() || this->secondary_gpu_context.ghost_context != nullptr);
|
||||
use_main_context = BLI_thread_is_main();
|
||||
if (use_main_context) {
|
||||
DRW_gpu_context_enable();
|
||||
}
|
||||
else {
|
||||
gpu::GPU_activate_secondary_context(this->secondary_gpu_context);
|
||||
}
|
||||
}
|
||||
|
||||
this->cache_manager.free();
|
||||
|
||||
/* See comment above on context enabling. */
|
||||
if (this->last_evaluation_used_gpu) {
|
||||
if (use_main_context) {
|
||||
DRW_gpu_context_disable();
|
||||
}
|
||||
else {
|
||||
gpu::GPU_deactivate_secondary_context(this->secondary_gpu_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompositorCache::recreate_if_needed(bool gpu,
|
||||
compositor::ResultPrecision precision,
|
||||
const gpu::GPUSecondaryContextData &gpu_context)
|
||||
{
|
||||
this->secondary_gpu_context = gpu ? gpu_context : gpu::GPUSecondaryContextData();
|
||||
|
||||
if (this->last_evaluation_used_gpu == gpu && this->last_evaluation_precision == precision) {
|
||||
return;
|
||||
}
|
||||
this->cache_manager.free();
|
||||
this->last_evaluation_used_gpu = gpu;
|
||||
this->last_evaluation_precision = precision;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
34
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.hh
vendored
Normal file
34
blender-5.2.0/source/blender/sequencer/intern/cache/compositor_cache.hh
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "COM_static_cache_manager.hh"
|
||||
#include "GPU_context.hh"
|
||||
|
||||
class GHOST_IContext;
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
class CompositorCache {
|
||||
private:
|
||||
compositor::StaticCacheManager cache_manager;
|
||||
bool last_evaluation_used_gpu = false;
|
||||
compositor::ResultPrecision last_evaluation_precision = compositor::ResultPrecision::Half;
|
||||
gpu::GPUSecondaryContextData secondary_gpu_context = {};
|
||||
|
||||
public:
|
||||
~CompositorCache();
|
||||
|
||||
compositor::StaticCacheManager &get_cache_manager()
|
||||
{
|
||||
return cache_manager;
|
||||
}
|
||||
|
||||
void recreate_if_needed(bool gpu,
|
||||
compositor::ResultPrecision precision,
|
||||
const gpu::GPUSecondaryContextData &gpu_context);
|
||||
};
|
||||
|
||||
} // namespace blender::seq
|
||||
293
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.cc
vendored
Normal file
293
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.cc
vendored
Normal file
@@ -0,0 +1,293 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_hash.hh"
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_mutex.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
#include "final_image_cache.hh"
|
||||
#include "prefetch.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static Mutex final_image_cache_mutex;
|
||||
|
||||
struct FinalImageCache {
|
||||
struct Key {
|
||||
int timeline_frame;
|
||||
int view_id;
|
||||
int display_channel;
|
||||
int2 image_size;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(timeline_frame, view_id, display_channel, image_size);
|
||||
}
|
||||
|
||||
bool operator==(const Key &other) const
|
||||
{
|
||||
return timeline_frame == other.timeline_frame && view_id == other.view_id &&
|
||||
display_channel == other.display_channel && image_size == image_size;
|
||||
}
|
||||
};
|
||||
Map<Key, ImBuf *> map_;
|
||||
|
||||
~FinalImageCache()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (ImBuf *item : map_.values()) {
|
||||
IMB_freeImBuf(item);
|
||||
}
|
||||
map_.clear();
|
||||
}
|
||||
};
|
||||
|
||||
static FinalImageCache *ensure_final_image_cache(Scene *scene)
|
||||
{
|
||||
FinalImageCache **cache = &scene->ed->runtime->final_image_cache;
|
||||
if (*cache == nullptr) {
|
||||
*cache = MEM_new<FinalImageCache>(__func__);
|
||||
}
|
||||
return *cache;
|
||||
}
|
||||
|
||||
static FinalImageCache *query_final_image_cache(const Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->final_image_cache;
|
||||
}
|
||||
|
||||
ImBuf *final_image_cache_get(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render)
|
||||
{
|
||||
if (is_render) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const FinalImageCache::Key key = {
|
||||
int(math::round(timeline_frame)), view_id, display_channel, image_size};
|
||||
|
||||
ImBuf *res = nullptr;
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
res = cache->map_.lookup_default(key, nullptr);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
IMB_refImBuf(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void final_image_cache_put(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render,
|
||||
ImBuf *image)
|
||||
{
|
||||
if (is_render) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FinalImageCache::Key key = {
|
||||
int(math::round(timeline_frame)), view_id, display_channel, image_size};
|
||||
|
||||
IMB_refImBuf(image);
|
||||
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = ensure_final_image_cache(scene);
|
||||
|
||||
cache->map_.add_or_modify(
|
||||
key,
|
||||
[&](ImBuf **value) { *value = image; },
|
||||
[&](ImBuf **existing) {
|
||||
if (*existing) {
|
||||
IMB_freeImBuf(*existing);
|
||||
}
|
||||
*existing = image;
|
||||
});
|
||||
}
|
||||
|
||||
void final_image_cache_invalidate_frame_range(Scene *scene,
|
||||
const float timeline_frame_start,
|
||||
const float timeline_frame_end)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int key_start = int(math::floor(timeline_frame_start));
|
||||
const int key_end = int(math::ceil(timeline_frame_end));
|
||||
|
||||
for (auto it = cache->map_.items().begin(); it != cache->map_.items().end(); it++) {
|
||||
const int key = (*it).key.timeline_frame;
|
||||
if (key >= key_start && key <= key_end) {
|
||||
IMB_freeImBuf((*it).value);
|
||||
cache->map_.remove(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void final_image_cache_clear(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
scene->ed->runtime->final_image_cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void final_image_cache_destroy(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
BLI_assert(cache == scene->ed->runtime->final_image_cache);
|
||||
MEM_delete(scene->ed->runtime->final_image_cache);
|
||||
scene->ed->runtime->final_image_cache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void final_image_cache_iterate(Scene *scene,
|
||||
void *userdata,
|
||||
void callback_iter(void *userdata, int timeline_frame))
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const FinalImageCache::Key &frame_view : cache->map_.keys()) {
|
||||
callback_iter(userdata, frame_view.timeline_frame);
|
||||
}
|
||||
}
|
||||
|
||||
size_t final_image_cache_calc_memory_size(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = 0;
|
||||
for (ImBuf *frame : cache->map_.values()) {
|
||||
size += IMB_get_size_in_memory(frame);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t final_image_cache_get_image_count(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return cache->map_.size();
|
||||
}
|
||||
|
||||
bool final_image_cache_evict(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(final_image_cache_mutex);
|
||||
FinalImageCache *cache = query_final_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Find which entry to remove -- we pick the one that is furthest from the current frame,
|
||||
* biasing the ones that are behind the current frame.
|
||||
*
|
||||
* However, do not try to evict entries from the current prefetch job range -- we need to
|
||||
* be able to fully fill the cache from prefetching, and then actually stop the job when it
|
||||
* is full and no longer can evict anything. */
|
||||
int cur_prefetch_start = std::numeric_limits<int>::min();
|
||||
int cur_prefetch_end = std::numeric_limits<int>::min();
|
||||
if (scene->ed->cache_flag & SEQ_CACHE_STORE_FINAL_OUT) {
|
||||
/* Only activate the prefetch guards if the cache is active. */
|
||||
seq_prefetch_get_time_range(scene, &cur_prefetch_start, &cur_prefetch_end);
|
||||
}
|
||||
const bool prefetch_loops_around = cur_prefetch_start > cur_prefetch_end;
|
||||
|
||||
const int timeline_start = scene->playback_start();
|
||||
const int timeline_end = scene->playback_end();
|
||||
/* If we wrap around, treat the timeline start as the playback head position.
|
||||
* This is to try to mitigate un-needed cache evictions. */
|
||||
const int cur_frame = prefetch_loops_around ? timeline_start : scene->r.cfra;
|
||||
|
||||
FinalImageCache::Key best_key = {};
|
||||
ImBuf *best_item = nullptr;
|
||||
int best_score = 0;
|
||||
for (const auto &item : cache->map_.items()) {
|
||||
const int item_frame = item.key.timeline_frame;
|
||||
if (prefetch_loops_around) {
|
||||
if (item_frame >= timeline_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
if (item_frame >= cur_prefetch_start && item_frame <= timeline_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
}
|
||||
else if (item_frame >= cur_prefetch_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
|
||||
/* Score for removal is distance to current frame; 2x that if behind current frame. */
|
||||
int score = 0;
|
||||
if (item_frame < cur_frame) {
|
||||
score = (cur_frame - item_frame) * 2;
|
||||
}
|
||||
else if (item_frame > cur_frame) {
|
||||
score = item_frame - cur_frame;
|
||||
}
|
||||
if (score > best_score) {
|
||||
best_key = item.key;
|
||||
best_item = item.value;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove if we found one. */
|
||||
if (best_item != nullptr) {
|
||||
IMB_freeImBuf(best_item);
|
||||
cache->map_.remove(best_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Did not find anything to remove. */
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
56
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.hh
vendored
Normal file
56
blender-5.2.0/source/blender/sequencer/intern/cache/final_image_cache.hh
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Cache of final rendered frames.
|
||||
* - Keyed by (timeline frame, view_id).
|
||||
* - When full, cache eviction policy is to remove frames furthest
|
||||
* from the current-frame, biasing towards removal of
|
||||
* frames behind the current-frame.
|
||||
* - Invalidated fairly often while editing, basically whenever any
|
||||
* strip overlapping that frame changes.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct ImBuf;
|
||||
struct Strip;
|
||||
struct Scene;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void final_image_cache_put(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render,
|
||||
ImBuf *image);
|
||||
|
||||
ImBuf *final_image_cache_get(Scene *scene,
|
||||
float timeline_frame,
|
||||
int view_id,
|
||||
int display_channel,
|
||||
int2 image_size,
|
||||
bool is_render);
|
||||
|
||||
void final_image_cache_invalidate_frame_range(Scene *scene,
|
||||
const float timeline_frame_start,
|
||||
const float timeline_frame_end);
|
||||
|
||||
void final_image_cache_clear(Scene *scene);
|
||||
void final_image_cache_destroy(Scene *scene);
|
||||
|
||||
bool final_image_cache_evict(Scene *scene);
|
||||
|
||||
size_t final_image_cache_get_image_count(const Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
192
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.cc
vendored
Normal file
192
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.cc
vendored
Normal file
@@ -0,0 +1,192 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
#include "intra_frame_cache.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct StripImageMap {
|
||||
Map<const Strip *, SeqResult> map_;
|
||||
SeqResult get(const Strip *strip) const;
|
||||
void put(const Strip *strip, const SeqResult &result);
|
||||
void invalidate(const Strip *strip);
|
||||
void clear();
|
||||
};
|
||||
|
||||
struct IntraFrameCache {
|
||||
StripImageMap preprocessed;
|
||||
StripImageMap composite;
|
||||
float timeline_frame = -1.0f;
|
||||
int view_id = -1;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
bool is_render = false;
|
||||
|
||||
~IntraFrameCache()
|
||||
{
|
||||
preprocessed.clear();
|
||||
composite.clear();
|
||||
}
|
||||
};
|
||||
|
||||
static IntraFrameCache *query_intra_frame_cache(Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->intra_frame_cache;
|
||||
}
|
||||
|
||||
void intra_frame_cache_invalidate(Scene *scene)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->preprocessed.clear();
|
||||
cache->composite.clear();
|
||||
cache->timeline_frame = -1.0f;
|
||||
cache->view_id = -1;
|
||||
cache->width = -1;
|
||||
cache->height = -1;
|
||||
cache->is_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
void intra_frame_cache_invalidate(Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (strip == nullptr) {
|
||||
return;
|
||||
}
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->preprocessed.invalidate(strip);
|
||||
cache->composite.invalidate(strip);
|
||||
}
|
||||
}
|
||||
|
||||
void StripImageMap::invalidate(const Strip *strip)
|
||||
{
|
||||
/* Invalidate this strip, and all strips that are above it. */
|
||||
for (auto it = this->map_.items().begin(); it != this->map_.items().end(); it++) {
|
||||
const Strip *key = (*it).key;
|
||||
if (key == strip || key->channel >= strip->channel) {
|
||||
IMB_freeImBuf((*it).value.image);
|
||||
this->map_.remove(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SeqResult StripImageMap::get(const Strip *strip) const
|
||||
{
|
||||
SeqResult result = this->map_.lookup_default(strip, {});
|
||||
if (result.is_valid()) {
|
||||
IMB_refImBuf(result.image);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void StripImageMap::put(const Strip *strip, const SeqResult &result)
|
||||
{
|
||||
BLI_assert(strip != nullptr);
|
||||
if (!result.is_valid()) {
|
||||
return;
|
||||
}
|
||||
SeqResult existing = this->map_.lookup_default(strip, {});
|
||||
if (existing.is_valid()) {
|
||||
IMB_freeImBuf(existing.image);
|
||||
}
|
||||
this->map_.add_overwrite(strip, result);
|
||||
IMB_refImBuf(result.image);
|
||||
}
|
||||
|
||||
void StripImageMap::clear()
|
||||
{
|
||||
for (const auto &item : this->map_.items()) {
|
||||
IMB_freeImBuf(item.value.image);
|
||||
}
|
||||
this->map_.clear();
|
||||
}
|
||||
|
||||
SeqResult intra_frame_cache_get_preprocessed(Scene *scene, const Strip *strip)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (strip == nullptr || cache == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return cache->preprocessed.get(strip);
|
||||
}
|
||||
|
||||
SeqResult intra_frame_cache_get_composite(Scene *scene, const Strip *strip)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (strip == nullptr || cache == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return cache->composite.get(strip);
|
||||
}
|
||||
|
||||
void intra_frame_cache_put_preprocessed(Scene *scene, const Strip *strip, const SeqResult &result)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr || strip == nullptr || !result.is_valid()) {
|
||||
return;
|
||||
}
|
||||
IntraFrameCache *&cache = scene->ed->runtime->intra_frame_cache;
|
||||
if (cache == nullptr) {
|
||||
cache = MEM_new<IntraFrameCache>(__func__);
|
||||
}
|
||||
cache->preprocessed.put(strip, result);
|
||||
}
|
||||
|
||||
void intra_frame_cache_put_composite(Scene *scene, const Strip *strip, const SeqResult &result)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr || strip == nullptr || !result.is_valid()) {
|
||||
return;
|
||||
}
|
||||
IntraFrameCache *&cache = scene->ed->runtime->intra_frame_cache;
|
||||
if (cache == nullptr) {
|
||||
cache = MEM_new<IntraFrameCache>(__func__);
|
||||
}
|
||||
cache->composite.put(strip, result);
|
||||
}
|
||||
|
||||
void intra_frame_cache_destroy(Scene *scene)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
MEM_SAFE_DELETE(scene->ed->runtime->intra_frame_cache);
|
||||
}
|
||||
}
|
||||
|
||||
void intra_frame_cache_set_cur_frame(
|
||||
Scene *scene, float frame, int view_id, int width, int height, bool is_render)
|
||||
{
|
||||
IntraFrameCache *cache = query_intra_frame_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
if (cache->timeline_frame != frame || cache->view_id != view_id || cache->width != width ||
|
||||
cache->height != height || cache->is_render != is_render)
|
||||
{
|
||||
cache->timeline_frame = frame;
|
||||
cache->view_id = view_id;
|
||||
cache->width = width;
|
||||
cache->height = height;
|
||||
cache->is_render = is_render;
|
||||
cache->preprocessed.clear();
|
||||
cache->composite.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
45
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.hh
vendored
Normal file
45
blender-5.2.0/source/blender/sequencer/intern/cache/intra_frame_cache.hh
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Cached intermediate images used while rendering one sequencer frame.
|
||||
* - For each strip, "preprocessed" (strip source, possibly
|
||||
* transformed, with modifiers applied) and "composite" (result of
|
||||
* blending this strip with image underneath) images are cached.
|
||||
* - Whenever going to a different frame, the cached content of previous
|
||||
* frame is cleared.
|
||||
* - Primary reason for having this cache at all, is when the whole frame
|
||||
* is a complex stack of things, and you want to tweak settings of one
|
||||
* of the involved strips. You don't want to be re-calculating all the
|
||||
* strips that are "below" your tweaked strip, for better interactivity.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Strip;
|
||||
struct Scene;
|
||||
|
||||
namespace seq {
|
||||
|
||||
SeqResult intra_frame_cache_get_preprocessed(Scene *scene, const Strip *strip);
|
||||
SeqResult intra_frame_cache_get_composite(Scene *scene, const Strip *strip);
|
||||
void intra_frame_cache_put_preprocessed(Scene *scene, const Strip *strip, const SeqResult &result);
|
||||
void intra_frame_cache_put_composite(Scene *scene, const Strip *strip, const SeqResult &result);
|
||||
|
||||
void intra_frame_cache_destroy(Scene *scene);
|
||||
|
||||
void intra_frame_cache_invalidate(Scene *scene, const Strip *strip);
|
||||
void intra_frame_cache_invalidate(Scene *scene);
|
||||
|
||||
void intra_frame_cache_set_cur_frame(
|
||||
Scene *scene, float frame, int view_id, int width, int height, bool is_render);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
215
blender-5.2.0/source/blender/sequencer/intern/cache/preview_cache.cc
vendored
Normal file
215
blender-5.2.0/source/blender/sequencer/intern/cache/preview_cache.cc
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "GPU_texture.hh"
|
||||
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
struct PreviewCacheItem {
|
||||
int64_t last_used = -1;
|
||||
int timeline_frame = -1;
|
||||
int display_channel = -1;
|
||||
int width = -1;
|
||||
int height = -1;
|
||||
|
||||
gpu::Texture *texture = nullptr;
|
||||
gpu::Texture *scope_texture = nullptr;
|
||||
|
||||
void clear()
|
||||
{
|
||||
last_used = -1;
|
||||
timeline_frame = -1;
|
||||
width = -1;
|
||||
height = -1;
|
||||
GPU_TEXTURE_FREE_SAFE(texture);
|
||||
GPU_TEXTURE_FREE_SAFE(scope_texture);
|
||||
}
|
||||
};
|
||||
|
||||
struct PreviewCache {
|
||||
static constexpr int cache_size = 4;
|
||||
PreviewCacheItem items[cache_size];
|
||||
int64_t tick_count = 0;
|
||||
|
||||
~PreviewCache()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (PreviewCacheItem &item : this->items) {
|
||||
item.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static PreviewCache *query_preview_cache(Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->preview_cache;
|
||||
}
|
||||
|
||||
static PreviewCache *ensure_preview_cache(Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
PreviewCache *&cache = scene->ed->runtime->preview_cache;
|
||||
if (cache == nullptr) {
|
||||
cache = MEM_new<PreviewCache>(__func__);
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
gpu::Texture *preview_cache_get_gpu_texture(
|
||||
Scene *scene, int timeline_frame, int display_channel, int width, int height)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
cache->tick_count++;
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.timeline_frame == timeline_frame && item.display_channel == display_channel &&
|
||||
item.width == width && item.height == height && item.texture != nullptr)
|
||||
{
|
||||
item.last_used = cache->tick_count;
|
||||
return item.texture;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
gpu::Texture *preview_cache_get_gpu_scope_texture(
|
||||
Scene *scene, int timeline_frame, int display_channel, int width, int height)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
cache->tick_count++;
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.timeline_frame == timeline_frame && item.display_channel == display_channel &&
|
||||
item.width == width && item.height == height && item.scope_texture != nullptr)
|
||||
{
|
||||
item.last_used = cache->tick_count;
|
||||
return item.scope_texture;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static PreviewCacheItem *find_slot(
|
||||
PreviewCache *cache, int timeline_frame, int display_channel, int width, int height)
|
||||
{
|
||||
cache->tick_count++;
|
||||
|
||||
/* Try to find an exact frame match. */
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.timeline_frame == timeline_frame && item.display_channel == display_channel &&
|
||||
item.width == width && item.height == height)
|
||||
{
|
||||
return &item;
|
||||
}
|
||||
}
|
||||
|
||||
/* Find unused or least recently used slot. */
|
||||
PreviewCacheItem *best_slot = nullptr;
|
||||
int64_t best_score = -1;
|
||||
for (PreviewCacheItem &item : cache->items) {
|
||||
if (item.texture == nullptr && item.scope_texture == nullptr) {
|
||||
return &item;
|
||||
}
|
||||
int64_t score = cache->tick_count - item.last_used;
|
||||
if (score >= best_score) {
|
||||
best_score = score;
|
||||
best_slot = &item;
|
||||
}
|
||||
}
|
||||
|
||||
return best_slot;
|
||||
}
|
||||
|
||||
void preview_cache_set_gpu_texture(Scene *scene,
|
||||
int timeline_frame,
|
||||
int display_channel,
|
||||
gpu::Texture *texture)
|
||||
{
|
||||
PreviewCache *cache = ensure_preview_cache(scene);
|
||||
if (cache == nullptr || texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
const int width = GPU_texture_width(texture);
|
||||
const int height = GPU_texture_height(texture);
|
||||
PreviewCacheItem *slot = find_slot(cache, timeline_frame, display_channel, width, height);
|
||||
if (slot == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot->timeline_frame = timeline_frame;
|
||||
slot->display_channel = display_channel;
|
||||
slot->width = width;
|
||||
slot->height = height;
|
||||
slot->last_used = cache->tick_count;
|
||||
GPU_TEXTURE_FREE_SAFE(slot->texture);
|
||||
/* Free the display-space texture of this slot too. */
|
||||
GPU_TEXTURE_FREE_SAFE(slot->scope_texture);
|
||||
slot->texture = texture;
|
||||
}
|
||||
|
||||
void preview_cache_set_gpu_scope_texture(Scene *scene,
|
||||
int timeline_frame,
|
||||
int display_channel,
|
||||
gpu::Texture *texture)
|
||||
{
|
||||
PreviewCache *cache = ensure_preview_cache(scene);
|
||||
if (cache == nullptr || texture == nullptr) {
|
||||
return;
|
||||
}
|
||||
const int width = GPU_texture_width(texture);
|
||||
const int height = GPU_texture_height(texture);
|
||||
PreviewCacheItem *slot = find_slot(cache, timeline_frame, display_channel, width, height);
|
||||
if (slot == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot->timeline_frame = timeline_frame;
|
||||
slot->display_channel = display_channel;
|
||||
slot->width = width;
|
||||
slot->height = height;
|
||||
slot->last_used = cache->tick_count;
|
||||
GPU_TEXTURE_FREE_SAFE(slot->scope_texture);
|
||||
slot->scope_texture = texture;
|
||||
}
|
||||
|
||||
void preview_cache_invalidate(Scene *scene)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void preview_cache_destroy(Scene *scene)
|
||||
{
|
||||
PreviewCache *cache = query_preview_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
MEM_SAFE_DELETE(scene->ed->runtime->preview_cache);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
370
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.cc
vendored
Normal file
370
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.cc
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*/
|
||||
|
||||
#include "BLI_map.hh"
|
||||
#include "BLI_mutex.hh"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
#include "DNA_sequence_types.h"
|
||||
|
||||
#include "BKE_scene.hh"
|
||||
|
||||
#include "IMB_imbuf.hh"
|
||||
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
|
||||
#include "prefetch.hh"
|
||||
#include "source_image_cache.hh"
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
static Mutex source_image_cache_mutex;
|
||||
|
||||
struct SourceImageCache {
|
||||
struct FrameEntry {
|
||||
SeqResult image;
|
||||
/**
|
||||
* Frame in timeline, relative to strip start. Used to determine which
|
||||
* entries to evict (furthest from the play-head). Due to reversed
|
||||
* frames, playback rate, retiming the relationship between source frame
|
||||
* index and timeline frame is not a simple one.
|
||||
*/
|
||||
float strip_frame = 0;
|
||||
};
|
||||
|
||||
struct Key {
|
||||
float source_frame = 0.0f;
|
||||
int view_id = 0;
|
||||
eDrawType scene_draw_type = OB_SOLID;
|
||||
|
||||
uint64_t hash() const
|
||||
{
|
||||
return get_default_hash(source_frame, view_id, scene_draw_type);
|
||||
}
|
||||
|
||||
friend bool operator==(const Key &a, const Key &b) = default;
|
||||
};
|
||||
|
||||
struct StripEntry {
|
||||
Map<Key, FrameEntry> frames;
|
||||
};
|
||||
|
||||
Map<const Strip *, StripEntry> map_;
|
||||
|
||||
~SourceImageCache()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (const auto &item : map_.items()) {
|
||||
for (const auto &frame : item.value.frames.values()) {
|
||||
IMB_freeImBuf(frame.image.image);
|
||||
}
|
||||
}
|
||||
map_.clear();
|
||||
}
|
||||
|
||||
void remove_entry(const Strip *strip)
|
||||
{
|
||||
StripEntry *entry = map_.lookup_ptr(strip);
|
||||
if (entry == nullptr) {
|
||||
return;
|
||||
}
|
||||
for (const auto &frame : entry->frames.values()) {
|
||||
IMB_freeImBuf(frame.image.image);
|
||||
}
|
||||
map_.remove_contained(strip);
|
||||
}
|
||||
};
|
||||
|
||||
static SourceImageCache *ensure_source_image_cache(Scene *scene)
|
||||
{
|
||||
SourceImageCache **cache = &scene->ed->runtime->source_image_cache;
|
||||
if (*cache == nullptr) {
|
||||
*cache = MEM_new<SourceImageCache>(__func__);
|
||||
}
|
||||
return *cache;
|
||||
}
|
||||
|
||||
static SourceImageCache *query_source_image_cache(const Scene *scene)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
return scene->ed->runtime->source_image_cache;
|
||||
}
|
||||
|
||||
static float give_cache_frame_index(const Scene *scene, const Strip *strip, float timeline_frame)
|
||||
{
|
||||
float frame_index = give_frame_index(scene, strip, timeline_frame);
|
||||
if (strip->type != STRIP_TYPE_SCENE) {
|
||||
/* Scene strips that are slowed down need fractional frame index for animation interpolation;
|
||||
* for others use integer index for better cache hit rates. */
|
||||
frame_index = std::trunc(frame_index);
|
||||
}
|
||||
if (strip->type == STRIP_TYPE_MOVIE) {
|
||||
frame_index += strip->anim_startofs;
|
||||
}
|
||||
return frame_index;
|
||||
}
|
||||
|
||||
static SourceImageCache::Key get_key(const RenderData *context,
|
||||
const Scene *scene,
|
||||
const Strip *strip,
|
||||
float timeline_frame)
|
||||
{
|
||||
const float frame_index = give_cache_frame_index(scene, strip, timeline_frame);
|
||||
eDrawType draw_type = OB_RENDER;
|
||||
if (!context->render && strip->type == STRIP_TYPE_SCENE) {
|
||||
draw_type = eDrawType(scene->r.seq_prev_type);
|
||||
}
|
||||
return {frame_index, context->view_id, draw_type};
|
||||
}
|
||||
|
||||
SeqResult source_image_cache_get(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame)
|
||||
{
|
||||
if (context->skip_cache || strip == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
|
||||
timeline_frame = math::round(timeline_frame);
|
||||
const SourceImageCache::Key key = get_key(context, scene, strip, timeline_frame);
|
||||
|
||||
SeqResult res;
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return res;
|
||||
}
|
||||
|
||||
SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
|
||||
if (val == nullptr) {
|
||||
/* Nothing in cache for this strip yet. */
|
||||
return res;
|
||||
}
|
||||
/* Search entries for the frame we want. */
|
||||
SourceImageCache::FrameEntry *frame = val->frames.lookup_ptr(key);
|
||||
if (frame != nullptr) {
|
||||
res = frame->image;
|
||||
}
|
||||
|
||||
/* For effect, meta, and scene strips, check if the cached result matches our current
|
||||
* render resolution. If it does not, remove stale source entries for this strip. */
|
||||
if (res.is_valid() &&
|
||||
(strip->is_effect() || strip->type == STRIP_TYPE_SCENE || strip->type == STRIP_TYPE_META))
|
||||
{
|
||||
if (res.image->x != context->rectx || res.image->y != context->recty) {
|
||||
cache->remove_entry(strip);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.is_valid()) {
|
||||
IMB_refImBuf(res.image);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void source_image_cache_put(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame,
|
||||
const SeqResult &image)
|
||||
{
|
||||
if (context->skip_cache || strip == nullptr || !image.is_valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Scene *scene = prefetch_get_original_scene_and_strip(context, strip);
|
||||
timeline_frame = math::round(timeline_frame);
|
||||
const SourceImageCache::Key key = get_key(context, scene, strip, timeline_frame);
|
||||
|
||||
IMB_refImBuf(image.image);
|
||||
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = ensure_source_image_cache(scene);
|
||||
|
||||
SourceImageCache::StripEntry *val = cache->map_.lookup_ptr(strip);
|
||||
|
||||
if (val == nullptr) {
|
||||
/* Nothing in cache for this strip yet. */
|
||||
cache->map_.add_new(strip, {});
|
||||
val = cache->map_.lookup_ptr(strip);
|
||||
}
|
||||
BLI_assert_msg(val != nullptr, "Source image cache value should never be null here");
|
||||
|
||||
SourceImageCache::FrameEntry &frame = val->frames.lookup_or_add_default(key);
|
||||
if (frame.image.is_valid()) {
|
||||
IMB_freeImBuf(frame.image.image);
|
||||
}
|
||||
frame.strip_frame = timeline_frame - strip->start;
|
||||
frame.image = image;
|
||||
}
|
||||
|
||||
void source_image_cache_invalidate_strip(Scene *scene, const Strip *strip)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
cache->remove_entry(strip);
|
||||
}
|
||||
}
|
||||
|
||||
void source_image_cache_clear(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
scene->ed->runtime->source_image_cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void source_image_cache_destroy(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
BLI_assert(cache == scene->ed->runtime->source_image_cache);
|
||||
MEM_delete(scene->ed->runtime->source_image_cache);
|
||||
scene->ed->runtime->source_image_cache = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void source_image_cache_iterate(Scene *scene,
|
||||
void *userdata,
|
||||
void callback_iter(void *userdata,
|
||||
const Strip *strip,
|
||||
int timeline_frame))
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &[strip, frames] : cache->map_.items()) {
|
||||
for (const auto &[frame_key, frame] : frames.frames.items()) {
|
||||
const float timeline_frame = strip->start + frame.strip_frame;
|
||||
callback_iter(userdata, strip, int(timeline_frame));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t source_image_cache_calc_memory_size(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t size = 0;
|
||||
for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
|
||||
for (const SourceImageCache::FrameEntry &frame : entry.frames.values()) {
|
||||
if (frame.image.is_valid()) {
|
||||
size += IMB_get_size_in_memory(frame.image.image);
|
||||
}
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
size_t source_image_cache_get_image_count(const Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
size_t count = 0;
|
||||
for (const SourceImageCache::StripEntry &entry : cache->map_.values()) {
|
||||
count += entry.frames.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
bool source_image_cache_evict(Scene *scene)
|
||||
{
|
||||
std::lock_guard lock(source_image_cache_mutex);
|
||||
SourceImageCache *cache = query_source_image_cache(scene);
|
||||
if (cache == nullptr) {
|
||||
return false;
|
||||
}
|
||||
/* Find which entry to remove -- we pick the one that is furthest from the current frame,
|
||||
* biasing the ones that are behind the current frame.
|
||||
*
|
||||
* However, do not try to evict entries from the current prefetch job range -- we need to
|
||||
* be able to fully fill the cache from prefetching, and then actually stop the job when it
|
||||
* is full and no longer can evict anything. */
|
||||
int cur_prefetch_start = std::numeric_limits<int>::min();
|
||||
int cur_prefetch_end = std::numeric_limits<int>::min();
|
||||
if (scene->ed->cache_flag & SEQ_CACHE_STORE_RAW) {
|
||||
/* Only activate the prefetch guards if the cache is active. */
|
||||
seq_prefetch_get_time_range(scene, &cur_prefetch_start, &cur_prefetch_end);
|
||||
}
|
||||
const bool prefetch_loops_around = cur_prefetch_start > cur_prefetch_end;
|
||||
|
||||
const int timeline_start = scene->playback_start();
|
||||
const int timeline_end = scene->playback_end();
|
||||
/* If we wrap around, treat the timeline start as the playback head position.
|
||||
* This is to try to mitigate un-needed cache evictions. */
|
||||
const int cur_frame = prefetch_loops_around ? timeline_start : scene->r.cfra;
|
||||
|
||||
SourceImageCache::StripEntry *best_strip = nullptr;
|
||||
SourceImageCache::Key best_key;
|
||||
int best_score = 0;
|
||||
for (const auto &strip : cache->map_.items()) {
|
||||
for (const auto &entry : strip.value.frames.items()) {
|
||||
const int item_frame = int(strip.key->start + entry.value.strip_frame);
|
||||
if (prefetch_loops_around) {
|
||||
if (item_frame >= timeline_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
if (item_frame >= cur_prefetch_start && item_frame <= timeline_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
}
|
||||
else if (item_frame >= cur_prefetch_start && item_frame <= cur_prefetch_end) {
|
||||
continue; /* Within active prefetch range, do not try to remove it. */
|
||||
}
|
||||
|
||||
/* Score for removal is distance to current frame; 2x that if behind current frame. */
|
||||
int score = 0;
|
||||
if (item_frame < cur_frame) {
|
||||
score = (cur_frame - item_frame) * 2;
|
||||
}
|
||||
else if (item_frame > cur_frame) {
|
||||
score = item_frame - cur_frame;
|
||||
}
|
||||
if (score > best_score) {
|
||||
best_strip = &strip.value;
|
||||
best_key = entry.key;
|
||||
best_score = score;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove if we found one. */
|
||||
if (best_strip != nullptr) {
|
||||
IMB_freeImBuf(best_strip->frames.lookup(best_key).image.image);
|
||||
best_strip->frames.remove(best_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace blender::seq
|
||||
51
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.hh
vendored
Normal file
51
blender-5.2.0/source/blender/sequencer/intern/cache/source_image_cache.hh
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Cache source images for strips.
|
||||
* - Keyed by (strip + frame index within strip media + view ID + scene strip draw type).
|
||||
* - Caching is only done for strips that are independent of
|
||||
* any other strips (images, movies, no-input effect strips like
|
||||
* Text and Color).
|
||||
* - When full, cache eviction policy is to remove frames furthest
|
||||
* from the current-frame, biasing towards removal of
|
||||
* frames behind the current-frame.
|
||||
* - Invalidated fairly rarely, since the cached items only change
|
||||
* when the source content changes.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "render.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Strip;
|
||||
struct Scene;
|
||||
struct RenderData;
|
||||
|
||||
namespace seq {
|
||||
|
||||
void source_image_cache_put(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame,
|
||||
const SeqResult &image);
|
||||
|
||||
SeqResult source_image_cache_get(const RenderData *context,
|
||||
const Strip *strip,
|
||||
float timeline_frame);
|
||||
|
||||
void source_image_cache_invalidate_strip(Scene *scene, const Strip *strip);
|
||||
|
||||
void source_image_cache_clear(Scene *scene);
|
||||
void source_image_cache_destroy(Scene *scene);
|
||||
|
||||
bool source_image_cache_evict(Scene *scene);
|
||||
|
||||
size_t source_image_cache_get_image_count(const Scene *scene);
|
||||
|
||||
} // namespace seq
|
||||
} // namespace blender
|
||||
646
blender-5.2.0/source/blender/sequencer/intern/cache/thumbnail_cache.cc
vendored
Normal file
646
blender-5.2.0/source/blender/sequencer/intern/cache/thumbnail_cache.cc
vendored
Normal file
@@ -0,0 +1,646 @@
|
||||
/* 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"
|
||||
#include "BLI_mutex.hh"
|
||||
#include "BLI_path_utils.hh"
|
||||
#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 "MOV_read.hh"
|
||||
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_sequencer.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 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,
|
||||
StripType type,
|
||||
int64_t logical_time,
|
||||
float time_frame,
|
||||
int ch,
|
||||
int width,
|
||||
int height)
|
||||
: file_path(path),
|
||||
frame_index(frame),
|
||||
stream_index(stream),
|
||||
strip_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). */
|
||||
StripType strip_type = STRIP_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, strip_type);
|
||||
}
|
||||
bool operator==(const Request &o) const
|
||||
{
|
||||
return frame_index == o.frame_index && stream_index == o.stream_index &&
|
||||
strip_type == o.strip_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();
|
||||
requests_.clear();
|
||||
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;
|
||||
}
|
||||
|
||||
bool strip_can_have_thumbnail(const Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (scene == nullptr || scene->ed == nullptr || strip == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (!ELEM(strip->type, STRIP_TYPE_MOVIE, STRIP_TYPE_IMAGE)) {
|
||||
return false;
|
||||
}
|
||||
const StripElem *se = strip->data->stripdata;
|
||||
if (se->orig_height == 0 || se->orig_width == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string get_path_from_strip(Scene *scene, const Strip *strip, float timeline_frame)
|
||||
{
|
||||
char filepath[FILE_MAX];
|
||||
filepath[0] = 0;
|
||||
switch (strip->type) {
|
||||
case STRIP_TYPE_IMAGE: {
|
||||
const StripElem *s_elem = render_give_stripelem(scene, strip, timeline_frame);
|
||||
if (s_elem != nullptr) {
|
||||
BLI_path_join(filepath, sizeof(filepath), strip->data->dirpath, s_elem->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
||||
}
|
||||
} break;
|
||||
case STRIP_TYPE_MOVIE:
|
||||
BLI_path_join(
|
||||
filepath, sizeof(filepath), strip->data->dirpath, strip->data->stripdata->filename);
|
||||
BLI_path_abs(filepath, ID_BLEND_PATH_FROM_GLOBAL(&scene->id));
|
||||
break;
|
||||
default:
|
||||
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 = THUMB_SIZE;
|
||||
r_height = round_fl_to_int(THUMB_SIZE / aspect);
|
||||
}
|
||||
else {
|
||||
r_height = THUMB_SIZE;
|
||||
r_width = round_fl_to_int(THUMB_SIZE * aspect);
|
||||
}
|
||||
}
|
||||
|
||||
static ImBuf *make_thumb_for_image(const Scene *scene, const ThumbnailCache::Request &request)
|
||||
{
|
||||
ImBuf *ibuf = IMB_thumb_load_image(
|
||||
request.file_path.c_str(), THUMB_SIZE, nullptr, IMBThumbLoadFlags::LoadLargeFiles);
|
||||
if (ibuf == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
/* Keep only float buffer if we have both byte & float. */
|
||||
if (ibuf->float_data() != nullptr && ibuf->byte_data() != nullptr) {
|
||||
IMB_free_byte_pixels(ibuf);
|
||||
}
|
||||
|
||||
ensure_ibuf_is_sequencer_space(scene, ibuf, false);
|
||||
return ibuf;
|
||||
}
|
||||
|
||||
static void scale_to_thumbnail_size(ImBuf *ibuf)
|
||||
{
|
||||
if (ibuf == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* We only need byte thumbnails. */
|
||||
if (ibuf->float_data()) {
|
||||
if (ibuf->byte_data() == nullptr) {
|
||||
IMB_byte_from_float(ibuf);
|
||||
}
|
||||
IMB_free_float_pixels(ibuf);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
wmWindowManager *wm = CTX_wm_manager(C);
|
||||
wmWindow *win = CTX_wm_window(C);
|
||||
Scene *scene = CTX_data_sequencer_scene(C);
|
||||
wmJob *wm_job = WM_jobs_get(wm,
|
||||
win,
|
||||
scene,
|
||||
"Generating strip thumbnails...",
|
||||
eWM_JobFlag(0),
|
||||
WM_JOB_TYPE_SEQ_DRAW_THUMBNAIL);
|
||||
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);
|
||||
|
||||
WM_jobs_start(wm, wm_job);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
* NOTE: keep the requests set intact! We don't want to add new requests for same
|
||||
* 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::ranges::sort(requests,
|
||||
[](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;
|
||||
});
|
||||
|
||||
/* Note: we could process thumbnail cache requests somewhat in parallel,
|
||||
* but let's not do that so that UI responsiveness is not affected much.
|
||||
* Some of video/image loading code parts are multi-threaded internally already,
|
||||
* and that does provide some parallelism. */
|
||||
{
|
||||
/* 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 MovieReader
|
||||
* objects between them for performance. */
|
||||
MovieReader *cur_anim = nullptr;
|
||||
std::string cur_anim_path;
|
||||
int cur_stream = 0;
|
||||
IMB_Proxy_Size cur_proxy_size = IMB_PROXY_NONE;
|
||||
for (const ThumbnailCache::Request &request : requests) {
|
||||
if (worker_status->stop) {
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
++total_thumbs;
|
||||
#endif
|
||||
ImBuf *thumb = nullptr;
|
||||
if (request.strip_type == STRIP_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.strip_type == STRIP_TYPE_MOVIE) {
|
||||
/* Load thumbnail for an movie. */
|
||||
#ifdef DEBUG_PRINT_THUMB_JOB_TIMES
|
||||
++total_movies;
|
||||
#endif
|
||||
|
||||
/* Are we switching to a different movie file / stream? */
|
||||
if (request.file_path != cur_anim_path || request.stream_index != cur_stream) {
|
||||
if (cur_anim != nullptr) {
|
||||
MOV_close(cur_anim);
|
||||
cur_anim = nullptr;
|
||||
}
|
||||
|
||||
cur_anim_path = request.file_path;
|
||||
cur_stream = request.stream_index;
|
||||
cur_anim = MOV_open_file(
|
||||
cur_anim_path.c_str(), ImBufFlags::Zero, cur_stream, true, nullptr);
|
||||
cur_proxy_size = IMB_PROXY_NONE;
|
||||
if (cur_anim != nullptr) {
|
||||
/* Find the lowest proxy resolution available.
|
||||
* `x & -x` leaves only the lowest bit set. */
|
||||
int proxies_mask = MOV_get_existing_proxies(cur_anim);
|
||||
cur_proxy_size = IMB_Proxy_Size(proxies_mask & -proxies_mask);
|
||||
}
|
||||
}
|
||||
|
||||
/* Decode the movie frame. */
|
||||
if (cur_anim != nullptr) {
|
||||
thumb = MOV_decode_frame(cur_anim, request.frame_index, cur_proxy_size);
|
||||
if (thumb == nullptr && cur_proxy_size != IMB_PROXY_NONE) {
|
||||
/* Broken proxy file, switch to non-proxy. */
|
||||
cur_proxy_size = IMB_PROXY_NONE;
|
||||
thumb = MOV_decode_frame(cur_anim, request.frame_index, cur_proxy_size);
|
||||
}
|
||||
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) {
|
||||
MOV_close(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 Strip *strip)
|
||||
{
|
||||
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 (strip->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 = strip->data->stripdata;
|
||||
int img_width = se->orig_width;
|
||||
int img_height = se->orig_height;
|
||||
ThumbnailCache::Request request(key,
|
||||
frame_index,
|
||||
strip->streamindex,
|
||||
strip->type,
|
||||
cur_time,
|
||||
timeline_frame,
|
||||
strip->channel,
|
||||
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 Strip *strip,
|
||||
float timeline_frame)
|
||||
{
|
||||
if (!strip_can_have_thumbnail(scene, strip)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
timeline_frame = math::round(timeline_frame);
|
||||
|
||||
const std::string key = get_path_from_strip(scene, strip, timeline_frame);
|
||||
int frame_index = give_frame_index(scene, strip, timeline_frame);
|
||||
if (strip->type == STRIP_TYPE_MOVIE) {
|
||||
frame_index += strip->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, strip);
|
||||
}
|
||||
|
||||
if (res) {
|
||||
IMB_refImBuf(res);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void thumbnail_cache_invalidate_strip(Scene *scene, const Strip *strip)
|
||||
{
|
||||
if (!strip_can_have_thumbnail(scene, strip)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock lock(thumb_cache_mutex);
|
||||
ThumbnailCache *cache = query_thumbnail_cache(scene);
|
||||
if (cache != nullptr) {
|
||||
if (ELEM((strip)->type, STRIP_TYPE_MOVIE, STRIP_TYPE_IMAGE)) {
|
||||
const StripElem *elem = strip->data->stripdata;
|
||||
if (elem != nullptr) {
|
||||
int paths_count = 1;
|
||||
if (strip->type == STRIP_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 = ID_BLEND_PATH_FROM_GLOBAL(&scene->id);
|
||||
for (int i = 0; i < paths_count; i++, elem++) {
|
||||
BLI_path_join(filepath, sizeof(filepath), strip->data->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);
|
||||
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
|
||||
Reference in New Issue
Block a user