diff --git a/apps/paper/ios/Podfile.lock b/apps/paper/ios/Podfile.lock index 5342d28e59..9f9542198c 100644 --- a/apps/paper/ios/Podfile.lock +++ b/apps/paper/ios/Podfile.lock @@ -1969,7 +1969,7 @@ SPEC CHECKSUMS: RNScreens: 19719a9c326e925498ac3b2d35c4e50fe87afc06 RNSVG: 5da7a24f31968ec74f0b091e3440080f347e279b SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d - Yoga: 2a45d7e59592db061217551fd3bbe2dd993817ae + Yoga: a1d7895431387402a674fd0d1c04ec85e87909b8 PODFILE CHECKSUM: debc09f5cfcbea21f946ca0be3faa5351e907125 diff --git a/apps/paper/src/Examples/Breathe/Breathe.tsx b/apps/paper/src/Examples/Breathe/Breathe.tsx index 4d00e1ac38..ddda1d3b99 100644 --- a/apps/paper/src/Examples/Breathe/Breathe.tsx +++ b/apps/paper/src/Examples/Breathe/Breathe.tsx @@ -68,7 +68,7 @@ export const Breathe = () => { return ( - + diff --git a/apps/paper/src/Examples/Glassmorphism/Glassmorphism.tsx b/apps/paper/src/Examples/Glassmorphism/Glassmorphism.tsx index 8b61686445..90121c76ca 100644 --- a/apps/paper/src/Examples/Glassmorphism/Glassmorphism.tsx +++ b/apps/paper/src/Examples/Glassmorphism/Glassmorphism.tsx @@ -40,7 +40,7 @@ export const Glassmorphism = () => { ); return ( - + { } const symbols = font.getGlyphIDs("abcdefghijklmnopqrstuvwxyz"); return ( - + diff --git a/apps/paper/src/Examples/Matrix/Symbol.tsx b/apps/paper/src/Examples/Matrix/Symbol.tsx index 3248753ed3..5c038311a0 100644 --- a/apps/paper/src/Examples/Matrix/Symbol.tsx +++ b/apps/paper/src/Examples/Matrix/Symbol.tsx @@ -4,8 +4,8 @@ import { interpolateColors, vec, Glyphs } from "@shopify/react-native-skia"; import type { SharedValue } from "react-native-reanimated"; import { useDerivedValue } from "react-native-reanimated"; -export const COLS = 15; -export const ROWS = 30; +export const COLS = 8; +export const ROWS = 15; const pos = vec(0, 0); interface SymbolProps { diff --git a/apps/paper/src/Examples/Severance/Severance.tsx b/apps/paper/src/Examples/Severance/Severance.tsx index 3f197322ae..a1356a18a3 100644 --- a/apps/paper/src/Examples/Severance/Severance.tsx +++ b/apps/paper/src/Examples/Severance/Severance.tsx @@ -29,7 +29,7 @@ export const Severance = () => { return ( - + diff --git a/apps/paper/src/Examples/Stickers/Stickers.tsx b/apps/paper/src/Examples/Stickers/Stickers.tsx index 9b5f39d89e..b0e72d9921 100644 --- a/apps/paper/src/Examples/Stickers/Stickers.tsx +++ b/apps/paper/src/Examples/Stickers/Stickers.tsx @@ -24,7 +24,7 @@ export const Stickers = () => { } return ( - + diff --git a/apps/paper/src/Examples/Video/Video.tsx b/apps/paper/src/Examples/Video/Video.tsx index e206e205c5..76c9b6ce55 100644 --- a/apps/paper/src/Examples/Video/Video.tsx +++ b/apps/paper/src/Examples/Video/Video.tsx @@ -37,7 +37,7 @@ export const Video = () => { style={{ flex: 1 }} onPress={() => (paused.value = !paused.value)} > - + { /> - + surfaceAvailable(surface, width, height); + virtual void drawBitmap(jobject bitmap, int width, int height) { + _skiaAndroidView->drawBitmap(bitmap, width, height); } - virtual void surfaceSizeChanged(jobject surface, int width, int height) { - _skiaAndroidView->surfaceSizeChanged(surface, width, height); + virtual void surfaceAvailable(jobject surface, int width, int height, + bool opaque) { + _skiaAndroidView->surfaceAvailable(surface, width, height, opaque); + } + + virtual void surfaceSizeChanged(jobject surface, int width, int height, + bool opaque) { + _skiaAndroidView->surfaceSizeChanged(surface, width, height, opaque); } virtual void surfaceDestroyed() { _skiaAndroidView->surfaceDestroyed(); } diff --git a/packages/skia/android/cpp/jni/include/JniSkiaDomView.h b/packages/skia/android/cpp/jni/include/JniSkiaDomView.h index c675e098d8..e735169866 100644 --- a/packages/skia/android/cpp/jni/include/JniSkiaDomView.h +++ b/packages/skia/android/cpp/jni/include/JniSkiaDomView.h @@ -36,6 +36,7 @@ class JniSkiaDomView : public jni::HybridClass, static void registerNatives() { registerHybrid( {makeNativeMethod("initHybrid", JniSkiaDomView::initHybrid), + makeNativeMethod("drawBitmap", JniSkiaDomView::drawBitmap), makeNativeMethod("surfaceAvailable", JniSkiaDomView::surfaceAvailable), makeNativeMethod("surfaceDestroyed", JniSkiaDomView::surfaceDestroyed), makeNativeMethod("surfaceSizeChanged", @@ -46,12 +47,18 @@ class JniSkiaDomView : public jni::HybridClass, } protected: - void surfaceAvailable(jobject surface, int width, int height) override { - JniSkiaBaseView::surfaceAvailable(surface, width, height); + void drawBitmap(jobject bitmap, int width, int height) override { + JniSkiaBaseView::drawBitmap(bitmap, width, height); } - void surfaceSizeChanged(jobject surface, int width, int height) override { - JniSkiaBaseView::surfaceSizeChanged(surface, width, height); + void surfaceAvailable(jobject surface, int width, int height, + bool opaque) override { + JniSkiaBaseView::surfaceAvailable(surface, width, height, opaque); + } + + void surfaceSizeChanged(jobject surface, int width, int height, + bool opaque) override { + JniSkiaBaseView::surfaceSizeChanged(surface, width, height, opaque); } void surfaceDestroyed() override { JniSkiaBaseView::surfaceDestroyed(); } diff --git a/packages/skia/android/cpp/jni/include/JniSkiaPictureView.h b/packages/skia/android/cpp/jni/include/JniSkiaPictureView.h index a55cd324a3..976327b44e 100644 --- a/packages/skia/android/cpp/jni/include/JniSkiaPictureView.h +++ b/packages/skia/android/cpp/jni/include/JniSkiaPictureView.h @@ -35,6 +35,7 @@ class JniSkiaPictureView : public jni::HybridClass, static void registerNatives() { registerHybrid( {makeNativeMethod("initHybrid", JniSkiaPictureView::initHybrid), + makeNativeMethod("drawBitmap", JniSkiaPictureView::drawBitmap), makeNativeMethod("surfaceAvailable", JniSkiaPictureView::surfaceAvailable), makeNativeMethod("surfaceDestroyed", @@ -48,12 +49,18 @@ class JniSkiaPictureView : public jni::HybridClass, } protected: - void surfaceAvailable(jobject surface, int width, int height) override { - JniSkiaBaseView::surfaceAvailable(surface, width, height); + void drawBitmap(jobject bitmap, int width, int height) override { + JniSkiaBaseView::drawBitmap(bitmap, width, height); } - void surfaceSizeChanged(jobject surface, int width, int height) override { - JniSkiaBaseView::surfaceSizeChanged(surface, width, height); + void surfaceAvailable(jobject surface, int width, int height, + bool opaque) override { + JniSkiaBaseView::surfaceAvailable(surface, width, height, opaque); + } + + void surfaceSizeChanged(jobject surface, int width, int height, + bool opaque) override { + JniSkiaBaseView::surfaceSizeChanged(surface, width, height, opaque); } void surfaceDestroyed() override { JniSkiaBaseView::surfaceDestroyed(); } diff --git a/packages/skia/android/cpp/rnskia-android/OpenGLContext.h b/packages/skia/android/cpp/rnskia-android/OpenGLContext.h index 8f524c33ac..fcea1a0833 100644 --- a/packages/skia/android/cpp/rnskia-android/OpenGLContext.h +++ b/packages/skia/android/cpp/rnskia-android/OpenGLContext.h @@ -128,6 +128,7 @@ class OpenGLContext { // TODO: remove width, height std::unique_ptr MakeWindow(ANativeWindow *window, int width, int height) { + return std::make_unique( _directContext.get(), _glDisplay.get(), _glContext.get(), window); } @@ -154,6 +155,13 @@ class OpenGLContext { throw std::runtime_error("GrDirectContexts::MakeGL failed"); } } + + ~OpenGLContext() { + _glDisplay->clearContext(); + _glSurface = nullptr; + _glContext = nullptr; + _directContext = nullptr; + } }; } // namespace RNSkia \ No newline at end of file diff --git a/packages/skia/android/cpp/rnskia-android/RNSkAndroidView.h b/packages/skia/android/cpp/rnskia-android/RNSkAndroidView.h index e209a78bc9..24038c6825 100644 --- a/packages/skia/android/cpp/rnskia-android/RNSkAndroidView.h +++ b/packages/skia/android/cpp/rnskia-android/RNSkAndroidView.h @@ -11,11 +11,15 @@ namespace RNSkia { class RNSkBaseAndroidView { public: - virtual void surfaceAvailable(jobject surface, int width, int height) = 0; + virtual void drawBitmap(jobject bitmap, int width, int height) = 0; + + virtual void surfaceAvailable(jobject surface, int width, int height, + bool opaque) = 0; virtual void surfaceDestroyed() = 0; - virtual void surfaceSizeChanged(jobject surface, int width, int height) = 0; + virtual void surfaceSizeChanged(jobject surface, int width, int height, + bool opaque) = 0; virtual float getPixelDensity() = 0; @@ -34,12 +38,16 @@ class RNSkAndroidView : public T, public RNSkBaseAndroidView { std::make_shared( std::bind(&RNSkia::RNSkView::requestRedraw, this), context)) {} - void surfaceAvailable(jobject surface, int width, int height) override { + virtual void drawBitmap(jobject bitmap, int width, int height) override { std::static_pointer_cast(T::getCanvasProvider()) - ->surfaceAvailable(surface, width, height); + ->drawBitmap(bitmap, width, height); + RNSkView::redraw(); + } - // Try to render directly when the surface has been set so that - // we don't have to wait until the draw loop returns. + void surfaceAvailable(jobject surface, int width, int height, + bool opaque) override { + std::static_pointer_cast(T::getCanvasProvider()) + ->surfaceAvailable(surface, width, height, opaque); RNSkView::redraw(); } @@ -48,9 +56,10 @@ class RNSkAndroidView : public T, public RNSkBaseAndroidView { ->surfaceDestroyed(); } - void surfaceSizeChanged(jobject surface, int width, int height) override { + void surfaceSizeChanged(jobject surface, int width, int height, + bool opaque) override { std::static_pointer_cast(T::getCanvasProvider()) - ->surfaceSizeChanged(surface, width, height); + ->surfaceSizeChanged(surface, width, height, opaque); // This is only need for the first time to frame, this renderImmediate call // will invoke updateTexImage for the previous frame RNSkView::redraw(); diff --git a/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp b/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp index c3c587f70f..42703968ec 100644 --- a/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp +++ b/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.cpp @@ -1,5 +1,6 @@ #include "RNSkOpenGLCanvasProvider.h" +#include #include #include #include @@ -16,6 +17,7 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdocumentation" +#include "include/core/SkBitmap.h" #include "include/core/SkCanvas.h" #include "include/core/SkSurface.h" @@ -28,7 +30,13 @@ RNSkOpenGLCanvasProvider::RNSkOpenGLCanvasProvider( std::shared_ptr platformContext) : RNSkCanvasProvider(requestRedraw), _platformContext(platformContext) {} -RNSkOpenGLCanvasProvider::~RNSkOpenGLCanvasProvider() {} +RNSkOpenGLCanvasProvider::~RNSkOpenGLCanvasProvider() { + if (_jBitmap) { + JNIEnv *env = facebook::jni::Environment::current(); + env->DeleteGlobalRef(_jBitmap); + _jBitmap = nullptr; + } +} float RNSkOpenGLCanvasProvider::getScaledWidth() { if (_surfaceHolder) { @@ -46,17 +54,20 @@ float RNSkOpenGLCanvasProvider::getScaledHeight() { bool RNSkOpenGLCanvasProvider::renderToCanvas( const std::function &cb) { - JNIEnv *env = facebook::jni::Environment::current(); if (_surfaceHolder != nullptr && cb != nullptr) { // Get the surface auto surface = _surfaceHolder->getSurface(); - env->CallVoidMethod(_jSurfaceTexture, _updateTexImageMethod); - - // Check for exceptions - if (env->ExceptionCheck()) { - RNSkLogger::logToConsole("updateAndRelease() failed. The exception above " - "can safely be ignored"); - env->ExceptionClear(); + if (_jSurfaceTexture) { + JNIEnv *env = facebook::jni::Environment::current(); + 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 @@ -69,37 +80,75 @@ bool RNSkOpenGLCanvasProvider::renderToCanvas( // the render context did not provide a surface return false; } + } else if (_jBitmap) { + JNIEnv *env = facebook::jni::Environment::current(); + // Get the Android Bitmap info + AndroidBitmapInfo info; + if (AndroidBitmap_getInfo(env, _jBitmap, &info) < 0) { + return false; + } + + // Lock the pixels of the bitmap + void *pixels = nullptr; + if (AndroidBitmap_lockPixels(env, _jBitmap, &pixels) < 0) { + return false; + } + + // Create an SkBitmap from the Android Bitmap + SkBitmap skBitmap; + SkImageInfo imageInfo = SkImageInfo::Make( + info.width, info.height, kRGBA_8888_SkColorType, kPremul_SkAlphaType); + skBitmap.installPixels(imageInfo, pixels, info.stride); + + // Create a canvas to draw on the SkBitmap + SkCanvas canvas(skBitmap); + cb(&canvas); + + // Unlock the pixels + AndroidBitmap_unlockPixels(env, _jBitmap); + return true; } return false; } +void RNSkOpenGLCanvasProvider::drawBitmap(jobject bitmap, int width, + int height) { + + JNIEnv *env = facebook::jni::Environment::current(); + _jBitmap = env->NewGlobalRef(bitmap); +} + void RNSkOpenGLCanvasProvider::surfaceAvailable(jobject jSurfaceTexture, - int width, int height) { - // If the surface is 0, we can skip it - if (width == 0 && height == 0) { - return; - } + int width, int height, + bool opaque) { + // Release the old surface + _surfaceHolder = nullptr; + // Create renderer! + ANativeWindow *window = nullptr; 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 - auto window = ANativeWindow_fromSurface(env, jSurface); - // Clean up local references - env->DeleteLocalRef(jSurface); - env->DeleteLocalRef(surfaceClass); - env->DeleteLocalRef(surfaceTextureClass); + if (!opaque) { + _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 + auto jSurface = + env->NewObject(surfaceClass, surfaceConstructor, jSurfaceTexture); + window = ANativeWindow_fromSurface(env, jSurface); + + jclass surfaceTextureClass = env->GetObjectClass(_jSurfaceTexture); + _updateTexImageMethod = + env->GetMethodID(surfaceTextureClass, "updateTexImage", "()V"); + + // Acquire the native window from the Surface + // Clean up local references + env->DeleteLocalRef(jSurface); + env->DeleteLocalRef(surfaceClass); + env->DeleteLocalRef(surfaceTextureClass); + } else { + window = ANativeWindow_fromSurface(env, jSurfaceTexture); + } #if defined(SK_GRAPHITE) _surfaceHolder = DawnContext::getInstance().MakeWindow(window, width, height); #else @@ -121,8 +170,8 @@ void RNSkOpenGLCanvasProvider::surfaceDestroyed() { } } -void RNSkOpenGLCanvasProvider::surfaceSizeChanged(jobject jSurfaceTexture, - int width, int height) { +void RNSkOpenGLCanvasProvider::surfaceSizeChanged(jobject jSurface, int width, + int height, bool opaque) { if (width == 0 && height == 0) { // Setting width/height to zero is nothing we need to care about when // it comes to invalidating the surface. @@ -131,7 +180,7 @@ void RNSkOpenGLCanvasProvider::surfaceSizeChanged(jobject jSurfaceTexture, if (_surfaceHolder == nullptr) { _surfaceHolder = nullptr; - surfaceAvailable(jSurfaceTexture, width, height); + surfaceAvailable(jSurface, width, height, opaque); } else { _surfaceHolder->resize(width, height); } diff --git a/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h b/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h index fb73bcaa74..9f280652f6 100644 --- a/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h +++ b/packages/skia/android/cpp/rnskia-android/RNSkOpenGLCanvasProvider.h @@ -27,16 +27,19 @@ class RNSkOpenGLCanvasProvider bool renderToCanvas(const std::function &cb) override; - void surfaceAvailable(jobject surface, int width, int height); + void drawBitmap(jobject bitmap, int width, int height); + + void surfaceAvailable(jobject surface, int width, int height, bool opaque); void surfaceDestroyed(); - void surfaceSizeChanged(jobject jSurface, int width, int height); + void surfaceSizeChanged(jobject jSurface, int width, int height, bool opaque); private: std::unique_ptr _surfaceHolder = nullptr; std::shared_ptr _platformContext; jobject _jSurfaceTexture = nullptr; + jobject _jBitmap = nullptr; jmethodID _updateTexImageMethod = nullptr; }; } // namespace RNSkia diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaAHBView.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaAHBView.java new file mode 100644 index 0000000000..fb82bb0caa --- /dev/null +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaAHBView.java @@ -0,0 +1,113 @@ +package com.shopify.reactnative.skia; + + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.hardware.HardwareBuffer; +import android.media.Image; +import android.media.ImageReader; +import android.os.Build; +import android.util.Log; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +@SuppressLint("ViewConstructor") +@RequiresApi(api = Build.VERSION_CODES.Q) +public class SkiaAHBView extends View implements ImageReader.OnImageAvailableListener { + + private ImageReader mReader; + + private Bitmap mBitmap = null; + + private final Matrix matrix = new Matrix(); + + SkiaViewAPI mApi; + boolean mDebug; + + public SkiaAHBView(Context context, SkiaViewAPI api, boolean debug) { + super(context); + mApi = api; + mDebug = debug; + } + + private ImageReader createReader() { + ImageReader reader = ImageReader.newInstance(getWidth(), getHeight(), PixelFormat.RGBA_8888, 2, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | + HardwareBuffer.USAGE_GPU_COLOR_OUTPUT); + reader.setOnImageAvailableListener(this, null); + return reader; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + int width = getWidth(); + int height = getHeight(); + if (mReader == null) { + mReader = createReader(); + mApi.onSurfaceCreated(mReader.getSurface(), width, height); + } else { + mReader = createReader(); + mApi.onSurfaceChanged(mReader.getSurface(), width, height); + } + } + + @Override + public void onImageAvailable(ImageReader reader) { + try (Image image = reader.acquireLatestImage()) { + if (image != null) { + HardwareBuffer hb = image.getHardwareBuffer(); + if (mDebug) { + textureUpdated(image.getTimestamp()); + } + if (hb != null) { + Bitmap bitmap = Bitmap.wrapHardwareBuffer(hb, null); + if (bitmap != null) { + mBitmap = bitmap; + hb.close(); + invalidate(); + } + } + } + } + } + + @Override + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + if (mBitmap != null) { + float viewWidth = getWidth(); + float viewHeight = getHeight(); + float bitmapWidth = mBitmap.getWidth(); + float bitmapHeight = mBitmap.getHeight(); + + // Calculate the scale factors + float scaleX = viewWidth / bitmapWidth; + float scaleY = viewHeight / bitmapHeight; + + // Reset the matrix and apply scaling + matrix.reset(); + matrix.setScale(scaleX, scaleY); + + canvas.drawBitmap(mBitmap, matrix, null); + } + } + + private long _prevTimestamp = 0; + public void textureUpdated(long ts) { + long frameDuration = (ts - _prevTimestamp)/1000000; + Log.i("SkiaAHBView", "onSurfaceTextureUpdated "+frameDuration+"ms"); + _prevTimestamp = ts; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mApi.onSurfaceDestroyed(); + } +} \ No newline at end of file diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java index 8f88d6b23b..c84e674bad 100644 --- a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseView.java @@ -1,99 +1,101 @@ package com.shopify.reactnative.skia; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; import android.graphics.SurfaceTexture; import android.util.Log; -import android.view.TextureView; +import android.view.Surface; +import android.view.View; -import com.facebook.react.views.view.ReactViewGroup; +import androidx.annotation.NonNull; -public abstract class SkiaBaseView extends ReactViewGroup implements TextureView.SurfaceTextureListener { - private TextureView mTexture; +import com.facebook.react.views.view.ReactViewGroup; - private String tag = "SkiaView"; +public abstract class SkiaBaseView extends ReactViewGroup implements SkiaViewAPI { + private View mView; + private final Matrix mMatrix = new Matrix(); - private boolean isDropped = false; + private final boolean debug = false; + private boolean mSurfaceAvailable = false; + private Bitmap mBitmap = null; + private final String tag = "SkiaView"; public SkiaBaseView(Context context) { super(context); - mTexture = new TextureView(context); - mTexture.setSurfaceTextureListener(this); - mTexture.setOpaque(false); - addView(mTexture); + mView = new SkiaTextureView(context, this, debug); + addView(mView); } - private void createSurfaceTexture() { - // This API Level is >= 26, we created our own SurfaceTexture to have a faster time to first frame - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { - Log.i(tag, "Create SurfaceTexture"); - SurfaceTexture surface = new SurfaceTexture(false); - mTexture.setSurfaceTexture(surface); - this.onSurfaceTextureAvailable(surface, this.getMeasuredWidth(), this.getMeasuredHeight()); + public void setOpaque(boolean value) { + if (value && mView instanceof SkiaTextureView) { + removeView(mView); + mView = new SkiaSurfaceView(getContext(), this, debug); + addView(mView); + } else if (!value && mView instanceof SkiaSurfaceView) { + removeView(mView); + mView = new SkiaSurfaceView(getContext(), this, debug); + addView(mView); } } void dropInstance() { - isDropped = true; unregisterView(); } @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - if (this.getMeasuredWidth() == 0) { - createSurfaceTexture(); + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mView.layout(0, 0, getWidth(), getHeight()); + if (!mSurfaceAvailable && mView instanceof SkiaTextureView) { + mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); + drawBitmap(mBitmap, getWidth(), getHeight()); + setWillNotDraw(false); } } @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - Log.i(tag, "onLayout " + this.getMeasuredWidth() + "/" + this.getMeasuredHeight()); - super.onLayout(changed, left, top, right, bottom); - mTexture.layout(0, 0, this.getMeasuredWidth(), this.getMeasuredHeight()); + public void onSurfaceCreated(Surface surface, int width, int height) {; + surfaceAvailable(surface, width, height, true); + } + + @Override + public void onSurfaceChanged(Surface surface, int width, int height) { + Log.i(tag, "onSurfaceTextureSizeChanged " + width + "/" + height); + surfaceSizeChanged(surface, width, height, true); } @Override - public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - Log.i(tag, "onSurfaceTextureAvailable " + width + "/" + height); - surfaceAvailable(surface, width, height); + public void onSurfaceTextureCreated(SurfaceTexture surface, int width, int height) { + mSurfaceAvailable = true; + surfaceAvailable(surface, width, height, false); } @Override - public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { - if (isDropped) { - return; - } + public void onSurfaceTextureChanged(SurfaceTexture surface, int width, int height) { Log.i(tag, "onSurfaceTextureSizeChanged " + width + "/" + height); - surfaceSizeChanged(surface, width, height); + surfaceSizeChanged(surface, width, height, false); } @Override - public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - Log.i(tag, "onSurfaceTextureDestroyed"); - // https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(android.graphics.SurfaceTexture) - surfaceDestroyed(); - // 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); - if (!isDropped) { - createSurfaceTexture(); + protected void onDraw(@NonNull Canvas canvas) { + super.onDraw(canvas); + if (mBitmap != null) { + canvas.drawBitmap(mBitmap, mMatrix, null); } - return false; } - private long _prevTimestamp = 0; @Override - public void onSurfaceTextureUpdated(SurfaceTexture surface) { - long timestamp = surface.getTimestamp(); - long frameDuration = (timestamp - _prevTimestamp)/1000000; - Log.i(tag, "onSurfaceTextureUpdated "+frameDuration+"ms"); - _prevTimestamp = timestamp; + public void onSurfaceDestroyed() { + surfaceDestroyed(); } - protected abstract void surfaceAvailable(Object surface, int width, int height); + protected abstract void drawBitmap(Object bitmap, int width, int height); + + protected abstract void surfaceAvailable(Object surface, int width, int height, boolean opaque); - protected abstract void surfaceSizeChanged(Object surface, int width, int height); + protected abstract void surfaceSizeChanged(Object surface, int width, int height, boolean opaque); protected abstract void surfaceDestroyed(); diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseViewManager.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseViewManager.java index 4317de650a..38c8afc649 100644 --- a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseViewManager.java +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaBaseViewManager.java @@ -26,6 +26,11 @@ public void setDebug(T view, boolean show) { ((SkiaBaseView)view).setDebugMode(show); } + @ReactProp(name = "opaque") + public void setOpaque(T view, boolean value) { + ((SkiaBaseView)view).setOpaque(value); + } + @Override public void onDropViewInstance(@NonNull ReactViewGroup view) { super.onDropViewInstance(view); diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaDomView.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaDomView.java index eafe21a8f0..2ddd29fbd9 100644 --- a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaDomView.java +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaDomView.java @@ -25,9 +25,11 @@ protected void finalize() throws Throwable { private native HybridData initHybrid(SkiaManager skiaManager); - protected native void surfaceAvailable(Object surface, int width, int height); + protected native void drawBitmap(Object bitmap, int width, int height); - protected native void surfaceSizeChanged(Object surface, int width, int height); + protected native void surfaceAvailable(Object surface, int width, int height, boolean opaque); + + protected native void surfaceSizeChanged(Object surface, int width, int height, boolean opaque); protected native void surfaceDestroyed(); diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java index f4dc2a4695..a15ad43959 100644 --- a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaPictureView.java @@ -24,9 +24,11 @@ protected void finalize() throws Throwable { private native HybridData initHybrid(SkiaManager skiaManager); - protected native void surfaceAvailable(Object surface, int width, int height); + protected native void drawBitmap(Object bitmap, int width, int height); - protected native void surfaceSizeChanged(Object surface, int width, int height); + protected native void surfaceAvailable(Object surface, int width, int height, boolean opaque); + + protected native void surfaceSizeChanged(Object surface, int width, int height, boolean opaque); protected native void surfaceDestroyed(); diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaSurfaceView.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaSurfaceView.java new file mode 100644 index 0000000000..5e28a08c94 --- /dev/null +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaSurfaceView.java @@ -0,0 +1,42 @@ +package com.shopify.reactnative.skia; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import androidx.annotation.NonNull; + +@SuppressLint("ViewConstructor") +public class SkiaSurfaceView extends SurfaceView implements SurfaceHolder.Callback { + + SkiaViewAPI mApi; + boolean mDebug; + + public SkiaSurfaceView(Context context, SkiaViewAPI api, boolean debug) { + super(context); + mApi = api; + mDebug = debug; + getHolder().addCallback(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mApi.onSurfaceDestroyed(); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + mApi.onSurfaceCreated(holder.getSurface(), getWidth(), getHeight()); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) { + mApi.onSurfaceChanged(holder.getSurface(), getWidth(), getHeight()); + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + mApi.onSurfaceDestroyed(); + } +} \ No newline at end of file diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaTextureView.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaTextureView.java new file mode 100644 index 0000000000..8fc52c7c1d --- /dev/null +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaTextureView.java @@ -0,0 +1,56 @@ +package com.shopify.reactnative.skia; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.util.Log; +import android.view.Surface; +import android.view.TextureView; +import androidx.annotation.NonNull; + +@SuppressLint("ViewConstructor") +public class SkiaTextureView extends TextureView implements TextureView.SurfaceTextureListener { + + private String tag = "SkiaTextureView"; + + SkiaViewAPI mApi; + boolean mDebug; + + public SkiaTextureView(Context context, SkiaViewAPI api, boolean debug) { + super(context); + mApi = api; + mDebug = debug; + setOpaque(false); + setSurfaceTextureListener(this); + } + + @Override + public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int width, int height) { + mApi.onSurfaceTextureCreated(surfaceTexture, width, height); + } + + @Override + public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surfaceTexture, int width, int height) { + mApi.onSurfaceTextureCreated(surfaceTexture, width, height); + } + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture) { + Log.i(tag, "onSurfaceTextureDestroyed"); + // https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(android.graphics.SurfaceTexture) + mApi.onSurfaceDestroyed(); + return true; + } + + private long _prevTimestamp = 0; + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { + if (!mDebug) { + return; + } + long timestamp = surface.getTimestamp(); + long frameDuration = (timestamp - _prevTimestamp)/1000000; + Log.i("SkiaTextureView", "onSurfaceTextureUpdated "+frameDuration+"ms"); + _prevTimestamp = timestamp; + } +} \ No newline at end of file diff --git a/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaViewAPI.java b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaViewAPI.java new file mode 100644 index 0000000000..3bb79fe9a4 --- /dev/null +++ b/packages/skia/android/src/main/java/com/shopify/reactnative/skia/SkiaViewAPI.java @@ -0,0 +1,16 @@ +package com.shopify.reactnative.skia; + +import android.graphics.SurfaceTexture; +import android.view.Surface; + +public interface SkiaViewAPI { + void onSurfaceCreated(Surface surface, int width, int height); + + void onSurfaceChanged(Surface surface, int width, int height); + + void onSurfaceTextureCreated(SurfaceTexture surface, int width, int height); + + void onSurfaceTextureChanged(SurfaceTexture surface, int width, int height); + + void onSurfaceDestroyed(); +} diff --git a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerDelegate.java b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerDelegate.java index 929f675b29..13828a5c24 100644 --- a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerDelegate.java +++ b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerDelegate.java @@ -21,6 +21,9 @@ public SkiaDomViewManagerDelegate(U viewManager) { @Override public void setProperty(T view, String propName, @Nullable Object value) { switch (propName) { + case "opaque": + mViewManager.setOpaque(view, value != null && (boolean) value); + break; case "debug": mViewManager.setDebug(view, value != null && (boolean) value); break; diff --git a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerInterface.java b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerInterface.java index b3ceeab3ac..9423a93db7 100644 --- a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerInterface.java +++ b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaDomViewManagerInterface.java @@ -14,4 +14,5 @@ public interface SkiaDomViewManagerInterface { void setDebug(T view, boolean value); + void setOpaque(T view, boolean value); } diff --git a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerDelegate.java b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerDelegate.java index 6690d32833..9d47510bfe 100644 --- a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerDelegate.java +++ b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerDelegate.java @@ -24,8 +24,10 @@ public void setProperty(T view, String propName, @Nullable Object value) { case "mode": mViewManager.setMode(view, value == null ? null : (String) value); break; + case "opaque": + mViewManager.setOpaque(view, value != null && (boolean) value); case "debug": - mViewManager.setDebug(view, value == null ? false : (boolean) value); + mViewManager.setDebug(view, value != null && (boolean) value); break; default: super.setProperty(view, propName, value); diff --git a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerInterface.java b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerInterface.java index 282296dd12..10c5f75ff2 100644 --- a/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerInterface.java +++ b/packages/skia/android/src/paper/java/com/facebook/react/viewmanagers/SkiaPictureViewManagerInterface.java @@ -15,4 +15,5 @@ public interface SkiaPictureViewManagerInterface { void setMode(T view, @Nullable String value); void setDebug(T view, boolean value); + void setOpaque(T view, boolean value); } 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/src/specs/SkiaPictureViewNativeComponent.ts b/packages/skia/src/specs/SkiaPictureViewNativeComponent.ts index 79cfba1dc4..9ba262050e 100644 --- a/packages/skia/src/specs/SkiaPictureViewNativeComponent.ts +++ b/packages/skia/src/specs/SkiaPictureViewNativeComponent.ts @@ -3,6 +3,7 @@ import type { ViewProps } from "react-native"; export interface NativeProps extends ViewProps { debug?: boolean; + opaque?: boolean; } // eslint-disable-next-line import/no-default-export diff --git a/packages/skia/src/views/SkiaDomView.tsx b/packages/skia/src/views/SkiaDomView.tsx index 593ac66f4e..baad65a810 100644 --- a/packages/skia/src/views/SkiaDomView.tsx +++ b/packages/skia/src/views/SkiaDomView.tsx @@ -97,12 +97,13 @@ export class SkiaDomView extends React.Component { } render() { - const { debug = false, ...viewProps } = this.props; + const { debug = false, opaque = false, ...viewProps } = this.props; return ( ); diff --git a/packages/skia/src/views/SkiaPictureView.tsx b/packages/skia/src/views/SkiaPictureView.tsx index bc9ea2314c..e9edf1c82f 100644 --- a/packages/skia/src/views/SkiaPictureView.tsx +++ b/packages/skia/src/views/SkiaPictureView.tsx @@ -82,12 +82,13 @@ export class SkiaPictureView extends React.Component { } render() { - const { mode, debug = false, ...viewProps } = this.props; + const { mode, debug = false, opaque = false, ...viewProps } = this.props; return ( ); diff --git a/packages/skia/src/views/types.ts b/packages/skia/src/views/types.ts index 0d53e1f8bb..fb90840a79 100644 --- a/packages/skia/src/views/types.ts +++ b/packages/skia/src/views/types.ts @@ -6,6 +6,7 @@ import type { SharedValueType } from "../renderer/processors/Animations/Animatio export type NativeSkiaViewProps = ViewProps & { debug?: boolean; + opaque?: boolean; }; export interface DrawingInfo { @@ -32,6 +33,8 @@ export interface SkiaBaseViewProps extends ViewProps { * the Skia view is resized. */ onSize?: SharedValueType; + + opaque?: boolean; } export interface SkiaPictureViewNativeProps extends SkiaBaseViewProps {