feat: index buffer + OBJ cube rendering
- 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
This commit is contained in:
@@ -58,6 +58,8 @@ internal static unsafe class Vk
|
||||
public delegate void VkCmdSetScissor(VkCommandBuffer commandBuffer, uint firstScissor, uint scissorCount, VkRect2D* pScissors);
|
||||
public delegate void VkCmdBindVertexBuffers(VkCommandBuffer commandBuffer, uint firstBinding, uint bindingCount, VkBuffer* pBuffers, ulong* pOffsets);
|
||||
public delegate void VkCmdDraw(VkCommandBuffer commandBuffer, uint vertexCount, uint instanceCount, uint firstVertex, uint firstInstance);
|
||||
public delegate void VkCmdDrawIndexed(VkCommandBuffer commandBuffer, uint indexCount, uint instanceCount, uint firstIndex, int vertexOffset, uint firstInstance);
|
||||
public delegate void VkCmdBindIndexBuffer(VkCommandBuffer commandBuffer, VkBuffer buffer, ulong offset, int indexType);
|
||||
public delegate void VkCmdBeginRendering(VkCommandBuffer commandBuffer, VkRenderingInfo* pRenderingInfo);
|
||||
public delegate void VkCmdEndRendering(VkCommandBuffer commandBuffer);
|
||||
public delegate void VkCmdPipelineBarrier2(VkCommandBuffer commandBuffer, VkDependencyInfo* pDependencyInfo);
|
||||
@@ -133,6 +135,8 @@ internal static unsafe class Vk
|
||||
public static VkCmdSetScissor vkCmdSetScissor;
|
||||
public static VkCmdBindVertexBuffers vkCmdBindVertexBuffers;
|
||||
public static VkCmdDraw vkCmdDraw;
|
||||
public static VkCmdDrawIndexed vkCmdDrawIndexed;
|
||||
public static VkCmdBindIndexBuffer vkCmdBindIndexBuffer;
|
||||
public static VkCmdBeginRendering vkCmdBeginRendering;
|
||||
public static VkCmdEndRendering vkCmdEndRendering;
|
||||
public static VkCmdPipelineBarrier2 vkCmdPipelineBarrier2;
|
||||
@@ -215,6 +219,8 @@ internal static unsafe class Vk
|
||||
vkCmdSetScissor = LoadDev<VkCmdSetScissor>(p, "vkCmdSetScissor");
|
||||
vkCmdBindVertexBuffers = LoadDev<VkCmdBindVertexBuffers>(p, "vkCmdBindVertexBuffers");
|
||||
vkCmdDraw = LoadDev<VkCmdDraw>(p, "vkCmdDraw");
|
||||
vkCmdDrawIndexed = LoadDev<VkCmdDrawIndexed>(p, "vkCmdDrawIndexed");
|
||||
vkCmdBindIndexBuffer = LoadDev<VkCmdBindIndexBuffer>(p, "vkCmdBindIndexBuffer");
|
||||
vkCmdBeginRendering = LoadDev<VkCmdBeginRendering>(p, "vkCmdBeginRendering");
|
||||
vkCmdEndRendering = LoadDev<VkCmdEndRendering>(p, "vkCmdEndRendering");
|
||||
vkCmdPipelineBarrier2 = LoadDev<VkCmdPipelineBarrier2>(p, "vkCmdPipelineBarrier2");
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Numerics;
|
||||
using Engine.Core;
|
||||
using Engine.Core.Components;
|
||||
using Engine.Graphics.Loaders;
|
||||
using Flecs.NET.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
@@ -13,6 +14,8 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
||||
private readonly VulkanPipeline _pipeline;
|
||||
private readonly VulkanFrameResources _frameResources;
|
||||
private readonly VulkanVertexBuffer _vertexBuffer;
|
||||
private readonly VulkanIndexBuffer _indexBuffer;
|
||||
private readonly uint _indexCount;
|
||||
|
||||
private int _frameIndex;
|
||||
private bool _disposed;
|
||||
@@ -36,15 +39,30 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
||||
_frameResources = new VulkanFrameResources(ctx.Device, ctx.GraphicsQueueFamilyIndex,
|
||||
swapchain.ImageCount, ctx, _pipeline.DescriptorSetLayout);
|
||||
|
||||
var vertices = new Vertex[]
|
||||
{
|
||||
new(new Vector3( 0.0f, -0.5f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f), new Vector3(0, 0, 1)),
|
||||
new(new Vector3( 0.5f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(0, 0, 1)),
|
||||
new(new Vector3(-0.5f, 0.5f, 0.0f), new Vector3(0.0f, 0.0f, 1.0f), new Vector3(0, 0, 1)),
|
||||
};
|
||||
var mesh = LoadMesh("Content/cube.obj");
|
||||
_indexCount = (uint)mesh.Indices.Length;
|
||||
|
||||
_vertexBuffer = new VulkanVertexBuffer(ctx.Device, ctx.PhysicalDevice,
|
||||
_frameResources.CommandPool, ctx.GraphicsQueue, ctx, vertices);
|
||||
_frameResources.CommandPool, ctx.GraphicsQueue, ctx, mesh.Vertices);
|
||||
|
||||
_indexBuffer = new VulkanIndexBuffer(ctx.Device, _frameResources.CommandPool,
|
||||
ctx.GraphicsQueue, ctx, mesh.Indices);
|
||||
}
|
||||
|
||||
private static Mesh LoadMesh(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
var altPath = Path.Combine(AppContext.BaseDirectory, path);
|
||||
if (!File.Exists(altPath))
|
||||
{
|
||||
altPath = Path.Combine(AppContext.BaseDirectory, "Content", Path.GetFileName(path));
|
||||
if (!File.Exists(altPath))
|
||||
throw new FileNotFoundException($"Mesh file not found: {path}");
|
||||
}
|
||||
return ObjLoader.Load(altPath, new Vector3(0.8f, 0.6f, 0.3f));
|
||||
}
|
||||
return ObjLoader.Load(path, new Vector3(0.8f, 0.6f, 0.3f));
|
||||
}
|
||||
|
||||
public void RenderWorld(World world)
|
||||
@@ -147,10 +165,12 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
||||
ulong offset = 0;
|
||||
Vk.vkCmdBindVertexBuffers(cmd, 0, 1, &bufferHandle, &offset);
|
||||
|
||||
Vk.vkCmdBindIndexBuffer(cmd, _indexBuffer.Buffer, 0, 0);
|
||||
|
||||
var angle = _totalTime;
|
||||
Vk.vkCmdPushConstants(cmd, _pipeline.PipelineLayout, VkShaderStageFlags.Vertex, 0, 4, &angle);
|
||||
|
||||
Vk.vkCmdDraw(cmd, 3, 1, 0, 0);
|
||||
Vk.vkCmdDrawIndexed(cmd, _indexCount, 1, 0, 0, 0);
|
||||
|
||||
Vk.vkCmdEndRendering(cmd);
|
||||
|
||||
@@ -305,6 +325,7 @@ internal sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreen
|
||||
Vk.vkDeviceWaitIdle(_ctx.Device);
|
||||
|
||||
_vertexBuffer?.Dispose();
|
||||
_indexBuffer?.Dispose();
|
||||
_frameResources?.Dispose();
|
||||
_pipeline?.Dispose();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user