diff options
Diffstat (limited to 'compat')
51 files changed, 2002 insertions, 913 deletions
diff --git a/compat/CMakeLists.txt b/compat/CMakeLists.txt index 40850862..dc81436d 100644 --- a/compat/CMakeLists.txt +++ b/compat/CMakeLists.txt @@ -1,9 +1,9 @@ otr_module(compat NO-COMPAT BIN) if(NOT WIN32 AND NOT APPLE) - target_link_libraries(opentrack-compat rt) + target_link_libraries(${self} rt) endif() -if(CMAKE_COMPILER_IS_GNUCXX) - set_property(SOURCE nan.cpp APPEND_STRING PROPERTY COMPILE_FLAGS "-fno-lto -fno-fast-math -fno-finite-math-only -O0") +if(WIN32) + target_link_libraries(${self} strmiids) endif() diff --git a/compat/activation-context.cpp b/compat/activation-context.cpp new file mode 100644 index 00000000..8d34243d --- /dev/null +++ b/compat/activation-context.cpp @@ -0,0 +1,51 @@ +#ifdef _WIN32 + +#include "activation-context.hpp" +#include "compat/library-path.hpp" + +#include <QString> +#include <QFile> +#include <QDebug> + +#include <windows.h> + +static_assert(sizeof(std::uintptr_t) == sizeof(ULONG_PTR)); + +activation_context::activation_context(const QString& module_name, int resid) +{ + static const QString prefix = OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH + OPENTRACK_LIBRARY_PREFIX; + QString path = prefix + module_name; + QByteArray name = QFile::encodeName(path); + + ACTCTXA actx = {}; + actx.cbSize = sizeof(actx); + actx.lpResourceName = MAKEINTRESOURCEA(resid); + actx.dwFlags = ACTCTX_FLAG_RESOURCE_NAME_VALID; + actx.lpSource = name.constData(); + + handle = CreateActCtxA(&actx); + + if (handle != INVALID_HANDLE_VALUE) + { + if (!ActivateActCtx(handle, (ULONG_PTR*)&cookie)) + { + qDebug() << "win32: can't set activation context" << GetLastError(); + ReleaseActCtx(handle); + handle = INVALID_HANDLE_VALUE; + } + else + ok = true; + } else { + qDebug() << "win32: can't create activation context" << GetLastError(); + } +} + +activation_context::~activation_context() +{ + if (handle != INVALID_HANDLE_VALUE) + { + DeactivateActCtx(0, cookie); + ReleaseActCtx(handle); + } +} +#endif diff --git a/compat/activation-context.hpp b/compat/activation-context.hpp new file mode 100644 index 00000000..a3b0429e --- /dev/null +++ b/compat/activation-context.hpp @@ -0,0 +1,26 @@ +#pragma once + +#ifdef _WIN32 + +#include "export.hpp" + +#include <cstdint> +#include <QString> + +class OTR_COMPAT_EXPORT activation_context +{ +public: + explicit activation_context(const QString& module_name, int resid); + ~activation_context(); + + explicit operator bool() const { return ok; } + +private: + std::uintptr_t cookie = 0; + void* handle = (void*)-1; + bool ok = false; +}; + +#else +# error "tried to use win32-only activation context" +#endif diff --git a/compat/arch.hpp b/compat/arch.hpp new file mode 100644 index 00000000..3120116a --- /dev/null +++ b/compat/arch.hpp @@ -0,0 +1,50 @@ +#pragma once + +// fix MSVC arch check macros + +// this file is too simple to fall under copyright, and +// can be copied, modified, and redistributed freely with +// no conditions. there's no warranty. -sh 20181226 + +#if defined _MSC_VER +# ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wreserved-id-macro" +# pragma clang diagnostic ignored "-Wunused-macros" +# endif + +# if defined _M_AMD64 +# undef __x86_64__ +# define __x86_64__ 1 +# elif defined _M_IX86 +# undef __i386__ +# define __i386__ 1 +# endif + +# if defined __AVX__ || defined __x86_64__ || \ + defined _M_IX86 && _M_IX86_FP >= 2 +# undef __SSE__ +# undef __SSE2__ +# undef __SSE3__ +# define __SSE__ 1 +# define __SSE2__ 1 +# define __SSE3__ 1 // assume SSE3 in the _M_IX86_FP >= 2 case +# endif + +# ifdef __clang__ +# pragma clang diagnostic pop +# endif +#endif + +#if defined __SSE3__ +# define OTR_ARCH_DENORM_DAZ +# include <pmmintrin.h> +#elif defined __SSE2__ +# define OTR_ARCH_DENORM_FTZ +# include <emmintrin.h> +#endif + +#if defined __SSE2__ +# define OTR_ARCH_FPU_MASK +# include <xmmintrin.h> +#endif diff --git a/compat/base-path.cpp b/compat/base-path.cpp new file mode 100644 index 00000000..2a26dcff --- /dev/null +++ b/compat/base-path.cpp @@ -0,0 +1,12 @@ +#undef NDEBUG +#include <cassert> + +#include "base-path.hpp" +#include <QCoreApplication> + +const QString& application_base_path() +{ + assert(qApp && "logic error"); + static QString path = QCoreApplication::applicationDirPath(); + return path; +} diff --git a/compat/base-path.hpp b/compat/base-path.hpp new file mode 100644 index 00000000..7a9d4115 --- /dev/null +++ b/compat/base-path.hpp @@ -0,0 +1,11 @@ +#pragma once + +#include "export.hpp" + +#include <QString> + +OTR_COMPAT_EXPORT +const QString& application_base_path(); + +#define OPENTRACK_BASE_PATH (application_base_path()) + diff --git a/compat/camera-names.cpp b/compat/camera-names.cpp index 22dcdd8f..b9511037 100644 --- a/compat/camera-names.cpp +++ b/compat/camera-names.cpp @@ -1,95 +1,130 @@ #include "camera-names.hpp" +#include <algorithm> +#include <iterator> + #ifdef _WIN32 +# include <cwchar> # define NO_DSHOW_STRSAFE # include <dshow.h> -# include <cwchar> -#elif defined(__unix) || defined(__linux) || defined(__APPLE__) +#elif defined(__unix) || defined(__linux__) || defined(__APPLE__) # include <unistd.h> #endif -#ifdef __linux +#ifdef __APPLE__ +# include <QCameraInfo> +#endif + +#ifdef __linux__ # include <fcntl.h> # include <sys/ioctl.h> # include <linux/videodev2.h> # include <cerrno> +# include <cstring> #endif #include <QDebug> -OTR_COMPAT_EXPORT int camera_name_to_index(const QString &name) +int camera_name_to_index(const QString &name) { auto list = get_camera_names(); - int ret = list.indexOf(name); - if (ret < 0) - ret = 0; + auto it = std::find_if(list.cbegin(), list.cend(), [&name](const auto& tuple) { + const auto& [str, idx] = tuple; + return str == name; + }); + if (it != list.cend()) + { + const auto& [ str, idx ] = *it; + return idx; + } + + return -1; +} + +#ifdef _WIN32 +# include <QRegularExpression> + +static QString prop_to_qstring(IPropertyBag* pPropBag, const wchar_t* name) +{ + QString ret{}; + VARIANT var; + VariantInit(&var); + HRESULT hr = pPropBag->Read(name, &var, nullptr); + if (SUCCEEDED(hr)) + ret = QString{(const QChar*)var.bstrVal, int(std::wcslen(var.bstrVal))}; + VariantClear(&var); return ret; } -OTR_COMPAT_EXPORT QList<QString> get_camera_names() +static QString device_path_from_qstring(const QString& str) +{ + // language=RegExp prefix=R"/( suffix=)/" + static const QRegularExpression regexp{R"/(#vid_([0-9a-f]{4})&pid_([0-9a-f]{4})&mi_([0-9a-f]{2})#([^#]+))/", + QRegularExpression::CaseInsensitiveOption}; + auto match = regexp.match(str); + if (!match.hasMatch()) + return {}; + QString id = match.captured(4); + id.replace('&', '_'); + return id; +} + +#endif + +std::vector<std::tuple<QString, int>> get_camera_names() { - QList<QString> ret; -#if defined(_WIN32) + std::vector<std::tuple<QString, int>> ret; +#ifdef _WIN32 // Create the System Device Enumerator. HRESULT hr; CoInitialize(nullptr); - ICreateDevEnum *pSysDevEnum = NULL; - hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum); + ICreateDevEnum *pSysDevEnum = nullptr; + hr = CoCreateInstance(CLSID_SystemDeviceEnum, nullptr, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pSysDevEnum); if (FAILED(hr)) { qDebug() << "failed CLSID_SystemDeviceEnum" << hr; return ret; } // Obtain a class enumerator for the video compressor category. - IEnumMoniker *pEnumCat = NULL; + IEnumMoniker *pEnumCat = nullptr; hr = pSysDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pEnumCat, 0); if (hr == S_OK) { // Enumerate the monikers. - IMoniker *pMoniker = NULL; + IMoniker *pMoniker = nullptr; ULONG cFetched; while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK) { IPropertyBag *pPropBag; - hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pPropBag); - if (SUCCEEDED(hr)) { + hr = pMoniker->BindToStorage(nullptr, nullptr, IID_IPropertyBag, (void **)&pPropBag); + if (SUCCEEDED(hr)) { // To retrieve the filter's friendly name, do the following: - VARIANT varName; - VariantInit(&varName); - hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { - // Display the name in your UI somehow. - QString str((QChar*)varName.bstrVal, int(std::wcslen(varName.bstrVal))); - ret.append(str); + QString str = prop_to_qstring(pPropBag, L"FriendlyName"); + QString path = device_path_from_qstring(prop_to_qstring(pPropBag, L"DevicePath")); + if (!path.isNull()) + str += QStringLiteral(" [%1]").arg(path); + ret.push_back({ str, (int)ret.size() }); } - VariantClear(&varName); - - ////// To create an instance of the filter, do the following: - ////IBaseFilter *pFilter; - ////hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, - //// (void**)&pFilter); - // Now add the filter to the graph. - //Remember to release pFilter later. pPropBag->Release(); } pMoniker->Release(); } pEnumCat->Release(); } +#if 0 else qDebug() << "failed CLSID_VideoInputDeviceCategory" << hr; +#endif pSysDevEnum->Release(); #endif -#ifdef __linux + +#ifdef __linux__ for (int i = 0; i < 16; i++) { - char buf[128]; - sprintf(buf, "/dev/video%d", i); - if (access(buf, F_OK) == 0) - ret.append(buf); - else - continue; + char buf[32]; + snprintf(buf, sizeof(buf), "/dev/video%d", i); if (access(buf, R_OK | W_OK) == 0) { int fd = open(buf, O_RDONLY); @@ -102,10 +137,16 @@ OTR_COMPAT_EXPORT QList<QString> get_camera_names() close(fd); continue; } - ret[ret.size()-1] = reinterpret_cast<const char*>(video_cap.card); + ret.push_back({ QString((const char*)video_cap.card), i}); close(fd); } } #endif +#ifdef __APPLE__ + QList<QCameraInfo> cameras = QCameraInfo::availableCameras(); + for (const QCameraInfo &cameraInfo : cameras) + ret.push_back({ cameraInfo.description(), ret.size() }); +#endif + return ret; } diff --git a/compat/camera-names.hpp b/compat/camera-names.hpp index 97184c8c..3585edfe 100644 --- a/compat/camera-names.hpp +++ b/compat/camera-names.hpp @@ -8,11 +8,12 @@ #pragma once -#include <QList> +#include <vector> #include <QString> +# include <tuple> #include "export.hpp" -OTR_COMPAT_EXPORT QList<QString> get_camera_names(); +OTR_COMPAT_EXPORT std::vector<std::tuple<QString, int>> get_camera_names(); OTR_COMPAT_EXPORT int camera_name_to_index(const QString &name); diff --git a/compat/check-visible.cpp b/compat/check-visible.cpp new file mode 100644 index 00000000..621d941a --- /dev/null +++ b/compat/check-visible.cpp @@ -0,0 +1,98 @@ +#include "check-visible.hpp" + +#include <QMutex> +#include <QWidget> +#include <QDebug> + +static QMutex lock; +static bool visible = true; + +#if defined _WIN32 + +#include "timer.hpp" +#include "macros.h" + +static Timer timer; + +constexpr int visible_timeout = 1000; +constexpr int invisible_timeout = 250; + +#include <windows.h> + +void set_is_visible(const QWidget& w, bool force) +{ + QMutexLocker l(&lock); + + if (w.isHidden() || w.windowState() & Qt::WindowMinimized) + { + visible = false; + return; + } + + { + int ndisplays = GetSystemMetrics(SM_CMONITORS); + if (ndisplays > 1) + { + visible = true; + return; + } + } + + HWND hwnd = (HWND)w.winId(); + + if (!force && timer.elapsed_ms() < (visible ? visible_timeout : invisible_timeout)) + return; + + timer.start(); + + if (RECT r; GetWindowRect(hwnd, &r)) + { + const int x = r.left, y = r.top; + const int w = r.right - x, h = r.bottom - y; + + const POINT xs[] { + { x + w - 1, y + 1 }, + { x + 1, y + h - 1 }, + { x + w - 1, y + h - 1 }, + { x + 1, y + 1 }, + { x + w/2, y + h/2 }, + }; + + visible = false; + + for (const POINT& pt : xs) + if (WindowFromPoint(pt) == hwnd) + { + + visible = true; + break; + } + } + else + { + eval_once(qDebug() << "check-visible: GetWindowRect failed"); + visible = true; + } +} + +#else + +void set_is_visible(const QWidget& w, bool) +{ + QMutexLocker l(&lock); + visible = !(w.isHidden() || w.windowState() & Qt::WindowMinimized); +} + +#endif + +bool check_is_visible() +{ + QMutexLocker l(&lock); + return visible; +} + +void force_is_visible(bool value) +{ + QMutexLocker l(&lock); + visible = value; +} diff --git a/compat/check-visible.hpp b/compat/check-visible.hpp new file mode 100644 index 00000000..6669b84d --- /dev/null +++ b/compat/check-visible.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "export.hpp" +#include "macros.h" + +class QWidget; + +never_inline OTR_COMPAT_EXPORT +void set_is_visible(QWidget const& w, bool force = false); + +OTR_COMPAT_EXPORT +bool check_is_visible(); + +OTR_COMPAT_EXPORT +void force_is_visible(bool value); diff --git a/compat/correlation-calibrator.cpp b/compat/correlation-calibrator.cpp new file mode 100644 index 00000000..07ef7d3d --- /dev/null +++ b/compat/correlation-calibrator.cpp @@ -0,0 +1,160 @@ +#include "correlation-calibrator.hpp" +#include "variance.hpp" +#include "compat/math.hpp" +#include "compat/meta.hpp" +#include "compat/macros.h" + +#include <cmath> +#include <iterator> + +#include <QDebug> + +#define DEBUG_PRINT +#ifdef DEBUG_PRINT +# include <cstdio> +#endif + +namespace correlation_calibrator_impl { + +static constexpr unsigned nbuckets[6] = +{ + x_nbuckets, + y_nbuckets, + z_nbuckets, + + yaw_nbuckets, + pitch_nbuckets, + roll_nbuckets, +}; + +static constexpr double spacing[6] = +{ + translation_spacing, + translation_spacing, + translation_spacing, + + yaw_spacing_in_degrees, + pitch_spacing_in_degrees, + roll_spacing_in_degrees, +}; + +static constexpr char const* const names[6] { + "x", "y", "z", + "yaw", "pitch", "roll", +}; + +bool correlation_calibrator::check_buckets(const vec6& data) +{ + bool ret = false; + unsigned pos[6]; + + for (unsigned k = 0; k < 6; k++) + { + const double val = std::clamp(data[k], min[k], max[k]); + pos[k] = unsigned((val-min[k])/spacing[k]); + + if (pos[k] >= nbuckets[k]) + { + eval_once(qDebug() << "idx" << k + << "bucket" << (int)pos[k] + << "outside bounds" << nbuckets[k]); + + return false; + } + + if (!buckets[k][pos[k]]) + { + ret = true; + buckets[k][pos[k]] = true; + } + } + + return ret; +} + +void correlation_calibrator::input(const vec6& data_) +{ + if (!check_buckets(data_)) + return; + + data.push_back(data_); +} + +mat66 correlation_calibrator::get_coefficients() const +{ + if (data.size() < min_samples) + { + qDebug() << "correlation-calibrator: not enough data"; + + mat66 ret; + for (unsigned k = 0; k < 6; k++) + ret(k, k) = 1; + return ret; + } + + variance vs[6]; + vec6 devs, means; + + for (const vec6& x : data) + for (unsigned i = 0; i < 6; i++) + vs[i].input(x(i)); + + for (unsigned i = 0; i < 6; i++) + { + means(i) = vs[i].avg(); + devs(i) = vs[i].stddev(); + + constexpr double EPS = 1e-4; + + if (devs(i) < EPS) + devs(i) = EPS; + } + + mat66 cs; + + for (const vec6& x : data) + for (unsigned k = 0; k < 6; k++) + { + for (unsigned idx = 0; idx < 6; idx++) + { + const double zi = (x(idx) - means(idx)), + zk = (x(k) - means(k)); + + cs(idx, k) += zi * zk / (devs(k)*devs(k)); + } + } + + cs = cs * (1./(data.size() - 1)); + +#if defined DEBUG_PRINT + fprintf(stderr, "v:change-of h:due-to\n"); + fprintf(stderr, "%10s ", ""); + for (char const* k : names) + fprintf(stderr, "%10s", k); + fprintf(stderr, "\n"); + + for (unsigned i = 0; i < 6; i++) + { + fprintf(stderr, "%10s ", names[i]); + for (unsigned k = 0; k < 6; k++) + fprintf(stderr, "%10.3f", cs(i, k)); + fprintf(stderr, "\n"); + } + fflush(stderr); +#endif + + for (unsigned k = 0; k < 6; k++) + cs(k, k) = 1; + + // derivations from + // https://www.thoughtco.com/how-to-calculate-the-correlation-coefficient-3126228 + + return cs; +} + +unsigned correlation_calibrator::sample_count() const +{ + return data.size(); +} + +} // ns correlation_calibrator_impl diff --git a/compat/correlation-calibrator.hpp b/compat/correlation-calibrator.hpp new file mode 100644 index 00000000..44aee537 --- /dev/null +++ b/compat/correlation-calibrator.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "simple-mat.hpp" +#include <array> +#include <vector> +#include <tuple> + +#include "export.hpp" + +namespace correlation_calibrator_impl { + +static constexpr double min[6] = { + -50, + -50, + 250, + + -180, + -180, + -180, +}; + +static constexpr double max[6] = { + 50, + 50, + 250, + + 180, + 180, + 180, +}; + +static constexpr double yaw_spacing_in_degrees = 1.5; +static constexpr double pitch_spacing_in_degrees = 1; +static constexpr double roll_spacing_in_degrees = 1; + +static constexpr unsigned yaw_nbuckets = unsigned(1+ 360./yaw_spacing_in_degrees); +static constexpr unsigned pitch_nbuckets = unsigned(1+ 360./pitch_spacing_in_degrees); +static constexpr unsigned roll_nbuckets = unsigned(1+ 360./roll_spacing_in_degrees); + +static constexpr double translation_spacing = .25; +static constexpr unsigned x_nbuckets = unsigned(1+ (max[0]-min[0])/translation_spacing); +static constexpr unsigned y_nbuckets = unsigned(1+ (max[1]-min[1])/translation_spacing); +static constexpr unsigned z_nbuckets = unsigned(1+ (max[2]-min[2])/translation_spacing); + +using vec6 = Mat<double, 6, 1>; +using mat66 = Mat<double, 6, 6>; + +class OTR_COMPAT_EXPORT correlation_calibrator final +{ + // careful to avoid vector copies + std::array<std::vector<bool>, 6> buckets = + { + std::vector<bool>(x_nbuckets, false), + std::vector<bool>(y_nbuckets, false), + std::vector<bool>(z_nbuckets, false), + std::vector<bool>(yaw_nbuckets, false), + std::vector<bool>(pitch_nbuckets, false), + std::vector<bool>(roll_nbuckets, false), + }; + + std::vector<vec6> data; + + bool check_buckets(const vec6& data); + +public: + correlation_calibrator() = default; + void input(const vec6& data); + mat66 get_coefficients() const; + unsigned sample_count() const; + + static constexpr unsigned min_samples = 25; +}; + +} // ns correlation_calibrator_impl + +using correlation_calibrator = correlation_calibrator_impl::correlation_calibrator; diff --git a/compat/enum-operators.hpp b/compat/enum-operators.hpp new file mode 100644 index 00000000..e4f81a39 --- /dev/null +++ b/compat/enum-operators.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include <type_traits> + +#define OTR_FLAGS_OP2(type, op) \ + constexpr inline type operator op (type a, type b) \ + { \ + using t_ = std::underlying_type_t<type>; \ + return type(t_((a)) op t_((b))); \ + } // end + +#define OTR_FLAGS_DELETE_SHIFT(type, op) \ + template<typename u> \ + type operator op (type, u) = delete // end + +#define OTR_FLAGS_OP1(type, op) \ + constexpr inline type operator op (type x) \ + { \ + using t_ = std::underlying_type_t<type>; \ + return type(op t_((x))); \ + } // end + +#define OTR_FLAGS_ASSIGN_OP(type, op) \ + constexpr inline type& operator op ## = (type& lhs, type rhs) \ + { \ + using t_ = std::underlying_type_t<decltype(rhs)>; \ + lhs = type(t_((lhs)) op t_((rhs))); \ + return lhs; \ + } //end + +#define OTR_FLAGS_DELETE_SHIFT_ASSIGN(type, op) \ + type operator op ## = (type& lhs, type rhs) = delete //end + +#define DEFINE_ENUM_OPERATORS(type) \ + OTR_FLAGS_OP2(type, |) \ + OTR_FLAGS_OP2(type, &) \ + OTR_FLAGS_OP2(type, ^) \ + OTR_FLAGS_OP1(type, ~) \ + OTR_FLAGS_DELETE_SHIFT(type, <<); \ + OTR_FLAGS_DELETE_SHIFT(type, >>); \ + OTR_FLAGS_ASSIGN_OP(type, |) \ + OTR_FLAGS_ASSIGN_OP(type, &) \ + OTR_FLAGS_ASSIGN_OP(type, ^) \ + OTR_FLAGS_DELETE_SHIFT_ASSIGN(type, <<); \ + OTR_FLAGS_DELETE_SHIFT_ASSIGN(type, >>) // end diff --git a/compat/euler.cpp b/compat/euler.cpp index e48d977b..5d6fb25b 100644 --- a/compat/euler.cpp +++ b/compat/euler.cpp @@ -1,35 +1,34 @@ #include "euler.hpp" +#include "math-imports.hpp" #include <cmath> namespace euler { -euler_t OTR_COMPAT_EXPORT rmat_to_euler(const rmat& R) +Pose_ rmat_to_euler(const rmat& R) { - using std::atan2; - using std::sqrt; - const double cy = sqrt(R(2,2)*R(2, 2) + R(2, 1)*R(2, 1)); const bool large_enough = cy > 1e-10; if (large_enough) - return euler_t(atan2(-R(1, 0), R(0, 0)), - atan2(R(2, 0), cy), - atan2(-R(2, 1), R(2, 2))); + return { + atan2(-R(1, 0), R(0, 0)), + atan2(R(2, 0), cy), + atan2(-R(2, 1), R(2, 2)) + }; else - return euler_t(atan2(R(0, 1), R(1, 1)), - atan2(R(2, 0), cy), - 0); + return { + atan2(R(0, 1), R(1, 1)), + atan2(R(2, 0), cy), + 0 + }; } // tait-bryan angles, not euler -rmat OTR_COMPAT_EXPORT euler_to_rmat(const euler_t& input) +rmat euler_to_rmat(const Pose_& input) { const double H = -input(0); const double P = -input(1); const double B = -input(2); - using std::cos; - using std::sin; - const auto c1 = cos(H); const auto s1 = sin(H); const auto c2 = cos(P); @@ -37,99 +36,20 @@ rmat OTR_COMPAT_EXPORT euler_to_rmat(const euler_t& input) const auto c3 = cos(B); const auto s3 = sin(B); - return rmat( - // z - c1 * c2, - c1 * s2 * s3 - c3 * s1, - s1 * s3 + c1 * c3 * s2, - // y - c2 * s1, - c1 * c3 + s1 * s2 * s3, - c3 * s1 * s2 - c1 * s3, - // x - -s2, - c2 * s3, - c2 * c3 - ); -} - -// https://en.wikipedia.org/wiki/Davenport_chained_rotations#Tait.E2.80.93Bryan_chained_rotations -void OTR_COMPAT_EXPORT tait_bryan_to_matrices(const euler_t& input, - rmat& r_roll, - rmat& r_pitch, - rmat& r_yaw) -{ - using std::cos; - using std::sin; - - { - const double phi = -input(2); - const double sin_phi = sin(phi); - const double cos_phi = cos(phi); - - r_roll = rmat(1, 0, 0, - 0, cos_phi, -sin_phi, - 0, sin_phi, cos_phi); - } - - { - const double theta = input(1); - const double sin_theta = sin(theta); - const double cos_theta = cos(theta); - - r_pitch = rmat(cos_theta, 0, -sin_theta, - 0, 1, 0, - sin_theta, 0, cos_theta); - } - - { - const double psi = -input(0); - const double sin_psi = sin(psi); - const double cos_psi = cos(psi); - - r_yaw = rmat(cos_psi, -sin_psi, 0, - sin_psi, cos_psi, 0, - 0, 0, 1); - } -} - -// from http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ -Quat matrix_to_quat(const rmat& M) -{ - Quat q(1, 0, 0, 0); - - using std::sqrt; - - double trace = M(0, 0) + M(1, 1) + M(2, 2); // I removed + 1.0; see discussion with Ethan - if( trace > 0 ) {// I changed M_EPSILON to 0 - double s = .5 / std::sqrt(trace + 1); - q.w() = .25 / s; - q.x() = ( M(2, 1) - M(1, 2) ) * s; - q.y() = ( M(0, 2) - M(2, 0) ) * s; - q.z() = ( M(1, 0) - M(0, 1) ) * s; - } else { - if ( M(0, 0) > M(1, 1) && M(0, 0) > M(2, 2) ) { - double s = 2.0 * sqrt( 1.0 + M(0, 0) - M(1, 1) - M(2, 2)); - q.w() = (M(2, 1) - M(1, 2) ) / s; - q.x() = .25 * s; - q.y() = (M(0, 1) + M(1, 0) ) / s; - q.z() = (M(0, 2) + M(2, 0) ) / s; - } else if (M(1, 1) > M(2, 2)) { - double s = 2.0 * sqrt( 1.0 + M(1, 1) - M(0, 0) - M(2, 2)); - q.w() = (M(0, 2) - M(2, 0) ) / s; - q.x() = (M(0, 1) + M(1, 0) ) / s; - q.y() = .25 * s; - q.z() = (M(1, 2) + M(2, 1) ) / s; - } else { - double s = 2.0 * sqrt( 1.0 + M(2, 2) - M(0, 0) - M(1, 1) ); - q.w() = (M(1, 0) - M(0, 1) ) / s; - q.x() = (M(0, 2) + M(2, 0) ) / s; - q.y() = (M(1, 2) + M(2, 1) ) / s; - q.z() = .25 * s; - } - } - - return q; + return { + // z + c1*c2, + c1*s2*s3 - c3*s1, + s1*s3 + c1*c3*s2, + // y + c2*s1, + c1*c3 + s1*s2*s3, + c3*s1*s2 - c1*s3, + // x + -s2, + c2*s3, + c2*c3 + }; } } // end ns euler diff --git a/compat/euler.hpp b/compat/euler.hpp index 4eb6e00c..b2f10ac0 100644 --- a/compat/euler.hpp +++ b/compat/euler.hpp @@ -1,5 +1,12 @@ #pragma once +/* Copyright (c) 2016-2018 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + #include "export.hpp" #include "simple-mat.hpp" @@ -7,22 +14,12 @@ namespace euler { template<int h_, int w_> using dmat = Mat<double, h_, w_>; -using dvec2 = Mat<double, 2, 1>; using dvec3 = Mat<double, 3, 1>; using rmat = dmat<3, 3>; -using euler_t = dmat<3, 1>; - -rmat OTR_COMPAT_EXPORT euler_to_rmat(const euler_t& input); - -euler_t OTR_COMPAT_EXPORT rmat_to_euler(const rmat& R); - -void OTR_COMPAT_EXPORT tait_bryan_to_matrices(const euler_t& input, - rmat& r_roll, - rmat& r_pitch, - rmat& r_yaw); +using Pose_ = dmat<3, 1>; -Quat OTR_COMPAT_EXPORT matrix_to_quat(const rmat& M); -//XXX TODO rmat OTR_COMPAT_EXPORT quat_to_matrix(const Quat<double>& Q); +rmat OTR_COMPAT_EXPORT euler_to_rmat(const Pose_& input); +Pose_ OTR_COMPAT_EXPORT rmat_to_euler(const rmat& R); } // end ns euler diff --git a/compat/hamilton-tools.cpp b/compat/hamilton-tools.cpp new file mode 100644 index 00000000..eb0ef984 --- /dev/null +++ b/compat/hamilton-tools.cpp @@ -0,0 +1,109 @@ +#include "hamilton-tools.h" +#include <cmath> + +tQuat QuatFromAngleAxe(const double angle, const tVector& axe) +{ + double a = TO_RAD * 0.5 * angle; + double d = sin(a) / VectorLength(axe); + return ( tQuat ( + axe.v[0] * d, + axe.v[1] * d, + axe.v[2] * d, + cos(a) + ) + ); +} + +tQuat QuatMultiply(const tQuat& qL, const tQuat& qR) +{ + tQuat Q; + Q.x = qL.w*qR.x + qL.x*qR.w + qL.y*qR.z - qL.z*qR.y; + Q.y = qL.w*qR.y + qL.y*qR.w + qL.z*qR.x - qL.x*qR.z; + Q.z = qL.w*qR.z + qL.z*qR.w + qL.x*qR.y - qL.y*qR.x; + Q.w = qL.w*qR.w - qL.x*qR.x - qL.y*qR.y - qL.z*qR.z; + return(Q); +} + +tQuat QuatFromYPR(const double YPR[]) +{ + tQuat Q, Qp, Qy; + Q = QuatFromAngleAxe( -YPR[2], {0, 0, 1} ); //Roll, Z axe + Qp = QuatFromAngleAxe( -YPR[1], {1, 0, 0} ); //Pitch, X axe + Qy = QuatFromAngleAxe( -YPR[0], {0, 1, 0} ); //Yaw, Y axe + + Q = QuatMultiply(Qp, Q); + return(QuatMultiply(Qy, Q)); +} + +void Normalize(tQuat& Q) +{ + double m = sqrt(Q.x*Q.x + Q.y*Q.y + Q.z*Q.z + Q.w*Q.w); + if (m > EPSILON) + { + m = 1 / m; + Q.x = Q.x * m; + Q.y = Q.y * m; + Q.z = Q.z * m; + Q.w = Q.w * m; + } + else Q = tQuat(0, 0, 0, 1); +} + +tQuat Slerp(const tQuat& S, const tQuat& D, const double alpha) +{ + // calc cosine of half angle + double cosin = S.x*D.x + S.y*D.y + S.z*D.z + S.w*D.w; + + // select nearest rotation direction + tQuat Q; + if (cosin < 0) + { + cosin = - cosin; + Q.x = - D.x; + Q.y = - D.y; + Q.z = - D.z; + Q.w = - D.w; + } + else Q = D; + + // calculate coefficients + double scale0, scale1; + if ((1.0 - cosin) > EPSILON) + { + double omega = acos(cosin); + double sinus = 1 / sin(omega); + scale0 = sin((1.0 - alpha) * omega) * sinus; + scale1 = sin(alpha * omega)* sinus; + } + else + { + scale0 = 1.0 - alpha; + scale1 = alpha; + } + + Q.x = scale0 * S.x + scale1 * Q.x; + Q.y = scale0 * S.y + scale1 * Q.y; + Q.z = scale0 * S.z + scale1 * Q.z; + Q.w = scale0 * S.w + scale1 * Q.w; + + Normalize(Q); + + return( Q ); +} + +void QuatToYPR(const tQuat& Q, double YPR[]) +{ + const double xx = Q.x * Q.x; + const double xy = Q.x * Q.y; + const double xz = Q.x * Q.z; + const double xw = Q.x * Q.w; + const double yy = Q.y * Q.y; + const double yz = Q.y * Q.z; + const double yw = Q.y * Q.w; + const double zz = Q.z * Q.z; + const double zw = Q.z * Q.w; + + YPR[0] = TO_DEG * ( -atan2( 2 * ( xz + yw ), 1 - 2 * ( xx + yy ) )); + YPR[1] = TO_DEG * ( asin ( 2 * ( yz - xw ) )); + YPR[2] = TO_DEG * ( -atan2( 2 * ( xy + zw ), 1 - 2 * ( xx + zz ) )); +} diff --git a/compat/hamilton-tools.h b/compat/hamilton-tools.h new file mode 100644 index 00000000..885e9f79 --- /dev/null +++ b/compat/hamilton-tools.h @@ -0,0 +1,76 @@ +#pragma once + +#include <algorithm> +#include "export.hpp" +#include "compat/math.hpp" + +constexpr double TO_RAD = (M_PI / 180); +constexpr double TO_DEG = (180 / M_PI); +constexpr double EPSILON = 1e-30; + +struct tVector +{ + double v[3]; + tVector(double X = 0, double Y = 0, double Z = 0) {v[0]=X; v[1]=Y; v[2]=Z;} + tVector(const double V[]) {v[0]=V[0]; v[1]=V[1]; v[2]=V[2];} + + void operator=(const tVector& other) + { + std::copy(other.v, other.v + 3, v); + } + inline const tVector operator+(const tVector& other) const + { + return tVector(v[0] + other.v[0], v[1] + other.v[1], v[2] + other.v[2]); + } + void operator+=(const tVector& other) + { + v[0] += other.v[0]; + v[1] += other.v[1]; + v[2] += other.v[2]; + } + const tVector operator-(const tVector& other) const + { + return tVector(v[0] - other.v[0], v[1] - other.v[1], v[2] - other.v[2]); + } + void operator-=(const tVector& other) + { + v[0] -= other.v[0]; + v[1] -= other.v[1]; + v[2] -= other.v[2]; + } + const tVector operator*(double scalar) const + { + return tVector(v[0] * scalar, v[1] * scalar, v[2] * scalar); + } + void operator*=(double scalar) + { + v[0] *= scalar; + v[1] *= scalar; + v[2] *= scalar; + } + const tVector operator/(double scalar) const + { + return *this * (1.0 / scalar); + } + void operator/= (double scalar) + { + *this *= 1.0 / scalar; + } +}; + +struct tQuat +{ + double x, y, z, w; + tQuat(double X = 0, double Y = 0, double Z = 0, double W = 1) + {x = X; y = Y; z = Z; w = W;} +}; + +inline double VectorLength(const tVector& v) { return sqrt(v.v[0] * v.v[0] + v.v[1] * v.v[1] + v.v[2] * v.v[2]); } +inline double VectorDistance(const tVector& v1, const tVector& v2) { return VectorLength(v2 - v1); } +inline tVector Lerp(const tVector& s, const tVector& d, const double alpha) { return s + (d - s) * alpha; } +tQuat OTR_COMPAT_EXPORT QuatFromYPR(const double YPR[]); +tQuat OTR_COMPAT_EXPORT QuatMultiply(const tQuat& qL, const tQuat& qR); +inline tQuat QuatDivide(const tQuat& qL, const tQuat& qR) { return QuatMultiply(qL, tQuat(-qR.x, -qR.y, -qR.z, qR.w)); } +inline double AngleBetween(const tQuat& S, const tQuat& D) { return TO_DEG * 2 * acos(fabs(S.x * D.x + S.y * D.y + S.z * D.z + S.w * D.w)); } +tQuat OTR_COMPAT_EXPORT Slerp(const tQuat& S, const tQuat& D, const double alpha); +void OTR_COMPAT_EXPORT QuatToYPR(const tQuat& Q, double YPR[]); diff --git a/compat/lang/de_DE.ts b/compat/lang/de_DE.ts new file mode 100644 index 00000000..1552582e --- /dev/null +++ b/compat/lang/de_DE.ts @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="de_DE"> +</TS> diff --git a/compat/lang/zh_CN.ts b/compat/lang/zh_CN.ts new file mode 100644 index 00000000..e5ca8aa9 --- /dev/null +++ b/compat/lang/zh_CN.ts @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +</TS> diff --git a/compat/library-path.hpp b/compat/library-path.hpp new file mode 100644 index 00000000..49e2c414 --- /dev/null +++ b/compat/library-path.hpp @@ -0,0 +1,4 @@ +#pragma once + +// from build directory +#include "opentrack-library-path.hxx" diff --git a/compat/linkage-macros.hpp b/compat/linkage-macros.hpp index b9bc9801..f4d0104b 100644 --- a/compat/linkage-macros.hpp +++ b/compat/linkage-macros.hpp @@ -1,15 +1,26 @@ #pragma once -#if defined _MSC_VER || defined _WIN32 -# define OTR_GENERIC_EXPORT __declspec(dllexport) -# define OTR_GENERIC_IMPORT __declspec(dllimport) -#else -# define OTR_GENERIC_EXPORT __attribute__ ((visibility ("default"))) -# define OTR_GENERIC_IMPORT +#ifndef OTR_GENERIC_EXPORT +# if defined _MSC_VER +# define OTR_GENERIC_EXPORT __declspec(dllexport) +# define OTR_GENERIC_IMPORT __declspec(dllimport) +# elif defined _WIN32 && !defined __WINE__ +# define OTR_GENERIC_EXPORT __attribute__((dllexport, visibility ("default"))) +# define OTR_GENERIC_IMPORT __attribute__((dllimport)) +# else +# define OTR_GENERIC_EXPORT __attribute__((visibility ("default"))) +# define OTR_GENERIC_IMPORT +# endif +#endif + +#if defined __APPLE__ || defined __MINGW32__ +# define OTR_NO_TMPL_INST // link failure on both targets #endif -#if defined _MSC_VER -# define OTR_GENERIC_TEMPLATE +#if defined OTR_NO_TMPL_INST +# define OTR_TEMPLATE_IMPORT(x) +# define OTR_TEMPLATE_EXPORT(x) #else -# define OTR_GENERIC_TEMPLATE __attribute__ ((visibility ("default"))) +# define OTR_TEMPLATE_IMPORT(x) extern template class OTR_GENERIC_IMPORT x; +# define OTR_TEMPLATE_EXPORT(x) template class OTR_GENERIC_EXPORT x; #endif diff --git a/compat/macros.h b/compat/macros.h new file mode 100644 index 00000000..7de7fb86 --- /dev/null +++ b/compat/macros.h @@ -0,0 +1,57 @@ +#pragma once + +#if defined _MSC_VER +# define never_inline __declspec(noinline) +#else +# define never_inline __attribute__((noinline)) +#endif + +#if defined _MSC_VER +# define force_inline __forceinline +#else +# define force_inline __attribute__((always_inline)) inline +#endif + +#if !defined likely +# if defined __GNUC__ +# define likely(x) __builtin_expect(!!(x),1) +# define unlikely(x) __builtin_expect(!!(x),0) +# else +# define likely(x) (x) +# define unlikely(x) (x) +# endif +#endif + +#if defined _MSC_VER +# define function_name __FUNCSIG__ +#else +# define function_name __PRETTY_FUNCTION__ +#endif + +#ifdef _MSC_VER +# define unreachable() do { __assume(0); *(volatile int*)nullptr = 0; } while (0) /* NOLINT(clang-analyzer-core.NullDereference) */ +#else +# define unreachable() do { __builtin_unreachable(); *(volatile int*)nullptr = 0; } while (0) /* NOLINT(clang-analyzer-core.NullDereference) */ +#endif + +#ifdef __cplusplus +# define progn(...) ([&]() -> decltype(auto) { __VA_ARGS__ }()) +# define eval_once2(expr, ctr) eval_once3(expr, ctr) +# define eval_once3(expr, ctr) ([&] { [[maybe_unused]] static const char init_ ## ctr = ((void)(expr), 0); }()) +# ifdef QT_NO_DEBUG_OUTPUT +# define eval_once(expr) void() +# else +# define eval_once(expr) eval_once2(expr, __COUNTER__) +# endif + +#define OTR_DISABLE_MOVE_COPY(type) \ + type(const type&) = delete; \ + type(type&&) = delete; \ + type& operator=(const type&) = delete; \ + type& operator=(type&&) = delete +#endif + +#ifdef _MSC_VER +# define strncasecmp _strnicmp +# define strcasecmp _stricmp +#endif diff --git a/compat/math-imports.hpp b/compat/math-imports.hpp new file mode 100644 index 00000000..67b7e6ed --- /dev/null +++ b/compat/math-imports.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include <cmath> + +using std::copysign; + +using std::sqrt; +using std::pow; + +using std::fabs; +using std::fmin; +using std::fmax; + +using std::atan; +using std::atan2; +using std::asin; +using std::acos; + +using std::sin; +using std::cos; +using std::tan; + +using std::round; +using std::fmod; + +using std::log; +using std::log2; +using std::log10; +using std::exp; + +#include <cinttypes> + +using std::uintptr_t; +using std::intptr_t; + +using std::int64_t; +using std::int32_t; +using std::int8_t; + +using std::uint64_t; +using std::uint32_t; +using std::uint8_t; + +#include <algorithm> + +using std::min; +using std::max; + +#include "math.hpp" diff --git a/compat/math.hpp b/compat/math.hpp new file mode 100644 index 00000000..c5981cc2 --- /dev/null +++ b/compat/math.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include <cmath> +#include <type_traits> + +template<typename t> +inline auto iround(t val) -> std::enable_if_t<std::is_floating_point_v<std::decay_t<t>>, int> +{ + return (int)std::round(val); +} + +template <typename t> +constexpr int signum(const t& x) +{ + return x < t{0} ? -1 : 1; +} diff --git a/compat/meta.hpp b/compat/meta.hpp new file mode 100644 index 00000000..49686996 --- /dev/null +++ b/compat/meta.hpp @@ -0,0 +1,123 @@ +#pragma once + +/* Copyright (c) 2017 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ + +namespace meta::detail { + + template<typename... xs> + struct tuple; + + template<typename... xs> + struct reverse_; + + template<typename x0, typename... xs, template<typename...> class x, typename... ys> + struct reverse_<x<x0, xs...>, x<ys...>> + { + using type = typename reverse_<x<xs...>, x<x0, ys...>>::type; + }; + + template<template<typename...> class x, typename... ys> + struct reverse_<x<>, x<ys...>> + { + using type = x<ys...>; + }; + + template<template<typename...> class, typename, typename...> struct lift_; + + template<template<typename...> class to, template<typename...> class from, typename... xs> + struct lift_<to, from<xs...>> + { + using type = to<xs...>; + }; + + template<typename> struct append_helper; + + template<typename, typename> struct cons_; + + template<typename x, template<typename...> class t, typename... xs> + struct cons_<x, t<xs...>> + { + using type = t<x, xs...>; + }; + + template<typename> struct append2; + + template<template<typename...> class t, typename... xs> + struct append2<t<xs...>> + { + template<typename> struct append1; + + template<template<typename...> class u, typename... ys> + struct append1<u<ys...>> + { + using type = t<xs..., ys...>; + }; + }; + + template<typename, typename...> struct list__; + + template<typename rest, typename... first> + struct list__ + { + template<typename> struct list1; + + template<template<typename...> class t, typename... xs> + struct list1<t<xs...>> + { + using type = t<first..., xs...>; + }; + + using type = typename list1<rest>::type; + }; + + template<typename xs, typename ys> + struct append_ + { + using t1 = append2<xs>; + using type = typename t1::template append1<ys>::type; + }; + +} // ns meta::detail + +namespace meta { + template<typename... xs> + using tuple_ = detail::tuple<xs...>; + + template<typename... xs> + using reverse = typename detail::reverse_<detail::tuple<xs...>, detail::tuple<>>::type; + + // the to/from order is awkward but mimics function composition + template<template<typename...> class to, typename from> + using lift = typename detail::lift_<to, from>::type; + + template<template<typename...> class to, typename from> + constexpr inline auto lift_v = detail::lift_<to, from>::type::value; + + template<typename x, typename... xs> + using first = x; + + template<typename x, typename... xs> + using rest = detail::tuple<xs...>; + + template<typename... xs> + using butlast = reverse<rest<reverse<xs...>>>; + + template<typename... xs> + using last = lift<first, reverse<xs...>>; + + template<typename x, typename rest> + using cons = typename detail::cons_<x, rest>::type; + + template<typename xs, typename ys> + using append = typename detail::append_<xs, ys>; + + template<typename rest, typename... xs> + using list_ = typename detail::list__<rest, xs...>; + +} // ns meta + diff --git a/compat/mutex.cpp b/compat/mutex.cpp new file mode 100644 index 00000000..664677ea --- /dev/null +++ b/compat/mutex.cpp @@ -0,0 +1,33 @@ +#include "mutex.hpp" +#include <cstdlib> + +mutex& mutex::operator=(const mutex& rhs) +{ + if (rhs->isRecursive() != inner.isRecursive()) + std::abort(); + + return *this; +} + +mutex::mutex(const mutex& datum) : mutex{datum.inner.isRecursive() ? Recursive : NonRecursive} +{ +} + +mutex::mutex(RecursionMode m) : inner{m} +{ +} + +QMutex* mutex::operator&() const noexcept +{ + return &inner; +} + +mutex::operator QMutex*() const noexcept +{ + return &inner; +} + +QMutex* mutex::operator->() const noexcept +{ + return &inner; +} diff --git a/compat/mutex.hpp b/compat/mutex.hpp new file mode 100644 index 00000000..54758a08 --- /dev/null +++ b/compat/mutex.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include <QMutex> + +#include "export.hpp" + +class OTR_COMPAT_EXPORT mutex +{ + mutable QMutex inner; + +public: + using RecursionMode = QMutex::RecursionMode; + static constexpr RecursionMode Recursive = RecursionMode::Recursive; + static constexpr RecursionMode NonRecursive = RecursionMode::NonRecursive; + + mutex& operator=(const mutex& datum); + mutex(const mutex& datum); + explicit mutex(RecursionMode m); + mutex() : mutex{NonRecursive} {} + + QMutex* operator&() const noexcept; + explicit operator QMutex*() const noexcept; + QMutex* operator->() const noexcept; +}; diff --git a/compat/nan.cpp b/compat/nan.cpp deleted file mode 100644 index 9c7bf147..00000000 --- a/compat/nan.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include "export.hpp" - -#if defined(_MSC_VER) -# include <cmath> -# define my_isnan std::isnan -# define my_isinf std::isinf -extern "C" OTR_COMPAT_EXPORT __declspec(noinline) bool nanp(double x) -#elif defined __MINGW32__ - -int __cdecl my_isnan(double)__asm__("__isnan"); -int __cdecl my_fpclassify(double)__asm__("___fpclassify"); - -#define FP_NAN 0x0100 -#define FP_NORMAL 0x0400 -#define FP_INFINITE (FP_NAN | FP_NORMAL) -#define FP_ZERO 0x4000 -#define FP_SUBNORMAL (FP_NORMAL | FP_ZERO) - -#define my_isinf(x) (my_fpclassify(x) == FP_INFINITE) - -extern "C" OTR_COMPAT_EXPORT bool __attribute__ ((noinline)) nanp(double x) -#else -int my_isnan(double)__asm__("isnan"); -int my_isinf(double)__asm__("isinf"); - -extern "C" OTR_COMPAT_EXPORT bool __attribute__ ((noinline)) nanp(double x) -#endif -{ - return my_isnan(x) || my_isinf(x); -} diff --git a/compat/nan.hpp b/compat/nan.hpp deleted file mode 100644 index 0a2c900c..00000000 --- a/compat/nan.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "export.hpp" - -#if defined(__GNUC__) -extern "C" OTR_COMPAT_EXPORT bool __attribute__ ((noinline)) nanp(double value); -#elif defined(_WIN32) -extern "C" OTR_COMPAT_EXPORT __declspec(noinline) bool nanp(double value); -#else -extern "C" OTR_COMPAT_EXPORT bool nanp(double value); -#endif diff --git a/compat/ndebug-guard.hpp b/compat/ndebug-guard.hpp deleted file mode 100644 index e38fd0fe..00000000 --- a/compat/ndebug-guard.hpp +++ /dev/null @@ -1,5 +0,0 @@ -#ifdef NDEBUG -# error "NDEBUG defined, don't define it" -#endif - -#include <cassert> diff --git a/compat/process-list.hpp b/compat/process-list.hpp index 10613791..39e12603 100644 --- a/compat/process-list.hpp +++ b/compat/process-list.hpp @@ -52,7 +52,7 @@ static QStringList get_all_executable_names() template<typename = void> static QStringList get_all_executable_names() { - QStringList ret; + QStringList ret; ret.reserve(2048); std::vector<int> vec; while (true) @@ -103,7 +103,7 @@ static QStringList get_all_executable_names() if (idx != -1) { QString tmp = cmdline[0].mid(idx+1); - if (cmdline.size() > 1 && (tmp == "wine.bin" || tmp == "wine")) + if (cmdline.size() > 1 && (tmp == QStringLiteral("wine.bin") || tmp == QStringLiteral("wine"))) { idx = cmdline[1].lastIndexOf('/'); if (idx == -1) @@ -129,14 +129,68 @@ static QStringList get_all_executable_names() } } -#elif defined __linux +#elif defined __linux__ + +#include <cerrno> + +#ifdef OTR_HAS_LIBPROC2 +#include <libproc2/pids.h> +template<typename = void> +QStringList get_all_executable_names() +{ + QStringList ret; ret.reserve(2048); + enum pids_item items[] = { PIDS_ID_PID, PIDS_CMD, PIDS_CMDLINE_V }; + + enum rel_items { rel_pid, rel_cmd, rel_cmdline }; + struct pids_info *info = NULL; + struct pids_stack *stack; + QString tmp; tmp.reserve(255); + + procps_pids_new(&info, items, 3); + // procps-ng version 4.0.5 removed an unused argument in PIDS_VAL() macro. + // cf. https://gitlab.com/procps-ng/procps/-/commit/967fdcfb06e20aad0f3 + + // Although the emitted machine code is identical, backward API + // compatibility was silently broken in the patch with no upgrade path + // (e.g. deprecating PIDS_VAL() while introducing PIDS_VAL2()). + + // Unfortunately, procps-ng doesn't include a #define for identifying its + // version. For these reasons the code below depends on undocumented ABI + // compatibility between procps-ng versions.. -sh 20241226 + +#define OPENTRACK_PIDS_VAL(i, type, stack) stack->head[i].result.type + + while ((stack = procps_pids_get(info, PIDS_FETCH_TASKS_ONLY))) + { + char **p_cmdline = OPENTRACK_PIDS_VAL(rel_cmdline, strv, stack); + + // note, wine sets argv[0] so no parsing like in OSX case + if (p_cmdline && p_cmdline[0] && p_cmdline[0][0] && + !(p_cmdline[0][0] == '-' && !p_cmdline[0][1])) + { + tmp = QString{p_cmdline[0]}; + const int idx = std::max(tmp.lastIndexOf('\\'), tmp.lastIndexOf('/')); + if (idx != -1) + tmp = tmp.mid(idx+1); + //qDebug() << "procps" << tmp; + ret.append(tmp); + } + } + //qDebug() << "-- procps end"; + + procps_pids_unref(&info); + + return ret; +} +#else #include <proc/readproc.h> #include <cerrno> + template<typename = void> -static QStringList get_all_executable_names() +QStringList get_all_executable_names() { - QStringList ret; + QStringList ret; ret.reserve(2048); proc_t** procs = readproctab(PROC_FILLCOM); if (procs == nullptr) { @@ -159,6 +213,7 @@ static QStringList get_all_executable_names() free(procs); return ret; } +#endif #else template<typename = void> diff --git a/compat/qcopyable-mutex.hpp b/compat/qcopyable-mutex.hpp deleted file mode 100644 index 57b0030d..00000000 --- a/compat/qcopyable-mutex.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include <QMutex> - -class MyMutex { -private: - QMutex inner; - -public: - QMutex* operator->() { return &inner; } - QMutex* operator->() const { return &const_cast<MyMutex*>(this)->inner; } - - MyMutex operator=(const MyMutex& datum) - { - auto mode = - datum->isRecursive() - ? QMutex::Recursive - : QMutex::NonRecursive; - - return MyMutex(mode); - } - - MyMutex(const MyMutex& datum) - { - *this = datum; - } - - MyMutex(QMutex::RecursionMode mode = QMutex::NonRecursive) : - inner(mode) - { - } - - QMutex* operator&() const - { - return const_cast<QMutex*>(&inner); - } -}; diff --git a/compat/qhash.hpp b/compat/qhash.hpp new file mode 100644 index 00000000..5286e97e --- /dev/null +++ b/compat/qhash.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include <functional> + +#include <QtGlobal> +#include <QString> +#include <QHashFunctions> + +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + +#include <cstdlib> + +namespace std { +template<> struct hash<QString> +{ + using argument_type = QString; + using result_type = std::size_t; + + std::size_t operator()(const QString& value) const noexcept + { + return (std::size_t) qHash(value); + } +}; +} + +#endif diff --git a/compat/qt-dpi.hpp b/compat/qt-dpi.hpp new file mode 100644 index 00000000..fc17a062 --- /dev/null +++ b/compat/qt-dpi.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include <algorithm> +#include <QWidget> + +static inline double screen_dpi(const QPaintDevice* widget) +{ + const auto& x = *widget; +#ifdef _WIN32 + return std::max(x.devicePixelRatioF(), 1.); +#else + return std::max(std::max(x.logicalDpiX()/(double)x.physicalDpiX(), x.devicePixelRatioF()), 1.); +#endif +} + +template<typename self> +struct screen_dpi_mixin +{ +protected: + double screen_dpi() const + { + return ::screen_dpi(static_cast<const self*>(this)); + } +}; diff --git a/compat/qt-signal.cpp b/compat/qt-signal.cpp new file mode 100644 index 00000000..8c23866b --- /dev/null +++ b/compat/qt-signal.cpp @@ -0,0 +1,7 @@ +#define OTR_GENERATE_SIGNAL3(type) void sig_##type::operator()(const type& x) const { notify(x); } +#include "qt-signal.hpp" +namespace _qt_sig_impl { + +sig_void::sig_void(QObject* parent) : QObject(parent) {} + +} diff --git a/compat/qt-signal.hpp b/compat/qt-signal.hpp new file mode 100644 index 00000000..f74de642 --- /dev/null +++ b/compat/qt-signal.hpp @@ -0,0 +1,72 @@ +#pragma once + +// this is to avoid dealing with QMetaObject for the time being -sh 20190203 + +#include "export.hpp" +namespace options { class slider_value; } +#include <QObject> +#include <QList> + +namespace _qt_sig_impl { + +template<typename t> struct sig; + +class OTR_COMPAT_EXPORT sig_void final : public QObject +{ + Q_OBJECT + +public: + template<typename t, typename F> + sig_void(t* datum, F&& f, Qt::ConnectionType conntype = Qt::AutoConnection) : QObject(datum) + { + connect(this, &sig_void::notify, datum, f, conntype); + } + explicit sig_void(QObject* parent = nullptr); + void operator()() const { notify(); } + +signals: + void notify() const; +}; + +template<> struct sig<void> { using t = sig_void; }; + +#ifndef OTR_GENERATE_SIGNAL3 +# define OTR_GENERATE_SIGNAL3(x) +#endif +#define OTR_GENERATE_SIGNAL2(type) \ + class OTR_COMPAT_EXPORT sig_##type final : public QObject \ + { \ + Q_OBJECT \ + public: \ + explicit sig_##type(QObject* parent = nullptr) : QObject(parent) {} \ + void operator()(const type& x) const; \ + Q_SIGNALS: \ + void notify(const type& x) const; \ + }; \ + OTR_GENERATE_SIGNAL3(type) + +# define OTR_GENERATE_SIGNAL(type) \ + OTR_GENERATE_SIGNAL2(type); \ + using qlist##type = QList<type>; \ + OTR_GENERATE_SIGNAL2(qlist##type); \ + template<> struct sig<type> { using t = sig_##type; }; \ + template<> struct sig<qlist##type> { using t = qlist##type; } + +using slider_value = options::slider_value; + +OTR_GENERATE_SIGNAL(int); +OTR_GENERATE_SIGNAL(double); +OTR_GENERATE_SIGNAL(float); +OTR_GENERATE_SIGNAL(bool); +OTR_GENERATE_SIGNAL(QString); +OTR_GENERATE_SIGNAL(slider_value); +OTR_GENERATE_SIGNAL(QPointF); +OTR_GENERATE_SIGNAL(QVariant); + +} // namespace _qt_sig_impl + +#undef OTR_GENERATE_SIGNAL2 +#undef OTR_GENERATE_SIGNAL + +template<typename t> using qt_signal = typename _qt_sig_impl::sig<t>::t; + diff --git a/compat/run-in-thread.hpp b/compat/run-in-thread.hpp index ba99d0b9..c552c600 100644 --- a/compat/run-in-thread.hpp +++ b/compat/run-in-thread.hpp @@ -1,8 +1,12 @@ #pragma once -#include "ndebug-guard.hpp" +/* Copyright (c) 2016 Stanislaw Halik + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + */ -#include <cassert> #include <thread> #include <condition_variable> #include <utility> @@ -10,98 +14,56 @@ #include <QObject> #include <QThread> -namespace qt_impl_detail { - -template<typename t> -struct run_in_thread_traits +namespace impl_run_in_thread { +struct semaphore final { - using type = t; - using ret_type = t; - static inline void assign(t& lvalue, const t& rvalue) { lvalue = rvalue; } - static inline t pass(const t& val) { return val; } - template<typename F> static inline t call(F&& fun) { return std::move(fun()); } -}; + using lock_guard = std::unique_lock<std::mutex>; + std::mutex mtx; + std::condition_variable cvar; + bool flag = false; -template<typename u> -struct run_in_thread_traits<u&&> -{ - using t = typename std::remove_reference<u>::type; - using type = t; - using ret_type = u; - static inline void assign(t& lvalue, t&& rvalue) { lvalue = rvalue; } - static inline t&& pass(t&& val) { return val; } - template<typename F> static inline t&& call(F&& fun) { return std::move(fun()); } -}; + semaphore() = default; -template<> -struct run_in_thread_traits<void> -{ - using type = unsigned char; - using ret_type = void; - static inline void assign(unsigned char&, unsigned char&&) {} - static inline void pass(type&&) {} - template<typename F> static type call(F&& fun) { fun(); return type(0); } -}; + void wait() + { + lock_guard guard(mtx); + while (!flag) + cvar.wait(guard); + } + void notify() + { + lock_guard guard(mtx); + flag = true; + cvar.notify_one(); + } +}; } template<typename F> -auto run_in_thread_sync(QObject* obj, F&& fun) - -> typename qt_impl_detail::run_in_thread_traits<decltype(std::forward<F>(fun)())>::ret_type +void run_in_thread_sync(QObject* obj, F&& fun) { - using lock_guard = std::unique_lock<std::mutex>; - - using traits = qt_impl_detail::run_in_thread_traits<decltype(std::forward<F>(fun)())>; - - typename traits::type ret; - - struct semaphore final - { - std::mutex mtx; - std::condition_variable cvar; - bool flag; - - semaphore() : flag(false) {} - - void wait() - { - lock_guard guard(mtx); - while (!flag) - cvar.wait(guard); - } + if (obj->thread() == QThread::currentThread()) + return (void)fun(); - void notify() - { - lock_guard guard(mtx); - flag = true; - cvar.notify_one(); - } - }; - - semaphore sem; + impl_run_in_thread::semaphore sem; { QObject src; - QObject::connect(&src, - &QObject::destroyed, - obj, - [&]() { - traits::assign(ret, traits::call(fun)); - sem.notify(); - }, - Qt::AutoConnection); + QObject::connect(&src, &QObject::destroyed, + obj, [&] { fun(); sem.notify(); }, + Qt::QueuedConnection); } sem.wait(); - return traits::pass(std::move(ret)); } template<typename F> void run_in_thread_async(QObject* obj, F&& fun) { + if (obj->thread() == QThread::currentThread()) + return (void)fun(); + QObject src; - QThread* t(obj->thread()); - assert(t); - src.moveToThread(t); - QObject::connect(&src, &QObject::destroyed, obj, std::move(fun), Qt::AutoConnection); + QObject::connect(&src, &QObject::destroyed, obj, std::forward<F>(fun), Qt::QueuedConnection); } diff --git a/compat/shm.cpp b/compat/shm.cpp index 6aeb8ca9..f59469dc 100644 --- a/compat/shm.cpp +++ b/compat/shm.cpp @@ -7,190 +7,131 @@ #include "shm.h" -#if defined(_WIN32) +#if defined _WIN32 #include <cstring> -#include <stdio.h> +#include <cstdio> #include <accctrl.h> #include <aclapi.h> -struct secattr -{ - bool success; - SECURITY_DESCRIPTOR* pSD; - SECURITY_ATTRIBUTES attrs; - PSID pEveryoneSID; - PACL pACL; - - void cleanup() - { - if (pEveryoneSID) - FreeSid(pEveryoneSID); - if (pACL) - LocalFree(pACL); - if (pSD) - LocalFree(pSD); - success = false; - pSD = nullptr; - pEveryoneSID = nullptr; - pACL = nullptr; - } +#ifdef QT_CORE_LIB +# include <QDebug> +# define warn(str, ...) (qDebug() << "shm:" str ": " << __VA_ARGS__) +#else +# define warn(str, ...) (void)0 +#endif - secattr(DWORD perms) : success(true), pSD(nullptr), pEveryoneSID(nullptr), pACL(nullptr) +shm_wrapper::shm_wrapper(const char* shm_name, const char* mutex_name, int map_size) +{ + if (mutex_name == nullptr) + mutex = nullptr; + else { - SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY; - EXPLICIT_ACCESS ea; - - if(!AllocateAndInitializeSid(&SIDAuthWorld, 1, - SECURITY_WORLD_RID, - 0, 0, 0, 0, 0, 0, 0, - &pEveryoneSID)) - { - fprintf(stderr, "AllocateAndInitializeSid: %d\n", (int) GetLastError()); - goto cleanup; - } - - memset(&ea, 0, sizeof(ea)); + mutex = CreateMutexA(nullptr, false, mutex_name); - ea.grfAccessPermissions = perms; - ea.grfAccessMode = SET_ACCESS; - ea.grfInheritance = NO_INHERITANCE; - ea.Trustee.TrusteeForm = TRUSTEE_IS_SID; - ea.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; - ea.Trustee.ptstrName = (LPTSTR) pEveryoneSID; - - if (SetEntriesInAcl(1, &ea, NULL, &pACL) != ERROR_SUCCESS) - { - fprintf(stderr, "SetEntriesInAcl: %d\n", (int) GetLastError()); - goto cleanup; - } - - pSD = (SECURITY_DESCRIPTOR*) LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); - if (pSD == nullptr) + if (!mutex) { - fprintf(stderr, "LocalAlloc: %d\n", (int) GetLastError()); - goto cleanup; + warn("CreateMutexA", (int) GetLastError()); + return; } - - if (!InitializeSecurityDescriptor(pSD, - SECURITY_DESCRIPTOR_REVISION)) - { - fprintf(stderr, "InitializeSecurityDescriptor: %d\n", (int) GetLastError()); - goto cleanup; - } - - if (!SetSecurityDescriptorDacl(pSD, - TRUE, - pACL, - FALSE)) - { - fprintf(stderr, "SetSecurityDescriptorDacl: %d\n", (int) GetLastError()); - goto cleanup; - } - - attrs.bInheritHandle = false; - attrs.lpSecurityDescriptor = pSD; - attrs.nLength = sizeof(SECURITY_ATTRIBUTES); - - return; -cleanup: - cleanup(); - } - - ~secattr() - { - cleanup(); } -}; -PortableLockedShm::PortableLockedShm(const char* shmName, const char* mutexName, int mapSize) -{ - secattr sa(GENERIC_ALL|SYNCHRONIZE); - - hMutex = CreateMutexA(sa.success ? &sa.attrs : nullptr, false, mutexName); - if (!hMutex) - { - fprintf(stderr, "CreateMutexA: %d\n", (int) GetLastError()); - fflush(stderr); - } - hMapFile = CreateFileMappingA( + mapped_file = CreateFileMappingA( INVALID_HANDLE_VALUE, - sa.success ? &sa.attrs : nullptr, + nullptr, PAGE_READWRITE, 0, - mapSize, - shmName); - if (!hMapFile) + map_size, + shm_name); + + if (!mapped_file) { - fprintf(stderr, "CreateFileMappingA: %d\n", (int) GetLastError()); - fflush(stderr); + warn("CreateFileMappingA", (int) GetLastError()); + + return; } - mem = MapViewOfFile(hMapFile, + + mem = MapViewOfFile(mapped_file, FILE_MAP_WRITE, 0, 0, - mapSize); + map_size); + if (!mem) - { - fprintf(stderr, "MapViewOfFile: %d\n", (int) GetLastError()); - fflush(stderr); - } + warn("MapViewOfFile:", (int) GetLastError()); } -PortableLockedShm::~PortableLockedShm() +shm_wrapper::~shm_wrapper() { - UnmapViewOfFile(mem); - CloseHandle(hMapFile); - CloseHandle(hMutex); + if (mem && !UnmapViewOfFile(mem)) + goto fail; + + if (mapped_file && !CloseHandle(mapped_file)) + goto fail; + + if (mutex && !CloseHandle(mutex)) + goto fail; + + return; + +fail: + warn("failed to close mapping", (int) GetLastError()); } -void PortableLockedShm::lock() +bool shm_wrapper::lock() { - (void) WaitForSingleObject(hMutex, INFINITE); + if (mutex) + return WaitForSingleObject(mutex, INFINITE) == WAIT_OBJECT_0; + else + return false; } -void PortableLockedShm::unlock() +bool shm_wrapper::unlock() { - (void) ReleaseMutex(hMutex); + if (mutex) + return ReleaseMutex(mutex); + else + return false; } #else #include <limits.h> #pragma GCC diagnostic ignored "-Wunused-result" -PortableLockedShm::PortableLockedShm(const char *shmName, const char* /*mutexName*/, int mapSize) : size(mapSize) +shm_wrapper::shm_wrapper(const char *shm_name, const char* /*mutex_name*/, int map_size) : size(map_size) { - char filename[PATH_MAX+2] = {0}; + char filename[PATH_MAX+2] {}; strcpy(filename, "/"); - strcat(filename, shmName); + strcat(filename, shm_name); fd = shm_open(filename, O_RDWR | O_CREAT, 0600); - (void) ftruncate(fd, mapSize); - mem = mmap(NULL, mapSize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0); + (void) ftruncate(fd, map_size); + mem = mmap(nullptr, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, (off_t)0); } -PortableLockedShm::~PortableLockedShm() +shm_wrapper::~shm_wrapper() { (void) munmap(mem, size); (void) close(fd); } -void PortableLockedShm::lock() +bool shm_wrapper::lock() { - flock(fd, LOCK_EX); + return flock(fd, LOCK_EX) == 0; } -void PortableLockedShm::unlock() +bool shm_wrapper::unlock() { - flock(fd, LOCK_UN); + return flock(fd, LOCK_UN) == 0; } #endif -bool PortableLockedShm::success() +bool shm_wrapper::success() { #ifndef _WIN32 return mem != (void*) -1; #else - return mem != NULL; + return mem != nullptr; #endif } + diff --git a/compat/shm.h b/compat/shm.h index ffd1e1dc..5036b335 100644 --- a/compat/shm.h +++ b/compat/shm.h @@ -19,30 +19,26 @@ #include <sys/types.h> #endif -#ifdef __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wattributes" -#endif - #include "export.hpp" +#include "macros.h" -class OTR_COMPAT_EXPORT PortableLockedShm { -public: - PortableLockedShm(const char *shmName, const char *mutexName, int mapSize); - ~PortableLockedShm(); - void lock(); - void unlock(); - bool success(); - inline void* ptr() { return mem; } -private: +class OTR_COMPAT_EXPORT shm_wrapper final +{ void* mem; #if defined(_WIN32) - HANDLE hMutex, hMapFile; + HANDLE mutex, mapped_file; #else - int fd, size; + int fd; + unsigned size; #endif -}; -#ifdef __GNUC__ -# pragma GCC diagnostic pop -#endif +public: + shm_wrapper(const char *shm_name, const char *mutex_name, int map_size); + ~shm_wrapper(); + bool lock(); + bool unlock(); + bool success(); + inline void* ptr() { return mem; } + + OTR_DISABLE_MOVE_COPY(shm_wrapper); +}; diff --git a/compat/simple-mat.hpp b/compat/simple-mat.hpp index 1146984a..e697f76a 100644 --- a/compat/simple-mat.hpp +++ b/compat/simple-mat.hpp @@ -8,335 +8,328 @@ #pragma once -#include <cmath> -#include "export.hpp" -#include <compat/util.hpp> - -#include <initializer_list> +#include <cstddef> #include <type_traits> #include <utility> +#include <cmath> + +namespace simple_mat { + // last param to fool SFINAE into overloading + template<int i, int j, int> + struct equals + { + enum { value = i == j }; + }; + template<int i, int j, int min, int max> + struct maybe_add_swizzle + { + enum { value = (i == 1 || j == 1) && (i >= min || j >= min) && (i <= max || j <= max) }; + }; -namespace mat_detail { + template<int j, int i> + struct is_vector + { + enum { value = j == 1 || i == 1 }; + }; -// `zz' param to fool into SFINAE member overload + template<int i1, int j1, int i2, int j2> + struct is_vector_pair + { + enum { value = (i1 == i2 && j1 == 1 && j2 == 1) || (j1 == j2 && i1 == 1 && i2 == 1) }; + }; + template<int i, int j> + struct vector_len + { + enum { value = i > j ? i : j }; + }; + template<int a, int b, int c, int d> + struct is_dim3 + { + enum { value = (a == 1 && c == 1 && b == 3 && d == 3) || (a == 3 && c == 3 && b == 1 && d == 1) }; + enum { P = a == 1 ? 1 : 3 }; + enum { Q = a == 1 ? 3 : 1 }; + }; -template<int i, int j, int k, int zz> -constexpr bool equals = ((void)zz, i == k && j != k); + template<typename, int h, int w, typename...ts> + struct is_arglist_correct + { + enum { value = h * w == sizeof...(ts) }; + }; -template<int i, int j, int min> -constexpr bool maybe_swizzle = - (i == 1 || j == 1) && (i >= min || j >= min); +template<typename num, int H, int W> +class Mat +{ + static_assert(H > 0 && W > 0, "must have positive mat dimensions"); + num data[H][W]; -template<int i1, int j1, int i2, int j2> -constexpr bool is_vector_pair = - (i1 == i2 && j1 == 1 && j2 == 1) || (j1 == j2 && i1 == 1 && i2 == 1); +public: + // parameters W and H are rebound so that SFINAE occurs + // removing them causes a compile-time error -sh 20150811 -template<int i, int j> -constexpr unsigned vector_len = i > j ? i : j; + template<typename t, int Q = W> std::enable_if_t<equals<Q, 1, 0>::value, num> + constexpr inline operator()(t i) const& { return data[(unsigned)i][0]; } -template<int a, int b, int c, int d> -constexpr bool dim3 = - (a == 3 || b == 3) && (c == 3 || d == 3) && - (a == 1 || b == 1) && (c == 1 || d == 1); + template<typename t, int P = H> std::enable_if_t<equals<P, 1, 1>::value, num> + constexpr inline operator()(t i) const& { return data[0][(unsigned)i]; } -template<int h, int w, typename... ts> -constexpr bool arglist_correct = h * w == sizeof...(ts); + template<typename t, int Q = W> std::enable_if_t<equals<Q, 1, 2>::value, num&> + constexpr inline operator()(t i) & { return data[(unsigned)i][0]; } -template<bool x, typename t> -using sfinae = typename std::enable_if<x, t>::type; + template<typename t, int P = H> std::enable_if_t<equals<P, 1, 3>::value, num&> + constexpr inline operator()(t i) & { return data[0][(unsigned)i]; } -template<typename num, int h_, int w_> -class Mat -{ - static_assert(h_ > 0 && w_ > 0, "must have positive mat dimensions"); - num data[h_][w_]; +#define OTR_MAT_ASSERT_SWIZZLE static_assert(P == H && Q == W) -#define OTR_ASSERT_SWIZZLE static_assert(P == h_ && Q == w_, "") + // const variants + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 1, 4>::value, num> + constexpr inline x() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(0); } - Mat(std::initializer_list<num>&& init) - { - auto iter = init.begin(); - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) - data[j][i] = *iter++; - } + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 2, 4>::value, num> + constexpr inline y() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(1); } -public: - // start sfinae-R-us block + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 3, 4>::value, num> + constexpr inline z() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(2); } - // rebinding w_ and h_ since SFINAE requires dependent variables + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 4, 4>::value, num> + constexpr inline w() const& { OTR_MAT_ASSERT_SWIZZLE; return operator()(3); } - template<int P = h_, int Q = w_> sfinae<equals<Q, P, 1, 0>, num> - OTR_FLATTEN operator()(int i) const { OTR_ASSERT_SWIZZLE; return data[i][0]; } - template<int P = h_, int Q = w_> sfinae<equals<Q, 0, 1, 0>, num&> - OTR_FLATTEN operator()(int i) { OTR_ASSERT_SWIZZLE; return data[i][0]; } + // mutable variants + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 1, 4>::value, num&> + constexpr inline x() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(0); } - template<int P = h_, int Q = w_> sfinae<equals<P, Q, 1, 1>, num> - OTR_FLATTEN operator()(int i) const { OTR_ASSERT_SWIZZLE; return data[0][i]; } - template<int P = h_, int Q = w_> sfinae<equals<P, Q, 1, 1>, num&> - OTR_FLATTEN operator()(int i) { OTR_ASSERT_SWIZZLE; return data[0][i]; } + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 2, 4>::value, num&> + constexpr inline y() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(1); } - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 1>, num> - OTR_FLATTEN x() const { OTR_ASSERT_SWIZZLE; return operator()(0); } - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 1>, num&> - OTR_FLATTEN x() { OTR_ASSERT_SWIZZLE; return operator()(0); } + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 3, 4>::value, num&> + constexpr inline z() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(2); } - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 2>, num> - OTR_FLATTEN y() const { OTR_ASSERT_SWIZZLE; return operator()(1); } - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 2>, num&> - OTR_FLATTEN y() { OTR_ASSERT_SWIZZLE; return operator()(1); } + template<int P = H, int Q = W> std::enable_if_t<maybe_add_swizzle<P, Q, 4, 4>::value, num&> + constexpr inline w() & { OTR_MAT_ASSERT_SWIZZLE; return operator()(3); } - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 3>, num> - OTR_FLATTEN z() const { OTR_ASSERT_SWIZZLE; return operator()(2); } - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 3>, num&> - OTR_FLATTEN z() { OTR_ASSERT_SWIZZLE; return operator()(2); } + template<int P = H, int Q = W> + constexpr auto norm_squared() const -> std::enable_if_t<is_vector<P, Q>::value, num> + { + static_assert(P == H && Q == W); - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 4>, num> - OTR_FLATTEN w() const { OTR_ASSERT_SWIZZLE; return operator()(3); } - template<int P = h_, int Q = w_> sfinae<maybe_swizzle<P, Q, 4>, num&> - OTR_FLATTEN w() { OTR_ASSERT_SWIZZLE; return operator()(3); } + const num val = dot(*this); + constexpr num eps = num(1e-4); - // end sfinae-R-us block + if (val < eps) + return num(0); + else + return val; + } - // parameters w_ and h_ are rebound so that SFINAE occurs - // removing them causes a compile-time error -sh 20150811 + inline auto norm() const { return num(std::sqrt(norm_squared())); } - template<int R, int S, int P = h_, int Q = w_> - sfinae<is_vector_pair<R, S, P, Q>, num> - dot(const Mat<num, R, S>& p2) const + template<int R, int S, int P = H, int Q = W> + std::enable_if_t<is_vector_pair<R, S, P, Q>::value, num> + constexpr dot(const Mat<num, R, S>& p2) const { - OTR_ASSERT_SWIZZLE; + static_assert(P == H && Q == W); num ret = 0; - static constexpr unsigned len = vector_len<R, S>; - for (unsigned i = 0; i < len; i++) + constexpr int len = vector_len<R, S>::value; + for (int i = 0; i < len; i++) ret += operator()(i) * p2(i); return ret; } - template<int R, int S, int P = h_, int Q = w_> sfinae<dim3<P, Q, R, S>, Mat<num, 3, 1>> - cross(const Mat<num, R, S>& p2) const + template<int R, int S, int P = H, int Q = W> + std::enable_if_t<is_dim3<P, Q, R, S>::value, Mat<num, is_dim3<P, Q, R, S>::P, is_dim3<P, Q, R, S>::Q>> + constexpr cross(const Mat<num, R, S>& b) const { - OTR_ASSERT_SWIZZLE; - decltype(*this)& OTR_RESTRICT p1 = *this; + static_assert(P == H && Q == W); + const auto& a = *this; - return Mat<num, R, S>(p1.y() * p2.z() - p2.y() * p1.z(), - p2.x() * p1.z() - p1.x() * p2.z(), - p1.x() * p2.y() - p1.y() * p2.x()); + return Mat<num, R, S>(a.y()*b.z() - a.z()*b.y(), + a.z()*b.x() - a.x()*b.z(), + a.x()*b.y() - a.y()*b.x()); } - Mat<num, h_, w_> operator+(const Mat<num, h_, w_>& other) const + constexpr Mat<num, H, W> operator+(const Mat<num, H, W>& other) const { - Mat<num, h_, w_> ret; - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) + Mat<num, H, W> ret; + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) ret(j, i) = data[j][i] + other.data[j][i]; return ret; } - Mat<num, h_, w_> operator-(const Mat<num, h_, w_>& other) const + constexpr Mat<num, H, W> operator-(const Mat<num, H, W>& other) const { - Mat<num, h_, w_> ret; - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) + Mat<num, H, W> ret; + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) ret(j, i) = data[j][i] - other.data[j][i]; return ret; } + constexpr Mat<num, H, W> operator+(const num other) const + { + Mat<num, H, W> ret; + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) + ret(j, i) = data[j][i] + other; + return ret; + } + + constexpr Mat<num, H, W> operator-(const num other) const + { + Mat<num, H, W> ret; + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) + ret(j, i) = data[j][i] - other; + return ret; + } + template<int p> - Mat<num, h_, p> operator*(const Mat<num, w_, p>& other) const + constexpr Mat<num, H, p> operator*(const Mat<num, W, p>& other) const { - Mat<num, h_, p> ret; - for (int k = 0; k < h_; k++) + Mat<num, H, p> ret; + for (int k = 0; k < H; k++) for (int i = 0; i < p; i++) { ret(k, i) = 0; - for (int j = 0; j < w_; j++) + for (int j = 0; j < W; j++) ret(k, i) += data[k][j] * other(j, i); } return ret; } - inline num operator()(int j, int i) const { return data[j][i]; } - inline num& operator()(int j, int i) { return data[j][i]; } + constexpr Mat<num, H, W> mult_elementwise(const Mat<num, H, W>& other) const& + { + Mat<num, H, W> ret; - inline num operator()(unsigned j, unsigned i) const { return data[j][i]; } - inline num& operator()(unsigned j, unsigned i) { return data[j][i]; } + for (unsigned j = 0; j < H; j++) + for (unsigned i = 0; i < W; i++) + ret(j, i) = data[j][i] * other.data[j][i]; - template<typename... ts, int P = h_, int Q = w_, - typename = sfinae<arglist_correct<P, Q, ts...>, void>> - Mat(const ts... xs) - { - OTR_ASSERT_SWIZZLE; - static_assert(arglist_correct<P, Q, ts...>, ""); + return ret; + } - std::initializer_list<num> init = { static_cast<num>(xs)... }; + template<typename t, typename u> + constexpr inline num operator()(t j, u i) const& { return data[(unsigned)j][(unsigned)i]; } - *this = Mat(std::move(init)); + template<typename t, typename u> + constexpr inline num& operator()(t j, u i) & { return data[(unsigned)j][(unsigned)i]; } + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wmissing-braces" +#endif + + template<typename... ts, int h2 = H, int w2 = W, + typename = std::enable_if_t<is_arglist_correct<num, h2, w2, ts...>::value>> + constexpr Mat(const ts... xs) : data{static_cast<num>(xs)...} + { + static_assert(h2 == H && w2 == W); } - Mat() +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + + constexpr Mat() { - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) data[j][i] = num(0); } - Mat(const num* OTR_RESTRICT mem) + constexpr Mat(const num* mem) { - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) - data[j][i] = mem[i*h_+j]; + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) + data[j][i] = mem[i*H+j]; } - OTR_ALWAYS_INLINE operator num*() { return reinterpret_cast<num*>(data); } - OTR_ALWAYS_INLINE operator const num*() const { return reinterpret_cast<const num*>(data); } + constexpr Mat(const Mat<num, H, W>& x) + { + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) + data[j][i] = x(j, i); + } + + constexpr operator num*() & { return (num*)data; } + constexpr operator const num*() const& { return (const num*)data; } // XXX add more operators as needed, third-party dependencies mostly // not needed merely for matrix algebra -sh 20141030 - template<int P = h_> - static typename std::enable_if<P == w_, Mat<num, P, P>>::type eye() + template<int H_ = H> + static constexpr std::enable_if_t<H == W, Mat<num, H_, H_>> eye() { - static_assert(P == h_, ""); - - Mat<num, P, P> ret; + static_assert(H == H_); - for (int j = 0; j < P; j++) - for (int i = 0; i < w_; i++) + Mat<num, H, H> ret; + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) ret.data[j][i] = 0; - for (int i = 0; i < P; i++) + for (int i = 0; i < H; i++) ret.data[i][i] = 1; return ret; } - Mat<num, w_, h_> t() const + constexpr Mat<num, W, H> t() const { - Mat<num, w_, h_> ret; + Mat<num, W, H> ret; - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) ret(i, j) = data[j][i]; return ret; } -}; - -template<typename num> -class Quat : Mat<num, 4, 1> -{ - using quat = Quat<num>; - - enum idx { qw, qx, qy, qz }; - - static quat _from_array(const num* data) - { - return quat(data[qw], data[qx], data[qy], data[qz]); - } - inline num elt(idx k) const { return operator()(k); } - inline num& elt(idx k) { return Mat<num, 4, 1>::operator()(int(k)); } -public: - Quat(num w, num x, num y, num z) : Mat<num, 4, 1>(w, x, y, z) - { - } - - Quat() : quat(1, 0, 0, 0) {} - - static quat from_vector(const Mat<num, 4, 1>& data) + constexpr Mat<num, H, W>& operator=(const Mat<num, H, W>& rhs) { - return _from_array(data); - } + for (unsigned j = 0; j < H; j++) + for (unsigned i = 0; i < W; i++) + data[j][i] = rhs(j, i); - static quat from_vector(const Mat<num, 1, 4>& data) - { - return _from_array(data); + return *this; } - - quat normalized() const - { - const num x = elt(qx), y = elt(qy), z = elt(qz), w = elt(qw); - const num inv_n = 1./std::sqrt(x*x + y*y + z*z + w*w); - - return Quat<num>(elt(qw) * inv_n, - elt(qx) * inv_n, - elt(qy) * inv_n, - elt(qz) * inv_n); - } - - quat operator*(const quat& q2) - { - const quat& OTR_RESTRICT q1 = *this; - return quat(-q1.x() * q2.x() - q1.y() * q2.y() - q1.z() * q2.z() + q1.w() * q2.w(), - q1.x() * q2.w() + q1.y() * q2.z() - q1.z() * q2.y() + q1.w() * q2.x(), - -q1.x() * q2.z() + q1.y() * q2.w() + q1.z() * q2.x() + q1.w() * q2.y(), - q1.x() * q2.y() - q1.y() * q2.x() + q1.z() * q2.w() + q1.w() * q2.z()); - } - - inline num w() const { return elt(qw); } - inline num x() const { return elt(qx); } - inline num y() const { return elt(qy); } - inline num z() const { return elt(qz); } - - inline num& w() { return elt(qw); } - inline num& x() { return elt(qx); } - inline num& y() { return elt(qy); } - inline num& z() { return elt(qz); } }; -} // ns detail +template<unsigned k, typename num, int h, int w> +constexpr num get(const Mat<num, h, w>& m) { return m(k); } -template<typename num, int h, int w> -using Mat = mat_detail::Mat<num, h, w>; +template<unsigned k, typename num, int h, int w> +constexpr num& get(Mat<num, h, w>& m) { return m(k); } -template<typename num, int h, int w> -inline Mat<num, h, w> operator*(num scalar, const Mat<num, h, w>& mat) { return mat * scalar; } +} // ns simple_mat template<typename num, int h, int w> -inline Mat<num, h, w> operator-(num scalar, const Mat<num, h, w>& mat) { return mat - scalar; } +using Mat = simple_mat::Mat<num, h, w>; template<typename num, int h, int w> -inline Mat<num, h, w> operator+(num scalar, const Mat<num, h, w>& mat) { return mat + scalar; } - -template<typename num, int h_, int w_> -inline Mat<num, h_, w_> operator*(const Mat<num, h_, w_>& OTR_RESTRICT self, num other) +constexpr Mat<num, h, w> operator*(num scalar, const Mat<num, h, w>& mat) { - Mat<num, h_, w_> ret; - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) - ret(j, i) = self(j, i) * other; - return ret; -} - -template<typename num, int h_, int w_> -inline Mat<num, h_, w_> operator-(const Mat<num, h_, w_>& OTR_RESTRICT self, num other) -{ - Mat<num, h_, w_> ret; - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) - ret(j, i) = self(j, i) - other; - return ret; + return mat * scalar; } -template<typename num, int h_, int w_> -inline Mat<num, h_, w_> operator+(const Mat<num, h_, w_>& OTR_RESTRICT self, num other) +template<typename num, int H, int W> +constexpr Mat<num, H, W> operator*(const Mat<num, H, W>& self, num other) { - Mat<num, h_, w_> ret; - for (int j = 0; j < h_; j++) - for (int i = 0; i < w_; i++) - ret(j, i) = self(j, i) + other; + Mat<num, H, W> ret; + for (int j = 0; j < H; j++) + for (int i = 0; i < W; i++) + ret(j, i) = self(j, i) * other; return ret; } -template<typename num> -using Quat_ = mat_detail::Quat<num>; - -using Quat = Quat_<double>; +namespace std { + template<typename num, int H, int W> + struct tuple_size<Mat<num, H, W>> : + std::integral_constant<std::size_t, H == 1 || W == 1 ? W * H : 0> + {}; -template class mat_detail::Mat<float, 3, 3>; -template class mat_detail::Mat<float, 6, 1>; -template class mat_detail::Mat<float, 3, 1>; - -// eof + template<std::size_t k, typename num, int h, int w> + struct tuple_element<k, Mat<num, h, w>> + { + using type = std::remove_const_t<std::remove_reference_t<num>>; + }; +} // ns std diff --git a/compat/sleep.cpp b/compat/sleep.cpp new file mode 100644 index 00000000..e64e6254 --- /dev/null +++ b/compat/sleep.cpp @@ -0,0 +1,23 @@ +#include "sleep.hpp" + +#ifdef _WIN32 +# include <windows.h> +#else +# include <unistd.h> +#endif + +namespace portable +{ + void sleep(int milliseconds) + { + if (milliseconds > 0) + { +#ifdef _WIN32 + + Sleep((unsigned)milliseconds); +#else + usleep(unsigned(milliseconds) * 1000U); // takes microseconds +#endif + } + } +} diff --git a/compat/sleep.hpp b/compat/sleep.hpp index 4cd7a397..9419b36b 100644 --- a/compat/sleep.hpp +++ b/compat/sleep.hpp @@ -1,24 +1,7 @@ #pragma once -#ifdef _WIN32 -# include <windows.h> -#else -# include <unistd.h> -#endif +#include "export.hpp" -namespace portable -{ -#ifdef _WIN32 - inline void sleep(int milliseconds) - { - if (milliseconds > 0) - Sleep(milliseconds); - } -#else - inline void sleep(int milliseconds) - { - if (milliseconds > 0) - usleep(unsigned(milliseconds) * 1000U); // takes microseconds - } -#endif +namespace portable { + OTR_COMPAT_EXPORT void sleep(int milliseconds); } diff --git a/compat/sysexits.hpp b/compat/sysexits.hpp new file mode 100644 index 00000000..6747fc88 --- /dev/null +++ b/compat/sysexits.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include <cstdlib> // for EXIT_SUCCESS, EXIT_FAILRUE + +#ifndef _WIN32 +# include <sysexits.h> +#else +// this conforms to BSD sysexits(3) +// reference the manual page on FreeBSD or Linux for semantics +# define EX_OK 0 +# define EX_USAGE 64 +# define EX_DATAERR 65 +# define EX_NOINPUT 66 +# define EX_NOUSER 67 +# define EX_NOHOST 68 +# define EX_UNAVAILABLE 69 +# define EX_SOFTWARE 70 +# define EX_OSERR 71 +# define EX_OSFILE 72 +# define EX_CANTCREAT 73 +# define EX_IOERR 74 +# define EX_TEMPFAIL 75 +# define EX_PROTOCOL 76 +# define EX_NOPERM 77 +# define EX_CONFIG 78 +#endif + + diff --git a/compat/thread-name.cpp b/compat/thread-name.cpp new file mode 100644 index 00000000..08a7d628 --- /dev/null +++ b/compat/thread-name.cpp @@ -0,0 +1,87 @@ +#include "thread-name.hpp" +#ifdef _WIN32 +# include <QDebug> +# include <windows.h> +#else +# include <QThread> +#endif + +namespace portable { + +#ifdef _WIN32 + +#ifdef _MSC_VER +struct THREADNAME_INFO +{ + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in user addr space) + HANDLE dwThreadID; // thread ID (-1=caller thread) + DWORD dwFlags; // reserved for future use, must be zero +}; + +static inline +void set_curthread_name_old_(const char* name) +{ + HANDLE curthread = GetCurrentThread(); + THREADNAME_INFO info; // NOLINT(cppcoreguidelines-pro-type-member-init) + info.dwType = 0x1000; + info.szName = name; + info.dwThreadID = curthread; + info.dwFlags = 0; + __try + { + static_assert(sizeof(info) % sizeof(unsigned) == 0); + unsigned sz = sizeof(info)/sizeof(unsigned); + RaiseException(0x406D1388, 0, sz, (const ULONG_PTR*)&info); + } + __except (EXCEPTION_CONTINUE_EXECUTION) + { + } +} + +static inline +void set_curthread_name_old(const QString& name_) +{ + QByteArray str = name_.toLocal8Bit(); + const char* name = str.constData(); + + set_curthread_name_old_(name); +} +#else + +static inline void set_curthread_name_old(const QString&) {} + +#endif + +using SetThreadDescr_type = HRESULT (__stdcall *)(HANDLE, const wchar_t*); + +static SetThreadDescr_type get_funptr() +{ + HMODULE module; + if (GetModuleHandleExA(0, "kernel32.dll", &module)) + return (SetThreadDescr_type)GetProcAddress(module, "SetThreadDescription"); + else + return nullptr; +} + +void set_curthread_name(const QString& name) +{ + static_assert(sizeof(wchar_t) == sizeof(decltype(*QString().utf16()))); + static const SetThreadDescr_type fn = get_funptr(); + + if (fn) + fn(GetCurrentThread(), (const wchar_t*)name.utf16()); + else + set_curthread_name_old(name); +} + +#else + +void set_curthread_name(const QString& name) +{ + QThread::currentThread()->setObjectName(name); +} + +#endif + +} // ns portable diff --git a/compat/thread-name.hpp b/compat/thread-name.hpp new file mode 100644 index 00000000..21256003 --- /dev/null +++ b/compat/thread-name.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "export.hpp" +#include <QString> +namespace portable { + OTR_COMPAT_EXPORT void set_curthread_name(const QString& name); +} diff --git a/compat/time.hpp b/compat/time.hpp new file mode 100644 index 00000000..0d171038 --- /dev/null +++ b/compat/time.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include <chrono> + +namespace time_units { + +template<typename repr, typename ratio = std::ratio<1>> +using duration = std::chrono::duration<repr, ratio>; + +using secs = duration<float>; +using ms = duration<float, std::milli>; +using us = duration<float, std::micro>; +using ns = duration<float, std::nano>; + +} // ns time_units diff --git a/compat/timer.cpp b/compat/timer.cpp index d5052ccb..fdea185a 100644 --- a/compat/timer.cpp +++ b/compat/timer.cpp @@ -6,7 +6,12 @@ * notice appear in all copies. */ +#undef NDEBUG + #include "timer.hpp" +#include <cassert> +#include <cmath> +#include <QDebug> Timer::Timer() { @@ -15,103 +20,88 @@ Timer::Timer() void Timer::start() { - wrap_gettime(&state); + gettime(&state); } -// common - -void Timer::wrap_gettime(timespec* state) +struct timespec Timer::get_delta() const { -#if defined(_WIN32) || defined(__MACH__) - otr_clock_gettime(state); -#else - (void) clock_gettime(CLOCK_MONOTONIC, state); -#endif -} - -// nanoseconds - -long long Timer::elapsed_nsecs() const -{ - struct timespec cur = {}; - wrap_gettime(&cur); - return conv_nsecs(cur); -} - -long long Timer::conv_nsecs(const timespec& cur) const -{ - // this can and will overflow - return (cur.tv_sec - state.tv_sec) * 1000000000LL + (cur.tv_nsec - state.tv_nsec); -} - -// microseconds - -double Timer::elapsed_usecs() const -{ - struct timespec cur = {}; - wrap_gettime(&cur); - const long long nsecs = conv_nsecs(cur); - return nsecs * 1e-3; + struct timespec ts; // NOLINT + gettime(&ts); + return { ts.tv_sec - state.tv_sec, ts.tv_nsec - state.tv_nsec }; } // milliseconds double Timer::elapsed_ms() const { - return elapsed_usecs() / 1000.; + struct timespec delta = get_delta(); + return delta.tv_sec * 1000 + delta.tv_nsec * 1e-6; } double Timer::elapsed_seconds() const { - return double(elapsed_nsecs() * 1e-9L); + struct timespec delta = get_delta(); + return delta.tv_sec + delta.tv_nsec * 1e-9; } // -- // platform-specific code starts here // -- -#if defined _WIN32 -LARGE_INTEGER Timer::otr_get_clock_frequency() +#if defined (_WIN32) +# include <windows.h> + +static auto otr_get_clock_frequency() { - LARGE_INTEGER freq = {}; - (void) QueryPerformanceFrequency(&freq); - return freq; + LARGE_INTEGER freq{}; + BOOL ret = QueryPerformanceFrequency(&freq); + assert(ret && "QueryPerformanceFrequency failed"); + return freq.QuadPart; } -void Timer::otr_clock_gettime(timespec* ts) +void Timer::gettime(timespec* state) { - static LARGE_INTEGER freq = otr_get_clock_frequency(); - + static const auto freq = otr_get_clock_frequency(); LARGE_INTEGER d; + BOOL ret = QueryPerformanceCounter(&d); + assert(ret && "QueryPerformanceCounter failed"); - (void) QueryPerformanceCounter(&d); - - using ll = long long; - using ld = long double; - const long long part = ll(d.QuadPart / ld(freq.QuadPart) * 1000000000.L); - using t_s = decltype(ts->tv_sec); - using t_ns = decltype(ts->tv_nsec); - - ts->tv_sec = t_s(part / 1000000000LL); - ts->tv_nsec = t_ns(part % 1000000000LL); + constexpr int nsec = 1'000'000 * 1000; + state->tv_sec = (time_t)(d.QuadPart/freq); + state->tv_nsec = (decltype(state->tv_nsec))(d.QuadPart % freq * nsec / freq); } #elif defined __MACH__ -mach_timebase_info_data_t Timer::otr_get_mach_frequency() +# include <inttypes.h> +# include <mach/mach_time.h> + +static mach_timebase_info_data_t otr_get_mach_frequency() { mach_timebase_info_data_t timebase_info; - (void) mach_timebase_info(&timebase_info); + kern_return_t status = mach_timebase_info(&timebase_info); + assert(status == KERN_SUCCESS && "mach_timebase_info failed"); return timebase_info; } -double Timer::otr_clock_gettime(timespec* ts) +void Timer::gettime(timespec* ts) { - static mach_timebase_info_data_t timebase_info = otr_get_mach_frequency(); + static const mach_timebase_info_data_t timebase_info = otr_get_mach_frequency(); uint64_t state, nsec; state = mach_absolute_time(); nsec = state * timebase_info.numer / timebase_info.denom; - ts->tv_sec = nsec / 1000000000L; - ts->tv_nsec = nsec % 1000000000L; + ts->tv_sec = nsec / 1000000000UL; + ts->tv_nsec = nsec % 1000000000UL; +} + +#else + +void Timer::gettime(timespec* ts) +{ + int error = clock_gettime(CLOCK_MONOTONIC, ts); + assert(error == 0 && "clock_gettime failed"); } #endif + +// common + diff --git a/compat/timer.hpp b/compat/timer.hpp index e9efba79..47072226 100644 --- a/compat/timer.hpp +++ b/compat/timer.hpp @@ -9,38 +9,20 @@ #pragma once #include "export.hpp" - -#if defined (_WIN32) -# include <windows.h> -#elif defined(__MACH__) -# include <inttypes.h> -# include <mach/mach_time.h> -#endif - +#include "time.hpp" #include <ctime> -#include <tuple> -class OTR_COMPAT_EXPORT Timer +struct OTR_COMPAT_EXPORT Timer final { - struct timespec state; - long long conv_nsecs(const struct timespec& cur) const; - static void otr_clock_gettime(struct timespec* ts); -#ifdef _WIN32 - static LARGE_INTEGER otr_get_clock_frequency(); -#elif defined(__MACH__) - static mach_timebase_info_data_t otr_get_mach_frequency(); -#endif - - static void wrap_gettime(struct timespec* state); - -public: Timer(); - void start(); - long long elapsed_nsecs() const; - double elapsed_usecs() const; + double elapsed_ms() const; double elapsed_seconds() const; -}; - +private: + struct timespec state {}; + static void gettime(struct timespec* state); + struct timespec get_delta() const; + using ns = time_units::ns; +}; diff --git a/compat/tr.hpp b/compat/tr.hpp new file mode 100644 index 00000000..c5a561e7 --- /dev/null +++ b/compat/tr.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include <QObject> + +// The class does nothing except provide a fake assignment operator for QObject +// It's meant to be used inside classes that need i18n support but are returned by value. + +struct TR : QObject +{ + TR() = default; + TR(const TR&) : QObject(nullptr) {} + + TR& operator=(const TR&) { return *this; } +}; diff --git a/compat/util.hpp b/compat/util.hpp deleted file mode 100644 index 60427b53..00000000 --- a/compat/util.hpp +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include "ndebug-guard.hpp" -#include "run-in-thread.hpp" - -#include <memory> -#include <cmath> -#include <utility> - -#include <QSharedPointer> -#include <QDebug> - -#define progn(...) ([&]() { __VA_ARGS__ }()) - -#define once_only(...) progn(static bool once = false; if (!once) { once = true; __VA_ARGS__; }) - -template<typename t> using mem = std::shared_ptr<t>; -template<typename t> using ptr = std::unique_ptr<t>; - -#ifdef Q_CREATOR_RUN -# define DEFUN_WARN_UNUSED -#elif defined(_MSC_VER) -# define DEFUN_WARN_UNUSED _Check_return_ -#else -# define DEFUN_WARN_UNUSED __attribute__((warn_unused_result)) -#endif - -#if defined(__GNUG__) -# define unused(t, i) t __attribute__((unused)) i -#else -# define unused(t, i) t -#endif - -#if !defined(_WIN32) -# define unused_on_unix(t, i) unused(t, i) -#else -# define unused_on_unix(t, i) t i -#endif - -template<typename t> -int iround(const t& val) -{ - return int(std::round(val)); -} - -namespace util_detail { - -template<typename n> -inline auto clamp_(n val, n min, n max) -> n -{ - if (val > max) - return max; - if (val < min) - return min; - return val; -} - -} - -template<typename t, typename u, typename w> -inline auto clamp(const t& val, const u& min, const w& max) -> decltype(val * min * max) -{ - return ::util_detail::clamp_<decltype(val * min * max)>(val, min, max); -} - -template<typename t, typename... xs> -auto qptr(xs... args) -{ - return QSharedPointer<t>(new t(std::forward<xs>(args)...)); -} - -template<typename t> using qshared = QSharedPointer<t>; - -#if defined _MSC_VER -# define OTR_NEVER_INLINE __declspec(noinline) -#elif defined __GNUG__ -# define OTR_NEVER_INLINE __attribute__((noinline)) -#else -# define OTR_NEVER_INLINE -#endif - -#if defined _MSC_VER || defined __GNUG__ -# define OTR_RESTRICT __restrict -#else -# define OTR_RESTRICT -#endif - -#if defined _MSC_VER -# define OTR_ALWAYS_INLINE __forceinline -#elif defined __GNUG__ -# define OTR_ALWAYS_INLINE __attribute__((always_inline)) -#else -# define OTR_ALWAYS_INLINE inline -#endif - -#if defined __GNUG__ -# define OTR_FLATTEN __attribute__((flatten)) -#else -# define OTR_FLATTEN OTR_ALWAYS_INLINE -#endif diff --git a/compat/variance.hpp b/compat/variance.hpp new file mode 100644 index 00000000..8abefd33 --- /dev/null +++ b/compat/variance.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include <cmath> + +// no copyright information other than the attribution below. + +// code shared as an example/tutorial of running variance +// written by John D. Cook on the website <http://www.johndcook.com/blog/standard_deviation> + +// following references in the site's article: + +// Chan, Tony F.; Golub, Gene H.; LeVeque, Randall J. (1983). +// Algorithms for Computing the Sample Variance: Analysis and Recommendations. +// The American Statistician 37, 242-247. + +// Ling, Robert F. (1974). +// Comparison of Several Algorithms for Computing Sample Means and Variances. +// Journal of the American Statistical Association, Vol. 69, No. 348, 859-866. + +struct variance final +{ + using size_type = unsigned; + + variance& operator=(variance const&) = default; + void clear() { *this = {}; } + + constexpr size_type count() const { return cnt; } + + constexpr double avg() const { return cnt > 0 ? m_new : 0; } + constexpr double Var() const { return cnt > 1 ? s_new/(cnt - 1) : 0; } + double stddev() const { return std::sqrt(Var()); } + + void input(double x) + { + cnt++; + + if (cnt == 1) + { + m_old = m_new = x; + s_old = 0; + } + else + { + m_new = m_old + (x - m_old)/cnt; + s_new = s_old + (x - m_old)*(x - m_new); + + m_old = m_new; + s_old = s_new; + } + } + +private: + double m_old, m_new, s_old, s_new; + size_type cnt = 0; +}; |
