原文连接: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许可下提供的内容