雖然OpenGL ES一般印象是給嵌入式裝置使用,以前貼過這張圖,這類3D API除了顯示晶片以外作業系統也要配合,只要作業系統有把它加入系統API就可以使用,Linux也有OpenGL ES的API所以也能用。

OpenGL ES把OpenGL一些以前有用但現在已過時的功能刪除,函式和常數名稱和OpenGL相同,所以大部分程式碼可以不用修改轉成OpenGL ES,除了跟作業系統打交道的部分。筆者有考慮把目前製作中遊戲從GLX改成EGL+GLES,改寫應該不難,程式會比GLX好看,而且以後轉移到Wayland或Android比較方便。
程式教學一覽
有些在「【程式】OpenGL 3.3初始化(X Window)」提過的就不重覆寫了。
Fedora 40要裝這個套件:mesa-libEGL-devel。Mint的套件名稱是libegl1-mesa-dev。
本篇目標版本是OpenGL ES 3.0,功能大致與OpenGL 3.3相同。
參考這篇的程式碼
https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_platform_x11.txt
之前「建立視窗~把視窗指定為OpenGL畫布」要用GLX這個函式庫,本篇則是用EGL。
儲存global狀態的變數有EGLDisplay、EGLContext、EGLSurface三個,其中EGLDisplay和EGLSurface是把平台原生物件包裝一層,增加一些GLES用的屬性。在X Window這兩者是將Display*和Window包裝,在Windows則是HDC和HWND。
查egl.h會發現三者都是將void*取另一個名稱,這些是不透明的物件,外部看不到內容,只能呼叫函式由函式庫內部操作。
main()的內容跟GLX篇幾乎一樣,就不解釋了。
initGLESAndCreateWindow()是跟GLX不同的主要部分。EGL的目標是不同平台儘量使用相同的函式和常數名稱。相較於glX很多函式需要傳入X Window的Display*和Window,EGL只有eglGetDisplay()和eglCreateWindowSurface()需要傳入平台相關變數,產生出EGLDisplay和EGLSurface之後就使用這兩個。
第一步是用eglGetDisplay()和eglInitialize()產生EGLDisplay物件。eglInitialize()第二、三參數傳回這台裝置上的EGL版本,不需要的話可填NULL。
第二步類似GLX的fbConfig,要設定framebuffer的格式,如RGBA各幾bits、有沒有depth buffer和stencil buffer。用一個整數陣列設定格式,鍵與值交替,最後用一個鍵EGL_NONE結束。把陣列傳入eglChooseConfig(),符合條件的EGLConfig會用第三參數傳回,可能有不只一個,第三參數填一個陣列,第四參數填陣列長度就能取得多個,但本篇只取一個。
有哪些屬性可以設定看eglChooseConfig()的說明。
eglChooseConfig - EGL Reference Pages
EGLConfig是不透明物件,用eglGetConfigAttrib()取得config ID並用printf()印出,config ID是什麼意思在下面說明。第三步用eglCreateContext()產生EGLContext,跟eglChooseConfig()一樣用EGL_NONE結束的陣列指定GLES版本。
建視窗時跟GLX一樣要讓三個物件的framebuffer格式配合:OpenGL ES、視窗、螢幕。視窗的格式必須跟GLES相同,方法是從EGLContext取得visual ID並用它產生一個XVisualInfo物件。螢幕的格式不一定和視窗相同,要產生Colormap物件用來轉換。最後把visual和Colormap傳入XCreateWindow()產生視窗。Colormap物件必須一直留著,等視窗被刪除後才能刪除。
最後用eglCreateWindowSurface()產生EGLSurface物件,第四參數用整數陣列設定屬性,但本篇沒用到。用eglMakeCurrent()設定目前要用的context和surface(GLES可以建立複數個context或surface看情況切換)。設定vsync的函式是eglSwapInterval()。
除了eglChooseConfig()以外,本段提到的函式的說明文件:
eglGetDisplay()
eglInitialize()
eglGetConfigAttrib()
eglCreateContext()
eglCreateWindowSurface()
eglMakeCurrent()
eglSwapInterval()
此外還有這兩個函式存在。
eglGetPlatformDisplay()
eglCreatePlatformWindowSurface()
是EGL 1.5版新增的,eglGetDisplay()和eglCreateWindowSurface()則是EGL最初的版本就有。
這兩個做的事跟GLX版一樣,只是函式名稱不同。
本篇還沒有寫shader,GLES的shader要把開頭的「#version 330」改成「#version 300 es」。
用這個指令build:
用命令列執行才看得到prinf()的輸出。執行的樣子:

