简介:
紧接上文,本文将用一个不包含顶点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