/*
 * Copyright 2018 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 GPU-backend specific test. It relies on static initializers to work

#include "include/core/SkTypes.h"

#if defined(SK_GANESH) && defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 && defined(SK_VULKAN)

#include "include/core/SkBitmap.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColorSpace.h"
#include "include/core/SkImage.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrBackendSemaphore.h"
#include "include/gpu/GrDirectContext.h"
#include "include/gpu/MutableTextureState.h"
#include "include/gpu/ganesh/SkImageGanesh.h"
#include "include/gpu/ganesh/SkSurfaceGanesh.h"
#include "include/gpu/ganesh/gl/GrGLBackendSurface.h"
#include "include/gpu/ganesh/vk/GrVkBackendSurface.h"
#include "include/gpu/vk/GrVkBackendContext.h"
#include "include/gpu/vk/VulkanExtensions.h"
#include "src/base/SkAutoMalloc.h"
#include "src/gpu/ganesh/GrDirectContextPriv.h"
#include "src/gpu/ganesh/GrGpu.h"
#include "src/gpu/ganesh/GrProxyProvider.h"
#include "src/gpu/ganesh/SkGr.h"
#include "src/gpu/ganesh/gl/GrGLDefines.h"
#include "src/gpu/ganesh/gl/GrGLUtil.h"
#include "tests/Test.h"
#include "tools/gpu/GrContextFactory.h"
#include "tools/gpu/vk/VkTestUtils.h"

#include <android/hardware_buffer.h>
#include <cinttypes>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES/gl.h>
#include <GLES/glext.h>

static const int DEV_W = 16, DEV_H = 16;

class BaseTestHelper {
public:
    virtual ~BaseTestHelper() {}

    virtual bool init(skiatest::Reporter* reporter) = 0;

    virtual void cleanup() = 0;
    // This is used to release a surface back to the external queue in vulkan
    virtual void releaseSurfaceToExternal(SkSurface*) = 0;
    virtual void releaseImage() = 0;

    virtual sk_sp<SkImage> importHardwareBufferForRead(skiatest::Reporter* reporter,
                                                       AHardwareBuffer* buffer) = 0;
    virtual sk_sp<SkSurface> importHardwareBufferForWrite(skiatest::Reporter* reporter,
                                                          AHardwareBuffer* buffer) = 0;

    virtual void doClientSync() = 0;
    virtual bool flushSurfaceAndSignalSemaphore(skiatest::Reporter* reporter, sk_sp<SkSurface>) = 0;
    virtual bool importAndWaitOnSemaphore(skiatest::Reporter* reporter, int fdHandle,
                                          sk_sp<SkSurface>) = 0;

    virtual void makeCurrent() = 0;

    virtual GrDirectContext* directContext() = 0;

    int getFdHandle() { return fFdHandle; }

protected:
    BaseTestHelper() {}

    int fFdHandle = 0;
};

#ifdef SK_GL
class EGLTestHelper : public BaseTestHelper {
public:
    EGLTestHelper(const GrContextOptions& options) : fFactory(options) {}

    ~EGLTestHelper() override {}

    void releaseImage() override {
        this->makeCurrent();
        if (!fGLCtx) {
            return;
        }
        if (EGL_NO_IMAGE_KHR != fImage) {
            fGLCtx->destroyEGLImage(fImage);
            fImage = EGL_NO_IMAGE_KHR;
        }
        if (fTexID) {
            GR_GL_CALL(fGLCtx->gl(), DeleteTextures(1, &fTexID));
            fTexID = 0;
        }
    }

    void releaseSurfaceToExternal(SkSurface*) override {}

    void cleanup() override {
        this->releaseImage();
    }

    bool init(skiatest::Reporter* reporter) override;

    sk_sp<SkImage> importHardwareBufferForRead(skiatest::Reporter* reporter,
                                               AHardwareBuffer* buffer) override;
    sk_sp<SkSurface> importHardwareBufferForWrite(skiatest::Reporter* reporter,
                                                  AHardwareBuffer* buffer) override;

    void doClientSync() override;
    bool flushSurfaceAndSignalSemaphore(skiatest::Reporter* reporter, sk_sp<SkSurface>) override;
    bool importAndWaitOnSemaphore(skiatest::Reporter* reporter, int fdHandle,
                                  sk_sp<SkSurface>) override;

    void makeCurrent() override { fGLCtx->makeCurrent(); }

    GrDirectContext* directContext() override { return fDirectContext; }

private:
    bool importHardwareBuffer(skiatest::Reporter* reporter, AHardwareBuffer* buffer);

    typedef EGLClientBuffer (*EGLGetNativeClientBufferANDROIDProc)(const struct AHardwareBuffer*);
    typedef EGLImageKHR (*EGLCreateImageKHRProc)(EGLDisplay, EGLContext, EGLenum, EGLClientBuffer,
                                                 const EGLint*);
    typedef void (*EGLImageTargetTexture2DOESProc)(EGLenum, void*);
    EGLGetNativeClientBufferANDROIDProc fEGLGetNativeClientBufferANDROID;
    EGLCreateImageKHRProc fEGLCreateImageKHR;
    EGLImageTargetTexture2DOESProc fEGLImageTargetTexture2DOES;

    PFNEGLCREATESYNCKHRPROC              fEGLCreateSyncKHR;
    PFNEGLWAITSYNCKHRPROC                fEGLWaitSyncKHR;
    PFNEGLGETSYNCATTRIBKHRPROC           fEGLGetSyncAttribKHR;
    PFNEGLDUPNATIVEFENCEFDANDROIDPROC    fEGLDupNativeFenceFDANDROID;
    PFNEGLDESTROYSYNCKHRPROC             fEGLDestroySyncKHR;

    EGLImageKHR fImage = EGL_NO_IMAGE_KHR;
    GrGLuint fTexID = 0;

    sk_gpu_test::GrContextFactory fFactory;
    sk_gpu_test::ContextInfo fGLESContextInfo;

    sk_gpu_test::GLTestContext* fGLCtx = nullptr;
    GrDirectContext* fDirectContext = nullptr;
};

bool EGLTestHelper::init(skiatest::Reporter* reporter) {
    fGLESContextInfo = fFactory.getContextInfo(skgpu::ContextType::kGLES);
    fDirectContext = fGLESContextInfo.directContext();
    fGLCtx = fGLESContextInfo.glContext();
    if (!fDirectContext || !fGLCtx) {
        return false;
    }

    if (kGLES_GrGLStandard != fGLCtx->gl()->fStandard) {
        return false;
    }

    // Confirm we have egl and the needed extensions
    if (!fGLCtx->gl()->hasExtension("EGL_KHR_image") ||
        !fGLCtx->gl()->hasExtension("EGL_ANDROID_get_native_client_buffer") ||
        !fGLCtx->gl()->hasExtension("GL_OES_EGL_image_external") ||
        !fGLCtx->gl()->hasExtension("GL_OES_EGL_image") ||
        !fGLCtx->gl()->hasExtension("EGL_KHR_fence_sync") ||
        !fGLCtx->gl()->hasExtension("EGL_ANDROID_native_fence_sync")) {
        return false;
    }

    fEGLGetNativeClientBufferANDROID =
        (EGLGetNativeClientBufferANDROIDProc) eglGetProcAddress("eglGetNativeClientBufferANDROID");
    if (!fEGLGetNativeClientBufferANDROID) {
        ERRORF(reporter, "Failed to get the eglGetNativeClientBufferAndroid proc");
        return false;
    }

    fEGLCreateImageKHR = (EGLCreateImageKHRProc) eglGetProcAddress("eglCreateImageKHR");
    if (!fEGLCreateImageKHR) {
        ERRORF(reporter, "Failed to get the proc eglCreateImageKHR");
        return false;
    }

    fEGLImageTargetTexture2DOES =
            (EGLImageTargetTexture2DOESProc) eglGetProcAddress("glEGLImageTargetTexture2DOES");
    if (!fEGLImageTargetTexture2DOES) {
        ERRORF(reporter, "Failed to get the proc EGLImageTargetTexture2DOES");
        return false;
    }

    fEGLCreateSyncKHR = (PFNEGLCREATESYNCKHRPROC) eglGetProcAddress("eglCreateSyncKHR");
    if (!fEGLCreateSyncKHR) {
        ERRORF(reporter, "Failed to get the proc eglCreateSyncKHR");
        return false;

    }
    fEGLWaitSyncKHR = (PFNEGLWAITSYNCKHRPROC) eglGetProcAddress("eglWaitSyncKHR");
    if (!fEGLWaitSyncKHR) {
        ERRORF(reporter, "Failed to get the proc eglWaitSyncKHR");
        return false;

    }
    fEGLGetSyncAttribKHR = (PFNEGLGETSYNCATTRIBKHRPROC) eglGetProcAddress("eglGetSyncAttribKHR");
    if (!fEGLGetSyncAttribKHR) {
        ERRORF(reporter, "Failed to get the proc eglGetSyncAttribKHR");
        return false;

    }
    fEGLDupNativeFenceFDANDROID =
        (PFNEGLDUPNATIVEFENCEFDANDROIDPROC) eglGetProcAddress("eglDupNativeFenceFDANDROID");
    if (!fEGLDupNativeFenceFDANDROID) {
        ERRORF(reporter, "Failed to get the proc eglDupNativeFenceFDANDROID");
        return false;

    }
    fEGLDestroySyncKHR = (PFNEGLDESTROYSYNCKHRPROC) eglGetProcAddress("eglDestroySyncKHR");
    if (!fEGLDestroySyncKHR) {
        ERRORF(reporter, "Failed to get the proc eglDestroySyncKHR");
        return false;

    }

    return true;
}

bool EGLTestHelper::importHardwareBuffer(skiatest::Reporter* reporter, AHardwareBuffer* buffer) {
    while (fGLCtx->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {}

    EGLClientBuffer eglClientBuffer = fEGLGetNativeClientBufferANDROID(buffer);
    EGLint eglAttribs[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE,
                            EGL_NONE };
    EGLDisplay eglDisplay = eglGetCurrentDisplay();
    fImage = fEGLCreateImageKHR(eglDisplay, EGL_NO_CONTEXT,
                                EGL_NATIVE_BUFFER_ANDROID,
                                eglClientBuffer, eglAttribs);
    if (EGL_NO_IMAGE_KHR == fImage) {
        SkDebugf("Could not create EGL image, err = (%#x)\n", (int) eglGetError() );
        return false;
    }

    GR_GL_CALL(fGLCtx->gl(), GenTextures(1, &fTexID));
    if (!fTexID) {
        ERRORF(reporter, "Failed to create GL Texture");
        return false;
    }
    GR_GL_CALL_NOERRCHECK(fGLCtx->gl(), BindTexture(GR_GL_TEXTURE_2D, fTexID));
    if (fGLCtx->gl()->fFunctions.fGetError() != GR_GL_NO_ERROR) {
        ERRORF(reporter, "Failed to bind GL Texture");
        return false;
    }

    fEGLImageTargetTexture2DOES(GL_TEXTURE_2D, fImage);
    if (GrGLenum error = fGLCtx->gl()->fFunctions.fGetError(); error != GR_GL_NO_ERROR) {
        ERRORF(reporter, "EGLImageTargetTexture2DOES failed (%#x)", (int) error);
        return false;
    }

    fDirectContext->resetContext(kTextureBinding_GrGLBackendState);
    return true;
}

sk_sp<SkImage> EGLTestHelper::importHardwareBufferForRead(skiatest::Reporter* reporter,
                                                          AHardwareBuffer* buffer) {
    if (!this->importHardwareBuffer(reporter, buffer)) {
        return nullptr;
    }
    GrGLTextureInfo textureInfo;
    textureInfo.fTarget = GR_GL_TEXTURE_2D;
    textureInfo.fID = fTexID;
    textureInfo.fFormat = GR_GL_RGBA8;

    auto backendTex = GrBackendTextures::MakeGL(DEV_W, DEV_H, GrMipmapped::kNo, textureInfo);
    REPORTER_ASSERT(reporter, backendTex.isValid());

    sk_sp<SkImage> image = SkImages::BorrowTextureFrom(fDirectContext,
                                                       backendTex,
                                                       kTopLeft_GrSurfaceOrigin,
                                                       kRGBA_8888_SkColorType,
                                                       kPremul_SkAlphaType,
                                                       nullptr);

    if (!image) {
        ERRORF(reporter, "Failed to make wrapped GL SkImage");
        return nullptr;
    }

    return image;
}

sk_sp<SkSurface> EGLTestHelper::importHardwareBufferForWrite(skiatest::Reporter* reporter,
                                                             AHardwareBuffer* buffer) {
    if (!this->importHardwareBuffer(reporter, buffer)) {
        return nullptr;
    }
    GrGLTextureInfo textureInfo;
    textureInfo.fTarget = GR_GL_TEXTURE_2D;
    textureInfo.fID = fTexID;
    textureInfo.fFormat = GR_GL_RGBA8;

    auto backendTex = GrBackendTextures::MakeGL(DEV_W, DEV_H, GrMipmapped::kNo, textureInfo);
    REPORTER_ASSERT(reporter, backendTex.isValid());

    sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(fDirectContext,
                                                              backendTex,
                                                              kTopLeft_GrSurfaceOrigin,
                                                              0,
                                                              kRGBA_8888_SkColorType,
                                                              nullptr,
                                                              nullptr);

    if (!surface) {
        ERRORF(reporter, "Failed to make wrapped GL SkSurface");
        return nullptr;
    }

    return surface;
}

bool EGLTestHelper::flushSurfaceAndSignalSemaphore(skiatest::Reporter* reporter,
                                                   sk_sp<SkSurface> surface) {
    skgpu::ganesh::FlushAndSubmit(surface);

    EGLDisplay eglDisplay = eglGetCurrentDisplay();
    EGLSyncKHR eglsync = fEGLCreateSyncKHR(eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
    if (EGL_NO_SYNC_KHR == eglsync) {
        ERRORF(reporter, "Failed to create EGLSync for EGL_SYNC_NATIVE_FENCE_ANDROID\n");
        return false;
    }

    GR_GL_CALL(fGLCtx->gl(), Flush());
    fFdHandle = fEGLDupNativeFenceFDANDROID(eglDisplay, eglsync);

    EGLint result = fEGLDestroySyncKHR(eglDisplay, eglsync);
    if (EGL_TRUE != result) {
        ERRORF(reporter, "Failed to delete EGLSync, error: %d\n", result);
        return false;
    }

    return true;
}

bool EGLTestHelper::importAndWaitOnSemaphore(skiatest::Reporter* reporter, int fdHandle,
                                             sk_sp<SkSurface> surface) {
    EGLDisplay eglDisplay = eglGetCurrentDisplay();
    EGLint attr[] = {
        EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fdHandle,
        EGL_NONE
    };
    EGLSyncKHR eglsync = fEGLCreateSyncKHR(eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, attr);
    if (EGL_NO_SYNC_KHR == eglsync) {
        ERRORF(reporter,
               "Failed to create EGLSync when importing EGL_SYNC_NATIVE_FENCE_FD_ANDROID\n");
        return false;
    }
    EGLint result = fEGLWaitSyncKHR(eglDisplay, eglsync, 0);
    if (EGL_TRUE != result) {
        ERRORF(reporter, "Failed called to eglWaitSyncKHR, error: %d\n", result);
        // Don't return false yet, try to delete the sync first
    }
    result = fEGLDestroySyncKHR(eglDisplay, eglsync);
    if (EGL_TRUE != result) {
        ERRORF(reporter, "Failed to delete EGLSync, error: %d\n", result);
        return false;
    }
    return true;
}

void EGLTestHelper::doClientSync() {
    this->directContext()->flush();
    this->directContext()->submit(true);
}
#endif  // SK_GL

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

#define ACQUIRE_INST_VK_PROC(name)                                                           \
    do {                                                                                     \
    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;                                                                        \
    }                                                                                        \
    } while(false)

#define ACQUIRE_DEVICE_VK_PROC(name)                                                          \
    do {                                                                                      \
    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;                                                                         \
    }                                                                                         \
    } while(false)

class VulkanTestHelper : public BaseTestHelper {
public:
    VulkanTestHelper() {}

    ~VulkanTestHelper() override {}

    void releaseImage() override {
        if (VK_NULL_HANDLE == fDevice) {
            return;
        }
        if (fImage != VK_NULL_HANDLE) {
            fVkDestroyImage(fDevice, fImage, nullptr);
            fImage = VK_NULL_HANDLE;
        }

        if (fMemory != VK_NULL_HANDLE) {
            fVkFreeMemory(fDevice, fMemory, nullptr);
            fMemory = VK_NULL_HANDLE;
        }
    }

    void releaseSurfaceToExternal(SkSurface* surface) override {
        skgpu::MutableTextureState newState(VK_IMAGE_LAYOUT_UNDEFINED, VK_QUEUE_FAMILY_EXTERNAL);
        fDirectContext->flush(surface, {}, &newState);
    }

    void cleanup() override {
        fDirectContext.reset();
        this->releaseImage();
        if (fSignalSemaphore != VK_NULL_HANDLE) {
            fVkDestroySemaphore(fDevice, fSignalSemaphore, nullptr);
            fSignalSemaphore = VK_NULL_HANDLE;
        }
        fBackendContext.fMemoryAllocator.reset();
        if (fDevice != VK_NULL_HANDLE) {
            fVkDeviceWaitIdle(fDevice);
            fVkDestroyDevice(fDevice, nullptr);
            fDevice = VK_NULL_HANDLE;
        }
#ifdef SK_ENABLE_VK_LAYERS
        if (fDebugCallback != VK_NULL_HANDLE) {
            fDestroyDebugCallback(fBackendContext.fInstance, fDebugCallback, nullptr);
        }
#endif
        if (fBackendContext.fInstance != VK_NULL_HANDLE) {
            fVkDestroyInstance(fBackendContext.fInstance, nullptr);
            fBackendContext.fInstance = VK_NULL_HANDLE;
        }

        delete fExtensions;

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

    bool init(skiatest::Reporter* reporter) override;

    void doClientSync() override {
        if (!fDirectContext) {
            return;
        }

        fDirectContext->submit(true);
    }

    bool flushSurfaceAndSignalSemaphore(skiatest::Reporter* reporter, sk_sp<SkSurface>) override;
    bool importAndWaitOnSemaphore(skiatest::Reporter* reporter, int fdHandle,
                                  sk_sp<SkSurface>) override;

    sk_sp<SkImage> importHardwareBufferForRead(skiatest::Reporter* reporter,
                                               AHardwareBuffer* buffer) override;

    sk_sp<SkSurface> importHardwareBufferForWrite(skiatest::Reporter* reporter,
                                                  AHardwareBuffer* buffer) override;

    void makeCurrent() override {}

    GrDirectContext* directContext() override { return fDirectContext.get(); }

private:
    bool checkOptimalHardwareBuffer(skiatest::Reporter* reporter);

    bool importHardwareBuffer(skiatest::Reporter* reporter, AHardwareBuffer* buffer, bool forWrite,
                              GrVkImageInfo* outImageInfo);

    bool setupSemaphoreForSignaling(skiatest::Reporter* reporter, GrBackendSemaphore*);
    bool exportSemaphore(skiatest::Reporter* reporter, const GrBackendSemaphore&);

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

    DECLARE_VK_PROC(GetPhysicalDeviceExternalSemaphoreProperties);
    DECLARE_VK_PROC(GetPhysicalDeviceImageFormatProperties2);
    DECLARE_VK_PROC(GetPhysicalDeviceMemoryProperties2);

    DECLARE_VK_PROC(GetAndroidHardwareBufferPropertiesANDROID);

    DECLARE_VK_PROC(CreateImage);
    DECLARE_VK_PROC(GetImageMemoryRequirements2);
    DECLARE_VK_PROC(DestroyImage);

    DECLARE_VK_PROC(AllocateMemory);
    DECLARE_VK_PROC(BindImageMemory2);
    DECLARE_VK_PROC(FreeMemory);

    DECLARE_VK_PROC(CreateSemaphore);
    DECLARE_VK_PROC(GetSemaphoreFdKHR);
    DECLARE_VK_PROC(ImportSemaphoreFdKHR);
    DECLARE_VK_PROC(DestroySemaphore);

    VkImage fImage = VK_NULL_HANDLE;
    VkDeviceMemory fMemory = VK_NULL_HANDLE;

    skgpu::VulkanExtensions*            fExtensions = nullptr;
    VkPhysicalDeviceFeatures2*          fFeatures = nullptr;
    VkDebugReportCallbackEXT            fDebugCallback = VK_NULL_HANDLE;
    PFN_vkDestroyDebugReportCallbackEXT fDestroyDebugCallback = nullptr;

    // We hold on to the semaphore so we can delete once the GPU is done.
    VkSemaphore fSignalSemaphore = VK_NULL_HANDLE;

    VkDevice fDevice = VK_NULL_HANDLE;

    GrVkBackendContext fBackendContext;
    sk_sp<GrDirectContext> fDirectContext;
};

bool VulkanTestHelper::init(skiatest::Reporter* reporter) {
    PFN_vkGetInstanceProcAddr instProc;
    if (!sk_gpu_test::LoadVkLibraryAndGetProcAddrFuncs(&instProc)) {
        return false;
    }

    fExtensions = new skgpu::VulkanExtensions();
    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(instProc, &fBackendContext, fExtensions,
                                             fFeatures, &fDebugCallback)) {
        return false;
    }
    fDevice = fBackendContext.fDevice;
    auto getProc = fBackendContext.fGetProc;

    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);

    if (!fExtensions->hasExtension(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME,
                                  2)) {
        return false;
    }
    if (!fExtensions->hasExtension(VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, 1)) {
        return false;
    }
    if (!fExtensions->hasExtension(VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, 1)) {
        return false;
    }
    if (!fExtensions->hasExtension(VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, 1)) {
    //    return false;
    }

    ACQUIRE_INST_VK_PROC(GetPhysicalDeviceMemoryProperties2);
    ACQUIRE_INST_VK_PROC(GetPhysicalDeviceImageFormatProperties2);
    ACQUIRE_INST_VK_PROC(GetPhysicalDeviceExternalSemaphoreProperties);

    ACQUIRE_DEVICE_VK_PROC(GetAndroidHardwareBufferPropertiesANDROID);

    ACQUIRE_DEVICE_VK_PROC(CreateImage);
    ACQUIRE_DEVICE_VK_PROC(GetImageMemoryRequirements2);
    ACQUIRE_DEVICE_VK_PROC(DestroyImage);

    ACQUIRE_DEVICE_VK_PROC(AllocateMemory);
    ACQUIRE_DEVICE_VK_PROC(BindImageMemory2);
    ACQUIRE_DEVICE_VK_PROC(FreeMemory);

    ACQUIRE_DEVICE_VK_PROC(CreateSemaphore);
    ACQUIRE_DEVICE_VK_PROC(GetSemaphoreFdKHR);
    ACQUIRE_DEVICE_VK_PROC(ImportSemaphoreFdKHR);
    ACQUIRE_DEVICE_VK_PROC(DestroySemaphore);

    fDirectContext = GrDirectContext::MakeVulkan(fBackendContext);
    REPORTER_ASSERT(reporter, fDirectContext.get());
    if (!fDirectContext) {
        return false;
    }

    return this->checkOptimalHardwareBuffer(reporter);
}

bool VulkanTestHelper::checkOptimalHardwareBuffer(skiatest::Reporter* reporter) {
    VkResult err;

    VkPhysicalDeviceExternalImageFormatInfo externalImageFormatInfo;
    externalImageFormatInfo.sType =
            VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO;
    externalImageFormatInfo.pNext = nullptr;
    externalImageFormatInfo.handleType =
            VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
    //externalImageFormatInfo.handType = 0x80;

    // We will create the hardware buffer with gpu sampled so these usages should all be valid
    VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT |
                                   VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
                                   VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    VkPhysicalDeviceImageFormatInfo2 imageFormatInfo;
    imageFormatInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
    imageFormatInfo.pNext = &externalImageFormatInfo;
    imageFormatInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
    imageFormatInfo.type = VK_IMAGE_TYPE_2D;
    imageFormatInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
    imageFormatInfo.usage = usageFlags;
    imageFormatInfo.flags = 0;

    VkAndroidHardwareBufferUsageANDROID hwbUsage;
    hwbUsage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID;
    hwbUsage.pNext = nullptr;

    VkExternalImageFormatProperties externalImgFormatProps;
    externalImgFormatProps.sType = VK_STRUCTURE_TYPE_EXTERNAL_IMAGE_FORMAT_PROPERTIES;
    externalImgFormatProps.pNext = &hwbUsage;

    VkImageFormatProperties2 imgFormProps;
    imgFormProps.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
    imgFormProps.pNext = &externalImgFormatProps;

    err = fVkGetPhysicalDeviceImageFormatProperties2(fBackendContext.fPhysicalDevice,
                                                     &imageFormatInfo, &imgFormProps);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "vkGetPhysicalDeviceImageFormatProperites failed, err: %d", err);
        return false;
    }

    const VkImageFormatProperties& imageFormatProperties = imgFormProps.imageFormatProperties;
    REPORTER_ASSERT(reporter, DEV_W <= imageFormatProperties.maxExtent.width);
    REPORTER_ASSERT(reporter, DEV_H <= imageFormatProperties.maxExtent.height);

    const VkExternalMemoryProperties& externalImageFormatProps =
            externalImgFormatProps.externalMemoryProperties;
    REPORTER_ASSERT(reporter, SkToBool(VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT &
                                       externalImageFormatProps.externalMemoryFeatures));
    REPORTER_ASSERT(reporter, SkToBool(VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT &
                                       externalImageFormatProps.externalMemoryFeatures));

    REPORTER_ASSERT(reporter, SkToBool(AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE &
                                       hwbUsage.androidHardwareBufferUsage));

    return true;
}

bool VulkanTestHelper::importHardwareBuffer(skiatest::Reporter* reporter,
                                            AHardwareBuffer* buffer,
                                            bool forWrite,
                                            GrVkImageInfo* outImageInfo) {
    VkResult err;

    VkAndroidHardwareBufferFormatPropertiesANDROID hwbFormatProps;
    hwbFormatProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_FORMAT_PROPERTIES_ANDROID;
    hwbFormatProps.pNext = nullptr;

    VkAndroidHardwareBufferPropertiesANDROID hwbProps;
    hwbProps.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
    hwbProps.pNext = &hwbFormatProps;

    err = fVkGetAndroidHardwareBufferPropertiesANDROID(fDevice, buffer, &hwbProps);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "GetAndroidHardwareBufferPropertiesAndroid failed, err: %d", err);
        return false;
    }

    REPORTER_ASSERT(reporter, VK_FORMAT_R8G8B8A8_UNORM == hwbFormatProps.format);
    REPORTER_ASSERT(reporter,
                    SkToBool(VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT & hwbFormatProps.formatFeatures) &&
                    SkToBool(VK_FORMAT_FEATURE_TRANSFER_SRC_BIT & hwbFormatProps.formatFeatures) &&
                    SkToBool(VK_FORMAT_FEATURE_TRANSFER_DST_BIT & hwbFormatProps.formatFeatures));
    if (forWrite) {
        REPORTER_ASSERT(reporter,
                SkToBool(VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT & hwbFormatProps.formatFeatures));

    }

    bool useExternalFormat = VK_FORMAT_UNDEFINED == hwbFormatProps.format;
    const VkExternalFormatANDROID externalFormatInfo {
        VK_STRUCTURE_TYPE_EXTERNAL_FORMAT_ANDROID,             // sType
        nullptr,                                               // pNext
        useExternalFormat ? hwbFormatProps.externalFormat : 0, // externalFormat
    };

    const VkExternalMemoryImageCreateInfo externalMemoryImageInfo {
        VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO, // sType
        &externalFormatInfo, // pNext
        VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID, // handleTypes
    };

    VkImageUsageFlags usageFlags = VK_IMAGE_USAGE_SAMPLED_BIT |
                                   VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
                                   VK_IMAGE_USAGE_TRANSFER_DST_BIT;
    if (forWrite) {
        usageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    }

    const VkImageCreateInfo imageCreateInfo = {
        VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,         // sType
        &externalMemoryImageInfo,                    // pNext
        0,                                           // VkImageCreateFlags
        VK_IMAGE_TYPE_2D,                            // VkImageType
        hwbFormatProps.format,                       // VkFormat
        { DEV_W, DEV_H, 1 },                         // VkExtent3D
        1,                                           // mipLevels
        1,                                           // arrayLayers
        VK_SAMPLE_COUNT_1_BIT,                       // samples
        VK_IMAGE_TILING_OPTIMAL,                     // VkImageTiling
        usageFlags,                                  // VkImageUsageFlags
        VK_SHARING_MODE_EXCLUSIVE,                   // VkSharingMode
        0,                                           // queueFamilyCount
        0,                                           // pQueueFamilyIndices
        VK_IMAGE_LAYOUT_UNDEFINED,                   // initialLayout
    };

    err = fVkCreateImage(fDevice, &imageCreateInfo, nullptr, &fImage);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "Create Image failed, err: %d", err);
        return false;
    }

    VkPhysicalDeviceMemoryProperties2 phyDevMemProps;
    phyDevMemProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2;
    phyDevMemProps.pNext = nullptr;

    uint32_t typeIndex = 0;
    uint32_t heapIndex = 0;
    bool foundHeap = false;
    fVkGetPhysicalDeviceMemoryProperties2(fBackendContext.fPhysicalDevice, &phyDevMemProps);
    uint32_t memTypeCnt = phyDevMemProps.memoryProperties.memoryTypeCount;
    for (uint32_t i = 0; i < memTypeCnt && !foundHeap; ++i) {
        if (hwbProps.memoryTypeBits & (1 << i)) {
            const VkPhysicalDeviceMemoryProperties& pdmp = phyDevMemProps.memoryProperties;
            uint32_t supportedFlags = pdmp.memoryTypes[i].propertyFlags &
                    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
            if (supportedFlags == VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT) {
                typeIndex = i;
                heapIndex = pdmp.memoryTypes[i].heapIndex;
                REPORTER_ASSERT(reporter, heapIndex < pdmp.memoryHeapCount);
                foundHeap = true;
            }
        }
    }

    // Fallback to align with GrAHardwareBufferUtils
    if (!foundHeap && hwbProps.memoryTypeBits) {
        typeIndex = ffs(hwbProps.memoryTypeBits) - 1;
        foundHeap = true;
    }

    if (!foundHeap) {
        ERRORF(reporter, "Failed to find valid heap for imported memory");
        return false;
    }

    VkImportAndroidHardwareBufferInfoANDROID hwbImportInfo;
    hwbImportInfo.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
    hwbImportInfo.pNext = nullptr;
    hwbImportInfo.buffer = buffer;

    VkMemoryDedicatedAllocateInfo dedicatedAllocInfo;
    dedicatedAllocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
    dedicatedAllocInfo.pNext = &hwbImportInfo;
    dedicatedAllocInfo.image = fImage;
    dedicatedAllocInfo.buffer = VK_NULL_HANDLE;

    VkMemoryAllocateInfo allocInfo = {
        VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,      // sType
        &dedicatedAllocInfo,                         // pNext
        hwbProps.allocationSize,                     // allocationSize
        typeIndex,                                   // memoryTypeIndex
    };

    err = fVkAllocateMemory(fDevice, &allocInfo, nullptr, &fMemory);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "AllocateMemory failed for imported buffer, err: %d", err);
        return false;
    }

    VkBindImageMemoryInfo bindImageInfo;
    bindImageInfo.sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO;
    bindImageInfo.pNext = nullptr;
    bindImageInfo.image = fImage;
    bindImageInfo.memory = fMemory;
    bindImageInfo.memoryOffset = 0;

    err = fVkBindImageMemory2(fDevice, 1, &bindImageInfo);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "BindImageMemory failed for imported buffer, err: %d", err);
        return false;
    }

    skgpu::VulkanAlloc alloc;
    alloc.fMemory = fMemory;
    alloc.fOffset = 0;
    alloc.fSize = hwbProps.allocationSize;
    alloc.fFlags = 0;

    outImageInfo->fImage = fImage;
    outImageInfo->fAlloc = alloc;
    outImageInfo->fImageTiling = VK_IMAGE_TILING_OPTIMAL;
    outImageInfo->fImageLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    outImageInfo->fFormat = VK_FORMAT_R8G8B8A8_UNORM;
    outImageInfo->fImageUsageFlags = usageFlags;
    outImageInfo->fLevelCount = 1;
    outImageInfo->fCurrentQueueFamily = VK_QUEUE_FAMILY_EXTERNAL;
    return true;
}

sk_sp<SkImage> VulkanTestHelper::importHardwareBufferForRead(skiatest::Reporter* reporter,
                                                             AHardwareBuffer* buffer) {
    GrVkImageInfo imageInfo;
    if (!this->importHardwareBuffer(reporter, buffer, false, &imageInfo)) {
        return nullptr;
    }

    auto backendTex = GrBackendTextures::MakeVk(DEV_W, DEV_H, imageInfo);

    sk_sp<SkImage> wrappedImage = SkImages::BorrowTextureFrom(fDirectContext.get(),
                                                              backendTex,
                                                              kTopLeft_GrSurfaceOrigin,
                                                              kRGBA_8888_SkColorType,
                                                              kPremul_SkAlphaType,
                                                              nullptr);

    if (!wrappedImage.get()) {
        ERRORF(reporter, "Failed to create wrapped Vulkan SkImage");
        return nullptr;
    }

    return wrappedImage;
}

bool VulkanTestHelper::flushSurfaceAndSignalSemaphore(skiatest::Reporter* reporter,
                                                      sk_sp<SkSurface> surface) {
    this->releaseSurfaceToExternal(surface.get());
    surface.reset();
    GrBackendSemaphore semaphore;
    if (!this->setupSemaphoreForSignaling(reporter, &semaphore)) {
        return false;
    }
    GrFlushInfo info;
    info.fNumSemaphores = 1;
    info.fSignalSemaphores = &semaphore;
    GrSemaphoresSubmitted submitted = fDirectContext->flush(info);
    fDirectContext->submit();
    if (GrSemaphoresSubmitted::kNo == submitted) {
        ERRORF(reporter, "Failing call to flush on GrDirectContext");
        return false;
    }
    SkASSERT(semaphore.isInitialized());
    if (!this->exportSemaphore(reporter, semaphore)) {
        return false;
    }
    return true;
}

bool VulkanTestHelper::setupSemaphoreForSignaling(skiatest::Reporter* reporter,
                                                  GrBackendSemaphore* beSemaphore) {
    // Query supported info
    VkPhysicalDeviceExternalSemaphoreInfo exSemInfo;
    exSemInfo.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO;
    exSemInfo.pNext = nullptr;
    exSemInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;

    VkExternalSemaphoreProperties exSemProps;
    exSemProps.sType = VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES;
    exSemProps.pNext = nullptr;

    fVkGetPhysicalDeviceExternalSemaphoreProperties(fBackendContext.fPhysicalDevice, &exSemInfo,
                                                    &exSemProps);

    if (!SkToBool(exSemProps.exportFromImportedHandleTypes &
                 VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT)) {
        ERRORF(reporter, "HANDLE_TYPE_SYNC_FD not listed as exportFromImportedHandleTypes");
        return false;
    }
    if (!SkToBool(exSemProps.compatibleHandleTypes &
                  VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT)) {
        ERRORF(reporter, "HANDLE_TYPE_SYNC_FD not listed as compatibleHandleTypes");
        return false;
    }
    if (!SkToBool(exSemProps.externalSemaphoreFeatures &
                  VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) ||
        !SkToBool(exSemProps.externalSemaphoreFeatures &
                  VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT)) {
        ERRORF(reporter, "HANDLE_TYPE_SYNC_FD doesn't support export and import feature");
        return false;
    }

    VkExportSemaphoreCreateInfo exportInfo;
    exportInfo.sType = VK_STRUCTURE_TYPE_EXPORT_SEMAPHORE_CREATE_INFO;
    exportInfo.pNext = nullptr;
    exportInfo.handleTypes = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;

    VkSemaphoreCreateInfo semaphoreInfo;
    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
    semaphoreInfo.pNext = &exportInfo;
    semaphoreInfo.flags = 0;

    VkSemaphore semaphore;
    VkResult err = fVkCreateSemaphore(fDevice, &semaphoreInfo, nullptr, &semaphore);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "Failed to create signal semaphore, err: %d", err);
        return false;
    }
    beSemaphore->initVulkan(semaphore);
    return true;
}

bool VulkanTestHelper::exportSemaphore(skiatest::Reporter* reporter,
                                       const GrBackendSemaphore& beSemaphore) {
    VkSemaphore semaphore = beSemaphore.vkSemaphore();
    if (VK_NULL_HANDLE == semaphore) {
        ERRORF(reporter, "Invalid vulkan handle in export call");
        return false;
    }

    VkSemaphoreGetFdInfoKHR getFdInfo;
    getFdInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_GET_FD_INFO_KHR;
    getFdInfo.pNext = nullptr;
    getFdInfo.semaphore = semaphore;
    getFdInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;

    VkResult err = fVkGetSemaphoreFdKHR(fDevice, &getFdInfo, &fFdHandle);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "Failed to export signal semaphore, err: %d", err);
        return false;
    }
    fSignalSemaphore = semaphore;
    return true;
}

bool VulkanTestHelper::importAndWaitOnSemaphore(skiatest::Reporter* reporter, int fdHandle,
                                                sk_sp<SkSurface> surface) {
    VkSemaphoreCreateInfo semaphoreInfo;
    semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
    semaphoreInfo.pNext = nullptr;
    semaphoreInfo.flags = 0;

    VkSemaphore semaphore;
    VkResult err = fVkCreateSemaphore(fDevice, &semaphoreInfo, nullptr, &semaphore);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "Failed to create import semaphore, err: %d", err);
        return false;
    }

    VkImportSemaphoreFdInfoKHR importInfo;
    importInfo.sType = VK_STRUCTURE_TYPE_IMPORT_SEMAPHORE_FD_INFO_KHR;
    importInfo.pNext = nullptr;
    importInfo.semaphore = semaphore;
    importInfo.flags = VK_SEMAPHORE_IMPORT_TEMPORARY_BIT;
    importInfo.handleType = VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT;
    importInfo.fd = fdHandle;

    err = fVkImportSemaphoreFdKHR(fDevice, &importInfo);
    if (VK_SUCCESS != err) {
        ERRORF(reporter, "Failed to import semaphore, err: %d", err);
        return false;
    }

    GrBackendSemaphore beSemaphore;
    beSemaphore.initVulkan(semaphore);
    if (!surface->wait(1, &beSemaphore)) {
        ERRORF(reporter, "Failed to add wait semaphore to surface");
        fVkDestroySemaphore(fDevice, semaphore, nullptr);
        return false;
    }
    return true;
}

sk_sp<SkSurface> VulkanTestHelper::importHardwareBufferForWrite(skiatest::Reporter* reporter,
                                                                AHardwareBuffer* buffer) {
    GrVkImageInfo imageInfo;
    if (!this->importHardwareBuffer(reporter, buffer, true, &imageInfo)) {
        return nullptr;
    }

    auto backendTex = GrBackendTextures::MakeVk(DEV_W, DEV_H, imageInfo);

    sk_sp<SkSurface> surface = SkSurfaces::WrapBackendTexture(fDirectContext.get(),
                                                              backendTex,
                                                              kTopLeft_GrSurfaceOrigin,
                                                              0,
                                                              kRGBA_8888_SkColorType,
                                                              nullptr,
                                                              nullptr);

    if (!surface.get()) {
        ERRORF(reporter, "Failed to create wrapped Vulkan SkSurface");
        return nullptr;
    }

    return surface;
}

static SkPMColor get_src_color(int x, int y) {
    SkASSERT(x >= 0 && x < DEV_W);
    SkASSERT(y >= 0 && y < DEV_H);

    U8CPU r = x;
    U8CPU g = y;
    U8CPU b = 0xc;

    U8CPU a = 0xff;
    switch ((x+y) % 5) {
        case 0:
            a = 0xff;
            break;
        case 1:
            a = 0x80;
            break;
        case 2:
            a = 0xCC;
            break;
        case 4:
            a = 0x01;
            break;
        case 3:
            a = 0x00;
            break;
    }
    a = 0xff;
    return SkPremultiplyARGBInline(a, r, g, b);
}

static SkBitmap make_src_bitmap() {
    static SkBitmap bmp;
    if (bmp.isNull()) {
        bmp.allocN32Pixels(DEV_W, DEV_H);
        intptr_t pixels = reinterpret_cast<intptr_t>(bmp.getPixels());
        for (int y = 0; y < DEV_H; ++y) {
            for (int x = 0; x < DEV_W; ++x) {
                SkPMColor* pixel = reinterpret_cast<SkPMColor*>(
                        pixels + y * bmp.rowBytes() + x * bmp.bytesPerPixel());
                *pixel = get_src_color(x, y);
            }
        }
    }
    return bmp;
}

static bool check_read(skiatest::Reporter* reporter, const SkBitmap& srcBitmap,
                       const SkBitmap& dstBitmap) {
    bool result = true;
    for (int y = 0; y < DEV_H && result; ++y) {
        for (int x = 0; x < DEV_W && result; ++x) {
            const uint32_t srcPixel = *srcBitmap.getAddr32(x, y);
            const uint32_t dstPixel = *dstBitmap.getAddr32(x, y);
            if (srcPixel != dstPixel) {
                ERRORF(reporter, "Expected readback pixel (%d, %d) value 0x%08x, got 0x%08x.",
                       x, y,  srcPixel, dstPixel);
                result = false;
            } /*else {
                ERRORF(reporter, "Got good readback pixel (%d, %d) value 0x%08x, got 0x%08x.",
                       x, y,  srcPixel, dstPixel);

            }*/
        }
    }
    return result;
}

