From 12ff95197219200d833c4db3f240e29c77f1d217 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Tue, 26 Apr 2022 22:35:37 +0300 Subject: [PATCH] [Base] More flexible Xenos float16 conversion functions --- src/xenia/base/math.cc | 69 ------------------------------------------ src/xenia/base/math.h | 61 +++++++++++++++++++++++++++++++++++-- 2 files changed, 59 insertions(+), 71 deletions(-) delete mode 100644 src/xenia/base/math.cc diff --git a/src/xenia/base/math.cc b/src/xenia/base/math.cc deleted file mode 100644 index 661e1595e..000000000 --- a/src/xenia/base/math.cc +++ /dev/null @@ -1,69 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2014 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/base/math.h" - -namespace xe { - -// TODO(benvanik): replace with alternate implementation. -// XMConvertFloatToHalf -// Copyright (c) Microsoft Corporation. All rights reserved. -uint16_t float_to_half(float value) { - uint32_t Result; - uint32_t IValue = (reinterpret_cast(&value))[0]; - uint32_t Sign = (IValue & 0x80000000U) >> 16U; - IValue = IValue & 0x7FFFFFFFU; // Hack off the sign - if (IValue > 0x47FFEFFFU) { - // The number is too large to be represented as a half. Saturate to - // infinity. - Result = 0x7FFFU; - } else { - if (IValue < 0x38800000U) { - // The number is too small to be represented as a normalized half. - // Convert it to a denormalized value. - uint32_t Shift = 113U - (IValue >> 23U); - IValue = (0x800000U | (IValue & 0x7FFFFFU)) >> Shift; - } else { - // Rebias the exponent to represent the value as a normalized half. - IValue += 0xC8000000U; - } - Result = ((IValue + 0x0FFFU + ((IValue >> 13U) & 1U)) >> 13U) & 0x7FFFU; - } - return (uint16_t)(Result | Sign); -} - -// TODO(benvanik): replace with alternate implementation. -// XMConvertHalfToFloat -// Copyright (c) Microsoft Corporation. All rights reserved. -float half_to_float(uint16_t value) { - uint32_t Mantissa = (uint32_t)(value & 0x03FF); - uint32_t Exponent; - if ((value & 0x7C00) != 0) { - // The value is normalized - Exponent = (uint32_t)((value >> 10) & 0x1F); - } else if (Mantissa != 0) { - // The value is denormalized - // Normalize the value in the resulting float - Exponent = 1; - do { - Exponent--; - Mantissa <<= 1; - } while ((Mantissa & 0x0400) == 0); - Mantissa &= 0x03FF; - } else { - // The value is zero - Exponent = (uint32_t)-112; - } - uint32_t Result = ((value & 0x8000) << 16) | // Sign - ((Exponent + 112) << 23) | // Exponent - (Mantissa << 13); // Mantissa - return *reinterpret_cast(&Result); -} - -} // namespace xe diff --git a/src/xenia/base/math.h b/src/xenia/base/math.h index de92fbc7e..889cf03ed 100644 --- a/src/xenia/base/math.h +++ b/src/xenia/base/math.h @@ -378,8 +378,65 @@ int64_t m128_i64(const __m128& v) { } #endif -uint16_t float_to_half(float value); -float half_to_float(uint16_t value); +// Similar to the C++ implementation of XMConvertFloatToHalf and +// XMConvertHalfToFloat from DirectXMath 3.00 (pre-3.04, which switched from the +// Xenos encoding to IEEE 754), with the extended range instead of infinity and +// NaN, and optionally with denormalized numbers - as used in vpkd3d128 (no +// denormals, rounding towards zero) and on the Xenos (GL_OES_texture_float +// alternative encoding). + +inline uint16_t float_to_xenos_half(float value, bool preserve_denormal = false, + bool round_to_nearest_even = false) { + uint32_t integer_value = *reinterpret_cast(&value); + uint32_t abs_value = integer_value & 0x7FFFFFFFu; + uint32_t result; + if (abs_value >= 0x47FFE000u) { + // Saturate. + result = 0x7FFFu; + } else { + if (abs_value < 0x38800000u) { + // The number is too small to be represented as a normalized half. + if (preserve_denormal) { + uint32_t shift = + std::min(uint32_t(113u - (abs_value >> 23u)), uint32_t(24u)); + result = (0x800000u | (abs_value & 0x7FFFFFu)) >> shift; + } else { + result = 0u; + } + } else { + // Rebias the exponent to represent the value as a normalized half. + result = abs_value + 0xC8000000u; + } + if (round_to_nearest_even) { + result += 0xFFFu + ((result >> 13u) & 1u); + } + result = (result >> 13u) & 0x7FFFu; + } + return uint16_t(result | ((integer_value & 0x80000000u) >> 16u)); +} + +inline float xenos_half_to_float(uint16_t value, + bool preserve_denormal = false) { + uint32_t mantissa = value & 0x3FFu; + uint32_t exponent = (value >> 10u) & 0x1Fu; + if (!exponent) { + if (!preserve_denormal) { + mantissa = 0; + } else if (mantissa) { + // Normalize the value in the resulting float. + // do { Exponent--; Mantissa <<= 1; } while ((Mantissa & 0x0400) == 0) + uint32_t mantissa_lzcnt = xe::lzcnt(mantissa) - (32u - 11u); + exponent = uint32_t(1 - int32_t(mantissa_lzcnt)); + mantissa = (mantissa << mantissa_lzcnt) & 0x3FFu; + } + if (!mantissa) { + exponent = uint32_t(-112); + } + } + uint32_t result = (uint32_t(value & 0x8000u) << 16u) | + ((exponent + 112u) << 23u) | (mantissa << 13u); + return *reinterpret_cast(&result); +} // https://locklessinc.com/articles/sat_arithmetic/ template