Merge pull request #783 from mbarriault/master

Support for EXT_inline_uniform_block
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 07b0b9d..999b42e 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -270,6 +270,7 @@
 - `VK_EXT_debug_utils`
 - `VK_EXT_fragment_shader_interlock` *(requires Metal 2.0 and Raster Order Groups)*
 - `VK_EXT_host_query_reset`
+- `VK_EXT_inline_uniform_block`
 - `VK_EXT_memory_budget` *(requires Metal 2.0)*
 - `VK_EXT_metal_surface`
 - `VK_EXT_post_depth_coverage` *(iOS, requires GPU family 4)*
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
index 7fd30e8..705f201 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
@@ -424,6 +424,9 @@
     /** Binds the specified buffer for the specified shader stage. */
     void bindBuffer(MVKShaderStage stage, const MVKMTLBufferBinding& binding);
 
+    /** Binds the specified buffer for the specified shader stage. */
+    void bindInline(MVKShaderStage stage, const MVKMTLInlineBinding& binding);
+
     /** Binds the specified texture for the specified shader stage. */
     void bindTexture(MVKShaderStage stage, const MVKMTLTextureBinding& binding);
 
@@ -458,7 +461,8 @@
                         std::function<void(MVKCommandEncoder*, MVKMTLBufferBinding&)> bindBuffer,
                         std::function<void(MVKCommandEncoder*, MVKMTLBufferBinding&, MVKVector<uint32_t>&)> bindImplicitBuffer,
                         std::function<void(MVKCommandEncoder*, MVKMTLTextureBinding&)> bindTexture,
-                        std::function<void(MVKCommandEncoder*, MVKMTLSamplerStateBinding&)> bindSampler);
+                        std::function<void(MVKCommandEncoder*, MVKMTLSamplerStateBinding&)> bindSampler,
+                        std::function<void(MVKCommandEncoder*, MVKMTLInlineBinding&)> bindInline);
 
 #pragma mark Construction
     
@@ -474,6 +478,7 @@
         MVKVectorInline<MVKMTLBufferBinding, 8> bufferBindings;
         MVKVectorInline<MVKMTLTextureBinding, 8> textureBindings;
         MVKVectorInline<MVKMTLSamplerStateBinding, 8> samplerStateBindings;
+        MVKVectorInline<MVKMTLInlineBinding, 8> inlineBindings;
         MVKVectorInline<uint32_t, 8> swizzleConstants;
         MVKVectorInline<uint32_t, 8> bufferSizes;
         MVKMTLBufferBinding swizzleBufferBinding;
@@ -482,6 +487,7 @@
         bool areBufferBindingsDirty = false;
         bool areTextureBindingsDirty = false;
         bool areSamplerStateBindingsDirty = false;
+        bool areInlineBindingsDirty = false;
 
         bool needsSwizzle = false;
     };
@@ -501,6 +507,9 @@
     /** Binds the specified buffer. */
     void bindBuffer(const MVKMTLBufferBinding& binding);
 
+    /** Binds the specified buffer. */
+    void bindInline(const MVKMTLInlineBinding& binding);
+
     /** Binds the specified texture. */
     void bindTexture(const MVKMTLTextureBinding& binding);
 
@@ -526,6 +535,7 @@
     MVKVectorInline<MVKMTLBufferBinding, 4> _bufferBindings;
     MVKVectorInline<MVKMTLTextureBinding, 4> _textureBindings;
     MVKVectorInline<MVKMTLSamplerStateBinding, 4> _samplerStateBindings;
+    MVKVectorInline<MVKMTLInlineBinding, 4> _inlineBindings;
     MVKVectorInline<uint32_t, 4> _swizzleConstants;
     MVKVectorInline<uint32_t, 4> _bufferSizes;
     MVKMTLBufferBinding _swizzleBufferBinding;
@@ -534,6 +544,7 @@
     bool _areBufferBindingsDirty = false;
     bool _areTextureBindingsDirty = false;
     bool _areSamplerStateBindingsDirty = false;
+    bool _areInlineBindingsDirty = false;
 
     bool _needsSwizzle = false;
 };
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
index 42ff1a1..2ffb9fc 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
@@ -516,6 +516,10 @@
     bind(binding, _shaderStages[stage].samplerStateBindings, _shaderStages[stage].areSamplerStateBindingsDirty);
 }
 
