diff --git a/source/blender/editors/space_sequencer/sequencer_intern.hh b/source/blender/editors/space_sequencer/sequencer_intern.hh index 002c7fb935c..56c125ca260 100644 --- a/source/blender/editors/space_sequencer/sequencer_intern.hh +++ b/source/blender/editors/space_sequencer/sequencer_intern.hh @@ -130,8 +130,10 @@ void draw_seq_strip_thumbnail(View2D *v2d, Sequence *seq, float y1, float y2, + float y_top, float pixelx, - float pixely); + float pixely, + float round_radius); /* sequencer_draw_channels.c */ diff --git a/source/blender/editors/space_sequencer/sequencer_thumbnails.cc b/source/blender/editors/space_sequencer/sequencer_thumbnails.cc index 90323559870..a69315a3d98 100644 --- a/source/blender/editors/space_sequencer/sequencer_thumbnails.cc +++ b/source/blender/editors/space_sequencer/sequencer_thumbnails.cc @@ -8,6 +8,8 @@ #include "BLI_blenlib.h" #include "BLI_ghash.h" +#include "BLI_math_vector.hh" +#include "BLI_math_vector_types.hh" #include "BKE_context.hh" #include "BKE_global.hh" @@ -34,6 +36,8 @@ /* Own include. */ #include "sequencer_intern.hh" +using namespace blender; + struct ThumbnailDrawJob { SeqRenderData context; GHash *sequences_ghash; @@ -429,14 +433,79 @@ static ImBuf *sequencer_thumbnail_closest_from_memory(const SeqRenderData *conte return closest_in_memory; } +static void make_ibuf_semitransparent(ImBuf *ibuf) +{ + const uchar alpha = 120; + if (ibuf->byte_buffer.data) { + uchar *buf = ibuf->byte_buffer.data; + for (int pixel = ibuf->x * ibuf->y; pixel--; buf += 4) { + buf[3] = alpha; + } + } + if (ibuf->float_buffer.data) { + float *buf = ibuf->float_buffer.data; + for (int pixel = ibuf->x * ibuf->y; pixel--; buf += ibuf->channels) { + buf[3] = (alpha / 255.0f); + } + } +} + +/* Signed distance to rounded box, centered at origin. + * Reference: https://iquilezles.org/articles/distfunctions2d/ */ +static float sdf_rounded_box(float2 pos, float2 size, float radius) +{ + float2 q = math::abs(pos) - size + radius; + return math::min(math::max(q.x, q.y), 0.0f) + math::length(math::max(q, float2(0.0f))) - radius; +} + +static void eval_round_corners_pixel( + ImBuf *ibuf, float radius, float2 bmin, float2 bmax, float2 pos) +{ + int ix = int(pos.x); + int iy = int(pos.y); + if (ix < 0 || ix >= ibuf->x || iy < 0 || iy >= ibuf->y) { + return; + } + float2 center = (bmin + bmax) * 0.5f; + float2 size = (bmax - bmin) * 0.5f; + float d = sdf_rounded_box(pos - center, size, radius); + if (d <= 0.0f) { + return; + } + /* Outside of rounded rectangle, set pixel alpha to zero. */ + if (ibuf->byte_buffer.data != nullptr) { + int64_t ofs = (int64_t(iy) * ibuf->x + ix) * 4; + ibuf->byte_buffer.data[ofs + 3] = 0; + } + if (ibuf->float_buffer.data != nullptr) { + int64_t ofs = (int64_t(iy) * ibuf->x + ix) * ibuf->channels; + ibuf->float_buffer.data[ofs + 3] = 0.0f; + } +} + +static void make_ibuf_round_corners(ImBuf *ibuf, float radius, float2 bmin, float2 bmax) +{ + /* Evaluate radius*radius squares at corners. */ + for (int by = 0; by < radius; by++) { + for (int bx = 0; bx < radius; bx++) { + eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmin.x + bx, bmin.y + by)); + eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmax.x - bx, bmin.y + by)); + eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmin.x + bx, bmax.y - by)); + eval_round_corners_pixel(ibuf, radius, bmin, bmax, float2(bmax.x - bx, bmax.y - by)); + } + } +} + void draw_seq_strip_thumbnail(View2D *v2d, const bContext *C, Scene *scene, Sequence *seq, float y1, float y2, + float y_top, float pixelx, - float pixely) + float pixely, + float round_radius) { SpaceSeq *sseq = CTX_wm_space_seq(C); if ((sseq->flag & SEQ_SHOW_OVERLAY) == 0 || @@ -475,12 +544,14 @@ void draw_seq_strip_thumbnail(View2D *v2d, float thumb_y_end = y1 + thumb_height; + const float seq_left_handle = SEQ_time_left_handle_frame_get(scene, seq); + const float seq_right_handle = SEQ_time_right_handle_frame_get(scene, seq); + float cut_off = 0; - float upper_thumb_bound = SEQ_time_has_right_still_frames(scene, seq) ? - (seq->start + seq->len) : - SEQ_time_right_handle_frame_get(scene, seq); + float upper_thumb_bound = SEQ_time_has_right_still_frames(scene, seq) ? (seq->start + seq->len) : + seq_right_handle; if (seq->type == SEQ_TYPE_IMAGE) { - upper_thumb_bound = SEQ_time_right_handle_frame_get(scene, seq); + upper_thumb_bound = seq_right_handle; } float timeline_frame = SEQ_render_thumbnail_first_frame_get(scene, seq, thumb_width, &v2d->cur); @@ -505,8 +576,8 @@ void draw_seq_strip_thumbnail(View2D *v2d, } /* Set the clipping bound to show the left handle moving over thumbs and not shift thumbs. */ - if (IN_RANGE_INCL(SEQ_time_left_handle_frame_get(scene, seq), timeline_frame, thumb_x_end)) { - cut_off = SEQ_time_left_handle_frame_get(scene, seq) - timeline_frame; + if (IN_RANGE_INCL(seq_left_handle, timeline_frame, thumb_x_end)) { + cut_off = seq_left_handle - timeline_frame; clipped = true; } @@ -552,35 +623,37 @@ void draw_seq_strip_thumbnail(View2D *v2d, /* Transparency on mute. */ bool muted = channels ? SEQ_render_is_muted(channels, seq) : false; if (muted) { - const uchar alpha = 120; - GPU_blend(GPU_BLEND_ALPHA); - if (ibuf->byte_buffer.data) { - uchar *buf = ibuf->byte_buffer.data; - for (int pixel = ibuf->x * ibuf->y; pixel--; buf += 4) { - buf[3] = alpha; - } - } - else if (ibuf->float_buffer.data) { - float *buf = ibuf->float_buffer.data; - for (int pixel = ibuf->x * ibuf->y; pixel--; buf += ibuf->channels) { - buf[3] = (alpha / 255.0f); - } + make_ibuf_semitransparent(ibuf); + } + + /* If thumbnail start or end falls within strip corner rounding area, + * we need to manually set thumbnail pixels that are outside of rounded + * rectangle to be transparent. Ideally this would be done on the GPU + * while drawing, but since rendering is done through OCIO shaders that + * is hard to do. */ + const float xpos = timeline_frame + cut_off; + const float radius = ibuf->y * round_radius * pixely / (y2 - y1); + if (radius > 0.9f) { + if (xpos < seq_left_handle + round_radius * pixelx || + thumb_x_end > seq_right_handle - round_radius * pixelx) + { + /* Work on a copy of the thumbnail image, so that corner rounding + * is not stored into thumbnail cache. */ + ImBuf *copy = IMB_dupImBuf(ibuf); + IMB_freeImBuf(ibuf); + ibuf = copy; + + float round_y_top = ibuf->y * (y_top - y1) / (y2 - y1); + make_ibuf_round_corners(ibuf, + radius, + float2((seq_left_handle - xpos) / zoom_x, 0), + float2((seq_right_handle - xpos) / zoom_x, round_y_top)); } } - ED_draw_imbuf_ctx_clipping(C, - ibuf, - timeline_frame + cut_off, - y1, - true, - timeline_frame + cut_off, - y1, - thumb_x_end, - thumb_y_end, - zoom_x, - zoom_y); + ED_draw_imbuf_ctx_clipping( + C, ibuf, xpos, y1, true, xpos, y1, thumb_x_end, thumb_y_end, zoom_x, zoom_y); IMB_freeImBuf(ibuf); - GPU_blend(GPU_BLEND_NONE); cut_off = 0; timeline_frame = SEQ_render_thumbnail_next_frame_get(scene, seq, timeline_frame, thumb_width); } diff --git a/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc b/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc index 99eb6ea4f27..730045b7ffd 100644 --- a/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc +++ b/source/blender/editors/space_sequencer/sequencer_timeline_draw.cc @@ -10,6 +10,7 @@ #include #include +#include "BLI_array.hh" #include "BLI_blenlib.h" #include "BLI_string_utils.hh" #include "BLI_threads.h" @@ -26,7 +27,11 @@ #include "BKE_global.hh" #include "BKE_sound.h" +#include "GPU_batch.hh" +#include "GPU_batch_presets.hh" #include "GPU_immediate.hh" +#include "GPU_shader_shared.hh" +#include "GPU_uniform_buffer.hh" #include "GPU_viewport.hh" #include "ED_anim_api.hh" @@ -64,6 +69,8 @@ #include "sequencer_intern.hh" #include "sequencer_quads_batch.hh" +using namespace blender; + #define MUTE_ALPHA 120 constexpr float MISSING_ICON_SIZE = 16.0f; @@ -102,15 +109,15 @@ struct TimelineDrawContext { SeqQuadsBatch *quads; }; -blender::Vector sequencer_visible_strips_get(const bContext *C) +Vector sequencer_visible_strips_get(const bContext *C) { return sequencer_visible_strips_get(CTX_data_scene(C), UI_view2d_fromcontext(C)); } -blender::Vector sequencer_visible_strips_get(const Scene *scene, const View2D *v2d) +Vector sequencer_visible_strips_get(const Scene *scene, const View2D *v2d) { const Editing *ed = SEQ_editing_get(scene); - blender::Vector strips; + Vector strips; LISTBASE_FOREACH (Sequence *, seq, ed->seqbasep) { if (min_ii(SEQ_time_left_handle_frame_get(scene, seq), SEQ_time_start_frame_get(seq)) > @@ -214,7 +221,7 @@ static void strip_draw_context_set_strip_content_visibility(TimelineDrawContext static StripDrawContext strip_draw_context_get(TimelineDrawContext *ctx, Sequence *seq) { - using namespace blender::seq; + using namespace seq; StripDrawContext strip_ctx; Scene *scene = ctx->scene; @@ -609,9 +616,11 @@ static void draw_seq_waveform_overlay(TimelineDrawContext *timeline_ctx, } } -static void drawmeta_contents(TimelineDrawContext *timeline_ctx, const StripDrawContext *strip_ctx) +static void drawmeta_contents(TimelineDrawContext *timeline_ctx, + const StripDrawContext *strip_ctx, + float corner_radius) { - using namespace blender::seq; + using namespace seq; Sequence *seq_meta = strip_ctx->seq; if (!strip_ctx->can_draw_strip_content || (timeline_ctx->sseq->flag & SEQ_SHOW_OVERLAY) == 0) { return; @@ -661,15 +670,15 @@ static void drawmeta_contents(TimelineDrawContext *timeline_ctx, const StripDraw col[3] = 196; /* Alpha, used for all meta children. */ + const float meta_x1 = strip_ctx->left_handle + corner_radius * 0.8f * timeline_ctx->pixelx; + const float meta_x2 = strip_ctx->right_handle - corner_radius * 0.8f * timeline_ctx->pixelx; + /* Draw only immediate children (1 level depth). */ LISTBASE_FOREACH (Sequence *, seq, meta_seqbase) { - const int startdisp = SEQ_time_left_handle_frame_get(scene, seq) + offset; - const int enddisp = SEQ_time_right_handle_frame_get(scene, seq) + offset; - - if ((startdisp > strip_ctx->right_handle || enddisp < strip_ctx->left_handle) == 0) { + float x1_chan = SEQ_time_left_handle_frame_get(scene, seq) + offset; + float x2_chan = SEQ_time_right_handle_frame_get(scene, seq) + offset; + if (x1_chan <= meta_x2 && x2_chan >= meta_x1) { float y_chan = (seq->machine - chan_min) / float(chan_range) * draw_range; - float x1_chan = startdisp; - float x2_chan = enddisp; float y1_chan, y2_chan; if (seq->type == SEQ_TYPE_COLOR) { @@ -696,12 +705,8 @@ static void drawmeta_contents(TimelineDrawContext *timeline_ctx, const StripDraw } /* Clamp within parent sequence strip bounds. */ - if (x1_chan < strip_ctx->left_handle) { - x1_chan = strip_ctx->left_handle; - } - if (x2_chan > strip_ctx->right_handle) { - x2_chan = strip_ctx->right_handle; - } + x1_chan = max_ff(x1_chan, meta_x1); + x2_chan = min_ff(x2_chan, meta_x2); y1_chan = strip_ctx->bottom + y_chan + (draw_height * SEQ_STRIP_OFSBOTTOM); y2_chan = strip_ctx->bottom + y_chan + (draw_height * SEQ_STRIP_OFSTOP); @@ -768,119 +773,6 @@ float sequence_handle_size_get_clamped(const Scene *scene, Sequence *seq, const 4.0f)); } -static void draw_seq_handle(const TimelineDrawContext *timeline_ctx, - const StripDrawContext *strip_ctx, - eSeqHandle handle) -{ - const Sequence *seq = strip_ctx->seq; - const bool show_handles = (U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0; - const bool strip_selected = (seq->flag & SELECT) != 0; - const bool handle_selected = ED_sequencer_handle_is_selected(seq, handle); - - if ((!strip_selected || !handle_selected) && !show_handles) { - return; - } - if (SEQ_transform_is_locked(timeline_ctx->channels, seq)) { - return; - } - if ((seq->type & SEQ_TYPE_EFFECT) && SEQ_effect_get_num_inputs(seq->type) > 0) { - return; - } - if (!ED_sequencer_can_select_handle(timeline_ctx->scene, seq, timeline_ctx->v2d)) { - return; - } - - uchar col[4]; - if (strip_selected && handle_selected && seq == SEQ_select_active_get(timeline_ctx->scene)) { - UI_GetThemeColor4ubv(TH_SEQ_ACTIVE, col); - } - else if (strip_selected && handle_selected) { - UI_GetThemeColor4ubv(TH_SEQ_SELECTED, col); - } - else { - col[0] = col[1] = col[2] = 0; - col[3] = 50; - } - - rctf handle_rect = {0, 0, strip_ctx->bottom, strip_ctx->top}; - if (handle == SEQ_HANDLE_LEFT) { - handle_rect.xmin = strip_ctx->left_handle; - handle_rect.xmax = strip_ctx->left_handle + strip_ctx->handle_width; - } - else if (handle == SEQ_HANDLE_RIGHT) { - handle_rect.xmin = strip_ctx->right_handle - strip_ctx->handle_width; - handle_rect.xmax = strip_ctx->right_handle; - } - - timeline_ctx->quads->add_quad( - handle_rect.xmin, handle_rect.ymin, handle_rect.xmax, handle_rect.ymax, col); -} - -/* Strip border, and outline for selected/active strips. */ -static void draw_seq_outline(TimelineDrawContext *timeline_ctx, const StripDrawContext *strip_ctx) -{ - const Sequence *seq = strip_ctx->seq; - const bool selected = seq->flag & SELECT; - const bool active = strip_ctx->is_active_strip; - - /* Outline color. */ - uchar col[4]; - if (selected) { - UI_GetThemeColor3ubv(active ? TH_SEQ_ACTIVE : TH_SEQ_SELECTED, col); - } - else { - /* Color for unselected strips is a bit darker than the background. */ - UI_GetThemeColorShade3ubv(TH_BACK, -40, col); - } - col[3] = 255; - - /* Outline while translating strips: - * - Slightly lighter. - * - Red when overlapping with other strips. */ - const eSeqOverlapMode overlap_mode = SEQ_tool_settings_overlap_mode_get(timeline_ctx->scene); - if ((G.moving & G_TRANSFORM_SEQ) && selected && overlap_mode != SEQ_OVERLAP_OVERWRITE) { - if (seq->flag & SEQ_OVERLAP) { - col[0] = 255; - col[1] = col[2] = 33; - } - else { - UI_GetColorPtrShade3ubv(col, col, 70); - } - } - - /* Selected outline: 2px wide outline, plus 1px wide background inset. */ - const float x0 = strip_ctx->left_handle; - const float x1 = strip_ctx->right_handle; - const float y0 = strip_ctx->bottom; - const float y1 = strip_ctx->top; - const float dx = timeline_ctx->pixelx; - const float dy = timeline_ctx->pixely; - - if (selected) { - /* Left, right, bottom, top. */ - timeline_ctx->quads->add_quad(x0 - dx, y0, x0 + dx, y1, col); - timeline_ctx->quads->add_quad(x1 - dx, y0, x1 + dx, y1, col); - timeline_ctx->quads->add_quad(x0, y0, x1, y0 + dy * 2, col); - timeline_ctx->quads->add_quad(x0, y1 - dy * 2, x1, y1, col); - - /* Inset. */ - UI_GetThemeColor3ubv(TH_BACK, col); - timeline_ctx->quads->add_quad(x0 + dx, y0 + dy * 2, x0 + dx * 2, y1 - dy * 2, col); - timeline_ctx->quads->add_quad(x1 - dx * 2, y0 + dy * 2, x1 - dx, y1 - dy * 2, col); - timeline_ctx->quads->add_quad(x0 + dx, y0 + dy * 2, x1 - dx, y0 + dy * 3, col); - timeline_ctx->quads->add_quad(x0 + dx, y1 - dy * 3, x1 - dx, y1 - dy * 2, col); - } - else if (active) { - /* A subtle highlight outline when active but not selected. */ - UI_GetThemeColorShade3ubv(TH_SEQ_ACTIVE, -40, col); - timeline_ctx->quads->add_wire_quad(x0 + dx, y0, x1 - dx, y1, col); - } - else { - /* Thin wireframe outline for unselected strips. */ - timeline_ctx->quads->add_wire_quad(x0, y0, x1, y1, col); - } -} - static const char *draw_seq_text_get_name(const Sequence *seq) { const char *name = seq->name + 2; @@ -1030,9 +922,8 @@ static void draw_icon_centered(TimelineDrawContext &ctx, } static void draw_strip_icons(TimelineDrawContext *timeline_ctx, - const blender::Vector &strips) + const Vector &strips) { - timeline_ctx->quads->draw(); GPU_blend(GPU_BLEND_ALPHA); UI_icon_draw_cache_begin(); @@ -1207,183 +1098,6 @@ static uchar mute_alpha_factor_get(const ListBase *channels, const Sequence *seq return 255; } -static void draw_strip_color_band(TimelineDrawContext *timeline_ctx, - const StripDrawContext *strip_ctx) -{ - const Sequence *seq = strip_ctx->seq; - if ((timeline_ctx->sseq->flag & SEQ_SHOW_OVERLAY) == 0 || (seq->type != SEQ_TYPE_COLOR)) { - return; - } - - SolidColorVars *colvars = (SolidColorVars *)seq->effectdata; - uchar col[4]; - rgb_float_to_uchar(col, colvars->col); - col[3] = mute_alpha_factor_get(timeline_ctx->channels, seq); - - timeline_ctx->quads->add_quad(strip_ctx->left_handle, - strip_ctx->bottom, - strip_ctx->right_handle, - strip_ctx->strip_content_top, - col); - - /* 1px line to better separate the color band. */ - UI_GetColorPtrShade3ubv(col, col, -20); - timeline_ctx->quads->add_line(strip_ctx->left_handle, - strip_ctx->strip_content_top, - strip_ctx->right_handle, - strip_ctx->strip_content_top, - col); -} - -static void draw_strip_background(TimelineDrawContext *timeline_ctx, - const StripDrawContext *strip_ctx) -{ - const Scene *scene = timeline_ctx->scene; - const Sequence *seq = strip_ctx->seq; - - uchar col[4]; - color3ubv_from_seq(scene, seq, strip_ctx->show_strip_color_tag, col); - col[3] = mute_alpha_factor_get(timeline_ctx->channels, seq); - /* Muted strips: turn almost gray. */ - if (col[3] == MUTE_ALPHA) { - uchar muted_color[3] = {128, 128, 128}; - UI_GetColorPtrBlendShade3ubv(col, muted_color, col, 0.8f, 0); - } - - /* Draw the main strip body. */ - float x1 = strip_ctx->is_single_image ? strip_ctx->left_handle : strip_ctx->content_start; - float x2 = strip_ctx->is_single_image ? strip_ctx->right_handle : strip_ctx->content_end; - timeline_ctx->quads->add_quad(x1, strip_ctx->bottom, x2, strip_ctx->top, col); - - /* Draw background for hold still regions. */ - if (!strip_ctx->is_single_image) { - UI_GetColorPtrShade3ubv(col, col, -35); - if (SEQ_time_has_left_still_frames(scene, seq)) { - timeline_ctx->quads->add_quad(strip_ctx->left_handle, - strip_ctx->bottom, - strip_ctx->content_start, - strip_ctx->top, - col); - } - if (SEQ_time_has_right_still_frames(scene, seq)) { - timeline_ctx->quads->add_quad( - strip_ctx->content_end, strip_ctx->bottom, strip_ctx->right_handle, strip_ctx->top, col); - } - } -} - -enum TransitionType { - STRIP_TRANSITION_IN, - STRIP_TRANSITION_OUT, -}; - -static void draw_seq_transition_strip_half(TimelineDrawContext *timeline_ctx, - const StripDrawContext *strip_ctx, - const TransitionType transition_type) -{ - - const Sequence *seq1 = strip_ctx->seq->seq1; - const Sequence *seq2 = strip_ctx->seq->seq2; - const Sequence *target_seq = (transition_type == STRIP_TRANSITION_IN) ? seq1 : seq2; - - uchar col[4]; - if (target_seq->type == SEQ_TYPE_COLOR) { - SolidColorVars *colvars = (SolidColorVars *)target_seq->effectdata; - rgb_float_to_uchar(col, colvars->col); - } - else { - color3ubv_from_seq(timeline_ctx->scene, target_seq, strip_ctx->show_strip_color_tag, col); - /* If the transition inputs are of the same type, draw the right side slightly darker. */ - if ((seq1->type == seq2->type) && (transition_type == STRIP_TRANSITION_OUT)) { - UI_GetColorPtrShade3ubv(col, col, -15); - } - } - - col[3] = mute_alpha_factor_get(timeline_ctx->channels, strip_ctx->seq); - - float tri[3][2]; - - if (transition_type == STRIP_TRANSITION_IN) { - copy_v2_fl2(tri[0], strip_ctx->content_start, strip_ctx->bottom); - copy_v2_fl2(tri[1], strip_ctx->content_start, strip_ctx->strip_content_top); - copy_v2_fl2(tri[2], strip_ctx->content_end, strip_ctx->bottom); - } - else { - copy_v2_fl2(tri[0], strip_ctx->content_start, strip_ctx->strip_content_top); - copy_v2_fl2(tri[1], strip_ctx->content_end, strip_ctx->strip_content_top); - copy_v2_fl2(tri[2], strip_ctx->content_end, strip_ctx->bottom); - } - - /* Slightly suboptimal: we are pretending to draw a quad with two vertices at the same location. - */ - timeline_ctx->quads->add_quad( - tri[0][0], tri[0][1], tri[0][0], tri[0][1], tri[1][0], tri[1][1], tri[2][0], tri[2][1], col); -} - -static void draw_seq_transition_strip(TimelineDrawContext *timeline_ctx, - const StripDrawContext *strip_ctx) -{ - if (!strip_ctx->can_draw_strip_content || (timeline_ctx->sseq->flag & SEQ_SHOW_OVERLAY) == 0 || - !ELEM(strip_ctx->seq->type, SEQ_TYPE_CROSS, SEQ_TYPE_GAMCROSS, SEQ_TYPE_WIPE)) - { - return; - } - - draw_seq_transition_strip_half(timeline_ctx, strip_ctx, STRIP_TRANSITION_IN); - draw_seq_transition_strip_half(timeline_ctx, strip_ctx, STRIP_TRANSITION_OUT); -} - -static void draw_seq_locked(TimelineDrawContext *timeline_ctx, - const blender::Vector &strips) -{ - GPU_blend(GPU_BLEND_ALPHA); - - uint pos = GPU_vertformat_attr_add(immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - immBindBuiltinProgram(GPU_SHADER_2D_DIAG_STRIPES); - - immUniform4f("color1", 1.0f, 1.0f, 1.0f, 0.0f); - immUniform4f("color2", 0.0f, 0.0f, 0.0f, 0.25f); - immUniform1i("size1", 8); - immUniform1i("size2", 4); - - for (const StripDrawContext &strip : strips) { - if (!SEQ_transform_is_locked(timeline_ctx->channels, strip.seq)) { - continue; - } - - immRectf(pos, strip.left_handle, strip.bottom, strip.right_handle, strip.strip_content_top); - } - - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); -} - -static void draw_seq_missing(TimelineDrawContext *timeline_ctx, const StripDrawContext *strip_ctx) -{ - if (!strip_ctx->missing_data_block && !strip_ctx->missing_media) { - return; - } - /* Do not tint title area for muted strips; we want to see gray for them. */ - if (!SEQ_render_is_muted(timeline_ctx->channels, strip_ctx->seq)) { - uchar col_top[4] = {112, 0, 0, 230}; - timeline_ctx->quads->add_quad(strip_ctx->left_handle, - strip_ctx->top, - strip_ctx->right_handle, - strip_ctx->strip_content_top, - col_top); - } - /* Do not tint content area for meta strips; we want to display children. */ - if (strip_ctx->seq->type != SEQ_TYPE_META) { - uchar col_main[4] = {64, 0, 0, 230}; - timeline_ctx->quads->add_quad(strip_ctx->left_handle, - strip_ctx->strip_content_top, - strip_ctx->right_handle, - strip_ctx->bottom, - col_main); - } -} - /** * Draw f-curves as darkened regions of the strip: * - Volume for sound strips. @@ -1493,38 +1207,6 @@ static void draw_multicam_highlight(TimelineDrawContext *timeline_ctx, timeline_ctx->quads->add_quad(v2d->cur.xmin, channel, v2d->cur.xmax, channel + 1, color); } -/* Highlight strip if it is input of selected active strip. */ -static void draw_effect_inputs_highlight(TimelineDrawContext *timeline_ctx, - const StripDrawContext *strip_ctx) -{ - Sequence *act_seq = SEQ_select_active_get(timeline_ctx->scene); - - if (act_seq == nullptr || (act_seq->flag & SELECT) == 0) { - return; - } - if (act_seq->seq1 != strip_ctx->seq && act_seq->seq2 != strip_ctx->seq) { - return; - } - - uchar color[4] = {255, 255, 255, 48}; - timeline_ctx->quads->add_quad( - strip_ctx->left_handle, strip_ctx->bottom, strip_ctx->right_handle, strip_ctx->top, color); -} - -static void draw_seq_solo_highlight(TimelineDrawContext *timeline_ctx, - const StripDrawContext *strip_ctx) -{ - if (ED_sequencer_special_preview_get() == nullptr || - ED_sequencer_special_preview_get() != strip_ctx->seq) - { - return; - } - - uchar color[4] = {255, 255, 255, 48}; - timeline_ctx->quads->add_quad( - strip_ctx->left_handle, strip_ctx->bottom, strip_ctx->right_handle, strip_ctx->top, color); -} - /* Force redraw, when prefetching and using cache view. */ static void seq_prefetch_wm_notify(const bContext *C, Scene *scene) { @@ -1560,11 +1242,11 @@ static void draw_seq_timeline_channels(TimelineDrawContext *ctx) * sure that visually selected are always "on top" of others. It matters * while selection is being dragged over other strips. */ static void visible_strips_ordered_get(TimelineDrawContext *timeline_ctx, - blender::Vector &r_unselected, - blender::Vector &r_selected) + Vector &r_unselected, + Vector &r_selected) { Sequence *act_seq = SEQ_select_active_get(timeline_ctx->scene); - blender::Vector strips = sequencer_visible_strips_get(timeline_ctx->C); + Vector strips = sequencer_visible_strips_get(timeline_ctx->C); r_unselected.clear(); r_selected.clear(); const bool act_seq_is_selected = act_seq != nullptr && (act_seq->flag & SELECT) != 0; @@ -1594,8 +1276,294 @@ static void visible_strips_ordered_get(TimelineDrawContext *timeline_ctx, } } +static uint color_pack(const uchar rgba[4]) +{ + return rgba[0] | (rgba[1] << 8u) | (rgba[2] << 16u) | (rgba[3] << 24u); +} + +static float calc_strip_round_radius(float pixely) +{ + float height_pixels = 1.0f / pixely; + if (height_pixels < 16.0f) { + return 0.0f; + } + if (height_pixels < 64.0f) { + return 4.0f; + } + if (height_pixels < 128.0f) { + return 6.0f; + } + return 8.0f; +} + +class SeqStripsBatch { + SeqContextDrawData context_; + Array strips_; + GPUUniformBuf *ubo_context_ = nullptr; + GPUUniformBuf *ubo_strips_ = nullptr; + GPUShader *shader_ = nullptr; + gpu::Batch *batch_ = nullptr; + int binding_context_ = 0; + int binding_strips_ = 0; + int strips_count_ = 0; + + public: + SeqStripsBatch(float pixelx, float pixely) : strips_(GPU_SEQ_STRIP_DRAW_DATA_LEN) + { + context_.pixelx = pixelx; + context_.pixely = pixely; + context_.inv_pixelx = 1.0f / pixelx; + context_.inv_pixely = 1.0f / pixely; + context_.round_radius = calc_strip_round_radius(pixely); + + uchar col[4]; + UI_GetThemeColor3ubv(TH_BACK, col); + col[3] = 255; + context_.col_back = color_pack(col); + + shader_ = GPU_shader_get_builtin_shader(GPU_SHADER_SEQUENCER_STRIPS); + binding_strips_ = GPU_shader_get_ubo_binding(shader_, "strip_data"); + binding_context_ = GPU_shader_get_ubo_binding(shader_, "context_data"); + + ubo_context_ = GPU_uniformbuf_create_ex(sizeof(SeqContextDrawData), &context_, __func__); + ubo_strips_ = GPU_uniformbuf_create(sizeof(SeqStripDrawData) * GPU_SEQ_STRIP_DRAW_DATA_LEN); + + batch_ = GPU_batch_preset_quad(); + } + + ~SeqStripsBatch() + { + flush_batch(); + + GPU_uniformbuf_unbind(ubo_strips_); + GPU_uniformbuf_free(ubo_strips_); + GPU_uniformbuf_unbind(ubo_context_); + GPU_uniformbuf_free(ubo_context_); + } + + SeqStripDrawData &add_strip(const StripDrawContext &strip) + { + if (strips_count_ == GPU_SEQ_STRIP_DRAW_DATA_LEN) { + flush_batch(); + } + + SeqStripDrawData &res = strips_[strips_count_]; + strips_count_++; + + memset(&res, 0, sizeof(res)); + res.content_start = strip.content_start; + res.content_end = strip.content_end; + res.top = strip.top; + res.bottom = strip.bottom; + res.strip_content_top = strip.strip_content_top; + res.left_handle = strip.left_handle; + res.right_handle = strip.right_handle; + res.handle_width = strip.handle_width; + if (strip.is_single_image) { + res.flags |= GPU_SEQ_FLAG_SINGLE_IMAGE; + } + return res; + } + + void flush_batch() + { + if (strips_count_ == 0) { + return; + } + + GPU_uniformbuf_update(ubo_strips_, strips_.data()); + + GPU_shader_bind(shader_); + GPU_uniformbuf_bind(ubo_strips_, binding_strips_); + GPU_uniformbuf_bind(ubo_context_, binding_context_); + + GPU_batch_set_shader(batch_, shader_); + GPU_batch_draw_instance_range(batch_, 0, strips_count_); + strips_count_ = 0; + } +}; + +static void draw_strips_background(TimelineDrawContext *timeline_ctx, + SeqStripsBatch &strips_batch, + const Vector &strips) +{ + GPU_blend(GPU_BLEND_ALPHA_PREMULT); + + const bool show_overlay = (timeline_ctx->sseq->flag & SEQ_SHOW_OVERLAY) != 0; + const Scene *scene = timeline_ctx->scene; + for (const StripDrawContext &strip : strips) { + SeqStripDrawData &data = strips_batch.add_strip(strip); + + data.flags |= GPU_SEQ_FLAG_BACKGROUND_PART; + + /* Background color. */ + uchar col[4]; + color3ubv_from_seq(scene, strip.seq, strip.show_strip_color_tag, col); + col[3] = mute_alpha_factor_get(timeline_ctx->channels, strip.seq); + /* Muted strips: turn almost gray. */ + if (col[3] == MUTE_ALPHA) { + uchar muted_color[3] = {128, 128, 128}; + UI_GetColorPtrBlendShade3ubv(col, muted_color, col, 0.8f, 0); + } + data.col_background = color_pack(col); + + /* Color band state. */ + if (show_overlay && (strip.seq->type == SEQ_TYPE_COLOR)) { + data.flags |= GPU_SEQ_FLAG_COLOR_BAND; + SolidColorVars *colvars = (SolidColorVars *)strip.seq->effectdata; + rgb_float_to_uchar(col, colvars->col); + data.col_color_band = color_pack(col); + } + + /* Transition state. */ + if (show_overlay && strip.can_draw_strip_content && + ELEM(strip.seq->type, SEQ_TYPE_CROSS, SEQ_TYPE_GAMCROSS, SEQ_TYPE_WIPE)) + { + data.flags |= GPU_SEQ_FLAG_TRANSITION; + + const Sequence *seq1 = strip.seq->seq1; + const Sequence *seq2 = strip.seq->seq2; + + /* Left side. */ + if (seq1->type == SEQ_TYPE_COLOR) { + rgb_float_to_uchar(col, ((const SolidColorVars *)seq1->effectdata)->col); + } + else { + color3ubv_from_seq(scene, seq1, strip.show_strip_color_tag, col); + } + data.col_transition_in = color_pack(col); + + /* Right side. */ + if (seq2->type == SEQ_TYPE_COLOR) { + rgb_float_to_uchar(col, ((const SolidColorVars *)seq2->effectdata)->col); + } + else { + color3ubv_from_seq(scene, seq2, strip.show_strip_color_tag, col); + /* If the transition inputs are of the same type, draw the right side slightly darker. */ + if (seq1->type == seq2->type) { + UI_GetColorPtrShade3ubv(col, col, -15); + } + } + data.col_transition_out = color_pack(col); + } + } + strips_batch.flush_batch(); + GPU_blend(GPU_BLEND_ALPHA); +} + +static void draw_strips_foreground(TimelineDrawContext *timeline_ctx, + SeqStripsBatch &strips_batch, + const Vector &strips) +{ + GPU_blend(GPU_BLEND_ALPHA_PREMULT); + const Scene *scene = timeline_ctx->scene; + const Sequence *act_seq = SEQ_select_active_get(scene); + const Sequence *special_preview = ED_sequencer_special_preview_get(); + const bool show_handles = (U.sequencer_editor_flag & USER_SEQ_ED_SIMPLE_TWEAKING) == 0; + + uchar col[4]; + for (const StripDrawContext &strip : strips) { + SeqStripDrawData &data = strips_batch.add_strip(strip); + + /* Missing media state. */ + if (strip.missing_data_block || strip.missing_media) { + /* Do not tint title area for muted strips; we want to see gray for them. */ + if (!SEQ_render_is_muted(timeline_ctx->channels, strip.seq)) { + data.flags |= GPU_SEQ_FLAG_MISSING_TITLE; + } + /* Do not tint content area for meta strips; we want to display children. */ + if (strip.seq->type != SEQ_TYPE_META) { + data.flags |= GPU_SEQ_FLAG_MISSING_CONTENT; + } + } + + /* Locked state. */ + const bool locked = SEQ_transform_is_locked(timeline_ctx->channels, strip.seq); + if (locked) { + data.flags |= GPU_SEQ_FLAG_LOCKED; + } + + /* Border and outline. */ + const bool selected = strip.seq->flag & SELECT; + const bool active = strip.is_active_strip; + if (selected) { + UI_GetThemeColor3ubv(active ? TH_SEQ_ACTIVE : TH_SEQ_SELECTED, col); + } + else { + /* Color for unselected strips is a bit darker than the background. */ + UI_GetThemeColorShade3ubv(TH_BACK, -40, col); + } + col[3] = 255; + /* Outline while translating strips: + * - Slightly lighter. + * - Red when overlapping with other strips. */ + const eSeqOverlapMode overlap_mode = SEQ_tool_settings_overlap_mode_get(timeline_ctx->scene); + if ((G.moving & G_TRANSFORM_SEQ) && selected && overlap_mode != SEQ_OVERLAP_OVERWRITE) { + if (strip.seq->flag & SEQ_OVERLAP) { + col[0] = 255; + col[1] = col[2] = 33; + } + else { + UI_GetColorPtrShade3ubv(col, col, 70); + } + } + if (selected) + data.flags |= GPU_SEQ_FLAG_SELECTED; + else if (active) { + /* A subtle highlight outline when active but not selected. */ + UI_GetThemeColorShade3ubv(TH_SEQ_ACTIVE, -40, col); + data.flags |= GPU_SEQ_FLAG_ACTIVE; + } + data.col_outline = color_pack(col); + + /* Highlight if strip is an input of an active strip, or if the strip is solo preview. */ + if (act_seq != nullptr && (act_seq->flag & SELECT) != 0) { + if (act_seq->seq1 == strip.seq || act_seq->seq2 == strip.seq) { + data.flags |= GPU_SEQ_FLAG_HIGHLIGHT; + } + } + if (special_preview == strip.seq) { + data.flags |= GPU_SEQ_FLAG_HIGHLIGHT; + } + + /* Handles on left/right side. */ + if (!locked && ED_sequencer_can_select_handle(scene, strip.seq, timeline_ctx->v2d)) { + data.flags |= GPU_SEQ_FLAG_HANDLES; + const bool selected_l = ED_sequencer_handle_is_selected(strip.seq, SEQ_HANDLE_LEFT); + const bool selected_r = ED_sequencer_handle_is_selected(strip.seq, SEQ_HANDLE_RIGHT); + const bool show_l = show_handles || (selected && selected_l); + const bool show_r = show_handles || (selected && selected_r); + + /* Left handle color. */ + col[0] = col[1] = col[2] = 0; + col[3] = 50; + if (selected && selected_l) { + UI_GetThemeColor4ubv(active ? TH_SEQ_ACTIVE : TH_SEQ_SELECTED, col); + } + if (!show_l) { + col[3] = 0; + } + data.col_handle_left = color_pack(col); + + /* Right handle color. */ + col[0] = col[1] = col[2] = 0; + col[3] = 50; + if (selected && selected_r) { + UI_GetThemeColor4ubv(active ? TH_SEQ_ACTIVE : TH_SEQ_SELECTED, col); + } + if (!show_r) { + col[3] = 0; + } + data.col_handle_right = color_pack(col); + } + } + strips_batch.flush_batch(); + GPU_blend(GPU_BLEND_ALPHA); +} + static void draw_seq_strips(TimelineDrawContext *timeline_ctx, - const blender::Vector &strips) + SeqStripsBatch &strips_batch, + const Vector &strips) { if (strips.is_empty()) { return; @@ -1605,17 +1573,17 @@ static void draw_seq_strips(TimelineDrawContext *timeline_ctx, /* Draw parts of strips below thumbnails. */ GPU_blend(GPU_BLEND_ALPHA); + draw_strips_background(timeline_ctx, strips_batch, strips); + + const float round_radius = calc_strip_round_radius(timeline_ctx->pixely); for (const StripDrawContext &strip_ctx : strips) { - draw_strip_background(timeline_ctx, &strip_ctx); - draw_strip_color_band(timeline_ctx, &strip_ctx); draw_strip_offsets(timeline_ctx, &strip_ctx); - draw_seq_transition_strip(timeline_ctx, &strip_ctx); - drawmeta_contents(timeline_ctx, &strip_ctx); + drawmeta_contents(timeline_ctx, &strip_ctx, round_radius); } timeline_ctx->quads->draw(); - GPU_blend(GPU_BLEND_NONE); /* Draw all thumbnails. */ + GPU_blend(GPU_BLEND_ALPHA); for (const StripDrawContext &strip_ctx : strips) { draw_seq_strip_thumbnail(timeline_ctx->v2d, timeline_ctx->C, @@ -1623,8 +1591,10 @@ static void draw_seq_strips(TimelineDrawContext *timeline_ctx, strip_ctx.seq, strip_ctx.bottom, strip_ctx.strip_content_top, + strip_ctx.top, timeline_ctx->pixelx, - timeline_ctx->pixely); + timeline_ctx->pixely, + round_radius); } /* Draw parts of strips above thumbnails. */ @@ -1632,53 +1602,38 @@ static void draw_seq_strips(TimelineDrawContext *timeline_ctx, for (const StripDrawContext &strip_ctx : strips) { draw_seq_fcurve_overlay(timeline_ctx, &strip_ctx); draw_seq_waveform_overlay(timeline_ctx, &strip_ctx); - draw_seq_missing(timeline_ctx, &strip_ctx); - } - timeline_ctx->quads->draw(); - GPU_blend(GPU_BLEND_NONE); - - /* Locked state is drawn separately since it uses a different shader. */ - draw_seq_locked(timeline_ctx, strips); - - /* Draw the rest. */ - GPU_blend(GPU_BLEND_ALPHA); - for (const StripDrawContext &strip_ctx : strips) { - draw_effect_inputs_highlight(timeline_ctx, &strip_ctx); draw_multicam_highlight(timeline_ctx, &strip_ctx); - draw_seq_solo_highlight(timeline_ctx, &strip_ctx); - draw_seq_handle(timeline_ctx, &strip_ctx, SEQ_HANDLE_LEFT); - draw_seq_handle(timeline_ctx, &strip_ctx, SEQ_HANDLE_RIGHT); draw_handle_transform_text(timeline_ctx, &strip_ctx, SEQ_HANDLE_LEFT); draw_handle_transform_text(timeline_ctx, &strip_ctx, SEQ_HANDLE_RIGHT); - draw_seq_outline(timeline_ctx, &strip_ctx); draw_seq_text_overlay(timeline_ctx, &strip_ctx); } - - /* Draw icons separately (different shader). */ - draw_strip_icons(timeline_ctx, strips); - timeline_ctx->quads->draw(); + draw_strips_foreground(timeline_ctx, strips_batch, strips); + + /* Draw icons. */ + draw_strip_icons(timeline_ctx, strips); + /* Draw text labels with a drop shadow. */ const int font_id = BLF_default(); BLF_enable(font_id, BLF_SHADOW); - BLF_shadow(font_id, FontShadowType::Blur3x3, blender::float4{0.0f, 0.0f, 0.0f, 1.0f}); + BLF_shadow(font_id, FontShadowType::Blur3x3, float4{0.0f, 0.0f, 0.0f, 1.0f}); BLF_shadow_offset(font_id, 1, -1); UI_view2d_text_cache_draw(timeline_ctx->region); BLF_disable(font_id, BLF_SHADOW); GPU_blend(GPU_BLEND_NONE); } -static void draw_seq_strips(TimelineDrawContext *timeline_ctx) +static void draw_seq_strips(TimelineDrawContext *timeline_ctx, SeqStripsBatch &strips_batch) { if (timeline_ctx->ed == nullptr) { return; } - blender::Vector unselected, selected; + Vector unselected, selected; visible_strips_ordered_get(timeline_ctx, unselected, selected); - draw_seq_strips(timeline_ctx, unselected); - draw_seq_strips(timeline_ctx, selected); + draw_seq_strips(timeline_ctx, strips_batch, unselected); + draw_seq_strips(timeline_ctx, strips_batch, selected); } static void draw_timeline_sfra_efra(TimelineDrawContext *ctx) @@ -1773,7 +1728,6 @@ static bool draw_cache_view_iter_fn(void *userdata, int timeline_frame, int cache_type) { - using blender::uchar4; CacheDrawData *drawdata = static_cast(userdata); const View2D *v2d = drawdata->v2d; float stripe_top, stripe_bot; @@ -1847,7 +1801,6 @@ static void draw_cache_stripe(const Scene *scene, static void draw_cache_background(const bContext *C, CacheDrawData *draw_data) { - using blender::uchar4; const Scene *scene = CTX_data_scene(C); const View2D *v2d = UI_view2d_fromcontext(C); const SpaceSeq *sseq = CTX_wm_space_seq(C); @@ -1876,7 +1829,7 @@ static void draw_cache_background(const bContext *C, CacheDrawData *draw_data) return; } - blender::Vector strips = sequencer_visible_strips_get(C); + Vector strips = sequencer_visible_strips_get(C); strips.remove_if([&](Sequence *seq) { return seq->type == SEQ_TYPE_SOUND_RAM; }); for (const Sequence *seq : strips) { @@ -2029,6 +1982,7 @@ void draw_timeline_seq(const bContext *C, ARegion *region) { SeqQuadsBatch quads_batch; TimelineDrawContext ctx = timeline_draw_context_get(C, &quads_batch); + SeqStripsBatch strips_batch(ctx.pixelx, ctx.pixely); draw_timeline_pre_view_callbacks(&ctx); UI_ThemeClearColor(TH_BACK); @@ -2036,7 +1990,7 @@ void draw_timeline_seq(const bContext *C, ARegion *region) draw_timeline_grid(&ctx); draw_timeline_backdrop(&ctx); draw_timeline_sfra_efra(&ctx); - draw_seq_strips(&ctx); + draw_seq_strips(&ctx, strips_batch); sequencer_draw_retiming(C, &quads_batch); draw_timeline_markers(&ctx); UI_view2d_view_ortho(ctx.v2d); diff --git a/source/blender/gpu/CMakeLists.txt b/source/blender/gpu/CMakeLists.txt index fbbcef64ef1..199beddb9f5 100644 --- a/source/blender/gpu/CMakeLists.txt +++ b/source/blender/gpu/CMakeLists.txt @@ -503,6 +503,9 @@ set(GLSL_SRC shaders/gpu_shader_keyframe_shape_vert.glsl shaders/gpu_shader_keyframe_shape_frag.glsl + shaders/gpu_shader_sequencer_strips_vert.glsl + shaders/gpu_shader_sequencer_strips_frag.glsl + shaders/gpu_shader_codegen_lib.glsl shaders/common/gpu_shader_bicubic_sampler_lib.glsl @@ -788,6 +791,7 @@ set(SRC_SHADER_CREATE_INFOS shaders/infos/gpu_shader_instance_varying_color_varying_size_info.hh shaders/infos/gpu_shader_keyframe_shape_info.hh shaders/infos/gpu_shader_line_dashed_uniform_color_info.hh + shaders/infos/gpu_shader_sequencer_info.hh shaders/infos/gpu_shader_simple_lighting_info.hh shaders/infos/gpu_shader_text_info.hh shaders/infos/gpu_srgb_to_framebuffer_space_info.hh diff --git a/source/blender/gpu/GPU_shader_builtin.hh b/source/blender/gpu/GPU_shader_builtin.hh index 43935ec4ce8..17635df6363 100644 --- a/source/blender/gpu/GPU_shader_builtin.hh +++ b/source/blender/gpu/GPU_shader_builtin.hh @@ -75,6 +75,9 @@ enum eGPUBuiltinShader { /** Draw wide lines with uniform color. Has an additional clip plane parameter. */ GPU_SHADER_3D_POLYLINE_CLIPPED_UNIFORM_COLOR, + /** Draw strip widgets in sequencer timeline. */ + GPU_SHADER_SEQUENCER_STRIPS, + /** Compute shaders to generate 2d index buffers (mainly for curve drawing). */ GPU_SHADER_INDEXBUF_POINTS, GPU_SHADER_INDEXBUF_LINES, diff --git a/source/blender/gpu/GPU_shader_shared.hh b/source/blender/gpu/GPU_shader_shared.hh index 55fa10e78f2..cc34a03f286 100644 --- a/source/blender/gpu/GPU_shader_shared.hh +++ b/source/blender/gpu/GPU_shader_shared.hh @@ -90,6 +90,55 @@ struct MultiIconCallData { }; BLI_STATIC_ASSERT_ALIGN(MultiIconCallData, 16) +#define GPU_SEQ_STRIP_DRAW_DATA_LEN 256 + +enum eGPUSeqFlags : uint32_t { + GPU_SEQ_FLAG_BACKGROUND_PART = (1u << 0u), + GPU_SEQ_FLAG_SINGLE_IMAGE = (1u << 1u), + GPU_SEQ_FLAG_COLOR_BAND = (1u << 2u), + GPU_SEQ_FLAG_TRANSITION = (1u << 3u), + GPU_SEQ_FLAG_LOCKED = (1u << 4u), + GPU_SEQ_FLAG_MISSING_TITLE = (1u << 5u), + GPU_SEQ_FLAG_MISSING_CONTENT = (1u << 6u), + GPU_SEQ_FLAG_SELECTED = (1u << 7u), + GPU_SEQ_FLAG_ACTIVE = (1u << 8u), + GPU_SEQ_FLAG_HIGHLIGHT = (1u << 9u), + GPU_SEQ_FLAG_HANDLES = (1u << 10u), +}; + +/* VSE per-strip data for timeline rendering. */ +struct SeqStripDrawData { + /* Horizontal strip positions (1.0 is one frame). */ + float left_handle, right_handle; /* Left and right strip sides. */ + float content_start, content_end; /* Start and end of actual content (only relevant for strips that have holdout regions). */ + float handle_width; + /* Vertical strip positions (1.0 is one channel). */ + float bottom; + float top; + float strip_content_top; /* Content coordinate, i.e. below title bar if there is one. */ + uint flags; /* eGPUSeqFlags bitmask. */ + /* Strip colors, each is uchar4 packed with equivalent of packUnorm4x8. */ + uint col_background; + uint col_outline; + uint col_color_band; + uint col_transition_in, col_transition_out; + uint col_handle_left, col_handle_right; +}; +BLI_STATIC_ASSERT_ALIGN(SeqStripDrawData, 16) +BLI_STATIC_ASSERT(sizeof(SeqStripDrawData) * GPU_SEQ_STRIP_DRAW_DATA_LEN <= 16384, + "SeqStripDrawData UBO must not exceed minspec UBO size (16K)") + +/* VSE global data for timeline rendering. */ +struct SeqContextDrawData { + float pixelx, pixely; /* Size of one pixel in timeline coordinate space. */ + float inv_pixelx, inv_pixely; + float round_radius; + uint col_back; + float _pad0, _pad1; +}; +BLI_STATIC_ASSERT_ALIGN(SeqContextDrawData, 16) + + enum TestStatus : uint32_t { TEST_STATUS_NONE = 0u, TEST_STATUS_PASSED = 1u, diff --git a/source/blender/gpu/intern/gpu_shader_builtin.cc b/source/blender/gpu/intern/gpu_shader_builtin.cc index 1b40caff978..4d7d44f2e49 100644 --- a/source/blender/gpu/intern/gpu_shader_builtin.cc +++ b/source/blender/gpu/intern/gpu_shader_builtin.cc @@ -87,6 +87,8 @@ static const char *builtin_shader_create_info_name(eGPUBuiltinShader shader) return "gpu_shader_2D_nodelink_inst"; case GPU_SHADER_GPENCIL_STROKE: return "gpu_shader_gpencil_stroke"; + case GPU_SHADER_SEQUENCER_STRIPS: + return "gpu_shader_sequencer_strips"; case GPU_SHADER_INDEXBUF_POINTS: return "gpu_shader_index_2d_array_points"; case GPU_SHADER_INDEXBUF_LINES: diff --git a/source/blender/gpu/shaders/gpu_shader_sequencer_strips_frag.glsl b/source/blender/gpu/shaders/gpu_shader_sequencer_strips_frag.glsl new file mode 100644 index 00000000000..71ff97d4aa3 --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_sequencer_strips_frag.glsl @@ -0,0 +1,161 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/* Signed distance to rounded box, centered at origin. + * Reference: https://iquilezles.org/articles/distfunctions2d/ */ +float sdf_rounded_box(vec2 pos, vec2 size, float radius) +{ + vec2 q = abs(pos) - size + radius; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius; +} + +vec3 color_shade(vec3 rgb, float shade) +{ + rgb += vec3(shade / 255.0); + rgb = clamp(rgb, vec3(0.0), vec3(1.0)); + return rgb; +} + +/* Blends in a straight alpha `color` into premultiplied `cur` and returns premultiplied result. */ +vec4 blend_color(vec4 cur, vec4 color) +{ + float t = color.a; + return cur * (1.0 - t) + vec4(color.rgb * t, t); +} + +/* Given signed distance `d` to a shape and current premultiplied color `cur`, blends + * in an outline of at least 1px width (plus `extra_half_width` on each side), inset + * by `inset` pixels. Outline color `outline_color` is in straight alpha. */ +vec4 add_outline(float d, float extra_half_width, float inset, vec4 cur, vec4 outline_color) +{ + float f = abs(d + inset) - extra_half_width; + float a = clamp(1.0 - f, 0.0, 1.0); + outline_color.a *= a; + return blend_color(cur, outline_color); +} + +void main() +{ + vec2 co = co_interp; + + SeqStripDrawData strip = strip_data[strip_id]; + vec2 size = vec2(strip.right_handle - strip.left_handle, strip.top - strip.bottom) * 0.5; + vec2 center = vec2(strip.right_handle + strip.left_handle, strip.top + strip.bottom) * 0.5; + + /* Transform strip rectangle into pixel coordinates, so that + * rounded corners have proper aspect ratio and can be expressed in pixels. */ + vec2 view_to_pixel = vec2(context_data.inv_pixelx, context_data.inv_pixely); + size *= view_to_pixel; + center *= view_to_pixel; + vec2 pos = co * view_to_pixel; + + float radius = context_data.round_radius; + if (radius > size.x) { + radius = 0.0; + } + + float sdf = sdf_rounded_box(pos - center, size, radius); + + vec4 col = vec4(0.0); + + bool back_part = (strip.flags & GPU_SEQ_FLAG_BACKGROUND_PART) != 0; + + if (back_part) { + + col = unpackUnorm4x8(strip.col_background); + /* Darker background for multi-image strip hold still regions. */ + if ((strip.flags & GPU_SEQ_FLAG_SINGLE_IMAGE) == 0) { + if (co.x < strip.content_start || co.x > strip.content_end) { + col.rgb = color_shade(col.rgb, -35.0); + } + } + + /* Color band. */ + if ((strip.flags & GPU_SEQ_FLAG_COLOR_BAND) != 0) { + if (co.y < strip.strip_content_top) { + col.rgb = unpackUnorm4x8(strip.col_color_band).rgb; + /* Darker line to better separate the color band. */ + if (co.y > strip.strip_content_top - context_data.pixely) { + col.rgb = color_shade(col.rgb, -20.0); + } + } + } + + /* Transition. */ + if ((strip.flags & GPU_SEQ_FLAG_TRANSITION) != 0) { + if (co.x >= strip.content_start && co.x <= strip.content_end && + co.y < strip.strip_content_top) + { + float diag_y = strip.strip_content_top - (strip.strip_content_top - strip.bottom) * + (co.x - strip.content_start) / + (strip.content_end - strip.content_start); + uint transition_color = co.y <= diag_y ? strip.col_transition_in : + strip.col_transition_out; + col.rgb = unpackUnorm4x8(transition_color).rgb; + } + } + + col.rgb *= col.a; /* Premultiply alpha. */ + } + else { + /* Missing media. */ + if ((strip.flags & GPU_SEQ_FLAG_MISSING_TITLE) != 0) { + if (co.y > strip.strip_content_top) { + col = blend_color(col, vec4(112.0 / 255.0, 0.0, 0.0, 230.0 / 255.0)); + } + } + if ((strip.flags & GPU_SEQ_FLAG_MISSING_CONTENT) != 0) { + if (co.y <= strip.strip_content_top) { + col = blend_color(col, vec4(64.0 / 255.0, 0.0, 0.0, 230.0 / 255.0)); + } + } + + /* Locked. */ + if ((strip.flags & GPU_SEQ_FLAG_LOCKED) != 0) { + if (co.y <= strip.strip_content_top) { + float phase = mod(gl_FragCoord.x + gl_FragCoord.y, 12.0); + if (phase >= 8.0) { + col = blend_color(col, vec4(0.0, 0.0, 0.0, 0.25)); + } + } + } + + /* Highlight. */ + if ((strip.flags & GPU_SEQ_FLAG_HIGHLIGHT) != 0) { + col = blend_color(col, vec4(1.0, 1.0, 1.0, 48.0 / 255.0)); + } + + /* Handles. */ + if ((strip.flags & GPU_SEQ_FLAG_HANDLES) != 0) { + if (co.x >= strip.left_handle && co.x < strip.left_handle + strip.handle_width) { + col = blend_color(col, unpackUnorm4x8(strip.col_handle_left)); + } + if (co.x > strip.right_handle - strip.handle_width && co.x <= strip.right_handle) { + col = blend_color(col, unpackUnorm4x8(strip.col_handle_right)); + } + } + } + + /* Outside of strip rounded rect? */ + if (sdf > 0.0) { + col = vec4(0.0); + } + + /* Outline. */ + if (!back_part) { + bool selected = (strip.flags & GPU_SEQ_FLAG_SELECTED) != 0; + vec4 col_outline = unpackUnorm4x8(strip.col_outline); + if (selected) { + /* Inset 1px line with backround color. */ + col = add_outline(sdf, 0.0, 1.0, col, unpackUnorm4x8(context_data.col_back)); + /* 2x wide outline. */ + col = add_outline(sdf, 0.5, -0.5, col, col_outline); + } + else { + col = add_outline(sdf, 0.0, 0.0, col, col_outline); + } + } + + fragColor = col; +} diff --git a/source/blender/gpu/shaders/gpu_shader_sequencer_strips_vert.glsl b/source/blender/gpu/shaders/gpu_shader_sequencer_strips_vert.glsl new file mode 100644 index 00000000000..767e17808df --- /dev/null +++ b/source/blender/gpu/shaders/gpu_shader_sequencer_strips_vert.glsl @@ -0,0 +1,34 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +void main() +{ + int id = gl_InstanceID; + strip_id = id; + int vid = gl_VertexID; + SeqStripDrawData strip = strip_data[id]; + vec4 rect = vec4(strip.left_handle, strip.bottom, strip.right_handle, strip.top); + /* Expand rasterized rectangle by 1px so that we can do outlines. */ + rect.x -= context_data.pixelx; + rect.z += context_data.pixelx; + rect.y -= context_data.pixely; + rect.w += context_data.pixely; + + vec2 co; + if (vid == 0) { + co = rect.xw; + } + else if (vid == 1) { + co = rect.xy; + } + else if (vid == 2) { + co = rect.zw; + } + else { + co = rect.zy; + } + + co_interp = co; + gl_Position = ModelViewProjectionMatrix * vec4(co, 0.0f, 1.0f); +} diff --git a/source/blender/gpu/shaders/infos/gpu_shader_sequencer_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_sequencer_info.hh new file mode 100644 index 00000000000..7204ef1095f --- /dev/null +++ b/source/blender/gpu/shaders/infos/gpu_shader_sequencer_info.hh @@ -0,0 +1,25 @@ +/* SPDX-FileCopyrightText: 2024 Blender Authors + * + * SPDX-License-Identifier: GPL-2.0-or-later */ + +/** \file + * \ingroup gpu + */ + +#include "gpu_interface_info.hh" +#include "gpu_shader_create_info.hh" + +GPU_SHADER_INTERFACE_INFO(gpu_seq_strip_iface, "") + .no_perspective(Type::VEC2, "co_interp") + .flat(Type::UINT, "strip_id"); + +GPU_SHADER_CREATE_INFO(gpu_shader_sequencer_strips) + .vertex_out(gpu_seq_strip_iface) + .fragment_out(0, Type::VEC4, "fragColor") + .push_constant(Type::MAT4, "ModelViewProjectionMatrix") + .uniform_buf(0, "SeqStripDrawData", "strip_data[GPU_SEQ_STRIP_DRAW_DATA_LEN]") + .uniform_buf(1, "SeqContextDrawData", "context_data") + .typedef_source("GPU_shader_shared.hh") + .vertex_source("gpu_shader_sequencer_strips_vert.glsl") + .fragment_source("gpu_shader_sequencer_strips_frag.glsl") + .do_static_compilation(true); diff --git a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl index 1508a7d59be..f605da8baa5 100644 --- a/source/blender/gpu/shaders/metal/mtl_shader_defines.msl +++ b/source/blender/gpu/shaders/metal/mtl_shader_defines.msl @@ -2091,6 +2091,11 @@ template int findMSB(T x) return int(sizeof(T) * 8) - 1 - int(clz(x)); } +#define unpackUnorm4x8 unpack_unorm4x8_to_float +#define unpackSnorm4x8 unpack_snorm4x8_to_float +#define unpackUnorm2x16 unpack_unorm2x16_to_float +#define unpackSnorm2x16 unpack_snorm2x16_to_float + /* Texture size functions. Add texture types as needed. */ #define imageSize(image) textureSize(image, 0)