Support the VK_KHR_imageless_framebuffer extension.

I'm not sure this is the elegant way but it works in my own
project:)
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 3ee145f..413436c 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -273,6 +273,7 @@
 - `VK_KHR_get_memory_requirements2`
 - `VK_KHR_get_physical_device_properties2`
 - `VK_KHR_get_surface_capabilities2`
+- `VK_KHR_imageless_framebuffer`
 - `VK_KHR_image_format_list`
 - `VK_KHR_maintenance1`
 - `VK_KHR_maintenance2`
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
index 70c7ba2..c5b76f8 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
@@ -50,6 +50,7 @@
 	MVKFramebuffer* _framebuffer;
 	VkRect2D _renderArea;
 	VkSubpassContents _contents;
+	MVKSmallVector<MVKImageView*, 8> _imagelessAttachments;
 };
 
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
index 7f523f1..a987c52 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
@@ -36,6 +36,20 @@
 	_framebuffer = (MVKFramebuffer*)pRenderPassBegin->framebuffer;
 	_renderArea = pRenderPassBegin->renderArea;
 
+	for (auto* next = (const VkBaseInStructure*)pRenderPassBegin->pNext; next; next = next->pNext) {
+		switch (next->sType) {
+		case VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO: {
+			const auto* pAttachmentBegin = (VkRenderPassAttachmentBeginInfo*)next;
+			for(uint32_t i = 0; i < pAttachmentBegin->attachmentCount; i++) {
+				_imagelessAttachments.push_back((MVKImageView*)pAttachmentBegin->pAttachments[i]);
+			}
+			break;
+		}
+		default:
+ 			break;
+		}
+	}
+
 	return VK_SUCCESS;
 }
 
@@ -70,7 +84,7 @@
 template <size_t N>
 void MVKCmdBeginRenderPass<N>::encode(MVKCommandEncoder* cmdEncoder) {
 //	MVKLogDebug("Encoding vkCmdBeginRenderPass(). Elapsed time: %.6f ms.", mvkGetElapsedMilliseconds());
-	cmdEncoder->beginRenderpass(this, _contents, _renderPass, _framebuffer, _renderArea, _clearValues.contents());
+	cmdEncoder->beginRenderpass(this, _contents, _renderPass, _framebuffer, _renderArea, _clearValues.contents(), _imagelessAttachments.contents());
 }
 
 template class MVKCmdBeginRenderPass<1>;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
index 6bc4f4e..9cd4ab5 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
@@ -275,7 +275,8 @@
 						 MVKRenderPass* renderPass,
 						 MVKFramebuffer* framebuffer,
 						 VkRect2D& renderArea,
-						 MVKArrayRef<VkClearValue> clearValues);
+						 MVKArrayRef<VkClearValue> clearValues,
+						 MVKArrayRef<MVKImageView*> imageless_attachments);
 
 	/** Begins the next render subpass. */
 	void beginNextSubpass(MVKCommand* subpassCmd, VkSubpassContents renderpassContents);
@@ -494,6 +495,7 @@
 	VkRect2D _renderArea;
     MVKActivatedQueries* _pActivatedQueries;
 	MVKSmallVector<VkClearValue, kMVKDefaultAttachmentCount> _clearValues;
+	MVKSmallVector<MVKImageView*, kMVKDefaultAttachmentCount> _imagelessAttachments;
 	id<MTLComputeCommandEncoder> _mtlComputeEncoder;
 	MVKCommandUse _mtlComputeEncoderUse;
 	id<MTLBlitCommandEncoder> _mtlBlitEncoder;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
index 2de0f53..54424cd 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
@@ -288,13 +288,17 @@
 										MVKRenderPass* renderPass,
 										MVKFramebuffer* framebuffer,
 										VkRect2D& renderArea,
