Implement basic sampler parameters

- Implement support for mipmapping:
  Involved fixing retrieving correct texel pointers for each mip level.
- Implement addressing mode.
- Implement filter mode.
- Removed textureSize(). We already have an implementation of
  OpImageQuerySize which doesn't require the sampling code.

For now, these parameters are converted to the legacy sampler enum
values to avoid a big refactoring which may break functionality before
we pass the tests. Future changes will eliminate them in favor of using
the Vulkan sampler parameters directly.

Bug: b/129523279
Test: dEQP-VK.texture.filtering.2d.formats.r8g8b8a8_unorm.*
Test: dEQP-VK.pipeline.sampler.view_type.2d.format.r8g8b8a8_unorm.*
Change-Id: I313e598ae61c0016a5a929bf5c1a53b66b3ff0d1
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/29770
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Alexis Hétu <sugoi@google.com>
diff --git a/src/Device/Renderer.cpp b/src/Device/Renderer.cpp
index 5034f36..03de441 100644
--- a/src/Device/Renderer.cpp
+++ b/src/Device/Renderer.cpp
@@ -504,7 +504,7 @@
 
 				if(draw->renderTarget[index])
 				{
-					data->colorBuffer[index] = (unsigned int*)context->renderTarget[index]->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_COLOR_BIT);
+					data->colorBuffer[index] = (unsigned int*)context->renderTarget[index]->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_COLOR_BIT, 0);
 					data->colorPitchB[index] = context->renderTarget[index]->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0);
 					data->colorSliceB[index] = context->renderTarget[index]->slicePitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0);
 				}
@@ -515,14 +515,14 @@
 
 			if(draw->depthBuffer)
 			{
-				data->depthBuffer = (float*)context->depthBuffer->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_DEPTH_BIT);
+				data->depthBuffer = (float*)context->depthBuffer->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_DEPTH_BIT, 0);
 				data->depthPitchB = context->depthBuffer->rowPitchBytes(VK_IMAGE_ASPECT_DEPTH_BIT, 0);
 				data->depthSliceB = context->depthBuffer->slicePitchBytes(VK_IMAGE_ASPECT_DEPTH_BIT, 0);
 			}
 
 			if(draw->stencilBuffer)
 			{
-				data->stencilBuffer = (unsigned char*)context->stencilBuffer->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_STENCIL_BIT);
+				data->stencilBuffer = (unsigned char*)context->stencilBuffer->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_STENCIL_BIT, 0);
 				data->stencilPitchB = context->stencilBuffer->rowPitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
 				data->stencilSliceB = context->stencilBuffer->slicePitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
 			}
diff --git a/src/Device/Sampler.cpp b/src/Device/Sampler.cpp
index 029f4c4..6f4e61b 100644
--- a/src/Device/Sampler.cpp
+++ b/src/Device/Sampler.cpp
@@ -68,11 +68,8 @@
 
 		compare = COMPARE_BYPASS;
 
-		texture.LOD = 0.0f;
 		exp2LOD = 1.0f;
 
-		texture.baseLevel = 0;
-		texture.maxLevel = 1000;
 		texture.maxLod = MAX_TEXTURE_LOD;
 		texture.minLod = 0;
 	}
@@ -346,16 +343,6 @@
 		this->compare = compare;
 	}
 
