/* Copyright (c) 2019-2023 The Khronos Group Inc.
 * Copyright (c) 2019-2023 Valve Corporation
 * Copyright (c) 2019-2023 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 "sync/sync_renderpass.h"
#include "sync/sync_validation.h"
#include "sync/sync_op.h"

// Action for validating resolve operations
class ValidateResolveAction {
  public:
    ValidateResolveAction(VkRenderPass render_pass, uint32_t subpass, const AccessContext &context,
                          const SyncValidationInfo &val_info, vvl::Func command)
        : render_pass_(render_pass), subpass_(subpass), context_(context), val_info_(val_info), command_(command), skip_(false) {}
    void operator()(const char *aspect_name, const char *attachment_name, uint32_t src_at, uint32_t dst_at,
                    const AttachmentViewGen &view_gen, AttachmentViewGen::Gen gen_type, SyncStageAccessIndex current_usage,
                    SyncOrdering ordering_rule) {
        HazardResult hazard;
        hazard = context_.DetectHazard(view_gen, gen_type, current_usage, ordering_rule);
        if (hazard.IsHazard()) {
            const Location loc(command_);
            skip_ |= val_info_.GetSyncState().LogError(string_SyncHazardVUID(hazard.Hazard()), render_pass_, loc,
                                                       "Hazard %s in subpass %" PRIu32 "during %s %s, from attachment %" PRIu32
                                                       " to resolve attachment %" PRIu32 ". Access info %s.",
                                                       string_SyncHazard(hazard.Hazard()), subpass_, aspect_name, attachment_name,
                                                       src_at, dst_at, val_info_.FormatHazard(hazard).c_str());
        }
    }
    // 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 AccessContext &context_;
    const SyncValidationInfo &val_info_;
    vvl::Func command_;
    bool skip_;
};

// Update action for resolve operations
class UpdateStateResolveAction {
  public:
    UpdateStateResolveAction(AccessContext &context, ResourceUsageTag tag) : context_(context), tag_(tag) {}
    void operator()(const char *, const char *, uint32_t, uint32_t, const AttachmentViewGen &view_gen,
                    AttachmentViewGen::Gen gen_type, SyncStageAccessIndex current_usage, SyncOrdering ordering_rule) {
        // Ignores validation only arguments...
        context_.UpdateAccessState(view_gen, gen_type, current_usage, ordering_rule, tag_);
    }

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

void InitSubpassContexts(VkQueueFlags queue_flags, const vvl::RenderPass &rp_state, const AccessContext *external_context,
                         std::vector<AccessContext> &subpass_contexts) {
    const auto &create_info = rp_state.createInfo;
    // Add this for all subpasses here so that they exsist during next subpass validation
    subpass_contexts.clear();
    subpass_contexts.reserve(create_info.subpassCount);
    for (uint32_t pass = 0; pass < create_info.subpassCount; pass++) {
        subpass_contexts.emplace_back(pass, queue_flags, rp_state.subpass_dependencies, subpass_contexts, external_context);
    }
}

static SyncStageAccessIndex GetLoadOpUsageIndex(VkAttachmentLoadOp load_op, syncval_state::AttachmentType type) {
    SyncStageAccessIndex usage_index;
    if (load_op == VK_ATTACHMENT_LOAD_OP_NONE_EXT) {
        usage_index = SYNC_ACCESS_INDEX_NONE;
    } else if (type == syncval_state::AttachmentType::kColor) {
        usage_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
        usage_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 usage_index;
}

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

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

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

// Layout transitions are handled as if the were occuring in the beginning of the next subpass
bool RenderPassAccessContext::ValidateLayoutTransitions(const SyncValidationInfo &val_info, const AccessContext &access_context,
                                                        const vvl::RenderPass &rp_state, const VkRect2D &render_area,
                                                        uint32_t subpass, 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> proxy_for_prev;
    AccessContext::TrackBack proxy_track_back;

    const auto &transitions = rp_state.subpass_transitions[subpass];
    for (const auto &transition : transitions) {
        const bool prev_needs_proxy = transition.prev_pass != VK_SUBPASS_EXTERNAL && (transition.prev_pass + 1 == subpass);

        const auto *track_back = access_context.GetTrackBackFromSubpass(transition.prev_pass);
        assert(track_back);
        if (prev_needs_proxy) {
            if (!proxy_for_prev) {
                proxy_for_prev.reset(
                    CreateStoreResolveProxyContext(*track_back->source_subpass, rp_state, transition.prev_pass, attachment_views));
                proxy_track_back = *track_back;
                proxy_track_back.source_subpass = proxy_for_prev.get();
            }
            track_back = &proxy_track_back;
        }
        auto hazard = access_context.DetectSubpassTransitionHazard(*track_back, attachment_views[transition.attachment]);
        if (hazard.IsHazard()) {
            const Location loc(command);
            if (hazard.Tag() == kInvalidTag) {
                skip |= val_info.GetSyncState().LogError(
                    string_SyncHazardVUID(hazard.Hazard()), rp_state.renderPass(), loc,
                    "Hazard %s in subpass %" PRIu32 " for attachment %" PRIu32
                    " image layout transition (old_layout: %s, new_layout: %s) after store/resolve operation in subpass %" PRIu32,
                    string_SyncHazard(hazard.Hazard()), subpass, transition.attachment, string_VkImageLayout(transition.old_layout),
                    string_VkImageLayout(transition.new_layout), transition.prev_pass);
            } else {
                skip |= val_info.GetSyncState().LogError(
                    string_SyncHazardVUID(hazard.Hazard()), rp_state.renderPass(), loc,
                    "Hazard %s in subpass %" PRIu32 " for attachment %" PRIu32
                    " image layout transition (old_layout: %s, new_layout: %s). Access info %s.",
                    string_SyncHazard(hazard.Hazard()), subpass, transition.attachment, string_VkImageLayout(transition.old_layout),
                    string_VkImageLayout(transition.new_layout), val_info.FormatHazard(hazard).c_str());
            }
        }
    }
    return skip;
}

bool RenderPassAccessContext::ValidateLoadOperation(const SyncValidationInfo &val_info, const AccessContext &access_context,
                                                    const vvl::RenderPass &rp_state, const VkRect2D &render_area, uint32_t subpass,
                                                    const AttachmentViewGenVector &attachment_views, vvl::Func command) {
    bool skip = false;
    const auto *attachment_ci = rp_state.createInfo.pAttachments;

    for (uint32_t i = 0; i < rp_state.createInfo.attachmentCount; i++) {
        if (subpass == rp_state.attachment_first_subpass[i]) {
            const auto &view_gen = attachment_views[i];
            if (!view_gen.IsValid()) continue;
            const auto &ci = attachment_ci[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 SyncStageAccessIndex load_index = has_depth ? DepthStencilLoadUsage(ci.loadOp) : ColorLoadUsage(ci.loadOp);
            const SyncStageAccessIndex 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)) {
                hazard = access_context.DetectHazard(view_gen, AttachmentViewGen::Gen::kRenderArea, load_index,
                                                     SyncOrdering::kColorAttachment);
                aspect = "color";
            } else {
                if (has_depth && (load_index != SYNC_ACCESS_INDEX_NONE)) {
                    hazard = access_context.DetectHazard(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea, load_index,
                                                         SyncOrdering::kDepthStencilAttachment);
                    aspect = "depth";
                }
                if (!hazard.IsHazard() && has_stencil && (stencil_load_index != SYNC_ACCESS_INDEX_NONE)) {
                    hazard = access_context.DetectHazard(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                         stencil_load_index, SyncOrdering::kDepthStencilAttachment);
                    aspect = "stencil";
                    checked_stencil = true;
                }
            }

            if (hazard.IsHazard()) {
                auto load_op_string = string_VkAttachmentLoadOp(checked_stencil ? ci.stencilLoadOp : ci.loadOp);
                const auto &sync_state = val_info.GetSyncState();
                const Location loc(command);
                if (hazard.Tag() == kInvalidTag) {
                    // Hazard vs. ILT
                    skip |= sync_state.LogError(string_SyncHazardVUID(hazard.Hazard()), rp_state.renderPass(), loc,
                                                "Hazard %s vs. layout transition in subpass %" PRIu32 " for attachment %" PRIu32
                                                " aspect %s during load with loadOp %s.",
                                                string_SyncHazard(hazard.Hazard()), subpass, i, aspect, load_op_string);
                } else {
                    skip |= sync_state.LogError(string_SyncHazardVUID(hazard.Hazard()), rp_state.renderPass(), loc,
                                                "Hazard %s in subpass %" PRIu32 " for attachment %" PRIu32
                                                " aspect %s during load with loadOp %s. Access info %s.",
                                                string_SyncHazard(hazard.Hazard()), subpass, i, aspect, load_op_string,
                                                val_info.FormatHazard(hazard).c_str());
                }
            }
        }
    }
    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 SyncValidationInfo &val_info, vvl::Func command) const {
    bool skip = false;
    const auto *attachment_ci = rp_state_->createInfo.pAttachments;

    for (uint32_t i = 0; i < rp_state_->createInfo.attachmentCount; i++) {
        if (current_subpass_ == rp_state_->attachment_last_subpass[i]) {
            const AttachmentViewGen &view_gen = attachment_views_[i];
            if (!view_gen.IsValid()) continue;
            const auto &ci = attachment_ci[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_EXT;
            if (!has_stencil && !store_op_stores) continue;

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

            if (hazard.IsHazard()) {
                const char *const op_type_string = checked_stencil ? "stencilStoreOp" : "storeOp";
                const char *const store_op_string = string_VkAttachmentStoreOp(checked_stencil ? ci.stencilStoreOp : ci.storeOp);
                const Location loc(command);
                skip |= val_info.GetSyncState().LogError(string_SyncHazardVUID(hazard.Hazard()), rp_state_->renderPass(), loc,
                                                         "Hazard %s in subpass %" PRIu32 " for attachment %" PRIu32
                                                         " %s aspect during store with %s %s. Access info %s",
                                                         string_SyncHazard(hazard.Hazard()), current_subpass_, i, aspect,
                                                         op_type_string, store_op_string, val_info.FormatHazard(hazard).c_str());
            }
        }
    }
    return skip;
}

bool IsImageLayoutDepthWritable(VkImageLayout image_layout) {
    return (image_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ||
            image_layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL ||
            image_layout == VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
}

bool IsImageLayoutStencilWritable(VkImageLayout image_layout) {
    return (image_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ||
            image_layout == VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL ||
            image_layout == VK_IMAGE_LAYOUT_STENCIL_ATTACHMENT_OPTIMAL);
}

bool IsDepthAttachmentWriteable(const LastBound &last_bound_state, const VkFormat format, const VkImageLayout layout) {
    // PHASE1 TODO: These validation should be in core_checks.
    const bool depth_write_enable = last_bound_state.IsDepthWriteEnable();  // implicitly means DepthTestEnable is set
    return !vkuFormatIsStencilOnly(format) && depth_write_enable && IsImageLayoutDepthWritable(layout);
}

bool IsStencilAttachmentWriteable(const LastBound &last_bound_state, const VkFormat format, const VkImageLayout layout) {
    // PHASE1 TODO: It needs to check if stencil is writable.
    //              If failOp, passOp, or depthFailOp are not KEEP, and writeMask isn't 0, it's writable.
    //              If depth test is disable, it's considered depth test passes, and then depthFailOp doesn't run.
    // PHASE1 TODO: These validation should be in core_checks.
    const bool stencil_test_enable = last_bound_state.IsStencilTestEnable();
    return !vkuFormatIsDepthOnly(format) && stencil_test_enable && IsImageLayoutStencilWritable(layout);
}

// 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 subpass) {
    const auto &rp_ci = rp_state.createInfo;
    const auto *attachment_ci = rp_ci.pAttachments;
    const auto &subpass_ci = rp_ci.pSubpasses[subpass];

    // Color resolves -- require an inuse color attachment and a matching inuse resolve attachment
    const auto *color_attachments = subpass_ci.pColorAttachments;
    const auto *color_resolve = subpass_ci.pResolveAttachments;
    if (color_resolve && color_attachments) {
        for (uint32_t i = 0; i < subpass_ci.colorAttachmentCount; i++) {
            const auto &color_attach = color_attachments[i].attachment;
            const auto &resolve_attach = subpass_ci.pResolveAttachments[i].attachment;
            if ((color_attach != VK_ATTACHMENT_UNUSED) && (resolve_attach != VK_ATTACHMENT_UNUSED)) {
                action("color", "resolve read", color_attach, resolve_attach, attachment_views[color_attach],
                       AttachmentViewGen::Gen::kRenderArea, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ,
                       SyncOrdering::kColorAttachment);
                action("color", "resolve write", color_attach, resolve_attach, attachment_views[resolve_attach],
                       AttachmentViewGen::Gen::kRenderArea, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE,
                       SyncOrdering::kColorAttachment);
            }
        }
    }

    // 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) {
            action(aspect_string, "resolve read", src_at, dst_at, attachment_views[src_at], gen_type,
                   SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ, SyncOrdering::kRaster);
            action(aspect_string, "resolve write", src_at, dst_at, attachment_views[dst_at], gen_type,
                   SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, SyncOrdering::kRaster);
        }
    }
}

bool RenderPassAccessContext::ValidateResolveOperations(const SyncValidationInfo &val_info, vvl::Func command) const {
    ValidateResolveAction validate_action(rp_state_->renderPass(), current_subpass_, CurrentContext(), val_info, command);
    ResolveOperation(validate_action, *rp_state_, attachment_views_, current_subpass_);
    return validate_action.GetSkip();
}

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

void RenderPassAccessContext::UpdateAttachmentStoreAccess(const vvl::RenderPass &rp_state,
                                                          const AttachmentViewGenVector &attachment_views, uint32_t subpass,
                                                          const ResourceUsageTag tag, AccessContext &access_context) {
    const auto *attachment_ci = rp_state.createInfo.pAttachments;

    for (uint32_t i = 0; i < rp_state.createInfo.attachmentCount; i++) {
        if (rp_state.attachment_last_subpass[i] == subpass) {
            const auto &view_gen = attachment_views[i];
            if (!view_gen.IsValid()) continue;  // UNUSED

            const auto &ci = attachment_ci[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_EXT;

            if (is_color && store_op_stores) {
                access_context.UpdateAccessState(view_gen, AttachmentViewGen::Gen::kRenderArea,
                                                 SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, SyncOrdering::kRaster, tag);
            } else {
                if (has_depth && store_op_stores) {
                    access_context.UpdateAccessState(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea,
                                                     SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, SyncOrdering::kRaster,
                                                     tag);
                }
                const bool stencil_op_stores = ci.stencilStoreOp != VK_ATTACHMENT_STORE_OP_NONE_EXT;
                if (has_stencil && stencil_op_stores) {
                    access_context.UpdateAccessState(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                     SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, SyncOrdering::kRaster,
                                                     tag);
                }
            }
        }
    }
}

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];
    const ResourceAccessState empty_infill;
    for (const auto &transition : transitions) {
        const auto prev_pass = transition.prev_pass;
        const auto &view_gen = attachment_views[transition.attachment];
        if (!view_gen.IsValid()) continue;

        const auto *trackback = access_context.GetTrackBackFromSubpass(prev_pass);
        assert(trackback);

        // Import the attachments into the current context
        const auto *prev_context = trackback->source_subpass;
        assert(prev_context);
        ApplySubpassTransitionBarriersAction barrier_action(trackback->barriers);
        const std::optional<ImageRangeGen> &attachment_gen = view_gen.GetRangeGen(AttachmentViewGen::Gen::kViewSubresource);
        assert(attachment_gen);

        access_context.ResolveFromContext(barrier_action, *prev_context, *attachment_gen, &empty_infill,
                                          true /* recur to infill */);
        assert(attachment_gen);
    }

    // If there were no transitions skip this global map walk
    if (transitions.size()) {
        ResolvePendingBarrierFunctor apply_pending_action(tag);
        access_context.ApplyToContext(apply_pending_action);
    }
}

