Don't expose individual descriptor set layouts to SPIR-V compilation

Shader compilation requires access to the pipeline layout, but not to
each of its descriptor set layout interfaces. This change ensures that
all information is queried only through the pipeline layout interface.
This facilitates refactoring the pipeline layout object to contain all
this information, instead of depending on the descriptor set layout
objects to remain alive after pipeline layout creation.

The Vulkan spec states that "a VkDescriptorSetLayout object passed as
a parameter to create another object is not further accessed by that
object after the duration of the command it is passed into."

Also consistently use "index" for values that index into an array, and
"offset" for byte offsets. "descriptor" signifies an individual resource
descriptor, while "binding" refers to the descriptor set binding which
is an array (often of just one descriptor). Use "setNumber" and
"bindingNumber" for the 32-bit identifiers used by SPIR-V, to
distinguish them from the actual objects.

Bug: b/154522740
Change-Id: If3f6e56b6769aae6ebbd49109e7dc1e78cf6558c
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/44188
Presubmit-Ready: Nicolas Capens <nicolascapens@google.com>
Tested-by: Nicolas Capens <nicolascapens@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Kokoro-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/Pipeline/SpirvShader.cpp b/src/Pipeline/SpirvShader.cpp
index 927bb89..6f2a9b0 100644
--- a/src/Pipeline/SpirvShader.cpp
+++ b/src/Pipeline/SpirvShader.cpp
@@ -1236,9 +1236,8 @@
 					auto d = descriptorDecorations.at(baseId);
 					ASSERT(d.DescriptorSet >= 0);
 					ASSERT(d.Binding >= 0);
-					auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
-					auto stride = static_cast<uint32_t>(setLayout->getBindingStride(d.Binding));
-					ptr.base += stride * GetConstScalarInt(indexIds[i]);
+					uint32_t descriptorSize = routine->pipelineLayout->getDescriptorSize(d.DescriptorSet, d.Binding);
+					ptr.base += descriptorSize * GetConstScalarInt(indexIds[i]);
 				}
 				else
 				{
diff --git a/src/Pipeline/SpirvShaderImage.cpp b/src/Pipeline/SpirvShaderImage.cpp
index b803b40..6da2de9 100644
--- a/src/Pipeline/SpirvShaderImage.cpp
+++ b/src/Pipeline/SpirvShaderImage.cpp
@@ -345,8 +345,7 @@
 	bool isCubeMap = imageType.definition.word(3) == spv::DimCube;
 
 	const DescriptorDecorations &d = descriptorDecorations.at(imageId);
-	auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
-	auto descriptorType = setLayout->getDescriptorType(d.Binding);
+	auto descriptorType = routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
 
 	Pointer<Byte> descriptor = state->getPointer(imageId).base;
 
@@ -409,8 +408,7 @@
 	auto imageId = Object::ID(insn.word(3));
 
 	const DescriptorDecorations &d = descriptorDecorations.at(imageId);
-	auto setLayout = state->routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
-	auto descriptorType = setLayout->getDescriptorType(d.Binding);
+	auto descriptorType = state->routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
 
 	Pointer<Byte> descriptor = state->getPointer(imageId).base;
 	Int mipLevels = 0;
@@ -442,8 +440,7 @@
 	ASSERT(imageTy.definition.word(6 /* MS */) == 1);
 
 	const DescriptorDecorations &d = descriptorDecorations.at(imageId);
-	auto setLayout = state->routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
-	auto descriptorType = setLayout->getDescriptorType(d.Binding);
+	auto descriptorType = state->routine->pipelineLayout->getDescriptorType(d.DescriptorSet, d.Binding);
 
 	Pointer<Byte> descriptor = state->getPointer(imageId).base;
 	Int sampleCount = 0;
diff --git a/src/Pipeline/SpirvShaderMemory.cpp b/src/Pipeline/SpirvShaderMemory.cpp
index 5d65e6d..5eab8c9 100644
--- a/src/Pipeline/SpirvShaderMemory.cpp
+++ b/src/Pipeline/SpirvShaderMemory.cpp
@@ -170,9 +170,7 @@
 			ASSERT(d.DescriptorSet >= 0);
 			ASSERT(d.Binding >= 0);
 
-			uint32_t arrayIndex = 0;  // TODO(b/129523279)
-			auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
-			uint32_t bindingOffset = static_cast<uint32_t>(setLayout->getBindingOffset(d.Binding, arrayIndex));
+			uint32_t bindingOffset = routine->pipelineLayout->getBindingOffset(d.DescriptorSet, d.Binding);
 			Pointer<Byte> set = routine->descriptorSets[d.DescriptorSet];  // DescriptorSet*
 			Pointer<Byte> binding = Pointer<Byte>(set + bindingOffset);    // vk::SampledImageDescriptor*
 			auto size = 0;                                                 // Not required as this pointer is not directly used by SIMD::Read or SIMD::Write.
@@ -390,22 +388,23 @@
 			ASSERT(d.DescriptorSet >= 0 && d.DescriptorSet < vk::MAX_BOUND_DESCRIPTOR_SETS);
 			ASSERT(d.Binding >= 0);
 
+			uint32_t bindingOffset = routine->pipelineLayout->getBindingOffset(d.DescriptorSet, d.Binding);
+			uint32_t descriptorSize = routine->pipelineLayout->getDescriptorSize(d.DescriptorSet, d.Binding);
+			uint32_t descriptorOffset = bindingOffset + descriptorSize * arrayIndex;
+
 			auto set = state->getPointer(id);
-
-			auto setLayout = routine->pipelineLayout->getDescriptorSetLayout(d.DescriptorSet);
-			int bindingOffset = static_cast<int>(setLayout->getBindingOffset(d.Binding, arrayIndex));
-
-			Pointer<Byte> descriptor = set.base + bindingOffset;                                           // BufferDescriptor*
+			Pointer<Byte> descriptor = set.base + descriptorOffset;                                        // BufferDescriptor*
 			Pointer<Byte> data = *Pointer<Pointer<Byte>>(descriptor + OFFSET(vk::BufferDescriptor, ptr));  // void*
 			Int size = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, sizeInBytes));
-			if(setLayout->isBindingDynamic(d.Binding))
+
+			if(routine->pipelineLayout->isDescriptorDynamic(d.DescriptorSet, d.Binding))
 			{
-				uint32_t dynamicBindingIndex =
-				    routine->pipelineLayout->getDynamicOffsetBase(d.DescriptorSet) +
-				    setLayout->getDynamicDescriptorOffset(d.Binding) +
+				uint32_t dynamicOffsetIndex =
+				    routine->pipelineLayout->getDynamicOffsetIndex(d.DescriptorSet, d.Binding) +
 				    arrayIndex;
-				Int offset = routine->descriptorDynamicOffsets[dynamicBindingIndex];
+				Int offset = routine->descriptorDynamicOffsets[dynamicOffsetIndex];
 				Int robustnessSize = *Pointer<Int>(descriptor + OFFSET(vk::BufferDescriptor, robustnessSize));
+
 				return SIMD::Pointer(data + offset, Min(size, robustnessSize - offset));
 			}
 			else
diff --git a/src/Reactor/Reactor.cpp b/src/Reactor/Reactor.cpp
index 2bf5324..0ffa010 100644
--- a/src/Reactor/Reactor.cpp
+++ b/src/Reactor/Reactor.cpp
@@ -13,6 +13,7 @@
 // limitations under the License.
 
 #include "Reactor.hpp"
+
 #include "Debug.hpp"
 #include "Print.hpp"
 
