1. 程式人生 > >iOS OpenGL ES Guide

iOS OpenGL ES Guide

OpenGL ES 小結

概述

OpenGL ES (Open Graphics Library for Embedded Systems)是訪問類似 iPhone 和 iPad 的現代嵌入式系統的 2D 和 3D 圖形加速硬體的標準。

把程式提供的幾何資料轉換為螢幕上的影象的過程叫做渲染。

GPU 控制的快取是高效渲染的關鍵。容納幾何資料的快取定義了要渲染的點、線段和三角形。

OpenGL ES 3D 的預設座標系、頂點和向量為幾何資料的描述提供了數學基礎。

渲染的結果通常儲存在幀快取中。有兩個特別的幀快取,前幀快取和後幀快取,它們控制著螢幕畫素的最終顏色。

OpenGL ES 的上下文儲存了 OpenGL ES 的狀態資訊,包括用於提供渲染資料的快取地址和用於接收渲染結果的快取地址。

OpenGL ES 是 OpenGL 的子集,它移除了 OpenGL 中冗餘的函式,使其更易學也更容易在移動圖形硬體中實現。OpenGL ES 是基於 C 語言的 API ,所以可以無縫移植到 Objective—C 中,然後通過建立上下文來接收命令和操縱幀快取。

在 iOS 中,使用 OpenGL ES 時,可以使用 GLKit 框架中的 GLKView 將 OpenGL ES 繪製的內容渲染到螢幕上,並且可以使用 GLKViewController 來管理 GLKView 檢視。另外,還可以使用 CAEAGLLayer 圖層將動畫與檢視相結合。但是,需要注意的是,當應用處於後臺狀態時,不能呼叫 OpenGL ES 中的函式,否則應用便會被終止,而且 OpenGL ES 中的上下文也不支援在同一時刻被不同的執行緒訪問。

在 iOS 中使用 OpenGL ES

OpenGL ES 定義了跨平臺的介面來使用 GPU 的硬體效能加速圖形的渲染,其中由渲染上下文來執行渲染命令,幀快取儲存渲染結果,渲染目標顯示幀快取中結果。對應到 iOS 中,EAGLContext 是實現上下文的類,GLKView 和 CAEAGLLayer 則用來顯示最終的渲染結果。

使用 OpenGL ES 之前,需要明確自己要使用哪個版本的 OpenGL ES 。目前最新的版本是 3.0 ,相較於 iOS 7.0 之前的 2.0 版本,添加了一些新的特性,包含原本只在桌面系統中有效的技術,使得 GPU 的效能能夠更好的發揮出來。

EAGLContext

在 iOS 中,要想使用 OpenGL ES 中的函式必須要先建立一個 EAGLContext 例項物件,該物件表示渲染上下文。對於每一個執行緒都只能對應一個上下文,可以呼叫 EAGLContext 的類方法設定或獲取當前上下文。在同一個執行緒中切換不同的上下文時,需要注意應用應自己對上下文進行強引用以防止其被釋放,並且在切換之前應呼叫 glFlush 函式將當前上下文提交的指令傳到圖形硬體中去。

使用下面的方法設定或獲取當前上下文:

+ (BOOL)            setCurrentContext:(EAGLContext*) context;
+ (EAGLContext*)    currentContext;

對於不同的裝置,其支援的 OpenGL ES 版本也不同,所以在建立上下文時,如果返回的值為 nil 那麼表示裝置不支援指定版本的 OpenGL ES 。

- (instancetype) initWithAPI:(EAGLRenderingAPI) api;
- (instancetype) initWithAPI:(EAGLRenderingAPI) api sharegroup:(EAGLSharegroup*) sharegroup NS_DESIGNATED_INITIALIZER;

建立上下文時需要指定 OpenGL ES 的版本,並且這裡上下文的狀態與上下文物件是分離的,其狀態都儲存在 EAGLSharegroup 例項物件中,該物件是透明的,不應該主動建立該類的例項。這種設計方式是為了節約系統資源,對於不同的上下文可能擁有相同的上下文狀態,那麼這種設計方式便十分便利。如,需要在子執行緒中載入資料,在主執行緒中進行渲染,那麼當資料載入完成後,可以直接將子執行緒中上下文的狀態繫結到主執行緒上下文中。

GLKView

GLKView 類為 OpenGL ES 上下文提供了渲染結果的顯示檢視,在建立一個 GLKView 檢視之後,需要將其與一個上下文相繫結。

- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;

同在 UIView 檢視中繪製圖形一樣,可以通過繼承 GLKView 類重寫 drawRect: 方法來進行圖形的繪製。另一種方式不用建立子類,直接設定 GLKView 的代理,實現協議 GLKViewDelegate 中的代理方法。

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect;

當重寫 drawRect: 時,代理方法不會被執行。

GLKViewController

使用 GLKViewController 類可以管理 GLKView 檢視,兩者通常配合使用。

設定該類的 preferredFramesPerSecond 屬性值可以指定每秒鐘重新整理的幀數,但是實際幀的重新整理頻率可能並不與之相等。

設定該類的代理物件 delegate ,該代理需要實現 GLKViewControllerDelegate 協議。

//如果 GLKViewController 被繼承,並且實現了 -(void)update 方法,那麼該代理方法不會被呼叫。
- (void)glkViewControllerUpdate:(GLKViewController *)controller;

//當暫停狀態發生改變時,呼叫該方法。
- (void)glkViewController:(GLKViewController *)controller willPause:(BOOL)pause;

渲染目標

