diff --git a/sceneview/src/main/java/io/github/sceneview/Scene.kt b/sceneview/src/main/java/io/github/sceneview/Scene.kt index c3486d24..0c86b127 100644 --- a/sceneview/src/main/java/io/github/sceneview/Scene.kt +++ b/sceneview/src/main/java/io/github/sceneview/Scene.kt @@ -584,7 +584,6 @@ fun rememberCameraManipulator( } } -@RequiresApi(Build.VERSION_CODES.P) @Composable fun rememberViewNodeManager( context: Context = LocalContext.current, @@ -605,4 +604,4 @@ private fun ScenePreview(modifier: Modifier) { modifier = modifier .background(Color.DarkGray) ) -} \ No newline at end of file +} diff --git a/sceneview/src/main/java/io/github/sceneview/SceneView.kt b/sceneview/src/main/java/io/github/sceneview/SceneView.kt index 0cd3497e..a3dce6d0 100644 --- a/sceneview/src/main/java/io/github/sceneview/SceneView.kt +++ b/sceneview/src/main/java/io/github/sceneview/SceneView.kt @@ -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. @@ -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() } } @@ -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) @@ -1085,4 +1077,4 @@ open class SceneView @JvmOverloads constructor( fun createCollisionSystem(view: View) = CollisionSystem(view) } -} \ No newline at end of file +} diff --git a/sceneview/src/main/java/io/github/sceneview/node/ViewNode2.kt b/sceneview/src/main/java/io/github/sceneview/node/ViewNode2.kt index 7d7cd710..f900715d 100644 --- a/sceneview/src/main/java/io/github/sceneview/node/ViewNode2.kt +++ b/sceneview/src/main/java/io/github/sceneview/node/ViewNode2.kt @@ -1,15 +1,11 @@ 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 @@ -17,11 +13,16 @@ 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 @@ -60,7 +61,6 @@ 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, @@ -68,13 +68,8 @@ class ViewNode2( 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) { @@ -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() @@ -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() } /** @@ -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, @@ -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) { @@ -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) } } @@ -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) @@ -442,3 +423,8 @@ class ViewNode2( } } } + + +private fun Context.findActivity(): ComponentActivity? { + return generateSequence(this) { (it as? ContextWrapper)?.baseContext }.filterIsInstance().firstOrNull() +}