mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-15 08:34:40 +00:00
update openal
This commit is contained in:
parent
62f3b93ff9
commit
6721a6b021
287 changed files with 33851 additions and 27325 deletions
38
Engine/lib/openal-soft/common/alassert.cpp
Normal file
38
Engine/lib/openal-soft/common/alassert.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
#include "alassert.h"
|
||||
|
||||
#include <exception>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace al {
|
||||
|
||||
[[noreturn]]
|
||||
void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept
|
||||
{
|
||||
std::string errstr{filename};
|
||||
errstr += ':';
|
||||
errstr += std::to_string(linenum);
|
||||
errstr += ": ";
|
||||
errstr += funcname;
|
||||
errstr += ": ";
|
||||
errstr += message;
|
||||
/* Calling std::terminate in a catch block hopefully causes the system to
|
||||
* provide info about the caught exception in the error dialog. At least on
|
||||
* Linux, this results in the process printing
|
||||
*
|
||||
* terminate called after throwing an instance of 'std::runtime_error'
|
||||
* what(): <message here>
|
||||
*
|
||||
* before terminating from a SIGABRT. Hopefully Windows and Mac will do the
|
||||
* appropriate things with the message for an abnormal termination.
|
||||
*/
|
||||
try {
|
||||
throw std::runtime_error{errstr};
|
||||
}
|
||||
catch(...) {
|
||||
std::terminate();
|
||||
}
|
||||
}
|
||||
|
||||
} /* namespace al */
|
||||
24
Engine/lib/openal-soft/common/alassert.h
Normal file
24
Engine/lib/openal-soft/common/alassert.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef AL_ASSERT_H
|
||||
#define AL_ASSERT_H
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "opthelpers.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
[[noreturn]]
|
||||
void do_assert(const char *message, int linenum, const char *filename, const char *funcname) noexcept;
|
||||
|
||||
} /* namespace al */
|
||||
|
||||
/* A custom assert macro that is not compiled out for Release/NDEBUG builds,
|
||||
* making it an appropriate replacement for assert() checks that must not be
|
||||
* ignored.
|
||||
*/
|
||||
#define alassert(cond) do { \
|
||||
if(!(cond)) UNLIKELY \
|
||||
al::do_assert("Assertion '" #cond "' failed", __LINE__, __FILE__, std::data(__func__)); \
|
||||
} while(0)
|
||||
|
||||
#endif /* AL_ASSERT_H */
|
||||
|
|
@ -1,8 +1,13 @@
|
|||
#ifndef AL_BIT_H
|
||||
#define AL_BIT_H
|
||||
|
||||
#include <array>
|
||||
#ifndef __GNUC__
|
||||
#include <cstdint>
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#if !defined(__GNUC__) && (defined(_WIN32) || defined(_WIN64))
|
||||
#include <intrin.h>
|
||||
|
|
@ -10,6 +15,16 @@
|
|||
|
||||
namespace al {
|
||||
|
||||
template<typename To, typename From>
|
||||
std::enable_if_t<sizeof(To) == sizeof(From) && std::is_trivially_copyable_v<From>
|
||||
&& std::is_trivially_copyable_v<To>,
|
||||
To> bit_cast(const From &src) noexcept
|
||||
{
|
||||
alignas(To) std::array<char,sizeof(To)> dst;
|
||||
std::memcpy(dst.data(), &src, sizeof(To));
|
||||
return *std::launder(reinterpret_cast<To*>(dst.data()));
|
||||
}
|
||||
|
||||
#ifdef __BYTE_ORDER__
|
||||
enum class endian {
|
||||
little = __ORDER_LITTLE_ENDIAN__,
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
#ifndef AL_BYTE_H
|
||||
#define AL_BYTE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
namespace al {
|
||||
|
||||
using byte = unsigned char;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_BYTE_H */
|
||||
|
|
@ -4,10 +4,11 @@
|
|||
#include "alcomplex.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include "albit.h"
|
||||
|
|
@ -20,36 +21,38 @@ namespace {
|
|||
|
||||
using ushort = unsigned short;
|
||||
using ushort2 = std::pair<ushort,ushort>;
|
||||
using complex_d = std::complex<double>;
|
||||
|
||||
constexpr size_t BitReverseCounter(size_t log2_size) noexcept
|
||||
constexpr std::size_t BitReverseCounter(std::size_t log2_size) noexcept
|
||||
{
|
||||
/* Some magic math that calculates the number of swaps needed for a
|
||||
* sequence of bit-reversed indices when index < reversed_index.
|
||||
*/
|
||||
return (1u<<(log2_size-1)) - (1u<<((log2_size-1u)/2u));
|
||||
return (1_zu<<(log2_size-1)) - (1_zu<<((log2_size-1_zu)/2_zu));
|
||||
}
|
||||
|
||||
|
||||
template<size_t N>
|
||||
template<std::size_t N>
|
||||
struct BitReverser {
|
||||
static_assert(N <= sizeof(ushort)*8, "Too many bits for the bit-reversal table.");
|
||||
|
||||
ushort2 mData[BitReverseCounter(N)]{};
|
||||
std::array<ushort2,BitReverseCounter(N)> mData{};
|
||||
|
||||
constexpr BitReverser()
|
||||
{
|
||||
const size_t fftsize{1u << N};
|
||||
size_t ret_i{0};
|
||||
const std::size_t fftsize{1u << N};
|
||||
std::size_t ret_i{0};
|
||||
|
||||
/* Bit-reversal permutation applied to a sequence of fftsize items. */
|
||||
for(size_t idx{1u};idx < fftsize-1;++idx)
|
||||
for(std::size_t idx{1u};idx < fftsize-1;++idx)
|
||||
{
|
||||
size_t revidx{0u}, imask{idx};
|
||||
for(size_t i{0};i < N;++i)
|
||||
{
|
||||
revidx = (revidx<<1) | (imask&1);
|
||||
imask >>= 1;
|
||||
}
|
||||
std::size_t revidx{idx};
|
||||
revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1);
|
||||
revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2);
|
||||
revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4);
|
||||
revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8);
|
||||
revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16);
|
||||
revidx >>= 32-N;
|
||||
|
||||
if(idx < revidx)
|
||||
{
|
||||
|
|
@ -58,14 +61,13 @@ struct BitReverser {
|
|||
++ret_i;
|
||||
}
|
||||
}
|
||||
assert(ret_i == al::size(mData));
|
||||
assert(ret_i == std::size(mData));
|
||||
}
|
||||
};
|
||||
|
||||
/* These bit-reversal swap tables support up to 10-bit indices (1024 elements),
|
||||
* which is the largest used by OpenAL Soft's filters and effects. Larger FFT
|
||||
* requests, used by some utilities where performance is less important, will
|
||||
* use a slower table-less path.
|
||||
/* These bit-reversal swap tables support up to 11-bit indices (2048 elements),
|
||||
* which is large enough for the filters and effects in OpenAL Soft. Larger FFT
|
||||
* requests will use a slower table-less path.
|
||||
*/
|
||||
constexpr BitReverser<2> BitReverser2{};
|
||||
constexpr BitReverser<3> BitReverser3{};
|
||||
|
|
@ -76,7 +78,8 @@ constexpr BitReverser<7> BitReverser7{};
|
|||
constexpr BitReverser<8> BitReverser8{};
|
||||
constexpr BitReverser<9> BitReverser9{};
|
||||
constexpr BitReverser<10> BitReverser10{};
|
||||
constexpr std::array<al::span<const ushort2>,11> gBitReverses{{
|
||||
constexpr BitReverser<11> BitReverser11{};
|
||||
constexpr std::array<al::span<const ushort2>,12> gBitReverses{{
|
||||
{}, {},
|
||||
BitReverser2.mData,
|
||||
BitReverser3.mData,
|
||||
|
|
@ -86,75 +89,124 @@ constexpr std::array<al::span<const ushort2>,11> gBitReverses{{
|
|||
BitReverser7.mData,
|
||||
BitReverser8.mData,
|
||||
BitReverser9.mData,
|
||||
BitReverser10.mData
|
||||
BitReverser10.mData,
|
||||
BitReverser11.mData
|
||||
}};
|
||||
|
||||
/* Lookup table for std::polar(1, pi / (1<<index)); */
|
||||
template<typename T>
|
||||
constexpr std::array<std::complex<T>,gBitReverses.size()-1> gArgAngle{{
|
||||
{static_cast<T>(-1.00000000000000000e+00), static_cast<T>(0.00000000000000000e+00)},
|
||||
{static_cast<T>( 0.00000000000000000e+00), static_cast<T>(1.00000000000000000e+00)},
|
||||
{static_cast<T>( 7.07106781186547524e-01), static_cast<T>(7.07106781186547524e-01)},
|
||||
{static_cast<T>( 9.23879532511286756e-01), static_cast<T>(3.82683432365089772e-01)},
|
||||
{static_cast<T>( 9.80785280403230449e-01), static_cast<T>(1.95090322016128268e-01)},
|
||||
{static_cast<T>( 9.95184726672196886e-01), static_cast<T>(9.80171403295606020e-02)},
|
||||
{static_cast<T>( 9.98795456205172393e-01), static_cast<T>(4.90676743274180143e-02)},
|
||||
{static_cast<T>( 9.99698818696204220e-01), static_cast<T>(2.45412285229122880e-02)},
|
||||
{static_cast<T>( 9.99924701839144541e-01), static_cast<T>(1.22715382857199261e-02)},
|
||||
{static_cast<T>( 9.99981175282601143e-01), static_cast<T>(6.13588464915447536e-03)},
|
||||
{static_cast<T>( 9.99995293809576172e-01), static_cast<T>(3.06795676296597627e-03)}
|
||||
}};
|
||||
|
||||
} // namespace
|
||||
|
||||
template<typename Real>
|
||||
std::enable_if_t<std::is_floating_point<Real>::value>
|
||||
complex_fft(const al::span<std::complex<Real>> buffer, const al::type_identity_t<Real> sign)
|
||||
void complex_fft(const al::span<std::complex<double>> buffer, const double sign)
|
||||
{
|
||||
const size_t fftsize{buffer.size()};
|
||||
const std::size_t fftsize{buffer.size()};
|
||||
/* Get the number of bits used for indexing. Simplifies bit-reversal and
|
||||
* the main loop count.
|
||||
*/
|
||||
const size_t log2_size{static_cast<size_t>(al::countr_zero(fftsize))};
|
||||
const std::size_t log2_size{static_cast<std::size_t>(al::countr_zero(fftsize))};
|
||||
|
||||
if(log2_size >= gBitReverses.size()) UNLIKELY
|
||||
if(log2_size < gBitReverses.size()) LIKELY
|
||||
{
|
||||
for(size_t idx{1u};idx < fftsize-1;++idx)
|
||||
for(auto &rev : gBitReverses[log2_size])
|
||||
std::swap(buffer[rev.first], buffer[rev.second]);
|
||||
|
||||
/* Iterative form of Danielson-Lanczos lemma */
|
||||
for(std::size_t i{0};i < log2_size;++i)
|
||||
{
|
||||
size_t revidx{0u}, imask{idx};
|
||||
for(size_t i{0};i < log2_size;++i)
|
||||
const std::size_t step2{1_uz << i};
|
||||
const std::size_t step{2_uz << i};
|
||||
/* The first iteration of the inner loop would have u=1, which we
|
||||
* can simplify to remove a number of complex multiplies.
|
||||
*/
|
||||
for(std::size_t k{0};k < fftsize;k+=step)
|
||||
{
|
||||
revidx = (revidx<<1) | (imask&1);
|
||||
imask >>= 1;
|
||||
}
|
||||
|
||||
if(idx < revidx)
|
||||
std::swap(buffer[idx], buffer[revidx]);
|
||||
}
|
||||
}
|
||||
else for(auto &rev : gBitReverses[log2_size])
|
||||
std::swap(buffer[rev.first], buffer[rev.second]);
|
||||
|
||||
/* Iterative form of Danielson-Lanczos lemma */
|
||||
const Real pi{al::numbers::pi_v<Real> * sign};
|
||||
size_t step2{1u};
|
||||
for(size_t i{0};i < log2_size;++i)
|
||||
{
|
||||
const Real arg{pi / static_cast<Real>(step2)};
|
||||
|
||||
/* TODO: Would std::polar(1.0, arg) be any better? */
|
||||
const std::complex<Real> w{std::cos(arg), std::sin(arg)};
|
||||
std::complex<Real> u{1.0, 0.0};
|
||||
const size_t step{step2 << 1};
|
||||
for(size_t j{0};j < step2;j++)
|
||||
{
|
||||
for(size_t k{j};k < fftsize;k+=step)
|
||||
{
|
||||
std::complex<Real> temp{buffer[k+step2] * u};
|
||||
const complex_d temp{buffer[k+step2]};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
|
||||
u *= w;
|
||||
const complex_d w{gArgAngle<double>[i].real(), gArgAngle<double>[i].imag()*sign};
|
||||
complex_d u{w};
|
||||
for(std::size_t j{1};j < step2;j++)
|
||||
{
|
||||
for(std::size_t k{j};k < fftsize;k+=step)
|
||||
{
|
||||
const complex_d temp{buffer[k+step2] * u};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
u *= w;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(log2_size < 32);
|
||||
|
||||
for(std::size_t idx{1u};idx < fftsize-1;++idx)
|
||||
{
|
||||
std::size_t revidx{idx};
|
||||
revidx = ((revidx&0xaaaaaaaa) >> 1) | ((revidx&0x55555555) << 1);
|
||||
revidx = ((revidx&0xcccccccc) >> 2) | ((revidx&0x33333333) << 2);
|
||||
revidx = ((revidx&0xf0f0f0f0) >> 4) | ((revidx&0x0f0f0f0f) << 4);
|
||||
revidx = ((revidx&0xff00ff00) >> 8) | ((revidx&0x00ff00ff) << 8);
|
||||
revidx = (revidx >> 16) | ((revidx&0x0000ffff) << 16);
|
||||
revidx >>= 32-log2_size;
|
||||
|
||||
if(idx < revidx)
|
||||
std::swap(buffer[idx], buffer[revidx]);
|
||||
}
|
||||
|
||||
step2 <<= 1;
|
||||
const double pi{al::numbers::pi * sign};
|
||||
for(std::size_t i{0};i < log2_size;++i)
|
||||
{
|
||||
const std::size_t step2{1_uz << i};
|
||||
const std::size_t step{2_uz << i};
|
||||
for(std::size_t k{0};k < fftsize;k+=step)
|
||||
{
|
||||
const complex_d temp{buffer[k+step2]};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
|
||||
const double arg{pi / static_cast<double>(step2)};
|
||||
const complex_d w{std::polar(1.0, arg)};
|
||||
complex_d u{w};
|
||||
for(std::size_t j{1};j < step2;j++)
|
||||
{
|
||||
for(std::size_t k{j};k < fftsize;k+=step)
|
||||
{
|
||||
const complex_d temp{buffer[k+step2] * u};
|
||||
buffer[k+step2] = buffer[k] - temp;
|
||||
buffer[k] += temp;
|
||||
}
|
||||
u *= w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void complex_hilbert(const al::span<std::complex<double>> buffer)
|
||||
{
|
||||
using namespace std::placeholders;
|
||||
|
||||
inverse_fft(buffer);
|
||||
|
||||
const double inverse_size = 1.0/static_cast<double>(buffer.size());
|
||||
auto bufiter = buffer.begin();
|
||||
const auto halfiter = bufiter + (buffer.size()>>1);
|
||||
const auto halfiter = bufiter + ptrdiff_t(buffer.size()>>1);
|
||||
|
||||
*bufiter *= inverse_size; ++bufiter;
|
||||
bufiter = std::transform(bufiter, halfiter, bufiter,
|
||||
|
|
@ -165,7 +217,3 @@ void complex_hilbert(const al::span<std::complex<double>> buffer)
|
|||
|
||||
forward_fft(buffer);
|
||||
}
|
||||
|
||||
|
||||
template void complex_fft<>(const al::span<std::complex<float>> buffer, const float sign);
|
||||
template void complex_fft<>(const al::span<std::complex<double>> buffer, const double sign);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
#define ALCOMPLEX_H
|
||||
|
||||
#include <complex>
|
||||
#include <type_traits>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
|
@ -11,27 +10,21 @@
|
|||
* FFT and 1 is inverse FFT. Applies the Discrete Fourier Transform (DFT) to
|
||||
* the data supplied in the buffer, which MUST BE power of two.
|
||||
*/
|
||||
template<typename Real>
|
||||
std::enable_if_t<std::is_floating_point<Real>::value>
|
||||
complex_fft(const al::span<std::complex<Real>> buffer, const al::type_identity_t<Real> sign);
|
||||
void complex_fft(const al::span<std::complex<double>> buffer, const double sign);
|
||||
|
||||
/**
|
||||
* Calculate the frequency-domain response of the time-domain signal in the
|
||||
* provided buffer, which MUST BE power of two.
|
||||
*/
|
||||
template<typename Real, size_t N>
|
||||
std::enable_if_t<std::is_floating_point<Real>::value>
|
||||
forward_fft(const al::span<std::complex<Real>,N> buffer)
|
||||
{ complex_fft(buffer.subspan(0), -1); }
|
||||
inline void forward_fft(const al::span<std::complex<double>> buffer)
|
||||
{ complex_fft(buffer, -1.0); }
|
||||
|
||||
/**
|
||||
* Calculate the time-domain signal of the frequency-domain response in the
|
||||
* provided buffer, which MUST BE power of two.
|
||||
*/
|
||||
template<typename Real, size_t N>
|
||||
std::enable_if_t<std::is_floating_point<Real>::value>
|
||||
inverse_fft(const al::span<std::complex<Real>,N> buffer)
|
||||
{ complex_fft(buffer.subspan(0), 1); }
|
||||
inline void inverse_fft(const al::span<std::complex<double>> buffer)
|
||||
{ complex_fft(buffer, +1.0); }
|
||||
|
||||
/**
|
||||
* Calculate the complex helical sequence (discrete-time analytical signal) of
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
#ifndef ALDEQUE_H
|
||||
#define ALDEQUE_H
|
||||
|
||||
#include <deque>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T>
|
||||
using deque = std::deque<T, al::allocator<T>>;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* ALDEQUE_H */
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "alfstream.h"
|
||||
|
||||
#include "strutils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
namespace al {
|
||||
|
||||
ifstream::ifstream(const char *filename, std::ios_base::openmode mode)
|
||||
: std::ifstream{utf8_to_wstr(filename).c_str(), mode}
|
||||
{ }
|
||||
|
||||
void ifstream::open(const char *filename, std::ios_base::openmode mode)
|
||||
{
|
||||
std::wstring wstr{utf8_to_wstr(filename)};
|
||||
std::ifstream::open(wstr.c_str(), mode);
|
||||
}
|
||||
|
||||
ifstream::~ifstream() = default;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
#ifndef AL_FSTREAM_H
|
||||
#define AL_FSTREAM_H
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
// Inherit from std::ifstream to accept UTF-8 filenames
|
||||
class ifstream final : public std::ifstream {
|
||||
public:
|
||||
explicit ifstream(const char *filename, std::ios_base::openmode mode=std::ios_base::in);
|
||||
explicit ifstream(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in)
|
||||
: ifstream{filename.c_str(), mode} { }
|
||||
|
||||
explicit ifstream(const wchar_t *filename, std::ios_base::openmode mode=std::ios_base::in)
|
||||
: std::ifstream{filename, mode} { }
|
||||
explicit ifstream(const std::wstring &filename, std::ios_base::openmode mode=std::ios_base::in)
|
||||
: ifstream{filename.c_str(), mode} { }
|
||||
|
||||
void open(const char *filename, std::ios_base::openmode mode=std::ios_base::in);
|
||||
void open(const std::string &filename, std::ios_base::openmode mode=std::ios_base::in)
|
||||
{ open(filename.c_str(), mode); }
|
||||
|
||||
~ifstream() override;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#else /* _WIN32 */
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace al {
|
||||
|
||||
using ifstream = std::ifstream;
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#endif /* AL_FSTREAM_H */
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#ifdef HAVE_MALLOC_H
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
|
||||
void *al_malloc(size_t alignment, size_t size)
|
||||
{
|
||||
assert((alignment & (alignment-1)) == 0);
|
||||
alignment = std::max(alignment, alignof(std::max_align_t));
|
||||
|
||||
#if defined(HAVE_POSIX_MEMALIGN)
|
||||
void *ret{};
|
||||
if(posix_memalign(&ret, alignment, size) == 0)
|
||||
return ret;
|
||||
return nullptr;
|
||||
#elif defined(HAVE__ALIGNED_MALLOC)
|
||||
return _aligned_malloc(size, alignment);
|
||||
#else
|
||||
size_t total_size{size + alignment-1 + sizeof(void*)};
|
||||
void *base{std::malloc(total_size)};
|
||||
if(base != nullptr)
|
||||
{
|
||||
void *aligned_ptr{static_cast<char*>(base) + sizeof(void*)};
|
||||
total_size -= sizeof(void*);
|
||||
|
||||
std::align(alignment, size, aligned_ptr, total_size);
|
||||
*(static_cast<void**>(aligned_ptr)-1) = base;
|
||||
base = aligned_ptr;
|
||||
}
|
||||
return base;
|
||||
#endif
|
||||
}
|
||||
|
||||
void *al_calloc(size_t alignment, size_t size)
|
||||
{
|
||||
void *ret{al_malloc(alignment, size)};
|
||||
if(ret) std::memset(ret, 0, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void al_free(void *ptr) noexcept
|
||||
{
|
||||
#if defined(HAVE_POSIX_MEMALIGN)
|
||||
std::free(ptr);
|
||||
#elif defined(HAVE__ALIGNED_MALLOC)
|
||||
_aligned_free(ptr);
|
||||
#else
|
||||
if(ptr != nullptr)
|
||||
std::free(*(static_cast<void**>(ptr) - 1));
|
||||
#endif
|
||||
}
|
||||
|
|
@ -3,49 +3,24 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "pragmadefs.h"
|
||||
#include <variant>
|
||||
|
||||
|
||||
void al_free(void *ptr) noexcept;
|
||||
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
|
||||
void *al_malloc(size_t alignment, size_t size);
|
||||
[[gnu::alloc_align(1), gnu::alloc_size(2), gnu::malloc]]
|
||||
void *al_calloc(size_t alignment, size_t size);
|
||||
namespace gsl {
|
||||
template<typename T> using owner = T;
|
||||
}
|
||||
|
||||
|
||||
#define DISABLE_ALLOC() \
|
||||
#define DISABLE_ALLOC \
|
||||
void *operator new(size_t) = delete; \
|
||||
void *operator new[](size_t) = delete; \
|
||||
void operator delete(void*) noexcept = delete; \
|
||||
void operator delete[](void*) noexcept = delete;
|
||||
|
||||
#define DEF_NEWDEL(T) \
|
||||
void *operator new(size_t size) \
|
||||
{ \
|
||||
static_assert(&operator new == &T::operator new, \
|
||||
"Incorrect container type specified"); \
|
||||
if(void *ret{al_malloc(alignof(T), size)}) \
|
||||
return ret; \
|
||||
throw std::bad_alloc(); \
|
||||
} \
|
||||
void *operator new[](size_t size) { return operator new(size); } \
|
||||
void operator delete(void *block) noexcept { al_free(block); } \
|
||||
void operator delete[](void *block) noexcept { operator delete(block); }
|
||||
|
||||
#define DEF_PLACE_NEWDEL() \
|
||||
void *operator new(size_t /*size*/, void *ptr) noexcept { return ptr; } \
|
||||
void *operator new[](size_t /*size*/, void *ptr) noexcept { return ptr; } \
|
||||
void operator delete(void *block, void*) noexcept { al_free(block); } \
|
||||
void operator delete(void *block) noexcept { al_free(block); } \
|
||||
void operator delete[](void *block, void*) noexcept { al_free(block); } \
|
||||
void operator delete[](void *block) noexcept { al_free(block); }
|
||||
|
||||
enum FamCount : size_t { };
|
||||
|
||||
|
|
@ -58,56 +33,64 @@ enum FamCount : size_t { };
|
|||
sizeof(T)); \
|
||||
} \
|
||||
\
|
||||
void *operator new(size_t /*size*/, FamCount count) \
|
||||
gsl::owner<void*> operator new(size_t /*size*/, FamCount count) \
|
||||
{ \
|
||||
if(void *ret{al_malloc(alignof(T), T::Sizeof(count))}) \
|
||||
return ret; \
|
||||
throw std::bad_alloc(); \
|
||||
const auto alignment = std::align_val_t{alignof(T)}; \
|
||||
return ::operator new[](T::Sizeof(count), alignment); \
|
||||
} \
|
||||
void operator delete(gsl::owner<void*> block, FamCount) noexcept \
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(T)}); } \
|
||||
void operator delete(gsl::owner<void*> block) noexcept \
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(T)}); } \
|
||||
void *operator new[](size_t /*size*/) = delete; \
|
||||
void operator delete(void *block, FamCount) { al_free(block); } \
|
||||
void operator delete(void *block) noexcept { al_free(block); } \
|
||||
void operator delete[](void* /*block*/) = delete;
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T, std::size_t Align=alignof(T)>
|
||||
template<typename T, std::size_t AlignV=alignof(T)>
|
||||
struct allocator {
|
||||
static constexpr std::size_t alignment{std::max(Align, alignof(T))};
|
||||
static constexpr auto Alignment = std::max(AlignV, alignof(T));
|
||||
static constexpr auto AlignVal = std::align_val_t{Alignment};
|
||||
|
||||
using value_type = T;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using value_type = std::remove_cv_t<std::remove_reference_t<T>>;
|
||||
using reference = value_type&;
|
||||
using const_reference = const value_type&;
|
||||
using pointer = value_type*;
|
||||
using const_pointer = const value_type*;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using is_always_equal = std::true_type;
|
||||
|
||||
template<typename U>
|
||||
template<typename U, std::enable_if_t<alignof(U) <= Alignment,bool> = true>
|
||||
struct rebind {
|
||||
using other = allocator<U, Align>;
|
||||
using other = allocator<U,Alignment>;
|
||||
};
|
||||
|
||||
constexpr explicit allocator() noexcept = default;
|
||||
template<typename U, std::size_t N>
|
||||
constexpr explicit allocator(const allocator<U,N>&) noexcept { }
|
||||
constexpr explicit allocator(const allocator<U,N>&) noexcept
|
||||
{ static_assert(Alignment == allocator<U,N>::Alignment); }
|
||||
|
||||
T *allocate(std::size_t n)
|
||||
gsl::owner<T*> allocate(std::size_t n)
|
||||
{
|
||||
if(n > std::numeric_limits<std::size_t>::max()/sizeof(T)) throw std::bad_alloc();
|
||||
if(auto p = al_malloc(alignment, n*sizeof(T))) return static_cast<T*>(p);
|
||||
throw std::bad_alloc();
|
||||
return static_cast<gsl::owner<T*>>(::operator new[](n*sizeof(T), AlignVal));
|
||||
}
|
||||
void deallocate(T *p, std::size_t) noexcept { al_free(p); }
|
||||
void deallocate(gsl::owner<T*> p, std::size_t) noexcept
|
||||
{ ::operator delete[](gsl::owner<void*>{p}, AlignVal); }
|
||||
};
|
||||
template<typename T, std::size_t N, typename U, std::size_t M>
|
||||
constexpr bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept { return true; }
|
||||
constexpr bool operator==(const allocator<T,N>&, const allocator<U,M>&) noexcept
|
||||
{ return allocator<T,N>::Alignment == allocator<U,M>::Alignment; }
|
||||
template<typename T, std::size_t N, typename U, std::size_t M>
|
||||
constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept { return false; }
|
||||
constexpr bool operator!=(const allocator<T,N>&, const allocator<U,M>&) noexcept
|
||||
{ return allocator<T,N>::Alignment != allocator<U,M>::Alignment; }
|
||||
|
||||
|
||||
#ifdef __cpp_lib_to_address
|
||||
using std::to_address;
|
||||
#else
|
||||
template<typename T>
|
||||
constexpr T *to_address(T *p) noexcept
|
||||
{
|
||||
|
|
@ -117,194 +100,55 @@ constexpr T *to_address(T *p) noexcept
|
|||
|
||||
template<typename T>
|
||||
constexpr auto to_address(const T &p) noexcept
|
||||
{ return to_address(p.operator->()); }
|
||||
|
||||
{
|
||||
return ::al::to_address(p.operator->());
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename T, typename ...Args>
|
||||
constexpr T* construct_at(T *ptr, Args&& ...args)
|
||||
noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
{ return ::new(static_cast<void*>(ptr)) T{std::forward<Args>(args)...}; }
|
||||
|
||||
/* At least VS 2015 complains that 'ptr' is unused when the given type's
|
||||
* destructor is trivial (a no-op). So disable that warning for this call.
|
||||
*/
|
||||
DIAGNOSTIC_PUSH
|
||||
msc_pragma(warning(disable : 4100))
|
||||
template<typename T>
|
||||
constexpr std::enable_if_t<!std::is_array<T>::value>
|
||||
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<T>::value)
|
||||
{ ptr->~T(); }
|
||||
DIAGNOSTIC_POP
|
||||
template<typename T>
|
||||
constexpr std::enable_if_t<std::is_array<T>::value>
|
||||
destroy_at(T *ptr) noexcept(std::is_nothrow_destructible<std::remove_all_extents_t<T>>::value)
|
||||
noexcept(std::is_nothrow_constructible_v<T, Args...>)
|
||||
{
|
||||
for(auto &elem : *ptr)
|
||||
al::destroy_at(std::addressof(elem));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr void destroy(T first, T end) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
|
||||
{
|
||||
while(first != end)
|
||||
{
|
||||
al::destroy_at(std::addressof(*first));
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename N>
|
||||
constexpr std::enable_if_t<std::is_integral<N>::value,T>
|
||||
destroy_n(T first, N count) noexcept(noexcept(al::destroy_at(std::addressof(*first))))
|
||||
{
|
||||
if(count != 0)
|
||||
{
|
||||
do {
|
||||
al::destroy_at(std::addressof(*first));
|
||||
++first;
|
||||
} while(--count);
|
||||
}
|
||||
return first;
|
||||
/* NOLINTBEGIN(cppcoreguidelines-owning-memory) construct_at doesn't
|
||||
* necessarily handle the address from an owner, while placement new
|
||||
* expects to.
|
||||
*/
|
||||
return ::new(static_cast<void*>(ptr)) T{std::forward<Args>(args)...};
|
||||
/* NOLINTEND(cppcoreguidelines-owning-memory) */
|
||||
}
|
||||
|
||||
|
||||
template<typename T, typename N>
|
||||
inline std::enable_if_t<std::is_integral<N>::value,
|
||||
T> uninitialized_default_construct_n(T first, N count)
|
||||
{
|
||||
using ValueT = typename std::iterator_traits<T>::value_type;
|
||||
T current{first};
|
||||
if(count != 0)
|
||||
template<typename SP, typename PT, typename ...Args>
|
||||
class out_ptr_t {
|
||||
static_assert(!std::is_same_v<PT,void*>);
|
||||
|
||||
SP &mRes;
|
||||
std::variant<PT,void*> mPtr{};
|
||||
|
||||
public:
|
||||
out_ptr_t(SP &res) : mRes{res} { }
|
||||
~out_ptr_t()
|
||||
{
|
||||
try {
|
||||
do {
|
||||
::new(static_cast<void*>(std::addressof(*current))) ValueT;
|
||||
++current;
|
||||
} while(--count);
|
||||
}
|
||||
catch(...) {
|
||||
al::destroy(first, current);
|
||||
throw;
|
||||
}
|
||||
auto set_res = [this](auto &ptr)
|
||||
{ mRes.reset(static_cast<PT>(ptr)); };
|
||||
std::visit(set_res, mPtr);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
out_ptr_t(const out_ptr_t&) = delete;
|
||||
out_ptr_t& operator=(const out_ptr_t&) = delete;
|
||||
|
||||
operator PT*() noexcept
|
||||
{ return &std::get<PT>(mPtr); }
|
||||
|
||||
/* Storage for flexible array data. This is trivially destructible if type T is
|
||||
* trivially destructible.
|
||||
*/
|
||||
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
|
||||
struct FlexArrayStorage {
|
||||
const size_t mSize;
|
||||
union {
|
||||
char mDummy;
|
||||
alignas(alignment) T mArray[1];
|
||||
};
|
||||
|
||||
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
|
||||
{
|
||||
const size_t len{sizeof(T)*count};
|
||||
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
|
||||
}
|
||||
|
||||
FlexArrayStorage(size_t size) : mSize{size}
|
||||
{ al::uninitialized_default_construct_n(mArray, mSize); }
|
||||
~FlexArrayStorage() = default;
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
operator void**() noexcept
|
||||
{ return &mPtr.template emplace<void*>(); }
|
||||
};
|
||||
|
||||
template<typename T, size_t alignment>
|
||||
struct FlexArrayStorage<T,alignment,false> {
|
||||
const size_t mSize;
|
||||
union {
|
||||
char mDummy;
|
||||
alignas(alignment) T mArray[1];
|
||||
};
|
||||
|
||||
static constexpr size_t Sizeof(size_t count, size_t base) noexcept
|
||||
{
|
||||
const size_t len{sizeof(T)*count};
|
||||
return std::max(offsetof(FlexArrayStorage,mArray)+len, sizeof(FlexArrayStorage)) + base;
|
||||
}
|
||||
|
||||
FlexArrayStorage(size_t size) : mSize{size}
|
||||
{ al::uninitialized_default_construct_n(mArray, mSize); }
|
||||
~FlexArrayStorage() { al::destroy_n(mArray, mSize); }
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
};
|
||||
|
||||
/* A flexible array type. Used either standalone or at the end of a parent
|
||||
* struct, with placement new, to have a run-time-sized array that's embedded
|
||||
* with its size.
|
||||
*/
|
||||
template<typename T, size_t alignment=alignof(T)>
|
||||
struct FlexArray {
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
using Storage_t_ = FlexArrayStorage<element_type,alignment>;
|
||||
|
||||
Storage_t_ mStore;
|
||||
|
||||
static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept
|
||||
{ return Storage_t_::Sizeof(count, base); }
|
||||
static std::unique_ptr<FlexArray> Create(index_type count)
|
||||
{
|
||||
void *ptr{al_calloc(alignof(FlexArray), Sizeof(count))};
|
||||
return std::unique_ptr<FlexArray>{al::construct_at(static_cast<FlexArray*>(ptr), count)};
|
||||
}
|
||||
|
||||
FlexArray(index_type size) : mStore{size} { }
|
||||
~FlexArray() = default;
|
||||
|
||||
index_type size() const noexcept { return mStore.mSize; }
|
||||
bool empty() const noexcept { return mStore.mSize == 0; }
|
||||
|
||||
pointer data() noexcept { return mStore.mArray; }
|
||||
const_pointer data() const noexcept { return mStore.mArray; }
|
||||
|
||||
reference operator[](index_type i) noexcept { return mStore.mArray[i]; }
|
||||
const_reference operator[](index_type i) const noexcept { return mStore.mArray[i]; }
|
||||
|
||||
reference front() noexcept { return mStore.mArray[0]; }
|
||||
const_reference front() const noexcept { return mStore.mArray[0]; }
|
||||
|
||||
reference back() noexcept { return mStore.mArray[mStore.mSize-1]; }
|
||||
const_reference back() const noexcept { return mStore.mArray[mStore.mSize-1]; }
|
||||
|
||||
iterator begin() noexcept { return mStore.mArray; }
|
||||
const_iterator begin() const noexcept { return mStore.mArray; }
|
||||
const_iterator cbegin() const noexcept { return mStore.mArray; }
|
||||
iterator end() noexcept { return mStore.mArray + mStore.mSize; }
|
||||
const_iterator end() const noexcept { return mStore.mArray + mStore.mSize; }
|
||||
const_iterator cend() const noexcept { return mStore.mArray + mStore.mSize; }
|
||||
|
||||
reverse_iterator rbegin() noexcept { return end(); }
|
||||
const_reverse_iterator rbegin() const noexcept { return end(); }
|
||||
const_reverse_iterator crbegin() const noexcept { return cend(); }
|
||||
reverse_iterator rend() noexcept { return begin(); }
|
||||
const_reverse_iterator rend() const noexcept { return begin(); }
|
||||
const_reverse_iterator crend() const noexcept { return cbegin(); }
|
||||
|
||||
DEF_PLACE_NEWDEL()
|
||||
};
|
||||
template<typename T=void, typename SP, typename ...Args>
|
||||
auto out_ptr(SP &res)
|
||||
{
|
||||
using ptype = typename SP::element_type*;
|
||||
return out_ptr_t<SP,ptype>{res};
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
#ifndef COMMON_ALNUMBERS_H
|
||||
#define COMMON_ALNUMBERS_H
|
||||
|
||||
#include <utility>
|
||||
#include <type_traits>
|
||||
|
||||
namespace al {
|
||||
|
||||
namespace numbers {
|
||||
namespace al::numbers {
|
||||
|
||||
namespace detail_ {
|
||||
template<typename T>
|
||||
|
|
@ -13,24 +11,22 @@ namespace detail_ {
|
|||
} // detail_
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto pi_v = detail_::as_fp<T>(3.141592653589793238462643383279502884L);
|
||||
inline constexpr auto pi_v = detail_::as_fp<T>(3.141592653589793238462643383279502884L);
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto inv_pi_v = detail_::as_fp<T>(0.318309886183790671537767526745028724L);
|
||||
inline constexpr auto inv_pi_v = detail_::as_fp<T>(0.318309886183790671537767526745028724L);
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto sqrt2_v = detail_::as_fp<T>(1.414213562373095048801688724209698079L);
|
||||
inline constexpr auto sqrt2_v = detail_::as_fp<T>(1.414213562373095048801688724209698079L);
|
||||
|
||||
template<typename T>
|
||||
static constexpr auto sqrt3_v = detail_::as_fp<T>(1.732050807568877293527446341505872367L);
|
||||
inline constexpr auto sqrt3_v = detail_::as_fp<T>(1.732050807568877293527446341505872367L);
|
||||
|
||||
static constexpr auto pi = pi_v<double>;
|
||||
static constexpr auto inv_pi = inv_pi_v<double>;
|
||||
static constexpr auto sqrt2 = sqrt2_v<double>;
|
||||
static constexpr auto sqrt3 = sqrt3_v<double>;
|
||||
inline constexpr auto pi = pi_v<double>;
|
||||
inline constexpr auto inv_pi = inv_pi_v<double>;
|
||||
inline constexpr auto sqrt2 = sqrt2_v<double>;
|
||||
inline constexpr auto sqrt3 = sqrt3_v<double>;
|
||||
|
||||
} // namespace numbers
|
||||
|
||||
} // namespace al
|
||||
} // namespace al::numbers
|
||||
|
||||
#endif /* COMMON_ALNUMBERS_H */
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@
|
|||
#define AL_NUMERIC_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <type_traits>
|
||||
#ifdef HAVE_INTRIN_H
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
|
@ -12,75 +15,34 @@
|
|||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
#include "albit.h"
|
||||
#include "altraits.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
inline constexpr int64_t operator "" _i64(unsigned long long int n) noexcept { return static_cast<int64_t>(n); }
|
||||
inline constexpr uint64_t operator "" _u64(unsigned long long int n) noexcept { return static_cast<uint64_t>(n); }
|
||||
constexpr auto operator "" _i64(unsigned long long n) noexcept { return static_cast<std::int64_t>(n); }
|
||||
constexpr auto operator "" _u64(unsigned long long n) noexcept { return static_cast<std::uint64_t>(n); }
|
||||
|
||||
constexpr auto operator "" _z(unsigned long long n) noexcept
|
||||
{ return static_cast<std::make_signed_t<std::size_t>>(n); }
|
||||
constexpr auto operator "" _uz(unsigned long long n) noexcept { return static_cast<std::size_t>(n); }
|
||||
constexpr auto operator "" _zu(unsigned long long n) noexcept { return static_cast<std::size_t>(n); }
|
||||
|
||||
|
||||
constexpr inline float minf(float a, float b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline float maxf(float a, float b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline float clampf(float val, float min, float max) noexcept
|
||||
{ return minf(max, maxf(min, val)); }
|
||||
|
||||
constexpr inline double mind(double a, double b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline double maxd(double a, double b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline double clampd(double val, double min, double max) noexcept
|
||||
{ return mind(max, maxd(min, val)); }
|
||||
|
||||
constexpr inline unsigned int minu(unsigned int a, unsigned int b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline unsigned int maxu(unsigned int a, unsigned int b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline unsigned int clampu(unsigned int val, unsigned int min, unsigned int max) noexcept
|
||||
{ return minu(max, maxu(min, val)); }
|
||||
|
||||
constexpr inline int mini(int a, int b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline int maxi(int a, int b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline int clampi(int val, int min, int max) noexcept
|
||||
{ return mini(max, maxi(min, val)); }
|
||||
|
||||
constexpr inline int64_t mini64(int64_t a, int64_t b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline int64_t maxi64(int64_t a, int64_t b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline int64_t clampi64(int64_t val, int64_t min, int64_t max) noexcept
|
||||
{ return mini64(max, maxi64(min, val)); }
|
||||
|
||||
constexpr inline uint64_t minu64(uint64_t a, uint64_t b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline uint64_t maxu64(uint64_t a, uint64_t b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline uint64_t clampu64(uint64_t val, uint64_t min, uint64_t max) noexcept
|
||||
{ return minu64(max, maxu64(min, val)); }
|
||||
|
||||
constexpr inline size_t minz(size_t a, size_t b) noexcept
|
||||
{ return ((a > b) ? b : a); }
|
||||
constexpr inline size_t maxz(size_t a, size_t b) noexcept
|
||||
{ return ((a > b) ? a : b); }
|
||||
constexpr inline size_t clampz(size_t val, size_t min, size_t max) noexcept
|
||||
{ return minz(max, maxz(min, val)); }
|
||||
constexpr auto GetCounterSuffix(size_t count) noexcept -> const char*
|
||||
{
|
||||
auto &suffix = (((count%100)/10) == 1) ? "th" :
|
||||
((count%10) == 1) ? "st" :
|
||||
((count%10) == 2) ? "nd" :
|
||||
((count%10) == 3) ? "rd" : "th";
|
||||
return std::data(suffix);
|
||||
}
|
||||
|
||||
|
||||
constexpr inline float lerpf(float val1, float val2, float mu) noexcept
|
||||
{ return val1 + (val2-val1)*mu; }
|
||||
constexpr inline float cubic(float val1, float val2, float val3, float val4, float mu) noexcept
|
||||
{
|
||||
const float mu2{mu*mu}, mu3{mu2*mu};
|
||||
const float a0{-0.5f*mu3 + mu2 + -0.5f*mu};
|
||||
const float a1{ 1.5f*mu3 + -2.5f*mu2 + 1.0f};
|
||||
const float a2{-1.5f*mu3 + 2.0f*mu2 + 0.5f*mu};
|
||||
const float a3{ 0.5f*mu3 + -0.5f*mu2};
|
||||
return val1*a0 + val2*a1 + val3*a2 + val4*a3;
|
||||
}
|
||||
constexpr inline double lerpd(double val1, double val2, double mu) noexcept
|
||||
{ return val1 + (val2-val1)*mu; }
|
||||
|
||||
|
||||
/** Find the next power-of-2 for non-power-of-2 numbers. */
|
||||
|
|
@ -125,21 +87,18 @@ inline int fastf2i(float f) noexcept
|
|||
#if defined(HAVE_SSE_INTRINSICS)
|
||||
return _mm_cvt_ss2si(_mm_set_ss(f));
|
||||
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86_FP)
|
||||
#elif defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0
|
||||
|
||||
int i;
|
||||
__asm fld f
|
||||
__asm fistp i
|
||||
return i;
|
||||
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__))
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
|
||||
&& !defined(__SSE_MATH__)
|
||||
|
||||
int i;
|
||||
#ifdef __SSE_MATH__
|
||||
__asm__("cvtss2si %1, %0" : "=r"(i) : "x"(f));
|
||||
#else
|
||||
__asm__ __volatile__("fistpl %0" : "=m"(i) : "t"(f) : "st");
|
||||
#endif
|
||||
return i;
|
||||
|
||||
#else
|
||||
|
|
@ -159,21 +118,16 @@ inline int float2int(float f) noexcept
|
|||
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP == 0) \
|
||||
|| ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
|
||||
&& !defined(__SSE_MATH__))
|
||||
int sign, shift, mant;
|
||||
union {
|
||||
float f;
|
||||
int i;
|
||||
} conv;
|
||||
const int conv_i{al::bit_cast<int>(f)};
|
||||
|
||||
conv.f = f;
|
||||
sign = (conv.i>>31) | 1;
|
||||
shift = ((conv.i>>23)&0xff) - (127+23);
|
||||
const int sign{(conv_i>>31) | 1};
|
||||
const int shift{((conv_i>>23)&0xff) - (127+23)};
|
||||
|
||||
/* Over/underflow */
|
||||
if(shift >= 31 || shift < -23) UNLIKELY
|
||||
return 0;
|
||||
|
||||
mant = (conv.i&0x7fffff) | 0x800000;
|
||||
const int mant{(conv_i&0x7fffff) | 0x800000};
|
||||
if(shift < 0) LIKELY
|
||||
return (mant >> -shift) * sign;
|
||||
return (mant << shift) * sign;
|
||||
|
|
@ -195,25 +149,19 @@ inline int double2int(double d) noexcept
|
|||
#elif (defined(_MSC_VER) && defined(_M_IX86_FP) && _M_IX86_FP < 2) \
|
||||
|| ((defined(__GNUC__) || defined(__clang__)) && (defined(__i386__) || defined(__x86_64__)) \
|
||||
&& !defined(__SSE2_MATH__))
|
||||
int sign, shift;
|
||||
int64_t mant;
|
||||
union {
|
||||
double d;
|
||||
int64_t i64;
|
||||
} conv;
|
||||
const int64_t conv_i64{al::bit_cast<int64_t>(d)};
|
||||
|
||||
conv.d = d;
|
||||
sign = (conv.i64 >> 63) | 1;
|
||||
shift = ((conv.i64 >> 52) & 0x7ff) - (1023 + 52);
|
||||
const int sign{static_cast<int>(conv_i64 >> 63) | 1};
|
||||
const int shift{(static_cast<int>(conv_i64 >> 52) & 0x7ff) - (1023 + 52)};
|
||||
|
||||
/* Over/underflow */
|
||||
if(shift >= 63 || shift < -52) UNLIKELY
|
||||
return 0;
|
||||
|
||||
mant = (conv.i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64;
|
||||
const int64_t mant{(conv_i64 & 0xfffffffffffff_i64) | 0x10000000000000_i64};
|
||||
if(shift < 0) LIKELY
|
||||
return (int)(mant >> -shift) * sign;
|
||||
return (int)(mant << shift) * sign;
|
||||
return static_cast<int>(mant >> -shift) * sign;
|
||||
return static_cast<int>(mant << shift) * sign;
|
||||
|
||||
#else
|
||||
|
||||
|
|
@ -246,19 +194,14 @@ inline float fast_roundf(float f) noexcept
|
|||
/* Integral limit, where sub-integral precision is not available for
|
||||
* floats.
|
||||
*/
|
||||
static const float ilim[2]{
|
||||
static constexpr std::array ilim{
|
||||
8388608.0f /* 0x1.0p+23 */,
|
||||
-8388608.0f /* -0x1.0p+23 */
|
||||
};
|
||||
unsigned int sign, expo;
|
||||
union {
|
||||
float f;
|
||||
unsigned int i;
|
||||
} conv;
|
||||
const unsigned int conv_i{al::bit_cast<unsigned int>(f)};
|
||||
|
||||
conv.f = f;
|
||||
sign = (conv.i>>31)&0x01;
|
||||
expo = (conv.i>>23)&0xff;
|
||||
const unsigned int sign{(conv_i>>31)&0x01};
|
||||
const unsigned int expo{(conv_i>>23)&0xff};
|
||||
|
||||
if(expo >= 150/*+23*/) UNLIKELY
|
||||
{
|
||||
|
|
@ -275,20 +218,18 @@ inline float fast_roundf(float f) noexcept
|
|||
* optimize this out because of non-associative rules on floating-point
|
||||
* math (as long as you don't use -fassociative-math,
|
||||
* -funsafe-math-optimizations, -ffast-math, or -Ofast, in which case this
|
||||
* may break).
|
||||
* may break without __builtin_assoc_barrier support).
|
||||
*/
|
||||
#if HAS_BUILTIN(__builtin_assoc_barrier)
|
||||
return __builtin_assoc_barrier(f + ilim[sign]) - ilim[sign];
|
||||
#else
|
||||
f += ilim[sign];
|
||||
return f - ilim[sign];
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
constexpr const T& clamp(const T& value, const T& min_value, const T& max_value) noexcept
|
||||
{
|
||||
return std::min(std::max(value, min_value), max_value);
|
||||
}
|
||||
|
||||
// Converts level (mB) to gain.
|
||||
inline float level_mb_to_gain(float x)
|
||||
{
|
||||
|
|
@ -302,7 +243,7 @@ inline float gain_to_level_mb(float x)
|
|||
{
|
||||
if (x <= 0.0f)
|
||||
return -10'000.0f;
|
||||
return maxf(std::log10(x) * 2'000.0f, -10'000.0f);
|
||||
return std::max(std::log10(x) * 2'000.0f, -10'000.0f);
|
||||
}
|
||||
|
||||
#endif /* AL_NUMERIC_H */
|
||||
|
|
|
|||
|
|
@ -1,353 +0,0 @@
|
|||
#ifndef AL_OPTIONAL_H
|
||||
#define AL_OPTIONAL_H
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
struct nullopt_t { };
|
||||
struct in_place_t { };
|
||||
|
||||
constexpr nullopt_t nullopt{};
|
||||
constexpr in_place_t in_place{};
|
||||
|
||||
#define NOEXCEPT_AS(...) noexcept(noexcept(__VA_ARGS__))
|
||||
|
||||
namespace detail_ {
|
||||
/* Base storage struct for an optional. Defines a trivial destructor, for types
|
||||
* that can be trivially destructed.
|
||||
*/
|
||||
template<typename T, bool = std::is_trivially_destructible<T>::value>
|
||||
struct optstore_base {
|
||||
bool mHasValue{false};
|
||||
union {
|
||||
char mDummy{};
|
||||
T mValue;
|
||||
};
|
||||
|
||||
constexpr optstore_base() noexcept { }
|
||||
template<typename ...Args>
|
||||
constexpr explicit optstore_base(in_place_t, Args&& ...args)
|
||||
noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
: mHasValue{true}, mValue{std::forward<Args>(args)...}
|
||||
{ }
|
||||
~optstore_base() = default;
|
||||
};
|
||||
|
||||
/* Specialization needing a non-trivial destructor. */
|
||||
template<typename T>
|
||||
struct optstore_base<T, false> {
|
||||
bool mHasValue{false};
|
||||
union {
|
||||
char mDummy{};
|
||||
T mValue;
|
||||
};
|
||||
|
||||
constexpr optstore_base() noexcept { }
|
||||
template<typename ...Args>
|
||||
constexpr explicit optstore_base(in_place_t, Args&& ...args)
|
||||
noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
: mHasValue{true}, mValue{std::forward<Args>(args)...}
|
||||
{ }
|
||||
~optstore_base() { if(mHasValue) al::destroy_at(std::addressof(mValue)); }
|
||||
};
|
||||
|
||||
/* Next level of storage, which defines helpers to construct and destruct the
|
||||
* stored object.
|
||||
*/
|
||||
template<typename T>
|
||||
struct optstore_helper : public optstore_base<T> {
|
||||
using optstore_base<T>::optstore_base;
|
||||
|
||||
template<typename... Args>
|
||||
constexpr void construct(Args&& ...args) noexcept(std::is_nothrow_constructible<T, Args...>::value)
|
||||
{
|
||||
al::construct_at(std::addressof(this->mValue), std::forward<Args>(args)...);
|
||||
this->mHasValue = true;
|
||||
}
|
||||
|
||||
constexpr void reset() noexcept
|
||||
{
|
||||
if(this->mHasValue)
|
||||
al::destroy_at(std::addressof(this->mValue));
|
||||
this->mHasValue = false;
|
||||
}
|
||||
|
||||
constexpr void assign(const optstore_helper &rhs)
|
||||
noexcept(std::is_nothrow_copy_constructible<T>::value
|
||||
&& std::is_nothrow_copy_assignable<T>::value)
|
||||
{
|
||||
if(!rhs.mHasValue)
|
||||
this->reset();
|
||||
else if(this->mHasValue)
|
||||
this->mValue = rhs.mValue;
|
||||
else
|
||||
this->construct(rhs.mValue);
|
||||
}
|
||||
|
||||
constexpr void assign(optstore_helper&& rhs)
|
||||
noexcept(std::is_nothrow_move_constructible<T>::value
|
||||
&& std::is_nothrow_move_assignable<T>::value)
|
||||
{
|
||||
if(!rhs.mHasValue)
|
||||
this->reset();
|
||||
else if(this->mHasValue)
|
||||
this->mValue = std::move(rhs.mValue);
|
||||
else
|
||||
this->construct(std::move(rhs.mValue));
|
||||
}
|
||||
};
|
||||
|
||||
/* Define copy and move constructors and assignment operators, which may or may
|
||||
* not be trivial.
|
||||
*/
|
||||
template<typename T, bool trivial_copy = std::is_trivially_copy_constructible<T>::value,
|
||||
bool trivial_move = std::is_trivially_move_constructible<T>::value,
|
||||
/* Trivial assignment is dependent on trivial construction+destruction. */
|
||||
bool = trivial_copy && std::is_trivially_copy_assignable<T>::value
|
||||
&& std::is_trivially_destructible<T>::value,
|
||||
bool = trivial_move && std::is_trivially_move_assignable<T>::value
|
||||
&& std::is_trivially_destructible<T>::value>
|
||||
struct optional_storage;
|
||||
|
||||
/* Some versions of GCC have issues with 'this' in the following noexcept(...)
|
||||
* statements, so this macro is a workaround.
|
||||
*/
|
||||
#define _this std::declval<optional_storage*>()
|
||||
|
||||
/* Completely trivial. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, true, true> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage&) = default;
|
||||
constexpr optional_storage& operator=(optional_storage&&) = default;
|
||||
};
|
||||
|
||||
/* Non-trivial move assignment. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, true, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage&) = default;
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial move construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, false, true, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
|
||||
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
|
||||
constexpr optional_storage& operator=(const optional_storage&) = default;
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial copy assignment. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, false, true> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&&) = default;
|
||||
};
|
||||
|
||||
/* Non-trivial copy construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, false, true, false, true> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
|
||||
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&&) = default;
|
||||
};
|
||||
|
||||
/* Non-trivial assignment. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, true, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial assignment, non-trivial move construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, true, false, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage&) = default;
|
||||
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
|
||||
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Non-trivial assignment, non-trivial copy construction. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, false, true, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
|
||||
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
|
||||
constexpr optional_storage(optional_storage&&) = default;
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
/* Completely non-trivial. */
|
||||
template<typename T>
|
||||
struct optional_storage<T, false, false, false, false> : public optstore_helper<T> {
|
||||
using optstore_helper<T>::optstore_helper;
|
||||
constexpr optional_storage() noexcept = default;
|
||||
constexpr optional_storage(const optional_storage &rhs) NOEXCEPT_AS(_this->construct(rhs.mValue))
|
||||
{ if(rhs.mHasValue) this->construct(rhs.mValue); }
|
||||
constexpr optional_storage(optional_storage&& rhs) NOEXCEPT_AS(_this->construct(std::move(rhs.mValue)))
|
||||
{ if(rhs.mHasValue) this->construct(std::move(rhs.mValue)); }
|
||||
constexpr optional_storage& operator=(const optional_storage &rhs) NOEXCEPT_AS(_this->assign(rhs))
|
||||
{ this->assign(rhs); return *this; }
|
||||
constexpr optional_storage& operator=(optional_storage&& rhs) NOEXCEPT_AS(_this->assign(std::move(rhs)))
|
||||
{ this->assign(std::move(rhs)); return *this; }
|
||||
};
|
||||
|
||||
#undef _this
|
||||
|
||||
} // namespace detail_
|
||||
|
||||
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
|
||||
|
||||
template<typename T>
|
||||
class optional {
|
||||
using storage_t = detail_::optional_storage<T>;
|
||||
|
||||
storage_t mStore{};
|
||||
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
constexpr optional() = default;
|
||||
constexpr optional(const optional&) = default;
|
||||
constexpr optional(optional&&) = default;
|
||||
constexpr optional(nullopt_t) noexcept { }
|
||||
template<typename ...Args>
|
||||
constexpr explicit optional(in_place_t, Args&& ...args)
|
||||
NOEXCEPT_AS(storage_t{al::in_place, std::forward<Args>(args)...})
|
||||
: mStore{al::in_place, std::forward<Args>(args)...}
|
||||
{ }
|
||||
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
|
||||
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
|
||||
&& !std::is_same<std::decay_t<U>, optional<T>>::value
|
||||
&& std::is_convertible<U&&, T>::value)>
|
||||
constexpr optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
|
||||
: mStore{al::in_place, std::forward<U>(rhs)}
|
||||
{ }
|
||||
template<typename U, REQUIRES(std::is_constructible<T, U&&>::value
|
||||
&& !std::is_same<std::decay_t<U>, al::in_place_t>::value
|
||||
&& !std::is_same<std::decay_t<U>, optional<T>>::value
|
||||
&& !std::is_convertible<U&&, T>::value)>
|
||||
constexpr explicit optional(U&& rhs) NOEXCEPT_AS(storage_t{al::in_place, std::forward<U>(rhs)})
|
||||
: mStore{al::in_place, std::forward<U>(rhs)}
|
||||
{ }
|
||||
~optional() = default;
|
||||
|
||||
constexpr optional& operator=(const optional&) = default;
|
||||
constexpr optional& operator=(optional&&) = default;
|
||||
constexpr optional& operator=(nullopt_t) noexcept { mStore.reset(); return *this; }
|
||||
template<typename U=T>
|
||||
constexpr std::enable_if_t<std::is_constructible<T, U>::value
|
||||
&& std::is_assignable<T&, U>::value
|
||||
&& !std::is_same<std::decay_t<U>, optional<T>>::value
|
||||
&& (!std::is_same<std::decay_t<U>, T>::value || !std::is_scalar<U>::value),
|
||||
optional&> operator=(U&& rhs)
|
||||
{
|
||||
if(mStore.mHasValue)
|
||||
mStore.mValue = std::forward<U>(rhs);
|
||||
else
|
||||
mStore.construct(std::forward<U>(rhs));
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr const T* operator->() const { return std::addressof(mStore.mValue); }
|
||||
constexpr T* operator->() { return std::addressof(mStore.mValue); }
|
||||
constexpr const T& operator*() const& { return mStore.mValue; }
|
||||
constexpr T& operator*() & { return mStore.mValue; }
|
||||
constexpr const T&& operator*() const&& { return std::move(mStore.mValue); }
|
||||
constexpr T&& operator*() && { return std::move(mStore.mValue); }
|
||||
|
||||
constexpr explicit operator bool() const noexcept { return mStore.mHasValue; }
|
||||
constexpr bool has_value() const noexcept { return mStore.mHasValue; }
|
||||
|
||||
constexpr T& value() & { return mStore.mValue; }
|
||||
constexpr const T& value() const& { return mStore.mValue; }
|
||||
constexpr T&& value() && { return std::move(mStore.mValue); }
|
||||
constexpr const T&& value() const&& { return std::move(mStore.mValue); }
|
||||
|
||||
template<typename U>
|
||||
constexpr T value_or(U&& defval) const&
|
||||
{ return bool(*this) ? **this : static_cast<T>(std::forward<U>(defval)); }
|
||||
template<typename U>
|
||||
constexpr T value_or(U&& defval) &&
|
||||
{ return bool(*this) ? std::move(**this) : static_cast<T>(std::forward<U>(defval)); }
|
||||
|
||||
template<typename ...Args>
|
||||
constexpr T& emplace(Args&& ...args)
|
||||
{
|
||||
mStore.reset();
|
||||
mStore.construct(std::forward<Args>(args)...);
|
||||
return mStore.mValue;
|
||||
}
|
||||
template<typename U, typename ...Args>
|
||||
constexpr std::enable_if_t<std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value,
|
||||
T&> emplace(std::initializer_list<U> il, Args&& ...args)
|
||||
{
|
||||
mStore.reset();
|
||||
mStore.construct(il, std::forward<Args>(args)...);
|
||||
return mStore.mValue;
|
||||
}
|
||||
|
||||
constexpr void reset() noexcept { mStore.reset(); }
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
constexpr optional<std::decay_t<T>> make_optional(T&& arg)
|
||||
{ return optional<std::decay_t<T>>{in_place, std::forward<T>(arg)}; }
|
||||
|
||||
template<typename T, typename... Args>
|
||||
constexpr optional<T> make_optional(Args&& ...args)
|
||||
{ return optional<T>{in_place, std::forward<Args>(args)...}; }
|
||||
|
||||
template<typename T, typename U, typename... Args>
|
||||
constexpr optional<T> make_optional(std::initializer_list<U> il, Args&& ...args)
|
||||
{ return optional<T>{in_place, il, std::forward<Args>(args)...}; }
|
||||
|
||||
#undef REQUIRES
|
||||
#undef NOEXCEPT_AS
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_OPTIONAL_H */
|
||||
|
|
@ -20,8 +20,7 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "opthelpers.h"
|
||||
#include "threads.h"
|
||||
#include "alsem.h"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
|
|
@ -32,44 +31,14 @@
|
|||
|
||||
#include <limits>
|
||||
|
||||
void althrd_setname(const char *name)
|
||||
{
|
||||
#if defined(_MSC_VER) && !defined(_M_ARM)
|
||||
|
||||
#define MS_VC_EXCEPTION 0x406D1388
|
||||
#pragma pack(push,8)
|
||||
struct {
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
} info;
|
||||
#pragma pack(pop)
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = ~DWORD{0};
|
||||
info.dwFlags = 0;
|
||||
|
||||
__try {
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except(EXCEPTION_CONTINUE_EXECUTION) {
|
||||
}
|
||||
#undef MS_VC_EXCEPTION
|
||||
|
||||
#else
|
||||
|
||||
(void)name;
|
||||
#endif
|
||||
}
|
||||
|
||||
namespace al {
|
||||
|
||||
semaphore::semaphore(unsigned int initial)
|
||||
{
|
||||
if(initial > static_cast<unsigned int>(std::numeric_limits<int>::max()))
|
||||
throw std::system_error(std::make_error_code(std::errc::value_too_large));
|
||||
mSem = CreateSemaphore(nullptr, initial, std::numeric_limits<int>::max(), nullptr);
|
||||
mSem = CreateSemaphoreW(nullptr, static_cast<LONG>(initial), std::numeric_limits<int>::max(),
|
||||
nullptr);
|
||||
if(mSem == nullptr)
|
||||
throw std::system_error(std::make_error_code(std::errc::resource_unavailable_try_again));
|
||||
}
|
||||
|
|
@ -93,49 +62,8 @@ bool semaphore::try_wait() noexcept
|
|||
|
||||
#else
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef HAVE_PTHREAD_NP_H
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
#include <tuple>
|
||||
|
||||
namespace {
|
||||
|
||||
using setname_t1 = int(*)(const char*);
|
||||
using setname_t2 = int(*)(pthread_t, const char*);
|
||||
using setname_t3 = void(*)(pthread_t, const char*);
|
||||
using setname_t4 = int(*)(pthread_t, const char*, void*);
|
||||
|
||||
void setname_caller(setname_t1 func, const char *name)
|
||||
{ func(name); }
|
||||
|
||||
void setname_caller(setname_t2 func, const char *name)
|
||||
{ func(pthread_self(), name); }
|
||||
|
||||
void setname_caller(setname_t3 func, const char *name)
|
||||
{ func(pthread_self(), name); }
|
||||
|
||||
void setname_caller(setname_t4 func, const char *name)
|
||||
{ func(pthread_self(), "%s", static_cast<void*>(const_cast<char*>(name))); }
|
||||
|
||||
} // namespace
|
||||
|
||||
void althrd_setname(const char *name)
|
||||
{
|
||||
#if defined(HAVE_PTHREAD_SET_NAME_NP)
|
||||
setname_caller(pthread_set_name_np, name);
|
||||
#elif defined(HAVE_PTHREAD_SETNAME_NP)
|
||||
setname_caller(pthread_setname_np, name);
|
||||
#endif
|
||||
/* Avoid unused function/parameter warnings. */
|
||||
std::ignore = name;
|
||||
std::ignore = static_cast<void(*)(setname_t1,const char*)>(&setname_caller);
|
||||
std::ignore = static_cast<void(*)(setname_t2,const char*)>(&setname_caller);
|
||||
std::ignore = static_cast<void(*)(setname_t3,const char*)>(&setname_caller);
|
||||
std::ignore = static_cast<void(*)(setname_t4,const char*)>(&setname_caller);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
/* Do not try using libdispatch on systems where it is absent. */
|
||||
#if defined(AL_APPLE_HAVE_DISPATCH)
|
||||
|
||||
namespace al {
|
||||
|
||||
43
Engine/lib/openal-soft/common/alsem.h
Normal file
43
Engine/lib/openal-soft/common/alsem.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#ifndef COMMON_ALSEM_H
|
||||
#define COMMON_ALSEM_H
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <TargetConditionals.h>
|
||||
#if (((MAC_OS_X_VERSION_MIN_REQUIRED > 1050) && !defined(__ppc__)) || TARGET_OS_IOS || TARGET_OS_TV)
|
||||
#include <dispatch/dispatch.h>
|
||||
#define AL_APPLE_HAVE_DISPATCH 1
|
||||
#else
|
||||
#include <semaphore.h> /* Fallback option for Apple without a working libdispatch */
|
||||
#endif
|
||||
#elif !defined(_WIN32)
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
namespace al {
|
||||
|
||||
class semaphore {
|
||||
#ifdef _WIN32
|
||||
using native_type = void*;
|
||||
#elif defined(AL_APPLE_HAVE_DISPATCH)
|
||||
using native_type = dispatch_semaphore_t;
|
||||
#else
|
||||
using native_type = sem_t;
|
||||
#endif
|
||||
native_type mSem{};
|
||||
|
||||
public:
|
||||
semaphore(unsigned int initial=0);
|
||||
semaphore(const semaphore&) = delete;
|
||||
~semaphore();
|
||||
|
||||
semaphore& operator=(const semaphore&) = delete;
|
||||
|
||||
void post();
|
||||
void wait() noexcept;
|
||||
bool try_wait() noexcept;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* COMMON_ALSEM_H */
|
||||
|
|
@ -1,180 +1,250 @@
|
|||
#ifndef AL_SPAN_H
|
||||
#define AL_SPAN_H
|
||||
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <initializer_list>
|
||||
#include <iterator>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
#include "alassert.h"
|
||||
#include "almalloc.h"
|
||||
#include "altraits.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
/* This is here primarily to help ensure proper behavior for span's iterators,
|
||||
* being an actual object with member functions instead of a raw pointer (which
|
||||
* has requirements like + and - working with ptrdiff_t). This also helps
|
||||
* silence clang-tidy's pointer arithmetic warnings for span and FlexArray
|
||||
* iterators. It otherwise behaves like a plain pointer and should optimize
|
||||
* accordingly.
|
||||
*
|
||||
* Shouldn't be needed once we use std::span in C++20.
|
||||
*/
|
||||
template<typename T>
|
||||
constexpr auto size(const T &cont) noexcept(noexcept(cont.size())) -> decltype(cont.size())
|
||||
{ return cont.size(); }
|
||||
class ptr_wrapper {
|
||||
static_assert(std::is_pointer_v<T>);
|
||||
T mPointer{};
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr size_t size(const T (&)[N]) noexcept
|
||||
{ return N; }
|
||||
public:
|
||||
using value_type = std::remove_pointer_t<T>;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
using iterator_category = std::random_access_iterator_tag;
|
||||
|
||||
explicit constexpr ptr_wrapper(T ptr) : mPointer{ptr} { }
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
constexpr auto operator++() noexcept -> ptr_wrapper& { ++mPointer; return *this; }
|
||||
constexpr auto operator--() noexcept -> ptr_wrapper& { --mPointer; return *this; }
|
||||
constexpr auto operator++(int) noexcept -> ptr_wrapper
|
||||
{
|
||||
auto temp = *this;
|
||||
++*this;
|
||||
return temp;
|
||||
}
|
||||
constexpr auto operator--(int) noexcept -> ptr_wrapper
|
||||
{
|
||||
auto temp = *this;
|
||||
--*this;
|
||||
return temp;
|
||||
}
|
||||
|
||||
constexpr
|
||||
auto operator+=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer += n; return *this; }
|
||||
constexpr
|
||||
auto operator-=(std::ptrdiff_t n) noexcept -> ptr_wrapper& { mPointer -= n; return *this; }
|
||||
|
||||
[[nodiscard]] constexpr auto operator*() const noexcept -> value_type& { return *mPointer; }
|
||||
[[nodiscard]] constexpr auto operator->() const noexcept -> value_type* { return mPointer; }
|
||||
[[nodiscard]] constexpr
|
||||
auto operator[](std::size_t idx) const noexcept -> value_type& {return mPointer[idx];}
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator+(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper
|
||||
{ return ptr_wrapper{lhs.mPointer + n}; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator+(std::ptrdiff_t n, const ptr_wrapper &rhs) noexcept -> ptr_wrapper
|
||||
{ return ptr_wrapper{n + rhs.mPointer}; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator-(const ptr_wrapper &lhs, std::ptrdiff_t n) noexcept -> ptr_wrapper
|
||||
{ return ptr_wrapper{lhs.mPointer - n}; }
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator-(const ptr_wrapper &lhs, const ptr_wrapper &rhs)noexcept->std::ptrdiff_t
|
||||
{ return lhs.mPointer - rhs.mPointer; }
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator==(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer == rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator!=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer != rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator<=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer <= rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator>=(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer >= rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator<(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer < rhs.mPointer; }
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator>(const ptr_wrapper &lhs, const ptr_wrapper &rhs) noexcept -> bool
|
||||
{ return lhs.mPointer > rhs.mPointer; }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
constexpr auto data(T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data())
|
||||
{ return cont.data(); }
|
||||
inline constexpr std::size_t dynamic_extent{static_cast<std::size_t>(-1)};
|
||||
|
||||
template<typename T>
|
||||
constexpr auto data(const T &cont) noexcept(noexcept(cont.data())) -> decltype(cont.data())
|
||||
{ return cont.data(); }
|
||||
|
||||
template<typename T, size_t N>
|
||||
constexpr T* data(T (&arr)[N]) noexcept
|
||||
{ return arr; }
|
||||
|
||||
template<typename T>
|
||||
constexpr const T* data(std::initializer_list<T> list) noexcept
|
||||
{ return list.begin(); }
|
||||
|
||||
|
||||
constexpr size_t dynamic_extent{static_cast<size_t>(-1)};
|
||||
|
||||
template<typename T, size_t E=dynamic_extent>
|
||||
template<typename T, std::size_t E=dynamic_extent>
|
||||
class span;
|
||||
|
||||
namespace detail_ {
|
||||
template<typename... Ts>
|
||||
using void_t = void;
|
||||
|
||||
template<typename T>
|
||||
struct is_span_ : std::false_type { };
|
||||
template<typename T, size_t E>
|
||||
template<typename T, std::size_t E>
|
||||
struct is_span_<span<T,E>> : std::true_type { };
|
||||
template<typename T>
|
||||
constexpr bool is_span_v = is_span_<std::remove_cv_t<T>>::value;
|
||||
inline constexpr bool is_span_v = is_span_<std::remove_cv_t<T>>::value;
|
||||
|
||||
template<typename T>
|
||||
struct is_std_array_ : std::false_type { };
|
||||
template<typename T, size_t N>
|
||||
template<typename T, std::size_t N>
|
||||
struct is_std_array_<std::array<T,N>> : std::true_type { };
|
||||
template<typename T>
|
||||
constexpr bool is_std_array_v = is_std_array_<std::remove_cv_t<T>>::value;
|
||||
inline constexpr bool is_std_array_v = is_std_array_<std::remove_cv_t<T>>::value;
|
||||
|
||||
template<typename T, typename = void>
|
||||
constexpr bool has_size_and_data = false;
|
||||
inline constexpr bool has_size_and_data = false;
|
||||
template<typename T>
|
||||
constexpr bool has_size_and_data<T,
|
||||
void_t<decltype(al::size(std::declval<T>())), decltype(al::data(std::declval<T>()))>>
|
||||
inline constexpr bool has_size_and_data<T,
|
||||
std::void_t<decltype(std::size(std::declval<T>())),decltype(std::data(std::declval<T>()))>>
|
||||
= true;
|
||||
|
||||
template<typename C>
|
||||
inline constexpr bool is_valid_container_type = !is_span_v<C> && !is_std_array_v<C>
|
||||
&& !std::is_array<C>::value && has_size_and_data<C>;
|
||||
|
||||
template<typename T, typename U>
|
||||
constexpr bool is_array_compatible = std::is_convertible<T(*)[],U(*)[]>::value;
|
||||
inline constexpr bool is_array_compatible = std::is_convertible<T(*)[],U(*)[]>::value; /* NOLINT(*-avoid-c-arrays) */
|
||||
|
||||
template<typename C, typename T>
|
||||
constexpr bool is_valid_container = !is_span_v<C> && !is_std_array_v<C>
|
||||
&& !std::is_array<C>::value && has_size_and_data<C>
|
||||
&& is_array_compatible<std::remove_pointer_t<decltype(al::data(std::declval<C&>()))>,T>;
|
||||
inline constexpr bool is_valid_container = is_valid_container_type<C>
|
||||
&& is_array_compatible<std::remove_pointer_t<decltype(std::data(std::declval<C&>()))>,T>;
|
||||
} // namespace detail_
|
||||
|
||||
#define REQUIRES(...) std::enable_if_t<(__VA_ARGS__),bool> = true
|
||||
|
||||
template<typename T, size_t E>
|
||||
template<typename T, std::size_t E>
|
||||
class span {
|
||||
public:
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
using iterator = ptr_wrapper<pointer>;
|
||||
using const_iterator = ptr_wrapper<const_pointer>;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
static constexpr size_t extent{E};
|
||||
static constexpr std::size_t extent{E};
|
||||
|
||||
template<bool is0=(extent == 0), REQUIRES(is0)>
|
||||
constexpr span() noexcept { }
|
||||
template<typename U>
|
||||
constexpr explicit span(U iter, index_type) : mData{to_address(iter)} { }
|
||||
template<typename U, typename V, REQUIRES(!std::is_convertible<V,size_t>::value)>
|
||||
constexpr explicit span(U first, V) : mData{to_address(first)} { }
|
||||
constexpr explicit span(U iter, size_type size_) : mData{::al::to_address(iter)}
|
||||
{ alassert(size_ == extent); }
|
||||
template<typename U, typename V, REQUIRES(!std::is_convertible<V,std::size_t>::value)>
|
||||
constexpr explicit span(U first, V last) : mData{::al::to_address(first)}
|
||||
{ alassert(static_cast<std::size_t>(last-first) == extent); }
|
||||
|
||||
constexpr span(type_identity_t<element_type> (&arr)[E]) noexcept
|
||||
: span{al::data(arr), al::size(arr)}
|
||||
{ }
|
||||
constexpr span(std::array<value_type,E> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
|
||||
template<typename U=T, REQUIRES(std::is_const<U>::value)>
|
||||
constexpr span(const std::array<value_type,E> &arr) noexcept
|
||||
: span{al::data(arr), al::size(arr)}
|
||||
{ }
|
||||
template<std::size_t N>
|
||||
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */
|
||||
: mData{std::data(arr)}
|
||||
{ static_assert(N == extent); }
|
||||
template<std::size_t N>
|
||||
constexpr span(std::array<value_type,N> &arr) noexcept : mData{std::data(arr)}
|
||||
{ static_assert(N == extent); }
|
||||
template<typename U=T, std::size_t N, REQUIRES(std::is_const<U>::value)>
|
||||
constexpr span(const std::array<value_type,N> &arr) noexcept : mData{std::data(arr)}
|
||||
{ static_assert(N == extent); }
|
||||
|
||||
template<typename U, REQUIRES(detail_::is_valid_container<U, element_type>)>
|
||||
constexpr explicit span(U&& cont) : span{al::data(cont), al::size(cont)} { }
|
||||
constexpr explicit span(U&& cont) : span{std::data(cont), std::size(cont)} { }
|
||||
|
||||
template<typename U, index_type N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
&& detail_::is_array_compatible<U,element_type> && N == dynamic_extent)>
|
||||
constexpr explicit span(const span<U,N> &span_) noexcept
|
||||
: span{al::data(span_), al::size(span_)}
|
||||
{ }
|
||||
template<typename U, index_type N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
constexpr explicit span(const span<U,N> &span_) noexcept : mData{std::data(span_)}
|
||||
{ alassert(std::size(span_) == extent); }
|
||||
template<typename U, std::size_t N, REQUIRES(!std::is_same<element_type,U>::value
|
||||
&& detail_::is_array_compatible<U,element_type> && N == extent)>
|
||||
constexpr span(const span<U,N> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
|
||||
constexpr span(const span<U,N> &span_) noexcept : mData{std::data(span_)} { }
|
||||
constexpr span(const span&) noexcept = default;
|
||||
|
||||
constexpr span& operator=(const span &rhs) noexcept = default;
|
||||
|
||||
constexpr reference front() const { return *mData; }
|
||||
constexpr reference back() const { return *(mData+E-1); }
|
||||
constexpr reference operator[](index_type idx) const { return mData[idx]; }
|
||||
constexpr pointer data() const noexcept { return mData; }
|
||||
[[nodiscard]] constexpr auto front() const -> reference { return mData[0]; }
|
||||
[[nodiscard]] constexpr auto back() const -> reference { return mData[E-1]; }
|
||||
[[nodiscard]] constexpr auto operator[](size_type idx) const -> reference { return mData[idx]; }
|
||||
[[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; }
|
||||
|
||||
constexpr index_type size() const noexcept { return E; }
|
||||
constexpr index_type size_bytes() const noexcept { return E * sizeof(value_type); }
|
||||
constexpr bool empty() const noexcept { return E == 0; }
|
||||
[[nodiscard]] constexpr auto size() const noexcept -> size_type { return E; }
|
||||
[[nodiscard]] constexpr auto size_bytes() const noexcept -> size_type { return E * sizeof(value_type); }
|
||||
[[nodiscard]] constexpr auto empty() const noexcept -> bool { return E == 0; }
|
||||
|
||||
constexpr iterator begin() const noexcept { return mData; }
|
||||
constexpr iterator end() const noexcept { return mData+E; }
|
||||
constexpr const_iterator cbegin() const noexcept { return mData; }
|
||||
constexpr const_iterator cend() const noexcept { return mData+E; }
|
||||
[[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; }
|
||||
[[nodiscard]] constexpr auto end() const noexcept -> iterator { return iterator{mData+E}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cend() const noexcept -> const_iterator { return const_iterator{mData+E}; }
|
||||
|
||||
constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; }
|
||||
constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; }
|
||||
constexpr const_reverse_iterator crbegin() const noexcept
|
||||
{ return const_reverse_iterator{cend()}; }
|
||||
constexpr const_reverse_iterator crend() const noexcept
|
||||
{ return const_reverse_iterator{cbegin()}; }
|
||||
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
|
||||
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> first() const
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto first() const noexcept -> span<element_type,C>
|
||||
{
|
||||
static_assert(E >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData, C};
|
||||
}
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> last() const
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto last() const noexcept -> span<element_type,C>
|
||||
{
|
||||
static_assert(E >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData+(E-C), C};
|
||||
}
|
||||
|
||||
template<size_t O, size_t C>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
template<std::size_t O, std::size_t C>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const noexcept -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
static_assert(E >= O, "Offset exceeds extent");
|
||||
static_assert(E-O >= C, "New size exceeds original capacity");
|
||||
return span<element_type,C>{mData+O, C};
|
||||
}
|
||||
|
||||
template<size_t O, size_t C=dynamic_extent>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
|
||||
template<std::size_t O, std::size_t C=dynamic_extent>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const noexcept -> std::enable_if_t<C==dynamic_extent,span<element_type,E-O>>
|
||||
{
|
||||
static_assert(E >= O, "Offset exceeds extent");
|
||||
return span<element_type,E-O>{mData+O, E-O};
|
||||
|
|
@ -184,10 +254,13 @@ public:
|
|||
* defining the specialization. As a result, these methods need to be
|
||||
* defined later.
|
||||
*/
|
||||
constexpr span<element_type,dynamic_extent> first(size_t count) const;
|
||||
constexpr span<element_type,dynamic_extent> last(size_t count) const;
|
||||
constexpr span<element_type,dynamic_extent> subspan(size_t offset,
|
||||
size_t count=dynamic_extent) const;
|
||||
[[nodiscard]] constexpr
|
||||
auto first(std::size_t count) const noexcept -> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr
|
||||
auto last(std::size_t count) const noexcept -> span<element_type,dynamic_extent>;
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept
|
||||
-> span<element_type,dynamic_extent>;
|
||||
|
||||
private:
|
||||
pointer mData{nullptr};
|
||||
|
|
@ -198,7 +271,7 @@ class span<T,dynamic_extent> {
|
|||
public:
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using size_type = std::size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
|
|
@ -206,146 +279,175 @@ public:
|
|||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
using iterator = pointer;
|
||||
using const_iterator = const_pointer;
|
||||
using iterator = ptr_wrapper<pointer>;
|
||||
using const_iterator = ptr_wrapper<const_pointer>;
|
||||
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||
|
||||
static constexpr size_t extent{dynamic_extent};
|
||||
static constexpr std::size_t extent{dynamic_extent};
|
||||
|
||||
constexpr span() noexcept = default;
|
||||
template<typename U>
|
||||
constexpr span(U iter, index_type count)
|
||||
: mData{to_address(iter)}, mDataEnd{to_address(iter)+count}
|
||||
constexpr span(U iter, size_type count) : mData{::al::to_address(iter)}, mDataLength{count}
|
||||
{ }
|
||||
template<typename U, typename V, REQUIRES(!std::is_convertible<V,size_t>::value)>
|
||||
constexpr span(U first, V last) : span{to_address(first), static_cast<size_t>(last-first)}
|
||||
template<typename U, typename V, REQUIRES(!std::is_convertible<V,std::size_t>::value)>
|
||||
constexpr span(U first, V last)
|
||||
: span{::al::to_address(first), static_cast<std::size_t>(last-first)}
|
||||
{ }
|
||||
|
||||
template<size_t N>
|
||||
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept
|
||||
: span{al::data(arr), al::size(arr)}
|
||||
template<std::size_t N>
|
||||
constexpr span(type_identity_t<element_type> (&arr)[N]) noexcept /* NOLINT(*-avoid-c-arrays) */
|
||||
: mData{std::data(arr)}, mDataLength{std::size(arr)}
|
||||
{ }
|
||||
template<size_t N>
|
||||
constexpr span(std::array<value_type,N> &arr) noexcept : span{al::data(arr), al::size(arr)} { }
|
||||
template<size_t N, typename U=T, REQUIRES(std::is_const<U>::value)>
|
||||
template<std::size_t N>
|
||||
constexpr span(std::array<value_type,N> &arr) noexcept
|
||||
: mData{std::data(arr)}, mDataLength{std::size(arr)}
|
||||
{ }
|
||||
template<std::size_t N, typename U=T, REQUIRES(std::is_const<U>::value)>
|
||||
constexpr span(const std::array<value_type,N> &arr) noexcept
|
||||
: span{al::data(arr), al::size(arr)}
|
||||
: mData{std::data(arr)}, mDataLength{std::size(arr)}
|
||||
{ }
|
||||
|
||||
template<typename U, REQUIRES(detail_::is_valid_container<U, element_type>)>
|
||||
constexpr span(U&& cont) : span{al::data(cont), al::size(cont)} { }
|
||||
constexpr span(U&& cont) : span{std::data(cont), std::size(cont)} { }
|
||||
|
||||
template<typename U, size_t N, REQUIRES((!std::is_same<element_type,U>::value || extent != N)
|
||||
&& detail_::is_array_compatible<U,element_type>)>
|
||||
constexpr span(const span<U,N> &span_) noexcept : span{al::data(span_), al::size(span_)} { }
|
||||
template<typename U, std::size_t N, REQUIRES(detail_::is_array_compatible<U,element_type>
|
||||
&& (!std::is_same<element_type,U>::value || extent != N))>
|
||||
constexpr span(const span<U,N> &span_) noexcept : span{std::data(span_), std::size(span_)} { }
|
||||
constexpr span(const span&) noexcept = default;
|
||||
|
||||
constexpr span& operator=(const span &rhs) noexcept = default;
|
||||
|
||||
constexpr reference front() const { return *mData; }
|
||||
constexpr reference back() const { return *(mDataEnd-1); }
|
||||
constexpr reference operator[](index_type idx) const { return mData[idx]; }
|
||||
constexpr pointer data() const noexcept { return mData; }
|
||||
[[nodiscard]] constexpr auto front() const -> reference { return mData[0]; }
|
||||
[[nodiscard]] constexpr auto back() const -> reference { return mData[mDataLength-1]; }
|
||||
[[nodiscard]] constexpr auto operator[](size_type idx) const -> reference {return mData[idx];}
|
||||
[[nodiscard]] constexpr auto data() const noexcept -> pointer { return mData; }
|
||||
|
||||
constexpr index_type size() const noexcept { return static_cast<index_type>(mDataEnd-mData); }
|
||||
constexpr index_type size_bytes() const noexcept
|
||||
{ return static_cast<index_type>(mDataEnd-mData) * sizeof(value_type); }
|
||||
constexpr bool empty() const noexcept { return mData == mDataEnd; }
|
||||
[[nodiscard]] constexpr auto size() const noexcept -> size_type { return mDataLength; }
|
||||
[[nodiscard]] constexpr
|
||||
auto size_bytes() const noexcept -> size_type { return mDataLength * sizeof(value_type); }
|
||||
[[nodiscard]] constexpr auto empty() const noexcept -> bool { return mDataLength == 0; }
|
||||
|
||||
constexpr iterator begin() const noexcept { return mData; }
|
||||
constexpr iterator end() const noexcept { return mDataEnd; }
|
||||
constexpr const_iterator cbegin() const noexcept { return mData; }
|
||||
constexpr const_iterator cend() const noexcept { return mDataEnd; }
|
||||
[[nodiscard]] constexpr auto begin() const noexcept -> iterator { return iterator{mData}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto end() const noexcept -> iterator { return iterator{mData+mDataLength}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cbegin() const noexcept -> const_iterator { return const_iterator{mData}; }
|
||||
[[nodiscard]] constexpr
|
||||
auto cend() const noexcept -> const_iterator { return const_iterator{mData+mDataLength}; }
|
||||
|
||||
constexpr reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; }
|
||||
constexpr reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; }
|
||||
constexpr const_reverse_iterator crbegin() const noexcept
|
||||
{ return const_reverse_iterator{cend()}; }
|
||||
constexpr const_reverse_iterator crend() const noexcept
|
||||
{ return const_reverse_iterator{cbegin()}; }
|
||||
[[nodiscard]] constexpr auto rbegin() const noexcept -> reverse_iterator { return end(); }
|
||||
[[nodiscard]] constexpr auto rend() const noexcept -> reverse_iterator { return begin(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crbegin() const noexcept -> const_reverse_iterator { return cend(); }
|
||||
[[nodiscard]] constexpr
|
||||
auto crend() const noexcept -> const_reverse_iterator { return cbegin(); }
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> first() const
|
||||
{ return span<element_type,C>{mData, C}; }
|
||||
|
||||
constexpr span first(size_t count) const
|
||||
{ return (count >= size()) ? *this : span{mData, mData+count}; }
|
||||
|
||||
template<size_t C>
|
||||
constexpr span<element_type,C> last() const
|
||||
{ return span<element_type,C>{mDataEnd-C, C}; }
|
||||
|
||||
constexpr span last(size_t count) const
|
||||
{ return (count >= size()) ? *this : span{mDataEnd-count, mDataEnd}; }
|
||||
|
||||
template<size_t O, size_t C>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{ return span<element_type,C>{mData+O, C}; }
|
||||
|
||||
template<size_t O, size_t C=dynamic_extent>
|
||||
constexpr auto subspan() const -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
|
||||
{ return span<element_type,C>{mData+O, mDataEnd}; }
|
||||
|
||||
constexpr span subspan(size_t offset, size_t count=dynamic_extent) const
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto first() const noexcept -> span<element_type,C>
|
||||
{
|
||||
return (offset > size()) ? span{} :
|
||||
(count >= size()-offset) ? span{mData+offset, mDataEnd} :
|
||||
span{mData+offset, mData+offset+count};
|
||||
assert(C <= mDataLength);
|
||||
return span<element_type,C>{mData, C};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto first(std::size_t count) const noexcept -> span
|
||||
{
|
||||
assert(count <= mDataLength);
|
||||
return span{mData, count};
|
||||
}
|
||||
|
||||
template<std::size_t C>
|
||||
[[nodiscard]] constexpr auto last() const noexcept -> span<element_type,C>
|
||||
{
|
||||
assert(C <= mDataLength);
|
||||
return span<element_type,C>{mData+mDataLength-C, C};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr auto last(std::size_t count) const noexcept -> span
|
||||
{
|
||||
assert(count <= mDataLength);
|
||||
return span{mData+mDataLength-count, count};
|
||||
}
|
||||
|
||||
template<std::size_t O, std::size_t C>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const noexcept -> std::enable_if_t<C!=dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
assert(O <= mDataLength);
|
||||
assert(C <= mDataLength-O);
|
||||
return span<element_type,C>{mData+O, C};
|
||||
}
|
||||
|
||||
template<std::size_t O, std::size_t C=dynamic_extent>
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan() const noexcept -> std::enable_if_t<C==dynamic_extent,span<element_type,C>>
|
||||
{
|
||||
assert(O <= mDataLength);
|
||||
return span<element_type,C>{mData+O, mDataLength-O};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr
|
||||
auto subspan(std::size_t offset, std::size_t count=dynamic_extent) const noexcept -> span
|
||||
{
|
||||
assert(offset <= mDataLength);
|
||||
if(count != dynamic_extent)
|
||||
{
|
||||
assert(count <= mDataLength-offset);
|
||||
return span{mData+offset, count};
|
||||
}
|
||||
return span{mData+offset, mDataLength-offset};
|
||||
}
|
||||
|
||||
private:
|
||||
pointer mData{nullptr};
|
||||
pointer mDataEnd{nullptr};
|
||||
size_type mDataLength{0};
|
||||
};
|
||||
|
||||
template<typename T, size_t E>
|
||||
constexpr inline auto span<T,E>::first(size_t count) const -> span<element_type,dynamic_extent>
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::first(std::size_t count) const noexcept -> span<element_type,dynamic_extent>
|
||||
{
|
||||
return (count >= size()) ? span<element_type>{mData, extent} :
|
||||
span<element_type>{mData, count};
|
||||
assert(count <= size());
|
||||
return span<element_type>{mData, count};
|
||||
}
|
||||
|
||||
template<typename T, size_t E>
|
||||
constexpr inline auto span<T,E>::last(size_t count) const -> span<element_type,dynamic_extent>
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::last(std::size_t count) const noexcept -> span<element_type,dynamic_extent>
|
||||
{
|
||||
return (count >= size()) ? span<element_type>{mData, extent} :
|
||||
span<element_type>{mData+extent-count, count};
|
||||
assert(count <= size());
|
||||
return span<element_type>{mData+size()-count, count};
|
||||
}
|
||||
|
||||
template<typename T, size_t E>
|
||||
constexpr inline auto span<T,E>::subspan(size_t offset, size_t count) const
|
||||
template<typename T, std::size_t E>
|
||||
[[nodiscard]] constexpr
|
||||
auto span<T,E>::subspan(std::size_t offset, std::size_t count) const noexcept
|
||||
-> span<element_type,dynamic_extent>
|
||||
{
|
||||
return (offset > size()) ? span<element_type>{} :
|
||||
(count >= size()-offset) ? span<element_type>{mData+offset, mData+extent} :
|
||||
span<element_type>{mData+offset, mData+offset+count};
|
||||
assert(offset <= size());
|
||||
if(count != dynamic_extent)
|
||||
{
|
||||
assert(count <= size()-offset);
|
||||
return span<element_type>{mData+offset, count};
|
||||
}
|
||||
return span<element_type>{mData+offset, size()-offset};
|
||||
}
|
||||
|
||||
/* Helpers to deal with the lack of user-defined deduction guides (C++17). */
|
||||
template<typename T, typename U>
|
||||
constexpr auto as_span(T ptr, U count_or_end)
|
||||
{
|
||||
using value_type = typename std::pointer_traits<T>::element_type;
|
||||
return span<value_type>{ptr, count_or_end};
|
||||
}
|
||||
template<typename T, size_t N>
|
||||
constexpr auto as_span(T (&arr)[N]) noexcept { return span<T,N>{al::data(arr), al::size(arr)}; }
|
||||
template<typename T, size_t N>
|
||||
constexpr auto as_span(std::array<T,N> &arr) noexcept
|
||||
{ return span<T,N>{al::data(arr), al::size(arr)}; }
|
||||
template<typename T, size_t N>
|
||||
constexpr auto as_span(const std::array<T,N> &arr) noexcept
|
||||
{ return span<std::add_const_t<T>,N>{al::data(arr), al::size(arr)}; }
|
||||
template<typename U, REQUIRES(!detail_::is_span_v<U> && !detail_::is_std_array_v<U>
|
||||
&& !std::is_array<U>::value && detail_::has_size_and_data<U>)>
|
||||
constexpr auto as_span(U&& cont)
|
||||
{
|
||||
using value_type = std::remove_pointer_t<decltype(al::data(std::declval<U&>()))>;
|
||||
return span<value_type>{al::data(cont), al::size(cont)};
|
||||
}
|
||||
template<typename T, size_t N>
|
||||
constexpr auto as_span(span<T,N> span_) noexcept { return span_; }
|
||||
|
||||
template<typename T, typename EndOrSize>
|
||||
span(T, EndOrSize) -> span<std::remove_reference_t<decltype(*std::declval<T&>())>>;
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
span(T (&)[N]) -> span<T, N>; /* NOLINT(*-avoid-c-arrays) */
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
span(std::array<T, N>&) -> span<T, N>;
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
span(const std::array<T, N>&) -> span<const T, N>;
|
||||
|
||||
template<typename C, REQUIRES(detail_::is_valid_container_type<C>)>
|
||||
span(C&&) -> span<std::remove_pointer_t<decltype(std::data(std::declval<C&>()))>>;
|
||||
|
||||
#undef REQUIRES
|
||||
|
||||
|
|
|
|||
|
|
@ -3,43 +3,62 @@
|
|||
|
||||
#include "alstring.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cwctype>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
int to_upper(const char ch)
|
||||
{
|
||||
using char8_traits = std::char_traits<char>;
|
||||
return std::toupper(char8_traits::to_int_type(ch));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace al {
|
||||
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept
|
||||
int case_compare(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{
|
||||
do {
|
||||
const int diff{to_upper(*str0) - to_upper(*str1)};
|
||||
if(diff < 0) return -1;
|
||||
if(diff > 0) return 1;
|
||||
} while(*(str0++) && *(str1++));
|
||||
using Traits = std::string_view::traits_type;
|
||||
|
||||
auto ch0 = str0.cbegin();
|
||||
auto ch1 = str1.cbegin();
|
||||
auto ch1end = ch1 + std::min(str0.size(), str1.size());
|
||||
while(ch1 != ch1end)
|
||||
{
|
||||
const int u0{std::toupper(Traits::to_int_type(*ch0))};
|
||||
const int u1{std::toupper(Traits::to_int_type(*ch1))};
|
||||
if(const int diff{u0-u1}) return diff;
|
||||
++ch0; ++ch1;
|
||||
}
|
||||
|
||||
if(str0.size() < str1.size()) return -1;
|
||||
if(str0.size() > str1.size()) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept
|
||||
{
|
||||
using Traits = std::wstring_view::traits_type;
|
||||
|
||||
auto ch0 = str0.cbegin();
|
||||
auto ch1 = str1.cbegin();
|
||||
auto ch1end = ch1 + std::min(str0.size(), str1.size());
|
||||
while(ch1 != ch1end)
|
||||
{
|
||||
const auto u0 = std::towupper(Traits::to_int_type(*ch0));
|
||||
const auto u1 = std::towupper(Traits::to_int_type(*ch1));
|
||||
if(const auto diff = static_cast<int>(u0-u1)) return diff;
|
||||
++ch0; ++ch1;
|
||||
}
|
||||
|
||||
if(str0.size() < str1.size()) return -1;
|
||||
if(str0.size() > str1.size()) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept
|
||||
{ return case_compare(str0, str1); }
|
||||
|
||||
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept
|
||||
{
|
||||
if(len > 0)
|
||||
{
|
||||
do {
|
||||
const int diff{to_upper(*str0) - to_upper(*str1)};
|
||||
if(diff < 0) return -1;
|
||||
if(diff > 0) return 1;
|
||||
} while(--len && *(str0++) && *(str1++));
|
||||
}
|
||||
return 0;
|
||||
return case_compare(std::string_view{str0, std::min(std::strlen(str0), len)},
|
||||
std::string_view{str1, std::min(std::strlen(str1), len)});
|
||||
}
|
||||
|
||||
} // namespace al
|
||||
|
|
|
|||
|
|
@ -1,16 +1,41 @@
|
|||
#ifndef AL_STRING_H
|
||||
#define AL_STRING_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
|
||||
namespace al {
|
||||
|
||||
/* These would be better served by using a string_view-like span/view with
|
||||
* case-insensitive char traits.
|
||||
*/
|
||||
template<typename T, typename Traits>
|
||||
[[nodiscard]] constexpr
|
||||
auto sizei(const std::basic_string_view<T,Traits> str) noexcept -> int
|
||||
{ return static_cast<int>(std::min<std::size_t>(str.size(), std::numeric_limits<int>::max())); }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool contains(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{ return str0.find(str1) != std::string_view::npos; }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool starts_with(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{ return str0.substr(0, std::min(str0.size(), str1.size())) == str1; }
|
||||
|
||||
[[nodiscard]]
|
||||
constexpr bool ends_with(const std::string_view str0, const std::string_view str1) noexcept
|
||||
{ return str0.substr(str0.size() - std::min(str0.size(), str1.size())) == str1; }
|
||||
|
||||
[[nodiscard]]
|
||||
int case_compare(const std::string_view str0, const std::string_view str1) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int case_compare(const std::wstring_view str0, const std::wstring_view str1) noexcept;
|
||||
|
||||
[[nodiscard]]
|
||||
int strcasecmp(const char *str0, const char *str1) noexcept;
|
||||
[[nodiscard]]
|
||||
int strncasecmp(const char *str0, const char *str1, std::size_t len) noexcept;
|
||||
|
||||
} // namespace al
|
||||
|
|
|
|||
77
Engine/lib/openal-soft/common/althrd_setname.cpp
Normal file
77
Engine/lib/openal-soft/common/althrd_setname.cpp
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
#include "config.h"
|
||||
|
||||
#include "althrd_setname.h"
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
void althrd_setname(const char *name [[maybe_unused]])
|
||||
{
|
||||
#if defined(_MSC_VER) && !defined(_M_ARM)
|
||||
|
||||
#define MS_VC_EXCEPTION 0x406D1388
|
||||
#pragma pack(push,8)
|
||||
struct InfoStruct {
|
||||
DWORD dwType; // Must be 0x1000.
|
||||
LPCSTR szName; // Pointer to name (in user addr space).
|
||||
DWORD dwThreadID; // Thread ID (-1=caller thread).
|
||||
DWORD dwFlags; // Reserved for future use, must be zero.
|
||||
};
|
||||
#pragma pack(pop)
|
||||
InfoStruct info{};
|
||||
info.dwType = 0x1000;
|
||||
info.szName = name;
|
||||
info.dwThreadID = ~DWORD{0};
|
||||
info.dwFlags = 0;
|
||||
|
||||
/* FIXME: How to do this on MinGW? */
|
||||
__try {
|
||||
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info)/sizeof(ULONG_PTR), (ULONG_PTR*)&info);
|
||||
}
|
||||
__except(EXCEPTION_CONTINUE_EXECUTION) {
|
||||
}
|
||||
#undef MS_VC_EXCEPTION
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#include <pthread.h>
|
||||
#ifdef HAVE_PTHREAD_NP_H
|
||||
#include <pthread_np.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
using setname_t1 = int(*)(const char*);
|
||||
using setname_t2 = int(*)(pthread_t, const char*);
|
||||
using setname_t3 = void(*)(pthread_t, const char*);
|
||||
using setname_t4 = int(*)(pthread_t, const char*, void*);
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t1 func, const char *name)
|
||||
{ func(name); }
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t2 func, const char *name)
|
||||
{ func(pthread_self(), name); }
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t3 func, const char *name)
|
||||
{ func(pthread_self(), name); }
|
||||
|
||||
[[maybe_unused]] void setname_caller(setname_t4 func, const char *name)
|
||||
{ func(pthread_self(), "%s", const_cast<char*>(name)); /* NOLINT(*-const-cast) */ }
|
||||
|
||||
} // namespace
|
||||
|
||||
void althrd_setname(const char *name [[maybe_unused]])
|
||||
{
|
||||
#if defined(HAVE_PTHREAD_SET_NAME_NP)
|
||||
setname_caller(pthread_set_name_np, name);
|
||||
#elif defined(HAVE_PTHREAD_SETNAME_NP)
|
||||
setname_caller(pthread_setname_np, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
6
Engine/lib/openal-soft/common/althrd_setname.h
Normal file
6
Engine/lib/openal-soft/common/althrd_setname.h
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef COMMON_ALTHRD_SETNAME_H
|
||||
#define COMMON_ALTHRD_SETNAME_H
|
||||
|
||||
void althrd_setname(const char *name);
|
||||
|
||||
#endif /* COMMON_ALTHRD_SETNAME_H */
|
||||
143
Engine/lib/openal-soft/common/althreads.h
Normal file
143
Engine/lib/openal-soft/common/althreads.h
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#ifndef AL_THREADS_H
|
||||
#define AL_THREADS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
#else
|
||||
|
||||
#include <threads.h>
|
||||
#endif
|
||||
|
||||
#include "albit.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T>
|
||||
class tss {
|
||||
static_assert(sizeof(T) <= sizeof(void*));
|
||||
static_assert(std::is_trivially_destructible_v<T> && std::is_trivially_copy_constructible_v<T>);
|
||||
|
||||
[[nodiscard]]
|
||||
static auto to_ptr(const T &value) noexcept -> void*
|
||||
{
|
||||
if constexpr(std::is_pointer_v<T>)
|
||||
{
|
||||
if constexpr(std::is_const_v<std::remove_pointer_t<T>>)
|
||||
return const_cast<void*>(static_cast<const void*>(value)); /* NOLINT(*-const-cast) */
|
||||
else
|
||||
return static_cast<void*>(value);
|
||||
}
|
||||
else if constexpr(sizeof(T) == sizeof(void*))
|
||||
return al::bit_cast<void*>(value);
|
||||
else if constexpr(std::is_integral_v<T>)
|
||||
return al::bit_cast<void*>(static_cast<std::uintptr_t>(value));
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
static auto from_ptr(void *ptr) noexcept -> T
|
||||
{
|
||||
if constexpr(std::is_pointer_v<T>)
|
||||
return static_cast<T>(ptr);
|
||||
else if constexpr(sizeof(T) == sizeof(void*))
|
||||
return al::bit_cast<T>(ptr);
|
||||
else if constexpr(std::is_integral_v<T>)
|
||||
return static_cast<T>(al::bit_cast<std::uintptr_t>(ptr));
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD mTss{TLS_OUT_OF_INDEXES};
|
||||
|
||||
public:
|
||||
tss() : mTss{TlsAlloc()}
|
||||
{
|
||||
if(mTss == TLS_OUT_OF_INDEXES)
|
||||
throw std::runtime_error{"al::tss::tss()"};
|
||||
}
|
||||
explicit tss(const T &init) : tss{}
|
||||
{
|
||||
if(TlsSetValue(mTss, to_ptr(init)) == FALSE)
|
||||
throw std::runtime_error{"al::tss::tss(T)"};
|
||||
}
|
||||
~tss() { TlsFree(mTss); }
|
||||
|
||||
void set(const T &value) const
|
||||
{
|
||||
if(TlsSetValue(mTss, to_ptr(value)) == FALSE)
|
||||
throw std::runtime_error{"al::tss::set(T)"};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get() const noexcept -> T { return from_ptr(TlsGetValue(mTss)); }
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
pthread_key_t mTss{};
|
||||
|
||||
public:
|
||||
tss()
|
||||
{
|
||||
if(int res{pthread_key_create(&mTss, nullptr)}; res != 0)
|
||||
throw std::runtime_error{"al::tss::tss()"};
|
||||
}
|
||||
explicit tss(const T &init) : tss{}
|
||||
{
|
||||
if(int res{pthread_setspecific(mTss, to_ptr(init))}; res != 0)
|
||||
throw std::runtime_error{"al::tss::tss(T)"};
|
||||
}
|
||||
~tss() { pthread_key_delete(mTss); }
|
||||
|
||||
void set(const T &value) const
|
||||
{
|
||||
if(int res{pthread_setspecific(mTss, to_ptr(value))}; res != 0)
|
||||
throw std::runtime_error{"al::tss::set(T)"};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get() const noexcept -> T { return from_ptr(pthread_getspecific(mTss)); }
|
||||
|
||||
#else
|
||||
|
||||
tss_t mTss{};
|
||||
|
||||
public:
|
||||
tss()
|
||||
{
|
||||
if(int res{tss_create(&mTss, nullptr)}; res != thrd_success)
|
||||
throw std::runtime_error{"al::tss::tss()"};
|
||||
}
|
||||
explicit tss(const T &init) : tss{}
|
||||
{
|
||||
if(int res{tss_set(mTss, to_ptr(init))}; res != thrd_success)
|
||||
throw std::runtime_error{"al::tss::tss(T)"};
|
||||
}
|
||||
~tss() { tss_delete(mTss); }
|
||||
|
||||
void set(const T &value) const
|
||||
{
|
||||
if(int res{tss_set(mTss, to_ptr(value))}; res != thrd_success)
|
||||
throw std::runtime_error{"al::tss::set(T)"};
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto get() const noexcept -> T { return from_ptr(tss_get(mTss)); }
|
||||
#endif /* _WIN32 */
|
||||
|
||||
tss(const tss&) = delete;
|
||||
tss(tss&&) = delete;
|
||||
void operator=(const tss&) = delete;
|
||||
void operator=(tss&&) = delete;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_THREADS_H */
|
||||
|
|
@ -2,17 +2,17 @@
|
|||
#define AL_ATOMIC_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
using RefCount = std::atomic<unsigned int>;
|
||||
|
||||
inline void InitRef(RefCount &ref, unsigned int value)
|
||||
{ ref.store(value, std::memory_order_relaxed); }
|
||||
inline unsigned int ReadRef(RefCount &ref)
|
||||
{ return ref.load(std::memory_order_acquire); }
|
||||
inline unsigned int IncrementRef(RefCount &ref)
|
||||
template<typename T>
|
||||
auto IncrementRef(std::atomic<T> &ref) noexcept
|
||||
{ return ref.fetch_add(1u, std::memory_order_acq_rel)+1u; }
|
||||
inline unsigned int DecrementRef(RefCount &ref)
|
||||
|
||||
template<typename T>
|
||||
auto DecrementRef(std::atomic<T> &ref) noexcept
|
||||
{ return ref.fetch_sub(1u, std::memory_order_acq_rel)-1u; }
|
||||
|
||||
|
||||
|
|
@ -30,4 +30,75 @@ inline void AtomicReplaceHead(std::atomic<T> &head, T newhead)
|
|||
std::memory_order_acq_rel, std::memory_order_acquire));
|
||||
}
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T, typename D=std::default_delete<T>>
|
||||
class atomic_unique_ptr {
|
||||
std::atomic<gsl::owner<T*>> mPointer{};
|
||||
|
||||
using unique_ptr_t = std::unique_ptr<T,D>;
|
||||
|
||||
public:
|
||||
atomic_unique_ptr() = default;
|
||||
atomic_unique_ptr(const atomic_unique_ptr&) = delete;
|
||||
explicit atomic_unique_ptr(std::nullptr_t) noexcept { }
|
||||
explicit atomic_unique_ptr(gsl::owner<T*> ptr) noexcept : mPointer{ptr} { }
|
||||
explicit atomic_unique_ptr(unique_ptr_t&& rhs) noexcept : mPointer{rhs.release()} { }
|
||||
~atomic_unique_ptr()
|
||||
{
|
||||
if(auto ptr = mPointer.exchange(nullptr, std::memory_order_relaxed))
|
||||
D{}(ptr);
|
||||
}
|
||||
|
||||
auto operator=(const atomic_unique_ptr&) -> atomic_unique_ptr& = delete;
|
||||
auto operator=(std::nullptr_t) noexcept -> atomic_unique_ptr&
|
||||
{
|
||||
if(auto ptr = mPointer.exchange(nullptr))
|
||||
D{}(ptr);
|
||||
return *this;
|
||||
}
|
||||
auto operator=(unique_ptr_t&& rhs) noexcept -> atomic_unique_ptr&
|
||||
{
|
||||
if(auto ptr = mPointer.exchange(rhs.release()))
|
||||
D{}(ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto load(std::memory_order m=std::memory_order_seq_cst) const noexcept -> T*
|
||||
{ return mPointer.load(m); }
|
||||
void store(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
if(auto oldptr = mPointer.exchange(nullptr, m))
|
||||
D{}(oldptr);
|
||||
}
|
||||
void store(gsl::owner<T*> ptr, std::memory_order m=std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
if(auto oldptr = mPointer.exchange(ptr, m))
|
||||
D{}(oldptr);
|
||||
}
|
||||
void store(unique_ptr_t&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept
|
||||
{
|
||||
if(auto oldptr = mPointer.exchange(ptr.release(), m))
|
||||
D{}(oldptr);
|
||||
}
|
||||
|
||||
[[nodiscard]]
|
||||
auto exchange(std::nullptr_t, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
|
||||
{ return unique_ptr_t{mPointer.exchange(nullptr, m)}; }
|
||||
[[nodiscard]]
|
||||
auto exchange(gsl::owner<T*> ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
|
||||
{ return unique_ptr_t{mPointer.exchange(ptr, m)}; }
|
||||
[[nodiscard]]
|
||||
auto exchange(std::unique_ptr<T>&& ptr, std::memory_order m=std::memory_order_seq_cst) noexcept -> unique_ptr_t
|
||||
{ return unique_ptr_t{mPointer.exchange(ptr.release(), m)}; }
|
||||
|
||||
[[nodiscard]]
|
||||
auto is_lock_free() const noexcept -> bool { return mPointer.is_lock_free(); }
|
||||
|
||||
static constexpr auto is_always_lock_free = std::atomic<gsl::owner<T*>>::is_always_lock_free;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_ATOMIC_H */
|
||||
|
|
|
|||
|
|
@ -2,49 +2,86 @@
|
|||
#define COMMON_COMPTR_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
|
||||
#include "opthelpers.h"
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <objbase.h>
|
||||
|
||||
struct ComWrapper {
|
||||
HRESULT mStatus{};
|
||||
|
||||
ComWrapper(void *reserved, DWORD coinit)
|
||||
: mStatus{CoInitializeEx(reserved, coinit)}
|
||||
{ }
|
||||
ComWrapper(DWORD coinit=COINIT_APARTMENTTHREADED)
|
||||
: mStatus{CoInitializeEx(nullptr, coinit)}
|
||||
{ }
|
||||
ComWrapper(ComWrapper&& rhs) { mStatus = std::exchange(rhs.mStatus, E_FAIL); }
|
||||
ComWrapper(const ComWrapper&) = delete;
|
||||
~ComWrapper() { if(SUCCEEDED(mStatus)) CoUninitialize(); }
|
||||
|
||||
ComWrapper& operator=(ComWrapper&& rhs)
|
||||
{
|
||||
if(SUCCEEDED(mStatus))
|
||||
CoUninitialize();
|
||||
mStatus = std::exchange(rhs.mStatus, E_FAIL);
|
||||
return *this;
|
||||
}
|
||||
ComWrapper& operator=(const ComWrapper&) = delete;
|
||||
|
||||
[[nodiscard]]
|
||||
HRESULT status() const noexcept { return mStatus; }
|
||||
explicit operator bool() const noexcept { return SUCCEEDED(status()); }
|
||||
|
||||
void uninit()
|
||||
{
|
||||
if(SUCCEEDED(mStatus))
|
||||
CoUninitialize();
|
||||
mStatus = E_FAIL;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename T>
|
||||
class ComPtr {
|
||||
T *mPtr{nullptr};
|
||||
struct ComPtr {
|
||||
using element_type = T;
|
||||
|
||||
static constexpr bool RefIsNoexcept{noexcept(std::declval<T&>().AddRef())
|
||||
&& noexcept(std::declval<T&>().Release())};
|
||||
|
||||
public:
|
||||
ComPtr() noexcept = default;
|
||||
ComPtr(const ComPtr &rhs) : mPtr{rhs.mPtr} { if(mPtr) mPtr->AddRef(); }
|
||||
ComPtr(const ComPtr &rhs) noexcept(RefIsNoexcept) : mPtr{rhs.mPtr}
|
||||
{ if(mPtr) mPtr->AddRef(); }
|
||||
ComPtr(ComPtr&& rhs) noexcept : mPtr{rhs.mPtr} { rhs.mPtr = nullptr; }
|
||||
ComPtr(std::nullptr_t) noexcept { }
|
||||
explicit ComPtr(T *ptr) noexcept : mPtr{ptr} { }
|
||||
~ComPtr() { if(mPtr) mPtr->Release(); }
|
||||
|
||||
ComPtr& operator=(const ComPtr &rhs)
|
||||
/* NOLINTNEXTLINE(bugprone-unhandled-self-assignment) Yes it is. */
|
||||
ComPtr& operator=(const ComPtr &rhs) noexcept(RefIsNoexcept)
|
||||
{
|
||||
if(!rhs.mPtr)
|
||||
if constexpr(RefIsNoexcept)
|
||||
{
|
||||
if(mPtr)
|
||||
mPtr->Release();
|
||||
mPtr = nullptr;
|
||||
if(rhs.mPtr) rhs.mPtr->AddRef();
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = rhs.mPtr;
|
||||
return *this;
|
||||
}
|
||||
else
|
||||
{
|
||||
rhs.mPtr->AddRef();
|
||||
try {
|
||||
if(mPtr)
|
||||
mPtr->Release();
|
||||
mPtr = rhs.mPtr;
|
||||
}
|
||||
catch(...) {
|
||||
rhs.mPtr->Release();
|
||||
throw;
|
||||
}
|
||||
ComPtr tmp{rhs};
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = tmp.release();
|
||||
return *this;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
ComPtr& operator=(ComPtr&& rhs)
|
||||
ComPtr& operator=(ComPtr&& rhs) noexcept(RefIsNoexcept)
|
||||
{
|
||||
if(&rhs != this) LIKELY
|
||||
if(&rhs != this)
|
||||
{
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = std::exchange(rhs.mPtr, nullptr);
|
||||
|
|
@ -52,17 +89,25 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
void reset(T *ptr=nullptr) noexcept(RefIsNoexcept)
|
||||
{
|
||||
if(mPtr) mPtr->Release();
|
||||
mPtr = ptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const noexcept { return mPtr != nullptr; }
|
||||
|
||||
T& operator*() const noexcept { return *mPtr; }
|
||||
T* operator->() const noexcept { return mPtr; }
|
||||
T* get() const noexcept { return mPtr; }
|
||||
T** getPtr() noexcept { return &mPtr; }
|
||||
|
||||
T* release() noexcept { return std::exchange(mPtr, nullptr); }
|
||||
|
||||
void swap(ComPtr &rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
|
||||
void swap(ComPtr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
|
||||
|
||||
private:
|
||||
T *mPtr{nullptr};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
#include "dynload.h"
|
||||
|
||||
#include "strutils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
#include "strutils.h"
|
||||
|
||||
void *LoadLib(const char *name)
|
||||
{
|
||||
std::wstring wname{utf8_to_wstr(name)};
|
||||
|
|
|
|||
139
Engine/lib/openal-soft/common/flexarray.h
Normal file
139
Engine/lib/openal-soft/common/flexarray.h
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#ifndef AL_FLEXARRAY_H
|
||||
#define AL_FLEXARRAY_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alspan.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
/* Storage for flexible array data. This is trivially destructible if type T is
|
||||
* trivially destructible.
|
||||
*/
|
||||
template<typename T, size_t alignment, bool = std::is_trivially_destructible<T>::value>
|
||||
struct alignas(alignment) FlexArrayStorage : al::span<T> {
|
||||
/* NOLINTBEGIN(bugprone-sizeof-expression) clang-tidy warns about the
|
||||
* sizeof(T) being suspicious when T is a pointer type, which it will be
|
||||
* for flexible arrays of pointers.
|
||||
*/
|
||||
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
|
||||
{ return sizeof(FlexArrayStorage) + sizeof(T)*count + base; }
|
||||
/* NOLINTEND(bugprone-sizeof-expression) */
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) Flexible
|
||||
* arrays store their payloads after the end of the object, which must be
|
||||
* the last in the whole parent chain.
|
||||
*/
|
||||
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
|
||||
{ }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
~FlexArrayStorage() = default;
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
};
|
||||
|
||||
template<typename T, size_t alignment>
|
||||
struct alignas(alignment) FlexArrayStorage<T,alignment,false> : al::span<T> {
|
||||
static constexpr size_t Sizeof(size_t count, size_t base=0u) noexcept
|
||||
{ return sizeof(FlexArrayStorage) + sizeof(T)*count + base; }
|
||||
|
||||
/* NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
FlexArrayStorage(size_t size) noexcept(std::is_nothrow_constructible_v<T>)
|
||||
: al::span<T>{::new(static_cast<void*>(this+1)) T[size], size}
|
||||
{ }
|
||||
/* NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||
~FlexArrayStorage() { std::destroy(this->begin(), this->end()); }
|
||||
|
||||
FlexArrayStorage(const FlexArrayStorage&) = delete;
|
||||
FlexArrayStorage& operator=(const FlexArrayStorage&) = delete;
|
||||
};
|
||||
|
||||
/* A flexible array type. Used either standalone or at the end of a parent
|
||||
* struct, to have a run-time-sized array that's embedded with its size. Should
|
||||
* be used delicately, ensuring there's no additional data after the FlexArray
|
||||
* member.
|
||||
*/
|
||||
template<typename T, size_t Align=alignof(T)>
|
||||
struct FlexArray {
|
||||
using element_type = T;
|
||||
using value_type = std::remove_cv_t<T>;
|
||||
using index_type = size_t;
|
||||
using difference_type = ptrdiff_t;
|
||||
|
||||
using pointer = T*;
|
||||
using const_pointer = const T*;
|
||||
using reference = T&;
|
||||
using const_reference = const T&;
|
||||
|
||||
static constexpr std::size_t StorageAlign{std::max(alignof(T), Align)};
|
||||
using Storage_t_ = FlexArrayStorage<element_type,std::max(alignof(al::span<T>), StorageAlign)>;
|
||||
|
||||
using iterator = typename Storage_t_::iterator;
|
||||
using const_iterator = typename Storage_t_::const_iterator;
|
||||
using reverse_iterator = typename Storage_t_::reverse_iterator;
|
||||
using const_reverse_iterator = typename Storage_t_::const_reverse_iterator;
|
||||
|
||||
const Storage_t_ mStore;
|
||||
|
||||
static constexpr index_type Sizeof(index_type count, index_type base=0u) noexcept
|
||||
{ return Storage_t_::Sizeof(count, base); }
|
||||
static std::unique_ptr<FlexArray> Create(index_type count)
|
||||
{ return std::unique_ptr<FlexArray>{new(FamCount{count}) FlexArray{count}}; }
|
||||
|
||||
FlexArray(index_type size) noexcept(std::is_nothrow_constructible_v<Storage_t_,index_type>)
|
||||
: mStore{size}
|
||||
{ }
|
||||
~FlexArray() = default;
|
||||
|
||||
[[nodiscard]] auto size() const noexcept -> index_type { return mStore.size(); }
|
||||
[[nodiscard]] auto empty() const noexcept -> bool { return mStore.empty(); }
|
||||
|
||||
[[nodiscard]] auto data() noexcept -> pointer { return mStore.data(); }
|
||||
[[nodiscard]] auto data() const noexcept -> const_pointer { return mStore.data(); }
|
||||
|
||||
[[nodiscard]] auto operator[](index_type i) noexcept -> reference { return mStore[i]; }
|
||||
[[nodiscard]] auto operator[](index_type i) const noexcept -> const_reference { return mStore[i]; }
|
||||
|
||||
[[nodiscard]] auto front() noexcept -> reference { return mStore.front(); }
|
||||
[[nodiscard]] auto front() const noexcept -> const_reference { return mStore.front(); }
|
||||
|
||||
[[nodiscard]] auto back() noexcept -> reference { return mStore.back(); }
|
||||
[[nodiscard]] auto back() const noexcept -> const_reference { return mStore.back(); }
|
||||
|
||||
[[nodiscard]] auto begin() noexcept -> iterator { return mStore.begin(); }
|
||||
[[nodiscard]] auto begin() const noexcept -> const_iterator { return mStore.cbegin(); }
|
||||
[[nodiscard]] auto cbegin() const noexcept -> const_iterator { return mStore.cbegin(); }
|
||||
[[nodiscard]] auto end() noexcept -> iterator { return mStore.end(); }
|
||||
[[nodiscard]] auto end() const noexcept -> const_iterator { return mStore.cend(); }
|
||||
[[nodiscard]] auto cend() const noexcept -> const_iterator { return mStore.cend(); }
|
||||
|
||||
[[nodiscard]] auto rbegin() noexcept -> reverse_iterator { return mStore.rbegin(); }
|
||||
[[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); }
|
||||
[[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator { return mStore.crbegin(); }
|
||||
[[nodiscard]] auto rend() noexcept -> reverse_iterator { return mStore.rend(); }
|
||||
[[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { return mStore.crend(); }
|
||||
[[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { return mStore.crend(); }
|
||||
|
||||
gsl::owner<void*> operator new(size_t, FamCount count)
|
||||
{ return ::operator new[](Sizeof(count), std::align_val_t{alignof(FlexArray)}); }
|
||||
void operator delete(gsl::owner<void*> block, FamCount) noexcept
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); }
|
||||
void operator delete(gsl::owner<void*> block) noexcept
|
||||
{ ::operator delete[](block, std::align_val_t{alignof(FlexArray)}); }
|
||||
|
||||
void *operator new(size_t size) = delete;
|
||||
void *operator new[](size_t size) = delete;
|
||||
void operator delete[](void *block) = delete;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_FLEXARRAY_H */
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef INTRUSIVE_PTR_H
|
||||
#define INTRUSIVE_PTR_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <utility>
|
||||
|
||||
#include "atomic.h"
|
||||
|
|
@ -11,7 +13,7 @@ namespace al {
|
|||
|
||||
template<typename T>
|
||||
class intrusive_ref {
|
||||
RefCount mRef{1u};
|
||||
std::atomic<unsigned int> mRef{1u};
|
||||
|
||||
public:
|
||||
unsigned int add_ref() noexcept { return IncrementRef(mRef); }
|
||||
|
|
@ -60,6 +62,9 @@ public:
|
|||
explicit intrusive_ptr(T *ptr) noexcept : mPtr{ptr} { }
|
||||
~intrusive_ptr() { if(mPtr) mPtr->dec_ref(); }
|
||||
|
||||
/* NOLINTBEGIN(bugprone-unhandled-self-assignment)
|
||||
* Self-assignment is handled properly here.
|
||||
*/
|
||||
intrusive_ptr& operator=(const intrusive_ptr &rhs) noexcept
|
||||
{
|
||||
static_assert(noexcept(std::declval<T*>()->dec_ref()), "dec_ref must be noexcept");
|
||||
|
|
@ -69,6 +74,7 @@ public:
|
|||
mPtr = rhs.mPtr;
|
||||
return *this;
|
||||
}
|
||||
/* NOLINTEND(bugprone-unhandled-self-assignment) */
|
||||
intrusive_ptr& operator=(intrusive_ptr&& rhs) noexcept
|
||||
{
|
||||
if(&rhs != this) LIKELY
|
||||
|
|
@ -81,9 +87,9 @@ public:
|
|||
|
||||
explicit operator bool() const noexcept { return mPtr != nullptr; }
|
||||
|
||||
T& operator*() const noexcept { return *mPtr; }
|
||||
T* operator->() const noexcept { return mPtr; }
|
||||
T* get() const noexcept { return mPtr; }
|
||||
[[nodiscard]] auto operator*() const noexcept -> T& { return *mPtr; }
|
||||
[[nodiscard]] auto operator->() const noexcept -> T* { return mPtr; }
|
||||
[[nodiscard]] auto get() const noexcept -> T* { return mPtr; }
|
||||
|
||||
void reset(T *ptr=nullptr) noexcept
|
||||
{
|
||||
|
|
@ -98,23 +104,6 @@ public:
|
|||
void swap(intrusive_ptr&& rhs) noexcept { std::swap(mPtr, rhs.mPtr); }
|
||||
};
|
||||
|
||||
#define AL_DECL_OP(op) \
|
||||
template<typename T> \
|
||||
inline bool operator op(const intrusive_ptr<T> &lhs, const T *rhs) noexcept \
|
||||
{ return lhs.get() op rhs; } \
|
||||
template<typename T> \
|
||||
inline bool operator op(const T *lhs, const intrusive_ptr<T> &rhs) noexcept \
|
||||
{ return lhs op rhs.get(); }
|
||||
|
||||
AL_DECL_OP(==)
|
||||
AL_DECL_OP(!=)
|
||||
AL_DECL_OP(<=)
|
||||
AL_DECL_OP(>=)
|
||||
AL_DECL_OP(<)
|
||||
AL_DECL_OP(>)
|
||||
|
||||
#undef AL_DECL_OP
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* INTRUSIVE_PTR_H */
|
||||
|
|
|
|||
|
|
@ -19,10 +19,13 @@
|
|||
|
||||
#ifdef __GNUC__
|
||||
#define force_inline [[gnu::always_inline]] inline
|
||||
#define NOINLINE [[gnu::noinline]]
|
||||
#elif defined(_MSC_VER)
|
||||
#define force_inline __forceinline
|
||||
#define NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define force_inline inline
|
||||
#define NOINLINE
|
||||
#endif
|
||||
|
||||
/* Unlike the likely attribute, ASSUME requires the condition to be true or
|
||||
|
|
@ -39,7 +42,7 @@
|
|||
#elif HAS_BUILTIN(__builtin_unreachable)
|
||||
#define ASSUME(x) do { if(x) break; __builtin_unreachable(); } while(0)
|
||||
#else
|
||||
#define ASSUME(x) ((void)0)
|
||||
#define ASSUME(x) (static_cast<void>(0))
|
||||
#endif
|
||||
|
||||
/* This shouldn't be needed since unknown attributes are ignored, but older
|
||||
|
|
|
|||
2308
Engine/lib/openal-soft/common/pffft.cpp
Normal file
2308
Engine/lib/openal-soft/common/pffft.cpp
Normal file
File diff suppressed because it is too large
Load diff
212
Engine/lib/openal-soft/common/pffft.h
Normal file
212
Engine/lib/openal-soft/common/pffft.h
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
|
||||
|
||||
Based on original fortran 77 code from FFTPACKv4 from NETLIB,
|
||||
authored by Dr Paul Swarztrauber of NCAR, in 1985.
|
||||
|
||||
As confirmed by the NCAR fftpack software curators, the following
|
||||
FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
|
||||
released under the same terms.
|
||||
|
||||
FFTPACK license:
|
||||
|
||||
http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
|
||||
|
||||
Copyright (c) 2004 the University Corporation for Atmospheric
|
||||
Research ("UCAR"). All rights reserved. Developed by NCAR's
|
||||
Computational and Information Systems Laboratory, UCAR,
|
||||
www.cisl.ucar.edu.
|
||||
|
||||
Redistribution and use of the Software in source and binary forms,
|
||||
with or without modification, is permitted provided that the
|
||||
following conditions are met:
|
||||
|
||||
- Neither the names of NCAR's Computational and Information Systems
|
||||
Laboratory, the University Corporation for Atmospheric Research,
|
||||
nor the names of its sponsors or contributors may be used to
|
||||
endorse or promote products derived from this Software without
|
||||
specific prior written permission.
|
||||
|
||||
- Redistributions of source code must retain the above copyright
|
||||
notices, this list of conditions, and the disclaimer below.
|
||||
|
||||
- Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions, and the disclaimer below in the
|
||||
documentation and/or other materials provided with the
|
||||
distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
/* PFFFT : a Pretty Fast FFT.
|
||||
*
|
||||
* This is basically an adaptation of the single precision fftpack (v4) as
|
||||
* found on netlib taking advantage of SIMD instructions found on CPUs such as
|
||||
* Intel x86 (SSE1), PowerPC (Altivec), and Arm (NEON).
|
||||
*
|
||||
* For architectures where SIMD instructions aren't available, the code falls
|
||||
* back to a scalar version.
|
||||
*
|
||||
* Restrictions:
|
||||
*
|
||||
* - 1D transforms only, with 32-bit single precision.
|
||||
*
|
||||
* - supports only transforms for inputs of length N of the form
|
||||
* N=(2^a)*(3^b)*(5^c), given a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128, 144,
|
||||
* 160, etc are all acceptable lengths). Performance is best for 128<=N<=8192.
|
||||
*
|
||||
* - all (float*) pointers for the functions below are expected to have a
|
||||
* "SIMD-compatible" alignment, that is 16 bytes.
|
||||
*
|
||||
* You can allocate such buffers with the pffft_aligned_malloc function, and
|
||||
* deallocate them with pffft_aligned_free (or with stuff like posix_memalign,
|
||||
* aligned_alloc, etc).
|
||||
*
|
||||
* Note that for the z-domain data of real transforms, when in the canonical
|
||||
* order (as interleaved complex numbers) both 0-frequency and half-frequency
|
||||
* components, which are real, are assembled in the first entry as
|
||||
* F(0)+i*F(n/2+1). The original fftpack placed F(n/2+1) at the end of the
|
||||
* arrays instead.
|
||||
*/
|
||||
|
||||
#ifndef PFFFT_H
|
||||
#define PFFFT_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
|
||||
/* opaque struct holding internal stuff (precomputed twiddle factors) this
|
||||
* struct can be shared by many threads as it contains only read-only data.
|
||||
*/
|
||||
struct PFFFT_Setup;
|
||||
|
||||
/* direction of the transform */
|
||||
enum pffft_direction_t { PFFFT_FORWARD, PFFFT_BACKWARD };
|
||||
|
||||
/* type of transform */
|
||||
enum pffft_transform_t { PFFFT_REAL, PFFFT_COMPLEX };
|
||||
|
||||
void pffft_destroy_setup(gsl::owner<PFFFT_Setup*> setup) noexcept;
|
||||
struct PFFFTSetupDeleter {
|
||||
void operator()(gsl::owner<PFFFT_Setup*> setup) const noexcept { pffft_destroy_setup(setup); }
|
||||
};
|
||||
using PFFFTSetupPtr = std::unique_ptr<PFFFT_Setup,PFFFTSetupDeleter>;
|
||||
|
||||
/**
|
||||
* Prepare for performing transforms of size N -- the returned PFFFT_Setup
|
||||
* structure is read-only so it can safely be shared by multiple concurrent
|
||||
* threads.
|
||||
*/
|
||||
PFFFTSetupPtr pffft_new_setup(unsigned int N, pffft_transform_t transform);
|
||||
|
||||
/**
|
||||
* Perform a Fourier transform. The z-domain data is stored in the most
|
||||
* efficient order for transforming back or using for convolution, and as
|
||||
* such, there's no guarantee to the order of the values. If you need to have
|
||||
* its content sorted in the usual way, that is as an array of interleaved
|
||||
* complex numbers, either use pffft_transform_ordered, or call pffft_zreorder
|
||||
* after the forward fft and before the backward fft.
|
||||
*
|
||||
* Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x. Typically
|
||||
* you will want to scale the backward transform by 1/N.
|
||||
*
|
||||
* The 'work' pointer must point to an area of N (2*N for complex fft) floats,
|
||||
* properly aligned. It cannot be NULL.
|
||||
*
|
||||
* The input and output parameters may alias.
|
||||
*/
|
||||
void pffft_transform(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
|
||||
|
||||
/**
|
||||
* Similar to pffft_transform, but handles the complex values in the usual form
|
||||
* (interleaved complex numbers). This is similar to calling
|
||||
* pffft_transform(..., PFFFT_FORWARD) followed by
|
||||
* pffft_zreorder(..., PFFFT_FORWARD), or
|
||||
* pffft_zreorder(..., PFFFT_BACKWARD) followed by
|
||||
* pffft_transform(..., PFFFT_BACKWARD), for the given direction.
|
||||
*
|
||||
* The input and output parameters may alias.
|
||||
*/
|
||||
void pffft_transform_ordered(const PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
|
||||
|
||||
/**
|
||||
* Reorder the z-domain data. For PFFFT_FORWARD, it reorders from the internal
|
||||
* representation to the "canonical" order (as interleaved complex numbers).
|
||||
* For PFFFT_BACKWARD, it reorders from the canonical order to the internal
|
||||
* order suitable for pffft_transform(..., PFFFT_BACKWARD) or
|
||||
* pffft_zconvolve_accumulate.
|
||||
*
|
||||
* The input and output parameters should not alias.
|
||||
*/
|
||||
void pffft_zreorder(const PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction);
|
||||
|
||||
/**
|
||||
* Perform a multiplication of the z-domain data in dft_a and dft_b, and scale
|
||||
* and accumulate into dft_ab. The arrays should have been obtained with
|
||||
* pffft_transform(..., PFFFT_FORWARD) or pffft_zreorder(..., PFFFT_BACKWARD)
|
||||
* and should *not* be in the usual order (otherwise just perform the operation
|
||||
* yourself as the dft coeffs are stored as interleaved complex numbers).
|
||||
*
|
||||
* The operation performed is: dft_ab += (dft_a * dft_b)*scaling
|
||||
*
|
||||
* The dft_a, dft_b, and dft_ab parameters may alias.
|
||||
*/
|
||||
void pffft_zconvolve_scale_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
|
||||
|
||||
/**
|
||||
* Perform a multiplication of the z-domain data in dft_a and dft_b, and
|
||||
* accumulate into dft_ab.
|
||||
*
|
||||
* The operation performed is: dft_ab += dft_a * dft_b
|
||||
*
|
||||
* The dft_a, dft_b, and dft_ab parameters may alias.
|
||||
*/
|
||||
void pffft_zconvolve_accumulate(const PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab);
|
||||
|
||||
|
||||
struct PFFFTSetup {
|
||||
PFFFTSetupPtr mSetup{};
|
||||
|
||||
PFFFTSetup() = default;
|
||||
PFFFTSetup(const PFFFTSetup&) = delete;
|
||||
PFFFTSetup(PFFFTSetup&& rhs) noexcept = default;
|
||||
explicit PFFFTSetup(std::nullptr_t) noexcept { }
|
||||
explicit PFFFTSetup(unsigned int n, pffft_transform_t transform)
|
||||
: mSetup{pffft_new_setup(n, transform)}
|
||||
{ }
|
||||
~PFFFTSetup() = default;
|
||||
|
||||
PFFFTSetup& operator=(const PFFFTSetup&) = delete;
|
||||
PFFFTSetup& operator=(PFFFTSetup&& rhs) noexcept = default;
|
||||
|
||||
[[nodiscard]] explicit operator bool() const noexcept { return mSetup != nullptr; }
|
||||
|
||||
void transform(const float *input, float *output, float *work, pffft_direction_t direction) const
|
||||
{ pffft_transform(mSetup.get(), input, output, work, direction); }
|
||||
|
||||
void transform_ordered(const float *input, float *output, float *work,
|
||||
pffft_direction_t direction) const
|
||||
{ pffft_transform_ordered(mSetup.get(), input, output, work, direction); }
|
||||
|
||||
void zreorder(const float *input, float *output, pffft_direction_t direction) const
|
||||
{ pffft_zreorder(mSetup.get(), input, output, direction); }
|
||||
|
||||
void zconvolve_scale_accumulate(const float *dft_a, const float *dft_b, float *dft_ab,
|
||||
float scaling) const
|
||||
{ pffft_zconvolve_scale_accumulate(mSetup.get(), dft_a, dft_b, dft_ab, scaling); }
|
||||
|
||||
void zconvolve_accumulate(const float *dft_a, const float *dft_b, float *dft_ab) const
|
||||
{ pffft_zconvolve_accumulate(mSetup.get(), dft_a, dft_b, dft_ab); }
|
||||
};
|
||||
|
||||
#endif // PFFFT_H
|
||||
|
|
@ -8,89 +8,51 @@
|
|||
#endif
|
||||
|
||||
#include <array>
|
||||
#include <stddef.h>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
#include "alcomplex.h"
|
||||
#include "alnumbers.h"
|
||||
#include "alspan.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
/* Implements a wide-band +90 degree phase-shift. Note that this should be
|
||||
* given one sample less of a delay (FilterSize/2 - 1) compared to the direct
|
||||
* signal delay (FilterSize/2) to properly align.
|
||||
*/
|
||||
template<size_t FilterSize>
|
||||
template<std::size_t FilterSize>
|
||||
struct PhaseShifterT {
|
||||
static_assert(FilterSize >= 16, "FilterSize needs to be at least 16");
|
||||
static_assert((FilterSize&(FilterSize-1)) == 0, "FilterSize needs to be power-of-two");
|
||||
|
||||
alignas(16) std::array<float,FilterSize/2> mCoeffs{};
|
||||
|
||||
/* Some notes on this filter construction.
|
||||
*
|
||||
* A wide-band phase-shift filter needs a delay to maintain linearity. A
|
||||
* dirac impulse in the center of a time-domain buffer represents a filter
|
||||
* passing all frequencies through as-is with a pure delay. Converting that
|
||||
* to the frequency domain, adjusting the phase of each frequency bin by
|
||||
* +90 degrees, then converting back to the time domain, results in a FIR
|
||||
* filter that applies a +90 degree wide-band phase-shift.
|
||||
*
|
||||
* A particularly notable aspect of the time-domain filter response is that
|
||||
* every other coefficient is 0. This allows doubling the effective size of
|
||||
* the filter, by storing only the non-0 coefficients and double-stepping
|
||||
* over the input to apply it.
|
||||
*
|
||||
* Additionally, the resulting filter is independent of the sample rate.
|
||||
* The same filter can be applied regardless of the device's sample rate
|
||||
* and achieve the same effect.
|
||||
*/
|
||||
PhaseShifterT()
|
||||
{
|
||||
using complex_d = std::complex<double>;
|
||||
constexpr size_t fft_size{FilterSize};
|
||||
constexpr size_t half_size{fft_size / 2};
|
||||
|
||||
auto fftBuffer = std::make_unique<complex_d[]>(fft_size);
|
||||
std::fill_n(fftBuffer.get(), fft_size, complex_d{});
|
||||
fftBuffer[half_size] = 1.0;
|
||||
|
||||
forward_fft(al::as_span(fftBuffer.get(), fft_size));
|
||||
for(size_t i{0};i < half_size+1;++i)
|
||||
fftBuffer[i] = complex_d{-fftBuffer[i].imag(), fftBuffer[i].real()};
|
||||
for(size_t i{half_size+1};i < fft_size;++i)
|
||||
fftBuffer[i] = std::conj(fftBuffer[fft_size - i]);
|
||||
inverse_fft(al::as_span(fftBuffer.get(), fft_size));
|
||||
|
||||
auto fftiter = fftBuffer.get() + half_size + (FilterSize/2 - 1);
|
||||
for(float &coeff : mCoeffs)
|
||||
/* Every other coefficient is 0, so we only need to calculate and store
|
||||
* the non-0 terms and double-step over the input to apply it. The
|
||||
* calculated coefficients are in reverse to make applying in the time-
|
||||
* domain more efficient.
|
||||
*/
|
||||
for(std::size_t i{0};i < FilterSize/2;++i)
|
||||
{
|
||||
coeff = static_cast<float>(fftiter->real() / double{fft_size});
|
||||
fftiter -= 2;
|
||||
const int k{static_cast<int>(i*2 + 1) - int{FilterSize/2}};
|
||||
|
||||
/* Calculate the Blackman window value for this coefficient. */
|
||||
const double w{2.0*al::numbers::pi * static_cast<double>(i*2 + 1)
|
||||
/ double{FilterSize}};
|
||||
const double window{0.3635819 - 0.4891775*std::cos(w) + 0.1365995*std::cos(2.0*w)
|
||||
- 0.0106411*std::cos(3.0*w)};
|
||||
|
||||
const double pk{al::numbers::pi * static_cast<double>(k)};
|
||||
mCoeffs[i] = static_cast<float>(window * (1.0-std::cos(pk)) / pk);
|
||||
}
|
||||
}
|
||||
|
||||
void process(al::span<float> dst, const float *RESTRICT src) const;
|
||||
void process(const al::span<float> dst, const al::span<const float> src) const;
|
||||
|
||||
private:
|
||||
#if defined(HAVE_NEON)
|
||||
/* There doesn't seem to be NEON intrinsics to do this kind of stipple
|
||||
* shuffling, so there's two custom methods for it.
|
||||
*/
|
||||
static auto shuffle_2020(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 0))};
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(a, 2), ret, 1);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 0), ret, 2);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 2), ret, 3);
|
||||
return ret;
|
||||
}
|
||||
static auto shuffle_3131(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x4_t ret{vmovq_n_f32(vgetq_lane_f32(a, 1))};
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(a, 3), ret, 1);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 1), ret, 2);
|
||||
ret = vsetq_lane_f32(vgetq_lane_f32(b, 3), ret, 3);
|
||||
return ret;
|
||||
}
|
||||
static auto unpacklo(float32x4_t a, float32x4_t b)
|
||||
{
|
||||
float32x2x2_t result{vzip_f32(vget_low_f32(a), vget_low_f32(b))};
|
||||
|
|
@ -109,105 +71,141 @@ private:
|
|||
ret = vsetq_lane_f32(d, ret, 3);
|
||||
return ret;
|
||||
}
|
||||
static void vtranspose4(float32x4_t &x0, float32x4_t &x1, float32x4_t &x2, float32x4_t &x3)
|
||||
{
|
||||
float32x4x2_t t0_{vzipq_f32(x0, x2)};
|
||||
float32x4x2_t t1_{vzipq_f32(x1, x3)};
|
||||
float32x4x2_t u0_{vzipq_f32(t0_.val[0], t1_.val[0])};
|
||||
float32x4x2_t u1_{vzipq_f32(t0_.val[1], t1_.val[1])};
|
||||
x0 = u0_.val[0];
|
||||
x1 = u0_.val[1];
|
||||
x2 = u1_.val[0];
|
||||
x3 = u1_.val[1];
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
template<size_t S>
|
||||
inline void PhaseShifterT<S>::process(al::span<float> dst, const float *RESTRICT src) const
|
||||
template<std::size_t S>
|
||||
NOINLINE inline
|
||||
void PhaseShifterT<S>::process(const al::span<float> dst, const al::span<const float> src) const
|
||||
{
|
||||
auto in = src.begin();
|
||||
#ifdef HAVE_SSE_INTRINSICS
|
||||
if(size_t todo{dst.size()>>1})
|
||||
if(const std::size_t todo{dst.size()>>2})
|
||||
{
|
||||
auto *out = reinterpret_cast<__m64*>(dst.data());
|
||||
do {
|
||||
__m128 r04{_mm_setzero_ps()};
|
||||
__m128 r14{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
auto out = al::span{reinterpret_cast<__m128*>(dst.data()), todo};
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
__m128 r0{_mm_setzero_ps()};
|
||||
__m128 r1{_mm_setzero_ps()};
|
||||
__m128 r2{_mm_setzero_ps()};
|
||||
__m128 r3{_mm_setzero_ps()};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s0{_mm_loadu_ps(&src[j*2])};
|
||||
const __m128 s1{_mm_loadu_ps(&src[j*2 + 4])};
|
||||
const __m128 s0{_mm_loadu_ps(&in[j*2])};
|
||||
const __m128 s1{_mm_loadu_ps(&in[j*2 + 4])};
|
||||
const __m128 s2{_mm_movehl_ps(_mm_movelh_ps(s1, s1), s0)};
|
||||
const __m128 s3{_mm_loadh_pi(_mm_movehl_ps(s1, s1),
|
||||
reinterpret_cast<const __m64*>(&in[j*2 + 8]))};
|
||||
|
||||
__m128 s{_mm_shuffle_ps(s0, s1, _MM_SHUFFLE(2, 0, 2, 0))};
|
||||
r04 = _mm_add_ps(r04, _mm_mul_ps(s, coeffs));
|
||||
r0 = _mm_add_ps(r0, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s0, s1, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
r14 = _mm_add_ps(r14, _mm_mul_ps(s, coeffs));
|
||||
r1 = _mm_add_ps(r1, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(2, 0, 2, 0));
|
||||
r2 = _mm_add_ps(r2, _mm_mul_ps(s, coeffs));
|
||||
|
||||
s = _mm_shuffle_ps(s2, s3, _MM_SHUFFLE(3, 1, 3, 1));
|
||||
r3 = _mm_add_ps(r3, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
src += 2;
|
||||
in += 4;
|
||||
|
||||
__m128 r4{_mm_add_ps(_mm_unpackhi_ps(r04, r14), _mm_unpacklo_ps(r04, r14))};
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
|
||||
_mm_storel_pi(out, r4);
|
||||
++out;
|
||||
} while(--todo);
|
||||
_MM_TRANSPOSE4_PS(r0, r1, r2, r3);
|
||||
return _mm_add_ps(_mm_add_ps(r0, r1), _mm_add_ps(r2, r3));
|
||||
});
|
||||
}
|
||||
if((dst.size()&1))
|
||||
if(const std::size_t todo{dst.size()&3})
|
||||
{
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s{_mm_setr_ps(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
|
||||
dst.back() = _mm_cvtss_f32(r4);
|
||||
__m128 r4{_mm_setzero_ps()};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const __m128 coeffs{_mm_load_ps(&mCoeffs[j])};
|
||||
const __m128 s{_mm_setr_ps(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])};
|
||||
r4 = _mm_add_ps(r4, _mm_mul_ps(s, coeffs));
|
||||
}
|
||||
++in;
|
||||
r4 = _mm_add_ps(r4, _mm_shuffle_ps(r4, r4, _MM_SHUFFLE(0, 1, 2, 3)));
|
||||
r4 = _mm_add_ps(r4, _mm_movehl_ps(r4, r4));
|
||||
return _mm_cvtss_f32(r4);
|
||||
});
|
||||
}
|
||||
|
||||
#elif defined(HAVE_NEON)
|
||||
|
||||
size_t pos{0};
|
||||
if(size_t todo{dst.size()>>1})
|
||||
if(const std::size_t todo{dst.size()>>2})
|
||||
{
|
||||
do {
|
||||
float32x4_t r04{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r14{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
auto out = al::span{reinterpret_cast<float32x4_t*>(dst.data()), todo};
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
float32x4_t r0{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r1{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r2{vdupq_n_f32(0.0f)};
|
||||
float32x4_t r3{vdupq_n_f32(0.0f)};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s0{vld1q_f32(&src[j*2])};
|
||||
const float32x4_t s1{vld1q_f32(&src[j*2 + 4])};
|
||||
const float32x4_t s0{vld1q_f32(&in[j*2])};
|
||||
const float32x4_t s1{vld1q_f32(&in[j*2 + 4])};
|
||||
const float32x4_t s2{vcombine_f32(vget_high_f32(s0), vget_low_f32(s1))};
|
||||
const float32x4_t s3{vcombine_f32(vget_high_f32(s1), vld1_f32(&in[j*2 + 8]))};
|
||||
const float32x4x2_t values0{vuzpq_f32(s0, s1)};
|
||||
const float32x4x2_t values1{vuzpq_f32(s2, s3)};
|
||||
|
||||
r04 = vmlaq_f32(r04, shuffle_2020(s0, s1), coeffs);
|
||||
r14 = vmlaq_f32(r14, shuffle_3131(s0, s1), coeffs);
|
||||
r0 = vmlaq_f32(r0, values0.val[0], coeffs);
|
||||
r1 = vmlaq_f32(r1, values0.val[1], coeffs);
|
||||
r2 = vmlaq_f32(r2, values1.val[0], coeffs);
|
||||
r3 = vmlaq_f32(r3, values1.val[1], coeffs);
|
||||
}
|
||||
src += 2;
|
||||
in += 4;
|
||||
|
||||
float32x4_t r4{vaddq_f32(unpackhi(r04, r14), unpacklo(r04, r14))};
|
||||
float32x2_t r2{vadd_f32(vget_low_f32(r4), vget_high_f32(r4))};
|
||||
|
||||
vst1_f32(&dst[pos], r2);
|
||||
pos += 2;
|
||||
} while(--todo);
|
||||
vtranspose4(r0, r1, r2, r3);
|
||||
return vaddq_f32(vaddq_f32(r0, r1), vaddq_f32(r2, r3));
|
||||
});
|
||||
}
|
||||
if((dst.size()&1))
|
||||
if(const std::size_t todo{dst.size()&3})
|
||||
{
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(size_t j{0};j < mCoeffs.size();j+=4)
|
||||
auto out = dst.last(todo);
|
||||
std::generate(out.begin(), out.end(), [&in,this]
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s{load4(src[j*2], src[j*2 + 2], src[j*2 + 4], src[j*2 + 6])};
|
||||
r4 = vmlaq_f32(r4, s, coeffs);
|
||||
}
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
dst[pos] = vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
float32x4_t r4{vdupq_n_f32(0.0f)};
|
||||
for(std::size_t j{0};j < mCoeffs.size();j+=4)
|
||||
{
|
||||
const float32x4_t coeffs{vld1q_f32(&mCoeffs[j])};
|
||||
const float32x4_t s{load4(in[j*2], in[j*2 + 2], in[j*2 + 4], in[j*2 + 6])};
|
||||
r4 = vmlaq_f32(r4, s, coeffs);
|
||||
}
|
||||
++in;
|
||||
r4 = vaddq_f32(r4, vrev64q_f32(r4));
|
||||
return vget_lane_f32(vadd_f32(vget_low_f32(r4), vget_high_f32(r4)), 0);
|
||||
});
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
for(float &output : dst)
|
||||
std::generate(dst.begin(), dst.end(), [&in,this]
|
||||
{
|
||||
float ret{0.0f};
|
||||
for(size_t j{0};j < mCoeffs.size();++j)
|
||||
ret += src[j*2] * mCoeffs[j];
|
||||
|
||||
output = ret;
|
||||
++src;
|
||||
}
|
||||
for(std::size_t j{0};j < mCoeffs.size();++j)
|
||||
ret += in[j*2] * mCoeffs[j];
|
||||
++in;
|
||||
return ret;
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,16 +3,61 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <numeric>
|
||||
#include <tuple>
|
||||
|
||||
#include "alnumbers.h"
|
||||
#include "opthelpers.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double Epsilon{1e-9};
|
||||
|
||||
using uint = unsigned int;
|
||||
#if __cpp_lib_math_special_functions >= 201603L
|
||||
using std::cyl_bessel_i;
|
||||
|
||||
#else
|
||||
|
||||
/* The zero-order modified Bessel function of the first kind, used for the
|
||||
* Kaiser window.
|
||||
*
|
||||
* I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
|
||||
* = sum_{k=0}^inf ((x / 2)^k / k!)^2
|
||||
*
|
||||
* This implementation only handles nu = 0, and isn't the most precise (it
|
||||
* starts with the largest value and accumulates successively smaller values,
|
||||
* compounding the rounding and precision error), but it's good enough.
|
||||
*/
|
||||
template<typename T, typename U>
|
||||
U cyl_bessel_i(T nu, U x)
|
||||
{
|
||||
if(nu != T{0})
|
||||
throw std::runtime_error{"cyl_bessel_i: nu != 0"};
|
||||
|
||||
/* Start at k=1 since k=0 is trivial. */
|
||||
const double x2{x/2.0};
|
||||
double term{1.0};
|
||||
double sum{1.0};
|
||||
int k{1};
|
||||
|
||||
/* Let the integration converge until the term of the sum is no longer
|
||||
* significant.
|
||||
*/
|
||||
double last_sum{};
|
||||
do {
|
||||
const double y{x2 / k};
|
||||
++k;
|
||||
last_sum = sum;
|
||||
term *= y * y;
|
||||
sum += term;
|
||||
} while(sum != last_sum);
|
||||
return static_cast<U>(sum);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This is the normalized cardinal sine (sinc) function.
|
||||
*
|
||||
|
|
@ -26,33 +71,6 @@ double Sinc(const double x)
|
|||
return std::sin(al::numbers::pi*x) / (al::numbers::pi*x);
|
||||
}
|
||||
|
||||
/* The zero-order modified Bessel function of the first kind, used for the
|
||||
* Kaiser window.
|
||||
*
|
||||
* I_0(x) = sum_{k=0}^inf (1 / k!)^2 (x / 2)^(2 k)
|
||||
* = sum_{k=0}^inf ((x / 2)^k / k!)^2
|
||||
*/
|
||||
constexpr double BesselI_0(const double x)
|
||||
{
|
||||
// Start at k=1 since k=0 is trivial.
|
||||
const double x2{x/2.0};
|
||||
double term{1.0};
|
||||
double sum{1.0};
|
||||
int k{1};
|
||||
|
||||
// Let the integration converge until the term of the sum is no longer
|
||||
// significant.
|
||||
double last_sum{};
|
||||
do {
|
||||
const double y{x2 / k};
|
||||
++k;
|
||||
last_sum = sum;
|
||||
term *= y * y;
|
||||
sum += term;
|
||||
} while(sum != last_sum);
|
||||
return sum;
|
||||
}
|
||||
|
||||
/* Calculate a Kaiser window from the given beta value and a normalized k
|
||||
* [-1, 1].
|
||||
*
|
||||
|
|
@ -67,23 +85,11 @@ constexpr double BesselI_0(const double x)
|
|||
*
|
||||
* k = 2 i / M - 1, where 0 <= i <= M.
|
||||
*/
|
||||
double Kaiser(const double b, const double k)
|
||||
double Kaiser(const double beta, const double k, const double besseli_0_beta)
|
||||
{
|
||||
if(!(k >= -1.0 && k <= 1.0))
|
||||
return 0.0;
|
||||
return BesselI_0(b * std::sqrt(1.0 - k*k)) / BesselI_0(b);
|
||||
}
|
||||
|
||||
// Calculates the greatest common divisor of a and b.
|
||||
constexpr uint Gcd(uint x, uint y)
|
||||
{
|
||||
while(y > 0)
|
||||
{
|
||||
const uint z{y};
|
||||
y = x % y;
|
||||
x = z;
|
||||
}
|
||||
return x;
|
||||
return cyl_bessel_i(0, beta * std::sqrt(1.0 - k*k)) / besseli_0_beta;
|
||||
}
|
||||
|
||||
/* Calculates the size (order) of the Kaiser window. Rejection is in dB and
|
||||
|
|
@ -124,11 +130,11 @@ constexpr double CalcKaiserBeta(const double rejection)
|
|||
* p -- gain compensation factor when sampling
|
||||
* f_t -- normalized center frequency (or cutoff; 0.5 is nyquist)
|
||||
*/
|
||||
double SincFilter(const uint l, const double b, const double gain, const double cutoff,
|
||||
const uint i)
|
||||
double SincFilter(const uint l, const double beta, const double besseli_0_beta, const double gain,
|
||||
const double cutoff, const uint i)
|
||||
{
|
||||
const double x{static_cast<double>(i) - l};
|
||||
return Kaiser(b, x / l) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
|
||||
return Kaiser(beta, x/l, besseli_0_beta) * 2.0 * gain * cutoff * Sinc(2.0 * cutoff * x);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
@ -137,7 +143,7 @@ double SincFilter(const uint l, const double b, const double gain, const double
|
|||
// that's used to cut frequencies above the destination nyquist.
|
||||
void PPhaseResampler::init(const uint srcRate, const uint dstRate)
|
||||
{
|
||||
const uint gcd{Gcd(srcRate, dstRate)};
|
||||
const uint gcd{std::gcd(srcRate, dstRate)};
|
||||
mP = dstRate / gcd;
|
||||
mQ = srcRate / gcd;
|
||||
|
||||
|
|
@ -145,78 +151,70 @@ void PPhaseResampler::init(const uint srcRate, const uint dstRate)
|
|||
* ends before the nyquist (0.5). Both are scaled by the downsampling
|
||||
* factor.
|
||||
*/
|
||||
double cutoff, width;
|
||||
if(mP > mQ)
|
||||
{
|
||||
cutoff = 0.475 / mP;
|
||||
width = 0.05 / mP;
|
||||
}
|
||||
else
|
||||
{
|
||||
cutoff = 0.475 / mQ;
|
||||
width = 0.05 / mQ;
|
||||
}
|
||||
const auto [cutoff, width] = (mP > mQ) ? std::make_tuple(0.475 / mP, 0.05 / mP)
|
||||
: std::make_tuple(0.475 / mQ, 0.05 / mQ);
|
||||
|
||||
// A rejection of -180 dB is used for the stop band. Round up when
|
||||
// calculating the left offset to avoid increasing the transition width.
|
||||
const uint l{(CalcKaiserOrder(180.0, width)+1) / 2};
|
||||
const double beta{CalcKaiserBeta(180.0)};
|
||||
const double besseli_0_beta{cyl_bessel_i(0, beta)};
|
||||
mM = l*2 + 1;
|
||||
mL = l;
|
||||
mF.resize(mM);
|
||||
for(uint i{0};i < mM;i++)
|
||||
mF[i] = SincFilter(l, beta, mP, cutoff, i);
|
||||
mF[i] = SincFilter(l, beta, besseli_0_beta, mP, cutoff, i);
|
||||
}
|
||||
|
||||
// Perform the upsample-filter-downsample resampling operation using a
|
||||
// polyphase filter implementation.
|
||||
void PPhaseResampler::process(const uint inN, const double *in, const uint outN, double *out)
|
||||
void PPhaseResampler::process(const al::span<const double> in, const al::span<double> out)
|
||||
{
|
||||
if(outN == 0) UNLIKELY
|
||||
if(out.empty()) UNLIKELY
|
||||
return;
|
||||
|
||||
// Handle in-place operation.
|
||||
std::vector<double> workspace;
|
||||
double *work{out};
|
||||
if(work == in) UNLIKELY
|
||||
al::span work{out};
|
||||
if(work.data() == in.data()) UNLIKELY
|
||||
{
|
||||
workspace.resize(outN);
|
||||
work = workspace.data();
|
||||
workspace.resize(out.size());
|
||||
work = workspace;
|
||||
}
|
||||
|
||||
// Resample the input.
|
||||
const uint p{mP}, q{mQ}, m{mM}, l{mL};
|
||||
const double *f{mF.data()};
|
||||
for(uint i{0};i < outN;i++)
|
||||
const al::span<const double> f{mF};
|
||||
for(uint i{0};i < out.size();i++)
|
||||
{
|
||||
// Input starts at l to compensate for the filter delay. This will
|
||||
// drop any build-up from the first half of the filter.
|
||||
size_t j_f{(l + q*i) % p};
|
||||
size_t j_s{(l + q*i) / p};
|
||||
std::size_t j_f{(l + q*i) % p};
|
||||
std::size_t j_s{(l + q*i) / p};
|
||||
|
||||
// Only take input when 0 <= j_s < inN.
|
||||
// Only take input when 0 <= j_s < in.size().
|
||||
double r{0.0};
|
||||
if(j_f < m) LIKELY
|
||||
{
|
||||
size_t filt_len{(m-j_f+p-1) / p};
|
||||
if(j_s+1 > inN) LIKELY
|
||||
std::size_t filt_len{(m-j_f+p-1) / p};
|
||||
if(j_s+1 > in.size()) LIKELY
|
||||
{
|
||||
size_t skip{std::min<size_t>(j_s+1 - inN, filt_len)};
|
||||
std::size_t skip{std::min(j_s+1 - in.size(), filt_len)};
|
||||
j_f += p*skip;
|
||||
j_s -= skip;
|
||||
filt_len -= skip;
|
||||
}
|
||||
if(size_t todo{std::min<size_t>(j_s+1, filt_len)}) LIKELY
|
||||
std::size_t todo{std::min(j_s+1, filt_len)};
|
||||
while(todo)
|
||||
{
|
||||
do {
|
||||
r += f[j_f] * in[j_s];
|
||||
j_f += p;
|
||||
--j_s;
|
||||
} while(--todo);
|
||||
r += f[j_f] * in[j_s];
|
||||
j_f += p; --j_s;
|
||||
--todo;
|
||||
}
|
||||
}
|
||||
work[i] = r;
|
||||
}
|
||||
// Clean up after in-place operation.
|
||||
if(work != out)
|
||||
std::copy_n(work, outN, out);
|
||||
if(work.data() != out.data())
|
||||
std::copy(work.cbegin(), work.cend(), out.begin());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "alspan.h"
|
||||
|
||||
|
||||
using uint = unsigned int;
|
||||
|
||||
|
|
@ -35,12 +37,12 @@ using uint = unsigned int;
|
|||
|
||||
struct PPhaseResampler {
|
||||
void init(const uint srcRate, const uint dstRate);
|
||||
void process(const uint inN, const double *in, const uint outN, double *out);
|
||||
void process(const al::span<const double> in, const al::span<double> out);
|
||||
|
||||
explicit operator bool() const noexcept { return !mF.empty(); }
|
||||
|
||||
private:
|
||||
uint mP, mQ, mM, mL;
|
||||
uint mP{}, mQ{}, mM{}, mL{};
|
||||
std::vector<double> mF;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -23,202 +23,150 @@
|
|||
#include "ringbuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
|
||||
#include "almalloc.h"
|
||||
#include "alnumeric.h"
|
||||
|
||||
|
||||
RingBufferPtr RingBuffer::Create(size_t sz, size_t elem_sz, int limit_writes)
|
||||
auto RingBuffer::Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> RingBufferPtr
|
||||
{
|
||||
size_t power_of_two{0u};
|
||||
std::size_t power_of_two{0u};
|
||||
if(sz > 0)
|
||||
{
|
||||
power_of_two = sz;
|
||||
power_of_two = sz - 1;
|
||||
power_of_two |= power_of_two>>1;
|
||||
power_of_two |= power_of_two>>2;
|
||||
power_of_two |= power_of_two>>4;
|
||||
power_of_two |= power_of_two>>8;
|
||||
power_of_two |= power_of_two>>16;
|
||||
#if SIZE_MAX > UINT_MAX
|
||||
power_of_two |= power_of_two>>32;
|
||||
#endif
|
||||
if constexpr(sizeof(size_t) > sizeof(uint32_t))
|
||||
power_of_two |= power_of_two>>32;
|
||||
}
|
||||
++power_of_two;
|
||||
if(power_of_two <= sz || power_of_two > std::numeric_limits<size_t>::max()/elem_sz)
|
||||
if(power_of_two < sz || power_of_two > std::numeric_limits<std::size_t>::max()>>1
|
||||
|| power_of_two > std::numeric_limits<std::size_t>::max()/elem_sz)
|
||||
throw std::overflow_error{"Ring buffer size overflow"};
|
||||
|
||||
const size_t bufbytes{power_of_two * elem_sz};
|
||||
RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{bufbytes}};
|
||||
rb->mWriteSize = limit_writes ? sz : (power_of_two-1);
|
||||
rb->mSizeMask = power_of_two - 1;
|
||||
rb->mElemSize = elem_sz;
|
||||
const std::size_t bufbytes{power_of_two * elem_sz};
|
||||
RingBufferPtr rb{new(FamCount(bufbytes)) RingBuffer{limit_writes ? sz : power_of_two,
|
||||
power_of_two-1, elem_sz, bufbytes}};
|
||||
|
||||
return rb;
|
||||
}
|
||||
|
||||
void RingBuffer::reset() noexcept
|
||||
{
|
||||
mWritePtr.store(0, std::memory_order_relaxed);
|
||||
mReadPtr.store(0, std::memory_order_relaxed);
|
||||
std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, al::byte{});
|
||||
mWriteCount.store(0, std::memory_order_relaxed);
|
||||
mReadCount.store(0, std::memory_order_relaxed);
|
||||
std::fill_n(mBuffer.begin(), (mSizeMask+1)*mElemSize, std::byte{});
|
||||
}
|
||||
|
||||
|
||||
size_t RingBuffer::read(void *dest, size_t cnt) noexcept
|
||||
auto RingBuffer::read(void *dest, std::size_t count) noexcept -> std::size_t
|
||||
{
|
||||
const size_t free_cnt{readSpace()};
|
||||
if(free_cnt == 0) return 0;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t readable{w - r};
|
||||
if(readable == 0) return 0;
|
||||
|
||||
const size_t to_read{std::min(cnt, free_cnt)};
|
||||
size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
|
||||
const std::size_t to_read{std::min(count, readable)};
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
size_t n1, n2;
|
||||
const size_t cnt2{read_ptr + to_read};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
{
|
||||
n1 = mSizeMask+1 - read_ptr;
|
||||
n2 = cnt2 & mSizeMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_read;
|
||||
n2 = 0;
|
||||
}
|
||||
const std::size_t rdend{read_idx + to_read};
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
|
||||
|
||||
auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
|
||||
static_cast<al::byte*>(dest));
|
||||
read_ptr += n1;
|
||||
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
|
||||
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
|
||||
dstbytes.begin());
|
||||
if(n2 > 0)
|
||||
{
|
||||
std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
|
||||
read_ptr += n2;
|
||||
}
|
||||
mReadPtr.store(read_ptr, std::memory_order_release);
|
||||
mReadCount.store(r+n1+n2, std::memory_order_release);
|
||||
return to_read;
|
||||
}
|
||||
|
||||
size_t RingBuffer::peek(void *dest, size_t cnt) const noexcept
|
||||
auto RingBuffer::peek(void *dest, std::size_t count) const noexcept -> std::size_t
|
||||
{
|
||||
const size_t free_cnt{readSpace()};
|
||||
if(free_cnt == 0) return 0;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t readable{w - r};
|
||||
if(readable == 0) return 0;
|
||||
|
||||
const size_t to_read{std::min(cnt, free_cnt)};
|
||||
size_t read_ptr{mReadPtr.load(std::memory_order_relaxed) & mSizeMask};
|
||||
const std::size_t to_read{std::min(count, readable)};
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
size_t n1, n2;
|
||||
const size_t cnt2{read_ptr + to_read};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
{
|
||||
n1 = mSizeMask+1 - read_ptr;
|
||||
n2 = cnt2 & mSizeMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_read;
|
||||
n2 = 0;
|
||||
}
|
||||
const std::size_t rdend{read_idx + to_read};
|
||||
const auto [n1, n2] = (rdend <= mSizeMask+1) ? std::make_tuple(to_read, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - read_idx, rdend&mSizeMask);
|
||||
|
||||
auto outiter = std::copy_n(mBuffer.begin() + read_ptr*mElemSize, n1*mElemSize,
|
||||
static_cast<al::byte*>(dest));
|
||||
auto dstbytes = al::span{static_cast<std::byte*>(dest), count*mElemSize};
|
||||
auto outiter = std::copy_n(mBuffer.begin() + ptrdiff_t(read_idx*mElemSize), n1*mElemSize,
|
||||
dstbytes.begin());
|
||||
if(n2 > 0)
|
||||
std::copy_n(mBuffer.begin(), n2*mElemSize, outiter);
|
||||
return to_read;
|
||||
}
|
||||
|
||||
size_t RingBuffer::write(const void *src, size_t cnt) noexcept
|
||||
auto RingBuffer::write(const void *src, std::size_t count) noexcept -> std::size_t
|
||||
{
|
||||
const size_t free_cnt{writeSpace()};
|
||||
if(free_cnt == 0) return 0;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
const std::size_t writable{mWriteSize - (w - r)};
|
||||
if(writable == 0) return 0;
|
||||
|
||||
const size_t to_write{std::min(cnt, free_cnt)};
|
||||
size_t write_ptr{mWritePtr.load(std::memory_order_relaxed) & mSizeMask};
|
||||
const std::size_t to_write{std::min(count, writable)};
|
||||
const std::size_t write_idx{w & mSizeMask};
|
||||
|
||||
size_t n1, n2;
|
||||
const size_t cnt2{write_ptr + to_write};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
{
|
||||
n1 = mSizeMask+1 - write_ptr;
|
||||
n2 = cnt2 & mSizeMask;
|
||||
}
|
||||
else
|
||||
{
|
||||
n1 = to_write;
|
||||
n2 = 0;
|
||||
}
|
||||
const std::size_t wrend{write_idx + to_write};
|
||||
const auto [n1, n2] = (wrend <= mSizeMask+1) ? std::make_tuple(to_write, 0_uz)
|
||||
: std::make_tuple(mSizeMask+1 - write_idx, wrend&mSizeMask);
|
||||
|
||||
auto srcbytes = static_cast<const al::byte*>(src);
|
||||
std::copy_n(srcbytes, n1*mElemSize, mBuffer.begin() + write_ptr*mElemSize);
|
||||
write_ptr += n1;
|
||||
auto srcbytes = al::span{static_cast<const std::byte*>(src), count*mElemSize};
|
||||
std::copy_n(srcbytes.cbegin(), n1*mElemSize, mBuffer.begin() + ptrdiff_t(write_idx*mElemSize));
|
||||
if(n2 > 0)
|
||||
{
|
||||
std::copy_n(srcbytes + n1*mElemSize, n2*mElemSize, mBuffer.begin());
|
||||
write_ptr += n2;
|
||||
}
|
||||
mWritePtr.store(write_ptr, std::memory_order_release);
|
||||
std::copy_n(srcbytes.cbegin() + ptrdiff_t(n1*mElemSize), n2*mElemSize, mBuffer.begin());
|
||||
mWriteCount.store(w+n1+n2, std::memory_order_release);
|
||||
return to_write;
|
||||
}
|
||||
|
||||
|
||||
auto RingBuffer::getReadVector() const noexcept -> DataPair
|
||||
auto RingBuffer::getReadVector() noexcept -> DataPair
|
||||
{
|
||||
DataPair ret;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t readable{w - r};
|
||||
const std::size_t read_idx{r & mSizeMask};
|
||||
|
||||
size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
size_t r{mReadPtr.load(std::memory_order_acquire)};
|
||||
w &= mSizeMask;
|
||||
r &= mSizeMask;
|
||||
const size_t free_cnt{(w-r) & mSizeMask};
|
||||
|
||||
const size_t cnt2{r + free_cnt};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
const std::size_t rdend{read_idx + readable};
|
||||
if(rdend > mSizeMask+1)
|
||||
{
|
||||
/* Two part vector: the rest of the buffer after the current read ptr,
|
||||
* plus some from the start of the buffer. */
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
|
||||
ret.first.len = mSizeMask+1 - r;
|
||||
ret.second.buf = const_cast<al::byte*>(mBuffer.data());
|
||||
ret.second.len = cnt2 & mSizeMask;
|
||||
* plus some from the start of the buffer.
|
||||
*/
|
||||
return DataPair{{mBuffer.data() + read_idx*mElemSize, mSizeMask+1 - read_idx},
|
||||
{mBuffer.data(), rdend&mSizeMask}};
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Single part vector: just the rest of the buffer */
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + r*mElemSize);
|
||||
ret.first.len = free_cnt;
|
||||
ret.second.buf = nullptr;
|
||||
ret.second.len = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return DataPair{{mBuffer.data() + read_idx*mElemSize, readable}, {}};
|
||||
}
|
||||
|
||||
auto RingBuffer::getWriteVector() const noexcept -> DataPair
|
||||
auto RingBuffer::getWriteVector() noexcept -> DataPair
|
||||
{
|
||||
DataPair ret;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
const std::size_t writable{mWriteSize - (w - r)};
|
||||
const std::size_t write_idx{w & mSizeMask};
|
||||
|
||||
size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};
|
||||
w &= mSizeMask;
|
||||
r &= mSizeMask;
|
||||
const size_t free_cnt{(r-w-1) & mSizeMask};
|
||||
|
||||
const size_t cnt2{w + free_cnt};
|
||||
if(cnt2 > mSizeMask+1)
|
||||
const std::size_t wrend{write_idx + writable};
|
||||
if(wrend > mSizeMask+1)
|
||||
{
|
||||
/* Two part vector: the rest of the buffer after the current write ptr,
|
||||
* plus some from the start of the buffer. */
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
|
||||
ret.first.len = mSizeMask+1 - w;
|
||||
ret.second.buf = const_cast<al::byte*>(mBuffer.data());
|
||||
ret.second.len = cnt2 & mSizeMask;
|
||||
* plus some from the start of the buffer.
|
||||
*/
|
||||
return DataPair{{mBuffer.data() + write_idx*mElemSize, mSizeMask+1 - write_idx},
|
||||
{mBuffer.data(), wrend&mSizeMask}};
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.first.buf = const_cast<al::byte*>(mBuffer.data() + w*mElemSize);
|
||||
ret.first.len = free_cnt;
|
||||
ret.second.buf = nullptr;
|
||||
ret.second.len = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
return DataPair{{mBuffer.data() + write_idx*mElemSize, writable}, {}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,111 +2,133 @@
|
|||
#define RINGBUFFER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <stddef.h>
|
||||
#include <new>
|
||||
#include <utility>
|
||||
|
||||
#include "albyte.h"
|
||||
#include "almalloc.h"
|
||||
#include "flexarray.h"
|
||||
|
||||
|
||||
/* NOTE: This lockless ringbuffer implementation is copied from JACK, extended
|
||||
* to include an element size. Consequently, parameters and return values for a
|
||||
* size or count is in 'elements', not bytes. Additionally, it only supports
|
||||
* size or count are in 'elements', not bytes. Additionally, it only supports
|
||||
* single-consumer/single-provider operation.
|
||||
*/
|
||||
|
||||
struct RingBuffer {
|
||||
private:
|
||||
std::atomic<size_t> mWritePtr{0u};
|
||||
std::atomic<size_t> mReadPtr{0u};
|
||||
size_t mWriteSize{0u};
|
||||
size_t mSizeMask{0u};
|
||||
size_t mElemSize{0u};
|
||||
#if defined(__cpp_lib_hardware_interference_size) && !defined(_LIBCPP_VERSION)
|
||||
static constexpr std::size_t sCacheAlignment{std::hardware_destructive_interference_size};
|
||||
#else
|
||||
/* Assume a 64-byte cache line, the most common/likely value. */
|
||||
static constexpr std::size_t sCacheAlignment{64};
|
||||
#endif
|
||||
alignas(sCacheAlignment) std::atomic<std::size_t> mWriteCount{0u};
|
||||
alignas(sCacheAlignment) std::atomic<std::size_t> mReadCount{0u};
|
||||
|
||||
al::FlexArray<al::byte, 16> mBuffer;
|
||||
alignas(sCacheAlignment) const std::size_t mWriteSize;
|
||||
const std::size_t mSizeMask;
|
||||
const std::size_t mElemSize;
|
||||
|
||||
al::FlexArray<std::byte, 16> mBuffer;
|
||||
|
||||
public:
|
||||
struct Data {
|
||||
al::byte *buf;
|
||||
size_t len;
|
||||
std::byte *buf;
|
||||
std::size_t len;
|
||||
};
|
||||
using DataPair = std::pair<Data,Data>;
|
||||
|
||||
|
||||
RingBuffer(const size_t count) : mBuffer{count} { }
|
||||
RingBuffer(const std::size_t writesize, const std::size_t mask, const std::size_t elemsize,
|
||||
const std::size_t numbytes)
|
||||
: mWriteSize{writesize}, mSizeMask{mask}, mElemSize{elemsize}, mBuffer{numbytes}
|
||||
{ }
|
||||
|
||||
/** Reset the read and write pointers to zero. This is not thread safe. */
|
||||
void reset() noexcept;
|
||||
auto reset() noexcept -> void;
|
||||
|
||||
/**
|
||||
* Return the number of elements available for reading. This is the number
|
||||
* of elements in front of the read pointer and behind the write pointer.
|
||||
*/
|
||||
[[nodiscard]] auto readSpace() const noexcept -> std::size_t
|
||||
{
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
/* mWriteCount is never more than mWriteSize greater than mReadCount. */
|
||||
return w - r;
|
||||
}
|
||||
|
||||
/**
|
||||
* The copying data reader. Copy at most `count' elements into `dest'.
|
||||
* Returns the actual number of elements copied.
|
||||
*/
|
||||
[[nodiscard]] auto read(void *dest, std::size_t count) noexcept -> std::size_t;
|
||||
/**
|
||||
* The copying data reader w/o read pointer advance. Copy at most `count'
|
||||
* elements into `dest'. Returns the actual number of elements copied.
|
||||
*/
|
||||
[[nodiscard]] auto peek(void *dest, std::size_t count) const noexcept -> std::size_t;
|
||||
|
||||
/**
|
||||
* The non-copying data reader. Returns two ringbuffer data pointers that
|
||||
* hold the current readable data. If the readable data is in one segment
|
||||
* the second segment has zero length.
|
||||
*/
|
||||
DataPair getReadVector() const noexcept;
|
||||
[[nodiscard]] auto getReadVector() noexcept -> DataPair;
|
||||
/** Advance the read pointer `count' places. */
|
||||
auto readAdvance(std::size_t count) noexcept -> void
|
||||
{
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_acquire)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_relaxed)};
|
||||
[[maybe_unused]] const std::size_t readable{w - r};
|
||||
assert(readable >= count);
|
||||
mReadCount.store(r+count, std::memory_order_release);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of elements available for writing. This is the total
|
||||
* number of writable elements excluding what's readable (already written).
|
||||
*/
|
||||
[[nodiscard]] auto writeSpace() const noexcept -> std::size_t
|
||||
{ return mWriteSize - readSpace(); }
|
||||
|
||||
/**
|
||||
* The copying data writer. Copy at most `count' elements from `src'. Returns
|
||||
* the actual number of elements copied.
|
||||
*/
|
||||
[[nodiscard]] auto write(const void *src, std::size_t count) noexcept -> std::size_t;
|
||||
|
||||
/**
|
||||
* The non-copying data writer. Returns two ringbuffer data pointers that
|
||||
* hold the current writeable data. If the writeable data is in one segment
|
||||
* the second segment has zero length.
|
||||
*/
|
||||
DataPair getWriteVector() const noexcept;
|
||||
|
||||
/**
|
||||
* Return the number of elements available for reading. This is the number
|
||||
* of elements in front of the read pointer and behind the write pointer.
|
||||
*/
|
||||
size_t readSpace() const noexcept
|
||||
[[nodiscard]] auto getWriteVector() noexcept -> DataPair;
|
||||
/** Advance the write pointer `count' places. */
|
||||
auto writeAdvance(std::size_t count) noexcept -> void
|
||||
{
|
||||
const size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
const size_t r{mReadPtr.load(std::memory_order_acquire)};
|
||||
return (w-r) & mSizeMask;
|
||||
const std::size_t w{mWriteCount.load(std::memory_order_relaxed)};
|
||||
const std::size_t r{mReadCount.load(std::memory_order_acquire)};
|
||||
[[maybe_unused]] const std::size_t writable{mWriteSize - (w - r)};
|
||||
assert(writable >= count);
|
||||
mWriteCount.store(w+count, std::memory_order_release);
|
||||
}
|
||||
|
||||
/**
|
||||
* The copying data reader. Copy at most `cnt' elements into `dest'.
|
||||
* Returns the actual number of elements copied.
|
||||
*/
|
||||
size_t read(void *dest, size_t cnt) noexcept;
|
||||
/**
|
||||
* The copying data reader w/o read pointer advance. Copy at most `cnt'
|
||||
* elements into `dest'. Returns the actual number of elements copied.
|
||||
*/
|
||||
size_t peek(void *dest, size_t cnt) const noexcept;
|
||||
/** Advance the read pointer `cnt' places. */
|
||||
void readAdvance(size_t cnt) noexcept
|
||||
{ mReadPtr.fetch_add(cnt, std::memory_order_acq_rel); }
|
||||
|
||||
|
||||
/**
|
||||
* Return the number of elements available for writing. This is the number
|
||||
* of elements in front of the write pointer and behind the read pointer.
|
||||
*/
|
||||
size_t writeSpace() const noexcept
|
||||
{
|
||||
const size_t w{mWritePtr.load(std::memory_order_acquire)};
|
||||
const size_t r{mReadPtr.load(std::memory_order_acquire) + mWriteSize - mSizeMask};
|
||||
return (r-w-1) & mSizeMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* The copying data writer. Copy at most `cnt' elements from `src'. Returns
|
||||
* the actual number of elements copied.
|
||||
*/
|
||||
size_t write(const void *src, size_t cnt) noexcept;
|
||||
/** Advance the write pointer `cnt' places. */
|
||||
void writeAdvance(size_t cnt) noexcept
|
||||
{ mWritePtr.fetch_add(cnt, std::memory_order_acq_rel); }
|
||||
|
||||
size_t getElemSize() const noexcept { return mElemSize; }
|
||||
[[nodiscard]] auto getElemSize() const noexcept -> std::size_t { return mElemSize; }
|
||||
|
||||
/**
|
||||
* Create a new ringbuffer to hold at least `sz' elements of `elem_sz'
|
||||
* bytes. The number of elements is rounded up to the next power of two
|
||||
* (even if it is already a power of two, to ensure the requested amount
|
||||
* can be written).
|
||||
* bytes. The number of elements is rounded up to a power of two. If
|
||||
* `limit_writes' is true, the writable space will be limited to `sz'
|
||||
* elements regardless of the rounded size.
|
||||
*/
|
||||
static std::unique_ptr<RingBuffer> Create(size_t sz, size_t elem_sz, int limit_writes);
|
||||
[[nodiscard]] static
|
||||
auto Create(std::size_t sz, std::size_t elem_sz, bool limit_writes) -> std::unique_ptr<RingBuffer>;
|
||||
|
||||
DEF_FAM_NEWDEL(RingBuffer, mBuffer)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,36 +5,37 @@
|
|||
|
||||
#include <cstdlib>
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
std::string wstr_to_utf8(const WCHAR *wstr)
|
||||
#include "alstring.h"
|
||||
|
||||
std::string wstr_to_utf8(std::wstring_view wstr)
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
|
||||
const int len{WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), nullptr, 0,
|
||||
nullptr, nullptr)};
|
||||
if(len > 0)
|
||||
{
|
||||
ret.resize(len);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, &ret[0], len, nullptr, nullptr);
|
||||
ret.pop_back();
|
||||
ret.resize(static_cast<size_t>(len));
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), al::sizei(wstr), ret.data(), len,
|
||||
nullptr, nullptr);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::wstring utf8_to_wstr(const char *str)
|
||||
std::wstring utf8_to_wstr(std::string_view str)
|
||||
{
|
||||
std::wstring ret;
|
||||
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, str, -1, nullptr, 0);
|
||||
const int len{MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), nullptr, 0)};
|
||||
if(len > 0)
|
||||
{
|
||||
ret.resize(len);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str, -1, &ret[0], len);
|
||||
ret.pop_back();
|
||||
ret.resize(static_cast<size_t>(len));
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.data(), al::sizei(str), ret.data(), len);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
@ -43,21 +44,25 @@ std::wstring utf8_to_wstr(const char *str)
|
|||
|
||||
namespace al {
|
||||
|
||||
al::optional<std::string> getenv(const char *envname)
|
||||
std::optional<std::string> getenv(const char *envname)
|
||||
{
|
||||
#ifdef _GAMING_XBOX
|
||||
const char *str{::getenv(envname)};
|
||||
#else
|
||||
const char *str{std::getenv(envname)};
|
||||
if(str && str[0] != '\0')
|
||||
#endif
|
||||
if(str && *str != '\0')
|
||||
return str;
|
||||
return al::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
al::optional<std::wstring> getenv(const WCHAR *envname)
|
||||
std::optional<std::wstring> getenv(const WCHAR *envname)
|
||||
{
|
||||
const WCHAR *str{_wgetenv(envname)};
|
||||
if(str && str[0] != L'\0')
|
||||
if(str && *str != L'\0')
|
||||
return str;
|
||||
return al::nullopt;
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,22 @@
|
|||
#ifndef AL_STRUTILS_H
|
||||
#define AL_STRUTILS_H
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "aloptional.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wchar.h>
|
||||
#include <cwchar>
|
||||
#include <string_view>
|
||||
|
||||
std::string wstr_to_utf8(const wchar_t *wstr);
|
||||
std::wstring utf8_to_wstr(const char *str);
|
||||
std::string wstr_to_utf8(std::wstring_view wstr);
|
||||
std::wstring utf8_to_wstr(std::string_view str);
|
||||
#endif
|
||||
|
||||
namespace al {
|
||||
|
||||
al::optional<std::string> getenv(const char *envname);
|
||||
std::optional<std::string> getenv(const char *envname);
|
||||
#ifdef _WIN32
|
||||
al::optional<std::wstring> getenv(const wchar_t *envname);
|
||||
std::optional<std::wstring> getenv(const wchar_t *envname);
|
||||
#endif
|
||||
|
||||
} // namespace al
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
#ifndef AL_THREADS_H
|
||||
#define AL_THREADS_H
|
||||
|
||||
#if defined(__GNUC__) && defined(__i386__)
|
||||
/* force_align_arg_pointer is required for proper function arguments aligning
|
||||
* when SSE code is used. Some systems (Windows, QNX) do not guarantee our
|
||||
* thread functions will be properly aligned on the stack, even though GCC may
|
||||
* generate code with the assumption that it is. */
|
||||
#define FORCE_ALIGN __attribute__((force_align_arg_pointer))
|
||||
#else
|
||||
#define FORCE_ALIGN
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <dispatch/dispatch.h>
|
||||
#elif !defined(_WIN32)
|
||||
#include <semaphore.h>
|
||||
#endif
|
||||
|
||||
void althrd_setname(const char *name);
|
||||
|
||||
namespace al {
|
||||
|
||||
class semaphore {
|
||||
#ifdef _WIN32
|
||||
using native_type = void*;
|
||||
#elif defined(__APPLE__)
|
||||
using native_type = dispatch_semaphore_t;
|
||||
#else
|
||||
using native_type = sem_t;
|
||||
#endif
|
||||
native_type mSem;
|
||||
|
||||
public:
|
||||
semaphore(unsigned int initial=0);
|
||||
semaphore(const semaphore&) = delete;
|
||||
~semaphore();
|
||||
|
||||
semaphore& operator=(const semaphore&) = delete;
|
||||
|
||||
void post();
|
||||
void wait() noexcept;
|
||||
bool try_wait() noexcept;
|
||||
};
|
||||
|
||||
} // namespace al
|
||||
|
||||
#endif /* AL_THREADS_H */
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef COMMON_VECMAT_H
|
||||
#define COMMON_VECMAT_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
|
@ -11,22 +12,24 @@
|
|||
|
||||
namespace alu {
|
||||
|
||||
template<typename T>
|
||||
class VectorR {
|
||||
static_assert(std::is_floating_point<T>::value, "Must use floating-point types");
|
||||
alignas(16) T mVals[4];
|
||||
class Vector {
|
||||
alignas(16) std::array<float,4> mVals{};
|
||||
|
||||
public:
|
||||
constexpr VectorR() noexcept = default;
|
||||
constexpr VectorR(const VectorR&) noexcept = default;
|
||||
constexpr explicit VectorR(T a, T b, T c, T d) noexcept : mVals{a, b, c, d} { }
|
||||
constexpr Vector() noexcept = default;
|
||||
constexpr Vector(const Vector&) noexcept = default;
|
||||
constexpr Vector(Vector&&) noexcept = default;
|
||||
constexpr explicit Vector(float a, float b, float c, float d) noexcept : mVals{{a,b,c,d}} { }
|
||||
|
||||
constexpr VectorR& operator=(const VectorR&) noexcept = default;
|
||||
constexpr auto operator=(const Vector&) noexcept -> Vector& = default;
|
||||
constexpr auto operator=(Vector&&) noexcept -> Vector& = default;
|
||||
|
||||
constexpr T& operator[](size_t idx) noexcept { return mVals[idx]; }
|
||||
constexpr const T& operator[](size_t idx) const noexcept { return mVals[idx]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto operator[](std::size_t idx) noexcept -> float& { return mVals[idx]; }
|
||||
[[nodiscard]] constexpr
|
||||
auto operator[](std::size_t idx) const noexcept -> const float& { return mVals[idx]; }
|
||||
|
||||
constexpr VectorR& operator+=(const VectorR &rhs) noexcept
|
||||
constexpr auto operator+=(const Vector &rhs) noexcept -> Vector&
|
||||
{
|
||||
mVals[0] += rhs.mVals[0];
|
||||
mVals[1] += rhs.mVals[1];
|
||||
|
|
@ -35,85 +38,85 @@ public:
|
|||
return *this;
|
||||
}
|
||||
|
||||
constexpr VectorR operator-(const VectorR &rhs) const noexcept
|
||||
[[nodiscard]] constexpr
|
||||
auto operator-(const Vector &rhs) const noexcept -> Vector
|
||||
{
|
||||
return VectorR{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
|
||||
return Vector{mVals[0] - rhs.mVals[0], mVals[1] - rhs.mVals[1],
|
||||
mVals[2] - rhs.mVals[2], mVals[3] - rhs.mVals[3]};
|
||||
}
|
||||
|
||||
constexpr T normalize(T limit = std::numeric_limits<T>::epsilon())
|
||||
constexpr auto normalize(float limit = std::numeric_limits<float>::epsilon()) -> float
|
||||
{
|
||||
limit = std::max(limit, std::numeric_limits<T>::epsilon());
|
||||
const T length_sqr{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]};
|
||||
limit = std::max(limit, std::numeric_limits<float>::epsilon());
|
||||
const auto length_sqr = float{mVals[0]*mVals[0] + mVals[1]*mVals[1] + mVals[2]*mVals[2]};
|
||||
if(length_sqr > limit*limit)
|
||||
{
|
||||
const T length{std::sqrt(length_sqr)};
|
||||
T inv_length{T{1}/length};
|
||||
const auto length = float{std::sqrt(length_sqr)};
|
||||
auto inv_length = float{1.0f / length};
|
||||
mVals[0] *= inv_length;
|
||||
mVals[1] *= inv_length;
|
||||
mVals[2] *= inv_length;
|
||||
return length;
|
||||
}
|
||||
mVals[0] = mVals[1] = mVals[2] = T{0};
|
||||
return T{0};
|
||||
mVals[0] = mVals[1] = mVals[2] = 0.0f;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
constexpr VectorR cross_product(const alu::VectorR<T> &rhs) const noexcept
|
||||
[[nodiscard]] constexpr auto cross_product(const Vector &rhs) const noexcept -> Vector
|
||||
{
|
||||
return VectorR{
|
||||
return Vector{
|
||||
mVals[1]*rhs.mVals[2] - mVals[2]*rhs.mVals[1],
|
||||
mVals[2]*rhs.mVals[0] - mVals[0]*rhs.mVals[2],
|
||||
mVals[0]*rhs.mVals[1] - mVals[1]*rhs.mVals[0],
|
||||
T{0}};
|
||||
0.0f};
|
||||
}
|
||||
|
||||
constexpr T dot_product(const alu::VectorR<T> &rhs) const noexcept
|
||||
[[nodiscard]] constexpr auto dot_product(const Vector &rhs) const noexcept -> float
|
||||
{ return mVals[0]*rhs.mVals[0] + mVals[1]*rhs.mVals[1] + mVals[2]*rhs.mVals[2]; }
|
||||
};
|
||||
using Vector = VectorR<float>;
|
||||
|
||||
template<typename T>
|
||||
class MatrixR {
|
||||
static_assert(std::is_floating_point<T>::value, "Must use floating-point types");
|
||||
alignas(16) T mVals[16];
|
||||
class Matrix {
|
||||
alignas(16) std::array<float,16> mVals{};
|
||||
|
||||
public:
|
||||
constexpr MatrixR() noexcept = default;
|
||||
constexpr MatrixR(const MatrixR&) noexcept = default;
|
||||
constexpr explicit MatrixR(
|
||||
T aa, T ab, T ac, T ad,
|
||||
T ba, T bb, T bc, T bd,
|
||||
T ca, T cb, T cc, T cd,
|
||||
T da, T db, T dc, T dd) noexcept
|
||||
: mVals{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}
|
||||
constexpr Matrix() noexcept = default;
|
||||
constexpr Matrix(const Matrix&) noexcept = default;
|
||||
constexpr Matrix(Matrix&&) noexcept = default;
|
||||
constexpr explicit Matrix(
|
||||
float aa, float ab, float ac, float ad,
|
||||
float ba, float bb, float bc, float bd,
|
||||
float ca, float cb, float cc, float cd,
|
||||
float da, float db, float dc, float dd) noexcept
|
||||
: mVals{{aa,ab,ac,ad, ba,bb,bc,bd, ca,cb,cc,cd, da,db,dc,dd}}
|
||||
{ }
|
||||
|
||||
constexpr MatrixR& operator=(const MatrixR&) noexcept = default;
|
||||
constexpr auto operator=(const Matrix&) noexcept -> Matrix& = default;
|
||||
constexpr auto operator=(Matrix&&) noexcept -> Matrix& = default;
|
||||
|
||||
constexpr auto operator[](size_t idx) noexcept { return al::span<T,4>{&mVals[idx*4], 4}; }
|
||||
constexpr auto operator[](size_t idx) const noexcept
|
||||
{ return al::span<const T,4>{&mVals[idx*4], 4}; }
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) noexcept
|
||||
{ return al::span<float,4>{&mVals[idx*4], 4}; }
|
||||
[[nodiscard]] constexpr auto operator[](std::size_t idx) const noexcept
|
||||
{ return al::span<const float,4>{&mVals[idx*4], 4}; }
|
||||
|
||||
static constexpr MatrixR Identity() noexcept
|
||||
[[nodiscard]] static constexpr auto Identity() noexcept -> Matrix
|
||||
{
|
||||
return MatrixR{
|
||||
T{1}, T{0}, T{0}, T{0},
|
||||
T{0}, T{1}, T{0}, T{0},
|
||||
T{0}, T{0}, T{1}, T{0},
|
||||
T{0}, T{0}, T{0}, T{1}};
|
||||
return Matrix{
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f};
|
||||
}
|
||||
|
||||
[[nodiscard]] friend constexpr
|
||||
auto operator*(const Matrix &mtx, const Vector &vec) noexcept -> Vector
|
||||
{
|
||||
return Vector{
|
||||
vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
|
||||
vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
|
||||
vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
|
||||
vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]};
|
||||
}
|
||||
};
|
||||
using Matrix = MatrixR<float>;
|
||||
|
||||
template<typename T>
|
||||
constexpr VectorR<T> operator*(const MatrixR<T> &mtx, const VectorR<T> &vec) noexcept
|
||||
{
|
||||
return VectorR<T>{
|
||||
vec[0]*mtx[0][0] + vec[1]*mtx[1][0] + vec[2]*mtx[2][0] + vec[3]*mtx[3][0],
|
||||
vec[0]*mtx[0][1] + vec[1]*mtx[1][1] + vec[2]*mtx[2][1] + vec[3]*mtx[3][1],
|
||||
vec[0]*mtx[0][2] + vec[1]*mtx[1][2] + vec[2]*mtx[2][2] + vec[3]*mtx[3][2],
|
||||
vec[0]*mtx[0][3] + vec[1]*mtx[1][3] + vec[2]*mtx[2][3] + vec[3]*mtx[3][3]};
|
||||
}
|
||||
|
||||
} // namespace alu
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
#ifndef AL_VECTOR_H
|
||||
#define AL_VECTOR_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "almalloc.h"
|
||||
|
||||
namespace al {
|
||||
|
||||
template<typename T, size_t alignment=alignof(T)>
|
||||
template<typename T, std::size_t alignment=alignof(T)>
|
||||
using vector = std::vector<T, al::allocator<T, alignment>>;
|
||||
|
||||
} // namespace al
|
||||
|
|
|
|||
|
|
@ -20,14 +20,20 @@
|
|||
|
||||
#define STATIC_CAST(...) static_cast<__VA_ARGS__>
|
||||
#define REINTERPRET_CAST(...) reinterpret_cast<__VA_ARGS__>
|
||||
#define MAYBE_UNUSED [[maybe_unused]]
|
||||
|
||||
#else
|
||||
|
||||
#define STATIC_CAST(...) (__VA_ARGS__)
|
||||
#define REINTERPRET_CAST(...) (__VA_ARGS__)
|
||||
#ifdef __GNUC__
|
||||
#define MAYBE_UNUSED __attribute__((__unused__))
|
||||
#else
|
||||
#define MAYBE_UNUSED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
static FILE *my_fopen(const char *fname, const char *mode)
|
||||
MAYBE_UNUSED static FILE *my_fopen(const char *fname, const char *mode)
|
||||
{
|
||||
wchar_t *wname=NULL, *wmode=NULL;
|
||||
int namelen, modelen;
|
||||
|
|
@ -44,10 +50,11 @@ static FILE *my_fopen(const char *fname, const char *mode)
|
|||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
auto strbuf = std::make_unique<wchar_t[]>(static_cast<size_t>(namelen)+modelen);
|
||||
auto strbuf = std::make_unique<wchar_t[]>(static_cast<size_t>(namelen) +
|
||||
static_cast<size_t>(modelen));
|
||||
wname = strbuf.get();
|
||||
#else
|
||||
wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + modelen);
|
||||
wname = (wchar_t*)calloc(sizeof(wchar_t), (size_t)namelen + (size_t)modelen);
|
||||
#endif
|
||||
wmode = wname + namelen;
|
||||
MultiByteToWideChar(CP_UTF8, 0, fname, -1, wname, namelen);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue