编写你的应用程序(三)、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许可下提供的内容

相关文章
相关标签/搜索