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

// This is a Vulkan protected memory specific test.

#include "include/core/SkTypes.h"

#if SK_SUPPORT_GPU && defined(SK_VULKAN)

#include "include/core/SkCanvas.h"
#include "include/core/SkMaskFilter.h"
#include "include/core/SkPaint.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrBackendSurface.h"
#include "include/gpu/vk/GrVkBackendContext.h"
#include "include/gpu/vk/GrVkExtensions.h"
#include "tests/Test.h"
#include "tools/gpu/GrContextFactory.h"
#include "tools/gpu/vk/VkTestUtils.h"

namespace {

#define DECLARE_VK_PROC(name) PFN_vk##name fVk##name

#define ACQUIRE_INST_VK_PROC(name)                                                           \
    fVk##name = reinterpret_cast<PFN_vk##name>(getProc("vk" #name, fBackendContext.fInstance,\
                                                       VK_NULL_HANDLE));                     \
    if (fVk##name == nullptr) {                                                              \
        ERRORF(reporter, "Function ptr for vk%s could not be acquired\n", #name);            \
        return false;                                                                        \
    }

#define ACQUIRE_DEVICE_VK_PROC(name)                                                          \
    fVk##name = reinterpret_cast<PFN_vk##name>(getProc("vk" #name, VK_NULL_HANDLE, fDevice)); \
    if (fVk##name == nullptr) {                                                               \
        ERRORF(reporter, "Function ptr for vk%s could not be acquired\n", #name);             \
        return false;                                                                         \
    }

class VulkanTestHelper {
public:
    VulkanTestHelper(bool isProtected) : fIsProtected(isProtected) {}

    ~VulkanTestHelper() {
        cleanup();
    }

    bool init(skiatest::Reporter* reporter);

    GrContext* grContext() { return fGrContext.get(); }

    sk_sp<SkSurface> createSkSurface(skiatest::Reporter* reporter);

   private:
    void cleanup();

    DECLARE_VK_PROC(DestroyInstance);
    DECLARE_VK_PROC(DeviceWaitIdle);
    DECLARE_VK_PROC(DestroyDevice);

    bool fIsProtected = false;
    VkDevice fDevice = VK_NULL_HANDLE;

    GrVkExtensions* fExtensions = nullptr;
    VkPhysicalDeviceFeatures2* fFeatures = nullptr;
    VkDebugReportCallbackEXT fDebugCallback = VK_NULL_HANDLE;
    PFN_vkDestroyDebugReportCallbackEXT fDestroyDebugCallback = nullptr;
    GrVkBackendContext fBackendContext;
    sk_sp<GrContext> fGrContext;
};

} // namespace

bool VulkanTestHelper::init(skiatest::Reporter* reporter) {
    PFN_vkGetInstanceProcAddr instProc;
    PFN_vkGetDeviceProcAddr devProc;
    if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc, &devProc)) {
        return false;
    }
    auto getProc = [&instProc, &devProc](const char* proc_name,
                                         VkInstance instance, VkDevice device) {
        if (device != VK_NULL_HANDLE) {
            return devProc(device, proc_name);
        }
        return instProc(instance, proc_name);
    };

    fExtensions = new GrVkExtensions();
    fFeatures = new VkPhysicalDeviceFeatures2;
    memset(fFeatures, 0, sizeof(VkPhysicalDeviceFeatures2));
    fFeatures->sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
    fFeatures->pNext = nullptr;

    fBackendContext.fInstance = VK_NULL_HANDLE;
    fBackendContext.fDevice = VK_NULL_HANDLE;

    if (!sk_gpu_test::CreateVkBackendContext(getProc, &fBackendContext, fExtensions,
                                             fFeatures, &fDebugCallback, nullptr,
                                             sk_gpu_test::CanPresentFn(), fIsProtected)) {
        return false;
    }
    fDevice = fBackendContext.fDevice;

    if (fDebugCallback != VK_NULL_HANDLE) {
        fDestroyDebugCallback = (PFN_vkDestroyDebugReportCallbackEXT) instProc(
                fBackendContext.fInstance, "vkDestroyDebugReportCallbackEXT");
    }
    ACQUIRE_INST_VK_PROC(DestroyInstance)
    ACQUIRE_INST_VK_PROC(DeviceWaitIdle)
    ACQUIRE_INST_VK_PROC(DestroyDevice)

    fGrContext = GrContext::MakeVulkan(fBackendContext);
    if (!fGrContext) {
        return false;
    }

    return true;
}

