Expose VK_EXT_image_robustness support

The feature is currently always enabled, so we ignore the creation flag
for now.

Bug: b/159329067
Tests: dEQP-VK.robustness.image_robustness.*
Change-Id: I5385196afd93e3deeaec5713f301af4fed4ef15e
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/46828
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/src/Pipeline/SamplerCore.cpp b/src/Pipeline/SamplerCore.cpp
index 1a84042..1c9124b 100644
--- a/src/Pipeline/SamplerCore.cpp
+++ b/src/Pipeline/SamplerCore.cpp
@@ -2257,7 +2257,6 @@
 		xyz0 = Min(Max(xyz, Int4(0)), maxXYZ);
 
 		// VK_EXT_image_robustness requires checking for out-of-bounds accesses.
-		// TODO(b/159329067): Claim VK_EXT_image_robustness
 		// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
 		// If the above clamping altered the result, the access is out-of-bounds.
 		// In that case set the coordinate to -1 to perform texel replacement later.
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index afcdbac..0374aaa 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -1511,7 +1511,6 @@
 
 		case spv::StorageClassImage:
 			// VK_EXT_image_robustness requires nullifying out-of-bounds accesses.
-			// TODO(b/159329067): Claim VK_EXT_image_robustness
 			// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
 			return OutOfBoundsBehavior::Nullify;
 
diff --git a/src/Pipeline/SpirvShaderImage.cpp b/src/Pipeline/SpirvShaderImage.cpp
index e291fe8..4ce3532 100644
--- a/src/Pipeline/SpirvShaderImage.cpp
+++ b/src/Pipeline/SpirvShaderImage.cpp
@@ -648,14 +648,7 @@
 
 	auto &dst = state->createIntermediate(insn.resultId(), resultType.componentCount);
 
-	// "The value returned by a read of an invalid texel is undefined,
-	//  unless that read operation is from a buffer resource and the robustBufferAccess feature is enabled."
-	// TODO: Don't always assume a buffer resource.
-	//
-	// While we could be using OutOfBoundsBehavior::RobustBufferAccess for read operations from buffer resources,
-	// emulating the glsl function loadImage() requires that this function returns 0 when used with out of bounds
-	// coordinates, so we have to use OutOfBoundsBehavior::Nullify in that case.
-	// TODO(b/159329067): Claim VK_EXT_image_robustness
+	// VK_EXT_image_robustness requires replacing out-of-bounds access with zero.
 	// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
 	auto robustness = OutOfBoundsBehavior::Nullify;
 
@@ -1178,12 +1171,9 @@
 			break;
 	}
 
-	// SPIR-V 1.4: "If the coordinates are outside the image, the memory location that is accessed is undefined."
-
-	// Emulating the glsl function imageStore() requires that this function is noop when used with out of bounds
-	// coordinates, so we have to use OutOfBoundsBehavior::Nullify in that case.
-	// TODO(b/159329067): Claim VK_EXT_image_robustness
-	// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
+	// "The integer texel coordinates are validated according to the same rules as for texel input coordinate
+	//  validation. If the texel fails integer texel coordinate validation, then the write has no effect."
+	// - https://www.khronos.org/registry/vulkan/specs/1.2/html/chap16.html#textures-output-coordinate-validation
 	auto robustness = OutOfBoundsBehavior::Nullify;
 
 	auto texelPtr = GetTexelAddress(state, imageBase, imageSizeInBytes, coordinate, imageType, binding, texelSize, 0, false, robustness);
@@ -1251,7 +1241,6 @@
 	auto imageSizeInBytes = *Pointer<Int>(binding + OFFSET(vk::StorageImageDescriptor, sizeInBytes));
 
 	// VK_EXT_image_robustness requires checking for out-of-bounds accesses.
-	// TODO(b/159329067): Claim VK_EXT_image_robustness
 	// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
 	auto robustness = OutOfBoundsBehavior::Nullify;
 
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index e9203ab..07e4d39 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -82,7 +82,6 @@
 		{
 			// OpImageFetch does not take a sampler descriptor, but for VK_EXT_image_robustness
 			// requires replacing invalid texels with zero.
-			// TODO(b/159329067): Claim VK_EXT_image_robustness
 			// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
 			samplerState.border = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
 		}
@@ -366,7 +365,6 @@
 
 		// VK_EXT_image_robustness requires nullifying out-of-bounds accesses.
 		// ADDRESSING_BORDER causes texel replacement to be performed.
-		// TODO(b/159329067): Claim VK_EXT_image_robustness
 		// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
 		return ADDRESSING_BORDER;
 	}
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index 9518a95..39b56e2 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -191,6 +191,11 @@
 	features->provokingVertexLast = VK_TRUE;
 }
 
