Context refactor: from OpenGL-like state to Vulkan-like state

Overview of the changes:

The Context class has been split into more Vulkan related functional
elements:
- The IndexBuffer class, which contains the index buffer information
  and has been extended to also the functionality to support
  primitive restart. Should we need primitive restart related caching
  in the future, this would be the place to do it.
- The Attachments class, which contains information about render
  targets and depth and stencil buffers
- The Inputs class, which contains information about descriptor sets
  and input streams.
- The GraphicsState is a completely constant class which can only be
  modified in the constructor, which represents the state of a
  graphics pipeline, which never changes past construction. The
  GraphicsState can be combined with a DynamicState structure in
  order to create a complete state used by rendering.

Also to note:
- The DynamicState class in now in Context.hpp, in order for the
  GraphicsState to have functionality related to it.
- PushConstantStorage was moved to vk::Pipeline, as it is used by
  both the Graphics Pipeline and the Compute Pipeline.
- Viewport/scissor functionality is contained in the GraphicsState
  and was removed from the Renderer.
- All *Processor::update() functions now receive only the information
  that they require, rather that having access to the entire context.

Bug: b/132280877

Change-Id: I74f2582d34e45aa1e7b192dbd2b9b770e7af118d
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/48830
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Alexis Hétu <sugoi@google.com>
Commit-Queue: Alexis Hétu <sugoi@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Device/Context.cpp b/src/Device/Context.cpp
index 8e35ff2..e5879d2 100644
--- a/src/Device/Context.cpp
+++ b/src/Device/Context.cpp
@@ -1,4 +1,4 @@
-// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
+// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -13,18 +13,513 @@
 // limitations under the License.
 
 #include "Context.hpp"
-
-#include "Primitive.hpp"
-#include "Pipeline/SpirvShader.hpp"
-#include "System/Debug.hpp"
-#include "System/Memory.hpp"
+#include "Vulkan/VkBuffer.hpp"
+#include "Vulkan/VkDevice.hpp"
 #include "Vulkan/VkImageView.hpp"
+#include "Vulkan/VkRenderPass.hpp"
+#include "Vulkan/VkStringify.hpp"
 
