Files
Cortex_Engine/src/Engine.Graphics.Vulkan/VulkanFrameResources.cs
T
emil28092005 1dde764342 fix: move light data from UBO to push constants — fixes std140 layout issue
Root cause: GLSL std140 layout adds padding after mat4 for vec4 fields,
causing light data to be read at wrong offsets → NaN/artifacts.

Fix:
- UBO back to 64 bytes (mat4 vp only)
- Light data (pos + color, 32 bytes) sent via push constants at offset 64
- Two push constant ranges: Vertex (0-64, model) + Fragment (64-96, light)
- Both vertex and fragment shaders read from same push_constant block
- PBR shader fully restored with Cook-Torrance BRDF
- Dynamic point light follows physics ball
2026-06-18 19:53:45 +03:00

248 lines
9.1 KiB
C#

using System.Runtime.InteropServices;
namespace Engine.Graphics.Vulkan;
internal sealed unsafe class VulkanFrameResources : IDisposable
{
public const int MaxFramesInFlight = 2;
public const ulong UboSize = 64;
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>();
public VkBuffer[] UboBuffers = new VkBuffer[MaxFramesInFlight];
public VkDeviceMemory[] UboMemories = new VkDeviceMemory[MaxFramesInFlight];
public VkDescriptorSet[] DescriptorSets = new VkDescriptorSet[MaxFramesInFlight];
public VkDescriptorPool DescriptorPool;
private readonly VkDevice _device;
private bool _disposed;
public VulkanFrameResources(VkDevice device, uint queueFamilyIndex, uint swapchainImageCount,
VulkanContext ctx, VkDescriptorSetLayout descriptorSetLayout)
{
_device = device;
var poolInfo = new VkCommandPoolCreateInfo
{
sType = VkStructureType.CommandPoolCreateInfo,
flags = VkCommandPoolCreateFlags.ResetCommandBuffer,
queueFamilyIndex = queueFamilyIndex,
};
var cmdPool = VkCommandPool.Null;
var result = Vk.vkCreateCommandPool(_device, &poolInfo, 0, &cmdPool);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateCommandPool failed: {result}");
CommandPool = cmdPool;
var allocInfo = new VkCommandBufferAllocateInfo
{
sType = VkStructureType.CommandBufferAllocateInfo,
commandPool = CommandPool,
level = VkCommandBufferLevel.Primary,
commandBufferCount = MaxFramesInFlight,
};
fixed (VkCommandBuffer* cmdPtr = CommandBuffers)
{
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;
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;
result = Vk.vkCreateSemaphore(_device, &semInfo, 0, &sem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateSemaphore (submit) failed: {result}");
SubmitSemaphores[i] = sem;
}
CreateUniformBuffers(ctx);
CreateDescriptorPoolAndSets(descriptorSetLayout);
Console.WriteLine($"[Vulkan] Frame resources: {MaxFramesInFlight} frames in flight, {swapchainImageCount} submit semaphores");
}
private void CreateUniformBuffers(VulkanContext ctx)
{
for (int i = 0; i < MaxFramesInFlight; i++)
{
var info = new VkBufferCreateInfo
{
sType = VkStructureType.BufferCreateInfo,
size = UboSize,
usage = VkBufferUsageFlags.UniformBuffer,
sharingMode = VkSharingMode.Exclusive,
};
var buf = VkBuffer.Null;
var result = Vk.vkCreateBuffer(_device, &info, 0, &buf);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateBuffer (UBO) failed: {result}");
UboBuffers[i] = buf;
var reqs = new VkMemoryRequirements();
Vk.vkGetBufferMemoryRequirements(_device, buf, &reqs);
var memTypeIndex = ctx.FindMemoryType(reqs.memoryTypeBits,
VkMemoryPropertyFlags.HostVisible | VkMemoryPropertyFlags.HostCoherent);
var allocInfo = new VkMemoryAllocateInfo
{
sType = VkStructureType.MemoryAllocateInfo,
allocationSize = reqs.size,
memoryTypeIndex = memTypeIndex,
};
var mem = VkDeviceMemory.Null;
result = Vk.vkAllocateMemory(_device, &allocInfo, 0, &mem);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkAllocateMemory (UBO) failed: {result}");
UboMemories[i] = mem;
Vk.vkBindBufferMemory(_device, buf, mem, 0);
}
}
private void CreateDescriptorPoolAndSets(VkDescriptorSetLayout layout)
{
var poolSize = new VkDescriptorPoolSize
{
type = VkDescriptorType.UniformBuffer,
descriptorCount = MaxFramesInFlight,
};
var poolInfo = new VkDescriptorPoolCreateInfo
{
sType = VkStructureType.DescriptorPoolCreateInfo,
flags = VkDescriptorPoolCreateFlags.FreeDescriptorSet,
maxSets = MaxFramesInFlight,
poolSizeCount = 1,
pPoolSizes = &poolSize,
};
var descPool = VkDescriptorPool.Null;
var result = Vk.vkCreateDescriptorPool(_device, &poolInfo, 0, &descPool);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkCreateDescriptorPool failed: {result}");
DescriptorPool = descPool;
var layouts = stackalloc VkDescriptorSetLayout[MaxFramesInFlight];
for (int i = 0; i < MaxFramesInFlight; i++)
layouts[i] = layout;
var allocInfo = new VkDescriptorSetAllocateInfo
{
sType = VkStructureType.DescriptorSetAllocateInfo,
descriptorPool = DescriptorPool,
descriptorSetCount = MaxFramesInFlight,
pSetLayouts = layouts,
};
fixed (VkDescriptorSet* setPtr = DescriptorSets)
{
result = Vk.vkAllocateDescriptorSets(_device, &allocInfo, setPtr);
if (result != VkResult.Success)
throw new InvalidOperationException($"vkAllocateDescriptorSets failed: {result}");
}
for (int i = 0; i < MaxFramesInFlight; i++)
{
var bufferInfo = new VkDescriptorBufferInfo
{
buffer = UboBuffers[i],
offset = 0,
range = UboSize,
};
var write = new VkWriteDescriptorSet
{
sType = VkStructureType.WriteDescriptorSet,
dstSet = DescriptorSets[i],
dstBinding = 0,
dstArrayElement = 0,
descriptorCount = 1,
descriptorType = VkDescriptorType.UniformBuffer,
pBufferInfo = &bufferInfo,
};
Vk.vkUpdateDescriptorSets(_device, 1, &write, 0, 0);
}
}
public void UpdateUbo(int frameIndex, void* data, ulong size)
{
void* pData = null;
Vk.vkMapMemory(_device, UboMemories[frameIndex], 0, size, 0, &pData);
System.Buffer.MemoryCopy(data, pData, (long)size, (long)size);
Vk.vkUnmapMemory(_device, UboMemories[frameIndex]);
}
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);
if (UboBuffers[i].Handle != 0) Vk.vkDestroyBuffer(_device, UboBuffers[i], 0);
if (UboMemories[i].Handle != 0) Vk.vkFreeMemory(_device, UboMemories[i], 0);
}
for (int i = 0; i < SubmitSemaphores.Length; i++)
{
if (SubmitSemaphores[i].Handle != 0) Vk.vkDestroySemaphore(_device, SubmitSemaphores[i], 0);
}
if (DescriptorPool.Handle != 0) Vk.vkDestroyDescriptorPool(_device, DescriptorPool, 0);
if (CommandPool.Handle != 0) Vk.vkDestroyCommandPool(_device, CommandPool, 0);
}
}