bool RenderPassAccessContext::ValidateDrawSubpassAttachment(const CommandExecutionContext &exec_context,
                                                            const vvl::CommandBuffer &cmd_buffer, vvl::Func command) const {
    bool skip = false;
    const auto &sync_state = exec_context.GetSyncState();
    const auto lv_bind_point = ConvertToLvlBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS);
    const auto &last_bound_state = cmd_buffer.lastBound[lv_bind_point];
    const auto *pipe = last_bound_state.pipeline_state;
    if (!pipe) {
        return skip;
    }

    const auto raster_state = pipe->RasterizationState();
    if (raster_state && raster_state->rasterizerDiscardEnable) {
        return skip;
    }
    const auto &list = pipe->fragmentShader_writable_output_location_list;
    const auto &subpass = rp_state_->createInfo.pSubpasses[current_subpass_];

    const auto &current_context = CurrentContext();
    // 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 AttachmentViewGen &view_gen = attachment_views_[subpass.pColorAttachments[location].attachment];
            if (!view_gen.IsValid()) continue;
            HazardResult hazard =
                current_context.DetectHazard(view_gen, AttachmentViewGen::Gen::kRenderArea,
                                             SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, SyncOrdering::kColorAttachment);
            if (hazard.IsHazard()) {
                const VkImageView view_handle = view_gen.GetViewState()->image_view();
                const Location loc(command);
                skip |= sync_state.LogError(string_SyncHazardVUID(hazard.Hazard()), view_handle, loc,
                                            "Hazard %s for %s in %s, Subpass #%d, and pColorAttachments #%d. Access info %s.",
                                            string_SyncHazard(hazard.Hazard()), sync_state.FormatHandle(view_handle).c_str(),
                                            sync_state.FormatHandle(cmd_buffer).c_str(), cmd_buffer.GetActiveSubpass(), location,
                                            exec_context.FormatHazard(hazard).c_str());
            }
        }
    }

    // PHASE1 TODO: Add layout based read/vs. write selection.
    // 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) && attachment_views_[depth_stencil_attachment].IsValid()) {
        const AttachmentViewGen &view_gen = attachment_views_[depth_stencil_attachment];
        const vvl::ImageView &view_state = *view_gen.GetViewState();
        const VkImageLayout ds_layout = subpass.pDepthStencilAttachment->layout;
        const VkFormat ds_format = view_state.create_info.format;
        const bool depth_write = IsDepthAttachmentWriteable(last_bound_state, ds_format, ds_layout);
        const bool stencil_write = IsStencilAttachmentWriteable(last_bound_state, ds_format, ds_layout);

        // PHASE1 TODO: Add EARLY stage detection based on ExecutionMode.
        // PHASE1 TODO: It needs to check if stencil is writable.
        //              If failOp, passOp, or depthFailOp are not KEEP, and writeMask isn't 0, it's writable.
        //              If depth test is disable, it's considered depth test passes, and then depthFailOp doesn't run.
        // const bool early_fragment_test = pipe->fragment_shader_state->early_fragment_test;
        if (depth_write) {
            HazardResult hazard = current_context.DetectHazard(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea,
                                                               SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                               SyncOrdering::kDepthStencilAttachment);
            if (hazard.IsHazard()) {
                const Location loc(command);
                skip |= sync_state.LogError(
                    string_SyncHazardVUID(hazard.Hazard()), view_state.image_view(), loc,
                    "Hazard %s for %s in %s, Subpass #%d, and depth part of pDepthStencilAttachment. Access info %s.",
                    string_SyncHazard(hazard.Hazard()), sync_state.FormatHandle(view_state).c_str(),
                    sync_state.FormatHandle(cmd_buffer).c_str(), cmd_buffer.GetActiveSubpass(),
                    exec_context.FormatHazard(hazard).c_str());
            }
        }
        if (stencil_write) {
            HazardResult hazard = current_context.DetectHazard(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea,
                                                               SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                               SyncOrdering::kDepthStencilAttachment);
            if (hazard.IsHazard()) {
                const Location loc(command);
                skip |= sync_state.LogError(
                    string_SyncHazardVUID(hazard.Hazard()), view_state.image_view(), loc,
                    "Hazard %s for %s in %s, Subpass #%d, and stencil part of pDepthStencilAttachment. Access info %s.",
                    string_SyncHazard(hazard.Hazard()), sync_state.FormatHandle(view_state).c_str(),
                    sync_state.FormatHandle(cmd_buffer).c_str(), cmd_buffer.GetActiveSubpass(),
                    exec_context.FormatHazard(hazard).c_str());
            }
        }
    }
    return skip;
}

