1。什么是WebGPU1.1?OpenGL于30年前的1992年由KhronosGroup推出。OpenGLES由KhronosGroup于2003年设计,用于移动电话、PDA和游戏机等嵌入式设备。OpenGLES2.0诞生于2007年3月,3.0版本诞生于2012年8月,3.1版本于2014年3月,最后一个正式版3.2是2015年8月,之后会以扩展的形式加入新功能。与之相对应的是,OpenGL4.6版本于2017年7月发布。2009年,Khronos成立了WebGL工作组,成员包括Apple、Google、Mozilla、Opera等。2011年,WebGL1.0版本正式推出,基于OpenGL发布。ES版本2.0。2013年,WebGL工作组开始定制WebGL2.0规范,但直到2017年2月,2.0标准才正式发布并得到Google/Mozilla的支持。WebGL2.0基于OpenGLES3.0版。之后,OpenGLES3.1的一些特性被引入到WebGL2.0版本中,作为扩展被各个浏览器实现。2021年9月,即标准发布四年半后,Apple正式宣布支持WebGL2.0。苹果前掌门人史蒂夫·乔布斯曾力挺OpenGLES,认为开放才是未来,对Flash嗤之以鼻。谁知老爷子走后,苹果采用了自研图形框架Metal,从开环到闭环。说到Metal,当代有一个三足图形框架,就是苹果的“Metal”、Khronos的“Vulkan”(没错,新开了一个账号)、Windows的“DirectX12”,这完全释放了GPU。编程能力**。**也就是说,这几年计算机图形学发生了翻天覆地的变化,OpenGL的思想越来越跟不上时代了。另外根据壳大佬在GMTC上的分享,在Chrome上运行的WebGL并没有使用OpenGL引擎,而是通过Angle(https://github.com/google/angle)转换成本地图形编程接口)库,比如Windows转换为DirectX,Apple转换为Metal来绘制。然而,OpenGL还没有完全过时。虽然3A级游戏大作不太可能继续用OpenGL构建,但OpenGL仍然是简单场景、嵌入式图形、科研行业最舒服的选择。1.2WebGPUPKWebGLNext2016年6月,谷歌萌生了用新的API代替WebGL的想法,叫做WebGLNext。2017年1月,KhronosGroup举办了WebGLNext研讨会。Chromium一马当先,展示了基于OpenGL和Metal的可独立运行的新型图形系统原型。同时,Apple和Mozilla也展示了自己的原型,都与MetalAPI非常相似。次月,Apple向W3C提交了一个名为WebGPU的技术概念验证解决方案,基于Metal图形开放接口,最终W3C采用WebGPU作为下一代标准的名称,Apple的提案进入正式组提案。3月,Mozilla向KhronosGroup提交了一项名为WebGLNext的基于Vulkan的提案。2018年6月,Chrome团队宣布实现WebGPU,这意味着Khronos失败了,WebGPU赢了,未来大家将团结在W3C周围。正如预期的那样,工作组希望在2021年底发布WebGPU1.0标准,但目前只有草案。WebGPU1.0草案:https://www.w3.org/standards/types#WD1.3WebGPU的特点1.直接对标Vulkan、Metal、Direct3D等高性能本地图形标准库12.这意味着WebGPU将是高性能GPU的桥接层。只要遵循这个标准,就可以实现使用GPU的工具库。它的着色器是一组符合VulkanSPIR-V的二进制规范。只要是遵循这个规范的产品,加上支持GPU的runtime,这就有相当大的潜力。例如,WebAssembly最初被设计为一种浏览器可执行的二进制格式,但后来在服务器端获得了更广泛的应用,并具有取代Docker的潜力。2.支持GPUComputeShader,支持GPU通用计算,也就是说GPU可以用来在浏览器端运行计算任务,不仅可以用来绘制图形,还可以利用GPU的并行计算能力做更多的算法,比如大数排序、机器学习等任务可能会在浏览器端实现。3、自定义着色器语言WGSLWGSL(WebGPUShadingLanguage)是一门全新的语言。在设计这门语言时,WebGPU大量参考了VulkanSPIR-V。因为版权和利润分配等问题,最终还是决定创造一种新的语言。一种混合了Rust、TypeScript和Metal的编程语言。以前用过WebGL的同学应该都知道shaders是用GLSL写的。目前WGSL还没有最终定稿,学习成本也比GLSL高。4、更好的架构设计WebGPU摆脱了状态机机制,增加了Pipeline、Renderpass、CommandEncoder等对象。WebGPU对应的JavaScript对象实际上是对GPU的内部对象进行操作。WebGPU的所有方法都是Promise,异步代码会交给GPU去实现,外层不需要关心。更好的TypeScript打字支持。5.更好的性能是最重要的。我们来看一下benchmark,这是在保持60fps的情况下最多可以画出的三角形。可见显卡的潜力已经释放。还有babylon的例子(搬自知乎)。这个场景有1000多棵未实例化的树,每棵树都有一个drawcall。使用WebGL,CPU成为一个巨大的瓶颈,每帧耗时81ms,而使用WebGPU,CPU一帧只需要花费0.18ms。减少CPU时间消耗意味着给GPU留出更多的运行时间,这是WebGPU的强大之处。1.4体验WebGPU目前Chrome正式版不支持WebGPU。我们需要下载canary版本:https://www.google.com/chrome/canary/然后进入chrome://flags/,找到#enable-unsafe-webgpu并打开目前主流的web库如三个。js和babylon已经支持WebGPU,可以查看Demo:ThreeJS:https://threejs.org/examples/?q=webgpu#webgpu_computeBabylonJS:https://playground.babylonjs.com/在右上角选择webgpu学习实例:https://austin-eng.com/webgpu-samples/samples/helloTriangle文章合集:https://github.com/mikbry/awesome-webgpu2.手写一个WebGPU程序WebGPU还不稳定,所以我们不需要在学习上花费太多的精力。我们将基于webgpu-samples做一些简单的学习。源码参考:https://github.com/austinEng/webgpu-samples/2.1初始化需要比WebGL绘图至少多10次API调用,WebGPU使用的刻板印象少得多。首先创建一个适配器constadapter=awaitnavigator.gpu.requestAdapter(option);注意,如果浏览器不支持WebGPU,则gpu对象未定义,需要异常处理。这里的适配器是指显示适配器,也就是俗称的显卡。每个适配器都标记一个硬件加速器(例如GPU或CPU)的实例,浏览器在硬件加速器之上实现WebGPU。该方法接受一个option,目前是这样的:powerPreference:'low-power'|'high-performance'powerPreference表示使用哪种类型的耗电显卡,low-power一般是自带的集成显卡它,性能较差,但更省电,高性能意味着使用更高性能的独立显卡。WebGPU推荐开发者尽可能使用低功耗的GPU,除非绝对需要使用专用显卡。接下来,我们获取具体的设备constdevice=awaitadapter.requestDevice();这个设备是一个实例化的对象,同一个适配器可以共享设备实例,设备可以创建缓存、纹理、渲染管线、着色器模块等等。创建一个WebGPU画布上下文实例constcontext=canvas.getContext('webgpu');然后我们需要得到canvas可以绘制的最精细的像素constsize=[canvas.clientWidth*devicePixelRatio,canvas.clientHeight*devicePixelRatio]然后我们需要声明图像颜色格式,比如brga8unorm,它使用8位无符号整数和rgba表示颜色,也可以直接从adapter中获取constformat=context.getPreferredFormat(adapter);将参数配置写入contextcontext.configure({device,format,size,usage:GPUTextureUsage.RENDER_ATTACHMENT|GPUTextureUsage.COPY_SRC})在WebGL中,我们有一个默认的帧缓冲区(DefaultFrameBuffer),如果我们不做任何其他操作,那么当我们执行绘图命令(drawcall)时,所有绘制的内容都会被填充到默认的帧缓冲区中,显卡会直接将这个默认的帧缓冲区提交给显示器,并显示在显示器上。这会造成两个问题:如果渲染太慢,显示器会取未完成的图像,如果渲染太快,GPU会等待显示器取图像,造成性能浪费。参考:https://gavinkg.github.io/ILearnVulkanFromScratch-CN/mdroot/%E6%A6%82%E5%BF%B5%E6%B1%87%E6%80%BB/%E4%BA%A4%E6%8D%A2%E9%93%BE.html第一个问题的解决方法是应用双缓冲技术,即用一个缓冲区来缓存上次渲染的内容,这和ReactFiber的双缓冲很相似.看来技术是相通的。解决第二个问题,可以继续应用三重缓冲,充分压榨显卡的性能。这个configure的作用主要是关联context和device实例。内部会实现一个buffer(因为需要和显示器交互),size是绘制图片的大小,usage是图片的用途。一般是固定搭配,说明需要对外输出。图像。2.2CommandEncoder创建命令编码器CommandEncoderconstcmdEncoder=device.createCommandEncoder();commandencoder,它的作用是把你需要让GPU执行的命令写入GPU的命令缓冲区(CommandBuffer),比如我们要在render中输入顶点数据,设置背景颜色,drawcalls等经过。创建渲染通道RenderPassconstrenderPassDescriptor={colorAttachments:[{view:context.getCurrentTexture().createView(),loadValue:{r:0.0,g:0.0,b:0.0,a:1.0},storeOp:'store',},],};colorAttachments是必填字段,用于存储(或暂存)图像信息,我们通常只将渲染pass的结果保存为一个副本,即只渲染到一个目标,但在一些高级渲染中在技??术中,我们需要将渲染结果存储成多份,即渲染到多个目标,所以类型是数组。以下视图指示存储当前通道渲染的图像数据的位置。我们指定使用上下文来创建一个二进制数组来表示它。loadValue可以理解为背景色,storeOp表示存储时的操作。您可以选择'store'存储或'clear'清除数据,默认使用store。还有一个可选的字段depthStencilAttachment附加到当前渲染通道,用于存储渲染通道的深度信息和模板信息。由于我们只绘制二维图形,所以不需要处理深度、遮挡和混合。让指令编码器开启渲染管线constrenderPassEncoder=cmdEncoder.beginRenderPass(renderPassDescriptor);这里把cmd和renderpass关联起来,就可以运行pipeline了。2.3渲染管线创建渲染管线(pipeline)是最复杂的一步,这里要应用我们的shader程序。着色器分为“顶点着色器”和“片段着色器”。对于不懂的同学,可以简单的解释一下**。**顶点着色器计算传入图形的顶点。比如我们要画一个三角形,需要通过shader代码计算出三角形的三个顶点。片元着色器就是给顶点计算出来的表面着色。例如,如果我们想画一个红色三角形,片段着色器应该输出红色。shader是怎么写的我们不用去了解,下面会做一些解释,先看JSAPI。最简单的场景,我们只需要配置如下constpipeline=device.createRenderPipeline({vertex:{module:device.createShaderModule({code:triangleVertWGSL,//vertexshadercode}),entryPoint:'main',//entryfunction},fragment:{module:device.createShaderModule({code:redFragWGSL,//片段着色器代码}),entryPoint:'main',//入口函数targets:[{format:format,//就是上面的finalrenderingcolorformat},],},primitive:{//绘制模式topology:'triangle-list',//按照三角形绘制},});shader部分后面会讲解,绘制方式支持绘制为Points、lines、repeatedconnections、triangles、repeatedtriangles,大多数情况下我们只用triangle-list。将管道与passencoderrenderPassEncoder.setPipeline(pipeline)关联起来;开始绘制renderPassEncoder.draw(3,1,0,0);这里四个参数解释如下:第一:需要绘制的顶点个数,三角形当然是3个顶点第二:需要绘制几个实例,我们只绘制一个第三:起始顶点位置第四:绘制的前几个实例宣告绘图结束renderPassEncoder.endPass();这行代码表示当前渲染通道已经结束,不再向GPU发送命令。结束命令编码器并提交数据device.queue.submit([commandEncoder.finish()])这行代码结束当前命令编码器,将所有命令提交到GPU设备的默认队列中。结束了,如果一切顺利的话,我们最后画一个三角形怎么样,是不是很简单呢?当然,仅仅画一个三角形就费了那么大的劲,但主要还是要理解WebGPU的设计理念,举一反三。相比较而言,WebGL的绘图要比它复杂一点。3.ShaderWGSL介绍完整的语法说明可以参考官方文档:https://gpuweb.github.io/gpuweb/wgsl这里只对上面的例子做一个简单的解释3.1Vertexshader我们来看看代码[[stage(vertex)]]fnmain([[builtin(vertex_index)]]VertexIndex:u32)->[[builtin(position)]]vec4