-	void Sampler::setBaseLevel(int baseLevel)
-	{
-		texture.baseLevel = baseLevel;
-	}
-
-	void Sampler::setMaxLevel(int maxLevel)
-	{
-		texture.maxLevel = maxLevel;
-	}
-
 	void Sampler::setMinLod(float minLod)
 	{
 		texture.minLod = clamp(minLod, 0.0f, (float)(MAX_TEXTURE_LOD));
@@ -378,7 +365,6 @@
 
 	void Sampler::setMipmapLOD(float LOD)
 	{
-		texture.LOD = LOD;
 		exp2LOD = exp2(LOD);
 	}
 
diff --git a/src/Device/Sampler.hpp b/src/Device/Sampler.hpp
index 0013b8d..841fe70 100644
--- a/src/Device/Sampler.hpp
+++ b/src/Device/Sampler.hpp
@@ -50,7 +50,6 @@
 	{
 		Mipmap mipmap[MIPMAP_LEVELS];
 
-		float LOD;
 		float4 widthHeightLOD;
 		float4 widthLOD;
 		float4 heightLOD;
@@ -59,8 +58,6 @@
 		word4 borderColor4[4];   // FIXME(b/129523279): Part of Vulkan sampler.
 		float4 borderColorF[4];  // FIXME(b/129523279): Part of Vulkan sampler.
 		float maxAnisotropy;     // FIXME(b/129523279): Part of Vulkan sampler.
-		int baseLevel;
-		int maxLevel;
 		float minLod;  // FIXME(b/129523279): Part of Vulkan sampler.
 		float maxLod;  // FIXME(b/129523279): Part of Vulkan sampler.
 	};
@@ -195,8 +192,6 @@
 		void setSwizzleB(SwizzleType swizzleB);
 		void setSwizzleA(SwizzleType swizzleA);
 		void setCompareFunc(CompareFunc compare);
-		void setBaseLevel(int baseLevel);
-		void setMaxLevel(int maxLevel);
 		void setMinLod(float minLod);
 		void setMaxLod(float maxLod);
 
diff --git a/src/Pipeline/SamplerCore.cpp b/src/Pipeline/SamplerCore.cpp
index 7469004..764bd30 100644
--- a/src/Pipeline/SamplerCore.cpp
+++ b/src/Pipeline/SamplerCore.cpp
@@ -255,22 +255,6 @@
 		return c;
 	}
 
-	Vector4f SamplerCore::textureSize(Pointer<Byte> &texture, Float4 &lod)
-	{
-		Vector4f size;
-
-		for(int i = 0; i < 4; ++i)
-		{
-			Int baseLevel = *Pointer<Int>(texture + OFFSET(Texture, baseLevel));
-			Pointer<Byte> mipmap = texture + OFFSET(Texture, mipmap) + (As<Int>(Extract(lod, i)) + baseLevel) * sizeof(Mipmap);
-			size.x = Insert(size.x, As<Float>(Int(*Pointer<Short>(mipmap + OFFSET(Mipmap, width)))), i);
-			size.y = Insert(size.y, As<Float>(Int(*Pointer<Short>(mipmap + OFFSET(Mipmap, height)))), i);
-			size.z = Insert(size.z, As<Float>(Int(*Pointer<Short>(mipmap + OFFSET(Mipmap, depth)))), i);
-		}
-
-		return size;
-	}
-
 	void SamplerCore::border(Short4 &mask, Float4 &coordinates)
 	{
 		Int4 border = As<Int4>(CmpLT(Abs(coordinates - Float4(0.5f)), Float4(0.5f)));
@@ -1152,7 +1136,7 @@
 				duvdxy = Float4(dudxy.xz, dvdxy.xz);
 			}
 
-			// Scale by texture dimensions and global LOD.
+			// Scale by texture dimensions and sampler LOD bias.
 			Float4 dUVdxy = duvdxy * *Pointer<Float4>(texture + OFFSET(Texture,widthHeightLOD));
 
 			Float4 dUV2dxy = dUVdxy * dUVdxy;
@@ -1948,6 +1932,7 @@
 
 			if(state.mipmapFilter == MIPMAP_POINT)
 			{
+				// TODO: Preferred formula is ceil(lod + 0.5) - 1
 				ilod = RoundInt(lod);
 			}
 			else   // MIPMAP_LINEAR
diff --git a/src/Pipeline/SamplerCore.hpp b/src/Pipeline/SamplerCore.hpp
index 2021c36..0ab2a1e 100644
--- a/src/Pipeline/SamplerCore.hpp
+++ b/src/Pipeline/SamplerCore.hpp
@@ -51,7 +51,6 @@
 		SamplerCore(Pointer<Byte> &constants, const Sampler::State &state);
 
 		Vector4f sampleTextureF(Pointer<Byte> &texture, Float4 &u, Float4 &v, Float4 &w, Float4 &q, Float4 &bias, Vector4f &dsx, Vector4f &dsy, Vector4f &offset, SamplerFunction function);
-		static Vector4f textureSize(Pointer<Byte> &mipmap, Float4 &lod);
 
 	private:
 		Vector4s sampleTexture(Pointer<Byte> &texture, Float4 &u, Float4 &v, Float4 &w, Float4 &q, Float4 &bias, Vector4f &dsx, Vector4f &dsy, Vector4f &offset, SamplerFunction function);
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index 8361121..0d5c154 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -23,6 +23,7 @@
 #include "Vulkan/VkDescriptorSet.hpp"
 #include "Common/Types.hpp"
 #include "Device/Config.hpp"
