Blender V4.3
sequencer_thumbnails.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021-2024 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BKE_context.hh"
10
11#include "BLI_array.hh"
12
13#include "IMB_imbuf.hh"
14
15#include "DNA_sequence_types.h"
16#include "DNA_space_types.h"
17
18#include "GPU_batch_presets.hh"
19#include "GPU_matrix.hh"
20#include "GPU_shader_shared.hh"
21#include "GPU_texture.hh"
22
24
25#include "SEQ_render.hh"
26#include "SEQ_sequencer.hh"
28
29#include "WM_api.hh"
30
31#include "sequencer_intern.hh"
33
34using namespace blender;
35
36/* Information for one thumbnail picture in the timeline. Note that a single
37 * strip could have multiple thumbnails. */
40 /* Strip coordinates in timeline space (X: frames, Y: channels). */
42 /* Thumbnail coordinates in timeline space. */
43 float x1, x2, y1, y2;
44 /* Horizontal cropping of thumbnail image, in pixels. Often a thumbnail
45 * does not have to be cropped, in which case these are 0 and ibuf->x-1. */
48};
49
51 float left_handle,
52 float frame_step,
53 const rctf *view_area)
54{
55 int first_drawable_frame = max_iii(left_handle, seq->start, view_area->xmin);
56
57 /* First frame should correspond to handle position. */
58 if (first_drawable_frame == left_handle) {
59 return left_handle;
60 }
61
62 float aligned_frame_offset = int((first_drawable_frame - seq->start) / frame_step) * frame_step;
63 return seq->start + aligned_frame_offset;
64}
65
67 float left_handle,
68 float last_frame,
69 float frame_step)
70{
71 float next_frame = last_frame + frame_step;
72
73 /* If handle position was displayed, align next frame with `seq->start`. */
74 if (last_frame == left_handle) {
75 next_frame = seq->start + (int((last_frame - seq->start) / frame_step) + 1) * frame_step;
76 }
77
78 return next_frame;
79}
80
82 float pixelx,
83 float pixely,
84 float *r_thumb_width,
85 float thumb_height,
86 float *r_image_width,
87 float *r_image_height)
88{
91
92 /* Fix the dimensions to be max SEQ_THUMB_SIZE for x or y. */
93 float aspect_ratio = image_width / image_height;
97 }
98 else {
100 image_width = round_fl_to_int(image_height * aspect_ratio);
101 }
102
103 /* Calculate thumb dimensions. */
104 aspect_ratio = image_width / image_height;
105 float thumb_h_px = thumb_height / pixely;
106 float thumb_width = aspect_ratio * thumb_h_px * pixelx;
107
108 *r_thumb_width = thumb_width;
109 *r_image_width = image_width;
110 *r_image_height = image_height;
111}
112
113static void get_seq_strip_thumbnails(const View2D *v2d,
114 const bContext *C,
115 Scene *scene,
116 const StripDrawContext &strip,
117 float pixelx,
118 float pixely,
119 bool is_muted,
120 Vector<SeqThumbInfo> &r_thumbs)
121{
122 if (!seq::strip_can_have_thumbnail(scene, strip.seq)) {
123 return;
124 }
125
126 /* No thumbnails is height of the strip is too small. */
127 const float thumb_height = strip.strip_content_top - strip.bottom;
128 if (thumb_height / pixely <= 20 * UI_SCALE_FAC) {
129 return;
130 }
131
132 float thumb_width, image_width, image_height;
134 strip.seq, pixelx, pixely, &thumb_width, thumb_height, &image_width, &image_height);
135
136 const float crop_x_multiplier = 1.0f / pixelx / (thumb_height / image_height / pixely);
137
138 float upper_thumb_bound = min_ff(strip.right_handle, strip.content_end);
139 if (strip.seq->type == SEQ_TYPE_IMAGE) {
140 upper_thumb_bound = strip.right_handle;
141 }
142
143 float timeline_frame = thumb_calc_first_timeline_frame(
144 strip.seq, strip.left_handle, thumb_width, &v2d->cur);
145
146 /* Start going over the strip length. */
147 while (timeline_frame < upper_thumb_bound) {
148 float thumb_x_end = timeline_frame + thumb_width;
149 bool clipped = false;
150
151 /* Reached end of view, no more thumbnails needed. */
152 if (timeline_frame > v2d->cur.xmax) {
153 break;
154 }
155
156 /* Set the clipping bound to show the left handle moving over thumbs and not shift thumbs. */
157 float cut_off = 0.0f;
158 if (strip.left_handle > timeline_frame && strip.left_handle < thumb_x_end) {
159 cut_off = strip.left_handle - timeline_frame;
160 clipped = true;
161 }
162
163 /* Clip if full thumbnail cannot be displayed. */
164 if (thumb_x_end > upper_thumb_bound) {
165 thumb_x_end = upper_thumb_bound;
166 clipped = true;
167 }
168
169 float cropx_min = cut_off * crop_x_multiplier;
170 float cropx_max = (thumb_x_end - timeline_frame) * crop_x_multiplier;
171 if (cropx_max < 1.0f) {
172 break;
173 }
174
175 /* Get the thumbnail image. */
176 ImBuf *ibuf = seq::thumbnail_cache_get(C, scene, strip.seq, timeline_frame);
177 if (ibuf == nullptr) {
178 break;
179 }
180
181 SeqThumbInfo thumb = {};
182 thumb.ibuf = ibuf;
183 thumb.cropx_min = 0;
184 thumb.cropx_max = ibuf->x - 1;
185 if (clipped) {
186 thumb.cropx_min = clamp_f(cropx_min, 0, ibuf->x - 1);
187 thumb.cropx_max = clamp_f(cropx_max - 1 * 0, 0, ibuf->x - 1);
188 }
189 thumb.left_handle = strip.left_handle;
190 thumb.right_handle = strip.right_handle;
191 thumb.is_muted = is_muted;
192 thumb.bottom = strip.bottom;
193 thumb.top = strip.top;
194 thumb.x1 = timeline_frame + cut_off;
195 thumb.x2 = thumb_x_end;
196 thumb.y1 = strip.bottom;
197 thumb.y2 = strip.strip_content_top;
198 r_thumbs.append(thumb);
199
200 timeline_frame = thumb_calc_next_timeline_frame(
201 strip.seq, strip.left_handle, timeline_frame, thumb_width);
202 }
203}
204
208 GPUUniformBuf *ubo_thumbs_ = nullptr;
209 GPUShader *shader_ = nullptr;
210 gpu::Batch *batch_ = nullptr;
211 GPUTexture *atlas_ = nullptr;
216
229
236
238 const SeqThumbInfo &info, float width, const rcti &rect, int tex_width, int tex_height)
239 {
241 flush_batch();
242 }
243
246
249 res.bottom = strips_batch_.pos_to_pixel_space_y(info.bottom);
250 res.top = strips_batch_.pos_to_pixel_space_y(info.top);
251 res.tint_color = float4(1.0f, 1.0f, 1.0f, info.is_muted ? 0.47f : 1.0f);
256 res.u1 = float(rect.xmin) / float(tex_width);
257 res.u2 = float(rect.xmin + width) / float(tex_width);
258 res.v1 = float(rect.ymin) / float(tex_height);
259 res.v2 = float(rect.ymax) / float(tex_height);
260 }
261
279};
280
282 ed::seq::StripsDrawBatch &strips_batch,
283 const Vector<StripDrawContext> &strips)
284{
285 /* Nothing to do if we're not showing thumbnails overall. */
286 if ((ctx->sseq->flag & SEQ_SHOW_OVERLAY) == 0 ||
288 {
289 return;
290 }
291
292 /* Gather information for all thumbnails. */
294 for (const StripDrawContext &strip : strips) {
296 ctx->v2d, ctx->C, ctx->scene, strip, ctx->pixelx, ctx->pixely, strip.is_muted, thumbs);
297 }
298 if (thumbs.is_empty()) {
299 return;
300 }
301
302 ColorManagedViewSettings *view_settings;
303 ColorManagedDisplaySettings *display_settings;
304 IMB_colormanagement_display_settings_from_ctx(ctx->C, &view_settings, &display_settings);
305
306 /* Arrange thumbnail images into a texture atlas, using a simple
307 * "add to current row until end, then start a new row". Thumbnail
308 * images are most often same height (but varying width due to horizontal
309 * cropping), so this simple algorithm works well enough. */
310 constexpr int ATLAS_WIDTH = 4096;
311 constexpr int ATLAS_MAX_HEIGHT = 4096;
312 int cur_row_x = 0;
313 int cur_row_y = 0;
314 int cur_row_height = 0;
315 Vector<rcti> rects;
316 rects.reserve(thumbs.size());
317 for (const SeqThumbInfo &info : thumbs) {
318 int cropx_min = int(info.cropx_min);
319 int cropx_max = int(math::ceil(info.cropx_max));
320 int width = cropx_max - cropx_min + 1;
321 int height = info.ibuf->y;
322 cur_row_height = math::max(cur_row_height, height);
323
324 /* If this thumb would not fit onto current row, start a new row. */
325 if (cur_row_x + width > ATLAS_WIDTH) {
326 cur_row_y += cur_row_height + 1; /* +1 empty pixel for bilinear filter. */
327 cur_row_height = height;
328 cur_row_x = 0;
329 if (cur_row_y > ATLAS_MAX_HEIGHT) {
330 break;
331 }
332 }
333
334 /* Record our rect. */
335 rcti rect{cur_row_x, cur_row_x + width, cur_row_y, cur_row_y + height};
336 rects.append(rect);
337
338 /* Advance to next item inside row. */
339 cur_row_x += width + 1; /* +1 empty pixel for bilinear filter. */
340 }
341
342 /* Create the atlas GPU texture. */
343 const int tex_width = ATLAS_WIDTH;
344 const int tex_height = cur_row_y + cur_row_height;
345 Array<uchar> tex_data(tex_width * tex_height * 4, 0);
346 for (int64_t i = 0; i < rects.size(); i++) {
347 /* Copy one thumbnail into atlas. */
348 const rcti &rect = rects[i];
349 SeqThumbInfo &info = thumbs[i];
350
351 void *cache_handle = nullptr;
352 uchar *display_buffer = IMB_display_buffer_acquire(
353 info.ibuf, view_settings, display_settings, &cache_handle);
354 if (display_buffer != nullptr && info.ibuf != nullptr) {
355 int cropx_min = int(info.cropx_min);
356 int cropx_max = int(math::ceil(info.cropx_max));
357 int width = cropx_max - cropx_min + 1;
358 int height = info.ibuf->y;
359 const uchar *src = display_buffer + cropx_min * 4;
360 uchar *dst = &tex_data[(rect.ymin * ATLAS_WIDTH + rect.xmin) * 4];
361 for (int y = 0; y < height; y++) {
362 memcpy(dst, src, width * 4);
363 src += info.ibuf->x * 4;
364 dst += ATLAS_WIDTH * 4;
365 }
366 }
367 IMB_display_buffer_release(cache_handle);
368
369 /* Release thumb image reference. */
370 IMB_freeImBuf(info.ibuf);
371 info.ibuf = nullptr;
372 }
373 GPUTexture *atlas = GPU_texture_create_2d(
374 "thumb_atlas", tex_width, tex_height, 1, GPU_RGBA8, GPU_TEXTURE_USAGE_SHADER_READ, nullptr);
375 GPU_texture_update(atlas, GPU_DATA_UBYTE, tex_data.data());
376 GPU_texture_filter_mode(atlas, true);
378
379 /* Draw all thumbnails. */
382
383 ThumbsDrawBatch batch(strips_batch, atlas);
384 for (int64_t i = 0; i < rects.size(); i++) {
385 const rcti &rect = rects[i];
386 const SeqThumbInfo &info = thumbs[i];
387 batch.add_thumb(info, info.cropx_max - info.cropx_min + 1, rect, tex_width, tex_height);
388 }
389 batch.flush_batch();
390
392
393 GPU_texture_unbind(atlas);
394 GPU_texture_free(atlas);
395}
MINLINE int round_fl_to_int(float a)
MINLINE float clamp_f(float value, float min, float max)
MINLINE float min_ff(float a, float b)
MINLINE int max_iii(int a, int b, int c)
static constexpr int image_width
static constexpr int image_height
unsigned char uchar
@ SEQ_TYPE_IMAGE
@ SEQ_TIMELINE_SHOW_THUMBNAILS
@ SEQ_SHOW_OVERLAY
#define UI_SCALE_FAC
void GPU_batch_draw_instance_range(blender::gpu::Batch *batch, int instance_first, int instance_count)
void GPU_batch_set_shader(blender::gpu::Batch *batch, GPUShader *shader)
blender::gpu::Batch * GPU_batch_preset_quad()
void GPU_matrix_push_projection()
void GPU_matrix_pop_projection()
int GPU_shader_get_sampler_binding(GPUShader *shader, const char *name)
int GPU_shader_get_ubo_binding(GPUShader *shader, const char *name)
void GPU_shader_bind(GPUShader *shader)
GPUShader * GPU_shader_get_builtin_shader(eGPUBuiltinShader shader)
@ GPU_SHADER_SEQUENCER_THUMBS
#define GPU_SEQ_STRIP_DRAW_DATA_LEN
void GPU_texture_bind(GPUTexture *texture, int unit)
GPUTexture * GPU_texture_create_2d(const char *name, int width, int height, int mip_len, eGPUTextureFormat format, eGPUTextureUsage usage, const float *data)
void GPU_texture_free(GPUTexture *texture)
void GPU_texture_unbind(GPUTexture *texture)
@ GPU_DATA_UBYTE
void GPU_texture_extend_mode(GPUTexture *texture, GPUSamplerExtendMode extend_mode)
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER
void GPU_texture_filter_mode(GPUTexture *texture, bool use_filter)
void GPU_texture_update(GPUTexture *texture, eGPUDataFormat data_format, const void *data)
void GPU_uniformbuf_unbind(GPUUniformBuf *ubo)
#define GPU_uniformbuf_create(size)
void GPU_uniformbuf_update(GPUUniformBuf *ubo, const void *data)
void GPU_uniformbuf_free(GPUUniformBuf *ubo)
void GPU_uniformbuf_bind(GPUUniformBuf *ubo, int slot)
unsigned char * IMB_display_buffer_acquire(ImBuf *ibuf, const ColorManagedViewSettings *view_settings, const ColorManagedDisplaySettings *display_settings, void **cache_handle)
void IMB_display_buffer_release(void *cache_handle)
void IMB_colormanagement_display_settings_from_ctx(const bContext *C, ColorManagedViewSettings **r_view_settings, ColorManagedDisplaySettings **r_display_settings)
struct GPUShader GPUShader
const T * data() const
Definition BLI_array.hh:301
int64_t size() const
void append(const T &value)
bool is_empty() const
void reserve(const int64_t min_capacity)
draw_view in_light_buf[] float
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
RAYTRACE_GROUP_SIZE additional_info("eevee_shared", "eevee_gbuffer_data", "eevee_global_ubo", "eevee_sampling_data", "eevee_utility_texture", "eevee_hiz_data", "draw_view") .specialization_constant(Type RAYTRACE_GROUP_SIZE in_sh_0_tx in_sh_2_tx screen_normal_tx GPU_RGBA8
struct @620::@622 batch
void IMB_freeImBuf(ImBuf *)
no_perspective(Type::VEC2, "pos_interp") .no_perspective(Type fragColor SeqStripThumbData
T ceil(const T &a)
T max(const T &a, const T &b)
static constexpr int SEQ_THUMB_SIZE
ImBuf * thumbnail_cache_get(const bContext *C, Scene *scene, const Sequence *seq, float timeline_frame)
bool strip_can_have_thumbnail(const Scene *scene, const Sequence *seq)
VecBase< float, 4 > float4
void draw_strip_thumbnails(TimelineDrawContext *ctx, ed::seq::StripsDrawBatch &strips_batch, const Vector< StripDrawContext > &strips)
static float thumb_calc_next_timeline_frame(const Sequence *seq, float left_handle, float last_frame, float frame_step)
static void seq_get_thumb_image_dimensions(const Sequence *seq, float pixelx, float pixely, float *r_thumb_width, float thumb_height, float *r_image_width, float *r_image_height)
static void get_seq_strip_thumbnails(const View2D *v2d, const bContext *C, Scene *scene, const StripDrawContext &strip, float pixelx, float pixely, bool is_muted, Vector< SeqThumbInfo > &r_thumbs)
static float thumb_calc_first_timeline_frame(const Sequence *seq, float left_handle, float frame_step, const rctf *view_area)
__int64 int64_t
Definition stdint.h:89
struct SequencerTimelineOverlay timeline_overlay
StripElem * stripdata
ed::seq::StripsDrawBatch & strips_batch_
GPUUniformBuf * ubo_thumbs_
void add_thumb(const SeqThumbInfo &info, float width, const rcti &rect, int tex_width, int tex_height)
Array< SeqStripThumbData > thumbs_
ThumbsDrawBatch(ed::seq::StripsDrawBatch &strips_batch, GPUTexture *atlas)
float xmax
float xmin
int ymin
int ymax
int xmin
void wmOrtho2_region_pixelspace(const ARegion *region)