| /* |
| * 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. |
| * |
| * 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 <thread> |
| #include "../framework/layer_validation_tests.h" |
| #include "../framework/external_memory_sync.h" |
| #include "../framework/render_pass_helper.h" |
| #include "../framework/sync_helper.h" |
| #include "containers/container_utils.h" |
| |
| #ifndef VK_USE_PLATFORM_WIN32_KHR |
| #include <poll.h> |
| #endif |
| |
| class PositiveSyncObject : public SyncObjectTest {}; |
| |
| TEST_F(PositiveSyncObject, Sync2OwnershipTranfersImage) { |
| TEST_DESCRIPTION("Valid image ownership transfers that shouldn't create errors"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Queue *no_gfx_queue = m_device->NonGraphicsQueue(); |
| if (!no_gfx_queue) { |
| GTEST_SKIP() << "Required queue not present (non-graphics capable required)"; |
| } |
| |
| vkt::CommandPool no_gfx_pool(*m_device, no_gfx_queue->family_index, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); |
| vkt::CommandBuffer no_gfx_cb(*m_device, no_gfx_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY); |
| |
| // Create an "exclusive" image owned by the graphics queue. |
| VkFlags image_use = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_B8G8R8A8_UNORM, image_use); |
| image.SetLayout(VK_IMAGE_LAYOUT_GENERAL); |
| |
| VkImageMemoryBarrier2 image_barrier = vku::InitStructHelper(); |
| image_barrier.srcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; |
| image_barrier.srcAccessMask = 0; |
| image_barrier.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; |
| image_barrier.dstAccessMask = 0; |
| image_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; |
| image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| image_barrier.srcQueueFamilyIndex = m_device->graphics_queue_node_index_; |
| image_barrier.dstQueueFamilyIndex = no_gfx_queue->family_index; |
| image_barrier.image = image; |
| image_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| ValidOwnershipTransfer(m_default_queue, m_command_buffer, no_gfx_queue, no_gfx_cb, nullptr, &image_barrier); |
| |
| // Change layouts while changing ownership |
| image_barrier.srcQueueFamilyIndex = no_gfx_queue->family_index; |
| image_barrier.dstQueueFamilyIndex = m_device->graphics_queue_node_index_; |
| image_barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; |
| image_barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; |
| image_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; |
| // Make sure the new layout is different from the old |
| if (image_barrier.oldLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { |
| image_barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; |
| } else { |
| image_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| } |
| |
| ValidOwnershipTransfer(no_gfx_queue, no_gfx_cb, m_default_queue, m_command_buffer, nullptr, &image_barrier); |
| } |
| |
| TEST_F(PositiveSyncObject, Sync2OwnershipTranfersBuffer) { |
| TEST_DESCRIPTION("Valid buffer ownership transfers that shouldn't create errors"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Queue *no_gfx_queue = m_device->NonGraphicsQueue(); |
| if (!no_gfx_queue) { |
| GTEST_SKIP() << "Required queue not present (non-graphics capable required)"; |
| } |
| |
| vkt::CommandPool no_gfx_pool(*m_device, no_gfx_queue->family_index, VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); |
| vkt::CommandBuffer no_gfx_cb(*m_device, no_gfx_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT); |
| auto buffer_barrier = buffer.BufferMemoryBarrier(VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT, |
| VK_ACCESS_2_NONE, VK_ACCESS_2_NONE, 0, VK_WHOLE_SIZE); |
| |
| // Let gfx own it. |
| buffer_barrier.srcQueueFamilyIndex = m_device->graphics_queue_node_index_; |
| buffer_barrier.dstQueueFamilyIndex = m_device->graphics_queue_node_index_; |
| ValidOwnershipTransferOp(m_default_queue, m_command_buffer, &buffer_barrier, nullptr); |
| |
| // Transfer it to non-gfx |
| buffer_barrier.dstQueueFamilyIndex = no_gfx_queue->family_index; |
| ValidOwnershipTransfer(m_default_queue, m_command_buffer, no_gfx_queue, no_gfx_cb, &buffer_barrier, nullptr); |
| |
| // Transfer it to gfx |
| buffer_barrier.srcQueueFamilyIndex = no_gfx_queue->family_index; |
| buffer_barrier.dstQueueFamilyIndex = m_device->graphics_queue_node_index_; |
| buffer_barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT; |
| buffer_barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_GRAPHICS_BIT; |
| |
| ValidOwnershipTransfer(no_gfx_queue, no_gfx_cb, m_default_queue, m_command_buffer, &buffer_barrier, nullptr); |
| } |
| |
| TEST_F(PositiveSyncObject, BarrierQueueFamily2) { |
| TEST_DESCRIPTION("Create and submit barriers with invalid queue families"); |
| SetTargetApiVersion(VK_API_VERSION_1_0); |
| RETURN_IF_SKIP(Init()); |
| |
| // Find queues of two families |
| const uint32_t submit_family = m_device->graphics_queue_node_index_; |
| const uint32_t queue_family_count = static_cast<uint32_t>(m_device->Physical().queue_properties_.size()); |
| const uint32_t other_family = submit_family != 0 ? 0 : 1; |
| const bool only_one_family = (queue_family_count == 1) || |
| (m_device->Physical().queue_properties_[other_family].queueCount == 0) || |
| ((m_device->Physical().queue_properties_[other_family].queueFlags & VK_QUEUE_TRANSFER_BIT) == 0); |
| |
| if (only_one_family) { |
| GTEST_SKIP() << "Single queue family found"; |
| } |
| std::vector<uint32_t> qf_indices{{submit_family, other_family}}; |
| BarrierQueueFamilyTestHelper::Context test_context(this, qf_indices); |
| |
| BarrierQueueFamilyTestHelper excl_test(&test_context); |
| excl_test.Init(nullptr); |
| |
| // Although other_family does not match submit_family, because the barrier families are |
| // equal here, no ownership transfer actually happens, and this barrier is valid by the spec. |
| excl_test(other_family, other_family, submit_family); |
| |
| // positive test (testing both the index logic and the QFO transfer tracking. |
| excl_test(submit_family, other_family, submit_family); |
| excl_test(submit_family, other_family, other_family); |
| excl_test(other_family, submit_family, other_family); |
| excl_test(other_family, submit_family, submit_family); |
| } |
| |
| TEST_F(PositiveSyncObject, LayoutFromPresentWithoutAccessMemoryRead) { |
| // Transition an image away from PRESENT_SRC_KHR without ACCESS_MEMORY_READ |
| // in srcAccessMask. |
| |
| // The required behavior here was a bit unclear in earlier versions of the |
| // spec, but there is no memory dependency required here, so this should |
| // work without warnings. |
| |
| AddRequiredExtensions(VK_KHR_SWAPCHAIN_EXTENSION_NAME); |
| RETURN_IF_SKIP(Init()); |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_B8G8R8A8_UNORM, |
| (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)); |
| |
| VkImageMemoryBarrier barrier = vku::InitStructHelper(); |
| VkImageSubresourceRange range; |
| barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
| barrier.dstAccessMask = 0; |
| barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
| barrier.image = image; |
| range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; |
| range.baseMipLevel = 0; |
| range.levelCount = 1; |
| range.baseArrayLayer = 0; |
| range.layerCount = 1; |
| barrier.subresourceRange = range; |
| vkt::CommandBuffer cmdbuf(*m_device, m_command_pool); |
| cmdbuf.Begin(); |
| vk::CmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, |
| nullptr, 1, &barrier); |
| barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; |
| barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| barrier.srcAccessMask = 0; |
| barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| vk::CmdPipelineBarrier(cmdbuf, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, |
| nullptr, 1, &barrier); |
| } |
| |
| TEST_F(PositiveSyncObject, QueueSubmitSemaphoresAndLayoutTracking) { |
| TEST_DESCRIPTION("Submit multiple command buffers with chained semaphore signals and layout transitions"); |
| |
| RETURN_IF_SKIP(Init()); |
| VkCommandBuffer cmd_bufs[4]; |
| VkCommandBufferAllocateInfo alloc_info = vku::InitStructHelper(); |
| alloc_info.commandBufferCount = 4; |
| alloc_info.commandPool = m_command_pool; |
| alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; |
| vk::AllocateCommandBuffers(device(), &alloc_info, cmd_bufs); |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_B8G8R8A8_UNORM, |
| (VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT)); |
| image.SetLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); |
| VkCommandBufferBeginInfo cb_binfo = vku::InitStructHelper(); |
| cb_binfo.pInheritanceInfo = VK_NULL_HANDLE; |
| cb_binfo.flags = 0; |
| // Use 4 command buffers, each with an image layout transition, ColorAO->General->ColorAO->TransferSrc->TransferDst |
| vk::BeginCommandBuffer(cmd_bufs[0], &cb_binfo); |
| VkImageMemoryBarrier img_barrier = vku::InitStructHelper(); |
| img_barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; |
| img_barrier.dstAccessMask = VK_ACCESS_HOST_WRITE_BIT; |
| img_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| img_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| img_barrier.image = image; |
| img_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| img_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| vk::CmdPipelineBarrier(cmd_bufs[0], VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 0, nullptr, 1, |
| &img_barrier); |
| vk::EndCommandBuffer(cmd_bufs[0]); |
| vk::BeginCommandBuffer(cmd_bufs[1], &cb_binfo); |
| img_barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; |
| img_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| vk::CmdPipelineBarrier(cmd_bufs[1], VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 0, nullptr, 1, |
| &img_barrier); |
| vk::EndCommandBuffer(cmd_bufs[1]); |
| vk::BeginCommandBuffer(cmd_bufs[2], &cb_binfo); |
| img_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| img_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
| vk::CmdPipelineBarrier(cmd_bufs[2], VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 0, nullptr, 1, |
| &img_barrier); |
| vk::EndCommandBuffer(cmd_bufs[2]); |
| vk::BeginCommandBuffer(cmd_bufs[3], &cb_binfo); |
| img_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
| img_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| vk::CmdPipelineBarrier(cmd_bufs[3], VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 0, nullptr, 1, |
| &img_barrier); |
| vk::EndCommandBuffer(cmd_bufs[3]); |
| |
| // Submit 4 command buffers in 3 submits, with submits 2 and 3 waiting for semaphores from submits 1 and 2 |
| vkt::Semaphore semaphore1(*m_device); |
| vkt::Semaphore semaphore2(*m_device); |
| VkPipelineStageFlags flags[]{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT}; |
| VkSubmitInfo submit_info[3]; |
| submit_info[0] = vku::InitStructHelper(); |
| submit_info[0].commandBufferCount = 1; |
| submit_info[0].pCommandBuffers = &cmd_bufs[0]; |
| submit_info[0].signalSemaphoreCount = 1; |
| submit_info[0].pSignalSemaphores = &semaphore1.handle(); |
| submit_info[0].waitSemaphoreCount = 0; |
| submit_info[0].pWaitDstStageMask = nullptr; |
| submit_info[0].pWaitDstStageMask = flags; |
| submit_info[1] = vku::InitStructHelper(); |
| submit_info[1].commandBufferCount = 1; |
| submit_info[1].pCommandBuffers = &cmd_bufs[1]; |
| submit_info[1].waitSemaphoreCount = 1; |
| submit_info[1].pWaitSemaphores = &semaphore1.handle(); |
| submit_info[1].signalSemaphoreCount = 1; |
| submit_info[1].pSignalSemaphores = &semaphore2.handle(); |
| submit_info[1].pWaitDstStageMask = flags; |
| submit_info[2] = vku::InitStructHelper(); |
| submit_info[2].commandBufferCount = 2; |
| submit_info[2].pCommandBuffers = &cmd_bufs[2]; |
| submit_info[2].waitSemaphoreCount = 1; |
| submit_info[2].pWaitSemaphores = &semaphore2.handle(); |
| submit_info[2].signalSemaphoreCount = 0; |
| submit_info[2].pSignalSemaphores = nullptr; |
| submit_info[2].pWaitDstStageMask = flags; |
| vk::QueueSubmit(m_default_queue->handle(), 3, submit_info, VK_NULL_HANDLE); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, ResetUnsignaledFence) { |
| RETURN_IF_SKIP(Init()); |
| vkt::Fence testFence(*m_device); |
| VkFence fences[1] = {testFence}; |
| VkResult result = vk::ResetFences(device(), 1, fences); |
| ASSERT_EQ(VK_SUCCESS, result); |
| } |
| |
| TEST_F(PositiveSyncObject, FenceCreateSignaledWaitHandling) { |
| RETURN_IF_SKIP(Init()); |
| |
| // A fence created signaled |
| VkFenceCreateInfo fci = vku::InitStructHelper(); |
| fci.flags = VK_FENCE_CREATE_SIGNALED_BIT; |
| vkt::Fence f1(*m_device, fci); |
| |
| // A fence created not |
| fci.flags = 0; |
| vkt::Fence f2(*m_device, fci); |
| |
| // Submit the unsignaled fence |
| m_default_queue->Submit(vkt::no_cmd, f2); |
| |
| // Wait on both fences, with signaled first. |
| VkFence fences[] = {f1, f2}; |
| vk::WaitForFences(device(), 2, fences, VK_TRUE, kWaitTimeout); |
| // Should have both retired! (get destroyed now) |
| } |
| |
| TEST_F(PositiveSyncObject, TwoFencesThreeFrames) { |
| TEST_DESCRIPTION( |
| "Two command buffers with two separate fences are each run through a Submit & WaitForFences cycle 3 times. This previously " |
| "revealed a bug so running this positive test to prevent a regression."); |
| |
| RETURN_IF_SKIP(Init()); |
| |
| constexpr uint32_t NUM_OBJECTS = 2; |
| constexpr uint32_t NUM_FRAMES = 3; |
| vkt::CommandBuffer cmd_buffers[NUM_OBJECTS]; |
| vkt::Fence fences[NUM_OBJECTS]; |
| |
| for (uint32_t i = 0; i < NUM_OBJECTS; ++i) { |
| cmd_buffers[i].Init(*m_device, m_command_pool); |
| fences[i].Init(*m_device); |
| } |
| for (uint32_t frame = 0; frame < NUM_FRAMES; ++frame) { |
| for (uint32_t obj = 0; obj < NUM_OBJECTS; ++obj) { |
| // Create empty cmd buffer |
| VkCommandBufferBeginInfo cmdBufBeginDesc = vku::InitStructHelper(); |
| vk::BeginCommandBuffer(cmd_buffers[obj], &cmdBufBeginDesc); |
| vk::EndCommandBuffer(cmd_buffers[obj]); |
| |
| // Submit cmd buffer and wait for fence |
| m_default_queue->Submit(cmd_buffers[obj], fences[obj]); |
| vk::WaitForFences(device(), 1, &fences[obj].handle(), VK_TRUE, kWaitTimeout); |
| vk::ResetFences(device(), 1, &fences[obj].handle()); |
| } |
| } |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsSeparateQueuesWithSemaphoreAndOneFenceQWI) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call submitted on separate queues followed by a QueueWaitIdle."); |
| |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| if ((m_second_queue_caps & VK_QUEUE_GRAPHICS_BIT) == 0) { |
| GTEST_SKIP() << "2 graphics queues are needed"; |
| } |
| |
| vkt::Semaphore semaphore(*m_device); |
| vkt::CommandPool pool0(*m_device, m_second_queue->family_index); |
| vkt::CommandBuffer cb0(*m_device, pool0); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_second_queue->Submit(cb0, vkt::Signal(semaphore)); |
| m_default_queue->Submit(cb1, vkt::Wait(semaphore)); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsSeparateQueuesWithSemaphoreAndOneFenceQWIFence) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call submitted on separate queues, the second having a fence followed " |
| "by a QueueWaitIdle."); |
| |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| if ((m_second_queue_caps & VK_QUEUE_GRAPHICS_BIT) == 0) { |
| GTEST_SKIP() << "2 graphics queues are needed"; |
| } |
| |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore semaphore(*m_device); |
| vkt::CommandPool pool0(*m_device, m_second_queue->family_index); |
| vkt::CommandBuffer cb0(*m_device, pool0); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_second_queue->Submit(cb0, vkt::Signal(semaphore)); |
| m_default_queue->Submit(cb1, vkt::Wait(semaphore), fence); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsSeparateQueuesWithSemaphoreAndOneFenceTwoWFF) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call submitted on separate queues, the second having a fence followed " |
| "by two consecutive WaitForFences calls on the same fence."); |
| |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| if ((m_second_queue_caps & VK_QUEUE_GRAPHICS_BIT) == 0) { |
| GTEST_SKIP() << "2 graphics queues are needed"; |
| } |
| |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore semaphore(*m_device); |
| vkt::CommandPool pool0(*m_device, m_second_queue->family_index); |
| vkt::CommandBuffer cb0(*m_device, pool0); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_second_queue->Submit(cb0, vkt::Signal(semaphore)); |
| m_default_queue->Submit(cb1, vkt::Wait(semaphore), fence); |
| |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueuesEnsureCorrectRetirementWithWorkStolen) { |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| if (!m_second_queue) { |
| GTEST_SKIP() << "Test requires two queues"; |
| } |
| |
| // An (empty) command buffer. We must have work in the first submission -- |
| // the layer treats unfenced work differently from fenced work. |
| m_command_buffer.Begin(); |
| m_command_buffer.End(); |
| |
| vkt::Semaphore s(*m_device); |
| m_default_queue->Submit(m_command_buffer, vkt::Signal(s)); |
| m_second_queue->Submit(vkt::no_cmd, vkt::Wait(s)); |
| |
| m_default_queue->Wait(); |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsSeparateQueuesWithSemaphoreAndOneFence) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call submitted on separate queues, the second having a fence, " |
| "followed by a WaitForFences call."); |
| |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| if ((m_second_queue_caps & VK_QUEUE_GRAPHICS_BIT) == 0) { |
| GTEST_SKIP() << "2 graphics queues are needed"; |
| } |
| |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore semaphore(*m_device); |
| vkt::CommandPool pool0(*m_device, m_second_queue->family_index); |
| vkt::CommandBuffer cb0(*m_device, pool0); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_second_queue->Submit(cb0, vkt::Signal(semaphore)); |
| m_default_queue->Submit(cb1, vkt::Wait(semaphore), fence); |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsSeparateQueuesWithTimelineSemaphoreAndOneFence) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call submitted on separate queues, ordered by a timeline semaphore," |
| " the second having a fence, followed by a WaitForFences call."); |
| |
| AddRequiredExtensions(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| if ((m_second_queue_caps & VK_QUEUE_GRAPHICS_BIT) == 0) { |
| GTEST_SKIP() << "2 graphics queues are needed"; |
| } |
| |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| vkt::CommandPool pool0(*m_device, m_second_queue->family_index); |
| vkt::CommandBuffer cb0(*m_device, pool0); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_second_queue->Submit(cb0, vkt::TimelineSignal(semaphore, 1)); |
| m_default_queue->Submit(cb1, vkt::TimelineWait(semaphore, 1), fence); |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsOneQueueWithSemaphoreAndOneFence) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call on the same queue, sharing a signal/wait semaphore, the second " |
| "having a fence, followed by a WaitForFences call."); |
| |
| RETURN_IF_SKIP(Init()); |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore semaphore(*m_device); |
| vkt::CommandBuffer cb0(*m_device, m_command_pool); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_default_queue->Submit(cb0, vkt::Signal(semaphore)); |
| m_default_queue->Submit(cb1, vkt::Wait(semaphore), fence); |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsOneQueueNullQueueSubmitWithFence) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call on the same queue, no fences, followed by a third QueueSubmit " |
| "with NO SubmitInfos but with a fence, followed by a WaitForFences call."); |
| |
| RETURN_IF_SKIP(Init()); |
| vkt::Fence fence(*m_device); |
| vkt::CommandBuffer cb0(*m_device, m_command_pool); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_default_queue->Submit(cb0); |
| m_default_queue->Submit(cb1); |
| m_default_queue->Submit(vkt::no_cmd, fence); |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoQueueSubmitsOneQueueOneFence) { |
| TEST_DESCRIPTION( |
| "Two command buffers, each in a separate QueueSubmit call on the same queue, the second having a fence, followed by a " |
| "WaitForFences call."); |
| |
| RETURN_IF_SKIP(Init()); |
| vkt::Fence fence(*m_device); |
| vkt::CommandBuffer cb0(*m_device, m_command_pool); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| m_default_queue->Submit(cb0); |
| m_default_queue->Submit(cb1, fence); |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TwoSubmitInfosWithSemaphoreOneQueueSubmitsOneFence) { |
| TEST_DESCRIPTION( |
| "Two command buffers each in a separate SubmitInfo sent in a single QueueSubmit call followed by a WaitForFences call."); |
| |
| RETURN_IF_SKIP(Init()); |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore semaphore(*m_device); |
| vkt::CommandBuffer cb0(*m_device, m_command_pool); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| |
| cb0.Begin(); |
| vk::CmdPipelineBarrier(cb0, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, nullptr, |
| 0, nullptr); |
| vk::CmdSetViewport(cb0, 0, 1, &viewport); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdSetViewport(cb1, 0, 1, &viewport); |
| cb1.End(); |
| |
| VkSubmitInfo submit_info[2]; |
| VkPipelineStageFlags flags[]{VK_PIPELINE_STAGE_ALL_COMMANDS_BIT}; |
| |
| submit_info[0] = vku::InitStructHelper(); |
| submit_info[0].commandBufferCount = 1; |
| submit_info[0].pCommandBuffers = &cb0.handle(); |
| submit_info[0].signalSemaphoreCount = 1; |
| submit_info[0].pSignalSemaphores = &semaphore.handle(); |
| |
| submit_info[1] = vku::InitStructHelper(); |
| submit_info[1].commandBufferCount = 1; |
| submit_info[1].pCommandBuffers = &cb1.handle(); |
| submit_info[1].waitSemaphoreCount = 1; |
| submit_info[1].pWaitSemaphores = &semaphore.handle(); |
| submit_info[1].pWaitDstStageMask = flags; |
| vk::QueueSubmit(m_default_queue->handle(), 2, &submit_info[0], fence); |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, WaitBeforeSignalOnDifferentQueuesSignalLargerThanWait) { |
| TEST_DESCRIPTION("Wait before signal on different queues, signal value is larger than the wait value"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (!m_second_queue) { |
| GTEST_SKIP() << "Two queues are needed"; |
| } |
| vkt::CommandPool second_pool(*m_device, m_second_queue->family_index); |
| vkt::CommandBuffer second_cb(*m_device, second_pool); |
| |
| const VkBufferUsageFlags buffer_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
| vkt::Buffer buffer_a(*m_device, 256, buffer_usage); |
| vkt::Buffer buffer_b(*m_device, 256, buffer_usage); |
| VkBufferCopy region = {0, 0, 256}; |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| |
| // Wait for value 1 (or greater) |
| m_command_buffer.Begin(); |
| vk::CmdCopyBuffer(m_command_buffer, buffer_a, buffer_b, 1, ®ion); |
| m_command_buffer.End(); |
| m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1, VK_PIPELINE_STAGE_2_COPY_BIT)); |
| |
| // Signal value 2 |
| second_cb.Begin(); |
| vk::CmdCopyBuffer(second_cb, buffer_a, buffer_b, 1, ®ion); |
| second_cb.End(); |
| m_second_queue->Submit2(second_cb, vkt::TimelineSignal(semaphore, 2, VK_PIPELINE_STAGE_2_COPY_BIT)); |
| |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, LongSemaphoreChain) { |
| RETURN_IF_SKIP(Init()); |
| std::vector<VkSemaphore> semaphores; |
| |
| const int chainLength = 32768; |
| VkPipelineStageFlags flags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; |
| |
| for (int i = 0; i < chainLength; i++) { |
| VkSemaphoreCreateInfo sci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0}; |
| VkSemaphore semaphore; |
| vk::CreateSemaphore(device(), &sci, nullptr, &semaphore); |
| |
| semaphores.push_back(semaphore); |
| |
| VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, |
| nullptr, |
| semaphores.size() > 1 ? 1u : 0u, |
| semaphores.size() > 1 ? &semaphores[semaphores.size() - 2] : nullptr, |
| &flags, |
| 0, |
| nullptr, |
| 1, |
| &semaphores[semaphores.size() - 1]}; |
| vk::QueueSubmit(m_default_queue->handle(), 1, &si, VK_NULL_HANDLE); |
| } |
| |
| vkt::Fence fence(*m_device); |
| VkSubmitInfo si = {VK_STRUCTURE_TYPE_SUBMIT_INFO, nullptr, 1, &semaphores.back(), &flags, 0, nullptr, 0, nullptr}; |
| vk::QueueSubmit(m_default_queue->handle(), 1, &si, fence); |
| |
| vk::WaitForFences(device(), 1, &fence.handle(), VK_TRUE, kWaitTimeout); |
| |
| for (auto semaphore : semaphores) vk::DestroySemaphore(device(), semaphore, nullptr); |
| } |
| |
| TEST_F(PositiveSyncObject, ExternalSemaphore) { |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_KMT_BIT; |
| #else |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; |
| #endif |
| |
| AddRequiredExtensions(extension_name); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME); |
| |
| RETURN_IF_SKIP(Init()); |
| |
| // Check for external semaphore import and export capability |
| VkPhysicalDeviceExternalSemaphoreInfo esi = vku::InitStructHelper(); |
| esi.handleType = handle_type; |
| VkExternalSemaphorePropertiesKHR esp = vku::InitStructHelper(); |
| vk::GetPhysicalDeviceExternalSemaphorePropertiesKHR(Gpu(), &esi, &esp); |
| |
| if (!(esp.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) || |
| !(esp.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT)) { |
| GTEST_SKIP() << "External semaphore does not support importing and exporting"; |
| } |
| |
| // Create a semaphore to export payload from |
| VkExportSemaphoreCreateInfo esci = vku::InitStructHelper(); |
| esci.handleTypes = handle_type; |
| VkSemaphoreCreateInfo sci = vku::InitStructHelper(&esci); |
| |
| vkt::Semaphore export_semaphore(*m_device, sci); |
| |
| // Create a semaphore to import payload into |
| sci.pNext = nullptr; |
| vkt::Semaphore import_semaphore(*m_device, sci); |
| |
| ExternalHandle ext_handle{}; |
| export_semaphore.ExportHandle(ext_handle, handle_type); |
| import_semaphore.ImportHandle(ext_handle, handle_type); |
| |
| // Signal the exported semaphore and wait on the imported semaphore |
| const VkPipelineStageFlags flags = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| std::vector<VkSubmitInfo> si(4, vku::InitStruct<VkSubmitInfo>()); |
| si[0].signalSemaphoreCount = 1; |
| si[0].pSignalSemaphores = &export_semaphore.handle(); |
| si[1].pWaitDstStageMask = &flags; |
| si[1].waitSemaphoreCount = 1; |
| si[1].pWaitSemaphores = &import_semaphore.handle(); |
| si[2] = si[0]; |
| si[3] = si[1]; |
| |
| vk::QueueSubmit(m_default_queue->handle(), si.size(), si.data(), VK_NULL_HANDLE); |
| |
| if (m_device->Physical().Features().sparseBinding) { |
| // Signal the imported semaphore and wait on the exported semaphore |
| std::vector<VkBindSparseInfo> bi(4, vku::InitStruct<VkBindSparseInfo>()); |
| bi[0].signalSemaphoreCount = 1; |
| bi[0].pSignalSemaphores = &export_semaphore.handle(); |
| bi[1].waitSemaphoreCount = 1; |
| bi[1].pWaitSemaphores = &import_semaphore.handle(); |
| bi[2] = bi[0]; |
| bi[3] = bi[1]; |
| vk::QueueBindSparse(m_device->QueuesWithSparseCapability()[0]->handle(), bi.size(), bi.data(), VK_NULL_HANDLE); |
| } |
| |
| // Cleanup |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, ExternalTimelineSemaphore) { |
| TEST_DESCRIPTION( |
| "Export and import a timeline semaphore. " |
| "Should be roughly equivalant to the CTS *cross_instance*timeline_semaphore* tests"); |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| #else |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; |
| #endif |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_CAPABILITIES_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); |
| AddRequiredExtensions(extension_name); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD"; |
| } |
| |
| // Check for external semaphore import and export capability |
| VkSemaphoreTypeCreateInfo tci = vku::InitStructHelper(); |
| tci.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; |
| |
| VkPhysicalDeviceExternalSemaphoreInfo esi = vku::InitStructHelper(&tci); |
| esi.handleType = handle_type; |
| |
| VkExternalSemaphorePropertiesKHR esp = vku::InitStructHelper(); |
| |
| vk::GetPhysicalDeviceExternalSemaphorePropertiesKHR(Gpu(), &esi, &esp); |
| |
| if (!(esp.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) || |
| !(esp.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT)) { |
| GTEST_SKIP() << "External semaphore does not support importing and exporting, skipping test"; |
| } |
| |
| // Create a semaphore to export payload from |
| VkExportSemaphoreCreateInfo esci = vku::InitStructHelper(); |
| esci.handleTypes = handle_type; |
| VkSemaphoreTypeCreateInfo stci = vku::InitStructHelper(&esci); |
| stci.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; |
| VkSemaphoreCreateInfo sci = vku::InitStructHelper(&stci); |
| |
| vkt::Semaphore export_semaphore(*m_device, sci); |
| |
| // Create a semaphore to import payload into |
| stci.pNext = nullptr; |
| vkt::Semaphore import_semaphore(*m_device, sci); |
| |
| ExternalHandle ext_handle{}; |
| export_semaphore.ExportHandle(ext_handle, handle_type); |
| import_semaphore.ImportHandle(ext_handle, handle_type); |
| |
| uint64_t wait_value = 1; |
| uint64_t signal_value = 12345; |
| |
| // Signal the exported semaphore and wait on the imported semaphore |
| VkPipelineStageFlags flags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; |
| std::vector<VkTimelineSemaphoreSubmitInfo> ti(2, vku::InitStruct<VkTimelineSemaphoreSubmitInfo>()); |
| std::vector<VkSubmitInfo> si(2, vku::InitStruct<VkSubmitInfo>()); |
| |
| si[0].pWaitDstStageMask = &flags; |
| si[0].signalSemaphoreCount = 1; |
| si[0].pSignalSemaphores = &export_semaphore.handle(); |
| si[0].pNext = &ti[0]; |
| ti[0].signalSemaphoreValueCount = 1; |
| ti[0].pSignalSemaphoreValues = &signal_value; |
| si[1].waitSemaphoreCount = 1; |
| si[1].pWaitSemaphores = &import_semaphore.handle(); |
| si[1].pWaitDstStageMask = &flags; |
| si[1].pNext = &ti[1]; |
| ti[1].waitSemaphoreValueCount = 1; |
| ti[1].pWaitSemaphoreValues = &wait_value; |
| |
| vk::QueueSubmit(m_default_queue->handle(), si.size(), si.data(), VK_NULL_HANDLE); |
| |
| m_default_queue->Wait(); |
| |
| uint64_t import_value{0}, export_value{0}; |
| |
| vk::GetSemaphoreCounterValueKHR(*m_device, export_semaphore, &export_value); |
| ASSERT_EQ(export_value, signal_value); |
| |
| vk::GetSemaphoreCounterValueKHR(*m_device, import_semaphore, &import_value); |
| ASSERT_EQ(import_value, signal_value); |
| } |
| |
| TEST_F(PositiveSyncObject, ExternalFence) { |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| const auto extension_name = VK_KHR_EXTERNAL_FENCE_WIN32_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| #else |
| const auto extension_name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_FENCE_HANDLE_TYPE_OPAQUE_FD_BIT; |
| #endif |
| AddRequiredExtensions(VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME); |
| AddRequiredExtensions(extension_name); |
| RETURN_IF_SKIP(Init()); |
| |
| // Check for external fence import and export capability |
| VkPhysicalDeviceExternalFenceInfo efi = vku::InitStructHelper(); |
| efi.handleType = handle_type; |
| VkExternalFencePropertiesKHR efp = vku::InitStructHelper(); |
| vk::GetPhysicalDeviceExternalFencePropertiesKHR(Gpu(), &efi, &efp); |
| |
| if (!(efp.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT) || |
| !(efp.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT)) { |
| GTEST_SKIP() << "External fence does not support importing and exporting, skipping test."; |
| } |
| |
| // Create a fence to export payload from |
| VkExportFenceCreateInfo efci = vku::InitStructHelper(); |
| efci.handleTypes = handle_type; |
| VkFenceCreateInfo fci = vku::InitStructHelper(&efci); |
| vkt::Fence export_fence(*m_device, fci); |
| |
| // Create a fence to import payload into |
| fci.pNext = nullptr; |
| vkt::Fence import_fence(*m_device, fci); |
| |
| // Export fence payload to an opaque handle |
| ExternalHandle ext_fence{}; |
| export_fence.ExportHandle(ext_fence, handle_type); |
| import_fence.ImportHandle(ext_fence, handle_type); |
| |
| // Signal the exported fence and wait on the imported fence |
| vk::QueueSubmit(m_default_queue->handle(), 0, nullptr, export_fence); |
| vk::WaitForFences(device(), 1, &import_fence.handle(), VK_TRUE, 1000000000); |
| vk::ResetFences(device(), 1, &import_fence.handle()); |
| vk::QueueSubmit(m_default_queue->handle(), 0, nullptr, export_fence); |
| vk::WaitForFences(device(), 1, &import_fence.handle(), VK_TRUE, 1000000000); |
| vk::ResetFences(device(), 1, &import_fence.handle()); |
| |
| // Signal the imported fence and wait on the exported fence |
| vk::QueueSubmit(m_default_queue->handle(), 0, nullptr, import_fence); |
| vk::WaitForFences(device(), 1, &export_fence.handle(), VK_TRUE, 1000000000); |
| vk::ResetFences(device(), 1, &export_fence.handle()); |
| vk::QueueSubmit(m_default_queue->handle(), 0, nullptr, import_fence); |
| vk::WaitForFences(device(), 1, &export_fence.handle(), VK_TRUE, 1000000000); |
| vk::ResetFences(device(), 1, &export_fence.handle()); |
| |
| // Cleanup |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, ExternalFenceSyncFdLoop) { |
| const auto extension_name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT; |
| AddRequiredExtensions(VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME); |
| AddRequiredExtensions(extension_name); |
| RETURN_IF_SKIP(Init()); |
| |
| // Check for external fence import and export capability |
| VkPhysicalDeviceExternalFenceInfo efi = vku::InitStructHelper(); |
| efi.handleType = handle_type; |
| VkExternalFencePropertiesKHR efp = vku::InitStructHelper(); |
| vk::GetPhysicalDeviceExternalFencePropertiesKHR(Gpu(), &efi, &efp); |
| |
| if (!(efp.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT) || |
| !(efp.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT)) { |
| GTEST_SKIP() << "External fence does not support importing and exporting, skipping test."; |
| return; |
| } |
| |
| // Create a fence to export payload from |
| VkExportFenceCreateInfo efci = vku::InitStructHelper(); |
| efci.handleTypes = handle_type; |
| VkFenceCreateInfo fci = vku::InitStructHelper(&efci); |
| vkt::Fence export_fence(*m_device, fci); |
| |
| fci.pNext = nullptr; |
| fci.flags = VK_FENCE_CREATE_SIGNALED_BIT; |
| std::array<vkt::Fence, 2> fences; |
| fences[0].Init(*m_device, fci); |
| fences[1].Init(*m_device, fci); |
| |
| for (uint32_t i = 0; i < 1000; i++) { |
| auto submitter = i & 1; |
| auto waiter = (~i) & 1; |
| fences[submitter].Reset(); |
| vk::QueueSubmit(m_default_queue->handle(), 0, nullptr, fences[submitter].handle()); |
| |
| fences[waiter].Wait(kWaitTimeout); |
| |
| vk::QueueSubmit(m_default_queue->handle(), 0, nullptr, export_fence); |
| int fd_handle = -1; |
| export_fence.ExportHandle(fd_handle, handle_type); |
| #ifndef VK_USE_PLATFORM_WIN32_KHR |
| close(fd_handle); |
| #endif |
| } |
| |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, ExternalFenceSubmitCmdBuffer) { |
| const auto extension_name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT; |
| AddRequiredExtensions(VK_KHR_EXTERNAL_FENCE_CAPABILITIES_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_FENCE_EXTENSION_NAME); |
| AddRequiredExtensions(extension_name); |
| RETURN_IF_SKIP(Init()); |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD"; |
| } |
| |
| // Check for external fence export capability |
| VkPhysicalDeviceExternalFenceInfo efi = vku::InitStructHelper(); |
| efi.handleType = handle_type; |
| VkExternalFencePropertiesKHR efp = vku::InitStructHelper(); |
| vk::GetPhysicalDeviceExternalFencePropertiesKHR(Gpu(), &efi, &efp); |
| |
| if (!(efp.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_EXPORTABLE_BIT)) { |
| GTEST_SKIP() << "External fence does not support exporting, skipping test."; |
| return; |
| } |
| |
| // Create a fence to export payload from |
| VkExportFenceCreateInfo efci = vku::InitStructHelper(); |
| efci.handleTypes = handle_type; |
| VkFenceCreateInfo fci = vku::InitStructHelper(&efci); |
| vkt::Fence export_fence(*m_device, fci); |
| |
| for (uint32_t i = 0; i < 1000; i++) { |
| m_command_buffer.Begin(); |
| m_command_buffer.End(); |
| |
| VkSubmitInfo submit_info = vku::InitStructHelper(); |
| submit_info.commandBufferCount = 1; |
| submit_info.pCommandBuffers = &m_command_buffer.handle(); |
| vk::QueueSubmit(m_default_queue->handle(), 1, &submit_info, export_fence); |
| |
| int fd_handle = -1; |
| export_fence.ExportHandle(fd_handle, handle_type); |
| |
| #ifndef VK_USE_PLATFORM_WIN32_KHR |
| // Wait until command buffer is finished using the exported handle. |
| if (fd_handle != -1) { |
| struct pollfd fds; |
| fds.fd = fd_handle; |
| fds.events = POLLIN; |
| int timeout_ms = static_cast<int>(kWaitTimeout / 1000000); |
| while (true) { |
| int ret = poll(&fds, 1, timeout_ms); |
| if (ret > 0) { |
| ASSERT_FALSE(fds.revents & (POLLERR | POLLNVAL)); |
| break; |
| } |
| ASSERT_FALSE(ret == 0); // Timeout. |
| ASSERT_TRUE(ret == -1 && (errno == EINTR || errno == EAGAIN)); // Retry... |
| } |
| close(fd_handle); |
| } |
| #else |
| // On Windows this test works with MockICD driver and it's fine not to close fd_handle, |
| // because it's a dummy value. In case we get access to a real POSIX environment on |
| // Windows and VK_KHR_external_fence_fd will be provided through regular graphics drivers, |
| // then we need to do a proper POSIX clean-up sequence as shown above. |
| m_default_queue->Wait(); |
| #endif |
| |
| m_command_buffer.Reset(); |
| } |
| |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, BasicSetAndWaitEvent) { |
| TEST_DESCRIPTION("Sets event and then wait for it using CmdSetEvent/CmdWaitEvents"); |
| RETURN_IF_SKIP(Init()); |
| |
| const vkt::Event event(*m_device); |
| |
| // Record time validation |
| m_command_buffer.Begin(); |
| vk::CmdSetEvent(m_command_buffer, event, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT); |
| vk::CmdWaitEvents(m_command_buffer, 1, &event.handle(), VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, |
| 0, nullptr, 0, nullptr, 0, nullptr); |
| m_command_buffer.End(); |
| |
| // Also submit to the queue to test submit time validation |
| m_default_queue->Submit(m_command_buffer); |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, BasicSetAndWaitEvent2) { |
| TEST_DESCRIPTION("Sets event and then wait for it using CmdSetEvent2/CmdWaitEvents2"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| VkMemoryBarrier2 barrier = vku::InitStructHelper(); |
| barrier.srcAccessMask = 0; |
| barrier.dstAccessMask = 0; |
| barrier.srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| barrier.dstStageMask = VK_PIPELINE_STAGE_NONE; |
| |
| VkDependencyInfo dependency_info = vku::InitStructHelper(); |
| dependency_info.memoryBarrierCount = 1; |
| dependency_info.pMemoryBarriers = &barrier; |
| |
| const vkt::Event event(*m_device); |
| |
| // Record time validation |
| m_command_buffer.Begin(); |
| vk::CmdSetEvent2(m_command_buffer, event, &dependency_info); |
| vk::CmdWaitEvents2(m_command_buffer, 1, &event.handle(), &dependency_info); |
| m_command_buffer.End(); |
| |
| // Also submit to the queue to test submit time validation |
| m_default_queue->Submit(m_command_buffer); |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, WaitEvent2HostStage) { |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| vkt::Event event(*m_device); |
| VkEvent event_handle = event; |
| |
| VkMemoryBarrier2 barrier = vku::InitStructHelper(); |
| barrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; |
| barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT; |
| barrier.srcStageMask = VK_PIPELINE_STAGE_2_HOST_BIT; // Ok to use if outside the renderpass |
| barrier.dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT; |
| VkDependencyInfo dependency_info = vku::InitStructHelper(); |
| dependency_info.memoryBarrierCount = 1; |
| dependency_info.pMemoryBarriers = &barrier; |
| |
| m_command_buffer.Begin(); |
| vk::CmdWaitEvents2KHR(m_command_buffer, 1, &event_handle, &dependency_info); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, DoubleLayoutTransition) { |
| TEST_DESCRIPTION("Attempt vkCmdPipelineBarrier with 2 layout transitions of the same image."); |
| |
| RETURN_IF_SKIP(Init()); |
| |
| VkImageCreateInfo image_create_info = vku::InitStructHelper(); |
| image_create_info.imageType = VK_IMAGE_TYPE_2D; |
| image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM; |
| image_create_info.extent = {32, 1, 1}; |
| image_create_info.mipLevels = 1; |
| image_create_info.arrayLayers = 1; |
| image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; |
| image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; |
| image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; |
| |
| VkImageSubresourceRange image_sub_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| vkt::Image image(*m_device, image_create_info, vkt::set_layout); |
| |
| m_command_buffer.Begin(); |
| |
| { |
| VkImageMemoryBarrier image_barriers[] = {image.ImageMemoryBarrier( |
| 0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image_sub_range)}; |
| |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, |
| nullptr, 0, nullptr, 1, image_barriers); |
| } |
| |
| // TODO: is it allowed to transition the same image twice within a single barrier command? |
| // Is it undefined behavior? Write a comment and provide references to the spec if that's allowed. |
| { |
| VkImageMemoryBarrier image_barriers[] = { |
| image.ImageMemoryBarrier(VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, |
| VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL, image_sub_range), |
| image.ImageMemoryBarrier(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, |
| VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, image_sub_range)}; |
| |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, |
| nullptr, 0, nullptr, 2, image_barriers); |
| } |
| |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, QueueSubmitTimelineSemaphore2Queue) { |
| TEST_DESCRIPTION("Signal a timeline semaphore on 2 queues."); |
| AddRequiredExtensions(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (!m_second_queue) { |
| GTEST_SKIP() << "Test requires 2 queues"; |
| } |
| |
| VkMemoryPropertyFlags mem_prop = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; |
| VkBufferUsageFlags transfer_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; |
| vkt::Buffer buffer_a(*m_device, 256, transfer_usage, mem_prop); |
| vkt::Buffer buffer_b(*m_device, 256, transfer_usage, mem_prop); |
| vkt::Buffer buffer_c(*m_device, 256, transfer_usage, mem_prop); |
| VkBufferCopy region = {0, 0, 256}; |
| |
| vkt::CommandPool pool0(*m_device, m_default_queue->family_index); |
| vkt::CommandBuffer cb0(*m_device, pool0); |
| cb0.Begin(); |
| vk::CmdCopyBuffer(cb0, buffer_a, buffer_b, 1, ®ion); |
| cb0.End(); |
| |
| vkt::CommandPool pool1(*m_device, m_second_queue->family_index); |
| vkt::CommandBuffer cb1(*m_device, pool1); |
| cb1.Begin(); |
| vk::CmdCopyBuffer(cb1, buffer_c, buffer_b, 1, ®ion); |
| cb1.End(); |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| |
| // timeline values, Begins will be signaled by host, Ends by the queues |
| constexpr uint64_t kQ0Begin = 1; |
| constexpr uint64_t kQ0End = 2; |
| constexpr uint64_t kQ1Begin = 3; |
| constexpr uint64_t kQ1End = 4; |
| |
| m_default_queue->Submit(cb0, vkt::TimelineWait(semaphore, kQ0Begin), vkt::TimelineSignal(semaphore, kQ0End)); |
| m_second_queue->Submit(cb1, vkt::TimelineWait(semaphore, kQ1Begin), vkt::TimelineSignal(semaphore, kQ1End)); |
| |
| semaphore.SignalKHR(kQ0Begin); // initiate forward progress on q0 |
| semaphore.WaitKHR(kQ0End, kWaitTimeout); |
| buffer_a.Destroy(); // buffer_a is only used by the q0 commands |
| |
| semaphore.SignalKHR(kQ1Begin); // initiate forward progress on q1 |
| semaphore.WaitKHR(kQ1End, kWaitTimeout); |
| buffer_b.Destroy(); // buffer_b is used by both q0 and q1 |
| buffer_c.Destroy(); // buffer_c is used by q1 |
| |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, ResetQueryPoolFromDifferentCBWithFenceAfter) { |
| TEST_DESCRIPTION("Reset query pool from a different command buffer and wait on fence after both are submitted"); |
| |
| RETURN_IF_SKIP(Init()); |
| |
| if (m_device->Physical().queue_properties_[m_device->graphics_queue_node_index_].timestampValidBits == 0) { |
| GTEST_SKIP() << "Device graphic queue has timestampValidBits of 0, skipping.\n"; |
| } |
| |
| vkt::CommandBuffer cb0(*m_device, m_command_pool); |
| vkt::CommandBuffer cb1(*m_device, m_command_pool); |
| |
| VkFenceCreateInfo fence_info = vku::InitStructHelper(); |
| fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT; |
| vkt::Fence ts_fence(*m_device, fence_info); |
| |
| vkt::QueryPool query_pool(*m_device, VK_QUERY_TYPE_TIMESTAMP, 1); |
| |
| cb0.Begin(VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT); |
| vk::CmdResetQueryPool(cb0, query_pool, 0, 1); |
| cb0.End(); |
| |
| cb1.Begin(); |
| vk::CmdWriteTimestamp(cb1, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, query_pool, 0); |
| cb1.End(); |
| |
| // Begin by resetting the query pool. |
| m_default_queue->Submit(cb0); |
| |
| // Write a timestamp, and add a fence to be signalled. |
| vk::ResetFences(device(), 1, &ts_fence.handle()); |
| m_default_queue->Submit(cb1, ts_fence); |
| |
| // Reset query pool again. |
| m_default_queue->Submit(cb0); |
| |
| // Finally, write a second timestamp, but before that, wait for the fence. |
| vk::WaitForFences(device(), 1, &ts_fence.handle(), true, kWaitTimeout); |
| m_default_queue->Submit(cb1); |
| |
| m_default_queue->Wait(); |
| } |
| |
| struct FenceSemRaceData { |
| VkDevice device{VK_NULL_HANDLE}; |
| VkSemaphore sem{VK_NULL_HANDLE}; |
| std::atomic<bool> *bailout{nullptr}; |
| uint64_t wait_value{0}; |
| uint64_t timeout{kWaitTimeout}; |
| uint32_t iterations{100000}; |
| }; |
| |
| void WaitTimelineSem(FenceSemRaceData *data) { |
| uint64_t wait_value = data->wait_value; |
| VkSemaphoreWaitInfo wait_info = vku::InitStructHelper(); |
| wait_info.semaphoreCount = 1; |
| wait_info.pSemaphores = &data->sem; |
| wait_info.pValues = &wait_value; |
| |
| for (uint32_t i = 0; i < data->iterations; i++, wait_value++) { |
| vk::WaitSemaphoresKHR(data->device, &wait_info, data->timeout); |
| if (*data->bailout) { |
| break; |
| } |
| } |
| } |
| |
| TEST_F(PositiveSyncObject, FenceSemThreadRace) { |
| AddRequiredExtensions(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD (WaitSemaphores)"; |
| } |
| |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore sem(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| uint64_t signal_value = 1; |
| |
| std::atomic<bool> bailout{false}; |
| FenceSemRaceData data; |
| data.device = device(); |
| data.sem = sem; |
| data.wait_value = signal_value; |
| data.bailout = &bailout; |
| std::thread thread(WaitTimelineSem, &data); |
| |
| m_errorMonitor->SetBailout(&bailout); |
| |
| for (uint32_t i = 0; i < data.iterations; i++, signal_value++) { |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(sem, signal_value), fence); |
| fence.Wait(data.timeout); |
| vk::ResetFences(device(), 1, &fence.handle()); |
| } |
| m_errorMonitor->SetBailout(nullptr); |
| thread.join(); |
| } |
| |
| TEST_F(PositiveSyncObject, SubmitFenceButWaitIdle) { |
| TEST_DESCRIPTION("Submit a CB and Fence but synchronize with vkQueueWaitIdle() (Issue 2756)"); |
| AddSurfaceExtension(); |
| AddRequiredExtensions(VK_KHR_SWAPCHAIN_EXTENSION_NAME); |
| RETURN_IF_SKIP(Init()); |
| RETURN_IF_SKIP(InitSwapchain()); |
| |
| vkt::Fence fence(*m_device); |
| vkt::Semaphore sem(*m_device); |
| |
| VkCommandPoolCreateInfo pool_create_info = vku::InitStructHelper(); |
| pool_create_info.queueFamilyIndex = m_device->graphics_queue_node_index_; |
| pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; |
| std::optional<vkt::CommandPool> command_pool(vvl::in_place, *m_device, pool_create_info); |
| |
| // create a raw command buffer because we'll just the destroy the pool. |
| VkCommandBuffer command_buffer = VK_NULL_HANDLE; |
| VkCommandBufferAllocateInfo alloc_info = vku::InitStructHelper(); |
| alloc_info.commandPool = command_pool->handle(); |
| alloc_info.commandBufferCount = 1; |
| alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; |
| |
| auto err = vk::AllocateCommandBuffers(*m_device, &alloc_info, &command_buffer); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| m_swapchain.AcquireNextImage(sem, kWaitTimeout, &err); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| VkCommandBufferBeginInfo begin_info = vku::InitStructHelper(); |
| err = vk::BeginCommandBuffer(command_buffer, &begin_info); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| vk::CmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 0, nullptr, 0, |
| nullptr, 0, nullptr); |
| |
| VkViewport viewport{}; |
| viewport.maxDepth = 1.0f; |
| viewport.minDepth = 0.0f; |
| viewport.width = 512; |
| viewport.height = 512; |
| viewport.x = 0; |
| viewport.y = 0; |
| vk::CmdSetViewport(command_buffer, 0, 1, &viewport); |
| err = vk::EndCommandBuffer(command_buffer); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| VkSubmitInfo submit_info = vku::InitStructHelper(); |
| submit_info.commandBufferCount = 1; |
| submit_info.pCommandBuffers = &command_buffer; |
| vk::QueueSubmit(m_default_queue->handle(), 1, &submit_info, fence); |
| |
| m_default_queue->Wait(); |
| |
| command_pool.reset(); |
| } |
| |
| struct SemBufferRaceData { |
| SemBufferRaceData(vkt::Device &dev_) : dev(dev_), sem(dev_, VK_SEMAPHORE_TYPE_TIMELINE) {} |
| |
| vkt::Device &dev; |
| vkt::Semaphore sem; |
| uint64_t start_wait_value{0}; |
| uint64_t timeout_ns{kWaitTimeout}; |
| uint32_t iterations{10000}; |
| std::atomic<bool> bailout{false}; |
| |
| std::unique_ptr<vkt::Buffer> thread_buffer; |
| |
| void ThreadFunc() { |
| auto wait_value = start_wait_value; |
| |
| while (!bailout) { |
| auto err = sem.WaitKHR(wait_value, timeout_ns); |
| if (err != VK_SUCCESS) { |
| break; |
| } |
| auto buffer = std::move(thread_buffer); |
| if (!buffer) { |
| break; |
| } |
| buffer.reset(); |
| |
| err = sem.SignalKHR(wait_value + 1); |
| ASSERT_EQ(VK_SUCCESS, err); |
| wait_value += 3; |
| } |
| } |
| |
| void Run(vkt::CommandPool &command_pool, ErrorMonitor &error_mon) { |
| uint64_t gpu_wait_value, gpu_signal_value; |
| VkResult err; |
| start_wait_value = 2; |
| error_mon.SetBailout(&bailout); |
| std::thread thread(&SemBufferRaceData::ThreadFunc, this); |
| auto queue = dev.QueuesWithGraphicsCapability()[0]; |
| for (uint32_t i = 0; i < iterations; i++) { |
| vkt::CommandBuffer cb(dev, command_pool); |
| |
| VkMemoryPropertyFlags reqs = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT; |
| auto buffer = std::make_unique<vkt::Buffer>(); |
| buffer->Init(dev, 20, VK_BUFFER_USAGE_TRANSFER_DST_BIT, reqs); |
| |
| // main thread sets up buffer |
| // main thread signals 1 |
| // gpu queue waits for 1 |
| // gpu queue signals 2 |
| // sub thread waits for 2 |
| // sub thread frees buffer |
| // sub thread signals 3 |
| // main thread waits for 3 |
| uint64_t host_signal_value = (i * 3) + 1; |
| gpu_wait_value = host_signal_value; |
| gpu_signal_value = (i * 3) + 2; |
| uint64_t host_wait_value = (i * 3) + 3; |
| |
| cb.Begin(); |
| vk::CmdFillBuffer(cb, buffer->handle(), 0, 12, 0x11111111); |
| cb.End(); |
| thread_buffer = std::move(buffer); |
| |
| err = queue->Submit(cb, vkt::TimelineWait(sem, gpu_wait_value), vkt::TimelineSignal(sem, gpu_signal_value)); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| err = sem.SignalKHR(host_signal_value); |
| ASSERT_EQ(VK_SUCCESS, err); |
| |
| err = sem.WaitKHR(host_wait_value, timeout_ns); |
| ASSERT_EQ(VK_SUCCESS, err); |
| } |
| bailout = true; |
| // make sure worker thread wakes up. |
| err = sem.SignalKHR((iterations + 1) * 3); |
| ASSERT_EQ(VK_SUCCESS, err); |
| thread.join(); |
| error_mon.SetBailout(nullptr); |
| queue->Wait(); |
| } |
| }; |
| |
| TEST_F(PositiveSyncObject, WaitTimelineSemThreadRace) { |
| #if defined(VVL_ENABLE_TSAN) |
| // NOTE: This test in particular has failed sporadically on CI when TSAN is enabled. |
| GTEST_SKIP() << "https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5965"; |
| #endif |
| AddRequiredExtensions(VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD (semaphore host signal/wait)"; |
| } |
| SemBufferRaceData data(*m_device); |
| |
| data.Run(m_command_pool, *m_errorMonitor); |
| } |
| |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| TEST_F(PositiveSyncObject, WaitTimelineSemaphoreWithWin32HandleRetrieved) { |
| TEST_DESCRIPTION("Use vkWaitSemaphores with exported semaphore to wait for the queue"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD"; |
| } |
| constexpr auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| |
| if (!SemaphoreExportImportSupported(Gpu(), VK_SEMAPHORE_TYPE_TIMELINE, handle_type)) { |
| GTEST_SKIP() << "Semaphore does not support export and import through Win32 handle"; |
| } |
| |
| // Create exportable timeline semaphore |
| VkSemaphoreTypeCreateInfo semaphore_type_create_info = vku::InitStructHelper(); |
| semaphore_type_create_info.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; |
| semaphore_type_create_info.initialValue = 0; |
| |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(&semaphore_type_create_info); |
| export_info.handleTypes = handle_type; |
| |
| const VkSemaphoreCreateInfo create_info = vku::InitStructHelper(&export_info); |
| vkt::Semaphore semaphore(*m_device, create_info); |
| |
| // This caused original issue: exported semaphore failed to retire queue operations. |
| HANDLE win32_handle = NULL; |
| ASSERT_EQ(VK_SUCCESS, semaphore.ExportHandle(win32_handle, handle_type)); |
| |
| // Put semaphore to work |
| const uint64_t signal_value = 1; |
| VkTimelineSemaphoreSubmitInfo timeline_submit_info = vku::InitStructHelper(); |
| timeline_submit_info.signalSemaphoreValueCount = 1; |
| timeline_submit_info.pSignalSemaphoreValues = &signal_value; |
| |
| VkSubmitInfo submit_info = vku::InitStructHelper(&timeline_submit_info); |
| submit_info.signalSemaphoreCount = 1; |
| submit_info.pSignalSemaphores = &semaphore.handle(); |
| ASSERT_EQ(VK_SUCCESS, vk::QueueSubmit(m_default_queue->handle(), 1, &submit_info, VK_NULL_HANDLE)); |
| |
| // This wait (with exported semaphore) should properly retire all queue operations |
| VkSemaphoreWaitInfo wait_info = vku::InitStructHelper(); |
| wait_info.semaphoreCount = 1; |
| wait_info.pSemaphores = &semaphore.handle(); |
| wait_info.pValues = &signal_value; |
| ASSERT_EQ(VK_SUCCESS, vk::WaitSemaphores(*m_device, &wait_info, uint64_t(1e10))); |
| } |
| #endif // VK_USE_PLATFORM_WIN32_KHR |
| |
| TEST_F(PositiveSyncObject, SubpassBarrier) { |
| TEST_DESCRIPTION("The queue family indices for subpass barrier should be equal (but otherwise are not restricted"); |
| RETURN_IF_SKIP(Init()); |
| |
| RenderPassSingleSubpass rp(*this); |
| rp.AddAttachmentDescription(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); |
| rp.AddAttachmentReference({0, VK_IMAGE_LAYOUT_GENERAL}); |
| rp.AddColorAttachment(0); |
| rp.AddInputAttachment(0); |
| rp.AddSubpassDependency(); |
| rp.CreateRenderPass(); |
| |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, |
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT); |
| vkt::ImageView image_view = image.CreateView(); |
| |
| vkt::Framebuffer fb(*m_device, rp.Handle(), 1, &image_view.handle()); |
| |
| VkImageMemoryBarrier barrier = vku::InitStructHelper(); |
| barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
| barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
| barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; |
| barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| barrier.srcQueueFamilyIndex = 0; |
| barrier.dstQueueFamilyIndex = 0; |
| barrier.image = image; |
| barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(rp.Handle(), fb, 32, 32); |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, |
| VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_DEPENDENCY_BY_REGION_BIT, 0, nullptr, 0, nullptr, 1, |
| &barrier); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, SubpassBarrier2) { |
| TEST_DESCRIPTION("The queue family indices for subpass barrier should be equal (but otherwise are not restricted"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| RenderPassSingleSubpass rp(*this); |
| rp.AddAttachmentDescription(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); |
| rp.AddAttachmentReference({0, VK_IMAGE_LAYOUT_GENERAL}); |
| rp.AddColorAttachment(0); |
| rp.AddInputAttachment(0); |
| rp.AddSubpassDependency(); |
| rp.CreateRenderPass(); |
| |
| vkt::Image image(*m_device, 32, 32, VK_FORMAT_R8G8B8A8_UNORM, |
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT); |
| vkt::ImageView image_view = image.CreateView(); |
| |
| vkt::Framebuffer fb(*m_device, rp, 1, &image_view.handle()); |
| |
| VkImageMemoryBarrier2 barrier = vku::InitStructHelper(); |
| barrier.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
| barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
| barrier.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; |
| barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
| barrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; |
| barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| barrier.srcQueueFamilyIndex = 0; |
| barrier.dstQueueFamilyIndex = 0; |
| barrier.image = image; |
| barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(rp, fb, 32, 32); |
| m_command_buffer.Barrier(barrier, VK_DEPENDENCY_BY_REGION_BIT); |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| } |
| |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/6204 |
| TEST_F(PositiveSyncObject, SubpassBarrierWithExpandableStages) { |
| TEST_DESCRIPTION("Specify expandable stages in subpass barrier"); |
| RETURN_IF_SKIP(Init()); |
| InitRenderTarget(); |
| |
| VkSubpassDescription subpass{}; |
| subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; |
| |
| VkSubpassDependency subpass_dependency{}; |
| subpass_dependency.srcSubpass = 0; |
| subpass_dependency.dstSubpass = 0; |
| subpass_dependency.srcStageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; |
| subpass_dependency.dstStageMask = VK_PIPELINE_STAGE_VERTEX_INPUT_BIT; |
| subpass_dependency.srcAccessMask = VK_ACCESS_INDEX_READ_BIT; |
| subpass_dependency.dstAccessMask = VK_ACCESS_INDEX_READ_BIT; |
| |
| VkRenderPassCreateInfo rpci = vku::InitStructHelper(); |
| rpci.subpassCount = 1; |
| rpci.pSubpasses = &subpass; |
| rpci.dependencyCount = 1; |
| rpci.pDependencies = &subpass_dependency; |
| const vkt::RenderPass rp(*m_device, rpci); |
| const vkt::Framebuffer fb(*m_device, rp, 0, nullptr, m_width, m_height); |
| |
| m_renderPassBeginInfo.renderPass = rp; |
| m_renderPassBeginInfo.framebuffer = fb; |
| |
| VkMemoryBarrier barrier = vku::InitStructHelper(); |
| barrier.srcAccessMask = VK_ACCESS_INDEX_READ_BIT; |
| barrier.dstAccessMask = VK_ACCESS_INDEX_READ_BIT; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BeginRenderPass(m_renderPassBeginInfo); |
| |
| // The issue was that implementation expands *subpass* compound stages but did not expand *barrier* compound stages. |
| // Specify expandable stage (VERTEX_INPUT_BIT is INDEX_INPUT_BIT + VERTEX_ATTRIBUTE_INPUT_BIT) to ensure it's correctly |
| // matched against subpass stages. |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, 1, &barrier, |
| 0, nullptr, 0, nullptr); |
| |
| m_command_buffer.EndRenderPass(); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, BarrierWithHostStage) { |
| TEST_DESCRIPTION("Barrier includes VK_PIPELINE_STAGE_2_HOST_BIT as srcStageMask or dstStageMask"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| // HOST stage as source |
| vkt::Buffer buffer(*m_device, 32, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); |
| VkBufferMemoryBarrier2 buffer_barrier = vku::InitStructHelper(); |
| buffer_barrier.srcStageMask = VK_PIPELINE_STAGE_2_HOST_BIT; |
| buffer_barrier.srcAccessMask = VK_ACCESS_2_HOST_WRITE_BIT; |
| buffer_barrier.dstStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; |
| buffer_barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; |
| buffer_barrier.srcQueueFamilyIndex = 0; |
| buffer_barrier.dstQueueFamilyIndex = 0; |
| buffer_barrier.buffer = buffer; |
| buffer_barrier.size = VK_WHOLE_SIZE; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.Barrier(buffer_barrier); |
| m_command_buffer.End(); |
| |
| // HOST stage as destination |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); |
| VkImageMemoryBarrier2 image_barrier = vku::InitStructHelper(); |
| image_barrier.srcStageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; |
| image_barrier.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT; |
| image_barrier.dstStageMask = VK_PIPELINE_STAGE_2_HOST_BIT; |
| image_barrier.dstAccessMask = VK_ACCESS_2_HOST_READ_BIT; |
| image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| image_barrier.srcQueueFamilyIndex = 0; |
| image_barrier.dstQueueFamilyIndex = 0; |
| image_barrier.image = image; |
| image_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.Barrier(image_barrier); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, BarrierASBuildWithShaderReadAccess) { |
| TEST_DESCRIPTION("Test barrier with acceleration structure build stage and shader read access to access geometry input data."); |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| AddRequiredFeature(vkt::Feature::accelerationStructure); |
| RETURN_IF_SKIP(Init()); |
| |
| VkMemoryBarrier2 mem_barrier = vku::InitStructHelper(); |
| mem_barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; |
| mem_barrier.dstStageMask = VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BarrierKHR(mem_barrier); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, BarrierASCopy) { |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_RAY_TRACING_MAINTENANCE_1_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| AddRequiredFeature(vkt::Feature::rayTracingMaintenance1); |
| RETURN_IF_SKIP(Init()); |
| |
| VkMemoryBarrier2 mem_barrier = vku::InitStructHelper(); |
| mem_barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_READ_BIT; |
| mem_barrier.dstStageMask = VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_COPY_BIT_KHR; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BarrierKHR(mem_barrier); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, BarrierAccessSyncMicroMap) { |
| TEST_DESCRIPTION("Test VK_PIPELINE_STAGE_2_MICROMAP_BUILD_BIT_EXT can be used with VK_ACCESS_2_SHADER_READ_BIT."); |
| |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredExtensions(VK_EXT_OPACITY_MICROMAP_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| AddRequiredFeature(vkt::Feature::micromap); |
| RETURN_IF_SKIP(Init()); |
| |
| VkMemoryBarrier2 mem_barrier = vku::InitStructHelper(); |
| mem_barrier.srcAccessMask = VK_ACCESS_2_SHADER_WRITE_BIT; |
| mem_barrier.srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; |
| mem_barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; |
| mem_barrier.dstStageMask = VK_PIPELINE_STAGE_2_MICROMAP_BUILD_BIT_EXT; |
| |
| m_command_buffer.Begin(); |
| m_command_buffer.BarrierKHR(mem_barrier); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, DynamicRenderingLocalReadImageBarrier) { |
| TEST_DESCRIPTION("Test using an image memory barrier with the dynamic rendering local read extension"); |
| |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_DYNAMIC_RENDERING_LOCAL_READ_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::dynamicRendering); |
| AddRequiredFeature(vkt::Feature::dynamicRenderingLocalRead); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::CommandBuffer secondary(*m_device, m_command_pool, VK_COMMAND_BUFFER_LEVEL_SECONDARY); |
| |
| vkt::Image image(*m_device, 128, 128, VK_FORMAT_B8G8R8A8_UNORM, |
| VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT); |
| |
| VkFormat colorAttachment = VK_FORMAT_R16_UNORM; |
| |
| VkCommandBufferInheritanceRenderingInfo inheritanceRenderingInfo = vku::InitStructHelper(); |
| inheritanceRenderingInfo.colorAttachmentCount = 1u; |
| inheritanceRenderingInfo.pColorAttachmentFormats = &colorAttachment; |
| inheritanceRenderingInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; |
| |
| VkCommandBufferInheritanceInfo inheritanceInfo = vku::InitStructHelper(&inheritanceRenderingInfo); |
| |
| VkCommandBufferBeginInfo beginInfo = vku::InitStructHelper(); |
| beginInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT; |
| beginInfo.pInheritanceInfo = &inheritanceInfo; |
| |
| VkImageMemoryBarrier imageMemoryBarrier = vku::InitStructHelper(); |
| imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; |
| imageMemoryBarrier.dstAccessMask = VK_ACCESS_INPUT_ATTACHMENT_READ_BIT; |
| imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ; |
| imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_RENDERING_LOCAL_READ; |
| imageMemoryBarrier.image = image; |
| imageMemoryBarrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0u, 1u, 0u, 1u}; |
| |
| secondary.Begin(&beginInfo); |
| vk::CmdPipelineBarrier(secondary, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, |
| VK_DEPENDENCY_BY_REGION_BIT, 0u, nullptr, 0u, nullptr, 1u, &imageMemoryBarrier); |
| secondary.End(); |
| } |
| |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/6172 |
| TEST_F(PositiveSyncObject, TwoQueuesReuseBinarySemaphore) { |
| TEST_DESCRIPTION("Use binary semaphore with the first queue then re-use on a different queue"); |
| RETURN_IF_SKIP(Init()); |
| |
| if (!m_second_queue) { |
| GTEST_SKIP() << "Test requires two queues"; |
| } |
| |
| VkQueue q0 = m_default_queue->handle(); |
| VkQueue q1 = m_second_queue->handle(); |
| |
| constexpr VkPipelineStageFlags wait_dst_stages = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| vkt::Semaphore semaphore(*m_device); |
| |
| VkSubmitInfo submits[2]; |
| |
| submits[0] = vku::InitStructHelper(); |
| submits[0].signalSemaphoreCount = 1; |
| submits[0].pSignalSemaphores = &semaphore.handle(); |
| |
| submits[1] = vku::InitStructHelper(); |
| submits[1].waitSemaphoreCount = 1; |
| submits[1].pWaitSemaphores = &semaphore.handle(); |
| submits[1].pWaitDstStageMask = &wait_dst_stages; |
| |
| vk::QueueSubmit(q0, 2, submits, VK_NULL_HANDLE); |
| vk::QueueWaitIdle(q0); |
| |
| vk::QueueSubmit(q1, 2, submits, VK_NULL_HANDLE); |
| vk::QueueWaitIdle(q1); |
| } |
| |
| TEST_F(PositiveSyncObject, SingleSubmitSignalBinarySemaphoreTwoTimes) { |
| TEST_DESCRIPTION("Setup submission in such a way to be able to signal the same binary semaphore twice"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device); |
| vkt::Semaphore semaphore2(*m_device); |
| |
| VkSemaphoreSubmitInfo semaphore_info = vku::InitStructHelper(); |
| semaphore_info.semaphore = semaphore; |
| semaphore_info.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; |
| |
| VkSemaphoreSubmitInfo semaphore_info2 = vku::InitStructHelper(); |
| semaphore_info2.semaphore = semaphore2; |
| semaphore_info2.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; |
| |
| VkSubmitInfo2 submits[3]; |
| submits[0] = vku::InitStructHelper(); |
| submits[0].signalSemaphoreInfoCount = 1; |
| submits[0].pSignalSemaphoreInfos = &semaphore_info; |
| |
| submits[1] = vku::InitStructHelper(); |
| submits[1].waitSemaphoreInfoCount = 1; |
| submits[1].pWaitSemaphoreInfos = &semaphore_info; |
| submits[1].signalSemaphoreInfoCount = 1; |
| submits[1].pSignalSemaphoreInfos = &semaphore_info2; |
| |
| submits[2] = vku::InitStructHelper(); |
| submits[2].waitSemaphoreInfoCount = 1; |
| submits[2].pWaitSemaphoreInfos = &semaphore_info2; |
| // Here we can signal the first semaphore again. This should work because of the wait on the semaphore2. |
| // Regarding internal implementation, this demonstrates that the binary semaphore's timeline map |
| // can have more than one entry. The entry with payload 1 is for the first signal/wait, |
| // and the entry with payload 2 will contain the following signal. |
| submits[2].signalSemaphoreInfoCount = 1; |
| submits[2].pSignalSemaphoreInfos = &semaphore_info; |
| |
| vk::QueueSubmit2(*m_default_queue, 3, submits, VK_NULL_HANDLE); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, SubmitImportedBinarySemaphoreWithNonZeroValue) { |
| TEST_DESCRIPTION("QueueSubmit2 can specify arbitrary payload value for binary semaphore and it should be ignored"); |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| #else |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; |
| #endif |
| AddRequiredExtensions(extension_name); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| // Check semaphore's import/export capability |
| VkPhysicalDeviceExternalSemaphoreInfo semaphore_info = vku::InitStructHelper(); |
| semaphore_info.handleType = handle_type; |
| VkExternalSemaphorePropertiesKHR semaphore_properties = vku::InitStructHelper(); |
| vk::GetPhysicalDeviceExternalSemaphorePropertiesKHR(Gpu(), &semaphore_info, &semaphore_properties); |
| if (!(semaphore_properties.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) || |
| !(semaphore_properties.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT)) { |
| GTEST_SKIP() << "Semaphore does not support import and/or export"; |
| } |
| |
| // Signaling semaphore |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(); |
| export_info.handleTypes = handle_type; |
| VkSemaphoreCreateInfo semaphore_ci = vku::InitStructHelper(&export_info); |
| vkt::Semaphore semaphore(*m_device, semaphore_ci); |
| VkSemaphoreSubmitInfo signal_info = vku::InitStructHelper(); |
| signal_info.semaphore = semaphore; |
| signal_info.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; |
| |
| ExternalHandle ext_handle{}; |
| semaphore.ExportHandle(ext_handle, handle_type); |
| |
| // Wait semaphore is imported from the signaling one. |
| vkt::Semaphore semaphore2(*m_device); |
| semaphore2.ImportHandle(ext_handle, handle_type); |
| VkSemaphoreSubmitInfo wait_info = vku::InitStructHelper(); |
| wait_info.semaphore = semaphore2; |
| // Specify some payload value, even if it's a binary semaphore. It should be ignored. |
| wait_info.value = 1; |
| wait_info.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT; |
| |
| VkSubmitInfo2 submits[2]; |
| submits[0] = vku::InitStructHelper(); |
| submits[0].signalSemaphoreInfoCount = 1; |
| submits[0].pSignalSemaphoreInfos = &signal_info; |
| |
| submits[1] = vku::InitStructHelper(); |
| submits[1].waitSemaphoreInfoCount = 1; |
| submits[1].pWaitSemaphoreInfos = &wait_info; |
| |
| vk::QueueSubmit2(*m_default_queue, 2, submits, VK_NULL_HANDLE); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, IgnoreAcquireOpSrcStage) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7928 |
| TEST_DESCRIPTION("Test that graphics src stage is ignored during acquire operation on the transfer queue"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| std::optional<uint32_t> transfer_only_family = m_device->TransferOnlyQueueFamily(); |
| if (!transfer_only_family.has_value()) { |
| GTEST_SKIP() << "Transfer-only queue family is required"; |
| } |
| vkt::CommandPool transfer_pool(*m_device, transfer_only_family.value()); |
| vkt::CommandBuffer transfer_cb(*m_device, transfer_pool); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| VkBufferMemoryBarrier2 acquire_barrier = vku::InitStructHelper(); |
| acquire_barrier.srcStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; |
| acquire_barrier.srcAccessMask = VK_ACCESS_2_SHADER_READ_BIT; |
| acquire_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; |
| acquire_barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| acquire_barrier.srcQueueFamilyIndex = m_default_queue->family_index; |
| acquire_barrier.dstQueueFamilyIndex = transfer_only_family.value(); |
| acquire_barrier.buffer = buffer; |
| acquire_barrier.offset = 0; |
| acquire_barrier.size = 256; |
| |
| transfer_cb.Begin(); |
| transfer_cb.Barrier(acquire_barrier); |
| transfer_cb.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, IgnoreReleaseOpDstStage) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7928 |
| TEST_DESCRIPTION("Test that graphics dst stage is ignored during release operation on the transfer queue"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| std::optional<uint32_t> transfer_only_family = m_device->TransferOnlyQueueFamily(); |
| if (!transfer_only_family.has_value()) { |
| GTEST_SKIP() << "Transfer-only queue family is required"; |
| } |
| vkt::CommandPool release_pool(*m_device, transfer_only_family.value()); |
| vkt::CommandBuffer release_cb(*m_device, release_pool); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| VkBufferMemoryBarrier2 release_barrier = vku::InitStructHelper(); |
| release_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; |
| release_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| release_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; |
| release_barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; |
| release_barrier.srcQueueFamilyIndex = transfer_only_family.value(); |
| release_barrier.dstQueueFamilyIndex = m_default_queue->family_index; |
| release_barrier.buffer = buffer; |
| release_barrier.offset = 0; |
| release_barrier.size = 256; |
| |
| release_cb.Begin(); |
| release_cb.Barrier(release_barrier); |
| release_cb.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, ImageOwnershipTransferNormalizeSubresourceRange) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8823 |
| TEST_DESCRIPTION("Use different representations of image subresource range for release and acquire ownership operations"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Queue *transfer_queue = m_device->TransferOnlyQueue(); |
| if (!transfer_queue) { |
| GTEST_SKIP() << "Transfer-only queue is not present"; |
| } |
| vkt::CommandPool release_pool(*m_device, transfer_queue->family_index); |
| vkt::CommandBuffer release_cb(*m_device, release_pool); |
| vkt::CommandBuffer acquire_cb(*m_device, m_command_pool); |
| |
| const VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; |
| VkImageCreateInfo image_ci = vkt::Image::ImageCreateInfo2D(128, 128, 1, 1, VK_FORMAT_B8G8R8A8_UNORM, usage); |
| image_ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; |
| vkt::Image image(*m_device, image_ci); |
| image.SetLayout(VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); |
| |
| // Release image |
| VkImageMemoryBarrier2 release_barrier = vku::InitStructHelper(); |
| release_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; |
| release_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| release_barrier.dstStageMask = VK_PIPELINE_STAGE_2_NONE; |
| release_barrier.dstAccessMask = VK_ACCESS_2_NONE; |
| release_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| release_barrier.newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL; |
| release_barrier.srcQueueFamilyIndex = transfer_queue->family_index; |
| release_barrier.dstQueueFamilyIndex = m_default_queue->family_index; |
| release_barrier.image = image; |
| // Specify exact mip/layer count |
| release_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| release_cb.Begin(); |
| release_cb.Barrier(release_barrier); |
| release_cb.End(); |
| |
| // Acquire image |
| VkImageMemoryBarrier2 acquire_barrier = vku::InitStructHelper(); |
| acquire_barrier.srcStageMask = VK_PIPELINE_STAGE_2_NONE; |
| acquire_barrier.srcAccessMask = VK_ACCESS_2_NONE; |
| acquire_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT; |
| acquire_barrier.dstAccessMask = VK_ACCESS_2_SHADER_SAMPLED_READ_BIT; |
| acquire_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| acquire_barrier.newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL; |
| acquire_barrier.srcQueueFamilyIndex = transfer_queue->family_index; |
| acquire_barrier.dstQueueFamilyIndex = m_default_queue->family_index; |
| acquire_barrier.image = image; |
| // Use VK_REMAINING shortcut to specify mip/layer count. |
| // Test for regression when VK_REMAINING is not compared correctly against specific mip/layer values for ownership transfer. |
| acquire_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}; |
| |
| acquire_cb.Begin(); |
| acquire_cb.Barrier(acquire_barrier); |
| acquire_cb.End(); |
| |
| // Submit release on the transfer queue and acquire on the main queue. |
| // There should be no errors about missing release operation for acquire operation. |
| vkt::Semaphore semaphore(*m_device); |
| transfer_queue->Submit2(release_cb, vkt::Signal(semaphore)); |
| m_default_queue->Submit2(acquire_cb, vkt::Wait(semaphore)); |
| m_device->Wait(); |
| } |
| |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| TEST_F(PositiveSyncObject, GetCounterValueOfExportedSemaphore) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8212 |
| TEST_DESCRIPTION("Getting counter value of exported semaphore should not introduce orphaned signals"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD"; |
| } |
| constexpr auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| if (!SemaphoreExportImportSupported(Gpu(), VK_SEMAPHORE_TYPE_TIMELINE, handle_type)) { |
| GTEST_SKIP() << "Semaphore does not support export and import through Win32 handle"; |
| } |
| |
| // Create exportable timeline semaphore |
| VkSemaphoreTypeCreateInfo semaphore_type_create_info = vku::InitStructHelper(); |
| semaphore_type_create_info.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; |
| semaphore_type_create_info.initialValue = 0; |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(&semaphore_type_create_info); |
| export_info.handleTypes = handle_type; |
| const VkSemaphoreCreateInfo create_info = vku::InitStructHelper(&export_info); |
| vkt::Semaphore semaphore(*m_device, create_info); |
| HANDLE win32_handle = NULL; |
| semaphore.ExportHandle(win32_handle, handle_type); |
| |
| // The problem was that GetSemaphoreCounterValue creates temporary signal to make forward progress, |
| // but the code path for external semaphore failed to retire that signal. Being stuck that signal |
| // conflicted with other signals. |
| uint64_t counter = 0; |
| vk::GetSemaphoreCounterValue(device(), semaphore, &counter); |
| |
| // Test that there are no leftovers from GetSemaphoreCounterValue, this signal should just work. |
| semaphore.Signal(1); |
| } |
| |
| TEST_F(PositiveSyncObject, GetCounterValueOfExportedSemaphore2) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8212 |
| TEST_DESCRIPTION("Getting counter value of exported semaphore should not introduce orphaned signals"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD"; |
| } |
| constexpr auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| if (!SemaphoreExportImportSupported(Gpu(), VK_SEMAPHORE_TYPE_TIMELINE, handle_type)) { |
| GTEST_SKIP() << "Semaphore does not support export and import through Win32 handle"; |
| } |
| |
| // Create exportable timeline semaphore |
| VkSemaphoreTypeCreateInfo semaphore_type_create_info = vku::InitStructHelper(); |
| semaphore_type_create_info.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; |
| semaphore_type_create_info.initialValue = 0; |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(&semaphore_type_create_info); |
| export_info.handleTypes = handle_type; |
| const VkSemaphoreCreateInfo create_info = vku::InitStructHelper(&export_info); |
| vkt::Semaphore semaphore(*m_device, create_info); |
| HANDLE win32_handle = NULL; |
| semaphore.ExportHandle(win32_handle, handle_type); |
| |
| // Slight variation of the previous test to ensure that issue was not related to semaphore initial value |
| semaphore.Signal(1); |
| uint64_t counter = 0; |
| vk::GetSemaphoreCounterValue(device(), semaphore, &counter); |
| semaphore.Signal(2); |
| } |
| #endif // VK_USE_PLATFORM_WIN32_KHR |
| |
| TEST_F(PositiveSyncObject, TimelineHostWaitThenSubmitSignal) { |
| TEST_DESCRIPTION("Wait on the host then submit signal"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD (WaitSemaphores)"; |
| } |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| auto thread = [&semaphore]() { semaphore.Wait(1, kWaitTimeout); }; |
| std::thread t(thread); |
| |
| // This delay increases the probability that the wait started before the signal. |
| // If the waiting thread was not fast enough this becomes a common signal-then-wait setup. |
| std::this_thread::sleep_for(std::chrono::milliseconds{50}); |
| |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1)); |
| t.join(); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineHostWaitThenSubmitLargerSignal) { |
| TEST_DESCRIPTION("Wait on the host then submit signal with larger value"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD (WaitSemaphores)"; |
| } |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| auto thread = [&semaphore]() { semaphore.Wait(1, kWaitTimeout); }; |
| std::thread t(thread); |
| |
| // This delay increases the probability that the wait started before the signal. |
| // If the waiting thread was not fast enough this becomes a common signal-then-wait setup. |
| std::this_thread::sleep_for(std::chrono::milliseconds{50}); |
| |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(semaphore, 2)); |
| t.join(); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineHostWaitThenHostSignal) { |
| TEST_DESCRIPTION("Wait on the host then signal from the host"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD (WaitSemaphores)"; |
| } |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| auto thread = [&semaphore]() { semaphore.Wait(1, kWaitTimeout); }; |
| std::thread t(thread); |
| |
| // This delay increases the probability that the wait started before the signal. |
| // If the waiting thread was not fast enough this becomes a common signal-then-wait setup. |
| std::this_thread::sleep_for(std::chrono::milliseconds{50}); |
| |
| semaphore.Signal(1); |
| t.join(); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineHostSignalThenHostWait) { |
| TEST_DESCRIPTION("Signal on the host then wait on the host"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| semaphore.Signal(1); |
| semaphore.Wait(1, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineTwoHostSignals) { |
| TEST_DESCRIPTION("Signal on the host two times"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| semaphore.Signal(1); |
| semaphore.Signal(2); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineSubmitSignalThenHostWaitSmallerValue) { |
| TEST_DESCRIPTION("Submit signal then wait smaller value on the host"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(semaphore, 2)); |
| semaphore.Wait(1, kWaitTimeout); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineSubmitWaitThenSubmitSignalLargerValue) { |
| TEST_DESCRIPTION("Submit wait then submit signal with larger value, wait on the host for wait completion"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| |
| if (!m_second_queue) { |
| GTEST_SKIP() << "2 queues are needed"; |
| } |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(semaphore, 1)); |
| m_second_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(semaphore, 2)); |
| |
| // This should also sync with the second queue because the second queue signals a default one. |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineSubmitSignalThenSubmitWaitSmallerValue) { |
| TEST_DESCRIPTION("Submit signal then submit wait with smaller value, wait on the host for wait completion"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| all_queue_count_ = true; |
| RETURN_IF_SKIP(Init()); |
| |
| if (!m_second_queue) { |
| GTEST_SKIP() << "2 queues are needed"; |
| } |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| m_second_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(semaphore, 2)); |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(semaphore, 1)); |
| |
| // This should also sync with the second queue because the second queue signals a default one. |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineSubmitWaitThenHostSignal) { |
| TEST_DESCRIPTION("Submit wait then signal on the host, wait on the host for wait completion"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(semaphore, 1)); |
| semaphore.Signal(1); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineSubmitWaitThenHostSignalLargerValue) { |
| TEST_DESCRIPTION("Submit wait then signal on the host larger value, wait on the host for wait completion"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(semaphore, 1)); |
| semaphore.Signal(2); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, PollSemaphoreCounter) { |
| TEST_DESCRIPTION("Basic semaphore polling test"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD (GetSemaphoreCounterValue)"; |
| } |
| |
| vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1)); |
| |
| uint64_t counter = 0; |
| do { |
| vk::GetSemaphoreCounterValue(*m_device, semaphore, &counter); |
| } while (counter != 1); |
| } |
| |
| TEST_F(PositiveSyncObject, KhronosTimelineSemaphoreExample) { |
| TEST_DESCRIPTION("https://www.khronos.org/blog/vulkan-timeline-semaphores"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD (host synchronization)"; |
| } |
| if (!m_second_queue) { |
| GTEST_SKIP() << "Two queues are needed"; |
| } |
| |
| int N = 1000; |
| |
| for (int i = 0; i < N; i++) { |
| vkt::Semaphore timeline(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| |
| auto thread1 = [this, &timeline]() { |
| // Can start immediately, wait on 0 is noop |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(timeline, 0), vkt::TimelineSignal(timeline, 5)); |
| }; |
| auto thread2 = [&timeline]() { |
| // Wait for thread1 |
| timeline.Wait(4, kWaitTimeout); |
| // Unblock thread3 |
| timeline.Signal(7); |
| }; |
| auto thread3 = [this, &timeline]() { |
| m_second_queue->Submit(vkt::no_cmd, vkt::TimelineWait(timeline, 7), vkt::TimelineSignal(timeline, 8)); |
| }; |
| |
| std::thread t1(thread1); |
| std::thread t2(thread2); |
| std::thread t3(thread3); |
| |
| timeline.Wait(8, kWaitTimeout); |
| |
| t3.join(); |
| t2.join(); |
| t1.join(); |
| } |
| } |
| |
| TEST_F(PositiveSyncObject, BarrierWithoutOwnershipTransferUseAllStages) { |
| TEST_DESCRIPTION("Barrier without ownership transfer with USE_ALL_STAGES flag (no-op)"); |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance8); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| VkBufferMemoryBarrier barrier = vku::InitStructHelper(); |
| barrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; |
| barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| // The queue family are the same here, so flag is basically a no-op |
| barrier.srcQueueFamilyIndex = 0; |
| barrier.dstQueueFamilyIndex = 0; |
| barrier.buffer = buffer; |
| barrier.offset = 0; |
| barrier.size = 256; |
| |
| m_command_buffer.Begin(); |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, |
| VK_DEPENDENCY_QUEUE_FAMILY_OWNERSHIP_TRANSFER_USE_ALL_STAGES_BIT_KHR, 0, nullptr, 1, &barrier, 0, |
| nullptr); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, OwnershipTransferUseAllStages) { |
| TEST_DESCRIPTION("Barrier with ownership transfer with USE_ALL_STAGES flag"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| // Enable feature to use stage other than ALL_COMMANDS during ownership transfer |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance8); |
| RETURN_IF_SKIP(Init()); |
| |
| std::optional<uint32_t> transfer_only_family = m_device->TransferOnlyQueueFamily(); |
| if (!transfer_only_family.has_value()) { |
| GTEST_SKIP() << "Transfer-only queue family is required"; |
| } |
| vkt::CommandPool transfer_pool(*m_device, transfer_only_family.value(), VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); |
| vkt::CommandBuffer transfer_cb(*m_device, transfer_pool); |
| |
| vkt::Buffer buffer(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| // Acquire operation on transfer queue. |
| // The src stage should be a valid transfer stage. |
| VkBufferMemoryBarrier2 acquire_barrier = vku::InitStructHelper(); |
| acquire_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; |
| acquire_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| acquire_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; |
| acquire_barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| acquire_barrier.srcQueueFamilyIndex = m_default_queue->family_index; |
| acquire_barrier.dstQueueFamilyIndex = transfer_only_family.value(); |
| acquire_barrier.buffer = buffer; |
| acquire_barrier.offset = 0; |
| acquire_barrier.size = 256; |
| |
| transfer_cb.Begin(); |
| // Use dependency flag to be able to use src stage other then ALL_COMMANDS |
| transfer_cb.Barrier(acquire_barrier, VK_DEPENDENCY_QUEUE_FAMILY_OWNERSHIP_TRANSFER_USE_ALL_STAGES_BIT_KHR); |
| transfer_cb.End(); |
| |
| // Release operation on transfer queue. |
| // The dst stage should be a valid transfer stage. |
| VkBufferMemoryBarrier2 release_barrier = vku::InitStructHelper(); |
| release_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; |
| release_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| release_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT; |
| release_barrier.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| release_barrier.srcQueueFamilyIndex = transfer_only_family.value(); |
| release_barrier.dstQueueFamilyIndex = m_default_queue->family_index; |
| release_barrier.buffer = buffer; |
| release_barrier.offset = 0; |
| release_barrier.size = 256; |
| |
| transfer_cb.Begin(); |
| // Use dependency flag to be able to use dst stage other then ALL_COMMANDS |
| transfer_cb.Barrier(release_barrier, VK_DEPENDENCY_QUEUE_FAMILY_OWNERSHIP_TRANSFER_USE_ALL_STAGES_BIT_KHR); |
| transfer_cb.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, BinarySyncAfterResolvedTimelineWait) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8900 |
| TEST_DESCRIPTION("Test binary wait followed by the previous resolved timeline wait"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore timeline_semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| vkt::Semaphore binary_semaphore(*m_device); |
| |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(timeline_semaphore, 1)); |
| |
| // This removes signal's timepoint from timeline (not pending anymore) |
| timeline_semaphore.Wait(1, kWaitTimeout); |
| |
| // Wait one more time (should just check completed state). |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(timeline_semaphore, 1)); |
| |
| m_default_queue->Submit(vkt::no_cmd, vkt::Signal(binary_semaphore)); |
| |
| // Waiting on binary semaphore initiates check of unresolved timeline wait dependency. |
| // This check did not work properly when resolving timeline signal was already retired. |
| m_default_queue->Submit(vkt::no_cmd, vkt::Wait(binary_semaphore)); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, QueueWaitAfterBinarySignal) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8989 |
| TEST_DESCRIPTION("Wait for binary signal after queue wait"); |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore timeline_semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE, 1); |
| vkt::Semaphore binary_semaphore(*m_device); |
| |
| m_default_queue->Submit(vkt::no_cmd, vkt::Signal(binary_semaphore)); |
| // this removes timepoint with signal op from timeline, but it should not be a problem to wait for this signal |
| m_default_queue->Wait(); |
| |
| // Values that corresponds to binary semaphore should not affect internal logic. |
| // Use non-zero value for binary semaphore to increase probability of the issues in case of regression. |
| const uint64_t values[2] = { |
| 1, // timeline value |
| 42 // arbitrary value that should be ignored (binary semaphore slot) |
| }; |
| VkTimelineSemaphoreSubmitInfo timeline_info = vku::InitStructHelper(); |
| timeline_info.waitSemaphoreValueCount = 2; |
| timeline_info.pWaitSemaphoreValues = values; |
| |
| const VkPipelineStageFlags wait_dst_stages[2] = {VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT}; |
| const VkSemaphore wait_semaphores[2] = {timeline_semaphore, binary_semaphore}; |
| |
| VkSubmitInfo submit = vku::InitStructHelper(&timeline_info); |
| submit.waitSemaphoreCount = 2; |
| submit.pWaitSemaphores = wait_semaphores; |
| submit.pWaitDstStageMask = wait_dst_stages; |
| |
| // Wait on the binary signal that was followed by Wait(). |
| // This also waits on the timeline initial value, so effectiely no-wait. |
| // The only reason timeline semaphore is used in this test, is to have a |
| // slot in VkTimelineSemaphoreSubmitInfo that corresponds to binary semaphore. |
| vk::QueueSubmit(m_default_queue->handle(), 1, &submit, VK_NULL_HANDLE); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, QueueWaitAfterBinarySignal2) { |
| // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/8989 |
| TEST_DESCRIPTION("Binary signal followed by queue wait"); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device); |
| m_default_queue->Submit(vkt::no_cmd, vkt::Signal(semaphore)); |
| m_default_queue->Wait(); // this removes timepoint with signal op from timeline |
| |
| m_default_queue->Submit(vkt::no_cmd, vkt::Wait(semaphore)); |
| // Test for regression that triggers assert in Semaphore::CanBinaryBeSignaled |
| m_default_queue->Submit(vkt::no_cmd, vkt::Signal(semaphore)); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, QueueWaitAfterBinarySignal3) { |
| TEST_DESCRIPTION("Binary signal followed by queue wait"); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Semaphore semaphore(*m_device); |
| m_default_queue->Submit(vkt::no_cmd, vkt::Signal(semaphore)); |
| m_default_queue->Wait(); // this removes timepoint with signal op from timeline |
| m_default_queue->Submit(vkt::no_cmd, vkt::Wait(semaphore), vkt::Signal(semaphore)); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, AccessFlags3) { |
| SetTargetApiVersion(VK_API_VERSION_1_1); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_8_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance8); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Buffer buffer(*m_device, 32, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT); |
| |
| VkImageCreateInfo image_ci = |
| vkt::Image::ImageCreateInfo2D(32u, 32u, 1u, 1u, VK_FORMAT_B8G8R8A8_UNORM, |
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, VK_IMAGE_TILING_OPTIMAL); |
| vkt::Image image(*m_device, image_ci); |
| |
| VkMemoryBarrierAccessFlags3KHR memory_barrier_access_flags = vku::InitStructHelper(); |
| memory_barrier_access_flags.srcAccessMask3 = VK_ACCESS_3_NONE_KHR; |
| memory_barrier_access_flags.dstAccessMask3 = VK_ACCESS_3_NONE_KHR; |
| |
| VkMemoryBarrier2 memory_barrier = vku::InitStructHelper(&memory_barrier_access_flags); |
| |
| VkBufferMemoryBarrier2 buffer_barrier = vku::InitStructHelper(&memory_barrier_access_flags); |
| buffer_barrier.buffer = buffer; |
| buffer_barrier.size = VK_WHOLE_SIZE; |
| buffer_barrier.dstStageMask = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT_KHR; |
| |
| VkImageMemoryBarrier2 image_barrier = vku::InitStructHelper(&memory_barrier_access_flags); |
| image_barrier.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT_KHR; |
| image_barrier.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT; |
| image_barrier.dstStageMask = VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT; |
| image_barrier.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT; |
| image_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| image_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| image_barrier.image = image; |
| image_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; |
| |
| VkDependencyInfo dependency_info = vku::InitStructHelper(); |
| dependency_info.memoryBarrierCount = 1u; |
| dependency_info.pMemoryBarriers = &memory_barrier; |
| dependency_info.bufferMemoryBarrierCount = 1u; |
| dependency_info.pBufferMemoryBarriers = &buffer_barrier; |
| dependency_info.imageMemoryBarrierCount = 1u; |
| dependency_info.pImageMemoryBarriers = &image_barrier; |
| |
| m_command_buffer.Begin(); |
| |
| vk::CmdPipelineBarrier2KHR(m_command_buffer, &dependency_info); |
| |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, Transition3dImageSlice) { |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_1_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance9); |
| RETURN_IF_SKIP(Init()); |
| |
| VkImageCreateInfo image_create_info = vku::InitStructHelper(); |
| image_create_info.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; |
| image_create_info.imageType = VK_IMAGE_TYPE_3D; |
| image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM; |
| image_create_info.extent = {32u, 32u, 4u}; |
| image_create_info.mipLevels = 1u; |
| image_create_info.arrayLayers = 1u; |
| image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; |
| image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; |
| image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
| vkt::Image image(*m_device, image_create_info, vkt::set_layout); |
| |
| VkImageMemoryBarrier image_memory_barrier = vku::InitStructHelper(); |
| image_memory_barrier.srcAccessMask = VK_ACCESS_NONE; |
| image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.image = image; |
| image_memory_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 1, 1}; |
| |
| m_command_buffer.Begin(); |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, |
| nullptr, 1u, &image_memory_barrier); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, Transition3dImageSlices) { |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_1_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance9); |
| RETURN_IF_SKIP(Init()); |
| |
| VkImageCreateInfo image_create_info = vku::InitStructHelper(); |
| image_create_info.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; |
| image_create_info.imageType = VK_IMAGE_TYPE_3D; |
| image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM; |
| image_create_info.extent = {32u, 32u, 4u}; |
| image_create_info.mipLevels = 1u; |
| image_create_info.arrayLayers = 1u; |
| image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; |
| image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; |
| image_create_info.usage = |
| VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; |
| vkt::Image image(*m_device, image_create_info, vkt::set_layout); |
| |
| VkImageMemoryBarrier image_memory_barrier = vku::InitStructHelper(); |
| image_memory_barrier.srcAccessMask = VK_ACCESS_NONE; |
| image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.image = image; |
| image_memory_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 4}; |
| |
| m_command_buffer.Begin(); |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, |
| nullptr, 1u, &image_memory_barrier); |
| |
| image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
| image_memory_barrier.subresourceRange.baseArrayLayer = 1u; |
| image_memory_barrier.subresourceRange.layerCount = 1u; |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, |
| nullptr, 1u, &image_memory_barrier); |
| |
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| image_memory_barrier.subresourceRange.baseArrayLayer = 2u; |
| image_memory_barrier.subresourceRange.layerCount = 1u; |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, |
| nullptr, 1u, &image_memory_barrier); |
| |
| image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; |
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, |
| nullptr, 1u, &image_memory_barrier); |
| m_command_buffer.End(); |
| |
| m_default_queue->Submit(m_command_buffer); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, Transition3dImageWithMipLevels) { |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_1_EXTENSION_NAME); |
| RETURN_IF_SKIP(Init()); |
| |
| VkImageCreateInfo image_create_info = vku::InitStructHelper(); |
| image_create_info.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; |
| image_create_info.imageType = VK_IMAGE_TYPE_3D; |
| image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM; |
| image_create_info.extent = {32u, 32u, 4u}; |
| image_create_info.mipLevels = 2u; |
| image_create_info.arrayLayers = 1u; |
| image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; |
| image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; |
| image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
| vkt::Image image(*m_device, image_create_info); |
| |
| VkImageMemoryBarrier image_memory_barrier = vku::InitStructHelper(); |
| image_memory_barrier.srcAccessMask = VK_ACCESS_NONE; |
| image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.image = image; |
| image_memory_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 2, 0, VK_REMAINING_ARRAY_LAYERS}; |
| |
| m_command_buffer.Begin(); |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, |
| nullptr, 1u, &image_memory_barrier); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, Transition3dImageWithMipLevelsRemainingArrayLayers) { |
| TEST_DESCRIPTION("https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/7483"); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_1_EXTENSION_NAME); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance9); |
| RETURN_IF_SKIP(Init()); |
| |
| VkImageCreateInfo image_create_info = vku::InitStructHelper(); |
| image_create_info.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; |
| image_create_info.imageType = VK_IMAGE_TYPE_3D; |
| image_create_info.format = VK_FORMAT_B8G8R8A8_UNORM; |
| image_create_info.extent = {32u, 32u, 4u}; |
| image_create_info.mipLevels = 2u; |
| image_create_info.arrayLayers = 1u; |
| image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; |
| image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; |
| image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
| vkt::Image image(*m_device, image_create_info); |
| |
| VkImageMemoryBarrier image_memory_barrier = vku::InitStructHelper(); |
| image_memory_barrier.srcAccessMask = VK_ACCESS_NONE; |
| image_memory_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| image_memory_barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| image_memory_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; |
| image_memory_barrier.image = image; |
| image_memory_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 2, 0, VK_REMAINING_ARRAY_LAYERS}; |
| |
| m_command_buffer.Begin(); |
| vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0u, 0u, nullptr, 0u, |
| nullptr, 1u, &image_memory_barrier); |
| m_command_buffer.End(); |
| } |
| |
| TEST_F(PositiveSyncObject, SetEvent2Flags) { |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance9); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| m_command_buffer.Begin(); |
| |
| VkMemoryBarrier2 memory_barrier = vku::InitStructHelper(); |
| |
| VkDependencyInfo dependency_info = vku::InitStructHelper(); |
| dependency_info.dependencyFlags = VK_DEPENDENCY_ASYMMETRIC_EVENT_BIT_KHR; |
| dependency_info.memoryBarrierCount = 1u; |
| dependency_info.pMemoryBarriers = &memory_barrier; |
| |
| vkt::Event event(*m_device); |
| vk::CmdSetEvent2(m_command_buffer, event, &dependency_info); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineSemaphoreAndExportedCopyCooperation) { |
| TEST_DESCRIPTION("Test that queue submission state is updated properly when using semaphores with shared payload"); |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| #else |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; |
| #endif |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(extension_name); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD"; |
| } |
| if (!m_second_queue) { |
| GTEST_SKIP() << "Two queues are needed"; |
| } |
| if (!SemaphoreExportImportSupported(Gpu(), VK_SEMAPHORE_TYPE_TIMELINE, handle_type)) { |
| GTEST_SKIP() << "Semaphore does not support export and import through opaque handle"; |
| } |
| |
| VkSemaphoreTypeCreateInfo semaphore_type_ci = vku::InitStructHelper(); |
| semaphore_type_ci.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(&semaphore_type_ci); |
| export_info.handleTypes = handle_type; |
| VkSemaphoreCreateInfo semaphore_ci = vku::InitStructHelper(&export_info); |
| |
| vkt::Semaphore semaphore(*m_device, semaphore_ci); |
| vkt::Semaphore import_semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| |
| ExternalHandle handle{}; |
| semaphore.ExportHandle(handle, handle_type); |
| import_semaphore.ImportHandle(handle, handle_type); |
| |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1)); |
| m_second_queue->Submit(vkt::no_cmd, vkt::TimelineWait(import_semaphore, 1), vkt::TimelineSignal(import_semaphore, 2)); |
| |
| // Wait until imported copy reaches value 2 |
| import_semaphore.Wait(2, kWaitTimeout); |
| |
| // This will update current value for original semaphore to 2. |
| semaphore.GetCounterValue(); |
| |
| // Test that quering semaphroe counter also processed signal=1 and we don't get |
| // an error that semaphore signaled with smaller value 1 than current value 2. |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, TimelineSemaphoreAndExportedCopyCooperation2) { |
| TEST_DESCRIPTION("Test that queue submission state is updated properly when using semaphores with shared payload"); |
| #ifdef VK_USE_PLATFORM_WIN32_KHR |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT; |
| #else |
| const auto extension_name = VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME; |
| const auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_FD_BIT; |
| #endif |
| SetTargetApiVersion(VK_API_VERSION_1_2); |
| AddRequiredExtensions(extension_name); |
| AddRequiredFeature(vkt::Feature::timelineSemaphore); |
| RETURN_IF_SKIP(Init()); |
| |
| if (IsPlatformMockICD()) { |
| GTEST_SKIP() << "Test not supported by MockICD"; |
| } |
| if (!m_second_queue) { |
| GTEST_SKIP() << "Two queues are needed"; |
| } |
| if (!SemaphoreExportImportSupported(Gpu(), VK_SEMAPHORE_TYPE_TIMELINE, handle_type)) { |
| GTEST_SKIP() << "Semaphore does not support export and import through opaque handle"; |
| } |
| |
| VkSemaphoreTypeCreateInfo semaphore_type_ci = vku::InitStructHelper(); |
| semaphore_type_ci.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE; |
| VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(&semaphore_type_ci); |
| export_info.handleTypes = handle_type; |
| VkSemaphoreCreateInfo semaphore_ci = vku::InitStructHelper(&export_info); |
| |
| vkt::Semaphore semaphore(*m_device, semaphore_ci); |
| vkt::Semaphore imported_semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE); |
| |
| ExternalHandle handle{}; |
| semaphore.ExportHandle(handle, handle_type); |
| imported_semaphore.ImportHandle(handle, handle_type); |
| |
| vkt::Fence fence(*m_device); |
| |
| const uint32_t N = 20; |
| for (uint32_t i = 0; i < N; i += 4) { |
| // q0: (w=0, s=1) (w=2, s=3) (w=4, s=5) ... |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(semaphore, i), vkt::TimelineSignal(semaphore, i + 1)); |
| m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(semaphore, i + 2), vkt::TimelineSignal(semaphore, i + 3), fence); |
| // q1: (w=1, s2) (w=3, s=4) (w=5, s=6) ... |
| m_second_queue->Submit(vkt::no_cmd, vkt::TimelineWait(imported_semaphore, i + 1), |
| vkt::TimelineSignal(imported_semaphore, i + 2)); |
| m_second_queue->Submit(vkt::no_cmd, vkt::TimelineWait(imported_semaphore, i + 3), |
| vkt::TimelineSignal(imported_semaphore, i + 4)); |
| |
| semaphore.GetCounterValue(); |
| fence.Wait(kWaitTimeout); |
| fence.Reset(); |
| } |
| m_device->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, ZeroInitializeLayoutSubresource) { |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredExtensions(VK_EXT_ZERO_INITIALIZE_DEVICE_MEMORY_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| AddRequiredFeature(vkt::Feature::zeroInitializeDeviceMemory); |
| RETURN_IF_SKIP(Init()); |
| |
| VkImageCreateInfo info = |
| vkt::Image::ImageCreateInfo2D(4, 4, 2, 2, VK_FORMAT_B8G8R8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); |
| vkt::Image image1(*m_device, info); |
| info.initialLayout = VK_IMAGE_LAYOUT_ZERO_INITIALIZED_EXT; |
| vkt::Image image2(*m_device, info); |
| |
| m_command_buffer.Begin(); |
| VkImageMemoryBarrier2 img_barrier = vku::InitStructHelper(); |
| img_barrier.image = image1; |
| img_barrier.srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| img_barrier.dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT; |
| img_barrier.oldLayout = VK_IMAGE_LAYOUT_ZERO_INITIALIZED_EXT; |
| img_barrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| |
| img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 2, 0, 2}; |
| m_command_buffer.Barrier(img_barrier); |
| |
| img_barrier.image = image2; |
| img_barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}; |
| m_command_buffer.Barrier(img_barrier); |
| } |
| |
| TEST_F(PositiveSyncObject, AsymmetricWaitEvent2) { |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance9); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| VkMemoryBarrier2 barrier = vku::InitStructHelper(); |
| barrier.srcStageMask = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; |
| |
| VkDependencyInfo dependency_info = vku::InitStructHelper(); |
| dependency_info.dependencyFlags = VK_DEPENDENCY_ASYMMETRIC_EVENT_BIT_KHR; |
| dependency_info.memoryBarrierCount = 1u; |
| dependency_info.pMemoryBarriers = &barrier; |
| |
| const vkt::Event event(*m_device); |
| |
| m_command_buffer.Begin(); |
| |
| vk::CmdSetEvent2(m_command_buffer, event, &dependency_info); |
| |
| barrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; |
| barrier.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; |
| vk::CmdWaitEvents2(m_command_buffer, 1, &event.handle(), &dependency_info); |
| |
| m_command_buffer.End(); |
| |
| m_default_queue->Submit(m_command_buffer); |
| m_default_queue->Wait(); |
| } |
| |
| TEST_F(PositiveSyncObject, Maintenance9ImageBarriers) { |
| TEST_DESCRIPTION("https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10302"); |
| SetTargetApiVersion(VK_API_VERSION_1_3); |
| AddRequiredExtensions(VK_KHR_MAINTENANCE_9_EXTENSION_NAME); |
| AddRequiredFeature(vkt::Feature::maintenance9); |
| AddRequiredFeature(vkt::Feature::synchronization2); |
| RETURN_IF_SKIP(Init()); |
| |
| vkt::Buffer buffer(*m_device, 32 * 32 * 4 * 4, VK_BUFFER_USAGE_TRANSFER_DST_BIT); |
| |
| VkImageCreateInfo image_ci = vku::InitStructHelper(); |
| image_ci.flags = VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; |
| image_ci.imageType = VK_IMAGE_TYPE_3D; |
| image_ci.format = VK_FORMAT_R8G8B8A8_UNORM; |
| image_ci.extent = {32, 32, 4}; |
| image_ci.mipLevels = 1; |
| image_ci.arrayLayers = 1; |
| image_ci.samples = VK_SAMPLE_COUNT_1_BIT; |
| image_ci.tiling = VK_IMAGE_TILING_OPTIMAL; |
| image_ci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; |
| image_ci.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; |
| vkt::Image image(*m_device, image_ci); |
| |
| VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper(); |
| layout_transition.srcStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; |
| layout_transition.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; |
| layout_transition.dstStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT; |
| layout_transition.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; |
| layout_transition.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; |
| layout_transition.newLayout = VK_IMAGE_LAYOUT_GENERAL; |
| layout_transition.image = image; |
| layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1}; // set layers later |
| |
| m_command_buffer.Begin(); |
| // Transition slice 1 |
| layout_transition.subresourceRange.baseArrayLayer = 1; |
| layout_transition.subresourceRange.layerCount = 1; |
| m_command_buffer.Barrier(layout_transition); |
| |
| // Copy from slice 1 |
| VkBufferImageCopy copy_region{}; |
| copy_region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; |
| copy_region.imageOffset = {0, 0, 1}; |
| copy_region.imageExtent = {32, 32, 1}; |
| vk::CmdCopyImageToBuffer(m_command_buffer, image, VK_IMAGE_LAYOUT_GENERAL, buffer, 1, ©_region); |
| |
| // Transition slice 0 |
| layout_transition.subresourceRange.baseArrayLayer = 0; |
| layout_transition.subresourceRange.layerCount = 1; |
| m_command_buffer.Barrier(layout_transition); |
| m_command_buffer.End(); |
| } |