static void cleanup_resources(BaseTestHelper* srcHelper, BaseTestHelper* dstHelper,
                              AHardwareBuffer* buffer) {
    if (srcHelper) {
        srcHelper->cleanup();
    }
    if (dstHelper) {
        dstHelper->cleanup();
    }
    if (buffer) {
        AHardwareBuffer_release(buffer);
    }
}

enum class SrcType {
    kCPU,
    kEGL,
    kVulkan,
};

enum class DstType {
    kEGL,
    kVulkan,
};

void run_test(skiatest::Reporter* reporter, const GrContextOptions& options,
              SrcType srcType, DstType dstType, bool shareSyncs) {
    if (SrcType::kCPU == srcType && shareSyncs) {
        // We don't currently test this since we don't do any syncs in this case.
        return;
    }
    std::unique_ptr<BaseTestHelper> srcHelper;
    std::unique_ptr<BaseTestHelper> dstHelper;
    AHardwareBuffer* buffer = nullptr;
    if (SrcType::kVulkan == srcType) {
        srcHelper.reset(new VulkanTestHelper());
    } else if (SrcType::kEGL == srcType) {
#ifdef SK_GL
        srcHelper.reset(new EGLTestHelper(options));
#else
        SkASSERTF(false, "SrcType::kEGL used without OpenGL support.");
#endif
    }
    if (srcHelper) {
        if (!srcHelper->init(reporter)) {
            cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
            return;
        }
    }

    if (DstType::kVulkan == dstType) {
        dstHelper.reset(new VulkanTestHelper());
    } else {
#ifdef SK_GL
        SkASSERT(DstType::kEGL == dstType);
        dstHelper.reset(new EGLTestHelper(options));
#else
        SkASSERTF(false, "DstType::kEGL used without OpenGL support.");
#endif
    }
    if (dstHelper) {
        if (!dstHelper->init(reporter)) {
            cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
            return;
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Setup SkBitmaps
    ///////////////////////////////////////////////////////////////////////////

    SkBitmap srcBitmap = make_src_bitmap();
    SkBitmap dstBitmapSurface;
    dstBitmapSurface.allocN32Pixels(DEV_W, DEV_H);
    SkBitmap dstBitmapFinal;
    dstBitmapFinal.allocN32Pixels(DEV_W, DEV_H);

    ///////////////////////////////////////////////////////////////////////////
    // Setup AHardwareBuffer
    ///////////////////////////////////////////////////////////////////////////

    AHardwareBuffer_Desc hwbDesc;
    hwbDesc.width = DEV_W;
    hwbDesc.height = DEV_H;
    hwbDesc.layers = 1;
    if (SrcType::kCPU == srcType) {
        hwbDesc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
                        AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
                        AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE;
    } else {
        hwbDesc.usage = AHARDWAREBUFFER_USAGE_CPU_READ_NEVER |
                        AHARDWAREBUFFER_USAGE_CPU_WRITE_NEVER |
                        AHARDWAREBUFFER_USAGE_GPU_SAMPLED_IMAGE |
                        AHARDWAREBUFFER_USAGE_GPU_COLOR_OUTPUT;
    }
    hwbDesc.format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM;
    // The following three are not used in the allocate
    hwbDesc.stride = 0;
    hwbDesc.rfu0= 0;
    hwbDesc.rfu1= 0;

    if (int error = AHardwareBuffer_allocate(&hwbDesc, &buffer)) {
        ERRORF(reporter, "Failed to allocated hardware buffer, error: %d", error);
        cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
        return;
    }

    if (SrcType::kCPU == srcType) {
        // Get actual desc for allocated buffer so we know the stride for uploading cpu data.
        AHardwareBuffer_describe(buffer, &hwbDesc);

        uint32_t* bufferAddr;
        if (AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, nullptr,
                                 reinterpret_cast<void**>(&bufferAddr))) {
            ERRORF(reporter, "Failed to lock hardware buffer");
            cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
            return;
        }

        int bbp = srcBitmap.bytesPerPixel();
        uint32_t* src = (uint32_t*)srcBitmap.getPixels();
        uint32_t* dst = bufferAddr;
        for (int y = 0; y < DEV_H; ++y) {
            memcpy(dst, src, DEV_W * bbp);
            src += DEV_W;
            dst += hwbDesc.stride;
        }

        for (int y = 0; y < DEV_H; ++y) {
            for (int x = 0; x < DEV_W; ++x) {
                const uint32_t srcPixel = *srcBitmap.getAddr32(x, y);
                uint32_t dstPixel = bufferAddr[y * hwbDesc.stride + x];
                if (srcPixel != dstPixel) {
                    ERRORF(reporter, "CPU HWB Expected readpix (%d, %d) value 0x%08x, got 0x%08x.",
                           x, y, srcPixel, dstPixel);
                }
            }
        }

        AHardwareBuffer_unlock(buffer, nullptr);

    } else {
        srcHelper->makeCurrent();
        sk_sp<SkSurface> surface = srcHelper->importHardwareBufferForWrite(reporter, buffer);

        if (!surface) {
            cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
            return;
        }

        sk_sp<SkImage> srcBmpImage = SkImages::RasterFromBitmap(srcBitmap);
        surface->getCanvas()->drawImage(srcBmpImage, 0, 0);

        // If we are testing sharing of syncs, don't do a read here since it forces sychronization
        // to occur.
        if (!shareSyncs) {
            bool readResult = surface->readPixels(dstBitmapSurface, 0, 0);
            if (!readResult) {
                ERRORF(reporter, "Read Pixels on surface failed");
                surface.reset();
                cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
                return;
            }
            REPORTER_ASSERT(reporter, check_read(reporter, srcBitmap, dstBitmapSurface));
        }

        ///////////////////////////////////////////////////////////////////////////
        // Cleanup GL/EGL and add syncs
        ///////////////////////////////////////////////////////////////////////////

        if (shareSyncs) {
            if (!srcHelper->flushSurfaceAndSignalSemaphore(reporter, std::move(surface))) {
                cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
                return;
            }
        } else {
            srcHelper->releaseSurfaceToExternal(surface.get());
            srcHelper->doClientSync();
            surface.reset();
            srcHelper->releaseImage();
        }
    }

    ///////////////////////////////////////////////////////////////////////////
    // Import the HWB into backend and draw it to a surface
    ///////////////////////////////////////////////////////////////////////////

    dstHelper->makeCurrent();
    sk_sp<SkImage> wrappedImage = dstHelper->importHardwareBufferForRead(reporter, buffer);

    if (!wrappedImage) {
        cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
        return;
    }

    auto direct = dstHelper->directContext();

    // Make SkSurface to render wrapped HWB into.
    SkImageInfo imageInfo = SkImageInfo::Make(DEV_W, DEV_H, kRGBA_8888_SkColorType,
                                              kPremul_SkAlphaType, nullptr);

    sk_sp<SkSurface> dstSurf = SkSurfaces::RenderTarget(
            direct, skgpu::Budgeted::kNo, imageInfo, 0, kTopLeft_GrSurfaceOrigin, nullptr, false);
    if (!dstSurf.get()) {
        ERRORF(reporter, "Failed to create destination SkSurface");
        wrappedImage.reset();
        cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
        return;
    }

    if (shareSyncs) {
        if (!dstHelper->importAndWaitOnSemaphore(reporter, srcHelper->getFdHandle(), dstSurf)) {
            wrappedImage.reset();
            cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
            return;
        }
    }
    dstSurf->getCanvas()->drawImage(wrappedImage, 0, 0);

    bool readResult = dstSurf->readPixels(dstBitmapFinal, 0, 0);
    if (!readResult) {
        ERRORF(reporter, "Read Pixels failed");
        wrappedImage.reset();
        dstSurf.reset();
        dstHelper->doClientSync();
        cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
        return;
    }

    REPORTER_ASSERT(reporter, check_read(reporter, srcBitmap, dstBitmapFinal));

    dstSurf.reset();
    wrappedImage.reset();
    dstHelper->doClientSync();
    cleanup_resources(srcHelper.get(), dstHelper.get(), buffer);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_CPU_Vulkan, reporter, options, CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kCPU, DstType::kVulkan, false);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_Vulkan_Vulkan,
                reporter,
                options,
                CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kVulkan, DstType::kVulkan, false);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_Vulkan_Vulkan_Syncs,
                reporter,
                options,
                CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kVulkan, DstType::kVulkan, true);
}

#if defined(SK_GL)
DEF_GANESH_TEST(VulkanHardwareBuffer_EGL_Vulkan, reporter, options, CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kEGL, DstType::kVulkan, false);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_CPU_EGL, reporter, options, CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kCPU, DstType::kEGL, false);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_EGL_EGL, reporter, options, CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kEGL, DstType::kEGL, false);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_Vulkan_EGL, reporter, options, CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kVulkan, DstType::kEGL, false);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_EGL_EGL_Syncs,
                reporter,
                options,
                CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kEGL, DstType::kEGL, true);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_Vulkan_EGL_Syncs,
                reporter,
                options,
                CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kVulkan, DstType::kEGL, true);
}

DEF_GANESH_TEST(VulkanHardwareBuffer_EGL_Vulkan_Syncs,
                reporter,
                options,
                CtsEnforcement::kApiLevel_T) {
    run_test(reporter, options, SrcType::kEGL, DstType::kVulkan, true);
}
#endif

#endif  // defined(SK_GANESH) && defined(SK_BUILD_FOR_ANDROID) &&
        // __ANDROID_API__ >= 26 && defined(SK_VULKAN)

