Implement dynamic buffer offsets.

Tests: *dynamic*
Bug: b/126330097
Change-Id: I7e4f7e3d921acb72878b7728216415ba66f63ec7
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/28249
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Tested-by: Ben Clayton <bclayton@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Device/Context.cpp b/src/Device/Context.cpp
index c341409..f3721ef 100644
--- a/src/Device/Context.cpp
+++ b/src/Device/Context.cpp
@@ -112,11 +112,6 @@
 
 	void Context::init()
 	{
-		for(int i = 0; i < vk::MAX_BOUND_DESCRIPTOR_SETS; i++)
-		{
-			descriptorSets[i] = nullptr;
-		}
-
 		// Set vertex streams to null stream
 		for(int i = 0; i < MAX_VERTEX_INPUTS; i++)
 		{
diff --git a/src/Device/Context.hpp b/src/Device/Context.hpp
index ac63a89..581871f 100644
--- a/src/Device/Context.hpp
+++ b/src/Device/Context.hpp
@@ -16,6 +16,7 @@
 #define sw_Context_hpp
 
 #include "Vulkan/VkConfig.h"
+#include "Vulkan/VkDescriptorSet.hpp"
 #include "Sampler.hpp"
 #include "Stream.hpp"
 #include "Point.hpp"
@@ -153,7 +154,8 @@
 		int colorWriteActive(int index);
 		bool colorUsed();
 
-		vk::DescriptorSet *descriptorSets[vk::MAX_BOUND_DESCRIPTOR_SETS];
+		vk::DescriptorSet::Bindings descriptorSets = {};
+		vk::DescriptorSet::DynamicOffsets descriptorDynamicOffsets = {};
 		Stream input[MAX_VERTEX_INPUTS];
 		void *indexBuffer;
 
diff --git a/src/Device/QuadRasterizer.hpp b/src/Device/QuadRasterizer.hpp
index a41464c..456887d 100644
--- a/src/Device/QuadRasterizer.hpp
+++ b/src/Device/QuadRasterizer.hpp
@@ -28,7 +28,7 @@
 		QuadRasterizer(const PixelProcessor::State &state, SpirvShader const *spirvShader);
 		virtual ~QuadRasterizer();
 
-		virtual void generate();
+		void generate();
 
 	protected:
 		Pointer<Byte> constants;
diff --git a/src/Device/Renderer.cpp b/src/Device/Renderer.cpp
index 4a77700..f70f519 100644
--- a/src/Device/Renderer.cpp
+++ b/src/Device/Renderer.cpp
@@ -388,10 +388,8 @@
 		draw->setupPrimitives = setupPrimitives;
 		draw->setupState = setupState;
 
-		for(int i = 0; i < vk::MAX_BOUND_DESCRIPTOR_SETS; i++)
-		{
-			data->descriptorSets[i] = context->descriptorSets[i];
-		}
+		data->descriptorSets = context->descriptorSets;
+		data->descriptorDynamicOffsets = context->descriptorDynamicOffsets;
 
 		for(int i = 0; i < MAX_VERTEX_INPUTS; i++)
 		{
diff --git a/src/Device/Renderer.hpp b/src/Device/Renderer.hpp
index 63a5b55..2f1fa61 100644
--- a/src/Device/Renderer.hpp
+++ b/src/Device/Renderer.hpp
@@ -23,6 +23,7 @@
 #include "System/MutexLock.hpp"
 #include "System/Thread.hpp"
 #include "Device/Config.hpp"
+#include "Vulkan/VkDescriptorSet.hpp"
 
 #include <list>
 
@@ -114,7 +115,8 @@
 	{
 		const Constants *constants;
 
-		vk::DescriptorSet *descriptorSets[vk::MAX_BOUND_DESCRIPTOR_SETS];
+		vk::DescriptorSet::Bindings descriptorSets = {};
+		vk::DescriptorSet::DynamicOffsets descriptorDynamicOffsets = {};
 
 		const void *input[MAX_VERTEX_INPUTS];
 		unsigned int stride[MAX_VERTEX_INPUTS];
diff --git a/src/Pipeline/ComputeProgram.cpp b/src/Pipeline/ComputeProgram.cpp
index 6672ece..293655a 100644
--- a/src/Pipeline/ComputeProgram.cpp
+++ b/src/Pipeline/ComputeProgram.cpp
@@ -45,14 +45,9 @@
 
 	void ComputeProgram::emit()
 	{
-		Pointer<Pointer<Byte>> descriptorSetsIn = *Pointer<Pointer<Pointer<Byte>>>(data + OFFSET(Data, descriptorSets));
-		size_t numDescriptorSets = routine.pipelineLayout->getNumDescriptorSets();
-		for(unsigned int i = 0; i < numDescriptorSets; i++)
-		{
-			routine.descriptorSets[i] = descriptorSetsIn[i];
-		}
-
-		routine.pushConstants = Pointer<Byte>(data + OFFSET(Data, pushConstants));
+		routine.descriptorSets = data + OFFSET(Data, descriptorSets);
+		routine.descriptorDynamicOffsets = data + OFFSET(Data, descriptorDynamicOffsets);
+		routine.pushConstants = data + OFFSET(Data, pushConstants);
 
 		auto &modes = shader->getModes();
 
@@ -178,13 +173,17 @@
 	}
 
 	void ComputeProgram::run(
-		Routine *routine, void** descriptorSets, PushConstantStorage const &pushConstants,
+		Routine *routine,
+		vk::DescriptorSet::Bindings const &descriptorSets,
+		vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
+		PushConstantStorage const &pushConstants,
 		uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ)
 	{
 		auto runWorkgroup = (void(*)(void*))(routine->getEntry());
 
 		Data data;
 		data.descriptorSets = descriptorSets;
+		data.descriptorDynamicOffsets = descriptorDynamicOffsets;
 		data.numWorkgroups[X] = groupCountX;
 		data.numWorkgroups[Y] = groupCountY;
 		data.numWorkgroups[Z] = groupCountZ;
diff --git a/src/Pipeline/ComputeProgram.hpp b/src/Pipeline/ComputeProgram.hpp
index 6b63233..e8265bc0 100644
--- a/src/Pipeline/ComputeProgram.hpp
+++ b/src/Pipeline/ComputeProgram.hpp
@@ -19,6 +19,7 @@
 
 #include "Reactor/Reactor.hpp"
 #include "Device/Context.hpp"
+#include "Vulkan/VkDescriptorSet.hpp"
 
 #include <functional>
 
@@ -48,7 +49,10 @@
 		// run executes the compute shader routine for all workgroups.
 		// TODO(bclayton): This probably does not belong here. Consider moving.
 		static void run(
-			Routine *routine, void** descriptorSets, PushConstantStorage const &pushConstants,
+			Routine *routine,
+			vk::DescriptorSet::Bindings const &descriptorSetBindings,
+			vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
+			PushConstantStorage const &pushConstants,
 			uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ);
 
 	protected:
@@ -60,7 +64,8 @@
 
 		struct Data
 		{
-			void** descriptorSets;
+			vk::DescriptorSet::Bindings descriptorSets;
+			vk::DescriptorSet::DynamicOffsets descriptorDynamicOffsets;
 			uint4 numWorkgroups;
 			uint4 workgroupID;
 			PushConstantStorage pushConstants;
diff --git a/src/Pipeline/PixelProgram.cpp b/src/Pipeline/PixelProgram.cpp
index 8d0424e..c64c7ae 100644
--- a/src/Pipeline/PixelProgram.cpp
+++ b/src/Pipeline/PixelProgram.cpp
@@ -31,6 +31,8 @@
 	{
 		enableIndex = 0;
 
+		routine.descriptorSets = data + OFFSET(DrawData, descriptorSets);
+		routine.descriptorDynamicOffsets = data + OFFSET(DrawData, descriptorDynamicOffsets);
 		routine.pushConstants = data + OFFSET(DrawData, pushConstants);
 
 		auto activeLaneMask = SIMD::Int(0xFFFFFFFF); // TODO: Control this.
diff --git a/src/Pipeline/PixelRoutine.cpp b/src/Pipeline/PixelRoutine.cpp
index 782d8b8..7e841e0 100644
--- a/src/Pipeline/PixelRoutine.cpp
+++ b/src/Pipeline/PixelRoutine.cpp
@@ -54,18 +54,6 @@
 	{
 	}
 
-	void PixelRoutine::generate()
-	{
-		Pointer<Pointer<Byte>> descriptorSets = Pointer<Pointer<Byte>>(data + OFFSET(DrawData, descriptorSets));
-		auto numDescriptorSets = routine.pipelineLayout->getNumDescriptorSets();
-		for(unsigned int i = 0; i < numDescriptorSets; i++)
-		{
-			routine.descriptorSets[i] = descriptorSets[i];
-		}
-
-		QuadRasterizer::generate();
-	}
-
 	void PixelRoutine::quad(Pointer<Byte> cBuffer[RENDERTARGETS], Pointer<Byte> &zBuffer, Pointer<Byte> &sBuffer, Int cMask[4], Int &x, Int &y)
 	{
 		#if PERF_PROFILE
diff --git a/src/Pipeline/PixelRoutine.hpp b/src/Pipeline/PixelRoutine.hpp
index 15c9307..e7a3079 100644
--- a/src/Pipeline/PixelRoutine.hpp
+++ b/src/Pipeline/PixelRoutine.hpp
@@ -31,8 +31,6 @@
 
 		virtual ~PixelRoutine();
 
-		void generate() override;
-
 	protected:
 		Float4 z[4]; // Multisampled z
 		Float4 w;    // Used as is
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 389f838..9a91e16 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -18,6 +18,7 @@
 #include "System/Math.hpp"
 #include "Vulkan/VkBuffer.hpp"
 #include "Vulkan/VkDebug.hpp"
+#include "Vulkan/VkDescriptorSet.hpp"
 #include "Vulkan/VkPipelineLayout.hpp"
 #include "Device/Config.hpp"
 
@@ -1722,15 +1723,25 @@
 			ASSERT(d.DescriptorSet >= 0);
 			ASSERT(d.Binding >= 0);
 
-			size_t bindingOffset = routine->pipelineLayout->getBindingOffset(d.DescriptorSet, d.Binding);
+			auto set = routine->descriptorSets[d.DescriptorSet]; // DescriptorSet*
+			auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
+			size_t arrayIndex = 0; // TODO: descriptor arrays
+			size_t bindingOffset = setLayout->getBindingOffset(d.Binding, arrayIndex);
 
-			Pointer<Byte> set = routine->descriptorSets[d.DescriptorSet]; // DescriptorSet*
-			Pointer<Byte> binding = Pointer<Byte>(set + bindingOffset); // VkDescriptorBufferInfo*
-			Pointer<Byte> buffer = *Pointer<Pointer<Byte>>(binding + OFFSET(VkDescriptorBufferInfo, buffer)); // vk::Buffer*
+			Pointer<Byte> bufferInfo = Pointer<Byte>(set + bindingOffset); // VkDescriptorBufferInfo*
+			Pointer<Byte> buffer = *Pointer<Pointer<Byte>>(bufferInfo + OFFSET(VkDescriptorBufferInfo, buffer)); // vk::Buffer*
 			Pointer<Byte> data = *Pointer<Pointer<Byte>>(buffer + vk::Buffer::DataOffset); // void*
-			Int offset = *Pointer<Int>(binding + OFFSET(VkDescriptorBufferInfo, offset));
-			Pointer<Byte> address = data + offset;
-			routine->physicalPointers[resultId] = address;
+			Int offset = *Pointer<Int>(bufferInfo + OFFSET(VkDescriptorBufferInfo, offset));
+			if (setLayout->isBindingDynamic(d.Binding))
+			{
+				uint32_t dynamicBindingIndex =
+					routine->pipelineLayout->getDynamicOffsetBase(d.DescriptorSet) +
+					setLayout->getDynamicDescriptorOffset(d.Binding) +
+					arrayIndex;
+				offset += routine->descriptorDynamicOffsets[dynamicBindingIndex];
+			}
+
+			routine->physicalPointers[resultId] = data + offset;
 			break;
 		}
 		case spv::StorageClassPushConstant:
diff --git a/src/Pipeline/SpirvShader.hpp b/src/Pipeline/SpirvShader.hpp
index 47f8e32..0314d0a 100644
--- a/src/Pipeline/SpirvShader.hpp
+++ b/src/Pipeline/SpirvShader.hpp
@@ -660,7 +660,8 @@
 		Value inputs = Value{MAX_INTERFACE_COMPONENTS};
 		Value outputs = Value{MAX_INTERFACE_COMPONENTS};
 
-		std::array<Pointer<Byte>, vk::MAX_BOUND_DESCRIPTOR_SETS> descriptorSets;
+		Pointer<Pointer<Byte>> descriptorSets;
+		Pointer<Int> descriptorDynamicOffsets;
 		Pointer<Byte> pushConstants;
 		Int killMask = Int{0};
 
diff --git a/src/Pipeline/VertexProgram.cpp b/src/Pipeline/VertexProgram.cpp
index 71e48c4..a11c80b 100644
--- a/src/Pipeline/VertexProgram.cpp
+++ b/src/Pipeline/VertexProgram.cpp
@@ -46,14 +46,9 @@
 					As<Float4>(Int4((*Pointer<Int>(data + OFFSET(DrawData, instanceID)))));
 		}
 
+		routine.descriptorSets = data + OFFSET(DrawData, descriptorSets);
+		routine.descriptorDynamicOffsets = data + OFFSET(DrawData, descriptorDynamicOffsets);
 		routine.pushConstants = data + OFFSET(DrawData, pushConstants);
-
-		Pointer<Pointer<Byte>> descriptorSets = Pointer<Pointer<Byte>>(data + OFFSET(DrawData, descriptorSets));
-		auto numDescriptorSets = routine.pipelineLayout->getNumDescriptorSets();
-		for(unsigned int i = 0; i < numDescriptorSets; i++)
-		{
-			routine.descriptorSets[i] = descriptorSets[i];
-		}
 	}
 
 	VertexProgram::~VertexProgram()
diff --git a/src/Vulkan/VkCommandBuffer.cpp b/src/Vulkan/VkCommandBuffer.cpp
index cd3f72f..75a76cd 100644
--- a/src/Vulkan/VkCommandBuffer.cpp
+++ b/src/Vulkan/VkCommandBuffer.cpp
@@ -152,7 +152,7 @@
 protected:
 	void play(CommandBuffer::ExecutionState& executionState) override
 	{
-		executionState.pipelines[pipelineBindPoint] = Cast(pipeline);
+		executionState.pipelineState[pipelineBindPoint].pipeline = Cast(pipeline);
 	}
 
 private:
@@ -171,11 +171,12 @@
 protected:
 	void play(CommandBuffer::ExecutionState& executionState) override
 	{
-		ComputePipeline* pipeline = static_cast<ComputePipeline*>(
-			executionState.pipelines[VK_PIPELINE_BIND_POINT_COMPUTE]);
+		auto const &pipelineState = executionState.pipelineState[VK_PIPELINE_BIND_POINT_COMPUTE];
+
+		ComputePipeline* pipeline = static_cast<ComputePipeline*>(pipelineState.pipeline);
 		pipeline->run(groupCountX, groupCountY, groupCountZ,
-			MAX_BOUND_DESCRIPTOR_SETS,
-			executionState.boundDescriptorSets[VK_PIPELINE_BIND_POINT_COMPUTE],
+			pipelineState.descriptorSets,
+			pipelineState.descriptorDynamicOffsets,
 			executionState.pushConstants);
 	}
 
@@ -198,12 +199,13 @@
 	{
 		auto cmd = reinterpret_cast<VkDispatchIndirectCommand const *>(Cast(buffer)->getOffsetPointer(offset));
 
-		ComputePipeline* pipeline = static_cast<ComputePipeline*>(
-				executionState.pipelines[VK_PIPELINE_BIND_POINT_COMPUTE]);
+		auto const &pipelineState = executionState.pipelineState[VK_PIPELINE_BIND_POINT_COMPUTE];
+
+		ComputePipeline* pipeline = static_cast<ComputePipeline*>(pipelineState.pipeline);
 		pipeline->run(cmd->x, cmd->y, cmd->z,
-					  MAX_BOUND_DESCRIPTOR_SETS,
-					  executionState.boundDescriptorSets[VK_PIPELINE_BIND_POINT_COMPUTE],
-					  executionState.pushConstants);
+			pipelineState.descriptorSets,
+			pipelineState.descriptorDynamicOffsets,
+			executionState.pushConstants);
 	}
 
 private:
@@ -304,19 +306,16 @@
 	void draw(CommandBuffer::ExecutionState& executionState, bool indexed,
 			uint32_t count, uint32_t instanceCount, uint32_t first, int32_t vertexOffset, uint32_t firstInstance)
 	{
-		GraphicsPipeline* pipeline = static_cast<GraphicsPipeline*>(
-				executionState.pipelines[VK_PIPELINE_BIND_POINT_GRAPHICS]);
+		auto const &pipelineState = executionState.pipelineState[VK_PIPELINE_BIND_POINT_GRAPHICS];
+
+		GraphicsPipeline* pipeline = static_cast<GraphicsPipeline*>(pipelineState.pipeline);
 
 		sw::Context context = pipeline->getContext();
 
 		executionState.bindVertexInputs(context, vertexOffset, firstInstance);
 
-		const auto& boundDescriptorSets = executionState.boundDescriptorSets[VK_PIPELINE_BIND_POINT_GRAPHICS];
-		for(int i = 0; i < vk::MAX_BOUND_DESCRIPTOR_SETS; i++)
-		{
-			context.descriptorSets[i] = reinterpret_cast<vk::DescriptorSet*>(boundDescriptorSets[i]);
-		}
-
+		context.descriptorSets = pipelineState.descriptorSets;
+		context.descriptorDynamicOffsets = pipelineState.descriptorDynamicOffsets;
 		context.pushConstants = executionState.pushConstants;
 
 		if (indexed)
@@ -683,21 +682,38 @@
 
 struct BindDescriptorSet : public CommandBuffer::Command
 {
-	BindDescriptorSet(VkPipelineBindPoint pipelineBindPoint, uint32_t set, const VkDescriptorSet& descriptorSet)
-		: pipelineBindPoint(pipelineBindPoint), set(set), descriptorSet(descriptorSet)
+	BindDescriptorSet(VkPipelineBindPoint pipelineBindPoint, uint32_t set, const VkDescriptorSet& descriptorSet,
+		uint32_t dynamicOffsetCount, uint32_t const *dynamicOffsets)
+		: pipelineBindPoint(pipelineBindPoint), set(set), descriptorSet(descriptorSet),
+		  dynamicOffsetCount(dynamicOffsetCount)
 	{
+		for (uint32_t i = 0; i < dynamicOffsetCount; i++)
+		{
+			this->dynamicOffsets[i] = dynamicOffsets[i];
+		}
 	}
 
 	void play(CommandBuffer::ExecutionState& executionState)
 	{
-		ASSERT((pipelineBindPoint < VK_PIPELINE_BIND_POINT_RANGE_SIZE) && (set < MAX_BOUND_DESCRIPTOR_SETS));
-		executionState.boundDescriptorSets[pipelineBindPoint][set] = descriptorSet;
+		ASSERT_OR_RETURN((pipelineBindPoint < VK_PIPELINE_BIND_POINT_RANGE_SIZE) && (set < MAX_BOUND_DESCRIPTOR_SETS));
+		auto &pipelineState = executionState.pipelineState[pipelineBindPoint];
+		auto pipelineLayout = pipelineState.pipeline->getLayout();
+		auto dynamicOffsetBase = pipelineLayout->getDynamicOffsetBase(set);
+		ASSERT_OR_RETURN(dynamicOffsetBase + dynamicOffsetCount <= MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC);
+
+		pipelineState.descriptorSets[set] = vk::Cast(descriptorSet);
+		for (uint32_t i = 0; i < dynamicOffsetCount; i++)
+		{
+			pipelineState.descriptorDynamicOffsets[dynamicOffsetBase + i] = dynamicOffsets[i];
+		}
 	}
 
 private:
 	VkPipelineBindPoint pipelineBindPoint;
 	uint32_t set;
 	const VkDescriptorSet descriptorSet;
+	uint32_t dynamicOffsetCount;
+	vk::DescriptorSet::DynamicOffsets dynamicOffsets;
 };
 
 struct SetPushConstants : public CommandBuffer::Command
@@ -974,20 +990,28 @@
 	UNIMPLEMENTED("setStencilReference");
 }
 
-void CommandBuffer::bindDescriptorSets(VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout layout,
+void CommandBuffer::bindDescriptorSets(VkPipelineBindPoint pipelineBindPoint, VkPipelineLayout vkLayout,
 	uint32_t firstSet, uint32_t descriptorSetCount, const VkDescriptorSet* pDescriptorSets,
 	uint32_t dynamicOffsetCount, const uint32_t* pDynamicOffsets)
 {
 	ASSERT(state == RECORDING);
 
-	if(dynamicOffsetCount > 0)
-	{
-		UNIMPLEMENTED("bindDescriptorSets");
-	}
-
 	for(uint32_t i = 0; i < descriptorSetCount; i++)
 	{
-		addCommand<BindDescriptorSet>(pipelineBindPoint, firstSet + i, pDescriptorSets[i]);
+		auto descriptorSetIndex = firstSet + i;
+		auto layout = vk::Cast(vkLayout);
+		auto setLayout = layout->getDescriptorSetLayout(descriptorSetIndex);
+
+		auto numDynamicDescriptors = setLayout->getDynamicDescriptorCount();
+		ASSERT(numDynamicDescriptors == 0 || pDynamicOffsets != nullptr);
+		ASSERT(dynamicOffsetCount >= numDynamicDescriptors);
+
+		addCommand<BindDescriptorSet>(
+				pipelineBindPoint, descriptorSetIndex, pDescriptorSets[i],
+				dynamicOffsetCount, pDynamicOffsets);
+
+		pDynamicOffsets += numDynamicDescriptors;
+		dynamicOffsetCount -= numDynamicDescriptors;
 	}
 }
 
diff --git a/src/Vulkan/VkCommandBuffer.hpp b/src/Vulkan/VkCommandBuffer.hpp
index 03a0b20..39b4138 100644
--- a/src/Vulkan/VkCommandBuffer.hpp
+++ b/src/Vulkan/VkCommandBuffer.hpp
@@ -17,6 +17,7 @@
 
 #include "VkConfig.h"
 #include "VkObject.hpp"
+#include "VkDescriptorSet.hpp"
 #include "Device/Context.hpp"
 #include <memory>
 #include <vector>
@@ -123,11 +124,17 @@
 	// TODO(sugoi): Move ExecutionState out of CommandBuffer (possibly into Device)
 	struct ExecutionState
 	{
+		struct PipelineState
+		{
+			Pipeline *pipeline = nullptr;
+			vk::DescriptorSet::Bindings descriptorSets = {};
+			vk::DescriptorSet::DynamicOffsets descriptorDynamicOffsets = {};
+		};
+
 		sw::Renderer* renderer = nullptr;
 		RenderPass* renderPass = nullptr;
 		Framebuffer* renderPassFramebuffer = nullptr;
-		Pipeline* pipelines[VK_PIPELINE_BIND_POINT_RANGE_SIZE] = {};
-		VkDescriptorSet boundDescriptorSets[VK_PIPELINE_BIND_POINT_RANGE_SIZE][MAX_BOUND_DESCRIPTOR_SETS] = { { VK_NULL_HANDLE } };
+		std::array<PipelineState, VK_PIPELINE_BIND_POINT_RANGE_SIZE> pipelineState;
 		sw::PushConstantStorage pushConstants;
 
 		struct VertexInputBinding
diff --git a/src/Vulkan/VkConfig.h b/src/Vulkan/VkConfig.h
index 772f772..3aa5de8 100644
--- a/src/Vulkan/VkConfig.h
+++ b/src/Vulkan/VkConfig.h
@@ -62,6 +62,15 @@
 
 enum
 {
+	MAX_DESCRIPTOR_SET_UNIFORM_BUFFERS_DYNAMIC = 8,
+	MAX_DESCRIPTOR_SET_STORAGE_BUFFERS_DYNAMIC = 4,
+	MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC =
+			MAX_DESCRIPTOR_SET_UNIFORM_BUFFERS_DYNAMIC +
+			MAX_DESCRIPTOR_SET_STORAGE_BUFFERS_DYNAMIC,
+};
+
+enum
+{
 	MAX_POINT_SIZE = 1,		// Large points are not supported. If/when we turn this on, must be >= 64.
 };
 
diff --git a/src/Vulkan/VkDescriptorSet.hpp b/src/Vulkan/VkDescriptorSet.hpp
new file mode 100644
index 0000000..ca0626b
--- /dev/null
+++ b/src/Vulkan/VkDescriptorSet.hpp
@@ -0,0 +1,44 @@
+// Copyright 2019 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.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef VK_DESCRIPTOR_SET_HPP_
+#define VK_DESCRIPTOR_SET_HPP_
+
+// Intentionally not including VkObject.hpp here due to b/127920555
+
+#include <array>
+#include <memory>
+
+namespace vk
+{
+	class DescriptorSetLayout;
+
+	class DescriptorSet
+	{
+	public:
+		using Bindings = std::array<vk::DescriptorSet*, vk::MAX_BOUND_DESCRIPTOR_SETS>;
+		using DynamicOffsets = std::array<uint32_t, vk::MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC>;
+
+		DescriptorSetLayout* layout;
+		uint8_t data[];
+	};
+
+	inline DescriptorSet* Cast(VkDescriptorSet object)
+	{
+		return reinterpret_cast<DescriptorSet*>(object);
+	}
+
+} // namespace vk
+
+#endif // VK_DESCRIPTOR_SET_HPP_
diff --git a/src/Vulkan/VkDescriptorSetLayout.cpp b/src/Vulkan/VkDescriptorSetLayout.cpp
index 4b65eca..7a5dcca 100644
--- a/src/Vulkan/VkDescriptorSetLayout.cpp
+++ b/src/Vulkan/VkDescriptorSetLayout.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "VkDescriptorSetLayout.hpp"
+#include "VkDescriptorSet.hpp"
 #include "System/Types.hpp"
 
 #include <algorithm>
@@ -134,7 +135,7 @@
 		}
 	}
 
-	ASSERT(false); // Bindings should always be found
+	DABORT("Invalid DescriptorSetLayout binding: %d", int(binding));
 	return 0;
 }
 
@@ -164,10 +165,63 @@
 	}
 }
 
-size_t DescriptorSetLayout::getBindingOffset(uint32_t binding) const
+size_t DescriptorSetLayout::getBindingCount() const
+{
+	return bindingCount;
+}
+
+size_t DescriptorSetLayout::getBindingOffset(uint32_t binding, uint32_t arrayElement) const
 {
 	uint32_t index = getBindingIndex(binding);
-	return bindingOffsets[index] + OFFSET(DescriptorSet, data[0]);
+	auto typeSize = GetDescriptorSize(bindings[index].descriptorType);
+	return bindingOffsets[index] + OFFSET(DescriptorSet, data[0]) + (typeSize * arrayElement);
+}
+
+bool DescriptorSetLayout::isDynamic(VkDescriptorType type)
+{
+	return type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC ||
+		   type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
+}
+
+bool DescriptorSetLayout::isBindingDynamic(uint32_t binding) const
+{
+	uint32_t index = getBindingIndex(binding);
+	return isDynamic(bindings[index].descriptorType);
+}
+
+size_t DescriptorSetLayout::getDynamicDescriptorCount() const
+{
+	size_t count = 0;
+	for (size_t i = 0; i < bindingCount; i++)
+	{
+		if (isDynamic(bindings[i].descriptorType))
+		{
+			count += bindings[i].descriptorCount;
+		}
+	}
+	return count;
+}
+
+size_t DescriptorSetLayout::getDynamicDescriptorOffset(uint32_t binding) const
+{
+	uint32_t n = getBindingIndex(binding);
+	ASSERT(isDynamic(bindings[n].descriptorType));
+
+	size_t index = 0;
+	for (uint32_t i = 0; i < n; i++)
+	{
+		if (isDynamic(bindings[i].descriptorType))
+		{
+			index += bindings[i].descriptorCount;
+		}
+	}
+	return index;
+}
+
+VkDescriptorSetLayoutBinding const & DescriptorSetLayout::getBindingLayout(uint32_t binding) const
+{
+	ASSERT(binding < bindingCount);
+	return bindings[binding];
 }
 
 uint8_t* DescriptorSetLayout::getOffsetPointer(DescriptorSet *descriptorSet, uint32_t binding, uint32_t arrayElement, uint32_t count, size_t* typeSize) const
diff --git a/src/Vulkan/VkDescriptorSetLayout.hpp b/src/Vulkan/VkDescriptorSetLayout.hpp
index 8cad112..b086626 100644
--- a/src/Vulkan/VkDescriptorSetLayout.hpp
+++ b/src/Vulkan/VkDescriptorSetLayout.hpp
@@ -20,18 +20,7 @@
 namespace vk
 {
 
-class DescriptorSetLayout;
-
-struct DescriptorSet
-{
-	vk::DescriptorSetLayout* layout;
-	uint8_t data[];
-};
-
-inline DescriptorSet* Cast(VkDescriptorSet object)
-{
-	return reinterpret_cast<DescriptorSet*>(object);
-}
+class DescriptorSet;
 
 class DescriptorSetLayout : public Object<DescriptorSetLayout, VkDescriptorSetLayout>
 {
@@ -47,15 +36,43 @@
 	static void CopyDescriptorSet(const VkCopyDescriptorSet& descriptorCopies);
 
 	void initialize(VkDescriptorSet descriptorSet);
+
+	// Returns the total size of the descriptor set in bytes.
 	size_t getDescriptorSetAllocationSize() const;
 
-	size_t getBindingOffset(uint32_t binding) const;
+	// Returns the number of bindings in the descriptor set.
+	size_t getBindingCount() const;
+
+	// Returns the byte offset from the base address of the descriptor set for
+	// the given binding and array element within that binding.
+	size_t getBindingOffset(uint32_t binding, uint32_t arrayElement) const;
+
+	// Returns the number of descriptors across all bindings that are dynamic
+	// (see isBindingDynamic).
+	size_t getDynamicDescriptorCount() const;
+
+	// Returns the relative offset into the pipeline's dynamic offsets array for
+	// the given binding. This offset should be added to the base offset
+	// returned by PipelineLayout::getDynamicOffsetBase() to produce the
+	// starting index for dynamic descriptors.
+	size_t getDynamicDescriptorOffset(uint32_t binding) const;
+
+	// Returns true if the given binding is of type:
+	//  VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC or
+	//  VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC
+	bool isBindingDynamic(uint32_t binding) const;
+
+	// Returns the VkDescriptorSetLayoutBinding for the binding with the given
+	// index.
+	VkDescriptorSetLayoutBinding const & getBindingLayout(uint32_t binding) const;
+
 	uint8_t* getOffsetPointer(DescriptorSet *descriptorSet, uint32_t binding, uint32_t arrayElement, uint32_t count, size_t* typeSize) const;
 
 private:
 	size_t getDescriptorSetDataSize() const;
 	uint32_t getBindingIndex(uint32_t binding) const;
 	static const uint8_t* GetInputData(const VkWriteDescriptorSet& descriptorWrites);
+	static bool isDynamic(VkDescriptorType type);
 
 	VkDescriptorSetLayoutCreateFlags flags;
 	uint32_t                         bindingCount;
diff --git a/src/Vulkan/VkDescriptorUpdateTemplate.cpp b/src/Vulkan/VkDescriptorUpdateTemplate.cpp
index 1a814e8..1b72419 100644
--- a/src/Vulkan/VkDescriptorUpdateTemplate.cpp
+++ b/src/Vulkan/VkDescriptorUpdateTemplate.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "VkDescriptorUpdateTemplate.hpp"
+#include "VkDescriptorSet.hpp"
 #include "VkDescriptorSetLayout.hpp"
 #include <cstring>
 
diff --git a/src/Vulkan/VkPhysicalDevice.cpp b/src/Vulkan/VkPhysicalDevice.cpp
index c4591fc..2d2f5a7 100644
--- a/src/Vulkan/VkPhysicalDevice.cpp
+++ b/src/Vulkan/VkPhysicalDevice.cpp
@@ -162,9 +162,9 @@
 		128, // maxPerStageResources
 		96, // maxDescriptorSetSamplers
 		72, // maxDescriptorSetUniformBuffers
-		8, // maxDescriptorSetUniformBuffersDynamic
+		MAX_DESCRIPTOR_SET_UNIFORM_BUFFERS_DYNAMIC, // maxDescriptorSetUniformBuffersDynamic
 		24, // maxDescriptorSetStorageBuffers
-		4, // maxDescriptorSetStorageBuffersDynamic
+		MAX_DESCRIPTOR_SET_STORAGE_BUFFERS_DYNAMIC, // maxDescriptorSetStorageBuffersDynamic
 		96, // maxDescriptorSetSampledImages
 		24, // maxDescriptorSetStorageImages
 		4, // maxDescriptorSetInputAttachments
diff --git a/src/Vulkan/VkPipeline.cpp b/src/Vulkan/VkPipeline.cpp
index 4cb0240..50b83f4 100644
--- a/src/Vulkan/VkPipeline.cpp
+++ b/src/Vulkan/VkPipeline.cpp
@@ -517,11 +517,13 @@
 }
 
 void ComputePipeline::run(uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
-	size_t numDescriptorSets, VkDescriptorSet *descriptorSets, sw::PushConstantStorage const &pushConstants)
+	vk::DescriptorSet::Bindings const &descriptorSets,
+	vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
+	sw::PushConstantStorage const &pushConstants)
 {
 	ASSERT_OR_RETURN(routine != nullptr);
 	sw::ComputeProgram::run(
-		routine, reinterpret_cast<void**>(descriptorSets), pushConstants,
+		routine, descriptorSets, descriptorDynamicOffsets, pushConstants,
 		groupCountX, groupCountY, groupCountZ);
 }
 
diff --git a/src/Vulkan/VkPipeline.hpp b/src/Vulkan/VkPipeline.hpp
index c43a8e9..b220027 100644
--- a/src/Vulkan/VkPipeline.hpp
+++ b/src/Vulkan/VkPipeline.hpp
@@ -16,6 +16,7 @@
 #define VK_PIPELINE_HPP_
 
 #include "VkObject.hpp"
+#include "Vulkan/VkDescriptorSet.hpp"
 #include "Device/Renderer.hpp"
 
 namespace sw { class SpirvShader; }
@@ -104,7 +105,9 @@
 	void compileShaders(const VkAllocationCallbacks* pAllocator, const VkComputePipelineCreateInfo* pCreateInfo);
 
 	void run(uint32_t groupCountX, uint32_t groupCountY, uint32_t groupCountZ,
-		size_t numDescriptorSets, VkDescriptorSet *descriptorSets, sw::PushConstantStorage const &pushConstants);
+		vk::DescriptorSet::Bindings const &descriptorSets,
+		vk::DescriptorSet::DynamicOffsets const &descriptorDynamicOffsets,
+		sw::PushConstantStorage const &pushConstants);
 
 protected:
 	sw::SpirvShader *shader = nullptr;
