如何使用 OpenGL 在 Android 中离屏渲染位图?

How to render Bitmap off-screen in Android using OpenGL?

我需要渲染位图而不在屏幕上显示它。为此,我使用 EGL14 创建 OpenGL 上下文,如 this answer 中所述。然后我使用 GLES20.glReadPixels 将 OpenGL 表面保存为位图。但由于某种原因,它没有按预期呈现,只是透明的。

import android.graphics.Bitmap
import android.opengl.*
import android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION
import java.nio.ByteBuffer


class Renderer {

    private lateinit var display: EGLDisplay
    private lateinit var surface: EGLSurface
    private lateinit var eglContext: EGLContext

    fun draw() {
        // Just a stub that fills the bitmap with red color
        GLES20.glClearColor(1f, 0f, 0f, 1f)
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
    }

    fun saveBitmap(): Bitmap {
        val width = 320
        val height = 240
        val mPixelBuf = ByteBuffer.allocate(width * height * 4)
        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf)
        return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    }

    private fun initializeEglContext() {
        display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        if (display == EGL14.EGL_NO_DISPLAY) {
            throw RuntimeException("eglGetDisplay failed ${EGL14.eglGetError()}")
        }

        val versions = IntArray(2)
        if (!EGL14.eglInitialize(display, versions, 0, versions, 1)) {
            throw RuntimeException("eglInitialize failed ${EGL14.eglGetError()}")
        }

        val configAttr = intArrayOf(
            EGL14.EGL_COLOR_BUFFER_TYPE, EGL14.EGL_RGB_BUFFER,
            EGL14.EGL_LEVEL, 0,
            EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
            EGL14.EGL_NONE
        )
        val configs: Array<EGLConfig?> = arrayOfNulls(1)
        val numConfig = IntArray(1)
        EGL14.eglChooseConfig(
            display, configAttr, 0,
            configs, 0, 1, numConfig, 0
        )
        if (numConfig[0] == 0) {
            throw RuntimeException("No configs found")
        }
        val config: EGLConfig? = configs[0]

        val surfAttr = intArrayOf(
            EGL14.EGL_WIDTH, 320,
            EGL14.EGL_HEIGHT, 240,
            EGL14.EGL_NONE
        )
        surface = EGL14.eglCreatePbufferSurface(display, config, surfAttr, 0)

        val contextAttrib = intArrayOf(
            EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL14.EGL_NONE
        )
        eglContext = EGL14.eglCreateContext(display, config, EGL14.EGL_NO_CONTEXT, contextAttrib, 0)

        EGL14.eglMakeCurrent(display, surface, surface, eglContext)
    }

    fun destroy() {
        EGL14.eglMakeCurrent(display, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
            EGL14.EGL_NO_CONTEXT)
        EGL14.eglDestroySurface(display, surface)
        EGL14.eglDestroyContext(display, eglContext)
        EGL14.eglTerminate(display)
    }
}

我是这样使用的:

val renderer = Renderer()
renderer.initializeEglContext()
renderer.draw()
val bitmap = renderer.saveBitmap()
renderer.destroy()

代码运行没有任何错误。我检查了上下文是否已成功创建。例如 GLES20.glCreateProgram 按预期工作并且 returns 是一个有效的 id。我得到的唯一警告是

W/OpenGLRenderer: Failed to choose config with EGL_SWAP_BEHAVIOR_PRESERVED, retrying without...

但我不确定它是否会以任何方式影响结果。 然而bitmap没有填充颜色并且是透明的:

val color = bitmap[0, 0]
Log.d("Main", "onCreate: ${Color.valueOf(color)}")
Color(0.0, 0.0, 0.0, 0.0, sRGB IEC61966-2.1)

我想我漏掉了什么,但我不知道是什么。如何让它真正呈现出来?

必须将像素缓冲区复制到位图:

val mPixelBuf bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
bitmap.copyPixelsFromBuffer(mPixelBuf)
return bitmap