编写你的应用程序(三)、3D图形
原文鏈接:https://developer.chrome.com/native-client/devguide/coding/3D-graphics
注意:已針對ChromeOS以外的平臺公布了此處所述技術的棄用。
請訪問我們的?遷移指南?了解詳情。
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編程指南。
驗證客戶端圖形平臺
Native Client是一種軟件技術,它允許您對應用程序進行一次編碼并在多個平臺上運行,而無需擔心每個可能的目標平臺上的實現細節。在硬件級別提供相同的支持很困難。圖形硬件來自許多不同的制造商,并由不同質量的驅動程序控制。特定的GPU驅動程序可能不支持每個OpenGL ES 2.0功能,并且已知某些驅動程序具有可被利用的漏洞。
即使GPU驅動程序可以安全使用,您的程序也應該在啟動應用程序之前執行驗證檢查,以確保驅動程序支持您需要的所有功能。
用JavaScript審核驅動程序
在啟動時,應用程序應執行一些可在其托管網頁上以JavaScript實現的其他測試。執行這些測試的腳本應該包含在模塊的embed標記之前,理想情況下,embed只有在這些測試成功時,標記才會出現在托管頁面上。
首先要檢查的是你是否可以創建圖形上下文。如果可以,請使用上下文確認是否存在任何所需的OpenGL ES 2.0擴展。在檢查擴展時,您可能需要引用擴展注冊表并包含供應商前綴。
在Native Client中審核驅動程序
創建一個上下文
一旦您通過了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_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的值。
- 如果在單個著色器中使用的紋理超過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確定用戶的驅動程序不足,則應用程序將不會顯示在商店的磁貼顯示中。但是,它會出現在商店搜索結果中,或者如果用戶直接鏈接到它,在這種情況下,用戶仍然可以下載它。但是當用戶到達安裝頁面時將檢查清單要求,如果出現問題,瀏覽器將顯示消息“此計算機不支持此應用程序。安裝已被禁用?!?/p>
基于清單的檢查僅適用于直接從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::DidChangeView以InitGL在必要時調用:在應用程序啟動時(當圖形上下文為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線程中進行的。
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處理。程序員經常嘗試通過在渲染代碼中使用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 moduledocument.nacl_module.postMessage('INACTIVE');} else {// PostMessage to your Native Client moduledocument.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_BUFFER和GL_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許可下提供的內容
總結
以上是生活随笔為你收集整理的编写你的应用程序(三)、3D图形的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国白领最苦累的六大城市
- 下一篇: 如何把照片转换成jpg格式呢?