关于gpu:WebGPU-规范篇-08-管线

3次阅读

共计 18868 个字符,预计需要花费 48 分钟才能阅读完成。

系列博客总目录:https://segmentfault.com/a/1190000040716735


https://www.w3.org/TR/webgpu/…

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 是举世无双的。

dictionary GPUVertexAttribute {
  required GPUVertexFormat format;
  required GPUSize64 offset;
  required GPUIndex32 shaderLocation;
};

在 GPUVertexBufferLayout 对象的 attributes 数组中,每个元素即 GPUVertexAttribute 对象。

每个 GPUVertexAttribute 对象有三个必选参数:

  • formatGPUVertexFormat 字符枚举类型,见下文 顶点格局,确定某种顶点属性的数字类型、元素组成状况;
  • offset,unsigned longlong 类型,指定它在每块顶点数据中的偏移量,单位 byte;
  • shaderLocation,unsigned long 类型,指定它在 WGSL 顶点着色器中的 location 号。

2.3.2 顶点格局 GPUVertexFormat

enum GPUVertexFormat {
  "uint8x2",
  "uint8x4",
  "sint8x2",
  "sint8x4",
  "unorm8x2",
  "unorm8x4",
  "snorm8x2",
  "snorm8x4",
  "uint16x2",
  "uint16x4",
  "sint16x2",
  "sint16x4",
  "unorm16x2",
  "unorm16x4",
  "snorm16x2",
  "snorm16x4",
  "float16x2",
  "float16x4",
  "float32",
  "float32x2",
  "float32x3",
  "float32x4",
  "uint32",
  "uint32x2",
  "uint32x3",
  "uint32x4",
  "sint32",
  "sint32x2",
  "sint32x3",
  "sint32x4",
};

下面 WebIDL 中顶点格局 GPUVertexFormat 枚举的每一个值中,有按顶点的组件类型(二三四维)、每个维度的数字类型两局部信息,其中有一些简写:

  • unorm = 无符号归一化 u(unsigned)+ 归一化 norm(normalized)
  • snorm = 有符号归一化 s(signed)+ 归一化 norm(normalized)
  • uint = 无符号整型
  • sint = 有符号整型
  • float = 浮点数

以常见的 “float32x3” 为例(留神那个 x 是小写字母的 X),示意某个顶点属性的数值类型是三个 float32 数字(不肯定是坐标,也能够是其余的顶点属性(Vertex Attribute))。

2.3.3 代码举例

const renderPipeline = device.createRenderPipeline({
  /* ... */
  vertex: {
    module: device.createShaderModule({code: ` /* wgsl vertex shader code */ `,}),
    entryPoint: 'vertex_main',
    buffers: [
      {
        arrayStride: 4 * 5, // 一个顶点数据占 20 bytes
        attributes: [
          {
            // for Position VertexAttribute
            shaderLocation: 0,
            offset: 0,
            format: "float32x3" // 其中顶点的坐标属性占 12 字节,三个 float32 数字
          },
          {
            // for UV0 VertexAttribute
            shaderLocation: 1,
            offset: 3 * 4,
            format: "float32x2" // 顶点的纹理坐标占 8 字节,两个 float32 数字
          }
        ]
      }
    ]
  }
})

下面例子:

  • 用到的 GPUVertexFormat 有 “float32x3″、”float32x2″;
  • 一个顶点数据(块)长 20 bytes,即从一个顶点数据的头部返回下一个顶点数据的头部,须要 “stride” 20 个字节;
  • 对于排列在这块 VertexBuffer 中的某个顶点数据块中,有两个 GPUVertexAttribute,一个是坐标,紧接着第二个是二维纹理坐标,所以纹理坐标的数据应在顶点数据块的第 12 个字节开始起算(0~11 字节是坐标的三个 float32 数字)

2.3.4 如何验证顶点缓存布局对象 GPUVertexBufferLayout 的合规性

