OpenGLES跨平台Context的建立

OpenGL与GLES不同点

OpenGL和GLES最大的不同点,我觉得就是对于纹理坐标的设置上。对于OpenGL,其纹理坐标原点在纹理图片的左下角,和OpenCV的图片坐标在y轴上是相反的.OpenGLES则不遵守这一规定,GLES的纹理坐标原点在左上角,和OpenCV一致。

GLES几个关键的概念

除iOS、OSX以外,其他平台初始化OpenGLES的代码段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
EGLint configSpec[] = { EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8,
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, EGL_NONE };
EGLint surfaceAttr[] = {
EGL_WIDTH, width,
EGL_HEIGHT, height,
EGL_NONE
};
this->glContext->eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
EGLint eglMajVers, eglMinVers;
EGLint numConfigs;
eglInitialize(this->glContext->eglDisp, &eglMajVers, &eglMinVers);
// std::cout << eglMajVers << " " << eglMinVers << std::endl;
eglChooseConfig(this->glContext->eglDisp, configSpec, &this->glContext->eglConf, 1, &numConfigs);
this->glContext->eglSurface = eglCreatePbufferSurface(this->glContext->eglDisp, this->glContext->eglConf, surfaceAttr);
const EGLint ctxAttr[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
this->glContext->eglCtx = eglCreateContext(this->glContext->eglDisp, this->glContext->eglConf,EGL_NO_CONTEXT, ctxAttr);
eglMakeCurrent(this->glContext->eglDisp, this->glContext->eglSurface, this->glContext->eglSurface, this->glContext->eglCtx);

整个流程是:获得DISPLAY, 在DISPLAY基础上创建Surface,将DISPLAY、Surface等一系列上下文写入创建Context中去。

  1. 获取Display。
    Display代表显示器,在有些系统上可以有多个显示器,也就会有多个Display。获得Display要调用EGLboolean eglGetDisplay(NativeDisplay dpy),参数一般为 EGL_DEFAULT_DISPLAY 。该参数实际的意义是平台实现相关的,在X-Window下是XDisplay ID,在MS Windows下是Window DC。

  2. 初始化egl。
    调用 EGLboolean eglInitialize(EGLDisplay dpy, EGLint major, EGLint minor),该函数会进行一些内部初始化工作,并传回EGL版本号(major.minor)。

  3. 选择Config。
    所谓Config实际指的是FrameBuffer的参数,在MS Windows下对应于PixelFormat,在X-Window下对应Visual。一般用EGLboolean eglChooseConfig(EGLDisplay dpy, const EGLint attr_list, EGLConfig config, EGLint config_size, EGLint num_config),其中attr_list是以EGL_NONE结束的参数数组,通常以id,value依次存放,对于个别标识性的属性可以只有 id,没有value。另一个办法是用EGLboolean eglGetConfigs(EGLDisplay dpy, EGLConfig config, EGLint config_size, EGLint *num_config) 来获得所有config。这两个函数都会返回不多于config_size个Config,结果保存在config[]中,系统的总Config个数保存 在num_config中。可以利用eglGetConfig()中间两个参数为0来查询系统支持的Config总个数。
    Config有众多的Attribute,这些Attribute决定FrameBuffer的格式和能力,通过eglGetConfigAttrib ()来读取,但不能修改。

  4. 构造Surface。
    Surface实际上就是一个FrameBuffer,通过 EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig confg, NativeWindow win, EGLint *cfg_attr) 来创建一个可实际显示的Surface。系统通常还支持另外两种Surface:PixmapSurface和PBufferSurface,这两种都不 是可显示的Surface,PixmapSurface是保存在系统内存中的位图,PBuffer则是保存在显存中的帧。
    Surface也有一些attribute,基本上都可以故名思意, EGL_HEIGHT EGL_WIDTH EGL_LARGEST_PBUFFER EGL_TEXTURE_FORMAT EGL_TEXTURE_TARGET EGL_MIPMAP_TEXTURE EGL_MIPMAP_LEVEL,通过eglSurfaceAttrib()设置、eglQuerySurface()读取。

  5. 创建Context。
    OpenGL的pipeline从程序的角度看就是一个状态机,有当前的颜色、纹理坐标、变换矩阵、绚染模式等一大堆状态,这些状态作用于程序提交的顶点 坐标等图元从而形成帧缓冲内的像素。在OpenGL的编程接口中,Context就代表这个状态机,程序的主要工作就是向Context提供图元、设置状 态,偶尔也从Context里获取一些信息。
    用EGLContext eglCreateContext(EGLDisplay dpy, EGLSurface write, EGLSurface read, EGLContext * share_list)来创建一个Context。用eglMakeCurrent来将当前context设置为默认context

  6. 绘制。
    应用程序通过OpenGL API进行绘制,一帧完成之后,调用eglSwapBuffers(EGLDisplay dpy, EGLContext ctx)来显示。

