简介:
紧接上文,本文将用一个不包含顶点shader和片元shader的最小模型讲述如何把通过EGL创建的OpenGL ES环境渲染后的结果进行提取,单纯输出一片铺满视口的红色的像素。
EGL环境创建逻辑:
先看完整代码:
package com.cjztest.glOffscreenProcess.demo0
import android.content.Context
import android.opengl.EGL14
import android.opengl.EGLConfig
import android.opengl.EGLContext
import android.opengl.EGLDisplay
import android.opengl.EGLSurface
import android.util.DisplayMetrics
import android.view.WindowManager
/**以指定的大小产生EGLContext、EGLDisplay对象,并把OpenGL ES渲染管线的内容输出到EGLDisplay中*/
class EGLMaker : Object {
    protected var mEGLDisplay: EGLDisplay? = null
    protected var mEGLConfig: EGLConfig? = null
    protected var mEGLContext: EGLContext? = null
    protected var mEGLSurface: EGLSurface? = null
    protected var mEglStatus: EglStatus = EglStatus.INVALID
    protected var mContext: Context? = null
    private var mRenderer: IRenderer? = null
    protected var mWidth: Int = 0
    protected var mHeight: Int = 0
    var mIsCreated = false
    // EGLConfig参数
    private val mEGLConfigAttrs = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_ALPHA_SIZE, 8,
            EGL14.EGL_DEPTH_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE,
            EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_NONE
    )
    enum class EglStatus {
        INVALID, INITIALIZED, CREATED, CHANGED, DRAW;
        val isValid: Boolean
            get() = this != INVALID
    }
    // 渲染器接口
    interface IRenderer {
        fun onSurfaceCreated()
        fun onSurfaceChanged(width: Int, height: Int)
        fun onDrawFrame()
    }
    fun setRenderer(renderer: IRenderer) {
        mRenderer = renderer
    }
    // EGLContext参数
    private val mEGLContextAttrs = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)
    /**以当前window宽高创建EGLDisplay**/
    constructor(context: Context) {
        mContext = context
        val mWindowManager = mContext!!.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val displayMetrics = DisplayMetrics()
        mWindowManager.defaultDisplay.getRealMetrics(displayMetrics)
        mWidth = displayMetrics.widthPixels
        mHeight = displayMetrics.heightPixels
        createEGLEnv()
    }
   
    /** 以指定宽高创建EGLDisplay**/
    constructor(context: Context, width: Int, height: Int) {
        mContext = context
        mWidth = width
        mHeight = height
        createEGLEnv()
    }
    // 创建EGL环境
    fun createEGLEnv() : Boolean {
        if (mIsCreated) {
            return true
        }
        //1.创建EGLDisplay
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        val versions = IntArray(2)
        EGL14.eglInitialize(mEGLDisplay, versions, 0, versions, 1)
        // 2.创建EGLConfig
        val configs: Array<EGLConfig?> = arrayOfNulls(1)
        val configNum = IntArray(1)
        EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0, 1, configNum, 0) //获取1个EGL配置,因为这个例子只需要一个
        if (configNum[0] > 0) {
            mEGLConfig = configs[0]
        } else {
            return false
        }
        // 3.创建EGLContext
        if (mEGLConfig != null) {
            mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0)
        }
        // 4.创建EGLSurface
        if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {
            val eglSurfaceAttrs = intArrayOf(EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE) //以传入的宽高作为eglSurface
            mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0)
        }
        // 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay),但这个EGLDisplay的内容没有和View绑定,所以并不会直接显示
        if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {
            EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)
            mEglStatus = EglStatus.INITIALIZED
        }
        mIsCreated = true
        return true
    }
    // 销毁EGL环境
    fun destroyEGLEnv() {
        if (!mIsCreated) {
            return
        }
        // 与显示设备解绑
        EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)
        // 销毁 EGLSurface
        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface)
        // 销毁EGLContext
        EGL14.eglDestroyContext(mEGLDisplay, mEGLContext)
        // 销毁EGLDisplay(显示设备)
        EGL14.eglTerminate(mEGLDisplay)
        mEGLContext = null
        mEGLSurface = null
        mEGLDisplay = null
        mIsCreated = false
    }
    override fun finalize() {
        super.finalize()
        destroyEGLEnv()
    }
    // 请求渲染
    fun requestRender() {
        if (!mEglStatus.isValid) {
            return
        }
        if (mEglStatus == EglStatus.INITIALIZED) {
            mRenderer?.onSurfaceCreated()
            mEglStatus = EglStatus.CREATED
        }
        if (mEglStatus == EglStatus.CREATED) {
            mRenderer?.onSurfaceChanged(mWidth, mHeight)
            mEglStatus = EglStatus.CHANGED
        }
        if (mEglStatus == EglStatus.CHANGED || mEglStatus == EglStatus.DRAW) {
            mRenderer?.onDrawFrame()
            mEglStatus = EglStatus.DRAW
        }
    }
    fun getWidth() : Int {
        return mWidth
    }
    fun getHeight() : Int {
        return mHeight
    }
    
}
类中核心逻辑在createEGLEnv方法中,负责根据制定的宽高、配置创建EGL环境。
在当前线程把EGL环境创建后之后,就可以在当前线程中调用OpenGLES API进行渲染了。为了方便灵活地自定义GL接口的调用逻辑,和EGL环境的创建逻辑进行解耦,在这里写了一个回调接口IRenderer:
    // 渲染器接口
    interface IRenderer {
        fun onSurfaceCreated()
        fun onSurfaceChanged(width: Int, height: Int)
        fun onDrawFrame()
    }