+void MVKGraphicsResourcesCommandEncoderState::bindInline(MVKShaderStage stage, const MVKMTLInlineBinding& binding) {
+    bind(binding, _shaderStages[stage].inlineBindings, _shaderStages[stage].areInlineBindingsDirty);
+}
+
 void MVKGraphicsResourcesCommandEncoderState::bindSwizzleBuffer(const MVKShaderImplicitRezBinding& binding,
 																bool needVertexSwizzleBuffer,
 																bool needTessCtlSwizzleBuffer,
@@ -550,9 +554,11 @@
                                                              std::function<void(MVKCommandEncoder*, MVKMTLBufferBinding&)> bindBuffer,
                                                              std::function<void(MVKCommandEncoder*, MVKMTLBufferBinding&, MVKVector<uint32_t>&)> bindImplicitBuffer,
                                                              std::function<void(MVKCommandEncoder*, MVKMTLTextureBinding&)> bindTexture,
-                                                             std::function<void(MVKCommandEncoder*, MVKMTLSamplerStateBinding&)> bindSampler) {
+                                                             std::function<void(MVKCommandEncoder*, MVKMTLSamplerStateBinding&)> bindSampler,
+                                                             std::function<void(MVKCommandEncoder*, MVKMTLInlineBinding&)> bindInline) {
     auto& shaderStage = _shaderStages[stage];
     encodeBinding<MVKMTLBufferBinding>(shaderStage.bufferBindings, shaderStage.areBufferBindingsDirty, bindBuffer);
+    encodeBinding<MVKMTLInlineBinding>(shaderStage.inlineBindings, shaderStage.areInlineBindingsDirty, bindInline);
 
     if (shaderStage.swizzleBufferBinding.isDirty) {
 
@@ -585,6 +591,7 @@
         MVKResourcesCommandEncoderState::markDirty(_shaderStages[i].bufferBindings, _shaderStages[i].areBufferBindingsDirty);
         MVKResourcesCommandEncoderState::markDirty(_shaderStages[i].textureBindings, _shaderStages[i].areTextureBindingsDirty);
         MVKResourcesCommandEncoderState::markDirty(_shaderStages[i].samplerStateBindings, _shaderStages[i].areSamplerStateBindingsDirty);
+        MVKResourcesCommandEncoderState::markDirty(_shaderStages[i].inlineBindings, _shaderStages[i].areInlineBindingsDirty);
     }
 }
 
@@ -614,6 +621,12 @@
                        [](MVKCommandEncoder* cmdEncoder, MVKMTLSamplerStateBinding& b)->void {
                            [cmdEncoder->_mtlRenderEncoder setVertexSamplerState: b.mtlSamplerState
                                                                         atIndex: b.index];
+                       },
+                       [](MVKCommandEncoder* cmdEncoder, MVKMTLInlineBinding& b)->void {
+                           cmdEncoder->setVertexBytes(cmdEncoder->_mtlRenderEncoder,
+                                                      b.mtlBytes,
+                                                      b.size,
+                                                      b.index);
                        });
 
     }
@@ -638,6 +651,12 @@
                        [](MVKCommandEncoder* cmdEncoder, MVKMTLSamplerStateBinding& b)->void {
                            [cmdEncoder->getMTLComputeEncoder(kMVKCommandUseTessellationControl) setSamplerState: b.mtlSamplerState
                                                                                                         atIndex: b.index];
+                       },
+                       [](MVKCommandEncoder* cmdEncoder, MVKMTLInlineBinding& b)->void {
+                           cmdEncoder->setComputeBytes(cmdEncoder->getMTLComputeEncoder(kMVKCommandUseTessellationControl),
+                                                       b.mtlBytes,
+                                                       b.size,
+                                                       b.index);
                        });
 
     }
@@ -662,6 +681,12 @@
                        [](MVKCommandEncoder* cmdEncoder, MVKMTLSamplerStateBinding& b)->void {
                            [cmdEncoder->_mtlRenderEncoder setVertexSamplerState: b.mtlSamplerState
                                                                         atIndex: b.index];
+                       },
+                       [](MVKCommandEncoder* cmdEncoder, MVKMTLInlineBinding& b)->void {
+                           cmdEncoder->setVertexBytes(cmdEncoder->_mtlRenderEncoder,
+                                                      b.mtlBytes,
+                                                      b.size,
+                                                      b.index);
                        });
 
     }
