-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add PNG importing of GameDataTextures (#212)
- Loading branch information
Showing
9 changed files
with
364 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Runtime.CompilerServices; | ||
using BCnEncoder.Decoder; | ||
using BCnEncoder.Shared; | ||
using Microsoft.Toolkit.HighPerformance; | ||
using Pfim; | ||
using SixLabors.ImageSharp.Formats; | ||
using SixLabors.ImageSharp.Memory; | ||
using SixLabors.ImageSharp.Metadata; | ||
|
||
namespace Refresh.GameServer.Importing.Gtf; | ||
|
||
public class GtfDecoder : ImageDecoder | ||
{ | ||
protected override Image<TPixel> Decode<TPixel>(DecoderOptions options, Stream stream, CancellationToken cancellationToken) | ||
{ | ||
Span<byte> magic = stackalloc byte[4]; | ||
|
||
stream.ReadExactly(magic); | ||
|
||
//Check magic | ||
if(!magic.SequenceEqual("GTF "u8)) ThrowInvalidImageContentException(); | ||
|
||
//Read the header in | ||
GtfHeader header = GtfHeader.Read(stream); | ||
|
||
//If we dont know what format this is, throw an error | ||
if(!Enum.IsDefined(header.PixelFormat)) ThrowInvalidImageContentException(); | ||
|
||
Image<TPixel> image = new(options.Configuration, header.Width, header.Height); | ||
Buffer2D<TPixel> pixels = image.Frames.RootFrame.PixelBuffer; | ||
|
||
this.ProcessPixels(stream, pixels, header); | ||
|
||
return image; | ||
} | ||
|
||
private void ProcessPixels<TPixel>(Stream compressedStream, Buffer2D<TPixel> pixels, GtfHeader header) where TPixel : unmanaged, IPixel<TPixel> | ||
{ | ||
TexStream decompressedStream = new(compressedStream, false); | ||
BEBinaryReader reader = new(decompressedStream); | ||
|
||
if (header.PixelFormat is GtfPixelFormat.CompressedDxt1 or GtfPixelFormat.CompressedDxt23 or GtfPixelFormat.CompressedDxt45) | ||
{ | ||
BcDecoder decoder = new(); | ||
|
||
CompressionFormat format = header.PixelFormat switch { | ||
GtfPixelFormat.CompressedDxt1 => CompressionFormat.Bc1, | ||
GtfPixelFormat.CompressedDxt23 => CompressionFormat.Bc2, | ||
GtfPixelFormat.CompressedDxt45 => CompressionFormat.Bc3, | ||
_ => throw new ArgumentOutOfRangeException(), | ||
}; | ||
|
||
// im not the happiest with this massive allocation :/ | ||
Span<ColorRgba32> colors = decoder.DecodeRaw(decompressedStream, pixels.Width, pixels.Height, format).AsSpan(); | ||
|
||
for (int y = 0; y < pixels.Height; y++) | ||
{ | ||
Span<TPixel> row = pixels.DangerousGetRowSpan(y); | ||
if (typeof(TPixel) == typeof(Rgba32)) | ||
{ | ||
Span<ColorRgba32> rgba32Row = row.Cast<TPixel, ColorRgba32>(); | ||
colors.Slice(y * header.Width, header.Width).CopyTo(rgba32Row); | ||
} | ||
else | ||
{ | ||
for (int x = 0; x < pixels.Width; x++) | ||
{ | ||
#if NET8_0_OR_GREATER | ||
#error Please move this to use Unsafe.BitCast<T>(); | ||
#endif | ||
|
||
TPixel pixel = new(); | ||
pixel.FromRgba32(Unsafe.As<ColorRgba32, Rgba32>(ref colors[y * header.Width + x])); | ||
row[x] = pixel; | ||
} | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
for (int y = 0; y < pixels.Height; y++) | ||
{ | ||
Span<TPixel> row = pixels.DangerousGetRowSpan(y); | ||
|
||
for (int x = 0; x < row.Length; x++) | ||
{ | ||
TPixel pixel = new(); | ||
switch (header.PixelFormat) | ||
{ | ||
|
||
case GtfPixelFormat.B8: | ||
pixel.FromRgba32(new Rgba32(0, 0, reader.ReadByte(), 255)); | ||
break; | ||
case GtfPixelFormat.A1R5G5B5: { | ||
ushort packed = reader.ReadUInt16(); | ||
byte a = ShiftLeftShiftingInLsb((byte)((packed & 0b1000000000000000) >> 15), 7); | ||
byte r = ShiftLeftShiftingInLsb((byte)((packed & 0b0111110000000000) >> 10), 3); | ||
byte g = ShiftLeftShiftingInLsb((byte)((packed & 0b0000001111100000) >> 5), 3); | ||
byte b = ShiftLeftShiftingInLsb((byte)(packed & 0b0000000000011111), 3); | ||
pixel.FromRgba32(new Rgba32(r, g, b, a)); | ||
break; | ||
} | ||
case GtfPixelFormat.A4R4G4B4: { | ||
ushort packed = reader.ReadUInt16(); | ||
byte a = ShiftLeftShiftingInLsb((byte)((packed & 0b1111000000000000) >> 12), 4); | ||
byte r = ShiftLeftShiftingInLsb((byte)((packed & 0b0000111100000000) >> 8), 4); | ||
byte g = ShiftLeftShiftingInLsb((byte)((packed & 0b0000000011110000) >> 4), 4); | ||
byte b = ShiftLeftShiftingInLsb((byte)(packed & 0b0000000000001111), 4); | ||
pixel.FromRgba32(new Rgba32(r, g, b, a)); | ||
break; | ||
} | ||
case GtfPixelFormat.R5G6B5: { | ||
ushort packed = reader.ReadUInt16(); | ||
byte r = ShiftLeftShiftingInLsb((byte)((packed & 0b1111100000000000) >> 11), 3); | ||
byte g = ShiftLeftShiftingInLsb((byte)((packed & 0b0000011111100000) >> 5), 2); | ||
byte b = ShiftLeftShiftingInLsb((byte)(packed & 0b0000000000011111), 3); | ||
pixel.FromRgba32(new Rgba32(r, g, b, 255)); | ||
break; | ||
} | ||
case GtfPixelFormat.A8R8G8B8: { | ||
byte a = reader.ReadByte(); | ||
byte r = reader.ReadByte(); | ||
byte b = reader.ReadByte(); | ||
byte g = reader.ReadByte(); | ||
pixel.FromRgba32(new Rgba32(r, g, b, a)); | ||
break; | ||
} | ||
case GtfPixelFormat.G8B8: | ||
pixel.FromRgba32(new Rgba32(0, reader.ReadByte(), reader.ReadByte(), 255)); | ||
break; | ||
case GtfPixelFormat.R6G5B5: { | ||
ushort packed = reader.ReadUInt16(); | ||
byte r = ShiftLeftShiftingInLsb((byte)((packed & 0b1111110000000000) >> 10), 2); | ||
byte g = ShiftLeftShiftingInLsb((byte)((packed & 0b0000001111100000) >> 5), 3); | ||
byte b = ShiftLeftShiftingInLsb((byte)(packed & 0b0000000000011111), 3); | ||
pixel.FromRgba32(new Rgba32(r, g, b, 255)); | ||
break; | ||
} | ||
case GtfPixelFormat.R5G5B5A1: { | ||
ushort packed = reader.ReadUInt16(); | ||
byte r = ShiftLeftShiftingInLsb((byte)((packed & 0b1111100000000000) >> 11), 3); | ||
byte g = ShiftLeftShiftingInLsb((byte)((packed & 0b0000011111000000) >> 6), 3); | ||
byte b = ShiftLeftShiftingInLsb((byte)((packed & 0b0000000000111110) >> 1), 3); | ||
byte a = ShiftLeftShiftingInLsb((byte)(packed & 0b0000000000000001), 7); | ||
pixel.FromRgba32(new Rgba32(r, g, b, a)); | ||
break; | ||
} | ||
default: | ||
throw new ArgumentOutOfRangeException(); | ||
} | ||
row[x] = pixel; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Shifts a number to the left, shifting the LSB in | ||
/// </summary> | ||
/// <param name="num">The number to shift</param> | ||
/// <param name="amt">The amount of bits to shift</param> | ||
/// <returns>The shifted number</returns> | ||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] | ||
private static byte ShiftLeftShiftingInLsb(byte num, byte amt) | ||
{ | ||
int mask = (1 << amt) - 1; | ||
return (byte)((num << amt) | ((num & 1) * mask)); | ||
} | ||
|
||
protected override Image Decode(DecoderOptions options, Stream stream, CancellationToken cancellationToken) | ||
{ | ||
return this.Decode<Rgba32>(options, stream, cancellationToken); | ||
} | ||
|
||
protected override ImageInfo Identify(DecoderOptions options, Stream stream, CancellationToken cancellationToken) | ||
{ | ||
Span<byte> magic = stackalloc byte[4]; | ||
|
||
stream.ReadExactly(magic); | ||
|
||
//Check magic | ||
if(!magic.SequenceEqual("GTF "u8)) ThrowInvalidImageContentException(); | ||
|
||
//Read the header in | ||
GtfHeader header = GtfHeader.Read(stream); | ||
|
||
//If we dont know what format this is, throw an error | ||
if(!Enum.IsDefined(header.PixelFormat)) ThrowInvalidImageContentException(); | ||
|
||
return new ImageInfo( | ||
new PixelTypeInfo(header.PixelFormat.BitsPerPixel()), | ||
new Size(header.Width, header.Height), | ||
new ImageMetadata | ||
{ | ||
HorizontalResolution = header.Width, | ||
VerticalResolution = header.Height, | ||
ResolutionUnits = PixelResolutionUnit.AspectRatio, | ||
} | ||
); | ||
} | ||
|
||
[DoesNotReturn] | ||
private static void ThrowInvalidImageContentException() | ||
=> throw new InvalidImageContentException("The image is not a valid GTF image."); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using SixLabors.ImageSharp.Formats; | ||
|
||
namespace Refresh.GameServer.Importing.Gtf; | ||
|
||
public class GtfFormat : IImageFormat<GtfMetadata> | ||
{ | ||
public GtfMetadata CreateDefaultFormatMetadata() => new(); | ||
|
||
public string Name => "GTF"; | ||
public string DefaultMimeType => ""; | ||
public IEnumerable<string> MimeTypes => new string[] { }; | ||
public IEnumerable<string> FileExtensions => new[] { "GTF" }; | ||
|
||
public static IImageFormat Instance { get; } = new GtfFormat(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
namespace Refresh.GameServer.Importing.Gtf; | ||
|
||
public struct GtfHeader | ||
{ | ||
public GtfPixelFormat PixelFormat; | ||
public bool Mipmap; | ||
public byte Dimension; | ||
public bool Cubemap; | ||
public uint Remap; | ||
public ushort Width; | ||
public ushort Height; | ||
public ushort Depth; | ||
public byte Location; | ||
public uint Pitch; | ||
public uint Offset; | ||
|
||
public static GtfHeader Read(Stream stream) | ||
{ | ||
BEBinaryReader reader = new(stream); | ||
|
||
GtfHeader header = new(); | ||
|
||
header.PixelFormat = (GtfPixelFormat)reader.ReadByte(); | ||
header.Mipmap = reader.ReadBoolean(); | ||
header.Dimension = reader.ReadByte(); | ||
header.Cubemap = reader.ReadBoolean(); | ||
header.Remap = reader.ReadUInt32(); | ||
header.Width = reader.ReadUInt16(); | ||
header.Height = reader.ReadUInt16(); | ||
header.Depth = reader.ReadUInt16(); | ||
header.Location = reader.ReadByte(); | ||
_ = reader.ReadByte(); //unused padding | ||
header.Pitch = reader.ReadUInt32(); | ||
header.Offset = reader.ReadUInt32(); | ||
|
||
return header; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Refresh.GameServer.Importing.Gtf; | ||
|
||
public class GtfMetadata | ||
{ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
namespace Refresh.GameServer.Importing.Gtf; | ||
|
||
//See https://github.com/RPCS3/rpcs3/blob/23cb67e0a10f7d7ecf39122fa697af8dcc30690e/rpcs3/Emu/RSX/gcm_enums.h#L135 | ||
public enum GtfPixelFormat : byte | ||
{ | ||
//Color flag | ||
B8 = 0x81, | ||
A1R5G5B5 = 0x82, | ||
A4R4G4B4 = 0x83, | ||
R5G6B5 = 0x84, | ||
A8R8G8B8 = 0x85, | ||
CompressedDxt1 = 0x86, | ||
CompressedDxt23 = 0x87, | ||
CompressedDxt45 = 0x88, | ||
G8B8 = 0x8B, | ||
CompressedB8R8G8R8 = 0x8D, // NOTE: 0xAD in firmware | ||
CompressedR8B8R8G8 = 0x8E, // NOTE: 0xAE in firmware | ||
R6G5B5 = 0x8F, | ||
Depth24D8 = 0x90, | ||
Depth24D8Float = 0x91, | ||
Depth16 = 0x92, | ||
Depth16Float = 0x93, | ||
X16 = 0x94, | ||
Y16X16 = 0x95, | ||
R5G5B5A1 = 0x97, | ||
CompressedHiLo8 = 0x98, | ||
CompressedHiLoS8 = 0x99, | ||
W16Z16Y16X16Float = 0x9A, | ||
W32Z32Y32X32Float = 0x9B, | ||
X32Float = 0x9C, | ||
D1R5G5B5 = 0x9D, | ||
D8R8G8B8 = 0x9E, | ||
Y16X16Float = 0x9F, | ||
//Swizzle flag | ||
SZ = 0x00, | ||
LN = 0x20, | ||
// Normalization Flag | ||
NR = 0x00, | ||
UN = 0x40, | ||
} | ||
|
||
public static class GtfPixelFormatExtensions | ||
{ | ||
public static int BitsPerPixel(this GtfPixelFormat pixelFormat) | ||
{ | ||
return pixelFormat switch { | ||
GtfPixelFormat.B8 => 8, | ||
GtfPixelFormat.A1R5G5B5 => 16, | ||
GtfPixelFormat.A4R4G4B4 => 16, | ||
GtfPixelFormat.R5G6B5 => 16, | ||
GtfPixelFormat.A8R8G8B8 => 32, | ||
GtfPixelFormat.CompressedDxt1 => 32, | ||
GtfPixelFormat.CompressedDxt23 => 32, | ||
GtfPixelFormat.CompressedDxt45 => 32, | ||
GtfPixelFormat.G8B8 => 16, | ||
GtfPixelFormat.CompressedB8R8G8R8 => 32, | ||
GtfPixelFormat.CompressedR8B8R8G8 => 32, | ||
GtfPixelFormat.R6G5B5 => 16, | ||
GtfPixelFormat.Depth24D8 => 32, | ||
GtfPixelFormat.Depth24D8Float => 32, | ||
GtfPixelFormat.Depth16 => 16, | ||
GtfPixelFormat.Depth16Float => 16, | ||
GtfPixelFormat.X16 => 16, | ||
GtfPixelFormat.Y16X16 => 32, | ||
GtfPixelFormat.R5G5B5A1 => 16, | ||
GtfPixelFormat.CompressedHiLo8 => 16, | ||
GtfPixelFormat.CompressedHiLoS8 => 16, | ||
GtfPixelFormat.W16Z16Y16X16Float => 64, | ||
GtfPixelFormat.W32Z32Y32X32Float => 128, | ||
GtfPixelFormat.X32Float => 32, | ||
GtfPixelFormat.D1R5G5B5 => 16, | ||
GtfPixelFormat.D8R8G8B8 => 32, | ||
GtfPixelFormat.Y16X16Float => 32, | ||
_ => throw new ArgumentOutOfRangeException(nameof(pixelFormat), pixelFormat, null) | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.