-										MVKArrayRef<VkClearValue> clearValues) {
+										MVKArrayRef<VkClearValue> clearValues,
+										MVKArrayRef<MVKImageView*> imagelessAttachments) {
 	_renderPass = renderPass;
 	_framebuffer = framebuffer;
 	_renderArea = renderArea;
 	_isRenderingEntireAttachment = (mvkVkOffset2DsAreEqual(_renderArea.offset, {0,0}) &&
 									mvkVkExtent2DsAreEqual(_renderArea.extent, _framebuffer->getExtent2D()));
 	_clearValues.assign(clearValues.begin(), clearValues.end());
+	for(auto* v : imagelessAttachments) {
+		_imagelessAttachments.push_back(v);
+	}
 	setSubpass(passCmd, subpassContents, 0);
 }
 
@@ -334,7 +338,7 @@
     endCurrentMetalEncoding();
 
     MTLRenderPassDescriptor* mtlRPDesc = [MTLRenderPassDescriptor renderPassDescriptor];
-    getSubpass()->populateMTLRenderPassDescriptor(mtlRPDesc, _multiviewPassIndex, _framebuffer, _clearValues.contents(), _isRenderingEntireAttachment, loadOverride);
+	getSubpass()->populateMTLRenderPassDescriptor(mtlRPDesc, _multiviewPassIndex, _framebuffer, _imagelessAttachments.contents(), _clearValues.contents(), _isRenderingEntireAttachment, loadOverride);
     if (_cmdBuffer->_needsVisibilityResultMTLBuffer) {
         if (!_visibilityResultMTLBuffer) {
             _visibilityResultMTLBuffer = getTempMTLBuffer(_pDeviceMetalFeatures->maxQueryBufferSize, true, true);
@@ -393,7 +397,10 @@
 }
 
 void MVKCommandEncoder::encodeStoreActions(bool storeOverride) {
-	getSubpass()->encodeStoreActions(this, _isRenderingEntireAttachment, storeOverride);
+	getSubpass()->encodeStoreActions(this,
+									 _isRenderingEntireAttachment,
+									 _imagelessAttachments.contents(),
+									 storeOverride);
 }
 
 MVKRenderSubpass* MVKCommandEncoder::getSubpass() { return _renderPass->getSubpass(_renderSubpassIndex); }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index b1bd357..8eef871 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -738,6 +738,7 @@
 	const VkPhysicalDeviceTexelBufferAlignmentFeaturesEXT _enabledTexelBuffAlignFeatures;
 	const VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT _enabledVtxAttrDivFeatures;
 	const VkPhysicalDevicePortabilitySubsetFeaturesKHR _enabledPortabilityFeatures;
+	const VkPhysicalDeviceImagelessFramebufferFeaturesKHR _enabledImagelessFramebufferFeatures;
 
 	/** The list of Vulkan extensions, indicating whether each has been enabled by the app for this device. */
 	const MVKExtensionList _enabledExtensions;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index e70de7e..013b251 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -267,6 +267,11 @@
 				inlineUniformBlockFeatures->descriptorBindingInlineUniformBlockUpdateAfterBind = true;
 				break;
 			}
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES: {
+				auto* imagelessFramebufferFeatures = (VkPhysicalDeviceImagelessFramebufferFeaturesKHR*)next;
+				imagelessFramebufferFeatures->imagelessFramebuffer = true;
+				break;
+			}
 			default:
 				break;
 		}
@@ -3789,6 +3794,7 @@
 	_enabledVtxAttrDivFeatures(),
 	_enabledPrivateDataFeatures(),
 	_enabledPortabilityFeatures(),
+	_enabledImagelessFramebufferFeatures(),
 	_enabledExtensions(this),
 	_isCurrentlyAutoGPUCapturing(false)
 {
@@ -3899,11 +3905,16 @@
 	mvkClear(&_enabledTexelBuffAlignFeatures);
 	mvkClear(&_enabledVtxAttrDivFeatures);
 	mvkClear(&_enabledPortabilityFeatures);
+	mvkClear(&_enabledImagelessFramebufferFeatures);
 
+	VkPhysicalDeviceImagelessFramebufferFeaturesKHR pdImagelessFramebufferFeatures;
+	pdImagelessFramebufferFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES;
+	pdImagelessFramebufferFeatures.pNext = NULL;
+    
 	// Fetch the available physical device features.
 	VkPhysicalDevicePortabilitySubsetFeaturesKHR pdPortabilityFeatures;
 	pdPortabilityFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PORTABILITY_SUBSET_FEATURES_KHR;
-	pdPortabilityFeatures.pNext = NULL;
+	pdPortabilityFeatures.pNext = &pdImagelessFramebufferFeatures;
 
 	VkPhysicalDeviceVertexAttributeDivisorFeaturesEXT pdVtxAttrDivFeatures;
 	pdVtxAttrDivFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VERTEX_ATTRIBUTE_DIVISOR_FEATURES_EXT;
@@ -4088,6 +4099,13 @@
 							   &pdPortabilityFeatures.constantAlphaColorBlendFactors, 15);
 				break;
 			}
