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

#include <vulkan/utility/vk_format_utils.h>
#include "sync/sync_render_pass.h"
#include "sync/sync_validation.h"
#include "sync/sync_op.h"
#include "sync/sync_image.h"
#include "state_tracker/render_pass_state.h"
#include "state_tracker/pipeline_state.h"
#include "utils/math_utils.h"

namespace syncval {

class ValidateResolveAction {
  public:
    ValidateResolveAction(VkRenderPass render_pass, uint32_t subpass, uint32_t view_mask, const AccessContext& context,
                          const CommandBufferAccessContext& cb_context, vvl::Func command)
        : render_pass_(render_pass),
          subpass_(subpass),
          view_mask_(view_mask),
          context_(context),
          cb_context_(cb_context),
          command_(command),
          skip_(false) {}

    void operator()(const char* aspect_name, const char* resolve_action_name, uint32_t src_at, uint32_t dst_at,
                    const AttachmentViewGen& view_gen, AttachmentViewGen::Gen gen_type, SyncAccessIndex current_usage,
                    const AttachmentAccess& attachment_access) {
        const HazardResult hazard =
            context_.DetectAttachmentHazard(view_gen, gen_type, current_usage, attachment_access, view_mask_);
        if (hazard.IsHazard()) {
            const Location loc(command_);

            const SyncValidator& validator = cb_context_.GetSyncState();

            std::ostringstream ss;
            ss << validator.FormatHandle(view_gen.GetViewState()->Handle());
            ss << " (" << resolve_action_name << " of " << aspect_name << " multisample attachment " << src_at;
            ss << " in subpass " << subpass_ << " of " << validator.FormatHandle(render_pass_) << ")";
            const std::string resource_description = ss.str();
            const auto error =
                validator.error_messages_.RenderPassResolveError(hazard, cb_context_, command_, resource_description);
            skip_ |= validator.SyncError(hazard.Hazard(), render_pass_, loc, error);
        }
    }
    // Providing a mechanism for the constructing caller to get the result of the validation
    bool GetSkip() const { return skip_; }

  private:
    VkRenderPass render_pass_;
    const uint32_t subpass_;
    const uint32_t view_mask_;
    const AccessContext& context_;
    const CommandBufferAccessContext& cb_context_;
    vvl::Func command_;
    bool skip_;
};

class UpdateStateResolveAction {
  public:
    UpdateStateResolveAction(AccessContext& context, uint32_t view_mask, ResourceUsageTag tag)
        : context_(context), view_mask_(view_mask), tag_(tag) {}
    void operator()(const char*, const char*, uint32_t, uint32_t, const AttachmentViewGen& view_gen,
                    AttachmentViewGen::Gen gen_type, SyncAccessIndex current_usage, const AttachmentAccess& attachment_access) {
        context_.UpdateAttachmentAccessState(view_gen, gen_type, current_usage, attachment_access, ResourceUsageTagEx{tag_},
                                             view_mask_);
    }

