Compare commits

..
9 Commits
Author SHA1 Message Date
ds5678 d57e3461ff Update version to 2.3.0 in Directory.Build.props
Bump the version number from 2.2.1 to 2.3.0 in the
Directory.Build.props file to reflect the latest changes.
2025-03-30 22:48:41 -07:00
ds5678 1d2d19c9ef Support Bc images with non-standard sizes
* Resolves #39
2025-03-27 01:37:00 -07:00
ds5678 f3ba84589d Update package references
Updated `AssetRipper.Text.SourceGeneration` to version `1.2.2` in `AssetRipper.TextureDecoder.TestGenerator` and `AssetRipper.TextureDecoder.ColorGenerator`.

In `AssetRipper.TextureDecoder.Tests.csproj`, the following packages were updated:
- `Microsoft.NET.Test.Sdk` from `17.10.0` to `17.13.0`
- `NUnit` from `4.1.0` to `4.3.2`
- `NUnit3TestAdapter` from `4.6.0` to `5.0.0`
- `coverlet.collector` from `6.0.2` to `6.0.4`
2025-03-26 23:44:19 -07:00
ds5678 7cee390b2e Bump to .NET 9 2025-03-26 23:41:27 -07:00
dependabot[bot] 024ac9eb8c Bump actions/setup-dotnet from 4.0.1 to 4.3.0 (#96)
Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 4.0.1 to 4.3.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v4.0.1...v4.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-26 23:39:28 -07:00
dependabot[bot] d47fe4dcfb Bump NUnit.Analyzers from 4.3.0 to 4.4.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/4.3.0...4.4.0)

---
updated-dependencies:
- dependency-name: NUnit.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-11-13 17:57:48 -08:00
ds5678 7d028ce3f5 Guard against big endian 2024-10-22 19:21:11 -07:00
dependabot[bot] beb819daa4 Bump NUnit.Analyzers from 4.2.0 to 4.3.0
Bumps [NUnit.Analyzers](https://github.com/nunit/nunit.analyzers) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/nunit/nunit.analyzers/releases)
- [Changelog](https://github.com/nunit/nunit.analyzers/blob/master/CHANGES.md)
- [Commits](https://github.com/nunit/nunit.analyzers/compare/4.2.0...4.3.0)

---
updated-dependencies:
- dependency-name: NUnit.Analyzers
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-11 19:37:00 -07:00
dependabot[bot] c9d338f159 Bump BenchmarkDotNet from 0.13.12 to 0.14.0
Bumps [BenchmarkDotNet](https://github.com/dotnet/BenchmarkDotNet) from 0.13.12 to 0.14.0.
- [Release notes](https://github.com/dotnet/BenchmarkDotNet/releases)
- [Commits](https://github.com/dotnet/BenchmarkDotNet/compare/v0.13.12...v0.14.0)

---
updated-dependencies:
- dependency-name: BenchmarkDotNet
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <[email protected]>
2024-08-06 19:33:32 -07:00
22 changed files with 319 additions and 100 deletions

No files matched your search

+2 -2
View File
@@ -14,9 +14,9 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4.0.1
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>
@@ -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>
@@ -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.10.0" />
<PackageReference Include="NUnit" Version="4.1.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
<PackageReference Include="NUnit.Analyzers" Version="4.2.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>
+80 -9
View File
@@ -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(() =>
{
@@ -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));
}
+21 -4
View File
@@ -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;
}
}
+21 -4
View File
@@ -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;
}
}
+21 -4
View File
@@ -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;
}
}
+26 -9
View File
@@ -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;
}
+26 -9
View File
@@ -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;
}
+29 -10
View File
@@ -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;
}
+10 -3
View File
@@ -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;
}
}
+47 -36
View File
@@ -1,39 +1,52 @@
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)
{
Debug.Assert(decompressedBlock.Length == Bc6h.DecodedBlockSize);
BitStream bstream = new(compressedBlock);
Span<uint> r = stackalloc uint[4]; // wxyz
Span<uint> g = stackalloc uint[4];
@@ -448,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;
}
@@ -535,7 +540,7 @@ internal static class BcHelpers
BinaryPrimitives.WriteUInt16LittleEndian(decompressedBlock.Slice(bOffset), bFinal);
}
decompressedOffset += destinationPitch * sizeof(ushort);
decompressedOffset += 4 * 3 * sizeof(ushort);
}
static uint ReadBc6hModeBits(ref BitStream bstream)
@@ -553,30 +558,20 @@ 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)
{
Debug.Assert(decompressedBlock.Length == Bc7.DecodedBlockSize);
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 decompressedOffset = 0;
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;
}
@@ -814,13 +809,12 @@ 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)
@@ -1096,4 +1090,21 @@ internal static class BcHelpers
{
(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));
}
}
}
@@ -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;
@@ -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);
@@ -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));
}
+2 -2
View File
@@ -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.1</Version>
<Version>2.3.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<Copyright>Copyright © ds5678</Copyright>
<Authors>ds5678</Authors>