using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; using LibEveryFileExplorer.IO; using LibEveryFileExplorer.GFX; namespace WiiU.GPU { public class Textures { public enum TileMode : uint { Default = 0, LinearAligned = 1, Tiled1DThin1 = 2, Tiled1DThick = 3, Tiled2DThin1 = 4, Tiled2DThin2 = 5, Tiled2DThin4 = 6, Tiled2DThick = 7, Tiled2BThin1 = 8, Tiled2BThin2 = 9, Tiled2BThin4 = 10, Tiled2BThick = 11, Tiled3DThin1 = 12, Tiled3DThick = 13, Tiled3BThin1 = 14, Tiled3BThick = 15, LinearSpecial = 16 } public enum ImageFormat : uint { RGB565, DXT5 //ETC1, //ETC1A4 } //private static readonly int[] Bpp = { 32, 24, 16, 16, 16, 16, 16, 8, 8, 8, 4, 4, 4, 8 }; private static readonly int[] TileOrder = { 0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15 }; //1. Swap column 1 and 2, 4 and 5 etc. //2. Put back 8x8 tiles in 16x16 blocks in the order 0, 3 // 1, 2 private static readonly int[,] ETC1Modifiers = { { 2, 8 }, { 5, 17 }, { 9, 29 }, { 13, 42 }, { 18, 60 }, { 24, 80 }, { 33, 106 }, { 47, 183 } }; //public static int GetBpp(ImageFormat Format) { return Bpp[(uint)Format]; } public static Bitmap ToBitmap(byte[] Data, int Width, int Height, ImageFormat Format, TileMode TileMode, uint SwizzleMode, bool ExactSize = false) { return ToBitmap(Data, 0, Width, Height, Format, TileMode, SwizzleMode, ExactSize); } public static unsafe Bitmap ToBitmap(byte[] Data, int Offset, int Width, int Height, ImageFormat Format, TileMode TileMode, uint SwizzleMode, bool ExactSize = false) { if (Data == null || Data.Length < 1 || Offset < 0 || Offset >= Data.Length || Width < 1 || Height < 1) return null; if (ExactSize && ((Width % 8) != 0 || (Height % 8) != 0)) return null; int physicalwidth = Width; int physicalheight = Height; if (!ExactSize) { Width = 1 << (int)Math.Ceiling(Math.Log(Width, 2)); Height = 1 << (int)Math.Ceiling(Math.Log(Height, 2)); } Bitmap bitm = new Bitmap(Width, Height);//physicalwidth, physicalheight); BitmapData d = bitm.LockBits(new Rectangle(0, 0, bitm.Width, bitm.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); uint* res = (uint*)d.Scan0; int offs = Offset;//0; int stride = d.Stride / 4; switch (Format) { case ImageFormat.RGB565: for (int y = 0; y < Height; y++) { for (int x = 0; x < Width; x++) { //if (x >= physicalwidth) continue; if (y >= physicalheight) continue; res[y * stride + x] = GFXUtil.ConvertColorFormat( IOUtil.ReadU16LE(Data, offs), ColorFormat.RGB565, ColorFormat.ARGB8888); offs += 2; } } break; case ImageFormat.DXT5: for (int y2 = 0; y2 < Height; y2 += 4) { for (int x2 = 0; x2 < Width; x2 += 4) { ulong a_data = IOUtil.ReadU64LE(Data, offs); byte[] AlphaPalette = new byte[8]; AlphaPalette[0] = (byte)(a_data & 0xFF); AlphaPalette[1] = (byte)((a_data >> 8) & 0xFF); a_data >>= 16; if (AlphaPalette[0] > AlphaPalette[1]) { AlphaPalette[2] = (byte)((6 * AlphaPalette[0] + 1 * AlphaPalette[1]) / 7); AlphaPalette[3] = (byte)((5 * AlphaPalette[0] + 2 * AlphaPalette[1]) / 7); AlphaPalette[4] = (byte)((4 * AlphaPalette[0] + 3 * AlphaPalette[1]) / 7); AlphaPalette[5] = (byte)((3 * AlphaPalette[0] + 4 * AlphaPalette[1]) / 7); AlphaPalette[6] = (byte)((2 * AlphaPalette[0] + 5 * AlphaPalette[1]) / 7); AlphaPalette[7] = (byte)((1 * AlphaPalette[0] + 6 * AlphaPalette[1]) / 7); } else { AlphaPalette[2] = (byte)((4 * AlphaPalette[0] + 1 * AlphaPalette[1]) / 5); AlphaPalette[3] = (byte)((3 * AlphaPalette[0] + 2 * AlphaPalette[1]) / 5); AlphaPalette[4] = (byte)((2 * AlphaPalette[0] + 3 * AlphaPalette[1]) / 5); AlphaPalette[5] = (byte)((1 * AlphaPalette[0] + 4 * AlphaPalette[1]) / 5); AlphaPalette[6] = 0; AlphaPalette[7] = 255; } offs += 8; ushort color0 = IOUtil.ReadU16LE(Data, offs); ushort color1 = IOUtil.ReadU16LE(Data, offs + 2); uint data = IOUtil.ReadU32LE(Data, offs + 4); uint[] Palette = new uint[4]; Palette[0] = GFXUtil.ConvertColorFormat(color0, ColorFormat.RGB565, ColorFormat.ARGB8888); Palette[1] = GFXUtil.ConvertColorFormat(color1, ColorFormat.RGB565, ColorFormat.ARGB8888); Color a = System.Drawing.Color.FromArgb((int)Palette[0]); Color b = System.Drawing.Color.FromArgb((int)Palette[1]); if (color0 > color1)//1/3 and 2/3 { Palette[2] = GFXUtil.ToColorFormat((a.R * 2 + b.R * 1) / 3, (a.G * 2 + b.G * 1) / 3, (a.B * 2 + b.B * 1) / 3, ColorFormat.ARGB8888); Palette[3] = GFXUtil.ToColorFormat((a.R * 1 + b.R * 2) / 3, (a.G * 1 + b.G * 2) / 3, (a.B * 1 + b.B * 2) / 3, ColorFormat.ARGB8888); } else//1/2 and transparent { Palette[2] = GFXUtil.ToColorFormat((a.R + b.R) / 2, (a.G + b.G) / 2, (a.B + b.B) / 2, ColorFormat.ARGB8888); Palette[3] = 0; } int q = 30; int aq = 45; for (int y3 = 0; y3 < 4; y3++) { for (int x3 = 0; x3 < 4; x3++) { //if (x2 + x3 >= physicalwidth) continue; if (y2 + y3 >= physicalheight) continue; res[(y2 + y3) * stride + x2 + x3] = (Palette[(data >> q) & 3] & 0xFFFFFF) | ((uint)AlphaPalette[(a_data >> aq) & 7] << 24); q -= 2; aq -= 3; } } offs += 8; } // } //} } break; /*case ImageFormat.ETC1://Some reference: http://www.khronos.org/registry/gles/extensions/OES/OES_compressed_ETC1_RGB8_texture.txt case ImageFormat.ETC1A4: { for (int y = 0; y < Height; y += 8) { for (int x = 0; x < Width; x += 8) { for (int i = 0; i < 8; i += 4) { for (int j = 0; j < 8; j += 4) { ulong alpha = 0xFFFFFFFFFFFFFFFF; ulong data = IOUtil.ReadU64BE(Data, offs); if (Format == ImageFormat.ETC1A4) { offs += 8; alpha = IOUtil.ReadU64BE(Data, offs); } bool diffbit = ((data >> 33) & 1) == 1; bool flipbit = ((data >> 32) & 1) == 1; //0: |||, 1: |-| int r1, r2, g1, g2, b1, b2; if (diffbit) //'differential' mode { int r = (int)((data >> 59) & 0x1F); int g = (int)((data >> 51) & 0x1F); int b = (int)((data >> 43) & 0x1F); r1 = (r << 3) | ((r & 0x1C) >> 2); g1 = (g << 3) | ((g & 0x1C) >> 2); b1 = (b << 3) | ((b & 0x1C) >> 2); r += (int)((data >> 56) & 0x7) << 29 >> 29; g += (int)((data >> 48) & 0x7) << 29 >> 29; b += (int)((data >> 40) & 0x7) << 29 >> 29; r2 = (r << 3) | ((r & 0x1C) >> 2); g2 = (g << 3) | ((g & 0x1C) >> 2); b2 = (b << 3) | ((b & 0x1C) >> 2); } else //'individual' mode { r1 = (int)((data >> 60) & 0xF) * 0x11; g1 = (int)((data >> 52) & 0xF) * 0x11; b1 = (int)((data >> 44) & 0xF) * 0x11; r2 = (int)((data >> 56) & 0xF) * 0x11; g2 = (int)((data >> 48) & 0xF) * 0x11; b2 = (int)((data >> 40) & 0xF) * 0x11; } int Table1 = (int)((data >> 37) & 0x7); int Table2 = (int)((data >> 34) & 0x7); for (int y3 = 0; y3 < 4; y3++) { for (int x3 = 0; x3 < 4; x3++) { if (x + j + x3 >= physicalwidth) continue; if (y + i + y3 >= physicalheight) continue; int val = (int)((data >> (x3 * 4 + y3)) & 0x1); bool neg = ((data >> (x3 * 4 + y3 + 16)) & 0x1) == 1; uint c; if ((flipbit && y3 < 2) || (!flipbit && x3 < 2)) { int add = ETC1Modifiers[Table1, val] * (neg ? -1 : 1); c = GFXUtil.ToColorFormat((byte)(((alpha >> ((x3 * 4 + y3) * 4)) & 0xF) * 0x11), (byte)ColorClamp(r1 + add), (byte)ColorClamp(g1 + add), (byte)ColorClamp(b1 + add), ColorFormat.ARGB8888); } else { int add = ETC1Modifiers[Table2, val] * (neg ? -1 : 1); c = GFXUtil.ToColorFormat((byte)(((alpha >> ((x3 * 4 + y3) * 4)) & 0xF) * 0x11), (byte)ColorClamp(r2 + add), (byte)ColorClamp(g2 + add), (byte)ColorClamp(b2 + add), ColorFormat.ARGB8888); } res[(i + y3) * stride + x + j + x3] = c; } } offs += 8; } } } res += stride * 8; } } break; * */ } Detile(res, stride, Width, Height, physicalwidth, physicalheight, TileMode); bitm.UnlockBits(d); return bitm; } private static unsafe void Detile(uint* res, int stride, int width, int height, int physicalwidth, int physicalheight, TileMode Mode) { switch (Mode) { case Textures.TileMode.Tiled2DThin1: DetileTiled2DThin1(res, stride, width, height, physicalwidth, physicalheight); return; default: throw new Exception("Unsupported Tilemode!"); } } //private static readonly int[] Tiled2DThin1OrderA = { 0, 1, 3, 2 }; //private static readonly int[] Tiled2DThin1OrderB = { 0, 2, 3, 1 }; private static readonly int[] Tiled2DThin1Order = { 2, 0, 1, 3 }; //Micro tiles: 8x8 //Macro tiles: 32x16 (4x2 tiles = 4 banks, 2 pipes) //Alignment: 2048 pixels (I think), which matches exactly with the 128x16 blocks //First block: // Macro: // 1 2 5 6 // 0 3 4 7 //Second block: // Macro: // 5 6 1 2 // 4 7 0 3 //Third block: // Macro: // 3 0 7 4 // 2 1 6 5 //Fourth block: // Macro: // 7 4 3 0 // 6 5 2 1 private static unsafe void DetileTiled2DThin1(uint* res, int stride, int width, int height, int physicalwidth, int physicalheight) { uint[] right_order = new uint[width * height]; int q = 0; //Let's put the tiles in the right order for (int y = 0; y < height; y += 8) { for (int xx = 0; xx < width; xx += 64) { for (int yy = 0; yy < 8; yy++) { for (int x = 0; x < 64; x++) { right_order[q++] = res[(y + yy) * stride + x + xx]; } } } } q = 0; uint[] Result = new uint[width * height]; for (int y = 0; y < height; y += 8) { for (int x = 0; x < width; x += 8) { for (int yy = 0; yy < 8; yy++) { for (int xx = 0; xx < 8; xx++) { Result[(y + yy) * width + x + xx] = right_order[q++]; } } } } // /* uint[] Result = new uint[width * height]; int px = 0; int py = 0; for (int y = 0; y < height; y += /*8/16) { for (int x = 0; x < width; x += 64) { /*for (int y2 = 0; y2 < 8; y2++) { for (int x2 = 0; x2 < 64; x2++) { Result[(y + (x2 / 8)) * width + x + y2 * 8 + x2 % 8] = res[(y + y2) * stride + x + x2]; } }/ /*for (int i = 0; i < 64; i++)//y2 = 0; y2 < 64; y2 += 8) { int tile = i; int q = Tiled2DThin1Order[(tile / 2) % 4]; int p = tile & ~7; int xx = (p % 16) * 2 + (q % 2) * 8; //if ((i / 0x20) == 1) //{ //xx += (i % 2) * 8; //} /*else / xx += (i % 2) * 32; int yy = p / 16 * 16 + (q / 2) * 8; for (int y3 = 0; y3 < 8; y3++) { for (int x3 = 0; x3 < 8; x3++) { if (y + y3 + yy >= height || x + x3 + xx >= width) continue; //if (x + x2 + x3 >= physicalwidth) continue; //if (y + y3 + yy >= physicalheight) continue; Result[(y + y3 + yy) * width + x + x3 + xx] = ReadPixel(res, stride, width, height, ref px, ref py); } } }/ } }*/ //TODO: We now have the tiles, so start constructing the macro tiles /*R600Tiling t = new R600Tiling(); R600Tiling._ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT r = new R600Tiling._ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_INPUT(); r.bpp = 32; r.tileMode = 4; r.tileType = 0; r.pitch = (uint)stride; r.height = (uint)height; r.numSlices = 1; r.numSamples = (uint)(height * width); R600Tiling._ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT o = new R600Tiling._ADDR_COMPUTE_SURFACE_ADDRFROMCOORD_OUTPUT(); int i = 0; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { r.x = (uint)x; r.y = (uint)y; r.sample = (uint)i; int pixel_number = (int)t.ComputeSurfaceAddrFromCoord(ref r, ref o); /*int pixel_number = 0; pixel_number |= ((x >> 0) & 1) << 0; // pn[0] = x[0] pixel_number |= ((x >> 1) & 1) << 1; // pn[1] = x[1] pixel_number |= ((x >> 2) & 1) << 2; // pn[2] = x[2] pixel_number |= ((y >> 1) & 1) << 3; // pn[3] = y[1] pixel_number |= ((y >> 0) & 1) << 4; // pn[4] = y[0] pixel_number |= ((y >> 2) & 1) << 5; // pn[5] = y[2]/ Result[y * width + x] = res[(pixel_number & 0xFFF) / 4]; i++; } }*/ for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { //if (x >= physicalwidth) continue; if (y >= physicalheight) continue; res[y * stride + x] = Result[y * width + x]; } } /* //Swap columns first! for (int c = 0; c < width; c += 16) { //Swap c + 4 and c + 8 for (int y = 0; y < height; y++) { for (int x = 0; x < 4; x++) { if (c + x >= physicalwidth) continue; if (y >= physicalheight) continue; uint a = res[y * stride + c + x + 4]; uint b = res[y * stride + c + x + 8]; res[y * stride + c + x + 4] = b; res[y * stride + c + x + 8] = a; } } } uint[] Result = new uint[width * height]; //work in 16x16 and then in 8x8 tiles int px = 0; int py = 0; for (int y = 0; y < height; y += 32) { for (int x = 0; x < width; x += 32) { for (int j = 0; j < 4; j++) { int x4 = (Tiled2DThin1OrderA[j] % 2) * 16; int y4 = ((Tiled2DThin1OrderA[j] & 2) >> 1) * 16; //Read out the 256 pixels needed! uint[] Pixels = new uint[256]; for (int i = 0; i < 256; i++) Pixels[i] = ReadPixel(res, stride, width, height, ref px, ref py); int idx = 0; for (int i = 0; i < 4; i++) { int x2 = (Tiled2DThin1OrderB[i] % 2) * 8; int y2 = ((Tiled2DThin1OrderB[i] & 2) >> 1) * 8; for (int y3 = 0; y3 < 8; y3++) { for (int x3 = 0; x3 < 8; x3++) { Result[(y + y2 + y3 + y4) * width + x + x2 + x3 + x4] = Pixels[idx++]; //res[(y + y2 + y3) * stride + x + x2 + x3] = Pixels[idx++]; } } } } } } for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { if (x >= physicalwidth) continue; if (y >= physicalheight) continue; res[y * stride + x] = Result[y * width + x]; } } */ } private static unsafe uint ReadPixel(uint* res, int stride, int width, int height, ref int px, ref int py) { if (px >= width || py >= height || px < 0 || py < 0) return 0;//throw new ArgumentException("ReadPixel fail!"); uint result = res[py * stride + px]; px++; if (px == width) { px = 0; py++; } return result; } private static int ColorClamp(int Color) { if (Color > 255) Color = 255; if (Color < 0) Color = 0; return Color; } } }