diff --git a/NResUI/App.cs b/NResUI/App.cs index 219fe41..34b4689 100644 --- a/NResUI/App.cs +++ b/NResUI/App.cs @@ -47,6 +47,9 @@ public class App serviceCollection.AddSingleton(type); } + serviceCollection.AddSingleton(openGl); + serviceCollection.AddSingleton(window); + serviceCollection.AddSingleton(new NResExplorerViewModel()); serviceCollection.AddSingleton(new TexmExplorerViewModel()); diff --git a/NResUI/ImGuiUI/TexmExplorer.cs b/NResUI/ImGuiUI/TexmExplorer.cs index 3c0bbc6..040a37c 100644 --- a/NResUI/ImGuiUI/TexmExplorer.cs +++ b/NResUI/ImGuiUI/TexmExplorer.cs @@ -1,16 +1,20 @@ -using ImGuiNET; +using System.Numerics; +using ImGuiNET; using NResUI.Abstractions; using NResUI.Models; +using Silk.NET.OpenGL; namespace NResUI.ImGuiUI; public class TexmExplorer : IImGuiPanel { private readonly TexmExplorerViewModel _viewModel; + private readonly GL _openGl; - public TexmExplorer(TexmExplorerViewModel viewModel) + public TexmExplorer(TexmExplorerViewModel viewModel, GL openGl) { _viewModel = viewModel; + _openGl = openGl; } public void OnImGuiRender() @@ -49,7 +53,7 @@ public class TexmExplorer : IImGuiPanel ImGui.SameLine(); ImGui.Text(_viewModel.TexmFile.Header.Stride.ToString()); - ImGui.Text("Magic1: "); + ImGui.Text("Magic1 (possibly ddsCaps): "); ImGui.SameLine(); ImGui.Text(_viewModel.TexmFile.Header.Magic1.ToString()); @@ -64,6 +68,45 @@ public class TexmExplorer : IImGuiPanel ImGui.Text("IsIndexed: "); ImGui.SameLine(); ImGui.Text(_viewModel.TexmFile.IsIndexed.ToString()); + + ImGui.Checkbox("Включить чёрный фон", ref _viewModel.IsBlackBgEnabled); + ImGui.Checkbox("Включить белый фон", ref _viewModel.IsWhiteBgEnabled); + + if (_viewModel.IsWhiteBgEnabled && _viewModel.IsBlackBgEnabled) + { + _viewModel.IsBlackBgEnabled = false; + _viewModel.IsWhiteBgEnabled = false; + } + + _viewModel.GenerateGlTextures(_openGl); + + var drawList = ImGui.GetWindowDrawList(); + for (var index = 0; index < _viewModel.GlTextures.Count; index++) + { + var glTexture = _viewModel.GlTextures[index]; + var screenPos = ImGui.GetCursorScreenPos(); + if (_viewModel.IsBlackBgEnabled) + { + drawList.AddRectFilled(screenPos, screenPos + new Vector2(glTexture.Width, glTexture.Height), 0xFF000000); + } + else if (_viewModel.IsWhiteBgEnabled) + { + drawList.AddRectFilled(screenPos, screenPos + new Vector2(glTexture.Width, glTexture.Height), 0xFFFFFFFF); + } + + ImGui.Image((IntPtr) glTexture.GlTexture, new Vector2(glTexture.Width, glTexture.Height)); + ImGui.SameLine(); + + if (ImGui.IsItemHovered()) + { + var mousePos = ImGui.GetMousePos(); + var relativePos = mousePos - screenPos; + + ImGui.Text("Hovering over: "); + ImGui.SameLine(); + ImGui.Text(relativePos.ToString()); + } + } } } diff --git a/NResUI/Models/TexmExplorerViewModel.cs b/NResUI/Models/TexmExplorerViewModel.cs index 8d25d25..cfc08b7 100644 --- a/NResUI/Models/TexmExplorerViewModel.cs +++ b/NResUI/Models/TexmExplorerViewModel.cs @@ -1,4 +1,5 @@ -using TexmLib; +using Silk.NET.OpenGL; +using TexmLib; namespace NResUI.Models; @@ -8,9 +9,15 @@ public class TexmExplorerViewModel public string? Error { get; set; } public TexmFile? TexmFile { get; set; } - + public string? Path { get; set; } - + public List GlTextures { get; set; } = []; + + private bool _glTexturesDirty = false; + public bool IsWhiteBgEnabled; + + public bool IsBlackBgEnabled; + public void SetParseResult(TexmParseResult result, string path) { Error = result.Error; @@ -22,6 +29,38 @@ public class TexmExplorerViewModel TexmFile = result.TexmFile; Path = path; + _glTexturesDirty = true; + } + + /// + /// Сгенерировать OpenGL текстуры из всех мипмапов Texm файла + /// + public void GenerateGlTextures(GL gl) + { + if (_glTexturesDirty && TexmFile is not null) + { + foreach (var glTexture in GlTextures) + { + glTexture.Dispose(); + } + + GlTextures.Clear(); + + for (var i = 0; i < TexmFile!.Header.MipmapCount; i++) + { + var bytes = TexmFile.GetRgba32BytesFromMipmap(i, out var width, out var height); + + var glTexture = new OpenGlTexture( + gl, + width, + height, + bytes + ); + + GlTextures.Add(glTexture); + } + + _glTexturesDirty = false; + } } - } \ No newline at end of file diff --git a/NResUI/OpenGlTexture.cs b/NResUI/OpenGlTexture.cs new file mode 100644 index 0000000..5ada0d4 --- /dev/null +++ b/NResUI/OpenGlTexture.cs @@ -0,0 +1,152 @@ +using System.Runtime.CompilerServices; +using Silk.NET.OpenGL; + +namespace NResUI +{ + public enum TextureCoordinate + { + S = TextureParameterName.TextureWrapS, + T = TextureParameterName.TextureWrapT, + R = TextureParameterName.TextureWrapR + } + + public class OpenGlTexture : IDisposable + { + public const SizedInternalFormat Srgb8Alpha8 = (SizedInternalFormat)GLEnum.Srgb8Alpha8; + public const SizedInternalFormat Rgb32F = (SizedInternalFormat)GLEnum.Rgb32f; + + public const GLEnum MaxTextureMaxAnisotropy = (GLEnum)0x84FF; + + public static float? MaxAniso; + private readonly GL _gl; + public readonly string Name; + public readonly uint GlTexture; + public readonly uint Width, Height; + public readonly uint MipmapLevels; + public readonly SizedInternalFormat InternalFormat; + + public OpenGlTexture(GL gl, int width, int height, IntPtr data, bool generateMipmaps = false, bool srgb = false) + { + _gl = gl; + MaxAniso ??= gl.GetFloat(MaxTextureMaxAnisotropy); + Width = (uint)width; + Height = (uint)height; + InternalFormat = srgb ? Srgb8Alpha8 : SizedInternalFormat.Rgba8; + MipmapLevels = (uint)(generateMipmaps == false ? 1 : (int)Math.Floor(Math.Log(Math.Max(Width, Height), 2))); + + GlTexture = _gl.GenTexture(); + Bind(); + + _gl.TexStorage2D(GLEnum.Texture2D, MipmapLevels, InternalFormat, Width, Height); + _gl.TexSubImage2D(GLEnum.Texture2D, 0, 0, 0, Width, Height, PixelFormat.Bgra, PixelType.UnsignedByte, data); + + if (generateMipmaps) _gl.GenerateTextureMipmap(GlTexture); + + SetWrap(TextureCoordinate.S, TextureWrapMode.Repeat); + SetWrap(TextureCoordinate.T, TextureWrapMode.Repeat); + + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLevel, MipmapLevels - 1); + } + + public OpenGlTexture(GL gl, int width, int height, byte[] data, bool generateMipmaps = false, bool srgb = false) + { + _gl = gl; + MaxAniso ??= gl.GetFloat(MaxTextureMaxAnisotropy); + Width = (uint)width; + Height = (uint)height; + InternalFormat = srgb ? Srgb8Alpha8 : SizedInternalFormat.Rgba8; + MipmapLevels = (uint)(generateMipmaps == false ? 1 : (int)Math.Floor(Math.Log(Math.Max(Width, Height), 2))); + + GlTexture = _gl.GenTexture(); + Bind(); + + _gl.TexStorage2D(GLEnum.Texture2D, MipmapLevels, InternalFormat, Width, Height); + _gl.TexSubImage2D(GLEnum.Texture2D, 0, 0, 0, Width, Height, PixelFormat.Bgra, PixelType.UnsignedByte, Unsafe.AsRef(ref data[0])); + + if (generateMipmaps) _gl.GenerateTextureMipmap(GlTexture); + + SetWrap(TextureCoordinate.S, TextureWrapMode.Repeat); + SetWrap(TextureCoordinate.T, TextureWrapMode.Repeat); + + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLevel, MipmapLevels - 1); + } + + public OpenGlTexture(GL gl, int width, int height, byte[] data, PixelFormat pixelFormat, bool generateMipmaps = false, bool srgb = false) + { + _gl = gl; + MaxAniso ??= gl.GetFloat(MaxTextureMaxAnisotropy); + Width = (uint)width; + Height = (uint)height; + InternalFormat = srgb ? Srgb8Alpha8 : SizedInternalFormat.Rgba8; + MipmapLevels = (uint)(generateMipmaps == false ? 1 : (int)Math.Floor(Math.Log(Math.Max(Width, Height), 2))); + + GlTexture = _gl.GenTexture(); + Bind(); + + _gl.TexStorage2D(GLEnum.Texture2D, MipmapLevels, InternalFormat, Width, Height); + _gl.TexSubImage2D(GLEnum.Texture2D, 0, 0, 0, Width, Height, pixelFormat, PixelType.UnsignedByte, Unsafe.AsRef(ref data[0])); + + if (generateMipmaps) _gl.GenerateTextureMipmap(GlTexture); + + SetWrap(TextureCoordinate.S, TextureWrapMode.Repeat); + SetWrap(TextureCoordinate.T, TextureWrapMode.Repeat); + + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLevel, MipmapLevels - 1); + } + + public void UpdateData(byte[] data, PixelFormat pixelFormat) + { + Bind(); + + _gl.TexSubImage2D(GLEnum.Texture2D, 0, 0, 0, Width, Height, pixelFormat, PixelType.UnsignedByte, Unsafe.AsRef(ref data[0])); + } + + public byte[] DownloadData() + { + Bind(); + + byte[] data = new byte[Width * Height * 4]; + _gl.GetTexImage(TextureTarget.Texture2D, 0, PixelFormat.Rgba, PixelType.UnsignedByte, out Unsafe.AsRef(ref data[0])); + + return data; + } + + public void Bind() + { + _gl.BindTexture(GLEnum.Texture2D, GlTexture); + } + + public void SetMinFilter(TextureMinFilter filter) + { + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinFilter, (int)filter); + } + + public void SetMagFilter(TextureMagFilter filter) + { + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMagFilter, (int)filter); + } + + // public void SetAnisotropy(float level) + // { + // const TextureParameterName textureMaxAnisotropy = (TextureParameterName)0x84FE; + // _gl.TexParameter(GLEnum.Texture2D, (GLEnum)textureMaxAnisotropy, Util.Clamp(level, 1, MaxAniso.GetValueOrDefault())); + // } + + public void SetLod(int @base, int min, int max) + { + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureLodBias, @base); + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMinLod, min); + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLod, max); + } + + public void SetWrap(TextureCoordinate coord, TextureWrapMode mode) + { + _gl.TexParameterI(GLEnum.Texture2D, (TextureParameterName)coord, (int)mode); + } + + public void Dispose() + { + _gl.DeleteTexture(GlTexture); + } + } +} \ No newline at end of file diff --git a/TexmLib/TexmFile.cs b/TexmLib/TexmFile.cs index 3c2090f..a80c6e1 100644 --- a/TexmLib/TexmFile.cs +++ b/TexmLib/TexmFile.cs @@ -114,6 +114,26 @@ public class TexmFile } } + public byte[] GetRgba32BytesFromMipmap(int index, out int mipWidth, out int mipHeight) + { + var mipmapBytes = MipmapBytes[index]; + + mipWidth = Header.Width / (int) Math.Pow(2, index); + mipHeight = Header.Height / (int) Math.Pow(2, index); + + if (IsIndexed) + { + return ReinterpretIndexedMipmap(mipmapBytes, LookupColors); + } + + return ReinterpretMipmapBytesAsRgba32( + mipmapBytes, + mipWidth, + mipHeight, + Header.Format + ); + } + private byte[] ReinterpretIndexedMipmap(byte[] bytes, byte[] lookupColors) { var span = bytes.AsSpan(); @@ -123,15 +143,15 @@ public class TexmFile { var index = span[i]; - var a = lookupColors[index * 4 + 0]; - var r = lookupColors[index * 4 + 1]; - var g = lookupColors[index * 4 + 2]; - var b = lookupColors[index * 4 + 3]; + var r = lookupColors[index * 4 + 0]; + var g = lookupColors[index * 4 + 1]; + var b = lookupColors[index * 4 + 2]; + var a = lookupColors[index * 4 + 3]; result[i * 4 + 0] = r; result[i * 4 + 1] = g; result[i * 4 + 2] = b; - result[i * 4 + 3] = a; + result[i * 4 + 3] = 255; } return result; @@ -161,8 +181,8 @@ public class TexmFile var rawPixel = span.Slice(i, 2); var r = (byte)(((rawPixel[0] >> 3) & 0b11111) / 32 * 255); - var g = (byte)(((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111) / 64 * 255); - var b = (byte)((rawPixel[1] & 0b11111) / 32 * 255); + var b = (byte)(((rawPixel[0] & 0b111) << 3) | ((rawPixel[1] >> 5) & 0b111) / 64 * 255); + var g = (byte)((rawPixel[1] & 0b11111) / 32 * 255); result[i / 2 * 4 + 0] = r; result[i / 2 * 4 + 1] = g; @@ -182,10 +202,10 @@ public class TexmFile { var rawPixel = span.Slice(i, 2); - var a = (byte)((float)((rawPixel[0] >> 4) & 0b1111) / 16 * 255); - var r = (byte)((float)((rawPixel[0] >> 0) & 0b1111) / 16 * 255); - var g = (byte)((float)((rawPixel[1] >> 4) & 0b1111) / 16 * 255); - var b = (byte)((float)((rawPixel[1] >> 0) & 0b1111) / 16 * 255); + var r = (byte)((float)((rawPixel[0] >> 4) & 0b1111) * 17); + var g = (byte)((float)((rawPixel[0] >> 0) & 0b1111) * 17); + var b = (byte)((float)((rawPixel[1] >> 4) & 0b1111) * 17); + var a = (byte)((float)((rawPixel[1] >> 0) & 0b1111) * 17); result[i / 2 * 4 + 0] = r; result[i / 2 * 4 + 1] = g; @@ -205,14 +225,14 @@ public class TexmFile { var rawPixel = span.Slice(i, 4); - var x = rawPixel[0]; - var y = rawPixel[1]; - var z = rawPixel[2]; + var r = rawPixel[0]; + var g = rawPixel[1]; + var b = rawPixel[2]; var w = rawPixel[3]; - result[i + 0] = y; - result[i + 1] = z; - result[i + 2] = w; + result[i + 0] = r; + result[i + 1] = g; + result[i + 2] = b; result[i + 3] = 255; } @@ -228,10 +248,10 @@ public class TexmFile { var rawPixel = span.Slice(i, 4); - var a = rawPixel[0]; - var r = rawPixel[1]; - var g = rawPixel[2]; - var b = rawPixel[3]; + var b = rawPixel[0]; + var g = rawPixel[1]; + var r = rawPixel[2]; + var a = rawPixel[3]; result[i + 0] = r; result[i + 1] = g;