  private:
    AccessContext& context_;
    const uint32_t view_mask_;
    const ResourceUsageTag tag_;
};

std::unique_ptr<AccessContext[]> InitSubpassContexts(VkQueueFlags queue_flags, const vvl::RenderPass& rp_state,
                                                     const AccessContext& external_context) {
    const uint32_t subpass_count = rp_state.create_info.subpassCount;
    auto subpass_contexts = std::make_unique<AccessContext[]>(subpass_count);
    // Add this for all subpasses here so that they exsist during next subpass validation
    for (uint32_t pass = 0; pass < subpass_count; pass++) {
        subpass_contexts[pass].validator = external_context.validator;
        subpass_contexts[pass].InitFrom(pass, queue_flags, rp_state.subpass_dependency_infos, subpass_contexts.get(),
                                        external_context);
    }
    return subpass_contexts;
}

static SyncAccessIndex GetLoadOpUsageIndex(VkAttachmentLoadOp load_op, AttachmentType type) {
    SyncAccessIndex access_index;
    if (load_op == VK_ATTACHMENT_LOAD_OP_NONE) {
        access_index = SYNC_ACCESS_INDEX_NONE;
    } else if (type == AttachmentType::kColor) {
        access_index = (load_op == VK_ATTACHMENT_LOAD_OP_LOAD) ? SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ
                                                               : SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE;
    } else {  // depth and stencil ops are the same
        access_index = (load_op == VK_ATTACHMENT_LOAD_OP_LOAD) ? SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_READ
                                                               : SYNC_EARLY_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE;
    }
    return access_index;
}

static SyncAccessIndex GetStoreOpUsageIndex(VkAttachmentStoreOp store_op, AttachmentType type) {
    SyncAccessIndex access_index;
    if (store_op == VK_ATTACHMENT_STORE_OP_NONE) {
        access_index = SYNC_ACCESS_INDEX_NONE;
    } else if (type == AttachmentType::kColor) {
        access_index = SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE;
    } else {  // depth and stencil ops are the same
        access_index = SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE;
    }
    return access_index;
}

static SyncAccessIndex ColorLoadUsage(VkAttachmentLoadOp load_op) { return GetLoadOpUsageIndex(load_op, AttachmentType::kColor); }
static SyncAccessIndex DepthStencilLoadUsage(VkAttachmentLoadOp load_op) {
    return GetLoadOpUsageIndex(load_op, AttachmentType::kDepth);
}

// Keeps only those bits in view_mask for which subpass is "enabled" based on subpass_per_view.
static std::optional<uint32_t> FilterViewMask(uint32_t view_mask, uint32_t subpass,
                                              const vvl::RenderPass::SubpassPerView& subpass_per_view) {
    // Special case when multiview is disabled to unify caller code
    if (view_mask == 0) {
        return (subpass_per_view[0] == subpass) ? std::optional<uint32_t>(0) : std::nullopt;
    }

    uint32_t filtered_view_mask = 0;
    const auto view_indices = GetSetBitIndices(view_mask);
    for (uint8_t view_index : view_indices) {
        if (subpass_per_view[view_index] == subpass) {
            filtered_view_mask |= 1 << view_index;
        }
    }
    return (filtered_view_mask != 0) ? std::optional<uint32_t>(filtered_view_mask) : std::nullopt;
}

// Caller must manage returned pointer
static AccessContext* CreateStoreResolveProxyContext(const AccessContext& context, const vvl::RenderPass& rp_state,
                                                     uint32_t render_pass_instance_id, uint32_t subpass, uint32_t view_mask,
                                                     const AttachmentViewGenVector& attachment_views) {
    auto* proxy = new AccessContext(*context.validator);
    proxy->InitFrom(context);
    RenderPassAccessContext::UpdateAttachmentResolveAccess(rp_state, attachment_views, render_pass_instance_id, subpass, view_mask,
                                                           kInvalidTag, *proxy);
    RenderPassAccessContext::UpdateAttachmentStoreAccess(rp_state, attachment_views, render_pass_instance_id, subpass, view_mask,
                                                         kInvalidTag, *proxy);
    return proxy;
}

// Layout transitions are handled as if the were occuring in the beginning of the next subpass
bool RenderPassAccessContext::ValidateLayoutTransitions(const CommandBufferAccessContext& cb_context,
                                                        const AccessContext& access_context, const vvl::RenderPass& rp_state,
                                                        uint32_t render_pass_instance_id, uint32_t subpass, uint32_t view_mask,
                                                        const AttachmentViewGenVector& attachment_views, vvl::Func command) {
    bool skip = false;
    // As validation methods are const and precede the record/update phase, for any tranistions from the immediately
    // previous subpass, we have to validate them against a copy of the AccessContext, with resolve operations applied, as
    // those affects have not been recorded yet.
    //
    // Note: we could be more efficient by tracking whether or not we actually *have* any changes (e.g. attachment resolve)
    // to apply and only copy then, if this proves a hot spot.
    std::unique_ptr<AccessContext> src_context_proxy;
    SubpassBarrier proxy_subpass_barrier;

    const auto& transitions = rp_state.subpass_transitions[subpass];
    for (const auto& transition : transitions) {
        const SubpassBarrier& subpass_barrier = access_context.GetSubpassBarrier(transition.src_subpass);
        const SubpassBarrier* p_subpass_barrier = &subpass_barrier;

        const bool src_context_needs_proxy =
            transition.src_subpass != VK_SUBPASS_EXTERNAL && (transition.src_subpass + 1 == subpass);

        if (src_context_needs_proxy) {
            if (!src_context_proxy) {
                // TODO: this looks wrong to create proxy once for all iterations.
                // Proxy depends on current iteration (transition.src_subpass), so it should be recreated
                // each time when needed. Write a test that exposes this and make a fix.
                src_context_proxy.reset(CreateStoreResolveProxyContext(*subpass_barrier.src_subpass_context, rp_state,
                                                                       render_pass_instance_id, transition.src_subpass, view_mask,
                                                                       attachment_views));
                proxy_subpass_barrier = subpass_barrier;
                proxy_subpass_barrier.src_subpass_context = src_context_proxy.get();
            }
            p_subpass_barrier = &proxy_subpass_barrier;
        }
        auto hazard = access_context.DetectSubpassTransitionHazard(*p_subpass_barrier, attachment_views[transition.attachment]);
        if (hazard.IsHazard()) {
            const SyncValidator& sync_state = cb_context.GetSyncState();
            const Location loc(command);

            const vvl::ImageView* attachment_view = attachment_views[transition.attachment].GetViewState();
            std::ostringstream ss;
            ss << "in subpass " << subpass << " of " << sync_state.FormatHandle(rp_state.Handle());
            ss << " on attachment " << transition.attachment << " (";
            ss << sync_state.FormatHandle(attachment_view->Handle());
            ss << ", " << sync_state.FormatHandle(attachment_view->image_state->Handle());
            ss << ", oldLayout " << string_VkImageLayout(transition.old_layout);
            ss << ", newLayout " << string_VkImageLayout(transition.new_layout);
            ss << ")";
            const std::string resource_description = ss.str();

            if (hazard.Tag() == kInvalidTag) {
                const auto error = sync_state.error_messages_.RenderPassLayoutTransitionVsResolveError(
                    hazard, cb_context, command, resource_description, transition.old_layout, transition.new_layout,
                    transition.src_subpass);
                skip |= sync_state.SyncError(hazard.Hazard(), rp_state.Handle(), loc, error);
            } else {
                const auto error = sync_state.error_messages_.RenderPassLayoutTransitionError(
                    hazard, cb_context, command, resource_description, transition.old_layout, transition.new_layout);
                skip |= sync_state.SyncError(hazard.Hazard(), rp_state.Handle(), loc, error);
            }
        }
    }
    return skip;
}

bool RenderPassAccessContext::ValidateLoadOperation(const CommandBufferAccessContext& cb_context,
                                                    const AccessContext& access_context, const vvl::RenderPass& rp_state,
                                                    uint32_t render_pass_instance_id, uint32_t subpass, uint32_t view_mask,
                                                    const AttachmentViewGenVector& attachment_views, vvl::Func command) {
    bool skip = false;

    AttachmentAccess attachment_access;
    attachment_access.type = AttachmentAccessType::LoadOp;
    attachment_access.render_pass_instance_id = render_pass_instance_id;
    attachment_access.subpass = subpass;

    for (uint32_t i = 0; i < rp_state.create_info.attachmentCount; i++) {
        if (auto filtered_view_mask = FilterViewMask(view_mask, subpass, rp_state.attachment_first_subpass[i])) {
            const auto& view_gen = attachment_views[i];
            const auto& ci = rp_state.create_info.pAttachments[i];

            // Need check in the following way
            // 1) if the usage bit isn't in the dest_access_scope, and there is layout traniition for initial use, report hazard
            //    vs. transition
            // 2) if there isn't a layout transition, we need to look at the  external context with a "detect hazard" operation
            //    for each aspect loaded.

            const bool has_depth = vkuFormatHasDepth(ci.format);
            const bool has_stencil = vkuFormatHasStencil(ci.format);
            const bool is_color = !(has_depth || has_stencil);

            const SyncAccessIndex load_index = has_depth ? DepthStencilLoadUsage(ci.loadOp) : ColorLoadUsage(ci.loadOp);
            const SyncAccessIndex stencil_load_index = has_stencil ? DepthStencilLoadUsage(ci.stencilLoadOp) : load_index;

            HazardResult hazard;
            const char* aspect = nullptr;

            bool checked_stencil = false;
            if (is_color && (load_index != SYNC_ACCESS_INDEX_NONE)) {
                attachment_access.ordering = SyncOrdering::kColorAttachment;
                hazard = access_context.DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kRenderArea, load_index,
                                                               attachment_access, *filtered_view_mask);
                aspect = "color";
            } else {
                if (has_depth && (load_index != SYNC_ACCESS_INDEX_NONE)) {
                    attachment_access.ordering = SyncOrdering::kDepthStencilAttachment;
                    hazard = access_context.DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea,
                                                                   load_index, attachment_access, *filtered_view_mask);
                    aspect = "depth";
                }
                if (!hazard.IsHazard() && has_stencil && (stencil_load_index != SYNC_ACCESS_INDEX_NONE)) {
                    attachment_access.ordering = SyncOrdering::kDepthStencilAttachment;
                    hazard = access_context.DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                                   stencil_load_index, attachment_access, *filtered_view_mask);
                    aspect = "stencil";
                    checked_stencil = true;
                }
            }

            if (hazard.IsHazard()) {
                const VkAttachmentLoadOp load_op = checked_stencil ? ci.stencilLoadOp : ci.loadOp;
                const SyncValidator& sync_state = cb_context.GetSyncState();
                const Location loc(command);

                std::ostringstream ss;
                ss << "the " << aspect << " aspect of attachment " << i;
                ss << " (" << sync_state.FormatHandle(view_gen.GetViewState()->Handle()) << ")";
                ss << " in subpass " << subpass;
                ss << " of " << sync_state.FormatHandle(rp_state.Handle());
                ss << " (loadOp " << string_VkAttachmentLoadOp(load_op) << ")";
                const std::string resource_description = ss.str();

                if (hazard.Tag() == kInvalidTag) {  // Hazard vs. ILT
                    const auto error = sync_state.error_messages_.RenderPassLoadOpVsLayoutTransitionError(
                        hazard, cb_context, command, resource_description, load_op, is_color);
                    skip |= sync_state.SyncError(hazard.Hazard(), rp_state.Handle(), loc, error);
                } else {
                    const std::string error = sync_state.error_messages_.RenderPassLoadOpError(
                        hazard, cb_context, command, resource_description, subpass, i, load_op, is_color);
                    skip |= sync_state.SyncError(hazard.Hazard(), rp_state.Handle(), loc, error);
                }
            }
        }
    }
    return skip;
}