+			case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGELESS_FRAMEBUFFER_FEATURES: {
+				auto* requestedFeatures = (VkPhysicalDeviceImagelessFramebufferFeaturesKHR*)next;
+				enableFeatures(&_enabledImagelessFramebufferFeatures.imagelessFramebuffer,
+							   &requestedFeatures->imagelessFramebuffer,
+							   &pdImagelessFramebufferFeatures.imagelessFramebuffer, 1);
+				break;
+			}
 			default:
 				break;
 		}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h
index 16a2a22..0fc06b4 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h
@@ -45,6 +45,7 @@
 	/** Returns the attachment at the specified index.  */
 	inline MVKImageView* getAttachment(uint32_t index) { return _attachments[index]; }
 
+	inline bool getImageless() { return _imageless; }
 
 #pragma mark Construction
 
@@ -57,5 +58,7 @@
 	VkExtent2D _extent;
 	uint32_t _layerCount;
 	MVKSmallVector<MVKImageView*, 4> _attachments;
+	bool _imageless;
+	MVKSmallVector<MVKImageView*, 4> _imagelessAttachments;
 };
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm
index 66af186..18d442a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm
@@ -28,10 +28,15 @@
     _extent = { .width = pCreateInfo->width, .height = pCreateInfo->height };
 	_layerCount = pCreateInfo->layers;
 
-	// Add attachments
-	_attachments.reserve(pCreateInfo->attachmentCount);
-	for (uint32_t i = 0; i < pCreateInfo->attachmentCount; i++) {
-		_attachments.push_back((MVKImageView*)pCreateInfo->pAttachments[i]);
+	if (pCreateInfo->flags & VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT_KHR) {
+		_imageless = true;
+	}
+	else {
+		_imageless = false;
+		// Add attachments
+		_attachments.reserve(pCreateInfo->attachmentCount);
+		for (uint32_t i = 0; i < pCreateInfo->attachmentCount; i++) {
+			_attachments.push_back((MVKImageView*)pCreateInfo->pAttachments[i]);
+		}
 	}
 }
-
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
index fdb9077..8c0a89f 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
@@ -96,6 +96,7 @@
 	void populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* mtlRPDesc,
 										 uint32_t passIdx,
 										 MVKFramebuffer* framebuffer,
+										 const MVKArrayRef<MVKImageView*>& imagelessAttachments,
 										 const MVKArrayRef<VkClearValue>& clearValues,
 										 bool isRenderingEntireAttachment,
                                          bool loadOverride = false);
@@ -117,7 +118,10 @@
 									 uint32_t caIdx, VkImageAspectFlags aspectMask);
 
 	/** If a render encoder is active, sets the store actions for all attachments to it. */
