3
\$\begingroup\$

Does what it says in the title. I just finished this and wanted to share with someone.

Looking for possible optimizations, bugs (most of it is tested to work) or any constructive criticism.

template<typename P>
struct alignas(P) [[gnu::packed, gnu::may_alias]] pixel : public P
{
    template<typename> friend struct pixel;

    using T = std::conditional_t<std::is_integral_v<typename P::T>, unsigned, typename P::T>;

    template<std::size_t N, typename VT>
    using V [[gnu::vector_size(N * sizeof(VT)), gnu::may_alias]] = VT;

    constexpr pixel() noexcept = default;
    template<typename U = P, typename PT = typename U::T, std::enable_if_t<pixel<U>::has_alpha(), bool> = { }>
    constexpr pixel(T cr, T cg, T cb, T ca) noexcept : P { static_cast<PT>(cb), static_cast<PT>(cg), static_cast<PT>(cr), static_cast<PT>(ca) } { }
    template<typename U = P, typename PT = typename U::T, std::enable_if_t<pixel<U>::has_alpha(), bool> = { } >
    constexpr pixel(T cr, T cg, T cb) noexcept : P { static_cast<PT>(cb), static_cast<PT>(cg), static_cast<PT>(cr), U::ax } { }
    template<typename U = P, typename PT = typename U::T, std::enable_if_t<not pixel<U>::has_alpha(), bool> = { } >
    constexpr pixel(T cr, T cg, T cb, T) noexcept : P { static_cast<PT>(cb), static_cast<PT>(cg), static_cast<PT>(cr) } { }
    template<typename U = P, typename PT = typename U::T, std::enable_if_t<not pixel<U>::has_alpha(), bool> = { } >
    constexpr pixel(T cr, T cg, T cb) noexcept : P { static_cast<PT>(cb), static_cast<PT>(cg), static_cast<PT>(cr) } { }

    constexpr pixel(const pixel& p) noexcept = default;
    constexpr pixel(pixel&& p) noexcept = default;
    constexpr pixel& operator=(const pixel&) noexcept = default;
    constexpr pixel& operator=(pixel&&) noexcept = default;

    template <typename U> constexpr operator pixel<U>() const noexcept { return cast_to<U>(); }

    static constexpr bool has_alpha() { return P::ax > 0; }

    template<typename U>
    constexpr pixel& blend(const pixel<U>& other)
    {
        if constexpr (not pixel<U>::has_alpha())
        {
            *this = other.template cast_to<P>();
        }
        else if constexpr (sse and (std::is_floating_point_v<typename P::T> or std::is_floating_point_v<typename U::T>))
        {
            *this = m128(m128_blend<U>(m128(), other.m128()));
            if constexpr (std::is_integral_v<typename U::T>) _mm_empty();
        }
        else if constexpr (mmx and std::is_integral_v<typename P::T> and std::is_integral_v<typename U::T>)
        {
            *this = m64(m64_blend<U>(m64(), other.m64()));
        }
        else
        {
            using VT = std::conditional_t<std::is_floating_point_v<typename P::T> or std::is_floating_point_v<typename U::T>, float, std::uint32_t>;
            V<4, VT> src = other.template vector<VT>();
            V<4, VT> dst = vector<VT>();
            *this = vector<VT>(vector_blend<U, VT>(dst, src));
        }
        return *this;
    }

    template<typename U>
    constexpr pixel& blend_straight(const pixel<U>& other)
    {
        if constexpr (not pixel<U>::has_alpha())
        {
            *this = other.template cast_to<P>();
        }
        else if constexpr (sse and (std::is_floating_point_v<typename P::T> or std::is_floating_point_v<typename U::T>))
        {
            *this = m128(m128_blend<U>(m128_premul(m128()), m128_premul(other.m128())));
            if constexpr (std::is_integral_v<typename U::T>) _mm_empty();
        }
        else if constexpr (mmx and std::is_integral_v<typename P::T> and std::is_integral_v<typename U::T>)
        {
            *this = m64(m64_blend<U>(m64_premul(m64()), m64_premul(other.m64())));
        }
        else
        {
            using VT = std::conditional_t<std::is_floating_point_v<typename P::T> or std::is_floating_point_v<typename U::T>, float, std::uint32_t>;
            V<4, VT> src = vector_premul<VT>(other.template vector<VT>());
            V<4, VT> dst = vector_premul<VT>(vector<VT>());
            *this = vector<VT>(vector_blend<U, VT>(dst, src));
        }
        return *this;
    }

    constexpr pixel& premultiply_alpha()
    {
        if constexpr (not has_alpha()) return *this;
        if constexpr (sse and std::is_floating_point_v<typename P::T>) *this = m128(m128_premul(m128()));
        else if constexpr (mmx and not std::is_floating_point_v<typename P::T>) *this = m64(m64_premul(m64()));
        else
        {
            using VT = std::conditional_t<std::is_floating_point_v<typename P::T>, float, std::uint8_t>;
            *this = vector<VT>(vector_premul<VT>(vector<VT>()));
        }
        return *this;
    }

private:
    template <typename U>
    constexpr pixel<U> cast_to() const
    {
        constexpr bool not_constexpr = true;// not is_constexpr(this->b);
        if constexpr (not_constexpr and sse and (std::is_floating_point_v<typename P::T> or std::is_floating_point_v<typename U::T>))
        {
            auto result = pixel<U>::m128(m128_cast_to<U>(m128()));
            if constexpr (std::is_integral_v<typename P::T>) _mm_empty();
            return result;
        }
        else if constexpr (not_constexpr and mmx and (sse or (std::is_integral_v<typename P::T> and std::is_integral_v<typename U::T>)))
        {
            return pixel<U>::m64(m64_cast_to<U>(m64()));
        }
        else
        {
            using VT = std::conditional_t<std::is_floating_point_v<typename P::T> or std::is_floating_point_v<typename U::T>, float, std::uint32_t>;
            return pixel<U>::template vector<VT>(vector_cast_to<U, VT>(vector<VT>()));
        }
    }

    static constexpr pixel m64(auto value) noexcept // V4HI
    {
        static_assert(not std::is_floating_point_v<typename P::T>);
        auto v = _mm_packs_pu16(value, _mm_setzero_si64());
        if constexpr (byte_aligned())
        {
            auto v2 = _mm_cvtsi64_si32(v);
            pixel result { *reinterpret_cast<pixel*>(&v2) };
            _mm_empty();
            return result;
        }
        else
        {
            auto v2 = reinterpret_cast<V<8, byte>&>(v);
            pixel result { v2[2], v2[1], v2[0], v2[3] };
            _mm_empty();
            return result;
        }
    }

    constexpr __m64 m64() const noexcept    // V4HI
    {
        static_assert(not std::is_floating_point_v<typename P::T>);
        __m64 v;
        if constexpr (byte_aligned()) v = _mm_cvtsi32_si64(*reinterpret_cast<const int*>(this));
        else if constexpr (has_alpha()) v = _mm_setr_pi8(this->b, this->g, this->r, this->a, 0, 0, 0, 0);
        else v = _mm_setr_pi8(this->b, this->g, this->r, 0, 0, 0, 0, 0);
        auto r = _mm_unpacklo_pi8(v, _mm_setzero_si64());
        return r;
    }

    static constexpr pixel m128(__m128 value) noexcept  // V4SF
    {
        if constexpr (std::is_floating_point_v<typename P::T>) return *reinterpret_cast<pixel*>(&value);
        else return m64(_mm_cvtps_pi16(value));
    }

    constexpr __m128 m128() const noexcept  // V4SF
    {
        if constexpr (std::is_floating_point_v<typename P::T>) return *reinterpret_cast<const __m128*>(this);
        else return _mm_cvtpu16_ps(m64());
    }

    template<typename VT = std::uint16_t>
    static constexpr pixel vector(V<4, VT> src) noexcept
    {
        if constexpr ((std::is_same_v<VT, float> and std::is_same_v<T, float>) or (sizeof(VT) == 1 and byte_aligned()))
        {
            return *reinterpret_cast<pixel*>(&src);
        }
        return pixel { static_cast<T>(src[2]), static_cast<T>(src[1]), static_cast<T>(src[0]), static_cast<T>(src[3]) };
    }

