Merge pull request #588 from billhollings/master

Add support for the VK_EXT_debug_report extension
diff --git a/Common/MVKLogging.cpp b/Common/MVKLogging.cpp
deleted file mode 100644
index 0b53aee..0000000
--- a/Common/MVKLogging.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * MVKLogging.cpp
- *
- * Copyright (c) 2014-2019 The Brenwill Workshop Ltd. (http://www.brenwill.com)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- * 
- *     http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-#include "MVKLogging.h"
-
-
-// The logging level
-// 0 = None
-// 1 = Errors only
-// 2 = All
-#ifndef MVK_CONFIG_LOG_LEVEL
-#   define MVK_CONFIG_LOG_LEVEL    2
-#endif
-
-static uint32_t _mvkLogLevel = MVK_CONFIG_LOG_LEVEL;
-
-void MVKLogImplV(bool logToPrintf, bool /*logToASL*/, int aslLvl, const char* lvlStr, const char* format, va_list args) {
-
-	if (aslLvl > (_mvkLogLevel << 2)) { return; }
-
-	// Combine the level and format string
-	char lvlFmt[strlen(lvlStr) + strlen(format) + 5];
-	sprintf(lvlFmt, "[%s] %s\n", lvlStr, format);
-
-	if (logToPrintf) { vfprintf(stderr, lvlFmt, args); }
-//	if (logToASL) { asl_vlog(NULL, NULL, aslLvl, lvlFmt, args); }       // Multi-threaded ASL support requires a separate ASL client to be opened per thread!
-}
-
-void MVKLogImpl(bool logToPrintf, bool logToASL, int aslLvl, const char* lvlStr, const char* format, ...) {
-	va_list args;
-	va_start(args, format);
-	MVKLogImplV(logToPrintf, logToASL, aslLvl, lvlStr, format, args);
-	va_end(args);
-}
-
-#ifdef MVK_ENV_LOG_LEVEL
-#include "MVKOSExtensions.h"
-static bool _mvkLoggingInitialized = false;
-__attribute__((constructor)) static void MVKInitLogging() {
-	if (_mvkLoggingInitialized ) { return; }
-	_mvkLoggingInitialized = true;
-
-	MVK_SET_FROM_ENV_OR_BUILD_INT32(_mvkLogLevel, MVK_CONFIG_LOG_LEVEL);
-}
-#endif
diff --git a/Common/MVKLogging.h b/Common/MVKLogging.h
index 0fc70c4..ddf2933 100644
--- a/Common/MVKLogging.h
+++ b/Common/MVKLogging.h
@@ -23,7 +23,6 @@
 extern "C" {
 #endif	//  __cplusplus
 
-#include "MVKCommonEnvironment.h"
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
@@ -93,12 +92,6 @@
  * from the compiled code, thus eliminating both the memory and CPU overhead that the assertion
  * calls would add
  *
- * A special MVKAssertUnimplemented(name) and MVKAssertCUnimplemented(name) assertion functions
- * are provided to conveniently raise an assertion exception when some expected functionality
- * is unimplemented. Either functions may be used as a temporary placeholder for functionalty
- * that will be added at a later time. MVKAssertUnimplemented(name) may also be used in a method
- * body by a superclass that requires each subclass to implement that method
- *
  * Although you can directly edit this file to turn on or off the switches below, the preferred
  * technique is to set these switches via the compiler build setting GCC_PREPROCESSOR_DEFINITIONS
  * in your build configuration.
@@ -172,22 +165,10 @@
 #	define MVKLogDebugIf(cond, fmt, ...)
 #endif
 
-/**
- * Combine the specified log level and format string, then log
- * the specified args to one or both of ASL and printf.
- */
-void MVKLogImplV(bool logToPrintf, bool logToASL, int aslLvl, const char* lvlStr, const char* format, va_list args) __printflike(5, 0);
-
-/** 
- * Combine the specified log level and format string, then log 
- * the specified args to one or both of ASL and printf.
- */
-void MVKLogImpl(bool logToPrintf, bool logToASL, int aslLvl, const char* lvlStr, const char* format, ...) __printflike(5, 6);
-
-#define MVKLogErrorImpl(fmt, ...)	MVKLogImpl(true, !(MVK_DEBUG), ASL_LEVEL_ERR, "***MoltenVK ERROR***", fmt, ##__VA_ARGS__)
-#define MVKLogInfoImpl(fmt, ...)	MVKLogImpl(true, !(MVK_DEBUG), ASL_LEVEL_NOTICE, "mvk-info", fmt, ##__VA_ARGS__)
-#define MVKLogTraceImpl(fmt, ...)	MVKLogImpl(true, !(MVK_DEBUG), ASL_LEVEL_DEBUG, "mvk-trace", fmt, ##__VA_ARGS__)
-#define MVKLogDebugImpl(fmt, ...)	MVKLogImpl(true, !(MVK_DEBUG), ASL_LEVEL_DEBUG, "mvk-debug", fmt, ##__VA_ARGS__)
+#define MVKLogErrorImpl(fmt, ...)		reportMessage(ASL_LEVEL_ERR, fmt, ##__VA_ARGS__)
+#define MVKLogInfoImpl(fmt, ...)		reportMessage(ASL_LEVEL_NOTICE, fmt, ##__VA_ARGS__)
+#define MVKLogTraceImpl(fmt, ...)		reportMessage(ASL_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
+#define MVKLogDebugImpl(fmt, ...)		reportMessage(ASL_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
 
 // Assertions
 #ifdef NS_BLOCK_ASSERTIONS
@@ -203,8 +184,6 @@
 	assert(!isErr || MVK_BLOCK_ASSERTIONS);		\
 } while(0)
 
-#define MVKAssertUnimplemented(name) MVKAssert(false, "%s is not implemented!", name)
-
 // Use this macro to open a break-point programmatically.
 #ifndef MVK_DEBUGGER
 #	define MVK_DEBUGGER() { kill( getpid(), SIGINT ) ; }
diff --git a/Common/MVKOSExtensions.mm b/Common/MVKOSExtensions.mm
index 698393c..7759e48 100644
--- a/Common/MVKOSExtensions.mm
+++ b/Common/MVKOSExtensions.mm
@@ -18,7 +18,6 @@
 
 
 #include "MVKOSExtensions.h"
-#include "MVKLogging.h"
 #include <mach/mach_time.h>
 
 #import <Foundation/Foundation.h>
@@ -51,10 +50,8 @@
 	return (double)(endTimestamp - startTimestamp) * _mvkTimestampPeriod / 1e6;
 }
 
-/**
- * Initialize timestamping capabilities on app startup.
- * Called automatically when the framework is loaded and initialized.
- */
+// Initialize timestamping capabilities on app startup.
+//Called automatically when the framework is loaded and initialized.
 static bool _mvkTimestampsInitialized = false;
 __attribute__((constructor)) static void MVKInitTimestamps() {
 	if (_mvkTimestampsInitialized ) { return; }
@@ -64,8 +61,6 @@
 	mach_timebase_info_data_t timebase;
 	mach_timebase_info(&timebase);
 	_mvkTimestampPeriod = (double)timebase.numer / (double)timebase.denom;
-	MVKLogDebug("Initializing MoltenVK timestamping. Mach time: %llu. Time period: %d / %d = %.6f.",
-				_mvkTimestampBase, timebase.numer, timebase.denom, _mvkTimestampPeriod);
 }
 
 void mvkDispatchToMainAndWait(dispatch_block_t block) {
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 47eb00d..24274e2 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -248,6 +248,7 @@
 - `VK_KHR_swapchain`
 - `VK_KHR_swapchain_mutable_format`
 - `VK_KHR_variable_pointers`
+- `VK_EXT_debug_report`
 - `VK_EXT_host_query_reset`
 - `VK_EXT_memory_budget`
 - `VK_EXT_shader_viewport_index_layer`
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 547cb4b..9e4d059 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -18,6 +18,9 @@
 
 Released TBD
 
+- Support the `VK_EXT_debug_report` extension.
+- Change log indication of error in logs from `[***MoltenVK ERROR***]` to 
+  `[mvk-error]`, for consistency with other log level indications. 
 - Tessellation fixes:
 	- Don't use setVertexBytes() for passing tessellation vertex counts.
 	- Fix intermediate Metal renderpasses load and store actions maintaining 
diff --git a/MoltenVK/MoltenVK.xcodeproj/project.pbxproj b/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
index 9d9e0b6..b41d81b 100644
--- a/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
+++ b/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
@@ -187,8 +187,8 @@
 		A9C96DD11DDC20C20053187F /* MVKMTLBufferAllocation.h in Headers */ = {isa = PBXBuildFile; fileRef = A9C96DCE1DDC20C20053187F /* MVKMTLBufferAllocation.h */; };
 		A9C96DD21DDC20C20053187F /* MVKMTLBufferAllocation.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9C96DCF1DDC20C20053187F /* MVKMTLBufferAllocation.mm */; };
 		A9C96DD31DDC20C20053187F /* MVKMTLBufferAllocation.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9C96DCF1DDC20C20053187F /* MVKMTLBufferAllocation.mm */; };
-		A9E337B5220129DD00363D2A /* MVKLogging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9E337B1220129DD00363D2A /* MVKLogging.cpp */; };
-		A9E337B6220129DD00363D2A /* MVKLogging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9E337B1220129DD00363D2A /* MVKLogging.cpp */; };
+		A9CEAAD5227378D400FAF779 /* mvk_datatypes.hpp in Headers */ = {isa = PBXBuildFile; fileRef = A9CEAAD1227378D400FAF779 /* mvk_datatypes.hpp */; };
+		A9CEAAD6227378D400FAF779 /* mvk_datatypes.hpp in Headers */ = {isa = PBXBuildFile; fileRef = A9CEAAD1227378D400FAF779 /* mvk_datatypes.hpp */; };
 		A9E4B7891E1D8AF10046A4CE /* MVKMTLResourceBindings.h in Headers */ = {isa = PBXBuildFile; fileRef = A9E4B7881E1D8AF10046A4CE /* MVKMTLResourceBindings.h */; };
 		A9E4B78A1E1D8AF10046A4CE /* MVKMTLResourceBindings.h in Headers */ = {isa = PBXBuildFile; fileRef = A9E4B7881E1D8AF10046A4CE /* MVKMTLResourceBindings.h */; };
 		A9E53DD72100B197002781DD /* MTLSamplerDescriptor+MoltenVK.m in Sources */ = {isa = PBXBuildFile; fileRef = A9E53DCD2100B197002781DD /* MTLSamplerDescriptor+MoltenVK.m */; };
@@ -370,8 +370,8 @@
 		A9C96DCE1DDC20C20053187F /* MVKMTLBufferAllocation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKMTLBufferAllocation.h; sourceTree = "<group>"; };
 		A9C96DCF1DDC20C20053187F /* MVKMTLBufferAllocation.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MVKMTLBufferAllocation.mm; sourceTree = "<group>"; };
 		A9CBEE011B6299D800E45FDC /* libMoltenVK.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libMoltenVK.a; sourceTree = BUILT_PRODUCTS_DIR; };
+		A9CEAAD1227378D400FAF779 /* mvk_datatypes.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = mvk_datatypes.hpp; sourceTree = "<group>"; };
 		A9DE1083200598C500F18F80 /* icd */ = {isa = PBXFileReference; lastKnownFileType = folder; path = icd; sourceTree = "<group>"; };
-		A9E337B1220129DD00363D2A /* MVKLogging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MVKLogging.cpp; sourceTree = "<group>"; };
 		A9E4B7881E1D8AF10046A4CE /* MVKMTLResourceBindings.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKMTLResourceBindings.h; sourceTree = "<group>"; };
 		A9E53DCD2100B197002781DD /* MTLSamplerDescriptor+MoltenVK.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MTLSamplerDescriptor+MoltenVK.m"; sourceTree = "<group>"; };
 		A9E53DD02100B197002781DD /* MTLTextureDescriptor+MoltenVK.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MTLTextureDescriptor+MoltenVK.h"; sourceTree = "<group>"; };
@@ -504,6 +504,7 @@
 		A94FB7A81C7DFB4800632CA3 /* Vulkan */ = {
 			isa = PBXGroup;
 			children = (
+				A9CEAAD1227378D400FAF779 /* mvk_datatypes.hpp */,
 				A94FB7A91C7DFB4800632CA3 /* mvk_datatypes.mm */,
 				A94FB7AC1C7DFB4800632CA3 /* vk_mvk_moltenvk.mm */,
 				A94FB7AD1C7DFB4800632CA3 /* vulkan.mm */,
@@ -581,7 +582,6 @@
 			isa = PBXGroup;
 			children = (
 				A9F0429D1FB4CF82009FCCB8 /* MVKCommonEnvironment.h */,
-				A9E337B1220129DD00363D2A /* MVKLogging.cpp */,
 				A9F0429E1FB4CF82009FCCB8 /* MVKLogging.h */,
 				A9B51BD6225E986A00AC74D2 /* MVKOSExtensions.h */,
 				A9B51BD2225E986A00AC74D2 /* MVKOSExtensions.mm */,
@@ -672,6 +672,7 @@
 				A94FB7E81C7DFB4800632CA3 /* MVKDeviceMemory.h in Headers */,
 				A9E4B7891E1D8AF10046A4CE /* MVKMTLResourceBindings.h in Headers */,
 				45003E73214AD4E500E989CB /* MVKExtensions.def in Headers */,
+				A9CEAAD5227378D400FAF779 /* mvk_datatypes.hpp in Headers */,
 				A90C8DEA1F45354D009CB32C /* MVKCommandEncodingPool.h in Headers */,
 				A94FB8081C7DFB4800632CA3 /* MVKResource.h in Headers */,
 				A9E53DDD2100B197002781DD /* MTLTextureDescriptor+MoltenVK.h in Headers */,
@@ -737,6 +738,7 @@
 				A94FB7E91C7DFB4800632CA3 /* MVKDeviceMemory.h in Headers */,
 				A9E4B78A1E1D8AF10046A4CE /* MVKMTLResourceBindings.h in Headers */,
 				45003E74214AD4E600E989CB /* MVKExtensions.def in Headers */,
+				A9CEAAD6227378D400FAF779 /* mvk_datatypes.hpp in Headers */,
 				A90C8DEB1F45354D009CB32C /* MVKCommandEncodingPool.h in Headers */,
 				A94FB8091C7DFB4800632CA3 /* MVKResource.h in Headers */,
 				A9E53DDE2100B197002781DD /* MTLTextureDescriptor+MoltenVK.h in Headers */,
@@ -974,7 +976,6 @@
 				A94FB8301C7DFB4800632CA3 /* vk_mvk_moltenvk.mm in Sources */,
 				A94FB8161C7DFB4800632CA3 /* MVKSwapchain.mm in Sources */,
 				A95B7D6B1D3EE486003183D3 /* MVKCommandEncoderState.mm in Sources */,
-				A9E337B5220129DD00363D2A /* MVKLogging.cpp in Sources */,
 				A93E83352121F0C8001FEBD4 /* MVKGPUCapture.mm in Sources */,
 				A9B51BD7225E986A00AC74D2 /* MVKOSExtensions.mm in Sources */,
 				A94FB7CE1C7DFB4800632CA3 /* MVKCommand.mm in Sources */,
@@ -1029,7 +1030,6 @@
 				A94FB8311C7DFB4800632CA3 /* vk_mvk_moltenvk.mm in Sources */,
 				A94FB8171C7DFB4800632CA3 /* MVKSwapchain.mm in Sources */,
 				A95B7D6C1D3EE486003183D3 /* MVKCommandEncoderState.mm in Sources */,
-				A9E337B6220129DD00363D2A /* MVKLogging.cpp in Sources */,
 				A93E83362121F0C8001FEBD4 /* MVKGPUCapture.mm in Sources */,
 				A9B51BD8225E986A00AC74D2 /* MVKOSExtensions.mm in Sources */,
 				A94FB7CF1C7DFB4800632CA3 /* MVKCommand.mm in Sources */,
@@ -1134,7 +1134,6 @@
 				GCC_OPTIMIZATION_LEVEL = 0;
 				GCC_PREPROCESSOR_DEFINITIONS = (
 					"DEBUG=1",
-					MVK_ENV_LOG_LEVEL,
 					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
 				);
 				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
@@ -1194,10 +1193,7 @@
 				GCC_INLINES_ARE_PRIVATE_EXTERN = YES;
 				GCC_NO_COMMON_BLOCKS = YES;
 				GCC_OPTIMIZATION_LEVEL = fast;
-				GCC_PREPROCESSOR_DEFINITIONS = (
-					MVK_ENV_LOG_LEVEL,
-					"SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross",
-				);
+				GCC_PREPROCESSOR_DEFINITIONS = "SPIRV_CROSS_NAMESPACE_OVERRIDE=MVK_spirv_cross";
 				GCC_SYMBOLS_PRIVATE_EXTERN = YES;
 				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
 				GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES;
diff --git a/MoltenVK/MoltenVK/API/mvk_datatypes.h b/MoltenVK/MoltenVK/API/mvk_datatypes.h
index eeeef02..41cbf7a 100644
--- a/MoltenVK/MoltenVK/API/mvk_datatypes.h
+++ b/MoltenVK/MoltenVK/API/mvk_datatypes.h
@@ -299,7 +299,7 @@
  */
 MTLSamplerAddressMode mvkMTLSamplerAddressModeFromVkSamplerAddressMode(VkSamplerAddressMode vkMode);
 
-#if MVK_MACOS
+#ifdef __MAC_OS_X_VERSION_MAX_ALLOWED
 /**
  * Returns the Metal MTLSamplerBorderColor corresponding to the specified Vulkan VkBorderColor,
  * or returns MTLSamplerBorderColorTransparentBlack if no corresponding MTLSamplerBorderColor exists.
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index d236b58..381fd4e 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -50,7 +50,7 @@
  */
 #define MVK_VERSION_MAJOR   1
 #define MVK_VERSION_MINOR   0
-#define MVK_VERSION_PATCH   34
+#define MVK_VERSION_PATCH   35
 
 #define MVK_MAKE_VERSION(major, minor, patch)    (((major) * 10000) + ((minor) * 100) + (patch))
 #define MVK_VERSION     MVK_MAKE_VERSION(MVK_VERSION_MAJOR, MVK_VERSION_MINOR, MVK_VERSION_PATCH)
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdDispatch.mm b/MoltenVK/MoltenVK/Commands/MVKCmdDispatch.mm
index beaa5d9..dc03d47 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdDispatch.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdDispatch.mm
@@ -21,7 +21,7 @@
 #include "MVKCommandPool.h"
 #include "MVKBuffer.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 
 
 #pragma mark -
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdDraw.h b/MoltenVK/MoltenVK/Commands/MVKCmdDraw.h
index f31c465..da4567f 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdDraw.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdDraw.h
@@ -70,7 +70,7 @@
 #pragma mark MVKCmdDraw
 
 /** Vulkan command to draw vertices. */
-class MVKCmdDraw : public MVKCommand, public MVKLoadStoreOverride {
+class MVKCmdDraw : public MVKCommand, public MVKLoadStoreOverrideMixin {
 
 public:
 	void setContent(uint32_t vertexCount,
@@ -94,7 +94,7 @@
 #pragma mark MVKCmdDrawIndexed
 
 /** Vulkan command to draw indexed vertices. */
-class MVKCmdDrawIndexed : public MVKCommand, public MVKLoadStoreOverride {
+class MVKCmdDrawIndexed : public MVKCommand, public MVKLoadStoreOverrideMixin {
 
 public:
 	void setContent(uint32_t indexCount,
@@ -120,7 +120,7 @@
 #pragma mark MVKCmdDrawIndirect
 
 /** Vulkan command to draw vertices indirectly. */
-class MVKCmdDrawIndirect : public MVKCommand, public MVKLoadStoreOverride {
+class MVKCmdDrawIndirect : public MVKCommand, public MVKLoadStoreOverrideMixin {
 
 public:
 	void setContent(VkBuffer buffer,
@@ -144,7 +144,7 @@
 #pragma mark MVKCmdDrawIndexedIndirect
 
 /** Vulkan command to draw indexed vertices indirectly. */
-class MVKCmdDrawIndexedIndirect : public MVKCommand, public MVKLoadStoreOverride {
+class MVKCmdDrawIndexedIndirect : public MVKCommand, public MVKLoadStoreOverrideMixin {
 
 public:
 	void setContent(VkBuffer buffer,
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm b/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm
index f1404b1..0f986d5 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdDraw.mm
@@ -22,7 +22,7 @@
 #include "MVKBuffer.h"
 #include "MVKPipeline.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 
 
 #pragma mark -
@@ -88,9 +88,8 @@
 	_storeOverride = false;
 
     // Validate
-    clearConfigurationResult();
     if ((_firstInstance != 0) && !(getDevice()->_pMetalFeatures->baseVertexInstanceDrawing)) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDraw(): The current device does not support drawing with a non-zero base instance."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDraw(): The current device does not support drawing with a non-zero base instance."));
     }
 }
 
@@ -276,12 +275,11 @@
 	_storeOverride = false;
 
     // Validate
-    clearConfigurationResult();
     if ((_firstInstance != 0) && !(getDevice()->_pMetalFeatures->baseVertexInstanceDrawing)) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndexed(): The current device does not support drawing with a non-zero base instance."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndexed(): The current device does not support drawing with a non-zero base instance."));
     }
     if ((_vertexOffset != 0) && !(getDevice()->_pMetalFeatures->baseVertexInstanceDrawing)) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndexed(): The current device does not support drawing with a non-zero base vertex."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndexed(): The current device does not support drawing with a non-zero base vertex."));
     }
 }
 
@@ -507,9 +505,8 @@
 	_storeOverride = false;
 
     // Validate
-    clearConfigurationResult();
     if ( !(getDevice()->_pMetalFeatures->indirectDrawing) ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndirect(): The current device does not support indirect drawing."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndirect(): The current device does not support indirect drawing."));
     }
 }
 
@@ -749,9 +746,8 @@
 	_storeOverride = false;
 
     // Validate
-    clearConfigurationResult();
     if ( !(getDevice()->_pMetalFeatures->indirectDrawing) ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndexedIndirect(): The current device does not support indirect drawing."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdDrawIndexedIndirect(): The current device does not support indirect drawing."));
     }
 }
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
index 3ed90af..5ce849e 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdPipeline.mm
@@ -23,7 +23,8 @@
 #include "MVKBuffer.h"
 #include "MVKPipeline.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "MVKEnvironment.h"
+#include "mvk_datatypes.hpp"
 
 
 #pragma mark -
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdQueries.mm b/MoltenVK/MoltenVK/Commands/MVKCmdQueries.mm
index 4ff8679..a15d241 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdQueries.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdQueries.mm
@@ -37,6 +37,7 @@
 #pragma mark MVKCmdBeginQuery
 
 void MVKCmdBeginQuery::added(MVKCommandBuffer* cmdBuffer) {
+	MVKCommand::added(cmdBuffer);
     _queryPool->beginQueryAddedTo(_query, cmdBuffer);
 };
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
index 4f9b3c9..519e196 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.h
@@ -33,7 +33,7 @@
 #pragma mark MVKCmdBeginRenderPass
 
 /** Vulkan command to begin a render pass. */
-class MVKCmdBeginRenderPass : public MVKCommand, public MVKLoadStoreOverride {
+class MVKCmdBeginRenderPass : public MVKCommand, public MVKLoadStoreOverrideMixin {
 
 public:
 	void setContent(const VkRenderPassBeginInfo* pRenderPassBegin,
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
index 6911aa2..3646abf 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdRenderPass.mm
@@ -22,7 +22,7 @@
 #include "MVKRenderPass.h"
 #include "MVKPipeline.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 
 
 #pragma mark -
@@ -149,9 +149,8 @@
     _lineWidth = lineWidth;
 
     // Validate
-    clearConfigurationResult();
     if (_lineWidth != 1.0 || getDevice()->_enabledFeatures.wideLines) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdSetLineWidth(): The current device does not support wide lines."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdSetLineWidth(): The current device does not support wide lines."));
     }
 }
 
@@ -208,9 +207,8 @@
     _maxDepthBounds = maxDepthBounds;
 
     // Validate
-    clearConfigurationResult();
     if (getDevice()->_enabledFeatures.depthBounds) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdSetDepthBounds(): The current device does not support setting depth bounds."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdSetDepthBounds(): The current device does not support setting depth bounds."));
     }
 }
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
index 56cd2a5..242a908 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCmdTransfer.mm
@@ -25,7 +25,9 @@
 #include "MVKFramebuffer.h"
 #include "MVKRenderPass.h"
 #include "MTLRenderPassDescriptor+MoltenVK.h"
-#include "mvk_datatypes.h"
+#include "MVKEnvironment.h"
+#include "MVKLogging.h"
+#include "mvk_datatypes.hpp"
 
 
 #pragma mark -
@@ -58,9 +60,8 @@
 	}
 
     // Validate
-    clearConfigurationResult();
     if ((_srcImage->getMTLTextureType() == MTLTextureType3D) != (_dstImage->getMTLTextureType() == MTLTextureType3D)) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Metal does not support copying to or from slices of a 3D texture."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdCopyImage(): Metal does not support copying to or from slices of a 3D texture."));
     }
 }
 
@@ -171,12 +172,11 @@
 	}
 
     // Validate
-    clearConfigurationResult();
     if (_blitKey.isDepthFormat() && renderRegionCount > 0) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdBlitImage(): Scaling of depth/stencil images is not supported."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdBlitImage(): Scaling of depth/stencil images is not supported."));
     }
     if ( !_mtlTexBlitRenders.empty() && mvkMTLPixelFormatIsStencilFormat(_mtlPixFmt)) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdBlitImage(): Stencil image formats cannot be scaled or inverted."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdBlitImage(): Stencil image formats cannot be scaled or inverted."));
     }
 }
 
@@ -648,10 +648,9 @@
     }
 
     // Validate
-    clearConfigurationResult();
     if ( !_image->hasExpectedTexelSize() ) {
         const char* cmdName = _toImage ? "vkCmdCopyBufferToImage" : "vkCmdCopyImageToBuffer";
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "%s(): The image is using Metal format %s as a substitute for Vulkan format %s. Since the pixel size is different, content for the image cannot be copied to or from a buffer.", cmdName, mvkMTLPixelFormatName(_image->getMTLPixelFormat()), mvkVkFormatName(_image->getVkFormat())));
+        setConfigurationResult(reportError(VK_ERROR_FORMAT_NOT_SUPPORTED, "%s(): The image is using Metal format %s as a substitute for Vulkan format %s. Since the pixel size is different, content for the image cannot be copied to or from a buffer.", cmdName, mvkMTLPixelFormatName(_image->getMTLPixelFormat()), mvkVkFormatName(_image->getVkFormat())));
     }
 }
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommand.h b/MoltenVK/MoltenVK/Commands/MVKCommand.h
index 0bb2ff0..ce32b91 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommand.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommand.h
@@ -37,8 +37,11 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
+
     /** Called when this command is added to a command buffer. */
-    virtual void added(MVKCommandBuffer* cmdBuffer) {};
+	virtual void added(MVKCommandBuffer* cmdBuffer) { _commandBuffer = cmdBuffer; };
 
     /** Indicates that this command has a valid configuration and can be encoded. */
     inline bool canEncode() { return _configurationResult == VK_SUCCESS; }
@@ -84,18 +87,31 @@
 
 protected:
     MVKCommandTypePool<MVKCommand>* _pool;
+	MVKCommandBuffer* _commandBuffer = nullptr;
 };
 
 
 #pragma mark -
 #pragma mark MVKCommandTypePool
 
+/**
+ * Static function for MVKCommandTypePool template to call to resolve getVulkanAPIObject().
+ * Needed because MVKCommandTypePool template cannot have function implementation outside
+ * the template, and MVKCommandPool is not completely defined in this header file.
+ */
+MVKVulkanAPIObject* mvkCommandTypePoolGetVulkanAPIObject(MVKCommandPool* cmdPool);
+
+
 /** A pool of MVKCommand instances of a particular type. */
 template <class T>
 class MVKCommandTypePool : public MVKObjectPool<T> {
 
 public:
 
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return mvkCommandTypePoolGetVulkanAPIObject(_commandPool); };
+
     /** Some commands require access to the command pool to access resources. */
     MVKCommandPool* getCommandPool() { return _commandPool; }
 
@@ -114,10 +130,16 @@
 
 
 #pragma mark -
-#pragma mark MVKLoadStoreOverride
+#pragma mark MVKLoadStoreOverrideMixin
 
