// license:BSD-3-Clause
// copyright-holders:Vas Crabb, Ryan Holtz
/***************************************************************************

    rgbsse.h

    SSE optimized RGB utilities.

    WARNING: This code assumes SSE2 or greater capability.

***************************************************************************/
#pragma once
#include "../../types.h"
#include "palette.h"

#include <emmintrin.h>
#ifdef __SSE4_1__
#include <smmintrin.h>
#endif

/***************************************************************************
    TYPE DEFINITIONS
***************************************************************************/

class rgbaint_t
{
public:
  rgbaint_t() {}
  explicit rgbaint_t(u32 rgba) { set(rgba); }
  rgbaint_t(s32 a, s32 r, s32 g, s32 b) { set(a, r, g, b); }
  explicit rgbaint_t(const rgb_t& rgb) { set(rgb); }
  explicit rgbaint_t(__m128i rgba) { m_value = rgba; }

  rgbaint_t(const rgbaint_t& other) = default;
  rgbaint_t& operator=(const rgbaint_t& other) = default;

  void set(const rgbaint_t& other) { m_value = other.m_value; }
  void set(const u32& rgba)
  {
    m_value = _mm_unpacklo_epi16(_mm_unpacklo_epi8(_mm_cvtsi32_si128(rgba), _mm_setzero_si128()), _mm_setzero_si128());
  }
  void set(s32 a, s32 r, s32 g, s32 b) { m_value = _mm_set_epi32(a, r, g, b); }
  void set(const rgb_t& rgb) { set((const u32&)rgb); }
  // This function sets all elements to the same val
  void set_all(const s32& val) { m_value = _mm_set1_epi32(val); }
  // This function zeros all elements
  void zero() { m_value = _mm_xor_si128(m_value, m_value); }
  // This function zeros only the alpha element
  void zero_alpha() { m_value = _mm_and_si128(m_value, alpha_mask()); }

  inline rgb_t to_rgba() const
  {
    return _mm_cvtsi128_si32(_mm_packus_epi16(_mm_packs_epi32(m_value, _mm_setzero_si128()), _mm_setzero_si128()));
  }

  inline rgb_t to_rgba_clamp() const
  {
    return _mm_cvtsi128_si32(_mm_packus_epi16(_mm_packs_epi32(m_value, _mm_setzero_si128()), _mm_setzero_si128()));
  }

  void set_a16(const s32 value) { m_value = _mm_insert_epi16(m_value, value, 6); }
#ifdef __SSE4_1__
  void set_a(const s32 value) { m_value = _mm_insert_epi32(m_value, value, 3); }
  void set_r(const s32 value) { m_value = _mm_insert_epi32(m_value, value, 2); }
  void set_g(const s32 value) { m_value = _mm_insert_epi32(m_value, value, 1); }
  void set_b(const s32 value) { m_value = _mm_insert_epi32(m_value, value, 0); }
#else
  void set_a(const s32 value)
  {
    m_value = _mm_or_si128(_mm_and_si128(m_value, alpha_mask()), _mm_set_epi32(value, 0, 0, 0));
  }
  void set_r(const s32 value)
  {
    m_value = _mm_or_si128(_mm_and_si128(m_value, red_mask()), _mm_set_epi32(0, value, 0, 0));
  }
  void set_g(const s32 value)
  {
    m_value = _mm_or_si128(_mm_and_si128(m_value, green_mask()), _mm_set_epi32(0, 0, value, 0));
  }
  void set_b(const s32 value)
  {
    m_value = _mm_or_si128(_mm_and_si128(m_value, blue_mask()), _mm_set_epi32(0, 0, 0, value));
  }
#endif