diff --git a/src/Vulkan/VkPipelineLayout.cpp b/src/Vulkan/VkPipelineLayout.cpp
index fcc6c66..1c80ca7 100644
--- a/src/Vulkan/VkPipelineLayout.cpp
+++ b/src/Vulkan/VkPipelineLayout.cpp
@@ -31,6 +31,16 @@
 	size_t pushConstantRangesSize = pCreateInfo->pushConstantRangeCount * sizeof(VkPushConstantRange);
 	pushConstantRanges = reinterpret_cast<VkPushConstantRange*>(hostMem);
 	memcpy(pushConstantRanges, pCreateInfo->pPushConstantRanges, pushConstantRangesSize);
+	hostMem += pushConstantRangesSize;
+
+	dynamicOffsetBases = reinterpret_cast<uint32_t*>(hostMem);
+	uint32_t dynamicOffsetBase = 0;
+	for (uint32_t i = 0; i < setLayoutCount; i++)
+	{
+		ASSERT_OR_RETURN(dynamicOffsetBase < MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC);
+		dynamicOffsetBases[i] = dynamicOffsetBase;
+		dynamicOffsetBase += setLayouts[i]->getDynamicDescriptorCount();
+	}
 }
 
 void PipelineLayout::destroy(const VkAllocationCallbacks* pAllocator)
