Skip to content

Commit

Permalink
Add first frame on Android (#1788)
Browse files Browse the repository at this point in the history
  • Loading branch information
wcandillon authored Sep 13, 2023
1 parent a3d6e03 commit 337cb2e
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 209 deletions.
2 changes: 1 addition & 1 deletion externals/depot_tools
Submodule depot_tools updated from c7aca3 to 6d0c23
2 changes: 1 addition & 1 deletion externals/skia
Submodule skia updated from ffbc37 to f44dbc
56 changes: 0 additions & 56 deletions package/android/cpp/jni/include/JniSkiaBaseView.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,62 +62,6 @@ class JniSkiaBaseView {
_skiaAndroidView->viewDidUnmount();
}

/**
* Android specific method for rendering an offscreen GPU buffer to an Android
* bitmap. The result can be used to render the first frame of the Skia render
* to avoid flickering on android.
*/
/*
// TODO: Remove if we find another solution for first frame rendering
// protected native Object renderToBitmap(Object bitmap, int width, int
height); virtual jobject renderToBitmap(jobject bitmapIn, int width, int
height) { auto platformContext = getSkiaManager()->getPlatformContext(); auto
provider = std::make_shared<RNSkOffscreenCanvasProvider>( platformContext,
[]() {}, width, height);
// Render into a gpu backed buffer
_skiaAndroidView->getSkiaView()->getRenderer()->renderImmediate(provider);
auto rect = SkRect::MakeXYWH(0, 0, width, height);
auto image = provider->makeSnapshot(&rect);
AndroidBitmapInfo infoIn;
auto env = facebook::jni::Environment::current();
void *pixels;
// Get image info
if (AndroidBitmap_getInfo(env, bitmapIn, &infoIn) !=
ANDROID_BITMAP_RESULT_SUCCESS) {
return env->NewStringUTF("failed");
}
// Check image
if (infoIn.format != ANDROID_BITMAP_FORMAT_RGBA_8888 &&
infoIn.format != ANDROID_BITMAP_FORMAT_RGB_565) {
return env->NewStringUTF("Only support ANDROID_BITMAP_FORMAT_RGBA_8888 "
"and ANDROID_BITMAP_FORMAT_RGB_565");
}
auto imageInfo = SkImageInfo::Make(image->width(), image->height(),
image->colorType(), image->alphaType());
// Lock all images
if (AndroidBitmap_lockPixels(env, bitmapIn, &pixels) !=
ANDROID_BITMAP_RESULT_SUCCESS) {
return env->NewStringUTF("AndroidBitmap_lockPixels failed!");
}
// Set pixels from SkImage
image->readPixels(imageInfo, pixels, imageInfo.minRowBytes(), 0, 0);
// Unlocks everything
AndroidBitmap_unlockPixels(env, bitmapIn);
image = nullptr;
provider = nullptr;
return bitmapIn;
}*/

private:
JniSkiaManager *_manager;
std::shared_ptr<RNSkBaseAndroidView> _skiaAndroidView;
Expand Down
32 changes: 12 additions & 20 deletions package/android/cpp/jni/include/JniSkiaDomView.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,18 @@ class JniSkiaDomView : public jni::HybridClass<JniSkiaDomView>,
}

static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JniSkiaDomView::initHybrid),
makeNativeMethod("surfaceAvailable", JniSkiaDomView::surfaceAvailable),
makeNativeMethod("surfaceDestroyed", JniSkiaDomView::surfaceDestroyed),
makeNativeMethod("surfaceSizeChanged",
JniSkiaDomView::surfaceSizeChanged),
makeNativeMethod("setMode", JniSkiaDomView::setMode),
makeNativeMethod("setDebugMode", JniSkiaDomView::setDebugMode),
makeNativeMethod("updateTouchPoints",
JniSkiaDomView::updateTouchPoints),
makeNativeMethod("registerView", JniSkiaDomView::registerView),
makeNativeMethod("unregisterView", JniSkiaDomView::unregisterView)
// TODO: Remove if we find another solution for first frame rendering
// makeNativeMethod("renderToBitmap", JniSkiaDomView::renderToBitmap)
});
registerHybrid(
{makeNativeMethod("initHybrid", JniSkiaDomView::initHybrid),
makeNativeMethod("surfaceAvailable", JniSkiaDomView::surfaceAvailable),
makeNativeMethod("surfaceDestroyed", JniSkiaDomView::surfaceDestroyed),
makeNativeMethod("surfaceSizeChanged",
JniSkiaDomView::surfaceSizeChanged),
makeNativeMethod("setMode", JniSkiaDomView::setMode),
makeNativeMethod("setDebugMode", JniSkiaDomView::setDebugMode),
makeNativeMethod("updateTouchPoints",
JniSkiaDomView::updateTouchPoints),
makeNativeMethod("registerView", JniSkiaDomView::registerView),
makeNativeMethod("unregisterView", JniSkiaDomView::unregisterView)});
}

protected:
Expand Down Expand Up @@ -76,11 +73,6 @@ class JniSkiaDomView : public jni::HybridClass<JniSkiaDomView>,

