Make MVKConfiguration access global, ignoring provided VkInstance.

MVKConfiguration access is now global, and the VkInstance provided in the
vkGet/Set/MoltenVKConfigurationMVK() functions is ignored. This allows these
functions to be provided with a VkInstance object that originates from a
different Vulkan layer than MoltenVK, without risking breaking the API.

MVKConfiguration extended to cover all MoltenVK environment variables.

Move all environment variable declarations to MVKEnvironment.h.
Add MVKEnvironment.cpp to define config functions.
Cleanup .m files to use MVKCommonEnvironment.h instead of MVKEnvironment.h.
diff --git a/Docs/MoltenVK_Runtime_UserGuide.md b/Docs/MoltenVK_Runtime_UserGuide.md
index 9f754a2..34fc24f 100644
--- a/Docs/MoltenVK_Runtime_UserGuide.md
+++ b/Docs/MoltenVK_Runtime_UserGuide.md
@@ -373,12 +373,13 @@
   These functions are exposed in this header for your own purposes such as interacting with *Metal* 
   directly, or simply logging data values.
 
->***Note:*** The functions in `vk_mvk_moltenvk.h` are not supported by the *Vulkan SDK Loader and Layers*
- framework. The opaque Vulkan objects used by the functions in `vk_mvk_moltenvk.h` (`VkInstance`, 
- `VkPhysicalDevice`, `VkShaderModule`, `VKImage`, ...), must have been retrieved directly from **MoltenVK**, 
- and not through the *Vulkan SDK Loader and Layers* framework. The *Vulkan SDK Loader and Layers* framework 
- often changes these opaque objects, and passing them from a higher layer directly to **MoltenVK** will 
- result in undefined behaviour.
+>***Note:*** Except for `vkGetMoltenVKConfigurationMVK()` and `vkSetMoltenVKConfigurationMVK()`, 
+ the functions in `vk_mvk_moltenvk.h` are not supported by the *Vulkan SDK Loader and Layers*
+ framework. The opaque Vulkan objects used by the functions in `vk_mvk_moltenvk.h` (`VkPhysicalDevice`, 
+ `VkShaderModule`, `VKImage`, ...), must have been retrieved directly from **MoltenVK**, and not through 
+ the *Vulkan SDK Loader and Layers* framework. The *Vulkan SDK Loader and Layers* framework often changes 
+ these opaque objects, and passing them from a higher layer directly to **MoltenVK** will result in 
+ undefined behaviour.
 
 
 <a name="moltenvk_config"></a>
@@ -402,12 +403,9 @@
 by a corresponding build setting at the time **MoltenVK** is compiled. The environment 
 variable and build setting for each configuration parameter share the same name.
 
-There are also a number of additional runtime environment variables that are not included in the
-`MVKConfiguration` structure, but that also control **MoltenVK** behaviour.
-
-See the description of the environment variables and the `MVKConfiguration` structure parameters 
-in the `vk_mvk_moltenvk.h` file for more info about configuring and optimizing **MoltenVK** 
-at runtime or build time.
+See the description of the `MVKConfiguration` structure parameters and corresponding environment 
+variables in the `vk_mvk_moltenvk.h` file for more info about configuring and optimizing 
+**MoltenVK** at runtime or build time.
 
 
 <a name="shaders"></a>
diff --git a/Docs/Whats_New.md b/Docs/Whats_New.md
index 9739ba9..7ee10cc 100644
--- a/Docs/Whats_New.md
+++ b/Docs/Whats_New.md
@@ -21,6 +21,9 @@
 - Advertise support for `shaderInt64` feature.
 - Support fast math on MSL compiler via `MVKConfiguration::fastMathEnabled` configuration 
   setting and `MVK_CONFIG_FAST_MATH_ENABLED` environment variable (both disabled by default).
+- `vkGetMoltenVKConfigurationMVK()` and `vkSetMoltenVKConfigurationMVK()` functions
+  can now be used with a `VkInstance` from another Vulkan layer, or with a `VK_NULL_HANDLE VkInstance`.
+- `MVKConfiguration` extended to cover all MoltenVK environment variables.
 - Support _GitHub Actions_ for CI builds on pull requests.
 - Remove support for _Travis-CI_.
 - `Makefile` and `fetchDependencies` support `xcpretty` (if available)
diff --git a/MoltenVK/MoltenVK.xcodeproj/project.pbxproj b/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
index bf220a0..f8b6034 100644
--- a/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
+++ b/MoltenVK/MoltenVK.xcodeproj/project.pbxproj
@@ -312,6 +312,9 @@
 		A99C91032295FAC600A061DA /* MVKVulkanAPIObject.mm in Sources */ = {isa = PBXBuildFile; fileRef = A99C91002295FAC500A061DA /* MVKVulkanAPIObject.mm */; };
 		A99C91042295FAC600A061DA /* MVKVulkanAPIObject.h in Headers */ = {isa = PBXBuildFile; fileRef = A99C91012295FAC500A061DA /* MVKVulkanAPIObject.h */; };
 		A99C91052295FAC600A061DA /* MVKVulkanAPIObject.h in Headers */ = {isa = PBXBuildFile; fileRef = A99C91012295FAC500A061DA /* MVKVulkanAPIObject.h */; };
+		A9A5E9C725C0822700E9085E /* MVKEnvironment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9A5E9C525C0822700E9085E /* MVKEnvironment.cpp */; };
+		A9A5E9C825C0822700E9085E /* MVKEnvironment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9A5E9C525C0822700E9085E /* MVKEnvironment.cpp */; };
+		A9A5E9C925C0822700E9085E /* MVKEnvironment.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A9A5E9C525C0822700E9085E /* MVKEnvironment.cpp */; };
 		A9B51BD7225E986A00AC74D2 /* MVKOSExtensions.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9B51BD2225E986A00AC74D2 /* MVKOSExtensions.mm */; };
 		A9B51BD8225E986A00AC74D2 /* MVKOSExtensions.mm in Sources */ = {isa = PBXBuildFile; fileRef = A9B51BD2225E986A00AC74D2 /* MVKOSExtensions.mm */; };
 		A9B51BD9225E986A00AC74D2 /* MVKOSExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = A9B51BD6225E986A00AC74D2 /* MVKOSExtensions.h */; };
@@ -509,6 +512,7 @@
 		A99C90ED229455B300A061DA /* MVKCmdDebug.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MVKCmdDebug.mm; sourceTree = "<group>"; };
 		A99C91002295FAC500A061DA /* MVKVulkanAPIObject.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MVKVulkanAPIObject.mm; sourceTree = "<group>"; };
 		A99C91012295FAC500A061DA /* MVKVulkanAPIObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKVulkanAPIObject.h; sourceTree = "<group>"; };
+		A9A5E9C525C0822700E9085E /* MVKEnvironment.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MVKEnvironment.cpp; sourceTree = "<group>"; };
 		A9AD67C72054DD6C00ED3C08 /* vulkan */ = {isa = PBXFileReference; lastKnownFileType = folder; path = vulkan; sourceTree = "<group>"; };
 		A9B51BD2225E986A00AC74D2 /* MVKOSExtensions.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MVKOSExtensions.mm; sourceTree = "<group>"; };
 		A9B51BD6225E986A00AC74D2 /* MVKOSExtensions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVKOSExtensions.h; sourceTree = "<group>"; };
@@ -679,6 +683,7 @@
 				45557A4D21C9EFF3008868BD /* MVKCodec.cpp */,
 				45557A5121C9EFF3008868BD /* MVKCodec.h */,
 				45557A5721CD83C3008868BD /* MVKDXTnCodec.def */,
+				A9A5E9C525C0822700E9085E /* MVKEnvironment.cpp */,
 				A98149431FB6A3F7005F00B4 /* MVKEnvironment.h */,
 				A98149451FB6A3F7005F00B4 /* MVKFoundation.cpp */,
 				A98149441FB6A3F7005F00B4 /* MVKFoundation.h */,
@@ -1312,6 +1317,7 @@
 				2FEA0A9A24902F9F00EEF3AD /* MVKExtensions.mm in Sources */,
 				2FEA0A9B24902F9F00EEF3AD /* MVKFoundation.cpp in Sources */,
 				2FEA0A9C24902F9F00EEF3AD /* MVKPixelFormats.mm in Sources */,
+				A9A5E9C825C0822700E9085E /* MVKEnvironment.cpp in Sources */,
 				2FEA0A9D24902F9F00EEF3AD /* MVKDevice.mm in Sources */,
 				453638362508A4C7000EFFD3 /* MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m in Sources */,
 				2FEA0A9E24902F9F00EEF3AD /* MTLRenderPassDescriptor+MoltenVK.m in Sources */,
@@ -1367,6 +1373,7 @@
 				A94FB7D21C7DFB4800632CA3 /* MVKCommandBuffer.mm in Sources */,
 				A94FB7C61C7DFB4800632CA3 /* MVKCmdRenderPass.mm in Sources */,
 				A94FB7DE1C7DFB4800632CA3 /* MVKBuffer.mm in Sources */,
+				A9A5E9C725C0822700E9085E /* MVKEnvironment.cpp in Sources */,
 				A94FB82A1C7DFB4800632CA3 /* mvk_datatypes.mm in Sources */,
 				A909F661213B190700FCD6BE /* MVKExtensions.mm in Sources */,
 				A98149551FB6A3F7005F00B4 /* MVKFoundation.cpp in Sources */,
@@ -1426,6 +1433,7 @@
 				A94FB7D31C7DFB4800632CA3 /* MVKCommandBuffer.mm in Sources */,
 				A94FB7C71C7DFB4800632CA3 /* MVKCmdRenderPass.mm in Sources */,
 				A94FB7DF1C7DFB4800632CA3 /* MVKBuffer.mm in Sources */,
+				A9A5E9C925C0822700E9085E /* MVKEnvironment.cpp in Sources */,
 				A94FB82B1C7DFB4800632CA3 /* mvk_datatypes.mm in Sources */,
 				A909F662213B190700FCD6BE /* MVKExtensions.mm in Sources */,
 				A98149561FB6A3F7005F00B4 /* MVKFoundation.cpp in Sources */,
diff --git a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
index fa158f4..6e69f10 100644
--- a/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
+++ b/MoltenVK/MoltenVK/API/vk_mvk_moltenvk.h
@@ -71,7 +71,9 @@
  *
  * To change the MoltenVK configuration settings at runtime using a programmatic API,
  * use the vkGetMoltenVKConfigurationMVK() and vkSetMoltenVKConfigurationMVK() functions
- * to retrieve, modify, and set a copy of the MVKConfiguration structure.
+ * to retrieve, modify, and set a copy of the MVKConfiguration structure. To be active,
+ * some configuration settings must be set before a VkInstance or VkDevice is created.
+ * See the description of each member for more information.
  *
  * The initial value of each of the configuration settings can established at runtime
  * by a corresponding environment variable, or if the environment variable is not set,
@@ -91,108 +93,6 @@
  * TO SUPPORT DYNAMIC LINKING TO THIS STRUCTURE AS DESCRIBED ABOVE, THIS STRUCTURE SHOULD NOT
  * BE CHANGED EXCEPT TO ADD ADDITIONAL MEMBERS ON THE END. EXISTING MEMBERS, AND THEIR ORDER,
  * SHOULD NOT BE CHANGED.
