/* Copyright (c) 2019-2026 The Khronos Group Inc.
 * Copyright (c) 2019-2026 Valve Corporation
 * Copyright (c) 2019-2026 LunarG, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <algorithm>
#include <iostream>
#include <memory>
#include <vector>

#include "sync/sync_error_messages.h"
#include "sync/sync_validation.h"
#include "sync/sync_event.h"
#include "sync/sync_image.h"
#include "state_tracker/buffer_state.h"
#include "state_tracker/ray_tracing_state.h"
#include "state_tracker/render_pass_state.h"
#include "utils/convert_utils.h"
#include "utils/image_utils.h"
#include "utils/ray_tracing_utils.h"
#include "utils/text_utils.h"
#include "vk_layer_config.h"
#include "containers/tls_guard.h"

namespace syncval {

static bool GetShowStatsEnvVar() {
    // Set environment variable as non zero number to enable stats reporting
    const auto show_stats_str = GetEnvironment("VK_SYNCVAL_SHOW_STATS");
    return !show_stats_str.empty() && std::atoi(show_stats_str.c_str()) != 0;
}

SyncValidator::SyncValidator(vvl::DispatchDevice* dev, syncval::Instance* instance_vo)
    : BaseClass(dev, instance_vo, LayerObjectTypeSyncValidation), error_messages_(*this), report_stats_(GetShowStatsEnvVar()) {}

SyncValidator::~SyncValidator() {
    // Instance level SyncValidator does not have much to say
    const bool device_validation_object = (device != nullptr);

    if (device_validation_object && report_stats_) {
        stats.ReportOnDestruction();
    }
}

// Location to add per-queue submit debug info if built with -D DEBUG_CAPTURE_KEYBOARD=ON.
void SyncValidator::DebugCapture() {
    if (report_stats_) {
        stats.UpdateAccessStats(*this);

        // NOTE: mimalloc stats are not updated here - mostly because they are tracked
        // per thread and updating stats only for current thread feels a bit unbalanced.
        // Instead we have specific places to trigger memory stats collection.

        const std::string report = stats.CreateReport();
        std::cout << report;
#ifdef VK_USE_PLATFORM_WIN32_KHR
        OutputDebugString(report.c_str());
#endif
    }
}

bool SyncValidator::SyncError(SyncHazard hazard, const LogObjectList& objlist, const Location& loc,
                              const std::string& error_message) const {
    return LogError(string_SyncHazardVUID(hazard), objlist, loc, "%s", error_message.c_str());
}

ResourceUsageRange SyncValidator::ReserveGlobalTagRange(size_t tag_count) const {
    ResourceUsageRange reserve;
    reserve.begin = tag_limit_.fetch_add(tag_count);
    reserve.end = reserve.begin + tag_count;
    return reserve;
}

void SyncValidator::EnsureTimelineSignalsLimit(uint32_t signals_per_queue_limit, QueueId queue) {
    for (auto& [_, signals] : timeline_signals_) {
        const size_t initial_signal_count = signals.size();
        vvl::unordered_map<QueueId, uint32_t> signals_per_queue;
        for (const SignalInfo& signal : signals) {
            ++signals_per_queue[signal.first_scope.queue];
        }
        const bool filter_queue = queue != kQueueIdInvalid;
        for (auto it = signals.begin(); it != signals.end();) {
            if (filter_queue && it->first_scope.queue != queue) {
                ++it;
                continue;
            }
            auto& counter = signals_per_queue[it->first_scope.queue];
            if (counter > signals_per_queue_limit) {
                it = signals.erase(it);
                --counter;
            } else {
                ++it;
            }
        }
        stats.RemoveTimelineSignals(uint32_t(initial_signal_count - signals.size()));
    }
}

void SyncValidator::ApplySignalsUpdate(SignalsUpdate& update, const BatchContextPtr& last_batch) {
    // NOTE: All conserved QueueBatchContexts need to have their access logs reset to use the global
    // logger and the only conserved QBCs are those referenced by unwaited signals and the last batch.

    for (auto& signal_entry : update.binary_signal_requests) {
        auto& signal_batch = signal_entry.second.batch;
        // Batches retained for signalled semaphore don't need to retain
        // event data, unless it's the last batch in the submit
        if (signal_batch != last_batch) {
            signal_batch->ResetEventsContext();
            // Make sure that retained batches are minimal, and trim
            // after the events contexts has been cleared.
            signal_batch->Trim();
        }
        const VkSemaphore semaphore = signal_entry.first;
        SignalInfo& signal_info = signal_entry.second;
        binary_signals_.insert_or_assign(semaphore, std::move(signal_info));
    }
    for (VkSemaphore semaphore : update.binary_unsignal_requests) {
        binary_signals_.erase(semaphore);
    }
    for (auto& [semaphore, new_signals] : update.timeline_signals) {
        std::vector<SignalInfo>& signals = timeline_signals_[semaphore];
        vvl::Append(signals, new_signals);
        stats.AddTimelineSignals((uint32_t)new_signals.size());

        // Update host sync points
        std::deque<TimelineHostSyncPoint>& host_sync_points = host_waitable_semaphores_[semaphore];
        for (SignalInfo& new_signal : new_signals) {
            if (new_signal.batch) {
                // The lifetimes of the semaphore host sync points are managed by vkWaitSemaphores.
                // kMaxTimelineHostSyncPoints limit is used when the program does not use vkWaitSemaphores.
                // We accumulate up to kMaxTimelineHostSyncPoints of the host sync points per semaphore.
                // Dropping old sync points cannot introduce false positives but may miss a sync hazard.
                // The limit is chosen to be large enough comparing to typical numbers of queue submissions
                // between host synchronization points.
                const uint32_t kMaxTimelineHostSyncPoints = 256;  // max ~6 Kb per semaphore
                if (host_sync_points.size() >= kMaxTimelineHostSyncPoints) {
                    host_sync_points.pop_front();
                }
                // Add a host sync point for this signal
                TimelineHostSyncPoint sync_point;
                assert(new_signal.first_scope.queue != kQueueIdInvalid);
                sync_point.queue_id = new_signal.first_scope.queue;
                sync_point.tag = new_signal.batch->GetTagRange().end - 1;
                sync_point.timeline_value = new_signal.timeline_value;
                host_sync_points.emplace_back(sync_point);
            }
        }
    }
    for (const auto& remove_signals_request : update.remove_timeline_signals_requests) {
        auto& signals = timeline_signals_[remove_signals_request.semaphore];
        for (auto it = signals.begin(); it != signals.end();) {
            const SignalInfo& signal = *it;
            if (signal.first_scope.queue == remove_signals_request.queue &&
                signal.timeline_value < remove_signals_request.signal_threshold_value) {
                it = signals.erase(it);
                stats.RemoveTimelineSignals(1);
                continue;
            }
            ++it;
        }
    }

    // Enforce max signals limit in case timeline is signaled multiple times and never/rarely is waited on.
    // This does not introduce errors/false-positives (check EnsureTimelineSignalsLimit documentation)
    const uint32_t kMaxTimelineSignalsPerQueue = 100;
    EnsureTimelineSignalsLimit(kMaxTimelineSignalsPerQueue);
}

void SyncValidator::ApplyTaggedWait(QueueId queue_id, ResourceUsageTag tag,
                                    const LastSynchronizedPresent& last_synchronized_present,
                                    const std::vector<ResourceUsageTag>& queue_sync_tags) {
    assert(queue_id < queue_id_limit_);
    assert(queue_sync_tags.empty() || queue_sync_tags.size() == queue_id_limit_);
    assert(queue_sync_tags.empty() || queue_sync_tags[queue_id] == tag);

    // Create a list of queues that have to be synchronized up to some point.
    // Note that, in general, not only the queue_id queue has to be synchronized.
    // If queue_id was synchronized with other queues through a semaphore wait,
    // then waiting for queue_id also means waiting for those other queues
    std::vector<std::pair<QueueId, ResourceUsageTag>> sync_points;
    if (queue_sync_tags.empty()) {
        sync_points.emplace_back(queue_id, tag);
    } else {
        sync_points.reserve(queue_id_limit_);
        for (const auto [sync_queue, sync_tag] : vvl::enumerate(queue_sync_tags)) {
            if (sync_tag > 0) {
                sync_points.emplace_back((QueueId)sync_queue, sync_tag);
            }
        }
    }

    const auto all_batches = GetAllQueueBatchContexts();
    for (const auto& batch : all_batches) {
        for (const auto& [sync_queue, sync_tag] : sync_points) {
            batch->ApplyTaggedWait(sync_queue, sync_tag, last_synchronized_present);
        }
        batch->Trim();
    }
}

void SyncValidator::ApplyAcquireWait(const AcquiredImage& acquired) {
    for (const auto& batch : GetAllQueueBatchContexts()) {
        batch->ApplyAcquireWait(acquired);
        batch->Trim();
    }
}

std::vector<BatchContextPtr> SyncValidator::GetAllQueueBatchContexts() {
    // Get last batch from each queue
    std::vector<BatchContextPtr> batch_contexts = GetLastBatches([](auto) { return true; });

    // Get batches from binary signals
    for (auto& [_, signal] : binary_signals_) {
        if (!vvl::Contains(batch_contexts, signal.batch)) {
            batch_contexts.emplace_back(signal.batch);
        }
    }
    // Get batches from timeline signals
    for (auto& [_, signals] : timeline_signals_) {
        for (const auto& signal : signals) {
            if (signal.batch && !vvl::Contains(batch_contexts, signal.batch)) {
                batch_contexts.emplace_back(signal.batch);
            }
        }
    }
    // Get present batches
    device_state->ForEachShared<vvl::Swapchain>([&batch_contexts](const std::shared_ptr<vvl::Swapchain>& swapchain) {
        auto& sync_swapchain = SubState(*swapchain);
        sync_swapchain.GetPresentBatches(batch_contexts);
    });

    return batch_contexts;
}

void SyncValidator::UpdateFenceHostSyncPoint(VkFence fence, FenceHostSyncPoint&& sync_point) {
    std::shared_ptr<const vvl::Fence> fence_state = Get<vvl::Fence>(fence);
    if (!vvl::StateObject::Invalid(fence_state)) {
        waitable_fences_[fence_state->VkHandle()] = std::move(sync_point);
    }
}

void SyncValidator::WaitForFence(VkFence fence) {
    if (auto fence_it = waitable_fences_.find(fence); fence_it != waitable_fences_.end()) {
        FenceHostSyncPoint& wait_for = fence_it->second;
        if (wait_for.queue_id != kQueueIdInvalid) {
            const QueueState& queue_state = GetQueueState(wait_for.queue_id);
            ApplyTaggedWait(wait_for.queue_id, wait_for.tag, queue_state.GetLastSynchronizedPresent(), wait_for.queue_sync_tags);
        } else if (!vvl::StateObject::Invalid(wait_for.acquired.image)) {
            ApplyAcquireWait(wait_for.acquired);
        }
        waitable_fences_.erase(fence_it);
    }
}

void SyncValidator::WaitForSemaphore(VkSemaphore semaphore, uint64_t value) {
    std::deque<TimelineHostSyncPoint>* sync_points = vvl::Find(host_waitable_semaphores_, semaphore);
    if (!sync_points) {
        return;
    }
    auto matching_sync_point = [value](const TimelineHostSyncPoint& sync_point) { return sync_point.timeline_value >= value; };
    auto sync_point_it = std::find_if(sync_points->begin(), sync_points->end(), matching_sync_point);
    if (sync_point_it == sync_points->end()) {
        return;
    }

    const TimelineHostSyncPoint& sync_point = *sync_point_it;
    const QueueState& queue_state = GetQueueState(sync_point.queue_id);

    // TODO: specify queue sync tags argument similar to WaitForFence
    ApplyTaggedWait(sync_point.queue_id, sync_point.tag, queue_state.GetLastSynchronizedPresent(), {});

    // Remove signals before the resolving one (keep the resolving signal).
    std::vector<SignalInfo>& signals = timeline_signals_[semaphore];
    const size_t initial_signal_count = signals.size();
    vvl::erase_if(signals, [&sync_point](SignalInfo& signal) {
        return signal.first_scope.queue == sync_point.queue_id && signal.timeline_value < sync_point.timeline_value;
    });
    stats.RemoveTimelineSignals(uint32_t(initial_signal_count - signals.size()));

    // We can remove all sync points that are in the scope of current wait.
    // Subsequent attempts to synchronize on the host with already synchronized
    // timeline values will result in noop.
    sync_points->erase(sync_points->begin(), sync_point_it + 1 /* include resolving sync point too*/);
}

void SyncValidator::UpdateSyncImageMemoryBindState(uint32_t count, const VkBindImageMemoryInfo* infos) {
    for (const auto& info : vvl::make_span(infos, count)) {
        if (VK_NULL_HANDLE == info.image) continue;
        auto image_state = Get<vvl::Image>(info.image);

        // Need to protect if some VkBindMemoryStatus are not VK_SUCCESS
        if (!image_state->HasBeenBound()) continue;

        auto& sub_state = SubState(*image_state);
        if (sub_state.IsTiled()) {
            sub_state.SetOpaqueBaseAddress(*device_state);
        }
    }
}

QueueId SyncValidator::GetQueueId(VkQueue queue) const {
    for (const QueueState& queue_state : queue_states_) {
        if (queue_state.GetQueue()->VkHandle() == queue) {
            return queue_state.GetQueueId();
        }
    }
    return kQueueIdInvalid;
}

QueueState& SyncValidator::GetQueueState(QueueId queue_id) {
    assert(queue_id != kQueueIdInvalid);
    assert(queue_id < queue_id_limit_);
    return queue_states_[queue_id];
}

void SyncValidator::Created(vvl::CommandBuffer& cb_state) {
    cb_state.SetSubState(container_type, std::make_unique<CommandBufferSubState>(*this, cb_state));
}

void SyncValidator::Created(vvl::Swapchain& swapchain_state) {
    swapchain_state.SetSubState(container_type, std::make_unique<SwapchainSubState>(swapchain_state));
}

void SyncValidator::Created(vvl::Image& image_state) {
    image_state.SetSubState(container_type, std::make_unique<ImageSubState>(image_state));
}

void SyncValidator::PreCallRecordDestroyBuffer(VkDevice device, VkBuffer buffer, const VkAllocationCallbacks* pAllocator,
                                               const RecordObject& record_obj) {
    if (const auto buffer_state = Get<vvl::Buffer>(buffer)) {
        const VkDeviceSize base_address = ResourceBaseAddress(*buffer_state);
        const AccessRange buffer_range(base_address, base_address + buffer_state->GetSize());
        for (const auto& batch : GetAllQueueBatchContexts()) {
            batch->OnResourceDestroyed(buffer_range);
            batch->Trim();
        }
    }
}

void SyncValidator::PreCallRecordDestroyImage(VkDevice device, VkImage image, const VkAllocationCallbacks* pAllocator,
                                              const RecordObject& record_obj) {
    if (const auto image_state = Get<vvl::Image>(image)) {
        for (const auto& batch : GetAllQueueBatchContexts()) {
            const auto& sub_state = SubState(*image_state);
            ImageRangeGen range_gen = sub_state.MakeImageRangeGen(image_state->full_range, false);
            for (; range_gen->non_empty(); ++range_gen) {
                const AccessRange subresource_range = *range_gen;
                batch->OnResourceDestroyed(subresource_range);
            }
            batch->Trim();
        }
    }
}

void SyncValidator::PreCallRecordDestroySwapchainKHR(VkDevice device, VkSwapchainKHR swapchain,
                                                     const VkAllocationCallbacks* pAllocator, const RecordObject& record_obj) {
    for (const auto& batch : GetAllQueueBatchContexts()) {
        batch->last_synchronized_present.OnDestroySwapchain(swapchain);
    }
}

