Fix clamp-to-border addressing mode

Previously we would check for out-of-range coordinates after filtering
to replace the sampled color with the border color. This is incorrect
as according to Vulkan 1.1 spec section 15.3.3. Texel Replacement the
replacement happens per texel, before filtering.

This is achieved by replacing out-of-range coordinates with -1, which
will get multiplied by pitch and slice for the second and third
coordinate respectively, but will remain negative. These negative
coordinates are then detected during sample fetch to look up the
texel at 0 instead, and then replace it with the border color.

Bug: b/129523279
Test: dEQP-VK.pipeline.image.*
Test: dEQP-VK.pipeline.sampler.*
Change-Id: I0949e6a4701285f7511bf2b1579bd8a303f2ddb6
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/30710
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Chris Forbes <chrisforbes@google.com>
diff --git a/src/Pipeline/SamplerCore.cpp b/src/Pipeline/SamplerCore.cpp
index 20dadd4..bca6f37 100644
--- a/src/Pipeline/SamplerCore.cpp
+++ b/src/Pipeline/SamplerCore.cpp
@@ -126,11 +126,13 @@
 			lod = Min(lod, *Pointer<Float>(sampler + OFFSET(vk::Sampler, maxLod)));
 		}
 
-		// FIXME: YUV is not supported by the floating point path
-		bool forceFloatFiltering = state.highPrecisionFiltering && !hasYuvFormat() && (state.textureFilter != FILTER_POINT);
+		bool force32BitFiltering = state.highPrecisionFiltering && !hasYuvFormat() && (state.textureFilter != FILTER_POINT);
 		bool seamlessCube = (state.addressingModeU == ADDRESSING_SEAMLESS);
 		bool rectangleTexture = (state.textureType == TEXTURE_RECTANGLE);
-		if(hasFloatTexture() || hasUnnormalizedIntegerTexture() || forceFloatFiltering || seamlessCube || rectangleTexture || state.compareEnable)   // FIXME: Mostly identical to integer sampling
+		bool use32BitFiltering = hasFloatTexture() || hasUnnormalizedIntegerTexture() || force32BitFiltering ||
+		                         seamlessCube || rectangleTexture || state.compareEnable || borderModeActive();
+
+		if(use32BitFiltering)
 		{
 			c = sampleFloatFilter(texture, uuuu, vvvv, wwww, qqqq, offset, lod, anisotropy, uDelta, vDelta, face, function);
 
@@ -170,7 +172,7 @@
 				}
 			}
 		}
-		else
+		else  // 16-bit filtering.
 		{
 			Vector4s cs = sampleFilter(texture, uuuu, vvvv, wwww, offset, lod, anisotropy, uDelta, vDelta, face, function);
 
@@ -294,17 +296,6 @@
 		return c;
 	}
 
-	void SamplerCore::border(Short4 &mask, Float4 &coordinates)
-	{
-		Int4 border = As<Int4>(CmpLT(Abs(coordinates - Float4(0.5f)), Float4(0.5f)));
-		mask = As<Short4>(Int2(As<Int4>(PackSigned(border, border))));
-	}
-
-	void SamplerCore::border(Int4 &mask, Float4 &coordinates)
-	{
-		mask = As<Int4>(CmpLT(Abs(coordinates - Float4(0.5f)), Float4(0.5f)));
-	}
-
 	Short4 SamplerCore::offsetSample(Short4 &uvw, Pointer<Byte> &mipmap, int halfOffset, bool wrap, int count, Float &lod)
 	{
 		Short4 offset = *Pointer<Short4>(mipmap + halfOffset);
@@ -384,80 +375,6 @@
 			if(!hasUnsignedTextureComponent(3)) c.w += c.w;
 		}
 