    template<typename VT = std::uint16_t>
    constexpr V<4, VT> vector() const noexcept
    {
        V<4, VT> src;
        if constexpr ((std::is_same_v<VT, float> and std::is_same_v<T, float>) or (sizeof(VT) == 1 and byte_aligned()))
        {
            src = *reinterpret_cast<const V<4, VT>*>(this);
            if constexpr (has_alpha()) src = V<4, VT> { src[0], src[1], src[2], 1 };
        }
        else if constexpr (has_alpha()) src = V<4, VT> { static_cast<VT>(this->b), static_cast<VT>(this->g), static_cast<VT>(this->r), static_cast<VT>(this->a), };
        else src = V<4, VT> { static_cast<VT>(this->b), static_cast<VT>(this->g), static_cast<VT>(this->r), 1 };
        return src;
    }

    template <typename U>
    static constexpr __m128 m128_cast_to(__m128 src) noexcept
    {
        constexpr __m128 cast = reinterpret_cast<__m128>(pixel<U>::template vector_max<float>(P::ax) * (1.0f / vector_max<float>(U::ax or 1.0f)));
        src = _mm_mul_ps(src, cast);
        if constexpr (pixel<U>::has_alpha() and not has_alpha()) src = _mm_setr_ps(src[0], src[1], src[2], static_cast<float>(U::ax));
        return src;
    }

    template <typename U>
    static constexpr __m64 m64_cast_to(__m64 src) noexcept
    {
        constexpr auto mullo = reinterpret_cast<__m64>(pixel<U>::template vector_max<std::uint16_t>());
        constexpr auto mulhi = reinterpret_cast<__m64>(vector_max_reciprocal<17, std::uint16_t, 15>());
        auto vector_max_contains = [](std::uint16_t value)
        {
            auto v = vector_max<std::uint16_t>();
            for (auto i = 0; i < 4; ++i) if (v[i] == value) return true;
            return false;
        };

        src = _mm_mullo_pi16(src, mullo);
        auto dst = _mm_mulhi_pi16(src, mulhi);
        dst = _mm_srli_pi16(_mm_adds_pu8(dst, _mm_set1_pi16(1)), 1);

        if constexpr (vector_max_contains(1))
        {
            constexpr auto is1 = reinterpret_cast<__m64>(vector_max<std::uint16_t>() == 1);
            auto v1 = _mm_and_si64(src, is1);
            dst = _mm_or_si64(_mm_andnot_si64(is1, dst), v1);
        }
        if constexpr (vector_max_contains(3))
        {
            constexpr auto mulhi3 = reinterpret_cast<__m64>(vector_max_reciprocal<16, std::uint16_t, 15>());
            constexpr auto is3 = reinterpret_cast<__m64>(vector_max<std::uint16_t>() == 3);
            auto v3 = _mm_mulhi_pi16(_mm_and_si64(src, is3), mulhi3);
            dst = _mm_or_si64(_mm_andnot_si64(is3, dst), v3);
        }
        if constexpr (pixel<U>::has_alpha() and not has_alpha()) dst = _mm_insert_pi16(dst, U::ax, 3);
        return dst;
    }

    template <typename U, typename VT>
    static constexpr V<4, VT> vector_cast_to(V<4, VT> src) noexcept
    {
        if constexpr (std::is_floating_point_v<VT>)
        {
            src *= pixel<U>::template vector_max<VT>(P::ax) * (1.0f / vector_max<VT>(U::ax or 1.0f));
        }
        else
        {
            constexpr auto rbits = (sizeof(VT) - 1) * 8;
            src *= pixel<U>::template vector_max<VT>(P::ax | 1);
            src *= vector_max_reciprocal<rbits, VT>(U::ax | 1);
            src += 1 << (rbits - 1);
            src >>= rbits;
        }
        if constexpr (has_alpha()) return src;
        else return V<4, VT> { src[0], src[1], src[2], static_cast<VT>(U::ax) };
    }

