Fix CameraX GL surface: render-on-demand, crop-to-fill, lifecycle
- Switch GLSurfaceView from RENDERMODE_CONTINUOUSLY to RENDERMODE_WHEN_DIRTY with OnFrameAvailableListener, halving GPU work - Add crop-to-fill aspect ratio correction so camera preview is not stretched on displays with non-16:9 aspect ratios - Add LifecycleEventObserver to pause/resume GLSurfaceView with Activity lifecycle, preventing background rendering and GL context issues Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4d755dce31
commit
7979ebd029
3 changed files with 107 additions and 18 deletions
|
|
@ -74,6 +74,8 @@ import androidx.compose.ui.semantics.stateDescription
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
|
@ -116,6 +118,7 @@ fun CameraScreen(
|
|||
val minZoom by viewModel.cameraManager.minZoomRatio.collectAsState()
|
||||
val maxZoom by viewModel.cameraManager.maxZoomRatio.collectAsState()
|
||||
val isFrontCamera by viewModel.cameraManager.isFrontCamera.collectAsState()
|
||||
val previewResolution by viewModel.cameraManager.previewResolution.collectAsState()
|
||||
val cameraError by viewModel.cameraManager.error.collectAsState()
|
||||
|
||||
// Gallery picker
|
||||
|
|
@ -161,6 +164,14 @@ fun CameraScreen(
|
|||
glSurfaceView?.requestRender()
|
||||
}
|
||||
|
||||
// Update renderer with camera preview resolution for crop-to-fill
|
||||
LaunchedEffect(previewResolution) {
|
||||
if (previewResolution.width > 0) {
|
||||
renderer?.setCameraResolution(previewResolution.width, previewResolution.height)
|
||||
glSurfaceView?.requestRender()
|
||||
}
|
||||
}
|
||||
|
||||
// Start camera when surface texture is available
|
||||
LaunchedEffect(surfaceTexture) {
|
||||
surfaceTexture?.let {
|
||||
|
|
@ -172,14 +183,28 @@ fun CameraScreen(
|
|||
LaunchedEffect(isGalleryPreview) {
|
||||
if (isGalleryPreview) {
|
||||
glSurfaceView?.onPause()
|
||||
} else {
|
||||
} else if (lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
|
||||
glSurfaceView?.onResume()
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup GL resources on GL thread (ViewModel handles its own cleanup in onCleared)
|
||||
DisposableEffect(Unit) {
|
||||
// Tie GLSurfaceView lifecycle to Activity lifecycle to prevent background rendering
|
||||
val currentIsGalleryPreview by rememberUpdatedState(isGalleryPreview)
|
||||
DisposableEffect(lifecycleOwner) {
|
||||
val observer = LifecycleEventObserver { _, event ->
|
||||
when (event) {
|
||||
Lifecycle.Event.ON_RESUME -> {
|
||||
if (!currentIsGalleryPreview) {
|
||||
glSurfaceView?.onResume()
|
||||
}
|
||||
}
|
||||
Lifecycle.Event.ON_PAUSE -> glSurfaceView?.onPause()
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
lifecycleOwner.lifecycle.addObserver(observer)
|
||||
onDispose {
|
||||
lifecycleOwner.lifecycle.removeObserver(observer)
|
||||
glSurfaceView?.queueEvent { renderer?.release() }
|
||||
}
|
||||
}
|
||||
|
|
@ -208,13 +233,16 @@ fun CameraScreen(
|
|||
GLSurfaceView(ctx).apply {
|
||||
setEGLContextClientVersion(2)
|
||||
|
||||
val newRenderer = TiltShiftRenderer(ctx) { st ->
|
||||
surfaceTexture = st
|
||||
}
|
||||
val view = this
|
||||
val newRenderer = TiltShiftRenderer(
|
||||
context = ctx,
|
||||
onSurfaceTextureAvailable = { st -> surfaceTexture = st },
|
||||
onFrameAvailable = { view.requestRender() }
|
||||
)
|
||||
renderer = newRenderer
|
||||
|
||||
setRenderer(newRenderer)
|
||||
renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
|
||||
renderMode = GLSurfaceView.RENDERMODE_WHEN_DIRTY
|
||||
|
||||
glSurfaceView = this
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue