Skip to content
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

Android ZOrder for transparent surfaceView #460

Merged
merged 9 commits into from
Sep 28, 2022
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,35 @@ private void setIsTransparentAndIsTopMost(Boolean isTransparent, Boolean isTopMo
this.transparentTextureView.setVisibility(View.GONE);
this.transparentTextureView = null;
}
if (isTransparent && !isTopMost) {

//if (isTransparent) {
CedricGuillemet marked this conversation as resolved.
Show resolved Hide resolved
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}/> }
ryantrem marked this conversation as resolved.
Show resolved Hide resolved
{ 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.

On Android, specific `androidView` property can help set the type of the view used for rendering. Depending on user needs and performance, refer to this table:
CedricGuillemet marked this conversation as resolved.
Show resolved Hide resolved

| Transparent | androidView value | Description |
| ----------- | ------------------------ | ----------- |
| False | TextureView | Opaque TextureView
| False | SurfaceView | Simple surfaceView (default when no `androidView` set with Transparency Off)
| False | SurfaceViewZTopMost | SurfaceView with ZTopMost set to true. https://developer.android.com/reference/android/view/SurfaceView#setZOrderOnTop(boolean)
| False | SurfaceViewZMediaOverlay | SurfaceView with ZMediaOverlay set to true. https://developer.android.com/reference/android/view/SurfaceView#setZOrderMediaOverlay(boolean)
| True | TextureView | Transparent TextureView, less performant than transparent SurfaceViews
| True | SurfaceView | SurfaceView will stay opaque
| True | SurfaceViewZTopMost | SurfaceView with ZTopMost set to true. (default when no `androidView` set with Transparency On) Transparent but top most.
| True | SurfaceViewZMediaOverlay | SurfaceView with ZMediaOverlay set to true. Only Transparent ontop of other SurfaceViews
CedricGuillemet marked this conversation as resolved.
Show resolved Hide resolved

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