編寫你的應用程序(三)、3D圖形

原文連接:https://developer.chrome.com/native-client/devguide/coding/3D-graphicsjavascript

注意:已針對ChromeOS之外的平臺公佈了此處所述技術的棄用。
請訪問咱們的 遷移指南 瞭解詳情。
java


3D圖形

Native Client應用程序使用OpenGL ES 2.0 API進行3D渲染。本文檔介紹如何在Native Client模塊中調用OpenGL ES 2.0接口以及如何構建高效的呈現循環。它還解釋瞭如何驗證GPU驅動程序和測試特定的GPU功能,並提供了有助於確保渲染代碼高效運行的提示。程序員

注意:3D繪圖和OpenGL是複雜的主題。本文檔僅涉及與Native Client環境中的編程直接相關的問題。要了解有關OpenGL ES 2.0自己的更多信息,請參閱OpenGL ES 2.0編程指南web

驗證客戶端圖形平臺

Native Client是一種軟件技術,它容許您對應用程序進行一次編碼並在多個平臺上運行,而無需擔憂每一個可能的目標平臺上的實現細節。在硬件級別提供相同的支持很困難。圖形硬件來自許多不一樣的製造商,並由不一樣質量的驅動程序控制。特定的GPU驅動程序可能不支持每一個OpenGL ES 2.0功能,而且已知某些驅動程序具備可被利用的漏洞。chrome

即便GPU驅動程序能夠安全使用,您的程序也應該在啓動應用程序以前執行驗證檢查,以確保驅動程序支持您須要的全部功能。編程

用JavaScript審覈驅動程序

在啓動時,應用程序應執行一些可在其託管網頁上以JavaScript實現的其餘測試。執行這些測試的腳本應該包含在模塊的embed標記以前,理想狀況下,embed只有在這些測試成功時,標記纔會出如今託管頁面上。json

首先要檢查的是你是否能夠建立圖形上下文。若是能夠,請使用上下文確認是否存在任何所需的OpenGL ES 2.0擴展。在檢查擴展時,您可能須要引用擴展註冊表幷包含供應商前綴canvas

在Native Client中審覈驅動程序

建立一個上下文api

一旦您經過了JavaScript驗證測試,就能夠安全地將Native Client embed標記添加到託管網頁並加載模塊。做爲模塊初始化代碼的一部分,您必須經過建立C ++ Graphics3D對象或調用PPB_Graphics3DAPI函數爲應用程序建立圖形上下文Create。不要覺得這總會成功; 你仍然可能在建立上下文時遇到問題。若是您處於開發模式且沒法建立上下文,請嘗試建立更簡單的版本,以查看是否要求不支持的功能或超出驅動程序資源限制。您的生產代碼應始終檢查上下文是否已建立,若是不是這樣,則應正常失敗。瀏覽器

檢查擴展和功能

並不是每一個GPU都支持每一個擴展或具備相同數量的紋理單元,頂點屬性等。在啓動時,調用glGetString(GL_EXTENSIONS)並檢查擴展和所需的功能。例如:

  • 若是您使用mipmaps的非2次冪紋理,請確保 GL_OES_texture_npot存在。
  • 若是您使用浮點紋理,請確保GL_OES_texture_float 存在。
  • 若是使用的是DXT1,DXT3,DXT5或紋理,確保相應的擴展EXT_texture_compression_dxt1GL_CHROMIUM_texture_compression_dxt3以及 GL_CHROMIUM_texture_compression_dxt5存在的。
  • 若是您正在使用的功能glDrawArraysInstancedANGLE, glDrawElementsInstancedANGLEglVertexAttribDivisorANGLE,或PPAPI接口PPB_OpenGLES2InstancedArrays,確保相應的擴展GL_ANGLE_instanced_arrays存在。
  • 若是您正在使用該功能glRenderbufferStorageMultisampleEXT或PPAPI接口PPB_OpenGLES2FramebufferMultisample,請確保GL_CHROMIUM_framebuffer_multisample存在相應的擴展名。
  • 若是您正在使用的功能glGenQueriesEXTglDeleteQueriesEXT, glIsQueryEXTglBeginQueryEXTglEndQueryEXTglGetQueryivEXT, glGetQueryObjectuivEXT,或PPAPI接口PPB_OpenGLES2Query,確保相應的擴展GL_EXT_occlusion_query_boolean 存在。
  • 若是您正在使用的功能glMapBufferSubDataCHROMIUM, glUnmapBufferSubDataCHROMIUMglMapTexSubImage2DCHROMIUM, glUnmapTexSubImage2DCHROMIUM,或PPAPI接口PPB_OpenGLES2ChromiumMapSub,確保相應的擴展 GL_CHROMIUM_map_sub存在。