// Store operation validation can ignore resolve (before it) and layout tranistions after it.  The first is ignored
// because of the ordering guarantees w.r.t. sample access and that the resolve validation hasn't altered the state, because
// store is part of the same Next/End operation.
// The latter is handled in layout transistion validation directly
bool RenderPassAccessContext::ValidateStoreOperation(const CommandBufferAccessContext& cb_context, vvl::Func command) const {
    bool skip = false;

    const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kRaster, AttachmentAccessType::StoreOp);
    const uint32_t view_mask = rp_state_->create_info.pSubpasses[current_subpass_].viewMask;

    for (uint32_t i = 0; i < rp_state_->create_info.attachmentCount; i++) {
        if (auto filtered_view_mask = FilterViewMask(view_mask, current_subpass_, rp_state_->attachment_last_subpass[i])) {
            const AttachmentViewGen& view_gen = attachment_views_[i];
            const auto& ci = rp_state_->create_info.pAttachments[i];

            // The spec states that "don't care" is an operation with VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
            // so we assume that an implementation is *free* to write in that case, meaning that for correctness
            // sake, we treat DONT_CARE as writing.
            const bool has_depth = vkuFormatHasDepth(ci.format);
            const bool has_stencil = vkuFormatHasStencil(ci.format);
            const bool is_color = !(has_depth || has_stencil);
            const bool store_op_stores = ci.storeOp != VK_ATTACHMENT_STORE_OP_NONE;
            if (!has_stencil && !store_op_stores) continue;

            HazardResult hazard;
            const char* aspect = nullptr;
            bool checked_stencil = false;
            if (is_color) {
                hazard = CurrentContext().DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kRenderArea,
                                                                 SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE,
                                                                 attachment_access, *filtered_view_mask);
                aspect = "color";
            } else {
                const bool stencil_op_stores = ci.stencilStoreOp != VK_ATTACHMENT_STORE_OP_NONE;
                if (has_depth && store_op_stores) {
                    hazard = CurrentContext().DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea,
                                                                     SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                                     attachment_access, *filtered_view_mask);
                    aspect = "depth";
                }
                if (!hazard.IsHazard() && has_stencil && stencil_op_stores) {
                    hazard = CurrentContext().DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                                     SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                                     attachment_access, *filtered_view_mask);
                    aspect = "stencil";
                    checked_stencil = true;
                }
            }

            if (hazard.IsHazard()) {
                const SyncValidator& sync_state = cb_context.GetSyncState();
                const char* const op_type_string = checked_stencil ? "stencilStoreOp" : "storeOp";
                const VkAttachmentStoreOp store_op = checked_stencil ? ci.stencilStoreOp : ci.storeOp;
                const Location loc(command);

                std::ostringstream ss;
                ss << sync_state.FormatHandle(view_gen.GetViewState()->Handle());
                ss << " (subpass " << current_subpass_ << " of " << sync_state.FormatHandle(rp_state_->Handle());
                ss << ", attachment " << i;
                ss << ", aspect " << aspect << " during store with " << op_type_string;
                ss << " " << string_VkAttachmentStoreOp(store_op) << ")";
                const std::string resource_description = ss.str();

                const std::string error =
                    sync_state.error_messages_.RenderPassStoreOpError(hazard, cb_context, command, resource_description, store_op);
                skip |= sync_state.SyncError(hazard.Hazard(), rp_state_->Handle(), loc, error);
            }
        }
    }
    return skip;
}

static bool IsDepthAttachmentWriteable(const LastBound& last_bound_state, const VkFormat format) {
    const bool depth_write_enable = last_bound_state.IsDepthWriteEnable();
    return (vkuFormatIsDepthAndStencil(format) || vkuFormatIsDepthOnly(format)) && depth_write_enable;
}

static bool IsStencilAttachmentWriteable(const LastBound& last_bound_state, const VkFormat format) {
    if (!vkuFormatIsDepthAndStencil(format) && !vkuFormatIsStencilOnly(format)) {
        return false;
    }
    if (!last_bound_state.IsStencilTestEnable()) {
        return false;
    }
    auto is_writable = [&last_bound_state](const VkStencilOpState& ops) -> bool {
        if (ops.writeMask == 0) {
            return false;
        }

        // If compareOp is ALWAYS then failOp never runs (no writes possible)
        const bool ignore_fail_op = (ops.compareOp == VK_COMPARE_OP_ALWAYS);

        // If compareOp is NEVER then passOp never runs (no writes possible)
        const bool ignore_pass_op = (ops.compareOp == VK_COMPARE_OP_NEVER);

        // If depth test is not enabled then depthFailOp never runs (no writes possible)
        const bool ignore_depth_fail_op = !last_bound_state.IsDepthTestEnable();

        const bool is_read = (ops.failOp == VK_STENCIL_OP_KEEP || ignore_fail_op) &&
                             (ops.passOp == VK_STENCIL_OP_KEEP || ignore_pass_op) &&
                             (ops.depthFailOp == VK_STENCIL_OP_KEEP || ignore_depth_fail_op);
        return !is_read;
    };
    const VkStencilOpState front_ops = last_bound_state.GetStencilOpStateFront();
    const VkStencilOpState back_ops = last_bound_state.GetStencilOpStateBack();
    return is_writable(front_ops) || is_writable(back_ops);
}

