Read/Update ColorBuffers from VK when using guest ANGLE
... to avoid the need for any GL operations.
Bug: b/229145718
Test: `launch_cvd --gpu_mode=gfxstream`
Test: `launch_cvd --gpu_mode=gfxstream` with WIP ANGLE
Change-Id: I8fec6a702aac9b0047318887fc7dfcf3f20c2e3c
diff --git a/stream-servers/CMakeLists.txt b/stream-servers/CMakeLists.txt
index f7727aa..ea0fb62 100644
--- a/stream-servers/CMakeLists.txt
+++ b/stream-servers/CMakeLists.txt
@@ -229,6 +229,7 @@
tests/DisplayVk_unittest.cpp
tests/VirtioGpuTimelines_unittest.cpp
vulkan/vk_util_unittest.cpp
+ vulkan/VkFormatUtils_unittest.cpp
vulkan/VkQsriTimeline_unittest.cpp
vulkan/VkDecoderGlobalState_unittest.cpp
)
diff --git a/stream-servers/FrameBuffer.cpp b/stream-servers/FrameBuffer.cpp
index 8d4ed4b..09007e8 100644
--- a/stream-servers/FrameBuffer.cpp
+++ b/stream-servers/FrameBuffer.cpp
@@ -2205,6 +2205,11 @@
GLenum format,
GLenum type,
void* pixels) {
+ if (m_guestUsesAngle) {
+ goldfish_vk::readColorBufferToBytes(p_colorbuffer, x, y, width, height, pixels);
+ return;
+ }
+
AutoLock mutex(m_lock);
ColorBufferPtr colorBuffer = findColorBuffer(p_colorbuffer);
@@ -2223,6 +2228,11 @@
int height,
void* pixels,
uint32_t pixels_size) {
+ if (m_guestUsesAngle) {
+ goldfish_vk::readColorBufferToBytes(p_colorbuffer, x, y, width, height, pixels);
+ return;
+ }
+
AutoLock mutex(m_lock);
ColorBufferPtr colorBuffer = findColorBuffer(p_colorbuffer);
@@ -2334,6 +2344,10 @@
return false;
}
+ if (m_guestUsesAngle) {
+ return goldfish_vk::updateColorBufferFromBytes(p_colorbuffer, x, y, width, height, pixels);
+ }
+
AutoLock mutex(m_lock);
ColorBufferPtr colorBuffer = findColorBuffer(p_colorbuffer);
@@ -2735,7 +2749,7 @@
bool FrameBuffer::post(HandleType p_colorbuffer, bool needLockAndBind) {
if (m_guestUsesAngle) {
- goldfish_vk::updateColorBufferFromVkImage(p_colorbuffer);
+ goldfish_vk::updateColorBufferFromGl(p_colorbuffer);
}
bool res = postImpl(p_colorbuffer, needLockAndBind);
diff --git a/stream-servers/RenderControl.cpp b/stream-servers/RenderControl.cpp
index cec056f..abe9bdf 100644
--- a/stream-servers/RenderControl.cpp
+++ b/stream-servers/RenderControl.cpp
@@ -895,8 +895,7 @@
}
// Update from Vulkan if necessary
- goldfish_vk::updateColorBufferFromVkImage(
- fb->getWindowSurfaceColorBufferHandle(windowSurface));
+ goldfish_vk::readColorBufferToGl(fb->getWindowSurfaceColorBufferHandle(windowSurface));
if (!fb->flushWindowSurfaceColorBuffer(windowSurface)) {
GRSYNC_DPRINT("unlock gralloc cb lock }");
@@ -904,8 +903,7 @@
}
// Update to Vulkan if necessary
- goldfish_vk::updateVkImageFromColorBuffer(
- fb->getWindowSurfaceColorBufferHandle(windowSurface));
+ goldfish_vk::updateColorBufferFromGl(fb->getWindowSurfaceColorBufferHandle(windowSurface));
GRSYNC_DPRINT("unlock gralloc cb lock }");
@@ -971,7 +969,7 @@
}
// Update from Vulkan if necessary
- goldfish_vk::updateColorBufferFromVkImage(colorBuffer);
+ goldfish_vk::readColorBufferToGl(colorBuffer);
fb->post(colorBuffer);
}
@@ -989,7 +987,7 @@
}
// Update from Vulkan if necessary
- goldfish_vk::updateColorBufferFromVkImage(colorBuffer);
+ goldfish_vk::readColorBufferToGl(colorBuffer);
fb->bindColorBufferToTexture(colorBuffer);
}
@@ -1002,7 +1000,7 @@
}
// Update from Vulkan if necessary
- goldfish_vk::updateColorBufferFromVkImage(colorBuffer);
+ goldfish_vk::readColorBufferToGl(colorBuffer);
fb->bindColorBufferToRenderbuffer(colorBuffer);
}
@@ -1028,7 +1026,7 @@
}
// Update from Vulkan if necessary
- goldfish_vk::updateColorBufferFromVkImage(colorBuffer);
+ goldfish_vk::readColorBufferToGl(colorBuffer);
fb->readColorBuffer(colorBuffer, x, y, width, height, format, type, pixels);
}
@@ -1048,7 +1046,7 @@
// Since this is a modify operation, also read the current contents
// of the VkImage, if any.
- goldfish_vk::updateColorBufferFromVkImage(colorBuffer);
+ goldfish_vk::readColorBufferToGl(colorBuffer);
fb->updateColorBuffer(colorBuffer, x, y, width, height, format, type, pixels);
@@ -1056,7 +1054,7 @@
sGrallocSync()->unlockColorBufferPrepare();
// Update to Vulkan if necessary
- goldfish_vk::updateVkImageFromColorBuffer(colorBuffer);
+ goldfish_vk::updateColorBufferFromGl(colorBuffer);
return 0;
}
@@ -1077,7 +1075,7 @@
// Since this is a modify operation, also read the current contents
// of the VkImage, if any.
- goldfish_vk::updateColorBufferFromVkImage(colorBuffer);
+ goldfish_vk::readColorBufferToGl(colorBuffer);
fb->updateColorBuffer(colorBuffer, x, y, width, height,
format, type, pixels);
@@ -1086,7 +1084,7 @@
sGrallocSync()->unlockColorBufferPrepare();
// Update to Vulkan if necessary
- goldfish_vk::updateVkImageFromColorBuffer(colorBuffer);
+ goldfish_vk::updateColorBufferFromGl(colorBuffer);
return 0;
}
@@ -1553,7 +1551,7 @@
}
// Update from Vulkan if necessary
- goldfish_vk::updateColorBufferFromVkImage(colorBuffer);
+ goldfish_vk::readColorBufferToGl(colorBuffer);
fb->readColorBuffer(colorBuffer, x, y, width, height, format, type, pixels);
return 0;
diff --git a/stream-servers/vulkan/Android.bp b/stream-servers/vulkan/Android.bp
index 41cf32e..7303067 100644
--- a/stream-servers/vulkan/Android.bp
+++ b/stream-servers/vulkan/Android.bp
@@ -14,6 +14,7 @@
static_libs: [
"gfxstream_base",
"gfxstream_compressedTextures",
+ "gfxstream_host_common",
"gfxstream_apigen_codec_common",
"gfxstream_vulkan_cereal_host",
],
@@ -39,6 +40,7 @@
"VkDecoder.cpp",
"VkDecoderGlobalState.cpp",
"VkDecoderSnapshot.cpp",
+ "VkFormatUtils.cpp",
"VkReconstruction.cpp",
"VulkanDispatch.cpp",
"VulkanHandleMapping.cpp",
@@ -52,3 +54,25 @@
"VkDecoderGlobalState.cpp", // took more than 400 seconds
],
}
+
+// Run with `atest --host gfxstream_vkformatutils_tests`
+cc_test_host {
+ name: "gfxstream_vkformatutils_tests",
+ defaults: [ "gfxstream_defaults" ],
+ srcs: [
+ "VkFormatUtils_unittest.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ ],
+ static_libs: [
+ "gfxstream_host_common",
+ "gfxstream_vulkan_server",
+ "libgtest",
+ "libgmock",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+}
\ No newline at end of file
diff --git a/stream-servers/vulkan/CMakeLists.txt b/stream-servers/vulkan/CMakeLists.txt
index 8d209f0..48f2247 100644
--- a/stream-servers/vulkan/CMakeLists.txt
+++ b/stream-servers/vulkan/CMakeLists.txt
@@ -10,6 +10,7 @@
VkDecoder.cpp
VkDecoderGlobalState.cpp
VkDecoderSnapshot.cpp
+ VkFormatUtils.cpp
VkReconstruction.cpp
VulkanDispatch.cpp
VulkanHandleMapping.cpp
diff --git a/stream-servers/vulkan/VkCommonOperations.cpp b/stream-servers/vulkan/VkCommonOperations.cpp
index 2af304b..cfc1c42 100644
--- a/stream-servers/vulkan/VkCommonOperations.cpp
+++ b/stream-servers/vulkan/VkCommonOperations.cpp
@@ -26,6 +26,7 @@
#include <unordered_set>
#include "FrameBuffer.h"
+#include "VkFormatUtils.h"
#include "VulkanDispatch.h"
#include "base/Lock.h"
#include "base/Lookup.h"
@@ -1849,332 +1850,407 @@
return res;
}
-bool updateColorBufferFromVkImage(uint32_t colorBufferHandle) {
- if (!sVkEmulation || !sVkEmulation->live) return false;
-
- auto vk = sVkEmulation->dvk;
-
- AutoLock lock(sVkEmulationLock);
-
- auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
-
- if (!infoPtr) {
- // Color buffer not found; this is usually OK.
+bool colorBufferNeedsTransferBetweenGlAndVk(const VkEmulation::ColorBufferInfo& colorBufferInfo) {
+ // GL is not used.
+ if (colorBufferInfo.vulkanMode == VkEmulation::VulkanMode::VulkanOnly) {
return false;
}
- if (!infoPtr->image) {
- fprintf(stderr, "%s: error: ColorBuffer 0x%x has no VkImage\n", __func__,
- colorBufferHandle);
- return false;
- }
-
- if (infoPtr->glExported || (infoPtr->vulkanMode == VkEmulation::VulkanMode::VulkanOnly) ||
- infoPtr->frameworkFormat != FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE) {
- // No sync needed if exported to GL or in Vulkan-only mode
+ // YUV formats require extra conversions.
+ if (colorBufferInfo.frameworkFormat != FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE) {
return true;
}
- // Record our synchronization commands.
- VkCommandBufferBeginInfo beginInfo = {
- VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
- 0,
- VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
- nullptr /* no inheritance info */,
- };
-
- vk->vkBeginCommandBuffer(sVkEmulation->commandBuffer, &beginInfo);
-
- // From the spec: If an application does not need the contents of a resource
- // to remain valid when transferring from one queue family to another, then
- // the ownership transfer should be skipped.
-
- // We definitely need to transition the image to
- // VK_TRANSFER_SRC_OPTIMAL and back.
-
- VkImageMemoryBarrier presentToTransferSrc = {
- VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
- 0,
- 0,
- VK_ACCESS_HOST_READ_BIT,
- infoPtr->currentLayout,
- VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
- VK_QUEUE_FAMILY_IGNORED,
- VK_QUEUE_FAMILY_IGNORED,
- infoPtr->image,
- {
- VK_IMAGE_ASPECT_COLOR_BIT,
- 0,
- 1,
- 0,
- 1,
- },
- };
-
- vk->vkCmdPipelineBarrier(sVkEmulation->commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
- VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1,
- &presentToTransferSrc);
-
- infoPtr->currentLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
-
- // Copy to staging buffer
- uint32_t bpp = 4; /* format always rgba8...not */
- switch (infoPtr->imageCreateInfoShallow.format) {
- case VK_FORMAT_R5G6B5_UNORM_PACK16:
- bpp = 2;
- break;
- case VK_FORMAT_R8G8B8_UNORM:
- bpp = 3;
- break;
- default:
- case VK_FORMAT_R8G8B8A8_UNORM:
- bpp = 4;
- break;
+ // GL and VK are sharing the same underlying memory.
+ if (colorBufferInfo.glExported) {
+ return false;
}
- VkBufferImageCopy region = {
- 0 /* buffer offset */,
- infoPtr->imageCreateInfoShallow.extent.width,
- infoPtr->imageCreateInfoShallow.extent.height,
- {
- VK_IMAGE_ASPECT_COLOR_BIT,
- 0,
- 0,
- 1,
- },
- {0, 0, 0},
- infoPtr->imageCreateInfoShallow.extent,
- };
-
- vk->vkCmdCopyImageToBuffer(sVkEmulation->commandBuffer, infoPtr->image,
- VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, sVkEmulation->staging.buffer,
- 1, ®ion);
-
- vk->vkEndCommandBuffer(sVkEmulation->commandBuffer);
-
- VkSubmitInfo submitInfo = {
- VK_STRUCTURE_TYPE_SUBMIT_INFO, 0, 0, nullptr, nullptr, 1,
- &sVkEmulation->commandBuffer, 0, nullptr,
- };
-
- {
- android::base::AutoLock lock(*sVkEmulation->queueLock);
- vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo, sVkEmulation->commandBufferFence);
- }
-
- static constexpr uint64_t ANB_MAX_WAIT_NS = 5ULL * 1000ULL * 1000ULL * 1000ULL;
-
- vk->vkWaitForFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence, VK_TRUE,
- ANB_MAX_WAIT_NS);
- vk->vkResetFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence);
-
- VkMappedMemoryRange toInvalidate = {
- VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
- 0,
- sVkEmulation->staging.memory.memory,
- 0,
- VK_WHOLE_SIZE,
- };
-
- vk->vkInvalidateMappedMemoryRanges(sVkEmulation->device, 1, &toInvalidate);
-
- const std::size_t copiedSize = infoPtr->imageCreateInfoShallow.extent.width *
- infoPtr->imageCreateInfoShallow.extent.height * bpp;
-
- FrameBuffer::getFB()->replaceColorBufferContents(
- colorBufferHandle, sVkEmulation->staging.memory.mappedPtr, copiedSize);
return true;
}
-bool updateVkImageFromColorBuffer(uint32_t colorBufferHandle) {
- if (!sVkEmulation || !sVkEmulation->live) return false;
+bool readColorBufferToGl(uint32_t colorBufferHandle) {
+ if (!sVkEmulation || !sVkEmulation->live) {
+ VK_COMMON_ERROR("VkEmulation not available.");
+ return false;
+ }
auto vk = sVkEmulation->dvk;
AutoLock lock(sVkEmulationLock);
- auto infoPtr = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
-
- if (!infoPtr) {
- // Color buffer not found; this is usually OK.
+ auto colorBufferInfo = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+ if (!colorBufferInfo) {
+ VK_COMMON_ERROR("Failed to read from ColorBuffer:%d, not found.", colorBufferHandle);
return false;
}
- if (infoPtr->frameworkFormat == FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE &&
- (infoPtr->glExported || infoPtr->vulkanMode == VkEmulation::VulkanMode::VulkanOnly)) {
- // No sync needed if exported to GL or in Vulkan-only mode
+ if (!colorBufferNeedsTransferBetweenGlAndVk(*colorBufferInfo)) {
return true;
}
- size_t cbNumBytes = 0;
- bool readRes =
- FrameBuffer::getFB()->readColorBufferContents(colorBufferHandle, &cbNumBytes, nullptr);
- if (!readRes) {
- fprintf(stderr, "%s: Failed to read color buffer 0x%x\n", __func__, colorBufferHandle);
+ VkDeviceSize bytesNeeded = 0;
+ bool result = getFormatTransferInfo(colorBufferInfo->imageCreateInfoShallow.format,
+ colorBufferInfo->imageCreateInfoShallow.extent.width,
+ colorBufferInfo->imageCreateInfoShallow.extent.height,
+ &bytesNeeded, nullptr);
+ if (!result) {
+ VK_COMMON_ERROR("Failed to read from ColorBuffer:%d, failed to get read size.",
+ colorBufferHandle);
return false;
}
- if (cbNumBytes > sVkEmulation->staging.memory.size) {
- fprintf(stderr,
- "%s: Not enough space to read to staging buffer. "
- "Wanted: 0x%llx Have: 0x%llx\n",
- __func__, (unsigned long long)cbNumBytes,
- (unsigned long long)(sVkEmulation->staging.memory.size));
+ std::vector<uint8_t> bytes(bytesNeeded);
+
+ result = readColorBufferToBytes(
+ colorBufferHandle, 0, 0, colorBufferInfo->imageCreateInfoShallow.extent.width,
+ colorBufferInfo->imageCreateInfoShallow.extent.height, bytes.data());
+ if (!result) {
+ VK_COMMON_ERROR("Failed to read from ColorBuffer:%d, failed to get read size.",
+ colorBufferHandle);
return false;
}
- readRes = FrameBuffer::getFB()->readColorBufferContents(colorBufferHandle, &cbNumBytes,
- sVkEmulation->staging.memory.mappedPtr);
+ return FrameBuffer::getFB()->replaceColorBufferContents(colorBufferHandle, bytes.data(),
+ bytes.size());
+}
- if (!readRes) {
- fprintf(stderr, "%s: Failed to read color buffer 0x%x (at glReadPixels)\n", __func__,
- colorBufferHandle);
+bool readColorBufferToBytes(uint32_t colorBufferHandle, uint32_t x, uint32_t y, uint32_t w,
+ uint32_t h, void* outPixels) {
+ if (!sVkEmulation || !sVkEmulation->live) {
+ VK_COMMON_ERROR("VkEmulation not available.");
return false;
}
+ auto vk = sVkEmulation->dvk;
+
+ AutoLock lock(sVkEmulationLock);
+ return readColorBufferToBytesLocked(colorBufferHandle, x, y, w, h, outPixels);
+}
+
+bool readColorBufferToBytesLocked(uint32_t colorBufferHandle, uint32_t x, uint32_t y, uint32_t w,
+ uint32_t h, void* outPixels) {
+ if (!sVkEmulation || !sVkEmulation->live) {
+ VK_COMMON_ERROR("VkEmulation not available.");
+ return false;
+ }
+
+ auto vk = sVkEmulation->dvk;
+
+ auto colorBufferInfo = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+ if (!colorBufferInfo) {
+ VK_COMMON_ERROR("Failed to read from ColorBuffer:%d, not found.", colorBufferHandle);
+ return false;
+ }
+
+ if (!colorBufferInfo->image) {
+ VK_COMMON_ERROR("Failed to read from ColorBuffer:%d, no VkImage.", colorBufferHandle);
+ return false;
+ }
+
+ if (x != 0 || y != 0 || w != colorBufferInfo->imageCreateInfoShallow.extent.width ||
+ h != colorBufferInfo->imageCreateInfoShallow.extent.height) {
+ VK_COMMON_ERROR("Failed to read from ColorBuffer:%d, unhandled subrect.",
+ colorBufferHandle);
+ return false;
+ }
+
+ std::size_t bufferCopySize = 0;
+ std::vector<VkBufferImageCopy> bufferImageCopies;
+ if (!getFormatTransferInfo(colorBufferInfo->imageCreateInfoShallow.format,
+ colorBufferInfo->imageCreateInfoShallow.extent.width,
+ colorBufferInfo->imageCreateInfoShallow.extent.height,
+ &bufferCopySize, &bufferImageCopies)) {
+ VK_COMMON_ERROR("Failed to read ColorBuffer:%d, unable to get transfer info.",
+ colorBufferHandle);
+ return false;
+ }
+
+ // Avoid transitioning from VK_IMAGE_LAYOUT_UNDEFINED. Unfortunetly, Android does not
+ // yet have a mechanism for sharing the expected VkImageLayout. However, the Vulkan
+ // spec's image layout transition sections says "If the old layout is
+ // VK_IMAGE_LAYOUT_UNDEFINED, the contents of that range may be discarded." Some
+ // Vulkan drivers have been observed to actually perform the discard which leads to
+ // ColorBuffer-s being unintentionally cleared. See go/ahb-vkimagelayout for a more
+ // thorough write up.
+ if (colorBufferInfo->currentLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
+ colorBufferInfo->currentLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
+ }
+
// Record our synchronization commands.
- VkCommandBufferBeginInfo beginInfo = {
- VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
- 0,
- VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
- nullptr /* no inheritance info */,
+ const VkCommandBufferBeginInfo beginInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = nullptr,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
- vk->vkBeginCommandBuffer(sVkEmulation->commandBuffer, &beginInfo);
+ VkCommandBuffer commandBuffer = sVkEmulation->commandBuffer;
- // From the spec: If an application does not need the contents of a resource
- // to remain valid when transferring from one queue family to another, then
- // the ownership transfer should be skipped.
+ VK_CHECK(vk->vkBeginCommandBuffer(commandBuffer, &beginInfo));
- // We definitely need to transition the image to
- // VK_TRANSFER_SRC_OPTIMAL and back.
-
- VkImageMemoryBarrier presentToTransferSrc = {
- VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
- 0,
- 0,
- VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
- infoPtr->currentLayout,
- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
- VK_QUEUE_FAMILY_IGNORED,
- VK_QUEUE_FAMILY_IGNORED,
- infoPtr->image,
- {
- VK_IMAGE_ASPECT_COLOR_BIT,
- 0,
- 1,
- 0,
- 1,
- },
+ const VkImageMemoryBarrier toTransferSrcImageBarrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_HOST_READ_BIT,
+ .oldLayout = colorBufferInfo->currentLayout,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = colorBufferInfo->image,
+ .subresourceRange =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
};
- infoPtr->currentLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
-
- vk->vkCmdPipelineBarrier(sVkEmulation->commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1,
- &presentToTransferSrc);
+ &toTransferSrcImageBarrier);
- // Copy to staging buffer
- std::vector<VkBufferImageCopy> regions;
- if (infoPtr->frameworkFormat == FrameworkFormat::FRAMEWORK_FORMAT_GL_COMPATIBLE) {
- regions.push_back({
- 0 /* buffer offset */,
- infoPtr->imageCreateInfoShallow.extent.width,
- infoPtr->imageCreateInfoShallow.extent.height,
- {
- VK_IMAGE_ASPECT_COLOR_BIT,
- 0,
- 0,
- 1,
- },
- {0, 0, 0},
- infoPtr->imageCreateInfoShallow.extent,
- });
- } else {
- // YUV formats
- bool swapUV = infoPtr->frameworkFormat == FRAMEWORK_FORMAT_YV12;
- VkExtent3D subplaneExtent = {infoPtr->imageCreateInfoShallow.extent.width / 2,
- infoPtr->imageCreateInfoShallow.extent.height / 2, 1};
- regions.push_back({
- 0 /* buffer offset */,
- infoPtr->imageCreateInfoShallow.extent.width,
- infoPtr->imageCreateInfoShallow.extent.height,
- {
- VK_IMAGE_ASPECT_PLANE_0_BIT,
- 0,
- 0,
- 1,
- },
- {0, 0, 0},
- infoPtr->imageCreateInfoShallow.extent,
- });
- regions.push_back({
- infoPtr->imageCreateInfoShallow.extent.width *
- infoPtr->imageCreateInfoShallow.extent.height /* buffer offset */,
- subplaneExtent.width,
- subplaneExtent.height,
- {
- (VkImageAspectFlags)(swapUV ? VK_IMAGE_ASPECT_PLANE_2_BIT
- : VK_IMAGE_ASPECT_PLANE_1_BIT),
- 0,
- 0,
- 1,
- },
- {0, 0, 0},
- subplaneExtent,
- });
- if (infoPtr->frameworkFormat == FRAMEWORK_FORMAT_YUV_420_888 ||
- infoPtr->frameworkFormat == FRAMEWORK_FORMAT_YV12) {
- regions.push_back({
- infoPtr->imageCreateInfoShallow.extent.width *
- infoPtr->imageCreateInfoShallow.extent.height +
- subplaneExtent.width * subplaneExtent.height,
- subplaneExtent.width,
- subplaneExtent.height,
- {
- (VkImageAspectFlags)(swapUV ? VK_IMAGE_ASPECT_PLANE_1_BIT
- : VK_IMAGE_ASPECT_PLANE_2_BIT),
- 0,
- 0,
- 1,
- },
- {0, 0, 0},
- subplaneExtent,
- });
- }
- }
+ colorBufferInfo->currentLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
- vk->vkCmdCopyBufferToImage(sVkEmulation->commandBuffer, sVkEmulation->staging.buffer,
- infoPtr->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, regions.size(),
- regions.data());
+ vk->vkCmdCopyImageToBuffer(commandBuffer, colorBufferInfo->image,
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, sVkEmulation->staging.buffer,
+ bufferImageCopies.size(), bufferImageCopies.data());
- vk->vkEndCommandBuffer(sVkEmulation->commandBuffer);
+ VK_CHECK(vk->vkEndCommandBuffer(commandBuffer));
- VkSubmitInfo submitInfo = {
- VK_STRUCTURE_TYPE_SUBMIT_INFO, 0, 0, nullptr, nullptr, 1,
- &sVkEmulation->commandBuffer, 0, nullptr,
+ const VkSubmitInfo submitInfo = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .pNext = nullptr,
+ .waitSemaphoreCount = 0,
+ .pWaitSemaphores = nullptr,
+ .pWaitDstStageMask = nullptr,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &commandBuffer,
+ .signalSemaphoreCount = 0,
+ .pSignalSemaphores = nullptr,
};
{
android::base::AutoLock lock(*sVkEmulation->queueLock);
- vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo, sVkEmulation->commandBufferFence);
+ VK_CHECK(vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo,
+ sVkEmulation->commandBufferFence));
}
static constexpr uint64_t ANB_MAX_WAIT_NS = 5ULL * 1000ULL * 1000ULL * 1000ULL;
- vk->vkWaitForFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence, VK_TRUE,
- ANB_MAX_WAIT_NS);
- vk->vkResetFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence);
+ VK_CHECK(vk->vkWaitForFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence,
+ VK_TRUE, ANB_MAX_WAIT_NS));
- VkMappedMemoryRange toInvalidate = {
- VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
- 0,
- sVkEmulation->staging.memory.memory,
- 0,
- VK_WHOLE_SIZE,
+ VK_CHECK(vk->vkResetFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence));
+
+ const VkMappedMemoryRange toInvalidate = {
+ .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+ .pNext = nullptr,
+ .memory = sVkEmulation->staging.memory.memory,
+ .offset = 0,
+ .size = VK_WHOLE_SIZE,
};
- vk->vkInvalidateMappedMemoryRanges(sVkEmulation->device, 1, &toInvalidate);
+ VK_CHECK(vk->vkInvalidateMappedMemoryRanges(sVkEmulation->device, 1, &toInvalidate));
+
+ const auto* stagingBufferPtr = sVkEmulation->staging.memory.mappedPtr;
+ std::memcpy(outPixels, stagingBufferPtr, bufferCopySize);
+
+ return true;
+}
+
+bool updateColorBufferFromGl(uint32_t colorBufferHandle) {
+ if (!sVkEmulation || !sVkEmulation->live) {
+ VK_COMMON_ERROR("VkEmulation not available.");
+ return false;
+ }
+
+ AutoLock lock(sVkEmulationLock);
+
+ auto colorBufferInfo = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+ if (!colorBufferInfo) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, not found.", colorBufferHandle);
+ return false;
+ }
+
+ if (!colorBufferNeedsTransferBetweenGlAndVk(*colorBufferInfo)) {
+ return true;
+ }
+
+ size_t bytesNeeded = 0;
+ bool result =
+ FrameBuffer::getFB()->readColorBufferContents(colorBufferHandle, &bytesNeeded, nullptr);
+ if (!result) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, failed to get read contents size.",
+ colorBufferHandle);
+ return false;
+ }
+
+ std::vector<uint8_t> bytes(bytesNeeded);
+ result = FrameBuffer::getFB()->readColorBufferContents(colorBufferHandle, &bytesNeeded,
+ bytes.data());
+ if (!result) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, failed to read contents.",
+ colorBufferHandle);
+ return false;
+ }
+
+ return updateColorBufferFromBytesLocked(
+ colorBufferHandle, 0, 0, colorBufferInfo->imageCreateInfoShallow.extent.width,
+ colorBufferInfo->imageCreateInfoShallow.extent.height, bytes.data());
+}
+
+bool updateColorBufferFromBytes(uint32_t colorBufferHandle, uint32_t x, uint32_t y, uint32_t w,
+ uint32_t h, const void* pixels) {
+ if (!sVkEmulation || !sVkEmulation->live) {
+ VK_COMMON_ERROR("VkEmulation not available.");
+ return false;
+ }
+
+ auto vk = sVkEmulation->dvk;
+ AutoLock lock(sVkEmulationLock);
+ return updateColorBufferFromBytesLocked(colorBufferHandle, x, y, w, h, pixels);
+}
+
+bool updateColorBufferFromBytesLocked(uint32_t colorBufferHandle, uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h, const void* pixels) {
+ if (!sVkEmulation || !sVkEmulation->live) {
+ VK_COMMON_ERROR("VkEmulation not available.");
+ return false;
+ }
+
+ auto vk = sVkEmulation->dvk;
+
+ auto colorBufferInfo = android::base::find(sVkEmulation->colorBuffers, colorBufferHandle);
+ if (!colorBufferInfo) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, not found.", colorBufferHandle);
+ return false;
+ }
+
+ if (!colorBufferInfo->image) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, no VkImage.", colorBufferHandle);
+ return false;
+ }
+
+ if (x != 0 || y != 0 || w != colorBufferInfo->imageCreateInfoShallow.extent.width ||
+ h != colorBufferInfo->imageCreateInfoShallow.extent.height) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, unhandled subrect.", colorBufferHandle);
+ return false;
+ }
+
+ std::size_t bufferCopySize = 0;
+ std::vector<VkBufferImageCopy> bufferImageCopies;
+ if (!getFormatTransferInfo(colorBufferInfo->imageCreateInfoShallow.format,
+ colorBufferInfo->imageCreateInfoShallow.extent.width,
+ colorBufferInfo->imageCreateInfoShallow.extent.height,
+ &bufferCopySize, &bufferImageCopies)) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, unable to get transfer info.",
+ colorBufferHandle);
+ return false;
+ }
+
+ const VkDeviceSize stagingBufferSize = sVkEmulation->staging.size;
+ if (bufferCopySize > stagingBufferSize) {
+ VK_COMMON_ERROR("Failed to update ColorBuffer:%d, transfer size %" PRIu64
+ " too large for staging buffer size:%" PRIu64 ".",
+ colorBufferHandle, bufferCopySize, stagingBufferSize);
+ return false;
+ }
+
+ auto* stagingBufferPtr = sVkEmulation->staging.memory.mappedPtr;
+ std::memcpy(stagingBufferPtr, pixels, bufferCopySize);
+
+ // Avoid transitioning from VK_IMAGE_LAYOUT_UNDEFINED. Unfortunetly, Android does not
+ // yet have a mechanism for sharing the expected VkImageLayout. However, the Vulkan
+ // spec's image layout transition sections says "If the old layout is
+ // VK_IMAGE_LAYOUT_UNDEFINED, the contents of that range may be discarded." Some
+ // Vulkan drivers have been observed to actually perform the discard which leads to
+ // ColorBuffer-s being unintentionally cleared. See go/ahb-vkimagelayout for a more
+ // thorough write up.
+ if (colorBufferInfo->currentLayout == VK_IMAGE_LAYOUT_UNDEFINED) {
+ colorBufferInfo->currentLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+ }
+
+ // Record our synchronization commands.
+ const VkCommandBufferBeginInfo beginInfo = {
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = nullptr,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ };
+
+ VkCommandBuffer commandBuffer = sVkEmulation->commandBuffer;
+
+ VK_CHECK(vk->vkBeginCommandBuffer(commandBuffer, &beginInfo));
+
+ const VkImageMemoryBarrier toTransferDstImageBarrier = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_HOST_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ .oldLayout = colorBufferInfo->currentLayout,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = colorBufferInfo->image,
+ .subresourceRange =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ };
+
+ vk->vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, 1,
+ &toTransferDstImageBarrier);
+
+ colorBufferInfo->currentLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
+
+ // Copy to staging buffer
+ vk->vkCmdCopyBufferToImage(commandBuffer, sVkEmulation->staging.buffer, colorBufferInfo->image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, bufferImageCopies.size(),
+ bufferImageCopies.data());
+
+ VK_CHECK(vk->vkEndCommandBuffer(commandBuffer));
+
+ const VkSubmitInfo submitInfo = {
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .pNext = nullptr,
+ .waitSemaphoreCount = 0,
+ .pWaitSemaphores = nullptr,
+ .pWaitDstStageMask = nullptr,
+ .commandBufferCount = 1,
+ .pCommandBuffers = &commandBuffer,
+ .signalSemaphoreCount = 0,
+ .pSignalSemaphores = nullptr,
+ };
+
+ {
+ android::base::AutoLock lock(*sVkEmulation->queueLock);
+ VK_CHECK(vk->vkQueueSubmit(sVkEmulation->queue, 1, &submitInfo,
+ sVkEmulation->commandBufferFence));
+ }
+
+ static constexpr uint64_t ANB_MAX_WAIT_NS = 5ULL * 1000ULL * 1000ULL * 1000ULL;
+
+ VK_CHECK(vk->vkWaitForFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence,
+ VK_TRUE, ANB_MAX_WAIT_NS));
+
+ VK_CHECK(vk->vkResetFences(sVkEmulation->device, 1, &sVkEmulation->commandBufferFence));
+
+ const VkMappedMemoryRange toInvalidate = {
+ .sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE,
+ .pNext = nullptr,
+ .memory = sVkEmulation->staging.memory.memory,
+ .offset = 0,
+ .size = VK_WHOLE_SIZE,
+ };
+ VK_CHECK(vk->vkInvalidateMappedMemoryRanges(sVkEmulation->device, 1, &toInvalidate));
+
return true;
}
diff --git a/stream-servers/vulkan/VkCommonOperations.h b/stream-servers/vulkan/VkCommonOperations.h
index a2a1bd5..2be2a8a 100644
--- a/stream-servers/vulkan/VkCommonOperations.h
+++ b/stream-servers/vulkan/VkCommonOperations.h
@@ -383,13 +383,23 @@
void** mappedPtr = nullptr);
bool teardownVkColorBuffer(uint32_t colorBufferHandle);
VkEmulation::ColorBufferInfo getColorBufferInfo(uint32_t colorBufferHandle);
-bool updateColorBufferFromVkImage(uint32_t colorBufferHandle);
-bool updateVkImageFromColorBuffer(uint32_t colorBufferHandle);
VK_EXT_MEMORY_HANDLE getColorBufferExtMemoryHandle(uint32_t colorBufferHandle);
MTLTextureRef getColorBufferMTLTexture(uint32_t colorBufferHandle);
bool setColorBufferVulkanMode(uint32_t colorBufferHandle, uint32_t vulkanMode);
int32_t mapGpaToBufferHandle(uint32_t bufferHandle, uint64_t gpa, uint64_t size = 0);
+bool readColorBufferToGl(uint32_t colorBufferHandle);
+bool readColorBufferToBytes(uint32_t colorBufferHandle, uint32_t x, uint32_t y, uint32_t w,
+ uint32_t h, void* outPixels);
+bool readColorBufferToBytesLocked(uint32_t colorBufferHandle, uint32_t x, uint32_t y, uint32_t w,
+ uint32_t h, void* outPixels);
+
+bool updateColorBufferFromGl(uint32_t colorBufferHandle);
+bool updateColorBufferFromBytes(uint32_t colorBufferHandle, uint32_t x, uint32_t y, uint32_t w,
+ uint32_t h, const void* pixels);
+bool updateColorBufferFromBytesLocked(uint32_t colorBufferHandle, uint32_t x, uint32_t y,
+ uint32_t w, uint32_t h, const void* pixels);
+
// Data buffer operations
bool setupVkBuffer(uint32_t bufferHandle, bool vulkanOnly = false, uint32_t memoryProperty = 0,
diff --git a/stream-servers/vulkan/VkDecoderGlobalState.cpp b/stream-servers/vulkan/VkDecoderGlobalState.cpp
index 19599a4..9a320f4 100644
--- a/stream-servers/vulkan/VkDecoderGlobalState.cpp
+++ b/stream-servers/vulkan/VkDecoderGlobalState.cpp
@@ -2881,7 +2881,7 @@
&localAllocInfo.allocationSize, &localAllocInfo.memoryTypeIndex, &mappedPtr);
if (!vulkanOnly) {
- updateVkImageFromColorBuffer(importCbInfoPtr->colorBuffer);
+ updateColorBufferFromGl(importCbInfoPtr->colorBuffer);
}
if (m_emu->instanceSupportsExternalMemoryCapabilities) {
diff --git a/stream-servers/vulkan/VkFormatUtils.cpp b/stream-servers/vulkan/VkFormatUtils.cpp
new file mode 100644
index 0000000..7685ea6
--- /dev/null
+++ b/stream-servers/vulkan/VkFormatUtils.cpp
@@ -0,0 +1,193 @@
+// Copyright 2022 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 "VkFormatUtils.h"
+
+#include <unordered_map>
+
+namespace {
+
+struct FormatPlaneLayout {
+ uint32_t horizontalSubsampling = 1;
+ uint32_t verticalSubsampling = 1;
+ uint32_t sampleIncrementBytes = 0;
+ VkImageAspectFlags aspectMask = 0;
+};
+
+struct FormatPlaneLayouts {
+ uint32_t horizontalAlignmentPixels = 1;
+ std::vector<FormatPlaneLayout> planeLayouts;
+};
+
+const std::unordered_map<VkFormat, FormatPlaneLayouts>& getFormatPlaneLayoutsMap() {
+ static const auto* kPlaneLayoutsMap = []() {
+ auto* map = new std::unordered_map<VkFormat, FormatPlaneLayouts>({
+ {VK_FORMAT_G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16,
+ {
+ .horizontalAlignmentPixels = 2,
+ .planeLayouts =
+ {
+ {
+ .horizontalSubsampling = 1,
+ .verticalSubsampling = 1,
+ .sampleIncrementBytes = 2,
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT,
+ },
+ {
+ .horizontalSubsampling = 2,
+ .verticalSubsampling = 2,
+ .sampleIncrementBytes = 4,
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT,
+ },
+ },
+ }},
+ {VK_FORMAT_G8_B8R8_2PLANE_420_UNORM,
+ {
+ .horizontalAlignmentPixels = 2,
+ .planeLayouts =
+ {
+ {
+ .horizontalSubsampling = 1,
+ .verticalSubsampling = 1,
+ .sampleIncrementBytes = 1,
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT,
+ },
+ {
+ .horizontalSubsampling = 2,
+ .verticalSubsampling = 2,
+ .sampleIncrementBytes = 2,
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT,
+ },
+ },
+ }},
+ {VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM,
+ {
+ .horizontalAlignmentPixels = 32,
+ .planeLayouts =
+ {
+ {
+ .horizontalSubsampling = 1,
+ .verticalSubsampling = 1,
+ .sampleIncrementBytes = 1,
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT,
+ },
+ {
+ .horizontalSubsampling = 2,
+ .verticalSubsampling = 2,
+ .sampleIncrementBytes = 1,
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT,
+ },
+ {
+ .horizontalSubsampling = 2,
+ .verticalSubsampling = 2,
+ .sampleIncrementBytes = 1,
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_2_BIT,
+ },
+ },
+ }},
+ });
+
+#define ADD_SINGLE_PLANE_FORMAT_INFO(format, bpp) \
+ (*map)[format] = FormatPlaneLayouts{ \
+ .horizontalAlignmentPixels = 1, \
+ .planeLayouts = \
+ { \
+ { \
+ .horizontalSubsampling = 1, \
+ .verticalSubsampling = 1, \
+ .sampleIncrementBytes = bpp, \
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, \
+ }, \
+ }, \
+ };
+ LIST_VK_FORMATS_LINEAR(ADD_SINGLE_PLANE_FORMAT_INFO)
+#undef ADD_SINGLE_PLANE_FORMAT_INFO
+
+ return map;
+ }();
+ return *kPlaneLayoutsMap;
+}
+
+inline uint32_t alignToPower2(uint32_t val, uint32_t align) {
+ return (val + (align - 1)) & ~(align - 1);
+}
+
+} // namespace
+
+const FormatPlaneLayouts* getFormatPlaneLayouts(VkFormat format) {
+ const auto& formatPlaneLayoutsMap = getFormatPlaneLayoutsMap();
+
+ auto it = formatPlaneLayoutsMap.find(format);
+ if (it == formatPlaneLayoutsMap.end()) {
+ return nullptr;
+ }
+ return &it->second;
+}
+
+bool getFormatTransferInfo(VkFormat format, uint32_t width, uint32_t height,
+ VkDeviceSize* outStagingBufferCopySize,
+ std::vector<VkBufferImageCopy>* outBufferImageCopies) {
+ const FormatPlaneLayouts* formatInfo = getFormatPlaneLayouts(format);
+ if (formatInfo == nullptr) {
+ ERR("Unhandled format: %s", string_VkFormat(format));
+ return false;
+ }
+
+ const uint32_t alignedWidth = alignToPower2(width, formatInfo->horizontalAlignmentPixels);
+ const uint32_t alignedHeight = height;
+ uint32_t cumulativeOffset = 0;
+ uint32_t cumulativeSize = 0;
+ for (const FormatPlaneLayout& planeInfo : formatInfo->planeLayouts) {
+ const uint32_t planeOffset = cumulativeOffset;
+ const uint32_t planeWidth = alignedWidth / planeInfo.horizontalSubsampling;
+ const uint32_t planeHeight = alignedHeight / planeInfo.verticalSubsampling;
+ const uint32_t planeBpp = planeInfo.sampleIncrementBytes;
+ const uint32_t planeStrideTexels = planeWidth;
+ const uint32_t planeStrideBytes = planeStrideTexels * planeBpp;
+ const uint32_t planeSize = planeHeight * planeStrideBytes;
+ if (outBufferImageCopies) {
+ outBufferImageCopies->emplace_back(VkBufferImageCopy{
+ .bufferOffset = planeOffset,
+ .bufferRowLength = planeStrideTexels,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = planeInfo.aspectMask,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .imageExtent =
+ {
+ .width = planeWidth,
+ .height = planeHeight,
+ .depth = 1,
+ },
+ });
+ }
+ cumulativeOffset += planeSize;
+ cumulativeSize += planeSize;
+ }
+ if (outStagingBufferCopySize) {
+ *outStagingBufferCopySize = cumulativeSize;
+ }
+
+ return true;
+}
diff --git a/stream-servers/vulkan/VkFormatUtils.h b/stream-servers/vulkan/VkFormatUtils.h
index 9cafb27..323146d 100644
--- a/stream-servers/vulkan/VkFormatUtils.h
+++ b/stream-servers/vulkan/VkFormatUtils.h
@@ -15,13 +15,17 @@
#include <vulkan/vulkan_core.h>
+#include <vector>
+
+#include "host-common/logging.h"
+#include "vulkan/vk_enum_string_helper.h"
+
// Header library that captures common patterns when working with
// Vulkan formats:
// - Macros to iterate over categories of formats
// - Add often-used parameters like the bytes per pixel and ASTC block size
#define LIST_VK_FORMATS_LINEAR(f) \
- f(VK_FORMAT_UNDEFINED, 0) \
f(VK_FORMAT_R4G4_UNORM_PACK8, 1) \
f(VK_FORMAT_R4G4B4A4_UNORM_PACK16, 2) \
f(VK_FORMAT_B4G4R4A4_UNORM_PACK16, 2) \
@@ -273,6 +277,7 @@
LIST_VK_FORMATS_LINEAR(VK_FORMATS_LINEAR_GET_PIXEL_SIZE)
+ ERR("Unhandled format: %s", string_VkFormat(format));
return 0;
}
@@ -642,4 +647,11 @@
default:
return false;
}
-}
\ No newline at end of file
+}
+
+// Returns the size in bytes needed to copy an image with the given format,
+// width, and height to a staging buffer and the VkBufferImageCopy-s needed
+// to copy from a staging buffer to destination VkImage.
+bool getFormatTransferInfo(VkFormat format, uint32_t width, uint32_t height,
+ VkDeviceSize* outStagingBufferCopySize,
+ std::vector<VkBufferImageCopy>* outBufferImageCopies);
diff --git a/stream-servers/vulkan/VkFormatUtils_unittest.cpp b/stream-servers/vulkan/VkFormatUtils_unittest.cpp
new file mode 100644
index 0000000..3cdb76a
--- /dev/null
+++ b/stream-servers/vulkan/VkFormatUtils_unittest.cpp
@@ -0,0 +1,259 @@
+// copyright (c) 2022 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 express or implied.
+// see the license for the specific language governing permissions and
+// limitations under the license.
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "VkFormatUtils.h"
+
+namespace {
+
+using ::testing::AllOf;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::ExplainMatchResult;
+using ::testing::Field;
+using ::testing::IsFalse;
+using ::testing::IsTrue;
+
+MATCHER_P(EqsVkExtent3D, expected, "") {
+ return ExplainMatchResult(AllOf(Field("width", &VkExtent3D::width, Eq(expected.width)),
+ Field("height", &VkExtent3D::height, Eq(expected.height)),
+ Field("depth", &VkExtent3D::depth, Eq(expected.depth))),
+ arg, result_listener);
+}
+
+MATCHER_P(EqsVkImageSubresourceLayers, expected, "") {
+ return ExplainMatchResult(
+ AllOf(Field("aspectMask", &VkImageSubresourceLayers::aspectMask, Eq(expected.aspectMask)),
+ Field("mipLevel", &VkImageSubresourceLayers::mipLevel, Eq(expected.mipLevel)),
+ Field("baseArrayLayer", &VkImageSubresourceLayers::baseArrayLayer,
+ Eq(expected.baseArrayLayer)),
+ Field("layerCount", &VkImageSubresourceLayers::layerCount, Eq(expected.layerCount))),
+ arg, result_listener);
+}
+
+MATCHER_P(EqsVkOffset3D, expected, "") {
+ return ExplainMatchResult(AllOf(Field("x", &VkOffset3D::x, Eq(expected.x)),
+ Field("y", &VkOffset3D::y, Eq(expected.y)),
+ Field("z", &VkOffset3D::z, Eq(expected.z))),
+ arg, result_listener);
+}
+
+MATCHER_P(EqsVkBufferImageCopy, expected, "") {
+ return ExplainMatchResult(
+ AllOf(Field("bufferOffset", &VkBufferImageCopy::bufferOffset, Eq(expected.bufferOffset)),
+ Field("bufferRowLength", &VkBufferImageCopy::bufferRowLength,
+ Eq(expected.bufferRowLength)),
+ Field("bufferImageHeight", &VkBufferImageCopy::bufferImageHeight,
+ Eq(expected.bufferImageHeight)),
+ Field("imageSubresource", &VkBufferImageCopy::imageSubresource,
+ EqsVkImageSubresourceLayers(expected.imageSubresource)),
+ Field("imageOffset", &VkBufferImageCopy::imageOffset,
+ EqsVkOffset3D(expected.imageOffset)),
+ Field("imageExtent", &VkBufferImageCopy::imageExtent,
+ EqsVkExtent3D(expected.imageExtent))),
+ arg, result_listener);
+}
+
+TEST(VkFormatUtilsTest, GetTransferInfoInvalidFormat) {
+ const VkFormat format = VK_FORMAT_UNDEFINED;
+ const uint32_t width = 16;
+ const uint32_t height = 16;
+ ASSERT_THAT(getFormatTransferInfo(format, width, height, nullptr, nullptr), IsFalse());
+}
+
+TEST(VkFormatUtilsTest, GetTransferInfoRGBA) {
+ const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
+ const uint32_t width = 16;
+ const uint32_t height = 16;
+
+ VkDeviceSize bufferCopySize;
+ std::vector<VkBufferImageCopy> bufferImageCopies;
+ ASSERT_THAT(getFormatTransferInfo(format, width, height, &bufferCopySize, &bufferImageCopies),
+ IsTrue());
+ EXPECT_THAT(bufferCopySize, Eq(1024));
+ ASSERT_THAT(bufferImageCopies, ElementsAre(EqsVkBufferImageCopy(VkBufferImageCopy{
+ .bufferOffset = 0,
+ .bufferRowLength = 16,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .imageExtent =
+ {
+ .width = 16,
+ .height = 16,
+ .depth = 1,
+ },
+ })));
+}
+
+TEST(VkFormatUtilsTest, GetTransferInfoNV12OrNV21) {
+ const VkFormat format = VK_FORMAT_G8_B8R8_2PLANE_420_UNORM;
+ const uint32_t width = 16;
+ const uint32_t height = 16;
+
+ VkDeviceSize bufferCopySize;
+ std::vector<VkBufferImageCopy> bufferImageCopies;
+ ASSERT_THAT(getFormatTransferInfo(format, width, height, &bufferCopySize, &bufferImageCopies),
+ IsTrue());
+ EXPECT_THAT(bufferCopySize, Eq(384));
+ ASSERT_THAT(bufferImageCopies,
+ ElementsAre(EqsVkBufferImageCopy(VkBufferImageCopy{
+ .bufferOffset = 0,
+ .bufferRowLength = 16,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .imageExtent =
+ {
+ .width = 16,
+ .height = 16,
+ .depth = 1,
+ },
+ }),
+ EqsVkBufferImageCopy(VkBufferImageCopy{
+ .bufferOffset = 256,
+ .bufferRowLength = 8,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .imageExtent =
+ {
+ .width = 8,
+ .height = 8,
+ .depth = 1,
+ },
+ })));
+}
+
+TEST(VkFormatUtilsTest, GetTransferInfoYV12OrYV21) {
+ const VkFormat format = VK_FORMAT_G8_B8_R8_3PLANE_420_UNORM;
+ const uint32_t width = 32;
+ const uint32_t height = 32;
+
+ VkDeviceSize bufferCopySize;
+ std::vector<VkBufferImageCopy> bufferImageCopies;
+ ASSERT_THAT(getFormatTransferInfo(format, width, height, &bufferCopySize, &bufferImageCopies),
+ IsTrue());
+ EXPECT_THAT(bufferCopySize, Eq(1536));
+ ASSERT_THAT(bufferImageCopies,
+ ElementsAre(EqsVkBufferImageCopy(VkBufferImageCopy{
+ .bufferOffset = 0,
+ .bufferRowLength = 32,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .imageExtent =
+ {
+ .width = 32,
+ .height = 32,
+ .depth = 1,
+ },
+ }),
+ EqsVkBufferImageCopy(VkBufferImageCopy{
+ .bufferOffset = 1024,
+ .bufferRowLength = 16,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_1_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .imageExtent =
+ {
+ .width = 16,
+ .height = 16,
+ .depth = 1,
+ },
+ }),
+ EqsVkBufferImageCopy(VkBufferImageCopy{
+ .bufferOffset = 1280,
+ .bufferRowLength = 16,
+ .bufferImageHeight = 0,
+ .imageSubresource =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_PLANE_2_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ .imageOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .imageExtent =
+ {
+ .width = 16,
+ .height = 16,
+ .depth = 1,
+ },
+ })));
+}
+
+} // namespace
\ No newline at end of file