Fix unreasonable memory expectations in pipeline.render_to_image

Affects:
dEQP-VK.pipeline.render_to_image.*

Components: Vulkan

VK-GL-CTS issue: 526

Change-Id: I71ea0381663c39786b227ed712e1111ec3c71cfb
(cherry picked from commit 069aa0ad097f543438b39a260eea52278867ffa8)
diff --git a/external/vulkancts/modules/vulkan/pipeline/vktPipelineRenderToImageTests.cpp b/external/vulkancts/modules/vulkan/pipeline/vktPipelineRenderToImageTests.cpp
index d11c802..89b4e2e 100644
--- a/external/vulkancts/modules/vulkan/pipeline/vktPipelineRenderToImageTests.cpp
+++ b/external/vulkancts/modules/vulkan/pipeline/vktPipelineRenderToImageTests.cpp
@@ -694,18 +694,90 @@
 	}
 }
 
-VkDeviceSize getMaxDeviceHeapSize (const InstanceInterface& vki, const VkPhysicalDevice physDevice)
+deUint32 selectMatchingMemoryType (const VkPhysicalDeviceMemoryProperties& deviceMemProps, deUint32 allowedMemTypeBits, MemoryRequirement requirement)
 {
-	const VkPhysicalDeviceMemoryProperties	memoryProperties	= getPhysicalDeviceMemoryProperties(vki, physDevice);
-	VkDeviceSize							memorySize			= 0;
+	const deUint32	compatibleTypes	= getCompatibleMemoryTypes(deviceMemProps, requirement);
+	const deUint32	candidates		= allowedMemTypeBits & compatibleTypes;
 
-	for (deUint32 heapNdx = 0; heapNdx < memoryProperties.memoryHeapCount; ++heapNdx)
+	if (candidates == 0)
+		TCU_THROW(NotSupportedError, "No compatible memory type found");
+
+	return (deUint32)deCtz32(candidates);
+}
+
+IVec4 getMaxImageSize (const VkImageViewType viewType, const IVec4& sizeHint)
+{
+	//Limits have been taken from the vulkan specification
+	IVec4 size = IVec4(
+		sizeHint.x() != MAX_SIZE ? sizeHint.x() : 4096,
+		sizeHint.y() != MAX_SIZE ? sizeHint.y() : 4096,
+		sizeHint.z() != MAX_SIZE ? sizeHint.z() : 256,
+		sizeHint.w() != MAX_SIZE ? sizeHint.w() : 256);
+
+	switch (viewType)
 	{
-		if ((memoryProperties.memoryHeaps[heapNdx].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0)
-			memorySize = std::max(memorySize, memoryProperties.memoryHeaps[heapNdx].size);
+		case VK_IMAGE_VIEW_TYPE_1D:
+		case VK_IMAGE_VIEW_TYPE_1D_ARRAY:
+			size.x() = deMin32(4096, size.x());
+			break;
+
+		case VK_IMAGE_VIEW_TYPE_2D:
+		case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
+			size.x() = deMin32(4096, size.x());
+			size.y() = deMin32(4096, size.y());
+			break;
+
+		case VK_IMAGE_VIEW_TYPE_3D:
+			size.x() = deMin32(256, size.x());
+			size.y() = deMin32(256, size.y());
+			break;
+
+		case VK_IMAGE_VIEW_TYPE_CUBE:
+		case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:
+			size.x() = deMin32(4096, size.x());
+			size.y() = deMin32(4096, size.y());
+			size.w() = deMin32(252, size.w());
+			size.w() = NUM_CUBE_FACES * (size.w() / NUM_CUBE_FACES);	// round down to 6 faces
+			break;
+
+		default:
+			DE_ASSERT(0);
+			return IVec4();
 	}
 
-	return memorySize;
+	return size;
+}
+
+deUint32 getMemoryTypeNdx (Context& context, const CaseDef& caseDef)
+{
+	const DeviceInterface&					vk					= context.getDeviceInterface();
+	const InstanceInterface&				vki					= context.getInstanceInterface();
+	const VkDevice							device				= context.getDevice();
+	const VkPhysicalDevice					physDevice			= context.getPhysicalDevice();
+
+	const VkPhysicalDeviceMemoryProperties	memoryProperties	= getPhysicalDeviceMemoryProperties(vki, physDevice);
+	Move<VkImage>							colorImage;
+	VkMemoryRequirements					memReqs;
+
+	const VkImageUsageFlags					imageUsage	= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
+	const IVec4								imageSize	= getMaxImageSize(caseDef.viewType, caseDef.imageSizeHint);
+
+	//create image, don't bind any memory to it
+	colorImage	= makeImage(vk, device, getImageCreateFlags(caseDef.viewType), getImageType(caseDef.viewType), caseDef.colorFormat,
+								imageSize.swizzle(0, 1, 2), 1u, imageSize.w(), imageUsage);
+
+	vk.getImageMemoryRequirements(device, *colorImage, &memReqs);
+	return selectMatchingMemoryType(memoryProperties, memReqs.memoryTypeBits, MemoryRequirement::Any);
+}
+
+VkDeviceSize getMaxDeviceHeapSize (Context& context, const CaseDef& caseDef)
+{
+	const InstanceInterface&				vki					= context.getInstanceInterface();
+	const VkPhysicalDevice					physDevice			= context.getPhysicalDevice();
+	const VkPhysicalDeviceMemoryProperties	memoryProperties	= getPhysicalDeviceMemoryProperties(vki, physDevice);
+	const deUint32							memoryTypeNdx		= getMemoryTypeNdx (context, caseDef);
+
+	return memoryProperties.memoryHeaps[memoryProperties.memoryTypes[memoryTypeNdx].heapIndex].size;
 }
 
 //! Get a smaller image size. Returns a vector of zeroes, if it can't reduce more.
@@ -743,51 +815,6 @@
 	return (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) != 0;
 }
 