@@ -41,7 +51,8 @@
 size_t PipelineLayout::ComputeRequiredAllocationSize(const VkPipelineLayoutCreateInfo* pCreateInfo)
 {
 	return (pCreateInfo->setLayoutCount * sizeof(DescriptorSetLayout*)) +
-	       (pCreateInfo->pushConstantRangeCount * sizeof(VkPushConstantRange));
+	       (pCreateInfo->pushConstantRangeCount * sizeof(VkPushConstantRange)) +
+		   (pCreateInfo->setLayoutCount * sizeof(uint32_t)); // dynamicOffsetBases
 }
 
 size_t PipelineLayout::getNumDescriptorSets() const
@@ -49,10 +60,16 @@
 	return setLayoutCount;
 }
 
-size_t PipelineLayout::getBindingOffset(size_t descriptorSet, size_t binding) const
+DescriptorSetLayout const* PipelineLayout::getDescriptorSetLayout(size_t descriptorSet) const
 {
 	ASSERT(descriptorSet < setLayoutCount);
-	return setLayouts[descriptorSet]->getBindingOffset(binding);
+	return setLayouts[descriptorSet];
+}
+
+uint32_t PipelineLayout::getDynamicOffsetBase(size_t descriptorSet) const
+{
+	ASSERT(descriptorSet < setLayoutCount);
+	return dynamicOffsetBases[descriptorSet];
 }
 
 } // namespace vk
