Skip to content

Commit

Permalink
Merge branch 'master' into xande
Browse files Browse the repository at this point in the history
  • Loading branch information
NotNite authored Aug 4, 2023
2 parents 2244c22 + 5710f32 commit cd34140
Show file tree
Hide file tree
Showing 13 changed files with 297 additions and 127 deletions.
9 changes: 8 additions & 1 deletion src/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ indent_style=space
indent_size=4

# Microsoft .NET properties
csharp_indent_braces=false
csharp_new_line_before_catch=true
csharp_new_line_before_else=true
csharp_new_line_before_finally=true
csharp_new_line_before_members_in_object_initializers=false
csharp_new_line_before_open_brace=anonymous_types,control_blocks,local_functions,methods,object_collection_array_initalizers,types
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
csharp_space_after_keywords_in_control_flow_statements=false
csharp_space_between_method_call_parameter_list_parentheses=true
Expand All @@ -27,11 +32,13 @@ dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint

# ReSharper properties
resharper_autodetect_indent_settings=true
resharper_csharp_case_block_braces=next_line
resharper_enforce_line_ending_style=true
resharper_space_within_array_access_brackets=true
resharper_space_within_checked_parentheses=true
resharper_space_within_default_parentheses=true
resharper_space_within_nameof_parentheses=true
resharper_space_within_new_parentheses=true
resharper_space_within_single_line_array_initializer_braces=true
resharper_space_within_sizeof_parentheses=true
resharper_space_within_typeof_parentheses=true
Expand All @@ -48,4 +55,4 @@ resharper_web_config_wrong_module_highlighting=warning
[*.{appxmanifest,asax,ascx,aspx,build,cs,cshtml,dtd,master,nuspec,razor,resw,resx,skin,vb,xaml,xamlx,xoml,xsd}]
indent_style=space
indent_size=4
tab_width=4
tab_width=4
190 changes: 165 additions & 25 deletions src/Lumina/Data/Files/TexFile.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Lumina.Data.Attributes;
Expand All @@ -12,6 +11,9 @@ namespace Lumina.Data.Files
[FileExtension( ".tex" )]
public class TexFile : FileResource
{
/// <summary>
/// Flags of the file.
/// </summary>
[Flags]
public enum Attribute : uint
{
Expand All @@ -32,8 +34,9 @@ public enum Attribute : uint
TextureType1D = 0x400000,
TextureType2D = 0x800000,
TextureType3D = 0x1000000,
TextureType2DArray = 0x10000000,
TextureTypeCube = 0x2000000,
TextureTypeMask = 0x3C00000,
TextureTypeMask = 0x13C00000,
TextureSwizzle = 0x4000000,
TextureNoTiled = 0x8000000,
TextureNoSwizzle = 0x80000000,
Expand Down Expand Up @@ -64,41 +67,45 @@ public enum TextureFormat
TypeDepthStencil = 0x4,
TypeSpecial = 0x5,
TypeBc57 = 0x6,

Unknown = 0x0,

// Integer types
L8 = 0x1130,
A8 = 0x1131,
B4G4R4A4 = 0x1440,
B5G5R5A1 = 0x1441,
B8G8R8A8 = 0x1450,
B8G8R8X8 = 0x1451,
[Obsolete("Use B4G4R4A4 instead.")]

[Obsolete( "Use B4G4R4A4 instead." )]
R4G4B4A4 = 0x1440,
[Obsolete("Use B5G5R5A1 instead.")]

[Obsolete( "Use B5G5R5A1 instead." )]
R5G5B5A1 = 0x1441,
[Obsolete("Use B8G8R8A8 instead.")]

[Obsolete( "Use B8G8R8A8 instead." )]
A8R8G8B8 = 0x1450,
[Obsolete("Use B8G8R8X8 instead.")]

[Obsolete( "Use B8G8R8X8 instead." )]
R8G8B8X8 = 0x1451,
[Obsolete("Not supported by Windows DirectX 11 version of the game, nor have any mention of the value, as of 6.15.")]

[Obsolete( "Not supported by Windows DirectX 11 version of the game, nor have any mention of the value, as of 6.15." )]
A8R8G8B82 = 0x1452,

// Floating point types
R32F = 0x2150,
R16G16F = 0x2250,
R32G32F = 0x2260,
R16G16B16A16F = 0x2460,
R32G32B32A32F = 0x2470,

// Block compression types (DX9 names)
DXT1 = 0x3420,
DXT3 = 0x3430,
DXT5 = 0x3431,
ATI2 = 0x6230,

// Block compression types (DX11 names)
BC1 = 0x3420,
BC2 = 0x3430,
Expand All @@ -117,16 +124,81 @@ public enum TextureFormat
Shadow24 = 0x5150,
}

[StructLayout( LayoutKind.Sequential )]
/// <summary>
/// Header of a .tex file.
/// </summary>
[StructLayout( LayoutKind.Explicit, Size = 80 )]
public unsafe struct TexHeader
{
/// <summary>
/// Various flags of the texture file.
/// </summary>
[FieldOffset( 0 )]
public Attribute Type;

/// <summary>
/// Format of the texture.
/// </summary>
[FieldOffset( 4 )]
public TextureFormat Format;

/// <summary>
/// Width of the texture.
/// </summary>
[FieldOffset( 8 )]
public ushort Width;

/// <summary>
/// Height of the texture.
/// </summary>
/// <remarks>
/// Relevant only if <see cref="Type"/> specifies <see cref="Attribute.TextureType2D"/>; otherwise it should be set to 1.
/// </remarks>
[FieldOffset( 10 )]
public ushort Height;

/// <summary>
/// Depth of the texture.
/// </summary>
/// <remarks>
/// Relevant only if <see cref="Type"/> specifies <see cref="Attribute.TextureType3D"/>; otherwise it should be set to 1.
/// </remarks>
[FieldOffset( 12 )]
public ushort Depth;

/// <summary>
/// The field has been repurposed; use <see cref="MipLevelsCount"/>.
/// </summary>
[FieldOffset( 14 )]
[Obsolete( $"Use {nameof( MipLevelsCount )} instead; the field has been repurposed as two fields." )]
public ushort MipLevels;

/// <summary>
/// Number of mipmaps in the texture.
/// </summary>
/// <remarks>There should be at least 1 and at most 13 mipmap entries; refer to the length of <see cref="OffsetToSurface"/>.</remarks>
[FieldOffset( 14 )]
public byte MipLevelsCount;

/// <summary>
/// Number of entries in texture array in the file.
/// </summary>
/// <remarks>
/// Relevant only if <see cref="Type"/> specifies <see cref="Attribute.TextureType2DArray"/>; otherwise it should be set to 0.
/// </remarks>
[FieldOffset( 15 )]
public byte ArraySize;

/// <summary>
/// <b>Index</b> of the mipmap entries corresponding for high, med, and low LoDs.
/// </summary>
[FieldOffset( 16 )]
public fixed uint LodOffset[3];

/// <summary>
/// Byte offset to the mipmap entries, from the beginning of the file. Entry is set to 0 if no corresponding mipmap exists.
/// </summary>
[FieldOffset( 28 )]
public fixed uint OffsetToSurface[13];
};

Expand All @@ -139,18 +211,18 @@ public enum DxgiFormatConversion
/// No conversion is required.
/// </summary>
NoConversion,

/// <summary>
/// Conversion from L8 (8bpp) to B8G8R8A8 (32bpp) is required.
/// Each byte indicates color value for each RGB channel. Alpha channel is fixed to 255.
/// </summary>
FromL8ToB8G8R8A8,

/// <summary>
/// Conversion from B4G4R4A4 (16bpp) to B8G8R8A8 (32bpp) is required.
/// </summary>
FromB4G4R4A4ToB8G8R8A8,

/// <summary>
/// Conversion from B5G5R5A1 (16bpp) to B8G8R8A8 (32bpp) is required.
/// </summary>
Expand All @@ -159,27 +231,32 @@ public enum DxgiFormatConversion

private byte[]? _imageDataDefault;

/// <summary>
/// File header of a .tex file.
/// </summary>
public TexHeader Header;

/// <summary>
/// Size of the <see cref="TexHeader"/>.
/// </summary>
public int HeaderLength => Unsafe.SizeOf< TexHeader >();

/// <summary>
/// Parsed texture buffer, in original texture format.
/// </summary>
public TextureBuffer TextureBuffer;
public TextureBuffer TextureBuffer = null!;

/// <summary>
/// The converted A8R8G8B8 image, taking the first Z/face/mipmap.
/// The converted A8R8G8B8 image, taking the first array item/Z/face/mipmap.
/// </summary>
public byte[] ImageData
{
get
{
public byte[] ImageData {
get {
_imageDataDefault ??= TextureBuffer.Filter( mip: 0, z: 0, format: TextureFormat.B8G8R8A8 ).RawData;
return _imageDataDefault;
}
}

/// <inheritdoc />
public override void LoadFile()
{
Reader.BaseStream.Position = 0;
Expand All @@ -191,6 +268,69 @@ public override void LoadFile()
TextureBuffer = TextureBuffer.FromStream( Header, Reader );
}

/// <summary>
/// Calculate the number of bytes occupied by a mipmap slice.
/// </summary>
/// <param name="mipmapIndex">Index of mipmap.</param>
/// <param name="width">Width of mipmap.</param>
/// <param name="height">Height of mipmap.</param>
/// <returns>Number of bytes.</returns>
public int SliceSize( int mipmapIndex, out int width, out int height )
{
if( mipmapIndex < 0 || mipmapIndex >= Math.Max( (int) Header.MipLevelsCount, 1 ) )
throw new ArgumentOutOfRangeException( nameof( mipmapIndex ), mipmapIndex, null );

var bpp = 1 << ( (int) ( Header.Format & TextureFormat.BppMask ) >> (int) TextureFormat.BppShift );
width = Math.Max( 1, Header.Width >> mipmapIndex );
height = Math.Max( 1, Header.Height >> mipmapIndex );
switch( (TextureFormat) ( (int) ( Header.Format & TextureFormat.TypeMask ) >> (int) TextureFormat.TypeShift ) )
{
case TextureFormat.TypeBc123:
case TextureFormat.TypeBc57:
var nbw = Math.Max( 1, ( width + 3 ) / 4 );
var nbh = Math.Max( 1, ( height + 3 ) / 4 );
return nbw * nbh * bpp * 2;
case TextureFormat.TypeInteger:
case TextureFormat.TypeFloat:
case TextureFormat.TypeDepthStencil:
case TextureFormat.TypeSpecial:
return width * height * bpp / 8;
default:
throw new NotSupportedException();
}
}

/// <summary>
/// Get the backing data of a slice in a 3D texture, a face in a cube map, or an image in texture array.
/// </summary>
/// <param name="mipmapIndex">Index of mipmap.</param>
/// <param name="sliceIndex">Index of slice/face/image.</param>
/// <param name="sliceSize">Number of bytes occupied for the slice.</param>
/// <param name="width">Width of mipmap.</param>
/// <param name="height">Height of mipmap.</param>
/// <returns>The slice data.</returns>
/// <remarks>Note that the length of span may be less than sliceSize, in which case, the truncated data should be treated as zeroes.</remarks>
public unsafe Span< byte > SliceSpan( int mipmapIndex, int sliceIndex, out int sliceSize, out int width, out int height )
{
sliceSize = SliceSize( mipmapIndex, out width, out height );
if( mipmapIndex < 0 || mipmapIndex >= Math.Max( (int) Header.MipLevelsCount, 1 ) )
throw new ArgumentOutOfRangeException( nameof( mipmapIndex ), mipmapIndex, null );

switch( Header.Type & Attribute.TextureTypeMask )
{
case var _ when sliceIndex < 0:
case Attribute.TextureType1D when sliceIndex != 0:
case Attribute.TextureType2D when sliceIndex != 0:
case Attribute.TextureType3D when sliceIndex >= Header.Depth:
case Attribute.TextureTypeCube when sliceIndex >= 6:
case Attribute.TextureType2DArray when sliceIndex >= Header.ArraySize:
throw new ArgumentOutOfRangeException( nameof( sliceIndex ), sliceIndex, null );
}

var offset = (int) Header.OffsetToSurface[ mipmapIndex ] + sliceIndex * sliceSize;
return offset >= Data.Length ? new() : Data.AsSpan( offset, Math.Min( Data.Length - offset, sliceSize ) );
}

/// <summary>
/// Get DXGI_FORMAT and required preprocessing from TextureFormat.
/// </summary>
Expand Down Expand Up @@ -224,14 +364,14 @@ public static Tuple< int, DxgiFormatConversion > GetDxgiFormatFromTextureFormat(
? Tuple.Create( 0x57, DxgiFormatConversion.FromB4G4R4A4ToB8G8R8A8 ) // DXGI_FORMAT_B8G8R8A8_UNORM
: Tuple.Create( 0x73, DxgiFormatConversion.NoConversion ) // DXGI_FORMAT_B4G4R4A4_UNORM
, // DXGI_FORMAT_B4G4R4A4_UNORM(0x73): unsupported in dx10, dx10.1, dx11, and dx11.1 (before windows8)
TextureFormat.B5G5R5A1 =>useGameCompatible
TextureFormat.B5G5R5A1 => useGameCompatible
? Tuple.Create( 0x57, DxgiFormatConversion.FromB5G5R5A1ToB8G8R8A8 ) // DXGI_FORMAT_B8G8R8A8_UNORM
: Tuple.Create( 0x56, DxgiFormatConversion.NoConversion ) // DXGI_FORMAT_B5G5R5A1_UNORM
, // DXGI_FORMAT_B5G5R5A1_UNORM(0x56): unsupported in dx10, dx10.1, dx11, and dx11.1 (before windows8)
TextureFormat.B8G8R8A8 => Tuple.Create( 0x57, DxgiFormatConversion.NoConversion ), // DXGI_FORMAT_B8G8R8A8_UNORM
TextureFormat.B8G8R8X8 => Tuple.Create( 0x58, DxgiFormatConversion.NoConversion ), // DXGI_FORMAT_B8G8R8X8_UNORM
TextureFormat.BC7 => Tuple.Create( 0x62, DxgiFormatConversion.NoConversion ), // DXGI_FORMAT_BC7_UNORM
_ => throw new NotSupportedException($"TextureFormat {(int)format:X04} is not supported."),
_ => throw new NotSupportedException( $"TextureFormat {(int) format:X04} is not supported." ),
};
}
}
Expand Down
9 changes: 5 additions & 4 deletions src/Lumina/Data/Parsing/Tex/Buffers/A8TextureBuffer.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using Lumina.Data.Files;

namespace Lumina.Data.Parsing.Tex.Buffers
{
Expand All @@ -8,8 +9,8 @@ namespace Lumina.Data.Parsing.Tex.Buffers
public class A8TextureBuffer : TextureBuffer
{
/// <inheritdoc />
public A8TextureBuffer( bool isDepthConstant, int width, int height, int depth, int[] mipmapAllocations, byte[] buffer )
: base( isDepthConstant, width, height, depth, mipmapAllocations, buffer )
public A8TextureBuffer( TexFile.Attribute attribute, int width, int height, int depth, int[] mipmapAllocations, byte[] buffer )
: base( attribute, width, height, depth, mipmapAllocations, buffer )
{
}

Expand All @@ -29,7 +30,7 @@ protected override unsafe void ConvertToB8G8R8A8( byte[] buffer, int destOffset,
}

/// <inheritdoc />
protected override TextureBuffer CreateNew( bool isDepthConstant, int width, int height, int depth, int[] mipmapAllocations, byte[] buffer )
=> new A8TextureBuffer( isDepthConstant, width, height, depth, mipmapAllocations, buffer );
protected override TextureBuffer CreateNew( TexFile.Attribute attribute, int width, int height, int depth, int[] mipmapAllocations, byte[] buffer )
=> new A8TextureBuffer( attribute, width, height, depth, mipmapAllocations, buffer );
}
}
Loading

0 comments on commit cd34140

Please sign in to comment.