前言
学习Android音视频肯定绕不开OpenGL ES,Android设备可以运用OpenGL ES来支撑高性能的2D和3D图形,它是一个图形烘托API。
本篇文章记载一下用OpenGL ES展现一张图片的一次实战,看似简略的操作,包含了很多的OpenGL ES的操作,有兴趣可以一起敲一下代码。为便利起见,下面对OpenGL ES简称OpenGL。
搭建UI
平常一般运用ImageView
来加载并展现图片,而在这儿咱们运用的是TextureView
来展现,为便利演示,运用Compose代码如下所示,也可以运用View形式。
咱们需求运用的是TextureView
的SurfaceTexture
,而SurfaceTexture
初始化需求时间,可以传入一个TextureView.SurfaceTextureListener
监听器,在SurfaceTexture
准备好的时候加载图片:
@Composable
fun OpenGLImageScreen(
modifier: Modifier = Modifier
) {
val viewModel = viewModel<OpenGLImageScreenViewModel>()
AndroidView(
factory = { TextureView(it) },
modifier = modifier
) { textureView ->
textureView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
override fun onSurfaceTextureAvailable(
surface: SurfaceTexture, width: Int, height: Int
) {
viewModel.loadLittleCatImage(surface)
}
...
}
}
}
ViewModel
中声明一个loadLittleCatImage
函数,这个函数干了四件事情:
- 在这个函数中运用TextureView的
SurfaceTexture
完结EGL资源的构建。 - 构建Texture2D纹路并加载图片。
- 将Texture2D纹路中的内容制作到
EGLSurface
。 - 将
EGLSurface
中的内容展现到显现设备中。
伪代码如下所示
fun loadLittleCatImage(textureView: TextureView) {
// 构建EGL资源
eglSystem = EglSystem.createSurface(textureView.surfaceTexture!!)
// 进入OpenGL上下文
eglSystem.withCurrent {
// 构建Texture2D纹路并加载图片
imageId = GLUtils.generate2DTextureId(bitmap.width, bitmap.height, byteBuffer)
// 将Texture2D纹路制作到EGLSurface中
shader.drawFrom(imageId, 0, 0, textureView.width, textureView.height)
// 将EGLSurface的内容冲刷到显现设备中
eglSystem.swapBuffers()
}
}
构建EGL资源
首要需求构建的是EGL资源,EGL可以简略理解为OpenGL和设备窗口的中间层。关于EGL具体可以看aserbao大佬的/post/711967…
EGL资源拥有四个重要的构件。
- EGLDisplay:连接到特定显现设备的一个抽象类,是EGL体系中的主要进口点,主要用于控制图形烘托的上下文和资源。
- EGLSurface:图形数据将要烘托的方针外表,可以是屏幕或窗口外表,也可以是帧缓冲区或纹路。
- EGLContext:表明OpenGL的上下文,存储当时图形状态,包含当时着色器、矩阵、纹路和缓冲区等。
一、获取EGLDisplay
可以通过eglGetDisplay
函数获取EGLDisplay
,而这个函数是获取EGLDisplay
的进口点,在创立、装备、管理EGL上下文和资源前需求调用这个函数。
val eglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
二、初始化EGLDisplay
val versions = IntArray(2)
EGL14.eglInitialize(eglDisplay, versions, 0, versions, 1)
传入的四个参数,第一个为刚刚获取的eglDisplay,第二个为存储EGL版别的数组,第三个为指向存储EGL主版别的指针,最终一个为指向存储EGL次版别的指针。
三、挑选装备
private val attributesForSurface = intArrayOf(
EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_DEPTH_SIZE, 0,
EGL14.EGL_STENCIL_SIZE, 0,
EGL14.EGL_RENDERABLE_TYPE, if (glVersion == 2)
EGL14.EGL_OPENGL_ES2_BIT else EGL14.EGL_OPENGL_ES2_BIT or EGLExt.EGL_OPENGL_ES3_BIT_KHR,
EGL_RECORDABLE_ANDROID, 1,
EGL14.EGL_NONE
)
val configs = arrayOfNulls<EGLConfig>(1)
val numConfigs = IntArray(1)
EGL14.eglChooseConfig(
eglDisplay,
attributesForSurface,
configs, 0,
configs.size,
numConfigs, 0
)
eglConfig = configs[0]
根据给定的特点集合中回来EGL装备,该装备可以用于创立GL上下文。
四、创立GL上下文
val attribList = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, glVersion, EGL14.EGL_NONE)
val eglContext = EGL14.eglCreateContext(eglDisplay, eglConfig, EGL14.EGL_NO_CONTEXT, attribList, 0)
eglCreateContext
可以传入刚刚获取到的EGLDisplay
,EGLConfig
,来生成一个EGLContext
。而这个EGLContext
便是一个OpenGL ES上下文。OpenGL上下文是在运用OpenGL API比较要害的资源,用于存储OpenGL API的履行环境,例如当时的烘托状态、矩阵栈、光照参数等。
五、创立EGLSurface
val eglSurface = EGL14.eglCreateWindowSurface(
eglDisplay, eglConfig, surface, intArrayOf(EGL14.EGL_NONE), 0
)
将刚刚获取的EGLDisplay
,EGLConfig
和SurfaceTexture
传进去,获得一个EGLSurface
,而这个EGLSurface
是一个烘托外表,用于把OpenGL图形烘托到窗口体系上。
OpenGL ES
一、进入OpenGL上下文
要运用OpenGL API,首要需求进入OpenGL上下文。
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)
-
eglMakeCurrent
函数的效果是将指定的EGL上下文与烘托外表相关,这样OpenGL API才干效果于该外表,调用此函数之后,任何OpenGL API都会直接效果于该烘托外表。
二、创立GL_TEXTURE_2D纹路
val textures = IntArray(1)
val target = GLES30.GL_TEXTURE_2D
GLES30.glGenTextures(textures.size, textures, 0)
GLES30.glBindTexture(target, textures[0])
GLES30.glTexParameteri(target, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR)
GLES30.glTexParameteri(target, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)
GLES30.glTexParameteri(target, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE)
GLES30.glTexParameteri(target, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE)
val textureId = textures[0]
-
glGenTextures
的效果是生成纹路标识符,用于代表OpenGL纹路方针。这儿就只生成一个好了。 -
glBindTexture
的效果是将一个纹路方针与一个纹路方针相关,并将该纹路方针绑定到当时激活的纹路单元。而纹路方针是指用于纹路的类型,此处生成一个GL_TEXTURE_2D
纹路。纹路单元是一个逻辑单元,它可以一次性绑定多个纹路。
三、加载数据
private val bitmap = BitmapFactory.decodeResource(application.resources, R.drawable.cat)
private val byteBuffer = ByteBuffer.allocateDirect(bitmap.byteCount).apply {
bitmap.copyPixelsToBuffer(this)
rewind()
}
GLES30.glTexImage2D(
GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, bitmap.width, bitmap.height,
0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer
)
-
glTexImage2D
的函数的效果是为纹路方针加载图形数据。依次传入纹路方针、纹路模型层次、图画宽度、图画高度、图画鸿沟宽度、图画数据格式、图画数据类型和图画数据指针。
将纹路中的图画展现出来
一、加载极点着色器和片段着色器
关于这两个着色器的效果可以看看Kenny大佬的文章Android OpenGL ES 2.0 手把手教学
val VERTEX_SHADER_STRING = """
#version 300 es
precision mediump float;
layout(location = 0) in vec4 position;
layout(location = 1) in vec4 inputTextureCoordinate;
uniform mat4 textureTransform;
out vec2 textureCoordinate;
void main()
{
gl_Position = position;
textureCoordinate = (textureTransform * inputTextureCoordinate).xy;
}
""".trimIndent()
val glPositionId = 0
val glAttribTextureCoordinate = 1
val RGB_FRAGMENT_SHADER_CODE = """
#version 300 es
precision mediump float;
in highp vec2 textureCoordinate;
uniform sampler2D inputImageTexture;
out vec4 fragColor;
void main()
{
fragColor = texture(inputImageTexture, textureCoordinate);
}
""".trimIndent()
// Load and compile shader code.
val vertexShaderId = GLES30.glCreateShader(GLES30.GL_VERTEX_SHADER)
val fragmentShaderId = GLES30.glCreateShader(GLES30.GL_FRAGMENT_SHADER)
GLES30.glShaderSource(vertexShaderId, VERTEX_SHADER_STRING)
GLES30.glShaderSource(fragmentShaderId, RGB_FRAGMENT_SHADER_CODE)
GLES30.glCompileShader(vertexShaderId)
GLES30.glCompileShader(fragmentShaderId)
注意:glPositionId
,glAttribTextureCoordinate
两个特点需求和上文极点着色器的代码中的layout
中的值相对应。
layout只能用于输入和输出变量,不能在其他类型中运用。
-
glCreateShader
的效果是创立一个新的着色器方针,回来该着色器方针的标识符。 -
glShaderSource
函数的效果将源代码和着色器方针相关。 -
glCompileShader
的效果是将着色器源代码编译成可履行的代码,如果没有过错,则编译后的代码可以在OpenGL程序中运行。
二、创立OpenGL程序
val programId = GLES30.glCreateProgram()
-
glCreateProgram
函数的效果是创立一个OpenGL程序方针,并回来该方针的标识符,OpenGL程序方针是一组OpenGL着色器方针的集合,共同组成了一个OpenGL应用方针。
三、将着色器附加到OpenGL程序
// Attach shader to GL program.
GLES30.glAttachShader(programId, vertexShaderId)
GLES30.glAttachShader(programId, fragmentShaderId)
四、相关OpenGL程序
GLES30.glLinkProgram(programId)
glLinkProgram
它的效果是将多个着色器方针链接为一个可履行的OpenGL的程序方针。
五、设置OpenGL程序为当时烘托程序
GLES30.glUseProgram(programId)
-
glUseProgram
可以指定任何现已链接的OpenGL程序方针为当时烘托程序。
六、铲除着色器方针
GLES30.glDeleteShader(vertexShaderId)
GLES30.glDeleteShader(fragmentShaderId)
-
glDeleteShader
函数用于删除现已创立的OpenGL着色器方针,将它从内存中开释。
七、应用极点着色器
private fun FloatArray.toFloatBuffer(): FloatBuffer {
return ByteBuffer.allocateDirect(this.size * Float.SIZE_BITS)
.order(ByteOrder.nativeOrder()).asFloatBuffer().put(this)
.apply { position(0) }
}
val FULL_RECTANGLE_BUF = floatArrayOf(
-1.0f, -1.0f, // Bottom left.
1.0f, -1.0f, // Bottom right.
-1.0f, 1.0f, // Top left.
1.0f, 1.0f // Top right.
).toFloatBuffer().apply { position(0) }
val FULL_RECTANGLE_TEX_BUF = floatArrayOf(
0.0f, 0.0f, // Bottom left.
1.0f, 0.0f, // Bottom right.
0.0f, 1.0f, // Top left.
1.0f, 1.0f // Top right.
).toFloatBuffer().apply { position(0) }
// Enable vertex
GLES30.glEnableVertexAttribArray(glPositionId)
GLES30.glEnableVertexAttribArray(glAttribTextureCoordinate)
// Assign render pointer. Always render full screen.
GLES30.glVertexAttribPointer(
glPositionId, 2, GLES30.GL_FLOAT, false, 0, FULL_RECTANGLE_BUF
)
GLES30.glVertexAttribPointer(
glAttribTextureCoordinate, 2, GLES30.GL_FLOAT, false, 0, FULL_RECTANGLE_TEX_BUF
)
-
glEnableVertexAttribArray
函数用于启用极点特点。 -
glVertexAttribPointer
函数的效果是定义极点特点的数据格式,极点特点数组是存储如极点位置、颜色、纹路坐标等的数组。
八、绑定纹路
private val emptyMtx = floatArrayOf(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
)
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId)
// 将纹路单元传入着色器uniform变量
val glInputTextureId = GLES30.glGetUniformLocation(programId, "inputImageTexture");
GLES30.glUniform1i(glInputTextureId, 0)
// 将矩阵传入着色器uniform变量
val glTexTransformId = GLES30.glGetUniformLocation(programId, "textureTransform")
GLES30.glUniformMatrix4fv(glTexTransformId, 1, false, emptyMtx, 0)
-
glActiveTexture
激活纹路单元,在之后的操作可以对该纹路单元进行操作,进行纹路操作前,需求激活一个纹路单元,然后在该单元上绑定纹路才干够完成对纹路的操作。 -
glUniform1i
将当时纹路单元透传进指定的uniform变量中,该变量对应了片段着色器代码中的inputImageTexture
,激活的纹路单元为GLES30.GL_TEXTURE0
,所以此处传0。 -
glUniformMatrix4fv
向一个uniform变量传递一个4*4的矩阵。
九、制作纹路
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
glDrawArrays
按照极点数组的顺序制作图形。
十、解绑纹路
GLES30.glBindTexture(target, 0)
十一、禁用极点着色器
GLES30.glDisableVertexAttribArray(glPositionId)
GLES30.glDisableVertexAttribArray(glAttribTextureCoordinate)
当OpenGL不再运用指定极点的特点数组,可以禁用它,进步烘托性能。
制作图画上屏
这是显现图片的最终一步了,将刚刚制作完的图片出现到屏幕上。
EGL14.eglSwapBuffers(eglDisplay, eglSurface)
这个时候就可以加载并展现相片了!!可是为什么是倒着的呢?并且还被拉长了!等会说一下。
开释资源
别忘了开释资源,由于OpenGL是会占用Native内存的,在运用完之后记住开释资源,避免形成内存泄漏。
// 开释图片资源
byteBuffer.clear()
bitmap.recycle()
// 开释OpenGL程序资源
GLES30.glDeleteProgram(programId)
// 铲除纹路资源
GLES30.glDeleteTextures(1, intArrayOf(textureId), 0)
// 铲除Surface
EGL14.eglDestroySurface(eglDisplay, eglSurface)
eglSurface = EGL14.EGL_NO_SURFACE
// 离开GL上下文
EGL14.eglMakeCurrent(eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)
// 铲除GL上下文资源
EGL14.eglDestroyContext(eglDisplay, eglContext)
eglContext = EGL14.EGL_NO_CONTEXT
// 开释与该线程相关的EGL资源
EGL14.eglReleaseThread()
// 开释EGL与显现设备或窗口体系的链接,开释与EGL相关的资源
EGL14.eglTerminate(eglDisplay)
项目地址
本来想把代码整理一下放上来的,可是太长了,两百多行,估量没人看得下去。这儿放一下项目地址吧。
github.com/MReP1/OpenG…
里边也包含了我对这些GL函数的封装库,个人觉得挺实用的,可以把以上的一堆代码以如下方法完成:
fun loadLittleCatImage(textureView: TextureView) {
// 构建EGL资源
eglSystem = EglSystem.createSurface(textureView.surfaceTexture!!)
// 进入OpenGL上下文
eglSystem.withCurrent {
// 构建Texture2D纹路并加载图片
imageId = GLUtils.generate2DTextureId(bitmap.width, bitmap.height, byteBuffer)
// 将Texture2D纹路制作到EGLSurface中
shader.drawFrom(imageId, 0, 0, textureView.width, textureView.height)
// 将EGLSurface的内容冲刷到显现设备中
eglSystem.swapBuffers()
}
}
总结
不知道这篇的笔记有没有人看完,音视频本来就很难入门,十分单调。笔记中只是对个别API进行简略介绍,并没有具体针对某一个点展开来讲,也没有具体解说为啥要这么干,就现已这样又长又臭了。可是信任我们跟着过程把代码敲一下,最终把图片展现出来,肯定会十分有成就感的。我喜欢用小比如记载知识点,所以之后音视频笔记我也会通过小比如来记载。
其实本次用OpenGL展现一张图片整理下来也就三步,分别是:
- 构建EGL资源
- 进入GL上下文
- 构建纹路资源
- 制作纹路资源
- 展现到显现设备中
对了,图片中的猫咪是倒着的,并且还拉长了,为什么呢?鉴于篇幅原因,我会放在下一篇笔记中记载一下如何将猫咪摆正。
参阅:
极点着色器 vertex shader /post/684490…
EGL /post/684490…
EGL /post/711967…
chat.openai.com/