/*
 * 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_command_buffer.h"
#include "error_message/error_location.h"
#include "sync/sync_image.h"
#include "sync/sync_op.h"
#include "sync/sync_reporting.h"
#include "sync/sync_validation.h"
#include "state_tracker/descriptor_sets.h"
#include "state_tracker/image_state.h"
#include "state_tracker/buffer_state.h"
#include "state_tracker/event_state.h"
#include "state_tracker/ray_tracing_state.h"
#include "state_tracker/render_pass_state.h"
#include "state_tracker/shader_module.h"
#include "state_tracker/pipeline_state.h"
#include "utils/image_utils.h"
#include "utils/math_utils.h"
#include "utils/text_utils.h"

namespace syncval {

constexpr VkImageAspectFlags kColorAspects =
    VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_PLANE_0_BIT | VK_IMAGE_ASPECT_PLANE_1_BIT | VK_IMAGE_ASPECT_PLANE_2_BIT;

constexpr SyncAccessIndex kResolveRead = SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_READ;
constexpr SyncAccessIndex kResolveWrite = SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE;
constexpr SyncOrdering kColorResolveOrder = SyncOrdering::kColorAttachment;

// Although depth resolve runs on the color attachment output stage and uses color accesses, depth accesses
// still participate in the ordering. That's why using raster and not only color attachment ordering
constexpr SyncOrdering kDepthStencilResolveOrder = SyncOrdering::kRaster;

constexpr SyncOrdering kStoreOrder = SyncOrdering::kRaster;

struct ShaderStageAccesses {
    SyncAccessIndex sampled_read;
    SyncAccessIndex storage_read;
    SyncAccessIndex storage_write;
    SyncAccessIndex uniform_read;
    SyncAccessIndex acceleration_structure_read;
};

// TODO: generate me
static ShaderStageAccesses GetShaderStageAccesses(VkShaderStageFlagBits shader_stage) {
    static const vvl::unordered_map<VkShaderStageFlagBits, ShaderStageAccesses> map = {
        // clang-format off
        {VK_SHADER_STAGE_VERTEX_BIT, {
            SYNC_VERTEX_SHADER_SHADER_SAMPLED_READ,
            SYNC_VERTEX_SHADER_SHADER_STORAGE_READ,
            SYNC_VERTEX_SHADER_SHADER_STORAGE_WRITE,
            SYNC_VERTEX_SHADER_UNIFORM_READ,
            SYNC_VERTEX_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, {
            SYNC_TESSELLATION_CONTROL_SHADER_SHADER_SAMPLED_READ,
            SYNC_TESSELLATION_CONTROL_SHADER_SHADER_STORAGE_READ,
            SYNC_TESSELLATION_CONTROL_SHADER_SHADER_STORAGE_WRITE,
            SYNC_TESSELLATION_CONTROL_SHADER_UNIFORM_READ,
            SYNC_TESSELLATION_CONTROL_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, {
            SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_SAMPLED_READ,
            SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_STORAGE_READ,
            SYNC_TESSELLATION_EVALUATION_SHADER_SHADER_STORAGE_WRITE,
            SYNC_TESSELLATION_EVALUATION_SHADER_UNIFORM_READ,
            SYNC_TESSELLATION_EVALUATION_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_GEOMETRY_BIT, {
            SYNC_GEOMETRY_SHADER_SHADER_SAMPLED_READ,
            SYNC_GEOMETRY_SHADER_SHADER_STORAGE_READ,
            SYNC_GEOMETRY_SHADER_SHADER_STORAGE_WRITE,
            SYNC_GEOMETRY_SHADER_UNIFORM_READ,
            SYNC_GEOMETRY_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_FRAGMENT_BIT, {
            SYNC_FRAGMENT_SHADER_SHADER_SAMPLED_READ,
            SYNC_FRAGMENT_SHADER_SHADER_STORAGE_READ,
            SYNC_FRAGMENT_SHADER_SHADER_STORAGE_WRITE,
            SYNC_FRAGMENT_SHADER_UNIFORM_READ,
            SYNC_FRAGMENT_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_COMPUTE_BIT, {
            SYNC_COMPUTE_SHADER_SHADER_SAMPLED_READ,
            SYNC_COMPUTE_SHADER_SHADER_STORAGE_READ,
            SYNC_COMPUTE_SHADER_SHADER_STORAGE_WRITE,
            SYNC_COMPUTE_SHADER_UNIFORM_READ,
            SYNC_COMPUTE_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_RAYGEN_BIT_KHR, {
            SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
            SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
            SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_ANY_HIT_BIT_KHR, {
            SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
            SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
            SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, {
            SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
            SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
            SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_MISS_BIT_KHR, {
            SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
            SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
            SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_INTERSECTION_BIT_KHR, {
            SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
            SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
            SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_CALLABLE_BIT_KHR, {
            SYNC_RAY_TRACING_SHADER_SHADER_SAMPLED_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_READ,
            SYNC_RAY_TRACING_SHADER_SHADER_STORAGE_WRITE,
            SYNC_RAY_TRACING_SHADER_UNIFORM_READ,
            SYNC_RAY_TRACING_SHADER_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_TASK_BIT_EXT, {
            SYNC_TASK_SHADER_EXT_SHADER_SAMPLED_READ,
            SYNC_TASK_SHADER_EXT_SHADER_STORAGE_READ,
            SYNC_TASK_SHADER_EXT_SHADER_STORAGE_WRITE,
            SYNC_TASK_SHADER_EXT_UNIFORM_READ,
            SYNC_TASK_SHADER_EXT_ACCELERATION_STRUCTURE_READ,
        }},
        {VK_SHADER_STAGE_MESH_BIT_EXT, {
            SYNC_MESH_SHADER_EXT_SHADER_SAMPLED_READ,
            SYNC_MESH_SHADER_EXT_SHADER_STORAGE_READ,
            SYNC_MESH_SHADER_EXT_SHADER_STORAGE_WRITE,
            SYNC_MESH_SHADER_EXT_UNIFORM_READ,
            SYNC_MESH_SHADER_EXT_ACCELERATION_STRUCTURE_READ,
        }},
        // clang-format on
    };
    auto it = map.find(shader_stage);
    assert(it != map.end());
    return it->second;
}

static AccessRange MakeRangeForVertexData(VkDeviceSize offset, uint32_t first_vertex, uint32_t vertex_count,
                                          const VertexBindingState& vertex_binding) {
    uint32_t element_size = 0;
    for (const auto& [_, vertex_attrib] : vertex_binding.locations) {
        element_size = std::max(element_size, vertex_attrib.desc.offset + GetVertexInputFormatSize(vertex_attrib.desc.format));
    }
    const VkDeviceSize range_start = offset + (first_vertex * vertex_binding.desc.stride);
    VkDeviceSize range_size = 0;
    if (vertex_count > 0) {
        // Take into account stride between elements but not after the last element.
        range_size = (vertex_count - 1) * vertex_binding.desc.stride + element_size;
    }
    return MakeRange(range_start, range_size);
}

static AccessRange MakeRangeForIndexData(VkDeviceSize offset, uint32_t first_index, uint32_t index_count, uint32_t index_size) {
    const VkDeviceSize range_start = offset + (first_index * index_size);
    const VkDeviceSize range_size = index_count * index_size;
    return MakeRange(range_start, range_size);
}

static AccessRange MakeRange(const vvl::BufferView& buf_view_state) {
    return MakeRange(*buf_view_state.buffer_state.get(), buf_view_state.create_info.offset, buf_view_state.create_info.range);
}

static SyncAccessIndex GetSyncStageAccessIndexsByDescriptorSet(VkDescriptorType descriptor_type,
                                                               const spirv::ResourceInterfaceVariable& variable,
                                                               VkShaderStageFlagBits stage_flag) {
    if (!variable.IsAccessed()) {
        return SYNC_ACCESS_INDEX_NONE;
    }
    if (descriptor_type == VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT) {
        assert(stage_flag == VK_SHADER_STAGE_FRAGMENT_BIT);
        return SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ;
    }
    const auto stage_accesses = GetShaderStageAccesses(stage_flag);

    if (descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER || descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC) {
        return stage_accesses.uniform_read;
    }
    if (descriptor_type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR ||
        descriptor_type == VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_NV ||
        descriptor_type == VK_DESCRIPTOR_TYPE_PARTITIONED_ACCELERATION_STRUCTURE_NV) {
        return stage_accesses.acceleration_structure_read;
    }

    // If the desriptorSet is writable, we don't need to care SHADER_READ. SHADER_WRITE is enough.
    // Because if write hazard happens, read hazard might or might not happen.
    // But if write hazard doesn't happen, read hazard is impossible to happen.
    if (variable.IsWrittenTo()) {
        return stage_accesses.storage_write;
    } else if (descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE ||
               descriptor_type == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ||
               descriptor_type == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER) {
        return stage_accesses.sampled_read;
    } else {
        if (variable.IsImage() && !variable.IsImageReadFrom()) {
            // only image descriptor was accessed, not the image data
            return SYNC_ACCESS_INDEX_NONE;
        }
        return stage_accesses.storage_read;
    }
}

static void UpdateImageAccessState(AccessContext& access_context, const vvl::Image& image, SyncAccessIndex current_usage,
                                   const VkImageSubresourceRange& subresource_range, const ResourceUsageTag& tag) {
    const auto& sub_state = SubState(image);
    ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, false);
    access_context.UpdateAccessState(range_gen, current_usage, ResourceUsageTagEx{tag});
}

static void UpdateImageAccessState(AccessContext& access_context, const vvl::Image& image, SyncAccessIndex current_usage,
                                   const VkImageSubresourceRange& subresource_range, const VkOffset3D& offset,
                                   const VkExtent3D& extent, ResourceUsageTagEx tag_ex) {
    const auto& sub_state = SubState(image);
    ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, offset, extent, false);
    access_context.UpdateAccessState(range_gen, current_usage, tag_ex);
}

static void UpdateVideoAccessState(AccessContext& access_context, const vvl::VideoSession& vs_state,
                                   const vvl::VideoPictureResource& resource, SyncAccessIndex current_usage, ResourceUsageTag tag) {
    const auto image = static_cast<const vvl::Image*>(resource.image_state.get());
    const auto offset = resource.GetEffectiveImageOffset(vs_state);
    const auto extent = resource.GetEffectiveImageExtent(vs_state);
    const auto& sub_state = SubState(*image);
    ImageRangeGen range_gen(sub_state.MakeImageRangeGen(resource.range, offset, extent, false));
    access_context.UpdateAccessState(range_gen, current_usage, ResourceUsageTagEx{tag});
}

CommandExecutionContext::CommandExecutionContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags)
    : sync_state_(sync_validator), error_messages_(sync_validator.error_messages_), queue_flags_(queue_flags) {}

CommandBufferAccessContext::CommandBufferAccessContext(const SyncValidator& sync_validator, VkQueueFlags queue_flags)
    : CommandExecutionContext(sync_validator, queue_flags),
      cb_state_(),
      access_log_(std::make_shared<AccessLog>()),
      cbs_referenced_(std::make_shared<CommandBufferSet>()),
      command_number_(0),
      reset_count_(0),
      cb_access_context_(sync_validator),
      current_context_(&cb_access_context_),
      events_context_(),
      render_pass_contexts_(),
      current_renderpass_context_(),
      sync_ops_() {}

CommandBufferAccessContext::CommandBufferAccessContext(SyncValidator& sync_validator, vvl::CommandBuffer* cb_state)
    : CommandBufferAccessContext(sync_validator, cb_state->GetQueueFlags()) {
    cb_state_ = cb_state;
    sync_state_.stats.AddCommandBufferContext();
}

// NOTE: Make sure the proxy doesn't outlive from, as the proxy is pointing directly to access contexts owned by from.
CommandBufferAccessContext::CommandBufferAccessContext(const CommandBufferAccessContext& from, AsProxyContext dummy)
    : CommandBufferAccessContext(from.sync_state_, from.cb_state_->GetQueueFlags()) {
    // Copy only the needed fields out of from for a temporary, proxy command buffer context
    cb_state_ = from.cb_state_;
    access_log_ = std::make_shared<AccessLog>(*from.access_log_);  // potentially large, but no choice given tagging lookup.
    command_number_ = from.command_number_;
    reset_count_ = from.reset_count_;

    handles_ = from.handles_;
    sync_state_.stats.AddHandleRecord((uint32_t)from.handles_.size());

    const AccessContext& from_context = from.GetCurrentAccessContext();

    // Construct a fully resolved single access context out of from
    cb_access_context_.ResolveFromContext(from_context);
    // The proxy has flatten the current render pass context (if any), but the async contexts are needed for hazard detection
    cb_access_context_.ImportAsyncContexts(from_context);

    events_context_ = from.events_context_;

    // We don't want to copy the full render_pass_context_ history just for the proxy.
    sync_state_.stats.AddCommandBufferContext();
}

CommandBufferAccessContext::~CommandBufferAccessContext() {
    sync_state_.stats.RemoveCommandBufferContext();
    sync_state_.stats.RemoveHandleRecord((uint32_t)handles_.size());
}

void CommandBufferAccessContext::Reset() {
    access_log_ = std::make_shared<AccessLog>();
    cbs_referenced_ = std::make_shared<CommandBufferSet>();
    if (cb_state_) {
        cbs_referenced_->push_back(cb_state_->shared_from_this());
    }
    sync_ops_.clear();
    command_number_ = 0;
    reset_count_++;

    sync_state_.stats.RemoveHandleRecord((uint32_t)handles_.size());
    handles_.clear();

    current_command_tag_ = vvl::kNoIndex32;
    cb_access_context_.Reset();
    render_pass_contexts_.clear();
    current_context_ = &cb_access_context_;
    current_renderpass_context_ = nullptr;
    events_context_.Clear();
    dynamic_rendering_info_.reset();
}

bool CommandBufferAccessContext::ValidateBeginRendering(const ErrorObject& error_obj, BeginRenderingCmdState& cmd_state) const {
    bool skip = false;
    const DynamicRenderingInfo& info = cmd_state.GetRenderingInfo();

    // Load operations do not happen when resuming
    if (info.info.flags & VK_RENDERING_RESUMING_BIT) {
        return skip;
    }

    // Need to hazard detect load operations vs. the attachment views
    for (size_t i = 0; i < info.attachments.size(); i++) {
        const auto& attachment = info.attachments[i];
        const SyncAccessIndex load_index = attachment.GetLoadUsage();
        if (load_index == SYNC_ACCESS_INDEX_NONE) {
            continue;
        }

        const AttachmentAccess attachment_access = GetAttachmentAccess(attachment.GetOrdering(), AttachmentAccessType::LoadOp);
        ImageRangeGen range_gen = attachment.GetRangeGen(info.info.viewMask);
        const HazardResult hazard = GetCurrentAccessContext().DetectAttachmentHazard(range_gen, load_index, attachment_access);
        if (hazard.IsHazard()) {
            LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());

            std::ostringstream ss;
            ss << vvl::String(vvl::Field::pRenderingInfo) << ".";
            ss << attachment.GetLocation(error_obj.location, uint32_t(i)).Fields();
            ss << " (" << sync_state_.FormatHandle(attachment.view->Handle());
            ss << ", loadOp " << string_VkAttachmentLoadOp(attachment.info.loadOp) << ")";
            std::string resource_description = ss.str();

            const std::string error = sync_state_.error_messages_.BeginRenderingError(hazard, *this, error_obj.location.function,
                                                                                      resource_description, attachment.info.loadOp);
            skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
            if (skip) {
                break;
            }
        }
    }
    return skip;
}

void CommandBufferAccessContext::RecordBeginRendering(BeginRenderingCmdState& cmd_state, const Location& loc) {
    const auto tag = NextCommandTag(loc.function);

    const DynamicRenderingInfo& info = cmd_state.GetRenderingInfo();
    if ((info.info.flags & VK_RENDERING_RESUMING_BIT) == 0) {
        AccessContext& access_context = GetCurrentAccessContext();
        for (size_t i = 0; i < info.attachments.size(); i++) {
            const DynamicRenderingInfo::Attachment& attachment = info.attachments[i];
            const SyncAccessIndex load_index = attachment.GetLoadUsage();
            if (load_index == SYNC_ACCESS_INDEX_NONE) {
                continue;
            }
            ImageRangeGen range_gen = attachment.GetRangeGen(info.info.viewMask);
            const AttachmentAccess attachment_access = GetAttachmentAccess(attachment.GetOrdering(), AttachmentAccessType::LoadOp);
            access_context.UpdateAttachmentAccessState(range_gen, load_index, attachment_access, ResourceUsageTagEx{tag});
        }
    }
    dynamic_rendering_info_ = std::move(cmd_state.info);
}

bool CommandBufferAccessContext::ValidateEndRendering(const ErrorObject& error_obj) const {
    bool skip = false;

    // Only validate resolve and store if not suspending (as specified by BeginRendering)
    if (!dynamic_rendering_info_ || (dynamic_rendering_info_->info.flags & VK_RENDERING_SUSPENDING_BIT) != 0) {
        return skip;
    }

    for (uint32_t i = 0; i < (uint32_t)dynamic_rendering_info_->attachments.size(); i++) {
        const auto& attachment = dynamic_rendering_info_->attachments[i];

        auto attachment_description = [this, &error_obj, &attachment, i](const auto& view, std::ostringstream& ss) {
            ss << vvl::String(vvl::Field::pRenderingInfo) << ".";
            ss << attachment.GetLocation(error_obj.location, uint32_t(i)).Fields();
            ss << " (" << sync_state_.FormatHandle(view->Handle());
        };

        // The logic about whether to resolve is embedded in the Attachment constructor
        if (attachment.resolve_gen) {
            const bool is_color = attachment.type == AttachmentType::kColor;
            const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder;

            const AttachmentAccess resolve_read_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveRead);
            ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);
            HazardResult hazard = current_context_->DetectAttachmentHazard(view_gen, kResolveRead, resolve_read_access);
            if (hazard.IsHazard()) {
                LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());

                std::ostringstream ss;
                attachment_description(attachment.view, ss);
                ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.info.resolveMode) << ")";
                const std::string resource_description = ss.str();

                const std::string error = sync_state_.error_messages_.EndRenderingResolveError(
                    hazard, *this, error_obj.location.function, resource_description, attachment.info.resolveMode, false);
                skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
                if (skip) {
                    break;
                }
            }

            const AttachmentAccess resolve_write_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveWrite);
            ImageRangeGen resolve_gen = *attachment.resolve_gen;
            hazard = current_context_->DetectAttachmentHazard(resolve_gen, kResolveWrite, resolve_write_access);
            if (hazard.IsHazard()) {
                LogObjectList objlist(cb_state_->Handle(), attachment.resolve_view->Handle());

                std::ostringstream ss;
                attachment_description(attachment.resolve_view, ss);
                ss << ", resolveMode " << string_VkResolveModeFlagBits(attachment.info.resolveMode) << ")";
                const std::string resource_description = ss.str();

                const std::string error = sync_state_.error_messages_.EndRenderingResolveError(
                    hazard, *this, error_obj.location.function, resource_description, attachment.info.resolveMode, true);
                skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
                if (skip) {
                    break;
                }
            }
        }

        const SyncAccessIndex store_access = attachment.GetStoreUsage();
        if (store_access != SYNC_ACCESS_INDEX_NONE) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(kStoreOrder, AttachmentAccessType::StoreOp);
            ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);

            HazardResult hazard = current_context_->DetectAttachmentHazard(view_gen, store_access, attachment_access);
            if (hazard.IsHazard()) {
                LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());

                std::ostringstream ss;
                attachment_description(attachment.view, ss);
                ss << ", storeOp " << string_VkAttachmentStoreOp(attachment.info.storeOp) << ")";
                const std::string resource_description = ss.str();

                const std::string error = sync_state_.error_messages_.EndRenderingStoreError(
                    hazard, *this, error_obj.location.function, resource_description, attachment.info.storeOp);
                skip |= sync_state_.SyncError(hazard.Hazard(), objlist, error_obj.location.function, error);
                if (skip) {
                    break;
                }
            }
        }
    }
    return skip;
}

void CommandBufferAccessContext::RecordEndRendering(const RecordObject& record_obj) {
    if (!dynamic_rendering_info_) {
        return;
    }
    if ((dynamic_rendering_info_->info.flags & VK_RENDERING_SUSPENDING_BIT) != 0) {
        dynamic_rendering_info_.reset();
        return;
    }

    auto store_tag = NextCommandTag(record_obj.location.function, SubCommandType::kStoreOp);
    AccessContext& access_context = GetCurrentAccessContext();

    for (const auto& attachment : dynamic_rendering_info_->attachments) {
        if (attachment.resolve_gen) {
            const bool is_color = attachment.type == AttachmentType::kColor;
            const SyncOrdering kResolveOrder = is_color ? kColorResolveOrder : kDepthStencilResolveOrder;

            const AttachmentAccess resolve_read_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveRead);
            ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);
            access_context.UpdateAttachmentAccessState(view_gen, kResolveRead, resolve_read_access, ResourceUsageTagEx{store_tag});

            const AttachmentAccess resolve_write_access = GetAttachmentAccess(kResolveOrder, AttachmentAccessType::ResolveWrite);
            ImageRangeGen resolve_gen = *attachment.resolve_gen;
            access_context.UpdateAttachmentAccessState(resolve_gen, kResolveWrite, resolve_write_access,
                                                       ResourceUsageTagEx{store_tag});
        }

        const SyncAccessIndex store_index = attachment.GetStoreUsage();
        if (store_index != SYNC_ACCESS_INDEX_NONE) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(kStoreOrder, AttachmentAccessType::StoreOp);
            ImageRangeGen view_gen = attachment.GetRangeGen(dynamic_rendering_info_->info.viewMask);
            access_context.UpdateAttachmentAccessState(view_gen, store_index, attachment_access, ResourceUsageTagEx{store_tag});
        }
    }
    current_render_pass_instance_id_++;
    dynamic_rendering_info_.reset();
}

bool CommandBufferAccessContext::ValidateDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint,
                                                                   const Location& loc) const {
    bool skip = false;
    if (!sync_state_.syncval_settings.shader_accesses_heuristic) {
        return skip;
    }
    const auto& last_bound_state = cb_state_->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
    const vvl::Pipeline* pipe = last_bound_state.pipeline_state;
    const std::vector<LastBound::DescriptorSetSlot>& ds_slots = last_bound_state.ds_slots;
    if (!pipe) {
        return skip;
    }

    using DescriptorClass = vvl::DescriptorClass;
    using BufferDescriptor = vvl::BufferDescriptor;
    using ImageDescriptor = vvl::ImageDescriptor;
    using TexelDescriptor = vvl::TexelDescriptor;

    for (const auto& stage_state : pipe->stage_states) {
        if (stage_state.GetStage() == VK_SHADER_STAGE_FRAGMENT_BIT && pipe->RasterizationDisabled()) {
            continue;
        } else if (!stage_state.HasSpirv()) {
            continue;
        }
        for (const auto& variable : stage_state.entrypoint->resource_interface_variables) {
            if (variable.decorations.set >= ds_slots.size()) {
                // This should be caught by Core validation, but if core checks are disabled SyncVal should not crash.
                continue;
            }
            const auto& ds_slot = ds_slots[variable.decorations.set];
            const auto* descriptor_set = ds_slot.ds_state.get();
            if (!descriptor_set) continue;
            auto binding = descriptor_set->GetBinding(variable.decorations.binding);
            if (!binding) continue;
            const auto descriptor_type = binding->type;
            SyncAccessIndex sync_index = GetSyncStageAccessIndexsByDescriptorSet(descriptor_type, variable, stage_state.GetStage());

            // Currently, validation of memory accesses based on declared descriptors can produce false-positives.
            // The shader can decide not to do such accesses, it can perform accesses with more narrow scope
            // (e.g. read access, when both reads and writes are allowed) or for an array of descriptors, not all
            // elements are accessed in the general case.
            //
            // This workaround disables validation for the descriptor array case.
            if (binding->count > 1) {
                continue;
            }

            for (uint32_t index = 0; index < binding->count; index++) {
                const auto* descriptor = binding->GetDescriptor(index);
                switch (descriptor->GetClass()) {
                    case DescriptorClass::ImageSampler:
                    case DescriptorClass::Image: {
                        if (descriptor->Invalid()) {
                            continue;
                        }

                        // NOTE: ImageSamplerDescriptor inherits from ImageDescriptor, so this cast works for both types.
                        const auto* image_descriptor = static_cast<const ImageDescriptor*>(descriptor);
                        const auto* img_view_state = image_descriptor->GetImageViewState();
                        VkImageLayout image_layout = image_descriptor->GetImageLayout();

                        if (img_view_state->is_depth_sliced) {
                            // NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in
                            // Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment.
                            // See: VUID 00343
                            continue;
                        }

                        HazardResult hazard;

                        if (sync_index == SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ) {
                            const VkExtent3D extent = CastTo3D(cb_state_->render_area.extent);
                            const VkOffset3D offset = CastTo3D(cb_state_->render_area.offset);
                            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kRaster);
                            hazard = current_context_->DetectAttachmentHazard(*img_view_state, offset, extent, sync_index,
                                                                              attachment_access);
                        } else {
                            hazard = current_context_->DetectHazard(*img_view_state, sync_index);
                        }

                        if (hazard.IsHazard()) {
                            LogObjectList objlist(cb_state_->Handle(), img_view_state->Handle(), pipe->Handle());
                            const auto error = error_messages_.ImageDescriptorError(
                                hazard, *this, loc.function, sync_state_.FormatHandle(*img_view_state), *pipe,
                                variable.decorations.set, *descriptor_set, descriptor_type, variable.decorations.binding, index,
                                stage_state.GetStage(), image_layout);
                            skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
                        }
                        break;
                    }
                    case DescriptorClass::TexelBuffer: {
                        const auto* texel_descriptor = static_cast<const TexelDescriptor*>(descriptor);
                        if (texel_descriptor->Invalid()) {
                            continue;
                        }
                        const auto* buf_view_state = texel_descriptor->GetBufferViewState();
                        const auto* buf_state = buf_view_state->buffer_state.get();
                        const AccessRange range = MakeRange(*buf_view_state);
                        auto hazard = current_context_->DetectHazard(*buf_state, sync_index, range);
                        if (hazard.IsHazard()) {
                            LogObjectList objlist(cb_state_->Handle(), buf_view_state->Handle(), pipe->Handle());
                            const auto error = error_messages_.BufferDescriptorError(
                                hazard, *this, loc.function, sync_state_.FormatHandle(*buf_view_state), *pipe,
                                variable.decorations.set, *descriptor_set, descriptor_type, variable.decorations.binding, index,
                                stage_state.GetStage());
                            skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
                        }
                        break;
                    }
                    case DescriptorClass::GeneralBuffer: {
                        const auto* buffer_descriptor = static_cast<const BufferDescriptor*>(descriptor);
                        if (buffer_descriptor->Invalid()) {
                            continue;
                        }
                        VkDeviceSize offset = buffer_descriptor->GetOffset();
                        if (vvl::IsDynamicDescriptor(descriptor_type)) {
                            const uint32_t dynamic_offset_index =
                                descriptor_set->GetDynamicOffsetIndexFromBinding(binding->binding);
                            if (dynamic_offset_index >= ds_slot.dynamic_offsets.size()) {
                                continue;  // core validation error
                            }
                            offset += ds_slot.dynamic_offsets[dynamic_offset_index];
                        }
                        const auto* buf_state = buffer_descriptor->GetBufferState();
                        const AccessRange range = MakeRange(*buf_state, offset, buffer_descriptor->GetRange());
                        auto hazard = current_context_->DetectHazard(*buf_state, sync_index, range);
                        if (hazard.IsHazard()) {
                            LogObjectList objlist(cb_state_->Handle(), buf_state->Handle(), pipe->Handle());
                            const auto error = error_messages_.BufferDescriptorError(
                                hazard, *this, loc.function, sync_state_.FormatHandle(*buf_state), *pipe, variable.decorations.set,
                                *descriptor_set, descriptor_type, variable.decorations.binding, index, stage_state.GetStage());
                            skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
                        }
                        break;
                    }
                    case DescriptorClass::AccelerationStructure: {
                        const auto* accel_descriptor = static_cast<const vvl::AccelerationStructureDescriptor*>(descriptor);
                        if (accel_descriptor->Invalid()) {
                            continue;
                        }
                        const vvl::AccelerationStructureKHR* accel = accel_descriptor->GetAccelerationStructureStateKHR();
                        if (!accel) {
                            continue;
                        }
                        if (const vvl::BufferAndOffset as_buffer = accel->GetFirstValidBuffer(cb_state_->dev_data)) {
                            const AccessRange range = MakeRange(*as_buffer.state, as_buffer.offset, accel->GetSize());
                            auto hazard = current_context_->DetectHazard(*as_buffer.state, sync_index, range);
                            if (hazard.IsHazard()) {
                                LogObjectList objlist(cb_state_->Handle(), as_buffer.state->Handle(), pipe->Handle());
                                const std::string resource_description = sync_state_.FormatHandle(accel->Handle());
                                const std::string error = error_messages_.AccelerationStructureDescriptorError(
                                    hazard, *this, loc.function, resource_description, *pipe, variable.decorations.set,
                                    *descriptor_set, descriptor_type, variable.decorations.binding, index, stage_state.GetStage());
                                skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
                            }
                        }
                        break;
                    }
                    // TODO: INLINE_UNIFORM_BLOCK_EXT
                    default:
                        break;
                }
            }
        }
    }
    return skip;
}

// TODO: Record structure repeats Validate. Unify this code, it was the source of bugs few times already.
void CommandBufferAccessContext::RecordDispatchDrawDescriptorSet(VkPipelineBindPoint pipelineBindPoint,
                                                                 const ResourceUsageTag tag) {
    if (!sync_state_.syncval_settings.shader_accesses_heuristic) {
        return;
    }

    const auto& last_bound_state = cb_state_->lastBound[ConvertToVvlBindPoint(pipelineBindPoint)];
    const vvl::Pipeline* pipe = last_bound_state.pipeline_state;
    const std::vector<LastBound::DescriptorSetSlot>& ds_slots = last_bound_state.ds_slots;
    if (!pipe) {
        return;
    }

    using DescriptorClass = vvl::DescriptorClass;
    using BufferDescriptor = vvl::BufferDescriptor;
    using ImageDescriptor = vvl::ImageDescriptor;
    using TexelDescriptor = vvl::TexelDescriptor;

    for (const auto& stage_state : pipe->stage_states) {
        if (stage_state.GetStage() == VK_SHADER_STAGE_FRAGMENT_BIT && pipe->RasterizationDisabled()) {
            continue;
        } else if (!stage_state.HasSpirv()) {
            continue;
        }
        for (const auto& variable : stage_state.entrypoint->resource_interface_variables) {
            if (variable.decorations.set >= ds_slots.size()) {
                // This should be caught by Core validation, but if core checks are disabled SyncVal should not crash.
                continue;
            }
            const auto& ds_slot = ds_slots[variable.decorations.set];
            const auto* descriptor_set = ds_slot.ds_state.get();
            if (!descriptor_set) continue;
            auto binding = descriptor_set->GetBinding(variable.decorations.binding);
            if (!binding) continue;
            const auto descriptor_type = binding->type;
            SyncAccessIndex sync_index = GetSyncStageAccessIndexsByDescriptorSet(descriptor_type, variable, stage_state.GetStage());

            // Do not update state for descriptor array (the same as in Validate function).
            if (binding->count > 1) {
                continue;
            }

            for (uint32_t i = 0; i < binding->count; i++) {
                const auto* descriptor = binding->GetDescriptor(i);
                switch (descriptor->GetClass()) {
                    case DescriptorClass::ImageSampler:
                    case DescriptorClass::Image: {
                        // NOTE: ImageSamplerDescriptor inherits from ImageDescriptor, so this cast works for both types.
                        const auto* image_descriptor = static_cast<const ImageDescriptor*>(descriptor);
                        if (image_descriptor->Invalid()) {
                            continue;
                        }
                        const auto* img_view_state = image_descriptor->GetImageViewState();
                        if (img_view_state->is_depth_sliced) {
                            // NOTE: 2D ImageViews of VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT Images are not allowed in
                            // Descriptors, unless VK_EXT_image_2d_view_of_3d is supported, which it isn't at the moment.
                            // See: VUID 00343
                            continue;
                        }
                        const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, img_view_state->image_state->Handle());
                        if (sync_index == SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ) {
                            const VkExtent3D extent = CastTo3D(cb_state_->render_area.extent);
                            const VkOffset3D offset = CastTo3D(cb_state_->render_area.offset);
                            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kRaster);

                            ImageRangeGen range_gen(MakeImageRangeGen(*img_view_state, offset, extent));
                            current_context_->UpdateAttachmentAccessState(range_gen, SYNC_FRAGMENT_SHADER_INPUT_ATTACHMENT_READ,
                                                                          attachment_access, tag_ex);
                        } else {
                            ImageRangeGen range_gen = MakeImageRangeGen(*img_view_state);
                            current_context_->UpdateAccessState(range_gen, sync_index, tag_ex);
                        }
                        break;
                    }
                    case DescriptorClass::TexelBuffer: {
                        const auto* texel_descriptor = static_cast<const TexelDescriptor*>(descriptor);
                        if (texel_descriptor->Invalid()) {
                            continue;
                        }
                        const auto* buf_view_state = texel_descriptor->GetBufferViewState();
                        const auto* buf_state = buf_view_state->buffer_state.get();
                        const AccessRange range = MakeRange(*buf_view_state);
                        const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_view_state->Handle());
                        current_context_->UpdateAccessState(*buf_state, sync_index, range, tag_ex);
                        break;
                    }
                    case DescriptorClass::GeneralBuffer: {
                        const auto* buffer_descriptor = static_cast<const BufferDescriptor*>(descriptor);
                        if (buffer_descriptor->Invalid()) {
                            continue;
                        }
                        VkDeviceSize offset = buffer_descriptor->GetOffset();
                        if (vvl::IsDynamicDescriptor(descriptor_type)) {
                            const uint32_t dynamic_offset_index =
                                descriptor_set->GetDynamicOffsetIndexFromBinding(binding->binding);
                            if (dynamic_offset_index >= ds_slot.dynamic_offsets.size()) {
                                continue;  // core validation error
                            }
                            offset += ds_slot.dynamic_offsets[dynamic_offset_index];
                        }
                        const auto* buf_state = buffer_descriptor->GetBufferState();
                        const AccessRange range = MakeRange(*buf_state, offset, buffer_descriptor->GetRange());
                        const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_state->Handle());
                        current_context_->UpdateAccessState(*buf_state, sync_index, range, tag_ex);
                        break;
                    }
                    case DescriptorClass::AccelerationStructure: {
                        const auto* accel_descriptor = static_cast<const vvl::AccelerationStructureDescriptor*>(descriptor);
                        if (accel_descriptor->Invalid()) {
                            continue;
                        }
                        const vvl::AccelerationStructureKHR* accel = accel_descriptor->GetAccelerationStructureStateKHR();
                        if (!accel) {
                            continue;
                        }
                        if (const vvl::BufferAndOffset as_buffer = accel->GetFirstValidBuffer(cb_state_->dev_data)) {
                            const AccessRange range = MakeRange(*as_buffer.state, as_buffer.offset, accel->GetSize());
                            const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, accel->Handle());
                            current_context_->UpdateAccessState(*as_buffer.state, sync_index, range, tag_ex);
                        }
                        break;
                    }
                    // TODO: INLINE_UNIFORM_BLOCK_EXT
                    default:
                        break;
                }
            }
        }
    }
}

bool CommandBufferAccessContext::ValidateDrawVertex(uint32_t vertexCount, uint32_t firstVertex, const Location& loc) const {
    bool skip = false;
    const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state;
    if (!pipe) {
        return skip;
    }

    const auto& binding_buffers = cb_state_->current_vertex_buffer_binding_info;
    const auto& vertex_bindings = pipe->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)
                                      ? cb_state_->dynamic_state_value.vertex_bindings
                                      : pipe->vertex_input_state->bindings;

    for (const auto& [_, binding_state] : vertex_bindings) {
        const auto& binding_desc = binding_state.desc;
        if (binding_desc.inputRate != VK_VERTEX_INPUT_RATE_VERTEX) {
            // TODO: add support to determine range of instance level attributes
            continue;
        }
        if (const vvl::VertexBufferBinding* vertex_buffer = vvl::Find(binding_buffers, binding_desc.binding)) {
            // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
            const auto buf_state = sync_state_.Get<vvl::Buffer>(vertex_buffer->Buffer());
            if (!buf_state) continue;  // also skips if using nullDescriptor

            const AccessRange range =
                MakeRangeForVertexData(vertex_buffer->BufferOffset(), firstVertex, vertexCount, binding_state);
            auto hazard = current_context_->DetectHazard(*buf_state, SYNC_VERTEX_ATTRIBUTE_INPUT_VERTEX_ATTRIBUTE_READ, range);
            if (hazard.IsHazard()) {
                LogObjectList objlist(cb_state_->Handle(), buf_state->Handle(), pipe->Handle());
                const std::string resource_description = "vertex " + sync_state_.FormatHandle(*buf_state);
                const auto error = error_messages_.BufferError(hazard, *this, loc.function, resource_description, range);
                skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
            }
        }
    }
    return skip;
}

void CommandBufferAccessContext::RecordDrawVertex(uint32_t vertexCount, uint32_t firstVertex, const ResourceUsageTag tag) {
    const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state;
    if (!pipe) {
        return;
    }
    const auto& binding_buffers = cb_state_->current_vertex_buffer_binding_info;
    const auto& vertex_bindings = pipe->IsDynamic(CB_DYNAMIC_STATE_VERTEX_INPUT_EXT)
                                      ? cb_state_->dynamic_state_value.vertex_bindings
                                      : pipe->vertex_input_state->bindings;

    for (const auto& [_, binding_state] : vertex_bindings) {
        const auto& binding_desc = binding_state.desc;
        if (binding_desc.inputRate != VK_VERTEX_INPUT_RATE_VERTEX) {
            // TODO: add support to determine range of instance level attributes
            continue;
        }
        if (const auto* vertex_buffer = vvl::Find(binding_buffers, binding_desc.binding)) {
            // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
            const auto buf_state = sync_state_.Get<vvl::Buffer>(vertex_buffer->Buffer());
            if (!buf_state) continue;  // also skips if using nullDescriptor

            const AccessRange range =
                MakeRangeForVertexData(vertex_buffer->BufferOffset(), firstVertex, vertexCount, binding_state);
            const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, buf_state->Handle());
            current_context_->UpdateAccessState(*buf_state, SYNC_VERTEX_ATTRIBUTE_INPUT_VERTEX_ATTRIBUTE_READ, range, tag_ex);
        }
    }
}

bool CommandBufferAccessContext::ValidateDrawVertexIndex(uint32_t index_count, uint32_t firstIndex, const Location& loc) const {
    bool skip = false;
    const auto& index_binding = cb_state_->index_buffer_binding;
    // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
    const auto index_buf_state = sync_state_.Get<vvl::Buffer>(index_binding.Buffer());
    if (!index_buf_state) return skip;

    const uint32_t index_size = IndexTypeByteSize(index_binding.index_type);
    const AccessRange range = MakeRangeForIndexData(index_binding.BufferOffset(), firstIndex, index_count, index_size);

    auto hazard = current_context_->DetectHazard(*index_buf_state, SYNC_INDEX_INPUT_INDEX_READ, range);
    if (hazard.IsHazard()) {
        LogObjectList objlist(cb_state_->Handle(), index_buf_state->Handle());
        if (const auto* pipe = cb_state_->GetLastBoundGraphics().pipeline_state) {
            objlist.add(pipe->Handle());
        }
        const std::string resource_description = "index " + sync_state_.FormatHandle(*index_buf_state);
        const auto error = error_messages_.BufferError(hazard, *this, loc.function, resource_description, range);
        skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
    }

    // TODO: Shader instrumentation support is needed to read index buffer content and determine the range of accessed
    // versices (new syncval mode). Scanning index buffer for each draw call might be the simplest option to implement
    // and the most reliable one, but potentially it can be heavy (still might be okay, testing is needed).
    // Some other options: a) rescan index buffer when its modification is detected, b) scan index buffer only once and
    // then assume it is immutable (common scenario).
    // skip |= ValidateDrawVertex(?, ?, loc);

    return skip;
}

void CommandBufferAccessContext::RecordDrawVertexIndex(uint32_t indexCount, uint32_t firstIndex, const ResourceUsageTag tag) {
    const auto& index_binding = cb_state_->index_buffer_binding;
    // TODO - Handle https://gitlab.khronos.org/vulkan/Vulkan-ValidationLayers/-/issues/45
    const auto index_buf_state = sync_state_.Get<vvl::Buffer>(index_binding.Buffer());
    if (!index_buf_state) {
        return;
    }

    const uint32_t index_size = IndexTypeByteSize(index_binding.index_type);
    const AccessRange range = MakeRangeForIndexData(index_binding.BufferOffset(), firstIndex, indexCount, index_size);
    const ResourceUsageTagEx tag_ex = AddCommandHandle(tag, index_buf_state->Handle());
    current_context_->UpdateAccessState(*index_buf_state, SYNC_INDEX_INPUT_INDEX_READ, range, tag_ex);

    // TODO: Shader instrumentation support is needed to read index buffer content and determine the range of accessed
    // versices (new syncval mode). Scanning index buffer for each draw call might be the simplest option to implement
    // and the most reliable one, but potentially it can be heavy (still might be okay, testing is needed).
    // Some other options: a) rescan index buffer when its modification is detected, b) scan index buffer only once and
    // then assume it is immutable (common scenario).
    // RecordDrawVertex(?, ?, tag);
}

bool CommandBufferAccessContext::ValidateDrawAttachment(const Location& loc) const {
    bool skip = false;
    if (current_renderpass_context_) {
        skip |= current_renderpass_context_->ValidateDrawSubpassAttachment(*this, loc.function);
    } else if (dynamic_rendering_info_) {
        skip |= ValidateDrawDynamicRenderingAttachment(loc);
    }
    return skip;
}

bool CommandBufferAccessContext::ValidateDrawDynamicRenderingAttachment(const Location& location) const {
    bool skip = false;
    const auto& last_bound_state = cb_state_->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 AccessContext& access_context = GetCurrentAccessContext();

    const DynamicRenderingInfo& info = *dynamic_rendering_info_;
    for (const auto output_location : list) {
        if (output_location >= info.info.colorAttachmentCount) {
            continue;
        }
        const auto& attachment = info.attachments[output_location];
        if (!attachment.IsWriteable(last_bound_state)) {
            continue;
        }
        const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
        ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
        HazardResult hazard =
            access_context.DetectAttachmentHazard(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);

        if (hazard.IsHazard()) {
            LogObjectList obj_list(cb_state_->Handle(), attachment.view->Handle());
            Location loc = attachment.GetLocation(location, output_location);
            const std::string error = error_messages_.Error(
                hazard, *this, location.function, sync_state_.FormatHandle(*attachment.view), "DynamicRenderingAttachmentError");
            skip |= sync_state_.SyncError(hazard.Hazard(), obj_list, loc.dot(vvl::Field::imageView), error);
        }
    }

    // TODO -- fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes
    // PHASE1 TODO: Add layout based read/vs. write selection.
    // PHASE1 TODO: Read operations for both depth and stencil are possible in the future.
    // PHASE1 TODO: Add EARLY stage detection based on ExecutionMode.
    for (size_t i = info.info.colorAttachmentCount; i < info.attachments.size(); i++) {
        const auto& attachment = info.attachments[i];
        bool writeable = attachment.IsWriteable(last_bound_state);

        if (writeable) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
            ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
            HazardResult hazard = access_context.DetectAttachmentHazard(
                view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access);

            if (hazard.IsHazard()) {
                LogObjectList objlist(cb_state_->Handle(), attachment.view->Handle());
                Location loc = attachment.GetLocation(location);
                const std::string error =
                    error_messages_.Error(hazard, *this, location.function, sync_state_.FormatHandle(*attachment.view),
                                          "DynamicRenderingAttachmentError");
                skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc.dot(vvl::Field::imageView), error);
            }
        }
    }

    return skip;
}

void CommandBufferAccessContext::RecordDrawAttachment(const ResourceUsageTag tag) {
    if (current_renderpass_context_) {
        current_renderpass_context_->RecordDrawSubpassAttachment(*cb_state_, tag);
    } else if (dynamic_rendering_info_) {
        RecordDrawDynamicRenderingAttachment(tag);
    }
}

void CommandBufferAccessContext::RecordDrawDynamicRenderingAttachment(ResourceUsageTag tag) {
    const auto& last_bound_state = cb_state_->GetLastBoundGraphics();
    const auto* pipe = last_bound_state.pipeline_state;
    if (!pipe || pipe->RasterizationDisabled()) {
        return;
    }

    const auto& list = pipe->fs_writable_output_location_list;
    auto& access_context = GetCurrentAccessContext();

    const DynamicRenderingInfo& info = *dynamic_rendering_info_;
    for (const auto output_location : list) {
        if (output_location >= info.info.colorAttachmentCount) {
            continue;
        }
        const auto& attachment = info.attachments[output_location];
        if (!attachment.IsWriteable(last_bound_state)) {
            continue;
        }
        const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
        ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
        access_context.UpdateAttachmentAccessState(view_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access,
                                                   ResourceUsageTagEx{tag});
    }

    // TODO -- fixup this and Subpass attachment to correct map the various depth stencil enables/reads vs. writes
    // PHASE1 TODO: Add layout based read/vs. write selection.
    // PHASE1 TODO: Read operations for both depth and stencil are possible in the future.
    // PHASE1 TODO: Add EARLY stage detection based on ExecutionMode.

    const uint32_t attachment_count = static_cast<uint32_t>(info.attachments.size());
    for (uint32_t i = info.info.colorAttachmentCount; i < attachment_count; i++) {
        const auto& attachment = info.attachments[i];
        bool writeable = attachment.IsWriteable(last_bound_state);

        if (writeable) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
            ImageRangeGen view_gen = attachment.GetRangeGen(info.info.viewMask);
            access_context.UpdateAttachmentAccessState(view_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                       attachment_access, ResourceUsageTagEx{tag});
        }
    }
}

VkImageAspectFlags CommandBufferAccessContext::GetAttachmentAspectsToClear(VkImageAspectFlags clear_aspect_mask,
                                                                           const vvl::ImageView& attachment_view) const {
    // Check if clear request is valid.
    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
    }

    // Color aspects to clear
    if (clear_color) {
        // The image view aspect mask is used only for color attachment.
        // For depth/stencil attachment, it is ignored according to the spec.
        const VkImageAspectFlags view_aspect_mask = attachment_view.normalized_subresource_range.aspectMask;
        return view_aspect_mask & kColorAspects;
    }

    // Depth-stencil aspects to clear
    bool has_depth_attachment = false;
    bool has_stencil_attachment = false;
    if (dynamic_rendering_info_) {
        has_depth_attachment = dynamic_rendering_info_->info.pDepthAttachment != nullptr;
        has_stencil_attachment = dynamic_rendering_info_->info.pStencilAttachment != nullptr;
    } else if (current_renderpass_context_) {
        const auto& rp_create_info = current_renderpass_context_->GetRenderPassState()->create_info;
        const auto& subpass = rp_create_info.pSubpasses[current_renderpass_context_->GetCurrentSubpass()];
        if (subpass.pDepthStencilAttachment) {
            const uint32_t attachment = subpass.pDepthStencilAttachment->attachment;
            if (attachment < rp_create_info.attachmentCount) {
                const VkFormat ds_format = rp_create_info.pAttachments[attachment].format;
                has_depth_attachment = vkuFormatHasDepth(ds_format);
                has_stencil_attachment = vkuFormatHasStencil(ds_format);
            }
        }
    }
    VkImageAspectFlags ds_aspects_to_clear = VK_IMAGE_ASPECT_NONE;
    if (clear_depth && has_depth_attachment) {
        ds_aspects_to_clear |= VK_IMAGE_ASPECT_DEPTH_BIT;
    }
    if (clear_stencil && has_stencil_attachment) {
        ds_aspects_to_clear |= VK_IMAGE_ASPECT_STENCIL_BIT;
    }
    return ds_aspects_to_clear;
}

static std::optional<VkImageSubresourceRange> RestrictSubresourceRangeToClearLayers(
    const VkImageSubresourceRange& normalized_subresource_range, uint32_t clear_first_layer, uint32_t clear_layer_count) {
    // Contract of this function
    assert(normalized_subresource_range.layerCount != VK_REMAINING_ARRAY_LAYERS);
    // According to spec
    assert(clear_layer_count != VK_REMAINING_ARRAY_LAYERS);

    const uint32_t first = std::max(normalized_subresource_range.baseArrayLayer, clear_first_layer);
    const uint32_t last_range = normalized_subresource_range.baseArrayLayer + normalized_subresource_range.layerCount;
    const uint32_t last_clear = clear_first_layer + clear_layer_count;
    const uint32_t last = std::min(last_range, last_clear);

    if (first >= last) {
        return {};
    }

    std::optional<VkImageSubresourceRange> result = normalized_subresource_range;
    result->baseArrayLayer = first;
    result->layerCount = last - first;
    return result;
}

std::optional<CommandBufferAccessContext::ClearAttachmentInfo> CommandBufferAccessContext::GetClearAttachmentInfo(
    const VkClearAttachment& clear_attachment, uint32_t clear_first_layer, uint32_t clear_layer_count) const {
    const vvl::ImageView* attachment_view = nullptr;
    if (current_renderpass_context_) {
        attachment_view = current_renderpass_context_->GetClearAttachmentView(clear_attachment);
    } else if (dynamic_rendering_info_) {
        attachment_view = dynamic_rendering_info_->GetClearAttachmentView(clear_attachment);
    }
    if (!attachment_view) {
        return {};
    }

    const VkImageAspectFlags aspects_to_clear = GetAttachmentAspectsToClear(clear_attachment.aspectMask, *attachment_view);
    if (!aspects_to_clear) {
        return {};
    }

    std::optional<VkImageSubresourceRange> subresource_range =
        RestrictSubresourceRangeToClearLayers(attachment_view->normalized_subresource_range, clear_first_layer, clear_layer_count);
    if (!subresource_range.has_value()) {
        return {};
    }
    subresource_range->aspectMask = aspects_to_clear;
    return ClearAttachmentInfo{*attachment_view, *subresource_range};
}

bool CommandBufferAccessContext::ValidateClearAttachment(const Location& loc, const VkClearAttachment& clear_attachment,
                                                         uint32_t clear_rect_index, const VkClearRect& clear_rect) const {
    bool skip = false;

    const auto optional_info = GetClearAttachmentInfo(clear_attachment, clear_rect.baseArrayLayer, clear_rect.layerCount);
    if (!optional_info) {
        return skip;
    }
    const ClearAttachmentInfo& info = *optional_info;
    const VkImageSubresourceRange subresource_range = info.subresource_range;
    const VkImageAspectFlags aspects_to_clear = subresource_range.aspectMask;
    const uint32_t view_mask = GetViewMask();
    const ImageSubState& sub_state = SubState(*info.attachment_view.image_state);

    // NOTE: when we teach ImageRangeGen to work with view masks all logic will be much simplified

    // Validate Color clear
    auto report_color_hazard = [this, &skip, &loc, &info](const HazardResult& hazard, const VkClearAttachment& clear_attachment,
                                                          uint32_t clear_rect_index, const VkClearRect& clear_rect) {
        std::ostringstream ss;
        ss << string_VkImageAspectFlags(clear_attachment.aspectMask);
        ss << " aspect of color attachment " << clear_attachment.colorAttachment;
        ss << " (" << sync_state_.FormatHandle(info.attachment_view) << ")";
        if (current_renderpass_context_) {
            ss << " in subpass " << current_renderpass_context_->GetCurrentSubpass();
        }
        const std::string resource_description = ss.str();
        const LogObjectList objlist(cb_state_->Handle(), info.attachment_view.Handle());
        const auto error = error_messages_.ClearAttachmentError(hazard, *this, loc.function, resource_description,
                                                                clear_attachment.aspectMask, clear_rect_index, clear_rect);
        skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
    };
    if (aspects_to_clear & kColorAspects) {
        // [core validation check]: if COLOR_ASPECT is included then PLANE aspects are not allowed,
        // and if PLANE aspect is included then only one is allowed.
        assert(CountSetBits(aspects_to_clear) == 1);

        const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
        if (view_mask == 0) {
            HazardResult hazard = current_context_->DetectAttachmentHazard(
                *info.attachment_view.image_state, subresource_range, info.attachment_view.is_depth_sliced,
                SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);
            if (hazard.IsHazard()) {
                report_color_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
            }
        } else {
            const auto view_indices = GetSetBitIndices(view_mask);
            const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range;
            for (uint32_t view_index : view_indices) {
                if (view_index < attachment_subresource.layerCount) {
                    VkImageSubresourceRange view_subresource = attachment_subresource;
                    view_subresource.baseArrayLayer += view_index;
                    view_subresource.layerCount = 1;

                    ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, info.attachment_view.is_depth_sliced);
                    HazardResult hazard = current_context_->DetectAttachmentHazard(
                        range_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, attachment_access);
                    if (hazard.IsHazard()) {
                        report_color_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
                    }
                }
            }
        }
    }

    // Validate Depth-Stencil clear
    auto report_depth_stencil_hazard = [this, &skip, &loc, &info](const HazardResult& hazard,
                                                                  const VkClearAttachment& clear_attachment,
                                                                  uint32_t clear_rect_index, const VkClearRect& clear_rect) {
        std::ostringstream ss;
        ss << string_VkImageAspectFlags(clear_attachment.aspectMask);
        ss << " aspect(s) of depth-stencil attachment (";
        ss << sync_state_.FormatHandle(info.attachment_view) << ")";
        if (current_renderpass_context_) {
            ss << " in subpass " << current_renderpass_context_->GetCurrentSubpass();
        }
        const std::string resource_description = ss.str();
        const LogObjectList objlist(cb_state_->Handle(), info.attachment_view.Handle());
        const auto error = error_messages_.ClearAttachmentError(hazard, *this, loc.function, resource_description,
                                                                clear_attachment.aspectMask, clear_rect_index, clear_rect);
        skip |= sync_state_.SyncError(hazard.Hazard(), objlist, loc, error);
    };
    if (aspects_to_clear & kDepthStencilAspects) {
        const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);

        if (view_mask == 0) {
            // vkCmdClearAttachments depth/stencil writes are executed by the EARLY_FRAGMENT_TESTS_BIT and LATE_FRAGMENT_TESTS_BIT
            // stages. The implementation tracks the most recent access, which happens in the LATE_FRAGMENT_TESTS_BIT stage.
            HazardResult hazard = current_context_->DetectAttachmentHazard(
                *info.attachment_view.image_state, subresource_range, info.attachment_view.is_depth_sliced,
                SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access);
            if (hazard.IsHazard()) {
                report_depth_stencil_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
            }
        } else {
            const auto view_indices = GetSetBitIndices(view_mask);
            const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range;
            for (uint32_t view_index : view_indices) {
                if (view_index < attachment_subresource.layerCount) {
                    VkImageSubresourceRange view_subresource = attachment_subresource;
                    view_subresource.baseArrayLayer += view_index;
                    view_subresource.layerCount = 1;

                    ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, info.attachment_view.is_depth_sliced);
                    HazardResult hazard = current_context_->DetectAttachmentHazard(
                        range_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE, attachment_access);
                    if (hazard.IsHazard()) {
                        report_depth_stencil_hazard(hazard, clear_attachment, clear_rect_index, clear_rect);
                    }
                }
            }
        }
    }
    return skip;
}

void CommandBufferAccessContext::RecordClearAttachment(ResourceUsageTag tag, const VkClearAttachment& clear_attachment,
                                                       const VkClearRect& rect) {
    const auto optional_info = GetClearAttachmentInfo(clear_attachment, rect.baseArrayLayer, rect.layerCount);
    if (!optional_info) {
        return;
    }
    const ClearAttachmentInfo& info = *optional_info;
    const VkImageSubresourceRange subresource_range = info.subresource_range;
    const VkImageAspectFlags aspects_to_clear = subresource_range.aspectMask;
    const uint32_t view_mask = GetViewMask();
    const ImageSubState& sub_state = SubState(*info.attachment_view.image_state);

    auto update_access_state = [this, aspects_to_clear, tag](ImageRangeGen& range_gen) {
        if (aspects_to_clear & kColorAspects) {
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kColorAttachment);
            current_context_->UpdateAttachmentAccessState(range_gen, SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE,
                                                          attachment_access, ResourceUsageTagEx{tag});
        } else {
            const AttachmentAccess attachment_access = GetAttachmentAccess(SyncOrdering::kDepthStencilAttachment);
            current_context_->UpdateAttachmentAccessState(range_gen, SYNC_LATE_FRAGMENT_TESTS_DEPTH_STENCIL_ATTACHMENT_WRITE,
                                                          attachment_access, ResourceUsageTagEx{tag});
        }
    };
    // NOTE: when we teach ImageRangeGen to work with view masks all logic will be much simplified
    if (view_mask == 0) {
        ImageRangeGen range_gen = sub_state.MakeImageRangeGen(subresource_range, false);
        update_access_state(range_gen);
    } else {
        const auto view_indices = GetSetBitIndices(view_mask);
        const VkImageSubresourceRange& attachment_subresource = info.attachment_view.normalized_subresource_range;
        for (uint32_t view_index : view_indices) {
            if (view_index < attachment_subresource.layerCount) {
                VkImageSubresourceRange view_subresource = attachment_subresource;
                view_subresource.baseArrayLayer += view_index;
                view_subresource.layerCount = 1;
                ImageRangeGen range_gen = sub_state.MakeImageRangeGen(view_subresource, false);
                update_access_state(range_gen);
            }
        }
    }
}

QueueId CommandBufferAccessContext::GetQueueId() const { return kQueueIdInvalid; }

ResourceUsageTag CommandBufferAccessContext::RecordBeginRenderPass(
    vvl::Func command, const vvl::RenderPass& rp_state, const VkRect2D& render_area,
    const std::vector<std::shared_ptr<const vvl::ImageView>>& attachment_views) {
    // Create an access context the current renderpass.
    const auto barrier_tag = NextCommandTag(command, SubCommandType::kSubpassTransition, 0);
    AddCommandHandle(barrier_tag, rp_state.Handle());
    const auto load_tag = NextSubCommandTag(command, SubCommandType::kLoadOp, 0);
    render_pass_contexts_.emplace_back(std::make_unique<RenderPassAccessContext>(
        rp_state, render_area, GetQueueFlags(), attachment_views, cb_access_context_, current_render_pass_instance_id_));
    current_renderpass_context_ = render_pass_contexts_.back().get();
    current_renderpass_context_->RecordBeginRenderPass(barrier_tag, load_tag);
    current_context_ = &current_renderpass_context_->CurrentContext();
    return barrier_tag;
}

ResourceUsageTag CommandBufferAccessContext::RecordNextSubpass(vvl::Func command) {
    assert(current_renderpass_context_);
    if (!current_renderpass_context_) return NextCommandTag(command);

    // At this point current subpass value has not updated yet to the index of "next subpass"
    const uint32_t previous_subpass = current_renderpass_context_->GetCurrentSubpass();
    const uint32_t this_subpass = previous_subpass + 1;

    auto resolve_tag = NextCommandTag(command, SubCommandType::kResolveOp, previous_subpass);
    AddCommandHandle(resolve_tag, current_renderpass_context_->GetRenderPassState()->Handle());
    auto store_tag = NextSubCommandTag(command, SubCommandType::kStoreOp, previous_subpass);
    auto transition_tag = NextSubCommandTag(command, SubCommandType::kSubpassTransition, this_subpass);
    auto load_tag = NextSubCommandTag(command, SubCommandType::kLoadOp, this_subpass);

    current_renderpass_context_->RecordNextSubpass(resolve_tag, store_tag, transition_tag, load_tag);
    current_context_ = &current_renderpass_context_->CurrentContext();
    return transition_tag;
}

ResourceUsageTag CommandBufferAccessContext::RecordEndRenderPass(vvl::Func command) {
    assert(current_renderpass_context_);
    if (!current_renderpass_context_) return NextCommandTag(command);

    const uint32_t current_subpass = current_renderpass_context_->GetCurrentSubpass();

    auto store_tag = NextCommandTag(command, SubCommandType::kStoreOp, current_subpass);
    AddCommandHandle(store_tag, current_renderpass_context_->GetRenderPassState()->Handle());

    auto barrier_tag = NextSubCommandTag(command, SubCommandType::kSubpassTransition);

    current_renderpass_context_->RecordEndRenderPass(&cb_access_context_, store_tag, barrier_tag);
    current_context_ = &cb_access_context_;
    current_renderpass_context_ = nullptr;
    current_render_pass_instance_id_++;
    return barrier_tag;
}

void CommandBufferAccessContext::RecordDestroyEvent(vvl::Event* event_state) { GetEventsContext().Destroy(event_state); }

void CommandBufferAccessContext::RecordExecutedCommandBuffer(const CommandBufferAccessContext& recorded_cb_context) {
    const AccessContext& recorded_context = recorded_cb_context.GetCurrentAccessContext();

    // Just run through the barriers ignoring the usage from the recorded context, as Resolve will overwrite outdated state
    const ResourceUsageTag base_tag = GetTagCount();
    for (const auto& sync_op : recorded_cb_context.GetSyncOps()) {
        // we update the range to any include layout transition first use writes,
        // as they are stored along with the source scope (as effective barrier) when recorded
        sync_op.sync_op->ReplayRecord(*this, base_tag + sync_op.tag);
    }

    ImportRecordedAccessLog(recorded_cb_context);
    ResolveExecutedCommandBuffer(recorded_context, base_tag);
}

void CommandBufferAccessContext::ResolveExecutedCommandBuffer(const AccessContext& recorded_context, ResourceUsageTag offset) {
    auto tag_offset = [offset](AccessState* access) { access->OffsetTag(offset); };
    GetCurrentAccessContext().ResolveFromContext(tag_offset, recorded_context);
}

void CommandBufferAccessContext::ImportRecordedAccessLog(const CommandBufferAccessContext& recorded_context) {
    cbs_referenced_->emplace_back(recorded_context.GetCBStateShared());
    access_log_->insert(access_log_->end(), recorded_context.access_log_->cbegin(), recorded_context.access_log_->cend());

    // Adjust command indices for the log records added from recorded_context.
    const auto& recorded_label_commands = recorded_context.cb_state_->GetLabelCommands();
    const bool use_proxy = !proxy_label_commands_.empty();
    const auto& label_commands = use_proxy ? proxy_label_commands_ : cb_state_->GetLabelCommands();
    if (!label_commands.empty()) {
        assert(label_commands.size() >= recorded_label_commands.size());
        const uint32_t command_offset = static_cast<uint32_t>(label_commands.size() - recorded_label_commands.size());
        for (size_t i = 0; i < recorded_context.access_log_->size(); i++) {
            size_t index = (access_log_->size() - 1) - i;
            assert((*access_log_)[index].label_command_index != vvl::kNoIndex32);
            (*access_log_)[index].label_command_index += command_offset;
        }
    }
}

ResourceUsageTag CommandBufferAccessContext::NextCommandTag(vvl::Func command, SubCommandType subcommand, uint32_t subpass) {
    command_number_++;
    current_command_tag_ = access_log_->size();

    ResourceUsageRecord& record = access_log_->emplace_back(command, command_number_, subcommand, cb_state_, reset_count_, subpass);

    if (!cb_state_->GetLabelCommands().empty()) {
        record.label_command_index = static_cast<uint32_t>(cb_state_->GetLabelCommands().size() - 1);
    }
    CheckCommandTagDebugCheckpoint();
    return current_command_tag_;
}

ResourceUsageTag CommandBufferAccessContext::NextSubCommandTag(vvl::Func command, SubCommandType subcommand, uint32_t subpass) {
    const ResourceUsageTag tag = access_log_->size();
    ResourceUsageRecord& record = access_log_->emplace_back(command, command_number_, subcommand, cb_state_, reset_count_, subpass);

    // By default copy handle range from the main command, but can be overwritten with AddSubcommandHandle.
    const auto& main_command_record = (*access_log_)[current_command_tag_];
    record.first_handle_index = main_command_record.first_handle_index;
    record.handle_count = main_command_record.handle_count;

    if (!cb_state_->GetLabelCommands().empty()) {
        record.label_command_index = static_cast<uint32_t>(cb_state_->GetLabelCommands().size() - 1);
    }
    return tag;
}

uint32_t CommandBufferAccessContext::AddHandle(const VulkanTypedHandle& typed_handle, uint32_t index) {
    const uint32_t handle_index = static_cast<uint32_t>(handles_.size());
    handles_.emplace_back(HandleRecord(typed_handle, index));
    sync_state_.stats.AddHandleRecord();
    return handle_index;
}

ResourceUsageTagEx CommandBufferAccessContext::AddCommandHandle(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle) {
    return AddCommandHandleIndexed(tag, typed_handle, vvl::kNoIndex32);
}

ResourceUsageTagEx CommandBufferAccessContext::AddCommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle,
                                                                       uint32_t index) {
    assert(tag < access_log_->size());
    const uint32_t handle_index = AddHandle(typed_handle, index);
    // TODO: the following range check is not needed. Test and remove.
    if (tag < access_log_->size()) {
        auto& record = (*access_log_)[tag];
        if (record.first_handle_index == vvl::kNoIndex32) {
            record.first_handle_index = handle_index;
            record.handle_count = 1;
        } else {
            // assert that command handles occupy continuous range
            assert(handle_index - record.first_handle_index == record.handle_count);
            record.handle_count++;
        }
    }
    return {tag, handle_index};
}

void CommandBufferAccessContext::AddSubcommandHandleIndexed(ResourceUsageTag tag, const VulkanTypedHandle& typed_handle,
                                                            uint32_t index) {
    assert(tag < access_log_->size());
    const uint32_t handle_index = AddHandle(typed_handle, index);
    // TODO: the following range check is not needed. Test and remove.
    if (tag < access_log_->size()) {
        auto& record = (*access_log_)[tag];
        const auto& main_command_record = (*access_log_)[current_command_tag_];
        if (record.first_handle_index == main_command_record.first_handle_index) {
            // override default behavior that subcommand references the same handles as the main command
            record.first_handle_index = handle_index;
            record.handle_count = 1;
        } else {
            // assert that command handles occupy continuous range
            assert(handle_index - record.first_handle_index == record.handle_count);
            record.handle_count++;
        }
    }
}

std::string CommandBufferAccessContext::GetDebugRegionName(const ResourceUsageRecord& record) const {
    const bool use_proxy = !proxy_label_commands_.empty();
    const auto& label_commands = use_proxy ? proxy_label_commands_ : cb_state_->GetLabelCommands();
    return vvl::CommandBuffer::GetDebugRegionName(label_commands, record.label_command_index);
}

void CommandBufferAccessContext::AddSyncOp(ResourceUsageTag tag, std::shared_ptr<SyncOpBase>&& sync_op) {
    // As renderpass operations can have side effects on the command buffer access context,
    // update the sync operation to record these if any.
    sync_ops_.emplace_back(tag, std::move(sync_op));
}

AttachmentAccess CommandBufferAccessContext::GetAttachmentAccess(SyncOrdering ordering, AttachmentAccessType type) const {
    AttachmentAccess attachment_access;
    attachment_access.type = type;
    attachment_access.ordering = ordering;
    attachment_access.render_pass_instance_id = current_render_pass_instance_id_;
    attachment_access.subpass = current_renderpass_context_ ? current_renderpass_context_->GetCurrentSubpass() : vvl::kNoIndex32;
    return attachment_access;
}

uint32_t CommandBufferAccessContext::GetViewMask() const {
    if (dynamic_rendering_info_) {
        return dynamic_rendering_info_->info.viewMask;
    } else if (current_renderpass_context_) {
        const auto& render_pass_ci = current_renderpass_context_->GetRenderPassState()->create_info;
        const uint32_t subpass = current_renderpass_context_->GetCurrentSubpass();
        return render_pass_ci.pSubpasses[subpass].viewMask;
    } else {
        assert(false && "GetViewMask musk be called only during render pass instance");
        return 0;
    }
}

// NOTE: debug location reporting feature works only for reproducible application sessions
// (it uses command number/reset count from the error message from the previous session).
// It's considered experimental and can be replaced with a better way to report syncval debug locations.
//
// Logs informational message when vulkan command stream reaches a specific location.
// The message can be intercepted by the reporting routines. For example, the message handler can trigger a breakpoint.
// The location can be specified through environment variables.
// VK_SYNCVAL_DEBUG_COMMAND_NUMBER: the command number
// VK_SYNCVAL_DEBUG_RESET_COUNT: (optional, default value is 1) command buffer reset count
// VK_SYNCVAL_DEBUG_CMDBUF_PATTERN: (optional, empty string by default) pattern to match command buffer debug name
void CommandBufferAccessContext::CheckCommandTagDebugCheckpoint() {
    auto get_cmdbuf_name = [](const DebugReport& debug_report, uint64_t cmdbuf_handle) {
        std::unique_lock<std::mutex> lock(debug_report.debug_output_mutex);
        std::string object_name = debug_report.GetUtilsObjectNameNoLock(cmdbuf_handle);
        if (object_name.empty()) {
            object_name = debug_report.GetMarkerObjectNameNoLock(cmdbuf_handle);
        }
        text::ToLower(object_name);
        return object_name;
    };
    if (sync_state_.debug_command_number == command_number_ && sync_state_.debug_reset_count == reset_count_) {
        const auto cmdbuf_name = get_cmdbuf_name(*sync_state_.debug_report, cb_state_->Handle().handle);
        const auto& pattern = sync_state_.debug_cmdbuf_pattern;
        const bool cmdbuf_match = pattern.empty() || (cmdbuf_name.find(pattern) != std::string::npos);
        if (cmdbuf_match) {
            sync_state_.LogInfo("SYNCVAL_DEBUG_COMMAND", LogObjectList(), Location(access_log_->back().command),
                                "Command stream has reached command #%" PRIu32 " in command buffer %s with reset count #%" PRIu32,
                                sync_state_.debug_command_number, sync_state_.FormatHandle(cb_state_->Handle()).c_str(),
                                sync_state_.debug_reset_count);
        }
    }
}

void UpdateAccessMapStats(const AccessMap& access_map, AccessContextStats& stats);

void CommandBufferAccessContext::UpdateStats(AccessStats& access_stats) const {
#if VVL_ENABLE_SYNCVAL_STATS != 0
    UpdateAccessMapStats(cb_access_context_.GetAccessMap(), access_stats.cb_access_stats);

    for (const auto& render_pass_context : render_pass_contexts_) {
        for (const AccessContext& subpass_access_context : render_pass_context->GetSubpassContexts()) {
            UpdateAccessMapStats(subpass_access_context.GetAccessMap(), access_stats.subpass_access_stats);
        }
    }
#endif
}

CommandBufferSubState::CommandBufferSubState(SyncValidator& dev, vvl::CommandBuffer& cb)
    : vvl::CommandBufferSubState(cb), access_context(dev, &cb) {
    access_context.SetSelfReference();
}

void CommandBufferSubState::End() {
    access_context.GetCurrentAccessContext().Finalize();

    // For threads that are dedicated to recording command buffers but do not submit themselves,
    // the end of recording is a logical point to update memory stats
    access_context.GetSyncState().stats.UpdateMemoryStats();
}

void CommandBufferSubState::Destroy() {
    access_context.Destroy();  // must be first to clean up self references correctly.
}

void CommandBufferSubState::Reset(const Location& loc) { access_context.Reset(); }

void CommandBufferSubState::NotifyInvalidate(const vvl::StateObject::NodeList& invalid_nodes, bool unlink) {
    for (auto& obj : invalid_nodes) {
        switch (obj->Type()) {
            case kVulkanObjectTypeEvent:
                access_context.RecordDestroyEvent(static_cast<vvl::Event*>(obj.get()));
                break;
            default:
                break;
        }
    }
}

void CommandBufferSubState::RecordCopyBuffer(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
                                             const VkBufferCopy* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        const AccessRange src_range = MakeRange(src_buffer_state, copy_region.srcOffset, copy_region.size);
        context.UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);

        const AccessRange dst_range = MakeRange(dst_buffer_state, copy_region.dstOffset, copy_region.size);
        context.UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordCopyBuffer2(vvl::Buffer& src_buffer_state, vvl::Buffer& dst_buffer_state, uint32_t region_count,
                                              const VkBufferCopy2* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        const AccessRange src_range = MakeRange(src_buffer_state, copy_region.srcOffset, copy_region.size);
        context.UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);

        const AccessRange dst_range = MakeRange(dst_buffer_state, copy_region.dstOffset, copy_region.size);
        context.UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordCopyImage(vvl::Image& src_image_state, vvl::Image& dst_image_state,
                                            VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
                                            const VkImageCopy* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        UpdateImageAccessState(context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.srcSubresource),
                               copy_region.srcOffset, copy_region.extent, src_tag_ex);
        UpdateImageAccessState(context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.dstSubresource),
                               copy_region.dstOffset, copy_region.extent, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordCopyImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state,
                                             VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
                                             const VkImageCopy2* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        UpdateImageAccessState(context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.srcSubresource),
                               copy_region.srcOffset, copy_region.extent, src_tag_ex);
        UpdateImageAccessState(context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.dstSubresource),
                               copy_region.dstOffset, copy_region.extent, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordCopyBufferToImage(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout,
                                                    uint32_t region_count, const VkBufferImageCopy* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        AccessRange src_range = MakeRange(copy_region.bufferOffset, dst_image_state.GetBufferSizeFromCopyImage(copy_region));
        context.UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);

        UpdateImageAccessState(context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.imageSubresource),
                               copy_region.imageOffset, copy_region.imageExtent, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordCopyBufferToImage2(vvl::Buffer& src_buffer_state, vvl::Image& dst_image_state, VkImageLayout,
                                                     uint32_t region_count, const VkBufferImageCopy2* regions,
                                                     const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_buffer_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        AccessRange src_range = MakeRange(copy_region.bufferOffset, dst_image_state.GetBufferSizeFromCopyImage(copy_region));
        context.UpdateAccessState(src_buffer_state, SYNC_COPY_TRANSFER_READ, src_range, src_tag_ex);

        UpdateImageAccessState(context, dst_image_state, SYNC_COPY_TRANSFER_WRITE, RangeFromLayers(copy_region.imageSubresource),
                               copy_region.imageOffset, copy_region.imageExtent, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordCopyImageToBuffer(vvl::Image& src_image_state, vvl::Buffer& dst_buffer_state,
                                                    VkImageLayout src_image_layout, uint32_t region_count,
                                                    const VkBufferImageCopy* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        UpdateImageAccessState(context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.imageSubresource),
                               copy_region.imageOffset, copy_region.imageExtent, src_tag_ex);

        AccessRange dst_range = MakeRange(copy_region.bufferOffset, src_image_state.GetBufferSizeFromCopyImage(copy_region));
        context.UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordCopyImageToBuffer2(vvl::Image& src_image_state, vvl::Buffer& dst_buffer_state,
                                                     VkImageLayout src_image_layout, uint32_t region_count,
                                                     const VkBufferImageCopy2* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());

    for (const auto& copy_region : vvl::make_span(regions, region_count)) {
        UpdateImageAccessState(context, src_image_state, SYNC_COPY_TRANSFER_READ, RangeFromLayers(copy_region.imageSubresource),
                               copy_region.imageOffset, copy_region.imageExtent, src_tag_ex);

        AccessRange dst_range = MakeRange(copy_region.bufferOffset, src_image_state.GetBufferSizeFromCopyImage(copy_region));
        context.UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, dst_range, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordBlitImage(vvl::Image& src_image_state, vvl::Image& dst_image_state,
                                            VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
                                            const VkImageBlit* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& blit_region : vvl::make_span(regions, region_count)) {
        VkOffset3D offset = {std::min(blit_region.srcOffsets[0].x, blit_region.srcOffsets[1].x),
                             std::min(blit_region.srcOffsets[0].y, blit_region.srcOffsets[1].y),
                             std::min(blit_region.srcOffsets[0].z, blit_region.srcOffsets[1].z)};
        VkExtent3D extent = {static_cast<uint32_t>(abs(blit_region.srcOffsets[1].x - blit_region.srcOffsets[0].x)),
                             static_cast<uint32_t>(abs(blit_region.srcOffsets[1].y - blit_region.srcOffsets[0].y)),
                             static_cast<uint32_t>(abs(blit_region.srcOffsets[1].z - blit_region.srcOffsets[0].z))};
        UpdateImageAccessState(context, src_image_state, SYNC_BLIT_TRANSFER_READ, RangeFromLayers(blit_region.srcSubresource),
                               offset, extent, src_tag_ex);

        offset = {std::min(blit_region.dstOffsets[0].x, blit_region.dstOffsets[1].x),
                  std::min(blit_region.dstOffsets[0].y, blit_region.dstOffsets[1].y),
                  std::min(blit_region.dstOffsets[0].z, blit_region.dstOffsets[1].z)};
        extent = {static_cast<uint32_t>(abs(blit_region.dstOffsets[1].x - blit_region.dstOffsets[0].x)),
                  static_cast<uint32_t>(abs(blit_region.dstOffsets[1].y - blit_region.dstOffsets[0].y)),
                  static_cast<uint32_t>(abs(blit_region.dstOffsets[1].z - blit_region.dstOffsets[0].z))};
        UpdateImageAccessState(context, dst_image_state, SYNC_BLIT_TRANSFER_WRITE, RangeFromLayers(blit_region.dstSubresource),
                               offset, extent, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordBlitImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state,
                                             VkImageLayout src_image_layout, VkImageLayout dst_image_layout, uint32_t region_count,
                                             const VkImageBlit2* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& blit_region : vvl::make_span(regions, region_count)) {
        VkOffset3D offset = {std::min(blit_region.srcOffsets[0].x, blit_region.srcOffsets[1].x),
                             std::min(blit_region.srcOffsets[0].y, blit_region.srcOffsets[1].y),
                             std::min(blit_region.srcOffsets[0].z, blit_region.srcOffsets[1].z)};
        VkExtent3D extent = {static_cast<uint32_t>(abs(blit_region.srcOffsets[1].x - blit_region.srcOffsets[0].x)),
                             static_cast<uint32_t>(abs(blit_region.srcOffsets[1].y - blit_region.srcOffsets[0].y)),
                             static_cast<uint32_t>(abs(blit_region.srcOffsets[1].z - blit_region.srcOffsets[0].z))};
        UpdateImageAccessState(context, src_image_state, SYNC_BLIT_TRANSFER_READ, RangeFromLayers(blit_region.srcSubresource),
                               offset, extent, src_tag_ex);

        offset = {std::min(blit_region.dstOffsets[0].x, blit_region.dstOffsets[1].x),
                  std::min(blit_region.dstOffsets[0].y, blit_region.dstOffsets[1].y),
                  std::min(blit_region.dstOffsets[0].z, blit_region.dstOffsets[1].z)};
        extent = {static_cast<uint32_t>(abs(blit_region.dstOffsets[1].x - blit_region.dstOffsets[0].x)),
                  static_cast<uint32_t>(abs(blit_region.dstOffsets[1].y - blit_region.dstOffsets[0].y)),
                  static_cast<uint32_t>(abs(blit_region.dstOffsets[1].z - blit_region.dstOffsets[0].z))};
        UpdateImageAccessState(context, dst_image_state, SYNC_BLIT_TRANSFER_WRITE, RangeFromLayers(blit_region.dstSubresource),
                               offset, extent, dst_tag_ex);
    }
}

void CommandBufferSubState::RecordResolveImage(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
                                               const VkImageResolve* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& resolve_region : vvl::make_span(regions, region_count)) {
        UpdateImageAccessState(context, src_image_state, SYNC_RESOLVE_TRANSFER_READ,
                               RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset, resolve_region.extent,
                               src_tag_ex);
        UpdateImageAccessState(context, dst_image_state, SYNC_RESOLVE_TRANSFER_WRITE,
                               RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset, resolve_region.extent,
                               dst_tag_ex);
    }
}

void CommandBufferSubState::RecordResolveImage2(vvl::Image& src_image_state, vvl::Image& dst_image_state, uint32_t region_count,
                                                const VkImageResolve2* regions, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    auto src_tag_ex = access_context.AddCommandHandle(tag, src_image_state.Handle());
    auto dst_tag_ex = access_context.AddCommandHandle(tag, dst_image_state.Handle());

    for (const auto& resolve_region : vvl::make_span(regions, region_count)) {
        UpdateImageAccessState(context, src_image_state, SYNC_RESOLVE_TRANSFER_READ,
                               RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset, resolve_region.extent,
                               src_tag_ex);
        UpdateImageAccessState(context, dst_image_state, SYNC_RESOLVE_TRANSFER_WRITE,
                               RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset, resolve_region.extent,
                               dst_tag_ex);
    }
}

void CommandBufferSubState::RecordClearColorImage(vvl::Image& image_state, VkImageLayout, const VkClearColorValue*,
                                                  uint32_t range_count, const VkImageSubresourceRange* ranges,
                                                  const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    access_context.AddCommandHandle(tag, image_state.Handle());

    for (uint32_t index = 0; index < range_count; index++) {
        const auto& range = ranges[index];
        UpdateImageAccessState(context, image_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag);
    }
}

void CommandBufferSubState::RecordClearDepthStencilImage(vvl::Image& image_state, VkImageLayout, const VkClearDepthStencilValue*,
                                                         uint32_t range_count, const VkImageSubresourceRange* ranges,
                                                         const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    access_context.AddCommandHandle(tag, image_state.Handle());

    for (uint32_t index = 0; index < range_count; index++) {
        const auto& range = ranges[index];
        UpdateImageAccessState(context, image_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag);
    }
}

void CommandBufferSubState::RecordClearAttachments(uint32_t attachment_count, const VkClearAttachment* pAttachments,
                                                   uint32_t rect_count, const VkClearRect* pRects, const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);

    for (const auto& attachment : vvl::make_span(pAttachments, attachment_count)) {
        for (const auto& rect : vvl::make_span(pRects, rect_count)) {
            access_context.RecordClearAttachment(tag, attachment, rect);
        }
    }
}

void CommandBufferSubState::RecordFillBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size,
                                             const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    const AccessRange range = MakeRange(buffer_state, offset, size);
    const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, buffer_state.Handle());
    context.UpdateAccessState(buffer_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag_ex);
}

void CommandBufferSubState::RecordUpdateBuffer(vvl::Buffer& buffer_state, VkDeviceSize offset, VkDeviceSize size,
                                               const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    // VK_WHOLE_SIZE not allowed
    const AccessRange range = MakeRange(offset, size);
    const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, buffer_state.Handle());
    context.UpdateAccessState(buffer_state, SYNC_CLEAR_TRANSFER_WRITE, range, tag_ex);
}

void CommandBufferSubState::RecordDecodeVideo(vvl::VideoSession& vs_state, const VkVideoDecodeInfoKHR& decode_info,
                                              const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    if (auto src_buffer = base.dev_data.Get<vvl::Buffer>(decode_info.srcBuffer)) {
        const AccessRange src_range = MakeRange(*src_buffer, decode_info.srcBufferOffset, decode_info.srcBufferRange);
        const ResourceUsageTagEx src_tag_ex = access_context.AddCommandHandle(tag, src_buffer->Handle());
        context.UpdateAccessState(*src_buffer, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, src_range, src_tag_ex);
    }

    const auto* device_state = access_context.GetSyncState().device_state;
    auto dst_resource = vvl::VideoPictureResource(*device_state, decode_info.dstPictureResource);
    if (dst_resource) {
        UpdateVideoAccessState(context, vs_state, dst_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE, tag);
    }

    if (decode_info.pSetupReferenceSlot != nullptr && decode_info.pSetupReferenceSlot->pPictureResource != nullptr) {
        auto setup_resource = vvl::VideoPictureResource(*device_state, *decode_info.pSetupReferenceSlot->pPictureResource);
        if (setup_resource && (setup_resource != dst_resource)) {
            UpdateVideoAccessState(context, vs_state, setup_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE, tag);
        }
    }

    for (uint32_t i = 0; i < decode_info.referenceSlotCount; ++i) {
        if (decode_info.pReferenceSlots[i].pPictureResource != nullptr) {
            auto reference_resource = vvl::VideoPictureResource(*device_state, *decode_info.pReferenceSlots[i].pPictureResource);
            if (reference_resource) {
                UpdateVideoAccessState(context, vs_state, reference_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, tag);
            }
        }
    }
}

void CommandBufferSubState::RecordEncodeVideo(vvl::VideoSession& vs_state, const VkVideoEncodeInfoKHR& encode_info,
                                              const Location& loc) {
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    if (auto src_buffer = base.dev_data.Get<vvl::Buffer>(encode_info.dstBuffer)) {
        const AccessRange src_range = MakeRange(*src_buffer, encode_info.dstBufferOffset, encode_info.dstBufferRange);
        const ResourceUsageTagEx src_tag_ex = access_context.AddCommandHandle(tag, src_buffer->Handle());
        context.UpdateAccessState(*src_buffer, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, src_range, src_tag_ex);
    }

    const auto* device_state = access_context.GetSyncState().device_state;
    auto src_resource = vvl::VideoPictureResource(*device_state, encode_info.srcPictureResource);
    if (src_resource) {
        UpdateVideoAccessState(context, vs_state, src_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, tag);
    }

    if (encode_info.pSetupReferenceSlot != nullptr && encode_info.pSetupReferenceSlot->pPictureResource != nullptr) {
        auto setup_resource = vvl::VideoPictureResource(*device_state, *encode_info.pSetupReferenceSlot->pPictureResource);
        if (setup_resource) {
            UpdateVideoAccessState(context, vs_state, setup_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, tag);
        }
    }

    for (uint32_t i = 0; i < encode_info.referenceSlotCount; ++i) {
        if (encode_info.pReferenceSlots[i].pPictureResource != nullptr) {
            auto reference_resource = vvl::VideoPictureResource(*device_state, *encode_info.pReferenceSlots[i].pPictureResource);
            if (reference_resource) {
                UpdateVideoAccessState(context, vs_state, reference_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, tag);
            }
        }
    }

    if (encode_info.flags & (VK_VIDEO_ENCODE_WITH_QUANTIZATION_DELTA_MAP_BIT_KHR | VK_VIDEO_ENCODE_WITH_EMPHASIS_MAP_BIT_KHR)) {
        auto quantization_map_info = vku::FindStructInPNextChain<VkVideoEncodeQuantizationMapInfoKHR>(encode_info.pNext);
        if (quantization_map_info) {
            auto image_view_state = base.dev_data.Get<vvl::ImageView>(quantization_map_info->quantizationMap);
            if (image_view_state) {
                VkOffset3D offset = {0, 0, 0};
                VkExtent3D extent = {quantization_map_info->quantizationMapExtent.width,
                                     quantization_map_info->quantizationMapExtent.height, 1};
                ImageRangeGen range_gen(MakeImageRangeGen(*image_view_state, offset, extent));
                context.UpdateAccessState(range_gen, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ, ResourceUsageTagEx{tag});
            }
        }
    }
}

void CommandBufferSubState::RecordCopyQueryPoolResults(vvl::QueryPool& pool_state, vvl::Buffer& dst_buffer_state,
                                                       uint32_t first_query, uint32_t query_count, VkDeviceSize dst_offset,
                                                       VkDeviceSize stride, VkQueryResultFlags flags, const Location& loc) {
    if (query_count == 0) {
        return;
    }
    const auto tag = access_context.NextCommandTag(loc.function);
    AccessContext& context = access_context.GetCurrentAccessContext();

    const uint32_t query_size = (flags & VK_QUERY_RESULT_64_BIT) ? 8 : 4;
    const VkDeviceSize range_size = (query_count - 1) * stride + query_size;
    const AccessRange range = MakeRange(dst_offset, range_size);
    const ResourceUsageTagEx tag_ex = access_context.AddCommandHandle(tag, dst_buffer_state.Handle());
    context.UpdateAccessState(dst_buffer_state, SYNC_COPY_TRANSFER_WRITE, range, tag_ex);

    // TODO:Track VkQueryPool
}

void CommandBufferSubState::RecordBeginRenderPass(const VkRenderPassBeginInfo& render_pass_begin,
                                                  const VkSubpassBeginInfo& subpass_begin_info, const Location& loc) {
    if (!base.IsPrimary()) {
        return;  // [core validation check]: only primary command buffer can begin render pass
    }
    const SyncValidator& validator = access_context.GetSyncState();
    auto rp_state = validator.Get<vvl::RenderPass>(render_pass_begin.renderPass);
    if (!rp_state) {
        return;
    }

    std::vector<std::shared_ptr<const vvl::ImageView>> attachments;
    auto fb_state = access_context.GetSyncState().Get<vvl::Framebuffer>(render_pass_begin.framebuffer);
    if (fb_state) {
        attachments = access_context.GetSyncState().device_state->GetAttachmentViews(render_pass_begin, *fb_state);
    }

    const ResourceUsageTag begin_tag =
        access_context.RecordBeginRenderPass(loc.function, *rp_state, render_pass_begin.renderArea, attachments);
    const RenderPassAccessContext* rp_context = access_context.GetCurrentRenderPassContext();
    auto sync_op = std::make_shared<SyncOpBeginRenderPass>(std::move(rp_state), std::move(attachments), rp_context, loc);
    access_context.AddSyncOp(begin_tag, std::move(sync_op));
}

void CommandBufferSubState::RecordNextSubpass(const VkSubpassBeginInfo& subpass_begin_info,
                                              const VkSubpassEndInfo* subpass_end_info, const Location& loc) {
    if (!base.IsPrimary()) {
        return;  // [core validation check]: only primary command buffer can start next subpass
    }
    const ResourceUsageTag tag = access_context.RecordNextSubpass(loc.function);
    auto sync_op = std::make_shared<SyncOpNextSubpass>(loc.function);
    access_context.AddSyncOp(tag, std::move(sync_op));
}

void CommandBufferSubState::RecordEndRenderPass(const VkSubpassEndInfo* subpass_end_info, const Location& loc) {
    if (!base.IsPrimary()) {
        return;  // [core validation check]: only primary command buffer can end render pass
    }
    // Resolve the all subpass contexts to the command buffer contexts
    const ResourceUsageTag tag = access_context.RecordEndRenderPass(loc.function);
    auto sync_op = std::make_shared<SyncOpEndRenderPass>(loc.function);
    access_context.AddSyncOp(tag, std::move(sync_op));
}

void CommandBufferSubState::RecordExecuteCommand(vvl::CommandBuffer& secondary_command_buffer, uint32_t cmd_index,
                                                 const Location& loc) {
    if (cmd_index == 0) {
        ResourceUsageTag cb_tag = access_context.NextCommandTag(loc.function, SubCommandType::kIndex);
        access_context.AddCommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index);
    } else {
        ResourceUsageTag cb_tag = access_context.NextSubCommandTag(loc.function, SubCommandType::kIndex);
        access_context.AddSubcommandHandleIndexed(cb_tag, secondary_command_buffer.Handle(), cmd_index);
    }
    access_context.RecordExecutedCommandBuffer(GetAccessContext(secondary_command_buffer));
}

}  // namespace syncval
