302 lines
9.9 KiB
C#
302 lines
9.9 KiB
C#
using System.Numerics;
|
|
using ImGuiNET;
|
|
using SDL3;
|
|
|
|
namespace ResoniteImGuiLib.SDL3;
|
|
|
|
/// <summary>
|
|
/// Implementation of ImGui for SDL3 Renderer backend.
|
|
/// https://github.com/ocornut/imgui/blob/master/backends/imgui_impl_sdlrenderer3.h
|
|
/// </summary>
|
|
public class ImGuiSDL3Renderer : IDisposable
|
|
{
|
|
struct BackupSDLRendererState
|
|
{
|
|
public SDL.Rect Viewport;
|
|
public bool ViewportEnabled;
|
|
public bool ClipEnabled;
|
|
public SDL.Rect ClipRect;
|
|
}
|
|
|
|
public SDL.Rect DefaultClipRect = new SDL.Rect();
|
|
public readonly IntPtr Renderer;
|
|
private IntPtr _fontTexture = IntPtr.Zero;
|
|
|
|
public ImGuiSDL3Renderer(IntPtr renderer)
|
|
{
|
|
Renderer = renderer;
|
|
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
io.BackendFlags |= ImGuiBackendFlags.RendererHasVtxOffset;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
if (_fontTexture != IntPtr.Zero)
|
|
{
|
|
io.Fonts.SetTexID(IntPtr.Zero);
|
|
SDL.DestroyTexture(_fontTexture);
|
|
_fontTexture = IntPtr.Zero;
|
|
}
|
|
}
|
|
|
|
public void NewFrame()
|
|
{
|
|
if (_fontTexture == IntPtr.Zero)
|
|
{
|
|
CreateDeviceObjects();
|
|
}
|
|
}
|
|
|
|
public void RenderDrawData(ImDrawDataPtr drawData)
|
|
{
|
|
// Skip if no data to render
|
|
unsafe
|
|
{
|
|
if (drawData.NativePtr == null || drawData.CmdListsCount == 0)
|
|
return;
|
|
}
|
|
|
|
// Get display size & framebuffer scale
|
|
Vector2 renderScale = new Vector2(drawData.FramebufferScale.X, drawData.FramebufferScale.Y);
|
|
|
|
int fbWidth = (int)(drawData.DisplaySize.X * renderScale.X);
|
|
int fbHeight = (int)(drawData.DisplaySize.Y * renderScale.Y);
|
|
if (fbWidth <= 0 || fbHeight <= 0)
|
|
return;
|
|
|
|
// Backup SDL renderer state
|
|
BackupSDLRendererState oldState = BackupRendererState();
|
|
|
|
// Set up render state
|
|
SetupRenderState();
|
|
|
|
// Set render state in platform IO
|
|
ImGuiPlatformIOPtr platformIo = ImGui.GetPlatformIO();
|
|
platformIo.Renderer_RenderState = Renderer;
|
|
|
|
Vector2 clipOffset = drawData.DisplayPos;
|
|
|
|
// Render command lists
|
|
for (int n = 0; n < drawData.CmdListsCount; n++)
|
|
{
|
|
ImDrawListPtr cmdList = drawData.CmdLists[n];
|
|
|
|
for (int cmdIndex = 0; cmdIndex < cmdList.CmdBuffer.Size; cmdIndex++)
|
|
{
|
|
ImDrawCmdPtr cmd = cmdList.CmdBuffer[cmdIndex];
|
|
|
|
if (cmd.UserCallback != IntPtr.Zero)
|
|
{
|
|
// User callback not implemented
|
|
continue;
|
|
}
|
|
|
|
// Apply clipping rectangle
|
|
Vector4 clipRect = cmd.ClipRect;
|
|
SDL.Rect r = CalculateClipRect(clipRect, clipOffset, renderScale, fbWidth, fbHeight);
|
|
SDL.SetRenderClipRect(Renderer, r);
|
|
|
|
// Get texture
|
|
IntPtr texId = cmd.GetTexID();
|
|
|
|
// Convert ImGui vertices to SDL vertices
|
|
if (!RenderDrawCommand(cmdList, cmd, texId, renderScale))
|
|
{
|
|
Console.WriteLine($"Failed to render ImGui draw command: {SDL.GetError()}");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reset render state
|
|
platformIo.Renderer_RenderState = IntPtr.Zero;
|
|
|
|
// Restore renderer state
|
|
RestoreRendererState(oldState);
|
|
}
|
|
|
|
private bool RenderDrawCommand(ImDrawListPtr drawList, ImDrawCmdPtr cmd, IntPtr texId, Vector2 scale)
|
|
{
|
|
// Get indices and vertices for this command
|
|
int indexOffset = (int)cmd.IdxOffset;
|
|
int vertexOffset = (int)cmd.VtxOffset;
|
|
int elemCount = (int)cmd.ElemCount;
|
|
|
|
// Create SDL vertices just for the vertices used by this command
|
|
// Determine the vertex range by looking at the indices
|
|
int minVertexIdx = int.MaxValue;
|
|
int maxVertexIdx = 0;
|
|
|
|
for (int i = 0; i < elemCount; i++)
|
|
{
|
|
ushort idx = drawList.IdxBuffer[indexOffset + i];
|
|
minVertexIdx = Math.Min(minVertexIdx, idx);
|
|
maxVertexIdx = Math.Max(maxVertexIdx, idx);
|
|
}
|
|
|
|
// Adjust for the vertex offset
|
|
minVertexIdx += vertexOffset;
|
|
maxVertexIdx += vertexOffset;
|
|
|
|
// Calculate the number of vertices we need
|
|
int numVertices = maxVertexIdx - minVertexIdx + 1;
|
|
|
|
// Create arrays for the vertices and indices we're going to use
|
|
SDL.Vertex[] vertices = new SDL.Vertex[numVertices];
|
|
int[] indices = new int[elemCount];
|
|
|
|
// Convert only the vertices we need
|
|
for (int i = 0; i < numVertices; i++)
|
|
{
|
|
int vertIdx = minVertexIdx + i;
|
|
ImDrawVertPtr srcVert = drawList.VtxBuffer[vertIdx];
|
|
|
|
// Convert from ImGui ABGR color to SDL RGBA color
|
|
uint col = srcVert.col;
|
|
byte r = (byte)(col >> 0 & 0xFF); // Extract R from the least significant byte
|
|
byte g = (byte)(col >> 8 & 0xFF); // Extract G
|
|
byte b = (byte)(col >> 16 & 0xFF); // Extract B
|
|
byte a = (byte)(col >> 24 & 0xFF); // Extract A from the most significant byte
|
|
|
|
vertices[i] = new SDL.Vertex
|
|
{
|
|
Position = new SDL.FPoint { X = srcVert.pos.X, Y = srcVert.pos.Y },
|
|
Color = new SDL.FColor { R = r / 255f, G = g / 255f, B = b / 255f, A = a / 255f },
|
|
TexCoord = new SDL.FPoint { X = srcVert.uv.X, Y = srcVert.uv.Y }
|
|
};
|
|
}
|
|
|
|
// Adjust indices to be relative to our new vertex array
|
|
for (int i = 0; i < elemCount; i++)
|
|
{
|
|
ushort originalIdx = drawList.IdxBuffer[indexOffset + i];
|
|
indices[i] = (ushort)(originalIdx - (minVertexIdx - vertexOffset));
|
|
}
|
|
|
|
// Call your SDL.RenderGeometry wrapper with the managed arrays
|
|
return SDL.RenderGeometry(
|
|
Renderer,
|
|
texId,
|
|
vertices,
|
|
numVertices,
|
|
indices,
|
|
elemCount
|
|
);
|
|
}
|
|
|
|
private void SetupRenderState()
|
|
{
|
|
SDL.SetRenderViewport(Renderer, 0);
|
|
SDL.SetRenderClipRect(Renderer, IntPtr.Zero);
|
|
SDL.SetRenderDrawBlendMode(Renderer, SDL.BlendMode.Blend);
|
|
}
|
|
|
|
private BackupSDLRendererState BackupRendererState()
|
|
{
|
|
BackupSDLRendererState state = new BackupSDLRendererState
|
|
{
|
|
ViewportEnabled = SDL.RenderViewportSet(Renderer),
|
|
ClipEnabled = SDL.RenderClipEnabled(Renderer)
|
|
};
|
|
SDL.GetRenderViewport(Renderer, out state.Viewport);
|
|
SDL.GetRenderClipRect(Renderer, out state.ClipRect);
|
|
return state;
|
|
}
|
|
|
|
private void RestoreRendererState(BackupSDLRendererState state)
|
|
{
|
|
if (state.ViewportEnabled)
|
|
SDL.SetRenderViewport(Renderer, state.Viewport);
|
|
else
|
|
SDL.SetRenderViewport(Renderer, IntPtr.Zero);
|
|
|
|
if (state.ClipEnabled)
|
|
SDL.SetRenderClipRect(Renderer, state.ClipRect);
|
|
else
|
|
SDL.SetRenderClipRect(Renderer, IntPtr.Zero);
|
|
}
|
|
|
|
private SDL.Rect CalculateClipRect(Vector4 clipRect, Vector2 clipOffset, Vector2 scale, int fbWidth, int fbHeight)
|
|
{
|
|
Vector2 clipMin = new Vector2((clipRect.X - clipOffset.X) * scale.X, (clipRect.Y - clipOffset.Y) * scale.Y);
|
|
Vector2 clipMax = new Vector2((clipRect.Z - clipOffset.X) * scale.X, (clipRect.W - clipOffset.Y) * scale.Y);
|
|
|
|
// Clamp to framebuffer bounds
|
|
clipMin.X = Math.Max(0, clipMin.X);
|
|
clipMin.Y = Math.Max(0, clipMin.Y);
|
|
clipMax.X = Math.Min(fbWidth, clipMax.X);
|
|
clipMax.Y = Math.Min(fbHeight, clipMax.Y);
|
|
|
|
SDL.Rect r = new SDL.Rect
|
|
{
|
|
X = (int)clipMin.X,
|
|
Y = (int)clipMin.Y,
|
|
W = (int)(clipMax.X - clipMin.X),
|
|
H = (int)(clipMax.Y - clipMin.Y)
|
|
};
|
|
|
|
return r;
|
|
}
|
|
|
|
private unsafe bool CreateDeviceObjects()
|
|
{
|
|
ImGuiIOPtr io = ImGui.GetIO();
|
|
|
|
io.Fonts.AddFontDefault();
|
|
|
|
// TODO: Load custom fonts from "Fonts" directory
|
|
//string fontsPath = Path.Combine(Plugin.AssemblyDirectory, "Fonts");
|
|
//if (Path.Exists(fontsPath))
|
|
//{
|
|
// string[] fonts = Directory.GetFiles(fontsPath, "*.ttf");
|
|
// if (fonts.Length > 0)
|
|
// {
|
|
// foreach (string font in fonts)
|
|
// {
|
|
// io.Fonts.AddFontFromFileTTF(font, 20f);
|
|
// }
|
|
// }
|
|
//}
|
|
|
|
// Build texture atlas
|
|
io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out int width, out int height);
|
|
|
|
// Create surface from pixel data
|
|
IntPtr surface = SDL.CreateSurfaceFrom(width, height, SDL.PixelFormat.RGBA8888, (IntPtr)pixels, width * 4);
|
|
if (surface == IntPtr.Zero)
|
|
{
|
|
SDL.LogError(SDL.LogCategory.Application, $"Failed to create font surface: {SDL.GetError()}");
|
|
return false;
|
|
}
|
|
|
|
// Create texture
|
|
_fontTexture = SDL.CreateTextureFromSurface(Renderer, surface);
|
|
if (_fontTexture == IntPtr.Zero)
|
|
{
|
|
SDL.LogError(SDL.LogCategory.Application, $"Failed to create font texture: {SDL.GetError()}");
|
|
return false;
|
|
}
|
|
|
|
// Update texture directly without converting pixel format
|
|
if (!SDL.UpdateTexture(_fontTexture, IntPtr.Zero, (IntPtr)pixels, width * 4))
|
|
{
|
|
SDL.LogError(SDL.LogCategory.Application, $"Failed to update font texture: {SDL.GetError()}");
|
|
return false;
|
|
}
|
|
|
|
// Ensure proper blending for font rendering
|
|
SDL.SetTextureBlendMode(_fontTexture, SDL.BlendMode.Blend);
|
|
|
|
// Use nearest neighbor filtering for crisp font rendering at small sizes
|
|
SDL.SetTextureScaleMode(_fontTexture, SDL.ScaleMode.Linear);
|
|
|
|
// Store our identifier
|
|
io.Fonts.SetTexID(_fontTexture);
|
|
|
|
SDL.DestroySurface(surface);
|
|
io.Fonts.ClearTexData();
|
|
|
|
return true;
|
|
}
|
|
} |