Merge pull request #678 from cdavis5e/more-format-fixes

More format feature fixes.
diff --git a/Common/MVKOSExtensions.mm b/Common/MVKOSExtensions.mm
index 7759e48..39ef03b 100644
--- a/Common/MVKOSExtensions.mm
+++ b/Common/MVKOSExtensions.mm
@@ -76,10 +76,12 @@
 #pragma mark Process environment
 
 string mvkGetEnvVar(string varName, bool* pWasFound) {
-	NSDictionary* env = [[NSProcessInfo processInfo] environment];
-	NSString* envStr = env[@(varName.c_str())];
-	if (pWasFound) { *pWasFound = envStr != nil; }
-	return envStr ? envStr.UTF8String : "";
+	@autoreleasepool {
+		NSDictionary*nsEnv = [[NSProcessInfo processInfo] environment];
+		NSString* envStr = nsEnv[@(varName.c_str())];
+		if (pWasFound) { *pWasFound = envStr != nil; }
+		return envStr ? envStr.UTF8String : "";
+	}
 }
 
 int64_t mvkGetEnvVarInt64(string varName, bool* pWasFound) {
diff --git a/Demos/LunarG-VulkanSamples/Cube/macOS/DemoViewController.m b/Demos/LunarG-VulkanSamples/Cube/macOS/DemoViewController.m
index 6018369..df24993 100644
--- a/Demos/LunarG-VulkanSamples/Cube/macOS/DemoViewController.m
+++ b/Demos/LunarG-VulkanSamples/Cube/macOS/DemoViewController.m
@@ -42,8 +42,12 @@
 	[super viewDidLoad];
 
 	self.view.wantsLayer = YES;		// Back the view with a layer created by the makeBackingLayer method.
-	const char* arg = "cube";
-	demo_main(&demo, self.view.layer, 1, &arg);
+
+	uint32_t argc = 2;
+	const char* argv[argc];
+	argv[0] = "cube";
+	argv[1] = "--use_staging";
+	demo_main(&demo, self.view.layer, argc, argv);
 
 	CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink);
 	CVDisplayLinkSetOutputCallback(_displayLink, &DisplayLinkCallback, &demo);
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 22a63de..d763dc3 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -31,9 +31,12 @@
 - Fix pipeline cache lookups.
 - Fix race condition between swapchain image destruction and presentation completion callback.
 - Set Metal texture usage to allow texture copy via view.
+- Fix memory leak in debug marker and debug utils labelling.
+- Reduce use of autoreleased Obj-C objects, and ensure those remaining are 
+  covered by deliberate autorelease pools. 
 - `vkCmdCopyImage()` support copying between compressed and uncompressed formats
   and validate that formats are compatible for copying.
-- `vkCmdBufferImageCopy()` fix crash when setting bytes per image in non-arrayed images. 
+- `vkCmdBufferImageCopy()` fix crash when setting bytes per image in non-arrayed images.
 - Document that the functions in `vk_mvk_moltenvk.h` cannot be used with objects 
   retrieved through the *Vulkan SDK Loader and Layers* framework.
 - Update `VK_MVK_MOLTENVK_SPEC_VERSION` to 21.
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
index bd7110a..231924a 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.h
@@ -67,8 +67,7 @@
 	bool _isSrcCompressed;
 	bool _isDstCompressed;
 	bool _canCopyFormats;
-	bool _shouldUseTextureView;
-	bool _shouldUseTempBuffer;
+	bool _useTempBuffer;
 	std::vector<VkImageCopy> _imageCopyRegions;
 	std::vector<VkBufferImageCopy> _srcTmpBuffImgCopies;
 	std::vector<VkBufferImageCopy> _dstTmpBuffImgCopies;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
index c7b9087..40c1fa1 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
@@ -77,8 +77,7 @@
 	uint32_t dstBytesPerBlock = mvkMTLPixelFormatBytesPerBlock(_dstMTLPixFmt);
 
 	_canCopyFormats = (srcBytesPerBlock == dstBytesPerBlock) && (_srcSampleCount == _dstSampleCount);
-	_shouldUseTextureView = (_srcMTLPixFmt != _dstMTLPixFmt) && !(_isSrcCompressed || _isDstCompressed);	// Different formats and neither is compressed
-	_shouldUseTempBuffer = (_srcMTLPixFmt != _dstMTLPixFmt) && (_isSrcCompressed || _isDstCompressed);		// Different formats and at least one is compressed
+	_useTempBuffer = (_srcMTLPixFmt != _dstMTLPixFmt) && (_isSrcCompressed || _isDstCompressed);	// Different formats and at least one is compressed
 
 	_commandUse = commandUse;
 	_tmpBuffSize = 0;
