From a9a5ecf398f1e1956079c0d3e3bca4c908f16f95 Mon Sep 17 00:00:00 2001 From: Luzifix Date: Wed, 18 Sep 2024 03:28:24 +1400 Subject: [PATCH] Add HalfFloat type --- Warcraft.NET/Extensions/ExtendedIO.cs | 34 +++- Warcraft.NET/Types/HalfFloat.cs | 235 ++++++++++++++++++++++++++ Warcraft.NET/Utils/HalfUtils.cs | 150 ++++++++++++++++ 3 files changed, 413 insertions(+), 6 deletions(-) create mode 100644 Warcraft.NET/Types/HalfFloat.cs create mode 100644 Warcraft.NET/Utils/HalfUtils.cs diff --git a/Warcraft.NET/Extensions/ExtendedIO.cs b/Warcraft.NET/Extensions/ExtendedIO.cs index a76ba06..296f792 100644 --- a/Warcraft.NET/Extensions/ExtendedIO.cs +++ b/Warcraft.NET/Extensions/ExtendedIO.cs @@ -1,12 +1,13 @@ -using Warcraft.NET.Files.Interfaces; -using System; -using System.IO; -using System.Text; -using Warcraft.NET.Files.Structures; +using System; using System.Collections.Generic; +using System.IO; using System.Numerics; -using Warcraft.NET.Exceptions; using System.Runtime.CompilerServices; +using System.Text; +using Warcraft.NET.Exceptions; +using Warcraft.NET.Files.Interfaces; +using Warcraft.NET.Files.Structures; +using Warcraft.NET.Types; namespace Warcraft.NET.Extensions { @@ -155,6 +156,16 @@ public static RGBA ReadBGRA(this BinaryReader reader) }; } + /// + /// Reads a 2-byte from the data stream. + /// + /// The reader. + /// The half. + public static HalfFloat ReadHalfFloat(this BinaryReader reader) + { + return new HalfFloat(reader.ReadUInt16()); + } + /// Reads ab 4.byte from the data stream. /// /// The reader. @@ -379,6 +390,17 @@ public static void WriteNullTerminatedString(this BinaryWriter binaryWriter, str binaryWriter.Write((char)0); } + + /// + /// Writes a 2-byte to the data stream. + /// + /// The current object. + /// + public static void WriteHalfFloat(this BinaryWriter binaryWriter, HalfFloat half) + { + binaryWriter.Write(half.RawValue); + } + /// /// Writes the provided string to the data stream as a C-style null-terminated string. /// diff --git a/Warcraft.NET/Types/HalfFloat.cs b/Warcraft.NET/Types/HalfFloat.cs new file mode 100644 index 0000000..aca6c09 --- /dev/null +++ b/Warcraft.NET/Types/HalfFloat.cs @@ -0,0 +1,235 @@ +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Warcraft.NET.Utils; + +namespace Warcraft.NET.Types +{ + /// + /// A half precision (16 bit) floating point value. + /// Code copy from https://github.com/sharpdx/SharpDX/blob/master/Source/SharpDX.Mathematics/Half.cs by SharpDX + /// + [StructLayout(LayoutKind.Sequential)] + public struct HalfFloat + { + private ushort value; + + /// + /// Number of decimal digits of precision. + /// + public const int PrecisionDigits = 3; + + /// + /// Number of bits in the mantissa. + /// + public const int MantissaBits = 11; + + /// + /// Maximum decimal exponent. + /// + public const int MaximumDecimalExponent = 4; + + /// + /// Maximum binary exponent. + /// + public const int MaximumBinaryExponent = 15; + + /// + /// Minimum decimal exponent. + /// + public const int MinimumDecimalExponent = -4; + + /// + /// Minimum binary exponent. + /// + public const int MinimumBinaryExponent = -14; + + /// + /// Exponent radix. + /// + public const int ExponentRadix = 2; + + /// + /// Additional rounding. + /// + public const int AdditionRounding = 1; + + /// + /// Smallest such that 1.0 + epsilon != 1.0 + /// + public static readonly float Epsilon; + + /// + /// Maximum value of the number. + /// + public static readonly float MaxValue; + + /// + /// Minimum value of the number. + /// + public static readonly float MinValue; + + /// + /// Initializes a new instance of the structure. + /// + /// The floating point value that should be stored in 16 bit format. + public HalfFloat(float value) + { + this.value = HalfUtils.Pack(value); + } + + /// + /// Initializes a new instance of the structure. + /// + /// The floating point value that should be stored in 16 bit format. + public HalfFloat(ushort rawvalue) + { + this.value = rawvalue; + } + + /// + /// Gets or sets the raw 16 bit value used to back this half-float. + /// + public ushort RawValue + { + get { return value; } + set { this.value = value; } + } + + /// + /// Converts an array of half precision values into full precision values. + /// + /// The values to be converted. + /// An array of converted values. + public static float[] ConvertToFloat(HalfFloat[] values) + { + float[] results = new float[values.Length]; + for (int i = 0; i < results.Length; i++) + results[i] = HalfUtils.Unpack(values[i].RawValue); + return results; + } + + /// + /// Converts an array of full precision values into half precision values. + /// + /// The values to be converted. + /// An array of converted values. + public static HalfFloat[] ConvertToHalf(float[] values) + { + HalfFloat[] results = new HalfFloat[values.Length]; + for (int i = 0; i < results.Length; i++) + results[i] = new HalfFloat(values[i]); + return results; + } + + /// + /// Performs an explicit conversion from to . + /// + /// The value to be converted. + /// The converted value. + public static implicit operator HalfFloat(float value) + { + return new HalfFloat(value); + } + + /// + /// Performs an implicit conversion from to . + /// + /// The value to be converted. + /// The converted value. + public static implicit operator float(HalfFloat value) + { + return HalfUtils.Unpack(value.value); + } + + /// + /// Tests for equality between two objects. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// true if has the same value as ; otherwise, false. + public static bool operator ==(HalfFloat left, HalfFloat right) + { + return left.value == right.value; + } + + /// + /// Tests for inequality between two objects. + /// + /// The first value to compare. + /// The second value to compare. + /// + /// true if has a different value than ; otherwise, false. + public static bool operator !=(HalfFloat left, HalfFloat right) + { + return left.value != right.value; + } + + /// + /// Converts the value of the object to its equivalent string representation. + /// + /// The string representation of the value of this instance. + public override string ToString() + { + float num = this; + return num.ToString(CultureInfo.CurrentCulture); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + ushort num = value; + return (((num * 3) / 2) ^ num); + } + + /// + /// Determines whether the specified object instances are considered equal. + /// + /// + /// + /// + /// true if is the same instance as or + /// if both are null references or if value1.Equals(value2) returns true; otherwise, false. + [MethodImpl((MethodImplOptions)0x100)] // MethodImplOptions.AggressiveInlining + public static bool Equals(ref HalfFloat value1, ref HalfFloat value2) + { + return value1.value == value2.value; + } + + /// + /// Returns a value that indicates whether the current instance is equal to the specified object. + /// + /// Object to make the comparison with. + /// + /// true if the current instance is equal to the specified object; false otherwise. + public bool Equals(HalfFloat other) + { + return other.value == value; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// Object to make the comparison with. + /// + /// true if the current instance is equal to the specified object; false otherwise. + public override bool Equals(object obj) + { + if (!(obj is HalfFloat)) + return false; + + return Equals((HalfFloat)obj); + } + + static HalfFloat() + { + Epsilon = 0.0004887581f; + MaxValue = 65504f; + MinValue = 6.103516E-05f; + } + } +} diff --git a/Warcraft.NET/Utils/HalfUtils.cs b/Warcraft.NET/Utils/HalfUtils.cs new file mode 100644 index 0000000..16b9de0 --- /dev/null +++ b/Warcraft.NET/Utils/HalfUtils.cs @@ -0,0 +1,150 @@ +using System.Runtime.InteropServices; + +namespace Warcraft.NET.Utils +{ + /// + /// Helper class to perform Half/Float conversion. + /// Code extract from paper : www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf by Jeroen van der Zijp + /// + internal class HalfUtils + { + [StructLayout(LayoutKind.Explicit)] + private struct FloatToUint + { + [FieldOffset(0)] + public uint uintValue; + [FieldOffset(0)] + public float floatValue; + } + + /// + /// Unpacks the specified h. + /// + /// The h. + /// + public static float Unpack(ushort h) + { + var conv = new FloatToUint(); + conv.uintValue = HalfToFloatMantissaTable[HalfToFloatOffsetTable[h >> 10] + (((uint)h) & 0x3ff)] + HalfToFloatExponentTable[h >> 10]; + return conv.floatValue; + } + + /// + /// Packs the specified f. + /// + /// The f. + /// + public static ushort Pack(float f) + { + FloatToUint conv = new FloatToUint(); + conv.floatValue = f; + return (ushort)(FloatToHalfBaseTable[(conv.uintValue >> 23) & 0x1ff] + ((conv.uintValue & 0x007fffff) >> FloatToHalfShiftTable[(conv.uintValue >> 23) & 0x1ff])); + } + + static readonly uint[] HalfToFloatMantissaTable = new uint[2048]; + static readonly uint[] HalfToFloatExponentTable = new uint[64]; + static readonly uint[] HalfToFloatOffsetTable = new uint[64]; + static readonly ushort[] FloatToHalfBaseTable = new ushort[512]; + static readonly byte[] FloatToHalfShiftTable = new byte[512]; + + static HalfUtils() + { + int i; + + // ------------------------------------------------------------------- + // Half to Float tables + // ------------------------------------------------------------------- + + // Mantissa table + + // 0 => 0 + HalfToFloatMantissaTable[0] = 0; + + // Transform subnormal to normalized + for (i = 1; i < 1024; i++) + { + uint m = ((uint)i) << 13; + uint e = 0; + + while ((m & 0x00800000) == 0) + { + e -= 0x00800000; + m <<= 1; + } + m &= ~0x00800000U; + e += 0x38800000; + HalfToFloatMantissaTable[i] = m | e; + } + + // Normal case + for (i = 1024; i < 2048; i++) + HalfToFloatMantissaTable[i] = 0x38000000 + (((uint)(i - 1024)) << 13); + + // Exponent table + + // 0 => 0 + HalfToFloatExponentTable[0] = 0; + + for (i = 1; i < 63; i++) + { + if (i < 31) // Positive Numbers + HalfToFloatExponentTable[i] = ((uint)i) << 23; + else // Negative Numbers + HalfToFloatExponentTable[i] = 0x80000000 + (((uint)(i - 32)) << 23); + } + HalfToFloatExponentTable[31] = 0x47800000; + HalfToFloatExponentTable[32] = 0x80000000; + HalfToFloatExponentTable[63] = 0xC7800000; + + // Offset table + HalfToFloatOffsetTable[0] = 0; + for (i = 1; i < 64; i++) + HalfToFloatOffsetTable[i] = 1024; + HalfToFloatOffsetTable[32] = 0; + + // ------------------------------------------------------------------- + // Float to Half tables + // ------------------------------------------------------------------- + + for (i = 0; i < 256; i++) + { + int e = i - 127; + if (e < -24) + { // Very small numbers map to zero + FloatToHalfBaseTable[i | 0x000] = 0x0000; + FloatToHalfBaseTable[i | 0x100] = 0x8000; + FloatToHalfShiftTable[i | 0x000] = 24; + FloatToHalfShiftTable[i | 0x100] = 24; + } + else if (e < -14) + { // Small numbers map to denorms + FloatToHalfBaseTable[i | 0x000] = (ushort)((0x0400 >> (-e - 14))); + FloatToHalfBaseTable[i | 0x100] = (ushort)((0x0400 >> (-e - 14)) | 0x8000); + FloatToHalfShiftTable[i | 0x000] = (byte)(-e - 1); + FloatToHalfShiftTable[i | 0x100] = (byte)(-e - 1); + } + else if (e <= 15) + { // Normal numbers just lose precision + FloatToHalfBaseTable[i | 0x000] = (ushort)(((e + 15) << 10)); + FloatToHalfBaseTable[i | 0x100] = (ushort)(((e + 15) << 10) | 0x8000); + FloatToHalfShiftTable[i | 0x000] = 13; + FloatToHalfShiftTable[i | 0x100] = 13; + } + else if (e < 128) + { // Large numbers map to Infinity + FloatToHalfBaseTable[i | 0x000] = 0x7C00; + FloatToHalfBaseTable[i | 0x100] = 0xFC00; + FloatToHalfShiftTable[i | 0x000] = 24; + FloatToHalfShiftTable[i | 0x100] = 24; + } + else + { // Infinity and NaN's stay Infinity and NaN's + FloatToHalfBaseTable[i | 0x000] = 0x7C00; + FloatToHalfBaseTable[i | 0x100] = 0xFC00; + FloatToHalfShiftTable[i | 0x000] = 13; + FloatToHalfShiftTable[i | 0x100] = 13; + } + } + } + } +}