@@ -686,6 +711,12 @@
                        [](MVKCommandEncoder* cmdEncoder, MVKMTLSamplerStateBinding& b)->void {
                            [cmdEncoder->_mtlRenderEncoder setFragmentSamplerState: b.mtlSamplerState
                                                                           atIndex: b.index];
+                       },
+                       [](MVKCommandEncoder* cmdEncoder, MVKMTLInlineBinding& b)->void {
+                           cmdEncoder->setFragmentBytes(cmdEncoder->_mtlRenderEncoder,
+                                                        b.mtlBytes,
+                                                        b.size,
+                                                        b.index);
                        });
     }
 }
@@ -695,12 +726,14 @@
         _shaderStages[i].bufferBindings.clear();
         _shaderStages[i].textureBindings.clear();
         _shaderStages[i].samplerStateBindings.clear();
+        _shaderStages[i].inlineBindings.clear();
         _shaderStages[i].swizzleConstants.clear();
         _shaderStages[i].bufferSizes.clear();
 
         _shaderStages[i].areBufferBindingsDirty = false;
         _shaderStages[i].areTextureBindingsDirty = false;
         _shaderStages[i].areSamplerStateBindingsDirty = false;
+        _shaderStages[i].areInlineBindingsDirty = false;
         _shaderStages[i].swizzleBufferBinding.isDirty = false;
         _shaderStages[i].bufferSizeBufferBinding.isDirty = false;
 
@@ -724,6 +757,10 @@
     bind(binding, _samplerStateBindings, _areSamplerStateBindingsDirty);
 }
 
+void MVKComputeResourcesCommandEncoderState::bindInline(const MVKMTLInlineBinding& binding) {
+    bind(binding, _inlineBindings, _areInlineBindingsDirty);
+}
+
 void MVKComputeResourcesCommandEncoderState::bindSwizzleBuffer(const MVKShaderImplicitRezBinding& binding,
 															   bool needSwizzleBuffer) {
     _swizzleBufferBinding.index = binding.stages[kMVKShaderStageCompute];
@@ -742,6 +779,7 @@
     MVKResourcesCommandEncoderState::markDirty(_bufferBindings, _areBufferBindingsDirty);
     MVKResourcesCommandEncoderState::markDirty(_textureBindings, _areTextureBindingsDirty);
     MVKResourcesCommandEncoderState::markDirty(_samplerStateBindings, _areSamplerStateBindingsDirty);
+    MVKResourcesCommandEncoderState::markDirty(_inlineBindings, _areInlineBindingsDirty);
 }
 
 void MVKComputeResourcesCommandEncoderState::encodeImpl(uint32_t) {
@@ -758,6 +796,14 @@
 																									   atIndex: b.index];
                                        });
 
+    encodeBinding<MVKMTLInlineBinding>(_inlineBindings, _areInlineBindingsDirty,
+                                      [](MVKCommandEncoder* cmdEncoder, MVKMTLInlineBinding& b)->void {
+                                          cmdEncoder->setComputeBytes(cmdEncoder->getMTLComputeEncoder(kMVKCommandUseDispatch),
+                                                                      b.mtlBytes,
+                                                                      b.size,
+                                                                      b.index);
+                                      });
+
     if (_swizzleBufferBinding.isDirty) {
 
 		for (auto& b : _textureBindings) {
@@ -802,6 +848,7 @@
     _bufferBindings.clear();
     _textureBindings.clear();
     _samplerStateBindings.clear();
+    _inlineBindings.clear();
     _swizzleConstants.clear();
     _bufferSizes.clear();
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h b/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
index cfdf82c..5d77bfd 100644
--- a/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
+++ b/MoltenVK/MoltenVK/Commands/MVKMTLResourceBindings.h
@@ -51,3 +51,12 @@
     MTLIndexType mtlIndexType;
     bool isDirty = true;
 } MVKIndexMTLBufferBinding;
+
+/** Describes host bytes resource binding. */
+typedef struct {
+    union { const void* mtlBytes = nil; const void* mtlResource; }; // aliases
+    uint32_t index = 0;
+    uint32_t size = 0;
+    bool isDirty = true;
+} MVKMTLInlineBinding;
+
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index 9f03ef4..9fffe70 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -253,7 +253,8 @@
 						  VkDescriptorType& descType,
 						  VkDescriptorImageInfo* pImageInfo,
 						  VkDescriptorBufferInfo* pBufferInfo,
-						  VkBufferView* pTexelBufferView);
+						  VkBufferView* pTexelBufferView,
+						  VkWriteDescriptorSetInlineUniformBlockEXT* inlineUniformBlock);
 
     /** Returns whether this instance represents the specified Vulkan binding point. */
     bool hasBinding(uint32_t binding);