设某个 GPUVertexBufferLayout 对象为 layout,当其合乎以下条件即可:

  • layout.arrayStride ≤ 设施限度列表中的 maxVertexBufferArrayStride,且为 4 的倍数
  • 对于 layout.attributes 中的每个元素,令其为 attrib:

    • 若 layout.arrayStride 是 0,则:attrib.offset + sizeof(attrib.format) ≤ 设施限度列表中的 maxVertexBufferArrayStride
    • 否则 attrib.offset + sizeof(attrib.format) ≤ layout.arrayStride
    • attrib.offset 至多是 min(4, sizeof(attrib.format)) 的倍数
    • attrib.shaderLocation < 设施限度列表中的 maxVertexAttributes
  • 对于顶点着色器代码中入口函数的每个顶点属性,layout.attributes 中的每个 GPUVertexAttribute 元素对象均要满足:

    • 着色器代码中的顶点属性变量类型,与 attrib.format 相比:

      • 当 attrib.format 是 “unorm”,”snorm” 或 “float” 其中的一种时,着色器代码中变量类型要是 vecN<f32>f32
      • 当 attrib.format 是 “uint” 其中的一种时,着色器代码中变量类型要是 vecN<u32>u32
      • 当 attrib.format 是 “sint” 其中的一种时,着色器代码中变量类型要是 vecN<i32>i32
    • 着色器中顶点属性的 location 个性值要等于 attrib.shaderLocation

2.3.5 如何验证顶点着色阶段对象 GPUVertexState 的合规性

设某个 GPUVertexState 对象为 state,当其合乎以下条件即可:

  • state.buffers.length ≤ 设施限度列表中的 maxVertexBuffers
  • state.buffers 中每个元素均要通过 顶点缓存布局对象的合规性验证
  • state.buffers 数组的所有元素的 attributes 个数之和 ≤ 设施限度列表中 maxVertexAttributes
  • 所有 GPUVertexAttribute 顶点属性对象的 shaderLocation 要惟一

2.4 片元着色阶段

片元着色阶段对象,是 GPURenderPipelineDescriptor 对象中 fragment 字段的值,是一个 JavaScript Object,要满足 GPUFragmentState 类型(包含其父类型 GPUProgrammableStage):

dictionary GPUFragmentState: GPUProgrammableStage {required sequence<GPUColorTargetState> targets;};
  • 参数 targets 是元素为 GPUColorTargetState 类型的对象的数组,是一个必须存在的参数,见 2.6 色彩输入阶段

如何验证片元着色阶段对象的合规性

设片元着色阶段 GPUFragmentState 对象为 state,其满足如下要求即可:

  • state.targets.length ≤ 8
  • state.targets 中每个元素,称之为 colorState:

    • colorState.format 必须是 WebGPU Spec 24.1.1 纯色格局(Plain color formats)中标为 RENDER_ATTACHMENT 的一种
    • 若 colorState.blend 是 undefined:

      • colorState.format 要在 WebGPU Spec 24.1.1 纯色格局(Plain color formats)当选
      • colorState.blend.color、colorState.blend.alpha 必须是无效的
    • colorState.writeMask ≤ 16
    • 片元着色器入口函数有与 state.targets 中相匹配的输入 location,管线输入对象的类型要与 colorState.format 绝对应,否则 colorState.writeMask 必须是 0

colorState.blend.color、colorState.blend.alpha 如何说是无效的?令它俩为 component,若 component.operation 是 “min” 或 “max”,那么 component.srcFactor 和 component.dstFactor 都必须是 “one” 即可。

举例

const renderPipeline = device.createRenderPipeline({
  /* ... */
  fragment: {
    module: device.createShaderModule({code: `/* wgsl fragment shader source code */`,}),
    entryPoint: "fragment_main",
    targets: [{format: "bgra8unorm"}]
  }
})

2.5 深度模板测试阶段

深度模板测试阶段对象,是 GPURenderPipelineDescriptor 对象中 depthStencil 字段的值,是一个 JavaScript Object,要满足 GPUDepthStencilState 类型:

dictionary GPUDepthStencilState {
  required GPUTextureFormat format;

  boolean depthWriteEnabled = false;
  GPUCompareFunction depthCompare = "always";

  GPUStencilFaceState stencilFront = {};
  GPUStencilFaceState stencilBack = {};

  GPUStencilValue stencilReadMask = 0xFFFFFFFF;
  GPUStencilValue stencilWriteMask = 0xFFFFFFFF;

  GPUDepthBias depthBias = 0;
  float depthBiasSlopeScale = 0;
  float depthBiasClamp = 0;
};

dictionary GPUStencilFaceState {
  GPUCompareFunction compare = "always";
  GPUStencilOperation failOp = "keep";
  GPUStencilOperation depthFailOp = "keep";
  GPUStencilOperation passOp = "keep";
};

enum GPUStencilOperation {
  "keep",
  "zero",
  "replace",
  "invert",
  "increment-clamp",
  "decrement-clamp",
  "increment-wrap",
  "decrement-wrap"
};

因为官网这部分没有文字介绍,到后续有例子学习时再专门补充。

如何验证深度模板测试阶段对象的合规性

对于深度模板测试阶段 GPUDepthStencilState 对象,令其为 state,只需满足:

  • state.format 是 WebGPU Spec 24.1.2 深度模板格局(Depth/stencil format)中的一个;
  • 若 state.depthWriteEnabled 是 true,或者 state.depthCompare 不是 “always”,那么 state.format 必须是有深度局部的格局
  • 若 state.stencilFront 或 state.stencilBack 不是默认值,那么 state.format 必须是有模板局部的格局

2.6 色彩输入阶段

色彩输入阶段对象,GPUColorTargeState 类型,是 GPURenderPipelineDescriptor 对象中 fragment 字段中 targets 这个数组字段中元素的类型。

dictionary GPUColorTargetState {
  required GPUTextureFormat format;

  GPUBlendState blend;
  GPUColorWriteFlags writeMask = 0xF;  // GPUColorWrite.ALL
};

子类型:

dictionary GPUBlendState {
  required GPUBlendComponent color;
  required GPUBlendComponent alpha;
};

typedef [EnforceRange] unsigned long GPUColorWriteFlags;
[Exposed=(Window, DedicatedWorker)]
namespace GPUColorWrite {
  const GPUFlagsConstant RED   = 0x1;
  const GPUFlagsConstant GREEN = 0x2;
  const GPUFlagsConstant BLUE  = 0x4;
  const GPUFlagsConstant ALPHA = 0x8;
  const GPUFlagsConstant ALL   = 0xF;
};

混合模式

dictionary GPUBlendComponent {
  GPUBlendOperation operation = "add";
  GPUBlendFactor srcFactor = "one";
  GPUBlendFactor dstFactor = "zero";
};

enum GPUBlendFactor {
  "zero",
  "one",
  "src",
  "one-minus-src",
  "src-alpha",
  "one-minus-src-alpha",
  "dst",
  "one-minus-dst",
  "dst-alpha",
  "one-minus-dst-alpha",
  "src-alpha-saturated",
  "constant",
  "one-minus-constant"
};

enum GPUBlendOperation {
  "add",
  "subtract",
  "reverse-subtract",
  "min",
  "max"
};

因为官网这部分没有文字介绍,到后续有例子学习时再专门补充。

2.7 多重采样阶段

色彩输入阶段对象,GPUMultisampleState 类型,是 GPURenderPipelineDescriptor 对象中 multisample 字段的值。

dictionary GPUMultisampleState {
  GPUSize32 count = 1;
  GPUSampleMask mask = 0xFFFFFFFF;
  boolean alphaToCoverageEnabled = false;
};
  • 参数 count,采样次数,只能是 1 或者 4,默认值是 1;
  • 参数 mask,采样遮罩值,类型是 unsigned long,默认值是 0xFFFFFFFF
  • 参数 alphaToCoverageEnabled,默认值是 false。

