blob: d9eed67bf4f5dcf28c21e4b445279c00b01f872d [file] [log] [blame]
/* Copyright (c) 2024 The Khronos Group Inc.
* Copyright (c) 2024 Valve Corporation
* Copyright (c) 2024 LunarG, 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "../framework/layer_validation_tests.h"
struct NegativeSyncValWsi : public VkSyncValTest {};
// Wrap FAIL:
// * DRY for common messages
// * for test stability reasons sometimes cleanup code is required *prior* to the return hidden in FAIL
// * result_arg_ *can* (should) have side-effect, but is referenced exactly once
// * label_ must be converitble to bool, and *should* *not* have side-effects
// * "{}" or ";" are valid clean_ values for noop
#define REQUIRE_SUCCESS(result_arg_, label_) \
{ \
const VkResult result_ = (result_arg_); \
if (result_ != VK_SUCCESS) { \
{ \
m_device->Wait(); \
} \
if (bool(label_)) { \
FAIL() << string_VkResult(result_) << ": " << (label_); \
} else { \
FAIL() << string_VkResult(result_); \
} \
} \
}
TEST_F(NegativeSyncValWsi, PresentAcquire) {
TEST_DESCRIPTION("Try destroying a swapchain presentable image with vkDestroyImage");
AddSurfaceExtension();
RETURN_IF_SKIP(InitSyncValFramework());
RETURN_IF_SKIP(InitState());
RETURN_IF_SKIP(InitSwapchain());
const std::vector<VkImage> images = m_swapchain.GetImages();
std::vector<bool> image_used(images.size(), false);
vkt::Fence fence(*m_device);
// Loop through the indices until we find one we are reusing...
// When fence is non-null this can timeout so we need to track results
// Acquire can always timeout, so we need to track results
auto acquire_used_image_semaphore = [this, &image_used](const vkt::Semaphore& sem, uint32_t& index) {
VkResult result = VK_SUCCESS;
while (true) {
index = m_swapchain.AcquireNextImage(sem, kWaitTimeout, &result);
if ((result != VK_SUCCESS) || image_used[index]) break;
result = m_default_queue->Present(m_swapchain, index, sem);
if (result != VK_SUCCESS) break;
image_used[index] = true;
}
return result;
};
auto acquire_used_image_fence = [this, &image_used](const vkt::Fence& fence, uint32_t& index) {
VkResult result = VK_SUCCESS;
while (true) {
index = m_swapchain.AcquireNextImage(fence, kWaitTimeout, &result);
if ((result != VK_SUCCESS) || image_used[index]) break;
result = fence.Wait(kWaitTimeout);
fence.Reset();
m_default_queue->Present(m_swapchain, index, vkt::no_semaphore);
if (result != VK_SUCCESS) break;
image_used[index] = true;
}
return result;
};
auto write_barrier_cb = [this](const VkImage h_image, VkImageLayout from, VkImageLayout to) {
VkImageSubresourceRange full_image{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkImageMemoryBarrier image_barrier = vku::InitStructHelper();
image_barrier.srcAccessMask = 0U;
image_barrier.dstAccessMask = 0U;
image_barrier.oldLayout = from;
image_barrier.newLayout = to;
image_barrier.image = h_image;
image_barrier.subresourceRange = full_image;
m_command_buffer.Begin();
vk::CmdPipelineBarrier(m_command_buffer.handle(), VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
nullptr, 0, nullptr, 1, &image_barrier);
m_command_buffer.End();
};
// Transition swapchain images to PRESENT_SRC layout for presentation
for (VkImage image : images) {
write_barrier_cb(image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
m_default_queue->Submit(m_command_buffer);
m_device->Wait();
m_command_buffer.Reset();
}
uint32_t acquired_index = 0;
REQUIRE_SUCCESS(acquire_used_image_fence(fence, acquired_index), "acquire_used_image");
write_barrier_cb(images[acquired_index], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
// Look for errors between the acquire and first use...
// No sync operations...
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-AFTER-PRESENT");
m_default_queue->Submit(m_command_buffer);
m_errorMonitor->VerifyFound();
// Sync operations that should ignore present operations
m_device->Wait();
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-AFTER-PRESENT");
m_default_queue->Submit(m_command_buffer);
m_errorMonitor->VerifyFound();
// Finally we wait for the fence associated with the acquire
REQUIRE_SUCCESS(vk::WaitForFences(m_device->handle(), 1, &fence.handle(), VK_TRUE, kWaitTimeout), "WaitForFences");
fence.Reset();
m_default_queue->Submit(m_command_buffer);
m_device->Wait();
// Release the image back to the present engine, so we don't run out
m_default_queue->Present(m_swapchain, acquired_index, vkt::no_semaphore); // present without fence can't timeout
vkt::Semaphore sem(*m_device);
REQUIRE_SUCCESS(acquire_used_image_semaphore(sem, acquired_index), "acquire_used_image");
m_command_buffer.Reset();
write_barrier_cb(images[acquired_index], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
// The wait mask doesn't match the operations in the command buffer
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-AFTER-READ");
m_default_queue->Submit(m_command_buffer, vkt::Wait(sem, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT));
m_errorMonitor->VerifyFound();
// Now then wait mask matches the operations in the command buffer
m_default_queue->Submit(m_command_buffer, vkt::Wait(sem, VK_PIPELINE_STAGE_TRANSFER_BIT));
// Try presenting without waiting for the ILT to finish
m_errorMonitor->SetDesiredError("SYNC-HAZARD-PRESENT-AFTER-WRITE");
m_default_queue->Present(m_swapchain, acquired_index, vkt::no_semaphore); // present without fence can't timeout
m_errorMonitor->VerifyFound();
// Let the ILT complete, and the release the image back
m_device->Wait();
m_default_queue->Present(m_swapchain, acquired_index, vkt::no_semaphore); // present without fence can't timeout
REQUIRE_SUCCESS(acquire_used_image_fence(fence, acquired_index), "acquire_used_index");
REQUIRE_SUCCESS(fence.Wait(kWaitTimeout), "WaitForFences");
m_command_buffer.Reset();
write_barrier_cb(images[acquired_index], VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
fence.Reset();
m_default_queue->Submit(m_command_buffer, vkt::Signal(sem));
m_errorMonitor->SetDesiredError("SYNC-HAZARD-PRESENT-AFTER-WRITE");
m_default_queue->Present(m_swapchain, acquired_index, vkt::no_semaphore); // present without fence can't timeout
m_errorMonitor->VerifyFound();
m_default_queue->Present(m_swapchain, acquired_index, sem); // present without fence can't timeout
m_device->Wait();
}
// TODO: https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5240
TEST_F(NegativeSyncValWsi, SubmitDoesNotWaitForAcquire) {
TEST_DESCRIPTION("Submit does not wait for the swapchain acquire semaphore");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddSurfaceExtension();
AddRequiredFeature(vkt::Feature::synchronization2);
RETURN_IF_SKIP(InitSyncVal());
RETURN_IF_SKIP(InitSwapchain());
const vkt::Semaphore acquire_semaphore(*m_device);
const auto swapchain_images = m_swapchain.GetImages();
const uint32_t image_index = m_swapchain.AcquireNextImage(acquire_semaphore, kWaitTimeout);
VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
layout_transition.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
layout_transition.srcAccessMask = 0;
layout_transition.dstStageMask = VK_PIPELINE_STAGE_2_NONE;
layout_transition.dstAccessMask = 0;
layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
layout_transition.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
layout_transition.image = swapchain_images[image_index];
layout_transition.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkDependencyInfo dep_info = vku::InitStructHelper();
dep_info.imageMemoryBarrierCount = 1;
dep_info.pImageMemoryBarriers = &layout_transition;
m_command_buffer.Begin();
vk::CmdPipelineBarrier2(m_command_buffer, &dep_info);
m_command_buffer.End();
// TODO: current implementation does not report that the image is still being read by the acquire
// and at the same time it is being transitioned (there is no wait on the acquire semaphore).
// Fix this and ensure the following submit triggers validation error.
m_default_queue->Submit2(m_command_buffer);
m_device->Wait();
}
TEST_F(NegativeSyncValWsi, PresentDoesNotWaitForSubmit2) {
TEST_DESCRIPTION("Present does not specify semaphore to wait for submit.");
SetTargetApiVersion(VK_API_VERSION_1_3);
AddSurfaceExtension();
AddRequiredFeature(vkt::Feature::synchronization2);
RETURN_IF_SKIP(InitSyncValFramework());
RETURN_IF_SKIP(InitState());
RETURN_IF_SKIP(InitSwapchain());
const vkt::Semaphore acquire_semaphore(*m_device);
const vkt::Semaphore submit_semaphore(*m_device);
const auto swapchain_images = m_swapchain.GetImages();
const uint32_t image_index = m_swapchain.AcquireNextImage(acquire_semaphore, kWaitTimeout);
VkImageMemoryBarrier2 layout_transition = vku::InitStructHelper();
layout_transition.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
layout_transition.srcAccessMask = 0;
layout_transition.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
layout_transition.dstAccessMask = 0;
layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
layout_transition.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
layout_transition.image = swapchain_images[image_index];
layout_transition.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
layout_transition.subresourceRange.baseMipLevel = 0;
layout_transition.subresourceRange.levelCount = 1;
layout_transition.subresourceRange.baseArrayLayer = 0;
layout_transition.subresourceRange.layerCount = 1;
VkDependencyInfo dep_info = vku::InitStructHelper();
dep_info.imageMemoryBarrierCount = 1;
dep_info.pImageMemoryBarriers = &layout_transition;
m_command_buffer.Begin();
vk::CmdPipelineBarrier2(m_command_buffer, &dep_info);
m_command_buffer.End();
m_default_queue->Submit2(m_command_buffer, vkt::Wait(acquire_semaphore, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT),
vkt::Signal(submit_semaphore, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT));
// DO NOT wait for submit. This should generate present after write (ILT) harard.
m_errorMonitor->SetDesiredError("SYNC-HAZARD-PRESENT-AFTER-WRITE");
m_default_queue->Present(m_swapchain, image_index, vkt::no_semaphore);
m_errorMonitor->VerifyFound();
m_device->Wait();
}
TEST_F(NegativeSyncValWsi, PresentDoesNotWaitForSubmit) {
TEST_DESCRIPTION("Present does not specify semaphore to wait for submit.");
AddSurfaceExtension();
RETURN_IF_SKIP(InitSyncValFramework());
RETURN_IF_SKIP(InitState());
RETURN_IF_SKIP(InitSwapchain());
const vkt::Semaphore acquire_semaphore(*m_device);
const vkt::Semaphore submit_semaphore(*m_device);
const auto swapchain_images = m_swapchain.GetImages();
const uint32_t image_index = m_swapchain.AcquireNextImage(acquire_semaphore, kWaitTimeout);
VkImageMemoryBarrier layout_transition = vku::InitStructHelper();
layout_transition.srcAccessMask = 0;
layout_transition.dstAccessMask = 0;
layout_transition.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
layout_transition.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
layout_transition.image = swapchain_images[image_index];
layout_transition.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
layout_transition.subresourceRange.baseMipLevel = 0;
layout_transition.subresourceRange.levelCount = 1;
layout_transition.subresourceRange.baseArrayLayer = 0;
layout_transition.subresourceRange.layerCount = 1;
m_command_buffer.Begin();
vk::CmdPipelineBarrier(m_command_buffer, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &layout_transition);
m_command_buffer.End();
m_default_queue->Submit(m_command_buffer, vkt::Wait(acquire_semaphore, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT),
vkt::Signal(submit_semaphore));
// DO NOT wait for submit. This should generate present after write (ILT) harard.
m_errorMonitor->SetDesiredError("SYNC-HAZARD-PRESENT-AFTER-WRITE");
m_default_queue->Present(m_swapchain, image_index, vkt::no_semaphore);
m_errorMonitor->VerifyFound();
m_device->Wait();
}