@@ -274,6 +275,7 @@
 	MVKDescriptorSetLayoutBinding* _pBindingLayout;
 	std::vector<VkDescriptorImageInfo> _imageBindings;
 	std::vector<VkDescriptorBufferInfo> _bufferBindings;
+    std::vector<VkWriteDescriptorSetInlineUniformBlockEXT> _inlineBindings;
 	std::vector<VkBufferView> _texelBufferBindings;
 	std::vector<id<MTLBuffer>> _mtlBuffers;
 	std::vector<NSUInteger> _mtlBufferOffsets;
@@ -311,7 +313,8 @@
 							VkDescriptorType& descType,
 							VkDescriptorImageInfo* pImageInfo,
 							VkDescriptorBufferInfo* pBufferInfo,
-							VkBufferView* pTexelBufferView);
+							VkBufferView* pTexelBufferView,
+							VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock);
 
 	MVKDescriptorSet(MVKDevice* device) : MVKVulkanAPIDeviceObject(device) {}
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index 1f97ac0..fc35927 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -90,6 +90,7 @@
     MVKMTLBufferBinding bb;
     MVKMTLTextureBinding tb;
     MVKMTLSamplerStateBinding sb;
+    MVKMTLInlineBinding ib;
     NSUInteger bufferDynamicOffset = 0;
 
     // Establish the resource indices to use, by combining the offsets of the DSL and this DSL binding.
@@ -122,6 +123,22 @@
                 break;
             }
 
+            case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT: {
+                ib.mtlBytes = descBinding._inlineBindings[rezIdx].pData;
+                ib.size = descBinding._inlineBindings[rezIdx].dataSize;
+                for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
+                    if (_applyToStage[i]) {
+                        ib.index = mtlIdxs.stages[i].bufferIndex + rezIdx;
+                        if (i == kMVKShaderStageCompute) {
+                            if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindInline(ib); }
+                        } else {
+                            if (cmdEncoder) { cmdEncoder->_graphicsResourcesState.bindInline(MVKShaderStage(i), ib); }
+                        }
+                    }
+                }
+                break;
+            }
+
             case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
             case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
             case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
@@ -208,6 +225,7 @@
     MVKMTLBufferBinding bb;
     MVKMTLTextureBinding tb;
     MVKMTLSamplerStateBinding sb;
+    MVKMTLInlineBinding ib;
 
     if (dstArrayElement >= _info.descriptorCount) {
         dstArrayElement -= _info.descriptorCount;
@@ -255,6 +273,23 @@
                 break;
             }
 
