/* * Copyright (c) 2021 The Khronos Group Inc. * Copyright (c) 2021 Valve Corporation * Copyright (c) 2021 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" enum class TestConfig { add_layer_implementation, add_layer_interception, }; bool has_flag(std::vector const& flags, TestConfig config) { for (auto const& flag : flags) if (flag == config) return true; return false; } /* Creates a TestICD with a function unknown to the loader called vkNotRealFuncTEST. The TestICD, when vk_icdGetPhysicalDeviceProcAddr is called, will return the custom_physical_device_function if the function name matches vkNotRealFuncTEST. The test then calls the function to verify that the unknown physical device function dispatching is working correctly. */ template struct custom_functions { static VKAPI_ATTR uint32_t VKAPI_CALL func_zero(DispatchableHandleType, uint32_t foo) { return foo; }; static VKAPI_ATTR uint32_t VKAPI_CALL func_one(DispatchableHandleType, uint32_t foo, uint32_t bar) { return foo + bar; }; static VKAPI_ATTR float VKAPI_CALL func_two(DispatchableHandleType, uint32_t foo, uint32_t bar, float baz) { return baz + foo + bar; }; static VKAPI_ATTR int VKAPI_CALL func_three(DispatchableHandleType, int* ptr_a, int* ptr_b) { return *ptr_a + *ptr_b; }; static VKAPI_ATTR float VKAPI_CALL func_four(DispatchableHandleType, int* ptr_a, int* ptr_b, int foo, int bar, float k, float l, char a, char b, char c) { return *ptr_a + *ptr_b + foo + bar + k + l + static_cast(a) + static_cast(b) + static_cast(c); }; }; /* Functions for testing of layer interception of unknown functions. Note the need to pass a pointer to the layer and the name of the called function as a parameter, this is necessary to allow a generic layer implementation, as the layer must look up the function pointer to use. A real layer would store the function pointer in a dedicated structure per-instance/device, but since the TestLayer is a generic layer, there isn't a fixed list of functions that should be supported. */ PFN_vkVoidFunction find_custom_func(TestLayer* layer, const char* name) { if (layer->custom_dispatch_functions.count(name) > 0) { return layer->custom_dispatch_functions.at(name); } return nullptr; } template struct layer_intercept_functions { static VKAPI_ATTR uint32_t VKAPI_CALL func_zero(DispatchableHandleType handle, TestLayer* layer, const char* name, uint32_t i) { auto func = reinterpret_cast(find_custom_func(layer, name)); if (func == nullptr) return 1337; return func(handle, layer, name, i + 3); } static VKAPI_ATTR uint32_t VKAPI_CALL func_one(DispatchableHandleType handle, TestLayer* layer, const char* name, uint32_t i, float f) { auto func = reinterpret_cast(find_custom_func(layer, name)); if (func == nullptr) return 1337; return func(handle, layer, name, i + 2, f + 1.f); } static VKAPI_ATTR float VKAPI_CALL func_two(DispatchableHandleType handle, TestLayer* layer, const char* name, uint32_t foo, uint32_t bar, float baz) { auto func = reinterpret_cast(find_custom_func(layer, name)); if (func == nullptr) return -1337; return func(handle, layer, name, foo + 1, bar + 2, baz * 2); }; static VKAPI_ATTR int VKAPI_CALL func_three(DispatchableHandleType handle, TestLayer* layer, const char* name, int* ptr_a, int* ptr_b) { auto func = reinterpret_cast(find_custom_func(layer, name)); if (func == nullptr) return -1337; *ptr_a += 1; *ptr_b -= 2; return func(handle, layer, name, ptr_a, ptr_b); }; static VKAPI_ATTR float VKAPI_CALL func_four(DispatchableHandleType handle, TestLayer* layer, const char* name, int* ptr_a, int* ptr_b, int foo, int bar, float k, float l, char, char, char) { auto func = reinterpret_cast(find_custom_func(layer, name)); if (func == nullptr) return -1337.f; return func(handle, layer, name, ptr_a, ptr_b, foo + 4, bar + 5, k + 1, l + 2, 'd', 'e', 'f'); }; }; template struct layer_implementation_functions { static VKAPI_ATTR uint32_t VKAPI_CALL func_zero(DispatchableHandleType, TestLayer*, const char*, uint32_t i) { return i * 3; } static VKAPI_ATTR uint32_t VKAPI_CALL func_one(DispatchableHandleType, TestLayer*, const char*, uint32_t i, float f) { return static_cast(i * 3 + f * 10.f); } static VKAPI_ATTR float VKAPI_CALL func_two(DispatchableHandleType, TestLayer*, const char*, uint32_t foo, uint32_t bar, float baz) { return baz + foo + bar; }; static VKAPI_ATTR int VKAPI_CALL func_three(DispatchableHandleType, TestLayer*, const char*, int* ptr_a, int* ptr_b) { return *ptr_a + *ptr_b; }; static VKAPI_ATTR float VKAPI_CALL func_four(DispatchableHandleType, TestLayer*, const char*, int* ptr_a, int* ptr_b, int foo, int bar, float k, float l, char a, char b, char c) { return *ptr_a + *ptr_b + foo + bar + k + l + static_cast(a) + static_cast(b) + static_cast(c); }; }; // Add function_count strings to the func_names vector, starting at function_start place. Essentially a utility for filling // up a list of names to use later void add_function_names(std::vector& func_names, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { func_names.push_back(std::string("vkNotIntRealFuncTEST_") + std::to_string(i++)); func_names.push_back(std::string("vkNotIntRealIntFuncTEST_") + std::to_string(i++)); func_names.push_back(std::string("vkIntNotIntRealFloatFuncTEST_") + std::to_string(i++)); func_names.push_back(std::string("vkNotRealFuncPointerPointerTEST_") + std::to_string(i++)); func_names.push_back(std::string("vkNotRealFuncTEST_pointer_pointer_int_int_float_float_char_char_char_") + std::to_string(i++)); } } // Add data to the function_list, which could be a driver or a layer list of implementation functions. template void fill_implementation_functions(std::vector& function_list, std::vector& func_names, FunctionStruct const& funcs, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { function_list.push_back(VulkanFunction{func_names.at(i++), to_vkVoidFunction(funcs.func_zero)}); function_list.push_back(VulkanFunction{func_names.at(i++), to_vkVoidFunction(funcs.func_one)}); function_list.push_back(VulkanFunction{func_names.at(i++), to_vkVoidFunction(funcs.func_two)}); function_list.push_back(VulkanFunction{func_names.at(i++), to_vkVoidFunction(funcs.func_three)}); function_list.push_back(VulkanFunction{func_names.at(i++), to_vkVoidFunction(funcs.func_four)}); } } // Add device interception functions to a layer. Need to call `add_custom_device_interception_function` since the layer has // to setup a unordered_map for storing the next function in the chain, and key it based on the name template void fill_device_intercept_functions(TestLayer& layer, std::vector& func_names, FunctionStruct const& funcs, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { layer.add_custom_device_interception_function(func_names.at(i++), to_vkVoidFunction(funcs.func_zero)); layer.add_custom_device_interception_function(func_names.at(i++), to_vkVoidFunction(funcs.func_one)); layer.add_custom_device_interception_function(func_names.at(i++), to_vkVoidFunction(funcs.func_two)); layer.add_custom_device_interception_function(func_names.at(i++), to_vkVoidFunction(funcs.func_three)); layer.add_custom_device_interception_function(func_names.at(i++), to_vkVoidFunction(funcs.func_four)); } } // Add physical device interception functions to a layer. Need to call `add_custom_device_interception_function` since the // layer has to setup a unordered_map for storing the next function in the chain, and key it based on the name template void fill_phys_dev_intercept_functions(TestLayer& layer, std::vector& func_names, FunctionStruct const& funcs, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { layer.add_custom_physical_device_intercept_function(func_names.at(i++), to_vkVoidFunction(funcs.func_zero)); layer.add_custom_physical_device_intercept_function(func_names.at(i++), to_vkVoidFunction(funcs.func_one)); layer.add_custom_physical_device_intercept_function(func_names.at(i++), to_vkVoidFunction(funcs.func_two)); layer.add_custom_physical_device_intercept_function(func_names.at(i++), to_vkVoidFunction(funcs.func_three)); layer.add_custom_physical_device_intercept_function(func_names.at(i++), to_vkVoidFunction(funcs.func_four)); } } template void check_custom_functions(FunctionLoader& loader, ParentType parent, DispatchableHandleType handle, FunctionStruct const&, std::vector& func_names, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { decltype(FunctionStruct::func_zero)* returned_func_i = loader.load(parent, func_names.at(i++).c_str()); ASSERT_NE(returned_func_i, nullptr); EXPECT_EQ(returned_func_i(handle, i * 10), i * 10); decltype(FunctionStruct::func_one)* returned_func_ii = loader.load(parent, func_names.at(i++).c_str()); ASSERT_NE(returned_func_ii, nullptr); EXPECT_EQ(returned_func_ii(handle, i * 10, i * 5), i * 10 + i * 5); decltype(FunctionStruct::func_two)* returned_func_iif = loader.load(parent, func_names.at(i++).c_str()); ASSERT_NE(returned_func_iif, nullptr); EXPECT_NEAR(returned_func_iif(handle, i * 10, i * 5, 0.1234f), i * 10 + i * 5 + 0.1234f, 0.001); int x = 5; int y = -505; decltype(FunctionStruct::func_three)* returned_func_pp = loader.load(parent, func_names.at(i++).c_str()); ASSERT_NE(returned_func_pp, nullptr); EXPECT_EQ(returned_func_pp(handle, &x, &y), -500); x = 5; y = -505; decltype(FunctionStruct::func_four)* returned_func_ppiiffccc = loader.load(parent, func_names.at(i++).c_str()); ASSERT_NE(returned_func_ppiiffccc, nullptr); EXPECT_NEAR(returned_func_ppiiffccc(handle, &x, &y, 200, 300, 0.123f, 1001.89f, 'a', 'b', 'c'), -500 + 200 + 300 + 0.123 + 1001.89 + 97 + 98 + 99, 0.001f); } } template void check_layer_custom_functions(FunctionLoader& loader, ParentType parent, DispatchableHandleType handle, TestLayer& layer, FunctionStruct const&, std::vector& func_names, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { decltype(FunctionStruct::func_zero)* returned_func_i = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_i, nullptr); EXPECT_EQ(returned_func_i(handle, &layer, func_names.at(i).c_str(), i), (i + 3) * 3); i++; decltype(FunctionStruct::func_one)* returned_func_if = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_if, nullptr); EXPECT_EQ(returned_func_if(handle, &layer, func_names.at(i).c_str(), i, i + 1.f), (i + 2) * 3 + (i + 2) * 10); i++; decltype(FunctionStruct::func_two)* returned_func_iif = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_iif, nullptr); EXPECT_NEAR(returned_func_iif(handle, &layer, func_names.at(i).c_str(), i * 10, i * 5, 0.1234f), (i * 10 + 1) + (i * 5 + 2) + (0.1234f * 2.f), 0.001); i++; int x = 5 + i; int y = -505 - i; decltype(FunctionStruct::func_three)* returned_func_pp = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_pp, nullptr); EXPECT_EQ(returned_func_pp(handle, &layer, func_names.at(i).c_str(), &x, &y), (5 + static_cast(i) + 1) + (-505 - static_cast(i) - 2)); i++; x = 5; y = -505; decltype(FunctionStruct::func_four)* returned_func_ppiiffccc = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_ppiiffccc, nullptr); EXPECT_NEAR( returned_func_ppiiffccc(handle, &layer, func_names.at(i).c_str(), &x, &y, 200, 300, 0.123f, 1001.89f, 'a', 'b', 'c'), -500 + (200 + 4) + (300 + 5) + (0.123 + 1) + (1001.89 + 2) + 100 + 101 + 102, 0.001f); // layer changes abc to def i++; } } template void check_layer_custom_functions_no_implementation(FunctionLoader& loader, ParentType parent, DispatchableHandleType handle, TestLayer& layer, FunctionStruct const&, std::vector& func_names, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { decltype(FunctionStruct::func_zero)* returned_func_i = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_i, nullptr); EXPECT_EQ(1337U, returned_func_i(handle, &layer, func_names.at(i).c_str(), i)); i++; decltype(FunctionStruct::func_one)* returned_func_if = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_if, nullptr); EXPECT_EQ(1337U, returned_func_if(handle, &layer, func_names.at(i).c_str(), i, i + 1.f)); i++; decltype(FunctionStruct::func_two)* returned_func_iif = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_iif, nullptr); EXPECT_NEAR(-1337.0, returned_func_iif(handle, &layer, func_names.at(i).c_str(), i * 10, i * 5, 0.1234f), 0.001); i++; int x = 5 + i; int y = -505 - i; decltype(FunctionStruct::func_three)* returned_func_pp = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_pp, nullptr); EXPECT_EQ(-1337, returned_func_pp(handle, &layer, func_names.at(i).c_str(), &x, &y)); i++; x = 5; y = -505; decltype(FunctionStruct::func_four)* returned_func_ppiiffccc = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_ppiiffccc, nullptr); EXPECT_NEAR( -1337.0, returned_func_ppiiffccc(handle, &layer, func_names.at(i).c_str(), &x, &y, 200, 300, 0.123f, 1001.89f, 'a', 'b', 'c'), 0.001); i++; } } template void check_layer_custom_functions_no_interception(FunctionLoader& loader, ParentType parent, DispatchableHandleType handle, TestLayer& layer, FunctionStruct const&, std::vector& func_names, uint32_t function_count, uint32_t function_start = 0) { for (uint32_t i = function_start; i < function_start + function_count;) { decltype(FunctionStruct::func_zero)* returned_func_i = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_i, nullptr); EXPECT_EQ(returned_func_i(handle, &layer, func_names.at(i).c_str(), i), (i)*3); i++; decltype(FunctionStruct::func_one)* returned_func_if = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_if, nullptr); EXPECT_EQ(returned_func_if(handle, &layer, func_names.at(i).c_str(), i, i + 1.f), (i)*3 + (i + 1) * 10); i++; decltype(FunctionStruct::func_two)* returned_func_iif = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_iif, nullptr); EXPECT_NEAR(returned_func_iif(handle, &layer, func_names.at(i).c_str(), i * 10, i * 5, 0.1234f), (i * 10) + (i * 5) + (0.1234f), 0.001); i++; int x = 5 + i; int y = -505 - i; decltype(FunctionStruct::func_three)* returned_func_pp = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_pp, nullptr); EXPECT_EQ(returned_func_pp(handle, &layer, func_names.at(i).c_str(), &x, &y), (5 + static_cast(i)) + (-505 - static_cast(i))); i++; x = 5; y = -505; decltype(FunctionStruct::func_four)* returned_func_ppiiffccc = loader.load(parent, func_names.at(i).c_str()); ASSERT_NE(returned_func_ppiiffccc, nullptr); EXPECT_NEAR( returned_func_ppiiffccc(handle, &layer, func_names.at(i).c_str(), &x, &y, 200, 300, 0.123f, 1001.89f, 'a', 'b', 'c'), -500 + (200) + (300) + (0.123) + (1001.89) + 97 + 98 + 99, 0.001f); i++; } } using custom_physical_device_functions = custom_functions; using layer_intercept_physical_device_functions = layer_intercept_functions; using layer_implementation_physical_device_functions = layer_implementation_functions; TEST(UnknownFunction, PhysicalDeviceFunction) { FrameworkEnvironment env{}; auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); fill_implementation_functions(driver.physical_devices.at(0).custom_physical_device_functions, function_names, custom_physical_device_functions{}, function_count); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); VkPhysicalDevice phys_dev = inst.GetPhysDev(); check_custom_functions(env.vulkan_functions, inst.inst, phys_dev, custom_physical_device_functions{}, function_names, function_count); } TEST(UnknownFunction, PhysicalDeviceFunctionMultipleDriverSupport) { FrameworkEnvironment env{}; auto& driver_0 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); auto& driver_1 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); // used to identify the GPUs driver_0.physical_devices.emplace_back("physical_device_0").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; driver_1.physical_devices.emplace_back("physical_device_1").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; for (uint32_t i = 0; i < function_count / 10; i++) { fill_implementation_functions(driver_0.physical_devices.at(0).custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10); fill_implementation_functions(driver_1.physical_devices.at(0).custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10 + 5); } InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); auto phys_devs = inst.GetPhysDevs(2); VkPhysicalDevice phys_dev_0 = phys_devs[0]; VkPhysicalDevice phys_dev_1 = phys_devs[1]; VkPhysicalDeviceProperties props{}; env.vulkan_functions.vkGetPhysicalDeviceProperties(phys_devs[0], &props); if (props.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { phys_dev_0 = phys_devs[1]; phys_dev_1 = phys_devs[0]; } for (uint32_t i = 0; i < function_count / 10; i++) { check_custom_functions(env.vulkan_functions, inst.inst, phys_dev_0, custom_physical_device_functions{}, function_names, 5, i * 10); check_custom_functions(env.vulkan_functions, inst.inst, phys_dev_1, custom_physical_device_functions{}, function_names, 5, i * 10 + 5); } } // Add unknown functions to driver 0, and try to use them on driver 1. TEST(UnknownFunctionDeathTests, PhysicalDeviceFunctionErrorPath) { FrameworkEnvironment env{}; auto& driver_0 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); auto& driver_1 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); std::vector function_names; add_function_names(function_names, 1); // used to identify the GPUs driver_0.physical_devices.emplace_back("physical_device_0").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; driver_1.physical_devices.emplace_back("physical_device_1").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; function_names.push_back(std::string("vkNotIntRealFuncTEST_0")); custom_physical_device_functions funcs{}; driver_0.physical_devices.at(0).custom_physical_device_functions.push_back( VulkanFunction{function_names.back(), to_vkVoidFunction(funcs.func_zero)}); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); auto phys_devs = inst.GetPhysDevs(2); VkPhysicalDevice phys_dev_to_use = phys_devs[1]; VkPhysicalDeviceProperties props{}; env.vulkan_functions.vkGetPhysicalDeviceProperties(phys_devs[1], &props); if (props.deviceType != VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) phys_dev_to_use = phys_devs[0]; // use the wrong GPU to query the functions, should get 5 errors decltype(custom_physical_device_functions::func_zero)* returned_func_i = env.vulkan_functions.load(inst.inst, function_names.at(0).c_str()); ASSERT_NE(returned_func_i, nullptr); ASSERT_DEATH(returned_func_i(phys_dev_to_use, 0), "Function vkNotIntRealFuncTEST_0 not supported for this physical device"); } TEST(UnknownFunction, PhysicalDeviceFunctionWithImplicitLayerImplementation) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("DISABLE_ME")), "implicit_layer_unknown_function_intercept.json"); auto& layer = env.get_test_layer(); fill_implementation_functions(layer.custom_physical_device_implementation_functions, function_names, layer_implementation_physical_device_functions{}, function_count); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); VkPhysicalDevice phys_dev = inst.GetPhysDev(); check_layer_custom_functions_no_interception(env.vulkan_functions, inst.inst, phys_dev, layer, layer_implementation_physical_device_functions{}, function_names, function_count); } TEST(UnknownFunction, PhysicalDeviceFunctionMultipleDriverSupportWithImplicitLayerImplementation) { FrameworkEnvironment env{}; auto& driver_0 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); auto& driver_1 = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); // used to identify the GPUs driver_0.physical_devices.emplace_back("physical_device_0").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU; driver_1.physical_devices.emplace_back("physical_device_1").properties.deviceType = VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU; for (uint32_t i = 0; i < function_count / 10; i++) { fill_implementation_functions(driver_0.physical_devices.at(0).custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10); fill_implementation_functions(driver_1.physical_devices.at(0).custom_physical_device_functions, function_names, custom_physical_device_functions{}, 5, i * 10 + 5); } env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("DISABLE_ME")), "implicit_layer_unknown_function_intercept.json"); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); auto phys_devs = inst.GetPhysDevs(2); VkPhysicalDevice phys_dev_0 = phys_devs[0]; VkPhysicalDevice phys_dev_1 = phys_devs[1]; VkPhysicalDeviceProperties props{}; env.vulkan_functions.vkGetPhysicalDeviceProperties(phys_devs[0], &props); if (props.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) { phys_dev_0 = phys_devs[1]; phys_dev_1 = phys_devs[0]; } for (uint32_t i = 0; i < function_count / 10; i++) { check_custom_functions(env.vulkan_functions, inst.inst, phys_dev_0, custom_physical_device_functions{}, function_names, 5, i * 10); check_custom_functions(env.vulkan_functions, inst.inst, phys_dev_1, custom_physical_device_functions{}, function_names, 5, i * 10 + 5); } } TEST(UnknownFunction, PhysicalDeviceFunctionWithImplicitLayerInterception) { FrameworkEnvironment env{}; env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("DISABLE_ME")), "implicit_layer_unknown_function_intercept.json"); auto& layer = env.get_test_layer(); fill_phys_dev_intercept_functions(layer, function_names, layer_intercept_physical_device_functions{}, function_count); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); VkPhysicalDevice phys_dev = inst.GetPhysDev(); check_layer_custom_functions_no_implementation(env.vulkan_functions, inst.inst, phys_dev, layer, layer_intercept_physical_device_functions{}, function_names, function_count); } TEST(UnknownFunction, PhysicalDeviceFunctionDriverSupportWithImplicitLayerInterception) { FrameworkEnvironment env{}; auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); uint32_t function_count = 100; std::vector function_names; add_function_names(function_names, function_count); fill_implementation_functions(driver.physical_devices.at(0).custom_physical_device_functions, function_names, layer_implementation_physical_device_functions{}, function_count); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("DISABLE_ME")), "implicit_layer_unknown_function_intercept.json"); auto& layer = env.get_test_layer(); fill_phys_dev_intercept_functions(layer, function_names, layer_intercept_physical_device_functions{}, function_count); InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); VkPhysicalDevice phys_dev = inst.GetPhysDev(); check_layer_custom_functions(env.vulkan_functions, inst.inst, phys_dev, layer, layer_intercept_physical_device_functions{}, function_names, function_count); } TEST(UnknownFunction, PhysicalDeviceFunctionWithMultipleImplicitLayersInterception) { FrameworkEnvironment env{}; auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)); std::vector function_names; uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; add_function_names(function_names, function_count); driver.physical_devices.emplace_back("physical_device_0"); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept_0") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("DISABLE_ME")), "implicit_layer_unknown_function_intercept_0.json"); auto& layer_0 = env.get_test_layer(0); layer_0.set_use_gipa_GetPhysicalDeviceProcAddr(true); env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept_1") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("DISABLE_ME")), "implicit_layer_unknown_function_intercept_1.json"); auto& layer_1 = env.get_test_layer(1); layer_1.set_use_gipa_GetPhysicalDeviceProcAddr(false); for (uint32_t i = 0; i < function_count / 10; i++) { fill_implementation_functions(driver.physical_devices.at(0).custom_physical_device_functions, function_names, layer_implementation_physical_device_functions{}, 5, i * 10); fill_phys_dev_intercept_functions(layer_0, function_names, layer_intercept_physical_device_functions{}, 5, i * 10); fill_phys_dev_intercept_functions(layer_1, function_names, layer_intercept_physical_device_functions{}, 5, i * 10 + 5); } InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); VkPhysicalDevice phys_dev = inst.GetPhysDev(); for (uint32_t i = 0; i < function_count / 10; i++) { check_layer_custom_functions(env.vulkan_functions, inst.inst, phys_dev, layer_0, layer_intercept_physical_device_functions{}, function_names, 5, i * 10); check_layer_custom_functions_no_implementation(env.vulkan_functions, inst.inst, phys_dev, layer_1, layer_intercept_physical_device_functions{}, function_names, 5, i * 10 + 5); } } template ParentType get_parent_type(InstWrapper const& inst, DeviceWrapper const& dev); template <> VkInstance get_parent_type(InstWrapper const& inst, DeviceWrapper const&) { return inst.inst; } template <> VkDevice get_parent_type(InstWrapper const&, DeviceWrapper const& dev) { return dev.dev; } template DispatchableHandleType get_dispatch_handle(FrameworkEnvironment& env, DeviceWrapper const& dev, std::vector const& flags); template <> VkDevice get_dispatch_handle(FrameworkEnvironment&, DeviceWrapper const& dev, std::vector const&) { return dev.dev; } template <> VkCommandBuffer get_dispatch_handle(FrameworkEnvironment& env, DeviceWrapper const& dev, std::vector const&) { VkCommandPool command_pool; VkCommandPoolCreateInfo pool_create_info{}; DeviceFunctions funcs{env.vulkan_functions, dev}; funcs.vkCreateCommandPool(dev, &pool_create_info, nullptr, &command_pool); VkCommandBuffer command_buffer; VkCommandBufferAllocateInfo alloc_info{}; alloc_info.commandBufferCount = 1; alloc_info.commandPool = command_pool; funcs.vkAllocateCommandBuffers(dev, &alloc_info, &command_buffer); return command_buffer; } template <> VkQueue get_dispatch_handle(FrameworkEnvironment& env, DeviceWrapper const& dev, std::vector const&) { DeviceFunctions funcs{env.vulkan_functions, dev.dev}; VkQueue queue; funcs.vkGetDeviceQueue(dev, 0, 0, &queue); return queue; } template void unknown_function_test_impl(std::vector const& flags) { using custom_functions_type = custom_functions; using layer_implementation_functions_type = layer_implementation_functions; using layer_intercept_functions_type = layer_intercept_functions; FrameworkEnvironment env{}; auto& driver = env.add_icd(TestICDDetails(TEST_ICD_PATH_VERSION_2_EXPORT_ICD_GPDPA)).add_physical_device({}); uint32_t function_count = MAX_NUM_UNKNOWN_EXTS; std::vector function_names; add_function_names(function_names, function_count); if (has_flag(flags, TestConfig::add_layer_interception)) { fill_implementation_functions(driver.physical_devices.back().known_device_functions, function_names, layer_implementation_functions_type{}, function_count); } else { fill_implementation_functions(driver.physical_devices.back().known_device_functions, function_names, custom_functions_type{}, function_count); } TestLayer* layer_ptr = nullptr; if (has_flag(flags, TestConfig::add_layer_implementation) || has_flag(flags, TestConfig::add_layer_interception)) { env.add_implicit_layer(ManifestLayer{}.add_layer(ManifestLayer::LayerDescription{} .set_name("VK_LAYER_implicit_layer_unknown_function_intercept") .set_lib_path(TEST_LAYER_PATH_EXPORT_VERSION_2) .set_disable_environment("DISABLE_ME")), "implicit_layer_unknown_function_intercept.json"); layer_ptr = &env.get_test_layer(); } if (has_flag(flags, TestConfig::add_layer_implementation) && has_flag(flags, TestConfig::add_layer_interception)) { for (uint32_t i = 0; i < function_count / 10; i++) { fill_implementation_functions(layer_ptr->custom_device_implementation_functions, function_names, layer_implementation_functions_type{}, 5, i * 10); fill_device_intercept_functions(*layer_ptr, function_names, layer_intercept_functions_type{}, 5, i * 10 + 5); } } else if (has_flag(flags, TestConfig::add_layer_implementation)) { fill_implementation_functions(layer_ptr->custom_device_implementation_functions, function_names, custom_functions_type{}, function_count); } else if (has_flag(flags, TestConfig::add_layer_interception)) { fill_device_intercept_functions(*layer_ptr, function_names, layer_intercept_functions_type{}, function_count); } InstWrapper inst{env.vulkan_functions}; inst.CheckCreate(); DeviceWrapper dev{inst}; dev.create_info.add_device_queue({}); dev.CheckCreate(inst.GetPhysDev()); auto dispatch_type = get_dispatch_handle(env, dev, flags); auto parent_type = get_parent_type(inst, dev); if (has_flag(flags, TestConfig::add_layer_implementation) && has_flag(flags, TestConfig::add_layer_interception)) { for (uint32_t i = 0; i < function_count / 10; i++) { check_layer_custom_functions_no_interception(env.vulkan_functions, parent_type, dispatch_type, *layer_ptr, layer_implementation_functions_type{}, function_names, 5, i * 10); } } else if (has_flag(flags, TestConfig::add_layer_interception)) { check_layer_custom_functions(env.vulkan_functions, parent_type, dispatch_type, *layer_ptr, layer_intercept_functions_type{}, function_names, function_count); } else { check_custom_functions(env.vulkan_functions, parent_type, dispatch_type, custom_functions_type{}, function_names, function_count); } } // Device TEST(UnknownFunction, DeviceFromGDPA) { unknown_function_test_impl({}); } TEST(UnknownFunction, DeviceFromGDPAWithLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_implementation}); } TEST(UnknownFunction, DeviceFromGDPAWithLayerInterception) { unknown_function_test_impl({TestConfig::add_layer_interception}); } TEST(UnknownFunction, DeviceFromGDPAWithLayerInterceptionAndLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_interception, TestConfig::add_layer_implementation}); } TEST(UnknownFunction, DeviceFromGIPA) { unknown_function_test_impl({}); } TEST(UnknownFunction, DeviceFromGIPAWithLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_implementation}); } TEST(UnknownFunction, DeviceFromGIPAWithLayerInterception) { unknown_function_test_impl({TestConfig::add_layer_interception}); } TEST(UnknownFunction, DeviceFromGIPAWithLayerInterceptionAndLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_interception, TestConfig::add_layer_implementation}); } // Command buffers TEST(UnknownFunction, CommandBufferFromGDPA) { unknown_function_test_impl({}); } TEST(UnknownFunction, CommandBufferFromGDPAWithLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_implementation}); } TEST(UnknownFunction, CommandBufferFromGDPAWithLayerInterception) { unknown_function_test_impl({TestConfig::add_layer_interception}); } TEST(UnknownFunction, CommandBufferFromGDPAWithLayerInterceptionAndLayerImplementation) { unknown_function_test_impl( {TestConfig::add_layer_interception, TestConfig::add_layer_implementation}); } TEST(UnknownFunction, CommandBufferFromGIPA) { unknown_function_test_impl({}); } TEST(UnknownFunction, CommandBufferFromGIPAWithLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_implementation}); } TEST(UnknownFunction, CommandBufferFromGIPAWithLayerInterception) { unknown_function_test_impl({TestConfig::add_layer_interception}); } TEST(UnknownFunction, CommandBufferFromGIPAWithLayerInterceptionAndLayerImplementation) { unknown_function_test_impl( {TestConfig::add_layer_interception, TestConfig::add_layer_implementation}); } // Queues TEST(UnknownFunction, QueueFromGDPA) { unknown_function_test_impl({}); } TEST(UnknownFunction, QueueFromGDPAWithLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_implementation}); } TEST(UnknownFunction, QueueFromGDPAWithLayerInterception) { unknown_function_test_impl({TestConfig::add_layer_interception}); } TEST(UnknownFunction, QueueFromGDPAWithLayerInterceptionAndLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_interception, TestConfig::add_layer_implementation}); } TEST(UnknownFunction, QueueFromGIPA) { unknown_function_test_impl({}); } TEST(UnknownFunction, QueueFromGIPAWithLayer) { unknown_function_test_impl({TestConfig::add_layer_implementation}); } TEST(UnknownFunction, QueueFromGIPAWithLayerInterception) { unknown_function_test_impl({TestConfig::add_layer_interception}); } TEST(UnknownFunction, QueueFromGIPAWithLayerInterceptionAndLayerImplementation) { unknown_function_test_impl({TestConfig::add_layer_interception, TestConfig::add_layer_implementation}); } /* The purpose of LayerInterceptData is to provide a place to store data that is accessible inside the interception function. It works by being a templated type with static variables. Every unique type used creates a new template instantiation, with its own static variable storage. Thus interception functions that are templated correctly have per-template static data storage at their disposal, which is used to query the next function in the chain and call down. */ template struct LayerInterceptData { static TestLayer* layer; static const char* name; }; template TestLayer* LayerInterceptData::layer = nullptr; template const char* LayerInterceptData::name = nullptr; template struct FunctionZero { static VKAPI_ATTR uint32_t VKAPI_CALL implementation(DispatchableHandle, uint32_t a, uint32_t b) { return a + b; } template static VKAPI_ATTR uint32_t VKAPI_CALL intercept(DispatchableHandle handle, uint32_t a, uint32_t b) { decltype(implementation)* func = reinterpret_cast(LayerType::layer->get_custom_intercept_function(LayerType::name)); if (func == nullptr) return 1337; return func(handle, a + 3, b + 7); } template static void check(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name, uint32_t interception_count = 1) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); EXPECT_EQ(returned_func(dispatch_type, 4, 9), (4 + 3 * interception_count) + (9 + 7 * interception_count)); } template static void check_no_implementation(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); EXPECT_EQ(returned_func(dispatch_type, 5, 2), 1337U); } }; template struct FunctionOne { static VKAPI_ATTR uint32_t VKAPI_CALL implementation(DispatchableHandle, uint32_t a, uint32_t b, char c) { return a + b + c; } template static VKAPI_ATTR uint32_t VKAPI_CALL intercept(DispatchableHandle handle, uint32_t a, uint32_t b, char c) { decltype(implementation)* func = reinterpret_cast(LayerType::layer->get_custom_intercept_function(LayerType::name)); if (func == nullptr) return 1337; return func(handle, a + 2, b + 9, c + 1); } template static void check(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name, uint32_t interception_count = 1) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); EXPECT_EQ(returned_func(dispatch_type, 12, 17, 'a'), (12 + 2 * interception_count) + (17 + 9 * interception_count) + ('a' + 1 * interception_count)); } template static void check_no_implementation(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); EXPECT_EQ(returned_func(dispatch_type, 1, 516, 'c'), 1337U); } }; template struct FunctionTwo { static VKAPI_ATTR float VKAPI_CALL implementation(DispatchableHandle, int* ptr_a, int* ptr_b) { return 0.123f + *ptr_a + *ptr_b; } template static VKAPI_ATTR float VKAPI_CALL intercept(DispatchableHandle handle, int* ptr_a, int* ptr_b) { decltype(implementation)* func = reinterpret_cast(LayerType::layer->get_custom_intercept_function(LayerType::name)); if (func == nullptr) return -1337.f; *ptr_a += 2; *ptr_b += 5; return func(handle, ptr_a, ptr_b); } template static void check(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name, uint32_t interception_count = 1) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); int x = 10, y = 3; EXPECT_NEAR(returned_func(dispatch_type, &x, &y), 0.123f + (10 + 2 * interception_count) + (3 + 5 * interception_count), 0.001); } template static void check_no_implementation(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); int x = 10, y = 0; EXPECT_NEAR(returned_func(dispatch_type, &x, &y), -1337.f, 0.001); } }; template struct FunctionThree { static VKAPI_ATTR float VKAPI_CALL implementation(DispatchableHandle, int* ptr_a, float* ptr_b, uint32_t c) { return 0.456f + *ptr_a + *ptr_b + c; } template static VKAPI_ATTR float VKAPI_CALL intercept(DispatchableHandle handle, int* ptr_a, float* ptr_b, uint32_t c) { decltype(implementation)* func = reinterpret_cast(LayerType::layer->get_custom_intercept_function(LayerType::name)); if (func == nullptr) return -1837.f; *ptr_a += 55; *ptr_b += 5.98f; return func(handle, ptr_a, ptr_b, c + 100); } template static void check(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name, uint32_t interception_count = 1) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); int x = 96; float y = 7; EXPECT_NEAR(returned_func(dispatch_type, &x, &y, 30), 0.456f + (96 + 55 * interception_count) + (7 + 5.98f * interception_count) + (30 + 100 * interception_count), 0.001); } template static void check_no_implementation(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); int x = 10; float y = 0; EXPECT_NEAR(returned_func(dispatch_type, &x, &y, 40), -1837.f, 0.001); } }; template struct FunctionFour { static VKAPI_ATTR VkResult VKAPI_CALL implementation(DispatchableHandle, VkPhysicalDeviceLimits* limits, uint32_t* count, VkExtensionProperties* props) { limits->nonCoherentAtomSize = 0x0000ABCD0000FEDCU; if (props == nullptr) { *count = 5; return VK_INCOMPLETE; } else { for (uint32_t i = 0; i < *count; i++) { props[i].specVersion = i; } return VK_SUCCESS; } } template static VKAPI_ATTR VkResult VKAPI_CALL intercept(DispatchableHandle handle, VkPhysicalDeviceLimits* limits, uint32_t* count, VkExtensionProperties* props) { decltype(implementation)* func = reinterpret_cast(LayerType::layer->get_custom_intercept_function(LayerType::name)); if (func == nullptr) return VK_ERROR_DEVICE_LOST; VkResult res = func(handle, limits, count, props); if (props) { for (uint32_t i = 5; i < *count; i++) { props[i].specVersion = 1234 + i * 2; } } else if (count) { *count += 1; } return res; } template static void check(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name, uint32_t interception_count = 1) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); VkPhysicalDeviceLimits limits{}; uint32_t count = 0; EXPECT_EQ(returned_func(dispatch_type, &limits, &count, nullptr), VK_INCOMPLETE); EXPECT_EQ(count, 5 + interception_count); std::vector props(count, VkExtensionProperties{}); EXPECT_EQ(returned_func(dispatch_type, &limits, &count, props.data()), VK_SUCCESS); for (uint32_t i = 0; i < 5; i++) { EXPECT_EQ(props.at(i).specVersion, i); } for (uint32_t i = 5; i < interception_count; i++) { EXPECT_EQ(props.at(i).specVersion, 1234 + i * 2); // interception should do this } } template static void check_no_implementation(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type, const char* name) { decltype(implementation)* returned_func = loader.load(parent, name); ASSERT_NE(returned_func, nullptr); VkPhysicalDeviceLimits limits{}; EXPECT_EQ(returned_func(dispatch_type, &limits, nullptr, nullptr), VK_ERROR_DEVICE_LOST); } }; struct UnknownFunction { std::string name; bool has_implementation = false; std::vector interception_stack; UnknownFunction() = default; UnknownFunction(std::string name) : name(name) {} template void check(VulkanFunctions const& loader, ParentType parent, DispatchableHandle dispatch_type) { if (has_implementation) { // Find how many layers intercept this function, stop if any layer 'implements' the function, thus doesn't return uint32_t intercept_count = 0; for (auto const& elem : interception_stack) { if (elem == 1) break; intercept_count++; } FunctionType::Function::check(loader, parent, dispatch_type, name.c_str(), intercept_count); } else { FunctionType::Function::check_no_implementation(loader, parent, dispatch_type, name.c_str()); } } void push_layer_implementation() { interception_stack.push_back(1); } void push_layer_interception() { interception_stack.push_back(0); } }; // For VkDevice, VkCommandBuffer, & VkQueue template struct UnknownFunctionInfo { using Function = FunctionType; static void add_to_driver(UnknownFunction& func, PhysicalDevice& physical_device) { physical_device.add_device_function({func.name.c_str(), to_vkVoidFunction(Function::implementation)}); func.has_implementation = true; } template static void add_to_layer(UnknownFunction& func, TestLayer& layer, LayerStruct) { LayerInterceptData::layer = &layer; LayerInterceptData::name = func.name.c_str(); layer.add_custom_device_interception_function( func.name, to_vkVoidFunction(&Function::template intercept>)); func.push_layer_interception(); } static void add_implementation_to_layer(UnknownFunction& func, TestLayer& layer) { layer.add_custom_device_implementation_function({func.name.c_str(), to_vkVoidFunction(Function::implementation)}); func.has_implementation = true; func.push_layer_implementation(); } }; // Specialization for VkPhysicalDevice template struct UnknownFunctionInfo { using Function = FunctionType; static void add_to_driver(UnknownFunction& func, PhysicalDevice& physical_device) { physical_device.add_custom_physical_device_function({func.name.c_str(), to_vkVoidFunction(Function::implementation)}); func.has_implementation = true; } template static void add_to_layer(UnknownFunction& func, TestLayer& layer, LayerStruct) { LayerInterceptData::layer = &layer; LayerInterceptData::name = func.name.c_str(); layer.add_custom_physical_device_intercept_function( func.name, to_vkVoidFunction(&Function::template intercept>)); func.push_layer_interception(); } static void add_implementation_to_layer(UnknownFunction& func, TestLayer& layer) { layer.add_custom_physical_device_implementation_function({func.name.c_str(), to_vkVoidFunction(Function::implementation)}); func.has_implementation = true; func.push_layer_implementation(); } }; struct Functions { template