-/** Shared state with all draw commands */
-class MVKLoadStoreOverride {
+/**
+ * Shared state mixin for draw commands.
+ *
+ * As a mixin, this class should only be used as a component of multiple inheritance.
+ * Any class that inherits from this class should also inherit from MVKBaseObject.
+ * This requirement is to avoid the diamond problem of multiple inheritance.
+ */
+class MVKLoadStoreOverrideMixin {
 public:
     void setLoadOverride(bool loadOverride);
     void setStoreOverride(bool storeOverride);
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommand.mm b/MoltenVK/MoltenVK/Commands/MVKCommand.mm
index ee43371..d05a68d 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommand.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommand.mm
@@ -27,7 +27,13 @@
 //	Opt 1: Leave arrays & rezs allocated in command, per current practice
 //  Opt 2: Allocate arrays & rezs from pools in Command pool, and return in returnToPool
 
-void MVKCommand::returnToPool() { _pool->returnObject(this); }
+void MVKCommand::returnToPool() {
+	clearConfigurationResult();
+	_commandBuffer = nullptr;
+	_pool->returnObject(this);
+}
+
+MVKVulkanAPIObject* MVKCommand::getVulkanAPIObject() { return _commandBuffer ? _commandBuffer->getVulkanAPIObject() : getCommandPool()->getVulkanAPIObject(); };
 
 MVKCommandPool* MVKCommand::getCommandPool() { return _pool->getCommandPool(); }
 
@@ -39,13 +45,19 @@
 
 
 #pragma mark -
-#pragma mark MVKLoadStoreOverride
+#pragma mark MVKCommandTypePool
 
-void MVKLoadStoreOverride::setLoadOverride(bool loadOverride) {
+MVKVulkanAPIObject* mvkCommandTypePoolGetVulkanAPIObject(MVKCommandPool* cmdPool) { return cmdPool->getVulkanAPIObject(); }
+
+
+#pragma mark -
+#pragma mark MVKLoadStoreOverrideMixin
+
+void MVKLoadStoreOverrideMixin::setLoadOverride(bool loadOverride) {
 	_loadOverride = loadOverride;
 }
 
-void MVKLoadStoreOverride::setStoreOverride(bool storeOverride) {
+void MVKLoadStoreOverrideMixin::setStoreOverride(bool storeOverride) {
 	_storeOverride = storeOverride;
 }
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
index 36e7061..8e2fa9b 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.h
@@ -19,7 +19,6 @@
 #pragma once
 
 #include "MVKDevice.h"
-#include "MVKSync.h"
 #include "MVKCommand.h"
 #include "MVKCommandEncoderState.h"
 #include "MVKMTLBufferAllocation.h"
@@ -42,7 +41,7 @@
 class MVKComputePipeline;
 class MVKCmdBeginRenderPass;
 class MVKCmdEndRenderPass;
-class MVKLoadStoreOverride;
+class MVKLoadStoreOverrideMixin;
 
 typedef uint64_t MVKMTLCommandBufferID;
 
@@ -51,10 +50,16 @@
 #pragma mark MVKCommandBuffer
 
 /** Represents a Vulkan command pool. */
-class MVKCommandBuffer : public MVKDispatchableDeviceObject {
+class MVKCommandBuffer : public MVKDispatchableVulkanAPIObject, public MVKDeviceTrackingMixin {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_BUFFER_EXT; }
+
+	/** Returns a pointer to the Vulkan instance. */
+	MVKInstance* getInstance() override { return _device->getInstance(); }
+
 	/** Prepares this instance to receive commands. */
 	VkResult begin(const VkCommandBufferBeginInfo* pBeginInfo);
 
@@ -73,12 +78,6 @@
 	/** Submit the commands in this buffer as part of the queue submission. */
 	void submit(MVKQueueCommandBufferSubmission* cmdBuffSubmit);
 
-	/*** If no error has occured yet, records the specified result. */
-    inline void recordResult(VkResult vkResult) { if (_recordingResult == VK_SUCCESS) { _recordingResult = vkResult; } }
-
-    /** Returns the first abnormal VkResult that occured during command recording. */
-    inline VkResult getRecordingResult() { return _recordingResult; }
-
     /** Returns whether this command buffer can be submitted to a queue more than once. */
     inline bool getIsReusable() { return _isReusable; }
 
@@ -113,7 +112,7 @@
 	void recordBindPipeline(MVKCmdBindPipeline* mvkBindPipeline);
 	
 	/** Update the last recorded drawcall to determine load/store actions */
-	void recordDraw(MVKLoadStoreOverride* mvkDraw);
+	void recordDraw(MVKLoadStoreOverrideMixin* mvkDraw);
 	
 	/** The most recent recorded begin renderpass */
 	MVKCmdBeginRenderPass* _lastBeginRenderPass;
@@ -122,12 +121,12 @@
 	MVKCmdBindPipeline* _lastTessellationPipeline;
 	
 	/** The most recent recorded multi-pass (ie, tessellation) draw */
-	MVKLoadStoreOverride* _lastTessellationDraw;
+	MVKLoadStoreOverrideMixin* _lastTessellationDraw;
 
 
 #pragma mark Construction
 
-	MVKCommandBuffer(MVKDevice* device) : MVKDispatchableDeviceObject(device) {}
+	MVKCommandBuffer(MVKDevice* device) : MVKDeviceTrackingMixin(device) {}
 
 	~MVKCommandBuffer() override;
 
@@ -149,6 +148,7 @@
 	friend class MVKCommandEncoder;
 	friend class MVKCommandPool;
 
+	MVKBaseObject* getBaseObject() override { return this; };
 	void init(const VkCommandBufferAllocateInfo* pAllocateInfo);
 	bool canExecute();
 	bool canPrefill();
@@ -159,7 +159,6 @@
 	MVKCommand* _tail = nullptr;
 	uint32_t _commandCount;
 	std::atomic_flag _isExecutingNonConcurrently;
-	VkResult _recordingResult;
 	VkCommandBufferInheritanceInfo _secondaryInheritanceInfo;
 	id<MTLCommandBuffer> _prefilledMTLCmdBuffer = nil;
 	bool _isSecondary;
@@ -256,6 +255,9 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _cmdBuffer->getVulkanAPIObject(); };
+
 	/** Encode commands from the command buffer onto the Metal command buffer. */
 	void encode(id<MTLCommandBuffer> mtlCmdBuff);
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
index 4c68f3d..4ccfe52 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandBuffer.mm
@@ -24,6 +24,7 @@
 #include "MVKFramebuffer.h"
 #include "MVKQueryPool.h"
 #include "MVKFoundation.h"
+#include "MVKLogging.h"
 #include "MTLRenderPassDescriptor+MoltenVK.h"
 #include "MVKCmdDraw.h"
 
@@ -37,7 +38,7 @@
 
 	reset(0);
 
-	_recordingResult = VK_SUCCESS;
+	clearConfigurationResult();
 	_canAcceptCommands = true;
 
 	VkCommandBufferUsageFlags usage = pBeginInfo->flags;
@@ -50,7 +51,7 @@
 	bool hasInheritInfo = mvkSetOrClear(&_secondaryInheritanceInfo, pInheritInfo);
 	_doesContinueRenderPass = mvkAreFlagsEnabled(usage, VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT) && hasInheritInfo;
 
-	return _recordingResult;
+	return getConfigurationResult();
 }
 
 VkResult MVKCommandBuffer::reset(VkCommandBufferResetFlags flags) {
@@ -71,9 +72,9 @@
 	_supportsConcurrentExecution = false;
 	_wasExecuted = false;
 	_isExecutingNonConcurrently.clear();
-	_recordingResult = VK_NOT_READY;
 	_commandCount = 0;
 	_initialVisibilityResultMTLBuffer = nil;		// not retained
+	setConfigurationResult(VK_NOT_READY);
 
 	if (mvkAreFlagsEnabled(flags, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT)) {
 		// TODO: what are we releasing or returning here?
@@ -85,12 +86,12 @@
 VkResult MVKCommandBuffer::end() {
 	_canAcceptCommands = false;
 	prefill();
-	return _recordingResult;
+	return getConfigurationResult();
 }
 
 void MVKCommandBuffer::addCommand(MVKCommand* command) {
 	if ( !_canAcceptCommands ) {
-		recordResult(mvkNotifyErrorWithText(VK_NOT_READY, "Command buffer cannot accept commands before vkBeginCommandBuffer() is called."));
+		setConfigurationResult(reportError(VK_NOT_READY, "Command buffer cannot accept commands before vkBeginCommandBuffer() is called."));
 		return;
 	}
 
@@ -102,7 +103,7 @@
 
     command->added(this);
 
-    recordResult(command->getConfigurationResult());
+    setConfigurationResult(command->getConfigurationResult());
 }
 
 void MVKCommandBuffer::submit(MVKQueueCommandBufferSubmission* cmdBuffSubmit) {
@@ -121,17 +122,17 @@
 
 bool MVKCommandBuffer::canExecute() {
 	if (_isSecondary) {
-		recordResult(mvkNotifyErrorWithText(VK_NOT_READY, "Secondary command buffers may not be submitted directly to a queue."));
+		setConfigurationResult(reportError(VK_NOT_READY, "Secondary command buffers may not be submitted directly to a queue."));
 		return false;
 	}
 	if ( !_isReusable && _wasExecuted ) {
-		recordResult(mvkNotifyErrorWithText(VK_NOT_READY, "Command buffer does not support execution more that once."));
+		setConfigurationResult(reportError(VK_NOT_READY, "Command buffer does not support execution more that once."));
 		return false;
 	}
 
 	// Do this test last so that _isExecutingNonConcurrently is only set if everything else passes
 	if ( !_supportsConcurrentExecution && _isExecutingNonConcurrently.test_and_set()) {
-		recordResult(mvkNotifyErrorWithText(VK_NOT_READY, "Command buffer does not support concurrent execution."));
+		setConfigurationResult(reportError(VK_NOT_READY, "Command buffer does not support concurrent execution."));
 		return false;
 	}
 
@@ -217,7 +218,7 @@
 		_lastTessellationPipeline = nullptr;
 }
 
-void MVKCommandBuffer::recordDraw(MVKLoadStoreOverride* mvkDraw) {
+void MVKCommandBuffer::recordDraw(MVKLoadStoreOverrideMixin* mvkDraw) {
 	if (_lastTessellationPipeline != nullptr) {
 		// If a multi-pass pipeline is bound and we've already drawn something, need to override load actions
 		mvkDraw->setLoadOverride(true);
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
index eb72f48..87646d2 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.h
@@ -20,6 +20,7 @@
 
 #include "MVKMTLResourceBindings.h"
 #include "MVKCommandResourceFactory.h"
+#include "MVKDevice.h"
 #include "MVKVector.h"
 
 class MVKCommandEncoder;
@@ -43,6 +44,9 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
+
     /**
      * Marks the content of this instance as dirty, relative to the
      * current or next Metal render pass, and in need of submission to Metal.
@@ -399,6 +403,9 @@
         }
     }
 
+	void updateSwizzle(MVKVector<uint32_t> &constants, uint32_t index, uint32_t swizzle);
+	void assertMissingSwizzles(bool needsSwizzle, const char* stageName, MVKVector<MVKMTLTextureBinding>& texBindings);
+
 };
 
 
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
index 7afca5a..92888b1 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncoderState.mm
@@ -23,12 +23,17 @@
 #include "MVKPipeline.h"
 #include "MVKQueryPool.h"
 #include "MVKLogging.h"
-#include "mvk_datatypes.h"
 
 using namespace std;
 
 
 #pragma mark -
+#pragma mark MVKCommandEncoderState
+
+MVKVulkanAPIObject* MVKCommandEncoderState::getVulkanAPIObject() { return _cmdEncoder->getVulkanAPIObject(); };
+
+
+#pragma mark -
 #pragma mark MVKPipelineCommandEncoderState
 
 void MVKPipelineCommandEncoderState::setPipeline(MVKPipeline* pipeline) {
@@ -443,13 +448,13 @@
 #pragma mark MVKResourcesCommandEncoderState
 
 // Updates the swizzle for an image in the given vector.
-static void updateSwizzle(MVKVector<uint32_t> &constants, uint32_t index, uint32_t swizzle) {
+void MVKResourcesCommandEncoderState::updateSwizzle(MVKVector<uint32_t> &constants, uint32_t index, uint32_t swizzle) {
 	if (index >= constants.size()) { constants.resize(index + 1); }
 	constants[index] = swizzle;
 }
 
 // If a swizzle is needed for this stage, iterates all the bindings and logs errors for those that need texture swizzling.
-static void assertMissingSwizzles(bool needsSwizzle, const char* stageName, MVKVector<MVKMTLTextureBinding>& texBindings) {
+void MVKResourcesCommandEncoderState::assertMissingSwizzles(bool needsSwizzle, const char* stageName, MVKVector<MVKMTLTextureBinding>& texBindings) {
 	if (needsSwizzle) {
 		for (MVKMTLTextureBinding& tb : texBindings) {
 			VkComponentMapping vkcm = mvkUnpackSwizzle(tb.swizzle);
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h
index 72e3fcb..53dab25 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.h
@@ -18,7 +18,6 @@
 
 #pragma once
 
-#include "MVKDevice.h"
 #include "MVKCommandResourceFactory.h"
 #include "MVKMTLBufferAllocation.h"
 #include <unordered_map>
@@ -27,6 +26,9 @@
 #import <Metal/Metal.h>
 
 
+class MVKCommandPool;
+
+
 #pragma mark -
 #pragma mark MVKCommandEncodingPool
 
@@ -37,12 +39,15 @@
  *
  * Access to the content within this pool is thread-safe.
  */
-class MVKCommandEncodingPool : public MVKBaseDeviceObject {
+class MVKCommandEncodingPool : public MVKBaseObject {
 
 public:
 
 #pragma mark Command resources
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
+
 	/** Returns a MTLRenderPipelineState to support certain Vulkan BLIT commands. */
     id<MTLRenderPipelineState> getCmdBlitImageMTLRenderPipelineState(MVKRPSKeyBlitImg& blitKey);
 
@@ -127,13 +132,14 @@
 
 #pragma mark Construction
 
-	MVKCommandEncodingPool(MVKDevice* device);
+	MVKCommandEncodingPool(MVKCommandPool* commandPool);
 
 	~MVKCommandEncodingPool() override;
 
-private:
+protected:
 	void destroyMetalResources();
 
+	MVKCommandPool* _commandPool;
 	std::mutex _lock;
     std::unordered_map<MVKRPSKeyBlitImg, id<MTLRenderPipelineState>> _cmdBlitImageMTLRenderPipelineStates;
 	std::unordered_map<MVKRPSKeyClearAtt, id<MTLRenderPipelineState>> _cmdClearMTLRenderPipelineStates;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm
index afede7e..82ed5a1 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandEncodingPool.mm
@@ -17,13 +17,18 @@
  */
 
 #include "MVKCommandEncodingPool.h"
+#include "MVKCommandPool.h"
 #include "MVKImage.h"
 
 using namespace std;
 
+
 #pragma mark -
 #pragma mark MVKCommandEncodingPool
 
+MVKVulkanAPIObject* MVKCommandEncodingPool::getVulkanAPIObject() { return _commandPool->getVulkanAPIObject(); };
+
+
 // In order to provide thread-safety with minimal performance impact, each of these access
 // functions follows a 3-step pattern:
 //
@@ -34,25 +39,25 @@
 // Step 1 handles the common case where the resource exists, without the expense of a lock.
 // Step 2 guards against a potential race condition where two threads get past Step 1 at
 // the same time, and then both barrel ahead onto Step 3.
-#define MVK_ENC_REZ_ACCESS(rezAccess, rezFactoryFunc)				\
-	auto rez = rezAccess;											\
-	if (rez) { return rez; }										\
-																	\
-	lock_guard<mutex> lock(_lock);									\
-	rez = rezAccess;												\
-	if (rez) { return rez; }										\
-																	\
-	rez = _device->getCommandResourceFactory()->rezFactoryFunc;		\
-	rezAccess = rez;												\
+#define MVK_ENC_REZ_ACCESS(rezAccess, rezFactoryFunc)								\
+	auto rez = rezAccess;															\
+	if (rez) { return rez; }														\
+																					\
+	lock_guard<mutex> lock(_lock);													\
+	rez = rezAccess;																\
+	if (rez) { return rez; }														\
+																					\
+	rez = _commandPool->getDevice()->getCommandResourceFactory()->rezFactoryFunc;	\
+	rezAccess = rez;																\
 	return rez
 
 
 id<MTLRenderPipelineState> MVKCommandEncodingPool::getCmdClearMTLRenderPipelineState(MVKRPSKeyClearAtt& attKey) {
-	MVK_ENC_REZ_ACCESS(_cmdClearMTLRenderPipelineStates[attKey], newCmdClearMTLRenderPipelineState(attKey));
+	MVK_ENC_REZ_ACCESS(_cmdClearMTLRenderPipelineStates[attKey], newCmdClearMTLRenderPipelineState(attKey, _commandPool));
 }
 
 id<MTLRenderPipelineState> MVKCommandEncodingPool::getCmdBlitImageMTLRenderPipelineState(MVKRPSKeyBlitImg& blitKey) {
-	MVK_ENC_REZ_ACCESS(_cmdBlitImageMTLRenderPipelineStates[blitKey], newCmdBlitImageMTLRenderPipelineState(blitKey));
+	MVK_ENC_REZ_ACCESS(_cmdBlitImageMTLRenderPipelineStates[blitKey], newCmdBlitImageMTLRenderPipelineState(blitKey, _commandPool));
 }
 
 id<MTLSamplerState> MVKCommandEncodingPool::getCmdBlitImageMTLSamplerState(MTLSamplerMinMagFilter mtlFilter) {
@@ -102,27 +107,27 @@
 }
 
 id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdCopyBufferBytesMTLComputePipelineState() {
-	MVK_ENC_REZ_ACCESS(_mtlCopyBufferBytesComputePipelineState, newCmdCopyBufferBytesMTLComputePipelineState());
+	MVK_ENC_REZ_ACCESS(_mtlCopyBufferBytesComputePipelineState, newCmdCopyBufferBytesMTLComputePipelineState(_commandPool));
 }
 
 id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdFillBufferMTLComputePipelineState() {
-	MVK_ENC_REZ_ACCESS(_mtlFillBufferComputePipelineState, newCmdFillBufferMTLComputePipelineState());
+	MVK_ENC_REZ_ACCESS(_mtlFillBufferComputePipelineState, newCmdFillBufferMTLComputePipelineState(_commandPool));
 }
 
 id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needsTempBuff) {
-	MVK_ENC_REZ_ACCESS(_mtlCopyBufferToImage3DDecompressComputePipelineState[needsTempBuff ? 1 : 0], newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(needsTempBuff));
+	MVK_ENC_REZ_ACCESS(_mtlCopyBufferToImage3DDecompressComputePipelineState[needsTempBuff ? 1 : 0], newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(needsTempBuff, _commandPool));
 }
 
 id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdDrawIndirectConvertBuffersMTLComputePipelineState(bool indexed) {
-	MVK_ENC_REZ_ACCESS(_mtlDrawIndirectConvertBuffersComputePipelineState[indexed ? 1 : 0], newCmdDrawIndirectConvertBuffersMTLComputePipelineState(indexed));
+	MVK_ENC_REZ_ACCESS(_mtlDrawIndirectConvertBuffersComputePipelineState[indexed ? 1 : 0], newCmdDrawIndirectConvertBuffersMTLComputePipelineState(indexed, _commandPool));
 }
 
 id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(MTLIndexType type) {
-	MVK_ENC_REZ_ACCESS(_mtlDrawIndexedCopyIndexBufferComputePipelineState[type == MTLIndexTypeUInt16 ? 1 : 0], newCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(type));
+	MVK_ENC_REZ_ACCESS(_mtlDrawIndexedCopyIndexBufferComputePipelineState[type == MTLIndexTypeUInt16 ? 1 : 0], newCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(type, _commandPool));
 }
 
 id<MTLComputePipelineState> MVKCommandEncodingPool::getCmdCopyQueryPoolResultsMTLComputePipelineState() {
-	MVK_ENC_REZ_ACCESS(_mtlCopyQueryPoolResultsComputePipelineState, newCmdCopyQueryPoolResultsMTLComputePipelineState());
+	MVK_ENC_REZ_ACCESS(_mtlCopyQueryPoolResultsComputePipelineState, newCmdCopyQueryPoolResultsMTLComputePipelineState(_commandPool));
 }
 
 void MVKCommandEncodingPool::clear() {
@@ -133,8 +138,8 @@
 
 #pragma mark Construction
 
-MVKCommandEncodingPool::MVKCommandEncodingPool(MVKDevice* device) : MVKBaseDeviceObject(device),
-    _mtlBufferAllocator(device, device->_pMetalFeatures->maxMTLBufferSize, true) {
+MVKCommandEncodingPool::MVKCommandEncodingPool(MVKCommandPool* commandPool) : _commandPool(commandPool),
+    _mtlBufferAllocator(commandPool->getDevice(), commandPool->getDevice()->_pMetalFeatures->maxMTLBufferSize, true) {
 }
 
 MVKCommandEncodingPool::~MVKCommandEncodingPool() {
@@ -143,6 +148,8 @@
 
 /**  Ensure all cached Metal components are released. */
 void MVKCommandEncodingPool::destroyMetalResources() {
+	MVKDevice* mvkDev = _commandPool->getDevice();
+
     for (auto& pair : _cmdBlitImageMTLRenderPipelineStates) { [pair.second release]; }
     _cmdBlitImageMTLRenderPipelineStates.clear();
 
@@ -152,13 +159,13 @@
     for (auto& pair : _mtlDepthStencilStates) { [pair.second release]; }
     _mtlDepthStencilStates.clear();
 
-    for (auto& pair : _transferImages) { _device->destroyImage(pair.second, nullptr); }
+    for (auto& pair : _transferImages) { mvkDev->destroyImage(pair.second, nullptr); }
     _transferImages.clear();
 
-    for (auto& pair : _transferBuffers) { _device->destroyBuffer(pair.second, nullptr); }
+    for (auto& pair : _transferBuffers) { mvkDev->destroyBuffer(pair.second, nullptr); }
     _transferBuffers.clear();
 
-    for (auto& pair : _transferBufferMemory) { _device->freeMemory(pair.second, nullptr); }
+    for (auto& pair : _transferBufferMemory) { mvkDev->freeMemory(pair.second, nullptr); }
     _transferBufferMemory.clear();
 
     [_cmdBlitImageLinearMTLSamplerState release];
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandPool.h b/MoltenVK/MoltenVK/Commands/MVKCommandPool.h
index 41971ec..efabe55 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandPool.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandPool.h
@@ -49,10 +49,13 @@
  * of this pool should be done during the setContent() function of each MVKCommand, and NOT 
  * during the execution of the command via the MVKCommand::encode() member function.
  */
-class MVKCommandPool : public MVKBaseDeviceObject {
+class MVKCommandPool : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_COMMAND_POOL_EXT; }
+
 #pragma mark Command type pools
 
 	MVKCommandTypePool<MVKCmdPipelineBarrier> _cmdPipelineBarrierPool;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm b/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm
index cfac20a..273da53 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandPool.mm
@@ -22,7 +22,7 @@
 #include "MVKQueue.h"
 #include "MVKDeviceMemory.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 #include "MVKLogging.h"
 
 using namespace std;
@@ -56,7 +56,10 @@
 		mvkCmdBuff->init(pAllocateInfo);
 		_allocatedCommandBuffers.insert(mvkCmdBuff);
         pCmdBuffer[cbIdx] = mvkCmdBuff->getVkCommandBuffer();
-		if (rslt == VK_SUCCESS) { rslt = mvkCmdBuff->getConfigurationResult(); }
+
+		// Command buffers start out in a VK_NOT_READY config result
+		VkResult cbRslt = mvkCmdBuff->getConfigurationResult();
+		if (rslt == VK_SUCCESS && cbRslt != VK_NOT_READY) { rslt = cbRslt; }
 	}
 	return rslt;
 }
@@ -127,9 +130,9 @@
 
 MVKCommandPool::MVKCommandPool(MVKDevice* device,
 							   const VkCommandPoolCreateInfo* pCreateInfo) :
-	MVKBaseDeviceObject(device),
+	MVKVulkanAPIDeviceObject(device),
 	_commandBufferPool(device),
-	_commandEncodingPool(device),
+	_commandEncodingPool(this),
 	_queueFamilyIndex(pCreateInfo->queueFamilyIndex),
 	_cmdPipelineBarrierPool(this, true),
 	_cmdBindPipelinePool(this, true),
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
index ee0051d..a9ddfb0 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.h
@@ -20,7 +20,7 @@
 
 #include "MVKDevice.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 #include <string>
 
 #import <Metal/Metal.h>
@@ -306,10 +306,14 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _device->getVulkanAPIObject(); };
+
 #pragma mark Command resources
 
 	/** Returns a new MTLRenderPipelineState to support certain Vulkan BLIT commands. */
-	id<MTLRenderPipelineState> newCmdBlitImageMTLRenderPipelineState(MVKRPSKeyBlitImg& blitKey);
+	id<MTLRenderPipelineState> newCmdBlitImageMTLRenderPipelineState(MVKRPSKeyBlitImg& blitKey,
+																	 MVKVulkanAPIDeviceObject* owner);
 
 	/**
 	 * Returns a new MTLSamplerState dedicated to rendering to a texture using the
@@ -321,7 +325,8 @@
 	 * Returns a new MTLRenderPipelineState dedicated to rendering to several 
 	 * attachments to support clearing regions of those attachments.
 	 */
-	id<MTLRenderPipelineState> newCmdClearMTLRenderPipelineState(MVKRPSKeyClearAtt& attKey);
+	id<MTLRenderPipelineState> newCmdClearMTLRenderPipelineState(MVKRPSKeyClearAtt& attKey,
+																 MVKVulkanAPIDeviceObject* owner);
 
 	/**
 	 * Returns a new MTLDepthStencilState dedicated to rendering to several 
@@ -353,22 +358,25 @@
     MVKBuffer* newMVKBuffer(MVKBufferDescriptorData& buffData, MVKDeviceMemory*& buffMem);
     
     /** Returns a new MTLComputePipelineState for copying between two buffers with byte-aligned copy regions. */
-    id<MTLComputePipelineState> newCmdCopyBufferBytesMTLComputePipelineState();
+    id<MTLComputePipelineState> newCmdCopyBufferBytesMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner);
 
 	/** Returns a new MTLComputePipelineState for filling a buffer. */
-	id<MTLComputePipelineState> newCmdFillBufferMTLComputePipelineState();
+	id<MTLComputePipelineState> newCmdFillBufferMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner);
 
 	/** Returns a new MTLComputePipelineState for copying between a buffer holding compressed data and a 3D image. */
-	id<MTLComputePipelineState> newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needTempBuf);
+	id<MTLComputePipelineState> newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needTempBuf,
+																						   MVKVulkanAPIDeviceObject* owner);
 
 	/** Returns a new MTLComputePipelineState for converting an indirect buffer for use in a tessellated draw. */
-	id<MTLComputePipelineState> newCmdDrawIndirectConvertBuffersMTLComputePipelineState(bool indexed);
+	id<MTLComputePipelineState> newCmdDrawIndirectConvertBuffersMTLComputePipelineState(bool indexed,
+																						MVKVulkanAPIDeviceObject* owner);
 
 	/** Returns a new MTLComputePipelineState for copying an index buffer for use in a tessellated draw. */
-	id<MTLComputePipelineState> newCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(MTLIndexType type);
+	id<MTLComputePipelineState> newCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(MTLIndexType type,
+																						MVKVulkanAPIDeviceObject* owner);
 
 	/** Returns a new MTLComputePipelineState for copying query results to a buffer. */
-	id<MTLComputePipelineState> newCmdCopyQueryPoolResultsMTLComputePipelineState();
+	id<MTLComputePipelineState> newCmdCopyQueryPoolResultsMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner);
 
 
 #pragma mark Construction
@@ -386,8 +394,10 @@
 	NSString* getMTLFormatTypeString(MTLPixelFormat mtlPixFmt);
     id<MTLFunction> getFunctionNamed(const char* funcName);
 	id<MTLFunction> newMTLFunction(NSString* mslSrcCode, NSString* funcName);
-    id<MTLRenderPipelineState> newMTLRenderPipelineState(MTLRenderPipelineDescriptor* plDesc);
-	id<MTLComputePipelineState> newMTLComputePipelineState(id<MTLFunction> mtlFunction);
+	id<MTLRenderPipelineState> newMTLRenderPipelineState(MTLRenderPipelineDescriptor* plDesc,
+														 MVKVulkanAPIDeviceObject* owner);
+	id<MTLComputePipelineState> newMTLComputePipelineState(id<MTLFunction> mtlFunction,
+														   MVKVulkanAPIDeviceObject* owner);
 
 	id<MTLLibrary> _mtlLibrary;
 	MVKDeviceMemory* _transferImageMemory;
diff --git a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
index 3ff0399..28d2327 100644
--- a/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKCommandResourceFactory.mm
@@ -31,7 +31,8 @@
 #pragma mark -
 #pragma mark MVKCommandResourceFactory
 
-id<MTLRenderPipelineState> MVKCommandResourceFactory::newCmdBlitImageMTLRenderPipelineState(MVKRPSKeyBlitImg& blitKey) {
+id<MTLRenderPipelineState> MVKCommandResourceFactory::newCmdBlitImageMTLRenderPipelineState(MVKRPSKeyBlitImg& blitKey,
+																							MVKVulkanAPIDeviceObject* owner) {
     MTLRenderPipelineDescriptor* plDesc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
     plDesc.label = @"CmdBlitImage";
 
@@ -69,7 +70,7 @@
     vbDesc.stepRate = 1;
     vbDesc.stride = vtxStride;
 
-    return newMTLRenderPipelineState(plDesc);
+    return newMTLRenderPipelineState(plDesc, owner);
 }
 
 id<MTLSamplerState> MVKCommandResourceFactory::newCmdBlitImageMTLSamplerState(MTLSamplerMinMagFilter mtlFilter) {
@@ -85,7 +86,8 @@
     return [getMTLDevice() newSamplerStateWithDescriptor: sDesc];
 }
 
-id<MTLRenderPipelineState> MVKCommandResourceFactory::newCmdClearMTLRenderPipelineState(MVKRPSKeyClearAtt& attKey) {
+id<MTLRenderPipelineState> MVKCommandResourceFactory::newCmdClearMTLRenderPipelineState(MVKRPSKeyClearAtt& attKey,
+																						MVKVulkanAPIDeviceObject* owner) {
     MTLRenderPipelineDescriptor* plDesc = [[[MTLRenderPipelineDescriptor alloc] init] autorelease];
     plDesc.label = @"CmdClearAttachments";
 	plDesc.vertexFunction = getClearVertFunction(attKey);
@@ -124,7 +126,7 @@
     vbDesc.stepRate = 1;
     vbDesc.stride = vtxStride;
 
-    return newMTLRenderPipelineState(plDesc);
+    return newMTLRenderPipelineState(plDesc, owner);
 }
 
 id<MTLFunction> MVKCommandResourceFactory::getBlitFragFunction(MVKRPSKeyBlitImg& blitKey) {
@@ -372,31 +374,37 @@
     return mvkBuff;
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyBufferBytesMTLComputePipelineState() {
-	return newMTLComputePipelineState(getFunctionNamed("cmdCopyBufferBytes"));
+id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyBufferBytesMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner) {
+	return newMTLComputePipelineState(getFunctionNamed("cmdCopyBufferBytes"), owner);
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdFillBufferMTLComputePipelineState() {
-	return newMTLComputePipelineState(getFunctionNamed("cmdFillBuffer"));
+id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdFillBufferMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner) {
+	return newMTLComputePipelineState(getFunctionNamed("cmdFillBuffer"), owner);
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needTempBuf) {
-	return newMTLComputePipelineState(getFunctionNamed(needTempBuf ? "cmdCopyBufferToImage3DDecompressTempBufferDXTn" :
-																	 "cmdCopyBufferToImage3DDecompressDXTn"));
+id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyBufferToImage3DDecompressMTLComputePipelineState(bool needTempBuf,
+																												  MVKVulkanAPIDeviceObject* owner) {
+	return newMTLComputePipelineState(getFunctionNamed(needTempBuf
+													   ? "cmdCopyBufferToImage3DDecompressTempBufferDXTn"
+													   : "cmdCopyBufferToImage3DDecompressDXTn"), owner);
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdDrawIndirectConvertBuffersMTLComputePipelineState(bool indexed) {
-	return newMTLComputePipelineState(getFunctionNamed(indexed ? "cmdDrawIndexedIndirectConvertBuffers" :
-                                                                 "cmdDrawIndirectConvertBuffers"));
+id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdDrawIndirectConvertBuffersMTLComputePipelineState(bool indexed,
+																											   MVKVulkanAPIDeviceObject* owner) {
+	return newMTLComputePipelineState(getFunctionNamed(indexed
+													   ? "cmdDrawIndexedIndirectConvertBuffers"
+													   : "cmdDrawIndirectConvertBuffers"), owner);
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(MTLIndexType type) {
-	return newMTLComputePipelineState(getFunctionNamed(type == MTLIndexTypeUInt16 ? "cmdDrawIndexedCopyIndex16Buffer" :
-    "cmdDrawIndexedCopyIndex32Buffer"));
+id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdDrawIndexedCopyIndexBufferMTLComputePipelineState(MTLIndexType type,
+																											   MVKVulkanAPIDeviceObject* owner) {
+	return newMTLComputePipelineState(getFunctionNamed(type == MTLIndexTypeUInt16
+													   ? "cmdDrawIndexedCopyIndex16Buffer"
+													   : "cmdDrawIndexedCopyIndex32Buffer"), owner);
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyQueryPoolResultsMTLComputePipelineState() {
-	return newMTLComputePipelineState(getFunctionNamed("cmdCopyQueryPoolResultsToBuffer"));
+id<MTLComputePipelineState> MVKCommandResourceFactory::newCmdCopyQueryPoolResultsMTLComputePipelineState(MVKVulkanAPIDeviceObject* owner) {
+	return newMTLComputePipelineState(getFunctionNamed("cmdCopyQueryPoolResultsToBuffer"), owner);
 }
 
 
@@ -418,9 +426,9 @@
 																error: &err] autorelease];
 		_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.mslCompile, startTime);
 		if (err) {
-			mvkNotifyErrorWithText(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);
+			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;
 		}
 
@@ -431,15 +439,17 @@
 	}
 }
 
-id<MTLRenderPipelineState> MVKCommandResourceFactory::newMTLRenderPipelineState(MTLRenderPipelineDescriptor* plDesc) {
-	MVKRenderPipelineCompiler* plc = new MVKRenderPipelineCompiler(_device);
+id<MTLRenderPipelineState> MVKCommandResourceFactory::newMTLRenderPipelineState(MTLRenderPipelineDescriptor* plDesc,
+																				MVKVulkanAPIDeviceObject* owner) {
+	MVKRenderPipelineCompiler* plc = new MVKRenderPipelineCompiler(owner);
 	id<MTLRenderPipelineState> rps = plc->newMTLRenderPipelineState(plDesc);	// retained
 	plc->destroy();
     return rps;
 }
 
-id<MTLComputePipelineState> MVKCommandResourceFactory::newMTLComputePipelineState(id<MTLFunction> mtlFunction) {
-	MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(_device);
+id<MTLComputePipelineState> MVKCommandResourceFactory::newMTLComputePipelineState(id<MTLFunction> mtlFunction,
+																				  MVKVulkanAPIDeviceObject* owner) {
+	MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(owner);
 	id<MTLComputePipelineState> cps = plc->newMTLComputePipelineState(mtlFunction);		// retained
 	plc->destroy();
     return cps;
diff --git a/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.h b/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.h
index 9b52dc9..822dac8 100644
--- a/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.h
+++ b/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.h
@@ -38,6 +38,9 @@
     NSUInteger _offset;
     NSUInteger _length;
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
+
     /**
      * Returns a pointer to the begining of this allocation memory, taking into
      * consideration this allocation's offset into the underlying MTLBuffer.
@@ -83,6 +86,9 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _device->getVulkanAPIObject(); };
+
     /** Returns a new MVKMTLBufferAllocation instance. */
     MVKMTLBufferAllocation* newObject() override;
 
@@ -119,6 +125,9 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _device->getVulkanAPIObject(); };
+
     /** 
      * Returns a MVKMTLBufferAllocation instance with a size that is the next 
      * power-of-two value that is at least as big as the requested size.
diff --git a/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.mm b/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.mm
index 3f73a54..4b708e1 100644
--- a/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.mm
+++ b/MoltenVK/MoltenVK/Commands/MVKMTLBufferAllocation.mm
@@ -23,6 +23,8 @@
 #pragma mark -
 #pragma mark MVKMTLBufferAllocation
 
+MVKVulkanAPIObject* MVKMTLBufferAllocation::getVulkanAPIObject() { return _pool->getVulkanAPIObject(); };
+
 void MVKMTLBufferAllocation::returnToPool() { _pool->returnObjectSafely(this); }
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h
index 6839df1..f88a504 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.h
@@ -31,6 +31,9 @@
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_EXT; }
+
 #pragma mark Resource memory
 
 	/** Returns the memory requirements of this resource by populating the specified structure. */
@@ -89,10 +92,13 @@
 #pragma mark MVKBufferView
 
 /** Represents a Vulkan buffer view. */
-class MVKBufferView : public MVKRefCountedDeviceObject {
+class MVKBufferView : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_BUFFER_VIEW_EXT; }
+
 #pragma mark Metal
 
     /** Returns a Metal texture that overlays this buffer view. */
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
index c022e2a..858d30f 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKBuffer.mm
@@ -19,7 +19,8 @@
 #include "MVKBuffer.h"
 #include "MVKCommandBuffer.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "MVKEnvironment.h"
+#include "mvk_datatypes.hpp"
 
 using namespace std;
 
@@ -167,7 +168,7 @@
 
 #pragma mark Construction
 
-MVKBufferView::MVKBufferView(MVKDevice* device, const VkBufferViewCreateInfo* pCreateInfo) : MVKRefCountedDeviceObject(device) {
+MVKBufferView::MVKBufferView(MVKDevice* device, const VkBufferViewCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
     _buffer = (MVKBuffer*)pCreateInfo->buffer;
     _mtlBufferOffset = _buffer->getMTLBufferOffset() + pCreateInfo->offset;
     _mtlPixelFormat = getMTLPixelFormatFromVkFormat(pCreateInfo->format);
@@ -193,7 +194,7 @@
 	_textureSize.height = uint32_t(rowCount * fmtBlockSize.height);
 
     if ( !_device->_pMetalFeatures->texelBuffers ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "Texel buffers are not supported on this device."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Texel buffers are not supported on this device."));
     }
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
index 7499208..3570f52 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.h
@@ -21,7 +21,6 @@
 #include "MVKDevice.h"
 #include "MVKImage.h"
 #include "MVKVector.h"
-#include "mvk_datatypes.h"
 #include <MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h>
 #include <unordered_set>
 #include <unordered_map>
@@ -76,6 +75,9 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
+
 	/** Encodes this binding layout and the specified descriptor set binding on the specified command encoder. */
     void bind(MVKCommandEncoder* cmdEncoder,
               MVKDescriptorBinding& descBinding,
@@ -116,6 +118,7 @@
 									   MVKShaderStageResourceBinding* pDescSetCounts,
 									   const VkDescriptorSetLayoutBinding* pBinding);
 
+	MVKDescriptorSetLayout* _layout;
 	VkDescriptorSetLayoutBinding _info;
 	std::vector<MVKSampler*> _immutableSamplers;
 	MVKShaderResourceBinding _mtlResourceIndexOffsets;
@@ -127,10 +130,13 @@
 #pragma mark MVKDescriptorSetLayout
 
 /** Represents a Vulkan descriptor set layout. */
-class MVKDescriptorSetLayout : public MVKBaseDeviceObject {
+class MVKDescriptorSetLayout : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT_EXT; }
+
 	/** Encodes this descriptor set layout and the specified descriptor set on the specified command encoder. */
     void bindDescriptorSet(MVKCommandEncoder* cmdEncoder,
                            MVKDescriptorSet* descSet,
@@ -184,6 +190,9 @@
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
+
 	/**
 	 * Updates the internal element bindings from the specified content.
 	 *
@@ -242,7 +251,7 @@
     bool hasBinding(uint32_t binding);
 
 	/** Constructs an instance. */
-	MVKDescriptorBinding(MVKDescriptorSetLayoutBinding* pBindingLayout);
+	MVKDescriptorBinding(MVKDescriptorSet* pDescSet, MVKDescriptorSetLayoutBinding* pBindingLayout);
 
 	/** Destructor. */
 	~MVKDescriptorBinding();
@@ -252,6 +261,7 @@
 
 	void initMTLSamplers(MVKDescriptorSetLayoutBinding* pBindingLayout);
 
+	MVKDescriptorSet* _pDescSet;
 	MVKDescriptorSetLayoutBinding* _pBindingLayout;
 	std::vector<VkDescriptorImageInfo> _imageBindings;
 	std::vector<VkDescriptorBufferInfo> _bufferBindings;
@@ -268,10 +278,13 @@
 #pragma mark MVKDescriptorSet
 
 /** Represents a Vulkan descriptor set. */
-class MVKDescriptorSet : public MVKBaseDeviceObject {
+class MVKDescriptorSet : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_SET_EXT; }
+
 	/** Updates the resource bindings in this instance from the specified content. */
 	template<typename DescriptorAction>
 	void writeDescriptorSets(const DescriptorAction* pDescriptorAction,
@@ -295,7 +308,7 @@
 	 */
 	MVKDescriptorSet* _next;
 
-	MVKDescriptorSet(MVKDevice* device) : MVKBaseDeviceObject(device) {}
+	MVKDescriptorSet(MVKDevice* device) : MVKVulkanAPIDeviceObject(device) {}
 
 protected:
 	friend class MVKDescriptorSetLayout;
@@ -315,10 +328,13 @@
 typedef MVKDeviceObjectPool<MVKDescriptorSet> MVKDescriptorSetPool;
 
 /** Represents a Vulkan descriptor pool. */
-class MVKDescriptorPool : public MVKBaseDeviceObject {
+class MVKDescriptorPool : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_POOL_EXT; }
+
 	/** Allocates the specified number of descriptor sets. */
 	VkResult allocateDescriptorSets(uint32_t count,
 									const VkDescriptorSetLayout* pSetLayouts,
@@ -349,10 +365,13 @@
 #pragma mark MVKDescriptorUpdateTemplate
 
 /** Represents a Vulkan descriptor update template. */
-class MVKDescriptorUpdateTemplate : public MVKConfigurableObject {
+class MVKDescriptorUpdateTemplate : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE_EXT; }
+
 	/** Get the nth update template entry. */
 	const VkDescriptorUpdateTemplateEntryKHR* getEntry(uint32_t n) const;
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index 12402a8..3a62682 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -21,6 +21,7 @@
 #include "MVKBuffer.h"
 #include "MVKFoundation.h"
 #include "MVKLogging.h"
+#include "mvk_datatypes.hpp"
 #include <stdlib.h>
 
 using namespace std;
@@ -28,7 +29,7 @@
 
 #pragma mark MVKShaderStageResourceBinding
 
-MVK_PUBLIC_SYMBOL MVKShaderStageResourceBinding MVKShaderStageResourceBinding::operator+ (const MVKShaderStageResourceBinding& rhs) {
+MVKShaderStageResourceBinding MVKShaderStageResourceBinding::operator+ (const MVKShaderStageResourceBinding& rhs) {
 	MVKShaderStageResourceBinding rslt;
 	rslt.bufferIndex = this->bufferIndex + rhs.bufferIndex;
 	rslt.textureIndex = this->textureIndex + rhs.textureIndex;
@@ -36,7 +37,7 @@
 	return rslt;
 }
 
-MVK_PUBLIC_SYMBOL MVKShaderStageResourceBinding& MVKShaderStageResourceBinding::operator+= (const MVKShaderStageResourceBinding& rhs) {
+MVKShaderStageResourceBinding& MVKShaderStageResourceBinding::operator+= (const MVKShaderStageResourceBinding& rhs) {
 	this->bufferIndex += rhs.bufferIndex;
 	this->textureIndex += rhs.textureIndex;
 	this->samplerIndex += rhs.samplerIndex;
@@ -46,19 +47,19 @@
 
 #pragma mark MVKShaderResourceBinding
 
-MVK_PUBLIC_SYMBOL uint32_t MVKShaderResourceBinding::getMaxBufferIndex() {
+uint32_t MVKShaderResourceBinding::getMaxBufferIndex() {
 	return max({stages[kMVKShaderStageVertex].bufferIndex, stages[kMVKShaderStageTessCtl].bufferIndex, stages[kMVKShaderStageTessEval].bufferIndex, stages[kMVKShaderStageFragment].bufferIndex, stages[kMVKShaderStageCompute].bufferIndex});
 }
 
-MVK_PUBLIC_SYMBOL uint32_t MVKShaderResourceBinding::getMaxTextureIndex() {
+uint32_t MVKShaderResourceBinding::getMaxTextureIndex() {
 	return max({stages[kMVKShaderStageVertex].textureIndex, stages[kMVKShaderStageTessCtl].textureIndex, stages[kMVKShaderStageTessEval].textureIndex, stages[kMVKShaderStageFragment].textureIndex, stages[kMVKShaderStageCompute].textureIndex});
 }
 
-MVK_PUBLIC_SYMBOL uint32_t MVKShaderResourceBinding::getMaxSamplerIndex() {
+uint32_t MVKShaderResourceBinding::getMaxSamplerIndex() {
 	return max({stages[kMVKShaderStageVertex].samplerIndex, stages[kMVKShaderStageTessCtl].samplerIndex, stages[kMVKShaderStageTessEval].samplerIndex, stages[kMVKShaderStageFragment].samplerIndex, stages[kMVKShaderStageCompute].samplerIndex});
 }
 
-MVK_PUBLIC_SYMBOL MVKShaderResourceBinding MVKShaderResourceBinding::operator+ (const MVKShaderResourceBinding& rhs) {
+MVKShaderResourceBinding MVKShaderResourceBinding::operator+ (const MVKShaderResourceBinding& rhs) {
 	MVKShaderResourceBinding rslt;
 	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
 		rslt.stages[i] = this->stages[i] + rhs.stages[i];
@@ -66,7 +67,7 @@
 	return rslt;
 }
 
-MVK_PUBLIC_SYMBOL MVKShaderResourceBinding& MVKShaderResourceBinding::operator+= (const MVKShaderResourceBinding& rhs) {
+MVKShaderResourceBinding& MVKShaderResourceBinding::operator+= (const MVKShaderResourceBinding& rhs) {
 	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
 		this->stages[i] += rhs.stages[i];
 	}
@@ -77,6 +78,8 @@
 #pragma mark -
 #pragma mark MVKDescriptorSetLayoutBinding
 
+MVKVulkanAPIObject* MVKDescriptorSetLayoutBinding::getVulkanAPIObject() { return _layout; };
+
 void MVKDescriptorSetLayoutBinding::bind(MVKCommandEncoder* cmdEncoder,
                                          MVKDescriptorBinding& descBinding,
                                          MVKShaderResourceBinding& dslMTLRezIdxOffsets,
@@ -376,8 +379,9 @@
 
 MVKDescriptorSetLayoutBinding::MVKDescriptorSetLayoutBinding(MVKDevice* device,
 															 MVKDescriptorSetLayout* layout,
-                                                             const VkDescriptorSetLayoutBinding* pBinding) : MVKBaseDeviceObject(device) {
-    for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
+                                                             const VkDescriptorSetLayoutBinding* pBinding) : MVKBaseDeviceObject(device), _layout(layout) {
+
+	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
         // Determine if this binding is used by this shader stage
         _applyToStage[i] = mvkAreFlagsEnabled(pBinding->stageFlags, mvkVkShaderStageFlagBitsFromMVKShaderStage(MVKShaderStage(i)));
 	    // If this binding is used by the shader, set the Metal resource index
@@ -403,9 +407,11 @@
 }
 
 MVKDescriptorSetLayoutBinding::MVKDescriptorSetLayoutBinding(const MVKDescriptorSetLayoutBinding& binding) :
-	MVKBaseDeviceObject(binding._device), _info(binding._info), _immutableSamplers(binding._immutableSamplers),
+	MVKBaseDeviceObject(binding._device), _layout(binding._layout),
+	_info(binding._info), _immutableSamplers(binding._immutableSamplers),
 	_mtlResourceIndexOffsets(binding._mtlResourceIndexOffsets) {
-    for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
+
+	for (uint32_t i = kMVKShaderStageVertex; i < kMVKShaderStageMax; i++) {
         _applyToStage[i] = binding._applyToStage[i];
     }
 	for (MVKSampler* sampler : _immutableSamplers) {
@@ -430,7 +436,7 @@
             pDescSetCounts->samplerIndex += pBinding->descriptorCount;
 
 			if (pBinding->descriptorCount > 1 && !_device->_pMetalFeatures->arrayOfSamplers) {
-				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of samplers.", _device->getName()));
+				_layout->setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of samplers.", _device->getName()));
 			}
             break;
 
@@ -442,10 +448,10 @@
 
 			if (pBinding->descriptorCount > 1) {
 				if ( !_device->_pMetalFeatures->arrayOfTextures ) {
-					setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of textures.", _device->getName()));
+					_layout->setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of textures.", _device->getName()));
 				}
 				if ( !_device->_pMetalFeatures->arrayOfSamplers ) {
-					setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of samplers.", _device->getName()));
+					_layout->setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of samplers.", _device->getName()));
 				}
 			}
             break;
@@ -459,7 +465,7 @@
             pDescSetCounts->textureIndex += pBinding->descriptorCount;
 
 			if (pBinding->descriptorCount > 1 && !_device->_pMetalFeatures->arrayOfTextures) {
-				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of textures.", _device->getName()));
+				_layout->setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Device %s does not support arrays of textures.", _device->getName()));
 			}
             break;
 
@@ -601,14 +607,13 @@
 }
 
 MVKDescriptorSetLayout::MVKDescriptorSetLayout(MVKDevice* device,
-                                               const VkDescriptorSetLayoutCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+                                               const VkDescriptorSetLayoutCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
     _isPushDescriptorLayout = (pCreateInfo->flags & VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR) != 0;
     // Create the descriptor bindings
     _bindings.reserve(pCreateInfo->bindingCount);
     for (uint32_t i = 0; i < pCreateInfo->bindingCount; i++) {
         _bindings.emplace_back(_device, this, &pCreateInfo->pBindings[i]);
         _bindingToIndex[pCreateInfo->pBindings[i].binding] = i;
-        setConfigurationResult(_bindings.back().getConfigurationResult());
     }
 }
 
@@ -616,6 +621,8 @@
 #pragma mark -
 #pragma mark MVKDescriptorBinding
 
+MVKVulkanAPIObject* MVKDescriptorBinding::getVulkanAPIObject() { return _pDescSet->getVulkanAPIObject(); };
+
 uint32_t MVKDescriptorBinding::writeBindings(uint32_t srcStartIndex,
 											 uint32_t dstStartIndex,
 											 uint32_t count,
@@ -785,7 +792,7 @@
     return _pBindingLayout->_info.binding == binding;
 }
 
-MVKDescriptorBinding::MVKDescriptorBinding(MVKDescriptorSetLayoutBinding* pBindingLayout) : MVKBaseObject() {
+MVKDescriptorBinding::MVKDescriptorBinding(MVKDescriptorSet* pDescSet, MVKDescriptorSetLayoutBinding* pBindingLayout) : _pDescSet(pDescSet) {
 
 	uint32_t descCnt = pBindingLayout->_info.descriptorCount;
 
@@ -941,7 +948,7 @@
 		_bindings.clear();
 		_bindings.reserve(bindCnt);
 		for (uint32_t i = 0; i < bindCnt; i++) {
-			_bindings.emplace_back(&layout->_bindings[i]);
+			_bindings.emplace_back(this, &layout->_bindings[i]);
 		}
 	}
 }
@@ -957,7 +964,7 @@
 		if (_device->_enabledExtensions.vk_KHR_maintenance1.enabled) {
 			return VK_ERROR_OUT_OF_POOL_MEMORY;		// Failure is an acceptable test...don't log as error.
 		} else {
-			return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "The maximum number of descriptor sets that can be allocated by this descriptor pool is %d.", _maxSets);
+			return reportError(VK_ERROR_INITIALIZATION_FAILED, "The maximum number of descriptor sets that can be allocated by this descriptor pool is %d.", _maxSets);
 		}
 	}
 
@@ -1004,7 +1011,7 @@
 }
 
 MVKDescriptorPool::MVKDescriptorPool(MVKDevice* device,
-									 const VkDescriptorPoolCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+									 const VkDescriptorPoolCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
 	_maxSets = pCreateInfo->maxSets;
 }
 
@@ -1030,8 +1037,9 @@
 	return _type;
 }
 
-MVKDescriptorUpdateTemplate::MVKDescriptorUpdateTemplate(MVKDevice* device, const VkDescriptorUpdateTemplateCreateInfoKHR* pCreateInfo) :
-	MVKConfigurableObject(), _type(pCreateInfo->templateType) {
+MVKDescriptorUpdateTemplate::MVKDescriptorUpdateTemplate(MVKDevice* device,
+														 const VkDescriptorUpdateTemplateCreateInfoKHR* pCreateInfo) :
+	MVKVulkanAPIDeviceObject(device), _type(pCreateInfo->templateType) {
 
 	for (uint32_t i = 0; i < pCreateInfo->descriptorUpdateEntryCount; i++)
 		_entries.push_back(pCreateInfo->pDescriptorUpdateEntries[i]);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index 45c438d..aef11ea 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -22,7 +22,7 @@
 #include "MVKBaseObject.h"
 #include "MVKLayers.h"
 #include "MVKObjectPool.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 #include "vk_mvk_moltenvk.h"
 #include <vector>
 #include <string>
@@ -75,10 +75,16 @@
 #pragma mark MVKPhysicalDevice
 
 /** Represents a Vulkan physical GPU device. */
-class MVKPhysicalDevice : public MVKDispatchableObject {
+class MVKPhysicalDevice : public MVKDispatchableVulkanAPIObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_PHYSICAL_DEVICE_EXT; }
+
+	/** Returns a pointer to the Vulkan instance. */
+	MVKInstance* getInstance() override { return _mvkInstance; }
+
 	/** Populates the specified structure with the features of this device. */
 	void getFeatures(VkPhysicalDeviceFeatures* features);
 
@@ -224,12 +230,6 @@
 	 */
 	VkResult getQueueFamilyProperties(uint32_t* pCount, VkQueueFamilyProperties2KHR* pQueueFamilyProperties);
 
-	/** Returns a pointer to the Vulkan instance. */
-	inline MVKInstance* getInstance() { return _mvkInstance; }
-
-	/** Returns a pointer to the layer manager. */
-	inline MVKLayerManager* getLayerManager() { return MVKLayerManager::globalManager(); }
-
 
 #pragma mark Memory models
 
@@ -339,12 +339,15 @@
 #pragma mark MVKDevice
 
 /** Represents a Vulkan logical GPU device, associated with a physical device. */
-class MVKDevice : public MVKDispatchableObject {
+class MVKDevice : public MVKDispatchableVulkanAPIObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_EXT; }
+
 	/** Returns a pointer to the Vulkan instance. */
-	inline MVKInstance* getInstance() { return _physicalDevice->_mvkInstance; }
+	MVKInstance* getInstance() override { return _physicalDevice->getInstance(); }
 
 	/** Returns the physical device underlying this logical device. */
 	inline MVKPhysicalDevice* getPhysicalDevice() { return _physicalDevice; }
@@ -658,13 +661,17 @@
 
 
 #pragma mark -
-#pragma mark MVKBaseDeviceObject
+#pragma mark MVKDeviceTrackingMixin
 
 /**
- * Represents an object that is spawned from a Vulkan device, and tracks that device.
+ * This mixin class adds the ability for an object to track the device that created it.
  * Implementation supports an instance where the device is null.
+ *
+ * As a mixin, this class should only be used as a component of multiple inheritance.
+ * Any class that inherits from this class should also inherit from MVKBaseObject.
+ * This requirement is to avoid the diamond problem of multiple inheritance.
  */
-class MVKBaseDeviceObject : public MVKConfigurableObject {
+class MVKDeviceTrackingMixin {
 
 public:
 
@@ -682,91 +689,54 @@
 	 * See the notes for that function for more information about how MTLPixelFormats
 	 * are managed for each platform device.
 	 */
-    inline MTLPixelFormat getMTLPixelFormatFromVkFormat(VkFormat vkFormat) {
-		return _device ? _device->getMTLPixelFormatFromVkFormat(vkFormat) : mvkMTLPixelFormatFromVkFormat(vkFormat);
-    }
+	inline MTLPixelFormat getMTLPixelFormatFromVkFormat(VkFormat vkFormat) {
+		return _device ? _device->getMTLPixelFormatFromVkFormat(vkFormat)
+					   : mvkMTLPixelFormatFromVkFormatInObj(vkFormat, getBaseObject());
+	}
 
 	/** Constructs an instance for the specified device. */
-    MVKBaseDeviceObject(MVKDevice* device) : _device(device) {}
+    MVKDeviceTrackingMixin(MVKDevice* device) : _device(device) {}
+
+	virtual ~MVKDeviceTrackingMixin() {}
 
 protected:
+	virtual MVKBaseObject* getBaseObject() = 0;
 	MVKDevice* _device;
 };
 
 
 #pragma mark -
-#pragma mark MVKDispatchableDeviceObject
+#pragma mark MVKBaseDeviceObject
 
-/**
- * Represents a dispatchable object that is spawned from a Vulkan device, and tracks that device.
- * Implementation supports an instance where the device is null.
- */
-class MVKDispatchableDeviceObject : public MVKDispatchableObject {
+/** Represents an object that is spawned from a Vulkan device, and tracks that device. */
+class MVKBaseDeviceObject : public MVKBaseObject, public MVKDeviceTrackingMixin {
 
 public:
 
-    /** Returns the device for which this object was created. */
-    inline MVKDevice* getDevice() { return _device; }
-
-    /** Returns the underlying Metal device. */
-	inline id<MTLDevice> getMTLDevice() { return _device ? _device->getMTLDevice() : nil; }
-
-    /** Constructs an instance for the specified device. */
-    MVKDispatchableDeviceObject(MVKDevice* device) : _device(device) {}
+	/** Constructs an instance for the specified device. */
+	MVKBaseDeviceObject(MVKDevice* device) : MVKDeviceTrackingMixin(device) {}
 
 protected:
-    MVKDevice* _device;
+	MVKBaseObject* getBaseObject() override { return this; };
 };
 
 
 #pragma mark -
-#pragma mark MVKRefCountedDeviceObject
+#pragma mark MVKVulkanAPIDeviceObject
 
-/**
- * Represents a device-spawned object that supports basic reference counting.
- *
- * An object of this type will automatically be deleted iff it has been destroyed
- * by the client, and all references have been released. An object of this type is
- * therefore allowed to live past its destruction by the client, until it is no
- * longer referenced by other objects.
- */
-class MVKRefCountedDeviceObject : public MVKBaseDeviceObject {
+/** Abstract class that represents an opaque Vulkan API handle object spawned from a Vulkan device. */
+class MVKVulkanAPIDeviceObject : public MVKVulkanAPIObject, public MVKDeviceTrackingMixin {
 
 public:
 
-    /**
-	 * Called when this instance has been retained as a reference by another object,
-	 * indicating that this instance will not be deleted until that reference is released.
-	 */
-    void retain();
+	/** Returns a pointer to the Vulkan instance. */
+	MVKInstance* getInstance() override { return _device ? _device->getInstance() : nullptr; }
 
-    /**
-	 * Called when this instance has been released as a reference from another object.
-	 * Once all references have been released, this object is free to be deleted.
-	 * If the destroy() function has already been called on this instance by the time
-	 * this function is called, this instance will be deleted.
-	 */
-    void release();
+	/** Constructs an instance for the specified device. */
+	MVKVulkanAPIDeviceObject(MVKDevice* device) : MVKDeviceTrackingMixin(device) {}
 
-	/**
-	 * Marks this instance as destroyed. If all previous references to this instance
-	 * have been released, this instance will be deleted, otherwise deletion of this
-	 * instance will automatically be deferred until all references have been released.
-	 */
-    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;
+protected:
+	MVKBaseObject* getBaseObject() override { return this; };
 };
 
 
@@ -779,6 +749,10 @@
 
 public:
 
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _device; };
+
 	/** Returns a new instance. */
 	T* newObject() override { return new T(_device); }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index be3fbba..58fd374 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -32,6 +32,7 @@
 #include "MVKFoundation.h"
 #include "MVKCodec.h"
 #include "MVKEnvironment.h"
+#include "MVKLogging.h"
 #include "MVKOSExtensions.h"
 #include <MoltenVKSPIRVToMSLConverter/SPIRVToMSLConverter.h>
 #include "vk_mvk_moltenvk.h"
@@ -2033,7 +2034,7 @@
     _globalVisibilityQueryCount = uint32_t(newBuffLen / kMVKQuerySlotSizeInBytes);
 
     if (reqBuffLen > maxBuffLen) {
-        mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "vkCreateQueryPool(): A maximum of %d total queries are available on this device in its current configuration. See the API notes for the MVKConfiguration.supportLargeQueryPools configuration parameter for more info.", _globalVisibilityQueryCount);
+        reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "vkCreateQueryPool(): A maximum of %d total queries are available on this device in its current configuration. See the API notes for the MVKConfiguration.supportLargeQueryPools configuration parameter for more info.", _globalVisibilityQueryCount);
     }
 
     NSUInteger mtlBuffLen = mvkAlignByteOffset(newBuffLen, _pMetalFeatures->mtlBufferAlignment);
@@ -2055,7 +2056,8 @@
 	_enabledVarPtrFeatures(),
 	_enabledHostQryResetFeatures(),
 	_enabledVtxAttrDivFeatures(),
-	_enabledPortabilityFeatures()
+	_enabledPortabilityFeatures(),
+	_enabledExtensions(this)
 {
 
 	initPerformanceTracking();
@@ -2070,9 +2072,10 @@
 
 	initQueues(pCreateInfo);
 
-	string logMsg = "Created VkDevice to run on GPU %s with the following Vulkan extensions enabled:";
-	logMsg += _enabledExtensions.enabledNamesString("\n\t\t", true);
-	MVKLogInfo(logMsg.c_str(), _pProperties->deviceName);
+	MVKLogInfo("Created VkDevice to run on GPU %s with the following %d Vulkan extensions enabled:%s",
+			   _pProperties->deviceName,
+			   _enabledExtensions.getEnabledCount(),
+			   _enabledExtensions.enabledNamesString("\n\t\t", true).c_str());
 }
 
 void MVKDevice::initPerformanceTracking() {
@@ -2243,7 +2246,7 @@
 	for (uint32_t i = 0; i < count; i++) {
 		((VkBool32*)pEnable)[i] = pRequested[i] && pAvailable[i];
 		if (pRequested[i] && !pAvailable[i]) {
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateDevice(): Requested feature is not available on this device."));
+			setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateDevice(): Requested feature is not available on this device."));
 		}
 	}
 }
@@ -2285,40 +2288,6 @@
 
 
 #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;
-}
-
-
-#pragma mark -
 #pragma mark Support functions
 
 uint64_t mvkRecommendedMaxWorkingSetSize(id<MTLDevice> mtlDevice) {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h
index ced14e9..da0a2dd 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.h
@@ -20,6 +20,7 @@
 
 #include "MVKDevice.h"
 #include <vector>
+#include <mutex>
 
 #import <Metal/Metal.h>
 
@@ -30,10 +31,13 @@
 #pragma mark MVKDeviceMemory
 
 /** Represents a Vulkan device-space memory allocation. */
-class MVKDeviceMemory : public MVKBaseDeviceObject {
+class MVKDeviceMemory : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT; }
+
 	/** Returns whether the memory is accessible from the host. */
     inline bool isMemoryHostAccessible() {
 #if MVK_IOS
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm
index 224f651..e3be6ba 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDeviceMemory.mm
@@ -19,8 +19,10 @@
 #include "MVKDeviceMemory.h"
 #include "MVKBuffer.h"
 #include "MVKImage.h"
-#include "mvk_datatypes.h"
+#include "MVKEnvironment.h"
+#include "mvk_datatypes.hpp"
 #include "MVKFoundation.h"
+#include "MVKLogging.h"
 #include <cstdlib>
 #include <stdlib.h>
 
@@ -31,15 +33,15 @@
 VkResult MVKDeviceMemory::map(VkDeviceSize offset, VkDeviceSize size, VkMemoryMapFlags flags, void** ppData) {
 
 	if ( !isMemoryHostAccessible() ) {
-		return mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Private GPU-only memory cannot be mapped to host memory.");
+		return reportError(VK_ERROR_MEMORY_MAP_FAILED, "Private GPU-only memory cannot be mapped to host memory.");
 	}
 
 	if (_isMapped) {
-		return mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Memory is already mapped. Call vkUnmapMemory() first.");
+		return reportError(VK_ERROR_MEMORY_MAP_FAILED, "Memory is already mapped. Call vkUnmapMemory() first.");
 	}
 
 	if ( !ensureMTLBuffer() && !ensureHostMemory() ) {
-		return mvkNotifyErrorWithText(VK_ERROR_OUT_OF_HOST_MEMORY, "Could not allocate %llu bytes of host-accessible device memory.", _allocationSize);
+		return reportError(VK_ERROR_OUT_OF_HOST_MEMORY, "Could not allocate %llu bytes of host-accessible device memory.", _allocationSize);
 	}
 
 	_mapOffset = offset;
@@ -57,7 +59,7 @@
 void MVKDeviceMemory::unmap() {
 
 	if ( !_isMapped ) {
-		mvkNotifyErrorWithText(VK_ERROR_MEMORY_MAP_FAILED, "Memory is not mapped. Call vkMapMemory() first.");
+		reportError(VK_ERROR_MEMORY_MAP_FAILED, "Memory is not mapped. Call vkMapMemory() first.");
 		return;
 	}
 
@@ -108,11 +110,11 @@
 	// If a dedicated alloc, ensure this buffer is the one and only buffer
 	// I am dedicated to.
 	if (_isDedicated && (_buffers.empty() || _buffers[0] != mvkBuff) ) {
-		return mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not bind VkBuffer %p to a VkDeviceMemory dedicated to resource %p. A dedicated allocation may only be used with the resource it was dedicated to.", mvkBuff, getDedicatedResource() );
+		return reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not bind VkBuffer %p to a VkDeviceMemory dedicated to resource %p. A dedicated allocation may only be used with the resource it was dedicated to.", mvkBuff, getDedicatedResource() );
 	}
 
 	if (!ensureMTLBuffer() ) {
-		return mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not bind a VkBuffer to a VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a VkDeviceMemory that supports a VkBuffer is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize);
+		return reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not bind a VkBuffer to a VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a VkDeviceMemory that supports a VkBuffer is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize);
 	}
 
 	// In the dedicated case, we already saved the buffer we're going to use.
@@ -133,7 +135,7 @@
 	// If a dedicated alloc, ensure this image is the one and only image
 	// I am dedicated to.
 	if (_isDedicated && (_images.empty() || _images[0] != mvkImg) ) {
-		return mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not bind VkImage %p to a VkDeviceMemory dedicated to resource %p. A dedicated allocation may only be used with the resource it was dedicated to.", mvkImg, getDedicatedResource() );
+		return reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not bind VkImage %p to a VkDeviceMemory dedicated to resource %p. A dedicated allocation may only be used with the resource it was dedicated to.", mvkImg, getDedicatedResource() );
 	}
 
 	if (!_isDedicated)
@@ -201,7 +203,7 @@
 
 MVKDeviceMemory::MVKDeviceMemory(MVKDevice* device,
 								 const VkMemoryAllocateInfo* pAllocateInfo,
-								 const VkAllocationCallbacks* pAllocator) : MVKBaseDeviceObject(device) {
+								 const VkAllocationCallbacks* pAllocator) : MVKVulkanAPIDeviceObject(device) {
 	// Set Metal memory parameters
 	VkMemoryPropertyFlags vkMemProps = _device->_pMemoryProperties->memoryTypes[pAllocateInfo->memoryTypeIndex].propertyFlags;
 	_mtlResourceOptions = mvkMTLResourceOptionsFromVkMemoryPropertyFlags(vkMemProps);
@@ -233,13 +235,13 @@
 #if MVK_MACOS
 		if (isMemoryHostCoherent() ) {
 			if (!((MVKImage*)dedicatedImage)->_isLinear) {
-				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Host-coherent VkDeviceMemory objects cannot be associated with optimal-tiling images."));
+				setConfigurationResult(reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Host-coherent VkDeviceMemory objects cannot be associated with optimal-tiling images."));
 			} else {
 				// Need to use the managed mode for images.
 				_mtlStorageMode = MTLStorageModeManaged;
 				// Nonetheless, we need a buffer to be able to map the memory at will.
 				if (!ensureMTLBuffer() ) {
-					setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not allocate a host-coherent VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a host-coherent VkDeviceMemory is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize));
+					setConfigurationResult(reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not allocate a host-coherent VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a host-coherent VkDeviceMemory is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize));
 				}
 			}
 		}
@@ -251,7 +253,7 @@
 
 	// If memory needs to be coherent it must reside in an MTLBuffer, since an open-ended map() must work.
 	if (isMemoryHostCoherent() && !ensureMTLBuffer() ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not allocate a host-coherent VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a host-coherent VkDeviceMemory is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize));
+		setConfigurationResult(reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "Could not allocate a host-coherent VkDeviceMemory of size %llu bytes. The maximum memory-aligned size of a host-coherent VkDeviceMemory is %llu bytes.", _allocationSize, _device->_pMetalFeatures->maxMTLBufferSize));
 	}
 
 	if (dedicatedBuffer) {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h
index b109348..1f91afc 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.h
@@ -26,10 +26,13 @@
 #pragma mark MVKFramebuffer
 
 /** Represents a Vulkan framebuffer. */
-class MVKFramebuffer : public MVKBaseDeviceObject {
+class MVKFramebuffer : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_FRAMEBUFFER_EXT; }
+
 	/** Returns the dimensions of this framebuffer. */
 	inline VkExtent2D getExtent2D() { return _extent; }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm
index 57dd79d..c37c977 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKFramebuffer.mm
@@ -24,7 +24,7 @@
 #pragma mark Construction
 
 MVKFramebuffer::MVKFramebuffer(MVKDevice* device,
-							   const VkFramebufferCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+							   const VkFramebufferCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
     _extent = { .width = pCreateInfo->width, .height = pCreateInfo->height };
 	_layerCount = pCreateInfo->layers;
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
index ab68ab4..c3735ad 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.h
@@ -48,6 +48,9 @@
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_EXT; }
+
 	/** Returns the Vulkan image type of this image. */
     VkImageType getImageType();
 
@@ -246,10 +249,12 @@
 #pragma mark MVKImageView
 
 /** Represents a Vulkan image view. */
-class MVKImageView : public MVKRefCountedDeviceObject {
+class MVKImageView : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_IMAGE_VIEW_EXT; }
 
 #pragma mark Metal
 
@@ -311,10 +316,13 @@
 #pragma mark MVKSampler
 
 /** Represents a Vulkan sampler. */
-class MVKSampler : public MVKRefCountedDeviceObject {
+class MVKSampler : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SAMPLER_EXT; }
+
 	/** Returns the Metal sampler state. */
 	inline id<MTLSamplerState> getMTLSamplerState() { return _mtlSamplerState; }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index d7b6891..2092f45 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -20,8 +20,9 @@
 #include "MVKQueue.h"
 #include "MVKSwapchain.h"
 #include "MVKCommandBuffer.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 #include "MVKFoundation.h"
+#include "MVKLogging.h"
 #include "MVKEnvironment.h"
 #include "MVKLogging.h"
 #include "MVKCodec.h"
@@ -330,7 +331,7 @@
 
 VkResult MVKImage::useIOSurface(IOSurfaceRef ioSurface) {
 
-    if (!_device->_pMetalFeatures->ioSurfaces) { return mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkUseIOSurfaceMVK() : IOSurfaces are not supported on this platform."); }
+    if (!_device->_pMetalFeatures->ioSurfaces) { return reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkUseIOSurfaceMVK() : IOSurfaces are not supported on this platform."); }
 
 #if MVK_SUPPORT_IOSURFACE_BOOL
 
@@ -338,11 +339,11 @@
     resetIOSurface();
 
     if (ioSurface) {
-		if (IOSurfaceGetWidth(ioSurface) != _extent.width) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface width %zu does not match VkImage width %d.", IOSurfaceGetWidth(ioSurface), _extent.width); }
-		if (IOSurfaceGetHeight(ioSurface) != _extent.height) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface height %zu does not match VkImage height %d.", IOSurfaceGetHeight(ioSurface), _extent.height); }
-		if (IOSurfaceGetBytesPerElement(ioSurface) != mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface bytes per element %zu does not match VkImage bytes per element %d.", IOSurfaceGetBytesPerElement(ioSurface), mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)); }
-		if (IOSurfaceGetElementWidth(ioSurface) != mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element width %zu does not match VkImage element width %d.", IOSurfaceGetElementWidth(ioSurface), mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width); }
-		if (IOSurfaceGetElementHeight(ioSurface) != mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height) { return mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element height %zu does not match VkImage element height %d.", IOSurfaceGetElementHeight(ioSurface), mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height); }
+		if (IOSurfaceGetWidth(ioSurface) != _extent.width) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface width %zu does not match VkImage width %d.", IOSurfaceGetWidth(ioSurface), _extent.width); }
+		if (IOSurfaceGetHeight(ioSurface) != _extent.height) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface height %zu does not match VkImage height %d.", IOSurfaceGetHeight(ioSurface), _extent.height); }
+		if (IOSurfaceGetBytesPerElement(ioSurface) != mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface bytes per element %zu does not match VkImage bytes per element %d.", IOSurfaceGetBytesPerElement(ioSurface), mvkMTLPixelFormatBytesPerBlock(_mtlPixelFormat)); }
+		if (IOSurfaceGetElementWidth(ioSurface) != mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element width %zu does not match VkImage element width %d.", IOSurfaceGetElementWidth(ioSurface), mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).width); }
+		if (IOSurfaceGetElementHeight(ioSurface) != mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height) { return reportError(VK_ERROR_INITIALIZATION_FAILED, "vkUseIOSurfaceMVK() : IOSurface element height %zu does not match VkImage element height %d.", IOSurfaceGetElementHeight(ioSurface), mvkMTLPixelFormatBlockTexelSize(_mtlPixelFormat).height); }
 
         _ioSurface = ioSurface;
         CFRetain(_ioSurface);
@@ -473,7 +474,7 @@
         // can upload the decompressed image data.
         std::unique_ptr<MVKCodec> codec = mvkCreateCodec(getVkFormat());
         if (!codec) {
-            mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "A 3D texture used a compressed format that MoltenVK does not yet support.");
+            reportError(VK_ERROR_FORMAT_NOT_SUPPORTED, "A 3D texture used a compressed format that MoltenVK does not yet support.");
             return;
         }
         VkSubresourceLayout destLayout;
@@ -545,20 +546,20 @@
 MVKImage::MVKImage(MVKDevice* device, const VkImageCreateInfo* pCreateInfo) : MVKResource(device) {
 
     if (pCreateInfo->flags & VK_IMAGE_CREATE_BLOCK_TEXEL_VIEW_COMPATIBLE_BIT) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Metal does not allow uncompressed views of compressed images."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Metal does not allow uncompressed views of compressed images."));
     }
 
 #if MVK_IOS
     if ( (pCreateInfo->imageType != VK_IMAGE_TYPE_2D) && (mvkFormatTypeFromVkFormat(pCreateInfo->format) == kMVKFormatCompressed) ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, compressed formats may only be used with 2D images."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, compressed formats may only be used with 2D images."));
     }
 #else
     if ( (pCreateInfo->imageType != VK_IMAGE_TYPE_2D) && (mvkFormatTypeFromVkFormat(pCreateInfo->format) == kMVKFormatCompressed) && !mvkCanDecodeFormat(pCreateInfo->format) ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, compressed formats may only be used with 2D images."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, compressed formats may only be used with 2D images."));
     }
 #endif
     if ( (pCreateInfo->imageType != VK_IMAGE_TYPE_2D) && (mvkFormatTypeFromVkFormat(pCreateInfo->format) == kMVKFormatDepthStencil) ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, depth/stencil formats may only be used with 2D images."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, depth/stencil formats may only be used with 2D images."));
     }
 
     // Adjust the info components to be compatible with Metal, then use the modified versions
@@ -573,7 +574,7 @@
 
     _mipLevels = max(pCreateInfo->mipLevels, minDim);
     if ( (_mipLevels > 1) && (pCreateInfo->imageType == VK_IMAGE_TYPE_1D) ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, 1D images cannot use mipmaps. Setting mip levels to 1."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, 1D images cannot use mipmaps. Setting mip levels to 1."));
         _mipLevels = 1;
     }
 
@@ -587,9 +588,9 @@
     if ( (_samples > 1) && (_mtlTextureType != MTLTextureType2DMultisample) &&
          (pCreateInfo->imageType != VK_IMAGE_TYPE_2D || !_device->_pMetalFeatures->multisampleArrayTextures) ) {
         if (pCreateInfo->imageType != VK_IMAGE_TYPE_2D) {
-            setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling can only be used with a 2D image type. Setting sample count to 1."));
+            setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling can only be used with a 2D image type. Setting sample count to 1."));
         } else {
-            setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : This version of Metal does not support multisampled array textures. Setting sample count to 1."));
+            setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : This version of Metal does not support multisampled array textures. Setting sample count to 1."));
         }
         _samples = VK_SAMPLE_COUNT_1_BIT;
         if (pCreateInfo->imageType == VK_IMAGE_TYPE_2D) {
@@ -597,7 +598,7 @@
         }
     }
     if ( (_samples > 1) && (mvkFormatTypeFromVkFormat(pCreateInfo->format) == kMVKFormatCompressed) ) {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling cannot be used with compressed images. Setting sample count to 1."));
+        setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : Under Metal, multisampling cannot be used with compressed images. Setting sample count to 1."));
         _samples = VK_SAMPLE_COUNT_1_BIT;
     }
 
@@ -623,33 +624,33 @@
 	if (pCreateInfo->tiling != VK_IMAGE_TILING_LINEAR) { return false; }
 
 	if (pCreateInfo->imageType != VK_IMAGE_TYPE_2D) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, imageType must be VK_IMAGE_TYPE_2D."));
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, imageType must be VK_IMAGE_TYPE_2D."));
 		return false;
 	}
 
 	if (_isDepthStencilAttachment) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a depth/stencil format."));
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, format must not be a depth/stencil format."));
 		return false;
 	}
 
 	if (_mipLevels > 1) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, mipLevels must be 1."));
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, mipLevels must be 1."));
 		return false;
 	}
 
 	if (_arrayLayers > 1) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, arrayLayers must be 1."));
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, arrayLayers must be 1."));
 		return false;
 	}
 
 	if (_samples > 1) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, samples must be VK_SAMPLE_COUNT_1_BIT."));
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, samples must be VK_SAMPLE_COUNT_1_BIT."));
 		return false;
 	}
 
 #if MVK_MACOS
 	if ( mvkIsAnyFlagEnabled(_usage, (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT)) ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, usage must not include VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, and/or VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT."));
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImage() : If tiling is VK_IMAGE_TILING_LINEAR, usage must not include VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT, and/or VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT."));
 		return false;
 	}
 #endif
@@ -775,7 +776,7 @@
 // is constructed to validate image view capabilities
 MVKImageView::MVKImageView(MVKDevice* device,
 						   const VkImageViewCreateInfo* pCreateInfo,
-						   const MVKConfiguration* pAltMVKConfig) : MVKRefCountedDeviceObject(device) {
+						   const MVKConfiguration* pAltMVKConfig) : MVKVulkanAPIDeviceObject(device) {
 	_image = (MVKImage*)pCreateInfo->image;
 	_usage = _image ? _image->_usage : 0;
 
@@ -830,12 +831,12 @@
 	// VK_KHR_maintenance1 supports taking 2D image views of 3D slices. No dice in Metal.
 	if ((viewType == VK_IMAGE_VIEW_TYPE_2D || viewType == VK_IMAGE_VIEW_TYPE_2D_ARRAY) && (imgType == VK_IMAGE_TYPE_3D)) {
 		if (pCreateInfo->subresourceRange.layerCount != image->_extent.depth) {
-			mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): Metal does not fully support views on a subset of a 3D texture.");
+			reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): Metal does not fully support views on a subset of a 3D texture.");
 		}
 		if (!mvkAreFlagsEnabled(_usage, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) {
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): 2D views on 3D images are only supported for color attachments."));
+			setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): 2D views on 3D images are only supported for color attachments."));
 		} else if (mvkIsAnyFlagEnabled(_usage, ~VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) {
-			mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): 2D views on 3D images are only supported for color attachments.");
+			reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateImageView(): 2D views on 3D images are only supported for color attachments.");
 		}
 	}
 }
@@ -932,9 +933,9 @@
 			const char* errMsg = ("The value of %s::components) (%s, %s, %s, %s), when applied to a VkImageView, requires full component swizzling to be enabled both at the"
 								  " time when the VkImageView is created and at the time any pipeline that uses that VkImageView is compiled. Full component swizzling can"
 								  " be enabled via the MVKConfiguration::fullImageViewSwizzle config parameter or MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE environment variable.");
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, errMsg, vkCmd,
-														  mvkVkComponentSwizzleName(components.r), mvkVkComponentSwizzleName(components.g),
-														  mvkVkComponentSwizzleName(components.b), mvkVkComponentSwizzleName(components.a)));
+			setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, errMsg, vkCmd,
+											   mvkVkComponentSwizzleName(components.r), mvkVkComponentSwizzleName(components.g),
+											   mvkVkComponentSwizzleName(components.b), mvkVkComponentSwizzleName(components.a)));
 		}
 	}
 
@@ -999,7 +1000,7 @@
 		if (_device->_pMetalFeatures->depthSampleCompare) {
 			mtlSampDesc.compareFunctionMVK = mvkMTLCompareFunctionFromVkCompareOp(pCreateInfo->compareOp);
 		} else {
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateSampler(): Depth texture samplers do not support the comparison of the pixel value against a reference value."));
+			setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateSampler(): Depth texture samplers do not support the comparison of the pixel value against a reference value."));
 		}
 	}
 
@@ -1021,7 +1022,7 @@
 }
 
 // Constructs an instance on the specified image.
-MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKRefCountedDeviceObject(device) {
+MVKSampler::MVKSampler(MVKDevice* device, const VkSamplerCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
     _mtlSamplerState = [getMTLDevice() newSamplerStateWithDescriptor: getMTLSamplerDescriptor(pCreateInfo)];
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
index ebed273..22551cd 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
@@ -18,16 +18,19 @@
 
 #pragma once
 
+#include "MVKEnvironment.h"
 #include "MVKLayers.h"
-#include "MVKSurface.h"
 #include "MVKBaseObject.h"
 #include "vk_mvk_moltenvk.h"
 #include <vector>
 #include <unordered_map>
 #include <string>
+#include <mutex>
 
 class MVKPhysicalDevice;
 class MVKDevice;
+class MVKSurface;
+class MVKDebugReportCallback;
 
 
 /** Tracks info about entry point function pointer addresses. */
@@ -48,10 +51,19 @@
 #pragma mark MVKInstance
 
 /** Represents a Vulkan instance. */
-class MVKInstance : public MVKDispatchableObject {
+class MVKInstance : public MVKDispatchableVulkanAPIObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_INSTANCE_EXT; }
+
+	/** Returns a pointer to the Vulkan instance. */
+	MVKInstance* getInstance() override { return this; }
+
+	/** Returns a pointer to the layer manager. */
+	inline MVKLayerManager* getLayerManager() { return MVKLayerManager::globalManager(); }
+
 	/** Returns the function pointer corresponding to the named entry point, or NULL if it doesn't exist. */
 	PFN_vkVoidFunction getProcAddr(const char* pName);
 
@@ -72,14 +84,31 @@
 	/** Returns the driver layer. */
 	MVKLayer* getDriverLayer() { return MVKLayerManager::globalManager()->getDriverLayer(); }
 
-	/** Creates and returns a new object. */
 	MVKSurface* createSurface(const Vk_PLATFORM_SurfaceCreateInfoMVK* pCreateInfo,
 							  const VkAllocationCallbacks* pAllocator);
 
-	/** Destroys the specified object. */
 	void destroySurface(MVKSurface* mvkSrfc,
 						const VkAllocationCallbacks* pAllocator);
 
+	MVKDebugReportCallback* createDebugReportCallback(const VkDebugReportCallbackCreateInfoEXT* pCreateInfo,
+													  const VkAllocationCallbacks* pAllocator);
+
+	void destroyDebugReportCallback(MVKDebugReportCallback* mvkDRCB,
+									const VkAllocationCallbacks* pAllocator);
+
+	void debugReportMessage(VkDebugReportFlagsEXT flags,
+							VkDebugReportObjectTypeEXT objectType,
+							uint64_t object,
+							size_t location,
+							int32_t messageCode,
+							const char* pLayerPrefix,
+							const char* pMessage);
+
+	void debugReportMessage(MVKVulkanAPIObject* mvkAPIObj, int aslLvl, const char* pMessage);
+
+	/** Returns whether debug report callbacks are being used. */
+	bool hasDebugReportCallbacks() { return _hasDebugReportCallbacks; }
+
 	/** Returns the MoltenVK configuration settings. */
 	const MVKConfiguration* getMoltenVKConfiguration() { return &_mvkConfig; }
 
@@ -115,6 +144,8 @@
 	friend MVKDevice;
 
 	void initProcAddrs();
+	void initCreationDebugReportCallbacks(const VkInstanceCreateInfo* pCreateInfo);
+	VkDebugReportFlagsEXT getVkDebugReportFlagsFromASLLevel(int aslLvl);
 	MVKEntryPoint* getEntryPoint(const char* pName);
 	void initConfig();
     void logVersions();
@@ -124,5 +155,40 @@
 	VkApplicationInfo _appInfo;
 	std::vector<MVKPhysicalDevice*> _physicalDevices;
 	std::unordered_map<std::string, MVKEntryPoint> _entryPoints;
+	std::vector<MVKDebugReportCallback*> _debugReportCallbacks;
+	std::mutex _drcbLock;
+	bool _hasDebugReportCallbacks;
+	bool _useCreationCallbacks;
+	const char* _debugReportCallbackLayerPrefix;
+};
+
+
+#pragma mark -
+#pragma mark MVKDebugReportCallback
+
+/** Represents a Vulkan Debug Report callback. */
+class MVKDebugReportCallback : public MVKVulkanAPIObject {
+
+public:
+
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT_EXT; }
+
+	/** Returns a pointer to the Vulkan instance. */
+	MVKInstance* getInstance() override { return _mvkInstance; }
+
+	MVKDebugReportCallback(MVKInstance* mvkInstance,
+						   const VkDebugReportCallbackCreateInfoEXT* pCreateInfo,
+						   bool isCreationCallback) :
+	_mvkInstance(mvkInstance), _info(*pCreateInfo), _isCreationCallback(isCreationCallback) {
+		_info.pNext = nullptr;
+	}
+
+protected:
+	friend MVKInstance;
+	
+	MVKInstance* _mvkInstance;
+	VkDebugReportCallbackCreateInfoEXT _info;
+	bool _isCreationCallback;
 };
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
index e0a3d4b..a27d8eb 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
@@ -23,6 +23,7 @@
 #include "MVKEnvironment.h"
 #include "MVKSurface.h"
 #include "MVKOSExtensions.h"
+#include "MVKLogging.h"
 
 using namespace std;
 
@@ -79,6 +80,78 @@
 	mvkSrfc->destroy();
 }
 
+MVKDebugReportCallback* MVKInstance::createDebugReportCallback(const VkDebugReportCallbackCreateInfoEXT* pCreateInfo,
+															   const VkAllocationCallbacks* pAllocator) {
+	lock_guard<mutex> lock(_drcbLock);
+
+	MVKDebugReportCallback* mvkDRCB = new MVKDebugReportCallback(this, pCreateInfo, _useCreationCallbacks);
+	_debugReportCallbacks.push_back(mvkDRCB);
+	_hasDebugReportCallbacks = true;
+	return mvkDRCB;
+}
+
+void MVKInstance::destroyDebugReportCallback(MVKDebugReportCallback* mvkDRCB,
+								const VkAllocationCallbacks* pAllocator) {
+	lock_guard<mutex> lock(_drcbLock);
+
+	mvkRemoveAllOccurances(_debugReportCallbacks, mvkDRCB);
+	_hasDebugReportCallbacks = (_debugReportCallbacks.size() != 0);
+	mvkDRCB->destroy();
+}
+
+void MVKInstance::debugReportMessage(VkDebugReportFlagsEXT flags,
+									 VkDebugReportObjectTypeEXT objectType,
+									 uint64_t object,
+									 size_t location,
+									 int32_t messageCode,
+									 const char* pLayerPrefix,
+									 const char* pMessage) {
+
+	// Fail fast to avoid further unnecessary processing and locking.
+	if ( !(_hasDebugReportCallbacks) ) { return; }
+
+	lock_guard<mutex> lock(_drcbLock);
+
+	for (auto mvkDRCB : _debugReportCallbacks) {
+		auto& drbcInfo = mvkDRCB->_info;
+		if (drbcInfo.pfnCallback && mvkIsAnyFlagEnabled(drbcInfo.flags, flags) && (mvkDRCB->_isCreationCallback == _useCreationCallbacks)) {
+			drbcInfo.pfnCallback(flags, objectType, object, location, messageCode, pLayerPrefix, pMessage, drbcInfo.pUserData);
+		}
+	}
+}
+
+void MVKInstance::debugReportMessage(MVKVulkanAPIObject* mvkAPIObj, int aslLvl, const char* pMessage) {
+
+	// Fail fast to avoid further unnecessary processing and locking.
+	if ( !(_hasDebugReportCallbacks) ) { return; }
+
+	VkDebugReportFlagsEXT flags = getVkDebugReportFlagsFromASLLevel(aslLvl);
+	uint64_t object = (uint64_t)(mvkAPIObj ? mvkAPIObj->getVkHandle() : nullptr);
+	VkDebugReportObjectTypeEXT objectType = mvkAPIObj ? mvkAPIObj->getVkDebugReportObjectType() : VK_DEBUG_REPORT_OBJECT_TYPE_UNKNOWN_EXT;
+	debugReportMessage(flags, objectType, object, 0, 0, _debugReportCallbackLayerPrefix, pMessage);
+}
+
+VkDebugReportFlagsEXT MVKInstance::getVkDebugReportFlagsFromASLLevel(int aslLvl) {
+	switch (aslLvl) {
+		case ASL_LEVEL_DEBUG:
+			return VK_DEBUG_REPORT_DEBUG_BIT_EXT;
+
+		case ASL_LEVEL_INFO:
+		case ASL_LEVEL_NOTICE:
+			return VK_DEBUG_REPORT_INFORMATION_BIT_EXT;
+
+		case ASL_LEVEL_WARNING:
+			return VK_DEBUG_REPORT_WARNING_BIT_EXT;
+
+		case ASL_LEVEL_ERR:
+		case ASL_LEVEL_CRIT:
+		case ASL_LEVEL_ALERT:
+		case ASL_LEVEL_EMERG:
+		default:
+			return VK_DEBUG_REPORT_ERROR_BIT_EXT;
+	}
+}
+
 
 #pragma mark Object Creation
 
@@ -130,7 +203,9 @@
 #endif	// MVK_IOS
 }
 
-MVKInstance::MVKInstance(const VkInstanceCreateInfo* pCreateInfo) {
+MVKInstance::MVKInstance(const VkInstanceCreateInfo* pCreateInfo) : _enabledExtensions(this) {
+
+	initCreationDebugReportCallbacks(pCreateInfo);	// Do before any creation activities
 
 	_appInfo.apiVersion = MVK_VULKAN_API_VERSION;	// Default
 	mvkSetOrClear(&_appInfo, pCreateInfo->pApplicationInfo);
@@ -147,10 +222,10 @@
 
 	if (MVK_VULKAN_API_VERSION_CONFORM(MVK_VULKAN_API_VERSION) <
 		MVK_VULKAN_API_VERSION_CONFORM(_appInfo.apiVersion)) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INCOMPATIBLE_DRIVER,
-													  "Request for Vulkan version %s is not compatible with supported version %s.",
-													  mvkGetVulkanVersionString(_appInfo.apiVersion).c_str(),
-													  mvkGetVulkanVersionString(MVK_VULKAN_API_VERSION).c_str()));
+		setConfigurationResult(reportError(VK_ERROR_INCOMPATIBLE_DRIVER,
+										   "Request for Vulkan version %s is not compatible with supported version %s.",
+										   mvkGetVulkanVersionString(_appInfo.apiVersion).c_str(),
+										   mvkGetVulkanVersionString(MVK_VULKAN_API_VERSION).c_str()));
 	}
 
 	// Populate the array of physical GPU devices
@@ -160,18 +235,38 @@
 		_physicalDevices.push_back(new MVKPhysicalDevice(this, mtlDev));
 	}
 	if (_physicalDevices.empty()) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INCOMPATIBLE_DRIVER, "Vulkan is not supported on this device. MoltenVK requires Metal, which is not available on this device."));
+		setConfigurationResult(reportError(VK_ERROR_INCOMPATIBLE_DRIVER, "Vulkan is not supported on this device. MoltenVK requires Metal, which is not available on this device."));
 	}
 
-	string logMsg = "Created VkInstance with the following Vulkan extensions enabled:";
-	logMsg += _enabledExtensions.enabledNamesString("\n\t\t", true);
-	MVKLogInfo("%s", logMsg.c_str());
+	MVKLogInfo("Created VkInstance with the following %d Vulkan extensions enabled:%s",
+			   _enabledExtensions.getEnabledCount(),
+			   _enabledExtensions.enabledNamesString("\n\t\t", true).c_str());
+
+	_useCreationCallbacks = false;
+}
+
+void MVKInstance::initCreationDebugReportCallbacks(const VkInstanceCreateInfo* pCreateInfo) {
+	_useCreationCallbacks = true;
+	_hasDebugReportCallbacks = false;
+	_debugReportCallbackLayerPrefix = getDriverLayer()->getName();
+
+	MVKVkAPIStructHeader* next = (MVKVkAPIStructHeader*)pCreateInfo->pNext;
+	while (next) {
+		switch (next->sType) {
+			case VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT:
+				createDebugReportCallback((VkDebugReportCallbackCreateInfoEXT*)next, nullptr);
+				break;
+			default:
+				break;
+		}
+		next = (MVKVkAPIStructHeader*)next->pNext;
+	}
 }
 
 #define ADD_ENTRY_POINT(func, ext1, ext2, isDev)	_entryPoints[""#func] = { (PFN_vkVoidFunction)&func,  ext1,  ext2,  isDev }
 
 #define ADD_INST_ENTRY_POINT(func)					ADD_ENTRY_POINT(func, nullptr, nullptr, false)
-#define ADD_DVC_ENTRY_POINT(func)						ADD_ENTRY_POINT(func, nullptr, nullptr, true)
+#define ADD_DVC_ENTRY_POINT(func)					ADD_ENTRY_POINT(func, nullptr, nullptr, true)
 
 #define ADD_INST_EXT_ENTRY_POINT(func, EXT)			ADD_ENTRY_POINT(func, VK_ ##EXT ##_EXTENSION_NAME, nullptr, false)
 #define ADD_DVC_EXT_ENTRY_POINT(func, EXT)			ADD_ENTRY_POINT(func, VK_ ##EXT ##_EXTENSION_NAME, nullptr, true)
@@ -179,7 +274,7 @@
 #define ADD_INST_EXT2_ENTRY_POINT(func, EXT1, EXT2)	ADD_ENTRY_POINT(func, VK_ ##EXT1 ##_EXTENSION_NAME, VK_ ##EXT2 ##_EXTENSION_NAME, false)
 #define ADD_DVC_EXT2_ENTRY_POINT(func, EXT1, EXT2)	ADD_ENTRY_POINT(func, VK_ ##EXT1 ##_EXTENSION_NAME, VK_ ##EXT2 ##_EXTENSION_NAME, true)
 
-/** Initializes the function pointer map. */
+// Initializes the function pointer map.
 void MVKInstance::initProcAddrs() {
 
 	// Instance functions
@@ -335,6 +430,9 @@
 	ADD_INST_EXT_ENTRY_POINT(vkGetPhysicalDeviceSurfacePresentModesKHR, KHR_SURFACE);
 	ADD_INST_EXT_ENTRY_POINT(vkGetPhysicalDeviceSurfaceCapabilities2KHR, KHR_GET_SURFACE_CAPABILITIES_2);
 	ADD_INST_EXT_ENTRY_POINT(vkGetPhysicalDeviceSurfaceFormats2KHR, KHR_GET_SURFACE_CAPABILITIES_2);
+	ADD_INST_EXT_ENTRY_POINT(vkCreateDebugReportCallbackEXT, EXT_DEBUG_REPORT);
+	ADD_INST_EXT_ENTRY_POINT(vkDestroyDebugReportCallbackEXT, EXT_DEBUG_REPORT);
+	ADD_INST_EXT_ENTRY_POINT(vkDebugReportMessageEXT, EXT_DEBUG_REPORT);
 
 #ifdef VK_USE_PLATFORM_IOS_MVK
 	ADD_INST_EXT_ENTRY_POINT(vkCreateIOSSurfaceMVK, MVK_IOS_SURFACE);
@@ -382,16 +480,14 @@
 }
 
 void MVKInstance::logVersions() {
-	string logMsg = "MoltenVK version ";
-	logMsg += mvkGetMoltenVKVersionString(MVK_VERSION);
-	logMsg += ". Vulkan version ";
-	logMsg += mvkGetVulkanVersionString(MVK_VULKAN_API_VERSION);
-	logMsg += ".\n\tThe following Vulkan extensions are supported:";
-	logMsg += getDriverLayer()->getSupportedExtensions()->enabledNamesString("\n\t\t", true);
-	MVKLogInfo("%s", logMsg.c_str());
+	MVKExtensionList* pExtns  = getDriverLayer()->getSupportedExtensions();
+	MVKLogInfo("MoltenVK version %s. Vulkan version %s.\n\tThe following %d Vulkan extensions are supported:%s",
+			   mvkGetMoltenVKVersionString(MVK_VERSION).c_str(),
+			   mvkGetVulkanVersionString(MVK_VULKAN_API_VERSION).c_str(),
+			   pExtns->getEnabledCount(),
+			   pExtns->enabledNamesString("\n\t\t", true).c_str());
 }
 
-// Init config.
 void MVKInstance::initConfig() {
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.debugMode,                              MVK_DEBUG);
 	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.shaderConversionFlipVertexY,            MVK_CONFIG_SHADER_CONVERSION_FLIP_VERTEX_Y);
@@ -416,13 +512,17 @@
     VkResult result = VK_SUCCESS;
     for (uint32_t i = 0; i < count; i++) {
         if ( !MVKLayerManager::globalManager()->getLayerNamed(names[i]) ) {
-            result = mvkNotifyErrorWithText(VK_ERROR_LAYER_NOT_PRESENT, "Vulkan layer %s is not supported.", names[i]);
+            result = reportError(VK_ERROR_LAYER_NOT_PRESENT, "Vulkan layer %s is not supported.", names[i]);
         }
     }
     return result;
 }
 
 MVKInstance::~MVKInstance() {
+	_useCreationCallbacks = true;
 	mvkDestroyContainerContents(_physicalDevices);
+
+	lock_guard<mutex> lock(_drcbLock);
+	mvkDestroyContainerContents(_debugReportCallbacks);
 }
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
index 6f76d07..a685e4c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.h
@@ -42,10 +42,13 @@
 };
 
 /** Represents a Vulkan pipeline layout. */
-class MVKPipelineLayout : public MVKBaseDeviceObject {
+class MVKPipelineLayout : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_LAYOUT_EXT; }
+
 	/** Binds descriptor sets to a command encoder. */
     void bindDescriptorSets(MVKCommandEncoder* cmdEncoder,
                             MVKVector<MVKDescriptorSet*>& descriptorSets,
@@ -113,10 +116,13 @@
 static const uint32_t kMVKTessEvalNumReservedBuffers = 3;
 
 /** Represents an abstract Vulkan pipeline. */
-class MVKPipeline : public MVKBaseDeviceObject {
+class MVKPipeline : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_EXT; }
+
 	/** Returns the order of stages in this pipeline. Draws and dispatches must encode this pipeline once per stage. */
 	virtual void getStages(MVKVector<uint32_t>& stages) = 0;
 
@@ -130,7 +136,7 @@
 	bool fullImageViewSwizzle() const { return _fullImageViewSwizzle; }
 
 	/** Constructs an instance for the device. layout, and parent (which may be NULL). */
-	MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipeline* parent) : MVKBaseDeviceObject(device),
+	MVKPipeline(MVKDevice* device, MVKPipelineCache* pipelineCache, MVKPipeline* parent) : MVKVulkanAPIDeviceObject(device),
 																						   _pipelineCache(pipelineCache),
 	   																					   _fullImageViewSwizzle(device->_pMVKConfig->fullImageViewSwizzle)	{}
 
@@ -301,10 +307,13 @@
 #pragma mark MVKPipelineCache
 
 /** Represents a Vulkan pipeline cache. */
