Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions AssetRipper.TextureDecoder.ColorGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ internal static class Program
( "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 ),
};

static void Main()
Expand Down
5 changes: 3 additions & 2 deletions AssetRipper.TextureDecoder.TestGenerator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +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(ColorRGBA64), 8),
new GenerationData(typeof(ColorRGBA64Signed), 8),
new GenerationData(typeof(ColorRGBAHalf), 8),
Expand All @@ -60,7 +61,7 @@ internal static class Program
static void Main()
{
Directory.CreateDirectory(OutputFolder);

foreach (GenerationData data in dataList)
{
using MemoryStream memoryStream = new();
Expand Down Expand Up @@ -300,7 +301,7 @@ private static void WriteLosslessConversionTest(this IndentedTextWriter textWrit
textWriter.WriteLine($"{containingName} converted = original.{nameof(ColorExtensions.Convert)}<{currentName}, {currentData.ChannelTypeName}, {containingName}, {containingData.ChannelTypeName}>();");
textWriter.WriteLine($"{currentName} convertedBack = converted.{nameof(ColorExtensions.Convert)}<{containingName}, {containingData.ChannelTypeName}, {currentName}, {currentData.ChannelTypeName}>();");
textWriter.WriteLine("Assert.That(convertedBack, Is.EqualTo(original));");

textWriter.Indent -= 1;
textWriter.WriteLine("}");
}
Expand Down
192 changes: 192 additions & 0 deletions AssetRipper.TextureDecoder.Tests/Formats/ColorRGB32HalfTests.g.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//This code is source generated. Do not edit manually.

using AssetRipper.TextureDecoder.Rgb;
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<ColorRGB32Half>(), 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,
};
}

[Test]
public void ConversionToColorRGBAHalfIsLossless()
{
ColorRGB32Half original = MakeRandomColor();
ColorRGBAHalf converted = original.Convert<ColorRGB32Half, Half, ColorRGBAHalf, Half>();
ColorRGB32Half convertedBack = converted.Convert<ColorRGBAHalf, Half, ColorRGB32Half, Half>();
Assert.That(convertedBack, Is.EqualTo(original));
}

[Test]
public void ConversionToColorRGBASingleIsLossless()
{
ColorRGB32Half original = MakeRandomColor();
ColorRGBASingle converted = original.Convert<ColorRGB32Half, Half, ColorRGBASingle, float>();
ColorRGB32Half convertedBack = converted.Convert<ColorRGBASingle, float, ColorRGB32Half, Half>();
Assert.That(convertedBack, Is.EqualTo(original));
}

[Test]
public void ConversionToColorRGBHalfIsLossless()
{
ColorRGB32Half original = MakeRandomColor();
ColorRGBHalf converted = original.Convert<ColorRGB32Half, Half, ColorRGBHalf, Half>();
ColorRGB32Half convertedBack = converted.Convert<ColorRGBHalf, Half, ColorRGB32Half, Half>();
Assert.That(convertedBack, Is.EqualTo(original));
}

[Test]
public void ConversionToColorRGBSingleIsLossless()
{
ColorRGB32Half original = MakeRandomColor();
ColorRGBSingle converted = original.Convert<ColorRGB32Half, Half, ColorRGBSingle, float>();
ColorRGB32Half convertedBack = converted.Convert<ColorRGBSingle, float, ColorRGB32Half, Half>();
Assert.That(convertedBack, Is.EqualTo(original));
}
}
20 changes: 19 additions & 1 deletion AssetRipper.TextureDecoder.Tests/RgbTests.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -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<byte> data = File.ReadAllBytes(PathConstants.RgbTestFilesFolder + "test.rgb24");
RgbConverter.Convert<ColorRGB24, byte, ColorRGB32Half, Half>(data, 256, 256, out var halfData);
int legacyBytesRead = RgbConverter.R11G11B10FloatToBGRA32(halfData, 256, 256, out var legacyRgbData);
int bytesRead = RgbConverter.Convert<ColorRGB32Half, Half, ColorBGRA32, byte>(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));
});
}
}
}
64 changes: 64 additions & 0 deletions AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
namespace AssetRipper.TextureDecoder.Rgb.Formats
{
/// <summary>
/// Also called R11G11B10_FLOAT
/// </summary>
/// <remarks>
/// <see href="https://github.com/microsoft/DirectX-Graphics-Samples/blob/e5ea2ac7430ce39e6f6d619fd85ae32581931589/MiniEngine/Core/Shaders/PixelPacking_R11G11B10.hlsli#L31-L37" />
/// </remarks>
public partial struct ColorRGB32Half : IColor<Half>
{
private uint bits;

/// <summary>
/// 11 bits
/// </summary>
public Half R {
get {
var value = (ushort) ((bits << 4) & 0x7FF0);
return Unsafe.As<ushort, Half>(ref value);
}
set => bits = (uint) ((bits & ~0x7FF) | ((uint) Unsafe.As<Half, ushort>(ref value) & 0x7FF0) >> 4);
}

/// <summary>
/// 11 bits
/// </summary>
public Half G {
get {
var value = (ushort) ((bits >> 7) & 0x7FF0);
return Unsafe.As<ushort, Half>(ref value);
}
set => bits = (uint) ((bits & ~0x3FF800) | (((uint) Unsafe.As<Half, ushort>(ref value) >> 4) & 0x7FF0) << 11);
}

/// <summary>
/// 10 bits
/// </summary>
public Half B {
get {
var value = (ushort) ((bits >> 17) & 0x7FE0);
return Unsafe.As<ushort, Half>(ref value);
}
set => bits = (bits & ~0xFFC00000) | ((((uint) Unsafe.As<Half, ushort>(ref 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 = (((uint) Unsafe.As<Half, ushort>(ref r) >> 4) & 0x7FF) |
((((uint) Unsafe.As<Half, ushort>(ref g) >> 4) & 0x7FF) << 11) |
((((uint) Unsafe.As<Half, ushort>(ref b) >> 5) & 0x3FF) << 22);
}
}
}
11 changes: 11 additions & 0 deletions AssetRipper.TextureDecoder/Rgb/Formats/ColorRGB32Half.g.cs
Original file line number Diff line number Diff line change
@@ -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<Half>
{
}
}
36 changes: 34 additions & 2 deletions AssetRipper.TextureDecoder/Rgb/RgbConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static int ARGB16ToBGRA32(ReadOnlySpan<byte> 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;
Expand Down Expand Up @@ -214,7 +214,7 @@ public static int RGBA16ToBGRA32(ReadOnlySpan<byte> 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;
Expand Down Expand Up @@ -486,6 +486,38 @@ public static int RGB9e5FloatToBGRA32(ReadOnlySpan<byte> input, int width, int h
return io;
}

[MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)]
public static int R11G11B10FloatToBGRA32(ReadOnlySpan<byte> 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<byte> input, int width, int height, Span<byte> 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<ushort, Half>(ref b) * 255f); // b
output[oo + 1] = ClampByte((float) Unsafe.As<ushort, Half>(ref g) * 255f); // g
output[oo + 2] = ClampByte((float) Unsafe.As<ushort, Half>(ref r) * 255f); // r
output[oo + 3] = 255; // a
io += 4;
oo += 4;
}
}
return io;
}

[MethodImpl(OptimizationConstants.AggressiveInliningAndOptimization)]
public static int RG32ToBGRA32(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
{
Expand Down