|  | /* | 
|  | * 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/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(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; | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | GrBackendTexture backendTex(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; | 
|  | } | 
|  |  | 
|  | GrBackendTexture backendTex(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) | 
|  |  |