blob: e756151d85df119781c7aaf204dd4d1a928300c2 [file] [log] [blame]
/* Copyright (c) 2019-2024 The Khronos Group Inc.
* 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"
class ResourceAccessState;
class WriteState;
struct ReadState;
struct ResourceFirstAccess;
// NOTE: the attachement read flag is put *only* in the access scope and not in the exect scope, since the ordering
// rules apply only to this specific access for this stage, and not the stage as a whole. The ordering detection
// also reflects this special case for read hazard detection (using access instead of exec scope)
constexpr VkPipelineStageFlags2 kColorAttachmentExecScope = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
const SyncAccessFlags kColorAttachmentAccessScope =
SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ_BIT |
SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ_NONCOHERENT_BIT_EXT |
SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE_BIT |
SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ_BIT; // Note: this is intentionally not in the exec scope
constexpr VkPipelineStageFlags2 kDepthStencilAttachmentExecScope =
VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
const SyncAccessFlags kDepthStencilAttachmentAccessScope =
SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ_BIT; // Note: this is intentionally not in the exec scope
constexpr VkPipelineStageFlags2 kRasterAttachmentExecScope = kDepthStencilAttachmentExecScope | kColorAttachmentExecScope;
const SyncAccessFlags kRasterAttachmentAccessScope = kDepthStencilAttachmentAccessScope | kColorAttachmentAccessScope;
enum SyncHazard {
NONE = 0,
READ_AFTER_WRITE,
WRITE_AFTER_READ,
WRITE_AFTER_WRITE,
READ_RACING_WRITE,
WRITE_RACING_WRITE,
WRITE_RACING_READ,
WRITE_AFTER_PRESENT, // Once presented, an image may not be used until acquired
READ_AFTER_PRESENT,
PRESENT_AFTER_READ, // Must be unreferenced and visible to present
PRESENT_AFTER_WRITE,
};
enum class SyncOrdering : uint8_t {
kOrderingNone = 0,
kNonAttachment = kOrderingNone,
kColorAttachment = 1,
kDepthStencilAttachment = 2,
kRaster = 3,
kNumOrderings = 4,
};
const char *string_SyncHazardVUID(SyncHazard hazard);
class HazardResult {
public:
struct HazardState {
std::unique_ptr<const ResourceAccessState> access_state;
std::unique_ptr<const ResourceFirstAccess> recorded_access;
SyncAccessIndex access_index = std::numeric_limits<SyncAccessIndex>::max();
SyncAccessIndex prior_access_index;
ResourceUsageTag tag = ResourceUsageTag();
uint32_t handle_index = vvl::kNoIndex32;
SyncHazard hazard = NONE;
HazardState(const ResourceAccessState *access_state, const SyncAccessInfo &usage_info, SyncHazard hazard,
SyncAccessIndex prior_access_index, ResourceUsageTagEx tag_ex);
};
static HazardResult HazardVsPriorWrite(const ResourceAccessState *access_state, const SyncAccessInfo &usage_info,
SyncHazard hazard, const WriteState &prior_write);
static HazardResult HazardVsPriorRead(const ResourceAccessState *access_state, const SyncAccessInfo &usage_info,
SyncHazard hazard, const ReadState &prior_read);
void AddRecordedAccess(const ResourceFirstAccess &first_access);
bool IsHazard() const { return state_.has_value() && NONE != state_->hazard; }
bool IsWAWHazard() const;
ResourceUsageTag Tag() const {
assert(state_);
return state_->tag;
}
ResourceUsageTagEx TagEx() const {
assert(state_);
return ResourceUsageTagEx{state_->tag, state_->handle_index};
}
SyncHazard Hazard() const {
assert(state_);
return state_->hazard;
}
const std::unique_ptr<const ResourceFirstAccess> &RecordedAccess() const {
assert(state_);
return state_->recorded_access;
}
const HazardState &State() const {
assert(state_);
return state_.value();
}
private:
std::optional<HazardState> state_;
};
struct SyncExecScope {
VkPipelineStageFlags2 mask_param; // the xxxStageMask parameter passed by the caller
VkPipelineStageFlags2 expanded_mask; // all stage bits covered by any 'catch all bits' in the parameter (eg. ALL_GRAPHICS_BIT).
VkPipelineStageFlags2 exec_scope; // all earlier or later stages that would be affected by a barrier using this scope.
SyncAccessFlags valid_accesses; // all valid accesses that can be used with this scope.
SyncExecScope() : mask_param(0), expanded_mask(0), exec_scope(0), valid_accesses(0) {}
SyncExecScope(VkPipelineStageFlags2 mask_param_, VkPipelineStageFlags2 expanded_mask_, VkPipelineStageFlags2 exec_scope_,
const SyncAccessFlags &valid_accesses_)
: mask_param(mask_param_), expanded_mask(expanded_mask_), exec_scope(exec_scope_), valid_accesses(valid_accesses_) {}
static SyncExecScope MakeSrc(VkQueueFlags queue_flags, VkPipelineStageFlags2 src_stage_mask,
const VkPipelineStageFlags2 disabled_feature_mask = 0);
static SyncExecScope MakeDst(VkQueueFlags queue_flags, VkPipelineStageFlags2 src_stage_mask);
};
struct SemaphoreScope : SyncExecScope {
SemaphoreScope(QueueId qid, const SyncExecScope &exec_scope) : SyncExecScope(exec_scope), queue(qid) {}
SemaphoreScope() = default;
QueueId queue;
};
struct SyncBarrier {
struct AllAccess {};
SyncExecScope src_exec_scope;
SyncAccessFlags src_access_scope;
SyncExecScope dst_exec_scope;
SyncAccessFlags dst_access_scope;
SyncBarrier() = default;
SyncBarrier(const SyncBarrier &other) = default;
SyncBarrier &operator=(const SyncBarrier &) = default;
SyncBarrier(const SyncExecScope &src, const SyncExecScope &dst);
SyncBarrier(const SyncExecScope &src, const SyncExecScope &dst, const AllAccess &);
SyncBarrier(const SyncExecScope &src_exec, const SyncAccessFlags &src_access, const SyncExecScope &dst_exec,
const SyncAccessFlags &dst_access)
: src_exec_scope(src_exec), src_access_scope(src_access), dst_exec_scope(dst_exec), dst_access_scope(dst_access) {}
template <typename Barrier>
SyncBarrier(const Barrier &barrier, const SyncExecScope &src, const SyncExecScope &dst);
SyncBarrier(VkQueueFlags queue_flags, const VkSubpassDependency2 &barrier);
// template constructor for sync2 barriers
template <typename Barrier>
SyncBarrier(VkQueueFlags queue_flags, const Barrier &barrier);
void Merge(const SyncBarrier &other) {
// Note that after merge, only the exec_scope and access_scope fields are fully valid
// TODO: Do we need to update any of the other fields? Merging has limited application.
src_exec_scope.exec_scope |= other.src_exec_scope.exec_scope;
src_access_scope |= other.src_access_scope;
dst_exec_scope.exec_scope |= other.dst_exec_scope.exec_scope;
dst_access_scope |= other.dst_access_scope;
}
SyncBarrier(const std::vector<SyncBarrier> &barriers);
};
struct ResourceFirstAccess {
const SyncAccessInfo *usage_info;
ResourceUsageTag tag;
uint32_t handle_index;
SyncOrdering ordering_rule;
ResourceFirstAccess(const SyncAccessInfo &usage_info, ResourceUsageTagEx tag_ex, SyncOrdering ordering_rule)
: usage_info(&usage_info), tag(tag_ex.tag), handle_index(tag_ex.handle_index), ordering_rule(ordering_rule) {}
bool operator==(const ResourceFirstAccess &rhs) const {
return (tag == rhs.tag) && (usage_info == rhs.usage_info) && (ordering_rule == rhs.ordering_rule);
}
ResourceUsageTagEx TagEx() const { return {tag, handle_index}; }
};
using QueueId = uint32_t;
struct OrderingBarrier {
VkPipelineStageFlags2 exec_scope;
SyncAccessFlags access_scope;
OrderingBarrier() = default;
OrderingBarrier(const OrderingBarrier &) = default;
OrderingBarrier(VkPipelineStageFlags2 es, SyncAccessFlags as) : exec_scope(es), access_scope(as) {}
OrderingBarrier &operator=(const OrderingBarrier &) = default;
OrderingBarrier &operator|=(const OrderingBarrier &rhs) {
exec_scope |= rhs.exec_scope;
access_scope |= rhs.access_scope;
return *this;
}
bool operator==(const OrderingBarrier &rhs) const {
return (exec_scope == rhs.exec_scope) && (access_scope == rhs.access_scope);
}
};
using ResourceUsageTagSet = CachedInsertSet<ResourceUsageTag, 4>;
// Mutliple read operations can be simlutaneously (and independently) synchronized,
// given the only the second execution scope creates a dependency chain, we have to track each,
// but only up to one per pipeline stage (as another read from the *same* stage become more recent,
// and applicable one for hazard detection
struct ReadState {
VkPipelineStageFlags2 stage; // The stage of this read
SyncAccessIndex access_index; // TODO: Revisit whether this needs to support multiple reads per stage
VkPipelineStageFlags2 barriers; // all applicable barriered stages
VkPipelineStageFlags2 sync_stages; // reads known to have happened after this
ResourceUsageTag tag;
uint32_t handle_index;
QueueId queue;
VkPipelineStageFlags2 pending_dep_chain; // Should be zero except during barrier application
// Excluded from comparison
ReadState() = default;
ReadState(VkPipelineStageFlags2 stage, SyncAccessIndex access_index, ResourceUsageTagEx tag_ex);
void Set(VkPipelineStageFlags2 stage, SyncAccessIndex access_index, ResourceUsageTagEx tag_ex);
ResourceUsageTagEx TagEx() const { return {tag, handle_index}; }
bool operator==(const ReadState &rhs) const {
return (stage == rhs.stage) && (access_index == rhs.access_index) && (barriers == rhs.barriers) &&
(sync_stages == rhs.sync_stages) && (tag == rhs.tag) && (queue == rhs.queue) &&
(pending_dep_chain == rhs.pending_dep_chain);
}
void Normalize() { pending_dep_chain = VK_PIPELINE_STAGE_2_NONE; }
bool IsReadBarrierHazard(VkPipelineStageFlags2 src_exec_scope) const {
// If the read stage is not in the src sync scope
// *AND* not execution chained with an existing sync barrier (that's the or)
// then the barrier access is unsafe (R/W after R)
return (src_exec_scope & (stage | barriers)) == 0;
}
bool IsReadBarrierHazard(QueueId barrier_queue, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const {
// If the read stage is not in the src sync scope
// *AND* not execution chained with an existing sync barrier (that's the or)
// then the barrier access is unsafe (R/W after R)
VkPipelineStageFlags2 queue_ordered_stage = (queue == barrier_queue) ? stage : VK_PIPELINE_STAGE_2_NONE;
// Current implementation relies on TOP_OF_PIPE constant due to the fact that it's non-zero value
// and AND-ing with it can create execution dependency when it's necessary. When NONE constant is
// used, which equals to zero, then AND-ing with it always results in 0 which means "no barrier",
// so it's not possible to use NONE internally in equivalent way to TOP_OF_PIPE.
// Replace NONE with TOP_OF_PIPE in the scenarios where they are equivalent.
//
// If we update implementation to get rid of deprecated TOP_OF_PIPE/BOTTOM_OF_PIPE then we must
// invert the condition below and exchange TOP_OF_PIPE and NONE roles, so deprecated stages would
// not propagate into implementation internals.
if (src_exec_scope == VK_PIPELINE_STAGE_2_NONE && src_access_scope.none()) {
src_exec_scope = VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT;
}
return (src_exec_scope & (queue_ordered_stage | barriers)) == 0;
}
bool ReadInScopeOrChain(VkPipelineStageFlags2 exec_scope) const { return (exec_scope & (stage | barriers)) != 0; }
bool ReadInQueueScopeOrChain(QueueId queue, VkPipelineStageFlags2 exec_scope) const;
bool ReadInEventScope(VkPipelineStageFlags2 exec_scope, QueueId scope_queue, ResourceUsageTag scope_tag) const {
// If this read is the same one we included in the set event and in scope, then apply the execution barrier...
// NOTE: That's not really correct... this read stage might *not* have been included in the setevent, and the barriers
// representing the chain might have changed since then (that would be an odd usage), so as a first approximation
// we'll assume the barriers *haven't* been changed since (if the tag hasn't), and while this could be a false
// positive in the case of Set; SomeBarrier; Wait; we'll live with it until we can add more state to the first scope
// capture (the specific write and read stages that *were* in scope at the moment of SetEvents.
return (tag < scope_tag) && ReadInQueueScopeOrChain(scope_queue, exec_scope);
}
void ApplyReadBarrier(VkPipelineStageFlags2 dst_scope) { pending_dep_chain |= dst_scope; }
VkPipelineStageFlags2 ApplyPendingBarriers();
};
class WriteState {
public:
WriteState() = default;
WriteState(const SyncAccessInfo &usage_info, ResourceUsageTagEx tag_ex);
bool operator==(const WriteState &rhs) const {
return (access_ == rhs.access_) && (barriers_ == rhs.barriers_) && (tag_ == rhs.tag_) && (queue_ == rhs.queue_) &&
(dependency_chain_ == rhs.dependency_chain_);
}
bool WriteInChain(VkPipelineStageFlags2 src_exec_scope) const;
bool WriteInScope(const SyncAccessFlags &src_access_scope) const;
bool WriteInSourceScopeOrChain(VkPipelineStageFlags2 src_exec_scope, SyncAccessFlags src_access_scope) const;
bool WriteInQueueSourceScopeOrChain(QueueId queue, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const;
bool WriteInEventScope(VkPipelineStageFlags2 src_exec_scope, const SyncAccessFlags &src_access_scope, QueueId scope_queue,
ResourceUsageTag scope_tag) const;
SyncAccessIndex Index() const { return access_->access_index; }
bool IsIndex(SyncAccessIndex access_index) const { return Index() == access_index; }
bool IsQueue(QueueId other_queue) const { return queue_ == other_queue; }
const SyncAccessInfo &Access() const { return *access_; }
const SyncAccessFlags &Barriers() const { return barriers_; }
ResourceUsageTag Tag() const { return tag_; }
ResourceUsageTagEx TagEx() const { return {tag_, handle_index_}; }
bool IsWriteHazard(const SyncAccessInfo &usage_info) const;
bool IsOrdered(const OrderingBarrier &ordering, QueueId queue_id) const;
bool IsWriteBarrierHazard(QueueId queue_id, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const;
void SetQueueId(QueueId id);
void Set(const SyncAccessInfo &usage_info, ResourceUsageTagEx tag_ex);
void MergeBarriers(const WriteState &other);
void OffsetTag(ResourceUsageTag offset) { tag_ += offset; }
bool HasPendingState() const { return pending_barriers_.any() || (0 != pending_dep_chain_); }
void UpdatePendingBarriers(const SyncBarrier &barrier);
void ApplyPendingBarriers();
void UpdatePendingLayoutOrdering(const SyncBarrier &barrier);
const OrderingBarrier &GetPendingLayoutOrdering() const { return pending_layout_ordering_; }
private:
const SyncAccessInfo *access_;
SyncAccessFlags barriers_; // union of applicable barrier masks since last write
ResourceUsageTag tag_;
uint32_t handle_index_;
QueueId queue_;
// intially zero, but accumulating the dstStages of barriers if they chain.
VkPipelineStageFlags2 dependency_chain_;
// Write specific layout state
OrderingBarrier pending_layout_ordering_;
VkPipelineStageFlags2 pending_dep_chain_;
SyncAccessFlags pending_barriers_;
friend ResourceAccessState;
};
class ResourceAccessState : public SyncStageAccess {
protected:
using OrderingBarriers = std::array<OrderingBarrier, static_cast<size_t>(SyncOrdering::kNumOrderings)>;
using FirstAccesses = small_vector<ResourceFirstAccess, 3>;
public:
HazardResult DetectHazard(const SyncAccessInfo &usage_info) const;
HazardResult DetectHazard(const SyncAccessInfo &usage_info, SyncOrdering ordering_rule, QueueId queue_id) const;
HazardResult DetectHazard(const SyncAccessInfo &usage_info, const OrderingBarrier &ordering, QueueId queue_id) const;
HazardResult DetectHazard(const ResourceAccessState &recorded_use, QueueId queue_id, const ResourceUsageRange &tag_range) const;
HazardResult DetectAsyncHazard(const SyncAccessInfo &usage_info, ResourceUsageTag start_tag, QueueId queue_id) const;
HazardResult DetectAsyncHazard(const ResourceAccessState &recorded_use, const ResourceUsageRange &tag_range,
ResourceUsageTag start_tag, QueueId queue_id) const;
HazardResult DetectBarrierHazard(const SyncAccessInfo &usage_info, QueueId queue_id, VkPipelineStageFlags2 source_exec_scope,
const SyncAccessFlags &source_access_scope) const;
HazardResult DetectBarrierHazard(const SyncAccessInfo &usage_info, const ResourceAccessState &scope_state,
VkPipelineStageFlags2 source_exec_scope, const SyncAccessFlags &source_access_scope,
QueueId event_queue, ResourceUsageTag event_tag) const;
void Update(const SyncAccessInfo &usage_info, SyncOrdering ordering_rule, ResourceUsageTagEx tag_ex);
void SetWrite(const SyncAccessInfo &usage_info, ResourceUsageTagEx tag_ex);
void ClearWrite();
void ClearRead();
void ClearFirstUse();
void Resolve(const ResourceAccessState &other);
void ApplyBarriers(const std::vector<SyncBarrier> &barriers, bool layout_transition);
void ApplyBarriersImmediate(const std::vector<SyncBarrier> &barriers);
template <typename ScopeOps>
void ApplyBarrier(ScopeOps &&scope, const SyncBarrier &barrier, bool layout_transition);
void ApplyPendingBarriers(ResourceUsageTag tag);
void ApplySemaphore(const SemaphoreScope &signal, const SemaphoreScope wait);
struct WaitQueueTagPredicate {
QueueId queue;
ResourceUsageTag tag;
bool operator()(const ReadState &read_access) const; // Read access predicate
bool operator()(const ResourceAccessState &access) const; // Write access predicate
};
friend WaitQueueTagPredicate;
struct WaitTagPredicate {
ResourceUsageTag tag;
bool operator()(const ReadState &read_access) const; // Read access predicate
bool operator()(const ResourceAccessState &access) const; // Write access predicate
};
friend WaitTagPredicate;
struct WaitAcquirePredicate {
ResourceUsageTag present_tag;
ResourceUsageTag acquire_tag;
bool operator()(const ReadState &read_access) const; // Read access predicate
bool operator()(const ResourceAccessState &access) const; // Write access predicate
};
friend WaitAcquirePredicate;
template <typename Predicate>
bool ApplyPredicatedWait(Predicate &predicate);
bool FirstAccessInTagRange(const ResourceUsageRange &tag_range) const;
void OffsetTag(ResourceUsageTag offset);
ResourceAccessState();
bool HasPendingState() const { return (0 != pending_layout_transition) || (last_write && last_write->HasPendingState()); }
bool HasWriteOp() const { return last_write.has_value(); }
SyncAccessIndex LastWriteOp() const { return last_write.has_value() ? last_write->Index() : SYNC_ACCESS_INDEX_NONE; }
bool IsLastWriteOp(SyncAccessIndex access_index) const { return LastWriteOp() == access_index; }
ResourceUsageTag LastWriteTag() const { return last_write.has_value() ? last_write->Tag() : ResourceUsageTag(0); }
bool operator==(const ResourceAccessState &rhs) const {
const bool write_same = (read_execution_barriers == rhs.read_execution_barriers) &&
(input_attachment_read == rhs.input_attachment_read) && (last_write == rhs.last_write);
const bool read_write_same = write_same && (last_read_stages == rhs.last_read_stages) && (last_reads == rhs.last_reads);
const bool same = read_write_same && (first_accesses_ == rhs.first_accesses_) &&
(first_read_stages_ == rhs.first_read_stages_) &&
(first_write_layout_ordering_ == rhs.first_write_layout_ordering_);
return same;
}
bool operator!=(const ResourceAccessState &rhs) const { return !(*this == rhs); }
VkPipelineStageFlags2 GetReadBarriers(SyncAccessIndex access_index) const;
SyncAccessFlags GetWriteBarriers() const { return last_write.has_value() ? last_write->Barriers() : SyncAccessFlags(); }
void SetQueueId(QueueId id);
bool IsWriteBarrierHazard(QueueId queue_id, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const;
bool WriteInSourceScopeOrChain(VkPipelineStageFlags2 src_exec_scope, SyncAccessFlags src_access_scope) const;
bool WriteInQueueSourceScopeOrChain(QueueId queue, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const;
bool WriteInEventScope(VkPipelineStageFlags2 src_exec_scope, const SyncAccessFlags &src_access_scope, QueueId scope_queue,
ResourceUsageTag scope_tag) const;
struct UntaggedScopeOps {
bool WriteInScope(const SyncBarrier &barrier, const ResourceAccessState &access) const {
return access.WriteInSourceScopeOrChain(barrier.src_exec_scope.exec_scope, barrier.src_access_scope);
}
bool ReadInScope(const SyncBarrier &barrier, const ReadState &read_state) const {
return read_state.ReadInScopeOrChain(barrier.src_exec_scope.exec_scope);
}
};
struct QueueScopeOps {
bool WriteInScope(const SyncBarrier &barrier, const ResourceAccessState &access) const {
return access.WriteInQueueSourceScopeOrChain(queue, barrier.src_exec_scope.exec_scope, barrier.src_access_scope);
}
bool ReadInScope(const SyncBarrier &barrier, const ReadState &read_state) const {
return read_state.ReadInQueueScopeOrChain(queue, barrier.src_exec_scope.exec_scope);
}
QueueScopeOps(QueueId scope_queue) : queue(scope_queue) {}
QueueId queue;
};
struct EventScopeOps {
bool WriteInScope(const SyncBarrier &barrier, const ResourceAccessState &access) const {
return access.WriteInEventScope(barrier.src_exec_scope.exec_scope, barrier.src_access_scope, scope_queue, scope_tag);
}
bool ReadInScope(const SyncBarrier &barrier, const ReadState &read_state) const {
return read_state.ReadInEventScope(barrier.src_exec_scope.exec_scope, scope_queue, scope_tag);
}
EventScopeOps(QueueId qid, ResourceUsageTag event_tag) : scope_queue(qid), scope_tag(event_tag) {}
QueueId scope_queue;
ResourceUsageTag scope_tag;
};
void Normalize();
void GatherReferencedTags(ResourceUsageTagSet &used) const;
private:
static constexpr VkPipelineStageFlags2 kInvalidAttachmentStage = ~VkPipelineStageFlags2(0);
bool IsRAWHazard(const SyncAccessInfo &usage_info) const;
bool WriteInScope(const SyncAccessFlags &src_access_scope) const;
// Apply ordering scope to write hazard detection
bool ReadInSourceScopeOrChain(VkPipelineStageFlags2 src_exec_scope) const {
return (0 != (src_exec_scope & (last_read_stages | read_execution_barriers)));
}
static bool IsReadHazard(VkPipelineStageFlags2 stage_mask, const VkPipelineStageFlags2 barriers) {
return stage_mask != (stage_mask & barriers);
}
bool IsReadHazard(VkPipelineStageFlags2 stage_mask, const ReadState &read_access) const {
return IsReadHazard(stage_mask, read_access.barriers);
}
VkPipelineStageFlags2 GetOrderedStages(QueueId queue_id, const OrderingBarrier &ordering) const;
void UpdateFirst(ResourceUsageTagEx tag_ex, const SyncAccessInfo &usage_info, SyncOrdering ordering_rule);
void TouchupFirstForLayoutTransition(ResourceUsageTag tag, const OrderingBarrier &layout_ordering);
void MergePending(const ResourceAccessState &other);
void MergeReads(const ResourceAccessState &other);
static const OrderingBarrier &GetOrderingRules(SyncOrdering ordering_enum) {
return kOrderingRules[static_cast<size_t>(ordering_enum)];
}
// TODO: Add a NONE (zero) enum to SyncStageAccessFlags for input_attachment_read and last_write
// With reads, each must be "safe" relative to it's prior write, so we need only
// save the most recent write operation (as anything *transitively* unsafe would arleady
// be included
// SyncStageAccessFlags write_barriers; // union of applicable barrier masks since last write
// VkPipelineStageFlags2 write_dependency_chain; // intiially zero, but accumulating the dstStages of barriers if they
// chain. ResourceUsageTag write_tag; QueueId write_queue;
std::optional<WriteState> last_write; // only the most recent write
VkPipelineStageFlags2 last_read_stages;
VkPipelineStageFlags2 read_execution_barriers;
using ReadStates = small_vector<ReadState, 3, uint32_t>;
ReadStates last_reads;
// TODO Input Attachment cleanup for multiple reads in a given stage
// Tracks whether the fragment shader read is input attachment read
bool input_attachment_read;
// Not part of the write state, logically. Can exist when !last_write
// Pending execution state to support independent parallel barriers
bool pending_layout_transition;
FirstAccesses first_accesses_;
VkPipelineStageFlags2 first_read_stages_;
OrderingBarrier first_write_layout_ordering_;
bool first_access_closed_;
static OrderingBarriers kOrderingRules;
};
using ResourceAccessStateFunction = std::function<void(ResourceAccessState *)>;
using ResourceAccessRangeMap = sparse_container::range_map<ResourceAddress, ResourceAccessState>;
using ResourceRangeMergeIterator = sparse_container::parallel_iterator<ResourceAccessRangeMap, const ResourceAccessRangeMap>;
// Apply the memory barrier without updating the existing barriers. The execution barrier
// changes the "chaining" state, but to keep barriers independent, we defer this until all barriers
// of the batch have been processed. Also, depending on whether layout transition happens, we'll either
// replace the current write barriers or add to them, so accumulate to pending as well.
template <typename ScopeOps>
void ResourceAccessState::ApplyBarrier(ScopeOps &&scope, const SyncBarrier &barrier, bool layout_transition) {
// For independent barriers we need to track what the new barriers and dependency chain *will* be when we're done
// applying the memory barriers
// NOTE: We update the write barrier if the write is in the first access scope or if there is a layout
// transistion, under the theory of "most recent access". If the resource acces *isn't* safe
// vs. this layout transition DetectBarrierHazard should report it. We treat the layout
// transistion *as* a write and in scope with the barrier (it's before visibility).
if (layout_transition) {
if (!last_write.has_value()) {
last_write.emplace(AccessInfo(SYNC_ACCESS_INDEX_NONE), ResourceUsageTagEx{0U});
}
last_write->UpdatePendingBarriers(barrier);
last_write->UpdatePendingLayoutOrdering(barrier);
pending_layout_transition = true;
} else {
if (scope.WriteInScope(barrier, *this)) {
last_write->UpdatePendingBarriers(barrier);
}
if (!pending_layout_transition) {
// Once we're dealing with a layout transition (which is modelled as a *write*) then the last reads/chains
// don't need to be tracked as we're just going to clear them.
VkPipelineStageFlags2 stages_in_scope = VK_PIPELINE_STAGE_2_NONE;
for (auto &read_access : last_reads) {
// The | implements the "dependency chain" logic for this access, as the barriers field stores the second sync
// scope
if (scope.ReadInScope(barrier, read_access)) {
// We'll apply the barrier in the next loop, because it's DRY'r to do it one place.
stages_in_scope |= read_access.stage;
}
}
for (auto &read_access : last_reads) {
if (0 != ((read_access.stage | read_access.sync_stages) & stages_in_scope)) {
// If this stage, or any stage known to be synchronized after it are in scope, apply the barrier to this
// read NOTE: Forwarding barriers to known prior stages changes the sync_stages from shallow to deep,
// because the
// barriers used to determine sync_stages have been propagated to all known earlier stages
read_access.ApplyReadBarrier(barrier.dst_exec_scope.exec_scope);
}
}
}
}
}
// Return if the resulting state is "empty"
template <typename Predicate>
bool ResourceAccessState::ApplyPredicatedWait(Predicate &predicate) {
VkPipelineStageFlags2 sync_reads = VK_PIPELINE_STAGE_2_NONE;
// Use the predicate to build a mask of the read stages we are synchronizing
// Use the sync_stages to also detect reads known to be before any synchronized reads (first pass)
for (auto &read_access : last_reads) {
if (predicate(read_access)) {
// If we know this stage is before any stage we syncing, or if the predicate tells us that we are waited for..
sync_reads |= read_access.stage;
}
}
// Now that we know the reads directly in scopejust need to go over the list again to pick up the "known earlier" stages.
// NOTE: sync_stages is "deep" catching all stages synchronized after it because we forward barriers
uint32_t unsync_count = 0;
for (auto &read_access : last_reads) {
if (0 != ((read_access.stage | read_access.sync_stages) & sync_reads)) {
// This is redundant in the "stage" case, but avoids a second branch to get an accurate count
sync_reads |= read_access.stage;
} else {
++unsync_count;
}
}
if (unsync_count) {
if (sync_reads) {
// When have some remaining unsynchronized reads, we have to rewrite the last_reads array.
ReadStates unsync_reads;
unsync_reads.reserve(unsync_count);
VkPipelineStageFlags2 unsync_read_stages = VK_PIPELINE_STAGE_2_NONE;
for (auto &read_access : last_reads) {
if (0 == (read_access.stage & sync_reads)) {
unsync_reads.emplace_back(read_access);
unsync_read_stages |= read_access.stage;
}
}
last_read_stages = unsync_read_stages;
last_reads = std::move(unsync_reads);
}
} else {
// Nothing remains (or it was empty to begin with)
ClearRead();
}
bool all_clear = last_reads.empty();
if (last_write.has_value()) {
if (predicate(*this) || sync_reads) {
// Clear any predicated write, or any the write from any any access with synchronized reads.
// This could drop RAW detection, but only if the synchronized reads were RAW hazards, and given
// MRR approach to reporting, this is consistent with other drops, especially since fixing the
// RAW wit the sync_reads stages would preclude a subsequent RAW.
ClearWrite();
} else {
all_clear = false;
}
}
return all_clear;
}
template <typename Barrier>
SyncBarrier::SyncBarrier(const Barrier &barrier, const SyncExecScope &src, const SyncExecScope &dst)
: src_exec_scope(src),
src_access_scope(SyncStageAccess::AccessScope(src.valid_accesses, barrier.srcAccessMask)),
dst_exec_scope(dst),
dst_access_scope(SyncStageAccess::AccessScope(dst.valid_accesses, barrier.dstAccessMask)) {}
template <typename Barrier>
SyncBarrier::SyncBarrier(VkQueueFlags queue_flags, const Barrier &barrier) {
auto src = SyncExecScope::MakeSrc(queue_flags, barrier.srcStageMask);
src_exec_scope = src.exec_scope;
src_access_scope = SyncStageAccess::AccessScope(src.valid_accesses, barrier.srcAccessMask);
auto dst = SyncExecScope::MakeDst(queue_flags, barrier.dstStageMask);
dst_exec_scope = dst.exec_scope;
dst_access_scope = SyncStageAccess::AccessScope(dst.valid_accesses, barrier.dstAccessMask);
}