@@ -352,11 +352,28 @@ template void convert_planarrgb_to_yuv_uint16_avx2<12>(BYTE* (&dstp)[3], int(&ds
352352template void convert_planarrgb_to_yuv_uint16_avx2<14 >(BYTE * (&dstp)[3 ], int (&dstPitch)[3 ], const BYTE * (&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix& m);
353353template void convert_planarrgb_to_yuv_uint16_avx2<16 >(BYTE * (&dstp)[3 ], int (&dstPitch)[3 ], const BYTE * (&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix& m);
354354
355- template <typename pixel_t , int bits_per_pixel >
356- void convert_yuv_to_planarrgb_uint8_14_avx2 (BYTE * (&dstp)[3], int(&dstPitch)[3], const BYTE* (&srcp)[3], const int(&srcPitch)[3], int width, int height, const ConversionMatrix& m)
355+ template <typename pixel_t , bool lessthan16bit >
356+ void convert_yuv_to_planarrgb_uintN_avx2 (BYTE * (&dstp)[3], int(&dstPitch)[3], const BYTE* (&srcp)[3], const int(&srcPitch)[3], int width, int height, const ConversionMatrix& m, const int bits_per_pixel )
357357{
358358 // 8 bit uint8_t
359359 // 10,12,14 bit uint16_t (signed range)
360+ // 16 bit logic:
361+ /*
362+ 1. pivot the pixel:
363+ Convert uint16 pixel Y to a signed int16 by flipping the MSB (this is mathematically identical to Y−32768).
364+ 0 becomes −32768.
365+ 65535 becomes 32767.
366+ All values now fit in int16 without saturation.
367+ 2. reorganize the formula (offset is negative when exists, e.g. -4096):
368+ Original: (Y + offset_y) * Cy
369+ Substitute
370+ Y = (Ysigned + 32768)
371+ => (Ysigned + 32768 + offset_y) * Cy
372+ => (Ysigned * Cy) + (32768 + offset_y) * Cy)
373+ 3. correction after the madd section:
374+ Add ((32768 + offset_y) * Cy) to the existing 32-bit rounding/offset constant.
375+ 4. Output rgb offset is also added to the precalculated patch.
376+ */
360377 const __m256i half = _mm256_set1_epi16 ((short )(1 << (bits_per_pixel - 1 ))); // 128
361378 const __m256i limit = _mm256_set1_epi16 ((short )((1 << bits_per_pixel) - 1 )); // 255
362379 const __m256i offset = _mm256_set1_epi16 ((short )(m.offset_y ));
@@ -366,9 +383,34 @@ void convert_yuv_to_planarrgb_uint8_14_avx2(BYTE* (&dstp)[3], int(&dstPitch)[3],
366383 // original : 1 * 4096
367384 // needed : 1 * (4096 + offset_rgb<<13) (4096 + 131072 overflows i16)
368385 // changed to : 4096 * (1 + offset_rgb>>(13-1)
369- const int round_scale = 4096 ; // 1 << 12
370- const int round_mask_plus_rgb_offset_scaled_i = (4096 + (m.offset_rgb << 13 )) / round_scale;
371- const __m256i m256i_round_scale = _mm256_set1_epi16 (round_scale);
386+ // Except for exact 16 bit, where we do the output offset adjustment along with the 16 bit signed-unsigned pivot fix
387+ constexpr int ROUND_SCALE = 4096 ; // 1 << 12, for 13 bit integer arithmetic: "0.5"
388+ const __m256i m256i_round_scale = _mm256_set1_epi16 (ROUND_SCALE );
389+
390+ int round_mask_plus_rgb_offset_scaled_i;
391+ __m256i v_patch_G, v_patch_B, v_patch_R;
392+ __m256i sign_flip_mask = _mm256_set1_epi16 ((short )0x8000 ); // for 16 bit pivot
393+
394+ if constexpr (lessthan16bit) {
395+ // 8-14 bit
396+ round_mask_plus_rgb_offset_scaled_i = (4096 + (m.offset_rgb << 13 )) / ROUND_SCALE ;
397+ v_patch_G = v_patch_B = v_patch_R = _mm256_setzero_si256 (); // No patch needed
398+ }
399+ else {
400+ // exact 16 bit
401+ // keep madd simple: only handle the rounding (ROUND_SCALE * 1)
402+ round_mask_plus_rgb_offset_scaled_i = 1 ; // effectively 1
403+
404+ // move BOTH the pivot and the output RGB offset to the 32-bit patch
405+ // Since we have to do the patching anyway, we can combine both adjustments here
406+ const int luma_pivot = 32768 + m.offset_y ;
407+ const int rgb_out_offset = (m.offset_rgb << 13 );
408+
409+ // total patch = (pivot * coeff) + output RGB offset
410+ v_patch_G = _mm256_set1_epi32 (luma_pivot * m.y_g + rgb_out_offset);
411+ v_patch_B = _mm256_set1_epi32 (luma_pivot * m.y_b + rgb_out_offset);
412+ v_patch_R = _mm256_set1_epi32 (luma_pivot * m.y_r + rgb_out_offset);
413+ }
372414
373415 __m256i zero = _mm256_setzero_si256 ();
374416
@@ -402,7 +444,7 @@ void convert_yuv_to_planarrgb_uint8_14_avx2(BYTE* (&dstp)[3], int(&dstPitch)[3],
402444 v_2 = _mm256_unpackhi_epi8 (v_32, zero);
403445
404446 }
405- else { // uint16_t pixels, 14 bits OK, but 16 bit pixels are unsigned, cannot madd
447+ else { // uint16_t pixels, 10- 16 bits
406448 y = _mm256_load_si256 (reinterpret_cast <const __m256i*>(srcp[0 ] + x));
407449 u = _mm256_load_si256 (reinterpret_cast <const __m256i*>(srcp[1 ] + x));
408450 v = _mm256_load_si256 (reinterpret_cast <const __m256i*>(srcp[2 ] + x));
@@ -411,19 +453,34 @@ void convert_yuv_to_planarrgb_uint8_14_avx2(BYTE* (&dstp)[3], int(&dstPitch)[3],
411453 v_2 = _mm256_load_si256 (reinterpret_cast <const __m256i*>(srcp[2 ] + x + 32 ));
412454 }
413455
414- y = _mm256_adds_epi16 (y, offset); // offset is negative
415- u = _mm256_subs_epi16 (u, half);
416- v = _mm256_subs_epi16 (v, half);
417-
418- y_2 = _mm256_adds_epi16 (y_2, offset); // offset is negative
419- u_2 = _mm256_subs_epi16 (u_2, half);
420- v_2 = _mm256_subs_epi16 (v_2, half);
456+ // exact 16 bit workaround
457+ if constexpr (lessthan16bit) {
458+ y = _mm256_adds_epi16 (y, offset); // offset is negative
459+ u = _mm256_subs_epi16 (u, half); // subs, though integer formats compulsory have clamped values for their bitness range
460+ v = _mm256_subs_epi16 (v, half);
421461
462+ y_2 = _mm256_adds_epi16 (y_2, offset); // offset is negative
463+ u_2 = _mm256_subs_epi16 (u_2, half);
464+ v_2 = _mm256_subs_epi16 (v_2, half);
465+ }
466+ else {
467+ // pivot the luma, adjust the offset separately
468+ // make unsigned to signed by flipping MSB
469+ y = _mm256_xor_si256 (y, sign_flip_mask); // flip MSB; y - 32768
470+ u = _mm256_sub_epi16 (u, half); // no saturation here
471+ v = _mm256_sub_epi16 (v, half);
472+
473+ y_2 = _mm256_xor_si256 (y_2, sign_flip_mask); // flip MSB; y - 32768
474+ u_2 = _mm256_sub_epi16 (u_2, half);
475+ v_2 = _mm256_sub_epi16 (v_2, half);
476+ }
422477 /*
478+ // for 8-14 bits Y is already offset adjusted: Y = Y_orig + offset (offset is negative)
423479 // int b = (((int64_t)matrix.y_b * Y + (int64_t)matrix.u_b * U + (int64_t)matrix.v_b * V + 4096)>>13);
424480 // int g = (((int64_t)matrix.y_g * Y + (int64_t)matrix.u_g * U + (int64_t)matrix.v_g * V + 4096)>>13);
425481 // int r = (((int64_t)matrix.y_r * Y + (int64_t)matrix.u_r * U + (int64_t)matrix.v_r * V + 4096)>>13);
426482 */
483+ // for 16 bit, the rgb_offset is merged into the patch adjustment
427484 // Need1: (m.y_b m.u_b ) (m.y_b m.u_b) (m.y_b m.u_b) (m.y_b m.u_b) 8x16 bit
428485 // ( y3 u3 ) ( y2 u2 ) ( y1 u1 ) ( y0 u0 ) 8x16 bit
429486 // res1= (y_b*y3 + u_b*u3) ... 4x32 bit
@@ -450,75 +507,133 @@ void convert_yuv_to_planarrgb_uint8_14_avx2(BYTE* (&dstp)[3], int(&dstPitch)[3],
450507 // res2_lo = _mm256_madd_epi16(m_vR_G, xv0123);
451508 // __m256i g_lo = _mm256_srai_epi32(_mm256_add_epi32(res1, res2), 13);
452509 __m256i g_sum_lo = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_G, uy0123), _mm256_madd_epi16 (m_vR_G, xv0123));
510+ if constexpr (!lessthan16bit) {
511+ // 16-bit adjustment (signed patch, offset, output rgb offset)
512+ g_sum_lo = _mm256_add_epi32 (g_sum_lo, v_patch_G);
513+ }
453514 __m256i g_lo = _mm256_srai_epi32 (g_sum_lo, 13 );
454515 __m256i g_sum_lo_2 = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_G, uy0123_2), _mm256_madd_epi16 (m_vR_G, xv0123_2));
516+ if constexpr (!lessthan16bit) {
517+ // 16-bit adjustment (signed patch, offset, output rgb offset)
518+ g_sum_lo_2 = _mm256_add_epi32 (g_sum_lo_2, v_patch_G);
519+ }
455520 __m256i g_lo_2 = _mm256_srai_epi32 (g_sum_lo_2, 13 );
456521
457522 __m256i g_sum_hi = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_G, uy4567), _mm256_madd_epi16 (m_vR_G, xv4567));
523+ if constexpr (!lessthan16bit) {
524+ // 16-bit adjustment (signed patch, offset, output rgb offset)
525+ g_sum_hi = _mm256_add_epi32 (g_sum_hi, v_patch_G);
526+ }
458527 __m256i g_hi = _mm256_srai_epi32 (g_sum_hi, 13 );
459528 __m256i g_sum_hi_2 = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_G, uy4567_2), _mm256_madd_epi16 (m_vR_G, xv4567_2));
529+ if constexpr (!lessthan16bit) {
530+ // 16-bit adjustment (signed patch, offset, output rgb offset)
531+ g_sum_hi_2 = _mm256_add_epi32 (g_sum_hi_2, v_patch_G);
532+ }
460533 __m256i g_hi_2 = _mm256_srai_epi32 (g_sum_hi_2, 13 );
461534
462- __m256i g = _mm256_packs_epi32 (g_lo, g_hi); // 2x4x32 -> 2x4xuint16_t
463- __m256i g_2 = _mm256_packs_epi32 (g_lo_2, g_hi_2); // 2x4x32 -> 2x4xuint16_t
535+ // unlike SSE2, AVX2 has packus_epi32
536+ __m256i g_1 = _mm256_packus_epi32 (g_lo, g_hi); // 2x8x32 -> 16xuint16_t // 32-bit signed -> 16-bit signed
537+ __m256i g_2 = _mm256_packus_epi32 (g_lo_2, g_hi_2); // 2x8x32 -> 16xuint16_t // 32-bit signed -> 16-bit signed
464538 if constexpr (sizeof (pixel_t ) == 1 ) {
465- g = _mm256_packus_epi16 (g , g_2); // 32x uint16_t -> 32x uint_8
539+ __m256i g = _mm256_packus_epi16 (g_1 , g_2); // // 2x16x uint16_t -> 32x uint_8 // 16-bit signed -> 8-bit unsigned (clamping <0 to 0)
466540 _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[0 ] + x), g);
467541 }
468542 else {
469- g = _mm256_max_epi16 (_mm256_min_epi16 (g, limit), zero); // clamp 10,12,14 bit
470- g_2 = _mm256_max_epi16 (_mm256_min_epi16 (g_2, limit), zero); // clamp 10,12,14 bit
471- _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[0 ] + x), g);
543+ if constexpr (lessthan16bit) {
544+ // packus already clamps negative to zero, so only clamp upper limit
545+ g_1 = _mm256_min_epi16 (g_1, limit); // clamp 10,12,14 bit
546+ g_2 = _mm256_min_epi16 (g_2, limit); // clamp 10,12,14 bit
547+ }
548+ _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[0 ] + x), g_1);
472549 _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[0 ] + x + 32 ), g_2);
473550 }
474551
475552 // *B* ----------------
476553 __m256i b_sum_lo = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_B, uy0123), _mm256_madd_epi16 (m_vR_B, xv0123));
554+ if constexpr (!lessthan16bit) {
555+ // 16-bit adjustment (signed patch, offset, output rgb offset)
556+ b_sum_lo = _mm256_add_epi32 (b_sum_lo, v_patch_B);
557+ }
477558 __m256i b_lo = _mm256_srai_epi32 (b_sum_lo, 13 );
478559 __m256i b_sum_lo_2 = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_B, uy0123_2), _mm256_madd_epi16 (m_vR_B, xv0123_2));
560+ if constexpr (!lessthan16bit) {
561+ // 16-bit adjustment (signed patch, offset, output rgb offset)
562+ b_sum_lo_2 = _mm256_add_epi32 (b_sum_lo_2, v_patch_B);
563+ }
479564 __m256i b_lo_2 = _mm256_srai_epi32 (b_sum_lo_2, 13 );
480565
481566 __m256i b_sum_hi = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_B, uy4567), _mm256_madd_epi16 (m_vR_B, xv4567));
567+ if constexpr (!lessthan16bit) {
568+ // 16-bit adjustment (signed patch, offset, output rgb offset)
569+ b_sum_hi = _mm256_add_epi32 (b_sum_hi, v_patch_B);
570+ }
482571 __m256i b_hi = _mm256_srai_epi32 (b_sum_hi, 13 );
483572 __m256i b_sum_hi_2 = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_B, uy4567_2), _mm256_madd_epi16 (m_vR_B, xv4567_2));
573+ if constexpr (!lessthan16bit) {
574+ // 16-bit adjustment (signed patch, offset, output rgb offset)
575+ b_sum_hi_2 = _mm256_add_epi32 (b_sum_hi_2, v_patch_B);
576+ }
484577 __m256i b_hi_2 = _mm256_srai_epi32 (b_sum_hi_2, 13 );
485578
486- __m256i b = _mm256_packs_epi32 (b_lo, b_hi); // 2x4x32 -> 2x4xuint16_t
487- __m256i b_2 = _mm256_packs_epi32 (b_lo_2, b_hi_2 ); // 2x4x32 -> 2x4xuint16_t
488-
579+ // unlike SSE2, AVX2 has packus_epi32
580+ __m256i b_1 = _mm256_packus_epi32 (b_lo, b_hi ); // 2x8x32 -> 16xuint16_t // 32-bit signed -> 16-bit signed
581+ __m256i b_2 = _mm256_packus_epi32 (b_lo_2, b_hi_2); // 2x8x32 -> 16xuint16_t // 32-bit signed -> 16-bit signed
489582 if constexpr (sizeof (pixel_t ) == 1 ) {
490- b = _mm256_packus_epi16 (b , b_2); // 32x uint16_t -> 32x uint_8
583+ __m256i b = _mm256_packus_epi16 (b_1 , b_2); // // 2x16x uint16_t -> 32x uint_8 // 16-bit signed -> 8-bit unsigned (clamping <0 to 0)
491584 _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[1 ] + x), b);
492585 }
493586 else {
494- b = _mm256_max_epi16 (_mm256_min_epi16 (b, limit), zero); // clamp 10,12,14 bit
495- b_2 = _mm256_max_epi16 (_mm256_min_epi16 (b_2, limit), zero); // clamp 10,12,14 bit
496- _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[1 ] + x), b);
587+ if constexpr (lessthan16bit) {
588+ // packus already clamps negative to zero, so only clamp upper limit
589+ b_1 = _mm256_min_epi16 (b_1, limit); // clamp 10,12,14 bit
590+ b_2 = _mm256_min_epi16 (b_2, limit); // clamp 10,12,14 bit
591+ }
592+ _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[1 ] + x), b_1);
497593 _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[1 ] + x + 32 ), b_2);
498594 }
499595
500596 // *R* ----------------
501597 __m256i r_sum_lo = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_R, uy0123), _mm256_madd_epi16 (m_vR_R, xv0123));
598+ if constexpr (!lessthan16bit) {
599+ // 16-bit adjustment (signed patch, offset, output rgb offset)
600+ r_sum_lo = _mm256_add_epi32 (r_sum_lo, v_patch_R);
601+ }
502602 __m256i r_lo = _mm256_srai_epi32 (r_sum_lo, 13 );
503603 __m256i r_sum_lo_2 = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_R, uy0123_2), _mm256_madd_epi16 (m_vR_R, xv0123_2));
604+ if constexpr (!lessthan16bit) {
605+ // 16-bit adjustment (signed patch, offset, output rgb offset)
606+ r_sum_lo_2 = _mm256_add_epi32 (r_sum_lo_2, v_patch_R);
607+ }
504608 __m256i r_lo_2 = _mm256_srai_epi32 (r_sum_lo_2, 13 );
505609
506610 __m256i r_sum_hi = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_R, uy4567), _mm256_madd_epi16 (m_vR_R, xv4567));
611+ if constexpr (!lessthan16bit) {
612+ // 16-bit adjustment (signed patch, offset, output rgb offset)
613+ r_sum_hi = _mm256_add_epi32 (r_sum_hi, v_patch_R);
614+ }
507615 __m256i r_hi = _mm256_srai_epi32 (r_sum_hi, 13 );
508616 __m256i r_sum_hi_2 = _mm256_add_epi32 (_mm256_madd_epi16 (m_uy_R, uy4567_2), _mm256_madd_epi16 (m_vR_R, xv4567_2));
617+ if constexpr (!lessthan16bit) {
618+ // 16-bit adjustment (signed patch, offset, output rgb offset)
619+ r_sum_hi_2 = _mm256_add_epi32 (r_sum_hi_2, v_patch_R);
620+ }
509621 __m256i r_hi_2 = _mm256_srai_epi32 (r_sum_hi_2, 13 );
510622
511- __m256i r = _mm256_packs_epi32 (r_lo, r_hi); // 2x4x32 -> 2x4xuint16_t
512- __m256i r_2 = _mm256_packs_epi32 (r_lo_2, r_hi_2 ); // 2x4x32 -> 2x4xuint16_t
513-
623+ // unlike SSE2, AVX2 has packus_epi32
624+ __m256i r_1 = _mm256_packus_epi32 (r_lo, r_hi ); // 2x8x32 -> 16xuint16_t // 32-bit signed -> 16-bit signed
625+ __m256i r_2 = _mm256_packus_epi32 (r_lo_2, r_hi_2); // 2x8x32 -> 16xuint16_t // 32-bit signed -> 16-bit signed
514626 if constexpr (sizeof (pixel_t ) == 1 ) {
515- r = _mm256_packus_epi16 (r , r_2); // 32x uint16_t -> 32x uint_8
627+ __m256i r = _mm256_packus_epi16 (r_1 , r_2); // // 2x16x uint16_t -> 32x uint_8 // 16-bit signed -> 8-bit unsigned (clamping <0 to 0)
516628 _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[2 ] + x), r);
517629 }
518630 else {
519- r = _mm256_max_epi16 (_mm256_min_epi16 (r, limit), zero); // clamp 10,12,14 bit
520- r_2 = _mm256_max_epi16 (_mm256_min_epi16 (r_2, limit), zero); // clamp 10,12,14 bit
521- _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[2 ] + x), r);
631+ if constexpr (lessthan16bit) {
632+ // packus already clamps negative to zero, so only clamp upper limit
633+ r_1 = _mm256_min_epi16 (r_1, limit); // clamp 10,12,14 bit
634+ r_2 = _mm256_min_epi16 (r_2, limit); // clamp 10,12,14 bit
635+ }
636+ _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[2 ] + x), r_1);
522637 _mm256_store_si256 (reinterpret_cast <__m256i*>(dstp[2 ] + x + 32 ), r_2);
523638 }
524639
@@ -533,11 +648,10 @@ void convert_yuv_to_planarrgb_uint8_14_avx2(BYTE* (&dstp)[3], int(&dstPitch)[3],
533648}
534649
535650// instantiate
536- // template<typename pixel_t, int bits_per_pixel>
537- template void convert_yuv_to_planarrgb_uint8_14_avx2<uint8_t , 8 >(BYTE *(&dstp)[3 ], int (&dstPitch)[3 ], const BYTE *(&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix &m);
538- template void convert_yuv_to_planarrgb_uint8_14_avx2<uint16_t , 10 >(BYTE *(&dstp)[3 ], int (&dstPitch)[3 ], const BYTE *(&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix &m);
539- template void convert_yuv_to_planarrgb_uint8_14_avx2<uint16_t , 12 >(BYTE *(&dstp)[3 ], int (&dstPitch)[3 ], const BYTE *(&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix &m);
540- template void convert_yuv_to_planarrgb_uint8_14_avx2<uint16_t , 14 >(BYTE *(&dstp)[3 ], int (&dstPitch)[3 ], const BYTE *(&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix &m);
651+ // template<typename pixel_t, bool lessthan16bit>
652+ template void convert_yuv_to_planarrgb_uintN_avx2<uint8_t , true >(BYTE *(&dstp)[3 ], int (&dstPitch)[3 ], const BYTE *(&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix &m, const int bits_per_pixel);
653+ template void convert_yuv_to_planarrgb_uintN_avx2<uint16_t , true >(BYTE * (&dstp)[3 ], int (&dstPitch)[3 ], const BYTE * (&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix& m, const int bits_per_pixel);
654+ template void convert_yuv_to_planarrgb_uintN_avx2<uint16_t , false >(BYTE *(&dstp)[3 ], int (&dstPitch)[3 ], const BYTE *(&srcp)[3 ], const int (&srcPitch)[3], int width, int height, const ConversionMatrix &m, const int bits_per_pixel);
541655
542656void convert_yuv_to_planarrgb_float_avx2 (BYTE * (&dstp)[3], int(&dstPitch)[3], const BYTE* (&srcp)[3], const int(&srcPitch)[3], int width, int height, const ConversionMatrix& m)
543657{
0 commit comments