檢查系統功能glGetIntegerv並相應地調整着色器程序以及紋理和頂點數據:

  • 若是在頂點着色器中使用紋理,請確保 glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, ...)glGetIntegerv(GL_MAX_TEXTURE_SIZE, ...)返回大於0的值。
  • 若是在單個着色器中使用的紋理超過8個,請確保 glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...)返回的值大於或等於所需的同時紋理數。

在Chrome網上應用店中審覈驅動程序

若是您選擇將應用程序放在Chrome Web Store中,則其Web Store 清單文件能夠webgl 在requirements參數中包含該功能。它看起來像這樣:

"requirements": {
  "3D": {
    "features": ["webgl"]
  }
}

雖然WebGL在技術上是一個JavaScript API,但指定該webgl功能也適用於OpenGL ES 2.0,由於兩個接口都使用相同的驅動程序。

此清單項目不是必需的,但若是您將其包含在內,那麼若是瀏覽器在不支持OpenGL ES 2.0或使用已知列入黑名單的GPU驅動程序的計算機上運行,​​Chrome網上應用店將阻止用戶安裝該應用程序能夠邀請一次攻擊。

若是Web Store肯定用戶的驅動程序不足,則應用程序將不會顯示在商店的磁貼顯示中。可是,它會出如今商店搜索結果中,或者若是用戶直接連接到它,在這種狀況下,用戶仍然能夠下載它。可是當用戶到達安裝頁面時將檢查清單要求,若是出現問題,瀏覽器將顯示消息「此計算機不支持此應用程序。安裝已被禁用。「

基於清單的檢查僅適用於直接從Chrome網上應用店下載。經過內聯安裝加載應用程序時不會執行此操做。

遇到問題時該怎麼辦

使用上述審查程序,您應該可以在應用程序運行以前檢測最多見的問題。若是存在問題,您的代碼應儘量清楚地描述問題。若是缺乏功能,這很容易。沒法建立圖形上下文更難以診斷。至少,您能夠建議用戶嘗試更新驅動程序。您可能但願轉到描述如何進行更新的Chrome頁面。

若是用戶沒法更新驅動程序,或者問題仍然存在,請務必收集有關其圖形環境的信息。詢問Chrome about:gpu頁面的內容 。

記錄不可靠的驅動程序

在用戶文檔中包含有關已知可疑驅動程序的信息會頗有幫助。這可能有助於肯定流氓驅動程序是不是問題的緣由。GPU驅動程序黑名單有不少來源。能夠在Chromium項目 和Khronos找到兩個這樣的列表。您可使用這些列表在文檔中包含警告用戶有關危險驅動程序的信息。

測試你的防護

您能夠經過使用如下標誌運行Chrome(一次性所有)並觀察應用程序如何響應來測試您的驅動程序驗證代碼:

  • --disable-webgl
  • --disable-pepper-3d
  • --disable_multisampling
  • --disable-accelerated-compositing
  • --disable-accelerated-2d-canvas

調用OpenGL ES 2.0命令

在Native Client中編寫OpenGL ES 2.0調用有三種方法。

使用「純」OpenGL ES 2.0函數調用

您能夠經過Pepper擴展庫進行OpenGL ES 2.0調用。SDK示例以examples/api/graphics_3d這種方式工做。在文件中 graphics_3d.cc,密鑰初始化步驟以下:

  • 在文件頂部添加如下內容:

  • #include <GLES2/gl2.h>
    #include "ppapi/lib/gl/gles2/gl2ext_ppapi.h"

    定義功能InitGL。確切的規範attrib_list 將是特定於應用程序的。

  • bool InitGL(int32_t new_width, int32_t new_height) {
      if (!glInitializePPAPI(pp::Module::Get()->get_browser_interface())) {
        fprintf(stderr, "Unable to initialize GL PPAPI!\n");
        return false;
      }
    
      const int32_t attrib_list[] = {
        PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8,
        PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 24,
        PP_GRAPHICS3DATTRIB_WIDTH, new_width,
        PP_GRAPHICS3DATTRIB_HEIGHT, new_height,
        PP_GRAPHICS3DATTRIB_NONE
      };
    
      context_ = pp::Graphics3D(this, attrib_list);
      if (!BindGraphics(context_)) {
        fprintf(stderr, "Unable to bind 3d context!\n");
        context_ = pp::Graphics3D();
        glSetCurrentContextPPAPI(0);
        return false;
      }
    
      glSetCurrentContextPPAPI(context_.pp_resource());
      return true;
    }
    包含邏輯Instance::DidChangeViewInitGL在必要時調用:在應用程序啓動時(當圖形上下文爲NULL時)以及模塊的View更改大小時。

