1010#include < mferror.h>
1111#include < propvarutil.h>
1212
13+ // SSE2 only where it exists. On ARM64 MSVC's emmintrin.h is an #error unless the
14+ // soft-intrinsics emulation is linked in, so the converter below falls back to its
15+ // scalar loops there, which the colour test checks against BT.709 the same way.
16+ #if defined(_M_X64) || defined(_M_IX86)
17+ #define OPENSCREEN_NV12_SSE2 1
1318#include < emmintrin.h>
19+ #endif
1420
1521#include < algorithm>
1622#include < cstring>
@@ -608,6 +614,7 @@ void convertBgraToNv12Bt709(const BYTE* bgra, int stride, int width, int height,
608614 // Luma four pixels at a time in SSE2, which every x64 CPU has: this runs on
609615 // the video writer's thread for every frame, and the scalar loop alone cost
610616 // 2.5 ms a 1080p frame. 15-bit coefficients so they fit madd's int16 lanes.
617+ #ifdef OPENSCREEN_NV12_SSE2
611618 const __m128i zero = _mm_setzero_si128 ();
612619 const __m128i lumaCoefficients = _mm_setr_epi16 (2032 , 20127 , 5983 , 0 , 2032 , 20127 , 5983 , 0 );
613620 const __m128i lumaRounding = _mm_set1_epi32 (16384 );
@@ -617,10 +624,12 @@ void convertBgraToNv12Bt709(const BYTE* bgra, int stride, int width, int height,
617624 const __m128i products = _mm_madd_epi16 (pixels, lumaCoefficients);
618625 return _mm_add_epi32 (products, _mm_shuffle_epi32 (products, _MM_SHUFFLE (2 , 3 , 0 , 1 )));
619626 };
627+ #endif
620628 for (int y = 0 ; y < height; y += 1 ) {
621629 const BYTE * row = bgra + static_cast <size_t >(y) * stride;
622630 BYTE * out = luma + static_cast <size_t >(y) * width;
623631 int x = 0 ;
632+ #ifdef OPENSCREEN_NV12_SSE2
624633 for (; x + 4 <= width; x += 4 ) {
625634 const __m128i pixels = _mm_loadu_si128 (reinterpret_cast <const __m128i*>(row + x * 4 ));
626635 const __m128i first = lumaOfTwo (_mm_unpacklo_epi8 (pixels, zero));
@@ -632,6 +641,7 @@ void convertBgraToNv12Bt709(const BYTE* bgra, int stride, int width, int height,
632641 const int packed = _mm_cvtsi128_si32 (bytes);
633642 std::memcpy (out + x, &packed, 4 );
634643 }
644+ #endif
635645 for (; x < width; x += 1 ) {
636646 const int b = row[x * 4 ];
637647 const int g = row[x * 4 + 1 ];
@@ -641,6 +651,7 @@ void convertBgraToNv12Bt709(const BYTE* bgra, int stride, int width, int height,
641651 }
642652 // Chroma the same way, two 2x2 blocks at a time: the four pixels of each
643653 // block summed in int16 lanes (at most 1020), then one madd per channel.
654+ #ifdef OPENSCREEN_NV12_SSE2
644655 const __m128i cbCoefficients = _mm_setr_epi16 (28784 , -22189 , -6596 , 0 , 28784 , -22189 , -6596 , 0 );
645656 const __m128i crCoefficients = _mm_setr_epi16 (-2642 , -26142 , 28784 , 0 , -2642 , -26142 , 28784 , 0 );
646657 const __m128i chromaRounding = _mm_set1_epi32 (131072 );
@@ -650,11 +661,13 @@ void convertBgraToNv12Bt709(const BYTE* bgra, int stride, int width, int height,
650661 const __m128i sums = _mm_add_epi32 (products, _mm_shuffle_epi32 (products, _MM_SHUFFLE (2 , 3 , 0 , 1 )));
651662 return _mm_shuffle_epi32 (sums, _MM_SHUFFLE (2 , 0 , 2 , 0 ));
652663 };
664+ #endif
653665 for (int y = 0 ; y < height; y += 2 ) {
654666 const BYTE * top = bgra + static_cast <size_t >(y) * stride;
655667 const BYTE * bottom = y + 1 < height ? top + stride : top;
656668 BYTE * out = chroma + static_cast <size_t >(y / 2 ) * width;
657669 int x = 0 ;
670+ #ifdef OPENSCREEN_NV12_SSE2
658671 for (; x + 4 <= width; x += 4 ) {
659672 const __m128i upper = _mm_loadu_si128 (reinterpret_cast <const __m128i*>(top + x * 4 ));
660673 const __m128i lower = _mm_loadu_si128 (reinterpret_cast <const __m128i*>(bottom + x * 4 ));
@@ -669,6 +682,7 @@ void convertBgraToNv12Bt709(const BYTE* bgra, int stride, int width, int height,
669682 const int packed = _mm_cvtsi128_si32 (_mm_packus_epi16 (_mm_packs_epi32 (values, zero), zero));
670683 std::memcpy (out + x, &packed, 4 );
671684 }
685+ #endif
672686 for (; x < width; x += 2 ) {
673687 const int right = (x + 1 < width ? x + 1 : x) * 4 ;
674688 const int left = x * 4 ;
0 commit comments