bool SyncValidator::PreCallValidateCmdCopyBuffer(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkBuffer dstBuffer,
                                                 uint32_t regionCount, const VkBufferCopy* pRegions,
                                                 const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    // If we have no previous accesses, we have no hazards
    auto src_buffer = Get<vvl::Buffer>(srcBuffer);
    auto dst_buffer = Get<vvl::Buffer>(dstBuffer);

    for (const auto [region_index, copy_region] : vvl::enumerate(pRegions, regionCount)) {
        if (src_buffer) {
            const AccessRange src_range = MakeRange(*src_buffer, copy_region.srcOffset, copy_region.size);
            auto hazard = context.DetectHazard(*src_buffer, SYNC_COPY_TRANSFER_READ, src_range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, srcBuffer);
                const std::string error = error_messages_.BufferCopyError(hazard, cb_context, error_obj.location.function,
                                                                          FormatHandle(srcBuffer), region_index, src_range);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
        if (dst_buffer) {
            const AccessRange dst_range = MakeRange(*dst_buffer, copy_region.dstOffset, copy_region.size);
            auto hazard = context.DetectHazard(*dst_buffer, SYNC_COPY_TRANSFER_WRITE, dst_range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, dstBuffer);
                const std::string error = error_messages_.BufferCopyError(hazard, cb_context, error_obj.location.function,
                                                                          FormatHandle(dstBuffer), region_index, dst_range);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
        if (skip) {
            break;
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdCopyBuffer2(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2* pCopyBufferInfo,
                                                  const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    // If we have no previous accesses, we have no hazards
    auto src_buffer = Get<vvl::Buffer>(pCopyBufferInfo->srcBuffer);
    auto dst_buffer = Get<vvl::Buffer>(pCopyBufferInfo->dstBuffer);

    for (const auto [region_index, copy_region] : vvl::enumerate(pCopyBufferInfo->pRegions, pCopyBufferInfo->regionCount)) {
        if (src_buffer) {
            const AccessRange src_range = MakeRange(*src_buffer, copy_region.srcOffset, copy_region.size);
            auto hazard = context.DetectHazard(*src_buffer, SYNC_COPY_TRANSFER_READ, src_range);
            if (hazard.IsHazard()) {
                // TODO -- add tag information to log msg when useful.
                // TODO: there are no tests for this error
                const LogObjectList objlist(commandBuffer, pCopyBufferInfo->srcBuffer);
                const std::string error =
                    error_messages_.BufferCopyError(hazard, cb_context, error_obj.location.function,
                                                    FormatHandle(pCopyBufferInfo->srcBuffer), region_index, src_range);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
        if (dst_buffer && !skip) {
            const AccessRange dst_range = MakeRange(*dst_buffer, copy_region.dstOffset, copy_region.size);
            auto hazard = context.DetectHazard(*dst_buffer, SYNC_COPY_TRANSFER_WRITE, dst_range);
            if (hazard.IsHazard()) {
                // TODO: there are no tests for this error
                const LogObjectList objlist(commandBuffer, pCopyBufferInfo->dstBuffer);
                const std::string error =
                    error_messages_.BufferCopyError(hazard, cb_context, error_obj.location.function,
                                                    FormatHandle(pCopyBufferInfo->dstBuffer), region_index, dst_range);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
        if (skip) break;
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdCopyBuffer2KHR(VkCommandBuffer commandBuffer, const VkCopyBufferInfo2KHR* pCopyBufferInfo,
                                                     const ErrorObject& error_obj) const {
    return PreCallValidateCmdCopyBuffer2(commandBuffer, pCopyBufferInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdCopyImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                                const VkImageCopy* pRegions, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto src_image = Get<vvl::Image>(srcImage);
    auto dst_image = Get<vvl::Image>(dstImage);
    for (const auto [region_index, copy_region] : vvl::enumerate(pRegions, regionCount)) {
        if (src_image) {
            auto hazard = context.DetectHazard(*src_image, RangeFromLayers(copy_region.srcSubresource), copy_region.srcOffset,
                                               copy_region.extent, SYNC_COPY_TRANSFER_READ);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, srcImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(srcImage), region_index,
                    copy_region.srcOffset, copy_region.extent, copy_region.srcSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }

        if (dst_image) {
            auto hazard = context.DetectHazard(*dst_image, RangeFromLayers(copy_region.dstSubresource), copy_region.dstOffset,
                                               copy_region.extent, SYNC_COPY_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, dstImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(dstImage), region_index,
                    copy_region.dstOffset, copy_region.extent, copy_region.dstSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
        if (skip) {
            break;
        }
    }

    return skip;
}

bool SyncValidator::PreCallValidateCmdCopyImage2(VkCommandBuffer commandBuffer, const VkCopyImageInfo2* pCopyImageInfo,
                                                 const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto src_image = Get<vvl::Image>(pCopyImageInfo->srcImage);
    auto dst_image = Get<vvl::Image>(pCopyImageInfo->dstImage);

    for (const auto [region_index, copy_region] : vvl::enumerate(pCopyImageInfo->pRegions, pCopyImageInfo->regionCount)) {
        if (src_image) {
            auto hazard = context.DetectHazard(*src_image, RangeFromLayers(copy_region.srcSubresource), copy_region.srcOffset,
                                               copy_region.extent, SYNC_COPY_TRANSFER_READ);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, pCopyImageInfo->srcImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(pCopyImageInfo->srcImage), region_index,
                    copy_region.srcOffset, copy_region.extent, copy_region.srcSubresource);
                // TODO: this error not covered by the test
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }

        if (dst_image) {
            auto hazard = context.DetectHazard(*dst_image, RangeFromLayers(copy_region.dstSubresource), copy_region.dstOffset,
                                               copy_region.extent, SYNC_COPY_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, pCopyImageInfo->dstImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(pCopyImageInfo->dstImage), region_index,
                    copy_region.dstOffset, copy_region.extent, copy_region.dstSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
            if (skip) break;
        }
    }

    return skip;
}

bool SyncValidator::PreCallValidateCmdCopyImage2KHR(VkCommandBuffer commandBuffer, const VkCopyImageInfo2KHR* pCopyImageInfo,
                                                    const ErrorObject& error_obj) const {
    return PreCallValidateCmdCopyImage2(commandBuffer, pCopyImageInfo, error_obj);
}

bool SyncValidator::ValidateCmdPipelineBarrier(const CommandBufferAccessContext& cb_context, const BarrierSet& barrier_set,
                                               const Location& loc) const {
    bool skip = false;
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    // Validate Image Layout transitions
    for (const auto& image_barrier : barrier_set.image_barriers) {
        if (!image_barrier.layout_transition) {
            continue;
        }
        const vvl::Image& image_state = *image_barrier.image;
        const bool can_transition_depth_slices =
            CanTransitionDepthSlices(cb_context.GetSyncState().extensions, image_state.GetImageType(), image_state.create_flags);
        const auto hazard = context.DetectImageBarrierHazard(
            image_state, image_barrier.barrier.src_exec_scope.exec_scope, image_barrier.barrier.src_access_scope,
            image_barrier.subresource_range, can_transition_depth_slices, AccessContext::kDetectAll);
        if (hazard.IsHazard()) {
            LogObjectList objlist(cb_context.GetCBState().Handle(), image_state.Handle());
            const SyncValidator& sync_state = cb_context.GetSyncState();
            const std::string resource_description = sync_state.FormatHandle(image_state.Handle());
            const std::string error =
                sync_state.error_messages_.ImageBarrierError(hazard, cb_context, loc.function, resource_description, image_barrier);
            skip |= sync_state.SyncError(hazard.Hazard(), objlist, loc, error);
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdPipelineBarrier(
    VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
    VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,
    uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
    const VkImageMemoryBarrier* pImageMemoryBarriers, const ErrorObject& error_obj) const {
    bool skip = false;

    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const VkQueueFlags queue_flags = cb_access_context.GetQueueFlags();

    const SyncExecScope src_exec_scope(SyncExecScope::MakeSrc(queue_flags, srcStageMask));
    const SyncExecScope dst_exec_scope(SyncExecScope::MakeDst(queue_flags, dstStageMask));
    const BarrierSet barrier_set(*this, src_exec_scope, dst_exec_scope, memoryBarrierCount, pMemoryBarriers,
                                 bufferMemoryBarrierCount, pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);

    skip |= ValidateCmdPipelineBarrier(cb_access_context, barrier_set, error_obj.location);
    stats.OnBarrierCommand(memoryBarrierCount, bufferMemoryBarrierCount, imageMemoryBarrierCount,
                           barrier_set.execution_dependency_barrier_count);
    return skip;
}

void SyncValidator::RecordCmdPipelineBarrier(CommandBufferAccessContext& cb_context, BarrierSet&& barrier_set,
                                             const Location& loc) const {
    const ResourceUsageTag tag = cb_context.NextCommandTag(loc.function);
    for (const SyncBufferBarrier& buffer_barrier : barrier_set.buffer_barriers) {
        cb_context.AddCommandHandle(tag, buffer_barrier.buffer->Handle());
    }
    for (SyncImageBarrier& image_barrier : barrier_set.image_barriers) {
        if (image_barrier.layout_transition) {
            const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, image_barrier.image->Handle());
            image_barrier.handle_index = tag_ex.handle_index;
        }
    }
    ApplyBarrier(cb_context, barrier_set, tag);
    auto sync_op = std::make_shared<SyncOpPipelineBarrier>(std::move(barrier_set));
    cb_context.AddSyncOp(tag, std::move(sync_op));
}

void SyncValidator::PostCallRecordCmdPipelineBarrier(
    VkCommandBuffer commandBuffer, VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
    VkDependencyFlags dependencyFlags, uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,
    uint32_t bufferMemoryBarrierCount, const VkBufferMemoryBarrier* pBufferMemoryBarriers, uint32_t imageMemoryBarrierCount,
    const VkImageMemoryBarrier* pImageMemoryBarriers, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope src_exec_scope(SyncExecScope::MakeSrc(queue_flags, srcStageMask));
    const SyncExecScope dst_exec_scope(SyncExecScope::MakeDst(queue_flags, dstStageMask));

    BarrierSet barrier_set(*this, src_exec_scope, dst_exec_scope, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount,
                           pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);
    RecordCmdPipelineBarrier(cb_context, std::move(barrier_set), record_obj.location);
}

bool SyncValidator::PreCallValidateCmdPipelineBarrier2KHR(VkCommandBuffer commandBuffer, const VkDependencyInfoKHR* pDependencyInfo,
                                                          const ErrorObject& error_obj) const {
    return PreCallValidateCmdPipelineBarrier2(commandBuffer, pDependencyInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo,
                                                       const ErrorObject& error_obj) const {
    bool skip = false;

    if (!pDependencyInfo) {
        return skip;
    }
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const VkQueueFlags queue_flags = cb_access_context.GetQueueFlags();

    const BarrierSet barrier_set(*this, queue_flags, *pDependencyInfo);

    skip |= ValidateCmdPipelineBarrier(cb_access_context, barrier_set, error_obj.location);
    stats.OnBarrierCommand(pDependencyInfo->memoryBarrierCount, pDependencyInfo->bufferMemoryBarrierCount,
                           pDependencyInfo->imageMemoryBarrierCount, barrier_set.execution_dependency_barrier_count);
    return skip;
}

void SyncValidator::PostCallRecordCmdPipelineBarrier2KHR(VkCommandBuffer commandBuffer, const VkDependencyInfoKHR* pDependencyInfo,
                                                         const RecordObject& record_obj) {
    PostCallRecordCmdPipelineBarrier2(commandBuffer, pDependencyInfo, record_obj);
}

void SyncValidator::PostCallRecordCmdPipelineBarrier2(VkCommandBuffer commandBuffer, const VkDependencyInfo* pDependencyInfo,
                                                      const RecordObject& record_obj) {
    if (!pDependencyInfo) {
        return;
    }
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    BarrierSet barrier_set(*this, queue_flags, *pDependencyInfo);

    RecordCmdPipelineBarrier(cb_context, std::move(barrier_set), record_obj.location);
}

void SyncValidator::FinishDeviceSetup(const VkDeviceCreateInfo* pCreateInfo, const Location& loc) {
    // The state tracker sets up the device state
    BaseClass::FinishDeviceSetup(pCreateInfo, loc);

    // Returns queues in the same order as advertised by the driver.
    // This allows to have deterministic QueueId between runs that simplifies debugging.
    auto get_sorted_queues = [this]() {
        std::vector<std::shared_ptr<vvl::Queue>> queues;
        device_state->ForEachShared<vvl::Queue>(
            [&queues](const std::shared_ptr<vvl::Queue>& queue) { queues.emplace_back(queue); });
        std::sort(queues.begin(), queues.end(), [](const auto& q1, const auto& q2) {
            return (q1->queue_family_index < q2->queue_family_index) ||
                   (q1->queue_family_index == q2->queue_family_index && q1->queue_index < q2->queue_index);
        });
        return queues;
    };
    queue_states_.reserve(device_state->Count<vvl::Queue>());
    for (const auto& queue : get_sorted_queues()) {
        queue_states_.emplace_back(QueueState(queue, queue_id_limit_++));
    }

    const auto env_debug_command_number = GetEnvironment("VK_SYNCVAL_DEBUG_COMMAND_NUMBER");
    if (!env_debug_command_number.empty()) {
        debug_command_number = static_cast<uint32_t>(std::stoul(env_debug_command_number));
    }
    const auto env_debug_reset_count = GetEnvironment("VK_SYNCVAL_DEBUG_RESET_COUNT");
    if (!env_debug_reset_count.empty()) {
        debug_reset_count = static_cast<uint32_t>(std::stoul(env_debug_reset_count));
    }
    debug_cmdbuf_pattern = GetEnvironment("VK_SYNCVAL_DEBUG_CMDBUF_PATTERN");
    text::ToLower(debug_cmdbuf_pattern);
}

void SyncValidator::PreCallRecordDestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator,
                                               const RecordObject& record_obj) {
    queue_states_.clear();
    binary_signals_.clear();
    timeline_signals_.clear();
    waitable_fences_.clear();
    host_waitable_semaphores_.clear();
}

void SyncValidator::PostCallRecordCreateSemaphore(VkDevice device, const VkSemaphoreCreateInfo* pCreateInfo,
                                                  const VkAllocationCallbacks* pAllocator, VkSemaphore* pSemaphore,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    assert(!vvl::Contains(timeline_signals_, *pSemaphore));
}

void SyncValidator::PreCallRecordDestroySemaphore(VkDevice device, VkSemaphore semaphore, const VkAllocationCallbacks* pAllocator,
                                                  const RecordObject& record_obj) {
    if (auto sem_state = Get<vvl::Semaphore>(semaphore); sem_state && (sem_state->type == VK_SEMAPHORE_TYPE_TIMELINE)) {
        if (auto it = timeline_signals_.find(semaphore); it != timeline_signals_.end()) {
            stats.RemoveTimelineSignals((uint32_t)it->second.size());
            timeline_signals_.erase(it);
        }
    }
}

bool SyncValidator::ValidateBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
                                            const VkSubpassBeginInfo* pSubpassBeginInfo, const ErrorObject& error_obj) const {
    bool skip = false;
    if (!pRenderPassBegin) {
        return skip;
    }
    auto rp_state = Get<vvl::RenderPass>(pRenderPassBegin->renderPass);
    if (!rp_state) {
        return skip;
    }
    auto fb_state = Get<vvl::Framebuffer>(pRenderPassBegin->framebuffer);
    if (!fb_state) {
        return skip;
    }

    const std::vector<std::shared_ptr<const vvl::ImageView>> attachments =
        device_state->GetAttachmentViews(*pRenderPassBegin, *fb_state);
    if (attachments.empty()) {
        return skip;
    }

    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    const uint32_t subpass_zero = 0;
    const uint32_t view_mask = rp_state->create_info.pSubpasses[0].viewMask;

    // Construct the state to validate against (since validation is const and RecordCmdBeginRenderPass hasn't happened yet).
    AccessContext temp_context(cb_context.GetSyncState());

    // TODO: investigate if using nullptr in InitFrom is safe (this just follows the initial implementation - it assumes
    // that array of subpass dependencies won't be indexed, but it's not obvious).
    temp_context.InitFrom(subpass_zero, cb_context.GetQueueFlags(), rp_state->subpass_dependency_infos, nullptr,
                          cb_context.GetCurrentAccessContext());

    // Validate attachment operations
    const uint32_t render_pass_instance_id = cb_context.GetCurrentRenderPassInstanceId();

    // Since the isn't a valid RenderPassAccessContext until Record, needs to create the view/generator list.
    // We could limit this by predicating on whether subpass 0 uses the attachment if it is too expensive
    // to create the full list redundantly here. More broadly we could look at thread specific state shared
    // between Validate and Record as is done for other heavyweight operations.
    const AttachmentViewGenVector view_gens =
        RenderPassAccessContext::CreateAttachmentViewGen(pRenderPassBegin->renderArea, attachments);

    // Check if any of the layout transitions are hazardous
    skip |= RenderPassAccessContext::ValidateLayoutTransitions(cb_context, temp_context, *rp_state, render_pass_instance_id,
                                                               subpass_zero, view_mask, view_gens, error_obj.location.function);

    // Validate load operations if there were no layout transition hazards
    if (!skip) {
        RenderPassAccessContext::RecordLayoutTransitions(*rp_state, subpass_zero, view_gens, kInvalidTag, temp_context);
        skip |= RenderPassAccessContext::ValidateLoadOperation(cb_context, temp_context, *rp_state, render_pass_instance_id,
                                                               subpass_zero, view_mask, view_gens, error_obj.location.function);
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdBeginRenderPass(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
                                                      VkSubpassContents contents, const ErrorObject& error_obj) const {
    VkSubpassBeginInfo subpass_begin_info = vku::InitStructHelper();
    subpass_begin_info.contents = contents;
    return ValidateBeginRenderPass(commandBuffer, pRenderPassBegin, &subpass_begin_info, error_obj);
}

bool SyncValidator::PreCallValidateCmdBeginRenderPass2(VkCommandBuffer commandBuffer, const VkRenderPassBeginInfo* pRenderPassBegin,
                                                       const VkSubpassBeginInfo* pSubpassBeginInfo,
                                                       const ErrorObject& error_obj) const {
    return ValidateBeginRenderPass(commandBuffer, pRenderPassBegin, pSubpassBeginInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdBeginRenderPass2KHR(VkCommandBuffer commandBuffer,
                                                          const VkRenderPassBeginInfo* pRenderPassBegin,
                                                          const VkSubpassBeginInfo* pSubpassBeginInfo,
                                                          const ErrorObject& error_obj) const {
    return PreCallValidateCmdBeginRenderPass2(commandBuffer, pRenderPassBegin, pSubpassBeginInfo, error_obj);
}

bool SyncValidator::ValidateCmdNextSubpass(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo* pSubpassBeginInfo,
                                           const VkSubpassEndInfo* pSubpassEndInfo, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const RenderPassAccessContext* renderpass_context = cb_context.GetCurrentRenderPassContext();
    if (!renderpass_context) {
        return skip;
    }
    skip |= renderpass_context->ValidateNextSubpass(cb_context, error_obj.location.function);
    return skip;
}

bool SyncValidator::PreCallValidateCmdNextSubpass(VkCommandBuffer commandBuffer, VkSubpassContents contents,
                                                  const ErrorObject& error_obj) const {
    // Convert to a NextSubpass2
    VkSubpassBeginInfo subpass_begin_info = vku::InitStructHelper();
    subpass_begin_info.contents = contents;
    VkSubpassEndInfo subpass_end_info = vku::InitStructHelper();
    return ValidateCmdNextSubpass(commandBuffer, &subpass_begin_info, &subpass_end_info, error_obj);
}

bool SyncValidator::PreCallValidateCmdNextSubpass2KHR(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo* pSubpassBeginInfo,
                                                      const VkSubpassEndInfo* pSubpassEndInfo, const ErrorObject& error_obj) const {
    return PreCallValidateCmdNextSubpass2(commandBuffer, pSubpassBeginInfo, pSubpassEndInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdNextSubpass2(VkCommandBuffer commandBuffer, const VkSubpassBeginInfo* pSubpassBeginInfo,
                                                   const VkSubpassEndInfo* pSubpassEndInfo, const ErrorObject& error_obj) const {
    return ValidateCmdNextSubpass(commandBuffer, pSubpassBeginInfo, pSubpassEndInfo, error_obj);
}

bool SyncValidator::ValidateCmdEndRenderPass(VkCommandBuffer commandBuffer, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const RenderPassAccessContext* renderpass_context = cb_context.GetCurrentRenderPassContext();
    if (!renderpass_context) {
        return skip;
    }
    skip |= renderpass_context->ValidateEndRenderPass(cb_context, error_obj.location.function);
    return skip;
}

bool SyncValidator::PreCallValidateCmdEndRenderPass(VkCommandBuffer commandBuffer, const ErrorObject& error_obj) const {
    return ValidateCmdEndRenderPass(commandBuffer, error_obj);
}

bool SyncValidator::PreCallValidateCmdEndRenderPass2(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassEndInfo,
                                                     const ErrorObject& error_obj) const {
    return ValidateCmdEndRenderPass(commandBuffer, error_obj);
}

bool SyncValidator::PreCallValidateCmdEndRenderPass2KHR(VkCommandBuffer commandBuffer, const VkSubpassEndInfo* pSubpassEndInfo,
                                                        const ErrorObject& error_obj) const {
    return PreCallValidateCmdEndRenderPass2(commandBuffer, pSubpassEndInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdBeginRenderingKHR(VkCommandBuffer commandBuffer, const VkRenderingInfoKHR* pRenderingInfo,
                                                        const ErrorObject& error_obj) const {
    return PreCallValidateCmdBeginRendering(commandBuffer, pRenderingInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo,
                                                     const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    if (!pRenderingInfo) return skip;

    vvl::TlsGuard<BeginRenderingCmdState> cmd_state(&skip, std::move(cb_state));
    cmd_state->AddRenderingInfo(*this, *pRenderingInfo);

    // We need to set skip, because the TlsGuard destructor is looking at the skip value for RAII cleanup.
    skip |= GetAccessContext(*cmd_state->cb_state).ValidateBeginRendering(error_obj, *cmd_state);
    return skip;
}

void SyncValidator::PostCallRecordCmdBeginRenderingKHR(VkCommandBuffer commandBuffer, const VkRenderingInfoKHR* pRenderingInfo,
                                                       const RecordObject& record_obj) {
    PostCallRecordCmdBeginRendering(commandBuffer, pRenderingInfo, record_obj);
}

void SyncValidator::PostCallRecordCmdBeginRendering(VkCommandBuffer commandBuffer, const VkRenderingInfo* pRenderingInfo,
                                                    const RecordObject& record_obj) {
    vvl::TlsGuard<BeginRenderingCmdState> cmd_state;

    assert(cmd_state && cmd_state->cb_state && (cmd_state->cb_state->VkHandle() == commandBuffer));
    // Note: for fine grain locking need to to something other than cast.
    auto cb_state = std::const_pointer_cast<vvl::CommandBuffer>(cmd_state->cb_state);
    GetAccessContext(*cb_state).RecordBeginRendering(*cmd_state, record_obj.location);
}

bool SyncValidator::PreCallValidateCmdEndRenderingKHR(VkCommandBuffer commandBuffer, const ErrorObject& error_obj) const {
    return PreCallValidateCmdEndRendering(commandBuffer, error_obj);
}

bool SyncValidator::PreCallValidateCmdEndRendering(VkCommandBuffer commandBuffer, const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    skip |= GetAccessContext(*cb_state).ValidateEndRendering(error_obj);
    return skip;
}

void SyncValidator::PreCallRecordCmdEndRenderingKHR(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    PreCallRecordCmdEndRendering(commandBuffer, record_obj);
}

void SyncValidator::PreCallRecordCmdEndRendering(VkCommandBuffer commandBuffer, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    GetAccessContext(*cb_state).RecordEndRendering(record_obj);
}

template <typename RegionType>
bool SyncValidator::ValidateCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage,
                                                 VkImageLayout dstImageLayout, uint32_t regionCount, const RegionType* pRegions,
                                                 const Location& loc) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto src_buffer = Get<vvl::Buffer>(srcBuffer);
    auto dst_image = Get<vvl::Image>(dstImage);

    for (const auto [region_index, copy_region] : vvl::enumerate(pRegions, regionCount)) {
        HazardResult hazard;
        if (dst_image) {
            if (src_buffer) {
                AccessRange src_range = MakeRange(copy_region.bufferOffset, dst_image->GetBufferSizeFromCopyImage(copy_region));
                hazard = context.DetectHazard(*src_buffer, SYNC_COPY_TRANSFER_READ, src_range);
                if (hazard.IsHazard()) {
                    // PHASE1 TODO -- add tag information to log msg when useful.
                    const LogObjectList objlist(commandBuffer, srcBuffer);
                    const std::string error = error_messages_.BufferCopyError(hazard, cb_access_context, loc.function,
                                                                              FormatHandle(srcBuffer), region_index, src_range);
                    skip |= SyncError(hazard.Hazard(), objlist, loc, error);
                }
            }

            hazard = context.DetectHazard(*dst_image, RangeFromLayers(copy_region.imageSubresource), copy_region.imageOffset,
                                          copy_region.imageExtent, SYNC_COPY_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, dstImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, loc.function, FormatHandle(dstImage), region_index, copy_region.imageOffset,
                    copy_region.imageExtent, copy_region.imageSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, loc, error);
            }
            if (skip) break;
        }
        if (skip) break;
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdCopyBufferToImage(VkCommandBuffer commandBuffer, VkBuffer srcBuffer, VkImage dstImage,
                                                        VkImageLayout dstImageLayout, uint32_t regionCount,
                                                        const VkBufferImageCopy* pRegions, const ErrorObject& error_obj) const {
    return ValidateCmdCopyBufferToImage(commandBuffer, srcBuffer, dstImage, dstImageLayout, regionCount, pRegions,
                                        error_obj.location);
}

bool SyncValidator::PreCallValidateCmdCopyBufferToImage2KHR(VkCommandBuffer commandBuffer,
                                                            const VkCopyBufferToImageInfo2KHR* pCopyBufferToImageInfo,
                                                            const ErrorObject& error_obj) const {
    return PreCallValidateCmdCopyBufferToImage2(commandBuffer, pCopyBufferToImageInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdCopyBufferToImage2(VkCommandBuffer commandBuffer,
                                                         const VkCopyBufferToImageInfo2* pCopyBufferToImageInfo,
                                                         const ErrorObject& error_obj) const {
    return ValidateCmdCopyBufferToImage(commandBuffer, pCopyBufferToImageInfo->srcBuffer, pCopyBufferToImageInfo->dstImage,
                                        pCopyBufferToImageInfo->dstImageLayout, pCopyBufferToImageInfo->regionCount,
                                        pCopyBufferToImageInfo->pRegions, error_obj.location.dot(Field::pCopyBufferToImageInfo));
}

template <typename RegionType>
bool SyncValidator::ValidateCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                 VkBuffer dstBuffer, uint32_t regionCount, const RegionType* pRegions,
                                                 const Location& loc) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto src_image = Get<vvl::Image>(srcImage);
    auto dst_buffer = Get<vvl::Buffer>(dstBuffer);
    const VkDeviceMemory dst_memory = (dst_buffer && !dst_buffer->sparse) ? dst_buffer->MemoryState()->VkHandle() : VK_NULL_HANDLE;
    for (const auto [region_index, copy_region] : vvl::enumerate(pRegions, regionCount)) {
        if (src_image) {
            auto hazard = context.DetectHazard(*src_image, RangeFromLayers(copy_region.imageSubresource), copy_region.imageOffset,
                                               copy_region.imageExtent, SYNC_COPY_TRANSFER_READ);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, srcImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, loc.function, FormatHandle(srcImage), region_index, copy_region.imageOffset,
                    copy_region.imageExtent, copy_region.imageSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, loc, error);
            }
            if (dst_memory != VK_NULL_HANDLE) {
                AccessRange dst_range = MakeRange(copy_region.bufferOffset, src_image->GetBufferSizeFromCopyImage(copy_region));
                hazard = context.DetectHazard(*dst_buffer, SYNC_COPY_TRANSFER_WRITE, dst_range);
                if (hazard.IsHazard()) {
                    const LogObjectList objlist(commandBuffer, dstBuffer);
                    const std::string error = error_messages_.BufferCopyError(hazard, cb_access_context, loc.function,
                                                                              FormatHandle(dstBuffer), region_index, dst_range);
                    skip |= SyncError(hazard.Hazard(), objlist, loc, error);
                }
            }
        }
        if (skip) break;
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdCopyImageToBuffer(VkCommandBuffer commandBuffer, VkImage srcImage,
                                                        VkImageLayout srcImageLayout, VkBuffer dstBuffer, uint32_t regionCount,
                                                        const VkBufferImageCopy* pRegions, const ErrorObject& error_obj) const {
    return ValidateCmdCopyImageToBuffer(commandBuffer, srcImage, srcImageLayout, dstBuffer, regionCount, pRegions,
                                        error_obj.location);
}

bool SyncValidator::PreCallValidateCmdCopyImageToBuffer2KHR(VkCommandBuffer commandBuffer,
                                                            const VkCopyImageToBufferInfo2KHR* pCopyImageToBufferInfo,
                                                            const ErrorObject& error_obj) const {
    return PreCallValidateCmdCopyImageToBuffer2(commandBuffer, pCopyImageToBufferInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdCopyImageToBuffer2(VkCommandBuffer commandBuffer,
                                                         const VkCopyImageToBufferInfo2* pCopyImageToBufferInfo,
                                                         const ErrorObject& error_obj) const {
    return ValidateCmdCopyImageToBuffer(commandBuffer, pCopyImageToBufferInfo->srcImage, pCopyImageToBufferInfo->srcImageLayout,
                                        pCopyImageToBufferInfo->dstBuffer, pCopyImageToBufferInfo->regionCount,
                                        pCopyImageToBufferInfo->pRegions, error_obj.location.dot(Field::pCopyImageToBufferInfo));
}

template <typename RegionType>
bool SyncValidator::ValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                         VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                         const RegionType* pRegions, VkFilter filter, const Location& loc) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto src_image = Get<vvl::Image>(srcImage);
    auto dst_image = Get<vvl::Image>(dstImage);

    for (const auto [region_index, blit_region] : vvl::enumerate(pRegions, regionCount)) {
        if (src_image) {
            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))};
            auto hazard = context.DetectHazard(*src_image, RangeFromLayers(blit_region.srcSubresource), offset, extent,
                                               SYNC_BLIT_TRANSFER_READ);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, srcImage);
                const std::string error =
                    error_messages_.ImageCopyResolveBlitError(hazard, cb_access_context, loc.function, FormatHandle(srcImage),
                                                              region_index, offset, extent, blit_region.srcSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, loc, error);
            }
        }

        if (dst_image) {
            VkOffset3D 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)};
            VkExtent3D 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))};
            auto hazard = context.DetectHazard(*dst_image, RangeFromLayers(blit_region.dstSubresource), offset, extent,
                                               SYNC_BLIT_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, dstImage);
                const std::string error =
                    error_messages_.ImageCopyResolveBlitError(hazard, cb_access_context, loc.function, FormatHandle(dstImage),
                                                              region_index, offset, extent, blit_region.dstSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, loc, error);
            }
            if (skip) break;
        }
    }

    return skip;
}

bool SyncValidator::PreCallValidateCmdBlitImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                                const VkImageBlit* pRegions, VkFilter filter, const ErrorObject& error_obj) const {
    return ValidateCmdBlitImage(commandBuffer, srcImage, srcImageLayout, dstImage, dstImageLayout, regionCount, pRegions, filter,
                                error_obj.location);
}

bool SyncValidator::PreCallValidateCmdBlitImage2KHR(VkCommandBuffer commandBuffer, const VkBlitImageInfo2KHR* pBlitImageInfo,
                                                    const ErrorObject& error_obj) const {
    return PreCallValidateCmdBlitImage2(commandBuffer, pBlitImageInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdBlitImage2(VkCommandBuffer commandBuffer, const VkBlitImageInfo2* pBlitImageInfo,
                                                 const ErrorObject& error_obj) const {
    return ValidateCmdBlitImage(commandBuffer, pBlitImageInfo->srcImage, pBlitImageInfo->srcImageLayout, pBlitImageInfo->dstImage,
                                pBlitImageInfo->dstImageLayout, pBlitImageInfo->regionCount, pBlitImageInfo->pRegions,
                                pBlitImageInfo->filter, error_obj.location.dot(Field::pBlitImageInfo));
}

bool SyncValidator::ValidateIndirectBuffer(const CommandBufferAccessContext& cb_context, const AccessContext& context,
                                           const VkDeviceSize struct_size, const VkBuffer buffer, const VkDeviceSize offset,
                                           const uint32_t drawCount, const uint32_t stride, const Location& loc) const {
    bool skip = false;
    if (drawCount == 0) return skip;

    auto buf_state = Get<vvl::Buffer>(buffer);
    VkDeviceSize size = struct_size;
    if (drawCount == 1 || stride == size) {
        if (drawCount > 1) size *= drawCount;
        const AccessRange range = MakeRange(offset, size);
        auto hazard = context.DetectHazard(*buf_state, SYNC_DRAW_INDIRECT_INDIRECT_COMMAND_READ, range);
        if (hazard.IsHazard()) {
            const LogObjectList objlist(cb_context.GetCBState().Handle(), buf_state->Handle());
            const std::string resource_description = "indirect " + FormatHandle(buffer);
            const auto error = error_messages_.BufferError(hazard, cb_context, loc.function, resource_description, range);
            skip |= SyncError(hazard.Hazard(), objlist, loc, error);
        }
    } else {
        for (uint32_t i = 0; i < drawCount; ++i) {
            const AccessRange range = MakeRange(offset + i * stride, size);
            auto hazard = context.DetectHazard(*buf_state, SYNC_DRAW_INDIRECT_INDIRECT_COMMAND_READ, range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(cb_context.GetCBState().Handle(), buf_state->Handle());
                const std::string resource_description = "indirect " + FormatHandle(buffer);
                const auto error = error_messages_.BufferError(hazard, cb_context, loc.function, resource_description, range);
                skip |= SyncError(hazard.Hazard(), objlist, loc, error);
                break;
            }
        }
    }
    return skip;
}

void SyncValidator::RecordIndirectBuffer(CommandBufferAccessContext& cb_context, const ResourceUsageTag tag,
                                         const VkDeviceSize struct_size, const VkBuffer buffer, const VkDeviceSize offset,
                                         const uint32_t drawCount, uint32_t stride) {
    auto buf_state = Get<vvl::Buffer>(buffer);
    auto tag_ex = buf_state ? cb_context.AddCommandHandle(tag, buf_state->Handle()) : ResourceUsageTagEx{tag};

    VkDeviceSize size = struct_size;
    AccessContext& context = cb_context.GetCurrentAccessContext();
    if (drawCount == 1 || stride == size) {
        if (drawCount > 1) size *= drawCount;
        const AccessRange range = MakeRange(offset, size);
        context.UpdateAccessState(*buf_state, SYNC_DRAW_INDIRECT_INDIRECT_COMMAND_READ, range, tag_ex);
    } else {
        for (uint32_t i = 0; i < drawCount; ++i) {
            const AccessRange range = MakeRange(offset + i * stride, size);
            context.UpdateAccessState(*buf_state, SYNC_DRAW_INDIRECT_INDIRECT_COMMAND_READ, range, tag_ex);
        }
    }
}

bool SyncValidator::ValidateCountBuffer(const CommandBufferAccessContext& cb_context, const AccessContext& context, VkBuffer buffer,
                                        VkDeviceSize offset, const Location& loc) const {
    bool skip = false;

    auto count_buf_state = Get<vvl::Buffer>(buffer);
    const AccessRange range = MakeRange(offset, 4);
    auto hazard = context.DetectHazard(*count_buf_state, SYNC_DRAW_INDIRECT_INDIRECT_COMMAND_READ, range);
    if (hazard.IsHazard()) {
        const LogObjectList objlist(cb_context.GetCBState().Handle(), count_buf_state->Handle());
        const std::string resource_description = "draw count " + FormatHandle(buffer);
        const auto error = error_messages_.BufferError(hazard, cb_context, loc.function, resource_description, range);
        skip |= SyncError(hazard.Hazard(), objlist, loc, error);
    }
    return skip;
}

void SyncValidator::RecordCountBuffer(CommandBufferAccessContext& cb_context, const ResourceUsageTag tag, VkBuffer buffer,
                                      VkDeviceSize offset) {
    auto count_buf_state = Get<vvl::Buffer>(buffer);
    const AccessRange range = MakeRange(offset, 4);
    const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, count_buf_state->Handle());
    AccessContext& context = cb_context.GetCurrentAccessContext();
    context.UpdateAccessState(*count_buf_state, SYNC_DRAW_INDIRECT_INDIRECT_COMMAND_READ, range, tag_ex);
}

bool SyncValidator::PreCallValidateCmdDispatch(VkCommandBuffer commandBuffer, uint32_t x, uint32_t y, uint32_t z,
                                               const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    skip |= GetAccessContext(*cb_state).ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDispatch(VkCommandBuffer commandBuffer, uint32_t x, uint32_t y, uint32_t z,
                                              const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, tag);
}

bool SyncValidator::PreCallValidateCmdDispatchIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                       const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    skip |= cb_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, error_obj.location);
    skip |= ValidateIndirectBuffer(cb_context, context, sizeof(VkDispatchIndirectCommand), buffer, offset, 1,
                                   sizeof(VkDispatchIndirectCommand), error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDispatchIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                      const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, tag);
    RecordIndirectBuffer(cb_access_context, tag, sizeof(VkDispatchIndirectCommand), buffer, offset, 1,
                         sizeof(VkDispatchIndirectCommand));
}

bool SyncValidator::PreCallValidateCmdDispatchBase(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY,
                                                   uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY,
                                                   uint32_t groupCountZ, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    skip |= GetAccessContext(*cb_state).ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, error_obj.location);
    return skip;
}

bool SyncValidator::PreCallValidateCmdDispatchBaseKHR(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY,
                                                      uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY,
                                                      uint32_t groupCountZ, const ErrorObject& error_obj) const {
    return PreCallValidateCmdDispatchBase(commandBuffer, baseGroupX, baseGroupY, baseGroupZ, groupCountX, groupCountY, groupCountZ,
                                          error_obj);
}

void SyncValidator::PostCallRecordCmdDispatchBase(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY,
                                                  uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY,
                                                  uint32_t groupCountZ, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);
    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_COMPUTE, tag);
}

void SyncValidator::PostCallRecordCmdDispatchBaseKHR(VkCommandBuffer commandBuffer, uint32_t baseGroupX, uint32_t baseGroupY,
                                                     uint32_t baseGroupZ, uint32_t groupCountX, uint32_t groupCountY,
                                                     uint32_t groupCountZ, const RecordObject& record_obj) {
    PostCallRecordCmdDispatchBase(commandBuffer, baseGroupX, baseGroupY, baseGroupZ, groupCountX, groupCountY, groupCountZ,
                                  record_obj);
}

bool SyncValidator::PreCallValidateCmdDraw(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount,
                                           uint32_t firstVertex, uint32_t firstInstance, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawVertex(vertexCount, firstVertex, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDraw(VkCommandBuffer commandBuffer, uint32_t vertexCount, uint32_t instanceCount,
                                          uint32_t firstVertex, uint32_t firstInstance, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawVertex(vertexCount, firstVertex, tag);
    cb_access_context.RecordDrawAttachment(tag);
}

bool SyncValidator::PreCallValidateCmdDrawIndexed(VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount,
                                                  uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance,
                                                  const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawVertexIndex(indexCount, firstIndex, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDrawIndexed(VkCommandBuffer commandBuffer, uint32_t indexCount, uint32_t instanceCount,
                                                 uint32_t firstIndex, int32_t vertexOffset, uint32_t firstInstance,
                                                 const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawVertexIndex(indexCount, firstIndex, tag);
    cb_access_context.RecordDrawAttachment(tag);
}

bool SyncValidator::PreCallValidateCmdDrawIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                   uint32_t drawCount, uint32_t stride, const ErrorObject& error_obj) const {
    bool skip = false;
    if (drawCount == 0) return skip;

    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);

    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    skip |= ValidateIndirectBuffer(cb_access_context, context, sizeof(VkDrawIndirectCommand), buffer, offset, drawCount, stride,
                                   error_obj.location);
    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // skip |= cb_access_context->ValidateDrawVertex(?, ?, error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDrawIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                  uint32_t drawCount, uint32_t stride, const RecordObject& record_obj) {
    if (drawCount == 0) {
        return;
    }
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawAttachment(tag);
    RecordIndirectBuffer(cb_access_context, tag, sizeof(VkDrawIndirectCommand), buffer, offset, drawCount, stride);

    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // cb_access_context->RecordDrawVertex(?, ?, tag);
}

bool SyncValidator::PreCallValidateCmdDrawIndexedIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                          uint32_t drawCount, uint32_t stride, const ErrorObject& error_obj) const {
    bool skip = false;
    if (drawCount == 0) return skip;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);

    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    skip |= ValidateIndirectBuffer(cb_access_context, context, sizeof(VkDrawIndexedIndirectCommand), buffer, offset, drawCount,
                                   stride, error_obj.location);

    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // skip |= cb_access_context->ValidateDrawVertexIndex(?, ?, error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDrawIndexedIndirect(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                         uint32_t drawCount, uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawAttachment(tag);
    RecordIndirectBuffer(cb_access_context, tag, sizeof(VkDrawIndexedIndirectCommand), buffer, offset, drawCount, stride);

    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // cb_access_context->RecordDrawVertexIndex(?, ?, tag);
}

bool SyncValidator::PreCallValidateCmdDrawIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                        VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                        uint32_t stride, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);

    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    skip |= ValidateIndirectBuffer(cb_access_context, context, sizeof(VkDrawIndirectCommand), buffer, offset, maxDrawCount, stride,
                                   error_obj.location);
    skip |= ValidateCountBuffer(cb_access_context, context, countBuffer, countBufferOffset, error_obj.location);

    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // skip |= cb_access_context->ValidateDrawVertex(?, ?, error_obj.location);
    return skip;
}

void SyncValidator::RecordCmdDrawIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                               VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                               uint32_t stride, Func command) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(command);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawAttachment(tag);
    RecordIndirectBuffer(cb_access_context, tag, sizeof(VkDrawIndirectCommand), buffer, offset, 1, stride);
    RecordCountBuffer(cb_access_context, tag, countBuffer, countBufferOffset);

    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // cb_access_context->RecordDrawVertex(?, ?, tag);
}

void SyncValidator::PostCallRecordCmdDrawIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                       VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                       uint32_t stride, const RecordObject& record_obj) {
    RecordCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                               record_obj.location.function);
}
bool SyncValidator::PreCallValidateCmdDrawIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                           VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                           uint32_t maxDrawCount, uint32_t stride,
                                                           const ErrorObject& error_obj) const {
    return PreCallValidateCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                               error_obj);
}

void SyncValidator::PostCallRecordCmdDrawIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                          VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                          uint32_t maxDrawCount, uint32_t stride, const RecordObject& record_obj) {
    PostCallRecordCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                       record_obj);
}

bool SyncValidator::PreCallValidateCmdDrawIndirectCountAMD(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                           VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                           uint32_t maxDrawCount, uint32_t stride,
                                                           const ErrorObject& error_obj) const {
    return PreCallValidateCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                               error_obj);
}

void SyncValidator::PostCallRecordCmdDrawIndirectCountAMD(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                          VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                          uint32_t maxDrawCount, uint32_t stride, const RecordObject& record_obj) {
    PostCallRecordCmdDrawIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                       record_obj);
}

bool SyncValidator::PreCallValidateCmdDrawIndexedIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                               VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                               uint32_t maxDrawCount, uint32_t stride,
                                                               const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    skip |= ValidateIndirectBuffer(cb_access_context, context, sizeof(VkDrawIndexedIndirectCommand), buffer, offset, maxDrawCount,
                                   stride, error_obj.location);
    skip |= ValidateCountBuffer(cb_access_context, context, countBuffer, countBufferOffset, error_obj.location);

    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // skip |= cb_access_context->ValidateDrawVertexIndex(?, ?, error_obj.location);
    return skip;
}

void SyncValidator::RecordCmdDrawIndexedIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                      VkBuffer countBuffer, VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                      uint32_t stride, Func command) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(command);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawAttachment(tag);
    RecordIndirectBuffer(cb_access_context, tag, sizeof(VkDrawIndexedIndirectCommand), buffer, offset, 1, stride);
    RecordCountBuffer(cb_access_context, tag, countBuffer, countBufferOffset);

    // TODO: Shader instrumentation support is needed to read indirect buffer content (new syncval mode)
    // cb_access_context->RecordDrawVertexIndex(?, ?, tag);
}

void SyncValidator::PostCallRecordCmdDrawIndexedIndirectCount(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                              VkBuffer countBuffer, VkDeviceSize countBufferOffset,
                                                              uint32_t maxDrawCount, uint32_t stride,
                                                              const RecordObject& record_obj) {
    RecordCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                      record_obj.location.function);
}

bool SyncValidator::PreCallValidateCmdDrawIndexedIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer,
                                                                  VkDeviceSize offset, VkBuffer countBuffer,
                                                                  VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                                  uint32_t stride, const ErrorObject& error_obj) const {
    return PreCallValidateCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount,
                                                      stride, error_obj);
}

void SyncValidator::PostCallRecordCmdDrawIndexedIndirectCountKHR(VkCommandBuffer commandBuffer, VkBuffer buffer,
                                                                 VkDeviceSize offset, VkBuffer countBuffer,
                                                                 VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                                 uint32_t stride, const RecordObject& record_obj) {
    PostCallRecordCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                              record_obj);
}