使用Regal

若是您正在移植OpenGL ES 2.0應用程序,或者習慣使用OpenGL ES 2.0編寫,那麼您應該堅持使用上面描述的Pepper API或純OpenGL ES 2.0調用。若是要移植使用不在OpenGL ES 2.0中的功能的應用程序,請考慮使用Regal。Regal是一個支持許多OpenGL版本的開源庫。Regal最近添加了對Native Client的支持。Regal將大多數OpenGL調用直接轉發到底層圖形庫,但它也能夠模擬其餘未包含的調用(當存在硬件支持時)。有關 詳細信息,請參閱libregal

使用Pepper API

您的代碼能夠直接調用Pepper PPB_OpenGLES2 API,就像任何Pepper接口同樣。當您以這種方式編寫時,每次調用OpenGL ES 2.0函數都必須以對Pepper接口的引用開始,第一個參數是圖形上下文。要調用該函數glCompileShader,您的代碼可能以下所示:

ppb_g3d_interface->CompileShader(graphicsContext, shader);

這種方法專門針對Pepper API。每一個調用對應一個OpenGL ES 2.0函數,但語法對於Native Client是惟一的,所以源文件不可移植。

實現渲染循環

圖形應用程序須要以高頻率運行的連續幀渲染和重繪循環。要得到最佳幀速率,瞭解Native Client模塊中的OpenGL ES 2.0代碼如何與Chrome進行交互很是重要。

Chrome和Native Client流程

Chrome是一款多進程瀏覽器。每一個Chrome標籤都是一個單獨的進程,運行具備本身主線程的應用程序(咱們稱之爲Chrome主線程)。當應用程序啓動Native Client模塊時,該模塊將在新的單獨沙盒進程中運行。模塊的進程有本身的主線程(Native Client線程)。Chrome和Native Client進程在其主線程上使用Pepper API調用相互通訊。

當Chrome主線程調用Native Client線程(例如鍵盤和鼠標回調)時,Chrome主線程將阻止。這意味着Native Client線程上的冗長操做能夠從Chrome中竊取週期,而且在Native Client線程上執行阻止操做可能會使您的應用程序停頓。

Native Client使用回調函數來同步兩個進程的主線程。只有某些Pepper函數使用回調; SwapBuffers 就是其中之一。

SwapBuffers 及其回調函數

SwapBuffers是無阻礙的; 它從Native Client線程調用並當即返回。當SwapBuffers被調用時,它異步運行的Chrome的主線程上。它切換圖形數據緩衝區,處理任何所需的合成操做,並重繪屏幕。屏幕更新完成後,SwapBuffer將從Chrome線程調用做爲參數之一包含的回調函數,並在Native Client線程上執行。

要建立渲染循環,Native Client模塊應該包含一個執行渲染工做而後執行的函數SwapBuffers,並將自身做爲SwapBuffer回調傳遞。若是您的渲染代碼高效且運行速度快,則此方案將實現最高的幀速率。該文檔SwapBuffers解釋了爲何這是最佳的:由於僅當插件的當前狀態實際在屏幕上時才執行回調,此功能提供了一種速率限制動畫的方法。經過在繪製下一幀以前等待圖像在屏幕上,您能夠確保不會比屏幕更新更快地生成更新。

下圖說明了Chrome和Native Client進程之間的交互。特定於應用程序的呈現代碼在DrawNative Client線程上調用的函數中運行。藍色向下箭頭阻止從主線程到Native Client的調用,綠色向上箭頭是SwapBuffers從Native Client到主線程的非阻塞 調用。全部OpenGL ES 2.0調用都是Draw在Native Client線程中進行的。