幀快取接收渲染命令,在應用中,配置不同幀快取,實現不同的目的。

  • 渲染離屏影象,與幀快取相關聯的所有配置都以渲染快取的形式存在。

    1. 建立幀快取並繫結

      GLuint framebuffer;
      glGenFramebuffers(1,&framebuffer);
      glBindFramebuffer(GL_FRAMEBUFFER, framebuffer); 
    2. 建立顏色渲染快取並繫結

      GLuint colorRenderbuffer;
      glGenRenderbuffers(1, &colorRenderbuffer);
      glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
      glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, width, height);
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);
    3. 建立渲染深度並繫結

      GLuint depthRenderbuffer;
      glGenRenderbuffers(1, &depthRenderbuffer);
      glBindRenderbuffer(GL_RENDERBUFFER, depthRenderbuffer);
      glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height);
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRenderbuffer);
    4. 判斷幀快取配置是否完成

      //配置狀態改變時,需要重新判斷
      GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER) ;
      if(status != GL_FRAMEBUFFER_COMPLETE) {
          NSLog(@"failed to make complete framebuffer object %x", status);
      }

    當渲染結束後,使用 glReadPixels 函式讀取畫素值,進行其他的處理。

  • 渲染紋理

    1. 建立幀快取並繫結(同上)
    2. 建立紋理快取並繫結

      GLuint texture;
      glGenTextures(1, &texture);
      glBindTexture(GL_TEXTURE_2D, texture);
      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
      glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8,  width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
      glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    3. 建立渲染深度並繫結(同上)

    4. 判斷幀快取配置是否完成(同上)

    可見,紋理渲染只是在離屏渲染操作中改變了顏色的設定

  • 渲染動畫圖層

    要將 OpenGL ES 渲染快取中的內容顯示在裝置上,需要通過 CAEAGLLayer 圖層類。該類提供了一個共享的記憶體空間給渲染快取,並且負責將渲染快取中的內容插入到動畫圖層中。

    1. 建立 CAEAGLLayer 類的例項物件,並設定屬性

      • presentsWithTransaction 設定圖層重新整理的方式,預設 false 則非同步重新整理到圖層,設定為 true 則以標準的 CATransaction 機制將內容傳送到螢幕進行顯示。
      • drawableProperties 該屬性是在協議 EAGLDrawable 中進行宣告的,通過一個字典來改變渲染的畫素的格式以及內容顯示後是否仍然被引用,其可能的鍵值如下:
        • kEAGLDrawablePropertyRetainedBacking 對應的值為 NSNumber(boolean)
        • kEAGLDrawablePropertyColorFormat 可能的值為 kEAGLColorFormatRGBA8 、kEAGLColorFormatRGB565 、kEAGLColorFormatSRGBA8
    2. 建立一個 OpenGL ES 上下文,並設定為當前上下文

    3. 建立幀快取
    4. 建立顏色渲染快取,而後呼叫上下文的方法 renderbufferStorage:fromDrawable: 來為渲染快取建立記憶體

      GLuint colorRenderbuffer;
      glGenRenderbuffers(1, &colorRenderbuffer);
      glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
      [myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:myEAGLLayer];
      glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderbuffer);

      需要注意的是,當動畫圖層大小或屬性發生改變時,渲染記憶體應重新分配,否則渲染結果可能會被縮放以覆蓋整個圖層。

    5. 獲取實際的顏色渲染快取的寬高

      GLint width;
      GLint height;
      glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &width);
      glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &height);

      對於不是明確指定的顏色快取記憶體的大小,需要使用該方式獲取實際的快取大小,其他渲染快取要與此保持大小一致。

    6. 建立渲染深度並繫結(同上)

    7. 判斷幀快取配置是否完成(同上)
    8. 將 CAEAGLLayer 圖層插入到可見的動畫圖層中

繪製幀

當建立並配置好了一個幀快取後,接下來的任務就是填滿整個幀。首先要確認的是繪製幀的時機,一種是需要顯示 OpenGL ES 的內容時,進行繪製,如同使用 GLKit 框架時,繪製總是在檢視要顯示時進行。另一種是與動畫迴圈同步,使用 CADisplayLink 類例項物件可以實現繪製與螢幕重新整理頻率同步。

使用 UIScreen 的例項方法,獲取一個與螢幕重新整理頻率一致的 CADisplayLink 例項物件。

- (nullable CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel NS_AVAILABLE_IOS(4_0);

而後呼叫 CADisplayLink 的例項方法,將指定的方法新增到當前迴圈中。

- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;

另外,可以根據需要調整指定方法的呼叫頻率,只要修改 CADisplayLink 的屬性 frameIntervalpreferredFramesPerSecond 值即可。但是,frameInterval(已廢棄)的值指的是重新整理多少次才觸發一次重新整理方法,如設定該值為 5 ,那麼對於 60Hz 的螢幕重新整理頻率而言,重新整理方法的呼叫頻率為 12Hz 。preferredFramesPerSecond 則是直接表示指定方法每秒鐘的呼叫次數,即幀的重新整理頻率。

  1. 清空快取

    在繪製每一幀之前,清空幀中一些不需要的資訊,防止其被繪製到下一幀中,從而提高效能。

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
  2. 準備需要的資源並提交到 GPU 後,執行繪製命令進行幀的繪製

  3. 如果採用多重取樣改善圖片的質量,需要在其顯示之前完成畫素的處理
  4. 當渲染的內容顯示後,那麼一些快取資料就不需要了,為提高效能應進行捨棄

    const GLenum discards[]  = {GL_DEPTH_ATTACHMENT};
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glDiscardFramebufferEXT(GL_FRAMEBUFFER,1,discards);

    glDiscardFramebufferEXT 函式適用於 OpenGL ES 1.1 和 2.0 版本,3.0 版本要使用 glInvalidateFramebuffer 函式。

  5. 顯示渲染的結果

    顏色渲染快取持有最終的幀,所以將其繫結到當前上下文中並顯示。

    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];

    內容顯示後,如果想要繼續持有顏色渲染快取中的內容,需要設定 CAEAGLLayer 的屬性 drawableProperties 值,使其包含的 kEAGLDrawablePropertyRetainedBacking 的鍵所對應的值為真,並且繪製下一幀呼叫 glClear 函式時,不傳 GL_COLOR_BUFFER_BIT 引數。

多重取樣

多重取樣是保證圖片邊界平滑的技術之一,其會消耗一些記憶體和處理時間,但這是提高圖片質量的有效方式。

要實現多重取樣,需要建立兩個幀快取。一個多重樣本幀快取,其包含了渲染內容所需的所有關聯資料,如顏色快取和深度快取。另一個抽樣幀快取,只包含顯示渲染結果所需要的資料,通常是顏色快取,也可能是紋理快取。

多重樣本幀快取所包含的所有的渲染快取的大小同抽樣幀快取的大小一樣,但是每一個渲染快取都有一個額外的引數指定每一個畫素所需要的樣本數。

//建立樣本幀快取並繫結
glGenFramebuffers(1, &sampleFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);

//建立樣本顏色渲染快取並繫結
glGenRenderbuffers(1, &sampleColorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleColorRenderbuffer);
//為顏色快取生成記憶體空間
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_RGBA8_OES, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, sampleColorRenderbuffer);

//建立樣本深度渲染快取
glGenRenderbuffers(1, &sampleDepthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, sampleDepthRenderbuffer);
//為深度快取生成記憶體空間
glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER, 4, GL_DEPTH_COMPONENT16, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, sampleDepthRenderbuffer);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    NSLog(@"Failed to make complete framebuffer object %x", glCheckFramebufferStatus(GL_FRAMEBUFFER));

