mirror of
https://github.com/AssetRipper/AssetRipper.TextureDecoder.git
synced 2026-11-13 12:41:27 -10:00
Support int, uint, long, ulong, and decimal in IColor<T>
This commit is contained in:
1 parent
959f6584b2
commit
ddcb97be28
9 files changed
+1229
-397
No files matched your search
+5
-1
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
@@ -7,4 +7,8 @@
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,74 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.ColorGenerator;
|
||||
|
||||
internal static class CSharpPrimitives
|
||||
{
|
||||
internal static IReadOnlyDictionary<Type, string> MinimumValues { get; } = new Dictionary<Type, string>()
|
||||
{
|
||||
{ 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<Type, string> MaximumValues { get; } = new Dictionary<Type, string>()
|
||||
{
|
||||
{ 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<Type, string> TypeNames { get; } = new Dictionary<Type, string>()
|
||||
{
|
||||
{ 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<Type, int> Sizes { get; } = new Dictionary<Type, int>()
|
||||
{
|
||||
{ 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<Half>() },
|
||||
{ typeof(float), sizeof(float) },
|
||||
{ typeof(double), sizeof(double) },
|
||||
{ typeof(decimal), sizeof(decimal) }
|
||||
};
|
||||
|
||||
internal static IEnumerable<Type> Types => TypeNames.Keys;
|
||||
|
||||
internal static Type FirstType { get; } = Types.First();
|
||||
}
|
||||
@@ -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, TTo>(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<TFrom, TTo>(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)}<TTo>(Unsafe.As<TFrom, {fromName}>(ref value));");
|
||||
}
|
||||
}
|
||||
writer.WriteLine("else");
|
||||
using (new CurlyBrackets(writer))
|
||||
{
|
||||
writer.WriteLine("return ThrowOrReturnDefault<TTo>();");
|
||||
}
|
||||
}
|
||||
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}<TTo>({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)}<TTo>(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))}<TTo>((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}<float>(value);");
|
||||
writer.WriteLine("Half converted = (Half)x;");
|
||||
writer.WriteLine("return Unsafe.As<Half, TTo>(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<byte, TTo>(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<TTo>();");
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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<Type, string> 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<Type, string> 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<Type, string> TypeNames = new()
|
||||
{
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
{ typeof(Half), "Half" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(double), "double" },
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Name, Type, Red, Blue, Green, Alpha
|
||||
/// </summary>
|
||||
@@ -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++;
|
||||
|
||||
Reference in new issue
Block a user