void RenderPassAccessContext::RecordDrawSubpassAttachment(const vvl::CommandBuffer &cmd_buffer, const ResourceUsageTag tag) {
    const auto lv_bind_point = ConvertToLvlBindPoint(VK_PIPELINE_BIND_POINT_GRAPHICS);
    const auto &last_bound_state = cmd_buffer.lastBound[lv_bind_point];
    const auto *pipe = last_bound_state.pipeline_state;
    if (!pipe) {
        return;
    }

    const auto *raster_state = pipe->RasterizationState();
    if (raster_state && raster_state->rasterizerDiscardEnable) {
        return;
    }
    const auto &list = pipe->fragmentShader_writable_output_location_list;
    const auto &subpass = rp_state_->createInfo.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 AttachmentViewGen &view_gen = attachment_views_[subpass.pColorAttachments[location].attachment];
            current_context.UpdateAccessState(view_gen, AttachmentViewGen::Gen::kRenderArea,
                                              SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, SyncOrdering::kColorAttachment,
                                              tag);
        }
    }

    // PHASE1 TODO: Add layout based read/vs. write selection.
    // 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) && attachment_views_[depth_stencil_attachment].IsValid()) {
        const AttachmentViewGen &view_gen = attachment_views_[depth_stencil_attachment];
        const vvl::ImageView &view_state = *view_gen.GetViewState();
        bool depth_write = false, stencil_write = false;
        const bool has_depth = 0 != (view_state.normalized_subresource_range.aspectMask & VK_IMAGE_ASPECT_DEPTH_BIT);
        const bool has_stencil = 0 != (view_state.normalized_subresource_range.aspectMask & VK_IMAGE_ASPECT_STENCIL_BIT);

        const bool depth_write_enable = last_bound_state.IsDepthWriteEnable();  // implicitly means DepthTestEnable is set
        const bool stencil_test_enable = last_bound_state.IsStencilTestEnable();

        // PHASE1 TODO: These validation should be in core_checks.
        if (has_depth && !vkuFormatIsStencilOnly(view_state.create_info.format) && depth_write_enable &&
            IsImageLayoutDepthWritable(subpass.pDepthStencilAttachment->layout)) {
            depth_write = true;
        }
        // PHASE1 TODO: It needs to check if stencil is writable.
        //              If failOp, passOp, or depthFailOp are not KEEP, and writeMask isn't 0, it's writable.
        //              If depth test is disable, it's considered depth test passes, and then depthFailOp doesn't run.
        // PHASE1 TODO: These validation should be in core_checks.
        if (has_stencil && !vkuFormatIsDepthOnly(view_state.create_info.format) && stencil_test_enable &&
            IsImageLayoutStencilWritable(subpass.pDepthStencilAttachment->layout)) {
            stencil_write = true;
        }

        if (depth_write || stencil_write) {
            const auto ds_gentype = view_gen.GetDepthStencilRenderAreaGenType(depth_write, stencil_write);
            // PHASE1 TODO: Add EARLY stage detection based on ExecutionMode.
            current_context.UpdateAccessState(view_gen, ds_gentype, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                              SyncOrdering::kDepthStencilAttachment, tag);
        }
    }
}