// Traverse the attachment resolves for this a specific subpass, and do action() to them.
// Used by both validation and record operations
//
// The signature for Action() reflect the needs of both uses.
template <typename Action>
void ResolveOperation(Action& action, const vvl::RenderPass& rp_state, const AttachmentViewGenVector& attachment_views,
                      uint32_t render_pass_instance_id, uint32_t subpass) {
    const auto& rp_ci = rp_state.create_info;
    const auto* attachment_ci = rp_ci.pAttachments;
    const auto& subpass_ci = rp_ci.pSubpasses[subpass];

    AttachmentAccess attachment_access;
    attachment_access.render_pass_instance_id = render_pass_instance_id;
    attachment_access.subpass = subpass;

    // Color resolve requires an inuse color attachment and a matching inuse resolve attachment
    if (subpass_ci.pResolveAttachments && subpass_ci.pColorAttachments) {
        attachment_access.ordering = SyncOrdering::kColorAttachment;
        for (uint32_t i = 0; i < subpass_ci.colorAttachmentCount; i++) {
            const uint32_t color_attach = subpass_ci.pColorAttachments[i].attachment;
            const uint32_t resolve_attach = subpass_ci.pResolveAttachments[i].attachment;
            if (color_attach != VK_ATTACHMENT_UNUSED && resolve_attach != VK_ATTACHMENT_UNUSED) {
                attachment_access.type = AttachmentAccessType::ResolveRead;
                action("color", "resolve read", color_attach, resolve_attach, attachment_views[color_attach],
                       AttachmentViewGen::Gen::kRenderArea, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ, attachment_access);

                attachment_access.type = AttachmentAccessType::ResolveWrite;
                action("color", "resolve write", color_attach, resolve_attach, attachment_views[resolve_attach],
                       AttachmentViewGen::Gen::kRenderArea, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);
            }
        }
    }

    // Depth stencil resolve only if the extension is present
    const auto ds_resolve = vku::FindStructInPNextChain<VkSubpassDescriptionDepthStencilResolve>(subpass_ci.pNext);
    if (ds_resolve && ds_resolve->pDepthStencilResolveAttachment &&
        (ds_resolve->pDepthStencilResolveAttachment->attachment != VK_ATTACHMENT_UNUSED) && subpass_ci.pDepthStencilAttachment &&
        (subpass_ci.pDepthStencilAttachment->attachment != VK_ATTACHMENT_UNUSED)) {
        const auto src_at = subpass_ci.pDepthStencilAttachment->attachment;
        const auto src_ci = attachment_ci[src_at];
        // The formats are required to match so we can pick either
        const bool resolve_depth = (ds_resolve->depthResolveMode != VK_RESOLVE_MODE_NONE) && vkuFormatHasDepth(src_ci.format);
        const bool resolve_stencil = (ds_resolve->stencilResolveMode != VK_RESOLVE_MODE_NONE) && vkuFormatHasStencil(src_ci.format);
        const auto dst_at = ds_resolve->pDepthStencilResolveAttachment->attachment;

        // Figure out which aspects are actually touched during resolve operations
        const char* aspect_string = nullptr;
        AttachmentViewGen::Gen gen_type = AttachmentViewGen::Gen::kRenderArea;
        if (resolve_depth && resolve_stencil) {
            aspect_string = "depth/stencil";
        } else if (resolve_depth) {
            // Validate depth only
            gen_type = AttachmentViewGen::Gen::kDepthOnlyRenderArea;
            aspect_string = "depth";
        } else if (resolve_stencil) {
            // Validate all stencil only
            gen_type = AttachmentViewGen::Gen::kStencilOnlyRenderArea;
            aspect_string = "stencil";
        }

        if (aspect_string) {
            attachment_access.ordering = SyncOrdering::kRaster;

            attachment_access.type = AttachmentAccessType::ResolveRead;
            action(aspect_string, "resolve read", src_at, dst_at, attachment_views[src_at], gen_type,
                   SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ, attachment_access);

            attachment_access.type = AttachmentAccessType::ResolveWrite;
            action(aspect_string, "resolve write", src_at, dst_at, attachment_views[dst_at], gen_type,
                   SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);
        }
    }
}

bool RenderPassAccessContext::ValidateResolveOperations(const CommandBufferAccessContext& cb_context, vvl::Func command) const {
    const uint32_t view_mask = rp_state_->create_info.pSubpasses[current_subpass_].viewMask;
    ValidateResolveAction validate_action(rp_state_->VkHandle(), current_subpass_, view_mask, CurrentContext(), cb_context,
                                          command);
    ResolveOperation(validate_action, *rp_state_, attachment_views_, render_pass_instance_id_, current_subpass_);
    return validate_action.GetSkip();
}

void RenderPassAccessContext::UpdateAttachmentResolveAccess(const vvl::RenderPass& rp_state,
                                                            const AttachmentViewGenVector& attachment_views,
                                                            uint32_t render_pass_instance_id, uint32_t subpass, uint32_t view_mask,
                                                            const ResourceUsageTag tag, AccessContext& access_context) {
    UpdateStateResolveAction update(access_context, view_mask, tag);
    ResolveOperation(update, rp_state, attachment_views, render_pass_instance_id, subpass);
}

void RenderPassAccessContext::UpdateAttachmentStoreAccess(const vvl::RenderPass& rp_state,
                                                          const AttachmentViewGenVector& attachment_views,
                                                          uint32_t render_pass_instance_id, uint32_t subpass, uint32_t view_mask,
                                                          const ResourceUsageTag tag, AccessContext& access_context) {
    AttachmentAccess attachment_access;
    attachment_access.type = AttachmentAccessType::StoreOp;
    attachment_access.ordering = SyncOrdering::kRaster;
    attachment_access.render_pass_instance_id = render_pass_instance_id;
    attachment_access.subpass = subpass;

    for (uint32_t i = 0; i < rp_state.create_info.attachmentCount; i++) {
        if (auto filtered_view_mask = FilterViewMask(view_mask, subpass, rp_state.attachment_last_subpass[i])) {
            const auto& view_gen = attachment_views[i];

            const auto& ci = rp_state.create_info.pAttachments[i];
            const bool has_depth = vkuFormatHasDepth(ci.format);
            const bool has_stencil = vkuFormatHasStencil(ci.format);
            const bool is_color = !(has_depth || has_stencil);
            const bool store_op_stores = ci.storeOp != VK_ATTACHMENT_STORE_OP_NONE;

            if (is_color && store_op_stores) {
                access_context.UpdateAttachmentAccessState(view_gen, AttachmentViewGen::Gen::kRenderArea,
                                                           SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access,
                                                           ResourceUsageTagEx{tag}, *filtered_view_mask);
            } else {
                if (has_depth && store_op_stores) {
                    access_context.UpdateAttachmentAccessState(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea,
                                                               SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                               attachment_access, ResourceUsageTagEx{tag}, *filtered_view_mask);
                }
                const bool stencil_op_stores = ci.stencilStoreOp != VK_ATTACHMENT_STORE_OP_NONE;
                if (has_stencil && stencil_op_stores) {
                    access_context.UpdateAttachmentAccessState(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                               SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                               attachment_access, ResourceUsageTagEx{tag}, *filtered_view_mask);
                }
            }
        }
    }
}

void RenderPassAccessContext::RecordLayoutTransitions(const vvl::RenderPass& rp_state, uint32_t subpass,
                                                      const AttachmentViewGenVector& attachment_views, const ResourceUsageTag tag,
                                                      AccessContext& access_context) {
    const auto& transitions = rp_state.subpass_transitions[subpass];
    for (const auto& transition : transitions) {
        const auto& view_gen = attachment_views[transition.attachment];
        const SubpassBarrier& subpass_barrier = access_context.GetSubpassBarrier(transition.src_subpass);
        const AccessContext& src_subpass_context = *subpass_barrier.src_subpass_context;

        // Import the attachments into the current context
        ApplySubpassTransitionBarrierAction barrier_action(subpass_barrier, tag);
        ImageRangeGen attachment_gen = view_gen.GetRangeGen(AttachmentViewGen::Gen::kViewSubresource);
        access_context.ResolveFromSubpassContext(barrier_action, src_subpass_context, attachment_gen);
    }
}

static uint32_t GetSubpassDepthStencilAttachmentIndex(const vku::safe_VkPipelineDepthStencilStateCreateInfo* pipe_ds_ci,
                                                      const vku::safe_VkAttachmentReference2* depth_stencil_ref) {
    return (pipe_ds_ci && depth_stencil_ref) ? depth_stencil_ref->attachment : VK_ATTACHMENT_UNUSED;
}