  u8 get_a() const { return u8(unsigned(_mm_extract_epi16(m_value, 6))); }
  u8 get_r() const { return u8(unsigned(_mm_extract_epi16(m_value, 4))); }
  u8 get_g() const { return u8(unsigned(_mm_extract_epi16(m_value, 2))); }
  u8 get_b() const { return u8(unsigned(_mm_cvtsi128_si32(m_value))); }

#ifdef __SSE4_1__
  s32 get_a32() const { return _mm_extract_epi32(m_value, 3); }
  s32 get_r32() const { return _mm_extract_epi32(m_value, 2); }
  s32 get_g32() const { return _mm_extract_epi32(m_value, 1); }
  s32 get_b32() const { return _mm_extract_epi32(m_value, 0); }
#else
  s32 get_a32() const { return (_mm_cvtsi128_si32(_mm_shuffle_epi32(m_value, _MM_SHUFFLE(0, 0, 0, 3)))); }
  s32 get_r32() const { return (_mm_cvtsi128_si32(_mm_shuffle_epi32(m_value, _MM_SHUFFLE(0, 0, 0, 2)))); }
  s32 get_g32() const { return (_mm_cvtsi128_si32(_mm_shuffle_epi32(m_value, _MM_SHUFFLE(0, 0, 0, 1)))); }
  s32 get_b32() const { return (_mm_cvtsi128_si32(m_value)); }
#endif

  // These selects return an rgbaint_t with all fields set to the element choosen (a, r, g, or b)
  rgbaint_t select_alpha32() const { return (rgbaint_t)_mm_shuffle_epi32(m_value, _MM_SHUFFLE(3, 3, 3, 3)); }
  rgbaint_t select_red32() const { return (rgbaint_t)_mm_shuffle_epi32(m_value, _MM_SHUFFLE(2, 2, 2, 2)); }
  rgbaint_t select_green32() const { return (rgbaint_t)_mm_shuffle_epi32(m_value, _MM_SHUFFLE(1, 1, 1, 1)); }
  rgbaint_t select_blue32() const { return (rgbaint_t)_mm_shuffle_epi32(m_value, _MM_SHUFFLE(0, 0, 0, 0)); }

  inline void add(const rgbaint_t& color2) { m_value = _mm_add_epi32(m_value, color2.m_value); }

  inline void add_imm(const s32 imm) { m_value = _mm_add_epi32(m_value, _mm_set1_epi32(imm)); }

  inline void add_imm_rgba(const s32 a, const s32 r, const s32 g, const s32 b)
  {
    m_value = _mm_add_epi32(m_value, _mm_set_epi32(a, r, g, b));
  }

  inline void sub(const rgbaint_t& color2) { m_value = _mm_sub_epi32(m_value, color2.m_value); }

  inline void sub_imm(const s32 imm) { m_value = _mm_sub_epi32(m_value, _mm_set1_epi32(imm)); }

  inline void sub_imm_rgba(const s32 a, const s32 r, const s32 g, const s32 b)
  {
    m_value = _mm_sub_epi32(m_value, _mm_set_epi32(a, r, g, b));
  }

  inline void subr(const rgbaint_t& color2) { m_value = _mm_sub_epi32(color2.m_value, m_value); }

  inline void subr_imm(const s32 imm) { m_value = _mm_sub_epi32(_mm_set1_epi32(imm), m_value); }

  inline void subr_imm_rgba(const s32 a, const s32 r, const s32 g, const s32 b)
  {
    m_value = _mm_sub_epi32(_mm_set_epi32(a, r, g, b), m_value);
  }

  inline void mul(const rgbaint_t& color)
  {
    __m128i tmp1 = _mm_mul_epu32(m_value, color.m_value);
    __m128i tmp2 = _mm_mul_epu32(_mm_srli_si128(m_value, 4), _mm_srli_si128(color.m_value, 4));
    m_value = _mm_unpacklo_epi32(_mm_shuffle_epi32(tmp1, _MM_SHUFFLE(0, 0, 2, 0)),
                                 _mm_shuffle_epi32(tmp2, _MM_SHUFFLE(0, 0, 2, 0)));
  }

