ETH官方钱包

前往
大廳
主題

【程式】OpenGL ES 3.0初始化(X Window)

Shark | 2024-09-20 20:56:42 | 巴幣 114 | 人氣 237

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

OpenGL ES把OpenGL一些以前有用但現(xiàn)在已過時的功能刪除,函式和常數(shù)名稱和OpenGL相同,所以大部分程式碼可以不用修改轉(zhuǎn)成OpenGL ES,除了跟作業(yè)系統(tǒng)打交道的部分。筆者有考慮把目前製作中遊戲從GLX改成EGL+GLES,改寫應(yīng)該不難,程式會比GLX好看,而且以後轉(zhuǎn)移到Wayland或Android比較方便。

程式教學(xué)一覽



有些在「【程式】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;
  }

  //設(shè)標題
  XStoreName(dsp, window, "title");
  //設(shè)定事件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;
}
之前「建立視窗~把視窗指定為OpenGL畫布」要用GLX這個函式庫,本篇則是用EGL。

儲存global狀態(tài)的變數(shù)有EGLDisplay、EGLContext、EGLSurface三個,其中EGLDisplay和EGLSurface是把平臺原生物件包裝一層,增加一些GLES用的屬性。在X Window這兩者是將Display*和Window包裝,在Windows則是HDC和HWND。
查egl.h會發(fā)現(xiàn)三者都是將void*取另一個名稱,這些是不透明的物件,外部看不到內(nèi)容,只能呼叫函式由函式庫內(nèi)部操作。

main()的內(nèi)容跟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, //這個跟預(yù)設(shè)值相同,可不填
    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);

  //產(chǎn)生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);

  //設(shè)定Vsync
  eglSwapInterval(eglDsp, 0);
  return window;
}
initGLESAndCreateWindow()是跟GLX不同的主要部分。EGL的目標是不同平臺儘量使用相同的函式和常數(shù)名稱。相較於glX很多函式需要傳入X Window的Display*和Window,EGL只有eglGetDisplay()和eglCreateWindowSurface()需要傳入平臺相關(guān)變數(shù),產(chǎn)生出EGLDisplay和EGLSurface之後就使用這兩個。

第一步是用eglGetDisplay()和eglInitialize()產(chǎn)生EGLDisplay物件。eglInitialize()第二、三參數(shù)傳回這臺裝置上的EGL版本,不需要的話可填NULL。

第二步類似GLX的fbConfig,要設(shè)定framebuffer的格式,如RGBA各幾bits、有沒有depth buffer和stencil buffer。用一個整數(shù)陣列設(shè)定格式,鍵與值交替,最後用一個鍵EGL_NONE結(jié)束。把陣列傳入eglChooseConfig(),符合條件的EGLConfig會用第三參數(shù)傳回,可能有不只一個,第三參數(shù)填一個陣列,第四參數(shù)填陣列長度就能取得多個,但本篇只取一個。
有哪些屬性可以設(shè)定看eglChooseConfig()的說明。
eglChooseConfig - EGL Reference Pages

EGLConfig是不透明物件,用eglGetConfigAttrib()取得config ID並用printf()印出,config ID是什麼意思在下面說明。第三步用eglCreateContext()產(chǎn)生EGLContext,跟eglChooseConfig()一樣用EGL_NONE結(jié)束的陣列指定GLES版本。

建視窗時跟GLX一樣要讓三個物件的framebuffer格式配合:OpenGL ES、視窗、螢?zāi)弧R暣暗母袷奖仨毟鶪LES相同,方法是從EGLContext取得visual ID並用它產(chǎn)生一個XVisualInfo物件。螢?zāi)坏母袷讲灰欢ê鸵暣跋嗤a(chǎn)生Colormap物件用來轉(zhuǎn)換。最後把visual和Colormap傳入XCreateWindow()產(chǎn)生視窗。Colormap物件必須一直留著,等視窗被刪除後才能刪除。

最後用eglCreateWindowSurface()產(chǎn)生EGLSurface物件,第四參數(shù)用整數(shù)陣列設(shè)定屬性,但本篇沒用到。用eglMakeCurrent()設(shè)定目前要用的context和surface(GLES可以建立複數(shù)個context或surface看情況切換)。設(shè)定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);
}
這兩個做的事跟GLX版一樣,只是函式名稱不同。
本篇還沒有寫shader,GLES的shader要把開頭的「#version 330」改成「#version 300 es」。

用這個指令build:
gcc simplegles.c -o simplegles -Os -s -lX11 -lEGL -lGLESv2
用命令列執(zhí)行才看得到prinf()的輸出。執(zhí)行的樣子:

這個例子GLES的config ID是0x01,視窗與螢?zāi)坏膙isual ID是0x49(十六進位),代表什麼意思在下面「eglinfo」一節(jié)說明。



vsync與擴充函式的注意事項跟GLX那篇相同故不再贅述。EGL用來取得擴充函式的函式是eglGetProcAddress()。
想查OpenGL ES哪一版有哪些功能,方法一是在以下網(wǎng)址查。
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。
看函式或常數(shù)名稱有沒有列在裡面,有就是有功能,沒有就是沒功能。例如看glCreateShader()的說明,3.0版參數(shù)可填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資訊的軟體,F(xiàn)edora 40要安裝這個套件:egl-utils。
此套件有另一個程式es2_info,但它只能取得目前平臺的GLES擴充字串,用途跟eglinfo重複。
在命令列直接打eglinfo會列出很大量的資訊,用「eglinfo>a.txt」把資訊輸出到文字檔比較好查?!竐glinfo -h」可以查命令列參數(shù)的用法。

首先它會列出很多平臺(platform),像筆者的情況有這些:
GBM platform
Wayland platform
X11 platform
Surfaceless platform
Device platform

其中GBM platform只有這兩行,表示這臺電腦不支援GBM平臺。
GBM platform:
eglinfo: eglInitialize failed
本篇用的是X11平臺,先找到「X11 platform:」的部分再看內(nèi)容。
OpenGL core、OpenGL compatibility、OpenGL ES這些的extensions內(nèi)容跟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
  ……
程式執(zhí)行時印出config ID是0x01,對照這個表就能查到framebuffer格式。

這個程式不會列出visual資訊,要用glxinfo才看得到。

創(chuàng)作回應(yīng)

相關(guān)創(chuàng)作

更多創(chuàng)作