blob: 3e38f92a19c2a1593544baa0fbb0ce8295131843 [file]
/*
* 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<SubpassDependencyInfo>& subpass_dependency_infos, const AccessContext* contexts,
const AccessContext& external_context) {
const SubpassDependencyInfo& info = subpass_dependency_infos[subpass];
async_.reserve(info.async.size());
for (const uint32_t async_subpass : info.async) {
// Start tags are not known at creation time (as it's done at BeginRenderpass)
async_.emplace_back(contexts[async_subpass], kInvalidTag, kQueueIdInvalid);
}
// Initialize barriers for the preceding subpasses and the external src barrier.
// To resolve contexts, we usually need regular subpass contexts and the external
// src context, so the corresponding barriers are stored together.
subpass_barriers_.resize(subpass + 1);
for (const auto& [src_subpass, subpass_dependencies] : info.dependencies) {
subpass_barriers_[src_subpass] = SubpassBarrier(contexts[src_subpass], queue_flags, subpass_dependencies);
}
subpass_barriers_[subpass] = SubpassBarrier(external_context, queue_flags, info.barrier_from_external);
// External dst barrier
dst_external_ = SubpassBarrier(*this, queue_flags, info.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_);
async_ = other.async_;
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();
// TODO: the following assignments look incorrect: the copies will reference the old context.
// Find a scenario when this does not work, write a test and make a fix.
subpass_barriers_ = other.subpass_barriers_;
dst_external_ = other.dst_external_;
}
void AccessContext::Reset() {
access_state_map_.Clear();
async_.clear();
start_tag_ = {};
ResetGlobalBarriers();
finalized_ = false;
sorted_first_accesses_.Clear();
subpass_barriers_.clear();
dst_external_ = {};
}
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);
}
}
const SubpassBarrier& AccessContext::GetSubpassBarrier(uint32_t src_subpass) const {
if (src_subpass == VK_SUBPASS_EXTERNAL) {
return subpass_barriers_.back();
} else {
assert(subpass_barriers_[src_subpass].src_subpass_context != nullptr);
return subpass_barriers_[src_subpass];
}
}
void AccessContext::ResolveFromContext(const AccessContext& from) {
assert(!finalized_);
auto noop_action = [](AccessState* access) {};
from.ResolveAccessRangeRecursePrev(kFullRange, noop_action, *this, false);
}
void AccessContext::ResolveFromSubpassContext(const ApplySubpassTransitionBarrierAction& 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::ResolveAllSubpassDependencies() {
assert(!finalized_);
ResolveSubpassDependencies(kFullRange, *this, true);
}
void AccessContext::ResolveSubpassDependencies(const AccessRange& range, AccessContext& resolve_context, bool infill,
const AccessStateFunction* previous_barrier_action) const {
for (const SubpassBarrier& subpass_barrier : subpass_barriers_) {
if (subpass_barrier.src_subpass_context) {
const ApplySubpassBarrierAction barrier_action(subpass_barrier, previous_barrier_action);
subpass_barrier.src_subpass_context->ResolveAccessRangeRecursePrev(range, barrier_action, resolve_context, infill);
}
}
}
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());
if (!subpass_barriers_.empty()) {
ResolveSubpassDependencies(range, descent_context, infill, &previous_barrier_action);
return;
}
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_context.access_state_map_.InfillGaps(range, access_state);
}
}
AccessMap::iterator AccessContext::ResolveGapRecursePrev(const AccessRange& gap_range, AccessMap::iterator pos_hint) {
assert(gap_range.non_empty());
if (!subpass_barriers_.empty()) {
ResolveSubpassDependencies(gap_range, *this, true);
return access_state_map_.LowerBound(gap_range.begin);
}
AccessState access_state = AccessState::DefaultAccessState();
// The next line 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);
}
// 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::UpdateAttachmentAccessState(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::UpdateAttachmentAccessState(const AttachmentViewGen& view_gen, AttachmentViewGen::Gen gen_type,
SyncAccessIndex current_usage, const AttachmentAccess& attachment_access,
ResourceUsageTagEx tag_ex, uint32_t view_mask) {
if (view_mask == 0) {
ImageRangeGen range_gen = view_gen.GetRangeGen(gen_type);
UpdateAttachmentAccessState(range_gen, current_usage, attachment_access, tag_ex);
} else {
uint32_t view_index = 0;
while (view_mask) {
if (view_mask & 1) {
ImageRangeGen range_gen = view_gen.GetRangeGen(gen_type, view_index);
UpdateAttachmentAccessState(range_gen, current_usage, attachment_access, tag_ex);
}
view_mask >>= 1;
view_index++;
}
}
}
void AccessContext::ResolveChildContexts(vvl::span<AccessContext> subpass_contexts) {
assert(!finalized_);
for (AccessContext& context : subpass_contexts) {
ApplySubpassBarrierAction barrier_action(context.GetDstExternalSubpassBarrier());
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));
}
}
ImageRangeGen AttachmentViewGen::GetRangeGen(AttachmentViewGen::Gen type, uint32_t view_index) const {
// Restrict image view's subresource range to a specific multiview layer
if (view_index != vvl::kNoIndex32) {
VkImageSubresourceRange subresource = view_->normalized_subresource_range;
if (view_index >= subresource.layerCount) {
return {}; // invalid view index
}
subresource.baseArrayLayer += view_index;
subresource.layerCount = 1;
auto range_gen = SubState(*view_->image_state).MakeImageRangeGen(subresource, view_->is_depth_sliced);
return range_gen;
}
// If a single aspect of depth-stencil attachment is requested, but the attachment actually
// consist of a single aspect, then the render area's range gen is what was requested
// (in this case we also don't cache separate depth/stencil-only range gens).
const bool asked_depth_aspect_for_depth_only =
(type == kDepthOnlyRenderArea) && vkuFormatIsDepthOnly(view_->create_info.format);
const bool asked_stencil_aspect_for_stencil_only =
(type == kStencilOnlyRenderArea) && vkuFormatIsStencilOnly(view_->create_info.format);
if (asked_depth_aspect_for_depth_only || asked_stencil_aspect_for_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;
}
SubpassBarrier::SubpassBarrier(const AccessContext& src_subpass_context, VkQueueFlags queue_flags,
const std::vector<const VkSubpassDependency2*>& subpass_dependencies)
: src_subpass_context(&src_subpass_context) {
barriers.reserve(subpass_dependencies.size());
for (const VkSubpassDependency2* dependency : subpass_dependencies) {
barriers.emplace_back(queue_flags, *dependency);
}
}
} // namespace syncval