| /* 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/semaphore_state.h" |
| #include "state_tracker/queue_state.h" |
| #include "state_tracker/state_tracker.h" |
| #include "state_tracker/wsi_state.h" |
| #include "utils/math_utils.h" |
| #include "containers/container_utils.h" |
| |
| static bool CanSignalBinarySemaphoreAfterOperation(vvl::Semaphore::OpType op_type) { |
| return op_type == vvl::Semaphore::kNone || op_type == vvl::Semaphore::kWait; |
| } |
| |
| static bool CanWaitBinarySemaphoreAfterOperation(vvl::Semaphore::OpType op_type) { |
| return op_type == vvl::Semaphore::kSignal || op_type == vvl::Semaphore::kBinaryAcquire; |
| } |
| |
| static VkExternalSemaphoreHandleTypeFlags GetExportHandleTypes(const VkSemaphoreCreateInfo *pCreateInfo) { |
| auto export_info = vku::FindStructInPNextChain<VkExportSemaphoreCreateInfo>(pCreateInfo->pNext); |
| return export_info ? export_info->handleTypes : 0; |
| } |
| |
| void vvl::Semaphore::TimePoint::Notify() const { |
| assert(signal_submit.has_value() && signal_submit->queue); |
| signal_submit->queue->Notify(signal_submit->seq); |
| } |
| |
| vvl::Semaphore::Semaphore(DeviceState &device, VkSemaphore handle, const VkSemaphoreTypeCreateInfo *type_create_info, |
| const VkSemaphoreCreateInfo *pCreateInfo) |
| : RefcountedStateObject(handle, kVulkanObjectTypeSemaphore), |
| type(type_create_info ? type_create_info->semaphoreType : VK_SEMAPHORE_TYPE_BINARY), |
| flags(pCreateInfo->flags), |
| export_handle_types(GetExportHandleTypes(pCreateInfo)), |
| initial_value(type == VK_SEMAPHORE_TYPE_TIMELINE ? type_create_info->initialValue : 0), |
| #ifdef VK_USE_PLATFORM_METAL_EXT |
| metal_semaphore_export(GetMetalExport(pCreateInfo)), |
| #endif // VK_USE_PLATFORM_METAL_EXT |
| device_(device), |
| current_payload_(type_create_info ? type_create_info->initialValue : 0), |
| completed_{type == VK_SEMAPHORE_TYPE_TIMELINE ? kSignal : kNone, nullptr, current_payload_}, |
| next_payload_(current_payload_ + 1) { |
| } |
| |
| const VulkanTypedHandle *vvl::Semaphore::InUse() const { |
| auto guard = ReadLock(); |
| // Semaphore 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 semaphore's own handle) |
| const bool in_use = RefcountedStateObject::InUse() != nullptr; |
| if (!in_use) { |
| return nullptr; |
| } |
| // Scan timeline to find the first queue that uses the semaphore |
| for (const auto &[_, timepoint] : timeline_) { |
| if (timepoint.signal_submit.has_value() && timepoint.signal_submit->queue) { |
| return &timepoint.signal_submit->queue->Handle(); |
| } else { |
| for (const SubmissionReference &wait_submit : timepoint.wait_submits) { |
| if (wait_submit.queue) { |
| return &wait_submit.queue->Handle(); |
| } |
| } |
| } |
| } |
| // NOTE: In current implementation timepoints represent pending state. In-use tracking |
| // can retire timepoint even if submission is still pending, so timeline_ state it's |
| // always pending state but empty timeline does not mean there is no pending state. |
| // We don't make stronger guarantees because it's enough for in-use tracking. |
| // You can use NegativeSyncObject.TimelineSubmitSignalAndInUseTracking to check for |
| // a scenario when there is pending submission and timeline is empty. |
| // |
| // This should be taken into account when semaphore is used by functionality other than |
| // in-use tracking. In the following code we check completed_ state in case pending queue |
| // cannot be derived from timeline_. It's a bit unconventional. Maybe we need better |
| // separation between in-use tracking on other type of functionality. Or maybe it's about |
| // better definitions. |
| if (completed_.queue) { |
| return &completed_.queue->Handle(); |
| } |
| assert(false && "Can't find queue that uses the semaphore"); |
| static const VulkanTypedHandle empty{}; |
| return ∅ |
| } |
| |
| enum vvl::Semaphore::Scope vvl::Semaphore::Scope() const { |
| auto guard = ReadLock(); |
| return scope_; |
| } |
| |
| void vvl::Semaphore::EnqueueSignal(const SubmissionReference &signal_submit, uint64_t &payload) { |
| auto guard = WriteLock(); |
| if (type == VK_SEMAPHORE_TYPE_BINARY) { |
| payload = next_payload_++; |
| } else { |
| assert(type == VK_SEMAPHORE_TYPE_TIMELINE); |
| // Host signal (vkSignalSemaphore) updates payload immediately. |
| // This also handles vkLatencySleepNV external signal. |
| if (signal_submit.queue == nullptr) { |
| // Non-increasing signal value, do not track it. Validation phase already reported this. |
| if (payload <= current_payload_) { |
| return; |
| } |
| current_payload_ = payload; |
| } |
| |
| // Track smallest pending signal |
| if (signal_submit.queue != nullptr) { |
| if (!smallest_pending_signal_value_.has_value() || payload < *smallest_pending_signal_value_) { |
| smallest_pending_signal_value_ = payload; |
| } |
| } |
| } |
| |
| timeline_[payload].signal_submit.emplace(signal_submit); |
| } |
| |
| void vvl::Semaphore::EnqueueWait(const SubmissionReference &wait_submit, uint64_t &payload) { |
| auto guard = WriteLock(); |
| if (type == VK_SEMAPHORE_TYPE_BINARY) { |
| // Update the swapchain image acquire state if the semaphore was used by the acquire operation |
| if (acquired_image_swapchain_) { |
| assert(acquired_image_index_ != vvl::kNoIndex32); |
| acquired_image_swapchain_->images[acquired_image_index_].acquire_semaphore_status = AcquireSyncStatus::WasWaitedOn; |
| acquired_image_swapchain_.reset(); |
| acquired_image_index_ = vvl::kNoIndex32; |
| } |
| // Get payload value |
| if (timeline_.empty()) { |
| if (scope_ != vvl::Semaphore::kInternal) { |
| // for external semaphore mark wait as completed, no guarantee of signal visibility |
| completed_ = SemOp(kWait, wait_submit.queue, 0); |
| current_payload_ = 0; |
| return; |
| } else { |
| // generate binary payload value from the last completed signals |
| assert(completed_.op_type == kSignal); |
| payload = completed_.payload; |
| } |
| } else { |
| // generate binary payload value from the most recent pending binary signal |
| assert(timeline_.rbegin()->second.HasSignaler()); |
| payload = timeline_.rbegin()->first; |
| } |
| } |
| |
| if (payload <= completed_.payload) { |
| // Signal is already retired and its timepoint removed. Mark wait as completed. |
| // NOTE: wait's submission can still be pending, but timepoint lifetime logic |
| // is determined by the signal. completed_ is updated when signal is retired. |
| // The matching waits should be resolved against completed_ in this case. |
| assert(!vvl::Contains(timeline_, payload)); |
| completed_.op_type = kWait; |
| completed_.queue = wait_submit.queue; |
| return; |
| } |
| |
| timeline_[payload].wait_submits.emplace_back(wait_submit); |
| } |
| |
| void vvl::Semaphore::EnqueueAcquire(vvl::Func acquire_command) { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| auto guard = WriteLock(); |
| auto payload = next_payload_++; |
| assert(timeline_.find(payload) == timeline_.end()); |
| timeline_[payload].acquire_command.emplace(acquire_command); |
| } |
| |
| std::optional<uint64_t> vvl::Semaphore::CheckMaxDiffThreshold(uint64_t value, const char *&payload_type) const { |
| assert(type == VK_SEMAPHORE_TYPE_TIMELINE); |
| auto guard = ReadLock(); |
| |
| const uint64_t max_diff = device_.phys_dev_props_core12.maxTimelineSemaphoreValueDifference; |
| |
| // Check the current payload |
| if (AbsDiff(value, current_payload_) > max_diff) { |
| payload_type = "current"; |
| return current_payload_; |
| } |
| |
| // It is enough to check only the first (smallest payload) and last (largest payload) entries. |
| // If either exceeds the threshold, it must be a pending operation, since the current payload |
| // has already been checked. One of these entries may equal the current payload (e.g., for |
| // host signals), but in that case it will not exceed the threshold. |
| if (!timeline_.empty()) { |
| const auto &[first_payload, first_timepoint] = *timeline_.begin(); |
| if (AbsDiff(value, first_payload) > max_diff) { |
| payload_type = first_timepoint.HasWaiters() ? "pending wait" : "pending signal"; |
| return first_payload; |
| } |
| const auto &[last_payload, last_timepoint] = *timeline_.rbegin(); |
| if (AbsDiff(value, last_payload) > max_diff) { |
| payload_type = last_timepoint.HasWaiters() ? "pending wait" : "pending signal"; |
| return last_payload; |
| } |
| } |
| return {}; |
| } |
| |
| bool vvl::Semaphore::HasPendingTimelineSignal(uint64_t signal_value) const { |
| assert(type == VK_SEMAPHORE_TYPE_TIMELINE); |
| auto guard = ReadLock(); |
| |
| auto it = timeline_.find(signal_value); |
| if (it == timeline_.end()) { |
| return false; |
| } |
| const TimePoint &timepoint = it->second; |
| if (!timepoint.signal_submit.has_value()) { |
| return false; |
| } |
| const bool pending = timepoint.signal_submit->queue != nullptr; |
| return pending; |
| } |
| |
| std::optional<uint64_t> vvl::Semaphore::GetSmallestPendingTimelineSignal() const { |
| assert(type == VK_SEMAPHORE_TYPE_TIMELINE); |
| auto guard = ReadLock(); |
| return smallest_pending_signal_value_; |
| } |
| |
| std::optional<vvl::SubmissionReference> vvl::Semaphore::GetPendingBinarySignalSubmission() const { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| auto guard = ReadLock(); |
| if (timeline_.empty()) { |
| return {}; |
| } |
| const TimePoint &timepoint = timeline_.rbegin()->second; |
| assert(timepoint.HasSignaler()); // semaphore was signaled or acquired |
| |
| if (!timepoint.signal_submit.has_value()) { |
| assert(timepoint.acquire_command.has_value()); |
| return {}; |
| } |
| assert(timepoint.signal_submit->queue != nullptr); // binary semaphore can't be signaled from the host |
| return timepoint.signal_submit; |
| } |
| |
| std::optional<vvl::SubmissionReference> vvl::Semaphore::GetPendingBinaryWaitSubmission() const { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| auto guard = ReadLock(); |
| if (timeline_.empty()) { |
| return {}; |
| } |
| const auto &timepoint = timeline_.rbegin()->second; |
| assert(timepoint.wait_submits.empty() || timepoint.wait_submits.size() == 1); |
| |
| // No waits |
| if (timepoint.wait_submits.empty()) { |
| return {}; |
| } |
| // Skip waits that are not associated with a queue |
| if (timepoint.wait_submits[0].queue == nullptr) { |
| return {}; |
| } |
| return timepoint.wait_submits[0]; |
| } |
| |
| std::optional<vvl::SemaphoreInfo> vvl::Semaphore::GetPendingBinarySignalTimelineDependency() const { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| auto guard = ReadLock(); |
| if (timeline_.empty()) { |
| return {}; |
| } |
| const TimePoint &timepoint = timeline_.rbegin()->second; |
| assert(timepoint.HasSignaler()); |
| const auto &signal_submit = timepoint.signal_submit; |
| |
| // A signal not associated with a queue cannot be blocked by timeline wait |
| // (host signal or image acquire signal) |
| if (!signal_submit.has_value() || signal_submit->queue == nullptr) { |
| return {}; |
| } |
| |
| return signal_submit->queue->FindTimelineWaitWithoutResolvingSignal(signal_submit->seq); |
| } |
| |
| uint64_t vvl::Semaphore::CurrentPayload() const { |
| auto guard = ReadLock(); |
| return current_payload_; |
| } |
| |
| bool vvl::Semaphore::CanBinaryBeSignaled() const { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| auto guard = ReadLock(); |
| if (timeline_.empty()) { |
| return CanSignalBinarySemaphoreAfterOperation(completed_.op_type); |
| } |
| // Every timeline slot of binary semaphore should contain at least a signal. |
| // Wait before signal is not allowed. |
| assert(timeline_.rbegin()->second.HasSignaler()); |
| |
| return timeline_.rbegin()->second.HasWaiters(); |
| } |
| |
| bool vvl::Semaphore::CanBinaryBeWaited() const { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| auto guard = ReadLock(); |
| if (timeline_.empty()) { |
| return CanWaitBinarySemaphoreAfterOperation(completed_.op_type); |
| } |
| |
| const TimePoint &timepoint = timeline_.rbegin()->second; |
| |
| assert(scope_ == vvl::Semaphore::kInternal); // Ensured by all calling sites |
| |
| // Every timeline slot of binary semaphore should contain at least a signal. |
| // Wait before signal is not allowed. |
| assert(timepoint.HasSignaler()); |
| |
| // Can wait if there are no waiters |
| return !timepoint.HasWaiters(); |
| } |
| |
| void vvl::Semaphore::GetLastBinarySignalSource(VkQueue &queue, vvl::Func &acquire_command) const { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| queue = VK_NULL_HANDLE; |
| acquire_command = vvl::Func::Empty; |
| |
| auto guard = ReadLock(); |
| if (timeline_.empty()) { |
| if (completed_.op_type == kSignal && completed_.queue) { |
| queue = completed_.queue->VkHandle(); |
| } else if (completed_.op_type == kBinaryAcquire) { |
| acquire_command = *completed_.acquire_command; |
| } |
| } else { |
| const TimePoint &timepoint = timeline_.rbegin()->second; |
| if (timepoint.signal_submit.has_value() && timepoint.signal_submit->queue) { |
| queue = timepoint.signal_submit->queue->VkHandle(); |
| } else if (timepoint.acquire_command.has_value()) { |
| acquire_command = *timepoint.acquire_command; |
| } |
| } |
| } |
| |
| bool vvl::Semaphore::HasResolvingTimelineSignal(uint64_t wait_payload) const { |
| assert(type == VK_SEMAPHORE_TYPE_TIMELINE); |
| auto guard = ReadLock(); |
| |
| // For external semaphore we can't track the signal. |
| // In theory, it can be context-dependent whether to assume the external signal is |
| // available or not in order to have false positive free validation. Assert that |
| // this function is only called for regular semaphores and it is up to the caller |
| // to handle external case. |
| assert(scope_ == vvl::Semaphore::kInternal); |
| |
| // Check if completed payload value (which includes initial value) resolves the wait. |
| if (wait_payload <= completed_.payload) { |
| return true; |
| } |
| |
| auto it = timeline_.find(wait_payload); |
| assert(it != timeline_.end()); // for each registered wait there is a timepoint |
| while (it != timeline_.end()) { |
| if (it->second.signal_submit.has_value()) { |
| assert(it->first >= wait_payload); // timepoints are ordered in increasing order |
| return true; |
| } |
| ++it; |
| } |
| return false; |
| } |
| |
| bool vvl::Semaphore::CanRetireBinaryWait(TimePoint &timepoint) const { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); |
| // The only allowed configuration when binary semaphore wait does not have a signal |
| // is external semaphore. Just retire the wait because there is no guarantee we can |
| // track the signal. |
| if (!timepoint.signal_submit.has_value()) { |
| assert(scope_ != kInternal); |
| return true; |
| } |
| |
| // The resolving signal can only be on another queue (the earlier signals on the |
| // current queue are already processed and corresponding timepoints are retired). |
| // Initiate forward progress on signaling queue and ask the caller to wait. |
| timepoint.Notify(); |
| return false; |
| } |
| |
| bool vvl::Semaphore::CanRetireTimelineWait(const vvl::Queue *current_queue, uint64_t payload) const { |
| assert(type == VK_SEMAPHORE_TYPE_TIMELINE); |
| |
| // In the correct program the resolving signal is the next signal on the timeline, |
| // otherwise this violates the rule of strictly increasing signal values. |
| auto it = timeline_.find(payload); |
| assert(it != timeline_.end()); |
| for (; it != timeline_.end(); ++it) { |
| const TimePoint &t = it->second; |
| if (!t.signal_submit.has_value()) { |
| continue; |
| } |
| // If the next signal is on the waiting (current) queue, it can't be a resolving signal (blocked by wait). |
| // QueueSubmissionValidator will also report an error about non-increasing signal values |
| if (t.signal_submit->queue != nullptr && t.signal_submit->queue == current_queue) { |
| continue; |
| } |
| // Found the resolving signal |
| break; |
| } |
| |
| // There is always a resolving signal when we reach a retirement phase (CPU successfully finished waiting on GPU). |
| // For external semaphore we might not have visibility of this signal. Just retire the wait. |
| if (it == timeline_.end()) { |
| assert(scope_ != kInternal); |
| return true; |
| } |
| |
| // Found host signal that finishes this wait |
| const TimePoint &t = it->second; |
| if (t.signal_submit->queue == nullptr) { |
| return true; |
| } |
| |
| // Notify signaling queue and wait for its queue thread |
| t.Notify(); |
| return false; |
| } |
| |
| void vvl::Semaphore::RetireWait(vvl::Queue *current_queue, uint64_t payload, const Location &loc, bool queue_thread) { |
| std::shared_future<void> waiter; |
| bool retire_external_payload = false; |
| uint64_t external_payload = 0; |
| { |
| auto guard = WriteLock(); |
| if (payload <= completed_.payload) { |
| return; |
| } |
| if (scope_ != kInternal) { |
| if (!vvl::Find(timeline_, payload)) { |
| // GetSemaphoreCounterValue for external semaphore might not have a registered timepoint. |
| // Add timepoint so we can retire timeline up to that point. |
| assert(type == VK_SEMAPHORE_TYPE_TIMELINE); |
| auto payload_it = timeline_.insert({payload, TimePoint{}}).first; |
| |
| // Search existing signal. If found, notify corresponding submission. |
| // (external payload, which is already reached by the gpu, is larger then found signal, |
| // this means that earlier signals were also processed, so we can retire them) |
| for (auto it = std::make_reverse_iterator(payload_it); it != timeline_.rend(); ++it) { |
| const TimePoint &t = it->second; |
| if (t.signal_submit.has_value() && t.signal_submit->queue) { |
| retire_external_payload = true; |
| external_payload = payload; |
| // Update payload value to retire existing signal. |
| // External payload will be retired after that to update current payload value. |
| payload = it->first; |
| break; |
| } |
| } |
| } |
| if (scope_ == kExternalTemporary) { |
| scope_ = kInternal; |
| imported_handle_type_.reset(); |
| } |
| } |
| TimePoint &timepoint = vvl::FindExisting(timeline_, payload); |
| |
| bool retire = false; |
| if (timepoint.acquire_command) { |
| retire = true; // There is resolving acquire signal, timepoint can be retired |
| } else if (type == VK_SEMAPHORE_TYPE_BINARY) { |
| retire = CanRetireBinaryWait(timepoint); |
| } else { |
| retire = CanRetireTimelineWait(current_queue, payload); |
| } |
| if (retire) { |
| RetireTimePoint(payload, kWait, current_queue); |
| return; |
| } |
| |
| // Wait for some other queue or a host operation to retire |
| assert(timepoint.waiter.valid()); |
| // the current timepoint should get destroyed while we're waiting, so copy out the waiter. |
| waiter = timepoint.waiter; |
| } |
| |
| WaitTimePoint(std::move(waiter), payload, !queue_thread, loc); |
| |
| if (retire_external_payload) { |
| auto guard = WriteLock(); |
| RetireTimePoint(external_payload, kWait, nullptr); |
| } |
| } |
| |
| void vvl::Semaphore::RetireSignal(uint64_t payload) { |
| auto guard = WriteLock(); |
| if (payload <= completed_.payload) { |
| return; |
| } |
| TimePoint &timepoint = vvl::FindExisting(timeline_, payload); |
| assert(timepoint.signal_submit.has_value()); |
| |
| OpType completed_op = kSignal; |
| const Queue *completed_op_queue = timepoint.signal_submit->queue; |
| |
| // If there is a wait operation then mark it as the last completed instead. |
| // The reason to do this here instead on the waiter side (after it is unblocked) |
| // is because signal can have larger (timeline) value than corresponding wait value. |
| // In this case it's the signal that defines the last completed value. |
| if (!timepoint.wait_submits.empty()) { |
| completed_op = kWait; |
| // NOTE: for timeline semaphores there can be several waiters. Also for timeline |
| // semaphores the queue of the completed operation is only used by Semaphore::InUse |
| // for reporting purposes. We can choose any valid wait queue if there are multiple. |
| completed_op_queue = timepoint.wait_submits[0].queue; |
| } |
| |
| RetireTimePoint(payload, completed_op, completed_op_queue); |
| } |
| |
| void vvl::Semaphore::RetireTimePoint(uint64_t payload, OpType completed_op, const Queue *completed_op_queue) { |
| auto it = timeline_.begin(); |
| while (it != timeline_.end() && it->first <= payload) { |
| assert(it->first > completed_.payload); |
| it->second.completed.set_value(); |
| ++it; |
| } |
| timeline_.erase(timeline_.begin(), it); |
| completed_ = SemOp(completed_op, completed_op_queue, payload); |
| |
| // Update the current payload only if the given payload is larger. |
| // vkSignalSemaphore updates the current payload immediately, so it can be |
| // larger than the given payload from the most recently synchronized batch. |
| if (payload > current_payload_) { |
| current_payload_ = payload; |
| } |
| |
| // Update smallest pending signal |
| if (type == VK_SEMAPHORE_TYPE_TIMELINE) { |
| if (timeline_.empty()) { |
| smallest_pending_signal_value_.reset(); |
| } else if (smallest_pending_signal_value_.has_value() && timeline_.begin()->first > *smallest_pending_signal_value_) { |
| smallest_pending_signal_value_.reset(); |
| for (const auto &[payload, timepoint] : timeline_) { |
| if (!timepoint.signal_submit.has_value()) { |
| continue; |
| } |
| |
| // host signals must always go before pending signals and at this point |
| // we iterate over pending operations |
| assert(timepoint.signal_submit->queue != nullptr); |
| |
| smallest_pending_signal_value_ = payload; |
| break; |
| } |
| } |
| } |
| } |
| |
| void vvl::Semaphore::WaitTimePoint(std::shared_future<void> &&waiter, uint64_t payload, bool unblock_validation_object, |
| const Location &loc) { |
| if (unblock_validation_object) { |
| device_.BeginBlockingOperation(); |
| } |
| |
| auto result = waiter.wait_until(GetCondWaitTimeout()); |
| |
| if (unblock_validation_object) { |
| device_.EndBlockingOperation(); |
| } |
| |
| if (result != std::future_status::ready) { |
| device_.LogError( |
| "INTERNAL-ERROR-VkSemaphore-state-timeout", Handle(), loc, |
| "The Validation Layers hit a timeout waiting for timeline semaphore state to update. completed_.payload=%" PRIu64 |
| " wait_payload=%" PRIu64, |
| completed_.payload, payload); |
| } |
| } |
| |
| void vvl::Semaphore::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::Semaphore::SetSwapchainWaitInfo(const SwapchainWaitInfo &info) { |
| auto guard = WriteLock(); |
| swapchain_wait_info_.emplace(info); |
| } |
| |
| void vvl::Semaphore::ClearSwapchainWaitInfo() { |
| auto guard = WriteLock(); |
| swapchain_wait_info_.reset(); |
| } |
| |
| std::optional<vvl::Semaphore::SwapchainWaitInfo> vvl::Semaphore::GetSwapchainWaitInfo() const { |
| auto guard = ReadLock(); |
| // Return by value due to locking (not safe to access reference when unlocked) |
| return swapchain_wait_info_; |
| } |
| |
| void vvl::Semaphore::Import(VkExternalSemaphoreHandleTypeFlagBits handle_type, VkSemaphoreImportFlags flags) { |
| auto guard = WriteLock(); |
| if (scope_ != kExternalPermanent) { |
| if ((handle_type == VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT || flags & VK_SEMAPHORE_IMPORT_TEMPORARY_BIT) && |
| scope_ == kInternal) { |
| scope_ = kExternalTemporary; |
| } else { |
| scope_ = kExternalPermanent; |
| } |
| } |
| imported_handle_type_ = handle_type; |
| } |
| |
| void vvl::Semaphore::Export(VkExternalSemaphoreHandleTypeFlagBits handle_type) { |
| if (handle_type != VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) { |
| // Cannot track semaphore state once it is exported, except for Sync FD handle types which have copy transference |
| auto guard = WriteLock(); |
| scope_ = kExternalPermanent; |
| } else { |
| assert(type == VK_SEMAPHORE_TYPE_BINARY); // checked by validation phase |
| // Exporting a semaphore payload to a handle with copy transference has the same side effects |
| // on the source semaphore's payload as executing a semaphore wait operation |
| if (std::optional<SubmissionReference> pending_signal_submit = GetPendingBinarySignalSubmission()) { |
| uint64_t temp_payload; // don't need output parameter |
| EnqueueWait(*pending_signal_submit, temp_payload); |
| } else { |
| assert(completed_.op_type == kSignal); // checked by validation phase |
| completed_.op_type = kWait; |
| completed_.queue = nullptr; // Export's wait is not associated with a queue |
| } |
| } |
| } |
| |
| std::optional<VkExternalSemaphoreHandleTypeFlagBits> vvl::Semaphore::ImportedHandleType() const { |
| auto guard = ReadLock(); |
| |
| // Sanity check: semaphore imported -> scope is not internal |
| assert(!imported_handle_type_.has_value() || scope_ != kInternal); |
| |
| return imported_handle_type_; |
| } |
| |
| #ifdef VK_USE_PLATFORM_METAL_EXT |
| bool vvl::Semaphore::GetMetalExport(const VkSemaphoreCreateInfo *info) { |
| bool retval = false; |
| auto export_metal_object_info = vku::FindStructInPNextChain<VkExportMetalObjectCreateInfoEXT>(info->pNext); |
| while (export_metal_object_info) { |
| if (export_metal_object_info->exportObjectType == VK_EXPORT_METAL_OBJECT_TYPE_METAL_SHARED_EVENT_BIT_EXT) { |
| retval = true; |
| break; |
| } |
| export_metal_object_info = vku::FindStructInPNextChain<VkExportMetalObjectCreateInfoEXT>(export_metal_object_info->pNext); |
| } |
| return retval; |
| } |
| #endif // VK_USE_PLATFORM_METAL_EXT |