bool SyncValidator::PreCallValidateCmdDrawIndexedIndirectCountAMD(VkCommandBuffer commandBuffer, VkBuffer buffer,
                                                                  VkDeviceSize offset, VkBuffer countBuffer,
                                                                  VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                                  uint32_t stride, const ErrorObject& error_obj) const {
    return PreCallValidateCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount,
                                                      stride, error_obj);
}

void SyncValidator::PostCallRecordCmdDrawIndexedIndirectCountAMD(VkCommandBuffer commandBuffer, VkBuffer buffer,
                                                                 VkDeviceSize offset, VkBuffer countBuffer,
                                                                 VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                                 uint32_t stride, const RecordObject& record_obj) {
    PostCallRecordCmdDrawIndexedIndirectCount(commandBuffer, buffer, offset, countBuffer, countBufferOffset, maxDrawCount, stride,
                                              record_obj);
}

bool SyncValidator::PreCallValidateCmdDrawMeshTasksIndirectEXT(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                               uint32_t drawCount, uint32_t stride,
                                                               const ErrorObject& error_obj) const {
    bool skip = false;
    if (drawCount == 0) {
        return skip;
    }
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    skip |= ValidateIndirectBuffer(cb_access_context, context, sizeof(VkDrawMeshTasksIndirectCommandEXT), buffer, offset, drawCount,
                                   stride, error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDrawMeshTasksIndirectEXT(VkCommandBuffer commandBuffer, VkBuffer buffer, VkDeviceSize offset,
                                                              uint32_t drawCount, uint32_t stride, const RecordObject& record_obj) {
    if (drawCount == 0) {
        return;
    }
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawAttachment(tag);
    RecordIndirectBuffer(cb_access_context, tag, sizeof(VkDrawMeshTasksIndirectCommandEXT), buffer, offset, drawCount, stride);
}

bool SyncValidator::PreCallValidateCmdDrawMeshTasksIndirectCountEXT(VkCommandBuffer commandBuffer, VkBuffer buffer,
                                                                    VkDeviceSize offset, VkBuffer countBuffer,
                                                                    VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                                    uint32_t stride, const ErrorObject& error_obj) const {
    bool skip = false;

    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    skip |= cb_access_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_access_context.ValidateDrawAttachment(error_obj.location);
    skip |= ValidateCountBuffer(cb_access_context, context, countBuffer, countBufferOffset, error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdDrawMeshTasksIndirectCountEXT(VkCommandBuffer commandBuffer, VkBuffer buffer,
                                                                   VkDeviceSize offset, VkBuffer countBuffer,
                                                                   VkDeviceSize countBufferOffset, uint32_t maxDrawCount,
                                                                   uint32_t stride, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);

    cb_access_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_access_context.RecordDrawAttachment(tag);
    RecordCountBuffer(cb_access_context, tag, countBuffer, countBufferOffset);
}

bool SyncValidator::PreCallValidateCmdDrawMultiIndexedEXT(VkCommandBuffer commandBuffer, uint32_t drawCount,
                                                          const VkMultiDrawIndexedInfoEXT* pIndexInfo, uint32_t instanceCount,
                                                          uint32_t firstInstance, uint32_t stride, const int32_t* pVertexOffset,
                                                          const ErrorObject& error_obj) const {
    bool skip = false;
    if (!pIndexInfo) {
        return skip;
    }
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    skip |= cb_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, error_obj.location);
    skip |= cb_context.ValidateDrawAttachment(error_obj.location);
    const auto ptr = reinterpret_cast<const uint8_t*>(pIndexInfo);
    for (uint32_t i = 0; i < drawCount; i++) {
        const auto info_ptr = reinterpret_cast<const VkMultiDrawIndexedInfoEXT*>(ptr + i * stride);
        skip |= cb_context.ValidateDrawVertexIndex(info_ptr->indexCount, info_ptr->firstIndex, error_obj.location);
    }
    return skip;
}

void SyncValidator::PostCallRecordCmdDrawMultiIndexedEXT(VkCommandBuffer commandBuffer, uint32_t drawCount,
                                                         const VkMultiDrawIndexedInfoEXT* pIndexInfo, uint32_t instanceCount,
                                                         uint32_t firstInstance, uint32_t stride, const int32_t* pVertexOffset,
                                                         const RecordObject& record_obj) {
    if (!pIndexInfo) {
        return;
    }
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);

    cb_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_GRAPHICS, tag);
    cb_context.RecordDrawAttachment(tag);
    const auto ptr = reinterpret_cast<const uint8_t*>(pIndexInfo);
    for (uint32_t i = 0; i < drawCount; i++) {
        const auto info_ptr = reinterpret_cast<const VkMultiDrawIndexedInfoEXT*>(ptr + i * stride);
        cb_context.RecordDrawVertexIndex(info_ptr->indexCount, info_ptr->firstIndex, tag);
    }
}

