Merge pull request #329 from cdavis5e/destroyed-resource-descset

Remove destroyed resources from descriptor sets.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h
index edd5fc6..dcb972b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h
@@ -84,7 +84,7 @@
 #pragma mark MVKBufferView
 
 /** Represents a Vulkan buffer view. */
-class MVKBufferView : public MVKBaseDeviceObject {
+class MVKBufferView : public MVKRefCountedDeviceObject {
 
 public:
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
index ce8205d..c346ba3 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
@@ -129,7 +129,6 @@
 #pragma mark -
 #pragma mark MVKBufferView
 
-
 #pragma mark Metal
 
 id<MTLTexture> MVKBufferView::getMTLTexture() {
@@ -153,7 +152,7 @@
 
 #pragma mark Construction
 
-MVKBufferView::MVKBufferView(MVKDevice* device, const VkBufferViewCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+MVKBufferView::MVKBufferView(MVKDevice* device, const VkBufferViewCreateInfo* pCreateInfo) : MVKRefCountedDeviceObject(device) {
     _buffer = (MVKBuffer*)pCreateInfo->buffer;
     _mtlBufferOffset = _buffer->getMTLBufferOffset() + pCreateInfo->offset;
     _mtlPixelFormat = mtlPixelFormatFromVkFormat(pCreateInfo->format);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index c5fdd2b..ec6323d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -239,6 +239,9 @@
 	/** Constructs an instance. */
 	MVKDescriptorBinding(MVKDescriptorSetLayoutBinding* pBindingLayout);
 
+	/** Destructor. */
+	~MVKDescriptorBinding();
+
 protected:
 	friend class MVKDescriptorSetLayoutBinding;
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index 05dd688..cb4778c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -633,9 +633,15 @@
 			for (uint32_t i = 0; i < dstCnt; i++) {
 				uint32_t dstIdx = dstStartIndex + i;
 				const auto* pImgInfo = &get<VkDescriptorImageInfo>(pData, stride, srcStartIndex + i);
+				auto* oldSampler = (MVKSampler*)_imageBindings[dstIdx].sampler;
 				_imageBindings[dstIdx] = *pImgInfo;
 				if (_hasDynamicSamplers) {
-					_mtlSamplers[dstIdx] = pImgInfo->sampler ? ((MVKSampler*)pImgInfo->sampler)->getMTLSamplerState() : nil;
+					auto* mvkSampler = (MVKSampler*)pImgInfo->sampler;
+					mvkSampler->retain();
+					_mtlSamplers[dstIdx] = mvkSampler ? mvkSampler->getMTLSamplerState() : nil;
+				}
+				if (oldSampler) {
+					oldSampler->release();
 				}
 			}
 			break;
@@ -644,10 +650,22 @@
 			for (uint32_t i = 0; i < dstCnt; i++) {
 				uint32_t dstIdx = dstStartIndex + i;
 				const auto* pImgInfo = &get<VkDescriptorImageInfo>(pData, stride, srcStartIndex + i);
+				auto* mvkImageView = (MVKImageView*)pImgInfo->imageView;
+				auto* oldImageView = (MVKImageView*)_imageBindings[dstIdx].imageView;
+				auto* oldSampler = (MVKSampler*)_imageBindings[dstIdx].sampler;
+				mvkImageView->retain();
 				_imageBindings[dstIdx] = *pImgInfo;
-				_mtlTextures[dstIdx] = pImgInfo->imageView ? ((MVKImageView*)pImgInfo->imageView)->getMTLTexture() : nil;
+				_mtlTextures[dstIdx] = mvkImageView ? mvkImageView->getMTLTexture() : nil;
 				if (_hasDynamicSamplers) {
-					_mtlSamplers[dstIdx] = pImgInfo->sampler ? ((MVKSampler*)pImgInfo->sampler)->getMTLSamplerState() : nil;
+					auto* mvkSampler = (MVKSampler*)pImgInfo->sampler;
+					mvkSampler->retain();
+					_mtlSamplers[dstIdx] = mvkSampler ? mvkSampler->getMTLSamplerState() : nil;
+				}
+				if (oldImageView) {
+					oldImageView->release();
+				}
+				if (oldSampler) {
+					oldSampler->release();
 				}
 			}
 			break;
@@ -658,8 +676,16 @@
 			for (uint32_t i = 0; i < dstCnt; i++) {
 				uint32_t dstIdx = dstStartIndex + i;
 				const auto* pImgInfo = &get<VkDescriptorImageInfo>(pData, stride, srcStartIndex + i);
+                auto* mvkImageView = (MVKImageView*)pImgInfo->imageView;
+                auto* oldImageView = (MVKImageView*)_imageBindings[dstIdx].imageView;
+                if (mvkImageView) {
+                    mvkImageView->retain();
+                }
 				_imageBindings[dstIdx] = *pImgInfo;
-				_mtlTextures[dstIdx] = pImgInfo->imageView ? ((MVKImageView*)pImgInfo->imageView)->getMTLTexture() : nil;
+				_mtlTextures[dstIdx] = mvkImageView ? mvkImageView->getMTLTexture() : nil;
+                if (oldImageView) {
+                    oldImageView->release();
+                }
 			}
 			break;
 
@@ -670,10 +696,17 @@
 			for (uint32_t i = 0; i < dstCnt; i++) {
 				uint32_t dstIdx = dstStartIndex + i;
 				const auto* pBuffInfo = &get<VkDescriptorBufferInfo>(pData, stride, srcStartIndex + i);
+				auto* oldBuff = (MVKBuffer*)_bufferBindings[dstIdx].buffer;
 				_bufferBindings[dstIdx] = *pBuffInfo;
-                MVKBuffer* mtlBuff = (MVKBuffer*)pBuffInfo->buffer;
+                auto* mtlBuff = (MVKBuffer*)pBuffInfo->buffer;
+                if (mtlBuff) {
+                    mtlBuff->retain();
+                }
 				_mtlBuffers[dstIdx] = mtlBuff ? mtlBuff->getMTLBuffer() : nil;
 				_mtlBufferOffsets[dstIdx] = mtlBuff ? (mtlBuff->getMTLBufferOffset() + pBuffInfo->offset) : 0;
+				if (oldBuff) {
+					oldBuff->release();
+				}
 			}
 			break;
 
@@ -682,8 +715,16 @@
             for (uint32_t i = 0; i < dstCnt; i++) {
                 uint32_t dstIdx = dstStartIndex + i;
                 const auto* pBuffView = &get<VkBufferView>(pData, stride, srcStartIndex + i);
+                auto* mvkBuffView = (MVKBufferView*)*pBuffView;
+				auto* oldBuffView = (MVKBufferView*)_texelBufferBindings[dstIdx];
+                if (mvkBuffView) {
+                    mvkBuffView->retain();
+                }
                 _texelBufferBindings[dstIdx] = *pBuffView;
-                _mtlTextures[dstIdx] = *pBuffView ? ((MVKBufferView*)*pBuffView)->getMTLTexture() : nil;
+                _mtlTextures[dstIdx] = mvkBuffView ? mvkBuffView->getMTLTexture() : nil;
+				if (oldBuffView) {
+					oldBuffView->release();
+				}
             }
 			break;
 		default:
@@ -792,6 +833,27 @@
     _pBindingLayout = pBindingLayout;
 }
 
+MVKDescriptorBinding::~MVKDescriptorBinding() {
+	for (const VkDescriptorImageInfo& imgInfo : _imageBindings) {
+		if (imgInfo.imageView) {
+			((MVKImageView*)imgInfo.imageView)->release();
+		}
+		if (imgInfo.sampler) {
+			((MVKSampler*)imgInfo.sampler)->release();
+		}
+	}
+	for (const VkDescriptorBufferInfo& buffInfo : _bufferBindings) {
+		if (buffInfo.buffer) {
+			((MVKBuffer*)buffInfo.buffer)->release();
+		}
+	}
+	for (VkBufferView buffView : _texelBufferBindings) {
+		if (buffView) {
+			((MVKBufferView*)buffView)->release();
+		}
+	}
+}
+
 /**
  * If the descriptor set layout binding contains immutable samplers, immediately populate
  * the corresponding Metal sampler in this descriptor binding from it. Otherwise add a null
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index 81e8ea1..213c690 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -647,6 +647,37 @@
 
 
 #pragma mark -
+#pragma mark MVKRefCountedDeviceObject
+
+/** Represents a device-spawned object that can live past destruction by the client. */
+class MVKRefCountedDeviceObject : public MVKBaseDeviceObject {
+
+public:
+
+    /** Increments the object reference count by one. */
+    void retain();
+
+    /** Decrements the object reference count by one. */
+    void release();
+
+    void destroy() override;
+
+#pragma mark Construction
+
+	MVKRefCountedDeviceObject(MVKDevice* device) : MVKBaseDeviceObject(device) {}
+
+private:
+
+    bool decrementRetainCount();
+    bool markDestroyed();
+
+	std::mutex _refLock;
+    unsigned _refCount = 0;
+    bool _isDestroyed = false;
+};
+
+
+#pragma mark -
 #pragma mark MVKDeviceObjectPool
 
 /** Manages a pool of instances of a particular object type that requires an MVKDevice during construction. */
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 281ea47..4c22229 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -1749,3 +1749,37 @@
 }
 
 
+#pragma mark -
+#pragma mark MVKRefCountedDeviceObject
+
+void MVKRefCountedDeviceObject::retain() {
+	lock_guard<mutex> lock(_refLock);
+
+	_refCount++;
+}
+
+void MVKRefCountedDeviceObject::release() {
+	if (decrementRetainCount()) { destroy(); }
+}
+
+// Decrements the reference count, and returns whether it's time to destroy this object.
+bool MVKRefCountedDeviceObject::decrementRetainCount() {
+	lock_guard<mutex> lock(_refLock);
+
+	if (_refCount > 0) { _refCount--; }
+	return (_isDestroyed && _refCount == 0);
+}
+
+void MVKRefCountedDeviceObject::destroy() {
+	if (markDestroyed()) { MVKBaseDeviceObject::destroy(); }
+}
+
+// Marks this object as destroyed, and returns whether no references are left outstanding.
+bool MVKRefCountedDeviceObject::markDestroyed() {
+	lock_guard<mutex> lock(_refLock);
+
+	_isDestroyed = true;
+	return _refCount == 0;
+}
+
+
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index 9c3c98b..0a6b2e0 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -245,7 +245,7 @@
 #pragma mark MVKImageView
 
 /** Represents a Vulkan image view. */
-class MVKImageView : public MVKBaseDeviceObject {
+class MVKImageView : public MVKRefCountedDeviceObject {
 
 public:
 
@@ -308,7 +308,7 @@
 #pragma mark MVKSampler
 
 /** Represents a Vulkan sampler. */
-class MVKSampler : public MVKBaseDeviceObject {
+class MVKSampler : public MVKRefCountedDeviceObject {
 
 public:
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 239771f..e6edb3d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -689,7 +689,7 @@
 
 #pragma mark Construction
 
-MVKImageView::MVKImageView(MVKDevice* device, const VkImageViewCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+MVKImageView::MVKImageView(MVKDevice* device, const VkImageViewCreateInfo* pCreateInfo) : MVKRefCountedDeviceObject(device) {
 
 	_image = (MVKImage*)pCreateInfo->image;
 	_usage = _image->_usage;
@@ -913,7 +913,7 @@
 }
 
 // Constructs an instance on the specified image.
-MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKRefCountedDeviceObject(device) {
     _mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: getMTLSamplerDescriptor(pCreateInfo)];
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKResource.h b/MoltenVK/MoltenVK/GPUObjects/MVKResource.h
index 5f7ce03..5e3adfc 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKResource.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKResource.h
@@ -28,7 +28,7 @@
 #pragma mark MVKResource
 
 /** Represents an abstract Vulkan resource. Specialized subclasses include MVKBuffer and MVKImage. */
-class MVKResource : public MVKBaseDeviceObject {
+class MVKResource : public MVKRefCountedDeviceObject {
 
 public:
 
@@ -74,7 +74,7 @@
 	
 #pragma mark Construction
 
-    MVKResource(MVKDevice* device) : MVKBaseDeviceObject(device) {}
+    MVKResource(MVKDevice* device) : MVKRefCountedDeviceObject(device) {}
 
 protected:
 	virtual bool needsHostReadSync(VkPipelineStageFlags srcStageMask,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
index 9fc8b13..9fbf517 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
@@ -101,34 +101,23 @@
 #pragma mark MVKSignalable
 
 /** Abstract class for Vulkan semaphores and fences. */
-class MVKSignalable : public MVKBaseDeviceObject {
+class MVKSignalable : public MVKRefCountedDeviceObject {
 
 public:
 
 	/* Called when this object has been added to a tracker for later signaling. */
-	void wasAddedToSignaler();
+	void wasAddedToSignaler() { retain(); }
 
 	/**
 	 * Called when this object has been removed from a tracker for later signaling.
 	 * If this object was destroyed while this signal was pending, it will now be deleted.
 	 */
-	void wasRemovedFromSignaler();
-
-	/** If this object is waiting to be signaled, deletion will be deferred until then. */
-	void destroy() override;
+	void wasRemovedFromSignaler() { release(); }
 
 
 #pragma mark Construction
 
-	MVKSignalable(MVKDevice* device) : MVKBaseDeviceObject(device) {}
-
-protected:
-	bool decrementSignalerCount();
-	bool markDestroyed();
-
-	std::mutex _signalerLock;
-	uint32_t _signalerCount = 0;
-	bool _isDestroyed = false;
+	MVKSignalable(MVKDevice* device) : MVKRefCountedDeviceObject(device) {}
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
index 0d3851a..a4f1a1b 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
@@ -66,40 +66,6 @@
 
 
 #pragma mark -
-#pragma mark MVKSignalable
-
-void MVKSignalable::wasAddedToSignaler() {
-	lock_guard<mutex> lock(_signalerLock);
-
-	_signalerCount++;
-}
-
-void MVKSignalable::wasRemovedFromSignaler() {
-	if (decrementSignalerCount()) { destroy(); }
-}
-
-// Decrements the signaler count, and returns whether it's time to destroy this object.
-bool MVKSignalable::decrementSignalerCount() {
-	lock_guard<mutex> lock(_signalerLock);
-
-	if (_signalerCount > 0) { _signalerCount--; }
-	return (_isDestroyed && _signalerCount == 0);
-}
-
-void MVKSignalable::destroy() {
-	if (markDestroyed()) { MVKBaseDeviceObject::destroy(); }
-}
-
-// Marks this object as destroyed, and returns whether the compilation is complete.
-bool MVKSignalable::markDestroyed() {
-	lock_guard<mutex> lock(_signalerLock);
-
-	_isDestroyed = true;
-	return _signalerCount == 0;
-}
-
-
-#pragma mark -
 #pragma mark MVKSemaphore
 
 bool MVKSemaphore::wait(uint64_t timeout) {