Download pixel data from a texture without blocking the graphics queue.
Allocates a per-call staging buffer, command buffer, and fence. Records a copy-image-to-buffer op, submits with the fence, and blocks the calling thread on vkWaitForFences until the copy completes. Unlike download_data, this does not call queue.waitIdle, so other graphics work proceeds concurrently.
Intended to be called from a worker thread (e.g. via std::async). Calling from the graphics thread or any thread that must not block will stall that thread for the duration of the copy.
411{
414 "Invalid parameters for download_data_async");
415 return;
416 }
417
420 if (!buffer_service || !buffer_service->execute_fenced
421 || !buffer_service->wait_fenced || !buffer_service->release_fenced
422 || !buffer_service->initialize_buffer || !buffer_service->destroy_buffer
423 || !buffer_service->invalidate_range) {
425 "download_data_async: BufferService unavailable or incomplete");
426 return;
427 }
428
429 auto staging = std::make_shared<Buffers::VKBuffer>(
430 size,
433
434 buffer_service->initialize_buffer(std::static_pointer_cast<void>(staging));
435
436 auto handle = buffer_service->execute_fenced([&](void* cmd_ptr) {
437 vk::CommandBuffer
cmd(
static_cast<VkCommandBuffer
>(cmd_ptr));
438
439 vk::ImageMemoryBarrier barrier {};
440 barrier.oldLayout =
image->get_current_layout();
441 barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
442 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
443 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
444 barrier.image =
image->get_image();
445 barrier.subresourceRange.aspectMask =
image->get_aspect_flags();
446 barrier.subresourceRange.baseMipLevel = 0;
447 barrier.subresourceRange.levelCount =
image->get_mip_levels();
448 barrier.subresourceRange.baseArrayLayer = 0;
449 barrier.subresourceRange.layerCount =
image->get_array_layers();
450 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
451 barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
452
454 vk::PipelineStageFlagBits::eFragmentShader,
455 vk::PipelineStageFlagBits::eTransfer,
456 vk::DependencyFlags {}, {}, {}, barrier);
457
458 vk::BufferImageCopy region {};
459 region.bufferOffset = 0;
460 region.bufferRowLength = 0;
461 region.bufferImageHeight = 0;
462 region.imageSubresource.aspectMask =
image->get_aspect_flags();
463 region.imageSubresource.mipLevel = 0;
464 region.imageSubresource.baseArrayLayer = 0;
465 region.imageSubresource.layerCount =
image->get_array_layers();
466 region.imageOffset = vk::Offset3D { 0, 0, 0 };
467 region.imageExtent = vk::Extent3D {
471 };
472
473 cmd.copyImageToBuffer(
475 vk::ImageLayout::eTransferSrcOptimal,
476 staging->get_buffer(),
477 1, ®ion);
478
479 barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
480 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
481 barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
482 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
483
485 vk::PipelineStageFlagBits::eTransfer,
486 vk::PipelineStageFlagBits::eFragmentShader,
487 vk::DependencyFlags {}, {}, {}, barrier);
488 });
489
490 if (!handle) {
492 "download_data_async: execute_fenced returned null handle");
493 buffer_service->destroy_buffer(std::static_pointer_cast<void>(staging));
494 return;
495 }
496
497 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
498
499 buffer_service->wait_fenced(handle);
500
501 auto& resources = staging->get_buffer_resources();
502 buffer_service->invalidate_range(resources.memory, 0, 0);
503
504 void* mapped = staging->get_mapped_ptr();
505 if (mapped) {
506 std::memcpy(data, mapped, size);
507 } else {
509 "download_data_async: staging buffer has no mapped pointer");
510 }
511
512 buffer_service->release_fenced(handle);
513 buffer_service->destroy_buffer(std::static_pointer_cast<void>(staging));
514
516 "download_data_async: completed {} byte download for {}x{}",
517 size,
image->get_width(),
image->get_height());
518}
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
@ STAGING
Host-visible staging buffer (CPU-writable, eTransferSrc|Dst)
bool is_initialized() const
Check if manager is initialized.
Interface * get_service()
Query for a backend service.
static BackendRegistry & instance()
Get the global registry instance.
@ ImageProcessing
Image processing tasks (filters, transformations)
@ Portal
High-level user-facing API layer.
@ IMAGE_COLOR
2D RGB/RGBA image