Add Vector template class
Also experimentally bump to C++20 just to see if it works on GCC 9.3feature/simd
parent
c96450b5fe
commit
debaa6b54d
@ -0,0 +1,73 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "Vec.h"
|
||||
|
||||
namespace quicktex::color {
|
||||
|
||||
constexpr size_t uint5_max = (1 << 5) - 1;
|
||||
constexpr size_t uint6_max = (1 << 6) - 1;
|
||||
|
||||
template <size_t N> struct MidpointTable {
|
||||
public:
|
||||
constexpr MidpointTable() : _values() {
|
||||
constexpr float fN = (float)N;
|
||||
for (unsigned i = 0; i < N - 1; i++) { _values[i] = ((float)i / fN) + (0.5f / fN); }
|
||||
_values[N - 1] = 1e+37f;
|
||||
}
|
||||
|
||||
float operator[](size_t i) const {
|
||||
assert(i < N);
|
||||
return _values[i];
|
||||
}
|
||||
|
||||
private:
|
||||
float _values[N];
|
||||
};
|
||||
|
||||
constexpr MidpointTable<32> Midpoints5bit;
|
||||
constexpr MidpointTable<64> Midpoints6bit;
|
||||
|
||||
template <typename T> Vec<T, 3> scale_to_565(Vec<T, 3> unscaled) {
|
||||
return Vec<T, 3>{scale_from_8<T, 5>(unscaled.r()), scale_from_8<T, 6>(unscaled.g()),
|
||||
scale_from_8<T, 5>(unscaled.b())};
|
||||
}
|
||||
|
||||
template <typename T> Vec<T, 3> scale_from_565(Vec<T, 3> scaled) {
|
||||
return Vec<T, 3>{scale_to_8<T, 5>(scaled.r()), scale_to_8<T, 6>(scaled.g()), scale_to_8<T, 5>(scaled.b())};
|
||||
}
|
||||
|
||||
template <typename T = int16_t> Vec<T, 3> precise_round_565(Vec<float, 3> &v) {
|
||||
auto scaled = v * Vec<float, 3>{uint5_max, uint6_max, uint5_max}; // rescale by from (0,1) to (0,int_max)
|
||||
auto rounded = (Vec<T, 3>)scaled; // downcast to integral type
|
||||
rounded = rounded.clamp({0, 0, 0}, {uint5_max, uint6_max, uint5_max}); // clamp to avoid out of bounds float errors
|
||||
|
||||
// increment each channel if above the rounding point
|
||||
if (v.r() > Midpoints5bit[rounded.r()]) rounded.r()++;
|
||||
if (v.g() > Midpoints6bit[rounded.g()]) rounded.g()++;
|
||||
if (v.b() > Midpoints5bit[rounded.b()]) rounded.b()++;
|
||||
|
||||
assert(rounded.r() <= uint5_max);
|
||||
assert(rounded.g() <= uint6_max);
|
||||
assert(rounded.b() <= uint5_max);
|
||||
|
||||
return rounded;
|
||||
}
|
||||
} // namespace quicktex::color
|
@ -0,0 +1,245 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <numeric>
|
||||
#include <xsimd/xsimd.hpp>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
namespace quicktex {
|
||||
|
||||
template <typename T, size_t N> class Vec {
|
||||
public:
|
||||
// region constructors
|
||||
/**
|
||||
* Create a vector from an intializer list
|
||||
* @param vals values to populate with
|
||||
*/
|
||||
Vec(std::initializer_list<T> vals) { std::copy(vals.begin(), vals.end(), _c.begin()); }
|
||||
|
||||
/**
|
||||
* Create a vector from a scalar value
|
||||
* @param scalar value to populate with
|
||||
*/
|
||||
Vec(const T &scalar = 0) { _c.fill(scalar); }
|
||||
|
||||
/**
|
||||
* Create a vector from another vector of the same size and another type
|
||||
* @tparam S Source vector type
|
||||
* @param rvalue Source vector to copy from
|
||||
*/
|
||||
template <typename S> Vec(std::enable_if_t<std::is_convertible_v<S, T>, const Vec<S, N>> &rvalue) {
|
||||
Vec lvalue;
|
||||
for (unsigned i = 0; i < N; i++) { lvalue[i] = static_cast<T>(rvalue[i]); }
|
||||
return lvalue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a vector from a naked pointer
|
||||
* @tparam S Source data type
|
||||
* @param ptr Pointer to the start of the source data. N values will be read.
|
||||
*/
|
||||
template <typename S> Vec(const S *ptr) {
|
||||
for (unsigned i = 0; i < N; i++) { at(i) = static_cast<T>(ptr[i]); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a vector from a std::array
|
||||
* @tparam S Source data type
|
||||
* @param arr Array to copy from
|
||||
*/
|
||||
template <typename S> Vec(const std::array<S, N> &arr) : Vec(arr.begin()) {}
|
||||
// endregion
|
||||
|
||||
// region subscript accessors
|
||||
/**
|
||||
* Get the element at index i
|
||||
* @param i index to read from
|
||||
* @return the element at index i
|
||||
*/
|
||||
T at(size_t i) const {
|
||||
assert(i < N);
|
||||
return _c[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a reference to the element at index i
|
||||
* @param i index to read from
|
||||
* @return Reference to the element at index i
|
||||
*/
|
||||
T &at(size_t i) {
|
||||
assert(i < N);
|
||||
return _c[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element at index i
|
||||
* @param i index to read from
|
||||
* @return the element at index i
|
||||
*/
|
||||
T operator[](size_t i) const { return at(i); }
|
||||
|
||||
/**
|
||||
* Get a reference to the element at index i
|
||||
* @param i index to read from
|
||||
* @return Reference to the element at index i
|
||||
*/
|
||||
T &operator[](size_t i) { return at(i); }
|
||||
|
||||
T *begin() { return _c.begin(); }
|
||||
T *end() { return _c.end(); }
|
||||
const T *begin() const { return _c.begin(); }
|
||||
const T *end() const { return _c.end(); }
|
||||
|
||||
// endregion
|
||||
|
||||
// region accessor shortcuts
|
||||
// RGBA accessors
|
||||
std::enable_if<N >= 1, T> r() const { return _c[0]; }
|
||||
std::enable_if<N >= 1, T &> r() { return _c[0]; }
|
||||
std::enable_if<N >= 2, T> g() const { return _c[1]; }
|
||||
std::enable_if<N >= 2, T &> g() { return _c[1]; }
|
||||
std::enable_if<N >= 3, T> b() const { return _c[2]; }
|
||||
std::enable_if<N >= 3, T &> b() { return _c[2]; }
|
||||
std::enable_if<N >= 4, T> a() const { return _c[3]; }
|
||||
std::enable_if<N >= 4, T &> a() { return _c[3]; }
|
||||
|
||||
// XYZW accessors
|
||||
std::enable_if<N >= 1, T> x() const { return _c[0]; }
|
||||
std::enable_if<N >= 1, T &> x() { return _c[0]; }
|
||||
std::enable_if<N >= 2, T> y() const { return _c[1]; }
|
||||
std::enable_if<N >= 2, T &> y() { return _c[1]; }
|
||||
std::enable_if<N >= 3, T> z() const { return _c[2]; }
|
||||
std::enable_if<N >= 3, T &> z() { return _c[2]; }
|
||||
std::enable_if<N >= 4, T> w() const { return _c[3]; }
|
||||
std::enable_if<N >= 4, T &> w() { return _c[3]; }
|
||||
// endregion
|
||||
|
||||
// region simple operators
|
||||
friend Vec operator+(const Vec &lhs, const Vec &rhs) { return map(lhs, rhs, std::plus()); }
|
||||
friend Vec operator-(const Vec &lhs, const Vec &rhs) { return map(lhs, rhs, std::minus()); }
|
||||
friend Vec operator*(const Vec &lhs, const Vec &rhs) { return map(lhs, rhs, std::multiplies()); }
|
||||
friend Vec operator/(const Vec &lhs, const Vec &rhs) { return map(lhs, rhs, std::divides()); }
|
||||
|
||||
friend Vec operator+(const Vec &lhs, const T &rhs) { return map(lhs, rhs, std::plus()); }
|
||||
friend Vec operator-(const Vec &lhs, const T &rhs) { return map(lhs, rhs, std::minus()); }
|
||||
friend Vec operator*(const Vec &lhs, const T &rhs) { return map(lhs, rhs, std::multiplies()); }
|
||||
friend Vec operator/(const Vec &lhs, const T &rhs) { return map(lhs, rhs, std::divides()); }
|
||||
|
||||
friend Vec &operator+=(Vec &lhs, const Vec &rhs) { return lhs = lhs + rhs; }
|
||||
friend Vec &operator-=(Vec &lhs, const Vec &rhs) { return lhs = lhs - rhs; }
|
||||
friend Vec &operator*=(Vec &lhs, const Vec &rhs) { return lhs = lhs * rhs; }
|
||||
friend Vec &operator/=(Vec &lhs, const Vec &rhs) { return lhs = lhs / rhs; }
|
||||
|
||||
friend Vec &operator+=(Vec &lhs, const T &rhs) { return lhs = lhs + rhs; }
|
||||
friend Vec &operator-=(Vec &lhs, const T &rhs) { return lhs = lhs - rhs; }
|
||||
friend Vec &operator*=(Vec &lhs, const T &rhs) { return lhs = lhs * rhs; }
|
||||
friend Vec &operator/=(Vec &lhs, const T &rhs) { return lhs = lhs / rhs; }
|
||||
|
||||
bool operator==(const Vec &rhs) const { return _c == rhs._c; };
|
||||
bool operator!=(const Vec &rhs) const { return _c != rhs._c; };
|
||||
// endregion
|
||||
|
||||
template <typename U> void write(U *ptr) const {
|
||||
if constexpr (std::is_same_v<T, U>) {
|
||||
std::memcpy(ptr, _c.begin(), N * sizeof(T));
|
||||
} else {
|
||||
for (unsigned i = 0; i < N; i++) { ptr[i] = static_cast<U>(_c[i]); }
|
||||
}
|
||||
}
|
||||
|
||||
template <typename P = T, typename W = size_t>
|
||||
requires std::is_unsigned_v<P> && std::is_integral_v<T>
|
||||
P pack(const Vec<W, N> &widths) const {
|
||||
assert((sizeof(P) * 8) >= (size_t)std::accumulate(widths.begin(), widths.end(), 0));
|
||||
|
||||
P packed = 0;
|
||||
|
||||
for (unsigned i = 0; i < N; i++) {
|
||||
T val = at(i);
|
||||
if constexpr (std::is_signed_v<T>) { val &= ((1 << widths[i]) - 1); } // mask out upper bits of signed vals
|
||||
|
||||
assert(val < (1 << widths[i]));
|
||||
|
||||
packed = (packed << widths[i]) | val;
|
||||
}
|
||||
return packed;
|
||||
}
|
||||
|
||||
T sum() const { return std::accumulate(begin(), end(), T{0}); }
|
||||
|
||||
T dot(const Vec &rhs) const {
|
||||
Vec product = (*this) * rhs;
|
||||
return product.sum();
|
||||
}
|
||||
|
||||
T sqr_mag() const { return this->dot(*this); }
|
||||
|
||||
Vec abs() const {
|
||||
return map(*this, [](T val) { return quicktex::abs(val); });
|
||||
}
|
||||
|
||||
Vec clamp(const float &low, const float &high) {
|
||||
return map(*this, [&low, &high](T val) { return quicktex::clamp(val, low, high); });
|
||||
}
|
||||
|
||||
Vec clamp(const Vec &low, const Vec &high) {
|
||||
Vec r;
|
||||
for (unsigned i = 0; i < N; i++) { r[i] = quicktex::clamp(at(i), low[i], high[i]); }
|
||||
return r;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::array<T, N> _c; // internal array of components
|
||||
|
||||
template <typename Op> static inline Vec map(const Vec &lhs, Op f) {
|
||||
Vec r;
|
||||
for (unsigned i = 0; i < N; i++) { r[i] = f(lhs[i]); }
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename Op> static inline Vec map(const Vec &lhs, const T &rhs, Op f) {
|
||||
Vec r;
|
||||
for (unsigned i = 0; i < N; i++) { r[i] = f(lhs[i], rhs); }
|
||||
return r;
|
||||
}
|
||||
|
||||
template <typename Op> static inline Vec map(const Vec &lhs, const Vec &rhs, Op f) {
|
||||
Vec r;
|
||||
for (unsigned i = 0; i < N; i++) { r[i] = f(lhs[i], rhs[i]); }
|
||||
return r;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, size_t N, typename A = xsimd::default_arch> class BatchVec : Vec<xsimd::batch<T, A>, N> {
|
||||
template <typename M = xsimd::unaligned_mode> void store(std::array<T *, N> mem_rows, M) const {
|
||||
for (unsigned i = 0; i < N; i++) { this->_c[i].store(mem_rows[i], M{}); }
|
||||
}
|
||||
|
||||
template <typename M = xsimd::unaligned_mode> static Vec<T, N> load(std::array<T *, N> mem_rows, M) {
|
||||
BatchVec<T, N, A> val;
|
||||
for (unsigned i = 0; i < N; i++) { val[i] = xsimd::load<A, T>(mem_rows[i], M{}); }
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace quicktex
|
@ -1,26 +0,0 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace quicktex::tests {
|
||||
|
||||
void test_widening_hadd();
|
||||
|
||||
} // namespace quicktex::tests
|
@ -0,0 +1,140 @@
|
||||
/* Quicktex Texture Compression Library
|
||||
Copyright (C) 2021 Andrew Cassidy <drewcassidy@me.com>
|
||||
Partially derived from rgbcx.h written by Richard Geldreich <richgel99@gmail.com>
|
||||
and licenced under the public domain
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <utest.h>
|
||||
|
||||
#include "../Vec.h"
|
||||
|
||||
namespace quicktex::tests {
|
||||
|
||||
// region Vec_float unit tests
|
||||
UTEST(Vec_float, add) {
|
||||
auto a = Vec<float, 3>{1.0f, 1.5f, 2.0f};
|
||||
auto b = Vec<float, 3>{2.0f, -2.5f, 3.0f};
|
||||
auto expected = Vec<float, 3>{3.0f, -1.0f, 5.0f};
|
||||
float diff = ((a + b) - expected).sqr_mag();
|
||||
|
||||
ASSERT_LT(diff, 0.01f);
|
||||
}
|
||||
|
||||
UTEST(Vec_float, sub) {
|
||||
auto a = Vec<float, 3>{1.0f, 1.5f, 2.0f};
|
||||
auto b = Vec<float, 3>{3.0f, 1.5f, 1.0f};
|
||||
auto expected = Vec<float, 3>{-2.0f, 0.0f, 1.0f};
|
||||
float diff = ((a - b) - expected).sqr_mag();
|
||||
|
||||
ASSERT_LT(diff, 0.01f);
|
||||
}
|
||||
|
||||
UTEST(Vec_float, mul) {
|
||||
auto a = Vec<float, 3>{1.0f, 1.5f, 2.0f};
|
||||
auto b = Vec<float, 3>{3.0f, 1.5f, 0.0f};
|
||||
auto expected = Vec<float, 3>{3.0f, 2.25f, 0.0f};
|
||||
float diff = ((a * b) - expected).sqr_mag();
|
||||
|
||||
ASSERT_LT(diff, 0.01f);
|
||||
}
|
||||
|
||||
UTEST(Vec_float, div) {
|
||||
auto a = Vec<float, 3>{1.0f, 1.5f, 2.0f};
|
||||
auto b = Vec<float, 3>{2.0f, 1.5f, 1.0f};
|
||||
auto expected = Vec<float, 3>{0.5f, 1.0f, 2.0f};
|
||||
float diff = ((a / b) - expected).sqr_mag();
|
||||
|
||||
ASSERT_LT(diff, 0.01f);
|
||||
}
|
||||
|
||||
UTEST(Vec_float, sum) {
|
||||
auto a = Vec<float, 5>{1.0f, 2.0f, 3.5f, 4.0f, -4.0f};
|
||||
auto diff = abs(a.sum() - 6.5f);
|
||||
|
||||
ASSERT_LT(diff, 0.01f);
|
||||
}
|
||||
|
||||
UTEST(Vec_float, dot) {
|
||||
auto a = Vec<float, 3>{1.0f, 1.5f, 2.0f};
|
||||
auto b = Vec<float, 3>{2.0f, 1.5f, 2.0f};
|
||||
auto diff = abs(a.dot(b) - 8.25f);
|
||||
|
||||
ASSERT_LT(diff, 0.01f);
|
||||
}
|
||||
|
||||
UTEST(Vec_float, abs) {
|
||||
auto a = Vec<float, 3>{1.0f, -5.0f, -1.0f};
|
||||
auto expected = Vec<float, 3>{1.0f, 5.0f, 1.0f};
|
||||
auto diff = (a.abs() - expected).sqr_mag();
|
||||
|
||||
ASSERT_LT(diff, 0.01f);
|
||||
}
|
||||
|
||||
UTEST(Vec_float, clamp) {
|
||||
auto a = Vec<float, 6>{-1, -1, -1, 1, 1, 1};
|
||||
auto low1 = Vec<float, 6>{-2, -0.5, -2, 0, 2, 0.5};
|
||||
auto high1 = Vec<float, 6>{-1.5, 0, 0, 0.5, 3, 2};
|
||||
auto result1 = a.clamp(low1, high1);
|
||||
auto expected1 = Vec<float, 6>{-1.5, -0.5, -1, 0.5, 2, 1};
|
||||
auto diff1 = (result1 - expected1).sqr_mag();
|
||||
|
||||
ASSERT_LT(diff1, 0.01f);
|
||||
|
||||
auto b = Vec<float, 6>{-1, -0.5, 0, 0.2, 0.5, 1};
|
||||
auto result2 = b.clamp(-0.8, 0.3);
|
||||
auto expected2 = Vec<float, 6>{-0.8, -0.5, 0, 0.2, 0.3, 0.3};
|
||||
auto diff2 = (result2 - expected2).sqr_mag();
|
||||
|
||||
ASSERT_LT(diff2, 0.01f);
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Vec_int unit tests
|
||||
UTEST(Vec_int, subscript) {
|
||||
auto a = Vec<int, 4>{1, 3, 1, 2};
|
||||
|
||||
ASSERT_EQ(a[0], 1);
|
||||
ASSERT_EQ(a[1], 3);
|
||||
ASSERT_EQ(a[2], 1);
|
||||
ASSERT_EQ(a[3], 2);
|
||||
|
||||
a[2] = 4;
|
||||
ASSERT_EQ(a[2], 4);
|
||||
}
|
||||
|
||||
UTEST(Vec_int, pack) {
|
||||
auto a = Vec<uint16_t, 3>{0x1F, 0x2A, 0x01};
|
||||
auto w = Vec<int, 3>{5, 6, 5};
|
||||
auto result = a.pack(w);
|
||||
|
||||
ASSERT_EQ(result, 0xFD41);
|
||||
}
|
||||
|
||||
UTEST(Vec_int, write) {
|
||||
std::array<int, 4> arr{1, 3, 1, 2};
|
||||
Vec<int, 4> a(arr);
|
||||
Vec<int, 4> expected{1, 3, 1, 2};
|
||||
|
||||
ASSERT_TRUE(a == expected);
|
||||
|
||||
std::array<int, 4> out{-1, -3, -1, -2};
|
||||
a.write(out.begin());
|
||||
|
||||
ASSERT_TRUE(out == arr);
|
||||
}
|
||||
// endregion
|
||||
} // namespace quicktex::tests
|
Loading…
Reference in New Issue