建立樣本幀快取和抽樣幀快取後,繪製過程也要做一些變化。

  1. 清空樣本幀快取

    glBindFramebuffer(GL_FRAMEBUFFER, sampleFramebuffer);
    glViewport(0, 0, framebufferWidth, framebufferHeight);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  2. 將所有的繪製命令提交後,要將多個樣本中的每一個畫素匯合為一個樣本,最後儲存到抽樣幀快取中

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER_APPLE, resolveFrameBuffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER_APPLE, sampleFramebuffer);
    glResolveMultisampleFramebufferAPPLE();
  3. 要顯示的內容已經儲存在了抽樣幀快取中,所有樣本幀快取可以遺棄了

    const GLenum discards[]  = {GL_COLOR_ATTACHMENT0,GL_DEPTH_ATTACHMENT};
    glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE,2,discards);
  4. 最後顯示抽樣幀快取中的顏色渲染快取

    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderbuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];

上述的函式都是屬於 OpenGL ES 1.1 和 2.0 中的 GL_APPLE_framebuffer_multisample 部分的擴充套件函式,在 OpenGL ES 3.0 中,實現多重取樣的函式並不一樣。

其他特性

OpenGL ES 是跨平臺的,但是在一些平臺上的某些特性需要特殊考慮。在 iOS 系統中,需要注意多工的正確處理、防止在後臺呼叫 OpenGL ES 函式,並且要考慮裝置的解析度及其他特性。

iOS 為了前臺應用程式能夠流暢執行,禁止處在後臺的程式呼叫圖形硬體指令,除了終止嘗試呼叫的應用外,其還會清除進入後臺的應用提交的指令,所以應用進入後臺前,應確保其提交的指令均執行完畢。如果並沒有使用 GLKView 進行圖形的繪製,那麼應當在 applicationWillResignActive: 方法中,停止圖形重新整理的定時器。在 applicationDidEnterBackground: 方法中釋放 OpenGL ES 使用的資源,呼叫函式 glFinish 確保所有提交的指令均被執行完畢,之後便不可以嘗試圖形硬體指令的呼叫。當應用將要重新回到前臺時,在 applicationWillEnterForeground: 方法中重啟定時器並重新建立渲染需要的資源。

當應用進入後臺時,對於一些易於重新生成的資源或必須重新生成的資料,如幀快取等,應當釋放。而一些耗費大量時間才生成的資源,不應釋放。

當螢幕發生轉動時,繪製的影象也要做出相應的改變。如果使用了 GLKViewController 或 GLKView 則可以通過重寫 viewWillLayoutSubviews 、viewDidLayoutSubviews 或 layoutSubviews 方法來調整圖形的大小。

渲染流程設計

基本概念

OpenGL ES 的使用一般分為兩種結構,一種是客戶端-伺服器結構,另一種是圖形管線的概念。

在客戶端-伺服器結構中,OpenGL ES 框架被當作客戶端,圖形硬體被當作伺服器,使用者應用同客戶端互動,將要渲染的資源,如紋理、頂點資料等,提供給客戶端,客戶端將這些資料轉化為圖形硬體可以處理的資料,然後傳遞給 GPU 進行處理。