-		Short4 borderMask;
-
-		if(state.addressingModeU == ADDRESSING_BORDER)
-		{
-			Short4 u0;
-
-			border(u0, u);
-
-			borderMask = u0;
-		}
-
-		if(state.addressingModeV == ADDRESSING_BORDER)
-		{
-			Short4 v0;
-
-			border(v0, v);
-
-			if(state.addressingModeU == ADDRESSING_BORDER)
-			{
-				borderMask &= v0;
-			}
-			else
-			{
-				borderMask = v0;
-			}
-		}
-
-		if(state.addressingModeW == ADDRESSING_BORDER && state.textureType == TEXTURE_3D)
-		{
-			Short4 s0;
-
-			border(s0, w);
-
-			if(state.addressingModeU == ADDRESSING_BORDER ||
-			   state.addressingModeV == ADDRESSING_BORDER)
-			{
-				borderMask &= s0;
-			}
-			else
-			{
-				borderMask = s0;
-			}
-		}
-
-		if(state.addressingModeU == ADDRESSING_BORDER ||
-		   state.addressingModeV == ADDRESSING_BORDER ||
-		   (state.addressingModeW == ADDRESSING_BORDER && state.textureType == TEXTURE_3D))
-		{
-			Short4 borderRgb;
-			Short4 borderA;
-			switch (state.border)
-			{
-			case VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK:
-				borderRgb = Short4(0);
-				borderA = Short4(0);
-				break;
-			case VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK:
-				borderRgb = Short4(0);
-				borderA = hasUnsignedTextureComponent(0) ? Short4(0xffff) : Short4(0x7fff);
-				break;
-			case VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE:
-				borderRgb = hasUnsignedTextureComponent(0) ? Short4(0xffff) : Short4(0x7fff);
-				borderA = hasUnsignedTextureComponent(0) ? Short4(0xffff) : Short4(0x7fff);
-				break;
-			default:
-				UNIMPLEMENTED("snorm/unorm border %u", state.border);
-			}
-
-			c.x = (borderMask & c.x) | (~borderMask & borderRgb);
-			c.y = (borderMask & c.y) | (~borderMask & borderRgb);
-			c.z = (borderMask & c.z) | (~borderMask & borderRgb);
-			c.w = (borderMask & c.w) | (~borderMask & borderA);
-		}
-
 		return c;
 	}
 
@@ -874,93 +791,6 @@
 			c.w = (cc.w - c.w) * lod4 + c.w;
 		}
 
-		Int4 borderMask;
-
-		if(state.addressingModeU == ADDRESSING_BORDER)
-		{
-			Int4 u0;
-
-			border(u0, u);
-
-			borderMask = u0;
-		}
-
-		if(state.addressingModeV == ADDRESSING_BORDER)
-		{
-			Int4 v0;
-
-			border(v0, v);
-
-			if(state.addressingModeU == ADDRESSING_BORDER)
-			{
-				borderMask &= v0;
-			}
-			else
-			{
-				borderMask = v0;
-			}
-		}
-
-		if(state.addressingModeW == ADDRESSING_BORDER && state.textureType == TEXTURE_3D)
-		{
-			Int4 s0;
-
-			border(s0, w);
-
-			if(state.addressingModeU == ADDRESSING_BORDER ||
-			   state.addressingModeV == ADDRESSING_BORDER)
-			{
-				borderMask &= s0;
-			}
-			else
-			{
-				borderMask = s0;
-			}
-		}
-
-		if(state.addressingModeU == ADDRESSING_BORDER ||
-		   state.addressingModeV == ADDRESSING_BORDER ||
-		   (state.addressingModeW == ADDRESSING_BORDER && state.textureType == TEXTURE_3D))
-		{
-			Int4 borderRgb;
-			Int4 borderA;
-
-			switch (state.border)
-			{
-			case VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK:
-				borderRgb = As<Int4>(Float4(0));
-				borderA = As<Int4>(Float4(0));
-				break;
-			case VK_BORDER_COLOR_INT_TRANSPARENT_BLACK:
-				borderRgb = Int4(0);
-				borderA = Int4(0);
-				break;
-			case VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK:
-				borderRgb = As<Int4>(Float4(0));
-				borderA = As<Int4>(Float4(1));
-				break;
-			case VK_BORDER_COLOR_INT_OPAQUE_BLACK:
-				borderRgb = Int4(0);
-				borderA = Int4(1);
-				break;
-			case VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE:
-				borderRgb = As<Int4>(Float4(1));
-				borderA = As<Int4>(Float4(1));
-				break;
-			case VK_BORDER_COLOR_INT_OPAQUE_WHITE:
-				borderRgb = Int4(1);
-				borderA = Int4(1);
-				break;
-			default:
-				UNIMPLEMENTED("sint/uint/sfloat border: %u", state.border);
-			}
-
-			c.x = As<Float4>((borderMask & As<Int4>(c.x)) | (~borderMask & borderRgb));
-			c.y = As<Float4>((borderMask & As<Int4>(c.y)) | (~borderMask & borderRgb));
-			c.z = As<Float4>((borderMask & As<Int4>(c.z)) | (~borderMask & borderRgb));
-			c.w = As<Float4>((borderMask & As<Int4>(c.w)) | (~borderMask & borderA));
-		}
-
 		return c;
 	}
 