bool RenderPassAccessContext::ValidateDrawSubpassAttachment(const CommandBufferAccessContext& cb_context, vvl::Func command) const {
    bool skip = false;
    const vvl::CommandBuffer& cmd_buffer = cb_context.GetCBState();
    const auto& last_bound_state = cmd_buffer.GetLastBoundGraphics();
    const auto* pipe = last_bound_state.pipeline_state;

    if (!pipe || pipe->RasterizationDisabled()) {
        return skip;
    }

    const auto& list = pipe->fs_writable_output_location_list;
    const auto& subpass = rp_state_->create_info.pSubpasses[current_subpass_];
    const auto& current_context = CurrentContext();
    const SyncValidator& sync_state = cb_context.GetSyncState();

    auto report_atachment_hazard = [&sync_state, &cb_context, command](const HazardResult& hazard,
                                                                       const vvl::ImageView& attachment_view,
                                                                       std::string_view attachment_description) {
        const vvl::Image& attachment_image = *attachment_view.image_state;
        LogObjectList objlist(cb_context.GetCBState().Handle(), attachment_view.Handle(), attachment_image.Handle());
        const Location loc(command);

        std::ostringstream ss;
        ss << attachment_description;
        ss << " (" << sync_state.FormatHandle(attachment_view.Handle());
        ss << ", " << sync_state.FormatHandle(attachment_image.Handle()) << ")";
        const std::string resource_description = ss.str();

        const std::string error =
            sync_state.error_messages_.RenderPassAttachmentError(hazard, cb_context, command, resource_description);
        return sync_state.SyncError(hazard.Hazard(), objlist, loc, error);
    };

    // Subpass's inputAttachment has been done in ValidateDispatchDrawDescriptorSet
    if (subpass.pColorAttachments && subpass.colorAttachmentCount && !list.empty()) {
        for (const auto location : list) {
            if (location >= subpass.colorAttachmentCount ||
                subpass.pColorAttachments[location].attachment == VK_ATTACHMENT_UNUSED) {
                continue;
            }
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
            const AttachmentViewGen& view_gen = attachment_views_[subpass.pColorAttachments[location].attachment];
            HazardResult hazard = current_context.DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kRenderArea,
                                                                         SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE,
                                                                         attachment_access, subpass.viewMask);
            if (hazard.IsHazard()) {
                std::ostringstream ss;
                ss << "color attachment " << location << " in subpass " << cmd_buffer.GetActiveSubpass();
                const std::string attachment_description = ss.str();
                skip |= report_atachment_hazard(hazard, *view_gen.GetViewState(), attachment_description);
            }
        }
    }

    // PHASE1 TODO: Read operations for both depth and stencil are possible in the future.
    const auto ds_state = pipe->DepthStencilState();
    const uint32_t depth_stencil_attachment = GetSubpassDepthStencilAttachmentIndex(ds_state, subpass.pDepthStencilAttachment);

    if (depth_stencil_attachment != VK_ATTACHMENT_UNUSED) {
        const AttachmentViewGen& view_gen = attachment_views_[depth_stencil_attachment];
        const vvl::ImageView& view_state = *view_gen.GetViewState();
        const VkFormat ds_format = view_state.create_info.format;
        const bool depth_write = IsDepthAttachmentWriteable(last_bound_state, ds_format);
        const bool stencil_write = IsStencilAttachmentWriteable(last_bound_state, ds_format);

        if (depth_write) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
            HazardResult hazard = current_context.DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea,
                                                                         SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                                         attachment_access, subpass.viewMask);
            if (hazard.IsHazard()) {
                std::ostringstream ss;
                ss << "depth aspect of depth-stencil attachment  in subpass " << cmd_buffer.GetActiveSubpass();
                const std::string attachment_description = ss.str();
                skip |= report_atachment_hazard(hazard, view_state, attachment_description);
            }
        }
        if (stencil_write) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
            HazardResult hazard = current_context.DetectAttachmentHazard(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                                         SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                                         attachment_access, subpass.viewMask);
            if (hazard.IsHazard()) {
                std::ostringstream ss;
                ss << "stencil aspect of depth-stencil attachment  in subpass " << cmd_buffer.GetActiveSubpass();
                const std::string attachment_description = ss.str();
                skip |= report_atachment_hazard(hazard, view_state, attachment_description);
            }
        }
    }
    return skip;
}

void RenderPassAccessContext::RecordDrawSubpassAttachment(const vvl::CommandBuffer& cmd_buffer, const ResourceUsageTag tag) {
    const auto& last_bound_state = cmd_buffer.GetLastBoundGraphics();
    const auto* pipe = last_bound_state.pipeline_state;
    if (!pipe || pipe->RasterizationDisabled()) {
        return;
    }

    const auto& list = pipe->fs_writable_output_location_list;
    const auto& subpass = rp_state_->create_info.pSubpasses[current_subpass_];

    auto& current_context = CurrentContext();
    // Subpass's inputAttachment has been done in RecordDispatchDrawDescriptorSet
    if (subpass.pColorAttachments && subpass.colorAttachmentCount && !list.empty()) {
        for (const auto location : list) {
            if (location >= subpass.colorAttachmentCount ||
                subpass.pColorAttachments[location].attachment == VK_ATTACHMENT_UNUSED) {
                continue;
            }
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
            const AttachmentViewGen& view_gen = attachment_views_[subpass.pColorAttachments[location].attachment];
            current_context.UpdateAttachmentAccessState(view_gen, AttachmentViewGen::Gen::kRenderArea,
                                                        SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access,
                                                        ResourceUsageTagEx{tag}, subpass.viewMask);
        }
    }

    // PHASE1 TODO: Read operations for both depth and stencil are possible in the future.
    const auto* ds_state = pipe->DepthStencilState();
    const uint32_t depth_stencil_attachment = GetSubpassDepthStencilAttachmentIndex(ds_state, subpass.pDepthStencilAttachment);
    if (depth_stencil_attachment != VK_ATTACHMENT_UNUSED) {
        const AttachmentViewGen& view_gen = attachment_views_[depth_stencil_attachment];
        const vvl::ImageView& view_state = *view_gen.GetViewState();
        const VkFormat ds_format = view_state.create_info.format;
        const bool depth_write = IsDepthAttachmentWriteable(last_bound_state, ds_format);
        const bool stencil_write = IsStencilAttachmentWriteable(last_bound_state, ds_format);

        if (depth_write || stencil_write) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
            const auto ds_gentype = view_gen.GetDepthStencilRenderAreaGenType(depth_write, stencil_write);
            current_context.UpdateAttachmentAccessState(view_gen, ds_gentype,
                                                        SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access,
                                                        ResourceUsageTagEx{tag}, subpass.viewMask);
        }
    }
}

const vvl::ImageView* RenderPassAccessContext::GetClearAttachmentView(const VkClearAttachment& clear_attachment) const {
    const auto& subpass = rp_state_->create_info.pSubpasses[current_subpass_];
    uint32_t attachment_index = VK_ATTACHMENT_UNUSED;
    if (clear_attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
        if (clear_attachment.colorAttachment < subpass.colorAttachmentCount) {
            attachment_index = subpass.pColorAttachments[clear_attachment.colorAttachment].attachment;
        }
    } else if (clear_attachment.aspectMask & kDepthStencilAspects) {
        if (subpass.pDepthStencilAttachment) {
            attachment_index = subpass.pDepthStencilAttachment->attachment;
        }
    }
    // This catches both out of bounds attachment index and VK_ATTACHMENT_UNUSED special value.
    if (attachment_index >= rp_state_->create_info.attachmentCount) {
        return nullptr;
    }
    return attachment_views_[attachment_index].GetViewState();
}

