区块链技术博客
www.b2bchain.cn

WebGPU 规范篇 08 管线求职学习资料

本文介绍了WebGPU 规范篇 08 管线求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

系列博客目录传送门:https://xiaozhuanlan.com/topic/9587342601

  • 管线
  • 1 基础管线
    • 1.1 基础管线的 getBindGroupLayout 方法
    • 1.2 默认管线布局
    • 1.3 可编程阶段:GPUProgrammableStage
    • 1.4 补充资料:管线和着色阶段
    • 1.5 如何验证可编程阶段对象的合规性
    • 1.6 如何验证某个着色器中被绑定的变量的合规性
  • 2 渲染管线
    • 2.1 管线的创建
      • 2.1.1 异步创建管线
      • 2.1.2 如何验证 GPURenderPipelineDescriptor 合规性
    • 2.2 图元拼装阶段
      • 2.2.1 顶点索引格式 GPUIndexFormat
      • 2.2.2 如何验证图元拼装阶段对象合规性
      • 2.2.3 代码举例
    • 2.3 顶点着色阶段
      • 2.3.1 顶点缓存(VertexBuffer)与顶点属性(GPUVertexAttribute)


https://www.w3.org/TR/webgpu/#pipelines

GPUPipelineBase ├ GPURenderPipeline └ GPUComputePipeline

管线

管线代表某种计算的过程,在 WebGPU 中,有渲染管线和计算管线两种。

这一过程需要用到绑定组、VBO、着色器等对象或资源,然后最终能输出一些内容,譬如渲染管线输出颜色值(以颜色附件形式),计算管线输出到其指定的地方,此处就不列举太详细了。

管线在结构上看,由一系列 可编程阶段 和一些固定的状态组合而成。

注意,根据操作系统、显卡驱动不同,有部分固定的状态会编译到着色器代码中,因此将他们组合成一个管线对象里是不错的选择。

两种管线对象均可由设备对象创建。

在对应的通道编码器中,可以切换管线以进行不同的计算过程。

1 基础管线

dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase {     GPUPipelineLayout layout; };  interface mixin GPUPipelineBase {     GPUBindGroupLayout getBindGroupLayout(unsigned long index); };

两种管线在创建时均需要参数对象,参数对象各自有不同的具体类型,但均继承自这里的 GPUPipelineDescriptorBase 类型。

两种管线也均继承自基础管线类型 GPUPipelineBase

1.1 基础管线的 getBindGroupLayout 方法

每个管线对象均有此方法,它接受一个 unsigned long 类型的数字作为参数,返回一个管线布局对象中对应位置的绑定组布局对象。

译者注:若创建管线时没有传递布局对象,这个方法会根据着色器代码内的 group 特性自动构造出绑定组布局对象。

有一个需要注意的,那就是这个数字参数要小于设备限制列表中的 maxBindGroups 值。

1.2 默认管线布局

如果创建一个管线时,没有设置管线布局对象,那么会自动在内部创建一个默认的布局对象。这个过程在文档中尚未详尽解释,只说了是一种“反射”技术,然后一步一步构造出来。

具体过程要参考文档中 WebGPU Spec 10.1.1 默认管线布局 的创建步骤。

默认的管线布局对象,其 bindGroupLayouts 数组是空数组。

1.3 可编程阶段:GPUProgrammableStage

在创建管线对象时,需要用到参数对象,这个参数对象有不同可编程阶段可以设置,其中每一个阶段都是一个 GPUProgrammableStage 对象。这一点在下文管线创建部分会详细列举出来。

GPUProgrammableStage 类型的对象组织起了管线中具体的一个阶段用到什么 GPUShaderModule,其 WGSL 入口点函数名是什么,需要传递哪些常量值这些信息。

会有详细介绍。

dictionary GPUProgrammableStage {   required GPUShaderModule module;   required USVString entryPoint;   record<USVString, GPUPipelineConstantValue> constants; };  typedef double GPUPipelineConstantValue;

每一个这样的对象,有两个必选参数:

  • module,GPUShaderModule 类型的变量,着色器模块,参考着色器模块章节

  • entryPoint,字符串,指定 module 着色器代码中的入口函数

还有一个可选参数 constants,是一个值是 GPUPipelineConstantValue 类型的 JavaScript 对象,它用来传递着色器代码中具有 override 特性的常量值。

这个对象的键可以是 WGSL 中特性 override(i) 括号里的 i,也可以是常量名。例如(以顶点阶段为例):