@@ -1479,7 +1309,7 @@
 		}
 	}
 
-	void SamplerCore::computeIndices(UInt index[4], Int4& uuuu, Int4& vvvv, Int4& wwww, const Pointer<Byte> &mipmap, SamplerFunction function)
+	void SamplerCore::computeIndices(UInt index[4], Int4 uuuu, Int4 vvvv, Int4 wwww, Int4 valid, const Pointer<Byte> &mipmap, SamplerFunction function)
 	{
 		UInt4 indices = uuuu + vvvv;
 
@@ -1488,6 +1318,13 @@
 			indices += As<UInt4>(wwww);
 		}
 
+		if(borderModeActive())
+		{
+			// Texels out of range are still sampled before being replaced
+			// with the border color, so sample them at linear index 0.
+			indices &= As<UInt4>(valid);
+		}
+
 		for(int i = 0; i < 4; i++)
 		{
 			index[i] = Extract(As<Int4>(indices), i);
@@ -1734,9 +1571,9 @@
 		if(hasYuvFormat())
 		{
 			// Generic YPbPr to RGB transformation
-				// R = Y                               +           2 * (1 - Kr) * Pr
-				// G = Y - 2 * Kb * (1 - Kb) / Kg * Pb - 2 * Kr * (1 - Kr) / Kg * Pr
-				// B = Y +           2 * (1 - Kb) * Pb
+			// R = Y                               +           2 * (1 - Kr) * Pr
+			// G = Y - 2 * Kb * (1 - Kb) / Kg * Pb - 2 * Kr * (1 - Kr) / Kg * Pr
+			// B = Y +           2 * (1 - Kb) * Pb
 
 			float Kb = 0.114f;
 			float Kr = 0.299f;
@@ -1838,11 +1675,23 @@
 
 	Vector4f SamplerCore::sampleTexel(Int4 &uuuu, Int4 &vvvv, Int4 &wwww, Float4 &z, Pointer<Byte> &mipmap, Pointer<Byte> buffer[4], SamplerFunction function)
 	{
-		Vector4f c;
+		Int4 valid;
+
+		if(borderModeActive())
+		{
+			// 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;
+			valid = CmpNLT(negative, Int4(0));
+		}
 
 		UInt index[4];
 		UInt4 t0, t1, t2, t3;
-		computeIndices(index, uuuu, vvvv, wwww, mipmap, function);
+		computeIndices(index, uuuu, vvvv, wwww, valid, mipmap, function);
+
+		Vector4f c;
 
 		if(hasFloatTexture() || has32bitIntegerTextureComponents())
 		{
@@ -2027,9 +1876,59 @@
 			c.w = Float4(1.0f);
 		}
 
+		if(borderModeActive())
+		{
+			c = replaceBorderTexel(c, valid);
+		}
+
 		return c;
 	}
 
+	Vector4f SamplerCore::replaceBorderTexel(const Vector4f &c, Int4 valid)
+	{
+		Int4 borderRGB;
+		Int4 borderA;
+
+		bool scaled = !hasFloatTexture() && !hasUnnormalizedIntegerTexture() && !state.compareEnable;
+		bool sign = !hasUnsignedTextureComponent(0);
+		Int4 float_one = scaled ? As<Int4>(Float4(sign ? 0x7FFF : 0xFFFF)) : As<Int4>(Float4(1.0f));
+
+		switch(state.border)
+		{
+		case VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK:
+		case VK_BORDER_COLOR_INT_TRANSPARENT_BLACK:
+			borderRGB = Int4(0);
+			borderA = Int4(0);
+			break;
+		case VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK:
+			borderRGB = Int4(0);
+			borderA = float_one;
+			break;
+		case VK_BORDER_COLOR_INT_OPAQUE_BLACK:
+			borderRGB = Int4(0);
+			borderA = Int4(1);
+			break;
+		case VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE:
+			borderRGB = float_one;
+			borderA = float_one;
+			break;
+		case VK_BORDER_COLOR_INT_OPAQUE_WHITE:
+			borderRGB = Int4(1);
+			borderA = Int4(1);
+			break;
+		default:
+			UNIMPLEMENTED("sint/uint/sfloat border: %u", state.border);
+		}
+
+		Vector4f out;
+		out.x = As<Float4>((valid & As<Int4>(c.x)) | (~valid & borderRGB));
+		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));
+
+		return out;
+	}
+
 	void SamplerCore::selectMipmap(Pointer<Byte> &texture, Pointer<Byte> buffer[4], Pointer<Byte> &mipmap, Float &lod, Int face[4], bool secondLOD)
 	{
 		if(state.mipmapFilter == MIPMAP_NONE)
@@ -2055,41 +1954,39 @@
 
 		if(state.textureType != TEXTURE_CUBE)
 		{
-			buffer[0] = *Pointer<Pointer<Byte> >(mipmap + OFFSET(Mipmap,buffer[0]));
+			buffer[0] = *Pointer<Pointer<Byte>>(mipmap + OFFSET(Mipmap,buffer[0]));
 
 			if(hasYuvFormat())
 			{
-				buffer[1] = *Pointer<Pointer<Byte> >(mipmap + OFFSET(Mipmap,buffer[1]));
-				buffer[2] = *Pointer<Pointer<Byte> >(mipmap + OFFSET(Mipmap,buffer[2]));
+				buffer[1] = *Pointer<Pointer<Byte>>(mipmap + OFFSET(Mipmap,buffer[1]));
+				buffer[2] = *Pointer<Pointer<Byte>>(mipmap + OFFSET(Mipmap,buffer[2]));
 			}
 		}
 		else
 		{
 			for(int i = 0; i < 4; i++)
 			{
-				buffer[i] = *Pointer<Pointer<Byte> >(mipmap + OFFSET(Mipmap,buffer) + face[i] * sizeof(void*));
+				buffer[i] = *Pointer<Pointer<Byte>>(mipmap + OFFSET(Mipmap,buffer) + face[i] * sizeof(void*));
 			}
 		}
 	}
 
 	Int4 SamplerCore::computeFilterOffset(Float &lod)
 	{
-		Int4 filter = -1;
-
 		if(state.textureFilter == FILTER_POINT)
 		{
-			filter = 0;
+			return Int4(0);
 		}
 		else if(state.textureFilter == FILTER_MIN_LINEAR_MAG_POINT)
 		{
-			filter = CmpNLE(Float4(lod), Float4(0.0f));
+			return CmpNLE(Float4(lod), Float4(0.0f));
 		}
 		else if(state.textureFilter == FILTER_MIN_POINT_MAG_LINEAR)
 		{
-			filter = CmpLE(Float4(lod), Float4(0.0f));
+			return CmpLE(Float4(lod), Float4(0.0f));
 		}
 
-		return filter;
+		return Int4(~0);
 	}
 
 	Short4 SamplerCore::address(Float4 &uw, AddressingMode addressingMode, Pointer<Byte> &mipmap)
