/*
 * Copyright 2017 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "include/private/SkMacros.h"
#include "src/gpu/vk/VulkanInterface.h"
#include "tools/gpu/vk/VkTestMemoryAllocator.h"
#include "tools/gpu/vk/VkTestUtils.h"

#include <functional>

#ifdef SK_VULKAN

#ifndef SK_GPU_TOOLS_VK_LIBRARY_NAME
    #if defined _WIN32
        #define SK_GPU_TOOLS_VK_LIBRARY_NAME vulkan-1.dll
    #elif defined SK_BUILD_FOR_MAC
        #define SK_GPU_TOOLS_VK_LIBRARY_NAME libvk_swiftshader.dylib
    #else
        #define SK_GPU_TOOLS_VK_LIBRARY_NAME        libvulkan.so
        #define SK_GPU_TOOLS_VK_LIBRARY_NAME_BACKUP libvulkan.so.1
    #endif
#endif

#include <algorithm>

#if defined(__GLIBC__)
#include <execinfo.h>
#endif
#include "include/gpu/vk/VulkanBackendContext.h"
#include "include/gpu/vk/VulkanExtensions.h"
#include "src/core/SkAutoMalloc.h"
#include "tools/library/LoadDynamicLibrary.h"

#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
#include <sanitizer/lsan_interface.h>
#endif

using namespace skia_private;

namespace sk_gpu_test {

bool LoadVkLibraryAndGetProcAddrFuncs(PFN_vkGetInstanceProcAddr* instProc) {
    static void* vkLib = nullptr;
    static PFN_vkGetInstanceProcAddr localInstProc = nullptr;
    if (!vkLib) {
        vkLib = SkLoadDynamicLibrary(SK_MACRO_STRINGIFY(SK_GPU_TOOLS_VK_LIBRARY_NAME));
        if (!vkLib) {
            // vulkaninfo tries to load the library from two places, so we do as well
            // https://github.com/KhronosGroup/Vulkan-Tools/blob/078d44e4664b7efa0b6c96ebced1995c4425d57a/vulkaninfo/vulkaninfo.h#L249
#ifdef SK_GPU_TOOLS_VK_LIBRARY_NAME_BACKUP
            vkLib = SkLoadDynamicLibrary(SK_MACRO_STRINGIFY(SK_GPU_TOOLS_VK_LIBRARY_NAME_BACKUP));
            if (!vkLib) {
                return false;
            }
#else
            return false;
#endif
        }
        localInstProc = (PFN_vkGetInstanceProcAddr) SkGetProcedureAddress(vkLib,
                                                                          "vkGetInstanceProcAddr");
    }
    if (!localInstProc) {
        return false;
    }
    *instProc = localInstProc;
    return true;
}

////////////////////////////////////////////////////////////////////////////////
// Helper code to set up Vulkan context objects

#ifdef SK_ENABLE_VK_LAYERS

static void print_backtrace() {
#if defined(__GLIBC__)
    void* stack[64];
    int count = backtrace(stack, std::size(stack));
    backtrace_symbols_fd(stack, count, 2);
#else
    // Please add implementations for other platforms.
#endif
}

VKAPI_ATTR VkBool32 VKAPI_CALL
DebugUtilsMessenger(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
                    VkDebugUtilsMessageTypeFlagsEXT messageTypes,
                    const VkDebugUtilsMessengerCallbackDataEXT* callbackData,
                    void* userData) {
    // VUID-VkDebugUtilsMessengerCallbackDataEXT-pMessage-parameter
    // pMessage must be a null-terminated UTF-8 string
    SkASSERT(callbackData->pMessage != nullptr);

    static constexpr const char* kSkippedMessages[] = {
            "Nothing for now, this string works around msvc bug with empty array",
    };

    // See if it's an issue we are aware of and don't want to be spammed about.
    // Always report the debug message if message ID is missing
    if (callbackData->pMessageIdName != nullptr) {
        for (const char* skipped : kSkippedMessages) {
            if (strstr(callbackData->pMessageIdName, skipped) != nullptr) {
                return VK_FALSE;
            }
        }
    }

    bool printStackTrace = true;
    bool fail = false;

    const char* severity = "message";
    if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) != 0) {
        severity = "error";
        fail = true;
    } else if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) != 0) {
        severity = "warning";
    } else if ((messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) != 0) {
        severity = "info";
        printStackTrace = false;
    }

    std::string type;
    if ((messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) != 0) {
        type += " <general>";
    }
    if ((messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) != 0) {
        type += " <validation>";
    }
    if ((messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) != 0) {
        type += " <performance>";
    }

    SkDebugf("Vulkan %s%s [%s]: %s\n",
             severity,
             type.c_str(),
             callbackData->pMessageIdName ? callbackData->pMessageIdName : "<no id>",
             callbackData->pMessage);

    if (printStackTrace) {
        print_backtrace();
    }

    if (fail) {
        SkDEBUGFAIL("Vulkan debug layer error");
    }

    return VK_FALSE;
}
#endif

#define ACQUIRE_VK_INST_PROC_LOCAL(name, instance)                                 \
    PFN_vk##name grVk##name =                                                      \
        reinterpret_cast<PFN_vk##name>(getInstProc(instance, "vk" #name));         \
    do {                                                                           \
        if (grVk##name == nullptr) {                                               \
            SkDebugf("Function ptr for vk%s could not be acquired\n", #name);      \
            return false;                                                          \
        }                                                                          \
    } while (0)

#define GET_PROC_LOCAL(F, inst, device) PFN_vk ## F F = (PFN_vk ## F) getProc("vk" #F, inst, device)

#define ACQUIRE_VK_INST_PROC_NOCHECK(name, instance) \
    PFN_vk##name grVk##name = reinterpret_cast<PFN_vk##name>(getInstProc(instance, "vk" #name))

#define ACQUIRE_VK_INST_PROC(name, instance)                                                     \
    PFN_vk##name grVk##name = reinterpret_cast<PFN_vk##name>(getInstProc(instance, "vk" #name)); \
    do {                                                                                         \
        if (grVk##name == nullptr) {                                                             \
            SkDebugf("Function ptr for vk%s could not be acquired\n", #name);                    \
            if (inst != VK_NULL_HANDLE) {                                                        \
                destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);          \
            }                                                                                    \
            return false;                                                                        \
        }                                                                                        \
    } while (0)

#define ACQUIRE_VK_PROC_NOCHECK(name, instance, device) \
    PFN_vk##name grVk##name = reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device))

#define ACQUIRE_VK_PROC(name, instance, device)                                         \
    PFN_vk##name grVk##name =                                                           \
            reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device));      \
    do {                                                                                \
        if (grVk##name == nullptr) {                                                    \
            SkDebugf("Function ptr for vk%s could not be acquired\n", #name);           \
            if (inst != VK_NULL_HANDLE) {                                               \
                destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension); \
            }                                                                           \
            return false;                                                               \
        }                                                                               \
    } while (0)

#define ACQUIRE_VK_PROC_LOCAL(name, instance, device)                              \
    PFN_vk##name grVk##name =                                                      \
            reinterpret_cast<PFN_vk##name>(getProc("vk" #name, instance, device)); \
    do {                                                                           \
        if (grVk##name == nullptr) {                                               \
            SkDebugf("Function ptr for vk%s could not be acquired\n", #name);      \
            return false;                                                          \
        }                                                                          \
    } while (0)

static bool destroy_instance(PFN_vkGetInstanceProcAddr getInstProc,
                             VkInstance inst,
                             VkDebugUtilsMessengerEXT* debugMessenger,
                             bool hasDebugExtension) {
    if (hasDebugExtension && *debugMessenger != VK_NULL_HANDLE) {
        ACQUIRE_VK_INST_PROC_LOCAL(DestroyDebugUtilsMessengerEXT, inst);
        grVkDestroyDebugUtilsMessengerEXT(inst, *debugMessenger, nullptr);
        *debugMessenger = VK_NULL_HANDLE;
    }
    ACQUIRE_VK_INST_PROC_LOCAL(DestroyInstance, inst);
    grVkDestroyInstance(inst, nullptr);
    return true;
}
namespace {

uint32_t remove_patch_version(uint32_t specVersion) {
    return (specVersion >> 12) << 12;
}

#if defined (SK_ENABLE_VK_LAYERS)
// Checks a list of availableLayers for layers we want to use (kDebugLayerNames). If the desired
// layers are available and compatible with our Vulkan version, append their VkLayerProperties to
// layersToUse.
void append_desired_layers(const VkLayerProperties* availableLayers,
                           const uint32_t availLayerCount,
                           const uint32_t majorSpecVersion,
                           TArray<VkLayerProperties>& layersToUse,
                           TArray<const char*>& layersToUseNames) {
    // Comment out or include different debugging layers by modifying the array below:
    const char* kDebugLayerNames[] = {
        "VK_LAYER_KHRONOS_validation",
        // Not included in standard_validation:
        // "VK_LAYER_LUNARG_api_dump",
        // "VK_LAYER_LUNARG_vktrace",
        // "VK_LAYER_LUNARG_screenshot",
    };

    // Check for each desired debug layer within the list of available layers. If we encounter a
    // match, verify that the layer was authored against a version >= the major Vulkan version we
    // are using before appending it to the list of layers to use. This ensures the validation
    // layers will have all the expected API entry points in order to intercept Vulkan calls.
    for (size_t i = 0; i < std::size(kDebugLayerNames); ++i) {
        for (size_t j = 0; j < availLayerCount; ++j) {
            if (!strcmp(kDebugLayerNames[i], availableLayers[j].layerName) &&
                majorSpecVersion <= remove_patch_version(availableLayers[j].specVersion)) {
                layersToUse.push_back(availableLayers[i]);
                layersToUseNames.push_back(kDebugLayerNames[i]);
            }
        }
    }
}

bool append_desired_available_layers(std::function<VkResult(VkLayerProperties*)> QueryLayers,
                                     uint32_t* availLayerCount,
                                     uint32_t majorSpecVersion,
                                     TArray<VkLayerProperties>& layersToEnable,
                                     TArray<const char*>& enabledLayerNames) {
    if (QueryLayers(nullptr) != VK_SUCCESS) {
        return false;
    }
    VkLayerProperties* availLayers = new VkLayerProperties[*availLayerCount];
    if (QueryLayers(availLayers) != VK_SUCCESS) {
        delete[] availLayers;
        return false;
    }

    append_desired_layers(
            availLayers, *availLayerCount, majorSpecVersion, layersToEnable, enabledLayerNames);

    delete[] availLayers;
    return true;
}

// Given a list of enabled layers and a ptr to the Vulkan driver call to query layer extension
// information, tack on to the list and quantity of available extensions.
bool add_enabled_layer_extensions_to_available_list(
        uint32_t* availExtCount,
        TArray<VkExtensionProperties>& availExtensions,
        const TArray<VkLayerProperties>& layersToEnable,
        std::function<VkResult(const char*, uint32_t*, VkExtensionProperties*)> getExtensions) {
    // Add extensions from layers we intend to enable to our list of available extensions
    for (const auto& layer : layersToEnable) {
        uint32_t layerAvailExtCount = 0;
        if (getExtensions(layer.layerName, &layerAvailExtCount, nullptr) != VK_SUCCESS) {
            return false;
        }

        // Pushing default entries that the query call can write over preserves TArray size
        // attribute accuracy as opposed to reserve calls which only update capacity (useful for
        // for/each loops, not having to separately track + pass around more size values, etc.).
        availExtensions.push_back_n(layerAvailExtCount);
        if (getExtensions(layer.layerName,
                          &layerAvailExtCount,
                          &availExtensions[*availExtCount]) != VK_SUCCESS) {
            return false;
        }
        *availExtCount += layerAvailExtCount;
    }

    return true;
}
#endif // SK_ENABLE_VK_LAYERS

bool should_include_extension(const char* extensionName) {
    const char* kExtensionsForTests[] = {
            VK_EXT_DEBUG_UTILS_EXTENSION_NAME,
            VK_EXT_DEVICE_FAULT_EXTENSION_NAME,
            VK_EXT_LAYER_SETTINGS_EXTENSION_NAME,
            VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME,
            VK_KHR_SURFACE_EXTENSION_NAME,
            VK_KHR_SWAPCHAIN_EXTENSION_NAME,
            // Currently only used by Ganesh:
            VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME,
            VK_KHR_IMAGE_FORMAT_LIST_EXTENSION_NAME, // Required for above extension if using 1.1
            VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME,
            // Below are all platform specific extensions. The name macros like we use above are
            // all defined in platform specific vulkan headers. We currently don't include these
            // headers as they are a little bit of a pain (e.g. windows headers requires including
            // <windows.h> which causes all sorts of fun annoyances/problems. So instead we are
            // just listing the strings these macros are defined to. This really shouldn't cause
            // any long term issues as the chances of the strings connected to the name macros
            // changing is next to zero.
            "VK_KHR_win32_surface",  // VK_KHR_WIN32_SURFACE_EXTENSION_NAME
            "VK_KHR_xcb_surface",    // VK_KHR_XCB_SURFACE_EXTENSION_NAME,
            "VK_KHR_android_surface",  // VK_KHR_ANDROID_SURFACE_EXTENSION_NAME,
    };

    for (size_t i = 0; i < std::size(kExtensionsForTests); i++) {
        if (!strcmp(extensionName, kExtensionsForTests[i])) {
            return true;
        }
    }

    return false;
}

// Checks a list of availExtensions, populating a list of all available extension names. If we see
// an extension we want to enable, add the extension and its name to the relvant enabled
// extensions + extension names lists.
void append_desired_extensions(TArray<VkExtensionProperties>& availableExtensions,
                               TArray<const char*>& availExtNames,
                               TArray<VkExtensionProperties>& extensionsToEnable,
                               std::vector<const char*>& enabledExtNames) {
    for (const auto& ext : availableExtensions) {
        availExtNames.push_back(ext.extensionName);
        if (should_include_extension(ext.extensionName)) {
            extensionsToEnable.push_back(ext);
            enabledExtNames.push_back(ext.extensionName);
        }
    }
}

// Provided a function ptr with which to perform the Vulkan query, populate the quantity and
// extension properties of all available extensions.
bool get_avail_extensions(
        std::function<VkResult(const char*, uint32_t*, VkExtensionProperties*)> QueryExtensions,
        TArray<VkExtensionProperties>& availExtensions,
        uint32_t* availExtCount) {
    if (QueryExtensions(nullptr, availExtCount, nullptr) != VK_SUCCESS) {
        return false;
    }

    // Pushing default entries that the query call can write over preserves TArray size attribute
    // accuracy as opposed to reserve calls which only update capacity (useful for for/each loops,
    // not having to separately track + pass around more size values, etc.).
    availExtensions.push_back_n(*availExtCount);
    if (QueryExtensions(nullptr, availExtCount, availExtensions.begin()) != VK_SUCCESS) {
        return false;
    }

    return true;
}

// Determine all available instance extensions, layers, and those layers' extensions. From that,
// decide which layers to include. Also decide which instance extensions to enable, making sure to
// allow Skia to add to that list. Returns true upon success; false otherwise.
bool init_instance_extensions_and_layers(PFN_vkGetInstanceProcAddr getInstProc,
                                         uint32_t majorSpecVersion,
                                         TArray<VkExtensionProperties>& availableInstExts,
                                         TArray<const char*>& availInstExtNames,
                                         TArray<VkExtensionProperties>& instExtensionsToEnable,
                                         std::vector<const char*>& enabledInstExtNames,
                                         TArray<VkLayerProperties>& instLayersToEnable,
                                         TArray<const char*>& enabledInstLayerNames,
                                         skgpu::VulkanPreferredFeatures& skiaFeatures) {
    // First populate the list of all available extensions
    ACQUIRE_VK_INST_PROC_LOCAL(EnumerateInstanceExtensionProperties, VK_NULL_HANDLE);
    uint32_t availExtCount = 0;
    auto getExtensions = [&](const char* layerName,
                             uint32_t* extensionCount,
                             VkExtensionProperties* extensionList) {
        return grVkEnumerateInstanceExtensionProperties(layerName, extensionCount, extensionList);
    };
    if (!get_avail_extensions(getExtensions, availableInstExts, &availExtCount)) {
        return false;
    }

#if defined SK_ENABLE_VK_LAYERS
    ACQUIRE_VK_INST_PROC_LOCAL(EnumerateInstanceLayerProperties, VK_NULL_HANDLE);
    uint32_t availLayerCount = 0;

    auto getLayers = [&](VkLayerProperties* layersList) {
        return grVkEnumerateInstanceLayerProperties(&availLayerCount, layersList);
    };
    if (!append_desired_available_layers(getLayers,
                                         &availLayerCount,
                                         majorSpecVersion,
                                         instLayersToEnable,
                                         enabledInstLayerNames)) {
        return false;
    }

    if (!add_enabled_layer_extensions_to_available_list(
            &availExtCount, availableInstExts, instLayersToEnable, getExtensions)) {
        return false;
    }
#endif

    // Now that we have compiled a list of all available instance extensions, populate a list of
    // ones we actually want to enable.
    append_desired_extensions(
            availableInstExts, availInstExtNames, instExtensionsToEnable, enabledInstExtNames);

    // Allow Skia the chance to add to the list of enabled instance extensions.
    skiaFeatures.addToInstanceExtensions(
            availableInstExts.data(), availExtCount, enabledInstExtNames);

    return true;
}

void setup_feature_query(TestVkFeatures& testFeatures, bool isProtected) {
    // Note: Any structs chained on to the feature query must stay in scope until vkCreateDevice.
    // This is why these structs are located in TestVkFeatures and passed into this function.

    // Initialize physical device feature query struct
    testFeatures.deviceFeatures = {};
    testFeatures.deviceFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;

    // Add features that would be useful to enable for testing to the feature query.
    void** tailPNext = &testFeatures.deviceFeatures.pNext;

    // If |isProtected| is given, attach that first
    testFeatures.protectedMemoryFeatures = {};
    if (isProtected) {
        testFeatures.protectedMemoryFeatures.sType =
                VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_FEATURES;
        *tailPNext = &testFeatures.protectedMemoryFeatures;
        tailPNext = &testFeatures.protectedMemoryFeatures.pNext;
    }
}

bool init_device_extensions_and_layers(const skgpu::VulkanGetProc& getProc,
                                       uint32_t majorSpecVersion,
                                       VkInstance inst,
                                       VkPhysicalDevice physDev,
                                       TArray<VkExtensionProperties>& availableDevExts,
                                       TArray<const char*>& availDevExtNames,
                                       TArray<VkExtensionProperties>& devExtsToEnable,
                                       std::vector<const char*>& enabledDevExtNames,
                                       TArray<VkLayerProperties>& devLayersToEnable,
                                       TArray<const char*>& enabledDevLayerNames,
                                       TestVkFeatures& testFeatures,
                                       bool isProtected) {
    // First populate the list of all available extensions
    GET_PROC_LOCAL(EnumerateDeviceExtensionProperties, inst, VK_NULL_HANDLE);
    uint32_t availExtCount = 0;
    auto getExtensions = [&](const char* layerName,
                             uint32_t* extensionCount,
                             VkExtensionProperties* extensionList) {
        return EnumerateDeviceExtensionProperties(
                physDev, layerName, extensionCount, extensionList);
    };
    if (!get_avail_extensions(getExtensions, availableDevExts, &availExtCount)) {
        return false;
    }

#if defined SK_ENABLE_VK_LAYERS
    GET_PROC_LOCAL(EnumerateDeviceLayerProperties, inst, VK_NULL_HANDLE);
    uint32_t availLayerCount = 0;

    auto getLayers = [&](VkLayerProperties* layersList) {
        return EnumerateDeviceLayerProperties(physDev, &availLayerCount, layersList);
    };
    append_desired_available_layers(
            getLayers, &availLayerCount, majorSpecVersion, devLayersToEnable, enabledDevLayerNames);

    if (!add_enabled_layer_extensions_to_available_list(
            &availExtCount, availableDevExts, devLayersToEnable, getExtensions)) {
        return false;
    }
#endif

    // Now that we have compiled a list of all available device extensions, populate a list of
    // ones we actually want to enable.
    append_desired_extensions(
            availableDevExts, availDevExtNames, devExtsToEnable, enabledDevExtNames);

    // First, add any features we want for testing to the query. Then allow Skia to add to it.
    setup_feature_query(testFeatures, isProtected);

    testFeatures.skiaFeatures.addFeaturesToQuery(
            availableDevExts.data(), availExtCount, testFeatures.deviceFeatures);

    return true;
}
} // anonymous namespace

bool CreateVkBackendContext(PFN_vkGetInstanceProcAddr getInstProc,
                            skgpu::VulkanBackendContext* ctx,
                            skgpu::VulkanExtensions* extensions,
                            TestVkFeatures* testVkFeatures,
                            VkDebugUtilsMessengerEXT* debugMessenger,
                            uint32_t* presentQueueIndexPtr,
                            const CanPresentFn& canPresent,
                            bool isProtected) {
    if (!getInstProc || !testVkFeatures) {
        return false;
    }

    VkResult err;

    ACQUIRE_VK_INST_PROC_NOCHECK(EnumerateInstanceVersion, VK_NULL_HANDLE);
    uint32_t instanceVersion = 0;
    // Vulkan 1.1 is required, so vkEnumerateInstanceVersion should always be available.
    SkASSERT(grVkEnumerateInstanceVersion != nullptr);
    err = grVkEnumerateInstanceVersion(&instanceVersion);
    if (err) {
        SkDebugf("failed to enumerate instance version. Err: %d\n", err);
        return false;
    }
    SkASSERT(instanceVersion >= VK_API_VERSION_1_1);

    // We could set the Vulkan API version to be the newest version supported by Skia, but for now,
    // set it to 1.1 (the most common Vulkan version on Android devices).
    static const uint32_t kApiVersion = VK_API_VERSION_1_1;
    instanceVersion = std::min(instanceVersion, kApiVersion);

    STArray<2, VkPhysicalDevice> physDevs;
    VkDevice device;
    VkInstance inst = VK_NULL_HANDLE;

    static constexpr VkApplicationInfo appInfo = { VK_STRUCTURE_TYPE_APPLICATION_INFO,
                                                   /*pNext=*/nullptr,
                                                   "vkTestApp",
                                                   /*applicationVersion=*/0,
                                                   "vkTestEngine",
                                                   /*engineVerison=*/0,
                                                   kApiVersion };
    // Initialize VulkanPreferredFeatures using whatever API value matches that of the version
    // assigned to VkApplicationInfo.
    testVkFeatures->skiaFeatures.init(kApiVersion);

    // Determine all available instance extensions - which is used by Skia's VulkanPreferredFeatures
    // and VulkanExtensions - in addition to which instance extensions we want to enable for
    // testing. Skia and/or Vulkan API calls eventually expect to be provided with a list of
    // extension/layer *names*, so populate those lists at the same time.
    TArray<VkExtensionProperties> availableInstExts;
    TArray<const char*> availInstExtNames;
    TArray<VkExtensionProperties> instExtsToEnable;
    std::vector<const char*> enabledInstExtNames;
    // For layers, however, we can get away with only tracking which ones we want to enable
    // since Skia doesn't currently parse all available layers in order to make any decisions.
    TArray<VkLayerProperties> instLayersToEnable;
    TArray<const char*> enabledInstLayerNames;
    if (!init_instance_extensions_and_layers(getInstProc,
                                             remove_patch_version(instanceVersion),
                                             availableInstExts,
                                             availInstExtNames,
                                             instExtsToEnable,
                                             enabledInstExtNames,
                                             instLayersToEnable,
                                             enabledInstLayerNames,
                                             testVkFeatures->skiaFeatures)) {
        return false;
    }

    // Set up VkInstanceCreateInfo
    VkInstanceCreateInfo createInstanceInfo = { VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
                                                /*pNext=*/nullptr,
                                                /*flags=*/0,
                                                &appInfo,
                                                (uint32_t)enabledInstLayerNames.size(),
                                                enabledInstLayerNames.data(),
                                                (uint32_t)enabledInstExtNames.size(),
                                                enabledInstExtNames.data() };
    bool hasDebugExtension = false;
    *debugMessenger = VK_NULL_HANDLE;