void VulkanTestHelper::cleanup() {
    fGrContext.reset();

    fBackendContext.fMemoryAllocator.reset();
    if (fDevice != VK_NULL_HANDLE) {
        fVkDeviceWaitIdle(fDevice);
        fVkDestroyDevice(fDevice, nullptr);
        fDevice = VK_NULL_HANDLE;
    }
    if (fDebugCallback != VK_NULL_HANDLE) {
        fDestroyDebugCallback(fBackendContext.fInstance, fDebugCallback, nullptr);
    }

    if (fBackendContext.fInstance != VK_NULL_HANDLE) {
        fVkDestroyInstance(fBackendContext.fInstance, nullptr);
        fBackendContext.fInstance = VK_NULL_HANDLE;
    }

    delete fExtensions;

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

sk_sp<SkSurface> VulkanTestHelper::createSkSurface(skiatest::Reporter* reporter) {
    const int kW = 8;
    const int kH = 8;
    GrBackendTexture backendTex = grContext()->createBackendTexture(
        kW, kH, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo,
        fIsProtected ? GrProtected::kYes : GrProtected::kNo);
    REPORTER_ASSERT(reporter, backendTex.isValid());
    REPORTER_ASSERT(reporter, backendTex.isProtected() == fIsProtected);

    SkSurfaceProps surfaceProps =
        SkSurfaceProps(0, SkSurfaceProps::kLegacyFontHost_InitType);
    sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTextureAsRenderTarget(
        grContext(), backendTex, kTopLeft_GrSurfaceOrigin, 1,
        kRGBA_8888_SkColorType, nullptr, &surfaceProps);
    REPORTER_ASSERT(reporter, surface);
    return surface;
}

DEF_GPUTEST(VkProtectedContext_CreateNonprotectedContext, reporter, options) {
    auto nonprotectedTestHelper = std::make_unique<VulkanTestHelper>(false);
    REPORTER_ASSERT(reporter, nonprotectedTestHelper->init(reporter));
}


DEF_GPUTEST(VkProtectedContext_CreateProtectedContext, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
}

DEF_GPUTEST(VkProtectedContext_CreateProtectedSkSurface, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    const int kW = 8;
    const int kH = 8;
    GrBackendTexture backendTex =
        protectedTestHelper->grContext()->createBackendTexture(
            kW, kH, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo,
            GrProtected::kYes);
    REPORTER_ASSERT(reporter, backendTex.isValid());
    REPORTER_ASSERT(reporter, backendTex.isProtected());

    SkSurfaceProps surfaceProps =
        SkSurfaceProps(0, SkSurfaceProps::kLegacyFontHost_InitType);
    sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTextureAsRenderTarget(
        protectedTestHelper->grContext(), backendTex, kTopLeft_GrSurfaceOrigin, 1,
        kRGBA_8888_SkColorType, nullptr, &surfaceProps);
    REPORTER_ASSERT(reporter, surface);

    protectedTestHelper->grContext()->deleteBackendTexture(backendTex);
}

DEF_GPUTEST(VkProtectedContext_CreateNonprotectedTextureInProtectedContext, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    const int kW = 8;
    const int kH = 8;
    GrBackendTexture backendTex =
        protectedTestHelper->grContext()->createBackendTexture(
            kW, kH, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo,
            GrProtected::kNo);
    REPORTER_ASSERT(reporter, !backendTex.isValid());
}

DEF_GPUTEST(VkProtectedContext_CreateProtectedTextureInNonprotectedContext, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(false);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    const int kW = 8;
    const int kH = 8;
    GrBackendTexture backendTex =
        protectedTestHelper->grContext()->createBackendTexture(
            kW, kH, kRGBA_8888_SkColorType, GrMipMapped::kNo, GrRenderable::kNo,
            GrProtected::kYes);
    REPORTER_ASSERT(reporter, !backendTex.isValid());
}

DEF_GPUTEST(VkProtectedContext_ReadFromProtectedSurface, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    REPORTER_ASSERT(reporter,
                    !surface->readPixels(SkImageInfo(), nullptr, 8, 0, 0));

    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

namespace {

struct AsyncContext {
    bool fCalled = false;
    std::unique_ptr<const SkSurface::AsyncReadResult> fResult;
};

static void async_callback(void* c, std::unique_ptr<const SkSurface::AsyncReadResult> result) {
    auto context = static_cast<AsyncContext*>(c);
    context->fResult = std::move(result);
    context->fCalled = true;
};

}  // anonymous namespace

DEF_GPUTEST(VkProtectedContext_AsyncReadFromProtectedSurface, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    AsyncContext cbContext;
    const auto image_info = SkImageInfo::Make(10, 10, kRGBA_8888_SkColorType, kPremul_SkAlphaType,
                                      SkColorSpace::MakeSRGB());
    surface->asyncRescaleAndReadPixelsYUV420(kIdentity_SkYUVColorSpace, SkColorSpace::MakeSRGB(),
                                             image_info.bounds(), image_info.dimensions(),
                                             SkSurface::RescaleGamma::kSrc, kNone_SkFilterQuality,
                                             &async_callback, &cbContext);
    while (!cbContext.fCalled) {
        surface->getCanvas()->getGrContext()->checkAsyncWorkCompletion();
    }
    REPORTER_ASSERT(reporter, !cbContext.fResult);

    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

DEF_GPUTEST(VkProtectedContext_DrawRectangle, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    SkCanvas* canvas = surface->getCanvas();
    REPORTER_ASSERT(reporter, canvas);
    SkPaint paint;
    paint.setColor(SK_ColorBLACK);
    canvas->drawRect(SkRect::MakeWH(4, 4), paint);

    GrFlushInfo flushInfo;
    flushInfo.fFlags = kSyncCpu_GrFlushFlag;
    surface->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

DEF_GPUTEST(VkProtectedContext_DrawRectangleWithAntiAlias, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    SkCanvas* canvas = surface->getCanvas();
    REPORTER_ASSERT(reporter, canvas);
    SkPaint paint;
    paint.setColor(SK_ColorBLACK);
    paint.setAntiAlias(true);
    canvas->drawRect(SkRect::MakeWH(4, 4), paint);

    GrFlushInfo flushInfo;
    flushInfo.fFlags = kSyncCpu_GrFlushFlag;
    surface->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

DEF_GPUTEST(VkProtectedContext_DrawRectangleWithBlendMode, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    SkCanvas* canvas = surface->getCanvas();
    REPORTER_ASSERT(reporter, canvas);
    SkPaint paint;
    paint.setColor(SK_ColorBLACK);
    paint.setBlendMode(SkBlendMode::kColorDodge);
    canvas->drawRect(SkRect::MakeWH(4, 4), paint);

    GrFlushInfo flushInfo;
    flushInfo.fFlags = kSyncCpu_GrFlushFlag;
    surface->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

DEF_GPUTEST(VkProtectedContext_DrawRectangleWithFilter, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    SkCanvas* canvas = surface->getCanvas();
    REPORTER_ASSERT(reporter, canvas);
    SkPaint paint;
    paint.setColor(SK_ColorBLACK);
    paint.setStyle(SkPaint::kFill_Style);
    paint.setMaskFilter(SkMaskFilter::MakeBlur(
          SkBlurStyle::kOuter_SkBlurStyle, 1.1f));
    canvas->drawRect(SkRect::MakeWH(4, 4), paint);

    GrFlushInfo flushInfo;
    flushInfo.fFlags = kSyncCpu_GrFlushFlag;
    surface->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

DEF_GPUTEST(VkProtectedContext_DrawThinPath, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    SkCanvas* canvas = surface->getCanvas();
    REPORTER_ASSERT(reporter, canvas);
    SkPaint paint;
    paint.setColor(SK_ColorBLACK);
    paint.setStyle(SkPaint::kStroke_Style);
    paint.setAntiAlias(true);
    paint.setStrokeWidth(.4f);
    canvas->drawPath(SkPath().moveTo(4, 4).lineTo(6, 6), paint);

    GrFlushInfo flushInfo;
    flushInfo.fFlags = kSyncCpu_GrFlushFlag;
    surface->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

DEF_GPUTEST(VkProtectedContext_SaveLayer, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    auto surface = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface);
    SkCanvas* canvas = surface->getCanvas();
    REPORTER_ASSERT(reporter, canvas);
    canvas->saveLayer(nullptr, nullptr);
    SkPaint paint;
    paint.setColor(SK_ColorBLACK);
    canvas->drawRect(SkRect::MakeWH(4, 4), paint);
    canvas->restore();

    GrFlushInfo flushInfo;
    flushInfo.fFlags = kSyncCpu_GrFlushFlag;
    surface->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}


DEF_GPUTEST(VkProtectedContext_DrawProtectedImageOnProtectedSurface, reporter, options) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    // Create protected image.
    auto surface1 = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface1);
    auto image = surface1->makeImageSnapshot();
    REPORTER_ASSERT(reporter, image);

    // Create protected canvas.
    auto surface2 = protectedTestHelper->createSkSurface(reporter);
    REPORTER_ASSERT(reporter, surface2);
    SkCanvas* canvas = surface2->getCanvas();
    REPORTER_ASSERT(reporter, canvas);

    canvas->drawImage(image, 0, 0);

    GrFlushInfo flushInfo;
    flushInfo.fFlags = kSyncCpu_GrFlushFlag;
    surface1->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface1->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
    surface2->flush(SkSurface::BackendSurfaceAccess::kNoAccess, flushInfo);
    protectedTestHelper->grContext()->deleteBackendTexture(
        surface2->getBackendTexture(SkSurface::kFlushRead_BackendHandleAccess));
}

//////////////////////////////////////////////////////////////////////////////////////////////////
// Test out DDLs using a protected Vulkan context

void DDLMakeRenderTargetTestImpl(GrContext* context, skiatest::Reporter* reporter);

DEF_GPUTEST(VkProtectedContext_DDLMakeRenderTargetTest, reporter, ctxInfo) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    DDLMakeRenderTargetTestImpl(protectedTestHelper->grContext(), reporter);
}

void DDLSurfaceCharacterizationTestImpl(GrContext* context, skiatest::Reporter* reporter);

DEF_GPUTEST(VkProtectedContext_DDLSurfaceCharacterizationTest, reporter, ctxInfo) {
    auto protectedTestHelper = std::make_unique<VulkanTestHelper>(true);
    if (!protectedTestHelper->init(reporter)) {
        return;
    }
    REPORTER_ASSERT(reporter, protectedTestHelper->grContext() != nullptr);

    DDLSurfaceCharacterizationTestImpl(protectedTestHelper->grContext(), reporter);
}

#endif  // SK_SUPPORT_GPU && defined(SK_VULKAN)
