MMX SSE 到 C 代码转换时图像质量下降

Posted

技术标签:

【中文标题】MMX SSE 到 C 代码转换时图像质量下降【英文标题】:Image quality is decresing when MMX SSE to C code conversion 【发布时间】:2012-10-03 14:52:35 【问题描述】:

我正在将 MMX SSE 转换为等效的 C 代码。我几乎已经转换了它,但是我得到的图像质量不合适,或者我可以看到图像中有一些噪点。我正在调试过去 5 天的代码,但我没有得到任何原因。如果你们调查这个问题并帮助我,我会非常高兴。

原始 SSE 代码:

void unpack_8bit_to_16bit( __m128i *a, __m128i* b0, __m128i* b1 ) 

    __m128i zero = _mm_setzero_si128();
    b0 = _mm_unpacklo_epi8( a, zero );
    b1 = _mm_unpackhi_epi8( a, zero );


void convolve_cols_3x3( const unsigned char* in, int16_t* out_v, int16_t* out_h, int w, int h )

    using namespace std;
    assert( w % 16 == 0 && "width must be multiple of 16!" );
    const int w_chunk  = w/16;
    __m128i*    i0       = (__m128i*)( in );
    __m128i*    i1       = (__m128i*)( in ) + w_chunk*1;
    __m128i*    i2       = (__m128i*)( in ) + w_chunk*2;
    __m128i* result_h  = (__m128i*)( out_h ) + 2*w_chunk;
    __m128i* result_v  = (__m128i*)( out_v ) + 2*w_chunk;
    __m128i* end_input = (__m128i*)( in ) + w_chunk*h;

    for( ; i2 != end_input; i0++, i1++, i2++, result_v+=2, result_h+=2 ) 
    
        *result_h     = _mm_setzero_si128();
        *(result_h+1) = _mm_setzero_si128();
        *result_v     = _mm_setzero_si128();
        *(result_v+1) = _mm_setzero_si128();
        __m128i ilo, ihi;
        unpack_8bit_to_16bit( *i0, ihi, ilo ); 
        *result_h     = _mm_add_epi16( ihi, *result_h );
        *(result_h+1) = _mm_add_epi16( ilo, *(result_h+1) );
        *result_v     = _mm_add_epi16( *result_v, ihi );
        *(result_v+1) = _mm_add_epi16( *(result_v+1), ilo );
        unpack_8bit_to_16bit( *i1, ihi, ilo );
        *result_v     = _mm_add_epi16( *result_v, ihi );
        *(result_v+1) = _mm_add_epi16( *(result_v+1), ilo );
        *result_v     = _mm_add_epi16( *result_v, ihi );
        *(result_v+1) = _mm_add_epi16( *(result_v+1), ilo );
        unpack_8bit_to_16bit( *i2, ihi, ilo );
        *result_h     = _mm_sub_epi16( *result_h, ihi );
        *(result_h+1) = _mm_sub_epi16( *(result_h+1), ilo );
        *result_v     = _mm_add_epi16( *result_v, ihi );
        *(result_v+1) = _mm_add_epi16( *(result_v+1), ilo );
    

我转换的代码如下

void convolve_cols_3x3( const unsigned char* in, int16_t* out_v, int16_t* out_h, int w, int h )

    using namespace std;
    assert( w % 16 == 0 && "width must be multiple of 16!" );
    const int w_chunk  = w/16;

    uint8_t*    i0       = (uint8_t*)( in );
    uint8_t*    i1       = (uint8_t*)( in ) + w_chunk*1*16;
    uint8_t*    i2       = (uint8_t*)( in ) + w_chunk*2*16;
    int16_t* result_h  = (int16_t*)( out_h ) + 2*w_chunk*16;
    int16_t* result_v  = (int16_t*)( out_v ) + 2*w_chunk*16;
    uint8_t* end_input = (uint8_t*)( in ) + w_chunk*h*16;
    for( ; i2 != end_input; i0+= 16, i1+= 16, i2+= 16, result_v+= 16, result_h+= 16 ) 
    
        for (int i=0; i<8;i++)
        
            result_h[i]     = 0;
            result_h[i + 8] = 0;
            result_v[i]        = 0;
            result_v[i + 8] = 0;
            result_h[i]     = (int16_t)(i0[i]) + result_h[i] ;
            result_h[i + 8] = (int16_t)(i0[i + 8]) + result_h[i + 8] ;
            result_v[i]     = (int16_t)(i0[i]) + result_v[i] ;
            result_v[i + 8] = (int16_t)(i0[i + 8]) + result_v[i + 8] ;
            result_v[i]     = (int16_t)(i1[i]) + result_v[i] ;
            result_v[i + 8] = (int16_t)(i1[i + 8]) + result_v[i + 8] ;
            result_v[i]     = (int16_t)(i1[i]) + result_v[i] ;
            result_v[i + 8] = (int16_t)(i1[i + 8]) + result_v[i + 8] ;
            result_h[i]     = result_h[i] - (int16_t)(i2[i]);
            result_h[i + 8] = result_h[i + 8] - (int16_t)(i2[i + 8]);
            result_v[i]     = (int16_t)(i2[i]) + result_v[i] ;
            result_v[i + 8] = (int16_t)(i2[i + 8]) + result_v[i + 8] ;
        
    

对不起,如果代码不是那么可读。 wh 代表宽度和高度。 out_hout_v 是两个稍后用于其他目的的参数。

【问题讨论】:

SSE 代码中似乎至少存在一个错误:iloihi 在使用前未初始化。还是您不小心删除了unpack_8bit_to_16bit 行? @paul:: ihi 和 ilo 通过引用传递给 unpack_8bit_to_16bit(---) 函数,因此它们在原始 sse 代码中被初始化......并且在转换后的代码中我直接使用了 i0, i1 和 i2 以最小化函数调用... 再看代码——特别是这两行:__m128i ilo, ihi; *result_h = _mm_add_epi16( ihi, *result_h ); 我很抱歉保罗...在 __ma128i ilo,ihi 之后有一行。该行是 unpack_8bit_to_16bit( *i0, ihi, ilo ); 【参考方案1】:

错误似乎在您的指针数学和源数据的读取中。指针变量 i0、i1、i2 是无符号字符。在你的代码中这样的行:

 result_h[i + 8] = (int16_t)(i0[i + 8]) + result_h[i + 8] ;

应该是这样的:

result_h[i + 8] = (int16_t)(i0[i*2 + 16]) + result_h[i + 8] ;

转换为 int16_t 不会影响 i0 方括号内的偏移量。您正在使用 16 字节结构 (__m128i),但以 8 字节偏移量访问它们。您也只使用 i0 和 i1 指向的整数的低 8 位。在原始 SSE 代码中,您正在读取 16 位整数。如果您需要在加法之前读取 16 位整数,最终更正后的代码可能如下所示:

result_h[i + 8] = *(int16_t *)(&i0[i*2 + 16]) + result_h[i + 8];

【讨论】:

@user1717323 - 你试过我的解决方案了吗?

以上是关于MMX SSE 到 C 代码转换时图像质量下降的主要内容,如果未能解决你的问题,请参考以下文章

将 MMX/SSE 指令移植到 AltiVec

在颤振中使用 image.asset 时图像质量下降

MMX 与 SSE2 性能比较

调整大小时 SVG 图像质量下降

MMX:褪色两个图像结果

交叉编译到树莓派时 JPEG 图像质量下降