圖形管線將圖形的繪製分為多個有序的步驟,從應用準備原始資料,到執行繪製命令傳送頂點資料,然後處理頂點資料進行柵格化為片段,對每個片段進行顏色的計算和深度值的設定,最後將所有的片段集合成一頁幀資料進行顯示。這些步驟可以同步進行,但是下一個步驟的資料輸入來自上一個步驟的輸出,所以最低處理資料效率的步驟將限制整個幀的生成效率。當進行效能優化時,應首先確定最低效率的步驟是哪一步及造成其效率低的原因。

  • OpenGL ES 3.0

    從 iOS 7 開始,可以使用 OpenGL ES 3.0 版本,該版本相較於以前的版本提供了一些新的特性。詳細資訊可以參見官方網站

    在 3.0 版本中,可以同時渲染多個與幀快取相關聯的目標,即將片段著色器的計算結果輸出到多個目標快取中。

    //為幀快取關聯多個顏色快取目標
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _colorTexture, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, _positionTexture, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, _normalTexture, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, _depthTexture, 0);
    
    //渲染指定的關聯目標
    GLenum targets[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
    glDrawBuffers(3, targets);
  • OpenGL ES 2.0

    OpenGL ES 2.0 中通過可程式設計著色器來實現圖形管線的可變性,適用於當前所有的 iOS 裝置,並且在 OpenGL ES 3.0 中引入的特性可以通過 2.0 版本中的擴充套件函式實現,所以兩者可以相互相容。

  • OpenGL ES 1.1

    OpenGL ES 1.1 提供最基本的固定圖形管線。

設計

要實現高效能的 OpenGL ES 應用,需要注意兩點,一是注意圖形管線各個步驟的並行推進,另一個是注意應用與圖形硬體間的資料流動。

當應用載入時,第一步應初始化所需要的靜態資源,將這些資源封裝到 OpenGL ES 的物件中去,在整個或部分應用的生命週期中,這些資源保持不變。

另外,複雜指令和狀態的改變也應用 OpenGL ES 物件替代,這樣,便可以通過一個函式呼叫來使用這個物件。

建立或修改 OpenGL ES 物件很耗費資源,所以這種操作應儘可能的放在幀渲染的開始或結束時。

在渲染迴圈中,OpenGL ES 中的資料不應再次傳回應用中,因為從 GPU 中向 CPU 中傳輸資料很慢,並且當該資料在下面的幀渲染中仍然需要用到時,當前應用會阻塞直到所有提交的指令均已完成。

當繪圖指令發出後,其並不一定會立刻執行,而是儲存在指令快取區中。但是 OpenGL ES 中的某些函式會將快取區中的所有指令提交到圖形硬體中執行,而有的函式除了提交所有的指令外,還會停止接收指令直到已提交的指令全部執行結束。

  • glFlush 函式會提交指令快取中的所有指令到圖形硬體中,但不會等待所有指令執行完畢。
  • glFinish 、glReadPixels 函式不僅提交所有指令,而且會等待所有指令執行完畢。
  • 當指令區滿時,指令會提交到圖形硬體中。

在桌面系統的 OpenGL 實現中,可以通過呼叫 glFlush 函式來實現 GPU 和 CPU 的平衡,但是在 iOS 系統中不可以這樣操作,呼叫 glFlush 和 glFinish 函式一般只有下面兩種情況:

* 當應用進入後臺時,需要提交所有指令,因為 iOS 系統禁止後臺應用執行圖形硬體指令。
* 當需要在不同上下文間共享 OpenGL ES 物件時,需要確保所有指令被執行完畢,共享的資源渲染結束。

在使用 glGet*() 、glGetError() 請求 OpenGL ES 的狀態時,總是要求已提交的繪製命令執行完畢,所以這種訪問 OpenGL ES 狀態的方式會使 CPU 鎖定 GPU 而導致處理效能的降低,所以通常應拷貝一份 OpenGL ES 狀態,直接訪問拷貝的狀態。

諸如 glCheckFramebufferStatus() 、glGetProgramInfoLog() 和 glValidateProgram() 等函式只在除錯模式下有效,釋出模式下應省略。

雙快取區

當使用 OpenGL ES 物件管理資源時,其不能夠被 OpenGL ES 和應用同時使用,這就降低了 CPU 和 GPU 的並行效能。使用單緩衝區處理同一個資源時,CPU 總是要同步 GPU ,等待提交的命令執行完畢後,在進行後續的處理,這同樣降低了 CPU 與 GPU 的並行效能。

所以為了提高 CPU 和 GPU 的並行效能,採用雙快取區,CPU 和 GPU 可以同時處理不同快取區中的資料,但是這要求兩者處理任務的結束時間相近。可以增加快取區來防止 CPU 或 GPU 處於空閒狀態,但是該操作會消耗額外的記憶體空間。

另外,OpenGL ES 儲存著當前複雜的狀態資料,當前著色器程式,全域性變數,紋理單元,頂點資料快取以及頂點資料的關聯屬性等。改變這些狀態需要耗費資源,所以應儘量避免不必要的狀態改變,也不要重複設定狀態,即使狀態值相同,OpenGL ES 也不會去校驗,而是直接去更新狀態值。並且狀態值設定後並不是立即生效,而是當繪製命令執行時,使用必要的狀態資訊去進行繪製,所以可以在繪製之前或需要該狀態去繪製時,設定相應的狀態。

效能調優

不同於 OS X 及其他桌面系統,基於 iOS 系統的裝置的記憶體資源及 CPU 效能終究要差一點。嵌入的 GPU 所用的演算法為適應低記憶體和有限的電量進行了優化,不同於普通電腦 GPU 所用的演算法,所以如果不能高效的渲染影象,不僅會造成幀的重新整理頻率過低,還會降低電池的壽命。

除錯

在上線應用前,要對應用做效能測試及調優,使用 Xcode 中的除錯功能檢視應用的整體效能。使用 OpenGL ES Analysis 和 OpenGL ES Driver 工具獲取更詳細的資訊分析應用執行時的效能。使用 OpenGL ES Frame Debugger 和 Performance Analyser 工具定位效能問題。通過逐個執行 OpenGL ES 指令來觀察每一條指令對狀態、資源和輸出的幀資料的影響;還可以檢視著色程式原始碼並進行修改,觀察修改後對渲染影象的影響。

通過呼叫 glGetError 函式可以獲取呼叫 OpenGL ES API 時產生的錯誤,或者其他效能問題,但是頻繁的呼叫該函式本就會降低應用的效能,所以在調優過程中,應使用工具直接檢視其記錄的錯誤,還可以新增 OpenGL ES Error 斷點,當 OpenGL ES 報錯時,程式會自動停止。

為了除錯的可讀性,可以通過 EXT_debug_markerEXT_debug_label 擴充套件將一組相關的繪製指令新增到一個邏輯分組中並且可以為 OpenGL ES 物件新增一個可讀的名稱。

使用 glPushGroupMarkerEXT 函式定義一個命令組的開始,並提供組名,然後後面新增相關的指令函式,最後使用 glPopGroupMarkerEXT 函式結束一個組,組與組之間可以進行巢狀。當使用 GLKView 進行繪製時,所有的指令函式都放在 Rendering 中。

glPushGroupMarkerEXT(0, "Draw Spaceship");
glBindTexture(GL_TEXTURE_2D, _spaceshipTexture);
glUseProgram(_diffuseShading);
glBindVertexArrayOES(_spaceshipMesh);
glDrawElements(GL_TRIANGLE_STRIP, 256, GL_UNSIGNED_SHORT, 0);
glPopGroupMarkerEXT();

同樣,使用 glLabelObjectEXT 函式為 OpenGL ES 物件指定一個可讀的名稱,如使用 GLKTextureLoader 載入紋理資料物件,那麼該物件命名為其所在的檔名稱。

glGenVertexArraysOES(1, &_spaceshipMesh);
glBindVertexArrayOES(_spaceshipMesh);
glLabelObjectEXT(GL_VERTEX_ARRAY_OBJECT_EXT, _spaceshipMesh, 0, "Spaceship");

效能優化

為了儘可能的節省系統資源,應對應用進行調優。

  • Core Animation 會快取渲染的結果,當資料未發生變化時,不應重新渲染影象,當資料發生變化時,也不應以最快的速度進行渲染,而是以適當且穩定的速度進行渲染,這樣既能流暢平滑的顯示內容,也能節約電量。

  • 對於能夠預先計算儲存的資料,不應放在執行時進行計算。

  • 在使用 OpenGL ES 2.0 及之後版本的框架時,應該針對不同的任務建立多個著色器,不應讓一個著色器完成所有的任務。
  • 禁用所有不必要的函式,如禁用 OpenGL ES 1.1 中不需要的固定函式操作;禁用不需要的高亮、混合操作函式;在 2D 繪製時,禁用霧化和深度測試等功能。

基於瓦片的延遲渲染

在 iOS 裝置上的所有 GPU 都使用了 tile-based deffered rendering(TBDR) 技術。當 OpenGL ES 函式將渲染指令提交到硬體時,其只是被儲存在命令快取區中,並沒有立即執行。當要顯示渲染快取區中的內容或重新整理命令快取區中的命令時,硬體才開始執行這些命令。在處理過程中,整個幀會被分為許多個瓦片,然後對每一個瓦片執行一遍渲染命令。瓦片的記憶體是 GPU 硬體中的一部分,所以渲染過程相較於傳統流模式快。因為在這種 GPU 結構中可以一次處理整個場景中的所有頂點,並且消除隱藏面的片段資料。對於不可見且不參與抽樣處理的畫素會被遺棄,這樣減少 GPU 的計算量。

當 TBDR 圖形處理器開始渲染一個瓦片時,其必需先將幀快取中的部分內容從共享記憶體空間中轉換到 GPU 中的瓦片記憶體中,這個過程叫做 logical buffer load ,為了避免不必要的時間和電量的消耗,應先呼叫 glClear 函式清空前一幀快取的資料內容。當 GPU 結束了一個瓦片的渲染,其必需將瓦片畫素資料寫回共享記憶體中,這個轉換過程叫做 logical buffer store 。這個過程同樣消耗資源,所以除了需要顯示在螢幕上的顏色渲染資料必需要寫回共享記憶體外,其他與幀快取相關聯的資料會在下一個幀渲染時重新生成,所以不必進行儲存。對於 OpenGL ES 而言,其會自動儲存這些快取到共享記憶體,可以呼叫 glInvalidateFramebuffer(OpenGL ES 3.0)或 glDiscardFramebufferEXT(OpenGL ES 1.1~2.0)函式明確廢棄這些快取。當渲染目標發生切換時,logical buffer load 和 store 步驟也會重新執行,所以對於同一個目標的渲染應放在一起進行,儘量避免反覆切換目標。

當 TBDR 圖形處理器使用深度值快取資料自動處理整個場景的隱藏介面消除操作時,需要確保只有一個片段著色器是有效的。並且,當顏色混合、透明度測試有效或片段著色器使用了廢棄指令或輸出 gl_FlagDepth 變數時,GPU 便無法使用深度快取資料來判斷一個片段是否是可見的來,此時,便需要片段著色器對每一個畫素進行計算。這樣會增加額外的消耗,所以要儘量避免這些操作。如果無法避免,可以使用下面的方法來減少效能的消耗。

  • 根據透明度進行排序,先繪製不透明的物件,再繪製有著色器參與的圖形(使用了廢棄指令或透明度測試),最後繪製透明度混合的物件。
  • 裁剪空白的區域,以減少片段處理的資料量。
  • 儘早使用廢棄指令以減少不必要的計算。
  • 將透明度的值設為 0 ,而不是使用透明度測試或廢棄指令來消除畫素。
  • 考慮使用 Z-Prepass 策略進行渲染,先用包含要廢棄的資料的著色器簡單的渲染整個場景,儲存到深度快取中。然後,使用深度測試函式和燈光渲染器再次渲染整個場景。雖然多通道的渲染相較於單通道的渲染會帶來效能上的損耗,但是如果有大量的丟棄性操作,那麼這種方式效能更優。

上述節約記憶體和計算資源的建議對於大型場景的處理有效,但是並不適用於簡單場景的處理。

減少繪製命令呼叫

每當提交資料進行處理時,CPU 都要向圖形硬體傳送相關的指令,如果頻繁呼叫 glDrawArrays 和 glDrawElements 函式渲染場景,那麼 CPU 的效能可能會限制 GPU 的效能發揮,所以減少不必要的繪製命令很有必要。

  • 將多個數據合併為一組資料
  • 用紋理中的不同部分組成一個紋理集合
  • 使用一個繪製命令渲染多個類似的物件

instanced drawing commands 可以實現一次繪製函式的呼叫實現相同頂點資料的多次繪製,而不必耗費 CPU 時間來設定不同例項的相關的繪製引數,如位置偏移、變換矩陣、顏色或紋理座標等。

如下面的程式碼,過度的呼叫繪製函式,導致 CPU 負載過重。

for (x = 0; x < 10; x++) {
    for (y = 0; y < 10; y++) {
        glUniform4fv(uniformPositionOffset, 1, positionOffsets[x][y]);
        glDrawArrays(GL_TRIANGLES, 0, numVertices);
    }
}

要避免使用這種迴圈方式,首先要使用 glDrawArraysInstanced 和 glDrawElementsInstanced 替換 glDrawArrays 和 glDrawElements 函式,然後為頂點著色器提供繪製每個例項時需要的資訊。OpenGL ES 中有兩種方式提供相關的資訊。

  • shader instance ID 策略,每一次頂點著色器執行時,其內建的 gl_InstanceID 變數儲存著當前需要繪製的例項標識,通過該值可以計算出所需的資訊。
#version 300 es 
in vec4 position; 
uniform mat4 modelViewProjectionMatrix;

void main()
{
    float xOffset = float(gl_InstanceID % 10) * 0.5 - 2.5;
    float yOffset = float(gl_InstanceID / 10) * 0.5 - 2.5;
    vec4 offset = vec4(xOffset, yOffset, 0, 0);

    gl_Position = modelViewProjectionMatrix * (position + offset);
}
  • instanced arrays 策略,將每個例項的資訊儲存在頂點陣列屬性中,著色器需要時可以訪問這些資訊。
//儲存
#define kMyInstanceDataAttrib 5

glGenBuffers(1, &_instBuffer);
glBindBuffer(GL_ARRAY_BUFFER, _instBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(instData), instData, GL_STATIC_DRAW);
glEnableVertexAttribArray(kMyInstanceDataAttrib);
glVertexAttribPointer(kMyInstanceDataAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
glVertexAttribDivisor(kMyInstanceDataAttrib, 1);
#version 300 es

layout(location = 0) in vec4 position;
layout(location = 5) in vec2 inOffset;

uniform mat4 modelViewProjectionMatrix;

void main()
{
    vec4 offset = vec4(inOffset, 0.0, 0.0)
    gl_Position = modelViewProjectionMatrix * (position + offset);
}

頂點資料的使用

不管使用者提交了那些原始圖形以及對圖形管線進行了怎樣的配置,OpenGL ES 總是要對頂點進行處理。一個頂點資料由一個或多個屬性組成,如位置、顏色、法向量或紋理座標等。在 OpenGL ES 2.0 和 3.0 版本中,可以自由定義相關的屬性,但是在 OpenGL ES 1.1 版本中,只能使用由固定的圖形管線函式定義的屬性。

定義一個屬性作為向量,其由一個或多個通道組成,所有屬性中包含的通道的資料型別是統一的。當這些屬性作為引數被載入到著色器的變數中時,未提供的通道的值採用 OpenGL ES 的預設值,即最後一個通道填充 1 ,其他通道填充 0 。

如果在繪製的過程中,所有的或部分的點共享相同的屬性,那麼可以定義一個屬性常量,將其作為處理點的命令的一部分提交到圖形硬體中。

對於需要提交大量原始資料進行渲染的應用,應該小心處理頂點資料以及其提交到 OpenGL ES 的方式。

  • 應減少頂點的資料量
  • 應減少 OpenGL ES 轉換頂點資料到圖形硬體之前的預處理過程
  • 應減少拷貝頂點資料到圖形硬體中的時間
  • 應減少對每個頂點資料的計算量

簡化模型

iOS 中的圖形硬體很強大,但是沒必要使用過於複雜的模型去顯示圖形。減少繪製模型時使用的點的數量,就直接減少了頂點的資料量,同時減少了頂點資料的計算量。

  • 提供不同細節程度的模型,在執行時根據視點距離物體的距離和顯示的尺寸來選擇合適的模型。
  • 使用紋理來取代頂點資訊的提供。
  • 一些模型通過新增點來改善光照細節或渲染質量,該步驟通常放在對每一個點進行計算的光柵化階段,但是這樣做會增加頂點的資料量和模型的計算量。所以,可以考慮將計算過程放在片段著色階段,而不是直接新增額外的點。

    • 使用 OpenGL ES 2.0 或之後的版本,頂點著色器的計算結果經圖形硬體處理後,傳遞給片段著色器,這樣可以將頂點著色器的負擔分給片段著色器一些,避免頂點處理程式被阻塞。
    • 在 OpenGL ES 1.1 中,可以通過 DOT3 對每個片段進行光照的處理,該過程使用 GL_DOT3_TGB 模式組合包含法向量資訊的紋理。

避免常量的重複儲存

在整個模型中都要用到的常量,不應該複製到每個頂點資料中,可以設定頂點屬性常量,或者儲存到著色器的全域性變數中。

使用最小的資料型別

當指定屬性的通道的大小時,應選擇其中最小的資料型別,可以參考下面幾條建議:

  • 頂點顏色使用 4 個無符號位元組(GL_UNSIGNED_BYTE
  • 紋理座標使用 2 個或 4 個無符號位元組(GL_UNSIGNED_BYTE)或無符號短整型(GL_UNSIGNED_SHORT),不宜將一系列紋理座標放在一個屬性中。
  • 避免使用 GL_FIXED 資料型別,因為其與 GL_FLOAT 資料型別佔用相同的記憶體空間,但是表示的資料範圍較小,而且 iOS 裝置硬體支援浮點資料快速處理。
  • OpenGL ES 3.0 上下文支援使用更小的資料型別表示更大的範圍,如 GL_HALF_FLOATGL_INT_2_10_10_10_REVGL_FLOAT 佔用更小的記憶體但能夠方便精準的表達諸如法向量等屬性。

如果指定更小的通道資料型別,需要重新排列頂點資料以避免出錯。

使用交錯的頂點資料

在使用頂點資料時,其結構可以是一個結構體包含多個數組,也可以是一個數組中包含多個結構體。在 iOS 中,採用第二種方式組織資料。將頂點所有的屬性結構體放在一起組成一個數據組,而後每個頂點資料依次排列,這樣整個資料區域由多個結構體交錯排列,能夠更好的利用記憶體空間。

當然,如果存在需要共享的頂點屬性資料,或者某個屬性資料需要按時重新整理,那麼,可以將該資料從交錯的記憶體區域中分離出來,單獨儲存在一個結構體中。

頂點資料的排列

自定義頂點資料結構時,需要注意其屬性資料的偏移量必需是通道大小的倍數並且必需要是 4 個位元組的倍數,否則,當資料被提交到圖形硬體進行處理時,系統需要先將資料進行拷貝然後排列成所需的格式,才能進行提交。如下圖所示,法向量屬性的偏移量為 6 個位元組,雖然是通道 2 個位元組的倍數,但是不是 4 個位元組的倍數,所以要補充 2 個位元組。

使用三角形帶批量處理頂點資料

使用三角形帶可以避免重合點的重複計算,降低效能的損耗。如下圖,將 9 個點縮減到 5 個點,大大減少了計算量。

通常,可以將多個三角形帶關聯起來進行繪製,但是這也意味著在繪製圖形的過程中使用相同的頂點屬性和著色器。

在關聯兩個三角形帶時,複製第一個三角形帶的最後一個點和第二個三角形帶的第一個點,插入到資料中,當資料提交時,相同點構不成三角形會自動忽略。

當然,為了更好的效能,最好單獨提交三角形帶資料,並且為了避免在同一個頂點快取中多次設定相同頂點的資料,可以單獨建立一個索引快取記錄三角形帶在記憶體中的位置,然後使用 glDrawElements 函式進行繪製(合適時,可以使用 glDrawElementsInstanced 或 glDrawRangeElements 函式)。

在 OpenGL ES 3.0 中,可以在索引表中插入極大值來表示一個三角形帶的結束,這樣便不必重複儲存頂點的資料而實現了三角形帶的組合。

GLushort indexData[11] = {
    0, 1, 2, 3, 4,    // triangle strip ABCDE
    0xFFFF,           // primitive restart index (largest possible GLushort value)
    5, 6, 7, 8, 9,    // triangle strip FGHIJ
};

// Draw triangle strips
glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX);
glDrawElements(GL_TRIANGLE_STRIP, 11, GL_UNSIGNED_SHORT, 0);

在提交資料前,可以對頂點和索引進行排序,這樣相近的點在一起進行繪製,圖形硬體會儲存最近的計算的點的結果,可以避免相關資訊的重複計算。

使用頂點快取物件

下面的例子中定義了一個結構體,結構體中包含要傳遞給頂點著色器的位置和顏色資料。在 DrawModel 函式中,配置了兩個屬性,最後進行三角形帶的渲染。

typedef struct _vertexStruct
{
    GLfloat position[2];
    GLubyte color[4];
} vertexStruct;

void DrawModel()
{
    const vertexStruct vertices[] = {...};
    const GLubyte indices[] = {...};

    glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE,
        sizeof(vertexStruct), &vertices[0].position);
    glEnableVertexAttribArray(GLKVertexAttribPosition);

    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_UNSIGNED_BYTE, GL_TRUE,
        sizeof(vertexStruct), &vertices[0].color);
    glEnableVertexAttribArray(GLKVertexAttribColor);

    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, indices);
}

