| /* Copyright (c) 2026 The Khronos Group Inc. |
| * Copyright (c) 2026 Valve Corporation |
| * Copyright (c) 2026 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 "state_tracker/submit_time_tracker.h" |
| #include "state_tracker/queue_state.h" |
| #include "state_tracker/semaphore_state.h" |
| #include "state_tracker/state_tracker.h" |
| #include "state_tracker/wsi_state.h" |
| #include "containers/container_utils.h" |
| #include "utils/convert_utils.h" |
| |
| namespace vvl { |
| |
| void SubmitTimeTracker::OnCreateTimelineSemaphore(VkSemaphore timeline, uint64_t initial_value) { |
| std::lock_guard lock(mutex_); |
| timeline_signals_[timeline] = initial_value; |
| } |
| |
| void SubmitTimeTracker::OnDestroyTimelineSemaphore(VkSemaphore timeline) { |
| std::lock_guard lock(mutex_); |
| timeline_signals_.erase(timeline); |
| } |
| |
| bool SubmitTimeTracker::ProcessQueueSubmission(VkQueue queue, const QueueSubmission& submission) const { |
| std::vector<std::shared_ptr<CommandBuffer>> command_buffers; |
| command_buffers.reserve(submission.cb_submissions.size()); |
| for (const auto& cb_info : submission.cb_submissions) { |
| command_buffers.emplace_back(cb_info.cb); |
| } |
| |
| std::vector<VkSemaphoreSubmitInfo> wait_semaphores; |
| wait_semaphores.reserve(submission.wait_semaphores.size()); |
| for (const SemaphoreInfo& wait : submission.wait_semaphores) { |
| VkSemaphoreSubmitInfo& semaphore_info = wait_semaphores.emplace_back(); |
| semaphore_info.semaphore = wait.semaphore->VkHandle(); |
| semaphore_info.value = wait.payload; |
| } |
| |
| std::vector<VkSemaphoreSubmitInfo> signal_semaphores; |
| signal_semaphores.reserve(submission.signal_semaphores.size()); |
| for (const SemaphoreInfo& signal : submission.signal_semaphores) { |
| VkSemaphoreSubmitInfo& semaphore_info = signal_semaphores.emplace_back(); |
| semaphore_info.semaphore = signal.semaphore->VkHandle(); |
| semaphore_info.value = signal.payload; |
| } |
| |
| std::lock_guard lock(mutex_); |
| SubmitTimeTracker& this_tracker = *const_cast<SubmitTimeTracker*>(this); |
| return this_tracker.ProcessBatch(std::move(command_buffers), wait_semaphores, signal_semaphores, queue, submission.loc.Get()); |
| } |
| |
| bool SubmitTimeTracker::ProcessSignalSemaphore(const VkSemaphoreSignalInfo& signal_info) const { |
| std::lock_guard lock(mutex_); |
| SubmitTimeTracker& this_tracker = *const_cast<SubmitTimeTracker*>(this); |
| return this_tracker.ProcessSignal(signal_info.semaphore, signal_info.value); |
| } |
| |
| // NOTE: when swapchain learns how to work with timeline semaphores, this function should be |
| // reworked to check for resolving timeline signals similar to ProcessBatch. |
| // Current version assumes only binary semaphores are allowed (the only option as of April 2026) |
| bool SubmitTimeTracker::ProcessPresent(const VkPresentInfoKHR& present_info, const Location& present_info_loc) const { |
| std::lock_guard lock(mutex_); |
| bool skip = false; |
| for (uint32_t i = 0; i < present_info.swapchainCount; i++) { |
| if (auto swapchain_state = validator_.Get<Swapchain>(present_info.pSwapchains[i])) { |
| const uint32_t image_index = present_info.pImageIndices[i]; |
| if (image_index >= swapchain_state->images.size()) { |
| continue; // invalid image index, reported elsewhere |
| } |
| skip |= validator_.ProcessPresentBatch(*swapchain_state->images[image_index].image_state, present_info_loc); |
| } |
| } |
| return skip; |
| } |
| |
| bool SubmitTimeTracker::ProcessBatch(std::vector<std::shared_ptr<CommandBuffer>>&& command_buffers, |
| vvl::span<const VkSemaphoreSubmitInfo> wait_semaphores, |
| vvl::span<const VkSemaphoreSubmitInfo> signal_semaphores, VkQueue queue, |
| const Location& submit_loc) { |
| bool skip = false; |
| std::vector<UnresolvedBatch>& unresolved_batches = unresolved_batches_[queue]; |
| std::vector<VkSemaphoreSubmitInfo> unresolved_timeline_waits = GetUnresolvedTimelineWaits(wait_semaphores); |
| |
| // Add wait-before-signal batches to unresolved list and return |
| const bool has_pending_waits = !unresolved_batches.empty() || !unresolved_timeline_waits.empty(); |
| if (has_pending_waits) { |
| UnresolvedBatch batch(submit_loc); |
| batch.command_buffers = std::move(command_buffers); |
| batch.unresolved_timeline_waits = std::move(unresolved_timeline_waits); |
| batch.signals.assign(signal_semaphores.begin(), signal_semaphores.end()); |
| unresolved_batches.emplace_back(std::move(batch)); |
| return skip; |
| } |
| |
| skip |= validator_.ProcessSubmissionBatch(*this, command_buffers, signal_semaphores, submit_loc); |
| |
| const bool new_timeline_signals = RegisterTimelineSignals(signal_semaphores); |
| if (new_timeline_signals) { |
| skip |= PropagateTimelineSignals(); |
| } |
| return skip; |
| } |
| |
| bool SubmitTimeTracker::ProcessSignal(VkSemaphore timeline, uint64_t signal_value) { |
| bool skip = false; |
| const bool new_timeline_signal = UpdateTimelineValue(timeline, signal_value); |
| if (new_timeline_signal) { |
| skip |= PropagateTimelineSignals(); |
| } |
| return skip; |
| } |
| |
| std::vector<VkSemaphoreSubmitInfo> SubmitTimeTracker::GetUnresolvedTimelineWaits( |
| vvl::span<const VkSemaphoreSubmitInfo> wait_semaphores) { |
| std::vector<VkSemaphoreSubmitInfo> unresolved; |
| for (const auto& wait : wait_semaphores) { |
| const std::optional<uint64_t> current_value = GetTimelineValue(wait.semaphore); |
| if (!current_value.has_value()) { |
| // Invalid or external semaphores should not block this batch |
| continue; |
| } |
| if (wait.value > *current_value) { |
| unresolved.emplace_back(wait); |
| } |
| } |
| return unresolved; |
| } |
| |
| bool SubmitTimeTracker::RegisterTimelineSignals(vvl::span<const VkSemaphoreSubmitInfo> signal_semaphores) { |
| bool new_timeline_signals = false; |
| for (const VkSemaphoreSubmitInfo& signal : signal_semaphores) { |
| new_timeline_signals |= UpdateTimelineValue(signal.semaphore, signal.value); |
| } |
| return new_timeline_signals; |
| } |
| |
| bool SubmitTimeTracker::PropagateTimelineSignals() { |
| bool skip = false; |
| |
| // The caller ensures we just registered new timeline signals |
| bool new_timeline_signals = true; |
| |
| // Each iteration uses registered timeline signals to resolve batches. |
| // If a resolved batch generates new timeline signals, the loop runs again |
| while (new_timeline_signals) { |
| new_timeline_signals = false; |
| |
| for (auto& [queue, batches] : unresolved_batches_) { |
| while (!batches.empty()) { |
| UnresolvedBatch& batch = batches.front(); |
| if (!CanBeResolved(batch)) { |
| break; |
| } |
| const auto signals = vvl::span<const VkSemaphoreSubmitInfo>(batch.signals.data(), batch.signals.size()); |
| skip |= validator_.ProcessSubmissionBatch(*this, batch.command_buffers, signals, batch.submit_loc_capture.Get()); |
| new_timeline_signals |= RegisterTimelineSignals(batch.signals); |
| batches.erase(batches.begin()); |
| } |
| } |
| } |
| return skip; |
| } |
| |
| bool SubmitTimeTracker::CanBeResolved(const UnresolvedBatch& batch) const { |
| for (const VkSemaphoreSubmitInfo& wait : batch.unresolved_timeline_waits) { |
| const std::optional<uint64_t> current_value = GetTimelineValue(wait.semaphore); |
| if (!current_value.has_value()) { |
| // Invalid or external semaphores should not block this batch |
| continue; |
| } |
| if (wait.value > current_value) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| std::optional<uint64_t> SubmitTimeTracker::GetTimelineValue(VkSemaphore timeline) const { |
| auto semaphore_state = validator_.Get<Semaphore>(timeline); |
| if (!semaphore_state || semaphore_state->type != VK_SEMAPHORE_TYPE_TIMELINE || |
| semaphore_state->Scope() != Semaphore::kInternal) { |
| // Used by the caller to detect invalid/non-timeline/external semaphores |
| return {}; |
| } |
| const uint64_t current_value = vvl::FindExisting(timeline_signals_, timeline); |
| return current_value; |
| } |
| |
| bool SubmitTimeTracker::UpdateTimelineValue(VkSemaphore timeline, uint64_t signal_value) { |
| auto semaphore_state = validator_.Get<Semaphore>(timeline); |
| if (!semaphore_state || semaphore_state->type != VK_SEMAPHORE_TYPE_TIMELINE) { |
| return false; |
| } |
| uint64_t& current_value = vvl::FindExisting(timeline_signals_, timeline); |
| if (signal_value <= current_value) { |
| return false; // non-increasing signal, the error should be reported elsewhere |
| } |
| current_value = signal_value; |
| return true; |
| } |
| |
| } // namespace vvl |