#ifdef SK_ENABLE_VK_LAYERS
    bool hasLayerSettingsExt = false;
    for (size_t i = 0;
         i < enabledInstExtNames.size() && !hasDebugExtension && !hasLayerSettingsExt; ++i) {
        if (!strcmp(enabledInstExtNames[i], VK_EXT_DEBUG_UTILS_EXTENSION_NAME)) {
            hasDebugExtension = true;
        } else if (!strcmp(enabledInstExtNames[i], VK_EXT_LAYER_SETTINGS_EXTENSION_NAME)) {
            hasLayerSettingsExt = true;
        }
    }

    // Fine grain control of validation layer features
    const char* name = "VK_LAYER_KHRONOS_validation";
    const VkBool32 settingValidateCore = VK_TRUE;
    // Syncval is disabled for now, but would be useful to enable eventually.
    const VkBool32 settingValidateSync = VK_FALSE;
    const VkBool32 settingThreadSafety = VK_TRUE;
    // Shader validation could be useful (previously broken on Android, might already be fixed:
    // http://anglebug.com/42265520).
    const VkBool32 settingCheckShaders = VK_FALSE;
    // If syncval is enabled, submit time validation could stay disabled due to performance issues:
    // https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/7285
    const VkBool32 settingSyncvalSubmitTimeValidation = VK_FALSE;
    // Extra properties in syncval make it easier to filter the messages.
    const VkBool32 settingSyncvalMessageExtraProperties = VK_TRUE;
    const VkLayerSettingEXT layerSettings[] = {
            {name, "validate_core", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &settingValidateCore},
            {name, "validate_sync", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &settingValidateSync},
            {name, "thread_safety", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &settingThreadSafety},
            {name, "check_shaders", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &settingCheckShaders},
            {name,
             "syncval_submit_time_validation",
             VK_LAYER_SETTING_TYPE_BOOL32_EXT,
             1,
             &settingSyncvalSubmitTimeValidation},
            {name,
             "syncval_message_extra_properties",
             VK_LAYER_SETTING_TYPE_BOOL32_EXT,
             1,
             &settingSyncvalMessageExtraProperties},
    };
    VkLayerSettingsCreateInfoEXT layerSettingsCreateInfo = {};
    layerSettingsCreateInfo.sType = VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT;
    layerSettingsCreateInfo.settingCount = static_cast<uint32_t>(std::size(layerSettings));
    layerSettingsCreateInfo.pSettings = layerSettings;
    if (hasDebugExtension && hasLayerSettingsExt) {
        createInstanceInfo.pNext = &layerSettingsCreateInfo;
    }
