EGL环境
抽象工厂模式定义EGL环境接口
kotlin
interface EGLComponentFactory {
fun createEGL(): EGL10
fun createEGLDisplay(egl: EGL10): EGLDisplay
fun createEGLConfig(egl: EGL10, display: EGLDisplay): EGLConfig
fun createEGLContext(egl: EGL10, display: EGLDisplay, config: EGLConfig): EGLContext
fun createEGLSurface(
egl: EGL10,
display: EGLDisplay,
config: EGLConfig,
surface: Surface
): EGLSurface
}
EGL环境接口的具体实现
kotlin
class DefaultEGLFactory : EGLComponentFactory {
override fun createEGL(): EGL10 = EGLContext.getEGL() as EGL10
override fun createEGLDisplay(egl: EGL10): EGLDisplay {
val eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
if (eglDisplay == EGL10.EGL_NO_DISPLAY) {
throw RuntimeException("eglGetDisplay failed")
}
val version = IntArray(2)
if (!egl.eglInitialize(eglDisplay, version)) {
throw RuntimeException("eglInitialize failed")
}
return eglDisplay
}
override fun createEGLConfig(egl: EGL10, display: EGLDisplay): EGLConfig {
val attributes = intArrayOf(
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 8,
EGL_STENCIL_SIZE, 8,
EGL_NONE
)
val numConfigs = IntArray(1)
egl.eglChooseConfig(display, attributes, null, 0, numConfigs)
if (numConfigs[0] <= 0) {
throw RuntimeException("No matching EGL configs")
}
val configs = arrayOfNulls<EGLConfig>(numConfigs[0])
egl.eglChooseConfig(display, attributes, configs, numConfigs.size, numConfigs)
return configs[0] ?: throw RuntimeException("No suitable EGL config found")
}
override fun createEGLContext(egl: EGL10, display: EGLDisplay, config: EGLConfig): EGLContext {
val contextAttrs = intArrayOf(
EGL_CONTEXT_CLIENT_VERSION, 3,
EGL_NONE
)
val eglContext = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, contextAttrs)
if (eglContext == EGL10.EGL_NO_CONTEXT) {
throw RuntimeException("eglCreateContext failed")
}
return eglContext
}
override fun createEGLSurface(
egl: EGL10,
display: EGLDisplay,
config: EGLConfig,
surface: Surface
): EGLSurface {
val eglSurface = egl.eglCreateWindowSurface(display, config, surface, null)
if (eglSurface == EGL10.EGL_NO_SURFACE) {
throw RuntimeException("eglCreateWindowSurface failed")
}
return eglSurface
}
}
构造者实现EGL环境
kotlin
class EGLEnvironmentBuilder(private val factory: EGLComponentFactory = DefaultEGLFactory()) {
private lateinit var mEGL: EGL10
private lateinit var mEGLDisplay: EGLDisplay
private lateinit var mEGLConfig: EGLConfig
private lateinit var mEGLContext: EGLContext
private lateinit var mEGLSurface: EGLSurface
fun build(surface: Surface): EGLEnvironment {
mEGL = factory.createEGL()
mEGLDisplay = factory.createEGLDisplay(mEGL)
mEGLConfig = factory.createEGLConfig(mEGL, mEGLDisplay)
mEGLContext = factory.createEGLContext(mEGL, mEGLDisplay, mEGLConfig)
mEGLSurface = factory.createEGLSurface(mEGL, mEGLDisplay, mEGLConfig, surface)
if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw RuntimeException("eglMakeCurrent failed")
}
return EGLEnvironment(mEGL, mEGLDisplay, mEGLContext, mEGLSurface)
}
// EGL环境类
inner class EGLEnvironment(
val egl: EGL10,
val display: EGLDisplay,
val context: EGLContext,
val surface: EGLSurface
) {
fun swapBuffers() {
if (!egl.eglSwapBuffers(display, surface)) {
throw RuntimeException("eglSwapBuffers failed")
}
}
fun release() {
egl.eglMakeCurrent(
display,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT
)
egl.eglDestroySurface(display, surface)
egl.eglDestroyContext(display, context)
egl.eglTerminate(display)
}
}
}
渲染数据
渲染数据接口定义
kotlin
/**
* OpenGL渲染数据接口
*/
interface OpenGLData {
/**
* Surface创建时调用,用于初始化OpenGL资源
*/
fun onSurfaceCreated()
/**
* Surface尺寸变化时调用,用于更新视口
*/
fun onSurfaceChanged(width: Int, height: Int)
/**
* 每帧渲染时调用,执行实际的绘制操作
*/
fun onDrawFrame()
/**
* Surface销毁时调用,可以标记资源需要重新初始化
*/
fun onSurfaceDestroyed()
}
渲染数据接口实现
kotlin
/**
* OpenGL渲染数据默认实现类
* 提供OpenGL渲染所需的基本数据结构和操作方法
*/
class BaseOpenGLData(private val context: Context) : OpenGLData {
private val NO_OFFSET = 0
private val VERTEX_POS_DATA_SIZE = 3
private val TEXTURE_POS_DATA_SIZE = 2
private val STRIDE = (VERTEX_POS_DATA_SIZE + TEXTURE_POS_DATA_SIZE) * 4 // 每个顶点的总字节数
// 着色器程序ID
private var mProgram: Int = -1
// 顶点和纹理坐标合并在一个数组中
// 格式:x, y, z, u, v (顶点坐标后跟纹理坐标)
val vertexData = floatArrayOf(
// 顶点坐标 // 纹理坐标
-1.0f, 1.0f, 0.0f, 0.0f, 1.0f, // 左上
-1.0f, -1.0f, 0.0f, 0.0f, 0.0f, // 左下
1.0f, 1.0f, 0.0f, 1.0f, 1.0f, // 右上
1.0f, -1.0f, 0.0f, 1.0f, 0.0f // 右下
)
val vertexDataBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(vertexData)
.position(NO_OFFSET)
val index = shortArrayOf(
0, 1, 2, // 第一个三角形
1, 3, 2 // 第二个三角形
)
val indexBuffer = ByteBuffer.allocateDirect(index.size * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(index)
.position(NO_OFFSET)
// VAO(Vertex Array Object), 顶点数组对象, 用于存储VBO
private var mVAO = IntArray(1)
// VBO(Vertex Buffer Object), 顶点缓冲对象,用于存储顶点数据和纹理数据
private var mVBO = IntArray(1) // 只需要一个VBO
// IBO(Index Buffer Object), 索引缓冲对象,用于存储顶点索引数据
private var mIBO = IntArray(1)
// 纹理ID
private var mTextureID = IntArray(1)
// 变换矩阵
private var mMVPMatrix = FloatArray(16) // 最终变换矩阵
private val mProjectionMatrix = FloatArray(16) // 投影矩阵
private val mViewMatrix = FloatArray(16) // 视图矩阵
private val mModelMatrix = FloatArray(16) // 模型矩阵
// 视口尺寸
private var mWidth = 0
private var mHeight = 0
/**
* Surface创建时调用,用于初始化OpenGL资源
*/
override fun onSurfaceCreated() {
initTexture()
initShaderProgram()
initVertexBuffer()
resetMatrix()
}
/**
* Surface尺寸变化时调用,用于更新视口
*/
override fun onSurfaceChanged(width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
mWidth = width
mHeight = height
computeMVPMatrix()
}
/**
* 每帧渲染时调用,执行实际的绘制操作
*/
override fun onDrawFrame() {
clearBuffers()
draw()
}
/**
* Surface销毁时调用,可以标记资源需要重新初始化
*/
override fun onSurfaceDestroyed() {
release()
}
/**
* 初始化着色器程序
*/
private fun initShaderProgram() {
val vertexShaderCode = """#version 300 es
uniform mat4 uMVPMatrix; // 变换矩阵
in vec4 aPosition; // 顶点坐标
in vec2 aTexCoord; // 纹理坐标
out vec2 vTexCoord;
void main() {
// 输出顶点坐标和纹理坐标到片段着色器
gl_Position = uMVPMatrix * aPosition;
vTexCoord = aTexCoord;
}""".trimIndent()
val fragmentShaderCode = """#version 300 es
precision mediump float;
uniform sampler2D uTexture_0;
in vec2 vTexCoord;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture_0, vTexCoord);
}""".trimIndent()
// 加载顶点着色器和片段着色器, 并创建着色器程序
val vertexShader = OpenGLUtils.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader = OpenGLUtils.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
mProgram = GLES30.glCreateProgram()
GLES30.glAttachShader(mProgram, vertexShader)
GLES30.glAttachShader(mProgram, fragmentShader)
GLES30.glLinkProgram(mProgram)
// 删除着色器对象
GLES30.glDeleteShader(vertexShader)
GLES30.glDeleteShader(fragmentShader)
}
/**
* 初始化顶点缓冲区
*/
private fun initVertexBuffer() {
// 绑定VAO
GLES30.glGenVertexArrays(mVAO.size, mVAO, NO_OFFSET)
GLES30.glBindVertexArray(mVAO[0])
// 绑定VBO - 只需要一个VBO存储所有数据
GLES30.glGenBuffers(mVBO.size, mVBO, NO_OFFSET)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, mVBO[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertexData.size * 4,
vertexDataBuffer,
GLES30.GL_STATIC_DRAW
)
// 设置顶点属性指针 - 顶点坐标
val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
GLES30.glEnableVertexAttribArray(positionHandle)
GLES30.glVertexAttribPointer(
positionHandle,
VERTEX_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
STRIDE, // 步长,每个顶点5个float (x,y,z,u,v)
NO_OFFSET // 偏移量,位置数据在前
)
// 设置顶点属性指针 - 纹理坐标
val textureHandle = GLES30.glGetAttribLocation(mProgram, "aTexCoord")
GLES30.glEnableVertexAttribArray(textureHandle)
GLES30.glVertexAttribPointer(
textureHandle,
TEXTURE_POS_DATA_SIZE,
GLES30.GL_FLOAT,
false,
STRIDE, // 步长,每个顶点5个float (x,y,z,u,v)
VERTEX_POS_DATA_SIZE * 4 // 偏移量,纹理数据在位置数据之后
)
// 绑定IBO
GLES30.glGenBuffers(mIBO.size, mIBO, NO_OFFSET)
// 绑定索引缓冲区数据到IBO[0]
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, mIBO[0])
GLES30.glBufferData(
GLES30.GL_ELEMENT_ARRAY_BUFFER,
index.size * 2,
indexBuffer,
GLES30.GL_STATIC_DRAW
)
// 解绑VAO
GLES30.glBindVertexArray(0)
// 解绑VBO和IBO
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0)
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, 0)
}
/**
* 初始化纹理
*/
private fun initTexture() {
val textureId = IntArray(1)
// 生成纹理
GLES30.glGenTextures(1, textureId, 0)
// 绑定纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureId[0])
// 设置纹理参数
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MIN_FILTER,
GLES30.GL_LINEAR
) // 纹理缩小时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_MAG_FILTER,
GLES30.GL_LINEAR
) // 纹理放大时使用线性插值
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_S,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
GLES30.glTexParameteri(
GLES30.GL_TEXTURE_2D,
GLES30.GL_TEXTURE_WRAP_T,
GLES30.GL_CLAMP_TO_EDGE
) // 纹理坐标超出范围时,超出部分使用最边缘像素进行填充
// 加载图片
val options = BitmapFactory.Options().apply {
inScaled = false // 不进行缩放
}
val bitmap = BitmapFactory.decodeResource(context.resources, R.drawable.picture, options)
// 将图片数据加载到纹理中
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D, 0, bitmap, 0)
// 释放资源
bitmap.recycle()
// 解绑纹理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
Log.e(
"yang",
"loadTexture: 纹理加载成功 bitmap.width:${bitmap.width} bitmap.height:${bitmap.height}"
)
mTextureID[0] = textureId[0]
}
/**
* 重置矩阵
*/
private fun resetMatrix() {
Matrix.setIdentityM(mProjectionMatrix, NO_OFFSET)
Matrix.setIdentityM(mViewMatrix, NO_OFFSET)
Matrix.setIdentityM(mModelMatrix, NO_OFFSET)
Matrix.setIdentityM(mMVPMatrix, NO_OFFSET)
}
/**
* 计算最终变换矩阵
*/
private fun computeMVPMatrix() {
val isLandscape = mWidth > mHeight
val viewPortRatio = if (isLandscape) mWidth.toFloat() / mHeight else mHeight.toFloat() / mWidth
// 计算包围图片的球半径
val radius = sqrt(1f + viewPortRatio * viewPortRatio)
val near = 0.1f
val far = near + 2 * radius
val distance = near / (near + radius)
// 视图矩阵View Matrix
Matrix.setLookAtM(
mViewMatrix, NO_OFFSET,
0f, 0f, near + radius, // 相机位置
0f, 0f, 0f, // 看向原点
0f, 1f, 0f // 上方向
)
// 投影矩阵Projection Matrix
Matrix.frustumM(
mProjectionMatrix, NO_OFFSET,
if (isLandscape) (-viewPortRatio * distance) else (-1f * distance), // 左边界
if (isLandscape) (viewPortRatio * distance) else (1f * distance), // 右边界
if (isLandscape) (-1f * distance) else (-viewPortRatio * distance), // 下边界
if (isLandscape) (1f * distance) else (viewPortRatio * distance), // 上边界
near, // 近平面
far // 远平面
)
// 最终变换矩阵,第一次变换,模型矩阵 x 视图矩阵 = Model x View, 但是OpenGL ES矩阵乘法是右乘,所以是View x Model
Matrix.multiplyMM(
mMVPMatrix,
NO_OFFSET,
mViewMatrix,
NO_OFFSET,
mModelMatrix,
NO_OFFSET
)
// 最终变换矩阵,第二次变换,模型矩阵 x 视图矩阵 x 投影矩阵 = Model x View x Projection, 但是OpenGL ES矩阵乘法是右乘,所以是Projection x View x Model
Matrix.multiplyMM(
mMVPMatrix,
NO_OFFSET,
mProjectionMatrix,
NO_OFFSET,
mMVPMatrix,
NO_OFFSET
)
// 纹理坐标系为(0, 0), (1, 0), (1, 1), (0, 1)的正方形逆时针坐标系,从Bitmap生成纹理,即像素拷贝到纹理坐标系
// 变换矩阵需要加上一个y方向的翻转, x方向和z方向不改变
Matrix.scaleM(
mMVPMatrix,
NO_OFFSET,
1f,
-1f,
1f,
)
}
/**
* 清除缓冲区
*/
private fun clearBuffers() {
// 清除颜色缓冲区
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
}
/**
* 绘制图形
*/
private fun draw() {
val state = saveGLState()
try {
GLES30.glUseProgram(mProgram)
enableTexture0(mProgram, mTextureID[0])
// 解析变换矩阵
val matrixHandle = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
GLES30.glUniformMatrix4fv(matrixHandle, 1, false, mMVPMatrix, NO_OFFSET)
// 绑定VAO
GLES30.glBindVertexArray(mVAO[0])
// 绘制图形
GLES30.glDrawElements(
GLES30.GL_TRIANGLES,
index.size,
GLES30.GL_UNSIGNED_SHORT,
NO_OFFSET
)
// 解绑VAO
GLES30.glBindVertexArray(0)
disableTexture0()
} finally {
restoreGLState(state)
}
}
/**
* 释放资源
*/
private fun release() {
// 删除着色器程序、VAO、VBO和纹理等OpenGL资源
}
}
object OpenGLUtils {
// OpenGL状态数据类
data class GLState(
val viewport: IntArray,
val program: Int,
val framebuffer: Int
)
// 保存OpenGL状态
fun saveGLState(): GLState {
val viewport = IntArray(4)
val program = IntArray(1)
val framebuffer = IntArray(1)
GLES30.glGetIntegerv(GLES30.GL_VIEWPORT, viewport, 0)
GLES30.glGetIntegerv(GLES30.GL_CURRENT_PROGRAM, program, 0)
GLES30.glGetIntegerv(GLES30.GL_FRAMEBUFFER_BINDING, framebuffer, 0)
return GLState(viewport, program[0], framebuffer[0])
}
// 恢复OpenGL状态
fun restoreGLState(state: GLState) {
GLES30.glViewport(
state.viewport[0],
state.viewport[1],
state.viewport[2],
state.viewport[3]
)
GLES30.glUseProgram(state.program)
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, state.framebuffer)
}
fun enableTexture0(program: Int, id: Int) {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, id)
val textureSampleHandle = GLES30.glGetUniformLocation(program, "uTexture_0")
if (textureSampleHandle != -1) {
GLES30.glUniform1i(textureSampleHandle, 0)
}
}
fun disableTexture0() {
GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, 0)
}
// 创建着色器对象
fun loadShader(type: Int, source: String): Int {
val shader = GLES30.glCreateShader(type)
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
return shader
}
}
渲染引擎
渲染引擎接口
kotlin
/**
* OpenGL渲染引擎接口
*/
interface OpenGLEngine {
/**
* SurfaceHolder回调,用于绑定到SurfaceView
*/
val callback: SurfaceHolder.Callback
/**
* 请求执行一次渲染
*/
fun requestRender(mode : Int? = null)
}
渲染引擎接口实现
kotlin
open class BaseOpenGLEngine(private val renderData: OpenGLData) : OpenGLEngine {
private var mRenderThread: RenderThread? = null
override val callback: SurfaceHolder.Callback = object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
mRenderThread = RenderThread(holder.surface, renderData)?.apply {
start()
}
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
mRenderThread?.updateSize(width, height)
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
mRenderThread?.shutdown()
mRenderThread = null
}
}
override fun requestRender(mode: Int?) {
mRenderThread?.requestRender(mode)
}
}
渲染线程
kotlin
class RenderThread(private val surface: Surface, private val renderData: OpenGLData) : Thread() {
private val TAG = "RenderThread"
private var mEGLEnvironment : EGLEnvironmentBuilder.EGLEnvironment ?= null
@Volatile
private var running = true
@Volatile
private var sizeChanged = false
@Volatile
private var renderMode = RENDERMODE_WHEN_DIRTY
@Volatile
private var requestRender = true
private var mCacheWidth = 0
private var mCacheHeight = 0
private val lock = Object()
fun updateSize(width: Int, height: Int) {
synchronized(lock) {
mCacheWidth = width
mCacheHeight = height
sizeChanged = true
requestRender = true
lock.notifyAll()
Log.d(TAG, "updateSize width = $width, height = $height")
}
}
fun requestRender(mode: Int? = null) {
synchronized(lock) {
mode?.let { renderMode = it }
requestRender = true
lock.notifyAll()
Log.d(TAG, "requestRender${mode?.let { " mode = $it" } ?: ""}")
}
}
fun shutdown() {
synchronized(lock) {
running = false
lock.notifyAll()
Log.d(TAG, "requestRender shutdown")
}
join() // 等待线程完成
renderData.onSurfaceDestroyed()
mEGLEnvironment?.release()
}
override fun run() {
mEGLEnvironment = EGLEnvironmentBuilder().build(surface)
renderData.onSurfaceCreated()
renderLoop()
}
private fun renderLoop() {
while (running) {
synchronized(lock) {
// 等待渲染条件
while (running && !requestRender && !sizeChanged && renderMode != RENDERMODE_CONTINUOUSLY) {
lock.wait()
}
if (!running) return
// 处理尺寸变化
if (sizeChanged) {
renderData.onSurfaceChanged(mCacheWidth, mCacheHeight)
sizeChanged = false
}
// 处理渲染
if (requestRender || renderMode == RENDERMODE_CONTINUOUSLY) {
renderData.onDrawFrame()
mEGLEnvironment?.swapBuffers()
requestRender = false
}
}
}
}
}
activity_main的XML文件
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 预览区Fragment容器 -->
<FrameLayout
android:id="@id/preview_fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#000000">
<com.example.render.opengl.BaseSurfaceView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<!-- 操作区Fragment容器 -->
<FrameLayout
android:id="@+id/operate_fragment_container"
android:layout_width="match_parent"
android:layout_height="230dp"
android:background="#F5F5F5" />
</LinearLayout>
Activity的代码
kotlin
class MainActivity : AppCompatActivity() {\
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
效果图
