Adreno OpenGL ES 2.0 介绍(3)
1. 应用程序示例
本节讨论在开发OpenGL ES应用程序时可能面临的一些关键任务。特别是:
- 如何在Android下建立一个OpenGL ES上下文
- 如何检测Adreno GPU并确定其版本
- 如何检测可用的OpenGL ES扩展
- 如何实现基本的Phong-Blinn照明
- 如何检索渲染上下文的OpenGL ES常量的值
本节通过引用Adreno SDK中包含的一个示例应用程序的源代码来说明以上这些要点,该示例应用程序称为Lighting。本节还用代码片段说明了其他附加要点。
1.2 在Android上创建ES 2.0 上下文/环境
当开发Android平台时,OpenGL ES上下文必须使用EGL API。对于Adreno SDK中提供的示例应用程序,均是通过公共框架代码完成的,这些框架代码在不同的示例应用程序之间都是共通的(共享)。本节将讨论该框架的Android的处理与实现。本文还提供了一些用于理解EGL的基本信息的代码的。有关EGL的更多详细信息,请参见:
初始化过程由CFrmAppContainer::InitializeEgl方法处理,在SDK\Development\Samples\Framework\Android\FrmApplication_Platform.cpp文件中实现。如果该方法返回TRUE,则初始化已经成功执行,并且OpenGL ES渲染上下文已经绑定到调用线程。
该方法首先初始化EGLDisplay实例来表示默认显示。EGLDisplay是允许绘制图形显示的抽象。每个EGLDisplay实例通常对应一个物理屏幕。所有其他EGL对象都是EGLDisplay实例的子对象。因此,OpenGL ES上下文由类型为EGLContext的EGL对象表示,所以有必要在继续之前初始化EGLDisplay。
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
eglInitialize(display,
NULL, /* major */
NULL); /* minor */
eglInitialize的最后两个参数是可选的。如果提供了指向EGLint变量的指针,
它们就会被驱动程序提供的EGL实现的主版本号和次版本号所填充。
在创建OpenGL ES上下文之前,先格外注意以下几个问题:
- 默认的帧缓冲区应该提供什么功能?
- 每个组件的颜色缓冲区应该使用多少位?
- 它是否应该包含深度缓冲区?如果是,每个像素应该使用多少位?
- 它应该包含一个模板缓冲区吗?如果是,每个像素应该使用多少位?
- 需要哪个OpenGL ES版本?
- 呈现过程的输出应该指向哪里?如果是指向当前窗口,或者是屏幕外的渲染目标?
底层硬件通常支持渲染许多不同的帧缓冲区配置。为了查询其中哪个与应用程序的需求相匹配,EGL实现必须提供一个表示受支持配置的EGLConfig实例列表。
下面的代码片段定义了一个“属性列表(attribute list)”,它是一个键/值数组,以一个EGL_NONE条目结束。属性列表指定帧缓冲区配置的要求。这里只指定了少数属性。列表中还可以包含许多其他属性。由于它们被忽略了,EGL假定它们采用EGL规范中定义的默认值。
属性列表作为参数之一传递给EGL API函数eglChooseConfig。该函数返回满足要求的EGLConfig实例列表。它们按照与请求的属性的最佳匹配进行排序。如果列表大小被限制为单个条目,则保证检索到最佳匹配配置。
EGLConfig config;
EGLint configAttribs[] =
{
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_DEPTH_SIZE, 16,
EGL_STENCIL_SIZE, 8,
#ifdef _OGLES3
// this bit opens access to ES3 functions on
// QCOM hardware pre-Android support for ES3
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT_KHR,
#else
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
#endif
EGL_NONE /* terminator */
};
eglChooseConfig(display,
configAttribs, &config,
1, /* config_size */
&numConfigs);
同时,要确保本机窗口缓冲区的尺寸和像素格式与所呈现的图像数据相匹配。因为它使用的是本机代码,所以可以通过调用ANativeWindow_setBuffersGeometry来实现这一点。本机视觉ID从所选配置中检索,并以ANativeWindow_setBuffersGeometry保证能够理解的方式定义像素格式。
eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(
m_pAndroidApp->window, ANativeWindow_getWidth
(m_pAndroidApp->window),
ANativeWindow_getHeight(m_pAndroidApp->window),
format);
为了创建一个OpenGL ES渲染上下文,有必要提供绘制和读取表面。考虑到EGL被要求创建的是OpenGL ES上下文,这意味着:
- 绘制表面对应于所有在默认帧缓冲区上操作的绘制调用将栅格化到的表面
- Read surface对应于所有在默认帧缓冲区上操作的Read调用将从其中获取数据的表面
在这种情况下,正在创建一个窗口表面,因为应用程序必须渲染到一个Android窗口。在EGL中,窗口表面总是双缓冲的。
为了创建一个表面,提供EGLDisplay句柄,以及EGLConfig实例,它们告诉EGL要创建的对象的需求。可以通过传递属性列表来进一步自定义表面行为,但在这种情况下没有必要这样做。
EGLSurface surface = eglCreateWindowSurface(
display, config,
m_pAndroidApp->window,
NULL); /* attrib_list */
现在所有必要的EGL对象都可用来创建OpenGL ES上下文。调用eglCreateContext函数,传递前面创建的EGLDisplay和EGLConfig。由于上下文不需要与任何其他呈现上下文共享其名称空间,因此将第三个参数设置为NULL。
在传递给eglCreateContext的属性列表中,EGL_CONTEXT_CLIENT_VERSION属性被设置为2。
对此的解释是,EGL为最高层次创建了一个环境以向后兼容请求版本的OpenGL ES版本,且驱动程序支持该版本。
eglCreateContext调用返回一个OpenGL ES上下文实例,但不绑定到当前线程;这是由下一行中的eglMakeCurrent调用完成的。如下面的代码片段所示,调用使用前面创建的EGLSurface实例作为绘制和读取表面。在eglMakeCurrent调用成功完成后,应用程序可以从调用eglMakeCurrent的线程开始发出OpenGL ES调用。
EGLint contextAttribs[] =
{
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE /* terminator */
};
EGLContext context = eglCreateContext(
display, config, NULL,
/* share_context */ contextAttribs);
if (eglMakeCurrent(display,
surface, /* draw */
surface, /* read */
context) == EGL_FALSE) {
return FALSE; }
1.3 Adreno GPU检测
如果应用程序需要检查是否存在Adreno GPU,它可以:
- 在活动呈现上下文中调用glGetString(GL_RENDERER)来检索包含有关于活动渲染程序特定于平台的信息的字符串。
- 检查检索到的字符串是否包含子字符串Adreno。
Adreno GPU版本也可以在GL_RENDERER字符串中找到,在“Adreno”关键字后面。
来自PlatformDetect示例应用程序的以下代码展示了如何做到这一点。它解析GL_RENDERER字符串并使用文本输出函数来显示结果。这段代码可以在文件场景中找到。const GLubyte renderer = glGetString(GL_RENDERER);。
const char* pos = strstr((char *) renderer, "Adreno");
if (pos) {
ShowText("Adreno GPU detected\n");
pos += strlen("Adreno");
if (*pos == ' ')
++pos;
if (!strncmp(pos, "(TM)", strlen("(TM)"))) {
pos += strlen("(TM)"); // Ignore TM marker
}
if (*pos == ' ')
++pos;
ShowText("Adreno version: %s\n", pos);
} else {
ShowText("Adreno GPU not detected\n");
}
1.4 检查支持的ES拓展
根据OpenGL ES的运行版本,有两种方法来检索驱动程序支持的扩展列表。
- 在OpenGL ES 2.0(或更高版本)下,通过调用来检索扩展列表:
const GLubyte* extensions = glGetString(GL_EXTENSIONS);
在执行此调用之前,将线程绑定到呈现上下文。它返回一个以空结尾的字符串,其中包含active OpenGL ES实现支持的扩展列表。
扩展名由单个空格字符分隔。当前的扩展数也可以通过OpenGL ES常量值GL_NUM_EXTENSIONS获得,可以通过glGetIntegerv调用查询。
请注意扩展字符串可以很大。应用程序绝不能断言字符串是一个特定的最大大小,或将扩展名的数量限制为特定的数量。
Adreno SDK中的平台检测示例应用程序是为OpenGL ES 2.0编写的,它使用上述方法列出了所有支持的扩展。从文件场景中检索和解析GL_EXTENSIONS字符串的代码。方法CSample::ListExtensions如下所示。
const char* extensions = (const char *) glGetString(GL_EXTENSIONS);
for (int posStart = 0, posCurrent = 0; true; ++posCurrent) {
char c = extensions[posCurrent];
if (c == ' ' || c == 0) { if (posCurrent > posStart) {
ShowText("Extension: %.*s\n", posCurrent - posStart,
extensions + posStart);
}
if (c == 0) {
break;
// reached the terminating EOS character
}
posStart = posCurrent + 1; // next extension will start
// after the space character
}
}
- 使用OpenGL ES 3.0或更高版本时,会更加简单些,因为引入了一个名为glGetStringi的新函数。这个函数允许根据索引号请求每个扩展名,这意味着不再需要编写字符串解析代码。在这种情况下,上面的代码片段可以被下面的代码替换:
glGetIntegerv(GL_NUM_EXTENSIONS, &n_extensions);
for (int n_extension = 0; n_extension < n_extensions; ++n_extension) {
const GLubyte*
extension;
extension = glGetStringi(GL_EXTENSIONS, n_extension);
ShowText("Extension: %s\n", extension);
}