diff --git a/project_name/ImGui/DrawVertDeclaration.cs b/project_name/ImGui/DrawVertDeclaration.cs deleted file mode 100644 index 276e5df..0000000 --- a/project_name/ImGui/DrawVertDeclaration.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Xna.Framework.Graphics; - -namespace ImGuiNET.SampleProgram.XNA -{ - public static class DrawVertDeclaration - { - public static readonly VertexDeclaration Declaration; - public static readonly int Size; - - static DrawVertDeclaration() - { - unsafe { Size = sizeof(ImDrawVert); } - - Declaration = new VertexDeclaration( - Size, - - // Position - new VertexElement(0, VertexElementFormat.Vector2, VertexElementUsage.Position, 0), - - // UV - new VertexElement(8, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0), - - // Color - new VertexElement(16, VertexElementFormat.Color, VertexElementUsage.Color, 0) - ); - } - } -} \ No newline at end of file diff --git a/project_name/ImGui/ImGuiFinalRenderDelegate.cs b/project_name/ImGui/ImGuiFinalRenderDelegate.cs index 349dbf1..4d70719 100644 --- a/project_name/ImGui/ImGuiFinalRenderDelegate.cs +++ b/project_name/ImGui/ImGuiFinalRenderDelegate.cs @@ -1,7 +1,6 @@ using System; using System.Reflection; using ImGuiNET; -using ImGuiNET.SampleProgram.XNA; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -11,68 +10,99 @@ namespace Nez { public Scene scene { get; set; } - ImGuiRenderer _imGuiRenderer; - RenderTarget2D _lastRenderTarget; - IntPtr _renderTargetId; + ImGuiRenderer _imGuiRenderer; + RenderTarget2D _lastRenderTarget; + IntPtr _renderTargetId; - public ImGuiFinalRenderDelegate() - { - _imGuiRenderer = new ImGuiRenderer(Core.instance); - _imGuiRenderer.RebuildFontAtlas(); - ImGui.GetIO().ConfigWindowsMoveFromTitleBarOnly = true; - } - - public void handleFinalRender( Color letterboxColor, RenderTarget2D source, Rectangle finalRenderDestinationRect, SamplerState samplerState ) + public ImGuiFinalRenderDelegate() { - if(_lastRenderTarget != source) - { - // unbind the old texture if we had one - if(_lastRenderTarget != null) - _imGuiRenderer.UnbindTexture(_renderTargetId); - - // bind the new texture - _lastRenderTarget = source; - _renderTargetId = _imGuiRenderer.BindTexture(source); - } - - Core.graphicsDevice.setRenderTarget( null ); - Core.graphicsDevice.Clear( letterboxColor ); - - - _imGuiRenderer.BeforeLayout(Time.time); - layoutGui(); - _imGuiRenderer.AfterLayout(); + _imGuiRenderer = new ImGuiRenderer( Core.instance ); + _imGuiRenderer.rebuildFontAtlas(); } - void layoutGui() - { - ImGui.ShowDemoWindow(); + [Console.Command( "toggle-imgui", "Toggles the Dear ImGui renderer" )] + static void toggleImGui() + { + if( Core.scene.finalRenderDelegate == null ) + Core.scene.finalRenderDelegate = new ImGuiFinalRenderDelegate(); + else + Core.scene.finalRenderDelegate = null; + } - var maxSize = new System.Numerics.Vector2(_lastRenderTarget.Width, _lastRenderTarget.Height); - var minSize = maxSize / 4; - unsafe - { - ImGui.SetNextWindowSizeConstraints(minSize, maxSize, data => - { - var size = (*data).CurrentSize; - var ratio = size.X / _lastRenderTarget.Width; - (*data).DesiredSize.Y = ratio * _lastRenderTarget.Height; - }); - } + void layoutGui() + { + ImGui.ShowDemoWindow(); - ImGui.SetNextWindowPos(new System.Numerics.Vector2(0, 0), ImGuiCond.FirstUseEver); - ImGui.Begin("Game Window"); - ImGui.Image(_renderTargetId, ImGui.GetContentRegionAvail()); - ImGui.End(); - } + var maxSize = new System.Numerics.Vector2( _lastRenderTarget.Width, _lastRenderTarget.Height ); + var minSize = maxSize / 4; + maxSize *= 4; + unsafe + { + ImGui.SetNextWindowSizeConstraints( minSize, maxSize, data => + { + var size = ( *data ).CurrentSize; + var ratio = size.X / _lastRenderTarget.Width; + ( *data ).DesiredSize.Y = ratio * _lastRenderTarget.Height; + } ); + } + + ImGui.SetNextWindowPos( new System.Numerics.Vector2( 0, 0 ), ImGuiCond.FirstUseEver ); + ImGui.PushStyleVar( ImGuiStyleVar.WindowPadding, new System.Numerics.Vector2( 0, 0 ) ); + ImGui.Begin( "Game Window" ); + + Nugget.InputDisplay.cursorScreenPos = new Vector2( ImGui.GetCursorScreenPos().X, ImGui.GetCursorScreenPos().Y ); + Nugget.InputDisplay.scaleX = ImGui.GetContentRegionAvail().X / _lastRenderTarget.Width; + Nugget.InputDisplay.scaleY = ImGui.GetContentRegionAvail().Y / _lastRenderTarget.Height; + + //Debug.log( $"window pos: {ImGui.GetWindowPos()}" ); + //Debug.log( $"avail size: {ImGui.GetContentRegionAvail()}" ); + //Debug.log( $"rt {_lastRenderTarget.Width} x {_lastRenderTarget.Height}" ); + //Debug.log( $"scaleX: {ImGui.GetContentRegionAvail().X / _lastRenderTarget.Width}" ); + //Debug.log( $"scaleY: {ImGui.GetContentRegionAvail().Y / _lastRenderTarget.Height}" ); + //Debug.log( ImGui.GetWindowSize() - ImGui.GetContentRegionAvail() ); + //Debug.log( $"titleHeight: {titleHeight}" ); + //Debug.log( $"screenPos: {ImGui.GetCursorScreenPos()}" ); + + + ImGui.Image( _renderTargetId, ImGui.GetContentRegionAvail() ); + ImGui.End(); + + ImGui.PopStyleVar(); + } + + #region IFinalRenderDelegate + + public void handleFinalRender( RenderTarget2D finalRenderTarget, Color letterboxColor, RenderTarget2D source, Rectangle finalRenderDestinationRect, SamplerState samplerState ) + { + if( _lastRenderTarget != source ) + { + // unbind the old texture if we had one + if( _lastRenderTarget != null ) + _imGuiRenderer.unbindTexture( _renderTargetId ); + + // bind the new texture + _lastRenderTarget = source; + _renderTargetId = _imGuiRenderer.bindTexture( source ); + } + + Core.graphicsDevice.setRenderTarget( finalRenderTarget ); + Core.graphicsDevice.Clear( letterboxColor ); + + + _imGuiRenderer.beforeLayout( Time.deltaTime ); + layoutGui(); + _imGuiRenderer.afterLayout(); + } public void onAddedToScene() - {} + { } public void onSceneBackBufferSizeChanged( int newWidth, int newHeight ) - {} + { } public void unload() - {} + { } + + #endregion } } diff --git a/project_name/ImGui/ImGuiRenderer.cs b/project_name/ImGui/ImGuiRenderer.cs index 0c6886a..41c7523 100644 --- a/project_name/ImGui/ImGuiRenderer.cs +++ b/project_name/ImGui/ImGuiRenderer.cs @@ -1,387 +1,389 @@ using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; +using Nez; using System; using System.Collections.Generic; using System.Runtime.InteropServices; -namespace ImGuiNET.SampleProgram.XNA +namespace ImGuiNET { - /// - /// ImGui renderer for use with XNA-likes (FNA & MonoGame) - /// - public class ImGuiRenderer - { - private Game _game; - - // Graphics - private GraphicsDevice _graphicsDevice; - - private BasicEffect _effect; - private RasterizerState _rasterizerState; - - private byte[] _vertexData; - private VertexBuffer _vertexBuffer; - private int _vertexBufferSize; - - private byte[] _indexData; - private IndexBuffer _indexBuffer; - private int _indexBufferSize; - - // Textures - private Dictionary _loadedTextures; - - private int _textureId; - private IntPtr? _fontTextureId; - - // Input - private int _scrollWheelValue; - - private List _keys = new List(); - - public ImGuiRenderer(Game game) - { - var context = ImGui.CreateContext(); - ImGui.SetCurrentContext(context); - - _game = game ?? throw new ArgumentNullException(nameof(game)); - _graphicsDevice = game.GraphicsDevice; - - _loadedTextures = new Dictionary(); - - _rasterizerState = new RasterizerState() - { - CullMode = CullMode.None, - DepthBias = 0, - FillMode = FillMode.Solid, - MultiSampleAntiAlias = false, - ScissorTestEnable = true, - SlopeScaleDepthBias = 0 - }; - - SetupInput(); - } - - #region ImGuiRenderer - - /// - /// Creates a texture and loads the font data from ImGui. Should be called when the is initialized but before any rendering is done - /// - public virtual unsafe void RebuildFontAtlas() - { - // Get font texture from ImGui - var io = ImGui.GetIO(); - io.Fonts.GetTexDataAsRGBA32(out byte* pixelData, out int width, out int height, out int bytesPerPixel); - - // Copy the data to a managed array - var pixels = new byte[width * height * bytesPerPixel]; - unsafe { Marshal.Copy(new IntPtr(pixelData), pixels, 0, pixels.Length); } - - // Create and register the texture as an XNA texture - var tex2d = new Texture2D(_graphicsDevice, width, height, false, SurfaceFormat.Color); - tex2d.SetData(pixels); - - // Should a texture already have been build previously, unbind it first so it can be deallocated - if (_fontTextureId.HasValue) UnbindTexture(_fontTextureId.Value); - - // Bind the new texture to an ImGui-friendly id - _fontTextureId = BindTexture(tex2d); - - // Let ImGui know where to find the texture - io.Fonts.SetTexID(_fontTextureId.Value); - io.Fonts.ClearTexData(); // Clears CPU side texture data - } - - /// - /// Creates a pointer to a texture, which can be passed through ImGui calls such as . That pointer is then used by ImGui to let us know what texture to draw - /// - public virtual IntPtr BindTexture(Texture2D texture) - { - var id = new IntPtr(_textureId++); - - _loadedTextures.Add(id, texture); - - return id; - } - - /// - /// Removes a previously created texture pointer, releasing its reference and allowing it to be deallocated - /// - public virtual void UnbindTexture(IntPtr textureId) - { - _loadedTextures.Remove(textureId); - } - - /// - /// Sets up ImGui for a new frame, should be called at frame start - /// - public virtual void BeforeLayout(float deltaTime) - { - ImGui.GetIO().DeltaTime = deltaTime; - UpdateInput(); - ImGui.NewFrame(); - } - - /// - /// Asks ImGui for the generated geometry data and sends it to the graphics pipeline, should be called after the UI is drawn using ImGui.** calls - /// - public virtual void AfterLayout() - { - ImGui.Render(); - - unsafe { RenderDrawData(ImGui.GetDrawData()); } - } - - #endregion ImGuiRenderer - - #region Setup & Update - - /// - /// Maps ImGui keys to XNA keys. We use this later on to tell ImGui what keys were pressed - /// - protected virtual void SetupInput() - { - var io = ImGui.GetIO(); - - _keys.Add(io.KeyMap[(int)ImGuiKey.Tab] = (int)Keys.Tab); - _keys.Add(io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Keys.Left); - _keys.Add(io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Keys.Right); - _keys.Add(io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Keys.Up); - _keys.Add(io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Keys.Down); - _keys.Add(io.KeyMap[(int)ImGuiKey.PageUp] = (int)Keys.PageUp); - _keys.Add(io.KeyMap[(int)ImGuiKey.PageDown] = (int)Keys.PageDown); - _keys.Add(io.KeyMap[(int)ImGuiKey.Home] = (int)Keys.Home); - _keys.Add(io.KeyMap[(int)ImGuiKey.End] = (int)Keys.End); - _keys.Add(io.KeyMap[(int)ImGuiKey.Delete] = (int)Keys.Delete); - _keys.Add(io.KeyMap[(int)ImGuiKey.Backspace] = (int)Keys.Back); - _keys.Add(io.KeyMap[(int)ImGuiKey.Enter] = (int)Keys.Enter); - _keys.Add(io.KeyMap[(int)ImGuiKey.Escape] = (int)Keys.Escape); - _keys.Add(io.KeyMap[(int)ImGuiKey.A] = (int)Keys.A); - _keys.Add(io.KeyMap[(int)ImGuiKey.C] = (int)Keys.C); - _keys.Add(io.KeyMap[(int)ImGuiKey.V] = (int)Keys.V); - _keys.Add(io.KeyMap[(int)ImGuiKey.X] = (int)Keys.X); - _keys.Add(io.KeyMap[(int)ImGuiKey.Y] = (int)Keys.Y); - _keys.Add(io.KeyMap[(int)ImGuiKey.Z] = (int)Keys.Z); - - - // MonoGame-specific ////////////////////// - // _game.Window.TextInput += (s, a) => - // { - // if (a.Character == '\t') return; - - // io.AddInputCharacter(a.Character); - // }; - /////////////////////////////////////////// - - // FNA-specific /////////////////////////// - TextInputEXT.TextInput += c => - { - if (c == '\t') return; - - ImGui.GetIO().AddInputCharacter(c); - }; - /////////////////////////////////////////// - - ImGui.GetIO().Fonts.AddFontDefault(); - } - - /// - /// Updates the to the current matrices and texture - /// - protected virtual Effect UpdateEffect(Texture2D texture) - { - _effect = _effect ?? new BasicEffect(_graphicsDevice); - - var io = ImGui.GetIO(); - - // MonoGame-specific ////////////////////// - //var offset = .5f; - /////////////////////////////////////////// - - // FNA-specific /////////////////////////// - var offset = 0f; - /////////////////////////////////////////// - - _effect.World = Matrix.Identity; - _effect.View = Matrix.Identity; - _effect.Projection = Matrix.CreateOrthographicOffCenter(offset, io.DisplaySize.X + offset, io.DisplaySize.Y + offset, offset, -1f, 1f); - _effect.TextureEnabled = true; - _effect.Texture = texture; - _effect.VertexColorEnabled = true; - - return _effect; - } - - /// - /// Sends XNA input state to ImGui - /// - protected virtual void UpdateInput() - { - var io = ImGui.GetIO(); - - var mouse = Mouse.GetState(); - var keyboard = Keyboard.GetState(); - - for (int i = 0; i < _keys.Count; i++) - { - io.KeysDown[_keys[i]] = keyboard.IsKeyDown((Keys)_keys[i]); - } - - io.KeyShift = keyboard.IsKeyDown(Keys.LeftShift) || keyboard.IsKeyDown(Keys.RightShift); - io.KeyCtrl = keyboard.IsKeyDown(Keys.LeftControl) || keyboard.IsKeyDown(Keys.RightControl); - io.KeyAlt = keyboard.IsKeyDown(Keys.LeftAlt) || keyboard.IsKeyDown(Keys.RightAlt); - io.KeySuper = keyboard.IsKeyDown(Keys.LeftWindows) || keyboard.IsKeyDown(Keys.RightWindows); - - io.DisplaySize = new System.Numerics.Vector2(_graphicsDevice.PresentationParameters.BackBufferWidth, _graphicsDevice.PresentationParameters.BackBufferHeight); - io.DisplayFramebufferScale = new System.Numerics.Vector2(1f, 1f); - - io.MousePos = new System.Numerics.Vector2(mouse.X, mouse.Y); - - io.MouseDown[0] = mouse.LeftButton == ButtonState.Pressed; - io.MouseDown[1] = mouse.RightButton == ButtonState.Pressed; - io.MouseDown[2] = mouse.MiddleButton == ButtonState.Pressed; - - var scrollDelta = mouse.ScrollWheelValue - _scrollWheelValue; - io.MouseWheel = scrollDelta > 0 ? 1 : scrollDelta < 0 ? -1 : 0; - _scrollWheelValue = mouse.ScrollWheelValue; - } - - #endregion Setup & Update - - #region Internals - - /// - /// Gets the geometry as set up by ImGui and sends it to the graphics device - /// - private void RenderDrawData(ImDrawDataPtr drawData) - { - // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers - var lastViewport = _graphicsDevice.Viewport; - var lastScissorBox = _graphicsDevice.ScissorRectangle; - - _graphicsDevice.BlendFactor = Color.White; - _graphicsDevice.BlendState = BlendState.NonPremultiplied; - _graphicsDevice.RasterizerState = _rasterizerState; - _graphicsDevice.DepthStencilState = DepthStencilState.DepthRead; - - // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) - drawData.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale); - - // Setup projection - _graphicsDevice.Viewport = new Viewport(0, 0, _graphicsDevice.PresentationParameters.BackBufferWidth, _graphicsDevice.PresentationParameters.BackBufferHeight); - - UpdateBuffers(drawData); - - RenderCommandLists(drawData); - - // Restore modified state - _graphicsDevice.Viewport = lastViewport; - _graphicsDevice.ScissorRectangle = lastScissorBox; - } - - private unsafe void UpdateBuffers(ImDrawDataPtr drawData) - { - if (drawData.TotalVtxCount == 0) - { - return; - } - - // Expand buffers if we need more room - if (drawData.TotalVtxCount > _vertexBufferSize) - { - _vertexBuffer?.Dispose(); - - _vertexBufferSize = (int)(drawData.TotalVtxCount * 1.5f); - _vertexBuffer = new VertexBuffer(_graphicsDevice, DrawVertDeclaration.Declaration, _vertexBufferSize, BufferUsage.None); - _vertexData = new byte[_vertexBufferSize * DrawVertDeclaration.Size]; - } - - if (drawData.TotalIdxCount > _indexBufferSize) - { - _indexBuffer?.Dispose(); - - _indexBufferSize = (int)(drawData.TotalIdxCount * 1.5f); - _indexBuffer = new IndexBuffer(_graphicsDevice, IndexElementSize.SixteenBits, _indexBufferSize, BufferUsage.None); - _indexData = new byte[_indexBufferSize * sizeof(ushort)]; - } - - // Copy ImGui's vertices and indices to a set of managed byte arrays - int vtxOffset = 0; - int idxOffset = 0; - - for (int n = 0; n < drawData.CmdListsCount; n++) - { - ImDrawListPtr cmdList = drawData.CmdListsRange[n]; - - fixed (void* vtxDstPtr = &_vertexData[vtxOffset * DrawVertDeclaration.Size]) - fixed (void* idxDstPtr = &_indexData[idxOffset * sizeof(ushort)]) - { - Buffer.MemoryCopy((void*)cmdList.VtxBuffer.Data, vtxDstPtr, _vertexData.Length, cmdList.VtxBuffer.Size * DrawVertDeclaration.Size); - Buffer.MemoryCopy((void*)cmdList.IdxBuffer.Data, idxDstPtr, _indexData.Length, cmdList.IdxBuffer.Size * sizeof(ushort)); - } - - vtxOffset += cmdList.VtxBuffer.Size; - idxOffset += cmdList.IdxBuffer.Size; - } - - // Copy the managed byte arrays to the gpu vertex- and index buffers - _vertexBuffer.SetData(_vertexData, 0, drawData.TotalVtxCount * DrawVertDeclaration.Size); - _indexBuffer.SetData(_indexData, 0, drawData.TotalIdxCount * sizeof(ushort)); - } - - private unsafe void RenderCommandLists(ImDrawDataPtr drawData) - { - _graphicsDevice.SetVertexBuffer(_vertexBuffer); - _graphicsDevice.Indices = _indexBuffer; - - int vtxOffset = 0; - int idxOffset = 0; - - for (int n = 0; n < drawData.CmdListsCount; n++) - { - ImDrawListPtr cmdList = drawData.CmdListsRange[n]; - - for (int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++) - { - ImDrawCmdPtr drawCmd = cmdList.CmdBuffer[cmdi]; - - if (!_loadedTextures.ContainsKey(drawCmd.TextureId)) - { - throw new InvalidOperationException($"Could not find a texture with id '{drawCmd.TextureId}', please check your bindings"); - } - - _graphicsDevice.ScissorRectangle = new Rectangle( - (int)drawCmd.ClipRect.X, - (int)drawCmd.ClipRect.Y, - (int)(drawCmd.ClipRect.Z - drawCmd.ClipRect.X), - (int)(drawCmd.ClipRect.W - drawCmd.ClipRect.Y) - ); - - var effect = UpdateEffect(_loadedTextures[drawCmd.TextureId]); - - foreach (var pass in effect.CurrentTechnique.Passes) - { - pass.Apply(); + /// + /// ImGui renderer for use with XNA-likes (FNA and MonoGame) + /// + public class ImGuiRenderer + { + // Graphics + BasicEffect _effect; + RasterizerState _rasterizerState; + + readonly VertexDeclaration _vertexDeclaration; + readonly int _vertexDeclarationSize; + + byte[] _vertexData; + VertexBuffer _vertexBuffer; + int _vertexBufferSize; + + byte[] _indexData; + IndexBuffer _indexBuffer; + int _indexBufferSize; + + // Textures + Dictionary _loadedTextures; + + int _textureId; + IntPtr? _fontTextureId; + + // Input + int _scrollWheelValue; + + List _keys = new List(); + + public ImGuiRenderer( Game game ) + { + unsafe { _vertexDeclarationSize = sizeof( ImDrawVert ); } + _vertexDeclaration = new VertexDeclaration( + _vertexDeclarationSize, + // Position + new VertexElement( 0, VertexElementFormat.Vector2, VertexElementUsage.Position, 0 ), + // UV + new VertexElement( 8, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0 ), + // Color + new VertexElement( 16, VertexElementFormat.Color, VertexElementUsage.Color, 0 ) + ); + + var context = ImGui.CreateContext(); + ImGui.SetCurrentContext( context ); + + _loadedTextures = new Dictionary(); + + _rasterizerState = new RasterizerState() + { + CullMode = CullMode.None, + DepthBias = 0, + FillMode = FillMode.Solid, + MultiSampleAntiAlias = false, + ScissorTestEnable = true, + SlopeScaleDepthBias = 0 + }; + + setupInput(); + } + + #region ImGuiRenderer + + /// + /// Creates a texture and loads the font data from ImGui. Should be called when the is initialized but before any rendering is done + /// + public unsafe void rebuildFontAtlas() + { + // Get font texture from ImGui + var io = ImGui.GetIO(); + io.Fonts.GetTexDataAsRGBA32( out byte* pixelData, out int width, out int height, out int bytesPerPixel ); + + // Copy the data to a managed array + var pixels = new byte[width * height * bytesPerPixel]; + Marshal.Copy( new IntPtr( pixelData ), pixels, 0, pixels.Length ); + + // Create and register the texture as an XNA texture + var tex2d = new Texture2D( Core.graphicsDevice, width, height, false, SurfaceFormat.Color ); + tex2d.SetData( pixels ); + + // Should a texture already have been built previously, unbind it first so it can be deallocated + if( _fontTextureId.HasValue ) + unbindTexture( _fontTextureId.Value ); + + // Bind the new texture to an ImGui-friendly id + _fontTextureId = bindTexture( tex2d ); + + // Let ImGui know where to find the texture + io.Fonts.SetTexID( _fontTextureId.Value ); + io.Fonts.ClearTexData(); // Clears CPU side texture data + } + + /// + /// Creates a pointer to a texture, which can be passed through ImGui calls such as . That pointer is then used by ImGui to let us know what texture to draw + /// + public IntPtr bindTexture( Texture2D texture ) + { + var id = new IntPtr( _textureId++ ); + _loadedTextures.Add( id, texture ); + return id; + } + + /// + /// Removes a previously created texture pointer, releasing its reference and allowing it to be deallocated + /// + public void unbindTexture( IntPtr textureId ) + { + _loadedTextures.Remove( textureId ); + } + + /// + /// Sets up ImGui for a new frame, should be called at frame start + /// + public void beforeLayout( float deltaTime ) + { + ImGui.GetIO().DeltaTime = deltaTime; + updateInput(); + ImGui.NewFrame(); + } + + /// + /// Asks ImGui for the generated geometry data and sends it to the graphics pipeline, should be called after the UI is drawn using ImGui.** calls + /// + public void afterLayout() + { + ImGui.Render(); + unsafe { renderDrawData( ImGui.GetDrawData() ); } + } + + #endregion ImGuiRenderer + + #region Setup & Update + + /// + /// Maps ImGui keys to XNA keys. We use this later on to tell ImGui what keys were pressed + /// + void setupInput() + { + var io = ImGui.GetIO(); + + _keys.Add( io.KeyMap[(int)ImGuiKey.Tab] = (int)Keys.Tab ); + _keys.Add( io.KeyMap[(int)ImGuiKey.LeftArrow] = (int)Keys.Left ); + _keys.Add( io.KeyMap[(int)ImGuiKey.RightArrow] = (int)Keys.Right ); + _keys.Add( io.KeyMap[(int)ImGuiKey.UpArrow] = (int)Keys.Up ); + _keys.Add( io.KeyMap[(int)ImGuiKey.DownArrow] = (int)Keys.Down ); + _keys.Add( io.KeyMap[(int)ImGuiKey.PageUp] = (int)Keys.PageUp ); + _keys.Add( io.KeyMap[(int)ImGuiKey.PageDown] = (int)Keys.PageDown ); + _keys.Add( io.KeyMap[(int)ImGuiKey.Home] = (int)Keys.Home ); + _keys.Add( io.KeyMap[(int)ImGuiKey.End] = (int)Keys.End ); + _keys.Add( io.KeyMap[(int)ImGuiKey.Delete] = (int)Keys.Delete ); + _keys.Add( io.KeyMap[(int)ImGuiKey.Backspace] = (int)Keys.Back ); + _keys.Add( io.KeyMap[(int)ImGuiKey.Enter] = (int)Keys.Enter ); + _keys.Add( io.KeyMap[(int)ImGuiKey.Escape] = (int)Keys.Escape ); + _keys.Add( io.KeyMap[(int)ImGuiKey.A] = (int)Keys.A ); + _keys.Add( io.KeyMap[(int)ImGuiKey.C] = (int)Keys.C ); + _keys.Add( io.KeyMap[(int)ImGuiKey.V] = (int)Keys.V ); + _keys.Add( io.KeyMap[(int)ImGuiKey.X] = (int)Keys.X ); + _keys.Add( io.KeyMap[(int)ImGuiKey.Y] = (int)Keys.Y ); + _keys.Add( io.KeyMap[(int)ImGuiKey.Z] = (int)Keys.Z ); + + + // MonoGame-specific ////////////////////// + // _game.Window.TextInput += (s, a) => + // { + // if (a.Character == '\t') return; + + // io.AddInputCharacter(a.Character); + // }; + /////////////////////////////////////////// + + // FNA-specific /////////////////////////// + TextInputEXT.TextInput += c => + { + if( c == '\t' ) return; + + ImGui.GetIO().AddInputCharacter( c ); + }; + /////////////////////////////////////////// + + ImGui.GetIO().Fonts.AddFontDefault(); + } + + /// + /// Updates the to the current matrices and texture + /// + Effect updateEffect( Texture2D texture ) + { + _effect = _effect ?? new BasicEffect( Core.graphicsDevice ); + + var io = ImGui.GetIO(); + + // MonoGame-specific ////////////////////// + //var offset = .5f; + /////////////////////////////////////////// + + // FNA-specific /////////////////////////// + var offset = 0f; + /////////////////////////////////////////// + + _effect.World = Matrix.Identity; + _effect.View = Matrix.Identity; + _effect.Projection = Matrix.CreateOrthographicOffCenter( offset, io.DisplaySize.X + offset, io.DisplaySize.Y + offset, offset, -1f, 1f ); + _effect.TextureEnabled = true; + _effect.Texture = texture; + _effect.VertexColorEnabled = true; + + return _effect; + } + + /// + /// Sends XNA input state to ImGui + /// + void updateInput() + { + var io = ImGui.GetIO(); + + var mouse = Mouse.GetState(); + var keyboard = Keyboard.GetState(); + + for( int i = 0; i < _keys.Count; i++ ) + { + io.KeysDown[_keys[i]] = keyboard.IsKeyDown( (Keys)_keys[i] ); + } + + io.KeyShift = keyboard.IsKeyDown( Keys.LeftShift ) || keyboard.IsKeyDown( Keys.RightShift ); + io.KeyCtrl = keyboard.IsKeyDown( Keys.LeftControl ) || keyboard.IsKeyDown( Keys.RightControl ); + io.KeyAlt = keyboard.IsKeyDown( Keys.LeftAlt ) || keyboard.IsKeyDown( Keys.RightAlt ); + io.KeySuper = keyboard.IsKeyDown( Keys.LeftWindows ) || keyboard.IsKeyDown( Keys.RightWindows ); + + io.DisplaySize = new System.Numerics.Vector2( Core.graphicsDevice.PresentationParameters.BackBufferWidth, Core.graphicsDevice.PresentationParameters.BackBufferHeight ); + io.DisplayFramebufferScale = new System.Numerics.Vector2( 1f, 1f ); + + io.MousePos = new System.Numerics.Vector2( mouse.X, mouse.Y ); + + io.MouseDown[0] = mouse.LeftButton == ButtonState.Pressed; + io.MouseDown[1] = mouse.RightButton == ButtonState.Pressed; + io.MouseDown[2] = mouse.MiddleButton == ButtonState.Pressed; + + var scrollDelta = mouse.ScrollWheelValue - _scrollWheelValue; + io.MouseWheel = scrollDelta > 0 ? 1 : scrollDelta < 0 ? -1 : 0; + _scrollWheelValue = mouse.ScrollWheelValue; + } + + #endregion Setup & Update + + #region Internals + + /// + /// Gets the geometry as set up by ImGui and sends it to the graphics device + /// + void renderDrawData( ImDrawDataPtr drawData ) + { + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers + var lastViewport = Core.graphicsDevice.Viewport; + var lastScissorBox = Core.graphicsDevice.ScissorRectangle; + + Core.graphicsDevice.BlendFactor = Color.White; + Core.graphicsDevice.BlendState = BlendState.NonPremultiplied; + Core.graphicsDevice.RasterizerState = _rasterizerState; + Core.graphicsDevice.DepthStencilState = DepthStencilState.DepthRead; + + // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) + drawData.ScaleClipRects( ImGui.GetIO().DisplayFramebufferScale ); + + // Setup projection + Core.graphicsDevice.Viewport = new Viewport( 0, 0, Core.graphicsDevice.PresentationParameters.BackBufferWidth, Core.graphicsDevice.PresentationParameters.BackBufferHeight ); + + updateBuffers( drawData ); + renderCommandLists( drawData ); + + // Restore modified state + Core.graphicsDevice.Viewport = lastViewport; + Core.graphicsDevice.ScissorRectangle = lastScissorBox; + } + + unsafe void updateBuffers( ImDrawDataPtr drawData ) + { + if( drawData.TotalVtxCount == 0 ) + { + return; + } + + // Expand buffers if we need more room + if( drawData.TotalVtxCount > _vertexBufferSize ) + { + _vertexBuffer?.Dispose(); + + _vertexBufferSize = (int)( drawData.TotalVtxCount * 1.5f ); + _vertexBuffer = new VertexBuffer( Core.graphicsDevice, _vertexDeclaration, _vertexBufferSize, BufferUsage.None ); + _vertexData = new byte[_vertexBufferSize * _vertexDeclarationSize]; + } + + if( drawData.TotalIdxCount > _indexBufferSize ) + { + _indexBuffer?.Dispose(); + + _indexBufferSize = (int)( drawData.TotalIdxCount * 1.5f ); + _indexBuffer = new IndexBuffer( Core.graphicsDevice, IndexElementSize.SixteenBits, _indexBufferSize, BufferUsage.None ); + _indexData = new byte[_indexBufferSize * sizeof( ushort )]; + } + + // Copy ImGui's vertices and indices to a set of managed byte arrays + int vtxOffset = 0; + int idxOffset = 0; + + for( var n = 0; n < drawData.CmdListsCount; n++ ) + { + var cmdList = drawData.CmdListsRange[n]; + + fixed ( void* vtxDstPtr = &_vertexData[vtxOffset * _vertexDeclarationSize] ) + fixed ( void* idxDstPtr = &_indexData[idxOffset * sizeof( ushort )] ) + { + Buffer.MemoryCopy( (void*)cmdList.VtxBuffer.Data, vtxDstPtr, _vertexData.Length, cmdList.VtxBuffer.Size * _vertexDeclarationSize ); + Buffer.MemoryCopy( (void*)cmdList.IdxBuffer.Data, idxDstPtr, _indexData.Length, cmdList.IdxBuffer.Size * sizeof( ushort ) ); + } + + vtxOffset += cmdList.VtxBuffer.Size; + idxOffset += cmdList.IdxBuffer.Size; + } + + // Copy the managed byte arrays to the gpu vertex- and index buffers + _vertexBuffer.SetData( _vertexData, 0, drawData.TotalVtxCount * _vertexDeclarationSize ); + _indexBuffer.SetData( _indexData, 0, drawData.TotalIdxCount * sizeof( ushort ) ); + } + + unsafe void renderCommandLists( ImDrawDataPtr drawData ) + { + Core.graphicsDevice.SetVertexBuffer( _vertexBuffer ); + Core.graphicsDevice.Indices = _indexBuffer; + + int vtxOffset = 0; + int idxOffset = 0; + + for( int n = 0; n < drawData.CmdListsCount; n++ ) + { + var cmdList = drawData.CmdListsRange[n]; + for( int cmdi = 0; cmdi < cmdList.CmdBuffer.Size; cmdi++ ) + { + var drawCmd = cmdList.CmdBuffer[cmdi]; + if( !_loadedTextures.ContainsKey( drawCmd.TextureId ) ) + { + throw new InvalidOperationException( $"Could not find a texture with id '{drawCmd.TextureId}', please check your bindings" ); + } + + Core.graphicsDevice.ScissorRectangle = new Rectangle( + (int)drawCmd.ClipRect.X, + (int)drawCmd.ClipRect.Y, + (int)( drawCmd.ClipRect.Z - drawCmd.ClipRect.X ), + (int)( drawCmd.ClipRect.W - drawCmd.ClipRect.Y ) + ); + + var effect = updateEffect( _loadedTextures[drawCmd.TextureId] ); + foreach( var pass in effect.CurrentTechnique.Passes ) + { + pass.Apply(); #pragma warning disable CS0618 // // FNA does not expose an alternative method. - _graphicsDevice.DrawIndexedPrimitives( - primitiveType: PrimitiveType.TriangleList, - baseVertex: vtxOffset, - minVertexIndex: 0, - numVertices: cmdList.VtxBuffer.Size, - startIndex: idxOffset, - primitiveCount: (int)drawCmd.ElemCount / 3 - ); + Core.graphicsDevice.DrawIndexedPrimitives( + primitiveType: PrimitiveType.TriangleList, + baseVertex: vtxOffset, + minVertexIndex: 0, + numVertices: cmdList.VtxBuffer.Size, + startIndex: idxOffset, + primitiveCount: (int)drawCmd.ElemCount / 3 + ); #pragma warning restore CS0618 - } + } - idxOffset += (int)drawCmd.ElemCount; - } + idxOffset += (int)drawCmd.ElemCount; + } - vtxOffset += cmdList.VtxBuffer.Size; - } - } + vtxOffset += cmdList.VtxBuffer.Size; + } + } - #endregion Internals - } + #endregion Internals + } } \ No newline at end of file