diff --git a/src/Vulkan/VkPipelineLayout.hpp b/src/Vulkan/VkPipelineLayout.hpp
index 5723317..57dc9ca 100644
--- a/src/Vulkan/VkPipelineLayout.hpp
+++ b/src/Vulkan/VkPipelineLayout.hpp
@@ -30,13 +30,18 @@
 	static size_t ComputeRequiredAllocationSize(const VkPipelineLayoutCreateInfo* pCreateInfo);
 
 	size_t getNumDescriptorSets() const;
-	size_t getBindingOffset(size_t descriptorSet, size_t binding) const;
+	DescriptorSetLayout const* getDescriptorSetLayout(size_t descriptorSet) const;
+
+	// Returns the starting index into the pipeline's dynamic offsets array for
+	// the given descriptor set.
+	uint32_t getDynamicOffsetBase(size_t descriptorSet) const;
 
 private:
 	uint32_t              setLayoutCount = 0;
 	DescriptorSetLayout** setLayouts = nullptr;
 	uint32_t              pushConstantRangeCount = 0;
 	VkPushConstantRange*  pushConstantRanges = nullptr;
+	uint32_t*             dynamicOffsetBases = nullptr; // Base offset per set layout.
 };
 
 static inline PipelineLayout* Cast(VkPipelineLayout object)