Android中OpenGL ES版本

可使用的包(使用GLSurfaceView和GLSurfaceView.Renderer绘制)

包javax.microedition.khronos.opengles

- 提供OpenGL ES 1.0/1.1标准实现
- 可使用的API类包括:GL10,GL10Ext,GL11,GL11Ext和GL11ExtensionPack

包android.opengl

- 提供一套静态函数接口,包括OpenGL ES 1.0/1.1和2.0,其性能优于javax.microedition.khronos.opengles
- 支持OpenGL ES 1.0/1.1的类包括:GLES10、 GLES10Ext、 GLES11和GLES10Ext
- 支持OpenGL ES 2.0的API类是:android.opengl.GLES20(自Android 2.2开始)

版本选择

  • 性能:通常,OpenGL ES 2.0能比ES 1.0/1.1提供较快的性能。但是,这最终依赖于Android设备,不同的设备平台OpenGL的实现不同。
  • 设备兼容性:开发者开发的应用程序需要考虑Android设备类型,不同的Android版本对OpenGL版本支持不同,如OpenGL ES 2.0自Android 2.2才开始支持。
  • 编码便利性:OpenGL ES 1.0/1.1 API编程较为方便,2.0版本相对复杂些。
  • 图形控制:通过使用shaders,OpenGL ES 2.0对图形绘制能提供较多的控制,可以创建更好的效果,在1.0/1.1版本上则很难达到
  • 上面是谷歌的建议, 但是对于现在肯定选择2.0

EGL

前言

EGL是本地平台和OpenGL ES之间的抽象层,其完成了本地相关的环境初始化和上下文控制工作,以保证OpenGL ES的平台无关性。主要包含如下工作:

  • 选择显示设备
  • 选择像素格式。
  • 选择某些特性,比如如果你打算画中国水墨画,你需要额外指定宣纸和毛笔。
  • 申请显存。
  • 创建上下文(Context),上下文本质上是一组状态的集合,描述了在某个特定时刻系统的状态, 用于处理暂停、恢复、销毁、重建等情况;
  • 指定当前的环境为绘制环境 。

总体流程上,EGL按顺序分为若干步骤:

  1. 选择显示设备display,即上述的a.
  2. 指定特性,包括上述的像素格式(b)和特定特性(c),根据指定的特性来获取多个满足这些特性的config
    (比如你指定RGB中的R为5bits,那么可能会有RGB_565和RGB_555两种像素格式均满足此特性),
    用户从这些可用的configs中选择一个,根据display和config获取绘制用的buffer(一般为显存).
  3. 使用display、config、buffer来创建context,及即上述的e.
  4. 使用display、buffer、context 设置当前的渲染环境,即上述的f.

选择显示设备及确认EGL版本

EGL有1.0、1.1、1.2、1.3、1.4这几个版本,Android中使用的是1.4,EGL提供了查询版本的API,以下为Android中例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EGL10 egl = (EGL10) EGLContext.getEGL();
EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); //获取显示设备

// Init
int[] version = new int[2];
egl.eglInitialize(display, version); //version中存放EGL 版本号,int[0]为主版本号,int[1]为子版本号

String vendor = egl.eglQueryString(display, EGL10.EGL_VENDOR);
WLog.d("egl vendor: " + vendor); // 打印此版本EGL的实现厂商

String version = egl.eglQueryString(display, EGL10.EGL_VERSION);
WLog.d("egl version: " + version);// 打印EGL版本号

String extension = egl.eglQueryString(display, EGL10.EGL_EXTENSIONS);
WLog.d("egl extension: " + extension); //打印支持的EGL扩展

说明:

  1. 虽然Android使用(实现)的是EGL 1.4(从打印的版本号中可见), 但在Android 4.2(API 17)以前的版本没
    有EGL14,只有EGL10和EGL11,而这两个版本是不支持OpengGL ES 2.x的,因此在老版本中某些ES 2.x相关的常
    量参数只能用手写的硬编码代替,典型的如设定EGL渲染类型API的参数EGL10.EGL_RENDERABLE_TYPE,这个属性
    用不同的赋值指定的不同的渲染API,包括OpenGL,OpenGL ES 1.x, OpenGL ES 2.x,OpenVG等,
    如果采用ES 2.0,应该设置此值为: EGL14.EGL_OPENGL_ES2_BIT,但是在Android 4.2之前,没有EGL14接口.
    只能采取手写的硬编码来指定,类似: EGL_RENDERABLE_TYPE = 4;

  2. EGL10.EGL_DEFAULT_DISPLAY 默认对应手机主屏幕。

指定(buffer)特性,获取config

1.构造需要的特性列表

1
2
3
4
5
6
7
8
int[] attributes = new int[] { 
EGL10.EGL_RED_SIZE, 8, //指定RGB中的R大小(bits)
EGL10.EGL_GREEN_SIZE, 8, //指定G大小
EGL10.EGL_BLUE_SIZE, 8, //指定B大小
EGL10.EGL_ALPHA_SIZE, 8, //指定Alpha大小,以上四项实际上指定了像素格式
EGL10.EGL_DEPTH_SIZE, 16, //指定深度缓存(Z Buffer)大小
EGL10.EGL_RENDERABLE_TYPE, 4, //指定渲染api类别, 如上一小节描述,这里或者是硬编码的4,或者是EGL14.EGL_OPENGL_ES2_BIT
EGL10.EGL_NONE }; //总是以EGL10.EGL_NONE结尾

  1. 获取所有可用的configs,每个config都是EGL系统根据特定规则选择出来的最符合特性列表要求的一组特性。