bool SyncValidator::PreCallValidateCmdClearColorImage(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout imageLayout,
                                                      const VkClearColorValue* pColor, uint32_t rangeCount,
                                                      const VkImageSubresourceRange* pRanges, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    if (auto image_state = Get<vvl::Image>(image)) {
        for (const auto [range_index, range] : vvl::enumerate(pRanges, rangeCount)) {
            auto hazard = context.DetectHazard(*image_state, range, SYNC_CLEAR_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, image);
                const auto error = error_messages_.ImageClearError(hazard, cb_access_context, error_obj.location.function,
                                                                   FormatHandle(image), range_index, range);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdClearDepthStencilImage(VkCommandBuffer commandBuffer, VkImage image,
                                                             VkImageLayout imageLayout,
                                                             const VkClearDepthStencilValue* pDepthStencil, uint32_t rangeCount,
                                                             const VkImageSubresourceRange* pRanges,
                                                             const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    if (auto image_state = Get<vvl::Image>(image)) {
        for (const auto [range_index, range] : vvl::enumerate(pRanges, rangeCount)) {
            auto hazard = context.DetectHazard(*image_state, range, SYNC_CLEAR_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, image);
                const auto error = error_messages_.ImageClearError(hazard, cb_access_context, error_obj.location.function,
                                                                   FormatHandle(image), range_index, range);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdClearAttachments(VkCommandBuffer commandBuffer, uint32_t attachmentCount,
                                                       const VkClearAttachment* pAttachments, uint32_t rectCount,
                                                       const VkClearRect* pRects, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);

    for (const VkClearAttachment& attachment : vvl::make_span(pAttachments, attachmentCount)) {
        for (const auto [rect_index, rect] : vvl::enumerate(pRects, rectCount)) {
            skip |= GetAccessContext(*cb_state).ValidateClearAttachment(error_obj.location, attachment, rect_index, rect);
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdCopyQueryPoolResults(VkCommandBuffer commandBuffer, VkQueryPool queryPool,
                                                           uint32_t firstQuery, uint32_t queryCount, VkBuffer dstBuffer,
                                                           VkDeviceSize dstOffset, VkDeviceSize stride, VkQueryResultFlags flags,
                                                           const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto dst_buffer = Get<vvl::Buffer>(dstBuffer);

    if (dst_buffer && queryCount > 0) {
        const uint32_t query_size = (flags & VK_QUERY_RESULT_64_BIT) ? 8 : 4;
        const VkDeviceSize range_size = (queryCount - 1) * stride + query_size;
        const AccessRange range = MakeRange(dstOffset, range_size);
        auto hazard = context.DetectHazard(*dst_buffer, SYNC_COPY_TRANSFER_WRITE, range);
        if (hazard.IsHazard()) {
            const LogObjectList objlist(commandBuffer, queryPool, dstBuffer);
            const std::string resource_description = "dstBuffer " + FormatHandle(dstBuffer);
            const auto error =
                error_messages_.BufferError(hazard, cb_access_context, error_obj.location.function, resource_description, range);
            skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
        }
    }

    // TODO:Track VkQueryPool
    return skip;
}

bool SyncValidator::PreCallValidateCmdFillBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset,
                                                 VkDeviceSize size, uint32_t data, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);

    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto dst_buffer = Get<vvl::Buffer>(dstBuffer);

    if (dst_buffer) {
        const AccessRange range = MakeRange(*dst_buffer, dstOffset, size);
        auto hazard = context.DetectHazard(*dst_buffer, SYNC_CLEAR_TRANSFER_WRITE, range);
        if (hazard.IsHazard()) {
            const LogObjectList objlist(commandBuffer, dstBuffer);
            const std::string resource_description = "dstBuffer " + FormatHandle(dstBuffer);
            const auto error =
                error_messages_.BufferError(hazard, cb_access_context, error_obj.location.function, resource_description, range);
            skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdResolveImage(VkCommandBuffer commandBuffer, VkImage srcImage, VkImageLayout srcImageLayout,
                                                   VkImage dstImage, VkImageLayout dstImageLayout, uint32_t regionCount,
                                                   const VkImageResolve* pRegions, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto src_image = Get<vvl::Image>(srcImage);
    auto dst_image = Get<vvl::Image>(dstImage);

    for (const auto [region_index, resolve_region] : vvl::enumerate(pRegions, regionCount)) {
        if (src_image) {
            auto hazard = context.DetectHazard(*src_image, RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset,
                                               resolve_region.extent, SYNC_RESOLVE_TRANSFER_READ);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, srcImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(srcImage), region_index,
                    resolve_region.srcOffset, resolve_region.extent, resolve_region.srcSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }

        if (dst_image) {
            auto hazard = context.DetectHazard(*dst_image, RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset,
                                               resolve_region.extent, SYNC_RESOLVE_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, dstImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(dstImage), region_index,
                    resolve_region.dstOffset, resolve_region.extent, resolve_region.dstSubresource);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
            if (skip) break;
        }
    }

    return skip;
}

bool SyncValidator::PreCallValidateCmdResolveImage2(VkCommandBuffer commandBuffer, const VkResolveImageInfo2* pResolveImageInfo,
                                                    const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    const Location image_info_loc = error_obj.location.dot(Field::pResolveImageInfo);
    auto src_image = Get<vvl::Image>(pResolveImageInfo->srcImage);
    auto dst_image = Get<vvl::Image>(pResolveImageInfo->dstImage);

    for (const auto [region_index, resolve_region] : vvl::enumerate(pResolveImageInfo->pRegions, pResolveImageInfo->regionCount)) {
        const Location region_loc = image_info_loc.dot(Field::pRegions, region_index);
        if (src_image) {
            auto hazard = context.DetectHazard(*src_image, RangeFromLayers(resolve_region.srcSubresource), resolve_region.srcOffset,
                                               resolve_region.extent, SYNC_RESOLVE_TRANSFER_READ);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, pResolveImageInfo->srcImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(pResolveImageInfo->srcImage), region_index,
                    resolve_region.srcOffset, resolve_region.extent, resolve_region.srcSubresource);
                // TODO: this error is not covered by the test
                skip |= SyncError(hazard.Hazard(), objlist, region_loc, error);
            }
        }

        if (dst_image) {
            auto hazard = context.DetectHazard(*dst_image, RangeFromLayers(resolve_region.dstSubresource), resolve_region.dstOffset,
                                               resolve_region.extent, SYNC_RESOLVE_TRANSFER_WRITE);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, pResolveImageInfo->dstImage);
                const std::string error = error_messages_.ImageCopyResolveBlitError(
                    hazard, cb_access_context, error_obj.location.function, FormatHandle(pResolveImageInfo->dstImage), region_index,
                    resolve_region.dstOffset, resolve_region.extent, resolve_region.dstSubresource);
                // TODO: this error is not covered by the test
                skip |= SyncError(hazard.Hazard(), objlist, region_loc, error);
            }
            if (skip) break;
        }
    }

    return skip;
}

bool SyncValidator::PreCallValidateCmdResolveImage2KHR(VkCommandBuffer commandBuffer,
                                                       const VkResolveImageInfo2KHR* pResolveImageInfo,
                                                       const ErrorObject& error_obj) const {
    return PreCallValidateCmdResolveImage2(commandBuffer, pResolveImageInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdUpdateBuffer(VkCommandBuffer commandBuffer, VkBuffer dstBuffer, VkDeviceSize dstOffset,
                                                   VkDeviceSize dataSize, const void* pData, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    auto dst_buffer = Get<vvl::Buffer>(dstBuffer);

    if (dst_buffer) {
        // VK_WHOLE_SIZE not allowed
        const AccessRange range = MakeRange(dstOffset, dataSize);
        auto hazard = context.DetectHazard(*dst_buffer, SYNC_CLEAR_TRANSFER_WRITE, range);
        if (hazard.IsHazard()) {
            const LogObjectList objlist(commandBuffer, dstBuffer);
            const std::string resource_description = "dstBuffer " + FormatHandle(dstBuffer);
            const auto error =
                error_messages_.BufferError(hazard, cb_access_context, error_obj.location.function, resource_description, range);
            skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdWriteBufferMarkerAMD(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage,
                                                           VkBuffer dstBuffer, VkDeviceSize dstOffset, uint32_t marker,
                                                           const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    if (auto dst_buffer = Get<vvl::Buffer>(dstBuffer)) {
        const AccessRange range = MakeRange(dstOffset, 4);
        auto hazard = context.DetectMarkerHazard(*dst_buffer, range);
        if (hazard.IsHazard()) {
            const std::string resource_description = "dstBuffer " + FormatHandle(dstBuffer);
            const auto error =
                error_messages_.BufferError(hazard, cb_access_context, error_obj.location.function, resource_description, range);
            skip |= SyncError(hazard.Hazard(), dstBuffer, error_obj.location, error);
        }
    }
    return skip;
}

void SyncValidator::PostCallRecordCmdWriteBufferMarkerAMD(VkCommandBuffer commandBuffer, VkPipelineStageFlagBits pipelineStage,
                                                          VkBuffer dstBuffer, VkDeviceSize dstOffset, uint32_t marker,
                                                          const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);
    AccessContext& context = cb_access_context.GetCurrentAccessContext();

    if (auto dst_buffer = Get<vvl::Buffer>(dstBuffer)) {
        const AccessRange range = MakeRange(dstOffset, 4);
        const ResourceUsageTagEx tag_ex = cb_access_context.AddCommandHandle(tag, dst_buffer->Handle());
        context.UpdateAccessState(*dst_buffer, SYNC_COPY_TRANSFER_WRITE, range, tag_ex, SyncFlag::kMarker);
    }
}

bool SyncValidator::PreCallValidateCmdDecodeVideoKHR(VkCommandBuffer commandBuffer, const VkVideoDecodeInfoKHR* pDecodeInfo,
                                                     const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    const auto vs_state = cb_state->bound_video_session.get();
    if (!vs_state) return skip;

    auto src_buffer = Get<vvl::Buffer>(pDecodeInfo->srcBuffer);
    if (src_buffer) {
        const AccessRange src_range = MakeRange(*src_buffer, pDecodeInfo->srcBufferOffset, pDecodeInfo->srcBufferRange);
        auto hazard = context.DetectHazard(*src_buffer, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ, src_range);
        if (hazard.IsHazard()) {
            const std::string resource_description = "bitstream buffer " + FormatHandle(pDecodeInfo->srcBuffer);
            // TODO: there are no tests for this error
            const auto error = error_messages_.BufferError(hazard, cb_access_context, error_obj.location.function,
                                                           resource_description, src_range);
            skip |= SyncError(hazard.Hazard(), src_buffer->Handle(), error_obj.location, error);
        }
    }

    auto dst_resource = vvl::VideoPictureResource(*device_state, pDecodeInfo->dstPictureResource);
    if (dst_resource) {
        auto hazard = context.DetectVideoHazard(*vs_state, dst_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE);
        if (hazard.IsHazard()) {
            std::ostringstream ss;
            ss << "decode output picture ";
            ss << Location(Func::Empty, Field::pDecodeInfo).dot(Field::dstPictureResource).Fields();
            ss << " ";
            FormatVideoPictureResouce(*this, pDecodeInfo->dstPictureResource, ss);
            const std::string resouce_description = ss.str();
            const std::string error =
                error_messages_.VideoError(hazard, cb_access_context, error_obj.location.function, resouce_description);
            skip |= SyncError(hazard.Hazard(), dst_resource.image_view_state->Handle(), error_obj.location, error);
        }
    }

    if (pDecodeInfo->pSetupReferenceSlot != nullptr && pDecodeInfo->pSetupReferenceSlot->pPictureResource != nullptr) {
        const VkVideoPictureResourceInfoKHR& video_picture = *pDecodeInfo->pSetupReferenceSlot->pPictureResource;
        auto setup_resource = vvl::VideoPictureResource(*device_state, video_picture);
        if (setup_resource && (setup_resource != dst_resource)) {
            auto hazard = context.DetectVideoHazard(*vs_state, setup_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_WRITE);
            if (hazard.IsHazard()) {
                std::ostringstream ss;
                ss << "reconstructed picture ";
                ss << Location(Func::Empty, Field::pDecodeInfo)
                          .dot(Field::pSetupReferenceSlot)
                          .dot(Field::pPictureResource)
                          .Fields();
                ss << " ";
                FormatVideoPictureResouce(*this, video_picture, ss);
                const std::string resouce_description = ss.str();
                const std::string error =
                    error_messages_.VideoError(hazard, cb_access_context, error_obj.location.function, resouce_description);
                skip |= SyncError(hazard.Hazard(), setup_resource.image_view_state->Handle(), error_obj.location, error);
            }
        }
    }

    for (uint32_t i = 0; i < pDecodeInfo->referenceSlotCount; ++i) {
        if (pDecodeInfo->pReferenceSlots[i].pPictureResource != nullptr) {
            const VkVideoPictureResourceInfoKHR& video_picture = *pDecodeInfo->pReferenceSlots[i].pPictureResource;
            auto reference_resource = vvl::VideoPictureResource(*device_state, video_picture);
            if (reference_resource) {
                auto hazard = context.DetectVideoHazard(*vs_state, reference_resource, SYNC_VIDEO_DECODE_VIDEO_DECODE_READ);
                if (hazard.IsHazard()) {
                    std::ostringstream ss;
                    ss << "reference picture " << i << " ";
                    ss << Location(Func::Empty, Field::pDecodeInfo)
                              .dot(Field::pReferenceSlots, i)
                              .dot(Field::pPictureResource)
                              .Fields();
                    ss << " ";
                    FormatVideoPictureResouce(*this, video_picture, ss);
                    const std::string resouce_description = ss.str();
                    const std::string error =
                        error_messages_.VideoError(hazard, cb_access_context, error_obj.location.function, resouce_description);
                    skip |= SyncError(hazard.Hazard(), reference_resource.image_view_state->Handle(), error_obj.location, error);
                }
            }
        }
    }

    return skip;
}

bool SyncValidator::PreCallValidateCmdEncodeVideoKHR(VkCommandBuffer commandBuffer, const VkVideoEncodeInfoKHR* pEncodeInfo,
                                                     const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    const auto vs_state = cb_state->bound_video_session.get();
    if (!vs_state) return skip;

    auto dst_buffer = Get<vvl::Buffer>(pEncodeInfo->dstBuffer);
    if (dst_buffer) {
        const AccessRange dst_range = MakeRange(*dst_buffer, pEncodeInfo->dstBufferOffset, pEncodeInfo->dstBufferRange);
        auto hazard = context.DetectHazard(*dst_buffer, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE, dst_range);
        if (hazard.IsHazard()) {
            const std::string resource_description = "bitstream buffer " + FormatHandle(pEncodeInfo->dstBuffer);
            const auto error = error_messages_.BufferError(hazard, cb_access_context, error_obj.location.function,
                                                           resource_description, dst_range);
            skip |= SyncError(hazard.Hazard(), dst_buffer->Handle(), error_obj.location, error);
        }
    }

    if (auto src_resource = vvl::VideoPictureResource(*device_state, pEncodeInfo->srcPictureResource)) {
        auto hazard = context.DetectVideoHazard(*vs_state, src_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ);
        if (hazard.IsHazard()) {
            std::ostringstream ss;
            ss << "encode input picture ";
            ss << Location(Func::Empty, Field::pEncodeInfo).dot(Field::srcPictureResource).Fields();
            ss << " ";
            FormatVideoPictureResouce(*this, pEncodeInfo->srcPictureResource, ss);
            const std::string resouce_description = ss.str();
            // TODO: there are no tests for this error
            const std::string error =
                error_messages_.VideoError(hazard, cb_access_context, error_obj.location.function, resouce_description);
            skip |= SyncError(hazard.Hazard(), src_resource.image_view_state->Handle(), error_obj.location, error);
        }
    }

    if (pEncodeInfo->pSetupReferenceSlot != nullptr && pEncodeInfo->pSetupReferenceSlot->pPictureResource != nullptr) {
        const VkVideoPictureResourceInfoKHR& video_picture = *pEncodeInfo->pSetupReferenceSlot->pPictureResource;
        auto setup_resource = vvl::VideoPictureResource(*device_state, video_picture);
        if (setup_resource) {
            auto hazard = context.DetectVideoHazard(*vs_state, setup_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_WRITE);
            if (hazard.IsHazard()) {
                std::ostringstream ss;
                ss << "reconstructed picture ";
                ss << Location(Func::Empty, Field::pEncodeInfo)
                          .dot(Field::pSetupReferenceSlot)
                          .dot(Field::pPictureResource)
                          .Fields();
                ss << " ";
                FormatVideoPictureResouce(*this, video_picture, ss);
                const std::string resouce_description = ss.str();
                const std::string error =
                    error_messages_.VideoError(hazard, cb_access_context, error_obj.location.function, resouce_description);
                skip |= SyncError(hazard.Hazard(), setup_resource.image_view_state->Handle(), error_obj.location, error);
            }
        }
    }

    for (uint32_t i = 0; i < pEncodeInfo->referenceSlotCount; ++i) {
        if (pEncodeInfo->pReferenceSlots[i].pPictureResource != nullptr) {
            const VkVideoPictureResourceInfoKHR& video_picture = *pEncodeInfo->pReferenceSlots[i].pPictureResource;
            auto reference_resource = vvl::VideoPictureResource(*device_state, video_picture);
            if (reference_resource) {
                auto hazard = context.DetectVideoHazard(*vs_state, reference_resource, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ);
                if (hazard.IsHazard()) {
                    std::ostringstream ss;
                    ss << "reference picture " << i << " ";
                    ss << Location(Func::Empty, Field::pEncodeInfo)
                              .dot(Field::pReferenceSlots, i)
                              .dot(Field::pPictureResource)
                              .Fields();
                    ss << " ";
                    FormatVideoPictureResouce(*this, video_picture, ss);
                    const std::string resource_description = ss.str();
                    const std::string error =
                        error_messages_.VideoError(hazard, cb_access_context, error_obj.location.function, resource_description);
                    skip |= SyncError(hazard.Hazard(), reference_resource.image_view_state->Handle(), error_obj.location, error);
                }
            }
        }
    }

    if (pEncodeInfo->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>(pEncodeInfo->pNext);
        if (quantization_map_info) {
            auto image_view_state = 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};
                auto hazard = context.DetectHazard(*image_view_state, offset, extent, SYNC_VIDEO_ENCODE_VIDEO_ENCODE_READ);
                if (hazard.IsHazard()) {
                    std::ostringstream ss;
                    ss << "quantization map ";
                    ss << Location(Func::Empty, Field::pEncodeInfo).dot(Field::quantizationMap).Fields();
                    ss << " ";
                    FormatVideoQuantizationMap(*this, *quantization_map_info, ss);
                    const std::string resource_description = ss.str();
                    const std::string error =
                        error_messages_.VideoError(hazard, cb_access_context, error_obj.location.function, resource_description);
                    skip |= SyncError(hazard.Hazard(), image_view_state->Handle(), error_obj.location, error);
                }
            }
        }
    }

    return skip;
}

void SyncValidator::PostCallRecordResetEvent(VkDevice device, VkEvent event, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    if (auto event_state = Get<vvl::Event>(event)) {
        const auto all_batches = GetAllQueueBatchContexts();
        for (const auto& batch : all_batches) {
            SyncEventsContext& events_context = batch->GetEventsContext();
            if (SyncEventState* sync_event = events_context.GetFromShared(event_state)) {
                sync_event->last_command = record_obj.location.function;
                sync_event->unsynchronized_set = vvl::Func::Empty;
                sync_event->ResetFirstScope();
                sync_event->barriers = 0;
            }
        }
    }
}

bool SyncValidator::ValidateCmdSetEvent(const CommandExecutionContext& exec_context, const std::shared_ptr<const vvl::Event>& event,
                                        const SyncExecScope& src_exec_scope, ResourceUsageTag base_tag, const Location& loc) const {
    bool skip = false;

    const SyncValidator& sync_state = exec_context.GetSyncState();
    const SyncEventsContext& events_context = exec_context.GetEventsContext();

    const auto* sync_event = events_context.Get(event);
    if (!sync_event) {
        return skip;
    }
    if (sync_event->last_command_tag >= base_tag) {
        return skip;  // for replay we don't want to revalidate internal "last commmand"
    }
    if (!sync_event->HasBarrier(src_exec_scope.stage_mask, src_exec_scope.exec_scope)) {
        const std::string vuid_prefix = std::string("SYNC-") + vvl::String(loc.function);
        if (IsValueIn(sync_event->last_command, {Func::vkCmdResetEvent, Func::vkCmdResetEvent2, Func::vkCmdResetEvent2KHR})) {
            skip |= sync_state.LogError(vuid_prefix + "-reset-race", event->Handle(), loc,
                                        "%s is set after %s without an intervening execution dependency. This is a race condition "
                                        "and may result in data hazards.",
                                        sync_state.FormatHandle(event->Handle()).c_str(), vvl::String(sync_event->last_command));
        } else if (IsValueIn(sync_event->last_command, {Func::vkCmdSetEvent, Func::vkCmdSetEvent2, Func::vkCmdSetEvent2KHR})) {
            skip |= sync_state.LogError(vuid_prefix + "-set-race", event->Handle(), loc,
                                        "%s is set after a previous %s without an intervening execution dependency. This is a race "
                                        "condition and may result in data hazards.",
                                        sync_state.FormatHandle(event->Handle()).c_str(), vvl::String(sync_event->last_command));
        } else if (IsValueIn(sync_event->last_command,
                             {Func::vkCmdWaitEvents, Func::vkCmdWaitEvents2, Func::vkCmdWaitEvents2KHR})) {
            skip |= sync_state.LogError(vuid_prefix + "-wait", event->Handle(), loc,
                                        "%s is set after %s without intervening vkCmdResetEvent, may result in data hazard.",
                                        sync_state.FormatHandle(event->Handle()).c_str(), vvl::String(sync_event->last_command));
        }
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdSetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask,
                                               const ErrorObject& error_obj) const {
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    auto event_state = Get<vvl::Event>(event);
    const SyncExecScope src_exec_scope = SyncExecScope::MakeSrc(cb_context.GetQueueFlags(), stageMask);
    return ValidateCmdSetEvent(cb_context, event_state, src_exec_scope, ResourceUsageRecord::kMaxIndex, error_obj.location);
}

void SyncValidator::ApplySetEvent(CommandExecutionContext& exec_context, const std::shared_ptr<const vvl::Event>& event,
                                  const SyncExecScope& src_exec_scope,
                                  const std::shared_ptr<const AccessContext>& src_access_context, ResourceUsageTag tag,
                                  vvl::Func command) const {
    SyncEventsContext& events_context = exec_context.GetEventsContext();
    SyncEventState* sync_event = events_context.GetFromShared(event);
    if (!sync_event) {
        return;
    }

    // What happens with two SetEvent is that one cannot know what group of operations will be waited for.
    // Given:
    //     Stuff1; SetEvent; Stuff2; SetEvent; WaitEvents;
    // WaitEvents cannot know which of Stuff1, Stuff2, or both has completed execution.

    if (!sync_event->HasBarrier(src_exec_scope.stage_mask, src_exec_scope.exec_scope)) {
        sync_event->unsynchronized_set = sync_event->last_command;
        sync_event->ResetFirstScope();
    } else if (!sync_event->first_scope) {
        // We only set the scope if there isn't one
        sync_event->scope = src_exec_scope;

        // Save the shared_ptr to copy of the access_context present at set time (sent us by the caller)
        sync_event->first_scope = src_access_context;
        sync_event->unsynchronized_set = vvl::Func::Empty;
        sync_event->first_scope_tag = tag;
    }
    sync_event->last_command = command;
    sync_event->last_command_tag = tag;
    sync_event->barriers = 0;
}

void SyncValidator::RecordCmdSetEvent(CommandBufferAccessContext& cb_context, std::shared_ptr<const vvl::Event>&& event,
                                      const SyncExecScope& src_exec_scope, const Location& loc) const {
    const ResourceUsageTag tag = cb_context.NextCommandTag(loc.function);

    // Snapshot the current access_context for later inspection at wait time.
    // NOTE: This appears brute force, but given that we only save a "first-last" model
    // of access history, the current access context (include barrier state for chaining)
    // won't necessarily contain the needed information at Wait or Submit time reference.
    auto src_access_context = std::make_shared<AccessContext>(*this);
    src_access_context->InitFrom(cb_context.GetCurrentAccessContext());

    ApplySetEvent(cb_context, event, src_exec_scope, src_access_context, tag, loc.function);

    auto sync_op = std::make_shared<SyncOpSetEvent>(std::move(event), src_exec_scope, std::move(src_access_context), loc);
    cb_context.AddSyncOp(tag, std::move(sync_op));
}

void SyncValidator::PostCallRecordCmdSetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask,
                                              const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    auto event_state = Get<vvl::Event>(event);
    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope src_exec_scope = SyncExecScope::MakeSrc(queue_flags, stageMask);
    RecordCmdSetEvent(cb_context, std::move(event_state), src_exec_scope, record_obj.location);
}

bool SyncValidator::PreCallValidateCmdSetEvent2KHR(VkCommandBuffer commandBuffer, VkEvent event,
                                                   const VkDependencyInfoKHR* pDependencyInfo, const ErrorObject& error_obj) const {
    return PreCallValidateCmdSetEvent2(commandBuffer, event, pDependencyInfo, error_obj);
}

bool SyncValidator::PreCallValidateCmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event,
                                                const VkDependencyInfo* pDependencyInfo, const ErrorObject& error_obj) const {
    bool skip = false;
    if (!pDependencyInfo) {
        return skip;
    }
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    auto event_state = Get<vvl::Event>(event);
    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope src_exec_scope = SyncExecScope::MakeSrc(queue_flags, sync_utils::GetExecScopes(*pDependencyInfo).src);
    return ValidateCmdSetEvent(cb_context, event_state, src_exec_scope, ResourceUsageRecord::kMaxIndex, error_obj.location);
}

void SyncValidator::PostCallRecordCmdSetEvent2KHR(VkCommandBuffer commandBuffer, VkEvent event,
                                                  const VkDependencyInfoKHR* pDependencyInfo, const RecordObject& record_obj) {
    PostCallRecordCmdSetEvent2(commandBuffer, event, pDependencyInfo, record_obj);
}

void SyncValidator::PostCallRecordCmdSetEvent2(VkCommandBuffer commandBuffer, VkEvent event,
                                               const VkDependencyInfo* pDependencyInfo, const RecordObject& record_obj) {
    if (!pDependencyInfo) {
        return;
    }
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    auto event_state = Get<vvl::Event>(event);
    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope src_exec_scope = SyncExecScope::MakeSrc(queue_flags, sync_utils::GetExecScopes(*pDependencyInfo).src);
    RecordCmdSetEvent(cb_context, std::move(event_state), src_exec_scope, record_obj.location);
}

bool SyncValidator::ValidateCmdResetEvent(const CommandExecutionContext& exec_context,
                                          const std::shared_ptr<const vvl::Event>& event, const SyncExecScope& exec_scope,
                                          const ResourceUsageTag base_tag, const Location& loc) const {
    bool skip = false;
    const auto* sync_event = exec_context.GetEventsContext().Get(event);
    if (!sync_event) {
        return skip;
    }
    if (sync_event->last_command_tag > base_tag) {
        return skip;  // if we validated this in recording of the secondary, don't repeat
    }
    if (IsValueIn(sync_event->last_command, {Func::vkCmdSetEvent, Func::vkCmdSetEvent2, Func::vkCmdSetEvent2KHR}) &&
        !sync_event->HasBarrier(exec_scope.stage_mask, exec_scope.exec_scope)) {
        skip |= LogError("SYNC-vkCmdResetEvent-set-race", event->Handle(), loc,
                         "%s is reset after %s without an intervening execution dependency. This is a race condition "
                         "and may result in data hazards.",
                         FormatHandle(event->Handle()).c_str(), vvl::String(sync_event->last_command));
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdResetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask,
                                                 const ErrorObject& error_obj) const {
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    auto event_state = Get<vvl::Event>(event);
    const SyncExecScope exec_scope = SyncExecScope::MakeSrc(cb_context.GetQueueFlags(), stageMask);
    return ValidateCmdResetEvent(cb_context, event_state, exec_scope, ResourceUsageRecord::kMaxIndex, error_obj.location);
}

void SyncValidator::ApplyResetEvent(CommandExecutionContext& exec_context, const std::shared_ptr<const vvl::Event>& event,
                                    ResourceUsageTag tag, vvl::Func command) const {
    SyncEventsContext& events_context = exec_context.GetEventsContext();
    SyncEventState* sync_event = events_context.GetFromShared(event);
    if (!sync_event) {
        return;
    }
    sync_event->last_command = command;
    sync_event->last_command_tag = tag;
    sync_event->unsynchronized_set = vvl::Func::Empty;
    sync_event->ResetFirstScope();
    sync_event->barriers = 0;
}

void SyncValidator::RecordCmdResetEvent(CommandBufferAccessContext& cb_context, std::shared_ptr<const vvl::Event>&& event,
                                        const SyncExecScope& exec_scope, const Location& loc) const {
    const ResourceUsageTag tag = cb_context.NextCommandTag(loc.function);
    ApplyResetEvent(cb_context, event, tag, loc.function);
    auto sync_op = std::make_shared<SyncOpResetEvent>(std::move(event), exec_scope, loc);
    cb_context.AddSyncOp(tag, std::move(sync_op));
}

void SyncValidator::PostCallRecordCmdResetEvent(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags stageMask,
                                                const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    auto event_state = Get<vvl::Event>(event);
    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope exec_scope = SyncExecScope::MakeSrc(queue_flags, stageMask);
    RecordCmdResetEvent(cb_context, std::move(event_state), exec_scope, record_obj.location);
}

bool SyncValidator::PreCallValidateCmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask,
                                                  const ErrorObject& error_obj) const {
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    auto event_state = Get<vvl::Event>(event);
    const SyncExecScope exec_scope = SyncExecScope::MakeSrc(cb_context.GetQueueFlags(), stageMask);
    return ValidateCmdResetEvent(cb_context, event_state, exec_scope, ResourceUsageRecord::kMaxIndex, error_obj.location);
}

bool SyncValidator::PreCallValidateCmdResetEvent2KHR(VkCommandBuffer commandBuffer, VkEvent event,
                                                     VkPipelineStageFlags2KHR stageMask, const ErrorObject& error_obj) const {
    return PreCallValidateCmdResetEvent2(commandBuffer, event, stageMask, error_obj);
}

void SyncValidator::PostCallRecordCmdResetEvent2KHR(VkCommandBuffer commandBuffer, VkEvent event,
                                                    VkPipelineStageFlags2KHR stageMask, const RecordObject& record_obj) {
    PostCallRecordCmdResetEvent2(commandBuffer, event, stageMask, record_obj);
}

void SyncValidator::PostCallRecordCmdResetEvent2(VkCommandBuffer commandBuffer, VkEvent event, VkPipelineStageFlags2 stageMask,
                                                 const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    auto event_state = Get<vvl::Event>(event);
    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope exec_scope = SyncExecScope::MakeSrc(queue_flags, stageMask);
    RecordCmdResetEvent(cb_context, std::move(event_state), exec_scope, record_obj.location);
}

bool SyncValidator::ValidateCmdWaitEvents(const CommandExecutionContext& exec_context,
                                          const std::vector<std::shared_ptr<const vvl::Event>>& events,
                                          const vvl::span<const BarrierSet>& barrier_sets, const ResourceUsageTag base_tag,
                                          const Location& loc) const {
    bool skip = false;
    const auto& sync_state = exec_context.GetSyncState();
    const QueueId queue_id = exec_context.GetQueueId();

    // This is only interesting at record and not replay (Execute/Submit) time
    if (base_tag == ResourceUsageRecord::kMaxIndex) {
        for (size_t barrier_set_index = 0; barrier_set_index < barrier_sets.size(); barrier_set_index++) {
            const auto& barrier_set = barrier_sets[barrier_set_index];
            if (barrier_set.single_exec_scope) {
                if (barrier_set.src_exec_scope.stage_mask & VK_PIPELINE_STAGE_HOST_BIT) {
                    const std::string vuid =
                        std::string("SYNC-") + vvl::String(loc.function) + std::string("-hostevent-unsupported");
                    sync_state.LogInfo(vuid, exec_context.Handle(), loc,
                                       "srcStageMask includes %s, unsupported by synchronization validation.",
                                       string_VkPipelineStageFlagBits(VK_PIPELINE_STAGE_HOST_BIT));
                } else {
                    const auto& barriers = barrier_set.memory_barriers;
                    for (size_t barrier_index = 0; barrier_index < barriers.size(); barrier_index++) {
                        const auto& barrier = barriers[barrier_index];
                        if (barrier.src_exec_scope.stage_mask & VK_PIPELINE_STAGE_HOST_BIT) {
                            const std::string vuid =
                                std::string("SYNC-") + vvl::String(loc.function) + std::string("-hostevent-unsupported");

                            sync_state.LogInfo(vuid, exec_context.Handle(), loc,
                                               "srcStageMask %s of %s %zu, %s %zu, unsupported by synchronization validation.",
                                               string_VkPipelineStageFlagBits(VK_PIPELINE_STAGE_HOST_BIT), "pDependencyInfo",
                                               barrier_set_index, "pMemoryBarriers", barrier_index);
                        }
                    }
                }
            }
        }
    }

    // The rest is common to record time and replay time
    const SyncEventsContext& events_context = exec_context.GetEventsContext();
    size_t barrier_set_index = 0;
    size_t barrier_set_incr = (barrier_sets.size() == 1) ? 0 : 1;
    for (const auto& event : events) {
        const auto* sync_event = events_context.Get(event);
        const auto& barrier_set = barrier_sets[barrier_set_index];
        if (!sync_event || !sync_event->first_scope) {
            barrier_set_index += barrier_set_incr;
            continue;  // Core, Lifetimes, or Param check needs to catch invalid events.
        }

        // For replay calls, don't revalidate "same command buffer" events
        if (sync_event->last_command_tag >= base_tag) {
            continue;
        }

        const VkEvent event_handle = sync_event->event->VkHandle();

        // TODO: Cleanup this error message
        if (sync_event->unsynchronized_set != vvl::Func::Empty) {
            // Issue error message that Wait is waiting on an signal subject to race condition, and is thus ignored for
            // this event
            const char* const vuid = "SYNC-vkCmdWaitEvents-unsynchronized-setops";
            const char* const message = "%s Unsychronized %s calls result in race conditions w.r.t. event signalling, %s %s";
            const char* const reason = "First synchronization scope is undefined.";
            skip |= sync_state.LogError(vuid, event_handle, loc, message, sync_state.FormatHandle(event_handle).c_str(),
                                        vvl::String(sync_event->last_command), reason, "Wait operation is ignored for this event.");
        }
        if (barrier_set.image_barriers.size()) {
            const auto& image_memory_barriers = barrier_set.image_barriers;
            const AccessContext& context = exec_context.GetCurrentAccessContext();
            for (const auto& image_memory_barrier : image_memory_barriers) {
                if (!image_memory_barrier.layout_transition) continue;
                const auto* image_state = image_memory_barrier.image.get();
                if (!image_state) continue;
                const auto& subresource_range = image_memory_barrier.subresource_range;
                const auto& src_access_scope = image_memory_barrier.barrier.src_access_scope;
                const auto hazard = context.DetectImageBarrierHazard(
                    *image_state, subresource_range, sync_event->scope.exec_scope, src_access_scope, queue_id,
                    sync_event->FirstScope(), sync_event->first_scope_tag, AccessContext::DetectOptions::kDetectAll);
                if (hazard.IsHazard()) {
                    LogObjectList objlist(exec_context.Handle(), image_state->Handle());
                    const std::string resource_description = sync_state.FormatHandle(image_state->Handle());
                    const std::string error = sync_state.error_messages_.ImageBarrierError(
                        hazard, exec_context, loc.function, resource_description, image_memory_barrier);
                    skip |= sync_state.SyncError(hazard.Hazard(), image_state->Handle(), loc, error);
                    break;
                }
            }
        }
        barrier_set_index += barrier_set_incr;
    }
    return skip;
}

bool SyncValidator::PreCallValidateCmdWaitEvents(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                                 VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
                                                 uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,
                                                 uint32_t bufferMemoryBarrierCount,
                                                 const VkBufferMemoryBarrier* pBufferMemoryBarriers,
                                                 uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers,
                                                 const ErrorObject& error_obj) const {
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope src_exec_scope = SyncExecScope::MakeSrc(queue_flags, srcStageMask);
    const SyncExecScope dst_exec_scope = SyncExecScope::MakeDst(queue_flags, dstStageMask);
    const BarrierSet barrier_set =
        BarrierSet(*this, src_exec_scope, dst_exec_scope, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount,
                   pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);

    std::vector<std::shared_ptr<const vvl::Event>> events(eventCount);
    for (uint32_t i = 0; i < eventCount; i++) {
        events[i] = Get<vvl::Event>(pEvents[i]);
    }

    return ValidateCmdWaitEvents(cb_context, events, vvl::make_span(&barrier_set, 1), ResourceUsageRecord::kMaxIndex,
                                 error_obj.location);
}

void SyncValidator::RecordCmdWaitEvents(CommandBufferAccessContext& cb_context,
                                        std::vector<std::shared_ptr<const vvl::Event>>&& events,
                                        std::vector<BarrierSet>&& barrier_sets, const Location& loc) const {
    const ResourceUsageTag tag = cb_context.NextCommandTag(loc.function);
    ApplyWaitEvents(cb_context, events, barrier_sets, tag, loc.function);
    auto sync_op = std::make_shared<SyncOpWaitEvents>(std::move(events), std::move(barrier_sets), loc);
    cb_context.AddSyncOp(tag, std::move(sync_op));
}

void SyncValidator::PostCallRecordCmdWaitEvents(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                                VkPipelineStageFlags srcStageMask, VkPipelineStageFlags dstStageMask,
                                                uint32_t memoryBarrierCount, const VkMemoryBarrier* pMemoryBarriers,
                                                uint32_t bufferMemoryBarrierCount,
                                                const VkBufferMemoryBarrier* pBufferMemoryBarriers,
                                                uint32_t imageMemoryBarrierCount, const VkImageMemoryBarrier* pImageMemoryBarriers,
                                                const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    std::vector<std::shared_ptr<const vvl::Event>> events(eventCount);
    for (uint32_t i = 0; i < eventCount; i++) {
        events[i] = Get<vvl::Event>(pEvents[i]);
    }

    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    const SyncExecScope src_exec_scope = SyncExecScope::MakeSrc(queue_flags, srcStageMask);
    const SyncExecScope dst_exec_scope = SyncExecScope::MakeDst(queue_flags, dstStageMask);
    std::vector<BarrierSet> barrier_sets;
    barrier_sets.emplace_back(*this, src_exec_scope, dst_exec_scope, memoryBarrierCount, pMemoryBarriers, bufferMemoryBarrierCount,
                              pBufferMemoryBarriers, imageMemoryBarrierCount, pImageMemoryBarriers);

    RecordCmdWaitEvents(cb_context, std::move(events), std::move(barrier_sets), record_obj.location);
}

bool SyncValidator::PreCallValidateCmdWaitEvents2KHR(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                                     const VkDependencyInfoKHR* pDependencyInfos,
                                                     const ErrorObject& error_obj) const {
    return PreCallValidateCmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos, error_obj);
}

void SyncValidator::PostCallRecordCmdWaitEvents2KHR(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                                    const VkDependencyInfoKHR* pDependencyInfos, const RecordObject& record_obj) {
    PostCallRecordCmdWaitEvents2(commandBuffer, eventCount, pEvents, pDependencyInfos, record_obj);
}

bool SyncValidator::PreCallValidateCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                                  const VkDependencyInfo* pDependencyInfos, const ErrorObject& error_obj) const {
    bool skip = false;
    if (!pDependencyInfos) {
        return skip;
    }
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    std::vector<std::shared_ptr<const vvl::Event>> events(eventCount);
    for (uint32_t i = 0; i < eventCount; i++) {
        events[i] = Get<vvl::Event>(pEvents[i]);
    }
    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    std::vector<BarrierSet> barrier_sets(eventCount);
    for (uint32_t i = 0; i < eventCount; i++) {
        barrier_sets[i] = BarrierSet(*this, queue_flags, pDependencyInfos[i]);
    }
    return ValidateCmdWaitEvents(cb_context, events, barrier_sets, ResourceUsageRecord::kMaxIndex, error_obj.location);
}

void SyncValidator::PostCallRecordCmdWaitEvents2(VkCommandBuffer commandBuffer, uint32_t eventCount, const VkEvent* pEvents,
                                                 const VkDependencyInfo* pDependencyInfos, const RecordObject& record_obj) {
    if (!pDependencyInfos) {
        return;
    }
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    std::vector<std::shared_ptr<const vvl::Event>> events(eventCount);
    for (uint32_t i = 0; i < eventCount; i++) {
        events[i] = Get<vvl::Event>(pEvents[i]);
    }
    const VkQueueFlags queue_flags = cb_context.GetQueueFlags();
    std::vector<BarrierSet> barrier_sets(eventCount);
    for (uint32_t i = 0; i < eventCount; i++) {
        barrier_sets[i] = BarrierSet(*this, queue_flags, pDependencyInfos[i]);
    }
    RecordCmdWaitEvents(cb_context, std::move(events), std::move(barrier_sets), record_obj.location);
}

bool SyncValidator::PreCallValidateCmdWriteBufferMarker2AMD(VkCommandBuffer commandBuffer, VkPipelineStageFlags2KHR pipelineStage,
                                                            VkBuffer dstBuffer, VkDeviceSize dstOffset, uint32_t marker,
                                                            const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_access_context.GetCurrentAccessContext();

    if (auto dst_buffer = Get<vvl::Buffer>(dstBuffer)) {
        const AccessRange range = MakeRange(dstOffset, 4);
        auto hazard = context.DetectMarkerHazard(*dst_buffer, range);
        if (hazard.IsHazard()) {
            const std::string resource_description = "dstBuffer " + FormatHandle(dstBuffer);
            const auto error =
                error_messages_.BufferError(hazard, cb_access_context, error_obj.location.function, resource_description, range);
            skip |= SyncError(hazard.Hazard(), dstBuffer, error_obj.location, error);
        }
    }
    return skip;
}

void SyncValidator::PostCallRecordCmdWriteBufferMarker2AMD(VkCommandBuffer commandBuffer, VkPipelineStageFlags2KHR pipelineStage,
                                                           VkBuffer dstBuffer, VkDeviceSize dstOffset, uint32_t marker,
                                                           const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_access_context = GetAccessContext(*cb_state);
    const ResourceUsageTag tag = cb_access_context.NextCommandTag(record_obj.location.function);
    AccessContext& context = cb_access_context.GetCurrentAccessContext();

    if (auto dst_buffer = Get<vvl::Buffer>(dstBuffer)) {
        const AccessRange range = MakeRange(dstOffset, 4);
        const ResourceUsageTagEx tag_ex = cb_access_context.AddCommandHandle(tag, dst_buffer->Handle());
        context.UpdateAccessState(*dst_buffer, SYNC_COPY_TRANSFER_WRITE, range, tag_ex, SyncFlag::kMarker);
    }
}

bool SyncValidator::PreCallValidateCmdExecuteCommands(VkCommandBuffer commandBuffer, uint32_t commandBufferCount,
                                                      const VkCommandBuffer* pCommandBuffers, const ErrorObject& error_obj) const {
    bool skip = false;
    const auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    // Heavyweight, but we need a proxy copy of the active command buffer access context
    CommandBufferAccessContext proxy_cb_context(cb_context, CommandBufferAccessContext::AsProxyContext());

    auto& proxy_label_commands = proxy_cb_context.GetProxyLabelCommands();
    proxy_label_commands = cb_state->GetLabelCommands();

    // Make working copies of the access and events contexts
    for (uint32_t cb_index = 0; cb_index < commandBufferCount; ++cb_index) {
        if (cb_index == 0) {
            proxy_cb_context.NextCommandTag(error_obj.location.function, SubCommandType::kIndex);
        } else {
            proxy_cb_context.NextSubCommandTag(error_obj.location.function, SubCommandType::kIndex);
        }

        const auto recorded_cb = Get<vvl::CommandBuffer>(pCommandBuffers[cb_index]);
        if (!recorded_cb) continue;
        const CommandBufferAccessContext& recorded_cb_context = GetAccessContext(*recorded_cb);

        const ResourceUsageTag base_tag = proxy_cb_context.GetTagCount();
        skip |= ReplayState(proxy_cb_context, recorded_cb_context, error_obj, cb_index, base_tag).ValidateFirstUse();

        // Update proxy label commands so they can be used by ImportRecordedAccessLog
        const auto& recorded_label_commands = recorded_cb->GetLabelCommands();
        proxy_label_commands.insert(proxy_label_commands.end(), recorded_label_commands.begin(), recorded_label_commands.end());

        // The barriers have already been applied in ValidatFirstUse
        proxy_cb_context.ImportRecordedAccessLog(recorded_cb_context);
        proxy_cb_context.ResolveExecutedCommandBuffer(recorded_cb_context.GetCurrentAccessContext(), base_tag);
    }
    proxy_label_commands.clear();

    return skip;
}

void SyncValidator::PostCallRecordBindImageMemory(VkDevice device, VkImage image, VkDeviceMemory memory, VkDeviceSize memoryOffset,
                                                  const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS) {
        return;
    }
    VkBindImageMemoryInfo bind_info = vku::InitStructHelper();
    bind_info.image = image;
    bind_info.memory = memory;
    bind_info.memoryOffset = memoryOffset;
    UpdateSyncImageMemoryBindState(1, &bind_info);
}

void SyncValidator::PostCallRecordBindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos,
                                                   const RecordObject& record_obj) {
    // Don't check |record_obj.result| as some binds might still be valid
    UpdateSyncImageMemoryBindState(bindInfoCount, pBindInfos);
}

void SyncValidator::PostCallRecordBindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount,
                                                      const VkBindImageMemoryInfo* pBindInfos, const RecordObject& record_obj) {
    PostCallRecordBindImageMemory2(device, bindInfoCount, pBindInfos, record_obj);
}

void SyncValidator::PostCallRecordQueueWaitIdle(VkQueue queue, const RecordObject& record_obj) {
    if (record_obj.result != VK_SUCCESS || !syncval_settings.submit_time_validation || queue == VK_NULL_HANDLE) {
        return;
    }
    const QueueId waited_queue = GetQueueId(queue);
    if (waited_queue == kQueueIdInvalid) {
        return;
    }
    const QueueState& queue_state = GetQueueState(waited_queue);
    ApplyTaggedWait(waited_queue, ResourceUsageRecord::kMaxIndex, queue_state.GetLastSynchronizedPresent(), {});

    // For each timeline, remove all signals signaled on the waited queue, except the last one.
    // The last signal is needed to represent the current timeline state.
    EnsureTimelineSignalsLimit(1, waited_queue);

    // Eliminate host waitable objects from the current queue.
    vvl::EraseIf(waitable_fences_, [waited_queue](const auto& sf) { return sf.second.queue_id == waited_queue; });
    for (auto& [semaphore, sync_points] : host_waitable_semaphores_) {
        vvl::EraseIf(sync_points, [waited_queue](const auto& sync_point) { return sync_point.queue_id == waited_queue; });
    }
}

void SyncValidator::PostCallRecordDeviceWaitIdle(VkDevice device, const RecordObject& record_obj) {
    const auto batches = GetAllQueueBatchContexts();

    // Collect information about last synchronized present over all queues
    LastSynchronizedPresent global_last_synchronized_present;
    for (const auto& batch : batches) {
        global_last_synchronized_present.Merge(batch->last_synchronized_present);
    }

    // Device wait will preserve unsynchronized present operations.
    for (const auto& batch : batches) {
        batch->ApplyDeviceWait(global_last_synchronized_present);
    }

    // For each timeline keep only the last signal per queue.
    // The last signal is needed to represent the current timeline state.
    EnsureTimelineSignalsLimit(1);

    // Cleanup fence waits associated with queues. Acquire fence waits are preserved
    auto is_queue_fence = [](const auto& waitable) { return waitable.second.queue_id != kQueueIdInvalid; };
    vvl::EraseIf(waitable_fences_, is_queue_fence);
    host_waitable_semaphores_.clear();
}

bool SyncValidator::PreCallValidateQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo,
                                                   const ErrorObject& error_obj) const {
    bool skip = false;
    if (!syncval_settings.submit_time_validation) {
        return skip;
    }
    std::lock_guard lock_guard(queue_mutex_);
    skip |= const_cast<SyncValidator*>(this)->ProcessQueuePresent(queue, pPresentInfo, error_obj);
    return skip;
}

