VSE: Rounded corners for timeline strips

VSE timeline strips now have rounded corners. Strip corner rounding radius is
4, 6 or 8px depending on strip height (if strip is too narrow to fit
rounding, then rounding is turned off).

This is achieved with a dedicated GPU shader for drawing most of VSE
strip widget, that it could do proper rounded corner masking.

More details and images in the PR.

Pull Request: https://projects.blender.org/blender/blender/pulls/122576
This commit is contained in:
Aras Pranckevicius
2024-06-04 20:05:35 +02:00
committed by Aras Pranckevicius
parent a7171e0391
commit 7fdfa47f23
11 changed files with 726 additions and 414 deletions

View File

@@ -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 */

View File

@@ -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);
}

View File

@@ -10,6 +10,7 @@
#include <cmath>
#include <cstring>
#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<Sequence *> sequencer_visible_strips_get(const bContext *C)
Vector<Sequence *> sequencer_visible_strips_get(const bContext *C)
{
return sequencer_visible_strips_get(CTX_data_scene(C), UI_view2d_fromcontext(C));
}
blender::Vector<Sequence *> sequencer_visible_strips_get(const Scene *scene, const View2D *v2d)
Vector<Sequence *> sequencer_visible_strips_get(const Scene *scene, const View2D *v2d)
{
const Editing *ed = SEQ_editing_get(scene);
blender::Vector<Sequence *> strips;
Vector<Sequence *> 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<StripDrawContext> &strips)
const Vector<StripDrawContext> &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<StripDrawContext> &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<StripDrawContext> &r_unselected,
blender::Vector<StripDrawContext> &r_selected)
Vector<StripDrawContext> &r_unselected,
Vector<StripDrawContext> &r_selected)
{
Sequence *act_seq = SEQ_select_active_get(timeline_ctx->scene);
blender::Vector<Sequence *> strips = sequencer_visible_strips_get(timeline_ctx->C);
Vector<Sequence *> 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<SeqStripDrawData> 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<StripDrawContext> &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<StripDrawContext> &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<StripDrawContext> &strips)
SeqStripsBatch &strips_batch,
const Vector<StripDrawContext> &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<StripDrawContext> unselected, selected;
Vector<StripDrawContext> 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<CacheDrawData *>(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<Sequence *> strips = sequencer_visible_strips_get(C);
Vector<Sequence *> 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);

View File

@@ -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

View File

@@ -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,

View File

@@ -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,

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -2091,6 +2091,11 @@ template<typename T> 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)