  inline void mul_imm(const s32 imm)
  {
    __m128i immv = _mm_set1_epi32(imm);
    __m128i tmp1 = _mm_mul_epu32(m_value, immv);
    __m128i tmp2 = _mm_mul_epu32(_mm_srli_si128(m_value, 4), _mm_srli_si128(immv, 4));
    m_value = _mm_unpacklo_epi32(_mm_shuffle_epi32(tmp1, _MM_SHUFFLE(0, 0, 2, 0)),
                                 _mm_shuffle_epi32(tmp2, _MM_SHUFFLE(0, 0, 2, 0)));
  }

  inline void mul_imm_rgba(const s32 a, const s32 r, const s32 g, const s32 b)
  {
    __m128i immv = _mm_set_epi32(a, r, g, b);
    __m128i tmp1 = _mm_mul_epu32(m_value, immv);
    __m128i tmp2 = _mm_mul_epu32(_mm_srli_si128(m_value, 4), _mm_srli_si128(immv, 4));
    m_value = _mm_unpacklo_epi32(_mm_shuffle_epi32(tmp1, _MM_SHUFFLE(0, 0, 2, 0)),
                                 _mm_shuffle_epi32(tmp2, _MM_SHUFFLE(0, 0, 2, 0)));
  }

  inline void shl(const rgbaint_t& shift)
  {
    rgbaint_t areg(*this);
    rgbaint_t rreg(*this);
    rgbaint_t greg(*this);
    rgbaint_t breg(*this);
    rgbaint_t ashift(0, 0, 0, shift.get_a32());
    rgbaint_t rshift(0, 0, 0, shift.get_r32());
    rgbaint_t gshift(0, 0, 0, shift.get_g32());
    rgbaint_t bshift(0, 0, 0, shift.get_b32());
    areg.m_value = _mm_sll_epi32(areg.m_value, ashift.m_value);
    rreg.m_value = _mm_sll_epi32(rreg.m_value, rshift.m_value);
    greg.m_value = _mm_sll_epi32(greg.m_value, gshift.m_value);
    breg.m_value = _mm_sll_epi32(breg.m_value, bshift.m_value);
    set(areg.get_a32(), rreg.get_r32(), greg.get_g32(), breg.get_b32());
  }

  inline void shl_imm(const u8 shift) { m_value = _mm_slli_epi32(m_value, shift); }

  inline void shr(const rgbaint_t& shift)
  {
    rgbaint_t areg(*this);
    rgbaint_t rreg(*this);
    rgbaint_t greg(*this);
    rgbaint_t breg(*this);
    rgbaint_t ashift(0, 0, 0, shift.get_a32());
    rgbaint_t rshift(0, 0, 0, shift.get_r32());
    rgbaint_t gshift(0, 0, 0, shift.get_g32());
    rgbaint_t bshift(0, 0, 0, shift.get_b32());
    areg.m_value = _mm_srl_epi32(areg.m_value, ashift.m_value);
    rreg.m_value = _mm_srl_epi32(rreg.m_value, rshift.m_value);
    greg.m_value = _mm_srl_epi32(greg.m_value, gshift.m_value);
    breg.m_value = _mm_srl_epi32(breg.m_value, bshift.m_value);
    set(areg.get_a32(), rreg.get_r32(), greg.get_g32(), breg.get_b32());
  }

  inline void shr_imm(const u8 shift) { m_value = _mm_srli_epi32(m_value, shift); }

  inline void sra(const rgbaint_t& shift)
  {
    rgbaint_t areg(*this);
    rgbaint_t rreg(*this);
    rgbaint_t greg(*this);
    rgbaint_t breg(*this);
    rgbaint_t ashift(0, 0, 0, shift.get_a32());
    rgbaint_t rshift(0, 0, 0, shift.get_r32());
    rgbaint_t gshift(0, 0, 0, shift.get_g32());
    rgbaint_t bshift(0, 0, 0, shift.get_b32());
    areg.m_value = _mm_sra_epi32(areg.m_value, ashift.m_value);
    rreg.m_value = _mm_sra_epi32(rreg.m_value, rshift.m_value);
    greg.m_value = _mm_sra_epi32(greg.m_value, gshift.m_value);
    breg.m_value = _mm_sra_epi32(breg.m_value, bshift.m_value);
    set(areg.get_a32(), rreg.get_r32(), greg.get_g32(), breg.get_b32());
  }

