Skip to content

Commit ff4cae8

Browse files
Fix ViewNode2 rendering (#579)
* Use SurfaceTexture instead * No minimum Android 28 required in ViewNode2 anymore * Actually remove RequiresApi * Some more SDK guards removed
1 parent 2969a2c commit ff4cae8

File tree

3 files changed

+42
-65
lines changed

3 files changed

+42
-65
lines changed

sceneview/src/main/java/io/github/sceneview/Scene.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,6 @@ fun rememberCameraManipulator(
584584
}
585585
}
586586

587-
@RequiresApi(Build.VERSION_CODES.P)
588587
@Composable
589588
fun rememberViewNodeManager(
590589
context: Context = LocalContext.current,
@@ -605,4 +604,4 @@ private fun ScenePreview(modifier: Modifier) {
605604
modifier = modifier
606605
.background(Color.DarkGray)
607606
)
608-
}
607+
}

sceneview/src/main/java/io/github/sceneview/SceneView.kt

+4-12
Original file line numberDiff line numberDiff line change
@@ -893,9 +893,7 @@ open class SceneView @JvmOverloads constructor(
893893
private inner class LifeCycleObserver : DefaultLifecycleObserver {
894894
override fun onResume(owner: LifecycleOwner) {
895895

896-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
897-
viewNodeWindowManager?.resume(this@SceneView)
898-
}
896+
viewNodeWindowManager?.resume(this@SceneView)
899897

900898
// Start the drawing when the renderer is resumed. Remove and re-add the callback
901899
// to avoid getting called twice.
@@ -908,15 +906,11 @@ open class SceneView @JvmOverloads constructor(
908906
override fun onPause(owner: LifecycleOwner) {
909907
Choreographer.getInstance().removeFrameCallback(frameCallback)
910908

911-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
912-
viewNodeWindowManager?.pause()
913-
}
909+
viewNodeWindowManager?.pause()
914910
}
915911

916912
override fun onDestroy(owner: LifecycleOwner) {
917-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
918-
viewNodeWindowManager?.destroy()
919-
}
913+
viewNodeWindowManager?.destroy()
920914
destroy()
921915
}
922916
}
@@ -1055,8 +1049,6 @@ open class SceneView @JvmOverloads constructor(
10551049
targetPosition = targetPosition
10561050
)
10571051

1058-
1059-
@RequiresApi(Build.VERSION_CODES.P)
10601052
fun createViewNodeManager(context: Context) = ViewNode2.WindowManager(context)
10611053

10621054
fun createMainLightNode(engine: Engine): LightNode = DefaultLightNode(engine)
@@ -1085,4 +1077,4 @@ open class SceneView @JvmOverloads constructor(
10851077

10861078
fun createCollisionSystem(view: View) = CollisionSystem(view)
10871079
}
1088-
}
1080+
}

sceneview/src/main/java/io/github/sceneview/node/ViewNode2.kt

+37-51
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
package io.github.sceneview.node
22

