blob: 7867ff40670055526e9c8c8271bb261a48f5bfa7 [file] [log] [blame]
// Copyright 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expresso or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "vulkan/VkDecoderSnapshotUtils.h"
#include "VkCommonOperations.h"
namespace gfxstream {
namespace vk {
namespace {
uint32_t GetMemoryType(const PhysicalDeviceInfo& physicalDevice,
const VkMemoryRequirements& memoryRequirements,
VkMemoryPropertyFlags memoryProperties) {
const auto& props = physicalDevice.memoryProperties;
for (uint32_t i = 0; i < props.memoryTypeCount; i++) {
if (!(memoryRequirements.memoryTypeBits & (1 << i))) {
continue;
}
if ((props.memoryTypes[i].propertyFlags & memoryProperties) != memoryProperties) {
continue;
}
return i;
}
GFXSTREAM_ABORT(emugl::FatalError(emugl::ABORT_REASON_OTHER))
<< "Cannot find memory type for snapshot save " << __func__ << " (" << __FILE__ << ":"
<< __LINE__ << ")";
}
uint32_t bytes_per_pixel(VkFormat format) {
switch (format) {
case VK_FORMAT_R8_UNORM:
case VK_FORMAT_R8_SNORM:
case VK_FORMAT_R8_USCALED:
case VK_FORMAT_R8_SSCALED:
case VK_FORMAT_R8_UINT:
case VK_FORMAT_R8_SINT:
case VK_FORMAT_R8_SRGB:
return 1;
case VK_FORMAT_R8G8_UNORM:
case VK_FORMAT_R8G8_SNORM:
case VK_FORMAT_R8G8_USCALED:
case VK_FORMAT_R8G8_SSCALED:
case VK_FORMAT_R8G8_UINT:
case VK_FORMAT_R8G8_SINT:
case VK_FORMAT_R8G8_SRGB:
return 2;
case VK_FORMAT_R8G8B8_UNORM:
case VK_FORMAT_R8G8B8_SNORM:
case VK_FORMAT_R8G8B8_USCALED:
case VK_FORMAT_R8G8B8_SSCALED:
case VK_FORMAT_R8G8B8_UINT:
case VK_FORMAT_R8G8B8_SINT:
case VK_FORMAT_R8G8B8_SRGB:
case VK_FORMAT_B8G8R8_UNORM:
case VK_FORMAT_B8G8R8_SNORM:
case VK_FORMAT_B8G8R8_USCALED:
case VK_FORMAT_B8G8R8_SSCALED:
case VK_FORMAT_B8G8R8_UINT:
case VK_FORMAT_B8G8R8_SINT:
case VK_FORMAT_B8G8R8_SRGB:
return 3;
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SNORM:
case VK_FORMAT_R8G8B8A8_USCALED:
case VK_FORMAT_R8G8B8A8_SSCALED:
case VK_FORMAT_R8G8B8A8_UINT:
case VK_FORMAT_R8G8B8A8_SINT:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_B8G8R8A8_UNORM:
case VK_FORMAT_B8G8R8A8_SNORM:
case VK_FORMAT_B8G8R8A8_USCALED:
case VK_FORMAT_B8G8R8A8_SSCALED:
case VK_FORMAT_B8G8R8A8_UINT:
case VK_FORMAT_B8G8R8A8_SINT:
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
case VK_FORMAT_A8B8G8R8_USCALED_PACK32:
case VK_FORMAT_A8B8G8R8_SSCALED_PACK32:
case VK_FORMAT_A8B8G8R8_UINT_PACK32:
case VK_FORMAT_A8B8G8R8_SINT_PACK32:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
return 4;
default:
GFXSTREAM_ABORT(emugl::FatalError(emugl::ABORT_REASON_OTHER))
<< "Unsupported VkFormat on snapshot save " << format << " " << __func__ << " ("
<< __FILE__ << ":" << __LINE__ << ")";
}
}
VkExtent3D getMipmapExtent(VkExtent3D baseExtent, uint32_t mipLevel) {
return VkExtent3D{
.width = (baseExtent.width + (1 << mipLevel) - 1) >> mipLevel,
.height = (baseExtent.height + (1 << mipLevel) - 1) >> mipLevel,
.depth = baseExtent.depth,
};
}
} // namespace
#define _RUN_AND_CHECK(command) \
{ \
if (command) \
GFXSTREAM_ABORT(emugl::FatalError(emugl::ABORT_REASON_OTHER)) \
<< "Vulkan snapshot save failed at " << __func__ << " (" << __FILE__ << ":" \
<< __LINE__ << ")"; \
}
void saveImageContent(android::base::Stream* stream, StateBlock* stateBlock, VkImage image,
const ImageInfo* imageInfo) {
VkEmulation* vkEmulation = getGlobalVkEmulation();
VulkanDispatch* dispatch = vkEmulation->dvk;
const VkImageCreateInfo& imageCreateInfo = imageInfo->imageCreateInfoShallow;
VkCommandBufferAllocateInfo allocInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = vkEmulation->commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer commandBuffer;
_RUN_AND_CHECK(dispatch->vkAllocateCommandBuffers(stateBlock->device, &allocInfo,
&commandBuffer) != VK_SUCCESS);
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
};
VkFence fence;
_RUN_AND_CHECK(dispatch->vkCreateFence(stateBlock->device, &fenceCreateInfo, nullptr, &fence));
VkBufferCreateInfo bufferCreateInfo = {
.size = static_cast<VkDeviceSize>(
imageCreateInfo.extent.width * imageCreateInfo.extent.height *
imageCreateInfo.extent.depth * bytes_per_pixel(imageCreateInfo.format)),
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VkBuffer readbackBuffer;
_RUN_AND_CHECK(
dispatch->vkCreateBuffer(stateBlock->device, &bufferCreateInfo, nullptr, &readbackBuffer));
VkMemoryRequirements readbackBufferMemoryRequirements{};
dispatch->vkGetBufferMemoryRequirements(stateBlock->device, readbackBuffer,
&readbackBufferMemoryRequirements);
const auto readbackBufferMemoryType =
GetMemoryType(*stateBlock->physicalDeviceInfo, readbackBufferMemoryRequirements,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
// Staging memory
// TODO(b/323064243): reuse staging memory
VkMemoryAllocateInfo readbackBufferMemoryAllocateInfo = {
.allocationSize = readbackBufferMemoryRequirements.size,
.memoryTypeIndex = readbackBufferMemoryType,
};
VkDeviceMemory readbackMemory;
_RUN_AND_CHECK(dispatch->vkAllocateMemory(stateBlock->device, &readbackBufferMemoryAllocateInfo,
nullptr, &readbackMemory));
_RUN_AND_CHECK(
dispatch->vkBindBufferMemory(stateBlock->device, readbackBuffer, readbackMemory, 0));
void* mapped = nullptr;
_RUN_AND_CHECK(dispatch->vkMapMemory(stateBlock->device, readbackMemory, 0, VK_WHOLE_SIZE,
VkMemoryMapFlags{}, &mapped));
for (uint32_t mipLevel = 0; mipLevel < imageInfo->imageCreateInfoShallow.mipLevels;
mipLevel++) {
for (uint32_t arrayLayer = 0; arrayLayer < imageInfo->imageCreateInfoShallow.arrayLayers;
arrayLayer++) {
// TODO(b/323064243): reuse command buffers
VkCommandBufferBeginInfo beginInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
if (dispatch->vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
GFXSTREAM_ABORT(emugl::FatalError(emugl::ABORT_REASON_OTHER))
<< "Failed to start command buffer on snapshot save";
}
// TODO(b/323059453): get the right aspect
VkExtent3D mipmapExtent = getMipmapExtent(imageCreateInfo.extent, mipLevel);
VkImageAspectFlags aspects = VK_IMAGE_ASPECT_COLOR_BIT;
VkImageLayout layoutBeforeSave = imageInfo->layout;
VkImageMemoryBarrier imgMemoryBarrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = static_cast<VkAccessFlags>(~VK_ACCESS_NONE_KHR),
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = layoutBeforeSave,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange = VkImageSubresourceRange{.aspectMask = aspects,
.baseMipLevel = mipLevel,
.levelCount = 1,
.baseArrayLayer = arrayLayer,
.layerCount = 1}};
dispatch->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &imgMemoryBarrier);
VkBufferImageCopy region{
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = VkImageSubresourceLayers{.aspectMask = aspects,
.mipLevel = mipLevel,
.baseArrayLayer = arrayLayer,
.layerCount = 1},
.imageOffset =
VkOffset3D{
.x = 0,
.y = 0,
.z = 0,
},
.imageExtent = mipmapExtent,
};
dispatch->vkCmdCopyImageToBuffer(commandBuffer, image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, readbackBuffer,
1, &region);
imgMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
imgMemoryBarrier.newLayout = layoutBeforeSave;
imgMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
imgMemoryBarrier.dstAccessMask = ~VK_ACCESS_NONE_KHR;
dispatch->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &imgMemoryBarrier);
_RUN_AND_CHECK(dispatch->vkEndCommandBuffer(commandBuffer));
// Execute the command to copy image
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer,
};
_RUN_AND_CHECK(dispatch->vkQueueSubmit(stateBlock->queue, 1, &submitInfo, fence));
_RUN_AND_CHECK(
dispatch->vkWaitForFences(stateBlock->device, 1, &fence, VK_TRUE, 3000000000L));
_RUN_AND_CHECK(dispatch->vkResetFences(stateBlock->device, 1, &fence));
size_t bytes = mipmapExtent.width * mipmapExtent.height * mipmapExtent.depth *
bytes_per_pixel(imageCreateInfo.format);
stream->putBe64(bytes);
stream->write(mapped, bytes);
}
}
dispatch->vkDestroyFence(stateBlock->device, fence, nullptr);
dispatch->vkUnmapMemory(stateBlock->device, readbackMemory);
dispatch->vkFreeMemory(stateBlock->device, readbackMemory, nullptr);
}
void loadImageContent(android::base::Stream* stream, StateBlock* stateBlock, VkImage image,
const ImageInfo* imageInfo) {
VkEmulation* vkEmulation = getGlobalVkEmulation();
VulkanDispatch* dispatch = vkEmulation->dvk;
const VkImageCreateInfo& imageCreateInfo = imageInfo->imageCreateInfoShallow;
VkCommandBufferAllocateInfo allocInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = vkEmulation->commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
VkCommandBuffer commandBuffer;
_RUN_AND_CHECK(dispatch->vkAllocateCommandBuffers(stateBlock->device, &allocInfo,
&commandBuffer) != VK_SUCCESS);
VkFenceCreateInfo fenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
};
VkFence fence;
_RUN_AND_CHECK(dispatch->vkCreateFence(stateBlock->device, &fenceCreateInfo, nullptr, &fence));
VkBufferCreateInfo bufferCreateInfo = {
.size = static_cast<VkDeviceSize>(
imageCreateInfo.extent.width * imageCreateInfo.extent.height *
imageCreateInfo.extent.depth * bytes_per_pixel(imageCreateInfo.format)),
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
VkBuffer stagingBuffer;
_RUN_AND_CHECK(
dispatch->vkCreateBuffer(stateBlock->device, &bufferCreateInfo, nullptr, &stagingBuffer));
VkMemoryRequirements stagingBufferMemoryRequirements{};
dispatch->vkGetBufferMemoryRequirements(stateBlock->device, stagingBuffer,
&stagingBufferMemoryRequirements);
const auto stagingBufferMemoryType =
GetMemoryType(*stateBlock->physicalDeviceInfo, stagingBufferMemoryRequirements,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
// Staging memory
// TODO(b/323064243): reuse staging memory
VkMemoryAllocateInfo stagingBufferMemoryAllocateInfo = {
.allocationSize = stagingBufferMemoryRequirements.size,
.memoryTypeIndex = stagingBufferMemoryType,
};
VkDeviceMemory stagingMemory;
_RUN_AND_CHECK(dispatch->vkAllocateMemory(stateBlock->device, &stagingBufferMemoryAllocateInfo,
nullptr, &stagingMemory));
_RUN_AND_CHECK(
dispatch->vkBindBufferMemory(stateBlock->device, stagingBuffer, stagingMemory, 0));
void* mapped = nullptr;
_RUN_AND_CHECK(dispatch->vkMapMemory(stateBlock->device, stagingMemory, 0, VK_WHOLE_SIZE,
VkMemoryMapFlags{}, &mapped));
for (uint32_t mipLevel = 0; mipLevel < imageInfo->imageCreateInfoShallow.mipLevels;
mipLevel++) {
for (uint32_t arrayLayer = 0; arrayLayer < imageInfo->imageCreateInfoShallow.arrayLayers;
arrayLayer++) {
// TODO(b/323064243): reuse command buffers
VkCommandBufferBeginInfo beginInfo{
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
if (dispatch->vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
GFXSTREAM_ABORT(emugl::FatalError(emugl::ABORT_REASON_OTHER))
<< "Failed to start command buffer on snapshot save";
}
VkExtent3D mipmapExtent = getMipmapExtent(imageCreateInfo.extent, mipLevel);
size_t bytes = stream->getBe64();
stream->read(mapped, bytes);
// TODO(b/323059453): get the right aspect
VkImageAspectFlags aspects = VK_IMAGE_ASPECT_COLOR_BIT;
VkImageMemoryBarrier imgMemoryBarrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = static_cast<VkAccessFlags>(~VK_ACCESS_NONE_KHR),
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image,
.subresourceRange = VkImageSubresourceRange{.aspectMask = aspects,
.baseMipLevel = mipLevel,
.levelCount = 1,
.baseArrayLayer = arrayLayer,
.layerCount = 1}};
dispatch->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &imgMemoryBarrier);
VkBufferImageCopy region{
.bufferOffset = 0,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource = VkImageSubresourceLayers{.aspectMask = aspects,
.mipLevel = mipLevel,
.baseArrayLayer = arrayLayer,
.layerCount = 1},
.imageOffset =
VkOffset3D{
.x = 0,
.y = 0,
.z = 0,
},
.imageExtent = mipmapExtent,
};
dispatch->vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, image,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 1, &region);
imgMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
imgMemoryBarrier.newLayout = imageInfo->layout;
imgMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
imgMemoryBarrier.dstAccessMask = ~VK_ACCESS_NONE_KHR;
dispatch->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0,
nullptr, 1, &imgMemoryBarrier);
_RUN_AND_CHECK(dispatch->vkEndCommandBuffer(commandBuffer));
// Execute the command to copy image
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer,
};
_RUN_AND_CHECK(dispatch->vkQueueSubmit(stateBlock->queue, 1, &submitInfo, fence));
_RUN_AND_CHECK(
dispatch->vkWaitForFences(stateBlock->device, 1, &fence, VK_TRUE, 3000000000L));
_RUN_AND_CHECK(dispatch->vkResetFences(stateBlock->device, 1, &fence));
}
}
dispatch->vkDestroyFence(stateBlock->device, fence, nullptr);
dispatch->vkUnmapMemory(stateBlock->device, stagingMemory);
dispatch->vkFreeMemory(stateBlock->device, stagingMemory, nullptr);
}
} // namespace vk
} // namespace gfxstream