Parse 'Sample' image instruction operand

OpImageFetch/Read/Write can have an optional 'Sample' operand which
indicates the sample index to operate on. It is orthogonal to other
operands so it is indicated by a new Boolean field in
sw::SamplerFunction and SpirvShader::ImageInstruction. The offset
operand was also turned into a Boolean field / component count.

The sample index is not wired up to be taken into account by the Fetch
sampling routine yet. OpImageRead already had support for the Sample
operand, but OpImageWrite does not.

Bug: b/135265531
Tests: dEQP-VK.pipeline.multisample.sampled_image.*
Change-Id: I20f50a888436775b996221e8283a4c4ab7f28e17
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/32908
Tested-by: Nicolas Capens <nicolascapens@google.com>
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Chris Forbes <chrisforbes@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Pipeline/SamplerCore.cpp b/src/Pipeline/SamplerCore.cpp
index ea950ae..302c73e 100644
--- a/src/Pipeline/SamplerCore.cpp
+++ b/src/Pipeline/SamplerCore.cpp
@@ -1231,7 +1231,7 @@
 	void SamplerCore::computeIndices(UInt index[4], Short4 uuuu, Short4 vvvv, Short4 wwww, Vector4f &offset, const Pointer<Byte> &mipmap, SamplerFunction function)
 	{
 		bool texelFetch = (function == Fetch);
-		bool hasOffset = (function.option == Offset);
+		bool hasOffset = (function.offset != 0);
 
 		if(!texelFetch)
 		{
@@ -2088,7 +2088,7 @@
 
 		if(function == Fetch)
 		{
-			xyz0 = Min(Max(((function.option == Offset) && (addressingMode != ADDRESSING_LAYER)) ? As<Int4>(uvw) + As<Int4>(texOffset) : As<Int4>(uvw), Int4(0)), maxXYZ);
+			xyz0 = Min(Max(((function.offset != 0) && (addressingMode != ADDRESSING_LAYER)) ? As<Int4>(uvw) + As<Int4>(texOffset) : As<Int4>(uvw), Int4(0)), maxXYZ);
 		}
 		else if(addressingMode == ADDRESSING_LAYER)   // Note: Offset does not apply to array layers
 		{
@@ -2139,7 +2139,7 @@
 				Float4 floor = Floor(coord);
 				xyz0 = Int4(floor);
 
-				if(function.option == Offset)
+				if(function.offset != 0)
 				{
 					xyz0 += As<Int4>(texOffset);
 				}
@@ -2153,7 +2153,7 @@
 			}
 			else
 			{
-				if(function.option != Offset)
+				if(function.offset == 0)
 				{
 					switch(addressingMode)
 					{
@@ -2197,7 +2197,7 @@
 
 			if(state.textureFilter == FILTER_POINT)
 			{
-				if(addressingMode == ADDRESSING_BORDER || function.option == Offset)
+				if(addressingMode == ADDRESSING_BORDER || function.offset != 0)
 				{
 					xyz0 = Int4(Floor(coord));
 				}
@@ -2223,7 +2223,7 @@
 				f = coord - floor;
 			}
 
-			if(function.option == Offset)
+			if(function.offset != 0)
 			{
 				xyz0 += As<Int4>(texOffset);
 			}
@@ -2243,7 +2243,7 @@
 				xyz0 |= border0;
 				xyz1 |= border1;
 			}
-			else if(function.option == Offset)
+			else if(function.offset != 0)
 			{
 				switch(addressingMode)
 				{
diff --git a/src/Pipeline/SamplerCore.hpp b/src/Pipeline/SamplerCore.hpp
index a516a5c..de32eed 100644
--- a/src/Pipeline/SamplerCore.hpp
+++ b/src/Pipeline/SamplerCore.hpp
@@ -40,21 +40,18 @@
 		SAMPLER_METHOD_LAST = Gather,
 	};
 
-	enum SamplerOption
-	{
-		None,
-		Offset,   // Offset sample location by provided integer coordinates.
-		SAMPLER_OPTION_LAST = Offset,
-	};
-
 	// TODO(b/129523279): Eliminate and use SpirvShader::ImageInstruction instead.
 	struct SamplerFunction
 	{
-		SamplerFunction(SamplerMethod method, SamplerOption option = None) : method(method), option(option) {}
+		SamplerFunction(SamplerMethod method, bool offset = false, bool sample = false)
+			: method(method), offset(offset), sample(sample)
+		{}
+
 		operator SamplerMethod() { return method; }
 
 		const SamplerMethod method;
-		const SamplerOption option;
+		const bool offset;
+		const bool sample;
  	};
 
 	class SamplerCore
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 4a8c890..7294102 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -4783,6 +4783,7 @@
 		bool constOffset = false;
 		Object::ID offsetId = 0;
 		bool sample = false;
+		Object::ID sampleId = 0;
 
 		uint32_t operand = (instruction.isDref() || instruction.samplerMethod == Gather) ? 6 : 5;
 
@@ -4829,14 +4830,17 @@
 
 			if(imageOperands & spv::ImageOperandsSampleMask)
 			{
-				UNIMPLEMENTED("Image operand %x", spv::ImageOperandsSampleMask); (void)sample;
 				sample = true;
+				sampleId = insn.word(operand);
 				imageOperands &= ~spv::ImageOperandsSampleMask;
+
+				ASSERT(instruction.samplerMethod == Fetch);
+				instruction.sample = true;
 			}
 
 			if(imageOperands != 0)
 			{
-				UNIMPLEMENTED("Image operand %x", imageOperands);
+				UNSUPPORTED("Image operand %x", imageOperands);
 			}
 		}
 
@@ -4887,7 +4891,7 @@
 			auto &dxyType = getType(dxValue.type);
 			ASSERT(dxyType.sizeInComponents == getType(dyValue.type).sizeInComponents);
 
-			instruction.gradComponents = dxyType.sizeInComponents;
+			instruction.grad = dxyType.sizeInComponents;
 
 			for(uint32_t j = 0; j < dxyType.sizeInComponents; j++, i++)
 			{
@@ -4913,8 +4917,7 @@
 			auto offsetValue = GenericValue(this, state->routine, offsetId);
 			auto &offsetType = getType(offsetValue.type);
 
-			instruction.samplerOption = Offset;
-			instruction.offsetComponents = offsetType.sizeInComponents;
+			instruction.offset = offsetType.sizeInComponents;
 
 			for(uint32_t j = 0; j < offsetType.sizeInComponents; j++, i++)
 			{
@@ -4922,6 +4925,12 @@
 			}
 		}
 
+		if(sample)
+		{
+			auto sampleValue = GenericValue(this, state->routine, sampleId);
+			in[i] = sampleValue.Float(0);
+		}
+
 		auto samplerFunc = Call(getImageSampler, instruction.parameters, imageDescriptor, sampler);
 
 		Array<SIMD::Float> out(4);
@@ -5455,7 +5464,7 @@
 
 		ASSERT(imageType.definition.opcode() == spv::OpTypeImage);
 
-		// Not handling any image operands yet.
+		// TODO(b/131171141): Not handling any image operands yet.
 		ASSERT(insn.wordCount() == 4);
 
 		auto coordinate = GenericValue(this, state->routine, insn.word(2));
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index c6fd58f..67ce732 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -504,7 +504,7 @@
 
 			SamplerFunction getSamplerFunction() const
 			{
-				return { static_cast<SamplerMethod>(samplerMethod), static_cast<SamplerOption>(samplerOption) };
+				return { static_cast<SamplerMethod>(samplerMethod), offset != 0, sample != 0 };
 			}
 
 			bool isDref() const
@@ -523,22 +523,22 @@
 				{
 					uint32_t variant : BITS(VARIANT_LAST);
 					uint32_t samplerMethod : BITS(SAMPLER_METHOD_LAST);
-					uint32_t samplerOption : BITS(SAMPLER_OPTION_LAST);
 					uint32_t gatherComponent : 2;
 
 					// Parameters are passed to the sampling routine in this order:
 					uint32_t coordinates : 3;       // 1-4 (does not contain projection component)
 				//	uint32_t dref : 1;              // Indicated by Variant::ProjDref|Dref
 				//	uint32_t lodOrBias : 1;         // Indicated by SamplerMethod::Lod|Bias|Fetch
-					uint32_t gradComponents : 2;    // 0-3 (for each of dx / dy)
-					uint32_t offsetComponents : 2;  // 0-3
+					uint32_t grad : 2;              // 0-3 components (for each of dx / dy)
+					uint32_t offset : 2;            // 0-3 components
+					uint32_t sample : 1;            // 0-1 scalar integer
 				};
 
 				uint32_t parameters;
 			};
 		};
 
-		static_assert(sizeof(ImageInstruction) == 4, "ImageInstruction must be 32-bit");
+		static_assert(sizeof(ImageInstruction) == sizeof(uint32_t), "ImageInstruction must be 32-bit");
 
 		int getSerialID() const
 		{
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index 25763a9..a54eb8d 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -164,25 +164,24 @@
 		}
 		else if(instruction.samplerMethod == Grad)
 		{
-			for(uint32_t j = 0; j < instruction.gradComponents; j++, i++)
+			for(uint32_t j = 0; j < instruction.grad; j++, i++)
 			{
 				dsx[j] = in[i];
 			}
 
-			for(uint32_t j = 0; j < instruction.gradComponents; j++, i++)
+			for(uint32_t j = 0; j < instruction.grad; j++, i++)
 			{
 				dsy[j] = in[i];
 			}
 		}
 
-		if(instruction.samplerOption == Offset)
+		for(uint32_t j = 0; j < instruction.offset; j++, i++)
 		{
-			for(uint32_t j = 0; j < instruction.offsetComponents; j++, i++)
-			{
-				offset[j] = in[i];
-			}
+			offset[j] = in[i];
 		}
 
+		// TODO(b/133868964): Handle 'Sample' operand.
+
 		SamplerCore s(constants, samplerState);
 
 		// For explicit-lod instructions the LOD can be different per SIMD lane. SamplerCore currently assumes