-#include <string.h>
+namespace {
 
-namespace sw {
+uint32_t ComputePrimitiveCount(VkPrimitiveTopology topology, uint32_t vertexCount)
+{
+	switch(topology)
+	{
+		case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
+			return vertexCount;
+		case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
+			return vertexCount / 2;
+		case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
+			return std::max<uint32_t>(vertexCount, 1) - 1;
+		case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
+			return vertexCount / 3;
+		case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
+			return std::max<uint32_t>(vertexCount, 2) - 2;
+		case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
+			return std::max<uint32_t>(vertexCount, 2) - 2;
+		default:
+			UNSUPPORTED("VkPrimitiveTopology %d", int(topology));
+	}
 
-bool Context::isDrawPoint(bool polygonModeAware) const
+	return 0;
+}
+
+template<typename T>
+void ProcessPrimitiveRestart(T *indexBuffer,
+                             VkPrimitiveTopology topology,
+                             uint32_t count,
+                             std::vector<std::pair<uint32_t, void *>> *indexBuffers)
+{
+	static const T RestartIndex = static_cast<T>(-1);
+	T *indexBufferStart = indexBuffer;
+	uint32_t vertexCount = 0;
+	for(uint32_t i = 0; i < count; i++)
+	{
+		if(indexBuffer[i] == RestartIndex)
+		{
+			// Record previous segment
+			if(vertexCount > 0)
+			{
+				uint32_t primitiveCount = ComputePrimitiveCount(topology, vertexCount);
+				if(primitiveCount > 0)
+				{
+					indexBuffers->push_back({ primitiveCount, indexBufferStart });
+				}
+			}
+			vertexCount = 0;
+		}
+		else
+		{
+			if(vertexCount == 0)
+			{
+				indexBufferStart = indexBuffer + i;
+			}
+			vertexCount++;
+		}
+	}
+
+	// Record last segment
+	if(vertexCount > 0)
+	{
+		uint32_t primitiveCount = ComputePrimitiveCount(topology, vertexCount);
+		if(primitiveCount > 0)
+		{
+			indexBuffers->push_back({ primitiveCount, indexBufferStart });
+		}
+	}
+}
+
+}  // namespace
+
+namespace vk {
+
+int IndexBuffer::bytesPerIndex() const
+{
+	return indexType == VK_INDEX_TYPE_UINT16 ? 2 : 4;
+}
+
+void IndexBuffer::setIndexBufferBinding(const VertexInputBinding &indexBufferBinding, VkIndexType type)
+{
+	binding = indexBufferBinding;
+	indexType = type;
+}
+
+void IndexBuffer::getIndexBuffers(VkPrimitiveTopology topology, uint32_t count, uint32_t first, bool indexed, bool hasPrimitiveRestartEnable, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const
+{
+
+	if(indexed)
+	{
+		void *indexBuffer = binding.buffer->getOffsetPointer(binding.offset + first * bytesPerIndex());
+		if(hasPrimitiveRestartEnable)
+		{
+			switch(indexType)
+			{
+				case VK_INDEX_TYPE_UINT16:
+					ProcessPrimitiveRestart(static_cast<uint16_t *>(indexBuffer), topology, count, indexBuffers);
+					break;
+				case VK_INDEX_TYPE_UINT32:
+					ProcessPrimitiveRestart(static_cast<uint32_t *>(indexBuffer), topology, count, indexBuffers);
+					break;
+				default:
+					UNSUPPORTED("VkIndexType %d", int(indexType));
+			}
+		}
+		else
+		{
+			indexBuffers->push_back({ ComputePrimitiveCount(topology, count), indexBuffer });
+		}
+	}
+	else
+	{
+		indexBuffers->push_back({ ComputePrimitiveCount(topology, count), nullptr });
+	}
+}
+
+bool Attachments::isColorClamped(int index) const
+{
+	if(renderTarget[index] && renderTarget[index]->getFormat().isFloatFormat())
+	{
+		return false;
+	}
+
+	return true;
+}
+
+VkFormat Attachments::renderTargetInternalFormat(int index) const
+{
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
+
+	if(renderTarget[index])
+	{
+		return renderTarget[index]->getFormat();
+	}
+	else
+	{
+		return VK_FORMAT_UNDEFINED;
+	}
+}
+
+Inputs::Inputs(const VkPipelineVertexInputStateCreateInfo *vertexInputState)
+{
+	if(vertexInputState->flags != 0)
+	{
+		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+		UNSUPPORTED("vertexInputState->flags");
+	}
+
+	// Temporary in-binding-order representation of buffer strides, to be consumed below
+	// when considering attributes. TODO: unfuse buffers from attributes in backend, is old GL model.
+	uint32_t vertexStrides[MAX_VERTEX_INPUT_BINDINGS];
+	uint32_t instanceStrides[MAX_VERTEX_INPUT_BINDINGS];
+	for(uint32_t i = 0; i < vertexInputState->vertexBindingDescriptionCount; i++)
+	{
+		auto const &desc = vertexInputState->pVertexBindingDescriptions[i];
+		vertexStrides[desc.binding] = desc.inputRate == VK_VERTEX_INPUT_RATE_VERTEX ? desc.stride : 0;
+		instanceStrides[desc.binding] = desc.inputRate == VK_VERTEX_INPUT_RATE_INSTANCE ? desc.stride : 0;
+	}
+
+	for(uint32_t i = 0; i < vertexInputState->vertexAttributeDescriptionCount; i++)
+	{
+		auto const &desc = vertexInputState->pVertexAttributeDescriptions[i];
+		sw::Stream &input = stream[desc.location];
+		input.format = desc.format;
+		input.offset = desc.offset;
+		input.binding = desc.binding;
+		input.vertexStride = vertexStrides[desc.binding];
+		input.instanceStride = instanceStrides[desc.binding];
+	}
+}
+
+void Inputs::updateDescriptorSets(const DescriptorSet::Array &dso,
+                                  const DescriptorSet::Bindings &ds,
+                                  const DescriptorSet::DynamicOffsets &ddo)
+{
+	descriptorSetObjects = dso;
+	descriptorSets = ds;
+	descriptorDynamicOffsets = ddo;
+}
+
+void Inputs::bindVertexInputs(int firstInstance)
+{
+	for(uint32_t i = 0; i < MAX_VERTEX_INPUT_BINDINGS; i++)
+	{
+		auto &attrib = stream[i];
+		if(attrib.format != VK_FORMAT_UNDEFINED)
+		{
+			const auto &vertexInput = vertexInputBindings[attrib.binding];
+			VkDeviceSize offset = attrib.offset + vertexInput.offset +
+			                      attrib.instanceStride * firstInstance;
+			attrib.buffer = vertexInput.buffer ? vertexInput.buffer->getOffsetPointer(offset) : nullptr;
+
+			VkDeviceSize size = vertexInput.buffer ? vertexInput.buffer->getSize() : 0;
+			attrib.robustnessSize = (size > offset) ? size - offset : 0;
+		}
+	}
+}
+
+void Inputs::setVertexInputBinding(const VertexInputBinding bindings[])
+{
+	for(uint32_t i = 0; i < MAX_VERTEX_INPUT_BINDINGS; ++i)
+	{
+		vertexInputBindings[i] = bindings[i];
+	}
+}
+
+// TODO(b/137740918): Optimize instancing to use a single draw call.
+void Inputs::advanceInstanceAttributes()
+{
+	for(uint32_t i = 0; i < vk::MAX_VERTEX_INPUT_BINDINGS; i++)
+	{
+		auto &attrib = stream[i];
+		if((attrib.format != VK_FORMAT_UNDEFINED) && attrib.instanceStride && (attrib.instanceStride < attrib.robustnessSize))
+		{
+			// Under the casts: attrib.buffer += attrib.instanceStride
+			attrib.buffer = (void const *)((uintptr_t)attrib.buffer + attrib.instanceStride);
+			attrib.robustnessSize -= attrib.instanceStride;
+		}
+	}
+}
+
+GraphicsState::GraphicsState(const Device *device, const VkGraphicsPipelineCreateInfo *pCreateInfo,
+                             const PipelineLayout *layout, bool robustBufferAccess)
+    : pipelineLayout(layout)
+    , robustBufferAccess(robustBufferAccess)
+{
+	if((pCreateInfo->flags &
+	    ~(VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT |
+	      VK_PIPELINE_CREATE_DERIVATIVE_BIT |
+	      VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT)) != 0)
+	{
+		UNSUPPORTED("pCreateInfo->flags %d", int(pCreateInfo->flags));
+	}
+
+	if(pCreateInfo->pDynamicState)
+	{
+		if(pCreateInfo->pDynamicState->flags != 0)
+		{
+			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+			UNSUPPORTED("pCreateInfo->pDynamicState->flags %d", int(pCreateInfo->pDynamicState->flags));
+		}
+
+		for(uint32_t i = 0; i < pCreateInfo->pDynamicState->dynamicStateCount; i++)
+		{
+			VkDynamicState dynamicState = pCreateInfo->pDynamicState->pDynamicStates[i];
+			switch(dynamicState)
+			{
+				case VK_DYNAMIC_STATE_VIEWPORT:
+				case VK_DYNAMIC_STATE_SCISSOR:
+				case VK_DYNAMIC_STATE_LINE_WIDTH:
+				case VK_DYNAMIC_STATE_DEPTH_BIAS:
+				case VK_DYNAMIC_STATE_BLEND_CONSTANTS:
+				case VK_DYNAMIC_STATE_DEPTH_BOUNDS:
+				case VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK:
+				case VK_DYNAMIC_STATE_STENCIL_WRITE_MASK:
+				case VK_DYNAMIC_STATE_STENCIL_REFERENCE:
+					ASSERT(dynamicState < (sizeof(dynamicStateFlags) * 8));
+					dynamicStateFlags |= (1 << dynamicState);
+					break;
+				default:
+					UNSUPPORTED("VkDynamicState %d", int(dynamicState));
+			}
+		}
+	}
+
+	const VkPipelineVertexInputStateCreateInfo *vertexInputState = pCreateInfo->pVertexInputState;
+
+	if(vertexInputState->flags != 0)
+	{
+		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+		UNSUPPORTED("vertexInputState->flags");
+	}
+
+	const VkPipelineInputAssemblyStateCreateInfo *inputAssemblyState = pCreateInfo->pInputAssemblyState;
+
+	if(inputAssemblyState->flags != 0)
+	{
+		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+		UNSUPPORTED("pCreateInfo->pInputAssemblyState->flags %d", int(pCreateInfo->pInputAssemblyState->flags));
+	}
+
+	primitiveRestartEnable = (inputAssemblyState->primitiveRestartEnable != VK_FALSE);
+	topology = inputAssemblyState->topology;
+
+	const VkPipelineRasterizationStateCreateInfo *rasterizationState = pCreateInfo->pRasterizationState;
+
+	if(rasterizationState->flags != 0)
+	{
+		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+		UNSUPPORTED("pCreateInfo->pRasterizationState->flags %d", int(pCreateInfo->pRasterizationState->flags));
+	}
+
+	if(rasterizationState->depthClampEnable != VK_FALSE)
+	{
+		UNSUPPORTED("VkPhysicalDeviceFeatures::depthClamp");
+	}
+
+	rasterizerDiscard = (rasterizationState->rasterizerDiscardEnable != VK_FALSE);
+	cullMode = rasterizationState->cullMode;
+	frontFace = rasterizationState->frontFace;
+	polygonMode = rasterizationState->polygonMode;
+	constantDepthBias = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasConstantFactor : 0.0f;
+	slopeDepthBias = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasSlopeFactor : 0.0f;
+	depthBiasClamp = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasClamp : 0.0f;
+	depthRangeUnrestricted = device->hasExtension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME);
+
+	// From the Vulkan spec for vkCmdSetDepthBias:
+	//    The bias value O for a polygon is:
+	//        O = dbclamp(...)
+	//    where dbclamp(x) =
+	//        * x                       depthBiasClamp = 0 or NaN
+	//        * min(x, depthBiasClamp)  depthBiasClamp > 0
+	//        * max(x, depthBiasClamp)  depthBiasClamp < 0
+	// So it should be safe to resolve NaNs to 0.0f.
+	if(std::isnan(depthBiasClamp))
+	{
+		depthBiasClamp = 0.0f;
+	}
+
+	lineWidth = rasterizationState->lineWidth;
+
+	const VkBaseInStructure *extensionCreateInfo = reinterpret_cast<const VkBaseInStructure *>(rasterizationState->pNext);
+	while(extensionCreateInfo)
+	{
+		// Casting to a long since some structures, such as
+		// VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT
+		// are not enumerated in the official Vulkan header
+		switch((long)(extensionCreateInfo->sType))
+		{
+			case VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT:
+			{
+				const VkPipelineRasterizationLineStateCreateInfoEXT *lineStateCreateInfo = reinterpret_cast<const VkPipelineRasterizationLineStateCreateInfoEXT *>(extensionCreateInfo);
+				lineRasterizationMode = lineStateCreateInfo->lineRasterizationMode;
+			}
+			break;
+			case VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT:
+			{
+				const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT *provokingVertexModeCreateInfo =
+				    reinterpret_cast<const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT *>(extensionCreateInfo);
+				provokingVertexMode = provokingVertexModeCreateInfo->provokingVertexMode;
+			}
+			break;
+			default:
+				WARN("pCreateInfo->pRasterizationState->pNext sType = %s", vk::Stringify(extensionCreateInfo->sType).c_str());
+				break;
+		}
+
+		extensionCreateInfo = extensionCreateInfo->pNext;
+	}
+
+	// The sample count affects the batch size, so it needs initialization even if rasterization is disabled.
+	// TODO(b/147812380): Eliminate the dependency between multisampling and batch size.
+	sampleCount = 1;
+
+	// Only access rasterization state if rasterization is not disabled.
+	if(rasterizationState->rasterizerDiscardEnable == VK_FALSE)
+	{
+		const VkPipelineViewportStateCreateInfo *viewportState = pCreateInfo->pViewportState;
+		const VkPipelineMultisampleStateCreateInfo *multisampleState = pCreateInfo->pMultisampleState;
+		const VkPipelineDepthStencilStateCreateInfo *depthStencilState = pCreateInfo->pDepthStencilState;
+		const VkPipelineColorBlendStateCreateInfo *colorBlendState = pCreateInfo->pColorBlendState;
+
+		if(viewportState->flags != 0)
+		{
+			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+			UNSUPPORTED("pCreateInfo->pViewportState->flags %d", int(pCreateInfo->pViewportState->flags));
+		}
+
+		if((viewportState->viewportCount != 1) ||
+		   (viewportState->scissorCount != 1))
+		{
+			UNSUPPORTED("VkPhysicalDeviceFeatures::multiViewport");
+		}
+
+		if(!hasDynamicState(VK_DYNAMIC_STATE_SCISSOR))
+		{
+			scissor = viewportState->pScissors[0];
+		}
+
+		if(!hasDynamicState(VK_DYNAMIC_STATE_VIEWPORT))
+		{
+			viewport = viewportState->pViewports[0];
+		}
+
+		if(multisampleState->flags != 0)
+		{
+			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+			UNSUPPORTED("pCreateInfo->pMultisampleState->flags %d", int(pCreateInfo->pMultisampleState->flags));
+		}
+
+		if(multisampleState->sampleShadingEnable != VK_FALSE)
+		{
+			UNSUPPORTED("VkPhysicalDeviceFeatures::sampleRateShading");
+		}
+
+		if(multisampleState->alphaToOneEnable != VK_FALSE)
+		{
+			UNSUPPORTED("VkPhysicalDeviceFeatures::alphaToOne");
+		}
+
+		switch(multisampleState->rasterizationSamples)
+		{
+			case VK_SAMPLE_COUNT_1_BIT:
+				sampleCount = 1;
+				break;
+			case VK_SAMPLE_COUNT_4_BIT:
+				sampleCount = 4;
+				break;
+			default:
+				UNSUPPORTED("Unsupported sample count");
+		}
+
+		VkSampleMask sampleMask;
+		if(multisampleState->pSampleMask)
+		{
+			sampleMask = multisampleState->pSampleMask[0];
+		}
+		else  // "If pSampleMask is NULL, it is treated as if the mask has all bits set to 1."
+		{
+			sampleMask = ~0;
+		}
+
+		alphaToCoverage = (multisampleState->alphaToCoverageEnable != VK_FALSE);
+		multiSampleMask = sampleMask & ((unsigned)0xFFFFFFFF >> (32 - sampleCount));
+
+		const vk::RenderPass *renderPass = vk::Cast(pCreateInfo->renderPass);
+		const VkSubpassDescription &subpass = renderPass->getSubpass(pCreateInfo->subpass);
+
+		//  Ignore pDepthStencilState when "the subpass of the render pass the pipeline is created against does not use a depth/stencil attachment"
+		if(subpass.pDepthStencilAttachment && subpass.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED)
+		{
+			if(depthStencilState->flags != 0)
+			{
+				// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+				UNSUPPORTED("pCreateInfo->pDepthStencilState->flags %d", int(pCreateInfo->pDepthStencilState->flags));
+			}
+
+			if(depthStencilState->depthBoundsTestEnable != VK_FALSE)
+			{
+				UNSUPPORTED("VkPhysicalDeviceFeatures::depthBounds");
+			}
+
+			depthBoundsTestEnable = (depthStencilState->depthBoundsTestEnable != VK_FALSE);
+			depthBufferEnable = (depthStencilState->depthTestEnable != VK_FALSE);
+			depthWriteEnable = (depthStencilState->depthWriteEnable != VK_FALSE);
+			depthCompareMode = depthStencilState->depthCompareOp;
+
+			stencilEnable = (depthStencilState->stencilTestEnable != VK_FALSE);
+			if(stencilEnable)
+			{
+				frontStencil = depthStencilState->front;
+				backStencil = depthStencilState->back;
+			}
+		}
+
+		bool colorAttachmentUsed = false;
+		for(uint32_t i = 0; i < subpass.colorAttachmentCount; i++)
+		{
+			if(subpass.pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED)
+			{
+				colorAttachmentUsed = true;
+				break;
+			}
+		}
+
+		// Ignore pColorBlendState when "the subpass of the render pass the pipeline is created against does not use any color attachments"
+		if(colorAttachmentUsed)
+		{
+			if(colorBlendState->flags != 0)
+			{
+				// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
+				UNSUPPORTED("pCreateInfo->pColorBlendState->flags %d", int(pCreateInfo->pColorBlendState->flags));
+			}
+
+			if(colorBlendState->logicOpEnable != VK_FALSE)
+			{
+				UNSUPPORTED("VkPhysicalDeviceFeatures::logicOp");
+			}
+
+			if(!hasDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS))
+			{
+				blendConstants.x = colorBlendState->blendConstants[0];
+				blendConstants.y = colorBlendState->blendConstants[1];
+				blendConstants.z = colorBlendState->blendConstants[2];
+				blendConstants.w = colorBlendState->blendConstants[3];
+			}
+
+			ASSERT(colorBlendState->attachmentCount <= sw::RENDERTARGETS);
+			for(auto i = 0u; i < colorBlendState->attachmentCount; i++)
+			{
+				const VkPipelineColorBlendAttachmentState &attachment = colorBlendState->pAttachments[i];
+				colorWriteMask[i] = attachment.colorWriteMask;
+				blendState[i] = { (attachment.blendEnable != VK_FALSE),
+					              attachment.srcColorBlendFactor, attachment.dstColorBlendFactor, attachment.colorBlendOp,
+					              attachment.srcAlphaBlendFactor, attachment.dstAlphaBlendFactor, attachment.alphaBlendOp };
+			}
+		}
+	}
+}
+
+bool GraphicsState::isDrawPoint(bool polygonModeAware) const
 {
 	switch(topology)
 	{
@@ -43,7 +538,7 @@
 	return false;
 }
 
-bool Context::isDrawLine(bool polygonModeAware) const
+bool GraphicsState::isDrawLine(bool polygonModeAware) const
 {
 	switch(topology)
 	{
@@ -62,7 +557,7 @@
 	return false;
 }
 
-bool Context::isDrawTriangle(bool polygonModeAware) const
+bool GraphicsState::isDrawTriangle(bool polygonModeAware) const
 {
 	switch(topology)
 	{
@@ -80,78 +575,121 @@
 	return false;
 }
 
-bool Context::depthWriteActive() const
+bool GraphicsState::depthWriteActive(const Attachments &attachments) const
 {
-	if(!depthBufferActive()) return false;
+	if(!depthBufferActive(attachments)) return false;
 
 	return depthWriteEnable;
 }
 
-bool Context::depthBufferActive() const
+bool GraphicsState::depthBufferActive(const Attachments &attachments) const
 {
-	return depthBuffer && depthBufferEnable;
+	return attachments.depthBuffer && depthBufferEnable;
 }
 
-bool Context::stencilActive() const
+bool GraphicsState::stencilActive(const Attachments &attachments) const
 {
-	return stencilBuffer && stencilEnable;
+	return attachments.stencilBuffer && stencilEnable;
 }
 
-void Context::setBlendState(int index, BlendState state)
+const GraphicsState GraphicsState::combineStates(const DynamicState &dynamicState) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	GraphicsState combinedState = *this;
 
-	blendState[index] = state;
+	// Apply either pipeline state or dynamic state
+	if(hasDynamicState(VK_DYNAMIC_STATE_SCISSOR))
+	{
+		combinedState.scissor = dynamicState.scissor;
+	}
+
+	if(hasDynamicState(VK_DYNAMIC_STATE_VIEWPORT))
+	{
+		combinedState.viewport = dynamicState.viewport;
+	}
+
+	if(hasDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS))
+	{
+		combinedState.blendConstants = dynamicState.blendConstants;
+	}
+
+	if(hasDynamicState(VK_DYNAMIC_STATE_DEPTH_BIAS))
+	{
+		combinedState.constantDepthBias = dynamicState.depthBiasConstantFactor;
+		combinedState.slopeDepthBias = dynamicState.depthBiasSlopeFactor;
+		combinedState.depthBiasClamp = dynamicState.depthBiasClamp;
+	}
+
+	if(hasDynamicState(VK_DYNAMIC_STATE_DEPTH_BOUNDS) && depthBoundsTestEnable)
+	{
+		// Unless the VK_EXT_depth_range_unrestricted extension is enabled,
+		// minDepthBounds and maxDepthBounds must be between 0.0 and 1.0, inclusive
+		ASSERT(dynamicState.minDepthBounds >= 0.0f && dynamicState.minDepthBounds <= 1.0f);
+		ASSERT(dynamicState.maxDepthBounds >= 0.0f && dynamicState.maxDepthBounds <= 1.0f);
+
+		UNSUPPORTED("VkPhysicalDeviceFeatures::depthBounds");
+	}
+
+	if(hasDynamicState(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK) && stencilEnable)
+	{
+		combinedState.frontStencil.compareMask = dynamicState.compareMask[0];
+		combinedState.backStencil.compareMask = dynamicState.compareMask[1];
+	}
+
+	if(hasDynamicState(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK) && stencilEnable)
+	{
+		combinedState.frontStencil.writeMask = dynamicState.writeMask[0];
+		combinedState.backStencil.writeMask = dynamicState.writeMask[1];
+	}
+
+	if(hasDynamicState(VK_DYNAMIC_STATE_STENCIL_REFERENCE) && stencilEnable)
+	{
+		combinedState.frontStencil.reference = dynamicState.reference[0];
+		combinedState.backStencil.reference = dynamicState.reference[1];
+	}
+
+	return combinedState;
 }
 
-BlendState Context::getBlendState(int index) const
+BlendState GraphicsState::getBlendState(int index, const Attachments &attachments, bool fragmentContainsKill) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	BlendState activeBlendState;
-	activeBlendState.alphaBlendEnable = alphaBlendActive(index);
+	activeBlendState.alphaBlendEnable = alphaBlendActive(index, attachments, fragmentContainsKill);
 	activeBlendState.sourceBlendFactor = sourceBlendFactor(index);
 	activeBlendState.destBlendFactor = destBlendFactor(index);
-	activeBlendState.blendOperation = blendOperation(index);
+	activeBlendState.blendOperation = blendOperation(index, attachments);
 	activeBlendState.sourceBlendFactorAlpha = sourceBlendFactorAlpha(index);
 	activeBlendState.destBlendFactorAlpha = destBlendFactorAlpha(index);
-	activeBlendState.blendOperationAlpha = blendOperationAlpha(index);
+	activeBlendState.blendOperationAlpha = blendOperationAlpha(index, attachments);
 	return activeBlendState;
 }
 
-bool Context::isColorClamped(int index) const
+bool GraphicsState::alphaBlendActive(int index, const Attachments &attachments, bool fragmentContainsKill) const
 {
-	if(renderTarget[index] && renderTarget[index]->getFormat().isFloatFormat())
-	{
-		return false;
-	}
-
-	return true;
-}
-
-bool Context::alphaBlendActive(int index) const
-{
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	if(!blendState[index].alphaBlendEnable)
 	{
 		return false;
 	}
 
-	if(!colorUsed())
+	if(!(colorWriteActive(attachments) || fragmentContainsKill))
 	{
 		return false;
 	}
 
-	bool colorBlend = !(blendOperation(index) == VK_BLEND_OP_SRC_EXT && sourceBlendFactor(index) == VK_BLEND_FACTOR_ONE);
-	bool alphaBlend = !(blendOperationAlpha(index) == VK_BLEND_OP_SRC_EXT && sourceBlendFactorAlpha(index) == VK_BLEND_FACTOR_ONE);
+	bool colorBlend = !(blendOperation(index, attachments) == VK_BLEND_OP_SRC_EXT &&
+	                    sourceBlendFactor(index) == VK_BLEND_FACTOR_ONE);
+	bool alphaBlend = !(blendOperationAlpha(index, attachments) == VK_BLEND_OP_SRC_EXT &&
+	                    sourceBlendFactorAlpha(index) == VK_BLEND_FACTOR_ONE);
 
 	return colorBlend || alphaBlend;
 }
 
-VkBlendFactor Context::sourceBlendFactor(int index) const
+VkBlendFactor GraphicsState::sourceBlendFactor(int index) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	if(!blendState[index].alphaBlendEnable) return VK_BLEND_FACTOR_ONE;
 
@@ -172,9 +710,9 @@
 	return blendState[index].sourceBlendFactor;
 }
 
