blob: 81ba93149a246add2b497ce28b15da6ec809a828 [file] [log] [blame]
/* Copyright (c) 2015-2026 The Khronos Group Inc.
* Copyright (c) 2015-2026 Valve Corporation
* Copyright (c) 2015-2026 LunarG, Inc.
* Copyright (C) 2015-2024 Google Inc.
* Modifications Copyright (C) 2020 Advanced Micro Devices, Inc. All rights reserved.
*
* 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 "state_tracker/fence_state.h"
#include "state_tracker/queue_state.h"
#include "state_tracker/state_tracker.h"
#include "state_tracker/wsi_state.h"
static VkExternalFenceHandleTypeFlags GetExportHandleTypes(const VkFenceCreateInfo *info) {
auto export_info = vku::FindStructInPNextChain<VkExportFenceCreateInfo>(info->pNext);
return export_info ? export_info->handleTypes : 0;
}
vvl::Fence::Fence(Logger &logger, VkFence handle, const VkFenceCreateInfo *pCreateInfo)
: RefcountedStateObject(handle, kVulkanObjectTypeFence),
flags(pCreateInfo->flags),
export_handle_types(GetExportHandleTypes(pCreateInfo)),
state_((pCreateInfo->flags & VK_FENCE_CREATE_SIGNALED_BIT) ? kRetired : kUnsignaled),
completed_(),
waiter_(completed_.get_future()),
logger_(logger) {}
const VulkanTypedHandle *vvl::Fence::InUse() const {
auto guard = ReadLock();
// Fence does not have a parent (in the sense of a VVL state object), and the value returned
// by the base class InUse is not useful for reporting (it is the fence's own handle)
const bool in_use = RefcountedStateObject::InUse() != nullptr;
if (!in_use) {
return nullptr;
}
// If the fence is in-use there should be a queue that uses it.
// NOTE: in-use checks are always with regard to queue operations.
assert(queue_ != nullptr && "Can't find queue that uses the fence");
if (queue_) {
return &queue_->Handle();
}
static const VulkanTypedHandle empty{};
return &empty;
}
bool vvl::Fence::EnqueueSignal(vvl::Queue *queue_state, uint64_t next_seq) {
auto guard = WriteLock();
if (scope_ != kInternal) {
return true;
}
// Ensure that the fence's std::promise is not set when the fence is submitted to the queue.
// This guarantees that we can always call set_value() on the promise during Retire.
// (For ill-formed applications that forgot to call vkResetFences, the fence may be in the
// signaled state and its promise set - this would cause a crash during Retire)
ResetPromise();
// Mark fence in use
state_ = kInflight;
queue_ = queue_state;
seq_ = next_seq;
return false;
}
// Called from a non-queue operation, such as vkWaitForFences()|
void vvl::Fence::NotifyAndWait(const Location &loc) {
std::shared_future<void> waiter;
std::optional<SubmissionReference> present_submission_ref;
{
// Hold the lock only while updating members, but not
// while waiting
auto guard = WriteLock();
if (state_ == kInflight) {
if (queue_) {
queue_->Notify(seq_);
waiter = waiter_;
} else {
state_ = kRetired;
completed_.set_value();
queue_ = nullptr;
seq_ = 0;
// Update the swapchain image acquire state if the fence was used by the acquire operation
if (acquired_image_swapchain_) {
assert(acquired_image_index_ != vvl::kNoIndex32);
acquired_image_swapchain_->images[acquired_image_index_].acquire_fence_status = AcquireSyncStatus::WasWaitedOn;
acquired_image_swapchain_.reset();
acquired_image_index_ = vvl::kNoIndex32;
}
}
present_submission_ref = std::move(present_submission_ref_);
present_submission_ref_.reset();
}
// Cleanup wait semaphores.
// NOTE: Functions like QueueWaitIdle put fence in the retired state, still it can have
// the list of present semaphores, which are not cleared by QueueWaitIdle when swapchain
// maintenance extension is enabled. That's the reason this code is not under kInflight condition.
for (auto &semaphore : present_wait_semaphores_) {
semaphore->ClearSwapchainWaitInfo();
}
present_wait_semaphores_.clear();
}
if (waiter.valid()) {
auto result = waiter.wait_until(GetCondWaitTimeout());
if (result != std::future_status::ready) {
logger_.LogError("INTERNAL-ERROR-VkFence-state-timeout", Handle(), loc,
"The Validation Layers hit a timeout waiting for fence state to update.");
}
}
if (present_submission_ref.has_value()) {
present_submission_ref->queue->NotifyAndWait(loc, present_submission_ref->seq);
}
}
// Retire from a queue operation
void vvl::Fence::Retire() {
auto guard = WriteLock();
if (state_ == kInflight) {
state_ = kRetired;
completed_.set_value();
queue_ = nullptr;
seq_ = 0;
}
}
void vvl::Fence::Reset() {
auto guard = WriteLock();
queue_ = nullptr;
seq_ = 0;
// spec: If any member of pFences currently has its payload imported with temporary permanence,
// that fence’s prior permanent payload is first restored. The remaining operations described
// therefore operate on the restored payload.
if (scope_ == kExternalTemporary) {
scope_ = kInternal;
imported_handle_type_.reset();
}
state_ = kUnsignaled;
present_submission_ref_.reset();
// Do not reset swapchain-in-use state of each semaphore here, only stop the tracking.
// In order to reset swapchain-in-use state we need to wait on the fence.
present_wait_semaphores_.clear();
ResetPromise();
}
void vvl::Fence::Import(VkExternalFenceHandleTypeFlagBits handle_type, VkFenceImportFlags flags) {
auto guard = WriteLock();
if (scope_ != kExternalPermanent) {
if (handle_type != VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT && (flags & VK_FENCE_IMPORT_TEMPORARY_BIT) == 0) {
scope_ = kExternalPermanent;
} else if (scope_ == kInternal) {
scope_ = kExternalTemporary;
}
}
imported_handle_type_ = handle_type;
}
void vvl::Fence::Export(VkExternalFenceHandleTypeFlagBits handle_type) {
auto guard = WriteLock();
if (handle_type != VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT) {
// Export with reference transference becomes external
scope_ = kExternalPermanent;
} else {
// Export with copy transference has a side effect of resetting the fence
if (scope_ == kExternalTemporary) {
scope_ = kInternal;
imported_handle_type_.reset();
}
state_ = kUnsignaled;
ResetPromise();
}
}
std::optional<VkExternalFenceHandleTypeFlagBits> vvl::Fence::ImportedHandleType() const {
auto guard = ReadLock();
// Sanity check: fence imported -> scope is not internal
assert(!imported_handle_type_.has_value() || scope_ != kInternal);
return imported_handle_type_;
}
void vvl::Fence::SetAcquiredImage(const std::shared_ptr<vvl::Swapchain> &swapchain, uint32_t image_index) {
auto guard = WriteLock();
acquired_image_swapchain_ = swapchain;
acquired_image_index_ = image_index;
}
void vvl::Fence::SetPresentSubmissionRef(const SubmissionReference &present_submission_ref) {
auto guard = WriteLock();
// At this point, in an error-free scenario, "present_submission_ref_" member has no value
// (as an optional). If the fence is reused without being waited on (which causes a validation
// error), then we may find a stale ref value here. Simply overwrite it with a new ref.
//
// VVL INSIGHT: Interestingly, stale ref value can't happen in VVL tests because this function
// (from the Record phase) is not called if validation fails. However, the Record phase is
// always called for regular applications even in the case of validation errors.
// This means we cannot assert that submission ref member is empty. Such an assert would be
// valid for VVL tests but not for regular apps.
//
// assert(!present_submission_ref_.has_value()); - only valid for VVL tests
assert(present_submission_ref.queue != nullptr);
present_submission_ref_ = present_submission_ref;
}
void vvl::Fence::SetPresentWaitSemaphores(vvl::span<std::shared_ptr<vvl::Semaphore>> present_wait_semaphores) {
present_wait_semaphores_.clear();
for (const auto &semaphore : present_wait_semaphores) {
present_wait_semaphores_.emplace_back(semaphore);
}
}
void vvl::Fence::ResetPromise() {
completed_ = std::promise<void>();
waiter_ = std::shared_future<void>(completed_.get_future());
}