Files
Cortex_Engine/src/Engine.Graphics.Vulkan/VulkanBuffer.cs
T
emil28092005 749fbc8df4 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.
2026-06-17 23:39:38 +03:00

169 lines
5.9 KiB
C#

using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan;
public sealed unsafe class VulkanBuffer : IDisposable
{
public VkBuffer Buffer;
public VkDeviceMemory Memory;
public ulong Size;
public void* MappedData;
private readonly VulkanContext _ctx;
private bool _disposed;
public VulkanBuffer(VulkanContext ctx, ulong size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
{
_ctx = ctx;
Size = size;
VkBufferCreateInfo bufferInfo;
bufferInfo.sType = VkStructureType.BufferCreateInfo;
bufferInfo.pNext = null;
bufferInfo.flags = 0;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VkSharingMode.Exclusive;
bufferInfo.queueFamilyIndexCount = 0;
bufferInfo.pQueueFamilyIndices = null;
VkBuffer buffer;
VkResult result = Vk.vkCreateBuffer(_ctx.Device, &bufferInfo, null, &buffer);
Vk.CheckResult(result, "vkCreateBuffer");
Buffer = buffer;
VkMemoryRequirements2 memReq;
Vk.vkGetBufferMemoryRequirements(_ctx.Device, Buffer, &memReq);
VkMemoryAllocateInfo allocInfo;
allocInfo.sType = VkStructureType.MemoryAllocateInfo;
allocInfo.pNext = null;
allocInfo.allocationSize = memReq.size;
allocInfo.memoryTypeIndex = _ctx.FindMemoryType(memReq.memoryTypeBits, properties);
VkDeviceMemory memory;
result = Vk.vkAllocateMemory(_ctx.Device, &allocInfo, null, &memory);
Vk.CheckResult(result, "vkAllocateMemory (buffer)");
Memory = memory;
result = Vk.vkBindBufferMemory(_ctx.Device, Buffer, Memory, 0);
Vk.CheckResult(result, "vkBindBufferMemory");
if ((properties & VkMemoryPropertyFlags.HostVisible) != 0)
{
void* mapped;
result = Vk.vkMapMemory(_ctx.Device, Memory, 0, size, 0, &mapped);
Vk.CheckResult(result, "vkMapMemory");
MappedData = mapped;
}
}
public void Write(void* data, ulong size, ulong offset = 0)
{
if (MappedData == null)
throw new InvalidOperationException("Buffer is not host-visible/mapped.");
System.Buffer.MemoryCopy(data, (void*)((byte*)MappedData + offset), size, size);
}
public void Write<T>(T[] data, ulong offset = 0) where T : struct
{
var size = (ulong)(data.Length * Marshal.SizeOf<T>());
fixed (T* pData = data)
{
Write(pData, size, offset);
}
}
public static unsafe VulkanBuffer CreateStaging(VulkanContext ctx, void* data, ulong size)
{
var staging = new VulkanBuffer(ctx, size, VkBufferUsageFlags.TransferSrc, VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
staging.Write(data, size);
return staging;
}
public static unsafe void CopyBuffer(VulkanContext ctx, VkCommandPool cmdPool, VkBuffer src, VkBuffer dst, ulong size)
{
VkCommandBuffer cmd = BeginSingleTimeCommands(ctx, cmdPool);
var region = new VkBufferCopy { srcOffset = 0, dstOffset = 0, size = size };
Vk.vkCmdCopyBuffer(cmd, src, dst, 1, &region);
EndSingleTimeCommands(ctx, cmdPool, cmd);
}
public static VulkanBuffer CreateDeviceLocal<T>(VulkanContext ctx, VkCommandPool cmdPool, T[] data, VkBufferUsageFlags usage) where T : struct
{
var size = (ulong)(data.Length * Marshal.SizeOf<T>());
var staging = new VulkanBuffer(ctx, size, VkBufferUsageFlags.TransferSrc, VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
fixed (T* pData = data)
{
staging.Write(pData, size);
}
var deviceBuffer = new VulkanBuffer(ctx, size, usage | VkBufferUsageFlags.TransferDst, VkMemoryPropertyFlags.DeviceLocal);
CopyBuffer(ctx, cmdPool, staging.Buffer, deviceBuffer.Buffer, size);
staging.Dispose();
return deviceBuffer;
}
public static unsafe VkCommandBuffer BeginSingleTimeCommands(VulkanContext ctx, VkCommandPool cmdPool)
{
VkCommandBufferAllocateInfo allocInfo;
allocInfo.sType = VkStructureType.CommandBufferAllocateInfo;
allocInfo.pNext = null;
allocInfo.commandPool = cmdPool;
allocInfo.level = VkCommandBufferLevel.Primary;
allocInfo.commandBufferCount = 1;
VkCommandBuffer cmd;
Vk.vkAllocateCommandBuffers(ctx.Device, &allocInfo, &cmd);
VkCommandBufferBeginInfo beginInfo;
beginInfo.sType = VkStructureType.CommandBufferBeginInfo;
beginInfo.pNext = null;
beginInfo.flags = VkCommandBufferUsageFlags.OneTimeSubmit;
beginInfo.pInheritanceInfo = null;
Vk.vkBeginCommandBuffer(cmd, &beginInfo);
return cmd;
}
public static unsafe void EndSingleTimeCommands(VulkanContext ctx, VkCommandPool cmdPool, VkCommandBuffer cmd)
{
Vk.vkEndCommandBuffer(cmd);
VkSubmitInfo submitInfo;
submitInfo.sType = VkStructureType.SubmitInfo;
submitInfo.pNext = null;
submitInfo.waitSemaphoreCount = 0;
submitInfo.pWaitSemaphores = null;
submitInfo.pWaitDstStageMask = null;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &cmd;
submitInfo.signalSemaphoreCount = 0;
submitInfo.pSignalSemaphores = null;
Vk.vkQueueSubmit(ctx.GraphicsQueue, 1, &submitInfo, default);
Vk.vkQueueWaitIdle(ctx.GraphicsQueue);
Vk.vkFreeCommandBuffers(ctx.Device, cmdPool, 1, &cmd);
}
public unsafe void Dispose()
{
if (_disposed) return;
_disposed = true;
if (MappedData != null)
{
Vk.vkUnmapMemory(_ctx.Device, Memory);
MappedData = null;
}
if (Buffer.Value != 0) Vk.vkDestroyBuffer(_ctx.Device, Buffer, null);
if (Memory.Value != 0) Vk.vkFreeMemory(_ctx.Device, Memory, null);
}
}