From a723dc1f21cf5d2bb5f25807c52b369bb4f46995 Mon Sep 17 00:00:00 2001 From: UnsafeBy Date: Fri, 28 May 2021 23:28:20 +0800 Subject: [PATCH] Support the VK_KHR_imageless_framebuffer extension. I'm not sure this is the elegant way but it works in my own project:) --- Docs/MoltenVK_Runtime_UserGuide.md | 1 + MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h | 1 + .../MoltenVK/Commands/MVKCmdRenderPass.mm | 16 +++++++- MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h | 4 +- .../MoltenVK/Commands/MVKCommandBuffer.mm | 13 +++++-- MoltenVK/MoltenVK/GPUObjects/MVKDevice.h | 1 + MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm | 20 +++++++++- MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h | 3 ++ .../MoltenVK/GPUObjects/MVKFramebuffer.mm | 15 +++++--- MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h | 6 ++- MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm | 38 +++++++++++++++---- MoltenVK/MoltenVK/Layers/MVKExtensions.def | 1 + 12 files changed, 100 insertions(+), 19 deletions(-) diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md index 3ee145f6..413436cc 100644 --- a/Docs/MoltenVK_Runtime_UserGuide.md +++ b/Docs/MoltenVK_Runtime_UserGuide.md @@ -273,6 +273,7 @@ In addition to core *Vulkan* functionality, **MoltenVK** also supports the foll - `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 70c7ba2d..c5b76f88 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h +++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h @@ -50,6 +50,7 @@ protected: MVKFramebuffer* _framebuffer; VkRect2D _renderArea; VkSubpassContents _contents; + MVKSmallVector _imagelessAttachments; }; diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm index 7f523f10..a987c52a 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm @@ -36,6 +36,20 @@ VkResult MVKCmdBeginRenderPassBase::setContent(MVKCommandBuffer* cmdBuff, _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 @@ VkResult MVKCmdBeginRenderPass::setContent(MVKCommandBuffer* cmdBuff, template void MVKCmdBeginRenderPass::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 6bc4f4e5..9cd4ab54 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h +++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h @@ -275,7 +275,8 @@ public: MVKRenderPass* renderPass, MVKFramebuffer* framebuffer, VkRect2D& renderArea, - MVKArrayRef clearValues); + MVKArrayRef clearValues, + MVKArrayRef imageless_attachments); /** Begins the next render subpass. */ void beginNextSubpass(MVKCommand* subpassCmd, VkSubpassContents renderpassContents); @@ -494,6 +495,7 @@ protected: VkRect2D _renderArea; MVKActivatedQueries* _pActivatedQueries; MVKSmallVector _clearValues; + MVKSmallVector _imagelessAttachments; id _mtlComputeEncoder; MVKCommandUse _mtlComputeEncoderUse; id _mtlBlitEncoder; diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm index 2de0f531..54424cd0 100644 --- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm +++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm @@ -288,13 +288,17 @@ void MVKCommandEncoder::beginRenderpass(MVKCommand* passCmd, MVKRenderPass* renderPass, MVKFramebuffer* framebuffer, VkRect2D& renderArea, - MVKArrayRef clearValues) { + MVKArrayRef clearValues, + MVKArrayRef 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 @@ void MVKCommandEncoder::beginMetalRenderPass(bool loadOverride) { 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::beginMetalRenderPass(bool loadOverride) { } 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 b1bd3577..8eef871d 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h @@ -738,6 +738,7 @@ public: 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 e70de7e3..013b2518 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm @@ -267,6 +267,11 @@ void MVKPhysicalDevice::getFeatures(VkPhysicalDeviceFeatures2* features) { 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 @@ MVKDevice::MVKDevice(MVKPhysicalDevice* physicalDevice, const VkDeviceCreateInfo _enabledVtxAttrDivFeatures(), _enabledPrivateDataFeatures(), _enabledPortabilityFeatures(), + _enabledImagelessFramebufferFeatures(), _enabledExtensions(this), _isCurrentlyAutoGPUCapturing(false) { @@ -3899,11 +3905,16 @@ void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) { 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 @@ void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) { &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 16a2a22c..0fc06b41 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h @@ -45,6 +45,7 @@ public: /** 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 @@ protected: VkExtent2D _extent; uint32_t _layerCount; MVKSmallVector _attachments; + bool _imageless; + MVKSmallVector _imagelessAttachments; }; diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm index 66af186c..18d442a4 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm @@ -28,10 +28,15 @@ MVKFramebuffer::MVKFramebuffer(MVKDevice* device, _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 fdb90771..8c0a89f1 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h +++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h @@ -96,6 +96,7 @@ public: void populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* mtlRPDesc, uint32_t passIdx, MVKFramebuffer* framebuffer, + const MVKArrayRef& imagelessAttachments, const MVKArrayRef& clearValues, bool isRenderingEntireAttachment, bool loadOverride = false); @@ -117,7 +118,10 @@ public: 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& 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 4a09569b..0b8f2a9d 100644 --- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm +++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm @@ -176,10 +176,12 @@ uint32_t MVKRenderSubpass::getViewCountUpToMetalPass(uint32_t passIdx) const { void MVKRenderSubpass::populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* mtlRPDesc, uint32_t passIdx, MVKFramebuffer* framebuffer, + const MVKArrayRef& imagelessAttachments, const MVKArrayRef& 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 @@ void MVKRenderSubpass::populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* 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 @@ void MVKRenderSubpass::populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* // 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 @@ void MVKRenderSubpass::populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* 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::populateMTLRenderPassDescriptor(MTLRenderPassDescriptor* void MVKRenderSubpass::encodeStoreActions(MVKCommandEncoder* cmdEncoder, bool isRenderingEntireAttachment, + const MVKArrayRef& imagelessAttachments, bool storeOverride) { if (!cmdEncoder->_mtlRenderEncoder) { return; } if (!_renderPass->getDevice()->_pMetalFeatures->deferredStoreActions) { return; } @@ -377,7 +397,9 @@ void MVKRenderSubpass::encodeStoreActions(MVKCommandEncoder* cmdEncoder, 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 @@ void MVKRenderSubpass::encodeStoreActions(MVKCommandEncoder* cmdEncoder, 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 5fb1c189..a80de24a 100644 --- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def +++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def @@ -58,6 +58,7 @@ MVK_EXTENSION(KHR_external_semaphore_capabilities, KHR_EXTERNAL_SEMAPHORE_CAPABI 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)