uint32_t RenderPassAccessContext::GetAttachmentIndex(const VkClearAttachment &clear_attachment) const {
    const auto &rpci = rp_state_->createInfo;
    const auto &subpass = rpci.pSubpasses[GetCurrentSubpass()];
    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;
        }
    }
    // As _UNUSED is UINT32_MAX (~0U) this catches all "no attachment" cases -- unknown aspectMask, UNUSED, and out of bounds
    if (attachment_index >= rpci.attachmentCount) {
        attachment_index = VK_ATTACHMENT_UNUSED;
    }
    return attachment_index;
}

VkImageAspectFlags ClearAttachmentInfo::GetAspectsToClear(VkImageAspectFlags clear_aspect_mask, const ImageViewState &view) {
    // Check if clear request is valid.
    const VkImageAspectFlags view_aspect_mask = view.normalized_subresource_range.aspectMask;
    const bool clear_color = (clear_aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
    const bool clear_depth = (clear_aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
    const bool clear_stencil = (clear_aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0;
    if (!clear_color && !clear_depth && !clear_stencil) {
        return 0;  // nothing to clear
    }
    if (clear_color && (clear_depth || clear_stencil)) {
        return 0;  // according to spec it's not allowed
    }

    // Collect aspects that should be cleared.
    VkImageAspectFlags aspects_to_clear = VK_IMAGE_ASPECT_NONE;
    if (clear_color && (view_aspect_mask & kColorAspects) != 0) {
        assert(GetBitSetCount(view_aspect_mask) == 1);
        aspects_to_clear |= view_aspect_mask;
    }
    if (clear_depth && (view_aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0) {
        aspects_to_clear |= VK_IMAGE_ASPECT_DEPTH_BIT;
    }
    if (clear_stencil && (view_aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0) {
        aspects_to_clear |= VK_IMAGE_ASPECT_STENCIL_BIT;
    }
    return aspects_to_clear;
}

ClearAttachmentInfo::ClearAttachmentInfo(const VkClearAttachment &clear_attachment, const VkClearRect &rect,
                                         const ImageViewState &view_, uint32_t attachment_index_, uint32_t subpass_)
    : view(&view_),
      aspects_to_clear(GetAspectsToClear(clear_attachment.aspectMask, view_)),
      subresource_range(RestrictSubresourceRange(rect, view_)),
      offset(CastTo3D(rect.rect.offset)),
      extent(CastTo3D(rect.rect.extent)),
      attachment_index(attachment_index_),
      subpass(subpass_) {}

std::string ClearAttachmentInfo::GetSubpassAttachmentText() const {
    if (attachment_index == VK_ATTACHMENT_UNUSED) return std::string();
    std::stringstream text;
    text << " render pass attachment index " << attachment_index << " in subpass " << subpass;
    return text.str();
}

VkImageSubresourceRange ClearAttachmentInfo::RestrictSubresourceRange(const VkClearRect &clear_rect, const ImageViewState &view) {
    const VkImageSubresourceRange &normalized_subresource_range = view.normalized_subresource_range;

    assert(normalized_subresource_range.layerCount != VK_REMAINING_ARRAY_LAYERS);  // contract of this function
    assert(clear_rect.layerCount != VK_REMAINING_ARRAY_LAYERS);                    // according to spec
    const uint32_t first = std::max(normalized_subresource_range.baseArrayLayer, clear_rect.baseArrayLayer);
    const uint32_t last_range = normalized_subresource_range.baseArrayLayer + normalized_subresource_range.layerCount;
    const uint32_t last_clear = clear_rect.baseArrayLayer + clear_rect.layerCount;
    const uint32_t last = std::min(last_range, last_clear);
    // We use an invalid range instead of optional to indicate an invalid restricted range for a clear operation.
    VkImageSubresourceRange result = {0, 0, 0, 0, 0};
    if (first < last) {
        result = normalized_subresource_range;
        result.baseArrayLayer = first;
        result.layerCount = last - first;
    }
    return result;
}

ClearAttachmentInfo RenderPassAccessContext::GetClearAttachmentInfo(const VkClearAttachment &clear_attachment,
                                                                    const VkClearRect &rect) const {
    const uint32_t attachment_index = GetAttachmentIndex(clear_attachment);
    if (attachment_index == VK_ATTACHMENT_UNUSED) {
        return ClearAttachmentInfo();
    }
    const syncval_state::ImageViewState *view_state = attachment_views_[attachment_index].GetViewState();
    if (!view_state) {
        return ClearAttachmentInfo();
    }

    return ClearAttachmentInfo(clear_attachment, rect, *view_state, attachment_index, GetCurrentSubpass());
}

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

    const auto next_subpass = current_subpass_ + 1;
    if (next_subpass >= subpass_contexts_.size()) {
        return skip;
    }
    const auto &next_context = subpass_contexts_[next_subpass];
    skip |=
        ValidateLayoutTransitions(exec_context, next_context, *rp_state_, render_area_, next_subpass, 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(next_context);
        RecordLayoutTransitions(*rp_state_, next_subpass, attachment_views_, kInvalidTag, temp_context);
        skip |=
            ValidateLoadOperation(exec_context, temp_context, *rp_state_, render_area_, next_subpass, attachment_views_, command);
    }
    return skip;
}
bool RenderPassAccessContext::ValidateEndRenderPass(const CommandExecutionContext &exec_context, vvl::Func command) const {
    // PHASE1 TODO: Validate Preserve
    bool skip = false;
    skip |= ValidateResolveOperations(exec_context, command);
    skip |= ValidateStoreOperation(exec_context, command);
    skip |= ValidateFinalSubpassLayoutTransitions(exec_context, command);
    return skip;
}

AccessContext *RenderPassAccessContext::CreateStoreResolveProxy() const {
    return CreateStoreResolveProxyContext(CurrentContext(), *rp_state_, current_subpass_, attachment_views_);
}

bool RenderPassAccessContext::ValidateFinalSubpassLayoutTransitions(const CommandExecutionContext &exec_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;

    // 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) {
        const auto &view_gen = attachment_views_[transition.attachment];
        const auto &trackback = subpass_contexts_[transition.prev_pass].GetDstExternalTrackBack();
        assert(trackback.source_subpass);  // Transitions are given implicit transitions if the StateTracker is working correctly
        auto *context = trackback.source_subpass;

        if (transition.prev_pass == current_subpass_) {
            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();
        }

        // Use the merged barrier for the hazard check (safe since it just considers the src (first) scope.
        const SyncBarrier merged_barrier(trackback.barriers);
        auto hazard = context->DetectImageBarrierHazard(view_gen, merged_barrier, AccessContext::DetectOptions::kDetectPrevious);
        if (hazard.IsHazard()) {
            const Location loc(command);
            if (hazard.Tag() == kInvalidTag) {
                // Hazard vs. ILT
                skip |= exec_context.GetSyncState().LogError(
                    string_SyncHazardVUID(hazard.Hazard()), rp_state_->renderPass(), loc,
                    "Hazard %s vs. store/resolve operations in subpass %" PRIu32 " for attachment %" PRIu32
                    " final image layout transition (old_layout: %s, new_layout: %s).",
                    string_SyncHazard(hazard.Hazard()), transition.prev_pass, transition.attachment,
                    string_VkImageLayout(transition.old_layout), string_VkImageLayout(transition.new_layout));
            } else {
                skip |= exec_context.GetSyncState().LogError(
                    string_SyncHazardVUID(hazard.Hazard()), rp_state_->renderPass(), loc,
                    "Hazard %s with last use subpass %" PRIu32 " for attachment %" PRIu32
                    " final image layout transition (old_layout: %s, new_layout: %s). Access info %s.",
                    string_SyncHazard(hazard.Hazard()), transition.prev_pass, transition.attachment,
                    string_VkImageLayout(transition.old_layout), string_VkImageLayout(transition.new_layout),
                    exec_context.FormatHazard(hazard).c_str());
            }
        }
    }
    return skip;
}

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

void RenderPassAccessContext::RecordLoadOperations(const ResourceUsageTag tag) {
    const auto *attachment_ci = rp_state_->createInfo.pAttachments;
    auto &subpass_context = subpass_contexts_[current_subpass_];

    for (uint32_t i = 0; i < rp_state_->createInfo.attachmentCount; i++) {
        if (rp_state_->attachment_first_subpass[i] == current_subpass_) {
            const AttachmentViewGen &view_gen = attachment_views_[i];
            if (!view_gen.IsValid()) continue;  // UNUSED

            const auto &ci = attachment_ci[i];
            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) {
                const SyncStageAccessIndex load_op = ColorLoadUsage(ci.loadOp);
                if (load_op != SYNC_ACCESS_INDEX_NONE) {
                    subpass_context.UpdateAccessState(view_gen, AttachmentViewGen::Gen::kRenderArea, load_op,
                                                      SyncOrdering::kColorAttachment, tag);
                }
            } else {
                if (has_depth) {
                    const SyncStageAccessIndex load_op = DepthStencilLoadUsage(ci.loadOp);
                    if (load_op != SYNC_ACCESS_INDEX_NONE) {
                        subpass_context.UpdateAccessState(view_gen, AttachmentViewGen::Gen::kDepthOnlyRenderArea, load_op,
                                                          SyncOrdering::kDepthStencilAttachment, tag);
                    }
                }
                if (has_stencil) {
                    const SyncStageAccessIndex load_op = DepthStencilLoadUsage(ci.stencilLoadOp);
                    if (load_op != SYNC_ACCESS_INDEX_NONE) {
                        subpass_context.UpdateAccessState(view_gen, AttachmentViewGen::Gen::kStencilOnlyRenderArea, load_op,
                                                          SyncOrdering::kDepthStencilAttachment, tag);
                    }
                }
            }
        }
    }
}
AttachmentViewGenVector RenderPassAccessContext::CreateAttachmentViewGen(
    const VkRect2D &render_area, const std::vector<const syncval_state::ImageViewState *> &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, offset, extent);
    }
    return view_gens;
}
RenderPassAccessContext::RenderPassAccessContext(const vvl::RenderPass &rp_state, const VkRect2D &render_area,
                                                 VkQueueFlags queue_flags,
                                                 const std::vector<const syncval_state::ImageViewState *> &attachment_views,
                                                 const AccessContext *external_context)
    : rp_state_(&rp_state), render_area_(render_area), current_subpass_(0U), attachment_views_() {
    // Add this for all subpasses here so that they exist during next subpass validation
    InitSubpassContexts(queue_flags, rp_state, external_context, subpass_contexts_);
    attachment_views_ = CreateAttachmentViewGen(render_area, attachment_views);
}
void RenderPassAccessContext::RecordBeginRenderPass(const ResourceUsageTag barrier_tag, const ResourceUsageTag load_tag) {
    assert(0 == current_subpass_);
    AccessContext &current_context = subpass_contexts_[current_subpass_];
    current_context.SetStartTag(barrier_tag);

    RecordLayoutTransitions(barrier_tag);
    RecordLoadOperations(load_tag);
}