bool SyncValidator::ProcessQueuePresent(VkQueue queue, const VkPresentInfoKHR* pPresentInfo, const ErrorObject& error_obj) {
    bool skip = false;

    const QueueId queue_id = GetQueueId(queue);
    if (queue_id == kQueueIdInvalid) {
        return skip;
    }
    QueueState& queue_state = GetQueueState(queue_id);
    const uint64_t submit_id = queue_state.ReserveSubmitId();

    BatchContextPtr last_batch = queue_state.LastBatch();
    BatchContextPtr batch(std::make_shared<QueueBatchContext>(*this, queue_state));

    const auto wait_semaphores = vvl::make_span(pPresentInfo->pWaitSemaphores, pPresentInfo->waitSemaphoreCount);

    PresentedImages presented_images;
    uint32_t present_tag_count = SetupPresentInfo(*pPresentInfo, batch, presented_images);

    SignalsUpdate signals_update(*this);
    auto resolved_batches = batch->ResolvePresentWaits(wait_semaphores, presented_images, signals_update);

    // Import the previous batch information
    if (last_batch && !vvl::Contains(resolved_batches, last_batch)) {
        batch->ResolveLastBatch(last_batch);
        resolved_batches.emplace_back(std::move(last_batch));
    }

    // The purpose of keeping return value is to ensure async batches are alive during validation.
    // Validation accesses raw pointer to async contexts stored in AsyncReference.
    const auto async_batches = batch->RegisterAsyncContexts(resolved_batches);

    // Convert present tags to global range
    const ResourceUsageTag global_range_start = batch->SetupBatchTags(present_tag_count);
    for (PresentedImage& presented : presented_images) {
        presented.tag += global_range_start;
    }

    skip |= batch->DoQueuePresentValidate(error_obj.location, presented_images);
    batch->DoPresentOperations(presented_images);
    batch->LogPresentOperations(presented_images, submit_id);

    // Update state if there are no validation errors
    if (!skip) {
        stats.UpdateAccessStats(*this);
        stats.UpdateMemoryStats();
        queue_state.SetLastBatch(std::move(batch));
        ApplySignalsUpdate(signals_update, queue_state.LastBatch());
        for (auto& presented : presented_images) {
            presented.ExportToSwapchain();
        }
    }
    return skip;
}

uint32_t SyncValidator::SetupPresentInfo(const VkPresentInfoKHR& present_info, BatchContextPtr& batch,
                                         PresentedImages& presented_images) {
    const VkSwapchainKHR* swapchains = present_info.pSwapchains;
    const uint32_t* image_indices = present_info.pImageIndices;
    const uint32_t swapchain_count = present_info.swapchainCount;

    presented_images.reserve(swapchain_count);
    for (uint32_t present_index = 0; present_index < swapchain_count; present_index++) {
        // Note: Given the "EraseIf" implementation for acquire fence waits, each presentation needs a unique tag.
        const ResourceUsageTag tag = presented_images.size();
        presented_images.emplace_back(*this, batch, swapchains[present_index], image_indices[present_index], present_index, tag);
        if (presented_images.back().Invalid()) {
            presented_images.pop_back();
        }
    }
    // Present is tagged for each swapchain.
    return static_cast<uint32_t>(presented_images.size());
}

void SyncValidator::PostCallRecordAcquireNextImageKHR(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout,
                                                      VkSemaphore semaphore, VkFence fence, uint32_t* pImageIndex,
                                                      const RecordObject& record_obj) {
    if (!syncval_settings.submit_time_validation) {
        return;
    }
    RecordAcquireNextImageState(device, swapchain, timeout, semaphore, fence, pImageIndex, record_obj);
}

void SyncValidator::PostCallRecordAcquireNextImage2KHR(VkDevice device, const VkAcquireNextImageInfoKHR* pAcquireInfo,
                                                       uint32_t* pImageIndex, const RecordObject& record_obj) {
    if (!syncval_settings.submit_time_validation) {
        return;
    }
    RecordAcquireNextImageState(device, pAcquireInfo->swapchain, pAcquireInfo->timeout, pAcquireInfo->semaphore,
                                pAcquireInfo->fence, pImageIndex, record_obj);
}

void SyncValidator::RecordAcquireNextImageState(VkDevice device, VkSwapchainKHR swapchain, uint64_t timeout, VkSemaphore semaphore,
                                                VkFence fence, uint32_t* pImageIndex, const RecordObject& record_obj) {
    if ((VK_SUCCESS != record_obj.result) && (VK_SUBOPTIMAL_KHR != record_obj.result)) {
        return;
    }

    // Get the image out of the presented list and create apppropriate fences/semaphores.
    auto swapchain_base = Get<vvl::Swapchain>(swapchain);
    if (vvl::StateObject::Invalid(swapchain_base)) {
        return;
    }  // Invalid acquire calls to be caught in CoreCheck/Parameter validation

    auto& swapchain_state = SubState(*swapchain_base);

    PresentedImage presented = swapchain_state.MovePresentedImage(*pImageIndex);
    if (presented.Invalid()) {
        return;
    }

    // No way to make access safe, so nothing to record
    if ((semaphore == VK_NULL_HANDLE) && (fence == VK_NULL_HANDLE)) {
        return;
    }

    // We create a queue-less QBC for the Semaphore and fences to wait on

    // Note: this is a heavyweight way to deal with the fact that all operation logs live in the QueueBatchContext... and
    // acquire doesn't happen on a queue, but we need a place to put the acquire operation access record.
    auto batch = std::make_shared<QueueBatchContext>(*this);
    batch->SetupAccessContext(presented);
    const ResourceUsageTag acquire_tag = batch->SetupBatchTags(1);
    batch->DoAcquireOperation(presented);
    batch->LogAcquireOperation(presented, record_obj.location.function);

    // Now swap out the present queue batch with the acquired one.
    // Note that fence and signal will read the acquire batch from presented, so this needs to be done before
    // setting up the synchronization
    presented.batch = std::move(batch);

    if (semaphore != VK_NULL_HANDLE) {
        std::shared_ptr<const vvl::Semaphore> sem_state = Get<vvl::Semaphore>(semaphore);
        if (sem_state) {
            // This will ignore any duplicated signal (emplace does not update existing entry),
            // and the core validation reports and error in this case.
            binary_signals_.emplace(sem_state->VkHandle(), SignalInfo(sem_state, presented, acquire_tag));
        }
    }
    if (fence != VK_NULL_HANDLE) {
        FenceHostSyncPoint sync_point;
        sync_point.tag = acquire_tag;
        sync_point.acquired = AcquiredImage(presented, acquire_tag);
        UpdateFenceHostSyncPoint(fence, std::move(sync_point));
    }
}

bool SyncValidator::PreCallValidateQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo* pSubmits, VkFence fence,
                                               const ErrorObject& error_obj) const {
    bool skip = false;
    if (!syncval_settings.submit_time_validation) {
        return skip;
    }

    SubmitInfoArrayConverter submit_info(pSubmits, submitCount);
    const VkSubmitInfo2* submits = submit_info.submit_infos2.data();

    std::lock_guard lock_guard(queue_mutex_);
    skip |= const_cast<SyncValidator*>(this)->ProcessQueueSubmit(queue, submitCount, submits, fence, error_obj);
    return skip;
}

bool SyncValidator::PreCallValidateQueueSubmit2(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence,
                                                const ErrorObject& error_obj) const {
    bool skip = false;
    if (!syncval_settings.submit_time_validation) {
        return skip;
    }
    std::lock_guard lock_guard(queue_mutex_);
    skip |= const_cast<SyncValidator*>(this)->ProcessQueueSubmit(queue, submitCount, pSubmits, fence, error_obj);
    return skip;
}

bool SyncValidator::PreCallValidateQueueSubmit2KHR(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2KHR* pSubmits,
                                                   VkFence fence, const ErrorObject& error_obj) const {
    return PreCallValidateQueueSubmit2(queue, submitCount, pSubmits, fence, error_obj);
}

static std::vector<CommandBufferConstPtr> GetCommandBuffers(const vvl::DeviceState& device_state,
                                                            const VkSubmitInfo2& submit_info) {
    // Collected command buffers have the same indexing as in the input VkSubmitInfo2 for reporting purposes.
    // If Get query returns null, it is stored in the result array to keep original indexing.
    std::vector<CommandBufferConstPtr> command_buffers;
    command_buffers.reserve(submit_info.commandBufferInfoCount);
    for (const auto& cb_info : vvl::make_span(submit_info.pCommandBufferInfos, submit_info.commandBufferInfoCount)) {
        command_buffers.emplace_back(device_state.Get<vvl::CommandBuffer>(cb_info.commandBuffer));
    }
    return command_buffers;
}

