Add support for multiview rendering

This is a mandatory feature for 1.1, but Vulkan CTS 1.1.3 doesn't
enforce that -- it only tests the feature if it is present.

Highlights:
- Multiple views implemented by running each draw multiple times. We
  could do something more efficient, but this is a fringe feature so
  far.
- Render targets and input attachments are adjusted to use the layer
  corresponding to the current view.
- Explicit attachment clears and end-of-subpass resolves are broadcast
  to the layers corresponding to the current subpass's view mask.
- Renderpass attachment load ops are executed for the layers corresponding
  to the union of the subpass view masks for all subpasses which use the
  attachment. The actual load ops are still performed together at the
  beginning of the renderpass.
- ViewIndex builtin variable is exposed to the shaders. In a
  non-multiview draw call, ViewIndex is still available, but is always
  zero.

Bug: b/139862810
Test: dEQP-VK.*multiview*
Change-Id: Iaf40cfdb2f5afa61253cc756f97c0db30fb4d813
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/35408
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Chris Forbes <chrisforbes@google.com>
diff --git a/src/Device/Context.hpp b/src/Device/Context.hpp
index 9a2864a..4f31d00 100644
--- a/src/Device/Context.hpp
+++ b/src/Device/Context.hpp
@@ -96,6 +96,7 @@
 
 		// Instancing
 		int instanceID;
+		int viewID;
 
 		bool occlusionEnabled;
 
diff --git a/src/Device/Renderer.cpp b/src/Device/Renderer.cpp
index b33ff0a..4df1b3c 100644
--- a/src/Device/Renderer.cpp
+++ b/src/Device/Renderer.cpp
@@ -348,11 +348,8 @@
 		}
 
 		data->indices = context->indexBuffer;
-
-		if(context->vertexShader->hasBuiltinInput(spv::BuiltInInstanceIndex))
-		{
-			data->instanceID = context->instanceID;
-		}
+		data->viewID = context->viewID;
+		data->instanceID = context->instanceID;
 
 		data->baseVertex = baseVertex;
 
@@ -426,7 +423,7 @@
 
 				if(draw->renderTarget[index])
 				{
-					data->colorBuffer[index] = (unsigned int*)context->renderTarget[index]->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_COLOR_BIT, 0, 0);
+					data->colorBuffer[index] = (unsigned int*)context->renderTarget[index]->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_COLOR_BIT, 0, data->viewID);
 					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);
 				}
@@ -437,14 +434,14 @@
 
 			if(draw->depthBuffer)
 			{
-				data->depthBuffer = (float*)context->depthBuffer->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_DEPTH_BIT, 0, 0);
+				data->depthBuffer = (float*)context->depthBuffer->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_DEPTH_BIT, 0, data->viewID);
 				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, 0, 0);
+				data->stencilBuffer = (unsigned char*)context->stencilBuffer->getOffsetPointer({0, 0, 0}, VK_IMAGE_ASPECT_STENCIL_BIT, 0, data->viewID);
 				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/Renderer.hpp b/src/Device/Renderer.hpp
index fbff8d1..a8fb74a 100644
--- a/src/Device/Renderer.hpp
+++ b/src/Device/Renderer.hpp
@@ -60,6 +60,7 @@
 		int instanceID;
 		int baseVertex;
 		float lineWidth;
+		int viewID;
 
 		PixelProcessor::Stencil stencil[2];   // clockwise, counterclockwise
 		PixelProcessor::Factor factor;