void unregisterView() override { JniSkiaBaseView::unregisterView(); }

// TODO: Remove if we find another solution for first frame rendering
/*jobject renderToBitmap(jobject bitmap, int width, int height) override {
return JniSkiaBaseView::renderToBitmap(bitmap, width, height);
}*/

private:
friend HybridBase;

Expand Down
34 changes: 14 additions & 20 deletions package/android/cpp/jni/include/JniSkiaDrawView.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,20 @@ class JniSkiaDrawView : public jni::HybridClass<JniSkiaDrawView>,
}

static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JniSkiaDrawView::initHybrid),
makeNativeMethod("surfaceAvailable", JniSkiaDrawView::surfaceAvailable),
makeNativeMethod("surfaceDestroyed", JniSkiaDrawView::surfaceDestroyed),
makeNativeMethod("surfaceSizeChanged",
JniSkiaDrawView::surfaceSizeChanged),
makeNativeMethod("setMode", JniSkiaDrawView::setMode),
makeNativeMethod("setDebugMode", JniSkiaDrawView::setDebugMode),
makeNativeMethod("updateTouchPoints",
JniSkiaDrawView::updateTouchPoints),
makeNativeMethod("registerView", JniSkiaDrawView::registerView),
makeNativeMethod("unregisterView", JniSkiaDrawView::unregisterView),
// TODO: Remove if we find another solution for first frame rendering
// makeNativeMethod("renderToBitmap", JniSkiaDrawView::renderToBitmap)
});
registerHybrid(
{makeNativeMethod("initHybrid", JniSkiaDrawView::initHybrid),
makeNativeMethod("surfaceAvailable",
JniSkiaDrawView::surfaceAvailable),
makeNativeMethod("surfaceDestroyed",
JniSkiaDrawView::surfaceDestroyed),
makeNativeMethod("surfaceSizeChanged",
JniSkiaDrawView::surfaceSizeChanged),
makeNativeMethod("setMode", JniSkiaDrawView::setMode),
makeNativeMethod("setDebugMode", JniSkiaDrawView::setDebugMode),
makeNativeMethod("updateTouchPoints",
JniSkiaDrawView::updateTouchPoints),
makeNativeMethod("registerView", JniSkiaDrawView::registerView),
makeNativeMethod("unregisterView", JniSkiaDrawView::unregisterView)});
}

protected:
Expand Down Expand Up @@ -75,11 +74,6 @@ class JniSkiaDrawView : public jni::HybridClass<JniSkiaDrawView>,

void unregisterView() override { JniSkiaBaseView::unregisterView(); }

// TODO: Remove if we find another solution for first frame rendering
/*jobject renderToBitmap(jobject bitmap, int width, int height) override {
return JniSkiaBaseView::renderToBitmap(bitmap, width, height);
}*/

private:
friend HybridBase;

Expand Down
39 changes: 15 additions & 24 deletions package/android/cpp/jni/include/JniSkiaPictureView.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,21 @@ class JniSkiaPictureView : public jni::HybridClass<JniSkiaPictureView>,
}

static void registerNatives() {
registerHybrid({
makeNativeMethod("initHybrid", JniSkiaPictureView::initHybrid),
makeNativeMethod("surfaceAvailable",
JniSkiaPictureView::surfaceAvailable),
makeNativeMethod("surfaceDestroyed",
JniSkiaPictureView::surfaceDestroyed),
makeNativeMethod("surfaceSizeChanged",
JniSkiaPictureView::surfaceSizeChanged),
makeNativeMethod("setMode", JniSkiaPictureView::setMode),
makeNativeMethod("setDebugMode", JniSkiaPictureView::setDebugMode),
makeNativeMethod("updateTouchPoints",
JniSkiaPictureView::updateTouchPoints),
makeNativeMethod("registerView", JniSkiaPictureView::registerView),
makeNativeMethod("unregisterView", JniSkiaPictureView::unregisterView),
// TODO: Remove if we find another solution for first frame rendering
// makeNativeMethod("renderToBitmap",
// JniSkiaPictureView::renderToBitmap)
});
registerHybrid(
{makeNativeMethod("initHybrid", JniSkiaPictureView::initHybrid),
makeNativeMethod("surfaceAvailable",
JniSkiaPictureView::surfaceAvailable),
makeNativeMethod("surfaceDestroyed",
JniSkiaPictureView::surfaceDestroyed),
makeNativeMethod("surfaceSizeChanged",
JniSkiaPictureView::surfaceSizeChanged),
makeNativeMethod("setMode", JniSkiaPictureView::setMode),
makeNativeMethod("setDebugMode", JniSkiaPictureView::setDebugMode),
makeNativeMethod("updateTouchPoints",
JniSkiaPictureView::updateTouchPoints),
makeNativeMethod("registerView", JniSkiaPictureView::registerView),
makeNativeMethod("unregisterView",
JniSkiaPictureView::unregisterView)});
}

