如何解决带SSE的8位LERP
我一直在尝试找到使用AMD64 SIMD指令来实现可与大型u8值集配合使用的lerp的最佳方法,但我似乎无法在不需要所有SIMD扩展的情况下找出正确的指令。
我现在正在使用的公式是
u8* a;
u8* b;
u8* result;
size_t count;
u16 total;
u16 progress;
u32 invertedProgress = total - progress;
for(size_t i = 0; i < count; i++){
result[i] = (u8)((b[i] * progress + a[i] * invertedProgress) / total);
}
我认为它看起来像:
u8* a;
u8* b;
u8* result;
size_t count;
u16 total;
u16 progress;
__m128i mmxZero;
__m128i mmxProgress;
__m128i mmxInvertedProgress;
__m128i mmxProductA;
__m128i mmxProductB;
mmxZero = _mm_xor_ps(zero,zero); // Is there a clear?
mmxProgress = Fill with progress;
mmxTotal = Fill with total;
mmxInvertedProgress = mmxTotal;
mmxInvertedProgress = _mm_unpacklo_epi8(mmxInvertedProgres,mmxZero);
mmxInvertedProgress = _mm_sub_epi8(mmxTotal,progress);
for(size_t i = 0; i < count; i += 8){
mmxProductA = load A;
// u8 -> u16
mmxProductA = _mm_unpacklo_epi8(mmxProductA,mmxZero);
mmxProductB = load B;
// u8 -> u16
mmxProductB = _mm_unpacklo_epi8(mmxProductB,mmxZero);
// a * (total - progress)
mmxProductA = _mm_mullo_epi16(mmxProductA,mmxInvertedProgress);
// b * progress
mmxProductB = _mm_mullo_epi16(mmxProductB,mmxProgress);
// a * (total - progress) + b * progress
mmxProductA = _mm_add_epi16(mmxProductA,mmxProductB);
// (a * (total - progress) + b * progress) / total
mmxProductA = _mm_div_epi16(mmxProductA,mmxTotal);
mmxProductA = saturated u16 -> u8;
store result = maxProductA;
}
这里有些事情我似乎似乎找不到in the guide,主要与加载和存储值有关。
我知道有一些更新的指令可以同时做更多的事情,这种最初的实现应该可以在较旧的芯片上工作。
在此示例中,我还忽略了对齐方式和缓冲区溢出的可能性,我认为这超出了该问题的范围。
解决方法
好问题。如您所知,SSE没有整数除法指令,并且(与ARM NEON不同)它没有字节的乘法或FMA。
这是我通常要做的。下面的代码将向量分成偶数/奇数字节,使用16位乘法指令分别缩放,然后将它们合并回字节。
// Linear interpolation is based on the following formula: x*(1-s) + y*s which can equivalently be written as x + s(y-x).
class LerpBytes
{
// Multipliers are fixed point numbers in 16-bit lanes of these vectors,in 1.8 format
__m128i mulX,mulY;
public:
LerpBytes( uint16_t progress,uint16_t total )
{
// The source and result are bytes.
// Multipliers only need 1.8 fixed point format,anything above that is wasteful.
assert( total > 0 );
assert( progress >= 0 );
assert( progress <= total );
const uint32_t fp = (uint32_t)progress * 0x100 / total;
mulY = _mm_set1_epi16( (short)fp );
mulX = _mm_set1_epi16( (short)( 0x100 - fp ) );
}
__m128i lerp( __m128i x,__m128i y ) const
{
const __m128i lowMask = _mm_set1_epi16( 0xFF );
// Split both vectors into even/odd bytes in 16-bit lanes
__m128i lowX = _mm_and_si128( x,lowMask );
__m128i highX = _mm_srli_epi16( x,8 );
__m128i lowY = _mm_and_si128( y,lowMask );
__m128i highY = _mm_srli_epi16( y,8 );
// That multiply instruction has relatively high latency,3-5 cycles.
// We're lucky to have 4 vectors to handle.
lowX = _mm_mullo_epi16( lowX,mulX );
lowY = _mm_mullo_epi16( lowY,mulY );
highX = _mm_mullo_epi16( highX,mulX );
highY = _mm_mullo_epi16( highY,mulY );
// Add the products
__m128i low = _mm_adds_epu16( lowX,lowY );
__m128i high = _mm_adds_epu16( highX,highY );
// Pack them back into bytes.
// The multiplier was 1.8 fixed point,trimming the lowest byte off both vectors.
low = _mm_srli_epi16( low,8 );
high = _mm_andnot_si128( lowMask,high );
return _mm_or_si128( low,high );
}
};
static void print( const char* what,__m128i v )
{
printf( "%s:\t",what );
alignas( 16 ) std::array<uint8_t,16> arr;
_mm_store_si128( ( __m128i * )arr.data(),v );
for( uint8_t b : arr )
printf( " %02X",(int)b );
printf( "\n" );
}
int main()
{
const __m128i x = _mm_setr_epi32( 0x33221100,0x77665544,0xBBAA9988,0xFFEEDDCC );
const __m128i y = _mm_setr_epi32( 0xCCDDEEFF,0x8899AABB,0x44556677,0x00112233 );
LerpBytes test( 0,1 );
print( "zero",test.lerp( x,y ) );
test = LerpBytes( 1,1 );
print( "one",2 );
print( "half",3 );
print( "1/3",4 );
print( "1/4",y ) );
return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。