diff --git a/src/Pipeline/PixelProgram.cpp b/src/Pipeline/PixelProgram.cpp
index 424b129..2391e19 100644
--- a/src/Pipeline/PixelProgram.cpp
+++ b/src/Pipeline/PixelProgram.cpp
@@ -24,6 +24,12 @@
 	{
 		routine.setImmutableInputBuiltins(spirvShader);
 
+		routine.setInputBuiltin(spirvShader, spv::BuiltInViewIndex, [&](const SpirvShader::BuiltinMapping& builtin, Array<SIMD::Float>& value)
+		{
+			assert(builtin.SizeInComponents == 1);
+			value[builtin.FirstComponent] = As<Float4>(Int4((*Pointer<Int>(data + OFFSET(DrawData, viewID)))));
+		});
+
 		routine.setInputBuiltin(spirvShader, spv::BuiltInFragCoord, [&](const SpirvShader::BuiltinMapping& builtin, Array<SIMD::Float>& value)
 		{
 			assert(builtin.SizeInComponents == 4);
@@ -50,6 +56,7 @@
 
 		routine.windowSpacePosition[0] = x + SIMD::Int(0,1,0,1);
 		routine.windowSpacePosition[1] = y + SIMD::Int(0,0,1,1);
+		routine.viewID = *Pointer<Int>(data + OFFSET(DrawData, viewID));
 	}
 
 	void PixelProgram::applyShader(Int cMask[4])
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 061d9cf..5ec62c4 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -1131,6 +1131,7 @@
 				if (!strcmp(ext, "SPV_KHR_16bit_storage")) break;
 				if (!strcmp(ext, "SPV_KHR_variable_pointers")) break;
 				if (!strcmp(ext, "SPV_KHR_device_group")) break;
+				if (!strcmp(ext, "SPV_KHR_multiview")) break;
 				UNSUPPORTED("SPIR-V Extension: %s", ext);
 				break;
 			}
@@ -5325,6 +5326,12 @@
 			ptr += coordinate.Int(dims) * slicePitch;
 		}
 
