在这篇文章里记录笔者开始阅读《OpenGL(R) ES 2.0 Programming Guide》得到的一些基础知识,比较零散。
我们重点从
int main ( int argc, char *argv[] )
{
ESContext esContext;
UserData userData;
esInitContext ( &esContext );
esContext.userData = &userData;
esCreateWindow ( &esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB );
if ( !Init ( &esContext ) )
return 0;
esRegisterDrawFunc ( &esContext, Draw );
esMainLoop ( &esContext );
}
//
// Book: OpenGL(R) ES 2.0 Programming Guide
// Authors: Aaftab Munshi, Dan Ginsburg, Dave Shreiner
// ISBN-10: 0321502795
// ISBN-13: 9780321502797
// Publisher: Addison-Wesley Professional
// URLs: http://safari.informit.com/9780321563835
// http://www.opengles-book.com
//
// Hello_Triangle.c
//
// This is a simple example that draws a single triangle with
// a minimal vertex/fragment shader. The purpose of this
// example is to demonstrate the basic concepts of
// OpenGL ES 2.0 rendering.
#include <stdlib.h>
#include "esUtil.h"
typedef struct
{
// Handle to a program object
GLuint programObject;
} UserData;
///
// Create a shader object, load the shader source, and
// compile the shader.
//
GLuint LoadShader ( GLenum type, const char *shaderSrc )
{
GLuint shader;
GLint compiled;
// Create the shader object
shader = glCreateShader ( type );
if ( shader == 0 )
return 0;
// Load the shader source
glShaderSource ( shader, 1, &shaderSrc, NULL );
// Compile the shader
glCompileShader ( shader );
// Check the compile status
glGetShaderiv ( shader, GL_COMPILE_STATUS, &compiled );
if ( !compiled )
{
GLint infoLen = 0;
glGetShaderiv ( shader, GL_INFO_LOG_LENGTH, &infoLen );
if ( infoLen > 1 )
{
char* infoLog = malloc (sizeof(char) * infoLen );
glGetShaderInfoLog ( shader, infoLen, NULL, infoLog );
esLogMessage ( "Error compiling shader:\n%s\n", infoLog );
free ( infoLog );
}
glDeleteShader ( shader );
return 0;
}
return shader;
}
///
// Initialize the shader and program object
//
int Init ( ESContext *esContext )
{
UserData *userData = esContext->userData;
GLbyte vShaderStr[] =
"attribute vec4 vPosition; \n"
"void main() \n"
"{ \n"
" gl_Position = vPosition; \n"
"} \n";
GLbyte fShaderStr[] =
"precision mediump float;\n"\
"void main() \n"
"{ \n"
" gl_FragColor = vec4 ( 1.0, 0.0, 0.0, 1.0 );\n"
"} \n";
GLuint vertexShader;
GLuint fragmentShader;
GLuint programObject;
GLint linked;
// Load the vertex/fragment shaders / 加载后的shader处于编译但未链接的状态
vertexShader = LoadShader ( GL_VERTEX_SHADER, vShaderStr );
fragmentShader = LoadShader ( GL_FRAGMENT_SHADER, fShaderStr );
// Create the program object
programObject = glCreateProgram ( );
if ( programObject == 0 )
return 0;
glAttachShader ( programObject, vertexShader );
glAttachShader ( programObject, fragmentShader );
// Bind vPosition to attribute 0
glBindAttribLocation ( programObject, 0, "vPosition" );
// Link the program
glLinkProgram ( programObject );
// Check the link status
glGetProgramiv ( programObject, GL_LINK_STATUS, &linked );
if ( !linked )
{
GLint infoLen = 0;
glGetProgramiv ( programObject, GL_INFO_LOG_LENGTH, &infoLen );
if ( infoLen > 1 )
{
char* infoLog = malloc (sizeof(char) * infoLen );
glGetProgramInfoLog ( programObject, infoLen, NULL, infoLog );
esLogMessage ( "Error linking program:\n%s\n", infoLog );
free ( infoLog );
}
glDeleteProgram ( programObject );
return FALSE;
}
// Store the program object
userData->programObject = programObject;
glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
return TRUE;
}
///
// Draw a triangle using the shader pair created in Init()
//
void Draw ( ESContext *esContext )
{
UserData *userData = esContext->userData;
GLfloat vVertices[] = { 0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f };
// Set the viewport /设置视口,用在vertex shader的下一阶段,比如裁减,和光栅化 。经过裁剪等处理后到达渲染管线的Fragment shader阶段
glViewport ( 0, 0, esContext->width, esContext->height );
// Clear the color buffer
glClear ( GL_COLOR_BUFFER_BIT );
// Use the program object
glUseProgram ( userData->programObject );
// Load the vertex data / 使用index 关联之前生成的vertex shader属性到 vVertices
glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vVertices );
glEnableVertexAttribArray ( 0 );
glDrawArrays ( GL_TRIANGLES, 0, 3 );
eglSwapBuffers ( esContext->eglDisplay, esContext->eglSurface );
}
int main ( int argc, char *argv[] )
{
ESContext esContext;
UserData userData;
//这是本书提供的ES框架下的通用调用,
//在方法间传递context是因为一些平台不允许声明全局变量,比如塞班。甚至在ANDROID平台上使用类是单例等全局状态也会有一些问题
//这里context包含一些屏幕参数和函数指针,一个比较重要的成员是userData,这里用来记录程序需要使用的全局变量
esInitContext ( &esContext );
esContext.userData = &userData;
esCreateWindow ( &esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB );
//初始化函数,包括创建program, 编译链接shader程序;
if ( !Init ( &esContext ) )
return 0;
//注册绘制回调函数
esRegisterDrawFunc ( &esContext, Draw );
esMainLoop ( &esContext );
}
代码取自《OpenGL(R) ES 2.0 Programming Guide》,中文注释是笔者的理解,有误非常感谢指正!代码片断主要注释了对笔者而言比较薄弱的知识点,一些glxxx函数的定义和使用读者可以自行参考opengles api 或者上面的书名得到详细的说明;上面的代码是opengles的基本操作步骤,下面来看下egl的接口和使用方法。
EGL是为了和系统做交互的中间层协议,具体可以参考书本。
这里我们关注main方法下的窗口操作,尝试理解EGL的接口。
int main ( int argc, char *argv[] )
{
ESContext esContext;
UserData userData;
//给Context分配内存
esInitContext ( &esContext );
esContext.userData = &userData;
//这个函数很重要,负责创建窗口
esCreateWindow ( &esContext, "Hello Triangle", 320, 240, ES_WINDOW_RGB );
if ( !Init ( &esContext ) )
return 0;
esRegisterDrawFunc ( &esContext, Draw );
esMainLoop ( &esContext );
}
接下来我们先看
esInitContext
///
// esInitContext()
//
// Initialize ES utility context. This must be called before calling any other
// functions.
//
void ESUTIL_API esInitContext ( ESContext *esContext )
{
if ( esContext != NULL )
{
memset( esContext, 0, sizeof( ESContext) );
}
}
就是初始化;
再看 ESContext,这里就是对EGL的一个封装结构体
typedef struct
{
/// Put your user data here...
void* userData;
/// Window width
GLint width;
/// Window height
GLint height;
/// Window handle
EGLNativeWindowType hWnd;
/// EGL display
EGLDisplay eglDisplay;
/// EGL context
EGLContext eglContext;
/// EGL surface
EGLSurface eglSurface;
/// Callbacks
void (ESCALLBACK *drawFunc) ( void* );
void (ESCALLBACK *keyFunc) ( void*, unsigned char, int, int );
void (ESCALLBACK *updateFunc) ( void*, float deltaTime );
} ESContext;
我们重点从
esCreateWindow
中看怎么使用
eglDisplay
eglContext
eglSurface
///
// esCreateWindow()
//
// title - name for title bar of window
// width - width of window to create
// height - height of window to create
// flags - bitwise or of window creation flags
// ES_WINDOW_ALPHA - specifies that the framebuffer should have alpha
// ES_WINDOW_DEPTH - specifies that a depth buffer should be created
// ES_WINDOW_STENCIL - specifies that a stencil buffer should be created
// ES_WINDOW_MULTISAMPLE - specifies that a multi-sample buffer should be created
//
GLboolean ESUTIL_API esCreateWindow ( ESContext *esContext, const char* title, GLint width, GLint height, GLuint flags )
{
EGLint attribList[] =
{
EGL_RED_SIZE, 5,
EGL_GREEN_SIZE, 6,
EGL_BLUE_SIZE, 5,
EGL_ALPHA_SIZE, (flags & ES_WINDOW_ALPHA) ? 8 : EGL_DONT_CARE,
EGL_DEPTH_SIZE, (flags & ES_WINDOW_DEPTH) ? 8 : EGL_DONT_CARE,
EGL_STENCIL_SIZE, (flags & ES_WINDOW_STENCIL) ? 8 : EGL_DONT_CARE,
EGL_SAMPLE_BUFFERS, (flags & ES_WINDOW_MULTISAMPLE) ? 1 : 0,
EGL_NONE
};
if ( esContext == NULL )
{
return GL_FALSE;
}
esContext->width = width;
esContext->height = height;
if ( !WinCreate ( esContext, title) )
{
return GL_FALSE;
}
if ( !CreateEGLContext ( esContext->hWnd,
&esContext->eglDisplay,
&esContext->eglContext,
&esContext->eglSurface,
attribList) )
{
return GL_FALSE;
}
return GL_TRUE;
}
这里先调用了WinCreate ,然后调用了CreateEGLContext。WinCreate:
///
// WinCreate()
//
// Create Win32 instance and window
//
GLboolean WinCreate ( ESContext *esContext, const char *title )
{
WNDCLASS wndclass = {0};
DWORD wStyle = 0;
RECT windowRect;
HINSTANCE hInstance = GetModuleHandle(NULL);
wndclass.style = CS_OWNDC;
wndclass.lpfnWndProc = (WNDPROC)ESWindowProc;
wndclass.hInstance = hInstance;
wndclass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
wndclass.lpszClassName = "opengles2.0";
if (!RegisterClass (&wndclass) )
return FALSE;
wStyle = WS_VISIBLE | WS_POPUP | WS_BORDER | WS_SYSMENU | WS_CAPTION;
// Adjust the window rectangle so that the client area has
// the correct number of pixels
windowRect.left = 0;
windowRect.top = 0;
windowRect.right = esContext->width;
windowRect.bottom = esContext->height;
AdjustWindowRect ( &windowRect, wStyle, FALSE );
//这里要做的是赋值到方法返回句柄
esContext->hWnd = CreateWindow(
"opengles2.0",
title,
wStyle,
0,
0,
windowRect.right - windowRect.left,
windowRect.bottom - windowRect.top,
NULL,
NULL,
hInstance,
NULL);
// Set the ESContext* to the GWL_USERDATA so that it is available to the
// ESWindowProc
SetWindowLongPtr ( esContext->hWnd, GWL_USERDATA, (LONG) (LONG_PTR) esContext );
if ( esContext->hWnd == NULL )
return GL_FALSE;
ShowWindow ( esContext->hWnd, TRUE );
return GL_TRUE;
}
通过WinCreate,得到了window返回的句柄,接下来看CreateEGLContext:
///
// CreateEGLContext()
// 这里是初始化生成所有EGL元素和context的方法,opengles根据这些状态来进行渲染管线的处理
// Creates an EGL rendering context and all associated elements
//
EGLBoolean CreateEGLContext ( EGLNativeWindowType hWnd, EGLDisplay* eglDisplay,
EGLContext* eglContext, EGLSurface* eglSurface,
EGLint attribList[])
{
EGLint numConfigs;
EGLint majorVersion;
EGLint minorVersion;
EGLDisplay display;
EGLContext context;
EGLSurface surface;
EGLConfig config;
EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };
// Get Display
display = eglGetDisplay(GetDC(hWnd));
if ( display == EGL_NO_DISPLAY )
{
return EGL_FALSE;
}
// Initialize EGL
if ( !eglInitialize(display, &majorVersion, &minorVersion) )
{
return EGL_FALSE;
}
// Get configs
if ( !eglGetConfigs(display, NULL, 0, &numConfigs) )
{
return EGL_FALSE;
}
// Choose config
if ( !eglChooseConfig(display, attribList, &config, 1, &numConfigs) )
{
return EGL_FALSE;
}
//Create a surface
//这里可以选择使用eglCreateWindowSurface实现界面渲染,也可以选择Pbuffers,相对于Framebuffer,Pbuffers可以用来做非界面渲染,也常常用来作用与位图,
//Framebuffer后面书里会讲解到,使用eglCreatePbufferSurface实现非界面渲染,Pbuffers不能直接渲染到界面上,但可以通过swapping buffers的方式使用,可以用copy或 //者bind的方式使用Pbuffers。
surface = eglCreateWindowSurface(display, config, (EGLNativeWindowType)hWnd, NULL);
if ( surface == EGL_NO_SURFACE ) { return EGL_FALSE; }
// Create a GL context 包含在所有操作需要数据的上下文,比如vertex shader和fragment shader的数据,还有顶点数据。
context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs );
if ( context == EGL_NO_CONTEXT ) { return EGL_FALSE; }
// Make the context current
if ( !eglMakeCurrent(display, surface, surface, context) ) { return EGL_FALSE; }
*eglDisplay = display;
*eglSurface = surface;
*eglContext = context;
return EGL_TRUE;
}