2024-06-04 20:05:35 +02:00
|
|
|
/* SPDX-FileCopyrightText: 2024 Blender Authors
|
|
|
|
|
*
|
|
|
|
|
* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
|
|
|
|
2025-09-25 10:57:02 +02:00
|
|
|
#include "infos/gpu_shader_sequencer_infos.hh"
|
2024-11-12 18:53:34 +01:00
|
|
|
|
2024-10-04 15:48:22 +02:00
|
|
|
#include "gpu_shader_sequencer_lib.glsl"
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-11-12 18:53:34 +01:00
|
|
|
FRAGMENT_SHADER_CREATE_INFO(gpu_shader_sequencer_strips)
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float3 color_shade(float3 rgb, float shade)
|
2024-06-04 20:05:35 +02:00
|
|
|
{
|
2025-04-14 13:46:41 +02:00
|
|
|
rgb += float3(shade / 255.0f);
|
|
|
|
|
rgb = clamp(rgb, float3(0.0f), float3(1.0f));
|
2024-06-04 20:05:35 +02:00
|
|
|
return rgb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Blends in a straight alpha `color` into premultiplied `cur` and returns premultiplied result. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float4 blend_color(float4 cur, float4 color)
|
2024-06-04 20:05:35 +02:00
|
|
|
{
|
|
|
|
|
float t = color.a;
|
2025-04-14 13:46:41 +02:00
|
|
|
return cur * (1.0f - t) + float4(color.rgb * t, t);
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Given signed distance `d` to a shape and current premultiplied color `cur`, blends
|
2024-06-19 11:19:15 +02:00
|
|
|
* in an outline at distance between `edge1` and `edge2`.
|
|
|
|
|
* Outline color `outline_color` is in straight alpha. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float4 add_outline(float d, float edge1, float edge2, float4 cur, float4 outline_color)
|
2024-06-04 20:05:35 +02:00
|
|
|
{
|
2025-04-11 18:28:45 +02:00
|
|
|
d -= 0.5f;
|
2024-06-19 11:19:15 +02:00
|
|
|
edge1 *= context_data.pixelsize;
|
|
|
|
|
edge2 *= context_data.pixelsize;
|
2025-04-11 18:28:45 +02:00
|
|
|
float f = abs(d + (edge1 + edge2) * 0.5f) - abs(edge2 - edge1) * 0.5f + 0.5f;
|
|
|
|
|
float a = clamp(1.0f - f, 0.0f, 1.0f);
|
2024-06-04 20:05:35 +02:00
|
|
|
outline_color.a *= a;
|
|
|
|
|
return blend_color(cur, outline_color);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void main()
|
|
|
|
|
{
|
2025-04-14 13:46:41 +02:00
|
|
|
float2 co = co_interp;
|
2024-06-04 20:05:35 +02:00
|
|
|
|
|
|
|
|
SeqStripDrawData strip = strip_data[strip_id];
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float2 pos1, pos2, size, center, pos;
|
2025-04-11 18:28:45 +02:00
|
|
|
float radius = 0.0f;
|
VSE: Faster timeline thumbnail drawing
VSE timeline, when many (hundreds/thousands) of thumbnails were visible, was
very slow to redraw. This PR makes them 3-10x faster to redraw, by stopping
doing things that are slow :) Part of #126087 thumbnail improvements task.
- No longer do mute semitransparency or corner rounding on the CPU, do it in
shader instead.
- Stop creating a separate GPU texture for each thumbnail, on every repaint,
and drawing each thumbnail as a separate draw call. Instead, put thumbnails
into a single texture atlas (using a simple shelf packing algorithm), and
draw them in batch, passing data via UBO. The atlas is still re-created every
frame, but that does not seem to be a performance issue. Thumbnails are
cropped horizontally based on how much of their parts are visible (e.g. a
narrow strip on screen), so realistically the atlas size is kinda
proportional to screen size, and ends up being just several megabytes of data
transfer between CPU -> GPU each frame.
On this Sprite Fright edit timeline view (612 visible thumbnails), time taken
to repaint the timeline window:
- Mac (M1 Max, Metal): 68.1ms -> 4.7ms
- Windows (Ryzen 5950X, RTX 3080Ti, OpenGL): 23.7ms -> 6.8ms
This also fixes a visual issue with thumbnails, where when strips are very
tall, the "rounded corners" that were poked right into the thumbnail bitmap
on the CPU were showing up due to actual bitmap being scaled up a lot.
Pull Request: https://projects.blender.org/blender/blender/pulls/126972
2024-09-03 08:25:15 +02:00
|
|
|
strip_box(strip.left_handle,
|
|
|
|
|
strip.right_handle,
|
|
|
|
|
strip.bottom,
|
|
|
|
|
strip.top,
|
|
|
|
|
co,
|
|
|
|
|
pos1,
|
|
|
|
|
pos2,
|
|
|
|
|
size,
|
|
|
|
|
center,
|
|
|
|
|
pos,
|
|
|
|
|
radius);
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-06-19 11:49:20 +02:00
|
|
|
bool border = (strip.flags & GPU_SEQ_FLAG_BORDER) != 0;
|
|
|
|
|
bool selected = (strip.flags & GPU_SEQ_FLAG_SELECTED) != 0;
|
2025-04-11 18:28:45 +02:00
|
|
|
float outline_width = selected ? 2.0f : 1.0f;
|
2024-06-19 11:49:20 +02:00
|
|
|
|
|
|
|
|
/* Distance to whole strip shape. */
|
2024-06-04 20:05:35 +02:00
|
|
|
float sdf = sdf_rounded_box(pos - center, size, radius);
|
|
|
|
|
|
2024-06-19 11:49:20 +02:00
|
|
|
/* Distance to inner part when handles are taken into account. */
|
|
|
|
|
float sdf_inner = sdf;
|
|
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_ANY_HANDLE) != 0) {
|
2024-07-22 16:08:07 +02:00
|
|
|
float handle_width = strip.handle_width;
|
2024-06-19 11:49:20 +02:00
|
|
|
/* Take left/right handle from horizontal sides. */
|
2025-06-11 04:03:17 +02:00
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_SELECTED_LH) != 0) {
|
2024-06-19 11:49:20 +02:00
|
|
|
pos1.x += handle_width;
|
|
|
|
|
}
|
2025-06-11 04:03:17 +02:00
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_SELECTED_RH) != 0) {
|
2024-06-19 11:49:20 +02:00
|
|
|
pos2.x -= handle_width;
|
|
|
|
|
}
|
|
|
|
|
/* Reduce vertical size by outline width. */
|
|
|
|
|
pos1.y += context_data.pixelsize * outline_width;
|
|
|
|
|
pos2.y -= context_data.pixelsize * outline_width;
|
|
|
|
|
|
2025-04-11 18:28:45 +02:00
|
|
|
size = (pos2 - pos1) * 0.5f;
|
|
|
|
|
center = (pos1 + pos2) * 0.5f;
|
2024-06-19 11:49:20 +02:00
|
|
|
sdf_inner = sdf_rounded_box(pos - center, size, radius);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-14 13:46:41 +02:00
|
|
|
float4 col = float4(0.0f);
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-06-11 11:55:49 +02:00
|
|
|
/* Background. */
|
|
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_BACKGROUND) != 0) {
|
2024-06-04 20:05:35 +02:00
|
|
|
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) {
|
2025-04-11 18:28:45 +02:00
|
|
|
col.rgb = color_shade(col.rgb, -35.0f);
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-11 11:55:49 +02:00
|
|
|
}
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-06-11 11:55:49 +02:00
|
|
|
/* 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. */
|
2025-04-11 18:28:45 +02:00
|
|
|
if (co.y > strip.strip_content_top - 1.0f) {
|
|
|
|
|
col.rgb = color_shade(col.rgb, -20.0f);
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-11 11:55:49 +02:00
|
|
|
}
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-06-11 11:55:49 +02:00
|
|
|
/* 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;
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-11 11:55:49 +02:00
|
|
|
|
|
|
|
|
/* Previous parts were all assigning color (not blending it),
|
|
|
|
|
* make sure from now on alpha is premultiplied. */
|
|
|
|
|
col.rgb *= col.a;
|
|
|
|
|
|
|
|
|
|
/* Missing media. */
|
|
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_MISSING_TITLE) != 0) {
|
|
|
|
|
if (co.y > strip.strip_content_top) {
|
2025-04-14 13:46:41 +02:00
|
|
|
col = blend_color(col, float4(112.0f / 255.0f, 0.0f, 0.0f, 230.0f / 255.0f));
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
2024-06-11 11:55:49 +02:00
|
|
|
}
|
|
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_MISSING_CONTENT) != 0) {
|
|
|
|
|
if (co.y <= strip.strip_content_top) {
|
2025-04-14 13:46:41 +02:00
|
|
|
col = blend_color(col, float4(64.0f / 255.0f, 0.0f, 0.0f, 230.0f / 255.0f));
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
2024-06-11 11:55:49 +02:00
|
|
|
}
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-06-11 11:55:49 +02:00
|
|
|
/* Locked. */
|
|
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_LOCKED) != 0) {
|
|
|
|
|
if (co.y <= strip.strip_content_top) {
|
2025-04-11 18:28:45 +02:00
|
|
|
float phase = mod(gl_FragCoord.x + gl_FragCoord.y, 12.0f);
|
|
|
|
|
if (phase >= 8.0f) {
|
2025-04-14 13:46:41 +02:00
|
|
|
col = blend_color(col, float4(0.0f, 0.0f, 0.0f, 0.25f));
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-11 11:55:49 +02:00
|
|
|
}
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-06-11 11:55:49 +02:00
|
|
|
/* Highlight. */
|
|
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_HIGHLIGHT) != 0) {
|
2025-04-14 13:46:41 +02:00
|
|
|
col = blend_color(col, float4(1.0f, 1.0f, 1.0f, 48.0f / 255.0f));
|
2024-06-11 11:55:49 +02:00
|
|
|
}
|
2024-06-04 20:05:35 +02:00
|
|
|
|
2024-06-11 11:55:49 +02:00
|
|
|
/* Handles. */
|
2025-04-14 13:46:41 +02:00
|
|
|
float4 col_outline = unpackUnorm4x8(strip.col_outline);
|
2024-06-19 11:49:20 +02:00
|
|
|
if ((strip.flags & GPU_SEQ_FLAG_ANY_HANDLE) != 0) {
|
|
|
|
|
bool left_side = pos.x < center.x;
|
|
|
|
|
uint handle_flag = left_side ? GPU_SEQ_FLAG_SELECTED_LH : GPU_SEQ_FLAG_SELECTED_RH;
|
|
|
|
|
bool selected_handle = (strip.flags & handle_flag) != 0;
|
|
|
|
|
/* Blend in handle color in between strip shape and inner handle shape. */
|
2025-04-11 18:28:45 +02:00
|
|
|
if (sdf <= 0.0f && sdf_inner >= 0.0f) {
|
2025-04-14 13:46:41 +02:00
|
|
|
float4 hcol = selected_handle ? col_outline : float4(0, 0, 0, 0.2f);
|
2025-04-11 18:28:45 +02:00
|
|
|
hcol.a *= clamp(sdf_inner, 0.0f, 1.0f);
|
2024-06-19 11:49:20 +02:00
|
|
|
col = blend_color(col, hcol);
|
2024-06-11 11:55:49 +02:00
|
|
|
}
|
2024-06-19 11:49:20 +02:00
|
|
|
/* For an unselected handle, no longer take it into account
|
|
|
|
|
* for the "inner" distance. */
|
|
|
|
|
if (!selected_handle) {
|
|
|
|
|
sdf_inner = sdf;
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-06 09:43:48 +10:00
|
|
|
/* Outside of strip rounded rectangle? */
|
2025-04-11 18:28:45 +02:00
|
|
|
if (sdf > 0.0f) {
|
2025-04-14 13:46:41 +02:00
|
|
|
col = float4(0.0f);
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-11 11:55:49 +02:00
|
|
|
/* Outline / border. */
|
2024-06-19 11:49:20 +02:00
|
|
|
if (border) {
|
2024-06-20 13:01:26 +02:00
|
|
|
|
|
|
|
|
if (selected) {
|
|
|
|
|
/* Selection highlight + darker inset line. */
|
2025-04-11 18:28:45 +02:00
|
|
|
col = add_outline(sdf, 1.0f, 3.0f, col, col_outline);
|
2024-06-20 13:01:26 +02:00
|
|
|
/* Inset line should be inside regular border or inside the handles. */
|
2025-04-11 18:28:45 +02:00
|
|
|
float d = max(sdf_inner - 3.0f * context_data.pixelsize, sdf);
|
2025-04-14 13:46:41 +02:00
|
|
|
col = add_outline(d, 3.0f, 4.0f, col, float4(0, 0, 0, 0.33f));
|
2024-06-20 13:01:26 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-05 11:35:18 +02:00
|
|
|
/* Active, but not selected strips get a thin inner line. */
|
2024-07-05 17:22:52 +02:00
|
|
|
bool active_strip = (strip.flags & GPU_SEQ_FLAG_ACTIVE) != 0;
|
2024-07-11 07:38:02 +02:00
|
|
|
if (active_strip && !selected) {
|
2025-04-11 18:28:45 +02:00
|
|
|
col = add_outline(sdf, 1.0f, 2.0f, col, col_outline);
|
2024-07-05 11:35:18 +02:00
|
|
|
}
|
|
|
|
|
|
2024-07-11 07:38:02 +02:00
|
|
|
/* 2px outline for all overlapping strips. */
|
|
|
|
|
bool overlaps = (strip.flags & GPU_SEQ_FLAG_OVERLAP) != 0;
|
VSE: Clamp strip handles to video/audio bounds
This initial commit properly clamps handles for video/audio strips, and
provides functionality to enable/disable the behavior for all strip types
(addresses #90280).
Toggling handle clamping is done with "C",
just like with the redesigned slip operator (#137072).
If a strip is not already clamped when you start moving its handles,
then clamping behavior is disabled starting out. This means no abrupt
clamp until you explicitly ask for it.
Transform logic was altered, fixing a few bugs:
- When initializing a transform, `createTransSeqData` would already
create some clamping data for channels. This patch replaces it with
`offset_clamp` (for unconditional clamping which cannot be disabled)
and `handle_xmin/xmax` (for hold offset clamping, which is optional).
- Collecting this data ahead of time is necessary for the double
handle tweak case -- `flushTransSeq` only works one strip at a
time, so we can't clamp post-hoc.
- In `applySeqSlideValue`, we apply `transform_convert_sequencer_clamp`
before values are printed to the header, but let the unclamped values
get flushed to the strips themselves. This is so that we can have the
data later at the individual strip level to recalculate clamps.
Otherwise, if transform values are clamped preemptively, then we have
no idea whether strips are clamped vs. merely resting at their
boundaries.
Note that currently, handle clamping is drawn identically to overlaps.
More information in PR.
Pull Request: https://projects.blender.org/blender/blender/pulls/134319
2025-07-16 06:16:19 +02:00
|
|
|
bool clamped = (strip.flags & GPU_SEQ_FLAG_CLAMPED) != 0;
|
|
|
|
|
if (overlaps || clamped) {
|
2025-04-11 18:28:45 +02:00
|
|
|
col = add_outline(sdf, 1.0f, 3.0f, col, col_outline);
|
2024-07-11 07:38:02 +02:00
|
|
|
}
|
|
|
|
|
|
2024-06-20 13:01:26 +02:00
|
|
|
/* Outer 1px outline for all strips. */
|
2025-04-11 18:28:45 +02:00
|
|
|
col = add_outline(sdf, 0.0f, 1.0f, col, unpackUnorm4x8(context_data.col_back));
|
2024-06-04 20:05:35 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fragColor = col;
|
|
|
|
|
}
|