From 91e6bf30bfffd342ad719b757c310d20c0486502 Mon Sep 17 00:00:00 2001 From: Naomi Ahmed Date: Fri, 4 Nov 2022 09:17:49 +0000 Subject: [PATCH 1/4] Add R11G11B10FloatToBGRA32 and ColorRGB32Half --- .../Program.cs | 1 + .../Formats/ColorRGB32HalfTests.g.cs | 155 ++++++++++++++++++ .../Rgb/Formats/ColorRGB32Half.cs | 75 +++++++++ .../Rgb/RgbConverter.cs | 36 +++- 4 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs create mode 100644 AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs diff --git a/AssetRipper.TextureDecoder.TestGenerator/Program.cs b/AssetRipper.TextureDecoder.TestGenerator/Program.cs index 8f63154..efb01a9 100644 --- a/AssetRipper.TextureDecoder.TestGenerator/Program.cs +++ b/AssetRipper.TextureDecoder.TestGenerator/Program.cs @@ -22,6 +22,7 @@ internal static class Program new GenerationData(typeof(ColorRGB16), typeof(byte), 2), new GenerationData(typeof(ColorRGB24), typeof(byte), 3), new GenerationData(typeof(ColorRGB48), typeof(ushort), 6), + new GenerationData(typeof(ColorRGB32Half), typeof(Half), 4), new GenerationData(typeof(ColorRGB9e5), typeof(double), 4), new GenerationData(typeof(ColorRGBA16), typeof(byte), 2), new GenerationData(typeof(ColorRGBA32), typeof(byte), 4), diff --git a/AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs b/AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs new file mode 100644 index 0000000..9fc3bae --- /dev/null +++ b/AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs @@ -0,0 +1,155 @@ +//This code is source generated. Do not edit manually. + +using AssetRipper.TextureDecoder.Rgb.Formats; +using System.Runtime.CompilerServices; + +namespace AssetRipper.TextureDecoder.Tests.Formats; + +public partial class ColorRGB32HalfTests +{ + [Test] + public void CorrectSizeTest() + { + Assert.That(Unsafe.SizeOf(), Is.EqualTo(4)); + } + + [Test] + public void PropertyIsSymmetric_R() + { + var color = MakeRandomColor(); + var r = color.R; + color.R = r; + Assert.That(color.R, Is.EqualTo(r)); + } + + [Test] + public void PropertyIsSymmetric_G() + { + var color = MakeRandomColor(); + var g = color.G; + color.G = g; + Assert.That(color.G, Is.EqualTo(g)); + } + + [Test] + public void PropertyIsSymmetric_B() + { + var color = MakeRandomColor(); + var b = color.B; + color.B = b; + Assert.That(color.B, Is.EqualTo(b)); + } + + [Test] + public void PropertyIsSymmetric_A() + { + var color = MakeRandomColor(); + var a = color.A; + color.A = a; + Assert.That(color.A, Is.EqualTo(a)); + } + + [Test] + public void ChannelsAreIndependent_R() + { + var color = MakeRandomColor(); + var g = color.G; + var b = color.B; + var a = color.A; + color.R = (Half)0.333f; + Assert.Multiple(() => + { + Assert.That(color.G, Is.EqualTo(g)); + Assert.That(color.B, Is.EqualTo(b)); + Assert.That(color.A, Is.EqualTo(a)); + }); + } + + [Test] + public void ChannelsAreIndependent_G() + { + var color = MakeRandomColor(); + var r = color.R; + var b = color.B; + var a = color.A; + color.G = (Half)0.333f; + Assert.Multiple(() => + { + Assert.That(color.R, Is.EqualTo(r)); + Assert.That(color.B, Is.EqualTo(b)); + Assert.That(color.A, Is.EqualTo(a)); + }); + } + + [Test] + public void ChannelsAreIndependent_B() + { + var color = MakeRandomColor(); + var r = color.R; + var g = color.G; + var a = color.A; + color.B = (Half)0.333f; + Assert.Multiple(() => + { + Assert.That(color.R, Is.EqualTo(r)); + Assert.That(color.G, Is.EqualTo(g)); + Assert.That(color.A, Is.EqualTo(a)); + }); + } + + [Test] + public void ChannelsAreIndependent_A() + { + var color = MakeRandomColor(); + var r = color.R; + var g = color.G; + var b = color.B; + color.A = (Half)0.333f; + Assert.Multiple(() => + { + Assert.That(color.R, Is.EqualTo(r)); + Assert.That(color.G, Is.EqualTo(g)); + Assert.That(color.B, Is.EqualTo(b)); + }); + } + + [Test] + public void GetMethodMatchesProperties() + { + var color = MakeRandomColor(); + color.GetChannels(out var r, out var g, out var b, out var a); + Assert.Multiple(() => + { + Assert.That(color.R, Is.EqualTo(r)); + Assert.That(color.G, Is.EqualTo(g)); + Assert.That(color.B, Is.EqualTo(b)); + Assert.That(color.A, Is.EqualTo(a)); + }); + } + + [Test] + public void MethodsAreSymmetric() + { + var color = MakeRandomColor(); + color.GetChannels(out var r, out var g, out var b, out var a); + color.SetChannels(r, g, b, a); + Assert.Multiple(() => + { + Assert.That(color.R, Is.EqualTo(r)); + Assert.That(color.G, Is.EqualTo(g)); + Assert.That(color.B, Is.EqualTo(b)); + Assert.That(color.A, Is.EqualTo(a)); + }); + } + + public static ColorRGB32Half MakeRandomColor() + { + return new() + { + R = (Half)0.447f, + G = (Half)0.224f, + B = (Half)0.95f, + A = (Half)0.897f, + }; + } +} diff --git a/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs b/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs new file mode 100644 index 0000000..f8728c9 --- /dev/null +++ b/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs @@ -0,0 +1,75 @@ +namespace AssetRipper.TextureDecoder.Rgb.Formats +{ + /// + /// Also called R11G11B10_FLOAT + /// + public struct ColorRGB32Half : IColor + { + private uint bits; + + [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] + private static Half ToHalf(ushort value) + { + #if NET5_0_OR_GREATER + return Unsafe.As(ref value); + #else + return ToHalf(value); + #endif + } + + [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] + private static uint FromHalf(Half value) + { + #if NET5_0_OR_GREATER + return Unsafe.As(ref value); + #else + return FromHalf(value); + #endif + } + + /// + /// 5 bits + /// + public Half R + { + get => ToHalf((ushort) ((bits << 4) & 0x7FF0)); + set => bits = (uint) ((bits & ~0x7FF) | (FromHalf(value) & 0x7FF0) >> 4); + } + + /// + /// 6 bits + /// + public Half G + { + get => ToHalf((ushort) ((bits >> 7) & 0x7FF0)); + set => bits = (uint) ((bits & ~0x3FF800) | ((FromHalf(value) >> 4) & 0x7FF0) << 11); + } + + /// + /// 5 bits + /// + public Half B + { + get => ToHalf((ushort) ((bits >> 17) & 0x7FE0)); + set => bits = (bits & ~0xFFC00000) | (((FromHalf(value) >> 5) & 0x3FF) << 22); + } + + public Half A + { + get => (Half) 1.0f; + set { } + } + + public void GetChannels(out Half r, out Half g, out Half b, out Half a) + { + DefaultColorMethods.GetChannels(this, out r, out g, out b, out a); + } + + public void SetChannels(Half r, Half g, Half b, Half a) + { + bits = ((FromHalf(r) >> 4) & 0x7FF) | + (((FromHalf(g) >> 4) & 0x7FF) << 11) | + (((FromHalf(b) >> 5) & 0x3FF) << 22); + } + } +} diff --git a/AssetRipper.TextureDecoder/Rgb/RgbConverter.cs b/AssetRipper.TextureDecoder/Rgb/RgbConverter.cs index 4598c02..23141c9 100644 --- a/AssetRipper.TextureDecoder/Rgb/RgbConverter.cs +++ b/AssetRipper.TextureDecoder/Rgb/RgbConverter.cs @@ -49,7 +49,7 @@ public static int ARGB16ToBGRA32(ReadOnlySpan input, int width, int height for (int j = 0; j < height; j++) { output[oo + 0] = unchecked((byte)(input[io + 0] << 4)); // b - output[oo + 1] = (byte)(input[io + 0] & 0xF0); // g + output[oo + 1] = (byte)(input[io + 0] & 0xF0); // g output[oo + 2] = unchecked((byte)(input[io + 1] << 4)); // r output[oo + 3] = (byte)(input[io + 1] & 0xF0); // a io += 2; @@ -214,7 +214,7 @@ public static int RGBA16ToBGRA32(ReadOnlySpan input, int width, int height for (int j = 0; j < height; j++) { output[oo + 0] = (byte)(input[io + 0] & 0xF0); // b - output[oo + 1] = unchecked((byte)(input[io + 1] << 4)); // g + output[oo + 1] = unchecked((byte)(input[io + 1] << 4)); // g output[oo + 2] = (byte)(input[io + 1] & 0xF0); // r output[oo + 3] = unchecked((byte)(input[io + 0] << 4)); // a io += 2; @@ -486,6 +486,38 @@ public static int RGB9e5FloatToBGRA32(ReadOnlySpan input, int width, int h return io; } + [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] + public static int R11G11B10FloatToBGRA32(ReadOnlySpan input, int width, int height, out byte[] output) + { + output = new byte[width * height * 4]; + return R11G11B10FloatToBGRA32(input, width, height, output); + } + + // reference: https://github.com/microsoft/DirectX-Graphics-Samples/blob/e5ea2ac7430ce39e6f6d619fd85ae32581931589/MiniEngine/Core/Shaders/PixelPacking_R11G11B10.hlsli#L31-L37 + [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] + public static int R11G11B10FloatToBGRA32(ReadOnlySpan input, int width, int height, Span output) + { + int io = 0; + int oo = 0; + for (int i = 0; i < width; i++) + { + for (int j = 0; j < height; j++) + { + uint value = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(io, 4)); + ushort r = (ushort)((value << 4) & 0x7FF0); + ushort g = (ushort)((value >> 7) & 0x7FF0); + ushort b = (ushort)((value >> 17) & 0x7FE0); + output[oo + 0] = ClampByte((float) Unsafe.As(ref b) * 255f); // b + output[oo + 1] = ClampByte((float) Unsafe.As(ref g) * 255f); // g + output[oo + 2] = ClampByte((float) Unsafe.As(ref r) * 255f); // r + output[oo + 3] = 255; // a + io += 4; + oo += 4; + } + } + return io; + } + [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] public static int RG32ToBGRA32(ReadOnlySpan input, int width, int height, out byte[] output) { From 0c73e1bd7ab4acd13de0a3dcde541361e47dc78a Mon Sep 17 00:00:00 2001 From: Naomi Ahmed Date: Mon, 7 Nov 2022 03:39:20 +0000 Subject: [PATCH 2/4] Requested changes. --- .../Program.cs | 3 +- .../Formats/ColorRGB32HalfTests.g.cs | 37 ++++++++++ .../Rgb/Formats/ColorRGB32Half.cs | 67 ++++++++----------- .../Rgb/Formats/ColorRGB32Half.g.cs | 11 +++ 4 files changed, 78 insertions(+), 40 deletions(-) create mode 100644 AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.g.cs diff --git a/AssetRipper.TextureDecoder.ColorGenerator/Program.cs b/AssetRipper.TextureDecoder.ColorGenerator/Program.cs index bc469c1..284300a 100644 --- a/AssetRipper.TextureDecoder.ColorGenerator/Program.cs +++ b/AssetRipper.TextureDecoder.ColorGenerator/Program.cs @@ -89,7 +89,8 @@ internal static class Program ( "ColorBGRA32", typeof(byte), true, true, true, true, true ), ( "ColorRGB16", typeof(byte), true, true, true, false, false ), ( "ColorRGB9e5", typeof(double), true, true, true, false, false ), - ( "ColorRGBA16", typeof(byte), true, true, true, true, false ), + ( "ColorRGBA16", typeof(byte), true, true, true, true, false ), + ( "ColorRGB32Half", typeof(Half), true, true, true, false, false ), }; static void Main() diff --git a/AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs b/AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs index 9fc3bae..78bc7b6 100644 --- a/AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs +++ b/AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs @@ -1,5 +1,6 @@ //This code is source generated. Do not edit manually. +using AssetRipper.TextureDecoder.Rgb; using AssetRipper.TextureDecoder.Rgb.Formats; using System.Runtime.CompilerServices; @@ -152,4 +153,40 @@ public static ColorRGB32Half MakeRandomColor() A = (Half)0.897f, }; } + + [Test] + public void ConversionToColorRGBAHalfIsLossless() + { + ColorRGB32Half original = MakeRandomColor(); + ColorRGBAHalf converted = original.Convert(); + ColorRGB32Half convertedBack = converted.Convert(); + Assert.That(convertedBack, Is.EqualTo(original)); + } + + [Test] + public void ConversionToColorRGBASingleIsLossless() + { + ColorRGB32Half original = MakeRandomColor(); + ColorRGBASingle converted = original.Convert(); + ColorRGB32Half convertedBack = converted.Convert(); + Assert.That(convertedBack, Is.EqualTo(original)); + } + + [Test] + public void ConversionToColorRGBHalfIsLossless() + { + ColorRGB32Half original = MakeRandomColor(); + ColorRGBHalf converted = original.Convert(); + ColorRGB32Half convertedBack = converted.Convert(); + Assert.That(convertedBack, Is.EqualTo(original)); + } + + [Test] + public void ConversionToColorRGBSingleIsLossless() + { + ColorRGB32Half original = MakeRandomColor(); + ColorRGBSingle converted = original.Convert(); + ColorRGB32Half convertedBack = converted.Convert(); + Assert.That(convertedBack, Is.EqualTo(original)); + } } diff --git a/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs b/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs index f8728c9..d9b78bc 100644 --- a/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs +++ b/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs @@ -3,55 +3,44 @@ /// /// Also called R11G11B10_FLOAT /// - public struct ColorRGB32Half : IColor + /// + /// + /// + public partial struct ColorRGB32Half : IColor { private uint bits; - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static Half ToHalf(ushort value) - { - #if NET5_0_OR_GREATER - return Unsafe.As(ref value); - #else - return ToHalf(value); - #endif - } - - [MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)] - private static uint FromHalf(Half value) - { - #if NET5_0_OR_GREATER - return Unsafe.As(ref value); - #else - return FromHalf(value); - #endif - } - /// - /// 5 bits + /// 11 bits /// - public Half R - { - get => ToHalf((ushort) ((bits << 4) & 0x7FF0)); - set => bits = (uint) ((bits & ~0x7FF) | (FromHalf(value) & 0x7FF0) >> 4); + public Half R { + get { + var value = (ushort) ((bits << 4) & 0x7FF0); + return Unsafe.As(ref value); + } + set => bits = (uint) ((bits & ~0x7FF) | ((uint) Unsafe.As(ref value) & 0x7FF0) >> 4); } /// - /// 6 bits + /// 11 bits /// - public Half G - { - get => ToHalf((ushort) ((bits >> 7) & 0x7FF0)); - set => bits = (uint) ((bits & ~0x3FF800) | ((FromHalf(value) >> 4) & 0x7FF0) << 11); + public Half G { + get { + var value = (ushort) ((bits >> 7) & 0x7FF0); + return Unsafe.As(ref value); + } + set => bits = (uint) ((bits & ~0x3FF800) | (((uint) Unsafe.As(ref value) >> 4) & 0x7FF0) << 11); } /// - /// 5 bits + /// 10 bits /// - public Half B - { - get => ToHalf((ushort) ((bits >> 17) & 0x7FE0)); - set => bits = (bits & ~0xFFC00000) | (((FromHalf(value) >> 5) & 0x3FF) << 22); + public Half B { + get { + var value = (ushort) ((bits >> 17) & 0x7FE0); + return Unsafe.As(ref value); + } + set => bits = (bits & ~0xFFC00000) | ((((uint) Unsafe.As(ref value) >> 5) & 0x3FF) << 22); } public Half A @@ -67,9 +56,9 @@ public void GetChannels(out Half r, out Half g, out Half b, out Half a) public void SetChannels(Half r, Half g, Half b, Half a) { - bits = ((FromHalf(r) >> 4) & 0x7FF) | - (((FromHalf(g) >> 4) & 0x7FF) << 11) | - (((FromHalf(b) >> 5) & 0x3FF) << 22); + bits = (((uint) Unsafe.As(ref r) >> 4) & 0x7FF) | + ((((uint) Unsafe.As(ref g) >> 4) & 0x7FF) << 11) | + ((((uint) Unsafe.As(ref b) >> 5) & 0x3FF) << 22); } } } diff --git a/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.g.cs b/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.g.cs new file mode 100644 index 0000000..84ac1b3 --- /dev/null +++ b/AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.g.cs @@ -0,0 +1,11 @@ +//This code is source generated. Do not edit manually. + +using AssetRipper.TextureDecoder.Attributes; + +namespace AssetRipper.TextureDecoder.Rgb.Formats +{ + [RgbaAttribute(RedChannel = true, GreenChannel = true, BlueChannel = true, AlphaChannel = false, FullyUtilizedChannels = false)] + public partial struct ColorRGB32Half : IColor + { + } +} From f742adbb565142e0ebb474f0c03e7dc964a5221b Mon Sep 17 00:00:00 2001 From: Naomi Ahmed Date: Mon, 7 Nov 2022 03:40:51 +0000 Subject: [PATCH 3/4] spaces to tabs --- AssetRipper.TextureDecoder.ColorGenerator/Program.cs | 4 ++-- AssetRipper.TextureDecoder.TestGenerator/Program.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/AssetRipper.TextureDecoder.ColorGenerator/Program.cs b/AssetRipper.TextureDecoder.ColorGenerator/Program.cs index 284300a..a9d0b6a 100644 --- a/AssetRipper.TextureDecoder.ColorGenerator/Program.cs +++ b/AssetRipper.TextureDecoder.ColorGenerator/Program.cs @@ -89,8 +89,8 @@ internal static class Program ( "ColorBGRA32", typeof(byte), true, true, true, true, true ), ( "ColorRGB16", typeof(byte), true, true, true, false, false ), ( "ColorRGB9e5", typeof(double), true, true, true, false, false ), - ( "ColorRGBA16", typeof(byte), true, true, true, true, false ), - ( "ColorRGB32Half", typeof(Half), true, true, true, false, false ), + ( "ColorRGBA16", typeof(byte), true, true, true, true, false ), + ( "ColorRGB32Half", typeof(Half), true, true, true, false, false ), }; static void Main() diff --git a/AssetRipper.TextureDecoder.TestGenerator/Program.cs b/AssetRipper.TextureDecoder.TestGenerator/Program.cs index ac408bd..17edfb4 100644 --- a/AssetRipper.TextureDecoder.TestGenerator/Program.cs +++ b/AssetRipper.TextureDecoder.TestGenerator/Program.cs @@ -34,7 +34,7 @@ internal static class Program new GenerationData(typeof(ColorRGBA16), 2), new GenerationData(typeof(ColorRGBA32), 4), new GenerationData(typeof(ColorRGBA32Signed), 4), - new GenerationData(typeof(ColorRGB32Half), 4), + new GenerationData(typeof(ColorRGB32Half), 4), new GenerationData(typeof(ColorRGBA64), 8), new GenerationData(typeof(ColorRGBA64Signed), 8), new GenerationData(typeof(ColorRGBAHalf), 8), From 9eb8102fe0629d09196b4c9bb7e7d66d9eb8bd44 Mon Sep 17 00:00:00 2001 From: Naomi Ahmed Date: Mon, 7 Nov 2022 04:07:06 +0000 Subject: [PATCH 4/4] Assert.Multiple --- AssetRipper.TextureDecoder.Tests/RgbTests.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/AssetRipper.TextureDecoder.Tests/RgbTests.cs b/AssetRipper.TextureDecoder.Tests/RgbTests.cs index 283815d..3096cf9 100644 --- a/AssetRipper.TextureDecoder.Tests/RgbTests.cs +++ b/AssetRipper.TextureDecoder.Tests/RgbTests.cs @@ -1,4 +1,7 @@ -namespace AssetRipper.TextureDecoder.Tests +using AssetRipper.TextureDecoder.Rgb; +using AssetRipper.TextureDecoder.Rgb.Formats; + +namespace AssetRipper.TextureDecoder.Tests { public sealed class RgbTests { @@ -211,5 +214,20 @@ public void ConvertRGBA64Test() int bytesRead = Rgb.RgbConverter.RGBA64ToBGRA32(data, 512, 512, out _); Assert.That(bytesRead, Is.EqualTo(data.Length)); } + + [Test] + public void ConvertRGB32HalfTest() + { + ReadOnlySpan data = File.ReadAllBytes(PathConstants.RgbTestFilesFolder + "test.rgb24"); + RgbConverter.Convert(data, 256, 256, out var halfData); + int legacyBytesRead = RgbConverter.R11G11B10FloatToBGRA32(halfData, 256, 256, out var legacyRgbData); + int bytesRead = RgbConverter.Convert(halfData, 256, 256, out var rgbData); + Assert.Multiple(() => + { + Assert.That(bytesRead, Is.EqualTo(halfData.Length)); + Assert.That(legacyBytesRead, Is.EqualTo(halfData.Length)); + Assert.That(rgbData, Is.EqualTo(legacyRgbData)); + }); + } } } \ No newline at end of file