mesa3d源代码阅读笔记
http://www.cnblogs.com/zale_lzj/archive/2012/08/19/2646190.html
最近準備去閱讀Mesa3d(采用7.6.1版本)的源代碼,代碼量很大,感覺到十分困難。以后是否能夠讀下去,并且理解它的整個實現過程,不能確定。但既然閱讀了,就應該積累起來,特寫下本系列筆記。
1 編譯Mesa3d 7.6.1是很容易的事情,里邊包含有VC8的sln解決方案,打開它直接編譯即可完成;
2 如何調試編譯出來的opengl32.dll文件呢?其實Windows系統在檢索exe所需要的dll文件時,第一順序是在當前exe文件所在的目錄,所以只要保證編譯出的opengl32.dll與調試用的exe文件生成在同一目錄,就可以在調試時進入到對應opengl函數的實現代碼環節,并不需要將opengl32.dll拷貝到系統目錄(這樣做可以避免對系統目錄不必要的更改);
3 閱讀源代碼時,我首先是找opengl32.dll中導出的函數它具體對應的代碼函數在哪兒。嚴格地說這比較難找,因為在mesa里是使用函數分發表來處理的。在glapitemp.h頭文件里以宏的方式定義了許多函數,該文件被兩個.c文件包含,一個是dispatch.c,一個是glapi.c。前者結合dispatch.h包含glapitemp.h將對應宏展開成為對應opengl32.dll所導出的函數體定義;而后者則是定義成為一組對應opengl到處函數的相應NoOp*函數,并且將這些函數保存到__glapi_noop_table結構變量里。實際上在運行過程中,一直會有一個extern struct _glapi_table *_glapi_Dispatch;內部函數成員地址會被修正成為相應的mesa實現函數。調用opengl函數實際上是通過dispatch.c呼叫_glapi_Dispatch里所存儲的對應函數,這一過程即分發,所以說dispatch table唄;
4 當然這里wgl*系列函數和部分gl*函數都比較好找,而glBegin、glEnd以及用在它們之間的glColor*、glVertex*系列函數的分發過程就比較復雜。到現在為止,雖然調試過好幾遍mesa源代碼,但是仍然暈頭轉向的。
5 Mesa3d 7.6.1源代碼里有部分文件寫有一些說明,真的很寶貴,但遺憾的是實在太少;整個工程模塊又存有許多模塊,閱讀量很大;在光柵渲染環節存在許多函數分發表,搞得暈頭轉向。
不知道閱讀這樣的源代碼怎樣去做比較好?
?
2011-04-19 附加閱讀源碼時的opengl程序代碼
共計三個文件,代碼是在Nehe教程基礎上,附加面向對象封裝之后而成,不一定好,僅供參考。
從屬于demo項目,添加到mesa工程解決方案之下。
OpenGLDC.h/***************************************************************************** 封裝OpenGL的專用類 *****************************************************************************/ #ifndef __OPENGL_DC_H_INCLUDED #define __OPENGL_DC_H_INCLUDED#ifdef _DEBUG #include "gl/gl.h" #include "gl/glu.h"#pragma comment (lib, "opengl32.lib") #pragma comment (lib, "glu32.lib") #else #include <gl/gl.h> #include <gl/glu.h>#pragma comment (lib, "opengl32.lib") #pragma comment (lib, "glu32.lib") #endif // _DEBUG */class COpenGLDC { public:COpenGLDC();~COpenGLDC();public:GLboolean GLSetupRC(HWND hWnd);GLvoid GLRelease(GLvoid);GLboolean GLInit(GLvoid);GLvoid GLResize(GLsizei nWidth, GLsizei nHeight);GLboolean GLDrawScene(GLvoid);GLvoid ProcessKeys(bool keys[256]);protected:HWND m_hWnd;HGLRC m_hRC;HDC m_hDC; };#endif // __OPENGL_DC_H_INCLUDED?
第二個文件
OpenGLDC.cpp#ifndef VC_EXTRALEAN #define VC_EXTRALEAN // 從Windows 頭中排除極少使用的資料 #endif#include <windows.h> #include <assert.h>#include "OpenGLDC.h"COpenGLDC::COpenGLDC() { }COpenGLDC::~COpenGLDC() {GLRelease(); }GLboolean COpenGLDC::GLSetupRC(HWND hWnd) {int nPixelFormat;PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR), // 上述格式描述符的大小1, // 版本號PFD_DRAW_TO_WINDOW | // 支持在窗口中繪圖PFD_SUPPORT_OPENGL | // 支持OpenGL PFD_DOUBLEBUFFER, // 雙緩存模式PFD_TYPE_RGBA, // RGBA 顏色模式24, // 24 位顏色深度0, 0, 0, 0, 0, 0, // 忽略顏色位0, // 沒有Alpha非透明度緩存0, // 忽略移位位Shift Bit0, // 無累加緩存0, 0, 0, 0, // 忽略累加位32, // 32 位深度緩存 0, // 無模板緩存0, // 無輔助緩存PFD_MAIN_PLANE, // 主層0, // 保留0, 0, 0 // 忽略層,可見性和損毀掩模};m_hWnd = hWnd;if ((m_hDC = ::GetDC(m_hWnd)) == NULL){MessageBox(NULL, "獲取設備模式句柄失敗","錯誤",MB_OK|MB_ICONEXCLAMATION);return false;}if(!(nPixelFormat = ChoosePixelFormat(m_hDC, &pfd))){MessageBox(NULL, "沒找到合適的顯示模式","錯誤",MB_OK|MB_ICONEXCLAMATION);return false;}if(!SetPixelFormat(m_hDC, nPixelFormat, &pfd)) // 能夠設置像素格式么{MessageBox(NULL, "不能設置像素格式", "錯誤", MB_OK|MB_ICONEXCLAMATION);return false;}if(!(m_hRC = wglCreateContext(m_hDC))) // 獲取渲染描述句柄{MessageBox(NULL, "不能創建OpenGL渲染描述表", "錯誤",MB_OK|MB_ICONEXCLAMATION);return false;}if(!wglMakeCurrent(m_hDC, m_hRC)) // 嘗試激活著色描述表{MessageBox(NULL, "不能激活當前的OpenGL渲染描述表", "錯誤",MB_OK|MB_ICONEXCLAMATION);return false;}return true; }GLvoid COpenGLDC::GLRelease() {if(m_hRC){if(!wglMakeCurrent(NULL, NULL)) // 我們能否釋放DC和RC描述表{MessageBox(NULL, "釋放DC或RC失敗.", "關閉錯誤",MB_OK | MB_ICONINFORMATION);}if(!wglDeleteContext(m_hRC)) // 我們能否刪除RC?{MessageBox(NULL, "釋放RC失敗.", "關閉錯誤",MB_OK | MB_ICONINFORMATION);}m_hRC = NULL;}if(m_hDC != NULL && !::ReleaseDC(m_hWnd, m_hDC)) // 我們能否釋放DC{MessageBox(NULL, "釋放DC失敗.", "關閉錯誤",MB_OK | MB_ICONINFORMATION);}m_hDC = NULL; }GLvoid COpenGLDC::GLResize(GLsizei nWidth, GLsizei nHeight) {// 重置OpenGL窗口大小if(nHeight == 0) // 防止被零除nHeight = 1;glViewport(0, 0, nWidth, nHeight);glMatrixMode(GL_PROJECTION); // 選擇投影矩陣glLoadIdentity(); // 重置投影矩陣gluPerspective(45.0f, // 透視角設置為45 度(GLfloat)nWidth/(GLfloat)nHeight, // 窗口的寬與高比0.1f, 100.0f); // 視野透視深度:近點.1f遠點.0fglMatrixMode(GL_MODELVIEW); // 選擇模型觀察矩陣glLoadIdentity(); }// // // 主要改變增減代碼存放地 // ////GLboolean COpenGLDC::GLInit(GLvoid) {glShadeModel(GL_SMOOTH); // 啟用陰影平滑glClearColor(0.0f, 0.0f, 0.0f, 0.5f); // 黑色背景glClearDepth(1.0f); // 設置深度緩存glEnable(GL_DEPTH_TEST); // 啟用深度測試glDepthFunc(GL_LEQUAL); // 所作深度測試的類型glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // 告訴系統對透視進行修正return true; // 初始化OK }GLboolean COpenGLDC::GLDrawScene(GLvoid) {glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度緩存glLoadIdentity(); // 如果不添加這句,那么所畫的都會由大縮小,無法看見 //glTranslatef(-1.5f,0.0f,-6.0f); // 左移1.5 單位,并移入屏幕6.0glBegin(GL_LINE_LOOP); // 繪制三角形glColor3f(1.0f,0.0f,0.0f); // 設置當前色為紅色glVertex3f( 0.0f, 1.0f, 0.0f); // 上頂點glColor3f(0.0f,1.0f,0.0f); // 設置當前色為綠色glVertex3f(-1.0f,-1.0f, 0.0f); // 左下glColor3f(0.0f,0.0f,1.0f); // 設置當前色為藍色glVertex3f( 1.0f,-1.0f, 0.0f); // 右下glEnd(); // 三角形繪制結束glTranslatef(3.0f,0.0f,0.0f); // 右移單位glColor3f(0.5f,0.5f,1.0f); // 一次性將當前色設置為藍色glBegin(GL_QUADS); // 繪制正方形glVertex3f(-1.0f, 1.0f, 0.0f); // 左上glVertex3f( 1.0f, 1.0f, 0.0f); // 右上glVertex3f( 1.0f,-1.0f, 0.0f); // 左下glVertex3f(-1.0f,-1.0f, 0.0f); // 右下glEnd(); // 正方形繪制結束 //SwapBuffers(m_hDC); // 切換緩沖區,沒有切換的話,窗口不會繪制什么return true; }GLvoid COpenGLDC::ProcessKeys(bool keys[256]) { }
?
第三個文件
WinMain.cpp#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <assert.h>#include "OpenGLDC.h"COpenGLDC *s_pOpenGLDC = NULL; BOOL s_bFullScreen = FALSE; // 全屏標志缺省,缺省設定為不全屏 bool s_keys[256]; // 保存鍵盤按鍵的數組 BOOL s_bActive = TRUE; // 窗口的活動標志,缺省為TRUE HINSTANCE s_hInstance; HWND s_hWnd; char* s_pszTitle = "NeHe's顏色實例";// 窗口回調函數聲明 LRESULT CALLBACK WndProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {switch (uMsg) {case WM_ACTIVATE: // 監視窗口激活消息if (!HIWORD(wParam)) // 檢查最小化狀態s_bActive = TRUE; // 程序處于激活狀態else s_bActive = FALSE; // 程序不再激活return 0;case WM_SYSCOMMAND: // 系統中斷命令switch (wParam) // 檢查系統調用{case SC_SCREENSAVE: // 屏保要運行?case SC_MONITORPOWER: // 顯示器要進入節電模式?return 0; // 阻止發生}break; // 退出case WM_SIZE:if (s_pOpenGLDC != NULL)s_pOpenGLDC->GLResize(LOWORD(lParam), HIWORD(lParam));return 0;case WM_CLOSE:PostQuitMessage(0);return 0;case WM_KEYDOWN:s_keys[wParam] = true;return 0;case WM_KEYUP:s_keys[wParam] = false;return 0;}return DefWindowProc (hWnd, uMsg, wParam, lParam); }void KillGLWindow(void) {if (s_bFullScreen) // 我們處于全屏模式嗎?{ChangeDisplaySettings(NULL, 0); // 是的話,切換回桌面ShowCursor(TRUE); // 顯示鼠標指針}if (s_pOpenGLDC != NULL){s_pOpenGLDC->GLRelease();delete s_pOpenGLDC;s_pOpenGLDC = NULL;}if (s_hWnd && !DestroyWindow(s_hWnd)){MessageBox(NULL, "釋放窗口句柄失敗!", "關閉錯誤", MB_OK | MB_ICONINFORMATION);s_hWnd = NULL;}if (!UnregisterClass("OpenG", s_hInstance)) // 能否注銷類?{MessageBox(NULL,"不能注銷窗口類。","關閉錯誤",MB_OK | MB_ICONINFORMATION);s_hInstance = NULL;DWORD nReturnCode = GetLastError();} }/* 這個函數創建我們OpenGL窗口,參數為: ** title - 窗口標題 ** width - 窗口寬度 ** height - 窗口高度 ** bits - 顏色的位深(/16/32) ** fullscreenflag - 是否使用全屏模式,全屏模式(TRUE),窗口模式(FALSE) */ BOOL CreateGLWindow(const char* lpszTitle, int nWidth, int nHeight, int nBits, BOOL bFullScreenFlag) {WNDCLASS wndclass; // 窗口類結構DWORD dwExStyle; // 擴展窗口風格DWORD dwStyle; // 窗口風格RECT WindowRect; // 取得矩形的左上角和右下角的坐標值HWND hWnd; // 創建窗口的句柄HINSTANCE hInstance = GetModuleHandle(NULL);wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; // 移動時重畫,并為窗口取得DCwndclass.lpfnWndProc = (WNDPROC) WndProc; // WndProc處理消息wndclass.cbClsExtra = 0; // 無額外窗口數據wndclass.cbWndExtra = 0; // 無額外窗口數據wndclass.hInstance = hInstance; // 設置實例wndclass.hIcon = LoadIcon(NULL, IDI_WINLOGO); // 裝入缺省圖標wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // 裝入鼠標指針wndclass.hbrBackground = NULL; // GL不需要背景wndclass.lpszMenuName = NULL; // 不需要菜單wndclass.lpszClassName = "OpenG"; // 設定類名字if (!RegisterClass(&wndclass)) // 嘗試注冊窗口類{MessageBox(NULL,"注冊窗口失敗","錯誤",MB_OK|MB_ICONEXCLAMATION);return FALSE; // 退出并返回FALSE}s_bFullScreen = bFullScreenFlag; // 設置全局全屏標志if (s_bFullScreen) // 要嘗試全屏模式嗎?{DEVMODE dmScr; // 設備模式memset(&dmScr,0,sizeof(dmScr)); // 確保內存分配dmScr.dmSize = sizeof(dmScr); // Devmode 結構的大小dmScr.dmPelsWidth = nWidth; // 屏幕寬dmScr.dmPelsHeight= nHeight; // 屏幕高dmScr.dmBitsPerPel= nBits; // 色彩深度dmScr.dmDisplayFrequency = 75; // 刷屏速度dmScr.dmFields = DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT|DM_DISPLAYFREQUENCY;// 嘗試設置顯示模式并返回結果,注:CDC_FULLSCREEN移去了狀態條if (ChangeDisplaySettings(&dmScr, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL){// 若模式失敗,提供兩個選項:退出或在窗口內運行if (MessageBox(NULL,"全屏模式在當前顯卡上設置失敗!\n使用窗口模式?",lpszTitle, MB_YESNO|MB_ICONEXCLAMATION)==IDYES){//如果用戶選擇窗口模式,變量fullscreen 的值變為FALSE,程序繼續運行s_bFullScreen = FALSE; // 選擇窗口模式}else{//如果用戶選擇退出,彈出消息窗口告知用戶程序將結束。//并返回FALSE告訴程序窗口未能成功創建。程序退出。MessageBox(NULL,"程序將被關閉","錯誤",MB_OK|MB_ICONSTOP);return FALSE; // 退出并返回FALSE}}}if (s_bFullScreen){dwExStyle=WS_EX_APPWINDOW; // Window 擴展風格dwStyle=WS_POPUP; // Window 窗口風格ShowCursor(FALSE); // 隱藏鼠標}else {dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE; // 擴展窗體風格dwStyle = WS_OVERLAPPEDWINDOW; // 窗體風格}WindowRect.left=(long)0; // 將Left 設為0WindowRect.right=(long)nWidth; // 將Right 設為要求的寬度WindowRect.top=(long)0; // 將Top 設為0WindowRect.bottom=(long)nHeight; // 將Bottom 設為要求的高度AdjustWindowRectEx(&WindowRect, dwStyle, FALSE, dwExStyle); // 調整窗口達到真正要求的大小// 創建窗口if (!(hWnd=CreateWindowEx( dwExStyle, // 擴展窗體風格"OpenG", // 類名字lpszTitle, // 窗口標題dwStyle | // 必須的窗體風格屬性WS_CLIPSIBLINGS | // 必須的窗體風格屬性WS_CLIPCHILDREN, // 必須的窗體風格屬性CW_USEDEFAULT, CW_USEDEFAULT, // 窗口位置WindowRect.right-WindowRect.left, // 計算調整好的窗口寬度WindowRect.bottom-WindowRect.top, // 計算調整好的窗口高度NULL, // 無父窗口NULL, // 無菜單hInstance, // 實例NULL))) // 不向WM_CREATE傳遞任何東東{// 如果創建失敗KillGLWindow();MessageBox(NULL,"窗口創建錯誤","錯誤",MB_OK|MB_ICONEXCLAMATION);return FALSE; // 返回FALSE}s_hInstance = hInstance;s_hWnd = hWnd;ShowWindow(hWnd,SW_SHOW); // 顯示窗口SetForegroundWindow(hWnd); // 略略提高優先級SetFocus(hWnd); // 設置鍵盤的焦點至此窗口UpdateWindow(hWnd);s_pOpenGLDC = new COpenGLDC;assert(s_pOpenGLDC != NULL);s_pOpenGLDC->GLSetupRC(hWnd);s_pOpenGLDC->GLResize(nWidth, nHeight);if (!s_pOpenGLDC->GLInit()) // 初始化新建的GL窗口{KillGLWindow(); // 重置顯示區MessageBox(NULL, "調用OpenGLDC初始化函數失敗!", "錯誤", MB_OK | MB_ICONEXCLAMATION);return FALSE;}return TRUE; }int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) {MSG msg;BOOL done = FALSE; // 用來退出循環的BOOL變量// 提示用戶選擇運行模式 // if (MessageBox(NULL, "你想在全屏模式下運行么?", "設置全屏模式", MB_YESNO | MB_ICONQUESTION) == IDYES) // s_bFullScreen = TRUE; // else // s_bFullScreen = FALSE;// 創建OpenGL窗口if (!CreateGLWindow(s_pszTitle, 640, 480, 32, s_bFullScreen))return -1; // 失敗退出while (!done){if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) // 有消息在等待嗎?{if (msg.message == WM_QUIT) // 收到退出消息?done = TRUE; // 是,則done = TRUEelse // 不是,處理窗口消息{TranslateMessage(&msg); // 翻譯消息DispatchMessage(&msg); // 發送消息}}else {// 繪制場景。監視ESC鍵和來自DrawGLScene()的退出消息if (s_bActive){if (s_keys[VK_ESCAPE])done = TRUE;else if (s_pOpenGLDC != NULL){s_pOpenGLDC->GLDrawScene();s_pOpenGLDC->ProcessKeys(s_keys);}}if (s_keys[VK_F1]){s_keys[VK_F1] = false;KillGLWindow();s_bFullScreen = !s_bFullScreen;// 重建OpenGL窗口if (!CreateGLWindow(s_pszTitle, 640, 480, 32, s_bFullScreen))return -1; // 失敗退出}}}// 關閉程序KillGLWindow(); // 銷毀窗口return msg.wParam; // 退出程序 }自己編寫一個小的OpenGL程序,附加在mesa工程里調試,開始進入: // OpenGLDC.cpp文件,從第59行起 // 本代碼塊之前會調用ChoosePixelFormat/SetPixelFormat但不會運行mesa里的對應發布函數 /*59*/ if(!(m_hRC = wglCreateContext(m_hDC))) // 獲取渲染描述句柄{MessageBox(NULL, "不能創建OpenGL渲染描述表", "錯誤",MB_OK|MB_ICONEXCLAMATION);return false;}if(!wglMakeCurrent(m_hDC, m_hRC)) // 嘗試激活著色描述表{MessageBox(NULL, "不能激活當前的OpenGL渲染描述表", "錯誤",MB_OK|MB_ICONEXCLAMATION);return false; /*70*/ }
經第59行處的wglCreateContext函數,進入wgl.c里第179行位置的wglCreateContext,此即mesa工程的wglCreateContext發布函數,該處代碼如下所示:
wglCreateContext// wgl.c 從第179行到203行 WINGDIAPI HGLRC GLAPIENTRY wglCreateContext(HDC hdc) {int i = 0;if (!ctx_count) {for(i=0;i<MESAWGL_CTX_MAX_COUNT;i++) {wgl_ctx[i].ctx = NULL;}}for( i = 0; i < MESAWGL_CTX_MAX_COUNT; i++ ) {if ( wgl_ctx[i].ctx == NULL ) {wgl_ctx[i].ctx = WMesaCreateContext(hdc, NULL, (GLboolean)GL_TRUE,(GLboolean) (pfd[curPFD-1].doubleBuffered ?GL_TRUE : GL_FALSE), (GLboolean)(pfd[curPFD-1].pfd.cAlphaBits ? GL_TRUE : GL_FALSE) );if (wgl_ctx[i].ctx == NULL)break;ctx_count++;return ((HGLRC)wgl_ctx[i].ctx);}}SetLastError(0);return(NULL); } 在wgl.c文件聲明有wgl_ctx數組(限定大小MESAWGL_CTX_MAX_COUNT,當前取值20),ctx_count存儲該數組被初始化的數量。
第182行處判定若ctx_count大于0,則第一次初始化wgl_ctx數組所有元素全部為NULL;
第187處的for循環,目的在于尋找到一個空的wgl_ctx數組元素存儲即將創建的WMesaContext結構地址,只要創建一個,即增加ctx_count計數1,并返回所創建的結構地址(即HGLRC句柄);
第201處是不應該被執行的,當已創建的WMesaContext數量大于wgl_ctx數組寬度時,就會到達此處。所以不能創建超過20個HGLRC句柄,就本工程而言。
WMesaCreateContext函數在wmesa.c第1418行起始,該函數代碼如下所示;
WMesaCreateContext// wmesa.c 從第1418行至1531行 WMesaContext WMesaCreateContext(HDC hDC, HPALETTE* Pal,GLboolean rgb_flag,GLboolean db_flag,GLboolean alpha_flag) {WMesaContext c;struct dd_function_table functions;GLint red_bits, green_bits, blue_bits, alpha_bits;GLcontext *ctx;GLvisual *visual;(void) Pal;/* Indexed mode not supported */if (!rgb_flag)return NULL;/* Allocate wmesa context */c = CALLOC_STRUCT(wmesa_context);if (!c)return NULL;#if 0/* I do not understand this contributed code *//* Support memory and device contexts */if(WindowFromDC(hDC) != NULL) {c->hDC = GetDC(WindowFromDC(hDC)); /* huh ???? */}else {c->hDC = hDC;} #elsec->hDC = hDC; #endif/* Get data for visual *//* Dealing with this is actually a bit of overkill because Mesa will end* up treating all color component size requests less than 8 by using * a single byte per channel. In addition, the interface to the span* routines passes colors as an entire byte per channel anyway, so there* is nothing to be saved by telling the visual to be 16 bits if the device* is 16 bits. That is, Mesa is going to compute colors down to 8 bits per* channel anyway.* But we go through the motions here anyway.*/switch (GetDeviceCaps(c->hDC, BITSPIXEL)) {case 16:red_bits = green_bits = blue_bits = 5;alpha_bits = 0;break;default:red_bits = green_bits = blue_bits = 8;alpha_bits = 8;break;}/* Create visual based on flags */visual = _mesa_create_visual(rgb_flag,db_flag, /* db_flag */GL_FALSE, /* stereo */red_bits, green_bits, blue_bits, /* color RGB */alpha_flag ? alpha_bits : 0, /* color A */0, /* index bits */DEFAULT_SOFTWARE_DEPTH_BITS, /* depth_bits */8, /* stencil_bits */16,16,16, /* accum RGB */alpha_flag ? 16 : 0, /* accum A */1); /* num samples */if (!visual) {_mesa_free(c);return NULL;}/* Set up driver functions */_mesa_init_driver_functions(&functions);functions.GetString = wmesa_get_string;functions.UpdateState = wmesa_update_state;functions.GetBufferSize = wmesa_get_buffer_size;functions.Flush = wmesa_flush;functions.Clear = clear;functions.ClearIndex = clear_index;functions.ClearColor = clear_color;functions.ResizeBuffers = wmesa_resize_buffers;functions.Viewport = wmesa_viewport;/* initialize the Mesa context data */ctx = &c->gl_ctx;_mesa_initialize_context(ctx, visual, NULL, &functions, (void *)c);/* visual no longer needed - it was copied by _mesa_initialize_context() */_mesa_destroy_visual(visual);_mesa_enable_sw_extensions(ctx);_mesa_enable_1_3_extensions(ctx);_mesa_enable_1_4_extensions(ctx);_mesa_enable_1_5_extensions(ctx);_mesa_enable_2_0_extensions(ctx);_mesa_enable_2_1_extensions(ctx);/* Initialize the software rasterizer and helper modules. */if (!_swrast_CreateContext(ctx) ||!_vbo_CreateContext(ctx) ||!_tnl_CreateContext(ctx) ||!_swsetup_CreateContext(ctx)) {_mesa_free_context_data(ctx);_mesa_free(c);return NULL;}_swsetup_Wakeup(ctx);TNL_CONTEXT(ctx)->Driver.RunPipeline = _tnl_run_pipeline;return c; } 第1493行的_mesa_init_driver_functions(&functions)初始化dd_function_table結構,即設置結構的函數指針地址,該結構定義在dd.h頭文件第68行起,Device driver functions table. (設備驅動函數表),這些里的絕大多數函數直接與OpenGL狀態指令對應。
第1506行的_mesa_initialize_context函數初始化一個GLcontext結構,該結構為mesa的三大核心結構之一,成員很多。參見后文的解釋。
第1519行有4個內部模塊在做創建初始化:_swrast、_vbo、_tnl、_swsetup。
第1528行處設置_tnl管道函數,它引導了后來的對頂點變換、窗口裁剪、光照紋理計算、直至最終的渲染。
暫時停止對這些中途被呼叫內部函數的分析,來看wglMakeCurrent函數會做什么,wgl.c第234行起是工程對應的wglMakeCurrent發布函數。
wglMakeCurrent// wgl.c文件 第234行至254行 WINGDIAPI BOOL GLAPIENTRY wglMakeCurrent(HDC hdc, HGLRC hglrc) {int i;CurrentHDC = hdc;if (!hdc || !hglrc) {WMesaMakeCurrent(NULL, NULL);ctx_current = -1;return TRUE;}for ( i = 0; i < MESAWGL_CTX_MAX_COUNT; i++ ) {if ( wgl_ctx[i].ctx == (WMesaContext) hglrc ) {WMesaMakeCurrent( (WMesaContext) hglrc, hdc );ctx_current = i;return TRUE;}}return FALSE; } 第238行保存了當前設備DC句柄;
第240行檢查是否是要取消hDC與hglrc之間的關聯,如果是,則以NULL為參數調用WMesaMakeCurrent;
第246行起,for循環檢查輸入參數hglrc是否有效,即必須是先前創建時存儲過的變量地址,如果能夠找到,則調用WMesaMakeCurrent關聯hglrc與hdc設備DC;
ctx_current顯然是指向當前有效的hglrc對應的wgl_ctx數組處的序號。
之后,程序轉入到wmesa.c里的WMesaMakeCurrent函數,源代碼為:
WMesaMakeCurrent// wmesa.c 第1592行起至1648行 void WMesaMakeCurrent(WMesaContext c, HDC hdc) {WMesaFramebuffer pwfb;{/* return if already current */GET_CURRENT_CONTEXT(ctx);WMesaContext pwc = wmesa_context(ctx);if (pwc && c == pwc && pwc->hDC == hdc)return;}pwfb = wmesa_lookup_framebuffer(hdc);/* Lazy creation of framebuffers */if (c && !pwfb && hdc) {struct gl_renderbuffer *rb;GLvisual *visual = &c->gl_ctx.Visual;GLuint width, height;get_window_size(hdc, &width, &height);c->clearPen = CreatePen(PS_SOLID, 1, 0); c->clearBrush = CreateSolidBrush(0); pwfb = wmesa_new_framebuffer(hdc, visual);/* Create back buffer if double buffered */if (visual->doubleBufferMode == 1) {wmCreateBackingStore(pwfb, width, height);}/* make render buffers */if (visual->doubleBufferMode == 1) {rb = wmesa_new_renderbuffer();_mesa_add_renderbuffer(&pwfb->Base, BUFFER_BACK_LEFT, rb);wmesa_set_renderbuffer_funcs(rb, pwfb->pixelformat, pwfb->cColorBits, 1);}rb = wmesa_new_renderbuffer();_mesa_add_renderbuffer(&pwfb->Base, BUFFER_FRONT_LEFT, rb);wmesa_set_renderbuffer_funcs(rb, pwfb->pixelformat, pwfb->cColorBits, 0);/* Let Mesa own the Depth, Stencil, and Accum buffers */_mesa_add_soft_renderbuffers(&pwfb->Base,GL_FALSE, /* color */visual->depthBits > 0,visual->stencilBits > 0,visual->accumRedBits > 0,visual->alphaBits >0, GL_FALSE);}if (c && pwfb)_mesa_make_current(&c->gl_ctx, &pwfb->Base, &pwfb->Base);else_mesa_make_current(NULL, NULL, NULL); }? 第1596行至第1602行,顯然在確認hglrc與hdc是否已經是關聯的,如果是,不用做什么事直接返回即可;
第1604行查找已創建的且使用hdc的WMesaFramebuffer,若找不到則,返回NULL;
第1607處的if判斷:c(hglrc)與hdc有效,即不是要刪除,而pwfb為NULL的話,則執行第1608至1642的代碼體;
第1644行的if判斷,c(hglrc)與pwfb是否有效,若有效則調用_mesa_make_current關聯,否則用NULL參數取消關聯。
接前一文,來看WMesaMakeCurrent函數在調用_mesa_make_current之后所發生的事情:
_mesa_make_current// context.c 從第1310行到第1436行 GLboolean _mesa_make_current( GLcontext *newCtx, GLframebuffer *drawBuffer,GLframebuffer *readBuffer ) {if (MESA_VERBOSE & VERBOSE_API)_mesa_debug(newCtx, "_mesa_make_current()\n");/* Check that the context's and framebuffer's visuals are compatible.*/if (newCtx && drawBuffer && newCtx->WinSysDrawBuffer != drawBuffer) {if (!check_compatible(newCtx, drawBuffer)) {_mesa_warning(newCtx,"MakeCurrent: incompatible visuals for context and drawbuffer");return GL_FALSE;}}if (newCtx && readBuffer && newCtx->WinSysReadBuffer != readBuffer) {if (!check_compatible(newCtx, readBuffer)) {_mesa_warning(newCtx,"MakeCurrent: incompatible visuals for context and readbuffer");return GL_FALSE;}}/* We used to call _glapi_check_multithread() here. Now do it in drivers */_glapi_set_context((void *) newCtx);ASSERT(_mesa_get_current_context() == newCtx);if (!newCtx) {_glapi_set_dispatch(NULL); /* none current */}else {_glapi_set_dispatch(newCtx->CurrentDispatch);if (drawBuffer && readBuffer) {/* TODO: check if newCtx and buffer's visual match??? */ASSERT(drawBuffer->Name == 0);ASSERT(readBuffer->Name == 0);_mesa_reference_framebuffer(&newCtx->WinSysDrawBuffer, drawBuffer);_mesa_reference_framebuffer(&newCtx->WinSysReadBuffer, readBuffer);/** Only set the context's Draw/ReadBuffer fields if they're NULL* or not bound to a user-created FBO.*/if (!newCtx->DrawBuffer || newCtx->DrawBuffer->Name == 0) {/* KW: merge conflict here, revisit. *//* fix up the fb fields - these will end up wrong otherwise* if the DRIdrawable changes, and everything relies on them.* This is a bit messy (same as needed in _mesa_BindFramebufferEXT)*/unsigned int i;GLenum buffers[MAX_DRAW_BUFFERS];_mesa_reference_framebuffer(&newCtx->DrawBuffer, drawBuffer);for(i = 0; i < newCtx->Const.MaxDrawBuffers; i++) {buffers[i] = newCtx->Color.DrawBuffer[i];}_mesa_drawbuffers(newCtx, newCtx->Const.MaxDrawBuffers, buffers, NULL);}if (!newCtx->ReadBuffer || newCtx->ReadBuffer->Name == 0) {_mesa_reference_framebuffer(&newCtx->ReadBuffer, readBuffer);}/* XXX only set this flag if we're really changing the draw/read* framebuffer bindings.*/newCtx->NewState |= _NEW_BUFFERS;#if 1/* We want to get rid of these lines: */#if _HAVE_FULL_GLif (!drawBuffer->Initialized) {initialize_framebuffer_size(newCtx, drawBuffer);}if (readBuffer != drawBuffer && !readBuffer->Initialized) {initialize_framebuffer_size(newCtx, readBuffer);}_mesa_resizebuffers(newCtx); #endif#else/* We want the drawBuffer and readBuffer to be initialized by* the driver.* This generally means the Width and Height match the actual* window size and the renderbuffers (both hardware and software* based) are allocated to match. The later can generally be* done with a call to _mesa_resize_framebuffer().** It's theoretically possible for a buffer to have zero width* or height, but for now, assert check that the driver did what's* expected of it.*/ASSERT(drawBuffer->Width > 0);ASSERT(drawBuffer->Height > 0); #endifif (drawBuffer) {_mesa_check_init_viewport(newCtx,drawBuffer->Width, drawBuffer->Height);}}if (newCtx->FirstTimeCurrent) {check_context_limits(newCtx);/* We can use this to help debug user's problems. Tell them to set* the MESA_INFO env variable before running their app. Then the* first time each context is made current we'll print some useful* information.*/if (_mesa_getenv("MESA_INFO")) {_mesa_print_info();}newCtx->FirstTimeCurrent = GL_FALSE;}}return GL_TRUE; } 第1335行調用glapi.c里_glapi_set_context函數設定當前的ctx,即設定工程全局變量_glapi_Context的值指向context,在wmesa_context結構成員中首成員即GLcontext,所以二者的指針地址是相同的;
第1342行調用glapi.c里_glapi_set_dispatch設定當前的函數句柄,即設定工程全局變量_glapi_Dispatch指針指向。
在glapitable.h里定義了_glapi_table結構,它包括所有的OpenGL發布函數gl*函數指針成員變量,在gl*系列發布函數代碼體基本上都是獲取當前_glapi_Dispatch成員地址,調用該成員所指向的函數指針地址。而這些成員的賦值是在_mesa_initialize_context函數執行時所做的。而這里調用_glapi_set_dispatch將_glapi_Dispatch與具體的對應函數地址對應起來。
若沒有調用_mesa_make_current來關聯二者之前,_glapi_Dispatch初始指向__glapi_noop_table,而且如果使用NULL來調用_mesa_make_current也會使得_glapi_Dispatch指向__glapi_noop_table。
文件域全局變量__glapi_noop_table是在glapi.c里通過包含glapitemp.h定義宏替換來聲明定義的,該宏替換一方面定義了一組空的只警告又不做什么的gl*函數的翻版,起頭是Noop*,一如它們的功能一樣,然后將這些函數指針收集起來即__glapi_noop_table變量的值,顯然如果用戶沒有在事前調用wglMakeCurrent關聯到可用的hglrc的話,那么就是不應該使用gl*系列的函數的。
這里暫不關注對幀緩存區的相關操作,一時半會還關注不過來。
現在回過頭來看GLcontext結構中與_glapi_table有關的成員,以及它們的初始化賦值:
// mtypes.h /*2893*/ struct __GLcontextRec /*2894*/ {// ...... /*2898*/ /** \name API function pointer tables */ /*2899*/ /*@{*/ /*2900*/ struct _glapi_table *Save; /**< Display list save functions */ /*2901*/ struct _glapi_table *Exec; /**< Execute functions */ /*2902*/ struct _glapi_table *CurrentDispatch; /**< == Save or Exec !! */ /*2903*/ /*@}*/// ...... /*3094*/ };__GLcontextRec即GLcontext結構,成員Save和Exec均在_mesa_initialize_context時初始化,有許多gl*發布函數均在這里設定對應的函數指針地址,以下代碼嘗試弄清楚該函數所做的具體工作:
_mesa_initialize_context// context.c 從第843行起至933行 GLboolean _mesa_initialize_context(GLcontext *ctx,const GLvisual *visual,GLcontext *share_list,const struct dd_function_table *driverFunctions,void *driverContext) {struct gl_shared_state *shared;/*ASSERT(driverContext);*/assert(driverFunctions->NewTextureObject);assert(driverFunctions->FreeTexImageData);/* misc one-time initializations */one_time_init(ctx);ctx->Visual = *visual;ctx->DrawBuffer = NULL;ctx->ReadBuffer = NULL;ctx->WinSysDrawBuffer = NULL;ctx->WinSysReadBuffer = NULL;/* Plug in driver functions and context pointer here.* This is important because when we call alloc_shared_state() below* we'll call ctx->Driver.NewTextureObject() to create the default* textures.*/ctx->Driver = *driverFunctions;ctx->DriverCtx = driverContext;if (share_list) {/* share state with another context */shared = share_list->Shared;}else {/* allocate new, unshared state */shared = _mesa_alloc_shared_state(ctx);if (!shared)return GL_FALSE;}_glthread_LOCK_MUTEX(shared->Mutex);ctx->Shared = shared;shared->RefCount++;_glthread_UNLOCK_MUTEX(shared->Mutex);if (!init_attrib_groups( ctx )) {_mesa_free_shared_state(ctx, ctx->Shared);return GL_FALSE;}/* setup the API dispatch tables */ctx->Exec = alloc_dispatch_table();ctx->Save = alloc_dispatch_table();if (!ctx->Exec || !ctx->Save) {_mesa_free_shared_state(ctx, ctx->Shared);if (ctx->Exec)_mesa_free(ctx->Exec);return GL_FALSE;} #if FEATURE_dispatch_mesa_init_exec_table(ctx->Exec); #endifctx->CurrentDispatch = ctx->Exec; #if FEATURE_dlist_mesa_init_dlist_table(ctx->Save);_mesa_install_save_vtxfmt( ctx, &ctx->ListState.ListVtxfmt ); #endif/* Neutral tnl module stuff */_mesa_init_exec_vtxfmt( ctx ); ctx->TnlModule.Current = NULL;ctx->TnlModule.SwapCount = 0;ctx->FragmentProgram._MaintainTexEnvProgram= (_mesa_getenv("MESA_TEX_PROG") != NULL);ctx->VertexProgram._MaintainTnlProgram= (_mesa_getenv("MESA_TNL_PROG") != NULL);if (ctx->VertexProgram._MaintainTnlProgram) {/* this is required... */ctx->FragmentProgram._MaintainTexEnvProgram = GL_TRUE;}#ifdef FEATURE_extra_context_init_mesa_initialize_context_extra(ctx); #endifctx->FirstTimeCurrent = GL_TRUE;return GL_TRUE; } 第895行,申請_glapi_table結構,并以警告函數指針初始化,賦給ctx->Exec。而第896行與此一樣;
第904行,調用在api_exec.c里第151行的_mesa_init_exec_table函數設定ctx->Exec各成員指針指向對應的工程函數;
第905行,設定當前的函數分發表指向ctx->Exec;
第908行、第909行都是對ctx->Save的初始化,暫時不去解析;
第912行則是調用在vtxfmt.c里的_mesa_init_exec_vtxfmt函數設定ctx->Exec中對應GLvertexformat結構的成員函數指針初始化;
GLvertexformat結構在dd.h頭文件里定義,它所包含的函數都是有可能在glBegin()/glEnd()之間使用的函數。
neutral_vtxfmt是vtxfmt.c定義宏定義包含vtxfmt_tmp.h而得到的文件域全局變量,這些函數的代碼體都差不多,估計是與tnl模塊的交互;
第913行與914行初始化表明vtxfmt與tnl模塊之間還沒有交換過成員值。
執行完前面的函數之后就進入GLResize()函數,這里的它自身的源代碼如下:
// OpenGLDC.cpp 第99行至第115行 GLvoid COpenGLDC::GLResize(GLsizei nWidth, GLsizei nHeight) {// 重置OpenGL窗口大小if(nHeight == 0) // 防止被零除nHeight = 1;glViewport(0, 0, nWidth, nHeight);glMatrixMode(GL_PROJECTION); // 選擇投影矩陣glLoadIdentity(); // 重置投影矩陣gluPerspective(45.0f, // 透視角設置為45 度(GLfloat)nWidth/(GLfloat)nHeight, // 窗口的寬與高比0.6f, 100.0f); // 視野透視深度:近點.1f遠點.0fglMatrixMode(GL_MODELVIEW); // 選擇模型觀察矩陣glLoadIdentity(); }GLResize()函數第104行呼叫glViewport,這時將轉移到viewport.c處第45行_mesa_Viewport函數:
_mesa_Viewport// viewport.c 從第44行至50行 void GLAPIENTRY _mesa_Viewport(GLint x, GLint y, GLsizei width, GLsizei height) {GET_CURRENT_CONTEXT(ctx);ASSERT_OUTSIDE_BEGIN_END_AND_FLUSH(ctx);_mesa_set_viewport(ctx, x, y, width, height); } 第47行宏替換,即取_glapi_Context的值賦給聲明的GLcontext類型的ctx變量。
第48行處,則是斷言檢查,要求保證本函數在glBegin()/glEnd()之外使用,并且立即輸出還存在緩沖區的頂點FLUSH_VERTICES;
第49行調用_mesa_set_viewport正式處理需求glViewport需求;
第79行起開始設置新的VIEWPORT參數到ctx->Viewport,并寫入狀態_NEW_VIEWPORT;
第90行起調用_math_matrix_viewport來計算視口矩陣,計算公式為暫記在本子上;
第101行調用ctx所指向的Driver驅動Viewport函數地址指針,即wmesa.c里的wmesa_viewport函數,具體做一些重設幀緩存區大小之類的工作吧;
_math_matrix_viewport計算視口矩陣公式:以列為矩陣即第一行元素地址 0??? 4???? 8???? 12
[?? width / 2?????? 0???????????? 0?????????????????????????????????????????????? width /2 + x? ]
[?????? 0????????? height / 2???? 0?????????????????????????????????????????????? height /2 + y? ]
[?????? 0????????????? 0??????????? depthMax * (zFar – zNear) / 2 0?? depthMax * (zFar – zNear) / 2 0 + zNear]
[?????? 0????????????? 0???????? 0?????????????????????????????????????????????????? 0 ]
glViewport執行之后,就開始glMatrixMode(GL_PROJECTION);語句的執行了,在matrix.c第146行處的_mesa_MatrixMode:
_mesa_MatrixMode// matrix.c 從第145行至228行 void GLAPIENTRY _mesa_MatrixMode( GLenum mode ) {GET_CURRENT_CONTEXT(ctx);ASSERT_OUTSIDE_BEGIN_END(ctx);if (ctx->Transform.MatrixMode == mode && mode != GL_TEXTURE)return;FLUSH_VERTICES(ctx, _NEW_TRANSFORM);switch (mode) {case GL_MODELVIEW:ctx->CurrentStack = &ctx->ModelviewMatrixStack;break;case GL_PROJECTION:ctx->CurrentStack = &ctx->ProjectionMatrixStack;break;case GL_TEXTURE:/* This error check is disabled because if we're called from* glPopAttrib() when the active texture unit is >= MaxTextureCoordUnits* we'll generate an unexpected error.* From the GL_ARB_vertex_shader spec it sounds like we should instead* do error checking in other places when we actually try to access* texture matrices beyond MaxTextureCoordUnits.*/ #if 0if (ctx->Texture.CurrentUnit >= ctx->Const.MaxTextureCoordUnits) {_mesa_error(ctx, GL_INVALID_OPERATION, "glMatrixMode(invalid tex unit %d)",ctx->Texture.CurrentUnit);return;} #endifASSERT(ctx->Texture.CurrentUnit < Elements(ctx->TextureMatrixStack));ctx->CurrentStack = &ctx->TextureMatrixStack[ctx->Texture.CurrentUnit];break;case GL_COLOR:ctx->CurrentStack = &ctx->ColorMatrixStack;break;case GL_MATRIX0_NV:case GL_MATRIX1_NV:case GL_MATRIX2_NV:case GL_MATRIX3_NV:case GL_MATRIX4_NV:case GL_MATRIX5_NV:case GL_MATRIX6_NV:case GL_MATRIX7_NV:if (ctx->Extensions.NV_vertex_program) {ctx->CurrentStack = &ctx->ProgramMatrixStack[mode - GL_MATRIX0_NV];}else {_mesa_error( ctx, GL_INVALID_ENUM, "glMatrixMode(mode)" );return;}break;case GL_MATRIX0_ARB:case GL_MATRIX1_ARB:case GL_MATRIX2_ARB:case GL_MATRIX3_ARB:case GL_MATRIX4_ARB:case GL_MATRIX5_ARB:case GL_MATRIX6_ARB:case GL_MATRIX7_ARB:if (ctx->Extensions.ARB_vertex_program ||ctx->Extensions.ARB_fragment_program) {const GLuint m = mode - GL_MATRIX0_ARB;if (m > ctx->Const.MaxProgramMatrices) {_mesa_error(ctx, GL_INVALID_ENUM,"glMatrixMode(GL_MATRIX%d_ARB)", m);return;}ctx->CurrentStack = &ctx->ProgramMatrixStack[m];}else {_mesa_error( ctx, GL_INVALID_ENUM, "glMatrixMode(mode)" );return;}break;default:_mesa_error( ctx, GL_INVALID_ENUM, "glMatrixMode(mode)" );return;}ctx->Transform.MatrixMode = mode; } 第151行處檢查如果當前的模式正好匹配,而且不是紋理模式的話,即可返回,不需要重復做事;
第155行根據當前mode的設定值,設定當前所要操作的矩陣棧,當前只需要對投影矩陣棧和模型視圖矩陣棧有所了解即可;
第227行設定ctx->Transform.MatrixMode表征當前的矩陣模式;
然后開始OpenGLDC.cpp里第107行的glLoadIdentity();語句執行,對應在matrix.c第319行處的_mesa_LoadIdentity()函數:
_mesa_LoadIdentity// matrix 從第318行至329行 void GLAPIENTRY _mesa_LoadIdentity( void ) {GET_CURRENT_CONTEXT(ctx);ASSERT_OUTSIDE_BEGIN_END_AND_FLUSH(ctx);if (MESA_VERBOSE & VERBOSE_API)_mesa_debug(ctx, "glLoadIdentity()");_math_matrix_set_identity( ctx->CurrentStack->Top );ctx->NewState |= ctx->CurrentStack->DirtyFlag; }第327行調用_math_matrix_set_identity函數將當前矩陣棧頂矩陣設為Identity矩陣,它在m_matrix.c的第1142行處,采取MEMCPY內存拷貝的形式將一個Identity矩陣的數據拷貝至GLmatrix矩陣;
然后就是OpenGLDC.cpp的第109行處的gluPerspective語句了,這是屬于glu工程的發布函數,在project.c第65行處的gluPerspective位置,其矩陣計算暫時記在本子里,然后使用gluMultMatrixd將矩陣乘至當前的矩陣棧頂ctx->CurrentStack->Top;
gluPerspective視圖投影矩陣計算公式:
[ cos(fovy / 2) / (sin(fovy / 2) * aspect)?????? 0????????????? 0?????????? 0]
[ 0????? cos(fovy/2)/sin(fovy/2)???????????? 0????????????????????????????????? 0]
[ 0??????????????? 0????? -(zFar + zNear) / (zFar – zNear)??? -(2*zFar*zNear)/(zFar - zNear)]
[ 0??????????????? 0???????????????????????????????? -1????????????????????????????????? 0]
然后再執行OpenGLDC.cpp的第113行處glMatrixMode(GL_MODELVIEW);它將當前矩陣棧ctx->CurrentStack指向ModelviewMatrixStack,然后接下來的glLoadIdentity會使棧頂矩陣重置為單位矩陣;
在GLResize()函數執行完之后,會進入GLInit執行用戶的初始化語句,但這里暫時略過這一部分的內容。
重點來看GLDrawScene()函數的調試執行:
首先是glClear語句,此語句關聯clear.c里第112行處的_mesa_Clear函數,它做了不少事情,但當下并非我注意的重點對象,因為我還不大明白自己要去關注什么。收集一下當下可能需要的關注方向:
(1) 在_mesa_update_state_locked里調用的_mesa_update_modelview_project計算模型視圖投影矩陣;需要了解一下ctx->Transform與cull position有關的成員;后面會計算ctx->_ModelProjectMatrix,因為此時的ModelViewMatrixStack.Top為單位矩陣,所以結果就是投影矩陣;
(2) 該處也會調用update_viewport_matrix函數;
(3) 最后會調用ctx所帶驅動函數地址處記錄的Clear,即wmesa.c處第289行的clear函數;
再就是glLoadIdentity();語句的執行,前面已經看到過兩次了,因為此時矩陣模式還時GL_MODELVIEW,所以它執行的結果就是將ctx->ModelviewMatrixStack重置為單位矩陣;
接下來就是glTranslatef(-1.5f, 0.0f, -6.0f);語句,它對應執行matrix.c里第454行處的_mesa_Translatef函數,它的重點是檢查是否需要做一點別的事情,然后調用_math_matrix_translate來完成平移矩陣,因為前面為單位矩陣,而這里僅僅修改矩陣的平移項:
最后是重頭戲,glBegin()/glEnd()以及在它們之間調用的函數。
glBegin()函數首先由ctx->Exec所指向的函數指針轉移至neutral_Begin,neutral_Begin由宏包裹著,展開之后將是這樣的語句:
static void __stdcall neutral_Begin( GLenum mode ) {{ GLcontext *ctx = (GLcontext *) _glapi_Context; struct gl_tnl_module * const tnl = &(ctx->TnlModule); const int tmp_offset = 7 ;; ; ; if (tnl->SwapCount == 0) ctx->Driver.BeginVertices( ctx ); tnl->Swapped[tnl->SwapCount].location = & (((_glapi_proc *)ctx->Exec)[tmp_offset]); tnl->Swapped[tnl->SwapCount].function = (_glapi_proc)neutral_Begin; tnl->SwapCount++; if ( 0 ) _mesa_debug(ctx, " swapping gl" "Begin""...\n" ); ((ctx->Exec)->Begin = tnl->Current->Begin); };(*((_glapi_Dispatch)->Begin)) ( mode ); }執行過程是這樣的,如果是首次被調用,則會通過ctx->Driver來調用BeginVertices(即vbo_exec_api.c里第750行處的vbo_exec_BeginVertices),然后在tnl模塊的Swapped處記錄交交換的函數地址,從而將vbo_exec_Begin換至當前ctx->Exec處的對應glBegin函數指針位置處,然后調用vbo_exec_Begin;這就是neutral_Begin的執行過程。
vbo_exec_BeginVertices函數的執行過程,暫略。
vbo_exec_Begin函數的執行過程,在vbo_exec_api.c第492行處:
vbo_exec_Begin// vbo_exec_api.c 從第492行至532行 static void GLAPIENTRY vbo_exec_Begin( GLenum mode ) {GET_CURRENT_CONTEXT( ctx ); if (ctx->Driver.CurrentExecPrimitive == PRIM_OUTSIDE_BEGIN_END) {struct vbo_exec_context *exec = &vbo_context(ctx)->exec;int i;if (ctx->NewState) {_mesa_update_state( ctx );CALL_Begin(ctx->Exec, (mode));return;}if (!_mesa_valid_to_render(ctx, "glBegin")) {return;}/* Heuristic: attempt to isolate attributes occuring outside* begin/end pairs.*/if (exec->vtx.vertex_size && !exec->vtx.attrsz[0]) vbo_exec_FlushVertices_internal( ctx, GL_FALSE );i = exec->vtx.prim_count++;exec->vtx.prim[i].mode = mode;exec->vtx.prim[i].begin = 1;exec->vtx.prim[i].end = 0;exec->vtx.prim[i].indexed = 0;exec->vtx.prim[i].weak = 0;exec->vtx.prim[i].pad = 0;exec->vtx.prim[i].start = exec->vtx.vert_count;exec->vtx.prim[i].count = 0;ctx->Driver.CurrentExecPrimitive = mode;}else _mesa_error( ctx, GL_INVALID_OPERATION, "glBegin" );} 第496行處檢查是否在glBegin()/glEnd()之外調用,如果不是,則調用_mesa_error函數非法操作;
第500行處檢查是否有新的狀態變化,發現有ctx->NewState,則調用_mesa_update_state來更新狀態,這個函數就是先前glClear也會調用來更新狀態的那個函數,然后再調用一次vbo_exec_Begin之后退出程序;
實際上更新狀態之后的調用與沒有狀態時的調用除了前者會調用狀態更新函數之外,其余部分沒有區別,從第514行起到第527行處的代碼語句都要執行。
這就是glBegin()的執行過程。
glColor3f(1.0f, 0.0f, 0.0f);語句的對應執行流程,與glBegin()一樣經過ctx->Exec指向對應的neutral_Color3f函數,該函數被包裹在宏的聲明里,展開之后的語句是這樣的:
neutral_Color3f// vtxfmt.c 包含vtxfmt_tmp.h展開宏而產生 static void __stdcall neutral_Color3f( GLfloat r, GLfloat g, GLfloat b ) {{ GLcontext *ctx = (GLcontext *) _glapi_Context; struct gl_tnl_module * const tnl = &(ctx->TnlModule); const int tmp_offset = 13 ; ; ; ; if (tnl->SwapCount == 0) ctx->Driver.BeginVertices( ctx ); tnl->Swapped[tnl->SwapCount].location = & (((_glapi_proc *)ctx->Exec)[tmp_offset]); tnl->Swapped[tnl->SwapCount].function = (_glapi_proc)neutral_Color3f; tnl->SwapCount++; if ( 0 ) _mesa_debug(ctx, " swapping gl" "Color3f""...\n" ); ((ctx->Exec)->Color3f = tnl->Current->Color3f); };(*((_glapi_Dispatch)->Color3f)) ( r, g, b ); }樣式與neutral_Begin差不多,畢竟是同一批宏展開。這里不會再執行ctx->Driver.BeginVertices,記錄使用過的函數地址之后,轉而執行vbo_Color3f。
vbo_Color3f// vbo_exec_api.c 通過包含vbo_attrib_tmp.h頭文件產生 static void __stdcall vbo_Color3f( GLfloat x, GLfloat y, GLfloat z ) {GLcontext *ctx = (GLcontext *) _glapi_Context;do { struct vbo_exec_context *exec = &vbo_context(ctx)->exec; if (exec->vtx.active_sz[VBO_ATTRIB_COLOR0] != 3) vbo_exec_fixup_vertex(ctx, VBO_ATTRIB_COLOR0, 3); { GLfloat *dest = exec->vtx.attrptr[VBO_ATTRIB_COLOR0]; if (3>0) dest[0] = x; if (3>1) dest[1] = y; if (3>2) dest[2] = z; if (3>3) dest[3] = 1; } if ((VBO_ATTRIB_COLOR0) == 0) { GLuint i; for (i = 0; i < exec->vtx.vertex_size; i++) exec->vtx.buffer_ptr[i] = exec->vtx.vertex[i]; exec->vtx.buffer_ptr += exec->vtx.vertex_size; exec->ctx->Driver.NeedFlush |= 0x1; if (++exec->vtx.vert_count >= exec->vtx.max_vert) vbo_exec_vtx_wrap( exec ); } } while (0); } 第4行獲取vbo_exec_context指針;
第5行比對exec->vtx.active_sz[VBO_ATTRIB_COLOR0]是否等于3,若不等于3,則要修正頂點數據,在第一次調用這個函數時,該值為0,故會執行該函數;
?
而glVertex3f(0.0f, 1.0f, 0.0f);語句呢,也基本與glColor3f相似,其中neutral_Vertex3f對應的源代碼如下:
neutral_Vertex3fstatic void __stdcall neutral_Vertex3f( GLfloat x, GLfloat y, GLfloat z ) {{ GLcontext *ctx = (GLcontext *) _glapi_Context; struct gl_tnl_module * const tnl = &(ctx->TnlModule); const int tmp_offset = 136 ; ; ; ; if (tnl->SwapCount == 0) ctx->Driver.BeginVertices( ctx ); tnl->Swapped[tnl->SwapCount].location = & (((_glapi_proc *)ctx->Exec)[tmp_offset]); tnl->Swapped[tnl->SwapCount].function = (_glapi_proc)neutral_Vertex3f; tnl->SwapCount++; if ( 0 ) _mesa_debug(ctx, " swapping gl" "Vertex3f""...\n" ); ((ctx->Exec)->Vertex3f = tnl->Current->Vertex3f); };(*((_glapi_Dispatch)->Vertex3f)) ( x, y, z ); }?
該函數執行后,會轉至vbo_Vertex3f函數執行:
vbo_Vertex3fstatic void __stdcall vbo_Vertex3f( GLfloat x, GLfloat y, GLfloat z ) {GLcontext *ctx = (GLcontext *) _glapi_Context;do { struct vbo_exec_context *exec = &vbo_context(ctx)->exec; if (exec->vtx.active_sz[VBO_ATTRIB_POS] != 3) vbo_exec_fixup_vertex(ctx, VBO_ATTRIB_POS, 3); { GLfloat *dest = exec->vtx.attrptr[VBO_ATTRIB_POS]; if (3>0) dest[0] = x; if (3>1) dest[1] = y; if (3>2) dest[2] = z; if (3>3) dest[3] = 1; } if ((VBO_ATTRIB_POS) == 0) { GLuint i; for (i = 0; i < exec->vtx.vertex_size; i++) exec->vtx.buffer_ptr[i] = exec->vtx.vertex[i];exec->vtx.buffer_ptr += exec->vtx.vertex_size; exec->ctx->Driver.NeedFlush |= 0x1; if (++exec->vtx.vert_count >= exec->vtx.max_vert) vbo_exec_vtx_wrap( exec ); } } while (0); }其執行與前面vbo_Color3f相似,但這里我一直弄不大清楚到底數據存儲到哪里去了呢?而程序在后面又是怎樣找到這些存儲的數據呢?
再一次重復執行glColor3f和glVertex3f時,因為前面已經登記交換函數地址,所以會直接轉到vbo_Color3f和vbo_Vertex3f執行,與前類似。
最后是glEnd();語句的執行,與前類似,首先是neutral_End:
neutral_Endstatic void __stdcall neutral_End( void ) {{ GLcontext *ctx = (GLcontext *) _glapi_Context; struct gl_tnl_module * const tnl = &(ctx->TnlModule); const int tmp_offset = 43 ; ; ; ; if (tnl->SwapCount == 0) ctx->Driver.BeginVertices( ctx ); tnl->Swapped[tnl->SwapCount].location = & (((_glapi_proc *)ctx->Exec)[tmp_offset]); tnl->Swapped[tnl->SwapCount].function = (_glapi_proc)neutral_End; tnl->SwapCount++; if ( 0 ) _mesa_debug(ctx, " swapping gl" "End""...\n" ); ((ctx->Exec)->End = tnl->Current->End); };(*((_glapi_Dispatch)->End)) (); }不像neutral_Begin()在之前還要執行ctx->Driver.BeginVertices,neutral_End()首先會登記交換函數地址,然后轉移至vbo_exec_End執行。
vbo_exec_Endstatic void GLAPIENTRY vbo_exec_End( void ) {GET_CURRENT_CONTEXT( ctx ); if (ctx->Driver.CurrentExecPrimitive != PRIM_OUTSIDE_BEGIN_END) {struct vbo_exec_context *exec = &vbo_context(ctx)->exec;int idx = exec->vtx.vert_count;int i = exec->vtx.prim_count - 1;exec->vtx.prim[i].end = 1; exec->vtx.prim[i].count = idx - exec->vtx.prim[i].start;ctx->Driver.CurrentExecPrimitive = PRIM_OUTSIDE_BEGIN_END;if (exec->vtx.prim_count == VBO_MAX_PRIM)vbo_exec_vtx_flush( exec, GL_FALSE );}else _mesa_error( ctx, GL_INVALID_OPERATION, "glEnd" ); }vbo_exec_End只會記錄消息,除非數據滿了,否則不會刷新數據。
從當前的GLDrawScene()執行過程來看,第一個glBegin()/glEnd()之后不會立即刷新,但由于接下來的glTranslatef(3.0f, 0.0f, 0.0f);因為模型視圖矩陣的變更會觸導數據刷新。而來具體的過程來看,都是通過vbo_exec_vtx_flush函數來完成的。而這將是后續分析的起點。
?
vbo_exec_vtx_flush定義在vbo_exec_draw.c第353行處,具體代碼如下:
vbo_exec_vtx_flush// vbo_exec_draw.c 從第349行至417行 /*** Execute the buffer and save copied verts.*/ void vbo_exec_vtx_flush( struct vbo_exec_context *exec, GLboolean unmap ) {if (0)vbo_exec_debug_verts( exec );if (exec->vtx.prim_count && exec->vtx.vert_count) {exec->vtx.copied.nr = vbo_copy_vertices( exec ); if (exec->vtx.copied.nr != exec->vtx.vert_count) {GLcontext *ctx = exec->ctx;/* Before the update_state() as this may raise _NEW_ARRAY* from _mesa_set_varying_vp_inputs().*/vbo_exec_bind_arrays( ctx );if (ctx->NewState)_mesa_update_state( ctx );if (_mesa_is_bufferobj(exec->vtx.bufferobj)) {vbo_exec_vtx_unmap( exec );}if (0)_mesa_printf("%s %d %d\n", __FUNCTION__, exec->vtx.prim_count,exec->vtx.vert_count);vbo_context(ctx)->draw_prims( ctx, exec->vtx.inputs, exec->vtx.prim, exec->vtx.prim_count,NULL,GL_TRUE,0,exec->vtx.vert_count - 1);/* If using a real VBO, get new storage -- unless asked not to.*/if (_mesa_is_bufferobj(exec->vtx.bufferobj) && !unmap) {vbo_exec_vtx_map( exec );}}}/* May have to unmap explicitly if we didn't draw:*/if (unmap && _mesa_is_bufferobj(exec->vtx.bufferobj) &&exec->vtx.buffer_map) {vbo_exec_vtx_unmap( exec );}if (unmap || exec->vtx.vertex_size == 0)exec->vtx.max_vert = 0;elseexec->vtx.max_vert = ((VBO_VERT_BUFFER_SIZE - exec->vtx.buffer_used) / (exec->vtx.vertex_size * sizeof(GLfloat)));exec->vtx.buffer_ptr = exec->vtx.buffer_map;exec->vtx.prim_count = 0;exec->vtx.vert_count = 0; }第382行是開始準備進入管道繪制處的語句,具體對應t_draw.c第363行處的_tnl_vbo_draw_prims,然后到達_tnl_draw_prims函數;
_tnl_draw_prims// t_draw.c 從第378行到450行 /* This is the main entrypoint into the slimmed-down software tnl* module. In a regular swtnl driver, this can be plugged straight* into the vbo->Driver.DrawPrims() callback.*/ void _tnl_draw_prims( GLcontext *ctx,const struct gl_client_array *arrays[],const struct _mesa_prim *prim,GLuint nr_prims,const struct _mesa_index_buffer *ib,GLuint min_index,GLuint max_index) {TNLcontext *tnl = TNL_CONTEXT(ctx);const GLuint TEST_SPLIT = 0;const GLint max = TEST_SPLIT ? 8 : tnl->vb.Size - MAX_CLIPPED_VERTICES;if (0){GLuint i;_mesa_printf("%s %d..%d\n", __FUNCTION__, min_index, max_index);for (i = 0; i < nr_prims; i++)_mesa_printf("prim %d: %s start %d count %d\n", i, _mesa_lookup_enum_by_nr(prim[i].mode),prim[i].start,prim[i].count);}if (min_index) {/* We always translate away calls with min_index != 0. */vbo_rebase_prims( ctx, arrays, prim, nr_prims, ib, min_index, max_index,_tnl_vbo_draw_prims );return;}else if (max_index > max) {/* The software TNL pipeline has a fixed amount of storage for* vertices and it is necessary to split incoming drawing commands* if they exceed that limit.*/struct split_limits limits;limits.max_verts = max;limits.max_vb_size = ~0;limits.max_indices = ~0;/* This will split the buffers one way or another and* recursively call back into this function.*/vbo_split_prims( ctx, arrays, prim, nr_prims, ib, 0, max_index,_tnl_vbo_draw_prims,&limits );}else {/* May need to map a vertex buffer object for every attribute plus* one for the index buffer.*/struct gl_buffer_object *bo[VERT_ATTRIB_MAX + 1];GLuint nr_bo = 0;/* Binding inputs may imply mapping some vertex buffer objects.* They will need to be unmapped below.*/bind_inputs(ctx, arrays, max_index+1, bo, &nr_bo);bind_indices(ctx, ib, bo, &nr_bo);bind_prims(ctx, prim, nr_prims );TNL_CONTEXT(ctx)->Driver.RunPipeline(ctx);unmap_vbos(ctx, bo, nr_bo);free_space(ctx);} }實際執行時只會執行第431行起的else {}代碼塊,而第445行處的TNL_CONTEXT(ctx)->Driver.RunPipeline(ctx);是正式的進入管道的入口語句,它對應在t_pipeline.c第121行處的_tnl_run_pipeline。
_tnl_run_pipeline// t_pipeline.c 從第121行至162行 void _tnl_run_pipeline( GLcontext *ctx ) {TNLcontext *tnl = TNL_CONTEXT(ctx);unsigned short __tmp;GLuint i;if (!tnl->vb.Count)return;/* Check for changed input sizes or change in stride to/from zero* (ie const or non-const).*/if (check_input_changes( ctx ) || tnl->pipeline.new_state) {if (ctx->VertexProgram._MaintainTnlProgram)_tnl_UpdateFixedFunctionProgram( ctx );for (i = 0; i < tnl->pipeline.nr_stages ; i++) {struct tnl_pipeline_stage *s = &tnl->pipeline.stages[i];if (s->validate)s->validate( ctx, s );}tnl->pipeline.new_state = 0;tnl->pipeline.input_changes = 0;/* Pipeline can only change its output in response to either a* statechange or an input size/stride change. No other changes* are allowed.*/if (check_output_changes( ctx ))_tnl_notify_pipeline_output_change( ctx );}START_FAST_MATH(__tmp);for (i = 0; i < tnl->pipeline.nr_stages ; i++) {struct tnl_pipeline_stage *s = &tnl->pipeline.stages[i];if (!s->run( ctx, s ))break;}END_FAST_MATH(__tmp); }?
其中對應的管道結構變量是:
const struct tnl_pipeline_stage *_tnl_default_pipeline[] = {&_tnl_vertex_transform_stage,&_tnl_normal_transform_stage,&_tnl_lighting_stage,&_tnl_texgen_stage,&_tnl_texture_transform_stage,&_tnl_point_attenuation_stage,&_tnl_vertex_program_stage, &_tnl_fog_coordinate_stage,&_tnl_render_stage,NULL };由上可見首先計算頂點,然后變換,再光照等等,最后運行渲染。
?
好久沒有寫博客,同時也放下對mesa源碼的閱讀很久了。這一篇會是我最后的一篇,有不盡到之處也會放下了,畢竟OpenGL不是我的強項,后面可能沒有這么多時間來關注了。
這篇文章有點長,而我也不知道自己究竟說清楚了沒有,放在這里,一方面是作為自己閱讀的一個總結,另一方面也期望它能夠對人有所助益。
?
0? 數據結構的預備
1) __GLcontextRect
__GLcontextRect 也被定義為 GLcontext
Mesa rendering context,在這個數據結構里幾乎包括所有的OpenGL狀態
?
struct gl_transform_attrib Transform; 對應Transformation屬性
?
struct gl_viewport_attrib Viewport; 對應Viewprt屬性,glViewport()負責初始化它
?
struct gl_matrix_stack ModelviewMatrixStack;
struct gl_matrix_stack ProjectionMatrixStack;
struct gl_matrix_stack ColorMatrixStack;
struct gl_matrix_stack TextureMatrixStack[MAX_TEXTURE_UNITS];
struct gl_matrix_stack ProgramMatrixStack[MAX_PROGRAM_MATRICES];
struct gl_matrix_stack *CurrentStack; // 指向上面矩陣堆棧中的一個
?
GLmatrix _ModelProjectMatrix; // 聯合模型視圖矩陣與投影矩陣
?
2) wmesa_context? 與? wmesa_framebuffer
wmesa_context // The Windows Mesa rendering context, derived from GLcontext;
它存儲與Windows窗口有關的數據成員,除GLcontext之外,比如HDC, 清除顏色,清除畫筆,清除畫刷等等
?
wmeas_framebuffer // Windows framebuffer, derived from gl_framebuffer
除gl_framebuffer之外,窗口句柄,像素格式,顏色位數,位圖,以及像素指針,下一wmesa_framebuffer*next指針等等
?
真正地像素顏色等數據會存儲在這里,因而也就比較與平臺相關,而GLcontext則是能夠比較獨立于平臺的,如何做到的呢,依靠這里的數據結構了吧。
?
GLvisual 亦即 __GLcontextModesRes 看起來它是支持很多渲染模式的,比如RGB,累積緩存,深度緩存,各種GLX, ARB擴展等等,這個暫時不管它
?
dd_function_table Mesa內核使用這些函數指針以呼叫設備驅動,許多函數直接對應OpenGL狀態命令,Mesa內核會在完成錯誤檢查之后再調用它們,所以驅動不用再檢查
?
頂點transformation/clipping/lighting存放入 T&L模塊
Rasterization光柵化函數存放入 swrast模塊
?
3)? glapi_table
gdi項目下的 mesa.def 對應 OpenGL.dll 輸出的API函數列表,該列表與 glapi_table 很能對應上
_glapi_set_dispatch()? 是對應的設置捆綁接應函數,比如 對上? __glapi_noop_table 可表征空操作
_mesa_init_exec_table() 是對應的初始化 glBegin()/glEnd() 之間的函數
?
mesa里實現gl*的API函數是比較獨特的,通過宏替換的形式來進行。未初始化的時候,對應的都是空的函數體,在wglMakeCurrent()里才會被初始化。
?
1 前面的測試工程,調試后可發現其基本的執行流程如下:
(1) 窗口WM_CREATE之后調用 GLSetupRC(),此時也可以進行那些屬于一次性的初始化GLInit()
ChoosePixelFormat()
SetPixelFormat()
wglCreateContext
wglMakeCurrent()
(2) 窗口WM_SIZE消息處調用? GLResize()
glViewport()
glMatrixMode()
glLoadIdentity()
gluPerspective()
glMatrixMode()
glLoadIdentity()
(3) 在消息循環之外調用? GLDrawScene() 繪制
glClear()
glLoadIdentity()
glTranslatef()
glBegin()
??? glColor3f()
??? glVertex3f()
glEnd()
SwapBuffers()
(4) 退出窗口之前銷毀該銷毀的
略過
?
2 GLSetupRC() 處的執行流程
函數 wglCreateContext()
1) 若當前無有效Ctx數量,則初始化所有 wgl_ctx[i].ctx 為NULL
2) 循環遍歷所有 wgl_ctx[i],發現一個空的,則調用 WMesaCreateContext() 創建,若創建成功,則 ctx_count += 1,然后返回該句柄 HGLRC 形式
WMesaCreateContext()
1) 申請wmesa_context內存空間,然后初始化窗口句柄,像素位數,GLvisual(暫不理會)等
2) 調用 _mesa_init_driver_functions() 初始化驅動函數指針 dd_function_table 類型
3) 調用 _mesa_initialize_context() 初始化 GLcontext(這是一個重要類型)
4) 啟用一些擴展,暫時忽略
5) 初始化 software rasterizer and helper module, 四個模塊的初始化,重要!
6) 管道初始化,重要,_tnl_run_pipeline
7) 返回
?
_mesa_initialize_context()
1) 賦值初始化一些成員 GLcontext
2) share_list 的申請,(不大明白,暫時忽略處理)
3) 調用 init_attrib_groupd() 重要函數,會初始化很多東西,比如 buffer_objects, color, eval, depth, line, matrix, pixel, point, polygon, program, scissor, transform, viewport 等等
4) 申請內存空間,并初始化 ctx->Exec, ctx->Save,這個與glBegin(), glEnd() 有關
5) 再調用 _mesa_init_exec_vtxfmt() 初始化 Neutral tnl module stuff,與頂點有關的API了
6) 返回
?
wglMakeCurrent()
1) 檢查若二者當中有一個為NULL,則調用 WMesaMakeCurrent(NULL, NULL))
2) 否則找到對應的句柄,必須能找到,否則失敗,再調用 WMesaMakeCurrent()
?
WMesaMakeCurrent()
1) 如果hDC與hRC二者已經有關聯,不用做什么退出即可
2) 尋找對應 hDC 的 WMesaFramebuffer
3) 如果hDC, hRC有效,卻沒有framebuffer,則進入以下步驟創建
==> 1) 獲取窗口大小
==> 2) 調用 wmesa_new_framebuffer() 創建 WMesaFramebuffer
==> 3) 如果為雙緩存,還要創建 back buffer
==> 4) 然后make render buffers,在wmesa_set_renderbuffer_funcs()處應該注意,它初始化了讀寫像素的函數指針,比如rb->PutRow(), rb->PutRowRGB(), rb->PutMonoRow(), rb->PutValues(), rb->PubMonoValues(), rb->GetRow(), rb->GetValues() 可以從后面看出,這些函數指針完成對渲染場景像素的讀寫
雙緩存的時候,會建二個render buffers, 目前還沒有精力去關注這一方面的內容
==> 5) _mesa_add_soft_renderbuffers() 添加深度緩存,累積緩存,模板
4) 上一步之后,若hRC與framebuffer有效,則傳遞相關參數,調用 _mesa_make_current()
5) 若以上均不符合,則判定為取消置為當前,傳遞NULL,調用 _mesa_make_current()
?
_mesa_make_current()
1) 若傳入的為有效值(非空),則檢查newCtx與drawBuffer, readBuffer之間的兼容性
2) 若可行,則置全局變量 _glapi_Context 值(該值會到處被用到),否則的話會置空
3) 還有對 _glapi_set_dispatch() (它直接對應OpenGL各API函數的) 的初始化,若有效,置各函數指針 newCtx->CurrentDispatch 指針,最后初始化各種 framebuffer size,主要是drawBuffer, readBuffer這兩個
4) 后面還有一些初始化,默認也包括了 _mesa_set_viewport() _mesa_set_scissor 函數等
?
3 GLResize() 處的執行流程
注意:m_matrix.c 文件里的注釋
-- # 4x4 變換矩陣以列為優先存儲
-- # 點/頂點被認為是列矢量
-- # 點經矩陣的變換是 p' = M * p
glViewport()
1) 經過函數指針分派轉至 _mesa_Viewport()
2) 獲取當前 context
3) 確認是否在 glBegin(), glEnd() 外部使用
4) 調用 _mesa_set_viewport()
?
_mesa_set_viewport()
1) 在最大可能 MaxViewportWidth, MaxViewportHeigth 與給定值之間取最小值
2) 賦值改變 ctx->Viewport 參數,并置新狀態 _NEW_VIEWPORT
3) 調用 _math_matrix_viewport() 初始化 ctx->Viewport._WindowMap
glViewport(0, 0, nWidth, nHeight)轉換ctx->Viewport._WindowMap矩陣為
width/2?? 0???????????? 0???? width/2 + x
0?????? height/2??????? 0???? height/2 + y
0?????????? 0??? depthMax*((zFar - zNear)/2)?? depthMax*((zFar - zNear)/2 + zNear)
0?????????? 0????????????? 0?????????? 1
其中(x, y, width, height)對應上面函數的參數
zNear/zFar對應ctx->Viewport.Near/Far,初始值為0.0/1.0
depthMax值等于ctx->DrawBuffer->_DepthMaxF,該值初始值為65535.0
320? 0??? 0?? 320
0?? 240?? 0?? 240
0???? 0 32767.5 32767.5
0???? 0???? 0??? 1
以列為優先存儲的方式,故矩陣 0, 1, 2, 3 4, 5, 6, 7, 8,9,10,11, 12, 13, 14, 15內部值為
{ 320, 0, 0, 0, 0, 240, 0, 0, 0, 0, 32767.5, 0 320, 240, 32767.5, 1 }
4) 若存在驅動.Viewport,則調用它,對應 wmesa_viewport() 它負責resize_buffers()
?
glMatrixMode()
1) 經過函數指針分派轉至 _mesa_MatrixMode()
2) 獲取當前的 context
3) 確保在Begin與End() 之外使用,這個范圍跟 glViewport() 有差別,但具體怎樣暫時忽略
4) 如果ctx->Transform.MatrixMode已為給定模式,并且給定模式不等于 GL_TEXTURE,退出,不需要后面的處理
5) 因為 _NEW_TRANSFORM特性,需要完成以前的繪制,如果存在的話,FLUSH_VERTICES
6) 置 ctx->CurrentStack 為對應模式的矩陣堆棧,比如投影矩陣堆棧,模型視圖堆棧
7) 改變 ctx->Transform.MatrixMode 為給定模式
8) 返回
?
glLoadIdentity()
1) 經過函數指針分派轉至 _mesa_LoadIdentity()
2) 獲取當前的 context
3) 確保在Begin, End, Flush之外使用本函數
4) 調用 _math_matrix_set_identity() 置 ctx->CurrentStack->Top 指針為單位矩陣
5) 給 ctx->NewState 添加新狀態,后面會根據這個來調用相關的初始化函數
?
gluPerspective()
這個是glu里面的函數,設置透視投影矩陣
double radians = fovy / 2 * __glPi / 180 = 0.392699081698...;
double deltaZ = zFar - zNear = 99.4;
矩陣各數據為:
cos(radians)/sin(radians)/aspect????? 0?????????????????? 0?????????????????? 0
0??????????????????????????????? cos(radians)/sin(radians)?? 0??????????????????? 0
0???????????????????????????????????????????????? 0??? -(zFar+zNear)/deltaZ??? -2*zNear*zFar/deltaZ
0???????????????????????????????????????????????? 0????????????????? -1??????????????????? 0
其中fovy = 45.0; aspect = 1.3333; zNear = 0.6; zFar = 100.0f;
1.810660171???? 0???????????? 0???????????????????? 0
0?????????? 2.41421356???????? 0???????????????????? 0
0????????????????????? 0??? -1.0120724350???? -1.2072435090
0????????????????????? 0??????????? -1???????????????????? 0
1) 調用 __gluMakeIdentityd() 初始化 m[4][4] 為單位矩陣
2) 改變 m[0][0], m[1][1], m[2][2], m[2][3], m[3][2], m[3][3] 的值
注意從這里可以看出 m[4][4] 與 OpenGL內部矩陣的存放次序是相反的, m[2][3] 對應mtx[11]位置,而m[3][2] 對應 mtx[14]位置,這一點很需要注意
{ 1.810660171 0 0 0, 0, 2.41421356 0 0, 0 0 -1.0120724350 -1, 0 0 -1.2072435090, 0 }
3) 調用 glMultMatrixd()
?
glMultMatrixd()
1) 經過函數指針分派轉至 _mesa_MultMatrixd
2) 轉換數據精度為 float型,調用 _mesa_MultMatrixf()
3) _mesa_MultMatrixf() 獲取當前ctx,確保參數有效以及被調用位置
4) 然后調用 _math_matrix_mul_floats() 將矩陣乘至 ctx->CurrentStack->Top()
矩陣相乘,以列為優先,故 A(row, col) = A[col*4 + row] 這一點同 UGOPEN 一致
5) 給 ctx->NewState 添加新狀態
?
以上已經可以完成對 GLResize() 的分析
?
4 GLDrawScene() 處場景繪制流程
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
此函數有點復雜,會完成很多事情
1) 經函數指針分派轉至 _mesa_Clear()
2) 獲取當前 context
3) 確保在Begin, End, Flush() 之外被調用
4) 完成當前場景 FLUSH_CURRENT()
5) 確保 mask 參數正確
6) 如果有新狀態ctx->NewState,則調用 _mesa_update_state() 更新
更新過程很多,這個函數復雜也就復雜在這里了,更新完后,ctx->NewState會置為0
7) 如果有DrawBuffer, ctx->DrawBuffer有效,繼續,否則退出
8) 如果渲染模式為 GL_RENDER,ctx->RenderMode的值,繼續
9) 各種緩存模式,最后調用驅動的Clear函數,對應 wmesa.c 里的clear()函數
這里也有一些復雜,不是十分清楚,它內部具體在做什么,仔細分析,里邊應該有一個環節將記錄像素顏色的地址指針全部初始化為背景顏色的過程
?
glLoadIdentity() 置當前矩陣堆棧Top為單位矩陣
?
glTranslatef()
1) 經過函數指針分派轉至 _mesa_Translatef()
2) 獲取當前 context
3) 確保在Begin, End, Flush之外調用
4) 調用 _math_matrix_translate(),它相當于一個矩陣相乘,只是平移變換矩陣只需要更新有關的那三個值而已,可以具體化為矩陣相乘效果的
m[12] = m[0] * x + m[4] * y + m[8] * z + m[12];
m[13] = m[1] * x + m[5] * y + m[9] * z + m[13];
m[14] = m[2] * x + m[6] * y + m[10] * z + m[14];
m[15] = m[3] * x + m[7] * y + m[11] * z + m[15];
從而當前模型視圖矩陣變為:
1 0 0 -1.5
0 1 0 0
0 0 1 -6
0 0 0 1
在內部的存儲很明顯是 { 1 0 0 0, 0 1 0 0, 0 0 1 0, -1.5 0 -6 1 }
5) 變換矩陣狀態,給 ctx->NewState添加新狀態
?
glBegin()
??? glColor3f()
??? glVertex3f()
glEnd()
這里的函數會走另一個指派過程,有點復雜,難以理清頭緒
小專題1) 這里相關的函數所經過的指派過程,擴而廣之,應對里邊幾乎所有地方都有一個記錄吧,還包括所有 OpenGL API 函數的指派位置
1) neutral_**前綴指派
在 vtxfmt.c 內,然后包括 vtxfmt_tmp.h頭文件 定義PRE_LOOPBACK()宏,再轉而調用當前 GET_DISPATCH() 對應的位置,而在上面的 PRE_LOOPBACK()里有對這個位置的設置與改變的,會進入到 tnl模塊
2) vbo_***前綴指派,在 vbo_exec_api.c 內,然后包括 vbo_attrib_tmp.h 暫時忽略 vbo_save_api.c 里的
在第一次執行 neutral_**之后,以后會直接轉至 vbo_**來執行
?
glBegin()
1) 經過指派,轉至 neutral_Begin() vtxfmt_tmp.h 文件內
2) 再又指派至 vbo_exec_**處執行
在這個函數里主要是記錄和模式
?
glEnd()
1) 大體類似 先 neutral_End(),再 vbo_exec_End()
2) 并不會立即進入繪制管道
?
2012/06/05 星期二
初步的認定是 neutral_**() 會去fix頂點數據格式,vbo_**() 會記錄數據
glTranslatef()
再次執行一個矩陣變換之后,會因為檢測到需要Flush了,從而調用 驅動指針 ctx->Driver.FlushVertices, 對應 vbo_exec_FlushVertices() 函數
?
vbo_exec_FlushVertices()
內部確認參數之后,轉而調用 vbo_exec_FlushVertices_internal()
可以這么說,不帶 _internal的負責對上下文環境的處理,帶_internal的職司 FlushVertices
本函數在后面還負責恢復函數指針,讓它們重新首先指向 neutral_**()
?
5 結束場景繪制,準備進入管道
glEnd()之后未必會立即轉向實際的繪制,mesa會緩存下前面已發生的指令,當下一步發現必須繪制時(比如在glEnd()之后調用glTranlate()變換視圖矩陣了)或者緩沖區已滿,才會轉入管道準備繪制。
vbo_exec_FlushVertices_internal()
1) 若存在頂點 exec->vtx.vert_count > 0 或強制要求 flush
2) 調用 vbo_exec_vtx_flush() 轉到這里去了
3) 再回復 exec內的某些成員,方便接受下一次輸入
?
vbo_exec_vtx_flush() // Execute the buffer and save copied verts
1) 若存在 prim_count 以及 vert_count,亦即調用過 glBegin(), glVertex*() 則執行以下語句
==> 調用 vbo_copy_vertices() 獲取某個指針
==> 如果與 exec->vtx.vert_count 不等則
==> ==> vbo_exec_bind_arrays()
==> ==> 若 ctx->NewState 有新狀態,則調用 _mesa_update_state() 更新之
==> ==> 若 _mesa_is_bufferobj() 則調用 vbo_exec_vtx_unmap() 調試發現這里不執行
==> ==> 調用 vbo_context(ctx)-> draw_prims() 函數,轉至 _tnl_vbo_draw_prims() 執行,然后轉至 tnl_draw_prims() 完成,開始進入管道了,在 t_draw.c 文件之內
==> 上面完成之后,從調試來看,沒再執行什么了
2) 后面恢復數據,比如 exec->vtx.buffer_ptr 指向 exec->vtx.buffer_map,以前驗證過 glVertex*()命令所操作對應的位置就是在這里的
通過以上步驟之后,就可以結束 頂點的flush() 了
?
tnl_draw_prims()
進入 軟件 tnl 模塊的最主要入口
1) 如果min_index不為0,做一些設定,因為總是假定從0序號開始的
2) 如果max_index > max,頂點太多了,需要分割一下再處理
3) 普通情形下,現在只針對這一情形閱讀:
==> bind_inputs()
==> bind_indices()
==> bind_prims()
==> TNL_CONTEXT(ctx)->Driver.RunPipeline() 對應 t_pipeline.c 下的 _tnl_run_pipeline()
==> unmap_vbos()
==> free_space()
前面的過程都很明顯的在為管道初始化數據地址
中間的函數進入 管道運行 _tnl_run_pipeline()
最后面再清理一些東西
?
6 管道繪制
這一個環節會把glBegin()/glEnd()之間的指令轉換成為最終輸出上的像素,牽涉到好幾個???#xff0c;弄懂它們也才能最終明白繪制指令是如何被具體實現的,以前的過程中對這里雖然也花了不少精力,但并沒有完全弄明白。
在矩陣變換方面,直線繪制(好像用的是中點繪制算法,但不是非??隙ㄟ@一點),超出邊界裁剪算法等地方用了不少的宏替換生成方法,如果覺得難以讀懂,建議打開VC設定里的存儲宏替換之后的.i文件。
?
矩陣變換方面的指針在 m_xform.c 內初始化,會包括好幾個文件來構建數組
?
數據結構 SWvertex 在軟件光柵化時存儲頂點的數據結構,這個很重要,最終繪制時輸入就是這個。
wpos = attr[FRAG_ATTRIB_WPOS] 在頂點里必須是第一個值,因為 tnl clipping code的緣故
wpos[0] 和 wpoa[1] 是 SWvertex 的屏幕點
wpos[2] 是 z-buffer 坐標 (如果16-位的Zbuffer,在范圍 [0, 65535] 之內)
wpos[3] 是 1/w,其中 w是W坐標的倒數,這是 ndc[XYZ]必須乘以而得到的 clip[XYZ]值
?
管道是什么樣的??
在 t_context.h 頭文件內,數據結構 tnl_pipeline_stage 描述單一的管道操作,包括create, destroy, validate, run幾個函數指針以及少量的數據成員
數據結構 tnl_pipeline 包括所有管道的數組容器,默認值在 t_pipeline.c 的最后,亦即 _tnl_default_pipeline[] 數組,從中可以看出先做頂點變換,再光照,紋理等,最后是運行渲染,渲染部分與管道部分之間可以再進一步分開
各個管道的初始化,可以在源代碼中找到
?
安裝管道
由 _tnl_install_pipeline() 來完成,可能在最開始的wglCreateContext()里就調用此函數了,此段代碼的執行應該是比較靠前的。
?
管道運行 _tnl_run_pipeline()
1) 如果 tnl->vb.Count 為0,則可退出,對應頂點數量
2) 檢查輸入變換,校驗tnl->pipeline.new_state狀態
3) 遍歷每一個管道,執行里邊的 run函數
?
頂點變換管道 t_vb_vertec.c 內
run_vertex_stage() 函數執行流程
1) 如果使用了 頂點編程,則退出
2) 如果 ctx->_NeedEyeCoords 不為0,則執行xxx (這里不大明白,具體不做什么事情)
3) VB->ClipPtr = TransformRaw() 用 ctx->_ModelProjectMatrix 矩陣變換輸入頂點,在 glBegin()與glEnd()之間所用到的值
矩陣里也對應有不少宏指派,這里會轉至 m_xform_tmp.h 內的 transform_points3_general
這里所對應的就是用模型投影矩陣變換輸入頂點
1.8106601???? 0??????????? 0????????? -2.7159901
0??????????? 2.4142137???? 0??????????????? 0
0????????????????? 0???? -1.0120724??? 4.8651910
0????????????????? 0?????????? -1???????????????? 6
(1) 頂點變換結果情況列表如下上一矩陣右乘列向量獲得 p' = M * p
(0.0 1.0 0.0) 變換為 (-2.7159901 2.4142137 4.8651910 6)
(-1.0 -1.0 0.0)??????? (-4.5266504 -2.4142137 4.8651910 6)
(1.0 -1.0 0.0)???????? (-0.90532994 -2.4142137 4.8651910 6)
上述變換結果點為VB->ClipPtr取值
4) 不管 tnl->NeedNdcCoords 是否需要,都會再進行如下一個轉換,具體是 點從
(x, y, z, w) 變換成為 (x/w y/w z/w 1/w) ,賦值給 VB->NdcPtr
從上一步再變換為
(-0.45266503 0.40236896 0.81086516 0.16666667)
(-0.75444174 -0.40236896 0.81086516 0.1666667)
(-0.15088832 -0.40236896 0.81086516 0.1666667)
以上頂點變換過程就完成了。
?
中間的這些管道因為沒有被啟用,比如光照,紋理,霧等等,故而不需要執行什么,內部會返回GL_TRUE
?
光柵渲染管道 t_vb_render.c 內
run_render()
對于這一過程,我想重要的二個地方在:內部如何建構屏幕點SWvertex;從屏幕點繪制算法;目前對后一問題有所追索,而前一問題總結得還不夠,好像層次比較復雜。
1) 調用 tnl->Driver.Render.Start() 函數 對應_swsetup_RenderStart()
==> 這里會在 _swsetup_choose_trifuncs() 初始化 tnl->Driver.Render. 三角形,四邊形,直線繪制 swsetup_line(),點繪制函數指針
==> 還有調用 setup_vertex_format() 以明確如何構建 SWvertex
內部通過 tnl_attr_map 的map數組,記錄每一個需要拷貝的值,比如總是拷貝第一個位置的 EMIT_ATTR(_TNL_ATTRIB_POS, EMIT_4F_VIEWPORT, attrib[FRAG_ATTRIB_WPOS]),然后如果有顏色設定,則拷貝顏色 EMIT_ATTR(_TNL_ATTRIB_COLOR0, EMIT_4CHAN_4F_RGBA, color);等等,范例只有這2個有設定, 最后調用 _tnl_install_attrs() 設置頂點格式
這個過程有調用 invalidate_funcs() 設置函數指針,比如 emit 指向 choose_emit_func,
2) assert() 確認 Render內的指針有值
3) 調用 tnl->Driver.Render.BuildVertices() 亦即 _tnl_build_vertices(),這里會對頂點有一個變換,但是這個變換卻又藏得很深 insert_4f_viewport_4() 函數
關鍵是在這里基于先前的輸入開始構建起后面所需要的頂點,所以它執行了一個系列的轉換流程,變換到最終的屏幕點,再最后從屏幕點開始繪制,所以這里的變換過程也很關鍵的,值得深入探討一番
==> 獲取 tnl_clipspace 指針
==> 調用 update_input_ptrs() 根據被設定的屬性數量,更新對應的數據指針地址,然后窗口viewport矩陣設置 vtx->vp_scale vtx->vp_xlate 的幾個值
==> 調用 vtx->emit()指針函數,亦即 choose_emit_func(),它會根據 vtx->attr (數據類型 tnl_clipspace_aatr ),設置對應的 a[j].emit 函數指針,a[j].insert[] 而這里的編排可能需要一點時間,對于viewport,設置的是 insert_4f_viewport_4 函數
然后再盡可能重置 vtx->emit, 若最后沒有值,則使用 _tnl_generic_emit() 函數,再調用此函數,該函數所完成的工作亦即遍歷所有頂點,調用先前在a[j].emit()里設置的函數,因而也就會調用 insert_4f_viewport_4(), 完畢之后,至此,本函數的運行結束了
頂點再一步變換為
(175.14719 336.56854 59337.523 0.1666667)
(78.578644 143.43146 59337.523 0.1666667)
(271.71573 143.43146 59337.523 0.1666667)
4) 獲取合適的渲染函數指針地址
5) 遍歷所有的prim以及頂點,繪制它們,這里又使用了宏指令來得到不同的函數指針,如果是繪制直線并且帶顏色的,會到達 rgba_line() (此函數定義經過宏替換產生),里邊的算法很像 中點繪制算法,不過不再嚴格去比對確認是否真為中點繪制算法了
6) 調用 tnl->Driver.Render.Finish(ctx)
?
以上再次對 OpenGL變換與繪制 又重新有所了解了,我在學習OpenGL的時候,有時候頭昏腦漲,搞不清楚最終這些指令會是怎樣被執行的,所以過來閱讀mesa源代碼,應該說有所收獲吧,看到管道的一種設計,看到函數的規劃與宏替換,真正體會到,C語言的簡潔與強大,當然更強的是mesa開發者對內部模型的設計,如果搞清楚了設計模型,里邊很多代碼就順理成章了。所以閱讀之后,我的感受是開發語言并不重要,項目的規劃與設計才是最重要的。
總結
以上是生活随笔為你收集整理的mesa3d源代码阅读笔记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Qt 有层级关系的qss样式,使用id定
- 下一篇: k8s的安装部署