+void PhysicalDevice::getFeatures(VkPhysicalDeviceImageRobustnessFeaturesEXT *features) const
+{
+	features->robustImageAccess = VK_TRUE;
+}
+
 VkSampleCountFlags PhysicalDevice::getSampleCounts() const
 {
 	return VK_SAMPLE_COUNT_1_BIT | VK_SAMPLE_COUNT_4_BIT;
diff --git a/src/Vulkan/VkPhysicalDevice.hpp b/src/Vulkan/VkPhysicalDevice.hpp
index ea74d16..fbb9719 100644
--- a/src/Vulkan/VkPhysicalDevice.hpp
+++ b/src/Vulkan/VkPhysicalDevice.hpp
@@ -45,6 +45,7 @@
 	void getFeatures(VkPhysicalDeviceLineRasterizationFeaturesEXT *features) const;
 	void getFeatures(VkPhysicalDeviceSeparateDepthStencilLayoutsFeaturesKHR *features) const;
 	void getFeatures(VkPhysicalDeviceProvokingVertexFeaturesEXT *features) const;
+	void getFeatures(VkPhysicalDeviceImageRobustnessFeaturesEXT *features) const;
 	bool hasFeatures(const VkPhysicalDeviceFeatures &requestedFeatures) const;
 
 	const VkPhysicalDeviceProperties &getProperties() const;
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index 036dc33..acec24b 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -100,19 +100,6 @@
 }
 #endif  // __ANDROID__ && ENABLE_BUILD_VERSION_OUTPUT
 
-bool HasExtensionProperty(const char *extensionName, const VkExtensionProperties *extensionProperties, uint32_t extensionPropertiesCount)
-{
-	for(uint32_t j = 0; j < extensionPropertiesCount; ++j)
-	{
-		if(strcmp(extensionName, extensionProperties[j].extensionName) == 0)
-		{
-			return true;
-		}
-	}
-
-	return false;
-}
-
 // setReactorDefaultConfig() sets the default configuration for Vulkan's use of
 // Reactor.
 void setReactorDefaultConfig()
@@ -362,6 +349,7 @@
 	{ VK_EXT_LINE_RASTERIZATION_EXTENSION_NAME, VK_EXT_LINE_RASTERIZATION_SPEC_VERSION },
 	// The following extension is used by ANGLE to emulate blitting the stencil buffer
 	{ VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME, VK_EXT_SHADER_STENCIL_EXPORT_SPEC_VERSION },
+	{ VK_EXT_IMAGE_ROBUSTNESS_EXTENSION_NAME, VK_EXT_IMAGE_ROBUSTNESS_SPEC_VERSION },
 #ifndef __ANDROID__
 	// We fully support the KHR_swapchain v70 additions, so just track the spec version.
 	{ VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_KHR_SWAPCHAIN_SPEC_VERSION },
@@ -393,6 +381,29 @@
 	{ VK_GOOGLE_SAMPLER_FILTERING_PRECISION_EXTENSION_NAME, VK_GOOGLE_SAMPLER_FILTERING_PRECISION_SPEC_VERSION },
 };
 
+static bool hasExtension(const char *extensionName, const VkExtensionProperties *extensionProperties, uint32_t extensionPropertiesCount)
+{
+	for(uint32_t i = 0; i < extensionPropertiesCount; i++)
+	{
+		if(strcmp(extensionName, extensionProperties[i].extensionName) == 0)
+		{
+			return true;
+		}
+	}
+
+	return false;
+}
+
+static bool hasInstanceExtension(const char *extensionName)
+{
+	return hasExtension(extensionName, instanceExtensionProperties, sizeof(instanceExtensionProperties) / sizeof(instanceExtensionProperties[0]));
+}
+
+static bool hasDeviceExtension(const char *extensionName)
+{
+	return hasExtension(extensionName, deviceExtensionProperties, sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0]));
+}
+
 VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance(const VkInstanceCreateInfo *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkInstance *pInstance)
 {
 	TRACE("(const VkInstanceCreateInfo* pCreateInfo = %p, const VkAllocationCallbacks* pAllocator = %p, VkInstance* pInstance = %p)",
@@ -411,10 +422,9 @@
 		UNIMPLEMENTED("b/148240133: pCreateInfo->enabledLayerCount != 0");  // FIXME(b/148240133)
 	}
 
-	uint32_t extensionPropertiesCount = sizeof(instanceExtensionProperties) / sizeof(instanceExtensionProperties[0]);
 	for(uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i)
 	{
-		if(!HasExtensionProperty(pCreateInfo->ppEnabledExtensionNames[i], instanceExtensionProperties, extensionPropertiesCount))
+		if(!hasInstanceExtension(pCreateInfo->ppEnabledExtensionNames[i]))
 		{
 			return VK_ERROR_EXTENSION_NOT_PRESENT;
 		}
@@ -670,10 +680,9 @@
 		UNSUPPORTED("pCreateInfo->enabledLayerCount != 0");
 	}
 
-	uint32_t extensionPropertiesCount = sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0]);
 	for(uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; ++i)
 	{
-		if(!HasExtensionProperty(pCreateInfo->ppEnabledExtensionNames[i], deviceExtensionProperties, extensionPropertiesCount))
+		if(!hasDeviceExtension(pCreateInfo->ppEnabledExtensionNames[i]))
 		{
 			return VK_ERROR_EXTENSION_NOT_PRESENT;
 		}
@@ -804,6 +813,20 @@
 				(void)provokingVertexFeatures->provokingVertexLast;
 			}
 			break;
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES_EXT:
+			{
+				const VkPhysicalDeviceImageRobustnessFeaturesEXT *imageRobustnessFeatures = reinterpret_cast<const VkPhysicalDeviceImageRobustnessFeaturesEXT *>(extensionCreateInfo);
+
+				// We currently always provide robust image accesses. When the feature is disabled, results are
+				// undefined (for images with Dim != Buffer), so providing robustness is also acceptable.
+				// TODO(b/159329067): Only provide robustness when requested.
+				(void)imageRobustnessFeatures->robustImageAccess;
+			}
+			break;
+			// For unsupported structures, check that we don't expose the corresponding extension string:
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT:
+				ASSERT(!hasDeviceExtension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME));
+				break;
 			default:
 				// "the [driver] must skip over, without processing (other than reading the sType and pNext members) any structures in the chain with sType values not defined by [supported extenions]"
 				LOG_TRAP("pCreateInfo->pNext sType = %s", vk::Stringify(extensionCreateInfo->sType).c_str());