-class MVKPipelineCache : public MVKBaseDeviceObject {
+class MVKPipelineCache : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_PIPELINE_CACHE_EXT; }
+
 	/** 
 	 * If pData is not null, serializes at most pDataSize bytes of the contents of the cache into that
 	 * memory location, and returns the number of bytes serialized in pDataSize. If pData is null,
@@ -360,9 +369,9 @@
 
 #pragma mark Construction
 
-	MVKRenderPipelineCompiler(MVKDevice* device) : MVKMetalCompiler(device) {
+	MVKRenderPipelineCompiler(MVKVulkanAPIDeviceObject* owner) : MVKMetalCompiler(owner) {
 		_compilerType = "Render pipeline";
-		_pPerformanceTracker = &_device->_performanceStatistics.shaderCompilation.pipelineCompile;
+		_pPerformanceTracker = &_owner->getDevice()->_performanceStatistics.shaderCompilation.pipelineCompile;
 	}
 
 	~MVKRenderPipelineCompiler() override;
@@ -405,9 +414,9 @@
 
 #pragma mark Construction
 
-	MVKComputePipelineCompiler(MVKDevice* device) : MVKMetalCompiler(device) {
+	MVKComputePipelineCompiler(MVKVulkanAPIDeviceObject* owner) : MVKMetalCompiler(owner) {
 		_compilerType = "Compute pipeline";
-		_pPerformanceTracker = &_device->_performanceStatistics.shaderCompilation.pipelineCompile;
+		_pPerformanceTracker = &_owner->getDevice()->_performanceStatistics.shaderCompilation.pipelineCompile;
 	}
 
 	~MVKComputePipelineCompiler() override;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index 78ce185..6f307b1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -24,7 +24,7 @@
 #include "MVKOSExtensions.h"
 #include "MVKStrings.h"
 #include "MTLRenderPipelineDescriptor+MoltenVK.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 
 #include <cereal/archives/binary.hpp>
 #include <cereal/types/string.hpp>
@@ -107,7 +107,7 @@
 }
 
 MVKPipelineLayout::MVKPipelineLayout(MVKDevice* device,
-                                     const VkPipelineLayoutCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+                                     const VkPipelineLayoutCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
 
     // Add descriptor set layouts, accumulating the resource index offsets used by the
     // corresponding DSL, and associating the current accumulated resource index offsets
@@ -277,12 +277,12 @@
 	std::string reflectErrorLog;
 	if (_pTessCtlSS && _pTessEvalSS) {
 		if (!getTessReflectionData(((MVKShaderModule*)_pTessCtlSS->module)->getSPIRV(), _pTessCtlSS->pName, ((MVKShaderModule*)_pTessEvalSS->module)->getSPIRV(), _pTessEvalSS->pName, reflectData, reflectErrorLog) ) {
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Failed to reflect tessellation shaders: %s", reflectErrorLog.c_str()));
+			setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to reflect tessellation shaders: %s", reflectErrorLog.c_str()));
 			return;
 		}
 		// Unfortunately, we can't support line tessellation at this time.
 		if (reflectData.patchKind == spv::ExecutionModeIsolines) {
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "Metal does not support isoline tessellation."));
+			setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Metal does not support isoline tessellation."));
 			return;
 		}
 	}
@@ -326,7 +326,7 @@
 			if (_device->_enabledFeatures.depthClamp) {
 				_mtlDepthClipMode = MTLDepthClipModeClamp;
 			} else {
-				setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "This device does not support depth clamping."));
+				setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "This device does not support depth clamping."));
 			}
 		}
 	}
@@ -363,9 +363,8 @@
 // Either returns an existing pipeline state or compiles a new one.
 id<MTLRenderPipelineState> MVKGraphicsPipeline::getOrCompilePipeline(MTLRenderPipelineDescriptor* plDesc, id<MTLRenderPipelineState>& plState) {
 	if (!plState) {
-		MVKRenderPipelineCompiler* plc = new MVKRenderPipelineCompiler(_device);
+		MVKRenderPipelineCompiler* plc = new MVKRenderPipelineCompiler(this);
 		plState = plc->newMTLRenderPipelineState(plDesc);	// retained
-		setConfigurationResult(plc->getConfigurationResult());
 		plc->destroy();
 	}
 	return plState;
@@ -374,9 +373,8 @@
 // Either returns an existing pipeline state or compiles a new one.
 id<MTLComputePipelineState> MVKGraphicsPipeline::getOrCompilePipeline(MTLComputePipelineDescriptor* plDesc, id<MTLComputePipelineState>& plState) {
 	if (!plState) {
-		MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(_device);
+		MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(this);
 		plState = plc->newMTLComputePipelineState(plDesc);	// retained
-		setConfigurationResult(plc->getConfigurationResult());
 		plc->destroy();
 	}
 	return plState;
@@ -551,7 +549,7 @@
 	std::string errorLog;
 	// Unfortunately, MoltenVKShaderConverter doesn't know about MVKVector, so we can't use that here.
 	if (!getShaderOutputs(((MVKShaderModule*)_pVertexSS->module)->getSPIRV(), spv::ExecutionModelVertex, _pVertexSS->pName, vtxOutputs, errorLog) ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Failed to get vertex outputs: %s", errorLog.c_str()));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get vertex outputs: %s", errorLog.c_str()));
 		return nil;
 	}
 
@@ -590,7 +588,7 @@
 	std::vector<SPIRVShaderOutput> tcOutputs;
 	std::string errorLog;
 	if (!getShaderOutputs(((MVKShaderModule*)_pTessCtlSS->module)->getSPIRV(), spv::ExecutionModelTessellationControl, _pTessCtlSS->pName, tcOutputs, errorLog) ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation control outputs: %s", errorLog.c_str()));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to get tessellation control outputs: %s", errorLog.c_str()));
 		return nil;
 	}
 
@@ -705,7 +703,7 @@
     addVertexInputToShaderConverterContext(shaderContext, pCreateInfo);
 	id<MTLFunction> mtlFunction = ((MVKShaderModule*)_pVertexSS->module)->getMTLFunction(&shaderContext, _pVertexSS->pSpecializationInfo, _pipelineCache).mtlFunction;
 	if ( !mtlFunction ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader function could not be compiled into pipeline. See previous logged error."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader function could not be compiled into pipeline. See previous logged error."));
 		return false;
 	}
 	plDesc.vertexFunction = mtlFunction;
@@ -714,16 +712,16 @@
 	_needsVertexOutputBuffer = shaderContext.options.needsOutputBuffer;
 	// If we need the auxiliary buffer and there's no place to put it, we're in serious trouble.
 	if (_needsVertexAuxBuffer && _auxBufferIndex.stages[kMVKShaderStageVertex] >= _device->_pMetalFeatures->maxPerStageBufferCount - vbCnt) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader requires auxiliary buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader requires auxiliary buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	// Ditto captured output buffer.
 	if (_needsVertexOutputBuffer && _outputBufferIndex.stages[kMVKShaderStageVertex] >= _device->_pMetalFeatures->maxPerStageBufferCount - vbCnt) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader requires output buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader requires output buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	if (_needsVertexOutputBuffer && _indirectParamsIndex.stages[kMVKShaderStageVertex] >= _device->_pMetalFeatures->maxPerStageBufferCount - vbCnt) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader requires indirect parameters buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Vertex shader requires indirect parameters buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	return true;
@@ -741,7 +739,7 @@
 	addPrevStageOutputToShaderConverterContext(shaderContext, vtxOutputs);
 	id<MTLFunction> mtlFunction = ((MVKShaderModule*)_pTessCtlSS->module)->getMTLFunction(&shaderContext, _pTessCtlSS->pSpecializationInfo, _pipelineCache).mtlFunction;
 	if ( !mtlFunction ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader function could not be compiled into pipeline. See previous logged error."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader function could not be compiled into pipeline. See previous logged error."));
 		return false;
 	}
 	plDesc.computeFunction = mtlFunction;
@@ -750,23 +748,23 @@
 	_needsTessCtlPatchOutputBuffer = shaderContext.options.needsPatchOutputBuffer;
 	_needsTessCtlInput = shaderContext.options.needsInputThreadgroupMem;
 	if (_needsTessCtlAuxBuffer && _auxBufferIndex.stages[kMVKShaderStageTessCtl] >= _device->_pMetalFeatures->maxPerStageBufferCount - kMVKTessCtlNumReservedBuffers) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires auxiliary buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires auxiliary buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	if (_indirectParamsIndex.stages[kMVKShaderStageTessCtl] >= _device->_pMetalFeatures->maxPerStageBufferCount - kMVKTessCtlNumReservedBuffers) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires indirect parameters buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires indirect parameters buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	if (_needsTessCtlOutputBuffer && _outputBufferIndex.stages[kMVKShaderStageTessCtl] >= _device->_pMetalFeatures->maxPerStageBufferCount - kMVKTessCtlNumReservedBuffers) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires per-vertex output buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires per-vertex output buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	if (_needsTessCtlPatchOutputBuffer && _tessCtlPatchOutputBufferIndex >= _device->_pMetalFeatures->maxPerStageBufferCount - kMVKTessCtlNumReservedBuffers) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires per-patch output buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires per-patch output buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	if (_tessCtlLevelBufferIndex >= _device->_pMetalFeatures->maxPerStageBufferCount - kMVKTessCtlNumReservedBuffers) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires tessellation level output buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Tessellation control shader requires tessellation level output buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	return true;
@@ -781,7 +779,7 @@
 	addPrevStageOutputToShaderConverterContext(shaderContext, tcOutputs);
 	id<MTLFunction> mtlFunction = ((MVKShaderModule*)_pTessEvalSS->module)->getMTLFunction(&shaderContext, _pTessEvalSS->pSpecializationInfo, _pipelineCache).mtlFunction;
 	if ( !mtlFunction ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation evaluation shader function could not be compiled into pipeline. See previous logged error."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "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.
@@ -790,7 +788,7 @@
 	_needsTessEvalAuxBuffer = shaderContext.options.needsAuxBuffer;
 	// If we need the auxiliary buffer and there's no place to put it, we're in serious trouble.
 	if (_needsTessEvalAuxBuffer && _auxBufferIndex.stages[kMVKShaderStageTessEval] >= _device->_pMetalFeatures->maxPerStageBufferCount - kMVKTessEvalNumReservedBuffers) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Tessellation evaluation shader requires auxiliary buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Tessellation evaluation shader requires auxiliary buffer, but there is no free slot to pass it."));
 		return false;
 	}
 	return true;
@@ -804,13 +802,13 @@
 		shaderContext.options.shouldCaptureOutput = false;
 		id<MTLFunction> mtlFunction = ((MVKShaderModule*)_pFragmentSS->module)->getMTLFunction(&shaderContext, _pFragmentSS->pSpecializationInfo, _pipelineCache).mtlFunction;
 		if ( !mtlFunction ) {
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Fragment shader function could not be compiled into pipeline. See previous logged error."));
+			setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Fragment shader function could not be compiled into pipeline. See previous logged error."));
 			return false;
 		}
 		plDesc.fragmentFunction = mtlFunction;
 		_needsFragmentAuxBuffer = shaderContext.options.needsAuxBuffer;
 		if (_needsFragmentAuxBuffer && _auxBufferIndex.stages[kMVKShaderStageFragment] >= _device->_pMetalFeatures->maxPerStageBufferCount) {
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Fragment shader requires auxiliary buffer, but there is no free slot to pass it."));
+			setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Fragment shader requires auxiliary buffer, but there is no free slot to pass it."));
 			return false;
 		}
 	}
@@ -847,7 +845,7 @@
 				for (uint32_t j = 0; j < vbCnt; j++, pVKVB++) {
 					if (pVKVB->binding == pVKVA->binding) {
 						if (pVKVA->offset >= pVKVB->stride) {
-							setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Under Metal, vertex attribute offsets must not exceed the vertex buffer stride."));
+							setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Under Metal, vertex attribute offsets must not exceed the vertex buffer stride."));
 							return false;
 						}
 						break;
@@ -871,7 +869,7 @@
 			// Vulkan allows any stride, but Metal only allows multiples of 4.
             // TODO: We should try to expand the buffer to the required alignment in that case.
             if ((pVKVB->stride % 4) != 0) {
-                setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Under Metal, vertex buffer strides must be aligned to four bytes."));
+                setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Under Metal, vertex buffer strides must be aligned to four bytes."));
                 return false;
             }
 
@@ -1168,16 +1166,15 @@
 	_mtlPipelineState = nil;
 
 	if (shaderFunc.mtlFunction) {
-		MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(_device);
+		MVKComputePipelineCompiler* plc = new MVKComputePipelineCompiler(this);
 		_mtlPipelineState = plc->newMTLComputePipelineState(shaderFunc.mtlFunction);	// retained
-		setConfigurationResult(plc->getConfigurationResult());
 		plc->destroy();
 	} else {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Compute shader function could not be compiled into pipeline. See previous logged error."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Compute shader function could not be compiled into pipeline. See previous logged error."));
 	}
 
 	if (_needsAuxBuffer && _auxBufferIndex.stages[kMVKShaderStageCompute] > _device->_pMetalFeatures->maxPerStageBufferCount) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Compute shader requires auxiliary buffer, but there is no free slot to pass it."));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "Compute shader requires auxiliary buffer, but there is no free slot to pass it."));
 	}
 }
 
@@ -1227,7 +1224,7 @@
 MVKShaderLibraryCache* MVKPipelineCache::getShaderLibraryCache(MVKShaderModuleKey smKey) {
 	MVKShaderLibraryCache* slCache = _shaderCache[smKey];
 	if ( !slCache ) {
-		slCache = new MVKShaderLibraryCache(_device);
+		slCache = new MVKShaderLibraryCache(this);
 		_shaderCache[smKey] = slCache;
 	}
 	return slCache;
@@ -1311,7 +1308,11 @@
 
 // Helper class to iterate through the shader libraries in a shader library cache in order to serialize them.
 // Needs to support input of null shader library cache.
-class MVKShaderCacheIterator : MVKBaseObject {
+class MVKShaderCacheIterator : public MVKBaseObject {
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _pSLCache->getVulkanAPIObject(); };
+
 protected:
 	friend MVKPipelineCache;
 
@@ -1361,7 +1362,7 @@
 
 	} catch (cereal::Exception& ex) {
 		*pDataSize = 0;
-		return mvkNotifyErrorWithText(VK_INCOMPLETE, "Error writing pipeline cache data: %s", ex.what());
+		return reportError(VK_INCOMPLETE, "Error writing pipeline cache data: %s", ex.what());
 	}
 }
 
@@ -1475,7 +1476,7 @@
 		}
 
 	} catch (cereal::Exception& ex) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_SUCCESS, "Error reading pipeline cache data: %s", ex.what()));
+		setConfigurationResult(reportError(VK_SUCCESS, "Error reading pipeline cache data: %s", ex.what()));
 	}
 }
 
@@ -1499,7 +1500,7 @@
 
 #pragma mark Construction
 
-MVKPipelineCache::MVKPipelineCache(MVKDevice* device, const VkPipelineCacheCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+MVKPipelineCache::MVKPipelineCache(MVKDevice* device, const VkPipelineCacheCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
 	readData(pCreateInfo);
 }
 
@@ -1516,11 +1517,11 @@
 	unique_lock<mutex> lock(_completionLock);
 
 	compile(lock, ^{
-		[getMTLDevice() newRenderPipelineStateWithDescriptor: mtlRPLDesc
-										   completionHandler: ^(id<MTLRenderPipelineState> ps, NSError* error) {
-											   bool isLate = compileComplete(ps, error);
-											   if (isLate) { destroy(); }
-										   }];
+		[_owner->getMTLDevice() newRenderPipelineStateWithDescriptor: mtlRPLDesc
+												   completionHandler: ^(id<MTLRenderPipelineState> ps, NSError* error) {
+													   bool isLate = compileComplete(ps, error);
+													   if (isLate) { destroy(); }
+												   }];
 	});
 
 	return [_mtlRenderPipelineState retain];
@@ -1547,11 +1548,11 @@
 	unique_lock<mutex> lock(_completionLock);
 
 	compile(lock, ^{
-		[getMTLDevice() newComputePipelineStateWithFunction: mtlFunction
-										  completionHandler: ^(id<MTLComputePipelineState> ps, NSError* error) {
-											  bool isLate = compileComplete(ps, error);
-											  if (isLate) { destroy(); }
-										  }];
+		[_owner->getMTLDevice() newComputePipelineStateWithFunction: mtlFunction
+												  completionHandler: ^(id<MTLComputePipelineState> ps, NSError* error) {
+													  bool isLate = compileComplete(ps, error);
+													  if (isLate) { destroy(); }
+												  }];
 	});
 
 	return [_mtlComputePipelineState retain];
@@ -1561,12 +1562,12 @@
 	unique_lock<mutex> lock(_completionLock);
 
 	compile(lock, ^{
-		[getMTLDevice() newComputePipelineStateWithDescriptor: plDesc
-													  options: MTLPipelineOptionNone
-											completionHandler: ^(id<MTLComputePipelineState> ps, MTLComputePipelineReflection*, NSError* error) {
-												bool isLate = compileComplete(ps, error);
-												if (isLate) { destroy(); }
-											}];
+		[_owner->getMTLDevice() newComputePipelineStateWithDescriptor: plDesc
+															  options: MTLPipelineOptionNone
+													completionHandler: ^(id<MTLComputePipelineState> ps, MTLComputePipelineReflection*, NSError* error) {
+														bool isLate = compileComplete(ps, error);
+														if (isLate) { destroy(); }
+													}];
 	});
 
 	return [_mtlComputePipelineState retain];
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.h b/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.h
index b6e337e..cf77c01 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.h
@@ -40,10 +40,13 @@
  * Subclasses are specialized for specific query types.
  * Subclasses will generally override the beginQuery(), endQuery(), and getResult(uint32_t, void*, bool) member functions.
  */
-class MVKQueryPool : public MVKBaseDeviceObject {
+class MVKQueryPool : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_QUERY_POOL_EXT; }
+
     /** Begins the specified query. */
     virtual void beginQuery(uint32_t query, VkQueryControlFlags flags, MVKCommandEncoder* cmdEncoder) {}
 
@@ -94,7 +97,7 @@
 
 	MVKQueryPool(MVKDevice* device,
 				 const VkQueryPoolCreateInfo* pCreateInfo,
-				 const uint32_t queryElementCount) : MVKBaseDeviceObject(device),
+				 const uint32_t queryElementCount) : MVKVulkanAPIDeviceObject(device),
                     _availability(pCreateInfo->queryCount),
                     _queryElementCount(queryElementCount) {}
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm
index e068b47..0cdfd93 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm
@@ -286,7 +286,7 @@
     NSUInteger offset = getVisibilityResultOffset(query);
     NSUInteger maxOffset = getDevice()->_pMetalFeatures->maxQueryBufferSize - kMVKQuerySlotSizeInBytes;
     if (offset > maxOffset) {
-        cmdBuffer->recordResult(mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "vkCmdBeginQuery(): The query offset value %lu is larger than the maximum offset value %lu available on this device.", offset, maxOffset));
+        cmdBuffer->setConfigurationResult(reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "vkCmdBeginQuery(): The query offset value %lu is larger than the maximum offset value %lu available on this device.", offset, maxOffset));
     }
 
     if (cmdBuffer->_initialVisibilityResultMTLBuffer == nil) {
@@ -311,7 +311,7 @@
         queryCount = uint32_t(newBuffLen / kMVKQuerySlotSizeInBytes);
 
         if (reqBuffLen > maxBuffLen) {
-            mvkNotifyErrorWithText(VK_ERROR_OUT_OF_DEVICE_MEMORY, "vkCreateQueryPool(): Each query pool can support a maximum of %d queries.", queryCount);
+            reportError(VK_ERROR_OUT_OF_DEVICE_MEMORY, "vkCreateQueryPool(): Each query pool can support a maximum of %d queries.", queryCount);
         }
 
         NSUInteger mtlBuffLen = mvkAlignByteOffset(newBuffLen, _device->_pMetalFeatures->mtlBufferAlignment);
@@ -335,7 +335,7 @@
 MVKPipelineStatisticsQueryPool::MVKPipelineStatisticsQueryPool(MVKDevice* device,
 															   const VkQueryPoolCreateInfo* pCreateInfo) : MVKQueryPool(device, pCreateInfo, 1) {
 	if ( !_device->_enabledFeatures.pipelineStatisticsQuery ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateQueryPool: VK_QUERY_TYPE_PIPELINE_STATISTICS is not supported."));
+		setConfigurationResult(reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateQueryPool: VK_QUERY_TYPE_PIPELINE_STATISTICS is not supported."));
 	}
 }
 
@@ -345,5 +345,5 @@
 
 MVKUnsupportedQueryPool::MVKUnsupportedQueryPool(MVKDevice* device,
 												 const VkQueryPoolCreateInfo* pCreateInfo) : MVKQueryPool(device, pCreateInfo, 1) {
-	setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "vkCreateQueryPool: Unsupported query pool type: %d.", pCreateInfo->queryType));
+	setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED, "vkCreateQueryPool: Unsupported query pool type: %d.", pCreateInfo->queryType));
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
index 6ef0f08..e30d5ff 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.h
@@ -38,10 +38,13 @@
 #pragma mark MVKQueueFamily
 
 /** Represents a Vulkan queue family. */