#endif

    ACQUIRE_VK_INST_PROC(CreateInstance, VK_NULL_HANDLE);
    err = grVkCreateInstance(&createInstanceInfo, nullptr, &inst);
    if (err < 0) {
        SkDebugf("vkCreateInstance failed: %d\n", err);
        return false;
    }

    ACQUIRE_VK_INST_PROC(GetDeviceProcAddr, inst);
    auto getProc = [getInstProc, grVkGetDeviceProcAddr](const char* proc_name,
                                                        VkInstance instance, VkDevice device) {
        if (device != VK_NULL_HANDLE) {
            return grVkGetDeviceProcAddr(device, proc_name);
        }
        return getInstProc(instance, proc_name);
    };

#ifdef SK_ENABLE_VK_LAYERS
    if (hasDebugExtension) {
        VkDebugUtilsMessengerCreateInfoEXT messengerInfo = {};

        constexpr VkDebugUtilsMessageSeverityFlagsEXT kSeveritiesToLog =
                VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
                VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;

        constexpr VkDebugUtilsMessageTypeFlagsEXT kMessagesToLog =
                VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
                VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
                VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;

        messengerInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
        messengerInfo.messageSeverity = kSeveritiesToLog;
        messengerInfo.messageType = kMessagesToLog;
        messengerInfo.pfnUserCallback = &DebugUtilsMessenger;

        ACQUIRE_VK_PROC(CreateDebugUtilsMessengerEXT, inst, VK_NULL_HANDLE);
        // Register the callback
        grVkCreateDebugUtilsMessengerEXT(inst, &messengerInfo, nullptr, debugMessenger);
    }
