Skip to content

Commit

Permalink
Add support for Android hardware buffer (#2315)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored Apr 3, 2024
1 parent 1925223 commit fc7bb5f
Show file tree
Hide file tree
Showing 6 changed files with 316 additions and 1 deletion.
1 change: 1 addition & 0 deletions package/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
245 changes: 245 additions & 0 deletions package/android/cpp/rnskia-android/GrAHardwareBufferUtils.cpp
Original file line number Diff line number Diff line change
@@ -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 <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES/gl.h>
#include <GLES/glext.h>
#include <android/hardware_buffer.h>

#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<int>(fTexTarget), static_cast<int>(fTexID),
static_cast<int>(status));
return;
}
glEGLImageTargetTexture2DOES(fTexTarget, fImage);
if ((status = glGetError()) != GL_NO_ERROR) {
SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)",
static_cast<int>(status));
return;
}
dContext->resetContext(kTextureBinding_GrGLBackendState);
}

void delete_gl_texture(void *context) {
GLTextureHelper *cleanupHelper = static_cast<GLTextureHelper *>(context);
delete cleanupHelper;
}

void update_gl_texture(void *context, GrDirectContext *dContext) {
GLTextureHelper *cleanupHelper = static_cast<GLTextureHelper *>(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<int>(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<int>(status));
glDeleteTextures(1, &texID);
eglDestroyImageKHR(display, image);
return GrBackendTexture();
}
glEGLImageTargetTexture2DOES(target, image);
if ((status = glGetError()) != GL_NO_ERROR) {
SkDebugf("glEGLImageTargetTexture2DOES failed (%#x)",
static_cast<int>(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
33 changes: 33 additions & 0 deletions package/android/cpp/rnskia-android/GrAHardwareBufferUtils.h
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -12,6 +14,34 @@ namespace RNSkia {

thread_local SkiaOpenGLContext ThreadContextHolder::ThreadSkiaOpenGLContext;

sk_sp<SkImage>
SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(const SkImageInfo &info,
void *buffer) {
#if __ANDROID_API__ >= 26
const AHardwareBuffer *hardwareBuffer =
static_cast<AHardwareBuffer *>(buffer);
DeleteImageProc deleteImageProc = nullptr;
UpdateImageProc updateImageProc = nullptr;
TexImageCtx deleteImageCtx = nullptr;
auto backendTex = MakeGLBackendTexture(
ThreadContextHolder::ThreadSkiaOpenGLContext.directContext.get(),
const_cast<AHardwareBuffer *>(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<SkImage> 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<SkSurface> SkiaOpenGLSurfaceFactory::makeOffscreenSurface(int width,
int height) {
// Setup OpenGL and Skia:
Expand Down
3 changes: 3 additions & 0 deletions package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ class SkiaOpenGLSurfaceFactory {
*/
static sk_sp<SkSurface> makeOffscreenSurface(int width, int height);

static sk_sp<SkImage> makeImageFromHardwareBuffer(const SkImageInfo &info,
void *buffer);

/**
* Creates a windowed Skia Surface holder.
* @param width Initial width of surface
Expand Down
5 changes: 4 additions & 1 deletion scripts/copy-skia-module-headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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/.",
Expand Down

0 comments on commit fc7bb5f

Please sign in to comment.