/native-client/images/3d-graphics-render-loop.png

SDK示例 graphics_3d

SDK示例graphics_3d使用函數MainLoop(in hello_world.cc)建立如上所述的呈現循環。MainLoop 調用Render執行渲染工做,而後調用SwapBuffers,將自身做爲回調傳遞。

void MainLoop(void* foo, int bar) {
  if (g_LoadCnt == 3) {
    InitProgram();
    g_LoadCnt++;
  }
  if (g_LoadCnt > 3) {
    Render();
    PP_CompletionCallback cc = PP_MakeCompletionCallback(MainLoop, 0);
    ppb_g3d_interface->SwapBuffers(g_context, cc);
  } else {
    PP_CompletionCallback cc = PP_MakeCompletionCallback(MainLoop, 0);
    ppb_core_interface->CallOnMainThread(0, cc, 0);
  }
}

管理OpenGL ES 2.0管道

OpenGL ES 2.0命令沒法在Chrome或Native Client進程中運行。它們被傳遞到共享存儲器中的FIFO隊列,最好將其理解爲GPU命令緩衝區。命令緩衝區由專用GPU進程共享。經過使用單獨的GPU流程,Chrome實現了另外一層運行時安全性,在將全部OpenGL ES 2.0命令及其參數發送到GPU以前對其進行審查。經過FIFO緩衝命令也能夠加快代碼速度,由於Native Client線程中的每一個OpenGL ES 2.0調用都會​​當即返回,而處理可能會因GPU下降FIFO中排隊的命令而延遲。

在更新屏幕以前,全部介入的OpenGL ES 2.0命令必須由GPU處理。程序員常常嘗試經過在渲染代碼中使用glFlushglFinish命令來確保這一點 。在Native Client的狀況下,這一般是沒必要要的。該SwapBuffers命令執行隱式刷新,Chrome團隊不斷調整GPU代碼以儘快使用OpenGL ES 2.0 FIFO。

有時3D應用程序能夠以難以處理的方式寫入FIFO。命令管道可能會填滿,您的代碼必須等待GPU刷新FIFO。若是是這種狀況,您能夠添加glFlush 調用以加速OpenGL ES 2.0命令FIFO的流程。在開始添加本身的刷新以前,首先嚐試經過監視每幀的渲染時間並查找不一致落在同一OpenGL ES 2.0調用上的不規則尖峯來肯定管道飽和度是否真的成爲問題。若是您確信管道須要加速,請插入glFlush在啓動不生成OpenGL ES 2.0命令的處理塊以前調用代碼。例如,在開始任何多線程粒子工做以前發出刷新,這樣當您再次開始執行OpenGL ES 2.0調用時,命令緩衝區將會清除。肯定呼叫的地點和頻率glFlush可能很棘手,您須要嘗試找到最佳點。

渲染和非活動標籤

用戶一般會在多標籤瀏覽器中切換選項卡。執行3D渲染的性能良好的應用程序應暫停任何實時處理,並在其選項卡變爲非活動狀態時爲其餘進程產生循環。

在Chrome中,非活動選項卡將繼續執行定時功能(例如 setIntervalsetTimeout),但定時器間隔將自動覆蓋,而且在選項卡處於非活動狀態時限制爲很多於一秒。此外,SwapBuffers在選項卡再次處於活動狀態以前,不會發送與呼叫關聯的任何回叫。除了SwapBuffers選項卡處於非活動狀態以外,您可能會從函數接收異步回調。根據應用程序的設計,您能夠選擇在它們到達時處理它們,或者將它們排入緩衝區並在選項卡變爲活動狀態時對它們進行處理。

標籤處於非活動狀態時通過的時間可能至關大。若是主線程脈衝基於SwapBuffers回調,則當選項卡處於非活動狀態時,您的應用將不會更新。Native Client模塊應該可以檢測並響應其運行的選項卡的狀態。例如,當選項卡變爲非活動狀態時,您能夠在Native Client線程中設置一個原子標誌,該標誌將跳過3D渲染並SwapBuffers調用並繼續每隔30毫秒左右調用主線程。這提供了時間來更新仍應在後臺運行的功能,如音頻。調用sched_yieldusleep在任何工做線程上釋放資源並將循環中止到操做系統也可能會有所幫助 。

處理主線程中的選項卡激活