+            case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT: {
+                const auto& inlineUniformBlock = get<VkWriteDescriptorSetInlineUniformBlockEXT>(pData, stride, rezIdx - dstArrayElement);
+                ib.mtlBytes = inlineUniformBlock.pData;
+                ib.size = inlineUniformBlock.dataSize;
+                for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
+                    if (_applyToStage[i]) {
+                        ib.index = mtlIdxs.stages[i].bufferIndex + rezIdx;
+                        if (i == kMVKShaderStageCompute) {
+                            if (cmdEncoder) { cmdEncoder->_computeResourcesState.bindInline(ib); }
+                        } else {
+                            if (cmdEncoder) { cmdEncoder->_graphicsResourcesState.bindInline(MVKShaderStage(i), ib); }
+                        }
+                    }
+                }
+                break;
+            }
+
             case VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE:
             case VK_DESCRIPTOR_TYPE_STORAGE_IMAGE:
             case VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT: {
@@ -499,6 +534,7 @@
         case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER:
         case VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC:
         case VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC:
+        case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
             pBindingIndexes->bufferIndex = pDescSetCounts->bufferIndex;
             pDescSetCounts->bufferIndex += pBinding->descriptorCount;
             break;
@@ -532,6 +568,7 @@
 
 static const void* getWriteParameters(VkDescriptorType type, const VkDescriptorImageInfo* pImageInfo,
                                       const VkDescriptorBufferInfo* pBufferInfo, const VkBufferView* pTexelBufferView,
+                                      const VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock,
                                       size_t& stride) {
     const void* pData;
     switch (type) {
@@ -558,6 +595,11 @@
         stride = sizeof(MVKBufferView*);
         break;
 
+    case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
+        pData = pInlineUniformBlock;
+        stride = sizeof(VkWriteDescriptorSetInlineUniformBlockEXT);
+        break;
+
     default:
         pData = nullptr;
         stride = 0;
@@ -580,6 +622,20 @@
         const VkDescriptorImageInfo* pImageInfo = descWrite.pImageInfo;
         const VkDescriptorBufferInfo* pBufferInfo = descWrite.pBufferInfo;
         const VkBufferView* pTexelBufferView = descWrite.pTexelBufferView;
+        const VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock = nullptr;
+        if (_device->_enabledExtensions.vk_EXT_inline_uniform_block.enabled) {
+            for (auto* next = (VkWriteDescriptorSetInlineUniformBlockEXT*)descWrite.pNext; next; next = (VkWriteDescriptorSetInlineUniformBlockEXT*)next->pNext)
+            {
+                switch (next->sType) {
+                case VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT: {
+                    pInlineUniformBlock = next;
+                    break;
+                }
+                default:
+                    break;
+                }
+            }
+        }
         if (!_bindingToIndex.count(dstBinding)) continue;
         // Note: This will result in us walking off the end of the array
         // in case there are too many updates... but that's ill-defined anyway.
@@ -587,7 +643,7 @@
             if (!_bindingToIndex.count(dstBinding)) continue;
             size_t stride;
             const void* pData = getWriteParameters(descWrite.descriptorType, pImageInfo,
-                                                   pBufferInfo, pTexelBufferView, stride);
+                                                   pBufferInfo, pTexelBufferView, pInlineUniformBlock, stride);
             uint32_t descriptorsPushed = 0;
             uint32_t bindIdx = _bindingToIndex[dstBinding];
             _bindings[bindIdx].push(cmdEncoder, dstArrayElement, descriptorCount,
@@ -777,6 +833,26 @@
 				}
             }
 			break;
+
+        case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
+            for (uint32_t i = 0; i < dstCnt; i++) {
+                uint32_t dstIdx = dstStartIndex + i;
+                const auto& srcInlineUniformBlock = get<VkWriteDescriptorSetInlineUniformBlockEXT>(pData, stride, srcStartIndex + i);
+                auto& dstInlineUniformBlock = _inlineBindings[dstIdx];
+                if (dstInlineUniformBlock.pData && dstInlineUniformBlock.pData != srcInlineUniformBlock.pData)
+                    delete [] reinterpret_cast<const uint8_t*>(dstInlineUniformBlock.pData);
+                if (srcInlineUniformBlock.dataSize != 0) {
+                    dstInlineUniformBlock.pData = reinterpret_cast<const void*>(new uint8_t*[srcInlineUniformBlock.dataSize]);
+                    if (srcInlineUniformBlock.pData) {
+                        memcpy(const_cast<void*>(dstInlineUniformBlock.pData), srcInlineUniformBlock.pData, srcInlineUniformBlock.dataSize);
+                    }
+                } else {
+                    dstInlineUniformBlock.pData = nullptr;
+                }
+                dstInlineUniformBlock.dataSize = srcInlineUniformBlock.dataSize;
+            }
+            break;
+
 		default:
 			break;
 	}
@@ -790,7 +866,8 @@
 											VkDescriptorType& descType,
 											VkDescriptorImageInfo* pImageInfo,
 											VkDescriptorBufferInfo* pBufferInfo,
