cpp-sdl2
C++ header-only SDL2 wrapper
simd.hpp
Go to the documentation of this file.
1 #pragma once
2 
3 #include "SDL_version.h"
4 #if SDL_VERSION_ATLEAST(2, 0, 10)
5 
6 #include "SDL_cpuinfo.h"
7 #include <memory>
8 #include <type_traits>
9 
10 namespace sdl::simd
11 {
14 inline size_t get_alignment()
15 {
16  return SDL_SIMDGetAlignment();
17 }
18 
21 inline void* alloc(size_t len)
22 {
23  return SDL_SIMDAlloc(len);
24 }
25 
27 inline void free(void* ptr)
28 {
29  return SDL_SIMDFree(ptr);
30 }
31 
33 template<typename T>
34 struct allocator
35 {
36  using value_type = T;
37 
38  template<typename U>
39  struct rebind
40  {
42  };
43 
44  constexpr allocator() noexcept = default;
45 
46  template<typename U>
47  explicit constexpr allocator(allocator<U>) noexcept
48  {
49  }
50 
51  T* allocate(std::size_t size)
52  {
53  void* mem = simd::alloc(size);
54 #ifndef CPP_SDL2_DISABLE_EXCEPTIONS
55  if (!mem) throw std::bad_alloc();
56 #endif
57  return std::launder(reinterpret_cast<T*>(new (mem) std::byte[size * sizeof(T)]));
58  }
59 
60  void deallocate(T* ptr, [[maybe_unused]] std::size_t size) noexcept { simd::free(ptr); }
61 
62  friend constexpr bool operator==(allocator, allocator) noexcept { return true; }
63  friend constexpr bool operator!=(allocator, allocator) noexcept { return false; }
64 };
65 
66 namespace details
67 {
69 template<typename T>
70 void destroy_at(T* ptr)
71 {
72  static_assert(!(std::is_array_v<T> && std::extent_v<T> == 0), "destroy_at<T[]> is invalid");
73 
74  if constexpr (std::is_array_v<T>)
75  for (auto& elem : *ptr) details::destroy_at(std::addressof(elem));
76  else
77  ptr->~T();
78 }
79 } // namespace details
80 
82 template<typename T>
83 struct deleter
84 {
85  constexpr deleter() noexcept = default;
86 
87  template<typename U, typename = std::enable_if_t<std::is_convertible_v<U*, T*>>>
88  constexpr deleter(deleter<U>) noexcept
89  {
90  }
91 
92  void operator()(T* ptr) noexcept
93  {
95  simd::free(ptr);
96  }
97 };
98 
100 template<typename T>
101 class deleter<T[]>
102 {
103  std::size_t count = 0;
104 
105 public:
106  deleter() = delete;
107 
108  explicit constexpr deleter(std::size_t size) noexcept : count(size) {}
109 
110  template<typename U, typename = std::enable_if_t<std::is_convertible_v<U (*)[], T (*)[]>>>
111  explicit constexpr deleter(deleter<U[]> const& other) noexcept : count(other.count)
112  {
113  }
114 
115  template<typename U>
116  auto operator()(U* ptr) -> std::enable_if_t<std::is_convertible_v<U (*)[], T (*)[]>>
117  {
118  for (std::size_t i = 0; i < count; ++i) details::destroy_at(std::addressof((*ptr)[i]));
119  simd::free(ptr);
120  }
121 };
122 
123 template<typename T>
124 using unique_ptr = std::unique_ptr<T, simd::deleter<T>>;
125 
127 template<typename T, typename... Args>
128 auto make_unique(Args&&... args) -> std::enable_if_t<!std::is_array_v<T>, unique_ptr<T>>
129 {
130  allocator<T> a;
131  return unique_ptr<T>(new (a.allocate(1)) T(std::forward<Args>(args)...));
132 }
133 
135 template<typename T, typename... Args>
136 auto make_unique(Args&&... args) -> std::enable_if_t<std::extent_v<T> != 0, unique_ptr<T>> = delete;
137 
139 template<typename T>
140 auto make_unique(std::size_t count)
141  -> std::enable_if_t<std::is_array_v<T> && std::extent_v<T> == 0, unique_ptr<T>>
142 {
143  using U = std::remove_extent_t<T>;
145  auto* mem = a.allocate(count);
146  for (std::size_t i = 0; i < count; ++i) new (mem + i) U;
147  return unique_ptr<T>(mem, deleter<T>(count));
148 }
149 
151 template<typename T, typename... Args>
152 auto make_shared(Args&&... args) -> std::enable_if<!std::is_array_v<T>, std::shared_ptr<T>>
153 {
154  allocator<T> a;
155  auto* mem = new (a.allocate(1)) T(std::forward<Args>(args)...);
156  return std::shared_ptr<T>(mem, deleter<T>());
157 }
158 
159 } // namespace sdl::simd
160 
161 #endif // SDL_VERSION_ATLEAST(2, 0, 10)
auto make_unique(Args &&... args) -> std::enable_if_t<!std::is_array_v< T >, unique_ptr< T >>
Equivalent of std::make_unique<T> that returns a simd::unique_ptr.
Definition: simd.hpp:128
void operator()(T *ptr) noexcept
Definition: simd.hpp:92
friend constexpr bool operator==(allocator, allocator) noexcept
Definition: simd.hpp:62
auto operator()(U *ptr) -> std::enable_if_t< std::is_convertible_v< U(*)[], T(*)[]>>
Definition: simd.hpp:116
constexpr deleter(deleter< U >) noexcept
Definition: simd.hpp:88
T * allocate(std::size_t size)
Definition: simd.hpp:51
std::unique_ptr< T, simd::deleter< T > > unique_ptr
Definition: simd.hpp:124
auto * mem
Definition: simd.hpp:145
void * alloc(size_t len)
Allocate memory in a SIMD-friendly way.
Definition: simd.hpp:21
void free(void *ptr)
Deallocate memory obtained from sdl::simd::alloc().
Definition: simd.hpp:27
size_t get_alignment()
Report the alignment this system needs for SIMD allocations.
Definition: simd.hpp:14
constexpr allocator() noexcept=default
constexpr deleter(deleter< U[]> const &other) noexcept
Definition: simd.hpp:111
Allocator usable with standard containers.
Definition: simd.hpp:34
void deallocate(T *ptr, [[maybe_unused]] std::size_t size) noexcept
Definition: simd.hpp:60
void destroy_at(T *ptr)
recursive implementation of std::destroy_at, only available from C++20 onwards.
Definition: simd.hpp:70
allocator< U > a
Definition: simd.hpp:144
friend constexpr bool operator!=(allocator, allocator) noexcept
Definition: simd.hpp:63
allocator< U > other
Definition: simd.hpp:41
constexpr deleter(std::size_t size) noexcept
Definition: simd.hpp:108
auto make_shared(Args &&... args) -> std::enable_if<!std::is_array_v< T >, std::shared_ptr< T >>
Equivalent of std::make_shared<T> that uses simd-friendly storage.
Definition: simd.hpp:152
deleter usable with std::unique_ptr<T>.
Definition: simd.hpp:83