/* * Copyright (c) 2021-2023 The Khronos Group Inc. * Copyright (c) 2021-2023 Valve Corporation * Copyright (c) 2021-2023 LunarG, Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and/or associated documentation files (the "Materials"), to * deal in the Materials without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Materials, and to permit persons to whom the Materials are * furnished to do so, subject to the following conditions: * * The above copyright notice(s) and this permission notice shall be included in * all copies or substantial portions of the Materials. * * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE MATERIALS OR THE * USE OR OTHER DEALINGS IN THE MATERIALS. * * Author: Charles Giessen */ #include "test_environment.h" std::filesystem::path get_loader_path() { auto loader_path = std::filesystem::path(FRAMEWORK_VULKAN_LIBRARY_PATH); auto env_var_res = get_env_var("VK_LOADER_TEST_LOADER_PATH", false); if (!env_var_res.empty()) { loader_path = std::filesystem::path(env_var_res); } return loader_path; } void init_vulkan_functions(VulkanFunctions& funcs) { #if defined(APPLE_STATIC_LOADER) #define GPA(name) name #else #define GPA(name) funcs.loader.get_symbol(#name) #endif // clang-format off funcs.vkGetInstanceProcAddr = GPA(vkGetInstanceProcAddr); funcs.vkEnumerateInstanceExtensionProperties = GPA(vkEnumerateInstanceExtensionProperties); funcs.vkEnumerateInstanceLayerProperties = GPA(vkEnumerateInstanceLayerProperties); funcs.vkEnumerateInstanceVersion = GPA(vkEnumerateInstanceVersion); funcs.vkCreateInstance = GPA(vkCreateInstance); funcs.vkDestroyInstance = GPA(vkDestroyInstance); funcs.vkEnumeratePhysicalDevices = GPA(vkEnumeratePhysicalDevices); funcs.vkEnumeratePhysicalDeviceGroups = GPA(vkEnumeratePhysicalDeviceGroups); funcs.vkGetPhysicalDeviceFeatures = GPA(vkGetPhysicalDeviceFeatures); funcs.vkGetPhysicalDeviceFeatures2 = GPA(vkGetPhysicalDeviceFeatures2); funcs.vkGetPhysicalDeviceFormatProperties = GPA(vkGetPhysicalDeviceFormatProperties); funcs.vkGetPhysicalDeviceFormatProperties2 = GPA(vkGetPhysicalDeviceFormatProperties2); funcs.vkGetPhysicalDeviceImageFormatProperties = GPA(vkGetPhysicalDeviceImageFormatProperties); funcs.vkGetPhysicalDeviceImageFormatProperties2 = GPA(vkGetPhysicalDeviceImageFormatProperties2); funcs.vkGetPhysicalDeviceSparseImageFormatProperties = GPA(vkGetPhysicalDeviceSparseImageFormatProperties); funcs.vkGetPhysicalDeviceSparseImageFormatProperties2 = GPA(vkGetPhysicalDeviceSparseImageFormatProperties2); funcs.vkGetPhysicalDeviceProperties = GPA(vkGetPhysicalDeviceProperties); funcs.vkGetPhysicalDeviceProperties2 = GPA(vkGetPhysicalDeviceProperties2); funcs.vkGetPhysicalDeviceQueueFamilyProperties = GPA(vkGetPhysicalDeviceQueueFamilyProperties); funcs.vkGetPhysicalDeviceQueueFamilyProperties2 = GPA(vkGetPhysicalDeviceQueueFamilyProperties2); funcs.vkGetPhysicalDeviceMemoryProperties = GPA(vkGetPhysicalDeviceMemoryProperties); funcs.vkGetPhysicalDeviceMemoryProperties2 = GPA(vkGetPhysicalDeviceMemoryProperties2); funcs.vkGetPhysicalDeviceSurfaceSupportKHR = GPA(vkGetPhysicalDeviceSurfaceSupportKHR); funcs.vkGetPhysicalDeviceSurfaceFormatsKHR = GPA(vkGetPhysicalDeviceSurfaceFormatsKHR); funcs.vkGetPhysicalDeviceSurfacePresentModesKHR = GPA(vkGetPhysicalDeviceSurfacePresentModesKHR); funcs.vkGetPhysicalDeviceSurfaceCapabilitiesKHR = GPA(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); funcs.vkEnumerateDeviceExtensionProperties = GPA(vkEnumerateDeviceExtensionProperties); funcs.vkEnumerateDeviceLayerProperties = GPA(vkEnumerateDeviceLayerProperties); funcs.vkGetPhysicalDeviceExternalBufferProperties = GPA(vkGetPhysicalDeviceExternalBufferProperties); funcs.vkGetPhysicalDeviceExternalFenceProperties = GPA(vkGetPhysicalDeviceExternalFenceProperties); funcs.vkGetPhysicalDeviceExternalSemaphoreProperties = GPA(vkGetPhysicalDeviceExternalSemaphoreProperties); funcs.vkDestroySurfaceKHR = GPA(vkDestroySurfaceKHR); funcs.vkGetDeviceProcAddr = GPA(vkGetDeviceProcAddr); funcs.vkCreateDevice = GPA(vkCreateDevice); funcs.vkCreateHeadlessSurfaceEXT = GPA(vkCreateHeadlessSurfaceEXT); funcs.vkCreateDisplayPlaneSurfaceKHR = GPA(vkCreateDisplayPlaneSurfaceKHR); funcs.vkGetPhysicalDeviceDisplayPropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPropertiesKHR); funcs.vkGetPhysicalDeviceDisplayPlanePropertiesKHR = GPA(vkGetPhysicalDeviceDisplayPlanePropertiesKHR); funcs.vkGetDisplayPlaneSupportedDisplaysKHR = GPA(vkGetDisplayPlaneSupportedDisplaysKHR); funcs.vkGetDisplayModePropertiesKHR = GPA(vkGetDisplayModePropertiesKHR); funcs.vkCreateDisplayModeKHR = GPA(vkCreateDisplayModeKHR); funcs.vkGetDisplayPlaneCapabilitiesKHR = GPA(vkGetDisplayPlaneCapabilitiesKHR); funcs.vkGetPhysicalDevicePresentRectanglesKHR = GPA(vkGetPhysicalDevicePresentRectanglesKHR); funcs.vkGetPhysicalDeviceDisplayProperties2KHR = GPA(vkGetPhysicalDeviceDisplayProperties2KHR); funcs.vkGetPhysicalDeviceDisplayPlaneProperties2KHR = GPA(vkGetPhysicalDeviceDisplayPlaneProperties2KHR); funcs.vkGetDisplayModeProperties2KHR = GPA(vkGetDisplayModeProperties2KHR); funcs.vkGetDisplayPlaneCapabilities2KHR = GPA(vkGetDisplayPlaneCapabilities2KHR); funcs.vkGetPhysicalDeviceSurfaceCapabilities2KHR = GPA(vkGetPhysicalDeviceSurfaceCapabilities2KHR); funcs.vkGetPhysicalDeviceSurfaceFormats2KHR = GPA(vkGetPhysicalDeviceSurfaceFormats2KHR); #if defined(VK_USE_PLATFORM_ANDROID_KHR) funcs.vkCreateAndroidSurfaceKHR = GPA(vkCreateAndroidSurfaceKHR); #endif // VK_USE_PLATFORM_ANDROID_KHR #if defined(VK_USE_PLATFORM_DIRECTFB_EXT) funcs.vkCreateDirectFBSurfaceEXT = GPA(vkCreateDirectFBSurfaceEXT); funcs.vkGetPhysicalDeviceDirectFBPresentationSupportEXT = GPA(vkGetPhysicalDeviceDirectFBPresentationSupportEXT); #endif // VK_USE_PLATFORM_DIRECTFB_EXT #if defined(VK_USE_PLATFORM_FUCHSIA) funcs.vkCreateImagePipeSurfaceFUCHSIA = GPA(vkCreateImagePipeSurfaceFUCHSIA); #endif // VK_USE_PLATFORM_FUCHSIA #if defined(VK_USE_PLATFORM_GGP) funcs.vkCreateStreamDescriptorSurfaceGGP = GPA(vkCreateStreamDescriptorSurfaceGGP); #endif // VK_USE_PLATFORM_GGP #if defined(VK_USE_PLATFORM_IOS_MVK) funcs.vkCreateIOSSurfaceMVK = GPA(vkCreateIOSSurfaceMVK); #endif // VK_USE_PLATFORM_IOS_MVK #if defined(VK_USE_PLATFORM_MACOS_MVK) funcs.vkCreateMacOSSurfaceMVK = GPA(vkCreateMacOSSurfaceMVK); #endif // VK_USE_PLATFORM_MACOS_MVK #if defined(VK_USE_PLATFORM_METAL_EXT) funcs.vkCreateMetalSurfaceEXT = GPA(vkCreateMetalSurfaceEXT); #endif // VK_USE_PLATFORM_METAL_EXT #if defined(VK_USE_PLATFORM_SCREEN_QNX) funcs.vkCreateScreenSurfaceQNX = GPA(vkCreateScreenSurfaceQNX); funcs.vkGetPhysicalDeviceScreenPresentationSupportQNX = GPA(vkGetPhysicalDeviceScreenPresentationSupportQNX); #endif // VK_USE_PLATFORM_SCREEN_QNX #if defined(VK_USE_PLATFORM_WAYLAND_KHR) funcs.vkCreateWaylandSurfaceKHR = GPA(vkCreateWaylandSurfaceKHR); funcs.vkGetPhysicalDeviceWaylandPresentationSupportKHR = GPA(vkGetPhysicalDeviceWaylandPresentationSupportKHR); #endif // VK_USE_PLATFORM_WAYLAND_KHR #if defined(VK_USE_PLATFORM_XCB_KHR) funcs.vkCreateXcbSurfaceKHR = GPA(vkCreateXcbSurfaceKHR); funcs.vkGetPhysicalDeviceXcbPresentationSupportKHR = GPA(vkGetPhysicalDeviceXcbPresentationSupportKHR); #endif // VK_USE_PLATFORM_XCB_KHR #if defined(VK_USE_PLATFORM_XLIB_KHR) funcs.vkCreateXlibSurfaceKHR = GPA(vkCreateXlibSurfaceKHR); funcs.vkGetPhysicalDeviceXlibPresentationSupportKHR = GPA(vkGetPhysicalDeviceXlibPresentationSupportKHR); #endif // VK_USE_PLATFORM_XLIB_KHR #if defined(VK_USE_PLATFORM_WIN32_KHR) funcs.vkCreateWin32SurfaceKHR = GPA(vkCreateWin32SurfaceKHR); funcs.vkGetPhysicalDeviceWin32PresentationSupportKHR = GPA(vkGetPhysicalDeviceWin32PresentationSupportKHR); #endif // VK_USE_PLATFORM_WIN32_KHR funcs.vkDestroyDevice = GPA(vkDestroyDevice); funcs.vkGetDeviceQueue = GPA(vkGetDeviceQueue); #undef GPA // clang-format on } #if defined(APPLE_STATIC_LOADER) VulkanFunctions::VulkanFunctions() { #else VulkanFunctions::VulkanFunctions() : loader(get_loader_path()) { #endif init_vulkan_functions(*this); } void VulkanFunctions::load_instance_functions(VkInstance instance) { vkCreateDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT")); vkDestroyDebugReportCallbackEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT")); vkCreateDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); vkDestroyDebugUtilsMessengerEXT = FromVoidStarFunc(vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT")); } DeviceFunctions::DeviceFunctions(const VulkanFunctions& vulkan_functions, VkDevice device) { vkGetDeviceProcAddr = vulkan_functions.vkGetDeviceProcAddr; vkDestroyDevice = load(device, "vkDestroyDevice"); vkGetDeviceQueue = load(device, "vkGetDeviceQueue"); vkCreateCommandPool = load(device, "vkCreateCommandPool"); vkAllocateCommandBuffers = load(device, "vkAllocateCommandBuffers"); vkDestroyCommandPool = load(device, "vkDestroyCommandPool"); vkCreateSwapchainKHR = load(device, "vkCreateSwapchainKHR"); vkGetSwapchainImagesKHR = load(device, "vkGetSwapchainImagesKHR"); vkDestroySwapchainKHR = load(device, "vkDestroySwapchainKHR"); } InstWrapper::InstWrapper(VulkanFunctions& functions, VkAllocationCallbacks* callbacks) noexcept : functions(&functions), callbacks(callbacks) {} InstWrapper::InstWrapper(VulkanFunctions& functions, VkInstance inst, VkAllocationCallbacks* callbacks) noexcept : functions(&functions), inst(inst), callbacks(callbacks) {} InstWrapper::~InstWrapper() noexcept { if (inst != VK_NULL_HANDLE) functions->vkDestroyInstance(inst, callbacks); } InstWrapper::InstWrapper(InstWrapper&& other) noexcept { functions = other.functions; inst = other.inst; callbacks = other.callbacks; create_info = other.create_info; other.inst = VK_NULL_HANDLE; } InstWrapper& InstWrapper::operator=(InstWrapper&& other) noexcept { functions->vkDestroyInstance(inst, callbacks); functions = other.functions; inst = other.inst; callbacks = other.callbacks; create_info = other.create_info; other.inst = VK_NULL_HANDLE; return *this; } void InstWrapper::CheckCreate(VkResult result_to_check) { ASSERT_EQ(result_to_check, functions->vkCreateInstance(create_info.get(), callbacks, &inst)); functions->load_instance_functions(inst); } void InstWrapper::CheckCreateWithInfo(InstanceCreateInfo& create_info, VkResult result_to_check) { ASSERT_EQ(result_to_check, functions->vkCreateInstance(create_info.get(), callbacks, &inst)); functions->load_instance_functions(inst); } std::vector InstWrapper::GetPhysDevs(uint32_t phys_dev_count, VkResult result_to_check) { uint32_t physical_count = phys_dev_count; std::vector physical_devices; physical_devices.resize(phys_dev_count); VkResult res = functions->vkEnumeratePhysicalDevices(inst, &physical_count, physical_devices.data()); EXPECT_EQ(result_to_check, res); return physical_devices; } std::vector InstWrapper::GetPhysDevs(VkResult result_to_check) { uint32_t physical_count = 0; VkResult res = functions->vkEnumeratePhysicalDevices(inst, &physical_count, nullptr); EXPECT_EQ(result_to_check, res); std::vector physical_devices; physical_devices.resize(physical_count); res = functions->vkEnumeratePhysicalDevices(inst, &physical_count, physical_devices.data()); EXPECT_EQ(result_to_check, res); return physical_devices; } VkPhysicalDevice InstWrapper::GetPhysDev(VkResult result_to_check) { uint32_t physical_count = 1; VkPhysicalDevice physical_device = VK_NULL_HANDLE; VkResult res = this->functions->vkEnumeratePhysicalDevices(inst, &physical_count, &physical_device); EXPECT_EQ(result_to_check, res); return physical_device; } std::vector InstWrapper::GetActiveLayers(VkPhysicalDevice phys_dev, uint32_t expected_count) { uint32_t count = 0; VkResult res = functions->vkEnumerateDeviceLayerProperties(phys_dev, &count, nullptr); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); std::vector layer_props{count}; res = functions->vkEnumerateDeviceLayerProperties(phys_dev, &count, layer_props.data()); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); return layer_props; } std::vector InstWrapper::EnumerateDeviceExtensions(VkPhysicalDevice physical_device, uint32_t expected_count) { uint32_t count = 0; VkResult res = functions->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &count, nullptr); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); std::vector extensions{count}; res = functions->vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &count, extensions.data()); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); return extensions; } std::vector InstWrapper::EnumerateLayerDeviceExtensions(VkPhysicalDevice physical_device, const char* layer_name, uint32_t expected_count) { uint32_t count = 0; VkResult res = functions->vkEnumerateDeviceExtensionProperties(physical_device, layer_name, &count, nullptr); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); std::vector extensions{count}; res = functions->vkEnumerateDeviceExtensionProperties(physical_device, layer_name, &count, extensions.data()); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); return extensions; } DeviceWrapper::DeviceWrapper(InstWrapper& inst_wrapper, VkAllocationCallbacks* callbacks) noexcept : functions(inst_wrapper.functions), callbacks(callbacks){}; DeviceWrapper::DeviceWrapper(VulkanFunctions& functions, VkDevice device, VkAllocationCallbacks* callbacks) noexcept : functions(&functions), dev(device), callbacks(callbacks){}; DeviceWrapper::~DeviceWrapper() noexcept { functions->vkDestroyDevice(dev, callbacks); } DeviceWrapper::DeviceWrapper(DeviceWrapper&& other) noexcept { functions = other.functions; dev = other.dev; callbacks = other.callbacks; create_info = other.create_info; other.dev = VK_NULL_HANDLE; } DeviceWrapper& DeviceWrapper::operator=(DeviceWrapper&& other) noexcept { functions->vkDestroyDevice(dev, callbacks); functions = other.functions; dev = other.dev; callbacks = other.callbacks; create_info = other.create_info; other.dev = VK_NULL_HANDLE; return *this; } void DeviceWrapper::CheckCreate(VkPhysicalDevice phys_dev, VkResult result_to_check) { ASSERT_EQ(result_to_check, functions->vkCreateDevice(phys_dev, create_info.get(), callbacks, &dev)); } VkResult CreateDebugUtilsMessenger(DebugUtilsWrapper& debug_utils) { return debug_utils.local_vkCreateDebugUtilsMessengerEXT(debug_utils.inst, debug_utils.get(), debug_utils.callbacks, &debug_utils.messenger); } void FillDebugUtilsCreateDetails(InstanceCreateInfo& create_info, DebugUtilsLogger& logger) { create_info.add_extension("VK_EXT_debug_utils"); create_info.instance_info.pNext = logger.get(); } void FillDebugUtilsCreateDetails(InstanceCreateInfo& create_info, DebugUtilsWrapper& wrapper) { create_info.add_extension("VK_EXT_debug_utils"); create_info.instance_info.pNext = wrapper.get(); } // Look through the event log. If you find a line containing the prefix we're interested in, look for the end of // line character, and then see if the postfix occurs in it as well. bool DebugUtilsLogger::find_prefix_then_postfix(const char* prefix, const char* postfix) const { size_t new_start = 0; size_t postfix_index = 0; size_t next_eol = 0; while ((new_start = returned_output.find(prefix, new_start)) != std::string::npos) { next_eol = returned_output.find("\n", new_start); if ((postfix_index = returned_output.find(postfix, new_start)) != std::string::npos) { if (postfix_index < next_eol) { return true; } } new_start = next_eol + 1; } return false; } bool FindPrefixPostfixStringOnLine(DebugUtilsLogger const& env_log, const char* prefix, const char* postfix) { return env_log.find_prefix_then_postfix(prefix, postfix); } PlatformShimWrapper::PlatformShimWrapper(std::vector* folders, const char* log_filter) noexcept : loader_logging{"VK_LOADER_DEBUG"} { #if defined(WIN32) || defined(__APPLE__) shim_library = LibraryWrapper(SHIM_LIBRARY_NAME); PFN_get_platform_shim get_platform_shim_func = shim_library.get_symbol(GET_PLATFORM_SHIM_STR); assert(get_platform_shim_func != NULL && "Must be able to get \"platform_shim\""); platform_shim = get_platform_shim_func(folders); #elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__GNU__) platform_shim = get_platform_shim(folders); #endif platform_shim->reset(); if (log_filter) { loader_logging.set_new_value(log_filter); } } PlatformShimWrapper::~PlatformShimWrapper() noexcept { platform_shim->reset(); } TestICDHandle::TestICDHandle() noexcept {} TestICDHandle::TestICDHandle(std::filesystem::path const& icd_path) noexcept : icd_library(icd_path) { proc_addr_get_test_icd = icd_library.get_symbol(GET_TEST_ICD_FUNC_STR); proc_addr_reset_icd = icd_library.get_symbol(RESET_ICD_FUNC_STR); } TestICD& TestICDHandle::get_test_icd() noexcept { assert(proc_addr_get_test_icd != NULL && "symbol must be loaded before use"); return *proc_addr_get_test_icd(); } TestICD& TestICDHandle::reset_icd() noexcept { assert(proc_addr_reset_icd != NULL && "symbol must be loaded before use"); return *proc_addr_reset_icd(); } std::filesystem::path TestICDHandle::get_icd_full_path() noexcept { return icd_library.lib_path; } std::filesystem::path TestICDHandle::get_icd_manifest_path() noexcept { return manifest_path; } std::filesystem::path TestICDHandle::get_shimmed_manifest_path() noexcept { return shimmed_manifest_path; } TestLayerHandle::TestLayerHandle() noexcept {} TestLayerHandle::TestLayerHandle(std::filesystem::path const& layer_path) noexcept : layer_library(layer_path) { proc_addr_get_test_layer = layer_library.get_symbol(GET_TEST_LAYER_FUNC_STR); proc_addr_reset_layer = layer_library.get_symbol(RESET_LAYER_FUNC_STR); } TestLayer& TestLayerHandle::get_test_layer() noexcept { assert(proc_addr_get_test_layer != NULL && "symbol must be loaded before use"); return *proc_addr_get_test_layer(); } TestLayer& TestLayerHandle::reset_layer() noexcept { assert(proc_addr_reset_layer != NULL && "symbol must be loaded before use"); return *proc_addr_reset_layer(); } std::filesystem::path TestLayerHandle::get_layer_full_path() noexcept { return layer_library.lib_path; } std::filesystem::path TestLayerHandle::get_layer_manifest_path() noexcept { return manifest_path; } std::filesystem::path TestLayerHandle::get_shimmed_manifest_path() noexcept { return shimmed_manifest_path; } FrameworkEnvironment::FrameworkEnvironment() noexcept : FrameworkEnvironment(FrameworkSettings{}) {} FrameworkEnvironment::FrameworkEnvironment(FrameworkSettings const& settings) noexcept : settings(settings), platform_shim(&folders, settings.log_filter) { // This order is important, it matches the enum ManifestLocation, used to index the folders vector folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("null_dir")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("icd_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("icd_env_vars_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("explicit_layer_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("explicit_env_var_layer_folder")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("explicit_add_env_var_layer_folder")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("implicit_layer_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("implicit_env_var_layer_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("implicit_add_env_var_layer_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("override_layer_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("app_package_manifests")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("macos_bundle")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("unsecured_location")); folders.emplace_back(FRAMEWORK_BUILD_DIRECTORY, std::string("settings_location")); platform_shim->redirect_all_paths(get_folder(ManifestLocation::null).location()); if (settings.enable_default_search_paths) { platform_shim->set_fake_path(ManifestCategory::icd, get_folder(ManifestLocation::driver).location()); platform_shim->set_fake_path(ManifestCategory::explicit_layer, get_folder(ManifestLocation::explicit_layer).location()); platform_shim->set_fake_path(ManifestCategory::implicit_layer, get_folder(ManifestLocation::implicit_layer).location()); #if COMMON_UNIX_PLATFORMS auto home = get_env_var("HOME"); auto unsecured_location = get_folder(ManifestLocation::unsecured_location).location(); platform_shim->redirect_path(home + "/.local/share/vulkan/icd.d", unsecured_location); platform_shim->redirect_path(home + "/.local/share/vulkan/implicit_layer.d", unsecured_location); platform_shim->redirect_path(home + "/.local/share/vulkan/explicit_layer.d", unsecured_location); #endif } #if COMMON_UNIX_PLATFORMS if (settings.secure_loader_settings) { platform_shim->redirect_path("/etc/vulkan/loader_settings.d", get_folder(ManifestLocation::settings_location).location()); } else { platform_shim->redirect_path(get_env_var("HOME") + "/.local/share/vulkan/loader_settings.d", get_folder(ManifestLocation::settings_location).location()); } #endif #if defined(__APPLE__) // Necessary since bundles look in sub folders for manifests, not the test framework folder itself auto bundle_location = get_folder(ManifestLocation::macos_bundle).location(); platform_shim->redirect_path(bundle_location / "vulkan/icd.d", bundle_location); platform_shim->redirect_path(bundle_location / "vulkan/explicit_layer.d", bundle_location); platform_shim->redirect_path(bundle_location / "vulkan/implicit_layer.d", bundle_location); #endif // only set the settings file if there are elements in the app_specific_settings vector if (!settings.loader_settings.app_specific_settings.empty()) { update_loader_settings(settings.loader_settings); } } FrameworkEnvironment::~FrameworkEnvironment() { // This is necessary to prevent the folder manager from using dead memory during destruction. // What happens is that each folder manager tries to cleanup itself. Except, folders that were never called did not have their // DirEntry array's filled out. So when that folder calls delete_folder, which calls readdir, the shim tries to order the files. // Except, the list of files is in a object that is currently being destroyed. platform_shim->is_during_destruction = true; } TestICD& FrameworkEnvironment::add_icd(TestICDDetails icd_details) noexcept { size_t cur_icd_index = icds.size(); fs::FolderManager* folder = &get_folder(ManifestLocation::driver); if (icd_details.discovery_type == ManifestDiscoveryType::env_var || icd_details.discovery_type == ManifestDiscoveryType::add_env_var) { folder = &get_folder(ManifestLocation::driver_env_var); } if (icd_details.discovery_type == ManifestDiscoveryType::windows_app_package) { folder = &get_folder(ManifestLocation::windows_app_package); } if (icd_details.discovery_type == ManifestDiscoveryType::macos_bundle) { folder = &get_folder(ManifestLocation::macos_bundle); } if (icd_details.discovery_type == ManifestDiscoveryType::unsecured_generic) { folder = &get_folder(ManifestLocation::unsecured_location); } if (icd_details.discovery_type == ManifestDiscoveryType::null_dir || icd_details.discovery_type == ManifestDiscoveryType::none) { folder = &get_folder(ManifestLocation::null); } if (!icd_details.is_fake) { std::filesystem::path new_lib_name = icd_details.icd_manifest.lib_path.stem(); new_lib_name += "_"; new_lib_name += std::to_string(cur_icd_index); new_lib_name += icd_details.icd_manifest.lib_path.extension(); auto new_driver_location = folder->copy_file(icd_details.icd_manifest.lib_path, new_lib_name); #if COMMON_UNIX_PLATFORMS if (icd_details.library_path_type == LibraryPathType::default_search_paths) { platform_shim->redirect_dlopen_name(new_lib_name, new_driver_location); } else if (icd_details.library_path_type == LibraryPathType::relative) { platform_shim->redirect_dlopen_name(std::filesystem::path(SYSCONFDIR) / "vulkan" / "icd.d" / "." / new_lib_name, new_driver_location); } #endif #if defined(WIN32) if (icd_details.library_path_type == LibraryPathType::default_search_paths) { SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_USER_DIRS); AddDllDirectory(new_driver_location.parent_path().native().c_str()); } #endif icds.push_back(TestICDHandle(new_driver_location)); icds.back().reset_icd(); if (icd_details.library_path_type == LibraryPathType::relative) { icd_details.icd_manifest.lib_path = std::filesystem::path(".") / new_lib_name; } else if (icd_details.library_path_type == LibraryPathType::default_search_paths) { icd_details.icd_manifest.lib_path = new_lib_name; } else { icd_details.icd_manifest.lib_path = new_driver_location; } } if (icd_details.discovery_type != ManifestDiscoveryType::none) { std::filesystem::path new_manifest_path = icd_details.json_name.stem(); if (!icd_details.disable_icd_inc) { new_manifest_path += "_"; new_manifest_path += std::to_string(cur_icd_index); } new_manifest_path += ".json"; icds.back().manifest_path = folder->write_manifest(new_manifest_path, icd_details.icd_manifest.get_manifest_str()); icds.back().shimmed_manifest_path = icds.back().manifest_path; switch (icd_details.discovery_type) { default: case (ManifestDiscoveryType::generic): platform_shim->add_manifest(ManifestCategory::icd, icds.back().manifest_path); #if COMMON_UNIX_PLATFORMS icds.back().shimmed_manifest_path = platform_shim->query_default_redirect_path(ManifestCategory::icd) / new_manifest_path; #endif break; case (ManifestDiscoveryType::env_var): if (icd_details.is_dir) { env_var_vk_icd_filenames.add_to_list(narrow(folder->location())); } else { env_var_vk_icd_filenames.add_to_list(narrow(folder->location() / new_manifest_path)); } platform_shim->add_known_path(folder->location()); break; case (ManifestDiscoveryType::add_env_var): if (icd_details.is_dir) { add_env_var_vk_icd_filenames.add_to_list(narrow(folder->location())); } else { add_env_var_vk_icd_filenames.add_to_list(narrow(folder->location() / new_manifest_path)); } platform_shim->add_known_path(folder->location()); break; case (ManifestDiscoveryType::macos_bundle): platform_shim->add_manifest(ManifestCategory::icd, icds.back().manifest_path); break; case (ManifestDiscoveryType::unsecured_generic): platform_shim->add_unsecured_manifest(ManifestCategory::icd, icds.back().manifest_path); break; case (ManifestDiscoveryType::null_dir): break; #if defined(_WIN32) case (ManifestDiscoveryType::windows_app_package): platform_shim->set_app_package_path(folder->location()); break; #endif } } return icds.back().get_test_icd(); } void FrameworkEnvironment::add_implicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept { add_layer_impl(TestLayerDetails{layer_manifest, json_name}, ManifestCategory::implicit_layer); } void FrameworkEnvironment::add_explicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept { add_layer_impl(TestLayerDetails{layer_manifest, json_name}, ManifestCategory::explicit_layer); } void FrameworkEnvironment::add_fake_implicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept { add_layer_impl(TestLayerDetails{layer_manifest, json_name}.set_is_fake(true), ManifestCategory::implicit_layer); } void FrameworkEnvironment::add_fake_explicit_layer(ManifestLayer layer_manifest, const std::string& json_name) noexcept { add_layer_impl(TestLayerDetails{layer_manifest, json_name}.set_is_fake(true), ManifestCategory::explicit_layer); } void FrameworkEnvironment::add_implicit_layer(TestLayerDetails layer_details) noexcept { add_layer_impl(layer_details, ManifestCategory::implicit_layer); } void FrameworkEnvironment::add_explicit_layer(TestLayerDetails layer_details) noexcept { add_layer_impl(layer_details, ManifestCategory::explicit_layer); } void FrameworkEnvironment::add_layer_impl(TestLayerDetails layer_details, ManifestCategory category) { fs::FolderManager* fs_ptr = &get_folder(ManifestLocation::explicit_layer); switch (layer_details.discovery_type) { default: case (ManifestDiscoveryType::generic): if (category == ManifestCategory::implicit_layer) fs_ptr = &get_folder(ManifestLocation::implicit_layer); break; case (ManifestDiscoveryType::env_var): if (category == ManifestCategory::explicit_layer) { fs_ptr = &get_folder(ManifestLocation::explicit_layer_env_var); if (layer_details.is_dir) { env_var_vk_layer_paths.add_to_list(narrow(fs_ptr->location())); } else { env_var_vk_layer_paths.add_to_list(narrow(fs_ptr->location() / layer_details.json_name)); } } if (category == ManifestCategory::implicit_layer) { fs_ptr = &get_folder(ManifestLocation::implicit_layer_env_var); if (layer_details.is_dir) { env_var_vk_implicit_layer_paths.add_to_list(narrow(fs_ptr->location())); } else { env_var_vk_implicit_layer_paths.add_to_list(narrow(fs_ptr->location() / layer_details.json_name)); } } platform_shim->add_known_path(fs_ptr->location()); break; case (ManifestDiscoveryType::add_env_var): if (category == ManifestCategory::explicit_layer) { fs_ptr = &get_folder(ManifestLocation::explicit_layer_add_env_var); if (layer_details.is_dir) { add_env_var_vk_layer_paths.add_to_list(narrow(fs_ptr->location())); } else { add_env_var_vk_layer_paths.add_to_list(narrow(fs_ptr->location() / layer_details.json_name)); } } if (category == ManifestCategory::implicit_layer) { fs_ptr = &get_folder(ManifestLocation::implicit_layer_add_env_var); if (layer_details.is_dir) { add_env_var_vk_implicit_layer_paths.add_to_list(narrow(fs_ptr->location())); } else { add_env_var_vk_implicit_layer_paths.add_to_list(narrow(fs_ptr->location() / layer_details.json_name)); } } platform_shim->add_known_path(fs_ptr->location()); break; case (ManifestDiscoveryType::override_folder): fs_ptr = &get_folder(ManifestLocation::override_layer); break; case (ManifestDiscoveryType::macos_bundle): fs_ptr = &(get_folder(ManifestLocation::macos_bundle)); break; case (ManifestDiscoveryType::unsecured_generic): fs_ptr = &(get_folder(ManifestLocation::unsecured_location)); break; case (ManifestDiscoveryType::windows_app_package): fs_ptr = &(get_folder(ManifestLocation::windows_app_package)); break; case (ManifestDiscoveryType::none): case (ManifestDiscoveryType::null_dir): fs_ptr = &(get_folder(ManifestLocation::null)); break; } auto& folder = *fs_ptr; size_t new_layers_start = layers.size(); for (auto& layer : layer_details.layer_manifest.layers) { if (!layer.lib_path.empty()) { std::filesystem::path new_lib_path = layer.lib_path.stem(); new_lib_path += "_"; new_lib_path += std::to_string(layers.size()); new_lib_path += layer.lib_path.extension(); auto new_layer_location = folder.copy_file(layer.lib_path, new_lib_path); #if COMMON_UNIX_PLATFORMS if (layer_details.library_path_type == LibraryPathType::default_search_paths) { platform_shim->redirect_dlopen_name(new_lib_path, new_layer_location); } if (layer_details.library_path_type == LibraryPathType::relative) { platform_shim->redirect_dlopen_name( std::filesystem::path(SYSCONFDIR) / "vulkan" / category_path_name(category) / "." / new_lib_path, new_layer_location); } #endif #if defined(WIN32) if (layer_details.library_path_type == LibraryPathType::default_search_paths) { SetDefaultDllDirectories(LOAD_LIBRARY_SEARCH_USER_DIRS); AddDllDirectory(new_layer_location.parent_path().native().c_str()); } #endif // Don't load the layer binary if using any of the wrap objects layers, since it doesn't export the same interface // functions if (!layer_details.is_fake && layer.lib_path.stem().string().find(std::filesystem::path(TEST_LAYER_WRAP_OBJECTS).stem().string()) == std::string::npos) { layers.push_back(TestLayerHandle(new_layer_location)); layers.back().reset_layer(); } if (layer_details.library_path_type == LibraryPathType::relative) { layer.lib_path = std::filesystem::path(".") / new_lib_path; } else if (layer_details.library_path_type == LibraryPathType::default_search_paths) { layer.lib_path = new_lib_path; } else { layer.lib_path = new_layer_location; } } } if (layer_details.discovery_type != ManifestDiscoveryType::none) { // Write a manifest file to a folder as long as the discovery type isn't none auto layer_manifest_loc = folder.write_manifest(layer_details.json_name, layer_details.layer_manifest.get_manifest_str()); // only add the manifest to the registry if its a generic location (as if it was installed) - both system and user local if (layer_details.discovery_type == ManifestDiscoveryType::generic) { platform_shim->add_manifest(category, layer_manifest_loc); } if (layer_details.discovery_type == ManifestDiscoveryType::unsecured_generic) { platform_shim->add_unsecured_manifest(category, layer_manifest_loc); } #if defined(_WIN32) if (layer_details.discovery_type == ManifestDiscoveryType::windows_app_package) { platform_shim->set_app_package_path(layer_manifest_loc); } #endif for (size_t i = new_layers_start; i < layers.size(); i++) { layers.at(i).manifest_path = layer_manifest_loc; layers.at(i).shimmed_manifest_path = layer_manifest_loc; #if COMMON_UNIX_PLATFORMS if (layer_details.discovery_type == ManifestDiscoveryType::generic) { layers.at(i).shimmed_manifest_path = platform_shim->query_default_redirect_path(category) / layer_details.json_name; } #endif } } } std::string get_loader_settings_file_contents(const LoaderSettings& loader_settings) noexcept { JsonWriter writer; writer.StartObject(); writer.AddKeyedString("file_format_version", loader_settings.file_format_version.get_version_str()); bool one_setting_file = true; if (loader_settings.app_specific_settings.size() > 1) { writer.StartKeyedArray("settings_array"); one_setting_file = false; } for (const auto& setting : loader_settings.app_specific_settings) { if (one_setting_file) { writer.StartKeyedObject("settings"); } else { writer.StartObject(); } if (!setting.app_keys.empty()) { writer.StartKeyedArray("app_keys"); for (const auto& app_key : setting.app_keys) { writer.AddString(app_key); } writer.EndArray(); } if (!setting.layer_configurations.empty()) { writer.StartKeyedArray("layers"); for (const auto& config : setting.layer_configurations) { writer.StartObject(); writer.AddKeyedString("name", config.name); writer.AddKeyedString("path", escape_backslashes_for_json(config.path)); writer.AddKeyedString("control", config.control); writer.AddKeyedBool("treat_as_implicit_manifest", config.treat_as_implicit_manifest); writer.EndObject(); } writer.EndArray(); } if (!setting.stderr_log.empty()) { writer.StartKeyedArray("stderr_log"); for (const auto& filter : setting.stderr_log) { writer.AddString(filter); } writer.EndArray(); } if (!setting.log_configurations.empty()) { writer.StartKeyedArray("log_locations"); for (const auto& config : setting.log_configurations) { writer.StartObject(); writer.StartKeyedArray("destinations"); for (const auto& dest : config.destinations) { writer.AddString(dest); } writer.EndArray(); writer.StartKeyedArray("filter"); for (const auto& filter : config.filters) { writer.AddString(filter); } writer.EndArray(); writer.EndObject(); } writer.EndArray(); } writer.EndObject(); } if (!one_setting_file) { writer.EndArray(); } writer.EndObject(); return writer.output; } void FrameworkEnvironment::write_settings_file(std::string const& file_contents) { auto out_path = get_folder(ManifestLocation::settings_location).write_manifest("vk_loader_settings.json", file_contents); #if defined(WIN32) platform_shim->hkey_current_user_settings.clear(); platform_shim->hkey_local_machine_settings.clear(); #endif if (settings.secure_loader_settings) platform_shim->add_manifest(ManifestCategory::settings, out_path); else platform_shim->add_unsecured_manifest(ManifestCategory::settings, out_path); } void FrameworkEnvironment::update_loader_settings(const LoaderSettings& settings) noexcept { write_settings_file(get_loader_settings_file_contents(settings)); } void FrameworkEnvironment::remove_loader_settings() { get_folder(ManifestLocation::settings_location).remove("vk_loader_settings.json"); } TestICD& FrameworkEnvironment::get_test_icd(size_t index) noexcept { return icds[index].get_test_icd(); } TestICD& FrameworkEnvironment::reset_icd(size_t index) noexcept { return icds[index].reset_icd(); } std::filesystem::path FrameworkEnvironment::get_test_icd_path(size_t index) noexcept { return icds[index].get_icd_full_path(); } std::filesystem::path FrameworkEnvironment::get_icd_manifest_path(size_t index) noexcept { return icds[index].get_icd_manifest_path(); } std::filesystem::path FrameworkEnvironment::get_shimmed_icd_manifest_path(size_t index) noexcept { return icds[index].get_shimmed_manifest_path(); } TestLayer& FrameworkEnvironment::get_test_layer(size_t index) noexcept { return layers[index].get_test_layer(); } TestLayer& FrameworkEnvironment::reset_layer(size_t index) noexcept { return layers[index].reset_layer(); } std::filesystem::path FrameworkEnvironment::get_test_layer_path(size_t index) noexcept { return layers[index].get_layer_full_path(); } std::filesystem::path FrameworkEnvironment::get_layer_manifest_path(size_t index) noexcept { return layers[index].get_layer_manifest_path(); } std::filesystem::path FrameworkEnvironment::get_shimmed_layer_manifest_path(size_t index) noexcept { return layers[index].get_shimmed_manifest_path(); } fs::FolderManager& FrameworkEnvironment::get_folder(ManifestLocation location) noexcept { // index it directly using the enum location since they will always be in that order return folders.at(static_cast(location)); } fs::FolderManager const& FrameworkEnvironment::get_folder(ManifestLocation location) const noexcept { return folders.at(static_cast(location)); } #if defined(__APPLE__) void FrameworkEnvironment::setup_macos_bundle() noexcept { platform_shim->bundle_contents = get_folder(ManifestLocation::macos_bundle).location(); } #endif std::vector FrameworkEnvironment::GetInstanceExtensions(uint32_t expected_count, const char* layer_name) { uint32_t count = 0; VkResult res = vulkan_functions.vkEnumerateInstanceExtensionProperties(layer_name, &count, nullptr); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); std::vector extension_props{count}; res = vulkan_functions.vkEnumerateInstanceExtensionProperties(layer_name, &count, extension_props.data()); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); return extension_props; } std::vector FrameworkEnvironment::GetLayerProperties(uint32_t expected_count) { uint32_t count = 0; VkResult res = vulkan_functions.vkEnumerateInstanceLayerProperties(&count, nullptr); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); std::vector layer_props{count}; res = vulkan_functions.vkEnumerateInstanceLayerProperties(&count, layer_props.data()); EXPECT_EQ(VK_SUCCESS, res); EXPECT_EQ(count, expected_count); return layer_props; } template VkResult create_surface_helper(VulkanFunctions* functions, VkInstance inst, VkSurfaceKHR& surface, const char* load_func_name) { CreationFunc pfn_CreateSurface = functions->load(inst, load_func_name); if (!pfn_CreateSurface) return VK_ERROR_EXTENSION_NOT_PRESENT; CreateInfo surf_create_info{}; return pfn_CreateSurface(inst, &surf_create_info, nullptr, &surface); } VkResult create_surface(VulkanFunctions* functions, VkInstance inst, VkSurfaceKHR& surface, [[maybe_unused]] const char* api_selection) { #if defined(VK_USE_PLATFORM_ANDROID_KHR) return create_surface_helper(functions, inst, surface, "vkCreateAndroidSurfaceKHR"); #elif defined(VK_USE_PLATFORM_DIRECTFB_EXT) return create_surface_helper(functions, inst, surface, "vkCreateDirectFBSurfaceEXT"); #elif defined(VK_USE_PLATFORM_FUCHSIA) return create_surface_helper( functions, inst, surface, "vkCreateImagePipeSurfaceFUCHSIA"); #elif defined(VK_USE_PLATFORM_GGP) return create_surface_helper( functions, inst, surface, "vkCreateStreamDescriptorSurfaceGGP"); #elif defined(VK_USE_PLATFORM_IOS_MVK) return create_surface_helper(functions, inst, surface, "vkCreateIOSSurfaceMVK"); #elif defined(VK_USE_PLATFORM_MACOS_MVK) || defined(VK_USE_PLATFORM_METAL_EXT) #if defined(VK_USE_PLATFORM_MACOS_MVK) if (api_selection != nullptr && string_eq(api_selection, "VK_USE_PLATFORM_MACOS_MVK")) return create_surface_helper(functions, inst, surface, "vkCreateMacOSSurfaceMVK"); #endif #if defined(VK_USE_PLATFORM_METAL_EXT) if (api_selection == nullptr || (api_selection != nullptr && string_eq(api_selection, "VK_USE_PLATFORM_METAL_EXT"))) return create_surface_helper(functions, inst, surface, "vkCreateMetalSurfaceEXT"); #endif return VK_ERROR_NOT_PERMITTED_KHR; #elif defined(VK_USE_PLATFORM_SCREEN_QNX) return create_surface_helper(functions, inst, surface, "vkCreateScreenSurfaceQNX"); #elif defined(VK_USE_PLATFORM_VI_NN) return create_surface_helper(functions, inst, surface, "vkCreateViSurfaceNN"); #elif defined(VK_USE_PLATFORM_WIN32_KHR) return create_surface_helper(functions, inst, surface, "vkCreateWin32SurfaceKHR"); #elif defined(VK_USE_PLATFORM_XCB_KHR) || defined(VK_USE_PLATFORM_XLIB_KHR) || defined(VK_USE_PLATFORM_WAYLAND_KHR) #if defined(VK_USE_PLATFORM_XLIB_KHR) if (string_eq(api_selection, "VK_USE_PLATFORM_XLIB_KHR")) return create_surface_helper(functions, inst, surface, "vkCreateXlibSurfaceKHR"); #endif #if defined(VK_USE_PLATFORM_WAYLAND_KHR) if (string_eq(api_selection, "VK_USE_PLATFORM_WAYLAND_KHR")) return create_surface_helper(functions, inst, surface, "vkCreateWaylandSurfaceKHR"); #endif #if defined(VK_USE_PLATFORM_XCB_KHR) if (api_selection == nullptr || string_eq(api_selection, "VK_USE_PLATFORM_XCB_KHR")) return create_surface_helper(functions, inst, surface, "vkCreateXcbSurfaceKHR"); #endif return VK_ERROR_NOT_PERMITTED_KHR; #else return create_surface_helper( functions, inst, surface, "vkCreateDisplayPlaneSurfaceKHR"); #endif } VkResult create_surface(InstWrapper& inst, VkSurfaceKHR& surface, const char* api_selection) { return create_surface(inst.functions, inst.inst, surface, api_selection); } VkResult create_debug_callback(InstWrapper& inst, const VkDebugReportCallbackCreateInfoEXT& create_info, VkDebugReportCallbackEXT& callback) { return inst.functions->vkCreateDebugReportCallbackEXT(inst.inst, &create_info, nullptr, &callback); } extern "C" { void __ubsan_on_report() { FAIL() << "Encountered an undefined behavior sanitizer error"; } void __asan_on_error() { FAIL() << "Encountered an address sanitizer error"; } void __tsan_on_report() { FAIL() << "Encountered a thread sanitizer error"; } } // extern "C"