每一次呼叫這個函式時,都需要將頂點資料拷貝到 OpenGL ES 然後進行轉換,再傳遞給圖形硬體。如果反覆進行呼叫,並且頂點資料並沒有改變,那麼不必要的拷貝及轉換操作造成了效能的浪費。為了避免這種情況,應當使用 vertex buffer object (VBO),OpenGL ES 持有這些記憶體,並且可以進行預處理,將資料轉換為需要的格式,方便圖形硬體的訪問。

在應用啟動時,建立頂點快取,並將其繫結到當前的圖形上下文。

GLuint    vertexBuffer;
GLuint    indexBuffer;

void CreateVertexBuffers()
{

    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

}

下面的程式碼對上述的 DrawModel 函式進行了改寫,主要的區別在於 glVertexAttribPointer 函式中不在提供指向頂點陣列中資料的指標,而是提供頂點快取的屬性偏移量。

void DrawModelUsingVertexBuffers()
{
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE,
        sizeof(vertexStruct), (void *)offsetof(vertexStruct, position));
    glEnableVertexAttribArray(GLKVertexAttribPosition);

    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_UNSIGNED_BYTE, GL_TRUE,
        sizeof(vertexStruct), (void *)offsetof(vertexStruct, color));
    glEnableVertexAttribArray(GLKVertexAttribColor);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, (void*)0);
}
void glVertexAttribPointer( GLuint index, GLint size, 
                                 GLenum type, GLboolean normalized, 
                                 GLsizei stride, const GLvoid *pointer);
  • index 指定要修改的頂點屬性的索引值
  • size 指定要修改的頂點屬性的通道數量。必須為 1、2、3 或者 4。初始值為 4(如 position(x,y,z)有 3 個通道,而顏色(r,g,b,a)有 4 個)。
  • type 指定通道的資料型別(如 GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT,GL_UNSIGNED_SHORT, GL_FIXED, GL_FLOAT,預設為 GL_FLOAT)。
  • normalized 指定當被訪問時,固定點資料值是否應該被歸一化(GL_TRUE)或者直接轉換為固定點值(GL_FALSE)。
  • stride 指定連續頂點屬性之間的偏移量,預設值為 0 表示屬性依次排列(其實就是頂點所佔記憶體的大小)。
  • pointer 指定頂點資料中第一個頂點屬性的偏移量,預設值為 0 。