protected:
Expand Down Expand Up @@ -78,12 +75,6 @@ class JniSkiaPictureView : public jni::HybridClass<JniSkiaPictureView>,

void unregisterView() override { JniSkiaBaseView::unregisterView(); }

/*
TODO: Remove if we find another solution for first frame rendering
jobject renderToBitmap(jobject bitmap, int width, int height) override {
return JniSkiaBaseView::renderToBitmap(bitmap, width, height);
}*/

private:
friend HybridBase;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,53 +9,51 @@

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";

public SkiaBaseView(Context context) {
super(context);
// TODO: Remove if we find another solution for first frame rendering
//setWillNotDraw(!shouldRenderFirstFrameAsBitmap());
mTexture = new TextureView(context);
mTexture.setSurfaceTextureListener(this);
mTexture.setOpaque(false);
addView(mTexture);
}

/*@Override
TODO: Remove if we find another solution for first frame rendering
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// If we haven't got a surface yet, let's ask the view to
// draw into a bitmap and then render the bitmap. This method
// is typically only called once - for the first frame, and
// then the surface will be available and all rendering will
// be done directly to the surface itself.
if (shouldRenderFirstFrameAsBitmap() && mSurface == null) {
int width = getWidth();
int height = getHeight();
if (width > 0 && height > 0) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Bitmap result = (Bitmap) renderToBitmap(bitmap, width, height);
canvas.drawBitmap(
result,
new Rect(0, 0, width, height),
new Rect(0, 0, width, height),
null);
bitmap.recycle();
}
public void destroySurface() {
Log.i(tag, "destroySurface");
surfaceDestroyed();
mSurface.release();
mSurface = null;
}

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());
}
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (this.getMeasuredWidth() == 0) {
createSurfaceTexture();
}
}*/
}

@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());
}
Expand Down Expand Up @@ -132,52 +130,23 @@ private static int motionActionToType(int action) {

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Log.i("SkiaBaseView", "onSurfaceTextureAvailable " + width + "/" + height);
Log.i(tag, "onSurfaceTextureAvailable " + width + "/" + height);
mSurface = new Surface(surface);
surfaceAvailable(mSurface, width, height);

/*
TODO: Remove if we find another solution for first frame rendering
// Clear rendered bitmap when the surface texture has rendered
// We'll post a message to the main loop asking to invalidate
if (shouldRenderFirstFrameAsBitmap()) {
postUpdate(new AtomicInteger());
}*/
}

/**
* This method is a way for us to clear the bitmap rendered on the first frame
* after at least 16 frames have passed - to avoid seeing blinks on the screen caused by
* TextureView frame sync issues. This is a hack to avoid those pesky blinks. Have no
* idea on how to sync the TextureView OpenGL updates.
* @param counter
*/
/*
TODO: Remove if we find another solution for first frame rendering
void postUpdate(AtomicInteger counter) {
counter.getAndIncrement();
if (counter.get() > 16) {
invalidate();
} else {
this.post(() -> {
postUpdate(counter);
});
}
}*/

@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
Log.i("SkiaBaseView", "onSurfaceTextureSizeChanged " + width + "/" + height);
Log.i(tag, "onSurfaceTextureSizeChanged " + width + "/" + height);
surfaceSizeChanged(width, height);
}

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
Log.i("SkiaBaseView", "onSurfaceTextureDestroyed");
Log.i(tag, "onSurfaceTextureDestroyed");
// https://developer.android.com/reference/android/view/TextureView.SurfaceTextureListener#onSurfaceTextureDestroyed(android.graphics.SurfaceTexture)
surfaceDestroyed();
mSurface.release();
mSurface = null;
destroySurface();
createSurfaceTexture();
return false;
}

Expand All @@ -186,17 +155,6 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
// Nothing special to do here
}

/**
* Returns true if the view is able to directly render on the
* main thread. This can f.ex then be used to create a first frame
* render of the view. Returns true by default - override if not.
*/
/*
TODO: Remove if we find another solution for first frame rendering
protected boolean shouldRenderFirstFrameAsBitmap() {
return false;
}*/

protected abstract void surfaceAvailable(Object surface, int width, int height);

protected abstract void surfaceSizeChanged(int width, int height);
Expand All @@ -212,7 +170,4 @@ protected boolean shouldRenderFirstFrameAsBitmap() {
protected abstract void registerView(int nativeId);

protected abstract void unregisterView();

// TODO: Remove if we find another solution for first frame rendering
// protected native Object renderToBitmap(Object bitmap, int width, int height);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,6 @@ public void setDebug(ReactViewGroup view, boolean show) {
public void onDropViewInstance(@NonNull ReactViewGroup view) {
super.onDropViewInstance(view);
((SkiaBaseView)view).unregisterView();
((SkiaBaseView)view).destroySurface();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,4 @@ protected void finalize() throws Throwable {
protected native void registerView(int nativeId);

protected native void unregisterView();

// TODO: Remove if we find another solution for first frame rendering
// protected native Object renderToBitmap(Object bitmap, int width, int height);
}
Loading

0 comments on commit 337cb2e

Please sign in to comment.