-											VkBufferView* pTexelBufferView) {
+											VkBufferView* pTexelBufferView,
+											VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
 
 	uint32_t srcCnt = MIN(count, _pBindingLayout->_info.descriptorCount - srcStartIndex);
 
@@ -822,6 +899,24 @@
 			}
 			break;
 
+		case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT:
+			for (uint32_t i = 0; i < srcCnt; i++) {
+				const auto& srcInlineUniformBlock = _inlineBindings[srcStartIndex + i];
+				auto& dstInlineUniformBlock = pInlineUniformBlock[dstStartIndex + i];
+				if (dstInlineUniformBlock.pData && dstInlineUniformBlock.pData != srcInlineUniformBlock.pData)
+					delete [] reinterpret_cast<const uint8_t*>(dstInlineUniformBlock.pData);
+				if (srcInlineUniformBlock.dataSize != 0) {
+					dstInlineUniformBlock.pData = reinterpret_cast<const void*>(new uint8_t*[srcInlineUniformBlock.dataSize]);
+					if (srcInlineUniformBlock.pData) {
+						memcpy(const_cast<void*>(dstInlineUniformBlock.pData), srcInlineUniformBlock.pData, srcInlineUniformBlock.dataSize);
+					}
+				} else {
+					dstInlineUniformBlock.pData = nullptr;
+				}
+				dstInlineUniformBlock.dataSize = srcInlineUniformBlock.dataSize;
+			}
+			break;
+
 		default:
 			break;
 	}
@@ -830,7 +925,7 @@
 }
 
 bool MVKDescriptorBinding::hasBinding(uint32_t binding) {
-    return _pBindingLayout->_info.binding == binding;
+	return _pBindingLayout->_info.binding == binding;
 }
 
 MVKDescriptorBinding::MVKDescriptorBinding(MVKDescriptorSet* pDescSet, MVKDescriptorSetLayoutBinding* pBindingLayout) : _pDescSet(pDescSet) {
@@ -866,6 +961,12 @@
 			_mtlBufferOffsets.resize(descCnt, 0);
 			break;
 
+		case VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT: {
+			static const VkWriteDescriptorSetInlineUniformBlockEXT inlineUniformBlock {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT, nullptr, 0, nullptr};
+			_inlineBindings.resize(descCnt, inlineUniformBlock);
+			break;
+		}
+
 		case VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER:
 		case VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER:
 			_texelBufferBindings.resize(descCnt, nil);
@@ -902,6 +1003,11 @@
 			((MVKBufferView*)buffView)->release();
 		}
 	}
