Skip to content

Commit

Permalink
fix(🤖): fix minor threading issue on Android (#2725)
Browse files Browse the repository at this point in the history
The main goal of this PR is to refactor the platforms-specific Skia code in a way that will make the Graphite migration smoother.
It also fixes a minor issue on Android where one onscreen contextes could created in many threads instead of only once. This issue is most likely not affecting any users.
  • Loading branch information
wcandillon authored Nov 5, 2024
1 parent ce475ac commit 569acc9
Show file tree
Hide file tree
Showing 23 changed files with 318 additions and 453 deletions.
17 changes: 10 additions & 7 deletions apps/paper/src/Examples/WebGPU/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@ import {
Skia,
} from "@shopify/react-native-skia";
import { Dimensions, PixelRatio } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import { runOnUI, useSharedValue } from "react-native-reanimated";
import { useCanvasEffect } from "react-native-wgpu";

const pd = PixelRatio.get();

export const useSkiaContext = () => {
const context = useSharedValue<SkiaContext | null>(null);
const ref = useCanvasEffect(() => {
const nativeSurface = ref.current!.getNativeSurface();
context.value = Skia.Context(
nativeSurface.surface,
nativeSurface.width * pd,
nativeSurface.height * pd
);
const { surface, width, height } = nativeSurface;
runOnUI(() => {
context.value = Skia.Context(surface, width * pd, height * pd);
})();
});
return {
context,
ref,
};
};

const pd = PixelRatio.get();
const { width, height } = Dimensions.get("window");
const center = { x: width / 2, y: height / 2 };
const R = width / 4;
Expand All @@ -44,6 +44,9 @@ p2.setColor(Skia.Color(c2));
export const drawBreatheDemo = (ctx: SkiaContext, progress: number) => {
"worklet";
const surface = ctx.getSurface();
if (surface === null) {
throw new Error("No surface available");
}
const canvas = surface.getCanvas();
canvas.clear(Skia.Color("rgb(36, 43, 56)"));
canvas.save();
Expand Down
40 changes: 40 additions & 0 deletions packages/skia/android/cpp/rnskia-android/OpenGLContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include "SkiaOpenGLSurfaceFactory.h"
#include "WindowContext.h"

#include "include/core/SkSurface.h"

class OpenGLContext {
public:
OpenGLContext(const OpenGLContext &) = delete;
OpenGLContext &operator=(const OpenGLContext &) = delete;

static OpenGLContext &getInstance() {
static thread_local OpenGLContext instance;
return instance;
}

sk_sp<SkSurface> MakeOffscreen(int width, int height) {
return RNSkia::SkiaOpenGLSurfaceFactory::makeOffscreenSurface(
&_context, width, height);
}

sk_sp<SkImage> MakeImageFromBuffer(void *buffer) {
return RNSkia::SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(
&_context, buffer);
}

std::unique_ptr<RNSkia::WindowContext> MakeWindow(ANativeWindow *window,
int width, int height) {
return RNSkia::SkiaOpenGLSurfaceFactory::makeContext(&_context, window,
width, height);
}

private:
RNSkia::SkiaOpenGLContext _context;

OpenGLContext() {
RNSkia::SkiaOpenGLHelper::createSkiaDirectContextIfNecessary(&_context);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

#include "AHardwareBufferUtils.h"
#include "JniPlatformContext.h"
#include "OpenGLContext.h"
#include "RNSkAndroidVideo.h"
#include "RNSkPlatformContext.h"
#include "SkiaOpenGLSurfaceFactory.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"
Expand Down Expand Up @@ -51,17 +51,17 @@ class RNSkAndroidPlatformContext : public RNSkPlatformContext {
}

sk_sp<SkSurface> makeOffscreenSurface(int width, int height) override {
return SkiaOpenGLSurfaceFactory::makeOffscreenSurface(width, height);
return OpenGLContext::getInstance().MakeOffscreen(width, height);
}

std::shared_ptr<SkiaContext>
std::shared_ptr<WindowContext>
makeContextFromNativeSurface(void *surface, int width, int height) override {
return SkiaOpenGLSurfaceFactory::makeContext(
return OpenGLContext::getInstance().MakeWindow(
reinterpret_cast<ANativeWindow *>(surface), width, height);
}

sk_sp<SkImage> makeImageFromNativeBuffer(void *buffer) override {
return SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(buffer);
return OpenGLContext::getInstance().MakeImageFromBuffer(buffer);
}

std::shared_ptr<RNSkVideo> createVideo(const std::string &url) override {
Expand Down
4 changes: 2 additions & 2 deletions packages/skia/android/cpp/rnskia-android/RNSkAndroidVideo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@

#pragma clang diagnostic pop

#include "OpenGLContext.h"
#include "RNSkAndroidVideo.h"
#include "SkiaOpenGLSurfaceFactory.h"

namespace RNSkia {

Expand Down Expand Up @@ -52,7 +52,7 @@ sk_sp<SkImage> RNSkAndroidVideo::nextImage(double *timeStamp) {
// Convert jobject to AHardwareBuffer
AHardwareBuffer *buffer =
AHardwareBuffer_fromHardwareBuffer(env, jHardwareBuffer);
return SkiaOpenGLSurfaceFactory::makeImageFromHardwareBuffer(buffer);
return OpenGLContext::getInstance().MakeImageFromBuffer(buffer);
#else
return nullptr;
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include <memory>

#include "OpenGLContext.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"

Expand All @@ -20,33 +22,41 @@ RNSkOpenGLCanvasProvider::RNSkOpenGLCanvasProvider(
RNSkOpenGLCanvasProvider::~RNSkOpenGLCanvasProvider() {}

float RNSkOpenGLCanvasProvider::getScaledWidth() {
return _surfaceHolder ? _surfaceHolder->getWidth() : 0;
if (_surfaceHolder) {
return static_cast<float>(_surfaceHolder->getWidth());
}
return 0;
}

float RNSkOpenGLCanvasProvider::getScaledHeight() {
return _surfaceHolder ? _surfaceHolder->getHeight() : 0;
if (_surfaceHolder) {
return static_cast<float>(_surfaceHolder->getHeight());
}
return 0;
}

bool RNSkOpenGLCanvasProvider::renderToCanvas(
const std::function<void(SkCanvas *)> &cb) {

JNIEnv *env = facebook::jni::Environment::current();
if (_surfaceHolder != nullptr && cb != nullptr) {
// Get the surface
auto surface = _surfaceHolder->getSurface();
if (surface) {

// Ensure we are ready to render
if (!_surfaceHolder->makeCurrent()) {
return false;
}
_surfaceHolder->updateTexImage();
env->CallVoidMethod(_jSurfaceTexture, _updateTexImageMethod);

// Check for exceptions
if (env->ExceptionCheck()) {
RNSkLogger::logToConsole("updateAndRelease() failed. The exception above "
"can safely be ignored");
env->ExceptionClear();
}
if (surface) {
// Draw into canvas using callback
cb(surface->getCanvas());

// Swap buffers and show on screen
return _surfaceHolder->present();
_surfaceHolder->present();

return true;
} else {
// the render context did not provide a surface
return false;
Expand All @@ -56,11 +66,31 @@ bool RNSkOpenGLCanvasProvider::renderToCanvas(
return false;
}

void RNSkOpenGLCanvasProvider::surfaceAvailable(jobject surface, int width,
int height) {
void RNSkOpenGLCanvasProvider::surfaceAvailable(jobject jSurfaceTexture,
int width, int height) {
// Create renderer!
JNIEnv *env = facebook::jni::Environment::current();

_jSurfaceTexture = env->NewGlobalRef(jSurfaceTexture);
jclass surfaceClass = env->FindClass("android/view/Surface");
jmethodID surfaceConstructor = env->GetMethodID(
surfaceClass, "<init>", "(Landroid/graphics/SurfaceTexture;)V");
// Create a new Surface instance
jobject jSurface =
env->NewObject(surfaceClass, surfaceConstructor, jSurfaceTexture);

jclass surfaceTextureClass = env->GetObjectClass(_jSurfaceTexture);
_updateTexImageMethod =
env->GetMethodID(surfaceTextureClass, "updateTexImage", "()V");

// Acquire the native window from the Surface
auto window = ANativeWindow_fromSurface(env, jSurface);
// Clean up local references
env->DeleteLocalRef(jSurface);
env->DeleteLocalRef(surfaceClass);
env->DeleteLocalRef(surfaceTextureClass);
_surfaceHolder =
SkiaOpenGLSurfaceFactory::makeWindowedSurface(surface, width, height);
OpenGLContext::getInstance().MakeWindow(window, width, height);

// Post redraw request to ensure we paint in the next draw cycle.
_requestRedraw();
Expand All @@ -69,6 +99,11 @@ void RNSkOpenGLCanvasProvider::surfaceDestroyed() {
// destroy the renderer (a unique pointer so the dtor will be called
// immediately.)
_surfaceHolder = nullptr;
if (_jSurfaceTexture) {
JNIEnv *env = facebook::jni::Environment::current();
env->DeleteGlobalRef(_jSurfaceTexture);
_jSurfaceTexture = nullptr;
}
}

void RNSkOpenGLCanvasProvider::surfaceSizeChanged(int width, int height) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
#include <memory>

#include "RNSkView.h"
#include "SkiaOpenGLSurfaceFactory.h"
#include "WindowContext.h"

#include <android/native_window.h>

namespace RNSkia {
Expand Down Expand Up @@ -33,7 +34,9 @@ class RNSkOpenGLCanvasProvider
void surfaceSizeChanged(int width, int height);

private:
std::unique_ptr<WindowSurfaceHolder> _surfaceHolder = nullptr;
std::unique_ptr<WindowContext> _surfaceHolder = nullptr;
std::shared_ptr<RNSkPlatformContext> _platformContext;
jobject _jSurfaceTexture = nullptr;
jmethodID _updateTexImageMethod = nullptr;
};
} // namespace RNSkia
Loading

0 comments on commit 569acc9

Please sign in to comment.