WebGL,在探索程序员熟悉和跨平台的泛足中,采用OpenGL ES的API。因此,它从未能够利用底层GPU的全部力量,即使是DirectX 12,金属和vutkan等桌面API,也被宣告并变得流行。此外,Apple对WebGL 2的完全支持速度缓慢,截至本文,它仍然是Safari的实验特征)。
幸运的是,情况似乎在变化。经过W3C联盟的工作组开发超过四年后,WebGPU API希望对稳定性和广泛可用性的尖端。
在各种浏览器和操作系统上已经有几种WebGPU实现,就像WebGL一样。这些实现是以vulkan和金属的其他API写入,我将在下面称为“后端API”。
本文系列的目的是将金属程序员推出到Web上的现代GPU编程。金属和WebGPU之间有足够的相似之处,我们可以直接比较他们各自的API和着色语言。甚至甚至不熟悉金属的读者都可以受益于此概述和随附的样本。
在第一部分(本文)中,我们将在JavaScript中学习WebGPU API的基础,从头开始,在2D中的一个彩色三角形中旋转。第二部分(即将推出),我们将看看3D地形渲染的稍微实际的例子。
如果要查看本页中嵌入的示例,我建议使用像Chrome Canary这样的流放边缘浏览器,因为现有的实现在他们对WebGPU草案标准的支持下显着变化(在本文写作时)。
与本机发展相比,我们使用视图和图层类来显示可绘制的内容,在Web上我们使用< canvas> HTML元素。
要使用WebGPU绘制画布,我们首先需要知道页面何时加载。我们通过注册一个调用称为main()的函数的事件侦听器来执行此操作。反过来调用init()的主要函数,这是所有必要的一次性对象初始化:
主要函数标记为Async,因为它使用AWAIT关键字暂停其在调用异步WebGPU API时执行。
我们使用的第一个API是window.navigator.gpu。 GPU是允许我们要求适配器的对象。适配器是系统提供的WebGPU的实现。可能有多个适配器,但在这种情况下,我们调用RequestAdapter(),没有参数来获取默认适配器。
反过来,适配器允许我们查询实现的可用功能和限制,以及请求设备。在此示例中,我们询问适配器的默认设备,这将适合大多数目的。正如您所预期的,WebGPU的设备与Metal中的MTLDevice非常相似:使用设备,您可以创建资源并向GPU提交工作。
要绘制,我们需要从画布中检索上下文。首先,我们使用QuerySelector函数获取对我们创建的Canvas元素的引用,然后我们要求其GPUpresent上下文,这使我们能够访问与画布相关联的WebGPU资源。
要显示我们在屏幕上绘制的内容,我们需要配置上下文的交换链。摘要,交换链是我们将呈现给的纹理池,并将在屏幕上显示。这类似于我们如何配置MTKView或CametAllayer的设备和像素格式。
通过将设备传递给ConfiguresWapchain函数,我们在GPU和画布之间创建了联动。
如果您尚未使用WebGL或使用Web技术进行动画,我们设置绘制循环的方式可能看起来有点奇数。
JavaScript具有一个名为RequestAnimationFrame的函数,该函数计划下次浏览器决定它准备重新粉刷时要调用的函数。但是,通过函数仅调用一次:如果我们希望重复呈现,我们负责请求当前帧末尾的下一个帧。这导致以下模式:
当然,如果渲染的内容没有改变,无条件请求的绘图可能是效率低。我们在这里仅用于示范目的。
就像金属一样,WebGPU要求我们将GPU命令录制到命令缓冲区中以将它们提交给GPU以进行执行。
与金属对比,WebGPU不要求您在编码命令之前显式创建命令缓冲区。相反,您将直接从设备创建命令编码器,当您已准备好提交批次执行命令时,则可以在编码器上调用Finish()函数。此函数返回可以提交到设备命令队列的命令缓冲区。
要准确了解如何向GPU提交渲染工作,我们首先需要了解渲染传递的解剖学。
每个帧被细分为一个或多个渲染通行证。渲染通行证是由负载阶段,一个或多个绘制呼叫和商店阶段组成的命令序列。
渲染传递的输出写入一个或多个附件。在最简单的情况下,只有一个颜色附件,它存储将显示在屏幕上的渲染图像。其他场景可以使用多种颜色附件,深度附件等。
每个附件都有自己的负载操作,它在通过的开始时确定其内容,并存储操作,该内容确定通过的结果是否存储或丢弃。通过提供&#39的负载值;加载'我们告诉webgpu保留目标的现有内容;通过提供值(例如颜色),我们表示目标应该“清除”到该值。
要告诉WebGPU绘制的纹理,我们创建了渲染传递描述符,这几乎不仅仅是一个附件:
在这里,我们指定有一个颜色附件,我们希望在负载阶段清除特定颜色,并且我们希望在通过的末尾存储结果。要指示纹理绘制,我们会从交换链中获取当前纹理,然后从中创建一个视图。这类似于询问CametAllayer的下一个可绘制。
(视图是纹理上的薄抽象,允许我们引用其MIP级别和数组元素的子集(其所谓的子资源)。在金属中,纹理视图实际上只是另一个纹理,但在WebGPU中他们是独特的类型。)
渲染传递的负载和存储操作独立于传递是否实际包括任何绘制呼叫。我们可以使用它来通过开始渲染通过并立即结束它来将画布清除到纯色颜色:
如果我们正在做真实的工作,我们将使用渲染传递编码器来编码绘图命令。我们很快就会看到一个例子。
如上所述,完成命令编码器产生可以提交给GPU的命令缓冲区。我们通过向设备询问其命令队列并传递一个命令缓冲区来执行此操作,以便延长以进行执行。完成工作后,画布将显示图像。
上述代码的结果嵌入下面。根据您读取此操作以及您使用的浏览器时,您可能会看到错误消息。如果一切正常工作,你应该看到一个坚实的蓝色框。
既然我们知道建立画布和编码渲染工作的基础知识,让我们开始绘制。我们将首先概述WebGPU的所有新的着色语言。
现代着色语言在它们之间具有更多相似之处而不是差异。毕竟,它们的目的是为典型GPU提供的I / O和算术运算提供高级语法。
在句法上,WGSL借用远离生锈。函数定义以fn开头;返回类型出现在参数列表之后,前面箭头( - >);仿制性使用角括号(例如,VEC4< F32>)。标量数字类型具有如F32和U32等命名。
WGSL还与金属阴影语言(MSL)有一些相似之处。属性(例如,[[Location(0)]])使用C ++样式双方括号。从顶点着色器作为结构返回变化,内插片值被用作片段着色器的结构值参数(可能与顶点着色器的输出类型相同,但必须是“兼容的”)。
作为语言的介绍,我们将查看将产生我们的第一个三角形的顶点和片段着色器。
与金属阴影语言一样,我们可以定义包含顶点着色器的输出的结构。我们有义务提供vec4< f32> (一个包含夹子空间顶点位置的四元件浮点向量),其归因于[[内置(位置)]]。在此示例中,我们还返回一个顶点颜色,它将被碎片着色器使用:
顶点着色器本身是一种函数,其归因于[[阶段(顶点)]],类似于MSL的Vertex关键字。顶点函数参数指示它们的值应从位置属性获取。我们很快就会看到如何从这些位置索引创建映射到顶点缓冲区,我们在绘制时绑定。
在此示例中,顶点函数只需构造输出结构的实例,将其与获取的顶点数据填充,并返回它:
片段着色器的作业是根据其输入返回其像素的颜色。在此示例中,输出颜色只是来自光栅化器的内插颜色:
返回类型上的位置属性表示应写入颜色的颜色附件的索引。在金属中,推断出单个矢量返回值以对应于第一颜色附件。在WGSL中,我们有义务明确提供该指数。
这完成了样本的着色器代码。我们将在下一节中看到如何将此着色器代码合并到完整的渲染管道中。
要绘制一些东西,我们需要从3D模型文件生成或加载网格。在此示例中,我们只需绘制一个三角形,因此我们将通过将其硬编码为浮动列表来指定其数据。每个顶点都有一个位置,在x y z w r g b中包含在内存中的颜色:
要在GPU上使用此数据,我们需要将其复制到缓冲区中。与金属一样,我们使用该设备创建缓冲区。 CreateBuffer函数采用缓冲区描述符,包含缓冲区的大小,某些使用标志,以及缓冲区是否应该“映射”以在创建时写入。
如果我们在创建时映射缓冲区,则可以立即将数据复制到它。由于我们将在绘制时将缓冲区用作顶点缓冲区,因此我们通过gpubufferusage.vertex标志。由于我们希望将其用作复制操作的目的地,因此我们还包括gpubufferusage.copy_dst标志。
缓冲区的getMappedRange()函数返回一个ArrayBuffer,可以通过Set()函数将数据复制到它的类型以将数据复制到其中:
然后,我们求展开顶点缓冲区,表示我们完成了更改其内容。新内容被隐式复制到GPU。
现在我们有一些着色器和一些顶点数据,我们已准备好组装我们的渲染管道。这包括几个涉及许多不同描述符类型的步骤,所以我们会一次走一步。
首先,我们需要着色器作为字符串。在此示例中,着色器包含在HTML<脚本&gt中。元素,但它们可以从最终用户输入的外部文件中源自键入,或者作为JavaScript字符串文字。
我们使用设备的CreateShadermodule()函数将着色器串转换为着色器模块,从中可以获得着色器功能。不同的实现将在幕后进行不同的东西,但是金属实现可能在此时创建一个mtllllarary,因为WebGPU着色器模块和金属着色器库之间存在自然并行。
WebGPU中没有任何类似的MTLF功能。相反,函数由其模块及其名称唯一标识。
与金属一样,我们需要描述顶部的顶点数据的布局,以便它可以合并到渲染管道中。该描述称为顶点状态,包括一系列布局,每个布局包含一个或多个属性。这直接对应于顶点描述符的金属概念,其差异是由布局包含的属性而不是生活在单独的容器中。
每个布局都有一个步幅,它指示顶点之间的字节数和步进模式,其指示是否应该获取数据每个顶点或每个实例2。
属性阵列的每个成员描述了一个顶点属性,包括从顶点数据的开始,格式和着色器位置的偏移量。此着色器位置是用于从顶点缓冲区映射到与位置(n)归属的顶点着色器参数映射的内容。
镜像我们的顶点数据的布局,我们定义了两个顶点属性(位置和颜色),均以格式' float32x4',其中映射到WGSL vec4< f32>类型。
将所有这些都放在一起,这里是此示例的完整顶点布局列表:
渲染管道包含配置GPU的可编程和固定功能部分所需的大多数数据以执行呈现命令。因此,渲染管线描述符是我们看到的最复杂的描述符并不奇怪。
每个管道阶段(顶点和片段)具有相关的入口点和状态。在顶点阶段的情况下,状态是先前定义的顶点缓冲区布局列表。在片段阶段的情况下,该状态是彩色目标状态阵列,其描述了将由管道定位的渲染传递附件的格式。每个颜色目标状态也具有可选的混合状态,可控制固定功能α混合。我们不使用此示例中的Alpha混合,因此我们省略了该部分的州。
包含在渲染管线中的最终状态是原始状态。在最基本的基本上,这只是一个指示我们将要绘制哪种类型的原始。在METAL中,我们在编码绘制呼叫时指定此项,但由于某些API需要它,我们将其作为管道配置的一部分提供。
这里将所有这些都在一起,这里是我们的样本渲染管道描述符的完整定义:
请注意,入口点的名称匹配我们在Shader源中写入的函数的名称。我们使用的原始拓扑'三角列表',因为我们将绘制三角形列表(真的只是一个三角形)。
现在我们已经通过上述所有概念进行了衰减,实际上创建了一个管道很简单:
要对呈现工作进行编码,我们创建命令编码器并按照以前开始渲染传递:
配备管道和顶点缓冲区,我们绑定了它们中的每一个并发布我们的第一个绘图呼叫:
draw()函数有许多参数,大多数选项可选,但第一个 - 顶点计数 - 是最重要的。在这种情况下,因为我们绘制一个三角形,它是3。
下面的结果是嵌入的。在具有最新WebGPU实现的浏览器中,它应该显示单个彩色三角形。
尽管结果谦虚地,我们已经涵盖了很多概念地:
在第2部分中,我们将查看中间主题,包括纹理加载,位移映射和instancing。
无论好坏,WebGPU都是由多家大公司的联盟设计的开放式标准。在不参考企业政治的情况下,很明显,WGSL是这些实体中妥协的产物。尽管与介绍另一种着色语言相关的不便,但具有设计用于安全性的文本着色语言,并且易于转换为后端阴影语言看起来像我们希望的最佳结果。 ↩这是曲面细分的自然扩展点。如果WebGPU的曲面细分模型看起来像金属,则除了每次顶点数据外,顶点获取可用于获取每个补丁数据。 ↩