cashapp / paparazzi

Render your Android screens without a physical device or emulator
https://cashapp.github.io/paparazzi/
Apache License 2.0
2.31k stars 215 forks source link

OOM when snapshotting multiple lottie files #1669

Open jamesrapadmi opened 3 weeks ago

jamesrapadmi commented 3 weeks ago

Description I have a project with a lot of screenshots (2000+). One series of the tests are now failing with an OOM. This is caused by Canvas.getMatrix() on the NopCanvas returning an erroneous matrix of Matrix{[8.6342721E14, 1.85162577E15, 1.9006225E20][-3.56884751E15, 1.66418037E15, 1.17585415E20][0.0, 0.0, 1.0]} this then causes lottie to attempt to allocate a max size bitmap which causes the crash. (Issue occurs on this line)

java.lang.OutOfMemoryError
    at android.graphics.Bitmap.nativeCreate(Native Method)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:1206)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:1163)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:1111)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:1070)
    at com.airbnb.lottie.LottieDrawable.ensureSoftwareRenderingBitmap(LottieDrawable.java:1812)
    at com.airbnb.lottie.LottieDrawable.renderAndDrawAsBitmap(LottieDrawable.java:1765)
    at com.airbnb.lottie.LottieDrawable.draw(LottieDrawable.java:772)
    at com.airbnb.lottie.compose.LottieAnimationKt$LottieAnimation$2.invoke(LottieAnimation.kt:143)
    at com.airbnb.lottie.compose.LottieAnimationKt$LottieAnimation$2.invoke(LottieAnimation.kt:106)
    at androidx.compose.ui.draw.DrawBackgroundModifier.draw(DrawModifier.kt:127)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:110)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:89)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:450)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:280)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:280)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:280)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:280)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:1000)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:196)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:280)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:1000)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:196)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:58)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:469)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:468)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:503)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:502)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:258)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:468)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:466)
    at androidx.compose.ui.platform.GraphicsLayerOwnerLayer.drawLayer(GraphicsLayerOwnerLayer.android.kt:271)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:434)
    at androidx.compose.ui.node.LayoutModifierNodeCoordinator.performDraw(LayoutModifierNodeCoordinator.kt:280)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawContent(LayoutNodeDrawScope.kt:68)
    at androidx.compose.foundation.BackgroundNode.draw(Background.kt:163)
    at androidx.compose.ui.node.LayoutNodeDrawScope.drawDirect-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:110)
    at androidx.compose.ui.node.LayoutNodeDrawScope.draw-eZhPAX0$ui_release(LayoutNodeDrawScope.kt:89)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:450)
    at androidx.compose.ui.node.NodeCoordinator.access$drawContainedDrawModifiers(NodeCoordinator.kt:58)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:469)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1$1.invoke(NodeCoordinator.kt:468)
    at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2441)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:502)
    at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:258)
    at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:468)
    at androidx.compose.ui.node.NodeCoordinator$drawBlock$1.invoke(NodeCoordinator.kt:466)
    at androidx.compose.ui.platform.GraphicsLayerOwnerLayer.drawLayer(GraphicsLayerOwnerLayer.android.kt:271)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:434)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:1000)
    at androidx.compose.ui.node.InnerNodeCoordinator.performDraw(InnerNodeCoordinator.kt:196)
    at androidx.compose.ui.node.NodeCoordinator.drawContainedDrawModifiers(NodeCoordinator.kt:447)
    at androidx.compose.ui.node.NodeCoordinator.draw(NodeCoordinator.kt:439)
    at androidx.compose.ui.node.LayoutNode.draw$ui_release(LayoutNode.kt:1000)
    at androidx.compose.ui.platform.AndroidComposeView.dispatchDraw(AndroidComposeView.android.kt:1564)
    at android.view.View.draw_Original(View.java:23892)
    at android.view.View_Delegate.draw(View_Delegate.java:56)
    at android.view.View.draw(View.java:23862)
    at android.view.View.draw_Original(View.java:23762)
    at android.view.View_Delegate.draw(View_Delegate.java:68)
    at android.view.View.draw(View.java:23540)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw_Original(View.java:23760)
    at android.view.View_Delegate.draw(View_Delegate.java:68)
    at android.view.View.draw(View.java:23540)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw_Original(View.java:23760)
    at android.view.View_Delegate.draw(View_Delegate.java:68)
    at android.view.View.draw(View.java:23540)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw_Original(View.java:23760)
    at android.view.View_Delegate.draw(View_Delegate.java:68)
    at android.view.View.draw(View.java:23540)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw_Original(View.java:23760)
    at android.view.View_Delegate.draw(View_Delegate.java:68)
    at android.view.View.draw(View.java:23540)
    at android.view.ViewGroup.drawChild(ViewGroup.java:4556)
    at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4317)
    at android.view.View.draw_Original(View.java:23892)
    at android.view.View_Delegate.draw(View_Delegate.java:56)
    at android.view.View.draw(View.java:23862)
    at com.android.layoutlib.bridge.impl.RenderSessionImpl.renderAndBuildResult(RenderSessionImpl.java:558)
    at com.android.layoutlib.bridge.impl.RenderSessionImpl.render(RenderSessionImpl.java:445)
    at app.cash.paparazzi.PaparazziSdk$takeSnapshots$3.invoke(PaparazziSdk.kt:289)
    at app.cash.paparazzi.PaparazziSdk$takeSnapshots$3.invoke(PaparazziSdk.kt:288)
    at app.cash.paparazzi.PaparazziSdk.withTime(PaparazziSdk.kt:340)
    at app.cash.paparazzi.PaparazziSdk.takeSnapshots(PaparazziSdk.kt:288)
    at app.cash.paparazzi.PaparazziSdk.snapshot(PaparazziSdk.kt:179)
    at app.cash.paparazzi.PaparazziSdk.snapshot$default(PaparazziSdk.kt:178)
    at app.cash.paparazzi.PaparazziSdk.snapshot(PaparazziSdk.kt:174)
    at app.cash.paparazzi.Paparazzi.snapshot(Paparazzi.kt:103)
    at app.cash.paparazzi.Paparazzi.snapshot$default(Paparazzi.kt:100)
    at MY_PACKAGE_NAME.screenshots.PaparazziScreenshotTest.previewTests(PaparazziScreenshotTest.kt:69)
    at jdk.internal.reflect.GeneratedMethodAccessor22.invoke(Unknown Source)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at com.google.testing.junit.testparameterinjector.PluggableTestRunner$2.evaluate(PluggableTestRunner.java:249)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at app.cash.paparazzi.Paparazzi$apply$1.evaluate(Paparazzi.kt:79)
    at com.google.testing.junit.testparameterinjector.PluggableTestRunner$ContextMethodRule$1.evaluate(PluggableTestRunner.java:433)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.runTestClass(JUnitTestClassExecutor.java:112)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:58)
    at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecutor.execute(JUnitTestClassExecutor.java:40)
    at org.gradle.api.internal.tasks.testing.junit.AbstractJUnitTestClassProcessor.processTestClass(AbstractJUnitTestClassProcessor.java:54)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:53)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:92)
    at jdk.proxy1/jdk.proxy1.$Proxy4.processTestClass(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$2.run(TestWorker.java:183)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:132)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:121)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)

Steps to Reproduce Am struggling to reproduce this outside of my massive project, but it's something to do with reusing the canvas a lot (I can see that mNativeCanvasWrapper is the same across all the screenshots). The tests that are failing here pass if run by themselves, but not if I runn all the paparazzi tests.

Expected behavior Screenshot should be generated, and test should not crash.

Additional information:

TWiStErRob commented 2 weeks ago

You could try to create a repro where you take one test that you know is affected and parameterize it to run 2000 times?

jamesrapadmi commented 2 weeks ago

We've managed to resolve the issue by using hardware acceleration within the stuff we're screenshotting. But I'm still not sure why

geoff-powell commented 2 weeks ago

Do you mind providing a repro project? @jamesrapadmi Glad you fixed your issue but it would be helpful to see if the issue is related to paparazzi.

jamesrapadmi commented 1 week ago

I can try to, but I'll likely struggle to find time to do that.