using System; using System.Linq; using System.Collections.Generic; namespace Switch_Toolbox.Library { //Todo fix swizzle issues with this one public class GX2 { public const uint SwizzleMask = 0xFF00FF; //Some enums and parts from https://github.com/jam1garner/Smash-Forge/blob/master/Smash%20Forge/Filetypes/Textures/GTX.cs public class GX2Surface { public uint dim; public uint width; public uint height; public uint depth; public uint numMips; public uint firstSlice; public uint numSlices; public uint format; public uint aa; public uint use; public int resourceFlags; public uint imageSize; public uint imagePtr; public int MemPtr; public uint mipSize; public uint mipPtr; public uint tileMode; public uint swizzle; public uint mip_swizzle; //Used for botw Tex2 public uint alignment; public uint pitch; public uint bpp; public uint imageCount; public uint firstMip; public uint numArray; public byte[] data; public byte[] mipData; public uint[] mipOffset; public byte[] compSel; public uint[] texRegs; }; public static uint expPitch = 0; public static uint expHeight = 0; public static uint expNumSlices = 0; public class surfaceIn { public uint size = 0; public uint tileMode = 0; public uint format = 0; public uint bpp = 0; public uint numSamples = 0; public uint width = 0; public uint height = 0; public uint numSlices = 0; public uint slice = 0; public uint mipLevel = 0; public Flags flags = new Flags(); public uint numFrags = 0; public uint tileType = 0; public TileInfo pTileInfo = new TileInfo(); public int tileIndex = 0; } public class surfaceOut { public uint size = 0; public uint pitch = 0; public uint height = 0; public uint depth = 0; public long surfSize = 0; public uint tileMode = 0; public uint baseAlign = 0; public uint pitchAlign = 0; public uint heightAlign = 0; public uint depthAlign = 0; public uint bpp = 0; public uint pixelPitch = 0; public uint pixelHeight = 0; public uint pixelBits = 0; public uint sliceSize = 0; public uint pitchTileMax = 0; public uint heightTileMax = 0; public uint sliceTileMax = 0; public uint tileType = 0; public TileInfo pTileInfo = new TileInfo(); public int tileIndex = 0; } public class Flags { public uint value = 0; } public class TileInfo { public int banks = 0; public int bankWidth = 0; public int bankHeight = 0; public int macroAspectRatio = 0; public int tileSplitBytes = 0; public int pipeConfig = 0; } static surfaceIn pIn = new surfaceIn(); static surfaceOut pOut = new surfaceOut(); public enum GX2SurfaceDimension { DIM_1D = 0x0, DIM_2D = 0x1, DIM_3D = 0x2, DIM_CUBE = 0x3, DIM_1D_ARRAY = 0x4, DIM_2D_ARRAY = 0x5, DIM_2D_MSAA = 0x6, DIM_2D_MSAA_ARRAY = 0x7, DIM_FIRST = 0x0, DIM_LAST = 0x7, }; public enum GX2SurfaceFormat { INVALID = 0x0, TC_R8_UNORM = 0x1, TC_R8_UINT = 0x101, TC_R8_SNORM = 0x201, TC_R8_SINT = 0x301, T_R4_G4_UNORM = 0x2, TCD_R16_UNORM = 0x5, TC_R16_UINT = 0x105, TC_R16_SNORM = 0x205, TC_R16_SINT = 0x305, TC_R16_FLOAT = 0x806, TC_R8_G8_UNORM = 0x7, TC_R8_G8_UINT = 0x107, TC_R8_G8_SNORM = 0x207, TC_R8_G8_SINT = 0x307, TCS_R5_G6_B5_UNORM = 0x8, TC_R5_G5_B5_A1_UNORM = 0xA, TC_R4_G4_B4_A4_UNORM = 0xB, TC_A1_B5_G5_R5_UNORM = 0xC, TC_R32_UINT = 0x10D, TC_R32_SINT = 0x30D, TCD_R32_FLOAT = 0x80E, TC_R16_G16_UNORM = 0xF, TC_R16_G16_UINT = 0x10F, TC_R16_G16_SNORM = 0x20F, TC_R16_G16_SINT = 0x30F, TC_R16_G16_FLOAT = 0x810, D_D24_S8_UNORM = 0x11, T_R24_UNORM_X8 = 0x11, T_X24_G8_UINT = 0x111, D_D24_S8_FLOAT = 0x811, TC_R11_G11_B10_FLOAT = 0x816, TCS_R10_G10_B10_A2_UNORM = 0x19, TC_R10_G10_B10_A2_UINT = 0x119, T_R10_G10_B10_A2_SNORM = 0x219, TC_R10_G10_B10_A2_SNORM = 0x219, TC_R10_G10_B10_A2_SINT = 0x319, TCS_R8_G8_B8_A8_UNORM = 0x1A, TC_R8_G8_B8_A8_UINT = 0x11A, TC_R8_G8_B8_A8_SNORM = 0x21A, TC_R8_G8_B8_A8_SINT = 0x31A, TCS_R8_G8_B8_A8_SRGB = 0x41A, TCS_A2_B10_G10_R10_UNORM = 0x1B, TC_A2_B10_G10_R10_UINT = 0x11B, D_D32_FLOAT_S8_UINT_X24 = 0x81C, T_R32_FLOAT_X8_X24 = 0x81C, T_X32_G8_UINT_X24 = 0x11C, TC_R32_G32_UINT = 0x11D, TC_R32_G32_SINT = 0x31D, TC_R32_G32_FLOAT = 0x81E, TC_R16_G16_B16_A16_UNORM = 0x1F, TC_R16_G16_B16_A16_UINT = 0x11F, TC_R16_G16_B16_A16_SNORM = 0x21F, TC_R16_G16_B16_A16_SINT = 0x31F, TC_R16_G16_B16_A16_FLOAT = 0x820, TC_R32_G32_B32_A32_UINT = 0x122, TC_R32_G32_B32_A32_SINT = 0x322, TC_R32_G32_B32_A32_FLOAT = 0x823, T_BC1_UNORM = 0x31, T_BC1_SRGB = 0x431, T_BC2_UNORM = 0x32, T_BC2_SRGB = 0x432, T_BC3_UNORM = 0x33, T_BC3_SRGB = 0x433, T_BC4_UNORM = 0x34, T_BC4_SNORM = 0x234, T_BC5_UNORM = 0x35, T_BC5_SNORM = 0x235, T_NV12_UNORM = 0x81, FIRST = 0x1, LAST = 0x83F, }; public enum GX2AAMode { GX2_AA_MODE_1X = 0x0, GX2_AA_MODE_2X = 0x1, GX2_AA_MODE_4X = 0x2, GX2_AA_MODE_8X = 0x3, GX2_AA_MODE_FIRST = 0x0, GX2_AA_MODE_LAST = 0x3, }; public enum GX2SurfaceUse : uint { USE_TEXTURE = 0x1, USE_COLOR_BUFFER = 0x2, USE_DEPTH_BUFFER = 0x4, USE_SCAN_BUFFER = 0x8, USE_FTV = 0x80000000, USE_COLOR_BUFFER_TEXTURE = 0x3, USE_DEPTH_BUFFER_TEXTURE = 0x5, USE_COLOR_BUFFER_FTV = 0x80000002, USE_COLOR_BUFFER_TEXTURE_FTV = 0x80000003, USE_FIRST = 0x1, USE_LAST = 0x8, }; public enum GX2RResourceFlags { GX2R_RESOURCE_FLAGS_NONE = 0x0, GX2R_BIND_NONE = 0x0, GX2R_BIND_TEXTURE = 0x1, GX2R_BIND_COLOR_BUFFER = 0x2, GX2R_BIND_DEPTH_BUFFER = 0x4, GX2R_BIND_SCAN_BUFFER = 0x8, GX2R_BIND_VERTEX_BUFFER = 0x10, GX2R_BIND_INDEX_BUFFER = 0x20, GX2R_BIND_UNIFORM_BLOCK = 0x40, GX2R_BIND_SHADER_PROGRAM = 0x80, GX2R_BIND_STREAM_OUTPUT = 0x100, GX2R_BIND_DISPLAY_LIST = 0x200, GX2R_BIND_GS_RING = 0x400, GX2R_USAGE_NONE = 0x0, GX2R_USAGE_CPU_READ = 0x800, GX2R_USAGE_CPU_WRITE = 0x1000, GX2R_USAGE_GPU_READ = 0x2000, GX2R_USAGE_GPU_WRITE = 0x4000, GX2R_USAGE_DMA_READ = 0x8000, GX2R_USAGE_DMA_WRITE = 0x10000, GX2R_USAGE_FORCE_MEM1 = 0x20000, GX2R_USAGE_FORCE_MEM2 = 0x40000, GX2R_USAGE_MEM_DEFAULT = 0x0, GX2R_USAGE_CPU_READWRITE = 0x1800, GX2R_USAGE_GPU_READWRITE = 0x6000, GX2R_USAGE_NON_CPU_WRITE = 0x14000, GX2R_OPTION_NONE = 0x0, GX2R_OPTION_IGNORE_IN_USE = 0x80000, GX2R_OPTION_FIRST = 0x80000, GX2R_OPTION_NO_CPU_INVALIDATE = 0x100000, GX2R_OPTION_NO_GPU_INVALIDATE = 0x200000, GX2R_OPTION_LOCK_READONLY = 0x400000, GX2R_OPTION_NO_TOUCH_DESTROY = 0x800000, GX2R_OPTION_LAST = 0x800000, GX2R_OPTION_NO_INVALIDATE = 0x300000, GX2R_OPTION_MASK = 0xF80000, GX2R_RESOURCE_FLAG_RESERVED2 = 0x10000000, GX2R_RESOURCE_FLAG_RESERVED1 = 0x20000000, GX2R_RESOURCE_FLAG_RESERVED0 = 0x40000000, }; public enum GX2TileMode { MODE_DEFAULT = 0x0, MODE_LINEAR_SPECIAL = 0x10, MODE_LINEAR_ALIGNED = 0x1, MODE_1D_TILED_THIN1 = 0x2, MODE_1D_TILED_THICK = 0x3, MODE_2D_TILED_THIN1 = 0x4, MODE_2D_TILED_THIN2 = 0x5, MODE_2D_TILED_THIN4 = 0x6, MODE_2D_TILED_THICK = 0x7, MODE_2B_TILED_THIN1 = 0x8, MODE_2B_TILED_THIN2 = 0x9, MODE_2B_TILED_THIN4 = 0xA, MODE_2B_TILED_THICK = 0xB, MODE_3D_TILED_THIN1 = 0xC, MODE_3D_TILED_THICK = 0xD, MODE_3B_TILED_THIN1 = 0xE, MODE_3B_TILED_THICK = 0xF, MODE_LAST = 0x20, }; public enum AddrTileMode { ADDR_TM_LINEAR_GENERAL = 0x0, ADDR_TM_LINEAR_ALIGNED = 0x1, ADDR_TM_1D_TILED_THIN1 = 0x2, ADDR_TM_1D_TILED_THICK = 0x3, ADDR_TM_2D_TILED_THIN1 = 0x4, ADDR_TM_2D_TILED_THIN2 = 0x5, ADDR_TM_2D_TILED_THIN4 = 0x6, ADDR_TM_2D_TILED_THICK = 0x7, ADDR_TM_2B_TILED_THIN1 = 0x8, ADDR_TM_2B_TILED_THIN2 = 0x9, ADDR_TM_2B_TILED_THIN4 = 0x0A, ADDR_TM_2B_TILED_THICK = 0x0B, ADDR_TM_3D_TILED_THIN1 = 0x0C, ADDR_TM_3D_TILED_THICK = 0x0D, ADDR_TM_3B_TILED_THIN1 = 0x0E, ADDR_TM_3B_TILED_THICK = 0x0F, ADDR_TM_2D_TILED_XTHICK = 0x10, ADDR_TM_3D_TILED_XTHICK = 0x11, ADDR_TM_POWER_SAVE = 0x12, ADDR_TM_COUNT = 0x13, } public enum AddrTileType { ADDR_DISPLAYABLE = 0, ADDR_NON_DISPLAYABLE = 1, ADDR_DEPTH_SAMPLE_ORDER = 2, ADDR_THICK_TILING = 3, } public enum AddrPipeCfg { ADDR_PIPECFG_INVALID = 0x0, ADDR_PIPECFG_P2 = 0x1, ADDR_PIPECFG_P4_8x16 = 0x5, ADDR_PIPECFG_P4_16x16 = 0x6, ADDR_PIPECFG_P4_16x32 = 0x7, ADDR_PIPECFG_P4_32x32 = 0x8, ADDR_PIPECFG_P8_16x16_8x16 = 0x9, ADDR_PIPECFG_P8_16x32_8x16 = 0xA, ADDR_PIPECFG_P8_32x32_8x16 = 0xB, ADDR_PIPECFG_P8_16x32_16x16 = 0xC, ADDR_PIPECFG_P8_32x32_16x16 = 0xD, ADDR_PIPECFG_P8_32x32_16x32 = 0xE, ADDR_PIPECFG_P8_32x64_32x32 = 0xF, ADDR_PIPECFG_MAX = 0x10, } public enum AddrFormat { ADDR_FMT_INVALID = 0x0, ADDR_FMT_8 = 0x1, ADDR_FMT_4_4 = 0x2, ADDR_FMT_3_3_2 = 0x3, ADDR_FMT_RESERVED_4 = 0x4, ADDR_FMT_16 = 0x5, ADDR_FMT_16_FLOAT = 0x6, ADDR_FMT_8_8 = 0x7, ADDR_FMT_5_6_5 = 0x8, ADDR_FMT_6_5_5 = 0x9, ADDR_FMT_1_5_5_5 = 0xA, ADDR_FMT_4_4_4_4 = 0xB, ADDR_FMT_5_5_5_1 = 0xC, ADDR_FMT_32 = 0xD, ADDR_FMT_32_FLOAT = 0xE, ADDR_FMT_16_16 = 0xF, ADDR_FMT_16_16_FLOAT = 0x10, ADDR_FMT_8_24 = 0x11, ADDR_FMT_8_24_FLOAT = 0x12, ADDR_FMT_24_8 = 0x13, ADDR_FMT_24_8_FLOAT = 0x14, ADDR_FMT_10_11_11 = 0x15, ADDR_FMT_10_11_11_FLOAT = 0x16, ADDR_FMT_11_11_10 = 0x17, ADDR_FMT_11_11_10_FLOAT = 0x18, ADDR_FMT_2_10_10_10 = 0x19, ADDR_FMT_8_8_8_8 = 0x1A, ADDR_FMT_10_10_10_2 = 0x1B, ADDR_FMT_X24_8_32_FLOAT = 0x1C, ADDR_FMT_32_32 = 0x1D, ADDR_FMT_32_32_FLOAT = 0x1E, ADDR_FMT_16_16_16_16 = 0x1F, ADDR_FMT_16_16_16_16_FLOAT = 0x20, ADDR_FMT_RESERVED_33 = 0x21, ADDR_FMT_32_32_32_32 = 0x22, ADDR_FMT_32_32_32_32_FLOAT = 0x23, ADDR_FMT_RESERVED_36 = 0x24, ADDR_FMT_1 = 0x25, ADDR_FMT_1_REVERSED = 0x26, ADDR_FMT_GB_GR = 0x27, ADDR_FMT_BG_RG = 0x28, ADDR_FMT_32_AS_8 = 0x29, ADDR_FMT_32_AS_8_8 = 0x2A, ADDR_FMT_5_9_9_9_SHAREDEXP = 0x2B, ADDR_FMT_8_8_8 = 0x2C, ADDR_FMT_16_16_16 = 0x2D, ADDR_FMT_16_16_16_FLOAT = 0x2E, ADDR_FMT_32_32_32 = 0x2F, ADDR_FMT_32_32_32_FLOAT = 0x30, ADDR_FMT_BC1 = 0x31, ADDR_FMT_BC2 = 0x32, ADDR_FMT_BC3 = 0x33, ADDR_FMT_BC4 = 0x34, ADDR_FMT_BC5 = 0x35, ADDR_FMT_BC6 = 0x36, ADDR_FMT_BC7 = 0x37, ADDR_FMT_32_AS_32_32_32_32 = 0x38, ADDR_FMT_APC3 = 0x39, ADDR_FMT_APC4 = 0x3A, ADDR_FMT_APC5 = 0x3B, ADDR_FMT_APC6 = 0x3C, ADDR_FMT_APC7 = 0x3D, ADDR_FMT_CTX1 = 0x3E, ADDR_FMT_RESERVED_63 = 0x3F, }; public static void Debug(surfaceOut surf) { if (surf == null) surf = getSurfaceInfo((GX2SurfaceFormat)0x33, 701, 77, 1, 0, 13, 1, 0); Console.WriteLine($"size {surf.size}"); Console.WriteLine($"pitch {surf.pitch}"); Console.WriteLine($"height {surf.height}"); Console.WriteLine($"depth {surf.depth}"); Console.WriteLine($"surfSize {surf.surfSize}"); Console.WriteLine($"tileMode {surf.tileMode}"); Console.WriteLine($"baseAlign {surf.baseAlign}"); Console.WriteLine($"pitchAlign {surf.pitchAlign}"); Console.WriteLine($"heightAlign {surf.heightAlign}"); Console.WriteLine($"depthAlign {surf.depthAlign}"); Console.WriteLine($"bpp {surf.bpp}"); Console.WriteLine($"pixelPitch {surf.pixelPitch}"); Console.WriteLine($"pixelHeight {surf.pixelHeight}"); Console.WriteLine($"pixelBits {surf.pixelBits}"); Console.WriteLine($"sliceSize {surf.sliceSize}"); Console.WriteLine($"pitchTileMax {surf.pitchTileMax}"); Console.WriteLine($"heightTileMax {surf.heightTileMax}"); Console.WriteLine($"sliceTileMax {surf.sliceTileMax}"); Console.WriteLine($"tileType {surf.tileType}"); Console.WriteLine($"tileIndex {surf.tileIndex}"); } static bool DebugSurface = false; public static GX2Surface CreateGx2Texture(byte[] imageData, string Name, uint TileMode, uint AAMode, uint Width, uint Height, uint Depth, uint Format, uint swizzle, uint SurfaceDim, uint MipCount) { var surfOut = getSurfaceInfo((GX2SurfaceFormat)Format, Width, Height, Depth, SurfaceDim, TileMode, AAMode, 0); Console.WriteLine("Imported surfSize" + surfOut.surfSize); Console.WriteLine("Imported data block" + imageData.Length); Console.WriteLine("GX2SurfaceFormat " + (GX2SurfaceFormat)Format); uint imageSize = (uint)surfOut.surfSize; uint alignment = surfOut.baseAlign; uint pitch = surfOut.pitch; uint mipSize = 0; uint dataSize = (uint)imageData.Length; uint bpp = GX2.surfaceGetBitsPerPixel((uint)Format) >> 3; int DepthLevel = 1; if (dataSize <= 0) throw new Exception($"Image is empty!!"); Console.WriteLine("swizzle pattern " + swizzle); uint s = (swizzle << 8); uint blkWidth, blkHeight; if (GX2.IsFormatBCN((GX2SurfaceFormat)Format)) { blkWidth = 4; blkHeight = 4; } else { blkWidth = 1; blkHeight = 1; } if (TileMode == 0) TileMode = GX2.getDefaultGX2TileMode((uint)SurfaceDim, Width, Height, 1, (uint)Format, 0, 1); uint tilingDepth = surfOut.depth; if (TileMode == 3) tilingDepth /= 4; if (tilingDepth != 1) throw new Exception($"Unsupported Depth {surfOut.depth}!"); int tiling1dLevel = 0; bool tiling1dLevelSet = false; List mipOffsets = new List(); List Swizzled = new List(); if (MipCount == 0) MipCount = 1; uint Splice = 0; for (int mipLevel = 0; mipLevel < MipCount; mipLevel++) { var result = TextureHelper.GetCurrentMipSize(Width, Height, blkWidth, blkHeight, bpp, mipLevel); uint offset = result.Item1; uint size = result.Item2; byte[] data_ = new byte[size]; Array.Copy(imageData, offset, data_, 0, size); uint width_ = Math.Max(1, Width >> mipLevel); uint height_ = Math.Max(1, Height >> mipLevel); if (mipLevel != 0) { surfOut = GX2.getSurfaceInfo((GX2SurfaceFormat)Format, Width, Height, 1, 1, TileMode, 0, mipLevel); if (mipLevel == 1) mipOffsets.Add(imageSize); else mipOffsets.Add(mipSize); } data_ = Utils.CombineByteArray(data_, new byte[surfOut.surfSize - size]); byte[] dataAlignBytes = new byte[RoundUp(mipSize, surfOut.baseAlign) - mipSize]; if (mipLevel != 0) mipSize += (uint)(surfOut.surfSize + dataAlignBytes.Length); byte[] SwizzledData = GX2.swizzle(width_, height_, surfOut.depth, surfOut.height, (uint)Format, 0, 1, surfOut.tileMode, s, surfOut.pitch, surfOut.bpp, Splice, 0, data_); Swizzled.Add(dataAlignBytes.Concat(SwizzledData).ToArray()); if (surfOut.tileMode == 1 || surfOut.tileMode == 2 || surfOut.tileMode == 3 || surfOut.tileMode == 16) { tiling1dLevelSet = true; } if (tiling1dLevelSet == false) tiling1dLevel += 1; } if (tiling1dLevelSet) s |= (uint)(tiling1dLevel << 16); else s |= (uint)(13 << 16); GX2.GX2Surface surf = new GX2.GX2Surface(); surf.depth = Depth; surf.width = Width; surf.height = Height; surf.use = 1; surf.dim = (uint)SurfaceDim; surf.tileMode = TileMode; surf.swizzle = s; surf.resourceFlags = 0; surf.pitch = pitch; surf.bpp = bpp; surf.format = (uint)Format; surf.numMips = MipCount; surf.imageCount = MipCount; surf.firstSlice = 0; surf.firstMip = 0; surf.aa = AAMode; surf.mipOffset = mipOffsets.ToArray(); surf.alignment = alignment; surf.imageSize = imageSize; surf.data = Swizzled[0]; List mips = new List(); for (int mipLevel = 1; mipLevel < Swizzled.Count; mipLevel++) { mips.Add(Swizzled[mipLevel]); Console.WriteLine(Swizzled[mipLevel].Length); } surf.mipData = Utils.CombineByteArray(mips.ToArray()); mips.Clear(); surf.mipSize = surf.mipData != null ? (uint)surf.mipData.Length : 0; Console.WriteLine(""); Console.WriteLine("// ----- GX2Surface Swizzled Info ----- "); Console.WriteLine(" dim = 1"); Console.WriteLine(" width = " + surf.width); Console.WriteLine(" height = " + surf.height); Console.WriteLine(" depth = 1"); Console.WriteLine(" numMips = " + surf.numMips); Console.WriteLine(" format = " + surf.format); Console.WriteLine(" aa = 0"); Console.WriteLine(" use = 1"); Console.WriteLine(" imageSize = " + surf.imageSize); Console.WriteLine(" mipSize = " + surf.mipSize); Console.WriteLine(" tileMode = " + surf.tileMode); Console.WriteLine(" swizzle = " + surf.swizzle); Console.WriteLine(" alignment = " + surf.alignment); Console.WriteLine(" pitch = " + surf.pitch); Console.WriteLine(" data = " + surf.data.Length); Console.WriteLine(" mipData = " + surf.mipData.Length); Console.WriteLine(""); Console.WriteLine(" GX2 Component Selector:"); Console.WriteLine(""); Console.WriteLine(" bits per pixel = " + (surf.bpp << 3)); Console.WriteLine(" bytes per pixel = " + surf.bpp); Console.WriteLine(" realSize = " + imageData.Length); return surf; } private static int RoundUp(int X, int Y) { return ((X - 1) | (Y - 1)) + 1; } private static uint RoundUp(uint X, uint Y) { return ((X - 1) | (Y - 1)) + 1; } public static byte[] Decode(GX2Surface tex, int ArrayIndex = -1, int MipIndex = -1, string DebugTextureName = "") { uint blkWidth, blkHeight; if (IsFormatBCN((GX2SurfaceFormat)tex.format)) { blkWidth = 4; blkHeight = 4; } else { blkWidth = 1; blkHeight = 1; } var surfInfo = getSurfaceInfo((GX2SurfaceFormat)tex.format, tex.width, tex.height, tex.depth, (uint)tex.dim, (uint)tex.tileMode, (uint)tex.aa, 0); uint bpp = DIV_ROUND_UP(surfInfo.bpp, 8); if (tex.numArray == 0) tex.numArray = 1; byte[] data = new byte[tex.data.Length]; byte[] mipdata = new byte[tex.mipData.Length]; uint mipCount = tex.numMips; if (tex.mipData == null || tex.mipData.Length <= 0) mipCount = 1; int dataOffset = 0; int mipDataOffset = 0; int mipSpliceSize = 0; for (int arrayLevel = 0; arrayLevel < tex.depth; arrayLevel++) { for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) { bool GetLevel = (arrayLevel == ArrayIndex && mipLevel == MipIndex); uint swizzle = tex.swizzle; uint width_ = (uint)Math.Max(1, tex.width >> mipLevel); uint height_ = (uint)Math.Max(1, tex.height >> mipLevel); uint size = DIV_ROUND_UP(width_, blkWidth) * DIV_ROUND_UP(height_, blkHeight) * bpp; uint mipOffset; if (mipLevel != 0) { if (tex.mip_swizzle != 0) swizzle = tex.mip_swizzle; mipOffset = (tex.mipOffset[mipLevel - 1]); if (mipLevel == 1) { mipOffset -= (uint)surfInfo.surfSize; mipOffset += (uint)mipDataOffset; mipSpliceSize += (int)mipOffset; } Console.WriteLine("mipOffset " + mipOffset); if (GetLevel) { surfInfo = getSurfaceInfo((GX2SurfaceFormat)tex.format, tex.width, tex.height, tex.depth, (uint)tex.dim, (uint)tex.tileMode, (uint)tex.aa, mipLevel); Array.Copy(tex.mipData, 0, mipdata, 0, tex.mipData.Length); Array.Copy(tex.mipData, (int)mipOffset, mipdata, 0, (int)surfInfo.sliceSize); data = mipdata; } } else if (GetLevel) { Array.Copy(tex.data, 0, data, 0, tex.data.Length); Array.Copy(tex.data, (uint)dataOffset, data, 0, size); } if (GetLevel) { byte[] deswizzled = deswizzle(width_, height_, surfInfo.depth, surfInfo.height, (uint)tex.format, 0, tex.use, surfInfo.tileMode, (uint)swizzle, surfInfo.pitch, surfInfo.bpp, (uint)arrayLevel, 0, data); //Create a copy and use that to remove uneeded data byte[] result_ = new byte[size]; Array.Copy(deswizzled, 0, result_, 0, size); return result_; } } dataOffset += (int)surfInfo.sliceSize; mipDataOffset += mipSpliceSize; } return null; } public static List> Decode(GX2Surface tex, string DebugTextureName = "") { if (tex.data == null || tex.data.Length <= 0) throw new Exception("Invalid GX2 surface data. Make sure to not open Tex2 files if this is one. Those will load automatically next to Tex1!"); Console.WriteLine("DECODING TEX " + DebugTextureName); var surfdEBUG = getSurfaceInfo((GX2SurfaceFormat)tex.format, tex.width, tex.height, tex.depth, (uint)tex.dim, (uint)tex.tileMode, (uint)tex.aa, 0); Debug(surfdEBUG); /* Console.WriteLine(""); Console.WriteLine("// ----- GX2Surface Decode Info ----- "); Console.WriteLine(" dim = " + tex.dim); Console.WriteLine(" width = " + tex.width); Console.WriteLine(" height = " + tex.height); Console.WriteLine(" depth = " + tex.depth); Console.WriteLine(" numMips = " + tex.numMips); Console.WriteLine(" format = " + (GX2SurfaceFormat)tex.format); Console.WriteLine(" aa = " + tex.aa); Console.WriteLine(" use = " + tex.use); Console.WriteLine(" imageSize = " + tex.imageSize); Console.WriteLine(" mipSize = " + tex.mipSize); Console.WriteLine(" tileMode = " + (GX2TileMode)tex.tileMode); Console.WriteLine(" swizzle = " + tex.swizzle); Console.WriteLine(" alignment = " + tex.alignment); Console.WriteLine(" pitch = " + tex.pitch); Console.WriteLine(" bits per pixel = " + (tex.bpp << 3)); Console.WriteLine(" bytes per pixel = " + tex.bpp); Console.WriteLine(" data size = " + tex.data.Length); Console.WriteLine(" realSize = " + tex.imageSize);*/ uint blkWidth, blkHeight; if (IsFormatBCN((GX2SurfaceFormat)tex.format)) { blkWidth = 4; blkHeight = 4; } else { blkWidth = 1; blkHeight = 1; } byte[] data = tex.data; var surfInfo = getSurfaceInfo((GX2SurfaceFormat)tex.format, tex.width, tex.height, tex.depth, (uint)tex.dim, (uint)tex.tileMode, (uint)tex.aa, 0); uint bpp = DIV_ROUND_UP(surfInfo.bpp, 8); if (surfInfo.depth != 1) { // System.Windows.Forms.MessageBox.Show($"Unsupported Depth {surfInfo.depth} for texture {DebugTextureName}!"); // return new List>(); } if (tex.numArray == 0) tex.numArray = 1; uint mipCount = tex.numMips; if (tex.mipData == null || tex.mipData.Length <= 0) mipCount = 1; int ArrayImageize = 0; int ArrayMipImageize = 0; if (tex.mipData != null) ArrayMipImageize = tex.mipData.Length / (int)tex.depth; int dataOffset = 0; int mipDataOffset = 0; int TotalImageSize = tex.data.Length; List> result = new List>(); for (int arrayLevel = 0; arrayLevel < tex.depth; arrayLevel++) { List mips = new List(); for (int mipLevel = 0; mipLevel < mipCount; mipLevel++) { uint swizzle = tex.swizzle; uint width_ = (uint)Math.Max(1, tex.width >> mipLevel); uint height_ = (uint)Math.Max(1, tex.height >> mipLevel); uint size = DIV_ROUND_UP(width_, blkWidth) * DIV_ROUND_UP(height_, blkHeight) * bpp; uint mipOffset; if (mipLevel != 0) { if (tex.mip_swizzle != 0) swizzle = tex.mip_swizzle; mipOffset = (tex.mipOffset[mipLevel - 1]); if (mipLevel == 1) mipOffset -= (uint)surfInfo.sliceSize; surfInfo = getSurfaceInfo((GX2SurfaceFormat)tex.format, tex.width, tex.height, tex.depth, (uint)tex.dim, (uint)tex.tileMode, (uint)tex.aa, mipLevel); data = new byte[surfInfo.sliceSize]; Array.Copy(tex.mipData, (uint)mipDataOffset + mipOffset, data, 0, surfInfo.sliceSize); } else Array.Copy(tex.data, (uint)dataOffset, data, 0, size); byte[] deswizzled = deswizzle(width_, height_, surfInfo.depth, surfInfo.height, (uint)tex.format, 0, tex.use, surfInfo.tileMode, (uint)swizzle, surfInfo.pitch, surfInfo.bpp, (uint)arrayLevel, 0, data); //Create a copy and use that to remove uneeded data byte[] result_ = new byte[size]; Array.Copy(deswizzled, 0, result_, 0, size); mips.Add(result_); } result.Add(mips); dataOffset += (int)surfInfo.sliceSize; mipDataOffset += (int)surfInfo.sliceSize; } return result; } private static byte[] SubArray(byte[] data, int offset, int length) { return data.Skip(offset).Take(length).ToArray(); } private static uint DIV_ROUND_UP(uint n, uint d) { return (n + d - 1) / d; } /*--------------------------------------- * * Code ported from AboodXD's GTX Extractor: * https://github.com/aboood40091/GTX-Extractor/blob/cf1a15c41630745d9a0d370bafe5760c1e5f8cbe/addrlib/addrlib_cy.pyx * *---------------------------------------*/ public static bool IsFormatBCN(GX2SurfaceFormat Format) { switch (Format) { case GX2SurfaceFormat.T_BC1_UNORM: case GX2SurfaceFormat.T_BC1_SRGB: case GX2SurfaceFormat.T_BC2_UNORM: case GX2SurfaceFormat.T_BC2_SRGB: case GX2SurfaceFormat.T_BC3_UNORM: case GX2SurfaceFormat.T_BC3_SRGB: case GX2SurfaceFormat.T_BC4_UNORM: case GX2SurfaceFormat.T_BC4_SNORM: case GX2SurfaceFormat.T_BC5_SNORM: case GX2SurfaceFormat.T_BC5_UNORM: return true; default: return false; } } public static byte[] deswizzle(uint width, uint height, uint depth, uint height_, uint format_, uint aa, uint use, uint tileMode, uint swizzle_, uint pitch, uint bpp, uint slice, uint sample, byte[] data) { return swizzleSurf(width, height, depth, format_, aa, use, tileMode, swizzle_, pitch, bpp, slice, sample, data, 0); } public static byte[] swizzle(uint width, uint height, uint depth, uint height_, uint format_, uint aa, uint use, uint tileMode, uint swizzle_, uint pitch, uint bpp, uint slice, uint sample, byte[] data) { return swizzleSurf(width, height, depth, format_, aa, use, tileMode, swizzle_, pitch, bpp, slice, sample, data, 1); } private static byte[] swizzleSurf(uint width, uint height, uint depth, uint format, uint aa, uint use, uint tileMode, uint swizzle_, uint pitch, uint bitsPerPixel, uint slice, uint sample, byte[] data, int swizzle) { uint bytesPerPixel = bitsPerPixel / 8; byte[] result = new byte[data.Length]; uint pipeSwizzle, bankSwizzle, pos_; ulong pos; if (IsFormatBCN((GX2SurfaceFormat)format)) { width = (width + 3) / 4; height = (height + 3) / 4; } pipeSwizzle = (swizzle_ >> 8) & 1; bankSwizzle = (swizzle_ >> 9) & 3; if (depth > 1) { // bankSwizzle = (uint)(slice % 4); } tileMode = GX2TileModeToAddrTileMode(tileMode); bool IsDepth = (use & 4) != 0; uint numSamples = (uint)(1 << (int)aa); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (tileMode == 0 || tileMode == 1) { pos = computeSurfaceAddrFromCoordLinear((uint)x, (uint)y, slice, sample, bytesPerPixel, pitch, height, depth); } else if (tileMode == 2 || tileMode == 3) { pos = computeSurfaceAddrFromCoordMicroTiled((uint)x, (uint)y, slice, bitsPerPixel, pitch, height, (AddrTileMode)tileMode, IsDepth); } else { pos = computeSurfaceAddrFromCoordMacroTiled((uint)x, (uint)y, slice, sample, bitsPerPixel, pitch, height, numSamples, (AddrTileMode)tileMode, IsDepth, pipeSwizzle, bankSwizzle); } pos_ = (uint)(y * width + x) * bytesPerPixel; if (pos_ + bytesPerPixel <= data.Length && pos + bytesPerPixel <= (ulong)data.Length) { if (swizzle == 0) { for (int n = 0; n < bytesPerPixel; n++) result[pos_ + n] = data[(uint)pos + n]; } else { for (int n = 0; n < bytesPerPixel; n++) result[(uint)pos + n] = data[pos_ + n]; } } } } return result; } private static byte[] formatHwInfo = { 0x00, 0x00, 0x00, 0x01, 0x08, 0x03, 0x00, 0x01, 0x08, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x07, 0x00, 0x00, 0x10, 0x03, 0x00, 0x01, 0x10, 0x03, 0x00, 0x01, 0x10, 0x0B, 0x00, 0x01, 0x10, 0x01, 0x00, 0x01, 0x10, 0x03, 0x00, 0x01, 0x10, 0x03, 0x00, 0x01, 0x10, 0x03, 0x00, 0x01, 0x20, 0x03, 0x00, 0x00, 0x20, 0x07, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x20, 0x03, 0x00, 0x01, 0x20, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, 0x0B, 0x00, 0x01, 0x20, 0x0B, 0x00, 0x01, 0x20, 0x0B, 0x00, 0x01, 0x40, 0x05, 0x00, 0x00, 0x40, 0x03, 0x00, 0x00, 0x40, 0x03, 0x00, 0x00, 0x40, 0x03, 0x00, 0x00, 0x40, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x01, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x20, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x60, 0x01, 0x00, 0x00, 0x40, 0x01, 0x00, 0x01, 0x80, 0x01, 0x00, 0x01, 0x80, 0x01, 0x00, 0x01, 0x40, 0x01, 0x00, 0x01, 0x80, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; private static byte[] formatExInfo = { 0x00, 0x01, 0x01, 0x03, 0x08, 0x01, 0x01, 0x03, 0x08, 0x01, 0x01, 0x03, 0x08, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x10, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x40, 0x01, 0x01, 0x03, 0x40, 0x01, 0x01, 0x03, 0x40, 0x01, 0x01, 0x03, 0x40, 0x01, 0x01, 0x03, 0x40, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x80, 0x01, 0x01, 0x03, 0x80, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x01, 0x08, 0x01, 0x05, 0x01, 0x08, 0x01, 0x06, 0x10, 0x01, 0x01, 0x07, 0x10, 0x01, 0x01, 0x08, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x20, 0x01, 0x01, 0x03, 0x18, 0x03, 0x01, 0x04, 0x30, 0x03, 0x01, 0x04, 0x30, 0x03, 0x01, 0x04, 0x60, 0x03, 0x01, 0x04, 0x60, 0x03, 0x01, 0x04, 0x40, 0x04, 0x04, 0x09, 0x80, 0x04, 0x04, 0x0A, 0x80, 0x04, 0x04, 0x0B, 0x40, 0x04, 0x04, 0x0C, 0x40, 0x04, 0x04, 0x0D, 0x40, 0x04, 0x04, 0x0D, 0x40, 0x04, 0x04, 0x0D, 0x00, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, 0x40, 0x01, 0x01, 0x03, 0x00, 0x01, 0x01, 0x03, }; public static uint surfaceGetBitsPerPixel(uint surfaceFormat) { return formatHwInfo[(surfaceFormat & 0x3F) * 4]; } public static uint nextPow2(uint dim) { uint newDim = 1; if (dim < 0x7FFFFFFF) { while (newDim < dim) newDim *= 2; } else newDim = 0x80000000; return newDim; } public static uint getDefaultGX2TileMode(uint dim, uint width, uint height, uint depth, uint format_, uint aa, uint use) { uint tileMode = 1; bool IsDepthBuffer = (use & 4) != 0; bool isColorBuffer = (use & 2) != 0; if (dim != 0 || aa != 0 || IsDepthBuffer) { if (dim != 2 || isColorBuffer) tileMode = 4; else tileMode = 7; var surfOut = getSurfaceInfo((GX2SurfaceFormat)format_, width, height, depth, dim, tileMode, aa, 0); if (width < surfOut.pitchAlign && height < surfOut.heightAlign) { if (tileMode == 7) tileMode = 3; else tileMode = 2; } } return tileMode; } private static uint GX2TileModeToAddrTileMode(uint tileMode) { if (tileMode == 0) throw new Exception("Use tileMode from getDefaultGX2TileMode()."); if (tileMode == 16) return 0; return tileMode; } private static uint computeSurfaceThickness(AddrTileMode tileMode) { switch (tileMode) { case AddrTileMode.ADDR_TM_1D_TILED_THICK: case AddrTileMode.ADDR_TM_2D_TILED_THICK: case AddrTileMode.ADDR_TM_2B_TILED_THICK: case AddrTileMode.ADDR_TM_3D_TILED_THICK: case AddrTileMode.ADDR_TM_3B_TILED_THICK: return 4; case AddrTileMode.ADDR_TM_2D_TILED_XTHICK: case AddrTileMode.ADDR_TM_3D_TILED_XTHICK: return 8; default: return 1; } } private static uint computePixelIndexWithinMicroTile(uint x, uint y, uint z, uint bpp, AddrTileMode tileMode, bool IsDepth) { uint pixelBit0 = 0; uint pixelBit1 = 0; uint pixelBit2 = 0; uint pixelBit3 = 0; uint pixelBit4 = 0; uint pixelBit5 = 0; uint pixelBit6 = 0; uint pixelBit7 = 0; uint pixelBit8 = 0; uint thickness = computeSurfaceThickness(tileMode); if (IsDepth) { pixelBit0 = x & 1; pixelBit1 = y & 1; pixelBit2 = (x & 2) >> 1; pixelBit3 = (y & 2) >> 1; pixelBit4 = (x & 4) >> 2; pixelBit5 = (y & 4) >> 2; } else { switch (bpp) { case 8: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = (x & 4) >> 2; pixelBit3 = (y & 2) >> 1; pixelBit4 = y & 1; pixelBit5 = (y & 4) >> 2; break; case 0x10: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = (x & 4) >> 2; pixelBit3 = y & 1; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; case 0x20: case 0x60: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = y & 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; case 0x40: pixelBit0 = x & 1; pixelBit1 = y & 1; pixelBit2 = (x & 2) >> 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; case 0x80: pixelBit0 = y & 1; pixelBit1 = x & 1; pixelBit2 = (x & 2) >> 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; default: pixelBit0 = x & 1; pixelBit1 = (x & 2) >> 1; pixelBit2 = y & 1; pixelBit3 = (x & 4) >> 2; pixelBit4 = (y & 2) >> 1; pixelBit5 = (y & 4) >> 2; break; } } if (thickness > 1) { pixelBit6 = z & 1; pixelBit7 = (z & 2) >> 1; } if (thickness == 8) pixelBit8 = (z & 4) >> 2; return (pixelBit8 << 8) | (pixelBit7 << 7) | (pixelBit6 << 6) | 32 * pixelBit5 | 16 * pixelBit4 | 8 * pixelBit3 | 4 * pixelBit2 | pixelBit0 | 2 * pixelBit1; } private static uint computePipeFromCoordWoRotation(uint x, uint y) { return ((y >> 3) ^ (x >> 3)) & 1; } private static uint computeBankFromCoordWoRotation(uint x, uint y) { return ((y >> 5) ^ (x >> 3)) & 1 | 2 * (((y >> 4) ^ (x >> 4)) & 1); } private static uint computeSurfaceRotationFromTileMode(AddrTileMode tileMode) { switch (tileMode) { case AddrTileMode.ADDR_TM_2D_TILED_THIN1: case AddrTileMode.ADDR_TM_2D_TILED_THIN2: case AddrTileMode.ADDR_TM_2D_TILED_THIN4: case AddrTileMode.ADDR_TM_2D_TILED_THICK: case AddrTileMode.ADDR_TM_2B_TILED_THIN1: case AddrTileMode.ADDR_TM_2B_TILED_THIN2: case AddrTileMode.ADDR_TM_2B_TILED_THIN4: case AddrTileMode.ADDR_TM_2B_TILED_THICK: return 2; case AddrTileMode.ADDR_TM_3D_TILED_THIN1: case AddrTileMode.ADDR_TM_3D_TILED_THICK: case AddrTileMode.ADDR_TM_3B_TILED_THIN1: case AddrTileMode.ADDR_TM_3B_TILED_THICK: return 1; default: return 0; } } private static uint isThickMacroTiled(AddrTileMode tileMode) { switch (tileMode) { case AddrTileMode.ADDR_TM_2D_TILED_THICK: case AddrTileMode.ADDR_TM_2B_TILED_THICK: case AddrTileMode.ADDR_TM_3D_TILED_THICK: case AddrTileMode.ADDR_TM_3B_TILED_THICK: return 1; default: return 0; } } private static uint isBankSwappedTileMode(AddrTileMode tileMode) { switch (tileMode) { case AddrTileMode.ADDR_TM_2B_TILED_THIN1: case AddrTileMode.ADDR_TM_2B_TILED_THIN2: case AddrTileMode.ADDR_TM_2B_TILED_THIN4: case AddrTileMode.ADDR_TM_2B_TILED_THICK: case AddrTileMode.ADDR_TM_3B_TILED_THIN1: case AddrTileMode.ADDR_TM_3B_TILED_THICK: return 1; default: return 0; } } private static uint computeMacroTileAspectRatio(AddrTileMode tileMode) { switch (tileMode) { case AddrTileMode.ADDR_TM_2D_TILED_THIN2: case AddrTileMode.ADDR_TM_2B_TILED_THIN2: return 2; case AddrTileMode.ADDR_TM_2D_TILED_THIN4: case AddrTileMode.ADDR_TM_2B_TILED_THIN4: return 4; default: return 1; } } private static uint computeSurfaceBankSwappedWidth(AddrTileMode tileMode, uint bpp, uint numSamples, uint pitch) { if (isBankSwappedTileMode(tileMode) == 0) return 0; uint bytesPerSample = 8 * bpp; uint samplesPerTile, slicesPerTile; if (bytesPerSample != 0) { samplesPerTile = 2048 / bytesPerSample; slicesPerTile = Math.Max(1, numSamples / samplesPerTile); } else slicesPerTile = 1; if (isThickMacroTiled(tileMode) != 0) numSamples = 4; uint bytesPerTileSlice = numSamples * bytesPerSample / slicesPerTile; uint factor = computeMacroTileAspectRatio(tileMode); uint swapTiles = Math.Max(1, 128 / bpp); uint swapWidth = swapTiles * 32; uint heightBytes = numSamples * factor * bpp * 2 / slicesPerTile; uint swapMax = 0x4000 / heightBytes; uint swapMin = 256 / bytesPerTileSlice; uint bankSwapWidth = Math.Min(swapMax, Math.Max(swapMin, swapWidth)); while (bankSwapWidth >= 2 * pitch) bankSwapWidth >>= 1; return bankSwapWidth; } private static ulong computeSurfaceAddrFromCoordLinear(uint x, uint y, uint slice, uint sample, uint bpp, uint pitch, uint height, uint numSlices) { uint sliceOffset = pitch * height * (slice + sample * numSlices); return (y * pitch + x + sliceOffset) * bpp; } private static ulong computeSurfaceAddrFromCoordMicroTiled(uint x, uint y, uint slice, uint bpp, uint pitch, uint height, AddrTileMode tileMode, bool IsDepth) { int microTileThickness = 1; if (tileMode == AddrTileMode.ADDR_TM_1D_TILED_THICK) microTileThickness = 4; uint microTileBytes = (uint)(64 * microTileThickness * bpp + 7) / 8; uint microTilesPerRow = pitch >> 3; uint microTileIndexX = x >> 3; uint microTileIndexY = y >> 3; uint microTileIndexZ = slice / (uint)microTileThickness; ulong microTileOffset = microTileBytes * (microTileIndexX + microTileIndexY * microTilesPerRow); ulong sliceBytes = (ulong)(pitch * height * microTileThickness * bpp + 7) / 8; ulong sliceOffset = microTileIndexZ * sliceBytes; uint pixelIndex = computePixelIndexWithinMicroTile(x, y, slice, bpp, tileMode, IsDepth); ulong pixelOffset = (bpp * pixelIndex) >> 3; return pixelOffset + microTileOffset + sliceOffset; } private static byte[] bankSwapOrder = { 0, 1, 3, 2, 6, 7, 5, 4, 0, 0 }; private static ulong computeSurfaceAddrFromCoordMacroTiled(uint x, uint y, uint slice, uint sample, uint bpp, uint pitch, uint height, uint numSamples, AddrTileMode tileMode, bool IsDepth, uint pipeSwizzle, uint bankSwizzle) { uint microTileThickness = computeSurfaceThickness(tileMode); uint microTileBits = numSamples * bpp * (microTileThickness * 64); uint microTileBytes = (microTileBits + 7) / 8; uint pixelIndex = computePixelIndexWithinMicroTile(x, y, slice, bpp, tileMode, IsDepth); uint bytesPerSample = microTileBytes / numSamples; uint sampleOffset = 0; uint pixelOffset = 0; uint samplesPerSlice = 0; uint numSampleSplits = 0; uint sampleSlice = 0; if (IsDepth) { sampleOffset = bpp * sample; pixelOffset = numSamples * bpp * pixelIndex; } else { sampleOffset = sample * (microTileBits / numSamples); pixelOffset = bpp * pixelIndex; } uint elemOffset = pixelOffset + sampleOffset; if (numSamples <= 1 || microTileBytes <= 2048) { samplesPerSlice = numSamples; numSampleSplits = 1; sampleSlice = 0; } else { samplesPerSlice = 2048 / bytesPerSample; numSampleSplits = numSamples / samplesPerSlice; numSamples = samplesPerSlice; uint tileSliceBits = microTileBits / numSampleSplits; sampleSlice = elemOffset / tileSliceBits; elemOffset %= tileSliceBits; } elemOffset = (elemOffset + 7) / 8; uint pipe = computePipeFromCoordWoRotation(x, y); uint bank = computeBankFromCoordWoRotation(x, y); uint swizzle_ = pipeSwizzle + 2 * bankSwizzle; uint bankPipe = pipe + 2 * bank; uint rotation = computeSurfaceRotationFromTileMode(tileMode); uint sliceIn = slice; if (isThickMacroTiled(tileMode) != 0) sliceIn >>= 2; bankPipe ^= 2 * sampleSlice * 3 ^ (swizzle_ + sliceIn * rotation); bankPipe %= 8; pipe = bankPipe % 2; bank = bankPipe / 2; uint sliceBytes = (height * pitch * microTileThickness * bpp * numSamples + 7) / 8; uint sliceOffset = sliceBytes * (sampleSlice + numSampleSplits * slice) / microTileThickness; uint macroTilePitch = 32; uint macroTileHeight = 16; switch (tileMode) { case AddrTileMode.ADDR_TM_2D_TILED_THIN2: case AddrTileMode.ADDR_TM_2B_TILED_THIN2: { macroTilePitch = 16; macroTileHeight = 32; break; } case AddrTileMode.ADDR_TM_2D_TILED_THIN4: case AddrTileMode.ADDR_TM_2B_TILED_THIN4: { macroTilePitch = 8; macroTileHeight = 64; break; } } uint macroTilesPerRow = pitch / macroTilePitch; uint macroTileBytes = (numSamples * microTileThickness * bpp * macroTileHeight * macroTilePitch + 7) / 8; uint macroTileIndexX = x / macroTilePitch; uint macroTileIndexY = y / macroTileHeight; ulong macroTileOffset = (macroTileIndexX + macroTilesPerRow * macroTileIndexY) * macroTileBytes; if (isBankSwappedTileMode(tileMode) != 0) { uint bankSwapWidth = computeSurfaceBankSwappedWidth(tileMode, bpp, 1, pitch); uint swapIndex = macroTilePitch * macroTileIndexX / bankSwapWidth; bank ^= bankSwapOrder[swapIndex & 3]; } ulong totalOffset = elemOffset + ((macroTileOffset + sliceOffset) >> 3); return bank << 9 | pipe << 8 | totalOffset & 255 | (ulong)((int)totalOffset & -256) << 3; } public static uint computeSurfaceMipLevelTileMode(uint baseTileMode, uint bpp, uint level, uint width, uint height, uint numSlices, uint numSamples, uint isDepth, uint noRecursive) { uint widthAlignFactor = 1; uint macroTileWidth = 32; uint macroTileHeight = 16; uint tileSlices = computeSurfaceTileSlices(baseTileMode, bpp, numSamples); uint expTileMode = baseTileMode; uint widtha, heighta, numSlicesa, thickness, microTileBytes; if (DebugSurface) Console.WriteLine("baseTileMode " + baseTileMode); if (numSamples > 1 || tileSlices > 1 || isDepth != 0) { if (baseTileMode == 7) expTileMode = 4; else if (baseTileMode == 13) expTileMode = 12; else if (baseTileMode == 11) expTileMode = 8; else if (baseTileMode == 15) expTileMode = 14; } if (baseTileMode == 2 && numSamples > 1) { expTileMode = 4; } else if (baseTileMode == 3) { if (numSamples > 1 || isDepth != 0) expTileMode = 2; if (numSamples == 2 || numSamples == 4) expTileMode = 7; } else { expTileMode = baseTileMode; } if (DebugSurface) Console.WriteLine("computeSurfaceMipLevelTileMode expTileMode " + expTileMode); if (noRecursive != 0 || level == 0) return expTileMode; switch (bpp) { case 24: case 48: case 96: bpp /= 3; break; } widtha = nextPow2(width); heighta = nextPow2(height); numSlicesa = nextPow2(numSlices); expTileMode = convertToNonBankSwappedMode((AddrTileMode)expTileMode); thickness = computeSurfaceThickness((AddrTileMode)expTileMode); microTileBytes = (numSamples * bpp * (thickness << 6) + 7) >> 3; if (microTileBytes < 256) { widthAlignFactor = Math.Max(1, 256 / microTileBytes); } if (expTileMode == 4 || expTileMode == 12) { if ((widtha < widthAlignFactor * macroTileWidth) || heighta < macroTileHeight) expTileMode = 2; } else if (expTileMode == 5) { macroTileWidth = 16; macroTileHeight = 32; if ((widtha < widthAlignFactor * macroTileWidth) || heighta < macroTileHeight) expTileMode = 2; } else if (expTileMode == 6) { macroTileWidth = 8; macroTileHeight = 64; if ((widtha < widthAlignFactor * macroTileWidth) || heighta < macroTileHeight) expTileMode = 2; } else if (expTileMode == 7 || expTileMode == 13) { if ((widtha < widthAlignFactor * macroTileWidth) || heighta < macroTileHeight) expTileMode = 3; } if (numSlicesa < 4) { if (expTileMode == 3) expTileMode = 2; else if (expTileMode == 7) expTileMode = 4; else if (expTileMode == 13) expTileMode = 12; } return computeSurfaceMipLevelTileMode( expTileMode, bpp, level, widtha, heighta, numSlicesa, numSamples, isDepth, 1); } private static uint computeSurfaceTileSlices(uint tileMode, uint bpp, uint numSamples) { uint bytePerSample = ((bpp << 6) + 7) >> 3; uint tileSlices = 1; uint samplePerTile; if (computeSurfaceThickness((AddrTileMode)tileMode) > 1) numSamples = 4; if (bytePerSample != 0) { samplePerTile = 2048 / bytePerSample; if (samplePerTile < numSamples) tileSlices = Math.Max(1, numSamples / samplePerTile); } return tileSlices; } private static uint ComputeSurfaceInfoEx() { uint tileMode = pIn.tileMode; uint bpp = pIn.bpp; uint numSamples = Math.Max(1, pIn.numSamples); uint pitch = pIn.width; Console.WriteLine("ComputeSurfaceInfoEx pitch " + pitch); uint height = pIn.height; uint numSlices = pIn.numSlices; uint mipLevel = pIn.mipLevel; Flags flags = new Flags(); uint pPitchOut = pOut.pitch; uint pHeightOut = pOut.height; uint pNumSlicesOut = pOut.depth; uint pTileModeOut = pOut.tileMode; uint pSurfSize = (uint)pOut.surfSize; uint pBaseAlign = pOut.baseAlign; uint pPitchAlign = pOut.pitchAlign; uint pHeightAlign = pOut.heightAlign; uint pDepthAlign = pOut.depthAlign; uint padDims = 0; uint valid = 0; uint baseTileMode = tileMode; if (DebugSurface) { Console.WriteLine("---------------------------"); Console.WriteLine(tileMode); Console.WriteLine(bpp); Console.WriteLine(numSamples); Console.WriteLine(pitch); Console.WriteLine(height); Console.WriteLine(numSlices); Console.WriteLine(mipLevel); Console.WriteLine(flags); Console.WriteLine(pPitchOut); Console.WriteLine(pHeightOut); Console.WriteLine(pNumSlicesOut); Console.WriteLine(pTileModeOut); Console.WriteLine(pSurfSize); Console.WriteLine(pBaseAlign); Console.WriteLine(pPitchAlign); Console.WriteLine(pHeightAlign); Console.WriteLine(pDepthAlign); Console.WriteLine(padDims); Console.WriteLine(valid); Console.WriteLine(baseTileMode); Console.WriteLine("---------------------------"); } flags.value = pIn.flags.value; Console.WriteLine("padDims " + padDims); if ((((flags.value >> 4) & 1) != 0) && (mipLevel == 0)) padDims = 2; Console.WriteLine("padDims " + padDims); if (((flags.value >> 6) & 1) != 0) tileMode = convertToNonBankSwappedMode((AddrTileMode)tileMode); else { if (DebugSurface) Console.WriteLine(tileMode); tileMode = computeSurfaceMipLevelTileMode( tileMode, bpp, mipLevel, pitch, height, numSlices, numSamples, (flags.value >> 1) & 1, 0); if (DebugSurface) { Console.WriteLine("---------------------------"); Console.WriteLine(tileMode); Console.WriteLine("---------------------------"); } } switch (tileMode) { case 0: case 1: var compSurfInfoLinear = computeSurfaceInfoLinear( tileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags); valid = compSurfInfoLinear[0]; pPitchOut = compSurfInfoLinear[1]; pHeightOut = compSurfInfoLinear[2]; pNumSlicesOut = compSurfInfoLinear[3]; pSurfSize = compSurfInfoLinear[4]; pBaseAlign = compSurfInfoLinear[5]; pPitchAlign = compSurfInfoLinear[6]; pHeightAlign = compSurfInfoLinear[7]; pDepthAlign = compSurfInfoLinear[8]; pTileModeOut = tileMode; break; case 2: case 3: var compSurfInfoMicroTile = computeSurfaceInfoMicroTiled( tileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags); valid = compSurfInfoMicroTile[0]; pPitchOut = compSurfInfoMicroTile[1]; pHeightOut = compSurfInfoMicroTile[2]; pNumSlicesOut = compSurfInfoMicroTile[3]; pSurfSize = compSurfInfoMicroTile[4]; pTileModeOut = compSurfInfoMicroTile[5]; pBaseAlign = compSurfInfoMicroTile[6]; pPitchAlign = compSurfInfoMicroTile[7]; pHeightAlign = compSurfInfoMicroTile[8]; pDepthAlign = compSurfInfoMicroTile[9]; break; case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: var compSurfInfoMacoTile = computeSurfaceInfoMacroTiled( tileMode, baseTileMode, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags); valid = compSurfInfoMacoTile[0]; pPitchOut = compSurfInfoMacoTile[1]; pHeightOut = compSurfInfoMacoTile[2]; pNumSlicesOut = compSurfInfoMacoTile[3]; pSurfSize = compSurfInfoMacoTile[4]; pTileModeOut = compSurfInfoMacoTile[5]; pBaseAlign = compSurfInfoMacoTile[6]; pPitchAlign = compSurfInfoMacoTile[7]; pHeightAlign = compSurfInfoMacoTile[8]; pDepthAlign = compSurfInfoMacoTile[9]; break; } pOut.pitch = pPitchOut; pOut.height = pHeightOut; pOut.depth = pNumSlicesOut; pOut.tileMode = pTileModeOut; pOut.surfSize = pSurfSize; pOut.baseAlign = pBaseAlign; pOut.pitchAlign = pPitchAlign; pOut.heightAlign = pHeightAlign; pOut.depthAlign = pDepthAlign; if (DebugSurface) { Console.WriteLine(pOut.pitch); Console.WriteLine(pOut.height); Console.WriteLine(pOut.depth); Console.WriteLine(pOut.tileMode); Console.WriteLine(pOut.surfSize); Console.WriteLine(pOut.baseAlign); Console.WriteLine(pOut.pitchAlign); Console.WriteLine(pOut.heightAlign); Console.WriteLine(pOut.depthAlign); } if (valid == 0) return 3; return 0; } private static uint[] computeSurfaceInfoLinear(uint tileMode, uint bpp, uint numSamples, uint pitch, uint height, uint numSlices, uint mipLevel, uint padDims, Flags flags) { expPitch = pitch; expHeight = height; expNumSlices = numSlices; uint valid = 1; uint microTileThickness = computeSurfaceThickness((AddrTileMode)tileMode); uint baseAlign, pitchAlign, heightAlign, slices; uint pPitchOut, pHeightOut, pNumSlicesOut, pSurfSize, pBaseAlign, pPitchAlign, pHeightAlign, pDepthAlign; var compAllignLinear = computeSurfaceAlignmentsLinear(tileMode, bpp, flags); baseAlign = compAllignLinear.Item1; pitchAlign = compAllignLinear.Item2; heightAlign = compAllignLinear.Item3; if ((((flags.value >> 9) & 1) != 0) && (mipLevel == 0)) { expPitch /= 3; expPitch = nextPow2(expPitch); } if (mipLevel != 0) { expPitch = nextPow2(expPitch); expHeight = nextPow2(expHeight); if (((flags.value >> 4) & 1) != 0) { expNumSlices = numSlices; if (numSlices <= 1) padDims = 2; else padDims = 0; } else expNumSlices = nextPow2(numSlices); } var padimens = padDimensions( tileMode, padDims, (flags.value >> 4) & 1, pitchAlign, heightAlign, microTileThickness); expPitch = padimens.Item1; expHeight = padimens.Item2; expNumSlices = padimens.Item3; if ((((flags.value >> 9) & 1) != 0) && (mipLevel == 0)) expPitch *= 3; slices = expNumSlices * numSamples / microTileThickness; pPitchOut = expPitch; pHeightOut = expHeight; pNumSlicesOut = expNumSlices; pSurfSize = (expHeight * expPitch * slices * bpp * numSamples + 7) / 8; pBaseAlign = baseAlign; pPitchAlign = pitchAlign; pHeightAlign = heightAlign; pDepthAlign = microTileThickness; return new uint[] { valid, pPitchOut, pHeightOut, pNumSlicesOut, pSurfSize, pBaseAlign, pPitchAlign, pHeightAlign, pDepthAlign }; } private static Tuple computeSurfaceAlignmentsLinear(uint tileMode, uint bpp, Flags flags) { uint pixelsPerPipeInterleave; uint baseAlign, pitchAlign, heightAlign; if (tileMode == 0) { baseAlign = 1; pitchAlign = (bpp != 1 ? (uint)1 : 8); heightAlign = 1; } else if (tileMode == 1) { pixelsPerPipeInterleave = 2048 / bpp; baseAlign = 256; pitchAlign = Math.Max(0x40, pixelsPerPipeInterleave); heightAlign = 1; } else { baseAlign = 1; pitchAlign = 1; heightAlign = 1; } pitchAlign = adjustPitchAlignment(flags, pitchAlign); return new Tuple(baseAlign, pitchAlign, heightAlign); } private static uint convertToNonBankSwappedMode(AddrTileMode tileMode) { switch ((uint)tileMode) { case 8: return 4; case 9: return 5; case 10: return 6; case 11: return 7; case 14: return 12; case 15: return 13; } return (uint)tileMode; } private static void computeSurfaceInfo(surfaceIn aSurfIn, surfaceOut pSurfOut) { if (DebugSurface) { Console.WriteLine(" computeSurfaceInfo ------------------------------------ "); } pIn = aSurfIn; pOut = pSurfOut; uint returnCode = 0; uint width, height, bpp, elemMode = 0; uint expandY, expandX; if (pIn.bpp > 0x80) returnCode = 3; if (DebugSurface) Console.WriteLine("returnCode " + returnCode); if (returnCode == 0) { if (DebugSurface) { Console.WriteLine("-------------------------------------------"); Console.WriteLine(" computeMipLevel"); Console.WriteLine("-------------------------------------------"); Console.WriteLine(" pIn.width " + pIn.width); Console.WriteLine(" pIn.height " + pIn.height); Console.WriteLine(" pIn.numSlices " + pIn.numSlices); } computeMipLevel(); width = pIn.width; height = pIn.height; bpp = pIn.bpp; expandX = 1; expandY = 1; if (DebugSurface) { Console.WriteLine(pIn.width); Console.WriteLine(pIn.height); Console.WriteLine(pIn.numSlices); Console.WriteLine("-------------------------------------------"); } pOut.pixelBits = pIn.bpp; if (pIn.format != 0) { bpp = formatExInfo[pIn.format * 4]; expandX = formatExInfo[pIn.format * 4 + 1]; expandY = formatExInfo[pIn.format * 4 + 2]; elemMode = formatExInfo[pIn.format * 4 + 3]; if (DebugSurface) { Console.WriteLine($"bpp {bpp}"); Console.WriteLine($"expandX {expandX}"); Console.WriteLine($"expandY {expandY}"); Console.WriteLine($"elemMode {elemMode}"); } if (elemMode == 4 && expandX == 3 && pIn.tileMode == 1) pIn.flags.value |= 0x200; bpp = adjustSurfaceInfo(elemMode, expandX, expandY, bpp, width, height); if (DebugSurface) { Console.WriteLine($"width {pIn.width}"); Console.WriteLine($"height {pIn.height}"); Console.WriteLine($"bpp {pIn.bpp}"); } } else if (pIn.bpp != 0) { pIn.width = Math.Max(1, pIn.width); pIn.height = Math.Max(1, pIn.height); } else returnCode = 3; if (returnCode == 0) returnCode = ComputeSurfaceInfoEx(); if (returnCode == 0) { pOut.bpp = pIn.bpp; pOut.pixelPitch = pOut.pitch; pOut.pixelHeight = pOut.height; if (pIn.format != 0 && (((pIn.flags.value >> 9) & 1) == 0 || pIn.mipLevel == 0)) bpp = restoreSurfaceInfo(elemMode, expandX, expandY, bpp); if (((pIn.flags.value >> 5) & 1) != 0) pOut.sliceSize = (uint)pOut.surfSize; else { pOut.sliceSize = (uint)(pOut.surfSize / pOut.depth); if (pIn.slice == (pIn.numSlices - 1) && pIn.numSlices > 1) pOut.sliceSize += pOut.sliceSize * (pOut.depth - pIn.numSlices); } pOut.pitchTileMax = (pOut.pitch >> 3) - 1; pOut.heightTileMax = (pOut.height >> 3) - 1; pOut.sliceTileMax = (pOut.height * pOut.pitch >> 6) - 1; } } } private static uint[] computeSurfaceInfoMicroTiled(uint tileMode, uint bpp, uint numSamples, uint pitch, uint height, uint numSlices, uint mipLevel, uint padDims, Flags flags) { expPitch = pitch; expHeight = height; expNumSlices = numSlices; uint valid = 1; uint expTileMode = tileMode; uint microTileThickness = computeSurfaceThickness((AddrTileMode)tileMode); uint pPitchOut, pHeightOut, pNumSlicesOut, pSurfSize, pTileModeOut, pBaseAlign, pPitchAlign, pHeightAlign, pDepthAlign; if (mipLevel != 0) { expPitch = nextPow2(pitch); expHeight = nextPow2(height); if (((flags.value >> 4) & 1) != 0) { expNumSlices = numSlices; if (numSlices <= 1) padDims = 2; else padDims = 0; } else expNumSlices = nextPow2(numSlices); if (expTileMode == 3 && expNumSlices < 4) { expTileMode = 2; microTileThickness = 1; } } var surfMicroAlign = computeSurfaceAlignmentsMicroTiled( expTileMode, bpp, flags, numSamples); uint baseAlign = surfMicroAlign.Item1; uint pitchAlign = surfMicroAlign.Item2; uint heightAlign = surfMicroAlign.Item3; var padDimens = padDimensions( expTileMode, padDims, (flags.value >> 4) & 1, pitchAlign, heightAlign, microTileThickness); expPitch = padDimens.Item1; expHeight = padDimens.Item2; expNumSlices = padDimens.Item3; pPitchOut = expPitch; pHeightOut = expHeight; pNumSlicesOut = expNumSlices; pSurfSize = (expHeight * expPitch * expNumSlices * bpp * numSamples + 7) / 8; Console.WriteLine("pSurfSize " + pSurfSize); Console.WriteLine("expHeight " + expHeight); Console.WriteLine("expPitch " + expPitch); Console.WriteLine("expNumSlices " + expNumSlices); Console.WriteLine("numSamples " + numSamples); pTileModeOut = expTileMode; pBaseAlign = baseAlign; pPitchAlign = pitchAlign; pHeightAlign = heightAlign; pDepthAlign = microTileThickness; return new uint[] { valid, pPitchOut, pHeightOut, pNumSlicesOut, pSurfSize, pTileModeOut, pBaseAlign, pPitchAlign, pHeightAlign, pDepthAlign }; } private static Tuple padDimensions(uint tileMode, uint padDims, uint isCube, uint pitchAlign, uint heightAlign, uint sliceAlign) { uint thickness = computeSurfaceThickness((AddrTileMode)tileMode); if (padDims == 0) padDims = 3; if ((pitchAlign & (pitchAlign - 1)) == 0) expPitch = powTwoAlign(expPitch, pitchAlign); else { expPitch += pitchAlign - 1; expPitch /= pitchAlign; expPitch *= pitchAlign; } if (padDims > 1) expHeight = powTwoAlign(expHeight, heightAlign); if (padDims > 2 || thickness > 1) { if (isCube != 0) expNumSlices = nextPow2(expNumSlices); if (thickness > 1) expNumSlices = powTwoAlign(expNumSlices, sliceAlign); } return new Tuple(expPitch, expHeight, expNumSlices); } private static uint[] computeSurfaceInfoMacroTiled(uint tileMode, uint baseTileMode, uint bpp, uint numSamples, uint pitch, uint height, uint numSlices, uint mipLevel, uint padDims, Flags flags) { expPitch = pitch; expHeight = height; expNumSlices = numSlices; uint valid = 1; uint expTileMode = tileMode; uint microTileThickness = computeSurfaceThickness((AddrTileMode)tileMode); uint baseAlign, pitchAlign, heightAlign, macroWidth, macroHeight; uint bankSwappedWidth, pitchAlignFactor; uint result, pPitchOut, pHeightOut, pNumSlicesOut, pSurfSize, pTileModeOut, pBaseAlign, pPitchAlign, pHeightAlign, pDepthAlign; if (mipLevel != 0) { expPitch = nextPow2(pitch); expHeight = nextPow2(height); if (((flags.value >> 4) & 1) != 0) { expNumSlices = numSlices; if (numSlices <= 1) padDims = 2; else padDims = 0; } else expNumSlices = nextPow2(numSlices); if (expTileMode == 7 && expNumSlices < 4) { expTileMode = 4; microTileThickness = 1; } } if (tileMode == baseTileMode || mipLevel == 0 || isThickMacroTiled((AddrTileMode)baseTileMode) == 0 || isThickMacroTiled((AddrTileMode)tileMode) != 0) { var tup = computeSurfaceAlignmentsMacroTiled( tileMode, bpp, flags, numSamples); baseAlign = tup.Item1; pitchAlign = tup.Item2; heightAlign = tup.Item3; macroWidth = tup.Item4; macroHeight = tup.Item5; bankSwappedWidth = computeSurfaceBankSwappedWidth((AddrTileMode)tileMode, bpp, numSamples, pitch); if (bankSwappedWidth > pitchAlign) pitchAlign = bankSwappedWidth; var padDimens = padDimensions( tileMode, padDims, (flags.value >> 4) & 1, pitchAlign, heightAlign, microTileThickness); expPitch = padDimens.Item1; expHeight = padDimens.Item2; expNumSlices = padDimens.Item3; pPitchOut = expPitch; pHeightOut = expHeight; pNumSlicesOut = expNumSlices; pSurfSize = (expHeight * expPitch * expNumSlices * bpp * numSamples + 7) / 8; pTileModeOut = expTileMode; pBaseAlign = baseAlign; pPitchAlign = pitchAlign; pHeightAlign = heightAlign; pDepthAlign = microTileThickness; result = valid; } else { var tup = computeSurfaceAlignmentsMacroTiled( baseTileMode, bpp, flags, numSamples); baseAlign = tup.Item1; pitchAlign = tup.Item2; heightAlign = tup.Item3; macroWidth = tup.Item4; macroHeight = tup.Item5; pitchAlignFactor = Math.Max(1, 32 / bpp); if (expPitch < pitchAlign * pitchAlignFactor || expHeight < heightAlign) { expTileMode = 2; var microTileInfo = computeSurfaceInfoMicroTiled( 2, bpp, numSamples, pitch, height, numSlices, mipLevel, padDims, flags); result = microTileInfo[0]; pPitchOut = microTileInfo[1]; pHeightOut = microTileInfo[2]; pNumSlicesOut = microTileInfo[3]; pSurfSize = microTileInfo[4]; pTileModeOut = microTileInfo[5]; pBaseAlign = microTileInfo[6]; pPitchAlign = microTileInfo[7]; pHeightAlign = microTileInfo[8]; pDepthAlign = microTileInfo[9]; } else { tup = computeSurfaceAlignmentsMacroTiled( tileMode, bpp, flags, numSamples); baseAlign = tup.Item1; pitchAlign = tup.Item2; heightAlign = tup.Item3; macroWidth = tup.Item4; macroHeight = tup.Item5; bankSwappedWidth = computeSurfaceBankSwappedWidth((AddrTileMode)tileMode, bpp, numSamples, pitch); if (bankSwappedWidth > pitchAlign) pitchAlign = bankSwappedWidth; var padDimens = padDimensions( tileMode, padDims, (flags.value >> 4) & 1, pitchAlign, heightAlign, microTileThickness); expPitch = padDimens.Item1; expHeight = padDimens.Item2; expNumSlices = padDimens.Item3; pPitchOut = expPitch; pHeightOut = expHeight; pNumSlicesOut = expNumSlices; pSurfSize = (expHeight * expPitch * expNumSlices * bpp * numSamples + 7) / 8; pTileModeOut = expTileMode; pBaseAlign = baseAlign; pPitchAlign = pitchAlign; pHeightAlign = heightAlign; pDepthAlign = microTileThickness; result = valid; } } return new uint[] { result, pPitchOut, pHeightOut, pNumSlicesOut, pSurfSize, pTileModeOut, pBaseAlign, pitchAlign, heightAlign, pDepthAlign}; } private static Tuple computeSurfaceAlignmentsMicroTiled(uint tileMode, uint bpp, Flags flags, uint numSamples) { switch (bpp) { case 24: case 48: case 96: bpp /= 3; break; } uint thickness = computeSurfaceThickness((AddrTileMode)tileMode); uint baseAlign = 256; uint pitchAlign = Math.Max(8, 256 / bpp / numSamples / thickness); uint heightAlign = 8; pitchAlign = adjustPitchAlignment(flags, pitchAlign); return new Tuple(baseAlign, pitchAlign, heightAlign); } private static Tuple computeSurfaceAlignmentsMacroTiled(uint tileMode, uint bpp, Flags flags, uint numSamples) { uint aspectRatio = computeMacroTileAspectRatio((AddrTileMode)tileMode); uint thickness = computeSurfaceThickness((AddrTileMode)tileMode); switch (bpp) { case 24: case 48: case 96: bpp /= 3; break; case 3: bpp = 1; break; } uint macroTileWidth = 32 / aspectRatio; uint macroTileHeight = aspectRatio * 16; uint pitchAlign = Math.Max(macroTileWidth, macroTileWidth * (256 / bpp / (8 * thickness) / numSamples)); pitchAlign = adjustPitchAlignment(flags, pitchAlign); uint heightAlign = macroTileHeight; uint macroTileBytes = numSamples * ((bpp * macroTileHeight * macroTileWidth + 7) >> 3); uint baseAlign; if (thickness == 1) baseAlign = Math.Max(macroTileBytes, (numSamples * heightAlign * bpp * pitchAlign + 7) >> 3); else baseAlign = Math.Max(256, (4 * heightAlign * bpp * pitchAlign + 7) >> 3); uint microTileBytes = (thickness * numSamples * (bpp << 6) + 7) >> 3; uint numSlicesPerMicroTile = (microTileBytes < 2048 ? (uint)1 : microTileBytes / 2048); baseAlign /= numSlicesPerMicroTile; return new Tuple(baseAlign, pitchAlign, heightAlign, macroTileWidth, macroTileHeight); } private static uint adjustPitchAlignment(Flags flags, uint pitchAlign) { if (((flags.value >> 13) & 1) != 0) pitchAlign = powTwoAlign(pitchAlign, 0x20); return pitchAlign; } private static uint adjustSurfaceInfo(uint elemMode, uint expandX, uint expandY, uint bpp, uint width, uint height) { uint bBCnFormat = 0; uint widtha, heighta; switch (elemMode) { case 9: case 10: case 11: case 12: case 13: if (bpp != 0) bBCnFormat = 1; break; } if (width != 0 && height != 0) { if (expandX > 1 || expandY > 1) { if (elemMode == 4) { widtha = expandX * width; heighta = expandY * height; } else if (bBCnFormat != 0) { widtha = width / expandX; heighta = height / expandY; } else { widtha = (width + expandX - 1) / expandX; heighta = (height + expandY - 1) / expandY; } pIn.width = Math.Max(1, widtha); pIn.height = Math.Max(1, heighta); } } if (bpp != 0) { switch (elemMode) { case 4: pIn.bpp = bpp / expandX / expandY; break; case 5: case 6: pIn.bpp = expandY * expandX * bpp; break; case 9: case 12: pIn.bpp = 64; break; case 10: case 11: case 13: pIn.bpp = 128; break; default: pIn.bpp = bpp; break; } return pIn.bpp; } return 0; } private static void computeMipLevel() { uint slices = 0; uint height = 0; uint width = 0; uint hwlHandled = 0; if (49 <= pIn.format && pIn.format <= 55 && (pIn.mipLevel == 0 || ((pIn.flags.value >> 12) & 1) != 0)) { pIn.width = powTwoAlign(pIn.width, 4); pIn.height = powTwoAlign(pIn.height, 4); } if (DebugSurface) { Console.WriteLine("-------------------------------------------"); Console.WriteLine(" hwlComputeMipLevel"); Console.WriteLine("-------------------------------------------"); Console.WriteLine(" pIn.width " + pIn.width); Console.WriteLine(" pIn.height " + pIn.height); Console.WriteLine(" pIn.numSlices " + pIn.numSlices); } hwlHandled = hwlComputeMipLevel(); if (DebugSurface) { Console.WriteLine(" Output:"); Console.WriteLine(" pIn.width " + pIn.width); Console.WriteLine(" pIn.height " + pIn.height); Console.WriteLine(" pIn.numSlices " + pIn.numSlices); Console.WriteLine("-------------------------------------------"); } if (hwlHandled == 0 && pIn.mipLevel != 0 && ((pIn.flags.value >> 12) & 1) != 0) { width = Math.Max(1, pIn.width >> (int)pIn.mipLevel); height = Math.Max(1, pIn.height >> (int)pIn.mipLevel); slices = Math.Max(1, pIn.numSlices); if (((pIn.flags.value >> 4) & 1) == 0) slices = Math.Max(1, slices >> (int)pIn.mipLevel); if (pIn.format != 47 && pIn.format != 48) { width = nextPow2(width); height = nextPow2(height); slices = nextPow2(slices); } pIn.width = width; pIn.height = height; pIn.numSlices = slices; } } private static uint restoreSurfaceInfo(uint elemMode, uint expandX, uint expandY, uint bpp) { uint width, height; if (pOut.pixelPitch != 0 && pOut.pixelHeight != 0) { width = pOut.pixelPitch; height = pOut.pixelHeight; if (expandX > 1 || expandY > 1) { if (elemMode == 4) { width /= expandX; height /= expandY; } else { width *= expandX; height *= expandY; } } pOut.pixelPitch = Math.Max(1, width); pOut.pixelHeight = Math.Max(1, height); } if (bpp != 0) { switch (elemMode) { case 4: return expandY * expandX * bpp; case 5: case 6: return bpp / expandX / expandY; case 9: case 12: return 64; case 10: case 11: case 13: return 128; default: return bpp; } } return 0; } private static uint hwlComputeMipLevel() { uint handled = 0; if (49 <= pIn.format && pIn.format <= 55) { if (pIn.mipLevel != 0) { uint width = pIn.width; uint height = pIn.height; uint slices = pIn.numSlices; if (((pIn.flags.value >> 12) & 1) != 0) { uint widtha = width >> (int)pIn.mipLevel; uint heighta = height >> (int)pIn.mipLevel; if (((pIn.flags.value >> 4) & 1) == 0) slices >>= (int)pIn.mipLevel; width = Math.Max(1, widtha); height = Math.Max(1, heighta); slices = Math.Max(1, slices); } pIn.width = nextPow2(width); pIn.height = nextPow2(height); pIn.numSlices = slices; } handled = 1; } return handled; } private static uint powTwoAlign(uint x, uint align) { return ~(align - 1) & (x + align - 1); } /// /// Gets the surface info of a GX2 texture /// /// The of the surface. /// The width of the surface. /// The height of the surface. /// The depth of the surface. /// The /// The /// The /// The mip level of which the info will be calculated for (first mipmap corresponds to value 1 public static surfaceOut getSurfaceInfo(GX2SurfaceFormat surfaceFormat, uint surfaceWidth, uint surfaceHeight, uint surfaceDepth, uint surfaceDim, uint surfaceTileMode, uint surfaceAA, int level) { uint dim = 0; uint width = 0; uint blockSize = 0; uint numSamples = 0; uint hwFormat = 0; var aSurfIn = new surfaceIn(); var pSurfOut = new surfaceOut(); hwFormat = (uint)((int)surfaceFormat & 0x3F); if (surfaceTileMode == 16) { numSamples = (uint)(1 << (int)surfaceAA); if (hwFormat < 0x31 || hwFormat > 0x35) blockSize = 1; else blockSize = 4; width = (uint)(~(blockSize - 1) & (Math.Max(1, surfaceWidth >> level) + blockSize - 1)); pSurfOut.bpp = formatHwInfo[hwFormat * 4]; pSurfOut.size = 96; pSurfOut.pitch = (uint)(width / blockSize); pSurfOut.pixelBits = formatHwInfo[hwFormat * 4]; pSurfOut.baseAlign = 1; pSurfOut.pitchAlign = 1; pSurfOut.heightAlign = 1; pSurfOut.depthAlign = 1; dim = surfaceDim; if (dim == 0) { pSurfOut.height = 1; pSurfOut.depth = 1; } else if (dim == 1 || dim == 6) { pSurfOut.height = Math.Max(1, surfaceHeight >> level); pSurfOut.depth = 1; } else if (dim == 2) { pSurfOut.height = Math.Max(1, surfaceHeight >> level); pSurfOut.depth = Math.Max(1, surfaceDepth >> level); } else if (dim == 3) { pSurfOut.height = Math.Max(1, surfaceHeight >> level); pSurfOut.depth = Math.Max(6, surfaceDepth); } else if (dim == 4) { pSurfOut.height = 1; pSurfOut.depth = surfaceDepth; } else if (dim == 5 || dim == 7) { pSurfOut.height = Math.Max(1, surfaceHeight >> level); pSurfOut.depth = surfaceDepth; } pSurfOut.pixelPitch = width; pSurfOut.pixelHeight = (uint)(~(blockSize - 1) & (pSurfOut.height + blockSize - 1)); pSurfOut.height = (uint)(pSurfOut.pixelHeight / blockSize); pSurfOut.surfSize = pSurfOut.bpp * numSamples * pSurfOut.depth * pSurfOut.height * pSurfOut.pitch >> 3; if (surfaceDim == 2) pSurfOut.sliceSize = (uint)pSurfOut.surfSize; else pSurfOut.sliceSize = (uint)(pSurfOut.surfSize / pSurfOut.depth); pSurfOut.pitchTileMax = (pSurfOut.pitch >> 3) - 1; pSurfOut.heightTileMax = (pSurfOut.height >> 3) - 1; pSurfOut.sliceTileMax = (pSurfOut.height * pSurfOut.pitch >> 6) - 1; } else { aSurfIn.size = 60; aSurfIn.tileMode = surfaceTileMode & 0x0F; aSurfIn.format = hwFormat; aSurfIn.bpp = formatHwInfo[hwFormat * 4]; aSurfIn.numSamples = (uint)(1 << (int)surfaceAA); aSurfIn.numFrags = aSurfIn.numSamples; aSurfIn.width = (uint)Math.Max(1, surfaceWidth >> level); dim = surfaceDim; if (dim == 0) { aSurfIn.height = 1; aSurfIn.numSlices = 1; } else if (dim == 1 || dim == 6) { aSurfIn.height = (uint)Math.Max(1, surfaceHeight >> level); aSurfIn.numSlices = 1; } else if (dim == 2) { aSurfIn.height = (uint)Math.Max(1, surfaceHeight >> level); aSurfIn.numSlices = (uint)Math.Max(1, surfaceDepth >> level); } else if (dim == 3) { aSurfIn.height = (uint)Math.Max(1, surfaceHeight >> level); aSurfIn.numSlices = (uint)Math.Max(6, surfaceDepth); aSurfIn.flags.value |= 0x10; } else if (dim == 4) { aSurfIn.height = 1; aSurfIn.numSlices = (uint)surfaceDepth; } else if (dim == 5 || dim == 7) { aSurfIn.height = (uint)Math.Max(1, surfaceHeight >> level); aSurfIn.numSlices = (uint)surfaceDepth; } aSurfIn.slice = 0; aSurfIn.mipLevel = (uint)level; if (surfaceDim == 2) aSurfIn.flags.value |= 0x20; if (level == 0) aSurfIn.flags.value = (1 << 12) | aSurfIn.flags.value & 0xFFFFEFFF; else aSurfIn.flags.value = aSurfIn.flags.value & 0xFFFFEFFF; pSurfOut.size = 96; computeSurfaceInfo(aSurfIn, pSurfOut); pSurfOut = pOut; } if (pSurfOut.tileMode == 0) pSurfOut.tileMode = 16; return pSurfOut; } } }