| /* | |
| * 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 "include/android/GrAHardwareBufferUtils.h" | |
| #if defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 26 | |
| #define GL_GLEXT_PROTOTYPES | |
| #define EGL_EGLEXT_PROTOTYPES | |
| #include "include/gpu/ganesh/GrBackendSurface.h" | |
| #include "include/gpu/ganesh/GrDirectContext.h" | |
| #include "include/gpu/ganesh/gl/GrGLBackendSurface.h" | |
| #include "include/gpu/ganesh/gl/GrGLTypes.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/gpu/GlobalResourceStats.h" | |
| #include "src/gpu/ganesh/GrDirectContextPriv.h" | |
| #include "src/gpu/ganesh/GrSurface.h" | |
| #include "src/gpu/ganesh/gl/GrGLDefines.h" | |
| #include "src/gpu/ganesh/gl/GrGLUtil.h" | |
| #include <android/hardware_buffer.h> | |
| #include <EGL/egl.h> | |
| #include <EGL/eglext.h> | |
| #include <GLES/gl.h> | |
| #include <GLES/glext.h> | |
| #define PROT_CONTENT_EXT_STR "EGL_EXT_protected_content" | |
| #define EGL_PROTECTED_CONTENT_EXT 0x32C0 | |
| #if __has_include(<vndk/hardware_buffer.h>) | |
| // When building for the Android framework, there are formats defined outside of those publicly | |
| // available in android/hardware_buffer.h. | |
| #include <vndk/hardware_buffer.h> | |
| #define HAS_AHB_BGRA8_UNORM | |
| #endif | |
| namespace GrAHardwareBufferUtils { | |
| GrBackendFormat GetGLBackendFormat(GrDirectContext* dContext, | |
| uint32_t bufferFormat, bool requireKnownFormat) { | |
| GrBackendApi backend = dContext->backend(); | |
| if (backend != GrBackendApi::kOpenGL) { | |
| return GrBackendFormat(); | |
| } | |
| switch (bufferFormat) { | |
| case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: | |
| return GrBackendFormats::MakeGL(GR_GL_RGBA8); | |
| case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM: | |
| // According to https://developer.android.com/ndk/reference/group/a-hardware-buffer, | |
| // the GL format equivalent is GL_RGB8 but that is considered to be 24bpp throughout | |
| // Ganesh. For now keep it as an external format until we figure out the RGB vs RGBA | |
| // compatibility. | |
| return GrBackendFormats::MakeGLExternal(); | |
| case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: | |
| return GrBackendFormats::MakeGL(GR_GL_RGBA16F); | |
| case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: | |
| return GrBackendFormats::MakeGL(GR_GL_RGB565); | |
| case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: | |
| return GrBackendFormats::MakeGL(GR_GL_RGB10_A2); | |
| case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM: | |
| return GrBackendFormats::MakeGL(GR_GL_RGB8); | |
| #if __ANDROID_API__ >= 33 | |
| case AHARDWAREBUFFER_FORMAT_R8_UNORM: | |
| return GrBackendFormats::MakeGL(GR_GL_R8); | |
| #endif | |
| #if __ANDROID_API__ >= 34 | |
| case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM: | |
| // There is no GLFormat enum that corresponds to this texture format, but it is known | |
| // (GrColorType and SkColorType can describe it). | |
| return GrBackendFormats::MakeGLExternal(); | |
| #endif | |
| #ifdef HAS_AHB_BGRA8_UNORM | |
| case AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM: | |
| // On some platforms, HWComposer requests BGRA_8888 (DRM_FORMAT_AR24) | |
| // as the GPU client-composition / framebuffer render target. Map it to a | |
| // renderable GL_BGRA8 2D format (needs GL_EXT_texture_format_BGRA8888, which the | |
| // driver exposes) so output/renderable buffers pass isFormatRenderable() instead | |
| // of falling through to a non-renderable external format and aborting. | |
| return GrBackendFormats::MakeGL(GR_GL_BGRA8); | |
| #endif | |
| default: | |
| if (requireKnownFormat) { | |
| return GrBackendFormat(); | |
| } else { | |
| return GrBackendFormats::MakeGLExternal(); | |
| } | |
| } | |
| SkUNREACHABLE; | |
| } | |
| class GLTextureHelper { | |
| public: | |
| GLTextureHelper(GrGLuint texID, EGLImageKHR image, EGLDisplay display, GrGLuint texTarget, | |
| size_t size, skgpu::Protected isProtected) | |
| : fTexID(texID) | |
| , fImage(image) | |
| , fDisplay(display) | |
| , fTexTarget(texTarget) | |
| , fSize(size) | |
| , fProtected(isProtected) { | |
| skgpu::GlobalResourceStats::RecordCreateBackendTexture(isProtected, size); | |
| } | |
| ~GLTextureHelper() { | |
| glDeleteTextures(1, &fTexID); | |
| // eglDestroyImageKHR will remove a ref from the AHardwareBuffer | |
| eglDestroyImageKHR(fDisplay, fImage); | |
| skgpu::GlobalResourceStats::RecordDeleteBackendTexture(fProtected, fSize); | |
| } | |
| void rebind(GrDirectContext*); | |
| private: | |
| GrGLuint fTexID; | |
| EGLImageKHR fImage; | |
| EGLDisplay fDisplay; | |
| GrGLuint fTexTarget; | |
| // For stats tracking | |
| size_t fSize; | |
| skgpu::Protected fProtected; | |
| }; | |
| void GLTextureHelper::rebind(GrDirectContext* dContext) { | |
| glBindTexture(fTexTarget, fTexID); | |
| GLenum status = GL_NO_ERROR; | |
| if ((status = glGetError()) != GL_NO_ERROR) { | |
| SKIA_LOG_E("glBindTexture(%#x, %d) failed (%#x)", (int) fTexTarget, | |
| (int) fTexID, (int) status); | |
| return; | |
| } | |
| glEGLImageTargetTexture2DOES(fTexTarget, fImage); | |
| if ((status = glGetError()) != GL_NO_ERROR) { | |
| SKIA_LOG_E("glEGLImageTargetTexture2DOES failed (%#x)", (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) { | |
| while (GL_NO_ERROR != glGetError()) {} //clear GL errors | |
| auto textureType = backendFormat.textureType(); | |
| if (textureType != GrTextureType::k2D && textureType != GrTextureType::kExternal) { | |
| SKIA_LOG_E("Unsupported texture target type: %d\n", (int) textureType); | |
| 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) { | |
| SKIA_LOG_E("Could not create EGL image, err = (%#x)", (int) eglGetError() ); | |
| return GrBackendTexture(); | |
| } | |
| GrGLuint texID; | |
| glGenTextures(1, &texID); | |
| if (!texID) { | |
| eglDestroyImageKHR(display, image); | |
| return GrBackendTexture(); | |
| } | |
| GrGLuint target = textureType == GrTextureType::kExternal ? GR_GL_TEXTURE_EXTERNAL | |
| : GR_GL_TEXTURE_2D; | |
| if (!dContext->priv().caps()->shaderCaps()->fExternalTextureSupport && | |
| textureType == GrTextureType::kExternal) { | |
| // The extension OES_EGL_image_external is mandatory for an | |
| // Android-compatible device. See | |
| // https://source.android.com/docs/core/graphics/implement-opengl-es | |
| // | |
| // This path exists to support a RenderDoc fork which supports | |
| // OES_EGL_image but not OES_EGL_image_external. | |
| target = GR_GL_TEXTURE_2D; | |
| } | |
| glBindTexture(target, texID); | |
| GLenum status = GL_NO_ERROR; | |
| if ((status = glGetError()) != GL_NO_ERROR) { | |
| SKIA_LOG_E("glBindTexture failed (%#x)", (int) status); | |
| glDeleteTextures(1, &texID); | |
| eglDestroyImageKHR(display, image); | |
| return GrBackendTexture(); | |
| } | |
| glEGLImageTargetTexture2DOES(target, image); | |
| if ((status = glGetError()) != GL_NO_ERROR) { | |
| SKIA_LOG_E("glEGLImageTargetTexture2DOES failed (%#x)", (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); | |
| const size_t size = GrSurface::ComputeSize(backendFormat, {width, height}, | |
| /*colorSamplesPerPixel=*/1, skgpu::Mipmapped::kNo); | |
| *deleteProc = delete_gl_texture; | |
| *updateProc = update_gl_texture; | |
| *imageCtx = new GLTextureHelper(texID, image, display, target, size, textureInfo.fProtected); | |
| 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(); | |
| } | |
| if (isRenderable && | |
| !dContext->priv().caps()->isFormatRenderable(backendFormat, /*sampleCount=*/1)) { | |
| return GrBackendTexture(); | |
| } | |
| GrBackendTexture tex = | |
| make_gl_backend_texture(dContext, hardwareBuffer, width, height, deleteProc, | |
| updateProc, imageCtx, isProtectedContent, backendFormat); | |
| // Retry if we can still fallback external texture target. | |
| // | |
| // Even when the format is known, an AHB can fail to be imported as a regular GL_TEXTURE_2D for | |
| // reasons hidden from the GL API (e.g. lossy compression is meant for sample-only textures, in | |
| // which case a driver might fail glEGLImageTargetTexture2DOES for regular texture targets). | |
| // | |
| // NOTE: We exclude this fallback when `isRenderable == true` since we never treat external | |
| // formats as renderable. If we instead called MakeGLBackendTexture, we'd just fail the | |
| // renderable validation check above. | |
| if (!tex.isValid() && | |
| !isRenderable && | |
| backendFormat.textureType() != GrTextureType::kExternal) { | |
| tex = make_gl_backend_texture(dContext, hardwareBuffer, width, height, deleteProc, | |
| updateProc, imageCtx, isProtectedContent, | |
| GrBackendFormats::MakeGLExternal()); | |
| } | |
| return tex; | |
| } | |
| } // namespace GrAHardwareBufferUtils | |
| #endif |