blob: 40768c313b1375d206069d59e763d8a227372c84 [file] [log] [blame]
/*
* Copyright (c) 2019-2024 Valve Corporation
* Copyright (c) 2019-2024 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.
*/
#pragma once
#include "sync/sync_common.h"
#include "sync/sync_access_state.h"
struct SubpassDependencyGraphNode;
namespace vvl {
class Buffer;
class VideoSession;
class VideoPictureResource;
class Bindable;
class Event;
} // namespace vvl
namespace syncval_state {
class ImageState;
class ImageViewState;
} // namespace syncval_state
bool SimpleBinding(const vvl::Bindable &bindable);
VkDeviceSize ResourceBaseAddress(const vvl::Buffer &buffer);
// ForEachEntryInRangesUntil -- Execute Action for each map entry in the generated ranges until it returns true
//
// Action is const w.r.t. map
// Action is allowed (assumed) to modify pos
// Action must not advance pos for ranges strictly < pos->first
// Action must handle range strictly less than pos->first correctly
// Action must handle pos == end correctly
// Action is assumed to only require invocation once per map entry
// RangeGen must be strictly monotonic
// Note: If Action invocations are heavyweight and inter-entry (gap) calls are not needed
// add a template or function parameter to skip them. TBD.
template <typename RangeMap, typename RangeGen, typename Action>
bool ForEachEntryInRangesUntil(const RangeMap &map, RangeGen &range_gen, Action &action) {
using RangeType = typename RangeGen::RangeType;
using IndexType = typename RangeType::index_type;
auto pos = map.lower_bound(*range_gen);
const auto end = map.end();
IndexType skip_limit = 0;
for (; range_gen->non_empty() && pos != end; ++range_gen) {
RangeType range = *range_gen;
// See if a prev pos has covered this range
if (range.end <= skip_limit) {
// Since the map is const, we needn't call action on the same pos again
continue;
}
// If the current range was *partially* covered be a previous pos, trim, such that Action is only
// called once for a given range (and pos)
if (range.begin < skip_limit) {
range.begin = skip_limit;
}
// Now advance pos as needed to match range
if (pos->first.strictly_less(range)) {
++pos;
if (pos == end) break;
if (pos->first.strictly_less(range)) {
pos = map.lower_bound(range);
if (pos == end) break;
}
assert(pos == map.lower_bound(range));
}
// If the range intersects pos->first, consider Action performed for that map entry, and
// make sure not to call Action for this pos for any subsequent ranges
skip_limit = range.end > pos->first.begin ? pos->first.end : 0U;
// Action is allowed to alter pos but shouldn't do so if range is strictly < pos->first
if (action(range, end, pos)) return true;
}
// Action needs to handle the "at end " condition (and can be useful for recursive actions)
for (; range_gen->non_empty(); ++range_gen) {
if (action(*range_gen, end, pos)) return true;
}
return false;
}
struct NoopBarrierAction {
explicit NoopBarrierAction() {}
void operator()(ResourceAccessState *access) const {}
const bool layout_transition = false;
};
// This functor applies a collection of barriers, updating the "pending state" in each touched memory range, and optionally
// resolves the pending state. Suitable for processing Global memory barriers, or Subpass Barriers when the "final" barrier
// of a collection is known/present.
template <typename BarrierOp, typename OpVector = std::vector<BarrierOp>>
class ApplyBarrierOpsFunctor {
public:
using Iterator = ResourceAccessRangeMap::iterator;
// Only called with a gap, and pos at the lower_bound(range)
inline Iterator Infill(ResourceAccessRangeMap *accesses, const Iterator &pos, const ResourceAccessRange &range) const {
if (!infill_default_) {
return pos;
}
ResourceAccessState default_state;
auto inserted = accesses->insert(pos, std::make_pair(range, default_state));
return inserted;
}
void operator()(const Iterator &pos) const {
auto &access_state = pos->second;
for (const auto &op : barrier_ops_) {
op(&access_state);
}
if (resolve_) {
// If this is the last (or only) batch, we can do the pending resolve as the last step in this operation to avoid
// another walk
access_state.ApplyPendingBarriers(tag_);
}
}
// A valid tag is required IFF layout_transition is true, as transitions are write ops
ApplyBarrierOpsFunctor(bool resolve, typename OpVector::size_type size_hint, ResourceUsageTag tag)
: resolve_(resolve), infill_default_(false), barrier_ops_(), tag_(tag) {
barrier_ops_.reserve(size_hint);
}
void EmplaceBack(const BarrierOp &op) {
barrier_ops_.emplace_back(op);
infill_default_ |= op.layout_transition;
}
private:
bool resolve_;
bool infill_default_;
OpVector barrier_ops_;
const ResourceUsageTag tag_;
};
// This functor applies a single barrier, updating the "pending state" in each touched memory range, but does not
// resolve the pendinging state. Suitable for processing Image and Buffer barriers from PipelineBarriers or Events
template <typename BarrierOp>
class ApplyBarrierFunctor : public ApplyBarrierOpsFunctor<BarrierOp, small_vector<BarrierOp, 1>> {
using Base = ApplyBarrierOpsFunctor<BarrierOp, small_vector<BarrierOp, 1>>;
public:
ApplyBarrierFunctor(const BarrierOp &barrier_op) : Base(false, 1, kInvalidTag) { Base::EmplaceBack(barrier_op); }
};
// This functor resolves the pendinging state.
class ResolvePendingBarrierFunctor : public ApplyBarrierOpsFunctor<NoopBarrierAction, small_vector<NoopBarrierAction, 1>> {
using Base = ApplyBarrierOpsFunctor<NoopBarrierAction, small_vector<NoopBarrierAction, 1>>;
public:
ResolvePendingBarrierFunctor(ResourceUsageTag tag) : Base(true, 0, tag) {}
};
struct ApplySubpassTransitionBarriersAction {
explicit ApplySubpassTransitionBarriersAction(const std::vector<SyncBarrier> &barriers_) : barriers(barriers_) {}
void operator()(ResourceAccessState *access) const {
assert(access);
access->ApplyBarriers(barriers, true);
}
const std::vector<SyncBarrier> &barriers;
};
struct QueueTagOffsetBarrierAction {
QueueTagOffsetBarrierAction(QueueId qid, ResourceUsageTag offset) : queue_id(qid), tag_offset(offset) {}
void operator()(ResourceAccessState *access) const {
access->OffsetTag(tag_offset);
access->SetQueueId(queue_id);
};
QueueId queue_id;
ResourceUsageTag tag_offset;
};
struct ApplyTrackbackStackAction {
explicit ApplyTrackbackStackAction(const std::vector<SyncBarrier> &barriers_,
const ResourceAccessStateFunction *previous_barrier_ = nullptr)
: barriers(barriers_), previous_barrier(previous_barrier_) {}
void operator()(ResourceAccessState *access) const {
assert(access);
assert(!access->HasPendingState());
access->ApplyBarriers(barriers, false);
// NOTE: We can use invalid tag, as these barriers do no include layout transitions (would assert in SetWrite)
access->ApplyPendingBarriers(kInvalidTag);
if (previous_barrier) {
assert(bool(*previous_barrier));
(*previous_barrier)(access);
}
}
const std::vector<SyncBarrier> &barriers;
const ResourceAccessStateFunction *previous_barrier;
};
template <typename SubpassNode>
struct SubpassBarrierTrackback {
std::vector<SyncBarrier> barriers;
const SubpassNode *source_subpass = nullptr;
SubpassBarrierTrackback() = default;
SubpassBarrierTrackback(const SubpassBarrierTrackback &) = default;
SubpassBarrierTrackback(const SubpassNode *source_subpass_, VkQueueFlags queue_flags_,
const std::vector<const VkSubpassDependency2 *> &subpass_dependencies_)
: barriers(), source_subpass(source_subpass_) {
barriers.reserve(subpass_dependencies_.size());
for (const VkSubpassDependency2 *dependency : subpass_dependencies_) {
assert(dependency);
barriers.emplace_back(queue_flags_, *dependency);
}
}
SubpassBarrierTrackback(const SubpassNode *source_subpass_, const SyncBarrier &barrier_)
: barriers(1, barrier_), source_subpass(source_subpass_) {}
SubpassBarrierTrackback &operator=(const SubpassBarrierTrackback &) = default;
};
class AttachmentViewGen {
public:
enum Gen { kViewSubresource = 0, kRenderArea = 1, kDepthOnlyRenderArea = 2, kStencilOnlyRenderArea = 3, kGenSize = 4 };
AttachmentViewGen(const syncval_state::ImageViewState *image_view, const VkOffset3D &offset, const VkExtent3D &extent);
AttachmentViewGen(const AttachmentViewGen &other) = default;
AttachmentViewGen(AttachmentViewGen &&other) = default;
const syncval_state::ImageViewState *GetViewState() const { return view_; }
const std::optional<ImageRangeGen> &GetRangeGen(Gen type) const;
bool IsValid() const { return gen_store_[Gen::kViewSubresource].has_value(); }
Gen GetDepthStencilRenderAreaGenType(bool depth_op, bool stencil_op) const;
private:
const syncval_state::ImageViewState *view_ = nullptr;
VkImageAspectFlags view_mask_ = 0U;
std::array<std::optional<ImageRangeGen>, Gen::kGenSize> gen_store_;
};
using AttachmentViewGenVector = std::vector<AttachmentViewGen>;
class AccessContext {
public:
using ImageState = syncval_state::ImageState;
using ImageViewState = syncval_state::ImageViewState;
using ScopeMap = ResourceAccessRangeMap;
enum DetectOptions : uint32_t {
kDetectPrevious = 1U << 0,
kDetectAsync = 1U << 1,
kDetectAll = (kDetectPrevious | kDetectAsync)
};
using TrackBack = SubpassBarrierTrackback<AccessContext>;
HazardResult DetectHazard(const vvl::Buffer &buffer, SyncAccessIndex access_index, const ResourceAccessRange &range) const;
HazardResult DetectHazard(const ImageState &image, SyncAccessIndex current_usage,
const VkImageSubresourceRange &subresource_range, bool is_depth_sliced) const;
HazardResult DetectHazard(const ImageViewState &image_view, SyncAccessIndex current_usage) const;
HazardResult DetectHazard(const ImageRangeGen &ref_range_gen, SyncAccessIndex current_usage,
SyncOrdering ordering_rule = SyncOrdering::kOrderingNone) const;
HazardResult DetectHazard(const ImageViewState &image_view, const VkOffset3D &offset, const VkExtent3D &extent,
SyncAccessIndex current_usage, SyncOrdering ordering_rule) const;
HazardResult DetectHazard(const AttachmentViewGen &view_gen, AttachmentViewGen::Gen gen_type, SyncAccessIndex current_usage,
SyncOrdering ordering_rule) const;
HazardResult DetectHazard(const vvl::VideoSession &vs_state, const vvl::VideoPictureResource &resource,
SyncAccessIndex current_usage) const;
HazardResult DetectHazard(const ImageState &image, const VkImageSubresourceRange &subresource_range, const VkOffset3D &offset,
const VkExtent3D &extent, bool is_depth_sliced, SyncAccessIndex current_usage,
SyncOrdering ordering_rule = SyncOrdering::kOrderingNone) const;
HazardResult DetectImageBarrierHazard(const ImageState &image, const VkImageSubresourceRange &subresource_range,
VkPipelineStageFlags2 src_exec_scope, const SyncAccessFlags &src_access_scope,
QueueId queue_id, const ScopeMap &scope_map, ResourceUsageTag scope_tag,
DetectOptions options) const;
HazardResult DetectImageBarrierHazard(const AttachmentViewGen &attachment_view, const SyncBarrier &barrier,
DetectOptions options) const;
HazardResult DetectImageBarrierHazard(const ImageState &image, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope, const VkImageSubresourceRange &subresource_range,
DetectOptions options) const;
HazardResult DetectSubpassTransitionHazard(const TrackBack &track_back, const AttachmentViewGen &attach_view) const;
HazardResult DetectFirstUseHazard(QueueId queue_id, const ResourceUsageRange &tag_range,
const AccessContext &access_context) const;
const TrackBack &GetDstExternalTrackBack() const { return dst_external_; }
void Reset() {
prev_.clear();
prev_by_subpass_.clear();
async_.clear();
src_external_ = nullptr;
dst_external_ = TrackBack();
start_tag_ = ResourceUsageTag();
access_state_map_.clear();
}
void ResolvePreviousAccesses();
void ResolveFromContext(const AccessContext &from);
template <typename ResolveOp>
void ResolveFromContext(ResolveOp &&resolve_op, const AccessContext &from_context,
const ResourceAccessState *infill_state = nullptr, bool recur_to_infill = false);
template <typename ResolveOp, typename RangeGenerator>
void ResolveFromContext(ResolveOp &&resolve_op, const AccessContext &from_context, RangeGenerator range_gen,
const ResourceAccessState *infill_state = nullptr, bool recur_to_infill = false);
void UpdateAccessState(const vvl::Buffer &buffer, SyncAccessIndex current_usage, SyncOrdering ordering_rule,
const ResourceAccessRange &range, ResourceUsageTagEx tag_ex);
void UpdateAccessState(const ImageState &image, SyncAccessIndex current_usage, SyncOrdering ordering_rule,
const VkImageSubresourceRange &subresource_range, const ResourceUsageTag &tag);
void UpdateAccessState(const ImageState &image, SyncAccessIndex current_usage, SyncOrdering ordering_rule,
const VkImageSubresourceRange &subresource_range, const VkOffset3D &offset, const VkExtent3D &extent,
ResourceUsageTagEx tag_ex);
void UpdateAccessState(const AttachmentViewGen &view_gen, AttachmentViewGen::Gen gen_type, SyncAccessIndex current_usage,
SyncOrdering ordering_rule, ResourceUsageTag tag);
void UpdateAccessState(const ImageViewState &image_view, SyncAccessIndex current_usage, SyncOrdering ordering_rule,
const VkOffset3D &offset, const VkExtent3D &extent, ResourceUsageTagEx tag_ex);
void UpdateAccessState(const ImageViewState &image_view, SyncAccessIndex current_usage, SyncOrdering ordering_rule,
ResourceUsageTagEx tag_ex);
void UpdateAccessState(const ImageRangeGen &range_gen, SyncAccessIndex current_usage, SyncOrdering ordering_rule,
ResourceUsageTagEx tag_ex);
void UpdateAccessState(ImageRangeGen &range_gen, SyncAccessIndex current_usage, SyncOrdering ordering_rule,
ResourceUsageTagEx tag_ex);
void UpdateAccessState(const vvl::VideoSession &vs_state, const vvl::VideoPictureResource &resource,
SyncAccessIndex current_usage, ResourceUsageTag tag);
void ResolveChildContexts(const std::vector<AccessContext> &contexts);
void ImportAsyncContexts(const AccessContext &from);
void ClearAsyncContexts() { async_.clear(); }
template <typename Action>
void ApplyUpdateAction(const AttachmentViewGen &view_gen, AttachmentViewGen::Gen gen_type, const Action &action);
template <typename Action>
void ApplyToContext(const Action &barrier_action);
AccessContext(uint32_t subpass, VkQueueFlags queue_flags, const std::vector<SubpassDependencyGraphNode> &dependencies,
const std::vector<AccessContext> &contexts, const AccessContext *external_context);
AccessContext() { Reset(); }
AccessContext(const AccessContext &copy_from) = default;
void Trim();
void TrimAndClearFirstAccess();
void AddReferencedTags(ResourceUsageTagSet &referenced) const;
ResourceAccessRangeMap &GetAccessStateMap() { return access_state_map_; }
const ResourceAccessRangeMap &GetAccessStateMap() const { return access_state_map_; }
const TrackBack *GetTrackBackFromSubpass(uint32_t subpass) const {
if (subpass == VK_SUBPASS_EXTERNAL) {
return src_external_;
} else {
assert(subpass < prev_by_subpass_.size());
return prev_by_subpass_[subpass];
}
}
void SetStartTag(ResourceUsageTag tag) { start_tag_ = tag; }
ResourceUsageTag StartTag() const { return start_tag_; }
template <typename Action>
void ForAll(Action &&action);
template <typename Action>
void ConstForAll(Action &&action) const;
template <typename Predicate>
void EraseIf(Predicate &&pred);
// For use during queue submit building up the QueueBatchContext AccessContext for validation, otherwise clear.
void AddAsyncContext(const AccessContext *context, ResourceUsageTag tag, QueueId queue_id);
class AsyncReference {
public:
AsyncReference(const AccessContext &async_context, ResourceUsageTag async_tag, QueueId queue_id)
: context_(&async_context), tag_(async_tag), queue_id_(queue_id) {}
const AccessContext &Context() const { return *context_; }
// 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 StartTag() const;
QueueId GetQueueId() const { return queue_id_; }
protected:
const AccessContext *context_;
ResourceUsageTag tag_; // Start of open ended asynchronous range
QueueId queue_id_;
};
template <typename Action, typename RangeGen>
void UpdateMemoryAccessState(const Action &action, RangeGen &range_gen);
private:
template <typename Action>
static void UpdateMemoryAccessRangeState(ResourceAccessRangeMap &accesses, Action &action, const ResourceAccessRange &range);
struct UpdateMemoryAccessStateFunctor {
using Iterator = ResourceAccessRangeMap::iterator;
Iterator Infill(ResourceAccessRangeMap *accesses, const Iterator &pos, const ResourceAccessRange &range) const;
void operator()(const Iterator &pos) const;
UpdateMemoryAccessStateFunctor(const AccessContext &context_, SyncAccessIndex usage_, SyncOrdering ordering_rule_,
ResourceUsageTagEx tag_ex)
: context(context_), usage_info(SyncStageAccess::AccessInfo(usage_)), ordering_rule(ordering_rule_), tag_ex(tag_ex) {}
const AccessContext &context;
const SyncAccessInfo &usage_info;
const SyncOrdering ordering_rule;
const ResourceUsageTagEx tag_ex;
};
// Follow the context previous to access the access state, supporting "lazy" import into the context. Not intended for
// subpass layout transition, as the pending state handling is more complex
// TODO: See if returning the lower_bound would be useful from a performance POV -- look at the lower_bound overhead
// Would need to add a "hint" overload to parallel_iterator::invalidate_[AB] call, if so.
void ResolvePreviousAccess(const ResourceAccessRange &range, ResourceAccessRangeMap *descent_map,
const ResourceAccessState *infill_state,
const ResourceAccessStateFunction *previous_barrier = nullptr) const;
template <typename BarrierAction>
void ResolvePreviousAccessStack(const ResourceAccessRange &range, ResourceAccessRangeMap *descent_map,
const ResourceAccessState *infill_state, const BarrierAction &previous_barrie) const;
template <typename BarrierAction>
void ResolveAccessRange(const ResourceAccessRange &range, BarrierAction &barrier_action, ResourceAccessRangeMap *resolve_map,
const ResourceAccessState *infill_state, bool recur_to_infill = true) const;
template <typename Detector>
HazardResult DetectHazardRange(Detector &detector, const ResourceAccessRange &range, DetectOptions options) const;
template <typename Detector, typename RangeGen>
HazardResult DetectHazardGeneratedRanges(Detector &detector, RangeGen &range_gen, DetectOptions options) const;
template <typename Detector, typename RangeGen>
HazardResult DetectHazardGeneratedRanges(Detector &detector, const RangeGen &range_gen, DetectOptions options) const {
RangeGen mutable_gen(range_gen);
return DetectHazardGeneratedRanges<Detector, RangeGen>(detector, mutable_gen, options);
}
template <typename Detector>
HazardResult DetectHazard(Detector &detector, const AttachmentViewGen &view_gen, AttachmentViewGen::Gen gen_type,
DetectOptions options) const;
template <typename Detector>
HazardResult DetectHazard(Detector &detector, const ImageState &image, const VkImageSubresourceRange &subresource_range,
const VkOffset3D &offset, const VkExtent3D &extent, bool is_depth_sliced,
DetectOptions options) const;
template <typename Detector>
HazardResult DetectHazard(Detector &detector, const ImageState &image, const VkImageSubresourceRange &subresource_range,
bool is_depth_sliced, DetectOptions options) const;
template <typename Detector>
HazardResult DetectHazardOneRange(Detector &detector, bool detect_prev, ResourceAccessRangeMap::const_iterator &pos,
const ResourceAccessRangeMap::const_iterator &the_end,
const ResourceAccessRange &range) const;
template <typename Detector, typename RangeGen>
HazardResult DetectAsyncHazard(const Detector &detector, const RangeGen &const_range_gen, ResourceUsageTag async_tag,
QueueId async_queue_id) const;
template <typename NormalizeOp>
void Trim(NormalizeOp &&normalize);
template <typename Detector>
HazardResult DetectPreviousHazard(Detector &detector, const ResourceAccessRange &range) const;
ResourceAccessRangeMap access_state_map_;
std::vector<TrackBack> prev_;
std::vector<TrackBack *> prev_by_subpass_;
// These contexts *must* have the same lifespan as this context, or be cleared, before the referenced contexts can expire
std::vector<AsyncReference> async_;
TrackBack *src_external_;
TrackBack dst_external_;
ResourceUsageTag start_tag_;
};
// The semantics of the InfillUpdateOps of infill_update_range are slightly different than for the UpdateMemoryAccessState Action
// operations, as this simplifies the generic traversal. So we wrap them in a semantics Adapter to get the same effect.
template <typename Action>
struct ActionToOpsAdapter {
using Map = ResourceAccessRangeMap;
using Range = typename Map::key_type;
using Iterator = typename Map::iterator;
using IndexType = typename Map::index_type;
void infill(Map &accesses, const Iterator &pos, const Range &infill_range) const {
// Combine Infill and update operations to make the generic implementation simpler
Iterator infill = action.Infill(&accesses, pos, infill_range);
if (infill == accesses.end()) return; // Allow action to 'pass' on filling in the blanks
// Need to apply the action to the Infill. 'infill_update_range' expect ops.infill to be completely done with
// the infill_range, where as Action::Infill assumes the caller will apply the action() logic to the infill_range
for (; infill != pos; ++infill) {
assert(infill != accesses.end());
action(infill);
}
}
void update(const Iterator &pos) const { action(pos); }
const Action &action;
};
template <typename Action>
void AccessContext::ApplyToContext(const Action &barrier_action) {
// Note: Barriers do *not* cross context boundaries, applying to accessess within.... (at least for renderpass subpasses)
UpdateMemoryAccessRangeState(access_state_map_, barrier_action, kFullRange);
}
template <typename Action>
void AccessContext::UpdateMemoryAccessRangeState(ResourceAccessRangeMap &accesses, Action &action,
const ResourceAccessRange &range) {
ActionToOpsAdapter<Action> ops{action};
infill_update_range(accesses, range, ops);
}
template <typename Action, typename RangeGen>
void AccessContext::UpdateMemoryAccessState(const Action &action, RangeGen &range_gen) {
ActionToOpsAdapter<Action> ops{action};
infill_update_rangegen(access_state_map_, range_gen, ops);
}
template <typename Action>
void AccessContext::ApplyUpdateAction(const AttachmentViewGen &view_gen, AttachmentViewGen::Gen gen_type, const Action &action) {
const std::optional<ImageRangeGen> &ref_range_gen = view_gen.GetRangeGen(gen_type);
if (ref_range_gen) {
ImageRangeGen range_gen(*ref_range_gen);
UpdateMemoryAccessState(action, range_gen);
}
}
// A non recursive range walker for the asynchronous contexts (those we have no barriers with)
template <typename Detector, typename RangeGen>
HazardResult AccessContext::DetectAsyncHazard(const Detector &detector, const RangeGen &const_range_gen, ResourceUsageTag async_tag,
QueueId async_queue_id) const {
using RangeType = typename RangeGen::RangeType;
using ConstIterator = ResourceAccessRangeMap::const_iterator;
RangeGen range_gen(const_range_gen);
HazardResult hazard;
auto do_async_hazard_check = [&detector, async_tag, async_queue_id, &hazard](const RangeType &range, const ConstIterator &end,
ConstIterator &pos) {
while (pos != end && pos->first.begin < range.end) {
hazard = detector.DetectAsync(pos, async_tag, async_queue_id);
if (hazard.IsHazard()) return true;
++pos;
}
return false;
};
ForEachEntryInRangesUntil(access_state_map_, range_gen, do_async_hazard_check);
return hazard;
}
template <typename Detector>
HazardResult AccessContext::DetectHazardOneRange(Detector &detector, bool detect_prev, ResourceAccessRangeMap::const_iterator &pos,
const ResourceAccessRangeMap::const_iterator &the_end,
const ResourceAccessRange &range) const {
HazardResult hazard;
ResourceAccessRange gap = {range.begin, range.begin};
while (pos != the_end && pos->first.begin < range.end) {
// Cover any leading gap, or gap between entries
if (detect_prev) {
// TODO: After profiling we may want to change the descent logic such that we don't recur per gap...
// Cover any leading gap, or gap between entries
gap.end = pos->first.begin; // We know this begin is < range.end
if (gap.non_empty()) {
// Recur on all gaps
hazard = DetectPreviousHazard(detector, gap);
if (hazard.IsHazard()) return hazard;
}
// Set up for the next gap. If pos..end is >= range.end, loop will exit, and trailing gap will be empty
gap.begin = pos->first.end;
}
hazard = detector.Detect(pos);
if (hazard.IsHazard()) return hazard;
++pos;
}
if (detect_prev) {
// Detect in the trailing empty as needed
gap.end = range.end;
if (gap.non_empty()) {
hazard = DetectPreviousHazard(detector, gap);
}
}
return hazard;
}
template <typename Detector>
HazardResult AccessContext::DetectHazardRange(Detector &detector, const ResourceAccessRange &range, DetectOptions options) const {
SingleRangeGenerator range_gen(range);
return DetectHazardGeneratedRanges(detector, range_gen, options);
}
template <typename BarrierAction>
void AccessContext::ResolveAccessRange(const ResourceAccessRange &range, BarrierAction &barrier_action,
ResourceAccessRangeMap *resolve_map, const ResourceAccessState *infill_state,
bool recur_to_infill) const {
if (!range.non_empty()) return;
ResourceRangeMergeIterator 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->valid) {
const auto &src_pos = current->pos_B->lower_bound;
ResourceAccessState access(src_pos->second); // intentional copy
barrier_action(&access);
if (current->pos_A->valid) {
const auto trimmed = sparse_container::split(current->pos_A->lower_bound, *resolve_map, current_range);
trimmed->second.Resolve(access);
current.invalidate_A(trimmed);
} else {
auto inserted = resolve_map->insert(current->pos_A->lower_bound, std::make_pair(current_range, access));
current.invalidate_A(inserted); // Update the parallel iterator to point at the insert segment
}
} else {
// we have to descend to fill this gap
if (recur_to_infill) {
ResourceAccessRange 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.at_end()) {
// Do the remainder here....
recurrence_range.end = range.end;
} else {
// Recur only over the range until B becomes valid (within the limits of range).
recurrence_range.end = std::min(range.end, current->pos_B->lower_bound->first.begin);
}
ResolvePreviousAccessStack(recurrence_range, resolve_map, infill_state, barrier_action);
// Given that there could be gaps we need to seek carefully to not repeatedly search the same gaps in the next
// iterator of the outer while.
// Set the parallel iterator to the end of this range s.t. ++ will move us to the next range whether or
// not the end of the range is a gap. For the seek to work, first we need to warn the parallel iterator
// we stepped on the dest map
const auto seek_to = recurrence_range.end - 1; // The subtraction is safe as range can't be empty (loop condition)
current.invalidate_A(); // Changes current->range
current.seek(seek_to);
} else if (!current->pos_A->valid && infill_state) {
// If we didn't find anything in the current range, and we aren't reccuring... we infill if required
auto inserted = resolve_map->insert(current->pos_A->lower_bound, std::make_pair(current->range, *infill_state));
current.invalidate_A(inserted); // Update the parallel iterator to point at the correct segment after insert
}
}
if (current->range.non_empty()) {
++current;
}
}
// Infill if range goes passed both the current and resolve map prior contents
if (recur_to_infill && (current->range.end < range.end)) {
ResourceAccessRange trailing_fill_range = {current->range.end, range.end};
ResolvePreviousAccessStack<BarrierAction>(trailing_fill_range, resolve_map, infill_state, barrier_action);
}
}
// A recursive range walker for hazard detection, first for the current context and the (DetectHazardRecur) to walk
// the DAG of the contexts (for example subpasses)
template <typename Detector, typename RangeGen>
HazardResult AccessContext::DetectHazardGeneratedRanges(Detector &detector, RangeGen &range_gen, DetectOptions options) const {
HazardResult hazard;
// Do this before range_gen is incremented s.t. the copies used will be correct
if (static_cast<uint32_t>(options) & DetectOptions::kDetectAsync) {
// Async checks don't require recursive lookups, as the async lists are exhaustive for the top-level context
// so we'll check these first
for (const auto &async_ref : async_) {
hazard = async_ref.Context().DetectAsyncHazard(detector, range_gen, async_ref.StartTag(), async_ref.GetQueueId());
if (hazard.IsHazard()) return hazard;
}
}
const bool detect_prev = (static_cast<uint32_t>(options) & DetectOptions::kDetectPrevious) != 0;
using RangeType = typename RangeGen::RangeType;
using ConstIterator = ResourceAccessRangeMap::const_iterator;
auto do_detect_hazard_range = [this, &detector, &hazard, detect_prev](const RangeType &range, const ConstIterator &end,
ConstIterator &pos) {
hazard = DetectHazardOneRange(detector, detect_prev, pos, end, range);
return hazard.IsHazard();
};
ForEachEntryInRangesUntil(access_state_map_, range_gen, do_detect_hazard_range);
return hazard;
}
template <typename Detector>
HazardResult AccessContext::DetectPreviousHazard(Detector &detector, const ResourceAccessRange &range) const {
ResourceAccessRangeMap descent_map;
ResolvePreviousAccess(range, &descent_map, nullptr);
for (auto prev = descent_map.begin(); prev != descent_map.end(); ++prev) {
HazardResult hazard = detector.Detect(prev);
if (hazard.IsHazard()) {
return hazard;
}
}
return {};
}
template <typename Predicate>
void AccessContext::EraseIf(Predicate &&pred) {
// Note: Don't forward, we don't want r-values moved, since we're going to make multiple calls.
vvl::EraseIf(access_state_map_, pred);
}
template <typename ResolveOp>
void AccessContext::ResolveFromContext(ResolveOp &&resolve_op, const AccessContext &from_context,
const ResourceAccessState *infill_state, bool recur_to_infill) {
from_context.ResolveAccessRange(kFullRange, resolve_op, &access_state_map_, infill_state, recur_to_infill);
}
template <typename ResolveOp, typename RangeGenerator>
void AccessContext::ResolveFromContext(ResolveOp &&resolve_op, const AccessContext &from_context, RangeGenerator range_gen,
const ResourceAccessState *infill_state, bool recur_to_infill) {
for (; range_gen->non_empty(); ++range_gen) {
from_context.ResolveAccessRange(*range_gen, resolve_op, &access_state_map_, infill_state, recur_to_infill);
}
}
template <typename BarrierAction>
void AccessContext::ResolvePreviousAccessStack(const ResourceAccessRange &range, ResourceAccessRangeMap *descent_map,
const ResourceAccessState *infill_state,
const BarrierAction &previous_barrier) const {
ResourceAccessStateFunction stacked_barrier(std::ref(previous_barrier));
ResolvePreviousAccess(range, descent_map, infill_state, &stacked_barrier);
}