From 19e62c7abca4e5d837860c92985c3438e0edf5e9 Mon Sep 17 00:00:00 2001 From: Samuel Pitoiset Date: Tue, 4 Mar 2025 15:44:12 +0100 Subject: [PATCH] radv: add a small framework for RADV specific tests There are a lot of things that can't be tested outside of the driver, like drirc workarounds, RADV_DEBUG options and debugging stuff. Writing RADV specific tests would help to avoid introducing regressions. Signed-off-by: Samuel Pitoiset Part-of: --- src/amd/vulkan/meson.build | 5 +- src/amd/vulkan/tests/helpers.cpp | 206 +++++++++++++++++++++++++++++++ src/amd/vulkan/tests/helpers.h | 80 ++++++++++++ 3 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 src/amd/vulkan/tests/helpers.cpp create mode 100644 src/amd/vulkan/tests/helpers.h diff --git a/src/amd/vulkan/meson.build b/src/amd/vulkan/meson.build index 2ba0f741f92..c317f89487f 100644 --- a/src/amd/vulkan/meson.build +++ b/src/amd/vulkan/meson.build @@ -321,6 +321,8 @@ if with_radv_tests files( 'nir/radv_nir_lower_hit_attrib_derefs.c', 'tests/radv_nir_lower_hit_attrib_derefs_tests.cpp', + 'tests/helpers.cpp', + 'tests/helpers.h', ), cpp_args : [cpp_msvc_compat_args], gnu_symbol_visibility : 'hidden', @@ -333,7 +335,8 @@ if with_radv_tests inc_util, include_directories('.'), ], - dependencies : [dep_thread, idep_gtest, idep_nir, idep_mesautil], + link_with : [ libvulkan_radeon ], + dependencies : [dep_thread, idep_gtest, idep_nir, idep_mesautil, idep_vulkan_util_headers], ), suite : ['compiler', 'nir'], protocol : 'gtest', diff --git a/src/amd/vulkan/tests/helpers.cpp b/src/amd/vulkan/tests/helpers.cpp new file mode 100644 index 00000000000..6bc27f536e0 --- /dev/null +++ b/src/amd/vulkan/tests/helpers.cpp @@ -0,0 +1,206 @@ +/* + * Copyright © 2025 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#include "helpers.h" +#include "util/macros.h" + +extern "C" { +PFN_vkVoidFunction VKAPI_CALL vk_icdGetInstanceProcAddr(VkInstance instance, const char *pName); +} + +radv_test::radv_test() +{ + /* Force the driver to create a noop device that doesn't require AMDGPU. */ + setenv("RADV_FORCE_FAMILY", "navi21", 1); +} + +radv_test::~radv_test() +{ + assert(envvars.size() == 0); + unsetenv("RADV_FORCE_FAMILY"); +} + +void +radv_test::create_device() +{ + VkResult result; + + /* Create instance. */ + VkApplicationInfo app_info = { + .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, + .pApplicationName = "radv_tests", + .apiVersion = VK_API_VERSION_1_4, + }; + + VkInstanceCreateInfo instance_create_info = { + .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + .pApplicationInfo = &app_info, + }; + + result = ((PFN_vkCreateInstance)vk_icdGetInstanceProcAddr(NULL, "vkCreateInstance"))(&instance_create_info, NULL, + &instance); + assert(result == VK_SUCCESS); + +#define ITEM(n) n = (PFN_vk##n)vk_icdGetInstanceProcAddr(instance, "vk" #n); + FUNCTION_LIST +#undef ITEM + + /* Get physical device. */ + uint32_t device_count = 1; + + result = EnumeratePhysicalDevices(instance, &device_count, &physical_device); + assert(result == VK_SUCCESS); + + /* Create logical device. */ + static const char *extensions[] = {"VK_KHR_pipeline_executable_properties"}; + + VkDeviceCreateInfo device_create_info = { + .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, + .enabledExtensionCount = ARRAY_SIZE(extensions), + .ppEnabledExtensionNames = extensions, + }; + + result = CreateDevice(physical_device, &device_create_info, NULL, &device); + assert(result == VK_SUCCESS); +} + +void +radv_test::destroy_device() +{ + unset_envvars(); + + DestroyDevice(device, NULL); + DestroyInstance(instance, NULL); +} + +void +radv_test::get_physical_device_properties2(VkPhysicalDeviceProperties2 *pdev_props) +{ + GetPhysicalDeviceProperties2(physical_device, pdev_props); +} + +void +radv_test::get_physical_device_format_properties2(VkFormat format, VkFormatProperties2 *format_props) +{ + GetPhysicalDeviceFormatProperties2(physical_device, format, format_props); +} + +bool +radv_test::is_dedicated_sparse_queue_enabled() +{ + bool found_dedicated_sparse_queue = false; + uint32_t num_queue_family_props = 0; + + GetPhysicalDeviceQueueFamilyProperties2(physical_device, &num_queue_family_props, NULL); + if (num_queue_family_props > 0) { + VkQueueFamilyProperties2 *queue_family_props = NULL; + + queue_family_props = (VkQueueFamilyProperties2 *)calloc(num_queue_family_props, sizeof(*queue_family_props)); + assert(queue_family_props); + + GetPhysicalDeviceQueueFamilyProperties2(physical_device, &num_queue_family_props, queue_family_props); + + for (uint32_t i = 0; i < num_queue_family_props; i++) { + VkQueueFamilyProperties2 *queue_family_prop = &queue_family_props[i]; + if (queue_family_prop->queueFamilyProperties.queueFlags == VK_QUEUE_SPARSE_BINDING_BIT) + found_dedicated_sparse_queue = true; + } + + free(queue_family_props); + } + + return found_dedicated_sparse_queue; +} + +void +radv_test::create_compute_pipeline(uint32_t code_size, const uint32_t *code, VkPipelineCreateFlags flags) +{ + VkResult result; + + VkShaderModuleCreateInfo shader_module_create_info = { + .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, + .codeSize = code_size, + .pCode = code, + }; + VkShaderModule shader_module; + + result = CreateShaderModule(device, &shader_module_create_info, NULL, &shader_module); + assert(result == VK_SUCCESS); + + VkPipelineLayoutCreateInfo pipeline_layout_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, + }; + + result = CreatePipelineLayout(device, &pipeline_layout_info, NULL, &pipeline_layout); + assert(result == VK_SUCCESS); + + VkPipelineShaderStageCreateInfo stage_create_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, + .stage = VK_SHADER_STAGE_COMPUTE_BIT, + .module = shader_module, + .pName = "main", + }; + + VkComputePipelineCreateInfo pipeline_create_info = { + .sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO, + .flags = flags, + .stage = stage_create_info, + .layout = pipeline_layout, + }; + + result = CreateComputePipelines(device, VK_NULL_HANDLE, 1, &pipeline_create_info, NULL, &pipeline); + assert(result == VK_SUCCESS); + + DestroyShaderModule(device, shader_module, NULL); +} + +void +radv_test::destroy_pipeline() +{ + DestroyPipelineLayout(device, pipeline_layout, NULL); + DestroyPipeline(device, pipeline, NULL); +} + +uint64_t +radv_test::get_pipeline_hash(VkShaderStageFlags stage) +{ + VkResult result; + + uint32_t executable_count = 16; + VkPipelineExecutablePropertiesKHR executables[16]; + VkPipelineInfoKHR pipeline_info = { + .sType = VK_STRUCTURE_TYPE_PIPELINE_INFO_KHR, + .pipeline = pipeline, + }; + + result = GetPipelineExecutablePropertiesKHR(device, &pipeline_info, &executable_count, executables); + assert(result == VK_SUCCESS); + + uint32_t executable = 0; + for (; executable < executable_count; executable++) { + if (executables[executable].stages == stage) + break; + } + assert(executable != executable_count); + + uint32_t stat_count = 32; + VkPipelineExecutableStatisticKHR stats[32]; + VkPipelineExecutableInfoKHR exec_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_EXECUTABLE_INFO_KHR, + .pipeline = pipeline, + .executableIndex = executable, + }; + + result = GetPipelineExecutableStatisticsKHR(device, &exec_info, &stat_count, stats); + assert(result == VK_SUCCESS); + + for (uint32_t i = 0; i < stat_count; i++) { + if (!strcmp(stats[i].name, "Driver pipeline hash")) + return stats[i].value.u64; + } + + unreachable("Driver pipeline hash not found"); +} diff --git a/src/amd/vulkan/tests/helpers.h b/src/amd/vulkan/tests/helpers.h new file mode 100644 index 00000000000..1a39450b5b8 --- /dev/null +++ b/src/amd/vulkan/tests/helpers.h @@ -0,0 +1,80 @@ +/* + * Copyright © 2025 Valve Corporation + * + * SPDX-License-Identifier: MIT + */ + +#ifndef RADV_TEST_HELPERS_H +#define RADV_TEST_HELPERS_H + +#include +#include + +#include +#include + +#define FUNCTION_LIST \ + ITEM(CreateInstance) \ + ITEM(DestroyInstance) \ + ITEM(EnumeratePhysicalDevices) \ + ITEM(GetPhysicalDeviceProperties2) \ + ITEM(GetPhysicalDeviceQueueFamilyProperties2) \ + ITEM(GetPhysicalDeviceFormatProperties2) \ + ITEM(CreateDevice) \ + ITEM(DestroyDevice) \ + ITEM(CreateShaderModule) \ + ITEM(DestroyShaderModule) \ + ITEM(CreateComputePipelines) \ + ITEM(DestroyPipeline) \ + ITEM(CreatePipelineLayout) \ + ITEM(DestroyPipelineLayout) \ + ITEM(GetPipelineExecutableStatisticsKHR) \ + ITEM(GetPipelineExecutablePropertiesKHR) + +class radv_test : public testing::Test { +public: + radv_test(); + ~radv_test(); + + void create_device(); + void destroy_device(); + + void get_physical_device_properties2(VkPhysicalDeviceProperties2 *pdev_props); + void get_physical_device_format_properties2(VkFormat format, VkFormatProperties2 *format_props); + + bool is_dedicated_sparse_queue_enabled(); + + void create_compute_pipeline(uint32_t code_size, const uint32_t *code, VkPipelineCreateFlags flags = 0); + void destroy_pipeline(); + + uint64_t get_pipeline_hash(VkShaderStageFlags stage); + + void add_envvar(std::string name, std::string value) + { + setenv(name.c_str(), value.c_str(), 1); + + envvars.insert(std::make_pair(std::move(name), std::move(value))); + } + + void unset_envvars() + { + for (auto &envvar : envvars) + unsetenv(envvar.first.c_str()); + envvars.clear(); + } + +#define ITEM(n) PFN_vk##n n; + FUNCTION_LIST +#undef ITEM + + VkInstance instance; + VkPhysicalDevice physical_device; + VkDevice device; + + VkPipelineLayout pipeline_layout; + VkPipeline pipeline; + + std::unordered_map envvars; +}; + +#endif /* RADV_TEST_HELPERS_H */