@@ -2169,7 +2066,6 @@
 				switch(addressingMode)
 				{
 				case ADDRESSING_CLAMP:
-				case ADDRESSING_BORDER:
 				case ADDRESSING_SEAMLESS:
 					// Linear filtering of cube doesn't require clamping because the coordinates
 					// are already in [0, 1] range and numerical imprecision is tolerated.
@@ -2180,21 +2076,24 @@
 					}
 					break;
 				case ADDRESSING_MIRROR:
-				{
-					Float4 half = As<Float4>(Int4(halfBits));
-					Float4 one = As<Float4>(Int4(oneBits));
-					Float4 two = As<Float4>(Int4(twoBits));
-					coord = one - Abs(two * Frac(coord * half) - one);
-				}
-				break;
+					{
+						Float4 half = As<Float4>(Int4(halfBits));
+						Float4 one = As<Float4>(Int4(oneBits));
+						Float4 two = As<Float4>(Int4(twoBits));
+						coord = one - Abs(two * Frac(coord * half) - one);
+					}
+					break;
 				case ADDRESSING_MIRRORONCE:
-				{
-					Float4 half = As<Float4>(Int4(halfBits));
-					Float4 one = As<Float4>(Int4(oneBits));
-					Float4 two = As<Float4>(Int4(twoBits));
-					coord = one - Abs(two * Frac(Min(Max(coord, -one), two) * half) - one);
-				}
-				break;
+					{
+						Float4 half = As<Float4>(Int4(halfBits));
+						Float4 one = As<Float4>(Int4(oneBits));
+						Float4 two = As<Float4>(Int4(twoBits));
+						coord = one - Abs(two * Frac(Min(Max(coord, -one), two) * half) - one);
+					}
+					break;
+				case ADDRESSING_BORDER:
+					// Don't map to a valid range here.
+					break;
 				default:   // Wrap
 					coord = Frac(coord);
 					break;
