| /* 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 |