From bdb6daec821168497696108582d8dbcc81b0f80e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 6 Jan 2017 23:04:05 +0100 Subject: [PATCH] Math: new Half literal class. --- doc/types.dox | 14 +++ src/Magnum/Magnum.h | 3 + src/Magnum/Math/CMakeLists.txt | 1 + src/Magnum/Math/Half.h | 170 ++++++++++++++++++++++++++++++ src/Magnum/Math/Math.h | 2 + src/Magnum/Math/Packing.h | 2 +- src/Magnum/Math/Test/HalfTest.cpp | 156 ++++++++++++++++++++++++++- src/Magnum/PixelFormat.h | 2 +- src/Magnum/TextureFormat.h | 8 +- 9 files changed, 351 insertions(+), 7 deletions(-) create mode 100644 src/Magnum/Math/Half.h diff --git a/doc/types.dox b/doc/types.dox index 6b7c238ff..9e826daa1 100644 --- a/doc/types.dox +++ b/doc/types.dox @@ -52,6 +52,7 @@ refer to the same type). | @ref Int | 32bit signed | `int` | | @ref UnsignedLong | 64bit unsigned | | | @ref Long | 64bit signed | | +| @ref Half | 16bit | (*none*) | | @ref Float | 32bit | `float` | | @ref Double | 64bit | `double` | @@ -154,6 +155,19 @@ Float a = Math::sin(1.32457_radf); Complex b = Complex::rotation(60.0_degf); @endcode +There is also a @ref Half type for handling half-precision floating point +values. By design it doesn't support any arithmetic operations as they would be +done faster on single-precision, it's sole purpose is to make working with +half-float values easier. It provides either explicit constructors and +conversion operators from/to @ref Float and @ref UnsignedShort and you can also +use the @link Math::Literals::operator""_h() _h @endlink literal that is +provided in the @ref Math::Literals namespace: +@code +using namespace Math::Literals; + +Half a = 3.5_h; // 0x4300 internally +@endcode + @section types-other Other types Other types, which don't have their GLSL equivalent, are: diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 45261347e..c865c576b 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -212,6 +212,9 @@ typedef std::int64_t Long; /** @brief Float (32bit) */ typedef float Float; +/** @brief Half (16bit) */ +typedef Math::Half Half; + /** @brief Two-component float vector */ typedef Math::Vector2 Vector2; diff --git a/src/Magnum/Math/CMakeLists.txt b/src/Magnum/Math/CMakeLists.txt index 607815ffc..463a7cd48 100644 --- a/src/Magnum/Math/CMakeLists.txt +++ b/src/Magnum/Math/CMakeLists.txt @@ -35,6 +35,7 @@ set(MagnumMath_HEADERS DualQuaternion.h Frustum.h Functions.h + Half.h Math.h TypeTraits.h Matrix.h diff --git a/src/Magnum/Math/Half.h b/src/Magnum/Math/Half.h new file mode 100644 index 000000000..caeb82945 --- /dev/null +++ b/src/Magnum/Math/Half.h @@ -0,0 +1,170 @@ +#ifndef Magnum_Math_Half_h +#define Magnum_Math_Half_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Math::Half, literal @link Magnum::Math::Literals::operator""_h() @endlink + */ + +#include "Magnum/Math/Packing.h" + +namespace Magnum { namespace Math { + +/** +@brief Half-precision float literal + +The purpose of this class is just to make specifying and printing of half-float +literals easier. By design no arithmetic operations are supported, as majority +of CPUs has no dedicated instructions for half-precision floats and thus it is +faster to just use regular single-precision @ref Magnum::Float "Float". See +[Wikipedia](https://en.wikipedia.org/wiki/Half-precision_floating-point_format) +for more information about half floats. + +The class provides explicit conversion from and to @ref Magnum::Float "Float", +equality comparison with correct treatment of NaN values, promotion and +negation operator, an @link Literals::operator""_h() operator""_h() @endlink +literal and an @ref operator<<(Debug&, Half) debug operator. Internally the class uses +@ref packHalf() and @ref unpackHalf(). Example usage: +@code +using namespace Math::Literals; + +Half a = 3.14159_h; +Debug{} << a; // Prints 3.14159 +Debug{} << Float(a); // Prints 3.14159 +Debug{} << UnsignedShort(a); // Prints 25675 +@endcode + +Note that it is also possible to use this type inside @ref Vector classes, +though, again, only for passing data around and converting them, without any +arithmetic operations: +@code +Math::Vector3 a{3.14159_h, -1.4142_h, 1.618_h}; +Vector3 b{a}; // converts to 32-bit floats +Debug{} << a; // prints {3.14159, -1.4142, 1.618} +Debug{} << Math::Vector3{a}; // prints {16968, 48552, 15993} +@endcode + +@see @ref Magnum::Half +*/ +class Half { + public: + /** + * @brief Default constructor + * + * Creates a zero value. + */ + constexpr /*implicit*/ Half(ZeroInitT = ZeroInit) noexcept: _data{} {} + + /** @brief Construct a half value from underlying 16-bit representation */ + constexpr explicit Half(UnsignedShort data) noexcept: _data{data} {} + + /** + * @brief Construct a half value from 32-bit float representation + * + * @see @ref packHalf() + */ + explicit Half(Float value) noexcept: _data{packHalf(value)} {} + + /** @brief Construct without initializing the contents */ + explicit Half(NoInitT) noexcept {} + + /** + * @brief Equality comparison + * + * Returns `false` if one of the values is half-float representation of + * NaN, otherwise does bitwise comparison. + */ + constexpr bool operator==(Half other) const { + return ((( _data & 0x7c00) == 0x7c00 && ( _data & 0x03ff)) || + ((other._data & 0x7c00) == 0x7c00 && (other._data & 0x03ff))) ? + false : _data == other._data; + } + + /** + * @brief Non-equality comparison + * + * Simply negates the result of @ref operator==(). + */ + constexpr bool operator!=(Half other) const { + return !operator==(other); + } + + /** + * @brief Promotion + * + * Returns the value as-is. + */ + constexpr Half operator+() const { return *this; } + + /** @brief Negation */ + constexpr Half operator-() const { + return Half{UnsignedShort(_data ^ (1 << 15))}; + } + + /** + * @brief Conversion to underlying representation + * + * @see @ref data() + */ + constexpr explicit operator UnsignedShort() const { return _data; } + + /** + * @brief Conversion to 32-bit float representation + * + * @see @ref unpackHalf() + */ + explicit operator Float() const { return unpackHalf(_data); } + + /** + * @brief Underlying representation + * + * @see @ref operator UnsignedShort() + */ + constexpr UnsignedShort data() const { return _data; } + + private: + UnsignedShort _data; +}; + +namespace Literals { + +/** @relatesalso Magnum::Math::Half +@brief Half-float literal + +See @ref Half for more information. +*/ +inline Half operator "" _h(long double value) { return Half(Float(value)); } + +} + +/** @debugoperator{Magnum::Math::Half} */ +inline Corrade::Utility::Debug& operator<<(Corrade::Utility::Debug& debug, Half value) { + return debug << Float(value); +} + +}} + +#endif diff --git a/src/Magnum/Math/Math.h b/src/Magnum/Math/Math.h index a5738c3b1..f7f7ec510 100644 --- a/src/Magnum/Math/Math.h +++ b/src/Magnum/Math/Math.h @@ -78,6 +78,8 @@ template class, class> class Unit; template class Deg; template class Rad; +class Half; + template class Vector; template class Vector2; template class Vector3; diff --git a/src/Magnum/Math/Packing.h b/src/Magnum/Math/Packing.h index c4641edfc..e66426f2f 100644 --- a/src/Magnum/Math/Packing.h +++ b/src/Magnum/Math/Packing.h @@ -197,7 +197,7 @@ that rounding mode is unspecified in order to save some cycles. Implementation based on CC0 / public domain code by *Fabian Giesen*, https://fgiesen.wordpress.com/2012/03/28/half-to-float-done-quic/ . -@see @ref unpackHalf() +@see @ref unpackHalf(), @ref Half */ MAGNUM_EXPORT UnsignedShort packHalf(Float value); diff --git a/src/Magnum/Math/Test/HalfTest.cpp b/src/Magnum/Math/Test/HalfTest.cpp index 2c8f88c85..f22b896cf 100644 --- a/src/Magnum/Math/Test/HalfTest.cpp +++ b/src/Magnum/Math/Test/HalfTest.cpp @@ -23,9 +23,10 @@ DEALINGS IN THE SOFTWARE. */ +#include #include -#include "Magnum/Math/Packing.h" +#include "Magnum/Math/Half.h" #include "Magnum/Math/Vector3.h" namespace Magnum { namespace Math { namespace Test { @@ -44,6 +45,21 @@ struct HalfTest: Corrade::TestSuite::Tester { void pack1kNaive(); void pack1kTable(); + void constructDefault(); + void constructValue(); + void constructData(); + void constructNoInit(); + void constructCopy(); + + void compare(); + void compareNaN(); + + void promotion(); + void negation(); + + void literal(); + void debug(); + private: /* Naive / ground-truth packing helpers */ UnsignedShort packNaive(Float value); @@ -77,6 +93,22 @@ HalfTest::HalfTest() { &HalfTest::pack1kNaive, &HalfTest::pack1kTable}, 100); + addTests({&HalfTest::constructDefault, + &HalfTest::constructValue, + &HalfTest::constructData, + &HalfTest::constructNoInit, + &HalfTest::constructCopy, + + &HalfTest::compare}); + + addRepeatedTests({&HalfTest::compareNaN}, 65536); + + addTests({&HalfTest::promotion, + &HalfTest::negation, + + &HalfTest::literal, + &HalfTest::debug}); + /* Calculate tables for table-based benchmark */ _mantissaTable[0] = 0; for(std::size_t i = 1; i != 1024; ++i) @@ -466,6 +498,128 @@ void HalfTest::unpack1kTable() { CORRADE_VERIFY(out); } +void HalfTest::constructDefault() { + constexpr Half a; + CORRADE_COMPARE(Float(a), 0.0f); + CORRADE_COMPARE(UnsignedShort(a), 0); + CORRADE_COMPARE(a.data(), 0); + + constexpr Half b{ZeroInit}; + CORRADE_COMPARE(Float(b), 0.0f); + CORRADE_COMPARE(UnsignedShort(b), 0); + CORRADE_COMPARE(b.data(), 0); + + CORRADE_VERIFY((std::is_nothrow_default_constructible::value)); + CORRADE_VERIFY((std::is_nothrow_constructible::value)); +} + +void HalfTest::constructValue() { + Half a{3.5f}; + CORRADE_COMPARE(Float(a), 3.5f); + CORRADE_COMPARE(UnsignedShort(a), 0x4300); + CORRADE_COMPARE(a.data(), 0x4300); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void HalfTest::constructData() { + constexpr Half a{UnsignedShort(0x4300)}; + CORRADE_COMPARE(Float(a), 3.5f); + CORRADE_COMPARE(UnsignedShort(a), 0x4300); + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit conversion is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void HalfTest::constructNoInit() { + Half a{3.5f}; + new(&a) Half{NoInit}; + { + #if defined(__GNUC__) && __GNUC__*100 + __GNUC_MINOR__ >= 601 && __OPTIMIZE__ + CORRADE_EXPECT_FAIL("GCC 6.1+ misoptimizes and overwrites the value."); + #endif + CORRADE_COMPARE(a, Half{3.5f}); + } + + CORRADE_VERIFY((std::is_nothrow_constructible::value)); + + /* Implicit construction is not allowed */ + CORRADE_VERIFY(!(std::is_convertible::value)); +} + +void HalfTest::constructCopy() { + constexpr Half a{UnsignedShort(0x4300)}; + constexpr Half b{a}; + + CORRADE_COMPARE(b, Half{3.5f}); + + CORRADE_VERIFY(std::is_nothrow_copy_constructible::value); + CORRADE_VERIFY(std::is_nothrow_copy_assignable::value); +} + +void HalfTest::compare() { + constexpr Half a{UnsignedShort(0x4300)}; + constexpr Half b{UnsignedShort(0x4301)}; + + CORRADE_VERIFY(a == a); + CORRADE_VERIFY(a != b); +} + +void HalfTest::compareNaN() { + Half a{UnsignedShort(testCaseRepeatId())}; + Float fa(a); + + /* If the values are NaNs as Float, they should also not compare as Half. + Interestingly enough, writing Float(a) != Float(a) in expected confused + the optimizer in Emscripten on higher optimization levels and it was + always saying `true` for values >= 32769 (which is *not* right). */ + CORRADE_COMPARE(a != a, fa != fa); +} + +void HalfTest::promotion() { + constexpr Half a{UnsignedShort(0x4300)}; + constexpr Half b = +a; + + CORRADE_COMPARE(b, a); +} + +void HalfTest::negation() { + constexpr Half a{UnsignedShort(0x4300)}; + constexpr Half b = -a; + + CORRADE_COMPARE(b, Half{-3.5f}); + CORRADE_COMPARE(-b, a); +} + +void HalfTest::literal() { + using namespace Literals; + + Half a = 3.5_h; + CORRADE_COMPARE(a, Half{UnsignedShort(0x4300)}); + CORRADE_COMPARE(a, Half{3.5f}); +} + +void HalfTest::debug() { + using namespace Literals; + + std::ostringstream out; + + Debug{&out} << -3.64_h << Half{Constants::inf()} + << Math::Vector3{3.14159_h, -1.4142_h, 1.618_h}; + #ifdef _MSC_VER + CORRADE_COMPARE(out.str(), "-3.64063 inf Vector(3.14063, -1.41406, 1.61816)\n"); + #elif defined(CORRADE_TARGET_ANDROID) + CORRADE_COMPARE(out.str(), "-3.64062 Inf Vector(3.14062, -1.41406, 1.61816)\n"); + #else + CORRADE_COMPARE(out.str(), "-3.64062 inf Vector(3.14062, -1.41406, 1.61816)\n"); + #endif +} + }}} CORRADE_TEST_MAIN(Magnum::Math::Test::HalfTest) diff --git a/src/Magnum/PixelFormat.h b/src/Magnum/PixelFormat.h index 3a1d72bb1..11eb53c8b 100644 --- a/src/Magnum/PixelFormat.h +++ b/src/Magnum/PixelFormat.h @@ -375,7 +375,7 @@ enum class PixelType: GLenum { /** * Each component half float. - * @see @ref Math::packHalf(), @ref Math::unpackHalf() + * @see @ref Half, @ref Math::packHalf(), @ref Math::unpackHalf() * @requires_gl30 Extension @extension{ARB,half_float_pixel} * @requires_gles30 For texture data only, extension * @es_extension2{OES,texture_half_float,OES_texture_float} in OpenGL diff --git a/src/Magnum/TextureFormat.h b/src/Magnum/TextureFormat.h index 90c0acb95..def42c835 100644 --- a/src/Magnum/TextureFormat.h +++ b/src/Magnum/TextureFormat.h @@ -513,7 +513,7 @@ enum class TextureFormat: GLenum { /** * Red component, half float. - * @see @ref Math::packHalf(), @ref Math::unpackHalf() + * @ref Half, @ref Math::packHalf(), @ref Math::unpackHalf() * @requires_gl30 Extension @extension{ARB,texture_rg} and @extension{ARB,texture_float} * @requires_gles30 Only normalized integral formats are available in * OpenGL ES 2.0. @@ -524,7 +524,7 @@ enum class TextureFormat: GLenum { /** * Red and green component, each half float. - * @see @ref Math::packHalf(), @ref Math::unpackHalf() + * @ref Half, @ref Math::packHalf(), @ref Math::unpackHalf() * @requires_gl30 Extension @extension{ARB,texture_rg} and @extension{ARB,texture_float} * @requires_gles30 Only normalized integral formats are available in * OpenGL ES 2.0. @@ -535,7 +535,7 @@ enum class TextureFormat: GLenum { /** * RGB, each component half float. - * @see @ref Math::packHalf(), @ref Math::unpackHalf() + * @ref Half, @ref Math::packHalf(), @ref Math::unpackHalf() * @requires_gl30 Extension @extension{ARB,texture_float} * @requires_gles30 Only normalized integral formats are available in * OpenGL ES 2.0. @@ -546,7 +546,7 @@ enum class TextureFormat: GLenum { /** * RGBA, each component half float. - * @see @ref Math::packHalf(), @ref Math::unpackHalf() + * @ref Half, @ref Math::packHalf(), @ref Math::unpackHalf() * @requires_gl30 Extension @extension{ARB,texture_float} * @requires_gles30 Only normalized integral formats are available in * OpenGL ES 2.0.