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
This commit is contained in:
committed by
Aras Pranckevicius
parent
7bd19f7efb
commit
a52bed7786
@@ -1368,6 +1368,7 @@ static void scene_blend_read_data(BlendDataReader *reader, ID *id)
|
||||
ed->runtime.intra_frame_cache = nullptr;
|
||||
ed->runtime.source_image_cache = nullptr;
|
||||
ed->runtime.final_image_cache = nullptr;
|
||||
ed->runtime.preview_cache = nullptr;
|
||||
|
||||
/* recursive link sequences, lb will be correctly initialized */
|
||||
link_recurs_seq(reader, &ed->seqbase);
|
||||
|
||||
@@ -145,7 +145,6 @@ void draw_timeline_seq_display(const bContext *C, ARegion *region);
|
||||
* region.
|
||||
*/
|
||||
void sequencer_preview_region_draw(const bContext *C, ARegion *region);
|
||||
int sequencer_draw_get_transform_preview_frame(const Scene *scene);
|
||||
void sequencer_special_update_set(Strip *strip);
|
||||
|
||||
/* UNUSED */
|
||||
|
||||
@@ -37,11 +37,14 @@
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "GPU_compute.hh"
|
||||
#include "GPU_debug.hh"
|
||||
#include "GPU_framebuffer.hh"
|
||||
#include "GPU_immediate.hh"
|
||||
#include "GPU_immediate_util.hh"
|
||||
#include "GPU_matrix.hh"
|
||||
#include "GPU_primitive.hh"
|
||||
#include "GPU_shader_shared.hh"
|
||||
#include "GPU_state.hh"
|
||||
#include "GPU_viewport.hh"
|
||||
|
||||
@@ -58,6 +61,7 @@
|
||||
#include "SEQ_effects.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_prefetch.hh"
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_proxy.hh"
|
||||
#include "SEQ_render.hh"
|
||||
#include "SEQ_select.hh"
|
||||
@@ -672,32 +676,47 @@ static void draw_vectorscope_graticule(ARegion *region, SeqQuadsBatch &quads, co
|
||||
UI_view2d_text_cache_draw(region);
|
||||
}
|
||||
|
||||
static void sequencer_draw_scopes(const SpaceSeq &space_sequencer, ARegion ®ion)
|
||||
static const char *get_scope_debug_name(eSpaceSeq_RegionType type)
|
||||
{
|
||||
/* Figure out draw coordinates. */
|
||||
const rctf preview = preview_get_full_position(region);
|
||||
switch (type) {
|
||||
case SEQ_DRAW_IMG_VECTORSCOPE:
|
||||
return "VSE Vectorscope";
|
||||
case SEQ_DRAW_IMG_WAVEFORM:
|
||||
return "VSE Waveform";
|
||||
case SEQ_DRAW_IMG_RGBPARADE:
|
||||
return "VSE Parade";
|
||||
case SEQ_DRAW_IMG_HISTOGRAM:
|
||||
return "VSE Histogram";
|
||||
case SEQ_DRAW_IMG_IMBUF:
|
||||
return "VSE Overexposed";
|
||||
default:
|
||||
return "VSE Scope";
|
||||
}
|
||||
}
|
||||
|
||||
rctf uv;
|
||||
BLI_rctf_init(&uv, 0.0f, 1.0f, 0.0f, 1.0f);
|
||||
const bool keep_aspect = space_sequencer.mainb == SEQ_DRAW_IMG_VECTORSCOPE;
|
||||
float vecscope_aspect = 1.0f;
|
||||
if (keep_aspect) {
|
||||
float width = std::max(BLI_rctf_size_x(&preview), 0.1f);
|
||||
float height = std::max(BLI_rctf_size_y(&preview), 0.1f);
|
||||
vecscope_aspect = width / height;
|
||||
if (vecscope_aspect >= 1.0f) {
|
||||
BLI_rctf_resize_x(&uv, vecscope_aspect);
|
||||
}
|
||||
else {
|
||||
BLI_rctf_resize_y(&uv, 1.0f / vecscope_aspect);
|
||||
}
|
||||
static void sequencer_draw_scopes(Scene *scene,
|
||||
const SpaceSeq &space_sequencer,
|
||||
ARegion ®ion,
|
||||
int timeline_frame,
|
||||
int image_width,
|
||||
int image_height,
|
||||
bool premultiplied)
|
||||
{
|
||||
GPU_debug_group_begin(get_scope_debug_name(eSpaceSeq_RegionType(space_sequencer.mainb)));
|
||||
|
||||
gpu::Texture *input_texture = seq::preview_cache_get_gpu_display_texture(scene, timeline_frame);
|
||||
if (input_texture == nullptr) {
|
||||
input_texture = seq::preview_cache_get_gpu_texture(scene, timeline_frame);
|
||||
}
|
||||
|
||||
SeqQuadsBatch quads;
|
||||
const SeqScopes *scopes = &space_sequencer.runtime->scopes;
|
||||
|
||||
bool use_blend = space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF &&
|
||||
space_sequencer.flag & SEQ_USE_ALPHA;
|
||||
bool use_blend = (space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF &&
|
||||
space_sequencer.flag & SEQ_USE_ALPHA) ||
|
||||
(space_sequencer.mainb != SEQ_DRAW_IMG_IMBUF);
|
||||
|
||||
const rctf preview = preview_get_full_position(region);
|
||||
|
||||
/* Draw black rectangle over scopes area. */
|
||||
if (space_sequencer.mainb != SEQ_DRAW_IMG_IMBUF) {
|
||||
@@ -711,69 +730,123 @@ static void sequencer_draw_scopes(const SpaceSeq &space_sequencer, ARegion ®i
|
||||
immUnbindProgram();
|
||||
}
|
||||
|
||||
/* Draw scope image if there is one. */
|
||||
ImBuf *scope_image = nullptr;
|
||||
if (space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF) {
|
||||
scope_image = scopes->zebra_ibuf;
|
||||
}
|
||||
else if (space_sequencer.mainb == SEQ_DRAW_IMG_WAVEFORM) {
|
||||
scope_image = scopes->waveform_ibuf;
|
||||
}
|
||||
else if (space_sequencer.mainb == SEQ_DRAW_IMG_VECTORSCOPE) {
|
||||
scope_image = scopes->vector_ibuf;
|
||||
}
|
||||
else if (space_sequencer.mainb == SEQ_DRAW_IMG_RGBPARADE) {
|
||||
scope_image = scopes->sep_waveform_ibuf;
|
||||
}
|
||||
|
||||
if (use_blend) {
|
||||
GPU_blend(GPU_BLEND_ALPHA);
|
||||
}
|
||||
|
||||
if (scope_image != nullptr) {
|
||||
if (scope_image->float_buffer.data && scope_image->byte_buffer.data == nullptr) {
|
||||
IMB_byte_from_float(scope_image);
|
||||
if (input_texture) {
|
||||
if (space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF) {
|
||||
/* Draw overexposed overlay. */
|
||||
GPU_blend(GPU_BLEND_NONE);
|
||||
GPUVertFormat *imm_format = immVertexFormat();
|
||||
const uint pos = GPU_vertformat_attr_add(
|
||||
imm_format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
|
||||
const uint tex_coord = GPU_vertformat_attr_add(
|
||||
imm_format, "texCoord", blender::gpu::VertAttrType::SFLOAT_32_32);
|
||||
|
||||
immBindBuiltinProgram(GPU_SHADER_SEQUENCER_ZEBRA);
|
||||
immUniform1i("img_premultiplied", premultiplied ? 1 : 0);
|
||||
immUniform1f("zebra_limit", space_sequencer.zebra / 100.0f);
|
||||
immUniformColor3f(1.0f, 1.0f, 1.0f);
|
||||
|
||||
GPU_texture_bind(input_texture, 0);
|
||||
rctf uv;
|
||||
BLI_rctf_init(&uv, 0.0f, 1.0f, 0.0f, 1.0f);
|
||||
immRectf_with_texco(pos, tex_coord, preview, uv);
|
||||
GPU_texture_unbind(input_texture);
|
||||
immUnbindProgram();
|
||||
}
|
||||
else if (space_sequencer.mainb != SEQ_DRAW_IMG_HISTOGRAM) {
|
||||
/* Draw point-based scopes using a compute shader based rasterizer (using
|
||||
* regular GPU pipeline to draw many points, where thousands of them can
|
||||
* hit the same pixels, is very inefficient, especially on tile-based GPUs).
|
||||
*
|
||||
* Compute shader rasterizer does atomic adds of fixed point colors into
|
||||
* a screen size buffer, then a fragment shader resolve pass outputs the
|
||||
* final colors. */
|
||||
const float point_size = (BLI_rcti_size_x(®ion.v2d.mask) + 1) /
|
||||
BLI_rctf_size_x(®ion.v2d.cur);
|
||||
float3 coeffs;
|
||||
IMB_colormanagement_get_luminance_coefficients(coeffs);
|
||||
|
||||
blender::gpu::TextureFormat format = blender::gpu::TextureFormat::UNORM_8_8_8_8;
|
||||
eGPUDataFormat data = GPU_DATA_UBYTE;
|
||||
eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT;
|
||||
blender::gpu::Texture *texture = GPU_texture_create_2d(
|
||||
"seq_display_buf", scope_image->x, scope_image->y, 1, format, usage, nullptr);
|
||||
GPU_texture_update(texture, data, scope_image->byte_buffer.data);
|
||||
GPU_texture_filter_mode(texture, false);
|
||||
GPU_texture_extend_mode(texture, GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER);
|
||||
int viewport_size_i[4];
|
||||
GPU_viewport_size_get_i(viewport_size_i);
|
||||
const int2 viewport_size = int2(viewport_size_i[2], viewport_size_i[3]);
|
||||
const int2 image_size = int2(image_width, image_height);
|
||||
gpu::StorageBuf *raster_ssbo = GPU_storagebuf_create_ex(viewport_size.x * viewport_size.y *
|
||||
sizeof(SeqScopeRasterData),
|
||||
nullptr,
|
||||
GPU_USAGE_DEVICE_ONLY,
|
||||
"Scopes Raster");
|
||||
GPU_storagebuf_clear_to_zero(raster_ssbo);
|
||||
/* Compute shader rasterization. */
|
||||
{
|
||||
gpu::Shader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_SEQUENCER_SCOPE_RASTER);
|
||||
BLI_assert(shader);
|
||||
GPU_shader_bind(shader);
|
||||
|
||||
GPU_texture_bind(texture, 0);
|
||||
const int raster_ssbo_location = GPU_shader_get_ssbo_binding(shader, "raster_buf");
|
||||
GPU_storagebuf_bind(raster_ssbo, raster_ssbo_location);
|
||||
const int image_location = GPU_shader_get_sampler_binding(shader, "image");
|
||||
GPU_texture_bind(input_texture, image_location);
|
||||
|
||||
GPUVertFormat *imm_format = immVertexFormat();
|
||||
uint pos = GPU_vertformat_attr_add(
|
||||
imm_format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
|
||||
uint texCoord = GPU_vertformat_attr_add(
|
||||
imm_format, "texCoord", blender::gpu::VertAttrType::SFLOAT_32_32);
|
||||
immBindBuiltinProgram(GPU_SHADER_3D_IMAGE_COLOR);
|
||||
immUniformColor3f(1.0f, 1.0f, 1.0f);
|
||||
GPU_shader_uniform_1i(shader, "view_width", viewport_size.x);
|
||||
GPU_shader_uniform_1i(shader, "view_height", viewport_size.y);
|
||||
GPU_shader_uniform_3fv(shader, "luma_coeffs", coeffs);
|
||||
GPU_shader_uniform_1f(shader, "scope_point_size", point_size);
|
||||
GPU_shader_uniform_1b(shader, "img_premultiplied", premultiplied);
|
||||
GPU_shader_uniform_1i(shader, "image_width", image_width);
|
||||
GPU_shader_uniform_1i(shader, "image_height", image_height);
|
||||
GPU_shader_uniform_1i(shader, "scope_mode", space_sequencer.mainb);
|
||||
|
||||
immBegin(GPU_PRIM_TRI_FAN, 4);
|
||||
const int2 groups_to_dispatch = math::divide_ceil(image_size, int2(16));
|
||||
GPU_compute_dispatch(shader, groups_to_dispatch.x, groups_to_dispatch.y, 1);
|
||||
|
||||
immAttr2f(texCoord, uv.xmin, uv.ymin);
|
||||
immVertex2f(pos, preview.xmin, preview.ymin);
|
||||
GPU_shader_unbind();
|
||||
GPU_storagebuf_unbind(raster_ssbo);
|
||||
/* Make computed results consistently visible in the following resolve pass. */
|
||||
GPU_memory_barrier(GPU_BARRIER_SHADER_STORAGE);
|
||||
}
|
||||
|
||||
immAttr2f(texCoord, uv.xmin, uv.ymax);
|
||||
immVertex2f(pos, preview.xmin, preview.ymax);
|
||||
/* Resolve pass. */
|
||||
{
|
||||
if (use_blend) {
|
||||
GPU_blend(GPU_BLEND_ALPHA);
|
||||
}
|
||||
|
||||
immAttr2f(texCoord, uv.xmax, uv.ymax);
|
||||
immVertex2f(pos, preview.xmax, preview.ymax);
|
||||
/* Depending on resolution of the image, different amounts of pixels are expected
|
||||
* to hit the same locations of the scope. Adjust the scope transparency mapping
|
||||
* exponent so that the scope has decent visibility without saturating or being too dark:
|
||||
* 0.07 at height=2160 (4K) and up, 0.5 at height=360 and below, and interpolating between
|
||||
* those. */
|
||||
float alpha = math::clamp(ratiof(360.0f, 2160.0f, image_height), 0.0f, 1.0f);
|
||||
float exponent = math::interpolate(0.5f, 0.07f, alpha);
|
||||
|
||||
immAttr2f(texCoord, uv.xmax, uv.ymin);
|
||||
immVertex2f(pos, preview.xmax, preview.ymin);
|
||||
gpu::Shader *shader = GPU_shader_get_builtin_shader(GPU_SHADER_SEQUENCER_SCOPE_RESOLVE);
|
||||
BLI_assert(shader);
|
||||
|
||||
immEnd();
|
||||
const int raster_ssbo_location = GPU_shader_get_ssbo_binding(shader, "raster_buf");
|
||||
GPU_storagebuf_bind(raster_ssbo, raster_ssbo_location);
|
||||
|
||||
GPU_texture_unbind(texture);
|
||||
GPU_texture_free(texture);
|
||||
blender::gpu::Batch *batch = GPU_batch_create_procedural(GPU_PRIM_TRIS, 3);
|
||||
|
||||
immUnbindProgram();
|
||||
GPU_batch_set_shader(batch, shader);
|
||||
GPU_batch_uniform_1i(batch, "view_width", viewport_size.x);
|
||||
GPU_batch_uniform_1i(batch, "view_height", viewport_size.y);
|
||||
GPU_batch_uniform_1f(batch, "alpha_exponent", exponent);
|
||||
GPU_batch_draw(batch);
|
||||
|
||||
GPU_batch_discard(batch);
|
||||
GPU_storagebuf_unbind(raster_ssbo);
|
||||
}
|
||||
|
||||
GPU_storagebuf_free(raster_ssbo);
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw scope graticules. */
|
||||
if (use_blend) {
|
||||
GPU_blend(GPU_BLEND_ALPHA);
|
||||
}
|
||||
|
||||
if (space_sequencer.mainb == SEQ_DRAW_IMG_HISTOGRAM) {
|
||||
@@ -793,65 +866,98 @@ static void sequencer_draw_scopes(const SpaceSeq &space_sequencer, ARegion ®i
|
||||
if (use_blend) {
|
||||
GPU_blend(GPU_BLEND_NONE);
|
||||
}
|
||||
GPU_debug_group_end();
|
||||
}
|
||||
|
||||
static bool sequencer_calc_scopes(const SpaceSeq &space_sequencer,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings,
|
||||
const ImBuf &ibuf,
|
||||
const int timeline_frame)
|
||||
static void update_gpu_scopes(const ImBuf *input_ibuf,
|
||||
gpu::Texture *input_texture,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings,
|
||||
Scene *scene,
|
||||
int timeline_frame)
|
||||
{
|
||||
BLI_assert(input_ibuf && input_texture);
|
||||
|
||||
/* No need for GPU texture transformed to display space: can use input texture as-is. */
|
||||
if (!IMB_colormanagement_display_processor_needed(input_ibuf, &view_settings, &display_settings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Display space GPU texture is already calculated. */
|
||||
gpu::Texture *display_texture = seq::preview_cache_get_gpu_display_texture(scene,
|
||||
timeline_frame);
|
||||
if (display_texture != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Create GPU texture. */
|
||||
const int width = GPU_texture_width(input_texture);
|
||||
const int height = GPU_texture_height(input_texture);
|
||||
const eGPUTextureUsage usage = GPU_TEXTURE_USAGE_SHADER_READ | GPU_TEXTURE_USAGE_ATTACHMENT;
|
||||
const gpu::TextureFormat format = gpu::TextureFormat::SFLOAT_16_16_16_16;
|
||||
display_texture = GPU_texture_create_2d(
|
||||
"seq_scope_display_buf", width, height, 1, format, usage, nullptr);
|
||||
GPU_texture_filter_mode(display_texture, false);
|
||||
|
||||
GPU_matrix_push();
|
||||
GPU_matrix_push_projection();
|
||||
GPU_matrix_ortho_set(0.0f, 1.0f, 0.0f, 1.0f, -1.0, 1.0f);
|
||||
GPU_matrix_identity_set();
|
||||
|
||||
GPUFrameBuffer *fb = nullptr;
|
||||
GPU_framebuffer_ensure_config(&fb,
|
||||
{GPU_ATTACHMENT_NONE, GPU_ATTACHMENT_TEXTURE(display_texture)});
|
||||
GPU_framebuffer_bind(fb);
|
||||
|
||||
GPUVertFormat *imm_format = immVertexFormat();
|
||||
const uint pos = GPU_vertformat_attr_add(
|
||||
imm_format, "pos", blender::gpu::VertAttrType::SFLOAT_32_32);
|
||||
const uint tex_coord = GPU_vertformat_attr_add(
|
||||
imm_format, "texCoord", blender::gpu::VertAttrType::SFLOAT_32_32);
|
||||
|
||||
const ColorSpace *input_colorspace = input_ibuf->float_buffer.data ?
|
||||
input_ibuf->float_buffer.colorspace :
|
||||
input_ibuf->byte_buffer.colorspace;
|
||||
const bool predivide = input_ibuf->float_buffer.data != nullptr;
|
||||
if (IMB_colormanagement_setup_glsl_draw_from_space(
|
||||
&view_settings, &display_settings, input_colorspace, 0.0f, predivide, false))
|
||||
{
|
||||
GPU_texture_bind(input_texture, 0);
|
||||
const rctf position{0.0f, 1.0f, 0.0f, 1.0f};
|
||||
const rctf texture_coord{0.0f, 1.0f, 0.0f, 1.0f};
|
||||
immRectf_with_texco(pos, tex_coord, position, texture_coord);
|
||||
GPU_texture_unbind(input_texture);
|
||||
IMB_colormanagement_finish_glsl_draw();
|
||||
}
|
||||
|
||||
GPU_FRAMEBUFFER_FREE_SAFE(fb);
|
||||
|
||||
GPU_matrix_pop();
|
||||
GPU_matrix_pop_projection();
|
||||
|
||||
seq::preview_cache_set_gpu_display_texture(scene, timeline_frame, display_texture);
|
||||
}
|
||||
|
||||
static void update_cpu_scopes(const SpaceSeq &space_sequencer,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings,
|
||||
const ImBuf &ibuf,
|
||||
const int timeline_frame)
|
||||
|
||||
{
|
||||
if (space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF && space_sequencer.zebra == 0) {
|
||||
return false; /* Not drawing any scopes. */
|
||||
SeqScopes &scopes = space_sequencer.runtime->scopes;
|
||||
if (scopes.last_ibuf == &ibuf && scopes.last_timeline_frame == timeline_frame) {
|
||||
/* Nothing to do: scopes already calculated for this image/frame. */
|
||||
return;
|
||||
}
|
||||
|
||||
SeqScopes *scopes = &space_sequencer.runtime->scopes;
|
||||
if (scopes->reference_ibuf != &ibuf || scopes->timeline_frame != timeline_frame) {
|
||||
scopes->cleanup();
|
||||
scopes.cleanup();
|
||||
if (space_sequencer.mainb == SEQ_DRAW_IMG_HISTOGRAM) {
|
||||
scopes.histogram.calc_from_ibuf(&ibuf, view_settings, display_settings);
|
||||
}
|
||||
|
||||
switch (space_sequencer.mainb) {
|
||||
case SEQ_DRAW_IMG_IMBUF:
|
||||
if (!scopes->zebra_ibuf) {
|
||||
if (ibuf.float_buffer.data) {
|
||||
ImBuf *display_ibuf = IMB_dupImBuf(&ibuf);
|
||||
IMB_colormanagement_imbuf_make_display_space(
|
||||
display_ibuf, &view_settings, &display_settings);
|
||||
scopes->zebra_ibuf = make_zebra_view_from_ibuf(display_ibuf, space_sequencer.zebra);
|
||||
IMB_freeImBuf(display_ibuf);
|
||||
}
|
||||
else {
|
||||
scopes->zebra_ibuf = make_zebra_view_from_ibuf(&ibuf, space_sequencer.zebra);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SEQ_DRAW_IMG_WAVEFORM:
|
||||
if (!scopes->waveform_ibuf) {
|
||||
scopes->waveform_ibuf = make_waveform_view_from_ibuf(
|
||||
&ibuf, view_settings, display_settings);
|
||||
}
|
||||
break;
|
||||
case SEQ_DRAW_IMG_VECTORSCOPE:
|
||||
if (!scopes->vector_ibuf) {
|
||||
scopes->vector_ibuf = make_vectorscope_view_from_ibuf(
|
||||
&ibuf, view_settings, display_settings);
|
||||
}
|
||||
break;
|
||||
case SEQ_DRAW_IMG_HISTOGRAM: {
|
||||
scopes->histogram.calc_from_ibuf(&ibuf, view_settings, display_settings);
|
||||
} break;
|
||||
case SEQ_DRAW_IMG_RGBPARADE:
|
||||
if (!scopes->sep_waveform_ibuf) {
|
||||
scopes->sep_waveform_ibuf = make_sep_waveform_view_from_ibuf(
|
||||
&ibuf, view_settings, display_settings);
|
||||
}
|
||||
break;
|
||||
default: /* Future files might have scopes we don't know about. */
|
||||
return false;
|
||||
}
|
||||
scopes->reference_ibuf = &ibuf;
|
||||
return true;
|
||||
scopes.last_ibuf = &ibuf;
|
||||
scopes.last_timeline_frame = timeline_frame;
|
||||
}
|
||||
|
||||
static bool sequencer_draw_get_transform_preview(const SpaceSeq &sseq, const Scene &scene)
|
||||
@@ -866,7 +972,7 @@ static bool sequencer_draw_get_transform_preview(const SpaceSeq &sseq, const Sce
|
||||
(sseq.draw_flag & SEQ_DRAW_TRANSFORM_PREVIEW);
|
||||
}
|
||||
|
||||
int sequencer_draw_get_transform_preview_frame(const Scene *scene)
|
||||
static int sequencer_draw_get_transform_preview_frame(const Scene *scene)
|
||||
{
|
||||
Strip *last_seq = seq::select_active_get(scene);
|
||||
/* #sequencer_draw_get_transform_preview must already have been called. */
|
||||
@@ -1116,11 +1222,8 @@ static void sequencer_preview_draw_empty(ARegion ®ion)
|
||||
/* Begin drawing the sequence preview region.
|
||||
* Initializes the drawing state which is common for color render and overlay drawing.
|
||||
*
|
||||
* Returns true if the region is to be drawn. Example is when it is not to be drawn is when there
|
||||
* is ongoing offline rendering (to avoid possible threading conflict).
|
||||
*
|
||||
* If the function returns true preview_draw_end() is to be called after drawing is done. */
|
||||
static bool preview_draw_begin(const bContext *C,
|
||||
* #preview_draw_end() is to be called after drawing is done. */
|
||||
static void preview_draw_begin(const bContext *C,
|
||||
const RenderData &render_data,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings,
|
||||
@@ -1128,9 +1231,6 @@ static bool preview_draw_begin(const bContext *C,
|
||||
eSpaceSeq_RegionType preview_type)
|
||||
{
|
||||
sequencer_stop_running_jobs(C, CTX_data_sequencer_scene(C));
|
||||
if (G.is_rendering) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GPUViewport *viewport = WM_draw_region_get_bound_viewport(®ion);
|
||||
BLI_assert(viewport);
|
||||
@@ -1145,8 +1245,8 @@ static bool preview_draw_begin(const bContext *C,
|
||||
/* Setup view. */
|
||||
View2D &v2d = region.v2d;
|
||||
float viewrect[2];
|
||||
/* For histogram view, allow arbitrary zoom. */
|
||||
if (preview_type == SEQ_DRAW_IMG_HISTOGRAM) {
|
||||
/* For histogram and wave/parade scopes, allow arbitrary zoom. */
|
||||
if (ELEM(preview_type, SEQ_DRAW_IMG_HISTOGRAM, SEQ_DRAW_IMG_WAVEFORM, SEQ_DRAW_IMG_RGBPARADE)) {
|
||||
v2d.keepzoom &= ~(V2D_KEEPASPECT | V2D_KEEPZOOM);
|
||||
}
|
||||
else {
|
||||
@@ -1156,8 +1256,6 @@ static bool preview_draw_begin(const bContext *C,
|
||||
UI_view2d_totRect_set(&v2d, roundf(viewrect[0]), roundf(viewrect[1]));
|
||||
UI_view2d_curRect_validate(&v2d);
|
||||
UI_view2d_view_ortho(&v2d);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void preview_draw_end(const bContext *C)
|
||||
@@ -1504,11 +1602,17 @@ static void draw_registered_callbacks(const bContext *C, ARegion ®ion)
|
||||
GPU_framebuffer_bind_no_srgb(overlay_fb);
|
||||
}
|
||||
|
||||
static bool check_scope_needs_input_texture(const SpaceSeq &sseq)
|
||||
{
|
||||
return (sseq.mainb != SEQ_DRAW_IMG_HISTOGRAM) &&
|
||||
ELEM(sseq.view, SEQ_VIEW_PREVIEW, SEQ_VIEW_SEQUENCE_PREVIEW);
|
||||
}
|
||||
|
||||
/* Part of the sequencer preview region drawing which renders information overlays to the
|
||||
* viewport's overlay frame-buffer. */
|
||||
static void sequencer_preview_draw_overlays(const bContext *C,
|
||||
const wmWindowManager &wm,
|
||||
const Scene *scene,
|
||||
Scene *scene,
|
||||
const SpaceSeq &space_sequencer,
|
||||
const Editing &editing,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
@@ -1516,22 +1620,43 @@ static void sequencer_preview_draw_overlays(const bContext *C,
|
||||
ARegion ®ion,
|
||||
blender::gpu::Texture *current_texture,
|
||||
blender::gpu::Texture *reference_texture,
|
||||
const ImBuf *overlay_ibuf,
|
||||
const ImBuf *input_ibuf,
|
||||
const int timeline_frame)
|
||||
{
|
||||
const bool is_playing = ED_screen_animation_playing(&wm);
|
||||
const bool show_imbuf = check_show_imbuf(space_sequencer);
|
||||
const bool show_preview_image = space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF;
|
||||
const bool has_cpu_scope = input_ibuf && space_sequencer.mainb == SEQ_DRAW_IMG_HISTOGRAM;
|
||||
const bool has_gpu_scope = input_ibuf && current_texture &&
|
||||
((space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF &&
|
||||
space_sequencer.zebra != 0) ||
|
||||
ELEM(space_sequencer.mainb,
|
||||
SEQ_DRAW_IMG_WAVEFORM,
|
||||
SEQ_DRAW_IMG_RGBPARADE,
|
||||
SEQ_DRAW_IMG_VECTORSCOPE));
|
||||
|
||||
/* Update scopes before starting regular draw (GPU scopes update changes framebuffer, etc.). */
|
||||
space_sequencer.runtime->scopes.last_ibuf_float = input_ibuf &&
|
||||
input_ibuf->float_buffer.data != nullptr;
|
||||
if (has_cpu_scope) {
|
||||
update_cpu_scopes(
|
||||
space_sequencer, view_settings, display_settings, *input_ibuf, timeline_frame);
|
||||
}
|
||||
if (has_gpu_scope) {
|
||||
update_gpu_scopes(
|
||||
input_ibuf, current_texture, view_settings, display_settings, scene, timeline_frame);
|
||||
}
|
||||
|
||||
preview_draw_overlay_begin(region);
|
||||
|
||||
bool has_scopes = false;
|
||||
if (overlay_ibuf &&
|
||||
sequencer_calc_scopes(
|
||||
space_sequencer, view_settings, display_settings, *overlay_ibuf, timeline_frame))
|
||||
{
|
||||
if (has_cpu_scope || has_gpu_scope) {
|
||||
/* Draw scope. */
|
||||
sequencer_draw_scopes(space_sequencer, region);
|
||||
has_scopes = true;
|
||||
sequencer_draw_scopes(scene,
|
||||
space_sequencer,
|
||||
region,
|
||||
timeline_frame,
|
||||
input_ibuf->x,
|
||||
input_ibuf->y,
|
||||
input_ibuf->float_buffer.data != nullptr);
|
||||
}
|
||||
else if (space_sequencer.flag & SEQ_USE_ALPHA) {
|
||||
/* Draw checked-board. */
|
||||
@@ -1574,16 +1699,16 @@ static void sequencer_preview_draw_overlays(const bContext *C,
|
||||
}
|
||||
|
||||
/* Draw metadata. */
|
||||
if (!has_scopes && overlay_ibuf) {
|
||||
if (show_preview_image && input_ibuf) {
|
||||
if ((space_sequencer.preview_overlay.flag & SEQ_PREVIEW_SHOW_METADATA) &&
|
||||
(space_sequencer.flag & SEQ_SHOW_OVERLAY))
|
||||
{
|
||||
const View2D &v2d = region.v2d;
|
||||
ED_region_image_metadata_draw(0.0, 0.0, overlay_ibuf, &v2d.tot, 1.0, 1.0);
|
||||
ED_region_image_metadata_draw(0.0, 0.0, input_ibuf, &v2d.tot, 1.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (show_imbuf && (space_sequencer.flag & SEQ_SHOW_OVERLAY)) {
|
||||
if (show_preview_image && (space_sequencer.flag & SEQ_SHOW_OVERLAY)) {
|
||||
sequencer_draw_borders_overlay(space_sequencer, region.v2d, scene);
|
||||
|
||||
/* Various overlays like strip selection and text editing. */
|
||||
@@ -1599,9 +1724,7 @@ static void sequencer_preview_draw_overlays(const bContext *C,
|
||||
UI_view2d_view_restore(C);
|
||||
|
||||
/* No need to show the cursor for scopes. */
|
||||
if ((is_playing == false) && (space_sequencer.mainb == SEQ_DRAW_IMG_IMBUF) &&
|
||||
is_cursor_visible(space_sequencer))
|
||||
{
|
||||
if ((is_playing == false) && show_preview_image && is_cursor_visible(space_sequencer)) {
|
||||
GPU_color_mask(true, true, true, true);
|
||||
GPU_depth_mask(false);
|
||||
GPU_depth_test(GPU_DEPTH_NONE);
|
||||
@@ -1638,13 +1761,15 @@ static void sequencer_preview_draw_overlays(const bContext *C,
|
||||
|
||||
void sequencer_preview_region_draw(const bContext *C, ARegion *region)
|
||||
{
|
||||
const char *view_names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME};
|
||||
|
||||
const ScrArea *area = CTX_wm_area(C);
|
||||
const SpaceSeq &space_sequencer = *static_cast<const SpaceSeq *>(area->spacedata.first);
|
||||
const Scene *scene = CTX_data_sequencer_scene(C);
|
||||
Scene *scene = CTX_data_sequencer_scene(C);
|
||||
|
||||
if (!scene || !scene->ed || space_sequencer.render_size == SEQ_RENDER_SIZE_NONE) {
|
||||
/* Check if preview needs to be drawn at all. Note: do not draw preview region when
|
||||
* there is ongoing offline rendering, to avoid threading conflicts. */
|
||||
if (G.is_rendering || !scene || !scene->ed ||
|
||||
space_sequencer.render_size == SEQ_RENDER_SIZE_NONE)
|
||||
{
|
||||
sequencer_preview_draw_empty(*region);
|
||||
return;
|
||||
}
|
||||
@@ -1652,26 +1777,24 @@ void sequencer_preview_region_draw(const bContext *C, ARegion *region)
|
||||
const Editing &editing = *scene->ed;
|
||||
const RenderData &render_data = scene->r;
|
||||
|
||||
if (!preview_draw_begin(C,
|
||||
render_data,
|
||||
scene->view_settings,
|
||||
scene->display_settings,
|
||||
*region,
|
||||
eSpaceSeq_RegionType(space_sequencer.mainb)))
|
||||
{
|
||||
sequencer_preview_draw_empty(*region);
|
||||
return;
|
||||
}
|
||||
preview_draw_begin(C,
|
||||
render_data,
|
||||
scene->view_settings,
|
||||
scene->display_settings,
|
||||
*region,
|
||||
eSpaceSeq_RegionType(space_sequencer.mainb));
|
||||
|
||||
const bool show_imbuf = check_show_imbuf(space_sequencer);
|
||||
const bool use_gpu_texture = show_imbuf || check_scope_needs_input_texture(space_sequencer);
|
||||
|
||||
const bool draw_overlay = (space_sequencer.flag & SEQ_SHOW_OVERLAY);
|
||||
const bool draw_frame_overlay = (editing.overlay_frame_flag & SEQ_EDIT_OVERLAY_FRAME_SHOW) &&
|
||||
draw_overlay;
|
||||
const bool need_current_frame = !(draw_frame_overlay && (space_sequencer.overlay_frame_type ==
|
||||
SEQ_OVERLAY_FRAME_TYPE_REFERENCE));
|
||||
const bool need_reference_frame = draw_frame_overlay && space_sequencer.overlay_frame_type !=
|
||||
SEQ_OVERLAY_FRAME_TYPE_CURRENT;
|
||||
const bool need_reference_frame = show_imbuf && draw_frame_overlay &&
|
||||
space_sequencer.overlay_frame_type !=
|
||||
SEQ_OVERLAY_FRAME_TYPE_CURRENT;
|
||||
|
||||
int timeline_frame = render_data.cfra;
|
||||
if (sequencer_draw_get_transform_preview(space_sequencer, *scene)) {
|
||||
@@ -1690,13 +1813,7 @@ void sequencer_preview_region_draw(const bContext *C, ARegion *region)
|
||||
* Additionally, some image buffers might be needed for both color render and overlay drawing. */
|
||||
ImBuf *current_ibuf = nullptr;
|
||||
ImBuf *reference_ibuf = nullptr;
|
||||
if (need_current_frame) {
|
||||
current_ibuf = sequencer_ibuf_get(
|
||||
C, timeline_frame, view_names[space_sequencer.multiview_eye]);
|
||||
if (show_imbuf && current_ibuf) {
|
||||
current_texture = create_texture(*current_ibuf);
|
||||
}
|
||||
}
|
||||
const char *view_names[2] = {STEREO_LEFT_NAME, STEREO_RIGHT_NAME};
|
||||
if (need_reference_frame) {
|
||||
const int offset = get_reference_frame_offset(editing, render_data);
|
||||
reference_ibuf = sequencer_ibuf_get(
|
||||
@@ -1705,6 +1822,17 @@ void sequencer_preview_region_draw(const bContext *C, ARegion *region)
|
||||
reference_texture = create_texture(*reference_ibuf);
|
||||
}
|
||||
}
|
||||
if (need_current_frame) {
|
||||
current_ibuf = sequencer_ibuf_get(
|
||||
C, timeline_frame, view_names[space_sequencer.multiview_eye]);
|
||||
if (use_gpu_texture && current_ibuf) {
|
||||
current_texture = seq::preview_cache_get_gpu_texture(scene, timeline_frame);
|
||||
if (current_texture == nullptr) {
|
||||
current_texture = create_texture(*current_ibuf);
|
||||
seq::preview_cache_set_gpu_texture(scene, timeline_frame, current_texture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Image buffer used for overlays: scopes, metadata etc. */
|
||||
ImBuf *overlay_ibuf = need_current_frame ? current_ibuf : reference_ibuf;
|
||||
@@ -1714,9 +1842,9 @@ void sequencer_preview_region_draw(const bContext *C, ARegion *region)
|
||||
editing,
|
||||
*region,
|
||||
current_ibuf,
|
||||
current_texture,
|
||||
show_imbuf ? current_texture : nullptr,
|
||||
reference_ibuf,
|
||||
reference_texture);
|
||||
show_imbuf ? reference_texture : nullptr);
|
||||
sequencer_preview_draw_overlays(C,
|
||||
*CTX_wm_manager(C),
|
||||
scene,
|
||||
@@ -1734,10 +1862,8 @@ void sequencer_preview_region_draw(const bContext *C, ARegion *region)
|
||||
sequencer_draw_maskedit(C, scene, region, sseq);
|
||||
#endif
|
||||
|
||||
/* Free textures. */
|
||||
if (current_texture) {
|
||||
GPU_texture_free(current_texture);
|
||||
}
|
||||
/* Free GPU textures. Note that the #current_texture is kept around via #preview_set_gpu_texture,
|
||||
* for other preview areas or frames if nothing changes between them. */
|
||||
if (reference_texture) {
|
||||
GPU_texture_free(reference_texture);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* SPDX-FileCopyrightText: 2006-2008 Peter Schlaile < peter [at] schlaile [dot] de >.
|
||||
* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
@@ -6,24 +7,14 @@
|
||||
* \ingroup spseq
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include "BLI_math_vector.hh"
|
||||
#include "BLI_task.hh"
|
||||
|
||||
#include "IMB_colormanagement.hh"
|
||||
#include "IMB_imbuf.hh"
|
||||
#include "IMB_imbuf_types.hh"
|
||||
|
||||
#include "sequencer_scopes.hh"
|
||||
|
||||
// #define DEBUG_TIME
|
||||
|
||||
#ifdef DEBUG_TIME
|
||||
# include "BLI_timeit.hh"
|
||||
#endif
|
||||
|
||||
namespace blender::ed::vse {
|
||||
|
||||
SeqScopes::~SeqScopes()
|
||||
@@ -33,78 +24,9 @@ SeqScopes::~SeqScopes()
|
||||
|
||||
void SeqScopes::cleanup()
|
||||
{
|
||||
if (zebra_ibuf) {
|
||||
IMB_freeImBuf(zebra_ibuf);
|
||||
zebra_ibuf = nullptr;
|
||||
}
|
||||
if (waveform_ibuf) {
|
||||
IMB_freeImBuf(waveform_ibuf);
|
||||
waveform_ibuf = nullptr;
|
||||
}
|
||||
if (sep_waveform_ibuf) {
|
||||
IMB_freeImBuf(sep_waveform_ibuf);
|
||||
sep_waveform_ibuf = nullptr;
|
||||
}
|
||||
if (vector_ibuf) {
|
||||
IMB_freeImBuf(vector_ibuf);
|
||||
vector_ibuf = nullptr;
|
||||
}
|
||||
histogram.data.reinitialize(0);
|
||||
reference_ibuf = nullptr;
|
||||
timeline_frame = 0;
|
||||
}
|
||||
|
||||
static blender::float2 rgb_to_uv_normalized(const float rgb[3])
|
||||
{
|
||||
/* Exact same math as rgb_to_yuv BT709 case. Duplicated here
|
||||
* since this function is called a lot, and non-inline function
|
||||
* call plus color-space switch in there overhead does add up. */
|
||||
float r = rgb[0], g = rgb[1], b = rgb[2];
|
||||
/* We don't need Y. */
|
||||
float u = -0.09991f * r - 0.33609f * g + 0.436f * b;
|
||||
float v = 0.615f * r - 0.55861f * g - 0.05639f * b;
|
||||
|
||||
/* Normalize to 0..1 range. */
|
||||
u = clamp_f(u * SeqScopes::VECSCOPE_U_SCALE + 0.5f, 0.0f, 1.0f);
|
||||
v = clamp_f(v * SeqScopes::VECSCOPE_V_SCALE + 0.5f, 0.0f, 1.0f);
|
||||
return float2(u, v);
|
||||
}
|
||||
|
||||
static void scope_put_pixel(const uchar *table, uchar *pos)
|
||||
{
|
||||
uchar newval = table[*pos];
|
||||
pos[0] = pos[1] = pos[2] = newval;
|
||||
pos[3] = 255;
|
||||
}
|
||||
|
||||
static void scope_put_pixel_single(const uchar *table, uchar *pos, int col)
|
||||
{
|
||||
uint newval = table[pos[col]];
|
||||
/* So that the separate waveforms are not just pure RGB primaries, put
|
||||
* some amount of value into the other channels too: slightly reduce it,
|
||||
* and raise to 4th power. */
|
||||
uint other = newval * 31 / 32;
|
||||
other = (other * other) >> 8;
|
||||
other = (other * other) >> 8;
|
||||
pos[0] = pos[1] = pos[2] = uchar(other);
|
||||
pos[col] = uchar(newval);
|
||||
pos[3] = 255;
|
||||
}
|
||||
|
||||
static void init_wave_table(int height, uchar wtable[256])
|
||||
{
|
||||
/* For each pixel column of the image, waveform plots the intensity values
|
||||
* with height proportional to the intensity. So depending on the height of
|
||||
* the image, different amount of pixels are expected to hit the same
|
||||
* intensity. Adjust the waveform plotting table gamma factor so that
|
||||
* the waveform has decent visibility without saturating or being too dark:
|
||||
* 0.3 gamma at height=360 and below, 0.9 gamma at height 2160 (4K) and up,
|
||||
* and interpolating between those. */
|
||||
float alpha = clamp_f(ratiof(360.0f, 2160.0f, height), 0.0f, 1.0f);
|
||||
float gamma = interpf(0.9f, 0.3f, alpha);
|
||||
for (int x = 0; x < 256; x++) {
|
||||
wtable[x] = uchar(pow((float(x) + 1.0f) / 256.0f, gamma) * 255.0f);
|
||||
}
|
||||
last_ibuf = nullptr;
|
||||
last_timeline_frame = 0;
|
||||
}
|
||||
|
||||
static void rgba_float_to_display_space(ColormanageProcessor *processor,
|
||||
@@ -146,253 +68,10 @@ static Array<float4> pixels_to_display_space(ColormanageProcessor *processor,
|
||||
return result;
|
||||
}
|
||||
|
||||
ImBuf *make_waveform_view_from_ibuf(const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings)
|
||||
{
|
||||
#ifdef DEBUG_TIME
|
||||
SCOPED_TIMER(__func__);
|
||||
#endif
|
||||
const int w = ibuf->x;
|
||||
const int h = 256;
|
||||
ImBuf *rval = IMB_allocImBuf(w, h, 32, IB_byte_data);
|
||||
uchar *tgt = rval->byte_buffer.data;
|
||||
|
||||
uchar wtable[256];
|
||||
init_wave_table(ibuf->y, wtable);
|
||||
|
||||
ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_for_imbuf(
|
||||
ibuf, &view_settings, &display_settings);
|
||||
|
||||
/* IMB_colormanagement_get_luminance_byte for each pixel is quite a lot of
|
||||
* overhead, so instead get luma coefficients as 16-bit integers. */
|
||||
float coeffs[3];
|
||||
IMB_colormanagement_get_luminance_coefficients(coeffs);
|
||||
const int muls[3] = {int(coeffs[0] * 65535), int(coeffs[1] * 65535), int(coeffs[2] * 65535)};
|
||||
|
||||
/* Parallel over x, since each column is easily independent from others. */
|
||||
threading::parallel_for_each(IndexRange(ibuf->x), [&](const int x) {
|
||||
if (ibuf->float_buffer.data) {
|
||||
const float *src = ibuf->float_buffer.data + x * 4;
|
||||
if (!cm_processor) {
|
||||
/* Float image, no color space conversions needed. */
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
float4 pixel;
|
||||
premul_to_straight_v4_v4(pixel, src);
|
||||
float v = dot_v3v3(pixel, coeffs);
|
||||
uchar *p = tgt;
|
||||
int iv = clamp_i(int(v * h), 0, h - 1);
|
||||
p += 4 * (w * iv + x);
|
||||
scope_put_pixel(wtable, p);
|
||||
src += ibuf->x * 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Float image, with color space conversions. */
|
||||
Array<float4> pixels = pixels_to_display_space(
|
||||
cm_processor, ibuf->float_buffer.colorspace, ibuf->y, src, ibuf->x * 4);
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
float v = dot_v3v3(pixels[y], coeffs);
|
||||
uchar *p = tgt;
|
||||
int iv = clamp_i(int(v * h), 0, h - 1);
|
||||
p += 4 * (w * iv + x);
|
||||
scope_put_pixel(wtable, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const uchar *src = ibuf->byte_buffer.data + x * 4;
|
||||
if (!cm_processor) {
|
||||
/* Byte image, no color space conversions needed. */
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
/* +1 is "Sree's solution" from http://stereopsis.com/doubleblend.html */
|
||||
int rgb0 = src[0] + 1;
|
||||
int rgb1 = src[1] + 1;
|
||||
int rgb2 = src[2] + 1;
|
||||
int luma = (rgb0 * muls[0] + rgb1 * muls[1] + rgb2 * muls[2]) >> 16;
|
||||
int luma_y = clamp_i(luma, 0, 255);
|
||||
uchar *p = tgt + 4 * (w * luma_y + x);
|
||||
scope_put_pixel(wtable, p);
|
||||
src += ibuf->x * 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Byte image, with color space conversions. */
|
||||
Array<float4> pixels = pixels_to_display_space(
|
||||
cm_processor, ibuf->byte_buffer.colorspace, ibuf->y, src, ibuf->x * 4);
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
float v = dot_v3v3(pixels[y], coeffs);
|
||||
uchar *p = tgt;
|
||||
int iv = clamp_i(int(v * h), 0, h - 1);
|
||||
p += 4 * (w * iv + x);
|
||||
scope_put_pixel(wtable, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (cm_processor) {
|
||||
IMB_colormanagement_processor_free(cm_processor);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
ImBuf *make_sep_waveform_view_from_ibuf(const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings)
|
||||
{
|
||||
#ifdef DEBUG_TIME
|
||||
SCOPED_TIMER(__func__);
|
||||
#endif
|
||||
int w = ibuf->x;
|
||||
int h = 256;
|
||||
ImBuf *rval = IMB_allocImBuf(w, h, 32, IB_byte_data);
|
||||
uchar *tgt = rval->byte_buffer.data;
|
||||
int sw = ibuf->x / 3;
|
||||
|
||||
uchar wtable[256];
|
||||
init_wave_table(ibuf->y, wtable);
|
||||
|
||||
ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_for_imbuf(
|
||||
ibuf, &view_settings, &display_settings);
|
||||
|
||||
/* Parallel over x, since each column is easily independent from others. */
|
||||
threading::parallel_for_each(IndexRange(ibuf->x), [&](const int x) {
|
||||
if (ibuf->float_buffer.data) {
|
||||
const float *src = ibuf->float_buffer.data + x * 4;
|
||||
if (!cm_processor) {
|
||||
/* Float image, no color space conversions needed. */
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
float4 pixel;
|
||||
premul_to_straight_v4_v4(pixel, src);
|
||||
for (int c = 0; c < 3; c++) {
|
||||
uchar *p = tgt;
|
||||
float v = pixel[c];
|
||||
int iv = clamp_i(int(v * h), 0, h - 1);
|
||||
p += 4 * (w * iv + c * sw + x / 3);
|
||||
scope_put_pixel_single(wtable, p, c);
|
||||
}
|
||||
src += ibuf->x * 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Float image, with color space conversions. */
|
||||
Array<float4> pixels = pixels_to_display_space(
|
||||
cm_processor, ibuf->float_buffer.colorspace, ibuf->y, src, ibuf->x * 4);
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
float4 pixel = pixels[y];
|
||||
for (int c = 0; c < 3; c++) {
|
||||
uchar *p = tgt;
|
||||
float v = pixel[c];
|
||||
int iv = clamp_i(int(v * h), 0, h - 1);
|
||||
p += 4 * (w * iv + c * sw + x / 3);
|
||||
scope_put_pixel_single(wtable, p, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
const uchar *src = ibuf->byte_buffer.data + x * 4;
|
||||
if (!cm_processor) {
|
||||
/* Byte image, no color space conversions needed. */
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
uchar *p = tgt;
|
||||
p += 4 * (w * src[c] + c * sw + x / 3);
|
||||
scope_put_pixel_single(wtable, p, c);
|
||||
}
|
||||
src += ibuf->x * 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Byte image, with color space conversions. */
|
||||
Array<float4> pixels = pixels_to_display_space(
|
||||
cm_processor, ibuf->byte_buffer.colorspace, ibuf->y, src, ibuf->x * 4);
|
||||
for (int y = 0; y < ibuf->y; y++) {
|
||||
float4 pixel = pixels[y];
|
||||
for (int c = 0; c < 3; c++) {
|
||||
uchar *p = tgt;
|
||||
float v = pixel[c];
|
||||
int iv = clamp_i(int(v * h), 0, h - 1);
|
||||
p += 4 * (w * iv + c * sw + x / 3);
|
||||
scope_put_pixel_single(wtable, p, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (cm_processor) {
|
||||
IMB_colormanagement_processor_free(cm_processor);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
ImBuf *make_zebra_view_from_ibuf(const ImBuf *ibuf, float perc)
|
||||
{
|
||||
#ifdef DEBUG_TIME
|
||||
SCOPED_TIMER(__func__);
|
||||
#endif
|
||||
ImBuf *res = IMB_allocImBuf(ibuf->x, ibuf->y, 32, IB_byte_data | IB_uninitialized_pixels);
|
||||
|
||||
threading::parallel_for(IndexRange(ibuf->y), 16, [&](IndexRange y_range) {
|
||||
if (ibuf->float_buffer.data) {
|
||||
/* Float image. */
|
||||
const float limit = perc / 100.0f;
|
||||
const float *p = ibuf->float_buffer.data + y_range.first() * ibuf->x * 4;
|
||||
uchar *o = res->byte_buffer.data + y_range.first() * ibuf->x * 4;
|
||||
for (const int y : y_range) {
|
||||
for (int x = 0; x < ibuf->x; x++) {
|
||||
float pix[4];
|
||||
memcpy(pix, p, sizeof(pix));
|
||||
if (pix[0] >= limit || pix[1] >= limit || pix[2] >= limit) {
|
||||
if (((x + y) & 0x08) != 0) {
|
||||
pix[0] = 1.0f - pix[0];
|
||||
pix[1] = 1.0f - pix[1];
|
||||
pix[2] = 1.0f - pix[2];
|
||||
}
|
||||
}
|
||||
rgba_float_to_uchar(o, pix);
|
||||
p += 4;
|
||||
o += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Byte image. */
|
||||
const uint limit = 255.0f * perc / 100.0f;
|
||||
const uchar *p = ibuf->byte_buffer.data + y_range.first() * ibuf->x * 4;
|
||||
uchar *o = res->byte_buffer.data + y_range.first() * ibuf->x * 4;
|
||||
for (const int y : y_range) {
|
||||
for (int x = 0; x < ibuf->x; x++) {
|
||||
uchar pix[4];
|
||||
memcpy(pix, p, sizeof(pix));
|
||||
|
||||
if (pix[0] >= limit || pix[1] >= limit || pix[2] >= limit) {
|
||||
if (((x + y) & 0x08) != 0) {
|
||||
pix[0] = 255 - pix[0];
|
||||
pix[1] = 255 - pix[1];
|
||||
pix[2] = 255 - pix[2];
|
||||
}
|
||||
}
|
||||
memcpy(o, pix, sizeof(pix));
|
||||
p += 4;
|
||||
o += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return res;
|
||||
}
|
||||
|
||||
void ScopeHistogram::calc_from_ibuf(const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings)
|
||||
{
|
||||
#ifdef DEBUG_TIME
|
||||
SCOPED_TIMER(__func__);
|
||||
#endif
|
||||
|
||||
ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_for_imbuf(
|
||||
ibuf, &view_settings, &display_settings);
|
||||
|
||||
@@ -491,123 +170,4 @@ void ScopeHistogram::calc_from_ibuf(const ImBuf *ibuf,
|
||||
}
|
||||
}
|
||||
|
||||
ImBuf *make_vectorscope_view_from_ibuf(const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings)
|
||||
{
|
||||
#ifdef DEBUG_TIME
|
||||
SCOPED_TIMER(__func__);
|
||||
#endif
|
||||
constexpr int size = 512;
|
||||
const float size_mul = size - 1.0f;
|
||||
|
||||
ColormanageProcessor *cm_processor = IMB_colormanagement_display_processor_for_imbuf(
|
||||
ibuf, &view_settings, &display_settings);
|
||||
|
||||
const bool is_float = ibuf->float_buffer.data != nullptr;
|
||||
/* Vector scope is calculated by scattering writes into the resulting scope image. Do it with
|
||||
* parallel reduce, by filling a separate image per job and merging them. Since the payload
|
||||
* of each job is fairly large, make the jobs large enough too. */
|
||||
constexpr int64_t grain_size = 256 * 1024;
|
||||
Array<uint8_t> counts(size * size, uint8_t(0));
|
||||
Array<uint8_t> data = threading::parallel_reduce(
|
||||
IndexRange(IMB_get_pixel_count(ibuf)),
|
||||
grain_size,
|
||||
counts,
|
||||
[&](const IndexRange range, const Array<uint8_t> &init) {
|
||||
Array<uint8_t> res = init;
|
||||
|
||||
const float *src_f = is_float ? ibuf->float_buffer.data + range.first() * 4 : nullptr;
|
||||
const uchar *src_b = !is_float ? ibuf->byte_buffer.data + range.first() * 4 : nullptr;
|
||||
if (cm_processor) {
|
||||
/* Byte or float image, color space conversions needed. Do them in smaller chunks
|
||||
* than the whole job size, so they fit into CPU cache and can be on the stack. */
|
||||
constexpr int64_t chunk_size = 4 * 1024;
|
||||
float4 pixels[chunk_size];
|
||||
for (int64_t index = 0; index < range.size(); index += chunk_size) {
|
||||
const int64_t sub_size = std::min(range.size() - index, chunk_size);
|
||||
if (is_float) {
|
||||
for (int64_t i = 0; i < sub_size; i++) {
|
||||
premul_to_straight_v4_v4(pixels[i], src_f);
|
||||
src_f += 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int64_t i = 0; i < sub_size; i++) {
|
||||
rgba_uchar_to_float(pixels[i], src_b);
|
||||
src_b += 4;
|
||||
}
|
||||
}
|
||||
MutableSpan<float4> pixels_span = MutableSpan<float4>(pixels, sub_size);
|
||||
rgba_float_to_display_space(cm_processor,
|
||||
is_float ? ibuf->float_buffer.colorspace :
|
||||
ibuf->byte_buffer.colorspace,
|
||||
pixels_span);
|
||||
for (float4 pixel : pixels_span) {
|
||||
clamp_v3(pixel, 0.0f, 1.0f);
|
||||
float2 uv = rgb_to_uv_normalized(pixel) * size_mul;
|
||||
int offset = size * int(uv.y) + int(uv.x);
|
||||
res[offset] = std::min<int>(res[offset] + 1, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (is_float) {
|
||||
/* Float image, no color space conversions needed. */
|
||||
for ([[maybe_unused]] const int64_t index : range) {
|
||||
float4 pixel;
|
||||
premul_to_straight_v4_v4(pixel, src_f);
|
||||
clamp_v3(pixel, 0.0f, 1.0f);
|
||||
float2 uv = rgb_to_uv_normalized(pixel) * size_mul;
|
||||
int offset = size * int(uv.y) + int(uv.x);
|
||||
res[offset] = std::min<int>(res[offset] + 1, 255);
|
||||
src_f += 4;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* Byte image, no color space conversions needed. */
|
||||
for ([[maybe_unused]] const int64_t index : range) {
|
||||
float4 pixel;
|
||||
rgb_uchar_to_float(pixel, src_b);
|
||||
float2 uv = rgb_to_uv_normalized(pixel) * size_mul;
|
||||
int offset = size * int(uv.y) + int(uv.x);
|
||||
res[offset] = std::min<int>(res[offset] + 1, 255);
|
||||
src_b += 4;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
},
|
||||
/* Merge scopes computed per-thread. */
|
||||
[&](const Array<uint8_t> &a, const Array<uint8_t> &b) {
|
||||
BLI_assert(a.size() == b.size());
|
||||
Array<uint8_t> res(a.size(), NoInitialization());
|
||||
for (int64_t i : a.index_range()) {
|
||||
res[i] = std::min<int>(a[i] + b[i], 255);
|
||||
}
|
||||
return res;
|
||||
});
|
||||
|
||||
/* Fill the vector scope image from the computed data. */
|
||||
uchar wtable[256];
|
||||
init_wave_table(math::midpoint(ibuf->x, ibuf->y), wtable);
|
||||
|
||||
ImBuf *rval = IMB_allocImBuf(size, size, 32, IB_byte_data | IB_uninitialized_pixels);
|
||||
uchar *dst = rval->byte_buffer.data;
|
||||
for (int i = 0; i < size * size; i++) {
|
||||
uint8_t val = data[i];
|
||||
if (val != 0) {
|
||||
val = wtable[val];
|
||||
}
|
||||
dst[0] = val;
|
||||
dst[1] = val;
|
||||
dst[2] = val;
|
||||
dst[3] = 255;
|
||||
dst += 4;
|
||||
}
|
||||
|
||||
if (cm_processor) {
|
||||
IMB_colormanagement_processor_free(cm_processor);
|
||||
}
|
||||
return rval;
|
||||
}
|
||||
|
||||
} // namespace blender::ed::vse
|
||||
|
||||
@@ -51,12 +51,9 @@ struct SeqScopes : public NonCopyable {
|
||||
static constexpr float VECSCOPE_U_SCALE = 0.5f / 0.436f;
|
||||
static constexpr float VECSCOPE_V_SCALE = 0.5f / 0.615f;
|
||||
|
||||
const ImBuf *reference_ibuf = nullptr;
|
||||
int timeline_frame = 0;
|
||||
ImBuf *zebra_ibuf = nullptr;
|
||||
ImBuf *waveform_ibuf = nullptr;
|
||||
ImBuf *sep_waveform_ibuf = nullptr;
|
||||
ImBuf *vector_ibuf = nullptr;
|
||||
const ImBuf *last_ibuf = nullptr;
|
||||
int last_timeline_frame = 0;
|
||||
bool last_ibuf_float = false;
|
||||
ScopeHistogram histogram;
|
||||
|
||||
SeqScopes() = default;
|
||||
@@ -65,15 +62,4 @@ struct SeqScopes : public NonCopyable {
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
ImBuf *make_waveform_view_from_ibuf(const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings);
|
||||
ImBuf *make_sep_waveform_view_from_ibuf(const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings);
|
||||
ImBuf *make_vectorscope_view_from_ibuf(const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings &view_settings,
|
||||
const ColorManagedDisplaySettings &display_settings);
|
||||
ImBuf *make_zebra_view_from_ibuf(const ImBuf *ibuf, float perc);
|
||||
|
||||
} // namespace blender::ed::vse
|
||||
|
||||
@@ -171,32 +171,51 @@ void SEQUENCER_OT_view_frame(wmOperatorType *ot)
|
||||
/** \} */
|
||||
|
||||
/* For frame all/selected operators, when we are in preview region
|
||||
* with histogram display mode, frame the extents of the histogram. */
|
||||
static bool view_frame_preview_histogram(bContext *C, wmOperator *op, ARegion *region)
|
||||
* with histogram/waveform display mode, frame the extents of the scope. */
|
||||
static bool view_frame_preview_scope(bContext *C, wmOperator *op, ARegion *region)
|
||||
{
|
||||
if (!region || region->regiontype != RGN_TYPE_PREVIEW) {
|
||||
return false;
|
||||
}
|
||||
SpaceSeq *sseq = CTX_wm_space_seq(C);
|
||||
if (!sseq || sseq->mainb != SEQ_DRAW_IMG_HISTOGRAM) {
|
||||
if (!sseq) {
|
||||
return false;
|
||||
}
|
||||
const vse::ScopeHistogram &hist = sseq->runtime->scopes.histogram;
|
||||
if (hist.data.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const View2D *v2d = UI_view2d_fromcontext(C);
|
||||
rctf cur_new = v2d->tot;
|
||||
const float val_max = ScopeHistogram::bin_to_float(math::reduce_max(hist.max_bin));
|
||||
cur_new.xmax = cur_new.xmin + (cur_new.xmax - cur_new.xmin) * val_max;
|
||||
|
||||
/* Add some padding around whole histogram. */
|
||||
BLI_rctf_scale(&cur_new, 1.1f);
|
||||
|
||||
const int smooth_viewtx = WM_operator_smooth_viewtx_get(op);
|
||||
UI_view2d_smooth_view(C, region, &cur_new, smooth_viewtx);
|
||||
return true;
|
||||
|
||||
if (sseq->mainb == SEQ_DRAW_IMG_HISTOGRAM) {
|
||||
/* For histogram scope, use extents of the histogram. */
|
||||
const vse::ScopeHistogram &hist = sseq->runtime->scopes.histogram;
|
||||
if (hist.data.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rctf cur_new = v2d->tot;
|
||||
const float val_max = ScopeHistogram::bin_to_float(math::reduce_max(hist.max_bin));
|
||||
cur_new.xmax = cur_new.xmin + (cur_new.xmax - cur_new.xmin) * val_max;
|
||||
|
||||
/* Add some padding around whole histogram. */
|
||||
BLI_rctf_scale(&cur_new, 1.1f);
|
||||
|
||||
UI_view2d_smooth_view(C, region, &cur_new, smooth_viewtx);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ELEM(sseq->mainb, SEQ_DRAW_IMG_WAVEFORM, SEQ_DRAW_IMG_RGBPARADE)) {
|
||||
/* For waveform/parade scopes, use 3.0 display space Y value as bounds
|
||||
* for HDR content. */
|
||||
const bool hdr = sseq->runtime->scopes.last_ibuf_float;
|
||||
rctf cur_new = v2d->tot;
|
||||
if (hdr) {
|
||||
const float val_max = 3.0f;
|
||||
cur_new.ymax = cur_new.ymin + (cur_new.ymax - cur_new.ymin) * val_max;
|
||||
}
|
||||
UI_view2d_smooth_view(C, region, &cur_new, smooth_viewtx);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
@@ -209,7 +228,7 @@ static wmOperatorStatus sequencer_view_all_preview_exec(bContext *C, wmOperator
|
||||
bScreen *screen = CTX_wm_screen(C);
|
||||
ScrArea *area = CTX_wm_area(C);
|
||||
|
||||
if (view_frame_preview_histogram(C, op, CTX_wm_region(C))) {
|
||||
if (view_frame_preview_scope(C, op, CTX_wm_region(C))) {
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
@@ -429,7 +448,7 @@ static wmOperatorStatus sequencer_view_selected_exec(bContext *C, wmOperator *op
|
||||
View2D *v2d = UI_view2d_fromcontext(C);
|
||||
rctf cur_new = v2d->cur;
|
||||
|
||||
if (view_frame_preview_histogram(C, op, region)) {
|
||||
if (view_frame_preview_scope(C, op, region)) {
|
||||
return OPERATOR_FINISHED;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
#include "SEQ_channels.hh"
|
||||
#include "SEQ_offscreen.hh"
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_retiming.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_time.hh"
|
||||
@@ -68,10 +69,11 @@ namespace blender::ed::vse {
|
||||
|
||||
/**************************** common state *****************************/
|
||||
|
||||
static void sequencer_scopes_tag_refresh(ScrArea *area)
|
||||
static void sequencer_scopes_tag_refresh(ScrArea *area, const Scene *scene)
|
||||
{
|
||||
SpaceSeq *sseq = (SpaceSeq *)area->spacedata.first;
|
||||
sseq->runtime->scopes.reference_ibuf = nullptr;
|
||||
sseq->runtime->scopes.cleanup();
|
||||
seq::preview_cache_invalidate(const_cast<Scene *>(scene));
|
||||
}
|
||||
|
||||
SpaceSeq_Runtime::~SpaceSeq_Runtime() = default;
|
||||
@@ -302,14 +304,14 @@ static void sequencer_listener(const wmSpaceTypeListenerParams *params)
|
||||
switch (wmn->data) {
|
||||
case ND_FRAME:
|
||||
case ND_SEQUENCER:
|
||||
sequencer_scopes_tag_refresh(area);
|
||||
sequencer_scopes_tag_refresh(area, params->scene);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case NC_WINDOW:
|
||||
case NC_SPACE:
|
||||
if (wmn->data == ND_SPACE_SEQUENCER) {
|
||||
sequencer_scopes_tag_refresh(area);
|
||||
sequencer_scopes_tag_refresh(area, params->scene);
|
||||
}
|
||||
break;
|
||||
case NC_GPENCIL:
|
||||
|
||||
@@ -506,10 +506,13 @@ set(GLSL_SRC
|
||||
shaders/gpu_shader_keyframe_shape_vert.glsl
|
||||
shaders/gpu_shader_keyframe_shape_frag.glsl
|
||||
|
||||
shaders/gpu_shader_sequencer_scope_comp.glsl
|
||||
shaders/gpu_shader_sequencer_scope_frag.glsl
|
||||
shaders/gpu_shader_sequencer_strips_vert.glsl
|
||||
shaders/gpu_shader_sequencer_strips_frag.glsl
|
||||
shaders/gpu_shader_sequencer_thumbs_vert.glsl
|
||||
shaders/gpu_shader_sequencer_thumbs_frag.glsl
|
||||
shaders/gpu_shader_sequencer_zebra_frag.glsl
|
||||
|
||||
shaders/gpu_shader_codegen_lib.glsl
|
||||
|
||||
|
||||
@@ -85,6 +85,12 @@ enum eGPUBuiltinShader {
|
||||
GPU_SHADER_SEQUENCER_STRIPS,
|
||||
/** Draw strip thumbnails in sequencer timeline. */
|
||||
GPU_SHADER_SEQUENCER_THUMBS,
|
||||
/** Rasterize sequencer scope points into buffers via compute. */
|
||||
GPU_SHADER_SEQUENCER_SCOPE_RASTER,
|
||||
/** Resolve rasterized scope point buffers to display. */
|
||||
GPU_SHADER_SEQUENCER_SCOPE_RESOLVE,
|
||||
/** Draw sequencer zebra pattern (overexposed regions). */
|
||||
GPU_SHADER_SEQUENCER_ZEBRA,
|
||||
|
||||
/** Compute shaders to generate 2d index buffers (mainly for curve drawing). */
|
||||
GPU_SHADER_INDEXBUF_POINTS,
|
||||
|
||||
@@ -171,6 +171,14 @@ struct SeqContextDrawData {
|
||||
};
|
||||
BLI_STATIC_ASSERT_ALIGN(SeqContextDrawData, 16)
|
||||
|
||||
/* VSE scope point rasterizer data. */
|
||||
struct SeqScopeRasterData {
|
||||
uint col_r;
|
||||
uint col_g;
|
||||
uint col_b;
|
||||
uint col_a;
|
||||
};
|
||||
|
||||
struct GreasePencilStrokeData {
|
||||
packed_float3 position;
|
||||
float stroke_thickness;
|
||||
|
||||
@@ -109,6 +109,12 @@ static const char *builtin_shader_create_info_name(eGPUBuiltinShader shader)
|
||||
return "gpu_shader_sequencer_strips";
|
||||
case GPU_SHADER_SEQUENCER_THUMBS:
|
||||
return "gpu_shader_sequencer_thumbs";
|
||||
case GPU_SHADER_SEQUENCER_SCOPE_RASTER:
|
||||
return "gpu_shader_sequencer_scope_raster";
|
||||
case GPU_SHADER_SEQUENCER_SCOPE_RESOLVE:
|
||||
return "gpu_shader_sequencer_scope_resolve";
|
||||
case GPU_SHADER_SEQUENCER_ZEBRA:
|
||||
return "gpu_shader_sequencer_zebra";
|
||||
case GPU_SHADER_INDEXBUF_POINTS:
|
||||
return "gpu_shader_index_2d_array_points";
|
||||
case GPU_SHADER_INDEXBUF_LINES:
|
||||
|
||||
@@ -69,14 +69,17 @@ set(SRC_GLSL_FRAG
|
||||
gpu_shader_point_uniform_color_aa_frag.glsl
|
||||
gpu_shader_point_uniform_color_outline_aa_frag.glsl
|
||||
gpu_shader_point_varying_color_frag.glsl
|
||||
gpu_shader_sequencer_scope_frag.glsl
|
||||
gpu_shader_sequencer_strips_frag.glsl
|
||||
gpu_shader_sequencer_thumbs_frag.glsl
|
||||
gpu_shader_sequencer_zebra_frag.glsl
|
||||
gpu_shader_simple_lighting_frag.glsl
|
||||
gpu_shader_text_frag.glsl
|
||||
gpu_shader_uniform_color_frag.glsl
|
||||
)
|
||||
|
||||
set(SRC_GLSL_COMP
|
||||
gpu_shader_sequencer_scope_comp.glsl
|
||||
# TODO rename them properly to enable compilation.
|
||||
# gpu_shader_index_2d_array_lines.glsl
|
||||
# gpu_shader_index_2d_array_points.glsl
|
||||
@@ -84,7 +87,8 @@ set(SRC_GLSL_COMP
|
||||
)
|
||||
|
||||
set(SRC_VULKAN_GLSL_COMP
|
||||
vk_backbuffer_blit_comp.glsl
|
||||
# TODO incorrect path, if corrected fails c++ compile
|
||||
# vk_backbuffer_blit_comp.glsl
|
||||
)
|
||||
|
||||
if(WITH_VULKAN_BACKEND)
|
||||
@@ -101,6 +105,7 @@ if(WITH_GPU_SHADER_CPP_COMPILATION)
|
||||
|
||||
compile_sources_as_cpp(gpu_cpp_shaders_vert "${SRC_GLSL_VERT}" "GPU_VERTEX_SHADER")
|
||||
compile_sources_as_cpp(gpu_cpp_shaders_frag "${SRC_GLSL_FRAG}" "GPU_FRAGMENT_SHADER")
|
||||
compile_sources_as_cpp(gpu_cpp_shaders_comp "${SRC_GLSL_COMP}" "GPU_COMPUTE_SHADER")
|
||||
# Only enable to make sure they compile on their own.
|
||||
# Otherwise it creates a warning about `pragma once`.
|
||||
# compile_sources_as_cpp(gpu_cpp_shaders_lib "${SRC_GLSL_LIB}" "GPU_LIBRARY_SHADER")
|
||||
|
||||
148
source/blender/gpu/shaders/gpu_shader_sequencer_scope_comp.glsl
Normal file
148
source/blender/gpu/shaders/gpu_shader_sequencer_scope_comp.glsl
Normal file
@@ -0,0 +1,148 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
#include "infos/gpu_shader_sequencer_info.hh"
|
||||
|
||||
COMPUTE_SHADER_CREATE_INFO(gpu_shader_sequencer_scope_raster)
|
||||
|
||||
/* Match eSpaceSeq_RegionType */
|
||||
#define SEQ_DRAW_IMG_WAVEFORM 2
|
||||
#define SEQ_DRAW_IMG_VECTORSCOPE 3
|
||||
#define SEQ_DRAW_IMG_HISTOGRAM 4
|
||||
#define SEQ_DRAW_IMG_RGBPARADE 5
|
||||
|
||||
/* Compute shader that rasterizes scope points into screen-sized
|
||||
* raster buffer, with accumulated R,G,B,A values in fixed point.
|
||||
*
|
||||
* For pixels covered by each point, just do atomic adds into
|
||||
* the buffer. Even if this has potential to be highly contented
|
||||
* on the same memory locations, it is still way faster than rasterizing
|
||||
* points using regular GPU pipeline. */
|
||||
|
||||
void put_pixel(int x, int y, float4 col)
|
||||
{
|
||||
int index = y * view_width + x;
|
||||
|
||||
/* Use 24.8 fixed point; this allows 16M points to hit the same
|
||||
* location without overflowing the values. */
|
||||
uint4 col_fx = uint4(col * 255.0f + 0.5f);
|
||||
atomicAdd(raster_buf[index].col_r, col_fx.r);
|
||||
atomicAdd(raster_buf[index].col_g, col_fx.g);
|
||||
atomicAdd(raster_buf[index].col_b, col_fx.b);
|
||||
atomicAdd(raster_buf[index].col_a, col_fx.a);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
/* Fetch pixel from the input image, corresponding to current point. */
|
||||
int2 texel = int2(gl_GlobalInvocationID.xy);
|
||||
if (any(greaterThanEqual(texel, int2(image_width, image_height)))) {
|
||||
return;
|
||||
}
|
||||
float4 color = texelFetch(image, texel, 0);
|
||||
if (img_premultiplied) {
|
||||
color_alpha_unpremultiply(color, color);
|
||||
}
|
||||
|
||||
/* Calculate point position based on scope mode; possibly adjust color too. */
|
||||
float2 pos = float2(0.0);
|
||||
if (scope_mode == SEQ_DRAW_IMG_WAVEFORM) {
|
||||
/* Waveform: pixel height based on luminance. */
|
||||
pos.x = texel.x - image_width / 2;
|
||||
pos.y = (get_luminance(color.rgb, luma_coeffs) - 0.5f) * image_height;
|
||||
}
|
||||
else if (scope_mode == SEQ_DRAW_IMG_RGBPARADE) {
|
||||
/* RGB parade: similar to waveform, except three different "bands"
|
||||
* for each R/G/B intensity. */
|
||||
int channel = texel.x % 3;
|
||||
int column = texel.x / 3;
|
||||
|
||||
/* Use a bit desaturated color, and blend in a bit of original pixel color. */
|
||||
float other_channels = 0.6;
|
||||
float factor = 0.4;
|
||||
if (channel == 0) {
|
||||
pos.x = column - image_width / 2;
|
||||
pos.y = (color.r - 0.5f) * image_height;
|
||||
color.rgb = mix(color.rgb, float3(1, other_channels, other_channels), factor);
|
||||
}
|
||||
if (channel == 1) {
|
||||
pos.x = column - image_width / 2 + image_width / 3;
|
||||
pos.y = (color.g - 0.5f) * image_height;
|
||||
color.rgb = mix(color.rgb, float3(other_channels, 1, other_channels), factor);
|
||||
}
|
||||
if (channel == 2) {
|
||||
pos.x = column - image_width / 2 + image_width * 2 / 3;
|
||||
pos.y = (color.b - 0.5f) * image_height;
|
||||
color.rgb = mix(color.rgb, float3(other_channels, other_channels, 1), factor);
|
||||
}
|
||||
}
|
||||
else if (scope_mode == SEQ_DRAW_IMG_VECTORSCOPE) {
|
||||
/* Vectorscope: pixel position is based on U,V of the color. */
|
||||
float4 yuva;
|
||||
rgba_to_yuva_itu_709(color, yuva);
|
||||
float vec_size = min(image_width, image_height);
|
||||
/* Multiplier to map YUV U,V range (+-0.436, +-0.615) to +-0.5 on both axes. */
|
||||
float2 uv_scale = float2(0.5f / 0.436f, 0.5f / 0.615f);
|
||||
pos = yuva.yz * vec_size * uv_scale;
|
||||
}
|
||||
|
||||
/* Determine final point color: we want to keep the hue, desaturate it a bit,
|
||||
* and use full brightness. */
|
||||
float4 hsv;
|
||||
rgb_to_hsv(color, hsv);
|
||||
if (scope_mode != SEQ_DRAW_IMG_RGBPARADE) {
|
||||
/* Saturation adjustments for parade mode are already done above. */
|
||||
hsv.y *= 0.5f;
|
||||
}
|
||||
hsv.z = 1.0f;
|
||||
hsv_to_rgb(hsv, color);
|
||||
|
||||
/* Calculate final point position in integer pixels. */
|
||||
float4 clip_pos = ModelViewProjectionMatrix * float4(pos, 0.0f, 1.0f);
|
||||
int2 view_pos = int2((clip_pos.xy * 0.5f + float2(0.5f)) * float2(view_width, view_height));
|
||||
if (any(lessThan(view_pos, int2(0))) ||
|
||||
any(greaterThanEqual(view_pos, int2(view_width, view_height))))
|
||||
{
|
||||
/* Outside of view. */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Optimization for highly contended raster regions (e.g. vectorscope center):
|
||||
* if there's 50+ points rendered into this location already, stop adding more. */
|
||||
int index = view_pos.y * view_width + view_pos.x;
|
||||
if (raster_buf[index].col_a > 50 * 255) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Adjust point transparency based on ratio of wanted point size vs
|
||||
* quantized to integer pixel count point size. */
|
||||
float raster_size = scope_point_size;
|
||||
int px_size = max(int(ceil(raster_size)), 1);
|
||||
float factor = max(raster_size / px_size, 1.0f / 255.0f);
|
||||
color.rgb *= factor;
|
||||
color.a = factor;
|
||||
|
||||
if (px_size <= 1) {
|
||||
/* Single pixel. */
|
||||
put_pixel(view_pos.x, view_pos.y, color);
|
||||
}
|
||||
else {
|
||||
/* Multiple pixels. */
|
||||
px_size = min(px_size, 16);
|
||||
int x_min = view_pos.x - px_size / 2;
|
||||
int x_max = x_min + px_size;
|
||||
int y_min = view_pos.y - px_size / 2;
|
||||
int y_max = y_min + px_size;
|
||||
x_min = clamp(x_min, 0, view_width);
|
||||
x_max = clamp(x_max, 0, view_width);
|
||||
y_min = clamp(y_min, 0, view_height);
|
||||
y_max = clamp(y_max, 0, view_height);
|
||||
for (int y = y_min; y < y_max; y++) {
|
||||
for (int x = x_min; x < x_max; x++) {
|
||||
put_pixel(x, y, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "infos/gpu_shader_sequencer_info.hh"
|
||||
|
||||
FRAGMENT_SHADER_CREATE_INFO(gpu_shader_sequencer_scope_resolve)
|
||||
|
||||
void main()
|
||||
{
|
||||
/* Compute shader based scope point rasterizer produced screen-sized
|
||||
* raster buffer, with accumulated R,G,B,A values in fixed point.
|
||||
* Fetch data from that buffer and calculate resulting color. */
|
||||
int2 view_pos = int2(gl_FragCoord.xy);
|
||||
fragColor = float4(0.0f);
|
||||
if (any(lessThan(view_pos, int2(0))) ||
|
||||
any(greaterThanEqual(view_pos, int2(view_width, view_height))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int view_index = view_pos.y * view_width + view_pos.x;
|
||||
SeqScopeRasterData data = raster_buf[view_index];
|
||||
if (data.col_a != 0) {
|
||||
float4 pix = float4(data.col_r, data.col_g, data.col_b, data.col_a) / 255.0f;
|
||||
fragColor.rgb = pix.rgb / pix.a;
|
||||
|
||||
/* Use tonemap-like curve to map amount of points to transparency. */
|
||||
fragColor.w = 1.0f - exp(-pix.a * alpha_exponent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "gpu_shader_common_color_utils.glsl"
|
||||
#include "infos/gpu_shader_sequencer_info.hh"
|
||||
|
||||
FRAGMENT_SHADER_CREATE_INFO(gpu_shader_sequencer_zebra)
|
||||
|
||||
void main()
|
||||
{
|
||||
float4 color = texture(image, texCoord_interp);
|
||||
if (img_premultiplied) {
|
||||
color_alpha_unpremultiply(color, color);
|
||||
}
|
||||
|
||||
fragColor = float4(0.0);
|
||||
int phase = int(mod((gl_FragCoord.x + gl_FragCoord.y), 6.0f));
|
||||
if (any(greaterThan(color.rgb, float3(zebra_limit)))) {
|
||||
if (phase == 4) {
|
||||
fragColor = float4(0.0f, 0.0f, 0.0f, 0.85f);
|
||||
}
|
||||
else if (phase >= 3) {
|
||||
fragColor = float4(1.0f, 0.0f, 0.5f, 0.95f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
# include "gpu_glsl_cpp_stubs.hh"
|
||||
|
||||
# include "GPU_shader_shared.hh"
|
||||
# include "gpu_shader_fullscreen_info.hh"
|
||||
#endif
|
||||
|
||||
#include "gpu_interface_info.hh"
|
||||
@@ -51,3 +52,53 @@ VERTEX_SOURCE("gpu_shader_sequencer_thumbs_vert.glsl")
|
||||
FRAGMENT_SOURCE("gpu_shader_sequencer_thumbs_frag.glsl")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_INTERFACE_INFO(gpu_seq_scope_iface)
|
||||
SMOOTH(float4, finalColor)
|
||||
SMOOTH(float2, radii)
|
||||
GPU_SHADER_INTERFACE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(gpu_shader_sequencer_scope_raster)
|
||||
LOCAL_GROUP_SIZE(16, 16)
|
||||
PUSH_CONSTANT(float4x4, ModelViewProjectionMatrix)
|
||||
PUSH_CONSTANT(float3, luma_coeffs)
|
||||
PUSH_CONSTANT(float, scope_point_size)
|
||||
PUSH_CONSTANT(bool, img_premultiplied)
|
||||
PUSH_CONSTANT(int, image_width)
|
||||
PUSH_CONSTANT(int, image_height)
|
||||
PUSH_CONSTANT(int, scope_mode)
|
||||
PUSH_CONSTANT(int, view_width)
|
||||
PUSH_CONSTANT(int, view_height)
|
||||
SAMPLER(0, sampler2D, image)
|
||||
STORAGE_BUF(0, read_write, SeqScopeRasterData, raster_buf[])
|
||||
TYPEDEF_SOURCE("GPU_shader_shared.hh")
|
||||
COMPUTE_SOURCE("gpu_shader_sequencer_scope_comp.glsl")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(gpu_shader_sequencer_scope_resolve)
|
||||
FRAGMENT_OUT(0, float4, fragColor)
|
||||
PUSH_CONSTANT(int, view_width)
|
||||
PUSH_CONSTANT(int, view_height)
|
||||
PUSH_CONSTANT(float, alpha_exponent)
|
||||
STORAGE_BUF(0, read, SeqScopeRasterData, raster_buf[])
|
||||
TYPEDEF_SOURCE("GPU_shader_shared.hh")
|
||||
FRAGMENT_SOURCE("gpu_shader_sequencer_scope_frag.glsl")
|
||||
ADDITIONAL_INFO(gpu_fullscreen)
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
GPU_SHADER_CREATE_INFO(gpu_shader_sequencer_zebra)
|
||||
VERTEX_IN(0, float2, pos)
|
||||
VERTEX_IN(1, float2, texCoord)
|
||||
VERTEX_OUT(smooth_tex_coord_interp_iface)
|
||||
FRAGMENT_OUT(0, float4, fragColor)
|
||||
PUSH_CONSTANT(float4x4, ModelViewProjectionMatrix)
|
||||
PUSH_CONSTANT(float, zebra_limit)
|
||||
PUSH_CONSTANT(bool, img_premultiplied)
|
||||
SAMPLER(0, sampler2D, image)
|
||||
TYPEDEF_SOURCE("GPU_shader_shared.hh")
|
||||
VERTEX_SOURCE("gpu_shader_2D_image_vert.glsl")
|
||||
FRAGMENT_SOURCE("gpu_shader_sequencer_zebra_frag.glsl")
|
||||
DO_STATIC_COMPILATION()
|
||||
GPU_SHADER_CREATE_END()
|
||||
|
||||
@@ -456,6 +456,11 @@ ColormanageProcessor *IMB_colormanagement_display_processor_for_imbuf(
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space = DISPLAY_SPACE_DRAW);
|
||||
|
||||
bool IMB_colormanagement_display_processor_needed(
|
||||
const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings);
|
||||
|
||||
ColormanageProcessor *IMB_colormanagement_colorspace_processor_new(const char *from_colorspace,
|
||||
const char *to_colorspace);
|
||||
bool IMB_colormanagement_processor_is_noop(ColormanageProcessor *cm_processor);
|
||||
|
||||
@@ -1739,25 +1739,31 @@ static bool is_colorspace_same_as_display(const ColorSpace *colorspace,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IMB_colormanagement_display_processor_needed(
|
||||
const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings)
|
||||
{
|
||||
if (ibuf->float_buffer.data == nullptr && ibuf->byte_buffer.colorspace) {
|
||||
return !is_colorspace_same_as_display(
|
||||
ibuf->byte_buffer.colorspace, view_settings, display_settings);
|
||||
}
|
||||
if (ibuf->byte_buffer.data == nullptr && ibuf->float_buffer.colorspace) {
|
||||
return !is_colorspace_same_as_display(
|
||||
ibuf->float_buffer.colorspace, view_settings, display_settings);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ColormanageProcessor *IMB_colormanagement_display_processor_for_imbuf(
|
||||
const ImBuf *ibuf,
|
||||
const ColorManagedViewSettings *view_settings,
|
||||
const ColorManagedDisplaySettings *display_settings,
|
||||
const ColorManagedDisplaySpace display_space)
|
||||
{
|
||||
bool skip_transform = false;
|
||||
if (ibuf->float_buffer.data == nullptr && ibuf->byte_buffer.colorspace) {
|
||||
skip_transform = is_colorspace_same_as_display(
|
||||
ibuf->byte_buffer.colorspace, view_settings, display_settings);
|
||||
}
|
||||
if (ibuf->byte_buffer.data == nullptr && ibuf->float_buffer.colorspace) {
|
||||
skip_transform = is_colorspace_same_as_display(
|
||||
ibuf->float_buffer.colorspace, view_settings, display_settings);
|
||||
}
|
||||
if (skip_transform) {
|
||||
if (!IMB_colormanagement_display_processor_needed(ibuf, view_settings, display_settings)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return IMB_colormanagement_display_processor_new(view_settings, display_settings, display_space);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace blender::seq {
|
||||
struct FinalImageCache;
|
||||
struct IntraFrameCache;
|
||||
struct MediaPresence;
|
||||
struct PreviewCache;
|
||||
struct ThumbnailCache;
|
||||
struct TextVarsRuntime;
|
||||
struct PrefetchJob;
|
||||
@@ -40,6 +41,7 @@ struct StripLookup;
|
||||
using FinalImageCache = blender::seq::FinalImageCache;
|
||||
using IntraFrameCache = blender::seq::IntraFrameCache;
|
||||
using MediaPresence = blender::seq::MediaPresence;
|
||||
using PreviewCache = blender::seq::PreviewCache;
|
||||
using ThumbnailCache = blender::seq::ThumbnailCache;
|
||||
using TextVarsRuntime = blender::seq::TextVarsRuntime;
|
||||
using PrefetchJob = blender::seq::PrefetchJob;
|
||||
@@ -49,6 +51,7 @@ using StripLookup = blender::seq::StripLookup;
|
||||
typedef struct FinalImageCache FinalImageCache;
|
||||
typedef struct IntraFrameCache IntraFrameCache;
|
||||
typedef struct MediaPresence MediaPresence;
|
||||
typedef struct PreviewCache PreviewCache;
|
||||
typedef struct ThumbnailCache ThumbnailCache;
|
||||
typedef struct TextVarsRuntime TextVarsRuntime;
|
||||
typedef struct PrefetchJob PrefetchJob;
|
||||
@@ -328,6 +331,7 @@ typedef struct EditingRuntime {
|
||||
IntraFrameCache *intra_frame_cache;
|
||||
SourceImageCache *source_image_cache;
|
||||
FinalImageCache *final_image_cache;
|
||||
PreviewCache *preview_cache;
|
||||
} EditingRuntime;
|
||||
|
||||
typedef struct Editing {
|
||||
|
||||
@@ -341,7 +341,7 @@ typedef struct SpaceSeq {
|
||||
short mainb; /* eSpaceSeq_RegionType; strange name for view type (image, histogram, ...). */
|
||||
short render_size; /* eSpaceSeq_Proxy_RenderSize. */
|
||||
short chanshown;
|
||||
short zebra;
|
||||
short zebra; /* Show overexposed. 0=disabled; otherwise as percentage of "pure white". */
|
||||
int flag;
|
||||
/** Deprecated, handled by View2D now. */
|
||||
float zoom DNA_DEPRECATED;
|
||||
|
||||
@@ -27,6 +27,7 @@ set(SRC
|
||||
SEQ_modifier.hh
|
||||
SEQ_offscreen.hh
|
||||
SEQ_prefetch.hh
|
||||
SEQ_preview_cache.hh
|
||||
SEQ_proxy.hh
|
||||
SEQ_relations.hh
|
||||
SEQ_render.hh
|
||||
@@ -44,6 +45,7 @@ set(SRC
|
||||
intern/cache/final_image_cache.hh
|
||||
intern/cache/intra_frame_cache.cc
|
||||
intern/cache/intra_frame_cache.hh
|
||||
intern/cache/preview_cache.cc
|
||||
intern/cache/source_image_cache.cc
|
||||
intern/cache/source_image_cache.hh
|
||||
intern/cache/thumbnail_cache.cc
|
||||
@@ -100,6 +102,7 @@ set(LIB
|
||||
PRIVATE bf::blentranslation
|
||||
PRIVATE bf::depsgraph
|
||||
PRIVATE bf::dna
|
||||
PRIVATE bf::gpu
|
||||
PRIVATE bf::imbuf
|
||||
PRIVATE bf::imbuf::movie
|
||||
PRIVATE bf::intern::atomic
|
||||
|
||||
33
source/blender/sequencer/SEQ_preview_cache.hh
Normal file
33
source/blender/sequencer/SEQ_preview_cache.hh
Normal file
@@ -0,0 +1,33 @@
|
||||
/* SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup sequencer
|
||||
*
|
||||
* Additionally, this holds GPU textures representing the current frame.
|
||||
* This is to avoid the same GPU texture needing to get re-created if there
|
||||
* are multiple preview areas open (e.g. with scopes).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
struct Scene;
|
||||
|
||||
namespace blender::gpu {
|
||||
class Texture;
|
||||
}
|
||||
|
||||
namespace blender::seq {
|
||||
|
||||
gpu::Texture *preview_cache_get_gpu_texture(Scene *scene, int timeline_frame);
|
||||
void preview_cache_set_gpu_texture(Scene *scene, int timeline_frame, gpu::Texture *texture);
|
||||
gpu::Texture *preview_cache_get_gpu_display_texture(Scene *scene, int timeline_frame);
|
||||
void preview_cache_set_gpu_display_texture(Scene *scene,
|
||||
int timeline_frame,
|
||||
gpu::Texture *texture);
|
||||
|
||||
void preview_cache_invalidate(Scene *scene);
|
||||
void preview_cache_destroy(Scene *scene);
|
||||
|
||||
} // namespace blender::seq
|
||||
183
source/blender/sequencer/intern/cache/preview_cache.cc
vendored
Normal file
183
source/blender/sequencer/intern/cache/preview_cache.cc
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
/* 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
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "SEQ_effects.hh"
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_modifier.hh"
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_proxy.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_retiming.hh"
|
||||
@@ -310,11 +311,12 @@ void editing_free(Scene *scene, const bool do_id_user)
|
||||
|
||||
BLI_freelistN(&ed->metastack);
|
||||
strip_lookup_free(ed);
|
||||
blender::seq::media_presence_free(scene);
|
||||
blender::seq::thumbnail_cache_destroy(scene);
|
||||
blender::seq::intra_frame_cache_destroy(scene);
|
||||
blender::seq::source_image_cache_destroy(scene);
|
||||
blender::seq::final_image_cache_destroy(scene);
|
||||
seq::media_presence_free(scene);
|
||||
seq::thumbnail_cache_destroy(scene);
|
||||
seq::intra_frame_cache_destroy(scene);
|
||||
seq::source_image_cache_destroy(scene);
|
||||
seq::final_image_cache_destroy(scene);
|
||||
seq::preview_cache_destroy(scene);
|
||||
channels_free(&ed->channels);
|
||||
|
||||
MEM_freeN(ed);
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
#include "SEQ_iterator.hh"
|
||||
#include "SEQ_prefetch.hh"
|
||||
#include "SEQ_preview_cache.hh"
|
||||
#include "SEQ_relations.hh"
|
||||
#include "SEQ_sequencer.hh"
|
||||
#include "SEQ_thumbnail_cache.hh"
|
||||
@@ -51,6 +52,7 @@ void cache_cleanup(Scene *scene)
|
||||
source_image_cache_clear(scene);
|
||||
final_image_cache_clear(scene);
|
||||
intra_frame_cache_invalidate(scene);
|
||||
preview_cache_invalidate(scene);
|
||||
}
|
||||
|
||||
void cache_settings_changed(Scene *scene)
|
||||
@@ -151,6 +153,7 @@ void relations_invalidate_cache(Scene *scene, Strip *strip)
|
||||
|
||||
invalidate_final_cache_strip_range(scene, strip);
|
||||
intra_frame_cache_invalidate(scene, strip);
|
||||
preview_cache_invalidate(scene);
|
||||
invalidate_raw_cache_of_parent_meta(scene, strip);
|
||||
|
||||
/* Needed to update VSE sound. */
|
||||
|
||||
Reference in New Issue
Block a user