前往
大廳
主題

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

Shark | 2021-04-03 01:15:06 | 巴幣 1124 | 人氣 1341

這篇是在X Window初始化OpenGL的方法,Windows的之後寫在另一篇。
因為目標是D3D和OpenGL的只看其中一篇就能看懂,一部分內容與D3D初始化那篇重覆。

網路上有些比較舊的教學用的是OpenGL 1或2,有些做法在OpenGL 3以後被列為deprecated,本系列介紹的是OpenGL 3.3,會全面使用新方法。

2021/04/16:修改deinitGL()的部分。
2022/05/23:建立視窗的方法改成先建FBConfig、取得visualID再建視窗。



OpenGL設計目標是跨平台,畫布設好之後,在不同平台上函式和常數名稱是相同的(除了swap buffer以外)。但初始化時「產生一個視窗,把這個視窗設成OpenGL的畫布」不可能跟平台無關,需要一些平台相關函式做這件事,X Window的稱為GLX,Windows的稱為WGL。

SDK安裝方法:
要安裝X Window和OpenGL的開發用套件,筆者寫這篇時用的Mint 19.3是這些名稱:libx11-dev、libgl1-mesa-dev,其他發行版就搜尋一下libx、mesa或libgl。
有幾個名稱類似的套件,要注意不是libegl、libgle或libgles。
使用OpenGL除了顯示晶片和驅動程式以外,作業系統API也要配合,Linux版的API是mesa這個團隊開發的,因此套件名稱叫做mesa。

在X Window初始化OpenGL簡單來說是:準備好Display*和Window,將Window設成OpenGL的畫布,且建立視窗的方法要改一下。
參考:Tutorial: OpenGL 3.0 Context Creation (GLX)
建立基本視窗的方法以前寫過一篇教學,以那篇為基礎來製作。
【程式】如何建一個視窗—X Window篇

#define GL_GLEXT_PROTOTYPES
#define GLX_GLXEXT_PROTOTYPES
#include<GL/gl.h>//間接引用GL/glext.h
#include<GL/glx.h>  //間接引用GL/glxext.h和X11/Xlib.h

#include<stdio.h>
#include<unistd.h>
//使用usleep()

const int WINDOW_W=200, WINDOW_H=200;
//OpenGL必要物件
Display* dsp;
Window window;
Colormap cmap;
GLXContext context;
//畫面顏色
float color[]={0,0,0,1};

//這三個函式在下面說明
static Window initGLAndCreateWindow();
static void nextFrame();
static void deinitGL();

