Skip to content

Commit de6ab09

Browse files
committed
Fix #498: correct SIMD rounding in rgbp8 full->y10 limited and move limitedY/U/V offset post-addition to int32 domain
1 parent a40687f commit de6ab09

2 files changed

Lines changed: 67 additions & 21 deletions

File tree

avs_core/convert/intel/convert_planar_avx2.cpp

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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

avs_core/convert/intel/convert_planar_sse.cpp

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,11 @@ static void convert_yuv_to_planarrgb_sse2_internal(BYTE* (&dstp)[3], int(&dstPit
223223
__m128i half = _mm_set1_epi16((short)half_pixel_offset);
224224
__m128i limit = _mm_set1_epi16((short)max_pixel_value_target);
225225

226-
constexpr int ROUND_SCALE = 1 << (INT_ARITH_SHIFT - 1);
227-
const __m128i m128i_round_scale = _mm_set1_epi16(ROUND_SCALE);
226+
// Overflow and rounding magnitude considerations.
227+
// See comments in AVX2 version for details.
228+
229+
const int ROUND_SCALE = 1 << ((target_shift < INT_ARITH_SHIFT ? target_shift : INT_ARITH_SHIFT) - 1);
230+
const __m128i m128i_round_scale = _mm_set1_epi16((short)ROUND_SCALE);
228231

229232
int round_mask_plus_offset_out_scaled_i;
230233
int round_mask_plus_offset_out_chroma_scaled_i;
@@ -246,18 +249,36 @@ static void convert_yuv_to_planarrgb_sse2_internal(BYTE* (&dstp)[3], int(&dstPit
246249
offset_in = _mm_setzero_si128();
247250

248251
if constexpr (!float_matrix_workflow) {
252+
// integer preparations for the madd-based main loop
253+
// keep madd simple: only handle the rounding (ROUND_SCALE * 1)
254+
// offsets are handled later in the patch, after the madd, added to the same place as the optional 16-bit pivot adjustment
255+
round_mask_plus_offset_out_scaled_i = ROUNDER / ROUND_SCALE;
256+
round_mask_plus_offset_out_chroma_scaled_i = ROUNDER / ROUND_SCALE;
257+
258+
// 32-bit post-conversion adds offset in float domain, this is why it's 0 for final_is_float
259+
const int offset_out_for_patch = final_is_float ? 0 : (offset_out_scalar << INT_ARITH_SHIFT);
260+
const int chroma_offset_out_for_patch = final_is_float ? 0 : (half_pixel_offset << INT_ARITH_SHIFT);
261+
249262
if constexpr (lessthan16bit) {
250-
round_mask_plus_offset_out_scaled_i = final_is_float ? 0 : (ROUNDER + (offset_out_scalar << INT_ARITH_SHIFT)) / ROUND_SCALE;
251-
round_mask_plus_offset_out_chroma_scaled_i = final_is_float ? 0 : (ROUNDER + (half_pixel_offset << INT_ARITH_SHIFT)) / ROUND_SCALE;
252-
v_patch_G = v_patch_B = v_patch_R = _mm_setzero_si128();
263+
// 8-14 bit
264+
// move ONLY the output offset to the 32-bit patch
265+
if constexpr (direction == ConversionDirection::RGB_TO_YUV || direction == ConversionDirection::YUV_TO_YUV) {
266+
v_patch_G = _mm_set1_epi32(offset_out_for_patch);
267+
v_patch_B = _mm_set1_epi32(chroma_offset_out_for_patch);
268+
v_patch_R = _mm_set1_epi32(chroma_offset_out_for_patch);
269+
}
270+
else {
271+
// YUV_TO_RGB, RGB_TO_RGB, RGB_TO_Y: same nonchroma offset
272+
v_patch_G = v_patch_B = v_patch_R = _mm_set1_epi32(offset_out_for_patch);
273+
}
253274
}
254275
else {
255-
round_mask_plus_offset_out_scaled_i = ROUNDER / ROUND_SCALE;
256-
round_mask_plus_offset_out_chroma_scaled_i = ROUNDER / ROUND_SCALE;
276+
// exact 16 bit
277+
278+
// move BOTH the pivot and the output offset to the 32-bit patch
279+
// Since we have to do the patching anyway, we can combine both adjustments here
257280
const int luma_or_rgbin_pivot = 32768 + offset_in_scalar;
258281
const int chroma_pivot = 32768;
259-
const int offset_out_for_patch = final_is_float ? 0 : (offset_out_scalar << INT_ARITH_SHIFT);
260-
const int chroma_offset_out_for_patch = final_is_float ? 0 : (half_pixel_offset << INT_ARITH_SHIFT);
261282

262283
if constexpr (direction == ConversionDirection::YUV_TO_RGB) {
263284
v_patch_G = _mm_set1_epi32(luma_or_rgbin_pivot * m.y_g + offset_out_for_patch);

0 commit comments

Comments
 (0)