+		if (dim == spv::DimSubpassData)
+		{
+			// Multiview input attachment access is to the layer corresponding to the current view
+			ptr += SIMD::Int(routine->viewID) * slicePitch;
+		}
+
 		if (sampleId.value())
 		{
 			GenericValue sample(this, state, sampleId);
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index 6d77117..49736c0 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -1299,6 +1299,7 @@
 		Pointer<Byte> constants;
 		Int killMask = Int{0};
 		SIMD::Int windowSpacePosition[2];
+		Int viewID;	// slice offset into input attachments for multiview, even if the shader doesn't use ViewIndex
 
 		void createVariable(SpirvShader::Object::ID id, uint32_t size)
 		{
diff --git a/src/Pipeline/VertexProgram.cpp b/src/Pipeline/VertexProgram.cpp
index 4a56280..c7f020c 100644
--- a/src/Pipeline/VertexProgram.cpp
+++ b/src/Pipeline/VertexProgram.cpp
@@ -34,6 +34,12 @@
 	{
 		routine.setImmutableInputBuiltins(spirvShader);
 
+		routine.setInputBuiltin(spirvShader, spv::BuiltInViewIndex, [&](const SpirvShader::BuiltinMapping& builtin, Array<SIMD::Float>& value)
+		{
+			assert(builtin.SizeInComponents == 1);
+			value[builtin.FirstComponent] = As<Float4>(Int4((*Pointer<Int>(data + OFFSET(DrawData, viewID)))));
+		});
+
 		routine.setInputBuiltin(spirvShader, spv::BuiltInInstanceIndex, [&](const SpirvShader::BuiltinMapping& builtin, Array<SIMD::Float>& value)
 		{
 			// TODO: we could do better here; we know InstanceIndex is uniform across all lanes
diff --git a/src/Vulkan/VkCommandBuffer.cpp b/src/Vulkan/VkCommandBuffer.cpp
index 92f03e5..fde05d6 100644
--- a/src/Vulkan/VkCommandBuffer.cpp
+++ b/src/Vulkan/VkCommandBuffer.cpp
@@ -515,7 +515,7 @@
 	{
 		auto const &pipelineState = executionState.pipelineState[VK_PIPELINE_BIND_POINT_GRAPHICS];
 
-		GraphicsPipeline* pipeline = static_cast<GraphicsPipeline*>(pipelineState.pipeline);
+		GraphicsPipeline *pipeline = static_cast<GraphicsPipeline *>(pipelineState.pipeline);
 
 		sw::Context context = pipeline->getContext();
 
@@ -527,12 +527,13 @@
 
 		// Apply either pipeline state or dynamic state
 		executionState.renderer->setScissor(pipeline->hasDynamicState(VK_DYNAMIC_STATE_SCISSOR) ?
-		                                    executionState.dynamicState.scissor : pipeline->getScissor());
+											executionState.dynamicState.scissor : pipeline->getScissor());
 		executionState.renderer->setViewport(pipeline->hasDynamicState(VK_DYNAMIC_STATE_VIEWPORT) ?
-		                                     executionState.dynamicState.viewport : pipeline->getViewport());
+											 executionState.dynamicState.viewport : pipeline->getViewport());
 		executionState.renderer->setBlendConstant(pipeline->hasDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS) ?
-		                                          executionState.dynamicState.blendConstants : pipeline->getBlendConstants());
-		if(pipeline->hasDynamicState(VK_DYNAMIC_STATE_DEPTH_BIAS))
+												  executionState.dynamicState.blendConstants
+																											  : pipeline->getBlendConstants());
+		if (pipeline->hasDynamicState(VK_DYNAMIC_STATE_DEPTH_BIAS))
 		{
 			// If the depth bias clamping feature is not enabled, depthBiasClamp must be 0.0
 			ASSERT(executionState.dynamicState.depthBiasClamp == 0.0f);
@@ -540,25 +541,27 @@
 			context.depthBias = executionState.dynamicState.depthBiasConstantFactor;
 			context.slopeDepthBias = executionState.dynamicState.depthBiasSlopeFactor;
 		}
-		if(pipeline->hasDynamicState(VK_DYNAMIC_STATE_DEPTH_BOUNDS) && context.depthBoundsTestEnable)
+		if (pipeline->hasDynamicState(VK_DYNAMIC_STATE_DEPTH_BOUNDS) && context.depthBoundsTestEnable)
 		{
 			// Unless the VK_EXT_depth_range_unrestricted extension is enabled minDepthBounds and maxDepthBounds must be between 0.0 and 1.0, inclusive
-			ASSERT(executionState.dynamicState.minDepthBounds >= 0.0f && executionState.dynamicState.minDepthBounds <= 1.0f);
-			ASSERT(executionState.dynamicState.maxDepthBounds >= 0.0f && executionState.dynamicState.maxDepthBounds <= 1.0f);
+			ASSERT(executionState.dynamicState.minDepthBounds >= 0.0f &&
+				   executionState.dynamicState.minDepthBounds <= 1.0f);
+			ASSERT(executionState.dynamicState.maxDepthBounds >= 0.0f &&
+				   executionState.dynamicState.maxDepthBounds <= 1.0f);
 
 			UNIMPLEMENTED("depthBoundsTestEnable");
 		}
-		if(pipeline->hasDynamicState(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK) && context.stencilEnable)
+		if (pipeline->hasDynamicState(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK) && context.stencilEnable)
 		{
 			context.frontStencil.compareMask = executionState.dynamicState.compareMask[0];
 			context.backStencil.compareMask = executionState.dynamicState.compareMask[1];
 		}
-		if(pipeline->hasDynamicState(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK) && context.stencilEnable)
+		if (pipeline->hasDynamicState(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK) && context.stencilEnable)
 		{
 			context.frontStencil.writeMask = executionState.dynamicState.writeMask[0];
 			context.backStencil.writeMask = executionState.dynamicState.writeMask[1];
 		}
-		if(pipeline->hasDynamicState(VK_DYNAMIC_STATE_STENCIL_REFERENCE) && context.stencilEnable)
+		if (pipeline->hasDynamicState(VK_DYNAMIC_STATE_STENCIL_REFERENCE) && context.stencilEnable)
 		{
 			context.frontStencil.reference = executionState.dynamicState.reference[0];
 			context.backStencil.reference = executionState.dynamicState.reference[1];
@@ -566,23 +569,23 @@
 
 		executionState.bindAttachments(context);
 
-		context.multiSampleMask = context.sampleMask & ((unsigned)0xFFFFFFFF >> (32 - context.sampleCount));
+		context.multiSampleMask = context.sampleMask & ((unsigned) 0xFFFFFFFF >> (32 - context.sampleCount));
 		context.occlusionEnabled = executionState.renderer->hasOcclusionQuery();
 
-		std::vector<std::pair<uint32_t, void*>> indexBuffers;
-		if(indexed)
+		std::vector<std::pair<uint32_t, void *>> indexBuffers;
+		if (indexed)
 		{
-			void* indexBuffer = executionState.indexBufferBinding.buffer->getOffsetPointer(
-				executionState.indexBufferBinding.offset + first * bytesPerIndex(executionState));
-			if(pipeline->hasPrimitiveRestartEnable())
+			void *indexBuffer = executionState.indexBufferBinding.buffer->getOffsetPointer(
+					executionState.indexBufferBinding.offset + first * bytesPerIndex(executionState));
+			if (pipeline->hasPrimitiveRestartEnable())
 			{
-				switch(executionState.indexType)
+				switch (executionState.indexType)
 				{
 				case VK_INDEX_TYPE_UINT16:
-					processPrimitiveRestart(static_cast<uint16_t*>(indexBuffer), count, pipeline, indexBuffers);
+					processPrimitiveRestart(static_cast<uint16_t *>(indexBuffer), count, pipeline, indexBuffers);
 					break;
 				case VK_INDEX_TYPE_UINT32:
-					processPrimitiveRestart(static_cast<uint32_t*>(indexBuffer), count, pipeline, indexBuffers);
+					processPrimitiveRestart(static_cast<uint32_t *>(indexBuffer), count, pipeline, indexBuffers);
 					break;
 				default:
 					UNIMPLEMENTED("executionState.indexType %d", int(executionState.indexType));
@@ -590,23 +593,32 @@
 			}
 			else
 			{
-				indexBuffers.push_back({ pipeline->computePrimitiveCount(count), indexBuffer });
+				indexBuffers.push_back({pipeline->computePrimitiveCount(count), indexBuffer});
 			}
 		}
 		else
 		{
-			indexBuffers.push_back({ pipeline->computePrimitiveCount(count), nullptr });
+			indexBuffers.push_back({pipeline->computePrimitiveCount(count), nullptr});
 		}
 
-		for(uint32_t instance = firstInstance; instance != firstInstance + instanceCount; instance++)
+		for (uint32_t instance = firstInstance; instance != firstInstance + instanceCount; instance++)
 		{
 			context.instanceID = instance;
 
-			for(auto indexBuffer : indexBuffers)
+			// FIXME: reconsider instances/views nesting.
+			auto viewMask = executionState.renderPass->getViewMask();
+			while (viewMask)
 			{
-				const uint32_t primitiveCount = indexBuffer.first;
-				context.indexBuffer = indexBuffer.second;
-				executionState.renderer->draw(&context, executionState.indexType, primitiveCount, vertexOffset, executionState.events);
+				context.viewID = sw::log2i(viewMask);
+				viewMask &= ~(1 << context.viewID);
+
+				for (auto indexBuffer : indexBuffers)
+				{
+					const uint32_t primitiveCount = indexBuffer.first;
+					context.indexBuffer = indexBuffer.second;
+					executionState.renderer->draw(&context, executionState.indexType, primitiveCount, vertexOffset,
+												  executionState.events);
+				}
 			}
 
 			executionState.renderer->advanceInstanceAttributes(context.input);
diff --git a/src/Vulkan/VkFramebuffer.cpp b/src/Vulkan/VkFramebuffer.cpp
index 768c492..f9c0716 100644
--- a/src/Vulkan/VkFramebuffer.cpp
+++ b/src/Vulkan/VkFramebuffer.cpp
@@ -17,6 +17,7 @@
 #include "VkRenderPass.hpp"
 #include <algorithm>
 #include <memory.h>
+#include <System/Math.hpp>
 
 namespace vk
 {
@@ -60,15 +61,38 @@
 
 			if(clearDepth || clearStencil)
 			{
-				attachments[i]->clear(pClearValues[i],
-				                      (clearDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) |
-				                      (clearStencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0),
-				                      renderArea);
+				auto aspectMask = (clearDepth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0) |
+								  (clearStencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0);
+				if (renderPass->isMultiView())
+				{
+					auto viewMask = renderPass->getAttachmentViewMask(i);
+					while (viewMask)
+					{
+						uint32_t view = sw::log2i(viewMask);
+						viewMask &= ~(1 << view);
+						VkClearRect r{renderArea, view, 1};
+						attachments[i]->clear(pClearValues[i], aspectMask, r);
+					}
+				}
+				else
+					attachments[i]->clear(pClearValues[i], aspectMask, renderArea);
 			}
 		}
 		else if(attachment.loadOp == VK_ATTACHMENT_LOAD_OP_CLEAR)
 		{
-			attachments[i]->clear(pClearValues[i], VK_IMAGE_ASPECT_COLOR_BIT, renderArea);
+			if (renderPass->isMultiView())
+			{
+				auto viewMask = renderPass->getAttachmentViewMask(i);
+				while (viewMask)
+				{
+					uint32_t view = sw::log2i(viewMask);
+					viewMask &= ~(1 << view);
+					VkClearRect r{renderArea, view, 1};
+					attachments[i]->clear(pClearValues[i], VK_IMAGE_ASPECT_COLOR_BIT, r);
+				}
+			}
+			else
+				attachments[i]->clear(pClearValues[i], VK_IMAGE_ASPECT_COLOR_BIT, renderArea);
 		}
 	}
 }
@@ -84,8 +108,21 @@
 			ASSERT(attachment.colorAttachment < subpass.colorAttachmentCount);
 			ASSERT(subpass.pColorAttachments[attachment.colorAttachment].attachment < attachmentCount);
 
-			attachments[subpass.pColorAttachments[attachment.colorAttachment].attachment]->clear(
-				attachment.clearValue, attachment.aspectMask, rect);
+			if (renderPass->isMultiView())
+			{
+				auto viewMask = renderPass->getViewMask();
+				while (viewMask)
+				{
+					int view = sw::log2i(viewMask);
+					viewMask &= ~(1 << view);
+					VkClearRect r = rect;
+					r.baseArrayLayer = view;
+					attachments[subpass.pColorAttachments[attachment.colorAttachment].attachment]->clear(attachment.clearValue, attachment.aspectMask, r);
+				}
+			}
+			else
+				attachments[subpass.pColorAttachments[attachment.colorAttachment].attachment]->clear(
+					attachment.clearValue, attachment.aspectMask, rect);
 		}
 	}
 	else if(attachment.aspectMask & (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT))
@@ -94,7 +131,20 @@
 
 		ASSERT(subpass.pDepthStencilAttachment->attachment < attachmentCount);
 
-		attachments[subpass.pDepthStencilAttachment->attachment]->clear(attachment.clearValue, attachment.aspectMask, rect);
+		if (renderPass->isMultiView())
+			{
+				auto viewMask = renderPass->getViewMask();
+				while (viewMask)
+				{
+					int view = sw::log2i(viewMask);
+					viewMask &= ~(1 << view);
+					VkClearRect r = rect;
+					r.baseArrayLayer = view;
+					attachments[subpass.pDepthStencilAttachment->attachment]->clear(attachment.clearValue, attachment.aspectMask, r);
+				}
+			}
+		else
+			attachments[subpass.pDepthStencilAttachment->attachment]->clear(attachment.clearValue, attachment.aspectMask, rect);
 	}
 }
 
