blob: e611e86b37ee94bb8331addfbb4da35578e66ad7 [file] [log] [blame]
/*
* 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