int main(){
  dsp = XOpenDisplay(NULL);
  window=initGLAndCreateWindow();
  if(!window){
    printf("Can not initialize OpenGL\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);
  }

  deinitGL();
  XDestroyWindow( dsp, window );
  XFreeColormap(dsp, cmap);
  XFlush(dsp);
  XCloseDisplay( dsp );
  return 0;
}

跟上次的視窗程式不同的地方:
● 宣告了一個GLXContext變數,代表OpenGL系統的global狀態。
● 還有一個Colormap變數,建立視窗時會用到。
● 建視窗移到initGLAndCreateWindow()裡面,因為要先用GLX取得一些資訊才能建立視窗。
● XDestroyWindow()本來在ClientMessage裡面,移到最後面。
接收事件也有些不同。XNextEvent()會讓程式暫停,等收到訊息才繼續,「遊戲程式基本架構」有提到,遊戲程式即使玩家沒在操作時也有東西在跑,不能在這裡停下來,所以先用XPending()檢查有沒有事件,如果有才呼叫XNextEvent()取出事件。
由於break不能一次跳出一層switch和兩層while,用一個做記號的變數isEnd。

usleep()是讓程式暫停一段時間,單位為微秒(microsecond,百萬分之一秒)。這裡16000大約是1000的1/60,即60FPS。
真正寫遊戲不會直接填16000,會先檢查遊戲邏輯和繪製畫面花掉多少時間,然後算出該usleep多久。本篇是入門教學,先不做這個處理。

接下來是本篇的重點:initGLAndCreateWindow()、nextFrame()、deinitGL()這三個函式。
//傳回0代表成功,非0代表失敗
static Window initGLAndCreateWindow(){
  const int fbConfigAttr[]={
    GLX_X_RENDERABLE, True,
    GLX_DOUBLEBUFFER, True,
    GLX_X_VISUAL_TYPE, GLX_TRUE_COLOR,
    GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE,8,
    None
  };
  int fbCount;
  GLXFBConfig* fbc=glXChooseFBConfig(dsp, DefaultScreen(dsp),fbConfigAttr,&fbCount);
  if(fbc==NULL){ return 0; }

  //印出fbConfig編號
  int fbConfigID;
  glXGetFBConfigAttrib(dsp,fbc[0], GLX_FBCONFIG_ID, &fbConfigID);
  printf("fbconfig %x count %d\n", fbConfigID, fbCount);

  //產生OpenGL 3.3 context
  const int contextAttribs[] = {
    GLX_CONTEXT_MAJOR_VERSION_ARB, 3,
    GLX_CONTEXT_MINOR_VERSION_ARB, 3,
    GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
    None,
  };
  context = glXCreateContextAttribsARB(dsp, fbc[0], 0,True, contextAttribs );
  if(context==0){
    XFree(fbc);
    return 0;
  }

  //取得這個fbConfig對應的visual,用它來建立視窗
  XVisualInfo* vInfo=glXGetVisualFromFBConfig(dsp, fbc[0]);
  XSetWindowAttributes windowAttr;
  cmap = XCreateColormap(dsp, DefaultRootWindow(dsp), vInfo->visual,AllocNone);
  windowAttr.colormap = cmap;
  windowAttr.background_pixel = 0;
  windowAttr.border_pixel = 0;
  Window window = XCreateWindow(dsp, DefaultRootWindow(dsp),
    0,0,WINDOW_W,WINDOW_H,
    0, vInfo->depth, InputOutput, vInfo->visual,
    CWBackPixel|CWBorderPixel|CWColormap, &windowAttr);

  //印出visual編號
  int defaultVisualID=XVisualIDFromVisual(DefaultVisual(dsp, DefaultScreen(dsp)));
  int windowVisualID=vInfo->visualid;
  printf("defaultVisual:%x windowVisual:%x\n", defaultVisualID, windowVisualID);

  XFree(vInfo);
  XFree(fbc);

  glXMakeCurrent(dsp, window, context);

  //設定Vsync
  PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA=
    (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddress("glXSwapIntervalMESA");
  if(glXSwapIntervalMESA){
    glXSwapIntervalMESA(0);
  }
  PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI=
    (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddress("glXSwapIntervalSGI");
  if(glXSwapIntervalSGI){
    glXSwapIntervalSGI(0);
  }
  return window;
}

有些教學寫的是早期的做法,使用glXChooseVisual()和glXCreateContext(),舊方法最高只能建立OpenGL 3.0的context,本篇介紹的是新方法。

3D繪圖裡作為畫布的點陣圖稱為framebuffer,畫面本身也是一張framebuffer,首先設定我們要的framebuffer格式,除了一般點陣圖有的色彩格式,還有3D繪圖專用的屬性如multisample、Z buffer,以及一些跟X Window有關的屬性。
設定方法不是填struct或函式參數,而是一個整數陣列,內容是鍵和值交替,這裡GLX_X_RENDERABLE和GLX_DOUBLEBUFFER等等的是glx.h裡定義的常數。
C語言把陣列傳入函式的話函式內收到的只有一個指標,不知道陣列的長度,因此最後填一個None(=0)代表結束。

「GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_ALPHA_SIZE,8,」是色彩格式,RGBA各8 bit。
GLX_X_RENDERABLE、GLX_DOUBLEBUFFER如同字面意思,framebuffer設定也可以設成不可繪圖、沒有double buffer,要寫清楚我們要的是什麼。GLX_X_VISUAL_TYPE跟X Window有關,除了true color以外還有direct color,筆者也不太清楚是什麼。
其他的有些不用設,有些用預設值。程式裡沒寫到的其中兩個:
GLX_DRAWABLE_TYPE:畫在視窗還是pixmap(一種X Window的物件),本篇用預設值GLX_WINDOW_BIT。常數名稱的_BIT代表這是bit flag。
GLX_RENDER_TYPE:RGBA還是索引色,本篇用預設值RGBA。(索引色現在應該沒人在用了)

所有可以設的值和解說在這裡(除了multisample沒列上去,因為這是OpenGL 2.1的文件,multisample是之後的版本追加的)
Khronos Group glXChooseFBConfig()的說明

把陣列傳給glXChooseFBConfig(),傳回符合要求的FBConfig,通常不止一個,傳回的是陣列。FBConfig是什麼在下面「glxinfo」的部分解說。(函式名稱的FB=framebuffer)
之後印出fbConfig編號非必要,是為了跟glxinfo對照。

再來指定我們要的OpenGL版本,一樣用None結束的陣列。GLX_CONTEXT_PROFILE_MASK_ARB設定成core profile,這個模式禁用deprecated的函式,相對的是compatibility profile,可以使用舊的做法。然後用glXCreateContextAttribsARB()產生GLXContext。

下一步取出fbConfig對應的visual ID並用它來建視窗。visual ID也是用來設定framebuffer格式,這是什麼在最後「glxinfo」的部分解說。
這時要讓三個物件的framebuffer格式配合:OpenGL、視窗、螢幕。建立視窗時要把格式設成跟OpenGL一樣,否則程式可能掛掉。視窗的格式不一定跟螢幕的相同,還需要一種X Window物件:Colormap,用來在不同格式間轉換,此物件必須一直留著,等視窗刪除時再刪除。

建視窗要用另一個函式XCreateWindow(),這可以指定visual、Colormap和depth,之前用的XCreateSimpleWindow()這三項是取得親視窗的設定來用。
這邊用到的函式請自己查X Window的說明。
Xlib programming manual: function index
(經筆者測試,Intel和AMD晶片比較寬容,用XCreateSimpleWindow()建視窗而不設定visual也可以繼續執行;nVidia晶片比較嚴格,不做這一步程式容易在glXMakeCurrent()這一行掛掉)

用printf()印出螢幕與視窗的visual ID,之後用glxinfo可以查它們是什麼。

準備好context和Window後用glXMakeCurrent()將Window設成OpenGL的畫布,用XFree()釋放fbc和vInfo的記憶體空間。

最後取出擴充函式以及設vsync,這裡先關閉vsync,後面再介紹怎麼開啟。

static void nextFrame(){
  color[0]+=1.0/60;  //修改顏色的R分量
  if(color[0]>=1){
    color[0]=0;
  }
  glClearColor(color[0],color[1],color[2],color[3]);
  glClear(GL_COLOR_BUFFER_BIT);
  glXSwapBuffers(dsp, window);
}
每個frame執行一次這個函式。要是畫面一片黑我們也看不出是否成功初始化OpenGL,所以在這裡做一點事:讓畫面從黑漸變到紅。跟D3D一樣RGBA分量的範圍為0~1,此處每個frame把顏色改變1/60,也就是一秒循環一次。
glClear(GL_COLOR_BUFFER_BIT)是把畫面塗上單色。
Direct3D與OpenGL的繪圖管線(下)」有提過,OpenGL常常是在內部設定一個全域變數,之後的函式讀取這個全域變數得知要操作的物件。這裡沒有明確指定要塗上哪個framebuffer,預設就是畫面。前一行glClearColor()也是把顏色存在一個全域變數,glClear()讀取這個變數得知顏色。

glXSwapBuffers()是「遊戲程式基本架構」提到的double buffer,繪圖指令都是先畫在back buffer,再呼叫這個函式把back buffer複製到畫面上。

static void deinitGL(){
  glXDestroyContext(dsp, context);
}
程式結束時把物件刪除。OpenGL on X Window有一個地方要注意:如果視窗被刪除後呼叫glXSwapBuffers(),程式會掛掉且命令列輸出錯誤訊息,而照main()的寫法,處理完ClientMessage訊息之後還會執行一次nextFrame()才跳出迴圈,所以把XDestroyWindow()移到程式最後面。
(Direct3D和OpenGL on Windows沒這個問題,刪除視窗後呼叫Present()或SwapBuffers()頂多傳回一個值代表失敗,不會讓程式掛掉)

現在的作業系統資源管理很完善,即使你偷懶沒刪除物件,程式結束時作業系統也會把程式使用的資源釋放,不會殘留在系統裡。但順便介紹一下刪除的方法。

程式寫好,假設檔名是simplegl.c,用這個指令build
gcc simplegl.c -o simplegl -s -Os -lX11 -lGL
使用IDE的話可能要設定library。

要用命令列執行才看得到printf輸出的資訊。執行的樣子

這個例子有18個FBConfig符合條件,第一個的編號是0xb3(十六進位);visual ID是0x21和0x109,編號代表什麼意思後述。



- 暫停的方法 -

Linux的暫停有很多方法
#include<unistd.h>
unsigned int sleep(unsigned int seconds); //單位為秒
int usleep(useconds_t usec); //單位為微秒(10^-6秒)

#include<time.h>
int nanosleep(const struct timespec* req, NULL); //單位為奈秒(10^-9秒)
//用這個struct指定時間

struct timespec {
  long tv_sec;
  long tv_nsec;
};

#include<poll.h>
int poll(NULL, 0, int timeout); //單位為毫秒(1/1000秒)

#include<select.h>
int select(0,NULL,NULL,NULL, struct timeval* timeout);  //單位為微秒
//用這個struct指定時間

struct timeval {
  long tv_sec;
  long tv_usec;
};

poll()和select()本來的用途是暫停並偵測file descriptor,等到file descriptor發生變化(例如有新資料輸入)才繼續,如果file descriptor填NULL或0,就變成單純暫停一段時間。
參數是奈秒不一定真的能計時到奈秒,實際要看硬體性能,可能會呼叫一次至少都停100微秒。

在「檔案操作—Linux篇」講到有個linux.die.net的網站,筆者常在那裡查Linux的系統呼叫,這5個函式的說明如下。
https://linux.die.net/man/3/sleep
https://linux.die.net/man/3/usleep
https://linux.die.net/man/2/nanosleep
https://linux.die.net/man/2/poll
https://linux.die.net/man/2/select



- Vsync -

遊戲程式基本架構」講到的Vsync,現在很初學的階段就可以玩玩看了,不過OpenGL的vsync有一些細節要注意。

X Window設定vsync有三個函式,interval填1是啟用vsync。
int glXSwapIntervalMESA(unsigned int interval);
int glXSwapIntervalSGI(int interval);
void glXSwapIntervalEXT(Display* dpy, GLXDrawable drawable, int interval);
  //drawable填window,或用glXGetCurrentDrawable()取得
哪個函式有效在每個晶片不一樣,跟版本新舊可能也有關。筆者用手邊的電腦測試,Intel和AMD晶片只有MESA的函式可以用來開關,SGI可以開但不能關(填1才有效,填0無效),EXT完全無效;在nVidia晶片試只有SGI和EXT有效。看起來能cover三家晶片的方法是glXSwapIntervalMESA()和glXSwapIntervalSGI()都呼叫,但不知道有沒有哪個顯卡是只有EXT有效,徵求更多測試案例。

另一個要注意的是,OpenGL可以調整系統設定強制開或關vsync,讓函式呼叫無效(D3D11不能),下面是Intel晶片的方法,雖然是ArchLinux的文件但其他發行版也適用。
Intel graphics - Disable Vertical Synchronization (VSYNC)
如果安裝AMD或nVidia的專有driver,它會附一個GUI工具可調整設定。
沒特別設定的話,預設是開還是關就很難說了。

在本篇的程式把usleep()註解掉,且glXSwapIntervalMESA()和glXSwapIntervalSGI()填1啟用vsync試試看,正常的話程式仍然以穩定速率進行,如果跑得異常快那表示vsync是強制關閉。
此外筆者有遇過一台電腦,swap interval填1程式就跑得特別慢,只能用計時器定時,可能是驅動程式有問題。

由於無法預料玩家的電腦是什麼情況,寫OpenGL程式最好做到以下事項
● 明確呼叫glXSwapIntervalMESA()與glXSwapIntervalSGI()設定vsync。
● 遊戲中的Option讓玩家可以調整vsync。
● 偵測目前是否有開vsync,再看情況處理。
至於如何偵測,OpenGL沒有直接的方法 (雖然有個函式glXGetSwapIntervalMESA(),它傳回的是上次填入glXSwapIntervalMESA()的值,不能得知系統設定),筆者用的是很土的方法:呼叫glXSwapBuffers() 30次,看是不是經過0.5秒。Linux取得經過時間的函式是clock_gettime(),以後再介紹,有興趣的話先自己查。


- OpenGL的版本與擴充函式 -

這是OpenGL比較麻煩的地方,但處理程式、作業系統版本、晶片之間的相容性問題必須知道這些。

除了最早的版本就有的功能以外,各版本追加的功能是這樣加進去的:
  1. 顯卡廠商在驅動程式裡加入新函式或新參數,並為功能取一個名稱。擴充功能的命名有一定規則,此階段字頭會帶有廠商名稱,如S3、NV或AMD。
    也有可能先不公開,先跟其他廠商討論,直接進到2或3的階段再做出產品。
  2. 廠商們討論OpenGL規格時覺得這個功能好用,其他廠商也可以跟進,於是把它定為通用規格,字頭改成EXT或ARB。
  3. OpenGL出新版的時候,定OpenGL標準的團隊認為這個擴充適合加進去,就把它採納成為標準,規定有實作這個功能才能在產品標上「支援OpenGL某某版」。
    也有可能OpenGL團隊決定不採納,擴充就停留在1和2的階段。
  4. 作業系統API作者決定要支援到OpenGL哪一版,把函式和參數加進去。
Windows的OpenGL Extensions Viewer這個軟體,把哪個版本有什麼擴充整理出來
(我有找過網路上有什麼地方把哪一版有什麼擴充整理出來,沒找到,只有找到這個軟體)
  
如果某個晶片標示「支援OpenGL 3.3」,那表示一定有texture_swizzle、sampler_objects,以及3.2以前的功能,4.0以後的功能不一定有。
如果標示支援3.3卻沒有sampler objects的功能……,可以跟廠商反映標示不實。

查閱OpenGL wiki會常看到這種標示。
  
主要看的是Core since version,表示Separate attribute format的功能是在OpenGL 4.3被採納。
Core in version是有支援的最新版本,基本上一定是4.6,因為4.6是OpenGL最終版本,之後就用Vulkan取代。
擴充的規格會寫成純文字檔,在OpenGL wiki如上圖的標示點選連結可以看到。
ARB_vertex_attrib_binding的說明
由於一部分是給開發API和顯卡的團隊看的,內容很多,寫程式時主要看的是新增了哪些函式和常數。


麻煩的地方是,實際有哪些擴充各晶片和各平台會不一樣,相對地D3D比較標準化,只要是相同feature level所有晶片的功能都一樣。

第一個要注意的是比標示的版本新的功能,以及「OpenGL團隊決定不採納,停留在1和2的階段」的擴充,由於不保證所有廠商都支援,儘量不要使用以免在別人的電腦不能執行。假如作者設定程式最低支援OpenGL 3.3,那4.0以後的功能即使自己的電腦有支援也儘量別用。

但有些沒加入標準的擴充很實用,且市面上存在的廠商都已經支援,要不要用這類功能要考慮一下。
anisotropic filtering(各向異性過濾) 4.6版才加入標準;以及texture_compression_s3tc因為演算法不是自由軟體版權而沒有加入標準,但是OpenGL 3.0以後的I牌、A牌和N牌普遍都有支援,這兩個功能算是比較安全,OpenGL wiki也有特別介紹。(D3D9有這兩個功能,只要驅動程式沒有偷工減料,同一個晶片執行OpenGL程式照理說也做得到)
其他功能就不一定,例如vertex_attrib_binding和separate_shader_objects筆者認為實用,分別在4.3和4.1加入標準,筆者調查過一些晶片的支援度,OpenGL 3.3以後的I、A、N三牌都有支援,但不知道有沒有少數例外剛好筆者沒遇到,要不要用這兩個功能還在考慮。

第二個是「4.作業系統API的作者決定把哪些函式加入」。
這張圖再貼一次,一個功能即使晶片做得到,驅動程式和作業系統API沒有配合製作的話,也不能使用。

有加入API的函式不需特別處理就能使用;沒加入API但晶片和驅動程式實際有功能的不是不能用,只是要手動取出函式指標,取出的方法是上面程式裡的glXGetProcAddress()。

本篇用glXSwapIntervalMESA()和glXSwapIntervalSGI()示範這個特性。glxext.h裡有這樣的宣告:
//函式指標定義
typedef int ( *PFNGLXSWAPINTERVALMESAPROC) (unsigned int interval);
typedef int ( *PFNGLXSWAPINTERVALSGIPROC) (int interval);

//直接宣告函式
#ifdef GLX_GLXEXT_PROTOTYPES
int glXSwapIntervalMESA (unsigned int interval);
int glXSwapIntervalSGI (int interval);
#endif

使用這兩個函式有兩種方法
//1.直接打函式名稱
//程式裡要打#define GLX_GLXEXT_PROTOTYPES才能使用
//build時會在libGL.so尋找函式定義

glXSwapIntervalMESA(0);
glXSwapIntervalSGI(0);

//2.先取得函式指標再使用
//寫法如下,填一個字串參數,再把傳回值轉型
//此時libGL.so不需要有定義

PFNGLXSWAPINTERVALMESAPROC glXSwapIntervalMESA =
  (PFNGLXSWAPINTERVALMESAPROC)glXGetProcAddress("glXSwapIntervalMESA");
if(glXSwapIntervalMESA){ //檢查一下函式指標是不是NULL避免程式掛掉
  glXSwapIntervalMESA(0);
}
PFNGLXSWAPINTERVALSGIPROC glXSwapIntervalSGI =
  (PFNGLXSWAPINTERVALSGIPROC)glXGetProcAddress("glXSwapIntervalSGI");
if(glXSwapIntervalSGI){
  glXSwapIntervalSGI(0);
}
即使header有原型宣告library也不一定有定義,library沒有的函式編譯時不會跳語法錯誤,但連結和執行會跳「找不到某某函式」的錯誤,此時只能用glXGetProcAddress()的方法。
library有定義的函式兩種方法都可以用,全部都用glXGetProcAddress()最為保險,代價是程式會變得冗長。
如果glXGetProcAddress()傳回NULL表示晶片完全沒有此功能。

library有哪些函式mesa各個版本不一樣,越新的版本當然越多。例如本篇的程式,筆者測試時如果直接打函式名稱,Mint 19.3連結時會找不到glXSwapIntervalMESA(),但Fedora 33(內建軟體比Mint 19.3新)兩個函式都可以直接用,所以至少glXSwapIntervalMESA()要用取函式指標的做法,才能在這兩個發行版執行。

以前做Cyber Sprite一代的時候mesa只支援到OpenGL 1.4,而我設定最低支援OpenGL 2.0的晶片,那時需要大量使用glXGetProcAddress(),現在mesa增加很多新函式,glXGetProcAddress()可以少呼叫很多次。

綜合以上,要選定一個較舊的Linux發行版做為最低支援版本,在這個發行版開發,新版build的程式在舊版不一定能執行(不只OpenGL,寫所有Linux程式都是如此);並且要蒐集一下OpenGL各版本的資訊,與各家晶片的OpenGL支援度。



- glxinfo -

在X Window查看OpenGL資訊的軟體,Mint是包含在這個套件:mesa-utils。
在命令列直接打glxinfo會列出很大量的資訊,用「glxinfo>a.txt」把資料輸出到文字檔比較好查,「glxinfo -h」可以查命令列參數的用法。這裡示範其中一些資訊。

打「glxinfo>a.txt -s -t」,然後打開a.txt,最上面有像這樣的內容,每台電腦不會一樣
server glx vendor string: SGI
server glx version string: 1.4
server glx extensions:
    GLX_ARB_create_context
    GLX_ARB_create_context_no_error
    GLX_ARB_create_context_profile
    ……

client glx vendor string: Mesa Project and SGI
client glx version string: 1.4
    ……

GLX version: 1.4
    ……
列出這台電腦的GLX資訊,可看出GLX是1.4版,中間一堆GLX_ARB_、GLX_EXT_之類的東西是有支援的GLX擴充功能,各個功能是什麼有興趣就自己查吧。
本篇用到的vsync函式:glXSwapIntervalMESA()和glXSwapIntervalSGI(),其實是這兩個擴充功能:GLX_MESA_swap_control和GLX_SGI_swap_control。

再下面是這些
OpenGL vendor string: Intel Open Source Technology Center
OpenGL renderer string: Mesa DRI Intel(R) Sandybridge Mobile
OpenGL core profile version string: 3.3 (Core Profile) Mesa 19.0.8
OpenGL core profile shading language version string: 3.30
OpenGL core profile context flags: (none)
OpenGL core profile profile mask: core profile
OpenGL core profile extensions:
    GL_3DFX_texture_compression_FXT1
    GL_AMD_draw_buffers_blend
    GL_AMD_seamless_cubemap_per_texture
    ……

OpenGL version string: 3.0 Mesa 19.0.8
OpenGL shading language version string: 1.30
OpenGL context flags: (none)
OpenGL extensions:
    ……

OpenGL ES profile version string: OpenGL ES 3.0 Mesa 19.0.8
OpenGL ES profile shading language version string: OpenGL ES GLSL ES 3.00
OpenGL ES profile extensions:
    ……
列出三個profile各自支援的OpenGL版本、shader版本、以及擴充功能。
第一個是core profile,有支援到OpenGL 3.3,這是此晶片最高支援的OpenGL版本。第二個是compatibility profile,支援到3.0。本篇程式裡有「GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,」這一行,是在選擇用哪一個profile。

第三個是OpenGL ES版本。OpenGL ES一般印象中是給行動裝置使用,不過這張圖再拿出來一次:

作業系統API的部分mesa有寫好OpenGL和OpenGL ES的函式庫,右邊接到相同的驅動程式和晶片,所以Linux桌機版也可以使用OpenGL ES。

中間一堆GL_AMD_、GL_ARB_、GL_EXT_等等的是擴充功能,就是晶片實際有的功能,本篇「OpenGL的版本與擴充函式」提到vertex_attrib_binding、texture_compression_s3tc這些,可以在這個列表裡找找看。

再來是GLX Visuals。
74 GLX Visuals
Vis   Vis   Visual Trans  buff lev render DB ste  r   g   b   a      s  aux dep ste  accum buffer   MS   MS         
ID  Depth   Type  parent size el   type     reo sz  sz  sz  sz flt rgb buf th  ncl  r   g   b   a  num bufs caveats
--------------------------------------------------------------------------------------------------------------------
0x 21 24 TrueColor    0     32  0  rgba   1   0   8   8   8   8  .   .   0   24  8   0   0   0   0   0   0   None
0x 22 24 DirectColor  0     32  0  rgba   1   0   8   8   8   8  .   .   0   24  8   0   0   0   0   0   0   None
0x109 24 TrueColor    0     32  0  rgba   1   0   8   8   8   8  .   .   0    0  0   0   0   0   0   0   0   None
0x10a 24 TrueColor    0     32  0  rgba   0   0   8   8   8   8  .   .   0    0  0   0   0   0   0   0   0   None
0x10b 24 TrueColor    0     32  0  rgba   0   0   8   8   8   8  .   .   0   24  8   0   0   0   0   0   0   None
0x10c 24 TrueColor    0     24  0  rgba   1   0   8   8   8   0  .   .   0    0  0   0   0   0   0   0   0   None
0x10d 24 TrueColor    0     24  0  rgba   0   0   8   8   8   0  .   .   0    0  0   0   0   0   0   0   0   None
……
列出各種framebuffer格式:RGB bit數是565還是888、有沒有depth buffer和stencil buffer、有沒有accumulation buffer等等。(accumulation buffer是很早期的功能,現在已經有更好的方法替代)

最後是GLXFBConfigs的部分,可看到跟GLX Visuals類似的內容:
90 GLXFBConfigs:
Vis   Vis   Visual Trans  buff lev render DB ste  r   g   b   a      s  aux dep ste  accum buffer   MS   MS         
ID  Depth   Type  parent size el   type     reo sz  sz  sz  sz flt rgb buf th  ncl  r   g   b   a  num bufs caveats
--------------------------------------------------------------------------------------------------------------------
0x af  0 TrueColor    0     16  0  rgba   1   0   5   6   5   0  .   .   0    0  0   0   0   0   0   0   0   None
0x b0  0 TrueColor    0     16  0  rgba   0   0   5   6   5   0  .   .   0    0  0   0   0   0   0   0   0   None
0x b1  0 TrueColor    0     16  0  rgba   1   0   5   6   5   0  .   .   0   24  8   0   0   0   0   0   0   None
0x b2  0 TrueColor    0     16  0  rgba   0   0   5   6   5   0  .   .   0   24  8   0   0   0   0   0   0   None
0x b3 24 TrueColor    0     32  0  rgba   1   0   8   8   8   8  .   .   0    0  0   0   0   0   0   0   0   None
0x b4 24 TrueColor    0     32  0  rgba   0   0   8   8   8   8  .   .   0    0  0   0   0   0   0   0   0   None
0x b5 24 TrueColor    0     32  0  rgba   1   0   8   8   8   8  .   .   0   24  8   0   0   0   0   0   0   None
……
早期的glXChooseVisual()和glXCreateContext()使用GLX Visuals來設定畫面的framebuffer格式,3.0以後的方法改用GLXFBConfigs。
visual本來是X Windows的功能,GLX早期的版本也是跟據visual來設計。但後來增加一些OpenGL有但X Window沒有的功能,visual不夠用了,於是新增了FBConfig。
可以比對一下官方文件中兩個函式能用的attribList,glXChooseFBConfig()能設定的項目比較多。上面GLX Visuals的表雖然也有列出一些FBConfig專用的屬性,這些要先取得FBConfig再取得visual ID,不能用glXChooseVisual()設定。
glXChooseVisual()
glXChooseFBConfig()

「glxinfo -v」會印出比較詳細的欄位名稱,每個欄位是什麼意思請參照glXChooseFBConfig()的說明。
本篇的程式印出FBConfig編號是0xb3,以及visual ID是0x21和0x109,找到0x b3、0x 21、0x109這三列就能查到FBConfig和visual的格式。

創作回應

Ctrl+Shift+W
完全不同領域所以看不懂,不過你的每一篇文我都會看,推個
2021-04-03 02:38:22
♙♲⚙\~O_O~/⚙♲♙
+1 完全不懂 推個
想到當初某課作業是要用 FreeBSD 裝 X Window + WINE
裝完還要可以收信(裝類似outlook的東西)、文書處理(libreoffice)、畫畫(忘了)、玩3D遊戲(隨選)等
真是嚇死我了
2021-04-03 07:23:08
ays.
想問一下, 在選擇視窗上用 glx 相比 glfw 有甚麼優勢呢 (或是 glx 本身用起來的感覺)? 因為之前寫的時候都是用 glfw, 想了解一下的其他視窗lib
2021-04-03 18:59:08
Shark
實際的底層是X Window,glfw是把底層包裝一層,讓你可以用相同程式碼在不同平台執行,它內部根據不同平台使用不同的底層,如Windows用Windows API,Linux用X Window。

直接用底層的好處嘛,少一層包裝可以簡化程式、減少效能消耗,以及真的要用到平台相關功能時會比較容易配合,例如做一個編輯器,視窗一部分用OpenGL顯示畫面,另一部分要放按鈕、下拉式選單之類OS內建的UI元件。
2021-04-04 00:20:50
ays.
了解了, 感謝你~
2021-04-04 01:59:38

相關創作

更多創作