public static void RegenerateMipmapsFromTexture2D(NutTexture tex) { if (!TextureFormatTools.IsCompressed(tex.pixelInternalFormat)) { return; } //Rendering.OpenTKSharedResources.dummyResourceWindow.MakeCurrent(); // Create an OpenGL texture with generated mipmaps. Texture2D texture2D = new Texture2D(); texture2D.LoadImageData(tex.Width, tex.Height, tex.surfaces[0].mipmaps[0], (InternalFormat)tex.pixelInternalFormat); texture2D.Bind(); for (int i = 0; i < tex.surfaces[0].mipmaps.Count; i++) { // Get the image size for the current mip level of the bound texture. int imageSize; GL.GetTexLevelParameter(TextureTarget.Texture2D, i, GetTextureParameter.TextureCompressedImageSize, out imageSize); byte[] mipLevelData = new byte[imageSize]; // Replace the Nut texture with the OpenGL texture's data. GL.GetCompressedTexImage(TextureTarget.Texture2D, i, mipLevelData); tex.surfaces[0].mipmaps[i] = mipLevelData; } }
/* * public static bool texIdUsed(int texId) * { * foreach (var nut in Runtime.TextureContainers) * foreach (NutTexture tex in nut.Nodes) * if (tex.HashId == texId) * return true; * return false; * } * * public void ChangeTextureIds(int newTexId) * { * // Check if tex ID fixing would cause any naming conflicts. * if (TexIdDuplicate4thByte()) * { * MessageBox.Show("The first six digits should be the same for all textures to prevent duplicate IDs after changing the Tex ID.", * "Duplicate Texture ID"); * return; * } * * foreach (NutTexture tex in Textures) * { * Texture originalTexture = glTexByHashId[tex.HashId]; * glTexByHashId.Remove(tex.HashId); * * // Only change the first 3 bytes. * tex.HashId = tex.HashId & 0xFF; * int first3Bytes = (int)(newTexId & 0xFFFFFF00); * tex.HashId = tex.HashId | first3Bytes; * * glTexByHashId.Add(tex.HashId, originalTexture); * } * } * * public bool TexIdDuplicate4thByte() * { * // Check for duplicates. * List<byte> previous4thBytes = new List<byte>(); * foreach (NutTexture tex in Textures) * { * byte fourthByte = (byte)(tex.HashId & 0xFF); * if (!(previous4thBytes.Contains(fourthByte))) * previous4thBytes.Add(fourthByte); * else * return true; * * } * * return false; * } */ public static Texture2D CreateTexture2D(NutTexture nutTexture, int surfaceIndex = 0) { bool compressedFormatWithMipMaps = TextureFormatTools.IsCompressed(nutTexture.pixelInternalFormat); List <byte[]> mipmaps = nutTexture.surfaces[surfaceIndex].mipmaps; if (compressedFormatWithMipMaps) { // HACK: Skip loading mipmaps for non square textures for now. // The existing mipmaps don't display properly for some reason. if (nutTexture.surfaces[0].mipmaps.Count > 1 && nutTexture.isDds && (nutTexture.Width == nutTexture.Height)) { // Reading mipmaps past the first level is only supported for DDS currently. Texture2D texture = new Texture2D(); texture.LoadImageData(nutTexture.Width, nutTexture.Height, nutTexture.surfaces[surfaceIndex].mipmaps, (InternalFormat)nutTexture.pixelInternalFormat); return(texture); } else { // Only load the first level and generate the rest. Texture2D texture = new Texture2D(); texture.LoadImageData(nutTexture.Width, nutTexture.Height, mipmaps[0], (InternalFormat)nutTexture.pixelInternalFormat); return(texture); } } else { // Uncompressed. Texture2D texture = new Texture2D(); texture.LoadImageData(nutTexture.Width, nutTexture.Height, mipmaps[0], new TextureFormatUncompressed(nutTexture.pixelInternalFormat, nutTexture.pixelFormat, nutTexture.pixelType)); return(texture); } }
public bool getTextureByID(int hash, out NutTexture suc) { suc = null; foreach (NutTexture t in Textures) { if (t.HashId == hash) { suc = t; return(true); } } return(false); }
public void ConvertToDdsNut(bool regenerateMipMaps = true) { for (int i = 0; i < Textures.Count; i++) { NutTexture originalTexture = (NutTexture)Textures[i]; // Reading/writing mipmaps is only supported for DDS textures, // so we will need to convert all the textures. DDS dds = new DDS(originalTexture); NutTexture ddsTexture = dds.ToNutTexture(); ddsTexture.HashId = originalTexture.HashId; if (regenerateMipMaps) { RegenerateMipmapsFromTexture2D(ddsTexture); } Textures[i] = ddsTexture; } }
public static TextureCubeMap CreateTextureCubeMap(NutTexture t) { if (TextureFormatTools.IsCompressed(t.pixelInternalFormat)) { // Compressed cubemap with mipmaps. TextureCubeMap texture = new TextureCubeMap(); texture.LoadImageData(t.Width, (InternalFormat)t.pixelInternalFormat, t.surfaces[0].mipmaps, t.surfaces[1].mipmaps, t.surfaces[2].mipmaps, t.surfaces[3].mipmaps, t.surfaces[4].mipmaps, t.surfaces[5].mipmaps); return(texture); } else { // Uncompressed cube map with no mipmaps. TextureCubeMap texture = new TextureCubeMap(); texture.LoadImageData(t.Width, new TextureFormatUncompressed(t.pixelInternalFormat, t.pixelFormat, t.pixelType), t.surfaces[0].mipmaps[0], t.surfaces[1].mipmaps[0], t.surfaces[2].mipmaps[0], t.surfaces[3].mipmaps[0], t.surfaces[4].mipmaps[0], t.surfaces[5].mipmaps[0]); return(texture); } }
public NutTexture ToNutTexture() { NutTexture tex = new NutTexture(); tex.isDds = true; tex.HashId = 0x48415348; tex.Height = (int)header.height; tex.Width = (int)header.width; byte surfaceCount = 1; bool isCubemap = (header.caps2 & (uint)DDSCAPS2.CUBEMAP) == (uint)DDSCAPS2.CUBEMAP; if (isCubemap) { if ((header.caps2 & (uint)DDSCAPS2.CUBEMAP_ALLFACES) == (uint)DDSCAPS2.CUBEMAP_ALLFACES) { surfaceCount = 6; } else { throw new NotImplementedException($"Unsupported cubemap face amount for texture. Six faces are required."); } } bool isBlock = true; switch (header.ddspf.fourCC) { case 0x00000000: //RGBA isBlock = false; tex.pixelInternalFormat = PixelInternalFormat.Rgba; tex.pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; break; case 0x31545844: //DXT1 tex.pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt1Ext; break; case 0x33545844: //DXT3 tex.pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt3Ext; break; case 0x35545844: //DXT5 tex.pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt5Ext; break; case 0x31495441: //ATI1 case 0x55344342: //BC4U tex.pixelInternalFormat = PixelInternalFormat.CompressedRedRgtc1; break; case 0x32495441: //ATI2 case 0x55354342: //BC5U tex.pixelInternalFormat = PixelInternalFormat.CompressedRgRgtc2; break; default: MessageBox.Show("Unsupported DDS format - 0x" + header.ddspf.fourCC.ToString("x")); break; } uint formatSize = getFormatSize(header.ddspf.fourCC); FileData d = new FileData(bdata); if (header.mipmapCount == 0) { header.mipmapCount = 1; } uint off = 0; for (byte i = 0; i < surfaceCount; ++i) { TextureSurface surface = new TextureSurface(); uint w = header.width, h = header.height; for (int j = 0; j < header.mipmapCount; ++j) { //If texture is DXT5 and isn't square, limit the mipmaps to an amount such that width and height are each always >= 4 if (tex.pixelInternalFormat == PixelInternalFormat.CompressedRgbaS3tcDxt5Ext && tex.Width != tex.Height && (w < 4 || h < 4)) { break; } uint s = (w * h); //Total pixels if (isBlock) { s = (uint)(s * ((float)formatSize / 0x10)); //Bytes per 16 pixels if (s < formatSize) //Make sure it's at least one block { s = formatSize; } } else { s = (uint)(s * (formatSize)); //Bytes per pixel } w /= 2; h /= 2; surface.mipmaps.Add(d.getSection((int)off, (int)s)); off += s; } tex.surfaces.Add(surface); } return(tex); }
public void FromNutTexture(NutTexture tex) { header = new Header(); header.flags = (uint)(DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT | DDSD.MIPMAPCOUNT | DDSD.LINEARSIZE); header.width = (uint)tex.Width; header.height = (uint)tex.Height; header.pitchOrLinearSize = (uint)tex.ImageSize; header.mipmapCount = (uint)tex.surfaces[0].mipmaps.Count; header.caps2 = tex.DdsCaps2; bool isCubemap = (header.caps2 & (uint)DDSCAPS2.CUBEMAP) == (uint)DDSCAPS2.CUBEMAP; header.caps = (uint)DDSCAPS.TEXTURE; if (header.mipmapCount > 1) { header.caps |= (uint)(DDSCAPS.COMPLEX | DDSCAPS.MIPMAP); } if (isCubemap) { header.caps |= (uint)DDSCAPS.COMPLEX; } switch (tex.pixelInternalFormat) { case PixelInternalFormat.CompressedRgbaS3tcDxt1Ext: header.ddspf.fourCC = 0x31545844; header.ddspf.flags = (uint)DDPF.FOURCC; break; case PixelInternalFormat.CompressedRgbaS3tcDxt3Ext: header.ddspf.fourCC = 0x33545844; header.ddspf.flags = (uint)DDPF.FOURCC; break; case PixelInternalFormat.CompressedRgbaS3tcDxt5Ext: header.ddspf.fourCC = 0x35545844; header.ddspf.flags = (uint)DDPF.FOURCC; break; case PixelInternalFormat.CompressedRedRgtc1: header.ddspf.fourCC = 0x31495441; header.ddspf.flags = (uint)DDPF.FOURCC; break; case PixelInternalFormat.CompressedRgRgtc2: header.ddspf.fourCC = 0x32495441; header.ddspf.flags = (uint)DDPF.FOURCC; break; case PixelInternalFormat.Rgba: header.ddspf.fourCC = 0x00000000; if (tex.pixelFormat == OpenTK.Graphics.OpenGL.PixelFormat.Rgba) { header.ddspf.flags = (uint)(DDPF.RGB | DDPF.ALPHAPIXELS); header.ddspf.RGBBitCount = 0x8 * 4; header.ddspf.RBitMask = 0x000000FF; header.ddspf.GBitMask = 0x0000FF00; header.ddspf.BBitMask = 0x00FF0000; header.ddspf.ABitMask = 0xFF000000; } break; default: throw new NotImplementedException($"Unknown pixel format 0x{tex.pixelInternalFormat:X}"); } List <byte> d = new List <byte>(); foreach (byte[] b in tex.GetAllMipmaps()) { d.AddRange(b); } bdata = d.ToArray(); }
public DDS(NutTexture tex) { FromNutTexture(tex); }
public void ReadNTWU(FileData d, bool skipTextures = true) { d.seek(0x6); ushort count = d.readUShort(); d.skip(0x8); int headerPtr = 0x10; for (ushort i = 0; i < count; ++i) { d.seek(headerPtr); NutTexture tex = new NutTexture(); tex.pixelInternalFormat = PixelInternalFormat.Rgba32ui; int totalSize = d.readInt(); d.skip(4); int dataSize = d.readInt(); int headerSize = d.readUShort(); d.skip(2); d.skip(1); byte mipmapCount = d.readByte(); d.skip(1); tex.setPixelFormatFromNutFormat(d.readByte()); tex.Width = d.readUShort(); tex.Height = d.readUShort(); d.readInt(); //Always 1? uint caps2 = d.readUInt(); bool isCubemap = false; byte surfaceCount = 1; if ((caps2 & (uint)DDS.DDSCAPS2.CUBEMAP) == (uint)DDS.DDSCAPS2.CUBEMAP) { //Only supporting all six faces if ((caps2 & (uint)DDS.DDSCAPS2.CUBEMAP_ALLFACES) == (uint)DDS.DDSCAPS2.CUBEMAP_ALLFACES) { isCubemap = true; surfaceCount = 6; } else { throw new NotImplementedException($"Unsupported cubemap face amount for texture {i} with hash 0x{tex.HashId:X}. Six faces are required."); } } int dataOffset = d.readInt() + headerPtr; int mipDataOffset = d.readInt() + headerPtr; int gtxHeaderOffset = d.readInt() + headerPtr; d.readInt(); int cmapSize1 = 0; int cmapSize2 = 0; if (isCubemap) { cmapSize1 = d.readInt(); cmapSize2 = d.readInt(); d.skip(8); } int imageSize = 0; //Total size of first mipmap of every surface int mipSize = 0; //Total size of mipmaps other than the first of every surface if (mipmapCount == 1) { if (isCubemap) { imageSize = cmapSize1; } else { imageSize = dataSize; } } else { imageSize = d.readInt(); mipSize = d.readInt(); d.skip((mipmapCount - 2) * 4); d.align(0x10); } d.skip(0x10); //eXt data - always the same d.skip(4); //GIDX d.readInt(); //Always 0x10 tex.HashOffset = d.pos(); tex.HashId = d.readInt(); if (!skipTextures) { d.skip(4); // padding align 8 d.seek(gtxHeaderOffset); GTX.GX2Surface gtxHeader = new GTX.GX2Surface(); gtxHeader.dim = d.readInt(); gtxHeader.width = d.readInt(); gtxHeader.height = d.readInt(); gtxHeader.depth = d.readInt(); gtxHeader.numMips = d.readInt(); gtxHeader.format = d.readInt(); gtxHeader.aa = d.readInt(); gtxHeader.use = d.readInt(); gtxHeader.imageSize = d.readInt(); gtxHeader.imagePtr = d.readInt(); gtxHeader.mipSize = d.readInt(); gtxHeader.mipPtr = d.readInt(); gtxHeader.tileMode = d.readInt(); gtxHeader.swizzle = d.readInt(); gtxHeader.alignment = d.readInt(); gtxHeader.pitch = d.readInt(); //mipOffsets[0] is not in this list and is simply the start of the data (dataOffset) //mipOffsets[1] is relative to the start of the data (dataOffset + mipOffsets[1]) //Other mipOffsets are relative to mipOffset[1] (dataOffset + mipOffsets[1] + mipOffsets[i]) int[] mipOffsets = new int[mipmapCount]; mipOffsets[0] = 0; for (byte mipLevel = 1; mipLevel < mipmapCount; ++mipLevel) { mipOffsets[mipLevel] = 0; mipOffsets[mipLevel] = mipOffsets[1] + d.readInt(); } for (byte surfaceLevel = 0; surfaceLevel < surfaceCount; ++surfaceLevel) { tex.surfaces.Add(new TextureSurface()); } int w = tex.Width, h = tex.Height; for (byte mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) { int p = gtxHeader.pitch / (gtxHeader.width / w); int size; if (mipmapCount == 1) { size = imageSize; } else if (mipLevel + 1 == mipmapCount) { size = (mipSize + mipOffsets[1]) - mipOffsets[mipLevel]; } else { size = mipOffsets[mipLevel + 1] - mipOffsets[mipLevel]; } size /= surfaceCount; for (byte surfaceLevel = 0; surfaceLevel < surfaceCount; ++surfaceLevel) { gtxHeader.data = d.getSection(dataOffset + mipOffsets[mipLevel] + (size * surfaceLevel), size); //Real size //Leave the below line commented for now because it breaks RGBA textures //size = ((w + 3) >> 2) * ((h + 3) >> 2) * (GTX.getBPP(gtxHeader.format) / 8); if (size < (GTX.getBPP(gtxHeader.format) / 8)) { size = (GTX.getBPP(gtxHeader.format) / 8); } byte[] deswiz = GTX.swizzleBC( gtxHeader.data, w, h, gtxHeader.format, gtxHeader.tileMode, p, gtxHeader.swizzle ); tex.surfaces[surfaceLevel].mipmaps.Add(new FileData(deswiz).getSection(0, size)); } w /= 2; h /= 2; if (w < 1) { w = 1; } if (h < 1) { h = 1; } } } headerPtr += headerSize; Textures.Add(tex); } }
public void ReadNTP3(FileData d, bool skipTextures = true) { d.seek(0x6); ushort count = d.readUShort(); d.skip(0x8); int headerPtr = 0x10; for (ushort i = 0; i < count; ++i) { d.seek(headerPtr); NutTexture tex = new NutTexture(); tex.isDds = true; tex.pixelInternalFormat = PixelInternalFormat.Rgba32ui; int totalSize = d.readInt(); d.skip(4); int dataSize = d.readInt(); int headerSize = d.readUShort(); d.skip(2); //It might seem that mipmapCount and pixelFormat would be shorts, but they're bytes because they stay in the same place regardless of endianness d.skip(1); byte mipmapCount = d.readByte(); d.skip(1); tex.setPixelFormatFromNutFormat(d.readByte()); tex.Width = d.readUShort(); tex.Height = d.readUShort(); d.skip(4); //0 in dds nuts (like NTP3) and 1 in gtx nuts; texture type? uint caps2 = d.readUInt(); bool isCubemap = false; byte surfaceCount = 1; if ((caps2 & (uint)DDS.DDSCAPS2.CUBEMAP) == (uint)DDS.DDSCAPS2.CUBEMAP) { //Only supporting all six faces if ((caps2 & (uint)DDS.DDSCAPS2.CUBEMAP_ALLFACES) == (uint)DDS.DDSCAPS2.CUBEMAP_ALLFACES) { isCubemap = true; surfaceCount = 6; } else { throw new NotImplementedException($"Unsupported cubemap face amount for texture {i} with hash 0x{tex.HashId:X}. Six faces are required."); } } int dataOffset = 0; if (Version < 0x0200) { dataOffset = headerPtr + headerSize; d.readInt(); } else if (Version >= 0x0200) { dataOffset = d.readInt() + headerPtr; } d.readInt(); d.readInt(); d.readInt(); //The size of a single cubemap face (discounting mipmaps). I don't know why it is repeated. If mipmaps are present, this is also specified in the mipSize section anyway. int cmapSize1 = 0; int cmapSize2 = 0; if (isCubemap) { cmapSize1 = d.readInt(); cmapSize2 = d.readInt(); d.skip(8); } int[] mipSizes = new int[mipmapCount]; if (mipmapCount == 1) { if (isCubemap) { mipSizes[0] = cmapSize1; } else { mipSizes[0] = dataSize; } } else { for (byte mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) { mipSizes[mipLevel] = d.readInt(); } d.align(0x10); } d.skip(0x10); //eXt data - always the same d.skip(4); //GIDX d.readInt(); //Always 0x10 tex.HashOffset = d.pos(); tex.HashId = d.readInt(); d.skip(4); // padding align 8 if (!skipTextures) { for (byte surfaceLevel = 0; surfaceLevel < surfaceCount; ++surfaceLevel) { TextureSurface surface = new TextureSurface(); for (byte mipLevel = 0; mipLevel < mipmapCount; ++mipLevel) { byte[] texArray = d.getSection(dataOffset, mipSizes[mipLevel]); surface.mipmaps.Add(texArray); dataOffset += mipSizes[mipLevel]; } tex.surfaces.Add(surface); } if (tex.getNutFormat() == 14 || tex.getNutFormat() == 17) { tex.SwapChannelOrderUp(); } } if (Version < 0x0200) { headerPtr += totalSize; } else if (Version >= 0x0200) { headerPtr += headerSize; } Textures.Add(tex); } }