  inline void sra_imm(const u8 shift) { m_value = _mm_srai_epi32(m_value, shift); }

  void or_reg(const rgbaint_t& color2) { m_value = _mm_or_si128(m_value, color2.m_value); }
  void and_reg(const rgbaint_t& color2) { m_value = _mm_and_si128(m_value, color2.m_value); }
  void xor_reg(const rgbaint_t& color2) { m_value = _mm_xor_si128(m_value, color2.m_value); }

  void andnot_reg(const rgbaint_t& color2) { m_value = _mm_andnot_si128(color2.m_value, m_value); }

  void or_imm(s32 value) { m_value = _mm_or_si128(m_value, _mm_set1_epi32(value)); }
  void and_imm(s32 value) { m_value = _mm_and_si128(m_value, _mm_set1_epi32(value)); }
  void xor_imm(s32 value) { m_value = _mm_xor_si128(m_value, _mm_set1_epi32(value)); }

  void or_imm_rgba(s32 a, s32 r, s32 g, s32 b) { m_value = _mm_or_si128(m_value, _mm_set_epi32(a, r, g, b)); }
  void and_imm_rgba(s32 a, s32 r, s32 g, s32 b) { m_value = _mm_and_si128(m_value, _mm_set_epi32(a, r, g, b)); }
  void xor_imm_rgba(s32 a, s32 r, s32 g, s32 b) { m_value = _mm_xor_si128(m_value, _mm_set_epi32(a, r, g, b)); }

  inline void clamp_and_clear(const u32 sign)
  {
    __m128i vsign = _mm_set1_epi32(sign);
    m_value = _mm_and_si128(m_value, _mm_cmpeq_epi32(_mm_and_si128(m_value, vsign), _mm_setzero_si128()));
    vsign = _mm_srai_epi32(vsign, 1);
    vsign = _mm_xor_si128(vsign, _mm_set1_epi32(0xffffffff));
    __m128i mask = _mm_cmpgt_epi32(m_value, vsign);
    m_value =
      _mm_or_si128(_mm_and_si128(vsign, mask), _mm_and_si128(m_value, _mm_xor_si128(mask, _mm_set1_epi32(0xffffffff))));
  }

  inline void clamp_to_uint8()
  {
    m_value = _mm_packs_epi32(m_value, _mm_setzero_si128());
    m_value = _mm_packus_epi16(m_value, _mm_setzero_si128());
    m_value = _mm_unpacklo_epi8(m_value, _mm_setzero_si128());
    m_value = _mm_unpacklo_epi16(m_value, _mm_setzero_si128());
  }

  inline void sign_extend(const u32 compare, const u32 sign)
  {
    __m128i compare_vec = _mm_set1_epi32(compare);
    __m128i compare_mask = _mm_cmpeq_epi32(_mm_and_si128(m_value, compare_vec), compare_vec);
    __m128i compared = _mm_and_si128(_mm_set1_epi32(sign), compare_mask);
    m_value = _mm_or_si128(m_value, compared);
  }

  inline void min(const s32 value)
  {
    __m128i val = _mm_set1_epi32(value);
    __m128i is_greater_than = _mm_cmpgt_epi32(m_value, val);

    __m128i val_to_set = _mm_and_si128(val, is_greater_than);
    __m128i keep_mask = _mm_xor_si128(is_greater_than, _mm_set1_epi32(0xffffffff));

    m_value = _mm_and_si128(m_value, keep_mask);
    m_value = _mm_or_si128(val_to_set, m_value);
  }

