[FFmpeg-devel] [PATCH 2/2] swscale: Neon rgb_to_yuv_half process 16 pixels at a time

Martin Storsjö martin at martin.st
Fri May 30 10:08:18 EEST 2025


On Thu, 29 May 2025, Martin Storsjö wrote:

> On Tue, 27 May 2025, Dmitriy Kovalenko wrote:
>
>> This patches integrates so called double bufferring when we are loading
>> 2 batch elements at a time and then processing them in parallel. On the
>> moden arm processors especially Apple Silicon it gives a visible
>> benefit, for subsampled pixel processing it is especially nice because
>> it allows to read elements w/ 2 instructions and write with a single one
>> (which is usually the slowest part).
>> 
>> Including the previous patch in a stack on macbook pro m4 max 
>> rgb_to_yuv_half
>> in checkasm goes up 2x of the c version
>> ---
>> libswscale/aarch64/input.S | 332 ++++++++++++++++++++++++++++++++++---
>> 1 file changed, 309 insertions(+), 23 deletions(-)
>> 
>> diff --git a/libswscale/aarch64/input.S b/libswscale/aarch64/input.S
>> index ee8eb24c14..59d66d0022 100644
>> --- a/libswscale/aarch64/input.S
>> +++ b/libswscale/aarch64/input.S
>> @@ -194,40 +194,94 @@ function ff_\fmt_rgb\()ToUV_half_neon, export=1
>>         ldp             w12, w13, [x6, #20]     // w12: bu, w13: rv
>>         ldp             w14, w15, [x6, #28]     // w14: gv, w15: bv
>> 4:
>> -        cmp             w5, #8
>>         rgb_set_uv_coeff half=1
>> -        b.lt            2f
>> -1:  // load 16 pixels and prefetch memory for the next block
>> +
>> +        cmp             w5, #16
>> +        b.lt            2f                      // Go directly to scalar 
>> if < 16
>> +
>> +1:
>>     .if \element == 3
>> -        ld3             { v16.16b, v17.16b, v18.16b }, [x3], #48
>> -        prfm            pldl1strm, [x3, #48]
>> +        ld3             { v16.16b, v17.16b, v18.16b }, [x3], #48  // First 
>> 16 pixels
>> +        ld3             { v26.16b, v27.16b, v28.16b }, [x3], #48  // 
>> Second 16 pixels
>> +        prfm            pldl1keep, [x3, #96]
>>     .else
>> -        ld4             { v16.16b, v17.16b, v18.16b, v19.16b }, [x3], #64
>> -        prfm            pldl1strm, [x3, #64]
>> +        ld4             { v16.16b, v17.16b, v18.16b, v19.16b }, [x3], #64 
>> // First 16 pixels
>> +        ld4             { v26.16b, v27.16b, v28.16b, v29.16b }, [x3], #64 
>> // Second 16 pixels
>> +        prfm            pldl1keep, [x3, #128]
>>     .endif
>> 
>> +    // **Sum adjacent pixel pairs**
>>     .if \alpha_first
>> -        uaddlp          v21.8h, v19.16b         // v21: summed b pairs
>> -        uaddlp          v20.8h, v18.16b         // v20: summed g pairs
>> -        uaddlp          v19.8h, v17.16b         // v19: summed r pairs
>> +        uaddlp          v21.8h, v19.16b         // Block 1: B sums
>> +        uaddlp          v20.8h, v18.16b         // Block 1: G sums
>> +        uaddlp          v19.8h, v17.16b         // Block 1: R sums
>> +        uaddlp          v31.8h, v29.16b         // Block 2: B sums
>> +        uaddlp          v30.8h, v28.16b         // Block 2: G sums
>> +        uaddlp          v29.8h, v27.16b         // Block 2: R sums
>>     .else
>> -        uaddlp          v19.8h, v16.16b         // v19: summed r pairs
>> -        uaddlp          v20.8h, v17.16b         // v20: summed g pairs
>> -        uaddlp          v21.8h, v18.16b         // v21: summed b pairs
>> +        uaddlp          v19.8h, v16.16b         // Block 1: R sums
>> +        uaddlp          v20.8h, v17.16b         // Block 1: G sums
>> +        uaddlp          v21.8h, v18.16b         // Block 1: B sums
>> +        uaddlp          v29.8h, v26.16b         // Block 2: R sums
>> +        uaddlp          v30.8h, v27.16b         // Block 2: G sums
>> +        uaddlp          v31.8h, v28.16b         // Block 2: B sums
>>     .endif
>> 
>> -        mov             v22.16b, v6.16b         // U first half
>> -        mov             v23.16b, v6.16b         // U second half
>> -        mov             v24.16b, v6.16b         // V first half
>> -        mov             v25.16b, v6.16b         // V second half
>> -
>> -        rgb_to_uv_interleaved_product v19, v20, v21, v0, v1, v2, v3, v4, 
>> v5, v22, v23, v24, v25, v16, v17, #10
>> +        // init accumulatos for both blocks
>> +        mov             v7.16b, v6.16b          //  U_low
>> +        mov             v8.16b, v6.16b          //  U_high
>> +        mov             v9.16b, v6.16b          //  V_low
>> +        mov             v10.16b, v6.16b         //  V_high
>> +        mov             v11.16b, v6.16b         //  U_low
>> +        mov             v12.16b, v6.16b         //  U_high
>> +        mov             v13.16b, v6.16b         //  V_low
>> +        mov             v14.16b, v6.16b         //  V_high
>> +
>> +        smlal           v7.4s, v0.4h, v19.4h    // U += ru * r (0-3)
>> +        smlal           v9.4s, v3.4h, v19.4h    // V += rv * r (0-3)
>> +        smlal           v11.4s, v0.4h, v29.4h   // U += ru * r (0-3)
>> +        smlal           v13.4s, v3.4h, v29.4h   // V += rv * r (0-3)
>> +
>> +        smlal2          v8.4s, v0.8h, v19.8h    // U += ru * r (4-7)
>> +        smlal2          v10.4s, v3.8h, v19.8h   // V += rv * r (4-7)
>> +        smlal2          v12.4s, v0.8h, v29.8h   // U += ru * r (4-7)
>> +        smlal2          v14.4s, v3.8h, v29.8h   // V += rv * r (4-7)
>> +
>> +        smlal           v7.4s, v1.4h, v20.4h    // U += gu * g (0-3)
>> +        smlal           v9.4s, v4.4h, v20.4h    // V += gv * g (0-3)
>> +        smlal           v11.4s, v1.4h, v30.4h   // U += gu * g (0-3)
>> +        smlal           v13.4s, v4.4h, v30.4h   // V += gv * g (0-3)
>> +
>> +        smlal2          v8.4s, v1.8h, v20.8h    // U += gu * g (4-7)
>> +        smlal2          v10.4s, v4.8h, v20.8h   // V += gv * g (4-7)
>> +        smlal2          v12.4s, v1.8h, v30.8h   // U += gu * g (4-7)
>> +        smlal2          v14.4s, v4.8h, v30.8h   // V += gv * g (4-7)
>> +
>> +        smlal           v7.4s, v2.4h, v21.4h    // U += bu * b (0-3)
>> +        smlal           v9.4s, v5.4h, v21.4h    // V += bv * b (0-3)
>> +        smlal           v11.4s, v2.4h, v31.4h   // U += bu * b (0-3)
>> +        smlal           v13.4s, v5.4h, v31.4h   // V += bv * b (0-3)
>> +
>> +        smlal2          v8.4s, v2.8h, v21.8h    // U += bu * b (4-7)
>> +        smlal2          v10.4s, v5.8h, v21.8h   // V += bv * b (4-7)
>> +        smlal2          v12.4s, v2.8h, v31.8h   // U += bu * b (4-7)
>> +        smlal2          v14.4s, v5.8h, v31.8h   // V += bv * b (4-7)
>> +
>> +        sqshrn          v16.4h, v7.4s, #10      // U (0-3)
>> +        sqshrn          v17.4h, v9.4s, #10      // V (0-3)
>> +        sqshrn          v22.4h, v11.4s, #10     // U (0-3)
>> +        sqshrn          v23.4h, v13.4s, #10     // V (0-3)
>> +
>> +        sqshrn2         v16.8h, v8.4s, #10      // U (0-7)
>> +        sqshrn2         v17.8h, v10.4s, #10     // V (0-7)
>> +        sqshrn2         v22.8h, v12.4s, #10     // U (0-7)
>> +        sqshrn2         v23.8h, v14.4s, #10     // V (0-7)
>> 
>> -        str             q16, [x0], #16          // store dst_u
>> -        str             q17, [x1], #16          // store dst_v
>> +        stp             q16, q22, [x0], #32     // Store all 16 U values
>> +        stp             q17, q23, [x1], #32     // Store all 16 V values
>> 
>> -        sub             w5, w5, #8              // width -= 8
>> -        cmp             w5, #8                  // width >= 8 ?
>> +        sub             w5, w5, #16             // width -= 16
>> +        cmp             w5, #16                 // width >= 16 ?
>>         b.ge            1b
>>         cbz             w5, 3f                  // No pixels left? Exit
>> 
>> @@ -459,3 +513,235 @@ endfunc
>> 
>> DISABLE_DOTPROD
>> #endif
>> +
>> +.macro rgbToUV_half_neon_double fmt_bgr, fmt_rgb, element, alpha_first=0
>> +function ff_\fmt_bgr\()ToUV_half_neon_double, export=1
>> +        cbz             w5, 9f                  // exit immediately if 
>> width is 0
>> +        cmp             w5, #16                 // check if we have at 
>> least 16 pixels
>> +        b.lt            _ff_\fmt_bgr\()ToUV_half_neon
>
> Also, with that fixed, this fails to properly back up and restore registers 
> v8-v15; checkasm doesn't notice this on macOS, but on Linux and windows, 
> checkasm has a call wrapper which does detect such issues.

This comment is still unaddressed, checkasm still fails on Linux and 
Windows.

// Martin


More information about the ffmpeg-devel mailing list