类中的requestRender方法其实就是在判断EGL环境创建就绪后,回调里面的自定义的onDrawFrame实现调用用户自定义的GL绘制逻辑。但实际上,在本线程中,只要你确认EGL已经创建成功,你可以在任意地方调用GL API,只是这样写可以用EGLMaker顺便帮自定义逻辑做一次EGL环境是否就绪的判断。
最小绘制测试逻辑:
这里仅仅使用GLES30的API,做一次红色的清屏操作,然后使用glReadPixels获取里面的像素,然后显示到ImageView中,然后显示的是红色画面即代表EGL创建成功,而且OpenGL API能被成功调用。
测试逻辑搭建:
测试渲染逻辑:
由于只是渲染一个红色清屏的画面,所以之需要调用glClearColor当前渲染上下文状态机的清屏颜色为红色,并调用glClear并使用GL_COLOR_BUFFER_BIT参数对FrameBuffer的颜色组件进行清屏即可,足够简单,甚至不需要启用GL_DEPTH_BUFFER_BIT。
    override fun onDrawFrame() {
        GLES30.glClearColor(1f, 0f, 0f, 1f) //设定清理颜色为红色
        // 将颜色缓存区设置为预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        //试试读出里面的像素,如果有颜色就代表这一阶段成功了
        if (mWidth != null && mHeight != null) {
            val byteBuffer = ByteBuffer.allocate(mWidth!! * mHeight!! * 4)
            GLES30.glReadPixels(0, 0, mWidth!!, mHeight!!, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer)
            if (mContentBmp == null || mContentBmp?.width != mWidth!! || mContentBmp?.height != mWidth!!) {
                mContentBmp = Bitmap.createBitmap(mWidth!!, mHeight!!, Bitmap.Config.ARGB_8888)
            }
            mContentBmp?.copyPixelsFromBuffer(byteBuffer)
        }
    }
测试Activity
package com.cjztest.glOffscreenProcess.demo0
import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
class OffScreenGLDemoActivity : Activity() {
    private lateinit var mEGL: EGLMaker
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val imageView = ImageView(this)
        val renderer = Renderer()
        mEGL = EGLMaker(this, 300, 300)
        mEGL.setRenderer(renderer)
        mEGL.requestRender()
        imageView.setImageBitmap(renderer.getBitmap())
        setContentView(imageView)
    }
}
最终结果:
原本没有颜色的ImageView正确显示红色。

结尾:
GL上下文生效后,自然不止是能绘制这么简单的画面,这个只是最简单的实例。实际可以按照以往正常写OpenGL代码的方式,写好顶点shader和片元shader编译后运行对应的shader program,以及执行纹理贴图逻辑、顶点读取与塞入缓冲的逻辑等实现复杂的画面操作。本文只是为了简化阅读难度,故意不引入更多复杂的操作,所以仅仅只是做一次红色清屏而已。
github地址:
learnopengl/app/src/main/java/com/cjztest/glOffscreenProcess/demo0 at main · cjzjolly/learnopengl · GitHub


![[Godot3.3.3] – 人物死亡动画 part-2](https://i-blog.csdnimg.cn/direct/36a50a547dba46d9be44585bc8f789bd.png)
