@@ -113,7 +163,20 @@
 			uint32_t resolveAttachment = subpass.pResolveAttachments[i].attachment;
 			if(resolveAttachment != VK_ATTACHMENT_UNUSED)
 			{
-				attachments[subpass.pColorAttachments[i].attachment]->resolve(attachments[resolveAttachment]);
+				if (renderPass->isMultiView())
+				{
+					auto viewMask = renderPass->getViewMask();
+					while (viewMask)
+					{
+						int view = sw::log2i(viewMask);
+						viewMask &= ~(1 << view);
+						attachments[subpass.pColorAttachments[i].attachment]->resolve(attachments[resolveAttachment], view);
+					}
+				}
+				else
+				{
+					attachments[subpass.pColorAttachments[i].attachment]->resolve(attachments[resolveAttachment]);
+				}
 			}
 		}
 	}
diff --git a/src/Vulkan/VkImageView.cpp b/src/Vulkan/VkImageView.cpp
index a65f391..b0216fd 100644
--- a/src/Vulkan/VkImageView.cpp
+++ b/src/Vulkan/VkImageView.cpp
@@ -154,6 +154,36 @@
 	image->clear(clearValue, format, renderArea.rect, sr);
 }
 
+void ImageView::resolve(ImageView* resolveAttachment, int layer)
+{
+	if((subresourceRange.levelCount != 1) || (resolveAttachment->subresourceRange.levelCount != 1))
+	{
+		UNIMPLEMENTED("levelCount");
+	}
+
+	VkImageCopy region;
+	region.srcSubresource =
+	{
+		subresourceRange.aspectMask,
+		subresourceRange.baseMipLevel,
+		subresourceRange.baseArrayLayer + layer,
+		1
+	};
+	region.srcOffset = { 0, 0, 0 };
+	region.dstSubresource =
+	{
+		resolveAttachment->subresourceRange.aspectMask,
+		resolveAttachment->subresourceRange.baseMipLevel,
+		resolveAttachment->subresourceRange.baseArrayLayer + layer,
+		1
+	};
+	region.dstOffset = { 0, 0, 0 };
+	region.extent = image->getMipLevelExtent(static_cast<VkImageAspectFlagBits>(subresourceRange.aspectMask),
+	                                         subresourceRange.baseMipLevel);
+
+	image->copyTo(resolveAttachment->image, region);
+}
+
 void ImageView::resolve(ImageView* resolveAttachment)
 {
 	if((subresourceRange.levelCount != 1) || (resolveAttachment->subresourceRange.levelCount != 1))
diff --git a/src/Vulkan/VkImageView.hpp b/src/Vulkan/VkImageView.hpp
index db3848e..3d9942c 100644
--- a/src/Vulkan/VkImageView.hpp
+++ b/src/Vulkan/VkImageView.hpp
@@ -42,6 +42,7 @@
 	void clear(const VkClearValue& clearValues, VkImageAspectFlags aspectMask, const VkRect2D& renderArea);
 	void clear(const VkClearValue& clearValue, VkImageAspectFlags aspectMask, const VkClearRect& renderArea);
 	void resolve(ImageView* resolveAttachment);
+	void resolve(ImageView* resolveAttachment, int layer);
 
 	VkImageViewType getType() const { return viewType; }
 	Format getFormat(Usage usage = RAW) const;
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index 601c403..5e5b74c 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -119,7 +119,7 @@
 
 void PhysicalDevice::getFeatures(VkPhysicalDeviceMultiviewFeatures* features) const
 {
-	features->multiview = VK_FALSE;
+	features->multiview = VK_TRUE;
 	features->multiviewGeometryShader = VK_FALSE;
 	features->multiviewTessellationShader = VK_FALSE;
 }
@@ -295,8 +295,8 @@
 
 void PhysicalDevice::getProperties(VkPhysicalDeviceMultiviewProperties* properties) const
 {
-	properties->maxMultiviewViewCount = 0;
-	properties->maxMultiviewInstanceIndex = 0;
+	properties->maxMultiviewViewCount = 6;
+	properties->maxMultiviewInstanceIndex = 1u<<27;
 }
 
 void PhysicalDevice::getProperties(VkPhysicalDevicePointClippingProperties* properties) const
diff --git a/src/Vulkan/VkRenderPass.cpp b/src/Vulkan/VkRenderPass.cpp
index 9a23c0a..852679b 100644
--- a/src/Vulkan/VkRenderPass.cpp
+++ b/src/Vulkan/VkRenderPass.cpp
@@ -15,15 +15,6 @@
 #include "VkRenderPass.hpp"
 #include <cstring>
 
-namespace
-{
-	void MarkFirstUse(int& attachment, int subpass)
-	{
-		if (attachment == -1)
-			attachment = subpass;
-	}
-}
-
 namespace vk
 {
 
@@ -41,6 +32,8 @@
 	subpasses = reinterpret_cast<VkSubpassDescription*>(hostMemory);
 	memcpy(subpasses, pCreateInfo->pSubpasses, subpassesSize);
 	hostMemory += subpassesSize;
+	uint32_t *masks = reinterpret_cast<uint32_t *>(hostMemory);
+	hostMemory += pCreateInfo->subpassCount * sizeof(uint32_t);
 
 	if(pCreateInfo->attachmentCount > 0)
 	{
@@ -51,9 +44,43 @@
 
 		size_t firstUseSize = pCreateInfo->attachmentCount * sizeof(int);
 		attachmentFirstUse = reinterpret_cast<int*>(hostMemory);
-		for (auto i = 0u; i < pCreateInfo->attachmentCount; i++)
-			attachmentFirstUse[i] = -1;
 		hostMemory += firstUseSize;
+
+		attachmentViewMasks = reinterpret_cast<uint32_t *>(hostMemory);
+		hostMemory += pCreateInfo->attachmentCount * sizeof(uint32_t);
+		for (auto i = 0u; i < pCreateInfo->attachmentCount; i++)
+		{
+			attachmentFirstUse[i] = -1;
+			attachmentViewMasks[i] = 0;
+		}
+	}
+
+	const VkBaseInStructure* extensionCreateInfo = reinterpret_cast<const VkBaseInStructure*>(pCreateInfo->pNext);
+	while (extensionCreateInfo)
+	{
+		switch (extensionCreateInfo->sType)
+		{
+		case VK_STRUCTURE_TYPE_RENDER_PASS_MULTIVIEW_CREATE_INFO:
+		{
+			// Renderpass uses multiview if this structure is present AND some subpass specifies
+			// a nonzero view mask
+			auto const *multiviewCreateInfo = reinterpret_cast<VkRenderPassMultiviewCreateInfo const *>(extensionCreateInfo);
+			for (auto i = 0u; i < pCreateInfo->subpassCount; i++)
+			{
+				masks[i] = multiviewCreateInfo->pViewMasks[i];
+				// This is now a multiview renderpass, so make the masks available
+				if (masks[i])
+					viewMasks = masks;
+			}
+
+			break;
+		}
+		default:
+			/* Unknown structure in pNext chain must be ignored */
+			break;
+		}
+
+		extensionCreateInfo = extensionCreateInfo->pNext;
 	}
 
 	// Deep copy subpasses
@@ -77,7 +104,7 @@
 			for (auto j = 0u; j < subpasses[i].inputAttachmentCount; j++)
 			{
 				if (subpass.pInputAttachments[j].attachment != VK_ATTACHMENT_UNUSED)
-					MarkFirstUse(attachmentFirstUse[subpass.pInputAttachments[j].attachment], i);
+					MarkFirstUse(subpass.pInputAttachments[j].attachment, i);
 			}
 		}
 
@@ -100,10 +127,10 @@
 			for (auto j = 0u; j < subpasses[i].colorAttachmentCount; j++)
 			{
 				if (subpass.pColorAttachments[j].attachment != VK_ATTACHMENT_UNUSED)
-					MarkFirstUse(attachmentFirstUse[subpass.pColorAttachments[j].attachment], i);
+					MarkFirstUse(subpass.pColorAttachments[j].attachment, i);
 				if (subpass.pResolveAttachments &&
 					subpass.pResolveAttachments[j].attachment != VK_ATTACHMENT_UNUSED)
-					MarkFirstUse(attachmentFirstUse[subpass.pResolveAttachments[j].attachment], i);
+					MarkFirstUse(subpass.pResolveAttachments[j].attachment, i);
 			}
 		}
 
