Skip to content

Android ZOrder for transparent surfaceView #460

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 9 commits into from
Sep 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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