fix: video recording — proper layout transitions + deferred buffer read
Three validation errors fixed: 1. vkCmdCopyImageToBuffer inside render pass → moved AFTER vkCmdEndRendering 2. Swapchain images missing TRANSFER_SRC usage → added to swapchain creation 3. Wrong layout transitions → ColorAttachment→TransferSrc→copy→ColorAttachment Architecture change: - CaptureFrame now only records commands (returns null) - ReadCapturedBuffer called at start of NEXT frame (after WaitFrame/fence) GPU has finished by then, safe to map memory - CapturedFrame available 1 frame late (acceptable for video) - Image transitions: ColorAttachmentOptimal→TransferSrcOptimal→ColorAttachmentOptimal (present transition still works because image is back in ColorAttachmentOptimal)
This commit is contained in:
@@ -107,10 +107,10 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
var w = _swapchain.Extent.Width;
|
||||
var h = _swapchain.Extent.Height;
|
||||
|
||||
// Transition swapchain image: PresentSrc → TransferSrc
|
||||
// Transition: ColorAttachmentOptimal → TransferSrcOptimal
|
||||
TransitionImageLayout(cmd, _swapchain.Images[imageIndex],
|
||||
VkImageLayout.PresentSrcKHR, VkImageLayout.TransferSrcOptimal,
|
||||
0x8000, 0, 0x10000, 0x2000);
|
||||
VkImageLayout.ColorAttachmentOptimal, VkImageLayout.TransferSrcOptimal,
|
||||
0x400, 0x100, 0x10000, 0x2000);
|
||||
|
||||
// Copy image to buffer
|
||||
var region = new VkBufferImageCopyRegion
|
||||
@@ -132,12 +132,18 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
Vk.vkCmdCopyImageToBuffer(cmd, _swapchain.Images[imageIndex], VkImageLayout.TransferSrcOptimal,
|
||||
_screenshotBuffer, 1, ®ion);
|
||||
|
||||
// Transition back: TransferSrc → PresentSrc
|
||||
// Transition back: TransferSrcOptimal → ColorAttachmentOptimal
|
||||
TransitionImageLayout(cmd, _swapchain.Images[imageIndex],
|
||||
VkImageLayout.TransferSrcOptimal, VkImageLayout.PresentSrcKHR,
|
||||
0x10000, 0x2000, 0x8000, 0);
|
||||
VkImageLayout.TransferSrcOptimal, VkImageLayout.ColorAttachmentOptimal,
|
||||
0x10000, 0x2000, 0x400, 0x100);
|
||||
|
||||
return null; // Data will be read next frame after GPU completes
|
||||
}
|
||||
|
||||
private byte[]? ReadCapturedBuffer()
|
||||
{
|
||||
if (!_screenshotInitialized) return null;
|
||||
|
||||
// Map and read
|
||||
void* pData = null;
|
||||
Vk.vkMapMemory(_ctx.Device, _screenshotMemory, 0, _screenshotBufferSize, 0, &pData);
|
||||
|
||||
@@ -265,6 +271,12 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
{
|
||||
_frameResources.WaitFrame(_frameIndex);
|
||||
|
||||
// Read captured frame from previous render (GPU has finished by now)
|
||||
if (IsRecording)
|
||||
{
|
||||
CapturedFrame = ReadCapturedBuffer();
|
||||
}
|
||||
|
||||
uint imageIndex;
|
||||
var acquireResult = Vk.vkAcquireNextImageKHR(_ctx.Device, _swapchain.Swapchain,
|
||||
ulong.MaxValue, _frameResources.AcquireSemaphores[_frameIndex], VkFence.Null, &imageIndex);
|
||||
@@ -505,16 +517,16 @@ public sealed unsafe class VulkanRenderer : IRenderer, Engine.Graphics.IScreensh
|
||||
Vk.vkCmdDrawIndexed(cmd, dc.indexCount, 1, 0, 0, 0);
|
||||
}
|
||||
|
||||
// Capture frame BEFORE ImGui (video without UI)
|
||||
_imGui?.Render(cmd, _swapchain.Extent.Width, _swapchain.Extent.Height);
|
||||
|
||||
Vk.vkCmdEndRendering(cmd);
|
||||
|
||||
// Capture frame AFTER render pass ends, BEFORE present transition
|
||||
if (IsRecording)
|
||||
{
|
||||
CapturedFrame = CaptureFrame(cmd, imageIndex);
|
||||
}
|
||||
|
||||
_imGui?.Render(cmd, _swapchain.Extent.Width, _swapchain.Extent.Height);
|
||||
|
||||
Vk.vkCmdEndRendering(cmd);
|
||||
|
||||
TransitionImageLayout(cmd, _swapchain.Images[imageIndex],
|
||||
VkImageLayout.ColorAttachmentOptimal, VkImageLayout.PresentSrcKHR,
|
||||
0x400, 0x100,
|
||||
|
||||
Reference in New Issue
Block a user