Skip to content

Commit 3afe4b8

Browse files
topMost property with perf improvement for Android (#419)
* replace textureView with surfaceView Properties for transparency * topMost property * missing addview * podspec and zPosition * Update Modules/@babylonjs/react-native-iosandroid/android/src/main/java/com/babylonreactnative/EngineView.java Co-authored-by: Ryan Tremblay <[email protected]> * IsTopMost * more comprehensible code * reorder views * PR feedback Co-authored-by: Ryan Tremblay <[email protected]>
1 parent b617572 commit 3afe4b8

File tree

9 files changed

+91
-29
lines changed

9 files changed

+91
-29
lines changed

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

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import android.annotation.TargetApi;
44
import android.graphics.Bitmap;
5+
import android.graphics.PixelFormat;
56
import android.graphics.SurfaceTexture;
67
import android.os.Build;
78
import android.os.Handler;
@@ -26,17 +27,20 @@
2627

2728
public final class EngineView extends FrameLayout implements SurfaceHolder.Callback, TextureView.SurfaceTextureListener, View.OnTouchListener {
2829
private static final FrameLayout.LayoutParams childViewLayoutParams = new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
29-
private TextureView transparentTextureView;
30+
private SurfaceView surfaceView = null;
3031
private Surface transparentSurface = null;
31-
private SurfaceView opaqueSurfaceView = null;
32+
private TextureView transparentTextureView = null;
33+
3234
private SurfaceView xrSurfaceView;
35+
private boolean isTransparent = false;
36+
private boolean isTopMost = false;
3337
private final EventDispatcher reactEventDispatcher;
3438
private Runnable renderRunnable;
3539

3640
public EngineView(ReactContext reactContext) {
3741
super(reactContext);
3842

39-
this.setIsTransparent(false);
43+
this.setIsTransparentAndIsTopMost(false, false);
4044

4145
this.xrSurfaceView = new SurfaceView(reactContext);
4246
this.xrSurfaceView.setLayoutParams(childViewLayoutParams);
@@ -67,34 +71,54 @@ public void surfaceDestroyed(SurfaceHolder holder) {
6771
public void setAntiAliasing(Integer value) {
6872
BabylonNativeInterop.updateMSAA(value);
6973
}
74+
75+
public void setIsTopMost(Boolean isTopMost) {
76+
setIsTransparentAndIsTopMost(this.isTransparent, isTopMost);
77+
}
7078
// ------------------------------------
7179
// TextureView related
72-
7380
public void setIsTransparent(Boolean isTransparent) {
74-
if (isTransparent) {
75-
if (this.opaqueSurfaceView != null) {
76-
this.opaqueSurfaceView.setVisibility(View.GONE);
77-
this.opaqueSurfaceView = null;
78-
}
79-
if (this.transparentTextureView == null) {
80-
this.transparentTextureView = new TextureView(this.getContext());
81-
this.transparentTextureView.setLayoutParams(EngineView.childViewLayoutParams);
82-
this.transparentTextureView.setSurfaceTextureListener(this);
83-
this.transparentTextureView.setOpaque(false);
84-
this.addView(this.transparentTextureView);
85-
}
81+
setIsTransparentAndIsTopMost(isTransparent, this.isTopMost);
82+
}
83+
84+
private void setIsTransparentAndIsTopMost(Boolean isTransparent, Boolean isTopMost) {
85+
if (this.isTransparent == isTransparent && this.isTopMost == isTopMost &&
86+
(this.surfaceView != null || this.transparentTextureView != null)) {
87+
return;
88+
}
89+
if (this.surfaceView != null) {
90+
this.surfaceView.setVisibility(View.GONE);
91+
this.surfaceView = null;
92+
}
93+
if (this.transparentTextureView != null) {
94+
this.transparentTextureView.setVisibility(View.GONE);
95+
this.transparentTextureView = null;
96+
}
97+
if (isTransparent && !isTopMost) {
98+
this.transparentTextureView = new TextureView(this.getContext());
99+
this.transparentTextureView.setLayoutParams(EngineView.childViewLayoutParams);
100+
this.transparentTextureView.setSurfaceTextureListener(this);
101+
this.transparentTextureView.setOpaque(false);
102+
this.addView(this.transparentTextureView);
86103
} else {
87-
if (this.transparentTextureView != null) {
88-
this.transparentTextureView.setVisibility(View.GONE);
89-
this.transparentTextureView = null;
104+
this.surfaceView = new SurfaceView(this.getContext());
105+
this.surfaceView.setLayoutParams(EngineView.childViewLayoutParams);
106+
SurfaceHolder surfaceHolder = this.surfaceView.getHolder();
107+
if (isTransparent) {
108+
surfaceHolder.setFormat(PixelFormat.TRANSPARENT);
90109
}
91-
if (this.opaqueSurfaceView == null) {
92-
this.opaqueSurfaceView = new SurfaceView(this.getContext());
93-
this.opaqueSurfaceView.setLayoutParams(EngineView.childViewLayoutParams);
94-
this.opaqueSurfaceView.getHolder().addCallback(this);
95-
this.addView(this.opaqueSurfaceView);
110+
if (isTopMost) {
111+
// ZOrder is not dynamic before Android 11. Recreate the surfaceView and set order before adding to the parent
112+
// https://developer.android.com/reference/android/view/SurfaceView#setZOrderOnTop(boolean)
113+
this.surfaceView.setZOrderOnTop(true);
96114
}
115+
surfaceHolder.addCallback(this);
116+
this.addView(this.surfaceView);
97117
}
118+
119+
this.isTransparent = isTransparent;
120+
this.isTopMost = isTopMost;
121+
98122
// xr view needs to be on top of views that might be created after it.
99123
if (this.xrSurfaceView != null) {
100124
this.xrSurfaceView.bringToFront();
@@ -117,6 +141,9 @@ public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
117141
this.renderRunnable = null;
118142
}
119143

144+
// ------------------------------------
145+
// TextureView
146+
120147
@Override
121148
public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surfaceTexture, int i, int i1) {
122149
this.startRenderLoop();
@@ -140,8 +167,6 @@ public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surfaceTexture)
140167

141168
@Override
142169
public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surfaceTexture) {
143-
this.acquireNewTransparentSurface(surfaceTexture);
144-
BabylonNativeInterop.updateView(this.transparentSurface);
145170
}
146171

147172
private void acquireNewTransparentSurface(@NonNull SurfaceTexture surfaceTexture) {
@@ -182,8 +207,8 @@ public void takeSnapshot() {
182207
Surface sourceSurface = this.transparentSurface;
183208
if (BabylonNativeInterop.isXRActive()) {
184209
sourceSurface = this.xrSurfaceView.getHolder().getSurface();
185-
} else if (this.opaqueSurfaceView != null) {
186-
sourceSurface = this.opaqueSurfaceView.getHolder().getSurface();
210+
} else if (this.surfaceView != null) {
211+
sourceSurface = this.surfaceView.getHolder().getSurface();
187212
}
188213
PixelCopy.request(sourceSurface, bitmap, getOnPixelCopyFinishedListener(bitmap, helperThread), helperThreadHandler);
189214
}

Modules/@babylonjs/react-native-iosandroid/android/src/main/java/com/babylonreactnative/EngineViewManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public void setAntiAliasing(EngineView view, Integer value) {
3333
view.setAntiAliasing(value);
3434
}
3535

36+
@ReactProp(name = "isTopMost")
37+
public void setIsTopMost(EngineView view, Boolean isTopMost) {
38+
view.setIsTopMost(isTopMost);
39+
}
40+
3641
@NonNull
3742
@Override
3843
protected EngineView createViewInstance(@NonNull ThemedReactContext reactContext) {

Modules/@babylonjs/react-native-iosandroid/ios/EngineViewManager.mm

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ @interface EngineView : MTKView
1313
@property (nonatomic, copy) RCTDirectEventBlock onSnapshotDataReturned;
1414
@property (nonatomic, assign) BOOL isTransparent;
1515
@property (nonatomic, assign) NSNumber* antiAliasing;
16+
@property (nonatomic, assign) BOOL isTopMost;
1617

1718
@end
1819

@@ -45,6 +46,12 @@ - (void)setMSAA:(NSNumber*)value {
4546
[BabylonNativeInterop updateMSAA:value];
4647
}
4748

49+
- (void)setIsTopMostFlag:(NSNumber*)isTopMostFlag {
50+
BOOL isTopMost = [isTopMostFlag intValue] == 1;
51+
self.layer.zPosition = isTopMost ? FLT_MAX : 0.f;
52+
self.isTopMost = isTopMost;
53+
}
54+
4855
- (void)setBounds:(CGRect)bounds {
4956
[super setBounds:bounds];
5057
[BabylonNativeInterop updateView:self];
@@ -126,6 +133,10 @@ @implementation EngineViewManager
126133
[view setMSAA:json];
127134
}
128135

136+
RCT_CUSTOM_VIEW_PROPERTY(isTopMost, NSNumber*, EngineView){
137+
[view setIsTopMostFlag:json];
138+
}
139+
129140
RCT_EXPORT_MODULE(EngineViewManager)
130141

131142
RCT_EXPORT_VIEW_PROPERTY(onSnapshotDataReturned, RCTDirectEventBlock)

Modules/@babylonjs/react-native-iosandroid/react-native-babylon.podspec

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ Pod::Spec.new do |s|
1818

1919
s.libraries = 'astc-codec',
2020
'astc',
21+
'etc1',
22+
'etc2',
23+
'nvtt',
24+
'squish',
25+
'pvrtc',
26+
'iqa',
27+
'edtaa3',
2128
'BabylonNative',
2229
'bgfx',
2330
'bimg',

Modules/@babylonjs/react-native-windows/windows/BabylonReactNative/EngineView.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ namespace winrt::BabylonReactNative::implementation {
188188
{
189189
auto value = propertyValue.AsUInt8();
190190
BabylonNative::UpdateMSAA(value);
191+
} else if (propertyName == "isTopMost")
192+
{
193+
// todo: implementation
191194
}
192195
}
193196
}

Modules/@babylonjs/react-native-windows/windows/BabylonReactNative/EngineViewManager.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ namespace winrt::BabylonReactNative::implementation {
3838

3939
nativeProps.Insert(L"isTransparent", ViewManagerPropertyType::Boolean);
4040
nativeProps.Insert(L"antiAliasing", ViewManagerPropertyType::Number);
41+
nativeProps.Insert(L"isTopMost", ViewManagerPropertyType::Boolean);
4142

4243
return nativeProps.GetView();
4344
}

Modules/@babylonjs/react-native/EngineView.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export interface EngineViewProps extends ViewProps {
99
camera?: Camera;
1010
displayFrameRate?: boolean;
1111
isTransparent?: boolean;
12+
isTopMost?: boolean;
1213
antiAliasing?: 0 | 1 | 2 | 4 | 8 | 16;
1314
onInitialized?: (view: EngineViewCallbacks) => void;
1415
}
@@ -29,6 +30,7 @@ export const EngineView: FunctionComponent<EngineViewProps> = (props: EngineView
2930
const snapshotPromise = useRef<{ promise: Promise<string>, resolve: (data: string) => void }>();
3031
const isTransparent = props.isTransparent ?? false;
3132
const antiAliasing = props.antiAliasing ?? 0;
33+
const isTopMost = props.isTopMost ?? false;
3234

3335
const initialized = useModuleInitializer();
3436

@@ -114,7 +116,7 @@ export const EngineView: FunctionComponent<EngineViewProps> = (props: EngineView
114116
if (initialized !== false) {
115117
return (
116118
<View style={[{ flex: 1 }, props.style, { overflow: "hidden" }]}>
117-
{ initialized && <NativeEngineView ref={engineViewRef} style={{ flex: 1 }} onSnapshotDataReturned={snapshotDataReturnedHandler} isTransparent={isTransparent} antiAliasing={antiAliasing}/> }
119+
{ initialized && <NativeEngineView ref={engineViewRef} style={{ flex: 1 }} onSnapshotDataReturned={snapshotDataReturnedHandler} isTransparent={isTransparent} antiAliasing={antiAliasing} isTopMost={isTopMost}/> }
118120
{ sceneStats !== undefined &&
119121
<View style={{ backgroundColor: '#00000040', opacity: 1, position: 'absolute', right: 0, left: 0, top: 0, flexDirection: 'row-reverse' }}>
120122
<Text style={{ color: 'yellow', alignSelf: 'flex-end', margin: 3, fontVariant: ['tabular-nums'] }}>FPS: {sceneStats.frameRate.toFixed(0)}</Text>

Modules/@babylonjs/react-native/NativeEngineView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ declare const global: any;
66
export interface NativeEngineViewProps extends ViewProps {
77
isTransparent: boolean;
88
antiAliasing: number;
9+
isTopMost: boolean;
910
onSnapshotDataReturned?: (event: SyntheticEvent) => void;
1011
}
1112

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ e.g.
8989
```tsx
9090
<EngineView style={{flex: 1}} camera={camera} isTransparent={true} />
9191
```
92+
`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.
93+
94+
e.g.
95+
96+
```tsx
97+
<EngineView style={{flex: 1}} camera={camera} isTopMost={true} />
98+
```
9299

93100
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).
94101

0 commit comments

Comments
 (0)