diff --git a/src/Vulkan/VkCommandBuffer.cpp b/src/Vulkan/VkCommandBuffer.cpp
index ec1131a..4acabff 100644
--- a/src/Vulkan/VkCommandBuffer.cpp
+++ b/src/Vulkan/VkCommandBuffer.cpp
@@ -1087,11 +1087,11 @@
 class CmdBindDescriptorSet : public vk::CommandBuffer::Command
 {
 public:
-	CmdBindDescriptorSet(VkPipelineBindPoint pipelineBindPoint, const vk::PipelineLayout *pipelineLayout, uint32_t set, vk::DescriptorSet *descriptorSet,
+	CmdBindDescriptorSet(VkPipelineBindPoint pipelineBindPoint, const vk::PipelineLayout *pipelineLayout, uint32_t setNumber, vk::DescriptorSet *descriptorSet,
 	                     uint32_t dynamicOffsetCount, uint32_t const *dynamicOffsets)
 	    : pipelineBindPoint(pipelineBindPoint)
 	    , pipelineLayout(pipelineLayout)
-	    , set(set)
+	    , setNumber(setNumber)
 	    , descriptorSet(descriptorSet)
 	    , dynamicOffsetCount(dynamicOffsetCount)
 	{
@@ -1103,26 +1103,26 @@
 
 	void play(vk::CommandBuffer::ExecutionState &executionState) override
 	{
-		ASSERT_OR_RETURN((pipelineBindPoint < VK_PIPELINE_BIND_POINT_RANGE_SIZE) && (set < vk::MAX_BOUND_DESCRIPTOR_SETS));
+		ASSERT_OR_RETURN((pipelineBindPoint < VK_PIPELINE_BIND_POINT_RANGE_SIZE) && (setNumber < vk::MAX_BOUND_DESCRIPTOR_SETS));
 		auto &pipelineState = executionState.pipelineState[pipelineBindPoint];
-		auto dynamicOffsetBase = pipelineLayout->getDynamicOffsetBase(set);
-		ASSERT_OR_RETURN(dynamicOffsetBase + dynamicOffsetCount <= vk::MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC);
+		auto dynamicBindingBaseIndex = pipelineLayout->getDynamicOffsetBaseIndex(setNumber);
+		ASSERT_OR_RETURN(dynamicBindingBaseIndex + dynamicOffsetCount <= vk::MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC);
 
-		pipelineState.descriptorSets[set] = descriptorSet->data;
+		pipelineState.descriptorSets[setNumber] = descriptorSet->data;
 		for(uint32_t i = 0; i < dynamicOffsetCount; i++)
 		{
-			pipelineState.descriptorDynamicOffsets[dynamicOffsetBase + i] = dynamicOffsets[i];
+			pipelineState.descriptorDynamicOffsets[dynamicBindingBaseIndex + i] = dynamicOffsets[i];
 		}
 	}
 
 	std::string description() override { return "vkCmdBindDescriptorSet()"; }
 
 private:
-	VkPipelineBindPoint pipelineBindPoint;
-	const vk::PipelineLayout *pipelineLayout;
-	uint32_t set;
-	vk::DescriptorSet *descriptorSet;
-	uint32_t dynamicOffsetCount;
+	const VkPipelineBindPoint pipelineBindPoint;
+	const vk::PipelineLayout *const pipelineLayout;
+	const uint32_t setNumber;
+	vk::DescriptorSet *const descriptorSet;
+	const uint32_t dynamicOffsetCount;
 	vk::DescriptorSet::DynamicOffsets dynamicOffsets;
 };
 
@@ -1568,9 +1568,7 @@
 	for(uint32_t i = 0; i < descriptorSetCount; i++)
 	{
 		auto descriptorSetIndex = firstSet + i;
-		auto setLayout = layout->getDescriptorSetLayout(descriptorSetIndex);
-
-		auto numDynamicDescriptors = setLayout->getDynamicDescriptorCount();
+		uint32_t numDynamicDescriptors = layout->getDynamicDescriptorCount(descriptorSetIndex);
 		ASSERT(numDynamicDescriptors == 0 || pDynamicOffsets != nullptr);
 		ASSERT(dynamicOffsetCount >= numDynamicDescriptors);
 
diff --git a/src/Vulkan/VkDescriptorSetLayout.cpp b/src/Vulkan/VkDescriptorSetLayout.cpp
index 05c9297..102a024 100644
--- a/src/Vulkan/VkDescriptorSetLayout.cpp
+++ b/src/Vulkan/VkDescriptorSetLayout.cpp
@@ -109,7 +109,7 @@
 	       immutableSamplerCount * sizeof(VkSampler);
 }
 
-size_t DescriptorSetLayout::GetDescriptorSize(VkDescriptorType type)
+uint32_t DescriptorSetLayout::GetDescriptorSize(VkDescriptorType type)
 {
 	switch(type)
 	{
@@ -117,22 +117,28 @@
 		case VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER:
 		case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
 		case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
-			return sizeof(SampledImageDescriptor);
+			return static_cast<uint32_t>(sizeof(SampledImageDescriptor));
 		case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
 		case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
 		case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT:
-			return sizeof(StorageImageDescriptor);
+			return static_cast<uint32_t>(sizeof(StorageImageDescriptor));
 		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER:
 		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
 		case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
 		case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
-			return sizeof(BufferDescriptor);
+			return static_cast<uint32_t>(sizeof(BufferDescriptor));
 		default:
 			UNSUPPORTED("Unsupported Descriptor Type");
 			return 0;
 	}
 }
 
+bool DescriptorSetLayout::IsDescriptorDynamic(VkDescriptorType type)
+{
+	return type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC ||
+	       type == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
+}
+
 size_t DescriptorSetLayout::getDescriptorSetAllocationSize() const
 {
 	// vk::DescriptorSet has a header with a pointer to the layout.
@@ -176,29 +182,10 @@
 	}
 }
 
-size_t DescriptorSetLayout::getBindingStride(uint32_t binding) const
+uint32_t DescriptorSetLayout::getBindingOffset(uint32_t bindingNumber) const
 {
-	ASSERT(binding < bindingsArraySize);
-	return GetDescriptorSize(bindings[binding].descriptorType);
-}
-
-size_t DescriptorSetLayout::getBindingOffset(uint32_t binding, size_t arrayElement) const
-{
-	ASSERT(binding < bindingsArraySize && arrayElement < bindings[binding].descriptorCount);
-	auto typeSize = GetDescriptorSize(bindings[binding].descriptorType);
-	return bindings[binding].offset + (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
-{
-	ASSERT(binding < bindingsArraySize);
-	return isDynamic(bindings[binding].descriptorType);
+	ASSERT(bindingNumber < bindingsArraySize);
+	return bindings[bindingNumber].offset;
 }
 
 uint32_t DescriptorSetLayout::getDynamicDescriptorCount() const
@@ -206,7 +193,7 @@
 	uint32_t count = 0;
 	for(size_t i = 0; i < bindingsArraySize; i++)
 	{
-		if(isDynamic(bindings[i].descriptorType))
+		if(IsDescriptorDynamic(bindings[i].descriptorType))
 		{
 			count += bindings[i].descriptorCount;
 		}
@@ -215,15 +202,15 @@
 	return count;
 }
 
-uint32_t DescriptorSetLayout::getDynamicDescriptorOffset(uint32_t binding) const
+uint32_t DescriptorSetLayout::getDynamicOffsetIndex(uint32_t bindingNumber) const
 {
-	ASSERT(binding < bindingsArraySize);
-	ASSERT(isDynamic(bindings[binding].descriptorType));
+	ASSERT(bindingNumber < bindingsArraySize);
+	ASSERT(IsDescriptorDynamic(bindings[bindingNumber].descriptorType));
 
 	uint32_t index = 0;
-	for(uint32_t i = 0; i < binding; i++)
+	for(uint32_t i = 0; i < bindingNumber; i++)
 	{
-		if(isDynamic(bindings[i].descriptorType))
+		if(IsDescriptorDynamic(bindings[i].descriptorType))
 		{
 			index += bindings[i].descriptorCount;
 		}
@@ -232,17 +219,17 @@
 	return index;
 }
 
-VkDescriptorType DescriptorSetLayout::getDescriptorType(uint32_t binding) const
+VkDescriptorType DescriptorSetLayout::getDescriptorType(uint32_t bindingNumber) const
 {
-	ASSERT(binding < bindingsArraySize);
-	return bindings[binding].descriptorType;
+	ASSERT(bindingNumber < bindingsArraySize);
+	return bindings[bindingNumber].descriptorType;
 }
 
-uint8_t *DescriptorSetLayout::getOffsetPointer(DescriptorSet *descriptorSet, uint32_t binding, uint32_t arrayElement, uint32_t count, size_t *typeSize) const
+uint8_t *DescriptorSetLayout::getDescriptorPointer(DescriptorSet *descriptorSet, uint32_t bindingNumber, uint32_t arrayElement, uint32_t count, size_t *typeSize) const
 {
-	ASSERT(binding < bindingsArraySize);
-	*typeSize = GetDescriptorSize(bindings[binding].descriptorType);
-	size_t byteOffset = bindings[binding].offset + (*typeSize * arrayElement);
+	ASSERT(bindingNumber < bindingsArraySize);
+	*typeSize = GetDescriptorSize(bindings[bindingNumber].descriptorType);
+	size_t byteOffset = bindings[bindingNumber].offset + (*typeSize * arrayElement);
 	ASSERT(((*typeSize * count) + byteOffset) <= getDescriptorSetDataSize());  // Make sure the operation will not go out of bounds
 
 	return &descriptorSet->data[byteOffset];
@@ -261,7 +248,7 @@
 	ASSERT(binding.descriptorType == entry.descriptorType);
 
 	size_t typeSize = 0;
-	uint8_t *memToWrite = dstLayout->getOffsetPointer(dstSet, entry.dstBinding, entry.dstArrayElement, entry.descriptorCount, &typeSize);
+	uint8_t *memToWrite = dstLayout->getDescriptorPointer(dstSet, entry.dstBinding, entry.dstArrayElement, entry.descriptorCount, &typeSize);
 
 	ASSERT(reinterpret_cast<intptr_t>(memToWrite) % 16 == 0);  // Each descriptor must be 16-byte aligned.
 
@@ -629,10 +616,10 @@
 	ASSERT(dstLayout);
 
 	size_t srcTypeSize = 0;
-	uint8_t *memToRead = srcLayout->getOffsetPointer(srcSet, descriptorCopies.srcBinding, descriptorCopies.srcArrayElement, descriptorCopies.descriptorCount, &srcTypeSize);
+	uint8_t *memToRead = srcLayout->getDescriptorPointer(srcSet, descriptorCopies.srcBinding, descriptorCopies.srcArrayElement, descriptorCopies.descriptorCount, &srcTypeSize);
 
 	size_t dstTypeSize = 0;
-	uint8_t *memToWrite = dstLayout->getOffsetPointer(dstSet, descriptorCopies.dstBinding, descriptorCopies.dstArrayElement, descriptorCopies.descriptorCount, &dstTypeSize);
+	uint8_t *memToWrite = dstLayout->getDescriptorPointer(dstSet, descriptorCopies.dstBinding, descriptorCopies.dstArrayElement, descriptorCopies.descriptorCount, &dstTypeSize);
 
 	ASSERT(srcTypeSize == dstTypeSize);
 	size_t writeSize = dstTypeSize * descriptorCopies.descriptorCount;
diff --git a/src/Vulkan/VkDescriptorSetLayout.hpp b/src/Vulkan/VkDescriptorSetLayout.hpp
index 84a1443..5fd4d79 100644
--- a/src/Vulkan/VkDescriptorSetLayout.hpp
+++ b/src/Vulkan/VkDescriptorSetLayout.hpp
@@ -21,6 +21,8 @@
 #include "Vulkan/VkImageView.hpp"
 #include "Vulkan/VkSampler.hpp"
 
+#include <cstdint>
+
 namespace vk {
 
 class DescriptorSet;
@@ -93,7 +95,9 @@
 
 	static size_t ComputeRequiredAllocationSize(const VkDescriptorSetLayoutCreateInfo *pCreateInfo);
 
-	static size_t GetDescriptorSize(VkDescriptorType type);
+	static uint32_t GetDescriptorSize(VkDescriptorType type);
+	static bool IsDescriptorDynamic(VkDescriptorType type);
+
 	static void WriteDescriptorSet(Device *device, const VkWriteDescriptorSet &descriptorWrites);
 	static void CopyDescriptorSet(const VkCopyDescriptorSet &descriptorCopies);
 
@@ -106,33 +110,23 @@
 	size_t getDescriptorSetAllocationSize() 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, size_t arrayElement) const;
+	// the given binding.
+	uint32_t getBindingOffset(uint32_t bindingNumber) const;
 
-	// Returns the stride of an array of descriptors
-	size_t getBindingStride(uint32_t binding) const;
-
-	// Returns the number of descriptors across all bindings that are dynamic
-	// (see isBindingDynamic).
+	// Returns the number of descriptors across all bindings that are dynamic.
 	uint32_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
+	// Returns the relative index into the pipeline's dynamic offsets array for
+	// the given binding. This index should be added to the base index
 	// returned by PipelineLayout::getDynamicOffsetBase() to produce the
 	// starting index for dynamic descriptors.
-	uint32_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;
+	uint32_t getDynamicOffsetIndex(uint32_t bindingNumber) const;
 
 	// Returns the descriptor type for the given binding.
-	VkDescriptorType getDescriptorType(uint32_t binding) const;
-
-	uint8_t *getOffsetPointer(DescriptorSet *descriptorSet, uint32_t binding, uint32_t arrayElement, uint32_t count, size_t *typeSize) const;
+	VkDescriptorType getDescriptorType(uint32_t bindingNumber) const;
 
 private:
+	uint8_t *getDescriptorPointer(DescriptorSet *descriptorSet, uint32_t bindingNumber, uint32_t arrayElement, uint32_t count, size_t *typeSize) const;
 	size_t getDescriptorSetDataSize() const;
 	static bool isDynamic(VkDescriptorType type);
 
diff --git a/src/Vulkan/VkMemory.cpp b/src/Vulkan/VkMemory.cpp
index 76afb0e..753040e 100644
--- a/src/Vulkan/VkMemory.cpp
+++ b/src/Vulkan/VkMemory.cpp
@@ -16,6 +16,7 @@
 #define VK_OBJECT_HPP_
 
 #include "VkMemory.h"
+
 #include "VkConfig.h"
 #include "System/Memory.hpp"
 
diff --git a/src/Vulkan/VkPipelineLayout.cpp b/src/Vulkan/VkPipelineLayout.cpp
index ba9a777..9288a92 100644
--- a/src/Vulkan/VkPipelineLayout.cpp
+++ b/src/Vulkan/VkPipelineLayout.cpp
@@ -23,16 +23,16 @@
 
 PipelineLayout::PipelineLayout(const VkPipelineLayoutCreateInfo *pCreateInfo, void *mem)
     : identifier(layoutIdentifierSerial++)
-    , setLayoutCount(pCreateInfo->setLayoutCount)
+    , descriptorSetCount(pCreateInfo->setLayoutCount)
     , pushConstantRangeCount(pCreateInfo->pushConstantRangeCount)
 {
 	char *hostMem = reinterpret_cast<char *>(mem);
 
 	size_t setLayoutsSize = pCreateInfo->setLayoutCount * sizeof(DescriptorSetLayout *);
-	setLayouts = reinterpret_cast<DescriptorSetLayout **>(hostMem);
+	descriptorSetLayouts = reinterpret_cast<const DescriptorSetLayout **>(hostMem);
 	for(uint32_t i = 0; i < pCreateInfo->setLayoutCount; i++)
 	{
-		setLayouts[i] = vk::Cast(pCreateInfo->pSetLayouts[i]);
+		descriptorSetLayouts[i] = vk::Cast(pCreateInfo->pSetLayouts[i]);
 	}
 	hostMem += setLayoutsSize;
 
@@ -41,44 +41,73 @@
 	memcpy(pushConstantRanges, pCreateInfo->pPushConstantRanges, pushConstantRangesSize);
 	hostMem += pushConstantRangesSize;
 
-	dynamicOffsetBases = reinterpret_cast<uint32_t *>(hostMem);
+	dynamicOffsetBaseIndices = reinterpret_cast<uint32_t *>(hostMem);
 	uint32_t dynamicOffsetBase = 0;
-	for(uint32_t i = 0; i < setLayoutCount; i++)
+	for(uint32_t i = 0; i < descriptorSetCount; i++)
 	{
-		uint32_t dynamicDescriptorCount = setLayouts[i]->getDynamicDescriptorCount();
+		uint32_t dynamicDescriptorCount = descriptorSetLayouts[i]->getDynamicDescriptorCount();
 		ASSERT_OR_RETURN((dynamicOffsetBase + dynamicDescriptorCount) <= MAX_DESCRIPTOR_SET_COMBINED_BUFFERS_DYNAMIC);
-		dynamicOffsetBases[i] = dynamicOffsetBase;
+		dynamicOffsetBaseIndices[i] = dynamicOffsetBase;
 		dynamicOffsetBase += dynamicDescriptorCount;
 	}
 }
 
 void PipelineLayout::destroy(const VkAllocationCallbacks *pAllocator)
 {
-	vk::deallocate(setLayouts, pAllocator);  // pushConstantRanges are in the same allocation
+	vk::deallocate(descriptorSetLayouts, pAllocator);  // pushConstantRanges are in the same allocation
 }
 
 size_t PipelineLayout::ComputeRequiredAllocationSize(const VkPipelineLayoutCreateInfo *pCreateInfo)
 {
-	return (pCreateInfo->setLayoutCount * sizeof(DescriptorSetLayout *)) +
-	       (pCreateInfo->pushConstantRangeCount * sizeof(VkPushConstantRange)) +
-	       (pCreateInfo->setLayoutCount * sizeof(uint32_t));  // dynamicOffsetBases
+	return (pCreateInfo->setLayoutCount * sizeof(DescriptorSetLayout *)) +        // descriptorSetLayouts[]
+	       (pCreateInfo->pushConstantRangeCount * sizeof(VkPushConstantRange)) +  // pushConstantRanges[]
+	       (pCreateInfo->setLayoutCount * sizeof(uint32_t));                      // dynamicOffsetBaseIndices[]
 }
 
-size_t PipelineLayout::getNumDescriptorSets() const
+size_t PipelineLayout::getDescriptorSetCount() const
 {
-	return setLayoutCount;
+	return descriptorSetCount;
+}
+
+uint32_t PipelineLayout::getDynamicDescriptorCount(uint32_t setNumber) const
+{
+	return getDescriptorSetLayout(setNumber)->getDynamicDescriptorCount();
+}
+
+uint32_t PipelineLayout::getDynamicOffsetBaseIndex(uint32_t setNumber) const
+{
+	return dynamicOffsetBaseIndices[setNumber];
+}
+
+uint32_t PipelineLayout::getDynamicOffsetIndex(uint32_t setNumber, uint32_t bindingNumber) const
+{
+	return getDynamicOffsetBaseIndex(setNumber) + getDescriptorSetLayout(setNumber)->getDynamicOffsetIndex(bindingNumber);
+}
+
+uint32_t PipelineLayout::getBindingOffset(uint32_t setNumber, uint32_t bindingNumber) const
+{
+	return getDescriptorSetLayout(setNumber)->getBindingOffset(bindingNumber);
+}
+
+VkDescriptorType PipelineLayout::getDescriptorType(uint32_t setNumber, uint32_t bindingNumber) const
+{
+	return getDescriptorSetLayout(setNumber)->getDescriptorType(bindingNumber);
+}
+
+uint32_t PipelineLayout::getDescriptorSize(uint32_t setNumber, uint32_t bindingNumber) const
+{
+	return DescriptorSetLayout::GetDescriptorSize(getDescriptorType(setNumber, bindingNumber));
+}
+
+bool PipelineLayout::isDescriptorDynamic(uint32_t setNumber, uint32_t bindingNumber) const
+{
+	return DescriptorSetLayout::IsDescriptorDynamic(getDescriptorType(setNumber, bindingNumber));
 }
 
 DescriptorSetLayout const *PipelineLayout::getDescriptorSetLayout(size_t descriptorSet) const
 {
-	ASSERT(descriptorSet < setLayoutCount);
-	return setLayouts[descriptorSet];
-}
-
-uint32_t PipelineLayout::getDynamicOffsetBase(size_t descriptorSet) const
-{
-	ASSERT(descriptorSet < setLayoutCount);
-	return dynamicOffsetBases[descriptorSet];
+	ASSERT(descriptorSet < descriptorSetCount);
+	return descriptorSetLayouts[descriptorSet];
 }
 
 }  // namespace vk
diff --git a/src/Vulkan/VkPipelineLayout.hpp b/src/Vulkan/VkPipelineLayout.hpp
index 59a4283..3d7ae29 100644
--- a/src/Vulkan/VkPipelineLayout.hpp
+++ b/src/Vulkan/VkPipelineLayout.hpp
@@ -27,21 +27,29 @@
 
 	static size_t ComputeRequiredAllocationSize(const VkPipelineLayoutCreateInfo *pCreateInfo);
 
-	size_t getNumDescriptorSets() const;
-	DescriptorSetLayout const *getDescriptorSetLayout(size_t descriptorSet) const;
+	size_t getDescriptorSetCount() const;
+	uint32_t getDynamicDescriptorCount(uint32_t setNumber) const;
 
-	// Returns the starting index into the pipeline's dynamic offsets array for
-	// the given descriptor set.
-	uint32_t getDynamicOffsetBase(size_t descriptorSet) const;
+	// Returns the index into the pipeline's dynamic offsets array for
+	// the given descriptor set (and binding number).
+	uint32_t getDynamicOffsetBaseIndex(uint32_t setNumber) const;
+	uint32_t getDynamicOffsetIndex(uint32_t setNumber, uint32_t bindingNumber) const;
+
+	uint32_t getBindingOffset(uint32_t setNumber, uint32_t bindingNumber) const;
+	VkDescriptorType getDescriptorType(uint32_t setNumber, uint32_t bindingNumber) const;
+	uint32_t getDescriptorSize(uint32_t setNumber, uint32_t bindingNumber) const;
+	bool isDescriptorDynamic(uint32_t setNumber, uint32_t bindingNumber) const;
 
 	const uint32_t identifier;
 
 private:
-	uint32_t setLayoutCount = 0;
-	DescriptorSetLayout **setLayouts = nullptr;
-	uint32_t pushConstantRangeCount = 0;
+	DescriptorSetLayout const *getDescriptorSetLayout(size_t descriptorSet) const;
+
+	const uint32_t descriptorSetCount = 0;
+	const DescriptorSetLayout **descriptorSetLayouts = nullptr;
+	const uint32_t pushConstantRangeCount = 0;
 	VkPushConstantRange *pushConstantRanges = nullptr;
-	uint32_t *dynamicOffsetBases = nullptr;  // Base offset per set layout.
+	uint32_t *dynamicOffsetBaseIndices = nullptr;  // Base index per descriptor set for dynamic buffer offsets.
 };
 
 static inline PipelineLayout *Cast(VkPipelineLayout object)