+#include "Device/Sampler.hpp"
 
 #include <spirv/unified1/spirv.hpp>
 
@@ -854,10 +855,15 @@
 
 		using ImageSampler = void(void* image, void* uvsIn, void* texelOut);
 
-		static ImageSampler *getImageSampler(vk::ImageView *imageView, vk::Sampler *sampler);
+		static ImageSampler *getImageSampler(const vk::ImageView *imageView, const vk::Sampler *sampler);
 		static void emitSamplerFunction(
-			vk::ImageView *imageView, vk::Sampler *sampler,
+			const vk::ImageView *imageView, const vk::Sampler *sampler,
 			Pointer<Byte> image, Pointer<SIMD::Float> in, Pointer<Byte> out);
+
+		// TODO(b/129523279): Eliminate conversion and use vk::Sampler members directly.
+		static sw::FilterType convertFilterMode(const vk::Sampler *sampler);
+		static sw::MipmapType convertMipmapMode(const vk::Sampler *sampler);
+		static sw::AddressingMode convertAddressingMode(VkSamplerAddressMode);
 	};
 
 	class SpirvRoutine
diff --git a/src/Pipeline/SpirvShaderSampling.cpp b/src/Pipeline/SpirvShaderSampling.cpp
index e90aad0..6fc587a 100644
--- a/src/Pipeline/SpirvShaderSampling.cpp
+++ b/src/Pipeline/SpirvShaderSampling.cpp
@@ -39,94 +39,151 @@
 
 namespace sw {
 
-SpirvShader::ImageSampler *SpirvShader::getImageSampler(vk::ImageView *imageView, vk::Sampler *sampler)
+SpirvShader::ImageSampler *SpirvShader::getImageSampler(const vk::ImageView *imageView, const vk::Sampler *sampler)
 {
-    // TODO: Move somewhere sensible.
-    static std::unordered_map<uintptr_t, ImageSampler*> cache;
-    static std::mutex mutex;
+	// TODO: Move somewhere sensible.
+	static std::unordered_map<uintptr_t, ImageSampler*> cache;
+	static std::mutex mutex;
 
-    // TODO: Don't use pointers they can be deleted and reused, combine some two
-    // unique ids.
-    auto key = reinterpret_cast<uintptr_t>(imageView) ^ reinterpret_cast<uintptr_t>(sampler);
+	// TODO: Don't use pointers they can be deleted and reused, combine some two
+	// unique ids.
+	auto key = reinterpret_cast<uintptr_t>(imageView) ^ reinterpret_cast<uintptr_t>(sampler);
 
-    std::unique_lock<std::mutex> lock(mutex);
-    auto it = cache.find(key);
-    if (it != cache.end()) { return it->second; }
+	std::unique_lock<std::mutex> lock(mutex);
+	auto it = cache.find(key);
+	if (it != cache.end()) { return it->second; }
 
-    // TODO: Hold a separate mutex lock for the sampler being built.
-    auto function = rr::Function<Void(Pointer<Byte> image, Pointer<SIMD::Float>, Pointer<SIMD::Float>)>();
-    Pointer<Byte> image = function.Arg<0>();
-    Pointer<SIMD::Float> in = function.Arg<1>();
-    Pointer<SIMD::Float> out = function.Arg<2>();
-    emitSamplerFunction(imageView, sampler, image, in, out);
-    auto fptr = reinterpret_cast<ImageSampler*>((void *)function("sampler")->getEntry());
-    cache.emplace(key, fptr);
-    return fptr;
+	// TODO: Hold a separate mutex lock for the sampler being built.
+	auto function = rr::Function<Void(Pointer<Byte> image, Pointer<SIMD::Float>, Pointer<SIMD::Float>)>();
+	Pointer<Byte> image = function.Arg<0>();
+	Pointer<SIMD::Float> in = function.Arg<1>();
+	Pointer<SIMD::Float> out = function.Arg<2>();
+	emitSamplerFunction(imageView, sampler, image, in, out);
+	auto fptr = reinterpret_cast<ImageSampler*>((void *)function("sampler")->getEntry());
+	cache.emplace(key, fptr);
+	return fptr;
 }
 
 void SpirvShader::emitSamplerFunction(
-        vk::ImageView *imageView, vk::Sampler *sampler,
+        const vk::ImageView *imageView, const vk::Sampler *sampler,
         Pointer<Byte> image, Pointer<SIMD::Float> in, Pointer<Byte> out)
 {
-    SIMD::Float u = in[0];
-    SIMD::Float v = in[1];
+	SIMD::Float u = in[0];
+	SIMD::Float v = in[1];
 
-    Pointer<Byte> constants;  // FIXME(b/129523279)
+	Pointer<Byte> constants;  // FIXME(b/129523279)
 
-    Sampler::State samplerState;
-    samplerState.textureType = TEXTURE_2D;                  ASSERT(imageView->getType() == VK_IMAGE_VIEW_TYPE_2D);  // TODO(b/129523279)
-    samplerState.textureFormat = imageView->getFormat();
-    samplerState.textureFilter = FILTER_POINT;              ASSERT(sampler->magFilter == VK_FILTER_NEAREST); ASSERT(sampler->minFilter == VK_FILTER_NEAREST);  // TODO(b/129523279)
+	Sampler::State samplerState;
+	samplerState.textureType = TEXTURE_2D;                  ASSERT(imageView->getType() == VK_IMAGE_VIEW_TYPE_2D);  // TODO(b/129523279)
+	samplerState.textureFormat = imageView->getFormat();
+	samplerState.textureFilter = convertFilterMode(sampler);
 
-    samplerState.addressingModeU = ADDRESSING_WRAP;         ASSERT(sampler->addressModeU == VK_SAMPLER_ADDRESS_MODE_REPEAT);  // TODO(b/129523279)
-    samplerState.addressingModeV = ADDRESSING_WRAP;         ASSERT(sampler->addressModeV == VK_SAMPLER_ADDRESS_MODE_REPEAT);  // TODO(b/129523279)
-    samplerState.addressingModeW = ADDRESSING_WRAP;         ASSERT(sampler->addressModeW == VK_SAMPLER_ADDRESS_MODE_REPEAT);  // TODO(b/129523279)
-    samplerState.mipmapFilter = MIPMAP_POINT;               ASSERT(sampler->mipmapMode == VK_SAMPLER_MIPMAP_MODE_NEAREST);  // TODO(b/129523279)
-    samplerState.sRGB = false;                              ASSERT(imageView->getFormat().isSRGBformat() == false);  // TODO(b/129523279)
-    samplerState.swizzleR = SWIZZLE_RED;                    ASSERT(imageView->getComponentMapping().r == VK_COMPONENT_SWIZZLE_R);  // TODO(b/129523279)
-    samplerState.swizzleG = SWIZZLE_GREEN;                  ASSERT(imageView->getComponentMapping().g == VK_COMPONENT_SWIZZLE_G);  // TODO(b/129523279)
-    samplerState.swizzleB = SWIZZLE_BLUE;                   ASSERT(imageView->getComponentMapping().b == VK_COMPONENT_SWIZZLE_B);  // TODO(b/129523279)
-    samplerState.swizzleA = SWIZZLE_ALPHA;                  ASSERT(imageView->getComponentMapping().a == VK_COMPONENT_SWIZZLE_A);  // TODO(b/129523279)
-    samplerState.highPrecisionFiltering = false;
-    samplerState.compare = COMPARE_BYPASS;                  ASSERT(sampler->compareEnable == VK_FALSE);  // TODO(b/129523279)
+	samplerState.addressingModeU = convertAddressingMode(sampler->addressModeU);
+	samplerState.addressingModeV = convertAddressingMode(sampler->addressModeV);
+	samplerState.addressingModeW = convertAddressingMode(sampler->addressModeW);
+	samplerState.mipmapFilter = convertMipmapMode(sampler);
+	samplerState.sRGB = false;                              ASSERT(imageView->getFormat().isSRGBformat() == false);  // TODO(b/129523279)
+	samplerState.swizzleR = SWIZZLE_RED;                    ASSERT(imageView->getComponentMapping().r == VK_COMPONENT_SWIZZLE_R);  // TODO(b/129523279)
+	samplerState.swizzleG = SWIZZLE_GREEN;                  ASSERT(imageView->getComponentMapping().g == VK_COMPONENT_SWIZZLE_G);  // TODO(b/129523279)
+	samplerState.swizzleB = SWIZZLE_BLUE;                   ASSERT(imageView->getComponentMapping().b == VK_COMPONENT_SWIZZLE_B);  // TODO(b/129523279)
+	samplerState.swizzleA = SWIZZLE_ALPHA;                  ASSERT(imageView->getComponentMapping().a == VK_COMPONENT_SWIZZLE_A);  // TODO(b/129523279)
+	samplerState.highPrecisionFiltering = false;
+	samplerState.compare = COMPARE_BYPASS;                  ASSERT(sampler->compareEnable == VK_FALSE);  // TODO(b/129523279)
 
 //	minLod  // TODO(b/129523279)
 //	maxLod  // TODO(b/129523279)
 //	borderColor  // TODO(b/129523279)
-    ASSERT(sampler->mipLodBias == 0.0f);  // TODO(b/129523279)
-    ASSERT(sampler->anisotropyEnable == VK_FALSE);  // TODO(b/129523279)
-    ASSERT(sampler->unnormalizedCoordinates == VK_FALSE);  // TODO(b/129523279)
+	ASSERT(sampler->mipLodBias == 0.0f);  // TODO(b/129523279)
+	ASSERT(sampler->anisotropyEnable == VK_FALSE);  // TODO(b/129523279)
+	ASSERT(sampler->unnormalizedCoordinates == VK_FALSE);  // TODO(b/129523279)
 
-    SamplerCore s(constants, samplerState);
+	SamplerCore s(constants, samplerState);
 
-    Pointer<Byte> texture = image + OFFSET(vk::SampledImageDescriptor, texture); // sw::Texture*
-    SIMD::Float w(0);     // TODO(b/129523279)
-    SIMD::Float q(0);     // TODO(b/129523279)
-    SIMD::Float bias(0);  // TODO(b/129523279)
-    Vector4f dsx;         // TODO(b/129523279)
-    Vector4f dsy;         // TODO(b/129523279)
-    Vector4f offset;      // TODO(b/129523279)
-    SamplerFunction samplerFunction = { Implicit, None };   // ASSERT(insn.wordCount() == 5);  // TODO(b/129523279)
+	Pointer<Byte> texture = image + OFFSET(vk::SampledImageDescriptor, texture);  // sw::Texture*
+	SIMD::Float w(0);     // TODO(b/129523279)
+	SIMD::Float q(0);     // TODO(b/129523279)
+	SIMD::Float bias(0);  // TODO(b/129523279)
+	Vector4f dsx;         // TODO(b/129523279)
+	Vector4f dsy;         // TODO(b/129523279)
+	Vector4f offset;      // TODO(b/129523279)
+	SamplerFunction samplerFunction = { Implicit, None };  // ASSERT(insn.wordCount() == 5);  // TODO(b/129523279)
 
-    Vector4f sample = s.sampleTextureF(texture, u, v, w, q, bias, dsx, dsy, offset, samplerFunction);
+	Vector4f sample = s.sampleTextureF(texture, u, v, w, q, bias, dsx, dsy, offset, samplerFunction);
 
-    if(!vk::Format(imageView->getFormat()).isNonNormalizedInteger())
-    {
-        Pointer<SIMD::Float> rgba = out;
-        rgba[0] = sample.x;
-        rgba[1] = sample.y;
-        rgba[2] = sample.z;
-        rgba[3] = sample.w;
-    }
-    else
-    {
-        // TODO(b/129523279): Add a Sampler::sampleTextureI() method.
-        Pointer<SIMD::Int> rgba = out;
-        rgba[0] = As<SIMD::Int>(sample.x * SIMD::Float(0xFF));
-        rgba[1] = As<SIMD::Int>(sample.y * SIMD::Float(0xFF));
-        rgba[2] = As<SIMD::Int>(sample.z * SIMD::Float(0xFF));
-        rgba[3] = As<SIMD::Int>(sample.w * SIMD::Float(0xFF));
-    }
+	if(!vk::Format(imageView->getFormat()).isNonNormalizedInteger())
+	{
+		Pointer<SIMD::Float> rgba = out;
+		rgba[0] = sample.x;
+		rgba[1] = sample.y;
+		rgba[2] = sample.z;
+		rgba[3] = sample.w;
+	}
+	else
+	{
+		// TODO(b/129523279): Add a Sampler::sampleTextureI() method.
+		Pointer<SIMD::Int> rgba = out;
+		rgba[0] = As<SIMD::Int>(sample.x * SIMD::Float(0xFF));
+		rgba[1] = As<SIMD::Int>(sample.y * SIMD::Float(0xFF));
+		rgba[2] = As<SIMD::Int>(sample.z * SIMD::Float(0xFF));
+		rgba[3] = As<SIMD::Int>(sample.w * SIMD::Float(0xFF));
+	}
+}
+
+sw::FilterType SpirvShader::convertFilterMode(const vk::Sampler *sampler)
+{
+	switch(sampler->magFilter)
+	{
+	case VK_FILTER_NEAREST:
+		switch(sampler->minFilter)
+		{
+		case VK_FILTER_NEAREST: return FILTER_POINT;
+		case VK_FILTER_LINEAR:  return FILTER_MIN_LINEAR_MAG_POINT;
+		default:
+			UNIMPLEMENTED("minFilter %d", sampler->minFilter);
+			return FILTER_POINT;
+		}
+		break;
+	case VK_FILTER_LINEAR:
+		switch(sampler->minFilter)
+		{
+		case VK_FILTER_NEAREST: return FILTER_MIN_POINT_MAG_LINEAR;
+		case VK_FILTER_LINEAR:  return FILTER_LINEAR;
+		default:
+			UNIMPLEMENTED("minFilter %d", sampler->minFilter);
+			return FILTER_POINT;
+		}
+		break;
+	default:
+		UNIMPLEMENTED("magFilter %d", sampler->magFilter);
+		return FILTER_POINT;
+	}
+}
+
+sw::MipmapType SpirvShader::convertMipmapMode(const vk::Sampler *sampler)
+{
+	switch(sampler->mipmapMode)
+	{
+	case VK_SAMPLER_MIPMAP_MODE_NEAREST: return MIPMAP_POINT;
+	case VK_SAMPLER_MIPMAP_MODE_LINEAR:  return MIPMAP_LINEAR;
+	default:
+		UNIMPLEMENTED("mipmapMode %d", sampler->mipmapMode);
+		return MIPMAP_POINT;
+	}
+}
+
+sw::AddressingMode SpirvShader::convertAddressingMode(VkSamplerAddressMode addressMode)
+{
+	switch(addressMode)
+	{
+	case VK_SAMPLER_ADDRESS_MODE_REPEAT:               return ADDRESSING_WRAP;
+	case VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT:      return ADDRESSING_MIRROR;
+	case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:        return ADDRESSING_CLAMP;
+	case VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER:      return ADDRESSING_BORDER;
+	case VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE: return ADDRESSING_MIRRORONCE;
+	default:
+		UNIMPLEMENTED("addressMode %d", addressMode);
+		return ADDRESSING_WRAP;
+	}
 }
 
 } // namespace sw
