Fix synchronization issue with locking MTLArgumentEncoder.
Move mutex lock for using MTLArgumentEncoder to MVKMTLArgumentEncoder
to be tracked along with the MTLArgumentEncoder it guards.
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 8a2ffb8..6f87c16 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -26,6 +26,7 @@
to find a cached shader, by only considering resources from the current shader stage.
- Rename `kMVKShaderStageMax` to `kMVKShaderStageCount`.
- Fix crash when requesting `MTLCommandBuffer` logs in runtime debug mode on older OS versions.
+- Fix synchronization issue with locking `MTLArgumentEncoder` for Metal Argument Buffers.
- Protect against crash when retrieving `MTLTexture` when `VkImage` has no `VkDeviceMemory` bound.
- Adjust some `VkPhysicalDeviceLimits` values for Vulkan and Metal compliance.
- Fix internal reference from `SPIRV_CROSS_NAMESPACE_OVERRIDE` to `SPIRV_CROSS_NAMESPACE`.
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
index 1ff5a9c..96ea826 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
@@ -488,27 +488,28 @@
void MVKResourcesCommandEncoderState::encodeMetalArgumentBuffer(MVKShaderStage stage) {
if ( !_cmdEncoder->isUsingMetalArgumentBuffers() ) { return; }
- // The Metal arg encoder can only write to one arg buffer at a time (it holds the arg buffer),
- // so we need to lock out other access to it while we are writing to it.
- MVKPipeline* pipeline = getPipeline();
- lock_guard<mutex> lock(pipeline->_mtlArgumentEncodingLock);
+ bool useDescSetArgBuff = _cmdEncoder->isUsingDescriptorSetMetalArgumentBuffers();
+ MVKPipeline* pipeline = getPipeline();
uint32_t dsCnt = pipeline->getDescriptorSetCount();
for (uint32_t dsIdx = 0; dsIdx < dsCnt; dsIdx++) {
auto* descSet = _boundDescriptorSets[dsIdx];
if ( !descSet ) { continue; }
- id<MTLArgumentEncoder> mtlArgEncoder = nil;
+ auto* dsLayout = descSet->getLayout();
+
+ // The Metal arg encoder can only write to one arg buffer at a time (it holds the arg buffer),
+ // so we need to lock out other access to it while we are writing to it.
+ auto& mvkArgEnc = useDescSetArgBuff ? dsLayout->getMTLArgumentEncoder() : pipeline->getMTLArgumentEncoder(dsIdx, stage);
+ lock_guard<mutex> lock(mvkArgEnc.mtlArgumentEncodingLock);
+
id<MTLBuffer> mtlArgBuffer = nil;
NSUInteger metalArgBufferOffset = 0;
-
- auto* dsLayout = descSet->getLayout();
- if (dsLayout->isUsingDescriptorSetMetalArgumentBuffers()) {
- mtlArgEncoder = dsLayout->getMTLArgumentEncoder().getMTLArgumentEncoder();
+ id<MTLArgumentEncoder> mtlArgEncoder = mvkArgEnc.getMTLArgumentEncoder();
+ if (useDescSetArgBuff) {
mtlArgBuffer = descSet->getMetalArgumentBuffer();
metalArgBufferOffset = descSet->getMetalArgumentBufferOffset();
} else {
- mtlArgEncoder = pipeline->getMTLArgumentEncoder(dsIdx, stage).getMTLArgumentEncoder();
// TODO: Source a different arg buffer & offset for each pipeline-stage/desccriptors set
// Also need to only encode the descriptors that are referenced in the shader.
// MVKMTLArgumentEncoder could include an MVKBitArray to track that and have it checked below.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index fdced31..1e9dddc 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -34,8 +34,13 @@
#pragma mark -
#pragma mark MVKDescriptorSetLayout
-/** Holds and manages the lifecycle of a MTLArgumentEncoder. The encoder can only be set once. */
+/**
+ * Holds and manages the lifecycle of a MTLArgumentEncoder. The encoder can
+ * only be set once, and copying this object results in an uninitialized
+ * empty object, since mutex and MTLArgumentEncoder can/should not be copied.
+ */
struct MVKMTLArgumentEncoder {
+ std::mutex mtlArgumentEncodingLock;
NSUInteger mtlArgumentEncoderSize = 0;
id<MTLArgumentEncoder> getMTLArgumentEncoder() { return _mtlArgumentEncoder; }
@@ -44,6 +49,10 @@
_mtlArgumentEncoder = mtlArgEnc; // takes ownership
mtlArgumentEncoderSize = mtlArgEnc.encodedLength;
}
+
+ MVKMTLArgumentEncoder(const MVKMTLArgumentEncoder& other) {}
+ MVKMTLArgumentEncoder& operator=(const MVKMTLArgumentEncoder& other) { return *this; }
+ MVKMTLArgumentEncoder() {}
~MVKMTLArgumentEncoder() { [_mtlArgumentEncoder release]; }
private:
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index 0f69331..f262b82 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -188,9 +188,6 @@
/** Returns the number of descriptor sets in this pipeline layout. */
uint32_t getDescriptorSetCount() { return _descriptorSetCount; }
- /** A mutex lock to protect access to the Metal argument encoders. */
- std::mutex _mtlArgumentEncodingLock;
-
/** Constructs an instance for the device. layout, and parent (which may be NULL). */
MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipelineLayout* layout, MVKPipeline* parent);