Skip to content

Commit

Permalink
Android ZOrder for transparent surfaceView (#460)
Browse files Browse the repository at this point in the history
* overlay property

* setZOrderMediaOverlay instead of setZOrderOnTop

* Update Modules/@babylonjs/react-native-iosandroid/android/src/main/java/com/babylonreactnative/EngineView.java

Co-authored-by: Ryan Tremblay <[email protected]>

* Android view property

* PR feedback

* Update Modules/@babylonjs/react-native/README.md

Co-authored-by: Ryan Tremblay <[email protected]>

* Update Modules/@babylonjs/react-native/README.md

Co-authored-by: Ryan Tremblay <[email protected]>

* removed commented code

Co-authored-by: Ryan Tremblay <[email protected]>
  • Loading branch information
CedricGuillemet and ryantrem authored Sep 28, 2022
1 parent 522e4a9 commit 7121c42
Show file tree
Hide file tree
Showing 8 changed files with 43 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ public final class EngineView extends FrameLayout implements SurfaceHolder.Callb

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

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

this.setIsTransparentAndIsTopMost(false, false);
this.setIsTransparentAndAndroidView(false, "");

this.xrSurfaceView = new SurfaceView(reactContext);
this.xrSurfaceView.setLayoutParams(childViewLayoutParams);
Expand Down Expand Up @@ -72,17 +72,18 @@ public void setAntiAliasing(Integer value) {
BabylonNativeInterop.updateMSAA(value);
}

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

public void setAndroidView(String androidView) {
setIsTransparentAndAndroidView(this.isTransparent, androidView);
}

public void setIsTransparent(Boolean isTransparent) {
setIsTransparentAndIsTopMost(isTransparent, this.isTopMost);
setIsTransparentAndAndroidView(isTransparent, this.androidView);
}

private void setIsTransparentAndIsTopMost(Boolean isTransparent, Boolean isTopMost) {
if (this.isTransparent == isTransparent && this.isTopMost == isTopMost &&
private void setIsTransparentAndAndroidView(Boolean isTransparent, String androidView) {
if (this.isTransparent == isTransparent && this.androidView.equals(androidView) &&
(this.surfaceView != null || this.transparentTextureView != null)) {
return;
}
Expand All @@ -94,30 +95,34 @@ private void setIsTransparentAndIsTopMost(Boolean isTransparent, Boolean isTopMo
this.transparentTextureView.setVisibility(View.GONE);
this.transparentTextureView = null;
}
if (isTransparent && !isTopMost) {

if (androidView.equals("TextureView")) {
this.transparentTextureView = new TextureView(this.getContext());
this.transparentTextureView.setLayoutParams(EngineView.childViewLayoutParams);
this.transparentTextureView.setSurfaceTextureListener(this);
this.transparentTextureView.setOpaque(false);
this.transparentTextureView.setOpaque(isTransparent);
this.addView(this.transparentTextureView);
} else {
this.surfaceView = new SurfaceView(this.getContext());
this.surfaceView.setLayoutParams(EngineView.childViewLayoutParams);
SurfaceHolder surfaceHolder = this.surfaceView.getHolder();

if (isTransparent) {
// transparent and androidView equals "SurfaceView" will give an opaque SurfaceView
surfaceHolder.setFormat(PixelFormat.TRANSPARENT);
}
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)
if ((androidView.equals("") && isTransparent) || androidView.equals("SurfaceViewZTopMost")) {
this.surfaceView.setZOrderOnTop(true);
} else if (androidView.equals("SurfaceViewZMediaOverlay")) {
this.surfaceView.setZOrderMediaOverlay(true);
}

surfaceHolder.addCallback(this);
this.addView(this.surfaceView);
}

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

// xr view needs to be on top of views that might be created after it.
if (this.xrSurfaceView != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ public void setAntiAliasing(EngineView view, Integer value) {
view.setAntiAliasing(value);
}

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

@NonNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ @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 @@ -46,12 +45,6 @@ - (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 @@ -133,10 +126,6 @@ @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 @@ -188,9 +188,6 @@ 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,7 +38,6 @@ 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
6 changes: 3 additions & 3 deletions Modules/@babylonjs/react-native/EngineView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface EngineViewProps extends ViewProps {
camera?: Camera;
displayFrameRate?: boolean;
isTransparent?: boolean;
isTopMost?: boolean;
androidView?: "TextureView" | "SurfaceView" | "SurfaceViewZTopMost" | "SurfaceViewZMediaOverlay";
antiAliasing?: 0 | 1 | 2 | 4 | 8 | 16;
onInitialized?: (view: EngineViewCallbacks) => void;
}
Expand All @@ -30,7 +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 androidView = props.androidView ?? "";

const initialized = useModuleInitializer();

Expand Down Expand Up @@ -116,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} isTopMost={isTopMost}/> }
{ initialized && <NativeEngineView ref={engineViewRef} style={{ flex: 1 }} onSnapshotDataReturned={snapshotDataReturnedHandler} isTransparent={isTransparent} antiAliasing={antiAliasing} androidView={androidView}/> }
{ 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
2 changes: 1 addition & 1 deletion Modules/@babylonjs/react-native/NativeEngineView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ declare const global: any;
export interface NativeEngineViewProps extends ViewProps {
isTransparent: boolean;
antiAliasing: number;
isTopMost: boolean;
androidView: string;
onSnapshotDataReturned?: (event: SyntheticEvent) => void;
}

Expand Down
24 changes: 16 additions & 8 deletions Modules/@babylonjs/react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,6 @@ 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).

e.g.
Expand All @@ -106,3 +98,19 @@ e.g.
```

Note: Currently only one `EngineView` can be active at any given time. Multi-view will be supported in a future release.

The Android specific `androidView` property can help set the type of the view used for rendering. Depending on user needs and performance, refer to the table below. [`TextureView`](https://developer.android.com/reference/android/view/TextureView) can be inserted anywhere in the view hierarchy, but is less efficient. [`SurfaceView`](https://developer.android.com/reference/android/view/SurfaceView) can only be full above or fully below the rest of the UI, but is more efficient.

| isTransparent | androidView | Description |
| ----------- | ------------------------ | ----------- |
| False | TextureView | Opaque TextureView.
| False | SurfaceView | Simple surfaceView (default when no `androidView` set with `isTransparent=false`).
| False | SurfaceViewZTopMost | SurfaceView with [ZTopMost](https://developer.android.com/reference/android/view/SurfaceView#setZOrderOnTop(boolean)) set to `true`.
| False | SurfaceViewZMediaOverlay | SurfaceView with [ZMediaOverlay](https://developer.android.com/reference/android/view/SurfaceView#setZOrderMediaOverlay(boolean)) set to `true`.
| True | TextureView | Transparent TextureView.
| True | SurfaceView | SurfaceView will stay opaque
| True | SurfaceViewZTopMost | SurfaceView with [ZTopMost](https://developer.android.com/reference/android/view/SurfaceView#setZOrderOnTop(boolean)) set to `true`. Transparent but top most. (default when no `androidView` set with `isTransparent=true`)
| True | SurfaceViewZMediaOverlay | SurfaceView with [ZMediaOverlay](https://developer.android.com/reference/android/view/SurfaceView#setZOrderMediaOverlay(boolean)) set to `true`. Only Transparent on top of other SurfaceViews.

More infos on TextureView Vs SurfaceView performance here:
https://developer.android.com/reference/android/view/TextureView

0 comments on commit 7121c42

Please sign in to comment.