  inline void max(const s32 value)
  {
    __m128i val = _mm_set1_epi32(value);
    __m128i is_less_than = _mm_cmplt_epi32(m_value, val);

    __m128i val_to_set = _mm_and_si128(val, is_less_than);
    __m128i keep_mask = _mm_xor_si128(is_less_than, _mm_set1_epi32(0xffffffff));

    m_value = _mm_and_si128(m_value, keep_mask);
    m_value = _mm_or_si128(val_to_set, m_value);
  }

  void blend(const rgbaint_t& other, u8 factor);

  void scale_and_clamp(const rgbaint_t& scale);

  // Leave this here in case Model3 blows up...
  // inline void scale_imm_and_clamp(const s32 scale)
  //{
  //  mul_imm(scale);
  //  sra_imm(8);
  //  clamp_to_uint8();
  //}

  // This version needs absolute value of value and scale to be 11 bits or less
  inline void scale_imm_and_clamp(const s16 scale)
  {
    // Set mult a 16 bit inputs to scale
    __m128i immv = _mm_set1_epi16(scale);
    // Shift up by 4
    immv = _mm_slli_epi16(immv, 4);
    // Pack color into mult b 16 bit inputs
    m_value = _mm_packs_epi32(m_value, _mm_setzero_si128());
    // Shift up by 4
    m_value = _mm_slli_epi16(m_value, 4);
    // Do the 16 bit multiply, bottom 64 bits will contain 16 bit truncated results
    m_value = _mm_mulhi_epi16(m_value, immv);
    // Clamp to u8
    m_value = _mm_packus_epi16(m_value, _mm_setzero_si128());
    // Unpack up to s32
    m_value = _mm_unpacklo_epi8(m_value, _mm_setzero_si128());
    m_value = _mm_unpacklo_epi16(m_value, _mm_setzero_si128());
  }

  // This function needs absolute value of color and scale to be 11 bits or less
  inline void scale_add_and_clamp(const rgbaint_t& scale, const rgbaint_t& other)
  {
    // Pack scale into mult a 16 bits
    __m128i tmp1 = _mm_packs_epi32(scale.m_value, _mm_setzero_si128());
    // Shift up by 4
    tmp1 = _mm_slli_epi16(tmp1, 4);
    // Pack color into mult b 16 bit inputs
    m_value = _mm_packs_epi32(m_value, _mm_setzero_si128());
    // Shift up by 4
    m_value = _mm_slli_epi16(m_value, 4);
    // Do the 16 bit multiply, bottom 64 bits will contain 16 bit truncated results
    m_value = _mm_mulhi_epi16(m_value, tmp1);
    // Unpack up to s32, putting the 16 bit value at the top so the sign bit is set by the 16 bit result
    m_value = _mm_unpacklo_epi16(_mm_setzero_si128(), m_value);
    // Arithmetic shift down the 16 bit value to the lower 16 bits
    sra_imm(16);
    add(other);
    clamp_to_uint8();
  }

  // This function needs absolute value of color and scale to be 11 bits or less
  inline void scale2_add_and_clamp(const rgbaint_t& scale, const rgbaint_t& other, const rgbaint_t& scale2)
  {
    // Pack both scale values into mult a 16 bits
    __m128i tmp1 = _mm_packs_epi32(scale.m_value, scale2.m_value);
    // Shift up by 4
    tmp1 = _mm_slli_epi16(tmp1, 4);
    // Pack both color values into mult b 16 bit inputs
    m_value = _mm_packs_epi32(m_value, other.m_value);
    // Shift up by 4
    m_value = _mm_slli_epi16(m_value, 4);
    // Do the 16 bit multiply, top and bottom 64 bits will contain 16 bit truncated results
    tmp1 = _mm_mulhi_epi16(m_value, tmp1);
    // Unpack up to s32, putting the 16 bit value at the top so the sign bit is set by the 16 bit result
    m_value = _mm_unpacklo_epi16(_mm_setzero_si128(), tmp1);
    tmp1 = _mm_unpackhi_epi16(_mm_setzero_si128(), tmp1);
    // Arithmetic shift down the 16 bit value to the lower 16 bits
    sra_imm(16);
    tmp1 = _mm_srai_epi32(tmp1, 16);
    // Add the results
    m_value = _mm_add_epi32(m_value, tmp1);
    clamp_to_uint8();
  }