-IVec4 getMaxImageSize (const VkPhysicalDeviceLimits& limits, const VkImageViewType viewType, const IVec4& sizeHint, const bool useDepthStencil)
-{
-	// If we use a layered D/S together with a 3D image, we have to use the smallest common limit
-	const int maxDepth = (useDepthStencil ? deMin32(static_cast<int>(limits.maxImageArrayLayers), static_cast<int>(limits.maxImageDimension3D))
-										  : static_cast<int>(limits.maxImageDimension3D));
-
-	// Images have to respect framebuffer limits and image limits (the framebuffer is not layered in this case)
-	IVec4 size = IVec4(
-		sizeHint.x() != MAX_SIZE ? sizeHint.x() : static_cast<int>(limits.maxFramebufferWidth),
-		sizeHint.y() != MAX_SIZE ? sizeHint.y() : static_cast<int>(limits.maxFramebufferHeight),
-		sizeHint.z() != MAX_SIZE ? sizeHint.z() : maxDepth,
-		sizeHint.w() != MAX_SIZE ? sizeHint.w() : static_cast<int>(limits.maxImageArrayLayers));
-
-	switch (viewType)
-	{
-		case VK_IMAGE_VIEW_TYPE_1D:
-		case VK_IMAGE_VIEW_TYPE_1D_ARRAY:
-			size.x() = deMin32(size.x(), limits.maxImageDimension1D);
-			break;
-
-		case VK_IMAGE_VIEW_TYPE_2D:
-		case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
-			size.x() = deMin32(size.x(), limits.maxImageDimension2D);
-			size.y() = deMin32(size.y(), limits.maxImageDimension2D);
-			break;
-
-		case VK_IMAGE_VIEW_TYPE_3D:
-			size.x() = deMin32(size.x(), limits.maxImageDimension3D);
-			size.y() = deMin32(size.y(), limits.maxImageDimension3D);
-			break;
-
-		case VK_IMAGE_VIEW_TYPE_CUBE:
-		case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:
-			size.x() = size.y() = deMin32(size.x(), limits.maxImageDimensionCube);
-			size.w() = NUM_CUBE_FACES * (size.w() / NUM_CUBE_FACES);	// round down to 6 faces
-			break;
-
-		default:
-			DE_ASSERT(0);
-			return IVec4();
-	}
-
-	return size;
-}
-
 VkImageAspectFlags getFormatAspectFlags (const VkFormat format)
 {
 	if (format == VK_FORMAT_UNDEFINED)
@@ -860,7 +887,7 @@
 }
 
 //! See testAttachmentSize() description
