blob: a4007da40d4e185d640e406a17fe3ff9a34c8dc5 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/graphics/examples/vkproto/common/readback.h"
#include <cassert>
#include "vulkan/vulkan.hpp"
namespace vkp {
std::optional<vk::UniqueDeviceMemory> TransitionToHostVisibleImage(
const vk::PhysicalDevice& physical_device, const vk::Device& device, const vk::Image& src_image,
const vk::Extent2D& extent, const vk::CommandPool& command_pool, const vk::Queue& queue,
vk::SubresourceLayout* host_image_layout) {
// Create the linear tiled, host visible destination image to copy to and
// to read the memory from.
vk::ImageCreateInfo host_image_info;
host_image_info.imageType = vk::ImageType::e2D;
host_image_info.format = vk::Format::eR8G8B8A8Unorm;
host_image_info.extent = vk::Extent3D{extent.width, extent.height, 1};
host_image_info.arrayLayers = 1;
host_image_info.mipLevels = 1;
host_image_info.initialLayout = vk::ImageLayout::eUndefined;
host_image_info.samples = vk::SampleCountFlagBits::e1;
host_image_info.tiling = vk::ImageTiling::eLinear;
host_image_info.usage = vk::ImageUsageFlagBits::eTransferDst;
std::optional<vk::UniqueDeviceMemory> empty;
// Create the host visible destination image.
auto [r_host_image, host_image] = device.createImageUnique(host_image_info);
RTN_IF_VKH_ERR(empty, r_host_image, "Failed to create host visible readback image.\n");
// Create backing memory for the host image.
auto image_memory_requirements = device.getImageMemoryRequirements(*host_image);
vk::MemoryAllocateInfo alloc_info;
alloc_info.allocationSize = image_memory_requirements.size;
// Memory must be host visible to map and copy from.
RTN_IF_VKH_ERR(
empty,
vkp::FindMemoryIndex(physical_device, image_memory_requirements.memoryTypeBits,
vk::MemoryPropertyFlagBits::eHostVisible, &alloc_info.memoryTypeIndex),
"Failed to find matching memory index.");
auto r_host_image_memory = device.allocateMemoryUnique(alloc_info);
RTN_IF_VKH_ERR(empty, r_host_image_memory.result,
"Failed to allocate memory for host visible image.\n");
vk::UniqueDeviceMemory host_image_memory = std::move(r_host_image_memory.value);
RTN_IF_VKH_ERR(empty, device.bindImageMemory(*host_image, *host_image_memory, 0),
"Failed to bind device memory to host visible image.\n");
// Configure and submit a command buffer to copy from the offscreen color
// attachment image to our host-visible destination image.
vk::CommandBufferAllocateInfo cmd_buf_alloc_info;
cmd_buf_alloc_info.setCommandBufferCount(1);
cmd_buf_alloc_info.setCommandPool(command_pool);
auto [r_cmd_bufs, command_buffers] = device.allocateCommandBuffersUnique(cmd_buf_alloc_info);
RTN_IF_VKH_ERR(empty, r_cmd_bufs, "Failed to allocate command buffers.\n");
const vk::UniqueCommandBuffer& command_buffer = command_buffers[0];
vk::CommandBufferBeginInfo begin_info(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
RTN_IF_VKH_ERR(empty, command_buffer->begin(&begin_info), "Failed to begin command buffer.\n");
// Transition destination image to transfer destination layout.
const vk::ImageSubresourceRange subresource_range = {vk::ImageAspectFlagBits::eColor,
0 /* baseMipLevel */, 1 /* levelCount */,
0 /* baseArrayLayer */, 1 /* layerCount */};
vk::ImageSubresource subresource = {subresource_range.aspectMask, subresource_range.baseMipLevel,
subresource_range.baseArrayLayer};
vk::ImageMemoryBarrier transfer_memory_barrier;
transfer_memory_barrier.srcAccessMask = vk::AccessFlags{};
transfer_memory_barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
transfer_memory_barrier.oldLayout = vk::ImageLayout::eUndefined;
transfer_memory_barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
transfer_memory_barrier.image = *host_image;
transfer_memory_barrier.subresourceRange = subresource_range;
command_buffer->pipelineBarrier(
vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlags{}, 0 /* memoryBarrierCount */, nullptr /* pMemoryBarriers */,
0 /* bufferMemoryBarrierCount */, nullptr /* pBufferMemoryBarriers */,
1 /* imageMemoryBarrierCount */, &transfer_memory_barrier);
vk::ImageCopy image_copy_region;
image_copy_region.srcSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
image_copy_region.srcSubresource.layerCount = 1;
image_copy_region.dstSubresource.aspectMask = vk::ImageAspectFlagBits::eColor;
image_copy_region.dstSubresource.layerCount = 1;
image_copy_region.setExtent(vk::Extent3D{extent.width, extent.height, 1});
command_buffer->copyImage(src_image, vk::ImageLayout::eTransferSrcOptimal, *host_image,
vk::ImageLayout::eTransferDstOptimal, 1, &image_copy_region);
// Transition destination image to general layout, which is the required
// layout for mapping the image memory later on.
vk::ImageMemoryBarrier map_memory_barrier;
map_memory_barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
map_memory_barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
map_memory_barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
map_memory_barrier.newLayout = vk::ImageLayout::eGeneral;
map_memory_barrier.image = *host_image;
map_memory_barrier.subresourceRange = subresource_range;
command_buffer->pipelineBarrier(
vk::PipelineStageFlagBits::eTransfer, vk::PipelineStageFlagBits::eTransfer,
vk::DependencyFlags{}, 0 /* memoryBarrierCount */, nullptr /* pMemoryBarriers */,
0 /* bufferMemoryBarrierCount */, nullptr /* pBufferMemoryBarriers */,
1 /* imageMemoryBarrierCount */, &map_memory_barrier);
RTN_IF_VKH_ERR(empty, command_buffer->end(), "end failed\n");
vk::SubmitInfo submit_info;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &(*command_buffer);
const vk::FenceCreateInfo fence_info(vk::FenceCreateFlagBits::eSignaled);
auto [r_fence, unique_fence] = device.createFenceUnique(fence_info);
RTN_IF_VKH_ERR(empty, r_fence, "Failed to create readback image transition fence.\n");
vk::Fence& fence = *unique_fence;
RTN_IF_VKH_ERR(empty, device.resetFences(1, &fence), "resetFences failed\n");
RTN_IF_VKH_ERR(empty, queue.submit(1, &submit_info, fence),
"Failed to submit command buffer for readback image transition.\n");
RTN_IF_VKH_ERR(empty,
device.waitForFences(1, &fence, VK_TRUE, std::numeric_limits<uint64_t>::max()),
"waitForFences failed\n");
device.getImageSubresourceLayout(*host_image, &subresource, host_image_layout);
return std::make_optional<vk::UniqueDeviceMemory>(std::move(host_image_memory));
}
bool ReadPixels(const vk::PhysicalDevice& physical_device, const vk::Device& device,
const vk::Image& src_image, const vk::Extent2D& src_image_size,
const vk::CommandPool& command_pool, const vk::Queue& queue,
const vk::Extent2D& size, const vk::Offset2D& offset,
std::vector<uint32_t>* pixels) {
assert(offset.x + size.width <= src_image_size.width &&
offset.y + size.height <= src_image_size.height &&
"Incompatible output buffer size vs requested size.");
if (pixels->size() < size.width * size.height) {
pixels->resize(size.width * size.height);
}
// Transition image.
vk::SubresourceLayout host_image_layout;
auto host_image_memory_opt = TransitionToHostVisibleImage(
physical_device, device, src_image, src_image_size, command_pool, queue, &host_image_layout);
RTN_IF_MSG(false, !host_image_memory_opt, "Unable to transition to host image for readback.\n");
vk::UniqueDeviceMemory host_image_memory = std::move(host_image_memory_opt.value());
// Map host image.
auto [r_mapped_memory, mapped_memory] =
device.mapMemory(*host_image_memory, 0 /* offset */, VK_WHOLE_SIZE, vk::MemoryMapFlags{});
RTN_IF_VKH_ERR(false, r_mapped_memory, "Readback vulkan memory map failed.\n");
vk::MappedMemoryRange range(*host_image_memory, 0 /* offset */, VK_WHOLE_SIZE);
RTN_IF_VKH_ERR(false, device.invalidateMappedMemoryRanges(1 /* memoryRangeCount */, &range),
"invalidateMappedMemoryRanges failed\n");
// Copy to output buffer.
uint8_t* host_image_buffer = static_cast<uint8_t*>(mapped_memory);
for (uint32_t y = 0; y < size.height; y++) {
for (uint32_t x = 0; x < size.width; x++) {
pixels->at(y * size.width + x) =
*((uint32_t*)(host_image_buffer + ((offset.y + y) * host_image_layout.rowPitch) +
((offset.x + x) * 4)));
}
}
device.unmapMemory(*host_image_memory);
return true;
}
} // namespace vkp