  void cmpeq(const rgbaint_t& value) { m_value = _mm_cmpeq_epi32(m_value, value.m_value); }
  void cmpgt(const rgbaint_t& value) { m_value = _mm_cmpgt_epi32(m_value, value.m_value); }
  void cmplt(const rgbaint_t& value) { m_value = _mm_cmplt_epi32(m_value, value.m_value); }

  void cmpeq_imm(s32 value) { m_value = _mm_cmpeq_epi32(m_value, _mm_set1_epi32(value)); }
  void cmpgt_imm(s32 value) { m_value = _mm_cmpgt_epi32(m_value, _mm_set1_epi32(value)); }
  void cmplt_imm(s32 value) { m_value = _mm_cmplt_epi32(m_value, _mm_set1_epi32(value)); }

  void cmpeq_imm_rgba(s32 a, s32 r, s32 g, s32 b) { m_value = _mm_cmpeq_epi32(m_value, _mm_set_epi32(a, r, g, b)); }
  void cmpgt_imm_rgba(s32 a, s32 r, s32 g, s32 b) { m_value = _mm_cmpgt_epi32(m_value, _mm_set_epi32(a, r, g, b)); }
  void cmplt_imm_rgba(s32 a, s32 r, s32 g, s32 b) { m_value = _mm_cmplt_epi32(m_value, _mm_set_epi32(a, r, g, b)); }

  inline rgbaint_t& operator+=(const rgbaint_t& other)
  {
    m_value = _mm_add_epi32(m_value, other.m_value);
    return *this;
  }

  inline rgbaint_t& operator+=(const s32 other)
  {
    m_value = _mm_add_epi32(m_value, _mm_set1_epi32(other));
    return *this;
  }

  inline rgbaint_t& operator-=(const rgbaint_t& other)
  {
    m_value = _mm_sub_epi32(m_value, other.m_value);
    return *this;
  }

  inline rgbaint_t& operator*=(const rgbaint_t& other)
  {
    m_value =
      _mm_unpacklo_epi32(_mm_shuffle_epi32(_mm_mul_epu32(m_value, other.m_value), _MM_SHUFFLE(0, 0, 2, 0)),
                         _mm_shuffle_epi32(_mm_mul_epu32(_mm_srli_si128(m_value, 4), _mm_srli_si128(other.m_value, 4)),
                                           _MM_SHUFFLE(0, 0, 2, 0)));
    return *this;
  }

  inline rgbaint_t& operator*=(const s32 other)
  {
    const __m128i immv = _mm_set1_epi32(other);
    m_value = _mm_unpacklo_epi32(
      _mm_shuffle_epi32(_mm_mul_epu32(m_value, immv), _MM_SHUFFLE(0, 0, 2, 0)),
      _mm_shuffle_epi32(_mm_mul_epu32(_mm_srli_si128(m_value, 4), _mm_srli_si128(immv, 4)), _MM_SHUFFLE(0, 0, 2, 0)));
    return *this;
  }

  inline rgbaint_t& operator>>=(const s32 shift)
  {
    m_value = _mm_srai_epi32(m_value, shift);
    return *this;
  }

  inline void merge_alpha16(const rgbaint_t& alpha)
  {
    m_value = _mm_insert_epi16(m_value, _mm_extract_epi16(alpha.m_value, 6), 6);
  }