-class MVKQueueFamily : public MVKConfigurableObject {
+class MVKQueueFamily : public MVKBaseObject {
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _physicalDevice->getVulkanAPIObject(); }
+
 	/** Returns the index of this queue family. */
 	inline uint32_t getIndex() { return _queueFamilyIndex; }
 
@@ -71,10 +74,16 @@
 #pragma mark MVKQueue
 
 /** Represents a Vulkan queue. */
-class MVKQueue : public MVKDispatchableDeviceObject {
+class MVKQueue : public MVKDispatchableVulkanAPIObject, public MVKDeviceTrackingMixin {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_QUEUE_EXT; }
+
+	/** Returns a pointer to the Vulkan instance. */
+	MVKInstance* getInstance() override { return _device->getInstance(); }
+
 #pragma mark Queue submissions
 
 	/** Submits the specified command buffers to the queue. */
@@ -122,6 +131,7 @@
 	friend class MVKQueueCommandBufferSubmission;
 	friend class MVKQueuePresentSurfaceSubmission;
 
+	MVKBaseObject* getBaseObject() override { return this; };
 	void initName();
 	void initExecQueue();
 	void initMTLCommandQueue();
@@ -145,31 +155,30 @@
 #pragma mark MVKQueueSubmission
 
 /** This is an abstract class for an operation that can be submitted to an MVKQueue. */
-class MVKQueueSubmission : public MVKBaseDeviceObject {
+class MVKQueueSubmission : public MVKConfigurableObject {
 
 public:
 
-	/** 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _queue->getVulkanAPIObject(); }
+
+	/**
 	 * Executes this action on the queue and then disposes of this instance.
 	 *
 	 * Upon completion of this function, no further calls should be made to this instance.
 	 */
 	virtual void execute() = 0;
 
-	MVKQueueSubmission(MVKDevice* device,
-					   MVKQueue* queue,
+	MVKQueueSubmission(MVKQueue* queue,
 					   uint32_t waitSemaphoreCount,
 					   const VkSemaphore* pWaitSemaphores);
 
 protected:
 	friend class MVKQueue;
 
-   void recordResult(VkResult vkResult);
-
 	MVKQueue* _queue;
 	MVKQueueSubmission* _prev;
 	MVKQueueSubmission* _next;
-	VkResult _submissionResult;
 	MVKVectorInline<MVKSemaphore*, 8> _waitSemaphores;
 	bool _isAwaitingSemaphores;
 };
@@ -184,11 +193,8 @@
 public:
 	void execute() override;
 
-	/** 
-     * Constructs an instance for the device and queue.
-     */
-	MVKQueueCommandBufferSubmission(MVKDevice* device,
-									MVKQueue* queue,
+	/** Constructs an instance for the queue. */
+	MVKQueueCommandBufferSubmission(MVKQueue* queue,
 									const VkSubmitInfo* pSubmit,
 									VkFence fence,
                                     MVKCommandUse cmdBuffUse);
@@ -219,9 +225,7 @@
 public:
 	void execute() override;
 
-	/** Constructs an instance for the device and queue. */
-	MVKQueuePresentSurfaceSubmission(MVKDevice* device,
-									 MVKQueue* queue,
+	MVKQueuePresentSurfaceSubmission(MVKQueue* queue,
 									 const VkPresentInfoKHR* pPresentInfo);
 
 protected:
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
index e6ad5cf..4efac25 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
@@ -74,7 +74,7 @@
 VkResult MVKQueue::submit(MVKQueueSubmission* qSubmit) {
 	if ( !qSubmit ) { return VK_SUCCESS; }     // Ignore nils
 
-	VkResult rslt = qSubmit->_submissionResult;     // Extract result before submission to avoid race condition with early destruction
+	VkResult rslt = qSubmit->getConfigurationResult();     // Extract result before submission to avoid race condition with early destruction
 	if (_execQueue) {
 		dispatch_async(_execQueue, ^{ execute(qSubmit); } );
 	} else {
@@ -88,20 +88,20 @@
 
     // Fence-only submission
     if (submitCount == 0 && fence) {
-        return submit(new MVKQueueCommandBufferSubmission(_device, this, nullptr, fence, cmdBuffUse));
+        return submit(new MVKQueueCommandBufferSubmission(this, nullptr, fence, cmdBuffUse));
     }
 
     VkResult rslt = VK_SUCCESS;
     for (uint32_t sIdx = 0; sIdx < submitCount; sIdx++) {
         VkFence fenceOrNil = (sIdx == (submitCount - 1)) ? fence : VK_NULL_HANDLE; // last one gets the fence
-        VkResult subRslt = submit(new MVKQueueCommandBufferSubmission(_device, this, &pSubmits[sIdx], fenceOrNil, cmdBuffUse));
+        VkResult subRslt = submit(new MVKQueueCommandBufferSubmission(this, &pSubmits[sIdx], fenceOrNil, cmdBuffUse));
         if (rslt == VK_SUCCESS) { rslt = subRslt; }
     }
     return rslt;
 }
 
 VkResult MVKQueue::submit(const VkPresentInfoKHR* pPresentInfo) {
-	return submit(new MVKQueuePresentSurfaceSubmission(_device, this, pPresentInfo));
+	return submit(new MVKQueuePresentSurfaceSubmission(this, pPresentInfo));
 }
 
 // Create an empty submit struct and fence, submit to queue and wait on fence.
@@ -116,7 +116,7 @@
 	MVKFence mvkFence(_device, &vkFenceInfo);
 	VkFence fence = (VkFence)&mvkFence;
 	submit(0, nullptr, fence, cmdBuffUse);
-	return mvkWaitForFences(1, &fence, false);
+	return mvkWaitForFences(_device, 1, &fence, false);
 }
 
 
@@ -125,7 +125,7 @@
 #define MVK_DISPATCH_QUEUE_QOS_CLASS		QOS_CLASS_USER_INITIATED
 
 MVKQueue::MVKQueue(MVKDevice* device, MVKQueueFamily* queueFamily, uint32_t index, float priority)
-        : MVKDispatchableDeviceObject(device) {
+        : MVKDeviceTrackingMixin(device) {
 
 	_queueFamily = queueFamily;
 	_index = index;
@@ -167,7 +167,7 @@
 
 // Initializes Xcode GPU capture scopes
 void MVKQueue::initGPUCaptureScopes() {
-	const MVKConfiguration* pMVKConfig = _device->getInstance()->getMoltenVKConfiguration();
+	const MVKConfiguration* pMVKConfig = getInstance()->getMoltenVKConfiguration();
 
 	_submissionCaptureScope = new MVKGPUCaptureScope(this, "CommandBuffer-Submission");
 
@@ -197,14 +197,12 @@
 #pragma mark -
 #pragma mark MVKQueueSubmission
 
-MVKQueueSubmission::MVKQueueSubmission(MVKDevice* device,
-									   MVKQueue* queue,
+MVKQueueSubmission::MVKQueueSubmission(MVKQueue* queue,
 									   uint32_t waitSemaphoreCount,
-									   const VkSemaphore* pWaitSemaphores) : MVKBaseDeviceObject(device) {
+									   const VkSemaphore* pWaitSemaphores) {
 	_queue = queue;
 	_prev = VK_NULL_HANDLE;
 	_next = VK_NULL_HANDLE;
-	_submissionResult = VK_SUCCESS;
 
 	_isAwaitingSemaphores = waitSemaphoreCount > 0;
 	_waitSemaphores.reserve(waitSemaphoreCount);
@@ -213,10 +211,6 @@
 	}
 }
 
-void MVKQueueSubmission::recordResult(VkResult vkResult) {
-    if (_submissionResult == VK_SUCCESS) { _submissionResult = vkResult; }
-}
-
 
 #pragma mark -
 #pragma mark MVKQueueCommandBufferSubmission
@@ -227,8 +221,10 @@
 
 	_queue->_submissionCaptureScope->beginScope();
 
+	MVKDevice* mvkDev = _queue->getDevice();
+
 	// If the device supports it, wait for any semaphores on the device.
-	if (_device->_pMetalFeatures->events && _isAwaitingSemaphores) {
+	if (mvkDev->_pMetalFeatures->events && _isAwaitingSemaphores) {
 		_isAwaitingSemaphores = false;
 		for (auto* ws : _waitSemaphores) {
 			ws->encodeWait(getActiveMTLCommandBuffer());
@@ -243,7 +239,7 @@
 	if (_fence || _isSignalingSemaphores) { getActiveMTLCommandBuffer(); }
 
 	// If the device supports it, signal all semaphores on the device.
-	if (_device->_pMetalFeatures->events && _isSignalingSemaphores) {
+	if (mvkDev->_pMetalFeatures->events && _isSignalingSemaphores) {
 		_isSignalingSemaphores = false;
 		for (auto* ss : _signalSemaphores) {
 			ss->encodeSignal(getActiveMTLCommandBuffer());
@@ -315,13 +311,11 @@
     this->destroy();
 }
 
-MVKQueueCommandBufferSubmission::MVKQueueCommandBufferSubmission(MVKDevice* device,
-																 MVKQueue* queue,
+MVKQueueCommandBufferSubmission::MVKQueueCommandBufferSubmission(MVKQueue* queue,
 																 const VkSubmitInfo* pSubmit,
 																 VkFence fence,
                                                                  MVKCommandUse cmdBuffUse)
-        : MVKQueueSubmission(device,
-							 queue,
+        : MVKQueueSubmission(queue,
 							 (pSubmit ? pSubmit->waitSemaphoreCount : 0),
 							 (pSubmit ? pSubmit->pWaitSemaphores : nullptr)) {
 
@@ -332,7 +326,7 @@
         for (uint32_t i = 0; i < cbCnt; i++) {
             MVKCommandBuffer* cb = MVKCommandBuffer::getMVKCommandBuffer(pSubmit->pCommandBuffers[i]);
             _cmdBuffers.push_back(cb);
-            recordResult(cb->getRecordingResult());
+            setConfigurationResult(cb->getConfigurationResult());
         }
 
         uint32_t ssCnt = pSubmit->signalSemaphoreCount;
@@ -360,7 +354,8 @@
 
 	// If there are semaphores and this device supports MTLEvent, we must present
 	// with a command buffer in order to synchronize with the semaphores.
-	if (_device->_pMetalFeatures->events && !_waitSemaphores.empty()) {
+	MVKDevice* mvkDev = _queue->getDevice();
+	if (mvkDev->_pMetalFeatures->events && !_waitSemaphores.empty()) {
 		// Create a command buffer, have it wait for the semaphores, then present
 		// surfaces via the command buffer.
 		id<MTLCommandBuffer> mtlCmdBuff = [mtlQ commandBufferWithUnretainedReferences];
@@ -371,7 +366,7 @@
 		for (auto& si : _surfaceImages) { si->presentCAMetalDrawable(mtlCmdBuff); }
 
 		[mtlCmdBuff commit];
-	} else if (_device->_pMVKConfig->presentWithCommandBuffer || _device->_pMVKConfig->displayWatermark) {
+	} else if (mvkDev->_pMVKConfig->presentWithCommandBuffer || mvkDev->_pMVKConfig->displayWatermark) {
 		// Create a command buffer, present surfaces via the command buffer,
 		// then wait on the semaphores before committing.
 		id<MTLCommandBuffer> mtlCmdBuff = [mtlQ commandBufferWithUnretainedReferences];
@@ -396,13 +391,9 @@
     this->destroy();
 }
 
-MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKDevice* device,
-																   MVKQueue* queue,
+MVKQueuePresentSurfaceSubmission::MVKQueuePresentSurfaceSubmission(MVKQueue* queue,
 																   const VkPresentInfoKHR* pPresentInfo)
-		: MVKQueueSubmission(device,
-							 queue,
-							 pPresentInfo->waitSemaphoreCount,
-							 pPresentInfo->pWaitSemaphores) {
+		: MVKQueueSubmission(queue, pPresentInfo->waitSemaphoreCount, pPresentInfo->pWaitSemaphores) {
 
 	// Populate the array of swapchain images, testing each one for a change in surface size
 	_surfaceImages.reserve(pPresentInfo->swapchainCount);
@@ -411,9 +402,9 @@
 		_surfaceImages.push_back(mvkSC->getImage(pPresentInfo->pImageIndices[i]));
 		// Surface loss takes precedence over out-of-date errors.
 		if (mvkSC->getIsSurfaceLost()) {
-			_submissionResult = VK_ERROR_SURFACE_LOST_KHR;
-		} else if (mvkSC->getHasSurfaceSizeChanged() && _submissionResult != VK_ERROR_SURFACE_LOST_KHR) {
-			_submissionResult = VK_ERROR_OUT_OF_DATE_KHR;
+			setConfigurationResult(VK_ERROR_SURFACE_LOST_KHR);
+		} else if (mvkSC->getHasSurfaceSizeChanged() && getConfigurationResult() != VK_ERROR_SURFACE_LOST_KHR) {
+			setConfigurationResult(VK_ERROR_OUT_OF_DATE_KHR);
 		}
 	}
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
index 45c8788..d61285c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.h
@@ -36,6 +36,10 @@
 
 public:
 
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
+
 	/** Returns the number of color attachments, which may be zero for depth-only rendering. */
 	inline uint32_t getColorAttachmentCount() { return uint32_t(_colorAttachments.size()); }
 
@@ -97,7 +101,9 @@
 class MVKRenderPassAttachment : public MVKBaseObject {
 
 public:
-    friend MVKRenderSubpass;
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override;
 
     /** Returns the Vulkan format of this attachment. */
     VkFormat getFormat();
@@ -137,10 +143,13 @@
 #pragma mark MVKRenderPass
 
 /** Represents a Vulkan render pass. */
-class MVKRenderPass : public MVKBaseDeviceObject {
+class MVKRenderPass : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_RENDER_PASS_EXT; }
+
     /** Returns the granularity of the render area of this instance.  */
     VkExtent2D getRenderAreaGranularity();
 
@@ -150,7 +159,7 @@
 	/** Constructs an instance for the specified device. */
 	MVKRenderPass(MVKDevice* device, const VkRenderPassCreateInfo* pCreateInfo);
 
-private:
+protected:
 
 	friend class MVKRenderSubpass;
 	friend class MVKRenderPassAttachment;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
index 11fa241..9a7cffe 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKRenderPass.mm
@@ -20,7 +20,7 @@
 #include "MVKFramebuffer.h"
 #include "MVKCommandBuffer.h"
 #include "MVKFoundation.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 
 using namespace std;
 
@@ -28,6 +28,8 @@
 #pragma mark -
 #pragma mark MVKRenderSubpass
 
+MVKVulkanAPIObject* MVKRenderSubpass::getVulkanAPIObject() { return _renderPass->getVulkanAPIObject(); };
+
 VkFormat MVKRenderSubpass::getColorAttachmentFormat(uint32_t colorAttIdx) {
 	if (colorAttIdx < _colorAttachments.size()) {
 		uint32_t rpAttIdx = _colorAttachments[colorAttIdx].attachment;
@@ -213,7 +215,7 @@
 }
 
 MVKRenderSubpass::MVKRenderSubpass(MVKRenderPass* renderPass,
-								   const VkSubpassDescription* pCreateInfo) : MVKBaseObject() {
+								   const VkSubpassDescription* pCreateInfo) {
 	_renderPass = renderPass;
 	_subpassIndex = (uint32_t)_renderPass->_subpasses.size();
 
@@ -251,6 +253,8 @@
 #pragma mark -
 #pragma mark MVKRenderPassAttachment
 
+MVKVulkanAPIObject* MVKRenderPassAttachment::getVulkanAPIObject() { return _renderPass->getVulkanAPIObject(); };
+
 VkFormat MVKRenderPassAttachment::getFormat() { return _info.format; }
 
 VkSampleCountFlagBits MVKRenderPassAttachment::getSampleCount() { return _info.samples; }
@@ -302,7 +306,7 @@
 }
 
 MVKRenderPassAttachment::MVKRenderPassAttachment(MVKRenderPass* renderPass,
-												 const VkAttachmentDescription* pCreateInfo) : MVKBaseObject() {
+												 const VkAttachmentDescription* pCreateInfo) {
 	_renderPass = renderPass;
 	_attachmentIndex = uint32_t(_renderPass->_attachments.size());
 
@@ -329,7 +333,7 @@
 MVKRenderSubpass* MVKRenderPass::getSubpass(uint32_t subpassIndex) { return &_subpasses[subpassIndex]; }
 
 MVKRenderPass::MVKRenderPass(MVKDevice* device,
-							 const VkRenderPassCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device) {
+							 const VkRenderPassCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
 
     // Add subpasses and dependencies first
 	_subpasses.reserve(pCreateInfo->subpassCount);
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKResource.h b/MoltenVK/MoltenVK/GPUObjects/MVKResource.h
index a96867a..9022f86 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 MVKRefCountedDeviceObject {
+class MVKResource : public MVKVulkanAPIDeviceObject {
 
 public:
 
@@ -77,7 +77,7 @@
 	
 #pragma mark Construction
 
-    MVKResource(MVKDevice* device) : MVKRefCountedDeviceObject(device) {}
+    MVKResource(MVKDevice* device) : MVKVulkanAPIDeviceObject(device) {}
 
 protected:
 	virtual bool needsHostReadSync(VkPipelineStageFlags srcStageMask,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm b/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm
index b3a3bc8..e8f7b88 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKResource.mm
@@ -18,6 +18,7 @@
 
 #include "MVKResource.h"
 #include "MVKCommandBuffer.h"
+#include "MVKEnvironment.h"
 
 
 struct MVKBindDeviceMemoryInfo {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
index 5fb7556..4a0ff70 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.h
@@ -45,17 +45,23 @@
 extern const MVKMTLFunction MVKMTLFunctionNull;
 
 /** Wraps a single MTLLibrary. */
-class MVKShaderLibrary : public MVKBaseDeviceObject {
+class MVKShaderLibrary : public MVKBaseObject {
 
 public:
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _owner->getVulkanAPIObject(); };
+
 	/** Returns the Metal shader function, possibly specialized. */
 	MVKMTLFunction getMTLFunction(const VkSpecializationInfo* pSpecializationInfo);
 
 	/** Constructs an instance from the specified MSL source code. */
-	MVKShaderLibrary(MVKDevice* device, const std::string& mslSourceCode, const SPIRVEntryPoint& entryPoint);
+	MVKShaderLibrary(MVKVulkanAPIDeviceObject* owner,
+					 const std::string& mslSourceCode,
+					 const SPIRVEntryPoint& entryPoint);
 
 	/** Constructs an instance from the specified compiled MSL code data. */
-	MVKShaderLibrary(MVKDevice* device,
+	MVKShaderLibrary(MVKVulkanAPIDeviceObject* owner,
 					 const void* mslCompiledCodeData,
 					 size_t mslCompiledCodeLength);
 
@@ -70,6 +76,7 @@
 	void handleCompilationError(NSError* err, const char* opDesc);
     MTLFunctionConstant* getFunctionConstant(NSArray<MTLFunctionConstant*>* mtlFCs, NSUInteger mtlFCID);
 
+	MVKVulkanAPIDeviceObject* _owner;
 	id<MTLLibrary> _mtlLibrary;
 	SPIRVEntryPoint _entryPoint;
 	std::string _msl;
@@ -80,10 +87,13 @@
 #pragma mark MVKShaderLibraryCache
 
 /** Represents a cache of shader libraries for one shader module. */
-class MVKShaderLibraryCache : public MVKBaseDeviceObject {
+class MVKShaderLibraryCache : public MVKBaseObject {
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _owner->getVulkanAPIObject(); };
+
 	/**
 	 * Returns a shader library from the specified shader context sourced from the specified shader module,
 	 * lazily creating the shader library from source code in the shader module, if needed.
@@ -95,7 +105,7 @@
 									   MVKShaderModule* shaderModule,
 									   bool* pWasAdded = nullptr);
 
-	MVKShaderLibraryCache(MVKDevice* device) : MVKBaseDeviceObject(device) {};
+	MVKShaderLibraryCache(MVKVulkanAPIDeviceObject* owner) : _owner(owner) {};
 
 	~MVKShaderLibraryCache() override;
 
@@ -109,6 +119,7 @@
 									   const SPIRVEntryPoint& entryPoint);
 	void merge(MVKShaderLibraryCache* other);
 
+	MVKVulkanAPIDeviceObject* _owner;
 	std::mutex _accessLock;
 	std::vector<std::pair<SPIRVToMSLConverterContext, MVKShaderLibrary*>> _shaderLibraries;
 };
@@ -140,9 +151,13 @@
 }
 
 /** Represents a Vulkan shader module. */
-class MVKShaderModule : public MVKBaseDeviceObject {
+class MVKShaderModule : public MVKVulkanAPIDeviceObject {
 
 public:
+
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SHADER_MODULE_EXT; }
+
 	/** Returns the Metal shader function, possibly specialized. */
 	MVKMTLFunction getMTLFunction(SPIRVToMSLConverterContext* pContext,
 								  const VkSpecializationInfo* pSpecializationInfo,
@@ -207,9 +222,9 @@
 
 #pragma mark Construction
 
-	MVKShaderLibraryCompiler(MVKDevice* device) : MVKMetalCompiler(device) {
+	MVKShaderLibraryCompiler(MVKVulkanAPIDeviceObject* owner) : MVKMetalCompiler(owner) {
 		_compilerType = "Shader library";
-		_pPerformanceTracker = &_device->_performanceStatistics.shaderCompilation.mslCompile;
+		_pPerformanceTracker = &_owner->getDevice()->_performanceStatistics.shaderCompilation.mslCompile;
 	}
 
 	~MVKShaderLibraryCompiler() override;
@@ -245,9 +260,9 @@
 
 #pragma mark Construction
 
-	MVKFunctionSpecializer(MVKDevice* device) : MVKMetalCompiler(device) {
+	MVKFunctionSpecializer(MVKVulkanAPIDeviceObject* owner) : MVKMetalCompiler(owner) {
 		_compilerType = "Function specialization";
-		_pPerformanceTracker = &_device->_performanceStatistics.shaderCompilation.functionSpecialization;
+		_pPerformanceTracker = &_owner->getDevice()->_performanceStatistics.shaderCompilation.functionSpecialization;
 	}
 
 	~MVKFunctionSpecializer() override;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
index 46b5cec..199c0ba 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
@@ -19,6 +19,7 @@
 #include "MVKShaderModule.h"
 #include "MVKPipeline.h"
 #include "MVKFoundation.h"
+#include "MVKLogging.h"
 #include "vk_mvk_moltenvk.h"
 #include <string>
 
@@ -53,15 +54,17 @@
     // as a function name), and retrieve the unspecialized Metal function with that name.
     NSString* mtlFuncName = @(_entryPoint.mtlFunctionName.c_str());
 
-    uint64_t startTime = _device->getPerformanceTimestamp();
+
+	MVKDevice* mvkDev = _owner->getDevice();
+    uint64_t startTime = mvkDev->getPerformanceTimestamp();
     id<MTLFunction> mtlFunc = [[_mtlLibrary newFunctionWithName: mtlFuncName] autorelease];
-    _device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.functionRetrieval, startTime);
+    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 (_device->_pMetalFeatures->shaderSpecialization) {
+        if (mvkDev->_pMetalFeatures->shaderSpecialization) {
             NSArray<MTLFunctionConstant*>* mtlFCs = mtlFunc.functionConstantsDictionary.allValues;
             if (mtlFCs.count) {
                 // The Metal shader contains function constants and expects to be specialized
@@ -83,14 +86,13 @@
                 }
 
                 // Compile the specialized Metal function, and use it instead of the unspecialized Metal function.
-				MVKFunctionSpecializer* fs = new MVKFunctionSpecializer(_device);
+				MVKFunctionSpecializer* fs = new MVKFunctionSpecializer(_owner);
 				mtlFunc = [fs->newMTLFunction(_mtlLibrary, mtlFuncName, mtlFCVals) autorelease];
-				setConfigurationResult(fs->getConfigurationResult());
 				fs->destroy();
             }
         }
     } else {
-        mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "Shader module does not contain an entry point named '%s'.", mtlFuncName.UTF8String);
+        reportError(VK_ERROR_INITIALIZATION_FAILED, "Shader module does not contain an entry point named '%s'.", mtlFuncName.UTF8String);
     }
 
 	return { mtlFunc, MTLSizeMake(getWorkgroupDimensionSize(_entryPoint.workgroupSize.width, pSpecializationInfo),
@@ -106,34 +108,34 @@
     return nil;
 }
 
-MVKShaderLibrary::MVKShaderLibrary(MVKDevice* device, const string& mslSourceCode, const SPIRVEntryPoint& entryPoint) : MVKBaseDeviceObject(device) {
-	MVKShaderLibraryCompiler* slc = new MVKShaderLibraryCompiler(_device);
+MVKShaderLibrary::MVKShaderLibrary(MVKVulkanAPIDeviceObject* owner, const string& mslSourceCode, const SPIRVEntryPoint& entryPoint) : _owner(owner) {
+	MVKShaderLibraryCompiler* slc = new MVKShaderLibraryCompiler(_owner);
 	_mtlLibrary = slc->newMTLLibrary(@(mslSourceCode.c_str()));	// retained
-	setConfigurationResult(slc->getConfigurationResult());
 	slc->destroy();
 
 	_entryPoint = entryPoint;
 	_msl = mslSourceCode;
 }
 
-MVKShaderLibrary::MVKShaderLibrary(MVKDevice* device,
+MVKShaderLibrary::MVKShaderLibrary(MVKVulkanAPIDeviceObject* owner,
                                    const void* mslCompiledCodeData,
-                                   size_t mslCompiledCodeLength) : MVKBaseDeviceObject(device) {
-    uint64_t startTime = _device->getPerformanceTimestamp();
+                                   size_t mslCompiledCodeLength) : _owner(owner) {
+	MVKDevice* mvkDev = _owner->getDevice();
+    uint64_t startTime = mvkDev->getPerformanceTimestamp();
     @autoreleasepool {
         dispatch_data_t shdrData = dispatch_data_create(mslCompiledCodeData,
                                                         mslCompiledCodeLength,
                                                         NULL,
                                                         DISPATCH_DATA_DESTRUCTOR_DEFAULT);
         NSError* err = nil;
-        _mtlLibrary = [getMTLDevice() newLibraryWithData: shdrData error: &err];    // retained
+        _mtlLibrary = [mvkDev->getMTLDevice() newLibraryWithData: shdrData error: &err];    // retained
         handleCompilationError(err, "Compiled shader module creation");
         [shdrData release];
     }
-    _device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.mslLoad, startTime);
+    mvkDev->addActivityPerformance(mvkDev->_performanceStatistics.shaderCompilation.mslLoad, startTime);
 }
 
-MVKShaderLibrary::MVKShaderLibrary(MVKShaderLibrary& other) : MVKBaseDeviceObject(other._device) {
+MVKShaderLibrary::MVKShaderLibrary(MVKShaderLibrary& other) : _owner(other._owner) {
 	_mtlLibrary = [other._mtlLibrary retain];
 	_entryPoint = other._entryPoint;
 	_msl = other._msl;
@@ -148,10 +150,10 @@
     if (_mtlLibrary) {
         MVKLogInfo("%s succeeded with warnings (Error code %li):\n%s", opDesc, (long)err.code, err.localizedDescription.UTF8String);
     } else {
-        setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED,
-                                                      "%s failed (Error code %li):\n%s",
-                                                      opDesc, (long)err.code,
-                                                      err.localizedDescription.UTF8String));
+		_owner->setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED,
+												   "%s failed (Error code %li):\n%s",
+												   opDesc, (long)err.code,
+												   err.localizedDescription.UTF8String));
     }
 }
 
@@ -196,7 +198,7 @@
 MVKShaderLibrary* MVKShaderLibraryCache::addShaderLibrary(SPIRVToMSLConverterContext* pContext,
 														  const string& mslSourceCode,
 														  const SPIRVEntryPoint& entryPoint) {
-	MVKShaderLibrary* shLib = new MVKShaderLibrary(_device, mslSourceCode, entryPoint);
+	MVKShaderLibrary* shLib = new MVKShaderLibrary(_owner, mslSourceCode, entryPoint);
 	_shaderLibraries.emplace_back(*pContext, shLib);
 	return shLib;
 }
@@ -250,7 +252,7 @@
 	if (wasConverted) {
 		if (shouldLogCode) { MVKLogInfo("%s", _converter.getResultLog().data()); }
 	} else {
-		mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "Unable to convert SPIR-V to MSL:\n%s", _converter.getResultLog().data());
+		reportError(VK_ERROR_FORMAT_NOT_SUPPORTED, "Unable to convert SPIR-V to MSL:\n%s", _converter.getResultLog().data());
 	}
 	return wasConverted;
 }
@@ -259,7 +261,7 @@
 #pragma mark Construction
 
 MVKShaderModule::MVKShaderModule(MVKDevice* device,
-								 const VkShaderModuleCreateInfo* pCreateInfo) : MVKBaseDeviceObject(device), _shaderLibraryCache(device) {
+								 const VkShaderModuleCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device), _shaderLibraryCache(this) {
 
 	_defaultLibrary = nullptr;
 
@@ -268,7 +270,7 @@
 
     // Ensure something is there.
     if ( (pCreateInfo->pCode == VK_NULL_HANDLE) || (codeSize < 4) ) {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_INCOMPLETE, "Shader module contains no SPIR-V code."));
+		setConfigurationResult(reportError(VK_INCOMPLETE, "Shader module contains no SPIR-V code."));
 		return;
 	}
 
@@ -299,7 +301,7 @@
 			_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.hashShaderCode, startTime);
 
 			_converter.setMSL(pMSLCode, nullptr);
-			_defaultLibrary = new MVKShaderLibrary(_device, _converter.getMSL().c_str(), _converter.getEntryPoint());
+			_defaultLibrary = new MVKShaderLibrary(this, _converter.getMSL().c_str(), _converter.getEntryPoint());
 
 			break;
 		}
@@ -313,12 +315,12 @@
 			codeHash = mvkHash(pMSLCode, mslCodeLen, codeHash);
 			_device->addActivityPerformance(_device->_performanceStatistics.shaderCompilation.hashShaderCode, startTime);
 
-			_defaultLibrary = new MVKShaderLibrary(_device, (void*)(pMSLCode), mslCodeLen);
+			_defaultLibrary = new MVKShaderLibrary(this, (void*)(pMSLCode), mslCodeLen);
 
 			break;
 		}
 		default:
-			setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "SPIR-V contains invalid magic number %x.", magicNum));
+			setConfigurationResult(reportError(VK_ERROR_FORMAT_NOT_SUPPORTED, "SPIR-V contains invalid magic number %x.", magicNum));
 			break;
 	}
 
@@ -337,12 +339,12 @@
 	unique_lock<mutex> lock(_completionLock);
 
 	compile(lock, ^{
-		[getMTLDevice() newLibraryWithSource: mslSourceCode
-									 options: getDevice()->getMTLCompileOptions()
-						   completionHandler: ^(id<MTLLibrary> mtlLib, NSError* error) {
-							   bool isLate = compileComplete(mtlLib, error);
-							   if (isLate) { destroy(); }
-						   }];
+		[_owner->getMTLDevice() newLibraryWithSource: mslSourceCode
+											 options: _owner->getDevice()->getMTLCompileOptions()
+								   completionHandler: ^(id<MTLLibrary> mtlLib, NSError* error) {
+									   bool isLate = compileComplete(mtlLib, error);
+									   if (isLate) { destroy(); }
+								   }];
 	});
 
 	return [_mtlLibrary retain];
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h
index 7b718a6..5c21169 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.h
@@ -18,29 +18,23 @@
 
 #pragma once
 
-#include "mvk_vulkan.h"
 #include "MVKBaseObject.h"
+#include "MVKEnvironment.h"
 #include <mutex>
 
+#import <Metal/Metal.h>
+#import <QuartzCore/CAMetalLayer.h>
 
-// Expose MoltenVK Apple surface extension functionality
 #ifdef VK_USE_PLATFORM_IOS_MVK
-#	define vkCreate_PLATFORM_SurfaceMVK			vkCreateIOSSurfaceMVK
-#	define Vk_PLATFORM_SurfaceCreateInfoMVK		VkIOSSurfaceCreateInfoMVK
-#	define PLATFORM_VIEW_CLASS					UIView
+#	define PLATFORM_VIEW_CLASS	UIView
 #	import <UIKit/UIView.h>
 #endif
 
 #ifdef VK_USE_PLATFORM_MACOS_MVK
-#	define vkCreate_PLATFORM_SurfaceMVK			vkCreateMacOSSurfaceMVK
-#	define Vk_PLATFORM_SurfaceCreateInfoMVK		VkMacOSSurfaceCreateInfoMVK
-#	define PLATFORM_VIEW_CLASS					NSView
+#	define PLATFORM_VIEW_CLASS	NSView
 #	import <AppKit/NSView.h>
 #endif
 
-#import <Metal/Metal.h>
-#import <QuartzCore/CAMetalLayer.h>
-
 class MVKInstance;
 
 @class MVKBlockObserver;
@@ -49,10 +43,16 @@
 #pragma mark MVKSurface
 
 /** Represents a Vulkan WSI surface. */
-class MVKSurface : public MVKConfigurableObject {
+class MVKSurface : public MVKVulkanAPIObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SURFACE_KHR_EXT; }
+
+	/** Returns a pointer to the Vulkan instance. */
+	MVKInstance* getInstance() override { return _mvkInstance; }
+
     /** Returns the CAMetalLayer underlying this surface.  */
     inline CAMetalLayer* getCAMetalLayer() {
         std::lock_guard<std::mutex> lock(_lock);
@@ -69,6 +69,7 @@
 	~MVKSurface() override;
 
 protected:
+	MVKInstance* _mvkInstance;
 	CAMetalLayer* _mtlCAMetalLayer;
 	std::mutex _lock;
 	MVKBlockObserver* _layerObserver;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm
index fb9baaf..ab19020 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSurface.mm
@@ -19,30 +19,19 @@
 #include "MVKSurface.h"
 #include "MVKInstance.h"
 #include "MVKFoundation.h"
+#include "MVKLogging.h"
 #include "MVKOSExtensions.h"
 #import "MVKBlockObserver.h"
 
+#define STR(NAME) #NAME
+
 
 #pragma mark MVKSurface
 
-#pragma mark Construction
-
-#ifdef VK_USE_PLATFORM_IOS_MVK
-static const char* mvkSurfaceCreateFuncName = "vkCreateIOSSurfaceMVK";
-static const char* mvkSurfaceCreateStructName = "VkIOSSurfaceCreateInfoMVK";
-static const char* mvkViewClassName = "UIView";
-#endif
-
-#ifdef VK_USE_PLATFORM_MACOS_MVK
-static const char* mvkSurfaceCreateFuncName = "vkCreateMacOSSurfaceMVK";
-static const char* mvkSurfaceCreateStructName = "VkMacOSSurfaceCreateInfoMVK";
-static const char* mvkViewClassName = "NSView";
-#endif
-
 // pCreateInfo->pView can be either a CAMetalLayer or a view (NSView/UIView).
 MVKSurface::MVKSurface(MVKInstance* mvkInstance,
 					   const Vk_PLATFORM_SurfaceCreateInfoMVK* pCreateInfo,
-					   const VkAllocationCallbacks* pAllocator) {
+					   const VkAllocationCallbacks* pAllocator) : _mvkInstance(mvkInstance) {
 
 	// Get the platform object contained in pView
 	id<NSObject> obj = (id<NSObject>)pCreateInfo->pView;
@@ -50,7 +39,8 @@
 	// If it's a view (NSView/UIView), extract the layer, otherwise assume it's already a CAMetalLayer.
 	if ([obj isKindOfClass: [PLATFORM_VIEW_CLASS class]]) {
 		if ( !NSThread.isMainThread ) {
-			MVKLogInfo("%s(): You are not calling this function from the main thread. %s should only be accessed from the main thread. When using this function outside the main thread, consider passing the CAMetalLayer itself in %s::pView, instead of the %s.", mvkSurfaceCreateFuncName, mvkViewClassName, mvkSurfaceCreateStructName, mvkViewClassName);
+			MVKLogInfo("%s(): You are not calling this function from the main thread. %s should only be accessed from the main thread. When using this function outside the main thread, consider passing the CAMetalLayer itself in %s::pView, instead of the %s.",
+					   STR(vkCreate_PLATFORM_SurfaceMVK), STR(PLATFORM_VIEW_CLASS), STR(Vk_PLATFORM_SurfaceCreateInfoMVK), STR(PLATFORM_VIEW_CLASS));
 		}
 		obj = ((PLATFORM_VIEW_CLASS*)obj).layer;
 	}
@@ -72,7 +62,9 @@
 			} forObject: _mtlCAMetalLayer.delegate atKeyPath: @"layer"];
 		}
 	} else {
-		setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED, "%s(): On-screen rendering requires a layer of type CAMetalLayer.", mvkSurfaceCreateFuncName));
+		setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED,
+										   "%s(): On-screen rendering requires a layer of type CAMetalLayer.",
+										   STR(vkCreate_PLATFORM_SurfaceMVK)));
 		_mtlCAMetalLayer = nil;
 	}
 }
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
index 2c13f54..3ce0981 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.h
@@ -30,10 +30,13 @@
 #pragma mark MVKSwapchain
 
 /** Represents a Vulkan swapchain. */
-class MVKSwapchain : public MVKBaseDeviceObject {
+class MVKSwapchain : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SWAPCHAIN_KHR_EXT; }
+
 	/** Returns the number of images in this swapchain. */
 	uint32_t getImageCount();
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index 90ee5d2..4d720ed 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -24,7 +24,7 @@
 #include "MVKWatermark.h"
 #include "MVKWatermarkTextureContent.h"
 #include "MVKWatermarkShaderSource.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 #include "MVKLogging.h"
 #import "CAMetalLayer+MoltenVK.h"
 #import "MVKBlockObserver.h"
@@ -177,7 +177,7 @@
 #pragma mark Construction
 
 MVKSwapchain::MVKSwapchain(MVKDevice* device,
-						   const VkSwapchainCreateInfoKHR* pCreateInfo) : MVKBaseDeviceObject(device), _surfaceLost(false) {
+						   const VkSwapchainCreateInfoKHR* pCreateInfo) : MVKVulkanAPIDeviceObject(device), _surfaceLost(false) {
 	_currentAcquisitionID = 0;
 
 	// If applicable, release any surfaces (not currently being displayed) from the old swapchain.
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
index 034d473..1504c92 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.h
@@ -44,6 +44,9 @@
 
 public:
 
+	/** Returns nil as this object has no need to track the Vulkan object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
+
 	/**
 	 * Adds a reservation to this semaphore, incrementing the reservation count.
 	 * Subsequent calls to a wait() function will block until a corresponding call
@@ -105,10 +108,13 @@
 #pragma mark MVKSemaphore
 
 /** Represents a Vulkan semaphore. */
-class MVKSemaphore : public MVKRefCountedDeviceObject {
+class MVKSemaphore : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_SEMAPHORE_EXT; }
+
 	/** 
 	 * Blocks processing on the current thread until this semaphore is 
 	 * signaled, or until the specified timeout in nanoseconds expires.
@@ -146,10 +152,13 @@
 #pragma mark MVKFence
 
 /** Represents a Vulkan fence. */
-class MVKFence : public MVKRefCountedDeviceObject {
+class MVKFence : public MVKVulkanAPIDeviceObject {
 
 public:
 
+	/** Returns the debug report object type of this object. */
+	VkDebugReportObjectTypeEXT getVkDebugReportObjectType() override { return VK_DEBUG_REPORT_OBJECT_TYPE_FENCE_EXT; }
+
 	/**
 	 * If this fence has not been signaled yet, adds the specified fence sitter to the
 	 * internal list of fence sitters that will be notified when this fence is signaled,
@@ -180,7 +189,7 @@
 #pragma mark Construction
 
     MVKFence(MVKDevice* device, const VkFenceCreateInfo* pCreateInfo) :
-	MVKRefCountedDeviceObject(device), _isSignaled(mvkAreFlagsEnabled(pCreateInfo->flags, VK_FENCE_CREATE_SIGNALED_BIT)) {}
+		MVKVulkanAPIDeviceObject(device), _isSignaled(mvkAreFlagsEnabled(pCreateInfo->flags, VK_FENCE_CREATE_SIGNALED_BIT)) {}
 
 protected:
 	void notifySitters();
@@ -199,6 +208,9 @@
 
 public:
 
+	/** This is a temporarily instantiated helper class. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; }
+
 	/**
 	 * If this instance has been configured to wait for fences, blocks processing on the
 	 * current thread until any or all of the fences that this instance is waiting for are
@@ -236,7 +248,8 @@
  * Blocks the current thread until any or all of the specified 
  * fences have been signaled, or the specified timeout occurs.
  */
-VkResult mvkWaitForFences(uint32_t fenceCount,
+VkResult mvkWaitForFences(MVKDevice* device,
+						  uint32_t fenceCount,
 						  const VkFence* pFences,
 						  VkBool32 waitAll,
 						  uint64_t timeout = UINT64_MAX);
@@ -251,17 +264,20 @@
  *
  * Instances of this class are one-shot, and can only be used for a single compilation.
  */
-class MVKMetalCompiler : public MVKBaseDeviceObject {
+class MVKMetalCompiler : public MVKBaseObject {
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _owner->getVulkanAPIObject(); };
+
 	/** If this object is waiting for compilation to complete, deletion will be deferred until then. */
 	void destroy() override;
 
 
 #pragma mark Construction
 
-	MVKMetalCompiler(MVKDevice* device) : MVKBaseDeviceObject(device) {}
+	MVKMetalCompiler(MVKVulkanAPIDeviceObject* owner) : _owner(owner) {}
 
 	~MVKMetalCompiler() override;
 
@@ -271,6 +287,7 @@
 	bool endCompile(NSError* compileError);
 	bool markDestroyed();
 
+	MVKVulkanAPIDeviceObject* _owner;
 	NSError* _compileError = nil;
 	uint64_t _startTime = 0;
 	bool _isCompileDone = false;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
index 94679d7..171df2a 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
@@ -18,6 +18,7 @@
 
 #include "MVKSync.h"
 #include "MVKFoundation.h"
+#include "MVKLogging.h"
 
 using namespace std;
 
@@ -76,7 +77,7 @@
 
 bool MVKSemaphore::wait(uint64_t timeout) {
 	bool isDone = _blocker.wait(timeout, true);
-	if ( !isDone && timeout > 0 ) { mvkNotifyErrorWithText(VK_TIMEOUT, "Vulkan semaphore timeout after %llu nanoseconds.", timeout); }
+	if ( !isDone && timeout > 0 ) { reportError(VK_TIMEOUT, "Vulkan semaphore timeout after %llu nanoseconds.", timeout); }
 	return isDone;
 }
 
@@ -94,7 +95,7 @@
 }
 
 MVKSemaphore::MVKSemaphore(MVKDevice* device, const VkSemaphoreCreateInfo* pCreateInfo)
-    : MVKRefCountedDeviceObject(device), _blocker(false, 1), _mtlEvent(nil), _mtlEventValue(1) {
+    : MVKVulkanAPIDeviceObject(device), _blocker(false, 1), _mtlEvent(nil), _mtlEventValue(1) {
 
     if (device->_pMetalFeatures->events) {
         _mtlEvent = [device->getMTLDevice() newEvent];
@@ -165,7 +166,8 @@
 }
 
 // Create a blocking fence sitter, add it to each fence, wait, then remove it.
-VkResult mvkWaitForFences(uint32_t fenceCount,
+VkResult mvkWaitForFences(MVKDevice* device,
+						  uint32_t fenceCount,
 						  const VkFence* pFences,
 						  VkBool32 waitAll,
 						  uint64_t timeout) {
@@ -180,7 +182,7 @@
 	if ( !fenceSitter.wait(timeout) ) {
 		rslt = VK_TIMEOUT;
 		if (timeout > 0) {
-			mvkNotifyErrorWithText(rslt, "Vulkan fence timeout after %llu nanoseconds.", timeout);
+			device->reportError(rslt, "Vulkan fence timeout after %llu nanoseconds.", timeout);
 		}
 	}
 
@@ -202,12 +204,14 @@
 // over a second to return when a compiler failure occurs!
 void MVKMetalCompiler::compile(unique_lock<mutex>& lock, dispatch_block_t block) {
 	MVKAssert( _startTime == 0, "%s compile occurred already in this instance. Instances of %s should only be used for a single compile activity.", _compilerType.c_str(), getClassName().c_str());
-	_startTime = _device->getPerformanceTimestamp();
+
+	MVKDevice* mvkDev = _owner->getDevice();
+	_startTime = mvkDev->getPerformanceTimestamp();
 
 	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
 
 	// Limit timeout to avoid overflow since wait_for() uses wait_until()
-	chrono::nanoseconds nanoTimeout(min(_device->_pMVKConfig->metalCompileTimeout, kMVKUndefinedLargeUInt64));
+	chrono::nanoseconds nanoTimeout(min(mvkDev->_pMVKConfig->metalCompileTimeout, kMVKUndefinedLargeUInt64));
 	_blocker.wait_for(lock, nanoTimeout, [this]{ return _isCompileDone; });
 
 	if ( !_isCompileDone ) {
@@ -217,14 +221,14 @@
 
 	if (_compileError) { handleError(); }
 
-	_device->addActivityPerformance(*_pPerformanceTracker, _startTime);
+	mvkDev->addActivityPerformance(*_pPerformanceTracker, _startTime);
 }
 
 void MVKMetalCompiler::handleError() {
-	setConfigurationResult(mvkNotifyErrorWithText(VK_ERROR_INITIALIZATION_FAILED,
-												  "%s compile failed (Error code %li):\n%s.",
-												  _compilerType.c_str(), (long)_compileError.code,
-												  _compileError.localizedDescription.UTF8String));
+	_owner->setConfigurationResult(reportError(VK_ERROR_INITIALIZATION_FAILED,
+											   "%s compile failed (Error code %li):\n%s.",
+											   _compilerType.c_str(), (long)_compileError.code,
+											   _compileError.localizedDescription.UTF8String));
 }
 
 // Returns whether the compilation came in late, after the compiler was destroyed.
@@ -236,7 +240,7 @@
 }
 
 void MVKMetalCompiler::destroy() {
-	if (markDestroyed()) { MVKBaseDeviceObject::destroy(); }
+	if (markDestroyed()) { MVKBaseObject::destroy(); }
 }
 
 // Marks this object as destroyed, and returns whether the compilation is complete.
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.cpp b/MoltenVK/MoltenVK/Layers/MVKExtensions.cpp
index 761d6ab..d39ccb2 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.cpp
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.cpp
@@ -19,6 +19,7 @@
 #include "MVKExtensions.h"
 #include "MVKFoundation.h"
 #include "MVKOSExtensions.h"
+#include "MVKEnvironment.h"
 #include "vk_mvk_moltenvk.h"
 #include <vulkan/vulkan_ios.h>
 #include <vulkan/vulkan_macos.h>
@@ -26,6 +27,9 @@
 using namespace std;
 
 
+#pragma mark -
+#pragma mark MVKExtension
+
 // Returns a VkExtensionProperties struct populated with a name and version
 static VkExtensionProperties mvkMakeExtProps(const char* extensionName, uint32_t specVersion) {
 	VkExtensionProperties extProps;
@@ -40,11 +44,62 @@
 static VkExtensionProperties kVkExtProps_ ##EXT = mvkMakeExtProps(VK_ ##EXT ##_EXTENSION_NAME, VK_ ##EXT ##_SPEC_VERSION);
 #include "MVKExtensions.def"
 
-MVKExtensionList::MVKExtensionList(bool enableForPlatform) :
+// Returns whether the specified properties are valid for this platform
+static bool mvkIsSupportedOnPlatform(VkExtensionProperties* pProperties) {
+#if !(MVK_IOS)
+	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
+		return mvkOSVersion() >= 10.13;
+	}
+	if (pProperties == &kVkExtProps_MVK_IOS_SURFACE) { return false; }
+	if (pProperties == &kVkExtProps_IMG_FORMAT_PVRTC) { return false; }
+#endif
+#if !(MVK_MACOS)
+	if (pProperties == &kVkExtProps_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE) { return false; }
+	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
+		return mvkOSVersion() >= 11.0;
+	}
+	if (pProperties == &kVkExtProps_MVK_MACOS_SURFACE) { return false; }
+#endif
+
+	return true;
+}
+
+// Disable by default unless asked to enable for platform and the extension is valid for this platform
+MVKExtension::MVKExtension(VkExtensionProperties* pProperties, bool enableForPlatform) {
+	this->pProperties = pProperties;
+	this->enabled = enableForPlatform && mvkIsSupportedOnPlatform(pProperties);
+}
+
+
+#pragma mark -
+#pragma mark MVKExtensionList
+
+MVKExtensionList::MVKExtensionList(MVKVulkanAPIObject* apiObject, bool enableForPlatform) : _apiObject(apiObject),
 #define MVK_EXTENSION_LAST(var, EXT)	vk_ ##var(&kVkExtProps_ ##EXT, enableForPlatform)
 #define MVK_EXTENSION(var, EXT)			MVK_EXTENSION_LAST(var, EXT),
 #include "MVKExtensions.def"
-{}
+{
+	initCount();
+}
+
+// We can't determine size of annonymous struct, and can't rely on size of this class, since
+// it can contain additional member variables. So we need to explicitly count the extensions.
+void MVKExtensionList::initCount() {
+	_count = 0;
+
+#define MVK_EXTENSION(var, EXT) _count++;
+#include "MVKExtensions.def"
+}
+
+uint32_t MVKExtensionList::getEnabledCount() const {
+	uint32_t enabledCnt = 0;
+	uint32_t extnCnt = getCount();
+	const MVKExtension* extnAry = &extensionArray;
+	for (uint32_t extnIdx = 0; extnIdx < extnCnt; extnIdx++) {
+		if (extnAry[extnIdx].enabled) { enabledCnt++; }
+	}
+	return enabledCnt;
+}
 
 bool MVKExtensionList::isEnabled(const char* extnName) const {
 	if ( !extnName ) { return false; }
@@ -77,7 +132,7 @@
 	for (uint32_t i = 0; i < count; i++) {
 		auto extnName = names[i];
 		if (parent && !parent->isEnabled(extnName)) {
-			result = mvkNotifyErrorWithText(VK_ERROR_EXTENSION_NOT_PRESENT, "Vulkan extension %s is not supported.", extnName);
+			result = reportError(VK_ERROR_EXTENSION_NOT_PRESENT, "Vulkan extension %s is not supported.", extnName);
 		} else {
 			enable(extnName);
 		}
@@ -102,29 +157,3 @@
 	}
 	return logMsg;
 }
-
-// Returns whether the specified properties are valid for this platform
-static bool mvkIsSupportedOnPlatform(VkExtensionProperties* pProperties) {
-#if !(MVK_IOS)
-	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
-		return mvkOSVersion() >= 10.13;
-	}
-	if (pProperties == &kVkExtProps_MVK_IOS_SURFACE) { return false; }
-	if (pProperties == &kVkExtProps_IMG_FORMAT_PVRTC) { return false; }
-#endif
-#if !(MVK_MACOS)
-	if (pProperties == &kVkExtProps_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE) { return false; }
-	if (pProperties == &kVkExtProps_EXT_MEMORY_BUDGET) {
-		return mvkOSVersion() >= 11.0;
-	}
-	if (pProperties == &kVkExtProps_MVK_MACOS_SURFACE) { return false; }
-#endif
-
-	return true;
-}
-
-// Disable by default unless asked to enable for platform and the extension is valid for this platform
-MVKExtension::MVKExtension(VkExtensionProperties* pProperties, bool enableForPlatform) {
-	this->pProperties = pProperties;
-	this->enabled = enableForPlatform && mvkIsSupportedOnPlatform(pProperties);
-}
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.def b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
index 885b84d..764db98 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.def
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.def
@@ -52,6 +52,7 @@
 MVK_EXTENSION(KHR_swapchain, KHR_SWAPCHAIN)
 MVK_EXTENSION(KHR_swapchain_mutable_format, KHR_SWAPCHAIN_MUTABLE_FORMAT)
 MVK_EXTENSION(KHR_variable_pointers, KHR_VARIABLE_POINTERS)
+MVK_EXTENSION(EXT_debug_report, EXT_DEBUG_REPORT)
 MVK_EXTENSION(EXT_host_query_reset, EXT_HOST_QUERY_RESET)
 MVK_EXTENSION(EXT_memory_budget, EXT_MEMORY_BUDGET)
 MVK_EXTENSION(EXT_shader_viewport_index_layer, EXT_SHADER_VIEWPORT_INDEX_LAYER)
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.h b/MoltenVK/MoltenVK/Layers/MVKExtensions.h
index 77d4dde..ae4379e 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.h
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.h
@@ -18,9 +18,13 @@
 
 #pragma once
 
-#include "mvk_vulkan.h"
+#include "MVKBaseObject.h"
 #include <string>
 
+
+#pragma mark -
+#pragma mark MVKExtension
+
 /** Describes a Vulkan extension and whether or not it is enabled or supported. */
 struct MVKExtension {
 	bool enabled = false;
@@ -29,13 +33,23 @@
 	MVKExtension(VkExtensionProperties* pProperties, bool enableForPlatform = false);
 };
 
+
+#pragma mark -
+#pragma mark MVKExtensionList
+
 /**
  * A fixed list of the Vulkan extensions known to MoltenVK, with
  * an indication of whether each extension is supported/enabled.
  *
  * To add support for a Vulkan extension, add a variable to this list.
  */
-struct MVKExtensionList {
+class MVKExtensionList : public MVKBaseObject {
+
+public:
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _apiObject->getVulkanAPIObject(); };
+
 	union {
 		struct {
 #define MVK_EXTENSION(var, EXT) MVKExtension vk_ ##var;
@@ -45,7 +59,10 @@
 	};
 
 	/** Returns the total number of extensions that are tracked by this object. */
-	static uint32_t getCount() { return sizeof(MVKExtensionList) / sizeof(MVKExtension); }
+	uint32_t getCount() const { return _count; }
+
+	/** Returns the number of extensions that are enabled. */
+	uint32_t getEnabledCount() const;
 
 	/** Returns whether the named extension is enabled. */
 	bool isEnabled(const char* extnName) const;
@@ -68,6 +85,13 @@
 	 */
 	std::string enabledNamesString(const char* separator = " ", bool prefixFirstWithSeparator = false) const;
 
-	MVKExtensionList(bool enableForPlatform = false);
+	MVKExtensionList(MVKVulkanAPIObject* apiObject, bool enableForPlatform = false);
+
+protected:
+	void initCount();
+
+	MVKVulkanAPIObject* _apiObject;
+	uint32_t _count;
+
 };
 
diff --git a/MoltenVK/MoltenVK/Layers/MVKLayers.h b/MoltenVK/MoltenVK/Layers/MVKLayers.h
index 62a2b48..b93742a 100644
--- a/MoltenVK/MoltenVK/Layers/MVKLayers.h
+++ b/MoltenVK/MoltenVK/Layers/MVKLayers.h
@@ -26,10 +26,13 @@
 #pragma mark MVKLayer
 
 /** Represents a single Vulkan layer. */
-class MVKLayer : public MVKConfigurableObject {
+class MVKLayer : public MVKBaseObject {
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
+
 	/** Returns the name of this layer. */
 	const char* getName();
 
@@ -66,10 +69,13 @@
 #pragma mark MVKLayerManager
 
 /** Manages a set of Vulkan layers. */
-class MVKLayerManager : public MVKConfigurableObject {
+class MVKLayerManager : public MVKBaseObject {
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
+
 	/** Returns the driver layer. */
 	MVKLayer* getDriverLayer();
 
diff --git a/MoltenVK/MoltenVK/Layers/MVKLayers.mm b/MoltenVK/MoltenVK/Layers/MVKLayers.mm
index b6db84e..bbc5a79 100644
--- a/MoltenVK/MoltenVK/Layers/MVKLayers.mm
+++ b/MoltenVK/MoltenVK/Layers/MVKLayers.mm
@@ -60,7 +60,7 @@
 
 #pragma mark Object Creation
 
-MVKLayer::MVKLayer() : _supportedExtensions(true) {
+MVKLayer::MVKLayer() : _supportedExtensions(nullptr, true) {
 
 	// The core driver layer
 	memset(_layerProperties.layerName, 0, sizeof(_layerProperties.layerName));
@@ -116,7 +116,7 @@
 
 // Populate the layers
 MVKLayerManager::MVKLayerManager() {
-	_layers.push_back(MVKLayer());
+	_layers.emplace_back();
 }
 
 static mutex _lock;
diff --git a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
index 06b91f8..6316b91 100644
--- a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
@@ -17,8 +17,8 @@
  */
 
 
-#include "MVKCommonEnvironment.h"
 #include "CAMetalLayer+MoltenVK.h"
+#include "MVKEnvironment.h"
 
 @implementation CAMetalLayer (MoltenVK)
 
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m
index a4fd313..7017999 100644
--- a/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m
@@ -18,7 +18,7 @@
 
 
 #include "MTLRenderPassDescriptor+MoltenVK.h"
-#include "MVKCommonEnvironment.h"
+#include "MVKEnvironment.h"
 
 @implementation MTLRenderPassDescriptor (MoltenVK)
 
diff --git a/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m
index 745d0ba..b5dae5b 100644
--- a/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m
@@ -18,6 +18,7 @@
 
 
 #include "MTLSamplerDescriptor+MoltenVK.h"
+#include "MVKEnvironment.h"
 
 @implementation MTLSamplerDescriptor (MoltenVK)
 
diff --git a/MoltenVK/MoltenVK/OS/MVKGPUCapture.h b/MoltenVK/MoltenVK/OS/MVKGPUCapture.h
index 3bda589..1eb83be 100644
--- a/MoltenVK/MoltenVK/OS/MVKGPUCapture.h
+++ b/MoltenVK/MoltenVK/OS/MVKGPUCapture.h
@@ -18,12 +18,10 @@
 
 #pragma once
 
-#include "MVKDevice.h"
+#include "MVKQueue.h"
 
 #import <Metal/Metal.h>
 
-class MVKQueue;
-
 
 #pragma mark -
 #pragma mark MVKGPUCaptureScope
@@ -34,10 +32,13 @@
  * If the OS supports the MTLCaptureScope protocol, this class creates and wraps an MTLCaptureScope
  * instance for a MTLQueue, otherwise it interacts directly with the MTLQueue to define capture boundaries.
  */
-class MVKGPUCaptureScope : public MVKBaseDeviceObject {
+class MVKGPUCaptureScope : public MVKBaseObject {
 
 public:
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return _queue->getVulkanAPIObject(); };
+
 	/** Marks the beginning boundary of a capture scope. */
 	void beginScope();
 
@@ -58,6 +59,7 @@
 	~MVKGPUCaptureScope() override;
 
 protected:
+	MVKQueue* _queue;
 	id<MTLCaptureScope> _mtlCaptureScope = nil;
 	id<MTLCommandQueue> _mtlQueue = nil;
 	bool _isFirstBoundary = true;
diff --git a/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm b/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
index bd1d9f3..156a16e 100644
--- a/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
+++ b/MoltenVK/MoltenVK/OS/MVKGPUCapture.mm
@@ -19,6 +19,7 @@
 #include "MVKGPUCapture.h"
 #include "MVKQueue.h"
 #include "MVKOSExtensions.h"
+#include "MVKEnvironment.h"
 
 
 #pragma mark -
@@ -55,11 +56,11 @@
 	}
 }
 
-MVKGPUCaptureScope::MVKGPUCaptureScope(MVKQueue* mvkQueue, const char* purpose) : MVKBaseDeviceObject(mvkQueue->getDevice()) {
-	_mtlQueue = [mvkQueue->getMTLCommandQueue() retain];	// retained
+MVKGPUCaptureScope::MVKGPUCaptureScope(MVKQueue* mvkQueue, const char* purpose) : _queue(mvkQueue) {
+	_mtlQueue = [_queue->getMTLCommandQueue() retain];	// retained
 	if (mvkOSVersion() >= kMinOSVersionMTLCaptureScope) {
 		_mtlCaptureScope = [[MTLCaptureManager sharedCaptureManager] newCaptureScopeWithCommandQueue: _mtlQueue];	// retained
-		_mtlCaptureScope.label = @((mvkQueue->getName() + "-" + purpose).c_str());
+		_mtlCaptureScope.label = @((_queue->getName() + "-" + purpose).c_str());
 	}
 }
 
diff --git a/MoltenVK/MoltenVK/Utility/MVKBaseObject.cpp b/MoltenVK/MoltenVK/Utility/MVKBaseObject.cpp
index 0d67b9f..e8abb0c 100644
--- a/MoltenVK/MoltenVK/Utility/MVKBaseObject.cpp
+++ b/MoltenVK/MoltenVK/Utility/MVKBaseObject.cpp
@@ -17,12 +17,57 @@
  */
 
 #include "MVKBaseObject.h"
+#include "MVKInstance.h"
+#include "MVKFoundation.h"
+#include "MVKOSExtensions.h"
+#include "MVKLogging.h"
 #include <stdlib.h>
 #include <cxxabi.h>
 
 using namespace std;
 
 
+// The logging level
+// 0 = None
+// 1 = Errors only
+// 2 = All
+#ifndef MVK_CONFIG_LOG_LEVEL
+#   define MVK_CONFIG_LOG_LEVEL    2
+#endif
+
+static uint32_t _mvkLogLevel = MVK_CONFIG_LOG_LEVEL;
+
+// Initialize log level from environment
+static bool _mvkLoggingInitialized = false;
+__attribute__((constructor)) static void MVKInitLogging() {
+	if (_mvkLoggingInitialized ) { return; }
+	_mvkLoggingInitialized = true;
+
+	MVK_SET_FROM_ENV_OR_BUILD_INT32(_mvkLogLevel, MVK_CONFIG_LOG_LEVEL);
+}
+
+static const char* getReportingLevelString(int aslLvl) {
+	switch (aslLvl) {
+		case ASL_LEVEL_DEBUG:
+			return "mvk-debug";
+
+		case ASL_LEVEL_INFO:
+		case ASL_LEVEL_NOTICE:
+			return "mvk-info";
+
+		case ASL_LEVEL_WARNING:
+			return "mvk-warn";
+
+		case ASL_LEVEL_ERR:
+		case ASL_LEVEL_CRIT:
+		case ASL_LEVEL_ALERT:
+		case ASL_LEVEL_EMERG:
+		default:
+			return "mvk-error";
+	}
+}
+
+
 #pragma mark -
 #pragma mark MVKBaseObject
 
@@ -33,3 +78,125 @@
     free(demangled);
     return clzName;
 }
+
+void MVKBaseObject::reportMessage(int aslLvl, const char* format, ...) {
+	va_list args;
+	va_start(args, format);
+	reportMessage(this, aslLvl, format, args);
+	va_end(args);
+}
+
+void MVKBaseObject::reportMessage(MVKBaseObject* mvkObj, int aslLvl, const char* format, ...) {
+	va_list args;
+	va_start(args, format);
+	reportMessage(mvkObj, aslLvl, format, args);
+	va_end(args);
+}
+
+// This is the core reporting implementation. Other similar functions delegate here.
+void MVKBaseObject::reportMessage(MVKBaseObject* mvkObj, int aslLvl, const char* format, va_list args) {
+
+	MVKVulkanAPIObject* mvkAPIObj = mvkObj ? mvkObj->getVulkanAPIObject() : nullptr;
+	MVKInstance* mvkInst = mvkAPIObj ? mvkAPIObj->getInstance() : nullptr;
+	bool hasDebugReportCallbacks = mvkInst && mvkInst->hasDebugReportCallbacks();
+	bool shouldLog = (aslLvl < (_mvkLogLevel << 2));
+
+	// Fail fast to avoid further unnecessary processing.
+	if ( !(shouldLog || hasDebugReportCallbacks) ) { return; }
+
+	va_list origArgs, redoArgs;
+	va_copy(origArgs, args);
+	va_copy(redoArgs, args);
+
+	// Choose a buffer size suitable for most messages and attempt to write it out.
+	const int kOrigBuffSize = 2 * KIBI;
+	char origBuff[kOrigBuffSize];
+	char* pMessage = origBuff;
+	int msgLen = vsnprintf(origBuff, kOrigBuffSize, format, origArgs);
+
+	// If message is too big for original buffer, allocate a buffer big enough to hold it and
+	// write the message out again. We only want to do this double writing if we have to.
+	// Create the redoBuff outside scope of if block to allow it to be referencable by pMessage later below.
+	int redoBuffSize = (msgLen >= kOrigBuffSize) ? msgLen + 1 : 0;
+	char redoBuff[redoBuffSize];
+	if (redoBuffSize > 0) {
+		pMessage = redoBuff;
+		vsnprintf(redoBuff, redoBuffSize, format, redoArgs);
+	}
+
+	va_end(redoArgs);
+	va_end(origArgs);
+
+	// Log the message to the standard error stream
+	if (shouldLog) { fprintf(stderr, "[%s] %s\n", getReportingLevelString(aslLvl), pMessage); }
+
+	// Broadcast the message to any Vulkan debug report callbacks
+	if (hasDebugReportCallbacks) { mvkInst->debugReportMessage(mvkAPIObj, aslLvl, pMessage); }
+}
+
+VkResult MVKBaseObject::reportError(VkResult vkErr, const char* format, ...) {
+	va_list args;
+	va_start(args, format);
+	VkResult rslt = reportError(this, vkErr, format, args);
+	va_end(args);
+	return rslt;
+}
+
+VkResult MVKBaseObject::reportError(MVKBaseObject* mvkObj, VkResult vkErr, const char* format, ...) {
+	va_list args;
+	va_start(args, format);
+	VkResult rslt = reportError(mvkObj, vkErr, format, args);
+	va_end(args);
+	return rslt;
+}
+
+// This is the core reporting implementation. Other similar functions delegate here.
+VkResult MVKBaseObject::reportError(MVKBaseObject* mvkObj, VkResult vkErr, const char* format, va_list args) {
+
+	// Prepend the error code to the format string
+	const char* vkRsltName = mvkVkResultName(vkErr);
+	char fmtStr[strlen(vkRsltName) + strlen(format) + 4];
+	sprintf(fmtStr, "%s: %s", vkRsltName, format);
+
+	// Report the error
+	va_list lclArgs;
+	va_copy(lclArgs, args);
+	reportMessage(mvkObj, ASL_LEVEL_ERR, fmtStr, lclArgs);
+	va_end(lclArgs);
+
+	return vkErr;
+}
+
+
+#pragma mark -
+#pragma mark MVKVulkanAPIObject
+
+void MVKVulkanAPIObject::retain() {
+	lock_guard<mutex> lock(_refLock);
+
+	_refCount++;
+}
+
+void MVKVulkanAPIObject::release() {
+	if (decrementRetainCount()) { destroy(); }
+}
+
+void MVKVulkanAPIObject::destroy() {
+	if (markDestroyed()) { MVKConfigurableObject::destroy(); }
+}
+
+// Decrements the reference count, and returns whether it's time to destroy this object.
+bool MVKVulkanAPIObject::decrementRetainCount() {
+	lock_guard<mutex> lock(_refLock);
+
+	if (_refCount > 0) { _refCount--; }
+	return (_isDestroyed && _refCount == 0);
+}
+
+// Marks this object as destroyed, and returns whether no references are left outstanding.
+bool MVKVulkanAPIObject::markDestroyed() {
+	lock_guard<mutex> lock(_refLock);
+
+	_isDestroyed = true;
+	return _refCount == 0;
+}
diff --git a/MoltenVK/MoltenVK/Utility/MVKBaseObject.h b/MoltenVK/MoltenVK/Utility/MVKBaseObject.h
index e9abadf..ccdf4bc 100644
--- a/MoltenVK/MoltenVK/Utility/MVKBaseObject.h
+++ b/MoltenVK/MoltenVK/Utility/MVKBaseObject.h
@@ -21,6 +21,10 @@
 #include "mvk_vulkan.h"
 #include <vulkan/vk_icd.h>
 #include <string>
+#include <mutex>
+
+class MVKInstance;
+class MVKVulkanAPIObject;
 
 
 #pragma mark -
@@ -37,6 +41,59 @@
     /** Returns the name of the class of which this object is an instance. */
     std::string getClassName();
 
+	/** Returns the Vulkan API opaque object controlling this object. */
+	virtual MVKVulkanAPIObject* getVulkanAPIObject() = 0;
+
+	/**
+	 * Report a message. This includes logging to a standard system logging stream,
+	 * and some subclasses will also forward the message to their VkInstance for
+	 * output to the Vulkan debug report messaging API.
+	 */
+	void reportMessage(int aslLvl, const char* format, ...) __printflike(3, 4);
+
+	/**
+	 * Report a Vulkan error message, on behalf of the object, which may be nil.
+	 * Reporting includes logging to a standard system logging stream, and if the object
+	 * is not nil and has access to the VkInstance, the message will also be forwarded
+	 * to the VkInstance for output to the Vulkan debug report messaging API.
+	 */
+	static void reportMessage(MVKBaseObject* mvkObj, int aslLvl, const char* format, ...) __printflike(3, 4);
+
+	/**
+	 * Report a Vulkan error message, on behalf of the object, which may be nil.
+	 * Reporting includes logging to a standard system logging stream, and if the object
+	 * is not nil and has access to the VkInstance, the message will also be forwarded
+	 * to the VkInstance for output to the Vulkan debug report messaging API.
+	 *
+	 * This is the core reporting implementation. Other similar functions delegate here.
+	 */
+	static void reportMessage(MVKBaseObject* mvkObj, int aslLvl, const char* format, va_list args) __printflike(3, 0);
+
+	/**
+	 * Report a Vulkan error message. This includes logging to a standard system logging stream,
+	 * and some subclasses will also forward the message to their VkInstance for output to the
+	 * Vulkan debug report messaging API.
+	 */
+	VkResult reportError(VkResult vkErr, const char* format, ...) __printflike(3, 4);
+
+	/**
+	 * Report a Vulkan error message, on behalf of the object. which may be nil.
+	 * Reporting includes logging to a standard system logging stream, and if the object
+	 * is not nil and has access to the VkInstance, the message will also be forwarded
+	 * to the VkInstance for output to the Vulkan debug report messaging API.
+	 */
+	static VkResult reportError(MVKBaseObject* mvkObj, VkResult vkErr, const char* format, ...) __printflike(3, 4);
+
+	/**
+	 * Report a Vulkan error message, on behalf of the object. which may be nil.
+	 * Reporting includes logging to a standard system logging stream, and if the object
+	 * is not nil and has access to the VkInstance, the message will also be forwarded
+	 * to the VkInstance for output to the Vulkan debug report messaging API.
+	 *
+	 * This is the core reporting implementation. Other similar functions delegate here.
+	 */
+	static VkResult reportError(MVKBaseObject* mvkObj, VkResult vkErr, const char* format, va_list args) __printflike(3, 0);
+
 	/** Destroys this object. Default behaviour simply deletes it. Subclasses may override to delay deletion. */
 	virtual void destroy() { delete this; }
 
@@ -72,14 +129,83 @@
 
 
 #pragma mark -
-#pragma mark MVKDispatchableObject
+#pragma mark MVKVulkanAPIObject
 
-/** Abstract class that represents an object that can be used as a Vulkan API dispatchable object. */
-class MVKDispatchableObject : public MVKConfigurableObject {
+/**
+ * Abstract class that represents an opaque Vulkan API handle object.
+ *
+ * API objects can sometimes be destroyed by the client before the GPU is done with them.
+ * To support this, an object of this type will automatically be deleted iff it has been
+ * destroyed by the client, and all references have been released. An object of this type
+ * is therefore allowed to live past its destruction by the client, until it is no longer
+ * referenced by other objects.
+ */
+class MVKVulkanAPIObject : public MVKConfigurableObject {
+
+public:
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return this; };
+
+	/** Returns a reference to this object suitable for use as a Vulkan API handle. */
+	virtual void* getVkHandle() { return this; }
+
+	/** Returns the debug report object type of this object. */
+	virtual VkDebugReportObjectTypeEXT getVkDebugReportObjectType() = 0;
+
+	/** Returns the Vulkan instance. */
+	virtual MVKInstance* getInstance() = 0;
+
+	/**
+	 * Called when this instance has been retained as a reference by another object,
+	 * indicating that this instance will not be deleted until that reference is released.
+	 */
+	void retain();
+
+	/**
+	 * Called when this instance has been released as a reference from another object.
+	 * Once all references have been released, this object is free to be deleted.
+	 * If the destroy() function has already been called on this instance by the time
+	 * this function is called, this instance will be deleted.
+	 */
+	void release();
+
+	/**
+	 * Marks this instance as destroyed. If all previous references to this instance
+	 * have been released, this instance will be deleted, otherwise deletion of this
+	 * instance will automatically be deferred until all references have been released.
+	 */
+	void destroy() override;
+
+	/** Construct an empty instance. Declared here to support copy constructor. */
+	MVKVulkanAPIObject() {}
+
+	/**
+	 * Construct an instance from a copy. Default copy constructor disallowed due to mutex.
+	 * Copies start with fresh reference counts.
+	 */
+	MVKVulkanAPIObject(const MVKVulkanAPIObject& other) {}
+
+protected:
+
+	bool decrementRetainCount();
+	bool markDestroyed();
+
+	std::mutex _refLock;
+	unsigned _refCount = 0;
+	bool _isDestroyed = false;
+};
+
+
+#pragma mark -
+#pragma mark MVKDispatchableVulkanAPIObject
+
+/** Abstract class that represents a dispatchable opaque Vulkan API handle object. */
+class MVKDispatchableVulkanAPIObject : public MVKVulkanAPIObject {
 
     typedef struct {
         VK_LOADER_DATA loaderData;
-        MVKDispatchableObject* mvkObject;
+        MVKDispatchableVulkanAPIObject* mvkObject;
     } MVKDispatchableObjectICDRef;
 
 public:
@@ -88,13 +214,13 @@
      * Returns a reference to this object suitable for use as a Vulkan API handle.
      * This is the compliment of the getDispatchableObject() method.
      */
-    inline void* getVkHandle() { return &_icdRef; }
+    void* getVkHandle() override { return &_icdRef; }
 
     /**
-     * Retrieves the MVKDispatchableObject instance referenced by the dispatchable Vulkan handle.
+     * Retrieves the MVKDispatchableVulkanAPIObject instance referenced by the dispatchable Vulkan handle.
      * This is the compliment of the getVkHandle() method.
      */
-    static inline MVKDispatchableObject* getDispatchableObject(void* vkHandle) {
+    static inline MVKDispatchableVulkanAPIObject* getDispatchableObject(void* vkHandle) {
 		return vkHandle ? ((MVKDispatchableObjectICDRef*)vkHandle)->mvkObject : nullptr;
     }
 
diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
index 19a6819..f593530 100644
--- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
+++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
@@ -20,8 +20,20 @@
 #pragma once
 
 #include "MVKCommonEnvironment.h"
+#include "mvk_vulkan.h"
 
 
+// Expose MoltenVK Apple surface extension functionality
+#ifdef VK_USE_PLATFORM_IOS_MVK
+#	define vkCreate_PLATFORM_SurfaceMVK			vkCreateIOSSurfaceMVK
+#	define Vk_PLATFORM_SurfaceCreateInfoMVK		VkIOSSurfaceCreateInfoMVK
+#endif
+
+#ifdef VK_USE_PLATFORM_MACOS_MVK
+#	define vkCreate_PLATFORM_SurfaceMVK			vkCreateMacOSSurfaceMVK
+#	define Vk_PLATFORM_SurfaceCreateInfoMVK		VkMacOSSurfaceCreateInfoMVK
+#endif
+
 /** Macro to determine the Vulkan version supported by MoltenVK. */
 #define MVK_VULKAN_API_VERSION		VK_MAKE_VERSION(VK_VERSION_MAJOR(VK_API_VERSION_1_0),	\
 													VK_VERSION_MINOR(VK_API_VERSION_1_0),	\
diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp b/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp
index 276df6f..a6f699c 100644
--- a/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp
+++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.cpp
@@ -17,7 +17,6 @@
  */
 
 #include "MVKFoundation.h"
-#include "MVKLogging.h"
 
 
 #define CASE_STRINGIFY(V)  case V: return #V
@@ -75,23 +74,6 @@
 	}
 }
 
-VkResult mvkNotifyErrorWithText(VkResult vkErr, const char* errFmt, ...) {
-	va_list args;
-	va_start(args, errFmt);
-
-	// Prepend the error code to the format string
-	const char* vkRsltName = mvkVkResultName(vkErr);
-	char fmtStr[strlen(vkRsltName) + strlen(errFmt) + 4];
-	sprintf(fmtStr, "%s: %s", vkRsltName, errFmt);
-
-	// Log the error
-	MVKLogImplV(true, !(MVK_DEBUG), ASL_LEVEL_ERR, "***MoltenVK ERROR***", fmtStr, args);
-
-	va_end(args);
-
-	return vkErr;
-}
-
 
 #pragma mark -
 #pragma mark Alignment functions
diff --git a/MoltenVK/MoltenVK/Utility/MVKFoundation.h b/MoltenVK/MoltenVK/Utility/MVKFoundation.h
index 472e9a3..00b0b85 100644
--- a/MoltenVK/MoltenVK/Utility/MVKFoundation.h
+++ b/MoltenVK/MoltenVK/Utility/MVKFoundation.h
@@ -21,7 +21,6 @@
 
 
 #include "mvk_vulkan.h"
-#include "MVKLogging.h"
 #include <algorithm>
 #include <string>
 #include <simd/simd.h>
@@ -138,13 +137,6 @@
 	return verStr;
 }
 
-/**
- * Notifies the app of an error code and error message, via the following methods:
- *
- * - Logs the error code and message to the console
- */
-VkResult mvkNotifyErrorWithText(VkResult vkErr, const char* errFmt, ...) __printflike(2, 3);
-
 
 #pragma mark -
 #pragma mark Alignment functions
@@ -204,7 +196,7 @@
 static inline uintptr_t mvkAlignByteRef(uintptr_t byteRef, uintptr_t byteAlignment, bool alignDown = false) {
 	if (byteAlignment == 0) { return byteRef; }
 
-	MVKAssert(mvkIsPowerOfTwo(byteAlignment), "Byte alignment %lu is not a power-of-two value.", byteAlignment);
+	assert(mvkIsPowerOfTwo(byteAlignment));
 
 	uintptr_t mask = byteAlignment - 1;
 	uintptr_t alignedRef = (byteRef + mask) & ~mask;
diff --git a/MoltenVK/MoltenVK/Utility/MVKWatermark.h b/MoltenVK/MoltenVK/Utility/MVKWatermark.h
index 6836c03..b1b3028 100644
--- a/MoltenVK/MoltenVK/Utility/MVKWatermark.h
+++ b/MoltenVK/MoltenVK/Utility/MVKWatermark.h
@@ -58,6 +58,10 @@
 
 public:
 
+
+	/** Returns the Vulkan API opaque object controlling this object. */
+	MVKVulkanAPIObject* getVulkanAPIObject() override { return nullptr; };
+
     /** Sets the clip-space position (0.0 - 1.0) of this watermark. */
     void setPosition(MVKWatermarkPosition position);
 
diff --git a/MoltenVK/MoltenVK/Utility/MVKWatermark.mm b/MoltenVK/MoltenVK/Utility/MVKWatermark.mm
index a87897a..db7740c 100644
--- a/MoltenVK/MoltenVK/Utility/MVKWatermark.mm
+++ b/MoltenVK/MoltenVK/Utility/MVKWatermark.mm
@@ -21,6 +21,7 @@
 #include "MVKOSExtensions.h"
 #include "MVKLogging.h"
 #include "MTLTextureDescriptor+MoltenVK.h"
+#include "MVKEnvironment.h"
 
 
 /** The structure to hold shader uniforms. */
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
new file mode 100644
index 0000000..6cc918f
--- /dev/null
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
@@ -0,0 +1,78 @@
+/*
+ * mvk_datatypes.hpp
+ *
+ * Copyright (c) 2014-2019 The Brenwill Workshop Ltd. (http://www.brenwill.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mvkDataTypes_hpp_
+#define __mvkDataTypes_hpp_ 1
+
+
+#include "mvk_datatypes.h"
+
+class MVKBaseObject;
+
+/*
+ * This header file should be used internally within MoltenVK in place of mvk_datatypes.h,
+ * which is part of the public external MoltenVK C API.
+ */
+
+
+#pragma mark -
+#pragma mark Support for VK_EXT_debug_report extension
+
+/*
+ * The following function declarations are variations of functions declared in mvk_datatypes.h.
+ *
+ * Each function variation declared here accepts an MVKBaseObject instance, which, if not nil,
+ * allows calls to MVKBaseObject::reportError() to be made from within these functions to perform
+ * debug report callbacks in support of the VK_EXT_debug_report extension.
+ *
+ * The original functions in mvk_datatypes.h are redefined here to redirect to the equivalent
+ * functions declared here, passing the calling instance, which is assumed to be an instance
+ * of an MVKBaseObject subclass, which is true for all but static calling functions.
+ */
+
+MTLPixelFormat mvkMTLPixelFormatFromVkFormatInObj(VkFormat vkFormat, MVKBaseObject* mvkObj);
+#define mvkMTLPixelFormatFromVkFormat(vkFormat)	mvkMTLPixelFormatFromVkFormatInObj(vkFormat, this)
+
+MTLVertexFormat mvkMTLVertexFormatFromVkFormatInObj(VkFormat vkFormat, MVKBaseObject* mvkObj);
+#define mvkMTLVertexFormatFromVkFormat(vkFormat) mvkMTLVertexFormatFromVkFormatInObj(vkFormat, this)
+
+MTLPrimitiveType mvkMTLPrimitiveTypeFromVkPrimitiveTopologyInObj(VkPrimitiveTopology vkTopology, MVKBaseObject* mvkObj);
+#define mvkMTLPrimitiveTypeFromVkPrimitiveTopology(vkTopology) mvkMTLPrimitiveTypeFromVkPrimitiveTopologyInObj(vkTopology, this)
+
+MTLPrimitiveTopologyClass mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopologyInObj(VkPrimitiveTopology vkTopology, MVKBaseObject* mvkObj);
+#define mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopology(vkTopology) mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopologyInObj(vkTopology, this)
+
+MTLTriangleFillMode mvkMTLTriangleFillModeFromVkPolygonModeInObj(VkPolygonMode vkFillMode, MVKBaseObject* mvkObj);
+#define mvkMTLTriangleFillModeFromVkPolygonMode(vkFillMode) mvkMTLTriangleFillModeFromVkPolygonModeInObj(vkFillMode, this)
+
+MTLLoadAction mvkMTLLoadActionFromVkAttachmentLoadOpInObj(VkAttachmentLoadOp vkLoadOp, MVKBaseObject* mvkObj);
+#define mvkMTLLoadActionFromVkAttachmentLoadOp(vkLoadOp) mvkMTLLoadActionFromVkAttachmentLoadOpInObj(vkLoadOp, this)
+
+MTLStoreAction mvkMTLStoreActionFromVkAttachmentStoreOpInObj(VkAttachmentStoreOp vkStoreOp, bool hasResolveAttachment, MVKBaseObject* mvkObj);
+#define mvkMTLStoreActionFromVkAttachmentStoreOp(vkStoreOp, hasResolveAttachment) mvkMTLStoreActionFromVkAttachmentStoreOpInObj(vkStoreOp, hasResolveAttachment, this)
+
+MVKShaderStage mvkShaderStageFromVkShaderStageFlagBitsInObj(VkShaderStageFlagBits vkStage, MVKBaseObject* mvkObj);
+#define mvkShaderStageFromVkShaderStageFlagBits(vkStage) mvkShaderStageFromVkShaderStageFlagBitsInObj(vkStage, this)
+
+MTLWinding mvkMTLWindingFromSpvExecutionModeInObj(uint32_t spvMode, MVKBaseObject* mvkObj);
+#define mvkMTLWindingFromSpvExecutionMode(spvMode) mvkMTLWindingFromSpvExecutionModeInObj(spvMode, this)
+
+MTLTessellationPartitionMode mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(uint32_t spvMode, MVKBaseObject* mvkObj);
+#define mvkMTLTessellationPartitionModeFromSpvExecutionMode(spvMode) mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(spvMode, this)
+
+#endif
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
index b6a764c..329fb6d 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
@@ -17,10 +17,10 @@
  */
 
 #include "MVKEnvironment.h"
-#include "mvk_datatypes.h"
+#include "mvk_datatypes.hpp"
 #include "MVKFoundation.h"
 #include "MVKOSExtensions.h"
-#include "MVKLogging.h"
+#include "MVKBaseObject.h"
 #include <MoltenVKSPIRVToMSLConverter/SPIRVReflection.h>
 #include <unordered_map>
 #include <string>
@@ -489,8 +489,8 @@
  * Populates the lookup maps that map Vulkan and Metal pixel formats to one-another.
  *
  * Because both Metal and core Vulkan format value are enumerations that start at zero and are 
- * more or less consecutively enumerated, we can use a simple lookup array in each direction 
- * to map the value in one architecture (as an array index) to the corresponding value in the 
+ * more or less consecutively enumerated, we can use a simple lookup array in each direction
+ * to map the value in one architecture (as an array index) to the corresponding value in the
  * other architecture. Values that exist in one API but not the other are given a default value.
  *
  * Vulkan extension formats have very large values, and are tracked in a separate map.
@@ -563,7 +563,12 @@
 	return formatDescForMTLPixelFormat(mtlFormat).formatType;
 }
 
+#undef mvkMTLPixelFormatFromVkFormat
 MVK_PUBLIC_SYMBOL MTLPixelFormat mvkMTLPixelFormatFromVkFormat(VkFormat vkFormat) {
+	return mvkMTLPixelFormatFromVkFormatInObj(vkFormat, nullptr);
+}
+
+MTLPixelFormat mvkMTLPixelFormatFromVkFormatInObj(VkFormat vkFormat, MVKBaseObject* mvkObj) {
     MTLPixelFormat mtlPixFmt = MTLPixelFormatInvalid;
 
     const MVKFormatDesc& fmtDesc = formatDescForVkFormat(vkFormat);
@@ -585,7 +590,7 @@
             errMsg += (fmtDescSubs.vkName) ? fmtDescSubs.vkName : to_string(fmtDescSubs.vk);
             errMsg += " instead.";
         }
-		mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
+		MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
     }
 
     return mtlPixFmt;
@@ -660,7 +665,12 @@
     return formatDescForMTLPixelFormat(mtlFormat).mtlName;
 }
 
+#undef mvkMTLVertexFormatFromVkFormat
 MVK_PUBLIC_SYMBOL MTLVertexFormat mvkMTLVertexFormatFromVkFormat(VkFormat vkFormat) {
+	return mvkMTLVertexFormatFromVkFormatInObj(vkFormat, nullptr);
+}
+
+MTLVertexFormat mvkMTLVertexFormatFromVkFormatInObj(VkFormat vkFormat, MVKBaseObject* mvkObj) {
     MTLVertexFormat mtlVtxFmt = MTLVertexFormatInvalid;
 
     const MVKFormatDesc& fmtDesc = formatDescForVkFormat(vkFormat);
@@ -682,7 +692,7 @@
             errMsg += (fmtDescSubs.vkName) ? fmtDescSubs.vkName : to_string(fmtDescSubs.vk);
             errMsg += " instead.";
         }
-		mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
+		MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "%s", errMsg.c_str());
     }
 
     return mtlVtxFmt;
@@ -1046,7 +1056,12 @@
 	}
 }
 
+#undef mvkMTLPrimitiveTypeFromVkPrimitiveTopology
 MVK_PUBLIC_SYMBOL MTLPrimitiveType mvkMTLPrimitiveTypeFromVkPrimitiveTopology(VkPrimitiveTopology vkTopology) {
+	return mvkMTLPrimitiveTypeFromVkPrimitiveTopologyInObj(vkTopology, nullptr);
+}
+
+MTLPrimitiveType mvkMTLPrimitiveTypeFromVkPrimitiveTopologyInObj(VkPrimitiveTopology vkTopology, MVKBaseObject* mvkObj) {
 	switch (vkTopology) {
 		case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
 			return MTLPrimitiveTypePoint;
@@ -1070,12 +1085,17 @@
 
 		case VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN:
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "VkPrimitiveTopology value %d is not supported for rendering.", vkTopology);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkPrimitiveTopology value %d is not supported for rendering.", vkTopology);
 			return MTLPrimitiveTypePoint;
 	}
 }
 
+#undef mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopology
 MVK_PUBLIC_SYMBOL MTLPrimitiveTopologyClass mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopology(VkPrimitiveTopology vkTopology) {
+	return mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopologyInObj(vkTopology, nullptr);
+}
+
+MTLPrimitiveTopologyClass mvkMTLPrimitiveTopologyClassFromVkPrimitiveTopologyInObj(VkPrimitiveTopology vkTopology, MVKBaseObject* mvkObj) {
 	switch (vkTopology) {
 		case VK_PRIMITIVE_TOPOLOGY_POINT_LIST:
 			return MTLPrimitiveTopologyClassPoint;
@@ -1095,12 +1115,17 @@
 			return MTLPrimitiveTopologyClassTriangle;
 
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "VkPrimitiveTopology value %d is not supported for render pipelines.", vkTopology);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkPrimitiveTopology value %d is not supported for render pipelines.", vkTopology);
 			return MTLPrimitiveTopologyClassUnspecified;
 	}
 }
 
+#undef mvkMTLTriangleFillModeFromVkPolygonMode
 MVK_PUBLIC_SYMBOL MTLTriangleFillMode mvkMTLTriangleFillModeFromVkPolygonMode(VkPolygonMode vkFillMode) {
+	return mvkMTLTriangleFillModeFromVkPolygonModeInObj(vkFillMode, nullptr);
+}
+
+MTLTriangleFillMode mvkMTLTriangleFillModeFromVkPolygonModeInObj(VkPolygonMode vkFillMode, MVKBaseObject* mvkObj) {
 	switch (vkFillMode) {
 		case VK_POLYGON_MODE_FILL:
 		case VK_POLYGON_MODE_POINT:
@@ -1110,30 +1135,40 @@
 			return MTLTriangleFillModeLines;
 
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "VkPolygonMode value %d is not supported for render pipelines.", vkFillMode);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkPolygonMode value %d is not supported for render pipelines.", vkFillMode);
 			return MTLTriangleFillModeFill;
 	}
 }
 
+#undef mvkMTLLoadActionFromVkAttachmentLoadOp
 MVK_PUBLIC_SYMBOL MTLLoadAction mvkMTLLoadActionFromVkAttachmentLoadOp(VkAttachmentLoadOp vkLoadOp) {
+	return mvkMTLLoadActionFromVkAttachmentLoadOpInObj(vkLoadOp, nullptr);
+}
+
+MTLLoadAction mvkMTLLoadActionFromVkAttachmentLoadOpInObj(VkAttachmentLoadOp vkLoadOp, MVKBaseObject* mvkObj) {
 	switch (vkLoadOp) {
 		case VK_ATTACHMENT_LOAD_OP_LOAD:		return MTLLoadActionLoad;
 		case VK_ATTACHMENT_LOAD_OP_CLEAR:		return MTLLoadActionClear;
 		case VK_ATTACHMENT_LOAD_OP_DONT_CARE:	return MTLLoadActionDontCare;
 
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "VkAttachmentLoadOp value %d is not supported.", vkLoadOp);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkAttachmentLoadOp value %d is not supported.", vkLoadOp);
 			return MTLLoadActionLoad;
 	}
 }
 
+#undef mvkMTLStoreActionFromVkAttachmentStoreOp
 MVK_PUBLIC_SYMBOL MTLStoreAction mvkMTLStoreActionFromVkAttachmentStoreOp(VkAttachmentStoreOp vkStoreOp, bool hasResolveAttachment) {
+	return mvkMTLStoreActionFromVkAttachmentStoreOpInObj(vkStoreOp, hasResolveAttachment, nullptr);
+}
+
+MTLStoreAction mvkMTLStoreActionFromVkAttachmentStoreOpInObj(VkAttachmentStoreOp vkStoreOp, bool hasResolveAttachment, MVKBaseObject* mvkObj) {
 	switch (vkStoreOp) {
 		case VK_ATTACHMENT_STORE_OP_STORE:		return hasResolveAttachment ? MTLStoreActionStoreAndMultisampleResolve : MTLStoreActionStore;
 		case VK_ATTACHMENT_STORE_OP_DONT_CARE:	return hasResolveAttachment ? MTLStoreActionMultisampleResolve : MTLStoreActionDontCare;
 
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "VkAttachmentStoreOp value %d is not supported.", vkStoreOp);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkAttachmentStoreOp value %d is not supported.", vkStoreOp);
 			return MTLStoreActionStore;
 	}
 }
@@ -1218,7 +1253,12 @@
 	}
 }
 
+#undef mvkShaderStageFromVkShaderStageFlagBits
 MVK_PUBLIC_SYMBOL MVKShaderStage mvkShaderStageFromVkShaderStageFlagBits(VkShaderStageFlagBits vkStage) {
+	return mvkShaderStageFromVkShaderStageFlagBitsInObj(vkStage, nullptr);
+}
+
+MVKShaderStage mvkShaderStageFromVkShaderStageFlagBitsInObj(VkShaderStageFlagBits vkStage, MVKBaseObject* mvkObj) {
 	switch (vkStage) {
 		case VK_SHADER_STAGE_VERTEX_BIT:					return kMVKShaderStageVertex;
 		case VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT:		return kMVKShaderStageTessCtl;
@@ -1227,7 +1267,7 @@
 		case VK_SHADER_STAGE_FRAGMENT_BIT:					return kMVKShaderStageFragment;
 		case VK_SHADER_STAGE_COMPUTE_BIT:					return kMVKShaderStageCompute;
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "VkShaderStage %x is not supported.", vkStage);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "VkShaderStage %x is not supported.", vkStage);
 			return kMVKShaderStageMax;
 	}
 }
@@ -1246,24 +1286,34 @@
 	}
 }
 
+#undef mvkMTLWindingFromSpvExecutionMode
 MVK_PUBLIC_SYMBOL MTLWinding mvkMTLWindingFromSpvExecutionMode(uint32_t spvMode) {
+	return mvkMTLWindingFromSpvExecutionModeInObj(spvMode, nullptr);
+}
+
+MTLWinding mvkMTLWindingFromSpvExecutionModeInObj(uint32_t spvMode, MVKBaseObject* mvkObj) {
 	switch (spvMode) {
 		// These are reversed due to the vertex flip.
 		case spv::ExecutionModeVertexOrderCw:	return MTLWindingCounterClockwise;
 		case spv::ExecutionModeVertexOrderCcw:	return MTLWindingClockwise;
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "spv::ExecutionMode %u is not a winding order mode.\n", spvMode);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "spv::ExecutionMode %u is not a winding order mode.\n", spvMode);
 			return MTLWindingCounterClockwise;
 	}
 }
 
+#undef mvkMTLTessellationPartitionModeFromSpvExecutionMode
 MVK_PUBLIC_SYMBOL MTLTessellationPartitionMode mvkMTLTessellationPartitionModeFromSpvExecutionMode(uint32_t spvMode) {
+	return mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(spvMode, nullptr);
+}
+
+MTLTessellationPartitionMode mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(uint32_t spvMode, MVKBaseObject* mvkObj) {
 	switch (spvMode) {
 		case spv::ExecutionModeSpacingEqual:			return MTLTessellationPartitionModeInteger;
 		case spv::ExecutionModeSpacingFractionalEven:	return MTLTessellationPartitionModeFractionalEven;
 		case spv::ExecutionModeSpacingFractionalOdd:	return MTLTessellationPartitionModeFractionalOdd;
 		default:
-			mvkNotifyErrorWithText(VK_ERROR_FORMAT_NOT_SUPPORTED, "spv::ExecutionMode %u is not a tessellation partition mode.\n", spvMode);
+			MVKBaseObject::reportError(mvkObj, VK_ERROR_FORMAT_NOT_SUPPORTED, "spv::ExecutionMode %u is not a tessellation partition mode.\n", spvMode);
 			return MTLTessellationPartitionModePow2;
 	}
 }
diff --git a/MoltenVK/MoltenVK/Vulkan/vulkan.mm b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
index dbf86e4..3c5e144 100644
--- a/MoltenVK/MoltenVK/Vulkan/vulkan.mm
+++ b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
@@ -38,16 +38,12 @@
 #include "MVKQueue.h"
 #include "MVKQueryPool.h"
 #include "MVKSwapchain.h"
+#include "MVKSurface.h"
 #include "MVKFoundation.h"
 #include "MVKLogging.h"
 
-static VkResult MVKLogUnimplemented(const char* funcName) {
-    return mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "%s() is not implemented!", funcName);
-}
-
 static bool _mvkTraceVulkanCalls = false;
-#define MVKTraceVulkanCall()	MVKLogInfoIf(_mvkTraceVulkanCalls, "%s()", __FUNCTION__)
-
+#define MVKTraceVulkanCall()	if (_mvkTraceVulkanCalls) { fprintf(stderr, "[mvk-trace] %s()\n", __FUNCTION__); }
 
 MVK_PUBLIC_SYMBOL VkResult vkCreateInstance(
     const VkInstanceCreateInfo*                 pCreateInfo,
@@ -204,7 +200,7 @@
 
 	MVKTraceVulkanCall();
 	MVKPhysicalDevice* mvkPD = MVKPhysicalDevice::getMVKPhysicalDevice(physicalDevice);
-	return mvkPD->getLayerManager()->getLayerNamed(pLayerName)->getExtensionProperties(pCount, pProperties);
+	return mvkPD->getInstance()->getLayerManager()->getLayerNamed(pLayerName)->getExtensionProperties(pCount, pProperties);
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkEnumerateInstanceLayerProperties(
@@ -222,7 +218,7 @@
 
 	MVKTraceVulkanCall();
 	MVKPhysicalDevice* mvkPD = MVKPhysicalDevice::getMVKPhysicalDevice(physicalDevice);
-	return mvkPD->getLayerManager()->getLayerProperties(pCount, pProperties);
+	return mvkPD->getInstance()->getLayerManager()->getLayerProperties(pCount, pProperties);
 }
 
 MVK_PUBLIC_SYMBOL void vkGetDeviceQueue(
@@ -441,7 +437,8 @@
 	VkFence                                     fence) {
 
 	MVKTraceVulkanCall();
-	return mvkNotifyErrorWithText(VK_ERROR_FEATURE_NOT_PRESENT, "vkQueueBindSparse(): Sparse binding is not supported.");
+	MVKQueue* mvkQ = MVKQueue::getMVKQueue(queue);
+	return mvkQ->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkQueueBindSparse(): Sparse binding is not supported.");
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkCreateFence(
@@ -494,7 +491,8 @@
     uint64_t                                    timeout) {
 	
 	MVKTraceVulkanCall();
-	return mvkWaitForFences(fenceCount, pFences, waitAll, timeout);
+	MVKDevice* mvkDev = MVKDevice::getMVKDevice(device);
+	return mvkWaitForFences(mvkDev, fenceCount, pFences, waitAll, timeout);
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkCreateSemaphore(
@@ -528,7 +526,9 @@
     VkEvent*                                    pEvent) {
 	
 	MVKTraceVulkanCall();
-	return MVKLogUnimplemented("vkCreateEvent");
+	//VK_DEBUG_REPORT_OBJECT_TYPE_EVENT_EXT
+	MVKDevice* mvkDev = MVKDevice::getMVKDevice(device);
+	return mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCreateEvent(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL void vkDestroyEvent(
@@ -538,7 +538,8 @@
 
 	MVKTraceVulkanCall();
 	if ( !event ) { return; }
-	MVKLogUnimplemented("vkDestroyEvent");
+	MVKDevice* mvkDev = MVKDevice::getMVKDevice(device);
+	mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkDestroyEvent(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkGetEventStatus(
@@ -546,7 +547,8 @@
     VkEvent                                     event) {
 	
 	MVKTraceVulkanCall();
-	return MVKLogUnimplemented("vkGetEventStatus");
+	MVKDevice* mvkDev = MVKDevice::getMVKDevice(device);
+	return mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkGetEventStatus(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkSetEvent(
@@ -554,7 +556,8 @@
     VkEvent                                     event) {
 	
 	MVKTraceVulkanCall();
-	return MVKLogUnimplemented("vkSetEvent");
+	MVKDevice* mvkDev = MVKDevice::getMVKDevice(device);
+	return mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkSetEvent(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkResetEvent(
@@ -562,7 +565,8 @@
     VkEvent                                     event) {
 	
 	MVKTraceVulkanCall();
-	return MVKLogUnimplemented("vkResetEvent");
+	MVKDevice* mvkDev = MVKDevice::getMVKDevice(device);
+	return mvkDev->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkResetEvent(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkCreateQueryPool(
@@ -1468,7 +1472,8 @@
     VkPipelineStageFlags                        stageMask) {
 	
 	MVKTraceVulkanCall();
-	MVKLogUnimplemented("vkCmdSetEvent");
+	MVKCommandBuffer* cmdBuff = MVKCommandBuffer::getMVKCommandBuffer(commandBuffer);
+	cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdSetEvent(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL void vkCmdResetEvent(
@@ -1477,7 +1482,8 @@
     VkPipelineStageFlags                        stageMask) {
 	
 	MVKTraceVulkanCall();
-	MVKLogUnimplemented("vkCmdResetEvent");
+	MVKCommandBuffer* cmdBuff = MVKCommandBuffer::getMVKCommandBuffer(commandBuffer);
+	cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdResetEvent(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL void vkCmdWaitEvents(
@@ -1494,7 +1500,8 @@
 	const VkImageMemoryBarrier*                 pImageMemoryBarriers) {
 
 	MVKTraceVulkanCall();
-	MVKLogUnimplemented("vkCmdWaitEvents");
+	MVKCommandBuffer* cmdBuff = MVKCommandBuffer::getMVKCommandBuffer(commandBuffer);
+	cmdBuff->reportError(VK_ERROR_FEATURE_NOT_PRESENT, "vkCmdWaitEvents(): Vukan events are not supported.");
 }
 
 MVK_PUBLIC_SYMBOL void vkCmdPipelineBarrier(
@@ -2084,6 +2091,49 @@
 
 
 #pragma mark -
+#pragma mark VK_EXT_debug_report extension
+
+MVK_PUBLIC_SYMBOL VkResult vkCreateDebugReportCallbackEXT(
+	VkInstance                                  instance,
+	const VkDebugReportCallbackCreateInfoEXT*   pCreateInfo,
+	const VkAllocationCallbacks*                pAllocator,
+	VkDebugReportCallbackEXT*                   pCallback) {
+
+	MVKTraceVulkanCall();
+	MVKInstance* mvkInst = MVKInstance::getMVKInstance(instance);
+	MVKDebugReportCallback* mvkDRCB = mvkInst->createDebugReportCallback(pCreateInfo, pAllocator);
+	*pCallback = (VkDebugReportCallbackEXT)mvkDRCB;
+	return mvkDRCB->getConfigurationResult();
+}
+
+MVK_PUBLIC_SYMBOL void vkDestroyDebugReportCallbackEXT(
+	VkInstance                                  instance,
+	VkDebugReportCallbackEXT                    callback,
+	const VkAllocationCallbacks*                pAllocator) {
+
+	MVKTraceVulkanCall();
+	if ( !callback ) { return; }
+	MVKInstance* mvkInst = MVKInstance::getMVKInstance(instance);
+	mvkInst->destroyDebugReportCallback((MVKDebugReportCallback*)callback, pAllocator);
+}
+
+MVK_PUBLIC_SYMBOL void vkDebugReportMessageEXT(
+	VkInstance                                  instance,
+	VkDebugReportFlagsEXT                       flags,
+	VkDebugReportObjectTypeEXT                  objectType,
+	uint64_t                                    object,
+	size_t                                      location,
+	int32_t                                     messageCode,
+	const char*                                 pLayerPrefix,
+	const char*                                 pMessage) {
+
+	MVKTraceVulkanCall();
+	MVKInstance* mvkInst = MVKInstance::getMVKInstance(instance);
+	mvkInst->debugReportMessage(flags, objectType, object, location, messageCode, pLayerPrefix, pMessage);
+}
+
+
+#pragma mark -
 #pragma mark iOS & macOS surface extensions
 
 MVK_PUBLIC_SYMBOL VkResult vkCreate_PLATFORM_SurfaceMVK(
diff --git a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
index 8d0a209..634d744 100644
--- a/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
+++ b/MoltenVKShaderConverter/MoltenVKShaderConverter.xcodeproj/project.pbxproj
@@ -57,15 +57,10 @@
 		A9C70F65221B321700FBA31A /* SPIRVSupport.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9C70F5E221B321700FBA31A /* SPIRVSupport.cpp */; };
 		A9C70F66221B321700FBA31A /* SPIRVSupport.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9C70F5E221B321700FBA31A /* SPIRVSupport.cpp */; };
 		A9C70F67221B321700FBA31A /* SPIRVSupport.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9C70F5E221B321700FBA31A /* SPIRVSupport.cpp */; };
-		A9E337B8220129ED00363D2A /* MVKLogging.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9E337B7220129ED00363D2A /* MVKLogging.cpp */; };
 		A9F042B01FB4D060009FCCB8 /* MVKCommonEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AA1FB4D060009FCCB8 /* MVKCommonEnvironment.h */; };
 		A9F042B11FB4D060009FCCB8 /* MVKCommonEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AA1FB4D060009FCCB8 /* MVKCommonEnvironment.h */; };
 		A9F042B21FB4D060009FCCB8 /* MVKCommonEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AA1FB4D060009FCCB8 /* MVKCommonEnvironment.h */; };
 		A9F042B31FB4D060009FCCB8 /* MVKCommonEnvironment.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AA1FB4D060009FCCB8 /* MVKCommonEnvironment.h */; };
-		A9F042B41FB4D060009FCCB8 /* MVKLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AB1FB4D060009FCCB8 /* MVKLogging.h */; };
-		A9F042B51FB4D060009FCCB8 /* MVKLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AB1FB4D060009FCCB8 /* MVKLogging.h */; };
-		A9F042B61FB4D060009FCCB8 /* MVKLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AB1FB4D060009FCCB8 /* MVKLogging.h */; };
-		A9F042B71FB4D060009FCCB8 /* MVKLogging.h in Headers */ = {isa = PBXBuildFile; fileRef = A9F042AB1FB4D060009FCCB8 /* MVKLogging.h */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -120,9 +115,7 @@
 		A9B51BDC225E98BB00AC74D2 /* MVKOSExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKOSExtensions.h; sourceTree = "<group>"; };
 		A9C70F5D221B321600FBA31A /* SPIRVSupport.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPIRVSupport.h; path = Common/SPIRVSupport.h; sourceTree = SOURCE_ROOT; };
 		A9C70F5E221B321700FBA31A /* SPIRVSupport.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SPIRVSupport.cpp; path = Common/SPIRVSupport.cpp; sourceTree = SOURCE_ROOT; };
-		A9E337B7220129ED00363D2A /* MVKLogging.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MVKLogging.cpp; sourceTree = "<group>"; };
 		A9F042AA1FB4D060009FCCB8 /* MVKCommonEnvironment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKCommonEnvironment.h; sourceTree = "<group>"; };
-		A9F042AB1FB4D060009FCCB8 /* MVKLogging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKLogging.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -244,8 +237,6 @@
 			isa = PBXGroup;
 			children = (
 				A9F042AA1FB4D060009FCCB8 /* MVKCommonEnvironment.h */,
-				A9E337B7220129ED00363D2A /* MVKLogging.cpp */,
-				A9F042AB1FB4D060009FCCB8 /* MVKLogging.h */,
 				A9B51BDC225E98BB00AC74D2 /* MVKOSExtensions.h */,
 				A9B51BDB225E98BB00AC74D2 /* MVKOSExtensions.mm */,
 				A98149651FB6A98A005F00B4 /* MVKStrings.h */,
@@ -275,7 +266,6 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				A9F042B41FB4D060009FCCB8 /* MVKLogging.h in Headers */,
 				A9C70F5F221B321700FBA31A /* SPIRVSupport.h in Headers */,
 				A90941A51C581F840094110D /* GLSLConversion.h in Headers */,
 				A90940A91C5808BB0094110D /* GLSLToSPIRVConverter.h in Headers */,
@@ -288,7 +278,6 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				A9F042B51FB4D060009FCCB8 /* MVKLogging.h in Headers */,
 				A9C70F60221B321700FBA31A /* SPIRVSupport.h in Headers */,
 				A90941A61C581F840094110D /* GLSLConversion.h in Headers */,
 				A90940AA1C5808BB0094110D /* GLSLToSPIRVConverter.h in Headers */,
@@ -305,7 +294,6 @@
 				A98149681FB6A98A005F00B4 /* MVKStrings.h in Headers */,
 				A9C70F61221B321700FBA31A /* SPIRVSupport.h in Headers */,
 				A928C9191D0488DC00071B88 /* SPIRVConversion.h in Headers */,
-				A9F042B61FB4D060009FCCB8 /* MVKLogging.h in Headers */,
 				A909408C1C58013E0094110D /* SPIRVToMSLConverter.h in Headers */,
 				A9F042B21FB4D060009FCCB8 /* MVKCommonEnvironment.h in Headers */,
 			);
@@ -320,7 +308,6 @@
 				A9C70F62221B321700FBA31A /* SPIRVSupport.h in Headers */,
 				A928C91A1D0488DC00071B88 /* SPIRVConversion.h in Headers */,
 				A909408D1C58013E0094110D /* SPIRVToMSLConverter.h in Headers */,
-				A9F042B71FB4D060009FCCB8 /* MVKLogging.h in Headers */,
 				A9F042B31FB4D060009FCCB8 /* MVKCommonEnvironment.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -625,7 +612,6 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				A9E337B8220129ED00363D2A /* MVKLogging.cpp in Sources */,
 				A97CC7411C7527F3004A5C7E /* MoltenVKShaderConverterTool.cpp in Sources */,
 				A9C70F63221B321700FBA31A /* SPIRVSupport.cpp in Sources */,
 				A95096BF2003D32400F10950 /* OSSupport.mm in Sources */,