mirror of
https://github.com/AssetRipper/AssetRipper.TextureDecoder.git
synced 2026-11-12 18:07:28 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d57e3461ff | ||
|
|
1d2d19c9ef | ||
|
|
f3ba84589d | ||
|
|
7cee390b2e | ||
|
|
024ac9eb8c | ||
|
|
d47fe4dcfb | ||
|
|
7d028ce3f5 | ||
|
|
beb819daa4 | ||
|
|
c9d338f159 | ||
|
|
19b2469595 | ||
|
|
c5a2382733 | ||
|
|
9ce8311e1b | ||
|
|
e39b439ccd | ||
|
|
f2da43bcbb | ||
|
|
128eb58864 | ||
|
|
9f7a61172a | ||
|
|
93c177232f | ||
|
|
8b62957cdb | ||
|
|
95ecf5b1ac | ||
|
|
f8d17ad290 |
No files matched your search
@@ -14,9 +14,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4.0.0
|
||||
uses: actions/setup-dotnet@v4.3.1
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
dotnet-version: 9.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.1.0" />
|
||||
<PackageReference Include="AssetRipper.Text.SourceGeneration" Version="1.2.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -5,14 +5,14 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="NUnit" Version="4.1.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.1.0">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
|
||||
<PackageReference Include="NUnit" Version="4.3.2" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="5.0.0" />
|
||||
<PackageReference Include="NUnit.Analyzers" Version="4.6.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressDXT1Test()
|
||||
public void Decompress_BC1()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1");
|
||||
int totalBytesRead = 0;
|
||||
@@ -36,7 +36,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressDXT3Test()
|
||||
public void Decompress_BC2()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3");
|
||||
int totalBytesRead = 0;
|
||||
@@ -51,7 +51,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressDXT5Test()
|
||||
public void Decompress_BC3()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5");
|
||||
int totalBytesRead = 0;
|
||||
@@ -66,7 +66,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressBC4Test()
|
||||
public void Decompress_BC4()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4");
|
||||
int bytesRead = Bc4.Decompress(data, 512, 512, out _);
|
||||
@@ -74,7 +74,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DecompressBC5Test()
|
||||
public void Decompress_BC5()
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5");
|
||||
int bytesRead = Bc5.Decompress(data, 512, 512, out _);
|
||||
@@ -84,7 +84,7 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
[TestCase("test.bc6h_fast")]
|
||||
[TestCase("test.bc6h_normal")]
|
||||
[TestCase("test.bc6h_best")]
|
||||
public void DecompressBC6HTest(string fileName)
|
||||
public void Decompress_BC6H(string fileName)
|
||||
{
|
||||
AssertCorrectBC6HDecompression(TestFileFolders.BcTestFiles + fileName, 512, 512, false);
|
||||
}
|
||||
@@ -92,14 +92,85 @@ namespace AssetRipper.TextureDecoder.Tests
|
||||
[TestCase("test.bc7_fast")]
|
||||
[TestCase("test.bc7_normal")]
|
||||
[TestCase("test.bc7_best")]
|
||||
public void DecompressBC7Test(string fileName)
|
||||
public void Decompress_BC7(string fileName)
|
||||
{
|
||||
AssertCorrectBC7Decompression(TestFileFolders.BcTestFiles + fileName, 512, 512);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Ignore("Not implemented")]
|
||||
public void BC7PartialBlockTest([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
public void PartialBlock_BC1([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt1").AsSpan()[..Bc1.BlockSize];
|
||||
int bytesRead = Bc1.Decompress(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC2([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt3").AsSpan()[..Bc2.BlockSize];
|
||||
int bytesRead = Bc2.Decompress(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC3([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.DxtTestFiles + "test.dxt5").AsSpan()[..Bc3.BlockSize];
|
||||
int bytesRead = Bc3.Decompress(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC4([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc4").AsSpan()[..Bc4.BlockSize];
|
||||
int bytesRead = Bc4.Decompress(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC5([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc5").AsSpan()[..Bc5.BlockSize];
|
||||
int bytesRead = Bc5.Decompress(data, width, height, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC6H([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
ReadOnlySpan<byte> data = File.ReadAllBytes(TestFileFolders.BcTestFiles + "test.bc6h_best").AsSpan()[..Bc6h.BlockSize];
|
||||
int bytesRead = Bc6h.Decompress(data, width, height, false, out byte[] decodedData);
|
||||
Assert.That(bytesRead, Is.EqualTo(data.Length));
|
||||
Assert.That(decodedData, Has.Length.EqualTo(width * height * Unsafe.SizeOf<ColorBGRA32>()));
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PartialBlock_BC7([Range(1, 4)] int width, [Range(1, 4)] int height)
|
||||
{
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
|
||||
@@ -883,7 +883,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static int GetBits(ReadOnlySpan<byte> input, int bit, int len)
|
||||
{
|
||||
ReadOnlySpan<byte> slice = input[(bit / 8)..];
|
||||
ReadOnlySpan<byte> slice = input[(bit / 8)..];
|
||||
int bitBuffer;
|
||||
if (slice.Length >= sizeof(int))
|
||||
{
|
||||
@@ -894,7 +894,7 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
Span<byte> temp = stackalloc byte[sizeof(int)];
|
||||
slice.CopyTo(temp);
|
||||
bitBuffer = BinaryPrimitives.ReadInt32LittleEndian(temp);
|
||||
}
|
||||
}
|
||||
return (bitBuffer >> (bit % 8)) & ((1 << len) - 1);
|
||||
}
|
||||
|
||||
@@ -996,10 +996,12 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static byte SelectColor(int v0, int v1, int weight)
|
||||
{
|
||||
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * 255 + 32768) / 65536);
|
||||
const int SignAdjustment = short.MaxValue + 1;
|
||||
const int MaxValue = ushort.MaxValue + 1;
|
||||
return (byte)(((((v0 << 8 | v0) * (64 - weight) + (v1 << 8 | v1) * weight + 32) >> 6) * byte.MaxValue + SignAdjustment) / MaxValue);
|
||||
}
|
||||
|
||||
private static readonly byte[] BitReverseTable =
|
||||
private static ReadOnlySpan<byte> BitReverseTable =>
|
||||
[
|
||||
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
|
||||
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
|
||||
@@ -1019,15 +1021,15 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
|
||||
];
|
||||
|
||||
private static readonly int[] WeightPrecTableA = [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0];
|
||||
private static readonly int[] WeightPrecTableB = [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5];
|
||||
private static ReadOnlySpan<int> WeightPrecTableA => [0, 0, 0, 3, 0, 5, 3, 0, 0, 0, 5, 3, 0, 5, 3, 0];
|
||||
private static ReadOnlySpan<int> WeightPrecTableB => [0, 0, 1, 0, 2, 0, 1, 3, 0, 0, 1, 2, 4, 2, 3, 5];
|
||||
|
||||
private static readonly int[] CemTableA = [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0];
|
||||
private static readonly int[] CemTableB = [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1];
|
||||
private static ReadOnlySpan<int> CemTableA => [0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 5, 0, 3, 0, 0];
|
||||
private static ReadOnlySpan<int> CemTableB => [8, 6, 5, 7, 5, 4, 6, 4, 3, 5, 3, 2, 4, 2, 1, 3, 1, 2, 1];
|
||||
|
||||
private static readonly int[] DImt = [0, 2, 4, 5, 7];
|
||||
private static readonly int[] DImq = [0, 3, 5];
|
||||
private static readonly int[] DITritsTable =
|
||||
private static ReadOnlySpan<int> DImt => [0, 2, 4, 5, 7];
|
||||
private static ReadOnlySpan<int> DImq => [0, 3, 5];
|
||||
private static ReadOnlySpan<int> DITritsTable =>
|
||||
[
|
||||
0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 1, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2, 0, 1, 2, 0, 0, 1, 2, 1, 0, 1, 2, 2, 0, 1, 2, 2,
|
||||
0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1, 0, 0, 0, 0, 1, 1, 1, 0, 2, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 1, 2, 2, 2, 1,
|
||||
@@ -1035,14 +1037,14 @@ namespace AssetRipper.TextureDecoder.Astc
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
|
||||
];
|
||||
private static readonly int[] DIQuintsTable =
|
||||
private static ReadOnlySpan<int> DIQuintsTable =>
|
||||
[
|
||||
0, 1, 2, 3, 4, 0, 4, 4, 0, 1, 2, 3, 4, 1, 4, 4, 0, 1, 2, 3, 4, 2, 4, 4, 0, 1, 2, 3, 4, 3, 4, 4, 0, 1, 2, 3, 4, 0, 4, 0, 0, 1, 2, 3, 4, 1, 4, 1, 0, 1, 2, 3, 4, 2, 4, 2, 0, 1, 2, 3, 4, 3, 4, 3, 0, 1, 2, 3, 4, 0, 2, 3, 0, 1, 2, 3, 4, 1, 2, 3, 0, 1, 2, 3, 4, 2, 2, 3, 0, 1, 2, 3, 4, 3, 2, 3, 0, 1, 2, 3, 4, 0, 0, 1, 0, 1, 2, 3, 4, 1, 0, 1, 0, 1, 2, 3, 4, 2, 0, 1, 0, 1, 2, 3, 4, 3, 0, 1,
|
||||
0, 0, 0, 0, 0, 4, 4, 4, 1, 1, 1, 1, 1, 4, 4, 4, 2, 2, 2, 2, 2, 4, 4, 4, 3, 3, 3, 3, 3, 4, 4, 4, 0, 0, 0, 0, 0, 4, 0, 4, 1, 1, 1, 1, 1, 4, 1, 4, 2, 2, 2, 2, 2, 4, 2, 4, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3, 0, 0, 0, 0, 0, 4, 0, 0, 1, 1, 1, 1, 1, 4, 1, 1, 2, 2, 2, 2, 2, 4, 2, 2, 3, 3, 3, 3, 3, 4, 3, 3,
|
||||
0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 1, 4, 0, 0, 0, 0, 0, 0, 2, 4, 0, 0, 0, 0, 0, 0, 3, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 1, 1, 1, 1, 1, 1, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 2, 2, 2, 2, 2, 2, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4,
|
||||
];
|
||||
|
||||
private static readonly int[] DETritsTable = [0, 204, 93, 44, 22, 11, 5];
|
||||
private static readonly int[] DEQuintsTable = [0, 113, 54, 26, 13, 6];
|
||||
private static ReadOnlySpan<int> DETritsTable => [0, 204, 93, 44, 22, 11, 5];
|
||||
private static ReadOnlySpan<int> DEQuintsTable => [0, 113, 54, 26, 13, 6];
|
||||
}
|
||||
}
|
||||
@@ -121,6 +121,7 @@ namespace AssetRipper.TextureDecoder.Atc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static T ReadAtOffset<T>(this ReadOnlySpan<byte> input, int offset) where T : unmanaged
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return MemoryMarshal.Read<T>(input.Slice(offset));
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,22 @@ public static class Bc1
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 8;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
@@ -18,13 +34,14 @@ public static class Bc1
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), output.Slice(outputOffset), width * 4);
|
||||
BcHelpers.DecompressBc1(input.Slice(inputOffset, BlockSize), buffer);
|
||||
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,22 @@ public static class Bc2
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
@@ -18,13 +34,14 @@ public static class Bc2
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
BcHelpers.DecompressBc2(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
|
||||
BcHelpers.DecompressBc2(input.Slice(inputOffset), buffer);
|
||||
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,22 @@ public static class Bc3
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
@@ -18,13 +34,14 @@ public static class Bc3
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
BcHelpers.DecompressBc3(input.Slice(inputOffset), output.Slice(outputOffset), width * 4);
|
||||
BcHelpers.DecompressBc3(input.Slice(inputOffset), buffer);
|
||||
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,22 @@ public static class Bc4
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 8;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorR<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
@@ -19,21 +35,22 @@ public static class Bc4
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
int bufferSize = width * height * Unsafe.SizeOf<ColorR<byte>>();
|
||||
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
|
||||
int naturalSize = width * height * PixelSize;
|
||||
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
|
||||
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorR<byte>>();
|
||||
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer.Slice(bufferOffset), width);
|
||||
BcHelpers.DecompressBc4(input.Slice(inputOffset), buffer);
|
||||
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(bufferArray);
|
||||
RgbConverter.Convert<ColorR<byte>, byte, ColorBGRA32, byte>(naturalPixels, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(rentedArray);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,22 @@ public static class Bc5
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRG<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
@@ -19,21 +35,22 @@ public static class Bc5
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
int bufferSize = width * height * Unsafe.SizeOf<ColorRG<byte>>();
|
||||
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
|
||||
int naturalSize = width * height * PixelSize;
|
||||
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
|
||||
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int bufferOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRG<byte>>();
|
||||
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer.Slice(bufferOffset), width * 2);
|
||||
BcHelpers.DecompressBc5(input.Slice(inputOffset), buffer);
|
||||
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(buffer, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(bufferArray);
|
||||
RgbConverter.Convert<ColorRG<byte>, byte, ColorBGRA32, byte>(naturalPixels, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(rentedArray);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,23 @@ public static class Bc6h
|
||||
/// The size of an encoded block, in bytes.
|
||||
/// </summary>
|
||||
public const int BlockSize = 16;
|
||||
/// <summary>
|
||||
/// The width of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockWidth = 4;
|
||||
/// <summary>
|
||||
/// The height of a decoded block, in pixels.
|
||||
/// </summary>
|
||||
private const int BlockHeight = 4;
|
||||
/// <summary>
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGB<Half>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, out byte[] output)
|
||||
{
|
||||
@@ -19,25 +36,27 @@ public static class Bc6h
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, bool isSigned, Span<byte> output)
|
||||
{
|
||||
int bufferSize = width * height * Unsafe.SizeOf<ColorRGB<Half>>();
|
||||
byte[] bufferArray = ArrayPool<byte>.Shared.Rent(bufferSize);
|
||||
Span<byte> buffer = new Span<byte>(bufferArray, 0, bufferSize);
|
||||
int naturalSize = width * height * PixelSize;
|
||||
byte[] rentedArray = ArrayPool<byte>.Shared.Rent(naturalSize);
|
||||
Span<byte> naturalPixels = new Span<byte>(rentedArray, 0, naturalSize);
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += 4)
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += 4)
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * Unsafe.SizeOf<ColorRGB<Half>>();
|
||||
BcHelpers.DecompressBc6h(
|
||||
input.Slice(inputOffset, BlockSize),
|
||||
buffer.Slice(outputOffset),
|
||||
width * 3,
|
||||
buffer,
|
||||
isSigned);
|
||||
|
||||
BcHelpers.CopyBufferToOutput(buffer, naturalPixels, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
|
||||
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(buffer, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(bufferArray);
|
||||
RgbConverter.Convert<ColorRGB<Half>, Half, ColorBGRA32, byte>(naturalPixels, width, height, output);
|
||||
ArrayPool<byte>.Shared.Return(rentedArray);
|
||||
return inputOffset;
|
||||
}
|
||||
|
||||
|
||||
@@ -211,7 +211,7 @@ internal static class Bc6hTables
|
||||
]
|
||||
];
|
||||
|
||||
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
|
||||
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
}
|
||||
@@ -21,6 +21,10 @@ public static class Bc7
|
||||
/// The size of the natural pixel type.
|
||||
/// </summary>
|
||||
private static int PixelSize => Unsafe.SizeOf<ColorRGBA<byte>>();
|
||||
/// <summary>
|
||||
/// The size of a decoded block, in bytes.
|
||||
/// </summary>
|
||||
internal static int DecodedBlockSize => BlockWidth * BlockHeight * PixelSize;
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, out byte[] output)
|
||||
{
|
||||
@@ -30,16 +34,19 @@ public static class Bc7
|
||||
|
||||
public static int Decompress(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[DecodedBlockSize];
|
||||
|
||||
int inputOffset = 0;
|
||||
for (int i = 0; i < height; i += BlockHeight)
|
||||
{
|
||||
for (int j = 0; j < width; j += BlockWidth)
|
||||
{
|
||||
int outputOffset = ((i * width) + j) * PixelSize;
|
||||
BcHelpers.DecompressBc7(
|
||||
input.Slice(inputOffset, BlockSize),
|
||||
output.Slice(outputOffset),
|
||||
width * 4);
|
||||
buffer);
|
||||
|
||||
BcHelpers.CopyBufferToOutput(buffer, output, width, height, j, i, BlockWidth, BlockHeight, PixelSize);
|
||||
|
||||
inputOffset += BlockSize;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -789,11 +789,11 @@ internal static class Bc7Tables
|
||||
]
|
||||
];
|
||||
|
||||
internal static int[] AWeight2 { get; } = [0, 21, 43, 64];
|
||||
internal static ReadOnlySpan<int> AWeight2 => [0, 21, 43, 64];
|
||||
|
||||
internal static int[] AWeight3 { get; } = [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
internal static ReadOnlySpan<int> AWeight3 => [0, 9, 18, 27, 37, 46, 55, 64];
|
||||
|
||||
internal static int[] AWeight4 { get; } = [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
internal static ReadOnlySpan<int> AWeight4 => [0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64];
|
||||
|
||||
internal const byte bcdec_bc7_sModeHasPBits = 0b11001011;
|
||||
}
|
||||
@@ -1,47 +1,56 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
|
||||
internal static class BcHelpers
|
||||
{
|
||||
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc1(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc1.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 4;
|
||||
ColorBlock(compressedBlock, decompressedBlock, destinationPitch, 0);
|
||||
}
|
||||
|
||||
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc2(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc2.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 4;
|
||||
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
|
||||
SharpAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch);
|
||||
}
|
||||
|
||||
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc3(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc3.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 4;
|
||||
ColorBlock(compressedBlock.Slice(8), decompressedBlock, destinationPitch, 1);
|
||||
SmoothAlphaBlock(compressedBlock, decompressedBlock.Slice(3), destinationPitch, 4);
|
||||
}
|
||||
|
||||
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc4(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc4.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 1;
|
||||
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 1);
|
||||
}
|
||||
|
||||
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc5(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
Debug.Assert(decompressedBlock.Length == Bc5.DecodedBlockSize);
|
||||
int destinationPitch = 4 * 2;
|
||||
SmoothAlphaBlock(compressedBlock, decompressedBlock, destinationPitch, 2);
|
||||
SmoothAlphaBlock(compressedBlock.Slice(8), decompressedBlock.Slice(1), destinationPitch, 2);
|
||||
}
|
||||
|
||||
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch, bool isSigned)
|
||||
public static void DecompressBc6h(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, bool isSigned)
|
||||
{
|
||||
BitStream bstream = new()
|
||||
{
|
||||
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
|
||||
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
|
||||
};
|
||||
Span<int> r = stackalloc int[4]; // wxyz
|
||||
Span<int> g = stackalloc int[4];
|
||||
Span<int> b = stackalloc int[4];
|
||||
Debug.Assert(decompressedBlock.Length == Bc6h.DecodedBlockSize);
|
||||
|
||||
BitStream bstream = new(compressedBlock);
|
||||
Span<uint> r = stackalloc uint[4]; // wxyz
|
||||
Span<uint> g = stackalloc uint[4];
|
||||
Span<uint> b = stackalloc uint[4];
|
||||
|
||||
int decompressedOffset = 0;
|
||||
|
||||
@@ -52,7 +61,7 @@ internal static class BcHelpers
|
||||
int mode;
|
||||
|
||||
//modes >= 11 (10 in my code) are using 0 one, others will read it from the bitstream
|
||||
int partition;
|
||||
uint partition;
|
||||
|
||||
switch (ReadBc6hModeBits(ref bstream))
|
||||
{
|
||||
@@ -452,15 +461,7 @@ internal static class BcHelpers
|
||||
// Do not use these in your encoder. If the hardware is passed blocks
|
||||
// with one of these modes specified, the resulting decompressed block
|
||||
// must contain all zeroes in all channels except for the alpha channel.
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
Span<byte> pixel = decompressedBlock.Slice(decompressedOffset + (j * 3 * sizeof(ushort)), 3 * sizeof(ushort));
|
||||
pixel.Clear();
|
||||
}
|
||||
decompressedOffset += destinationPitch * sizeof(ushort);
|
||||
}
|
||||
decompressedBlock.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -477,7 +478,7 @@ internal static class BcHelpers
|
||||
|
||||
// Mode 11 (like Mode 10) does not use delta compression,
|
||||
// and instead stores both color endpoints explicitly.
|
||||
if ((mode != 9 && mode != 10) || isSigned)
|
||||
if ((mode is not 9 and not 10) || isSigned)
|
||||
{
|
||||
for (int i = 1; i < (numPartitions + 1) * 2; ++i)
|
||||
{
|
||||
@@ -487,7 +488,7 @@ internal static class BcHelpers
|
||||
}
|
||||
}
|
||||
|
||||
if (mode != 9 && mode != 10)
|
||||
if (mode is not 9 and not 10)
|
||||
{
|
||||
for (int i = 1; i < (numPartitions + 1) * 2; ++i)
|
||||
{
|
||||
@@ -520,7 +521,7 @@ internal static class BcHelpers
|
||||
}
|
||||
partitionSet &= 0x01;
|
||||
|
||||
int index = bstream.ReadBits(indexBits);
|
||||
int index = bstream.ReadBits(indexBits).ToSigned();
|
||||
|
||||
int ep_i = (partitionSet * 2);
|
||||
|
||||
@@ -539,15 +540,15 @@ internal static class BcHelpers
|
||||
BinaryPrimitives.WriteUInt16LittleEndian(decompressedBlock.Slice(bOffset), bFinal);
|
||||
}
|
||||
|
||||
decompressedOffset += destinationPitch * sizeof(ushort);
|
||||
decompressedOffset += 4 * 3 * sizeof(ushort);
|
||||
}
|
||||
|
||||
static int ReadBc6hModeBits(ref BitStream bstream)
|
||||
static uint ReadBc6hModeBits(ref BitStream bstream)
|
||||
{
|
||||
int twoBits = bstream.ReadBits(2);
|
||||
uint twoBits = bstream.ReadBits(2);
|
||||
if (twoBits > 1)
|
||||
{
|
||||
int threeBits = bstream.ReadBits(3);
|
||||
uint threeBits = bstream.ReadBits(3);
|
||||
return (threeBits << 2) | twoBits;
|
||||
}
|
||||
else
|
||||
@@ -557,61 +558,54 @@ internal static class BcHelpers
|
||||
}
|
||||
}
|
||||
|
||||
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock, int destinationPitch)
|
||||
public static void DecompressBc7(ReadOnlySpan<byte> compressedBlock, Span<byte> decompressedBlock)
|
||||
{
|
||||
BitStream bstream = new()
|
||||
{
|
||||
low = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock),
|
||||
high = BinaryPrimitives.ReadUInt64LittleEndian(compressedBlock.Slice(sizeof(ulong)))
|
||||
};
|
||||
Span2D<int> endpoints = new(stackalloc int[6 * 4], 6, 4);
|
||||
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
|
||||
Debug.Assert(decompressedBlock.Length == Bc7.DecodedBlockSize);
|
||||
|
||||
int decompressedOffset = 0;
|
||||
BitStream bstream = new(compressedBlock);
|
||||
Span2D<uint> endpoints = new(stackalloc uint[6 * 4], 6, 4);
|
||||
Span2D<int> indices = new(stackalloc int[4 * 4], 4, 4);
|
||||
|
||||
int mode = ReadBc7Mode(ref bstream);
|
||||
|
||||
// unexpected mode, clear the block (transparent black)
|
||||
if (mode >= 8)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
{
|
||||
for (int j = 0; j < 4; ++j)
|
||||
{
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 0] = 0;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 1] = 0;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 2] = 0;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 3] = 0;
|
||||
}
|
||||
decompressedOffset += destinationPitch;
|
||||
}
|
||||
decompressedBlock.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
int partition = 0;
|
||||
uint partition = 0;
|
||||
int numPartitions = 1;
|
||||
int rotation = 0;
|
||||
int indexSelectionBit = 0;
|
||||
uint rotation = 0;
|
||||
uint indexSelectionBit = 0;
|
||||
|
||||
if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 7)
|
||||
switch (mode)
|
||||
{
|
||||
numPartitions = (mode == 0 || mode == 2) ? 3 : 2;
|
||||
partition = bstream.ReadBits((mode == 0) ? 4 : 6);
|
||||
case 0:
|
||||
numPartitions = 3;
|
||||
partition = bstream.ReadBits(4);
|
||||
break;
|
||||
case 1 or 3 or 7:
|
||||
numPartitions = 2;
|
||||
partition = bstream.ReadBits(6);
|
||||
break;
|
||||
case 2:
|
||||
numPartitions = 3;
|
||||
partition = bstream.ReadBits(6);
|
||||
break;
|
||||
case 4:
|
||||
rotation = bstream.ReadBits(2);
|
||||
indexSelectionBit = bstream.ReadBit();
|
||||
break;
|
||||
case 5:
|
||||
rotation = bstream.ReadBits(2);
|
||||
break;
|
||||
}
|
||||
|
||||
int numEndpoints = numPartitions * 2;
|
||||
|
||||
if (mode == 4 || mode == 5)
|
||||
{
|
||||
rotation = bstream.ReadBits(2);
|
||||
|
||||
if (mode == 4)
|
||||
{
|
||||
indexSelectionBit = bstream.ReadBit();
|
||||
}
|
||||
}
|
||||
|
||||
// Extract endpoints
|
||||
// RGB
|
||||
for (int i = 0; i < 3; ++i)
|
||||
@@ -646,8 +640,8 @@ internal static class BcHelpers
|
||||
// if P-bit is shared
|
||||
if (mode == 1)
|
||||
{
|
||||
int i = bstream.ReadBit();
|
||||
int j = bstream.ReadBit();
|
||||
uint i = bstream.ReadBit();
|
||||
uint j = bstream.ReadBit();
|
||||
|
||||
// rgb component-wise insert pbits
|
||||
for (int k = 0; k < 3; ++k)
|
||||
@@ -663,7 +657,7 @@ internal static class BcHelpers
|
||||
// unique P-bit per endpoint
|
||||
for (int i = 0; i < numEndpoints; ++i)
|
||||
{
|
||||
int j = bstream.ReadBit();
|
||||
uint j = bstream.ReadBit();
|
||||
for (int k = 0; k < 4; ++k)
|
||||
{
|
||||
endpoints[i, k] |= j;
|
||||
@@ -705,10 +699,25 @@ internal static class BcHelpers
|
||||
}
|
||||
|
||||
// Determine weights tables
|
||||
int indexBits = (mode == 0 || mode == 1) ? 3 : ((mode == 6) ? 4 : 2);
|
||||
int indexBits2 = (mode == 4) ? 3 : ((mode == 5) ? 2 : 0);
|
||||
int[] weights = (indexBits == 2) ? Bc7Tables.AWeight2 : ((indexBits == 3) ? Bc7Tables.AWeight3 : Bc7Tables.AWeight4);
|
||||
int[] weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3;
|
||||
int indexBits = mode switch
|
||||
{
|
||||
0 or 1 => 3,
|
||||
6 => 4,
|
||||
_ => 2
|
||||
};
|
||||
int indexBits2 = mode switch
|
||||
{
|
||||
4 => 3,
|
||||
5 => 2,
|
||||
_ => 0
|
||||
};
|
||||
ReadOnlySpan<int> weights = indexBits switch
|
||||
{
|
||||
2 => Bc7Tables.AWeight2,
|
||||
3 => Bc7Tables.AWeight3,
|
||||
_ => Bc7Tables.AWeight4
|
||||
};
|
||||
ReadOnlySpan<int> weights2 = (indexBits2 == 2) ? Bc7Tables.AWeight2 : Bc7Tables.AWeight3;
|
||||
|
||||
// Quite inconvenient that indices aren't interleaved so we have to make 2 passes here
|
||||
// Pass #1: collecting color indices
|
||||
@@ -726,7 +735,7 @@ internal static class BcHelpers
|
||||
indexBits--;
|
||||
}
|
||||
|
||||
indices[i, j] = bstream.ReadBits(indexBits);
|
||||
indices[i, j] = bstream.ReadBits(indexBits).ToSigned();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,10 +751,10 @@ internal static class BcHelpers
|
||||
|
||||
int index = indices[i, j];
|
||||
|
||||
int r;
|
||||
int g;
|
||||
int b;
|
||||
int a;
|
||||
uint r;
|
||||
uint g;
|
||||
uint b;
|
||||
uint a;
|
||||
|
||||
if (indexBits2 == 0)
|
||||
{
|
||||
@@ -756,7 +765,7 @@ internal static class BcHelpers
|
||||
}
|
||||
else
|
||||
{
|
||||
int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1));
|
||||
int index2 = bstream.ReadBits((i | j) != 0 ? indexBits2 : (indexBits2 - 1)).ToSigned();
|
||||
// The index value for interpolating color comes from the secondary index bits for the texel
|
||||
// if the mode has an index selection bit and its value is one, and from the primary index bits otherwise.
|
||||
// The alpha index comes from the secondary index bits if the block has a secondary index
|
||||
@@ -800,23 +809,23 @@ internal static class BcHelpers
|
||||
break;
|
||||
}
|
||||
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 0] = (byte)r;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 1] = (byte)g;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 2] = (byte)b;
|
||||
decompressedBlock[decompressedOffset + (j * 4) + 3] = (byte)a;
|
||||
int offset = (i * 16) + (j * 4);
|
||||
decompressedBlock[offset + 0] = (byte)r;
|
||||
decompressedBlock[offset + 1] = (byte)g;
|
||||
decompressedBlock[offset + 2] = (byte)b;
|
||||
decompressedBlock[offset + 3] = (byte)a;
|
||||
}
|
||||
|
||||
decompressedOffset += destinationPitch;
|
||||
}
|
||||
|
||||
static int ReadBc7Mode(ref BitStream bstream)
|
||||
{
|
||||
int i = 0;
|
||||
while (i < 8 && bstream.ReadBit() == 0)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
// Read bits until we find a non-zero bit, up to 8 bits.
|
||||
// The mode is the number of bits read.
|
||||
uint eightBits = bstream.PeakBits(8);
|
||||
int zeros = BitOperations.TrailingZeroCount(eightBits);
|
||||
int bitCount = zeros >= 8 ? 8 : zeros + 1;
|
||||
bstream.Advance(bitCount);
|
||||
return int.Min(zeros, 8);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -949,6 +958,11 @@ internal static class BcHelpers
|
||||
return (val << (32 - bits)) >> (32 - bits);
|
||||
}
|
||||
|
||||
private static uint ExtendSign(uint val, int bits)
|
||||
{
|
||||
return ExtendSign(val.ToSigned(), bits).ToUnsigned();
|
||||
}
|
||||
|
||||
public static int TransformInverse(int val, int a0, int bits, bool isSigned)
|
||||
{
|
||||
// If the precision of A0 is "p" bits, then the transform algorithm is:
|
||||
@@ -961,6 +975,11 @@ internal static class BcHelpers
|
||||
return val;
|
||||
}
|
||||
|
||||
private static uint TransformInverse(uint val, uint a0, int bits, bool isSigned)
|
||||
{
|
||||
return TransformInverse(val.ToSigned(), a0.ToSigned(), bits, isSigned).ToUnsigned();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Essentially copy-paste from documentation
|
||||
/// </summary>
|
||||
@@ -1028,11 +1047,21 @@ internal static class BcHelpers
|
||||
return unq;
|
||||
}
|
||||
|
||||
private static uint Unquantize(uint val, int bits, bool isSigned)
|
||||
{
|
||||
return Unquantize(val.ToSigned(), bits, isSigned).ToUnsigned();
|
||||
}
|
||||
|
||||
public static int Interpolate(int a, int b, ReadOnlySpan<int> weights, int index)
|
||||
{
|
||||
return ((a * (64 - weights[index])) + (b * weights[index]) + 32) >> 6;
|
||||
}
|
||||
|
||||
private static uint Interpolate(uint a, uint b, ReadOnlySpan<int> weights, int index)
|
||||
{
|
||||
return Interpolate(a.ToSigned(), b.ToSigned(), weights, index).ToUnsigned();
|
||||
}
|
||||
|
||||
public static ushort FinishUnquantize(int val, bool isSigned)
|
||||
{
|
||||
if (!isSigned)
|
||||
@@ -1041,7 +1070,7 @@ internal static class BcHelpers
|
||||
}
|
||||
else
|
||||
{
|
||||
val = (val < 0) ? -(((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32
|
||||
val = (val < 0) ? (((-val) * 31) >> 5) : (val * 31) >> 5; // scale the magnitude by 31 / 32
|
||||
int s = 0;
|
||||
if (val < 0)
|
||||
{
|
||||
@@ -1052,8 +1081,30 @@ internal static class BcHelpers
|
||||
}
|
||||
}
|
||||
|
||||
public static void SwapValues(ref int a, ref int b)
|
||||
private static ushort FinishUnquantize(uint val, bool isSigned)
|
||||
{
|
||||
return FinishUnquantize(val.ToSigned(), isSigned);
|
||||
}
|
||||
|
||||
public static void SwapValues(ref uint a, ref uint b)
|
||||
{
|
||||
(a, b) = (b, a);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is for copying a decoded block to the output buffer. It handles the pitch and non-4x4 blocks (if necessary).
|
||||
/// </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static void CopyBufferToOutput(Span<byte> buffer, Span<byte> output, int width, int height, int w, int h, int blockWidth, int blockHeight, int pixelSize)
|
||||
{
|
||||
int hEnd = Math.Min(h + blockHeight, height);
|
||||
int wEnd = Math.Min(w + blockWidth, width);
|
||||
for (int i = h; i < hEnd; i++)
|
||||
{
|
||||
int outputOffset = ((i * width) + w) * pixelSize;
|
||||
int outputBufferOffset = ((i - h) * blockWidth) * pixelSize;
|
||||
int copyLength = (wEnd - w) * pixelSize;
|
||||
buffer.Slice(outputBufferOffset, copyLength).CopyTo(output.Slice(outputOffset, copyLength));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,59 @@
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
using System.Buffers.Binary;
|
||||
|
||||
internal struct BitStream
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
|
||||
internal ref struct BitStream
|
||||
{
|
||||
public ulong low;
|
||||
public ulong high;
|
||||
|
||||
public int ReadBits(int numBits)
|
||||
private UInt128 value;
|
||||
|
||||
private readonly uint BottomBits
|
||||
{
|
||||
uint mask = (uint)((1 << numBits) - 1);
|
||||
// Read the low N bits
|
||||
uint bits = (uint)(this.low & mask);
|
||||
|
||||
this.low >>= numBits;
|
||||
// Put the low N bits of "high" into the high 64-N bits of "low".
|
||||
this.low |= (this.high & mask) << ((sizeof(ulong) * 8) - numBits);
|
||||
this.high >>= numBits;
|
||||
|
||||
return (int)bits;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
get => unchecked((uint)this.value);
|
||||
}
|
||||
|
||||
public int ReadBit()
|
||||
public BitStream(UInt128 value)
|
||||
{
|
||||
return this.ReadBits(1);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public BitStream(ReadOnlySpan<byte> span) : this(BinaryPrimitives.ReadUInt128LittleEndian(span))
|
||||
{
|
||||
}
|
||||
|
||||
public BitStream(ulong low, ulong high)
|
||||
{
|
||||
value = new UInt128(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public readonly uint PeakBits(int numBits)
|
||||
{
|
||||
uint mask = (1u << numBits) - 1u;
|
||||
uint bits = BottomBits & mask;
|
||||
return bits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void Advance(int numBits)
|
||||
{
|
||||
this.value >>= numBits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public uint ReadBits(int numBits)
|
||||
{
|
||||
uint bits = PeakBits(numBits);
|
||||
Advance(numBits);
|
||||
return bits;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public uint ReadBit()
|
||||
{
|
||||
uint result = BottomBits & 1u;
|
||||
Advance(1);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -29,17 +61,11 @@ internal struct BitStream
|
||||
/// </summary>
|
||||
/// <param name="numBits"></param>
|
||||
/// <returns></returns>
|
||||
public int ReadBitsReversed(int numBits)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public uint ReadBitsReversed(int numBits)
|
||||
{
|
||||
int bits = this.ReadBits(numBits);
|
||||
uint bits = this.ReadBits(numBits);
|
||||
// Reverse the bits.
|
||||
int result = 0;
|
||||
while (numBits-- != 0)
|
||||
{
|
||||
result <<= 1;
|
||||
result |= bits & 1;
|
||||
bits >>= 1;
|
||||
}
|
||||
return result;
|
||||
return bits.ReverseBits() >> (32 - numBits);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace AssetRipper.TextureDecoder.Bc;
|
||||
|
||||
internal static class NumericExtensions
|
||||
{
|
||||
public static uint ReverseBits(this uint n)
|
||||
{
|
||||
n = (n >> 1) & 0x55555555 | (n << 1) & 0xAAAAAAAA;
|
||||
n = (n >> 2) & 0x33333333 | (n << 2) & 0xCCCCCCCC;
|
||||
n = (n >> 4) & 0x0F0F0F0F | (n << 4) & 0xF0F0F0F0;
|
||||
n = (n >> 8) & 0x00FF00FF | (n << 8) & 0xFF00FF00;
|
||||
n = (n >> 16) & 0x0000FFFF | (n << 16) & 0xFFFF0000;
|
||||
return n;
|
||||
}
|
||||
|
||||
public static uint ToUnsigned(this int value)
|
||||
{
|
||||
return unchecked((uint)value);
|
||||
}
|
||||
|
||||
public static int ToSigned(this uint value)
|
||||
{
|
||||
return unchecked((int)value);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressDXT1<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
@@ -185,6 +186,7 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressDXT3<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
@@ -316,6 +318,7 @@ namespace AssetRipper.TextureDecoder.Dxt
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressDXT5<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressETC(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -50,6 +51,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressETC2(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -90,6 +92,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressETC2A1(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -130,6 +133,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressETC2A8(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -171,6 +175,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressEACRUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -215,6 +220,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressEACRSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 8;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -259,6 +265,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressEACRGUnsigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int bcw = (width + 3) / 4;
|
||||
int bch = (height + 3) / 4;
|
||||
|
||||
@@ -304,6 +311,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
public static int DecompressEACRGSigned(ReadOnlySpan<byte> input, int width, int height, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int requiredLength = ((width + 3) / 4) * ((height + 3) / 4) * 16;
|
||||
if (input.Length < requiredLength)
|
||||
{
|
||||
@@ -741,6 +749,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
private static void DecodeEtc2a8Block(ReadOnlySpan<byte> input, Span<uint> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int @base = input[0];
|
||||
int data1 = input[1];
|
||||
int mul = data1 >> 4;
|
||||
@@ -761,6 +770,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
private static void DecodeEacUnsignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int @base = input[0];
|
||||
int data1 = input[1];
|
||||
int mul = data1 >> 4;
|
||||
@@ -778,6 +788,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
|
||||
private static void DecodeEacSignedBlock(ReadOnlySpan<byte> input, Span<uint> output, int channel)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int @base = 127 + unchecked((sbyte)input[0]);
|
||||
int data1 = input[1];
|
||||
int mul = data1 >> 4;
|
||||
@@ -805,7 +816,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void DecodeEac11Block(Span<byte> output, int @base, int ti, int mul, ulong l)
|
||||
{
|
||||
ReadOnlySpan<sbyte> table = Etc2AlphaModTable.AsSpan(ti * 8, 8);
|
||||
ReadOnlySpan<sbyte> table = Etc2AlphaModTable.Slice(ti * 8, 8);
|
||||
for (int i = 0; i < 16; i++, l >>= 3)
|
||||
{
|
||||
int val = @base + mul * table[unchecked((int)(l & 0b111))];
|
||||
@@ -849,14 +860,14 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
return data[7] | (uint)data[6] << 8 | (uint)data[5] << 16 | (uint)data[4] << 24 | (ulong)data[3] << 32 | (ulong)data[2] << 40;
|
||||
}
|
||||
|
||||
private static readonly byte[] WriteOrderTable = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
|
||||
private static readonly byte[] WriteOrderTableRev = [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
|
||||
private static readonly int[] Etc1SubblockTable =
|
||||
private static ReadOnlySpan<byte> WriteOrderTable => [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
|
||||
private static ReadOnlySpan<byte> WriteOrderTableRev => [15, 11, 7, 3, 14, 10, 6, 2, 13, 9, 5, 1, 12, 8, 4, 0];
|
||||
private static ReadOnlySpan<int> Etc1SubblockTable =>
|
||||
[
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1,
|
||||
];
|
||||
private static readonly int[] Etc1ModifierTable =
|
||||
private static ReadOnlySpan<int> Etc1ModifierTable =>
|
||||
[
|
||||
2, 8, -2, -8,
|
||||
5, 17, -5, -17,
|
||||
@@ -867,7 +878,7 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
33, 106, -33, -106,
|
||||
47, 183, -47, -183,
|
||||
];
|
||||
private static readonly int[] PunchthroughModifierTable =
|
||||
private static ReadOnlySpan<int> PunchthroughModifierTable =>
|
||||
[
|
||||
0, 8, 0, -8,
|
||||
0, 17, 0, -17,
|
||||
@@ -878,8 +889,8 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
0, 106, 0, -106,
|
||||
0, 183, 0, -183,
|
||||
];
|
||||
private static readonly byte[] Etc2DistanceTable = [3, 6, 11, 16, 23, 32, 41, 64];
|
||||
private static readonly sbyte[] Etc2AlphaModTable =
|
||||
private static ReadOnlySpan<byte> Etc2DistanceTable => [3, 6, 11, 16, 23, 32, 41, 64];
|
||||
private static ReadOnlySpan<sbyte> Etc2AlphaModTable =>
|
||||
[
|
||||
-3, -6, -9, -15, 2, 5, 8, 14,
|
||||
-3, -7, -10, -13, 2, 6, 9, 12,
|
||||
@@ -898,6 +909,6 @@ namespace AssetRipper.TextureDecoder.Etc
|
||||
-4, -6, -8, -9, 3, 5, 7, 8,
|
||||
-3, -5, -7, -9, 2, 4, 6, 8,
|
||||
];
|
||||
private static readonly uint[] PunchthroughMaskTable = [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF];
|
||||
private static ReadOnlySpan<uint> PunchthroughMaskTable => [0xFFFFFFFF, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF];
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,7 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static int DecompressPVRTC(ReadOnlySpan<byte> input, int xDim, int yDim, bool do2bitMode, Span<byte> output)
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
int xBlockSize = do2bitMode ? BlockX2bpp : BlockX4bpp;
|
||||
// for MBX don't allow the sizes to get too small
|
||||
int blockXDim = Math.Max(2, xDim / xBlockSize);
|
||||
@@ -217,36 +218,36 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
int modVal;
|
||||
if (modulationModes[y * 16 + x] == 0)
|
||||
{
|
||||
modVal = m_repVals0[modulationVals[y * 16 + x]];
|
||||
modVal = RepVals0[modulationVals[y * 16 + x]];
|
||||
}
|
||||
else if (do2bitMode)
|
||||
{
|
||||
// if this is a stored value
|
||||
if (((x ^ y) & 1) == 0)
|
||||
{
|
||||
modVal = m_repVals0[modulationVals[y * 16 + x]];
|
||||
modVal = RepVals0[modulationVals[y * 16 + x]];
|
||||
}
|
||||
// else average from the neighbours if H&V interpolation..
|
||||
else if (modulationModes[y * 16 + x] == 1)
|
||||
{
|
||||
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] +
|
||||
m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 2) / 4;
|
||||
modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] +
|
||||
RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 2) / 4;
|
||||
}
|
||||
// else if H-Only
|
||||
else if (modulationModes[y * 16 + x] == 2)
|
||||
{
|
||||
modVal = (m_repVals0[modulationVals[y * 16 + x - 1]] + m_repVals0[modulationVals[y * 16 + x + 1]] + 1) / 2;
|
||||
modVal = (RepVals0[modulationVals[y * 16 + x - 1]] + RepVals0[modulationVals[y * 16 + x + 1]] + 1) / 2;
|
||||
}
|
||||
// else it's V-Only
|
||||
else
|
||||
{
|
||||
modVal = (m_repVals0[modulationVals[(y - 1) * 16 + x]] + m_repVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2;
|
||||
modVal = (RepVals0[modulationVals[(y - 1) * 16 + x]] + RepVals0[modulationVals[(y + 1) * 16 + x]] + 1) / 2;
|
||||
}
|
||||
}
|
||||
// else it's 4BPP and PT encoding
|
||||
else
|
||||
{
|
||||
modVal = m_repVals1[modulationVals[y * 16 + x]];
|
||||
modVal = RepVals1[modulationVals[y * 16 + x]];
|
||||
doPT = modulationVals[y * 16 + x] == PTIndex;
|
||||
}
|
||||
|
||||
@@ -487,7 +488,7 @@ namespace AssetRipper.TextureDecoder.Pvrtc
|
||||
private const int BlockX2bpp = 8;
|
||||
private const int BlockX4bpp = 4;
|
||||
|
||||
private static readonly int[] m_repVals0 = [0, 3, 5, 8];
|
||||
private static readonly int[] m_repVals1 = [0, 4, 4, 8];
|
||||
private static ReadOnlySpan<int> RepVals0 => [0, 3, 5, 8];
|
||||
private static ReadOnlySpan<int> RepVals1 => [0, 4, 4, 8];
|
||||
}
|
||||
}
|
||||
@@ -503,6 +503,7 @@ namespace AssetRipper.TextureDecoder.Rgb
|
||||
where TDestinationChannel : unmanaged
|
||||
where TDestinationColor : unmanaged, IColor<TDestinationChannel>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
ReadOnlySpan<TSourceColor> sourceSpan = MemoryMarshal.Cast<byte, TSourceColor>(input).Slice(0, width * height);
|
||||
Span<TDestinationColor> destinationSpan = MemoryMarshal.Cast<byte, TDestinationColor>(output).Slice(0, width * height);
|
||||
Convert<TSourceColor, TSourceChannel, TDestinationColor, TDestinationChannel>(sourceSpan, destinationSpan);
|
||||
|
||||
@@ -19,5 +19,13 @@
|
||||
throw new InvalidOperationException($"Not enough space. {spaceRequired} is required, but only {spaceAvailable} is available");
|
||||
}
|
||||
}
|
||||
|
||||
internal static void ThrowIfNotLittleEndian()
|
||||
{
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
{
|
||||
throw new PlatformNotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,7 @@ namespace AssetRipper.TextureDecoder.Yuy2
|
||||
where TOutputChannelValue : unmanaged
|
||||
where TOutputColor : unmanaged, IColor<TOutputChannelValue>
|
||||
{
|
||||
ThrowHelper.ThrowIfNotLittleEndian();
|
||||
return DecompressYUY2<TOutputColor, TOutputChannelValue>(input, width, height, MemoryMarshal.Cast<byte, TOutputColor>(output));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<LangVersion>Latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||
|
||||
<Version>2.2.0</Version>
|
||||
<Version>2.3.0</Version>
|
||||
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||
<Copyright>Copyright © ds5678</Copyright>
|
||||
<Authors>ds5678</Authors>
|
||||
|
||||
Reference in new issue
Block a user