\ No newline at end of file
diff --git a/src/Vulkan/VkDescriptorSetLayout.cpp b/src/Vulkan/VkDescriptorSetLayout.cpp
index 8f6f602..13a30e9 100644
--- a/src/Vulkan/VkDescriptorSetLayout.cpp
+++ b/src/Vulkan/VkDescriptorSetLayout.cpp
@@ -248,8 +248,9 @@
 void DescriptorSetLayout::WriteDescriptorSet(DescriptorSet *dstSet, VkDescriptorUpdateTemplateEntry const &entry, char const *src)
 {
 	DescriptorSetLayout* dstLayout = dstSet->layout;
+	auto &binding = dstLayout->bindings[dstLayout->getBindingIndex(entry.dstBinding)];
 	ASSERT(dstLayout);
-	ASSERT(dstLayout->bindings[dstLayout->getBindingIndex(entry.dstBinding)].descriptorType == entry.descriptorType);
+	ASSERT(binding.descriptorType == entry.descriptorType);
 
 	size_t typeSize = 0;
 	uint8_t* memToWrite = dstLayout->getOffsetPointer(dstSet, entry.dstBinding, entry.dstArrayElement, entry.descriptorCount, &typeSize);
@@ -261,26 +262,36 @@
 		for(uint32_t i = 0; i < entry.descriptorCount; i++)
 		{
 			auto update = reinterpret_cast<VkDescriptorImageInfo const *>(src + entry.offset + entry.stride * i);
-			vk::Sampler *sampler = vk::Cast(update->sampler);
 			vk::ImageView *imageView = vk::Cast(update->imageView);
-
-			imageSampler[i].sampler = sampler;
-			imageSampler[i].imageView = imageView;
-
 			sw::Texture *texture = &imageSampler[i].texture;
+
+			// "All consecutive bindings updated via a single VkWriteDescriptorSet structure, except those with a
+			//  descriptorCount of zero, must all either use immutable samplers or must all not use immutable samplers."
+			if(!binding.pImmutableSamplers)
+			{
+				vk::Sampler *sampler = vk::Cast(update->sampler);
+
+				imageSampler[i].sampler = sampler;
+
+				texture->minLod = sw::clamp(sampler->minLod, 0.0f, (float)(sw::MAX_TEXTURE_LOD));
+				texture->maxLod = sw::clamp(sampler->maxLod, 0.0f, (float)(sw::MAX_TEXTURE_LOD));
+			}
+
 			memset(texture, 0, sizeof(sw::Texture));  // TODO(b/129523279): eliminate
 
+			imageSampler[i].imageView = imageView;
+
 			auto &subresourceRange = imageView->getSubresourceRange();
 			int baseLevel = subresourceRange.baseMipLevel;
 
 			for(int mipmapLevel = 0; mipmapLevel < sw::MIPMAP_LEVELS; mipmapLevel++)
 			{
 				int level = mipmapLevel - baseLevel;  // Level within the image view
-				level = sw::clamp(level, 0, (int)subresourceRange.levelCount);
+				level = sw::clamp(level, 0, (int)subresourceRange.levelCount - 1);
 
 				VkOffset3D offset = {0, 0, 0};
 				VkImageAspectFlagBits aspect = VK_IMAGE_ASPECT_COLOR_BIT;
-				void *buffer = imageView->getOffsetPointer(offset, aspect);
+				void *buffer = imageView->getOffsetPointer(offset, aspect, level);
 
 				sw::Mipmap &mipmap = texture->mipmap[mipmapLevel];
 				mipmap.buffer[0] = buffer;
@@ -422,7 +433,7 @@
 		{
 			auto update = reinterpret_cast<VkDescriptorImageInfo const *>(src + entry.offset + entry.stride * i);
 			auto imageView = Cast(update->imageView);
-			descriptor[i].ptr = imageView->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_COLOR_BIT);
+			descriptor[i].ptr = imageView->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_COLOR_BIT, 0);
 			descriptor[i].extent = imageView->getMipLevelExtent(0);
 			descriptor[i].rowPitchBytes = imageView->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0);
 			descriptor[i].slicePitchBytes = imageView->getSubresourceRange().layerCount > 1
diff --git a/src/Vulkan/VkImageView.cpp b/src/Vulkan/VkImageView.cpp
index b403fb2..6e99bf7 100644
--- a/src/Vulkan/VkImageView.cpp
+++ b/src/Vulkan/VkImageView.cpp
@@ -144,12 +144,14 @@
 	image->copyTo(*(resolveAttachment->image), region);
 }
 