@@ -115,7 +142,7 @@
 			hostMemory += sizeof(VkAttachmentReference);
 
 			if (subpass.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED)
-				MarkFirstUse(attachmentFirstUse[subpass.pDepthStencilAttachment->attachment], i);
+				MarkFirstUse(subpass.pDepthStencilAttachment->attachment, i);
 		}
 
 		if(subpass.preserveAttachmentCount > 0)
@@ -129,7 +156,7 @@
 			for (auto j = 0u; j < subpasses[i].preserveAttachmentCount; j++)
 			{
 				if (subpass.pPreserveAttachments[j] != VK_ATTACHMENT_UNUSED)
-					MarkFirstUse(attachmentFirstUse[subpass.pPreserveAttachments[j]], i);
+					MarkFirstUse(subpass.pPreserveAttachments[j], i);
 			}
 		}
 	}
@@ -150,7 +177,8 @@
 size_t RenderPass::ComputeRequiredAllocationSize(const VkRenderPassCreateInfo* pCreateInfo)
 {
 	size_t attachmentSize = pCreateInfo->attachmentCount * sizeof(VkAttachmentDescription)
-			+ pCreateInfo->attachmentCount * sizeof(int);
+			+ pCreateInfo->attachmentCount * sizeof(int)			// first use
+			+ pCreateInfo->attachmentCount * sizeof(uint32_t);		// union of subpass view masks, per attachment
 	size_t subpassesSize = 0;
 	for(uint32_t i = 0; i < pCreateInfo->subpassCount; ++i)
 	{
@@ -166,7 +194,8 @@
 		}
 		subpassesSize += sizeof(VkSubpassDescription) +
 		                 sizeof(VkAttachmentReference) * nbAttachments +
-		                 sizeof(uint32_t) * subpass.preserveAttachmentCount;
+		                 sizeof(uint32_t) * subpass.preserveAttachmentCount +
+		                 sizeof(uint32_t);			// view mask
 	}
 	size_t dependenciesSize = pCreateInfo->dependencyCount * sizeof(VkSubpassDependency);
 