void RenderPassAccessContext::RecordNextSubpass(const ResourceUsageTag store_tag, const ResourceUsageTag barrier_tag,
                                                const ResourceUsageTag load_tag) {
    // Resolves are against *prior* subpass context and thus *before* the subpass increment
    UpdateAttachmentResolveAccess(*rp_state_, attachment_views_, current_subpass_, store_tag, CurrentContext());
    UpdateAttachmentStoreAccess(*rp_state_, attachment_views_, current_subpass_, store_tag, CurrentContext());

    if (current_subpass_ + 1 >= subpass_contexts_.size()) {
        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 = subpass_contexts_[current_subpass_];
    current_context.SetStartTag(barrier_tag);

    RecordLayoutTransitions(barrier_tag);
    RecordLoadOperations(load_tag);
}

void RenderPassAccessContext::RecordEndRenderPass(AccessContext *external_context, const ResourceUsageTag store_tag,
                                                  const ResourceUsageTag barrier_tag) {
    // Add the resolve and store accesses
    UpdateAttachmentResolveAccess(*rp_state_, attachment_views_, current_subpass_, store_tag, CurrentContext());
    UpdateAttachmentStoreAccess(*rp_state_, attachment_views_, current_subpass_, store_tag, CurrentContext());

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

    // 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];
        const auto &last_trackback = subpass_contexts_[transition.prev_pass].GetDstExternalTrackBack();
        assert(&subpass_contexts_[transition.prev_pass] == last_trackback.source_subpass);
        ApplyBarrierOpsFunctor<PipelineBarrierOp> barrier_action(true /* resolve */, last_trackback.barriers.size(), barrier_tag);
        for (const auto &barrier : last_trackback.barriers) {
            barrier_action.EmplaceBack(PipelineBarrierOp(kQueueIdInvalid, barrier, true));
        }
        external_context->ApplyUpdateAction(view_gen, AttachmentViewGen::Gen::kViewSubresource, barrier_action);
    }
}

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

const syncval_state::DynamicRenderingInfo &syncval_state::BeginRenderingCmdState::GetRenderingInfo() const {
    assert(info);
    return *info;
}
syncval_state::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], syncval_state::AttachmentType::kColor, offset, extent);
    }

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

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

ClearAttachmentInfo syncval_state::DynamicRenderingInfo::GetClearAttachmentInfo(const VkClearAttachment &clear_attachment,
                                                                                const VkClearRect &rect) const {
    const syncval_state::ImageViewState *view = nullptr;
    ClearAttachmentInfo clear_info;
    if (clear_attachment.aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) {
        if (clear_attachment.colorAttachment < info.colorAttachmentCount) {
            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 the must both point to the same view
            view = attachments.back().view.get();
        }
    }

    if (view) {
        clear_info = ClearAttachmentInfo(clear_attachment, rect, *view);
    }

    return clear_info;
}

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

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

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

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

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

Location syncval_state::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 syncval_state::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, info.imageLayout);
        } else if (type == AttachmentType::kStencil) {
            writeable = last_bound_state.IsStencilTestEnable() &&
                        IsStencilAttachmentWriteable(last_bound_state, view->create_info.format, info.imageLayout);
        }
    }
    return writeable;
}