bool SyncValidator::ProcessQueueSubmit(VkQueue queue, uint32_t submitCount, const VkSubmitInfo2* pSubmits, VkFence fence,
                                       const ErrorObject& error_obj) {
    bool skip = false;

    const QueueId queue_id = GetQueueId(queue);
    if (queue_id == kQueueIdInvalid) {
        return skip;
    }

    QueueState& queue_state = GetQueueState(queue_id);
    SignalsUpdate signals_update(*this);

    // The submit id is a mutable automic which is not recoverable on a skip == true condition
    uint64_t submit_id = queue_state.ReserveSubmitId();

    // Update label stack as we progress through batches and command buffers
    auto current_label_stack = queue_state.GetQueue()->cmdbuf_label_stack;

    BatchContextPtr last_batch = queue_state.LastBatch();
    bool has_unresolved_batches = !queue_state.UnresolvedBatches().empty();

    BatchContextPtr new_last_batch;
    std::vector<UnresolvedBatch> new_unresolved_batches;
    bool new_timeline_signals = false;

    for (uint32_t batch_idx = 0; batch_idx < submitCount; batch_idx++) {
        const VkSubmitInfo2& submit = pSubmits[batch_idx];
        auto batch = std::make_shared<QueueBatchContext>(*this, queue_state);

        const auto wait_semaphores = vvl::make_span(submit.pWaitSemaphoreInfos, submit.waitSemaphoreInfoCount);
        std::vector<VkSemaphoreSubmitInfo> unresolved_waits;
        auto resolved_batches = batch->ResolveSubmitWaits(wait_semaphores, unresolved_waits, signals_update);

        // Add unresolved batch
        if (has_unresolved_batches || !unresolved_waits.empty()) {
            UnresolvedBatch unresolved_batch;
            unresolved_batch.batch = std::move(batch);
            unresolved_batch.submit_index = submit_id;
            unresolved_batch.batch_index = batch_idx;
            unresolved_batch.command_buffers = GetCommandBuffers(*device_state, submit);
            unresolved_batch.unresolved_waits = std::move(unresolved_waits);
            unresolved_batch.resolved_dependencies = std::move(resolved_batches);
            if (submit.pSignalSemaphoreInfos && submit.signalSemaphoreInfoCount) {
                const auto last_info = submit.pSignalSemaphoreInfos + submit.signalSemaphoreInfoCount;
                unresolved_batch.signals.assign(submit.pSignalSemaphoreInfos, last_info);
            }
            unresolved_batch.label_stack = current_label_stack;
            new_unresolved_batches.emplace_back(std::move(unresolved_batch));
            has_unresolved_batches = true;
            stats.AddUnresolvedBatch();
            continue;
        }
        new_last_batch = batch;

        // Import the previous batch information
        if (last_batch && !vvl::Contains(resolved_batches, last_batch)) {
            batch->ResolveLastBatch(last_batch);
            resolved_batches.emplace_back(std::move(last_batch));
        }

        // The purpose of keeping return value is to ensure async batches are alive during validation.
        // Validation accesses raw pointer to async contexts stored in AsyncReference.
        // TODO: All syncval tests pass when the return value is ignored. Write a regression test that fails/crashes in this case.
        const auto async_batches = batch->RegisterAsyncContexts(resolved_batches);

        const auto command_buffers = GetCommandBuffers(*device_state, submit);
        skip |= batch->ValidateSubmit(command_buffers, submit_id, batch_idx, current_label_stack, error_obj);

        const auto submit_signals = vvl::make_span(submit.pSignalSemaphoreInfos, submit.signalSemaphoreInfoCount);
        new_timeline_signals |= signals_update.RegisterSignals(batch, submit_signals);

        // Unless the previous batch was referenced by a signal it will self destruct
        // in the record phase when the last batch is updated.
        last_batch = batch;
    }

    if (new_last_batch && !skip) {
        queue_state.SetLastBatch(std::move(new_last_batch));
    }
    if (!new_unresolved_batches.empty() && !skip) {
        vvl::Append(queue_state.UnresolvedBatches(), new_unresolved_batches);
    }

    // Check if timeline signals resolve existing wait-before-signal dependencies
    if (new_timeline_signals) {
        skip |= PropagateTimelineSignals(signals_update, error_obj);
    }

    if (!skip) {
        stats.UpdateMemoryStats();
        ApplySignalsUpdate(signals_update, queue_state.LastBatch());
        FenceHostSyncPoint sync_point;
        sync_point.queue_id = queue_state.GetQueueId();
        sync_point.tag = ReserveGlobalTagRange(1).begin;
        if (queue_state.LastBatch()) {
            sync_point.queue_sync_tags = queue_state.LastBatch()->GetQueueSyncTags();
        }
        UpdateFenceHostSyncPoint(fence, std::move(sync_point));
    }
    return skip;
}

bool SyncValidator::PropagateTimelineSignals(SignalsUpdate& signals_update, const ErrorObject& error_obj) {
    bool skip = false;

    // The caller ensures we just registered new timeline signals
    bool new_timeline_signals = true;

    // Each iteration uses registered timeline signals to resolve batches.
    // If a resolved batch generates new timeline signals, the loop runs again
    while (new_timeline_signals) {
        new_timeline_signals = false;

        for (QueueState& queue_state : queue_states_) {
            auto& unresolved_batches = queue_state.UnresolvedBatches();
            if (unresolved_batches.empty()) {
                continue;
            }
            // Resolve waits that have matching signal
            for (UnresolvedBatch& unresolved_batch : unresolved_batches) {
                auto it = unresolved_batch.unresolved_waits.begin();
                while (it != unresolved_batch.unresolved_waits.end()) {
                    const VkSemaphoreSubmitInfo& wait_info = *it;
                    auto resolving_signal = signals_update.OnTimelineWait(wait_info.semaphore, wait_info.value);
                    if (!resolving_signal.has_value()) {
                        ++it;
                        continue;  // resolving signal not found, the wait stays unresolved
                    }
                    if (resolving_signal->batch) {  // null for host signals
                        unresolved_batch.batch->ResolveSubmitSemaphoreWait(*resolving_signal, wait_info.stageMask);
                        unresolved_batch.batch->ImportTags(*resolving_signal->batch);
                        unresolved_batch.resolved_dependencies.emplace_back(resolving_signal->batch);
                    }
                    it = unresolved_batch.unresolved_waits.erase(it);
                }
            }

            BatchContextPtr last_batch = queue_state.LastBatch();
            const BatchContextPtr initial_last_batch = last_batch;

            // Process batches that do not have unresolved waits anymore.
            // Stop when find a batch with unresolved waits or when all the batches are processed.
            while (!unresolved_batches.empty() && unresolved_batches.front().unresolved_waits.empty()) {
                UnresolvedBatch& ready_batch = unresolved_batches.front();

                // Import the previous batch information
                if (last_batch && !vvl::Contains(ready_batch.resolved_dependencies, last_batch)) {
                    ready_batch.batch->ResolveLastBatch(last_batch);
                    ready_batch.resolved_dependencies.emplace_back(std::move(last_batch));
                }

                const auto async_batches = ready_batch.batch->RegisterAsyncContexts(ready_batch.resolved_dependencies);

                skip |= ready_batch.batch->ValidateSubmit(ready_batch.command_buffers, ready_batch.submit_index,
                                                          ready_batch.batch_index, ready_batch.label_stack, error_obj);

                // Process signals. New timeline signals can resolve more batches on the next iteration
                const auto submit_signals = vvl::make_span(ready_batch.signals.data(), ready_batch.signals.size());
                new_timeline_signals |= signals_update.RegisterSignals(ready_batch.batch, submit_signals);

                last_batch = ready_batch.batch;
                unresolved_batches.erase(unresolved_batches.begin());
                stats.RemoveUnresolvedBatch();
            }

            if (last_batch != initial_last_batch) {
                queue_state.SetLastBatch(std::move(last_batch));
            }
        }
    }
    return skip;
}

void SyncValidator::PostCallRecordGetFenceStatus(VkDevice device, VkFence fence, const RecordObject& record_obj) {
    if (!syncval_settings.submit_time_validation) {
        return;
    }
    if (record_obj.result == VK_SUCCESS) {
        // fence is signalled, mark it as waited for
        WaitForFence(fence);
    }
}

void SyncValidator::PostCallRecordWaitForFences(VkDevice device, uint32_t fenceCount, const VkFence* pFences, VkBool32 waitAll,
                                                uint64_t timeout, const RecordObject& record_obj) {
    if (!syncval_settings.submit_time_validation) {
        return;
    }
    if ((record_obj.result == VK_SUCCESS) && ((VK_TRUE == waitAll) || (1 == fenceCount))) {
        // We can only know the pFences have signal if we waited for all of them, or there was only one of them
        for (uint32_t i = 0; i < fenceCount; i++) {
            WaitForFence(pFences[i]);
        }
    }
}

bool SyncValidator::PreCallValidateSignalSemaphore(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo,
                                                   const ErrorObject& error_obj) const {
    bool skip = false;
    if (!syncval_settings.submit_time_validation) {
        return skip;
    }
    // Although SignalSemaphore does not run on the queue, the signalling can resolve
    // previously submitted batches that are waiting for this signal, and this will
    // initiate validation that touches queue state. That's why queue mutex is used here.
    std::lock_guard lock_guard(queue_mutex_);
    skip |= const_cast<SyncValidator*>(this)->ProcessSignalSemaphore(device, pSignalInfo, error_obj);
    return skip;
}

bool SyncValidator::PreCallValidateSignalSemaphoreKHR(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo,
                                                      const ErrorObject& error_obj) const {
    return PreCallValidateSignalSemaphore(device, pSignalInfo, error_obj);
}

bool SyncValidator::ProcessSignalSemaphore(VkDevice device, const VkSemaphoreSignalInfo* pSignalInfo,
                                           const ErrorObject& error_obj) {
    bool skip = false;

    auto semaphore_state = Get<vvl::Semaphore>(pSignalInfo->semaphore);
    if (!semaphore_state) {
        return skip;
    }

    SignalsUpdate signals_update(*this);
    std::vector<SignalInfo>& signals = signals_update.timeline_signals[pSignalInfo->semaphore];

    // Reject invalid signal
    if (!signals.empty() && pSignalInfo->value <= signals.back().timeline_value) {
        return skip;  // [core validation check]: strictly increasing signal values
    }

    signals.emplace_back(SignalInfo(semaphore_state, pSignalInfo->value));
    skip |= PropagateTimelineSignals(signals_update, error_obj);

    if (!skip) {
        ApplySignalsUpdate(signals_update, nullptr);
    }
    return skip;
}

void SyncValidator::PostCallRecordWaitSemaphores(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout,
                                                 const RecordObject& record_obj) {
    if (!syncval_settings.submit_time_validation) {
        return;
    }
    const bool wait_all = pWaitInfo->semaphoreCount == 1 || (pWaitInfo->flags & VK_SEMAPHORE_WAIT_ANY_BIT) == 0;
    if (record_obj.result == VK_SUCCESS && wait_all) {
        for (uint32_t i = 0; i < pWaitInfo->semaphoreCount; i++) {
            WaitForSemaphore(pWaitInfo->pSemaphores[i], pWaitInfo->pValues[i]);
        }
    }
}

void SyncValidator::PostCallRecordWaitSemaphoresKHR(VkDevice device, const VkSemaphoreWaitInfo* pWaitInfo, uint64_t timeout,
                                                    const RecordObject& record_obj) {
    PostCallRecordWaitSemaphores(device, pWaitInfo, timeout, record_obj);
}

void SyncValidator::PostCallRecordGetSemaphoreCounterValue(VkDevice device, VkSemaphore semaphore, uint64_t* pValue,
                                                           const RecordObject& record_obj) {
    if (!syncval_settings.submit_time_validation) {
        return;
    }
    if (record_obj.result == VK_SUCCESS) {
        WaitForSemaphore(semaphore, *pValue);
    }
}

void SyncValidator::PostCallRecordGetSemaphoreCounterValueKHR(VkDevice device, VkSemaphore semaphore, uint64_t* pValue,
                                                              const RecordObject& record_obj) {
    PostCallRecordGetSemaphoreCounterValue(device, semaphore, pValue, record_obj);
}

// Returns null when device address is asssociated with no buffers or more than one buffer.
// Otherwise returns a valid buffer (device address is associated with a single buffer).
// When syncval adds memory aliasing support the need of this function can be revisited.
static const vvl::Buffer* GetSingleBufferFromDeviceAddress(const vvl::DeviceState& device, VkDeviceAddress device_address) {
    vvl::span<vvl::Buffer* const> buffers = device.GetBuffersByAddress(device_address);
    if (buffers.empty()) {
        return nullptr;
    }
    if (buffers.size() > 1) {  // memory aliasing use case
        return nullptr;
    }
    return buffers[0];
}

struct AccelerationStructureGeometryInfo {
    const vvl::Buffer* vertex_data = nullptr;
    AccessRange vertex_range;
    const vvl::Buffer* index_data = nullptr;
    AccessRange index_range;
    const vvl::Buffer* transform_data = nullptr;
    AccessRange transform_range;
    const vvl::Buffer* aabb_data = nullptr;
    AccessRange aabb_range;
    const vvl::Buffer* instance_data = nullptr;
    AccessRange instance_range;
};

static std::optional<AccelerationStructureGeometryInfo> GetValidGeometryInfo(
    const vvl::DeviceState& device, const VkAccelerationStructureGeometryKHR& geometry,
    const VkAccelerationStructureBuildRangeInfoKHR& range_info) {
    if (geometry.geometryType == VK_GEOMETRY_TYPE_TRIANGLES_KHR) {
        const VkAccelerationStructureGeometryTrianglesDataKHR& triangles = geometry.geometry.triangles;
        AccelerationStructureGeometryInfo geometry_info;

        // Assume that synchronization ranges cover the entire vertex struct,
        // even if positional data is strided (i.e., interleaved with other attributes).
        // That is, the application does not attempt to synchronize each position variable
        // with a separate buffer barrier range.
        const vvl::Buffer* p_vertex_data = GetSingleBufferFromDeviceAddress(device, triangles.vertexData.deviceAddress);

        if (triangles.indexType == VK_INDEX_TYPE_NONE_KHR) {
            // Vertex data
            if (p_vertex_data) {
                geometry_info.vertex_data = p_vertex_data;
                const VkDeviceSize base_vertex_offset = triangles.vertexData.deviceAddress - p_vertex_data->deviceAddress;
                const VkDeviceSize local_offset = range_info.primitiveOffset + range_info.firstVertex * triangles.vertexStride;
                const VkDeviceSize offset = base_vertex_offset + local_offset;
                const VkDeviceSize vertex_data_size = 3 * range_info.primitiveCount * triangles.vertexStride;
                geometry_info.vertex_range = MakeRange(*p_vertex_data, offset, vertex_data_size);
            }
        } else {
            // Vertex data
            if (p_vertex_data) {
                geometry_info.vertex_data = p_vertex_data;
                const VkDeviceSize base_vertex_offset = triangles.vertexData.deviceAddress - p_vertex_data->deviceAddress;
                const VkDeviceSize local_offset = range_info.firstVertex * triangles.vertexStride;
                const VkDeviceSize offset = base_vertex_offset + local_offset;
                const VkDeviceSize all_vertex_data_size = (triangles.maxVertex + 1) * triangles.vertexStride;
                const VkDeviceSize potentially_accessed_vertex_data_size = all_vertex_data_size - local_offset;
                geometry_info.vertex_range = MakeRange(*p_vertex_data, offset, potentially_accessed_vertex_data_size);
            }
            // Index data
            const auto p_index_data = GetSingleBufferFromDeviceAddress(device, triangles.indexData.deviceAddress);
            if (p_index_data) {
                geometry_info.index_data = p_index_data;
                const VkDeviceSize base_index_offset = triangles.indexData.deviceAddress - p_index_data->deviceAddress;
                const uint32_t index_byte_size = IndexTypeByteSize(triangles.indexType);
                const VkDeviceSize offset = base_index_offset + range_info.primitiveOffset;
                const uint32_t index_data_size = 3 * range_info.primitiveCount * index_byte_size;
                geometry_info.index_range = MakeRange(*p_index_data, offset, index_data_size);
            }
        }
        // Transform data
        if (const vvl::Buffer* p_transform_data = GetSingleBufferFromDeviceAddress(device, triangles.transformData.deviceAddress)) {
            const VkDeviceSize base_offset = triangles.transformData.deviceAddress - p_transform_data->deviceAddress;
            const VkDeviceSize offset = base_offset + range_info.transformOffset;
            geometry_info.transform_data = p_transform_data;
            geometry_info.transform_range = MakeRange(*p_transform_data, offset, sizeof(VkTransformMatrixKHR));
        }
        return geometry_info;
    } else if (geometry.geometryType == VK_GEOMETRY_TYPE_AABBS_KHR) {
        // Make a similar assumption for strided aabb data as for vertex data - synchronization ranges themselves are not strided.
        const VkAccelerationStructureGeometryAabbsDataKHR& aabbs = geometry.geometry.aabbs;
        if (const vvl::Buffer* p_aabbs = GetSingleBufferFromDeviceAddress(device, aabbs.data.deviceAddress)) {
            AccelerationStructureGeometryInfo geometry_info;
            geometry_info.aabb_data = p_aabbs;
            const VkDeviceSize base_offset = aabbs.data.deviceAddress - p_aabbs->deviceAddress;
            const VkDeviceSize offset = base_offset + range_info.primitiveOffset;
            const VkDeviceSize aabb_data_size = range_info.primitiveCount * sizeof(VkAabbPositionsKHR);
            geometry_info.aabb_range = MakeRange(*p_aabbs, offset, aabb_data_size);
            return geometry_info;
        }
    } else if (geometry.geometryType == VK_GEOMETRY_TYPE_INSTANCES_KHR) {
        const VkAccelerationStructureGeometryInstancesDataKHR& instances = geometry.geometry.instances;
        if (const vvl::Buffer* p_instances = GetSingleBufferFromDeviceAddress(device, instances.data.deviceAddress)) {
            AccelerationStructureGeometryInfo geometry_info;
            geometry_info.instance_data = p_instances;
            const VkDeviceSize base_offset = instances.data.deviceAddress - p_instances->deviceAddress;
            const VkDeviceSize offset = base_offset + range_info.primitiveOffset;
            const VkDeviceSize instance_data_size =
                range_info.primitiveCount *
                (instances.arrayOfPointers ? sizeof(VkDeviceAddress) : sizeof(VkAccelerationStructureInstanceKHR));
            geometry_info.instance_range = MakeRange(*p_instances, offset, instance_data_size);
            return geometry_info;
        }
    }
    return {};
}

