From 4f9e06cb0e211501387a8ce62b0714e9702aa56f Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Thu, 28 May 2026 09:10:33 +0800 Subject: [PATCH 1/5] Add basic expressions --- src/tenvex/internal/add.h | 28 +++++++++++++++ src/tenvex/internal/add_impl.hpp | 31 ++++++++++++++++ src/tenvex/internal/cross.h | 28 +++++++++++++++ src/tenvex/internal/cross_impl.hpp | 47 +++++++++++++++++++++++++ src/tenvex/internal/defines.h | 12 +++++++ src/tenvex/internal/dot.h | 31 ++++++++++++++++ src/tenvex/internal/dot_impl.hpp | 37 +++++++++++++++++++ src/tenvex/internal/expression.h | 47 +++++++++++++++++++++++++ src/tenvex/internal/expression_impl.hpp | 31 ++++++++++++++++ src/tenvex/internal/magnitude.h | 27 ++++++++++++++ src/tenvex/internal/magnitude_impl.hpp | 30 ++++++++++++++++ src/tenvex/internal/mul.h | 33 +++++++++++++++++ src/tenvex/internal/mul_impl.hpp | 37 +++++++++++++++++++ src/tenvex/internal/norm.h | 27 ++++++++++++++ src/tenvex/internal/norm_impl.hpp | 36 +++++++++++++++++++ src/tenvex/internal/scalar.h | 25 +++++++++++++ src/tenvex/internal/scalar_impl.hpp | 26 ++++++++++++++ src/tenvex/internal/sub.h | 28 +++++++++++++++ src/tenvex/internal/sub_impl.hpp | 28 +++++++++++++++ src/tenvex/tenvex.h | 9 +++++ src/tenvex/vec2.h | 10 ++++++ src/tenvex/vec3.h | 10 ++++++ src/tenvex/vec4.h | 36 +++++++++++++++++++ 23 files changed, 654 insertions(+) create mode 100644 src/tenvex/internal/add.h create mode 100644 src/tenvex/internal/add_impl.hpp create mode 100644 src/tenvex/internal/cross.h create mode 100644 src/tenvex/internal/cross_impl.hpp create mode 100644 src/tenvex/internal/defines.h create mode 100644 src/tenvex/internal/dot.h create mode 100644 src/tenvex/internal/dot_impl.hpp create mode 100644 src/tenvex/internal/expression.h create mode 100644 src/tenvex/internal/expression_impl.hpp create mode 100644 src/tenvex/internal/magnitude.h create mode 100644 src/tenvex/internal/magnitude_impl.hpp create mode 100644 src/tenvex/internal/mul.h create mode 100644 src/tenvex/internal/mul_impl.hpp create mode 100644 src/tenvex/internal/norm.h create mode 100644 src/tenvex/internal/norm_impl.hpp create mode 100644 src/tenvex/internal/scalar.h create mode 100644 src/tenvex/internal/scalar_impl.hpp create mode 100644 src/tenvex/internal/sub.h create mode 100644 src/tenvex/internal/sub_impl.hpp create mode 100644 src/tenvex/tenvex.h create mode 100644 src/tenvex/vec2.h create mode 100644 src/tenvex/vec3.h create mode 100644 src/tenvex/vec4.h diff --git a/src/tenvex/internal/add.h b/src/tenvex/internal/add.h new file mode 100644 index 0000000..f6d52fa --- /dev/null +++ b/src/tenvex/internal/add.h @@ -0,0 +1,28 @@ +#pragma once + +#include "expression.h" + +namespace tnvx +{ + +template +struct Add : Expr> +{ + inline + Add(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + +private: + tnvx_type_storage _l; + tnvx_type_storage _r; +}; + +template +[[nodiscard]] inline +Add operator+(const Expr& l, const Expr& r) noexcept; + +} + +#include "add_impl.hpp" diff --git a/src/tenvex/internal/add_impl.hpp b/src/tenvex/internal/add_impl.hpp new file mode 100644 index 0000000..0267546 --- /dev/null +++ b/src/tenvex/internal/add_impl.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "add.h" + +namespace tnvx +{ + +template +Add::Add(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept + : _l(l) + , _r(r) +{ + +} + +template +TNVX_INLINE +__m128 Add::eval() const noexcept +{ + return _mm_add_ps(_l.eval(), _r.eval()); + +} + +template +TNVX_INLINE +Add operator+(const Expr& l, const Expr& r) noexcept +{ + return { l.self(), r.self() }; +} + +} // namespace tnvx diff --git a/src/tenvex/internal/cross.h b/src/tenvex/internal/cross.h new file mode 100644 index 0000000..c7b3e94 --- /dev/null +++ b/src/tenvex/internal/cross.h @@ -0,0 +1,28 @@ +#pragma once + +#include "expression.h" + +namespace tnvx +{ + +template +struct Cross : Expr> +{ + inline + Cross(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + +private: + tnvx_type_storage _l; + tnvx_type_storage _r; +}; + +template +[[nodiscard]] inline +Cross cross(const Expr& l, const Expr& r) noexcept; + +} + +#include "cross_impl.hpp" diff --git a/src/tenvex/internal/cross_impl.hpp b/src/tenvex/internal/cross_impl.hpp new file mode 100644 index 0000000..f0a45c6 --- /dev/null +++ b/src/tenvex/internal/cross_impl.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "cross.h" + +namespace tnvx +{ + +template +TNVX_INLINE +Cross::Cross(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept + : _l(l) + , _r(r) +{ +} + +template +TNVX_INLINE +__m128 Cross::eval() const noexcept +{ + __m128 a = _l.eval(); + __m128 b = _r.eval(); + + // [ay, az, ax, 0] + __m128 a_yzx = _mm_shuffle_ps(a, a, _MM_SHUFFLE(3, 0, 2, 1)); + // [by, bz, bx, 0] + __m128 b_yzx = _mm_shuffle_ps(b, b, _MM_SHUFFLE(3, 0, 2, 1)); + + // [ay*bz, az*bx, ax*by, 0] + __m128 mul0 = _mm_mul_ps(a, b_yzx); + // [az*by, ax*bz, ay*bx, 0] + __m128 mul1 = _mm_mul_ps(a_yzx, b); + + // [ay*bz - az*by, az*bx - ax*bz, ax*by - ay*bx, 0] + __m128 sub = _mm_sub_ps(mul0, mul1); + + // shuffle обратно в [x, y, z, 0] + return _mm_shuffle_ps(sub, sub, _MM_SHUFFLE(3, 0, 2, 1)); +} + +template +Cross cross(const Expr& l, const Expr& r) noexcept +{ + return { l.self(), r.self() }; +} + + +} diff --git a/src/tenvex/internal/defines.h b/src/tenvex/internal/defines.h new file mode 100644 index 0000000..28f1b98 --- /dev/null +++ b/src/tenvex/internal/defines.h @@ -0,0 +1,12 @@ +#pragma once + +#if defined(_MSC_VER) +#define TNVX_INLINE __forceinline +#define TNVX_RESTRICT __restrict +#elif defined(__GNUC__) || defined(__clang__) +#define TNVX_INLINE __attribute__((always_inline)) inline +#define TNVX_RESTRICT __restrict__ +#else +#define TNVX_INLINE inline +#define TNVX_RESTRICT +#endif diff --git a/src/tenvex/internal/dot.h b/src/tenvex/internal/dot.h new file mode 100644 index 0000000..974067b --- /dev/null +++ b/src/tenvex/internal/dot.h @@ -0,0 +1,31 @@ +#pragma once + +#include "expression.h" +#include "scalar.h" +#include "../vec4.h" + +namespace tnvx +{ + +template +requires (!std::same_as && !std::same_as) +struct Dot : Expr +{ + inline + Dot(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + +private: + tnvx_type_storage _l; + tnvx_type_storage _r; +}; + +template +[[nodiscard]] inline +Dot dot(const Expr& l, const Expr& r) noexcept; + +} + +#include "dot_impl.hpp" diff --git a/src/tenvex/internal/dot_impl.hpp b/src/tenvex/internal/dot_impl.hpp new file mode 100644 index 0000000..ebbd27f --- /dev/null +++ b/src/tenvex/internal/dot_impl.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "dot.h" + +namespace tnvx +{ + +template +TNVX_INLINE +Dot::Dot(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept + : _l(l) + , _r(r) +{ +} + +template +TNVX_INLINE +__m128 Dot::eval() const noexcept +{ + __m128 a = _l.eval(); + __m128 b = _r.eval(); + + __m128 mul = _mm_mul_ps(a, b); + __m128 shuf = _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 sum = _mm_add_ps(mul, shuf); + shuf = _mm_movehl_ps(shuf, sum); + sum = _mm_add_ss(sum, shuf); + return _mm_shuffle_ps(sum, sum, 0); +} + +template +Dot dot(const Expr& l, const Expr& r) noexcept +{ + return { l.self(), r.self() }; +} + +} diff --git a/src/tenvex/internal/expression.h b/src/tenvex/internal/expression.h new file mode 100644 index 0000000..14cccd5 --- /dev/null +++ b/src/tenvex/internal/expression.h @@ -0,0 +1,47 @@ +#pragma once + +#include "defines.h" + +#include +#include +#include + +namespace tnvx +{ + +template +struct Expr +{ + [[nodiscard]] inline + const Derived& self() const noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + + [[nodiscard]] inline + float operator[](size_t i) const noexcept; +}; + +template +concept expression = requires(T a) +{ + { a.eval() } -> std::same_as<__m128>; +} +&& std::derived_from>; + +class Scalar; + +template +concept vector_expression = expression && !std::same_as; + +template +constexpr bool tnvx_store_by_value = std::is_trivially_copyable_v + && std::is_trivially_destructible_v + && sizeof(T) <= 16; + +template +using tnvx_type_storage = std::conditional_t, T, const T&>; + +} // namespace tnvx + +#include "expression_impl.hpp" diff --git a/src/tenvex/internal/expression_impl.hpp b/src/tenvex/internal/expression_impl.hpp new file mode 100644 index 0000000..c52d41a --- /dev/null +++ b/src/tenvex/internal/expression_impl.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "expression.h" + +namespace tnvx +{ + +template +TNVX_INLINE +const Derived& Expr::self() const noexcept +{ + return static_cast(*this); +} + +template +TNVX_INLINE +__m128 Expr::eval() const noexcept +{ + return self().eval(); +} + +template +TNVX_INLINE +float Expr::operator[](size_t i) const noexcept +{ + alignas(16) float tmp[4]; + _mm_store_ps(tmp, eval()); + return tmp[i]; +} + +} // namespace tnvx \ No newline at end of file diff --git a/src/tenvex/internal/magnitude.h b/src/tenvex/internal/magnitude.h new file mode 100644 index 0000000..8a30ffa --- /dev/null +++ b/src/tenvex/internal/magnitude.h @@ -0,0 +1,27 @@ +#pragma once + +#include "expression.h" + +namespace tnvx +{ + +template +struct Magnitude : Expr> +{ + inline + Magnitude(const E& TNVX_RESTRICT e) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + +private: + tnvx_type_storage _e; +}; + +template +[[nodiscard]] inline +Magnitude magnitude(const Expr e) noexcept; + +} + +#include "magnitude_impl.hpp" diff --git a/src/tenvex/internal/magnitude_impl.hpp b/src/tenvex/internal/magnitude_impl.hpp new file mode 100644 index 0000000..5d9db46 --- /dev/null +++ b/src/tenvex/internal/magnitude_impl.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "magnitude.h" +#include "dot.h" + +namespace tnvx +{ + +template +TNVX_INLINE +Magnitude::Magnitude(const E& TNVX_RESTRICT e) noexcept + : _e(e) +{ +} + +template +TNVX_INLINE +__m128 Magnitude::eval() const noexcept +{ + return _mm_sqrt_ps(Dot(_e, _e).eval()); +} + +template +TNVX_INLINE +Magnitude magnitude(const Expr e) noexcept +{ + return { e.self() }; +} + +} diff --git a/src/tenvex/internal/mul.h b/src/tenvex/internal/mul.h new file mode 100644 index 0000000..50b35ff --- /dev/null +++ b/src/tenvex/internal/mul.h @@ -0,0 +1,33 @@ +#pragma once + +#include "expression.h" +#include "scalar.h" + +namespace tnvx +{ + +template +struct Mul : Expr> +{ + inline + Mul(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + +private: + tnvx_type_storage _l; + tnvx_type_storage _r; +}; + +template +[[nodiscard]] inline +Mul operator*(const Expr& l, float r) noexcept; + +template +[[nodiscard]] inline +Mul operator*(float l, const Expr& r) noexcept; + +} + +#include "mul_impl.hpp" diff --git a/src/tenvex/internal/mul_impl.hpp b/src/tenvex/internal/mul_impl.hpp new file mode 100644 index 0000000..60f31a9 --- /dev/null +++ b/src/tenvex/internal/mul_impl.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "mul.h" + +namespace tnvx +{ + +template +TNVX_INLINE +inline Mul::Mul(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept + : _l(l) + , _r(r) +{ +} + +template +TNVX_INLINE +__m128 Mul::eval() const noexcept +{ + return _mm_mul_ps(_l.eval(), _r.eval()); +} + +template +TNVX_INLINE +Mul operator*(const Expr& l, float r) noexcept +{ + return { l.self(), Scalar(r) }; +} + +template +TNVX_INLINE +Mul operator*(float l, const Expr& r) noexcept +{ + return { Scalar(l), r.self() }; +} + +} diff --git a/src/tenvex/internal/norm.h b/src/tenvex/internal/norm.h new file mode 100644 index 0000000..8882029 --- /dev/null +++ b/src/tenvex/internal/norm.h @@ -0,0 +1,27 @@ +#pragma once + +#include "expression.h" + +namespace tnvx +{ + +template +struct Norm : Expr> +{ + inline + Norm(const E& TNVX_RESTRICT e) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + +private: + tnvx_type_storage _e; +}; + +template +[[nodiscard]] inline +Norm norm(const Expr e) noexcept; + +} + +#include "norm_impl.hpp" diff --git a/src/tenvex/internal/norm_impl.hpp b/src/tenvex/internal/norm_impl.hpp new file mode 100644 index 0000000..215e9bf --- /dev/null +++ b/src/tenvex/internal/norm_impl.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include "norm.h" +#include "dot.h" + +namespace tnvx +{ + +template +TNVX_INLINE +Norm::Norm(const E& TNVX_RESTRICT e) noexcept + : _e(e) +{ +} + +template +TNVX_INLINE +__m128 Norm::eval() const noexcept +{ + __m128 data = _e.eval(); + __m128 mul = _mm_mul_ps(data, data); + __m128 shuf = _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 sum = _mm_add_ps(mul, shuf); + shuf = _mm_movehl_ps(shuf, sum); + sum = _mm_add_ss(sum, shuf); + __m128 len = _mm_shuffle_ps(sum, sum, 0); + return _mm_div_ps(data, len); +} + +template +Norm norm(const Expr e) noexcept +{ + return { e.self() }; +} + +} diff --git a/src/tenvex/internal/scalar.h b/src/tenvex/internal/scalar.h new file mode 100644 index 0000000..2fc298c --- /dev/null +++ b/src/tenvex/internal/scalar.h @@ -0,0 +1,25 @@ +#pragma once + +#include "expression.h" + +namespace tnvx +{ + +struct Scalar : Expr +{ + inline + Scalar(float value) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + + [[nodiscard]] inline + float operator[](size_t) const noexcept; + +private: + float _value; +}; + +} + +#include "scalar_impl.hpp" diff --git a/src/tenvex/internal/scalar_impl.hpp b/src/tenvex/internal/scalar_impl.hpp new file mode 100644 index 0000000..f4804c7 --- /dev/null +++ b/src/tenvex/internal/scalar_impl.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include "scalar.h" + +namespace tnvx +{ + +TNVX_INLINE +Scalar::Scalar(float value) noexcept + : _value(value) +{ +} + +[[nodiscard]] TNVX_INLINE +__m128 Scalar::eval() const noexcept +{ + return _mm_set1_ps(_value); +} + +[[nodiscard]] TNVX_INLINE +float Scalar::operator[](size_t) const noexcept +{ + return _value; +} + +} diff --git a/src/tenvex/internal/sub.h b/src/tenvex/internal/sub.h new file mode 100644 index 0000000..a762069 --- /dev/null +++ b/src/tenvex/internal/sub.h @@ -0,0 +1,28 @@ +#pragma once + +#include "expression.h" + +namespace tnvx +{ + +template +struct Sub : Expr> +{ + inline + Sub(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept; + + [[nodiscard]] inline + __m128 eval() const noexcept; + +private: + tnvx_type_storage _l; + tnvx_type_storage _r; +}; + +template +[[nodiscard]] inline +Sub operator-(const Expr& l, const Expr& r) noexcept; + +} + +#include "sub_impl.hpp" diff --git a/src/tenvex/internal/sub_impl.hpp b/src/tenvex/internal/sub_impl.hpp new file mode 100644 index 0000000..cbf092e --- /dev/null +++ b/src/tenvex/internal/sub_impl.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "sub.h" + +namespace tnvx +{ + +template +tnvx::Sub::Sub(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept + : _l(l), _r(r) +{ +} + +template +TNVX_INLINE +__m128 Sub::eval() const noexcept +{ + return _mm_sub_ps(_l.eval(), _r.eval()); +} + +template +TNVX_INLINE +Sub operator-(const Expr& l, const Expr& r) noexcept +{ + return { l.self(), r.self() }; +} + +} diff --git a/src/tenvex/tenvex.h b/src/tenvex/tenvex.h new file mode 100644 index 0000000..a859cf1 --- /dev/null +++ b/src/tenvex/tenvex.h @@ -0,0 +1,9 @@ +#pragma once + +#include "vec2.h" +#include "vec3.h" +#include "vec4.h" + +#include "internal/add.h" +#include "internal/sub.h" +#include "internal/mul.h" diff --git a/src/tenvex/vec2.h b/src/tenvex/vec2.h new file mode 100644 index 0000000..e86b3ca --- /dev/null +++ b/src/tenvex/vec2.h @@ -0,0 +1,10 @@ +#pragma once + +namespace tnvx +{ + +struct vec2 +{ +}; + +} // namespace tenvex diff --git a/src/tenvex/vec3.h b/src/tenvex/vec3.h new file mode 100644 index 0000000..55e22eb --- /dev/null +++ b/src/tenvex/vec3.h @@ -0,0 +1,10 @@ +#pragma once + +namespace tnvx +{ + +struct vec3 +{ +}; + +} // namespace tenvex diff --git a/src/tenvex/vec4.h b/src/tenvex/vec4.h new file mode 100644 index 0000000..98560d5 --- /dev/null +++ b/src/tenvex/vec4.h @@ -0,0 +1,36 @@ +#pragma once + +#include "internal/expression.h" + +#include + +namespace tnvx +{ + +struct alignas(16) vec4 : public Expr +{ + vec4() = default; + + vec4(float x, float y, float z, float w); + + template + vec4(const Expr& expr); + + template + vec4& operator=(const Expr& expr); + + __m128 eval() const noexcept; + + float magnitude() const noexcept; + vec4 norm() const noexcept; + + float x() const noexcept; + float y() const noexcept; + float z() const noexcept; + float w() const noexcept; + +private: + __m128 _data; +}; + +} // namespace tenvex From b5022977eb91aa77a2a5d9d6a23da0e5a7b70274 Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Thu, 28 May 2026 11:04:20 +0800 Subject: [PATCH 2/5] Removed vec2 and vec3 for now, as their implementation is still unclear --- src/tenvex/tenvex.h | 2 -- src/tenvex/vec2.h | 10 ---------- src/tenvex/vec3.h | 10 ---------- 3 files changed, 22 deletions(-) delete mode 100644 src/tenvex/vec2.h delete mode 100644 src/tenvex/vec3.h diff --git a/src/tenvex/tenvex.h b/src/tenvex/tenvex.h index a859cf1..e891d73 100644 --- a/src/tenvex/tenvex.h +++ b/src/tenvex/tenvex.h @@ -1,7 +1,5 @@ #pragma once -#include "vec2.h" -#include "vec3.h" #include "vec4.h" #include "internal/add.h" diff --git a/src/tenvex/vec2.h b/src/tenvex/vec2.h deleted file mode 100644 index e86b3ca..0000000 --- a/src/tenvex/vec2.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace tnvx -{ - -struct vec2 -{ -}; - -} // namespace tenvex diff --git a/src/tenvex/vec3.h b/src/tenvex/vec3.h deleted file mode 100644 index 55e22eb..0000000 --- a/src/tenvex/vec3.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -namespace tnvx -{ - -struct vec3 -{ -}; - -} // namespace tenvex From 61e03f653fa60d2183eaf0fd58539c5160d96871 Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Thu, 28 May 2026 13:24:28 +0800 Subject: [PATCH 3/5] Add tests --- src/tests/vec4_tests.cpp | 178 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 src/tests/vec4_tests.cpp diff --git a/src/tests/vec4_tests.cpp b/src/tests/vec4_tests.cpp new file mode 100644 index 0000000..5186e30 --- /dev/null +++ b/src/tests/vec4_tests.cpp @@ -0,0 +1,178 @@ +#include "tenvex.h" + +#include "gtest/gtest.h" + +namespace tnvx +{ + +TEST(vec4, add) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 b = { 4.0f, 3.0f, 2.0f, 1.0f }; + vec4 check{ 5.0f, 5.0f, 5.0f, 5.0f }; + vec4 result = a + b; + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, sub) +{ + vec4 a = { 5.0f, 5.0f, 5.0f, 5.0f }; + vec4 b = { 4.0f, 3.0f, 2.0f, 1.0f }; + vec4 check = { 1.0f, 2.0f, 3.0f, 4.0f }; + vec4 result = a - b; + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, mult_a) +{ + vec4 a = { 1.0f, 2.0f, 0.0f, 0.0f }; + float b = 3.0f; + vec4 check = { 3.0f, 6.0f, 0.0f, 0.0f }; + vec4 result = a * b; + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, mult_b) +{ + float a = 2.0f; + vec4 b = { 1.0f, 0.0f, 2.0f, 0.0f }; + vec4 check = { 2.0f, 0.0f, 4.0f, 0.0f }; + vec4 result = a * b; + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, dot) +{ + vec4 a = { 1.0f, 0.0f, 3.0f, 0.0f }; + vec4 b = { 1.0f, 2.0f, 3.0f, 1.0f }; + float check = 10.0f; + float result = dot(a, b); + + EXPECT_FLOAT_EQ(check, result); +} + +TEST(vec4, cross) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f }; + vec4 check = { 0.0f, 0.0f, 1.0f, 0.0f }; + vec4 result = cross(a, b); + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, norm_a) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 check = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 result = norm(a); + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, norm_b) +{ + vec4 a = { 1.0f, 0.0f, 1.0f, 0.0f }; + vec4 check = { 0.707106f, 0.0f, 0.707106f, 0.0f }; + vec4 result = norm(a); + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, magnitude_a) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + float check = 1.0f; + float result = magnitude(a); + + EXPECT_FLOAT_EQ(check, result); +} + +TEST(vec4, magnitude_b) +{ + vec4 a = { 1.0f, 0.0f, 1.0f, 0.0f }; + float check = 1.4142135f; + float result = magnitude(a); + + EXPECT_FLOAT_EQ(check, result); +} + +TEST(vec4, expression_a) +{ + vec4 a = { 1.0f, 1.0f, 1.0f, 0.0f }; + vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f }; + vec4 c = { 1.15f, 0.0f, 11.0f, 0.0f }; + + vec4 check = { 3.45f, 0.0f, 33.0f, 0.0f }; + + vec4 result = dot(a, b) * 3.0f * c; + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, expression_b) +{ + vec4 a = { 1.0f, 1.0f, 1.0f, 0.0f }; + vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f }; + vec4 c = { 1.15f, 0.0f, 11.0f, 0.0f }; + + vec4 check = { 33.0f, 0.0f, -3.45f, 0.0f }; + + vec4 result = dot(a, b) * 3.0f * cross(b, c); + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, expression_c) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f }; + vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f }; + vec4 c = { 1.0f, 0.0f, 1.0f, 0.0f }; + + vec4 check = { 7.243f, 5.657f, 10.071f, 0.0f }; + + vec4 result = norm(a + b * 2.0f) * dot(b, c) + c * 3.0f; + + EXPECT_TRUE(approx_eq(check, result)); +} + +TEST(vec4, cross_anticommutative) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f }; + EXPECT_TRUE(approx_eq(cross(a, b), cross(b, a) * -1.0f)); +} + +TEST(vec4, dot_commutative) +{ + vec4 a = { 1.0f, 2.0f, 3.0f, 0.0f }; + vec4 b = { 4.0f, 5.0f, 6.0f, 0.0f }; + EXPECT_FLOAT_EQ(dot(a, b), dot(b, a)); +} + +TEST(vec4, norm_magnitude) +{ + vec4 a = { 3.0f, 1.0f, 4.0f, 0.0f }; + EXPECT_NEAR(magnitude(norm(a)), 1.0f, 1e-5f); +} + +TEST(vec4, dot_perpendicular) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 b = { 0.0f, 1.0f, 0.0f, 0.0f }; + EXPECT_FLOAT_EQ(dot(a, b), 0.0f); +} + +TEST(vec4, cross_parallel) +{ + vec4 a = { 1.0f, 0.0f, 0.0f, 0.0f }; + vec4 b = { 2.0f, 0.0f, 0.0f, 0.0f }; + vec4 check = { 0.0f, 0.0f, 0.0f, 0.0f }; + EXPECT_TRUE(approx_eq(cross(a, b), check)); +} + +} From 06a233c46482089db2c24494a881c2421a1d74da Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Thu, 28 May 2026 13:24:38 +0800 Subject: [PATCH 4/5] Update operations --- src/tenvex/internal/add_impl.hpp | 1 - src/tenvex/internal/cross_impl.hpp | 2 +- src/tenvex/internal/dot.h | 18 +++-- src/tenvex/internal/dot_impl.hpp | 25 ++++++- src/tenvex/internal/expression.h | 2 +- src/tenvex/internal/magnitude.h | 5 +- src/tenvex/internal/magnitude_impl.hpp | 11 ++- src/tenvex/internal/mul.h | 4 ++ src/tenvex/internal/mul_impl.hpp | 7 ++ src/tenvex/internal/norm.h | 2 +- src/tenvex/internal/norm_impl.hpp | 19 ++--- src/tenvex/tenvex.h | 5 ++ src/tenvex/vec4.h | 37 ++++++++-- src/tenvex/vec4_impl.hpp | 96 ++++++++++++++++++++++++++ 14 files changed, 207 insertions(+), 27 deletions(-) create mode 100644 src/tenvex/vec4_impl.hpp diff --git a/src/tenvex/internal/add_impl.hpp b/src/tenvex/internal/add_impl.hpp index 0267546..c124370 100644 --- a/src/tenvex/internal/add_impl.hpp +++ b/src/tenvex/internal/add_impl.hpp @@ -18,7 +18,6 @@ TNVX_INLINE __m128 Add::eval() const noexcept { return _mm_add_ps(_l.eval(), _r.eval()); - } template diff --git a/src/tenvex/internal/cross_impl.hpp b/src/tenvex/internal/cross_impl.hpp index f0a45c6..4f723ca 100644 --- a/src/tenvex/internal/cross_impl.hpp +++ b/src/tenvex/internal/cross_impl.hpp @@ -38,7 +38,7 @@ __m128 Cross::eval() const noexcept } template -Cross cross(const Expr& l, const Expr& r) noexcept +Cross cross(const Expr& l, const Expr& r) noexcept { return { l.self(), r.self() }; } diff --git a/src/tenvex/internal/dot.h b/src/tenvex/internal/dot.h index 974067b..1727731 100644 --- a/src/tenvex/internal/dot.h +++ b/src/tenvex/internal/dot.h @@ -1,15 +1,12 @@ #pragma once #include "expression.h" -#include "scalar.h" -#include "../vec4.h" namespace tnvx { template -requires (!std::same_as && !std::same_as) -struct Dot : Expr +struct Dot : Expr> { inline Dot(const L& TNVX_RESTRICT l, const R& TNVX_RESTRICT r) noexcept; @@ -17,6 +14,9 @@ struct Dot : Expr [[nodiscard]] inline __m128 eval() const noexcept; + [[nodiscard]] inline + operator float() const noexcept; + private: tnvx_type_storage _l; tnvx_type_storage _r; @@ -24,7 +24,15 @@ private: template [[nodiscard]] inline -Dot dot(const Expr& l, const Expr& r) noexcept; +Dot dot(const Expr& l, const Expr& r) noexcept; + +template +[[nodiscard]] inline +float operator*(const Dot& l, float r) noexcept; + +template +[[nodiscard]] inline +float operator*(float l, const Dot& r) noexcept; } diff --git a/src/tenvex/internal/dot_impl.hpp b/src/tenvex/internal/dot_impl.hpp index ebbd27f..bc3bce5 100644 --- a/src/tenvex/internal/dot_impl.hpp +++ b/src/tenvex/internal/dot_impl.hpp @@ -29,9 +29,32 @@ __m128 Dot::eval() const noexcept } template -Dot dot(const Expr& l, const Expr& r) noexcept +TNVX_INLINE +Dot::operator float() const noexcept +{ + return _mm_cvtss_f32(eval()); +} + +template +TNVX_INLINE +Dot dot(const Expr& l, const Expr& r) noexcept { return { l.self(), r.self() }; } +template +TNVX_INLINE +float operator*(const Dot& l, float r) noexcept +{ + return static_cast(l) * r; +} + +template +float operator*(float l, const Dot& r) noexcept +{ + return l * static_cast(r); +} + + + } diff --git a/src/tenvex/internal/expression.h b/src/tenvex/internal/expression.h index 14cccd5..4c3a61e 100644 --- a/src/tenvex/internal/expression.h +++ b/src/tenvex/internal/expression.h @@ -29,7 +29,7 @@ concept expression = requires(T a) } && std::derived_from>; -class Scalar; +struct Scalar; template concept vector_expression = expression && !std::same_as; diff --git a/src/tenvex/internal/magnitude.h b/src/tenvex/internal/magnitude.h index 8a30ffa..678c214 100644 --- a/src/tenvex/internal/magnitude.h +++ b/src/tenvex/internal/magnitude.h @@ -14,13 +14,16 @@ struct Magnitude : Expr> [[nodiscard]] inline __m128 eval() const noexcept; + [[nodiscard]] inline + operator float() const noexcept; + private: tnvx_type_storage _e; }; template [[nodiscard]] inline -Magnitude magnitude(const Expr e) noexcept; +Magnitude magnitude(const Expr& e) noexcept; } diff --git a/src/tenvex/internal/magnitude_impl.hpp b/src/tenvex/internal/magnitude_impl.hpp index 5d9db46..ce2723a 100644 --- a/src/tenvex/internal/magnitude_impl.hpp +++ b/src/tenvex/internal/magnitude_impl.hpp @@ -17,12 +17,19 @@ template TNVX_INLINE __m128 Magnitude::eval() const noexcept { - return _mm_sqrt_ps(Dot(_e, _e).eval()); + return _mm_sqrt_ss(Dot(_e, _e).eval()); } template TNVX_INLINE -Magnitude magnitude(const Expr e) noexcept +Magnitude::operator float() const noexcept +{ + return _mm_cvtss_f32(eval()); +} + +template +TNVX_INLINE +Magnitude magnitude(const Expr& e) noexcept { return { e.self() }; } diff --git a/src/tenvex/internal/mul.h b/src/tenvex/internal/mul.h index 50b35ff..3d08947 100644 --- a/src/tenvex/internal/mul.h +++ b/src/tenvex/internal/mul.h @@ -20,6 +20,10 @@ private: tnvx_type_storage _r; }; +//template +//[[nodiscard]] inline +//Mul operator*(const Expr& l, const Expr& r) noexcept; + template [[nodiscard]] inline Mul operator*(const Expr& l, float r) noexcept; diff --git a/src/tenvex/internal/mul_impl.hpp b/src/tenvex/internal/mul_impl.hpp index 60f31a9..8e6c50f 100644 --- a/src/tenvex/internal/mul_impl.hpp +++ b/src/tenvex/internal/mul_impl.hpp @@ -20,6 +20,13 @@ __m128 Mul::eval() const noexcept return _mm_mul_ps(_l.eval(), _r.eval()); } +//template +//TNVX_INLINE +//Mul operator*(const Expr& l, const Expr& r) noexcept +//{ +// return { l.self(), r.self() }; +//} + template TNVX_INLINE Mul operator*(const Expr& l, float r) noexcept diff --git a/src/tenvex/internal/norm.h b/src/tenvex/internal/norm.h index 8882029..dd0432d 100644 --- a/src/tenvex/internal/norm.h +++ b/src/tenvex/internal/norm.h @@ -20,7 +20,7 @@ private: template [[nodiscard]] inline -Norm norm(const Expr e) noexcept; +Norm norm(const Expr& e) noexcept; } diff --git a/src/tenvex/internal/norm_impl.hpp b/src/tenvex/internal/norm_impl.hpp index 215e9bf..d4af233 100644 --- a/src/tenvex/internal/norm_impl.hpp +++ b/src/tenvex/internal/norm_impl.hpp @@ -17,18 +17,19 @@ template TNVX_INLINE __m128 Norm::eval() const noexcept { - __m128 data = _e.eval(); - __m128 mul = _mm_mul_ps(data, data); - __m128 shuf = _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 3, 0, 1)); - __m128 sum = _mm_add_ps(mul, shuf); - shuf = _mm_movehl_ps(shuf, sum); - sum = _mm_add_ss(sum, shuf); - __m128 len = _mm_shuffle_ps(sum, sum, 0); - return _mm_div_ps(data, len); + __m128 data = _e.eval(); + __m128 mul = _mm_mul_ps(data, data); + __m128 shuf = _mm_shuffle_ps(mul, mul, _MM_SHUFFLE(2, 3, 0, 1)); + __m128 sum = _mm_add_ps(mul, shuf); + shuf = _mm_movehl_ps(shuf, sum); + sum = _mm_add_ss(sum, shuf); + __m128 len = _mm_shuffle_ps(sum, sum, 0); + __m128 sqrt_len = _mm_sqrt_ps(len); + return _mm_div_ps(data, sqrt_len); } template -Norm norm(const Expr e) noexcept +Norm norm(const Expr& e) noexcept { return { e.self() }; } diff --git a/src/tenvex/tenvex.h b/src/tenvex/tenvex.h index e891d73..c10d5f1 100644 --- a/src/tenvex/tenvex.h +++ b/src/tenvex/tenvex.h @@ -5,3 +5,8 @@ #include "internal/add.h" #include "internal/sub.h" #include "internal/mul.h" +#include "internal/dot.h" +#include "internal/cross.h" +#include "internal/norm.h" +#include "internal/magnitude.h" +#include "internal/scalar.h" diff --git a/src/tenvex/vec4.h b/src/tenvex/vec4.h index 98560d5..9554cd1 100644 --- a/src/tenvex/vec4.h +++ b/src/tenvex/vec4.h @@ -4,33 +4,60 @@ #include +#include + namespace tnvx { -struct alignas(16) vec4 : public Expr +struct alignas(16) vec4 : Expr { vec4() = default; - vec4(float x, float y, float z, float w); + inline + vec4(float x, float y, float z, float w) noexcept; template - vec4(const Expr& expr); + inline + vec4(const Expr& expr) noexcept; template - vec4& operator=(const Expr& expr); + inline + vec4& operator=(const Expr& expr) noexcept; + [[nodiscard]] inline __m128 eval() const noexcept; + [[nodiscard]] inline float magnitude() const noexcept; + + [[nodiscard]] inline vec4 norm() const noexcept; + [[nodiscard]] inline float x() const noexcept; + + [[nodiscard]] inline float y() const noexcept; + + [[nodiscard]] inline float z() const noexcept; + + [[nodiscard]] inline float w() const noexcept; + [[nodiscard]] inline + std::array data() const noexcept; + private: __m128 _data; }; -} // namespace tenvex +inline +bool operator==(const vec4& a, const vec4& b) noexcept; + +inline +bool approx_eq(const vec4& a, const vec4& b, float eps = 1e-3f) noexcept; + +} + +#include "vec4_impl.hpp" diff --git a/src/tenvex/vec4_impl.hpp b/src/tenvex/vec4_impl.hpp new file mode 100644 index 0000000..7078975 --- /dev/null +++ b/src/tenvex/vec4_impl.hpp @@ -0,0 +1,96 @@ +#pragma once + +#include "vec4.h" +#include "internal/magnitude.h" +#include "internal/norm.h" + +namespace tnvx +{ + +template +TNVX_INLINE +vec4::vec4(const Expr& expr) noexcept + : _data(expr.eval()) +{ +} + +template +TNVX_INLINE +vec4& vec4::operator=(const Expr& expr) noexcept +{ + _data = expr.eval(); + return *this; +} + +TNVX_INLINE +vec4::vec4(float x, float y, float z, float w) noexcept +{ + _data = _mm_set_ps(w, z, y, x); +} + +TNVX_INLINE +__m128 vec4::eval() const noexcept +{ + return _data; +} + +TNVX_INLINE +float vec4::magnitude() const noexcept +{ + return _mm_cvtss_f32(Magnitude(*this).eval()); +} + +TNVX_INLINE +vec4 vec4::norm() const noexcept +{ + return { Norm(*this) }; +} + +TNVX_INLINE +float vec4::x() const noexcept +{ + return _mm_cvtss_f32(_data); +} + +TNVX_INLINE +float vec4::y() const noexcept +{ + return _mm_cvtss_f32(_mm_shuffle_ps(_data, _data, _MM_SHUFFLE(1, 1, 1, 1))); +} + +TNVX_INLINE +float vec4::z() const noexcept +{ + return _mm_cvtss_f32(_mm_shuffle_ps(_data, _data, _MM_SHUFFLE(2, 2, 2, 2))); +} + +TNVX_INLINE +float vec4::w() const noexcept +{ + return _mm_cvtss_f32(_mm_shuffle_ps(_data, _data, _MM_SHUFFLE(3, 3, 3, 3))); +} + +TNVX_INLINE +std::array vec4::data() const noexcept +{ + std::array result; + _mm_storeu_ps(result.data(), _data); + return result; +} + +TNVX_INLINE +bool operator==(const vec4& a, const vec4& b) noexcept +{ + return _mm_movemask_ps(_mm_cmpeq_ps(a.eval(), b.eval())) == 0xF; +} + +TNVX_INLINE +bool approx_eq(const vec4& a, const vec4& b, float eps) noexcept +{ + __m128 epsilon = _mm_set1_ps(eps); + __m128 diff = _mm_sub_ps(a.eval(), b.eval()); + __m128 abs_diff = _mm_andnot_ps(_mm_set1_ps(-0.0f), diff); + return _mm_movemask_ps(_mm_cmplt_ps(abs_diff, epsilon)) == 0xF; +} + +} From 9875e72647d458dca8c623c8636914bc1097d6b1 Mon Sep 17 00:00:00 2001 From: Artem Zyktin Date: Thu, 28 May 2026 13:26:45 +0800 Subject: [PATCH 5/5] Update project configuration --- premake5.lua | 80 ++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/premake5.lua b/premake5.lua index 6279f82..68123c8 100644 --- a/premake5.lua +++ b/premake5.lua @@ -6,46 +6,6 @@ workspace "tenvex" outputdir = "%{cfg.system}/x64/%{cfg.buildcfg}" -project "tenvex" - location "build" - kind "None" - language "C++" - cppdialect "C++20" - staticruntime "on" - - targetdir ("bin/" .. outputdir .. "/output") - objdir ("bin/" .. outputdir .. "/intermediate") - - files { - "src/tenvex/**.h", - "src/tenvex/**.hpp", - } - - includedirs { - "src/tenvex", - } - - filter "configurations:debug" - runtime "Debug" - symbols "on" - optimize "off" - defines { "TENVEX_DEBUG" } - - filter "configurations:release" - runtime "Release" - symbols "on" - optimize "Speed" - defines { "TENVEX_RELEASE" } - - filter "system:windows" - systemversion "latest" - - filter "system:linux" - pic "on" - - filter "system:macosx" - pic "on" - project "gtest" location "build" kind "StaticLib" @@ -116,6 +76,46 @@ project "gtest_main" filter "system:windows" systemversion "latest" +project "tenvex" + location "build" + kind "None" + language "C++" + cppdialect "C++20" + staticruntime "on" + + targetdir ("bin/" .. outputdir .. "/output") + objdir ("bin/" .. outputdir .. "/intermediate") + + files { + "src/tenvex/**.h", + "src/tenvex/**.hpp", + } + + includedirs { + "src/tenvex", + } + + filter "configurations:debug" + runtime "Debug" + symbols "on" + optimize "off" + defines { "TENVEX_DEBUG" } + + filter "configurations:release" + runtime "Release" + symbols "on" + optimize "Speed" + defines { "TENVEX_RELEASE" } + + filter "system:windows" + systemversion "latest" + + filter "system:linux" + pic "on" + + filter "system:macosx" + pic "on" + project "tenvex_tests" location "build" kind "ConsoleApp"