快取資料的方式

在頂點快取物件中,一個關鍵的設計是其可以告知 OpenGL ES 資料以何種方式進行儲存。在 CreateVertexBuffers 函式中呼叫了下面的函式:

glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

其中 GL_STATIC_DRAW 表示儲存的內容不會進行修改,這樣 OpenGL ES 可以在儲存過程中進行優化。除此之外,還有 GL_DYNAMIC_DRAW 值,表示頂點快取會多次使用,並且儲存的內容在渲染迴圈中會發生改變,而 GL_STREAM_DRAW 則表示快取區在使用幾次後便會被遺棄。

當然,在 iOS 系統中,GL_DYNAMIC_DRAWGL_STREAM_DRAW 沒什麼區別,可以使用 glBufferSubData 函式重新整理快取區資料,但是這會增加系統開銷,因為命令快取區中的命令會被提交併等待提交的命令執行完畢,不過採用雙快取區或多快取區可以減緩這種情況帶來的效能消耗。

當頂點屬性無法統一格式或者某一個屬性會不斷變更時,應將相關的屬性分離出來,建立多個快取進行儲存。如下面的例程,單獨定義一個快取來儲存顏色資料,並且使用 GL_DYNAMIC_DRAW 來表明該快取中的內容是可變的。

typedef struct _vertexStatic
{
    GLfloat position[2];
} vertexStatic;