const pipeline = device.createRenderPipeline({   vertex: {     /* ... */     constants: {       1300: 2.0, // 将传递给着色器代码中第 1300 个,也即 gain 常量       depth: -1, // 将传递给着色器代码中的 depth 常量     }   } })

对应着色器代码:

[[override(1300)]] let gain: f32; // 将收到 2.0f [[override]] let depth: f32; // 将收到 -1 [[override(0)]] let has_point_light: bool = true; [[override(1200)]] let specular_param: f32 = 2.3; [[override]] let width: f32 = 0.0;  // 其他代码

如果你想把这几个 WGSL 常量中带默认值的也一并覆盖掉,可以传递这样的对象:

const pipeline = device.createRenderPipeline({   vertex: {     /* ... */     constants: {       1300: 2.0, // 将传递给着色器代码中第 1300 个,也即 gain 常量       depth: -1, // 将传递给着色器代码中的 depth 常量       0: false,       1200: 3.0,       width: 20,     }   } })

JavaScript 的数值类型转到 WGSL 时,会自动根据常量在 WGSL 中具体的类型(bool,i32,u32,f32)进行转换。

1.4 补充资料:管线和着色阶段

WebGPU 通过发出绘制命令或调度命令的方式向 GPU 下达指令。

管线在 GPU 执行的计算的行为,被描述为一系列的阶段,其中有一些是可编程的。在 WebGPU 中,执行绘制或调度之前需要创建管线,执行绘制的叫渲染管线,执行调度的叫计算管线。

1.5 如何验证可编程阶段对象的合规性

创建管线时,对某个阶段对象(设为 stage)及管线布局对象(设为 layout)是有要求的:

  • stage.module 必须是一个有效的 GPUShaderModule 对象
  • stage.module 的着色器代码中必须有一个入口函数,名字与 stage.entryPoint 要一致
  • 对入口函数中用到的每个被绑定的变量,验证绑定
  • 对入口函数中用到的每个采样纹理,设纹理为 texture,设采样器为 sampler,sampler.type 如果是 “filtering”,那么 texture.sampleType 不能是 “unfilterable-float”
  • 对于 stage.constants 中的所有常量,在着色器中必须有对应的 override 常量;如果着色器中的常量没有使用初始化语法给定默认值,那么 stage.constants 中必须给定值。

1.6 如何验证某个着色器中被绑定的变量的合规性

和标题意图一样,这一小节可以指导着色器中被绑定变量的语法书写、组织。

对于着色器中某个被绑定的变量,记作 variable,设其 groupbind 的数字分别是 bindGroupIdbindId,另外设管线布局对象为 pipelineLayout,绑定组布局可由 pipelineLayout.bindGroupLayouts[bindGroupId] 得到并记作 bindGroupLayout

  • bindGroupLayout 必须有一个 binding 值与 bindId 一样的 entry 对象;

  • 遍历 bindGroupLayout 中的 entries,设被遍历到的 GPUBindGroupLayoutEntry 变量为 entry:

    • 如果是 buffer 类型,且 entry.buffer.type 是

    • “uniform”,那么着色器代码中此 variable 使用 var<uniform> 声明

    • “storage”,那么着色器代码中此 variable 使用 var<storage, read_write> 声明

    • “read-only-storage”,那么着色器代码中此 variable 使用 var<storage, read> 声明

    • 如果是 buffer 类型且 entry.buffer.minBindingSize 不是0:

    • 如果着色器代码中某个结构体的最后一个字段是无界数组,那么 entry.buffer.minBindingSize 必须大于等于数组的偏移量与步幅的和

    • 如果着色器代码中某个结构体最后一个字段并不是无界数组,那么 entry.buffer.minBindingSize 必须大于等于结构体的大小

    • 如果是 sampler 类型,且 entry.sampler.type 是

    • “filtering”,那么 variable 使用 sampler 类型

    • “comparison”,那么 variable 使用 comparison_sampler 类型

    • 如果是 texture 类型,当且仅当 entry.texture.multisampled 是 true 时,variable 要使用 texture_multisampled_2d<T>texture_depth_multisampled_2d<T> 类型;

    • 如果是 texture 类型,当 entry.texture.type 是

    • “float”,”unfilterable-float”,”sint”,”uint” 时,variable 要使用 texture_1d<T>, texture_2d<T>, texture_2d_array<T>, texture_cube<T>, texture_cube_array<T>, texture_3d<T>, 或 texture_multisampled_2d<T> 类型来声明;关于泛型参数 T,entry.texture.sampleType 是 “float”、”unfilterable-float” 时,T 是 f32,是 “sint” 时,T 是 i32,是 “uint” 时,T 是 u32;

    • “depth”,variable 要使用 texture_depth_2d, texture_depth_2d_array, texture_depth_cube, texture_depth_cube_arraytexture_depth_multisampled_2d 类型来声明

    • 如果是 texture 类型,当 entry.texture.viewDimension 是

    • “1d”,variable 要使用 texture_1d<T> 声明;

    • “2d”,variable 要使用 texture_2d<T>texture_multisampled_2d<T> 声明;

    • “2d-array”,variable 要使用 texture_2d_array<T> 声明;

    • “cube”,variable 要使用 texture_cube<T> 声明;

    • “cube-array”,variable 要使用 texture_cube_array<T> 声明;

    • “3d”,variable 要使用 texture_3d<T> 声明;

    • 如果是 storageTexture 类型,当 entry.storageTexture.viewDimension 是

    • “1d”,variable 要使用 texture_storage_1d<T, A> 类型来声明;

    • “2d”,variable 要使用 texture_storage_2d<T, A> 类型来声明;

    • “2d-array”,variable 要使用 texture_storage_2d_array<T, A> 类型来声明;

    • “3d”,variable 要使用 texture_storage_3d<T, A> 类型来声明;

      entry.storageTexture.access 是 “write-only” 时,上述泛型中的 A 要使用 write,泛型类型 T 要跟 entry.storageTexture.format 一样。

2 渲染管线

渲染管线 GPURenderPipeline 是一种控制着顶点着色和片元着色阶段的管线。

它可以被渲染通道编码器 GPURenderPassEncoder 和 渲染打包编码器 GPURenderBundleEncoder 使用。

渲染管线的输入有:

  • 通过 GPUPipelineLayout 提供的绑定组所绑定的资源
  • 顶点和索引数据,由下面 顶点着色阶段 使用
  • 颜色附件,由颜色目标状态描述
  • 一个可选的深度模板附件,由深度模板状态描述

渲染管线的输出有:

  • 绑定组中类型为 "storage" 的 GPUBuffer 资源
  • 绑定组中 access 属性为 "write-only" 的 GPUStorageTexture 资源
  • 颜色附件
  • 可选的深度模板附件

渲染管线有如下几个 渲染阶段

  • 顶点提取(Vertex Fetch),由 GPUVertexState 对象的 buffers 属性来描述、控制
  • 顶点着色(Vertex Shader),由 GPUVertexState 对象描述、控制
  • 图元装配(Primitive Assembly),由 GPUPrimitiveState 对象描述、控制
  • 光栅化(Rasterization),由 GPUPrimitiveStateGPUDepthStencilStateGPUMultisampleState 对象来描述、控制
  • 片元着色(Fragment Shader),由 GPUFragmentState 对象来控制
  • 依次模板测试及操作(StencilTest)和深度测试并写入(DepthTest),由 GPUDepthStencilState 对象描述、控制
  • 融合输出,由 GPUFragmentState 对象的 targets 属性来描述、控制

上述的 GPUVertexStateGPUPrimitiveStateGPUFragmentStateGPUDepthStencilStateGPUMultisampleState 类型在下文的 顶点着色阶段、图元拼装阶段、片元着色阶段、深度模板测试阶段、多重采样阶段 几个小节均有介绍,对应着接下来 渲染管线创建 小节中介绍的 GPURenderPipelineDescriptor 对象中的 vertexprimitivefragmentdepthStencilfragment 字段。

这 5 个阶段中,只有顶点着色阶段和片元着色阶段是 可编程阶段。

渲染管线的 WebIDL 定义如下:

[Exposed=(Window, DedicatedWorker), SecureContext] interface GPURenderPipeline { }; GPURenderPipeline includes GPUObjectBase; GPURenderPipeline includes GPUPipelineBase;

2.1 管线的创建

渲染管线是通过设备对象的 createRenderPipeline 方法来创建的。

创建渲染管线需要一个 GPURenderPipelineDescriptor 类型的对象作为参数。

dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase {   required GPUVertexState vertex;   GPUPrimitiveState primitive = {};   GPUDepthStencilState depthStencil;   GPUMultisampleState multisample = {};   GPUFragmentState fragment; };

在此对象中,有若干个阶段状态,在此简单描述每个字段的用途,后面小节会详细介绍它们对应的类。

  • vertex 字段,GPUVertexState 类型的 JavaScript 对象,是一个可编程阶段,除了可编程阶段对象的属性外,还额外描述了顶点着色器的入口点函数、输入到管线中的顶点数据的布局;
  • primitive 字段,GPUPrimitiveState 类型的 JavaScript 对象,描述图元装配的信息,默认是空对象;
  • depthStencil 字段,GPUDepthStencilState 类型的 JavaScript 对象,是一个可选对象,描述深度模板测试信息;
  • multisample 字段,GPUMultisampleState 类型的 JavaScript 对象,默认是一个空对象,描述管线的多重采样信息;
  • fragment 字段,GPUFragmentState 类型的 JavaScript 对象,是一个可编程阶段,除了可编程阶段对象的属性外,还额外描述了片元着色器的入口点函数及如何输出片元颜色。如果它是 null,那么渲染管线就不会输出颜色,仅仅作为无颜色输出模式,一般这种管线只拿来作深度模板测试。

2.1.1 异步创建管线

除了通过设备对象的 createRenderPipeline 方法同步创建外,还可以通过设备对象的 createRenderPipelineAsync 对象来异步创建,它返回的是 resolve 值是渲染管线对象的一个 Promise,你可以在异步函数中调用 await 来获取它的 resolve 值,异步创建函数同样也用到 GPURenderPipelineDescriptor 参数对象。

2.1.2 如何验证 GPURenderPipelineDescriptor 合规性

满足下列所有条件即可:

  • 验证顶点着色阶段对象的可编程属性是否有问题
  • 验证顶点着色阶段对象自己是否有问题
  • 如果存在 descriptor.fragment 字段(不为 null):
    • 验证片元着色阶段对象的可编程属性是否有问题
    • 验证片元着色阶段对象自己是否有问题
    • 如果片元着色器中用到了 “sample_mask” 内置变量,那么 descriptor.multisample.alphaToCoverageEnable 要设为 false
  • 验证图元拼装阶段对象是否有问题
  • 如果 descriptor.depthStencil 不是 null,那么 验证深度模板测试阶段对象是否有问题
  • 验证多重采样阶段对象是否有问题
  • 对于顶点着色器中用户自定义的出口,片元着色器必定要有与之对应的入口,到 wgsl 会介绍
  • 对于片元着色器和片元着色器中自定义的组件(应该指的是通过 location 在两个着色器之间传递的总个数)的数量,要小于设备限制列表中的 maxInterStageShaderComponents 值。

2.2 图元拼装阶段

GPURenderPipelineDescriptor 对象里的 primitive 字段值,是一个 JavaScript Object。

enum GPUPrimitiveTopology {   "point-list",   "line-list",   "line-strip",   "triangle-list",   "triangle-strip" };  enum GPUFrontFace {   "ccw",   "cw" };  enum GPUCullMode {   "none",   "front",   "back" };  dictionary GPUPrimitiveState {   GPUPrimitiveTopology topology = "triangle-list";   GPUIndexFormat stripIndexFormat;   GPUFrontFace frontFace = "ccw";   GPUCullMode cullMode = "none";    boolean clampDepth = false; };

图元装配阶段对象是 GPUPrimitiveState 类型的,所有的参数均可选,它本身也是可选的。

  • 参数 topologyGPUPrimitiveTopology 字符串枚举类型的值,表示顶点如何装配,默认是三角形列表 "triangle-list",其余还有 三角带 "triangle-strip"、线列表 "line-list"、线带 "line-strip" 和单纯绘制 点 "piont-list",与 WebGL 中 drawArray 的 mode 参数类似;
  • 参数 stripIndexFormat,可选参数,若为索引三角形,则指定索引数值的数字类型,是一种字符串枚举类型 GPUIndexFormat,具体见顶点着色阶段中的 WebIDL 定义;
  • 参数 frontFace,可选参数,默认值是 "ccw",是一种字符串枚举类型 GPUFrontFace,指的是点构成三角形的旋绕方向,ccw 即逆时针,cw 即顺时针。
  • 参数 cullMode,可选参数,默认值是 "none",是一种字符串枚举类型 GPUCullMode,指的是剔除模式,除了 none 之外还有前剔除、背剔除;
  • 参数 clampDepth,可选参数,默认值是 false,布尔类型,若启用则表示深度值会被截取;需要在请求设备对象时启用 clamp-depth 功能。

2.2.1 顶点索引格式 GPUIndexFormat

enum GPUIndexFormat {   "uint16",   "uint32" };

顶点索引格式,GPUIndexFormat,它决定了 VBO 中索引值的数据类型以及当图元拓扑为 “line-strip” 和 “triangle-strip” 时图元的 重新起算值

图元的重新起算值(Primitive Restart Value),指示应从哪里重新开始算一个图元,而不是继续使用先前索引过的顶点来构造三角带。

GPUPrimitiveState,如果其 "topology" 字段指定了 “line-strip” 或 “triangle-strip”,那么它的 "stripIndexFormat" 字段也需要相应地设置,以便管线创建时可以用到图元重新起算值。

如果用的是 “triangle-list”、”line-list” 和 “point-list”,那么 "stripIndexFormat" 要设为 undefined,并要使用渲染通道编码器(GPURenderPassEncoder)的 setIndexBuffer 方法来设置索引。

译者注

原文介绍 GPUIndexFormat 类型是在顶点着色阶段(Vertex State),笔者觉得此部分应摆在用到它的类型下更合适。

uint16 代表重新起算值是 2byte(0xFFFF),即 2byte 才取一个索引数字;uint32 表示重新起算值是 4byte(0xFFFFFFFF),即 4byte 才取一个索引数字。

2.2.2 如何验证图元拼装阶段对象合规性

如果下列条件都满足,那么图元拼装阶段对象就是没问题的:

  • 图元拼装阶段对象的 topology 字段是 “line-strip” 或 “triangle-strip”,那么 stripIndexFormat 不能是 undefined;topology 是其他的,stripIndexFormat 就必须是 undefined;
  • 图元拼装阶段对象的 clampDepth 是 true,那么设备的功能列表要包括 “depth-clamp” 功能

2.2.3 代码举例

const renderPipeline = device.createRenderPipeline({   /* ... */   primitive: {     topology: "triangle-list",   } })

2.3 顶点着色阶段

顶点着色阶段对象,是 GPURenderPipelineDescriptor 对象中 vertex 字段的值,是一个 JavaScript Object,要满足 GPUVertexState 类型(包括其父类型 GPUProgrammableStage):

dictionary GPUVertexState: GPUProgrammableStage {     sequence<GPUVertexBufferLayout?> buffers = []; };  dictionary GPUVertexBufferLayout {   required GPUSize64 arrayStride;   GPUVertexStepMode stepMode = "vertex";   required sequence<GPUVertexAttribute> attributes; };  enum GPUVertexStepMode {   "vertex",   "instance" };

GPUVertexState 对象需要一个 buffers 字段,是 GPUVertexBufferLayout 对象的数组。

GPUVertexBufferLayout 对象,需要两个必选参数:

  • arrayStride,unsigned longlong 类型,表示一个顶点包括的所有数据(坐标、颜色、法线、纹理坐标等)步幅有多大;
  • attributes,一个数组,元素类型是 GPUVertexAttribute 的对象,描述这块顶点数据中有多少个 顶点属性;

还有一个可选参数,GPUVertexStepMode 字符枚举类型的字段 stepMode,默认值是 "vertex";它表示如何访问顶点数据,它有两种值:

  • “vertex”,表示无论渲染通道编码器发出几次绘制(draw 方法的 instanceCount 参数无论是几)指令,都不会从 VertexBuffer 的头部再重新开始读取顶点数据,而是基于第一次读取的末尾继续往下读;
  • “instance”,表示即使渲染通道编码器发出绘制多次(draw 方法的 instanceCount 参数大于 1)的指令,在绘制完第一轮后(顶点着色器跑了一遍后),仍然从同一个 VertexBuffer 的起点开始获取顶点数据;

2.3.1 顶点缓存(VertexBuffer)与顶点属性(GPUVertexAttribute)

概念上说,顶点缓存是显存中顶点数据的一个描述视图,具体而言是一个数组,前一个数组元素和后一个数组元素之间的距离被称作 ArrayStride(单位:byte),也可以称之为元素的长度。

每一个元素,被称为一个顶点数据,它由若干个顶点属性(VertexAttribute)构成。每个顶点属性在顶点着色器中的 location 是独一无二的。

“` web-idl
dictionary GPUVertexAttribute {
required GPUVertexFormat format;
required GPUSize64 offset;
required GPUIndex32 shaderLocation;

系列博客目录传送门:https://xiaozhuanlan.com/topic/9587342601

  • 管线
  • 1 基础管线
    • 1.1 基础管线的 getBindGroupLayout 方法
    • 1.2 默认管线布局
    • 1.3 可编程阶段:GPUProgrammableStage
    • 1.4 补充资料:管线和着色阶段
    • 1.5 如何验证可编程阶段对象的合规性
    • 1.6 如何验证某个着色器中被绑定的变量的合规性
  • 2 渲染管线
    • 2.1 管线的创建
      • 2.1.1 异步创建管线
      • 2.1.2 如何验证 GPURenderPipelineDescriptor 合规性
    • 2.2 图元拼装阶段
      • 2.2.1 顶点索引格式 GPUIndexFormat
      • 2.2.2 如何验证图元拼装阶段对象合规性
      • 2.2.3 代码举例
    • 2.3 顶点着色阶段
      • 2.3.1 顶点缓存(VertexBuffer)与顶点属性(GPUVertexAttribute)


https://www.w3.org/TR/webgpu/#pipelines

GPUPipelineBase ├ GPURenderPipeline └ GPUComputePipeline

管线

管线代表某种计算的过程,在 WebGPU 中,有渲染管线和计算管线两种。

这一过程需要用到绑定组、VBO、着色器等对象或资源,然后最终能输出一些内容,譬如渲染管线输出颜色值(以颜色附件形式),计算管线输出到其指定的地方,此处就不列举太详细了。

管线在结构上看,由一系列 可编程阶段 和一些固定的状态组合而成。

注意,根据操作系统、显卡驱动不同,有部分固定的状态会编译到着色器代码中,因此将他们组合成一个管线对象里是不错的选择。

两种管线对象均可由设备对象创建。

在对应的通道编码器中,可以切换管线以进行不同的计算过程。

1 基础管线

dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase {     GPUPipelineLayout layout; };  interface mixin GPUPipelineBase {     GPUBindGroupLayout getBindGroupLayout(unsigned long index); };

两种管线在创建时均需要参数对象,参数对象各自有不同的具体类型,但均继承自这里的 GPUPipelineDescriptorBase 类型。

两种管线也均继承自基础管线类型 GPUPipelineBase

1.1 基础管线的 getBindGroupLayout 方法

每个管线对象均有此方法,它接受一个 unsigned long 类型的数字作为参数,返回一个管线布局对象中对应位置的绑定组布局对象。

译者注:若创建管线时没有传递布局对象,这个方法会根据着色器代码内的 group 特性自动构造出绑定组布局对象。

有一个需要注意的,那就是这个数字参数要小于设备限制列表中的 maxBindGroups 值。

1.2 默认管线布局

如果创建一个管线时,没有设置管线布局对象,那么会自动在内部创建一个默认的布局对象。这个过程在文档中尚未详尽解释,只说了是一种“反射”技术,然后一步一步构造出来。

具体过程要参考文档中 WebGPU Spec 10.1.1 默认管线布局 的创建步骤。

默认的管线布局对象,其 bindGroupLayouts 数组是空数组。

1.3 可编程阶段:GPUProgrammableStage

在创建管线对象时,需要用到参数对象,这个参数对象有不同可编程阶段可以设置,其中每一个阶段都是一个 GPUProgrammableStage 对象。这一点在下文管线创建部分会详细列举出来。

GPUProgrammableStage 类型的对象组织起了管线中具体的一个阶段用到什么 GPUShaderModule,其 WGSL 入口点函数名是什么,需要传递哪些常量值这些信息。

会有详细介绍。

dictionary GPUProgrammableStage {   required GPUShaderModule module;   required USVString entryPoint;   record<USVString, GPUPipelineConstantValue> constants; };  typedef double GPUPipelineConstantValue;

每一个这样的对象,有两个必选参数:

  • module,GPUShaderModule 类型的变量,着色器模块,参考着色器模块章节

  • entryPoint,字符串,指定 module 着色器代码中的入口函数

还有一个可选参数 constants,是一个值是 GPUPipelineConstantValue 类型的 JavaScript 对象,它用来传递着色器代码中具有 override 特性的常量值。

这个对象的键可以是 WGSL 中特性 override(i) 括号里的 i,也可以是常量名。例如(以顶点阶段为例):

const pipeline = device.createRenderPipeline({   vertex: {     /* ... */     constants: {       1300: 2.0, // 将传递给着色器代码中第 1300 个,也即 gain 常量       depth: -1, // 将传递给着色器代码中的 depth 常量     }   } })

对应着色器代码:

[[override(1300)]] let gain: f32; // 将收到 2.0f [[override]] let depth: f32; // 将收到 -1 [[override(0)]] let has_point_light: bool = true; [[override(1200)]] let specular_param: f32 = 2.3; [[override]] let width: f32 = 0.0;  // 其他代码

如果你想把这几个 WGSL 常量中带默认值的也一并覆盖掉,可以传递这样的对象:

const pipeline = device.createRenderPipeline({   vertex: {     /* ... */     constants: {       1300: 2.0, // 将传递给着色器代码中第 1300 个,也即 gain 常量       depth: -1, // 将传递给着色器代码中的 depth 常量       0: false,       1200: 3.0,       width: 20,     }   } })

JavaScript 的数值类型转到 WGSL 时,会自动根据常量在 WGSL 中具体的类型(bool,i32,u32,f32)进行转换。

1.4 补充资料:管线和着色阶段

WebGPU 通过发出绘制命令或调度命令的方式向 GPU 下达指令。

管线在 GPU 执行的计算的行为,被描述为一系列的阶段,其中有一些是可编程的。在 WebGPU 中,执行绘制或调度之前需要创建管线,执行绘制的叫渲染管线,执行调度的叫计算管线。

1.5 如何验证可编程阶段对象的合规性

创建管线时,对某个阶段对象(设为 stage)及管线布局对象(设为 layout)是有要求的:

  • stage.module 必须是一个有效的 GPUShaderModule 对象
  • stage.module 的着色器代码中必须有一个入口函数,名字与 stage.entryPoint 要一致
  • 对入口函数中用到的每个被绑定的变量,验证绑定
  • 对入口函数中用到的每个采样纹理,设纹理为 texture,设采样器为 sampler,sampler.type 如果是 “filtering”,那么 texture.sampleType 不能是 “unfilterable-float”
  • 对于 stage.constants 中的所有常量,在着色器中必须有对应的 override 常量;如果着色器中的常量没有使用初始化语法给定默认值,那么 stage.constants 中必须给定值。

1.6 如何验证某个着色器中被绑定的变量的合规性

和标题意图一样,这一小节可以指导着色器中被绑定变量的语法书写、组织。

对于着色器中某个被绑定的变量,记作 variable,设其 groupbind 的数字分别是 bindGroupIdbindId,另外设管线布局对象为 pipelineLayout,绑定组布局可由 pipelineLayout.bindGroupLayouts[bindGroupId] 得到并记作 bindGroupLayout

  • bindGroupLayout 必须有一个 binding 值与 bindId 一样的 entry 对象;

  • 遍历 bindGroupLayout 中的 entries,设被遍历到的 GPUBindGroupLayoutEntry 变量为 entry:

    • 如果是 buffer 类型,且 entry.buffer.type 是

    • “uniform”,那么着色器代码中此 variable 使用 var<uniform> 声明

    • “storage”,那么着色器代码中此 variable 使用 var<storage, read_write> 声明

    • “read-only-storage”,那么着色器代码中此 variable 使用 var<storage, read> 声明

    • 如果是 buffer 类型且 entry.buffer.minBindingSize 不是0:

    • 如果着色器代码中某个结构体的最后一个字段是无界数组,那么 entry.buffer.minBindingSize 必须大于等于数组的偏移量与步幅的和

    • 如果着色器代码中某个结构体最后一个字段并不是无界数组,那么 entry.buffer.minBindingSize 必须大于等于结构体的大小

    • 如果是 sampler 类型,且 entry.sampler.type 是

    • “filtering”,那么 variable 使用 sampler 类型

    • “comparison”,那么 variable 使用 comparison_sampler 类型

    • 如果是 texture 类型,当且仅当 entry.texture.multisampled 是 true 时,variable 要使用 texture_multisampled_2d<T>texture_depth_multisampled_2d<T> 类型;

    • 如果是 texture 类型,当 entry.texture.type 是

    • “float”,”unfilterable-float”,”sint”,”uint” 时,variable 要使用 texture_1d<T>, texture_2d<T>, texture_2d_array<T>, texture_cube<T>, texture_cube_array<T>, texture_3d<T>, 或 texture_multisampled_2d<T> 类型来声明;关于泛型参数 T,entry.texture.sampleType 是 “float”、”unfilterable-float” 时,T 是 f32,是 “sint” 时,T 是 i32,是 “uint” 时,T 是 u32;

    • “depth”,variable 要使用 texture_depth_2d, texture_depth_2d_array, texture_depth_cube, texture_depth_cube_arraytexture_depth_multisampled_2d 类型来声明

    • 如果是 texture 类型,当 entry.texture.viewDimension 是

    • “1d”,variable 要使用 texture_1d<T> 声明;

    • “2d”,variable 要使用 texture_2d<T>texture_multisampled_2d<T> 声明;

    • “2d-array”,variable 要使用 texture_2d_array<T> 声明;

    • “cube”,variable 要使用 texture_cube<T> 声明;

    • “cube-array”,variable 要使用 texture_cube_array<T> 声明;

    • “3d”,variable 要使用 texture_3d<T> 声明;

    • 如果是 storageTexture 类型,当 entry.storageTexture.viewDimension 是

    • “1d”,variable 要使用 texture_storage_1d<T, A> 类型来声明;

    • “2d”,variable 要使用 texture_storage_2d<T, A> 类型来声明;

    • “2d-array”,variable 要使用 texture_storage_2d_array<T, A> 类型来声明;

    • “3d”,variable 要使用 texture_storage_3d<T, A> 类型来声明;

      entry.storageTexture.access 是 “write-only” 时,上述泛型中的 A 要使用 write,泛型类型 T 要跟 entry.storageTexture.format 一样。

2 渲染管线

渲染管线 GPURenderPipeline 是一种控制着顶点着色和片元着色阶段的管线。

它可以被渲染通道编码器 GPURenderPassEncoder 和 渲染打包编码器 GPURenderBundleEncoder 使用。

渲染管线的输入有:

  • 通过 GPUPipelineLayout 提供的绑定组所绑定的资源
  • 顶点和索引数据,由下面 顶点着色阶段 使用
  • 颜色附件,由颜色目标状态描述
  • 一个可选的深度模板附件,由深度模板状态描述

渲染管线的输出有:

  • 绑定组中类型为 "storage" 的 GPUBuffer 资源
  • 绑定组中 access 属性为 "write-only" 的 GPUStorageTexture 资源
  • 颜色附件
  • 可选的深度模板附件

渲染管线有如下几个 渲染阶段

  • 顶点提取(Vertex Fetch),由 GPUVertexState 对象的 buffers 属性来描述、控制
  • 顶点着色(Vertex Shader),由 GPUVertexState 对象描述、控制
  • 图元装配(Primitive Assembly),由 GPUPrimitiveState 对象描述、控制
  • 光栅化(Rasterization),由 GPUPrimitiveStateGPUDepthStencilStateGPUMultisampleState 对象来描述、控制
  • 片元着色(Fragment Shader),由 GPUFragmentState 对象来控制
  • 依次模板测试及操作(StencilTest)和深度测试并写入(DepthTest),由 GPUDepthStencilState 对象描述、控制
  • 融合输出,由 GPUFragmentState 对象的 targets 属性来描述、控制

上述的 GPUVertexStateGPUPrimitiveStateGPUFragmentStateGPUDepthStencilStateGPUMultisampleState 类型在下文的 顶点着色阶段、图元拼装阶段、片元着色阶段、深度模板测试阶段、多重采样阶段 几个小节均有介绍,对应着接下来 渲染管线创建 小节中介绍的 GPURenderPipelineDescriptor 对象中的 vertexprimitivefragmentdepthStencilfragment 字段。

这 5 个阶段中,只有顶点着色阶段和片元着色阶段是 可编程阶段。

渲染管线的 WebIDL 定义如下:

[Exposed=(Window, DedicatedWorker), SecureContext] interface GPURenderPipeline { }; GPURenderPipeline includes GPUObjectBase; GPURenderPipeline includes GPUPipelineBase;

2.1 管线的创建

渲染管线是通过设备对象的 createRenderPipeline 方法来创建的。

创建渲染管线需要一个 GPURenderPipelineDescriptor 类型的对象作为参数。

dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase {   required GPUVertexState vertex;   GPUPrimitiveState primitive = {};   GPUDepthStencilState depthStencil;   GPUMultisampleState multisample = {};   GPUFragmentState fragment; };

在此对象中,有若干个阶段状态,在此简单描述每个字段的用途,后面小节会详细介绍它们对应的类。

  • vertex 字段,GPUVertexState 类型的 JavaScript 对象,是一个可编程阶段,除了可编程阶段对象的属性外,还额外描述了顶点着色器的入口点函数、输入到管线中的顶点数据的布局;
  • primitive 字段,GPUPrimitiveState 类型的 JavaScript 对象,描述图元装配的信息,默认是空对象;
  • depthStencil 字段,GPUDepthStencilState 类型的 JavaScript 对象,是一个可选对象,描述深度模板测试信息;
  • multisample 字段,GPUMultisampleState 类型的 JavaScript 对象,默认是一个空对象,描述管线的多重采样信息;
  • fragment 字段,GPUFragmentState 类型的 JavaScript 对象,是一个可编程阶段,除了可编程阶段对象的属性外,还额外描述了片元着色器的入口点函数及如何输出片元颜色。如果它是 null,那么渲染管线就不会输出颜色,仅仅作为无颜色输出模式,一般这种管线只拿来作深度模板测试。

2.1.1 异步创建管线

除了通过设备对象的 createRenderPipeline 方法同步创建外,还可以通过设备对象的 createRenderPipelineAsync 对象来异步创建,它返回的是 resolve 值是渲染管线对象的一个 Promise,你可以在异步函数中调用 await 来获取它的 resolve 值,异步创建函数同样也用到 GPURenderPipelineDescriptor 参数对象。

2.1.2 如何验证 GPURenderPipelineDescriptor 合规性

满足下列所有条件即可:

  • 验证顶点着色阶段对象的可编程属性是否有问题
  • 验证顶点着色阶段对象自己是否有问题
  • 如果存在 descriptor.fragment 字段(不为 null):
    • 验证片元着色阶段对象的可编程属性是否有问题
    • 验证片元着色阶段对象自己是否有问题
    • 如果片元着色器中用到了 “sample_mask” 内置变量,那么 descriptor.multisample.alphaToCoverageEnable 要设为 false
  • 验证图元拼装阶段对象是否有问题
  • 如果 descriptor.depthStencil 不是 null,那么 验证深度模板测试阶段对象是否有问题
  • 验证多重采样阶段对象是否有问题
  • 对于顶点着色器中用户自定义的出口,片元着色器必定要有与之对应的入口,到 wgsl 会介绍
  • 对于片元着色器和片元着色器中自定义的组件(应该指的是通过 location 在两个着色器之间传递的总个数)的数量,要小于设备限制列表中的 maxInterStageShaderComponents 值。

2.2 图元拼装阶段

GPURenderPipelineDescriptor 对象里的 primitive 字段值,是一个 JavaScript Object。

enum GPUPrimitiveTopology {   "point-list",   "line-list",   "line-strip",   "triangle-list",   "triangle-strip" };  enum GPUFrontFace {   "ccw",   "cw" };  enum GPUCullMode {   "none",   "front",   "back" };  dictionary GPUPrimitiveState {   GPUPrimitiveTopology topology = "triangle-list";   GPUIndexFormat stripIndexFormat;   GPUFrontFace frontFace = "ccw";   GPUCullMode cullMode = "none";    boolean clampDepth = false; };

图元装配阶段对象是 GPUPrimitiveState 类型的,所有的参数均可选,它本身也是可选的。

  • 参数 topologyGPUPrimitiveTopology 字符串枚举类型的值,表示顶点如何装配,默认是三角形列表 "triangle-list",其余还有 三角带 "triangle-strip"、线列表 "line-list"、线带 "line-strip" 和单纯绘制 点 "piont-list",与 WebGL 中 drawArray 的 mode 参数类似;
  • 参数 stripIndexFormat,可选参数,若为索引三角形,则指定索引数值的数字类型,是一种字符串枚举类型 GPUIndexFormat,具体见顶点着色阶段中的 WebIDL 定义;
  • 参数 frontFace,可选参数,默认值是 "ccw",是一种字符串枚举类型 GPUFrontFace,指的是点构成三角形的旋绕方向,ccw 即逆时针,cw 即顺时针。
  • 参数 cullMode,可选参数,默认值是 "none",是一种字符串枚举类型 GPUCullMode,指的是剔除模式,除了 none 之外还有前剔除、背剔除;
  • 参数 clampDepth,可选参数,默认值是 false,布尔类型,若启用则表示深度值会被截取;需要在请求设备对象时启用 clamp-depth 功能。

2.2.1 顶点索引格式 GPUIndexFormat

enum GPUIndexFormat {   "uint16",   "uint32" };

顶点索引格式,GPUIndexFormat,它决定了 VBO 中索引值的数据类型以及当图元拓扑为 “line-strip” 和 “triangle-strip” 时图元的 重新起算值

图元的重新起算值(Primitive Restart Value),指示应从哪里重新开始算一个图元,而不是继续使用先前索引过的顶点来构造三角带。

GPUPrimitiveState,如果其 "topology" 字段指定了 “line-strip” 或 “triangle-strip”,那么它的 "stripIndexFormat" 字段也需要相应地设置,以便管线创建时可以用到图元重新起算值。

如果用的是 “triangle-list”、”line-list” 和 “point-list”,那么 "stripIndexFormat" 要设为 undefined,并要使用渲染通道编码器(GPURenderPassEncoder)的 setIndexBuffer 方法来设置索引。

译者注

原文介绍 GPUIndexFormat 类型是在顶点着色阶段(Vertex State),笔者觉得此部分应摆在用到它的类型下更合适。

uint16 代表重新起算值是 2byte(0xFFFF),即 2byte 才取一个索引数字;uint32 表示重新起算值是 4byte(0xFFFFFFFF),即 4byte 才取一个索引数字。

2.2.2 如何验证图元拼装阶段对象合规性

如果下列条件都满足,那么图元拼装阶段对象就是没问题的:

  • 图元拼装阶段对象的 topology 字段是 “line-strip” 或 “triangle-strip”,那么 stripIndexFormat 不能是 undefined;topology 是其他的,stripIndexFormat 就必须是 undefined;
  • 图元拼装阶段对象的 clampDepth 是 true,那么设备的功能列表要包括 “depth-clamp” 功能

2.2.3 代码举例

const renderPipeline = device.createRenderPipeline({   /* ... */   primitive: {     topology: "triangle-list",   } })

2.3 顶点着色阶段

顶点着色阶段对象,是 GPURenderPipelineDescriptor 对象中 vertex 字段的值,是一个 JavaScript Object,要满足 GPUVertexState 类型(包括其父类型 GPUProgrammableStage):

dictionary GPUVertexState: GPUProgrammableStage {     sequence<GPUVertexBufferLayout?> buffers = []; };  dictionary GPUVertexBufferLayout {   required GPUSize64 arrayStride;   GPUVertexStepMode stepMode = "vertex";   required sequence<GPUVertexAttribute> attributes; };  enum GPUVertexStepMode {   "vertex",   "instance" };

GPUVertexState 对象需要一个 buffers 字段,是 GPUVertexBufferLayout 对象的数组。

GPUVertexBufferLayout 对象,需要两个必选参数:

  • arrayStride,unsigned longlong 类型,表示一个顶点包括的所有数据(坐标、颜色、法线、纹理坐标等)步幅有多大;
  • attributes,一个数组,元素类型是 GPUVertexAttribute 的对象,描述这块顶点数据中有多少个 顶点属性;

还有一个可选参数,GPUVertexStepMode 字符枚举类型的字段 stepMode,默认值是 "vertex";它表示如何访问顶点数据,它有两种值:

  • “vertex”,表示无论渲染通道编码器发出几次绘制(draw 方法的 instanceCount 参数无论是几)指令,都不会从 VertexBuffer 的头部再重新开始读取顶点数据,而是基于第一次读取的末尾继续往下读;
  • “instance”,表示即使渲染通道编码器发出绘制多次(draw 方法的 instanceCount 参数大于 1)的指令,在绘制完第一轮后(顶点着色器跑了一遍后),仍然从同一个 VertexBuffer 的起点开始获取顶点数据;

2.3.1 顶点缓存(VertexBuffer)与顶点属性(GPUVertexAttribute)

概念上说,顶点缓存是显存中顶点数据的一个描述视图,具体而言是一个数组,前一个数组元素和后一个数组元素之间的距离被称作 ArrayStride(单位:byte),也可以称之为元素的长度。

每一个元素,被称为一个顶点数据,它由若干个顶点属性(VertexAttribute)构成。每个顶点属性在顶点着色器中的 location 是独一无二的。

“` web-idl
dictionary GPUVertexAttribute {
required GPUVertexFormat format;
required GPUSize64 offset;
required GPUIndex32 shaderLocation;

系列博客目录传送门:https://xiaozhuanlan.com/topic/9587342601

  • 管线
  • 1 基础管线
    • 1.1 基础管线的 getBindGroupLayout 方法
    • 1.2 默认管线布局
    • 1.3 可编程阶段:GPUProgrammableStage
    • 1.4 补充资料:管线和着色阶段
    • 1.5 如何验证可编程阶段对象的合规性
    • 1.6 如何验证某个着色器中被绑定的变量的合规性
  • 2 渲染管线
    • 2.1 管线的创建
      • 2.1.1 异步创建管线
      • 2.1.2 如何验证 GPURenderPipelineDescriptor 合规性
    • 2.2 图元拼装阶段
      • 2.2.1 顶点索引格式 GPUIndexFormat
      • 2.2.2 如何验证图元拼装阶段对象合规性
      • 2.2.3 代码举例
    • 2.3 顶点着色阶段
      • 2.3.1 顶点缓存(VertexBuffer)与顶点属性(GPUVertexAttribute)


https://www.w3.org/TR/webgpu/#pipelines

GPUPipelineBase ├ GPURenderPipeline └ GPUComputePipeline

管线

管线代表某种计算的过程,在 WebGPU 中,有渲染管线和计算管线两种。

这一过程需要用到绑定组、VBO、着色器等对象或资源,然后最终能输出一些内容,譬如渲染管线输出颜色值(以颜色附件形式),计算管线输出到其指定的地方,此处就不列举太详细了。

管线在结构上看,由一系列 可编程阶段 和一些固定的状态组合而成。

注意,根据操作系统、显卡驱动不同,有部分固定的状态会编译到着色器代码中,因此将他们组合成一个管线对象里是不错的选择。

两种管线对象均可由设备对象创建。

在对应的通道编码器中,可以切换管线以进行不同的计算过程。

1 基础管线

dictionary GPUPipelineDescriptorBase : GPUObjectDescriptorBase {     GPUPipelineLayout layout; };  interface mixin GPUPipelineBase {     GPUBindGroupLayout getBindGroupLayout(unsigned long index); };

两种管线在创建时均需要参数对象,参数对象各自有不同的具体类型,但均继承自这里的 GPUPipelineDescriptorBase 类型。

两种管线也均继承自基础管线类型 GPUPipelineBase

1.1 基础管线的 getBindGroupLayout 方法

每个管线对象均有此方法,它接受一个 unsigned long 类型的数字作为参数,返回一个管线布局对象中对应位置的绑定组布局对象。

译者注:若创建管线时没有传递布局对象,这个方法会根据着色器代码内的 group 特性自动构造出绑定组布局对象。

有一个需要注意的,那就是这个数字参数要小于设备限制列表中的 maxBindGroups 值。

1.2 默认管线布局

如果创建一个管线时,没有设置管线布局对象,那么会自动在内部创建一个默认的布局对象。这个过程在文档中尚未详尽解释,只说了是一种“反射”技术,然后一步一步构造出来。

具体过程要参考文档中 WebGPU Spec 10.1.1 默认管线布局 的创建步骤。

默认的管线布局对象,其 bindGroupLayouts 数组是空数组。

1.3 可编程阶段:GPUProgrammableStage

在创建管线对象时,需要用到参数对象,这个参数对象有不同可编程阶段可以设置,其中每一个阶段都是一个 GPUProgrammableStage 对象。这一点在下文管线创建部分会详细列举出来。

GPUProgrammableStage 类型的对象组织起了管线中具体的一个阶段用到什么 GPUShaderModule,其 WGSL 入口点函数名是什么,需要传递哪些常量值这些信息。

会有详细介绍。

dictionary GPUProgrammableStage {   required GPUShaderModule module;   required USVString entryPoint;   record<USVString, GPUPipelineConstantValue> constants; };  typedef double GPUPipelineConstantValue;

每一个这样的对象,有两个必选参数:

  • module,GPUShaderModule 类型的变量,着色器模块,参考着色器模块章节

  • entryPoint,字符串,指定 module 着色器代码中的入口函数

还有一个可选参数 constants,是一个值是 GPUPipelineConstantValue 类型的 JavaScript 对象,它用来传递着色器代码中具有 override 特性的常量值。

这个对象的键可以是 WGSL 中特性 override(i) 括号里的 i,也可以是常量名。例如(以顶点阶段为例):

const pipeline = device.createRenderPipeline({   vertex: {     /* ... */     constants: {       1300: 2.0, // 将传递给着色器代码中第 1300 个,也即 gain 常量       depth: -1, // 将传递给着色器代码中的 depth 常量     }   } })

对应着色器代码:

[[override(1300)]] let gain: f32; // 将收到 2.0f [[override]] let depth: f32; // 将收到 -1 [[override(0)]] let has_point_light: bool = true; [[override(1200)]] let specular_param: f32 = 2.3; [[override]] let width: f32 = 0.0;  // 其他代码

如果你想把这几个 WGSL 常量中带默认值的也一并覆盖掉,可以传递这样的对象:

const pipeline = device.createRenderPipeline({   vertex: {     /* ... */     constants: {       1300: 2.0, // 将传递给着色器代码中第 1300 个,也即 gain 常量       depth: -1, // 将传递给着色器代码中的 depth 常量       0: false,       1200: 3.0,       width: 20,     }   } })

JavaScript 的数值类型转到 WGSL 时,会自动根据常量在 WGSL 中具体的类型(bool,i32,u32,f32)进行转换。

1.4 补充资料:管线和着色阶段

WebGPU 通过发出绘制命令或调度命令的方式向 GPU 下达指令。

管线在 GPU 执行的计算的行为,被描述为一系列的阶段,其中有一些是可编程的。在 WebGPU 中,执行绘制或调度之前需要创建管线,执行绘制的叫渲染管线,执行调度的叫计算管线。

1.5 如何验证可编程阶段对象的合规性

创建管线时,对某个阶段对象(设为 stage)及管线布局对象(设为 layout)是有要求的:

  • stage.module 必须是一个有效的 GPUShaderModule 对象
  • stage.module 的着色器代码中必须有一个入口函数,名字与 stage.entryPoint 要一致
  • 对入口函数中用到的每个被绑定的变量,验证绑定
  • 对入口函数中用到的每个采样纹理,设纹理为 texture,设采样器为 sampler,sampler.type 如果是 “filtering”,那么 texture.sampleType 不能是 “unfilterable-float”
  • 对于 stage.constants 中的所有常量,在着色器中必须有对应的 override 常量;如果着色器中的常量没有使用初始化语法给定默认值,那么 stage.constants 中必须给定值。

1.6 如何验证某个着色器中被绑定的变量的合规性

和标题意图一样,这一小节可以指导着色器中被绑定变量的语法书写、组织。

对于着色器中某个被绑定的变量,记作 variable,设其 groupbind 的数字分别是 bindGroupIdbindId,另外设管线布局对象为 pipelineLayout,绑定组布局可由 pipelineLayout.bindGroupLayouts[bindGroupId] 得到并记作 bindGroupLayout

  • bindGroupLayout 必须有一个 binding 值与 bindId 一样的 entry 对象;

  • 遍历 bindGroupLayout 中的 entries,设被遍历到的 GPUBindGroupLayoutEntry 变量为 entry:

    • 如果是 buffer 类型,且 entry.buffer.type 是

    • “uniform”,那么着色器代码中此 variable 使用 var<uniform> 声明

    • “storage”,那么着色器代码中此 variable 使用 var<storage, read_write> 声明

    • “read-only-storage”,那么着色器代码中此 variable 使用 var<storage, read> 声明

    • 如果是 buffer 类型且 entry.buffer.minBindingSize 不是0:

    • 如果着色器代码中某个结构体的最后一个字段是无界数组,那么 entry.buffer.minBindingSize 必须大于等于数组的偏移量与步幅的和

    • 如果着色器代码中某个结构体最后一个字段并不是无界数组,那么 entry.buffer.minBindingSize 必须大于等于结构体的大小

    • 如果是 sampler 类型,且 entry.sampler.type 是

    • “filtering”,那么 variable 使用 sampler 类型

    • “comparison”,那么 variable 使用 comparison_sampler 类型

    • 如果是 texture 类型,当且仅当 entry.texture.multisampled 是 true 时,variable 要使用 texture_multisampled_2d<T>texture_depth_multisampled_2d<T> 类型;

    • 如果是 texture 类型,当 entry.texture.type 是

    • “float”,”unfilterable-float”,”sint”,”uint” 时,variable 要使用 texture_1d<T>, texture_2d<T>, texture_2d_array<T>, texture_cube<T>, texture_cube_array<T>, texture_3d<T>, 或 texture_multisampled_2d<T> 类型来声明;关于泛型参数 T,entry.texture.sampleType 是 “float”、”unfilterable-float” 时,T 是 f32,是 “sint” 时,T 是 i32,是 “uint” 时,T 是 u32;

    • “depth”,variable 要使用 texture_depth_2d, texture_depth_2d_array, texture_depth_cube, texture_depth_cube_arraytexture_depth_multisampled_2d 类型来声明

    • 如果是 texture 类型,当 entry.texture.viewDimension 是

    • “1d”,variable 要使用 texture_1d<T> 声明;

    • “2d”,variable 要使用 texture_2d<T>texture_multisampled_2d<T> 声明;

    • “2d-array”,variable 要使用 texture_2d_array<T> 声明;

    • “cube”,variable 要使用 texture_cube<T> 声明;

    • “cube-array”,variable 要使用 texture_cube_array<T> 声明;

    • “3d”,variable 要使用 texture_3d<T> 声明;

    • 如果是 storageTexture 类型,当 entry.storageTexture.viewDimension 是

    • “1d”,variable 要使用 texture_storage_1d<T, A> 类型来声明;

    • “2d”,variable 要使用 texture_storage_2d<T, A> 类型来声明;

    • “2d-array”,variable 要使用 texture_storage_2d_array<T, A> 类型来声明;

    • “3d”,variable 要使用 texture_storage_3d<T, A> 类型来声明;

      entry.storageTexture.access 是 “write-only” 时,上述泛型中的 A 要使用 write,泛型类型 T 要跟 entry.storageTexture.format 一样。

2 渲染管线

渲染管线 GPURenderPipeline 是一种控制着顶点着色和片元着色阶段的管线。

它可以被渲染通道编码器 GPURenderPassEncoder 和 渲染打包编码器 GPURenderBundleEncoder 使用。

渲染管线的输入有:

  • 通过 GPUPipelineLayout 提供的绑定组所绑定的资源
  • 顶点和索引数据,由下面 顶点着色阶段 使用
  • 颜色附件,由颜色目标状态描述
  • 一个可选的深度模板附件,由深度模板状态描述

渲染管线的输出有:

  • 绑定组中类型为 "storage" 的 GPUBuffer 资源
  • 绑定组中 access 属性为 "write-only" 的 GPUStorageTexture 资源
  • 颜色附件
  • 可选的深度模板附件

渲染管线有如下几个 渲染阶段

  • 顶点提取(Vertex Fetch),由 GPUVertexState 对象的 buffers 属性来描述、控制
  • 顶点着色(Vertex Shader),由 GPUVertexState 对象描述、控制
  • 图元装配(Primitive Assembly),由 GPUPrimitiveState 对象描述、控制
  • 光栅化(Rasterization),由 GPUPrimitiveStateGPUDepthStencilStateGPUMultisampleState 对象来描述、控制
  • 片元着色(Fragment Shader),由 GPUFragmentState 对象来控制
  • 依次模板测试及操作(StencilTest)和深度测试并写入(DepthTest),由 GPUDepthStencilState 对象描述、控制
  • 融合输出,由 GPUFragmentState 对象的 targets 属性来描述、控制

上述的 GPUVertexStateGPUPrimitiveStateGPUFragmentStateGPUDepthStencilStateGPUMultisampleState 类型在下文的 顶点着色阶段、图元拼装阶段、片元着色阶段、深度模板测试阶段、多重采样阶段 几个小节均有介绍,对应着接下来 渲染管线创建 小节中介绍的 GPURenderPipelineDescriptor 对象中的 vertexprimitivefragmentdepthStencilfragment 字段。

这 5 个阶段中,只有顶点着色阶段和片元着色阶段是 可编程阶段。

渲染管线的 WebIDL 定义如下:

[Exposed=(Window, DedicatedWorker), SecureContext] interface GPURenderPipeline { }; GPURenderPipeline includes GPUObjectBase; GPURenderPipeline includes GPUPipelineBase;

2.1 管线的创建

渲染管线是通过设备对象的 createRenderPipeline 方法来创建的。

创建渲染管线需要一个 GPURenderPipelineDescriptor 类型的对象作为参数。

dictionary GPURenderPipelineDescriptor : GPUPipelineDescriptorBase {   required GPUVertexState vertex;   GPUPrimitiveState primitive = {};   GPUDepthStencilState depthStencil;   GPUMultisampleState multisample = {};   GPUFragmentState fragment; };

在此对象中,有若干个阶段状态,在此简单描述每个字段的用途,后面小节会详细介绍它们对应的类。

  • vertex 字段,GPUVertexState 类型的 JavaScript 对象,是一个可编程阶段,除了可编程阶段对象的属性外,还额外描述了顶点着色器的入口点函数、输入到管线中的顶点数据的布局;
  • primitive 字段,GPUPrimitiveState 类型的 JavaScript 对象,描述图元装配的信息,默认是空对象;
  • depthStencil 字段,GPUDepthStencilState 类型的 JavaScript 对象,是一个可选对象,描述深度模板测试信息;
  • multisample 字段,GPUMultisampleState 类型的 JavaScript 对象,默认是一个空对象,描述管线的多重采样信息;
  • fragment 字段,GPUFragmentState 类型的 JavaScript 对象,是一个可编程阶段,除了可编程阶段对象的属性外,还额外描述了片元着色器的入口点函数及如何输出片元颜色。如果它是 null,那么渲染管线就不会输出颜色,仅仅作为无颜色输出模式,一般这种管线只拿来作深度模板测试。

2.1.1 异步创建管线

除了通过设备对象的 createRenderPipeline 方法同步创建外,还可以通过设备对象的 createRenderPipelineAsync 对象来异步创建,它返回的是 resolve 值是渲染管线对象的一个 Promise,你可以在异步函数中调用 await 来获取它的 resolve 值,异步创建函数同样也用到 GPURenderPipelineDescriptor 参数对象。

2.1.2 如何验证 GPURenderPipelineDescriptor 合规性

满足下列所有条件即可:

  • 验证顶点着色阶段对象的可编程属性是否有问题
  • 验证顶点着色阶段对象自己是否有问题
  • 如果存在 descriptor.fragment 字段(不为 null):
    • 验证片元着色阶段对象的可编程属性是否有问题
    • 验证片元着色阶段对象自己是否有问题
    • 如果片元着色器中用到了 “sample_mask” 内置变量,那么 descriptor.multisample.alphaToCoverageEnable 要设为 false
  • 验证图元拼装阶段对象是否有问题
  • 如果 descriptor.depthStencil 不是 null,那么 验证深度模板测试阶段对象是否有问题
  • 验证多重采样阶段对象是否有问题
  • 对于顶点着色器中用户自定义的出口,片元着色器必定要有与之对应的入口,到 wgsl 会介绍
  • 对于片元着色器和片元着色器中自定义的组件(应该指的是通过 location 在两个着色器之间传递的总个数)的数量,要小于设备限制列表中的 maxInterStageShaderComponents 值。

2.2 图元拼装阶段

GPURenderPipelineDescriptor 对象里的 primitive 字段值,是一个 JavaScript Object。

enum GPUPrimitiveTopology {   "point-list",   "line-list",   "line-strip",   "triangle-list",   "triangle-strip" };  enum GPUFrontFace {   "ccw",   "cw" };  enum GPUCullMode {   "none",   "front",   "back" };  dictionary GPUPrimitiveState {   GPUPrimitiveTopology topology = "triangle-list";   GPUIndexFormat stripIndexFormat;   GPUFrontFace frontFace = "ccw";   GPUCullMode cullMode = "none";    boolean clampDepth = false; };

图元装配阶段对象是 GPUPrimitiveState 类型的,所有的参数均可选,它本身也是可选的。

  • 参数 topologyGPUPrimitiveTopology 字符串枚举类型的值,表示顶点如何装配,默认是三角形列表 "triangle-list",其余还有 三角带 "triangle-strip"、线列表 "line-list"、线带 "line-strip" 和单纯绘制 点 "piont-list",与 WebGL 中 drawArray 的 mode 参数类似;
  • 参数 stripIndexFormat,可选参数,若为索引三角形,则指定索引数值的数字类型,是一种字符串枚举类型 GPUIndexFormat,具体见顶点着色阶段中的 WebIDL 定义;
  • 参数 frontFace,可选参数,默认值是 "ccw",是一种字符串枚举类型 GPUFrontFace,指的是点构成三角形的旋绕方向,ccw 即逆时针,cw 即顺时针。
  • 参数 cullMode,可选参数,默认值是 "none",是一种字符串枚举类型 GPUCullMode,指的是剔除模式,除了 none 之外还有前剔除、背剔除;
  • 参数 clampDepth,可选参数,默认值是 false,布尔类型,若启用则表示深度值会被截取;需要在请求设备对象时启用 clamp-depth 功能。

2.2.1 顶点索引格式 GPUIndexFormat

enum GPUIndexFormat {   "uint16",   "uint32" };

顶点索引格式,GPUIndexFormat,它决定了 VBO 中索引值的数据类型以及当图元拓扑为 “line-strip” 和 “triangle-strip” 时图元的 重新起算值

图元的重新起算值(Primitive Restart Value),指示应从哪里重新开始算一个图元,而不是继续使用先前索引过的顶点来构造三角带。

GPUPrimitiveState,如果其 "topology" 字段指定了 “line-strip” 或 “triangle-strip”,那么它的 "stripIndexFormat" 字段也需要相应地设置,以便管线创建时可以用到图元重新起算值。

如果用的是 “triangle-list”、”line-list” 和 “point-list”,那么 "stripIndexFormat" 要设为 undefined,并要使用渲染通道编码器(GPURenderPassEncoder)的 setIndexBuffer 方法来设置索引。

译者注

原文介绍 GPUIndexFormat 类型是在顶点着色阶段(Vertex State),笔者觉得此部分应摆在用到它的类型下更合适。

uint16 代表重新起算值是 2byte(0xFFFF),即 2byte 才取一个索引数字;uint32 表示重新起算值是 4byte(0xFFFFFFFF),即 4byte 才取一个索引数字。

2.2.2 如何验证图元拼装阶段对象合规性

如果下列条件都满足,那么图元拼装阶段对象就是没问题的:

  • 图元拼装阶段对象的 topology 字段是 “line-strip” 或 “triangle-strip”,那么 stripIndexFormat 不能是 undefined;topology 是其他的,stripIndexFormat 就必须是 undefined;
  • 图元拼装阶段对象的 clampDepth 是 true,那么设备的功能列表要包括 “depth-clamp” 功能

2.2.3 代码举例

const renderPipeline = device.createRenderPipeline({   /* ... */   primitive: {     topology: "triangle-list",   } })

2.3 顶点着色阶段

顶点着色阶段对象,是 GPURenderPipelineDescriptor 对象中 vertex 字段的值,是一个 JavaScript Object,要满足 GPUVertexState 类型(包括其父类型 GPUProgrammableStage):

dictionary GPUVertexState: GPUProgrammableStage {     sequence<GPUVertexBufferLayout?> buffers = []; };  dictionary GPUVertexBufferLayout {   required GPUSize64 arrayStride;   GPUVertexStepMode stepMode = "vertex";   required sequence<GPUVertexAttribute> attributes; };  enum GPUVertexStepMode {   "vertex",   "instance" };

GPUVertexState 对象需要一个 buffers 字段,是 GPUVertexBufferLayout 对象的数组。

GPUVertexBufferLayout 对象,需要两个必选参数:

  • arrayStride,unsigned longlong 类型,表示一个顶点包括的所有数据(坐标、颜色、法线、纹理坐标等)步幅有多大;
  • attributes,一个数组,元素类型是 GPUVertexAttribute 的对象,描述这块顶点数据中有多少个 顶点属性;

还有一个可选参数,GPUVertexStepMode 字符枚举类型的字段 stepMode,默认值是 "vertex";它表示如何访问顶点数据,它有两种值:

  • “vertex”,表示无论渲染通道编码器发出几次绘制(draw 方法的 instanceCount 参数无论是几)指令,都不会从 VertexBuffer 的头部再重新开始读取顶点数据,而是基于第一次读取的末尾继续往下读;
  • “instance”,表示即使渲染通道编码器发出绘制多次(draw 方法的 instanceCount 参数大于 1)的指令,在绘制完第一轮后(顶点着色器跑了一遍后),仍然从同一个 VertexBuffer 的起点开始获取顶点数据;

2.3.1 顶点缓存(VertexBuffer)与顶点属性(GPUVertexAttribute)

概念上说,顶点缓存是显存中顶点数据的一个描述视图,具体而言是一个数组,前一个数组元素和后一个数组元素之间的距离被称作 ArrayStride(单位:byte),也可以称之为元素的长度。

每一个元素,被称为一个顶点数据,它由若干个顶点属性(VertexAttribute)构成。每个顶点属性在顶点着色器中的 location 是独一无二的。

“` web-idl
dictionary GPUVertexAttribute {
required GPUVertexFormat format;
required GPUSize64 offset;
required GPUIndex32 shaderLocation;

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » WebGPU 规范篇 08 管线求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们