1
2
3
4
5
6
7
8
9
10
11
EGLConfig config = null;
int[] configNum = new int[1];
//获取满足attributes的config个数。
egl.eglChooseConfig(display, attributes, null, 0, configNum);
int num = configNum[0];
if(num != 0){
EGLConfig[] configs = new EGLConfig[num];
//获取所有满足attributes的configs
egl.eglChooseConfig(display, attributes, configs, num, configNum);
config = configs[0]; //以某种规则选择一个config,这里使用了最简单的规则。
}

说明:

  1. display和attributes都来自之前的步骤。
  2. eglChooseConfig(display, attributes, configs, num, configNum); 用于获取满足attributes的所有config,
    参数1、2其意明显,参数3用于存放输出的configs,参数4指定最多输出多少个config,参数5由EGL系统写入,
    表明满足attributes的config一共有多少个。如果使用eglChooseConfig(display, attributes, null, 0, configNum)
    这种形式调用,则会在configNum中输出所有满足条件的config个数。
  3. 一般习惯是获取所有满足attributes的config个数,再据此分配存放config的数组,获取所有config,根据某种特定规则,从中选择其一。
  4. API详细说明和所有可指定的attributes见这里:http://www.khronos.org/registry/egl/sdk/docs/man/xhtml/
  5. 打印config中的常用attributes:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
     /**
    * 打印EGLConfig信息
    *
    * @param egl
    * @param display
    * @param config
    * : 指定的EGLConfig
    */
    public static void printEGLConfigAttribs(EGL10 egl, EGLDisplay display, EGLConfig config) {
    int value = findConfigAttrib(egl, display, config, EGL10.EGL_RED_SIZE, -1);
    WLog.d("eglconfig: EGL_RED_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_GREEN_SIZE, -1);
    WLog.d("eglconfig: EGL_GREEN_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_BLUE_SIZE, -1);
    WLog.d("eglconfig: EGL_BLUE_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_ALPHA_SIZE, -1);
    WLog.d("eglconfig: EGL_ALPHA_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_DEPTH_SIZE, -1);
    WLog.d("eglconfig: EGL_DEPTH_SIZE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_RENDERABLE_TYPE, -1);
    WLog.d("eglconfig: EGL_RENDERABL_TYPE: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_SAMPLE_BUFFERS, -1);
    WLog.d("eglconfig: EGL_SAMPLE_BUFFERS: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_SAMPLES, -1);
    WLog.d("eglconfig: EGL_SAMPLES: " + value);

    value = findConfigAttrib(egl, display, config, EGL10.EGL_STENCIL_SIZE, -1);
    WLog.d("eglconfig: EGL_STENCIL_SIZE: " + value);
    }


    /**
    * 在指定EGLConfig中查找指定attrib的值,如果没有此属性,返回指定的默认值
    *
    * @param egl
    * @param display
    * @param config
    * : 指定的EGLConfig
    * @param attribute
    * : 指定的attrib
    * @param defaultValue
    * : 查找失败时返回的默认值
    * @return: 查找成功,返回查找值;查找失败,返回参数中指定的默认值
    */
    static public int findConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config,
    int attribute, int defaultValue) {
    int[] val = new int[1];
    if (egl.eglGetConfigAttrib(display, config, attribute, val)) {
    return val[0];
    }
    return defaultValue;
    }

获取显存

EGLSurface surface = egl.eglCreateWindowSurface(display, config, surfaceHolder, null);

说明:

  1. 详细的参数说明
  2. 参数surfaceHolder是android.view.SurfaceHolder类型,负责对Android Surface的管理.
  3. 参数4用于描述WindowSurface类型,初始化方式如同前面小节的egl attributes, 其中一个attribute是
    EGL_RENDER_BUFFER, 用于描述渲染buffer(所有的绘制在此buffer中进行)类别,取值为EGL_SINGLE_BUFFER
    以及默认的EGL_BACK_BUFFER,前者属于单缓冲,绘制的同时用户即可见;后者属于双缓冲,前端缓冲用于显示,
    OpenGL ES 在后端缓冲中进行绘制,绘制完毕后使用eglSwapBuffers()交换前后缓冲,用户即看到在后缓冲中的内容,
    如此反复。其他attributes见官方文档。

创建context

1
2
3
4
int attrs[] = { 
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
EGL10.EGL_NONE, };
EGLContext context = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrs);

说明:
函数原型:EGLContext eglCreateContext(EGLDisplay display, EGLConfig config, EGLContext share_context, int[] attrib_list);
share_context: 是否有context共享,共享的contxt之间亦共享所有数据。EGL_NO_CONTEXT代表不共享;
attrib_list: 目前可用属性只有EGL_CONTEXT_CLIENT_VERSION, 1代表OpenGL ES 1.x, 2代表2.0,
同样在Android4.2之前,没有EGL_CONTEXT_CLIENT_VERSION这个属性,只能使用硬编码0x3098代替。

函数详细描述

设置为当前的渲染环境

1
egl.eglMakeCurrent(display, surface, surface, contxt);

参考文档

环境初始化完毕,开始使用OpenGL ES 2.0 API 进行绘制。

1
2
3
// 开始使用OpenGL ES 2.0 API 进行绘制。
GLES20.glClearColor(0, 0, 0, 1);
GLES20.clear(GL_COLOR_BUFFER_BIT);

关于SurfaceHolder

一般在Android中使用OpenGL ES,总是会从GLSurfaceView和Renderer开始,但是由上面描述的过程可知,
只需要提供一个合适的SurfaceHolder,就可以完成整个环境初始化,并进行绘制。GLSurfaceView和Renderer
事实上只是在本文描述的基础上封装了一些便利的功能,便于开发者开发,比如渲染同步、状态控制、主(渲染)循环等。