using System;
using Silk.NET.Core;
using Silk.NET.Vulkan;
namespace Engine.Graphics;
///
/// GPU index buffer for indexed draws.
/// Uses 32-bit indices.
///
public sealed unsafe class IndexBuffer : IDisposable
{
private readonly VulkanContext _context;
public Silk.NET.Vulkan.Buffer Buffer { get; }
public DeviceMemory Memory { get; }
public ulong Size { get; }
public uint Count { get; }
public IndexBuffer(VulkanContext context, ReadOnlySpan data, uint count)
{
_context = context;
Size = (ulong)data.Length;
Count = count;
Buffer = CreateBuffer(Size, BufferUsageFlags.IndexBufferBit);
var memoryRequirements = GetMemoryRequirements(Buffer);
Memory = AllocateMemory(memoryRequirements, MemoryPropertyFlags.HostVisibleBit | MemoryPropertyFlags.HostCoherentBit);
var result = _context.Vk.BindBufferMemory(_context.Device, Buffer, Memory, 0);
if (result != Result.Success)
throw new InvalidOperationException($"vkBindBufferMemory failed: {result}");
CopyData(data);
}
private Silk.NET.Vulkan.Buffer CreateBuffer(ulong size, BufferUsageFlags usage)
{
var createInfo = new BufferCreateInfo
{
SType = StructureType.BufferCreateInfo,
Size = size,
Usage = usage,
SharingMode = SharingMode.Exclusive
};
Silk.NET.Vulkan.Buffer buffer;
var result = _context.Vk.CreateBuffer(_context.Device, &createInfo, null, &buffer);
if (result != Result.Success)
throw new InvalidOperationException($"vkCreateBuffer failed: {result}");
return buffer;
}
private MemoryRequirements GetMemoryRequirements(Silk.NET.Vulkan.Buffer buffer)
{
MemoryRequirements requirements;
_context.Vk.GetBufferMemoryRequirements(_context.Device, buffer, &requirements);
return requirements;
}
private DeviceMemory AllocateMemory(MemoryRequirements requirements, MemoryPropertyFlags properties)
{
var memoryTypeIndex = FindMemoryType(requirements.MemoryTypeBits, properties);
var allocateInfo = new MemoryAllocateInfo
{
SType = StructureType.MemoryAllocateInfo,
AllocationSize = requirements.Size,
MemoryTypeIndex = memoryTypeIndex
};
DeviceMemory memory;
var result = _context.Vk.AllocateMemory(_context.Device, &allocateInfo, null, &memory);
if (result != Result.Success)
throw new InvalidOperationException($"vkAllocateMemory failed: {result}");
return memory;
}
private uint FindMemoryType(uint typeFilter, MemoryPropertyFlags properties)
{
PhysicalDeviceMemoryProperties memoryProperties;
_context.Vk.GetPhysicalDeviceMemoryProperties(_context.PhysicalDevice, &memoryProperties);
for (var i = 0; i < memoryProperties.MemoryTypeCount; i++)
{
if ((typeFilter & (1u << i)) != 0 &&
(memoryProperties.MemoryTypes[i].PropertyFlags & properties) == properties)
{
return (uint)i;
}
}
throw new InvalidOperationException("Failed to find suitable memory type.");
}
private void CopyData(ReadOnlySpan data)
{
void* mappedData;
var result = _context.Vk.MapMemory(_context.Device, Memory, 0, Size, MemoryMapFlags.None, &mappedData);
if (result != Result.Success)
throw new InvalidOperationException($"vkMapMemory failed: {result}");
fixed (byte* src = data)
{
global::System.Buffer.MemoryCopy(src, mappedData, (long)Size, data.Length);
}
_context.Vk.UnmapMemory(_context.Device, Memory);
}
public void Dispose()
{
_context.Vk.DeviceWaitIdle(_context.Device);
_context.Vk.DestroyBuffer(_context.Device, Buffer, null);
_context.Vk.FreeMemory(_context.Device, Memory, null);
}
}