Sfoglia il codice sorgente

Merge pull request #68833 from BastiaanOlij/improve_extension_logic

Improve logic for detecting and tracking extensions
Rémi Verschelde 2 anni fa
parent
commit
eb8ce8d74e

+ 12 - 0
core/string/ustring.cpp

@@ -161,6 +161,18 @@ bool CharString::operator<(const CharString &p_right) const {
 	return is_str_less(get_data(), p_right.get_data());
 }
 
+bool CharString::operator==(const CharString &p_right) const {
+	if (length() == 0) {
+		// True if both have length 0, false if only p_right has a length
+		return p_right.length() == 0;
+	} else if (p_right.length() == 0) {
+		// False due to unequal length
+		return false;
+	}
+
+	return strcmp(ptr(), p_right.ptr()) == 0;
+}
+
 CharString &CharString::operator+=(char p_char) {
 	const int lhs_len = length();
 	resize(lhs_len + 2);

+ 1 - 0
core/string/ustring.h

@@ -156,6 +156,7 @@ public:
 
 	void operator=(const char *p_cstr);
 	bool operator<(const CharString &p_right) const;
+	bool operator==(const CharString &p_right) const;
 	CharString &operator+=(char p_char);
 	int length() const { return size() ? size() - 1 : 0; }
 	const char *get_data() const;

+ 1 - 0
core/templates/hashfuncs.h

@@ -310,6 +310,7 @@ struct HashMapHasherDefault {
 	static _FORCE_INLINE_ uint32_t hash(const char16_t p_uchar) { return hash_fmix32(p_uchar); }
 	static _FORCE_INLINE_ uint32_t hash(const char32_t p_uchar) { return hash_fmix32(p_uchar); }
 	static _FORCE_INLINE_ uint32_t hash(const RID &p_rid) { return hash_one_uint64(p_rid.get_id()); }
+	static _FORCE_INLINE_ uint32_t hash(const CharString &p_char_string) { return hash_djb2(p_char_string.ptr()); }
 	static _FORCE_INLINE_ uint32_t hash(const StringName &p_string_name) { return p_string_name.hash(); }
 	static _FORCE_INLINE_ uint32_t hash(const NodePath &p_path) { return p_path.hash(); }
 	static _FORCE_INLINE_ uint32_t hash(const ObjectID &p_id) { return hash_one_uint64(p_id); }

+ 320 - 285
drivers/vulkan/vulkan_context.cpp

@@ -68,7 +68,7 @@ Vector<VkAttachmentReference> VulkanContext::_convert_VkAttachmentReference2(uin
 }
 
 VkResult VulkanContext::vkCreateRenderPass2KHR(VkDevice p_device, const VkRenderPassCreateInfo2 *p_create_info, const VkAllocationCallbacks *p_allocator, VkRenderPass *p_render_pass) {
-	if (has_renderpass2_ext) {
+	if (is_device_extension_enabled(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) {
 		if (fpCreateRenderPass2KHR == nullptr) {
 			fpCreateRenderPass2KHR = (PFN_vkCreateRenderPass2KHR)vkGetDeviceProcAddr(p_device, "vkCreateRenderPass2KHR");
 		}
@@ -400,16 +400,28 @@ Error VulkanContext::_obtain_vulkan_version() {
 	return OK;
 }
 
-Error VulkanContext::_initialize_extensions() {
-	uint32_t instance_extension_count = 0;
+bool VulkanContext::instance_extensions_initialized = false;
+HashMap<CharString, bool> VulkanContext::requested_instance_extensions;
+
+void VulkanContext::register_requested_instance_extension(const CharString &extension_name, bool p_required) {
+	ERR_FAIL_COND_MSG(instance_extensions_initialized, "You can only registered extensions before the Vulkan instance is created");
+	ERR_FAIL_COND(requested_instance_extensions.has(extension_name));
+
+	requested_instance_extensions[extension_name] = p_required;
+}
+
+Error VulkanContext::_initialize_instance_extensions() {
+	enabled_instance_extension_names.clear();
 
-	enabled_extension_count = 0;
-	enabled_debug_utils = false;
-	enabled_debug_report = false;
-	// Look for instance extensions.
-	VkBool32 surfaceExtFound = 0;
-	VkBool32 platformSurfaceExtFound = 0;
-	memset(extension_names, 0, sizeof(extension_names));
+	// Make sure our core extensions are here
+	register_requested_instance_extension(VK_KHR_SURFACE_EXTENSION_NAME, true);
+	register_requested_instance_extension(_get_platform_surface_extension(), true);
+
+	if (_use_validation_layers()) {
+		register_requested_instance_extension(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, false);
+	}
+
+	register_requested_instance_extension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, false);
 
 	// Only enable debug utils in verbose mode or DEV_ENABLED.
 	// End users would get spammed with messages of varying verbosity due to the
@@ -420,54 +432,141 @@ Error VulkanContext::_initialize_extensions() {
 #else
 	bool want_debug_utils = OS::get_singleton()->is_stdout_verbose();
 #endif
+	if (want_debug_utils) {
+		register_requested_instance_extension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, false);
+	}
 
+	// Load instance extensions that are available...
+	uint32_t instance_extension_count = 0;
 	VkResult err = vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr);
 	ERR_FAIL_COND_V(err != VK_SUCCESS && err != VK_INCOMPLETE, ERR_CANT_CREATE);
+	ERR_FAIL_COND_V_MSG(instance_extension_count == 0, ERR_CANT_CREATE, "No instance extensions found, is a driver installed?");
 
-	if (instance_extension_count > 0) {
-		VkExtensionProperties *instance_extensions = (VkExtensionProperties *)malloc(sizeof(VkExtensionProperties) * instance_extension_count);
-		err = vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions);
-		if (err != VK_SUCCESS && err != VK_INCOMPLETE) {
-			free(instance_extensions);
-			ERR_FAIL_V(ERR_CANT_CREATE);
+	VkExtensionProperties *instance_extensions = (VkExtensionProperties *)malloc(sizeof(VkExtensionProperties) * instance_extension_count);
+	err = vkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions);
+	if (err != VK_SUCCESS && err != VK_INCOMPLETE) {
+		free(instance_extensions);
+		ERR_FAIL_V(ERR_CANT_CREATE);
+	}
+#ifdef DEV_ENABLED
+	for (uint32_t i = 0; i < instance_extension_count; i++) {
+		print_verbose(String("VULKAN: Found instance extension ") + String(instance_extensions[i].extensionName));
+	}
+#endif
+
+	// Enable all extensions that are supported and requested
+	for (uint32_t i = 0; i < instance_extension_count; i++) {
+		CharString extension_name(instance_extensions[i].extensionName);
+		if (requested_instance_extensions.has(extension_name)) {
+			enabled_instance_extension_names.insert(extension_name);
 		}
-		for (uint32_t i = 0; i < instance_extension_count; i++) {
-			if (!strcmp(VK_KHR_SURFACE_EXTENSION_NAME, instance_extensions[i].extensionName)) {
-				surfaceExtFound = 1;
-				extension_names[enabled_extension_count++] = VK_KHR_SURFACE_EXTENSION_NAME;
-			}
+	}
 
-			if (!strcmp(_get_platform_surface_extension(), instance_extensions[i].extensionName)) {
-				platformSurfaceExtFound = 1;
-				extension_names[enabled_extension_count++] = _get_platform_surface_extension();
-			}
-			if (!strcmp(VK_EXT_DEBUG_REPORT_EXTENSION_NAME, instance_extensions[i].extensionName)) {
-				if (_use_validation_layers()) {
-					extension_names[enabled_extension_count++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
-					enabled_debug_report = true;
-				}
-			}
-			if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, instance_extensions[i].extensionName)) {
-				if (want_debug_utils) {
-					extension_names[enabled_extension_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
-					enabled_debug_utils = true;
-				}
-			}
-			if (!strcmp(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, instance_extensions[i].extensionName)) {
-				extension_names[enabled_extension_count++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME;
-			}
-			if (enabled_extension_count >= MAX_EXTENSIONS) {
+	// Now check our requested extensions
+	for (KeyValue<CharString, bool> &requested_extension : requested_instance_extensions) {
+		if (!enabled_instance_extension_names.has(requested_extension.key)) {
+			if (requested_extension.value) {
 				free(instance_extensions);
-				ERR_FAIL_V_MSG(ERR_BUG, "Enabled extension count reaches MAX_EXTENSIONS, BUG");
+				ERR_FAIL_V_MSG(ERR_BUG, String("Required extension ") + String(requested_extension.key) + String(" not found, is a driver installed?"));
+			} else {
+				print_verbose(String("Optional extension ") + String(requested_extension.key) + String(" not found"));
 			}
 		}
+	}
 
-		free(instance_extensions);
+	free(instance_extensions);
+
+	instance_extensions_initialized = true;
+	return OK;
+}
+
+bool VulkanContext::device_extensions_initialized = false;
+HashMap<CharString, bool> VulkanContext::requested_device_extensions;
+
+void VulkanContext::register_requested_device_extension(const CharString &extension_name, bool p_required) {
+	ERR_FAIL_COND_MSG(device_extensions_initialized, "You can only registered extensions before the Vulkan instance is created");
+	ERR_FAIL_COND(requested_device_extensions.has(extension_name));
+
+	requested_device_extensions[extension_name] = p_required;
+}
+
+Error VulkanContext::_initialize_device_extensions() {
+	// Look for device extensions.
+	enabled_device_extension_names.clear();
+
+	// Make sure our core extensions are here
+	register_requested_device_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME, true);
+
+	register_requested_device_extension(VK_KHR_MULTIVIEW_EXTENSION_NAME, false);
+	register_requested_device_extension(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, false);
+	register_requested_device_extension(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, false);
+	register_requested_device_extension(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false);
+	register_requested_device_extension(VK_KHR_16BIT_STORAGE_EXTENSION_NAME, false);
+
+	// TODO consider the following extensions:
+	// - VK_KHR_spirv_1_4
+	// - VK_KHR_swapchain_mutable_format
+	// - VK_EXT_full_screen_exclusive
+	// - VK_EXT_hdr_metadata
+	// - VK_KHR_depth_stencil_resolve
+
+	// Even though the user "enabled" the extension via the command
+	// line, we must make sure that it's enumerated for use with the
+	// device.  Therefore, disable it here, and re-enable it again if
+	// enumerated.
+	if (VK_KHR_incremental_present_enabled) {
+		register_requested_device_extension(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, false);
+	}
+	if (VK_GOOGLE_display_timing_enabled) {
+		register_requested_device_extension(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, false);
+	}
+
+	// obtain available device extensions
+	uint32_t device_extension_count = 0;
+	VkResult err = vkEnumerateDeviceExtensionProperties(gpu, nullptr, &device_extension_count, nullptr);
+	ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
+	ERR_FAIL_COND_V_MSG(device_extension_count == 0, ERR_CANT_CREATE,
+			"vkEnumerateDeviceExtensionProperties failed to find any extensions\n\n"
+			"Do you have a compatible Vulkan installable client driver (ICD) installed?\n"
+			"vkCreateInstance Failure");
+
+	VkExtensionProperties *device_extensions = (VkExtensionProperties *)malloc(sizeof(VkExtensionProperties) * device_extension_count);
+	err = vkEnumerateDeviceExtensionProperties(gpu, nullptr, &device_extension_count, device_extensions);
+	if (err) {
+		free(device_extensions);
+		ERR_FAIL_V(ERR_CANT_CREATE);
+	}
+
+#ifdef DEV_ENABLED
+	for (uint32_t i = 0; i < device_extension_count; i++) {
+		print_verbose(String("VULKAN: Found device extension ") + String(device_extensions[i].extensionName));
+	}
+#endif
+
+	// Enable all extensions that are supported and requested
+	for (uint32_t i = 0; i < device_extension_count; i++) {
+		CharString extension_name(device_extensions[i].extensionName);
+		if (requested_device_extensions.has(extension_name)) {
+			enabled_device_extension_names.insert(extension_name);
+		}
+	}
+
+	// Now check our requested extensions
+	for (KeyValue<CharString, bool> &requested_extension : requested_device_extensions) {
+		if (!enabled_device_extension_names.has(requested_extension.key)) {
+			if (requested_extension.value) {
+				free(device_extensions);
+				ERR_FAIL_V_MSG(ERR_BUG,
+						String("vkEnumerateDeviceExtensionProperties failed to find the ") + String(requested_extension.key) + String(" extension.\n\nDo you have a compatible Vulkan installable client driver (ICD) installed?\nvkCreateInstance Failure"));
+			} else {
+				print_verbose(String("Optional extension ") + String(requested_extension.key) + String(" not found"));
+			}
+		}
 	}
 
-	ERR_FAIL_COND_V_MSG(!surfaceExtFound, ERR_CANT_CREATE, "No surface extension found, is a driver installed?");
-	ERR_FAIL_COND_V_MSG(!platformSurfaceExtFound, ERR_CANT_CREATE, "No platform surface extension found, is a driver installed?");
+	free(device_extensions);
 
+	device_extensions_initialized = true;
 	return OK;
 }
 
@@ -644,184 +743,176 @@ Error VulkanContext::_check_capabilities() {
 	storage_buffer_capabilities.storage_push_constant_16_is_supported = false;
 	storage_buffer_capabilities.storage_input_output_16 = false;
 
-	// Check for extended features.
-	PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2_func = (PFN_vkGetPhysicalDeviceFeatures2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceFeatures2");
-	if (vkGetPhysicalDeviceFeatures2_func == nullptr) {
-		// In Vulkan 1.0 might be accessible under its original extension name.
-		vkGetPhysicalDeviceFeatures2_func = (PFN_vkGetPhysicalDeviceFeatures2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceFeatures2KHR");
-	}
-	if (vkGetPhysicalDeviceFeatures2_func != nullptr) {
-		// Check our extended features.
-		VkPhysicalDeviceFragmentShadingRateFeaturesKHR vrs_features = {
-			/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR,
-			/*pNext*/ nullptr,
-			/*pipelineFragmentShadingRate*/ false,
-			/*primitiveFragmentShadingRate*/ false,
-			/*attachmentFragmentShadingRate*/ false,
-		};
+	if (is_instance_extension_enabled(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME)) {
+		// Check for extended features.
+		PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2_func = (PFN_vkGetPhysicalDeviceFeatures2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceFeatures2");
+		if (vkGetPhysicalDeviceFeatures2_func == nullptr) {
+			// In Vulkan 1.0 might be accessible under its original extension name.
+			vkGetPhysicalDeviceFeatures2_func = (PFN_vkGetPhysicalDeviceFeatures2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceFeatures2KHR");
+		}
+		if (vkGetPhysicalDeviceFeatures2_func != nullptr) {
+			// Check our extended features.
+			VkPhysicalDeviceFragmentShadingRateFeaturesKHR vrs_features = {
+				/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR,
+				/*pNext*/ nullptr,
+				/*pipelineFragmentShadingRate*/ false,
+				/*primitiveFragmentShadingRate*/ false,
+				/*attachmentFragmentShadingRate*/ false,
+			};
 
-		VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader_features = {
-			/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR,
-			/*pNext*/ &vrs_features,
-			/*shaderFloat16*/ false,
-			/*shaderInt8*/ false,
-		};
+			VkPhysicalDeviceShaderFloat16Int8FeaturesKHR shader_features = {
+				/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR,
+				/*pNext*/ &vrs_features,
+				/*shaderFloat16*/ false,
+				/*shaderInt8*/ false,
+			};
 
-		VkPhysicalDevice16BitStorageFeaturesKHR storage_feature = {
-			/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR,
-			/*pNext*/ &shader_features,
-			/*storageBuffer16BitAccess*/ false,
-			/*uniformAndStorageBuffer16BitAccess*/ false,
-			/*storagePushConstant16*/ false,
-			/*storageInputOutput16*/ false,
-		};
+			VkPhysicalDevice16BitStorageFeaturesKHR storage_feature = {
+				/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_16BIT_STORAGE_FEATURES_KHR,
+				/*pNext*/ &shader_features,
+				/*storageBuffer16BitAccess*/ false,
+				/*uniformAndStorageBuffer16BitAccess*/ false,
+				/*storagePushConstant16*/ false,
+				/*storageInputOutput16*/ false,
+			};
 
-		VkPhysicalDeviceMultiviewFeatures multiview_features = {
-			/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES,
-			/*pNext*/ &storage_feature,
-			/*multiview*/ false,
-			/*multiviewGeometryShader*/ false,
-			/*multiviewTessellationShader*/ false,
-		};
+			VkPhysicalDeviceMultiviewFeatures multiview_features = {
+				/*sType*/ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_FEATURES,
+				/*pNext*/ &storage_feature,
+				/*multiview*/ false,
+				/*multiviewGeometryShader*/ false,
+				/*multiviewTessellationShader*/ false,
+			};
 
-		VkPhysicalDeviceFeatures2 device_features;
-		device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
-		device_features.pNext = &multiview_features;
+			VkPhysicalDeviceFeatures2 device_features;
+			device_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
+			device_features.pNext = &multiview_features;
 
-		vkGetPhysicalDeviceFeatures2_func(gpu, &device_features);
+			vkGetPhysicalDeviceFeatures2_func(gpu, &device_features);
 
-		// We must check that the relative extension is present before assuming a
-		// feature as enabled. Actually, according to the spec we shouldn't add the
-		// structs in pNext at all, but this works fine.
-		// See also: https://github.com/godotengine/godot/issues/65409
-		for (uint32_t i = 0; i < enabled_extension_count; ++i) {
-			if (!strcmp(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, extension_names[i])) {
+			// We must check that the relative extension is present before assuming a
+			// feature as enabled. Actually, according to the spec we shouldn't add the
+			// structs in pNext at all, but this works fine.
+			// See also: https://github.com/godotengine/godot/issues/65409
+			if (is_device_extension_enabled(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME)) {
 				vrs_capabilities.pipeline_vrs_supported = vrs_features.pipelineFragmentShadingRate;
 				vrs_capabilities.primitive_vrs_supported = vrs_features.primitiveFragmentShadingRate;
 				vrs_capabilities.attachment_vrs_supported = vrs_features.attachmentFragmentShadingRate;
-
-				continue;
 			}
 
-			if (!strcmp(VK_KHR_MULTIVIEW_EXTENSION_NAME, extension_names[i])) {
+			if (is_device_extension_enabled(VK_KHR_MULTIVIEW_EXTENSION_NAME)) {
 				multiview_capabilities.is_supported = multiview_features.multiview;
 				multiview_capabilities.geometry_shader_is_supported = multiview_features.multiviewGeometryShader;
 				multiview_capabilities.tessellation_shader_is_supported = multiview_features.multiviewTessellationShader;
-
-				continue;
 			}
 
-			if (!strcmp(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, extension_names[i])) {
+			if (is_device_extension_enabled(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME)) {
 				shader_capabilities.shader_float16_is_supported = shader_features.shaderFloat16;
 				shader_capabilities.shader_int8_is_supported = shader_features.shaderInt8;
-
-				continue;
 			}
 
-			if (!strcmp(VK_KHR_16BIT_STORAGE_EXTENSION_NAME, extension_names[i])) {
+			if (is_device_extension_enabled(VK_KHR_16BIT_STORAGE_EXTENSION_NAME)) {
 				storage_buffer_capabilities.storage_buffer_16_bit_access_is_supported = storage_feature.storageBuffer16BitAccess;
 				storage_buffer_capabilities.uniform_and_storage_buffer_16_bit_access_is_supported = storage_feature.uniformAndStorageBuffer16BitAccess;
 				storage_buffer_capabilities.storage_push_constant_16_is_supported = storage_feature.storagePushConstant16;
 				storage_buffer_capabilities.storage_input_output_16 = storage_feature.storageInputOutput16;
-
-				continue;
 			}
 		}
-	}
 
-	// Check extended properties.
-	PFN_vkGetPhysicalDeviceProperties2 device_properties_func = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceProperties2");
-	if (device_properties_func == nullptr) {
-		// In Vulkan 1.0 might be accessible under its original extension name.
-		device_properties_func = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceProperties2KHR");
-	}
-	if (device_properties_func != nullptr) {
-		VkPhysicalDeviceFragmentShadingRatePropertiesKHR vrsProperties{};
-		VkPhysicalDeviceMultiviewProperties multiviewProperties{};
-		VkPhysicalDeviceSubgroupProperties subgroupProperties{};
-		VkPhysicalDeviceProperties2 physicalDeviceProperties{};
-		void *nextptr = nullptr;
-
-		if (!(vulkan_major == 1 && vulkan_minor == 0)) {
-			subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
-			subgroupProperties.pNext = nextptr;
-
-			nextptr = &subgroupProperties;
+		// Check extended properties.
+		PFN_vkGetPhysicalDeviceProperties2 device_properties_func = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceProperties2");
+		if (device_properties_func == nullptr) {
+			// In Vulkan 1.0 might be accessible under its original extension name.
+			device_properties_func = (PFN_vkGetPhysicalDeviceProperties2)vkGetInstanceProcAddr(inst, "vkGetPhysicalDeviceProperties2KHR");
 		}
+		if (device_properties_func != nullptr) {
+			VkPhysicalDeviceFragmentShadingRatePropertiesKHR vrsProperties{};
+			VkPhysicalDeviceMultiviewProperties multiviewProperties{};
+			VkPhysicalDeviceSubgroupProperties subgroupProperties{};
+			VkPhysicalDeviceProperties2 physicalDeviceProperties{};
+			void *nextptr = nullptr;
+
+			if (!(vulkan_major == 1 && vulkan_minor == 0)) {
+				subgroupProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SUBGROUP_PROPERTIES;
+				subgroupProperties.pNext = nextptr;
+
+				nextptr = &subgroupProperties;
+			}
 
-		if (multiview_capabilities.is_supported) {
-			multiviewProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;
-			multiviewProperties.pNext = nextptr;
+			if (multiview_capabilities.is_supported) {
+				multiviewProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MULTIVIEW_PROPERTIES;
+				multiviewProperties.pNext = nextptr;
 
-			nextptr = &multiviewProperties;
-		}
-
-		if (vrs_capabilities.attachment_vrs_supported) {
-			vrsProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
-			vrsProperties.pNext = nextptr;
+				nextptr = &multiviewProperties;
+			}
 
-			nextptr = &vrsProperties;
-		}
+			if (vrs_capabilities.attachment_vrs_supported) {
+				vrsProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_PROPERTIES_KHR;
+				vrsProperties.pNext = nextptr;
 
-		physicalDeviceProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
-		physicalDeviceProperties.pNext = nextptr;
+				nextptr = &vrsProperties;
+			}
 
-		device_properties_func(gpu, &physicalDeviceProperties);
+			physicalDeviceProperties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
+			physicalDeviceProperties.pNext = nextptr;
 
-		subgroup_capabilities.size = subgroupProperties.subgroupSize;
-		subgroup_capabilities.supportedStages = subgroupProperties.supportedStages;
-		subgroup_capabilities.supportedOperations = subgroupProperties.supportedOperations;
-		// Note: quadOperationsInAllStages will be true if:
-		// - supportedStages has VK_SHADER_STAGE_ALL_GRAPHICS + VK_SHADER_STAGE_COMPUTE_BIT.
-		// - supportedOperations has VK_SUBGROUP_FEATURE_QUAD_BIT.
-		subgroup_capabilities.quadOperationsInAllStages = subgroupProperties.quadOperationsInAllStages;
+			device_properties_func(gpu, &physicalDeviceProperties);
 
-		if (vrs_capabilities.pipeline_vrs_supported || vrs_capabilities.primitive_vrs_supported || vrs_capabilities.attachment_vrs_supported) {
-			print_verbose("- Vulkan Variable Rate Shading supported:");
-			if (vrs_capabilities.pipeline_vrs_supported) {
-				print_verbose("  Pipeline fragment shading rate");
-			}
-			if (vrs_capabilities.primitive_vrs_supported) {
-				print_verbose("  Primitive fragment shading rate");
-			}
-			if (vrs_capabilities.attachment_vrs_supported) {
-				// TODO expose these somehow to the end user.
-				vrs_capabilities.min_texel_size.x = vrsProperties.minFragmentShadingRateAttachmentTexelSize.width;
-				vrs_capabilities.min_texel_size.y = vrsProperties.minFragmentShadingRateAttachmentTexelSize.height;
-				vrs_capabilities.max_texel_size.x = vrsProperties.maxFragmentShadingRateAttachmentTexelSize.width;
-				vrs_capabilities.max_texel_size.y = vrsProperties.maxFragmentShadingRateAttachmentTexelSize.height;
+			subgroup_capabilities.size = subgroupProperties.subgroupSize;
+			subgroup_capabilities.supportedStages = subgroupProperties.supportedStages;
+			subgroup_capabilities.supportedOperations = subgroupProperties.supportedOperations;
+			// Note: quadOperationsInAllStages will be true if:
+			// - supportedStages has VK_SHADER_STAGE_ALL_GRAPHICS + VK_SHADER_STAGE_COMPUTE_BIT.
+			// - supportedOperations has VK_SUBGROUP_FEATURE_QUAD_BIT.
+			subgroup_capabilities.quadOperationsInAllStages = subgroupProperties.quadOperationsInAllStages;
 
-				// We'll attempt to default to a texel size of 16x16
-				vrs_capabilities.texel_size.x = CLAMP(16, vrs_capabilities.min_texel_size.x, vrs_capabilities.max_texel_size.x);
-				vrs_capabilities.texel_size.y = CLAMP(16, vrs_capabilities.min_texel_size.y, vrs_capabilities.max_texel_size.y);
+			if (vrs_capabilities.pipeline_vrs_supported || vrs_capabilities.primitive_vrs_supported || vrs_capabilities.attachment_vrs_supported) {
+				print_verbose("- Vulkan Variable Rate Shading supported:");
+				if (vrs_capabilities.pipeline_vrs_supported) {
+					print_verbose("  Pipeline fragment shading rate");
+				}
+				if (vrs_capabilities.primitive_vrs_supported) {
+					print_verbose("  Primitive fragment shading rate");
+				}
+				if (vrs_capabilities.attachment_vrs_supported) {
+					// TODO expose these somehow to the end user.
+					vrs_capabilities.min_texel_size.x = vrsProperties.minFragmentShadingRateAttachmentTexelSize.width;
+					vrs_capabilities.min_texel_size.y = vrsProperties.minFragmentShadingRateAttachmentTexelSize.height;
+					vrs_capabilities.max_texel_size.x = vrsProperties.maxFragmentShadingRateAttachmentTexelSize.width;
+					vrs_capabilities.max_texel_size.y = vrsProperties.maxFragmentShadingRateAttachmentTexelSize.height;
+
+					// We'll attempt to default to a texel size of 16x16
+					vrs_capabilities.texel_size.x = CLAMP(16, vrs_capabilities.min_texel_size.x, vrs_capabilities.max_texel_size.x);
+					vrs_capabilities.texel_size.y = CLAMP(16, vrs_capabilities.min_texel_size.y, vrs_capabilities.max_texel_size.y);
+
+					print_verbose(String("  Attachment fragment shading rate") + String(", min texel size: (") + itos(vrs_capabilities.min_texel_size.x) + String(", ") + itos(vrs_capabilities.min_texel_size.y) + String(")") + String(", max texel size: (") + itos(vrs_capabilities.max_texel_size.x) + String(", ") + itos(vrs_capabilities.max_texel_size.y) + String(")"));
+				}
 
-				print_verbose(String("  Attachment fragment shading rate") + String(", min texel size: (") + itos(vrs_capabilities.min_texel_size.x) + String(", ") + itos(vrs_capabilities.min_texel_size.y) + String(")") + String(", max texel size: (") + itos(vrs_capabilities.max_texel_size.x) + String(", ") + itos(vrs_capabilities.max_texel_size.y) + String(")"));
+			} else {
+				print_verbose("- Vulkan Variable Rate Shading not supported");
 			}
 
-		} else {
-			print_verbose("- Vulkan Variable Rate Shading not supported");
-		}
+			if (multiview_capabilities.is_supported) {
+				multiview_capabilities.max_view_count = multiviewProperties.maxMultiviewViewCount;
+				multiview_capabilities.max_instance_count = multiviewProperties.maxMultiviewInstanceIndex;
 
-		if (multiview_capabilities.is_supported) {
-			multiview_capabilities.max_view_count = multiviewProperties.maxMultiviewViewCount;
-			multiview_capabilities.max_instance_count = multiviewProperties.maxMultiviewInstanceIndex;
+				print_verbose("- Vulkan multiview supported:");
+				print_verbose("  max view count: " + itos(multiview_capabilities.max_view_count));
+				print_verbose("  max instances: " + itos(multiview_capabilities.max_instance_count));
+			} else {
+				print_verbose("- Vulkan multiview not supported");
+			}
 
-			print_verbose("- Vulkan multiview supported:");
-			print_verbose("  max view count: " + itos(multiview_capabilities.max_view_count));
-			print_verbose("  max instances: " + itos(multiview_capabilities.max_instance_count));
+			print_verbose("- Vulkan subgroup:");
+			print_verbose("  size: " + itos(subgroup_capabilities.size));
+			print_verbose("  stages: " + subgroup_capabilities.supported_stages_desc());
+			print_verbose("  supported ops: " + subgroup_capabilities.supported_operations_desc());
+			if (subgroup_capabilities.quadOperationsInAllStages) {
+				print_verbose("  quad operations in all stages");
+			}
 		} else {
-			print_verbose("- Vulkan multiview not supported");
-		}
-
-		print_verbose("- Vulkan subgroup:");
-		print_verbose("  size: " + itos(subgroup_capabilities.size));
-		print_verbose("  stages: " + subgroup_capabilities.supported_stages_desc());
-		print_verbose("  supported ops: " + subgroup_capabilities.supported_operations_desc());
-		if (subgroup_capabilities.quadOperationsInAllStages) {
-			print_verbose("  quad operations in all stages");
+			print_verbose("- Couldn't call vkGetPhysicalDeviceProperties2");
 		}
-	} else {
-		print_verbose("- Couldn't call vkGetPhysicalDeviceProperties2");
 	}
 
 	return OK;
@@ -833,12 +924,19 @@ Error VulkanContext::_create_instance() {
 
 	// Initialize extensions.
 	{
-		Error err = _initialize_extensions();
+		Error err = _initialize_instance_extensions();
 		if (err != OK) {
 			return err;
 		}
 	}
 
+	int enabled_extension_count = 0;
+	const char *enabled_extension_names[MAX_EXTENSIONS];
+	ERR_FAIL_COND_V(enabled_instance_extension_names.size() > MAX_EXTENSIONS, ERR_CANT_CREATE);
+	for (const CharString &extension_name : enabled_instance_extension_names) {
+		enabled_extension_names[enabled_extension_count++] = extension_name.ptr();
+	}
+
 	CharString cs = GLOBAL_GET("application/config/name").operator String().utf8();
 	const VkApplicationInfo app = {
 		/*sType*/ VK_STRUCTURE_TYPE_APPLICATION_INFO,
@@ -853,7 +951,7 @@ Error VulkanContext::_create_instance() {
 	inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
 	inst_info.pApplicationInfo = &app;
 	inst_info.enabledExtensionCount = enabled_extension_count;
-	inst_info.ppEnabledExtensionNames = (const char *const *)extension_names;
+	inst_info.ppEnabledExtensionNames = (const char *const *)enabled_extension_names;
 	if (_use_validation_layers()) {
 		_get_preferred_validation_layers(&inst_info.enabledLayerCount, &inst_info.ppEnabledLayerNames);
 	}
@@ -863,9 +961,9 @@ Error VulkanContext::_create_instance() {
 	 * After the instance is created, we use the instance-based
 	 * function to register the final callback.
 	 */
-	VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info;
-	VkDebugReportCallbackCreateInfoEXT dbg_report_callback_create_info{};
-	if (enabled_debug_utils) {
+	VkDebugUtilsMessengerCreateInfoEXT dbg_messenger_create_info = {};
+	VkDebugReportCallbackCreateInfoEXT dbg_report_callback_create_info = {};
+	if (is_instance_extension_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
 		// VK_EXT_debug_utils style.
 		dbg_messenger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
 		dbg_messenger_create_info.pNext = nullptr;
@@ -878,7 +976,7 @@ Error VulkanContext::_create_instance() {
 		dbg_messenger_create_info.pfnUserCallback = _debug_messenger_callback;
 		dbg_messenger_create_info.pUserData = this;
 		inst_info.pNext = &dbg_messenger_create_info;
-	} else if (enabled_debug_report) {
+	} else if (is_instance_extension_enabled(VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) {
 		dbg_report_callback_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
 		dbg_report_callback_create_info.flags = VK_DEBUG_REPORT_INFORMATION_BIT_EXT |
 				VK_DEBUG_REPORT_WARNING_BIT_EXT |
@@ -918,7 +1016,7 @@ Error VulkanContext::_create_instance() {
 	volkLoadInstance(inst);
 #endif
 
-	if (enabled_debug_utils) {
+	if (is_instance_extension_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
 		// Setup VK_EXT_debug_utils function pointers always (we use them for debug labels and names).
 		CreateDebugUtilsMessengerEXT =
 				(PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(inst, "vkCreateDebugUtilsMessengerEXT");
@@ -959,7 +1057,7 @@ Error VulkanContext::_create_instance() {
 				ERR_FAIL_V(ERR_CANT_CREATE);
 				break;
 		}
-	} else if (enabled_debug_report) {
+	} else if (is_instance_extension_enabled(VK_EXT_DEBUG_REPORT_EXTENSION_NAME)) {
 		CreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(inst, "vkCreateDebugReportCallbackEXT");
 		DebugReportMessageEXT = (PFN_vkDebugReportMessageEXT)vkGetInstanceProcAddr(inst, "vkDebugReportMessageEXT");
 		DestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(inst, "vkDestroyDebugReportCallbackEXT");
@@ -1140,12 +1238,6 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) {
 
 	free(physical_devices);
 
-	// Look for device extensions.
-	uint32_t device_extension_count = 0;
-	VkBool32 swapchainExtFound = 0;
-	enabled_extension_count = 0;
-	memset(extension_names, 0, sizeof(extension_names));
-
 	// Get identifier properties.
 	vkGetPhysicalDeviceProperties(gpu, &gpu_props);
 
@@ -1171,84 +1263,13 @@ Error VulkanContext::_create_physical_device(VkSurfaceKHR p_surface) {
 
 	device_api_version = gpu_props.apiVersion;
 
-	err = vkEnumerateDeviceExtensionProperties(gpu, nullptr, &device_extension_count, nullptr);
-	ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
-
-	if (device_extension_count > 0) {
-		VkExtensionProperties *device_extensions = (VkExtensionProperties *)malloc(sizeof(VkExtensionProperties) * device_extension_count);
-		err = vkEnumerateDeviceExtensionProperties(gpu, nullptr, &device_extension_count, device_extensions);
-		if (err) {
-			free(device_extensions);
-			ERR_FAIL_V(ERR_CANT_CREATE);
-		}
-
-		for (uint32_t i = 0; i < device_extension_count; i++) {
-			if (!strcmp(VK_KHR_SWAPCHAIN_EXTENSION_NAME, device_extensions[i].extensionName)) {
-				swapchainExtFound = 1;
-				extension_names[enabled_extension_count++] = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
-			}
-			if (!strcmp(VK_KHR_MULTIVIEW_EXTENSION_NAME, device_extensions[i].extensionName)) {
-				// If multiview is supported, enable it.
-				extension_names[enabled_extension_count++] = VK_KHR_MULTIVIEW_EXTENSION_NAME;
-			}
-			if (!strcmp(VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME, device_extensions[i].extensionName)) {
-				// if shading rate image is supported, enable it
-				extension_names[enabled_extension_count++] = VK_KHR_FRAGMENT_SHADING_RATE_EXTENSION_NAME;
-			}
-			if (!strcmp(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME, device_extensions[i].extensionName)) {
-				has_renderpass2_ext = true;
-				extension_names[enabled_extension_count++] = VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME;
-			}
-			if (enabled_extension_count >= MAX_EXTENSIONS) {
-				free(device_extensions);
-				ERR_FAIL_V_MSG(ERR_BUG, "Enabled extension count reaches MAX_EXTENSIONS, BUG");
-			}
-		}
-
-		if (VK_KHR_incremental_present_enabled) {
-			// Even though the user "enabled" the extension via the command
-			// line, we must make sure that it's enumerated for use with the
-			// device.  Therefore, disable it here, and re-enable it again if
-			// enumerated.
-			VK_KHR_incremental_present_enabled = false;
-			for (uint32_t i = 0; i < device_extension_count; i++) {
-				if (!strcmp(VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME, device_extensions[i].extensionName)) {
-					extension_names[enabled_extension_count++] = VK_KHR_INCREMENTAL_PRESENT_EXTENSION_NAME;
-					VK_KHR_incremental_present_enabled = true;
-				}
-				if (enabled_extension_count >= MAX_EXTENSIONS) {
-					free(device_extensions);
-					ERR_FAIL_V_MSG(ERR_BUG, "Enabled extension count reaches MAX_EXTENSIONS, BUG");
-				}
-			}
-		}
-
-		if (VK_GOOGLE_display_timing_enabled) {
-			// Even though the user "enabled" the extension via the command
-			// line, we must make sure that it's enumerated for use with the
-			// device.  Therefore, disable it here, and re-enable it again if
-			// enumerated.
-			VK_GOOGLE_display_timing_enabled = false;
-			for (uint32_t i = 0; i < device_extension_count; i++) {
-				if (!strcmp(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME, device_extensions[i].extensionName)) {
-					extension_names[enabled_extension_count++] = VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME;
-					VK_GOOGLE_display_timing_enabled = true;
-				}
-				if (enabled_extension_count >= MAX_EXTENSIONS) {
-					free(device_extensions);
-					ERR_FAIL_V_MSG(ERR_BUG, "Enabled extension count reaches MAX_EXTENSIONS, BUG");
-				}
-			}
+	{
+		Error _err = _initialize_device_extensions();
+		if (_err != OK) {
+			return _err;
 		}
-
-		free(device_extensions);
 	}
 
-	ERR_FAIL_COND_V_MSG(!swapchainExtFound, ERR_CANT_CREATE,
-			"vkEnumerateDeviceExtensionProperties failed to find the " VK_KHR_SWAPCHAIN_EXTENSION_NAME
-			" extension.\n\nDo you have a compatible Vulkan installable client driver (ICD) installed?\n"
-			"vkCreateInstance Failure");
-
 	// Call with nullptr data to get count.
 	vkGetPhysicalDeviceQueueFamilyProperties(gpu, &queue_family_count, nullptr);
 	ERR_FAIL_COND_V(queue_family_count == 0, ERR_CANT_CREATE);
@@ -1309,7 +1330,7 @@ Error VulkanContext::_create_device() {
 	};
 	nextptr = &shader_features;
 
-	VkPhysicalDeviceFragmentShadingRateFeaturesKHR vrs_features;
+	VkPhysicalDeviceFragmentShadingRateFeaturesKHR vrs_features = {};
 	if (vrs_capabilities.pipeline_vrs_supported || vrs_capabilities.primitive_vrs_supported || vrs_capabilities.attachment_vrs_supported) {
 		// Insert into our chain to enable these features if they are available.
 		vrs_features.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FRAGMENT_SHADING_RATE_FEATURES_KHR;
@@ -1321,9 +1342,9 @@ Error VulkanContext::_create_device() {
 		nextptr = &vrs_features;
 	}
 
-	VkPhysicalDeviceVulkan11Features vulkan11features;
-	VkPhysicalDevice16BitStorageFeaturesKHR storage_feature;
-	VkPhysicalDeviceMultiviewFeatures multiview_features;
+	VkPhysicalDeviceVulkan11Features vulkan11features = {};
+	VkPhysicalDevice16BitStorageFeaturesKHR storage_feature = {};
+	VkPhysicalDeviceMultiviewFeatures multiview_features = {};
 	if (vulkan_major > 1 || vulkan_minor >= 2) {
 		// In Vulkan 1.2 and newer we use a newer struct to enable various features.
 
@@ -1362,6 +1383,13 @@ Error VulkanContext::_create_device() {
 		}
 	}
 
+	uint32_t enabled_extension_count = 0;
+	const char *enabled_extension_names[MAX_EXTENSIONS];
+	ERR_FAIL_COND_V(enabled_device_extension_names.size() > MAX_EXTENSIONS, ERR_CANT_CREATE);
+	for (const CharString &extension_name : enabled_device_extension_names) {
+		enabled_extension_names[enabled_extension_count++] = extension_name.ptr();
+	}
+
 	VkDeviceCreateInfo sdevice = {
 		/*sType*/ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
 		/*pNext*/ nextptr,
@@ -1371,7 +1399,7 @@ Error VulkanContext::_create_device() {
 		/*enabledLayerCount*/ 0,
 		/*ppEnabledLayerNames*/ nullptr,
 		/*enabledExtensionCount*/ enabled_extension_count,
-		/*ppEnabledExtensionNames*/ (const char *const *)extension_names,
+		/*ppEnabledExtensionNames*/ (const char *const *)enabled_extension_names,
 		/*pEnabledFeatures*/ &physical_device_features, // If specific features are required, pass them in here.
 	};
 	if (separate_present_queue) {
@@ -1459,7 +1487,7 @@ Error VulkanContext::_initialize_queues(VkSurfaceKHR p_surface) {
 	GET_DEVICE_PROC_ADDR(device, GetSwapchainImagesKHR);
 	GET_DEVICE_PROC_ADDR(device, AcquireNextImageKHR);
 	GET_DEVICE_PROC_ADDR(device, QueuePresentKHR);
-	if (VK_GOOGLE_display_timing_enabled) {
+	if (is_device_extension_enabled(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME)) {
 		GET_DEVICE_PROC_ADDR(device, GetRefreshCycleDurationGOOGLE);
 		GET_DEVICE_PROC_ADDR(device, GetPastPresentationTimingGOOGLE);
 	}
@@ -2214,7 +2242,7 @@ Error VulkanContext::swap_buffers() {
 	VkResult err;
 
 #if 0
-	if (VK_GOOGLE_display_timing_enabled) {
+	if (is_device_extension_enabled(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME)) {
 		// Look at what happened to previous presents, and make appropriate
 		// adjustments in timing.
 		DemoUpdateTargetIPD(demo);
@@ -2335,7 +2363,7 @@ Error VulkanContext::swap_buffers() {
 	}
 
 #if 0
-	if (VK_KHR_incremental_present_enabled) {
+	if (is_device_extension_enabled(VK_KHR_incremental_present_enabled)) {
 		// If using VK_KHR_incremental_present, we provide a hint of the region
 		// that contains changed content relative to the previously-presented
 		// image.  The implementation can use this hint in order to save
@@ -2366,7 +2394,7 @@ Error VulkanContext::swap_buffers() {
 #endif
 
 #if 0
-	if (VK_GOOGLE_display_timing_enabled) {
+	if (is_device_extension_enabled(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME)) {
 		VkPresentTimeGOOGLE ptime;
 		if (prev_desired_present_time == 0) {
 			// This must be the first present for this swapchain.
@@ -2396,7 +2424,7 @@ Error VulkanContext::swap_buffers() {
 			/*swapchainCount*/ present.swapchainCount,
 			/*pTimes*/ &ptime,
 		};
-		if (VK_GOOGLE_display_timing_enabled) {
+		if (is_device_extension_enabled(VK_GOOGLE_DISPLAY_TIMING_EXTENSION_NAME)) {
 			present.pNext = &present_time;
 		}
 	}
@@ -2469,6 +2497,13 @@ RID VulkanContext::local_device_create() {
 		queues[0].pQueuePriorities = queue_priorities;
 		queues[0].flags = 0;
 
+		uint32_t enabled_extension_count = 0;
+		const char *enabled_extension_names[MAX_EXTENSIONS];
+		ERR_FAIL_COND_V(enabled_device_extension_names.size() > MAX_EXTENSIONS, RID());
+		for (const CharString &extension_name : enabled_device_extension_names) {
+			enabled_extension_names[enabled_extension_count++] = extension_name.ptr();
+		}
+
 		VkDeviceCreateInfo sdevice = {
 			/*sType =*/VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
 			/*pNext */ nullptr,
@@ -2478,7 +2513,7 @@ RID VulkanContext::local_device_create() {
 			/*enabledLayerCount */ 0,
 			/*ppEnabledLayerNames */ nullptr,
 			/*enabledExtensionCount */ enabled_extension_count,
-			/*ppEnabledExtensionNames */ (const char *const *)extension_names,
+			/*ppEnabledExtensionNames */ (const char *const *)enabled_extension_names,
 			/*pEnabledFeatures */ &physical_device_features, // If specific features are required, pass them in here.
 		};
 		err = vkCreateDevice(gpu, &sdevice, nullptr, &ld.device);
@@ -2543,7 +2578,7 @@ void VulkanContext::local_device_free(RID p_local_device) {
 }
 
 void VulkanContext::command_begin_label(VkCommandBuffer p_command_buffer, String p_label_name, const Color p_color) {
-	if (!enabled_debug_utils) {
+	if (!is_instance_extension_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
 		return;
 	}
 
@@ -2560,7 +2595,7 @@ void VulkanContext::command_begin_label(VkCommandBuffer p_command_buffer, String
 }
 
 void VulkanContext::command_insert_label(VkCommandBuffer p_command_buffer, String p_label_name, const Color p_color) {
-	if (!enabled_debug_utils) {
+	if (!is_instance_extension_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
 		return;
 	}
 	CharString cs = p_label_name.utf8();
@@ -2576,14 +2611,14 @@ void VulkanContext::command_insert_label(VkCommandBuffer p_command_buffer, Strin
 }
 
 void VulkanContext::command_end_label(VkCommandBuffer p_command_buffer) {
-	if (!enabled_debug_utils) {
+	if (!is_instance_extension_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
 		return;
 	}
 	CmdEndDebugUtilsLabelEXT(p_command_buffer);
 }
 
 void VulkanContext::set_object_name(VkObjectType p_object_type, uint64_t p_object_handle, String p_object_name) {
-	if (!enabled_debug_utils) {
+	if (!is_instance_extension_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
 		return;
 	}
 	CharString obj_data = p_object_name.utf8();
@@ -2644,7 +2679,7 @@ VulkanContext::~VulkanContext() {
 				vkDestroySemaphore(device, image_ownership_semaphores[i], nullptr);
 			}
 		}
-		if (inst_initialized && enabled_debug_utils) {
+		if (inst_initialized && is_instance_extension_enabled(VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
 			DestroyDebugUtilsMessengerEXT(inst, dbg_messenger, nullptr);
 		}
 		if (inst_initialized && dbg_debug_report != VK_NULL_HANDLE) {

+ 20 - 12
drivers/vulkan/vulkan_context.h

@@ -34,6 +34,7 @@
 #include "core/error/error_list.h"
 #include "core/os/mutex.h"
 #include "core/string/ustring.h"
+#include "core/templates/hash_map.h"
 #include "core/templates/rb_map.h"
 #include "core/templates/rid_owner.h"
 #include "servers/display_server.h"
@@ -184,19 +185,15 @@ private:
 	int command_buffer_count = 1;
 
 	// Extensions.
+	static bool instance_extensions_initialized;
+	static HashMap<CharString, bool> requested_instance_extensions;
+	HashSet<CharString> enabled_instance_extension_names;
 
+	static bool device_extensions_initialized;
+	static HashMap<CharString, bool> requested_device_extensions;
+	HashSet<CharString> enabled_device_extension_names;
 	bool VK_KHR_incremental_present_enabled = true;
 	bool VK_GOOGLE_display_timing_enabled = true;
-	uint32_t enabled_extension_count = 0;
-	const char *extension_names[MAX_EXTENSIONS];
-	bool enabled_debug_utils = false;
-	bool has_renderpass2_ext = false;
-
-	/**
-	 * True if VK_EXT_debug_report extension is used. VK_EXT_debug_report is deprecated but it is
-	 * still used if VK_EXT_debug_utils is not available.
-	 */
-	bool enabled_debug_report = false;
 
 	PFN_vkCreateDebugUtilsMessengerEXT CreateDebugUtilsMessengerEXT = nullptr;
 	PFN_vkDestroyDebugUtilsMessengerEXT DestroyDebugUtilsMessengerEXT = nullptr;
@@ -225,7 +222,8 @@ private:
 	VkDebugReportCallbackEXT dbg_debug_report = VK_NULL_HANDLE;
 
 	Error _obtain_vulkan_version();
-	Error _initialize_extensions();
+	Error _initialize_instance_extensions();
+	Error _initialize_device_extensions();
 	Error _check_capabilities();
 
 	VkBool32 _check_layers(uint32_t check_count, const char *const *check_names, uint32_t layer_count, VkLayerProperties *layers);
@@ -275,7 +273,7 @@ protected:
 
 public:
 	// Extension calls.
-	bool supports_renderpass2() const { return has_renderpass2_ext; }
+	bool supports_renderpass2() const { return is_device_extension_enabled(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); }
 	VkResult vkCreateRenderPass2KHR(VkDevice p_device, const VkRenderPassCreateInfo2 *p_create_info, const VkAllocationCallbacks *p_allocator, VkRenderPass *p_render_pass);
 
 	uint32_t get_vulkan_major() const { return vulkan_major; };
@@ -295,6 +293,16 @@ public:
 
 	static void set_vulkan_hooks(VulkanHooks *p_vulkan_hooks) { vulkan_hooks = p_vulkan_hooks; };
 
+	static void register_requested_instance_extension(const CharString &extension_name, bool p_required);
+	bool is_instance_extension_enabled(const CharString &extension_name) const {
+		return enabled_instance_extension_names.has(extension_name);
+	}
+
+	static void register_requested_device_extension(const CharString &extension_name, bool p_required);
+	bool is_device_extension_enabled(const CharString &extension_name) const {
+		return enabled_device_extension_names.has(extension_name);
+	}
+
 	void window_resize(DisplayServer::WindowID p_window_id, int p_width, int p_height);
 	int window_get_width(DisplayServer::WindowID p_window = 0);
 	int window_get_height(DisplayServer::WindowID p_window = 0);

+ 6 - 0
tests/core/string/test_string.h

@@ -226,6 +226,12 @@ TEST_CASE("[String] Comparisons (equal)") {
 	CHECK(s == U"Test Compare");
 	CHECK(s == L"Test Compare");
 	CHECK(s == String("Test Compare"));
+
+	CharString empty = "";
+	CharString cs = "Test Compare";
+	CHECK(!(empty == cs));
+	CHECK(!(cs == empty));
+	CHECK(cs == CharString("Test Compare"));
 }
 
 TEST_CASE("[String] Comparisons (not equal)") {