diff --git a/Videos/cortex_20260619_084733.mp4 b/Videos/cortex_20260619_084733.mp4 new file mode 100644 index 0000000..e422c6c Binary files /dev/null and b/Videos/cortex_20260619_084733.mp4 differ diff --git a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs index b9eaad4..310167f 100644 --- a/src/Engine.Graphics.Vulkan/VulkanRenderer.cs +++ b/src/Engine.Graphics.Vulkan/VulkanRenderer.cs @@ -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, diff --git a/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs b/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs index 18103ad..83a920c 100644 --- a/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs +++ b/src/Engine.Graphics.Vulkan/VulkanSwapchain.cs @@ -78,7 +78,7 @@ internal sealed unsafe class VulkanSwapchain : IDisposable imageColorSpace = _surfaceFormat.colorSpace, imageExtent = Extent, imageArrayLayers = 1, - imageUsage = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.TransferDst, + imageUsage = VkImageUsageFlags.ColorAttachment | VkImageUsageFlags.TransferDst | VkImageUsageFlags.TransferSrc, imageSharingMode = VkSharingMode.Exclusive, preTransform = caps.currentTransform, compositeAlpha = VkCompositeAlphaFlagsKHR.Opaque,