-VkBlendFactor Context::destBlendFactor(int index) const
+VkBlendFactor GraphicsState::destBlendFactor(int index) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	if(!blendState[index].alphaBlendEnable) return VK_BLEND_FACTOR_ONE;
 
@@ -195,9 +733,9 @@
 	return blendState[index].destBlendFactor;
 }
 
-VkBlendOp Context::blendOperation(int index) const
+VkBlendOp GraphicsState::blendOperation(int index, const Attachments &attachments) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	if(!blendState[index].alphaBlendEnable) return VK_BLEND_OP_SRC_EXT;
 
@@ -238,7 +776,7 @@
 				}
 			}
 		case VK_BLEND_OP_SUBTRACT:
-			if(sourceBlendFactor(index) == VK_BLEND_FACTOR_ZERO && isColorClamped(index))
+			if(sourceBlendFactor(index) == VK_BLEND_FACTOR_ZERO && attachments.isColorClamped(index))
 			{
 				return VK_BLEND_OP_ZERO_EXT;  // Negative, clamped to zero
 			}
@@ -278,7 +816,7 @@
 			}
 			else if(sourceBlendFactor(index) == VK_BLEND_FACTOR_ONE)
 			{
-				if(destBlendFactor(index) == VK_BLEND_FACTOR_ZERO && isColorClamped(index))
+				if(destBlendFactor(index) == VK_BLEND_FACTOR_ZERO && attachments.isColorClamped(index))
 				{
 					return VK_BLEND_OP_ZERO_EXT;  // Negative, clamped to zero
 				}
@@ -289,7 +827,7 @@
 			}
 			else
 			{
-				if(destBlendFactor(index) == VK_BLEND_FACTOR_ZERO && isColorClamped(index))
+				if(destBlendFactor(index) == VK_BLEND_FACTOR_ZERO && attachments.isColorClamped(index))
 				{
 					return VK_BLEND_OP_ZERO_EXT;  // Negative, clamped to zero
 				}
@@ -309,9 +847,9 @@
 	return blendState[index].blendOperation;
 }
 
-VkBlendFactor Context::sourceBlendFactorAlpha(int index) const
+VkBlendFactor GraphicsState::sourceBlendFactorAlpha(int index) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	switch(blendState[index].blendOperationAlpha)
 	{
@@ -330,9 +868,9 @@
 	return blendState[index].sourceBlendFactorAlpha;
 }
 
-VkBlendFactor Context::destBlendFactorAlpha(int index) const
+VkBlendFactor GraphicsState::destBlendFactorAlpha(int index) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	switch(blendState[index].blendOperationAlpha)
 	{
@@ -351,9 +889,9 @@
 	return blendState[index].destBlendFactorAlpha;
 }
 
-VkBlendOp Context::blendOperationAlpha(int index) const
+VkBlendOp GraphicsState::blendOperationAlpha(int index, const Attachments &attachments) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
 	switch(blendState[index].blendOperationAlpha)
 	{
@@ -392,7 +930,7 @@
 				}
 			}
 		case VK_BLEND_OP_SUBTRACT:
-			if(sourceBlendFactorAlpha(index) == VK_BLEND_FACTOR_ZERO && isColorClamped(index))
+			if(sourceBlendFactorAlpha(index) == VK_BLEND_FACTOR_ZERO && attachments.isColorClamped(index))
 			{
 				return VK_BLEND_OP_ZERO_EXT;  // Negative, clamped to zero
 			}
@@ -432,7 +970,7 @@
 			}
 			else if(sourceBlendFactorAlpha(index) == VK_BLEND_FACTOR_ONE)
 			{
-				if(destBlendFactorAlpha(index) == VK_BLEND_FACTOR_ZERO && isColorClamped(index))
+				if(destBlendFactorAlpha(index) == VK_BLEND_FACTOR_ZERO && attachments.isColorClamped(index))
 				{
 					return VK_BLEND_OP_ZERO_EXT;  // Negative, clamped to zero
 				}
@@ -443,7 +981,7 @@
 			}
 			else
 			{
-				if(destBlendFactorAlpha(index) == VK_BLEND_FACTOR_ZERO && isColorClamped(index))
+				if(destBlendFactorAlpha(index) == VK_BLEND_FACTOR_ZERO && attachments.isColorClamped(index))
 				{
 					return VK_BLEND_OP_ZERO_EXT;  // Negative, clamped to zero
 				}
@@ -463,25 +1001,11 @@
 	return blendState[index].blendOperationAlpha;
 }
 
-VkFormat Context::renderTargetInternalFormat(int index) const
+bool GraphicsState::colorWriteActive(const Attachments &attachments) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
-
-	if(renderTarget[index])
+	for(int i = 0; i < sw::RENDERTARGETS; i++)
 	{
-		return renderTarget[index]->getFormat();
-	}
-	else
-	{
-		return VK_FORMAT_UNDEFINED;
-	}
-}
-
-bool Context::colorWriteActive() const
-{
-	for(int i = 0; i < RENDERTARGETS; i++)
-	{
-		if(colorWriteActive(i))
+		if(colorWriteActive(i, attachments))
 		{
 			return true;
 		}
@@ -490,17 +1014,17 @@
 	return false;
 }
 
-int Context::colorWriteActive(int index) const
+int GraphicsState::colorWriteActive(int index, const Attachments &attachments) const
 {
-	ASSERT((index >= 0) && (index < RENDERTARGETS));
+	ASSERT((index >= 0) && (index < sw::RENDERTARGETS));
 
-	if(!renderTarget[index] || renderTarget[index]->getFormat() == VK_FORMAT_UNDEFINED)
+	if(!attachments.renderTarget[index] || attachments.renderTarget[index]->getFormat() == VK_FORMAT_UNDEFINED)
 	{
 		return 0;
 	}
 
-	if(blendOperation(index) == VK_BLEND_OP_DST_EXT && destBlendFactor(index) == VK_BLEND_FACTOR_ONE &&
-	   (blendOperationAlpha(index) == VK_BLEND_OP_DST_EXT && destBlendFactorAlpha(index) == VK_BLEND_FACTOR_ONE))
+	if(blendOperation(index, attachments) == VK_BLEND_OP_DST_EXT && destBlendFactor(index) == VK_BLEND_FACTOR_ONE &&
+	   (blendOperationAlpha(index, attachments) == VK_BLEND_OP_DST_EXT && destBlendFactorAlpha(index) == VK_BLEND_FACTOR_ONE))
 	{
 		return 0;
 	}
@@ -508,9 +1032,4 @@
 	return colorWriteMask[index];
 }
 
-bool Context::colorUsed() const
-{
-	return colorWriteActive() || (pixelShader && pixelShader->getModes().ContainsKill);
-}
-
-}  // namespace sw
+}  // namespace vk
\ No newline at end of file
diff --git a/src/Device/Context.hpp b/src/Device/Context.hpp
index 60c36b5..637855e 100644
--- a/src/Device/Context.hpp
+++ b/src/Device/Context.hpp
@@ -1,4 +1,4 @@
-// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
+// Copyright 2020 The SwiftShader Authors. All Rights Reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -12,33 +12,78 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#ifndef sw_Context_hpp
-#define sw_Context_hpp
+#ifndef vk_Context_hpp
+#define vk_Context_hpp
 
 #include "Config.hpp"
 #include "Memset.hpp"
 #include "Stream.hpp"
 #include "System/Types.hpp"
-#include "Vulkan/VkConfig.hpp"
 #include "Vulkan/VkDescriptorSet.hpp"
 
+#include <vector>
+
 namespace vk {
 
+class Buffer;
+class Device;
 class ImageView;
 class PipelineLayout;
 
-}  // namespace vk
-
-namespace sw {
-
-class SpirvShader;
-
-struct PushConstantStorage
+struct VertexInputBinding
 {
-	unsigned char data[vk::MAX_PUSH_CONSTANT_SIZE];
+	Buffer *buffer;
+	VkDeviceSize offset;
 };
 
-struct BlendState : Memset<BlendState>
+struct IndexBuffer
+{
+	inline VkIndexType getIndexType() const { return indexType; }
+	void setIndexBufferBinding(const VertexInputBinding &indexBufferBinding, VkIndexType type);
+	void getIndexBuffers(VkPrimitiveTopology topology, uint32_t count, uint32_t first, bool indexed, bool hasPrimitiveRestartEnable, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const;
+
+private:
+	int bytesPerIndex() const;
+
+	VertexInputBinding binding;
+	VkIndexType indexType;
+};
+
+struct Attachments
+{
+	ImageView *renderTarget[sw::RENDERTARGETS] = {};
+	ImageView *depthBuffer = nullptr;
+	ImageView *stencilBuffer = nullptr;
+
+	bool isColorClamped(int index) const;
+	VkFormat renderTargetInternalFormat(int index) const;
+};
+
+struct Inputs
+{
+	Inputs(const VkPipelineVertexInputStateCreateInfo *vertexInputState);
+
+	void updateDescriptorSets(const DescriptorSet::Array &dso,
+	                          const DescriptorSet::Bindings &ds,
+	                          const DescriptorSet::DynamicOffsets &ddo);
+	inline const DescriptorSet::Array &getDescriptorSetObjects() const { return descriptorSetObjects; }
+	inline const DescriptorSet::Bindings &getDescriptorSets() const { return descriptorSets; }
+	inline const DescriptorSet::DynamicOffsets &getDescriptorDynamicOffsets() const { return descriptorDynamicOffsets; }
+	inline const sw::Stream &getStream(uint32_t i) const { return stream[i]; }
+
+	void bindVertexInputs(int firstInstance);
+	void setVertexInputBinding(const VertexInputBinding vertexInputBindings[]);
+	void advanceInstanceAttributes();
+
+private:
+	VertexInputBinding vertexInputBindings[MAX_VERTEX_INPUT_BINDINGS] = {};
+	DescriptorSet::Array descriptorSetObjects = {};
+	DescriptorSet::Bindings descriptorSets = {};
+	DescriptorSet::DynamicOffsets descriptorDynamicOffsets = {};
+	sw::Stream stream[sw::MAX_INTERFACE_COMPONENTS / 4];
+};
+
+struct BlendState : sw::Memset<BlendState>
 {
 	BlendState()
 	    : Memset(this, 0)
@@ -70,24 +115,93 @@
 	VkBlendOp blendOperationAlpha;
 };
 
-class Context
+struct DynamicState
 {
-public:
-	Context() = default;
+	VkViewport viewport;
+	VkRect2D scissor;
+	sw::float4 blendConstants;
+	float depthBiasConstantFactor = 0.0f;
+	float depthBiasClamp = 0.0f;
+	float depthBiasSlopeFactor = 0.0f;
+	float minDepthBounds = 0.0f;
+	float maxDepthBounds = 0.0f;
+
+	uint32_t compareMask[2] = { 0 };
+	uint32_t writeMask[2] = { 0 };
+	uint32_t reference[2] = { 0 };
+};
+
+struct GraphicsState
+{
+	GraphicsState(const Device *device, const VkGraphicsPipelineCreateInfo *pCreateInfo, const PipelineLayout *layout, bool robustBufferAccess);
+
+	const GraphicsState combineStates(const DynamicState &dynamicState) const;
+
+	inline const PipelineLayout *getPipelineLayout() const { return pipelineLayout; }
+	inline bool getRobustBufferAccess() const { return robustBufferAccess; }
+	inline VkPrimitiveTopology getTopology() const { return topology; }
+
+	inline VkProvokingVertexModeEXT getProvokingVertexMode() const { return provokingVertexMode; }
+
+	inline VkStencilOpState getFrontStencil() const { return frontStencil; }
+	inline VkStencilOpState getBackStencil() const { return backStencil; }
+
+	// Pixel processor states
+	inline VkCullModeFlags getCullMode() const { return cullMode; }
+	inline VkFrontFace getFrontFace() const { return frontFace; }
+	inline VkPolygonMode getPolygonMode() const { return polygonMode; }
+	inline VkLineRasterizationModeEXT getLineRasterizationMode() const { return lineRasterizationMode; }
+
+	inline float getConstantDepthBias() const { return constantDepthBias; }
+	inline float getSlopeDepthBias() const { return slopeDepthBias; }
+	inline float getDepthBiasClamp() const { return depthBiasClamp; }
+	inline bool hasDepthRangeUnrestricted() const { return depthRangeUnrestricted; }
+
+	// Pixel processor states
+	inline bool hasRasterizerDiscard() const { return rasterizerDiscard; }
+	inline VkCompareOp getDepthCompareMode() const { return depthCompareMode; }
+
+	inline float getLineWidth() const { return lineWidth; }
+
+	inline unsigned int getMultiSampleMask() const { return multiSampleMask; }
+	inline int getSampleCount() const { return sampleCount; }
+	inline bool hasAlphaToCoverage() const { return alphaToCoverage; }
+
+	inline bool hasPrimitiveRestartEnable() const { return primitiveRestartEnable; }
+	inline const VkRect2D &getScissor() const { return scissor; }
+	inline const VkViewport &getViewport() const { return viewport; }
+	inline const sw::float4 &getBlendConstants() const { return blendConstants; }
 
 	bool isDrawPoint(bool polygonModeAware) const;
 	bool isDrawLine(bool polygonModeAware) const;
 	bool isDrawTriangle(bool polygonModeAware) const;
 
-	bool depthWriteActive() const;
-	bool depthBufferActive() const;
-	bool stencilActive() const;
+	BlendState getBlendState(int index, const Attachments &attachments, bool fragmentContainsKill) const;
 
-	void setBlendState(int index, BlendState state);
-	BlendState getBlendState(int index) const;
-	bool isColorClamped(int index) const;
+	int colorWriteActive(int index, const Attachments &attachments) const;
+	bool depthWriteActive(const Attachments &attachments) const;
+	bool depthBufferActive(const Attachments &attachments) const;
+	bool stencilActive(const Attachments &attachments) const;
 
+private:
+	inline bool hasDynamicState(VkDynamicState dynamicState) const { return (dynamicStateFlags & (1 << dynamicState)) != 0; }
+
+	VkBlendFactor sourceBlendFactor(int index) const;
+	VkBlendFactor destBlendFactor(int index) const;
+	VkBlendOp blendOperation(int index, const Attachments &attachments) const;
+
+	VkBlendFactor sourceBlendFactorAlpha(int index) const;
+	VkBlendFactor destBlendFactorAlpha(int index) const;
+	VkBlendOp blendOperationAlpha(int index, const Attachments &attachments) const;
+
+	bool alphaBlendActive(int index, const Attachments &attachments, bool fragmentContainsKill) const;
+	bool colorWriteActive(const Attachments &attachments) const;
+
+	const PipelineLayout *pipelineLayout;
+	const bool robustBufferAccess = true;
+	uint32_t dynamicStateFlags = 0;
 	VkPrimitiveTopology topology;
+
 	VkProvokingVertexModeEXT provokingVertexMode;
 
 	bool stencilEnable;
@@ -105,27 +219,6 @@
 	float depthBiasClamp;
 	bool depthRangeUnrestricted;
 
-	VkFormat renderTargetInternalFormat(int index) const;
-	int colorWriteActive(int index) const;
-
-	vk::DescriptorSet::Array descriptorSetObjects = {};
-	vk::DescriptorSet::Bindings descriptorSets = {};
-	vk::DescriptorSet::DynamicOffsets descriptorDynamicOffsets = {};
-	Stream input[MAX_INTERFACE_COMPONENTS / 4];
-	bool robustBufferAccess;
-
-	vk::ImageView *renderTarget[RENDERTARGETS];
-	vk::ImageView *depthBuffer;
-	vk::ImageView *stencilBuffer;
-
-	vk::PipelineLayout const *pipelineLayout;
-
-	// Shaders
-	const SpirvShader *pixelShader;
-	const SpirvShader *vertexShader;
-
-	bool occlusionEnabled;
-
 	// Pixel processor states
 	bool rasterizerDiscard;
 	bool depthBoundsTestEnable;
@@ -135,28 +228,19 @@
 
 	float lineWidth;
 
-	int colorWriteMask[RENDERTARGETS];  // RGBA
-	unsigned int sampleMask;
+	int colorWriteMask[sw::RENDERTARGETS];  // RGBA
 	unsigned int multiSampleMask;
 	int sampleCount;
 	bool alphaToCoverage;
 
-private:
-	bool colorWriteActive() const;
-	bool colorUsed() const;
+	bool primitiveRestartEnable = false;
+	VkRect2D scissor;
+	VkViewport viewport;
+	sw::float4 blendConstants;
 
-	bool alphaBlendActive(int index) const;
-	VkBlendFactor sourceBlendFactor(int index) const;
-	VkBlendFactor destBlendFactor(int index) const;
-	VkBlendOp blendOperation(int index) const;
-
-	VkBlendFactor sourceBlendFactorAlpha(int index) const;
-	VkBlendFactor destBlendFactorAlpha(int index) const;
-	VkBlendOp blendOperationAlpha(int index) const;
-
-	BlendState blendState[RENDERTARGETS];
+	BlendState blendState[sw::RENDERTARGETS];
 };
 
-}  // namespace sw
+}  // namespace vk
 
