From 6fbaeab7621862e11adf504894a7ffd52220533f Mon Sep 17 00:00:00 2001 From: William Candillon Date: Tue, 19 Nov 2024 23:46:40 +0100 Subject: [PATCH] =?UTF-8?q?fix(=F0=9F=A4=96):=20Fix=20serious=20Android=20?= =?UTF-8?q?threading=20issue=20(#2749)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every offscreen surfaces where sharing the OpenGL context even though they may be created from different thread. This can easily cause crashes, especially in viewRef.makeImageSnapshot which is using an offscreen surface. We sanitized the OpenGLContext to not have anything shared across threads. --- packages/skia/android/CMakeLists.txt | 3 +- .../cpp/rnskia-android/OpenGLContext.h | 133 +++++++- .../rnskia-android/OpenGLWindowContext.cpp | 73 +++++ .../cpp/rnskia-android/OpenGLWindowContext.h | 74 +++++ .../RNSkOpenGLCanvasProvider.cpp | 1 - .../cpp/rnskia-android/SkiaOpenGLHelper.h | 310 ------------------ .../SkiaOpenGLSurfaceFactory.cpp | 188 ----------- .../rnskia-android/SkiaOpenGLSurfaceFactory.h | 100 ------ .../cpp/rnskia-android/opengl/Context.h | 77 +++++ .../cpp/rnskia-android/opengl/Display.h | 117 +++++++ .../cpp/rnskia-android/opengl/Error.cpp | 9 + .../android/cpp/rnskia-android/opengl/Error.h | 44 +++ .../cpp/rnskia-android/opengl/Surface.h | 43 +++ .../ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm | 12 +- .../ios/RNSkia-iOS/SkiaMetalSurfaceFactory.h | 8 +- 15 files changed, 571 insertions(+), 621 deletions(-) create mode 100644 packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.cpp create mode 100644 packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.h delete mode 100644 packages/skia/android/cpp/rnskia-android/SkiaOpenGLHelper.h delete mode 100644 packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp delete mode 100644 packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h create mode 100644 packages/skia/android/cpp/rnskia-android/opengl/Context.h create mode 100644 packages/skia/android/cpp/rnskia-android/opengl/Display.h create mode 100644 packages/skia/android/cpp/rnskia-android/opengl/Error.cpp create mode 100644 packages/skia/android/cpp/rnskia-android/opengl/Error.h create mode 100644 packages/skia/android/cpp/rnskia-android/opengl/Surface.h diff --git a/packages/skia/android/CMakeLists.txt b/packages/skia/android/CMakeLists.txt index e1efb49fb8..a3da6f5b0b 100644 --- a/packages/skia/android/CMakeLists.txt +++ b/packages/skia/android/CMakeLists.txt @@ -54,7 +54,7 @@ if(SK_GRAPHITE) else() add_definitions(-DSK_GL -DSK_GANESH) set(BACKEND_SOURCES - "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp" + "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/OpenGLWindowContext.cpp" "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/GrAHardwareBufferUtils.cpp" ) endif() @@ -74,6 +74,7 @@ add_library( "${PROJECT_SOURCE_DIR}/cpp/jni/JniSkiaManager.cpp" "${PROJECT_SOURCE_DIR}/cpp/jni/JniPlatformContext.cpp" + "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/opengl/Error.cpp" "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp" "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/AHardwareBufferUtils.cpp" "${PROJECT_SOURCE_DIR}/cpp/rnskia-android/RNSkAndroidVideo.cpp" diff --git a/packages/skia/android/cpp/rnskia-android/OpenGLContext.h b/packages/skia/android/cpp/rnskia-android/OpenGLContext.h index 015de205e9..723e5eda81 100644 --- a/packages/skia/android/cpp/rnskia-android/OpenGLContext.h +++ b/packages/skia/android/cpp/rnskia-android/OpenGLContext.h @@ -1,9 +1,18 @@ #pragma once -#include "SkiaOpenGLSurfaceFactory.h" -#include "WindowContext.h" +#include "GrAHardwareBufferUtils.h" +#include "OpenGLWindowContext.h" +#include "opengl/Display.h" +#include "include/core/SkCanvas.h" +#include "include/core/SkColorSpace.h" #include "include/core/SkSurface.h" +#include "include/gpu/ganesh/GrDirectContext.h" +#include "include/gpu/ganesh/SkImageGanesh.h" +#include "include/gpu/ganesh/gl/GrGLBackendSurface.h" +#include "include/gpu/ganesh/gl/GrGLDirectContext.h" +#include "include/gpu/ganesh/gl/GrGLInterface.h" +#include "src/gpu/ganesh/gl/GrGLDefines.h" class OpenGLContext { public: @@ -16,25 +25,127 @@ class OpenGLContext { } sk_sp MakeOffscreen(int width, int height) { - return RNSkia::SkiaOpenGLSurfaceFactory::makeOffscreenSurface( - &_context, width, height); + auto colorType = kRGBA_8888_SkColorType; + + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + + auto result = _ctx->makeCurrent(*_surface); + if (!result) { + return nullptr; + } + + // Create texture + auto texture = _directContext->createBackendTexture( + width, height, colorType, skgpu::Mipmapped::kNo, GrRenderable::kYes); + + if (!texture.isValid()) { + RNSkia::RNSkLogger::logToConsole( + "couldn't create offscreen texture %dx%d", width, height); + } + + struct ReleaseContext { + GrDirectContext *directContext; + GrBackendTexture texture; + }; + + auto releaseCtx = new ReleaseContext{.directContext = _directContext.get(), + .texture = texture}; + + // Create a SkSurface from the GrBackendTexture + return SkSurfaces::WrapBackendTexture( + _directContext.get(), texture, kTopLeft_GrSurfaceOrigin, 0, colorType, + nullptr, &props, + [](void *addr) { + auto releaseCtx = reinterpret_cast(addr); + releaseCtx->directContext->deleteBackendTexture(releaseCtx->texture); + delete releaseCtx; + }, + releaseCtx); } - sk_sp MakeImageFromBuffer(void *buffer) { - return RNSkia::SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer( - &_context, buffer); + sk_sp MakeImageFromBuffer(void *buffer, + bool requireKnownFormat = false) { +#if __ANDROID_API__ >= 26 + const AHardwareBuffer *hardwareBuffer = + static_cast(buffer); + RNSkia::DeleteImageProc deleteImageProc = nullptr; + RNSkia::UpdateImageProc updateImageProc = nullptr; + RNSkia::TexImageCtx deleteImageCtx = nullptr; + + AHardwareBuffer_Desc description; + AHardwareBuffer_describe(hardwareBuffer, &description); + GrBackendFormat format; + switch (description.format) { + // TODO: find out if we can detect, which graphic buffers support + // GR_GL_TEXTURE_2D + case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: + format = GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: + format = GrBackendFormats::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: + format = GrBackendFormats::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: + format = GrBackendFormats::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_EXTERNAL); + case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM: + format = GrBackendFormats::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_EXTERNAL); +#if __ANDROID_API__ >= 33 + case AHARDWAREBUFFER_FORMAT_R8_UNORM: + format = GrBackendFormats::MakeGL(GR_GL_R8, GR_GL_TEXTURE_EXTERNAL); +#endif + default: + if (requireKnownFormat) { + format = GrBackendFormat(); + } else { + format = GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); + } + } + + auto backendTex = RNSkia::MakeGLBackendTexture( + _directContext.get(), const_cast(hardwareBuffer), + description.width, description.height, &deleteImageProc, + &updateImageProc, &deleteImageCtx, false, format, false); + if (!backendTex.isValid()) { + RNSkia::RNSkLogger::logToConsole( + "Failed to convert HardwareBuffer to OpenGL Texture!"); + return nullptr; + } + sk_sp image = SkImages::BorrowTextureFrom( + _directContext.get(), backendTex, kTopLeft_GrSurfaceOrigin, + kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr, deleteImageProc, + deleteImageCtx); + return image; +#else + throw std::runtime_error( + "HardwareBuffers are only supported on Android API 26 or higher! Set " + "your minSdk to 26 (or higher) and try again."); +#endif } std::unique_ptr MakeWindow(ANativeWindow *window, int width, int height) { - return RNSkia::SkiaOpenGLSurfaceFactory::makeContext(&_context, window, - width, height); + return std::make_unique( + _config, _display.get(), _ctx.get(), _directContext.get(), window, + width, height); } private: - RNSkia::SkiaOpenGLContext _context; + EGLConfig _config; + std::unique_ptr _display; + std::unique_ptr _ctx; + std::unique_ptr _surface; + sk_sp _directContext; OpenGLContext() { - RNSkia::SkiaOpenGLHelper::createSkiaDirectContextIfNecessary(&_context); + _display = std::make_unique(); + _config = _display->chooseConfig(); + _ctx = _display->makeContext(_config, nullptr); + _surface = _display->makePixelBufferSurface(_config, 1, 1); + _ctx->makeCurrent(*_surface); + auto backendInterface = GrGLMakeNativeInterface(); + _directContext = GrDirectContexts::MakeGL(backendInterface); + + if (_directContext == nullptr) { + throw std::runtime_error("GrDirectContexts::MakeGL failed"); + } } }; diff --git a/packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.cpp b/packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.cpp new file mode 100644 index 0000000000..cab7bef7e2 --- /dev/null +++ b/packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.cpp @@ -0,0 +1,73 @@ +#include "OpenGLWindowContext.h" +#include "GrAHardwareBufferUtils.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" + +#include "include/gpu/ganesh/SkImageGanesh.h" +#include "include/gpu/ganesh/gl/GrGLBackendSurface.h" +#include "src/gpu/ganesh/gl/GrGLDefines.h" + +#pragma clang diagnostic pop + +namespace RNSkia { + +sk_sp OpenGLWindowContext::getSurface() { + if (_skSurface == nullptr) { + + struct ReleaseContext { + std::unique_ptr surface = nullptr; + }; + + auto releaseCtx = new ReleaseContext(); + releaseCtx->surface = _display->makeWindowSurface(_config, _window); + _surface = releaseCtx->surface.get(); + + // Now make this one current + _context->makeCurrent(*releaseCtx->surface); + + // Set up parameters for the render target so that it + // matches the underlying OpenGL context. + GrGLFramebufferInfo fboInfo; + + // We pass 0 as the framebuffer id, since the + // underlying Skia GrGlGpu will read this when wrapping the context in the + // render target and the GrGlGpu object. + fboInfo.fFBOID = 0; + fboInfo.fFormat = 0x8058; // GL_RGBA8 + + GLint stencil; + glGetIntegerv(GL_STENCIL_BITS, &stencil); + + GLint samples; + glGetIntegerv(GL_SAMPLES, &samples); + + auto colorType = kN32_SkColorType; + + auto maxSamples = + _directContext->maxSurfaceSampleCountForColorType(colorType); + + if (samples > maxSamples) { + samples = maxSamples; + } + + auto renderTarget = GrBackendRenderTargets::MakeGL(_width, _height, samples, + stencil, fboInfo); + + SkSurfaceProps props(0, kUnknown_SkPixelGeometry); + + + // Create surface object + _skSurface = SkSurfaces::WrapBackendRenderTarget( + _directContext, renderTarget, kBottomLeft_GrSurfaceOrigin, colorType, + nullptr, &props, + [](void *addr) { + auto releaseCtx = reinterpret_cast(addr); + delete releaseCtx; + }, + reinterpret_cast(releaseCtx)); + } + return _skSurface; +} + +} // namespace RNSkia diff --git a/packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.h b/packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.h new file mode 100644 index 0000000000..31cd6bdc81 --- /dev/null +++ b/packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.h @@ -0,0 +1,74 @@ +#pragma once + +#include "RNSkLog.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "WindowContext.h" +#include "opengl/Display.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdocumentation" + +#include "include/core/SkCanvas.h" +#include "include/core/SkColorSpace.h" +#include "include/core/SkSurface.h" +#include "include/gpu/ganesh/GrBackendSurface.h" +#include "include/gpu/ganesh/GrDirectContext.h" +#include "include/gpu/ganesh/SkSurfaceGanesh.h" +#include "include/gpu/ganesh/gl/GrGLInterface.h" + +#pragma clang diagnostic pop + +namespace RNSkia { + +class OpenGLWindowContext : public WindowContext { +public: + OpenGLWindowContext(EGLConfig &config, Display *display, Context *context, + GrDirectContext *directContext, ANativeWindow *window, + int width, int height) + : _config(config), _display(display), _directContext(directContext), + _context(context), _window(window), _width(width), _height(height) {} + + ~OpenGLWindowContext() { ANativeWindow_release(_window); } + + sk_sp getSurface() override; + + void present() override { + _context->makeCurrent(*_surface); + _directContext->flushAndSubmit(); + _surface->Present(); + } + + void resize(int width, int height) override { + _skSurface = nullptr; + _width = width; + _height = height; + } + + int getWidth() override { return _width; }; + + int getHeight() override { return _height; }; + +private: + ANativeWindow *_window; + sk_sp _skSurface = nullptr; + EGLConfig _config; + Context *_context; + Surface* _surface; + Display *_display; + GrDirectContext *_directContext; + int _width = 0; + int _height = 0; +}; + +} // namespace RNSkia diff --git a/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp b/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp index 660f9ecef9..e932e35fcf 100644 --- a/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +++ b/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp @@ -71,7 +71,6 @@ bool RNSkOpenGLCanvasProvider::renderToCanvas( return false; } } - return false; } diff --git a/packages/skia/android/cpp/rnskia-android/SkiaOpenGLHelper.h b/packages/skia/android/cpp/rnskia-android/SkiaOpenGLHelper.h deleted file mode 100644 index d68119e4ea..0000000000 --- a/packages/skia/android/cpp/rnskia-android/SkiaOpenGLHelper.h +++ /dev/null @@ -1,310 +0,0 @@ -#pragma once - -#include "EGL/egl.h" -#include "GLES2/gl2.h" -#include -#include - -#include - -#include "RNSkLog.h" - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdocumentation" - -#include "include/core/SkCanvas.h" -#include "include/core/SkColorSpace.h" -#include "include/core/SkSurface.h" -#include "include/gpu/ganesh/GrDirectContext.h" -#include "include/gpu/ganesh/gl/GrGLDirectContext.h" -#include "include/gpu/ganesh/gl/GrGLInterface.h" - -#pragma clang diagnostic pop - -namespace RNSkia { - -/** - * Singleton holding the default display and shared eglContext that will be the - * first context we create so that we can share data between contexts. - */ -class OpenGLResourceHolder { -private: - OpenGLResourceHolder() { - // Initialize OpenGL - glDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (glDisplay == EGL_NO_DISPLAY) { - RNSkLogger::logToConsole("eglGetDisplay failed : %i", glGetError()); - return; - } - - EGLint major; - EGLint minor; - if (eglInitialize(glDisplay, &major, &minor) != EGL_TRUE) { - RNSkLogger::logToConsole("eglInitialize failed : %i", glGetError()); - return; - } - - // Create a default shared context - glConfig = getConfig(glDisplay); - - // Create OpenGL context attributes - EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - - // Initialize the offscreen context for this thread - glContext = - eglCreateContext(glDisplay, glConfig, glContext, contextAttribs); - if (glContext == EGL_NO_CONTEXT) { - RNSkLogger::logToConsole("eglCreateContext failed : %i", glGetError()); - } - } - - ~OpenGLResourceHolder() { - if (glContext != EGL_NO_CONTEXT) { - eglDestroyContext(glDisplay, glContext); - glContext = EGL_NO_CONTEXT; - } - - if (glDisplay != EGL_NO_DISPLAY) { - eglTerminate(glDisplay); - glDisplay = EGL_NO_DISPLAY; - } - } - /* Explicitly disallow copying. */ - OpenGLResourceHolder(const OpenGLResourceHolder &) = delete; - OpenGLResourceHolder &operator=(const OpenGLResourceHolder &) = delete; - -public: - static OpenGLResourceHolder &getInstance() { - static OpenGLResourceHolder Instance; - return Instance; - } - - /** - * The first context created will be considered the parent / shared context - * and will be used as the parent / shareable context when creating subsequent - * contexts. - */ - std::atomic glContext = EGL_NO_CONTEXT; - /** - * Shared egl display - */ - std::atomic glDisplay = EGL_NO_DISPLAY; - - /** - * Shared eglConfig - */ - std::atomic glConfig = 0; - -private: - /** - * Finds the correct EGL Config for the given parameters - * @param glDisplay - * @return Config or zero if no matching context could be found. - */ - static EGLConfig getConfig(EGLDisplay glDisplay) { - - EGLint att[] = {EGL_RENDERABLE_TYPE, - EGL_OPENGL_ES2_BIT, - EGL_ALPHA_SIZE, - 8, - EGL_BLUE_SIZE, - 8, - EGL_GREEN_SIZE, - 8, - EGL_RED_SIZE, - 8, - EGL_DEPTH_SIZE, - 0, - EGL_STENCIL_SIZE, - 0, - EGL_SAMPLE_BUFFERS, - 0, - EGL_NONE}; - - EGLint numConfigs; - EGLConfig glConfig = 0; - if (eglChooseConfig(glDisplay, att, &glConfig, 1, &numConfigs) != - EGL_TRUE || - numConfigs == 0) { - RNSkLogger::logToConsole( - "Failed to choose a config for %s surface. Error code: %d\n", - eglGetError()); - return 0; - } - - return glConfig; - } -}; - -struct SkiaOpenGLContext { - SkiaOpenGLContext() { - glContext = EGL_NO_CONTEXT; - gl1x1Surface = EGL_NO_SURFACE; - directContext = nullptr; - } - ~SkiaOpenGLContext() { - if (gl1x1Surface != EGL_NO_SURFACE) { - eglDestroySurface(OpenGLResourceHolder::getInstance().glDisplay, - gl1x1Surface); - gl1x1Surface = EGL_NO_SURFACE; - } - - if (directContext) { - directContext->releaseResourcesAndAbandonContext(); - directContext = nullptr; - } - - if (glContext != EGL_NO_CONTEXT) { - eglDestroyContext(OpenGLResourceHolder::getInstance().glDisplay, - glContext); - glContext = EGL_NO_CONTEXT; - } - } - EGLContext glContext; - EGLSurface gl1x1Surface; - sk_sp directContext; -}; - -class SkiaOpenGLHelper { -public: - /** - * Calls eglMakeCurrent on the surface provided using the provided - * thread context. - * @param context Skia OpenGL context to use - * @param surface Surface to set as current - * @return true if eglMakeCurrent was successfull - */ - static bool makeCurrent(SkiaOpenGLContext *context, EGLSurface glSurface) { - // We don't need to call make current if we already are current: - if (eglGetCurrentSurface(EGL_DRAW) != glSurface || - eglGetCurrentSurface(EGL_READ) != glSurface || - eglGetCurrentContext() != context->glContext) { - - // Make current! - if (eglMakeCurrent(OpenGLResourceHolder::getInstance().glDisplay, - glSurface, glSurface, - context->glContext) != EGL_TRUE) { - RNSkLogger::logToConsole("eglMakeCurrent failed: %d\n", eglGetError()); - return false; - } - } - return true; - } - - /** - * Creates a new windowed surface - * @param window ANativeWindow to create surface in - * @return EGLSurface or EGL_NO_SURFACE if the call failed - */ - static EGLSurface createWindowedSurface(ANativeWindow *window) { - const EGLint attribs[] = {EGL_NONE}; - return eglCreateWindowSurface(OpenGLResourceHolder::getInstance().glDisplay, - OpenGLResourceHolder::getInstance().glConfig, - window, attribs); - } - - /** - * Destroys an egl surface - * @param glSurface - * @return - */ - static bool destroySurface(EGLSurface glSurface) { - if (eglMakeCurrent(OpenGLResourceHolder::getInstance().glDisplay, - EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT) != EGL_TRUE) { - RNSkLogger::logToConsole( - "destroySurface: Could not clear selected surface"); - return false; - } - return eglDestroySurface(OpenGLResourceHolder::getInstance().glDisplay, - glSurface) == EGL_TRUE; - } - - /** - * Calls the eglSwapBuffer in the current thread with the provided surface - * @param context Thread context - * @param glSurface surface to present - * @return true if eglSwapBuffers succeeded. - */ - static bool swapBuffers(SkiaOpenGLContext *context, EGLSurface glSurface) { - if (eglSwapBuffers(OpenGLResourceHolder::getInstance().glDisplay, - glSurface) != EGL_TRUE) { - RNSkLogger::logToConsole("eglSwapBuffers failed: %d\n", eglGetError()); - return false; - } - return true; - } - - /*** - * Creates a new Skia direct context backed by the provided eglContext in the - * SkiaOpenGLContext. - * @param context Context to store results in - * @param sharedContext Shared Context - * @return true if the call to create a skia direct context suceeded. - */ - static bool createSkiaDirectContextIfNecessary(SkiaOpenGLContext *context) { - if (context->directContext == nullptr) { - - // Create OpenGL context - createOpenGLContext(context); - - // Create attributes for a simple 1x1 pbuffer surface that we can - // use to activate and create Skia direct context for - const EGLint offScreenSurfaceAttribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, - EGL_NONE}; - - context->gl1x1Surface = - eglCreatePbufferSurface(OpenGLResourceHolder::getInstance().glDisplay, - OpenGLResourceHolder::getInstance().glConfig, - offScreenSurfaceAttribs); - - if (context->gl1x1Surface == EGL_NO_SURFACE) { - RNSkLogger::logToConsole("Failed creating a 1x1 pbuffer surface"); - return false; - } - - // Activate - if (!makeCurrent(context, context->gl1x1Surface)) { - return false; - } - - // Create the Skia context - auto backendInterface = GrGLMakeNativeInterface(); - context->directContext = GrDirectContexts::MakeGL(backendInterface); - - if (context->directContext == nullptr) { - RNSkLogger::logToConsole("GrDirectContexts::MakeGL failed"); - return false; - } - } - - // It all went well! - return true; - } - -private: - /** - * Creates a new GLContext. - * @param context Context to save results in - * @return True if the call to eglCreateContext returned a valid OpenGL - * Context or if the context already is setup. - */ - static bool createOpenGLContext(SkiaOpenGLContext *context) { - // Create OpenGL context attributes - EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; - - // Initialize the offscreen context for this thread - context->glContext = eglCreateContext( - OpenGLResourceHolder::getInstance().glDisplay, - OpenGLResourceHolder::getInstance().glConfig, - OpenGLResourceHolder::getInstance().glContext, contextAttribs); - - if (context->glContext == EGL_NO_CONTEXT) { - RNSkLogger::logToConsole("eglCreateContext failed: %d\n", eglGetError()); - return EGL_NO_CONTEXT; - } - - return true; - } -}; -} // namespace RNSkia diff --git a/packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp b/packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp deleted file mode 100644 index f536a441c1..0000000000 --- a/packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#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" -#include "src/gpu/ganesh/gl/GrGLDefines.h" - -#pragma clang diagnostic pop - -namespace RNSkia { - -sk_sp SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer( - SkiaOpenGLContext *context, void *buffer, bool requireKnownFormat) { -#if __ANDROID_API__ >= 26 - const AHardwareBuffer *hardwareBuffer = - static_cast(buffer); - DeleteImageProc deleteImageProc = nullptr; - UpdateImageProc updateImageProc = nullptr; - TexImageCtx deleteImageCtx = nullptr; - - AHardwareBuffer_Desc description; - AHardwareBuffer_describe(hardwareBuffer, &description); - GrBackendFormat format; - switch (description.format) { - // TODO: find out if we can detect, which graphic buffers support - // GR_GL_TEXTURE_2D - case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM: - format = GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); - case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT: - format = GrBackendFormats::MakeGL(GR_GL_RGBA16F, GR_GL_TEXTURE_EXTERNAL); - case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM: - format = GrBackendFormats::MakeGL(GR_GL_RGB565, GR_GL_TEXTURE_EXTERNAL); - case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM: - format = GrBackendFormats::MakeGL(GR_GL_RGB10_A2, GR_GL_TEXTURE_EXTERNAL); - case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM: - format = GrBackendFormats::MakeGL(GR_GL_RGB8, GR_GL_TEXTURE_EXTERNAL); -#if __ANDROID_API__ >= 33 - case AHARDWAREBUFFER_FORMAT_R8_UNORM: - format = GrBackendFormats::MakeGL(GR_GL_R8, GR_GL_TEXTURE_EXTERNAL); -#endif - default: - if (requireKnownFormat) { - format = GrBackendFormat(); - } else { - format = GrBackendFormats::MakeGL(GR_GL_RGBA8, GR_GL_TEXTURE_EXTERNAL); - } - } - - auto backendTex = MakeGLBackendTexture( - context->directContext.get(), - const_cast(hardwareBuffer), description.width, - description.height, &deleteImageProc, &updateImageProc, &deleteImageCtx, - false, format, false); - if (!backendTex.isValid()) { - RNSkLogger::logToConsole( - "Failed to convert HardwareBuffer to OpenGL Texture!"); - return nullptr; - } - sk_sp image = SkImages::BorrowTextureFrom( - context->directContext.get(), backendTex, kTopLeft_GrSurfaceOrigin, - kRGBA_8888_SkColorType, kOpaque_SkAlphaType, nullptr, deleteImageProc, - deleteImageCtx); - return image; -#else - throw std::runtime_error( - "HardwareBuffers are only supported on Android API 26 or higher! Set " - "your minSdk to 26 (or higher) and try again."); -#endif -} - -sk_sp -SkiaOpenGLSurfaceFactory::makeOffscreenSurface(SkiaOpenGLContext *context, - int width, int height) { - - auto colorType = kN32_SkColorType; - - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); - - if (!SkiaOpenGLHelper::makeCurrent(context, context->gl1x1Surface)) { - RNSkLogger::logToConsole( - "Could not create EGL Surface from native window / surface. Could " - "not set new surface as current surface."); - return nullptr; - } - - // Create texture - auto texture = context->directContext->createBackendTexture( - width, height, colorType, skgpu::Mipmapped::kNo, GrRenderable::kYes); - - if (!texture.isValid()) { - RNSkLogger::logToConsole("couldn't create offscreen texture %dx%d", width, - height); - } - - struct ReleaseContext { - SkiaOpenGLContext *context; - GrBackendTexture texture; - }; - - auto releaseCtx = new ReleaseContext({context, texture}); - - // Create a SkSurface from the GrBackendTexture - return SkSurfaces::WrapBackendTexture( - context->directContext.get(), texture, kTopLeft_GrSurfaceOrigin, 0, - colorType, nullptr, &props, - [](void *addr) { - auto releaseCtx = reinterpret_cast(addr); - - releaseCtx->context->directContext->deleteBackendTexture( - releaseCtx->texture); - }, - releaseCtx); -} - -sk_sp AndroidSkiaContext::getSurface() { - if (_skSurface == nullptr) { - - // Now we can create a surface - _glSurface = SkiaOpenGLHelper::createWindowedSurface(_window); - if (_glSurface == EGL_NO_SURFACE) { - RNSkLogger::logToConsole( - "Could not create EGL Surface from native window / surface."); - return nullptr; - } - - // Now make this one current - if (!SkiaOpenGLHelper::makeCurrent(_context, _glSurface)) { - RNSkLogger::logToConsole( - "Could not create EGL Surface from native window / surface. Could " - "not set new surface as current surface."); - return nullptr; - } - - // Set up parameters for the render target so that it - // matches the underlying OpenGL context. - GrGLFramebufferInfo fboInfo; - - // We pass 0 as the framebuffer id, since the - // underlying Skia GrGlGpu will read this when wrapping the context in the - // render target and the GrGlGpu object. - fboInfo.fFBOID = 0; - fboInfo.fFormat = 0x8058; // GL_RGBA8 - - GLint stencil; - glGetIntegerv(GL_STENCIL_BITS, &stencil); - - GLint samples; - glGetIntegerv(GL_SAMPLES, &samples); - - auto colorType = kN32_SkColorType; - - auto maxSamples = - _context->directContext->maxSurfaceSampleCountForColorType(colorType); - - if (samples > maxSamples) { - samples = maxSamples; - } - - auto renderTarget = GrBackendRenderTargets::MakeGL(_width, _height, samples, - stencil, fboInfo); - - SkSurfaceProps props(0, kUnknown_SkPixelGeometry); - - struct ReleaseContext { - EGLSurface glSurface; - }; - - auto releaseCtx = new ReleaseContext({_glSurface}); - - // Create surface object - _skSurface = SkSurfaces::WrapBackendRenderTarget( - _context->directContext.get(), renderTarget, - kBottomLeft_GrSurfaceOrigin, colorType, nullptr, &props, - [](void *addr) { - auto releaseCtx = reinterpret_cast(addr); - SkiaOpenGLHelper::destroySurface(releaseCtx->glSurface); - delete releaseCtx; - }, - reinterpret_cast(releaseCtx)); - } - return _skSurface; -} - -} // namespace RNSkia diff --git a/packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h b/packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h deleted file mode 100644 index 3e2e343fa1..0000000000 --- a/packages/skia/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -#include "RNSkLog.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "SkiaOpenGLHelper.h" -#include "WindowContext.h" - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdocumentation" - -#include "include/core/SkCanvas.h" -#include "include/core/SkColorSpace.h" -#include "include/core/SkSurface.h" -#include "include/gpu/ganesh/GrBackendSurface.h" -#include "include/gpu/ganesh/GrDirectContext.h" -#include "include/gpu/ganesh/SkSurfaceGanesh.h" -#include "include/gpu/ganesh/gl/GrGLInterface.h" - -#pragma clang diagnostic pop - -namespace RNSkia { - -class AndroidSkiaContext : public WindowContext { -public: - AndroidSkiaContext(SkiaOpenGLContext *context, ANativeWindow *window, - int width, int height) - : _context(context), _window(window), _width(width), _height(height) {} - - ~AndroidSkiaContext() { ANativeWindow_release(_window); } - - sk_sp getSurface() override; - - void present() override { - if (!SkiaOpenGLHelper::makeCurrent(_context, _glSurface)) { - RNSkLogger::logToConsole( - "Could not create EGL Surface from native window / surface. Could " - "not set new surface as current surface."); - return; - } - // Flush and submit the direct context - _context->directContext->flushAndSubmit(); - - // Swap buffers - SkiaOpenGLHelper::swapBuffers(_context, _glSurface); - } - - void resize(int width, int height) override { - _skSurface = nullptr; - _width = width; - _height = height; - } - - int getWidth() override { return _width; }; - - int getHeight() override { return _height; }; - -private: - ANativeWindow *_window; - sk_sp _skSurface = nullptr; - EGLSurface _glSurface = EGL_NO_SURFACE; - SkiaOpenGLContext *_context; - int _width = 0; - int _height = 0; -}; - -class SkiaOpenGLSurfaceFactory { -public: - /** - * Creates a new Skia surface that is backed by a texture. - * @param width Width of surface - * @param height Height of surface - * @return An SkSurface backed by a texture. - */ - static sk_sp makeOffscreenSurface(SkiaOpenGLContext *context, - int width, int height); - - static sk_sp - makeImageFromHardwareBuffer(SkiaOpenGLContext *context, void *buffer, - bool requireKnownFormat = false); - - static std::unique_ptr - makeContext(SkiaOpenGLContext *context, ANativeWindow *surface, int width, - int height) { - return std::make_unique(context, surface, width, - height); - } -}; - -} // namespace RNSkia diff --git a/packages/skia/android/cpp/rnskia-android/opengl/Context.h b/packages/skia/android/cpp/rnskia-android/opengl/Context.h new file mode 100644 index 0000000000..3c73096249 --- /dev/null +++ b/packages/skia/android/cpp/rnskia-android/opengl/Context.h @@ -0,0 +1,77 @@ +#pragma once + +#include "opengl/Error.h" +#include "opengl/Surface.h" + +namespace RNSkia { + +class Surface; +class Display; + +class Context { +public: + ~Context() { + if (_context != EGL_NO_CONTEXT) { + if (eglDestroyContext(_display, _context) != EGL_TRUE) { + LOG_EGL_ERROR; + } + } + } + + bool isValid() const { return _context != EGL_NO_CONTEXT; } + + const EGLContext &getHandle() const { return _context; } + + bool makeCurrent(const Surface &surface) const { + if (_context == EGL_NO_CONTEXT) { + return false; + } + const auto result = + eglMakeCurrentIfNecessary(_display, surface.getHandle(), + surface.getHandle(), _context) == EGL_TRUE; + if (!result) { + LOG_EGL_ERROR; + } + return result; + } + + bool clearCurrent() const { + const auto result = + eglMakeCurrentIfNecessary(_display, EGL_NO_SURFACE, EGL_NO_SURFACE, + EGL_NO_CONTEXT) == EGL_TRUE; + if (!result) { + LOG_EGL_ERROR; + } + return result; + } + + bool isCurrent() const { return eglGetCurrentContext() == _context; } + +private: + friend class Display; + + EGLDisplay _display = EGL_NO_DISPLAY; + EGLContext _context = EGL_NO_CONTEXT; + + static EGLBoolean eglMakeCurrentIfNecessary(EGLDisplay display, + EGLSurface draw, EGLSurface read, + EGLContext context) { + if (display != eglGetCurrentDisplay() || + draw != eglGetCurrentSurface(EGL_DRAW) || + read != eglGetCurrentSurface(EGL_READ) || + context != eglGetCurrentContext()) { + return eglMakeCurrent(display, draw, read, context); + } + + return EGL_TRUE; + } + + Context(EGLDisplay display, EGLContext context) + : _display(display), _context(context) {} + + Context(const Context &) = delete; + + Context &operator=(const Context &) = delete; +}; + +} // namespace RNSkia \ No newline at end of file diff --git a/packages/skia/android/cpp/rnskia-android/opengl/Display.h b/packages/skia/android/cpp/rnskia-android/opengl/Display.h new file mode 100644 index 0000000000..06a3c55920 --- /dev/null +++ b/packages/skia/android/cpp/rnskia-android/opengl/Display.h @@ -0,0 +1,117 @@ +#pragma once + +#include + +#include "EGL/egl.h" +#include "GLES2/gl2.h" + +#include "opengl/Context.h" +#include "opengl/Error.h" + +namespace RNSkia { + +class Context; +class Surface; + +class Display { +public: + Display() { + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + + if (eglInitialize(display, nullptr, nullptr) != EGL_TRUE) { + LOG_EGL_ERROR; + return; + } + _display = display; + } + + ~Display() { + if (_display != EGL_NO_DISPLAY) { + if (eglTerminate(_display) != EGL_TRUE) { + LOG_EGL_ERROR; + } + } + } + + bool isValid() const { return _display != EGL_NO_DISPLAY; } + + EGLConfig chooseConfig() const { + + EGLint att[] = {EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_ALPHA_SIZE, + 8, + EGL_BLUE_SIZE, + 8, + EGL_GREEN_SIZE, + 8, + EGL_RED_SIZE, + 8, + EGL_DEPTH_SIZE, + 0, + EGL_STENCIL_SIZE, + 0, + EGL_SAMPLE_BUFFERS, + 0, + EGL_NONE}; + + EGLint numConfigs; + EGLConfig glConfig = 0; + if (eglChooseConfig(_display, att, &glConfig, 1, &numConfigs) != EGL_TRUE || + numConfigs == 0) { + LOG_EGL_ERROR; + return 0; + } + + return glConfig; + } + + std::unique_ptr makeContext(const EGLConfig &config, + const Context *share_context) { + EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + auto context = eglCreateContext( + _display, config, + share_context != nullptr ? share_context->getHandle() : nullptr, + contextAttribs); + + if (context == EGL_NO_CONTEXT) { + LOG_EGL_ERROR; + return nullptr; + } + return std::unique_ptr(new Context(_display, context)); + } + + std::unique_ptr makeWindowSurface(const EGLConfig &config, + EGLNativeWindowType window) { + const EGLint attribs[] = {EGL_NONE}; + auto surface = eglCreateWindowSurface(_display, config, window, attribs); + if (surface == EGL_NO_SURFACE) { + LOG_EGL_ERROR; + return nullptr; + } + return std::unique_ptr(new Surface(_display, surface)); + } + + std::unique_ptr makePixelBufferSurface(const EGLConfig &config, + size_t width, size_t height) { + const EGLint attribs[] = {EGL_WIDTH, static_cast(width), EGL_HEIGHT, + static_cast(height), EGL_NONE}; + auto surface = eglCreatePbufferSurface(_display, config, attribs); + if (surface == EGL_NO_SURFACE) { + LOG_EGL_ERROR; + return nullptr; + } + return std::unique_ptr(new Surface(_display, surface)); + } + + const EGLDisplay &getHandle() const { return _display; } + +private: + EGLDisplay _display = EGL_NO_DISPLAY; + + Display(const Display &) = delete; + + Display &operator=(const Display &) = delete; +}; + +} // namespace RNSkia diff --git a/packages/skia/android/cpp/rnskia-android/opengl/Error.cpp b/packages/skia/android/cpp/rnskia-android/opengl/Error.cpp new file mode 100644 index 0000000000..3ceff91b71 --- /dev/null +++ b/packages/skia/android/cpp/rnskia-android/opengl/Error.cpp @@ -0,0 +1,9 @@ +#include "opengl/Error.h" + +#include "RNSkLog.h" + +void LogEGLError(const char *file, int line) { + const auto error = eglGetError(); + RNSkia::RNSkLogger::logToConsole("EGL Error: %s (%d) in %s:%d", + EGLErrorToString(error), error, file, line); +} \ No newline at end of file diff --git a/packages/skia/android/cpp/rnskia-android/opengl/Error.h b/packages/skia/android/cpp/rnskia-android/opengl/Error.h new file mode 100644 index 0000000000..2e08851e95 --- /dev/null +++ b/packages/skia/android/cpp/rnskia-android/opengl/Error.h @@ -0,0 +1,44 @@ +#pragma once + +#include "EGL/egl.h" +#include "GLES2/gl2.h" + +#define LOG_EGL_ERROR LogEGLError(__FILE__, __LINE__); + +static const char *EGLErrorToString(EGLint error) { + switch (error) { + case EGL_SUCCESS: + return "Success"; + case EGL_NOT_INITIALIZED: + return "Not Initialized"; + case EGL_BAD_ACCESS: + return "Bad Access"; + case EGL_BAD_ALLOC: + return "Bad Alloc"; + case EGL_BAD_ATTRIBUTE: + return "Bad Attribute"; + case EGL_BAD_CONTEXT: + return "Bad Context"; + case EGL_BAD_CONFIG: + return "Bad Config"; + case EGL_BAD_CURRENT_SURFACE: + return "Bad Current Surface"; + case EGL_BAD_DISPLAY: + return "Bad Display"; + case EGL_BAD_SURFACE: + return "Bad Surface"; + case EGL_BAD_MATCH: + return "Bad Match"; + case EGL_BAD_PARAMETER: + return "Bad Parameter"; + case EGL_BAD_NATIVE_PIXMAP: + return "Bad Native Pixmap"; + case EGL_BAD_NATIVE_WINDOW: + return "Bad Native Window"; + case EGL_CONTEXT_LOST: + return "Context Lost"; + } + return "Unknown"; +} + +void LogEGLError(const char *file, int line); \ No newline at end of file diff --git a/packages/skia/android/cpp/rnskia-android/opengl/Surface.h b/packages/skia/android/cpp/rnskia-android/opengl/Surface.h new file mode 100644 index 0000000000..38fb5157b6 --- /dev/null +++ b/packages/skia/android/cpp/rnskia-android/opengl/Surface.h @@ -0,0 +1,43 @@ +#pragma once + +#include "opengl/Error.h" + +namespace RNSkia { + +class Surface { +public: + ~Surface() { + if (_surface != EGL_NO_SURFACE) { + if (eglDestroySurface(_display, _surface) != EGL_TRUE) { + LOG_EGL_ERROR; + } + } + } + + bool isValid() { return _surface != EGL_NO_SURFACE; } + + const EGLSurface &getHandle() const { return _surface; } + + bool Present() const { + const auto result = eglSwapBuffers(_display, _surface) == EGL_TRUE; + if (!result) { + LOG_EGL_ERROR; + } + return result; + } + +private: + friend class Display; + + EGLDisplay _display = EGL_NO_DISPLAY; + EGLSurface _surface = EGL_NO_SURFACE; + + Surface(EGLDisplay display, EGLSurface surface) + : _display(display), _surface(surface) {} + + Surface(const Surface &) = delete; + + Surface &operator=(const Surface &) = delete; +}; + +} // Namespace RNSkia \ No newline at end of file diff --git a/packages/skia/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm b/packages/skia/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm index 6e2b6fa064..67a4345518 100644 --- a/packages/skia/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm +++ b/packages/skia/ios/RNSkia-iOS/SkiaCVPixelBufferUtils.mm @@ -23,16 +23,12 @@ #include #if TARGET_RT_BIG_ENDIAN #define FourCC2Str(fourcc) \ - (const char[]) { \ - *((char *)&fourcc), *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 2), \ - *(((char *)&fourcc) + 3), 0 \ - } + (const char[]){*((char *)&fourcc), *(((char *)&fourcc) + 1), \ + *(((char *)&fourcc) + 2), *(((char *)&fourcc) + 3), 0} #else #define FourCC2Str(fourcc) \ - (const char[]) { \ - *(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \ - *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0 \ - } + (const char[]){*(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \ + *(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0} #endif // pragma MARK: TextureHolder diff --git a/packages/skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.h b/packages/skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.h index 7107066562..53d7010fb3 100644 --- a/packages/skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.h +++ b/packages/skia/ios/RNSkia-iOS/SkiaMetalSurfaceFactory.h @@ -109,9 +109,13 @@ class IOSSkiaContext : public RNSkia::WindowContext { void resize(int width, int height) override { _skSurface = nullptr; } - int getWidth() override { return _layer.frame.size.width * _layer.contentsScale; }; + int getWidth() override { + return _layer.frame.size.width * _layer.contentsScale; + }; - int getHeight() override { return _layer.frame.size.height * _layer.contentsScale; }; + int getHeight() override { + return _layer.frame.size.height * _layer.contentsScale; + }; private: SkiaMetalContext *_context;