| /* 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 ∅ |
| } |
| |
| 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()); |
| } |