Converting Array of Floats to UINT8 (`char`) or UINT16 (`unsigned short`) Using SSE4 The problem is given image in 32 Bit Floating Point Format (float) how to convert it to UINT8 (char) or UNIT16 (unsigned short) in the most efficient way.

Assumptions:


Image is very long, namely don't be troubled with edge cases.
One can not assume the image is in any given range.
Output data must be clipped into the proper range ([0, 255], [0, 2 ^ 16 - 1]). I think the term is to be saturated.
Data should be rounded to nearest integer.
One could use SSE4 operations (Namely, everything but AVX).


I'd rather have a code in C style.
But C++ is also OK (I will translate it on my own).

I tried this simple code for the UINT8 case:

void ConvertToUint8(char* mO, float* mF, float* mB, int numRows, int numCols, int numColsPad, float scalingFctr)
{
    // mB - Background, mF - Foreground

    int ii, jj;

    __m128  floatPx;
    __m128  scalingFactor;
    __m128i uint8Px;

    scalingFactor = _mm_set1_ps(scalingFctr);

#pragma omp parallel for private(jj, floatPx, scalingFactor, uint8Px)
    for (ii = 0; ii &lt; numRows; ii++) {
        for (jj = 0; jj &lt; numCols; jj += SSE_STRIDE) {

            floatPx = _mm_loadu_ps(&amp;mB[(ii * numColsPad) + jj]);
            uint8Px = _mm_cvtps_epi32(_mm_mul_ps(floatPx, scalingFactor));

            uint8Px = _mm_packus_epi32(uint8Px, uint8Px);
            uint8Px = _mm_packus_epi16(uint8Px, uint8Px);

            *(int *) (mO[(ii * numColsPad) + jj]) = _mm_cvtsi128_si32(uint8Px);

        }
    }


}


The code above is based on StackOverflow Q29856006 - SSE intrinsics: Convert 32-bit floats to UNSIGNED 8-bit integers.

But I'm sure there are better ways (For instance, why not load 4 packed __m128 and then store 32 Pixels at once)?
why not load 4 packed __m128 and then store 32 Pixels at once


I think that's 16 pixels, but it's a good plan. The pack instructions were used inefficiently (in the linked question that was not really an issue) and that would be improved by processing more elements simultaneously. For example, something like this:

#define SSE_STRIDE 16

void ConvertToUint8(char* mO, float* mF, float* mB, unsigned numRows, unsigned numCols, unsigned numColsPad, float scalingFctr)
{
    unsigned ii, jj;
    __m128 a, b, c, d;
    __m128i ai, bi, ci, di, p0, p1;
    __m128 scalingFactor = _mm_set1_ps(scalingFctr);
    float *inptr;
    __m128i *outptr;

#pragma omp parallel for private(jj, a, b, c, d, ai, bi, ci, di, p0, p1, inptr, outptr)
    for (ii = 0; ii &lt; numRows; ii++) {
        inptr = &amp;mB[ii * numColsPad];
        outptr = (__m128i*)&amp;mO[ii * numColsPad];
        for (jj = 0; jj &lt; numCols; jj += SSE_STRIDE) {
            a = _mm_loadu_ps(inptr);
            b = _mm_loadu_ps(inptr + 4);
            c = _mm_loadu_ps(inptr + 8);
            d = _mm_loadu_ps(inptr + 12);
            inptr += SSE_STRIDE;
            ai = _mm_cvtps_epi32(_mm_mul_ps(a, scalingFactor));
            bi = _mm_cvtps_epi32(_mm_mul_ps(b, scalingFactor));
            ci = _mm_cvtps_epi32(_mm_mul_ps(c, scalingFactor));
            di = _mm_cvtps_epi32(_mm_mul_ps(d, scalingFactor));
            p0 = _mm_packs_epi32(ai, bi),
            p1 = _mm_packs_epi32(ci, di);
            p0 = _mm_packus_epi16(p0, p1);
            _mm_storeu_si128(outptr++, p0);
        }
    }
}


I've also changed some thing to unsigned, because some useless sign-extension was going on sometimes (depending on how it was compiled). inptr and outptr are now calculated outside the inner loop, normally we can expect the compiler to be pretty clever about that too but compiling with OpenMP support seemed to make Clang less clever, it put the actual multiplication between ii and numColsPad inside the inner loop. That's not good, because on Intel that imul would go to p1, and p1 is already completely packed with the float-to-int conversions and fp-multiplications (eg on Skylake both of those go to p01). That loop should be able to run at 1 iteration every 4 cycles in the best case on most architectures, but with that extra imul in there that wouldn't be possible.

To convert to uint16 something similar can be used, but with _mm_packus_epi32 and no packing to bytes.