DISPLAY、Surface、Context、Configuration

  • DISPLAY可以理解为物理屏幕,对于PC就是显示器,对于手机,就是屏幕,获得这个句柄主要是用于正确放置surface,surface建立于DISPLAY以上。
  • Surface可以理解为画板,可以为物理屏幕上的像素,也可以是离屏渲染的FrameBuffer
  • Context就是设置好这些以后,所处的一个配置环境。

相应的,EGL中,
NativeDisplayType 平台显示数据类型,标识你所开发设备的物理屏幕
NativeWindowType 平台窗口数据类型,标识系统窗口
NativePixmapType 可以作为 Framebuffer 的系统图像(内存)数据类型,该类型只用于离屏渲染

获得DISPLAY

this->glContext->eglDisp = eglGetDisplay(EGL_DEFAULT_DISPLAY);
如果你只是想得到一个系统默认的 Display ,你可以使用 EGL_DEFAULT_DISPLAY 参数。如果系统中没有一个可用的 native display ID 与给定的 display 参数匹配,函数将返回 EGL_NO_DISPLAY ,而没有任何 Error 状态被设置。由于设置无效的 display 值不会有任何错误状态,在你继续操作前请检测返回值。因此,上述函数还应该检查返回的eglDisp是不是 EGL_NO_DISPLAY
assert(this->glContext->eglDisp != EGL_NO_DISPLAY)
查询到系统的Display之后,需要初始化这个eglDisp, 并且获得这个DISPLAY所支持GL版本。

Surface

Surface是可以绘制的画板,通常有屏幕像素、PixmapSurface(保存在系统内存中的位图),PBuffer(保存在显存中的帧)

iOS上的EGL配置

具体参考:https://developer.apple.com/library/archive/documentation/3DDrawing/Conceptual/OpenGLES_ProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008793

iOS和Android不一样,虽然iOS也支持EGL,但是是自己的一个版本。另外不能用纯C++来简历iOS上面的EGL上下文,必须采用部分object头来初始化。另外一点就是,EAGL不允许设置宽高等,直接获得context就好了。实际上,这个context还没有设置surface,后续才会去做这个设置。
另外,后台运行的任务,也就是没有占用屏幕的应用是无法执行OpenGL的渲染指令的。

一个iOS离屏渲染的例子

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
EAGLNativeContext eaglContext;
eaglContext.createMainContext();
eaglContext.makeCurrent();

GLuint frameBufferTexture;
GLuint frameBuffer;

int width = 640;
int height = 480;

glGenTextures(1, &frameBufferTexture);
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glBindTexture(GL_TEXTURE_2D, frameBufferTexture);

//use linear combination of pixel values when scaling and down-sampling
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// specify texture warping
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // the s axis
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // the t axis

//feed no data, and bind texture to framebuffer
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, frameBufferTexture, 0);

GLuint depthRenderbuffer;
glGenRenderbuffers(1, &depthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
ERROR("Frame Buffer InComplete");
return;
}


// begin drawing
glClearColor(0, 0, 1, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glFlush();

glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);

unsigned char tmp_data[20 * 20 * 4];
glReadPixels(0, 0, 20, 20, GL_RGBA, GL_UNSIGNED_BYTE, tmp_data);
std::cout << tmp_data << std::endl;

我们可以看到在readPixel之前tmp_data全是0,而在read_pixel之后就会变成0, 0, 0xff, 0,注意一点是,texture大小在iOS是有限制的,具体可以看context。注意这段要放在view加载之后,否则可能无法获得context。

注意,其中深度buffer有没有并不影响平面的画,但是会影响三维的draw。

总结

至此,离屏渲染实际上已经完成了跨平台,只需在android, windows, linux使用EGL,在iOS使用EAGL即可,只要对编译选项按平台稍加改造,就能够实现跨平台的opengles引擎的渲染逻辑的初始化。

0%