/* enoki/array_generic.h -- Generic array implementation that forwards all operations to the underlying data type (usually without making use of hardware vectorization) Copyright (c) 2019 Wenzel Jakob All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. */ #pragma once #include #include NAMESPACE_BEGIN(nanogui) template struct Array; NAMESPACE_END(nanogui) NAMESPACE_BEGIN(enoki) namespace detail { template using is_constructible = std::bool_constant< std::is_constructible_v && !std::is_same_v, reinterpret_flag>>; template using is_not_reinterpret_flag = std::bool_constant< !std::is_same_v, reinterpret_flag>>; template constexpr bool broadcast = !is_static_array_v || array_size_v != Target::Size || !(array_depth_v == array_depth_v || (array_depth_v < array_depth_v && detail::array_broadcast_outer_v)); template struct is_native { static constexpr bool value = false; }; template constexpr bool is_native_v = is_native::value; /** * \brief The class StaticArrayImpl has several different implementations. * This class specifies which one to use. */ template struct array_config { /// Use SSE/AVX/NEON implementation static constexpr bool use_native_impl = is_native_v; /// Reduce to several recursive operations static constexpr bool use_recursive_impl = !use_native_impl && is_std_type_v && has_vectorization && Size > 3; /// Special case for arrays of enumerations static constexpr bool use_enum_impl = std::is_enum_v; /// Special case for arrays of pointers of classes static constexpr bool use_pointer_impl = std::is_pointer_v && !std::is_arithmetic_v>; /// Catch-all for anything that wasn't matched so far static constexpr bool use_generic_impl = !use_native_impl && !use_recursive_impl && !use_enum_impl && !use_pointer_impl; }; template using has_bitmask = decltype(std::declval().bitmask_()); template constexpr bool has_bitmask_v = is_detected_v; } /// Macro to initialize uninitialized floating point arrays with 1 bits (NaN/-1) in debug mode #if defined(NDEBUG) #define ENOKI_TRIVIAL_CONSTRUCTOR(Value) \ template > = 0> \ ENOKI_INLINE StaticArrayImpl() { } #else #define ENOKI_TRIVIAL_CONSTRUCTOR(Value) \ template > = 0> \ ENOKI_INLINE StaticArrayImpl() \ : StaticArrayImpl(memcpy_cast(int_array_t(-1))) { } \ template && \ std::is_default_constructible_v> = 0> \ ENOKI_INLINE StaticArrayImpl() {} #endif /// SFINAE macro for constructors that convert from another type #define ENOKI_CONVERT(Value) \ template > = 0> \ ENOKI_INLINE StaticArrayImpl( \ const StaticArrayBase &a) /// SFINAE macro for constructors that reinterpret another type #define ENOKI_REINTERPRET(Value) \ template > = 0> \ ENOKI_INLINE StaticArrayImpl( \ const StaticArrayBase &a, \ detail::reinterpret_flag) #define ENOKI_ARRAY_DEFAULTS(Array) \ Array(const Array &) = default; \ Array(Array &&) = default; \ Array &operator=(const Array &) = default; \ Array &operator=(Array &&) = default; /// Import the essentials when declaring an array subclass #define ENOKI_ARRAY_IMPORT_BASIC(Base, Array) \ ENOKI_ARRAY_DEFAULTS(Array) \ using typename Base::Derived; \ using typename Base::Value; \ using typename Base::Scalar; \ using Base::Size; \ using Base::derived; \ /// Import the essentials when declaring an array subclass (+constructor/assignment op) #define ENOKI_ARRAY_IMPORT(Base, Array) \ ENOKI_ARRAY_IMPORT_BASIC(Base, Array) \ using Base::Base; \ using Base::operator=; /// Internal macro for native StaticArrayImpl overloads (SSE, AVX, ..) #define ENOKI_NATIVE_ARRAY(Value_, Size_, Register_) \ using Base = \ StaticArrayBase; \ ENOKI_ARRAY_IMPORT_BASIC(Base, StaticArrayImpl) \ using typename Base::Array1; \ using typename Base::Array2; \ using Base::ActualSize; \ using Ref = const Derived &; \ using Register = Register_; \ static constexpr bool IsNative = true; \ Register m; \ ENOKI_TRIVIAL_CONSTRUCTOR(Value_) \ ENOKI_INLINE StaticArrayImpl(Register value) : m(value) {} \ ENOKI_INLINE StaticArrayImpl(Register value, detail::reinterpret_flag) \ : m(value) { } \ ENOKI_INLINE StaticArrayImpl(bool b, detail::reinterpret_flag) \ : StaticArrayImpl(b ? memcpy_cast(int_array_t(-1)) \ : memcpy_cast(int_array_t(0))) { } \ template > = 0> \ ENOKI_INLINE StaticArrayImpl( \ const StaticArrayBase &a) \ : Base(a) { } \ ENOKI_INLINE StaticArrayImpl &operator=(const Derived &v) { \ m = v.m; \ return *this; \ } \ template ENOKI_INLINE StaticArrayImpl &operator=(const T &v) {\ return operator=(Derived(v)); return *this; \ } \ ENOKI_INLINE Value& raw_coeff_(size_t i) { \ union Data { \ Register value; \ Value data[Size_]; \ }; \ return ((Data *) &m)->data[i]; \ } \ ENOKI_INLINE const Value& raw_coeff_(size_t i) const { \ union Data { \ Register value; \ Value data[Size_]; \ }; \ return ((const Data *) &m)->data[i]; \ } \ ENOKI_INLINE decltype(auto) coeff(size_t i) { \ if constexpr (Derived::IsMask) \ return MaskBit(derived(), i); \ else \ return raw_coeff_(i); \ } \ ENOKI_INLINE decltype(auto) coeff(size_t i) const { \ if constexpr (Derived::IsMask) \ return MaskBit(derived(), i); \ else \ return raw_coeff_(i); \ } \ ENOKI_INLINE bool bit_(size_t i) const { \ return detail::convert_mask(raw_coeff_(i)); \ } \ ENOKI_INLINE void set_bit_(size_t i, bool value) { \ raw_coeff_(i) = reinterpret_array(value); \ } /// Internal macro for native StaticArrayImpl overloads -- 3D special case #define ENOKI_DECLARE_3D_ARRAY(Array) \ ENOKI_ARRAY_DEFAULTS(Array) \ using typename Base::Value; \ using typename Base::Derived; \ using typename Base::Ref; \ using Base::m; \ using Base::coeff; \ static constexpr size_t Size = 3; \ Array() = default; \ ENOKI_INLINE Array(Value v) : Base(v) { } \ ENOKI_INLINE Array(Value f1, Value f2, Value f3) \ : Base(f1, f2, f3, (Value) 0) { } \ ENOKI_INLINE Array(Value f1, Value f2, Value f3, Value f4) \ : Base(f1, f2, f3, f4) { } \ ENOKI_INLINE Array(typename Base::Register r) : Base(r) { } \ ENOKI_INLINE Array(typename Base::Register r, detail::reinterpret_flag) \ : Base(r, detail::reinterpret_flag()) { } \ ENOKI_INLINE Array(bool b, detail::reinterpret_flag) \ : Base(b, detail::reinterpret_flag()) { } \ template \ ENOKI_INLINE Array(const StaticArrayBase &a) \ : Base(a) { } \ template \ ENOKI_INLINE Array(const StaticArrayBase &a, \ detail::reinterpret_flag) \ : Base(a, detail::reinterpret_flag()) { } \ template \ ENOKI_INLINE Array(const StaticArrayBase&a) {\ ENOKI_TRACK_SCALAR("Constructor (conversion, 3D case)"); \ Base::operator=(Derived(Value(a.derived().coeff(0)), \ Value(a.derived().coeff(1)), \ Value(a.derived().coeff(2)))); \ } \ template \ ENOKI_INLINE Array(const StaticArrayBase &a, \ detail::reinterpret_flag) { \ ENOKI_TRACK_SCALAR("Constructor (reinterpreting, 3D case)"); \ Base::operator=( \ Derived(reinterpret_array(a.derived().coeff(0)), \ reinterpret_array(a.derived().coeff(1)), \ reinterpret_array(a.derived().coeff(2)))); \ } \ template Array &operator=(T &&value) { \ return (Array&) Base::operator=(Derived(value)); \ } template struct StaticArrayImpl; template struct StaticArrayImpl< Value_, Size_, IsMask_, Derived_, enable_if_t::use_generic_impl>> : StaticArrayBase, Value_>, Size_, IsMask_, Derived_> { using Base = StaticArrayBase, Value_>, Size_, IsMask_, Derived_>; using typename Base::Derived; using typename Base::Value; using typename Base::Scalar; using typename Base::Array1; using typename Base::Array2; using Base::Size; using Base::derived; using StorageType = std::conditional_t && Size_ != 0, std::reference_wrapper>, std::remove_reference_t>; using Ref = std::remove_reference_t &; using ConstRef = const std::remove_reference_t &; StaticArrayImpl(const StaticArrayImpl &) = default; StaticArrayImpl(StaticArrayImpl &&) = default; /// Trivial constructor ENOKI_TRIVIAL_CONSTRUCTOR(Value) #if defined(_MSC_VER) # pragma warning(push) # pragma warning(disable:4244) // warning C4244: 'argument': conversion from 'int' to 'Value_', possible loss of data # pragma warning(disable:4554) // warning C4554: '>>': check operator precedence for possible error; use parentheses to clarify precedence # pragma warning(disable:4702) // warning C4702: unreachable code #elif defined(__GNUC__) // Don't be so noisy about sign conversion in constructor # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wsign-conversion" # pragma GCC diagnostic ignored "-Wdouble-promotion" # pragma GCC diagnostic ignored "-Wunused-value" #endif template using cast_t = std::conditional_t< std::is_scalar_v || !std::is_same_v, std::decay_t>, expr_t, std::conditional_t, Src, Src &&>>; /// Construct from component values template ...>> = 0> ENOKI_INLINE StaticArrayImpl(Ts&&... ts) : m_data{{ cast_t(ts)... }} { ENOKI_CHKSCALAR("Constructor (component values)"); } /// Construct from a scalar or another array template > = 0> ENOKI_INLINE StaticArrayImpl(T &&value) : StaticArrayImpl(std::forward(value), std::make_index_sequence()) { } template > = 0> ENOKI_INLINE StaticArrayImpl(T &&value, detail::reinterpret_flag) : StaticArrayImpl(std::forward(value), std::make_index_sequence()) { } /// Construct from a scalar or another array (potential optimizations) template > = 0> ENOKI_INLINE StaticArrayImpl(T &&value) { if constexpr (Derived::IsMask) { derived() = Derived(value, detail::reinterpret_flag()); } else if constexpr (is_recursive_array_v && array_depth_v == array_depth_v) { derived() = Derived(Array1(low(value)), Array2(high(value))); } else { assign_(std::forward(value), std::make_index_sequence()); } } /// Reinterpret another array (potential optimizations) template > = 0> ENOKI_INLINE StaticArrayImpl(T&& value, detail::reinterpret_flag) { if constexpr (is_recursive_array_v && array_depth_v == array_depth_v) { derived() = Derived(reinterpret_array(low(value)), reinterpret_array(high(value))); } else { assign_(std::forward(value), detail::reinterpret_flag(), std::make_index_sequence()); } } template ENOKI_INLINE StaticArrayImpl &operator=(T &&value) { assign_(std::forward(value), std::make_index_sequence()); return *this; } StaticArrayImpl& operator=(const StaticArrayImpl& value) { assign_(value, std::make_index_sequence()); return *this; } StaticArrayImpl& operator=(StaticArrayImpl& value) { assign_(value, std::make_index_sequence()); return *this; } StaticArrayImpl& operator=(StaticArrayImpl&& value) { assign_(std::move(value), std::make_index_sequence()); return *this; } /// Construct from sub-arrays template == array_depth_v && array_size_v == Base::Size1 && array_depth_v == array_depth_v && array_size_v == Base::Size2 && Base::Size2 != 0> = 0> StaticArrayImpl(const T1 &a1, const T2 &a2) : StaticArrayImpl(a1, a2, std::make_index_sequence(), std::make_index_sequence()) { } private: template > = 0> ENOKI_INLINE StaticArrayImpl(T&& value, std::index_sequence) : m_data{{ cast_t(value.coeff(Is))... }} { ENOKI_CHKSCALAR("Copy constructor"); } template > = 0, size_t... Is> ENOKI_INLINE StaticArrayImpl(T&& value, std::index_sequence) : m_data{{ (Is, value)... }} { ENOKI_CHKSCALAR("Copy constructor (broadcast)"); } template ENOKI_INLINE StaticArrayImpl(const T1 &a1, const T2 &a2, std::index_sequence, std::index_sequence) : m_data{{ a1.coeff(Index1)..., a2.coeff(Index2)... }} { ENOKI_CHKSCALAR("Copy constructor (from 2 components)"); } template ENOKI_INLINE void assign_(T&& value, std::index_sequence) { if constexpr (std::is_same_v, array_shape_t> && std::is_same_v) { #if defined(ENOKI_X86_F16C) using Value2 = value_t; if constexpr (std::is_same_v) { derived() = float32_array_t(value); return; } else if constexpr (std::is_same_v) { if constexpr (Size == 4) { long long result = detail::mm_cvtsi128_si64(_mm_cvtps_ph( value.derived().m, _MM_FROUND_CUR_DIRECTION)); memcpy(m_data.data(), &result, sizeof(long long)); return; } else if constexpr (Size == 8) { __m128i result = _mm256_cvtps_ph(value.derived().m, _MM_FROUND_CUR_DIRECTION); _mm_storeu_si128((__m128i *) m_data.data(), result); return; } #if defined(ENOKI_X86_AVX512F) if constexpr (Size == 16) { __m256i result = _mm512_cvtps_ph(value.derived().m, _MM_FROUND_CUR_DIRECTION); _mm256_storeu_si256((__m256i *) m_data.data(), result); return; } #endif } #endif } constexpr bool Move = !std::is_lvalue_reference_v && !is_scalar_v && std::is_same_v, value_t>; ENOKI_MARK_USED(Move); if constexpr (std::is_same_v, nanogui::Array>) { for (size_t i = 0; i < Size; ++i) coeff(i) = value[i]; } else if constexpr (detail::broadcast) { auto s = static_cast>(value); bool unused[] = { (coeff(Is) = s, false)..., false }; (void) unused; (void) s; } else { if constexpr (Move) { bool unused[] = { (coeff(Is) = std::move(value.derived().coeff(Is)), false)..., false }; (void) unused; } else { using Src = decltype(value.derived().coeff(0)); bool unused[] = { (coeff(Is) = cast_t(value.derived().coeff(Is)), false)..., false }; (void) unused; } } } template ENOKI_INLINE void assign_(T&& value, detail::reinterpret_flag, std::index_sequence) { if constexpr (std::is_same_v, array_shape_t> && std::is_same_v && detail::has_bitmask_v) { #if defined(ENOKI_X86_AVX512VL) if constexpr (Size == 16) { _mm_storeu_si128((__m128i *) data(), _mm_maskz_set1_epi8((__mmask16) value.bitmask_(), (char) 1)); return; } else if constexpr (Size == 8) { uint64_t result = (uint64_t) detail::mm_cvtsi128_si64( _mm_maskz_set1_epi8((__mmask8) value.bitmask_(), (char) 1)); memcpy(data(), &result, sizeof(uint64_t)); return; } else if constexpr (Size == 4) { uint32_t result = (uint32_t) _mm_cvtsi128_si32( _mm_maskz_set1_epi8((__mmask8) value.bitmask_(), (char) 1)); memcpy(data(), &result, sizeof(uint32_t)); return; } #elif defined(ENOKI_X86_AVX2) && defined(ENOKI_X86_64) uint32_t k = value.bitmask_(); if constexpr (Size == 16) { uint64_t low = (uint64_t) _pdep_u64(k, 0x0101010101010101ull); uint64_t hi = (uint64_t) _pdep_u64(k >> 8, 0x0101010101010101ull); memcpy((uint8_t *) data(), &low, sizeof(uint64_t)); memcpy((uint8_t *) data() + sizeof(uint64_t), &hi, sizeof(uint64_t)); return; } else if constexpr (Size == 8) { uint64_t result = (uint64_t) _pdep_u64(k, 0x0101010101010101ull); memcpy(data(), &result, sizeof(uint64_t)); return; } else if constexpr (Size == 4) { uint32_t result = (uint32_t) _pdep_u32(k, 0x01010101ull); memcpy(data(), &result, sizeof(uint32_t)); return; } #endif } if constexpr(detail::broadcast) { bool unused[] = { (coeff(Is) = reinterpret_array(value), false)..., false }; (void) unused; } else { bool unused[] = { (coeff(Is) = reinterpret_array(value.coeff(Is)), false)..., false }; (void) unused; } } #if defined(_MSC_VER) # pragma warning(pop) #elif defined(__GNUC__) # pragma GCC diagnostic pop #endif public: /// Return the size in bytes size_t nbytes() const { if constexpr (is_dynamic_v) { size_t result = 0; for (size_t i = 0; i < Derived::Size; ++i) result += coeff(i).nbytes(); return result; } else { return Base::nbytes(); } } /// Arithmetic NOT operation ENOKI_INLINE Derived not_() const { Derived result; ENOKI_CHKSCALAR("not"); for (size_t i = 0; i < Derived::Size; ++i) { if constexpr (IsMask_) (Value &) result.coeff(i) = !(Value) derived().coeff(i); else (Value &) result.coeff(i) = ~(Value) derived().coeff(i); } return result; } /// Arithmetic unary negation operation ENOKI_INLINE Derived neg_() const { Derived result; ENOKI_CHKSCALAR("neg"); for (size_t i = 0; i < Derived::Size; ++i) (Value &) result.coeff(i) = - (Value) derived().coeff(i); return result; } /// Array indexing operator ENOKI_INLINE Ref coeff(size_t i) { ENOKI_CHKSCALAR("coeff"); return m_data[i]; } /// Array indexing operator (const) ENOKI_INLINE ConstRef coeff(size_t i) const { ENOKI_CHKSCALAR("coeff"); return m_data[i]; } /// Recursive array indexing operator (const) template = 1)> = 0> ENOKI_INLINE decltype(auto) coeff(size_t i0, Args... other) const { return coeff(i0).coeff(size_t(other)...); } /// Recursive array indexing operator template = 1)> = 0> ENOKI_INLINE decltype(auto) coeff(size_t i0, Args... other) { return coeff(i0).coeff(size_t(other)...); } StorageType *data() { return m_data.data(); } const StorageType *data() const { return m_data.data(); } private: std::array m_data; }; struct BitRef { private: struct BitWrapper { virtual bool get() = 0; virtual void set(bool value) = 0; virtual ~BitWrapper() = default; }; std::unique_ptr accessor; public: BitRef(bool &b) { struct BoolWrapper : BitWrapper { BoolWrapper(bool& data) : data(data) { } bool get() override { return data; } void set(bool value) override { data = value; } bool &data; }; accessor = std::make_unique(b); } template BitRef(MaskBit b) { struct MaskBitWrapper : BitWrapper { MaskBitWrapper(MaskBit data) : data(data) { } bool get() override { return (bool) data; } void set(bool value) override { data = value; } MaskBit data; }; accessor = std::make_unique(b); } operator bool() const { return accessor->get(); } BitRef& operator=(bool value) { accessor->set(value); return *this; } }; NAMESPACE_END(enoki)