-void *ImageView::getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect) const
+void *ImageView::getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect, uint32_t mipLevel) const
 {
+	ASSERT(mipLevel < subresourceRange.levelCount);
+
 	VkImageSubresourceLayers imageSubresourceLayers =
 	{
 		static_cast<VkImageAspectFlags>(aspect),
-		subresourceRange.baseMipLevel,
+		subresourceRange.baseMipLevel + mipLevel,
 		subresourceRange.baseArrayLayer,
 		subresourceRange.layerCount
 	};
diff --git a/src/Vulkan/VkImageView.hpp b/src/Vulkan/VkImageView.hpp
index b7ef034..e4c4d81 100644
--- a/src/Vulkan/VkImageView.hpp
+++ b/src/Vulkan/VkImageView.hpp
@@ -44,7 +44,7 @@
 	int layerPitchBytes(VkImageAspectFlagBits aspect) const { return static_cast<int>(image->getLayerSize(aspect)); }
 	VkExtent3D getMipLevelExtent(uint32_t mipLevel) const { return image->getMipLevelExtent(subresourceRange.baseMipLevel + mipLevel); }
 
-	void *getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect) const;
+	void *getOffsetPointer(const VkOffset3D& offset, VkImageAspectFlagBits aspect, uint32_t mipLevel) const;
 	bool hasDepthAspect() const { return (subresourceRange.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; }
 	bool hasStencilAspect() const { return (subresourceRange.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; }