switch to ImTool

This commit is contained in:
4sval 2022-09-03 13:03:21 +02:00
parent ac124e8f08
commit eabf6d9bcf
11 changed files with 774 additions and 105 deletions

View File

@ -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;
/// <summary>
/// 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
/// </summary>
public class ImGuiControllerExtensions : IDisposable
{
private GL _gl;
private IView _view;
private IInputContext _input;
private bool _frameBegun;
private readonly List<char> _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<int> 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<float> 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<int> lastPolygonMode = stackalloc int[2];
_gl.GetInteger(GLEnum.PolygonMode, lastPolygonMode);
Span<int> 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);
}
/// <summary>
/// Creates the texture used to render text.
/// </summary>
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);
}
}

View File

@ -101,6 +101,8 @@
<None Remove="Resources\skybox.vert" />
<None Remove="Resources\framebuffer.frag" />
<None Remove="Resources\framebuffer.vert" />
<None Remove="Resources\imgui.frag" />
<None Remove="Resources\imgui.vert" />
</ItemGroup>
<ItemGroup>
@ -118,6 +120,8 @@
<EmbeddedResource Include="Resources\skybox.vert" />
<EmbeddedResource Include="Resources\framebuffer.frag" />
<EmbeddedResource Include="Resources\framebuffer.vert" />
<EmbeddedResource Include="Resources\imgui.frag" />
<EmbeddedResource Include="Resources\imgui.vert" />
</ItemGroup>
<ItemGroup>
@ -129,6 +133,7 @@
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="EpicManifestParser" Version="1.2.70-temp" />
<PackageReference Include="HelixToolkit.SharpDX.Core.Wpf" Version="2.21.0" />
<PackageReference Include="ImTool" Version="1.3.6" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.16" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="NVorbis" Version="0.10.4" />
@ -138,8 +143,8 @@
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Silk.NET.Input" Version="2.16.0" />
<PackageReference Include="Silk.NET.Input.Extensions" Version="2.16.0" />
<PackageReference Include="Silk.NET.OpenGL" Version="2.16.0" />
<PackageReference Include="Silk.NET.OpenGL.Extensions.ImGui" Version="2.16.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
<PackageReference Include="SkiaSharp.HarfBuzz" Version="2.88.0" />
<PackageReference Include="SkiaSharp.Svg" Version="1.60.0" />

View File

@ -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; }
}

View File

@ -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
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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;
}
/// <summary>
@ -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();
}
}

View File

@ -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<string, int> _uniformToLocation = new ();
private readonly Dictionary<string, int> _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);

View File

@ -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<Model> 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);

View File

@ -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()