| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- using Microsoft.Xna.Framework;
- using Microsoft.Xna.Framework.Graphics;
- using Microsoft.Xna.Framework.Input;
- using System;
- using System.Collections.Generic;
- using System.Runtime.InteropServices;
- using ImGuiNET;
- namespace OpenVIII.Core
- {
- /// <summary>
- /// ImGui renderer for use with XNA-likes (FNA & MonoGame)
- /// </summary>
- 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<IntPtr, Texture2D> _loadedTextures;
- private int _textureId;
- private IntPtr? _fontTextureId;
- // Input
- private int _scrollWheelValue;
- private List<int> _keys = new List<int>();
- public ImGuiRenderer(Game game)
- {
- var context = ImGui.CreateContext();
- ImGui.SetCurrentContext(context);
- _game = game ?? throw new ArgumentNullException(nameof(game));
- _graphicsDevice = game.GraphicsDevice;
- _loadedTextures = new Dictionary<IntPtr, Texture2D>();
- _rasterizerState = new RasterizerState()
- {
- CullMode = CullMode.None,
- DepthBias = 0,
- FillMode = FillMode.Solid,
- MultiSampleAntiAlias = false,
- ScissorTestEnable = true,
- SlopeScaleDepthBias = 0
- };
- SetupInput();
- }
- #region ImGuiRenderer
- /// <summary>
- /// Creates a texture and loads the font data from ImGui. Should be called when the <see cref="GraphicsDevice" /> is initialized but before any rendering is done
- /// </summary>
- public virtual unsafe void RebuildFontAtlas()
- {
- // Get font texture from ImGui
- var io = ImGui.GetIO();
- io.Fonts.GetTexDataAsRGBA32(out byte* pixelData, out var width, out var height, out var 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
- }
- /// <summary>
- /// Creates a pointer to a texture, which can be passed through ImGui calls such as <see cref="ImGui.Image" />. That pointer is then used by ImGui to let us know what texture to draw
- /// </summary>
- public virtual IntPtr BindTexture(Texture2D texture)
- {
- var id = new IntPtr(_textureId++);
- _loadedTextures.Add(id, texture);
- return id;
- }
- /// <summary>
- /// Removes a previously created texture pointer, releasing its reference and allowing it to be deallocated
- /// </summary>
- public virtual void UnbindTexture(IntPtr textureId)
- {
- _loadedTextures.Remove(textureId);
- }
- /// <summary>
- /// Sets up ImGui for a new frame, should be called at frame start
- /// </summary>
- public virtual void BeforeLayout(GameTime gameTime)
- {
- ImGui.GetIO().DeltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
- UpdateInput();
- ImGui.NewFrame();
- }
- /// <summary>
- /// 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
- /// </summary>
- public virtual void AfterLayout()
- {
- ImGui.Render();
- unsafe { RenderDrawData(ImGui.GetDrawData()); }
- }
- #endregion ImGuiRenderer
- #region Setup & Update
- /// <summary>
- /// Maps ImGui keys to XNA keys. We use this later on to tell ImGui what keys were pressed
- /// </summary>
- 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();
- }
- /// <summary>
- /// Updates the <see cref="Effect" /> to the current matrices and texture
- /// </summary>
- 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;
- }
- /// <summary>
- /// Sends XNA input state to ImGui
- /// </summary>
- protected virtual void UpdateInput()
- {
- var io = ImGui.GetIO();
- var mouse = Mouse.GetState();
- var keyboard = Keyboard.GetState();
- for (var 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
- /// <summary>
- /// Gets the geometry as set up by ImGui and sends it to the graphics device
- /// </summary>
- 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
- var vtxOffset = 0;
- var idxOffset = 0;
- for (var n = 0; n < drawData.CmdListsCount; n++)
- {
- var 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;
- var vtxOffset = 0;
- var idxOffset = 0;
- for (var n = 0; n < drawData.CmdListsCount; n++)
- {
- var cmdList = drawData.CmdListsRange[n];
- for (var 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");
- }
- _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
- );
- #pragma warning restore CS0618
- }
- idxOffset += (int)drawCmd.ElemCount;
- }
- vtxOffset += cmdList.VtxBuffer.Size;
- }
- }
- #endregion Internals
- }
- }
|