Capture the current frame from a window's swapchain.
This initiates an asynchronous readback of the current swapchain image into CPU-accessible memory. The result is stored in the capture state for retrieval via the display service.
915{
916 if (!ctx.swapchain || ctx.window->get_rendering_buffers().empty()
917 || !ctx.window->is_capture_enabled())
918 return;
919
921 const auto ext = ctx.swapchain->get_extent();
922 const vk::Format fmt = ctx.swapchain->get_image_format();
924 const vk::Image src = ctx.swapchain->get_images()[ctx.current_image_index];
925
926 if (!ctx.capture) {
928 }
929
930 auto& slot = *ctx.capture->slots[ctx.capture->slot_index];
931
932 if (slot.pending.load(std::memory_order_acquire))
933 return;
934
935 if (slot.image && slot.extent != ext) {
936 dev.destroyImage(slot.image);
937 dev.freeMemory(slot.mem);
938 slot.image = vk::Image {};
939 slot.mem = vk::DeviceMemory {};
940 }
941
942 if (!slot.image) {
943 vk::ImageCreateInfo ici {};
944 ici.imageType = vk::ImageType::e2D;
945 ici.format = fmt;
946 ici.extent = vk::Extent3D { ext.width, ext.height, 1 };
947 ici.arrayLayers = 1;
948 ici.mipLevels = 1;
949 ici.samples = vk::SampleCountFlagBits::e1;
950 ici.tiling = vk::ImageTiling::eLinear;
951 ici.usage = vk::ImageUsageFlagBits::eTransferDst;
952
953 slot.image = dev.createImage(ici);
954
955 auto req = dev.getImageMemoryRequirements(slot.image);
956 vk::MemoryAllocateInfo mai {};
957 mai.allocationSize = req.size;
959 req.memoryTypeBits,
960 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
961
962 slot.mem = dev.allocateMemory(mai);
963 dev.bindImageMemory(slot.image, slot.mem, 0);
964 slot.extent = ext;
965 }
966
967 slot.cmd.reset({});
968 vk::CommandBufferBeginInfo bi {};
969 bi.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit;
970 slot.cmd.begin(bi);
971
972 vk::ImageMemoryBarrier
b {};
973 b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
974 b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
975 b.subresourceRange = { vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1 };
976
977 b.image = slot.image;
978 b.oldLayout = vk::ImageLayout::eUndefined;
979 b.newLayout = vk::ImageLayout::eTransferDstOptimal;
980 b.srcAccessMask = {};
981 b.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
982 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
983 vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
b);
984
986 b.oldLayout = vk::ImageLayout::ePresentSrcKHR;
987 b.newLayout = vk::ImageLayout::eTransferSrcOptimal;
988 b.srcAccessMask = vk::AccessFlagBits::eMemoryRead;
989 b.dstAccessMask = vk::AccessFlagBits::eTransferRead;
990 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
991 vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
b);
992
993 vk::ImageCopy copy {};
994 copy.srcSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 };
995 copy.dstSubresource = { vk::ImageAspectFlagBits::eColor, 0, 0, 1 };
996 copy.extent = vk::Extent3D { ext.width, ext.height, 1 };
997 slot.cmd.copyImage(src, vk::ImageLayout::eTransferSrcOptimal,
998 slot.image, vk::ImageLayout::eTransferDstOptimal, 1, ©);
999
1000 b.image = slot.image;
1001 b.oldLayout = vk::ImageLayout::eTransferDstOptimal;
1002 b.newLayout = vk::ImageLayout::eGeneral;
1003 b.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
1004 b.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
1005 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
1006 vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
b);
1007
1009 b.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
1010 b.newLayout = vk::ImageLayout::ePresentSrcKHR;
1011 b.srcAccessMask = vk::AccessFlagBits::eTransferRead;
1012 b.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
1013 slot.cmd.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
1014 vk::PipelineStageFlagBits::eTransfer, {}, {}, {},
b);
1015
1016 slot.cmd.end();
1017
1018 (void)dev.resetFences(1, &slot.fence);
1019
1020 vk::SubmitInfo si {};
1021 si.commandBufferCount = 1;
1022 si.pCommandBuffers = &slot.cmd;
1023
1024 slot.pending.store(true, std::memory_order_release);
1025
1028 "Failed to submit capture command buffer for window '{}': {}",
1029 ctx.window->get_create_info().title, vk::to_string(result));
1030 slot.pending.store(false, std::memory_order_release);
1031 return;
1032 }
1033
1034 ctx.capture->slot_index = (ctx.capture->slot_index + 1) % ctx.capture->slots.size();
1035}
#define MF_RT_ERROR(comp, ctx,...)
uint32_t find_memory_type(uint32_t type_filter, vk::MemoryPropertyFlags properties) const
Find a suitable memory type for Vulkan buffer allocation.
BackendResourceManager * m_resource_manager
void ensure_capture_state(WindowRenderContext &ctx)
Ensure capture state is initialized for a window context.
vk::Device get_device() const
Get logical device.
vk::Queue get_graphics_queue() const
Get graphics queue.
uint32_t vk_format_bytes_per_pixel(vk::Format fmt)
Byte width of a single pixel for a given Vulkan format.
@ GraphicsCallback
Graphics/visual rendering callback - frame-rate real-time.
@ Core
Core engine, backend, subsystems.