diff --git a/AssetRipper.TextureDecoder.ColorGenerator/AssetRipper.TextureDecoder.ColorGenerator.csproj b/AssetRipper.TextureDecoder.ColorGenerator/AssetRipper.TextureDecoder.ColorGenerator.csproj index efce80b..00eab3e 100644 --- a/AssetRipper.TextureDecoder.ColorGenerator/AssetRipper.TextureDecoder.ColorGenerator.csproj +++ b/AssetRipper.TextureDecoder.ColorGenerator/AssetRipper.TextureDecoder.ColorGenerator.csproj @@ -1,4 +1,4 @@ - + Exe @@ -7,4 +7,8 @@ enable + + + + diff --git a/AssetRipper.TextureDecoder.ColorGenerator/CSharpPrimitives.cs b/AssetRipper.TextureDecoder.ColorGenerator/CSharpPrimitives.cs new file mode 100644 index 0000000..b99ffb9 --- /dev/null +++ b/AssetRipper.TextureDecoder.ColorGenerator/CSharpPrimitives.cs @@ -0,0 +1,74 @@ +using System.Runtime.CompilerServices; + +namespace AssetRipper.TextureDecoder.ColorGenerator; + +internal static class CSharpPrimitives +{ + internal static IReadOnlyDictionary MinimumValues { get; } = new Dictionary() + { + { typeof(sbyte), "sbyte.MinValue" }, + { typeof(byte), "byte.MinValue" }, + { typeof(short), "short.MinValue" }, + { typeof(ushort), "ushort.MinValue" }, + { typeof(int), "int.MinValue" }, + { typeof(uint), "uint.MinValue" }, + { typeof(long), "long.MinValue" }, + { typeof(ulong), "ulong.MinValue" }, + { typeof(Half), "default" }, + { typeof(float), "0f" }, + { typeof(double), "0d" }, + { typeof(decimal), "0m" }, + }; + + internal static IReadOnlyDictionary MaximumValues { get; } = new Dictionary() + { + { typeof(sbyte), "sbyte.MaxValue" }, + { typeof(byte), "byte.MaxValue" }, + { typeof(short), "short.MaxValue" }, + { typeof(ushort), "ushort.MaxValue" }, + { typeof(int), "int.MaxValue" }, + { typeof(uint), "uint.MaxValue" }, + { typeof(long), "long.MaxValue" }, + { typeof(ulong), "ulong.MaxValue" }, + { typeof(Half), "Half.One" }, + { typeof(float), "1f" }, + { typeof(double), "1d" }, + { typeof(decimal), "1m" }, + }; + + internal static IReadOnlyDictionary TypeNames { get; } = new Dictionary() + { + { typeof(sbyte), "sbyte" }, + { typeof(byte), "byte" }, + { typeof(short), "short" }, + { typeof(ushort), "ushort" }, + { typeof(int), "int" }, + { typeof(uint), "uint" }, + { typeof(long), "long" }, + { typeof(ulong), "ulong" }, + { typeof(Half), "Half" }, + { typeof(float), "float" }, + { typeof(double), "double" }, + { typeof(decimal), "decimal" }, + }; + + internal static IReadOnlyDictionary Sizes { get; } = new Dictionary() + { + { typeof(sbyte), sizeof(sbyte) }, + { typeof(byte), sizeof(byte) }, + { typeof(short), sizeof(short) }, + { typeof(ushort), sizeof(ushort) }, + { typeof(int), sizeof(int) }, + { typeof(uint), sizeof(uint) }, + { typeof(long), sizeof(long) }, + { typeof(ulong), sizeof(ulong) }, + { typeof(Half), Unsafe.SizeOf() }, + { typeof(float), sizeof(float) }, + { typeof(double), sizeof(double) }, + { typeof(decimal), sizeof(decimal) } + }; + + internal static IEnumerable Types => TypeNames.Keys; + + internal static Type FirstType { get; } = Types.First(); +} \ No newline at end of file diff --git a/AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs b/AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs new file mode 100644 index 0000000..63a85cb --- /dev/null +++ b/AssetRipper.TextureDecoder.ColorGenerator/NumericConversionGenerator.cs @@ -0,0 +1,275 @@ +using AssetRipper.Text.SourceGeneration; +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace AssetRipper.TextureDecoder.ColorGenerator; +internal static class NumericConversionGenerator +{ + private const string OutputNamespace = "AssetRipper.TextureDecoder.Rgb"; + private const string OutputDirectory = "../../../../AssetRipper.TextureDecoder/Rgb/"; + private const string ClassName = "NumericConversion"; + private const string ChangeSign = "ChangeSign"; + + public static void Run() + { + using IndentedTextWriter writer = IndentedTextWriterFactory.Create(OutputDirectory, ClassName); + Write(writer); + } + + private static void Write(IndentedTextWriter writer) + { + writer.WriteLine("//This code is source generated. Do not edit manually."); + writer.WriteLine(); + writer.WriteFileScopedNamespace(OutputNamespace); + writer.WriteLine(); + writer.WriteLine($"static partial class {ClassName}"); + using (new CurlyBrackets(writer)) + { + WritePrimaryConvertMethod(writer); + foreach (Type type in CSharpPrimitives.Types) + { + WriteConvertMethod(writer, type); + } + WriteChangeSignMethods(writer); + } + } + + private static void WriteChangeSignMethods(IndentedTextWriter writer) + { + foreach ((string unsignedType, string signedType, int byteSize) in GetIntegerTypes()) + { + WriteMethod(writer, unsignedType, signedType, byteSize, false); + WriteMethod(writer, signedType, unsignedType, byteSize, true); + } + + static void WriteMethod(IndentedTextWriter writer, string parameterType, string returnType, int byteSize, bool needsCast) + { + writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]"); + writer.WriteLine($"private static {returnType} {ChangeSign}({parameterType} value)"); + using (new CurlyBrackets(writer)) + { + writer.WriteLine("unchecked"); + using (new CurlyBrackets(writer)) + { + string signBitNumber = $"0x8{new string('0', byteSize * 2 - 1)}"; + string signBitType = byteSize <= sizeof(uint) ? "uint" : "ulong"; + string cast = needsCast ? $"({signBitType})" : ""; + writer.WriteLine($"const {signBitType} SignBit = {signBitNumber};"); + writer.WriteLine($"return ({returnType})({cast}value ^ SignBit);"); + } + } + writer.WriteLineNoTabs(); + } + + static IEnumerable<(string, string, int)> GetIntegerTypes() + { + yield return ("byte", "sbyte", 1); + yield return ("ushort", "short", 2); + yield return ("uint", "int", 4); + yield return ("ulong", "long", 8); + } + } + + private static void WritePrimaryConvertMethod(IndentedTextWriter writer) + { + writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]"); + writer.WriteLine($"public static TTo Convert(TFrom value) where TFrom : unmanaged where TTo : unmanaged"); + using (new CurlyBrackets(writer)) + { + writer.WriteLine("if (typeof(TFrom) == typeof(TTo))"); + using (new CurlyBrackets(writer)) + { + writer.WriteLine("return Unsafe.As(ref value);"); + } + foreach ((Type from, string fromName) in CSharpPrimitives.TypeNames) + { + writer.WriteLine($"else if (typeof(TFrom) == typeof({fromName}))"); + using (new CurlyBrackets(writer)) + { + writer.WriteLine($"return {ConvertMethodName(from)}(Unsafe.As(ref value));"); + } + } + writer.WriteLine("else"); + using (new CurlyBrackets(writer)) + { + writer.WriteLine("return ThrowOrReturnDefault();"); + } + } + writer.WriteLineNoTabs(); + } + + private static void WriteConvertMethod(IndentedTextWriter writer, Type from) + { + string methodName = ConvertMethodName(from); + string fromName = CSharpPrimitives.TypeNames[from]; + writer.WriteLine("[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]"); + writer.WriteLine($"private static TTo {methodName}({fromName} value) where TTo : unmanaged"); + using (new CurlyBrackets(writer)) + { + if (IsSignedInteger(from, out Type? unsignedFrom)) + { + writer.WriteLine($"{CSharpPrimitives.TypeNames[unsignedFrom]} unsigned = {ChangeSign}(value);"); + writer.WriteLine($"return {ConvertMethodName(unsignedFrom)}(unsigned);"); + } + else + { + foreach ((Type to, string toName) in CSharpPrimitives.TypeNames) + { + string ifOrElseIf = to == CSharpPrimitives.FirstType ? "if" : "else if"; + writer.WriteLine($"{ifOrElseIf} (typeof(TTo) == typeof({toName}))"); + using (new CurlyBrackets(writer)) + { + if (from == to) + { + writer.WriteLine($"return Unsafe.As<{toName}, TTo>(ref value);"); + } + else if (IsSignedInteger(to, out Type? unsignedTo)) + { + string unsignedToName = CSharpPrimitives.TypeNames[unsignedTo]; + writer.WriteLine($"{toName} converted = {ChangeSign}({methodName}<{unsignedToName}>(value));"); + writer.WriteLine($"return Unsafe.As<{toName}, TTo>(ref converted);"); + } + else if (IsFloatingPoint(from)) + { + if (IsFloatingPoint(to)) + { + writer.WriteLine($"{toName} converted = ({toName})value;"); + writer.WriteLine($"return Unsafe.As<{toName}, TTo>(ref converted);"); + } + else + { + Debug.Assert(IsUnsignedInteger(to)); + if (from == typeof(Half)) + { + writer.WriteComment("We use float because it has enough precision to convert from Half to any integer type."); + writer.WriteLine($"return {ConvertMethodName(typeof(float))}((float)value);"); + } + else + { + writer.WriteComment("x must be clamped because of rounding errors."); + string minValue = CSharpPrimitives.MinimumValues[to]; + string maxValue = CSharpPrimitives.MaximumValues[to]; + writer.WriteLine($"{fromName} x = value * {maxValue};"); + writer.WriteLine($"{toName} converted = {maxValue} < x ? {maxValue} : (x > {minValue} ? ({toName})x : {minValue});"); + writer.WriteLine($"return Unsafe.As<{toName}, TTo>(ref converted);"); + } + } + } + else + { + Debug.Assert(IsUnsignedInteger(from)); + if (IsFloatingPoint(to)) + { + if (to == typeof(Half)) + { + writer.WriteComment("There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first."); + writer.WriteLine($"float x = {methodName}(value);"); + writer.WriteLine("Half converted = (Half)x;"); + writer.WriteLine("return Unsafe.As(ref converted);"); + } + else + { + string maxValue = CSharpPrimitives.MaximumValues[from]; + writer.WriteLine($"{toName} converted = value / ({toName}){maxValue};"); + writer.WriteLine($"return Unsafe.As<{toName}, TTo>(ref converted);"); + } + } + else + { + Debug.Assert(IsUnsignedInteger(to)); + writer.WriteComment("See https://github.com/AssetRipper/TextureDecoder/issues/19"); + int fromSize = CSharpPrimitives.Sizes[from]; + int toSize = CSharpPrimitives.Sizes[to]; + if (fromSize < toSize) + { + int divided = toSize / fromSize; + string conversionType = toSize <= sizeof(uint) ? "uint" : "ulong"; + writer.WriteLine("unchecked"); + using (new CurlyBrackets(writer)) + { + writer.Write($"{toName} converted = ({toName})("); + for (int i = divided - 1; i > 0; i--) + { + writer.Write($"(({conversionType})value << {i * fromSize * 8}) | "); + } + writer.WriteLine("value);"); + writer.WriteLine($"return Unsafe.As<{toName}, TTo>(ref converted);"); + } + } + else + { + Debug.Assert(fromSize > toSize); + if (from == typeof(ushort) && to == typeof(byte)) + { + writer.WriteComment("This is a special case where we already know an optimal algorithm."); + writer.WriteLine("uint x = (value * 255u + 32895u) >> 16;"); + writer.WriteLine("byte converted = unchecked((byte)x);"); + writer.WriteLine("return Unsafe.As(ref converted);"); + } + else + { + writer.WriteComment($"There are more accurate ways to map {fromName} onto {toName}, but this is the simplest."); + string conversionType = fromSize <= sizeof(uint) ? "uint" : "ulong"; + writer.WriteLine("unchecked"); + using (new CurlyBrackets(writer)) + { + writer.WriteLine($"{toName} converted = ({toName})(({conversionType})value >> {(fromSize / toSize - 1) * 8});"); + writer.WriteLine($"return Unsafe.As<{toName}, TTo>(ref converted);"); + } + } + } + } + } + } + } + writer.WriteLine("else"); + using (new CurlyBrackets(writer)) + { + writer.WriteLine("return ThrowOrReturnDefault();"); + } + } + } + writer.WriteLineNoTabs(); + } + + private static string ConvertMethodName(Type from) + { + return $"Convert{from.Name}"; + } + + private static bool IsFloatingPoint(Type type) + { + return type == typeof(Half) || type == typeof(float) || type == typeof(double) || type == typeof(decimal); + } + + private static bool IsSignedInteger(Type type, [NotNullWhen(true)] out Type? unsignedType) + { + if (type == typeof(sbyte)) + { + unsignedType = typeof(byte); + } + else if (type == typeof(short)) + { + unsignedType = typeof(ushort); + } + else if (type == typeof(int)) + { + unsignedType = typeof(uint); + } + else if (type == typeof(long)) + { + unsignedType = typeof(ulong); + } + else + { + unsignedType = null; + } + return unsignedType is not null; + } + + private static bool IsUnsignedInteger(Type type) + { + return type == typeof(byte) || type == typeof(ushort) || type == typeof(uint) || type == typeof(ulong); + } +} diff --git a/AssetRipper.TextureDecoder.ColorGenerator/Program.cs b/AssetRipper.TextureDecoder.ColorGenerator/Program.cs index 9e62cdb..1868877 100644 --- a/AssetRipper.TextureDecoder.ColorGenerator/Program.cs +++ b/AssetRipper.TextureDecoder.ColorGenerator/Program.cs @@ -8,39 +8,6 @@ internal static class Program private const string OutputNamespace = "AssetRipper.TextureDecoder.Rgb.Formats"; private const string OutputFolder = "../../../../AssetRipper.TextureDecoder/Rgb/Formats/"; - private static readonly Dictionary MinimumValues = new() - { - { typeof(byte), "byte.MinValue" }, - { typeof(sbyte), "sbyte.MinValue" }, - { typeof(short), "short.MinValue" }, - { typeof(ushort), "ushort.MinValue" }, - { typeof(Half), "default" }, - { typeof(float), "0f" }, - { typeof(double), "0d" }, - }; - - private static readonly Dictionary MaximumValues = new() - { - { typeof(byte), "byte.MaxValue" }, - { typeof(sbyte), "sbyte.MaxValue" }, - { typeof(short), "short.MaxValue" }, - { typeof(ushort), "ushort.MaxValue" }, - { typeof(Half), "Half.One" }, - { typeof(float), "1f" }, - { typeof(double), "1d" }, - }; - - private static readonly Dictionary TypeNames = new() - { - { typeof(byte), "byte" }, - { typeof(sbyte), "sbyte" }, - { typeof(short), "short" }, - { typeof(ushort), "ushort" }, - { typeof(Half), "Half" }, - { typeof(float), "float" }, - { typeof(double), "double" }, - }; - /// /// Name, Type, Red, Blue, Green, Alpha /// @@ -108,6 +75,7 @@ internal static class Program { WriteOtherColor(otherColor); } + NumericConversionGenerator.Run(); Console.WriteLine("Done!"); } @@ -137,7 +105,7 @@ internal static class Program private static void WriteOtherColor(IndentedTextWriter writer, string name, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha, bool fullyUtilized) { - string typeName = TypeNames[type]; + string typeName = CSharpPrimitives.TypeNames[type]; writer.WriteLine("//This code is source generated. Do not edit manually."); writer.WriteLine(); writer.WriteLine($"using {AttributeNamespace};"); @@ -168,7 +136,7 @@ internal static class Program private static void WriteColor(IndentedTextWriter writer, string name, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) { - string typeName = TypeNames[type]; + string typeName = CSharpPrimitives.TypeNames[type]; writer.WriteLine("//This code is source generated. Do not edit manually."); writer.WriteLine(); writer.WriteLine($"using {AttributeNamespace};"); @@ -182,13 +150,13 @@ internal static class Program writer.WriteLine('{'); writer.Indent++; - WriteProperty(writer, hasRed, typeName, 'R', MinimumValues[type]); + WriteProperty(writer, hasRed, typeName, 'R', CSharpPrimitives.MinimumValues[type]); writer.WriteLine(); - WriteProperty(writer, hasGreen, typeName, 'G', MinimumValues[type]); + WriteProperty(writer, hasGreen, typeName, 'G', CSharpPrimitives.MinimumValues[type]); writer.WriteLine(); - WriteProperty(writer, hasBlue, typeName, 'B', MinimumValues[type]); + WriteProperty(writer, hasBlue, typeName, 'B', CSharpPrimitives.MinimumValues[type]); writer.WriteLine(); - WriteProperty(writer, hasAlpha, typeName, 'A', MaximumValues[type]); + WriteProperty(writer, hasAlpha, typeName, 'A', CSharpPrimitives.MaximumValues[type]); writer.WriteLine(); WriteGetChannels(writer, typeName); @@ -219,7 +187,7 @@ internal static class Program private static void WriteSetChannels(IndentedTextWriter writer, Type type, bool hasRed, bool hasGreen, bool hasBlue, bool hasAlpha) { - string typeName = TypeNames[type]; + string typeName = CSharpPrimitives.TypeNames[type]; writer.WriteLine($"public void SetChannels({typeName} r, {typeName} g, {typeName} b, {typeName} a)"); writer.WriteLine('{'); writer.Indent++; diff --git a/AssetRipper.TextureDecoder.Tests/NumericConversionTests.cs b/AssetRipper.TextureDecoder.Tests/NumericConversionTests.cs new file mode 100644 index 0000000..427b224 --- /dev/null +++ b/AssetRipper.TextureDecoder.Tests/NumericConversionTests.cs @@ -0,0 +1,25 @@ +using AssetRipper.TextureDecoder.Rgb; + +namespace AssetRipper.TextureDecoder.Tests; +internal class NumericConversionTests +{ + [Test] + public void Int32ToUInt32ExtremesAreCorrect() + { + Assert.Multiple(() => + { + Assert.That(NumericConversion.Convert(int.MinValue), Is.EqualTo(uint.MinValue)); + Assert.That(NumericConversion.Convert(int.MaxValue), Is.EqualTo(uint.MaxValue)); + }); + } + + [Test] + public void UInt32ToInt32ExtremesAreCorrect() + { + Assert.Multiple(() => + { + Assert.That(NumericConversion.Convert(uint.MinValue), Is.EqualTo(int.MinValue)); + Assert.That(NumericConversion.Convert(uint.MaxValue), Is.EqualTo(int.MaxValue)); + }); + } +} diff --git a/AssetRipper.TextureDecoder/Rgb/ConversionUtilities.cs b/AssetRipper.TextureDecoder/Rgb/ConversionUtilities.cs deleted file mode 100644 index 95b07c5..0000000 --- a/AssetRipper.TextureDecoder/Rgb/ConversionUtilities.cs +++ /dev/null @@ -1,352 +0,0 @@ -namespace AssetRipper.TextureDecoder.Rgb -{ - internal static class ConversionUtilities - { - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - internal static TTo ConvertValue(TFrom value) - where TFrom : unmanaged - where TTo : unmanaged - { - if (typeof(TFrom) == typeof(TTo)) - { - return Unsafe.As(ref value); - } - else if (typeof(TFrom) == typeof(sbyte)) - { - byte converted = ChangeSign(Unsafe.As(ref value)); - return ConvertUInt8(converted); - } - else if(typeof(TFrom) == typeof(byte)) - { - return ConvertUInt8(Unsafe.As(ref value)); - } - else if (typeof(TFrom) == typeof(short)) - { - ushort converted = ChangeSign(Unsafe.As(ref value)); - return ConvertUInt16(converted); - } - else if (typeof(TFrom) == typeof(ushort)) - { - return ConvertUInt16(Unsafe.As(ref value)); - } - else if (typeof(TFrom) == typeof(Half)) - { - return ConvertHalf(Unsafe.As(ref value)); - } - else if (typeof(TFrom) == typeof(float)) - { - return ConvertSingle(Unsafe.As(ref value)); - } - else if (typeof(TFrom) == typeof(double)) - { - return ConvertDouble(Unsafe.As(ref value)); - } - else - { - return ThrowOrReturnDefault(); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static TTo ConvertUInt8(byte value) where TTo : unmanaged - { - if (typeof(TTo) == typeof(sbyte)) - { - sbyte converted = ChangeSign(value); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(byte)) - { - return Unsafe.As(ref value); - } - else if (typeof(TTo) == typeof(short)) - { - short converted = ChangeSign(ConvertUInt8ToUInt16(value)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(ushort)) - { - ushort converted = ConvertUInt8ToUInt16(value); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(Half)) - { - Half converted = (Half)(value / (float)byte.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(float)) - { - float converted = (float)(value / (float)byte.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(double)) - { - double converted = (double)(value / (double)byte.MaxValue); - return Unsafe.As(ref converted); - } - else - { - return ThrowOrReturnDefault(); - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - static ushort ConvertUInt8ToUInt16(byte value) - { - return unchecked((ushort)(((uint)value << 8) | value));//See https://github.com/AssetRipper/TextureDecoder/issues/19 - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static TTo ConvertUInt16(ushort value) where TTo : unmanaged - { - if (typeof(TTo) == typeof(sbyte)) - { - sbyte converted = ChangeSign(ConvertUInt16ToUInt8(value)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(byte)) - { - byte converted = ConvertUInt16ToUInt8(value); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(short)) - { - short converted = ChangeSign(value); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(ushort)) - { - return Unsafe.As(ref value); - } - else if (typeof(TTo) == typeof(Half)) - { - Half converted = (Half)(value / (float)ushort.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(float)) - { - float converted = (float)(value / (float)ushort.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(double)) - { - double converted = (double)(value / (double)ushort.MaxValue); - return Unsafe.As(ref converted); - } - else - { - return ThrowOrReturnDefault(); - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - static byte ConvertUInt16ToUInt8(ushort value) - { - uint result = (value * 255u + 32895u) >> 16;//See https://github.com/AssetRipper/TextureDecoder/issues/19 - return unchecked((byte)result); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static TTo ConvertHalf(Half value) where TTo : unmanaged - { - if (typeof(TTo) == typeof(sbyte)) - { - sbyte converted = ChangeSign(ClampUInt8((float)value * byte.MaxValue)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(byte)) - { - byte converted = ClampUInt8((float)value * byte.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(short)) - { - short converted = ChangeSign(ClampUInt16((float)value * ushort.MaxValue)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(ushort)) - { - ushort converted = ClampUInt16((float)value * ushort.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(Half)) - { - return Unsafe.As(ref value); - } - else if (typeof(TTo) == typeof(float)) - { - float converted = (float)value; - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(double)) - { - double converted = (double)value; - return Unsafe.As(ref converted); - } - else - { - return ThrowOrReturnDefault(); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static TTo ConvertSingle(float value) where TTo : unmanaged - { - if (typeof(TTo) == typeof(sbyte)) - { - sbyte converted = ChangeSign(ClampUInt8(value * byte.MaxValue)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(byte)) - { - byte converted = ClampUInt8(value * byte.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(short)) - { - short converted = ChangeSign(ClampUInt16(value * ushort.MaxValue)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(ushort)) - { - ushort converted = ClampUInt16(value * ushort.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(Half)) - { - Half converted = (Half)value; - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(float)) - { - return Unsafe.As(ref value); - } - else if (typeof(TTo) == typeof(double)) - { - double converted = (double)value; - return Unsafe.As(ref converted); - } - else - { - return ThrowOrReturnDefault(); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static TTo ConvertDouble(double value) where TTo : unmanaged - { - if (typeof(TTo) == typeof(sbyte)) - { - sbyte converted = ChangeSign(ClampUInt8(value * byte.MaxValue)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(byte)) - { - byte converted = ClampUInt8(value * byte.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(short)) - { - short converted = ChangeSign(ClampUInt16(value * ushort.MaxValue)); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(ushort)) - { - ushort converted = ClampUInt16(value * ushort.MaxValue); - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(Half)) - { - Half converted = (Half)value; - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(float)) - { - float converted = (float)value; - return Unsafe.As(ref converted); - } - else if (typeof(TTo) == typeof(double)) - { - return Unsafe.As(ref value); - } - else - { - return ThrowOrReturnDefault(); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static T ThrowOrReturnDefault() where T : unmanaged - { -#if DEBUG - throw new InvalidCastException(); -#else - return default; //exceptions prevent inlining -#endif - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static byte ClampUInt8(float x) - { - return byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue); - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static byte ClampUInt8(double x) - { - return byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue); - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static ushort ClampUInt16(float x) - { - return ushort.MaxValue < x ? ushort.MaxValue : (x > ushort.MinValue ? (ushort)x : ushort.MinValue); - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static ushort ClampUInt16(double x) - { - return ushort.MaxValue < x ? ushort.MaxValue : (x > ushort.MinValue ? (ushort)x : ushort.MinValue); - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static byte ChangeSign(sbyte x) - { - unchecked - { - const uint SignBit = 0x80; - return (byte)((uint)x ^ SignBit); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static sbyte ChangeSign(byte x) - { - unchecked - { - const uint SignBit = 0x80; - return (sbyte)(x ^ SignBit); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static ushort ChangeSign(short x) - { - unchecked - { - const uint SignBit = 0x8000; - return (ushort)((uint)x ^ SignBit); - } - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static short ChangeSign(ushort x) - { - unchecked - { - const uint SignBit = 0x8000; - return (short)(x ^ SignBit); - } - } - } -} diff --git a/AssetRipper.TextureDecoder/Rgb/IColor.cs b/AssetRipper.TextureDecoder/Rgb/IColor.cs index 7f7a55c..728dca6 100644 --- a/AssetRipper.TextureDecoder/Rgb/IColor.cs +++ b/AssetRipper.TextureDecoder/Rgb/IColor.cs @@ -14,9 +14,14 @@ /// /// /// + /// + /// + /// + /// /// /// /// + /// /// /// public interface IColor where T : unmanaged @@ -68,10 +73,10 @@ where TThis : unmanaged, IColor { color.SetChannels( - ConversionUtilities.ConvertValue(r), - ConversionUtilities.ConvertValue(g), - ConversionUtilities.ConvertValue(b), - ConversionUtilities.ConvertValue(a)); + NumericConversion.Convert(r), + NumericConversion.Convert(g), + NumericConversion.Convert(b), + NumericConversion.Convert(a)); } [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] diff --git a/AssetRipper.TextureDecoder/Rgb/NumericConversion.cs b/AssetRipper.TextureDecoder/Rgb/NumericConversion.cs new file mode 100644 index 0000000..67d751b --- /dev/null +++ b/AssetRipper.TextureDecoder/Rgb/NumericConversion.cs @@ -0,0 +1,29 @@ +using System.Numerics; + +namespace AssetRipper.TextureDecoder.Rgb; +public static partial class NumericConversion +{ + public static T GetMinimumValue() where T : unmanaged, INumberBase, IMinMaxValue + { + return typeof(T) == typeof(Half) || typeof(T) == typeof(float) || typeof(T) == typeof(double) || typeof(T) == typeof(decimal) + ? T.Zero + : T.MinValue; + } + + public static T GetMaximumValue() where T : unmanaged, INumberBase, IMinMaxValue + { + return typeof(T) == typeof(Half) || typeof(T) == typeof(float) || typeof(T) == typeof(double) || typeof(T) == typeof(decimal) + ? T.One + : T.MaxValue; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static T ThrowOrReturnDefault() where T : struct + { +#if DEBUG + throw new InvalidCastException(); +#else + return default; //exceptions prevent inlining +#endif + } +} diff --git a/AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs b/AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs new file mode 100644 index 0000000..3790353 --- /dev/null +++ b/AssetRipper.TextureDecoder/Rgb/NumericConversion.g.cs @@ -0,0 +1,804 @@ +//This code is source generated. Do not edit manually. + +namespace AssetRipper.TextureDecoder.Rgb; + +static partial class NumericConversion +{ + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static TTo Convert(TFrom value) where TFrom : unmanaged where TTo : unmanaged + { + if (typeof(TFrom) == typeof(TTo)) + { + return Unsafe.As(ref value); + } + else if (typeof(TFrom) == typeof(sbyte)) + { + return ConvertSByte(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(byte)) + { + return ConvertByte(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(short)) + { + return ConvertInt16(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(ushort)) + { + return ConvertUInt16(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(int)) + { + return ConvertInt32(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(uint)) + { + return ConvertUInt32(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(long)) + { + return ConvertInt64(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(ulong)) + { + return ConvertUInt64(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(Half)) + { + return ConvertHalf(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(float)) + { + return ConvertSingle(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(double)) + { + return ConvertDouble(Unsafe.As(ref value)); + } + else if (typeof(TFrom) == typeof(decimal)) + { + return ConvertDecimal(Unsafe.As(ref value)); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertSByte(sbyte value) where TTo : unmanaged + { + byte unsigned = ChangeSign(value); + return ConvertByte(unsigned); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertByte(byte value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertByte(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + return Unsafe.As(ref value); + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertByte(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + unchecked + { + ushort converted = (ushort)(((uint)value << 8) | value); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertByte(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + unchecked + { + uint converted = (uint)(((uint)value << 24) | ((uint)value << 16) | ((uint)value << 8) | value); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertByte(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + unchecked + { + ulong converted = (ulong)(((ulong)value << 56) | ((ulong)value << 48) | ((ulong)value << 40) | ((ulong)value << 32) | ((ulong)value << 24) | ((ulong)value << 16) | ((ulong)value << 8) | value); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(Half)) + { + // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. + float x = ConvertByte(value); + Half converted = (Half)x; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(float)) + { + float converted = value / (float)byte.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(double)) + { + double converted = value / (double)byte.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(decimal)) + { + decimal converted = value / (decimal)byte.MaxValue; + return Unsafe.As(ref converted); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertInt16(short value) where TTo : unmanaged + { + ushort unsigned = ChangeSign(value); + return ConvertUInt16(unsigned); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertUInt16(ushort value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertUInt16(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + // This is a special case where we already know an optimal algorithm. + uint x = (value * 255u + 32895u) >> 16; + byte converted = unchecked((byte)x); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertUInt16(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + return Unsafe.As(ref value); + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertUInt16(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + unchecked + { + uint converted = (uint)(((uint)value << 16) | value); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertUInt16(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + unchecked + { + ulong converted = (ulong)(((ulong)value << 48) | ((ulong)value << 32) | ((ulong)value << 16) | value); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(Half)) + { + // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. + float x = ConvertUInt16(value); + Half converted = (Half)x; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(float)) + { + float converted = value / (float)ushort.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(double)) + { + double converted = value / (double)ushort.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(decimal)) + { + decimal converted = value / (decimal)ushort.MaxValue; + return Unsafe.As(ref converted); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertInt32(int value) where TTo : unmanaged + { + uint unsigned = ChangeSign(value); + return ConvertUInt32(unsigned); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertUInt32(uint value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertUInt32(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + // There are more accurate ways to map uint onto byte, but this is the simplest. + unchecked + { + byte converted = (byte)((uint)value >> 24); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertUInt32(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + // There are more accurate ways to map uint onto ushort, but this is the simplest. + unchecked + { + ushort converted = (ushort)((uint)value >> 8); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertUInt32(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + return Unsafe.As(ref value); + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertUInt32(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + unchecked + { + ulong converted = (ulong)(((ulong)value << 32) | value); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(Half)) + { + // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. + float x = ConvertUInt32(value); + Half converted = (Half)x; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(float)) + { + float converted = value / (float)uint.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(double)) + { + double converted = value / (double)uint.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(decimal)) + { + decimal converted = value / (decimal)uint.MaxValue; + return Unsafe.As(ref converted); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertInt64(long value) where TTo : unmanaged + { + ulong unsigned = ChangeSign(value); + return ConvertUInt64(unsigned); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertUInt64(ulong value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertUInt64(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + // There are more accurate ways to map ulong onto byte, but this is the simplest. + unchecked + { + byte converted = (byte)((ulong)value >> 56); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertUInt64(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + // There are more accurate ways to map ulong onto ushort, but this is the simplest. + unchecked + { + ushort converted = (ushort)((ulong)value >> 24); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertUInt64(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + // See https://github.com/AssetRipper/TextureDecoder/issues/19 + // There are more accurate ways to map ulong onto uint, but this is the simplest. + unchecked + { + uint converted = (uint)((ulong)value >> 8); + return Unsafe.As(ref converted); + } + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertUInt64(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + return Unsafe.As(ref value); + } + else if (typeof(TTo) == typeof(Half)) + { + // There isn't enough precision to convert from anything bigger than byte to Half, so we convert to float first. + float x = ConvertUInt64(value); + Half converted = (Half)x; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(float)) + { + float converted = value / (float)ulong.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(double)) + { + double converted = value / (double)ulong.MaxValue; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(decimal)) + { + decimal converted = value / (decimal)ulong.MaxValue; + return Unsafe.As(ref converted); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertHalf(Half value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertHalf(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + // We use float because it has enough precision to convert from Half to any integer type. + return ConvertSingle((float)value); + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertHalf(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + // We use float because it has enough precision to convert from Half to any integer type. + return ConvertSingle((float)value); + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertHalf(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + // We use float because it has enough precision to convert from Half to any integer type. + return ConvertSingle((float)value); + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertHalf(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + // We use float because it has enough precision to convert from Half to any integer type. + return ConvertSingle((float)value); + } + else if (typeof(TTo) == typeof(Half)) + { + return Unsafe.As(ref value); + } + else if (typeof(TTo) == typeof(float)) + { + float converted = (float)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(double)) + { + double converted = (double)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(decimal)) + { + decimal converted = (decimal)value; + return Unsafe.As(ref converted); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertSingle(float value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertSingle(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + // x must be clamped because of rounding errors. + float x = value * byte.MaxValue; + byte converted = byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertSingle(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + // x must be clamped because of rounding errors. + float x = value * ushort.MaxValue; + ushort converted = ushort.MaxValue < x ? ushort.MaxValue : (x > ushort.MinValue ? (ushort)x : ushort.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertSingle(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + // x must be clamped because of rounding errors. + float x = value * uint.MaxValue; + uint converted = uint.MaxValue < x ? uint.MaxValue : (x > uint.MinValue ? (uint)x : uint.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertSingle(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + // x must be clamped because of rounding errors. + float x = value * ulong.MaxValue; + ulong converted = ulong.MaxValue < x ? ulong.MaxValue : (x > ulong.MinValue ? (ulong)x : ulong.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(Half)) + { + Half converted = (Half)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(float)) + { + return Unsafe.As(ref value); + } + else if (typeof(TTo) == typeof(double)) + { + double converted = (double)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(decimal)) + { + decimal converted = (decimal)value; + return Unsafe.As(ref converted); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertDouble(double value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertDouble(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + // x must be clamped because of rounding errors. + double x = value * byte.MaxValue; + byte converted = byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertDouble(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + // x must be clamped because of rounding errors. + double x = value * ushort.MaxValue; + ushort converted = ushort.MaxValue < x ? ushort.MaxValue : (x > ushort.MinValue ? (ushort)x : ushort.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertDouble(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + // x must be clamped because of rounding errors. + double x = value * uint.MaxValue; + uint converted = uint.MaxValue < x ? uint.MaxValue : (x > uint.MinValue ? (uint)x : uint.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertDouble(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + // x must be clamped because of rounding errors. + double x = value * ulong.MaxValue; + ulong converted = ulong.MaxValue < x ? ulong.MaxValue : (x > ulong.MinValue ? (ulong)x : ulong.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(Half)) + { + Half converted = (Half)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(float)) + { + float converted = (float)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(double)) + { + return Unsafe.As(ref value); + } + else if (typeof(TTo) == typeof(decimal)) + { + decimal converted = (decimal)value; + return Unsafe.As(ref converted); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static TTo ConvertDecimal(decimal value) where TTo : unmanaged + { + if (typeof(TTo) == typeof(sbyte)) + { + sbyte converted = ChangeSign(ConvertDecimal(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(byte)) + { + // x must be clamped because of rounding errors. + decimal x = value * byte.MaxValue; + byte converted = byte.MaxValue < x ? byte.MaxValue : (x > byte.MinValue ? (byte)x : byte.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(short)) + { + short converted = ChangeSign(ConvertDecimal(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ushort)) + { + // x must be clamped because of rounding errors. + decimal x = value * ushort.MaxValue; + ushort converted = ushort.MaxValue < x ? ushort.MaxValue : (x > ushort.MinValue ? (ushort)x : ushort.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(int)) + { + int converted = ChangeSign(ConvertDecimal(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(uint)) + { + // x must be clamped because of rounding errors. + decimal x = value * uint.MaxValue; + uint converted = uint.MaxValue < x ? uint.MaxValue : (x > uint.MinValue ? (uint)x : uint.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(long)) + { + long converted = ChangeSign(ConvertDecimal(value)); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(ulong)) + { + // x must be clamped because of rounding errors. + decimal x = value * ulong.MaxValue; + ulong converted = ulong.MaxValue < x ? ulong.MaxValue : (x > ulong.MinValue ? (ulong)x : ulong.MinValue); + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(Half)) + { + Half converted = (Half)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(float)) + { + float converted = (float)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(double)) + { + double converted = (double)value; + return Unsafe.As(ref converted); + } + else if (typeof(TTo) == typeof(decimal)) + { + return Unsafe.As(ref value); + } + else + { + return ThrowOrReturnDefault(); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static sbyte ChangeSign(byte value) + { + unchecked + { + const uint SignBit = 0x80; + return (sbyte)(value ^ SignBit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static byte ChangeSign(sbyte value) + { + unchecked + { + const uint SignBit = 0x80; + return (byte)((uint)value ^ SignBit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static short ChangeSign(ushort value) + { + unchecked + { + const uint SignBit = 0x8000; + return (short)(value ^ SignBit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static ushort ChangeSign(short value) + { + unchecked + { + const uint SignBit = 0x8000; + return (ushort)((uint)value ^ SignBit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static int ChangeSign(uint value) + { + unchecked + { + const uint SignBit = 0x80000000; + return (int)(value ^ SignBit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static uint ChangeSign(int value) + { + unchecked + { + const uint SignBit = 0x80000000; + return (uint)((uint)value ^ SignBit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static long ChangeSign(ulong value) + { + unchecked + { + const ulong SignBit = 0x8000000000000000; + return (long)(value ^ SignBit); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static ulong ChangeSign(long value) + { + unchecked + { + const ulong SignBit = 0x8000000000000000; + return (ulong)((ulong)value ^ SignBit); + } + } + +}