typedef struct _vertexDynamic
{
    GLubyte color[4];
} vertexDynamic;

//定義兩個快取,分別儲存可變內容和不變的內容
GLuint    staticBuffer;
GLuint    dynamicBuffer;
GLuint    indexBuffer;

const vertexStatic staticVertexData[] = {...};
vertexDynamic dynamicVertexData[] = {...};
const GLubyte indices[] = {...};

void CreateBuffers()
{
// Static position data
    glGenBuffers(1, &staticBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(staticVertexData), staticVertexData, GL_STATIC_DRAW);

// Dynamic color data
// While not shown here, the expectation is that the data in this buffer changes between frames.
    glGenBuffers(1, &dynamicBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(dynamicVertexData), dynamicVertexData, GL_DYNAMIC_DRAW);

// Static index data
    glGenBuffers(1, &indexBuffer);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
}

void DrawModelUsingMultipleVertexBuffers()
{
    glBindBuffer(GL_ARRAY_BUFFER, staticBuffer);
    glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE,
        sizeof(vertexStruct), (void *)offsetof(vertexStruct, position));
    glEnableVertexAttribArray(GLKVertexAttribPosition);

    glBindBuffer(GL_ARRAY_BUFFER, dynamicBuffer);
    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_UNSIGNED_BYTE, GL_TRUE,
        sizeof(vertexStruct), (void *)offsetof(vertexStruct, color));
    glEnableVertexAttribArray(GLKVertexAttribColor);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glDrawElements(GL_TRIANGLE_STRIP, sizeof(indices)/sizeof(GLubyte), GL_UNSIGNED_BYTE, (void*)0);
}

使用頂點陣列物件管理頂點陣列狀態的改變

在上述的 DrawModelUsingMultipleVertexBuffers 函式中,啟用了一些屬性,綁定了一些頂點快取物件並且進行了一些配置。但是每一幀的渲染呼叫這個函式時,其中的許多圖形管線都進行了重複的設定,這是對效能的浪費。使用頂點陣列物件來儲存整個屬性配置,這樣可以重複使用配置引數,提高渲染的效率。

如下圖,用兩個頂點陣列物件儲存了兩個不相互影響的頂點屬性配置,並且配置的不同屬性可以儲存在一個頂點快取區中或多個快取區中。

實現上圖的例程如下:

void ConfigureVertexArrayObject()
{
    // Create and bind the vertex array object.
    glGenVertexArrays(1,&vao1);
    glBindVertexArray(vao1);

    // Configure the attributes in the VAO.
    glBindBuffer(GL_ARRAY_BUFFER, vbo1);
    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE,
        sizeof(staticFmt), (void*)offsetof(staticFmt,position));
    glEnableVertexAttribArray(GLKVertexAttribPosition);
    glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_UNSIGNED_SHORT, GL_TRUE,
        sizeof(staticFmt), (void*)offsetof(staticFmt,texcoord));
    glEnableVertexAttribArray(GLKVertexAttribTexCoord0);
    glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE,
        sizeof(staticFmt), (void*)offsetof(staticFmt,normal));
    glEnableVertexAttribArray(GLKVertexAttribNormal);

    glBindBuffer(GL_ARRAY_BUFFER, vbo2);
    glVertexAttribPointer(GLKVertexAttribColor, 4, GL_UNSIGNED_BYTE, GL_TRUE,
        sizeof(dynamicFmt), (void*)offsetof(dynamicFmt,color));
    glEnableVertexAttribArray(GLKVertexAttribColor);

    // Bind back to the default state.
    glBindBuffer(GL_ARRAY_BUFFER,0);
    glBindVertexArray(0); 
}

在上面的例程中,生成的頂點陣列物件被繫結到當前上下文中,而生成的頂點配置屬性則是儲存在頂點陣列物件中,而不是繫結到上下文中。當頂點陣列物件設定完成後,不應在執行時對其進行修改,如果有需要,應建立多個頂點陣列物件。如在雙快取應用中,配置一組頂點陣列物件用來渲染奇數幀,另一組用來渲染偶數幀,當然所使用的頂點陣列物件要連線到待渲染的幀的頂點快取物件中。

快速渲染的快取對映

在 OpenGL ES 中,一個難點是實現動態資源的快速渲染。如在渲染每一幀時,頂點資料都發生了變化,那麼如何管理資料在應用和 OpenGL ES 之間的傳遞是平衡 CPU 和 GPU 效能的關鍵。傳統技術,如 glBufferSubData 函式,其會強制 GPU 等待資料傳入,即使 GPU 可以從當前緩衝區中獲取所需的渲染資料,所以這種做法並不高效。

