Skip to content

Commit

Permalink
Fix ViewNode2 rendering (#579)
Browse files Browse the repository at this point in the history
* Use SurfaceTexture instead

* No minimum Android 28 required in ViewNode2 anymore

* Actually remove RequiresApi

* Some more SDK guards removed
  • Loading branch information
davidgarciaanton authored Jan 8, 2025
1 parent 2969a2c commit ff4cae8
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 65 deletions.
3 changes: 1 addition & 2 deletions sceneview/src/main/java/io/github/sceneview/Scene.kt
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,6 @@ fun rememberCameraManipulator(
}
}

@RequiresApi(Build.VERSION_CODES.P)
@Composable
fun rememberViewNodeManager(
context: Context = LocalContext.current,
Expand All @@ -605,4 +604,4 @@ private fun ScenePreview(modifier: Modifier) {
modifier = modifier
.background(Color.DarkGray)
)
}
}
16 changes: 4 additions & 12 deletions sceneview/src/main/java/io/github/sceneview/SceneView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -893,9 +893,7 @@ open class SceneView @JvmOverloads constructor(
private inner class LifeCycleObserver : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
viewNodeWindowManager?.resume(this@SceneView)
}
viewNodeWindowManager?.resume(this@SceneView)

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

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
viewNodeWindowManager?.pause()
}
viewNodeWindowManager?.pause()
}

override fun onDestroy(owner: LifecycleOwner) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
viewNodeWindowManager?.destroy()
}
viewNodeWindowManager?.destroy()
destroy()
}
}
Expand Down Expand Up @@ -1055,8 +1049,6 @@ open class SceneView @JvmOverloads constructor(
targetPosition = targetPosition
)


@RequiresApi(Build.VERSION_CODES.P)
fun createViewNodeManager(context: Context) = ViewNode2.WindowManager(context)

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

fun createCollisionSystem(view: View) = CollisionSystem(view)
}
}
}
88 changes: 37 additions & 51 deletions sceneview/src/main/java/io/github/sceneview/node/ViewNode2.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,28 @@
package io.github.sceneview.node

import android.content.Context
import android.content.ContextWrapper
import android.graphics.Canvas
import android.graphics.ImageFormat
import android.graphics.Picture
import android.graphics.PixelFormat
import android.graphics.PorterDuff
import android.media.ImageReader
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.graphics.SurfaceTexture
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.view.WindowManager.LayoutParams
import android.widget.FrameLayout
import androidx.activity.ComponentActivity
import androidx.activity.setViewTreeFullyDrawnReporterOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
import androidx.annotation.LayoutRes
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import com.google.android.filament.Engine
import com.google.android.filament.MaterialInstance
import com.google.android.filament.RenderableManager
Expand Down Expand Up @@ -60,21 +61,15 @@ import io.github.sceneview.safeDestroyTexture
* (water, mirror surfaces, front camera in AR, etc.).
* True to invert front faces, false otherwise
*/
@RequiresApi(Build.VERSION_CODES.P)
class ViewNode2(
engine: Engine,
val windowManager: WindowManager,
materialLoader: MaterialLoader,
view: View,
unlit: Boolean = false,
invertFrontFaceWinding: Boolean = false,
// This seems a little high, but lower values cause occasional "client tried to acquire
// more than maxImages buffers" on a Pixel 3
val imageReaderMaxImages: Int = 7
) : PlaneNode(engine = engine) {

var surface: Surface? = null

// Updated when the view is added to the view manager
var pxPerUnits = 250.0f
set(value) {
Expand All @@ -92,11 +87,11 @@ class ViewNode2(
addView(view)
}

private var imageReader: ImageReader? = null
private val picture = Picture()
private val directImageHandler = Handler(Looper.getMainLooper())
private val surfaceTexture = SurfaceTexture(0).also { it.detachFromGLContext() }
private val surface = Surface(surfaceTexture)

val stream: Stream = Stream.Builder()
.stream(surfaceTexture)
.build(engine)

val texture: Texture = Texture.Builder()
Expand Down Expand Up @@ -295,13 +290,14 @@ class ViewNode2(
// }

override fun destroy() {
super.destroy()

windowManager.removeView(layout)

engine.safeDestroyMaterialInstance(materialInstance)
engine.safeDestroyTexture(texture)
engine.safeDestroyStream(stream)

windowManager.removeView(layout)
super.destroy()
}

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

// Only called when we first get View size
imageReader?.close()
imageReader = ImageReader.newInstance(
width,
height,
ImageFormat.RGB_565,
imageReaderMaxImages
)
surface?.release()
surface = imageReader?.surface
surfaceTexture.setDefaultBufferSize(width, height)

}

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

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

// Sanity that the surface is valid.
val viewSurface = surface?.takeIf { it.isValid } ?: return
if (isDirty) {
val pictureCanvas = picture.beginRecording(width, height)
pictureCanvas.drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
super.dispatchDraw(pictureCanvas)
picture.endRecording()
val surfaceCanvas = viewSurface.lockCanvas(null)
picture.draw(surfaceCanvas)
viewSurface.unlockCanvasAndPost(surfaceCanvas)

val image = imageReader!!.acquireLatestImage()
stream.setAcquiredImage(
image.hardwareBuffer!!,
directImageHandler
) {
image.close()
}
}
// Ask for redraw to update on each frames until stream is null
invalidate()
val viewSurface = surface.takeIf { it.isValid } ?: return
val surfaceCanvas = viewSurface.lockCanvas(null)
surfaceCanvas.drawColor(android.graphics.Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
super.dispatchDraw(surfaceCanvas)
viewSurface.unlockCanvasAndPost(surfaceCanvas)
}
}

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

val layout by lazy { FrameLayout(context) }
val layout by lazy {
FrameLayout(context).also {
context.findActivity()?.let { activity ->
it.setViewTreeLifecycleOwner(activity)
it.setViewTreeSavedStateRegistryOwner(activity)
it.setViewTreeViewModelStoreOwner(activity)
it.setViewTreeFullyDrawnReporterOwner(activity)
it.setViewTreeOnBackPressedDispatcherOwner(activity)
}
}
}

fun addView(view: View) = layout.addView(view)
fun addView(view: View, params: FrameLayout.LayoutParams) = layout.addView(view, params)
Expand Down Expand Up @@ -442,3 +423,8 @@ class ViewNode2(
}
}
}


private fun Context.findActivity(): ComponentActivity? {
return generateSequence(this) { (it as? ContextWrapper)?.baseContext }.filterIsInstance<ComponentActivity>().firstOrNull()
}

0 comments on commit ff4cae8

Please sign in to comment.