這個例子GLES的config ID是0x01,視窗與螢幕的visual ID是0x49(十六進位),代表什麼意思在下面「eglinfo」一節說明。
vsync與擴充函式的注意事項跟GLX那篇相同故不再贅述。EGL用來取得擴充函式的函式是eglGetProcAddress()。
想查OpenGL ES哪一版有哪些功能,方法一是在以下網址查。
OpenGL ES 3.0 Reference Pages
OpenGL ES 3.1 Reference Pages
OpenGL ES 3.2 Reference Pages
方法二是查自己電腦裡的C語言header。例如筆者寫這篇時用的Fedora 40是「/usr/include/GLES3」裡的gl3.h、gl31.h和gl32.h。
看函式或常數名稱有沒有列在裡面,有就是有功能,沒有就是沒功能。例如看glCreateShader()的說明,3.0版參數可填GL_VERTEX_SHADER和GL_FRAGMENT_SHADER,3.1版增加了GL_COMPUTE_SHADER,可得知compute shader是在3.1版加入的。
GLES最終版本是3.2,功能接近OpenGL 4.3,之後就用Vulkan取代。
- eglinfo -
查看EGL與GLES資訊的軟體,Fedora 40要安裝這個套件:egl-utils。
此套件有另一個程式es2_info,但它只能取得目前平台的GLES擴充字串,用途跟eglinfo重複。
在命令列直接打eglinfo會列出很大量的資訊,用「eglinfo>a.txt」把資訊輸出到文字檔比較好查。「eglinfo -h」可以查命令列參數的用法。
首先它會列出很多平台(platform),像筆者的情況有這些:
GBM platform
Wayland platform
X11 platform
Surfaceless platform
Device platform
其中GBM platform只有這兩行,表示這台電腦不支援GBM平台。
本篇用的是X11平台,先找到「X11 platform:」的部分再看內容。
OpenGL core、OpenGL compatibility、OpenGL ES這些的extensions內容跟glxinfo顯示的一樣,就請看GLX那篇。
新的東西是Configurations。
程式執行時印出config ID是0x01,對照這個表就能查到framebuffer格式。
這個程式不會列出visual資訊,要用glxinfo才看得到。

