Sergey Y.
04/09/2024, 11:28 AMGraphicsLayer API
and found it quite helpful. It allowed me to implement a rendering optimization trick.
However, I've encountered some challenges that I'm hoping to get some advice on.
Specifically, I'm looking for a way to create an android.graphics.Picture
from a graphics layer. For all the details, please check the full thread π§΅.
GraphicsLayerPicture(val graphicsLayer: GraphicsLayer) : Picture()
Sergey Y.
04/09/2024, 11:29 AMclass GraphicsLayerPicture(val graphicsLayer: GraphicsLayer) : Picture()
from the Compose sources. Unfortunately, the method graphicsLayer.draw(androidx.compose.ui.graphics.Canvas(canvas), null)
is an internal API, and I can't call it from my code.
Dealing with a baked hardware Bitmap is somewhat inconvenient for my needs. I'm looking to obtain it as a GL_TEXTURE_EXTERNAL_OES
texture target to perform further postprocessing in my fragment shader.
Sadly, accessing the bitmap's HardwareBuffer
(bitmap.hardwareBuffer) is only possible from API 31 onwards, and on the NDK side from API 30, even though hardware bitmaps were introduced as early as API 26:
val image: EGLImageKHR? = androidx.opengl.EGLExt.Companion.eglCreateImageFromHardwareBuffer(display, bitmap.hardwareBuffer) EGLExt.glEGLImageTargetTexture2DOES(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, image!!)
On the other hand, working with android.graphics.Picture
offers much broader API availability, and I can relatively easily turn it into a GL_TEXTURE_EXTERNAL_OES
texture target (pseudocode):
val picture: Picture = ...
val surfaceTexture: SurfaceTexture = SurfaceTexture(texId)
surfaceTexture.setDefaultBufferSize(viewportWidth, viewportHeight)
val sf = Surface(surfaceTexture)
val c = sf.lockHardwareCanvas()
c.drawPicture(picture)
surfaceTexture.updateTexImage()
sf.unlockCanvasAndPost(c)
sf.release()
surfaceTexture.release()
I'm aware of the documentation describing how to write the contents of a composable to a picture, but it's not as convenient as using the GraphicsLayer API.
Maybe I'm looking in the wrong direction? Could someone hint at something I might be missing when working with a hardware bitmap to access it from a shader without copying data to the CPU on lower APIs?Sergey Y.
04/09/2024, 11:39 AMAndrey Kulikov
04/09/2024, 11:54 AMromainguy
04/09/2024, 4:51 PMNader Jawad
04/09/2024, 5:02 PMSergey Y.
04/09/2024, 5:06 PMSo on newer platform versions (Android Q+) you can skip additional copies of bitmaps.I would like to skip additional copies and before Q by rendering Picture into a Surface or so. Is it possible?
Sergey Y.
04/09/2024, 5:07 PMSergey Y.
04/09/2024, 5:09 PM. You can always draw the GraphicsLayer itself into a canvas obtained from a Surface backed by a SurfaceTexture as well without the need for a PictureAre there any examples of this available? What I saw in a doc, to use Picture for recodring canvas commands
Nader Jawad
04/09/2024, 5:09 PMSergey Y.
04/09/2024, 5:14 PMSergey Y.
04/09/2024, 5:15 PMSergey Y.
04/09/2024, 5:24 PMNader Jawad
04/09/2024, 5:24 PMfun drawGraphicsLayerToSurface(
layer: GraphicsLayer,
texId: Int,
density: Density,
layoutDirection: LayoutDirection
) {
val size = layer.size
val surface = Surface(SurfaceTexture(texId).apply {
setDefaultBufferSize(size.width, size.height)
})
val hardwareCanvas = androidx.compose.ui.graphics.Canvas(surface.lockHardwareCanvas())
CanvasDrawScope().draw(density, layoutDirection, hardwareCanvas, size.toSize()) {
drawLayer(layer)
}
}
Sergey Y.
04/09/2024, 5:24 PM```CanvasDrawScope().draw(density, layoutDirection, hardwareCanvas, size.toSize()) {
drawLayer(layer)
}```
Nader Jawad
04/09/2024, 5:25 PMCanvasDrawScope
to create a DrawScope
from the canvas returned from lockHardwareCanvas
and draw the GraphicsLayer within this scopeSergey Y.
04/09/2024, 5:25 PMSergey Y.
04/09/2024, 5:26 PMSergey Y.
04/09/2024, 5:27 PMNader Jawad
04/09/2024, 5:27 PMNader Jawad
04/09/2024, 5:29 PMFrameBuffer
API to handle the GL logic of importing a HardwareBuffer into GL and making it current for rendering:
https://developer.android.com/reference/androidx/graphics/opengl/FrameBuffer
I noticed in your code snippet it looked like you were already using the graphics-core Androidx library so you also would have access to this API as well. There's nothing wrong with handling the EGLClientBuffer logic yourself, but it's already handled within FrameBuffer tooNader Jawad
04/09/2024, 5:30 PMSergey Y.
04/09/2024, 5:32 PMhttps://files.slack.com/files-pri/T09229ZC6-F06T92EE6NA/khr_texture_load.pngβΎ
Sergey Y.
04/09/2024, 5:36 PMNader Jawad
04/09/2024, 5:36 PMSurfaceTexture
usageNader Jawad
04/09/2024, 5:36 PMSurface#lockHardwareCanvas
Sergey Y.
04/09/2024, 5:37 PMNader Jawad
04/09/2024, 5:37 PMSergey Y.
04/09/2024, 5:37 PMSergey Y.
04/09/2024, 5:38 PMNader Jawad
04/09/2024, 5:39 PMNader Jawad
04/09/2024, 5:39 PMSergey Y.
04/09/2024, 5:40 PMNader Jawad
04/09/2024, 5:40 PMSurfaceControlCompat
+ SurfaceControlCompat.Transaction#setBuffer
to show it on screenNader Jawad
04/09/2024, 5:40 PMSergey Y.
04/09/2024, 5:41 PMNader Jawad
04/09/2024, 5:41 PMBitmap.wrapHardwareBuffer
otherwise you would need to do a copy of your destination surface back to a Bitmap for older API levelsSergey Y.
04/09/2024, 5:42 PMSergey Y.
04/09/2024, 5:42 PMNader Jawad
04/09/2024, 5:43 PMGraphicsLayer
API to help keep everything in a hardware accelerated code path all the time πSergey Y.
04/09/2024, 5:55 PMYes, that's exactly my goal here with what I'm trying to implement. It's crucial to keep everything on the hardware path as much as possible for speed. I'm truly amazed by the flexibility of Compose UI and Android as a whole. I've already accomplished so much without even needing to dive into NDK. Incredible! π€API to help keep everything in a hardware accelerated code path all the timeGraphicsLayer
Nader Jawad
04/09/2024, 5:57 PMSergey Y.
04/09/2024, 5:57 PMNader Jawad
04/09/2024, 5:58 PMTravis Griggs
04/10/2024, 9:20 PMSergey Y.
04/10/2024, 10:54 PMNader Jawad
04/12/2024, 8:45 PM