/* 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);
}
