blob: 14b7431621baf6330ab569de6ff025ecbf7f6d5d [file] [log] [blame]
/*
* Copyright (c) 2015-2025 The Khronos Group Inc.
* Copyright (c) 2015-2025 Valve Corporation
* Copyright (c) 2015-2025 LunarG, Inc.
* Copyright (c) 2015-2025 Google, Inc.
* Modifications Copyright (C) 2020-2021 Advanced Micro Devices, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*/
#include <vulkan/vulkan_core.h>
#include <thread>
#include "../framework/layer_validation_tests.h"
#include "../framework/descriptor_helper.h"
#include "../framework/thread_helper.h"
#if GTEST_IS_THREADSAFE
class PositiveThreading : public VkLayerTest {};
TEST_F(PositiveThreading, DisplayObjects) {
TEST_DESCRIPTION("Create and use VkDisplayKHR objects with GetPhysicalDeviceDisplayPropertiesKHR in thread-safety.");
AddRequiredExtensions(VK_KHR_SURFACE_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_DISPLAY_EXTENSION_NAME);
RETURN_IF_SKIP(Init());
uint32_t prop_count = 0;
vk::GetPhysicalDeviceDisplayPropertiesKHR(Gpu(), &prop_count, nullptr);
if (prop_count == 0) {
GTEST_SKIP() << "No VkDisplayKHR properties to query";
}
std::vector<VkDisplayPropertiesKHR> display_props{prop_count};
// Create a VkDisplayKHR object
vk::GetPhysicalDeviceDisplayPropertiesKHR(Gpu(), &prop_count, display_props.data());
ASSERT_NE(prop_count, 0U);
// Now use this new object in an API call that thread safety will track
prop_count = 0;
vk::GetDisplayModePropertiesKHR(Gpu(), display_props[0].display, &prop_count, nullptr);
}
TEST_F(PositiveThreading, DisplayPlaneObjects) {
TEST_DESCRIPTION("Create and use VkDisplayKHR objects with GetPhysicalDeviceDisplayPlanePropertiesKHR in thread-safety.");
AddRequiredExtensions(VK_KHR_SURFACE_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_DISPLAY_EXTENSION_NAME);
RETURN_IF_SKIP(Init());
uint32_t prop_count = 0;
vk::GetPhysicalDeviceDisplayPlanePropertiesKHR(Gpu(), &prop_count, nullptr);
if (prop_count != 0) {
// only grab first plane property
prop_count = 1;
VkDisplayPlanePropertiesKHR display_plane_props = {};
// Create a VkDisplayKHR object
vk::GetPhysicalDeviceDisplayPlanePropertiesKHR(Gpu(), &prop_count, &display_plane_props);
// Now use this new object in an API call
prop_count = 0;
vk::GetDisplayModePropertiesKHR(Gpu(), display_plane_props.currentDisplay, &prop_count, nullptr);
}
}
TEST_F(PositiveThreading, UpdateDescriptorUpdateAfterBindNoCollision) {
TEST_DESCRIPTION("Two threads updating the same UAB descriptor set, expected not to generate a threading error");
AddRequiredExtensions(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_MAINTENANCE_3_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::descriptorBindingStorageBufferUpdateAfterBind);
RETURN_IF_SKIP(Init());
InitRenderTarget();
OneOffDescriptorIndexingSet descriptor_set(m_device, {
{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr, VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT},
{1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr, VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT},
});
vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
ThreadTestData data;
data.device = device();
data.descriptorSet = descriptor_set.set_;
data.binding = 0;
data.buffer = buffer;
std::atomic<bool> bailout{false};
data.bailout = &bailout;
m_errorMonitor->SetBailout(data.bailout);
// Update descriptors from another thread.
std::thread thread(UpdateDescriptor, &data);
// Update descriptors from this thread at the same time.
ThreadTestData data2;
data2.device = device();
data2.descriptorSet = descriptor_set.set_;
data2.binding = 1;
data2.buffer = buffer;
data2.bailout = &bailout;
UpdateDescriptor(&data2);
thread.join();
m_errorMonitor->SetBailout(NULL);
}
TEST_F(PositiveThreading, UpdateDescriptorUnusedWhilePendingNoCollision) {
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8975");
AddRequiredExtensions(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME);
AddRequiredExtensions(VK_KHR_MAINTENANCE_3_EXTENSION_NAME);
AddRequiredFeature(vkt::Feature::descriptorBindingUpdateUnusedWhilePending);
RETURN_IF_SKIP(Init());
InitRenderTarget();
OneOffDescriptorIndexingSet descriptor_set(m_device, {
{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr, VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT},
{1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT,
nullptr, VK_DESCRIPTOR_BINDING_UPDATE_UNUSED_WHILE_PENDING_BIT},
});
vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
ThreadTestData data;
data.device = device();
data.descriptorSet = descriptor_set.set_;
data.binding = 0;
data.buffer = buffer;
std::atomic<bool> bailout{false};
data.bailout = &bailout;
m_errorMonitor->SetBailout(data.bailout);
// Update descriptors from another thread.
std::thread thread(UpdateDescriptor, &data);
// Update descriptors from this thread at the same time.
ThreadTestData data2;
data2.device = device();
data2.descriptorSet = descriptor_set.set_;
data2.binding = 1;
data2.buffer = buffer;
data2.bailout = &bailout;
UpdateDescriptor(&data2);
thread.join();
m_errorMonitor->SetBailout(NULL);
}
TEST_F(PositiveThreading, NullFenceCollision) {
RETURN_IF_SKIP(Init());
ThreadTestData data;
data.device = device();
std::atomic<bool> bailout{false};
data.bailout = &bailout;
m_errorMonitor->SetBailout(data.bailout);
// Call vk::DestroyFence of VK_NULL_HANDLE repeatedly using multiple threads.
// There should be no validation error from collision of that non-object.
std::thread thread(ReleaseNullFence, &data);
for (int i = 0; i < 40000; i++) {
vk::DestroyFence(device(), VK_NULL_HANDLE, NULL);
}
thread.join();
m_errorMonitor->SetBailout(NULL);
}
TEST_F(PositiveThreading, DebugObjectNames) {
AddRequiredExtensions(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
RETURN_IF_SKIP(InitFramework(&kDisableMessageLimit));
RETURN_IF_SKIP(InitState());
constexpr uint32_t count = 10000u;
VkDescriptorPoolSize pool_size{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, count};
VkDescriptorPoolCreateInfo descriptor_pool_ci = vku::InitStructHelper();
descriptor_pool_ci.maxSets = count;
descriptor_pool_ci.poolSizeCount = 1u;
descriptor_pool_ci.pPoolSizes = &pool_size;
vkt::DescriptorPool descriptor_pool(*m_device, descriptor_pool_ci);
VkDescriptorSetLayoutBinding binding{0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT, nullptr};
VkDescriptorSetLayoutCreateInfo set_layout_ci = vku::InitStructHelper();
set_layout_ci.bindingCount = 1u;
set_layout_ci.pBindings = &binding;
vkt::DescriptorSetLayout set_layout(*m_device, set_layout_ci);
binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
vkt::DescriptorSetLayout set_layout2(*m_device, set_layout_ci);
vkt::PipelineLayout pipeline_layout(*m_device, {&set_layout2});
VkDescriptorSetAllocateInfo allocate_info = vku::InitStructHelper();
allocate_info.descriptorPool = descriptor_pool;
allocate_info.descriptorSetCount = 1u;
allocate_info.pSetLayouts = &set_layout.handle();
VkDescriptorSet descriptor_sets[count];
for (uint32_t i = 0; i < count; ++i) {
vk::AllocateDescriptorSets(*m_device, &allocate_info, &descriptor_sets[i]);
}
vkt::Buffer buffer(*m_device, 256u, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
VkDescriptorBufferInfo buffer_info{buffer, 0, VK_WHOLE_SIZE};
VkWriteDescriptorSet descriptor_write = vku::InitStructHelper();
descriptor_write.dstBinding = 0u;
descriptor_write.dstArrayElement = 0u;
descriptor_write.descriptorCount = 1u;
descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
descriptor_write.pImageInfo = nullptr;
descriptor_write.pBufferInfo = &buffer_info;
descriptor_write.pTexelBufferView = nullptr;
for (uint32_t i = 0; i < count; ++i) {
descriptor_write.dstSet = descriptor_sets[i];
vk::UpdateDescriptorSets(*m_device, 1u, &descriptor_write, 0u, nullptr);
}
VkDebugUtilsObjectNameInfoEXT name_info = vku::InitStructHelper();
name_info.objectType = VK_OBJECT_TYPE_DESCRIPTOR_SET;
std::atomic<bool> bailout{false};
for (uint32_t i = 0; i < count; ++i) {
m_errorMonitor->SetDesiredError("VUID-vkCmdBindDescriptorSets-pDescriptorSets-00358");
}
m_errorMonitor->SetBailout(&bailout);
const auto set_name = [&]() {
for (uint32_t i = 0; i < count; ++i) {
std::string name = "handle" + std::to_string(i);
name_info.objectHandle = (uint64_t)descriptor_sets[i];
name_info.pObjectName = name.c_str();
vk::SetDebugUtilsObjectNameEXT(*m_device, &name_info);
if (i % 3 == 0) {
name_info.pObjectName = nullptr;
vk::SetDebugUtilsObjectNameEXT(*m_device, &name_info);
}
}
};
const auto bind_descriptor = [&]() {
m_command_buffer.Begin();
for (uint32_t i = 0; i < count; ++i) {
vk::CmdBindDescriptorSets(m_command_buffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0u, 1u,
&descriptor_sets[i], 0u, nullptr);
}
m_command_buffer.End();
};
std::thread thread2(bind_descriptor);
std::thread thread1(set_name);
thread1.join();
thread2.join();
m_errorMonitor->SetBailout(NULL);
m_errorMonitor->VerifyFound();
}
#endif // GTEST_IS_THREADSAFE
TEST_F(PositiveThreading, Queue) {
#if defined(VVL_ENABLE_TSAN)
GTEST_SKIP() << "https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5965";
#endif
TEST_DESCRIPTION("Test concurrent Queue access from vkGet and vkSubmit");
using namespace std::chrono;
SetTargetApiVersion(VK_API_VERSION_1_1);
RETURN_IF_SKIP(Init());
const auto queue_family = m_device->QueuesWithGraphicsCapability()[0]->family_index;
constexpr uint32_t queue_index = 0;
vkt::CommandPool command_pool(*m_device, queue_family);
const VkDevice device_h = device();
VkQueue queue_h;
vk::GetDeviceQueue(device(), queue_family, queue_index, &queue_h);
vkt::Queue queue_o(queue_h, queue_family);
const VkCommandBufferAllocateInfo cbai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, command_pool,
VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1};
vkt::CommandBuffer mock_cmdbuff(*m_device, cbai);
const VkCommandBufferBeginInfo cbbi{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr,
VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, nullptr};
mock_cmdbuff.Begin(&cbbi);
mock_cmdbuff.End();
std::mutex queue_mutex;
constexpr auto test_duration = seconds{2};
const auto timer_begin = steady_clock::now();
const auto &testing_thread1 = [&]() {
for (auto timer_now = steady_clock::now(); timer_now - timer_begin < test_duration; timer_now = steady_clock::now()) {
VkQueue dummy_q;
vk::GetDeviceQueue(device_h, queue_family, queue_index, &dummy_q);
}
};
const auto &testing_thread2 = [&]() {
for (auto timer_now = steady_clock::now(); timer_now - timer_begin < test_duration; timer_now = steady_clock::now()) {
VkSubmitInfo si = vku::InitStructHelper();
si.commandBufferCount = 1;
si.pCommandBuffers = &mock_cmdbuff.handle();
queue_mutex.lock();
ASSERT_EQ(VK_SUCCESS, vk::QueueSubmit(queue_h, 1, &si, VK_NULL_HANDLE));
queue_mutex.unlock();
}
};
const auto &testing_thread3 = [&]() {
for (auto timer_now = steady_clock::now(); timer_now - timer_begin < test_duration; timer_now = steady_clock::now()) {
queue_mutex.lock();
ASSERT_EQ(VK_SUCCESS, vk::QueueWaitIdle(queue_h));
queue_mutex.unlock();
}
};
std::array<std::thread, 3> threads = {std::thread(testing_thread1), std::thread(testing_thread2), std::thread(testing_thread3)};
for (auto &t : threads) t.join();
vk::QueueWaitIdle(queue_h);
}
TEST_F(PositiveThreading, GetPhysicalDeviceFeatures) {
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/9931");
VkInstanceCreateInfo instance_ci = GetInstanceCreateInfo();
// mimics --gtest_repeat=100 which is needed to reproduce constantly
for (uint32_t repeat = 0; repeat < 100; repeat++) {
VkInstance instance;
vk::CreateInstance(&instance_ci, nullptr, &instance);
const uint32_t thread_count = 4u;
uint32_t physical_device_count;
vk::EnumeratePhysicalDevices(instance, &physical_device_count, nullptr);
std::vector<VkPhysicalDevice> physical_devices(physical_device_count);
vk::EnumeratePhysicalDevices(instance, &physical_device_count, physical_devices.data());
const auto &thread = [&](uint32_t id) {
VkPhysicalDeviceFeatures features;
vk::GetPhysicalDeviceFeatures(physical_devices[id % physical_device_count], &features);
};
std::array<std::thread, thread_count> threads;
for (uint32_t i = 0u; i < thread_count; ++i) {
threads[i] = std::thread(thread, i);
}
for (auto &t : threads) {
t.join();
}
vk::DestroyInstance(instance, nullptr);
}
}
TEST_F(PositiveThreading, ObjectTrackerPoisoning) {
TEST_DESCRIPTION("Test that object tracker poisoning setup works in threaded environment");
RETURN_IF_SKIP(Init());
const VkDescriptorSetLayoutBinding binding = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_ALL, nullptr};
vkt::DescriptorSetLayout set_layout(*m_device, binding);
VkPipelineLayoutCreateInfo pipeline_layout_ci = vku::InitStructHelper();
pipeline_layout_ci.setLayoutCount = 1;
pipeline_layout_ci.pSetLayouts = &set_layout.handle();
auto thread_func = [this, &pipeline_layout_ci]() {
std::vector<vkt::PipelineLayout> pipeline_layouts;
for (uint32_t i = 0; i < 5000; i++) {
pipeline_layouts.emplace_back(*m_device, pipeline_layout_ci);
}
};
std::array<std::thread, 2> threads = {std::thread(thread_func), std::thread(thread_func)};
for (auto &t : threads) t.join();
}