feat: ImGui integration + real screenshot capture via Vulkan

ImGui:
- ImGui.NET 1.91.6.1 NuGet package
- VulkanImGui: font texture upload, ImGui pipeline (blending, no depth), vertex/index buffer streaming
- GLSL 450 ImGui shaders (vert: scale/translate push constants, frag: font texture sampler)
- Compiled to SPIR-V via @webgpu/glslang
- Debug overlay: FPS, camera position, entity count, light list
- 1600+ FPS with ImGui active

Screenshot:
- vkCmdCopyImageToBuffer embedded in main command buffer
- Layout transition PresentSrcKHR→TransferSrcOptimal→PresentSrcKHR
- Deferred read: staging buffer read on next frame after fence
- BMP format written directly to FileStream (row by row)
- All 16 camera tour screenshots captured (1280x720x32bpp)

All 66 tests pass.
This commit is contained in:
emil28092005
2026-06-17 23:39:38 +03:00
parent 544f6b59c0
commit 749fbc8df4
16 changed files with 838 additions and 231 deletions
+41
View File
@@ -14,6 +14,7 @@ using Engine.Graphics.Loaders;
using Engine.Graphics.Vulkan;
using Engine.Physics;
using Flecs.NET.Core;
using ImGuiNET;
namespace CortexEngine.App;
@@ -46,6 +47,16 @@ class Program
var input = window.Input;
using var renderer = renderContext.CreateRenderer();
VulkanImGui? imGuiLayer = null;
if (!cameraTour && renderer is VulkanRenderer vkRenderer)
{
ImGui.CreateContext();
imGuiLayer = new VulkanImGui(vkRenderer._ctx, vkRenderer._swapchain);
vkRenderer.ImGuiLayer = imGuiLayer;
ImGui.GetIO().DisplaySize = new System.Numerics.Vector2(window.Width, window.Height);
Console.WriteLine("[App] ImGui initialized.");
}
var (modelPath, mcpPort) = ParseArgs(args);
var mesh = LoadModel(modelPath);
@@ -249,6 +260,35 @@ class Program
demoScreenshotRequested = true;
}
if (imGuiLayer != null)
{
ImGui.NewFrame();
ImGui.Begin("Cortex Engine Debug");
ImGui.Text($"FPS: {currentFps}");
ImGui.Text($"Delta: {timing.DeltaTime * 1000.0:F2} ms");
ImGui.Text($"Camera: {cameraController.Name}");
ImGui.Separator();
var cam = cameraEntity.Get<Camera>();
ImGui.Text($"Pos: ({cam.Position.X:F2}, {cam.Position.Y:F2}, {cam.Position.Z:F2})");
ImGui.Text($"Target: ({cam.Target.X:F2}, {cam.Target.Y:F2}, {cam.Target.Z:F2})");
ImGui.Separator();
var entityCount = 0;
world.Each((Entity e, ref Transform _) => entityCount++);
ImGui.Text($"Entities: {entityCount}");
ImGui.Text($"Press F to toggle camera");
ImGui.Separator();
ImGui.Text("Lights:");
world.Each((Entity e, ref Light light) =>
{
ImGui.Text($" {e.Name()}: {light.Type} I={light.Intensity:F1}");
});
ImGui.End();
ImGui.Render();
}
renderer.RenderWorld(world);
queue.CompletePendingScreenshots();
@@ -263,6 +303,7 @@ class Program
}
Console.WriteLine("Shutting down...");
imGuiLayer?.Dispose();
#if !RELEASE_AOT
if (mcpApp != null)
await mcpApp.StopAsync();