@@ -195,4 +224,16 @@
 	currentSubpass = 0;
 }
 
+void RenderPass::MarkFirstUse(int attachment, int subpass)
+{
+	// FIXME: we may not actually need to track attachmentFirstUse if we're going to eagerly
+	//  clear attachments at the start of the renderpass; can use attachmentViewMasks always instead.
+
+	if (attachmentFirstUse[attachment] == -1)
+		attachmentFirstUse[attachment] = subpass;
+
+	if (isMultiView())
+		attachmentViewMasks[attachment] |= viewMasks[subpass];
+}
+
 } // namespace vk
\ No newline at end of file
diff --git a/src/Vulkan/VkRenderPass.hpp b/src/Vulkan/VkRenderPass.hpp
index 0fe9e22..2d0841f 100644
--- a/src/Vulkan/VkRenderPass.hpp
+++ b/src/Vulkan/VkRenderPass.hpp
@@ -76,6 +76,21 @@
 		return attachmentFirstUse[i] >= 0;
 	}
 
+	uint32_t getViewMask() const
+	{
+		return viewMasks ? viewMasks[currentSubpass] : 1;
+	}
+
+	bool isMultiView() const
+	{
+		return viewMasks != nullptr;
+	}
+
+	uint32_t getAttachmentViewMask(uint32_t i) const
+	{
+		return attachmentViewMasks[i];
+	}
+
 private:
 	uint32_t                 attachmentCount = 0;
 	VkAttachmentDescription* attachments = nullptr;
@@ -85,6 +100,10 @@
 	VkSubpassDependency*     dependencies = nullptr;
 	uint32_t                 currentSubpass = 0;
 	int*                     attachmentFirstUse = nullptr;
+	uint32_t*                viewMasks = nullptr;
+	uint32_t*                attachmentViewMasks = nullptr;
+
+	void MarkFirstUse(int attachment, int subpass);
 };
 
 static inline RenderPass* Cast(VkRenderPass object)
diff --git a/src/Vulkan/libVulkan.cpp b/src/Vulkan/libVulkan.cpp
index f2bbf2f..1c66491 100644
--- a/src/Vulkan/libVulkan.cpp
+++ b/src/Vulkan/libVulkan.cpp
@@ -511,8 +511,7 @@
 			{
 				const VkPhysicalDeviceMultiviewFeatures* multiviewFeatures = reinterpret_cast<const VkPhysicalDeviceMultiviewFeatures*>(extensionCreateInfo);
 
-				if (multiviewFeatures->multiview ||
-				    multiviewFeatures->multiviewGeometryShader ||
+				if (multiviewFeatures->multiviewGeometryShader ||
 				    multiviewFeatures->multiviewTessellationShader)
 				{
 					return VK_ERROR_FEATURE_NOT_PRESENT;