feat: rebuild Vulkan renderer from scratch — pure P/Invoke triangle (Vulkan 1.3)

- Complete rewrite of Engine.Graphics.Vulkan with pure P/Invoke (no wrapper libs)
- Vulkan 1.3: dynamic rendering (vkCmdBeginRendering/vkCmdEndRendering),
  synchronization2 (vkQueueSubmit2, vkCmdPipelineBarrier2)
- Split types into VulkanHandles.cs, VulkanEnums.cs, VulkanStructs.cs
- Staging buffer → device-local vertex buffer pattern
- Correct swapchain semaphore indexing (per-image, not per-frame-in-flight)
- VK_EXT_debug_utils debug messenger with validation layer fallback
- Dynamic viewport/scissor (no pipeline recreation on resize)
- Simplified Program.cs to triangle-only rendering
- Removed old Silk.NET renderer, ImGui, PBR shaders, screenshot code
- Updated VULKAN_IMPLEMENTATION_PLAN.md with full architecture decisions
This commit is contained in:
emil28092005
2026-06-18 01:49:25 +03:00
parent ee98e4ad08
commit 2e0970e769
44 changed files with 3882 additions and 5273 deletions
@@ -0,0 +1,117 @@
namespace Engine.Graphics.Vulkan;
internal sealed unsafe class VulkanFrameResources : IDisposable
{
public const int MaxFramesInFlight = 2;
public VkCommandPool CommandPool;
public VkCommandBuffer[] CommandBuffers = new VkCommandBuffer[MaxFramesInFlight];
public VkFence[] FrameFences = new VkFence[MaxFramesInFlight];
public VkSemaphore[] AcquireSemaphores = new VkSemaphore[MaxFramesInFlight];
public VkSemaphore[] SubmitSemaphores = Array.Empty<VkSemaphore>();
private readonly VkDevice _device;
private bool _disposed;
public VulkanFrameResources(VkDevice device, uint queueFamilyIndex, uint swapchainImageCount)
{
_device = device;
var poolInfo = new VkCommandPoolCreateInfo
{
sType = VkStructureType.CommandPoolCreateInfo,
flags = VkCommandPoolCreateFlags.ResetCommandBuffer,
queueFamilyIndex = queueFamilyIndex,
};
fixed (VkCommandPool* poolPtr = &CommandPool)
{
var result = Vk.vkCreateCommandPool(_device, &poolInfo, 0, poolPtr);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateCommandPool failed: {result}");
}
var allocInfo = new VkCommandBufferAllocateInfo
{
sType = VkStructureType.CommandBufferAllocateInfo,
commandPool = CommandPool,
level = VkCommandBufferLevel.Primary,
commandBufferCount = MaxFramesInFlight,
};
fixed (VkCommandBuffer* cmdPtr = CommandBuffers)
{
var result = Vk.vkAllocateCommandBuffers(_device, &allocInfo, cmdPtr);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkAllocateCommandBuffers failed: {result}");
}
var fenceInfo = new VkFenceCreateInfo
{
sType = VkStructureType.FenceCreateInfo,
flags = VkFenceCreateFlags.Signaled,
};
var semInfo = new VkSemaphoreCreateInfo
{
sType = VkStructureType.SemaphoreCreateInfo,
};
for (int i = 0; i < MaxFramesInFlight; i++)
{
var fence = VkFence.Null;
var result = Vk.vkCreateFence(_device, &fenceInfo, 0, &fence);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateFence failed: {result}");
FrameFences[i] = fence;
var sem = VkSemaphore.Null;
result = Vk.vkCreateSemaphore(_device, &semInfo, 0, &sem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateSemaphore (acquire) failed: {result}");
AcquireSemaphores[i] = sem;
}
SubmitSemaphores = new VkSemaphore[swapchainImageCount];
for (int i = 0; i < swapchainImageCount; i++)
{
var sem = VkSemaphore.Null;
var result = Vk.vkCreateSemaphore(_device, &semInfo, 0, &sem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateSemaphore (submit) failed: {result}");
SubmitSemaphores[i] = sem;
}
Console.WriteLine($"[Vulkan] Frame resources: {MaxFramesInFlight} frames in flight, {swapchainImageCount} submit semaphores");
}
public void WaitFrame(int frameIndex)
{
fixed (VkFence* fencePtr = &FrameFences[frameIndex])
{
Vk.vkWaitForFences(_device, 1, fencePtr, VkBool32.True, ulong.MaxValue);
Vk.vkResetFences(_device, 1, fencePtr);
}
}
public void Dispose()
{
if (_disposed) return;
_disposed = true;
Vk.vkDeviceWaitIdle(_device);
for (int i = 0; i < MaxFramesInFlight; i++)
{
if (FrameFences[i].Handle != 0) Vk.vkDestroyFence(_device, FrameFences[i], 0);
if (AcquireSemaphores[i].Handle != 0) Vk.vkDestroySemaphore(_device, AcquireSemaphores[i], 0);
}
for (int i = 0; i < SubmitSemaphores.Length; i++)
{
if (SubmitSemaphores[i].Handle != 0) Vk.vkDestroySemaphore(_device, SubmitSemaphores[i], 0);
}
if (CommandPool.Handle != 0) Vk.vkDestroyCommandPool(_device, CommandPool, 0);
}
}