OpenGL ES把OpenGL一些以前有用但現在已過時的功能刪除,函式和常數名稱和OpenGL相同,所以大部分程式碼可以不用修改轉成OpenGL ES,除了跟作業系統打交道的部分。筆者有考慮把目前製作中遊戲從GLX改成EGL+GLES,改寫應該不難,程式會比GLX好看,而且以後轉移到Wayland或Android比較方便。
程式教學一覽
有些在「【程式】OpenGL 3.3初始化(X Window)」提過的就不重覆寫了。
Fedora 40要裝這個套件:mesa-libEGL-devel。Mint的套件名稱是libegl1-mesa-dev。
本篇目標版本是OpenGL ES 3.0,功能大致與OpenGL 3.3相同。
參考這篇的程式碼
https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_platform_x11.txt
#include<EGL/egl.h> #include<GLES3/gl3.h> #include<X11/Xutil.h> //使用XGetVisualInfo() #include<stdio.h> #include<unistd.h> //使用usleep() const int WINDOW_W=200,WINDOW_H=200; //OpenGL ES必要物件 Display* dsp; Window window; Colormap cmap; EGLDisplay eglDsp; EGLContext context; EGLSurface surface; //畫面顏色 float color[]={0,0,0,1}; //這三個函式在下面說明 static Window initGLESAndCreateWindow(); static void nextFrame(); static void deinitGLES(); int main(){ dsp = XOpenDisplay( NULL ); window=initGLESAndCreateWindow(); if(!window){ printf("Can not initialize OpenGL ES\n"); return 0; } //設標題 XStoreName(dsp, window, "title"); //設定事件mask Atom wmDelete = XInternAtom(dsp, "WM_DELETE_WINDOW", False); XSetWMProtocols(dsp, window, &wmDelete, 1); XMapWindow(dsp, window); //接收事件 XEvent evt; int isEnd=0; while(!isEnd){ while(XPending(dsp)){ XNextEvent(dsp, &evt); switch(evt.type){ case ClientMessage: if(evt.xclient.data.l[0]== wmDelete){ isEnd=1; } break; } } //正式寫遊戲時,遊戲邏輯放在此處 nextFrame(); usleep(16000); } deinitGLES(); XDestroyWindow(dsp, window); XFreeColormap(dsp, cmap); XFlush(dsp); XCloseDisplay(dsp); return 0; } |
儲存global狀態的變數有EGLDisplay、EGLContext、EGLSurface三個,其中EGLDisplay和EGLSurface是把平台原生物件包裝一層,增加一些GLES用的屬性。在X Window這兩者是將Display*和Window包裝,在Windows則是HDC和HWND。
查egl.h會發現三者都是將void*取另一個名稱,這些是不透明的物件,外部看不到內容,只能呼叫函式由函式庫內部操作。
main()的內容跟GLX篇幾乎一樣,就不解釋了。
//X11必須先建EGLContext再建視窗 static Window initGLESAndCreateWindow(){ eglDsp=eglGetDisplay(dsp); if(eglDsp==EGL_NO_DISPLAY){ return 0; } eglInitialize(eglDsp,NULL,NULL); const int configAttrib[]={ EGL_RED_SIZE,8, EGL_GREEN_SIZE,8, EGL_BLUE_SIZE,8,EGL_ALPHA_SIZE,8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, //這個跟預設值相同,可不填 EGL_NONE, }; int numConfig; EGLConfig eglConfig; EGLBoolean ret=eglChooseConfig(eglDsp, configAttrib, &eglConfig,1,&numConfig); if(ret==EGL_FALSE){ eglTerminate(eglDsp); return 0; } //印出EGLConfig編號 EGLint configID; eglGetConfigAttrib(eglDsp, eglConfig, EGL_CONFIG_ID, &configID); printf("config ID %x\n",configID); //產生OpenGLES 3.0 context const int contextAttrib[]={ EGL_CONTEXT_MAJOR_VERSION, 3,EGL_CONTEXT_MINOR_VERSION,0, EGL_NONE, }; context=eglCreateContext(eglDsp, eglConfig, NULL, contextAttrib); if(context==0){ eglTerminate(eglDsp); return 0; } //取得這個EGLConfig的visual,用它建立視窗 EGLint visualID; eglGetConfigAttrib(eglDsp, eglConfig, EGL_NATIVE_VISUAL_ID, &visualID); XVisualInfo vInfoTemplate; vInfoTemplate.visualid=visualID; int numVisual; XVisualInfo* vInfo=XGetVisualInfo(dsp, VisualIDMask,&vInfoTemplate,&numVisual); XSetWindowAttributes windowAttr; cmap = XCreateColormap(dsp, DefaultRootWindow(dsp), vInfo->visual,AllocNone); windowAttr.colormap = cmap; window = XCreateWindow(dsp, DefaultRootWindow(dsp), 0,0,WINDOW_W,WINDOW_H, 0, vInfo->depth, InputOutput, vInfo->visual, CWColormap, &windowAttr); XFree(vInfo); //印出visual編號 int defaultVisualID=XVisualIDFromVisual(DefaultVisual(dsp, DefaultScreen(dsp))); printf("defaultVisual:%x windowVisual:%x\n",defaultVisualID,visualID); surface=eglCreateWindowSurface(eglDsp,eglConfig, window, NULL); eglMakeCurrent(eglDsp, surface,surface, context); //設定Vsync eglSwapInterval(eglDsp, 0); return window; } |
第一步是用eglGetDisplay()和eglInitialize()產生EGLDisplay物件。eglInitialize()第二、三參數傳回這台裝置上的EGL版本,不需要的話可填NULL。
第二步類似GLX的fbConfig,要設定framebuffer的格式,如RGBA各幾bits、有沒有depth buffer和stencil buffer。用一個整數陣列設定格式,鍵與值交替,最後用一個鍵EGL_NONE結束。把陣列傳入eglChooseConfig(),符合條件的EGLConfig會用第三參數傳回,可能有不只一個,第三參數填一個陣列,第四參數填陣列長度就能取得多個,但本篇只取一個。
有哪些屬性可以設定看eglChooseConfig()的說明。
eglChooseConfig - EGL Reference Pages
EGLConfig是不透明物件,用eglGetConfigAttrib()取得config ID並用printf()印出,config ID是什麼意思在下面說明。第三步用eglCreateContext()產生EGLContext,跟eglChooseConfig()一樣用EGL_NONE結束的陣列指定GLES版本。
建視窗時跟GLX一樣要讓三個物件的framebuffer格式配合:OpenGL ES、視窗、螢幕。視窗的格式必須跟GLES相同,方法是從EGLContext取得visual ID並用它產生一個XVisualInfo物件。螢幕的格式不一定和視窗相同,要產生Colormap物件用來轉換。最後把visual和Colormap傳入XCreateWindow()產生視窗。Colormap物件必須一直留著,等視窗被刪除後才能刪除。
最後用eglCreateWindowSurface()產生EGLSurface物件,第四參數用整數陣列設定屬性,但本篇沒用到。用eglMakeCurrent()設定目前要用的context和surface(GLES可以建立複數個context或surface看情況切換)。設定vsync的函式是eglSwapInterval()。
除了eglChooseConfig()以外,本段提到的函式的說明文件:
eglGetDisplay()
eglInitialize()
eglGetConfigAttrib()
eglCreateContext()
eglCreateWindowSurface()
eglMakeCurrent()
eglSwapInterval()
此外還有這兩個函式存在。
eglGetPlatformDisplay()
eglCreatePlatformWindowSurface()
是EGL 1.5版新增的,eglGetDisplay()和eglCreateWindowSurface()則是EGL最初的版本就有。
static void nextFram(){ color[0]+=1.0/60; if (color[0]>1){ color[0]=0; } glClearColor(color[0],color[1],color[2],color[3]); glClear(GL_COLOR_BUFFER_BIT); eglSwapBuffers(eglDsp, surface); } static void deinitGLES(){ eglDestroyContext(eglDsp, context); eglDestroySurface(eglDsp, surface); eglTerminate(eglDsp); } |
本篇還沒有寫shader,GLES的shader要把開頭的「#version 330」改成「#version 300 es」。
用這個指令build:
gcc simplegles.c -o simplegles -Os -s -lX11 -lEGL -lGLESv2 |
這個例子GLES的config ID是0x01,視窗與螢幕的visual ID是0x49(十六進位),代表什麼意思在下面「eglinfo」一節說明。
vsync與擴充函式的注意事項跟GLX那篇相同故不再贅述。EGL用來取得擴充函式的函式是eglGetProcAddress()。
想查OpenGL ES哪一版有哪些功能,方法一是在以下網址查。
OpenGL ES 3.0 Reference Pages
OpenGL ES 3.1 Reference Pages
OpenGL ES 3.2 Reference Pages
方法二是查自己電腦裡的C語言header。例如筆者寫這篇時用的Fedora 40是「/usr/include/GLES3」裡的gl3.h、gl31.h和gl32.h。
看函式或常數名稱有沒有列在裡面,有就是有功能,沒有就是沒功能。例如看glCreateShader()的說明,3.0版參數可填GL_VERTEX_SHADER和GL_FRAGMENT_SHADER,3.1版增加了GL_COMPUTE_SHADER,可得知compute shader是在3.1版加入的。
GLES最終版本是3.2,功能接近OpenGL 4.3,之後就用Vulkan取代。
- eglinfo -
查看EGL與GLES資訊的軟體,Fedora 40要安裝這個套件:egl-utils。
此套件有另一個程式es2_info,但它只能取得目前平台的GLES擴充字串,用途跟eglinfo重複。
在命令列直接打eglinfo會列出很大量的資訊,用「eglinfo>a.txt」把資訊輸出到文字檔比較好查。「eglinfo -h」可以查命令列參數的用法。
首先它會列出很多平台(platform),像筆者的情況有這些:
GBM platform
Wayland platform
X11 platform
Surfaceless platform
Device platform
其中GBM platform只有這兩行,表示這台電腦不支援GBM平台。
GBM platform: eglinfo: eglInitialize failed |
OpenGL core、OpenGL compatibility、OpenGL ES這些的extensions內容跟glxinfo顯示的一樣,就請看GLX那篇。
新的東西是Configurations。
Configurations: bf lv colorbuffer dp st ms vis cav bi renderable supported id sz l r g b a th cl ns b id eat nd gl es es2 vg surfaces --------------------------------------------------------------------- 0x01 32 0 8 8 8 8 0 0 0 0 0x49TC a y y y win,pb,pix 0x02 32 0 8 8 8 8 16 0 0 0 0x49TC a y y y win,pb,pix 0x03 32 0 8 8 8 8 24 0 0 0 0x49TC a y y y win,pb,pix …… |
這個程式不會列出visual資訊,要用glxinfo才看得到。