diff --git a/source/blender/blenkernel/intern/scene.cc b/source/blender/blenkernel/intern/scene.cc index cfd776ede3b..97340fff24d 100644 --- a/source/blender/blenkernel/intern/scene.cc +++ b/source/blender/blenkernel/intern/scene.cc @@ -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); diff --git a/source/blender/editors/space_sequencer/sequencer_intern.hh b/source/blender/editors/space_sequencer/sequencer_intern.hh index e92e6635a2b..58f1e4bf0c2 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.hh +++ b/source/blender/editors/space_sequencer/sequencer_intern.hh @@ -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 */ diff --git a/source/blender/editors/space_sequencer/sequencer_preview_draw.cc b/source/blender/editors/space_sequencer/sequencer_preview_draw.cc index 8647bffee02..9b5576ec296 100644 --- a/source/blender/editors/space_sequencer/sequencer_preview_draw.cc +++ b/source/blender/editors/space_sequencer/sequencer_preview_draw.cc @@ -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(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); } diff --git a/source/blender/editors/space_sequencer/sequencer_scopes.cc b/source/blender/editors/space_sequencer/sequencer_scopes.cc index e532847a0eb..6706779da24 100644 --- a/source/blender/editors/space_sequencer/sequencer_scopes.cc +++ b/source/blender/editors/space_sequencer/sequencer_scopes.cc @@ -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 -#include - #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 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 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 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 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 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 counts(size * size, uint8_t(0)); - Array data = threading::parallel_reduce( - IndexRange(IMB_get_pixel_count(ibuf)), - grain_size, - counts, - [&](const IndexRange range, const Array &init) { - Array 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 pixels_span = MutableSpan(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(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(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(res[offset] + 1, 255); - src_b += 4; - } - } - return res; - }, - /* Merge scopes computed per-thread. */ - [&](const Array &a, const Array &b) { - BLI_assert(a.size() == b.size()); - Array res(a.size(), NoInitialization()); - for (int64_t i : a.index_range()) { - res[i] = std::min(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 diff --git a/source/blender/editors/space_sequencer/sequencer_scopes.hh b/source/blender/editors/space_sequencer/sequencer_scopes.hh index 182172f9f4e..34cd11ab871 100644 --- a/source/blender/editors/space_sequencer/sequencer_scopes.hh +++ b/source/blender/editors/space_sequencer/sequencer_scopes.hh @@ -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 diff --git a/source/blender/editors/space_sequencer/sequencer_view.cc b/source/blender/editors/space_sequencer/sequencer_view.cc index ee8e252c43e..a95ea8f0b9e 100644 --- a/source/blender/editors/space_sequencer/sequencer_view.cc +++ b/source/blender/editors/space_sequencer/sequencer_view.cc @@ -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; } diff --git a/source/blender/editors/space_sequencer/space_sequencer.cc b/source/blender/editors/space_sequencer/space_sequencer.cc index e3689396241..d383c5f1013 100644 --- a/source/blender/editors/space_sequencer/space_sequencer.cc +++ b/source/blender/editors/space_sequencer/space_sequencer.cc @@ -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)); } 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: diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index 5805a826b2d..c54eb2e6abe 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -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 diff --git a/source/blender/gpu/GPU_shader_builtin.hh b/source/blender/gpu/GPU_shader_builtin.hh index 9f24b996a41..e2a8bbe909a 100644 --- a/source/blender/gpu/GPU_shader_builtin.hh +++ b/source/blender/gpu/GPU_shader_builtin.hh @@ -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, diff --git a/source/blender/gpu/GPU_shader_shared.hh b/source/blender/gpu/GPU_shader_shared.hh index fd3b3f52f9f..c06c952f18a 100644 --- a/source/blender/gpu/GPU_shader_shared.hh +++ b/source/blender/gpu/GPU_shader_shared.hh @@ -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; diff --git a/source/blender/gpu/intern/gpu_shader_builtin.cc b/source/blender/gpu/intern/gpu_shader_builtin.cc index 99c21151f6f..2c489822a1d 100644 --- a/source/blender/gpu/intern/gpu_shader_builtin.cc +++ b/source/blender/gpu/intern/gpu_shader_builtin.cc @@ -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: diff --git a/source/blender/gpu/shaders/CMakeLists.txt b/source/blender/gpu/shaders/CMakeLists.txt index ab7ec16c3bd..a293c297aef 100644 --- a/source/blender/gpu/shaders/CMakeLists.txt +++ b/source/blender/gpu/shaders/CMakeLists.txt @@ -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") diff --git a/source/blender/gpu/shaders/gpu_shader_sequencer_scope_comp.glsl b/source/blender/gpu/shaders/gpu_shader_sequencer_scope_comp.glsl new file mode 100644 index 00000000000..e176fadca3c --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_sequencer_scope_comp.glsl @@ -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); + } + } + } +} diff --git a/source/blender/gpu/shaders/gpu_shader_sequencer_scope_frag.glsl b/source/blender/gpu/shaders/gpu_shader_sequencer_scope_frag.glsl new file mode 100644 index 00000000000..61e483fa72b --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_sequencer_scope_frag.glsl @@ -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); + } +} diff --git a/source/blender/gpu/shaders/gpu_shader_sequencer_zebra_frag.glsl b/source/blender/gpu/shaders/gpu_shader_sequencer_zebra_frag.glsl new file mode 100644 index 00000000000..b9c5fdec31e --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_sequencer_zebra_frag.glsl @@ -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); + } + } +} diff --git a/source/blender/gpu/shaders/infos/gpu_shader_sequencer_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_sequencer_info.hh index f1a3344ffaa..cc45020b3b1 100644 --- a/source/blender/gpu/shaders/infos/gpu_shader_sequencer_info.hh +++ b/source/blender/gpu/shaders/infos/gpu_shader_sequencer_info.hh @@ -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() diff --git a/source/blender/imbuf/IMB_colormanagement.hh b/source/blender/imbuf/IMB_colormanagement.hh index 77b733aea95..26b7023e010 100644 --- a/source/blender/imbuf/IMB_colormanagement.hh +++ b/source/blender/imbuf/IMB_colormanagement.hh @@ -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); diff --git a/source/blender/imbuf/intern/colormanagement.cc b/source/blender/imbuf/intern/colormanagement.cc index fe5c28434a6..b9e61071746 100644 --- a/source/blender/imbuf/intern/colormanagement.cc +++ b/source/blender/imbuf/intern/colormanagement.cc @@ -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); } diff --git a/source/blender/makesdna/DNA_sequence_types.h b/source/blender/makesdna/DNA_sequence_types.h index c003537fb8f..599185632ed 100644 --- a/source/blender/makesdna/DNA_sequence_types.h +++ b/source/blender/makesdna/DNA_sequence_types.h @@ -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 { diff --git a/source/blender/makesdna/DNA_space_types.h b/source/blender/makesdna/DNA_space_types.h index b789d38f417..0e0abb4bf54 100644 --- a/source/blender/makesdna/DNA_space_types.h +++ b/source/blender/makesdna/DNA_space_types.h @@ -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; diff --git a/source/blender/sequencer/CMakeLists.txt b/source/blender/sequencer/CMakeLists.txt index 2e9d3d702c5..5f31c79b1c7 100644 --- a/source/blender/sequencer/CMakeLists.txt +++ b/source/blender/sequencer/CMakeLists.txt @@ -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 diff --git a/source/blender/sequencer/SEQ_preview_cache.hh b/source/blender/sequencer/SEQ_preview_cache.hh new file mode 100644 index 00000000000..82d399001c5 --- /dev/null +++ b/source/blender/sequencer/SEQ_preview_cache.hh @@ -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 diff --git a/source/blender/sequencer/intern/cache/preview_cache.cc b/source/blender/sequencer/intern/cache/preview_cache.cc new file mode 100644 index 00000000000..b2542413338 --- /dev/null +++ b/source/blender/sequencer/intern/cache/preview_cache.cc @@ -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(__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 diff --git a/source/blender/sequencer/intern/sequencer.cc b/source/blender/sequencer/intern/sequencer.cc index 9dcb1b8e443..35fb3bc16e3 100644 --- a/source/blender/sequencer/intern/sequencer.cc +++ b/source/blender/sequencer/intern/sequencer.cc @@ -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); diff --git a/source/blender/sequencer/intern/strip_relations.cc b/source/blender/sequencer/intern/strip_relations.cc index 09e43b9c057..96dfb2bf35a 100644 --- a/source/blender/sequencer/intern/strip_relations.cc +++ b/source/blender/sequencer/intern/strip_relations.cc @@ -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. */