GLSurfaceView的渲染模式。
其实也很简单
OpenGl ES关于渲染方式有以下两种:RENDERMODE_CONTINUOUSLY和RENDERMODE_WHEN_DIRTY。
默认渲染方式为RENDERMODE_CONTINUOUSLY,当设置为RENDERMODE_CONTINUOUSLY时渲染器会不停地渲染场景,当设置为RENDERMODE_WHEN_DIRTY时只有在创建和调用requestRender()时才会刷新。
一般设置为RENDERMODE_WHEN_DIRTY方式,这样不会让CPU一直处于高速运转状态,提高手机电池使用时间和软件整体性能。
接着简单写一个OpenGL ES程序。
为了使用OpenGL ES 2.0 API,需要添加如下声明:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
OpenGL ES 2.0 requires Android 2.2 (API Level 8) or higher,所以需要确认系统版本。
在Activity的布局中,需要加入GLSurfaceView来放置绘制的图形。
一个最简单的版本如下:
public class OpenGLES20 extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create a GLSurfaceView instance and set it // as the ContentView for this Activity. mGLView = new MyGLSurfaceView(this); setContentView(mGLView); } }
GLSurfaceView是一个特殊的组件,你可以在其中绘制OpenGL ES图形。
你需要扩展这个类,在它的构造方法中设置渲染器:
class MyGLSurfaceView extends GLSurfaceView { public MyGLSurfaceView(Context context){ super(context); // Set the Renderer for drawing on the GLSurfaceView setRenderer(new MyRenderer()); } }
如果使用OpenGL ES 2.0,还需要加一句声明:
// Create an OpenGL ES 2.0 context setEGLContextClientVersion(2);
还有一个可选的设置是,把渲染模式改为 GLSurfaceView.RENDERMODE_WHEN_DIRTY
,这样仅在你的数据有变化时重新进行渲染。
// Render the view only when there is a change in the drawing data setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
除非你调用requestRender(),这个设置会阻止帧被重画,有些情况下这样效率更高。
Renderer类(渲染器类),即 GLSurfaceView.Renderer
的实现类,它控制了与它相关联的 GLSurfaceView
上绘制什么。
其中有三个主要的回调方法:
onSurfaceCreated()
- Called once to set up the view's OpenGL ES environment.onDrawFrame()
- Called for each redraw of the view.onSurfaceChanged()
- Called if the geometry of the view changes, for example when the device's screen orientation changes.
一个简单的实现例子:
public class MyGL20Renderer implements GLSurfaceView.Renderer { public void onSurfaceCreated(GL10 unused, EGLConfig config) { // Set the background frame color GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); } public void onDrawFrame(GL10 unused) { // Redraw background color GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); } public void onSurfaceChanged(GL10 unused, int width, int height) { GLES20.glViewport(0, 0, width, height); } }
一个简单的程序例子,并没有绘制什么,只是设置了背景色,为了展示方便,GLSurfaceView类和渲染器类都作为Acitivity的内部类写出。
首先在Manifest中加上声明:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.helloopengles" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <uses-feature android:glEsVersion="0x00020000" android:required="true" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".HelloOpenGLESActivity" android:label="@string/title_activity_hello_open_gles" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> intent-filter> activity> application> manifest>
package com.example.helloopengles; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; import android.app.Activity; import android.content.Context; import android.opengl.GLES20; import android.opengl.GLSurfaceView; import android.os.Bundle; import android.util.Log; public class HelloOpenGLESActivity extends Activity { private GLSurfaceView mGLView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create a GLSurfaceView instance and set it // as the ContentView for this Activity. mGLView = new MyGLSurfaceView(this); setContentView(mGLView); } class MyGLSurfaceView extends GLSurfaceView { public MyGLSurfaceView(Context context) { super(context); try { // Create an OpenGL ES 2.0 context setEGLContextClientVersion(2); // Set the Renderer for drawing on the GLSurfaceView setRenderer(new MyRenderer()); // Render the view only when there is a change in the drawing // data setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 注意上面语句的顺序,反了可能会出错 } catch (Exception e) { e.printStackTrace(); } } } public class MyRenderer implements GLSurfaceView.Renderer { public void onSurfaceCreated(GL10 unused, EGLConfig config) { // Set the background frame color GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f); } public void onDrawFrame(GL10 unused) { // Redraw background color GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); } public void onSurfaceChanged(GL10 unused, int width, int height) { GLES20.glViewport(0, 0, width, height); } } }
GLSurfaceView的绘制过程要点
1,GLSurfaceview的渲染模式RenderMode
在onAttachedToWindow后就启动了一个无线循环的子线程,该子线程完成了整个绘制流程,并系统默认是负责不断刷新重绘,刷新的帧率是16FPS。从这里也可以看出来,GLSurfaceView系统默认是60ms就重绘一次,这样的耗性能的重绘操作一定是要用在那种有持续动画的效果才有意义。
当然,你也可以通过设置setRenderMode去设置主动刷新:
1
2
3
4
5
6
7
|
/**
* Set the rendering mode. When renderMode is
* RENDERMODE_CONTINUOUSLY, the renderer is called
* repeatedly to re-render the scene. When renderMode
* is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface
* is created, or when {
@link
#requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY.
*
|
* Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance * by allowing the GPU and CPU to idle when the view does not need to be updated. *
* This method can only be called after {@link #setRenderer(Renderer)} * * @param renderMode one of the RENDERMODE_X constants * @see #RENDERMODE_CONTINUOUSLY * @see #RENDERMODE_WHEN_DIRTY */ public void setRenderMode(int renderMode) { mGLThread.setRenderMode(renderMode); }
注解中提到:系统默认mode==RENDERMODE_CONTINUOUSLY,这样系统会自动重绘;mode==RENDERMODE_WHEN_DIRTY时,只有surfaceCreate的时候会绘制一次,然后就需要通过requestRender()方法主动请求重绘。同时也提到,如果你的界面不需要频繁的刷新最好是设置成RENDERMODE_WHEN_DIRTY,这样可以降低CPU和GPU的活动,可以省电。
2,事件处理
为了处理事件,一般都是继承GLSurfaceView类并重载它的事件方法。但是由于GLSurfaceView是多线程操作,所以需要一些特殊的处理。由于渲染器在独立的渲染线程里,你应该使用Java的跨线程机制跟渲染器通讯。queueEvent(Runnable)方法就是一种相对简单的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
class
MyGLSurfaceView
extends
GLSurfaceView {
private
MyRenderer mMyRenderer;
public
void
start() {
mMyRenderer = ...;
setRenderer(mMyRenderer);
}
public
boolean
onKeyDown(
int
keyCode, KeyEvent event) {
if
(keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
queueEvent(
new
Runnable() {
// 这个方法会在渲染线程里被调用
public
void
run() {
mMyRenderer.handleDpadCenter();
}});
return
true
;
}
return
super
.onKeyDown(keyCode, event);
}
}
}
|
调用queueEvent就是给队列中添加runnable
1
2
3
4
5
6
7
8
|
public
void
queueEvent(Runnable r) {
synchronized
(sGLThreadManager) {
mEventQueue.add(r);
sGLThreadManager.notifyAll();
}
}
|
在guardenRun()中有如下代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
if
(! mEventQueue.isEmpty()) {
event = mEventQueue.remove(
0
);
break
;
}
...
if
(event !=
null
) {
event.run();
event =
null
;
continue
;
}
|
因为每次都会remove掉添加的runnable,所以上面那个demo就是非常好的解释,每次按键就是添加runnable。当然,这也是要求绘制是一直在循环重绘的状态才能看到效果。
(注:如果在UI线程里调用渲染器的方法,很容易收到“call to OpenGL ES API with no current context”的警告,典型的误区就是在键盘或鼠标事件方法里直接调用opengl es的API,因为UI事件和渲染绘制在不同的线程里。更甚者,这种情况下调用glDeleteBuffers这种释放资源的方法,可能引起程序的崩溃,因为UI线程想释放它,渲染线程却要使用它。)
这个是在网上搜集的资料的整理,可能有点乱。
android GLSurfaceView渲染模式就讲完了。
就这么简单。