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.
410{
413 "Invalid parameters for download_data_async");
414 return;
415 }
416
419 if (!buffer_service || !buffer_service->execute_fenced
420 || !buffer_service->wait_fenced || !buffer_service->release_fenced
421 || !buffer_service->initialize_buffer || !buffer_service->destroy_buffer
422 || !buffer_service->invalidate_range) {
424 "download_data_async: BufferService unavailable or incomplete");
425 return;
426 }
427
428 auto staging = std::make_shared<Buffers::VKBuffer>(
432
433 buffer_service->initialize_buffer(std::static_pointer_cast<void>(staging));
434
435 auto handle = buffer_service->execute_fenced([&](void* cmd_ptr) {
436 vk::CommandBuffer
cmd(
static_cast<VkCommandBuffer
>(cmd_ptr));
437
438 vk::ImageMemoryBarrier barrier {};
439 barrier.oldLayout =
image->get_current_layout();
440 barrier.newLayout = vk::ImageLayout::eTransferSrcOptimal;
441 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
442 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
443 barrier.image =
image->get_image();
444 barrier.subresourceRange.aspectMask =
image->get_aspect_flags();
445 barrier.subresourceRange.baseMipLevel = 0;
446 barrier.subresourceRange.levelCount =
image->get_mip_levels();
447 barrier.subresourceRange.baseArrayLayer = 0;
448 barrier.subresourceRange.layerCount =
image->get_array_layers();
449 barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead;
450 barrier.dstAccessMask = vk::AccessFlagBits::eTransferRead;
451
453 vk::PipelineStageFlagBits::eFragmentShader,
454 vk::PipelineStageFlagBits::eTransfer,
455 vk::DependencyFlags {}, {}, {}, barrier);
456
457 vk::BufferImageCopy region {};
458 region.bufferOffset = 0;
459 region.bufferRowLength = 0;
460 region.bufferImageHeight = 0;
461 region.imageSubresource.aspectMask =
image->get_aspect_flags();
462 region.imageSubresource.mipLevel = 0;
463 region.imageSubresource.baseArrayLayer = 0;
464 region.imageSubresource.layerCount =
image->get_array_layers();
465 region.imageOffset = vk::Offset3D { 0, 0, 0 };
466 region.imageExtent = vk::Extent3D {
470 };
471
472 cmd.copyImageToBuffer(
474 vk::ImageLayout::eTransferSrcOptimal,
475 staging->get_buffer(),
476 1, ®ion);
477
478 barrier.oldLayout = vk::ImageLayout::eTransferSrcOptimal;
479 barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
480 barrier.srcAccessMask = vk::AccessFlagBits::eTransferRead;
481 barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
482
484 vk::PipelineStageFlagBits::eTransfer,
485 vk::PipelineStageFlagBits::eFragmentShader,
486 vk::DependencyFlags {}, {}, {}, barrier);
487 });
488
489 if (!handle) {
491 "download_data_async: execute_fenced returned null handle");
492 buffer_service->destroy_buffer(std::static_pointer_cast<void>(staging));
493 return;
494 }
495
496 image->set_current_layout(vk::ImageLayout::eShaderReadOnlyOptimal);
497
498 buffer_service->wait_fenced(handle);
499
500 auto& resources = staging->get_buffer_resources();
501 buffer_service->invalidate_range(resources.memory, 0, 0);
502
503 void* mapped = staging->get_mapped_ptr();
504 if (mapped) {
505 std::memcpy(data, mapped,
size);
506 } else {
508 "download_data_async: staging buffer has no mapped pointer");
509 }
510
511 buffer_service->release_fenced(handle);
512 buffer_service->destroy_buffer(std::static_pointer_cast<void>(staging));
513
515 "download_data_async: completed {} byte download for {}x{}",
517}
#define MF_ERROR(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
@ STAGING
Host-visible staging buffer (CPU-writable)
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