    static constexpr __m128 m128_premul(__m128 src) noexcept
    {
        if constexpr (not has_alpha()) return src;
        constexpr auto ax = reinterpret_cast<__m128>(1.0f / V<4, float> { P::ax, P::ax, P::ax, 1 });
        auto srca = _mm_setr_ps(src[3], src[3], src[3], 1);
        src = _mm_mul_ps(src, srca);
        src = _mm_mul_ps(src, ax);
        return src;
    }

    static constexpr __m64 m64_premul(__m64 src) noexcept
    {
        if constexpr (not has_alpha()) return src;
        auto a = _mm_shuffle_pi16(src, shuffle_mask(3, 3, 3, 3));
        src = _mm_mullo_pi16(src, a);
        if constexpr (P::ax == 3)
        {
            constexpr auto ax = vector_reciprocal<16, std::uint16_t, 15>(P::ax);
            src = _mm_mulhi_pi16(src, reinterpret_cast<__m64>(ax));
        }
        else if constexpr (P::ax > 3)
        {
            constexpr auto ax = vector_reciprocal<17, std::uint16_t, 15>(P::ax);
            src = _mm_mulhi_pi16(src, reinterpret_cast<__m64>(ax));
            src = _mm_srli_pi16(_mm_adds_pu8(src, _mm_set1_pi16(1)), 1);
        }
        src = _mm_insert_pi16(src, a[0], 3);
        return src;
    }

    template <typename VT>
    static constexpr V<4, VT> vector_premul(V<4, VT> src) noexcept
    {
        if constexpr (not has_alpha()) return src;
        auto a = V<4, VT> { src[3], src[3], src[3], 1 };
        if constexpr (std::is_floating_point_v<VT>)
        {
            constexpr auto ax = 1.0f / V<4, float> { P::ax, P::ax, P::ax, 1 };
            src *= a * ax;
        }
        else
        {
            constexpr auto rbits = (sizeof(VT) - 1) * 8;
            constexpr auto ax = vector_reciprocal<rbits, VT>(P::ax, P::ax, P::ax, 1);
            src *= a;
            src *= ax;
            src += 1 << (rbits - 1);
            src >>= rbits;
        }
        return src;
    }

    template <typename U>
    constexpr __m128 m128_blend(__m128 dst, __m128 src)
    {
        constexpr auto ax = reinterpret_cast<__m128>(1.0f / V<4, float> { U::ax, U::ax, U::ax, U::ax });
        auto a = _mm_sub_ps(_mm_set1_ps(U::ax), _mm_set1_ps(src[3]));

        if constexpr (not std::is_same_v<P, U>) src = pixel<U>::template m128_cast_to<P>(src);
        dst = _mm_mul_ps(dst, a);
        dst = _mm_mul_ps(dst, ax);
        dst = _mm_add_ps(dst, src);
        return dst;
    }

    template <typename U>
    constexpr __m64 m64_blend(__m64 dst, __m64 src)
    {
        //auto a = _mm_sub_pi16(_mm_set1_pi16(U::ax), _mm_shuffle_pi16(src, shuffle_mask(3, 3, 3, 3)));
        auto a = _mm_set1_pi16(U::ax - reinterpret_cast<V<4, std::uint16_t>>(src)[3]);

        if constexpr (not std::is_same_v<P, U>) src = pixel<U>::template m64_cast_to<P>(src);
        dst = _mm_mullo_pi16(dst, a);
        if constexpr (U::ax == 3)
        {
            constexpr auto ax = vector_reciprocal<16, std::uint16_t, 15>(U::ax);
            dst = _mm_mulhi_pi16(dst, reinterpret_cast<__m64>(ax));
        }
        else if constexpr (U::ax != 1)
        {
            constexpr auto ax = vector_reciprocal<17, std::uint16_t, 15>(U::ax);
            dst = _mm_mulhi_pi16(dst, reinterpret_cast<__m64>(ax));
            dst = _mm_srli_pi16(_mm_adds_pu8(dst, _mm_set1_pi16(1)), 1);
        }
        dst = _mm_adds_pu16(dst, src);
        return dst;
    }