  inline void merge_alpha(const rgbaint_t& alpha)
  {
#ifdef __SSE4_1__
    m_value = _mm_insert_epi32(m_value, _mm_extract_epi32(alpha.m_value, 3), 3);
#else
    m_value = _mm_insert_epi16(m_value, _mm_extract_epi16(alpha.m_value, 7), 7);
    m_value = _mm_insert_epi16(m_value, _mm_extract_epi16(alpha.m_value, 6), 6);
#endif
  }

  static u32 bilinear_filter(u32 rgb00, u32 rgb01, u32 rgb10, u32 rgb11, u8 u, u8 v)
  {
    __m128i color00 = _mm_cvtsi32_si128(rgb00);
    __m128i color01 = _mm_cvtsi32_si128(rgb01);
    __m128i color10 = _mm_cvtsi32_si128(rgb10);
    __m128i color11 = _mm_cvtsi32_si128(rgb11);

    /* interleave color01 and color00 at the byte level */
    color01 = _mm_unpacklo_epi8(color01, color00);
    color11 = _mm_unpacklo_epi8(color11, color10);
    color01 = _mm_unpacklo_epi8(color01, _mm_setzero_si128());
    color11 = _mm_unpacklo_epi8(color11, _mm_setzero_si128());
    color01 = _mm_madd_epi16(color01, scale_factor(u));
    color11 = _mm_madd_epi16(color11, scale_factor(u));
    color01 = _mm_slli_epi32(color01, 15);
    color11 = _mm_srli_epi32(color11, 1);
    color01 = _mm_max_epi16(color01, color11);
    color01 = _mm_madd_epi16(color01, scale_factor(v));
    color01 = _mm_srli_epi32(color01, 15);
    color01 = _mm_packs_epi32(color01, _mm_setzero_si128());
    color01 = _mm_packus_epi16(color01, _mm_setzero_si128());
    return _mm_cvtsi128_si32(color01);
  }

  void bilinear_filter_rgbaint(u32 rgb00, u32 rgb01, u32 rgb10, u32 rgb11, u8 u, u8 v)
  {
    __m128i color00 = _mm_cvtsi32_si128(rgb00);
    __m128i color01 = _mm_cvtsi32_si128(rgb01);
    __m128i color10 = _mm_cvtsi32_si128(rgb10);
    __m128i color11 = _mm_cvtsi32_si128(rgb11);

    /* interleave color01 and color00 at the byte level */
    color01 = _mm_unpacklo_epi8(color01, color00);
    color11 = _mm_unpacklo_epi8(color11, color10);
    color01 = _mm_unpacklo_epi8(color01, _mm_setzero_si128());
    color11 = _mm_unpacklo_epi8(color11, _mm_setzero_si128());
    color01 = _mm_madd_epi16(color01, scale_factor(u));
    color11 = _mm_madd_epi16(color11, scale_factor(u));
    color01 = _mm_slli_epi32(color01, 15);
    color11 = _mm_srli_epi32(color11, 1);
    color01 = _mm_max_epi16(color01, color11);
    color01 = _mm_madd_epi16(color01, scale_factor(v));
    m_value = _mm_srli_epi32(color01, 15);
  }

protected:
  struct _statics
  {
    __m128 dummy_for_alignment;
    u16 alpha_mask[8];
    u16 red_mask[8];
    u16 green_mask[8];
    u16 blue_mask[8];
    s16 scale_table[256][8];
  };

  static __m128i alpha_mask() { return *(__m128i*)&statics.alpha_mask[0]; }
  static __m128i red_mask() { return *(__m128i*)&statics.red_mask[0]; }
  static __m128i green_mask() { return *(__m128i*)&statics.green_mask[0]; }
  static __m128i blue_mask() { return *(__m128i*)&statics.blue_mask[0]; }
  static __m128i scale_factor(u8 index) { return *(__m128i*)&statics.scale_table[index][0]; }

  __m128i m_value;

  static const _statics statics;
};
