blob: 47df650e761935e3653624905be0940248d6b6b1 [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 <thread>
#include "../framework/layer_validation_tests.h"
#include "../framework/external_memory_sync.h"
struct PositiveSyncValTimelineSemaphore : public VkSyncValTest {};
TEST_F(PositiveSyncValTimelineSemaphore, WaitInitialValue) {
TEST_DESCRIPTION("Wait on the initial value");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE, 1);
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, 1));
m_default_queue->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignal) {
TEST_DESCRIPTION("Signal then wait for signaled 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));
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_default_queue->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignalSync1) {
TEST_DESCRIPTION("Signal then wait for signaled 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->Submit(m_command_buffer, vkt::TimelineSignal(semaphore, 1));
m_default_queue->Submit(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_default_queue->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignalTwoQueues) {
TEST_DESCRIPTION("Signal then wait for signaled 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));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignalSmallerValue) {
TEST_DESCRIPTION("Signal a value then wait for a smaller 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, 2));
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_default_queue->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignalSmallerValueTwoQueues) {
TEST_DESCRIPTION("Signal a value then wait for a smaller 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, 2));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignalNonDefaultStage) {
TEST_DESCRIPTION("Signal and wait with specific synchronization scopes");
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, 3, VK_PIPELINE_STAGE_2_COPY_BIT));
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1, VK_PIPELINE_STAGE_2_COPY_BIT));
m_default_queue->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignalNonDefaultStage2) {
TEST_DESCRIPTION("Signal and wait with specific synchronization scopes");
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, 3, VK_PIPELINE_STAGE_2_COPY_BIT));
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1, VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT));
m_default_queue->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitAfterSignalNonDefaultStageTwoQueues) {
TEST_DESCRIPTION("Signal and wait with specific synchronization scopes");
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, 3, VK_PIPELINE_STAGE_2_COPY_BIT));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 1, VK_PIPELINE_STAGE_2_COPY_BIT));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitBeforeSignal) {
TEST_DESCRIPTION("Wait on the first queue then signal from a different 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));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineSignal(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitBeforeSignalSync1) {
TEST_DESCRIPTION("Wait on the first queue then signal from a different 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->Submit(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_second_queue->Submit(m_second_command_buffer, vkt::TimelineSignal(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitSmallerValueBeforeSignal) {
TEST_DESCRIPTION("Wait for a value on the first queue then signal a larger value from a different 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));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineSignal(semaphore, 2));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitBeforeSignalNonDefaultStage) {
TEST_DESCRIPTION("Signal and wait with specific synchronization scopes");
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, VK_PIPELINE_STAGE_2_COPY_BIT));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineSignal(semaphore, 3, VK_PIPELINE_STAGE_2_COPY_BIT));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitBeforeSignalNonDefaultStage2) {
TEST_DESCRIPTION("Signal and wait with specific synchronization scopes");
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, VK_PIPELINE_STAGE_2_COPY_BIT));
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineSignal(semaphore, 3, VK_PIPELINE_STAGE_2_ALL_TRANSFER_BIT));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitLatestSignal) {
TEST_DESCRIPTION("Check that resolving signal is determined correctly");
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, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT));
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 2)); // includes all stages
// If due to regression signal=1 resolves this wait then it should generate a WAW hazard due to stage mask mismatch
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 2));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitLatestSignalTwoQueues) {
TEST_DESCRIPTION("Check that resolving signal is determined correctly");
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, VK_PIPELINE_STAGE_VERTEX_SHADER_BIT));
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 2)); // includes all stages
// If due to regression signal=1 resolves this wait then it should generate a WAW hazard due to stage mask mismatch
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(semaphore, 2));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, QueuesCollaborateToResolveEachOthersWait) {
TEST_DESCRIPTION("Two queues resolve wait-before-signal for one another");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Semaphore first_queue_wait(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
vkt::Semaphore second_queue_wait(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(second_queue_wait, 1));
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(first_queue_wait, 1));
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(second_queue_wait, 1));
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(first_queue_wait, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, SignalResolvesTwoWaits) {
TEST_DESCRIPTION("Signal resolves two wait-before-signal waits");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_third_queue) {
GTEST_SKIP() << "Three queues are needed";
}
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, 1));
m_third_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, 1));
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, SignalResolvesTwoWaits2) {
TEST_DESCRIPTION("Signal resolves two waits");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
semaphore.Signal(1);
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, 1));
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, SyncSubmitsWithSingleSemaphore) {
// TODO: existing implementation releases QBC objects and removes signals properly but vvl::Semaphore state
// objects accumulate timepoints. Investigate if it's possible to safely release timepoint in vvl::Semaphore
// or if should be special customization of vvl::Semaphore for syncval purposes, or maybe some other option.
TEST_DESCRIPTION("This test helps to check that implementation releases SignalInfo and QueueBatchContexts objects");
RETURN_IF_SKIP(InitTimelineSemaphore());
// This number can be increased to monitor memory usage.
// NOTE: currently memory usage increases due to vvl::Semaphore behavior mentioned above.
const int N = 100;
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);
for (int i = 1; i <= N; i++) {
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, i - 1), vkt::TimelineSignal(semaphore, i));
}
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, FrameSynchronization) {
TEST_DESCRIPTION("The host waits for frame completion by waiting on timeline semaphore");
RETURN_IF_SKIP(InitTimelineSemaphore());
const int N = 100;
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);
// Due to synchronization the copies from different frames do not collide
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);
for (int i = 1; i <= N; i++) {
m_default_queue->Submit2(m_command_buffer, vkt::TimelineSignal(semaphore, i));
semaphore.Wait(i, vvl::kU64Max);
}
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, FrameSynchronization2) {
TEST_DESCRIPTION("The host waits for frame completion by polling semaphore counter value");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (IsPlatformMockICD()) {
GTEST_SKIP() << "Test not supported by MockICD (requires counter from vkGetSemaphoreCounterValue)";
}
const int N = 100;
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);
// Due to synchronization the copies from different frames do not collide
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);
for (int i = 1; i <= N; i++) {
m_default_queue->Submit2(m_command_buffer, vkt::TimelineSignal(semaphore, i));
while (semaphore.GetCounterValue() != i)
;
}
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, HostSignalAndWait) {
TEST_DESCRIPTION("Signal on the host and then wait on the host");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
semaphore.Signal(1);
semaphore.Wait(1, kWaitTimeout);
}
TEST_F(PositiveSyncValTimelineSemaphore, HostSignal) {
TEST_DESCRIPTION("Host signal finishes device wait");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, 1));
semaphore.Signal(1);
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, HostWaitWait) {
TEST_DESCRIPTION("Wait for semaphore one more time on the host");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, 1));
semaphore.Wait(1, kWaitTimeout);
// Test that the second wait does not cause any issues.
semaphore.Wait(1, kWaitTimeout);
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, HostSignalSignal) {
TEST_DESCRIPTION("Signal semaphore two times in a row from the host");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
semaphore.Signal(1);
// Test that the second signal does not cause any issues.
semaphore.Signal(2);
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, 1));
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, HostWaitEmptyBatch) {
TEST_DESCRIPTION("Check that host wait for an empty batch correclty synchronizes with the previous batches");
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];
submits[0] = vku::InitStructHelper();
submits[0].commandBufferInfoCount = 1;
submits[0].pCommandBufferInfos = &cbuf_info;
// The second batch does not have command buffers and just signals the semaphore.
// The test checks that waiting on the semaphore from such an empty batch works as expected.
submits[1] = vku::InitStructHelper();
submits[1].signalSemaphoreInfoCount = 1;
submits[1].pSignalSemaphoreInfos = &semaphore_info;
vk::QueueSubmit2(*m_default_queue, 2, submits, VK_NULL_HANDLE);
semaphore.Wait(1, kWaitTimeout);
// The wait should synchronize writes to buffer_b, so it's safe to copy again
m_second_queue->Submit2(m_second_command_buffer);
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, KhronosTimelineSemaphoreExample) {
TEST_DESCRIPTION("https://www.khronos.org/blog/vulkan-timeline-semaphores");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (IsPlatformMockICD()) {
GTEST_SKIP() << "Test not supported by MockICD (synchronization dependencies)";
}
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Buffer buffer_a(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
vkt::Buffer buffer_b(*m_device, 256, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, kHostVisibleMemProps);
if (!buffer_b.Memory().initialized()) {
GTEST_SKIP() << "Can't allocate host visible/coherent memory";
}
uint8_t* bytes = static_cast<uint8_t*>(buffer_b.Memory().Map());
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_b, buffer_a);
m_second_command_buffer.End();
vkt::Semaphore timeline(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
auto thread1 = [this, &timeline]() {
const uint64_t wait_value_1 = 0; // No-op wait. Value is always >= 0.
const uint64_t signal_value_1 = 5; // Unblock thread2's CPU work.
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(timeline, wait_value_1),
vkt::TimelineSignal(timeline, signal_value_1));
};
auto thread2 = [&timeline, &bytes]() {
// Wait for thread1's device work to complete.
const uint64_t wait_value_2 = 4;
timeline.Wait(wait_value_2, kWaitTimeout);
// Increment everything
for (int i = 0; i < 256; i++, bytes++) {
++(*bytes);
}
// Unblock thread3's device work.
timeline.Signal(7);
};
auto thread3 = [this, &timeline]() {
const uint64_t wait_value_3 = 7; // Wait for thread2's CPU work to complete.
const uint64_t signal_value_3 = 8; // Signal completion of all work.
m_second_queue->Submit2(m_second_command_buffer, vkt::TimelineWait(timeline, wait_value_3),
vkt::TimelineSignal(timeline, signal_value_3));
};
std::thread t1(thread1);
std::thread t2(thread2);
std::thread t3(thread3);
timeline.Wait(8, kWaitTimeout);
t3.join();
t2.join();
t1.join();
}
TEST_F(PositiveSyncValTimelineSemaphore, ResolveManyWaitBeforeSignals) {
TEST_DESCRIPTION("Check for regression when resolved wait-before-signal did not cleanup older signals");
RETURN_IF_SKIP(InitTimelineSemaphore());
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
// This test is mostly for manual observation of memory usage.
// Increase constant value to get longer running time.
uint32_t N = 1000;
for (uint32_t i = 0; i < N; i++) {
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineWait(semaphore, i + 1));
m_second_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, i + 1));
m_device->Wait();
}
}
#ifdef VK_USE_PLATFORM_WIN32_KHR
TEST_F(PositiveSyncValTimelineSemaphore, ExternalSemaphoreWaitBeforeSignal) {
TEST_DESCRIPTION("Wait-before-signal with external semaphore should not hoard resources");
AddRequiredExtensions(VK_KHR_EXTERNAL_SEMAPHORE_WIN32_EXTENSION_NAME);
RETURN_IF_SKIP(InitTimelineSemaphore());
if (IsPlatformMockICD()) {
GTEST_SKIP() << "Test not supported by MockICD";
}
if (!m_second_queue) {
GTEST_SKIP() << "Two queues are needed";
}
constexpr auto handle_type = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_OPAQUE_WIN32_BIT;
if (!SemaphoreExportImportSupported(Gpu(), handle_type)) {
GTEST_SKIP() << "Semaphore does not support export and import through Win32 handle";
}
VkSemaphoreTypeCreateInfo semaphore_type_ci = vku::InitStructHelper();
semaphore_type_ci.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
semaphore_type_ci.initialValue = 0;
VkExportSemaphoreCreateInfo export_info = vku::InitStructHelper(&semaphore_type_ci);
export_info.handleTypes = handle_type;
const VkSemaphoreCreateInfo export_semaphore_ci = vku::InitStructHelper(&export_info);
vkt::Semaphore export_semaphore(*m_device, export_semaphore_ci);
vkt::Semaphore import_semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
HANDLE win32_handle = NULL;
export_semaphore.ExportHandle(win32_handle, handle_type);
import_semaphore.ImportHandle(win32_handle, handle_type);
// This test is for manual inspection. Without special handling the wait-before-signal with an
// external semaphore accumulates unresolved batches and registered signals (it's not possible
// to track external signal). Check that the list of unresolved batches and signals does not grow.
const int N = 100;
for (int i = 1; i <= N; i++) {
m_default_queue->Submit(vkt::no_cmd, vkt::TimelineWait(export_semaphore, i));
m_second_queue->Submit(vkt::no_cmd, vkt::TimelineSignal(import_semaphore, i));
m_device->Wait();
}
}
#endif // VK_USE_PLATFORM_WIN32_KHR
TEST_F(PositiveSyncValTimelineSemaphore, QueueWaitIdleRemovesSignals) {
TEST_DESCRIPTION("Test for manual inspection of registered signals (VK_SYNCVAL_SHOW_STATS can be used)");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
const uint32_t N = 100;
for (uint32_t i = 1; i <= N; i++) {
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, i));
// The maximum number of registered signals will be around 25
if (i % 25 == 0) {
m_default_queue->Wait();
}
}
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, DeviceWaitIdleRemovesignals) {
TEST_DESCRIPTION("Test for manual inspection of registered signals (VK_SYNCVAL_SHOW_STATS can be used)");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
const uint32_t N = 100;
for (uint32_t i = 1; i <= N; i++) {
m_default_queue->Submit2(vkt::no_cmd, vkt::TimelineSignal(semaphore, i));
// The maximum number of registered signals will be around 50
if (i % 50 == 0) {
m_device->Wait();
}
}
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, ManyOrphanedSignals) {
TEST_DESCRIPTION("Test limit of maximum number of registered signals per timeline");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
vkt::Semaphore binary_semaphore(*m_device);
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();
VkCommandBufferSubmitInfo cbuf_info = vku::InitStructHelper();
cbuf_info.commandBuffer = m_command_buffer;
VkSemaphoreSubmitInfo wait = vku::InitStructHelper();
wait.semaphore = binary_semaphore;
wait.stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
VkSemaphoreSubmitInfo signals[2];
// binary signal
signals[0] = vku::InitStructHelper();
signals[0].semaphore = binary_semaphore;
signals[0].stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
// timeline signal (value is set for each loop iteration)
signals[1] = vku::InitStructHelper();
signals[1].semaphore = semaphore;
signals[1].stageMask = VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT;
const uint32_t N = 1'000;
for (uint32_t i = 1; i <= N; i++) {
// signal timeline on each iteration but never wait for it
signals[1].value = i;
VkSubmitInfo2 submit = vku::InitStructHelper();
if (i > 1) {
// wait binary signal from previous iteration
submit.waitSemaphoreInfoCount = 1;
submit.pWaitSemaphoreInfos = &wait;
}
submit.commandBufferInfoCount = 1;
submit.pCommandBufferInfos = &cbuf_info;
submit.signalSemaphoreInfoCount = 2;
submit.pSignalSemaphoreInfos = signals;
vk::QueueSubmit2(m_default_queue->handle(), 1, &submit, VK_NULL_HANDLE);
}
m_device->Wait();
}
TEST_F(PositiveSyncValTimelineSemaphore, WaitForFencesWithTimelineSignalBatches) {
TEST_DESCRIPTION("Check that WaitForFences applies tagged waits to timeline signal batches");
RETURN_IF_SKIP(InitTimelineSemaphore());
vkt::Semaphore semaphore(*m_device, VK_SEMAPHORE_TYPE_TIMELINE);
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::Fence fence(*m_device);
// The first batch context.
// Specify VERTEX_SHADER signal scope, so waiting for timeline signal does not protect buffer copy
m_default_queue->Submit2(m_command_buffer, vkt::TimelineSignal(semaphore, 1, VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT), fence);
// The second batch context which imports the first one.
// The timeline signal in the first submit still references the first batch.
m_default_queue->Submit2(vkt::no_cmd);
// The original omission was that iteration over all batch context did not take into account
// batches associated with timeline signals. In the case of regression accesses in the first
// batch (stored in timeline signal) will survive the fence wait.
fence.Wait(kWaitTimeout);
// Waiting for timeline signal imports the first batch stored in that signal.
// In case of regression the first batch will contain unprotected copy writes and
// this will cause WRITE-AFTER-WRITE hazard.
m_default_queue->Submit2(m_command_buffer, vkt::TimelineWait(semaphore, 1));
m_default_queue->Wait();
}