    template <typename U, typename VT>
    static constexpr V<4, VT> vector_blend(V<4, VT> dst, V<4, VT> src) noexcept
    {
        if constexpr (not std::is_same_v<P, U>) src = pixel<U>::template vector_cast_to<P, VT>(src);
        if constexpr (std::is_floating_point_v<VT>)
        {
            constexpr auto ax = 1.0f / U::ax;
            dst *= static_cast<VT>(U::ax - src[3]) * ax;
            dst += src;
        }
        else
        {
            constexpr auto rbits = (sizeof(VT) - 1) * 8;
            constexpr auto ax = vector_reciprocal<rbits, VT>(U::ax);
            dst *= static_cast<VT>(U::ax - src[3]);
            dst *= ax;
            dst += 1 << (rbits - 1);
            dst >>= rbits;
            dst += src;
        }
        return dst;
    }

    template<std::size_t bits, typename VT = std::uint16_t, std::size_t maxbits = bits>
    static constexpr auto vector_reciprocal(VT v0, VT v1, VT v2, VT v3) noexcept
    {
        auto r = [](VT v) -> VT { return std::min(((1ul << bits) + v - 1) / v, (1ul << maxbits) - 1); };
        return V<4, VT> { r(v0), r(v1), r(v2), r(v3)};
    }

    template<std::size_t bits, typename VT = std::uint16_t, std::size_t maxbits = bits>
    static constexpr auto vector_reciprocal(VT v0) noexcept
    {
        return vector_reciprocal<bits, VT, maxbits>(v0, v0, v0, v0);
    }

    template<typename VT = float>
    static constexpr auto vector_max(VT noalpha = 1) noexcept
    {
        return V<4, VT> { P::bx, P::gx, P::rx, static_cast<VT>(has_alpha() ? P::ax : noalpha) };
    }

    template<std::size_t bits, typename VT = std::uint16_t, std::size_t maxbits = bits>
    static constexpr auto vector_max_reciprocal(VT noalpha = 1) noexcept
    {
        return vector_reciprocal<bits, VT, maxbits>(P::bx, P::gx, P::rx, static_cast<VT>(has_alpha() ? P::ax : noalpha));
    }

    template<typename T> constexpr bool is_constexpr(T value) { return __builtin_constant_p(value); }
    static constexpr auto shuffle_mask(int v0, int v1, int v2, int v3) noexcept { return (v0 & 3) | ((v1 & 3) << 2) | ((v2 & 3) << 4) | ((v3 & 3) << 6); }
    static constexpr bool byte_aligned() noexcept { return P::byte_aligned; }
};

struct alignas(0x10) bgra_ffff
{
    using T = float;
    T b, g, r, a;

    static constexpr T rx = 1.0f;
    static constexpr T gx = 1.0f;
    static constexpr T bx = 1.0f;
    static constexpr T ax = 1.0f;
    static constexpr bool byte_aligned = false;
};

struct alignas(0x10) bgra_fff0
{
    using T = float;
    T b, g, r;
    unsigned : sizeof(float);

    static constexpr T rx = 1.0f;
    static constexpr T gx = 1.0f;
    static constexpr T bx = 1.0f;
    static constexpr T ax = 0.0f;
    static constexpr bool byte_aligned = false;
};

struct alignas(4) bgra_8888
{
    using T = std::uint8_t;
    T b, g, r, a;

    static constexpr T rx = 255;
    static constexpr T gx = 255;
    static constexpr T bx = 255;
    static constexpr T ax = 255;
    static constexpr bool byte_aligned = true;
};

struct [[gnu::packed]] alignas(4) bgra_8880
{
    using T = std::uint8_t;
    T b, g, r;
    T : 8;

    static constexpr T rx = 255;
    static constexpr T gx = 255;
    static constexpr T bx = 255;
    static constexpr T ax = 0;
    static constexpr bool byte_aligned = true;
};

struct [[gnu::packed]] bgr_8880
{
    using T = std::uint8_t;
    T b, g, r;

    static constexpr T rx = 255;
    static constexpr T gx = 255;
    static constexpr T bx = 255;
    static constexpr T ax = 0;
    static constexpr bool byte_aligned = true;
};

struct [[gnu::packed]] bgra_6668
{
    using T = unsigned;
    T b : 6, : 2;
    T g : 6, : 2;
    T r : 6, : 2;
    T a : 8;