您能夠在託管頁面上使用JavaScript檢測並響應激活或停用標籤頁。添加一個EventListener visibilitychange ,將消息發送到Native Client模塊,以下例所示:

document.addEventListener('visibilitychange', function(){
  if (document.hidden) {
    // PostMessage to your Native Client module
    document.nacl_module.postMessage('INACTIVE');
  } else {
    // PostMessage to your Native Client module
    document.nacl_module.postMessage('ACTIVE');
  }

}, false);

處理來自Native Client線程的選項卡激活

您還能夠直接從Native Client模塊檢測並響應選項卡的激活或取消激活,方法是在函數中包含代碼,pp::Instance::DidChangeView只要模塊視圖發生更改,就會調用該代碼 。代碼能夠調用ppb::View::IsPageVisible以肯定頁面是否可見。不可見頁面的最多見緣由是頁面位於後臺選項卡中。

提示和最佳實踐

如下是編寫安全代碼並使用Pepper 3D API得到最佳性能的一些建議。

這樣作的

  • 確保啓用attrib 0. OpenGL要求您啓用attrib 0,但OpenGL ES 2.0不啓用。例如,您能夠定義具備2個屬性的頂點着色器,編號以下:

    glBindAttribLocation(program, "positions", 1);
    glBindAttribLocation(program, "normals", 2);

    在這種狀況下,着色器不使用attrib 0,若是Chrome在OpenGL上模擬OpenGL ES 2.0,Chrome可能必須執行一些額外的工做。即便您不使用attrib 0,啓用attrib 0也老是更有效。

  • 檢查着色器如何編譯。着色器能夠在不一樣系統上進行不一樣的編譯,這可能致使glGetAttrib*函數返回不一樣的結果。每次從新編譯着色器時,請確保頂點屬性索引與相應的名稱匹配。
  • 謹慎更新指數。出於安全緣由,必須驗證全部索引。若是更改索引,Native Client將再次驗證它們。構建代碼,以便不常常更新索引。
  • 使用較小的插件,讓CSS縮放它。若是您遇到填充問題,經過CSS執行擴展可能會有所幫助。插件渲染的大小由<embed> 模塊元素的width和height屬性決定。網頁上顯示的實際大小由應用於元素的CSS樣式控制。
  • 避免矩陣到矩陣的轉換。對於某些版本的Mac OS,編譯着色器時存在驅動程序問題。若是您遇到矩陣變換的編譯器錯誤,請避免矩陣到矩陣的轉換。例如,在經過mat4轉換它以前,將vec3轉換爲vec4,而不是將mat4轉換爲mat3。

注意事項

  • 不要使用客戶端緩衝區。OpenGL ES 2.0可使用glVertexAttribPointer和使用客戶端數據glDrawElements,但這確實很慢。儘可能避免客戶端緩衝區。請改用頂點緩衝區對象(VBO)。
  • 不要混合頂點數據和索引數據。默認狀況下,Pepper 3D將緩衝區綁定到單個點。您能夠建立一個緩衝區,並將其綁定到兩個 GL_ARRAY_BUFFERGL_ELEMENT_ARRAY_BUFFER,可是這將是昂貴的開銷,因此不推薦。
  • 在渲染過程當中不要調用glGet *或glCheck *。這是OpenGL程序的常規建議,但對於Chrome上的3D尤其重要。調用名稱以這些字符串開頭的任何OpenGL ES 2.0函數都會阻塞Native Client線程。這包括glGetError; 避免在發佈版本中調用它。
  • 不要使用固定點(GL_FIXED)頂點屬性。OpenGL ES 2.0不支持定點屬性,所以在OpenGL ES 2.0中模擬它們的速度很慢。默認狀況下,GL_FIXED在Pepper 3D API中關閉支持。
  • 不要從GPU讀取數據。不要打電話glReadPixels,由於它很慢。
  • 不要更新大緩衝區的一小部分。在當前的OpenGL ES 2.0實現中,當您更新緩衝區的一部分( glSubBufferData例如)時,必須從新處理整個緩衝區。要避免此問題,請將靜態和動態數據保存在不一樣的緩衝區中。
  • 不要調用glDisable(GL_TEXTURE_2D)。這是一個OpenGL ES 2.0錯誤。每次調用時,Chrome的about:gpu標籤中都會顯示錯誤消息 。

CC-By 3.0許可下提供的內容

相關文章
相關標籤/搜索