/***************************************************************************
 *
 * Copyright (c) 2015-2026 The Khronos Group Inc.
 * Copyright (c) 2015-2026 Valve Corporation
 * Copyright (c) 2015-2026 LunarG, Inc.
 * Copyright (c) 2015-2024 Google Inc.
 * Copyright (c) 2023-2024 RasterGrid Kft.
 *
 * 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 "chassis.h"

#include <cstring>
#include <sstream>

#include "chassis/dispatch_object.h"
#include "generated/dispatch_vector.h"
#include "chassis/validation_object.h"
#include "layer_options.h"
#include "state_tracker/descriptor_sets.h"
#include "chassis/chassis_modification_state.h"
#include "core_checks/core_validation.h"
#include "profiling/profiling.h"
#include "containers/small_vector.h"
#include "utils/dispatch_utils.h"

#define STRINGIFY(s) STRINGIFY_HELPER(s)
#define STRINGIFY_HELPER(s) #s

namespace vulkan_layer_chassis {

// Check enabled instance extensions against supported instance extension whitelist
static void InstanceExtensionWhitelist(vvl::DispatchInstance* layer_data, const VkInstanceCreateInfo* pCreateInfo,
                                       VkInstance instance, const Location& loc) {
    for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
        vvl::Extension extension = GetExtension(pCreateInfo->ppEnabledExtensionNames[i]);
        if (extension == vvl::Extension::Empty) {
            layer_data->LogWarning("WARNING-CreateInstance-extension-not-found", layer_data->instance,
                                   loc.dot(vvl::Field::pCreateInfo).dot(vvl::Field::ppEnabledExtensionNames, i),
                                   "%s is not a known extension string. Please make sure the spelling is correct. (If working on a "
                                   "new extension, the validation code generation needs to be ran with the new vk.xml)",
                                   pCreateInfo->ppEnabledExtensionNames[i]);
        } else if (!IsInstanceExtension(extension)) {
            layer_data->LogWarning(
                "WARNING-CreateInstance-extension-wrong-type", layer_data->instance,
                loc.dot(vvl::Field::pCreateInfo).dot(vvl::Field::ppEnabledExtensionNames, i),
                "%s is a device extension, not a instance extension. It should be set later during vkCreateDevice() time. (If you "
                "are trying to enable the device extensions because it has some physical device level info, it is valid to query "
                "the VkPhysicalDevice regardless of the extensions it supports.)",
                pCreateInfo->ppEnabledExtensionNames[i]);
        }
    }
}

// Check enabled device extensions against supported device extension whitelist
static void DeviceExtensionWhitelist(vvl::DispatchDevice* layer_data, const VkDeviceCreateInfo* pCreateInfo, VkDevice device) {
    Location loc(vvl::Func::vkCreateDevice);
    for (uint32_t i = 0; i < pCreateInfo->enabledExtensionCount; i++) {
        vvl::Extension extension = GetExtension(pCreateInfo->ppEnabledExtensionNames[i]);
        if (extension == vvl::Extension::Empty) {
            layer_data->LogWarning("WARNING-CreateDevice-extension-not-found", layer_data->device,
                                   loc.dot(vvl::Field::pCreateInfo).dot(vvl::Field::ppEnabledExtensionNames, i),
                                   "%s is not a known extension string. Please make sure the spelling is correct. (If working on a "
                                   "new extension, the validation code generation needs to be ran with the new vk.xml)",
                                   pCreateInfo->ppEnabledExtensionNames[i]);
        } else if (!IsDeviceExtension(extension)) {
            layer_data->LogWarning(
                "WARNING-CreateDevice-extension-wrong-type", layer_data->device,
                loc.dot(vvl::Field::pCreateInfo).dot(vvl::Field::ppEnabledExtensionNames, i),
                "%s is a instance extension, not a device extension. It should be set before at vkCreateInstance().",
                pCreateInfo->ppEnabledExtensionNames[i]);
        }
    }
}

// Output layer status information message
// TODO - We should just dump all settings to a file (see https://github.com/KhronosGroup/Vulkan-Utility-Libraries/issues/188)
void OutputLayerStatusInfo(vvl::DispatchInstance* context, const Location& loc) {
    // Will save a lot of time if every test doesn't have to build this string up to not be printed
    if (!context->debug_report->HasSeverityLevel(VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)) {
        return;
    }

    std::ostringstream ss;
    const auto& settings = context->settings;
    ss << "Current Validaiton Enabled:\n";

    // Match the order in vkconfig
    // The goal here is to not print ALL the settings, just which parts of validation are turned on
    if (!settings.disabled[core_checks]) {
        ss << "  - Core Checks\n";
    }
    if (settings.enabled[sync_validation]) {
        ss << "  - Synchronization\n";
    }
    if (!settings.disabled[stateless_checks]) {
        ss << "  - Stateless Parameter\n";
    }
    if (!settings.disabled[object_tracking]) {
        ss << "  - Object lifetime\n";
    }
    if (!settings.disabled[thread_safety]) {
        ss << "  - Thread Safety\n";
    }
    if (!settings.disabled[handle_wrapping]) {
        ss << "  - Handle Wrapping\n";
    }
    if (settings.enabled[legacy_detection]) {
        ss << "  - Legacy Detection\n";
    }
    if (settings.enabled[best_practices]) {
        ss << "  - Best Practices\n";
    }
    if (settings.enabled[gpu_validation]) {
        ss << "  - GPU-AV\n";
    }
    if (settings.enabled[gpu_dump]) {
        ss << "  - GPU Dump\n";
    }
    if (settings.enabled[debug_printf_validation]) {
        ss << "  - Debug Printf\n";
    }

    context->LogInfo("CURRENT-VALIDATION-ENABLED", context->instance, loc, "%s", ss.str().c_str());
}
const vvl::unordered_map<std::string, function_data>& GetNameToFuncPtrMap();

VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetDeviceProcAddr(VkDevice device, const char* funcName) {
    auto layer_data = vvl::GetDispatchDevice(device);
    if (!ApiParentExtensionEnabled(funcName, &layer_data->extensions)) {
        return nullptr;
    }
    const auto& item = GetNameToFuncPtrMap().find(funcName);
    if (item != GetNameToFuncPtrMap().end()) {
        if (item->second.function_type != kFuncTypeDev) {
            Location loc(vvl::Func::vkGetDeviceProcAddr);
            // Was discussed in https://gitlab.khronos.org/vulkan/vulkan/-/merge_requests/6583
            // This has "valid" behavior to return null, but still worth warning users for this unqiue function
            layer_data->LogWarning("WARNING-vkGetDeviceProcAddr-device", device, loc.dot(vvl::Field::pName),
                                   "is trying to grab %s which is an instance level function", funcName);
            return nullptr;
        } else {
            return reinterpret_cast<PFN_vkVoidFunction>(item->second.funcptr);
        }
    }
    auto& table = layer_data->device_dispatch_table;
    if (!table.GetDeviceProcAddr) return nullptr;
    return table.GetDeviceProcAddr(device, funcName);
}

VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetInstanceProcAddr(VkInstance instance, const char* funcName) {
    const auto& item = GetNameToFuncPtrMap().find(funcName);
    if (item != GetNameToFuncPtrMap().end()) {
        return reinterpret_cast<PFN_vkVoidFunction>(item->second.funcptr);
    }
    // Only global functions can pass in nullptr as the instance parameter - and GetNameToFuncPtrMap contains all global functions
    if (instance == nullptr) return nullptr;

    auto layer_data = vvl::GetDispatchInstance(instance);
    auto& table = layer_data->instance_dispatch_table;
    if (!table.GetInstanceProcAddr) return nullptr;
    return table.GetInstanceProcAddr(instance, funcName);
}

VKAPI_ATTR PFN_vkVoidFunction VKAPI_CALL GetPhysicalDeviceProcAddr(VkInstance instance, const char* funcName) {
    const auto& item = GetNameToFuncPtrMap().find(funcName);
    if (item != GetNameToFuncPtrMap().end()) {
        if (item->second.function_type != kFuncTypePdev) {
            return nullptr;
        } else {
            return reinterpret_cast<PFN_vkVoidFunction>(item->second.funcptr);
        }
    }
    auto layer_data = vvl::GetDispatchInstance(instance);
    auto& table = layer_data->instance_dispatch_table;
    if (!table.GetPhysicalDeviceProcAddr) return nullptr;
    return table.GetPhysicalDeviceProcAddr(instance, funcName);
}

// This is here as some applications will call exit() which results in all our static allocations (like std::map) having their
// destructor called and destroyed from under us. It is not possible to detect as sometimes (when using things like robin hood) the
// size()/empty() will give false positive that memory is there there. We add this global hook that will go through and remove all
// the function calls such that things can safely run in the case the applicaiton still wants to make Vulkan calls in their atexit()
// handler
void ApplicationAtExit() {
    // On a "normal" application, this function is called after vkDestroyInstance and layer_data_map is empty
    //
    // If there are multiple devices we still want to delete them all as exit() is a global scope call
    vvl::FreeAllDispatchObjects();
}

VKAPI_ATTR VkResult VKAPI_CALL CreateInstance(const VkInstanceCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator,
                                              VkInstance* pInstance) {
    atexit(ApplicationAtExit);

    VVL_ZoneScoped;
    VkLayerInstanceCreateInfo* chain_info = GetChainInfo(pCreateInfo, VK_LAYER_LINK_INFO);

    assert(chain_info->u.pLayerInfo);
    PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
    PFN_vkCreateInstance fpCreateInstance = (PFN_vkCreateInstance)fpGetInstanceProcAddr(nullptr, "vkCreateInstance");
    if (fpCreateInstance == nullptr) return VK_ERROR_INITIALIZATION_FAILED;
    chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;

    auto instance_dispatch = std::make_unique<vvl::DispatchInstance>(pCreateInfo);

    // Init dispatch array and call registration functions
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateInstance, VulkanTypedHandle());
    for (const auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        skip |= vo->PreCallValidateCreateInstance(pCreateInfo, pAllocator, pInstance, error_obj);
        if (skip) {
            return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkCreateInstance);
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PreCallRecordCreateInstance(pCreateInfo, pAllocator, pInstance, record_obj);
    }

    VkResult result = fpCreateInstance(pCreateInfo, pAllocator, pInstance);
    if (result != VK_SUCCESS) {
        return result;
    }
    record_obj.result = result;
    instance_dispatch->instance = *pInstance;
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->CopyDispatchState();
    }

    layer_init_instance_dispatch_table(*pInstance, &instance_dispatch->instance_dispatch_table, fpGetInstanceProcAddr);

    Location loc(vvl::Func::vkCreateInstance);
    // Create warning message if user is running debug layers.
#ifndef NDEBUG
    instance_dispatch->LogPerformanceWarning("WARNING-CreateInstance-debug-warning", instance_dispatch->instance, loc,
                                             "Using debug builds of the validation layers *will* adversely affect performance.");
#endif

    OutputLayerStatusInfo(instance_dispatch.get(), loc);
    InstanceExtensionWhitelist(instance_dispatch.get(), pCreateInfo, *pInstance, loc);
    instance_dispatch->FindSupportedExtensions();

    // save a raw pointer since the unique_ptr will be invalidate by the move() below
    auto* id = instance_dispatch.get();
    vvl::SetDispatchInstance(*pInstance, std::move(instance_dispatch));

    for (auto& vo : id->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PostCallRecordCreateInstance(pCreateInfo, pAllocator, pInstance, record_obj);
        vo->CopyExtensions();
    }

    DeactivateInstanceDebugCallbacks(id->debug_report);

    return result;
}

VKAPI_ATTR void VKAPI_CALL DestroyInstance(VkInstance instance, const VkAllocationCallbacks* pAllocator) {
    VVL_TracyCZone(tracy_zone_precall, true);
    auto* key = GetDispatchKey(instance);
    auto instance_dispatch = vvl::GetDispatchInstance(instance);
    ActivateInstanceDebugCallbacks(instance_dispatch->debug_report);
    ErrorObject error_obj(vvl::Func::vkDestroyInstance, VulkanTypedHandle(instance, kVulkanObjectTypeInstance));

    for (const auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PreCallValidateDestroyInstance(instance, pAllocator, error_obj);
    }
    vvl::BaseInstance* state_tracker = nullptr;
    RecordObject record_obj(vvl::Func::vkDestroyInstance);
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        if (vo->container_type == LayerObjectTypeStateTracker) {
            state_tracker = vo.get();
            continue;
        }
        vo->PreCallRecordDestroyInstance(instance, pAllocator, record_obj);
    }
    if (state_tracker) {
        state_tracker->PreCallRecordDestroyInstance(instance, pAllocator, record_obj);
    }

    VVL_TracyCZoneEnd(tracy_zone_precall);
    VVL_TracyCZone(tracy_zone_dispatch, true);
    instance_dispatch->instance_dispatch_table.DestroyInstance(instance, pAllocator);
    VVL_TracyCZoneEnd(tracy_zone_dispatch);

    VVL_TracyCZone(tracy_zone_postcall, true);
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        if (vo->container_type == LayerObjectTypeStateTracker) {
            continue;
        }
        vo->PostCallRecordDestroyInstance(instance, pAllocator, record_obj);
    }
    if (state_tracker) {
        state_tracker->PostCallRecordDestroyInstance(instance, pAllocator, record_obj);
    }

    DeactivateInstanceDebugCallbacks(instance_dispatch->debug_report);
    vvl::FreeDispatchInstance(key);

    VVL_TracyCZoneEnd(tracy_zone_postcall);

#if TRACY_MANUAL_LIFETIME
    tracy::ShutdownProfiler();
#endif
}

VKAPI_ATTR VkResult VKAPI_CALL CreateDevice(VkPhysicalDevice gpu, const VkDeviceCreateInfo* pCreateInfo,
                                            const VkAllocationCallbacks* pAllocator, VkDevice* pDevice) {
    VkLayerDeviceCreateInfo* chain_info = GetChainInfo(pCreateInfo, VK_LAYER_LINK_INFO);

    auto instance_dispatch = vvl::GetDispatchInstance(gpu);

    PFN_vkGetInstanceProcAddr fpGetInstanceProcAddr = chain_info->u.pLayerInfo->pfnNextGetInstanceProcAddr;
    PFN_vkGetDeviceProcAddr fpGetDeviceProcAddr = chain_info->u.pLayerInfo->pfnNextGetDeviceProcAddr;
    PFN_vkCreateDevice fpCreateDevice = (PFN_vkCreateDevice)fpGetInstanceProcAddr(instance_dispatch->instance, "vkCreateDevice");
    if (fpCreateDevice == nullptr) {
        return VK_ERROR_INITIALIZATION_FAILED;
    }
    chain_info->u.pLayerInfo = chain_info->u.pLayerInfo->pNext;

    // use a unique pointer to make sure we destroy this object on error
    auto device_dispatch = std::make_unique<vvl::DispatchDevice>(instance_dispatch, gpu, pCreateInfo);

    // This is odd but we need to set the current extensions in all of the
    // instance validation objects so that they are available for validating CreateDevice
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->extensions = device_dispatch->extensions;
    }

    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateDevice, VulkanTypedHandle(gpu, kVulkanObjectTypePhysicalDevice));
    for (const auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        skip |= vo->PreCallValidateCreateDevice(gpu, pCreateInfo, pAllocator, pDevice, error_obj);
        if (skip) {
            return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    // Make copy to modify as some ValidationObjects will want to add extensions/features on
    // After PreCallValidate incase it is invalid
    vku::safe_VkDeviceCreateInfo modified_create_info(pCreateInfo);

    RecordObject record_obj(vvl::Func::vkCreateDevice);
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PreCallRecordCreateDevice(gpu, pCreateInfo, pAllocator, pDevice, record_obj, &modified_create_info);
    }

    VkResult result = fpCreateDevice(gpu, reinterpret_cast<VkDeviceCreateInfo*>(&modified_create_info), pAllocator, pDevice);

    if (result != VK_SUCCESS) {
        // From https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/10472
        // While it is not invalid to have unsupported features, we use this time to help the developer bisect what they added that
        // caused the error from the driver
        //
        // Note that VK_ERROR_EXTENSION_NOT_PRESENT will be validated and reported in the Vulkan-Loader
        if (result == VK_ERROR_FEATURE_NOT_PRESENT) {
            instance_dispatch->ReportErrorFeatureNotPresent(gpu, *modified_create_info.ptr());
        }

        // If not successful, don't keep going as things will likely crash
        return result;
    }

    record_obj.result = result;
    device_dispatch->device = *pDevice;

    layer_init_device_dispatch_table(*pDevice, &device_dispatch->device_dispatch_table, fpGetDeviceProcAddr);

    instance_dispatch->debug_report->device_created++;

    for (auto& vo : device_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->CopyDispatchState();
    }
    DeviceExtensionWhitelist(device_dispatch.get(), pCreateInfo, *pDevice);
    // NOTE: many PostCallRecords expect to be able to look up the device dispatch object so we need to populate the map here.
#if defined(VVL_TRACY_GPU)
    InitTracyVk(instance_dispatch->instance, gpu, *pDevice, fpGetInstanceProcAddr, fpGetDeviceProcAddr,
                device_dispatch->device_dispatch_table);
#endif

    vvl::SetDispatchDevice(*pDevice, std::move(device_dispatch));
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        // Send down modified create info as we want to mark enabled features that we sent down on behalf of the app
        vo->PostCallRecordCreateDevice(gpu, modified_create_info.ptr(), pAllocator, pDevice, record_obj);
    }
    // Note: device_dispatch is no longer valid since it was a std::move source above.
    for (auto& vo : vvl::GetDispatchDevice(*pDevice)->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->FinishDeviceSetup(modified_create_info.ptr(), record_obj.location);
    }

    return result;
}

// NOTE: Do _not_ skip the dispatch call when destroying a device. Whether or not there was a validation error,
//       the loader will destroy the device, and know nothing about future references to this device making it
//       impossible for the caller to use this device handle further. IOW, this is our _only_ chance to (potentially)
//       dispatch the driver's DestroyDevice function.
VKAPI_ATTR void VKAPI_CALL DestroyDevice(VkDevice device, const VkAllocationCallbacks* pAllocator) {
    auto* key = GetDispatchKey(device);
    auto device_dispatch = vvl::GetDispatchDevice(device);
    ErrorObject error_obj(vvl::Func::vkDestroyDevice, VulkanTypedHandle(device, kVulkanObjectTypeDevice));
    for (const auto& vo : device_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PreCallValidateDestroyDevice(device, pAllocator, error_obj);
    }
    RecordObject record_obj(vvl::Func::vkDestroyDevice);

    // Even though layer object types reference the base device state tracker,
    // it needs to be destroyed first: it stores the various object maps,
    // those need to be destroyed by the time layer objects destroy their
    // own device state.
    for (auto& vo : device_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }

        vo->PreCallRecordDestroyDevice(device, pAllocator, record_obj);
    }
    // Before device is destroyed, allow aborted objects to clean up
    for (auto& vo : device_dispatch->aborted_object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PreCallRecordDestroyDevice(device, pAllocator, record_obj);
    }

#if defined(VVL_TRACY_GPU)
    CleanupTracyVk(device);
#endif

    device_dispatch->DestroyDevice(device, pAllocator);

    // The teardown order is
    // 1. All active validation objects
    // 2. All aborted validation objects (ex. if GPU-AV had to turn off mid run)
    // 3. State Tracker validation object
    // 4. Cleanup leaked handles that user didn't destroy
    vvl::BaseDevice* state_tracker = nullptr;
    for (auto& vo : device_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        if (vo->container_type == LayerObjectTypeStateTracker) {
            state_tracker = vo.get();
            continue;
        }
        vo->PostCallRecordDestroyDevice(device, pAllocator, record_obj);
    }
    for (auto& vo : device_dispatch->aborted_object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PostCallRecordDestroyDevice(device, pAllocator, record_obj);
    }
    if (state_tracker) {
        state_tracker->PostCallRecordDestroyDevice(device, pAllocator, record_obj);
    }

    auto instance_dispatch = vvl::GetDispatchInstance(device_dispatch->physical_device);
    instance_dispatch->debug_report->device_created--;

    vvl::FreeDispatchDevice(key);
}

// Special-case APIs for which core_validation needs custom parameter lists and/or modifies parameters

VKAPI_ATTR VkResult VKAPI_CALL CreateGraphicsPipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount,
                                                       const VkGraphicsPipelineCreateInfo* pCreateInfos,
                                                       const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateGraphicsPipelines, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    PipelineStates pipeline_states;
    chassis::CreateGraphicsPipelines chassis_state(pCreateInfos);

    {
        VVL_ZoneScopedN("PreCallValidate_CreateGraphicsPipelines");
        for (const auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreateGraphicsPipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator,
                                                               pPipelines, error_obj, pipeline_states, chassis_state);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkCreateGraphicsPipelines);
    {
        VVL_ZoneScopedN("PreCallRecord_CreateGraphicsPipelines");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreateGraphicsPipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines,
                                                     record_obj, pipeline_states, chassis_state);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_CreateGraphicsPipelines");
        result = device_dispatch->CreateGraphicsPipelines(device, pipelineCache, createInfoCount, chassis_state.pCreateInfos,
                                                          pAllocator, pPipelines);

        // If we have modified the pCreateInfos caused things to fail, revert to allow the app to continue
        if (result < VK_SUCCESS && chassis_state.is_modified) {
            chassis_state.is_modified = false;
            result = device_dispatch->CreateGraphicsPipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator,
                                                              pPipelines);
        }
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_CreateGraphicsPipelines");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordCreateGraphicsPipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines,
                                                      record_obj, pipeline_states, chassis_state);
        }
    }
    return result;
}

// This API saves some core_validation pipeline state state on the stack for performance purposes
VKAPI_ATTR VkResult VKAPI_CALL CreateComputePipelines(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount,
                                                      const VkComputePipelineCreateInfo* pCreateInfos,
                                                      const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateComputePipelines, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    PipelineStates pipeline_states;
    chassis::CreateComputePipelines chassis_state(pCreateInfos);

    {
        VVL_ZoneScopedN("PreCallValidate_CreateComputePipelines");
        for (const auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreateComputePipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator,
                                                              pPipelines, error_obj, pipeline_states, chassis_state);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkCreateComputePipelines);
    {
        VVL_ZoneScopedN("PreCallRecord_CreateComputePipelines");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreateComputePipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines,
                                                    record_obj, pipeline_states, chassis_state);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_CreateComputePipelines");
        result = device_dispatch->CreateComputePipelines(device, pipelineCache, createInfoCount, chassis_state.pCreateInfos,
                                                         pAllocator, pPipelines);
        // If we have modified the pCreateInfos caused things to fail, revert to allow the app to continue
        if (result < VK_SUCCESS && chassis_state.is_modified) {
            chassis_state.is_modified = false;
            result = device_dispatch->CreateComputePipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator,
                                                             pPipelines);
        }
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_CreateComputePipelines");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordCreateComputePipelines(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines,
                                                     record_obj, pipeline_states, chassis_state);
        }
    }
    return result;
}

VKAPI_ATTR VkResult VKAPI_CALL CreateRayTracingPipelinesNV(VkDevice device, VkPipelineCache pipelineCache, uint32_t createInfoCount,
                                                           const VkRayTracingPipelineCreateInfoNV* pCreateInfos,
                                                           const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) {
    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateRayTracingPipelinesNV, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    PipelineStates pipeline_states;

    for (const auto& vo : device_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        auto lock = vo->ReadLock();
        skip |= vo->PreCallValidateCreateRayTracingPipelinesNV(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator,
                                                               pPipelines, error_obj, pipeline_states);
        if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
    }

    RecordObject record_obj(vvl::Func::vkCreateRayTracingPipelinesNV);
    for (auto& vo : device_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        auto lock = vo->WriteLock();
        vo->PreCallRecordCreateRayTracingPipelinesNV(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines,
                                                     record_obj, pipeline_states);
    }

    VkResult result;
    result =
        device_dispatch->CreateRayTracingPipelinesNV(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines);
    record_obj.result = result;

    for (auto& vo : device_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        auto lock = vo->WriteLock();
        vo->PostCallRecordCreateRayTracingPipelinesNV(device, pipelineCache, createInfoCount, pCreateInfos, pAllocator, pPipelines,
                                                      record_obj, pipeline_states);
    }
    return result;
}

VKAPI_ATTR VkResult VKAPI_CALL CreateRayTracingPipelinesKHR(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                            VkPipelineCache pipelineCache, uint32_t createInfoCount,
                                                            const VkRayTracingPipelineCreateInfoKHR* pCreateInfos,
                                                            const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateRayTracingPipelinesKHR, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    PipelineStates pipeline_states;
    auto chassis_state = std::make_shared<chassis::CreateRayTracingPipelinesKHR>(pCreateInfos);

    {
        VVL_ZoneScopedN("PreCallValidate_CreateRayTracingPipelinesKHR");
        for (const auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreateRayTracingPipelinesKHR(device, deferredOperation, pipelineCache, createInfoCount,
                                                                    pCreateInfos, pAllocator, pPipelines, error_obj,
                                                                    pipeline_states, *chassis_state);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkCreateRayTracingPipelinesKHR);
    {
        VVL_ZoneScopedN("PreCallRecord_CreateRayTracingPipelinesKHR");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreateRayTracingPipelinesKHR(device, deferredOperation, pipelineCache, createInfoCount, pCreateInfos,
                                                          pAllocator, pPipelines, record_obj, pipeline_states, *chassis_state);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_CreateRayTracingPipelinesKHR");
        result = device_dispatch->CreateRayTracingPipelinesKHR(device, deferredOperation, pipelineCache, createInfoCount,
                                                               chassis_state->pCreateInfos, pAllocator, pPipelines);

        // If we have modified the pCreateInfos caused things to fail, revert to allow the app to continue
        // Note VK_OPERATION_DEFERRED_KHR is not "failing" as the pipeline will still be created
        if (result < VK_SUCCESS && chassis_state->is_modified) {
            chassis_state->is_modified = false;
            result = device_dispatch->CreateRayTracingPipelinesKHR(device, deferredOperation, pipelineCache, createInfoCount,
                                                                   pCreateInfos, pAllocator, pPipelines);
        }
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_CreateRayTracingPipelinesKHR");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordCreateRayTracingPipelinesKHR(device, deferredOperation, pipelineCache, createInfoCount, pCreateInfos,
                                                           pAllocator, pPipelines, record_obj, pipeline_states, chassis_state);
        }
    }
    return result;
}

// This API saves some core_validation pipeline state state on the stack for performance purposes
VKAPI_ATTR VkResult VKAPI_CALL CreateDataGraphPipelinesARM(VkDevice device, VkDeferredOperationKHR deferredOperation,
                                                           VkPipelineCache pipelineCache, uint32_t createInfoCount,
                                                           const VkDataGraphPipelineCreateInfoARM* pCreateInfos,
                                                           const VkAllocationCallbacks* pAllocator, VkPipeline* pPipelines) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateDataGraphPipelinesARM, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    PipelineStates pipeline_states;
    chassis::CreateDataGraphPipelinesARM chassis_state(pCreateInfos);

    {
        VVL_ZoneScopedN("PreCallValidate");
        for (const auto& vo : device_dispatch->object_dispatch) {
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreateDataGraphPipelinesARM(device, deferredOperation, pipelineCache, createInfoCount,
                                                                   pCreateInfos, pAllocator, pPipelines, error_obj, pipeline_states,
                                                                   chassis_state);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkCreateDataGraphPipelinesARM);
    {
        VVL_ZoneScopedN("PreCallRecord");
        for (auto& vo : device_dispatch->object_dispatch) {
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreateDataGraphPipelinesARM(device, deferredOperation, pipelineCache, createInfoCount, pCreateInfos,
                                                         pAllocator, pPipelines, record_obj, pipeline_states, chassis_state);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch");
        result = device_dispatch->CreateDataGraphPipelinesARM(device, deferredOperation, pipelineCache, createInfoCount,
                                                              chassis_state.pCreateInfos, pAllocator, pPipelines);
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord");
        for (auto& vo : device_dispatch->object_dispatch) {
            auto lock = vo->WriteLock();
            vo->PostCallRecordCreateDataGraphPipelinesARM(device, deferredOperation, pipelineCache, createInfoCount, pCreateInfos,
                                                          pAllocator, pPipelines, record_obj, pipeline_states, chassis_state);
        }
    }
    return result;
}

// This API needs the ability to modify a down-chain parameter
VKAPI_ATTR VkResult VKAPI_CALL CreatePipelineLayout(VkDevice device, const VkPipelineLayoutCreateInfo* pCreateInfo,
                                                    const VkAllocationCallbacks* pAllocator, VkPipelineLayout* pPipelineLayout) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreatePipelineLayout, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    {
        VVL_ZoneScopedN("PreCallValidate_CreatePipelineLayout");
        for (const auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallValidateCreatePipelineLayout]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreatePipelineLayout(device, pCreateInfo, pAllocator, pPipelineLayout, error_obj);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    chassis::CreatePipelineLayout chassis_state{};
    chassis_state.modified_create_info = *pCreateInfo;

    RecordObject record_obj(vvl::Func::vkCreatePipelineLayout);
    {
        VVL_ZoneScopedN("PreCallRecord_CreatePipelineLayout");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreatePipelineLayout(device, pCreateInfo, pAllocator, pPipelineLayout, record_obj, chassis_state);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_CreatePipelineLayout");
        result = device_dispatch->CreatePipelineLayout(device, &chassis_state.modified_create_info, pAllocator, pPipelineLayout);
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_CreatePipelineLayout");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPostCallRecordCreatePipelineLayout]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordCreatePipelineLayout(device, pCreateInfo, pAllocator, pPipelineLayout, record_obj);
        }
    }
    return result;
}

VKAPI_ATTR VkResult VKAPI_CALL GetShaderBinaryDataEXT(VkDevice device, VkShaderEXT shader, size_t* pDataSize, void* pData) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkGetShaderBinaryDataEXT, VulkanTypedHandle(device, kVulkanObjectTypeDevice));
    {
        VVL_ZoneScopedN("PreCallValidate_vkGetShaderBinaryDataEXT");
        for (const auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallValidateGetShaderBinaryDataEXT]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateGetShaderBinaryDataEXT(device, shader, pDataSize, pData, error_obj);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    chassis::ShaderBinaryData chassis_state{shader};

    RecordObject record_obj(vvl::Func::vkGetShaderBinaryDataEXT);
    {
        VVL_ZoneScopedN("PreCallRecord_vkGetShaderBinaryDataEXT");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordGetShaderBinaryDataEXT(device, shader, pDataSize, pData, record_obj, chassis_state);
        }
    }
    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_vkGetShaderBinaryDataEXT");
        result = device_dispatch->GetShaderBinaryDataEXT(device, chassis_state.modified_shader_handle, pDataSize, pData);
    }
    record_obj.result = result;
    {
        VVL_ZoneScopedN("PostCallRecord_vkGetShaderBinaryDataEXT");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPostCallRecordGetShaderBinaryDataEXT]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordGetShaderBinaryDataEXT(device, shader, pDataSize, pData, record_obj);
        }
    }
    return result;
}

// This API needs some local stack data for performance reasons and also may modify a parameter
VKAPI_ATTR VkResult VKAPI_CALL CreateShaderModule(VkDevice device, const VkShaderModuleCreateInfo* pCreateInfo,
                                                  const VkAllocationCallbacks* pAllocator, VkShaderModule* pShaderModule) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateShaderModule, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    {
        VVL_ZoneScopedN("PreCallValidate_CreateShaderModule");
        for (const auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreateShaderModule(device, pCreateInfo, pAllocator, pShaderModule, error_obj);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    chassis::CreateShaderModule chassis_state{};

    RecordObject record_obj(vvl::Func::vkCreateShaderModule);
    {
        VVL_ZoneScopedN("PreCallRecord_CreateShaderModule");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreateShaderModule(device, pCreateInfo, pAllocator, pShaderModule, record_obj, chassis_state);
        }
    }

    // Special extra check if SPIR-V itself fails runtime validation in PreCallRecord
    if (chassis_state.skip) return VK_ERROR_VALIDATION_FAILED_EXT;
    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_CreateShaderModule");
        result = device_dispatch->CreateShaderModule(device, pCreateInfo, pAllocator, pShaderModule);
    }
    record_obj.result = result;
    {
        VVL_ZoneScopedN("PostCallRecord_CreateShaderModule");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordCreateShaderModule(device, pCreateInfo, pAllocator, pShaderModule, record_obj, chassis_state);
        }
    }
    return result;
}

VKAPI_ATTR VkResult VKAPI_CALL CreateShadersEXT(VkDevice device, uint32_t createInfoCount,
                                                const VkShaderCreateInfoEXT* pCreateInfos, const VkAllocationCallbacks* pAllocator,
                                                VkShaderEXT* pShaders) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateShadersEXT, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    chassis::ShaderObject chassis_state(createInfoCount, pCreateInfos);

    {
        VVL_ZoneScopedN("PreCallValidate_CreateShadersEXT");
        for (const auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreateShadersEXT(device, createInfoCount, pCreateInfos, pAllocator, pShaders, error_obj);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkCreateShadersEXT);
    {
        VVL_ZoneScopedN("PreCallRecord_CreateShadersEXT");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreateShadersEXT(device, createInfoCount, pCreateInfos, pAllocator, pShaders, record_obj,
                                              chassis_state);
        }
    }

    // Special extra check if SPIR-V itself fails runtime validation in PreCallRecord
    if (chassis_state.skip) return VK_ERROR_VALIDATION_FAILED_EXT;

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_CreateShadersEXT");
        result = device_dispatch->CreateShadersEXT(device, createInfoCount, chassis_state.pCreateInfos, pAllocator, pShaders);

        // If we have modified the pCreateInfos caused things to fail, revert to allow the app to continue
        if (result < VK_SUCCESS && chassis_state.is_modified) {
            chassis_state.is_modified = false;
            result = device_dispatch->CreateShadersEXT(device, createInfoCount, pCreateInfos, pAllocator, pShaders);
        }
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_CreateShadersEXT");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordCreateShadersEXT(device, createInfoCount, pCreateInfos, pAllocator, pShaders, record_obj,
                                               chassis_state);
        }
    }
    return result;
}

VKAPI_ATTR VkResult VKAPI_CALL AllocateDescriptorSets(VkDevice device, const VkDescriptorSetAllocateInfo* pAllocateInfo,
                                                      VkDescriptorSet* pDescriptorSets) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkAllocateDescriptorSets, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    // Because this is a high frequency function call and want to save time and populate this struct once.
    // Both CoreCheck and Best Practice need this information during PreCallValidate time.
    // During State Tracker PreCallValidate (instead of PreCallRecord), we populate this so that everyone else can use it.
    vvl::AllocateDescriptorSetsData ads_state;

    {
        VVL_ZoneScopedN("PreCallValidate_AllocateDescriptorSets");
        for (const auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateAllocateDescriptorSets(device, pAllocateInfo, pDescriptorSets, error_obj, ads_state);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkAllocateDescriptorSets);
    {
        VVL_ZoneScopedN("PreCallRecord_AllocateDescriptorSets");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallRecordAllocateDescriptorSets]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordAllocateDescriptorSets(device, pAllocateInfo, pDescriptorSets, record_obj);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_AllocateDescriptorSets");
        result = device_dispatch->AllocateDescriptorSets(device, pAllocateInfo, pDescriptorSets);
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_AllocateDescriptorSets");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordAllocateDescriptorSets(device, pAllocateInfo, pDescriptorSets, record_obj, ads_state);
        }
    }
    return result;
}

// This API needs the ability to modify a down-chain parameter
VKAPI_ATTR VkResult VKAPI_CALL CreateBuffer(VkDevice device, const VkBufferCreateInfo* pCreateInfo,
                                            const VkAllocationCallbacks* pAllocator, VkBuffer* pBuffer) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(device);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCreateBuffer, VulkanTypedHandle(device, kVulkanObjectTypeDevice));

    {
        VVL_ZoneScopedN("PreCallValidate_CreateBuffer");
        for (const auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallValidateCreateBuffer]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCreateBuffer(device, pCreateInfo, pAllocator, pBuffer, error_obj);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    chassis::CreateBuffer chassis_state{};
    chassis_state.create_info_copy = pCreateInfo;

    RecordObject record_obj(vvl::Func::vkCreateBuffer);
    {
        VVL_ZoneScopedN("PreCallRecord_CreateBuffer");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCreateBuffer(device, pCreateInfo, pAllocator, pBuffer, record_obj, chassis_state);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_CreateBuffer");
        result = device_dispatch->CreateBuffer(device, chassis_state.create_info_copy, pAllocator, pBuffer);
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_CreateBuffer");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPostCallRecordCreateBuffer]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            // If we don't pass into PostCallRecord, CoreCheck may give false positives when using GPU-AV
            vo->PostCallRecordCreateBuffer(device, chassis_state.create_info_copy, pAllocator, pBuffer, record_obj);
        }
    }
    return result;
}

// This API needs to ensure that per-swapchain VkResult results are available
VKAPI_ATTR VkResult VKAPI_CALL QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(queue);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkQueuePresentKHR, VulkanTypedHandle(queue, kVulkanObjectTypeQueue));
    {
        VVL_ZoneScopedN("PreCallValidate_QueuePresentKHR");
        for (const auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallValidateQueuePresentKHR]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateQueuePresentKHR(queue, pPresentInfo, error_obj);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }
    RecordObject record_obj(vvl::Func::vkQueuePresentKHR);
    {
        VVL_ZoneScopedN("PreCallRecord_QueuePresentKHR");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallRecordQueuePresentKHR]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordQueuePresentKHR(queue, pPresentInfo, record_obj);
        }
    }

    // Track per-swapchain results when there is more than one swapchain and VkPresentInfoKHR::pResults is null
    small_vector<VkResult, 2> present_results;
    VkPresentInfoKHR modified_present_info;
    if (pPresentInfo && pPresentInfo->swapchainCount > 1 && pPresentInfo->pResults == nullptr) {
        present_results.resize(pPresentInfo->swapchainCount);
        modified_present_info = *pPresentInfo;
        modified_present_info.pResults = present_results.data();
        pPresentInfo = &modified_present_info;
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_QueuePresentKHR");
        result = device_dispatch->QueuePresentKHR(queue, pPresentInfo);
    }
    VVL_TracyCFrameMark;
#if defined(VVL_TRACY_GPU)
    TracyVkCollector::GetTracyVkCollector(queue).Collect();
#endif
    record_obj.result = result;
    {
        VVL_ZoneScopedN("PostCallRecord_QueuePresentKHR");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPostCallRecordQueuePresentKHR]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();

            if (result == VK_ERROR_DEVICE_LOST) {
                vo->is_device_lost = true;
            }
            vo->PostCallRecordQueuePresentKHR(queue, pPresentInfo, record_obj);
        }
    }
    return result;
}

VKAPI_ATTR VkResult VKAPI_CALL BeginCommandBuffer(VkCommandBuffer commandBuffer, const VkCommandBufferBeginInfo* pBeginInfo) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(commandBuffer);
    bool skip = false;
    chassis::HandleData handle_data;

    ErrorObject error_obj(vvl::Func::vkBeginCommandBuffer, VulkanTypedHandle(commandBuffer, kVulkanObjectTypeCommandBuffer),
                          &handle_data);
    handle_data.command_buffer.is_secondary = device_dispatch->IsSecondary(commandBuffer);
    {
        VVL_ZoneScopedN("PreCallValidate_BeginCommandBuffer");
        for (const auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallValidateBeginCommandBuffer]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateBeginCommandBuffer(commandBuffer, pBeginInfo, error_obj);
            if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
        }
    }

    RecordObject record_obj(vvl::Func::vkBeginCommandBuffer, &handle_data);
    {
        VVL_ZoneScopedN("PreCallRecord_BeginCommandBuffer");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallRecordBeginCommandBuffer]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordBeginCommandBuffer(commandBuffer, pBeginInfo, record_obj);
        }
    }

    VkResult result;
    {
        VVL_ZoneScopedN("Dispatch_BeginCommandBuffer");
        result = device_dispatch->BeginCommandBuffer(commandBuffer, pBeginInfo);
    }
    record_obj.result = result;

    {
        VVL_ZoneScopedN("PostCallRecord_BeginCommandBuffer");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPostCallRecordBeginCommandBuffer]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordBeginCommandBuffer(commandBuffer, pBeginInfo, record_obj);
        }
    }
    return result;
}

// Handle tooling queries manually as this is a request for layer information
static const VkPhysicalDeviceToolPropertiesEXT khronos_layer_tool_props = {
    VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TOOL_PROPERTIES_EXT,
    nullptr,
    "Khronos Validation Layer",
    STRINGIFY(VK_HEADER_VERSION),
    VK_TOOL_PURPOSE_VALIDATION_BIT | VK_TOOL_PURPOSE_ADDITIONAL_FEATURES_BIT | VK_TOOL_PURPOSE_DEBUG_REPORTING_BIT_EXT |
        VK_TOOL_PURPOSE_DEBUG_MARKERS_BIT_EXT,
    "Khronos Validation Layer",
    OBJECT_LAYER_NAME};

VKAPI_ATTR VkResult VKAPI_CALL GetPhysicalDeviceToolPropertiesEXT(VkPhysicalDevice physicalDevice, uint32_t* pToolCount,
                                                                  VkPhysicalDeviceToolPropertiesEXT* pToolProperties) {
    auto instance_dispatch = vvl::GetDispatchInstance(physicalDevice);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkGetPhysicalDeviceToolPropertiesEXT,
                          VulkanTypedHandle(physicalDevice, kVulkanObjectTypePhysicalDevice));

    auto original_pToolProperties = pToolProperties;

    if (pToolProperties != nullptr && *pToolCount > 0) {
        *pToolProperties = khronos_layer_tool_props;
        pToolProperties = ((*pToolCount > 1) ? &pToolProperties[1] : nullptr);
        (*pToolCount)--;
    }

    for (const auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        skip |= vo->PreCallValidateGetPhysicalDeviceToolPropertiesEXT(physicalDevice, pToolCount, pToolProperties, error_obj);
        if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
    }

    RecordObject record_obj(vvl::Func::vkGetPhysicalDeviceToolPropertiesEXT);
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PreCallRecordGetPhysicalDeviceToolPropertiesEXT(physicalDevice, pToolCount, pToolProperties, record_obj);
    }

    VkResult result = instance_dispatch->GetPhysicalDeviceToolPropertiesEXT(physicalDevice, pToolCount, pToolProperties);
    record_obj.result = result;

    if (original_pToolProperties != nullptr) {
        pToolProperties = original_pToolProperties;
    }
    assert(*pToolCount != vvl::kU32Max);
    (*pToolCount)++;

    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PostCallRecordGetPhysicalDeviceToolPropertiesEXT(physicalDevice, pToolCount, pToolProperties, record_obj);
    }
    return result;
}

VKAPI_ATTR VkResult VKAPI_CALL GetPhysicalDeviceToolProperties(VkPhysicalDevice physicalDevice, uint32_t* pToolCount,
                                                               VkPhysicalDeviceToolProperties* pToolProperties) {
    auto instance_dispatch = vvl::GetDispatchInstance(physicalDevice);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkGetPhysicalDeviceToolProperties,
                          VulkanTypedHandle(physicalDevice, kVulkanObjectTypePhysicalDevice));

    auto original_pToolProperties = pToolProperties;

    if (pToolProperties != nullptr && *pToolCount > 0) {
        *pToolProperties = khronos_layer_tool_props;
        pToolProperties = ((*pToolCount > 1) ? &pToolProperties[1] : nullptr);
        (*pToolCount)--;
    }

    for (const auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        skip |= vo->PreCallValidateGetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties, error_obj);
        if (skip) return VK_ERROR_VALIDATION_FAILED_EXT;
    }

    RecordObject record_obj(vvl::Func::vkGetPhysicalDeviceToolProperties);
    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PreCallRecordGetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties, record_obj);
    }

    VkResult result = instance_dispatch->GetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties);
    record_obj.result = result;

    if (original_pToolProperties != nullptr) {
        pToolProperties = original_pToolProperties;
    }
    assert(*pToolCount != vvl::kU32Max);
    (*pToolCount)++;

    for (auto& vo : instance_dispatch->object_dispatch) {
        if (!vo) {
            continue;
        }
        vo->PostCallRecordGetPhysicalDeviceToolProperties(physicalDevice, pToolCount, pToolProperties, record_obj);
    }
    return result;
}

VKAPI_ATTR void VKAPI_CALL CmdBindDescriptorBuffersEXT(VkCommandBuffer commandBuffer, uint32_t bufferCount,
                                                       const VkDescriptorBufferBindingInfoEXT* pBindingInfos) {
    VVL_ZoneScoped;

    auto device_dispatch = vvl::GetDispatchDevice(commandBuffer);
    bool skip = false;
    ErrorObject error_obj(vvl::Func::vkCmdBindDescriptorBuffersEXT,
                          VulkanTypedHandle(commandBuffer, kVulkanObjectTypeCommandBuffer));
    {
        VVL_ZoneScopedN("PreCallValidate_vkCmdBindDescriptorBuffersEXT");
        for (const auto& vo : device_dispatch->intercept_vectors[InterceptIdPreCallValidateCmdBindDescriptorBuffersEXT]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->ReadLock();
            skip |= vo->PreCallValidateCmdBindDescriptorBuffersEXT(commandBuffer, bufferCount, pBindingInfos, error_obj);
            if (skip) {
                return;
            }
        }
    }

    chassis::CmdBindDescriptorBuffers chassis_state(bufferCount, pBindingInfos);

    RecordObject record_obj(vvl::Func::vkCmdBindDescriptorBuffersEXT);
    {
        VVL_ZoneScopedN("PreCallRecord_vkCmdBindDescriptorBuffersEXT");
        for (auto& vo : device_dispatch->object_dispatch) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PreCallRecordCmdBindDescriptorBuffersEXT(commandBuffer, bufferCount, pBindingInfos, record_obj, chassis_state);
        }
    }

    // Only done in GPU-AV
    const uint32_t modified_count = (uint32_t)chassis_state.modified_binding_infos.size();
    {
        VVL_ZoneScopedN("Dispatch_vkCmdBindDescriptorBuffersEXT");
        device_dispatch->CmdBindDescriptorBuffersEXT(commandBuffer, modified_count, chassis_state.pBindInfos);
    }
    {
        VVL_ZoneScopedN("PostCallRecord_vkCmdBindDescriptorBuffersEXT");
        for (auto& vo : device_dispatch->intercept_vectors[InterceptIdPostCallRecordCmdBindDescriptorBuffersEXT]) {
            if (!vo) {
                continue;
            }
            auto lock = vo->WriteLock();
            vo->PostCallRecordCmdBindDescriptorBuffersEXT(commandBuffer, modified_count, chassis_state.pBindInfos, record_obj);
        }
    }
}

// ValidationCache APIs do not dispatch

VKAPI_ATTR VkResult VKAPI_CALL CreateValidationCacheEXT(VkDevice device, const VkValidationCacheCreateInfoEXT* pCreateInfo,
                                                        const VkAllocationCallbacks* pAllocator,
                                                        VkValidationCacheEXT* pValidationCache) {
    auto device_dispatch = vvl::GetDispatchDevice(device);
    if (auto core_checks = static_cast<CoreChecks*>(device_dispatch->GetValidationObject(LayerObjectTypeCoreValidation))) {
        auto lock = core_checks->WriteLock();
        return core_checks->CoreLayerCreateValidationCacheEXT(device, pCreateInfo, pAllocator, pValidationCache);
    }
    return VK_SUCCESS;
}

VKAPI_ATTR void VKAPI_CALL DestroyValidationCacheEXT(VkDevice device, VkValidationCacheEXT validationCache,
                                                     const VkAllocationCallbacks* pAllocator) {
    auto device_dispatch = vvl::GetDispatchDevice(device);
    if (auto core_checks = static_cast<CoreChecks*>(device_dispatch->GetValidationObject(LayerObjectTypeCoreValidation))) {
        auto lock = core_checks->WriteLock();
        core_checks->CoreLayerDestroyValidationCacheEXT(device, validationCache, pAllocator);
    }
}

VKAPI_ATTR VkResult VKAPI_CALL MergeValidationCachesEXT(VkDevice device, VkValidationCacheEXT dstCache, uint32_t srcCacheCount,
                                                        const VkValidationCacheEXT* pSrcCaches) {
    auto device_dispatch = vvl::GetDispatchDevice(device);
    if (auto core_checks = static_cast<CoreChecks*>(device_dispatch->GetValidationObject(LayerObjectTypeCoreValidation))) {
        auto lock = core_checks->WriteLock();
        return core_checks->CoreLayerMergeValidationCachesEXT(device, dstCache, srcCacheCount, pSrcCaches);
    }
    return VK_SUCCESS;
}

VKAPI_ATTR VkResult VKAPI_CALL GetValidationCacheDataEXT(VkDevice device, VkValidationCacheEXT validationCache, size_t* pDataSize,
                                                         void* pData) {
    auto device_dispatch = vvl::GetDispatchDevice(device);
    if (auto core_checks = static_cast<CoreChecks*>(device_dispatch->GetValidationObject(LayerObjectTypeCoreValidation))) {
        auto lock = core_checks->WriteLock();
        return core_checks->CoreLayerGetValidationCacheDataEXT(device, validationCache, pDataSize, pData);
    }
    return VK_SUCCESS;
}
}  // namespace vulkan_layer_chassis
