Optimize OpImageFetch for single-level mipmaps

The initial implementation of single-level mipmap LOD optimization left
out Fetch operations due to causing test failures. The root cause is
that minLod == maxLod is being used to indicate during sampling routine
generation that the image has only a single mipmap level, but this is
also true when we have no sampler object, which leaves these fields
both initialized at 0.

This change changes their initial value to [-1000, 1000]. While any
arbitrary non-equal values could be used, this is inspired by OpenGL's
defaults for GL_TEXTURE_MIN_LOD and GL_TEXTURE_MAX_LOD.

We then set them both to 0 for Fetch operations if the image view has a
single level.

Bug: b/151263485
Change-Id: I2ab565f5126cc8f8137f051704a6350853396e50
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/56112
Tested-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
Reviewed-by: Sean Risser <srisser@google.com>
diff --git a/src/Device/Sampler.hpp b/src/Device/Sampler.hpp
index 712977d..e42fb30 100644
--- a/src/Device/Sampler.hpp
+++ b/src/Device/Sampler.hpp
@@ -111,8 +111,8 @@
 
 	float mipLodBias = 0.0f;
 	float maxAnisotropy = 0.0f;
-	float minLod = 0.0f;
-	float maxLod = 0.0f;
+	float minLod = -1000.0f;
+	float maxLod = 1000.0f;
 
 	bool is1D() const
 	{
diff --git a/src/Pipeline/SamplerCore.cpp b/src/Pipeline/SamplerCore.cpp
index 2239ca9..655ec3f 100644
--- a/src/Pipeline/SamplerCore.cpp
+++ b/src/Pipeline/SamplerCore.cpp
@@ -57,7 +57,7 @@
 	// Determine if we can skip the LOD computation. This is the case when the mipmap has only one level, except for LOD query,
 	// where we have to return the computed value. Anisotropic filtering requires computing the anisotropy factor even for a single mipmap level.
 	bool singleMipLevel = (state.minLod == state.maxLod);
-	bool requiresLodComputation = (function == Query) || (function == Fetch) || (state.textureFilter == FILTER_ANISOTROPIC);
+	bool requiresLodComputation = (function == Query) || (state.textureFilter == FILTER_ANISOTROPIC);
 	bool skipLodComputation = singleMipLevel && !requiresLodComputation;
 
 	if(skipLodComputation)
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index beca443..fd6dc70 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -93,12 +93,21 @@
 				samplerState.maxLod = 0.0f;
 			}
 		}
-		else
+		else  // Fetch
 		{
+			ASSERT(samplerMethod == Fetch);
+
 			// OpImageFetch does not take a sampler descriptor, but for VK_EXT_image_robustness
 			// requires replacing invalid texels with zero.
 			// TODO(b/162327166): Only perform bounds checks when VK_EXT_image_robustness is enabled.
 			samplerState.border = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
+
+			// If there's a single mip level we can skip LOD computation.
+			if(imageViewState.singleMipLevel)
+			{
+				samplerState.minLod = 0.0f;
+				samplerState.maxLod = 0.0f;
+			}
 		}
 
 		return emitSamplerRoutine(instruction, samplerState);