bool RenderPassAccessContext::ValidateNextSubpass(const CommandBufferAccessContext& cb_context, vvl::Func command) const {
    // PHASE1 TODO: Add Validate Preserve attachments
    bool skip = false;
    skip |= ValidateResolveOperations(cb_context, command);
    skip |= ValidateStoreOperation(cb_context, command);

    const auto next_subpass = current_subpass_ + 1;
    if (next_subpass >= rp_state_->create_info.subpassCount) {
        return skip;
    }
    const uint32_t next_subpass_view_mask = rp_state_->create_info.pSubpasses[next_subpass].viewMask;
    const auto& next_context = subpass_contexts_[next_subpass];
    skip |= ValidateLayoutTransitions(cb_context, next_context, *rp_state_, render_pass_instance_id_, next_subpass,
                                      next_subpass_view_mask, attachment_views_, command);
    if (!skip) {
        // To avoid complex (and buggy) duplication of the affect of layout transitions on load operations, we'll record them
        // on a copy of the (empty) next context.
        // Note: The resource access map should be empty so hopefully this copy isn't too horrible from a perf POV.
        AccessContext temp_context(cb_context.GetSyncState());
        temp_context.InitFrom(next_context);
        RecordLayoutTransitions(*rp_state_, next_subpass, attachment_views_, kInvalidTag, temp_context);
        skip |= ValidateLoadOperation(cb_context, temp_context, *rp_state_, render_pass_instance_id_, next_subpass,
                                      next_subpass_view_mask, attachment_views_, command);
    }
    return skip;
}
bool RenderPassAccessContext::ValidateEndRenderPass(const CommandBufferAccessContext& cb_context, vvl::Func command) const {
    // PHASE1 TODO: Validate Preserve
    bool skip = false;
    skip |= ValidateResolveOperations(cb_context, command);
    skip |= ValidateStoreOperation(cb_context, command);
    skip |= ValidateFinalSubpassLayoutTransitions(cb_context, command);
    return skip;
}

AccessContext* RenderPassAccessContext::CreateStoreResolveProxy() const {
    return CreateStoreResolveProxyContext(CurrentContext(), *rp_state_, render_pass_instance_id_, current_subpass_,
                                          rp_state_->create_info.pSubpasses[current_subpass_].viewMask, attachment_views_);
}

bool RenderPassAccessContext::ValidateFinalSubpassLayoutTransitions(const CommandBufferAccessContext& cb_context,
                                                                    vvl::Func command) const {
    bool skip = false;

    // As validation methods are const and precede the record/update phase, for any tranistions from the current (last)
    // subpass, we have to validate them against a copy of the current AccessContext, with resolve operations applied.
    // Note: we could be more efficient by tracking whether or not we actually *have* any changes (e.g. attachment resolve)
    // to apply and only copy then, if this proves a hot spot.
    std::unique_ptr<AccessContext> proxy_for_current;

    const AccessContext* context = nullptr;

    // Validate the "finalLayout" transitions to external
    // Get them from where there we're hidding in the extra entry.
    const auto& final_transitions = rp_state_->subpass_transitions.back();
    for (const auto& transition : final_transitions) {
        SyncBarrier merged_barrier;
        const auto& view_gen = attachment_views_[transition.attachment];
        if (transition.src_subpass != VK_SUBPASS_EXTERNAL) {
            const SubpassBarrier& subpass_barrier = subpass_contexts_[transition.src_subpass].GetDstExternalSubpassBarrier();
            merged_barrier = SyncBarrier(subpass_barrier.barriers);
            if (transition.src_subpass != current_subpass_) {
                context = subpass_barrier.src_subpass_context;
            } else {
                if (!proxy_for_current) {
                    // We haven't recorded resolve ofor the current_subpass, so we need to copy current and update it *as if*
                    proxy_for_current.reset(CreateStoreResolveProxy());
                }
                context = proxy_for_current.get();
            }
        } else {
            context = external_context_;
            // NOTE: Unused attachments still transition from initialLayout to finalLayout, but no
            // subpass uses them, so there is no external subpass dependency barrier to merge.
        }

        // Use the merged barrier for the hazard check (safe since it just considers the src (first) scope.
        auto hazard = context->DetectImageBarrierHazard(view_gen, merged_barrier, AccessContext::DetectOptions::kDetectPrevious);
        if (hazard.IsHazard()) {
            const SyncValidator& sync_state = cb_context.GetSyncState();
            const Location loc(command);

            std::ostringstream ss;
            ss << "on attachment " << transition.attachment << " (";
            ss << sync_state.FormatHandle(view_gen.GetViewState()->Handle());
            ss << ", " << sync_state.FormatHandle(view_gen.GetViewState()->image_state->Handle());
            ss << ", oldLayout " << string_VkImageLayout(transition.old_layout);
            ss << ", newLayout " << string_VkImageLayout(transition.new_layout);
            ss << ")";
            const std::string resource_description = ss.str();

            if (hazard.Tag() == kInvalidTag) {  // Hazard vs. store/resolve
                const std::string error = sync_state.error_messages_.RenderPassFinalLayoutTransitionVsStoreOrResolveError(
                    hazard, cb_context, command, resource_description, transition.old_layout, transition.new_layout,
                    transition.src_subpass);
                skip |= sync_state.SyncError(hazard.Hazard(), rp_state_->Handle(), loc, error);
            } else {
                const std::string error = sync_state.error_messages_.RenderPassFinalLayoutTransitionError(
                    hazard, cb_context, command, resource_description, transition.old_layout, transition.new_layout);
                skip |= sync_state.SyncError(hazard.Hazard(), rp_state_->Handle(), loc, error);
            }
        }
    }
    return skip;
}

void RenderPassAccessContext::RecordLayoutTransitions(const ResourceUsageTag tag) {
    // Add layout transitions...
    RecordLayoutTransitions(*rp_state_, current_subpass_, attachment_views_, tag, CurrentContext());
}

void RenderPassAccessContext::RecordLoadOperations(const ResourceUsageTag tag) {
    const auto* attachment_ci = rp_state_->create_info.pAttachments;
    auto& subpass_context = CurrentContext();
    const uint32_t view_mask = rp_state_->create_info.pSubpasses[current_subpass_].viewMask;

    for (uint32_t i = 0; i < rp_state_->create_info.attachmentCount; i++) {
        if (auto filtered_view_mask = FilterViewMask(view_mask, current_subpass_, rp_state_->attachment_first_subpass[i])) {
            const AttachmentViewGen& view_gen = attachment_views_[i];

            const VkAttachmentDescription2& ci = *attachment_ci[i].ptr();
            const bool has_depth = vkuFormatHasDepth(ci.format);
            const bool has_stencil = vkuFormatHasStencil(ci.format);
            const bool is_color = !(has_depth || has_stencil);

            if (is_color) {
                // TODO: LoadOp can access the entire attachment subresource, not only the render area.
                // The exception is when a feedback loop is enabled for the attachment, then only the render area is accessed.
                const SyncAccessIndex load_op_access = ColorLoadUsage(ci.loadOp);
                if (load_op_access != SYNC_ACCESS_INDEX_NONE) {
                    const AttachmentAccess attachment_access =
                        GetAttachmentAccess(SyncOrdering::kColorAttachment, AttachmentAccessType::LoadOp);
                    subpass_context.UpdateAttachmentAccessState(view_gen, AttachmentViewGen::Gen::kRenderArea, load_op_access,
                                                                attachment_access, ResourceUsageTagEx{tag}, *filtered_view_mask);
                }
            } else {
                // TODO: Update depth/stencil aspects separately only if separateDepthStencilAttachmentAccess is defined,
                // otherwise both should be updated.
                // Also LoadOp can access the entire attachment subresource, not only the render area.
                // The exception is when a feedback loop is enabled for the attachment, then only the render area is accessed.
                const AttachmentAccess attachment_access =
                    GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment, AttachmentAccessType::LoadOp);
                if (has_depth) {
                    const SyncAccessIndex load_op = DepthStencilLoadUsage(ci.loadOp);
                    if (load_op != SYNC_ACCESS_INDEX_NONE) {
                        subpass_context.UpdateAttachmentAccessState(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea, load_op,
                                                                    attachment_access, ResourceUsageTagEx{tag},
                                                                    *filtered_view_mask);
                    }
                }
                if (has_stencil) {
                    const SyncAccessIndex load_op = DepthStencilLoadUsage(ci.stencilLoadOp);
                    if (load_op != SYNC_ACCESS_INDEX_NONE) {
                        subpass_context.UpdateAttachmentAccessState(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                                    load_op, attachment_access, ResourceUsageTagEx{tag},
                                                                    *filtered_view_mask);
                    }
                }
            }
        }
    }
}

