From eabf6d9bcfcec873ea917bfb97d102b74923c8ad Mon Sep 17 00:00:00 2001 From: 4sval Date: Sat, 3 Sep 2022 13:03:21 +0200 Subject: [PATCH] switch to ImTool --- .../Extensions/ImGuiControllerExtensions.cs | 496 ++++++++++++++++++ FModel/FModel.csproj | 7 +- FModel/Framework/ImGuiFontConfig.cs | 16 + FModel/MainWindow.xaml.cs | 2 +- FModel/Resources/imgui.frag | 13 + FModel/Resources/imgui.vert | 17 + FModel/Views/Snooper/Model.cs | 4 +- FModel/Views/Snooper/Section.cs | 121 ++--- FModel/Views/Snooper/Shader.cs | 35 +- FModel/Views/Snooper/SnimGui.cs | 120 ++++- FModel/Views/Snooper/Texture.cs | 48 +- 11 files changed, 774 insertions(+), 105 deletions(-) create mode 100644 FModel/Extensions/ImGuiControllerExtensions.cs create mode 100644 FModel/Framework/ImGuiFontConfig.cs create mode 100644 FModel/Resources/imgui.frag create mode 100644 FModel/Resources/imgui.vert diff --git a/FModel/Extensions/ImGuiControllerExtensions.cs b/FModel/Extensions/ImGuiControllerExtensions.cs new file mode 100644 index 00000000..369ea737 --- /dev/null +++ b/FModel/Extensions/ImGuiControllerExtensions.cs @@ -0,0 +1,496 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Numerics; +using FModel.Framework; +using ImGuiNET; +using Silk.NET.Input; +using Silk.NET.Input.Extensions; +using Silk.NET.Maths; +using Silk.NET.OpenGL; +using Silk.NET.Windowing; +using Shader = FModel.Views.Snooper.Shader; +using Texture = FModel.Views.Snooper.Texture; + +namespace FModel.Extensions; + +/// +/// because we use ImTool for docking purposes we pasted +/// https://github.com/dotnet/Silk.NET/blob/main/src/OpenGL/Extensions/Silk.NET.OpenGL.Extensions.ImGui/ImGuiController.cs +/// +public class ImGuiControllerExtensions : IDisposable +{ + private GL _gl; + private IView _view; + private IInputContext _input; + private bool _frameBegun; + private readonly List _pressedChars = new (); + private IKeyboard _keyboard; + + private int _attribLocationTex; + private int _attribLocationProjMtx; + private int _attribLocationVtxPos; + private int _attribLocationVtxUV; + private int _attribLocationVtxColor; + private uint _vboHandle; + private uint _elementsHandle; + private uint _vertexArrayObject; + + private Texture _fontTexture; + private Shader _shader; + + private int _windowWidth; + private int _windowHeight; + + public IntPtr Context; + + public ImGuiControllerExtensions(GL gl, IView view, IInputContext input, ImGuiFontConfig? imGuiFontConfig = null) + { + Init(gl, view, input); + + var io = ImGui.GetIO(); + if (imGuiFontConfig is not null) + { + io.Fonts.AddFontFromFileTTF(imGuiFontConfig.Value.FontPath, imGuiFontConfig.Value.FontSize); + } + + io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset; + + CreateDeviceResources(); + SetKeyMappings(); + + SetPerFrameImGuiData(1f / 60f); + + BeginFrame(); + } + + public void MakeCurrent() + { + ImGui.SetCurrentContext(Context); + } + + private void Init(GL gl, IView view, IInputContext input) + { + _gl = gl; + _view = view; + _input = input; + _windowWidth = view.Size.X; + _windowHeight = view.Size.Y; + + Context = ImGui.CreateContext(); + ImGui.SetCurrentContext(Context); + ImGui.StyleColorsDark(); + } + + private void BeginFrame() + { + ImGui.NewFrame(); + _frameBegun = true; + _keyboard = _input.Keyboards[0]; + _view.Resize += WindowResized; + _keyboard.KeyChar += OnKeyChar; + } + + private void OnKeyChar(IKeyboard arg1, char arg2) + { + _pressedChars.Add(arg2); + } + + private void WindowResized(Vector2D size) + { + _windowWidth = size.X; + _windowHeight = size.Y; + } + + public void Render() + { + if (!_frameBegun) return; + + var oldCtx = ImGui.GetCurrentContext(); + if (oldCtx != Context) + ImGui.SetCurrentContext(Context); + + _frameBegun = false; + ImGui.Render(); + RenderImDrawData(ImGui.GetDrawData()); + + if (oldCtx != Context) + ImGui.SetCurrentContext(oldCtx); + } + + public void Update(float deltaSeconds) + { + var oldCtx = ImGui.GetCurrentContext(); + if (oldCtx != Context) + ImGui.SetCurrentContext(Context); + + if (_frameBegun) + ImGui.Render(); + + SetPerFrameImGuiData(deltaSeconds); + UpdateImGuiInput(); + + _frameBegun = true; + ImGui.NewFrame(); + + if (oldCtx != Context) + ImGui.SetCurrentContext(oldCtx); + } + + private void SetPerFrameImGuiData(float deltaSeconds) + { + var io = ImGui.GetIO(); + io.DisplaySize = new Vector2(_windowWidth, _windowHeight); + + if (_windowWidth > 0 && _windowHeight > 0) + { + io.DisplayFramebufferScale = new Vector2(_view.FramebufferSize.X / _windowWidth, + _view.FramebufferSize.Y / _windowHeight); + } + + io.DeltaTime = deltaSeconds; // DeltaTime is in seconds. + } + + private static Key[] keyEnumArr = (Key[]) Enum.GetValues(typeof(Key)); + private void UpdateImGuiInput() + { + var io = ImGui.GetIO(); + + var mouseState = _input.Mice[0].CaptureState(); + var keyboardState = _input.Keyboards[0]; + + io.MouseDown[0] = mouseState.IsButtonPressed(MouseButton.Left); + io.MouseDown[1] = mouseState.IsButtonPressed(MouseButton.Right); + io.MouseDown[2] = mouseState.IsButtonPressed(MouseButton.Middle); + + var point = new Point((int) mouseState.Position.X, (int) mouseState.Position.Y); + io.MousePos = new Vector2(point.X, point.Y); + + var wheel = mouseState.GetScrollWheels()[0]; + io.MouseWheel = wheel.Y; + io.MouseWheelH = wheel.X; + + foreach (var key in keyEnumArr) + { + if (key == Key.Unknown) + { + continue; + } + io.KeysDown[(int) key] = keyboardState.IsKeyPressed(key); + } + + foreach (var c in _pressedChars) + { + io.AddInputCharacter(c); + } + + _pressedChars.Clear(); + + io.KeyCtrl = keyboardState.IsKeyPressed(Key.ControlLeft) || keyboardState.IsKeyPressed(Key.ControlRight); + io.KeyAlt = keyboardState.IsKeyPressed(Key.AltLeft) || keyboardState.IsKeyPressed(Key.AltRight); + io.KeyShift = keyboardState.IsKeyPressed(Key.ShiftLeft) || keyboardState.IsKeyPressed(Key.ShiftRight); + io.KeySuper = keyboardState.IsKeyPressed(Key.SuperLeft) || keyboardState.IsKeyPressed(Key.SuperRight); + } + + private void PressChar(char keyChar) + { + _pressedChars.Add(keyChar); + } + + private static void SetKeyMappings() + { + var io = ImGui.GetIO(); + io.KeyMap[(int) ImGuiKey.Tab] = (int) Key.Tab; + io.KeyMap[(int) ImGuiKey.LeftArrow] = (int) Key.Left; + io.KeyMap[(int) ImGuiKey.RightArrow] = (int) Key.Right; + io.KeyMap[(int) ImGuiKey.UpArrow] = (int) Key.Up; + io.KeyMap[(int) ImGuiKey.DownArrow] = (int) Key.Down; + io.KeyMap[(int) ImGuiKey.PageUp] = (int) Key.PageUp; + io.KeyMap[(int) ImGuiKey.PageDown] = (int) Key.PageDown; + io.KeyMap[(int) ImGuiKey.Home] = (int) Key.Home; + io.KeyMap[(int) ImGuiKey.End] = (int) Key.End; + io.KeyMap[(int) ImGuiKey.Delete] = (int) Key.Delete; + io.KeyMap[(int) ImGuiKey.Backspace] = (int) Key.Backspace; + io.KeyMap[(int) ImGuiKey.Enter] = (int) Key.Enter; + io.KeyMap[(int) ImGuiKey.Escape] = (int) Key.Escape; + io.KeyMap[(int) ImGuiKey.A] = (int) Key.A; + io.KeyMap[(int) ImGuiKey.C] = (int) Key.C; + io.KeyMap[(int) ImGuiKey.V] = (int) Key.V; + io.KeyMap[(int) ImGuiKey.X] = (int) Key.X; + io.KeyMap[(int) ImGuiKey.Y] = (int) Key.Y; + io.KeyMap[(int) ImGuiKey.Z] = (int) Key.Z; + } + + private unsafe void SetupRenderState(ImDrawDataPtr drawDataPtr, int framebufferWidth, int framebufferHeight) + { + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill + _gl.Enable(GLEnum.Blend); + _gl.BlendEquation(GLEnum.FuncAdd); + _gl.BlendFuncSeparate(GLEnum.SrcAlpha, GLEnum.OneMinusSrcAlpha, GLEnum.One, GLEnum.OneMinusSrcAlpha); + _gl.Disable(GLEnum.CullFace); + _gl.Disable(GLEnum.DepthTest); + _gl.Disable(GLEnum.StencilTest); + _gl.Enable(GLEnum.ScissorTest); + _gl.Disable(GLEnum.PrimitiveRestart); + _gl.PolygonMode(GLEnum.FrontAndBack, GLEnum.Fill); + + float L = drawDataPtr.DisplayPos.X; + float R = drawDataPtr.DisplayPos.X + drawDataPtr.DisplaySize.X; + float T = drawDataPtr.DisplayPos.Y; + float B = drawDataPtr.DisplayPos.Y + drawDataPtr.DisplaySize.Y; + + Span orthoProjection = stackalloc float[] { + 2.0f / (R - L), 0.0f, 0.0f, 0.0f, + 0.0f, 2.0f / (T - B), 0.0f, 0.0f, + 0.0f, 0.0f, -1.0f, 0.0f, + (R + L) / (L - R), (T + B) / (B - T), 0.0f, 1.0f, + }; + + _shader.Use(); + _gl.Uniform1(_attribLocationTex, 0); + _gl.UniformMatrix4(_attribLocationProjMtx, 1, false, orthoProjection); + + _gl.BindSampler(0, 0); + + // Setup desired GL state + // Recreate the VAO every time (this is to easily allow multiple GL contexts to be rendered to. VAO are not shared among GL contexts) + // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. + _vertexArrayObject = _gl.GenVertexArray(); + _gl.BindVertexArray(_vertexArrayObject); + + // Bind vertex/index buffers and setup attributes for ImDrawVert + _gl.BindBuffer(GLEnum.ArrayBuffer, _vboHandle); + _gl.BindBuffer(GLEnum.ElementArrayBuffer, _elementsHandle); + _gl.EnableVertexAttribArray((uint) _attribLocationVtxPos); + _gl.EnableVertexAttribArray((uint) _attribLocationVtxUV); + _gl.EnableVertexAttribArray((uint) _attribLocationVtxColor); + _gl.VertexAttribPointer((uint) _attribLocationVtxPos, 2, GLEnum.Float, false, (uint) sizeof(ImDrawVert), (void*) 0); + _gl.VertexAttribPointer((uint) _attribLocationVtxUV, 2, GLEnum.Float, false, (uint) sizeof(ImDrawVert), (void*) 8); + _gl.VertexAttribPointer((uint) _attribLocationVtxColor, 4, GLEnum.UnsignedByte, true, (uint) sizeof(ImDrawVert), (void*) 16); + } + + private unsafe void RenderImDrawData(ImDrawDataPtr drawDataPtr) + { + int framebufferWidth = (int) (drawDataPtr.DisplaySize.X * drawDataPtr.FramebufferScale.X); + int framebufferHeight = (int) (drawDataPtr.DisplaySize.Y * drawDataPtr.FramebufferScale.Y); + if (framebufferWidth <= 0 || framebufferHeight <= 0) + return; + + // Backup GL state + _gl.GetInteger(GLEnum.ActiveTexture, out int lastActiveTexture); + _gl.ActiveTexture(GLEnum.Texture0); + + _gl.GetInteger(GLEnum.CurrentProgram, out int lastProgram); + _gl.GetInteger(GLEnum.TextureBinding2D, out int lastTexture); + + _gl.GetInteger(GLEnum.SamplerBinding, out int lastSampler); + + _gl.GetInteger(GLEnum.ArrayBufferBinding, out int lastArrayBuffer); + _gl.GetInteger(GLEnum.VertexArrayBinding, out int lastVertexArrayObject); + + Span lastPolygonMode = stackalloc int[2]; + _gl.GetInteger(GLEnum.PolygonMode, lastPolygonMode); + + Span lastScissorBox = stackalloc int[4]; + _gl.GetInteger(GLEnum.ScissorBox, lastScissorBox); + + _gl.GetInteger(GLEnum.BlendSrcRgb, out int lastBlendSrcRgb); + _gl.GetInteger(GLEnum.BlendDstRgb, out int lastBlendDstRgb); + + _gl.GetInteger(GLEnum.BlendSrcAlpha, out int lastBlendSrcAlpha); + _gl.GetInteger(GLEnum.BlendDstAlpha, out int lastBlendDstAlpha); + + _gl.GetInteger(GLEnum.BlendEquationRgb, out int lastBlendEquationRgb); + _gl.GetInteger(GLEnum.BlendEquationAlpha, out int lastBlendEquationAlpha); + + bool lastEnableBlend = _gl.IsEnabled(GLEnum.Blend); + bool lastEnableCullFace = _gl.IsEnabled(GLEnum.CullFace); + bool lastEnableDepthTest = _gl.IsEnabled(GLEnum.DepthTest); + bool lastEnableStencilTest = _gl.IsEnabled(GLEnum.StencilTest); + bool lastEnableScissorTest = _gl.IsEnabled(GLEnum.ScissorTest); + bool lastEnablePrimitiveRestart = _gl.IsEnabled(GLEnum.PrimitiveRestart); + + SetupRenderState(drawDataPtr, framebufferWidth, framebufferHeight); + + // Will project scissor/clipping rectangles into framebuffer space + Vector2 clipOff = drawDataPtr.DisplayPos; // (0,0) unless using multi-viewports + Vector2 clipScale = drawDataPtr.FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + for (int n = 0; n < drawDataPtr.CmdListsCount; n++) + { + ImDrawListPtr cmdListPtr = drawDataPtr.CmdListsRange[n]; + + // Upload vertex/index buffers + + _gl.BufferData(GLEnum.ArrayBuffer, (nuint) (cmdListPtr.VtxBuffer.Size * sizeof(ImDrawVert)), (void*) cmdListPtr.VtxBuffer.Data, GLEnum.StreamDraw); + _gl.BufferData(GLEnum.ElementArrayBuffer, (nuint) (cmdListPtr.IdxBuffer.Size * sizeof(ushort)), (void*) cmdListPtr.IdxBuffer.Data, GLEnum.StreamDraw); + + for (int cmd_i = 0; cmd_i < cmdListPtr.CmdBuffer.Size; cmd_i++) + { + ImDrawCmdPtr cmdPtr = cmdListPtr.CmdBuffer[cmd_i]; + + if (cmdPtr.UserCallback != IntPtr.Zero) + throw new NotImplementedException(); + + Vector4 clipRect; + clipRect.X = (cmdPtr.ClipRect.X - clipOff.X) * clipScale.X; + clipRect.Y = (cmdPtr.ClipRect.Y - clipOff.Y) * clipScale.Y; + clipRect.Z = (cmdPtr.ClipRect.Z - clipOff.X) * clipScale.X; + clipRect.W = (cmdPtr.ClipRect.W - clipOff.Y) * clipScale.Y; + + if (clipRect.X < framebufferWidth && clipRect.Y < framebufferHeight && clipRect.Z >= 0.0f && clipRect.W >= 0.0f) + { + // Apply scissor/clipping rectangle + _gl.Scissor((int) clipRect.X, (int) (framebufferHeight - clipRect.W), (uint) (clipRect.Z - clipRect.X), (uint) (clipRect.W - clipRect.Y)); + + // Bind texture, Draw + _gl.BindTexture(GLEnum.Texture2D, (uint) cmdPtr.TextureId); + + _gl.DrawElementsBaseVertex(GLEnum.Triangles, cmdPtr.ElemCount, GLEnum.UnsignedShort, (void*) (cmdPtr.IdxOffset * sizeof(ushort)), (int) cmdPtr.VtxOffset); + } + } + } + + // Destroy the temporary VAO + _gl.DeleteVertexArray(_vertexArrayObject); + _vertexArrayObject = 0; + + // Restore modified GL state + _gl.UseProgram((uint) lastProgram); + _gl.BindTexture(GLEnum.Texture2D, (uint) lastTexture); + + _gl.BindSampler(0, (uint) lastSampler); + + _gl.ActiveTexture((GLEnum) lastActiveTexture); + + _gl.BindVertexArray((uint) lastVertexArrayObject); + + _gl.BindBuffer(GLEnum.ArrayBuffer, (uint) lastArrayBuffer); + _gl.BlendEquationSeparate((GLEnum) lastBlendEquationRgb, (GLEnum) lastBlendEquationAlpha); + _gl.BlendFuncSeparate((GLEnum) lastBlendSrcRgb, (GLEnum) lastBlendDstRgb, (GLEnum) lastBlendSrcAlpha, (GLEnum) lastBlendDstAlpha); + + if (lastEnableBlend) + { + _gl.Enable(GLEnum.Blend); + } + else + { + _gl.Disable(GLEnum.Blend); + } + + if (lastEnableCullFace) + { + _gl.Enable(GLEnum.CullFace); + } + else + { + _gl.Disable(GLEnum.CullFace); + } + + if (lastEnableDepthTest) + { + _gl.Enable(GLEnum.DepthTest); + } + else + { + _gl.Disable(GLEnum.DepthTest); + } + if (lastEnableStencilTest) + { + _gl.Enable(GLEnum.StencilTest); + } + else + { + _gl.Disable(GLEnum.StencilTest); + } + + if (lastEnableScissorTest) + { + _gl.Enable(GLEnum.ScissorTest); + } + else + { + _gl.Disable(GLEnum.ScissorTest); + } + + if (lastEnablePrimitiveRestart) + { + _gl.Enable(GLEnum.PrimitiveRestart); + } + else + { + _gl.Disable(GLEnum.PrimitiveRestart); + } + + _gl.PolygonMode(GLEnum.FrontAndBack, (GLEnum) lastPolygonMode[0]); + _gl.Scissor(lastScissorBox[0], lastScissorBox[1], (uint) lastScissorBox[2], (uint) lastScissorBox[3]); + } + + private void CreateDeviceResources() + { + // Backup GL state + + _gl.GetInteger(GLEnum.TextureBinding2D, out int lastTexture); + _gl.GetInteger(GLEnum.ArrayBufferBinding, out int lastArrayBuffer); + _gl.GetInteger(GLEnum.VertexArrayBinding, out int lastVertexArray); + + _shader = new Shader(_gl, "imgui"); + + _attribLocationTex = _shader.GetUniformLocation("Texture"); + _attribLocationProjMtx = _shader.GetUniformLocation("ProjMtx"); + _attribLocationVtxPos = _shader.GetAttribLocation("Position"); + _attribLocationVtxUV = _shader.GetAttribLocation("UV"); + _attribLocationVtxColor = _shader.GetAttribLocation("Color"); + + _vboHandle = _gl.GenBuffer(); + _elementsHandle = _gl.GenBuffer(); + + RecreateFontDeviceTexture(); + + // Restore modified GL state + _gl.BindTexture(GLEnum.Texture2D, (uint) lastTexture); + _gl.BindBuffer(GLEnum.ArrayBuffer, (uint) lastArrayBuffer); + + _gl.BindVertexArray((uint) lastVertexArray); + } + + /// + /// Creates the texture used to render text. + /// + private unsafe void RecreateFontDeviceTexture() + { + // Build texture atlas + var io = ImGui.GetIO(); + io.Fonts.GetTexDataAsRGBA32(out IntPtr pixels, out int width, out int height, out int bytesPerPixel); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + + // Upload texture to graphics system + _gl.GetInteger(GLEnum.TextureBinding2D, out int lastTexture); + + _fontTexture = new Texture(_gl, (uint) width, (uint) height, pixels); + _fontTexture.Bind(TextureTarget.Texture2D); + _fontTexture.SetMagFilter(TextureMagFilter.Linear); + _fontTexture.SetMinFilter(TextureMinFilter.Linear); + + // Store our identifier + io.Fonts.SetTexID(_fontTexture.GetPointer()); + + // Restore state + _gl.BindTexture(GLEnum.Texture2D, (uint) lastTexture); + } + + public void Dispose() + { + _view.Resize -= WindowResized; + _keyboard.KeyChar -= OnKeyChar; + + _gl.DeleteBuffer(_vboHandle); + _gl.DeleteBuffer(_elementsHandle); + _gl.DeleteVertexArray(_vertexArrayObject); + + _fontTexture.Dispose(); + _shader.Dispose(); + + ImGui.DestroyContext(Context); + } +} diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index ce1ddf6b..34b8130f 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -101,6 +101,8 @@ + + @@ -118,6 +120,8 @@ + + @@ -129,6 +133,7 @@ + @@ -138,8 +143,8 @@ + - diff --git a/FModel/Framework/ImGuiFontConfig.cs b/FModel/Framework/ImGuiFontConfig.cs new file mode 100644 index 00000000..7c862aa3 --- /dev/null +++ b/FModel/Framework/ImGuiFontConfig.cs @@ -0,0 +1,16 @@ +using System; + +namespace FModel.Framework; + +public readonly struct ImGuiFontConfig +{ + public ImGuiFontConfig(string fontPath, int fontSize) + { + if (fontSize <= 0) throw new ArgumentOutOfRangeException(nameof(fontSize)); + FontPath = fontPath ?? throw new ArgumentNullException(nameof(fontPath)); + FontSize = fontSize; + } + + public string FontPath { get; } + public int FontSize { get; } +} diff --git a/FModel/MainWindow.xaml.cs b/FModel/MainWindow.xaml.cs index 563c3b27..8c311284 100644 --- a/FModel/MainWindow.xaml.cs +++ b/FModel/MainWindow.xaml.cs @@ -79,7 +79,7 @@ public partial class MainWindow #if DEBUG await _threadWorkerView.Begin(_ => _applicationView.CUE4Parse.Extract( - "FortniteGame/Content/Environments/Props/Winter/Meshes/SM_ChristmasTree_Llama.uasset")); + "FortniteGame/Content/Characters/Player/Male/Medium/Bodies/M_MED_StaminaCat/Meshes/M_MED_StaminaCat.uasset")); #endif } diff --git a/FModel/Resources/imgui.frag b/FModel/Resources/imgui.frag new file mode 100644 index 00000000..5a705b23 --- /dev/null +++ b/FModel/Resources/imgui.frag @@ -0,0 +1,13 @@ +#version 330 + +layout (location = 0) out vec4 Out_Color; + +in vec2 Frag_UV; +in vec4 Frag_Color; + +uniform sampler2D Texture; + +void main() +{ + Out_Color = Frag_Color * texture(Texture, Frag_UV.st); +} diff --git a/FModel/Resources/imgui.vert b/FModel/Resources/imgui.vert new file mode 100644 index 00000000..284ed1a5 --- /dev/null +++ b/FModel/Resources/imgui.vert @@ -0,0 +1,17 @@ +#version 330 + +layout (location = 0) in vec2 Position; +layout (location = 1) in vec2 UV; +layout (location = 2) in vec4 Color; + +uniform mat4 ProjMtx; + +out vec2 Frag_UV; +out vec4 Frag_Color; + +void main() +{ + Frag_UV = UV; + Frag_Color = Color; + gl_Position = ProjMtx * vec4(Position.xy,0,1); +} diff --git a/FModel/Views/Snooper/Model.cs b/FModel/Views/Snooper/Model.cs index 22c939ca..8c432545 100644 --- a/FModel/Views/Snooper/Model.cs +++ b/FModel/Views/Snooper/Model.cs @@ -26,7 +26,7 @@ public class Model : IDisposable public readonly float[] Vertices; public readonly Section[] Sections; - public Transform Transforms = Transform.Identity; + public readonly Transform Transforms = Transform.Identity; public readonly string[] TransformsLabels = { "X Location", "Y", "Z", "X Rotation", "Y", "Z", @@ -127,7 +127,7 @@ public class Model : IDisposable for (int section = 0; section < Sections.Length; section++) { - Sections[section].Bind(section, _shader); + Sections[section].Bind(_shader); } } diff --git a/FModel/Views/Snooper/Section.cs b/FModel/Views/Snooper/Section.cs index d1bdd2da..3bc30302 100644 --- a/FModel/Views/Snooper/Section.cs +++ b/FModel/Views/Snooper/Section.cs @@ -6,7 +6,6 @@ using CUE4Parse_Conversion.Meshes.PSK; using CUE4Parse_Conversion.Textures; using FModel.Services; using FModel.Settings; -using ImGuiNET; using Silk.NET.OpenGL; namespace FModel.Views.Snooper; @@ -16,16 +15,6 @@ public class Section : IDisposable private uint _handle; private GL _gl; - private Texture _diffuseMap; - private Texture _normalMap; - private Texture _specularMap; - private Texture _emissionMap; - - private bool _hasSpecularMap; - private bool _hasDiffuseColor; - private Vector4 _diffuseColor = Vector4.Zero; - private Vector4 _emissionColor = Vector4.Zero; - private Vector3 _ambientLight; private Vector3 _diffuseLight; private Vector3 _specularLight; @@ -38,9 +27,14 @@ public class Section : IDisposable public readonly int FirstFaceIndex; public readonly CMaterialParams Parameters; - private bool _show = true; - private bool _wireframe; - private bool _selected; + public bool Show; + public bool Wireframe; + public readonly Texture[] Textures; + public readonly string[] TexturesLabels; + public Vector4 DiffuseColor; + public Vector4 EmissionColor; + public bool HasSpecularMap; + public bool HasDiffuseColor; public Section(string name, int index, uint facesCount, int firstFaceIndex, CMeshSection section) { @@ -55,6 +49,12 @@ public class Section : IDisposable unrealMaterial.GetParams(Parameters); } + Show = true; + Textures = new Texture[4]; + TexturesLabels = new[] { "Diffuse", "Normal", "Specular", "Emissive" }; + DiffuseColor = Vector4.Zero; + EmissionColor = Vector4.Zero; + _game = ApplicationService.ApplicationView.CUE4Parse.Game; } @@ -66,34 +66,34 @@ public class Section : IDisposable if (Parameters.IsNull) { - _diffuseColor = new Vector4(1, 0, 0, 1); + DiffuseColor = new Vector4(1, 0, 0, 1); } else { var platform = UserSettings.Default.OverridedPlatform; if (!Parameters.HasTopDiffuseTexture && Parameters.DiffuseColor is { A: > 0 } diffuseColor) { - _diffuseColor = new Vector4(diffuseColor.R, diffuseColor.G, diffuseColor.B, diffuseColor.A); + DiffuseColor = new Vector4(diffuseColor.R, diffuseColor.G, diffuseColor.B, diffuseColor.A); } else if (Parameters.Diffuse is UTexture2D { IsVirtual: false } diffuse) { var mip = diffuse.GetFirstMip(); TextureDecoder.DecodeTexture(mip, diffuse.Format, diffuse.isNormalMap, platform, out var data, out _); - _diffuseMap = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); + Textures[0] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); } if (Parameters.Normal is UTexture2D { IsVirtual: false } normal) { var mip = normal.GetFirstMip(); TextureDecoder.DecodeTexture(mip, normal.Format, normal.isNormalMap, platform, out var data, out _); - _normalMap = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); + Textures[1] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); } if (Parameters.Specular is UTexture2D { IsVirtual: false } specular) { var mip = specular.GetFirstMip(); SwapSpecular(specular, mip, platform, out var data); - _specularMap = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); + Textures[2] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); } if (Parameters.HasTopEmissiveTexture && @@ -102,18 +102,18 @@ public class Section : IDisposable { var mip = emissive.GetFirstMip(); TextureDecoder.DecodeTexture(mip, emissive.Format, emissive.isNormalMap, platform, out var data, out _); - _emissionMap = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); - _emissionColor = new Vector4(emissiveColor.R, emissiveColor.G, emissiveColor.B, emissiveColor.A); + Textures[3] = new Texture(_gl, data, (uint) mip.SizeX, (uint) mip.SizeY); + EmissionColor = new Vector4(emissiveColor.R, emissiveColor.G, emissiveColor.B, emissiveColor.A); } } // diffuse light is based on normal map, so increase ambient if no normal map - _ambientLight = new Vector3(_normalMap == null ? 1.0f : 0.2f); + _ambientLight = new Vector3(Textures[1] == null ? 1.0f : 0.2f); _diffuseLight = new Vector3(0.75f); _specularLight = new Vector3(0.5f); - _hasSpecularMap = _specularMap != null; - _hasDiffuseColor = _diffuseColor != Vector4.Zero; - _show = !Parameters.IsNull && !Parameters.IsTransparent; + HasSpecularMap = Textures[2] != null; + HasDiffuseColor = DiffuseColor != Vector4.Zero; + Show = !Parameters.IsNull && !Parameters.IsTransparent; } /// @@ -220,49 +220,19 @@ public class Section : IDisposable } } - public void Bind(int index, Shader shader) + public void Bind(Shader shader) { - // ImGui.TableNextRow(); - // - // ImGui.TableSetColumnIndex(0); - // ImGui.Text(Index.ToString()); - // ImGui.TableSetColumnIndex(1); - // ImGui.Text(Name); - // if (ImGui.IsItemHovered()) - // { - // ImGui.BeginTooltip(); - // ImGui.Text($"Faces: {FacesCount} ({Math.Round(FacesCount / indices * 100f, 2)}%%)"); - // ImGui.Text($"First Face: {FirstFaceIndex}"); - // ImGui.Separator(); - // if (_hasDiffuseColor) - // { - // ImGui.ColorEdit4("Diffuse Color", ref _diffuseColor, ImGuiColorEditFlags.NoInputs); - // } - // else - // { - // ImGui.Text($"Diffuse: ({Parameters.Diffuse?.ExportType}) {Parameters.Diffuse?.Name}"); - // ImGui.Text($"Normal: ({Parameters.Normal?.ExportType}) {Parameters.Normal?.Name}"); - // ImGui.Text($"Specular: ({Parameters.Specular?.ExportType}) {Parameters.Specular?.Name}"); - // if (Parameters.HasTopEmissiveTexture) - // ImGui.Text($"Emissive: ({Parameters.Emissive?.ExportType}) {Parameters.Emissive?.Name}"); - // ImGui.Separator(); - // } - // ImGui.EndTooltip(); - // } + for (var i = 0; i < Textures.Length; i++) + { + Textures[i]?.Bind(TextureUnit.Texture0 + i); + } - // DrawImGui(index); + shader.SetUniform("material.useSpecularMap", HasSpecularMap); - _diffuseMap?.Bind(TextureUnit.Texture0); - _normalMap?.Bind(TextureUnit.Texture1); - _specularMap?.Bind(TextureUnit.Texture2); - _emissionMap?.Bind(TextureUnit.Texture3); + shader.SetUniform("material.hasDiffuseColor", HasDiffuseColor); + shader.SetUniform("material.diffuseColor", DiffuseColor); - shader.SetUniform("material.useSpecularMap", _hasSpecularMap); - - shader.SetUniform("material.hasDiffuseColor", _hasDiffuseColor); - shader.SetUniform("material.diffuseColor", _diffuseColor); - - shader.SetUniform("material.emissionColor", _emissionColor); + shader.SetUniform("material.emissionColor", EmissionColor); shader.SetUniform("material.shininess", Parameters.MetallicValue); @@ -270,27 +240,16 @@ public class Section : IDisposable shader.SetUniform("light.diffuse", _diffuseLight); shader.SetUniform("light.specular", _specularLight); - _gl.PolygonMode(MaterialFace.Front, _wireframe ? PolygonMode.Line : PolygonMode.Fill); - if (_show) _gl.DrawArrays(PrimitiveType.Triangles, FirstFaceIndex, FacesCount); + _gl.PolygonMode(MaterialFace.Front, Wireframe ? PolygonMode.Line : PolygonMode.Fill); + if (Show) _gl.DrawArrays(PrimitiveType.Triangles, FirstFaceIndex, FacesCount); } public void Dispose() { - _diffuseMap?.Dispose(); - _normalMap?.Dispose(); - _specularMap?.Dispose(); - _emissionMap?.Dispose(); + for (var i = 0; i < Textures.Length; i++) + { + Textures[i]?.Dispose(); + } _gl.DeleteProgram(_handle); } - - private void DrawImGui(int index) - { - ImGui.PushID(index); - ImGui.Selectable(Name, ref _selected); - if (_selected) - { - - } - ImGui.PopID(); - } } diff --git a/FModel/Views/Snooper/Shader.cs b/FModel/Views/Snooper/Shader.cs index 89a6623d..e67fb2ef 100644 --- a/FModel/Views/Snooper/Shader.cs +++ b/FModel/Views/Snooper/Shader.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Numerics; using System.Reflection; @@ -8,8 +9,10 @@ namespace FModel.Views.Snooper; public class Shader : IDisposable { - private uint _handle; - private GL _gl; + private readonly uint _handle; + private readonly GL _gl; + private readonly Dictionary _uniformToLocation = new (); + private readonly Dictionary _attribLocation = new (); public Shader(GL gl) : this(gl, "default") {} @@ -103,6 +106,34 @@ public class Shader : IDisposable _gl.Uniform4(location, value.X, value.Y, value.Z, value.W); } + public int GetUniformLocation(string uniform) + { + if (!_uniformToLocation.TryGetValue(uniform, out int location)) + { + location = _gl.GetUniformLocation(_handle, uniform); + _uniformToLocation.Add(uniform, location); + if (location == -1) + { + Serilog.Log.Debug($"The uniform '{uniform}' does not exist in the shader!"); + } + } + return location; + } + + public int GetAttribLocation(string attrib) + { + if (!_attribLocation.TryGetValue(attrib, out int location)) + { + location = _gl.GetAttribLocation(_handle, attrib); + _attribLocation.Add(attrib, location); + if (location == -1) + { + Serilog.Log.Debug($"The attrib '{attrib}' does not exist in the shader!"); + } + } + return location; + } + public void Dispose() { _gl.DeleteProgram(_handle); diff --git a/FModel/Views/Snooper/SnimGui.cs b/FModel/Views/Snooper/SnimGui.cs index 21168d02..f8b1a6ec 100644 --- a/FModel/Views/Snooper/SnimGui.cs +++ b/FModel/Views/Snooper/SnimGui.cs @@ -1,18 +1,20 @@ using System; using System.Collections.Generic; using System.Numerics; +using FModel.Extensions; +using FModel.Framework; using ImGuiNET; using Silk.NET.Input; using Silk.NET.Maths; using Silk.NET.OpenGL; -using Silk.NET.OpenGL.Extensions.ImGui; using Silk.NET.Windowing; namespace FModel.Views.Snooper; public class SnimGui : IDisposable { - private readonly ImGuiController _controller; + private readonly ImGuiControllerExtensions _controller; + private readonly GraphicsAPI _api; private readonly Vector2 _outlinerSize; private readonly Vector2 _outlinerPosition; @@ -20,29 +22,36 @@ public class SnimGui : IDisposable private readonly Vector2 _propertiesPosition; private readonly Vector2 _viewportSize; private readonly Vector2 _viewportPosition; + private readonly Vector2 _textureSize; + private readonly Vector2 _texturePosition; private bool _viewportFocus; private int _selectedModel; + private int _selectedSection; - private const ImGuiWindowFlags _noResize = ImGuiWindowFlags.NoResize; // delete once we have a proper docking branch + private const ImGuiWindowFlags _noResize = ImGuiWindowFlags.NoResize| ImGuiWindowFlags.NoMove; // delete once we have a proper docking branch private const ImGuiCond _firstUse = ImGuiCond.Appearing; // switch to FirstUseEver once the docking branch will not be useful anymore... private const uint _dockspaceId = 1337; public SnimGui(GL gl, IWindow window, IInputContext input) { var fontConfig = new ImGuiFontConfig("C:\\Windows\\Fonts\\segoeui.ttf", 16); - _controller = new ImGuiController(gl, window, input, fontConfig); + _controller = new ImGuiControllerExtensions(gl, window, input, fontConfig); + _api = window.API; var style = ImGui.GetStyle(); var viewport = ImGui.GetMainViewport(); var titleBarHeight = ImGui.GetFontSize() + style.FramePadding.Y * 2; - _outlinerSize = new Vector2(300, 350); + _outlinerSize = new Vector2(400, 250); _outlinerPosition = new Vector2(viewport.WorkSize.X - _outlinerSize.X, titleBarHeight); _propertiesSize = _outlinerSize with { Y = viewport.WorkSize.Y - _outlinerSize.Y - titleBarHeight }; _propertiesPosition = new Vector2(viewport.WorkSize.X - _propertiesSize.X, _outlinerPosition.Y + _outlinerSize.Y); - _viewportSize = _outlinerPosition with { Y = viewport.WorkSize.Y - titleBarHeight }; + _viewportSize = _outlinerPosition with { Y = viewport.WorkSize.Y - titleBarHeight - 150 }; _viewportPosition = new Vector2(0, titleBarHeight); + _textureSize = _viewportSize with { Y = viewport.WorkSize.Y - _viewportSize.Y - titleBarHeight }; + _texturePosition = new Vector2(0, _viewportPosition.Y + _viewportSize.Y); _selectedModel = 0; + _selectedSection = 0; Theme(style); } @@ -54,6 +63,7 @@ public class SnimGui : IDisposable DrawOuliner(camera, models); DrawProperties(camera, models); + DrawTextures(models); Draw3DViewport(framebuffer, camera, mouse); } @@ -108,12 +118,19 @@ public class SnimGui : IDisposable ImGui.SetNextWindowPos(_outlinerPosition, _firstUse); ImGui.Begin("Scene", _noResize | ImGuiWindowFlags.NoCollapse); + ImGui.Text($"{_api.API} {_api.Profile} {_api.Version.MajorVersion}.{_api.Version.MinorVersion}"); + + var vertices = 0; + var indices = 0; + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); if (ImGui.TreeNode("Collection")) { for (var i = 0; i < models.Count; i++) { var model = models[i]; + vertices += model.Vertices.Length; + indices += model.Indices.Length; if (ImGui.Selectable(model.Name, _selectedModel == i)) _selectedModel = i; } @@ -131,8 +148,8 @@ public class SnimGui : IDisposable ImGui.TreePop(); } - ImGui.Text($"Position: {_viewportPosition}"); - ImGui.Text($"Size: {_viewportSize}"); + ImGui.Text($"Vertices: {vertices}"); + ImGui.Text($"Indices: {indices}"); ImGui.End(); } @@ -149,7 +166,6 @@ public class SnimGui : IDisposable ImGui.Checkbox("Vertex Colors", ref model.DisplayVertexColors); ImGui.EndDisabled(); - ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); if (ImGui.TreeNode("Transform")) { const int width = 100; @@ -199,6 +215,90 @@ public class SnimGui : IDisposable ImGui.TreePop(); } + ImGui.SetNextItemOpen(true, ImGuiCond.Appearing); + if (ImGui.TreeNode("Materials")) + { + ImGui.BeginTable("Sections", 2, ImGuiTableFlags.Borders | ImGuiTableFlags.Resizable); + ImGui.TableSetupColumn("Index", ImGuiTableColumnFlags.NoHeaderWidth | ImGuiTableColumnFlags.WidthFixed); + ImGui.TableSetupColumn("Name"); + ImGui.TableHeadersRow(); + for (var i = 0; i < model.Sections.Length; i++) + { + var section = model.Sections[i]; + + ImGui.PushID(i); + ImGui.TableNextRow(); + if (!section.Show) + ImGui.TableSetBgColor(ImGuiTableBgTarget.RowBg0, ImGui.GetColorU32(new Vector4(1, 0, 0, .5f))); + ImGui.TableSetColumnIndex(0); + ImGui.Text(section.Index.ToString("D")); + ImGui.TableSetColumnIndex(1); + if (ImGui.Selectable(section.Name, _selectedSection == i, ImGuiSelectableFlags.SpanAllColumns)) + _selectedSection = i; + ImGui.PopID(); + } + ImGui.EndTable(); + ImGui.TreePop(); + } + + ImGui.End(); + } + + private void DrawTextures(IList models) + { + ImGui.SetNextWindowSize(_textureSize, _firstUse); + ImGui.SetNextWindowPos(_texturePosition, _firstUse); + ImGui.Begin("Textures", _noResize | ImGuiWindowFlags.NoCollapse); + + var section = models[_selectedModel].Sections[_selectedSection]; + ImGui.BeginGroup(); + ImGui.Checkbox("Show", ref section.Show); + ImGui.Checkbox("Wireframe", ref section.Wireframe); + ImGui.Checkbox("3", ref section.Wireframe); + ImGui.Checkbox("4", ref section.Wireframe); + ImGui.EndGroup(); + ImGui.SameLine(); + ImGui.BeginGroup(); + if (section.HasDiffuseColor) + { + ImGui.ColorEdit4(section.TexturesLabels[0], ref section.DiffuseColor, ImGuiColorEditFlags.AlphaPreview | ImGuiColorEditFlags.AlphaBar); + } + else + { + for (var i = 0; i < section.Textures.Length; i++) + { + if (section.Textures[i] is not {} texture) + continue; + + ImGui.SameLine(); + ImGui.BeginGroup(); + ImGui.Image(texture.GetPointer(), new Vector2(88), Vector2.Zero, Vector2.One, Vector4.One, new Vector4(1, 1, 1, .5f)); + var text = section.TexturesLabels[i]; + var width = ImGui.GetCursorPos().X; + ImGui.SetCursorPosX(width + ImGui.CalcTextSize(text).X * 0.5f); + ImGui.Text(text); + ImGui.EndGroup(); + } + } + ImGui.EndGroup(); + + // ImGui.Text($"Faces: {FacesCount} ({Math.Round(FacesCount / indices * 100f, 2)}%%)"); + // ImGui.Text($"First Face: {FirstFaceIndex}"); + // ImGui.Separator(); + // if (_hasDiffuseColor) + // { + // ImGui.ColorEdit4("Diffuse Color", ref _diffuseColor, ImGuiColorEditFlags.NoInputs); + // } + // else + // { + // ImGui.Text($"Diffuse: ({Parameters.Diffuse?.ExportType}) {Parameters.Diffuse?.Name}"); + // ImGui.Text($"Normal: ({Parameters.Normal?.ExportType}) {Parameters.Normal?.Name}"); + // ImGui.Text($"Specular: ({Parameters.Specular?.ExportType}) {Parameters.Specular?.Name}"); + // if (Parameters.HasTopEmissiveTexture) + // ImGui.Text($"Emissive: ({Parameters.Emissive?.ExportType}) {Parameters.Emissive?.Name}"); + // ImGui.Separator(); + // } + ImGui.End(); } @@ -278,7 +378,7 @@ public class SnimGui : IDisposable io.ConfigFlags |= ImGuiConfigFlags.DockingEnable; io.ConfigFlags |= ImGuiConfigFlags.ViewportsEnable; io.ConfigWindowsMoveFromTitleBarOnly = true; - io.ConfigDockingWithShift = true; + // io.ConfigDockingWithShift = true; // style.WindowPadding = Vector2.Zero; // style.Colors[(int) ImGuiCol.Text] = new Vector4(0.95f, 0.96f, 0.98f, 1.00f); diff --git a/FModel/Views/Snooper/Texture.cs b/FModel/Views/Snooper/Texture.cs index 1e7840c6..f7915557 100644 --- a/FModel/Views/Snooper/Texture.cs +++ b/FModel/Views/Snooper/Texture.cs @@ -8,22 +8,21 @@ namespace FModel.Views.Snooper; public class Texture : IDisposable { - private uint _handle; - private GL _gl; - - private TextureType _type; + private readonly uint _handle; + private readonly GL _gl; + private readonly TextureType _type; public Texture(GL gl, TextureType type) { _gl = gl; _handle = _gl.GenTexture(); _type = type; - - Bind(TextureUnit.Texture0); } public Texture(GL gl, uint width, uint height) : this(gl, TextureType.MsaaFramebuffer) { + Bind(TextureUnit.Texture0); + _gl.TexImage2DMultisample(TextureTarget.Texture2DMultisample, Constants.SAMPLES_COUNT, InternalFormat.Rgb, width, height, Silk.NET.OpenGL.Boolean.True); _gl.TexParameter(TextureTarget.Texture2DMultisample, TextureParameterName.TextureMinFilter, (int) GLEnum.Nearest); @@ -36,6 +35,8 @@ public class Texture : IDisposable public unsafe Texture(GL gl, int width, int height) : this(gl, TextureType.Framebuffer) { + Bind(TextureUnit.Texture0); + _gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgb, (uint) width, (uint) height, 0, PixelFormat.Rgb, PixelType.UnsignedByte, null); _gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) GLEnum.Linear); @@ -48,6 +49,8 @@ public class Texture : IDisposable public unsafe Texture(GL gl, byte[] data, uint width, uint height) : this(gl, TextureType.Normal) { + Bind(TextureUnit.Texture0); + fixed (void* d = &data[0]) { _gl.TexImage2D(TextureTarget.Texture2D, 0, (int) InternalFormat.Rgba, width, height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, d); @@ -63,6 +66,8 @@ public class Texture : IDisposable public unsafe Texture(GL gl, string[] textures) : this(gl, TextureType.Cubemap) { + Bind(TextureUnit.Texture0); + for (int t = 0; t < textures.Length; t++) { var info = Application.GetResourceStream(new Uri($"/FModel;component/Resources/{textures[t]}.png", UriKind.Relative)); @@ -88,18 +93,45 @@ public class Texture : IDisposable _gl.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapT, (int) GLEnum.ClampToEdge); } + public unsafe Texture(GL gl, uint width, uint height, IntPtr data) : this(gl, TextureType.Normal) + { + Bind(TextureTarget.Texture2D); + + _gl.TexStorage2D(GLEnum.Texture2D, 1, SizedInternalFormat.Rgba8, width, height); + _gl.TexSubImage2D(GLEnum.Texture2D, 0, 0, 0, width, height, PixelFormat.Bgra, PixelType.UnsignedByte, (void*) data); + + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat); + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat); + + _gl.TexParameterI(GLEnum.Texture2D, TextureParameterName.TextureMaxLevel, 1 - 1); + } + public void Bind(TextureUnit textureSlot) { _gl.ActiveTexture(textureSlot); - var target = _type switch + Bind(_type switch { TextureType.Cubemap => TextureTarget.TextureCubeMap, TextureType.MsaaFramebuffer => TextureTarget.Texture2DMultisample, _ => TextureTarget.Texture2D - }; + }); + } + + public void Bind(TextureTarget target) + { _gl.BindTexture(target, _handle); } + 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 IntPtr GetPointer() => (IntPtr) _handle; public void Dispose()