text: /*
   * 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 intializers to work
  
  #include "include/core/SkTypes.h"
  
  #if SK_SUPPORT_GPU && defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 && defined(SK_VULKAN)
  
  #include "include/core/SkBitmap.h"
  #include "include/core/SkCanvas.h"
  #include "include/core/SkImage.h"
  #include "include/core/SkSurface.h"
  #include "include/gpu/GrBackendSemaphore.h"
  #include "include/gpu/GrDirectContext.h"
  #include "include/gpu/vk/GrVkBackendContext.h"
  #include "include/gpu/vk/GrVkExtensions.h"
  #include "src/core/SkAutoMalloc.h"
  #include "src/gpu/GrDirectContextPriv.h"
  #include "src/gpu/GrGpu.h"
  #include "src/gpu/GrProxyProvider.h"
  #include "src/gpu/SkGr.h"
  #include "src/gpu/gl/GrGLDefines.h"
  #include "src/gpu/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;
      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 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(sk_gpu_test::GrContextFactory::kGLES_ContextType);
      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;
  
      GrBackendTexture backendTex(DEV_W, DEV_H, GrMipmapped::kNo, textureInfo);
      REPORTER_ASSERT(reporter, backendTex.isValid());
  
      sk_sp<SkImage> image = SkImage::MakeFromTexture(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;
  
      GrBackendTexture backendTex(DEV_W, DEV_H, GrMipmapped::kNo, textureInfo);
      REPORTER_ASSERT(reporter, backendTex.isValid());
  
      sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTexture(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) {
      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;
      }
  
      surface->flushAndSubmit();
      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 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;
  
      GrVkExtensions*                     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 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(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;
                  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;
      }
  
      GrVkAlloc 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;
      }
  
      GrBackendTexture backendTex(DEV_W, DEV_H, imageInfo);
  
      sk_sp<SkImage> wrappedImage = SkImage::MakeFromTexture(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) {
      surface->flushAndSubmit();
      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;
      }
  
      GrBackendTexture backendTex(DEV_W, DEV_H, imageInfo);
  
      sk_sp<SkSurface> surface = SkSurface::MakeFromBackendTexture(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
          SkASSERT(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
          SkASSERT(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 = SkImage::MakeFromBitmap(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 {
              surface.reset();
              srcHelper->doClientSync();
              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 = SkSurface::MakeRenderTarget(direct,
                                                             SkBudgeted::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_GPUTEST(VulkanHardwareBuffer_CPU_Vulkan, reporter, options) {
      run_test(reporter, options, SrcType::kCPU, DstType::kVulkan, false);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_Vulkan_Vulkan, reporter, options) {
      run_test(reporter, options, SrcType::kVulkan, DstType::kVulkan, false);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_Vulkan_Vulkan_Syncs, reporter, options) {
      run_test(reporter, options, SrcType::kVulkan, DstType::kVulkan, true);
  }
  
  #if defined(SK_GL)
  DEF_GPUTEST(VulkanHardwareBuffer_EGL_Vulkan, reporter, options) {
      run_test(reporter, options, SrcType::kEGL, DstType::kVulkan, false);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_CPU_EGL, reporter, options) {
      run_test(reporter, options, SrcType::kCPU, DstType::kEGL, false);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_EGL_EGL, reporter, options) {
      run_test(reporter, options, SrcType::kEGL, DstType::kEGL, false);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_Vulkan_EGL, reporter, options) {
      run_test(reporter, options, SrcType::kVulkan, DstType::kEGL, false);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_EGL_EGL_Syncs, reporter, options) {
      run_test(reporter, options, SrcType::kEGL, DstType::kEGL, true);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_Vulkan_EGL_Syncs, reporter, options) {
      run_test(reporter, options, SrcType::kVulkan, DstType::kEGL, true);
  }
  
  DEF_GPUTEST(VulkanHardwareBuffer_EGL_Vulkan_Syncs, reporter, options) {
      run_test(reporter, options, SrcType::kEGL, DstType::kVulkan, true);
  }
  #endif
  
  #endif  // SK_SUPPORT_GPU && defined(SK_BUILD_FOR_ANDROID) &&
          // __ANDROID_API__ >= 26 && defined(SK_VULKAN)
  
  