@@ -512,14 +512,30 @@ static void convert_yuv_to_planarrgb_avx2_internal(BYTE* (&dstp)[3], int(&dstPit
512512 __m256i half = _mm256_set1_epi16 ((short )half_pixel_offset); // 128
513513 __m256i limit = _mm256_set1_epi16 ((short )max_pixel_value_target); // 255
514514
515+ // 13 bit INT_ARITH_SHIFT example:
515516 // to be able to use it as signed 16 bit in madd; 4096+(16<<13) would not fit into i16
516517 // multiplier is 4096 instead of 1:
517518 // original : 1 * 4096
518519 // needed : 1 * (4096 + offset_rgb<<13) (4096 + 131072 overflows i16)
519520 // changed to : 4096 * (1 + offset_rgb>>(13-1)
520521 // Except for exact 16 bit, where we do the output offset_in adjustment along with the 16 bit signed-unsigned pivot fix
521- constexpr int ROUND_SCALE = 1 << (INT_ARITH_SHIFT - 1 ); // 1 << 12, for 13 bit integer arithmetic: "0.5"
522- const __m256i m256i_round_scale = _mm256_set1_epi16 (ROUND_SCALE );
522+
523+ // Overflow and rounding magnitude considerations.
524+ // INT_ARITH_SHIFT is 13,14,15; bit_diff is +/-8; target_shift is INT_ARITH_SHIFT+/-8
525+ // Make ROUND_SCALE
526+ // - still safely fit in signed 16 bit
527+ // - keeping ROUNDER as multiple of ROUND_SCALE
528+ // Since ROUNDER is (1 << (target_shift - 1)), we have to limit it only when
529+ // ROUNDER/ROUND_SCALE would result in 0
530+ // e.g. rgb->Y 8->10 bits: INT_ARITH_SHIFT=15, bit_diff=10-8=2, target_shift=13
531+ // ROUNDER is 1<<(target_shift-1)=1<<(13-1)
532+ // old code: ROUND_SCALE is 1<<(INT_ARITH_SHIFT-1)=1<<(15-1)
533+ // ROUNDER/ROUND_SCALE would always be 0, so in madd the multiplication would result in 0.as well
534+ // new code (keep minimum of target_shift and INT_ARITH_SHIFT)
535+ // ROUNDER/ROUND_SCALE as a multiplier is now 1 or a small power-of-2
536+
537+ const int ROUND_SCALE = 1 << ((target_shift < INT_ARITH_SHIFT ? target_shift : INT_ARITH_SHIFT ) - 1 );
538+ const __m256i m256i_round_scale = _mm256_set1_epi16 ((short )ROUND_SCALE );
523539
524540 int round_mask_plus_offset_out_scaled_i;
525541 int round_mask_plus_offset_out_chroma_scaled_i;
@@ -548,26 +564,35 @@ static void convert_yuv_to_planarrgb_avx2_internal(BYTE* (&dstp)[3], int(&dstPit
548564
549565 if constexpr (!float_matrix_workflow) {
550566 // integer preparations for the madd-based main loop
567+ // keep madd simple: only handle the rounding (ROUND_SCALE * 1)
568+ // offsets are handled later in the patch, after the madd, added to the same place as the optional 16-bit pivot adjustment
569+ round_mask_plus_offset_out_scaled_i = ROUNDER / ROUND_SCALE ; // 1 or small power-of-2 or 0 (final_is_float)
570+ round_mask_plus_offset_out_chroma_scaled_i = ROUNDER / ROUND_SCALE ; // 1 or small power-of-2 or 0 (final_is_float)
571+
572+ // 32-bit post-conversion adds offset in float domain, this is why it's 0 for final_is_float
573+ const int offset_out_for_patch = final_is_float ? 0 : (offset_out_scalar << INT_ARITH_SHIFT );
574+ const int chroma_offset_out_for_patch = final_is_float ? 0 : (half_pixel_offset << INT_ARITH_SHIFT ); // 32-bit post-conversion adds offset in float domain.
575+
551576 if constexpr (lessthan16bit) {
552577 // 8-14 bit
553- // offset of same magnitude as coeffs, also in simd madd
554- round_mask_plus_offset_out_scaled_i = final_is_float ? 0 : (ROUNDER + (offset_out_scalar << INT_ARITH_SHIFT )) / ROUND_SCALE ;
555- round_mask_plus_offset_out_chroma_scaled_i = final_is_float ? 0 : (ROUNDER + (half_pixel_offset << INT_ARITH_SHIFT )) / ROUND_SCALE ;
556- v_patch_G = v_patch_B = v_patch_R = _mm256_setzero_si256 (); // No patch needed, since the signed 16-bit workaround is not needed.
578+ // move ONLY the output offset to the 32-bit patch
579+ if constexpr (direction == ConversionDirection::RGB_TO_YUV || direction == ConversionDirection::YUV_TO_YUV ) {
580+ v_patch_G = _mm256_set1_epi32 (offset_out_for_patch);
581+ v_patch_B = _mm256_set1_epi32 (chroma_offset_out_for_patch);
582+ v_patch_R = _mm256_set1_epi32 (chroma_offset_out_for_patch);
583+ }
584+ else {
585+ // YUV_TO_RGB, RGB_TO_RGB, RGB_TO_Y: same nonchroma offset
586+ v_patch_G = v_patch_B = v_patch_R = _mm256_set1_epi32 (offset_out_for_patch);
587+ }
557588 }
558589 else {
559590 // exact 16 bit
560- // keep madd simple: only handle the rounding (ROUND_SCALE * 1)
561- // rgb offset is handled later in the patch, after the madd, added to the same place as the pivot adjustment
562- round_mask_plus_offset_out_scaled_i = ROUNDER / ROUND_SCALE ; // effectively 1 or 0 (final_is_float)
563- round_mask_plus_offset_out_chroma_scaled_i = ROUNDER / ROUND_SCALE ; // effectively 1 or 0 (final_is_float)
564591
565592 // move BOTH the pivot and the output offset to the 32-bit patch
566593 // Since we have to do the patching anyway, we can combine both adjustments here
567594 const int luma_or_rgbin_pivot = 32768 + offset_in_scalar;
568595 const int chroma_pivot = 32768 ;
569- const int offset_out_for_patch = final_is_float ? 0 : (offset_out_scalar << INT_ARITH_SHIFT ); // 32-bit post-conversion adds offset in float domain.
570- const int chroma_offset_out_for_patch = final_is_float ? 0 : (half_pixel_offset << INT_ARITH_SHIFT ); // 32-bit post-conversion adds offset in float domain.
571596
572597 if constexpr (direction == ConversionDirection::YUV_TO_RGB ) {
573598 // for YUV->RGB, the pivot adjustment is needed for all three channels since the luma coeffs are not zero, but pivot only the Y
0 commit comments