Perform texel replacement on out-of-bounds OpImageFetch accesses

VK_EXT_image_robustness requires returning zero on out-of-bounds image
accesses. OpImageFetch was previously merely clamping the coordinates to
be in-bounds.

This change reuses some of the functionality for
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER to perform texel replacement.

Bug: b/159329067
Tests: dEQP-VK.robustness.image_robustness.*
Change-Id: I8c00b8de2793b0b7028230cb180d308a4b9b60ec
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/47095
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 ba17235..1a84042 100644
--- a/src/Pipeline/SamplerCore.cpp
+++ b/src/Pipeline/SamplerCore.cpp
@@ -1888,9 +1888,10 @@
 	{
 		// Valid texels have positive coordinates.
 		Int4 negative = Int4(0);
-		if(state.addressingModeU == ADDRESSING_BORDER) negative |= uuuu;
-		if(state.addressingModeV == ADDRESSING_BORDER) negative |= vvvv;
-		if(state.addressingModeW == ADDRESSING_BORDER) negative |= wwww;
+		if(state.addressingModeU != ADDRESSING_UNUSED) negative |= uuuu;
+		if(state.addressingModeV != ADDRESSING_UNUSED) negative |= vvvv;
+		if(state.addressingModeW != ADDRESSING_UNUSED) negative |= wwww;
+		if(state.addressingModeY != ADDRESSING_UNUSED) negative |= cubeArrayId;
 		valid = CmpNLT(negative, Int4(0));
 	}
 
@@ -2123,7 +2124,7 @@
 	}
 
 	Vector4f out;
-	out.x = As<Float4>((valid & As<Int4>(c.x)) | (~valid & borderRGB));
+	out.x = As<Float4>((valid & As<Int4>(c.x)) | (~valid & borderRGB));  // TODO: IfThenElse()
 	out.y = As<Float4>((valid & As<Int4>(c.y)) | (~valid & borderRGB));
 	out.z = As<Float4>((valid & As<Int4>(c.z)) | (~valid & borderRGB));
 	out.w = As<Float4>((valid & As<Int4>(c.w)) | (~valid & borderA));
@@ -2252,7 +2253,16 @@
 
 	if(function == Fetch)
 	{
-		xyz0 = Min(Max(((function.offset != 0) && (addressingMode != ADDRESSING_LAYER)) ? As<Int4>(uvw) + As<Int4>(texOffset) : As<Int4>(uvw), Int4(0)), maxXYZ);
+		Int4 xyz = (function.offset && (addressingMode != ADDRESSING_LAYER)) ? As<Int4>(uvw) + As<Int4>(texOffset) : As<Int4>(uvw);
+		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.
+		Int4 outOfBounds = CmpNEQ(xyz, xyz0);
+		xyz0 |= outOfBounds;
 	}
 	else if(addressingMode == ADDRESSING_LAYER)  // Note: Offset does not apply to array layers
 	{
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index 1660da1..e9203ab 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -78,6 +78,14 @@
 			samplerState.minLod = sampler->minLod;
 			samplerState.maxLod = sampler->maxLod;
 		}
+		else
+		{
+			// 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;
+		}
 
 		return emitSamplerRoutine(instruction, samplerState);
 	};
@@ -349,14 +357,18 @@
 
 	if(!sampler)
 	{
-		// OpImageFetch does not take a sampler descriptor, but still needs a valid,
-		// arbitrary addressing mode that prevents out-of-bounds accesses:
+		// OpImageFetch does not take a sampler descriptor, but still needs a valid
+		// addressing mode that prevents out-of-bounds accesses:
 		// "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. In that case, an invalid texel is replaced as described by the
 		//  robustBufferAccess feature." - Vulkan 1.1
 
-		return ADDRESSING_WRAP;
+		// 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;
 	}
 
 	VkSamplerAddressMode addressMode = VK_SAMPLER_ADDRESS_MODE_REPEAT;