    constexpr bgra_6668(T vb, T vg, T vr, T va) noexcept : b(vb), g(vg), r(vr), a(va) { }

    static constexpr T rx = 63;
    static constexpr T gx = 63;
    static constexpr T bx = 63;
    static constexpr T ax = 255;
    static constexpr bool byte_aligned = true;
};

struct alignas(2) [[gnu::packed]] bgr_5650
{
    using T = unsigned;
    T b : 5;
    T g : 6;
    T r : 5;

    static constexpr T rx = 31;
    static constexpr T gx = 63;
    static constexpr T bx = 31;
    static constexpr T ax = 0;
    static constexpr bool byte_aligned = false;
};

struct alignas(2) [[gnu::packed]] bgra_5551
{
    using T = unsigned;
    T b : 5;
    T g : 5;
    T r : 5;
    T a : 1;

    static constexpr T rx = 31;
    static constexpr T gx = 31;
    static constexpr T bx = 31;
    static constexpr T ax = 1;
    static constexpr bool byte_aligned = false;
};

struct alignas(2) [[gnu::packed]] bgra_5550
{
    using T = unsigned;
    T b : 5;
    T g : 5;
    T r : 5;
    T : 1;

    static constexpr T rx = 31;
    static constexpr T gx = 31;
    static constexpr T bx = 31;
    static constexpr T ax = 0;
    static constexpr bool byte_aligned = false;
};

struct alignas(2)[[gnu::packed]] bgra_4444
{
    using T = unsigned;
    T b : 4;
    T g : 4;
    T r : 4;
    T a : 4;

    static constexpr T rx = 15;
    static constexpr T gx = 15;
    static constexpr T bx = 15;
    static constexpr T ax = 15;
    static constexpr bool byte_aligned = false;
};

struct [[gnu::packed]] bgr_2330
{
    using T = unsigned;
    T b : 2;
    T g : 3;
    T r : 3;

    static constexpr T rx = 7;
    static constexpr T gx = 7;
    static constexpr T bx = 3;
    static constexpr T ax = 0;
    static constexpr bool byte_aligned = false;
};

struct [[gnu::packed]] bgra_2321
{
    using T = unsigned;
    T b : 2;
    T g : 3;
    T r : 2;
    T a : 1;

    static constexpr T rx = 3;
    static constexpr T gx = 7;
    static constexpr T bx = 3;
    static constexpr T ax = 1;
    static constexpr bool byte_aligned = false;
};

struct[[gnu::packed]] bgra_2222
{
    using T = unsigned;
    T b : 2;
    T g : 2;
    T r : 2;
    T a : 2;

    static constexpr T rx = 3;
    static constexpr T gx = 3;
    static constexpr T bx = 3;
    static constexpr T ax = 3;
    static constexpr bool byte_aligned = false;
};

using pxf    = pixel<bgra_ffff>;     // floating-point for use with SSE
using pxfn   = pixel<bgra_fff0>;     // floating-point, no alpha
using px32a  = pixel<bgra_8888>;     // 24-bit, 8-bit alpha channel
using px32n  = pixel<bgra_8880>;     // 24-bit, no alpha, 4 bytes wide
using px24   = pixel<bgr_8880>;      // 24-bit, 3 bytes wide
using px16   = pixel<bgr_5650>;      // 16-bit, typical 5:6:5 format
using px16a  = pixel<bgra_5551>;     // 15-bit with 1-bit alpha
using px16n  = pixel<bgra_5550>;     // 15-bit, no alpha, equal 5:5:5 format
using px16aa = pixel<bgra_4444>;     // 12-bit, 4-bit alpha, equal 4:4:4 format
using px8aa  = pixel<bgra_2222>;     // 6-bit 2:2:2, 2-bit alpha
using px8a   = pixel<bgra_2321>;     // 7-bit 2:3:2, 1-bit alpha
using px8n   = pixel<bgr_2330>;      // 8-bit 3:3:2, no alpha
using pxvga  = pixel<bgra_6668>;     // VGA DAC palette format

static_assert(sizeof(pxf   ) == 16);
static_assert(sizeof(pxfn  ) == 16);
static_assert(sizeof(px32a ) ==  4);
static_assert(sizeof(px32n ) ==  4);
static_assert(sizeof(px24  ) ==  3);
static_assert(sizeof(px16  ) ==  2);
static_assert(sizeof(px16aa) ==  2);
static_assert(sizeof(px16a ) ==  2);
static_assert(sizeof(px16n ) ==  2);
static_assert(sizeof(px8aa ) ==  1);
static_assert(sizeof(px8a  ) ==  1);
static_assert(sizeof(px8n  ) ==  1);
static_assert(sizeof(pxvga ) ==  4);

inline auto generate_px8n_palette()
{
    std::vector<px32n> result;
    result.reserve(256);
    for (auto i = 0; i < 256; ++i)
        result.emplace_back(reinterpret_cast<px8n&>(i));
    return result;
}
\$\endgroup\$

1 Answer 1

5
\$\begingroup\$

The code seems to get more and more questionable as we read downward. Starting at the bottom:

inline auto generate_px8n_palette()
{
    std::vector<px32n> result;
    result.reserve(256);
    for (auto i = 0; i < 256; ++i)
        result.emplace_back(reinterpret_cast<px8n&>(i));
    return result;
}
  • Writing auto instead of explicitly std::vector<px32n> doesn't save you much of anything.

  • Writing auto i = 0 instead of int i = 0 actually loses you something (in both character count and clarity).

  • Using reinterpret_cast<px8n&>(i) seems completely at odds with the rest of this code. Why not give px8n a proper constructor from int, thus eliminating the undefined behavior and making your code portable to big-endian systems at the same time?


struct[[gnu::packed]] bgra_2222
{
    using T = unsigned;
    T b : 2;
    T g : 2;
    T r : 2;
    T a : 2;

    static constexpr T rx = 3;
    static constexpr T gx = 3;
    static constexpr T bx = 3;
    static constexpr T ax = 3;
    static constexpr bool byte_aligned = false;
};
  • I'm not a fan of your whitespace style in struct[[gnu::packed]].

  • Anytime you're stamping out a bunch of copies of the same code over and over with only minor differences, you should try to use a template. I bet you could reduce these 150 lines of code to about 40 by using a template and a bunch of typedefs like using bgra_2222 = bgra<2,2,2,2,false>; and so on.


struct [[gnu::packed]] bgr_8880

I don't understand why this struct containing only uint8_t fields needs to be [[gnu::packed]] at all.


constexpr auto ax = vector_reciprocal<rbits, VT>(P::ax, P::ax, P::ax, 1);
src *= a;
src *= ax;

Here, I suggest that one_over_ax would be a much more appropriate name than ax. (Or, you could use a more verbose name such as P::max_alpha in the first place. Either way, try to eliminate the misleading parallelism between P::ax and ax.)


static constexpr pixel m64(auto value) noexcept // V4HI

This use of auto is not valid C++17 (nor is it currently expected to be valid C++20). Shouldn't GCC have given you a warning about this? Compile with at least -std=c++17 -W -Wall, to which some add (but others do not) -Wextra -pedantic.


constexpr bool not_constexpr = true;// not is_constexpr(this->b);
if constexpr (not_constexpr and sse and (std::is_floating_point_v<typename P::T> or std::is_floating_point_v<typename U::T>))
    // ...
else if constexpr (not_constexpr and mmx and (sse or (std::is_integral_v<typename P::T> and std::is_integral_v<typename U::T>)))

Two things:

  • What is the purpose of not_constexpr? From the name, I might think that it was intended as a clever way to force the if to be evaluated at runtime not compile-time, except for that not_constexpr is explicitly marked constexpr (so the compiler definitely knows its value at compile-time), and that the entire test is enclosed in if constexpr which means it must be evaluated at compile-time.

  • As Linus once said, -EEXPRESSIONTOOCOMPLICATEDTOLIVE.


*this = m128(m128_blend<U>(m128(), other.m128()));
if constexpr (std::is_integral_v<typename U::T>) _mm_empty();

IIUC, _mm_empty() is the way to signal to the processor that you're done doing MMX things for a little while. I don't really understand why or when you'd need to write it explicitly in C++ code, but let's assume that you're right that it needs doing here.

