@@ -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