-#endif  // sw_Context_hpp
+#endif  // vk_Context_hpp
diff --git a/src/Device/PixelProcessor.cpp b/src/Device/PixelProcessor.cpp
index 4691a01..72a86f3 100644
--- a/src/Device/PixelProcessor.cpp
+++ b/src/Device/PixelProcessor.cpp
@@ -82,17 +82,17 @@
 	routineCache = std::make_unique<RoutineCacheType>(clamp(cacheSize, 1, 65536));
 }
 
-const PixelProcessor::State PixelProcessor::update(const Context *context) const
+const PixelProcessor::State PixelProcessor::update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *fragmentShader, const sw::SpirvShader *vertexShader, const vk::Attachments &attachments, bool occlusionEnabled) const
 {
 	State state;
 
-	state.numClipDistances = context->vertexShader->getNumOutputClipDistances();
-	state.numCullDistances = context->vertexShader->getNumOutputCullDistances();
+	state.numClipDistances = vertexShader->getNumOutputClipDistances();
+	state.numCullDistances = vertexShader->getNumOutputCullDistances();
 
-	if(context->pixelShader)
+	if(fragmentShader)
 	{
-		state.shaderID = context->pixelShader->getSerialID();
-		state.pipelineLayoutIdentifier = context->pipelineLayout->identifier;
+		state.shaderID = fragmentShader->getSerialID();
+		state.pipelineLayoutIdentifier = pipelineState.getPipelineLayout()->identifier;
 	}
 	else
 	{
@@ -100,49 +100,50 @@
 		state.pipelineLayoutIdentifier = 0;
 	}
 
-	state.alphaToCoverage = context->alphaToCoverage;
-	state.depthWriteEnable = context->depthWriteActive();
+	state.alphaToCoverage = pipelineState.hasAlphaToCoverage();
+	state.depthWriteEnable = pipelineState.depthWriteActive(attachments);
 
-	if(context->stencilActive())
+	if(pipelineState.stencilActive(attachments))
 	{
 		state.stencilActive = true;
-		state.frontStencil = context->frontStencil;
-		state.backStencil = context->backStencil;
+		state.frontStencil = pipelineState.getFrontStencil();
+		state.backStencil = pipelineState.getBackStencil();
 	}
 
-	if(context->depthBufferActive())
+	if(pipelineState.depthBufferActive(attachments))
 	{
 		state.depthTestActive = true;
-		state.depthCompareMode = context->depthCompareMode;
-		state.depthFormat = context->depthBuffer->getFormat();
+		state.depthCompareMode = pipelineState.getDepthCompareMode();
+		state.depthFormat = attachments.depthBuffer->getFormat();
 
-		state.depthBias = (context->constantDepthBias != 0.0f) || (context->slopeDepthBias != 0.0f);
+		state.depthBias = (pipelineState.getConstantDepthBias() != 0.0f) || (pipelineState.getSlopeDepthBias() != 0.0f);
 
 		// "For fixed-point depth buffers, fragment depth values are always limited to the range [0,1] by clamping after depth bias addition is performed.
 		//  Unless the VK_EXT_depth_range_unrestricted extension is enabled, fragment depth values are clamped even when the depth buffer uses a floating-point representation."
-		state.depthClamp = !state.depthFormat.isFloatFormat() || !context->depthRangeUnrestricted;
+		state.depthClamp = !state.depthFormat.isFloatFormat() || !pipelineState.hasDepthRangeUnrestricted();
 	}
 
-	state.occlusionEnabled = context->occlusionEnabled;
+	state.occlusionEnabled = occlusionEnabled;
 
+	bool fragmentContainsKill = (fragmentShader && fragmentShader->getModes().ContainsKill);
 	for(int i = 0; i < RENDERTARGETS; i++)
 	{
-		state.colorWriteMask |= context->colorWriteActive(i) << (4 * i);
-		state.targetFormat[i] = context->renderTargetInternalFormat(i);
-		state.blendState[i] = context->getBlendState(i);
+		state.colorWriteMask |= pipelineState.colorWriteActive(i, attachments) << (4 * i);
+		state.targetFormat[i] = attachments.renderTargetInternalFormat(i);
+		state.blendState[i] = pipelineState.getBlendState(i, attachments, fragmentContainsKill);
 	}
 
-	state.multiSampleCount = static_cast<unsigned int>(context->sampleCount);
-	state.multiSampleMask = context->multiSampleMask;
+	state.multiSampleCount = static_cast<unsigned int>(pipelineState.getSampleCount());
+	state.multiSampleMask = pipelineState.getMultiSampleMask();
 	state.enableMultiSampling = (state.multiSampleCount > 1) &&
-	                            !(context->isDrawLine(true) && (context->lineRasterizationMode == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT));
+	                            !(pipelineState.isDrawLine(true) && (pipelineState.getLineRasterizationMode() == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT));
 
-	if(state.enableMultiSampling && context->pixelShader)
+	if(state.enableMultiSampling && fragmentShader)
 	{
-		state.centroid = context->pixelShader->getModes().NeedsCentroid;
+		state.centroid = fragmentShader->getModes().NeedsCentroid;
 	}
 
-	state.frontFace = context->frontFace;
+	state.frontFace = pipelineState.getFrontFace();
 
 	state.hash = state.computeHash();
 
@@ -150,8 +151,8 @@
 }
 
 PixelProcessor::RoutineType PixelProcessor::routine(const State &state,
-                                                    vk::PipelineLayout const *pipelineLayout,
-                                                    SpirvShader const *pixelShader,
+                                                    const vk::PipelineLayout *pipelineLayout,
+                                                    const SpirvShader *pixelShader,
                                                     const vk::DescriptorSet::Bindings &descriptorSets)
 {
 	auto routine = routineCache->lookup(state);
diff --git a/src/Device/PixelProcessor.hpp b/src/Device/PixelProcessor.hpp
index 16b879c..328197e 100644
--- a/src/Device/PixelProcessor.hpp
+++ b/src/Device/PixelProcessor.hpp
@@ -24,11 +24,9 @@
 
 namespace sw {
 
-class PixelShader;
-class Rasterizer;
-struct Texture;
 struct DrawData;
 struct Primitive;
+class SpirvShader;
 
 using RasterizerFunction = FunctionT<void(const Primitive *primitive, int count, int cluster, int clusterCount, DrawData *draw)>;
 
@@ -82,7 +80,7 @@
 		bool occlusionEnabled;
 		bool perspective;
 
-		BlendState blendState[RENDERTARGETS];
+		vk::BlendState blendState[RENDERTARGETS];
 
 		unsigned int colorWriteMask;
 		vk::Format targetFormat[RENDERTARGETS];
@@ -153,9 +151,9 @@
 
 	void setBlendConstant(const float4 &blendConstant);
 
-	const State update(const Context *context) const;
-	RoutineType routine(const State &state, vk::PipelineLayout const *pipelineLayout,
-	                    SpirvShader const *pixelShader, const vk::DescriptorSet::Bindings &descriptorSets);
+	const State update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *fragmentShader, const sw::SpirvShader *vertexShader, const vk::Attachments &attachments, bool occlusionEnabled) const;
+	RoutineType routine(const State &state, const vk::PipelineLayout *pipelineLayout,
+	                    const SpirvShader *pixelShader, const vk::DescriptorSet::Bindings &descriptorSets);
 	void setRoutineCacheSize(int routineCacheSize);
 
 	// Other semi-constants
diff --git a/src/Device/Rasterizer.hpp b/src/Device/Rasterizer.hpp
index 0c33857..a0c475a 100644
--- a/src/Device/Rasterizer.hpp
+++ b/src/Device/Rasterizer.hpp
@@ -15,7 +15,6 @@
 #ifndef sw_Rasterizer_hpp
 #define sw_Rasterizer_hpp
 
-#include "Context.hpp"
 #include "PixelProcessor.hpp"
 #include "Device/Config.hpp"
 
diff --git a/src/Device/Renderer.cpp b/src/Device/Renderer.cpp
index df22224..3cec3b2 100644
--- a/src/Device/Renderer.cpp
+++ b/src/Device/Renderer.cpp
@@ -180,9 +180,9 @@
 	vk::deallocate(mem, vk::DEVICE_MEMORY);
 }
 
-void Renderer::draw(const sw::Context *context, VkIndexType indexType, unsigned int count, int baseVertex,
+void Renderer::draw(const vk::GraphicsPipeline *pipeline, const vk::DynamicState &dynamicState, unsigned int count, int baseVertex,
                     CountedEvent *events, int instanceID, int viewID, void *indexBuffer, const VkExtent3D &framebufferExtent,
-                    PushConstantStorage const &pushConstants, bool update)
+                    vk::Pipeline::PushConstantStorage const &pushConstants, bool update)
 {
 	if(count == 0) { return; }
 
@@ -196,28 +196,38 @@
 	}
 	draw->id = id;
 