- *
- * In addition to the configuration parmeters in this structure, there are several settings that
- * can be configured through runtime environment variables or MoltenVK compile-time build settings:
- *
- * 1.  The MVK_CONFIG_LOG_LEVEL runtime environment variable or MoltenVK compile-time build setting
- *     controls the level of logging performned by MoltenVK using the following numeric values:
- *       0: No logging.
- *       1: Log errors only.
- *       2: Log errors and informational messages.
- *     If none of these is set, errors and informational messages are logged.
- *
- * 2.  The MVK_CONFIG_TRACE_VULKAN_CALLS runtime environment variable or MoltenVK compile-time build
- *     setting causes MoltenVK to log the name of each Vulkan call made by the application, along with
- *     the Mach thread ID, global system thread ID, and thread name. The logging format options can be
- *     controlled by setting the value of MVK_CONFIG_TRACE_VULKAN_CALLS as follows:
- *         0: No Vulkan call logging.
- *         1: Log the name of each Vulkan call when the call is entered.
- *         2: Log the name of each Vulkan call when the call is entered and exited. This effectively
- *            brackets any other logging activity within the scope of the Vulkan call.
- *         3: Same as option 2, plus logs the time spent inside the Vulkan function.
- *     If none of these is set, no Vulkan call logging will occur.
- *
- * 3.  Setting the MVK_CONFIG_FORCE_LOW_POWER_GPU runtime environment variable or MoltenVK compile-time
- *     build setting to 1 will force MoltenVK to use a low-power GPU, if one is availble on the device.
- *     By default, this setting is disabled, allowing both low-power and high-power GPU's to be used.
- *
- * 4.  Setting the MVK_ALLOW_METAL_FENCES or MVK_ALLOW_METAL_EVENTS runtime environment variable
- *     or MoltenVK compile-time build setting to 1 will cause MoltenVK to use MTLFence or MTLEvent,
- *     respectively, if it is available on the device, for VkSemaphore synchronization behaviour.
- *     If both variables are set, MVK_ALLOW_METAL_FENCES takes priority over MVK_ALLOW_METAL_EVENTS.
- *     If both are disabled, or if neither MTLFence nor MTLEvent is available on the device,
- *     MoltenVK will use CPU synchronization to control VkSemaphore synchronization behaviour.
- *     In the special case of VK_SEMAPHORE_TYPE_TIMELINE semaphores, MoltenVK will always
- *     use MTLSharedEvent if it is available on the platform, regardless of the values of
- *     MVK_ALLOW_METAL_FENCES or MVK_ALLOW_METAL_EVENTS.
- *     By default, both MVK_ALLOW_METAL_FENCES and MVK_ALLOW_METAL_EVENTS are enabled, meaning,
- *     to control VkSemaphore synchronization behaviour, by default MoltenVK will preferentially
- *     use MTLFence if it is available, followed by MTLEvent if it is available.
- *
- * 5.  The MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE runtime environment variable or MoltenVK compile-time
- *     build setting controls whether Metal should run an automatic GPU capture without the user
- *     having to trigger it manually via the Xcode user interface, and controls the scope under
- *     which that GPU capture will occur. This is useful when trying to capture a one-shot GPU
- *     trace, such as when running a Vulkan CTS test case. For the automatic GPU capture to occur,
- *     the Xcode scheme under which the app is run must have the Metal GPU capture option turned on.
- *     MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE should not be set to manually trigger a GPU capture via the
- *     Xcode user interface.
- *       0: No automatic GPU capture.
- *       1: Capture all GPU commands issued during the lifetime of the VkDevice.
- *     If MVK_CONFIG_AUTO_GPU_CAPTURE_OUTPUT_FILE is also set, it is a filename where the automatic
- *     GPU capture should be saved. In this case, the Xcode scheme need not have Metal GPU capture
- *     enabled, and in fact the app need not be run under Xcode's control at all. This is useful
- *     in case the app cannot be run under Xcode's control. A path starting with '~' can be used
- *     to place it in a user's home directory, as in the shell. This feature requires Metal 3.0
- *     (macOS 10.15, iOS 13).
- *     If none of these is set, no automatic GPU capture will occur.
- *
- * 6.  The MVK_CONFIG_TEXTURE_1D_AS_2D runtime environment variable or MoltenVK compile-time build
- *     setting controls whether MoltenVK should use a Metal 2D texture with a height of 1 for a
- *     Vulkan 1D image, or use a native Metal 1D texture. Metal imposes significant restrictions
- *     on native 1D textures, including not being renderable, clearable, or permitting mipmaps.
- *     Using a Metal 2D texture allows Vulkan 1D textures to support this additional functionality.
- *     This setting is enabled by default, and MoltenVK will use a Metal 2D texture for each Vulkan 1D image.
- *
- * 7.  The MVK_CONFIG_PREALLOCATE_DESCRIPTORS runtime environment variable or MoltenVK compile-time
- *     build setting controls whether MoltenVK should preallocate memory in each VkDescriptorPool
- *     according to the values of the VkDescriptorPoolSize parameters. Doing so may improve
- *     descriptor set allocation performance at a cost of preallocated application memory.
- *     If this setting is disabled, the descriptors required for a descriptor set will
- *     be dynamically allocated in application memory when the descriptor set itself is allocated.
- *     This setting is disabled by default, and MoltenVK will dynamically allocate descriptors
- *     when the containing descriptor set is allocated.
- *
- * 8.  The MVK_CONFIG_USE_COMMAND_POOLING runtime environment variable or MoltenVK compile-time
- *     build setting controls whether MoltenVK should use pools to manage memory used when
- *     adding commands to command buffers. If this setting is enabled, MoltenVK
- *     will use a pool to hold command resources for reuse during command execution. If this
- *     setting is disabled, command memory is allocated and destroyed each time
- *     a command is executed. This is a classic time-space trade off. When command pooling is
- *     active, the memory in the pool can be cleared via a call to the vkTrimCommandPoolKHR()
- *     command. This setting is enabled by default, and MoltenVK will pool command memory.
- *
- * 9.  The MVK_CONFIG_USE_MTLHEAP runtime environment variable or MoltenVK compile-time build
- *     setting controls whether MoltenVK should use MTLHeaps for allocating textures and buffers
- *     from device memory. If this setting is enabled, and placement MTLHeaps are
- *     available on the platform, MoltenVK will allocate a placement MTLHeap for each VkDeviceMemory
- *     instance, and allocate textures and buffers from that placement heap. If this environment
- *     variable is disabled, MoltenVK will allocate textures and buffers from general device memory.
- *     Apple recommends that MTLHeaps should only be used for specific requirements such as aliasing
- *     or hazard tracking, and MoltenVK testing has shown that allocating multiple textures of
- *     different types or usages from one MTLHeap can occassionally cause corruption issues under
- *     certain circumstances. Because of this, this setting is disabled by default, and MoltenVK
- *     will allocate texures and buffers from general device memory.
- *
- * 10. The MVK_CONFIG_PERFORMANCE_LOGGING_INLINE runtime environment variable or MoltenVK
- *     compile-time build setting controls whether MoltenVK should log the performance of
- *     individual activities as they happen. If this setting is enabled, activity performance
- *     will be logged when each activity happens. If this setting is disabled, activity
- *     performance will be logged when frame peformance is logged as determined by the
- *     MVK_CONFIG_PERFORMANCE_LOGGING_FRAME_COUNT environment variable or MoltenVK
- *     compile-time build setting. This setting is disabled by default, and activity
- *     performance will be logged only when frame activity is logged.
  */
 typedef struct {
 
@@ -376,12 +276,11 @@
 	 * If enabled, performance statistics, as defined by the MVKPerformanceStatistics structure,
 	 * are collected, and can be retrieved via the vkGetPerformanceStatisticsMVK() function.
 	 *
-	 * You can also use the performanceLoggingFrameCount parameter or MVK_CONFIG_PERFORMANCE_LOGGING_INLINE
-	 * environment variable or MoltenVK compile-time build setting to automatically log the performance
-	 * statistics collected by this parameter.
+	 * You can also use the performanceLoggingFrameCount or logActivityPerformanceInline
+	 * parameters to automatically log the performance statistics collected by this parameter.
 	 *
-	 * The value of this parameter may be changed at any time during application runtime,
-	 * and the changed value will immediately effect subsequent MoltenVK behaviour.
+	 * The value of this parameter must be changed before creating a VkDevice,
+	 * for the change to take effect.
 	 *
 	 * The initial value or this parameter is set by the
 	 * MVK_CONFIG_PERFORMANCE_TRACKING
@@ -574,6 +473,242 @@
 	 * If neither is set, the value of this parameter defaults to false.
 	 */
 	VkBool32 fastMathEnabled;
+
+	/**
+	 * Controls the level of logging performned by MoltenVK using the following numeric values:
+	 *   0: No logging.
+	 *   1: Log errors only.
+	 *   2: Log errors and informational messages.
+	 *
+	 * The value of this parameter may be changed at any time during application runtime,
+	 * and the changed value will immediately effect subsequent MoltenVK behaviour.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_LOG_LEVEL
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, errors and informational messages are logged.
+	 */
+	uint32_t logLevel;
+
+	/**
+	 * Causes MoltenVK to log the name of each Vulkan call made by the application,
+	 * along with the Mach thread ID, global system thread ID, and thread name.
+	 * The logging format options can be controlled as follows:
+	 *   0: No Vulkan call logging.
+	 *   1: Log the name of each Vulkan call when the call is entered.
+	 *   2: Log the name of each Vulkan call when the call is entered and exited. This
+	 *      effectively brackets any other logging activity within the scope of the Vulkan call.
+	 *   3: Same as option 2, plus logs the time spent inside the Vulkan function.
+	 * If none of these is set, no Vulkan call logging will occur.
+	 *
+	 * The value of this parameter may be changed at any time during application runtime,
+	 * and the changed value will immediately effect subsequent MoltenVK behaviour.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_TRACE_VULKAN_CALLS
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, no Vulkan call logging will occur.
+	 */
+	uint32_t traceVulkanCalls;
+
+	/**
+	 * Force MoltenVK to use a low-power GPU, if one is availble on the device.
+	 *
+	 * The value of this parameter must be changed before creating a VkInstance,
+	 * for the change to take effect.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_FORCE_LOW_POWER_GPU
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is disabled by default, allowing both
+	 * low-power and high-power GPU's to be used.
+	 */
+	VkBool32 forceLowPowerGPU;
+
+	/**
+	 * Use MTLFence, if it is available on the device, for VkSemaphore synchronization behaviour.
+	 *
+	 * This parameter interacts with semaphoreUseMTLEvent. If both are enabled, semaphoreUseMTLFence
+	 * takes priority and MTLFence will be used if it is available, otherwise MTLEvent will be used
+	 * if it is available. If neither semaphoreUseMTLFence or semaphoreUseMTLEvent are enabled, or
+	 * if neither MTLFence or MTLEvent are available, CPU-based synchoronization will be used.
+	 *
+	 * In the special case of VK_SEMAPHORE_TYPE_TIMELINE semaphores, MoltenVK will always
+	 * use MTLSharedEvent if it is available on the platform, regardless of the values of
+	 * MVK_ALLOW_METAL_FENCES or MVK_ALLOW_METAL_EVENTS.
+	 *
+	 * The value of this parameter must be changed before creating a VkDevice,
+	 * for the change to take effect.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_ALLOW_METAL_FENCES
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is enabled by default, and VkSemaphore will use MTLFence,
+	 * if it is available.
+	 */
+	VkBool32 semaphoreUseMTLFence;
+
+	/**
+	 * Use MTLEvent, if it is available on the device, for VkSemaphore synchronization behaviour.
+	 *
+	 * This parameter interacts with semaphoreUseMTLFence. If both are enabled, semaphoreUseMTLFence
+	 * takes priority and MTLFence will be used if it is available, otherwise MTLEvent will be used
+	 * if it is available. If neither semaphoreUseMTLFence or semaphoreUseMTLEvent are enabled, or
+	 * if neither MTLFence or MTLEvent are available, CPU-based synchoronization will be used.
+	 *
+	 * In the special case of VK_SEMAPHORE_TYPE_TIMELINE semaphores, MoltenVK will always
+	 * use MTLSharedEvent if it is available on the platform, regardless of the values of
+	 * MVK_ALLOW_METAL_FENCES or MVK_ALLOW_METAL_EVENTS.
+	 *
+	 * The value of this parameter must be changed before creating a VkDevice,
+	 * for the change to take effect.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_ALLOW_METAL_EVENTS
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is enabled by default, and VkSemaphore will use MTLEvent,
+	 * if it is available, unless if MTLFence is available and semaphoreUseMTLFence is enabled.
+	 */
+	VkBool32 semaphoreUseMTLEvent;
+
+	/**
+	 * Controls whether Metal should run an automatic GPU capture without the user having to
+	 * trigger it manually via the Xcode user interface, and controls the scope under which
+	 * that GPU capture will occur. This is useful when trying to capture a one-shot GPU trace,
+	 * such as when running a Vulkan CTS test case. For the automatic GPU capture to occur,
+	 * the Xcode scheme under which the app is run must have the Metal GPU capture option
+	 * enabled. MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE should not be set to manually trigger a
+	 * GPU capture via the Xcode user interface.
+	 *
+	 * To automatically trigger a GPU capture, set this value as follows:
+	 *   0: No automatic GPU capture.
+	 *   1: Capture all GPU commands issued during the lifetime of the VkDevice.
+	 *
+	 * The value of this parameter must be changed before creating a VkDevice,
+	 * for the change to take effect.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, no automatic GPU capture will occur.
+	 */
+	uint32_t autoGPUCaptureScope;
+
+	/**
+	 * The path to a file where the automatic GPU capture should be saved, if autoGPUCaptureScope
+	 * is enabled. In this case, the Xcode scheme need not have Metal GPU capture enabled, and in
+	 * fact the app need not be run under Xcode's control at all. This is useful in case the app
+	 * cannot be run under Xcode's control. A path starting with '~' can be used to place it in a
+	 * user's home directory, as in the shell. This feature requires Metal 3.0 (macOS 10.15, iOS 13).
+	 *
+	 * If this parameter is NULL or an empty string, and autoGPUCaptureScope is enabled, automatic
+	 * GPU capture will be handled by the Xcode user interface.
+	 *
+	 * The value of this parameter must be changed before creating a VkDevice,
+	 * for the change to take effect.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_AUTO_GPU_CAPTURE_OUTPUT_FILE
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, automatic GPU capture will be handled by the Xcode user interface.
+	 */
+	char* autoGPUCaptureOutputFilepath;
+
+	/**
+	 * Controls whether MoltenVK should use a Metal 2D texture with a height of 1 for a
+	 * Vulkan 1D image, or use a native Metal 1D texture. Metal imposes significant restrictions
+	 * on native 1D textures, including not being renderable, clearable, or permitting mipmaps.
+	 * Using a Metal 2D texture allows Vulkan 1D textures to support this additional functionality.
+	 *
+	 * The value of this parameter should only be changed before creating the VkInstance.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_TEXTURE_1D_AS_2D
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is enabled by default, and MoltenVK will
+	 * use a Metal 2D texture for each Vulkan 1D image.
+	 */
+	VkBool32 texture1DAs2D;
+
+	/**
+	 * Controls whether MoltenVK should preallocate memory in each VkDescriptorPool
+	 * ccording to the values of the VkDescriptorPoolSize parameters. Doing so may improve
+	 * descriptor set allocation performance at a cost of preallocated application memory,
+	 * and possible descreased performance when creating and reseting the VkDescriptorPool.
+	 * If this setting is disabled, the descriptors required for a descriptor set will
+	 * be dynamically allocated in application memory when the descriptor set itself is allocated.
+	 *
+	 * The value of this parameter may be changed at any time during application runtime,
+	 * and the changed value will immediately effect behavior of VkDescriptorPools created
+	 * after the setting is changed.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_PREALLOCATE_DESCRIPTORS
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is disabled by default, and MoltenVK will
+	 * dynamically allocate descriptors when the containing descriptor set is allocated.
+	 */
+	VkBool32 preallocateDescriptors;
+
+	/**
+	 * Controls whether MoltenVK should use pools to manage memory used when adding commands
+	 * to command buffers. If this setting is enabled, MoltenVK will use a pool to hold command
+	 * resources for reuse during command execution. If this setting is disabled, command memory
+	 * is allocated and destroyed each time a command is executed. This is a classic time-space
+	 * trade off. When command pooling is active, the memory in the pool can be cleared via a
+	 * call to the vkTrimCommandPoolKHR() command.
+	 *
+	 * The value of this parameter may be changed at any time during application runtime,
+	 * and the changed value will immediately effect behavior of VkCommandPools created
+	 * after the setting is changed.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_USE_COMMAND_POOLING
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is enabled by default, and MoltenVK will pool command memory.
+	 */
+	VkBool32 useCommandPooling;
+
+	/**
+	 * Controls whether MoltenVK should use MTLHeaps for allocating textures and buffers
+	 * from device memory. If this setting is enabled, and placement MTLHeaps are
+	 * available on the platform, MoltenVK will allocate a placement MTLHeap for each VkDeviceMemory
+	 * instance, and allocate textures and buffers from that placement heap. If this environment
+	 * variable is disabled, MoltenVK will allocate textures and buffers from general device memory.
+	 *
+	 * Apple recommends that MTLHeaps should only be used for specific requirements such as aliasing
+	 * or hazard tracking, and MoltenVK testing has shown that allocating multiple textures of
+	 * different types or usages from one MTLHeap can occassionally cause corruption issues under
+	 * certain circumstances.
+	 *
+	 * The value of this parameter must be changed before creating a VkInstance,
+	 * for the change to take effect.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_USE_MTLHEAP
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is disabled by default, and MoltenVK
+	 * will allocate texures and buffers from general device memory.
+	 */
+	VkBool32 useMTLHeap;
+
+	/**
+	 * Controls whether MoltenVK should log the performance of individual activities as they happen.
+	 * If this setting is enabled, activity performance will be logged when each activity happens.
+	 * If this setting is disabled, activity performance will be logged when frame peformance is
+	 * logged as determined by the performanceLoggingFrameCount value.
+	 *
+	 * The value of this parameter must be changed before creating a VkDevice,
+	 * for the change to take effect.
+	 *
+	 * The initial value or this parameter is set by the
+	 * MVK_CONFIG_PERFORMANCE_LOGGING_INLINE
+	 * runtime environment variable or MoltenVK compile-time build setting.
+	 * If neither is set, this setting is disabled by default, and activity
+	 * performance will be logged only when frame activity is logged.
+	 */
+	VkBool32 logActivityPerformanceInline;
+
 } MVKConfiguration;
 
 /**
@@ -715,8 +850,8 @@
 #pragma mark -
 #pragma mark Function types
 
-typedef VkResult (VKAPI_PTR *PFN_vkGetMoltenVKConfigurationMVK)(VkInstance instance, MVKConfiguration* pConfiguration, size_t* pConfigurationSize);
-typedef VkResult (VKAPI_PTR *PFN_vkSetMoltenVKConfigurationMVK)(VkInstance instance, MVKConfiguration* pConfiguration, size_t* pConfigurationSize);
+typedef VkResult (VKAPI_PTR *PFN_vkGetMoltenVKConfigurationMVK)(VkInstance ignored, MVKConfiguration* pConfiguration, size_t* pConfigurationSize);
+typedef VkResult (VKAPI_PTR *PFN_vkSetMoltenVKConfigurationMVK)(VkInstance ignored, MVKConfiguration* pConfiguration, size_t* pConfigurationSize);
 typedef VkResult (VKAPI_PTR *PFN_vkGetPhysicalDeviceMetalFeaturesMVK)(VkPhysicalDevice physicalDevice, MVKPhysicalDeviceMetalFeatures* pMetalFeatures, size_t* pMetalFeaturesSize);
 typedef VkResult (VKAPI_PTR *PFN_vkGetPerformanceStatisticsMVK)(VkDevice device, MVKPerformanceStatistics* pPerf, size_t* pPerfSize);
 typedef void (VKAPI_PTR *PFN_vkGetVersionStringsMVK)(char* pMoltenVersionStringBuffer, uint32_t moltenVersionStringBufferLength, char* pVulkanVersionStringBuffer, uint32_t vulkanVersionStringBufferLength);
@@ -743,8 +878,12 @@
  * the current configuration, make changes, and call  vkSetMoltenVKConfigurationMVK() to
  * update all of the values.
  *
- * To be active, some configuration settings must be set before a VkDevice is created.
- * See the description of the MVKConfiguration members for more information.
+ * The VkInstance object you provide here is ignored, and a VK_NULL_HANDLE value can be provided.
+ * This function can be called before the VkInstance has been created. It is safe to call this function
+ * with a VkInstance retrieved from a different layer in the Vulkan SDK Loader and Layers framework.
+ *
+ * To be active, some configuration settings must be set before a VkInstance or VkDevice
+ * is created. See the description of the MVKConfiguration members for more information.
  *
  * If you are linking to an implementation of MoltenVK that was compiled from a different
  * VK_MVK_MOLTENVK_SPEC_VERSION than your app was, the size of the MVKConfiguration structure
@@ -764,15 +903,9 @@
  * that MoltenVK expects the size of MVKConfiguration to be by setting the value of pConfiguration
  * to NULL. In that case, this function will set *pConfigurationSize to the size that MoltenVK
  * expects MVKConfiguration to be.
- *
- * This function is not supported by the Vulkan SDK Loader and Layers framework.
- * The VkInstance object you provide here must have been retrieved directly from MoltenVK,
- * and not through the Vulkan SDK Loader and Layers framework. Opaque Vulkan objects
- * are often changed by layers, and passing them from one layer to another, or from
- * a layer directly to MoltenVK, will result in undefined behaviour.
  */
 VKAPI_ATTR VkResult VKAPI_CALL vkGetMoltenVKConfigurationMVK(
-	VkInstance                                  instance,
+	VkInstance                                  ignored,
 	MVKConfiguration*                           pConfiguration,
 	size_t*                                     pConfigurationSize);
 
@@ -783,8 +916,12 @@
  * to retrieve the current configuration, make changes, and call
  * vkSetMoltenVKConfigurationMVK() to update all of the values.
  *
- * To be active, some configuration settings must be set before a VkDevice is created.
- * See the description of the MVKConfiguration members for more information.
+ * The VkInstance object you provide here is ignored, and a VK_NULL_HANDLE value can be provided.
+ * This function can be called before the VkInstance has been created. It is safe to call this function
+ * with a VkInstance retrieved from a different layer in the Vulkan SDK Loader and Layers framework.
+ *
+ * To be active, some configuration settings must be set before a VkInstance or VkDevice
+ * is created. See the description of the MVKConfiguration members for more information.
  *
  * If you are linking to an implementation of MoltenVK that was compiled from a different
  * VK_MVK_MOLTENVK_SPEC_VERSION than your app was, the size of the MVKConfiguration structure
@@ -804,15 +941,9 @@
  * that MoltenVK expects the size of MVKConfiguration to be by setting the value of pConfiguration
  * to NULL. In that case, this function will set *pConfigurationSize to the size that MoltenVK
  * expects MVKConfiguration to be.
- *
- * This function is not supported by the Vulkan SDK Loader and Layers framework.
- * The VkInstance object you provide here must have been retrieved directly from MoltenVK,
- * and not through the Vulkan SDK Loader and Layers framework. Opaque Vulkan objects
- * are often changed by layers, and passing them from one layer to another, or from
- * a layer directly to MoltenVK, will result in undefined behaviour.
  */
 VKAPI_ATTR VkResult VKAPI_CALL vkSetMoltenVKConfigurationMVK(
-	VkInstance                                  instance,
+	VkInstance                                  ignored,
 	const MVKConfiguration*                     pConfiguration,
 	size_t*                                     pConfigurationSize);
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
index 609ec03..b7a5f96 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDescriptorSet.mm
@@ -312,24 +312,6 @@
 #pragma mark -
 #pragma mark MVKDescriptorTypePreallocation
 