#endif

    ACQUIRE_VK_PROC(EnumeratePhysicalDevices, inst, VK_NULL_HANDLE);
    ACQUIRE_VK_PROC(GetPhysicalDeviceProperties, inst, VK_NULL_HANDLE);
    ACQUIRE_VK_PROC(GetPhysicalDeviceQueueFamilyProperties, inst, VK_NULL_HANDLE);
    ACQUIRE_VK_PROC(CreateDevice, inst, VK_NULL_HANDLE);
    ACQUIRE_VK_PROC(GetDeviceQueue, inst, VK_NULL_HANDLE);
    ACQUIRE_VK_PROC(DeviceWaitIdle, inst, VK_NULL_HANDLE);
    ACQUIRE_VK_PROC(DestroyDevice, inst, VK_NULL_HANDLE);

    uint32_t gpuCount;
    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, nullptr);
    if (err) {
        SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err);
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }
    if (!gpuCount) {
        SkDebugf("vkEnumeratePhysicalDevices returned no supported devices.\n");
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }
    // Allocate enough storage for all available physical devices. We should be able to just ask for
    // the first one, but a bug in RenderDoc (https://github.com/baldurk/renderdoc/issues/2766)
    // will smash the stack if we do that.
    physDevs.resize(gpuCount);
    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, physDevs.data());
    if (err) {
        SkDebugf("vkEnumeratePhysicalDevices failed: %d\n", err);
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }
    // We just use the first physical device.
    // TODO: find best match for our needs
    VkPhysicalDevice physDev = physDevs.front();

    VkPhysicalDeviceProperties physDeviceProperties;
    grVkGetPhysicalDeviceProperties(physDev, &physDeviceProperties);
    uint32_t physDeviceVersion = std::min(physDeviceProperties.apiVersion, kApiVersion);

    // Now, for the device rather than the instance, repeat the process of gathering all available
    // extensions + their names, determining layers we want to enable for testing + their associated
    // available extensions, and determining which extensions we actually want to enable (+ their
    // names).
    TArray<VkExtensionProperties> availableDevExts;
    TArray<const char*> availDevExtNames;
    TArray<VkExtensionProperties> devExtsToEnable;
    std::vector<const char*> enabledDevExtNames;
    TArray<VkLayerProperties> devLayersToEnable;
    TArray<const char*> enabledDevLayerNames;
    if (!init_device_extensions_and_layers(getProc,
                                           remove_patch_version(physDeviceVersion),
                                           inst,
                                           physDev,
                                           availableDevExts,
                                           availDevExtNames,
                                           devExtsToEnable,
                                           enabledDevExtNames,
                                           devLayersToEnable,
                                           enabledDevLayerNames,
                                           *testVkFeatures,
                                           isProtected)) {
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }

    // Now that we have determined all available instance and device extensions, we can initialize
    // VulkanExtensions.
    extensions->init(getProc,
                     inst,
                     physDev,
                     (uint32_t)availInstExtNames.size(),
                     availInstExtNames.data(),
                     (uint32_t)availDevExtNames.size(),
                     availDevExtNames.data());

    // Finally, perform the query.
    ACQUIRE_VK_INST_PROC(GetPhysicalDeviceFeatures2, inst);
    grVkGetPhysicalDeviceFeatures2(physDev, &testVkFeatures->deviceFeatures);

    if (isProtected && !testVkFeatures->protectedMemoryFeatures.protectedMemory) {
        SkDebugf("Device does not support protected memory.\n");
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }

    // We can manually disable any undesired features here. Currently, we only disable robust
    // buffer access which can negatively impact performance on some GPUs.
    testVkFeatures->deviceFeatures.features.robustBufferAccess = VK_FALSE;

    // Allow Skia to enable available features
    testVkFeatures->skiaFeatures.addFeaturesToEnable(enabledDevExtNames,
                                                     testVkFeatures->deviceFeatures);

    // Query for queue property size, then fetch the properties.
    uint32_t queueCount;
    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
    if (!queueCount) {
        SkDebugf("vkGetPhysicalDeviceQueueFamilyProperties returned no queues.\n");
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }

    SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties));
    VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get();
    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueProps);

    // Iterate to find the graphics queue
    uint32_t graphicsQueueIndex = queueCount;
    for (uint32_t i = 0; i < queueCount; i++) {
        if (queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
            graphicsQueueIndex = i;
            break;
        }
    }
    if (graphicsQueueIndex == queueCount) {
        SkDebugf("Could not find any supported graphics queues.\n");
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }

    // Iterate to find the present queue, if needed
    uint32_t presentQueueIndex = queueCount;
    if (presentQueueIndexPtr && canPresent) {
        for (uint32_t i = 0; i < queueCount; i++) {
            if (canPresent(inst, physDev, i)) {
                presentQueueIndex = i;
                break;
            }
        }
        if (presentQueueIndex == queueCount) {
            SkDebugf("Could not find any supported present queues.\n");
            destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
            return false;
        }
        *presentQueueIndexPtr = presentQueueIndex;
    } else {
        // Just setting this so we end up make a single queue for graphics since there was no
        // request for a present queue.
        presentQueueIndex = graphicsQueueIndex;
    }

    VkDeviceQueueCreateFlags flags = isProtected ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0;
    float queuePriorities[1] = { 0.0 };
    // Here we assume no need for swapchain queue
    // If one is needed, the client will need its own setup code
    const VkDeviceQueueCreateInfo queueInfo[2] = {
        {
            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType
            nullptr,                                    // pNext
            flags,                                      // VkDeviceQueueCreateFlags
            graphicsQueueIndex,                         // queueFamilyIndex
            1,                                          // queueCount
            queuePriorities,                            // pQueuePriorities

        },
        {
            VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, // sType
            nullptr,                                    // pNext
            0,                                          // VkDeviceQueueCreateFlags
            presentQueueIndex,                          // queueFamilyIndex
            1,                                          // queueCount
            queuePriorities,                            // pQueuePriorities
        }
    };
    uint32_t queueInfoCount = (presentQueueIndex != graphicsQueueIndex) ? 2 : 1;

    const VkDeviceCreateInfo deviceInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
                                            &testVkFeatures->deviceFeatures,
                                            /*flags=*/0,
                                            queueInfoCount,
                                            queueInfo,
                                            (uint32_t) enabledDevLayerNames.size(),
                                            enabledDevLayerNames.data(),
                                            (uint32_t) enabledDevExtNames.size(),
                                            enabledDevExtNames.data(),
                                            /*ppEnabledFeatures*/nullptr };
    {
#if defined(SK_ENABLE_SCOPED_LSAN_SUPPRESSIONS)
        // skbug.com/40040003
        __lsan::ScopedDisabler lsanDisabler;
#endif
        err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
    }
    if (err) {
        SkDebugf("CreateDevice failed: %d\n", err);
        destroy_instance(getInstProc, inst, debugMessenger, hasDebugExtension);
        return false;
    }

    VkQueue queue;
    if (isProtected) {
        ACQUIRE_VK_PROC(GetDeviceQueue2, inst, device);
        SkASSERT(grVkGetDeviceQueue2 != nullptr);
        VkDeviceQueueInfo2 queue_info2 = {
            VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2,          // sType
            nullptr,                                        // pNext
            VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT,           // flags
            graphicsQueueIndex,                             // queueFamilyIndex
            0                                               // queueIndex
        };
        grVkGetDeviceQueue2(device, &queue_info2, &queue);
    } else {
        grVkGetDeviceQueue(device, graphicsQueueIndex, 0, &queue);
    }

    skgpu::VulkanInterface interface = skgpu::VulkanInterface(
            getProc, inst, device, instanceVersion, physDeviceVersion, extensions);
    SkASSERT(interface.validate(instanceVersion, physDeviceVersion, extensions));

    sk_sp<skgpu::VulkanMemoryAllocator> memoryAllocator = VkTestMemoryAllocator::Make(
            inst, physDev, device, physDeviceVersion, extensions, &interface);

    ctx->fInstance = inst;
    ctx->fPhysicalDevice = physDev;
    ctx->fDevice = device;
    ctx->fQueue = queue;
    ctx->fGraphicsQueueIndex = graphicsQueueIndex;
    ctx->fMaxAPIVersion = kApiVersion;
    ctx->fVkExtensions = extensions;
    ctx->fDeviceFeatures2 = &testVkFeatures->deviceFeatures;
    ctx->fGetProc = getProc;
    ctx->fProtectedContext = skgpu::Protected(isProtected);
    ctx->fMemoryAllocator = memoryAllocator;

    return true;
}

}  // namespace sk_gpu_test

#endif