+	for (VkWriteDescriptorSetInlineUniformBlockEXT& inlineUniformBlock : _inlineBindings) {
+		if (inlineUniformBlock.pData) {
+			delete [] reinterpret_cast<const uint8_t*>(inlineUniformBlock.pData);
+		}
+	}
 }
 
 /**
@@ -956,7 +1062,8 @@
 										  VkDescriptorType& descType,
 										  VkDescriptorImageInfo* pImageInfo,
 										  VkDescriptorBufferInfo* pBufferInfo,
-										  VkBufferView* pTexelBufferView) {
+										  VkBufferView* pTexelBufferView,
+										  VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock) {
 	uint32_t srcStartIdx = pDescriptorCopy->srcArrayElement;
     uint32_t binding = pDescriptorCopy->srcBinding;
     uint32_t origCnt = pDescriptorCopy->descriptorCount;
@@ -966,7 +1073,7 @@
     while (mvkDescBind && remainCnt > 0) {
         uint32_t dstStartIdx = origCnt - remainCnt;
         remainCnt = mvkDescBind->readBindings(srcStartIdx, dstStartIdx, remainCnt, descType,
-                                              pImageInfo, pBufferInfo, pTexelBufferView);
+                                              pImageInfo, pBufferInfo, pTexelBufferView, pInlineUniformBlock);
         binding++;                              // If not consumed, move to next consecutive binding point
         mvkDescBind = getBinding(binding);
         srcStartIdx = 0;                        // Subsequent bindings start reading at first element
@@ -1125,10 +1232,26 @@
 	for (uint32_t i = 0; i < writeCount; i++) {
 		const VkWriteDescriptorSet* pDescWrite = &pDescriptorWrites[i];
 		size_t stride;
+		MVKDescriptorSet* dstSet = (MVKDescriptorSet*)pDescWrite->dstSet;
+
+		const VkWriteDescriptorSetInlineUniformBlockEXT* pInlineUniformBlock = nullptr;
+		if (dstSet->getDevice()->_enabledExtensions.vk_EXT_inline_uniform_block.enabled) {
+			for (auto* next = (VkWriteDescriptorSetInlineUniformBlockEXT*)pDescWrite->pNext; next; next = (VkWriteDescriptorSetInlineUniformBlockEXT*)next->pNext)
+			{
+				switch (next->sType) {
+				case VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_INLINE_UNIFORM_BLOCK_EXT: {
+					pInlineUniformBlock = next;
+					break;
+				}
+				default:
+					break;
+				}
+			}
+	}
+
 		const void* pData = getWriteParameters(pDescWrite->descriptorType, pDescWrite->pImageInfo,
 											   pDescWrite->pBufferInfo, pDescWrite->pTexelBufferView,
-											   stride);
-		MVKDescriptorSet* dstSet = (MVKDescriptorSet*)pDescWrite->dstSet;
+											   pInlineUniformBlock, stride);
 		dstSet->writeDescriptorSets(pDescWrite, stride, pData);
 	}
 
@@ -1141,13 +1264,14 @@
 		VkDescriptorImageInfo imgInfos[descCnt];
 		VkDescriptorBufferInfo buffInfos[descCnt];
 		VkBufferView texelBuffInfos[descCnt];
+		VkWriteDescriptorSetInlineUniformBlockEXT inlineUniformBlocks[descCnt];
 
 		MVKDescriptorSet* srcSet = (MVKDescriptorSet*)pDescCopy->srcSet;
-		srcSet->readDescriptorSets(pDescCopy, descType, imgInfos, buffInfos, texelBuffInfos);
+		srcSet->readDescriptorSets(pDescCopy, descType, imgInfos, buffInfos, texelBuffInfos, inlineUniformBlocks);
 
 		MVKDescriptorSet* dstSet = (MVKDescriptorSet*)pDescCopy->dstSet;
 		size_t stride;
-		const void* pData = getWriteParameters(descType, imgInfos, buffInfos, texelBuffInfos, stride);
+		const void* pData = getWriteParameters(descType, imgInfos, buffInfos, texelBuffInfos, inlineUniformBlocks, stride);
 		dstSet->writeDescriptorSets(pDescCopy, stride, pData);
 	}
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index d68ceb1..c21786d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -143,6 +143,12 @@
                     shaderIntFuncsFeatures->shaderIntegerFunctions2 = true;
                     break;
                 }
+                case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES_EXT: {
+                    auto* inlineUniformBlockFeatures = (VkPhysicalDeviceInlineUniformBlockFeaturesEXT*)next;
+                    inlineUniformBlockFeatures->inlineUniformBlock = true;
+                    inlineUniformBlockFeatures->descriptorBindingInlineUniformBlockUpdateAfterBind = true;
+                    break;
+                }
                 default:
                     break;
             }
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index 29a821d..0f8a992 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -70,6 +70,7 @@
 MVK_EXTENSION(EXT_fragment_shader_interlock, EXT_FRAGMENT_SHADER_INTERLOCK, MVK_EXTENSION_DEVICE)
 MVK_EXTENSION(EXT_hdr_metadata, EXT_HDR_METADATA, MVK_EXTENSION_DEVICE)
 MVK_EXTENSION(EXT_host_query_reset, EXT_HOST_QUERY_RESET, MVK_EXTENSION_DEVICE)
+MVK_EXTENSION(EXT_inline_uniform_block, EXT_INLINE_UNIFORM_BLOCK, MVK_EXTENSION_DEVICE)
 MVK_EXTENSION(EXT_memory_budget, EXT_MEMORY_BUDGET, MVK_EXTENSION_DEVICE)
 MVK_EXTENSION(EXT_metal_surface, EXT_METAL_SURFACE, MVK_EXTENSION_INSTANCE)
 MVK_EXTENSION(EXT_post_depth_coverage, EXT_POST_DEPTH_COVERAGE, MVK_EXTENSION_DEVICE)