Browse Source

Vulkan: Add texture readback (#2280)

* Vulkan: Add texture readback support

* Vulkan: Simplify screenshot code
pezcode 5 years ago
parent
commit
2829df055c
1 changed files with 182 additions and 91 deletions
  1. 182 91
      src/renderer_vk.cpp

+ 182 - 91
src/renderer_vk.cpp

@@ -1565,6 +1565,7 @@ VK_IMPORT_INSTANCE
 					| BGFX_CAPS_TEXTURE_2D_ARRAY
 					| BGFX_CAPS_TEXTURE_2D_ARRAY
 					| BGFX_CAPS_TEXTURE_3D
 					| BGFX_CAPS_TEXTURE_3D
 					| BGFX_CAPS_TEXTURE_BLIT
 					| BGFX_CAPS_TEXTURE_BLIT
+					| BGFX_CAPS_TEXTURE_READ_BACK
 					| BGFX_CAPS_TEXTURE_COMPARE_ALL
 					| BGFX_CAPS_TEXTURE_COMPARE_ALL
 					| BGFX_CAPS_TEXTURE_CUBE_ARRAY
 					| BGFX_CAPS_TEXTURE_CUBE_ARRAY
 					| BGFX_CAPS_VERTEX_ATTRIB_HALF
 					| BGFX_CAPS_VERTEX_ATTRIB_HALF
@@ -2582,8 +2583,145 @@ VK_IMPORT_DEVICE
 		{
 		{
 		}
 		}
 
 
-		void readTexture(TextureHandle /*_handle*/, void* /*_data*/, uint8_t /*_mip*/) override
+		void readTexture(TextureHandle _handle, void* _data, uint8_t _mip) override
 		{
 		{
+			const TextureVK& texture = m_textures[_handle.idx];
+
+			VkImage srcImage = texture.m_textureImage;
+			uint32_t srcWidth  = bx::uint32_max(1, texture.m_width  >> _mip);
+			uint32_t srcHeight = bx::uint32_max(1, texture.m_height >> _mip);
+
+			// Create the linear tiled destination image to copy to and to read the memory from
+			VkImage dstImage = VK_NULL_HANDLE;
+			VkImageCreateInfo ici;
+			ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+			ici.pNext = NULL;
+			ici.flags = 0;
+			ici.imageType = VK_IMAGE_TYPE_2D;
+			ici.format = texture.m_format;
+			ici.extent.width = srcWidth;
+			ici.extent.height = srcHeight;
+			ici.extent.depth = 1;
+			ici.arrayLayers = 1;
+			ici.mipLevels = 1;
+			ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+			ici.samples = VK_SAMPLE_COUNT_1_BIT;
+			ici.tiling = VK_IMAGE_TILING_LINEAR;
+			ici.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+			ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+			ici.queueFamilyIndexCount = 0;
+			ici.pQueueFamilyIndices = NULL;
+
+			VK_CHECK(vkCreateImage(m_device, &ici, m_allocatorCb, &dstImage));
+
+			// Create memory to back up the image
+			VkMemoryRequirements memRequirements;
+			vkGetImageMemoryRequirements(m_device, dstImage, &memRequirements);
+
+			VkDeviceMemory dstImageMemory = VK_NULL_HANDLE;
+			// Memory must be host visible to copy from
+			VK_CHECK(allocateMemory(&memRequirements, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &dstImageMemory));
+			VK_CHECK(vkBindImageMemory(m_device, dstImage, dstImageMemory, 0));
+
+			VkCommandBuffer copyCmd = beginNewCommand();
+
+			bgfx::vk::setImageMemoryBarrier(
+				copyCmd
+				, dstImage
+				, texture.m_aspectMask
+				, VK_IMAGE_LAYOUT_UNDEFINED
+				, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
+				, 1
+				, 1
+			);
+
+			bgfx::vk::setImageMemoryBarrier(
+				copyCmd
+				, srcImage
+				, texture.m_aspectMask
+				, texture.m_currentImageLayout
+				, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
+				, 1
+				, 1
+			);
+
+			VkImageCopy ic;
+
+			ic.srcSubresource.aspectMask = texture.m_aspectMask;
+			ic.srcSubresource.mipLevel = _mip;
+			ic.srcSubresource.baseArrayLayer = 0;
+			ic.srcSubresource.layerCount = 1;
+			ic.srcOffset = { 0, 0, 0 };
+
+			ic.dstSubresource.aspectMask = texture.m_aspectMask;
+			ic.dstSubresource.mipLevel = 0;
+			ic.dstSubresource.baseArrayLayer = 0;
+			ic.dstSubresource.layerCount = 1;
+			ic.dstOffset = { 0, 0, 0 };
+
+			ic.extent = { srcWidth, srcHeight, 1 };
+
+			vkCmdCopyImage(
+				copyCmd
+				, srcImage
+				, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
+				, dstImage
+				, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
+				, 1
+				, &ic
+			);
+
+			// Transition destination image to general layout, which is the required layout for mapping the image memory later on
+			bgfx::vk::setImageMemoryBarrier(
+				copyCmd
+				, dstImage
+				, texture.m_aspectMask
+				, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
+				, VK_IMAGE_LAYOUT_GENERAL
+				, 1
+				, 1
+			);
+
+			bgfx::vk::setImageMemoryBarrier(
+				copyCmd
+				, srcImage
+				, texture.m_aspectMask
+				, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
+				, texture.m_currentImageLayout
+				, 1
+				, 1
+			);
+
+			submitCommandAndWait(copyCmd);
+
+			// Get layout of the image (including row pitch)
+			const VkImageSubresource subResource{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 0 };
+			VkSubresourceLayout subResourceLayout;
+			vkGetImageSubresourceLayout(m_device, dstImage, &subResource, &subResourceLayout);
+			uint32_t srcPitch = uint32_t(subResourceLayout.rowPitch);
+
+			const uint8_t bpp = bimg::getBitsPerPixel(bimg::TextureFormat::Enum(texture.m_textureFormat));
+			uint8_t* dst = (uint8_t*)_data;
+			uint32_t dstPitch = srcWidth*bpp/8;
+
+			uint32_t pitch = bx::uint32_min(srcPitch, dstPitch);
+
+			uint8_t* src;
+			vkMapMemory(m_device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&src);
+			src += subResourceLayout.offset;
+
+			for (uint32_t yy = 0, height = srcHeight; yy < height; ++yy)
+			{
+				bx::memCopy(dst, src, pitch);
+
+				src += srcPitch;
+				dst += dstPitch;
+			}
+
+			// Clean up resources
+			vkUnmapMemory(m_device, dstImageMemory);
+			vkFreeMemory(m_device, dstImageMemory, m_allocatorCb);
+			vkDestroyImage(m_device, dstImage, m_allocatorCb);
 		}
 		}
 
 
 		void resizeTexture(TextureHandle /*_handle*/, uint16_t /*_width*/, uint16_t /*_height*/, uint8_t /*_numMips*/, uint16_t /*_numLayers*/) override
 		void resizeTexture(TextureHandle /*_handle*/, uint16_t /*_width*/, uint16_t /*_height*/, uint8_t /*_numMips*/, uint16_t /*_numLayers*/) override
@@ -2640,27 +2778,6 @@ VK_IMPORT_DEVICE
 
 
 		void requestScreenShot(FrameBufferHandle _fbh, const char* _filePath) override
 		void requestScreenShot(FrameBufferHandle _fbh, const char* _filePath) override
 		{
 		{
-			bool supportsBlit = true;
-
-			// Check blit support for source and destination
-			VkFormatProperties formatProps;
-
-			// Check if the device supports blitting from optimal images (the swapchain images are in optimal format)
-			vkGetPhysicalDeviceFormatProperties(m_physicalDevice, m_sci.imageFormat, &formatProps);
-			if (!(formatProps.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_SRC_BIT) )
-			{
-				BX_TRACE("Device does not support blitting from optimal tiled images, using copy instead of blit!\n");
-				supportsBlit = false;
-			}
-
-			// Check if the device supports blitting to linear images
-			vkGetPhysicalDeviceFormatProperties(m_physicalDevice, VK_FORMAT_R8G8B8A8_UNORM, &formatProps);
-			if (!(formatProps.linearTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) )
-			{
-				BX_TRACE("Device does not support blitting to linear tiled images, using copy instead of blit!\n");
-				supportsBlit = false;
-			}
-
 			// Source for the copy is the last rendered swapchain image
 			// Source for the copy is the last rendered swapchain image
 			VkImage srcImage = m_backBufferColorImage[m_backBufferColorIdx];
 			VkImage srcImage = m_backBufferColorImage[m_backBufferColorIdx];
 			uint32_t width = m_sci.imageExtent.width, height = m_sci.imageExtent.height;
 			uint32_t width = m_sci.imageExtent.width, height = m_sci.imageExtent.height;
@@ -2677,7 +2794,6 @@ VK_IMPORT_DEVICE
 			ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
 			ici.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
 			ici.pNext = NULL;
 			ici.pNext = NULL;
 			ici.flags = 0;
 			ici.flags = 0;
-			// Note that vkCmdBlitImage (if supported) will also do format conversions if the swapchain color format would differ
 			ici.imageType = VK_IMAGE_TYPE_2D;
 			ici.imageType = VK_IMAGE_TYPE_2D;
 			ici.format    = VK_FORMAT_R8G8B8A8_UNORM;
 			ici.format    = VK_FORMAT_R8G8B8A8_UNORM;
 			ici.extent.width  = width;
 			ici.extent.width  = width;
@@ -2729,70 +2845,32 @@ VK_IMPORT_DEVICE
 				, 1
 				, 1
 				);
 				);
 
 
-			// If source and destination support blit we'll blit as this also does automatic format conversion (e.g. from BGR to RGB)
-			if (supportsBlit)
-			{
-				// Define the region to blit (we will blit the whole swapchain image)
-				VkOffset3D blitSize { int32_t(width), int32_t(height), 1 };
+			VkImageCopy ic;
 
 
-				VkImageBlit ib;
+			ic.srcSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+			ic.srcSubresource.mipLevel       = 0;
+			ic.srcSubresource.baseArrayLayer = 0;
+			ic.srcSubresource.layerCount     = 1;
+			ic.srcOffset = { 0, 0, 0 };
 
 
-				ib.srcSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
-				ib.srcSubresource.mipLevel       = 0;
-				ib.srcSubresource.baseArrayLayer = 0;
-				ib.srcSubresource.layerCount     = 1;
-				ib.srcOffsets[0]                 = { 0, 0, 0 };
-				ib.srcOffsets[1]                 = blitSize;
+			ic.dstSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
+			ic.dstSubresource.mipLevel       = 0;
+			ic.dstSubresource.baseArrayLayer = 0;
+			ic.dstSubresource.layerCount     = 1;
+			ic.dstOffset = { 0, 0, 0 };
 
 
-				ib.dstSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
-				ib.dstSubresource.mipLevel       = 0;
-				ib.dstSubresource.baseArrayLayer = 0;
-				ib.dstSubresource.layerCount     = 1;
-				ib.dstOffsets[0]                 = { 0, 0, 0 };
-				ib.dstOffsets[1]                 = blitSize;
+			ic.extent = { width, height, 1 };
 
 
-				// Issue the blit command
-				vkCmdBlitImage(
-					  copyCmd
-					, srcImage
-					, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
-					, dstImage
-					, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
-					, 1
-					, &ib
-					, VK_FILTER_NEAREST
-					);
-			}
-			else
-			{
-				// Otherwise use image copy (requires us to manually flip components)
-				VkImageCopy ic;
-
-				ic.srcSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
-				ic.srcSubresource.mipLevel       = 0;
-				ic.srcSubresource.baseArrayLayer = 0;
-				ic.srcSubresource.layerCount     = 1;
-				ic.srcOffset = { 0, 0, 0 };
-
-				ic.dstSubresource.aspectMask     = VK_IMAGE_ASPECT_COLOR_BIT;
-				ic.dstSubresource.mipLevel       = 0;
-				ic.dstSubresource.baseArrayLayer = 0;
-				ic.dstSubresource.layerCount     = 1;
-				ic.dstOffset = { 0, 0, 0 };
-
-				ic.extent = { width, height, 1 };
-
-				// Issue the copy command
-				vkCmdCopyImage(
-					  copyCmd
-					, srcImage
-					, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
-					, dstImage
-					, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
-					, 1
-					, &ic
-					);
-			}
+			// Issue the copy command
+			vkCmdCopyImage(
+					copyCmd
+				, srcImage
+				, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL
+				, dstImage
+				, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL
+				, 1
+				, &ic
+				);
 
 
 			// Transition destination image to general layout, which is the required layout for mapping the image memory later on
 			// Transition destination image to general layout, which is the required layout for mapping the image memory later on
 			bgfx::vk::setImageMemoryBarrier(
 			bgfx::vk::setImageMemoryBarrier(
@@ -2828,14 +2906,27 @@ VK_IMPORT_DEVICE
 			vkMapMemory(m_device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data);
 			vkMapMemory(m_device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data);
 			data += subResourceLayout.offset;
 			data += subResourceLayout.offset;
 
 
-			bimg::imageSwizzleBgra8(
-				  data
-				, uint32_t(subResourceLayout.rowPitch)
-				, width
-				, height
-				, data
-				, uint32_t(subResourceLayout.rowPitch)
-				);
+			static const VkFormat unswizzledFormats[] =
+			{
+				VK_FORMAT_R8G8B8A8_UNORM,
+				VK_FORMAT_R8G8B8A8_SRGB
+			};
+
+			for (uint32_t ii = 0; ii < BX_COUNTOF(unswizzledFormats); ii++)
+			{
+				if (m_sci.imageFormat == unswizzledFormats[ii])
+				{
+					bimg::imageSwizzleBgra8(
+						  data
+						, uint32_t(subResourceLayout.rowPitch)
+						, width
+						, height
+						, data
+						, uint32_t(subResourceLayout.rowPitch)
+						);
+					break;
+				}
+			}
 
 
 			g_callback->screenShot(
 			g_callback->screenShot(
 				  _filePath
 				  _filePath