原文連接:https://developer.chrome.com/native-client/devguide/coding/3D-graphicsjavascript
注意:已針對ChromeOS之外的平臺公佈了此處所述技術的棄用。
請訪問咱們的 遷移指南 瞭解詳情。java
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實現的其餘測試。執行這些測試的腳本應該包含在模塊的embed
標記以前,理想狀況下,embed
只有在這些測試成功時,標記纔會出如今託管頁面上。json
首先要檢查的是你是否能夠建立圖形上下文。若是能夠,請使用上下文確認是否存在任何所需的OpenGL ES 2.0擴展。在檢查擴展時,您可能須要引用擴展註冊表幷包含供應商前綴。canvas
建立一個上下文api
一旦您經過了JavaScript驗證測試,就能夠安全地將Native Client embed標記添加到託管網頁並加載模塊。做爲模塊初始化代碼的一部分,您必須經過建立C ++ Graphics3D
對象或調用PPB_Graphics3D
API函數爲應用程序建立圖形上下文Create
。不要覺得這總會成功; 你仍然可能在建立上下文時遇到問題。若是您處於開發模式且沒法建立上下文,請嘗試建立更簡單的版本,以查看是否要求不支持的功能或超出驅動程序資源限制。您的生產代碼應始終檢查上下文是否已建立,若是不是這樣,則應正常失敗。瀏覽器
檢查擴展和功能
並不是每一個GPU都支持每一個擴展或具備相同數量的紋理單元,頂點屬性等。在啓動時,調用glGetString(GL_EXTENSIONS)
並檢查擴展和所需的功能。例如:
GL_OES_texture_npot
存在。GL_OES_texture_float
存在。EXT_texture_compression_dxt1
,GL_CHROMIUM_texture_compression_dxt3
以及 GL_CHROMIUM_texture_compression_dxt5
存在的。glDrawArraysInstancedANGLE
, glDrawElementsInstancedANGLE
,glVertexAttribDivisorANGLE
,或PPAPI接口PPB_OpenGLES2InstancedArrays
,確保相應的擴展GL_ANGLE_instanced_arrays
存在。glRenderbufferStorageMultisampleEXT
或PPAPI接口PPB_OpenGLES2FramebufferMultisample
,請確保GL_CHROMIUM_framebuffer_multisample
存在相應的擴展名。glGenQueriesEXT
,glDeleteQueriesEXT
, glIsQueryEXT
,glBeginQueryEXT
,glEndQueryEXT
,glGetQueryivEXT
, glGetQueryObjectuivEXT
,或PPAPI接口PPB_OpenGLES2Query
,確保相應的擴展GL_EXT_occlusion_query_boolean
存在。glMapBufferSubDataCHROMIUM
, glUnmapBufferSubDataCHROMIUM
,glMapTexSubImage2DCHROMIUM
, glUnmapTexSubImage2DCHROMIUM
,或PPAPI接口PPB_OpenGLES2ChromiumMapSub
,確保相應的擴展 GL_CHROMIUM_map_sub
存在。檢查系統功能glGetIntegerv
並相應地調整着色器程序以及紋理和頂點數據:
glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, ...)
並glGetIntegerv(GL_MAX_TEXTURE_SIZE, ...)
返回大於0的值。glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...)
返回的值大於或等於所需的同時紋理數。若是您選擇將應用程序放在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
在Native Client中編寫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::DidChangeView
以InitGL
在必要時調用:在應用程序啓動時(當圖形上下文爲NULL時)以及模塊的View更改大小時。若是您正在移植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 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是一款多進程瀏覽器。每一個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進程之間的交互。特定於應用程序的呈現代碼在Draw
Native Client線程上調用的函數中運行。藍色向下箭頭阻止從主線程到Native Client的調用,綠色向上箭頭是SwapBuffers
從Native Client到主線程的非阻塞 調用。全部OpenGL ES 2.0調用都是Draw
在Native Client線程中進行的。
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命令沒法在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處理。程序員常常嘗試經過在渲染代碼中使用glFlush
和glFinish
命令來確保這一點 。在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中,非活動選項卡將繼續執行定時功能(例如 setInterval
和setTimeout
),但定時器間隔將自動覆蓋,而且在選項卡處於非活動狀態時限制爲很多於一秒。此外,SwapBuffers
在選項卡再次處於活動狀態以前,不會發送與呼叫關聯的任何回叫。除了SwapBuffers
選項卡處於非活動狀態以外,您可能會從函數接收異步回調。根據應用程序的設計,您能夠選擇在它們到達時處理它們,或者將它們排入緩衝區並在選項卡變爲活動狀態時對它們進行處理。
標籤處於非活動狀態時通過的時間可能至關大。若是主線程脈衝基於SwapBuffers
回調,則當選項卡處於非活動狀態時,您的應用將不會更新。Native Client模塊應該可以檢測並響應其運行的選項卡的狀態。例如,當選項卡變爲非活動狀態時,您能夠在Native Client線程中設置一個原子標誌,該標誌將跳過3D渲染並SwapBuffers
調用並繼續每隔30毫秒左右調用主線程。這提供了時間來更新仍應在後臺運行的功能,如音頻。調用sched_yield
或usleep
在任何工做線程上釋放資源並將循環中止到操做系統也可能會有所幫助 。
您能夠在託管頁面上使用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模塊檢測並響應選項卡的激活或取消激活,方法是在函數中包含代碼,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*
函數返回不一樣的結果。每次從新編譯着色器時,請確保頂點屬性索引與相應的名稱匹配。<embed>
模塊元素的width和height屬性決定。網頁上顯示的實際大小由應用於元素的CSS樣式控制。glVertexAttribPointer
和使用客戶端數據glDrawElements
,但這確實很慢。儘可能避免客戶端緩衝區。請改用頂點緩衝區對象(VBO)。GL_ARRAY_BUFFER
和GL_ELEMENT_ARRAY_BUFFER
,可是這將是昂貴的開銷,因此不推薦。glGetError
; 避免在發佈版本中調用它。GL_FIXED
在Pepper 3D API中關閉支持。glReadPixels
,由於它很慢。glSubBufferData
例如)時,必須從新處理整個緩衝區。要避免此問題,請將靜態和動態數據保存在不一樣的緩衝區中。about:gpu
標籤中都會顯示錯誤消息 。CC-By 3.0許可下提供的內容