AttachmentViewGenVector RenderPassAccessContext::CreateAttachmentViewGen(
    const VkRect2D& render_area, const std::vector<std::shared_ptr<const vvl::ImageView>>& attachment_views) {
    AttachmentViewGenVector view_gens;
    VkExtent3D extent = CastTo3D(render_area.extent);
    VkOffset3D offset = CastTo3D(render_area.offset);
    view_gens.reserve(attachment_views.size());
    for (const auto& view : attachment_views) {
        view_gens.emplace_back(view.get(), offset, extent);
    }
    return view_gens;
}

RenderPassAccessContext::RenderPassAccessContext(const vvl::RenderPass& rp_state, const VkRect2D& render_area,
                                                 VkQueueFlags queue_flags,
                                                 const std::vector<std::shared_ptr<const vvl::ImageView>>& attachment_views,
                                                 const AccessContext& external_context, uint32_t render_pass_instance_id)
    : rp_state_(&rp_state),
      attachment_views_(CreateAttachmentViewGen(render_area, attachment_views)),
      external_context_(&external_context),
      subpass_contexts_(InitSubpassContexts(queue_flags, rp_state, external_context)),
      render_pass_instance_id_(render_pass_instance_id),
      current_subpass_(0) {}

void RenderPassAccessContext::RecordBeginRenderPass(const ResourceUsageTag barrier_tag, const ResourceUsageTag load_tag) {
    assert(0 == current_subpass_);
    AccessContext& current_context = CurrentContext();
    current_context.SetStartTag(barrier_tag);

    RecordLayoutTransitions(barrier_tag);
    RecordLoadOperations(load_tag);
}

void RenderPassAccessContext::RecordNextSubpass(ResourceUsageTag resolve_tag, const ResourceUsageTag store_tag,
                                                const ResourceUsageTag transition_tag, const ResourceUsageTag load_tag) {
    const uint32_t view_mask = rp_state_->create_info.pSubpasses[current_subpass_].viewMask;

    // Resolves are against *prior* subpass context and thus *before* the subpass increment
    UpdateAttachmentResolveAccess(*rp_state_, attachment_views_, render_pass_instance_id_, current_subpass_, view_mask, resolve_tag,
                                  CurrentContext());
    UpdateAttachmentStoreAccess(*rp_state_, attachment_views_, render_pass_instance_id_, current_subpass_, view_mask, store_tag,
                                CurrentContext());

    if (current_subpass_ + 1 >= rp_state_->create_info.subpassCount) {
        return;
    }
    // Move to the next sub-command for the new subpass. The resolve and store are logically part of the previous
    // subpass, so their tag needs to be different from the layout and load operations below.
    current_subpass_++;
    AccessContext& current_context = CurrentContext();
    current_context.SetStartTag(transition_tag);

    RecordLayoutTransitions(transition_tag);
    RecordLoadOperations(load_tag);
}

void RenderPassAccessContext::RecordEndRenderPass(AccessContext* external_context, const ResourceUsageTag store_tag,
                                                  const ResourceUsageTag transition_tag) {
    const uint32_t view_mask = rp_state_->create_info.pSubpasses[current_subpass_].viewMask;

    // Add the resolve and store accesses
    UpdateAttachmentResolveAccess(*rp_state_, attachment_views_, render_pass_instance_id_, current_subpass_, view_mask, store_tag,
                                  CurrentContext());
    UpdateAttachmentStoreAccess(*rp_state_, attachment_views_, render_pass_instance_id_, current_subpass_, view_mask, store_tag,
                                CurrentContext());

    // Export the accesses from the renderpass...
    external_context->ResolveChildContexts(GetSubpassContexts());

    // Add the "finalLayout" transitions to external
    // Get them from where there we're hidding in the extra entry.
    // Not that since *final* always comes from *one* subpass per view, we don't have to accumulate the barriers
    // TODO Aliasing we may need to reconsider barrier accumulation... though I don't know that it would be valid for aliasing
    //      that had mulitple final layout transistions from mulitple final subpasses.
    const auto& final_transitions = rp_state_->subpass_transitions.back();
    for (const auto& transition : final_transitions) {
        const AttachmentViewGen& view_gen = attachment_views_[transition.attachment];
        ImageRangeGen range_gen = view_gen.GetRangeGen(AttachmentViewGen::Gen::kViewSubresource);

        ImageRangeGen markup_range_gen = range_gen;  // second copy, preserve range_gen to use later
        ApplyMarkupFunctor markup_action(true);
        external_context->UpdateMemoryAccessState(markup_action, markup_range_gen);

        PendingBarriers pending_barriers;
        if (transition.src_subpass != VK_SUBPASS_EXTERNAL) {
            const SubpassBarrier& dst_external_barrier = subpass_contexts_[transition.src_subpass].GetDstExternalSubpassBarrier();
            assert(&subpass_contexts_[transition.src_subpass] == dst_external_barrier.src_subpass_context);
            for (const auto& barrier : dst_external_barrier.barriers) {
                const BarrierScope barrier_scope(barrier);
                CollectBarriersFunctor collect_barriers(*external_context, barrier_scope, barrier, true, vvl::kNoIndex32,
                                                        pending_barriers);
                external_context->UpdateMemoryAccessState(collect_barriers, range_gen);
            }
        } else {
            // Unused attachments still transition from initialLayout to finalLayout, but no subpass
            // uses them, so record the transition without applying a subpass dependency barrier.
            const SyncBarrier empty_barrier;
            CollectBarriersFunctor collect_barriers(*external_context, BarrierScope(empty_barrier), empty_barrier, true,
                                                    vvl::kNoIndex32, pending_barriers);
            external_context->UpdateMemoryAccessState(collect_barriers, range_gen);
        }
        pending_barriers.Apply(transition_tag);
    }
}

AccessContext& RenderPassAccessContext::CurrentContext() {
    assert(current_subpass_ < rp_state_->create_info.subpassCount);
    return subpass_contexts_[current_subpass_];
}

