blob: 0dcab42adf88c9308599641dc1d4867231a23d08 [file]
/* Copyright (c) 2019-2026 The Khronos Group Inc.
* 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.
*/
#pragma once
#include "sync/sync_common.h"
#include "sync/sync_barrier.h"
#include "containers/span.h"
#include <cstring> // memcpy
#include <optional>
namespace syncval {
class AccessState;
struct WriteState;
struct ReadState;
struct FirstAccess;
struct SemaphoreScope;
struct AccessContextStats;
// 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,
kColorAttachment = 1,
kDepthStencilAttachment = 2,
kRaster = 3,
kNumOrderings = 4,
};
struct SyncFlag {
enum : uint8_t {
kPresent = 0x01,
kMarker = 0x02,
};
};
using SyncFlags = uint8_t;
const char *string_SyncHazardVUID(SyncHazard hazard);
struct SyncHazardInfo {
bool is_write = false;
bool is_prior_write = false;
bool is_racing_hazard = false;
bool IsWrite() const { return is_write; }
bool IsRead() const { return !is_write; }
bool IsPriorWrite() const { return is_prior_write; }
bool IsPriorRead() const { return !is_prior_write; }
bool IsRacingHazard() const { return is_racing_hazard; }
};
SyncHazardInfo GetSyncHazardInfo(SyncHazard hazard);
class HazardResult {
public:
struct HazardState {
std::unique_ptr<const AccessState> access_state;
std::unique_ptr<const FirstAccess> 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 AccessState *access_state, const SyncAccessInfo &usage_info, SyncHazard hazard,
SyncAccessIndex prior_access_index, ResourceUsageTagEx tag_ex);
};
static HazardResult HazardVsPriorWrite(const AccessState *access_state, const SyncAccessInfo &usage_info, SyncHazard hazard,
const WriteState &prior_write);
static HazardResult HazardVsPriorRead(const AccessState *access_state, const SyncAccessInfo &usage_info, SyncHazard hazard,
const ReadState &prior_read);
void AddRecordedAccess(const FirstAccess &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 FirstAccess> &RecordedAccess() const {
assert(state_);
return state_->recorded_access;
}
const HazardState &State() const {
assert(state_);
return state_.value();
}
private:
std::optional<HazardState> state_;
};
enum class AttachmentAccessType : uint8_t {
Empty,
Access, // Generic access
LoadOp,
StoreOp,
ResolveRead,
ResolveWrite,
};
struct AttachmentAccess {
AttachmentAccessType type = AttachmentAccessType::Empty;
SyncOrdering ordering = SyncOrdering::kOrderingNone;
// Identifies a render pass instance (and subpass for legacy render passes).
// Used to determine whether two accesses are in the same render pass instance.
uint32_t render_pass_instance_id = vvl::kNoIndex32;
uint32_t subpass = vvl::kNoIndex32;
static AttachmentAccess NonAttachment() { return AttachmentAccess{}; }
bool operator==(const AttachmentAccess &other) const;
};
struct FirstAccess {
const SyncAccessInfo *usage_info;
ResourceUsageTag tag;
uint32_t handle_index;
AttachmentAccess attachment_access;
SyncFlags flags;
FirstAccess(const SyncAccessInfo &usage_info, ResourceUsageTagEx tag_ex, const AttachmentAccess &attachment_access,
SyncFlags flags)
: usage_info(&usage_info),
tag(tag_ex.tag),
handle_index(tag_ex.handle_index),
attachment_access(attachment_access),
flags(flags) {}
bool operator==(const FirstAccess &rhs) const {
return tag == rhs.tag && usage_info == rhs.usage_info && attachment_access == rhs.attachment_access && flags == rhs.flags;
}
ResourceUsageTagEx TagEx() const { return {tag, handle_index}; }
};
using QueueId = uint32_t;
struct OrderingBarrier {
VkPipelineStageFlags2 exec_scope = VK_PIPELINE_STAGE_2_NONE;
SyncAccessFlags access_scope;
bool operator==(const OrderingBarrier &rhs) const;
size_t Hash() const;
};
const OrderingBarrier &GetOrderingRules(SyncOrdering ordering_enum);
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 {
VkPipelineStageFlagBits2 stage; // The stage of this read
SyncAccessIndex access_index; // TODO: Revisit whether this needs to support multiple reads per stage
AttachmentAccess attachment_access;
VkPipelineStageFlags2 barriers; // all applicable barriered stages
VkPipelineStageFlags2 sync_stages; // reads known to have happened after this
ResourceUsageTag tag;
uint32_t handle_index;
QueueId queue;
void Set(VkPipelineStageFlagBits2 stage, SyncAccessIndex access_index, const AttachmentAccess &attachment_access,
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);
}
bool IsReadBarrierHazard(QueueId barrier_queue, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const {
// 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;
}
// 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;
return (src_exec_scope & (queue_ordered_stage | barriers)) == 0;
}
bool ReadOrDependencyChainInSourceScope(QueueId queue, VkPipelineStageFlags2 src_exec_scope) const;
bool InBarrierSourceScope(const BarrierScope &barrier_scope) const;
};
static_assert(std::is_trivially_copyable_v<ReadState>);
struct WriteState {
SyncAccessIndex access_index;
AttachmentAccess attachment_access;
// Union of applicable barrier masks since last write
SyncAccessFlags barriers;
// Accumulates the dstStages of barriers if they chain
VkPipelineStageFlags2 dependency_chain;
ResourceUsageTag tag;
uint32_t handle_index;
QueueId queue;
SyncFlags flags;
void Set(SyncAccessIndex access_index, const AttachmentAccess &attachment_access, ResourceUsageTagEx tag_ex, SyncFlags flags);
void SetQueueId(QueueId id);
void MergeBarriers(const WriteState &other);
bool operator==(const WriteState &rhs) const;
bool DependencyChainInSourceScope(VkPipelineStageFlags2 src_exec_scope) const;
bool WriteInSourceScope(const SyncAccessFlags &src_access_scope) const;
bool WriteOrDependencyChainInSourceScope(QueueId queue_id, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const;
bool InBarrierSourceScope(const BarrierScope &barrier_scope) const;
bool IsWriteHazard(const SyncAccessInfo &usage_info) const;
bool IsWriteBarrierHazard(QueueId queue_id, VkPipelineStageFlags2 src_exec_scope,
const SyncAccessFlags &src_access_scope) const;
bool IsOrdered(const OrderingBarrier &ordering, QueueId queue_id) const;
bool IsLoadOp() const { return attachment_access.type == AttachmentAccessType::LoadOp; }
bool IsStoreOp() const { return attachment_access.type == AttachmentAccessType::StoreOp; }
bool IsPresent() const { return flags & SyncFlag::kPresent; }
ResourceUsageTagEx TagEx() const { return {tag, handle_index}; }
};
static_assert(std::is_trivially_copyable_v<WriteState>);
enum class PendingBarrierType : uint8_t { ReadAccessBarrier, WriteAccessBarrier, LayoutTransition };
struct PendingBarrierInfo {
PendingBarrierType type;
uint32_t index; // indexes array determined by 'type'
AccessState *access_state;
};
struct PendingReadBarrier {
VkPipelineStageFlags2 barriers;
uint32_t last_reads_index; // indexes AccessState::last_reads
};
struct PendingWriteBarrier {
SyncAccessFlags barriers;
VkPipelineStageFlags2 dependency_chain;
};
struct PendingLayoutTransition {
OrderingBarrier ordering;
uint32_t handle_index;
};
// PendingBarriers stores the results of independent barrier applications, so the applied barriers
// do not interact (for example, they do not create execution dependencies between themselves).
// Apply() stores the final result in the access state. Independent barrier application is required
// by various sync APIs, such as vkCmdPipelineBarrier.
//
// A naive approach to applying a set of independent barriers is to apply them directly to the access
// state one at a time. This creates dependencies. PendingBarriers solves this by delaying updates to
// the access state until all barriers have been processed.
struct PendingBarriers {
std::vector<PendingBarrierInfo> infos;
std::vector<PendingReadBarrier> read_barriers;
std::vector<PendingWriteBarrier> write_barriers;
std::vector<PendingLayoutTransition> layout_transitions;
// Store result of barrier application as PendingBarriers state
void AddReadBarrier(AccessState *access_state, uint32_t last_reads_index, const SyncBarrier &barrier);
void AddWriteBarrier(AccessState *access_state, const SyncBarrier &barrier);
void AddLayoutTransition(AccessState *access_state, const OrderingBarrier &layout_transition_ordering_barrier,
uint32_t layout_transition_handle_index);
// Update accesss state with collected barriers
void Apply(const ResourceUsageTag exec_tag);
};
class AccessState {
public:
~AccessState();
AccessState(const AccessState &other);
AccessState(AccessState &&other);
// Use explicit default construction and assignment to control all places where this happens
static AccessState DefaultAccessState() { return {}; }
void Assign(const AccessState &other);
void Assign(AccessState &&other);
AccessState &operator=(const AccessState &other) = delete;
AccessState &operator=(AccessState &&other) = delete;
HazardResult DetectHazard(const SyncAccessInfo &usage_info) const;
HazardResult DetectMarkerHazard() const;
HazardResult DetectHazard(const SyncAccessInfo &usage_info, const OrderingBarrier &ordering,
const AttachmentAccess &attachment_access, SyncFlags flags, QueueId queue_id,
bool detect_load_op_after_store_op_hazards) const;
HazardResult DetectHazard(const AccessState &recorded_use, QueueId queue_id, const ResourceUsageRange &tag_range,
bool detect_load_op_after_store_op_hazards) const;
HazardResult DetectAsyncHazard(const SyncAccessInfo &usage_info, ResourceUsageTag start_tag, QueueId queue_id) const;
HazardResult DetectAsyncHazard(const AccessState &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 AccessState &scope_state,
VkPipelineStageFlags2 source_exec_scope, const SyncAccessFlags &source_access_scope,
QueueId event_queue, ResourceUsageTag event_tag) const;
void Update(const SyncAccessInfo &usage_info, const AttachmentAccess &attachment_access, ResourceUsageTagEx tag_ex,
SyncFlags flags = 0);
void SetWrite(SyncAccessIndex access_index, const AttachmentAccess &attachment_access, ResourceUsageTagEx tag_ex,
SyncFlags flags = 0);
void ClearWrite();
void ClearRead();
void ClearFirstUse();
void Resolve(const AccessState &other);
// Apply a single barrier to the access state
bool ApplyBarrier(const BarrierScope &barrier_scope, const SyncBarrier &barrier, bool layout_transition = false,
uint32_t layout_transition_handle_index = vvl::kNoIndex32,
ResourceUsageTag layout_transition_tag = kInvalidTag);
// Store the result of barrier application in PendingBarriers.
// Does not update the access state (as ApplyBarrier does).
// Used for applying multiple barriers independently.
void CollectPendingBarriers(const BarrierScope &barrier_scope, const SyncBarrier &barrier, bool layout_transition,
uint32_t layout_transition_handle_index, PendingBarriers &pending_barriers);
// Apply pending barriers to the access state.
// Called after all barrier application results are collected in PendingBarriers.
void ApplyPendingReadBarrier(const PendingReadBarrier &read_barrier, ResourceUsageTag tag);
void ApplyPendingWriteBarrier(const PendingWriteBarrier &write_barrier);
void ApplyPendingLayoutTransition(const PendingLayoutTransition &layout_transition, ResourceUsageTag layout_transition_tag);
void ApplySemaphore(const SemaphoreScope &signal, const SemaphoreScope &wait);
// Clear read/write accesses that satisfy the predicate
// (predicate says which accesses should be considered synchronized).
// Return true if all accesses were cleared and access state is empty
template <typename Predicate>
bool ClearPredicatedAccesses(Predicate &predicate);
ResourceUsageRange GetFirstAccessRange() const;
bool FirstAccessInTagRange(const ResourceUsageRange &tag_range) const;
void OffsetTag(ResourceUsageTag offset);
bool HasWriteOp() const { return last_write.has_value(); }
bool IsLastWriteOp(SyncAccessIndex access_index) const {
return last_write.has_value() && last_write->access_index == access_index;
}
ResourceUsageTag LastWriteTag() const { return last_write.has_value() ? last_write->tag : ResourceUsageTag(0); }
const WriteState &LastWrite() const;
bool operator==(const AccessState &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);
bool read_same = (last_read_stages == rhs.last_read_stages) && (last_read_count == rhs.last_read_count);
if (read_same) {
for (uint32_t i = 0; i < last_read_count; i++) {
read_same = read_same && (last_reads[i] == rhs.last_reads[i]);
}
}
const bool read_write_same = write_same && read_same;
const bool same = read_write_same && (first_accesses_ == rhs.first_accesses_) &&
(first_read_stages_ == rhs.first_read_stages_) &&
(first_write_layout_ordering_index == rhs.first_write_layout_ordering_index);
return same;
}
bool operator!=(const AccessState &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;
void Normalize();
void GatherReferencedTags(ResourceUsageTagSet &used) const;
void UpdateStats(AccessContextStats &stats) const;
private:
AccessState() = default; // not accessible, use DefaultAccessState instead
void CopySimpleMembers(const AccessState &other);
bool IsRAWHazard(const SyncAccessInfo &usage_info) const;
bool IsReadHazard(VkPipelineStageFlags2 stage_mask, const ReadState &read_access) const {
return stage_mask != (stage_mask & read_access.barriers);
}
VkPipelineStageFlags2 GetOrderedStages(QueueId queue_id, const OrderingBarrier &ordering,
AttachmentAccessType attachment_access_type) const;
void UpdateFirst(ResourceUsageTagEx tag_ex, const SyncAccessInfo &usage_info, const AttachmentAccess &attachment_access,
SyncFlags flags = 0);
void TouchupFirstForLayoutTransition(ResourceUsageTag tag, const OrderingBarrier &layout_ordering);
bool HasReads() const { return last_read_count != 0; }
vvl::span<ReadState> GetReads() { return vvl::make_span(last_reads, last_read_count); }
vvl::span<ReadState> GetReads() const { return vvl::make_span(last_reads, last_read_count); }
void AddRead(const ReadState &read);
void MergeReads(const AccessState &other);
void ClearReadStates();
public:
// Index of the next global barrier to apply.
// Global barriers are stored in the AccessContext associated with this access state.
// If 0, no global barriers have been applied yet.
// If greater than 0, then all global barriers with smaller indices are already applied.
uint32_t next_global_barrier_index = 0;
private:
// The most recent write.
// NOTE: For reads, each must be "safe" relative to its prior write, so we need
// only to save the most recent write operation, since anything *transitively*
// unsafe would already be included.
std::optional<WriteState> last_write;
uint32_t last_read_count = 0;
// Points to the last_read_count read states
ReadState *last_reads = nullptr;
// The common case is a single read. In that case, last_reads points to this object
// and last_read_count is 1.
ReadState single_last_read;
VkPipelineStageFlags2 last_read_stages = VK_PIPELINE_STAGE_2_NONE;
VkPipelineStageFlags2 read_execution_barriers = VK_PIPELINE_STAGE_2_NONE;
// NOTE: Reserve capacity for 2 first accesses, more than that is not very common
using FirstAccesses = small_vector<FirstAccess, 2>;
FirstAccesses first_accesses_;
VkPipelineStageFlags2 first_read_stages_ = VK_PIPELINE_STAGE_2_NONE;
uint32_t first_write_layout_ordering_index = vvl::kNoIndex32;
bool first_access_closed_ = false;
// TODO Input Attachment cleanup for multiple reads in a given stage
// Tracks whether the fragment shader read is input attachment read
// TODO: Add a NONE (zero) enum to SyncStageAccessFlags for
// input_attachment_read and last_write
bool input_attachment_read = false;
};
using AccessStateFunction = std::function<void(AccessState *)>;
template <typename Predicate>
bool AccessState::ClearPredicatedAccesses(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 (const auto &read_access : GetReads()) {
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 (const auto &read_access : GetReads()) {
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.
small_vector<ReadState, 4> unsync_reads;
unsync_reads.reserve(unsync_count);
VkPipelineStageFlags2 unsync_read_stages = VK_PIPELINE_STAGE_2_NONE;
for (auto &read_access : GetReads()) {
if ((read_access.stage & sync_reads) == 0) {
unsync_reads.emplace_back(read_access);
unsync_read_stages |= read_access.stage;
}
}
last_read_stages = unsync_read_stages;
ClearReadStates();
for (const ReadState &read : unsync_reads) {
AddRead(read);
}
}
} else {
// Nothing remains (or it was empty to begin with)
ClearRead();
}
bool all_clear = !HasReads();
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;
}
// A helper function to apply multiple barriers.
// NOTE: That's for use cases when BarrierScope does not use queue id or tag (record time, not-event barriers).
// This can be extended if necessary to provide BarrierScope for each barrier.
void ApplyBarriers(AccessState &access_state, const std::vector<SyncBarrier> &barriers, bool layout_transition = false,
ResourceUsageTag layout_transition_tag = kInvalidTag);
// Global registry of layout transition ordering barriers
ThreadSafeLookupTable<OrderingBarrier> &GetLayoutOrderingBarrierLookup();
} // namespace syncval
namespace std {
template <>
struct hash<syncval::OrderingBarrier> {
size_t operator()(const syncval::OrderingBarrier &ordering_barrier) const { return ordering_barrier.Hash(); }
};
} // namespace std