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