前面两个值,在 WebGPU Spec 21.3.10 采样遮罩 中会介绍它们的用处,采样遮罩值是 0 的片元将不会输入色彩,它由三个遮罩变量取逻辑与形成,其中一个 shader-output-mask 由片元着色器中的内置变量 sample_mask 决定,而这个内置变量又由此处的 alphaToCoverageEnabled、mask 值独特作用而成,此处为简略形容。

如何验证多重采样阶段对象的合规性

alphaToCoverageEnabled 若为 true,count 要大于 1.

代码举例

const renderPipeline = device.createRenderPipeline({
  /* ... */
  multisample: {count: 4}
})

2.8 综合代码举例

const renderPipeline = device.createRenderPipeline({layout: [/* GPURenderPipelineLayout[] */],
  vertex: {/* {}: GPUVertexState */ },
  fragment: {/* {}: GPUVertexState */ },
  primitive: {/* {}: GPUPrimitiveState */ },
  multisample: {/* {}: GPUMultisampleState */ }
})

3 计算管线

计算管线,GPUComputePipeline 类型,是一种能在计算通道编码器 GPUComputePassEncoder 中应用的管线,实现某个通用计算的过程。

计算管线的输出和输入在绑定组中绑定好了,并通过 GPUPipelineLayout 提供(详见资源绑定章节)。

它不像渲染管线一样有特地具体的输入后果,它的后果,将写入绑定组中 type 是 "storage" 的 buffer 和 type 是 "write-only" 的 storageTexture 的两种资源。

计算管线的阶段只有一个:计算着色器。

上面是计算管线的接口定义:

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

计算管线的创立

设施对象的 createComputePipeline 能够创立一个计算管线,它须要一个 GPUComputePipelineDescriptor 类型的 JavaScript 对象作为参数。

dictionary GPUComputePipelineDescriptor : GPUPipelineDescriptorBase {required GPUProgrammableStage compute;};

GPUComputePipelineDescriptor 类型继承自 GPUPipelineDescriptorBase 类型,意味着也要遵循父类型的字段规定。

也能够通过设施对象的 createComputePipelineAsync 办法异步创立计算管线,语法与异步创立渲染管线一样,参数与同步创立计算管线一样。

如何验证 GPUComputePipelineDescriptor 参数对象的合规性

令 GPUComputePipelineDescriptor 对象名为 descriptor。

  • descriptor.layout 与设施对象必须是无效的
  • 验证计算着色阶段对象的可编程属性是否有问题
  • descriptor.compute 中计算着色器用到的工作组大小要不大于设施对象限度列表中的 maxComputeWorkgroupStorageSize 值,每个工作组用到的 invocation 要不大于设施对象限度列表中 maxComputeInvocationsPerWorkgroup 值;
  • descriptor.compute 中计算着色器的 workgroup_size 个性的三个重量值都不能大于设施对象限度列表中的 maxComputeWorkgroupSizeXmaxComputeWorkgroupSizeYmaxComputeWorkgroupSizeZ 的值;

以上均通过的话,则能够返回一个正确的计算管线对象。

代码举例

const computePipeline = device.createComputePipeline({
  compute: {
    module: device.createShaderModule({code: `/* wgsl compute shader source code */`}),
    entryPoint: "main",
  }
})

4 译者注

这部分较长,有一部分我还没有实际,学习、翻译起来比拟吃力。

然而稀释起来,提炼骨干看,只讲了上面四件事:

  • 管线是什么,输出和输入是什么货色,如何创立的,有什么类型的管线
  • 管线是由阶段形成的,其中有三个阶段(顶点着色、片元着色、通用计算)是可编程阶段
  • 管线配套的绑定组布局、顶点缓存布局与着色器代码之间的坑位的连贯规定
  • 顶点着色阶段中十分重要的概念:VertexBuffer 和顶点属性

管线是 WebGPU 中执行一个残缺的计算(或渲染,或通用计算)的最小单元,他有着本人的输出和输入。

你能够通过下一篇文章的通道编码器向其传递不同的 VertexBuffer 和 资源绑定组,然而通道编码器的作用可不仅仅是向管线设置数据那么简略,下文见。

正文完
 0