-	void encodeStoreActions(MVKCommandEncoder* cmdEncoder, bool isRenderingEntireAttachment, bool storeOverride = false);
+	void encodeStoreActions(MVKCommandEncoder* cmdEncoder,
+							bool isRenderingEntireAttachment,
+							const MVKArrayRef<MVKImageView*>& imagelessAttachments,
+							bool storeOverride = false);
 
 	/** Constructs an instance for the specified parent renderpass. */
 	MVKRenderSubpass(MVKRenderPass* renderPass, const VkSubpassDescription* pCreateInfo,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
index 4a09569..0b8f2a9 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
@@ -176,10 +176,12 @@
 void MVKRenderSubpass::populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* mtlRPDesc,
 													   uint32_t passIdx,
 													   MVKFramebuffer* framebuffer,
+													   const MVKArrayRef<MVKImageView*>& imagelessAttachments,
 													   const MVKArrayRef<VkClearValue>& clearValues,
 													   bool isRenderingEntireAttachment,
 													   bool loadOverride) {
 	MVKPixelFormats* pixFmts = _renderPass->getPixelFormats();
+	bool imageless = framebuffer->getImageless();
 
 	// Populate the Metal color attachments
 	uint32_t caCnt = getColorAttachmentCount();
@@ -195,7 +197,13 @@
             uint32_t rslvRPAttIdx = _resolveAttachments.empty() ? VK_ATTACHMENT_UNUSED : _resolveAttachments[caIdx].attachment;
             bool hasResolveAttachment = (rslvRPAttIdx != VK_ATTACHMENT_UNUSED);
             if (hasResolveAttachment) {
-                framebuffer->getAttachment(rslvRPAttIdx)->populateMTLRenderPassAttachmentDescriptorResolve(mtlColorAttDesc);
+				if (imageless) {
+					imagelessAttachments[rslvRPAttIdx]->populateMTLRenderPassAttachmentDescriptorResolve(mtlColorAttDesc);
+				}
+				else {
+					framebuffer->getAttachment(rslvRPAttIdx)->populateMTLRenderPassAttachmentDescriptorResolve(mtlColorAttDesc);
+				}
+
 				// In a multiview render pass, we need to override the starting layer to ensure
 				// only the enabled views are loaded.
 				if (isMultiview()) {
@@ -209,10 +217,17 @@
 
             // Configure the color attachment
             MVKRenderPassAttachment* clrMVKRPAtt = &_renderPass->_attachments[clrRPAttIdx];
-			framebuffer->getAttachment(clrRPAttIdx)->populateMTLRenderPassAttachmentDescriptor(mtlColorAttDesc);
+			if (imageless) {
+				imagelessAttachments[clrRPAttIdx]->populateMTLRenderPassAttachmentDescriptor(mtlColorAttDesc);
+			}
+			else {
+				framebuffer->getAttachment(clrRPAttIdx)->populateMTLRenderPassAttachmentDescriptor(mtlColorAttDesc);
+			}
 			bool isMemorylessAttachment = false;
 #if MVK_APPLE_SILICON
-			isMemorylessAttachment = framebuffer->getAttachment(clrRPAttIdx)->getMTLTexture(0).storageMode == MTLStorageModeMemoryless;
+			isMemorylessAttachment = imageless
+									 ? imagelessAttachments[clrRPAttIdx]->getMTLTexture(0).storageMode == MTLStorageModeMemoryless
+									 : framebuffer->getAttachment(clrRPAttIdx)->getMTLTexture(0).storageMode == MTLStorageModeMemoryless;
 #endif
 			if (clrMVKRPAtt->populateMTLRenderPassAttachmentDescriptor(mtlColorAttDesc, this,
                                                                        isRenderingEntireAttachment,
@@ -236,12 +251,16 @@
 	uint32_t dsRslvRPAttIdx = _depthStencilResolveAttachment.attachment;
 	if (dsRPAttIdx != VK_ATTACHMENT_UNUSED) {
 		MVKRenderPassAttachment* dsMVKRPAtt = &_renderPass->_attachments[dsRPAttIdx];
-		MVKImageView* dsImage = framebuffer->getAttachment(dsRPAttIdx);
+		MVKImageView* dsImage = imageless
+								? imagelessAttachments[dsRPAttIdx]
+								: framebuffer->getAttachment(dsRPAttIdx);
 		MVKImageView* dsRslvImage = nullptr;
 		MTLPixelFormat mtlDSFormat = dsImage->getMTLPixelFormat(0);
 
 		if (dsRslvRPAttIdx != VK_ATTACHMENT_UNUSED) {
-			dsRslvImage = framebuffer->getAttachment(dsRslvRPAttIdx);
+			dsRslvImage = imageless
+						  ? imagelessAttachments[dsRslvRPAttIdx]
+						  : framebuffer->getAttachment(dsRslvRPAttIdx);
 		}
 
 		if (pixFmts->isDepthFormat(mtlDSFormat)) {
@@ -366,6 +385,7 @@
 
 void MVKRenderSubpass::encodeStoreActions(MVKCommandEncoder* cmdEncoder,
                                           bool isRenderingEntireAttachment,
+										  const MVKArrayRef<MVKImageView*>& imagelessAttachments,
                                           bool storeOverride) {
     if (!cmdEncoder->_mtlRenderEncoder) { return; }
 	if (!_renderPass->getDevice()->_pMetalFeatures->deferredStoreActions) { return; }
@@ -377,7 +397,9 @@
             bool hasResolveAttachment = _resolveAttachments.empty() ? false : _resolveAttachments[caIdx].attachment != VK_ATTACHMENT_UNUSED;
 			bool isMemorylessAttachment = false;
 #if MVK_APPLE_SILICON
-			isMemorylessAttachment = cmdEncoder->_framebuffer->getAttachment(clrRPAttIdx)->getMTLTexture(0).storageMode == MTLStorageModeMemoryless;
+			isMemorylessAttachment = cmdEncoder->_framebuffer->getImageless()
+									 ? imagelessAttachments[clrRPAttIdx]->getMTLTexture(0).storageMode == MTLStorageModeMemoryless
+									 : cmdEncoder->_framebuffer->getAttachment(clrRPAttIdx)->getMTLTexture(0).storageMode == MTLStorageModeMemoryless;
 #endif
             _renderPass->_attachments[clrRPAttIdx].encodeStoreAction(cmdEncoder, this, isRenderingEntireAttachment, isMemorylessAttachment, hasResolveAttachment, caIdx, false, storeOverride);
         }
@@ -389,7 +411,9 @@
         bool hasStencilResolveAttachment = hasResolveAttachment && _stencilResolveMode != VK_RESOLVE_MODE_NONE;
 		bool isMemorylessAttachment = false;
 #if MVK_APPLE_SILICON
-		isMemorylessAttachment = cmdEncoder->_framebuffer->getAttachment(dsRPAttIdx)->getMTLTexture(0).storageMode == MTLStorageModeMemoryless;
+		isMemorylessAttachment = cmdEncoder->_framebuffer->getImageless()
+								 ? imagelessAttachments[dsRPAttIdx]->getMTLTexture(0).storageMode == MTLStorageModeMemoryless
+								 : cmdEncoder->_framebuffer->getAttachment(dsRPAttIdx)->getMTLTexture(0).storageMode == MTLStorageModeMemoryless;
 #endif
         _renderPass->_attachments[dsRPAttIdx].encodeStoreAction(cmdEncoder, this, isRenderingEntireAttachment, isMemorylessAttachment, hasDepthResolveAttachment, 0, false, storeOverride);
         _renderPass->_attachments[dsRPAttIdx].encodeStoreAction(cmdEncoder, this, isRenderingEntireAttachment, isMemorylessAttachment, hasStencilResolveAttachment, 0, true, storeOverride);
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index 5fb1c18..a80de24 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -58,6 +58,7 @@
 MVK_EXTENSION(KHR_get_memory_requirements2, KHR_GET_MEMORY_REQUIREMENTS_2, DEVICE)
 MVK_EXTENSION(KHR_get_physical_device_properties2, KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2, INSTANCE)
 MVK_EXTENSION(KHR_get_surface_capabilities2, KHR_GET_SURFACE_CAPABILITIES_2, INSTANCE)
+MVK_EXTENSION(KHR_imageless_framebuffer, KHR_IMAGELESS_FRAMEBUFFER, DEVICE)
 MVK_EXTENSION(KHR_image_format_list, KHR_IMAGE_FORMAT_LIST, DEVICE)
 MVK_EXTENSION(KHR_maintenance1, KHR_MAINTENANCE1, DEVICE)
 MVK_EXTENSION(KHR_maintenance2, KHR_MAINTENANCE2, DEVICE)