Would it make more sense to express it as an RAII object that is created before you start doing MMX things, perhaps passed to each MMX operation as a parameter, and then destroyed automatically at the end of the MMX computation? So in practice your code above might look something like this after RAIIfication:

MMXComputationIf<std::is_integral_v<typename U::T>> guard;
*this = m128(m128_blend<U>(m128(), other.m128()));

template<typename U = P, typename PT = typename U::T, std::enable_if_t<pixel<U>::has_alpha(), bool> = { }>
constexpr pixel(T cr, T cg, T cb, T ca) noexcept : P { static_cast<PT>(cb), static_cast<PT>(cg), static_cast<PT>(cr), static_cast<PT>(ca) } { }
  • All your constructors could productively be marked explicit (to prevent people from accidentally constructing them via unadorned {1,2,3,4}, for example).

  • If I had to pick a short mnemonic name for typename U::T, the name PT would not be my first choice.

  • IIUC, the three template parameters here have three different purposes: one (PT) is just a convenience mnemonic, one is actually being used for SFINAE, and the last (U) is just an implementation detail of the SFINAE. None of them are meant to actually participate in template type deduction. So I think this code is much more confusing than it ought to be.

  • Trivial style nit: class is shorter and thus easier on the eyes than typename.

So I would write it like this:

template<class P_ = pixel, class = std::enable_if_t<P_::has_alpha()>>
constexpr pixel(T cr, T cg, T cb, T ca) noexcept :
    P { cb, cg, cr, ca } {}

This version of course eliminates the static_casts which you were doing to suppress warnings (or errors) resulting from narrowing conversions in the braced initializer-list. If you really really need those narrowing conversions to happen, then you should make up a convenient way to spell static_cast<typename P::T> — which you would already have, except that you have made the supremely confusing decision that pixel<P>::T should be a different type from P::T.

The easy but non-surgical solution would be to reverse that decision: replace your

using T = std::conditional_t<std::is_integral_v<typename P::T>, unsigned, typename P::T>;

with

using T = typename P::T;
using unsigned_or_T = std::conditional_t<std::is_integral_v<T>, unsigned, T>;

and then make the obvious substitions throughout:

template<class P_ = pixel, class = std::enable_if_t<P_::has_alpha()>>
constexpr pixel(unsigned_or_T cr, unsigned_or_T cg, unsigned_or_T cb, unsigned_or_T ca) noexcept :
    P { T(cb), T(cg), T(cr), T(ca) } {}

The more surgical but needlessly-antagonistic-to-your-maintainer solution would be

using T = std::conditional_t<std::is_integral_v<typename P::T>, unsigned, typename P::T>;

static constexpr inline PTcast(T t) noexcept {
    return static_cast<typename P::T>(t);
}

template<class P_ = pixel, class = std::enable_if_t<P_::has_alpha()>>
constexpr pixel(T cr, T cg, T cb, T ca) noexcept :
    P { PTcast(cb), PTcast(cg), PTcast(cr), PTcast(ca) } {}
\$\endgroup\$
9
  • \$\begingroup\$ "Writing auto instead of explicitly..." - That's plain laziness, I agree. \$\endgroup\$ Commented Sep 5, 2018 at 3:47
  • \$\begingroup\$ "I'm not a fan of your whitespace style in struct[[gnu::packed]]." - I absolutely hate that too. Visual studio keeps removing the space. \$\endgroup\$ Commented Sep 5, 2018 at 3:48
  • \$\begingroup\$ "Anytime you're stamping out a bunch of copies of the same code..." - I considered using templates there, but there are a few special cases that need extra padding or alignment. Eventually I ended up copy/pasting all of them as I added more. I think I'll merge the most common ones into a template struct. \$\endgroup\$ Commented Sep 5, 2018 at 3:52
  • \$\begingroup\$ "Here, I suggest that one_over_ax would be a much more appropriate name than ax." - Agreed. That's a leftover from earlier versions where I was dividing by ax directly. \$\endgroup\$ Commented Sep 5, 2018 at 3:53
  • \$\begingroup\$ "This use of auto is not valid C++17" - I know. I use -fconcepts just to suppress that warning... \$\endgroup\$ Commented Sep 5, 2018 at 3:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.