Implement OpImageSampleExplicitLod with Grad operand

OpImageSampleExplicitLod can either have a Lod or Grad operand.

Bug: b/129523279
Test: dEQP-VK.glsl.texture_functions.texturegrad.sampler2d_fixed_fragment
Change-Id: I0c4a885cc6833614572ed7a061b53c9f41838f0b
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/30032
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Chris Forbes <chrisforbes@google.com>
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 57bbd47..4b72909 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -4456,16 +4456,23 @@
 
 	SpirvShader::EmitResult SpirvShader::EmitImageSampleImplicitLod(InsnIterator insn, EmitState *state) const
 	{
-		ImageInstruction imageInstruction(Implicit);
-
-		return EmitImageSample(imageInstruction, insn, state);
+		return EmitImageSample({Implicit}, insn, state);
 	}
 
 	SpirvShader::EmitResult SpirvShader::EmitImageSampleExplicitLod(InsnIterator insn, EmitState *state) const
 	{
-		ImageInstruction imageInstruction(Lod);
+		uint32_t imageOperands = static_cast<spv::ImageOperandsMask>(insn.word(5));
 
-		return EmitImageSample(imageInstruction, insn, state);
+		if((imageOperands & spv::ImageOperandsLodMask) == imageOperands)
+		{
+			return EmitImageSample({Lod}, insn, state);
+		}
+		else if((imageOperands & spv::ImageOperandsGradMask) == imageOperands)
+		{
+			return EmitImageSample({Grad}, insn, state);
+		}
+		else UNIMPLEMENTED("Image Operands %x", imageOperands);
+		return EmitResult::Continue;
 	}
 
 	SpirvShader::EmitResult SpirvShader::EmitImageSample(ImageInstruction instruction, InsnIterator insn, EmitState *state) const
@@ -4485,14 +4492,13 @@
 		auto sampler = *Pointer<Pointer<Byte>>(descriptor + OFFSET(vk::SampledImageDescriptor, sampler)); // vk::Sampler*
 		auto imageView = *Pointer<Pointer<Byte>>(descriptor + OFFSET(vk::SampledImageDescriptor, imageView)); // vk::ImageView*
 
-		instruction.coordinates = coordinateType.sizeInComponents;
-		auto samplerFunc = Call(getImageSampler, instruction.parameters, imageView, sampler);
-
 		uint32_t imageOperands = spv::ImageOperandsMaskNone;
 		bool bias = false;
 		bool lod = false;
 		Object::ID lodId = 0;
 		bool grad = false;
+		Object::ID gradDxId = 0;
+		Object::ID gradDyId = 0;
 		bool constOffset = false;
 		bool sample = false;
 
@@ -4518,8 +4524,11 @@
 
 			if(imageOperands & spv::ImageOperandsGradMask)
 			{
-				UNIMPLEMENTED("Image operand %x", spv::ImageOperandsGradMask); (void)grad;
+				ASSERT(!lod);  // SPIR-V 1.3: "It is invalid to set both the Lod and Grad bits."
 				grad = true;
+				gradDxId = insn.word(operand + 0);
+				gradDyId = insn.word(operand + 1);
+				operand += 2;
 				imageOperands &= ~spv::ImageOperandsGradMask;
 			}
 
@@ -4543,7 +4552,7 @@
 			}
 		}
 
-		Array<SIMD::Float> in(coordinateType.sizeInComponents + lod);
+		Array<SIMD::Float> in(16);  // Maximum 16 input parameter components.
 
 		uint32_t i = 0;
 		for( ; i < coordinateType.sizeInComponents; i++)
@@ -4557,6 +4566,31 @@
 			in[i] = lodValue.Float(0);
 			i++;
 		}
+		else if(grad)
+		{
+			auto dxValue = GenericValue(this, state->routine, gradDxId);
+			auto dyValue = GenericValue(this, state->routine, gradDyId);
+			auto &dxyType = getType(dxValue.type);
+			ASSERT(dxyType.sizeInComponents == getType(dyValue.type).sizeInComponents);
+
+			instruction.gradComponents = dxyType.sizeInComponents;
+
+			for(uint32_t j = 0; j < dxyType.sizeInComponents; j++)
+			{
+				in[i] = dxValue.Float(j);
+				i++;
+			}
+
+			for(uint32_t j = 0; j < dxyType.sizeInComponents; j++)
+			{
+				in[i] = dyValue.Float(j);
+				i++;
+			}
+		}
+
+		instruction.coordinates = coordinateType.sizeInComponents;
+
+		auto samplerFunc = Call(getImageSampler, instruction.parameters, imageView, sampler);
 
 		Array<SIMD::Float> out(4);
 		Call<ImageSampler>(samplerFunc, sampledImage.base, &in[0], &out[0], state->routine->constants);
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index beb21b9..143b282 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -491,7 +491,8 @@
 				struct
 				{
 					uint32_t samplerMethod : BITS(SAMPLER_METHOD_LAST);
-					uint32_t coordinates : 3;
+					uint32_t coordinates : 3;     // 1-4
+					uint32_t gradComponents : 2;  // 0-3 (for each of dx / dy)
 				};
 
 				uint32_t parameters = 0;
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index baf5c32..d28abc4 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -119,12 +119,23 @@
 		uvw[1] = SIMD::Float(0);
 	}
 
+	// Lod and Grad are explicit-lod image operands, and always come after the coordinates.
 	if(instruction.samplerMethod == Lod)
 	{
-		// Lod is the second optional image operand, and is incompatible with the first one (Bias),
-		// so it always comes after the coordinates.
 		bias = in[instruction.coordinates];
 	}
+	else if(instruction.samplerMethod == Grad)
+	{
+		for(uint32_t i = 0; i < instruction.gradComponents; i++)
+		{
+			dsx[i] = in[instruction.coordinates + i];
+		}
+
+		for(uint32_t i = 0; i < instruction.gradComponents; i++)
+		{
+			dsy[i] = in[instruction.coordinates + instruction.gradComponents + i];
+		}
+	}
 
 	Vector4f sample = s.sampleTexture(texture, uvw[0], uvw[1], uvw[2], q, bias, dsx, dsy, offset, samplerFunction);