blob: c509b0ec86952eb201b3f20cc9abc5cb94931521 [file] [log] [blame]
/* Copyright (c) 2026 The Khronos Group Inc.
* Copyright (c) 2026 Valve Corporation
* Copyright (c) 2026 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "sync/sync_access_context.h"
#include "sync/sync_image.h"
#include "sync/sync_validation.h"
#include "state_tracker/buffer_state.h"
namespace syncval {
// 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
// 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 Action>
bool ForEachEntryInRangesUntil(const AccessMap &map, ImageRangeGen &range_gen, Action &action) {
using RangeType = ImageRangeGen::RangeType;
using IndexType = RangeType::index_type;
auto pos = map.LowerBound((*range_gen).begin);
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.LowerBound(range.begin);
if (pos == end) break;
}
assert(pos == map.LowerBound(range.begin));
}
// 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;
}
template <typename DetectorRunner>
HazardResult DoDetect(const AccessContext &access_context, const AccessState &access_state, DetectorRunner detector_runner) {
if (access_state.next_global_barrier_index < access_context.GetGlobalBarrierCount()) {
AccessState new_access_state = AccessState::DefaultAccessState();
new_access_state.Assign(access_state);
access_context.ApplyGlobalBarriers(new_access_state);
return detector_runner(new_access_state);
} else {
return detector_runner(access_state);
}
}
class HazardDetector {
public:
HazardDetector(SyncAccessIndex access_index, const AccessContext &access_context)
: access_info_(GetAccessInfo(access_index)), access_context_(access_context) {}
HazardResult Detect(const AccessMap::const_iterator &pos) const {
return DoDetect(access_context_, pos->second,
[this](const AccessState &access_state) { return access_state.DetectHazard(access_info_); });
}
HazardResult DetectAsync(const AccessMap::const_iterator &pos, ResourceUsageTag start_tag, QueueId queue_id) const {
return DoDetect(access_context_, pos->second, [this, start_tag, queue_id](const AccessState &access_state) {
return access_state.DetectAsyncHazard(access_info_, start_tag, queue_id);
});
}
private:
const SyncAccessInfo &access_info_;
const AccessContext &access_context_;
};
class HazardDetectorAttachment {
public:
HazardDetectorAttachment(SyncAccessIndex access_index, const AttachmentAccess &attachment_access,
const AccessContext &access_context, bool detect_load_op_after_store_op_hazards)
: access_info_(GetAccessInfo(access_index)),
attachment_access_(attachment_access),
access_context_(access_context),
detect_load_op_after_store_op_hazards(detect_load_op_after_store_op_hazards) {}
HazardResult Detect(const AccessMap::const_iterator &pos) const {
const OrderingBarrier &ordering = GetOrderingRules(attachment_access_.ordering);
return DoDetect(access_context_, pos->second, [this, &ordering](const AccessState &access_state) {
return access_state.DetectHazard(access_info_, ordering, attachment_access_, 0, kQueueIdInvalid,
detect_load_op_after_store_op_hazards);
});
}
HazardResult DetectAsync(const AccessMap::const_iterator &pos, ResourceUsageTag start_tag, QueueId queue_id) const {
return DoDetect(access_context_, pos->second, [this, start_tag, queue_id](const AccessState &access_state) {
return access_state.DetectAsyncHazard(access_info_, start_tag, queue_id);
});
}
private:
const SyncAccessInfo &access_info_;
const AttachmentAccess attachment_access_;
const AccessContext &access_context_;
const bool detect_load_op_after_store_op_hazards;
};
class HazardDetectFirstUse {
public:
HazardDetectFirstUse(const AccessState &recorded_use, QueueId queue_id, const ResourceUsageRange &tag_range,
const AccessContext &access_context, bool detect_load_op_after_store_op_hazards)
: recorded_use_(recorded_use),
queue_id_(queue_id),
tag_range_(tag_range),
access_context_(access_context),
detect_load_op_after_store_op_hazards(detect_load_op_after_store_op_hazards) {}
HazardResult Detect(const AccessMap::const_iterator &pos) const {
return DoDetect(access_context_, pos->second, [this](const AccessState &access_state) {
return access_state.DetectHazard(recorded_use_, queue_id_, tag_range_, detect_load_op_after_store_op_hazards);
});
}
HazardResult DetectAsync(const AccessMap::const_iterator &pos, ResourceUsageTag start_tag, QueueId queue_id) const {
return DoDetect(access_context_, pos->second, [this, start_tag, queue_id](const AccessState &access_state) {
return access_state.DetectAsyncHazard(recorded_use_, tag_range_, start_tag, queue_id);
});
}
private:
const AccessState &recorded_use_;
const QueueId queue_id_;
const ResourceUsageRange &tag_range_;
const AccessContext &access_context_;
const bool detect_load_op_after_store_op_hazards;
};
struct HazardDetectorMarker {
HazardDetectorMarker(const AccessContext &access_context) : access_context(access_context) {}
HazardResult Detect(const AccessMap::const_iterator &pos) const {
return DoDetect(access_context, pos->second,
[](const AccessState &access_state) { return access_state.DetectMarkerHazard(); });
}
HazardResult DetectAsync(const AccessMap::const_iterator &pos, ResourceUsageTag start_tag, QueueId queue_id) const {
return DoDetect(access_context, pos->second, [start_tag, queue_id](const AccessState &access_state) {
return access_state.DetectAsyncHazard(GetAccessInfo(SYNC_COPY_TRANSFER_WRITE), start_tag, queue_id);
});
}
const AccessContext &access_context;
};
class BarrierHazardDetector {
public:
BarrierHazardDetector(const AccessContext &access_context, SyncAccessIndex access_index, VkPipelineStageFlags2 src_exec_scope,
SyncAccessFlags src_access_scope)
: access_context_(access_context),
access_info_(GetAccessInfo(access_index)),
src_exec_scope_(src_exec_scope),
src_access_scope_(src_access_scope) {}
HazardResult Detect(const AccessMap::const_iterator &pos) const {
return DoDetect(access_context_, pos->second, [this](const AccessState &access_state) {
return access_state.DetectBarrierHazard(access_info_, kQueueIdInvalid, src_exec_scope_, src_access_scope_);
});
}
HazardResult DetectAsync(const AccessMap::const_iterator &pos, ResourceUsageTag start_tag, QueueId queue_id) const {
return DoDetect(access_context_, pos->second, [this, start_tag, queue_id](const AccessState &access_state) {
return access_state.DetectAsyncHazard(access_info_, start_tag, queue_id);
});
}
private:
const AccessContext &access_context_;
const SyncAccessInfo &access_info_;
VkPipelineStageFlags2 src_exec_scope_;
SyncAccessFlags src_access_scope_;
};
class EventBarrierHazardDetector {
public:
EventBarrierHazardDetector(SyncAccessIndex access_index, VkPipelineStageFlags2 src_exec_scope, SyncAccessFlags src_access_scope,
const AccessContext::ScopeMap &event_scope, QueueId queue_id, ResourceUsageTag scope_tag)
: access_info_(GetAccessInfo(access_index)),
src_exec_scope_(src_exec_scope),
src_access_scope_(src_access_scope),
event_scope_(event_scope),
scope_queue_id_(queue_id),
scope_tag_(scope_tag),
scope_pos_(event_scope.begin()),
scope_end_(event_scope.end()) {}
HazardResult Detect(const AccessMap::const_iterator &pos) {
// Need to piece together coverage of pos->first range:
// Copy the range as we'll be chopping it up as needed
AccessRange range = pos->first;
const AccessState &access = pos->second;
HazardResult hazard;
bool in_scope = AdvanceScope(range);
bool unscoped_tested = false;
while (in_scope && !hazard.IsHazard()) {
if (range.begin < ScopeBegin()) {
if (!unscoped_tested) {
unscoped_tested = true;
hazard = access.DetectHazard(access_info_);
}
// Note: don't need to check for in_scope as AdvanceScope true means range and ScopeRange intersect.
// Thus a [ ScopeBegin, range.end ) will be non-empty.
range.begin = ScopeBegin();
} else { // in_scope implied that ScopeRange and range intersect
hazard = access.DetectBarrierHazard(access_info_, ScopeState(), src_exec_scope_, src_access_scope_, scope_queue_id_,
scope_tag_);
if (!hazard.IsHazard()) {
range.begin = ScopeEnd();
in_scope = AdvanceScope(range); // contains a non_empty check
}
}
}
if (range.non_empty() && !hazard.IsHazard() && !unscoped_tested) {
hazard = access.DetectHazard(access_info_);
}
return hazard;
}
HazardResult DetectAsync(const AccessMap::const_iterator &pos, ResourceUsageTag start_tag, QueueId queue_id) const {
// Async barrier hazard detection can use the same path as the usage index is not IsRead, but is IsWrite
return pos->second.DetectAsyncHazard(access_info_, start_tag, queue_id);
}
private:
bool ScopeInvalid() const { return scope_pos_ == scope_end_; }
bool ScopeValid() const { return !ScopeInvalid(); }
void ScopeSeek(const AccessRange &range) { scope_pos_ = event_scope_.LowerBound(range.begin); }
// Hiding away the std::pair grunge...
ResourceAddress ScopeBegin() const { return scope_pos_->first.begin; }
ResourceAddress ScopeEnd() const { return scope_pos_->first.end; }
const AccessRange &ScopeRange() const { return scope_pos_->first; }
const AccessState &ScopeState() const { return scope_pos_->second; }
bool AdvanceScope(const AccessRange &range) {
// Note: non_empty is (valid && !empty), so don't change !non_empty to empty...
if (!range.non_empty()) return false;
if (ScopeInvalid()) return false;
if (ScopeRange().strictly_less(range)) {
ScopeSeek(range);
}
return ScopeValid() && ScopeRange().intersects(range);
}
const SyncAccessInfo access_info_;
VkPipelineStageFlags2 src_exec_scope_;
SyncAccessFlags src_access_scope_;
const AccessContext::ScopeMap &event_scope_;
QueueId scope_queue_id_;
const ResourceUsageTag scope_tag_;
AccessContext::ScopeMap::const_iterator scope_pos_;
AccessContext::ScopeMap::const_iterator scope_end_;
};
HazardResult AccessContext::DetectHazard(const vvl::Buffer &buffer, SyncAccessIndex access_index, const AccessRange &range) const {
if (!SimpleBinding(buffer)) {
return {};
}
const auto base_address = ResourceBaseAddress(buffer);
HazardDetector detector(access_index, *this);
return DetectHazardRange(detector, (range + base_address), DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectHazard(ImageRangeGen &range_gen, SyncAccessIndex current_usage) const {
HazardDetector detector(current_usage, *this);
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectHazard(const vvl::Image &image, const VkImageSubresourceRange &subresource_range,
SyncAccessIndex current_usage) const {
const auto &sub_state = SubState(image);
HazardDetector detector(current_usage, *this);
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, false);
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectHazard(const vvl::Image &image, const VkImageSubresourceRange &subresource_range,
const VkOffset3D &offset, const VkExtent3D &extent, SyncAccessIndex current_usage) const {
const auto &sub_state = SubState(image);
HazardDetector detector(current_usage, *this);
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, offset, extent, false);
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectHazard(const vvl::ImageView &image_view, SyncAccessIndex current_usage) const {
HazardDetector detector(current_usage, *this);
ImageRangeGen range_gen = MakeImageRangeGen(image_view);
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectHazard(const vvl::ImageView &image_view, const VkOffset3D &offset, const VkExtent3D &extent,
SyncAccessIndex current_usage) const {
HazardDetector detector(current_usage, *this);
ImageRangeGen range_gen(MakeImageRangeGen(image_view, offset, extent));
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectAttachmentHazard(ImageRangeGen &range_gen, SyncAccessIndex current_usage,
const AttachmentAccess &attachment_access) const {
HazardDetectorAttachment detector(current_usage, attachment_access, *this,
validator->syncval_settings.load_op_after_store_op_validation);
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectAttachmentHazard(const vvl::Image &image, const VkImageSubresourceRange &subresource_range,
bool is_depth_sliced, SyncAccessIndex current_usage,
const AttachmentAccess &attachment_access) const {
const auto &sub_state = SubState(image);
HazardDetectorAttachment detector(current_usage, attachment_access, *this, false);
ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, is_depth_sliced);
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectAttachmentHazard(const vvl::ImageView &image_view, const VkOffset3D &offset,
const VkExtent3D &extent, SyncAccessIndex current_usage,
const AttachmentAccess &attachment_access) const {
HazardDetectorAttachment detector(current_usage, attachment_access, *this,
validator->syncval_settings.load_op_after_store_op_validation);
ImageRangeGen range_gen(MakeImageRangeGen(image_view, offset, extent));
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectImageBarrierHazard(const vvl::Image &image, const VkImageSubresourceRange &subresource_range,
VkPipelineStageFlags2 src_exec_scope, const SyncAccessFlags &src_access_scope,
QueueId queue_id, const ScopeMap &scope_map, const ResourceUsageTag scope_tag,
AccessContext::DetectOptions options) const {
EventBarrierHazardDetector detector(SyncAccessIndex::SYNC_IMAGE_LAYOUT_TRANSITION, src_exec_scope, src_access_scope, scope_map,
queue_id, scope_tag);
ImageRangeGen range_gen = SubState(image).MakeImageRangeGen(subresource_range, false);
return DetectHazardGeneratedRangeGen(detector, range_gen, options);
}
HazardResult AccessContext::DetectImageBarrierHazard(const vvl::Image &image, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope,
const VkImageSubresourceRange &subresource_range, bool is_depth_sliced,
const DetectOptions options) const {
BarrierHazardDetector detector(*this, SyncAccessIndex::SYNC_IMAGE_LAYOUT_TRANSITION, src_exec_scope, src_access_scope);
ImageRangeGen range_gen = SubState(image).MakeImageRangeGen(subresource_range, is_depth_sliced);
return DetectHazardGeneratedRangeGen(detector, range_gen, options);
}
HazardResult AccessContext::DetectImageBarrierHazard(const AttachmentViewGen &view_gen, const SyncBarrier &barrier,
DetectOptions options) const {
BarrierHazardDetector detector(*this, SyncAccessIndex::SYNC_IMAGE_LAYOUT_TRANSITION, barrier.src_exec_scope.exec_scope,
barrier.src_access_scope);
const std::optional<ImageRangeGen> &attachment_gen = view_gen.GetRangeGen(AttachmentViewGen::Gen::kViewSubresource);
subresource_adapter::ImageRangeGenerator range_gen(*attachment_gen);
return DetectHazardGeneratedRangeGen(detector, range_gen, options);
}
HazardResult AccessContext::DetectSubpassTransitionHazard(const SubpassBarrierTrackback &track_back,
const AttachmentViewGen &attach_view) const {
// We should never ask for a transition from a context we don't have
assert(track_back.source_subpass);
// Do the detection against the specific prior context independent of other contexts. (Synchronous only)
// Hazard detection for the transition can be against the merged of the barriers (it only uses src_...)
const SyncBarrier merged_barrier(track_back.barriers);
HazardResult hazard = track_back.source_subpass->DetectImageBarrierHazard(attach_view, merged_barrier, kDetectPrevious);
if (!hazard.IsHazard()) {
// The Async hazard check is against the current context's async set.
SyncBarrier null_barrier = {};
hazard = DetectImageBarrierHazard(attach_view, null_barrier, kDetectAsync);
}
return hazard;
}
// This is called with the *recorded* command buffers access context, with the
// *active* access context pass in, againsts which hazards will be detected
HazardResult AccessContext::DetectFirstUseHazard(QueueId queue_id, const ResourceUsageRange &tag_range,
const AccessContext &access_context) const {
// If the context is finalized we have a fast path to find first accesses within a range
if (finalized_) {
for (const auto &single_tag : sorted_first_accesses_.IterateSingleTagFirstAccesses(tag_range)) {
const AccessRange access_range = single_tag.p_key_value->first;
const AccessState &access = single_tag.p_key_value->second;
// For single tag first accesses we have exact search and can assert the find
assert(access.FirstAccessInTagRange(tag_range));
HazardDetectFirstUse detector(access, queue_id, tag_range, access_context,
validator->syncval_settings.load_op_after_store_op_validation);
HazardResult hazard = access_context.DetectHazardRange(detector, access_range, DetectOptions::kDetectAll);
if (hazard.IsHazard()) {
return hazard;
}
}
for (const auto &multi_tag : sorted_first_accesses_.IterateMultiTagFirstAccesses(tag_range)) {
const AccessRange access_range = multi_tag.p_key_value->first;
const AccessState &access = multi_tag.p_key_value->second;
// For multi tag first accesses the search is not exact, so we need to check for range inclusion
// (on average multi tag search is faster than going over the entire access map)
if (!access.FirstAccessInTagRange(tag_range)) {
continue;
}
HazardDetectFirstUse detector(access, queue_id, tag_range, access_context,
validator->syncval_settings.load_op_after_store_op_validation);
HazardResult hazard = access_context.DetectHazardRange(detector, access_range, DetectOptions::kDetectAll);
if (hazard.IsHazard()) {
return hazard;
}
}
}
// The context is not finalized. We have to iterate over the entire access map
else {
for (const auto &recorded_access : access_state_map_) {
// Cull any entries not in the current tag range
if (!recorded_access.second.FirstAccessInTagRange(tag_range)) {
continue;
}
HazardDetectFirstUse detector(recorded_access.second, queue_id, tag_range, access_context,
validator->syncval_settings.load_op_after_store_op_validation);
HazardResult hazard = access_context.DetectHazardRange(detector, recorded_access.first, DetectOptions::kDetectAll);
if (hazard.IsHazard()) {
return hazard;
}
}
}
return {};
}
HazardResult AccessContext::DetectVideoHazard(const vvl::VideoSession &vs_state, const vvl::VideoPictureResource &resource,
SyncAccessIndex current_usage) const {
const vvl::Image &image = *resource.image_state.get();
const auto &sub_state = SubState(image);
const auto offset = resource.GetEffectiveImageOffset(vs_state);
const auto extent = resource.GetEffectiveImageExtent(vs_state);
ImageRangeGen range_gen(sub_state.MakeImageRangeGen(resource.range, offset, extent, false));
HazardDetector detector(current_usage, *this);
return DetectHazardGeneratedRangeGen(detector, range_gen, DetectOptions::kDetectAll);
}
HazardResult AccessContext::DetectMarkerHazard(const vvl::Buffer &buffer, const AccessRange &range) const {
if (!SimpleBinding(buffer)) {
return HazardResult();
}
const VkDeviceSize base_address = ResourceBaseAddress(buffer);
HazardDetectorMarker detector(*this);
return DetectHazardRange(detector, (range + base_address), DetectOptions::kDetectAll);
}
template <typename Detector>
HazardResult AccessContext::DetectHazardRange(Detector &detector, const AccessRange &range, DetectOptions options) const {
if (!range.non_empty()) {
return {};
}
HazardResult hazard;
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, async_ref.StartTag(), async_ref.GetQueueId());
if (hazard.IsHazard()) {
return hazard;
}
}
}
const bool detect_prev = (options & DetectOptions::kDetectPrevious) != 0;
auto pos = access_state_map_.LowerBound(range.begin);
hazard = DetectHazardOneRange(detector, detect_prev, pos, access_state_map_.end(), range);
return hazard;
}
template <typename Detector>
HazardResult AccessContext::DetectHazardGeneratedRangeGen(Detector &detector, ImageRangeGen &range_gen,
DetectOptions options) const {
HazardResult hazard;
if ((options & DetectOptions::kDetectAsync) != 0) {
// 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_) {
ImageRangeGen range_gen_copy(range_gen); // original range gen is needed later
hazard = async_ref.Context().DetectAsyncHazard(detector, range_gen_copy, async_ref.StartTag(), async_ref.GetQueueId());
if (hazard.IsHazard()) return hazard;
}
}
const bool detect_prev = (options & DetectOptions::kDetectPrevious) != 0;
using ConstIterator = AccessMap::const_iterator;
auto do_detect_hazard_range = [this, &detector, &hazard, detect_prev](const ImageRangeGen::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::DetectAsyncHazard(const Detector &detector, const AccessRange &range, ResourceUsageTag async_tag,
QueueId async_queue_id) const {
assert(range.non_empty());
HazardResult hazard;
auto pos = access_state_map_.LowerBound(range.begin);
if (pos != access_state_map_.end() && pos->first.begin < range.end) {
hazard = detector.DetectAsync(pos, async_tag, async_queue_id);
}
return hazard;
}
template <typename Detector>
HazardResult AccessContext::DetectAsyncHazard(const Detector &detector, ImageRangeGen &range_gen, ResourceUsageTag async_tag,
QueueId async_queue_id) const {
using ConstIterator = AccessMap::const_iterator;
HazardResult hazard;
auto do_async_hazard_check = [&detector, async_tag, async_queue_id, &hazard](const ImageRangeGen::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, AccessMap::const_iterator &pos,
const AccessMap::const_iterator &the_end, const AccessRange &range) const {
HazardResult hazard;
AccessRange 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::DetectPreviousHazard(Detector &detector, const AccessRange &range) const {
if (prev_.empty()) {
return {};
}
AccessContext descent_context;
for (const auto &prev_dep : prev_) {
const ApplyTrackbackStackAction barrier_action(prev_dep.barriers, nullptr);
prev_dep.source_subpass->ResolveAccessRangeRecursePrev(range, barrier_action, descent_context, false);
}
AccessMap &descent_map = descent_context.access_state_map_;
for (auto prev = descent_map.begin(); prev != descent_map.end(); ++prev) {
HazardResult hazard = detector.Detect(prev);
if (hazard.IsHazard()) {
return hazard;
}
}
return {};
}
} // namespace syncval