diff --git a/AssetRipper.TextureDecoder/Rgb/ConversionUtilities.cs b/AssetRipper.TextureDecoder/Rgb/ConversionUtilities.cs index 4126dd6..444b901 100644 --- a/AssetRipper.TextureDecoder/Rgb/ConversionUtilities.cs +++ b/AssetRipper.TextureDecoder/Rgb/ConversionUtilities.cs @@ -11,10 +11,20 @@ { 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)); @@ -40,13 +50,23 @@ [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] private static TTo ConvertUInt8(byte value) where TTo : unmanaged { - if (typeof(TTo) == typeof(byte)) + 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 = unchecked((ushort)((uint)value << 8)); + ushort converted = ConvertUInt8ToUInt16(value); return Unsafe.As(ref converted); } else if (typeof(TTo) == typeof(Half)) @@ -68,16 +88,29 @@ { return ThrowOrReturnDefault(); } + + [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] + static ushort ConvertUInt8ToUInt16(byte value) => unchecked((ushort)((uint)value << 8)); } [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] private static TTo ConvertUInt16(ushort value) where TTo : unmanaged { - if (typeof(TTo) == typeof(byte)) + if (typeof(TTo) == typeof(sbyte)) { - byte converted = unchecked((byte)((uint)value >> 8)); + 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); @@ -101,16 +134,29 @@ { return ThrowOrReturnDefault(); } + + [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] + static byte ConvertUInt16ToUInt8(ushort value) => unchecked((byte)((uint)value >> 8)); } [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] private static TTo ConvertHalf(Half value) where TTo : unmanaged { - if (typeof(TTo) == typeof(byte)) + 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); @@ -139,11 +185,21 @@ [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] private static TTo ConvertSingle(float value) where TTo : unmanaged { - if (typeof(TTo) == typeof(byte)) + 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); @@ -172,11 +228,21 @@ [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] private static TTo ConvertDouble(double value) where TTo : unmanaged { - if (typeof(TTo) == typeof(byte)) + 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); @@ -235,5 +301,45 @@ { 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 c4a092a..e6e8845 100644 --- a/AssetRipper.TextureDecoder/Rgb/IColor.cs +++ b/AssetRipper.TextureDecoder/Rgb/IColor.cs @@ -1,5 +1,24 @@ namespace AssetRipper.TextureDecoder.Rgb { + /// + /// An for handling color formats with up to 4 channels. + /// + /// + /// When used as a generic type constraint, the methods and properties get devirtualized by the JIT compiler. + /// This prevents boxing when the implementing type is a . + /// + /// + /// Supported types are: + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// public interface IColor where T : unmanaged { ///