Skip to content

Commit

Permalink
fix(🤖): Fix serious Android threading issue (#2749)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wcandillon authored Nov 19, 2024
1 parent 53a8db5 commit 6fbaeab
Show file tree
Hide file tree
Showing 15 changed files with 571 additions and 621 deletions.
3 changes: 2 additions & 1 deletion packages/skia/android/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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"
Expand Down
133 changes: 122 additions & 11 deletions packages/skia/android/cpp/rnskia-android/OpenGLContext.h
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -16,25 +25,127 @@ class OpenGLContext {
}

sk_sp<SkSurface> 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<ReleaseContext *>(addr);
releaseCtx->directContext->deleteBackendTexture(releaseCtx->texture);
delete releaseCtx;
},
releaseCtx);
}

sk_sp<SkImage> MakeImageFromBuffer(void *buffer) {
return RNSkia::SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(
&_context, buffer);
sk_sp<SkImage> MakeImageFromBuffer(void *buffer,
bool requireKnownFormat = false) {
#if __ANDROID_API__ >= 26
const AHardwareBuffer *hardwareBuffer =
static_cast<AHardwareBuffer *>(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<AHardwareBuffer *>(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<SkImage> 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<RNSkia::WindowContext> MakeWindow(ANativeWindow *window,
int width, int height) {
return RNSkia::SkiaOpenGLSurfaceFactory::makeContext(&_context, window,
width, height);
return std::make_unique<RNSkia::OpenGLWindowContext>(
_config, _display.get(), _ctx.get(), _directContext.get(), window,
width, height);
}

private:
RNSkia::SkiaOpenGLContext _context;
EGLConfig _config;
std::unique_ptr<RNSkia::Display> _display;
std::unique_ptr<RNSkia::Context> _ctx;
std::unique_ptr<RNSkia::Surface> _surface;
sk_sp<GrDirectContext> _directContext;

OpenGLContext() {
RNSkia::SkiaOpenGLHelper::createSkiaDirectContextIfNecessary(&_context);
_display = std::make_unique<RNSkia::Display>();
_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");
}
}
};
73 changes: 73 additions & 0 deletions packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.cpp
Original file line number Diff line number Diff line change
@@ -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<SkSurface> OpenGLWindowContext::getSurface() {
if (_skSurface == nullptr) {

struct ReleaseContext {
std::unique_ptr<Surface> 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<ReleaseContext *>(addr);
delete releaseCtx;
},
reinterpret_cast<void *>(releaseCtx));
}
return _skSurface;
}

} // namespace RNSkia
74 changes: 74 additions & 0 deletions packages/skia/android/cpp/rnskia-android/OpenGLWindowContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#pragma once

#include "RNSkLog.h"

#include <fbjni/fbjni.h>
#include <jni.h>

#include <android/native_window_jni.h>
#include <android/surface_texture.h>
#include <android/surface_texture_jni.h>
#include <condition_variable>
#include <memory>
#include <thread>
#include <unordered_map>

#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<SkSurface> 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> _skSurface = nullptr;
EGLConfig _config;
Context *_context;
Surface* _surface;
Display *_display;
GrDirectContext *_directContext;
int _width = 0;
int _height = 0;
};

} // namespace RNSkia
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ bool RNSkOpenGLCanvasProvider::renderToCanvas(
return false;
}
}

return false;
}

Expand Down
Loading

0 comments on commit 6fbaeab

Please sign in to comment.