Files
test/source/blender/sequencer/intern/cache/preview_cache.cc
Aras Pranckevicius a52bed7786 VSE: Do Scopes on the GPU, improve their look, HDR for waveform/parade
Faster and better looking VSE scopes & "show overexposed". Waveform &
RGB Parade now can also show HDR color intensities. (Note: this is
only about VSE scopes; Image Space scopes are to be improved separately)

- Waveform, RGB Parade, Vectorscope scopes are done on the GPU now, by
  drawing points for each input pixel, and placing them according to
  scope logic. The point drawing is implemented in a compute shader,
  with a fragment shader resolve pass; this is because drawing lots of
  points in the same location is very slow on some GPUs (e.g. Apple).
  The compute shader rasterizer is several times faster on regular
  desktop GPU as well.
- If a non-default color management is needed (e.g. VSE colorspace is
  not the same as display colorspace, or a custom look transform is used
  etc. etc.), then transform the VSE preview texture into display space
  RGBA 16F texture using OCIO GPU machinery, and calculate scopes
  from that.
- The "show overexposed" (zebra) preview option is also done on the
  GPU now.
- Waveform/Parade scopes unlock zoom X/Y aspect for viewing HDR scope,
  similar to how it was done for HDR histograms recently.
- Added SEQ_preview_cache.hh that holds GPU textures of VSE preview,
  this is so that when you have a preview and several scopes, each of
  them does not have to create/upload their own GPU texture (that would
  both waste memory, and be slow).

Screenshots and performance details in the PR.

Pull Request: https://projects.blender.org/blender/blender/pulls/144867
2025-08-26 12:25:43 +02:00

184 lines
4.2 KiB
C++

/* 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"
namespace blender::seq {
struct PreviewCacheItem {
int64_t last_used = -1;
int timeline_frame = -1;
gpu::Texture *texture = nullptr;
gpu::Texture *display_texture = nullptr;
void clear()
{
last_used = -1;
timeline_frame = -1;
GPU_TEXTURE_FREE_SAFE(texture);
GPU_TEXTURE_FREE_SAFE(display_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)
{
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.texture != nullptr) {
item.last_used = cache->tick_count;
return item.texture;
}
}
return nullptr;
}
gpu::Texture *preview_cache_get_gpu_display_texture(Scene *scene, int timeline_frame)
{
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_texture != nullptr) {
item.last_used = cache->tick_count;
return item.display_texture;
}
}
return nullptr;
}
static PreviewCacheItem *find_slot(PreviewCache *cache, int timeline_frame)
{
cache->tick_count++;
/* Try to find an exact frame match. */
for (PreviewCacheItem &item : cache->items) {
if (item.timeline_frame == timeline_frame) {
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.display_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, gpu::Texture *texture)
{
PreviewCache *cache = ensure_preview_cache(scene);
if (cache == nullptr || texture == nullptr) {
return;
}
PreviewCacheItem *slot = find_slot(cache, timeline_frame);
if (slot == nullptr) {
return;
}
slot->timeline_frame = timeline_frame;
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->display_texture);
slot->texture = texture;
}
void preview_cache_set_gpu_display_texture(Scene *scene, int timeline_frame, gpu::Texture *texture)
{
PreviewCache *cache = ensure_preview_cache(scene);
if (cache == nullptr || texture == nullptr) {
return;
}
PreviewCacheItem *slot = find_slot(cache, timeline_frame);
if (slot == nullptr) {
return;
}
slot->timeline_frame = timeline_frame;
slot->last_used = cache->tick_count;
GPU_TEXTURE_FREE_SAFE(slot->display_texture);
slot->display_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