+	const vk::GraphicsState &pipelineState = pipeline->getState(dynamicState);
+	pixelProcessor.setBlendConstant(pipelineState.getBlendConstants());
+
+	const vk::Inputs &inputs = pipeline->getInputs();
+
 	if(update)
 	{
 		MARL_SCOPED_EVENT("update");
-		vertexState = vertexProcessor.update(context);
-		setupState = setupProcessor.update(context);
-		pixelState = pixelProcessor.update(context);
 
-		vertexRoutine = vertexProcessor.routine(vertexState, context->pipelineLayout, context->vertexShader, context->descriptorSets);
+		const sw::SpirvShader *fragmentShader = pipeline->getShader(VK_SHADER_STAGE_FRAGMENT_BIT).get();
+		const sw::SpirvShader *vertexShader = pipeline->getShader(VK_SHADER_STAGE_VERTEX_BIT).get();
+
+		const vk::Attachments attachments = pipeline->getAttachments();
+
+		vertexState = vertexProcessor.update(pipelineState, vertexShader, inputs);
+		setupState = setupProcessor.update(pipelineState, fragmentShader, vertexShader, attachments);
+		pixelState = pixelProcessor.update(pipelineState, fragmentShader, vertexShader, attachments, hasOcclusionQuery());
+
+		vertexRoutine = vertexProcessor.routine(vertexState, pipelineState.getPipelineLayout(), vertexShader, inputs.getDescriptorSets());
 		setupRoutine = setupProcessor.routine(setupState);
-		pixelRoutine = pixelProcessor.routine(pixelState, context->pipelineLayout, context->pixelShader, context->descriptorSets);
+		pixelRoutine = pixelProcessor.routine(pixelState, pipelineState.getPipelineLayout(), fragmentShader, inputs.getDescriptorSets());
 	}
 
-	draw->containsImageWrite = (context->vertexShader && context->vertexShader->containsImageWrite()) ||
-	                           (context->pixelShader && context->pixelShader->containsImageWrite());
+	draw->containsImageWrite = pipeline->containsImageWrite();
 
 	DrawCall::SetupFunction setupPrimitives = nullptr;
-	int ms = context->sampleCount;
+	int ms = pipelineState.getSampleCount();
 	unsigned int numPrimitivesPerBatch = MaxBatchSize / ms;
 
-	if(context->isDrawTriangle(false))
+	if(pipelineState.isDrawTriangle(false))
 	{
-		switch(context->polygonMode)
+		switch(pipelineState.getPolygonMode())
 		{
 			case VK_POLYGON_MODE_FILL:
 				setupPrimitives = &DrawCall::setupSolidTriangles;
@@ -231,11 +241,11 @@
 				numPrimitivesPerBatch /= 3;
 				break;
 			default:
-				UNSUPPORTED("polygon mode: %d", int(context->polygonMode));
+				UNSUPPORTED("polygon mode: %d", int(pipelineState.getPolygonMode()));
 				return;
 		}
 	}
-	else if(context->isDrawLine(false))
+	else if(pipelineState.isDrawLine(false))
 	{
 		setupPrimitives = &DrawCall::setupLines;
 	}
@@ -251,12 +261,12 @@
 	draw->numPrimitives = count;
 	draw->numPrimitivesPerBatch = numPrimitivesPerBatch;
 	draw->numBatches = (count + draw->numPrimitivesPerBatch - 1) / draw->numPrimitivesPerBatch;
-	draw->topology = context->topology;
-	draw->provokingVertexMode = context->provokingVertexMode;
-	draw->indexType = indexType;
-	draw->lineRasterizationMode = context->lineRasterizationMode;
-	draw->descriptorSetObjects = context->descriptorSetObjects;
-	draw->pipelineLayout = context->pipelineLayout;
+	draw->topology = pipelineState.getTopology();
+	draw->provokingVertexMode = pipelineState.getProvokingVertexMode();
+	draw->indexType = pipeline->getIndexBuffer().getIndexType();
+	draw->lineRasterizationMode = pipelineState.getLineRasterizationMode();
+	draw->descriptorSetObjects = inputs.getDescriptorSetObjects();
+	draw->pipelineLayout = pipelineState.getPipelineLayout();
 
 	draw->vertexRoutine = vertexRoutine;
 	draw->setupRoutine = setupRoutine;
@@ -264,14 +274,15 @@
 	draw->setupPrimitives = setupPrimitives;
 	draw->setupState = setupState;
 
-	data->descriptorSets = context->descriptorSets;
-	data->descriptorDynamicOffsets = context->descriptorDynamicOffsets;
+	data->descriptorSets = inputs.getDescriptorSets();
+	data->descriptorDynamicOffsets = inputs.getDescriptorDynamicOffsets();
 
 	for(int i = 0; i < MAX_INTERFACE_COMPONENTS / 4; i++)
 	{
-		data->input[i] = context->input[i].buffer;
-		data->robustnessSize[i] = context->input[i].robustnessSize;
-		data->stride[i] = context->input[i].vertexStride;
+		const sw::Stream &stream = inputs.getStream(i);
+		data->input[i] = stream.buffer;
+		data->robustnessSize[i] = stream.robustnessSize;
+		data->stride[i] = stream.vertexStride;
 	}
 
 	data->indices = indexBuffer;
@@ -281,11 +292,11 @@
 
 	if(pixelState.stencilActive)
 	{
-		data->stencil[0].set(context->frontStencil.reference, context->frontStencil.compareMask, context->frontStencil.writeMask);
-		data->stencil[1].set(context->backStencil.reference, context->backStencil.compareMask, context->backStencil.writeMask);
+		data->stencil[0].set(pipelineState.getFrontStencil().reference, pipelineState.getFrontStencil().compareMask, pipelineState.getFrontStencil().writeMask);
+		data->stencil[1].set(pipelineState.getBackStencil().reference, pipelineState.getBackStencil().compareMask, pipelineState.getBackStencil().writeMask);
 	}
 
-	data->lineWidth = context->lineWidth;
+	data->lineWidth = pipelineState.getLineWidth();
 
 	data->factor = pixelProcessor.factor;
 
@@ -321,6 +332,8 @@
 
 	// Viewport
 	{
+		const VkViewport &viewport = pipelineState.getViewport();
+
 		float W = 0.5f * viewport.width;
 		float H = 0.5f * viewport.height;
 		float X0 = viewport.x + W;
@@ -339,13 +352,14 @@
 		data->viewportHeight = abs(viewport.height);
 		data->depthRange = Z;
 		data->depthNear = N;
-		data->constantDepthBias = context->constantDepthBias;
-		data->slopeDepthBias = context->slopeDepthBias;
-		data->depthBiasClamp = context->depthBiasClamp;
+		data->constantDepthBias = pipelineState.getConstantDepthBias();
+		data->slopeDepthBias = pipelineState.getSlopeDepthBias();
+		data->depthBiasClamp = pipelineState.getDepthBiasClamp();
 
-		if(context->depthBuffer)
+		const vk::Attachments attachments = pipeline->getAttachments();
+		if(attachments.depthBuffer)
 		{
-			switch(context->depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT))
+			switch(attachments.depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT))
 			{
 				case VK_FORMAT_D16_UNORM:
 					data->minimumResolvableDepthDifference = 1.0f / 0xFFFF;
@@ -355,45 +369,49 @@
 					// buffers. DrawData::minimumResolvableDepthDifference is unused.
 					break;
 				default:
-					UNSUPPORTED("Depth format: %d", int(context->depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT)));
+					UNSUPPORTED("Depth format: %d", int(attachments.depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT)));
 			}
 		}
 	}
 
 	// Target
 	{
+		const vk::Attachments attachments = pipeline->getAttachments();
+
 		for(int index = 0; index < RENDERTARGETS; index++)
 		{
-			draw->renderTarget[index] = context->renderTarget[index];
+			draw->renderTarget[index] = attachments.renderTarget[index];
 
 			if(draw->renderTarget[index])
 			{
-				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);
+				data->colorBuffer[index] = (unsigned int *)attachments.renderTarget[index]->getOffsetPointer({ 0, 0, 0 }, VK_IMAGE_ASPECT_COLOR_BIT, 0, data->viewID);
+				data->colorPitchB[index] = attachments.renderTarget[index]->rowPitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0);
+				data->colorSliceB[index] = attachments.renderTarget[index]->slicePitchBytes(VK_IMAGE_ASPECT_COLOR_BIT, 0);
 			}
 		}
 
-		draw->depthBuffer = context->depthBuffer;
-		draw->stencilBuffer = context->stencilBuffer;
+		draw->depthBuffer = attachments.depthBuffer;
+		draw->stencilBuffer = attachments.stencilBuffer;
 
 		if(draw->depthBuffer)
 		{
-			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);
+			data->depthBuffer = (float *)attachments.depthBuffer->getOffsetPointer({ 0, 0, 0 }, VK_IMAGE_ASPECT_DEPTH_BIT, 0, data->viewID);
+			data->depthPitchB = attachments.depthBuffer->rowPitchBytes(VK_IMAGE_ASPECT_DEPTH_BIT, 0);
+			data->depthSliceB = attachments.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, data->viewID);
-			data->stencilPitchB = context->stencilBuffer->rowPitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
-			data->stencilSliceB = context->stencilBuffer->slicePitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
+			data->stencilBuffer = (unsigned char *)attachments.stencilBuffer->getOffsetPointer({ 0, 0, 0 }, VK_IMAGE_ASPECT_STENCIL_BIT, 0, data->viewID);
+			data->stencilPitchB = attachments.stencilBuffer->rowPitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
+			data->stencilSliceB = attachments.stencilBuffer->slicePitchBytes(VK_IMAGE_ASPECT_STENCIL_BIT, 0);
 		}
 	}
 
 	// Scissor
 	{
+		const VkRect2D &scissor = pipelineState.getScissor();
+
 		data->scissorX0 = clamp<int>(scissor.offset.x, 0, framebufferExtent.width);
 		data->scissorX1 = clamp<int>(scissor.offset.x + scissor.extent.width, 0, framebufferExtent.width);
 		data->scissorY0 = clamp<int>(scissor.offset.y, 0, framebufferExtent.height);
@@ -1193,34 +1211,4 @@
 	occlusionQuery = nullptr;
 }
 
-// TODO(b/137740918): Optimize instancing to use a single draw call.
-void Renderer::advanceInstanceAttributes(Stream *inputs)
-{
-	for(uint32_t i = 0; i < vk::MAX_VERTEX_INPUT_BINDINGS; i++)
-	{
-		auto &attrib = inputs[i];
-		if((attrib.format != VK_FORMAT_UNDEFINED) && attrib.instanceStride && (attrib.instanceStride < attrib.robustnessSize))
-		{
-			// Under the casts: attrib.buffer += attrib.instanceStride
-			attrib.buffer = (void const *)((uintptr_t)attrib.buffer + attrib.instanceStride);
-			attrib.robustnessSize -= attrib.instanceStride;
-		}
-	}
-}
-
-void Renderer::setViewport(const VkViewport &viewport)
-{
-	this->viewport = viewport;
-}
-
-void Renderer::setScissor(const VkRect2D &scissor)
-{
-	this->scissor = scissor;
-}
-
-void Renderer::setBlendConstant(const float4 &blendConstant)
-{
-	pixelProcessor.setBlendConstant(blendConstant);
-}
-
 }  // namespace sw
diff --git a/src/Device/Renderer.hpp b/src/Device/Renderer.hpp
index 57eb7ba..2124e42 100644
--- a/src/Device/Renderer.hpp
+++ b/src/Device/Renderer.hpp
@@ -20,8 +20,8 @@
 #include "Primitive.hpp"
 #include "SetupProcessor.hpp"
 #include "VertexProcessor.hpp"
-#include "Device/Config.hpp"
 #include "Vulkan/VkDescriptorSet.hpp"
+#include "Vulkan/VkPipeline.hpp"
 
 #include "marl/finally.h"
 #include "marl/pool.h"
@@ -114,7 +114,7 @@
 	float4 a2c2;
 	float4 a2c3;
 
-	PushConstantStorage pushConstants;
+	vk::Pipeline::PushConstantStorage pushConstants;
 };
 
 struct DrawCall
@@ -209,27 +209,16 @@
 
 	bool hasOcclusionQuery() const { return occlusionQuery != nullptr; }
 
-	void draw(const sw::Context *context, VkIndexType indexType, unsigned int count, int baseVertex,
+	void draw(const vk::GraphicsPipeline *pipeline, const vk::DynamicState &dynamicState, unsigned int count, int baseVertex,
 	          CountedEvent *events, int instanceID, int viewID, void *indexBuffer, const VkExtent3D &framebufferExtent,
-	          PushConstantStorage const &pushConstants, bool update = true);
-
-	// Viewport & Clipper
-	void setViewport(const VkViewport &viewport);
-	void setScissor(const VkRect2D &scissor);
-
-	void setBlendConstant(const float4 &blendConstant);
+	          vk::Pipeline::PushConstantStorage const &pushConstants, bool update = true);
 
 	void addQuery(vk::Query *query);
 	void removeQuery(vk::Query *query);
 
-	void advanceInstanceAttributes(Stream *inputs);
-
 	void synchronize();
 
 private:
-	VkViewport viewport;
-	VkRect2D scissor;
-
 	DrawCall::Pool drawCallPool;
 	DrawCall::BatchData::Pool batchDataPool;
 
diff --git a/src/Device/SetupProcessor.cpp b/src/Device/SetupProcessor.cpp
index b5ff120..de13fb4 100644
--- a/src/Device/SetupProcessor.cpp
+++ b/src/Device/SetupProcessor.cpp
@@ -14,7 +14,6 @@
 
 #include "SetupProcessor.hpp"
 
-#include "Context.hpp"
 #include "Polygon.hpp"
 #include "Primitive.hpp"
 #include "Renderer.hpp"
@@ -56,37 +55,37 @@
 	setRoutineCacheSize(1024);
 }
 
-SetupProcessor::State SetupProcessor::update(const sw::Context *context) const
+SetupProcessor::State SetupProcessor::update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *fragmentShader, const sw::SpirvShader *vertexShader, const vk::Attachments &attachments) const
 {
 	State state;
 
-	bool vPosZW = (context->pixelShader && context->pixelShader->hasBuiltinInput(spv::BuiltInFragCoord));
+	bool vPosZW = (fragmentShader && fragmentShader->hasBuiltinInput(spv::BuiltInFragCoord));
 
-	state.isDrawPoint = context->isDrawPoint(true);
-	state.isDrawLine = context->isDrawLine(true);
-	state.isDrawTriangle = context->isDrawTriangle(true);
-	state.fixedPointDepthBuffer = context->depthBuffer && !context->depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT).isFloatFormat();
-	state.applyConstantDepthBias = context->isDrawTriangle(false) && (context->constantDepthBias != 0.0f);
-	state.applySlopeDepthBias = context->isDrawTriangle(false) && (context->slopeDepthBias != 0.0f);
-	state.applyDepthBiasClamp = context->isDrawTriangle(false) && (context->depthBiasClamp != 0.0f);
-	state.interpolateZ = context->depthBufferActive() || vPosZW;
-	state.interpolateW = context->pixelShader != nullptr;
-	state.frontFace = context->frontFace;
-	state.cullMode = context->cullMode;
+	state.isDrawPoint = pipelineState.isDrawPoint(true);
+	state.isDrawLine = pipelineState.isDrawLine(true);
+	state.isDrawTriangle = pipelineState.isDrawTriangle(true);
+	state.fixedPointDepthBuffer = attachments.depthBuffer && !attachments.depthBuffer->getFormat(VK_IMAGE_ASPECT_DEPTH_BIT).isFloatFormat();
+	state.applyConstantDepthBias = pipelineState.isDrawTriangle(false) && (pipelineState.getConstantDepthBias() != 0.0f);
+	state.applySlopeDepthBias = pipelineState.isDrawTriangle(false) && (pipelineState.getSlopeDepthBias() != 0.0f);
+	state.applyDepthBiasClamp = pipelineState.isDrawTriangle(false) && (pipelineState.getDepthBiasClamp() != 0.0f);
+	state.interpolateZ = pipelineState.depthBufferActive(attachments) || vPosZW;
+	state.interpolateW = fragmentShader != nullptr;
+	state.frontFace = pipelineState.getFrontFace();
+	state.cullMode = pipelineState.getCullMode();
 
-	state.multiSampleCount = context->sampleCount;
+	state.multiSampleCount = pipelineState.getSampleCount();
 	state.enableMultiSampling = (state.multiSampleCount > 1) &&
-	                            !(context->isDrawLine(true) && (context->lineRasterizationMode == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT));
-	state.rasterizerDiscard = context->rasterizerDiscard;
+	                            !(pipelineState.isDrawLine(true) && (pipelineState.getLineRasterizationMode() == VK_LINE_RASTERIZATION_MODE_BRESENHAM_EXT));
+	state.rasterizerDiscard = pipelineState.hasRasterizerDiscard();
 
-	state.numClipDistances = context->vertexShader->getNumOutputClipDistances();
-	state.numCullDistances = context->vertexShader->getNumOutputCullDistances();
+	state.numClipDistances = vertexShader->getNumOutputClipDistances();
+	state.numCullDistances = vertexShader->getNumOutputCullDistances();
 
-	if(context->pixelShader)
+	if(fragmentShader)
 	{
 		for(int interpolant = 0; interpolant < MAX_INTERFACE_COMPONENTS; interpolant++)
 		{
-			state.gradient[interpolant] = context->pixelShader->inputs[interpolant];
+			state.gradient[interpolant] = fragmentShader->inputs[interpolant];
 		}
 	}
 
diff --git a/src/Device/SetupProcessor.hpp b/src/Device/SetupProcessor.hpp
index de3f886..8aeb216 100644
--- a/src/Device/SetupProcessor.hpp
+++ b/src/Device/SetupProcessor.hpp
@@ -76,7 +76,7 @@
 
 	SetupProcessor();
 
-	State update(const sw::Context *context) const;
+	State update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *fragmentShader, const sw::SpirvShader *vertexShader, const vk::Attachments &attachments) const;
 	RoutineType routine(const State &state);
 
 	void setRoutineCacheSize(int cacheSize);
diff --git a/src/Device/VertexProcessor.cpp b/src/Device/VertexProcessor.cpp
index c7c403c..1650639 100644
--- a/src/Device/VertexProcessor.cpp
+++ b/src/Device/VertexProcessor.cpp
@@ -65,21 +65,21 @@
 	routineCache = std::make_unique<RoutineCacheType>(clamp(cacheSize, 1, 65536));
 }
 
-const VertexProcessor::State VertexProcessor::update(const sw::Context *context)
+const VertexProcessor::State VertexProcessor::update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *vertexShader, const vk::Inputs &inputs)
 {
 	State state;
 
-	state.shaderID = context->vertexShader->getSerialID();
-	state.pipelineLayoutIdentifier = context->pipelineLayout->identifier;
-	state.robustBufferAccess = context->robustBufferAccess;
-	state.isPoint = context->topology == VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
+	state.shaderID = vertexShader->getSerialID();
+	state.pipelineLayoutIdentifier = pipelineState.getPipelineLayout()->identifier;
+	state.robustBufferAccess = pipelineState.getRobustBufferAccess();
+	state.isPoint = pipelineState.getTopology() == VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
 
-	for(int i = 0; i < MAX_INTERFACE_COMPONENTS / 4; i++)
+	for(size_t i = 0; i < MAX_INTERFACE_COMPONENTS / 4; i++)
 	{
-		state.input[i].format = context->input[i].format;
+		state.input[i].format = inputs.getStream(i).format;
 		// TODO: get rid of attribType -- just keep the VK format all the way through, this fully determines
 		// how to handle the attribute.
-		state.input[i].attribType = context->vertexShader->inputs[i * 4].Type;
+		state.input[i].attribType = vertexShader->inputs[i * 4].Type;
 	}
 
 	state.hash = state.computeHash();
diff --git a/src/Device/VertexProcessor.hpp b/src/Device/VertexProcessor.hpp
index c84dc70..aa1256e 100644
--- a/src/Device/VertexProcessor.hpp
+++ b/src/Device/VertexProcessor.hpp
@@ -94,7 +94,7 @@
 
 	VertexProcessor();
 
-	const State update(const sw::Context *context);
+	const State update(const vk::GraphicsState &pipelineState, const sw::SpirvShader *vertexShader, const vk::Inputs &inputs);
 	RoutineType routine(const State &state, vk::PipelineLayout const *pipelineLayout,
 	                    SpirvShader const *vertexShader, const vk::DescriptorSet::Bindings &descriptorSets);
 
diff --git a/src/Pipeline/ComputeProgram.cpp b/src/Pipeline/ComputeProgram.cpp
index 92dad1f..b1cdb77 100644
--- a/src/Pipeline/ComputeProgram.cpp
+++ b/src/Pipeline/ComputeProgram.cpp
@@ -209,7 +209,7 @@
     vk::DescriptorSet::Array const &descriptorSetObjects,
     vk::DescriptorSet::Bindings const &descriptorSets,
     vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
-    PushConstantStorage const &pushConstants,
+    vk::Pipeline::PushConstantStorage const &pushConstants,
     uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ,
     uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ)
 {
diff --git a/src/Pipeline/ComputeProgram.hpp b/src/Pipeline/ComputeProgram.hpp
index c188546..839f494 100644
--- a/src/Pipeline/ComputeProgram.hpp
+++ b/src/Pipeline/ComputeProgram.hpp
@@ -17,9 +17,9 @@
 
 #include "SpirvShader.hpp"
 
-#include "Device/Context.hpp"
 #include "Reactor/Coroutine.hpp"
 #include "Vulkan/VkDescriptorSet.hpp"
+#include "Vulkan/VkPipeline.hpp"
 
 #include <functional>
 
@@ -58,7 +58,7 @@
 	    vk::DescriptorSet::Array const &descriptorSetObjects,
 	    vk::DescriptorSet::Bindings const &descriptorSetBindings,
 	    vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
-	    PushConstantStorage const &pushConstants,
+	    vk::Pipeline::PushConstantStorage const &pushConstants,
 	    uint32_t baseGroupX, uint32_t baseGroupY, uint32_t baseGroupZ,
 	    uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ);
 
@@ -76,7 +76,7 @@
 		uint32_t invocationsPerSubgroup;   // SPIR-V: "SubgroupSize"
 		uint32_t subgroupsPerWorkgroup;    // SPIR-V: "NumSubgroups"
 		uint32_t invocationsPerWorkgroup;  // Total number of invocations per workgroup.
-		PushConstantStorage pushConstants;
+		vk::Pipeline::PushConstantStorage pushConstants;
 		const Constants *constants;
 	};
 
diff --git a/src/Vulkan/VkCommandBuffer.cpp b/src/Vulkan/VkCommandBuffer.cpp
index f178d35..030487e 100644
--- a/src/Vulkan/VkCommandBuffer.cpp
+++ b/src/Vulkan/VkCommandBuffer.cpp
@@ -466,56 +466,6 @@
 class CmdDrawBase : public vk::CommandBuffer::Command
 {
 public:
-	int bytesPerIndex(vk::CommandBuffer::ExecutionState const &executionState)
-	{
-		return executionState.indexType == VK_INDEX_TYPE_UINT16 ? 2 : 4;
-	}
-
-	template<typename T>
-	void processPrimitiveRestart(T *indexBuffer,
-	                             uint32_t count,
-	                             vk::GraphicsPipeline *pipeline,
-	                             std::vector<std::pair<uint32_t, void *>> &indexBuffers)
-	{
-		static const T RestartIndex = static_cast<T>(-1);
-		T *indexBufferStart = indexBuffer;
-		uint32_t vertexCount = 0;
-		for(uint32_t i = 0; i < count; i++)
-		{
-			if(indexBuffer[i] == RestartIndex)
-			{
-				// Record previous segment
-				if(vertexCount > 0)
-				{
-					uint32_t primitiveCount = pipeline->computePrimitiveCount(vertexCount);
-					if(primitiveCount > 0)
-					{
-						indexBuffers.push_back({ primitiveCount, indexBufferStart });
-					}
-				}
-				vertexCount = 0;
-			}
-			else
-			{
-				if(vertexCount == 0)
-				{
-					indexBufferStart = indexBuffer + i;
-				}
-				vertexCount++;
-			}
-		}
-
-		// Record last segment
-		if(vertexCount > 0)
-		{
-			uint32_t primitiveCount = pipeline->computePrimitiveCount(vertexCount);
-			if(primitiveCount > 0)
-			{
-				indexBuffers.push_back({ primitiveCount, indexBufferStart });
-			}
-		}
-	}
-
 	void draw(vk::CommandBuffer::ExecutionState &executionState, bool indexed,
 	          uint32_t count, uint32_t instanceCount, uint32_t first, int32_t vertexOffset, uint32_t firstInstance)
 	{
@@ -523,87 +473,21 @@
 
 		auto *pipeline = static_cast<vk::GraphicsPipeline *>(pipelineState.pipeline);
 
-		sw::Context context = pipeline->getContext();
+		vk::Attachments &attachments = pipeline->getAttachments();
+		executionState.bindAttachments(&attachments);
 
-		executionState.bindVertexInputs(context, firstInstance);
+		vk::Inputs &inputs = pipeline->getInputs();
+		inputs.updateDescriptorSets(pipelineState.descriptorSetObjects,
+		                            pipelineState.descriptorSets,
+		                            pipelineState.descriptorDynamicOffsets);
+		inputs.setVertexInputBinding(executionState.vertexInputBindings);
+		inputs.bindVertexInputs(firstInstance);
 
-		context.descriptorSetObjects = pipelineState.descriptorSetObjects;
-		context.descriptorSets = pipelineState.descriptorSets;
-		context.descriptorDynamicOffsets = pipelineState.descriptorDynamicOffsets;
-
-		// Apply either pipeline state or dynamic state
-		executionState.renderer->setScissor(pipeline->hasDynamicState(VK_DYNAMIC_STATE_SCISSOR) ? executionState.dynamicState.scissor : pipeline->getScissor());
-		executionState.renderer->setViewport(pipeline->hasDynamicState(VK_DYNAMIC_STATE_VIEWPORT) ? 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))
-		{
-			context.constantDepthBias = executionState.dynamicState.depthBiasConstantFactor;
-			context.slopeDepthBias = executionState.dynamicState.depthBiasSlopeFactor;
-			context.depthBiasClamp = executionState.dynamicState.depthBiasClamp;
-		}
-
-		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);
-
-			UNSUPPORTED("VkPhysicalDeviceFeatures::depthBounds");
-		}
-
-		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)
-		{
-			context.frontStencil.writeMask = executionState.dynamicState.writeMask[0];
-			context.backStencil.writeMask = executionState.dynamicState.writeMask[1];
-		}
-
-		if(pipeline->hasDynamicState(VK_DYNAMIC_STATE_STENCIL_REFERENCE) && context.stencilEnable)
-		{
-			context.frontStencil.reference = executionState.dynamicState.reference[0];
-			context.backStencil.reference = executionState.dynamicState.reference[1];
-		}
-
-		executionState.bindAttachments(context);
-
-		context.occlusionEnabled = executionState.renderer->hasOcclusionQuery();
+		vk::IndexBuffer &indexBuffer = pipeline->getIndexBuffer();
+		indexBuffer.setIndexBufferBinding(executionState.indexBufferBinding, executionState.indexType);
 
 		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())
