Merge pull request #1566 from billhollings/VK_EXT_sample_locations
Add support for VK_EXT_sample_locations extension.
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index e6f9874..b09d066 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -307,6 +307,7 @@
- `VK_EXT_post_depth_coverage` *(iOS and macOS, requires family 4 (A11) or better Apple GPU)*
- `VK_EXT_private_data `
- `VK_EXT_robustness2`
+- `VK_EXT_sample_locations`
- `VK_EXT_scalar_block_layout`
- `VK_EXT_shader_stencil_export` *(requires Mac GPU family 2 or iOS GPU family 5)*
- `VK_EXT_shader_viewport_index_layer`
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 80c235a..8c0b556 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -18,6 +18,8 @@
Released TBD
+- Add support for extensions:
+ - `VK_EXT_sample_locations`
- Fixes to pipeline layout compatibility.
- Reinstate memory barriers on non-Apple GPUs, which were inadvertently disabled in an earlier update.
- Support base vertex instance support in shader conversion.
@@ -29,6 +31,7 @@
- Fixes to optimize resource objects retained by descriptors beyond their lifetimes.
- `MoltenVKShaderConverter` tool defaults to the highest MSL version supported on runtime OS.
- Update *glslang* version, to use `python3` in *glslang* scripts, to replace missing `python` on *macOS 12.3*.
+- Update `VK_MVK_MOLTENVK_SPEC_VERSION` to version `34`.
- Update to latest SPIRV-Cross:
- MSL: Support input/output blocks containing nested struct arrays.
- MSL: Use var name instead of var-type name for flattened interface members.
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index 583aef9..d4163a9 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -55,7 +55,7 @@
#define MVK_MAKE_VERSION(major, minor, patch) (((major) * 10000) + ((minor) * 100) + (patch))
#define MVK_VERSION MVK_MAKE_VERSION(MVK_VERSION_MAJOR, MVK_VERSION_MINOR, MVK_VERSION_PATCH)
-#define VK_MVK_MOLTENVK_SPEC_VERSION 33
+#define VK_MVK_MOLTENVK_SPEC_VERSION 34
#define VK_MVK_MOLTENVK_EXTENSION_NAME "VK_MVK_moltenvk"
/** Identifies the level of logging MoltenVK should be limited to outputting. */
@@ -786,7 +786,7 @@
* command buffer submission, to a physically removed GPU. In the case where this error does
* not impact the VkPhysicalDevice, Vulkan requires that the app destroy and re-create a new
* VkDevice. However, not all apps (including CTS) respect that requirement, leading to what
- * might be a transient command submission failure causing an unexpected catastophic app failure.
+ * might be a transient command submission failure causing an unexpected catastrophic app failure.
*
* If this setting is enabled, in the case of a VK_ERROR_DEVICE_LOST error that does NOT impact
* the VkPhysicalDevice, MoltenVK will log the error, but will not mark the VkDevice as lost,
@@ -929,6 +929,7 @@
VkBool32 descriptorSetArgumentBuffers; /**< If true, a Metal argument buffer can be assigned to a descriptor set, and used on any pipeline and pipeline stage. If false, a different Metal argument buffer must be used for each pipeline-stage/descriptor-set combination. */
MVKFloatRounding clearColorFloatRounding; /**< Identifies the type of rounding Metal uses for MTLClearColor float to integer conversions. */
MVKCounterSamplingFlags counterSamplingPoints; /**< Identifies the points where pipeline GPU counter sampling may occur. */
+ VkBool32 programmableSamplePositions; /**< If true, programmable MSAA sample positions are supported. */
} MVKPhysicalDeviceMetalFeatures;
/** MoltenVK performance of a particular type of activity. */
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
index 15c40fc..dcb3b02 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
@@ -46,6 +46,7 @@
protected:
+ MVKSmallVector<MVKSmallVector<MTLSamplePosition>> _subpassSamplePositions;
MVKRenderPass* _renderPass;
MVKFramebuffer* _framebuffer;
VkRect2D _renderArea;
@@ -138,6 +139,25 @@
#pragma mark -
+#pragma mark MVKCmdSetSampleLocations
+
+/** Vulkan command to dynamically set custom sample locations. */
+class MVKCmdSetSampleLocations : public MVKCommand {
+
+public:
+ VkResult setContent(MVKCommandBuffer* cmdBuff,
+ const VkSampleLocationsInfoEXT* pSampleLocationsInfo);
+
+ void encode(MVKCommandEncoder* cmdEncoder) override;
+
+protected:
+ MVKCommandTypePool<MVKCommand>* getTypePool(MVKCommandPool* cmdPool) override;
+
+ MVKSmallVector<MTLSamplePosition, 8> _samplePositions;
+};
+
+
+#pragma mark -
#pragma mark MVKCmdExecuteCommands
/**
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
index 56f0d49..f621be8 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
@@ -36,6 +36,30 @@
_renderPass = (MVKRenderPass*)pRenderPassBegin->renderPass;
_framebuffer = (MVKFramebuffer*)pRenderPassBegin->framebuffer;
_renderArea = pRenderPassBegin->renderArea;
+ _subpassSamplePositions.clear();
+
+ for (const auto* next = (VkBaseInStructure*)pRenderPassBegin->pNext; next; next = next->pNext) {
+ switch (next->sType) {
+ case VK_STRUCTURE_TYPE_RENDER_PASS_SAMPLE_LOCATIONS_BEGIN_INFO_EXT: {
+ // Build an array of arrays, one array of sample positions for each subpass index.
+ // For subpasses not included in VkRenderPassSampleLocationsBeginInfoEXT, the resulting array of samples will be empty.
+ _subpassSamplePositions.resize(_renderPass->getSubpassCount());
+ auto* pRPSampLocnsInfo = (VkRenderPassSampleLocationsBeginInfoEXT*)next;
+ for (uint32_t spSLIdx = 0; spSLIdx < pRPSampLocnsInfo->postSubpassSampleLocationsCount; spSLIdx++) {
+ auto& spsl = pRPSampLocnsInfo->pPostSubpassSampleLocations[spSLIdx];
+ uint32_t spIdx = spsl.subpassIndex;
+ auto& spSampPosns = _subpassSamplePositions[spIdx];
+ for (uint32_t slIdx = 0; slIdx < spsl.sampleLocationsInfo.sampleLocationsCount; slIdx++) {
+ auto& sl = spsl.sampleLocationsInfo.pSampleLocations[slIdx];
+ spSampPosns.push_back(MTLSamplePositionMake(sl.x, sl.y));
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
return VK_SUCCESS;
}
@@ -61,13 +85,23 @@
template <size_t N_CV, size_t N_A>
void MVKCmdBeginRenderPass<N_CV, N_A>::encode(MVKCommandEncoder* cmdEncoder) {
// MVKLogDebug("Encoding vkCmdBeginRenderPass(). Elapsed time: %.6f ms.", mvkGetElapsedMilliseconds());
+
+ // Convert the sample position array of arrays to an array of array-references,
+ // so that it can be passed to the command encoder.
+ size_t spSPCnt = _subpassSamplePositions.size();
+ MVKArrayRef<MTLSamplePosition> spSPRefs[spSPCnt];
+ for (uint32_t spSPIdx = 0; spSPIdx < spSPCnt; spSPIdx++) {
+ spSPRefs[spSPIdx] = _subpassSamplePositions[spSPIdx].contents();
+ }
+
cmdEncoder->beginRenderpass(this,
_contents,
_renderPass,
_framebuffer,
_renderArea,
_clearValues.contents(),
- _attachments.contents());
+ _attachments.contents(),
+ MVKArrayRef(spSPRefs, spSPCnt));
}
template class MVKCmdBeginRenderPass<1, 0>;
@@ -130,6 +164,24 @@
cmdEncoder->endRenderpass();
}
+#pragma mark -
+#pragma mark MVKCmdSetSampleLocations
+
+VkResult MVKCmdSetSampleLocations::setContent(MVKCommandBuffer* cmdBuff,
+ const VkSampleLocationsInfoEXT* pSampleLocationsInfo) {
+
+ for (uint32_t slIdx = 0; slIdx < pSampleLocationsInfo->sampleLocationsCount; slIdx++) {
+ auto& sl = pSampleLocationsInfo->pSampleLocations[slIdx];
+ _samplePositions.push_back(MTLSamplePositionMake(sl.x, sl.y));
+ }
+
+ return VK_SUCCESS;
+}
+
+void MVKCmdSetSampleLocations::encode(MVKCommandEncoder* cmdEncoder) {
+ cmdEncoder->setDynamicSamplePositions(_samplePositions.contents());
+}
+
#pragma mark -
#pragma mark MVKCmdExecuteCommands
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
index 9ad5e61..49a74ed 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
@@ -191,74 +191,6 @@
#pragma mark -
#pragma mark MVKCommandEncoder
-// The following commands can be issued both inside and outside a renderpass and their state must
-// span multiple MTLRenderCommandEncoders, to allow state to be set before a renderpass, and to
-// allow more than one MTLRenderCommandEncoder to be used for a single Vulkan renderpass or subpass.
-//
-// + vkCmdBindPipeline() : _graphicsPipelineState & _computePipelineState
-// + vkCmdBindDescriptorSets() : _graphicsResourcesState & _computeResourcesState
-// + vkCmdBindVertexBuffers() : _graphicsResourcesState
-// + vkCmdBindIndexBuffer() : _graphicsResourcesState
-// + vkCmdPushConstants() : _vertexPushConstants & _tessCtlPushConstants & _tessEvalPushConstants & _fragmentPushConstants & _computePushConstants
-// + vkCmdSetViewport() : _viewportState
-// + vkCmdSetDepthBias() : _depthBiasState
-// + vkCmdSetScissor() : _scissorState
-// + vkCmdSetStencilCompareMask() : _depthStencilState
-// + vkCmdSetStencilWriteMask() : _depthStencilState
-// + vkCmdSetStencilReference() : _stencilReferenceValueState
-// + vkCmdSetBlendConstants() : _blendColorState
-// + vkCmdBeginQuery() : _occlusionQueryState
-// + vkCmdEndQuery() : _occlusionQueryState
-// + vkCmdPipelineBarrier() : handled via textureBarrier and MTLBlitCommandEncoder
-// + vkCmdWriteTimestamp() : doesn't affect MTLCommandEncoders
-// + vkCmdExecuteCommands() : state managed by embedded commands
-// - vkCmdSetLineWidth() - unsupported by Metal
-// - vkCmdSetDepthBounds() - unsupported by Metal
-// - vkCmdWaitEvents() - unsupported by Metal
-
-// The above list of Vulkan commands covers the following corresponding MTLRenderCommandEncoder state:
-// + setBlendColorRed : _blendColorState
-// + setCullMode : _graphicsPipelineState
-// + setDepthBias : _depthBiasState
-// + setDepthClipMode : _graphicsPipelineState
-// + setDepthStencilState : _depthStencilState
-// + setFrontFacingWinding : _graphicsPipelineState
-// + setRenderPipelineState : _graphicsPipelineState
-// + setScissorRect : _scissorState
-// + setStencilFrontReferenceValue : _stencilReferenceValueState
-// + setStencilReferenceValue (unused) : _stencilReferenceValueState
-// + setTriangleFillMode : _graphicsPipelineState
-// + setViewport : _viewportState
-// + setVisibilityResultMode : _occlusionQueryState
-// + setVertexBuffer : _graphicsResourcesState & _vertexPushConstants & _tessEvalPushConstants
-// + setVertexBuffers (unused) : _graphicsResourcesState
-// + setVertexBytes : _vertexPushConstants & _tessEvalPushConstants
-// + setVertexBufferOffset (unused) : _graphicsResourcesState
-// + setVertexTexture : _graphicsResourcesState
-// + setVertexTextures (unused) : _graphicsResourcesState
-// + setVertexSamplerState : _graphicsResourcesState
-// + setVertexSamplerStates : (unused) : _graphicsResourcesState
-// + setFragmentBuffer : _graphicsResourcesState & _fragmentPushConstants
-// + setFragmentBuffers (unused) : _graphicsResourcesState
-// + setFragmentBytes : _fragmentPushConstants
-// + setFragmentBufferOffset (unused) : _graphicsResourcesState
-// + setFragmentTexture : _graphicsResourcesState
-// + setFragmentTextures (unused) : _graphicsResourcesState
-// + setFragmentSamplerState : _graphicsResourcesState
-// + setFragmentSamplerStates : (unused) : _graphicsResourcesState
-
-// The above list of Vulkan commands covers the following corresponding MTLComputeCommandEncoder state:
-// + setComputePipelineState : _computePipelineState & _graphicsPipelineState
-// + setBuffer : _computeResourcesState & _computePushConstants & _graphicsResourcesState & _tessCtlPushConstants
-// + setBuffers (unused) : _computeResourcesState & _graphicsResourcesState
-// + setBytes : _computePushConstants & _tessCtlPushConstants
-// + setBufferOffset (unused) : _computeResourcesState & _graphicsResourcesState
-// + setTexture : _computeResourcesState & _graphicsResourcesState
-// + setTextures (unused) : _computeResourcesState & _graphicsResourcesState
-// + setSamplerState : _computeResourcesState & _graphicsResourcesState
-// + setSamplerStates : (unused) : _computeResourcesState & _graphicsResourcesState
-
-
/*** Holds a collection of active queries for each query pool. */
typedef std::unordered_map<MVKQueryPool*, MVKSmallVector<uint32_t, kMVKDefaultQueryCount>> MVKActivatedQueries;
@@ -293,7 +225,8 @@
MVKFramebuffer* framebuffer,
VkRect2D& renderArea,
MVKArrayRef<VkClearValue> clearValues,
- MVKArrayRef<MVKImageView*> attachments);
+ MVKArrayRef<MVKImageView*> attachments,
+ MVKArrayRef<MVKArrayRef<MTLSamplePosition>> subpassSamplePositions);
/** Begins the next render subpass. */
void beginNextSubpass(MVKCommand* subpassCmd, VkSubpassContents renderpassContents);
@@ -301,6 +234,9 @@
/** Begins the next multiview Metal render pass. */
void beginNextMultiviewPass();
+ /** Sets the dynamic custom sample positions to use when rendering. */
+ void setDynamicSamplePositions(MVKArrayRef<MTLSamplePosition> dynamicSamplePositions);
+
/** Begins a Metal render pass for the current render subpass. */
void beginMetalRenderPass(MVKCommandUse cmdUse);
@@ -509,6 +445,7 @@
void encodeTimestampStageCounterSamples();
bool hasTimestampStageCounterQueries() { return !_timestampStageCounterQueries.empty(); }
id<MTLFence> getStageCountersMTLFence();
+ MVKArrayRef<MTLSamplePosition> getCustomSamplePositions();
typedef struct GPUCounterQuery {
MVKGPUCounterQueryPool* queryPool = nullptr;
@@ -526,6 +463,8 @@
MVKSmallVector<GPUCounterQuery, 16> _timestampStageCounterQueries;
MVKSmallVector<VkClearValue, kMVKDefaultAttachmentCount> _clearValues;
MVKSmallVector<MVKImageView*, kMVKDefaultAttachmentCount> _attachments;
+ MVKSmallVector<MTLSamplePosition> _dynamicSamplePositions;
+ MVKSmallVector<MVKSmallVector<MTLSamplePosition>> _subpassSamplePositions;
id<MTLComputeCommandEncoder> _mtlComputeEncoder;
MVKCommandUse _mtlComputeEncoderUse;
id<MTLBlitCommandEncoder> _mtlBlitEncoder;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
index 574cb98..e27ccb5 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
@@ -322,7 +322,8 @@
MVKFramebuffer* framebuffer,
VkRect2D& renderArea,
MVKArrayRef<VkClearValue> clearValues,
- MVKArrayRef<MVKImageView*> attachments) {
+ MVKArrayRef<MVKImageView*> attachments,
+ MVKArrayRef<MVKArrayRef<MTLSamplePosition>> subpassSamplePositions) {
_renderPass = renderPass;
_framebuffer = framebuffer;
_renderArea = renderArea;
@@ -330,6 +331,14 @@
mvkVkExtent2DsAreEqual(_renderArea.extent, getFramebufferExtent()));
_clearValues.assign(clearValues.begin(), clearValues.end());
_attachments.assign(attachments.begin(), attachments.end());
+
+ // Copy the sample positions array of arrays, one array of sample positions for each subpass index.
+ _subpassSamplePositions.resize(subpassSamplePositions.size);
+ for (uint32_t spSPIdx = 0; spSPIdx < subpassSamplePositions.size; spSPIdx++) {
+ _subpassSamplePositions[spSPIdx].assign(subpassSamplePositions[spSPIdx].begin(),
+ subpassSamplePositions[spSPIdx].end());
+ }
+
setSubpass(passCmd, subpassContents, 0);
}
@@ -365,6 +374,10 @@
uint32_t MVKCommandEncoder::getMultiviewPassIndex() { return _multiviewPassIndex; }
+void MVKCommandEncoder::setDynamicSamplePositions(MVKArrayRef<MTLSamplePosition> dynamicSamplePositions) {
+ _dynamicSamplePositions.assign(dynamicSamplePositions.begin(), dynamicSamplePositions.end());
+}
+
// Creates _mtlRenderEncoder and marks cached render state as dirty so it will be set into the _mtlRenderEncoder.
void MVKCommandEncoder::beginMetalRenderPass(MVKCommandUse cmdUse) {
@@ -416,6 +429,14 @@
}
}
+ // If programmable sample positions are supported, set them into the render pass descriptor.
+ // If no custom sample positions are established, size will be zero,
+ // and Metal will default to using default sample postions.
+ if (_pDeviceMetalFeatures->programmableSamplePositions) {
+ auto cstmSampPosns = getCustomSamplePositions();
+ [mtlRPDesc setSamplePositions: cstmSampPosns.data count: cstmSampPosns.size];
+ }
+
_mtlRenderEncoder = [_mtlCmdBuffer renderCommandEncoderWithDescriptor: mtlRPDesc]; // not retained
setLabelIfNotNil(_mtlRenderEncoder, getMTLRenderCommandEncoderName(cmdUse));
@@ -439,6 +460,18 @@
_occlusionQueryState.beginMetalRenderPass();
}
+// If custom sample positions have been set, return them, otherwise return an empty array.
+// For Metal, VkPhysicalDeviceSampleLocationsPropertiesEXT::variableSampleLocations is false.
+// As such, Vulkan requires that sample positions must be established at the beginning of
+// a renderpass, and that both pipeline and dynamic sample locations must be the same as those
+// set for each subpass. Therefore, the only sample positions of use are those set for each
+// subpass when the renderpass begins. The pipeline and dynamic sample positions are ignored.
+MVKArrayRef<MTLSamplePosition> MVKCommandEncoder::getCustomSamplePositions() {
+ return (_renderSubpassIndex < _subpassSamplePositions.size()
+ ? _subpassSamplePositions[_renderSubpassIndex].contents()
+ : MVKArrayRef<MTLSamplePosition>());
+}
+
void MVKCommandEncoder::encodeStoreActions(bool storeOverride) {
getSubpass()->encodeStoreActions(this,
_isRenderingEntireAttachment,
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandTypePools.def b/MoltenVK/MoltenVK/Commands/MVKCommandTypePools.def
index d5785ff..6c995bb 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandTypePools.def
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandTypePools.def
@@ -78,6 +78,7 @@
MVK_CMD_TYPE_POOLS_FROM_5_THRESHOLDS(BeginRenderPass, 1, 2, 0, 1, 2)
MVK_CMD_TYPE_POOL(NextSubpass)
MVK_CMD_TYPE_POOL(EndRenderPass)
+MVK_CMD_TYPE_POOL(SetSampleLocations)
MVK_CMD_TYPE_POOLS_FROM_THRESHOLD(ExecuteCommands, 1)
MVK_CMD_TYPE_POOLS_FROM_2_THRESHOLDS(BindDescriptorSetsStatic, 1, 4)
MVK_CMD_TYPE_POOLS_FROM_THRESHOLD(BindDescriptorSetsDynamic, 4)
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index ceb76a5..3cbf9cb 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -128,6 +128,10 @@
/** Populates the specified structure with the format properties of this device. */
void getFormatProperties(VkFormat format, VkFormatProperties2* pFormatProperties);
+ /** Populates the specified structure with the multisample properties of this device. */
+ void getMultisampleProperties(VkSampleCountFlagBits samples,
+ VkMultisamplePropertiesEXT* pMultisampleProperties);
+
/** Populates the image format properties supported on this device. */
VkResult getImageFormatProperties(VkFormat format,
VkImageType type,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index b22b8b8..17bc185 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -75,6 +75,9 @@
static const uint32_t kAMDRadeonRX6800DeviceId = 0x73bf;
static const uint32_t kAMDRadeonRX6700DeviceId = 0x73df;
+static const VkExtent2D kMetalSamplePositionGridSize = { 1, 1 };
+static const VkExtent2D kMetalSamplePositionGridSizeNotSupported = { 0, 0 };
+
#pragma clang diagnostic pop
@@ -457,6 +460,16 @@
portabilityProps->minVertexInputBindingStrideAlignment = (uint32_t)_metalFeatures.vertexStrideAlignment;
break;
}
+ case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLE_LOCATIONS_PROPERTIES_EXT: {
+ auto* sampLocnProps = (VkPhysicalDeviceSampleLocationsPropertiesEXT*)next;
+ sampLocnProps->sampleLocationSampleCounts = _metalFeatures.supportedSampleCounts;
+ sampLocnProps->maxSampleLocationGridSize = kMetalSamplePositionGridSize;
+ sampLocnProps->sampleLocationCoordinateRange[0] = 0.0;
+ sampLocnProps->sampleLocationCoordinateRange[1] = (15.0 / 16.0);
+ sampLocnProps->sampleLocationSubPixelBits = 4;
+ sampLocnProps->variableSampleLocations = VK_FALSE;
+ break;
+ }
default:
break;
}
@@ -526,6 +539,15 @@
getFormatProperties(format, &pFormatProperties->formatProperties);
}
+void MVKPhysicalDevice::getMultisampleProperties(VkSampleCountFlagBits samples,
+ VkMultisamplePropertiesEXT* pMultisampleProperties) {
+ if (pMultisampleProperties) {
+ pMultisampleProperties->maxSampleLocationGridSize = (mvkIsOnlyAnyFlagEnabled(samples, _metalFeatures.supportedSampleCounts)
+ ? kMetalSamplePositionGridSize
+ : kMetalSamplePositionGridSizeNotSupported);
+ }
+}
+
VkResult MVKPhysicalDevice::getImageFormatProperties(VkFormat format,
VkImageType type,
VkImageTiling tiling,
@@ -1519,9 +1541,12 @@
#endif
- // Note the selector name, which is different from the property name.
+ if ( [_mtlDevice respondsToSelector: @selector(areProgrammableSamplePositionsSupported)] ) {
+ _metalFeatures.programmableSamplePositions = _mtlDevice.areProgrammableSamplePositionsSupported;
+ }
+
if ( [_mtlDevice respondsToSelector: @selector(areRasterOrderGroupsSupported)] ) {
- _metalFeatures.rasterOrderGroups = _mtlDevice.rasterOrderGroupsSupported;
+ _metalFeatures.rasterOrderGroups = _mtlDevice.areRasterOrderGroupsSupported;
}
#if MVK_XCODE_12
if ( [_mtlDevice respondsToSelector: @selector(supportsPullModelInterpolation)] ) {
@@ -2738,6 +2763,9 @@
if (!_metalFeatures.samplerMirrorClampToEdge) {
pWritableExtns->vk_KHR_sampler_mirror_clamp_to_edge.enabled = false;
}
+ if (!_metalFeatures.programmableSamplePositions) {
+ pWritableExtns->vk_EXT_sample_locations.enabled = false;
+ }
if (!_metalFeatures.rasterOrderGroups) {
pWritableExtns->vk_EXT_fragment_shader_interlock.enabled = false;
}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
index 414fb62..1313cd0 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
@@ -655,6 +655,8 @@
ADD_DVC_EXT_ENTRY_POINT(vkDestroyPrivateDataSlotEXT, EXT_PRIVATE_DATA);
ADD_DVC_EXT_ENTRY_POINT(vkGetPrivateDataEXT, EXT_PRIVATE_DATA);
ADD_DVC_EXT_ENTRY_POINT(vkSetPrivateDataEXT, EXT_PRIVATE_DATA);
+ ADD_DVC_EXT_ENTRY_POINT(vkGetPhysicalDeviceMultisamplePropertiesEXT, EXT_SAMPLE_LOCATIONS);
+ ADD_DVC_EXT_ENTRY_POINT(vkCmdSetSampleLocationsEXT, EXT_SAMPLE_LOCATIONS);
ADD_DVC_EXT_ENTRY_POINT(vkGetRefreshCycleDurationGOOGLE, GOOGLE_DISPLAY_TIMING);
ADD_DVC_EXT_ENTRY_POINT(vkGetPastPresentationTimingGOOGLE, GOOGLE_DISPLAY_TIMING);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index a7f271d..21ffc44 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -203,9 +203,6 @@
MVKBitArray stages[4] = {};
};
-/** The number of dynamic states possible in Vulkan. */
-static const uint32_t kMVKVkDynamicStateCount = 32;
-
/** Represents an Vulkan graphics pipeline. */
class MVKGraphicsPipeline : public MVKPipeline {
@@ -259,6 +256,12 @@
/** Returns true if the tessellation control shader needs a buffer to store its per-patch output. */
bool needsTessCtlPatchOutputBuffer() { return _needsTessCtlPatchOutputBuffer; }
+ /** Returns whether this pipeline has custom sample positions enabled. */
+ bool isUsingCustomSamplePositions() { return _isUsingCustomSamplePositions; }
+
+ /** Returns the custom samples used by this pipeline. */
+ MVKArrayRef<MTLSamplePosition> getCustomSamplePositions() { return _customSamplePositions.contents(); }
+
/** Returns the Metal vertex buffer index to use for the specified vertex attribute binding number. */
uint32_t getMetalBufferIndexForVertexAttributeBinding(uint32_t binding) { return _device->getMetalBufferIndexForVertexAttributeBinding(binding); }
@@ -287,6 +290,7 @@
id<MTLRenderPipelineState> getOrCompilePipeline(MTLRenderPipelineDescriptor* plDesc, id<MTLRenderPipelineState>& plState);
id<MTLComputePipelineState> getOrCompilePipeline(MTLComputePipelineDescriptor* plDesc, id<MTLComputePipelineState>& plState, const char* compilerType);
+ void initCustomSamplePositions(const VkGraphicsPipelineCreateInfo* pCreateInfo);
void initMTLRenderPipelineState(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
void initShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
void addVertexInputToShaderConversionConfig(SPIRVToMSLConversionConfiguration& shaderConfig, const VkGraphicsPipelineCreateInfo* pCreateInfo);
@@ -323,6 +327,8 @@
MVKSmallVector<VkViewport, kMVKCachedViewportScissorCount> _viewports;
MVKSmallVector<VkRect2D, kMVKCachedViewportScissorCount> _scissors;
+ MVKSmallVector<VkDynamicState> _dynamicState;
+ MVKSmallVector<MTLSamplePosition> _customSamplePositions;
MVKSmallVector<MVKTranslatedVertexBinding> _translatedVertexBindings;
MVKSmallVector<MVKZeroDivisorVertexBinding> _zeroDivisorVertexBindings;
MVKSmallVector<MVKStagedMTLArgumentEncoders> _mtlArgumentEncoders;
@@ -350,7 +356,6 @@
uint32_t _tessCtlPatchOutputBufferIndex = 0;
uint32_t _tessCtlLevelBufferIndex = 0;
- bool _dynamicStateEnabled[kMVKVkDynamicStateCount];
bool _needsVertexSwizzleBuffer = false;
bool _needsVertexBufferSizeBuffer = false;
bool _needsVertexDynamicOffsetBuffer = false;
@@ -372,6 +377,7 @@
bool _isRasterizing = false;
bool _isRasterizingColor = false;
bool _isRasterizingDepthStencil = false;
+ bool _isUsingCustomSamplePositions = false;
};
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index bf545e3..2751bde 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -309,17 +309,18 @@
}
bool MVKGraphicsPipeline::supportsDynamicState(VkDynamicState state) {
-
- // First test if this dynamic state is explicitly turned off
- if ( (state >= kMVKVkDynamicStateCount) || !_dynamicStateEnabled[state] ) { return false; }
-
- // Some dynamic states have other restrictions
- switch (state) {
- case VK_DYNAMIC_STATE_DEPTH_BIAS:
- return _rasterInfo.depthBiasEnable;
- default:
- return true;
- }
+ for (auto& ds : _dynamicState) {
+ if (state == ds) {
+ // Some dynamic states have other restrictions
+ switch (state) {
+ case VK_DYNAMIC_STATE_DEPTH_BIAS:
+ return _rasterInfo.depthBiasEnable;
+ default:
+ return true;
+ }
+ }
+ }
+ return false;
}
static const char vtxCompilerType[] = "Vertex stage pipeline for tessellation";
@@ -408,13 +409,11 @@
}
}
- // Track dynamic state in _dynamicStateEnabled array
- mvkClear(_dynamicStateEnabled, kMVKVkDynamicStateCount); // start with all dynamic state disabled
+ // Track dynamic state
const VkPipelineDynamicStateCreateInfo* pDS = pCreateInfo->pDynamicState;
if (pDS) {
for (uint32_t i = 0; i < pDS->dynamicStateCount; i++) {
- VkDynamicState ds = pDS->pDynamicStates[i];
- _dynamicStateEnabled[ds] = true;
+ _dynamicState.push_back(pDS->pDynamicStates[i]);
}
}
@@ -457,6 +456,9 @@
}
}
+ // Must run after _isRasterizing and _dynamicState are populated
+ initCustomSamplePositions(pCreateInfo);
+
// Render pipeline state
initMTLRenderPipelineState(pCreateInfo, reflectData);
@@ -472,7 +474,7 @@
for (uint32_t vpIdx = 0; vpIdx < vpCnt; vpIdx++) {
// If viewport is dyanamic, we still add a dummy so that the count will be tracked.
VkViewport vp;
- if ( !_dynamicStateEnabled[VK_DYNAMIC_STATE_VIEWPORT] ) { vp = pVPState->pViewports[vpIdx]; }
+ if ( !supportsDynamicState(VK_DYNAMIC_STATE_VIEWPORT) ) { vp = pVPState->pViewports[vpIdx]; }
_viewports.push_back(vp);
}
@@ -481,7 +483,7 @@
for (uint32_t sIdx = 0; sIdx < sCnt; sIdx++) {
// If scissor is dyanamic, we still add a dummy so that the count will be tracked.
VkRect2D sc;
- if ( !_dynamicStateEnabled[VK_DYNAMIC_STATE_SCISSOR] ) { sc = pVPState->pScissors[sIdx]; }
+ if ( !supportsDynamicState(VK_DYNAMIC_STATE_SCISSOR) ) { sc = pVPState->pScissors[sIdx]; }
_scissors.push_back(sc);
}
}
@@ -512,6 +514,31 @@
return plState;
}
+// Must run after _isRasterizing and _dynamicState are populated
+void MVKGraphicsPipeline::initCustomSamplePositions(const VkGraphicsPipelineCreateInfo* pCreateInfo) {
+
+ // Must ignore allowed bad pMultisampleState pointer if rasterization disabled
+ if ( !(_isRasterizing && pCreateInfo->pMultisampleState) ) { return; }
+
+ for (const auto* next = (VkBaseInStructure*)pCreateInfo->pMultisampleState->pNext; next; next = next->pNext) {
+ switch (next->sType) {
+ case VK_STRUCTURE_TYPE_PIPELINE_SAMPLE_LOCATIONS_STATE_CREATE_INFO_EXT: {
+ auto* pSampLocnsCreateInfo = (VkPipelineSampleLocationsStateCreateInfoEXT*)next;
+ _isUsingCustomSamplePositions = pSampLocnsCreateInfo->sampleLocationsEnable;
+ if (_isUsingCustomSamplePositions && !supportsDynamicState(VK_DYNAMIC_STATE_SAMPLE_LOCATIONS_EXT)) {
+ for (uint32_t slIdx = 0; slIdx < pSampLocnsCreateInfo->sampleLocationsInfo.sampleLocationsCount; slIdx++) {
+ auto& sl = pSampLocnsCreateInfo->sampleLocationsInfo.pSampleLocations[slIdx];
+ _customSamplePositions.push_back(MTLSamplePositionMake(sl.x, sl.y));
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
// Constructs the underlying Metal render pipeline.
void MVKGraphicsPipeline::initMTLRenderPipelineState(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData) {
_mtlTessVertexStageState = nil;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
index 81f1b6e..2d1c698 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
@@ -261,8 +261,11 @@
/** Returns the granularity of the render area of this instance. */
VkExtent2D getRenderAreaGranularity();
- /** Returns the format of the color attachment at the specified index. */
- MVKRenderSubpass* getSubpass(uint32_t subpassIndex);
+ /** Returns the number of subpasses. */
+ size_t getSubpassCount() { return _subpasses.size(); }
+
+ /** Returns the subpass at the specified index. */
+ MVKRenderSubpass* getSubpass(uint32_t subpassIndex) { return &_subpasses[subpassIndex]; }
/** Returns whether or not this render pass is a multiview render pass. */
bool isMultiview() const;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
index 6d9c04a..ee92899 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
@@ -818,8 +818,6 @@
return { 1, 1 };
}
-MVKRenderSubpass* MVKRenderPass::getSubpass(uint32_t subpassIndex) { return &_subpasses[subpassIndex]; }
-
bool MVKRenderPass::isMultiview() const { return _subpasses[0].isMultiview(); }
MVKRenderPass::MVKRenderPass(MVKDevice* device,
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index baf519f..0638098 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -95,6 +95,7 @@
MVK_EXTENSION(EXT_post_depth_coverage, EXT_POST_DEPTH_COVERAGE, DEVICE, 11.0, 11.0)
MVK_EXTENSION(EXT_private_data, EXT_PRIVATE_DATA, DEVICE, 10.11, 8.0)
MVK_EXTENSION(EXT_robustness2, EXT_ROBUSTNESS_2, DEVICE, 10.11, 8.0)
+MVK_EXTENSION(EXT_sample_locations, EXT_SAMPLE_LOCATIONS, DEVICE, 10.13, 11.0)
MVK_EXTENSION(EXT_scalar_block_layout, EXT_SCALAR_BLOCK_LAYOUT, DEVICE, 10.11, 8.0)
MVK_EXTENSION(EXT_shader_stencil_export, EXT_SHADER_STENCIL_EXPORT, DEVICE, 10.14, 12.0)
MVK_EXTENSION(EXT_shader_viewport_index_layer, EXT_SHADER_VIEWPORT_INDEX_LAYER, DEVICE, 10.11, 8.0)
diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.h b/MoltenVK/MoltenVK/Utility/MVKFoundation.h
index 5e79965..30d0a4b 100644
--- a/MoltenVK/MoltenVK/Utility/MVKFoundation.h
+++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.h
@@ -442,6 +442,11 @@
const Type* end() const { return &data[size]; }
const Type& operator[]( const size_t i ) const { return data[i]; }
Type& operator[]( const size_t i ) { return data[i]; }
+ MVKArrayRef<Type>& operator=(const MVKArrayRef<Type>& other) {
+ data = other.data;
+ *(size_t*)&size = other.size;
+ return *this;
+ }
MVKArrayRef() : MVKArrayRef(nullptr, 0) {}
MVKArrayRef(Type* d, size_t s) : data(d), size(s) {}
};
diff --git a/MoltenVK/MoltenVK/Vulkan/vulkan.mm b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
index b266cf3..93d7c08 100644
--- a/MoltenVK/MoltenVK/Vulkan/vulkan.mm
+++ b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
@@ -3099,6 +3099,29 @@
}
#pragma mark -
+#pragma mark VK_EXT_sample_locations extension
+
+void vkGetPhysicalDeviceMultisamplePropertiesEXT(
+ VkPhysicalDevice physicalDevice,
+ VkSampleCountFlagBits samples,
+ VkMultisamplePropertiesEXT* pMultisampleProperties) {
+
+ MVKTraceVulkanCallStart();
+ MVKPhysicalDevice* mvkPD = MVKPhysicalDevice::getMVKPhysicalDevice(physicalDevice);
+ mvkPD->getMultisampleProperties(samples, pMultisampleProperties);
+ MVKTraceVulkanCallEnd();
+}
+
+void vkCmdSetSampleLocationsEXT(
+ VkCommandBuffer commandBuffer,
+ const VkSampleLocationsInfoEXT* pSampleLocationsInfo) {
+
+ MVKTraceVulkanCallStart();
+ MVKAddCmd(SetSampleLocations, commandBuffer, pSampleLocationsInfo);
+ MVKTraceVulkanCallEnd();
+}
+
+#pragma mark -
#pragma mark iOS & macOS surface extensions
MVK_PUBLIC_VULKAN_SYMBOL VkResult vkCreate_PLATFORM_SurfaceMVK(