Skip to content

topMost property with perf improvement for Android #419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.annotation.TargetApi;
import android.graphics.Bitmap;
import android.graphics.PixelFormat;
import android.graphics.SurfaceTexture;
import android.os.Build;
import android.os.Handler;
Expand All @@ -26,17 +27,20 @@

public final class EngineView extends FrameLayout implements SurfaceHolder.Callback, TextureView.SurfaceTextureListener, View.OnTouchListener {
private static final FrameLayout.LayoutParams childViewLayoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
private TextureView transparentTextureView;
private SurfaceView surfaceView = null;
private Surface transparentSurface = null;
private SurfaceView opaqueSurfaceView = null;
private TextureView transparentTextureView = null;

private SurfaceView xrSurfaceView;
private boolean isTransparent = false;
private boolean isTopMost = false;
private final EventDispatcher reactEventDispatcher;
private Runnable renderRunnable;

public EngineView(ReactContext reactContext) {
super(reactContext);

this.setIsTransparent(false);
this.setIsTransparentAndIsTopMost(false, false);

this.xrSurfaceView = new SurfaceView(reactContext);
this.xrSurfaceView.setLayoutParams(childViewLayoutParams);
Expand Down Expand Up @@ -67,34 +71,54 @@ public void surfaceDestroyed(SurfaceHolder holder) {
public void setAntiAliasing(Integer value) {
BabylonNativeInterop.updateMSAA(value);
}

public void setIsTopMost(Boolean isTopMost) {
setIsTransparentAndIsTopMost(this.isTransparent, isTopMost);
}
// ------------------------------------
// TextureView related

public void setIsTransparent(Boolean isTransparent) {
if (isTransparent) {
if (this.opaqueSurfaceView != null) {
this.opaqueSurfaceView.setVisibility(View.GONE);
this.opaqueSurfaceView = null;
}
if (this.transparentTextureView == null) {
this.transparentTextureView = new TextureView(this.getContext());
this.transparentTextureView.setLayoutParams(EngineView.childViewLayoutParams);
this.transparentTextureView.setSurfaceTextureListener(this);
this.transparentTextureView.setOpaque(false);
this.addView(this.transparentTextureView);
}
setIsTransparentAndIsTopMost(isTransparent, this.isTopMost);
}

private void setIsTransparentAndIsTopMost(Boolean isTransparent, Boolean isTopMost) {
if (this.isTransparent == isTransparent && this.isTopMost == isTopMost &&
(this.surfaceView != null || this.transparentTextureView != null)) {
return;
}
if (this.surfaceView != null) {
this.surfaceView.setVisibility(View.GONE);
this.surfaceView = null;
}
if (this.transparentTextureView != null) {
this.transparentTextureView.setVisibility(View.GONE);
this.transparentTextureView = null;
}
if (isTransparent && !isTopMost) {
this.transparentTextureView = new TextureView(this.getContext());
this.transparentTextureView.setLayoutParams(EngineView.childViewLayoutParams);
this.transparentTextureView.setSurfaceTextureListener(this);
this.transparentTextureView.setOpaque(false);
this.addView(this.transparentTextureView);
} else {
if (this.transparentTextureView != null) {
this.transparentTextureView.setVisibility(View.GONE);
this.transparentTextureView = null;
this.surfaceView = new SurfaceView(this.getContext());
this.surfaceView.setLayoutParams(EngineView.childViewLayoutParams);
SurfaceHolder surfaceHolder = this.surfaceView.getHolder();
if (isTransparent) {
surfaceHolder.setFormat(PixelFormat.TRANSPARENT);
}
if (this.opaqueSurfaceView == null) {
this.opaqueSurfaceView = new SurfaceView(this.getContext());
this.opaqueSurfaceView.setLayoutParams(EngineView.childViewLayoutParams);
this.opaqueSurfaceView.getHolder().addCallback(this);
this.addView(this.opaqueSurfaceView);
if (isTopMost) {
// ZOrder is not dynamic before Android 11. Recreate the surfaceView and set order before adding to the parent
// https://developer.android.com/reference/android/view/SurfaceView#setZOrderOnTop(boolean)
this.surfaceView.setZOrderOnTop(true);
}
surfaceHolder.addCallback(this);
this.addView(this.surfaceView);
}

this.isTransparent = isTransparent;
this.isTopMost = isTopMost;

// xr view needs to be on top of views that might be created after it.
if (this.xrSurfaceView != null) {
this.xrSurfaceView.bringToFront();
Expand All @@ -117,6 +141,9 @@ public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
this.renderRunnable = null;
}

// ------------------------------------
// TextureView

@Override
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
this.startRenderLoop();
Expand All @@ -140,8 +167,6 @@ public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture)

@Override
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
this.acquireNewTransparentSurface(surfaceTexture);
BabylonNativeInterop.updateView(this.transparentSurface);
}

private void acquireNewTransparentSurface(@NonNull SurfaceTexture surfaceTexture) {
Expand Down Expand Up @@ -182,8 +207,8 @@ public void takeSnapshot() {
Surface sourceSurface = this.transparentSurface;
if (BabylonNativeInterop.isXRActive()) {
sourceSurface = this.xrSurfaceView.getHolder().getSurface();
} else if (this.opaqueSurfaceView != null) {
sourceSurface = this.opaqueSurfaceView.getHolder().getSurface();
} else if (this.surfaceView != null) {
sourceSurface = this.surfaceView.getHolder().getSurface();
}
PixelCopy.request(sourceSurface, bitmap, getOnPixelCopyFinishedListener(bitmap, helperThread), helperThreadHandler);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public void setAntiAliasing(EngineView view, Integer value) {
view.setAntiAliasing(value);
}

@ReactProp(name = "isTopMost")
public void setIsTopMost(EngineView view, Boolean isTopMost) {
view.setIsTopMost(isTopMost);
}

@NonNull
@Override
protected EngineView createViewInstance(@NonNull ThemedReactContext reactContext) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ @interface EngineView : MTKView
@property (nonatomic, copy) RCTDirectEventBlock onSnapshotDataReturned;
@property (nonatomic, assign) BOOL isTransparent;
@property (nonatomic, assign) NSNumber* antiAliasing;
@property (nonatomic, assign) BOOL isTopMost;

@end

Expand Down Expand Up @@ -45,6 +46,12 @@ - (void)setMSAA:(NSNumber*)value {
[BabylonNativeInterop updateMSAA:value];
}

- (void)setIsTopMostFlag:(NSNumber*)isTopMostFlag {
BOOL isTopMost = [isTopMostFlag intValue] == 1;
self.layer.zPosition = isTopMost ? FLT_MAX : 0.f;
self.isTopMost = isTopMost;
}

- (void)setBounds:(CGRect)bounds {
[super setBounds:bounds];
[BabylonNativeInterop updateView:self];
Expand Down Expand Up @@ -126,6 +133,10 @@ @implementation EngineViewManager
[view setMSAA:json];
}

RCT_CUSTOM_VIEW_PROPERTY(isTopMost, NSNumber*, EngineView){
[view setIsTopMostFlag:json];
}

RCT_EXPORT_MODULE(EngineViewManager)

RCT_EXPORT_VIEW_PROPERTY(onSnapshotDataReturned, RCTDirectEventBlock)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ Pod::Spec.new do |s|

s.libraries = 'astc-codec',
'astc',
'etc1',
'etc2',
'nvtt',
'squish',
'pvrtc',
'iqa',
'edtaa3',
'BabylonNative',
'bgfx',
'bimg',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ namespace winrt::BabylonReactNative::implementation {
{
auto value = propertyValue.AsUInt8();
BabylonNative::UpdateMSAA(value);
} else if (propertyName == "isTopMost")
{
// todo: implementation
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ namespace winrt::BabylonReactNative::implementation {

nativeProps.Insert(L"isTransparent", ViewManagerPropertyType::Boolean);
nativeProps.Insert(L"antiAliasing", ViewManagerPropertyType::Number);
nativeProps.Insert(L"isTopMost", ViewManagerPropertyType::Boolean);

return nativeProps.GetView();
}
Expand Down
4 changes: 3 additions & 1 deletion Modules/@babylonjs/react-native/EngineView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface EngineViewProps extends ViewProps {
camera?: Camera;
displayFrameRate?: boolean;
isTransparent?: boolean;
isTopMost?: boolean;
antiAliasing?: 0 | 1 | 2 | 4 | 8 | 16;
onInitialized?: (view: EngineViewCallbacks) => void;
}
Expand All @@ -29,6 +30,7 @@ export const EngineView: FunctionComponent<EngineViewProps> = (props: EngineView
const snapshotPromise = useRef<{ promise: Promise<string>, resolve: (data: string) => void }>();
const isTransparent = props.isTransparent ?? false;
const antiAliasing = props.antiAliasing ?? 0;
const isTopMost = props.isTopMost ?? false;

const initialized = useModuleInitializer();

Expand Down Expand Up @@ -114,7 +116,7 @@ export const EngineView: FunctionComponent<EngineViewProps> = (props: EngineView
if (initialized !== false) {
return (
<View style={[{ flex: 1 }, props.style, { overflow: "hidden" }]}>
{ initialized && <NativeEngineView ref={engineViewRef} style={{ flex: 1 }} onSnapshotDataReturned={snapshotDataReturnedHandler} isTransparent={isTransparent} antiAliasing={antiAliasing}/> }
{ initialized && <NativeEngineView ref={engineViewRef} style={{ flex: 1 }} onSnapshotDataReturned={snapshotDataReturnedHandler} isTransparent={isTransparent} antiAliasing={antiAliasing} isTopMost={isTopMost}/> }
{ sceneStats !== undefined &&
<View style={{ backgroundColor: '#00000040', opacity: 1, position: 'absolute', right: 0, left: 0, top: 0, flexDirection: 'row-reverse' }}>
<Text style={{ color: 'yellow', alignSelf: 'flex-end', margin: 3, fontVariant: ['tabular-nums'] }}>FPS: {sceneStats.frameRate.toFixed(0)}</Text>
Expand Down
1 change: 1 addition & 0 deletions Modules/@babylonjs/react-native/NativeEngineView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare const global: any;
export interface NativeEngineViewProps extends ViewProps {
isTransparent: boolean;
antiAliasing: number;
isTopMost: boolean;
onSnapshotDataReturned?: (event: SyntheticEvent) => void;
}

Expand Down
7 changes: 7 additions & 0 deletions Modules/@babylonjs/react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ e.g.
```tsx
<EngineView style={{flex: 1}} camera={camera} isTransparent={true} />
```
`isTopMost` is a flag that allows to place the view on top of any other view. When enabled, this allows a huge performance improvement on Android with Transparency on.

e.g.

```tsx
<EngineView style={{flex: 1}} camera={camera} isTopMost={true} />
```

To configure anti-aliasing, a property called `antiAliasing` can be changed to a value of 0 or 1 (disable anti-aliasing, default), 2, 4, 8 or 16 (anti-aliasing samples).

Expand Down