-			{
-				switch(executionState.indexType)
-				{
-					case VK_INDEX_TYPE_UINT16:
-						processPrimitiveRestart(static_cast<uint16_t *>(indexBuffer), count, pipeline, indexBuffers);
-						break;
-					case VK_INDEX_TYPE_UINT32:
-						processPrimitiveRestart(static_cast<uint32_t *>(indexBuffer), count, pipeline, indexBuffers);
-						break;
-					default:
-						UNSUPPORTED("VkIndexType %d", int(executionState.indexType));
-				}
-			}
-			else
-			{
-				indexBuffers.push_back({ pipeline->computePrimitiveCount(count), indexBuffer });
-			}
-		}
-		else
-		{
-			indexBuffers.push_back({ pipeline->computePrimitiveCount(count), nullptr });
-		}
+		pipeline->getIndexBuffers(count, first, indexed, &indexBuffers);
 
 		for(uint32_t instance = firstInstance; instance != firstInstance + instanceCount; instance++)
 		{
@@ -616,14 +500,14 @@
 
 				for(auto indexBuffer : indexBuffers)
 				{
-					executionState.renderer->draw(&context, executionState.indexType, indexBuffer.first, vertexOffset,
+					executionState.renderer->draw(pipeline, executionState.dynamicState, indexBuffer.first, vertexOffset,
 					                              executionState.events, instance, viewID, indexBuffer.second,
 					                              executionState.renderPassFramebuffer->getExtent(),
 					                              executionState.pushConstants);
 				}
 			}
 
-			executionState.renderer->advanceInstanceAttributes(context.input);
+			inputs.advanceInstanceAttributes();
 		}
 	}
 };
@@ -1840,25 +1724,7 @@
 	}
 }
 
-void CommandBuffer::ExecutionState::bindVertexInputs(sw::Context &context, int firstInstance)
-{
-	for(uint32_t i = 0; i < MAX_VERTEX_INPUT_BINDINGS; i++)
-	{
-		auto &attrib = context.input[i];
-		if(attrib.format != VK_FORMAT_UNDEFINED)
-		{
-			const auto &vertexInput = vertexInputBindings[attrib.binding];
-			VkDeviceSize offset = attrib.offset + vertexInput.offset +
-			                      attrib.instanceStride * firstInstance;
-			attrib.buffer = vertexInput.buffer ? vertexInput.buffer->getOffsetPointer(offset) : nullptr;
-
-			VkDeviceSize size = vertexInput.buffer ? vertexInput.buffer->getSize() : 0;
-			attrib.robustnessSize = (size > offset) ? size - offset : 0;
-		}
-	}
-}
-
-void CommandBuffer::ExecutionState::bindAttachments(sw::Context &context)
+void CommandBuffer::ExecutionState::bindAttachments(Attachments *attachments)
 {
 	// Binds all the attachments for the current subpass
 	// Ideally this would be performed by BeginRenderPass and NextSubpass, but
@@ -1872,7 +1738,7 @@
 		auto attachmentReference = subpass.pColorAttachments[i];
 		if(attachmentReference.attachment != VK_ATTACHMENT_UNUSED)
 		{
-			context.renderTarget[i] = renderPassFramebuffer->getAttachment(attachmentReference.attachment);
+			attachments->renderTarget[i] = renderPassFramebuffer->getAttachment(attachmentReference.attachment);
 		}
 	}
 
@@ -1882,11 +1748,11 @@
 		auto attachment = renderPassFramebuffer->getAttachment(attachmentReference->attachment);
 		if(attachment->hasDepthAspect())
 		{
-			context.depthBuffer = attachment;
+			attachments->depthBuffer = attachment;
 		}
 		if(attachment->hasStencilAspect())
 		{
-			context.stencilBuffer = attachment;
+			attachments->stencilBuffer = attachment;
 		}
 	}
 }
diff --git a/src/Vulkan/VkCommandBuffer.hpp b/src/Vulkan/VkCommandBuffer.hpp
index e08f3ec..5671150 100644
--- a/src/Vulkan/VkCommandBuffer.hpp
+++ b/src/Vulkan/VkCommandBuffer.hpp
@@ -17,8 +17,7 @@
 
 #include "VkConfig.hpp"
 #include "VkDescriptorSet.hpp"
-#include "VkObject.hpp"
-#include "Device/Context.hpp"
+#include "VkPipeline.hpp"
 #include "System/Synchronization.hpp"
 
 #include <memory>
@@ -155,38 +154,17 @@
 		Framebuffer *renderPassFramebuffer = nullptr;
 		std::array<PipelineState, vk::VK_PIPELINE_BIND_POINT_RANGE_SIZE> pipelineState;
 
-		struct DynamicState
-		{
-			VkViewport viewport;
-			VkRect2D scissor;
-			sw::float4 blendConstants;
-			float depthBiasConstantFactor = 0.0f;
-			float depthBiasClamp = 0.0f;
-			float depthBiasSlopeFactor = 0.0f;
-			float minDepthBounds = 0.0f;
-			float maxDepthBounds = 0.0f;
+		vk::DynamicState dynamicState;
 
-			uint32_t compareMask[2] = { 0 };
-			uint32_t writeMask[2] = { 0 };
-			uint32_t reference[2] = { 0 };
-		};
-		DynamicState dynamicState;
+		vk::Pipeline::PushConstantStorage pushConstants;
 
-		sw::PushConstantStorage pushConstants;
-
-		struct VertexInputBinding
-		{
-			Buffer *buffer;
-			VkDeviceSize offset;
-		};
 		VertexInputBinding vertexInputBindings[MAX_VERTEX_INPUT_BINDINGS] = {};
 		VertexInputBinding indexBufferBinding;
 		VkIndexType indexType;
 
 		uint32_t subpassIndex = 0;
 
-		void bindAttachments(sw::Context &context);
-		void bindVertexInputs(sw::Context &context, int firstInstance);
+		void bindAttachments(Attachments *attachments);
 	};
 
 	void submit(CommandBuffer::ExecutionState &executionState);
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 81d9019..a7a6611 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -147,305 +147,9 @@
 
 GraphicsPipeline::GraphicsPipeline(const VkGraphicsPipelineCreateInfo *pCreateInfo, void *mem, Device *device)
     : Pipeline(vk::Cast(pCreateInfo->layout), device)
+    , state(device, pCreateInfo, layout, robustBufferAccess)
+    , inputs(pCreateInfo->pVertexInputState)
 {
-	context.robustBufferAccess = robustBufferAccess;
-
-	if((pCreateInfo->flags &
-	    ~(VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT |
-	      VK_PIPELINE_CREATE_DERIVATIVE_BIT |
-	      VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT)) != 0)
-	{
-		UNSUPPORTED("pCreateInfo->flags %d", int(pCreateInfo->flags));
-	}
-
-	if(pCreateInfo->pDynamicState)
-	{
-		if(pCreateInfo->pDynamicState->flags != 0)
-		{
-			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-			UNSUPPORTED("pCreateInfo->pDynamicState->flags %d", int(pCreateInfo->pDynamicState->flags));
-		}
-
-		for(uint32_t i = 0; i < pCreateInfo->pDynamicState->dynamicStateCount; i++)
-		{
-			VkDynamicState dynamicState = pCreateInfo->pDynamicState->pDynamicStates[i];
-			switch(dynamicState)
-			{
-				case VK_DYNAMIC_STATE_VIEWPORT:
-				case VK_DYNAMIC_STATE_SCISSOR:
-				case VK_DYNAMIC_STATE_LINE_WIDTH:
-				case VK_DYNAMIC_STATE_DEPTH_BIAS:
-				case VK_DYNAMIC_STATE_BLEND_CONSTANTS:
-				case VK_DYNAMIC_STATE_DEPTH_BOUNDS:
-				case VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK:
-				case VK_DYNAMIC_STATE_STENCIL_WRITE_MASK:
-				case VK_DYNAMIC_STATE_STENCIL_REFERENCE:
-					ASSERT(dynamicState < (sizeof(dynamicStateFlags) * 8));
-					dynamicStateFlags |= (1 << dynamicState);
-					break;
-				default:
-					UNSUPPORTED("VkDynamicState %d", int(dynamicState));
-			}
-		}
-	}
-
-	const VkPipelineVertexInputStateCreateInfo *vertexInputState = pCreateInfo->pVertexInputState;
-
-	if(vertexInputState->flags != 0)
-	{
-		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-		UNSUPPORTED("vertexInputState->flags");
-	}
-
-	// Context must always have a PipelineLayout set.
-	context.pipelineLayout = layout;
-
-	// Temporary in-binding-order representation of buffer strides, to be consumed below
-	// when considering attributes. TODO: unfuse buffers from attributes in backend, is old GL model.
-	uint32_t vertexStrides[MAX_VERTEX_INPUT_BINDINGS];
-	uint32_t instanceStrides[MAX_VERTEX_INPUT_BINDINGS];
-	for(uint32_t i = 0; i < vertexInputState->vertexBindingDescriptionCount; i++)
-	{
-		auto const &desc = vertexInputState->pVertexBindingDescriptions[i];
-		vertexStrides[desc.binding] = desc.inputRate == VK_VERTEX_INPUT_RATE_VERTEX ? desc.stride : 0;
-		instanceStrides[desc.binding] = desc.inputRate == VK_VERTEX_INPUT_RATE_INSTANCE ? desc.stride : 0;
-	}
-
-	for(uint32_t i = 0; i < vertexInputState->vertexAttributeDescriptionCount; i++)
-	{
-		auto const &desc = vertexInputState->pVertexAttributeDescriptions[i];
-		sw::Stream &input = context.input[desc.location];
-		input.format = desc.format;
-		input.offset = desc.offset;
-		input.binding = desc.binding;
-		input.vertexStride = vertexStrides[desc.binding];
-		input.instanceStride = instanceStrides[desc.binding];
-	}
-
-	const VkPipelineInputAssemblyStateCreateInfo *inputAssemblyState = pCreateInfo->pInputAssemblyState;
-
-	if(inputAssemblyState->flags != 0)
-	{
-		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-		UNSUPPORTED("pCreateInfo->pInputAssemblyState->flags %d", int(pCreateInfo->pInputAssemblyState->flags));
-	}
-
-	primitiveRestartEnable = (inputAssemblyState->primitiveRestartEnable != VK_FALSE);
-	context.topology = inputAssemblyState->topology;
-
-	const VkPipelineRasterizationStateCreateInfo *rasterizationState = pCreateInfo->pRasterizationState;
-
-	if(rasterizationState->flags != 0)
-	{
-		// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-		UNSUPPORTED("pCreateInfo->pRasterizationState->flags %d", int(pCreateInfo->pRasterizationState->flags));
-	}
-
-	if(rasterizationState->depthClampEnable != VK_FALSE)
-	{
-		UNSUPPORTED("VkPhysicalDeviceFeatures::depthClamp");
-	}
-
-	context.rasterizerDiscard = (rasterizationState->rasterizerDiscardEnable != VK_FALSE);
-	context.cullMode = rasterizationState->cullMode;
-	context.frontFace = rasterizationState->frontFace;
-	context.polygonMode = rasterizationState->polygonMode;
-	context.constantDepthBias = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasConstantFactor : 0.0f;
-	context.slopeDepthBias = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasSlopeFactor : 0.0f;
-	context.depthBiasClamp = (rasterizationState->depthBiasEnable != VK_FALSE) ? rasterizationState->depthBiasClamp : 0.0f;
-	context.depthRangeUnrestricted = device->hasExtension(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME);
-
-	// From the Vulkan spec for vkCmdSetDepthBias:
-	//    The bias value O for a polygon is:
-	//        O = dbclamp(...)
-	//    where dbclamp(x) =
-	//        * x                       depthBiasClamp = 0 or NaN
-	//        * min(x, depthBiasClamp)  depthBiasClamp > 0
-	//        * max(x, depthBiasClamp)  depthBiasClamp < 0
-	// So it should be safe to resolve NaNs to 0.0f.
-	if(std::isnan(context.depthBiasClamp))
-	{
-		context.depthBiasClamp = 0.0f;
-	}
-
-	context.lineWidth = rasterizationState->lineWidth;
-
-	const VkBaseInStructure *extensionCreateInfo = reinterpret_cast<const VkBaseInStructure *>(rasterizationState->pNext);
-	while(extensionCreateInfo)
-	{
-		// Casting to a long since some structures, such as
-		// VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROVOKING_VERTEX_FEATURES_EXT
-		// are not enumerated in the official Vulkan header
-		switch((long)(extensionCreateInfo->sType))
-		{
-			case VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_LINE_STATE_CREATE_INFO_EXT:
-			{
-				const VkPipelineRasterizationLineStateCreateInfoEXT *lineStateCreateInfo = reinterpret_cast<const VkPipelineRasterizationLineStateCreateInfoEXT *>(extensionCreateInfo);
-				context.lineRasterizationMode = lineStateCreateInfo->lineRasterizationMode;
-			}
-			break;
-			case VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_PROVOKING_VERTEX_STATE_CREATE_INFO_EXT:
-			{
-				const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT *provokingVertexModeCreateInfo =
-				    reinterpret_cast<const VkPipelineRasterizationProvokingVertexStateCreateInfoEXT *>(extensionCreateInfo);
-				context.provokingVertexMode = provokingVertexModeCreateInfo->provokingVertexMode;
-			}
-			break;
-			default:
-				WARN("pCreateInfo->pRasterizationState->pNext sType = %s", vk::Stringify(extensionCreateInfo->sType).c_str());
-				break;
-		}
-
-		extensionCreateInfo = extensionCreateInfo->pNext;
-	}
-
-	// The sample count affects the batch size, so it needs initialization even if rasterization is disabled.
-	// TODO(b/147812380): Eliminate the dependency between multisampling and batch size.
-	context.sampleCount = 1;
-
-	// Only access rasterization state if rasterization is not disabled.
-	if(rasterizationState->rasterizerDiscardEnable == VK_FALSE)
-	{
-		const VkPipelineViewportStateCreateInfo *viewportState = pCreateInfo->pViewportState;
-		const VkPipelineMultisampleStateCreateInfo *multisampleState = pCreateInfo->pMultisampleState;
-		const VkPipelineDepthStencilStateCreateInfo *depthStencilState = pCreateInfo->pDepthStencilState;
-		const VkPipelineColorBlendStateCreateInfo *colorBlendState = pCreateInfo->pColorBlendState;
-
-		if(viewportState->flags != 0)
-		{
-			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-			UNSUPPORTED("pCreateInfo->pViewportState->flags %d", int(pCreateInfo->pViewportState->flags));
-		}
-
-		if((viewportState->viewportCount != 1) ||
-		   (viewportState->scissorCount != 1))
-		{
-			UNSUPPORTED("VkPhysicalDeviceFeatures::multiViewport");
-		}
-
-		if(!hasDynamicState(VK_DYNAMIC_STATE_SCISSOR))
-		{
-			scissor = viewportState->pScissors[0];
-		}
-
-		if(!hasDynamicState(VK_DYNAMIC_STATE_VIEWPORT))
-		{
-			viewport = viewportState->pViewports[0];
-		}
-
-		if(multisampleState->flags != 0)
-		{
-			// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-			UNSUPPORTED("pCreateInfo->pMultisampleState->flags %d", int(pCreateInfo->pMultisampleState->flags));
-		}
-
-		if(multisampleState->sampleShadingEnable != VK_FALSE)
-		{
-			UNSUPPORTED("VkPhysicalDeviceFeatures::sampleRateShading");
-		}
-
-		if(multisampleState->alphaToOneEnable != VK_FALSE)
-		{
-			UNSUPPORTED("VkPhysicalDeviceFeatures::alphaToOne");
-		}
-
-		switch(multisampleState->rasterizationSamples)
-		{
-			case VK_SAMPLE_COUNT_1_BIT:
-				context.sampleCount = 1;
-				break;
-			case VK_SAMPLE_COUNT_4_BIT:
-				context.sampleCount = 4;
-				break;
-			default:
-				UNSUPPORTED("Unsupported sample count");
-		}
-
-		if(multisampleState->pSampleMask)
-		{
-			context.sampleMask = multisampleState->pSampleMask[0];
-		}
-		else  // "If pSampleMask is NULL, it is treated as if the mask has all bits set to 1."
-		{
-			context.sampleMask = ~0;
-		}
-
-		context.alphaToCoverage = (multisampleState->alphaToCoverageEnable != VK_FALSE);
-		context.multiSampleMask = context.sampleMask & ((unsigned)0xFFFFFFFF >> (32 - context.sampleCount));
-
-		const vk::RenderPass *renderPass = vk::Cast(pCreateInfo->renderPass);
-		const VkSubpassDescription &subpass = renderPass->getSubpass(pCreateInfo->subpass);
-
-		//  Ignore pDepthStencilState when "the subpass of the render pass the pipeline is created against does not use a depth/stencil attachment"
-		if(subpass.pDepthStencilAttachment && subpass.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED)
-		{
-			if(depthStencilState->flags != 0)
-			{
-				// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-				UNSUPPORTED("pCreateInfo->pDepthStencilState->flags %d", int(pCreateInfo->pDepthStencilState->flags));
-			}
-
-			if(depthStencilState->depthBoundsTestEnable != VK_FALSE)
-			{
-				UNSUPPORTED("VkPhysicalDeviceFeatures::depthBounds");
-			}
-
-			context.depthBoundsTestEnable = (depthStencilState->depthBoundsTestEnable != VK_FALSE);
-			context.depthBufferEnable = (depthStencilState->depthTestEnable != VK_FALSE);
-			context.depthWriteEnable = (depthStencilState->depthWriteEnable != VK_FALSE);
-			context.depthCompareMode = depthStencilState->depthCompareOp;
-
-			context.stencilEnable = (depthStencilState->stencilTestEnable != VK_FALSE);
-			if(context.stencilEnable)
-			{
-				context.frontStencil = depthStencilState->front;
-				context.backStencil = depthStencilState->back;
-			}
-		}
-
-		bool colorAttachmentUsed = false;
-		for(uint32_t i = 0; i < subpass.colorAttachmentCount; i++)
-		{
-			if(subpass.pColorAttachments[i].attachment != VK_ATTACHMENT_UNUSED)
-			{
-				colorAttachmentUsed = true;
-				break;
-			}
-		}
-
-		// Ignore pColorBlendState when "the subpass of the render pass the pipeline is created against does not use any color attachments"
-		if(colorAttachmentUsed)
-		{
-			if(colorBlendState->flags != 0)
-			{
-				// Vulkan 1.2: "flags is reserved for future use." "flags must be 0"
-				UNSUPPORTED("pCreateInfo->pColorBlendState->flags %d", int(pCreateInfo->pColorBlendState->flags));
-			}
-
-			if(colorBlendState->logicOpEnable != VK_FALSE)
-			{
-				UNSUPPORTED("VkPhysicalDeviceFeatures::logicOp");
-			}
-
-			if(!hasDynamicState(VK_DYNAMIC_STATE_BLEND_CONSTANTS))
-			{
-				blendConstants.x = colorBlendState->blendConstants[0];
-				blendConstants.y = colorBlendState->blendConstants[1];
-				blendConstants.z = colorBlendState->blendConstants[2];
-				blendConstants.w = colorBlendState->blendConstants[3];
-			}
-
-			for(auto i = 0u; i < colorBlendState->attachmentCount; i++)
-			{
-				const VkPipelineColorBlendAttachmentState &attachment = colorBlendState->pAttachments[i];
-				context.colorWriteMask[i] = attachment.colorWriteMask;
-
-				context.setBlendState(i, { (attachment.blendEnable != VK_FALSE),
-				                           attachment.srcColorBlendFactor, attachment.dstColorBlendFactor, attachment.colorBlendOp,
-				                           attachment.srcAlphaBlendFactor, attachment.dstAlphaBlendFactor, attachment.alphaBlendOp });
-			}
-		}
-	}
 }
 
 void GraphicsPipeline::destroyPipeline(const VkAllocationCallbacks *pAllocator)
