blob: acc8ba5899352a37a2910d1de6de5d596c7d996b [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 NegativeSyncValTimelineSemaphore : public VkSyncValTest {};
TEST_F(NegativeSyncValTimelineSemaphore, WaitInitialValue) {
TEST_DESCRIPTION("Wait on the initial value");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin(VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT);
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineSignal(semaphore, 1));
// Wait on the initial value 0 does not synchronize with signal 1
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-AFTER-WRITE");
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 0));
m_errorMonitor->VerifyFound();
m_default_queue->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, WaitInitialValueTwoQueues) {
TEST_DESCRIPTION("Wait on the initial value");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineSignal(semaphore, 1));
// Wait on the initial value 0 does not synchronize with signal 1
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 0));
m_errorMonitor->VerifyFound();
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, WaitAfterSignalStageMismatch) {
TEST_DESCRIPTION("Hazard due to stage mask mismatch");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin(VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT);
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
// Signal first scope includes only CLEAR commands but not COPY commands
m_default_queue->Submit2(m_command_buffer, vkt::TimelineSignal(semaphore, 2, VK_PIPELINE_STAGE_2_CLEAR_BIT));
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-AFTER-WRITE");
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1, VK_PIPELINE_STAGE_2_COPY_BIT));
m_errorMonitor->VerifyFound();
m_default_queue->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, WaitBeforeSignal) {
TEST_DESCRIPTION("Signal from the second queue resolves wait on the main queue");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
// Resolve waint on the main queue
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
// Writes on the second queue collide with writes on the main queue
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
m_second_queue->Submit2(m_second_command_buffer);
m_errorMonitor->VerifyFound();
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, WaitBeforeSignalBatchFollowedByOneMoreBatch) {
TEST_DESCRIPTION("Hazard between wait-before-signal batch and the next batch on the same queue");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin(VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT);
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-AFTER-WRITE");
m_default_queue->Submit2(m_command_buffer);
m_errorMonitor->VerifyFound();
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, WaitBeforeSignalEmptyWaitScope) {
TEST_DESCRIPTION("Hazard due to stage mask mismatch");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
// Buffer copy starts immediately because VERTEX_SHADER stage does not include copy operation
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT));
// TODO: this should be detected as WRITE-RACING-WRITE error
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-AFTER-WRITE");
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineSignal(semaphore, 1));
m_errorMonitor->VerifyFound();
// Unblock the first queue (the previous call due to error did not reach Record phase)
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, WaitBeforeSignalAfterNoDepsBatch) {
TEST_DESCRIPTION("Batch without dependencies is followed by wait-before-signal batch");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
VkCommandBufferSubmitInfo cbuf_info = vku::InitStructHelper();
cbuf_info.commandBuffer = m_command_buffer;
VkSemaphoreSubmitInfo semaphore_info = vku::InitStructHelper();
semaphore_info.semaphore = semaphore;
semaphore_info.value = 1;
semaphore_info.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
VkSubmitInfo2 submits[2];
// the first batch is a batch with a copy job
submits[0] = vku::InitStructHelper();
submits[0].commandBufferInfoCount = 1;
submits[0].pCommandBufferInfos = &cbuf_info;
// the second batch is a wait-before-signal batch
submits[1] = vku::InitStructHelper();
submits[1].waitSemaphoreInfoCount = 1;
submits[1].pWaitSemaphoreInfos = &semaphore_info;
vk::QueueSubmit2(*m_default_queue, 2, submits, VK_NULL_HANDLE);
// Submit signal to resolve the wait
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
// Submit one more copy to collide with a copy on the default queue
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_errorMonitor->VerifyFound();
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, HostSignal) {
TEST_DESCRIPTION("Host signal breaks synchronization between two submits");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
// Finish the previous wait, so both submits can run in parallel
semaphore.Signal(1);
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineSignal(semaphore, 2));
m_errorMonitor->VerifyFound();
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, SignalResolvesTwoWaits) {
TEST_DESCRIPTION("One signal resolves two wait-before-signal waits");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
semaphore.Signal(1);
m_errorMonitor->VerifyFound();
// Unblock queue threads (the previous call did not reach Record phase)
semaphore.Signal(1);
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, SignalResolvesTwoWaits2) {
TEST_DESCRIPTION("One signal resolves two wait-before-signal waits");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 2));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
semaphore.Signal(2);
m_errorMonitor->VerifyFound();
// Unblock queue threads (the previous call did not reach Record phase)
semaphore.Signal(2);
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, SignalResolvesTwoWaits3) {
TEST_DESCRIPTION("One signal resolves two wait-before-signal waits");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_third_queue) {
GTEST_SKIP() << "Three queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
m_third_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
m_errorMonitor->VerifyFound();
// Unblock queue threads (the previous call did not reach Record phase)
m_third_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
m_device->Wait();
}
TEST_F(NegativeSyncValTimelineSemaphore, SignalResolvesTwoWaits4) {
TEST_DESCRIPTION("One signal resolves two waits");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_DST_BIT);
m_command_buffer.Begin();
m_command_buffer.Copy(buffer_a, buffer_b);
m_command_buffer.End();
m_second_command_buffer.Begin();
m_second_command_buffer.Copy(buffer_a, buffer_b);
m_second_command_buffer.End();
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
semaphore.Signal(1);
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_errorMonitor->SetDesiredError("SYNC-HAZARD-WRITE-RACING-WRITE");
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_errorMonitor->VerifyFound();
m_device->Wait();
}