| /* |
| * Copyright (c) 2019-2026 Valve Corporation |
| * Copyright (c) 2019-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 "sync/sync_access_context.h" |
| #include "sync/sync_image.h" |
| #include "sync/sync_validation.h" |
| #include "state_tracker/buffer_state.h" |
| #include "state_tracker/render_pass_state.h" |
| #include "state_tracker/video_session_state.h" |
| #include <vulkan/utility/vk_format_utils.h> |
| |
| namespace syncval { |
| |
| bool SimpleBinding(const vvl::Bindable &bindable) { return !bindable.sparse && bindable.Binding(); } |
| VkDeviceSize ResourceBaseAddress(const vvl::Buffer &buffer) { return buffer.GetFakeBaseAddress(); } |
| |
| void AccessContext::InitFrom(uint32_t subpass, VkQueueFlags queue_flags, |
| const std::vector<SubpassDependencyGraphNode> &dependencies, const AccessContext *contexts, |
| const AccessContext *external_context) { |
| const auto &subpass_dep = dependencies[subpass]; |
| const bool has_barrier_from_external = subpass_dep.barrier_from_external.size() > 0U; |
| prev_.reserve(subpass_dep.prev.size() + (has_barrier_from_external ? 1U : 0U)); |
| prev_by_subpass_.resize(subpass, nullptr); // Can't be more prevs than the subpass we're on |
| for (const auto &prev_dep : subpass_dep.prev) { |
| const auto prev_pass = prev_dep.first->pass; |
| const auto &prev_barriers = prev_dep.second; |
| assert(prev_dep.second.size()); |
| prev_.emplace_back(&contexts[prev_pass], queue_flags, prev_barriers); |
| prev_by_subpass_[prev_pass] = &prev_.back(); |
| } |
| |
| async_.reserve(subpass_dep.async.size()); |
| for (const auto async_subpass : subpass_dep.async) { |
| // Start tags are not known at creation time (as it's done at BeginRenderpass) |
| async_.emplace_back(contexts[async_subpass], kInvalidTag, kQueueIdInvalid); |
| } |
| |
| if (has_barrier_from_external) { |
| // Store the barrier from external with the reat, but save pointer for "by subpass" lookups. |
| prev_.emplace_back(external_context, queue_flags, subpass_dep.barrier_from_external); |
| src_external_ = &prev_.back(); |
| } |
| if (subpass_dep.barrier_to_external.size()) { |
| dst_external_ = SubpassBarrierTrackback(this, queue_flags, subpass_dep.barrier_to_external); |
| } |
| } |
| |
| ApplySingleBufferBarrierFunctor::ApplySingleBufferBarrierFunctor(const AccessContext &access_context, |
| const BarrierScope &barrier_scope, const SyncBarrier &barrier) |
| : access_context(access_context), barrier_scope(barrier_scope), barrier(barrier) {} |
| |
| AccessMap::iterator ApplySingleBufferBarrierFunctor::Infill(AccessMap *accesses, const Iterator &pos_hint, |
| const AccessRange &range) const { |
| // The buffer barrier does not need to fill the gaps because barrier |
| // application to a range without accesses is a no-op. |
| // Return the pos iterator unchanged to indicate that no entry was created. |
| return pos_hint; |
| } |
| |
| void ApplySingleBufferBarrierFunctor::operator()(const Iterator &pos) const { |
| AccessState &access_state = pos->second; |
| access_context.ApplyGlobalBarriers(access_state); |
| access_state.ApplyBarrier(barrier_scope, barrier); |
| } |
| |
| ApplySingleImageBarrierFunctor::ApplySingleImageBarrierFunctor(const AccessContext &access_context, |
| const BarrierScope &barrier_scope, const SyncBarrier &barrier, |
| bool layout_transition, uint32_t layout_transition_handle_index, |
| ResourceUsageTag exec_tag) |
| : access_context(access_context), |
| barrier_scope(barrier_scope), |
| barrier(barrier), |
| exec_tag(exec_tag), |
| layout_transition(layout_transition), |
| layout_transition_handle_index(layout_transition_handle_index) { |
| // Suppress layout transition during submit time application. |
| // It adds write access but this is necessary only during recording. |
| if (barrier_scope.scope_queue != kQueueIdInvalid) { |
| this->layout_transition = false; |
| this->layout_transition_handle_index = vvl::kNoIndex32; |
| } |
| } |
| |
| AccessMap::iterator ApplySingleImageBarrierFunctor::Infill(AccessMap *accesses, const Iterator &pos_hint, |
| const AccessRange &range) const { |
| if (!layout_transition) { |
| // Do not create a new range if this is not a layout transition |
| return pos_hint; |
| } |
| // Create a new range for layout transition write access |
| auto inserted = accesses->Insert(pos_hint, range, AccessState::DefaultAccessState()); |
| return inserted; |
| } |
| |
| void ApplySingleImageBarrierFunctor::operator()(const Iterator &pos) const { |
| AccessState &access_state = pos->second; |
| access_context.ApplyGlobalBarriers(access_state); |
| access_state.ApplyBarrier(barrier_scope, barrier, layout_transition, layout_transition_handle_index, exec_tag); |
| } |
| |
| void CollectBarriersFunctor::operator()(const Iterator &pos) const { |
| AccessState &access_state = pos->second; |
| access_context.ApplyGlobalBarriers(access_state); |
| access_state.CollectPendingBarriers(barrier_scope, barrier, layout_transition, layout_transition_handle_index, |
| pending_barriers); |
| } |
| |
| void AccessContext::InitFrom(const AccessContext &other) { |
| access_state_map_.Assign(other.access_state_map_); |
| prev_ = other.prev_; |
| prev_by_subpass_ = other.prev_by_subpass_; |
| async_ = other.async_; |
| src_external_ = other.src_external_; |
| dst_external_ = other.dst_external_; |
| start_tag_ = other.start_tag_; |
| |
| global_barriers_queue_ = other.global_barriers_queue_; |
| for (uint32_t i = 0; i < other.global_barrier_def_count_; i++) { |
| global_barrier_defs_[i] = other.global_barrier_defs_[i]; |
| } |
| global_barrier_def_count_ = other.global_barrier_def_count_; |
| global_barriers_ = other.global_barriers_; |
| |
| // Even though the "other" context may be finalized, we might still need to update "this" copy. |
| // Therefore, the copied context cannot be marked as finalized yet. |
| finalized_ = false; |
| |
| sorted_first_accesses_.Clear(); |
| } |
| |
| void AccessContext::Reset() { |
| access_state_map_.Clear(); |
| prev_.clear(); |
| prev_by_subpass_.clear(); |
| async_.clear(); |
| src_external_ = nullptr; |
| dst_external_ = {}; |
| start_tag_ = {}; |
| ResetGlobalBarriers(); |
| finalized_ = false; |
| sorted_first_accesses_.Clear(); |
| } |
| |
| void AccessContext::Finalize() { |
| assert(!finalized_); // no need to finalize finalized |
| sorted_first_accesses_.Init(access_state_map_); |
| finalized_ = true; |
| } |
| |
| void AccessContext::RegisterGlobalBarrier(const SyncBarrier &barrier, QueueId queue_id) { |
| assert(global_barriers_.empty() || global_barriers_queue_ == queue_id); |
| |
| // Search for existing def |
| uint32_t def_index = 0; |
| for (; def_index < global_barrier_def_count_; def_index++) { |
| if (global_barrier_defs_[def_index].barrier == barrier) { |
| break; |
| } |
| } |
| // Register a new def if this barrier is encountered for the first time |
| if (def_index == global_barrier_def_count_) { |
| // Flush global barriers if all def slots are in use |
| if (global_barrier_def_count_ == kMaxGlobaBarrierDefCount) { |
| for (auto &[_, access] : access_state_map_) { |
| ApplyGlobalBarriers(access); |
| access.next_global_barrier_index = 0; // to match state after reset |
| } |
| ResetGlobalBarriers(); |
| def_index = 0; |
| } |
| |
| GlobalBarrierDef &new_def = global_barrier_defs_[global_barrier_def_count_++]; |
| new_def.barrier = barrier; |
| new_def.chain_mask = 0; |
| |
| // Update chain masks |
| for (uint32_t i = 0; i < global_barrier_def_count_ - 1; i++) { |
| GlobalBarrierDef &def = global_barrier_defs_[i]; |
| if ((new_def.barrier.src_exec_scope.exec_scope & def.barrier.dst_exec_scope.exec_scope) != 0) { |
| new_def.chain_mask |= 1u << i; |
| } |
| if ((def.barrier.src_exec_scope.exec_scope & new_def.barrier.dst_exec_scope.exec_scope) != 0) { |
| def.chain_mask |= 1u << (global_barrier_def_count_ - 1); |
| } |
| } |
| } |
| // A global barrier is just a reference to its def |
| global_barriers_.push_back(def_index); |
| global_barriers_queue_ = queue_id; |
| } |
| |
| void AccessContext::ApplyGlobalBarriers(AccessState &access_state) const { |
| const uint32_t global_barrier_count = GetGlobalBarrierCount(); |
| assert(access_state.next_global_barrier_index <= global_barrier_count); |
| if (access_state.next_global_barrier_index == global_barrier_count) { |
| return; // access state is up-to-date |
| } |
| uint32_t applied_barrier_mask = 0; // used to skip already applied barriers |
| uint32_t applied_count = 0; // used for early exit when all unique barriers are applied |
| uint32_t failed_mask = 0; // used to quickly test barriers that failed the first application attempt |
| |
| for (size_t i = access_state.next_global_barrier_index; i < global_barrier_count; i++) { |
| const uint32_t def_index = global_barriers_[i]; |
| const uint32_t def_mask = 1u << def_index; |
| assert(def_index < global_barrier_def_count_); |
| |
| const GlobalBarrierDef &def = global_barrier_defs_[def_index]; |
| |
| // Skip barriers that were already applied |
| if ((def_mask & applied_barrier_mask) != 0) { |
| continue; |
| } |
| |
| // If this barrier failed to apply initially, it can only be applied |
| // again if it can chain with one of the newly applied barriers |
| if ((def_mask & failed_mask) != 0) { |
| if ((def.chain_mask & applied_barrier_mask) == 0) { |
| continue; |
| } |
| } |
| |
| // TODO: for requests with multiple barriers we need to register them in groups |
| // and use PendingBarriers helper here. |
| const BarrierScope barrier_scope(def.barrier, global_barriers_queue_); |
| const bool is_barrier_applied = access_state.ApplyBarrier(barrier_scope, def.barrier); |
| if (is_barrier_applied) { |
| applied_barrier_mask |= def_mask; |
| applied_count++; |
| if (applied_count == global_barrier_def_count_) { |
| break; // no barriers left that can add new information |
| } |
| } else { |
| failed_mask |= def_mask; |
| } |
| } |
| access_state.next_global_barrier_index = global_barrier_count; |
| } |
| |
| void AccessContext::ResetGlobalBarriers() { |
| global_barriers_queue_ = kQueueIdInvalid; |
| global_barrier_def_count_ = 0; |
| global_barriers_.clear(); |
| } |
| |
| void AccessContext::TrimAndClearFirstAccess() { |
| assert(!finalized_); |
| for (auto &[range, access] : access_state_map_) { |
| access.Normalize(); |
| } |
| Consolidate(access_state_map_); |
| } |
| |
| void AccessContext::AddReferencedTags(ResourceUsageTagSet &used) const { |
| assert(!finalized_); |
| for (const auto &[range, access] : access_state_map_) { |
| access.GatherReferencedTags(used); |
| } |
| } |
| |
| void AccessContext::ResolveFromContext(const AccessContext &from) { |
| assert(!finalized_); |
| auto noop_action = [](AccessState *access) {}; |
| from.ResolveAccessRangeRecursePrev(kFullRange, noop_action, *this, false); |
| } |
| |
| void AccessContext::ResolvePreviousAccesses() { |
| assert(!finalized_); |
| if (!prev_.empty()) { |
| for (const auto &prev_dep : prev_) { |
| const ApplyTrackbackStackAction barrier_action(prev_dep.barriers, nullptr); |
| prev_dep.source_subpass->ResolveAccessRangeRecursePrev(kFullRange, barrier_action, *this, true); |
| } |
| } |
| } |
| |
| void AccessContext::ResolveFromSubpassContext(const ApplySubpassTransitionBarriersAction &subpass_transition_action, |
| const AccessContext &from_context, |
| subresource_adapter::ImageRangeGenerator attachment_range_gen) { |
| assert(!finalized_); |
| for (; attachment_range_gen->non_empty(); ++attachment_range_gen) { |
| from_context.ResolveAccessRangeRecursePrev(*attachment_range_gen, subpass_transition_action, *this, true); |
| } |
| } |
| |
| void AccessContext::ResolveAccessRange(const AccessRange &range, const AccessStateFunction &barrier_action, |
| AccessContext &resolve_context) const { |
| if (!range.non_empty()) { |
| return; |
| } |
| AccessMap &resolve_map = resolve_context.access_state_map_; |
| |
| ParallelIterator current(resolve_map, access_state_map_, range.begin); |
| while (current.range.non_empty() && range.includes(current.range.begin)) { |
| const auto current_range = current.range & range; |
| if (current.pos_B.inside_lower_bound_range) { |
| const auto &src_pos = current.pos_B.lower_bound; |
| |
| // Create a copy of the source access state (source is this context, destination is the resolve context). |
| // Then do the following steps: |
| // a) apply not yet applied global barriers |
| // b) update global barrier index to ensure global barriers from the resolve context are not applied |
| // c) apply barrier action |
| AccessState src_access = src_pos->second; |
| ApplyGlobalBarriers(src_access); // a |
| src_access.next_global_barrier_index = resolve_context.GetGlobalBarrierCount(); // b |
| barrier_action(&src_access); // c |
| |
| if (current.pos_A.inside_lower_bound_range) { |
| const auto trimmed = Split(current.pos_A.lower_bound, resolve_map, current_range); |
| AccessState &dst_state = trimmed->second; |
| resolve_context.ApplyGlobalBarriers(dst_state); |
| dst_state.Resolve(src_access); |
| current.OnCurrentRangeModified(trimmed); |
| } else { |
| auto inserted = resolve_map.Insert(current.pos_A.lower_bound, current_range, src_access); |
| current.OnCurrentRangeModified(inserted); |
| } |
| } |
| if (current.range.non_empty()) { |
| current.NextRange(); |
| } |
| } |
| } |
| |
| void AccessContext::ResolveAccessRangeRecursePrev(const AccessRange &range, const AccessStateFunction &barrier_action, |
| AccessContext &resolve_context, bool infill) const { |
| if (!range.non_empty()) { |
| return; |
| } |
| AccessMap &resolve_map = resolve_context.access_state_map_; |
| |
| ParallelIterator current(resolve_map, access_state_map_, range.begin); |
| while (current.range.non_empty() && range.includes(current.range.begin)) { |
| const auto current_range = current.range & range; |
| if (current.pos_B.inside_lower_bound_range) { |
| const auto &src_pos = current.pos_B.lower_bound; |
| |
| // Create a copy of the source access state (source is this context, destination is the resolve context). |
| // Then do the following steps: |
| // a) apply not yet applied global barriers |
| // b) update global barrier index to ensure global barriers from the resolve context are not applied |
| // c) apply barrier action |
| AccessState src_access = src_pos->second; |
| ApplyGlobalBarriers(src_access); // a |
| src_access.next_global_barrier_index = resolve_context.GetGlobalBarrierCount(); // b |
| barrier_action(&src_access); // c |
| |
| if (current.pos_A.inside_lower_bound_range) { |
| const auto trimmed = Split(current.pos_A.lower_bound, resolve_map, current_range); |
| AccessState &dst_state = trimmed->second; |
| resolve_context.ApplyGlobalBarriers(dst_state); |
| dst_state.Resolve(src_access); |
| current.OnCurrentRangeModified(trimmed); |
| } else { |
| auto inserted = resolve_map.Insert(current.pos_A.lower_bound, current_range, src_access); |
| current.OnCurrentRangeModified(inserted); |
| } |
| } else { // Descend to fill this gap |
| AccessRange recurrence_range = current_range; |
| // The current context is empty for the current_range, so recur to fill the gap. |
| // Since we will be recurring back up the DAG, expand the gap descent to cover the |
| // full range for which B is not valid, to minimize that recurrence |
| if (current.pos_B.lower_bound == access_state_map_.end()) { |
| recurrence_range.end = range.end; |
| } else { |
| recurrence_range.end = std::min(range.end, current.pos_B.lower_bound->first.begin); |
| } |
| |
| // Note that resolve_context over the recurrence_range may contain both empty and |
| // non-empty entries; only the current context has a continuous empty entry over |
| // this range. Therefore, the next call must iterate over potentially multiple |
| // ranges in resolve_context that cross the recurrence_range and fill the empty ones. |
| ResolveGapsRecursePrev(recurrence_range, resolve_context, infill, barrier_action); |
| |
| // recurrence_range is already processed and it can be larger than the current_range. |
| // The NextRange might move to the range that is still inside recurrence_range, but we |
| // need the range that goes after recurrence_range. Seek to the end of recurrence_range, |
| // so NextRange will get the expected range. |
| // TODO: it might be simpler to seek directly to recurrence_range.end without calling NextRange(). |
| assert(recurrence_range.non_empty()); |
| const auto seek_to = recurrence_range.end - 1; |
| current.SeekAfterModification(seek_to); |
| } |
| if (current.range.non_empty()) { |
| current.NextRange(); |
| } |
| } |
| |
| // Infill the remainder, which is empty for both the current and resolve contexts |
| if (current.range.end < range.end) { |
| AccessRange trailing_fill_range = {current.range.end, range.end}; |
| ResolveGapsRecursePrev(trailing_fill_range, resolve_context, infill, barrier_action); |
| } |
| } |
| |
| void AccessContext::ResolveGapsRecursePrev(const AccessRange &range, AccessContext &descent_context, bool infill, |
| const AccessStateFunction &previous_barrier_action) const { |
| assert(range.non_empty()); |
| AccessMap &descent_map = descent_context.access_state_map_; |
| if (prev_.empty()) { |
| if (infill) { |
| AccessState access_state = AccessState::DefaultAccessState(); |
| |
| // The following is not needed for correctness but is rather an optimization. We are going to fill |
| // the gaps and the application of the global barriers to an empty state is noop (nothing is in the |
| // barrier's source scope). Update the index to skip application of the registered global barriers. |
| access_state.next_global_barrier_index = descent_context.GetGlobalBarrierCount(); |
| |
| previous_barrier_action(&access_state); |
| descent_map.InfillGaps(range, access_state); |
| } |
| } else { |
| for (const auto &prev_dep : prev_) { |
| const ApplyTrackbackStackAction barrier_action(prev_dep.barriers, &previous_barrier_action); |
| prev_dep.source_subpass->ResolveAccessRangeRecursePrev(range, barrier_action, descent_context, infill); |
| } |
| } |
| } |
| |
| AccessMap::iterator AccessContext::ResolveGapRecursePrev(const AccessRange &gap_range, AccessMap::iterator pos_hint) { |
| assert(gap_range.non_empty()); |
| if (prev_.empty()) { |
| AccessState access_state = AccessState::DefaultAccessState(); |
| |
| // The following is not needed for correctness but is rather an optimization. We are going to fill |
| // the gaps and the application of the global barriers to an empty state is noop (nothing is in the |
| // barrier's source scope). Update the index to skip application of the registered global barriers. |
| access_state.next_global_barrier_index = GetGlobalBarrierCount(); |
| |
| return access_state_map_.InfillGap(pos_hint, gap_range, access_state); |
| } else { |
| for (const auto &prev_dep : prev_) { |
| const ApplyTrackbackStackAction barrier_action(prev_dep.barriers, nullptr); |
| prev_dep.source_subpass->ResolveAccessRangeRecursePrev(gap_range, barrier_action, *this, true); |
| } |
| return access_state_map_.LowerBound(gap_range.begin); |
| } |
| } |
| |
| // Update memory access state over the given range. |
| // This inserts new accesses for empty regions and updates existing accesses. |
| // The passed pos must either be a lower bound (can be the end iterator) or be strictly less than the range. |
| // Map entries that intersect range.begin or range.end are split at the intersection point. |
| AccessMap::iterator AccessContext::DoUpdateAccessState(AccessMap::iterator pos, const AccessRange &range, |
| SyncAccessIndex access_index, const AttachmentAccess &attachment_access, |
| ResourceUsageTagEx tag_ex, SyncFlags flags) { |
| assert(range.non_empty()); |
| const SyncAccessInfo &access_info = GetAccessInfo(access_index); |
| |
| const auto end = access_state_map_.end(); |
| assert(pos == access_state_map_.LowerBound(range.begin) || pos->first.strictly_less(range)); |
| |
| if (pos != end && pos->first.strictly_less(range)) { |
| // pos is not a lower bound for the range (pos < range), but if the range is |
| // monotonically increasing, the next map entry may be the lower bound |
| ++pos; |
| |
| // If the new pos is not a lower bound, run the full search |
| if (pos != end && pos->first.strictly_less(range)) { |
| pos = access_state_map_.LowerBound(range.begin); |
| } |
| } |
| assert(pos == access_state_map_.LowerBound(range.begin)); |
| |
| if (pos != end && range.begin > pos->first.begin) { |
| // Lower bound starts before the range. |
| // Split the entry so that a new entry starts exactly at the range.begin |
| pos = access_state_map_.Split(pos, range.begin); |
| ++pos; |
| } |
| |
| AccessMap::index_type current_begin = range.begin; |
| while (pos != end && current_begin < range.end) { |
| if (current_begin < pos->first.begin) { // infill the gap |
| // Infill the gap with an empty access state or, if the previous contexts |
| // exists (subpass case), derive the infill state from them |
| const AccessRange gap_range(current_begin, std::min(range.end, pos->first.begin)); |
| AccessMap::iterator infilled_it = ResolveGapRecursePrev(gap_range, pos); |
| |
| // Update |
| AccessState &new_access_state = infilled_it->second; |
| ApplyGlobalBarriers(new_access_state); |
| new_access_state.Update(access_info, attachment_access, tag_ex, flags); |
| |
| // Advance current location. |
| // Do not advance pos, as it's the next map entry to visit |
| current_begin = pos->first.begin; |
| } else { // update existing entry |
| assert(current_begin == pos->first.begin); |
| |
| // Split the current map entry if it goes beyond range.end. |
| // This ensures the update is restricted to the given range. |
| if (pos->first.end > range.end) { |
| pos = access_state_map_.Split(pos, range.end); |
| } |
| |
| // Update |
| AccessState &access_state = pos->second; |
| ApplyGlobalBarriers(access_state); |
| access_state.Update(access_info, attachment_access, tag_ex, flags); |
| |
| // Advance both current location and map entry |
| current_begin = pos->first.end; |
| ++pos; |
| } |
| } |
| |
| // Fill to the end if needed |
| if (current_begin < range.end) { |
| const AccessRange gap_range(current_begin, range.end); |
| AccessMap::iterator infilled_it = ResolveGapRecursePrev(gap_range, pos); |
| |
| // Update |
| AccessState &new_access_state = infilled_it->second; |
| ApplyGlobalBarriers(new_access_state); |
| new_access_state.Update(access_info, attachment_access, tag_ex, flags); |
| } |
| return pos; |
| } |
| |
| void AccessContext::UpdateAccessState(const vvl::Buffer &buffer, SyncAccessIndex current_usage, const AccessRange &range, |
| ResourceUsageTagEx tag_ex, SyncFlags flags) { |
| assert(range.valid()); |
| assert(!finalized_); |
| |
| if (current_usage == SYNC_ACCESS_INDEX_NONE) { |
| return; |
| } |
| if (!SimpleBinding(buffer)) { |
| return; |
| } |
| if (range.empty()) { |
| return; |
| } |
| |
| const VkDeviceSize base_address = ResourceBaseAddress(buffer); |
| const AccessRange buffer_range = range + base_address; |
| |
| auto pos = access_state_map_.LowerBound(buffer_range.begin); |
| DoUpdateAccessState(pos, buffer_range, current_usage, AttachmentAccess::NonAttachment(), tag_ex, flags); |
| } |
| |
| void AccessContext::UpdateAccessState(ImageRangeGen &range_gen, SyncAccessIndex current_usage, ResourceUsageTagEx tag_ex, |
| SyncFlags flags) { |
| assert(!finalized_); |
| if (current_usage == SYNC_ACCESS_INDEX_NONE) { |
| return; |
| } |
| auto pos = access_state_map_.LowerBound(range_gen->begin); |
| for (; range_gen->non_empty(); ++range_gen) { |
| pos = DoUpdateAccessState(pos, *range_gen, current_usage, AttachmentAccess::NonAttachment(), tag_ex, flags); |
| } |
| } |
| |
| void AccessContext::UpdateAccessState(ImageRangeGen &range_gen, SyncAccessIndex current_usage, |
| const AttachmentAccess &attachment_access, ResourceUsageTagEx tag_ex) { |
| assert(!finalized_); |
| if (current_usage == SYNC_ACCESS_INDEX_NONE) { |
| return; |
| } |
| auto pos = access_state_map_.LowerBound(range_gen->begin); |
| for (; range_gen->non_empty(); ++range_gen) { |
| pos = DoUpdateAccessState(pos, *range_gen, current_usage, attachment_access, tag_ex, 0); |
| } |
| } |
| |
| void AccessContext::ResolveChildContexts(vvl::span<AccessContext> subpass_contexts) { |
| assert(!finalized_); |
| |
| for (AccessContext &context : subpass_contexts) { |
| ApplyTrackbackStackAction barrier_action(context.GetDstExternalTrackBack().barriers); |
| context.ResolveAccessRange(kFullRange, barrier_action, *this); |
| } |
| } |
| |
| // Caller must ensure that lifespan of this is less than the lifespan of from |
| void AccessContext::ImportAsyncContexts(const AccessContext &from) { |
| async_.insert(async_.end(), from.async_.begin(), from.async_.end()); |
| } |
| |
| void AccessContext::AddAsyncContext(const AccessContext *context, ResourceUsageTag tag, QueueId queue_id) { |
| if (context) { |
| async_.emplace_back(*context, tag, queue_id); |
| } |
| } |
| |
| void SortedFirstAccesses::Init(const AccessMap &finalized_access_map) { |
| for (const auto &entry : finalized_access_map) { |
| const AccessState &access = entry.second; |
| const ResourceUsageRange range = access.GetFirstAccessRange(); |
| if (range.empty()) { |
| continue; |
| } |
| // Access map is not going to be updated (finalized) and we can store references to map entries |
| if (range.size() == 1) { |
| sorted_single_tags.emplace_back(SingleTag{range.begin, &entry}); |
| } else { |
| sorted_multi_tags.emplace_back(MultiTag{range, &entry}); |
| } |
| } |
| std::sort(sorted_single_tags.begin(), sorted_single_tags.end(), |
| [](const SingleTag &a, const SingleTag &b) { return a.tag < b.tag; }); |
| std::sort(sorted_multi_tags.begin(), sorted_multi_tags.end(), |
| [](const auto &a, const auto &b) { return a.range.begin < b.range.begin; }); |
| } |
| |
| void SortedFirstAccesses::Clear() { |
| sorted_single_tags.clear(); |
| sorted_multi_tags.clear(); |
| } |
| |
| std::vector<SortedFirstAccesses::SingleTag>::const_iterator SortedFirstAccesses::SingleTagRange::begin() { |
| return std::lower_bound(sorted_single_tags.begin(), sorted_single_tags.end(), tag_range.begin, |
| [](const SingleTag &single_tag, ResourceUsageTag tag) { return single_tag.tag < tag; }); |
| } |
| |
| std::vector<SortedFirstAccesses::SingleTag>::const_iterator SortedFirstAccesses::SingleTagRange::end() { |
| return std::lower_bound(sorted_single_tags.begin(), sorted_single_tags.end(), tag_range.end, |
| [](const SingleTag &single_tag, ResourceUsageTag tag) { return single_tag.tag < tag; }); |
| } |
| |
| SortedFirstAccesses::SingleTagRange SortedFirstAccesses::IterateSingleTagFirstAccesses(const ResourceUsageRange &tag_range) const { |
| return SingleTagRange{this->sorted_single_tags, tag_range}; |
| } |
| |
| std::vector<SortedFirstAccesses::MultiTag>::const_iterator SortedFirstAccesses::MultiTagRange::begin() { |
| return sorted_multi_tags.begin(); |
| } |
| |
| std::vector<SortedFirstAccesses::MultiTag>::const_iterator SortedFirstAccesses::MultiTagRange::end() { |
| return std::lower_bound(sorted_multi_tags.begin(), sorted_multi_tags.end(), tag_range.end, |
| [](const MultiTag &multi_tag, ResourceUsageTag tag) { return multi_tag.range.begin < tag; }); |
| } |
| |
| SortedFirstAccesses::MultiTagRange SortedFirstAccesses::IterateMultiTagFirstAccesses(const ResourceUsageRange &tag_range) const { |
| return MultiTagRange{this->sorted_multi_tags, tag_range}; |
| } |
| |
| // For RenderPass time validation this is "start tag", for QueueSubmit, this is the earliest |
| // unsynchronized tag for the Queue being tested against (max synchrononous + 1, perhaps) |
| ResourceUsageTag AccessContext::AsyncReference::StartTag() const { return (tag_ == kInvalidTag) ? context_->StartTag() : tag_; } |
| |
| AttachmentViewGen::AttachmentViewGen(const vvl::ImageView *image_view, const VkOffset3D &offset, const VkExtent3D &extent) |
| : view_(image_view) { |
| gen_store_[Gen::kViewSubresource].emplace(MakeImageRangeGen(*image_view)); |
| |
| const bool has_depth = vkuFormatHasDepth(image_view->create_info.format); |
| const bool has_stencil = vkuFormatHasStencil(image_view->create_info.format); |
| |
| // For depth-stencil attachment, the view's aspect flags are ignored according to the spec. |
| // MakeImageRangeGen works with the aspect flags. Derive aspect from format. |
| VkImageAspectFlags override_aspect_flags = 0; |
| if (has_depth || has_stencil) { |
| override_aspect_flags |= has_depth ? VK_IMAGE_ASPECT_DEPTH_BIT : 0; |
| override_aspect_flags |= has_stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0; |
| } |
| |
| // Range gen for attachment's render area |
| gen_store_[Gen::kRenderArea].emplace(MakeImageRangeGen(*image_view, offset, extent, override_aspect_flags)); |
| |
| // If attachment has both depth and stencil aspects then add range gens to represent each aspect separately. |
| if (has_depth && has_stencil) { |
| gen_store_[Gen::kDepthOnlyRenderArea].emplace(MakeImageRangeGen(*image_view, offset, extent, VK_IMAGE_ASPECT_DEPTH_BIT)); |
| gen_store_[Gen::kStencilOnlyRenderArea].emplace( |
| MakeImageRangeGen(*image_view, offset, extent, VK_IMAGE_ASPECT_STENCIL_BIT)); |
| } |
| } |
| |
| const ImageRangeGen &AttachmentViewGen::GetRangeGen(AttachmentViewGen::Gen type) const { |
| static_assert(Gen::kGenSize == 4, "Function written with this assumption"); |
| // If the view is a depth only view, then the depth only portion of the render area is simply the render area. |
| // If the view is a depth stencil view, then the depth only portion of the render area will be a subset, |
| // and thus needs the generator function that will produce the address ranges of that subset |
| const bool depth_only = (type == kDepthOnlyRenderArea) && vkuFormatIsDepthOnly(view_->create_info.format); |
| const bool stencil_only = (type == kStencilOnlyRenderArea) && vkuFormatIsStencilOnly(view_->create_info.format); |
| if (depth_only || stencil_only) { |
| type = Gen::kRenderArea; |
| } |
| assert(gen_store_[type].has_value()); |
| return *gen_store_[type]; |
| } |
| |
| AttachmentViewGen::Gen AttachmentViewGen::GetDepthStencilRenderAreaGenType(bool depth_op, bool stencil_op) const { |
| assert(vkuFormatIsDepthOrStencil(view_->create_info.format)); |
| if (depth_op) { |
| assert(vkuFormatHasDepth(view_->create_info.format)); |
| if (stencil_op) { |
| assert(vkuFormatHasStencil(view_->create_info.format)); |
| return kRenderArea; |
| } |
| return kDepthOnlyRenderArea; |
| } |
| if (stencil_op) { |
| assert(vkuFormatHasStencil(view_->create_info.format)); |
| return kStencilOnlyRenderArea; |
| } |
| assert(depth_op || stencil_op); |
| return kRenderArea; |
| } |
| |
| } // namespace syncval |