@@ -459,6 +163,17 @@
 	return 0;
 }
 
+void GraphicsPipeline::getIndexBuffers(uint32_t count, uint32_t first, bool indexed, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const
+{
+	indexBuffer.getIndexBuffers(state.getTopology(), count, first, indexed, state.hasPrimitiveRestartEnable(), indexBuffers);
+}
+
+bool GraphicsPipeline::containsImageWrite() const
+{
+	return (vertexShader.get() && vertexShader->containsImageWrite()) ||
+	       (fragmentShader.get() && fragmentShader->containsImageWrite());
+}
+
 void GraphicsPipeline::setShader(const VkShaderStageFlagBits &stage, const std::shared_ptr<sw::SpirvShader> spirvShader)
 {
 	switch(stage)
@@ -466,13 +181,11 @@
 		case VK_SHADER_STAGE_VERTEX_BIT:
 			ASSERT(vertexShader.get() == nullptr);
 			vertexShader = spirvShader;
-			context.vertexShader = vertexShader.get();
 			break;
 
 		case VK_SHADER_STAGE_FRAGMENT_BIT:
 			ASSERT(fragmentShader.get() == nullptr);
 			fragmentShader = spirvShader;
-			context.pixelShader = fragmentShader.get();
 			break;
 
 		default:
@@ -526,54 +239,6 @@
 	}
 }
 
-uint32_t GraphicsPipeline::computePrimitiveCount(uint32_t vertexCount) const
-{
-	switch(context.topology)
-	{
-		case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
-			return vertexCount;
-		case VK_PRIMITIVE_TOPOLOGY_LINE_LIST:
-			return vertexCount / 2;
-		case VK_PRIMITIVE_TOPOLOGY_LINE_STRIP:
-			return std::max<uint32_t>(vertexCount, 1) - 1;
-		case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST:
-			return vertexCount / 3;
-		case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP:
-			return std::max<uint32_t>(vertexCount, 2) - 2;
-		case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
-			return std::max<uint32_t>(vertexCount, 2) - 2;
-		default:
-			UNSUPPORTED("VkPrimitiveTopology %d", int(context.topology));
-	}
-
-	return 0;
-}
-
-const sw::Context &GraphicsPipeline::getContext() const
-{
-	return context;
-}
-
-const VkRect2D &GraphicsPipeline::getScissor() const
-{
-	return scissor;
-}
-
-const VkViewport &GraphicsPipeline::getViewport() const
-{
-	return viewport;
-}
-
-const sw::float4 &GraphicsPipeline::getBlendConstants() const
-{
-	return blendConstants;
-}
-
-bool GraphicsPipeline::hasDynamicState(VkDynamicState dynamicState) const
-{
-	return (dynamicStateFlags & (1 << dynamicState)) != 0;
-}
-
 ComputePipeline::ComputePipeline(const VkComputePipelineCreateInfo *pCreateInfo, void *mem, Device *device)
     : Pipeline(vk::Cast(pCreateInfo->layout), device)
 {
@@ -624,7 +289,7 @@
                           vk::DescriptorSet::Array const &descriptorSetObjects,
                           vk::DescriptorSet::Bindings const &descriptorSets,
                           vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
-                          sw::PushConstantStorage const &pushConstants)
+                          vk::Pipeline::PushConstantStorage const &pushConstants)
 {
 	ASSERT_OR_RETURN(program != nullptr);
 	program->run(
diff --git a/src/Vulkan/VkPipeline.hpp b/src/Vulkan/VkPipeline.hpp
index 401454f..2b13fcd 100644
--- a/src/Vulkan/VkPipeline.hpp
+++ b/src/Vulkan/VkPipeline.hpp
@@ -15,9 +15,7 @@
 #ifndef VK_PIPELINE_HPP_
 #define VK_PIPELINE_HPP_
 
-#include "VkObject.hpp"
-#include "Device/Renderer.hpp"
-#include "Vulkan/VkDescriptorSet.hpp"
+#include "Device/Context.hpp"
 #include "Vulkan/VkPipelineCache.hpp"
 #include <memory>
 
@@ -34,10 +32,7 @@
 class Context;
 }  // namespace dbg
 
-class PipelineCache;
-class PipelineLayout;
 class ShaderModule;
-class Device;
 
 class Pipeline
 {
@@ -67,6 +62,11 @@
 		return layout;
 	}
 
+	struct PushConstantStorage
+	{
+		unsigned char data[vk::MAX_PUSH_CONSTANT_SIZE];
+	};
+
 protected:
 	PipelineLayout *layout = nullptr;
 	Device *const device;
@@ -95,26 +95,31 @@
 
 	void compileShaders(const VkAllocationCallbacks *pAllocator, const VkGraphicsPipelineCreateInfo *pCreateInfo, PipelineCache *pipelineCache);
 
-	uint32_t computePrimitiveCount(uint32_t vertexCount) const;
-	const sw::Context &getContext() const;
-	const VkRect2D &getScissor() const;
-	const VkViewport &getViewport() const;
-	const sw::float4 &getBlendConstants() const;
-	bool hasDynamicState(VkDynamicState dynamicState) const;
-	bool hasPrimitiveRestartEnable() const { return primitiveRestartEnable; }
+	const GraphicsState getState(const DynamicState &ds) const { return state.combineStates(ds); }
+
+	void getIndexBuffers(uint32_t count, uint32_t first, bool indexed, std::vector<std::pair<uint32_t, void *>> *indexBuffers) const;
+
+	IndexBuffer &getIndexBuffer() { return indexBuffer; }
+	const IndexBuffer &getIndexBuffer() const { return indexBuffer; }
+	Attachments &getAttachments() { return attachments; }
+	const Attachments &getAttachments() const { return attachments; }
+	Inputs &getInputs() { return inputs; }
+	const Inputs &getInputs() const { return inputs; }
+
+	bool containsImageWrite() const;
+
+	const std::shared_ptr<sw::SpirvShader> getShader(const VkShaderStageFlagBits &stage) const;
 
 private:
 	void setShader(const VkShaderStageFlagBits &stage, const std::shared_ptr<sw::SpirvShader> spirvShader);
-	const std::shared_ptr<sw::SpirvShader> getShader(const VkShaderStageFlagBits &stage) const;
 	std::shared_ptr<sw::SpirvShader> vertexShader;
 	std::shared_ptr<sw::SpirvShader> fragmentShader;
 
-	uint32_t dynamicStateFlags = 0;
-	bool primitiveRestartEnable = false;
-	sw::Context context;
-	VkRect2D scissor;
-	VkViewport viewport;
-	sw::float4 blendConstants;
+	const GraphicsState state;
+
+	IndexBuffer indexBuffer;
+	Attachments attachments;
+	Inputs inputs;
 };
 
 class ComputePipeline : public Pipeline, public ObjectBase<ComputePipeline, VkPipeline>
@@ -141,7 +146,7 @@
 	         vk::DescriptorSet::Array const &descriptorSetObjects,
 	         vk::DescriptorSet::Bindings const &descriptorSets,
 	         vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
-	         sw::PushConstantStorage const &pushConstants);
+	         vk::Pipeline::PushConstantStorage const &pushConstants);
 
 protected:
 	std::shared_ptr<sw::SpirvShader> shader;