@@ -2206,7 +2105,14 @@
 			if(state.textureFilter == FILTER_POINT ||
 			   state.textureFilter == FILTER_GATHER)
 			{
-				xyz0 = Int4(coord);
+				if(addressingMode == ADDRESSING_BORDER)
+				{
+					xyz0 = Int4(Floor(coord));
+				}
+				else  // Can't have negative coordinates, so floor() is redundant when casting to int.
+				{
+					xyz0 = Int4(coord);
+				}
 			}
 			else
 			{
@@ -2237,7 +2143,15 @@
 
 			xyz1 = xyz0 - filter;   // Increment
 
-			if(function.option == Offset)
+			if(addressingMode == ADDRESSING_BORDER)
+			{
+				// Replace the coordinates with -1 if they're out of range.
+				Int4 border0 = CmpLT(xyz0, Int4(0)) | CmpNLT(xyz0, dim);
+				Int4 border1 = CmpLT(xyz1, Int4(0)) | CmpNLT(xyz1, dim);
+				xyz0 |= border0;
+				xyz1 |= border1;
+			}
+			else if(function.option == Offset)
 			{
 				switch(addressingMode)
 				{
@@ -2245,8 +2159,7 @@
 					ASSERT(false);   // Cube sampling doesn't support offset.
 				case ADDRESSING_MIRROR:
 				case ADDRESSING_MIRRORONCE:
-				case ADDRESSING_BORDER:
-					// FIXME: Implement ADDRESSING_MIRROR, ADDRESSING_MIRRORONCE, and ADDRESSING_BORDER.
+					// FIXME: Implement ADDRESSING_MIRROR and ADDRESSING_MIRRORONCE.
 					// Fall through to Clamp.
 				case ADDRESSING_CLAMP:
 					xyz0 = Min(Max(xyz0, Int4(0)), maxXYZ);
@@ -2266,7 +2179,6 @@
 					break;
 				case ADDRESSING_MIRROR:
 				case ADDRESSING_MIRRORONCE:
-				case ADDRESSING_BORDER:
 				case ADDRESSING_CLAMP:
 					xyz0 = Max(xyz0, Int4(0));
 					xyz1 = Min(xyz1, maxXYZ);
@@ -2363,4 +2275,11 @@
 	{
 		return state.textureFormat.isRGBComponent(component);
 	}
+
+	bool SamplerCore::borderModeActive() const
+	{
+		return state.addressingModeU == ADDRESSING_BORDER ||
+		       state.addressingModeV == ADDRESSING_BORDER ||
+		       state.addressingModeW == ADDRESSING_BORDER;
+	}
 }
diff --git a/src/Pipeline/SamplerCore.hpp b/src/Pipeline/SamplerCore.hpp
index 059daca7..434235a 100644
--- a/src/Pipeline/SamplerCore.hpp
+++ b/src/Pipeline/SamplerCore.hpp
@@ -63,8 +63,6 @@
 		Vector4f sampleTexture(Pointer<Byte> &texture, Pointer<Byte> &sampler, Float4 &u, Float4 &v, Float4 &w, Float4 &q, Float4 &lodOrBias, Vector4f &dsx, Vector4f &dsy, Vector4f &offset, SamplerFunction function);
 
 	private:
-		void border(Short4 &mask, Float4 &coordinates);
-		void border(Int4 &mask, Float4 &coordinates);
 		Short4 offsetSample(Short4 &uvw, Pointer<Byte> &mipmap, int halfOffset, bool wrap, int count, Float &lod);
 		Vector4s sampleFilter(Pointer<Byte> &texture, Float4 &u, Float4 &v, Float4 &w, Vector4f &offset, Float &lod, Float &anisotropy, Float4 &uDelta, Float4 &vDelta, Int face[4], SamplerFunction function);
 		Vector4s sampleAniso(Pointer<Byte> &texture, Float4 &u, Float4 &v, Float4 &w, Vector4f &offset, Float &lod, Float &anisotropy, Float4 &uDelta, Float4 &vDelta, Int face[4], bool secondLOD, SamplerFunction function);
@@ -84,10 +82,11 @@
 		void cubeFace(Int face[4], Float4 &U, Float4 &V, Float4 &x, Float4 &y, Float4 &z, Float4 &M);
 		Short4 applyOffset(Short4 &uvw, Float4 &offset, const Int4 &whd, AddressingMode mode);
 		void computeIndices(UInt index[4], Short4 uuuu, Short4 vvvv, Short4 wwww, Vector4f &offset, const Pointer<Byte> &mipmap, SamplerFunction function);
-		void computeIndices(UInt index[4], Int4& uuuu, Int4& vvvv, Int4& wwww, const Pointer<Byte> &mipmap, SamplerFunction function);
+		void computeIndices(UInt index[4], Int4 uuuu, Int4 vvvv, Int4 wwww, Int4 valid, const Pointer<Byte> &mipmap, SamplerFunction function);
 		Vector4s sampleTexel(Short4 &u, Short4 &v, Short4 &s, Vector4f &offset, Pointer<Byte> &mipmap, Pointer<Byte> buffer[4], SamplerFunction function);
 		Vector4s sampleTexel(UInt index[4], Pointer<Byte> buffer[4]);
 		Vector4f sampleTexel(Int4 &u, Int4 &v, Int4 &s, Float4 &z, Pointer<Byte> &mipmap, Pointer<Byte> buffer[4], SamplerFunction function);
+		Vector4f replaceBorderTexel(const Vector4f &c, Int4 valid);
 		void selectMipmap(Pointer<Byte> &texture, Pointer<Byte> buffer[4], Pointer<Byte> &mipmap, Float &lod, Int face[4], bool secondLOD);
 		Short4 address(Float4 &uw, AddressingMode addressingMode, Pointer<Byte>& mipmap);
 		void address(Float4 &uw, Int4& xyz0, Int4& xyz1, Float4& f, Pointer<Byte>& mipmap, Float4 &texOffset, Int4 &filter, int whd, AddressingMode addressingMode, SamplerFunction function);
@@ -108,6 +107,7 @@
 		bool has32bitIntegerTextureComponents() const;
 		bool hasYuvFormat() const;
 		bool isRGBComponent(int component) const;
+		bool borderModeActive() const;
 
 		Pointer<Byte> &constants;
 		const Sampler &state;
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index b53624c..4d9e677 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -238,10 +238,25 @@
 		}
 		break;
 	case VK_IMAGE_VIEW_TYPE_1D:
+		if(coordinateIndex >= 1)
+		{
+			return ADDRESSING_WRAP;  // Unused, but must avoid BORDER mode.
+		}
+		break;
 	case VK_IMAGE_VIEW_TYPE_2D:
+		if(coordinateIndex == 2)
+		{
+			return ADDRESSING_WRAP;  // Unused, but must avoid BORDER mode.
+		}
+		break;
 	case VK_IMAGE_VIEW_TYPE_3D:
 		break;
 	case VK_IMAGE_VIEW_TYPE_1D_ARRAY:  // Treated as 2D texture with second coordinate 0.
+		if(coordinateIndex == 1)
+		{
+			return ADDRESSING_WRAP;  // Unused, but must avoid BORDER mode.
+		}
+		// Fall through to 2D array case
 	case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
 		if(coordinateIndex == 2)
 		{