From 95d98b135e117c0c5d14a172559ff1d23888b8ca Mon Sep 17 00:00:00 2001 From: William Candillon Date: Fri, 24 Nov 2023 08:53:43 +0100 Subject: [PATCH] Update rendering logic on Android (#1980) --- example/android/build.gradle | 2 +- .../cpp/rnskia-android/RNSkAndroidView.h | 3 ++ .../RNSkOpenGLCanvasProvider.cpp | 1 + .../cpp/rnskia-android/SkiaOpenGLHelper.h | 1 - .../rnskia-android/SkiaOpenGLSurfaceFactory.h | 52 ++++++++++++++++--- .../reactnative/skia/PlatformContext.java | 6 +-- .../reactnative/skia/SkiaBaseView.java | 22 +++----- package/cpp/utils/RNSkLog.h | 6 +-- 8 files changed, 65 insertions(+), 28 deletions(-) diff --git a/example/android/build.gradle b/example/android/build.gradle index 67d887b030..c8ed00a62a 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -15,7 +15,7 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:7.3.1") + classpath('com.android.tools.build:gradle:7.3.1') classpath("com.facebook.react:react-native-gradle-plugin") } } diff --git a/package/android/cpp/rnskia-android/RNSkAndroidView.h b/package/android/cpp/rnskia-android/RNSkAndroidView.h index 3ee87baeca..bfb41ccbc5 100644 --- a/package/android/cpp/rnskia-android/RNSkAndroidView.h +++ b/package/android/cpp/rnskia-android/RNSkAndroidView.h @@ -56,6 +56,9 @@ class RNSkAndroidView : public T, public RNSkBaseAndroidView { void surfaceSizeChanged(int width, int height) override { std::static_pointer_cast(T::getCanvasProvider()) ->surfaceSizeChanged(width, height); + // This is only need for the first time to frame, this renderImmediate call + // will invoke updateTexImage for the previous frame + RNSkView::renderImmediate(); } float getPixelDensity() override { diff --git a/package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp b/package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp index c5e7ae9cd0..e3d92aa3ea 100644 --- a/package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +++ b/package/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp @@ -39,6 +39,7 @@ bool RNSkOpenGLCanvasProvider::renderToCanvas( if (!_surfaceHolder->makeCurrent()) { return false; } + _surfaceHolder->updateTexImage(); // Draw into canvas using callback cb(surface->getCanvas()); diff --git a/package/android/cpp/rnskia-android/SkiaOpenGLHelper.h b/package/android/cpp/rnskia-android/SkiaOpenGLHelper.h index c46e51ec74..9d89bf10ab 100644 --- a/package/android/cpp/rnskia-android/SkiaOpenGLHelper.h +++ b/package/android/cpp/rnskia-android/SkiaOpenGLHelper.h @@ -187,7 +187,6 @@ class SkiaOpenGLHelper { RNSkLogger::logToConsole("eglMakeCurrent failed: %d\n", eglGetError()); return false; } - return true; } return true; } diff --git a/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h b/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h index 1568b97ab4..ac8a28559e 100644 --- a/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h +++ b/package/android/cpp/rnskia-android/SkiaOpenGLSurfaceFactory.h @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include #include @@ -42,12 +44,34 @@ class ThreadContextHolder { */ class WindowSurfaceHolder { public: - WindowSurfaceHolder(jobject surface, int width, int height) - : _width(width), _height(height), - _window(ANativeWindow_fromSurface(facebook::jni::Environment::current(), - surface)) {} + WindowSurfaceHolder(jobject jSurfaceTexture, int width, int height) + : _width(width), _height(height) { + JNIEnv *env = facebook::jni::Environment::current(); + _jSurfaceTexture = env->NewGlobalRef(jSurfaceTexture); + jclass surfaceClass = env->FindClass("android/view/Surface"); + jmethodID surfaceConstructor = env->GetMethodID( + surfaceClass, "", "(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 + _window = ANativeWindow_fromSurface(env, jSurface); + // Clean up local references + env->DeleteLocalRef(jSurface); + env->DeleteLocalRef(surfaceClass); + env->DeleteLocalRef(surfaceTextureClass); + } - ~WindowSurfaceHolder() { ANativeWindow_release(_window); } + ~WindowSurfaceHolder() { + JNIEnv *env = facebook::jni::Environment::current(); + env->DeleteGlobalRef(_jSurfaceTexture); + ANativeWindow_release(_window); + } int getWidth() { return _width; } int getHeight() { return _height; } @@ -57,6 +81,20 @@ class WindowSurfaceHolder { */ sk_sp getSurface(); + void updateTexImage() { + JNIEnv *env = facebook::jni::Environment::current(); + + // Call updateTexImage on the SurfaceTexture object + env->CallVoidMethod(_jSurfaceTexture, _updateTexImageMethod); + + // Check for exceptions + if (env->ExceptionCheck()) { + RNSkLogger::logToConsole( + "updateTexImage() failed. The exception above can safely be ignored"); + env->ExceptionClear(); + } + } + /** * Resizes the surface * @param width @@ -92,9 +130,11 @@ class WindowSurfaceHolder { } private: - ANativeWindow *_window = nullptr; + ANativeWindow *_window; sk_sp _skSurface = nullptr; + jobject _jSurfaceTexture = nullptr; EGLSurface _glSurface = EGL_NO_SURFACE; + jmethodID _updateTexImageMethod = nullptr; int _width = 0; int _height = 0; }; diff --git a/package/android/src/main/java/com/shopify/reactnative/skia/PlatformContext.java b/package/android/src/main/java/com/shopify/reactnative/skia/PlatformContext.java index 00ebf26bf6..6d62047a21 100644 --- a/package/android/src/main/java/com/shopify/reactnative/skia/PlatformContext.java +++ b/package/android/src/main/java/com/shopify/reactnative/skia/PlatformContext.java @@ -52,13 +52,13 @@ private void postFrameLoop() { Choreographer.FrameCallback frameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { + if (_drawLoopActive) { + Choreographer.getInstance().postFrameCallback(this); + } if (_isPaused) { return; } notifyDrawLoop(); - if (_drawLoopActive) { - postFrameLoop(); - } } }; Choreographer.getInstance().postFrameCallback(frameCallback); diff --git a/package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java b/package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java index c6dc1eed60..572aa890e7 100644 --- a/package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +++ b/package/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java @@ -4,16 +4,11 @@ import android.graphics.SurfaceTexture; import android.util.Log; import android.view.MotionEvent; -import android.view.Surface; import android.view.TextureView; -import com.facebook.jni.annotations.DoNotStrip; import com.facebook.react.views.view.ReactViewGroup; public abstract class SkiaBaseView extends ReactViewGroup implements TextureView.SurfaceTextureListener { - - @DoNotStrip - private Surface mSurface; private TextureView mTexture; private String tag = "SkiaView"; @@ -30,12 +25,8 @@ public SkiaBaseView(Context context, boolean manageTexture) { } public void destroySurface() { - if (mSurface != null) { - Log.i(tag, "destroySurface"); - surfaceDestroyed(); - mSurface.release(); - mSurface = null; - } + Log.i(tag, "destroySurface"); + surfaceDestroyed(); } private void createSurfaceTexture() { @@ -138,8 +129,7 @@ private static int motionActionToType(int action) { @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { Log.i(tag, "onSurfaceTextureAvailable " + width + "/" + height); - mSurface = new Surface(surface); - surfaceAvailable(mSurface, width, height); + surfaceAvailable(surface, width, height); } @Override @@ -153,6 +143,10 @@ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { Log.i(tag, "onSurfaceTextureDestroyed"); // https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(android.graphics.SurfaceTexture) destroySurface(); + // Because of React Native Screens (which dettach the view), we always keep the surface alive. + // If not, Texture view will recreate the texture surface by itself and + // we will lose the fast first time to frame. + // We only delete the surface when the view is dropped (destroySurface invoked by SkiaBaseViewManager); createSurfaceTexture(); return false; } @@ -181,4 +175,4 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { protected abstract void registerView(int nativeId); protected abstract void unregisterView(); -} +} \ No newline at end of file diff --git a/package/cpp/utils/RNSkLog.h b/package/cpp/utils/RNSkLog.h index 901c02ffd6..97a6f8c214 100644 --- a/package/cpp/utils/RNSkLog.h +++ b/package/cpp/utils/RNSkLog.h @@ -7,7 +7,7 @@ #include #include -#ifdef ANDROID +#if defined(ANDROID) || defined(__ANDROID__) #include #endif @@ -26,7 +26,7 @@ class RNSkLogger { * @param message Message to be written out */ static void logToConsole(std::string message) { -#ifdef ANDROID +#if defined(ANDROID) || defined(__ANDROID__) __android_log_write(ANDROID_LOG_INFO, "RNSkia", message.c_str()); #endif @@ -46,7 +46,7 @@ class RNSkLogger { static char buffer[512]; vsnprintf(buffer, sizeof(buffer), fmt, args); -#ifdef ANDROID +#if defined(ANDROID) || defined(__ANDROID__) __android_log_write(ANDROID_LOG_INFO, "RNSkia", buffer); #endif #ifdef TARGET_OS_IPHONE