PR #25293 opened by huuufu
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25293
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25293.patch
# Summary of changes
`ff_blend_mask()` blends every sample through the generic `blend_pixel()` (bit
extraction for masks of any depth, subsampling average, all parameterized at
run time). For the most common use, libass bitmaps burnt into planar 8-bit
frames by the `subtitles` / `ass` filters, this dominates the filter.
This adds a row-wise path for 8-bit masks on planar 8-bit formats with at most
2x2 subsampling; everything else, including the partial samples at the edges of
subsampled planes, still takes the generic code. The arithmetic is unchanged,
so the output is bit-identical.
**Speed** (time spent in the ass filter per frame, 1080p yuv420p, 80 scrolling
lines of subtitles plus a few static ones, single thread, i7-13700K, gcc 16.2,
MSYS2 UCRT64):
| | ms / frame |
|---|---|
| before | 14.6 |
| after | 3.7 |
```
ffmpeg -f lavfi -i color=gray:s=1920x1080:r=25:d=20 -threads 1 -filter_threads
1 \
-vf format=yuv420p,ass=dense.ass -benchmark -f null -
```
(minus the same command without `ass=`, best of 3)
**Testing**
- `make fate` (without samples) passes.
- framemd5 of 50 frames identical before/after for `ass`, `drawtext` and
`datascope` × 15 pixel formats (yuv420p, yuvj420p, yuv422p, yuv444p, yuv440p,
yuv410p, yuv411p, nv12, nv21, gray, gbrp, rgb24, bgra, yuva420p, yuv420p10le,
i.e. both the new and the generic path) × 1280x720 and 853x479. The subtitles
sit at odd positions, move in from outside the frame (clipped edges) and use
blur, borders, alpha, rotation and `\clip`.
From e3ace82a53a83af5384f3e3eefc744d89f677a15 Mon Sep 17 00:00:00 2001
From: huuufu <[email protected]>
Date: Wed, 21 Oct 2026 17:51:04 +0900
Subject: [PATCH] avfilter/drawutils: add a fast path for 8-bit masks on planar
formats
ff_blend_mask() goes through blend_pixel() for every sample: two nested
loops, the generic bit extraction for masks of any depth and the
subsampling average, all parameterized at run time. For the by far most
common use, libass bitmaps (8-bit masks) burnt into planar 8-bit frames
by the subtitles and ass filters, the blending takes about eight times
as long as the libass rendering itself.
Add a path for that case (8-bit samples one byte apart, l2depth == 3,
subsampling at most 2x2) that blends a whole row with tight loops the
compiler can vectorize. The partial samples at the edges of subsampled
planes and all other cases still use the generic code. The arithmetic
is the same, so the output is bit-identical: the framemd5 of the ass,
drawtext and datascope filters over 15 pixel formats (covering both
paths) at 1280x720 and 853x479 is unchanged, and make fate passes.
Time spent in the ass filter per frame, 1080p yuv420p with 80 scrolling
lines of subtitles, single thread, i7-13700K, gcc 16.2:
before: 14.6 ms
after: 3.7 ms
Signed-off-by: huuufu <[email protected]>
---
libavfilter/drawutils.c | 70 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 69 insertions(+), 1 deletion(-)
diff --git a/libavfilter/drawutils.c b/libavfilter/drawutils.c
index b1ac440b061..20a6ef7d4af 100644
--- a/libavfilter/drawutils.c
+++ b/libavfilter/drawutils.c
@@ -571,6 +571,64 @@ static void blend_line_hv(uint8_t *dst, int dst_delta,
right, hband, hsub + vsub, xm);
}
+/* Fast path for the most common case of ff_blend_mask(): 8-bit samples one
+ * byte apart (planar formats), an 8-bit mask (l2depth == 3) and at most 2x2
+ * subsampling. A whole row is blended with tight loops that compilers can
+ * vectorize; the arithmetic is exactly that of blend_pixel(), so the result
+ * is bit-identical to the generic code. */
+#define BLEND8(dst, src, a) (((0x1010101 - (a)) * (dst) + (a) * (src)) >> 24)
+
+static void blend_row8(uint8_t *dst, unsigned src, unsigned alpha,
+ const uint8_t *mask, int mask_linesize, int w,
+ unsigned hsub, unsigned vsub)
+{
+ const uint8_t *m0 = mask, *m1 = mask + mask_linesize;
+
+ if (!hsub && !vsub) {
+ for (int x = 0; x < w; x++) {
+ unsigned a = m0[x] * alpha;
+ dst[x] = BLEND8(dst[x], src, a);
+ }
+ } else if (hsub && vsub) {
+ for (int x = 0; x < w; x++) {
+ unsigned a = ((m0[2 * x] + m0[2 * x + 1] + m1[2 * x] + m1[2 * x +
1]) >> 2) * alpha;
+ dst[x] = BLEND8(dst[x], src, a);
+ }
+ } else if (hsub) {
+ for (int x = 0; x < w; x++) {
+ unsigned a = ((m0[2 * x] + m0[2 * x + 1]) >> 1) * alpha;
+ dst[x] = BLEND8(dst[x], src, a);
+ }
+ } else {
+ for (int x = 0; x < w; x++) {
+ unsigned a = ((m0[x] + m1[x]) >> 1) * alpha;
+ dst[x] = BLEND8(dst[x], src, a);
+ }
+ }
+}
+
+/* One row of the interior of the mask: the partial samples at the left and
+ * right edge (subsampling blocks cut by the clipping) still go through
+ * blend_pixel(), the rest through blend_row8(). */
+static void blend_line_hv8(uint8_t *dst, unsigned src, unsigned alpha,
+ const uint8_t *mask, int mask_linesize, int w,
+ unsigned hsub, unsigned vsub,
+ int xm, int left, int right)
+{
+ if (left) {
+ blend_pixel(dst, src, alpha, mask, mask_linesize, 3,
+ left, 1 << vsub, hsub + vsub, xm);
+ dst++;
+ xm += left;
+ }
+ blend_row8(dst, src, alpha, mask + xm, mask_linesize, w, hsub, vsub);
+ dst += w;
+ xm += w << hsub;
+ if (right)
+ blend_pixel(dst, src, alpha, mask, mask_linesize, 3,
+ right, 1 << vsub, hsub + vsub, xm);
+}
+
void ff_blend_mask(FFDrawContext *draw, FFDrawColor *color,
uint8_t *dst[], int dst_linesize[], int dst_w, int dst_h,
const uint8_t *mask, int mask_linesize, int mask_w, int
mask_h,
@@ -633,7 +691,17 @@ void ff_blend_mask(FFDrawContext *draw, FFDrawColor *color,
p += dst_linesize[plane];
m += top * mask_linesize;
}
- if (depth <= 8) {
+ if (depth <= 8 && l2depth == 3 && draw->pixelstep[plane] == 1 &&
+ draw->hsub[plane] <= 1 && draw->vsub[plane] <= 1) {
+ for (int y = 0; y < h_sub; y++) {
+ blend_line_hv8(p, over.comp[plane].u8[index], alpha,
+ m, mask_linesize, w_sub,
+ draw->hsub[plane], draw->vsub[plane],
+ xm0, left, right);
+ p += dst_linesize[plane];
+ m += mask_linesize << draw->vsub[plane];
+ }
+ } else if (depth <= 8) {
for (int y = 0; y < h_sub; y++) {
blend_line_hv(p, draw->pixelstep[plane],
over.comp[plane].u8[index], alpha,
--
2.54.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]