学习在Android中使用OpenGL ES,就不得不提到一个控件:GLSurfaceView
GLSurfaceView从Android 1.5(API level 3)开始加入,继承自SurfaceView,实现了SurfaceHolder.Callback2接口,拥有SurfaceView的全部特性,也有view所有的功能和属性,特别是处理事件的能力,它主要是在SurfaceView的基础上它加入了EGL的管理,并自带了一个GLThread绘制线程(EGLContext创建GL环境所在线程即为GL线程),绘制的工作直接通过OpenGL在绘制线程进行,不会阻塞主线程,绘制的结果输出到SurfaceView所提供的Surface上,这使得GLSurfaceView也拥有了OpenGlES所提供的图形处理能力,通过它定义的Render接口,使更改具体的Render的行为非常灵活性,只需要将实现了渲染函数的Renderer的实现类设置给GLSurfaceView即可。
GLSurfaceView提供了下列特性:
1> 提供并且管理一个独立的Surface。
2> 提供并且管理一个EGL display,它能让opengl把内容渲染到上述的Surface上。
3> 支持用户自定义渲染器(Render),通过setRenderer设置一个自定义的Renderer。
4> 让渲染器在独立的GLThread线程里运作,和UI线程分离。
5> 支持按需渲染(on-demand)和连续渲染(continuous)两种模式。
6> GPU加速:GLSurfaceView的效率是SurfaceView的30倍以上,SurfaceView使用画布进行绘制,GLSurfaceView利用GPU加速提高了绘制效率。
7> View的绘制onDraw(Canvas canvas)使用Skia渲染引擎渲染,而GLSurfaceView的渲染器Renderer的onDrawFrame(GL10 gl)使用opengl绘制引擎进行渲染。
2.GLSurfaceView的使用步骤
开发基于GLSurfaceView的程序,通常需要继承GLSurfaceView,并重载一些和用户输入事件有关的方法,如果你不需监听事件,也可以直接使用GLSurfaceView, 你可以使用set方法来修改默认的属性,例如setRenderer(Renderer)设置渲染器,主要的工作就是设置Renderer,其他过程,例如EGL的创建,Surface的分配以及OpenGLES的调用都被隐藏起来了,GLSurfaceView会启动一个工作线程来完成渲染,避免阻塞UI主线程,这个工作线程就是mGLThread,这个线程在应用程序setRederer的时候启动,然后不停地等待和处理事件,同时还负责开展Render工作。
第一步:创建GLSurfaceView
GLSurfaceView也是View,可以通过布局文件的方式将它加入整棵view树中,或者在java代码中创建并且添加。
在布局中,摆布好GLSurfaceView的位置.如下:
<android.opengl.GLSurfaceView
android:id="@+id/glv_main"
android:layout_width="match_parent"
android:layout_height=
"200dp"
/>
在Activity中实例化,如下:
@Override
protected
void
onCreate
(Bundle savedInstanceState){
GLSurfaceView glv
= (GLSurfaceView)findViewById(R.id.glv_main);
}
第二步:初始化OpenGLES环境
GLSurfaceView默认情况下已经初始化好了OpenGLES的运行环境,不需要做额外的工作就可以使用,当然也可以更改一些默认的设置。
第三步:设置Renderer
渲染是OpenGLES的核心工作,setRenderer可以将用户自定义的一个
Renderer
加入实际的渲染流程中。
public class GLRender implements GLSurfaceView.Renderer{
//控制旋转的角度
private float rotate;
@Override
public void onSurfaceCreated(GL10 gl,
EGLConfig config) {
//关闭抗抖动
gl.glDisable(GL10.GL_DITHER);
//设置系统对透视进行修正
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl
.glClearColor
(
0
,
0
,
0
,
0
)
;
//设置阴影平滑模式
gl.glShadeModel(GL10.GL_SMOOTH);
//启动深度测试
gl.glEnable(GL10.GL_DEPTH_TEST);
//设置深度测试的类型
gl.glDepthFunc(GL10.GL_LEQUAL);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
//设置3D视窗的大小及位置
gl
.glViewport
(
0
,
0
, width, height)
;
//将当前矩阵模式设为投影矩形
gl.glMatrixMode(GL10.GL_PROJECTION);
//初始化单位矩阵
gl.glLoadIdentity();
//计算透视窗口的宽度高度比
float ratio = (float) width / height;
//调用此方法设置透视窗口的空间大小
gl
.glFrustumf
(-ratio, ratio, -
1
,
1
,
1
,
10
)
;
}
@Override
public void onDrawFrame(GL10 gl) {
//清除屏幕缓存和深度缓存
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
//启用顶点坐标数据
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
//启用顶点颜色数据
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
//设置当前矩阵堆栈为模型堆栈
gl.glMatrixMode(GL10.GL_MODELVIEW);
//------绘制第一个图形-----
//重置当前的模型视图矩阵
gl.glLoadIdentity();
gl.glTranslatef(0.95f, -0.8f, -1.5f); //1⃣
//设置顶点位置数据
gl
.glVertexPointer
(
3
, GL10
.GL
_FLOAT,
0
, PointData
.floatBufferUtil
(PointData
.triangleData
))
;
//设置顶点颜色数据
gl
.glColorPointer
(
4
, GL10
.GL
_FIXED,
0
, PointData
.intBufferUtil
(PointData
.triangleColor
))
;
//根据顶点数据绘制平面图形
gl
.glDrawArrays
(GL10
.GL
_TRIANGLES,
0
,
3
)
;
//-----绘制第二个图形-----
//重置当前的模型视图矩阵
gl.glLoadIdentity();
gl
.glTranslatef
(
0.95
f,
0.8
f, -
1.5
f)
;
//设置顶点位置数据
gl
.glVertexPointer
(
3
, GL10
.GL
_FLOAT,
0
, PointData
.floatBufferUtil
(PointData
.rectData
))
;
//设置顶点颜色数据
gl
.glColorPointer
(
4
, GL10
.GL
_FIXED,
0
, PointData
.intBufferUtil
(PointData
.rectColor
))
;
//更具顶点数据绘制平面图形
gl
.glDrawArrays
(GL10
.GL
_TRIANGLE_STRIP,
0
,
4
)
;
//-----绘制第三个图形----
//重置当前的模型视图矩阵
gl.glLoadIdentity();
gl
.glTranslatef
(-
0.95
f,
0.8
f, -
1.5
f)
;
//设置顶点位置数据
gl
.glVertexPointer
(
3
, GL10
.GL
_FLOAT,
0
, PointData
.floatBufferUtil
(PointData
.rectData
2))
;
//根据顶点数据绘制平面图形
gl
.glDrawArrays
(GL10
.GL
_TRIANGLE_STRIP,
0
,
4
)
;
//-----绘制第四个图形-----
//重置当前的模型视图矩阵
gl.glLoadIdentity();
gl.glTranslatef(-0.95f, -0.8f, -1.5f);
//设置使用纯色填充 **需要注意: 使用纯色填充需要禁用顶点颜色数组
gl
.glColor
4f(
1.0
f,
0.2
f,
0.2
f,
0.0
f)
;
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
//设置顶点位置数据
gl
.glVertexPointer
(
3
, GL10
.GL
_FLOAT,
0
, PointData
.floatBufferUtil
(PointData
.pentacle
))
;
//根据顶点数据绘制图形
gl
.glDrawArrays
(GL10
.GL
_TRIANGLE_STRIP,
0
,
5
)
;
//----绘制第五个图形---
//重置当前的模型视图矩阵
gl.glLoadIdentity();
gl
.glTranslatef
(
0
f,
0
f, -
1.5
f)
;
gl
.glRotatef
(rotate,
0
f,
0.2
f,
0
f)
;
//设置顶点位置数据
gl
.glVertexPointer
(
3
, GL10
.GL
_FLOAT,
0
, PointData
.floatBufferUtil
(TDPointData
.taperVertices
))
;
//启用顶点颜色组
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
//设置顶点颜色数据
gl
.glColorPointer
(
4
, GL10
.GL
_FIXED,
0
, PointData
.intBufferUtil
(TDPointData
.taperColors
))
;
//按taperFacetsBuffer指定的面绘制三角形
ByteBuffer byteBuffer = PointData.byteBufferUtil(TDPointData.taperFacets);
gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, byteBuffer.remaining(), GL10.GL_UNSIGNED_BYTE, byteBuffer);
//绘制结束
gl.glFinish();
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
//旋转角度+1
rotate +=
1
;
}
}
@Override
protected
void
onCreate
(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_glsurface);
glv = (GLSurfaceView)findViewById(R.id.glv_opengl);
GLRender render =
new
GLRender();
glv.setRenderer(render);
}
第四步:设置RenderingMode
GLSurfaceView
在onAttachedToWindow后就启动了一个无线循环的
GLThread
线程,
GLSurfaceView默认采用的是
RENDERMODE_CONTINUOUSLY
连续渲染的方式,
刷新的帧率是60FPS,16ms就重绘一次,
可以通过
mGLView.setRenderMode()更改其渲染方式为
RENDERMODE_WHEN_DIRTY,表示被动渲染,在
surfaceCreate的时候会绘制一次,之后
只有在调用requestRender或者onResume等方法
主动请求重绘
时才会进行渲染,
如果你的界面不需要频繁的刷新最好使用
RENDERMODE_WHEN_DIRTY
,这样可以降低CPU和GPU的活动
。
第五步:状态处理
使用GLSurfaceView需要注意程序的生命周期,
Activity及Fragment
会有暂停和恢复等状态,GLSurfaceView也需根据这些状态来做相应的处理,
GLSurfaceView具有onResume和onPause两个同Activity及Fragment中的生命周期同名的方法,在Activity或者Fragment中的onResume和onPause方法中,需要主动调用GLSurfaceView的实例的这两个方法
,这样能使OpenGLES的内部线程做出正确的判断,从而保证应用程序的稳定性。
第六部:事件处理
为了处理事件,一般都是继承GLSurfaceView类并重载它的事件方法,但是由于GLSurfaceView是多线程的,渲染器在独立的渲染线程里,你应该使用Java的跨线程机制跟渲染器通讯,GLSurfaceView提供的queueEvent(Runnable)方法就是一种相对简单的操作,queueEvent()方法被安全地用于在UI线程和渲染线程之间进行交流通信:
public void queueEvent(Runnable r) {
mGLThread.queueEvent(r);
}
这里直接调用
GLThread的
queueEvent(Runnable r)方法
给
GLThread的
mEventQueue
队列中添加
Runnable
public void queueEvent(Runnable r) {
synchronized(sGLThreadManager) {
mEventQueue.add(r);
sGLThreadManager.notifyAll();
}
}
在GLThread的guardenRun()方法中有如下代码:
一般事件的使用如下:
如果在UI线程里调用渲染器的方法,因为UI事件和渲染绘制是在不同的线程里,会收到“call to OpenGL ES API with no current context”的警告,典型的案例就是在键盘或触摸事件方法里直接调用opengl es的API。
GLSurfaceView源码分析
GLSurfaceView中对于GL环境的操作,除queueEvent是将事件放入队列中然后到GL线程中执行外,
其他方法基本都是在主线程中修改某个状态值(某个属性属性),然后调用
取消GL线程的等待,
在GL线程中根据属性的状态值作不同的操作,并在操作后反馈给主线程,当然有的方法也不需要反馈。
相关类图如下,GLSurfaceView中的EglHelper和GLThread分别实现了上面提到的管理EGL环境和渲染线程的工作,GLSurfaceView的使用者需要实现Renderer接口。
渲染的整体步骤如下:
- 获取EGLDisplay对象,初始化与EGLDisplay 之间的连接
- 获取EGLConfig对象
- 创建EGLContext 实例
- 创建EGLSurface实例
- 连接EGLContext和EGLSurface.
- 是否需要重新申请EGLSurface,如果尺寸发生了变化,此时createEGLSurface为true
- 是否需要申请GLObject,此时createGLinterface为true
- 是否需要生成EGLContext,此时createEGLContext为true
- 尺寸是否变化,sizeChanged为true,此时需要通知观察者
- 一切准备好之后,使用GL指令绘制图形,调用view.mRenderer.onDrawFrame进行真正的渲染
- 最后通过swap把渲染结果显示到屏幕
- 断开并释放与EGLSurface关联的EGLContext对象
- 删除EGLSurface对象
- 删除EGLContext对象
- 终止与EGLDisplay之间的连接
EGLHelper
readyToDraw后调用了
EGLHelper.
start方法初始化,然后跳出readyToDraw后调用了
EGLHelper.
createSurface方法创建一个
EglSurface,
最后绘制完成以后
通过
EGLHelper.
swap把渲染结果显示到屏幕上。
第一步:
EGLHelper.
start()方法初始化EGLHelper的内容
public void start() {
mEgl = (EGL10) EGLContext.getEGL();//获取一个EGL实例
//获取一个EGLDisplay
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
//初始化EGL并返回版本号
if(!mEgl.eglInitialize(mEglDisplay, version)) {
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
//选取一个配置
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
//创建EGLContext
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
mEglSurface = null;
}
第二步:如果需要创建
EglSurface,调用
mEglHelper.createSurface()方法
public boolean createSurface() {
if (LOG_EGL) {
Log.w("EglHelper", "createSurface() tid=" + Thread.currentThread().getId());
}
/*
* Check preconditions.
*/
if (mEgl == null) {
throw new RuntimeException("egl not initialized");
}
if (mEglDisplay == null) {
throw new RuntimeException("eglDisplay not initialized");
}
if (mEglConfig == null) {
throw new RuntimeException("mEglConfig not initialized");
}
GLSurfaceView view = mGLSurfaceViewWeakRef.get();
mEglSurface = view.mEGLWindowSurfaceFactory.createWindowSurface(mEgl,
mEglDisplay, mEglConfig, view.getHolder());
return true;
}
private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory {
public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display,
EGLConfig config, Object nativeWindow) {
EGLSurface result = null;
result = egl.eglCreateWindowSurface(display, config, nativeWindow, null);
return result;
}
}
第三步:提交绘制结果,通过mEglHelper.swap()把渲染结果显示到屏幕上
public int swap() {
if (! mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) {
return mEgl.eglGetError();
}
return EGL10.EGL_SUCCESS;
}
总结GLSurfaceView使用EGL的流程如下:
1.生成一个EGL实例
mEgl = (EGL10) EGLContext.getEGL();
2.获取一个EGL Display
mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
3.初始化EGL并返回版本号
if(!mEgl.eglInitialize(mEglDisplay, version)) {}
4.选取一个配置
mEglConfig = view.mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay);
5.创建一个EGLContext
mEglContext = view.mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig);
6.创建EGLSurface
egl.eglCreateWindowSurface(display, config, nativeWindow, null);
7.通过swap将渲染内容显示到屏幕
mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)