Skip to content

Commit dfb1a7f

Browse files
committed
tests: Test synchronized present accesses
1 parent 90e06eb commit dfb1a7f

3 files changed

Lines changed: 225 additions & 0 deletions

File tree

tests/framework/binding.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1967,6 +1967,12 @@ void CommandBuffer::Destroy() noexcept {
19671967
}
19681968
CommandBuffer::~CommandBuffer() noexcept { Destroy(); }
19691969

1970+
CommandPool& CommandPool::operator=(CommandPool&& rhs) noexcept {
1971+
Destroy();
1972+
NonDispHandle<VkCommandPool>::operator=(std::move(rhs));
1973+
return *this;
1974+
}
1975+
19701976
CommandBuffer::CommandBuffer(CommandBuffer&& rhs) noexcept : Handle(std::move(rhs)) {
19711977
dev_handle_ = rhs.dev_handle_;
19721978
cmd_pool_ = rhs.cmd_pool_;

tests/framework/binding.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,9 @@ class CommandPool : public internal::NonDispHandle<VkCommandPool> {
10711071
void Init(const Device &dev, const VkCommandPoolCreateInfo &info);
10721072
void Init(const Device &dev, uint32_t queue_family_index, VkCommandPoolCreateFlags flags = 0);
10731073
void SetName(const char *name) { NonDispHandle<VkCommandPool>::SetName(VK_OBJECT_TYPE_COMMAND_POOL, name); }
1074+
1075+
CommandPool(CommandPool&& rhs) noexcept : NonDispHandle(std::move(rhs)) {}
1076+
CommandPool& operator=(CommandPool&& rhs) noexcept;
10741077
};
10751078

10761079
class CommandBuffer : public internal::Handle<VkCommandBuffer> {

tests/unit/sync_val_wsi_positive.cpp

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,3 +1134,219 @@ TEST_F(PositiveSyncValWsi, WaitAcquireFenceForDestoryedSwapchain) {
11341134
swapchain.Destroy();
11351135
fence.Wait(kWaitTimeout);
11361136
}
1137+
1138+
TEST_F(PositiveSyncValWsi, ConcurrentPresentMultipleSwapchains) {
1139+
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8576");
1140+
AddSurfaceExtension();
1141+
RETURN_IF_SKIP(InitSyncVal());
1142+
1143+
const uint32_t num_threads = 8;
1144+
const uint32_t num_frames_in_flight = 2;
1145+
const uint32_t N = 200;
1146+
1147+
std::mutex queue_mutex;
1148+
1149+
std::vector<SurfaceContext> surface_contexts(num_threads);
1150+
std::vector<vkt::Surface> surfaces(num_threads);
1151+
std::vector<vkt::Swapchain> swapchains;
1152+
1153+
for (uint32_t i = 0; i < num_threads; i++) {
1154+
if (CreateSurface(surface_contexts[i], surfaces[i]) != VK_SUCCESS) {
1155+
GTEST_SKIP() << "Failed to create surface " << i;
1156+
}
1157+
const SurfaceInformation surface_info = GetSwapchainInfo(surfaces[i]);
1158+
const VkSwapchainCreateInfoKHR swapchain_ci = GetDefaultSwapchainCreateInfo(surfaces[i], surface_info);
1159+
swapchains.emplace_back(*m_device, swapchain_ci);
1160+
}
1161+
1162+
std::atomic<bool> bailout{false};
1163+
monitor_.SetBailout(&bailout);
1164+
1165+
auto presenter_thread = [&, this](uint32_t thread_index) {
1166+
vkt::Swapchain& swapchain = swapchains[thread_index];
1167+
const auto swapchain_images = swapchain.GetImages();
1168+
1169+
std::vector<vkt::Semaphore> acquire_semaphores;
1170+
std::vector<vkt::Semaphore> present_semaphores;
1171+
std::vector<vkt::Fence> frame_fences;
1172+
1173+
vkt::CommandPool command_pool(*m_device, m_default_queue->family_index, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
1174+
std::vector<vkt::CommandBuffer> command_buffers;
1175+
for (uint32_t i = 0; i < num_frames_in_flight; i++) {
1176+
acquire_semaphores.emplace_back(*m_device);
1177+
frame_fences.emplace_back(*m_device, VK_FENCE_CREATE_SIGNALED_BIT);
1178+
command_buffers.emplace_back(*m_device, command_pool);
1179+
}
1180+
for (uint32_t i = 0; i < swapchain.GetImageCount(); i++) {
1181+
present_semaphores.emplace_back(*m_device);
1182+
}
1183+
uint32_t current_frame = 0;
1184+
for (uint32_t i = 0; i < N; i++) {
1185+
const uint32_t frame_index = current_frame % num_frames_in_flight;
1186+
frame_fences[frame_index].Wait(kWaitTimeout);
1187+
frame_fences[frame_index].Reset();
1188+
1189+
const uint32_t image_index = swapchain.AcquireNextImage(acquire_semaphores[frame_index], kWaitTimeout);
1190+
1191+
vkt::CommandBuffer& command_buffer = command_buffers[frame_index];
1192+
command_buffer.Begin();
1193+
command_buffer.TransitionLayout(swapchain_images[image_index], VK_IMAGE_LAYOUT_UNDEFINED,
1194+
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
1195+
command_buffer.End();
1196+
1197+
{
1198+
std::unique_lock<std::mutex> lock(queue_mutex);
1199+
m_default_queue->Submit(command_buffer, vkt::Wait(acquire_semaphores[frame_index]),
1200+
vkt::Signal(present_semaphores[image_index]), frame_fences[frame_index]);
1201+
}
1202+
{
1203+
std::unique_lock<std::mutex> lock(queue_mutex);
1204+
m_default_queue->Present(swapchain, image_index, present_semaphores[image_index]);
1205+
}
1206+
current_frame++;
1207+
if (bailout.load()) {
1208+
break;
1209+
}
1210+
}
1211+
{
1212+
std::unique_lock<std::mutex> lock(queue_mutex);
1213+
m_default_queue->Wait();
1214+
}
1215+
};
1216+
std::vector<std::thread> presenters;
1217+
for (int i = 0; i < num_threads; i++) {
1218+
presenters.emplace_back(presenter_thread, i);
1219+
}
1220+
1221+
for (auto& presenter : presenters) {
1222+
presenter.join();
1223+
}
1224+
monitor_.SetBailout(nullptr);
1225+
}
1226+
1227+
TEST_F(PositiveSyncValWsi, WaitForFencesClearsLastSynchronizedPresents) {
1228+
// It's a single threaded deterministic version of ConcurrentPresentMultipleSwapchains.
1229+
// If present access is marked as synchronized (last_synchronized_present in syncval implementation),
1230+
// then WaitForFences must clear such accesses.
1231+
TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8576");
1232+
AddSurfaceExtension();
1233+
RETURN_IF_SKIP(InitSyncVal());
1234+
1235+
struct SwapchainContext {
1236+
SurfaceContext surface_context;
1237+
vkt::Surface surface;
1238+
vkt::Swapchain swapchain;
1239+
std::vector<VkImage> swapchain_images;
1240+
vkt::Fence frame_fence;
1241+
vkt::CommandBuffer command_buffer;
1242+
vkt::Semaphore acquire_semaphore;
1243+
std::vector<vkt::Semaphore> present_semaphores;
1244+
1245+
void Init(PositiveSyncValWsi& test, vkt::CommandPool& command_pool) {
1246+
if (test.CreateSurface(surface_context, surface) != VK_SUCCESS) {
1247+
GTEST_SKIP() << "Failed to create surfac";
1248+
}
1249+
const SurfaceInformation surface_info = test.GetSwapchainInfo(surface);
1250+
const VkSwapchainCreateInfoKHR swapchain_ci = GetDefaultSwapchainCreateInfo(surface, surface_info);
1251+
swapchain = vkt::Swapchain(*test.DeviceObj(), swapchain_ci);
1252+
swapchain_images = swapchain.GetImages();
1253+
if (swapchain_images.size() != 2) {
1254+
GTEST_SKIP() << "The test requires swapchain with 2 images";
1255+
}
1256+
frame_fence = vkt::Fence(*test.DeviceObj(), VK_FENCE_CREATE_SIGNALED_BIT);
1257+
command_buffer = vkt::CommandBuffer(*test.DeviceObj(), command_pool);
1258+
acquire_semaphore = vkt::Semaphore(*test.DeviceObj());
1259+
for (size_t i = 0; i < swapchain_images.size(); i++) {
1260+
present_semaphores.emplace_back(*test.DeviceObj());
1261+
}
1262+
}
1263+
};
1264+
SwapchainContext ctx_a;
1265+
SwapchainContext ctx_b;
1266+
RETURN_IF_SKIP(ctx_a.Init(*this, m_command_pool));
1267+
RETURN_IF_SKIP(ctx_b.Init(*this, m_command_pool));
1268+
1269+
auto begin_frame = [&](SwapchainContext& ctx) {
1270+
ctx.frame_fence.Wait(kWaitTimeout);
1271+
ctx.frame_fence.Reset();
1272+
1273+
const uint32_t image_index = ctx.swapchain.AcquireNextImage(ctx.acquire_semaphore, kWaitTimeout);
1274+
ctx.command_buffer.Begin();
1275+
ctx.command_buffer.TransitionLayout(ctx.swapchain_images[image_index], VK_IMAGE_LAYOUT_UNDEFINED,
1276+
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
1277+
ctx.command_buffer.End();
1278+
return image_index;
1279+
};
1280+
auto submit = [&](SwapchainContext& ctx, uint32_t image_index) {
1281+
m_default_queue->Submit(ctx.command_buffer, vkt::Wait(ctx.acquire_semaphore),
1282+
vkt::Signal(ctx.present_semaphores[image_index]), ctx.frame_fence);
1283+
};
1284+
auto present = [&](SwapchainContext& ctx, uint32_t image_index) {
1285+
m_default_queue->Present(ctx.swapchain, image_index, ctx.present_semaphores[image_index]);
1286+
};
1287+
1288+
// Re-acquire image on swapchain A (need image that was already presented)
1289+
{
1290+
uint32_t image_index = begin_frame(ctx_a);
1291+
if (image_index != 0) {
1292+
GTEST_SKIP() << "Expected image index 0";
1293+
}
1294+
submit(ctx_a, image_index);
1295+
present(ctx_a, image_index);
1296+
1297+
image_index = begin_frame(ctx_a);
1298+
if (image_index != 1) {
1299+
m_default_queue->Wait();
1300+
GTEST_SKIP() << "Expected image index 1";
1301+
}
1302+
submit(ctx_a, image_index);
1303+
present(ctx_a, image_index);
1304+
1305+
image_index = begin_frame(ctx_a);
1306+
if (image_index != 0) { // get image 0 again
1307+
m_default_queue->Wait();
1308+
GTEST_SKIP() << "Expected image index 0";
1309+
}
1310+
}
1311+
1312+
// Run frame on B. This imports A's synchronized present access into ctx B.
1313+
// (synchronized because image was re-acquired)
1314+
{
1315+
const uint32_t image_index = begin_frame(ctx_b);
1316+
if (image_index != 0) {
1317+
m_default_queue->Wait();
1318+
GTEST_SKIP() << "Expected image index 0";
1319+
}
1320+
submit(ctx_b, image_index);
1321+
present(ctx_b, image_index);
1322+
}
1323+
1324+
// Submit layout transition on A
1325+
submit(ctx_a, 0);
1326+
1327+
{
1328+
// Import A's layout transition into B (via last batch)
1329+
const uint32_t image_index = begin_frame(ctx_b);
1330+
if (image_index != 1) {
1331+
m_default_queue->Wait();
1332+
GTEST_SKIP() << "Expected image index 1";
1333+
}
1334+
submit(ctx_b, image_index);
1335+
present(ctx_b, image_index);
1336+
1337+
// Import synchronized present accesses from B's image 0
1338+
// Fence wait removes imported A's layout transitions.
1339+
// In the original issue this did not remove *already synchronized present accesses* though
1340+
const uint32_t image_index2 = begin_frame(ctx_b);
1341+
if (image_index2 != 0) {
1342+
m_default_queue->Wait();
1343+
GTEST_SKIP() << "Expected image index 0";
1344+
}
1345+
submit(ctx_b, image_index2);
1346+
}
1347+
1348+
// This caused WRITE-AFTER-PRESENT hazard in the original issue
1349+
present(ctx_a, 0);
1350+
1351+
m_default_queue->Wait();
1352+
}

0 commit comments

Comments
 (0)