-tcu::TestStatus testWithSizeReduction (Context& context, const CaseDef& caseDef, const int sizeReductionIndex)
+tcu::TestStatus testWithSizeReduction (Context& context, const CaseDef& caseDef)
 {
 	const DeviceInterface&			vk					= context.getDeviceInterface();
 	const InstanceInterface&		vki					= context.getInstanceInterface();
@@ -872,16 +899,67 @@
 
 	// The memory might be too small to allocate a largest possible attachment, so try to account for that.
 	const bool						useDepthStencil		= (caseDef.depthStencilFormat != VK_FORMAT_UNDEFINED);
-	const VkDeviceSize				deviceMemoryBudget	= getMaxDeviceHeapSize(vki, physDevice) >> 2;
-	IVec4							imageSize			= getMaxImageSize(context.getDeviceProperties().limits, caseDef.viewType, caseDef.imageSizeHint, useDepthStencil);
 
-	// Keep reducing the size, if needed
-	for (int i = 0; i < sizeReductionIndex; ++i)
+	IVec4							imageSize			= getMaxImageSize(caseDef.viewType, caseDef.imageSizeHint);
+	VkDeviceSize					colorSize			= product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.colorFormat));
+	VkDeviceSize					depthStencilSize	= (useDepthStencil ? product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.depthStencilFormat)) : 0ull);
+
+	const VkDeviceSize				reserveForChecking	= 500ull * 1024ull;	//left 512KB
+	const float						additionalMemory	= 1.15f;			//left some free memory on device (15%)
+	VkDeviceSize					neededMemory		= static_cast<VkDeviceSize>(static_cast<float>(colorSize + depthStencilSize) * additionalMemory) + reserveForChecking;
+	VkDeviceSize					maxMemory			= getMaxDeviceHeapSize(context, caseDef) >> 2;
+
+	const VkDeviceSize				deviceMemoryBudget	= std::min(neededMemory, maxMemory);
+	bool							allocationPossible	= false;
+
+	// Keep reducing the size, if image size is too big
+	while (neededMemory > deviceMemoryBudget)
 	{
 		imageSize = getReducedImageSize(caseDef, imageSize);
 
 		if (imageSize == IVec4())
 			return tcu::TestStatus::fail("Couldn't create an image with required size");
+
+		colorSize			= product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.colorFormat));
+		depthStencilSize	= (useDepthStencil ? product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.depthStencilFormat)) : 0ull);
+		neededMemory		= static_cast<VkDeviceSize>(static_cast<double>(colorSize + depthStencilSize) * additionalMemory);
+	}
+
+	// Keep reducing the size, if allocation return out of any memory
+	while (!allocationPossible)
+	{
+		VkDeviceMemory				object			= 0;
+		const VkMemoryAllocateInfo	allocateInfo	=
+		{
+			VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,	//VkStructureType	sType;
+			DE_NULL,								//const void*		pNext;
+			neededMemory,							//VkDeviceSize		allocationSize;
+			getMemoryTypeNdx(context, caseDef)		//deUint32			memoryTypeIndex;
+		};
+
+		const VkResult				result			= vk.allocateMemory(device, &allocateInfo, DE_NULL, &object);
+
+		if (VK_ERROR_OUT_OF_DEVICE_MEMORY == result || VK_ERROR_OUT_OF_HOST_MEMORY == result)
+		{
+			imageSize = getReducedImageSize(caseDef, imageSize);
+
+			if (imageSize == IVec4())
+				return tcu::TestStatus::fail("Couldn't create an image with required size");
+
+			colorSize			= product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.colorFormat));
+			depthStencilSize	= (useDepthStencil ? product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.depthStencilFormat)) : 0ull);
+			neededMemory		= static_cast<VkDeviceSize>(static_cast<double>(colorSize + depthStencilSize) * additionalMemory) + reserveForChecking;
+		}
+		else if (VK_SUCCESS != result)
+		{
+			return tcu::TestStatus::fail("Couldn't allocate memory");
+		}
+		else
+		{
+			//free memory using Move pointer
+			Move<VkDeviceMemory> memoryAllocated (check<VkDeviceMemory>(object), Deleter<VkDeviceMemory>(vk, device, DE_NULL));
+			allocationPossible = true;
+		}
 	}
 
 	context.getTestContext().getLog()
@@ -889,15 +967,11 @@
 
 	// "Slices" is either the depth of a 3D image, or the number of layers of an arrayed image
 	const deInt32					numSlices			= maxLayersOrDepth(imageSize);
-	const VkDeviceSize				colorSize			= product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.colorFormat));
-	const VkDeviceSize				depthStencilSize	= (useDepthStencil ? product(imageSize) * tcu::getPixelSize(mapVkFormat(caseDef.depthStencilFormat)) : 0ull);
+
 
 	if (useDepthStencil && !isDepthStencilFormatSupported(vki, physDevice, caseDef.depthStencilFormat))
 		TCU_THROW(NotSupportedError, "Unsupported depth/stencil format");
 
-	if (colorSize + depthStencilSize > deviceMemoryBudget)
-		throw OutOfMemoryError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Image size exceeds test's image memory budget");
-
 	// Determine the verification bounds. The checked region will be in the center of the rendered image
 	const IVec4	checkSize	= tcu::min(imageSize, IVec4(MAX_VERIFICATION_REGION_SIZE,
 														MAX_VERIFICATION_REGION_SIZE,
@@ -1184,8 +1258,6 @@
 {
 	checkImageViewTypeRequirements(context, caseDef.viewType);
 
-	int sizeReductionIndex = 0;
-
 	if (caseDef.allocationKind == ALLOCATION_KIND_DEDICATED)
 	{
 		const std::string extensionName("VK_KHR_dedicated_allocation");
@@ -1194,20 +1266,7 @@
 			TCU_THROW(NotSupportedError, std::string(extensionName + " is not supported").c_str());
 	}
 
-	for (;;)
-	{
-		try
-		{
-			return testWithSizeReduction(context, caseDef, sizeReductionIndex);
-		}
-		catch (OutOfMemoryError& ex)
-		{
-			context.getTestContext().getLog()
-				<< tcu::TestLog::Message << "-- OutOfMemoryError: " << ex.getMessage() << tcu::TestLog::EndMessage;
-
-			++sizeReductionIndex;
-		}
-	}
+	return testWithSizeReduction(context, caseDef);
 	// Never reached
 }