bool SyncValidator::PreCallValidateCmdBuildAccelerationStructuresKHR(
    VkCommandBuffer commandBuffer, uint32_t infoCount, const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
    const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos, const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    for (const auto [i, info] : vvl::enumerate(pInfos, infoCount)) {
        const Location info_loc = error_obj.location.dot(Field::pInfos, i);
        // Validate scratch buffer
        if (const vvl::Buffer* p_scratch_buffer = GetSingleBufferFromDeviceAddress(*device_state, info.scratchData.deviceAddress)) {
            const vvl::Buffer& scratch_buffer = *p_scratch_buffer;
            const VkDeviceSize scratch_size = rt::ComputeScratchSize(rt::BuildType::Device, device, info, ppBuildRangeInfos[i]);
            const VkDeviceSize offset = info.scratchData.deviceAddress - scratch_buffer.deviceAddress;
            const AccessRange range = MakeRange(scratch_buffer, offset, scratch_size);
            auto hazard =
                context.DetectHazard(scratch_buffer, SYNC_ACCELERATION_STRUCTURE_BUILD_ACCELERATION_STRUCTURE_WRITE, range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(commandBuffer, scratch_buffer.Handle());
                const std::string resource_description = "scratch buffer " + FormatHandle(scratch_buffer.VkHandle());
                const auto error =
                    error_messages_.BufferError(hazard, cb_context, error_obj.location.function, resource_description, range);
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
        // Validate access to source acceleration structure
        if (const auto src_accel = Get<vvl::AccelerationStructureKHR>(info.srcAccelerationStructure)) {
            if (const vvl::BufferAndOffset src_as_buffer = src_accel->GetFirstValidBuffer(*device_state)) {
                const AccessRange range = MakeRange(src_as_buffer.offset, src_accel->GetSize());
                auto hazard = context.DetectHazard(*src_as_buffer.state,
                                                   SYNC_ACCELERATION_STRUCTURE_BUILD_ACCELERATION_STRUCTURE_READ, range);
                if (hazard.IsHazard()) {
                    const LogObjectList objlist(commandBuffer, src_as_buffer.state->Handle(), src_accel->Handle());
                    const std::string resource_description = FormatHandle(src_as_buffer.state->VkHandle());
                    const std::string error = error_messages_.AccelerationStructureError(
                        hazard, cb_context, error_obj.location.function, resource_description, range, info.srcAccelerationStructure,
                        info_loc.dot(Field::srcAccelerationStructure));
                    skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
                }
            }
        }
        // Validate access to the acceleration structure being built
        if (const auto dst_accel = Get<vvl::AccelerationStructureKHR>(info.dstAccelerationStructure)) {
            if (const vvl::BufferAndOffset dst_as_buffer = dst_accel->GetFirstValidBuffer(*device_state)) {
                const AccessRange dst_range = MakeRange(dst_as_buffer.offset, dst_accel->GetSize());
                auto hazard = context.DetectHazard(*dst_as_buffer.state,
                                                   SYNC_ACCELERATION_STRUCTURE_BUILD_ACCELERATION_STRUCTURE_WRITE, dst_range);
                if (hazard.IsHazard()) {
                    const LogObjectList objlist(commandBuffer, dst_as_buffer.state->Handle(), dst_accel->Handle());
                    const std::string resource_description = FormatHandle(dst_as_buffer.state->VkHandle());
                    const std::string error = error_messages_.AccelerationStructureError(
                        hazard, cb_context, error_obj.location.function, resource_description, dst_range,
                        info.dstAccelerationStructure, info_loc.dot(Field::dstAccelerationStructure));
                    skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
                }
            }
        }
        // Validate geometry buffers
        const VkAccelerationStructureBuildRangeInfoKHR* p_range_infos = ppBuildRangeInfos[i];
        if (!p_range_infos) {
            continue;  // [core validation check]: range pointers should be valid
        }
        for (uint32_t k = 0; k < info.geometryCount; k++) {
            const auto* p_geometry = info.pGeometries ? &info.pGeometries[k] : info.ppGeometries[k];
            if (!p_geometry) {
                continue;  // [core validation check]: null pointer in ppGeometries
            }
            const auto geometry_info = GetValidGeometryInfo(*device_state, *p_geometry, p_range_infos[k]);
            if (!geometry_info.has_value()) {
                continue;
            }
            auto validate_accel_input_geometry = [this, &context, &cb_context, &commandBuffer, &error_obj](
                                                     const vvl::Buffer& geometry_data, const AccessRange& geometry_range,
                                                     const char* data_description) {
                auto hazard = context.DetectHazard(geometry_data, SYNC_ACCELERATION_STRUCTURE_BUILD_SHADER_READ, geometry_range);
                if (hazard.IsHazard()) {
                    const LogObjectList objlist(commandBuffer, geometry_data.Handle());
                    std::ostringstream ss;
                    ss << data_description << " ";
                    ss << FormatHandle(geometry_data.Handle());
                    const std::string resource_description = ss.str();
                    const std::string error = error_messages_.BufferError(hazard, cb_context, error_obj.location.function,
                                                                          resource_description, geometry_range);
                    return SyncError(hazard.Hazard(), objlist, error_obj.location, error);
                }
                return false;
            };
            if (geometry_info->vertex_data) {
                skip |= validate_accel_input_geometry(*geometry_info->vertex_data, geometry_info->vertex_range, "vertex data");
            }
            if (geometry_info->index_data) {
                skip |= validate_accel_input_geometry(*geometry_info->index_data, geometry_info->index_range, "index data");
            }
            if (geometry_info->transform_data) {
                skip |=
                    validate_accel_input_geometry(*geometry_info->transform_data, geometry_info->transform_range, "transform data");
            }
            if (geometry_info->aabb_data) {
                skip |= validate_accel_input_geometry(*geometry_info->aabb_data, geometry_info->aabb_range, "aabb data");
            }
            if (geometry_info->instance_data) {
                skip |=
                    validate_accel_input_geometry(*geometry_info->instance_data, geometry_info->instance_range, "instance data");
            }
        }
    }
    return skip;
}

void SyncValidator::PostCallRecordCmdBuildAccelerationStructuresKHR(
    VkCommandBuffer commandBuffer, uint32_t infoCount, const VkAccelerationStructureBuildGeometryInfoKHR* pInfos,
    const VkAccelerationStructureBuildRangeInfoKHR* const* ppBuildRangeInfos, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    AccessContext& context = cb_context.GetCurrentAccessContext();

    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);

    for (const auto [i, info] : vvl::enumerate(pInfos, infoCount)) {
        // Record scratch buffer access
        if (const vvl::Buffer* p_scratch_buffer = GetSingleBufferFromDeviceAddress(*device_state, info.scratchData.deviceAddress)) {
            const vvl::Buffer& scratch_buffer = *p_scratch_buffer;
            const VkDeviceSize scratch_size = rt::ComputeScratchSize(rt::BuildType::Device, device, info, ppBuildRangeInfos[i]);
            const VkDeviceSize offset = info.scratchData.deviceAddress - scratch_buffer.deviceAddress;
            const AccessRange scratch_range = MakeRange(scratch_buffer, offset, scratch_size);
            const ResourceUsageTagEx scratch_tag_ex = cb_context.AddCommandHandle(tag, scratch_buffer.Handle());
            context.UpdateAccessState(scratch_buffer, SYNC_ACCELERATION_STRUCTURE_BUILD_ACCELERATION_STRUCTURE_WRITE, scratch_range,
                                      scratch_tag_ex);
        }

        const auto src_accel = Get<vvl::AccelerationStructureKHR>(info.srcAccelerationStructure);
        const auto dst_accel = Get<vvl::AccelerationStructureKHR>(info.dstAccelerationStructure);

        // Record source acceleration structure access (READ).
        // If the source is the same as the destination then no need to record READ
        // (destination update will replace access with WRITE anyway).
        if (src_accel && src_accel != dst_accel) {
            if (const vvl::BufferAndOffset src_as_buffer = src_accel->GetFirstValidBuffer(*device_state)) {
                const AccessRange range = MakeRange(src_as_buffer.offset, src_accel->GetSize());
                const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, src_as_buffer.state->Handle());
                context.UpdateAccessState(*src_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_BUILD_ACCELERATION_STRUCTURE_READ,
                                          range, tag_ex);
            }
        }
        // Record destination acceleration structure access (WRITE)
        if (dst_accel) {
            if (const vvl::BufferAndOffset dst_as_buffer = dst_accel->GetFirstValidBuffer(*device_state)) {
                const AccessRange dst_range = MakeRange(dst_as_buffer.offset, dst_accel->GetSize());
                const ResourceUsageTagEx dst_tag_ex = cb_context.AddCommandHandle(tag, dst_as_buffer.state->Handle());
                context.UpdateAccessState(*dst_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_BUILD_ACCELERATION_STRUCTURE_WRITE,
                                          dst_range, dst_tag_ex);
            }
        }
        // Record geometry buffer acceses (READ)
        const VkAccelerationStructureBuildRangeInfoKHR* p_range_infos = ppBuildRangeInfos[i];
        if (!p_range_infos) {
            continue;  // [core validation check]: range pointers should be valid
        }
        for (uint32_t k = 0; k < info.geometryCount; k++) {
            const auto* p_geometry = info.pGeometries ? &info.pGeometries[k] : info.ppGeometries[k];
            if (!p_geometry) {
                continue;  // [core validation check]: null pointer in ppGeometries
            }
            const auto geometry_info = GetValidGeometryInfo(*device_state, *p_geometry, p_range_infos[k]);
            if (!geometry_info.has_value()) {
                continue;
            }
            if (geometry_info->vertex_data) {
                const ResourceUsageTagEx vertex_tag_ex = cb_context.AddCommandHandle(tag, geometry_info->vertex_data->Handle());
                context.UpdateAccessState(*geometry_info->vertex_data, SYNC_ACCELERATION_STRUCTURE_BUILD_SHADER_READ,
                                          geometry_info->vertex_range, vertex_tag_ex);
            }
            if (geometry_info->index_data) {
                const ResourceUsageTagEx index_tag_ex = cb_context.AddCommandHandle(tag, geometry_info->index_data->Handle());
                context.UpdateAccessState(*geometry_info->index_data, SYNC_ACCELERATION_STRUCTURE_BUILD_SHADER_READ,
                                          geometry_info->index_range, index_tag_ex);
            }
            if (geometry_info->transform_data) {
                const ResourceUsageTagEx transform_tag_ex =
                    cb_context.AddCommandHandle(tag, geometry_info->transform_data->Handle());
                context.UpdateAccessState(*geometry_info->transform_data, SYNC_ACCELERATION_STRUCTURE_BUILD_SHADER_READ,
                                          geometry_info->transform_range, transform_tag_ex);
            }
            if (geometry_info->aabb_data) {
                const ResourceUsageTagEx aabb_tag_ex = cb_context.AddCommandHandle(tag, geometry_info->aabb_data->Handle());
                context.UpdateAccessState(*geometry_info->aabb_data, SYNC_ACCELERATION_STRUCTURE_BUILD_SHADER_READ,
                                          geometry_info->aabb_range, aabb_tag_ex);
            }
            if (geometry_info->instance_data) {
                const ResourceUsageTagEx instance_tag_ex = cb_context.AddCommandHandle(tag, geometry_info->instance_data->Handle());
                context.UpdateAccessState(*geometry_info->instance_data, SYNC_ACCELERATION_STRUCTURE_BUILD_SHADER_READ,
                                          geometry_info->instance_range, instance_tag_ex);
            }
        }
    }
}

bool SyncValidator::PreCallValidateCmdCopyAccelerationStructureKHR(VkCommandBuffer commandBuffer,
                                                                   const VkCopyAccelerationStructureInfoKHR* pInfo,
                                                                   const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    const Location info_loc = error_obj.location.dot(Field::pInfo);

    if (const auto src_accel = Get<vvl::AccelerationStructureKHR>(pInfo->src)) {
        if (const vvl::BufferAndOffset src_as_buffer = src_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(src_as_buffer.offset, src_accel->GetSize());
            auto hazard =
                context.DetectHazard(*src_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_READ, range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(cb_state->Handle(), src_as_buffer.state->Handle(), src_accel->Handle());
                const std::string resource_description = FormatHandle(src_as_buffer.state->VkHandle());
                const std::string error =
                    error_messages_.AccelerationStructureError(hazard, cb_context, error_obj.location.function,
                                                               resource_description, range, pInfo->src, info_loc.dot(Field::src));
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
    }
    if (const auto dst_accel = Get<vvl::AccelerationStructureKHR>(pInfo->dst)) {
        if (const vvl::BufferAndOffset dst_as_buffer = dst_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(dst_as_buffer.offset, dst_accel->GetSize());
            auto hazard =
                context.DetectHazard(*dst_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_WRITE, range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(cb_state->Handle(), dst_as_buffer.state->Handle(), dst_accel->Handle());
                const std::string resource_description = FormatHandle(dst_as_buffer.state->VkHandle());
                const std::string error =
                    error_messages_.AccelerationStructureError(hazard, cb_context, error_obj.location.function,
                                                               resource_description, range, pInfo->dst, info_loc.dot(Field::dst));
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
    }
    return skip;
}

void SyncValidator::PostCallRecordCmdCopyAccelerationStructureKHR(VkCommandBuffer commandBuffer,
                                                                  const VkCopyAccelerationStructureInfoKHR* pInfo,
                                                                  const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    AccessContext& context = cb_context.GetCurrentAccessContext();

    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);

    if (const auto src_accel = Get<vvl::AccelerationStructureKHR>(pInfo->src)) {
        if (const vvl::BufferAndOffset src_as_buffer = src_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(src_as_buffer.offset, src_accel->GetSize());
            const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, src_as_buffer.state->Handle());
            context.UpdateAccessState(*src_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_READ, range,
                                      tag_ex);
        }
    }
    if (const auto dst_accel = Get<vvl::AccelerationStructureKHR>(pInfo->dst)) {
        if (const vvl::BufferAndOffset dst_as_buffer = dst_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(dst_as_buffer.offset, dst_accel->GetSize());
            const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, dst_as_buffer.state->Handle());
            context.UpdateAccessState(*dst_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_WRITE, range,
                                      tag_ex);
        }
    }
}

bool SyncValidator::PreCallValidateCmdCopyAccelerationStructureToMemoryKHR(VkCommandBuffer commandBuffer,
                                                                           const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo,
                                                                           const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    const Location info_loc = error_obj.location.dot(Field::pInfo);

    if (const auto src_accel = Get<vvl::AccelerationStructureKHR>(pInfo->src)) {
        if (const vvl::BufferAndOffset src_as_buffer = src_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(src_as_buffer.offset, src_accel->GetSize());
            auto hazard =
                context.DetectHazard(*src_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_READ, range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(cb_state->Handle(), src_as_buffer.state->Handle(), src_accel->Handle());
                const std::string resource_description = FormatHandle(src_as_buffer.state->VkHandle());
                const std::string error =
                    error_messages_.AccelerationStructureError(hazard, cb_context, error_obj.location.function,
                                                               resource_description, range, pInfo->src, info_loc.dot(Field::src));
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
    }

    // NOTE: do not validate src_buffer. This requires recording query and then waiting for it after submit.
    // Currently syncval does not support this but even if support is available this affects application:
    // it flushes entire GPU frame and it also affects app scheduling behavior (CPU and GPU frames do not overlap
    // anymore, and this can hide resource scheduling issues). Such submit-wait-validation can be an optional feature.

    return skip;
}

void SyncValidator::PostCallRecordCmdCopyAccelerationStructureToMemoryKHR(VkCommandBuffer commandBuffer,
                                                                          const VkCopyAccelerationStructureToMemoryInfoKHR* pInfo,
                                                                          const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    AccessContext& context = cb_context.GetCurrentAccessContext();

    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);

    if (const auto src_accel = Get<vvl::AccelerationStructureKHR>(pInfo->src)) {
        if (const vvl::BufferAndOffset src_as_buffer = src_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(src_as_buffer.offset, src_accel->GetSize());
            const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, src_as_buffer.state->Handle());
            context.UpdateAccessState(*src_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_READ, range,
                                      tag_ex);
        }
    }
}

bool SyncValidator::PreCallValidateCmdCopyMemoryToAccelerationStructureKHR(VkCommandBuffer commandBuffer,
                                                                           const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo,
                                                                           const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& context = cb_context.GetCurrentAccessContext();

    const Location info_loc = error_obj.location.dot(Field::pInfo);

    if (const auto dst_accel = Get<vvl::AccelerationStructureKHR>(pInfo->dst)) {
        if (const vvl::BufferAndOffset dst_as_buffer = dst_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(dst_as_buffer.offset, dst_accel->GetSize());
            auto hazard =
                context.DetectHazard(*dst_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_WRITE, range);
            if (hazard.IsHazard()) {
                const LogObjectList objlist(cb_state->Handle(), dst_as_buffer.state->Handle(), dst_accel->Handle());
                const std::string resource_description = FormatHandle(dst_as_buffer.state->VkHandle());
                const std::string error =
                    error_messages_.AccelerationStructureError(hazard, cb_context, error_obj.location.function,
                                                               resource_description, range, pInfo->dst, info_loc.dot(Field::dst));
                skip |= SyncError(hazard.Hazard(), objlist, error_obj.location, error);
            }
        }
    }

    // NOTE: do not validate src_buffer. This requires recording query and then waiting for it after submit.
    // Currently syncval does not support this but even if support is available this affects application:
    // it flushes entire GPU frame and it also affects app scheduling behavior (CPU and GPU frames do not overlap
    // anymore, and this can hide resource scheduling issues). Such submit-wait-validation can be an optional feature.

    return skip;
}

void SyncValidator::PostCallRecordCmdCopyMemoryToAccelerationStructureKHR(VkCommandBuffer commandBuffer,
                                                                          const VkCopyMemoryToAccelerationStructureInfoKHR* pInfo,
                                                                          const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    AccessContext& context = cb_context.GetCurrentAccessContext();

    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);

    if (const auto dst_accel = Get<vvl::AccelerationStructureKHR>(pInfo->dst)) {
        if (const vvl::BufferAndOffset dst_as_buffer = dst_accel->GetFirstValidBuffer(*device_state)) {
            const AccessRange range = MakeRange(dst_as_buffer.offset, dst_accel->GetSize());
            const ResourceUsageTagEx tag_ex = cb_context.AddCommandHandle(tag, dst_as_buffer.state->Handle());
            context.UpdateAccessState(*dst_as_buffer.state, SYNC_ACCELERATION_STRUCTURE_COPY_ACCELERATION_STRUCTURE_WRITE, range,
                                      tag_ex);
        }
    }
}

bool SyncValidator::PreCallValidateCmdTraceRaysKHR(VkCommandBuffer commandBuffer,
                                                   const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable,
                                                   const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable,
                                                   const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable,
                                                   const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable,
                                                   uint32_t width, uint32_t height, uint32_t depth,
                                                   const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    skip |= cb_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, error_obj.location);
    return skip;
}

void SyncValidator::PostCallRecordCmdTraceRaysKHR(VkCommandBuffer commandBuffer,
                                                  const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable,
                                                  const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable,
                                                  const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable,
                                                  const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable,
                                                  uint32_t width, uint32_t height, uint32_t depth, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);
    cb_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tag);
}

bool SyncValidator::PreCallValidateCmdTraceRaysIndirectKHR(VkCommandBuffer commandBuffer,
                                                           const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable,
                                                           const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable,
                                                           const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable,
                                                           const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable,
                                                           VkDeviceAddress indirectDeviceAddress,
                                                           const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& access_context = cb_context.GetCurrentAccessContext();

    skip |= cb_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, error_obj.location);

    if (const vvl::Buffer* indirect_buffer = GetSingleBufferFromDeviceAddress(*device_state, indirectDeviceAddress)) {
        skip |= ValidateIndirectBuffer(cb_context, access_context, sizeof(VkTraceRaysIndirectCommandKHR),
                                       indirect_buffer->VkHandle(), 0, 1, 0, error_obj.location);
    }
    return skip;
}

void SyncValidator::PostCallRecordCmdTraceRaysIndirectKHR(VkCommandBuffer commandBuffer,
                                                          const VkStridedDeviceAddressRegionKHR* pRaygenShaderBindingTable,
                                                          const VkStridedDeviceAddressRegionKHR* pMissShaderBindingTable,
                                                          const VkStridedDeviceAddressRegionKHR* pHitShaderBindingTable,
                                                          const VkStridedDeviceAddressRegionKHR* pCallableShaderBindingTable,
                                                          VkDeviceAddress indirectDeviceAddress, const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);
    cb_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tag);

    if (const vvl::Buffer* indirect_buffer = GetSingleBufferFromDeviceAddress(*device_state, indirectDeviceAddress)) {
        RecordIndirectBuffer(cb_context, tag, sizeof(VkTraceRaysIndirectCommandKHR), indirect_buffer->VkHandle(), 0, 1, 0);
    }
}

bool SyncValidator::PreCallValidateCmdTraceRaysIndirect2KHR(VkCommandBuffer commandBuffer, VkDeviceAddress indirectDeviceAddress,
                                                            const ErrorObject& error_obj) const {
    bool skip = false;
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    const CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);
    const AccessContext& access_context = cb_context.GetCurrentAccessContext();

    skip |= cb_context.ValidateDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, error_obj.location);

    if (const vvl::Buffer* indirect_buffer = GetSingleBufferFromDeviceAddress(*device_state, indirectDeviceAddress)) {
        skip |= ValidateIndirectBuffer(cb_context, access_context, sizeof(VkTraceRaysIndirectCommand2KHR),
                                       indirect_buffer->VkHandle(), 0, 1, 0, error_obj.location);
    }
    return skip;
}

void SyncValidator::PostCallRecordCmdTraceRaysIndirect2KHR(VkCommandBuffer commandBuffer, VkDeviceAddress indirectDeviceAddress,
                                                           const RecordObject& record_obj) {
    auto cb_state = Get<vvl::CommandBuffer>(commandBuffer);
    CommandBufferAccessContext& cb_context = GetAccessContext(*cb_state);

    const ResourceUsageTag tag = cb_context.NextCommandTag(record_obj.location.function);
    cb_context.RecordDispatchDrawDescriptorSet(VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, tag);

    if (const vvl::Buffer* indirect_buffer = GetSingleBufferFromDeviceAddress(*device_state, indirectDeviceAddress)) {
        RecordIndirectBuffer(cb_context, tag, sizeof(VkTraceRaysIndirectCommand2KHR), indirect_buffer->VkHandle(), 0, 1, 0);
    }
}

}  // namespace syncval