33
import android.content.Context
4+
import android.content.ContextWrapper
45
import android.graphics.Canvas
5-
import android.graphics.ImageFormat
6-
import android.graphics.Picture
76
import android.graphics.PixelFormat
87
import android.graphics.PorterDuff
9-
import android.media.ImageReader
10-
import android.os.Build
11-
import android.os.Handler
12-
import android.os.Looper
8+
import android.graphics.SurfaceTexture
139
import android.util.AttributeSet
1410
import android.view.LayoutInflater
1511
import android.view.MotionEvent
1612
import android.view.Surface
1713
import android.view.View
1814
import android.view.WindowManager.LayoutParams
1915
import android.widget.FrameLayout
16+
import androidx.activity.ComponentActivity
17+
import androidx.activity.setViewTreeFullyDrawnReporterOwner
18+
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
2019
import androidx.annotation.LayoutRes
21-
import androidx.annotation.RequiresApi
2220
import androidx.compose.runtime.Composable
2321
import androidx.compose.ui.platform.ComposeView
2422
import androidx.compose.ui.platform.ViewCompositionStrategy
23+
import androidx.lifecycle.setViewTreeLifecycleOwner
24+
import androidx.lifecycle.setViewTreeViewModelStoreOwner
25+
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
2526
import com.google.android.filament.Engine
2627
import com.google.android.filament.MaterialInstance
2728
import com.google.android.filament.RenderableManager
@@ -60,21 +61,15 @@ import io.github.sceneview.safeDestroyTexture
6061
* (water, mirror surfaces, front camera in AR, etc.).
6162
* True to invert front faces, false otherwise
6263
*/
63-
@RequiresApi(Build.VERSION_CODES.P)
6464
class ViewNode2(
6565
engine: Engine,
6666
val windowManager: WindowManager,
6767
materialLoader: MaterialLoader,
6868
view: View,
6969
unlit: Boolean = false,
7070
invertFrontFaceWinding: Boolean = false,
71-
// This seems a little high, but lower values cause occasional "client tried to acquire
72-
// more than maxImages buffers" on a Pixel 3
73-
val imageReaderMaxImages: Int = 7
7471
) : PlaneNode(engine = engine) {
7572

76-
var surface: Surface? = null
77-
7873
// Updated when the view is added to the view manager
7974
var pxPerUnits = 250.0f
8075
set(value) {
@@ -92,11 +87,11 @@ class ViewNode2(
9287
addView(view)
9388
}
9489

95-
private var imageReader: ImageReader? = null
96-
private val picture = Picture()
97-
private val directImageHandler = Handler(Looper.getMainLooper())
90+
private val surfaceTexture = SurfaceTexture(0).also { it.detachFromGLContext() }
91+
private val surface = Surface(surfaceTexture)
9892

9993
val stream: Stream = Stream.Builder()
94+
.stream(surfaceTexture)
10095
.build(engine)
10196

10297
val texture: Texture = Texture.Builder()
@@ -295,13 +290,14 @@ class ViewNode2(
295290
// }
296291

297292
override fun destroy() {
298-
super.destroy()
293+
294+
windowManager.removeView(layout)
299295

300296
engine.safeDestroyMaterialInstance(materialInstance)
301297
engine.safeDestroyTexture(texture)
302298
engine.safeDestroyStream(stream)
303299

304-
windowManager.removeView(layout)
300+
super.destroy()
305301
}
306302

307303
/**
@@ -320,7 +316,6 @@ class ViewNode2(
320316
* the view will not be marked as dirty when child views are animating when hardware
321317
* accelerated.
322318
*/
323-
@RequiresApi(Build.VERSION_CODES.P)
324319
inner class Layout @JvmOverloads constructor(
325320
context: Context,
326321
attrs: AttributeSet? = null,
@@ -332,15 +327,8 @@ class ViewNode2(
332327
super.onLayout(changed, left, top, right, bottom)
333328

334329
// Only called when we first get View size
335-
imageReader?.close()
336-
imageReader = ImageReader.newInstance(
337-
width,
338-
height,
339-
ImageFormat.RGB_565,
340-
imageReaderMaxImages
341-
)
342-
surface?.release()
343-
surface = imageReader?.surface
330+
surfaceTexture.setDefaultBufferSize(width, height)
331+
344332
}
345333

346334
override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
@@ -351,30 +339,13 @@ class ViewNode2(
351339

352340
override fun dispatchDraw(canvas: Canvas) {
353341
if (!isAttachedToWindow) return
354-
// Check for Stream validity
355-
val stream = stream.takeIf { it.timestamp > 0 } ?: return
356342

357343
// Sanity that the surface is valid.
358-
val viewSurface = surface?.takeIf { it.isValid } ?: return
359-
if (isDirty) {
360-
val pictureCanvas = picture.beginRecording(width, height)
361-
pictureCanvas.drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
362-
super.dispatchDraw(pictureCanvas)
363-
picture.endRecording()
364-
val surfaceCanvas = viewSurface.lockCanvas(null)
365-
picture.draw(surfaceCanvas)
366-
viewSurface.unlockCanvasAndPost(surfaceCanvas)
367-
368-
val image = imageReader!!.acquireLatestImage()
369-
stream.setAcquiredImage(
370-
image.hardwareBuffer!!,
371-
directImageHandler
372-
) {
373-
image.close()
374-
}
375-
}
376-
// Ask for redraw to update on each frames until stream is null
377-
invalidate()
344+
val viewSurface = surface.takeIf { it.isValid } ?: return
345+
val surfaceCanvas = viewSurface.lockCanvas(null)
346+
surfaceCanvas.drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
347+
super.dispatchDraw(surfaceCanvas)
348+
viewSurface.unlockCanvasAndPost(surfaceCanvas)
378349
}
379350
}
380351

@@ -383,7 +354,17 @@ class ViewNode2(
383354
private val windowManager =
384355
context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager
385356

386-
val layout by lazy { FrameLayout(context) }
357+
val layout by lazy {
358+
FrameLayout(context).also {
359+
context.findActivity()?.let { activity ->
360+
it.setViewTreeLifecycleOwner(activity)
361+
it.setViewTreeSavedStateRegistryOwner(activity)
362+
it.setViewTreeViewModelStoreOwner(activity)
363+
it.setViewTreeFullyDrawnReporterOwner(activity)
364+
it.setViewTreeOnBackPressedDispatcherOwner(activity)
365+
}
366+
}
367+
}
387368

388369
fun addView(view: View) = layout.addView(view)
389370
fun addView(view: View, params: FrameLayout.LayoutParams) = layout.addView(view, params)
@@ -442,3 +423,8 @@ class ViewNode2(
442423
}
443424
}
444425
}
426+
427+
428+
private fun Context.findActivity(): ComponentActivity? {
429+
return generateSequence(this) { (it as? ContextWrapper)?.baseContext }.filterIsInstance<ComponentActivity>().firstOrNull()
430+
}

0 commit comments

Comments
 (0)