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

#include "tools/graphite/vk/GraphiteVulkanTestContext.h"

#include "include/gpu/graphite/Context.h"
#include "include/gpu/graphite/ContextOptions.h"
#include "include/gpu/graphite/vk/VulkanGraphiteUtils.h"
#include "include/gpu/vk/VulkanExtensions.h"
#include "include/gpu/vk/VulkanMemoryAllocator.h"
#include "include/private/gpu/graphite/ContextOptionsPriv.h"
#include "tools/gpu/ContextType.h"
#include "tools/gpu/vk/VkTestUtils.h"
#include "tools/graphite/TestOptions.h"

extern bool gCreateProtectedContext;

namespace skiatest::graphite {

std::unique_ptr<GraphiteTestContext> VulkanTestContext::Make() {
    skgpu::VulkanBackendContext backendContext;
    skgpu::VulkanExtensions* extensions;
    VkPhysicalDeviceFeatures2* features;
    VkDebugReportCallbackEXT debugCallback = VK_NULL_HANDLE;
    PFN_vkDestroyDebugReportCallbackEXT destroyCallback = nullptr;

    PFN_vkGetInstanceProcAddr instProc;
    if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc)) {
        return nullptr;
    }

    extensions = new skgpu::VulkanExtensions();
    features = new VkPhysicalDeviceFeatures2;
    memset(features, 0, sizeof(VkPhysicalDeviceFeatures2));
    if (!sk_gpu_test::CreateVkBackendContext(instProc, &backendContext, extensions,
                                             features, &debugCallback,
                                             nullptr, sk_gpu_test::CanPresentFn(),
                                             gCreateProtectedContext)) {
        sk_gpu_test::FreeVulkanFeaturesStructs(features);
        delete features;
        delete extensions;
        return nullptr;
    }
    if (debugCallback != VK_NULL_HANDLE) {
        destroyCallback = (PFN_vkDestroyDebugReportCallbackEXT) instProc(
                backendContext.fInstance, "vkDestroyDebugReportCallbackEXT");
    }

    return std::unique_ptr<GraphiteTestContext>(new VulkanTestContext(backendContext,
                                                                      extensions,
                                                                      features,
                                                                      debugCallback,
                                                                      destroyCallback));
}

#define ACQUIRE_VK_PROC_LOCAL(name, inst)                                                \
    PFN_vk##name localVk##name =                                                         \
            reinterpret_cast<PFN_vk##name>(fVulkan.fGetProc("vk" #name, inst, nullptr)); \
    do {                                                                                 \
        if (localVk##name == nullptr) {                                                  \
            SkDebugf("Function ptr for vk%s could not be acquired\n", #name);            \
            return;                                                                      \
        }                                                                                \
    } while (0)

VulkanTestContext::~VulkanTestContext() {
    fVulkan.fMemoryAllocator.reset();
    ACQUIRE_VK_PROC_LOCAL(DeviceWaitIdle, fVulkan.fInstance);
    ACQUIRE_VK_PROC_LOCAL(DestroyDevice, fVulkan.fInstance);
    ACQUIRE_VK_PROC_LOCAL(DestroyInstance, fVulkan.fInstance);
    localVkDeviceWaitIdle(fVulkan.fDevice);
    localVkDestroyDevice(fVulkan.fDevice, nullptr);
#ifdef SK_ENABLE_VK_LAYERS
    if (fDebugCallback != VK_NULL_HANDLE) {
        fDestroyDebugReportCallbackEXT(fVulkan.fInstance, fDebugCallback, nullptr);
    }
#else
    // Surpress unused private member variable warning
    (void)fDebugCallback;
    (void)fDestroyDebugReportCallbackEXT;
#endif
    localVkDestroyInstance(fVulkan.fInstance, nullptr);
    delete fExtensions;

    sk_gpu_test::FreeVulkanFeaturesStructs(fFeatures);
    delete fFeatures;
}

skgpu::ContextType VulkanTestContext::contextType() {
    return skgpu::ContextType::kVulkan;
}

std::unique_ptr<skgpu::graphite::Context> VulkanTestContext::makeContext(
        const TestOptions& options) {
    SkASSERT(!options.fNeverYieldToWebGPU);
    skgpu::graphite::ContextOptions revisedContextOptions(options.fContextOptions);
    skgpu::graphite::ContextOptionsPriv contextOptionsPriv;
    if (!options.fContextOptions.fOptionsPriv) {
        revisedContextOptions.fOptionsPriv = &contextOptionsPriv;
    }
    // Needed to make synchronous readPixels work
    revisedContextOptions.fOptionsPriv->fStoreContextRefInRecorder = true;

    return skgpu::graphite::ContextFactory::MakeVulkan(fVulkan, revisedContextOptions);
}

}  // namespace skiatest::graphite