@@ -2936,21 +2959,27 @@
 				vk::Cast(physicalDevice)->getFeatures(features);
 			}
 			break;
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_ROBUSTNESS_FEATURES_EXT:
+			{
+				auto features = reinterpret_cast<VkPhysicalDeviceImageRobustnessFeaturesEXT *>(extensionFeatures);
+				vk::Cast(physicalDevice)->getFeatures(features);
+			}
+			break;
+			// For unsupported structures, check that we don't expose the corresponding extension string:
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CONDITIONAL_RENDERING_FEATURES_EXT:
-				ASSERT(!HasExtensionProperty(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_EXT_CONDITIONAL_RENDERING_EXTENSION_NAME));
 				break;
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES_EXT:
-				ASSERT(!HasExtensionProperty(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME));
 				break;
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES_KHR:
-				ASSERT(!HasExtensionProperty(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME));
 				break;
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PERFORMANCE_QUERY_FEATURES_KHR:
-				ASSERT(!HasExtensionProperty(VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_KHR_PERFORMANCE_QUERY_EXTENSION_NAME));
+				break;
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ROBUSTNESS_2_FEATURES_EXT:
+				ASSERT(!hasDeviceExtension(VK_EXT_ROBUSTNESS_2_EXTENSION_NAME));
 				break;
 			default:
 				LOG_TRAP("pFeatures->pNext sType = %s", vk::Stringify(extensionFeatures->sType).c_str());
@@ -3014,8 +3043,7 @@
 			break;
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLE_LOCATIONS_PROPERTIES_EXT:
 				// Explicitly ignored, since VK_EXT_sample_locations is not supported
-				ASSERT(!HasExtensionProperty(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_EXT_SAMPLE_LOCATIONS_EXTENSION_NAME));
 				break;
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT:
 			{
@@ -3091,15 +3119,13 @@
 			case VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO_KHR:
 			{
 				// Explicitly ignored, since VK_KHR_image_format_list is not supported
-				ASSERT(!HasExtensionProperty(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME));
 			}
 			break;
 			case VK_STRUCTURE_TYPE_IMAGE_STENCIL_USAGE_CREATE_INFO_EXT:
 			{
 				// Explicitly ignored, since VK_EXT_separate_stencil_usage is not supported
-				ASSERT(!HasExtensionProperty(VK_EXT_SEPARATE_STENCIL_USAGE_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_EXT_SEPARATE_STENCIL_USAGE_EXTENSION_NAME));
 			}
 			break;
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO:
@@ -3111,8 +3137,7 @@
 			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_DRM_FORMAT_MODIFIER_INFO_EXT:
 			{
 				// Explicitly ignored, since VK_EXT_image_drm_format_modifier is not supported
-				ASSERT(!HasExtensionProperty(VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME));
 			}
 			break;
 			default:
@@ -3144,8 +3169,7 @@
 			case VK_STRUCTURE_TYPE_TEXTURE_LOD_GATHER_FORMAT_PROPERTIES_AMD:
 			{
 				// Explicitly ignored, since VK_AMD_texture_gather_bias_lod is not supported
-				ASSERT(!HasExtensionProperty(VK_AMD_TEXTURE_GATHER_BIAS_LOD_EXTENSION_NAME, deviceExtensionProperties,
-				                             sizeof(deviceExtensionProperties) / sizeof(deviceExtensionProperties[0])));
+				ASSERT(!hasDeviceExtension(VK_AMD_TEXTURE_GATHER_BIAS_LOD_EXTENSION_NAME));
 			}
 			break;
 			default: