From fc7bb5fc19a26782ccc4c508cd7ca148a3d11bfa Mon Sep 17 00:00:00 2001 From: William Candillon Date: Wed, 3 Apr 2024 15:25:30 +0200 Subject: [PATCH] Add support for Android hardware buffer (#2315) --- package/android/CMakeLists.txt | 1 + .../rnskia-android/GrAHardwareBufferUtils.cpp | 245 ++++++++++++++++++ .../rnskia-android/GrAHardwareBufferUtils.h | 33 +++ .../SkiaOpenGLSurfaceFactory.cpp | 30 +++ .../rnskia-android/SkiaOpenGLSurfaceFactory.h | 3 + scripts/copy-skia-module-headers.ts | 5 +- 6 files changed, 316 insertions(+), 1 deletion(-) create mode 100644 package/android/cpp/rnskia-android/GrAHardwareBufferUtils.cpp create mode 100644 package/android/cpp/rnskia-android/GrAHardwareBufferUtils.h diff --git a/package/android/CMakeLists.txt b/package/android/CMakeLists.txt index ead2730da0..650f6af571 100644 --- a/package/android/CMakeLists.txt +++ b/package/android/CMakeLists.txt @@ -46,6 +46,7 @@ add_library( "${PROJECT_SOURCE_DIR}/cpp/jni/JniPlatformContext.cpp" "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp" "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp" + "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/GrAHardwareBufferUtils.cpp" "${PROJECT_SOURCE_DIR}/../cpp/jsi/JsiHostObject.cpp" "${PROJECT_SOURCE_DIR}/../cpp/jsi/JsiValue.cpp" diff --git a/package/android/cpp/rnskia-android/GrAHardwareBufferUtils.cpp b/package/android/cpp/rnskia-android/GrAHardwareBufferUtils.cpp new file mode 100644 index 0000000000..f1cb0e0221 --- /dev/null +++ b/package/android/cpp/rnskia-android/GrAHardwareBufferUtils.cpp @@ -0,0 +1,245 @@ +/* + * Copyright 2023 Google LLC + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "GrAHardwareBufferUtils.h" +#if __ANDROID_API__ >= 26 + +#define GL_GLEXT_PROTOTYPES +#define EGL_EGLEXT_PROTOTYPES + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrDirectContext.h" +#include "include/gpu/ganesh/gl/GrGLBackendSurface.h" +#include "include/gpu/gl/GrGLTypes.h" +#include "src/gpu/ganesh/gl/GrGLDefines.h" +// #include "src/gpu/ganesh/GrDirectContextPriv.h" +// #include "src/gpu/ganesh/gl/GrGLUtil.h" + +#include +#include +#include +#include +#include + +#define PROT_CONTENT_EXT_STR "EGL_EXT_protected_content" +#define EGL_PROTECTED_CONTENT_EXT 0x32C0 + +namespace RNSkia { + +typedef EGLClientBuffer (*EGLGetNativeClientBufferANDROIDProc)( + const struct AHardwareBuffer *); +typedef EGLImageKHR (*EGLCreateImageKHRProc)(EGLDisplay, EGLContext, EGLenum, + EGLClientBuffer, const EGLint *); +typedef void (*EGLImageTargetTexture2DOESProc)(EGLenum, void *); + +GrBackendFormat GetGLBackendFormat(GrDirectContext *dContext, + uint32_t bufferFormat, + bool requireKnownFormat) { + GrBackendApi backend = dContext->backend(); + if (backend != GrBackendApi::kOpenGL) { + return GrBackendFormat(); + } + switch (bufferFormat) { + // TODO: find out if we can detect, which graphic buffers support + // GR_GL_TEXTURE_2D + case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: + case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM: + return GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: + return GrBackendFormats::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: + return GrBackendFormats::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: + return GrBackendFormats::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM: + return GrBackendFormats::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_EXTERNAL); +#if __ANDROID_API__ >= 33 + case AHARDWAREBUFFER_FORMAT_R8_UNORM: + return GrBackendFormats::MakeGL(GR_GL_R8, GR_GL_TEXTURE_EXTERNAL); +#endif + default: + if (requireKnownFormat) { + return GrBackendFormat(); + } else { + return GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); + } + } + SkUNREACHABLE; +} + +class GLTextureHelper { +public: + GLTextureHelper(GrGLuint texID, EGLImageKHR image, EGLDisplay display, + GrGLuint texTarget) + : fTexID(texID), fImage(image), fDisplay(display), fTexTarget(texTarget) { + } + ~GLTextureHelper() { + glDeleteTextures(1, &fTexID); + // eglDestroyImageKHR will remove a ref from the AHardwareBuffer + eglDestroyImageKHR(fDisplay, fImage); + } + void rebind(GrDirectContext *); + +private: + GrGLuint fTexID; + EGLImageKHR fImage; + EGLDisplay fDisplay; + GrGLuint fTexTarget; +}; + +void GLTextureHelper::rebind(GrDirectContext *dContext) { + glBindTexture(fTexTarget, fTexID); + GLenum status = GL_NO_ERROR; + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glBindTexture(%#x, %d) failed (%#x)", + static_cast(fTexTarget), static_cast(fTexID), + static_cast(status)); + return; + } + glEGLImageTargetTexture2DOES(fTexTarget, fImage); + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)", + static_cast(status)); + return; + } + dContext->resetContext(kTextureBinding_GrGLBackendState); +} + +void delete_gl_texture(void *context) { + GLTextureHelper *cleanupHelper = static_cast(context); + delete cleanupHelper; +} + +void update_gl_texture(void *context, GrDirectContext *dContext) { + GLTextureHelper *cleanupHelper = static_cast(context); + cleanupHelper->rebind(dContext); +} + +static GrBackendTexture make_gl_backend_texture( + GrDirectContext *dContext, AHardwareBuffer *hardwareBuffer, int width, + int height, DeleteImageProc *deleteProc, UpdateImageProc *updateProc, + TexImageCtx *imageCtx, bool isProtectedContent, + const GrBackendFormat &backendFormat, bool isRenderable) { + while (GL_NO_ERROR != glGetError()) { + } // clear GL errors + + EGLGetNativeClientBufferANDROIDProc eglGetNativeClientBufferANDROID = + (EGLGetNativeClientBufferANDROIDProc)eglGetProcAddress( + "eglGetNativeClientBufferANDROID"); + if (!eglGetNativeClientBufferANDROID) { + RNSkLogger::logToConsole( + "Failed to get the eglGetNativeClientBufferAndroid proc"); + return GrBackendTexture(); + } + + EGLClientBuffer clientBuffer = + eglGetNativeClientBufferANDROID(hardwareBuffer); + EGLint attribs[] = {EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, + isProtectedContent ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE, + isProtectedContent ? EGL_TRUE : EGL_NONE, EGL_NONE}; + EGLDisplay display = eglGetCurrentDisplay(); + // eglCreateImageKHR will add a ref to the AHardwareBuffer + EGLImageKHR image = + eglCreateImageKHR(display, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID, + clientBuffer, attribs); + if (EGL_NO_IMAGE_KHR == image) { + SkDebugf("Could not create EGL image, err = (%#x)", + static_cast(eglGetError())); + return GrBackendTexture(); + } + + GrGLuint texID; + glGenTextures(1, &texID); + if (!texID) { + eglDestroyImageKHR(display, image); + return GrBackendTexture(); + } + + GrGLuint target = isRenderable ? GR_GL_TEXTURE_2D : GR_GL_TEXTURE_EXTERNAL; + + glBindTexture(target, texID); + GLenum status = GL_NO_ERROR; + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glBindTexture failed (%#x)", static_cast(status)); + glDeleteTextures(1, &texID); + eglDestroyImageKHR(display, image); + return GrBackendTexture(); + } + glEGLImageTargetTexture2DOES(target, image); + if ((status = glGetError()) != GL_NO_ERROR) { + SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)", + static_cast(status)); + glDeleteTextures(1, &texID); + eglDestroyImageKHR(display, image); + return GrBackendTexture(); + } + dContext->resetContext(kTextureBinding_GrGLBackendState); + + GrGLTextureInfo textureInfo; + textureInfo.fID = texID; + SkASSERT(backendFormat.isValid()); + textureInfo.fTarget = target; + textureInfo.fFormat = GrBackendFormats::AsGLFormatEnum(backendFormat); + textureInfo.fProtected = skgpu::Protected(isProtectedContent); + + *deleteProc = delete_gl_texture; + *updateProc = update_gl_texture; + *imageCtx = new GLTextureHelper(texID, image, display, target); + + return GrBackendTextures::MakeGL(width, height, skgpu::Mipmapped::kNo, + textureInfo); +} + +static bool can_import_protected_content_eglimpl() { + EGLDisplay dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY); + const char *exts = eglQueryString(dpy, EGL_EXTENSIONS); + size_t cropExtLen = strlen(PROT_CONTENT_EXT_STR); + size_t extsLen = strlen(exts); + bool equal = !strcmp(PROT_CONTENT_EXT_STR, exts); + bool atStart = !strncmp(PROT_CONTENT_EXT_STR " ", exts, cropExtLen + 1); + bool atEnd = + (cropExtLen + 1) < extsLen && + !strcmp(" " PROT_CONTENT_EXT_STR, exts + extsLen - (cropExtLen + 1)); + bool inMiddle = strstr(exts, " " PROT_CONTENT_EXT_STR " "); + return equal || atStart || atEnd || inMiddle; +} + +static bool can_import_protected_content(GrDirectContext *dContext) { + SkASSERT(GrBackendApi::kOpenGL == dContext->backend()); + // Only compute whether the extension is present once the first time this + // function is called. + static bool hasIt = can_import_protected_content_eglimpl(); + return hasIt; +} + +GrBackendTexture +MakeGLBackendTexture(GrDirectContext *dContext, AHardwareBuffer *hardwareBuffer, + int width, int height, DeleteImageProc *deleteProc, + UpdateImageProc *updateProc, TexImageCtx *imageCtx, + bool isProtectedContent, + const GrBackendFormat &backendFormat, bool isRenderable) { + SkASSERT(dContext); + if (!dContext || dContext->abandoned()) { + return GrBackendTexture(); + } + + if (GrBackendApi::kOpenGL != dContext->backend()) { + return GrBackendTexture(); + } + + if (isProtectedContent && !can_import_protected_content(dContext)) { + return GrBackendTexture(); + } + + return make_gl_backend_texture( + dContext, hardwareBuffer, width, height, deleteProc, updateProc, imageCtx, + isProtectedContent, backendFormat, isRenderable); +} + +} // namespace RNSkia + +#endif \ No newline at end of file diff --git a/package/android/cpp/rnskia-android/GrAHardwareBufferUtils.h b/package/android/cpp/rnskia-android/GrAHardwareBufferUtils.h new file mode 100644 index 0000000000..d084ef6129 --- /dev/null +++ b/package/android/cpp/rnskia-android/GrAHardwareBufferUtils.h @@ -0,0 +1,33 @@ +#pragma once + +#include "include/core/SkTypes.h" + +#include "RNSkLog.h" + +#if __ANDROID_API__ >= 26 + +#include "include/gpu/GrBackendSurface.h" +#include "include/gpu/GrTypes.h" + +class GrDirectContext; + +extern "C" { +typedef struct AHardwareBuffer AHardwareBuffer; +} + +namespace RNSkia { + +typedef void *TexImageCtx; +typedef void (*DeleteImageProc)(TexImageCtx); +typedef void (*UpdateImageProc)(TexImageCtx, GrDirectContext *); + +GrBackendTexture +MakeGLBackendTexture(GrDirectContext *dContext, AHardwareBuffer *hardwareBuffer, + int width, int height, DeleteImageProc *deleteProc, + UpdateImageProc *updateProc, TexImageCtx *imageCtx, + bool isProtectedContent, + const GrBackendFormat &backendFormat, bool isRenderable); + +} // namespace RNSkia + +#endif \ No newline at end of file diff --git a/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp b/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp index a88e9473c1..5207394e49 100644 --- a/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp +++ b/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp @@ -1,9 +1,11 @@ #include "SkiaOpenGLSurfaceFactory.h" +#include "GrAHardwareBufferUtils.h" #include "SkiaOpenGLHelper.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdocumentation" +#include "include/gpu/ganesh/SkImageGanesh.h" #include "include/gpu/ganesh/gl/GrGLBackendSurface.h" #pragma clang diagnostic pop @@ -12,6 +14,34 @@ namespace RNSkia { thread_local SkiaOpenGLContext ThreadContextHolder::ThreadSkiaOpenGLContext; +sk_sp +SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(const SkImageInfo &info, + void *buffer) { +#if __ANDROID_API__ >= 26 + const AHardwareBuffer *hardwareBuffer = + static_cast(buffer); + DeleteImageProc deleteImageProc = nullptr; + UpdateImageProc updateImageProc = nullptr; + TexImageCtx deleteImageCtx = nullptr; + auto backendTex = MakeGLBackendTexture( + ThreadContextHolder::ThreadSkiaOpenGLContext.directContext.get(), + const_cast(hardwareBuffer), info.width(), + info.height(), &deleteImageProc, &updateImageProc, &deleteImageCtx, false, + // GR_GL_RGBA8 0x8058 + // GR_GL_TEXTURE_EXTERNAL 0x8D65 + GrBackendFormats::MakeGL(0x8058, 0x8D65), false); + sk_sp image = SkImages::BorrowTextureFrom( + ThreadContextHolder::ThreadSkiaOpenGLContext.directContext.get(), + backendTex, kTopLeft_GrSurfaceOrigin, kRGBA_8888_SkColorType, + kPremul_SkAlphaType, nullptr); + return image; +#else + RNSkLogger::logToConsole( + "Hardware buffer in only supported on Android API level 26 and above."); + return nullptr; +#endif +} + sk_sp SkiaOpenGLSurfaceFactory::makeOffscreenSurface(int width, int height) { // Setup OpenGL and Skia: diff --git a/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h b/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h index 2287958b45..21c4564c97 100644 --- a/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h +++ b/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h @@ -149,6 +149,9 @@ class SkiaOpenGLSurfaceFactory { */ static sk_sp makeOffscreenSurface(int width, int height); + static sk_sp makeImageFromHardwareBuffer(const SkImageInfo &info, + void *buffer); + /** * Creates a windowed Skia Surface holder. * @param width Initial width of surface diff --git a/scripts/copy-skia-module-headers.ts b/scripts/copy-skia-module-headers.ts index 93bfdff1db..9fac228656 100644 --- a/scripts/copy-skia-module-headers.ts +++ b/scripts/copy-skia-module-headers.ts @@ -18,7 +18,10 @@ const copyModule = (module: string) => [ `cp -a ./externals/skia/src/core/SkChecksum.h ./package/cpp/skia/src/core/.`, `cp -a ./externals/skia/src/core/SkTHash.h ./package/cpp/skia/src/core/.`, - "cp -a ./externals/skia/src/core/SkLRUCache.h ./package/cpp/skia/src/core/.", + "mkdir -p ./package/cpp/skia/src/gpu/ganesh/gl", + `cp -a ./externals/skia/src/gpu/ganesh/gl/GrGLDefines.h ./package/cpp/skia/src/gpu/ganesh/gl/.`, + + `cp -a ./externals/skia/src/core/SkLRUCache.h ./package/cpp/skia/src/core/.`, "mkdir -p ./package/cpp/skia/src/base", "cp -a ./externals/skia/src/base/SkTInternalLList.h ./package/cpp/skia/src/base/.",