using System; using System.Collections.Generic; using System.Linq; using System.Drawing; using System.Drawing.Imaging; using System.Threading.Tasks; using OpenTK.Graphics.OpenGL; using System.Runtime.InteropServices; namespace Toolbox.Library.Rendering { //Parts roughly based on this helpful gl wrapper https://github.com/ScanMountGoat/SFGraphics/blob/89f96b754e17078153315a259baef3859ef5984d/Projects/SFGraphics/GLObjects/Textures/Texture.cs public class RenderableTex { public int width, height; public int TexID = -1; public PixelInternalFormat pixelInternalFormat; public OpenTK.Graphics.OpenGL.PixelFormat pixelFormat; public PixelType pixelType = PixelType.UnsignedByte; public TextureTarget TextureTarget = TextureTarget.Texture2D; public bool GLInitialized = false; public int ImageSize; public bool IsCubeMap = false; public TextureWrapMode TextureWrapS { get { return textureWrapS; } set { textureWrapS = value; SetTextureParameter(TextureParameterName.TextureWrapS, (int)value); } } public TextureWrapMode TextureWrapT { get { return textureWrapT; } set { textureWrapT = value; SetTextureParameter(TextureParameterName.TextureWrapT, (int)value); } } public TextureWrapMode TextureWrapR { get { return textureWrapR; } set { textureWrapR = value; SetTextureParameter(TextureParameterName.TextureWrapR, (int)value); } } public TextureMinFilter TextureMinFilter { get { return textureMinFilter; } set { textureMinFilter = value; SetTextureParameter(TextureParameterName.TextureMinFilter, (int)value); } } public TextureMagFilter TextureMagFilter { get { return textureMagFilter; } set { textureMagFilter = value; SetTextureParameter(TextureParameterName.TextureMinFilter, (int)value); } } private TextureWrapMode textureWrapS = TextureWrapMode.Repeat; private TextureWrapMode textureWrapT = TextureWrapMode.Repeat; private TextureWrapMode textureWrapR = TextureWrapMode.Clamp; private TextureMinFilter textureMinFilter = TextureMinFilter.Linear; private TextureMagFilter textureMagFilter = TextureMagFilter.Linear; public void Dispose() { if (TexID != -1) GL.DeleteTexture(TexID); } public void SetTextureParameter(TextureParameterName param, int value) { Bind(); GL.TexParameter(TextureTarget, param, value); } public static RenderableTex FromBitmap(Bitmap bitmap) { RenderableTex tex = new RenderableTex(); tex.TextureTarget = TextureTarget.Texture2D; tex.TextureWrapS = TextureWrapMode.Repeat; tex.TextureWrapT = TextureWrapMode.Repeat; tex.TextureMinFilter = TextureMinFilter.Linear; tex.TextureMagFilter = TextureMagFilter.Linear; tex.width = bitmap.Width; tex.height = bitmap.Height; tex.pixelInternalFormat = PixelInternalFormat.Rgb8; tex.pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; tex.GLInitialized = true; tex.TexID = GenerateOpenGLTexture(tex, bitmap); return tex; } public void UpdateFromBitmap(Bitmap bitmap) { width = bitmap.Width; height = bitmap.Height; TexID = GenerateOpenGLTexture(this, bitmap); } private bool IsFailedState = false; private bool UseMipmaps = false; private bool UseOpenGLDecoder = true; public void LoadOpenGLTexture(STGenericTexture GenericTexture, int ArrayStartIndex = 0, bool LoadArrayLevels = false) { if (!Runtime.OpenTKInitialized || GLInitialized || Runtime.UseLegacyGL || IsFailedState) return; width = (int)GenericTexture.Width; height = (int)GenericTexture.Height; if (Runtime.DisableLoadingGLHighResTextures) { if (width >= 3000 || height >= 3000) return; } switch (GenericTexture.SurfaceType) { case STSurfaceType.Texture1D: TextureTarget = TextureTarget.Texture1D; break; case STSurfaceType.Texture2D: TextureTarget = TextureTarget.Texture2D; break; case STSurfaceType.Texture2D_Array: TextureTarget = TextureTarget.Texture2DArray; break; case STSurfaceType.Texture2D_Mulitsample: TextureTarget = TextureTarget.Texture2DMultisample; break; case STSurfaceType.Texture2D_Multisample_Array: TextureTarget = TextureTarget.Texture2DMultisampleArray; break; case STSurfaceType.Texture3D: TextureTarget = TextureTarget.Texture3D; break; case STSurfaceType.TextureCube: TextureTarget = TextureTarget.TextureCubeMap; break; case STSurfaceType.TextureCube_Array: TextureTarget = TextureTarget.TextureCubeMapArray; break; } if (GenericTexture.ArrayCount == 0) GenericTexture.ArrayCount = 1; List Surfaces = new List(); try { if (UseMipmaps && GenericTexture.ArrayCount <= 1) { //Load surfaces with mip maps Surfaces = GenericTexture.GetSurfaces(ArrayStartIndex, false, 6); } else { //Only load first mip level. Will be generated after for (int i = 0; i < GenericTexture.ArrayCount; i++) { if (i >= ArrayStartIndex && i <= ArrayStartIndex + 6) //Only load up to 6 faces { Surfaces.Add(new STGenericTexture.Surface() { mipmaps = new List() { GenericTexture.GetImageData(i, 0) } }); } } } if (Surfaces.Count == 0 || Surfaces[0].mipmaps[0].Length == 0) return; IsCubeMap = Surfaces.Count == 6; ImageSize = Surfaces[0].mipmaps[0].Length; if (IsCubeMap) TextureTarget = TextureTarget.TextureCubeMap; //Force RGBA and use ST for decoding for weird width/heights //Open GL decoder has issues with certain width/heights Console.WriteLine($"width pow {width} {IsPow2(width)}"); Console.WriteLine($"height pow {height} {IsPow2(height)}"); if (!IsPow2(width) || !IsPow2(height)) UseOpenGLDecoder = false; pixelInternalFormat = PixelInternalFormat.Rgba; pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; if (GenericTexture.PlatformSwizzle == PlatformSwizzle.Platform_3DS || GenericTexture.PlatformSwizzle == PlatformSwizzle.Platform_Gamecube) { UseOpenGLDecoder = false; pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Bgra; } if (UseOpenGLDecoder) SetPixelFormats(GenericTexture.Format); GLInitialized = true; for (int i = 0; i < Surfaces.Count; i++) { for (int MipLevel = 0; MipLevel < Surfaces[i].mipmaps.Count; MipLevel++) { uint width = Math.Max(1, GenericTexture.Width >> MipLevel); uint height = Math.Max(1, GenericTexture.Height >> MipLevel); Surfaces[i].mipmaps[MipLevel] = DecodeWithoutOpenGLDecoder(Surfaces[i].mipmaps[MipLevel], width, height, GenericTexture); } } TexID = GenerateOpenGLTexture(this, Surfaces); if (IsCubeMap) { TextureWrapS = TextureWrapMode.Clamp; TextureWrapT = TextureWrapMode.Clamp; TextureWrapR = TextureWrapMode.Clamp; TextureMinFilter = TextureMinFilter.LinearMipmapLinear; TextureMagFilter = TextureMagFilter.Linear; } Surfaces.Clear(); } catch { IsFailedState = true; GLInitialized = false; return; } } static bool IsPow2(int Value) { return Value != 0 && (Value & (Value - 1)) == 0; } private void SetPixelFormats(TEX_FORMAT Format) { switch (Format) { case TEX_FORMAT.BC1_UNORM: pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt1Ext; break; case TEX_FORMAT.BC1_UNORM_SRGB: pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt1Ext; break; case TEX_FORMAT.BC2_UNORM: pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt3Ext; break; case TEX_FORMAT.BC2_UNORM_SRGB: pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt3Ext; break; case TEX_FORMAT.BC3_UNORM: pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt5Ext; break; case TEX_FORMAT.BC3_UNORM_SRGB: pixelInternalFormat = PixelInternalFormat.CompressedRgbaS3tcDxt5Ext; break; case TEX_FORMAT.BC4_UNORM: case TEX_FORMAT.BC4_SNORM: //Convert to rgb to prevent red output //While shaders could prevent this, converting is easier and works fine across all editors if (Runtime.UseDirectXTexDecoder) { pixelInternalFormat = PixelInternalFormat.Rgba; pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; } else { pixelInternalFormat = PixelInternalFormat.CompressedRedRgtc1; pixelInternalFormat = PixelInternalFormat.CompressedSignedRedRgtc1; } break; case TEX_FORMAT.BC5_SNORM: pixelInternalFormat = PixelInternalFormat.Rgba; pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; break; case TEX_FORMAT.BC5_UNORM: pixelInternalFormat = PixelInternalFormat.CompressedRgRgtc2; break; case TEX_FORMAT.BC6H_UF16: pixelInternalFormat = PixelInternalFormat.CompressedRgbBptcUnsignedFloat; break; case TEX_FORMAT.BC6H_SF16: pixelInternalFormat = PixelInternalFormat.CompressedRgbBptcSignedFloat; break; case TEX_FORMAT.BC7_UNORM: pixelInternalFormat = PixelInternalFormat.CompressedRgbaBptcUnorm; break; case TEX_FORMAT.BC7_UNORM_SRGB: pixelInternalFormat = PixelInternalFormat.CompressedSrgbAlphaBptcUnorm; break; case TEX_FORMAT.R8G8B8A8_UNORM: pixelInternalFormat = PixelInternalFormat.Rgba; pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; break; case TEX_FORMAT.R8G8B8A8_UNORM_SRGB: pixelInternalFormat = PixelInternalFormat.Rgba; pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; break; default: if (Runtime.UseDirectXTexDecoder) { pixelInternalFormat = PixelInternalFormat.Rgba; pixelFormat = OpenTK.Graphics.OpenGL.PixelFormat.Rgba; } break; } } private byte[] DecodeWithoutOpenGLDecoder(byte[] ImageData, uint width, uint height, STGenericTexture GenericTexture) { if (!UseOpenGLDecoder) { return STGenericTexture.ConvertBgraToRgba( STGenericTexture.DecodeBlock(ImageData, width, height, GenericTexture.Format, GenericTexture.GetPaletteData(), GenericTexture.Parameters, GenericTexture.PaletteFormat, GenericTexture.PlatformSwizzle)); } switch (GenericTexture.Format) { case TEX_FORMAT.BC1_UNORM: case TEX_FORMAT.BC1_UNORM_SRGB: case TEX_FORMAT.BC2_UNORM: case TEX_FORMAT.BC2_UNORM_SRGB: case TEX_FORMAT.BC3_UNORM: case TEX_FORMAT.BC3_UNORM_SRGB: case TEX_FORMAT.BC5_UNORM: case TEX_FORMAT.BC6H_SF16: case TEX_FORMAT.BC6H_UF16: case TEX_FORMAT.BC7_UNORM: case TEX_FORMAT.BC7_UNORM_SRGB: case TEX_FORMAT.R8G8B8A8_UNORM: case TEX_FORMAT.R8G8B8A8_UNORM_SRGB: return ImageData; case TEX_FORMAT.BC5_SNORM: return (DDSCompressor.DecompressBC5(ImageData, (int)width, (int)height, true, true)); default: if (Runtime.UseDirectXTexDecoder) { return STGenericTexture.ConvertBgraToRgba( STGenericTexture.DecodeBlock(ImageData, width, height, GenericTexture.Format, GenericTexture.GetPaletteData(), GenericTexture.Parameters, GenericTexture.PaletteFormat, GenericTexture.PlatformSwizzle)); } else return ImageData; } } public static int GenerateOpenGLTexture(RenderableTex t, Bitmap bitmap, bool generateMips = false) { if (!t.GLInitialized) return -1; int texID = GL.GenTexture(); GL.BindTexture(t.TextureTarget, texID); BitmapData data = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, data.Width, data.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); bitmap.UnlockBits(data); if (generateMips) GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); return texID; } public static int GenerateOpenGLTexture(RenderableTex t, List ImageData) { if (!t.GLInitialized) return -1; int texID = GL.GenTexture(); GL.BindTexture(t.TextureTarget, texID); if (t.IsCubeMap) { if (t.pixelInternalFormat != PixelInternalFormat.Rgba) { for (int mipLevel = 0; mipLevel < ImageData[0].mipmaps.Count; mipLevel++) { int width = Math.Max(1, t.width >> mipLevel); int height = Math.Max(1, t.height >> mipLevel); for (int i = 0; i < 6; i++) t.LoadCompressedMips(TextureTarget.TextureCubeMapPositiveX + i, ImageData[i], width, height, mipLevel); break; } } else { for (int mipLevel = 0; mipLevel < ImageData[0].mipmaps.Count; mipLevel++) { int width = Math.Max(1, t.width >> mipLevel); int height = Math.Max(1, t.height >> mipLevel); for (int i = 0; i < 6; i++) t.LoadUncompressedMips(TextureTarget.TextureCubeMapPositiveX + i, ImageData[i], width, height, mipLevel); break; } } GL.GenerateMipmap(GenerateMipmapTarget.TextureCubeMap); // if (ImageData[0].mipmaps.Count == 1) } else { if (t.pixelInternalFormat != PixelInternalFormat.Rgba) { for (int mipLevel = 0; mipLevel < ImageData[0].mipmaps.Count; mipLevel++) t.LoadCompressedMips(t.TextureTarget, ImageData[0], t.width, t.height, mipLevel); } else { for (int mipLevel = 0; mipLevel < ImageData[0].mipmaps.Count; mipLevel++) t.LoadUncompressedMips(t.TextureTarget, ImageData[0], t.width, t.height, mipLevel); } if (ImageData[0].mipmaps.Count == 1) GL.GenerateMipmap(GenerateMipmapTarget.Texture2D); } return texID; } public void LoadUncompressedMips(TextureTarget textureTarget, STGenericTexture.Surface ImageData,int mipwidth, int mipheight, int MipLevel = 0) { GL.TexImage2D(textureTarget, MipLevel, pixelInternalFormat, mipwidth, mipheight, 0, pixelFormat, PixelType.UnsignedByte, ImageData.mipmaps[MipLevel]); // GL.GenerateMipmap((GenerateMipmapTarget)textureTarget); } public void LoadCompressedMips(TextureTarget textureTarget, STGenericTexture.Surface ImageData, int mipwidth, int mipheight, int MipLevel = 0) { GL.CompressedTexImage2D(textureTarget, MipLevel, (InternalFormat)pixelInternalFormat, mipwidth, mipheight, 0, getImageSize(this), ImageData.mipmaps[MipLevel]); // GL.GenerateMipmap((GenerateMipmapTarget)textureTarget); } public void LoadParameters() { } public void Bind() { if (TexID == -1) return; GL.BindTexture(TextureTarget, TexID); Console.WriteLine($"binding {TextureTarget} {TexID}"); } private static int getImageSize(RenderableTex t) { switch (t.pixelInternalFormat) { case PixelInternalFormat.CompressedRgbaS3tcDxt1Ext: case PixelInternalFormat.CompressedSrgbAlphaS3tcDxt1Ext: case PixelInternalFormat.CompressedRedRgtc1: case PixelInternalFormat.CompressedSignedRedRgtc1: return (t.width * t.height / 2); case PixelInternalFormat.CompressedRgbaS3tcDxt3Ext: case PixelInternalFormat.CompressedSrgbAlphaS3tcDxt3Ext: case PixelInternalFormat.CompressedRgbaS3tcDxt5Ext: case PixelInternalFormat.CompressedSrgbAlphaS3tcDxt5Ext: case PixelInternalFormat.CompressedSignedRgRgtc2: case PixelInternalFormat.CompressedRgRgtc2: case PixelInternalFormat.CompressedRgbaBptcUnorm: case PixelInternalFormat.CompressedSrgbAlphaBptcUnorm: return (t.width * t.height); case PixelInternalFormat.Rgba: return t.ImageSize; default: return t.ImageSize; } } public unsafe Bitmap ToBitmap() { Bitmap bitmap = new Bitmap(width, height); System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.BindTexture(TextureTarget.Texture2D, TexID); GL.GetTexImage(TextureTarget.Texture2D, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bitmapData.Scan0); bitmap.UnlockBits(bitmapData); return bitmap; } public static unsafe Bitmap GLTextureToBitmap(RenderableTex t) { Bitmap bitmap = new Bitmap(t.width, t.height); System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, t.width, t.height), System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); GL.BindTexture(TextureTarget.Texture2D, t.TexID); GL.GetTexImage(TextureTarget.Texture2D, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra, PixelType.UnsignedByte, bitmapData.Scan0); bitmap.UnlockBits(bitmapData); return bitmap; } } }