Sergey Y.
05/20/2024, 8:07 PMGraphicsLayer
works in Jetpack Compose.
I thought I understood it before, but a recent discovery has made me question my understanding 🤔.
Details in the thread 🧵Sergey Y.
05/20/2024, 8:08 PMLazyColumn
with a Modifier.drawWithContent
that records its content into a GraphicsLayer
. Then, I draw this GraphicsLayer
in a loop onto an AndroidExternalSurface
(backed by a SurfaceView
) canvas (see the attached code for reference).
What I've found is that Modifier.drawWithContent
is not called every time I scroll the lazy list. This might be due to the lazy nature of LazyColumn
and how it reuses the composable widgets under the hood, idk. As a result, the GraphicsLayer
only records the initial layout.
However, every time I scroll the list, the continuous drawing loop in my AndroidExternalSurface
correctly updates the picture on the canvas, and I'm not sure why. It's puzzling because no new graphicsLayer.record { }
is happening during scrolling(logcat is alive).
If anyone has an explanation for this behavior, I would be glad to know. I'm curious to understand how the GraphicsLayer
is able to update the canvas without being recorded again during scrolling.
Thanks in advance for any insights!Sergey Y.
05/20/2024, 8:08 PMSergey Y.
05/20/2024, 8:10 PMZach Klippenstein (he/him) [MOD]
05/20/2024, 8:13 PMI thought I understood it before, but a recent discovery has made me question my understandingThis is me every time I think I understand graphics and then talk to Nader.
Sergey Y.
05/20/2024, 8:18 PMGraphicsLayer
, I needed to record into it each time the UI updates. It seemed logical to me, and it kind of worked with regular scrollable columns and rows. They invoke Modifier.drawWithSomething
each time I interact with them.
However, with the lazy list, it's a different situation. I'm not sure why it works the way it does. I expected that I would need to record into the GraphicsLayer
whenever the UI changes, but it seems to update correctly even without explicit recording during scrolling.
🤯Zach Klippenstein (he/him) [MOD]
05/20/2024, 8:20 PMSergey Y.
05/20/2024, 8:22 PMNader Jawad
05/20/2024, 9:14 PMSergey Y.
05/20/2024, 9:20 PMNader Jawad
05/20/2024, 9:22 PMNader Jawad
05/20/2024, 9:23 PMSergey Y.
05/20/2024, 9:25 PMLayout { measurable ->
val placeable = // measure
layout(width, height) {
placeable.placeWithLayer(x, y, myGraphicsLayer)
}
}
Instead of using draw callbacks, would this approach work just as well? It seems like it might be a cleaner way to handle the GraphicsLayer without the need for explicit drawing calls.Nader Jawad
05/20/2024, 9:26 PMplaceWithLayer
is mostly for authors of Layout composables in order to provide isolation boundaries for child composables.Nader Jawad
05/20/2024, 9:26 PMSergey Y.
05/20/2024, 9:30 PMSergey Y.
05/20/2024, 9:32 PMNader Jawad
05/20/2024, 9:32 PMNader Jawad
05/20/2024, 9:33 PMNader Jawad
05/20/2024, 9:35 PMSergey Y.
05/20/2024, 9:36 PMSergey Y.
05/20/2024, 9:37 PMSergey Y.
05/20/2024, 9:38 PMSergey Y.
05/20/2024, 9:41 PMSergey Y.
05/20/2024, 9:54 PMSergey Y.
05/20/2024, 9:58 PMSergey Y.
05/20/2024, 10:03 PMSergey Y.
05/20/2024, 10:13 PMNader Jawad
05/20/2024, 10:14 PMSergey Y.
05/20/2024, 10:26 PMNader Jawad
05/20/2024, 10:30 PMSergey Y.
05/20/2024, 11:03 PMAndroidExternalSurface
only flickers when clipping is applied through Modifier.graphicsLayer
, which is used by all of the shadow
, clip
, and clipToBounds
modifiers.
However, when I clip directly via canvas.clip*
, it does not flicker!
I'm wondering what the difference is between these two clipping methods 🤔.
I still don't know how layout alignment is involved here, but nevertheless, this fix is suitable for me. Updated the issue information.
Thank you so much for your assistance! :)Sergey Y.
05/21/2024, 10:54 PMval hwCanvas = layerSurface.lockHardwareCanvas()
hwCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
drawingScope.draw(density, LayoutDirection.Ltr, Canvas(hwCanvas), sizeDec) {
clipRect(0f, 0f, areaWidth, areaHeight) {
drawLayer(graphicsLayer)
}
}
layerSurface.unlockCanvasAndPost(hwCanvas)
vs
val hwCanvas = layerSurface.lockHardwareCanvas()
hwCanvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
drawingScope.draw(density, LayoutDirection.Ltr, Canvas(hwCanvas), sizeDec) {
drawLayer(graphicsLayer)
}
layerSurface.unlockCanvasAndPost(hwCanvas)
Sergey Y.
05/21/2024, 11:13 PMGraphicsLayer
does not behave like pointers to display lists on Android 6. I must explicitly record them at every drawing call in order to update the texture. I suppose this is because `RenderNode`s became public API only in Android 10.
So, I was wondering, can I safely assume and build my recording logic based on this behavior for SDK levels lower than 29? Or is there a possibility that GraphicsLayer
might act like mutable display lists on some other older SDKs?
I noticed that you're trying to bind to the private RenderNode
using reflection, but with some exceptions. It made me curious about the consistency of GraphicsLayer's behavior across different Android versions.Sergey Y.
05/22/2024, 3:32 PMSurface
jumping to (0,0)
and back. In the demo, I was using a LazyColumn
, and I started noticing that the jumping was occurring at suspicious intervals. The AndroidExternalSurface
is not a part of the LazyColumn
items; it is drawn above it in a separate composable widget. This led me to think that the LazyColumn
might be causing the issue. I replaced it with a plain scrollable Column
, keeping everything else the same (including the AndroidExternalSurface
with offset), and the flickering disappeared.
Upon further investigation, I found that the flickering (when the surface picture jumps to (0,0)
and back) also happens when I add or remove layouts from the composition(and as LazyList
does). Therefore, I suppose it occurs during the re-layout of the composable tree, but I have no idea why it jumps to (0,0)
.
It's important to note that the physical bounds of the AndroidExternalSurface
stay in the correct place and do not move; only the surface's internal content appears to move visually (I don't know how else to describe it).
In the video, you can see the case with a scrollable Column
. While I am scrolling through it, the SurfaceView
updates correctly. However, when I click on the image to open the fullscreen view, which is another composable added to the layout by an "if"
condition, you can notice the original blurred rectangle flickering just before the image opens.
I have updated the issue with these findings. https://issuetracker.google.com/issues/341537869
I hope this information helps with further investigation and fixing the issue. 🙂Zach Klippenstein (he/him) [MOD]
05/22/2024, 5:13 PMSergey Y.
05/22/2024, 5:14 PMSergey Y.
05/22/2024, 5:17 PMZach Klippenstein (he/him) [MOD]
05/22/2024, 5:25 PMSergey Y.
07/18/2024, 11:45 PMandroidx.graphics.opengl.GLRenderer
How to initialize it properly with sRGB EGL configuration. Details in the ticket https://issuetracker.google.com/issues/354005994
Thanks in advance.