const AccessContext& RenderPassAccessContext::CurrentContext() const {
    assert(current_subpass_ < rp_state_->create_info.subpassCount);
    return subpass_contexts_[current_subpass_];
}

vvl::span<const AccessContext> RenderPassAccessContext::GetSubpassContexts() const {
    return vvl::make_span<const AccessContext>(subpass_contexts_.get(), rp_state_->create_info.subpassCount);
}

vvl::span<AccessContext> RenderPassAccessContext::GetSubpassContexts() {
    return vvl::make_span<AccessContext>(subpass_contexts_.get(), rp_state_->create_info.subpassCount);
}

AttachmentAccess RenderPassAccessContext::GetAttachmentAccess(SyncOrdering ordering, AttachmentAccessType type) const {
    AttachmentAccess attachment_access;
    attachment_access.type = type;
    attachment_access.ordering = ordering;
    attachment_access.render_pass_instance_id = render_pass_instance_id_;
    attachment_access.subpass = current_subpass_;
    return attachment_access;
}

void BeginRenderingCmdState::AddRenderingInfo(const SyncValidator& state, const VkRenderingInfo& rendering_info) {
    info = std::make_unique<DynamicRenderingInfo>(state, rendering_info);
}

const DynamicRenderingInfo& BeginRenderingCmdState::GetRenderingInfo() const {
    assert(info);
    return *info;
}
DynamicRenderingInfo::DynamicRenderingInfo(const SyncValidator& state, const VkRenderingInfo& rendering_info)
    : info(&rendering_info) {
    uint32_t attachment_count = info.colorAttachmentCount + (info.pDepthAttachment ? 1 : 0) + (info.pStencilAttachment ? 1 : 0);

    const VkOffset3D offset = CastTo3D(info.renderArea.offset);
    const VkExtent3D extent = CastTo3D(info.renderArea.extent);

    attachments.reserve(attachment_count);
    for (uint32_t i = 0; i < info.colorAttachmentCount; i++) {
        attachments.emplace_back(state, info.pColorAttachments[i], AttachmentType::kColor, offset, extent);
    }

    if (info.pDepthAttachment) {
        attachments.emplace_back(state, *info.pDepthAttachment, AttachmentType::kDepth, offset, extent);
    }

    if (info.pStencilAttachment) {
        attachments.emplace_back(state, *info.pStencilAttachment, AttachmentType::kStencil, offset, extent);
    }
}

const vvl::ImageView* DynamicRenderingInfo::GetClearAttachmentView(const VkClearAttachment& clear_attachment) const {
    const vvl::ImageView* attachment_view = nullptr;
    if (clear_attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
        if (clear_attachment.colorAttachment < info.colorAttachmentCount) {
            attachment_view = attachments[clear_attachment.colorAttachment].view.get();
        }
    } else if (clear_attachment.aspectMask & kDepthStencilAspects) {
        if (attachments.size() > info.colorAttachmentCount) {
            // If both depth and stencil attachments are defined they must both point to the same view
            attachment_view = attachments.back().view.get();
        }
    }
    return attachment_view;
}

DynamicRenderingInfo::Attachment::Attachment(const SyncValidator& state, const vku::safe_VkRenderingAttachmentInfo& attachment_info,
                                             AttachmentType type_, const VkOffset3D& offset, const VkExtent3D& extent)
    : info(attachment_info), view(state.Get<vvl::ImageView>(attachment_info.imageView)), view_gen(), type(type_) {
    if (view) {
        if (type == AttachmentType::kColor) {
            view_gen = MakeImageRangeGen(*view, offset, extent);
        } else if (type == AttachmentType::kDepth) {
            view_gen = MakeImageRangeGen(*view, offset, extent, VK_IMAGE_ASPECT_DEPTH_BIT);
        } else {
            view_gen = MakeImageRangeGen(*view, offset, extent, VK_IMAGE_ASPECT_STENCIL_BIT);
        }

        if (info.resolveImageView != VK_NULL_HANDLE && (info.resolveMode != VK_RESOLVE_MODE_NONE)) {
            resolve_view = state.Get<vvl::ImageView>(info.resolveImageView);
            if (resolve_view) {
                if (type == AttachmentType::kColor) {
                    resolve_gen.emplace(MakeImageRangeGen(*resolve_view, offset, extent));
                } else if (type == AttachmentType::kDepth) {
                    // Only the depth aspect
                    resolve_gen.emplace(MakeImageRangeGen(*resolve_view, offset, extent, VK_IMAGE_ASPECT_DEPTH_BIT));
                } else {
                    resolve_gen.emplace(MakeImageRangeGen(*resolve_view, offset, extent, VK_IMAGE_ASPECT_STENCIL_BIT));
                }
            }
        }
    }
}

ImageRangeGen DynamicRenderingInfo::Attachment::GetRangeGen(uint32_t view_mask) const {
    // VkRenderingAttachmentInfo::imageView is allowed to be VK_NULL_HANDLE
    if (!view) {
        return {};
    }
    // Multiview is disabled: return precomputed range gen
    if (view_mask == 0) {
        return view_gen;
    }
    // Initialize range gen based on view mask
    if (type == AttachmentType::kColor) {
        return MakeImageRangeGen(*view, view_mask);
    } else if (type == AttachmentType::kDepth) {
        return MakeImageRangeGen(*view, view_mask, VK_IMAGE_ASPECT_DEPTH_BIT);
    } else {
        return MakeImageRangeGen(*view, view_mask, VK_IMAGE_ASPECT_STENCIL_BIT);
    }
}

SyncAccessIndex DynamicRenderingInfo::Attachment::GetLoadUsage() const { return GetLoadOpUsageIndex(info.loadOp, type); }

SyncAccessIndex DynamicRenderingInfo::Attachment::GetStoreUsage() const { return GetStoreOpUsageIndex(info.storeOp, type); }

SyncOrdering DynamicRenderingInfo::Attachment::GetOrdering() const {
    return (type == AttachmentType::kColor) ? SyncOrdering::kColorAttachment : SyncOrdering::kDepthStencilAttachment;
}

Location DynamicRenderingInfo::Attachment::GetLocation(const Location& loc, uint32_t attachment_index) const {
    if (type == AttachmentType::kColor) {
        return loc.dot(vvl::Struct::VkRenderingAttachmentInfo, vvl::Field::pColorAttachments, attachment_index);
    } else if (type == AttachmentType::kDepth) {
        return loc.dot(vvl::Struct::VkRenderingAttachmentInfo, vvl::Field::pDepthAttachment);
    } else {
        assert(type == AttachmentType::kStencil);
        return loc.dot(vvl::Struct::VkRenderingAttachmentInfo, vvl::Field::pStencilAttachment);
    }
}

bool DynamicRenderingInfo::Attachment::IsWriteable(const LastBound& last_bound_state) const {
    bool writeable = IsValid();
    if (writeable) {
        //  Depth and Stencil have additional criteria
        if (type == AttachmentType::kDepth) {
            writeable =
                last_bound_state.IsDepthWriteEnable() && IsDepthAttachmentWriteable(last_bound_state, view->create_info.format);
        } else if (type == AttachmentType::kStencil) {
            writeable =
                last_bound_state.IsStencilTestEnable() && IsStencilAttachmentWriteable(last_bound_state, view->create_info.format);
        }
    }
    return writeable;
}

}  // namespace syncval