在渲染幀頻繁的應用中,同時想要修改頂點快取中的內容,但是如果上一幀的渲染命令正在執行,GPU 正在使用快取中的資料,此時想要修改快取準備下一幀的內容,那麼,CPU 會被阻塞,直到 GPU 執行完畢。對於這種情況,可以手動同步 CPU 和 GPU 。通過呼叫 glMapBufferRange 函式來獲取 OpenGL ES 的記憶體範圍,然後寫入新的資料。另外,該函式還可以將緩衝區中的資料儲存到應用快取中,還允許使用同步物件對快取進行非同步修改。

GLsync fence;
GLboolean UpdateAndDraw(GLuint vbo, GLuint offset, GLuint length, void *data) {
    
            
           

相關推薦

iOS OpenGL ES Guide

OpenGL ES 小結 概述 OpenGL ES (Open Graphics Library for Embedded Systems)是訪問類似 iPhone 和 iPad 的現代嵌入式系統的 2D 和 3D 圖形加速硬體的標準。

OpenGL ES Programming Guide for iOS 第一章

關於OpenGL ES Open Graphics Library(OpenGL)用於二維及三維資料的視覺化。它是一種多用途的開放標準圖形庫,支援二維和三維數位內容創作,機械和建築設計,虛擬樣,飛行模擬,遊戲,以及更多的應用。OpenGL允許應用程式開發人員配置3D圖形管線

iOS開發-OpenGL ES入門教程1

貼圖 iba 細節 con osi tutorial name rip tex http://www.jianshu.com/p/750fde1d8b6a 這裏是一篇新手教程,環境是Xcode7+OpenGL ES 2.0,目標寫一個OpenGL ES的hello wor

iOS實現圖形編程可以使用三種API(UIKIT、Core Graphics、OpenGL ES及GLKit)

圖像處理 運動 esper 之前 類方法 ati ima quartz 環境 這些api包含的繪制操作都在一個圖形環境中進行繪制。一個圖形環境包含繪制參數和所有的繪制需要的設備特定信息,包括屏幕圖形環境、offscreen 位圖環境和PDF圖形環境,用來在屏幕表面、一個位圖

iOS上使用OpenGL ES渲染YUV

1)建立OpenGL context [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];2)layer設定成不透明 _eaglLayer = (CAEAGLLayer*) self.layer; _eaglLayer.opaque = Y

iOSOpenGL ES 3.0程式設計入門(一):構建Hello World環境

OpenGL ES簡介:      OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三維圖形 API 的子集,針對手機、PDA和遊戲主機等嵌入式裝置而設計。該API由Khronos集團定義推廣,Khronos是一個圖形軟硬體

opengl es入門---常見代碼解析

字符串數組 chm 視口 posit detail 編寫 組件 eat 包含著 轉自:http://blog.csdn.net/wangyuchun_799/article/details/7736928,尊重原創! 3.1創建渲染緩沖區 GLuint m

Mali GPU OpenGL ES 應用性能優化--基本方法

term 定位 mil nis 標誌位 compiler lte void 解決 1. 經常使用優化工具2. 經常使用優化方案 OpenGL ES優化的主要工作是在圖形管道中找到影響性能的bottleneck,其bottleneck一般表如今下面幾方面:

Opengl ES 1.x NDK實例開發之六:紋理貼圖

otto absolute decode super rep mit his viewport 一個 開發框架介紹請參見:Opengl ES NDK實例開發之中的一個:搭建開發框架 本章在第三章(Opengl ES 1.x NDK實例開發之三:多邊形的旋轉)的基礎

OpenGL ES平移矩陣和旋轉矩陣的左乘與右乘效果

角度 style 位置 作用 span 坐標系 rotate 不同的 世界 OpenGL ES平移矩陣和旋轉矩陣的左乘與右乘 在OpenGL 、OpenGL ES中矩陣起著舉足輕重的作用,而矩陣之間的左乘與右乘在效果上是不同的。 一、先平移後旋轉 場景效果:人繞樹旋轉。 原

如何獲取當前應用程序所用的OpenGL ES的版本

版本 wid tom ring add 程序 family 如何 mil ?如何獲取當前應用程序所用的OpenGL ES的版本? 【答案】 ????char* glVersion = (char*)glGetString(GL_VERSION); ????LOGW

glGetString(GL_VERSION) returns “OpenGL ES-CM 1.1” but my phone supports OpenGL 2

try har pri can string oid mean ins phone ?【問】 I‘m trying to make an NDK based OpenGL application. At some point in my code, I wan

Android OpenGL ES 入門系列(一) --- 了解OpenGL ES的前世今生

target 初始化 vertex 單獨 http hang tex 變化 3d圖 轉載請註明出處 本文出自Hansion的博客 OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三維圖形 API 的子集,

Android面試收集錄 OpenGL ES

face use 需要 pen 如何 clas bsp 使用 chan 1.如何用OpenGL ES繪制一個三角形? 編寫一個類實現Renderer接口,實現onDrawFrame方法,onSurfaceChanged方法,onSurfaceCreated方法

[原] OpenGL ES 學習筆記 (一)

信號 ppi sci DC RM 視錐 技術分享 img 比較 1. OpenGL ES 的坐標系在屏幕上的分布               OpenGL ES 的坐標系{x, y, z} 通過圖片的三維坐標系可以知道: - 它是一個三維坐標系 {x,

OpenGL ES

animation https uikit details 系統 數據 tails blog 命令 OpenGL ES是一套多功能開放標準的用於嵌入系統的C-based的圖形庫,用於2D和3D數據的可視化。OpenGL被設計用來轉換一組圖形調用功能到底層圖形硬件(GPU),

03: OpenGL ES 基礎教程02 使用OpenGL ES 基本步驟

war 點數據 緩存 ttr inf demo eve point 指南 第二章:讓硬件為你工作(OpenGL ES 應用實踐指南 iOS卷) 前言:   1:使用OpenGL ES 基本步驟   2:繪制三角形   3:效果    正文: 一:使用OpenGL ES

OpenGL ES著色器語言----------------儲存修飾符

可執行程序 函數參數 匹配 技術分享 預處理 但是 不變 基本 打包 一、存儲修飾符   本地變量只能使用存儲修飾符const。   函數參數只能用const。函數返回值類型和結構體字段不要使用const。 從一個運行時著色器到下一個運行時著色器之間進行數據

OpenGL ES EGL介紹

前面已經在android平臺上使用OpenGL ES的API瞭解瞭如何建立3D圖形已經使用FBO渲染到紋理進行一些其他的操作,起初我學習OpenGL ES的目的就是為了研究Android平臺上錄製螢幕的方案。到目前為止,基礎知識已經具備了,還差一點需要了解的是Embedded Graphic

OpenGL ES 幀緩衝物件(FBO):Render to texture

幀緩衝物件FBO 建立幀緩衝物件 紋理附著 渲染緩衝物件附著 渲染到紋理Render to Texture 渲染到深度紋理