-#ifndef MVK_CONFIG_PREALLOCATE_DESCRIPTORS
-#   define MVK_CONFIG_PREALLOCATE_DESCRIPTORS    0
-#endif
-
-static bool _mvkPreallocateDescriptors = MVK_CONFIG_PREALLOCATE_DESCRIPTORS;
-static bool _mvkPreallocateDescriptorsInitialized = false;
-
-// Returns whether descriptors should be preallocated in the descriptor pools
-// We do this once lazily instead of in a library constructor function to
-// ensure the NSProcessInfo environment is available when called upon.
-static inline bool getMVKPreallocateDescriptors() {
-	if ( !_mvkPreallocateDescriptorsInitialized ) {
-		_mvkPreallocateDescriptorsInitialized = true;
-		MVK_SET_FROM_ENV_OR_BUILD_BOOL(_mvkPreallocateDescriptors, MVK_CONFIG_PREALLOCATE_DESCRIPTORS);
-	}
-	return _mvkPreallocateDescriptors;
-}
-
 template<class DescriptorClass>
 VkResult MVKDescriptorTypePreallocation<DescriptorClass>::allocateDescriptor(MVKDescriptor** pMVKDesc) {
 
@@ -698,7 +680,7 @@
 MVKDescriptorPool::MVKDescriptorPool(MVKDevice* device,
 									 const VkDescriptorPoolCreateInfo* pCreateInfo) : MVKVulkanAPIDeviceObject(device) {
 	_maxSets = pCreateInfo->maxSets;
-	_preallocatedDescriptors = getMVKPreallocateDescriptors() ? new MVKPreallocatedDescriptors(pCreateInfo) : nullptr;
+	_preallocatedDescriptors = mvkGetMVKConfiguration()->preallocateDescriptors ? new MVKPreallocatedDescriptors(pCreateInfo) : nullptr;
 }
 
 // Destroy all allocated descriptor sets and preallocated descriptors
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
index 7bd2e81..e45d810 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.h
@@ -27,7 +27,6 @@
 #include "MVKPixelFormats.h"
 #include "MVKOSExtensions.h"
 #include "mvk_datatypes.hpp"
-#include "vk_mvk_moltenvk.h"
 #include <string>
 #include <mutex>
 
@@ -615,7 +614,7 @@
 	 * number of nanoseconds between the two calls. The convenience function mvkGetElapsedMilliseconds()
 	 * can be used to perform this calculation.
      */
-    inline uint64_t getPerformanceTimestamp() { return _pMVKConfig->performanceTracking ? mvkGetTimestamp() : 0; }
+    inline uint64_t getPerformanceTimestamp() { return _isPerformanceTracking ? mvkGetTimestamp() : 0; }
 
     /**
      * If performance is being tracked, adds the performance for an activity with a duration
@@ -625,7 +624,7 @@
      */
     inline void addActivityPerformance(MVKPerformanceTracker& activityTracker,
 									   uint64_t startTime, uint64_t endTime = 0) {
-		if (_pMVKConfig->performanceTracking) {
+		if (_isPerformanceTracking) {
 			updateActivityPerformance(activityTracker, startTime, endTime);
 
 			// Log call not locked. Very minor chance that the tracker data will be updated during log call,
@@ -690,9 +689,6 @@
 
 #pragma mark Properties directly accessible
 
-	/** Pointer to the MoltenVK configuration settings. */
-	const MVKConfiguration* _pMVKConfig;
-
 	/** Device features available and enabled. */
 	const VkPhysicalDeviceFeatures _enabledFeatures;
 	const VkPhysicalDevice16BitStorageFeatures _enabledStorage16Features;
@@ -795,8 +791,8 @@
     std::mutex _vizLock;
 	bool _useMTLFenceForSemaphores;
 	bool _useMTLEventForSemaphores;
-	bool _useCommandPooling;
 	bool _logActivityPerformanceInline;
+	bool _isPerformanceTracking;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
index 298f4c8..bdcbe15 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKDevice.mm
@@ -34,7 +34,6 @@
 #include "MVKCodec.h"
 #include "MVKEnvironment.h"
 #include <MoltenVKShaderConverter/SPIRVToMSLConverter.h>
-#include "vk_mvk_moltenvk.h"
 
 #import "CAMetalLayer+MoltenVK.h"
 
@@ -241,7 +240,7 @@
 				portabilityFeatures->events = true;
 				portabilityFeatures->imageViewFormatReinterpretation = true;
 				portabilityFeatures->imageViewFormatSwizzle = (_metalFeatures.nativeTextureSwizzle ||
-															   _mvkInstance->getMoltenVKConfiguration()->fullImageViewSwizzle);
+															   mvkGetMVKConfiguration()->fullImageViewSwizzle);
 				portabilityFeatures->imageView2DOn3DImage = false;
 				portabilityFeatures->multisampleArrayImage = _metalFeatures.multisampleArrayTextures;
 				portabilityFeatures->mutableComparisonSamplers = _metalFeatures.depthSampleCompare;
@@ -561,7 +560,7 @@
 		case VK_IMAGE_TYPE_1D:
 			maxExt.height = 1;
 			maxExt.depth = 1;
-			if (!mvkTreatTexture1DAs2D()) {
+			if (!mvkGetMVKConfiguration()->texture1DAs2D) {
 				maxExt.width = pLimits->maxImageDimension1D;
 				maxLevels = 1;
 				sampleCounts = VK_SAMPLE_COUNT_1_BIT;
@@ -1021,7 +1020,7 @@
 MVKArrayRef<MVKQueueFamily*> MVKPhysicalDevice::getQueueFamilies() {
 	if (_queueFamilies.empty()) {
 		VkQueueFamilyProperties qfProps;
-		bool specialize = _mvkInstance->getMoltenVKConfiguration()->specializedQueueFamilies;
+		bool specialize = mvkGetMVKConfiguration()->specializedQueueFamilies;
 		uint32_t qfIdx = 0;
 
 		qfProps.queueCount = kMVKQueueCountPerQueueFamily;
@@ -1160,12 +1159,6 @@
 // Initializes the Metal-specific physical device features of this instance.
 void MVKPhysicalDevice::initMetalFeatures() {
 
-#	ifndef MVK_CONFIG_USE_MTLHEAP
-#   	define MVK_CONFIG_USE_MTLHEAP    0
-#	endif
-	bool useMTLHeaps;
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL(useMTLHeaps, MVK_CONFIG_USE_MTLHEAP);
-
 	// Start with all Metal features cleared
 	mvkClear(&_metalFeatures);
 
@@ -1236,7 +1229,7 @@
 
 	if ( mvkOSVersionIsAtLeast(13.0) ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_2;
-		_metalFeatures.placementHeaps = useMTLHeaps;
+		_metalFeatures.placementHeaps = mvkGetMVKConfiguration()->useMTLHeap;
 		_metalFeatures.nativeTextureSwizzle = true;
 		if (supportsMTLGPUFamily(Apple3)) {
 			_metalFeatures.native3DCompressedTextures = true;
@@ -1327,7 +1320,7 @@
 
 	if ( mvkOSVersionIsAtLeast(13.0) ) {
 		_metalFeatures.mslVersionEnum = MTLLanguageVersion2_2;
-		_metalFeatures.placementHeaps = useMTLHeaps;
+		_metalFeatures.placementHeaps = mvkGetMVKConfiguration()->useMTLHeap;
 		_metalFeatures.nativeTextureSwizzle = true;
 		if (supportsMTLGPUFamily(Apple3)) {
 			_metalFeatures.native3DCompressedTextures = true;
@@ -1425,7 +1418,7 @@
         }
 		if (supportsMTLGPUFamily(Mac2)) {
 			_metalFeatures.nativeTextureSwizzle = true;
-			_metalFeatures.placementHeaps = useMTLHeaps;
+			_metalFeatures.placementHeaps = mvkGetMVKConfiguration()->useMTLHeap;
 		}
 	}
 
@@ -3091,7 +3084,7 @@
 	// MTLCreateSystemDefaultDevice function, and if that GPU is the same as the
 	// selected GPU, update the MTLDevice instance used by the MVKPhysicalDevice.
 	id<MTLDevice> mtlDevice = _physicalDevice->getMTLDevice();
-	if (_pMVKConfig->switchSystemGPU && !(mtlDevice.isLowPower || mtlDevice.isHeadless) ) {
+	if (mvkGetMVKConfiguration()->switchSystemGPU && !(mtlDevice.isLowPower || mtlDevice.isHeadless) ) {
 		id<MTLDevice> sysMTLDevice = MTLCreateSystemDefaultDevice();
 		if (mvkGetRegistryID(sysMTLDevice) == mvkGetRegistryID(mtlDevice)) {
 			_physicalDevice->replaceMTLDevice(sysMTLDevice);
@@ -3373,7 +3366,7 @@
 
 MVKCommandPool* MVKDevice::createCommandPool(const VkCommandPoolCreateInfo* pCreateInfo,
 											const VkAllocationCallbacks* pAllocator) {
-	return new MVKCommandPool(this, pCreateInfo, _useCommandPooling);
+	return new MVKCommandPool(this, pCreateInfo, mvkGetMVKConfiguration()->useCommandPooling);
 }
 
 void MVKDevice::destroyCommandPool(MVKCommandPool* mvkCmdPool,
@@ -3648,7 +3641,7 @@
 
 // Can't use prefilled Metal command buffers if any of the resource descriptors can be updated after binding.
 bool MVKDevice::shouldPrefillMTLCommandBuffers() {
-	return (_pMVKConfig->prefillMetalCommandBuffers &&
+	return (mvkGetMVKConfiguration()->prefillMetalCommandBuffers &&
 			!(_enabledDescriptorIndexingFeatures.descriptorBindingUniformBufferUpdateAfterBind ||
 			  _enabledDescriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind ||
 			  _enabledDescriptorIndexingFeatures.descriptorBindingStorageImageUpdateAfterBind ||
@@ -3706,9 +3699,10 @@
 // suppress deprecation warnings for startCaptureWithDevice: on MacCatalyst.
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
-	if (getInstance()->_autoGPUCaptureScope == MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE_DEVICE) {
+	if (mvkGetMVKConfiguration()->autoGPUCaptureScope == MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE_DEVICE) {
 		MTLCaptureManager *captureMgr = [MTLCaptureManager sharedCaptureManager];
-		if (!getInstance()->_autoGPUCaptureOutputFile.empty()) {
+		char* filePath = mvkGetMVKConfiguration()->autoGPUCaptureOutputFilepath;
+		if (strlen(filePath)) {
 			if ( ![captureMgr respondsToSelector: @selector(supportsDestination:)] ||
 				 ![captureMgr supportsDestination: MTLCaptureDestinationGPUTraceDocument] ) {
 				reportError(VK_ERROR_FEATURE_NOT_PRESENT, "Capturing GPU traces to a file requires macOS 10.15 or iOS 13.0. Falling back to Xcode GPU capture.");
@@ -3719,11 +3713,11 @@
 				MTLCaptureDescriptor *captureDesc = [MTLCaptureDescriptor new];
 				captureDesc.captureObject = getMTLDevice();
 				captureDesc.destination = MTLCaptureDestinationGPUTraceDocument;
-				path = [NSString stringWithUTF8String: getInstance()->_autoGPUCaptureOutputFile.c_str()];
+				path = [NSString stringWithUTF8String: filePath];
 				expandedPath = path.stringByExpandingTildeInPath;
 				captureDesc.outputURL = [NSURL fileURLWithPath: expandedPath];
 				if (![captureMgr startCaptureWithDescriptor: captureDesc error: &err]) {
-					reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to start GPU capture session to %s (Error code %li): %s", getInstance()->_autoGPUCaptureOutputFile.c_str(), (long)err.code, err.localizedDescription.UTF8String);
+					reportError(VK_ERROR_INITIALIZATION_FAILED, "Failed to start GPU capture session to %s (Error code %li): %s", filePath, (long)err.code, err.localizedDescription.UTF8String);
 					[err release];
 				}
 				[captureDesc.outputURL release];
@@ -3744,10 +3738,9 @@
 }
 
 void MVKDevice::initPerformanceTracking() {
-#	ifndef MVK_CONFIG_PERFORMANCE_LOGGING_INLINE
-#   	define MVK_CONFIG_PERFORMANCE_LOGGING_INLINE    0
-#	endif
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL(_logActivityPerformanceInline, MVK_CONFIG_PERFORMANCE_LOGGING_INLINE);
+
+	_isPerformanceTracking = mvkGetMVKConfiguration()->performanceTracking;
+	_logActivityPerformanceInline = mvkGetMVKConfiguration()->logActivityPerformanceInline;
 
 	MVKPerformanceTracker initPerf;
     initPerf.count = 0;
@@ -3793,32 +3786,15 @@
 	else
 		_physicalDevice = physicalDevice;
 
-	_pMVKConfig = _physicalDevice->_mvkInstance->getMoltenVKConfiguration();
 	_pMetalFeatures = _physicalDevice->getMetalFeatures();
 	_pProperties = &_physicalDevice->_properties;
 	_pMemoryProperties = &_physicalDevice->_memoryProperties;
 
-	// Indicates whether semaphores should use a MTLFence if available.
-	// Set by the MVK_ALLOW_METAL_FENCES environment variable if MTLFences are available.
-	// This should be a temporary fix after some repair to semaphore handling.
-	_useMTLFenceForSemaphores = false;
-	if (_pMetalFeatures->fences) {
-		MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLFenceForSemaphores, MVK_ALLOW_METAL_FENCES);
-	}
+	// Indicate whether semaphores should use a MTLFence or MTLEvent if they are available.
+	_useMTLFenceForSemaphores = _pMetalFeatures->fences && mvkGetMVKConfiguration()->semaphoreUseMTLFence;
+	_useMTLEventForSemaphores = _pMetalFeatures->events && mvkGetMVKConfiguration()->semaphoreUseMTLEvent;
 
-	// Indicates whether semaphores should use a MTLEvent if available.
-	// Set by the MVK_ALLOW_METAL_EVENTS environment variable if MTLEvents are available.
-	// This should be a temporary fix after some repair to semaphore handling.
-	_useMTLEventForSemaphores = false;
-	if (_pMetalFeatures->events) {
-		MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useMTLEventForSemaphores, MVK_ALLOW_METAL_EVENTS);
-	}
 	MVKLogInfo("Using %s for Vulkan semaphores.", _useMTLFenceForSemaphores ? "MTLFence" : (_useMTLEventForSemaphores ? "MTLEvent" : "emulation"));
-
-#	ifndef MVK_CONFIG_USE_COMMAND_POOLING
-#   	define MVK_CONFIG_USE_COMMAND_POOLING    1
-#	endif
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL(_useCommandPooling, MVK_CONFIG_USE_COMMAND_POOLING);
 }
 
 void MVKDevice::enableFeatures(const VkDeviceCreateInfo* pCreateInfo) {
@@ -4100,7 +4076,7 @@
 void MVKDevice::initMTLCompileOptions() {
 	_mtlCompileOptions = [MTLCompileOptions new];	// retained
 	_mtlCompileOptions.languageVersion = _pMetalFeatures->mslVersionEnum;
-	_mtlCompileOptions.fastMathEnabled = _pMVKConfig->fastMathEnabled;
+	_mtlCompileOptions.fastMathEnabled = mvkGetMVKConfiguration()->fastMathEnabled;
 }
 
 MVKDevice::~MVKDevice() {
@@ -4113,7 +4089,7 @@
     [_globalVisibilityResultMTLBuffer release];
 	[_defaultMTLSamplerState release];
 
-	if (getInstance()->_autoGPUCaptureScope == MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE_DEVICE) {
+	if (mvkGetMVKConfiguration()->autoGPUCaptureScope == MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE_DEVICE) {
 		[[MTLCaptureManager sharedCaptureManager] stopCapture];
 	}
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
index 209a251..84198ab 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKImage.mm
@@ -983,7 +983,7 @@
 	if (validSamples == VK_SAMPLE_COUNT_1_BIT) { return validSamples; }
 
 	// Don't use getImageType() because it hasn't been set yet.
-	if ( !((pCreateInfo->imageType == VK_IMAGE_TYPE_2D) || ((pCreateInfo->imageType == VK_IMAGE_TYPE_1D) && mvkTreatTexture1DAs2D())) ) {
+	if ( !((pCreateInfo->imageType == VK_IMAGE_TYPE_2D) || ((pCreateInfo->imageType == VK_IMAGE_TYPE_1D) && mvkGetMVKConfiguration()->texture1DAs2D)) ) {
 		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."));
 		validSamples = VK_SAMPLE_COUNT_1_BIT;
 	}
@@ -1476,7 +1476,7 @@
 																				  _imageView->_usage,
 																				  _imageView,
 																				  _device->_pMetalFeatures->nativeTextureSwizzle,
-																				  _device->_pMVKConfig->fullImageViewSwizzle,
+																				  mvkGetMVKConfiguration()->fullImageViewSwizzle,
 																				  _mtlPixFmt,
 																				  useSwizzle));
     _packedSwizzle = (useSwizzle) ? mvkPackSwizzle(pCreateInfo->components) : 0;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
index 6709365..a1b59cf 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.h
@@ -22,7 +22,6 @@
 #include "MVKLayers.h"
 #include "MVKVulkanAPIObject.h"
 #include "MVKSmallVector.h"
-#include "vk_mvk_moltenvk.h"
 #include <unordered_map>
 #include <string>
 #include <mutex>
@@ -149,12 +148,6 @@
 	/** Returns whether debug callbacks are being used. */
 	bool hasDebugCallbacks() { return _hasDebugReportCallbacks || _hasDebugUtilsMessengers; }
 
-	/** Returns the MoltenVK configuration settings. */
-	const MVKConfiguration* getMoltenVKConfiguration() { return &_mvkConfig; }
-
-	/** Returns the MoltenVK configuration settings. */
-	void setMoltenVKConfiguration(MVKConfiguration* mvkConfig) { _mvkConfig = *mvkConfig; }
-
 	/** The list of Vulkan extensions, indicating whether each has been enabled by the app. */
 	const MVKExtensionList _enabledExtensions;
 
@@ -186,14 +179,13 @@
 	void propagateDebugName() override {}
 	void initProcAddrs();
 	void initDebugCallbacks(const VkInstanceCreateInfo* pCreateInfo);
+	NSArray<id<MTLDevice>>* getAvailableMTLDevicesArray();
 	VkDebugReportFlagsEXT getVkDebugReportFlagsFromASLLevel(int aslLvl);
 	VkDebugUtilsMessageSeverityFlagBitsEXT getVkDebugUtilsMessageSeverityFlagBitsFromASLLevel(int aslLvl);
 	MVKEntryPoint* getEntryPoint(const char* pName);
-	void initConfig();
     void logVersions();
 	VkResult verifyLayers(uint32_t count, const char* const* names);
 
-	MVKConfiguration _mvkConfig;
 	VkApplicationInfo _appInfo;
 	MVKSmallVector<MVKPhysicalDevice*, 2> _physicalDevices;
 	MVKSmallVector<MVKDebugReportCallback*> _debugReportCallbacks;
@@ -204,8 +196,6 @@
 	bool _hasDebugUtilsMessengers;
 	bool _useCreationCallbacks;
 	const char* _debugReportCallbackLayerPrefix;
-	int32_t _autoGPUCaptureScope;
-	std::string _autoGPUCaptureOutputFile;
 };
 
 
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
index fafe879..ff98440 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKInstance.mm
@@ -282,19 +282,17 @@
 
 #pragma mark Object Creation
 
-// Returns an autoreleased array containing the MTLDevices available on this system, sorted according to power,
-// with higher power GPU's at the front of the array. This ensures that a lazy app that simply
-// grabs the first GPU will get a high-power one by default. If the MVK_CONFIG_FORCE_LOW_POWER_GPU
-// env var or build setting is set, the returned array will only include low-power devices.
-// If Metal is not supported, returns an empty array.
-static NSArray<id<MTLDevice>>* availableMTLDevicesArray() {
+// Returns an autoreleased array containing the MTLDevices available on this system, sorted according
+// to power, with higher power GPU's at the front of the array. This ensures that a lazy app that simply
+// grabs the first GPU will get a high-power one by default. If MVKConfiguration::forceLowPowerGPU is set,
+// the returned array will only include low-power devices.
+NSArray<id<MTLDevice>>* MVKInstance::getAvailableMTLDevicesArray() {
 	NSMutableArray* mtlDevs = [NSMutableArray array];
 
 #if MVK_MACOS
 	NSArray* rawMTLDevs = [MTLCopyAllDevices() autorelease];
 	if (rawMTLDevs) {
-		bool forceLowPower;
-		MVK_SET_FROM_ENV_OR_BUILD_BOOL(forceLowPower, MVK_CONFIG_FORCE_LOW_POWER_GPU);
+		bool forceLowPower = mvkGetMVKConfiguration()->forceLowPowerGPU;
 
 		// Populate the array of appropriate MTLDevices
 		for (id<MTLDevice> md in rawMTLDevs) {
@@ -339,7 +337,6 @@
 	if (_appInfo.apiVersion == 0) { _appInfo.apiVersion = VK_API_VERSION_1_0; }	// Default
 
 	initProcAddrs();		// Init function pointers
-	initConfig();
 
 	setConfigurationResult(verifyLayers(pCreateInfo->enabledLayerCount, pCreateInfo->ppEnabledLayerNames));
 	MVKExtensionList* pWritableExtns = (MVKExtensionList*)&_enabledExtensions;
@@ -352,7 +349,7 @@
 	// This effort creates a number of autoreleased instances of Metal
 	// and other Obj-C classes, so wrap it all in an autorelease pool.
 	@autoreleasepool {
-		NSArray<id<MTLDevice>>* mtlDevices = availableMTLDevicesArray();
+		NSArray<id<MTLDevice>>* mtlDevices = getAvailableMTLDevicesArray();
 		_physicalDevices.reserve(mtlDevices.count);
 		for (id<MTLDevice> mtlDev in mtlDevices) {
 			_physicalDevices.push_back(new MVKPhysicalDevice(this, mtlDev));
@@ -691,30 +688,6 @@
 			   allExtns.enabledNamesString("\n\t\t", true).c_str());
 }
 
-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);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.synchronousQueueSubmits,                MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.prefillMetalCommandBuffers,             MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS);
-	MVK_SET_FROM_ENV_OR_BUILD_INT32(_mvkConfig.maxActiveMetalCommandBuffersPerQueue,   MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.supportLargeQueryPools,                 MVK_CONFIG_SUPPORT_LARGE_QUERY_POOLS);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.presentWithCommandBuffer,               MVK_CONFIG_PRESENT_WITH_COMMAND_BUFFER);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.swapchainMagFilterUseNearest,           MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST);
-	MVK_SET_FROM_ENV_OR_BUILD_INT64(_mvkConfig.metalCompileTimeout,                    MVK_CONFIG_METAL_COMPILE_TIMEOUT);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.performanceTracking,                    MVK_CONFIG_PERFORMANCE_TRACKING);
-	MVK_SET_FROM_ENV_OR_BUILD_INT32(_mvkConfig.performanceLoggingFrameCount,           MVK_CONFIG_PERFORMANCE_LOGGING_FRAME_COUNT);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.displayWatermark,                       MVK_CONFIG_DISPLAY_WATERMARK);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.specializedQueueFamilies,               MVK_CONFIG_SPECIALIZED_QUEUE_FAMILIES);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.switchSystemGPU,                        MVK_CONFIG_SWITCH_SYSTEM_GPU);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.fullImageViewSwizzle,                   MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.defaultGPUCaptureScopeQueueFamilyIndex, MVK_CONFIG_DEFAULT_GPU_CAPTURE_SCOPE_QUEUE_FAMILY_INDEX);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.defaultGPUCaptureScopeQueueIndex,       MVK_CONFIG_DEFAULT_GPU_CAPTURE_SCOPE_QUEUE_INDEX);
-	MVK_SET_FROM_ENV_OR_BUILD_BOOL( _mvkConfig.fastMathEnabled,                        MVK_CONFIG_FAST_MATH_ENABLED);
-
-	MVK_SET_FROM_ENV_OR_BUILD_INT32(_autoGPUCaptureScope, MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE);
-	MVK_SET_FROM_ENV_OR_BUILD_STRING(_autoGPUCaptureOutputFile, MVK_CONFIG_AUTO_GPU_CAPTURE_OUTPUT_FILE);
-}
-
 VkResult MVKInstance::verifyLayers(uint32_t count, const char* const* names) {
     VkResult result = VK_SUCCESS;
     for (uint32_t i = 0; i < count; i++) {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
index b21afd3..027803c 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKPipeline.mm
@@ -174,7 +174,7 @@
 	MVKVulkanAPIDeviceObject(device),
 	_pipelineCache(pipelineCache),
 	_pushConstantsMTLResourceIndexes(layout->getPushConstantBindings()),
-	_fullImageViewSwizzle(device->_pMVKConfig->fullImageViewSwizzle) {}
+	_fullImageViewSwizzle(mvkGetMVKConfiguration()->fullImageViewSwizzle) {}
 
 
 #pragma mark -
@@ -1477,11 +1477,11 @@
 		}
 	}
 
-	shaderContext.options.mslOptions.texture_1D_as_2D = mvkTreatTexture1DAs2D();
+	shaderContext.options.mslOptions.texture_1D_as_2D = mvkGetMVKConfiguration()->texture1DAs2D;
     shaderContext.options.mslOptions.enable_point_size_builtin = isRenderingPoints(pCreateInfo) || reflectData.pointMode;
 	shaderContext.options.mslOptions.enable_frag_depth_builtin = pixFmts->isDepthFormat(mtlDSFormat);
 	shaderContext.options.mslOptions.enable_frag_stencil_ref_builtin = pixFmts->isStencilFormat(mtlDSFormat);
-    shaderContext.options.shouldFlipVertexY = _device->_pMVKConfig->shaderConversionFlipVertexY;
+    shaderContext.options.shouldFlipVertexY = mvkGetMVKConfiguration()->shaderConversionFlipVertexY;
     shaderContext.options.mslOptions.swizzle_texture_samples = _fullImageViewSwizzle && !getDevice()->_pMetalFeatures->nativeTextureSwizzle;
     shaderContext.options.mslOptions.tess_domain_origin_lower_left = pTessDomainOriginState && pTessDomainOriginState->domainOrigin == VK_TESSELLATION_DOMAIN_ORIGIN_LOWER_LEFT;
     shaderContext.options.mslOptions.multiview = mvkRendPass->isMultiview();
@@ -1692,7 +1692,7 @@
 	shaderContext.options.mslOptions.swizzle_texture_samples = _fullImageViewSwizzle && !getDevice()->_pMetalFeatures->nativeTextureSwizzle;
 	shaderContext.options.mslOptions.texture_buffer_native = _device->_pMetalFeatures->textureBuffers;
 	shaderContext.options.mslOptions.dispatch_base = _allowsDispatchBase;
-	shaderContext.options.mslOptions.texture_1D_as_2D = mvkTreatTexture1DAs2D();
+	shaderContext.options.mslOptions.texture_1D_as_2D = mvkGetMVKConfiguration()->texture1DAs2D;
     shaderContext.options.mslOptions.fixed_subgroup_size = mvkIsAnyFlagEnabled(pSS->flags, VK_PIPELINE_SHADER_STAGE_CREATE_ALLOW_VARYING_SUBGROUP_SIZE_BIT_EXT) ? 0 : _device->_pMetalFeatures->maxSubgroupSize;
 #if MVK_MACOS
     shaderContext.options.mslOptions.emulate_subgroups = !_device->_pMetalFeatures->simdPermute;
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm
index b4318f0..05073cb 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueryPool.mm
@@ -317,7 +317,7 @@
 MVKOcclusionQueryPool::MVKOcclusionQueryPool(MVKDevice* device,
                                              const VkQueryPoolCreateInfo* pCreateInfo) : MVKQueryPool(device, pCreateInfo, 1) {
 
-    if (_device->_pMVKConfig->supportLargeQueryPools) {
+    if (mvkGetMVKConfiguration()->supportLargeQueryPools) {
         _queryIndexOffset = 0;
 
         // Ensure we don't overflow the maximum number of queries
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
index 0c0c270..f304297 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKQueue.mm
@@ -38,7 +38,7 @@
 	id<MTLCommandQueue> mtlQ = _mtlQueues[queueIndex];
 	if ( !mtlQ ) {
 		@autoreleasepool {		// Catch any autoreleased objects created during MTLCommandQueue creation
-			uint32_t maxCmdBuffs = _physicalDevice->getInstance()->getMoltenVKConfiguration()->maxActiveMetalCommandBuffersPerQueue;
+			uint32_t maxCmdBuffs = mvkGetMVKConfiguration()->maxActiveMetalCommandBuffersPerQueue;
 			mtlQ = [_physicalDevice->getMTLDevice() newCommandQueueWithMaxCommandBufferCount: maxCmdBuffs];		// retained
 			_mtlQueues[queueIndex] = mtlQ;
 		}
@@ -172,7 +172,7 @@
 
 void MVKQueue::initExecQueue() {
 	_execQueue = nil;
-	if ( !_device->_pMVKConfig->synchronousQueueSubmits ) {
+	if ( !mvkGetMVKConfiguration()->synchronousQueueSubmits ) {
 		// Determine the dispatch queue priority
 		dispatch_qos_class_t dqQOS = MVK_DISPATCH_QUEUE_QOS_CLASS;
 		int dqPriority = (1.0 - _priority) * QOS_MIN_RELATIVE_PRIORITY;
@@ -192,12 +192,10 @@
 
 // Initializes Xcode GPU capture scopes
 void MVKQueue::initGPUCaptureScopes() {
-	const MVKConfiguration* pMVKConfig = getInstance()->getMoltenVKConfiguration();
-
 	_submissionCaptureScope = new MVKGPUCaptureScope(this);
 
-	if (_queueFamily->getIndex() == pMVKConfig->defaultGPUCaptureScopeQueueFamilyIndex &&
-		_index == pMVKConfig->defaultGPUCaptureScopeQueueIndex) {
+	if (_queueFamily->getIndex() == mvkGetMVKConfiguration()->defaultGPUCaptureScopeQueueFamilyIndex &&
+		_index == mvkGetMVKConfiguration()->defaultGPUCaptureScopeQueueIndex) {
 		_submissionCaptureScope->makeDefault();
 	}
 	_submissionCaptureScope->beginScope();	// Allow Xcode to capture the first frame if desired.
@@ -224,7 +222,7 @@
 									   uint32_t waitSemaphoreCount,
 									   const VkSemaphore* pWaitSemaphores) {
 	_queue = queue;
-	_trackPerformance = _queue->_device->_pMVKConfig->performanceTracking;
+	_trackPerformance = mvkGetMVKConfiguration()->performanceTracking;
 
 	_waitSemaphores.reserve(waitSemaphoreCount);
 	for (uint32_t i = 0; i < waitSemaphoreCount; i++) {
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
index 2cb0831..e88bfdd 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKShaderModule.mm
@@ -19,7 +19,6 @@
 #include "MVKShaderModule.h"
 #include "MVKPipeline.h"
 #include "MVKFoundation.h"
-#include "vk_mvk_moltenvk.h"
 #include <string>
 
 using namespace std;
@@ -274,7 +273,7 @@
 }
 
 bool MVKShaderModule::convert(SPIRVToMSLConversionConfiguration* pContext) {
-	bool shouldLogCode = _device->_pMVKConfig->debugMode;
+	bool shouldLogCode = mvkGetMVKConfiguration()->debugMode;
 	bool shouldLogEstimatedGLSL = shouldLogCode;
 
 	// If the SPIR-V converter does not have any code, but the GLSL converter does,
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
index 2fe04c9..db73b40 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSwapchain.mm
@@ -123,7 +123,7 @@
 
 // If the product has not been fully licensed, renders the watermark image to the surface.
 void MVKSwapchain::renderWatermark(id<MTLTexture> mtlTexture, id<MTLCommandBuffer> mtlCmdBuff) {
-    if (_device->_pMVKConfig->displayWatermark) {
+    if (mvkGetMVKConfiguration()->displayWatermark) {
         if ( !_licenseWatermark ) {
             _licenseWatermark = new MVKWatermarkRandom(getMTLDevice(),
                                                        __watermarkTextureContent,
@@ -144,7 +144,7 @@
 
 // Calculates and remembers the time interval between frames.
 void MVKSwapchain::markFrameInterval() {
-	if ( !(_device->_pMVKConfig->performanceTracking || _licenseWatermark) ) { return; }
+	if ( !(mvkGetMVKConfiguration()->performanceTracking || _licenseWatermark) ) { return; }
 
 	uint64_t prevFrameTime = _lastFrameTime;
 	_lastFrameTime = mvkGetTimestamp();
@@ -153,7 +153,7 @@
 
 	_device->addActivityPerformance(_device->_performanceStatistics.queue.frameInterval, prevFrameTime, _lastFrameTime);
 
-	uint32_t perfLogCntLimit = _device->_pMVKConfig->performanceLoggingFrameCount;
+	uint32_t perfLogCntLimit = mvkGetMVKConfiguration()->performanceLoggingFrameCount;
 	if ((perfLogCntLimit > 0) && (++_currentPerfLogFrameCount >= perfLogCntLimit)) {
 		_currentPerfLogFrameCount = 0;
 		MVKLogInfo("Performance statistics reporting every: %d frames, avg FPS: %.2f, elapsed time: %.3f seconds:",
@@ -271,7 +271,7 @@
 	_mtlLayer.pixelFormat = getPixelFormats()->getMTLPixelFormat(pCreateInfo->imageFormat);
 	_mtlLayer.maximumDrawableCountMVK = imgCnt;
 	_mtlLayer.displaySyncEnabledMVK = (pCreateInfo->presentMode != VK_PRESENT_MODE_IMMEDIATE_KHR);
-	_mtlLayer.magnificationFilter = _device->_pMVKConfig->swapchainMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear;
+	_mtlLayer.magnificationFilter = mvkGetMVKConfiguration()->swapchainMagFilterUseNearest ? kCAFilterNearest : kCAFilterLinear;
 	_mtlLayer.framebufferOnly = !mvkIsAnyFlagEnabled(pCreateInfo->imageUsage, (VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
 																			   VK_IMAGE_USAGE_TRANSFER_DST_BIT |
 																			   VK_IMAGE_USAGE_SAMPLED_BIT |
diff --git a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
index 95a687c..c9d26e1 100644
--- a/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
+++ b/MoltenVK/MoltenVK/GPUObjects/MVKSync.mm
@@ -521,7 +521,7 @@
 	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @autoreleasepool { block(); } });
 
 	// Limit timeout to avoid overflow since wait_for() uses wait_until()
-	chrono::nanoseconds nanoTimeout(min(mvkDev->_pMVKConfig->metalCompileTimeout, kMVKUndefinedLargeUInt64));
+	chrono::nanoseconds nanoTimeout(min(mvkGetMVKConfiguration()->metalCompileTimeout, kMVKUndefinedLargeUInt64));
 	_blocker.wait_for(lock, nanoTimeout, [this]{ return _isCompileDone; });
 
 	if ( !_isCompileDone ) {
diff --git a/MoltenVK/MoltenVK/Layers/MVKExtensions.mm b/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
index 3651ebb..45d342a 100644
--- a/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
+++ b/MoltenVK/MoltenVK/Layers/MVKExtensions.mm
@@ -20,7 +20,6 @@
 #include "MVKFoundation.h"
 #include "MVKOSExtensions.h"
 #include "MVKEnvironment.h"
-#include "vk_mvk_moltenvk.h"
 #include <vulkan/vulkan_ios.h>
 #include <vulkan/vulkan_macos.h>
 
diff --git a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
index dc5b196..2adaa5d 100644
--- a/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/CAMetalLayer+MoltenVK.m
@@ -18,7 +18,7 @@
 
 
 #include "CAMetalLayer+MoltenVK.h"
-#include "MVKEnvironment.h"
+#include "MVKCommonEnvironment.h"
 
 @implementation CAMetalLayer (MoltenVK)
 
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m
index a819c6c..bc0e393 100644
--- a/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassDepthAttachmentDescriptor+MoltenVK.m
@@ -18,25 +18,21 @@
 
 
 #include "MTLRenderPassDepthAttachmentDescriptor+MoltenVK.h"
-#include "MVKEnvironment.h"
+
 
 @implementation MTLRenderPassDepthAttachmentDescriptor (MoltenVK)
 
 -(MTLMultisampleDepthResolveFilter) depthResolveFilterMVK {
-
 	if ( [self respondsToSelector: @selector(depthResolveFilter)] ) {
 		return self.depthResolveFilter;
 	}
 	return MTLMultisampleDepthResolveFilterSample0;
-
 }
 
 -(void) setDepthResolveFilterMVK: (MTLMultisampleDepthResolveFilter) filter {
-
 	if ( [self respondsToSelector: @selector(setDepthResolveFilter:)] ) {
 		self.depthResolveFilter = filter;
 	}
-
 }
 
 @end
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m
index 60fb87d..8f7b7e4 100644
--- a/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassDescriptor+MoltenVK.m
@@ -18,7 +18,7 @@
 
 
 #include "MTLRenderPassDescriptor+MoltenVK.h"
-#include "MVKEnvironment.h"
+#include "MVKCommonEnvironment.h"
 
 @implementation MTLRenderPassDescriptor (MoltenVK)
 
diff --git a/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m
index 878725b..03fb034 100644
--- a/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/MTLRenderPassStencilAttachmentDescriptor+MoltenVK.m
@@ -18,25 +18,21 @@
 
 
 #include "MTLRenderPassStencilAttachmentDescriptor+MoltenVK.h"
-#include "MVKEnvironment.h"
+
 
 @implementation MTLRenderPassStencilAttachmentDescriptor (MoltenVK)
 
 -(MTLMultisampleStencilResolveFilter) stencilResolveFilterMVK {
-
 	if ( [self respondsToSelector: @selector(stencilResolveFilter)] ) {
 		return self.stencilResolveFilter;
 	}
 	return MTLMultisampleStencilResolveFilterSample0;
-
 }
 
 -(void) setStencilResolveFilterMVK: (MTLMultisampleStencilResolveFilter) filter {
-
 	if ( [self respondsToSelector: @selector(setStencilResolveFilter:)] ) {
 		self.stencilResolveFilter = filter;
 	}
-
 }
 
 @end
diff --git a/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m b/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m
index 8b9a842..1cbbdc0 100644
--- a/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m
+++ b/MoltenVK/MoltenVK/OS/MTLSamplerDescriptor+MoltenVK.m
@@ -18,7 +18,8 @@
 
 
 #include "MTLSamplerDescriptor+MoltenVK.h"
-#include "MVKEnvironment.h"
+#include "MVKCommonEnvironment.h"
+
 
 @implementation MTLSamplerDescriptor (MoltenVK)
 
diff --git a/MoltenVK/MoltenVK/Utility/MVKBaseObject.mm b/MoltenVK/MoltenVK/Utility/MVKBaseObject.mm
index 1fe294a..8d52b74 100644
--- a/MoltenVK/MoltenVK/Utility/MVKBaseObject.mm
+++ b/MoltenVK/MoltenVK/Utility/MVKBaseObject.mm
@@ -26,28 +26,6 @@
 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;
-static bool _mvkLoggingInitialized = false;
-
-// Returns log level from environment variable.
-// We do this once lazily instead of in a library constructor function to
-// ensure the NSProcessInfo environment is available when called upon.
-static inline uint32_t getMVKLogLevel() {
-	if ( !_mvkLoggingInitialized ) {
-		_mvkLoggingInitialized = true;
-		MVK_SET_FROM_ENV_OR_BUILD_INT32(_mvkLogLevel, MVK_CONFIG_LOG_LEVEL);
-	}
-	return _mvkLogLevel;
-}
-
 static const char* getReportingLevelString(int aslLvl) {
 	switch (aslLvl) {
 		case ASL_LEVEL_DEBUG:
@@ -101,7 +79,7 @@
 	MVKVulkanAPIObject* mvkAPIObj = mvkObj ? mvkObj->getVulkanAPIObject() : nullptr;
 	MVKInstance* mvkInst = mvkAPIObj ? mvkAPIObj->getInstance() : nullptr;
 	bool hasDebugCallbacks = mvkInst && mvkInst->hasDebugCallbacks();
-	bool shouldLog = (aslLvl < (getMVKLogLevel() << 2));
+	bool shouldLog = (aslLvl < (mvkGetMVKConfiguration()->logLevel << 2));
 
 	// Fail fast to avoid further unnecessary processing.
 	if ( !(shouldLog || hasDebugCallbacks) ) { return; }
diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp b/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp
new file mode 100644
index 0000000..16c11de
--- /dev/null
+++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.cpp
@@ -0,0 +1,82 @@
+/*
+ * MVKEnvironment.cpp
+ *
+ * Copyright (c) 2015-2021 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 "MVKEnvironment.h"
+#include "MVKOSExtensions.h"
+
+
+std::string _autoGPUCaptureOutputFile;
+static MVKConfiguration _mvkConfig;
+static bool _mvkConfigInitialized = false;
+
+static void mvkInitConfig() {
+	_mvkConfigInitialized = true;
+
+	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);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.synchronousQueueSubmits,                MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.prefillMetalCommandBuffers,             MVK_CONFIG_PREFILL_METAL_COMMAND_BUFFERS);
+	MVK_SET_FROM_ENV_OR_BUILD_INT32 (_mvkConfig.maxActiveMetalCommandBuffersPerQueue,   MVK_CONFIG_MAX_ACTIVE_METAL_COMMAND_BUFFERS_PER_QUEUE);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.supportLargeQueryPools,                 MVK_CONFIG_SUPPORT_LARGE_QUERY_POOLS);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.presentWithCommandBuffer,               MVK_CONFIG_PRESENT_WITH_COMMAND_BUFFER);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.swapchainMagFilterUseNearest,           MVK_CONFIG_SWAPCHAIN_MAG_FILTER_USE_NEAREST);
+	MVK_SET_FROM_ENV_OR_BUILD_INT64 (_mvkConfig.metalCompileTimeout,                    MVK_CONFIG_METAL_COMPILE_TIMEOUT);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.performanceTracking,                    MVK_CONFIG_PERFORMANCE_TRACKING);
+	MVK_SET_FROM_ENV_OR_BUILD_INT32 (_mvkConfig.performanceLoggingFrameCount,           MVK_CONFIG_PERFORMANCE_LOGGING_FRAME_COUNT);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.logActivityPerformanceInline,           MVK_CONFIG_PERFORMANCE_LOGGING_INLINE);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.displayWatermark,                       MVK_CONFIG_DISPLAY_WATERMARK);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.specializedQueueFamilies,               MVK_CONFIG_SPECIALIZED_QUEUE_FAMILIES);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.switchSystemGPU,                        MVK_CONFIG_SWITCH_SYSTEM_GPU);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.fullImageViewSwizzle,                   MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.defaultGPUCaptureScopeQueueFamilyIndex, MVK_CONFIG_DEFAULT_GPU_CAPTURE_SCOPE_QUEUE_FAMILY_INDEX);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.defaultGPUCaptureScopeQueueIndex,       MVK_CONFIG_DEFAULT_GPU_CAPTURE_SCOPE_QUEUE_INDEX);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.fastMathEnabled,                        MVK_CONFIG_FAST_MATH_ENABLED);
+
+	MVK_SET_FROM_ENV_OR_BUILD_INT32 (_mvkConfig.logLevel,                               MVK_CONFIG_LOG_LEVEL);
+	MVK_SET_FROM_ENV_OR_BUILD_INT32 (_mvkConfig.traceVulkanCalls,                       MVK_CONFIG_TRACE_VULKAN_CALLS);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.forceLowPowerGPU,                       MVK_CONFIG_FORCE_LOW_POWER_GPU);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.semaphoreUseMTLFence,                   MVK_ALLOW_METAL_FENCES);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.semaphoreUseMTLEvent,                   MVK_ALLOW_METAL_EVENTS);
+	MVK_SET_FROM_ENV_OR_BUILD_INT32 (_mvkConfig.autoGPUCaptureScope,                    MVK_CONFIG_AUTO_GPU_CAPTURE_SCOPE);
+	MVK_SET_FROM_ENV_OR_BUILD_STRING(_autoGPUCaptureOutputFile,                         MVK_CONFIG_AUTO_GPU_CAPTURE_OUTPUT_FILE);
+	_mvkConfig.autoGPUCaptureOutputFilepath = (char*)_autoGPUCaptureOutputFile.c_str();
+
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.texture1DAs2D,                          MVK_CONFIG_TEXTURE_1D_AS_2D);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.preallocateDescriptors,                 MVK_CONFIG_PREALLOCATE_DESCRIPTORS);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.useCommandPooling,                      MVK_CONFIG_USE_COMMAND_POOLING);
+	MVK_SET_FROM_ENV_OR_BUILD_BOOL  (_mvkConfig.useMTLHeap,                            MVK_CONFIG_USE_MTLHEAP);
+}
+
+// Returns the MoltenVK config, lazily initializing it if necessary.
+// We initialize lazily instead of in a library constructor function to
+// ensure the NSProcessInfo environment is available when called upon.
+const MVKConfiguration* mvkGetMVKConfiguration() {
+	if ( !_mvkConfigInitialized ) {
+		mvkInitConfig();
+	}
+	return &_mvkConfig;
+}
+
+// Updates config content, and updates any strings in MVKConfiguration by copying
+// the contents from the MVKConfiguration member to a corresponding std::string,
+// and then repointing the MVKConfiguration member to the contents of the std::string.
+void mvkSetMVKConfiguration(MVKConfiguration* pMVKConfig) {
+	_mvkConfig = *pMVKConfig;
+	_autoGPUCaptureOutputFile = _mvkConfig.autoGPUCaptureOutputFilepath;
+	_mvkConfig.autoGPUCaptureOutputFilepath = (char*)_autoGPUCaptureOutputFile.c_str();
+}
diff --git a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
index c53b60b..5eb6efe 100644
--- a/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
+++ b/MoltenVK/MoltenVK/Utility/MVKEnvironment.h
@@ -21,7 +21,7 @@
 
 #include "MVKCommonEnvironment.h"
 #include "MVKLogging.h"
-#include "mvk_vulkan.h"
+#include "vk_mvk_moltenvk.h"
 
 
 // Expose MoltenVK Apple surface extension functionality
@@ -52,6 +52,35 @@
                                                                     VK_VERSION_MINOR(api_ver),	\
                                                                     0)
 
+/**
+ * IOSurfaces are supported on macOS, and on iOS starting with iOS 11.
+ *
+ * To enable IOSurface support on iOS in MoltenVK, set the iOS Deployment Target
+ * (IPHONEOS_DEPLOYMENT_TARGET) build setting to 11.0 or greater when building
+ * MoltenVK, and any app that uses IOSurfaces.
+ */
+#if MVK_MACOS
+#	define MVK_SUPPORT_IOSURFACE_BOOL    1
+#endif
+
+#if MVK_IOS
+#	define MVK_SUPPORT_IOSURFACE_BOOL	(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0)
+#endif
+
+#if MVK_TVOS
+# define MVK_SUPPORT_IOSURFACE_BOOL (__TV_OS_VERSION_MIN_REQUIRED >= __TVOS_11_0)
+#endif
+
+
+#pragma mark -
+#pragma mark Global Configuration
+
+/** Global function to access MoltenVK configuration info. */
+const MVKConfiguration* mvkGetMVKConfiguration();
+
+/** Global function to update MoltenVK configuration info. */
+void mvkSetMVKConfiguration(MVKConfiguration* pMVKConfig);
+
 /** Flip the vertex coordinate in shaders. Enabled by default. */
 #ifndef MVK_CONFIG_SHADER_CONVERSION_FLIP_VERTEX_Y
 #   define MVK_CONFIG_SHADER_CONVERSION_FLIP_VERTEX_Y    1
@@ -67,13 +96,13 @@
  * MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS env var, or the config directly.
  */
 #if MVK_MACOS
-#   define MVK_MTLEVENT_MIN_OS  10.14
+#   define MVK_CONFIG_MTLEVENT_MIN_OS  10.14
 #endif
 #if MVK_IOS_OR_TVOS
-#   define MVK_MTLEVENT_MIN_OS  12.0
+#   define MVK_CONFIG_MTLEVENT_MIN_OS  12.0
 #endif
 #ifndef MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS
-#   define MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS    mvkOSVersionIsAtLeast(MVK_MTLEVENT_MIN_OS)
+#   define MVK_CONFIG_SYNCHRONOUS_QUEUE_SUBMITS    mvkOSVersionIsAtLeast(MVK_CONFIG_MTLEVENT_MIN_OS)
 #endif
 
 /** Fill a Metal command buffers when each Vulkan command buffer is filled. */
@@ -119,6 +148,11 @@
 #   define MVK_CONFIG_PERFORMANCE_LOGGING_FRAME_COUNT    0
 #endif
 
+/** Log activity performance every time an activity occurs. Disabled by default. */
+#	ifndef MVK_CONFIG_PERFORMANCE_LOGGING_INLINE
+#   	define MVK_CONFIG_PERFORMANCE_LOGGING_INLINE    0
+#	endif
+
 /** Display the MoltenVK logo watermark. Disabled by default. */
 #ifndef MVK_CONFIG_DISPLAY_WATERMARK
 #   define MVK_CONFIG_DISPLAY_WATERMARK    0
@@ -145,6 +179,28 @@
 #endif
 
 /**
+ * Set the logging level:
+ *   0 = None
+ *   1 = Errors only
+ *   2 = All
+ */
+#ifndef MVK_CONFIG_LOG_LEVEL
+#   define MVK_CONFIG_LOG_LEVEL    2
+#endif
+
+/**
+ * Set the Vulkan call logging level:
+ *   0: No Vulkan call logging.
+ *   1: Log the name of each Vulkan call when the call is entered.
+ *   2: Log the name of each Vulkan call when the call is entered and exited. This effectively
+ *      brackets any other logging activity within the scope of the Vulkan call.
+ *   3: Same as option 2, plus logs the time spent inside the Vulkan function.
+ */
+#ifndef MVK_CONFIG_TRACE_VULKAN_CALLS
+#   define MVK_CONFIG_TRACE_VULKAN_CALLS    0
+#endif
+
+/**
  * The index of the queue family whose presentation submissions will
  * be used as the default GPU Capture Scope during debugging in Xcode.
  */
@@ -206,21 +262,17 @@
 #   define MVK_CONFIG_TEXTURE_1D_AS_2D    1
 #endif
 
-/**
- * IOSurfaces are supported on macOS, and on iOS starting with iOS 11.
- *
- * To enable IOSurface support on iOS in MoltenVK, set the iOS Deployment Target
- * (IPHONEOS_DEPLOYMENT_TARGET) build setting to 11.0 or greater when building
- * MoltenVK, and any app that uses IOSurfaces.
- */
-#if MVK_MACOS
-#	define MVK_SUPPORT_IOSURFACE_BOOL    1
+/** Preallocate descriptors when creating VkDescriptorPool. Disabled by default. */
+#ifndef MVK_CONFIG_PREALLOCATE_DESCRIPTORS
+#   define MVK_CONFIG_PREALLOCATE_DESCRIPTORS    0
 #endif
 
-#if MVK_IOS
-#	define MVK_SUPPORT_IOSURFACE_BOOL	(__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0)
+/** Use pooling for command resources in a VkCommandPool. Enabled by default. */
+#ifndef MVK_CONFIG_USE_COMMAND_POOLING
+#  	define MVK_CONFIG_USE_COMMAND_POOLING    1
 #endif
 
-#if MVK_TVOS
-# define MVK_SUPPORT_IOSURFACE_BOOL (__TV_OS_VERSION_MIN_REQUIRED >= __TVOS_11_0)
-#endif
+/** Use MTLHeaps where possible when allocating MTLBuffers and MTLTextures. Disabled by default. */
+#	ifndef MVK_CONFIG_USE_MTLHEAP
+#   	define MVK_CONFIG_USE_MTLHEAP    0
+#	endif
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
index b1ac19e..8d408f5 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.hpp
@@ -79,11 +79,4 @@
 MTLTessellationPartitionMode mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(uint32_t spvMode, MVKBaseObject* mvkObj);
 #define mvkMTLTessellationPartitionModeFromSpvExecutionMode(spvMode) mvkMTLTessellationPartitionModeFromSpvExecutionModeInObj(spvMode, this)
 
-
-#pragma mark -
-#pragma mark Image properties
-
-/** Returns whether 1D textures should be treated as Metal 2D textures with height 1. */
-bool mvkTreatTexture1DAs2D();
-
 #endif
diff --git a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
index 9d5c19d..7cbc4fc 100644
--- a/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
+++ b/MoltenVK/MoltenVK/Vulkan/mvk_datatypes.mm
@@ -144,7 +144,7 @@
 																  bool isMultisample) {
 	switch (vkImageType) {
 		case VK_IMAGE_TYPE_3D: return MTLTextureType3D;
-		case VK_IMAGE_TYPE_1D: return (mvkTreatTexture1DAs2D()
+		case VK_IMAGE_TYPE_1D: return (mvkGetMVKConfiguration()->texture1DAs2D
 									   ? mvkMTLTextureTypeFromVkImageType(VK_IMAGE_TYPE_2D, arraySize, isMultisample)
 									   : (arraySize > 1 ? MTLTextureType1DArray : MTLTextureType1D));
 		case VK_IMAGE_TYPE_2D:
@@ -176,8 +176,8 @@
 		case VK_IMAGE_VIEW_TYPE_3D:			return MTLTextureType3D;
 		case VK_IMAGE_VIEW_TYPE_CUBE:		return MTLTextureTypeCube;
 		case VK_IMAGE_VIEW_TYPE_CUBE_ARRAY:	return MTLTextureTypeCubeArray;
-		case VK_IMAGE_VIEW_TYPE_1D:			return mvkTreatTexture1DAs2D() ? mvkMTLTextureTypeFromVkImageViewType(VK_IMAGE_VIEW_TYPE_2D, isMultisample) : MTLTextureType1D;
-		case VK_IMAGE_VIEW_TYPE_1D_ARRAY:	return mvkTreatTexture1DAs2D() ? mvkMTLTextureTypeFromVkImageViewType(VK_IMAGE_VIEW_TYPE_2D_ARRAY, isMultisample) : MTLTextureType1DArray;
+		case VK_IMAGE_VIEW_TYPE_1D:			return mvkGetMVKConfiguration()->texture1DAs2D ? mvkMTLTextureTypeFromVkImageViewType(VK_IMAGE_VIEW_TYPE_2D, isMultisample) : MTLTextureType1D;
+		case VK_IMAGE_VIEW_TYPE_1D_ARRAY:	return mvkGetMVKConfiguration()->texture1DAs2D ? mvkMTLTextureTypeFromVkImageViewType(VK_IMAGE_VIEW_TYPE_2D_ARRAY, isMultisample) : MTLTextureType1DArray;
 
 		case VK_IMAGE_VIEW_TYPE_2D_ARRAY:
 #if MVK_MACOS
@@ -766,18 +766,3 @@
 														   MTLCPUCacheMode mtlCPUCacheMode) {
 	return (mtlStorageMode << MTLResourceStorageModeShift) | (mtlCPUCacheMode << MTLResourceCPUCacheModeShift);
 }
-
-static bool _mvkTexture1DAs2D = MVK_CONFIG_TEXTURE_1D_AS_2D;
-static bool _mvkTexture1DAs2DInitialized = false;
-
-// Returns environment variable indicating whether to use Metal 2D textures for 1D textures.
-// We do this once lazily instead of in a library constructor function to
-// ensure the NSProcessInfo environment is available when called upon.
-bool mvkTreatTexture1DAs2D() {
-	if ( !_mvkTexture1DAs2DInitialized ) {
-		_mvkTexture1DAs2DInitialized = true;
-		MVK_SET_FROM_ENV_OR_BUILD_INT32(_mvkTexture1DAs2D, MVK_CONFIG_TEXTURE_1D_AS_2D);
-	}
-	return _mvkTexture1DAs2D;
-}
-
diff --git a/MoltenVK/MoltenVK/Vulkan/vk_mvk_moltenvk.mm b/MoltenVK/MoltenVK/Vulkan/vk_mvk_moltenvk.mm
index 1812200..02666b2 100644
--- a/MoltenVK/MoltenVK/Vulkan/vk_mvk_moltenvk.mm
+++ b/MoltenVK/MoltenVK/Vulkan/vk_mvk_moltenvk.mm
@@ -49,21 +49,22 @@
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkGetMoltenVKConfigurationMVK(
-	VkInstance                                  instance,
+	VkInstance                                  ignored,
 	MVKConfiguration*                           pConfiguration,
 	size_t*                                     pConfigurationSize) {
 
-	MVKInstance* mvkInst = MVKInstance::getMVKInstance(instance);
-	return mvkCopy(pConfiguration, mvkInst->getMoltenVKConfiguration(), pConfigurationSize);
+	return mvkCopy(pConfiguration, mvkGetMVKConfiguration(), pConfigurationSize);
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkSetMoltenVKConfigurationMVK(
-	VkInstance                                  instance,
+	VkInstance                                  ignored,
 	const MVKConfiguration*                     pConfiguration,
 	size_t*                                     pConfigurationSize) {
 
-	MVKInstance* mvkInst = MVKInstance::getMVKInstance(instance);
-	return mvkCopy((MVKConfiguration*)mvkInst->getMoltenVKConfiguration(), pConfiguration, pConfigurationSize);
+	MVKConfiguration mvkConfig;
+	VkResult rslt = mvkCopy(&mvkConfig, pConfiguration, pConfigurationSize);
+	mvkSetMVKConfiguration(&mvkConfig);
+	return rslt;
 }
 
 MVK_PUBLIC_SYMBOL VkResult vkGetPhysicalDeviceMetalFeaturesMVK(
diff --git a/MoltenVK/MoltenVK/Vulkan/vulkan.mm b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
index 14c56e7..f12ed43 100644
--- a/MoltenVK/MoltenVK/Vulkan/vulkan.mm
+++ b/MoltenVK/MoltenVK/Vulkan/vulkan.mm
@@ -56,23 +56,8 @@
 	MVKVulkanCallTraceLevelFunctionMax
 } MVKVulkanCallTraceLevel;
 
-#ifndef MVK_CONFIG_TRACE_VULKAN_CALLS
-#   define MVK_CONFIG_TRACE_VULKAN_CALLS    MVKVulkanCallTraceLevelNone
-#endif
-
-static uint32_t _mvkTraceVulkanCalls = MVK_CONFIG_TRACE_VULKAN_CALLS;
-static bool _mvkVulkanCallTracingInitialized = false;
-
 // Returns Vulkan call trace level from environment variable.
-// We do this once lazily instead of in a library constructor function to
-// ensure the NSProcessInfo environment is available when called upon.
-static inline uint32_t getCallTraceLevel() {
-	if ( !_mvkVulkanCallTracingInitialized ) {
-		_mvkVulkanCallTracingInitialized = true;
-		MVK_SET_FROM_ENV_OR_BUILD_INT32(_mvkTraceVulkanCalls, MVK_CONFIG_TRACE_VULKAN_CALLS);
-	}
-	return _mvkTraceVulkanCalls;
-}
+static inline uint32_t getCallTraceLevel() { return mvkGetMVKConfiguration()->traceVulkanCalls; }
 
 // Optionally log start of function calls to stderr
 static inline uint64_t MVKTraceVulkanCallStartImpl(const char* funcName) {