@@ -89,7 +88,7 @@
 }
 
 void MVKCmdCopyImage::addImageCopyRegion(const VkImageCopy& region) {
-	if (_shouldUseTempBuffer) {
+	if (_useTempBuffer) {
 		addTempBufferImageCopyRegion(region);	// Convert to image->buffer->image copies
 	} else {
 		_imageCopyRegions.push_back(region);
@@ -133,15 +132,14 @@
 }
 
 void MVKCmdCopyImage::encode(MVKCommandEncoder* cmdEncoder) {
-	id<MTLTexture> srcMTLTex = _srcImage->getMTLTexture();
+	// Unless we need to use an intermediary buffer copy, map the source pixel format to the
+	// dest pixel format through a texture view on the source texture. If the source and dest
+	// pixel formats are the same, this will simply degenerate to the source texture itself.
+	MTLPixelFormat mapSrcMTLPixFmt = _useTempBuffer ? _srcMTLPixFmt : _dstMTLPixFmt;
+	id<MTLTexture> srcMTLTex = _srcImage->getMTLTexture(mapSrcMTLPixFmt);
 	id<MTLTexture> dstMTLTex = _dstImage->getMTLTexture();
 	if ( !srcMTLTex || !dstMTLTex ) { return; }
 
-	// If the pixel formats are different but mappable, use a texture view on the source texture
-	if (_shouldUseTextureView) {
-		srcMTLTex = [[srcMTLTex newTextureViewWithPixelFormat: _dstMTLPixFmt] autorelease];
-	}
-
 	id<MTLBlitCommandEncoder> mtlBlitEnc = cmdEncoder->getMTLBlitEncoder(_commandUse);
 
 	// If copies can be performed using direct texture-texture copying, do so
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
index 0749be9..a14e2de 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
@@ -357,8 +357,8 @@
      */
     id<MTLDepthStencilState> newMTLDepthStencilState(MVKMTLDepthStencilDescriptorData& dsData);
 
-    /** Returns an autoreleased MTLStencilDescriptor constructed from the stencil data. */
-    MTLStencilDescriptor* getMTLStencilDescriptor(MVKMTLStencilDescriptorData& sData);
+    /** Returns an retained MTLStencilDescriptor constructed from the stencil data. */
+    MTLStencilDescriptor* newMTLStencilDescriptor(MVKMTLStencilDescriptorData& sData);
 
     /**
      * Returns a new MVKImage configured with content held in Private storage.
@@ -405,15 +405,15 @@
 protected:
 	void initMTLLibrary();
 	void initImageDeviceMemory();
-	id<MTLFunction> getBlitFragFunction(MVKRPSKeyBlitImg& blitKey);
-	id<MTLFunction> getClearVertFunction(MVKRPSKeyClearAtt& attKey);
-	id<MTLFunction> getClearFragFunction(MVKRPSKeyClearAtt& attKey);
+	id<MTLFunction> newBlitFragFunction(MVKRPSKeyBlitImg& blitKey);
+	id<MTLFunction> newClearVertFunction(MVKRPSKeyClearAtt& attKey);
+	id<MTLFunction> newClearFragFunction(MVKRPSKeyClearAtt& attKey);
 	NSString* getMTLFormatTypeString(MTLPixelFormat mtlPixFmt);
-    id<MTLFunction> getFunctionNamed(const char* funcName);
+    id<MTLFunction> newFunctionNamed(const char* funcName);
 	id<MTLFunction> newMTLFunction(NSString* mslSrcCode, NSString* funcName);
 	id<MTLRenderPipelineState> newMTLRenderPipelineState(MTLRenderPipelineDescriptor* plDesc,
 														 MVKVulkanAPIDeviceObject* owner);
-	id<MTLComputePipelineState> newMTLComputePipelineState(id<MTLFunction> mtlFunction,
+	id<MTLComputePipelineState> newMTLComputePipelineState(const char* funcName,
 														   MVKVulkanAPIDeviceObject* owner);
 
 	id<MTLLibrary> _mtlLibrary;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
index bf86b8f..20b6f32 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
@@ -33,11 +33,13 @@
 
 id<MTLRenderPipelineState> MVKCommandResourceFactory::newCmdBlitImageMTLRenderPipelineState(MVKRPSKeyBlitImg& blitKey,
 																							MVKVulkanAPIDeviceObject* owner) {
-    MTLRenderPipelineDescriptor* plDesc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
+	id<MTLFunction> vtxFunc = newFunctionNamed("vtxCmdBlitImage");				// temp retain
+	id<MTLFunction> fragFunc = newBlitFragFunction(blitKey);					// temp retain
+    MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// temp retain
     plDesc.label = @"CmdBlitImage";
 
-	plDesc.vertexFunction = getFunctionNamed("vtxCmdBlitImage");
-    plDesc.fragmentFunction = getBlitFragFunction(blitKey);
+	plDesc.vertexFunction = vtxFunc;
+	plDesc.fragmentFunction = fragFunc;
 	plDesc.sampleCount = blitKey.dstSampleCount;
 
 	plDesc.colorAttachments[0].pixelFormat = blitKey.getDstMTLPixelFormat();
@@ -71,12 +73,18 @@
     vbDesc.stepRate = 1;
     vbDesc.stride = vtxStride;
 
-    return newMTLRenderPipelineState(plDesc, owner);
+    id<MTLRenderPipelineState> rps = newMTLRenderPipelineState(plDesc, owner);
+
+	[vtxFunc release];															// temp release
+	[fragFunc release];															// temp release
+	[plDesc release];															// temp release
+
+	return rps;
 }
 
 id<MTLSamplerState> MVKCommandResourceFactory::newCmdBlitImageMTLSamplerState(MTLSamplerMinMagFilter mtlFilter) {
 
-    MTLSamplerDescriptor* sDesc = [[[MTLSamplerDescriptor alloc] init] autorelease];
+    MTLSamplerDescriptor* sDesc = [MTLSamplerDescriptor new];					// temp retain
     sDesc.rAddressMode = MTLSamplerAddressModeClampToZero;
     sDesc.sAddressMode = MTLSamplerAddressModeClampToZero;
     sDesc.tAddressMode = MTLSamplerAddressModeClampToZero;
@@ -84,15 +92,22 @@
     sDesc.normalizedCoordinates = YES;
     sDesc.minFilter = mtlFilter;
     sDesc.magFilter = mtlFilter;
-    return [getMTLDevice() newSamplerStateWithDescriptor: sDesc];
+
+	id<MTLSamplerState> ss = [getMTLDevice() newSamplerStateWithDescriptor: sDesc];
+
+	[sDesc release];															// temp release
+
+	return ss;
 }
 
 id<MTLRenderPipelineState> MVKCommandResourceFactory::newCmdClearMTLRenderPipelineState(MVKRPSKeyClearAtt& attKey,
 																						MVKVulkanAPIDeviceObject* owner) {
-    MTLRenderPipelineDescriptor* plDesc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
+	id<MTLFunction> vtxFunc = newClearVertFunction(attKey);						// temp retain
+	id<MTLFunction> fragFunc = newClearFragFunction(attKey);					// temp retain
+	MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// temp retain
     plDesc.label = @"CmdClearAttachments";
-	plDesc.vertexFunction = getClearVertFunction(attKey);
-    plDesc.fragmentFunction = getClearFragFunction(attKey);
+	plDesc.vertexFunction = vtxFunc;
+    plDesc.fragmentFunction = fragFunc;
 	plDesc.sampleCount = attKey.mtlSampleCount;
 	plDesc.inputPrimitiveTopologyMVK = MTLPrimitiveTopologyClassTriangle;
 
@@ -127,19 +142,23 @@
     vbDesc.stepRate = 1;
     vbDesc.stride = vtxStride;
 
-    return newMTLRenderPipelineState(plDesc, owner);
+	id<MTLRenderPipelineState> rps = newMTLRenderPipelineState(plDesc, owner);
+
+	[vtxFunc release];															// temp release
+	[fragFunc release];															// temp release
+	[plDesc release];															// temp release
+
+	return rps;
 }
 
-id<MTLFunction> MVKCommandResourceFactory::getBlitFragFunction(MVKRPSKeyBlitImg& blitKey) {
-	id<MTLFunction> mtlFunc = nil;
-
-	NSString* typeStr = getMTLFormatTypeString(blitKey.getSrcMTLPixelFormat());
-
-	bool isArrayType = blitKey.isSrcArrayType();
-	NSString* arraySuffix = isArrayType ? @"_array" : @"";
-	NSString* sliceArg = isArrayType ? @", srcSlice" : @"";
-
+id<MTLFunction> MVKCommandResourceFactory::newBlitFragFunction(MVKRPSKeyBlitImg& blitKey) {
 	@autoreleasepool {
+		NSString* typeStr = getMTLFormatTypeString(blitKey.getSrcMTLPixelFormat());
+
+		bool isArrayType = blitKey.isSrcArrayType();
+		NSString* arraySuffix = isArrayType ? @"_array" : @"";
+		NSString* sliceArg = isArrayType ? @", srcSlice" : @"";
+
 		NSMutableString* msl = [NSMutableString stringWithCapacity: (2 * KIBI) ];
 		[msl appendLineMVK: @"#include <metal_stdlib>"];
 		[msl appendLineMVK: @"using namespace metal;"];
@@ -161,14 +180,13 @@
 		[msl appendLineMVK];
 		[msl appendLineMVK: @"}"];
 
-		mtlFunc = newMTLFunction(msl, funcName);
 //		MVKLogDebug("\n%s", msl.UTF8String);
+
+		return newMTLFunction(msl, funcName);
 	}
-	return [mtlFunc autorelease];
 }
 
-id<MTLFunction> MVKCommandResourceFactory::getClearVertFunction(MVKRPSKeyClearAtt& attKey) {
-	id<MTLFunction> mtlFunc = nil;
+id<MTLFunction> MVKCommandResourceFactory::newClearVertFunction(MVKRPSKeyClearAtt& attKey) {
 	@autoreleasepool {
 		NSMutableString* msl = [NSMutableString stringWithCapacity: (2 * KIBI) ];
 		[msl appendLineMVK: @"#include <metal_stdlib>"];
@@ -197,14 +215,13 @@
 		[msl appendLineMVK: @"    return varyings;"];
 		[msl appendLineMVK: @"}"];
 
-		mtlFunc = newMTLFunction(msl, funcName);
 //		MVKLogDebug("\n%s", msl.UTF8String);
+
+		return newMTLFunction(msl, funcName);
 	}
-	return [mtlFunc autorelease];
 }
 
-id<MTLFunction> MVKCommandResourceFactory::getClearFragFunction(MVKRPSKeyClearAtt& attKey) {
-	id<MTLFunction> mtlFunc = nil;
+id<MTLFunction> MVKCommandResourceFactory::newClearFragFunction(MVKRPSKeyClearAtt& attKey) {
 	@autoreleasepool {
 		NSMutableString* msl = [NSMutableString stringWithCapacity: (2 * KIBI) ];
 		[msl appendLineMVK: @"#include <metal_stdlib>"];
@@ -243,10 +260,10 @@
 		[msl appendLineMVK: @"    return ccOut;"];
 		[msl appendLineMVK: @"}"];
 
-		mtlFunc = newMTLFunction(msl, funcName);
 //		MVKLogDebug("\n%s", msl.UTF8String);
+
+		return newMTLFunction(msl, funcName);
 	}
-	return [mtlFunc autorelease];
 }
 
 NSString* MVKCommandResourceFactory::getMTLFormatTypeString(MTLPixelFormat mtlPixFmt) {
@@ -265,12 +282,12 @@
 
 id<MTLDepthStencilState> MVKCommandResourceFactory::newMTLDepthStencilState(bool useDepth, bool useStencil) {
 
-	MTLDepthStencilDescriptor* dsDesc = [[[MTLDepthStencilDescriptor alloc] init] autorelease];
+	MTLDepthStencilDescriptor* dsDesc = [MTLDepthStencilDescriptor new];	// temp retain
 	dsDesc.depthCompareFunction = MTLCompareFunctionAlways;
 	dsDesc.depthWriteEnabled = useDepth;
 
 	if (useStencil) {
-		MTLStencilDescriptor* sDesc = [[[MTLStencilDescriptor alloc] init] autorelease];
+		MTLStencilDescriptor* sDesc = [MTLStencilDescriptor new];			// temp retain
 		sDesc.stencilCompareFunction = MTLCompareFunctionAlways;
 		sDesc.stencilFailureOperation = MTLStencilOperationReplace;
 		sDesc.depthFailureOperation = MTLStencilOperationReplace;
@@ -278,28 +295,42 @@
 
 		dsDesc.frontFaceStencil = sDesc;
 		dsDesc.backFaceStencil = sDesc;
+
+		[sDesc release];													// temp release
 	} else {
 		dsDesc.frontFaceStencil = nil;
 		dsDesc.backFaceStencil = nil;
 	}
 
-	return [getMTLDevice() newDepthStencilStateWithDescriptor: dsDesc];
+	id<MTLDepthStencilState> dss = [getMTLDevice() newDepthStencilStateWithDescriptor: dsDesc];
+
+	[dsDesc release];														// temp release
+
+	return dss;
 }
 
 id<MTLDepthStencilState> MVKCommandResourceFactory::newMTLDepthStencilState(MVKMTLDepthStencilDescriptorData& dsData) {
-    MTLDepthStencilDescriptor* dsDesc = [[[MTLDepthStencilDescriptor alloc] init] autorelease];
+	MTLStencilDescriptor* fsDesc = newMTLStencilDescriptor(dsData.frontFaceStencilData);	// temp retain
+	MTLStencilDescriptor* bsDesc = newMTLStencilDescriptor(dsData.backFaceStencilData);		// temp retain
+	MTLDepthStencilDescriptor* dsDesc = [MTLDepthStencilDescriptor new];					// temp retain
     dsDesc.depthCompareFunction = (MTLCompareFunction)dsData.depthCompareFunction;
     dsDesc.depthWriteEnabled = dsData.depthWriteEnabled;
-    dsDesc.frontFaceStencil = getMTLStencilDescriptor(dsData.frontFaceStencilData);
-    dsDesc.backFaceStencil = getMTLStencilDescriptor(dsData.backFaceStencilData);
+	dsDesc.frontFaceStencil = fsDesc;
+    dsDesc.backFaceStencil = bsDesc;
 
-    return [getMTLDevice() newDepthStencilStateWithDescriptor: dsDesc];
+	id<MTLDepthStencilState> dss = [getMTLDevice() newDepthStencilStateWithDescriptor: dsDesc];
+
+	[fsDesc release];																		// temp release
+	[bsDesc release];																		// temp release
+	[dsDesc release];																		// temp release
+
+	return dss;
 }
 
-MTLStencilDescriptor* MVKCommandResourceFactory::getMTLStencilDescriptor(MVKMTLStencilDescriptorData& sData) {
+MTLStencilDescriptor* MVKCommandResourceFactory::newMTLStencilDescriptor(MVKMTLStencilDescriptorData& sData) {
     if ( !sData.enabled ) { return nil; }
 
-    MTLStencilDescriptor* sDesc = [[[MTLStencilDescriptor alloc] init] autorelease];
+    MTLStencilDescriptor* sDesc = [MTLStencilDescriptor new];		// retained
     sDesc.stencilCompareFunction = (MTLCompareFunction)sData.stencilCompareFunction;
     sDesc.stencilFailureOperation = (MTLStencilOperation)sData.stencilFailureOperation;
     sDesc.depthFailureOperation = (MTLStencilOperation)sData.depthFailureOperation;
@@ -362,66 +393,75 @@
 }
 
 id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyBufferBytesMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner) {
-	return newMTLComputePipelineState(getFunctionNamed("cmdCopyBufferBytes"), owner);
+	return newMTLComputePipelineState("cmdCopyBufferBytes", owner);
 }
 
 id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdFillBufferMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner) {
-	return newMTLComputePipelineState(getFunctionNamed("cmdFillBuffer"), owner);
+	return newMTLComputePipelineState("cmdFillBuffer", owner);
 }
 
 id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needTempBuf,
 																												  MVKVulkanAPIDeviceObject* owner) {
-	return newMTLComputePipelineState(getFunctionNamed(needTempBuf
-													   ? "cmdCopyBufferToImage3DDecompressTempBufferDXTn"
-													   : "cmdCopyBufferToImage3DDecompressDXTn"), owner);
+	return newMTLComputePipelineState(needTempBuf
+									  ? "cmdCopyBufferToImage3DDecompressTempBufferDXTn"
+									  : "cmdCopyBufferToImage3DDecompressDXTn", owner);
 }
 
 id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdDrawIndirectConvertBuffersMTLComputePipelineState(bool indexed,
 																											   MVKVulkanAPIDeviceObject* owner) {
-	return newMTLComputePipelineState(getFunctionNamed(indexed
-													   ? "cmdDrawIndexedIndirectConvertBuffers"
-													   : "cmdDrawIndirectConvertBuffers"), owner);
+	return newMTLComputePipelineState(indexed
+									  ? "cmdDrawIndexedIndirectConvertBuffers"
+									  : "cmdDrawIndirectConvertBuffers", owner);
 }
 
 id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(MTLIndexType type,
 																											   MVKVulkanAPIDeviceObject* owner) {
-	return newMTLComputePipelineState(getFunctionNamed(type == MTLIndexTypeUInt16
-													   ? "cmdDrawIndexedCopyIndex16Buffer"
-													   : "cmdDrawIndexedCopyIndex32Buffer"), owner);
+	return newMTLComputePipelineState(type == MTLIndexTypeUInt16
+									  ? "cmdDrawIndexedCopyIndex16Buffer"
+									  : "cmdDrawIndexedCopyIndex32Buffer", owner);
 }
 
 id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyQueryPoolResultsMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner) {
-	return newMTLComputePipelineState(getFunctionNamed("cmdCopyQueryPoolResultsToBuffer"), owner);
+	return newMTLComputePipelineState("cmdCopyQueryPoolResultsToBuffer", owner);
 }
 
 
 #pragma mark Support methods
 
-id<MTLFunction> MVKCommandResourceFactory::getFunctionNamed(const char* funcName) {
-    uint64_t startTime = _device->getPerformanceTimestamp();
-    id<MTLFunction> mtlFunc = [[_mtlLibrary newFunctionWithName: @(funcName)] autorelease];
-    _device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.functionRetrieval, startTime);
-    return mtlFunc;
+// Returns the retained MTLFunction with the name.
+// The caller is responsible for releasing the returned function object.
+id<MTLFunction> MVKCommandResourceFactory::newFunctionNamed(const char* funcName) {
+	uint64_t startTime = _device->getPerformanceTimestamp();
+	NSString* nsFuncName = [[NSString alloc] initWithUTF8String: funcName];		// temp retained
+	id<MTLFunction> mtlFunc = [_mtlLibrary newFunctionWithName: nsFuncName];	// retained
+	[nsFuncName release];														// temp release
+	_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.functionRetrieval, startTime);
+	return mtlFunc;
 }
 
 id<MTLFunction> MVKCommandResourceFactory::newMTLFunction(NSString* mslSrcCode, NSString* funcName) {
 	@autoreleasepool {
+		id<MTLFunction> mtlFunc = nil;
 		NSError* err = nil;
+
 		uint64_t startTime = _device->getPerformanceTimestamp();
-		id<MTLLibrary> mtlLib = [[getMTLDevice() newLibraryWithSource: mslSrcCode
-															  options: getDevice()->getMTLCompileOptions()
-																error: &err] autorelease];
+		id<MTLLibrary> mtlLib = [getMTLDevice() newLibraryWithSource: mslSrcCode
+															 options: getDevice()->getMTLCompileOptions()
+															   error: &err];	// temp retain
 		_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.mslCompile, startTime);
+
 		if (err) {
 			reportError(VK_ERROR_INITIALIZATION_FAILED,
 						"Could not compile support shader from MSL source (Error code %li):\n%s\n%s",
 						(long)err.code, mslSrcCode.UTF8String, err.localizedDescription.UTF8String);
-			return nil;
+		} else {
+			startTime = _device->getPerformanceTimestamp();
+			mtlFunc = [mtlLib newFunctionWithName: funcName];
+			_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.functionRetrieval, startTime);
 		}
 
-		startTime = _device->getPerformanceTimestamp();
-		id<MTLFunction> mtlFunc = [mtlLib newFunctionWithName: funcName];
-		_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.functionRetrieval, startTime);
+		[mtlLib release];														// temp release
+
 		return mtlFunc;
 	}
 }
@@ -434,11 +474,13 @@
     return rps;
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newMTLComputePipelineState(id<MTLFunction> mtlFunction,
+id<MTLComputePipelineState> MVKCommandResourceFactory::newMTLComputePipelineState(const char* funcName,
 																				  MVKVulkanAPIDeviceObject* owner) {
+	id<MTLFunction> mtlFunc = newFunctionNamed(funcName);							// temp retain
 	MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(owner);
-	id<MTLComputePipelineState> cps = plc->newMTLComputePipelineState(mtlFunction);		// retained
+	id<MTLComputePipelineState> cps = plc->newMTLComputePipelineState(mtlFunc);		// retained
 	plc->destroy();
+	[mtlFunc release];																// temp release
     return cps;
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index e22b724..ee21903 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -282,13 +282,18 @@
 #pragma mark Metal
 
 	/** Populates the specified structure with the Metal-specific features of this device. */
-	const MVKPhysicalDeviceMetalFeatures* getMetalFeatures() { return &_metalFeatures; }
+	inline const MVKPhysicalDeviceMetalFeatures* getMetalFeatures() { return &_metalFeatures; }
 
 	/** Returns the underlying Metal device. */
 	inline id<MTLDevice> getMTLDevice() { return _mtlDevice; }
     
     /*** Replaces the underlying Metal device .*/
-    inline void replaceMTLDevice(id<MTLDevice> mtlDevice) { [_mtlDevice autorelease]; _mtlDevice = [mtlDevice retain]; }
+    inline void replaceMTLDevice(id<MTLDevice> mtlDevice) {
+		if (mtlDevice != _mtlDevice) {
+			[_mtlDevice release];
+			_mtlDevice = [mtlDevice retain];
+		}
+	}
 
 #pragma mark Construction
 
@@ -554,7 +559,7 @@
 	inline id<MTLDevice> getMTLDevice() { return _physicalDevice->getMTLDevice(); }
 
 	/** Returns standard compilation options to be used when compiling MSL shaders. */
-	MTLCompileOptions* getMTLCompileOptions();
+	inline MTLCompileOptions* getMTLCompileOptions() { return _mtlCompileOptions; }
 
 	/** Returns the Metal vertex buffer index to use for the specified vertex attribute binding number.  */
 	uint32_t getMetalBufferIndexForVertexAttributeBinding(uint32_t binding);
@@ -657,6 +662,7 @@
     void initPerformanceTracking();
 	void initPhysicalDevice(MVKPhysicalDevice* physicalDevice);
 	void initQueues(const VkDeviceCreateInfo* pCreateInfo);
+	void initMTLCompileOptions();
 	void enableFeatures(const VkDeviceCreateInfo* pCreateInfo);
 	void enableFeatures(const VkBool32* pEnable, const VkBool32* pRequested, const VkBool32* pAvailable, uint32_t count);
 	void enableExtensions(const VkDeviceCreateInfo* pCreateInfo);
@@ -667,6 +673,7 @@
 
 	MVKPhysicalDevice* _physicalDevice;
     MVKCommandResourceFactory* _commandResourceFactory;
+	MTLCompileOptions* _mtlCompileOptions;
 	std::vector<std::vector<MVKQueue*>> _queuesByQueueFamilyIndex;
 	std::vector<MVKResource*> _resources;
 	std::mutex _rezLock;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index a50cb46..8586b29 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -1711,9 +1711,11 @@
 
 #endif
 
-	MVKLogInfo(logMsg.c_str(), _properties.deviceName, devTypeStr.c_str(), _properties.vendorID, _properties.deviceID,
-			   [[[NSUUID alloc] initWithUUIDBytes: _properties.pipelineCacheUUID] autorelease].UUIDString.UTF8String,
+	NSUUID* nsUUID = [[NSUUID alloc] initWithUUIDBytes: _properties.pipelineCacheUUID];		// temp retain
+	MVKLogInfo(logMsg.c_str(), _properties.deviceName, devTypeStr.c_str(),
+			   _properties.vendorID, _properties.deviceID, nsUUID.UUIDString.UTF8String,
 			   SPIRVToMSLConversionOptions::printMSLVersion(_metalFeatures.mslVersion).c_str());
+	[nsUUID release];																		// temp release
 }
 
 MVKPhysicalDevice::~MVKPhysicalDevice() {
@@ -2164,12 +2166,6 @@
 
 #pragma mark Metal
 
-MTLCompileOptions* MVKDevice::getMTLCompileOptions() {
-	MTLCompileOptions* opts = [[MTLCompileOptions new] autorelease];
-	opts.languageVersion = _pMetalFeatures->mslVersionEnum;
-	return opts;
-}
-
 uint32_t MVKDevice::getMetalBufferIndexForVertexAttributeBinding(uint32_t binding) {
 	return ((_pMetalFeatures->maxPerStageBufferCount - 1) - binding);
 }
@@ -2240,6 +2236,8 @@
     _globalVisibilityResultMTLBuffer = nil;
     _globalVisibilityQueryCount = 0;
 
+	initMTLCompileOptions();	// Before command resource factory
+
 	_commandResourceFactory = new MVKCommandResourceFactory(this);
 
 	initQueues(pCreateInfo);
@@ -2451,12 +2449,19 @@
 	}
 }
 
+void MVKDevice::initMTLCompileOptions() {
+	_mtlCompileOptions = [MTLCompileOptions new];	// retained
+	_mtlCompileOptions.languageVersion = _pMetalFeatures->mslVersionEnum;
+}
+
 MVKDevice::~MVKDevice() {
 	for (auto& queues : _queuesByQueueFamilyIndex) {
 		mvkDestroyContainerContents(queues);
 	}
-    [_globalVisibilityResultMTLBuffer release];
 	_commandResourceFactory->destroy();
+
+	[_mtlCompileOptions release];
+    [_globalVisibilityResultMTLBuffer release];
 }
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index 0c5e3bf..2c03080 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -22,6 +22,7 @@
 #include "MVKSync.h"
 #include "MVKVector.h"
 #include <MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h>
+#include <unordered_map>
 #include <mutex>
 
 #import <IOSurface/IOSurfaceRef.h>
@@ -154,6 +155,9 @@
 	/** Returns the Metal texture underlying this image. */
 	id<MTLTexture> getMTLTexture();
 
+	/** Returns a Metal texture that interprets the pixels in the specified format. */
+	id<MTLTexture> getMTLTexture(MTLPixelFormat mtlPixFmt);
+
     /**
      * Sets this image to use the specified MTLTexture.
      *
@@ -243,7 +247,7 @@
 	virtual id<MTLTexture> newMTLTexture();
 	void resetMTLTexture();
     void resetIOSurface();
-	MTLTextureDescriptor* getMTLTextureDescriptor();
+	MTLTextureDescriptor* newMTLTextureDescriptor();
     void updateMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size);
     void getMTLTextureContent(MVKImageSubresource& subresource, VkDeviceSize offset, VkDeviceSize size);
 	bool shouldFlushHostMemory();
@@ -254,6 +258,7 @@
 						   VkImageMemoryBarrier* pImageMemoryBarrier);
 
 	std::vector<MVKImageSubresource> _subresources;
+	std::unordered_map<NSUInteger, id<MTLTexture>> _mtlTextureViews;
     VkExtent3D _extent;
     uint32_t _mipLevels;
     uint32_t _arrayLayers;
@@ -376,7 +381,7 @@
 
 protected:
 	void propogateDebugName() override {}
-	MTLSamplerDescriptor* getMTLSamplerDescriptor(const VkSamplerCreateInfo* pCreateInfo);
+	MTLSamplerDescriptor* newMTLSamplerDescriptor(const VkSamplerCreateInfo* pCreateInfo);
 	void initConstExprSampler(const VkSamplerCreateInfo* pCreateInfo);
 
 	id<MTLSamplerState> _mtlSamplerState;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index abda962..9204962 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -296,6 +296,22 @@
 	return _mtlTexture;
 }
 
+id<MTLTexture> MVKImage::getMTLTexture(MTLPixelFormat mtlPixFmt) {
+	if (mtlPixFmt == _mtlPixelFormat) { return getMTLTexture(); }
+
+	id<MTLTexture> mtlTex = _mtlTextureViews[mtlPixFmt];
+	if ( !mtlTex ) {
+		// Lock and check again in case another thread has created the texture.
+		lock_guard<mutex> lock(_lock);
+		mtlTex = _mtlTextureViews[mtlPixFmt];
+		if ( !mtlTex ) {
+			mtlTex = [getMTLTexture() newTextureViewWithPixelFormat: mtlPixFmt];	// retained
+			_mtlTextureViews[mtlPixFmt] = mtlTex;
+		}
+	}
+	return mtlTex;
+}
+
 VkResult MVKImage::setMTLTexture(id<MTLTexture> mtlTexture) {
     lock_guard<mutex> lock(_lock);
     resetMTLTexture();
@@ -325,15 +341,21 @@
 // This implementation creates a new MTLTexture from a MTLTextureDescriptor and possible IOSurface.
 // Subclasses may override this function to create the MTLTexture in a different manner.
 id<MTLTexture> MVKImage::newMTLTexture() {
-    if (_ioSurface) {
-        return [getMTLDevice() newTextureWithDescriptor: getMTLTextureDescriptor() iosurface: _ioSurface plane: 0];
+	id<MTLTexture> mtlTex = nil;
+	MTLTextureDescriptor* mtlTexDesc = newMTLTextureDescriptor();	// temp retain
+
+	if (_ioSurface) {
+		mtlTex = [getMTLDevice() newTextureWithDescriptor: mtlTexDesc iosurface: _ioSurface plane: 0];
 	} else if (_usesTexelBuffer) {
-        return [_deviceMemory->_mtlBuffer newTextureWithDescriptor: getMTLTextureDescriptor()
-															offset: getDeviceMemoryOffset()
-													   bytesPerRow: _subresources[0].layout.rowPitch];
-    } else {
-        return [getMTLDevice() newTextureWithDescriptor: getMTLTextureDescriptor()];
-    }
+		mtlTex = [_deviceMemory->_mtlBuffer newTextureWithDescriptor: mtlTexDesc
+															  offset: getDeviceMemoryOffset()
+														 bytesPerRow: _subresources[0].layout.rowPitch];
+	} else {
+		mtlTex = [getMTLDevice() newTextureWithDescriptor: mtlTexDesc];
+	}
+
+	[mtlTexDesc release];											// temp release
+	return mtlTex;
 }
 
 // Removes and releases the MTLTexture object, so that it can be lazily created by getMTLTexture().
@@ -372,14 +394,16 @@
     } else {
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
-        _ioSurface = IOSurfaceCreate((CFDictionaryRef)@{
-                                                        (id)kIOSurfaceWidth: @(_extent.width),
-                                                        (id)kIOSurfaceHeight: @(_extent.height),
-                                                        (id)kIOSurfaceBytesPerElement: @(mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)),
-                                                        (id)kIOSurfaceElementWidth: @(mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width),
-                                                        (id)kIOSurfaceElementHeight: @(mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height),
-                                                        (id)kIOSurfaceIsGlobal: @(true),    // Deprecated but needed for interprocess transfers
-                                                        });
+		@autoreleasepool {
+			_ioSurface = IOSurfaceCreate((CFDictionaryRef)@{
+															(id)kIOSurfaceWidth: @(_extent.width),
+															(id)kIOSurfaceHeight: @(_extent.height),
+															(id)kIOSurfaceBytesPerElement: @(mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)),
+															(id)kIOSurfaceElementWidth: @(mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width),
+															(id)kIOSurfaceElementHeight: @(mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height),
+															(id)kIOSurfaceIsGlobal: @(true),    // Deprecated but needed for interprocess transfers
+															});
+		}
 #pragma clang diagnostic pop
 
     }
@@ -418,9 +442,10 @@
 	return usage;
 }
 
-// Returns an autoreleased Metal texture descriptor constructed from the properties of this image.
-MTLTextureDescriptor* MVKImage::getMTLTextureDescriptor() {
-	MTLTextureDescriptor* mtlTexDesc = [[MTLTextureDescriptor alloc] init];
+// Returns a Metal texture descriptor constructed from the properties of this image.
+// It is the caller's responsibility to release the returned descriptor object.
+MTLTextureDescriptor* MVKImage::newMTLTextureDescriptor() {
+	MTLTextureDescriptor* mtlTexDesc = [MTLTextureDescriptor new];	// retained
 #if MVK_MACOS
 	if (_is3DCompressed) {
 		// Metal doesn't yet support 3D compressed textures, so we'll decompress
@@ -443,7 +468,7 @@
 	mtlTexDesc.storageModeMVK = getMTLStorageMode();
 	mtlTexDesc.cpuCacheMode = getMTLCPUCacheMode();
 
-	return [mtlTexDesc autorelease];
+	return mtlTexDesc;
 }
 
 MTLStorageMode MVKImage::getMTLStorageMode() {
@@ -770,6 +795,7 @@
 	if (_deviceMemory) { _deviceMemory->removeImage(this); }
 	resetMTLTexture();
     resetIOSurface();
+	for (auto elem : _mtlTextureViews) { [elem.second release]; }
 }
 
 
@@ -1056,10 +1082,11 @@
 	return _requiresConstExprSampler;
 }
 
-// Returns an autoreleased Metal sampler descriptor constructed from the properties of this image.
-MTLSamplerDescriptor* MVKSampler::getMTLSamplerDescriptor(const VkSamplerCreateInfo* pCreateInfo) {
+// Returns an Metal sampler descriptor constructed from the properties of this image.
+// It is the caller's responsibility to release the returned descriptor object.
+MTLSamplerDescriptor* MVKSampler::newMTLSamplerDescriptor(const VkSamplerCreateInfo* pCreateInfo) {
 
-	MTLSamplerDescriptor* mtlSampDesc = [[MTLSamplerDescriptor alloc] init];
+	MTLSamplerDescriptor* mtlSampDesc = [MTLSamplerDescriptor new];		// retained
 	mtlSampDesc.sAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeU);
 	mtlSampDesc.tAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeV);
     mtlSampDesc.rAddressMode = mvkMTLSamplerAddressModeFromVkSamplerAddressMode(pCreateInfo->addressModeW);
@@ -1097,12 +1124,16 @@
 		}
 	}
 #endif
-	return [mtlSampDesc autorelease];
+	return mtlSampDesc;
 }
 
 MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
 	_requiresConstExprSampler = pCreateInfo->compareEnable && !_device->_pMetalFeatures->depthSampleCompare;
-    _mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: getMTLSamplerDescriptor(pCreateInfo)];
+
+	MTLSamplerDescriptor* mtlSampDesc = newMTLSamplerDescriptor(pCreateInfo);	// temp retain
+    _mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: mtlSampDesc];
+	[mtlSampDesc release];														// temp release
+
 	initConstExprSampler(pCreateInfo);
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
index aae5cf4..88c41af 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
@@ -252,28 +252,28 @@
 
 #pragma mark Object Creation
 
-// Returns an autoreleased array containing the MTLDevices available on this system,
-// sorted according to power, with higher power GPU's at the front of the array.
-// This ensures that a lazy app that simply grabs the first GPU will get a high-power
-// one by default. If the MVK_CONFIG_FORCE_LOW_POWER_GPU env var or build setting is set,
-// the returned array will only include low-power devices.
-// If Metal is not supported, ensure we return an empty array.
-static NSArray<id<MTLDevice>>* getAvailableMTLDevices() {
+// Returns a new array containing the MTLDevices available on this system, sorted according to power,
+// with higher power GPU's at the front of the array. This ensures that a lazy app that simply
+// grabs the first GPU will get a high-power one by default. If the MVK_CONFIG_FORCE_LOW_POWER_GPU
+// env var or build setting is set, the returned array will only include low-power devices.
+// It is the caller's responsibility to release the array when not required anymore.
+// If Metal is not supported, returns an empty array.
+static NSArray<id<MTLDevice>>* newAvailableMTLDevicesArray() {
+	NSMutableArray* mtlDevs = [NSMutableArray new];
+
 #if MVK_MACOS
-	NSArray* mtlDevs = [MTLCopyAllDevices() autorelease];
-	if ( !mtlDevs ) { return @[]; }
+	NSArray* rawMTLDevs = MTLCopyAllDevices();			// temp retain
+	if (rawMTLDevs) {
+		bool forceLowPower = MVK_CONFIG_FORCE_LOW_POWER_GPU;
+		MVK_SET_FROM_ENV_OR_BUILD_BOOL(forceLowPower, MVK_CONFIG_FORCE_LOW_POWER_GPU);
 
-	bool forceLowPower = MVK_CONFIG_FORCE_LOW_POWER_GPU;
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL(forceLowPower, MVK_CONFIG_FORCE_LOW_POWER_GPU);
-
-	if (forceLowPower) {
-		NSMutableArray* lpDevs = [[NSMutableArray new] autorelease];
-		for (id<MTLDevice> md in mtlDevs) {
-			if (md.isLowPower) { [lpDevs addObject: md]; }
+		// Populate the array of appropriate MTLDevices
+		for (id<MTLDevice> md in rawMTLDevs) {
+			if ( !forceLowPower || md.isLowPower ) { [mtlDevs addObject: md]; }
 		}
-		return lpDevs;
-	} else {
-		return [mtlDevs sortedArrayUsingComparator: ^(id<MTLDevice> md1, id<MTLDevice> md2) {
+
+		// Sort by power
+		[mtlDevs sortUsingComparator: ^(id<MTLDevice> md1, id<MTLDevice> md2) {
 			BOOL md1IsLP = md1.isLowPower;
 			BOOL md2IsLP = md2.isLowPower;
 
@@ -290,14 +290,18 @@
 
 			return md2IsLP ? NSOrderedAscending : NSOrderedDescending;
 		}];
-	}
 
+	}
+	[rawMTLDevs release];								// release temp
 #endif	// MVK_MACOS
 
 #if MVK_IOS
-	id<MTLDevice> mtlDev = MTLCreateSystemDefaultDevice();
-	return mtlDev ? [NSArray arrayWithObject: mtlDev] : @[];
+	id<MTLDevice> md = MTLCreateSystemDefaultDevice();
+	if (md) { [mtlDevs addObject: md]; }
+	[md release];
 #endif	// MVK_IOS
+
+	return mtlDevs;		// retained
 }
 
 MVKInstance::MVKInstance(const VkInstanceCreateInfo* pCreateInfo) : _enabledExtensions(this) {
@@ -326,11 +330,13 @@
 	}
 
 	// Populate the array of physical GPU devices
-	NSArray<id<MTLDevice>>* mtlDevices = getAvailableMTLDevices();
+	NSArray<id<MTLDevice>>* mtlDevices = newAvailableMTLDevicesArray();		// temp retain
 	_physicalDevices.reserve(mtlDevices.count);
 	for (id<MTLDevice> mtlDev in mtlDevices) {
 		_physicalDevices.push_back(new MVKPhysicalDevice(this, mtlDev));
 	}
+	[mtlDevices release];													// release temp
+
 	if (_physicalDevices.empty()) {
 		setConfigurationResult(reportError(VK_ERROR_INCOMPATIBLE_DRIVER, "Vulkan is not supported on this device. MoltenVK requires Metal, which is not available on this device."));
 	}
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index 7b56702..99bb888 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -240,10 +240,10 @@
     void initMVKShaderConverterContext(SPIRVToMSLConversionConfiguration& _shaderContext, const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
     void addVertexInputToShaderConverterContext(SPIRVToMSLConversionConfiguration& shaderContext, const VkGraphicsPipelineCreateInfo* pCreateInfo);
     void addPrevStageOutputToShaderConverterContext(SPIRVToMSLConversionConfiguration& shaderContext, std::vector<SPIRVShaderOutput>& outputs);
-    MTLRenderPipelineDescriptor* getMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
-    MTLRenderPipelineDescriptor* getMTLTessVertexStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
-	MTLComputePipelineDescriptor* getMTLTessControlStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
-	MTLRenderPipelineDescriptor* getMTLTessRasterStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
+    MTLRenderPipelineDescriptor* newMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData);
+    MTLRenderPipelineDescriptor* newMTLTessVertexStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
+	MTLComputePipelineDescriptor* newMTLTessControlStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
+	MTLRenderPipelineDescriptor* newMTLTessRasterStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo, const SPIRVTessReflectionData& reflectData, SPIRVToMSLConversionConfiguration& shaderContext);
 	bool addVertexShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext);
 	bool addTessCtlShaderToPipeline(MTLComputePipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext, std::vector<SPIRVShaderOutput>& prevOutput);
 	bool addTessEvalShaderToPipeline(MTLRenderPipelineDescriptor* plDesc, const VkGraphicsPipelineCreateInfo* pCreateInfo, SPIRVToMSLConversionConfiguration& shaderContext, std::vector<SPIRVShaderOutput>& prevOutput);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index 84fa930..c21340d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -206,7 +206,7 @@
             id<MTLComputePipelineState> plState;
 			const char* compilerType = "Tessellation control stage pipeline";
 			const MVKIndexMTLBufferBinding& indexBuff = cmdEncoder->_graphicsResourcesState._mtlIndexBufferBinding;
-            MTLComputePipelineDescriptor* plDesc = [[_mtlTessControlStageDesc copy] autorelease];  // Use a copy to be thread-safe.
+            MTLComputePipelineDescriptor* plDesc = [_mtlTessControlStageDesc copy];  // temp retain a copy to be thread-safe.
             if (!indexBuff.mtlBuffer && getInputControlPointCount() >= getOutputControlPointCount()) {
                 plState = getOrCompilePipeline(plDesc, _mtlTessControlStageState, compilerType);
             } else if (indexBuff.mtlIndexType == MTLIndexTypeUInt16) {
@@ -218,6 +218,8 @@
                 plDesc.stageInputDescriptor.layouts[kMVKTessCtlInputBufferIndex].stepFunction = MTLStepFunctionThreadPositionInGridXIndexed;
                 plState = getOrCompilePipeline(plDesc, _mtlTessControlStageIndex32State, compilerType);
             }
+			[plDesc release];														// temp release
+
 			if ( !_hasValidMTLPipelineStates ) { return; }
 
             id<MTLComputeCommandEncoder> tessCtlEnc = cmdEncoder->getMTLComputeEncoder(kMVKCommandUseTessellationControl);
@@ -426,10 +428,11 @@
 	_mtlPipelineState = nil;
 	_mtlTessControlStageDesc = nil;
 	if (!isTessellationPipeline()) {
-		MTLRenderPipelineDescriptor* plDesc = getMTLRenderPipelineDescriptor(pCreateInfo, reflectData);
+		MTLRenderPipelineDescriptor* plDesc = newMTLRenderPipelineDescriptor(pCreateInfo, reflectData);	// temp retain
 		if (plDesc) {
 			getOrCompilePipeline(plDesc, _mtlPipelineState);
 		}
+		[plDesc release];																				// temp release
 	} else {
 		// In this case, we need to create three render pipelines. But, the way Metal handles
 		// index buffers for compute stage-in means we have to defer creation of stage 2 until
@@ -437,24 +440,27 @@
 		SPIRVToMSLConversionConfiguration shaderContext;
 		initMVKShaderConverterContext(shaderContext, pCreateInfo, reflectData);
 
-		MTLRenderPipelineDescriptor* vtxPLDesc = getMTLTessVertexStageDescriptor(pCreateInfo, reflectData, shaderContext);
-		_mtlTessControlStageDesc = getMTLTessControlStageDescriptor(pCreateInfo, reflectData, shaderContext);	// retained
-		MTLRenderPipelineDescriptor* rastPLDesc = getMTLTessRasterStageDescriptor(pCreateInfo, reflectData, shaderContext);
+		MTLRenderPipelineDescriptor* vtxPLDesc = newMTLTessVertexStageDescriptor(pCreateInfo, reflectData, shaderContext);	// temp retain
+		_mtlTessControlStageDesc = newMTLTessControlStageDescriptor(pCreateInfo, reflectData, shaderContext);				// retained
+		MTLRenderPipelineDescriptor* rastPLDesc = newMTLTessRasterStageDescriptor(pCreateInfo, reflectData, shaderContext);	// temp retained
 		if (vtxPLDesc && _mtlTessControlStageDesc && rastPLDesc) {
 			if (getOrCompilePipeline(vtxPLDesc, _mtlTessVertexStageState)) {
 				getOrCompilePipeline(rastPLDesc, _mtlPipelineState);
 			}
 		}
+		[vtxPLDesc release];	// temp release
+		[rastPLDesc release];	// temp release
 	}
 }
 
-// Returns a MTLRenderPipelineDescriptor constructed from this instance, or nil if an error occurs.
-MTLRenderPipelineDescriptor* MVKGraphicsPipeline::getMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
+// Returns a retained MTLRenderPipelineDescriptor constructed from this instance, or nil if an error occurs.
+// It is the responsibility of the caller to release the returned descriptor.
+MTLRenderPipelineDescriptor* MVKGraphicsPipeline::newMTLRenderPipelineDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
 																				 const SPIRVTessReflectionData& reflectData) {
 	SPIRVToMSLConversionConfiguration shaderContext;
 	initMVKShaderConverterContext(shaderContext, pCreateInfo, reflectData);
 
-	MTLRenderPipelineDescriptor* plDesc = [[MTLRenderPipelineDescriptor new] autorelease];
+	MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// retained
 
 	// Add shader stages. Compile vertex shader before others just in case conversion changes anything...like rasterizaion disable.
 	if (!addVertexShaderToPipeline(plDesc, pCreateInfo, shaderContext)) { return nil; }
@@ -476,11 +482,12 @@
 	return plDesc;
 }
 
-// Returns a MTLRenderPipelineDescriptor for the vertex stage of a tessellated draw constructed from this instance, or nil if an error occurs.
-MTLRenderPipelineDescriptor* MVKGraphicsPipeline::getMTLTessVertexStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
+// Returns a retained MTLRenderPipelineDescriptor for the vertex stage of a tessellated draw constructed from this instance, or nil if an error occurs.
+// It is the responsibility of the caller to release the returned descriptor.
+MTLRenderPipelineDescriptor* MVKGraphicsPipeline::newMTLTessVertexStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
 																				  const SPIRVTessReflectionData& reflectData,
 																				  SPIRVToMSLConversionConfiguration& shaderContext) {
-	MTLRenderPipelineDescriptor* plDesc = [[MTLRenderPipelineDescriptor new] autorelease];
+	MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// retained
 
 	// Add shader stages.
 	if (!addVertexShaderToPipeline(plDesc, pCreateInfo, shaderContext)) { return nil; }
@@ -587,11 +594,12 @@
 	return VK_FORMAT_UNDEFINED;
 }
 
-// Returns a MTLComputePipelineDescriptor for the tess. control stage of a tessellated draw constructed from this instance, or nil if an error occurs.
-MTLComputePipelineDescriptor* MVKGraphicsPipeline::getMTLTessControlStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
+// Returns a retained MTLComputePipelineDescriptor for the tess. control stage of a tessellated draw constructed from this instance, or nil if an error occurs.
+// It is the responsibility of the caller to release the returned descriptor.
+MTLComputePipelineDescriptor* MVKGraphicsPipeline::newMTLTessControlStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
 																					const SPIRVTessReflectionData& reflectData,
 																					SPIRVToMSLConversionConfiguration& shaderContext) {
-	MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new];
+	MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new];		// retained
 
 	std::vector<SPIRVShaderOutput> vtxOutputs;
 	std::string errorLog;
@@ -634,11 +642,12 @@
 	return plDesc;
 }
 
-// Returns a MTLRenderPipelineDescriptor for the last stage of a tessellated draw constructed from this instance, or nil if an error occurs.
-MTLRenderPipelineDescriptor* MVKGraphicsPipeline::getMTLTessRasterStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
+// Returns a retained MTLRenderPipelineDescriptor for the last stage of a tessellated draw constructed from this instance, or nil if an error occurs.
+// It is the responsibility of the caller to release the returned descriptor.
+MTLRenderPipelineDescriptor* MVKGraphicsPipeline::newMTLTessRasterStageDescriptor(const VkGraphicsPipelineCreateInfo* pCreateInfo,
 																				  const SPIRVTessReflectionData& reflectData,
 																				  SPIRVToMSLConversionConfiguration& shaderContext) {
-	MTLRenderPipelineDescriptor* plDesc = [[MTLRenderPipelineDescriptor new] autorelease];
+	MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// retained
 
 	std::vector<SPIRVShaderOutput> tcOutputs;
 	std::string errorLog;
@@ -775,11 +784,12 @@
     addVertexInputToShaderConverterContext(shaderContext, pCreateInfo);
 
 	MVKMTLFunction func = ((MVKShaderModule*)_pVertexSS->module)->getMTLFunction(&shaderContext, _pVertexSS->pSpecializationInfo, _pipelineCache);
-	if ( !func.mtlFunction ) {
+	id<MTLFunction> mtlFunc = func.getMTLFunction();
+	if ( !mtlFunc ) {
 		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Vertex shader function could not be compiled into pipeline. See previous logged error."));
 		return false;
 	}
-	plDesc.vertexFunction = func.mtlFunction;
+	plDesc.vertexFunction = mtlFunc;
 
 	auto& funcRslts = func.shaderConversionResults;
 	plDesc.rasterizationEnabled = !funcRslts.isRasterizationDisabled;
@@ -821,11 +831,12 @@
 	addPrevStageOutputToShaderConverterContext(shaderContext, vtxOutputs);
 
 	MVKMTLFunction func = ((MVKShaderModule*)_pTessCtlSS->module)->getMTLFunction(&shaderContext, _pTessCtlSS->pSpecializationInfo, _pipelineCache);
-	if ( !func.mtlFunction ) {
+	id<MTLFunction> mtlFunc = func.getMTLFunction();
+	if ( !mtlFunc ) {
 		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Tessellation control shader function could not be compiled into pipeline. See previous logged error."));
 		return false;
 	}
-	plDesc.computeFunction = func.mtlFunction;
+	plDesc.computeFunction = mtlFunc;
 
 	auto& funcRslts = func.shaderConversionResults;
 	_needsTessCtlSwizzleBuffer = funcRslts.needsSwizzleBuffer;
@@ -870,12 +881,13 @@
 	addPrevStageOutputToShaderConverterContext(shaderContext, tcOutputs);
 
 	MVKMTLFunction func = ((MVKShaderModule*)_pTessEvalSS->module)->getMTLFunction(&shaderContext, _pTessEvalSS->pSpecializationInfo, _pipelineCache);
-	if ( !func.mtlFunction ) {
+	id<MTLFunction> mtlFunc = func.getMTLFunction();
+	if ( !mtlFunc ) {
 		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Tessellation evaluation shader function could not be compiled into pipeline. See previous logged error."));
 		return false;
 	}
 	// Yeah, you read that right. Tess. eval functions are a kind of vertex function in Metal.
-	plDesc.vertexFunction = func.mtlFunction;
+	plDesc.vertexFunction = mtlFunc;
 
 	auto& funcRslts = func.shaderConversionResults;
 	plDesc.rasterizationEnabled = !funcRslts.isRasterizationDisabled;
@@ -902,11 +914,12 @@
 		shaderContext.options.mslOptions.capture_output_to_buffer = false;
 
 		MVKMTLFunction func = ((MVKShaderModule*)_pFragmentSS->module)->getMTLFunction(&shaderContext, _pFragmentSS->pSpecializationInfo, _pipelineCache);
-		if ( !func.mtlFunction ) {
+		id<MTLFunction> mtlFunc = func.getMTLFunction();
+		if ( !mtlFunc ) {
 			setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Fragment shader function could not be compiled into pipeline. See previous logged error."));
 			return false;
 		}
-		plDesc.fragmentFunction = func.mtlFunction;
+		plDesc.fragmentFunction = mtlFunc;
 
 		auto& funcRslts = func.shaderConversionResults;
 		_needsFragmentSwizzleBuffer = funcRslts.needsSwizzleBuffer;
@@ -1281,13 +1294,14 @@
 									   const VkComputePipelineCreateInfo* pCreateInfo) :
 	MVKPipeline(device, pipelineCache, (MVKPipelineLayout*)pCreateInfo->layout, parent) {
 
-	MVKMTLFunction shaderFunc = getMTLFunction(pCreateInfo);
-	_mtlThreadgroupSize = shaderFunc.threadGroupSize;
+	MVKMTLFunction func = getMTLFunction(pCreateInfo);
+	_mtlThreadgroupSize = func.threadGroupSize;
 	_mtlPipelineState = nil;
 
-	if (shaderFunc.mtlFunction) {
-		MTLComputePipelineDescriptor* plDesc = [[MTLComputePipelineDescriptor new] autorelease];
-		plDesc.computeFunction = shaderFunc.mtlFunction;
+	id<MTLFunction> mtlFunc = func.getMTLFunction();
+	if (mtlFunc) {
+		MTLComputePipelineDescriptor* plDesc = [MTLComputePipelineDescriptor new];	// temp retain
+		plDesc.computeFunction = mtlFunc;
 
 		// Metal does not allow the name of the pipeline to be changed after it has been created,
 		// and we need to create the Metal pipeline immediately to provide error feedback to app.
@@ -1297,6 +1311,8 @@
 		MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(this);
 		_mtlPipelineState = plc->newMTLComputePipelineState(plDesc);	// retained
 		plc->destroy();
+		[plDesc release];															// temp release
+
 		if ( !_mtlPipelineState ) { _hasValidMTLPipelineStates = false; }
 	} else {
 		setConfigurationResult(reportError(VK_ERROR_INVALID_SHADER_NV, "Compute shader function could not be compiled into pipeline. See previous logged error."));
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
index bdb6015..6a8647d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
@@ -65,13 +65,13 @@
 
 #pragma mark Queue submissions
 
-// Execute the queue submission under an autorelease pool to ensure transient Metal objects are autoreleased.
+// Execute the queue submission under an autoreleasepool to ensure transient Metal objects are autoreleased.
 // This is critical for apps that don't use standard OS autoreleasing runloop threading.
 static inline void execute(MVKQueueSubmission* qSubmit) { @autoreleasepool { qSubmit->execute(); } }
 
 // Executes the submmission, either immediately, or by dispatching to an execution queue.
-// Submissions to the execution queue are wrapped in a dedicated autorelease pool.
-// Relying on the dispatch queue to find time to drain the autorelease pool can
+// Submissions to the execution queue are wrapped in a dedicated autoreleasepool.
+// Relying on the dispatch queue to find time to drain the autoreleasepool can
 // result in significant memory creep under heavy workloads.
 VkResult MVKQueue::submit(MVKQueueSubmission* qSubmit) {
 	if ( !qSubmit ) { return VK_SUCCESS; }     // Ignore nils
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
index ff54bfb..d9a8f84 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
@@ -39,14 +39,22 @@
 #pragma mark MVKShaderLibrary
 
 /** A MTLFunction and corresponding result information resulting from a shader conversion. */
-typedef struct {
-	id<MTLFunction> mtlFunction;
-	const SPIRVToMSLConversionResults shaderConversionResults;
+typedef struct MVKMTLFunction {
+	SPIRVToMSLConversionResults shaderConversionResults;
 	MTLSize threadGroupSize;
+	inline id<MTLFunction> getMTLFunction() { return _mtlFunction; }
+
+	MVKMTLFunction(id<MTLFunction> mtlFunc, const SPIRVToMSLConversionResults scRslts, MTLSize tgSize);
+	MVKMTLFunction(const MVKMTLFunction& other);
+	~MVKMTLFunction();
+
+private:
+	id<MTLFunction> _mtlFunction;
+
 } MVKMTLFunction;
 
 /** A MVKMTLFunction indicating an invalid MTLFunction. The mtlFunction member is nil. */
-extern const MVKMTLFunction MVKMTLFunctionNull;
+const MVKMTLFunction MVKMTLFunctionNull(nil, SPIRVToMSLConversionResults(), MTLSizeMake(1, 1, 1));
 
 /** Wraps a single MTLLibrary. */
 class MVKShaderLibrary : public MVKBaseObject {
@@ -85,7 +93,7 @@
 					 size_t mslCompiledCodeLength);
 
 	/** Copy constructor. */
-	MVKShaderLibrary(MVKShaderLibrary& other);
+	MVKShaderLibrary(const MVKShaderLibrary& other);
 
 	~MVKShaderLibrary() override;
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
index b80362b..aa0823f 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
@@ -26,7 +26,22 @@
 using namespace std;
 
 
-const MVKMTLFunction MVKMTLFunctionNull = { nil, SPIRVToMSLConversionResults(), MTLSizeMake(1, 1, 1) };
+MVKMTLFunction::MVKMTLFunction(id<MTLFunction> mtlFunc, const SPIRVToMSLConversionResults scRslts, MTLSize tgSize) {
+	_mtlFunction = [mtlFunc retain];		// retained
+	shaderConversionResults = scRslts;
+	threadGroupSize = tgSize;
+}
+
+MVKMTLFunction::MVKMTLFunction(const MVKMTLFunction& other) {
+	_mtlFunction = [other._mtlFunction retain];		// retained
+	shaderConversionResults = other.shaderConversionResults;
+	threadGroupSize = other.threadGroupSize;
+}
+
+MVKMTLFunction::~MVKMTLFunction() {
+	[_mtlFunction release];
+}
+
 
 #pragma mark -
 #pragma mark MVKShaderLibrary
@@ -49,57 +64,65 @@
 
     if ( !_mtlLibrary ) { return MVKMTLFunctionNull; }
 
-    NSString* mtlFuncName = @(_shaderConversionResults.entryPoint.mtlFunctionName.c_str());
-	MVKDevice* mvkDev = _owner->getDevice();
-    uint64_t startTime = mvkDev->getPerformanceTimestamp();
-    id<MTLFunction> mtlFunc = [[_mtlLibrary newFunctionWithName: mtlFuncName] autorelease];
-    mvkDev->addActivityPerformance(mvkDev->_performanceStatistics.shaderCompilation.functionRetrieval, startTime);
+	id<MTLFunction> mtlFunc = nil;
+	@autoreleasepool {
+		NSString* mtlFuncName = @(_shaderConversionResults.entryPoint.mtlFunctionName.c_str());
+		MVKDevice* mvkDev = _owner->getDevice();
+		uint64_t startTime = mvkDev->getPerformanceTimestamp();
+		mtlFunc = [_mtlLibrary newFunctionWithName: mtlFuncName];								// temp retain
+		mvkDev->addActivityPerformance(mvkDev->_performanceStatistics.shaderCompilation.functionRetrieval, startTime);
 
-    if (mtlFunc) {
-        // If the Metal device supports shader specialization, and the Metal function expects to be
-        // specialized, populate Metal function constant values from the Vulkan specialization info,
-        // and compiled a specialized Metal function, otherwise simply use the unspecialized Metal function.
-        if (mvkDev->_pMetalFeatures->shaderSpecialization) {
-            NSArray<MTLFunctionConstant*>* mtlFCs = mtlFunc.functionConstantsDictionary.allValues;
-            if (mtlFCs.count) {
-                // The Metal shader contains function constants and expects to be specialized
-                // Populate the Metal function constant values from the Vulkan specialization info.
-                MTLFunctionConstantValues* mtlFCVals = [[MTLFunctionConstantValues new] autorelease];
-                if (pSpecializationInfo) {
-                    // Iterate through the provided Vulkan specialization entries, and populate the
-                    // Metal function constant value that matches the Vulkan specialization constantID.
-                    for (uint32_t specIdx = 0; specIdx < pSpecializationInfo->mapEntryCount; specIdx++) {
-                        const VkSpecializationMapEntry* pMapEntry = &pSpecializationInfo->pMapEntries[specIdx];
-                        NSUInteger mtlFCIndex = pMapEntry->constantID;
-                        MTLFunctionConstant* mtlFC = getFunctionConstant(mtlFCs, mtlFCIndex);
-                        if (mtlFC) {
-                            [mtlFCVals setConstantValue: &(((char*)pSpecializationInfo->pData)[pMapEntry->offset])
-                                                   type: mtlFC.type
-                                                atIndex: mtlFCIndex];
-                        }
-                    }
-                }
+		if (mtlFunc) {
+			// If the Metal device supports shader specialization, and the Metal function expects to be
+			// specialized, populate Metal function constant values from the Vulkan specialization info,
+			// and compiled a specialized Metal function, otherwise simply use the unspecialized Metal function.
+			if (mvkDev->_pMetalFeatures->shaderSpecialization) {
+				NSArray<MTLFunctionConstant*>* mtlFCs = mtlFunc.functionConstantsDictionary.allValues;
+				if (mtlFCs.count) {
+					// The Metal shader contains function constants and expects to be specialized
+					// Populate the Metal function constant values from the Vulkan specialization info.
+					MTLFunctionConstantValues* mtlFCVals = [MTLFunctionConstantValues new];		// temp retain
+					if (pSpecializationInfo) {
+						// Iterate through the provided Vulkan specialization entries, and populate the
+						// Metal function constant value that matches the Vulkan specialization constantID.
+						for (uint32_t specIdx = 0; specIdx < pSpecializationInfo->mapEntryCount; specIdx++) {
+							const VkSpecializationMapEntry* pMapEntry = &pSpecializationInfo->pMapEntries[specIdx];
+							NSUInteger mtlFCIndex = pMapEntry->constantID;
+							MTLFunctionConstant* mtlFC = getFunctionConstant(mtlFCs, mtlFCIndex);
+							if (mtlFC) {
+								[mtlFCVals setConstantValue: &(((char*)pSpecializationInfo->pData)[pMapEntry->offset])
+													   type: mtlFC.type
+													atIndex: mtlFCIndex];
+							}
+						}
+					}
 
-                // Compile the specialized Metal function, and use it instead of the unspecialized Metal function.
-				MVKFunctionSpecializer* fs = new MVKFunctionSpecializer(_owner);
-				mtlFunc = [fs->newMTLFunction(_mtlLibrary, mtlFuncName, mtlFCVals) autorelease];
-				fs->destroy();
-            }
-        }
-    } else {
-        reportError(VK_ERROR_INVALID_SHADER_NV, "Shader module does not contain an entry point named '%s'.", mtlFuncName.UTF8String);
-    }
+					// Compile the specialized Metal function, and use it instead of the unspecialized Metal function.
+					MVKFunctionSpecializer* fs = new MVKFunctionSpecializer(_owner);
+					[mtlFunc release];															// temp release
+					mtlFunc = fs->newMTLFunction(_mtlLibrary, mtlFuncName, mtlFCVals);			// temp retain
+					fs->destroy();
+					[mtlFCVals release];														// temp release
+				}
+			}
+		} else {
+			reportError(VK_ERROR_INVALID_SHADER_NV, "Shader module does not contain an entry point named '%s'.", mtlFuncName.UTF8String);
+		}
 
-	// Set the debug name. First try name of shader module, otherwise try name of owner.
-	NSString* dbName = shaderModule-> getDebugName();
-	if ( !dbName ) { dbName = _owner-> getDebugName(); }
-	setLabelIfNotNil(mtlFunc, dbName);
-
+		// Set the debug name. First try name of shader module, otherwise try name of owner.
+		NSString* dbName = shaderModule-> getDebugName();
+		if ( !dbName ) { dbName = _owner-> getDebugName(); }
+		setLabelIfNotNil(mtlFunc, dbName);
+	}
 
 	auto& wgSize = _shaderConversionResults.entryPoint.workgroupSize;
-	return { mtlFunc, _shaderConversionResults, MTLSizeMake(getWorkgroupDimensionSize(wgSize.width, pSpecializationInfo),
-															getWorkgroupDimensionSize(wgSize.height, pSpecializationInfo),
-															getWorkgroupDimensionSize(wgSize.depth, pSpecializationInfo))};
+
+	MVKMTLFunction mvkMTLFunc(mtlFunc, _shaderConversionResults, MTLSizeMake(getWorkgroupDimensionSize(wgSize.width, pSpecializationInfo),
+																			 getWorkgroupDimensionSize(wgSize.height, pSpecializationInfo),
+																			 getWorkgroupDimensionSize(wgSize.depth, pSpecializationInfo)));
+	[mtlFunc release];																			// temp release
+
+	return mvkMTLFunc;
 }
 
 // Returns the MTLFunctionConstant with the specified ID from the specified array of function constants.
@@ -124,7 +147,11 @@
 								   const string& mslSourceCode,
 								   const SPIRVToMSLConversionResults& shaderConversionResults) : _owner(owner) {
 	MVKShaderLibraryCompiler* slc = new MVKShaderLibraryCompiler(_owner);
-	_mtlLibrary = slc->newMTLLibrary(@(mslSourceCode.c_str()));	// retained
+
+	NSString* nsSrc = [[NSString alloc] initWithUTF8String: mslSourceCode.c_str()];	// temp retained
+	_mtlLibrary = slc->newMTLLibrary(nsSrc);	// retained
+	[nsSrc release];	// release temp string
+
 	slc->destroy();
 
 	_shaderConversionResults = shaderConversionResults;
@@ -149,7 +176,7 @@
     mvkDev->addActivityPerformance(mvkDev->_performanceStatistics.shaderCompilation.mslLoad, startTime);
 }
 
-MVKShaderLibrary::MVKShaderLibrary(MVKShaderLibrary& other) : _owner(other._owner) {
+MVKShaderLibrary::MVKShaderLibrary(const MVKShaderLibrary& other) : _owner(other._owner) {
 	_mtlLibrary = [other._mtlLibrary retain];
 	_shaderConversionResults = other._shaderConversionResults;
 	_msl = other._msl;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index e6f5ab0..ac3eabc 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -38,7 +38,9 @@
 	if (_debugName) {
 		size_t imgCnt = _surfaceImages.size();
 		for (size_t imgIdx = 0; imgIdx < imgCnt; imgIdx++) {
-			_surfaceImages[imgIdx]->setDebugName([NSString stringWithFormat: @"%@(%lu)", _debugName, imgIdx].UTF8String);
+			NSString* nsName = [[NSString alloc] initWithFormat: @"%@(%lu)", _debugName, imgIdx];	// temp retain
+			_surfaceImages[imgIdx]->setDebugName(nsName.UTF8String);
+			[nsName release];																		// release temp string
 		}
 	}
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
index 171df2a..f0a1b1c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
@@ -215,8 +215,10 @@
 	_blocker.wait_for(lock, nanoTimeout, [this]{ return _isCompileDone; });
 
 	if ( !_isCompileDone ) {
-		NSString* errDesc = [NSString stringWithFormat: @"Timeout after %.3f milliseconds. Likely internal Metal compiler error", (double)nanoTimeout.count() / 1e6];
-		_compileError = [[NSError alloc] initWithDomain: @"MoltenVK" code: 1 userInfo: @{NSLocalizedDescriptionKey : errDesc}];	// retained
+		@autoreleasepool {
+			NSString* errDesc = [NSString stringWithFormat: @"Timeout after %.3f milliseconds. Likely internal Metal compiler error", (double)nanoTimeout.count() / 1e6];
+			_compileError = [[NSError alloc] initWithDomain: @"MoltenVK" code: 1 userInfo: @{NSLocalizedDescriptionKey : errDesc}];	// retained
+		}
 	}
 
 	if (_compileError) { handleError(); }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKVulkanAPIObject.mm b/MoltenVK/MoltenVK/GPUObjects/MVKVulkanAPIObject.mm
index 6a390b1..109844d 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKVulkanAPIObject.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKVulkanAPIObject.mm
@@ -39,7 +39,7 @@
 VkResult MVKVulkanAPIObject::setDebugName(const char* pObjectName) {
 	if (pObjectName) {
 		[_debugName release];
-		_debugName = [[NSString stringWithUTF8String: pObjectName] retain];		// retained
+		_debugName = [[NSString alloc] initWithUTF8String: pObjectName];	// retained
 		propogateDebugName();
 	}
 	return VK_SUCCESS;
diff --git a/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm b/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
index 156a16e..4ae0de9 100644
--- a/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
+++ b/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
@@ -59,8 +59,10 @@
 MVKGPUCaptureScope::MVKGPUCaptureScope(MVKQueue* mvkQueue, const char* purpose) : _queue(mvkQueue) {
 	_mtlQueue = [_queue->getMTLCommandQueue() retain];	// retained
 	if (mvkOSVersion() >= kMinOSVersionMTLCaptureScope) {
+		NSString* nsQLbl = [[NSString alloc] initWithUTF8String: (_queue->getName() + "-" + purpose).c_str()];		// temp retained
 		_mtlCaptureScope = [[MTLCaptureManager sharedCaptureManager] newCaptureScopeWithCommandQueue: _mtlQueue];	// retained
-		_mtlCaptureScope.label = @((_queue->getName() + "-" + purpose).c_str());
+		_mtlCaptureScope.label = nsQLbl;
+		[nsQLbl release];																							// release temp
 	}
 }
 
diff --git a/MoltenVK/MoltenVK/Utility/MVKWatermark.mm b/MoltenVK/MoltenVK/Utility/MVKWatermark.mm
index db7740c..cfc4b56 100644
--- a/MoltenVK/MoltenVK/Utility/MVKWatermark.mm
+++ b/MoltenVK/MoltenVK/Utility/MVKWatermark.mm
@@ -75,7 +75,7 @@
 }
 
 id<MTLRenderPipelineState> MVKWatermark::newRenderPipelineState() {
-    MTLRenderPipelineDescriptor* plDesc = [[MTLRenderPipelineDescriptor new] autorelease];
+    MTLRenderPipelineDescriptor* plDesc = [MTLRenderPipelineDescriptor new];	// temp retained
     plDesc.label = _mtlName;
 
     plDesc.vertexFunction = _mtlFunctionVertex;
@@ -128,7 +128,8 @@
     NSError* err = nil;
     id<MTLRenderPipelineState> rps = [_mtlDevice newRenderPipelineStateWithDescriptor: plDesc error: &err];	// retained
     MVKAssert( !err, "Could not create watermark pipeline state (Error code %li)\n%s", (long)err.code, err.localizedDescription.UTF8String);
-    return rps;
+	[plDesc release];		// temp released
+	return rps;
 }
 
 
@@ -296,21 +297,26 @@
                    bytesPerRow: textureBytesPerRow
                  bytesPerImage: 0];
 
-    MTLSamplerDescriptor* sampDesc = [[MTLSamplerDescriptor new] autorelease];
+    MTLSamplerDescriptor* sampDesc = [MTLSamplerDescriptor new];				// temp retained
     sampDesc.minFilter = MTLSamplerMinMagFilterLinear;
-    _mtlSamplerState = [_mtlDevice newSamplerStateWithDescriptor: sampDesc];		// retained
+    _mtlSamplerState = [_mtlDevice newSamplerStateWithDescriptor: sampDesc];	// retained
+	[sampDesc release];															// temp released
 }
 
 // Initialize the shader functions for rendering the watermark
 void MVKWatermark::initShaders(const char* mslSourceCode) {
     NSError* err = nil;
-    id<MTLLibrary> mtlLib = [[_mtlDevice newLibraryWithSource: @(mslSourceCode)
-                                                      options: nil
-                                                        error: &err] autorelease];
+	NSString* nsSrc = [[NSString alloc] initWithUTF8String: mslSourceCode];	// temp retained
+	id<MTLLibrary> mtlLib = [_mtlDevice newLibraryWithSource: nsSrc
+													 options: nil
+													   error: &err];		// temp retained
 	MVKAssert( !err, "Could not compile watermark shaders (Error code %li):\n%s", (long)err.code, err.localizedDescription.UTF8String);
 
     _mtlFunctionVertex = [mtlLib newFunctionWithName: @"watermarkVertex"];          // retained
     _mtlFunctionFragment = [mtlLib newFunctionWithName: @"watermarkFragment"];      // retained
+
+	[nsSrc release];	// temp released
+	[mtlLib release];	// temp released
 }
 
 // Initialize the vertex buffers to use for rendering the watermark
diff --git a/README.md b/README.md
index 72d7fd1..ab05ba1 100644
--- a/README.md
+++ b/README.md
@@ -317,6 +317,19 @@
 Property claims.  
 
 
+### Memory Management
+
+*Metal*, and other *Objective-C* objects in *Apple's SDK* frameworks, use reference counting for memory management. 
+When instantiating *Objective-C* objects, it is important that you do not rely on implied *autorelease pools* to do 
+memory management for you. Because many *Vulkan* games and apps may be ported from other platforms, they will 
+typically not include autorelease pools in their threading models.
+
+Avoid the use of the `autorelease` method, or any object creation methods that imply use of `autorelease`,
+(eg- `[NSString stringWithFormat: ]`, etc). Instead, favour object creation methods that return a retained object
+(eg- `[[NSString alloc] initWithFormat: ]`, etc), and manually track and release those objects. If you need to use 
+autoreleased objects, wrap code blocks in an `@autoreleasepool {...}` block.
+
+
 ### Code Formatting
 
 When contributing code, please honour the code formatting style found in existing **MoltenVK** source code.