- VulkanIndexBuffer.cs: staging → device-local index buffer (uint16 indices) - vkCmdBindIndexBuffer + vkCmdDrawIndexed added to Vk.cs - VulkanRenderer loads Content/cube.obj via ObjLoader (36 vertices, 12 faces) - Replaced hardcoded triangle with rotating cube - 0 validation errors, ~3800 FPS
171 lines
5.8 KiB
C#
171 lines
5.8 KiB
C#
using System.Runtime.InteropServices;
|
|
|
|
namespace Engine.Graphics.Vulkan;
|
|
|
|
internal sealed unsafe class VulkanIndexBuffer : IDisposable
|
|
{
|
|
public VkBuffer Buffer;
|
|
public VkDeviceMemory Memory;
|
|
public uint IndexCount;
|
|
|
|
private readonly VkDevice _device;
|
|
private bool _disposed;
|
|
|
|
public VulkanIndexBuffer(VkDevice device, VkCommandPool commandPool, VkQueue queue,
|
|
VulkanContext ctx, uint[] indices)
|
|
{
|
|
_device = device;
|
|
IndexCount = (uint)indices.Length;
|
|
var bufferSize = (ulong)(indices.Length * sizeof(uint));
|
|
|
|
var stagingBuffer = VkBuffer.Null;
|
|
var stagingMemory = VkDeviceMemory.Null;
|
|
CreateStagingBuffer(device, ctx, bufferSize, &stagingBuffer, &stagingMemory);
|
|
UploadToStaging(device, stagingMemory, indices, bufferSize);
|
|
var buf = VkBuffer.Null;
|
|
var mem = VkDeviceMemory.Null;
|
|
CreateDeviceLocalBuffer(device, ctx, bufferSize, &buf, &mem);
|
|
Buffer = buf;
|
|
Memory = mem;
|
|
CopyBuffer(device, commandPool, queue, stagingBuffer, Buffer, bufferSize);
|
|
|
|
Vk.vkDestroyBuffer(device, stagingBuffer, 0);
|
|
Vk.vkFreeMemory(device, stagingMemory, 0);
|
|
}
|
|
|
|
private static void CreateStagingBuffer(VkDevice device, VulkanContext ctx, ulong size,
|
|
VkBuffer* pBuffer, VkDeviceMemory* pMemory)
|
|
{
|
|
var info = new VkBufferCreateInfo
|
|
{
|
|
sType = VkStructureType.BufferCreateInfo,
|
|
size = size,
|
|
usage = VkBufferUsageFlags.TransferSrc,
|
|
sharingMode = VkSharingMode.Exclusive,
|
|
};
|
|
|
|
var result = Vk.vkCreateBuffer(device, &info, 0, pBuffer);
|
|
if (result != VkResult.Success)
|
|
throw new InvalidOperationException($"vkCreateBuffer (staging) failed: {result}");
|
|
|
|
var reqs = new VkMemoryRequirements();
|
|
Vk.vkGetBufferMemoryRequirements(device, *pBuffer, &reqs);
|
|
|
|
var memTypeIndex = ctx.FindMemoryType(reqs.memoryTypeBits,
|
|
VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
|
|
|
|
var allocInfo = new VkMemoryAllocateInfo
|
|
{
|
|
sType = VkStructureType.MemoryAllocateInfo,
|
|
allocationSize = reqs.size,
|
|
memoryTypeIndex = memTypeIndex,
|
|
};
|
|
|
|
result = Vk.vkAllocateMemory(device, &allocInfo, 0, pMemory);
|
|
if (result != VkResult.Success)
|
|
throw new InvalidOperationException($"vkAllocateMemory (staging) failed: {result}");
|
|
|
|
Vk.vkBindBufferMemory(device, *pBuffer, *pMemory, 0);
|
|
}
|
|
|
|
private static void UploadToStaging(VkDevice device, VkDeviceMemory memory, uint[] indices, ulong size)
|
|
{
|
|
void* pData = null;
|
|
Vk.vkMapMemory(device, memory, 0, size, 0, &pData);
|
|
fixed (uint* pIndices = indices)
|
|
{
|
|
System.Buffer.MemoryCopy(pIndices, pData, (long)size, (long)size);
|
|
}
|
|
Vk.vkUnmapMemory(device, memory);
|
|
}
|
|
|
|
private static void CreateDeviceLocalBuffer(VkDevice device, VulkanContext ctx, ulong size,
|
|
VkBuffer* pBuffer, VkDeviceMemory* pMemory)
|
|
{
|
|
var info = new VkBufferCreateInfo
|
|
{
|
|
sType = VkStructureType.BufferCreateInfo,
|
|
size = size,
|
|
usage = VkBufferUsageFlags.TransferDst | VkBufferUsageFlags.IndexBuffer,
|
|
sharingMode = VkSharingMode.Exclusive,
|
|
};
|
|
|
|
var result = Vk.vkCreateBuffer(device, &info, 0, pBuffer);
|
|
if (result != VkResult.Success)
|
|
throw new InvalidOperationException($"vkCreateBuffer (index) failed: {result}");
|
|
|
|
var reqs = new VkMemoryRequirements();
|
|
Vk.vkGetBufferMemoryRequirements(device, *pBuffer, &reqs);
|
|
|
|
var memTypeIndex = ctx.FindMemoryType(reqs.memoryTypeBits, VkMemoryPropertyFlags.DeviceLocal);
|
|
|
|
var allocInfo = new VkMemoryAllocateInfo
|
|
{
|
|
sType = VkStructureType.MemoryAllocateInfo,
|
|
allocationSize = reqs.size,
|
|
memoryTypeIndex = memTypeIndex,
|
|
};
|
|
|
|
result = Vk.vkAllocateMemory(device, &allocInfo, 0, pMemory);
|
|
if (result != VkResult.Success)
|
|
throw new InvalidOperationException($"vkAllocateMemory (index) failed: {result}");
|
|
|
|
Vk.vkBindBufferMemory(device, *pBuffer, *pMemory, 0);
|
|
}
|
|
|
|
private static void CopyBuffer(VkDevice device, VkCommandPool pool, VkQueue queue,
|
|
VkBuffer src, VkBuffer dst, ulong size)
|
|
{
|
|
var allocInfo = new VkCommandBufferAllocateInfo
|
|
{
|
|
sType = VkStructureType.CommandBufferAllocateInfo,
|
|
commandPool = pool,
|
|
level = VkCommandBufferLevel.Primary,
|
|
commandBufferCount = 1,
|
|
};
|
|
|
|
var cmd = VkCommandBuffer.Null;
|
|
Vk.vkAllocateCommandBuffers(device, &allocInfo, &cmd);
|
|
|
|
var beginInfo = new VkCommandBufferBeginInfo
|
|
{
|
|
sType = VkStructureType.CommandBufferBeginInfo,
|
|
flags = VkCommandBufferUsageFlags.OneTimeSubmit,
|
|
};
|
|
|
|
Vk.vkBeginCommandBuffer(cmd, &beginInfo);
|
|
|
|
var copyRegion = new VkBufferCopy { srcOffset = 0, dstOffset = 0, size = size };
|
|
Vk.vkCmdCopyBuffer(cmd, src, dst, 1, ©Region);
|
|
|
|
Vk.vkEndCommandBuffer(cmd);
|
|
|
|
var cmdInfo = new VkCommandBufferSubmitInfo
|
|
{
|
|
sType = VkStructureType.CommandBufferSubmitInfo,
|
|
commandBuffer = cmd,
|
|
};
|
|
|
|
var submitInfo = new VkSubmitInfo2
|
|
{
|
|
sType = VkStructureType.SubmitInfo2,
|
|
commandBufferInfoCount = 1,
|
|
pCommandBufferInfos = &cmdInfo,
|
|
};
|
|
|
|
Vk.vkQueueSubmit2(queue, 1, &submitInfo, VkFence.Null);
|
|
Vk.vkQueueWaitIdle(queue);
|
|
|
|
Vk.vkFreeCommandBuffers(device, pool, 1, &cmd);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
_disposed = true;
|
|
|
|
if (Buffer.Handle != 0) Vk.vkDestroyBuffer(_device, Buffer, 0);
|
|
if (Memory.Handle != 0) Vk.vkFreeMemory(_device, Memory, 0);
|
|
}
|
|
}
|