系列博客总目录:https://segmentfault.com/a/1190000040716735
Pass,通道。
通道容许应用多个 Pipelines。
Pipelines 容许切换不同的资源(bindGroup、VBO)。
通道在设置好 Pipelines(setPipeline)和资源绑定(setVertexBuffer、setBindGroup)、绘制命令(draw)触发之后,要进行编码(endPass),编码实现即可通知指令编码器实现一个通道编码器的编码,并将指令编码器提交到渲染队列。
通道有两种
GPUProgrammablePassEncoder
├ GPURenderPassEncoder
└ GPUComputePassEncoder
本文介绍原文 13、14、15 章的局部内容。
可编程通道编码器:GPUProgrammablePassEncoder
简称为通道编码器,它有两种子类型,渲染通道编码器 GPURenderPassEncoder
和计算通道编码器 GPUComputePassEncoder
。
通道编码器的次要性能就是将不同的 pipeline 和绑定组依据理论须要排列组合在一起,实现一帧残缺的渲染或一次残缺的计算。
通道编码器的次要性能是,设置(切换)绑定组、VBO 和设置(切换)管线。除此之外,还能够进行调试,然而调试并不作为此文关注的内容,有趣味的读者能够自行查阅文档。
创立通道编码器
由某个指令编码器创立,具体见下文两种编码器的创立大节。
设置绑定组 setBindGroup
此办法是每一种具体的通道编码器都具备的性能,有两种重载
- setBindGroup(index, bindGroup, dynamicOffsets)
- setBindGroup(index, bindGroup, dynamicOffsetsData, dynamicOffsetsDataStart, dynamicOffsetsDataLength)
其目标都是向位于 index
地位的 bindGroupLayout 传递 GPUBindGroup 对象 bindGroup
,前面的动静偏移值、动静偏移数据等高级用法见官网文档。
通常,只用到后面两个参数即可。
1 渲染通道编码器
1.1 创立
应用指令编码器(GPUCommandEncoder)的 beginRenderPass
办法即可创立一个渲染通道编码器:
const renderPassEncoder = commandEncoder.beginRenderPass({/* {}: GPURenderPassDescriptor */
})
参数对象是 GPURenderPassDescriptor
类型的,它不能是空且必须传递。
dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase {
required sequence<GPURenderPassColorAttachment> colorAttachments;
GPURenderPassDepthStencilAttachment depthStencilAttachment;
GPUQuerySet occlusionQuerySet;
};
它有一个必选的数组参数 colorAttachments
,数组元素类型是 GPURenderPassColorAttachment
,示意其色彩附件,此数组长度≤8 且>0;若为 0,那么深度模板附件参数 depthStencilAttachment
不能为 null。
还有两个可选参数,GPURenderPassDepthStencilAttachment
类型的 depthStencilAttachment
,示意其深度纹理附件;GPUQuerySet
类型的 occlusionQuerySet
,示意一些查问无关的信息集。
1.1.1 色彩附件
dictionary GPURenderPassColorAttachment {
required GPUTextureView view;
GPUTextureView resolveTarget;
required (GPULoadOp or GPUColor) loadValue;
required GPUStoreOp storeOp;
};
色彩附件,是指渲染管线输入的色彩保留到什么中央去的一种后果容器。
而色彩附件对象,是在渲染通道编码器创立时的一个参数,用于形容。它有三个必选字段:
view
,GPUTextureView
类型,指色彩最终要输入到哪个纹理(视图)对象上;loadValue
,GPULoadOp
或GPUColor
类型,指定初始化时的行为或色彩;storeOp
,GPUStoreOp
类型,指定渲染通道执行完结后对 view 的存储行为
还有一个可选参数 resolveTarget
,GPUTextureView
类型,它用来接管当 view 应用了多重采样时的色彩输入后果,即配合多重采样抗锯齿技术应用。若设置了 resolveTarget
,即应用了多重采样,那么 view 是“中途的”,而 resolveTarget 个别会设为 Canvas 的纹理视图。
GPULoadOp、GPUStoreOp 两个类型见下:
GPULoadOp:初始化行为
它是一个单字符串值枚举:
enum GPULoadOp {"load"};
如果为色彩附件的 loadValue 指定了 “load”,那么渲染通道开始时,色彩附件的初始色彩会用色彩附件上已有的色彩。
留神,在某些设施(譬如挪动端)上,对 loadValue 应用 GPUColor
来初始化会好得多。
GPUStoreOp:存储行为
enum GPUStoreOp {
"store",
"discard"
};
“store” 示意通道完结后,色彩保留,”discard” 示意通道完结后,色彩抛弃。
1.1.2 深度模板附件
dictionary GPURenderPassDepthStencilAttachment {
required GPUTextureView view;
required (GPULoadOp or float) depthLoadValue;
required GPUStoreOp depthStoreOp;
boolean depthReadOnly = false;
required (GPULoadOp or GPUStencilValue) stencilLoadValue;
required GPUStoreOp stencilStoreOp;
boolean stencilReadOnly = false;
};
深度模板附件是渲染通道的第二大部分,负责保留通道完结后输入的深度和模板值。
view
参数与色彩附件的作用相似,提供容器。
depthLoadValue
、stencilLoadValue
与色彩附件中的 loadValue
作用也相似,区别在类型不大一样。
depthStoreOp
、stencilStoreOp
与色彩附件中的 storeOp
也是相似的。
多进去两个可选字段 depthReadOnly
、stencilReadOnly
默认值是 false,它们的意思是批示 view
这个纹理视图中,深度 / 模板的局部是否是只读的。
对深度模板附件的合规性校验省略,也不简单,有趣味的读者可本人到标准文档中查阅。
1.1.3 译者注
渲染通道与 WebGL 中的 Framebuffer 十分类似。
1.2 触发绘制性能相干的办法
以下办法均在渲染通道编码器上。
setPipeline 办法
设置(或者说切换到也行)管线的办法,参数是一个 GPURenderPipeline
对象。
undefined setPipeline(GPURenderPipeline pipeline);
setVertexBuffer 办法
这个是设置顶点数据(VertexBuffer)的办法。
办法签名:
undefined setVertexBuffer(
GPUIndex32 slot,
GPUBuffer buffer,
optional GPUSize64 offset = 0,
optional GPUSize64 size
);
slot
参数,指的是顶点着色器入口函数中的 location,必选,且maxVertexBuffers;
buffer
参数,即 VertexBuffer 自身,其 usage
属性要包含 VERTEX
,必选;
offset
指的是从哪个字节开始取顶点数据,必须是 4 的倍数;
size
通常就是指 VertexBuffer 的字节大小了,如果不传递,那就默认是 buffer.size - offset
。
setBindGroup 办法
这个办法是 GPUProgrammalePassEncoder
父类型上的办法,用法上是统一的,次要就是传递绑定组。具体见上文 设置绑定组 setBindGroup。
setIndexBuffer 办法
这个是设置索引数据用的办法,最经典的例子就是四个点形成一个四边形或者六个点形成一个四边形的问题。
办法签名:
undefined setIndexBuffer(
GPUBuffer buffer,
GPUIndexFormat indexFormat,
optional GPUSize64 offset = 0,
optional GPUSize64 size
);
须要留神,这个 buffer
的 usage
属性必须包含 INDEX
,而且 offset
必须是 sizeof(indexFormat) 的倍数。
只有用到索引数据来索引顶点时,才会用到这个办法。
draw 办法
draw 办法的签名如下:
undefined draw(
GPUSize32 vertexCount,
optional GPUSize32 instanceCount = 1,
optional GPUSize32 firstVertex = 0,
optional GPUSize32 firstInstance = 0
);
它用于触发绘制图元,vertexCount
即要绘制多少个顶点,instanceCount
示意要把这些顶点绘制多少次,firstVertex
示意从第几个开始绘制,firstInstance
示意从第几次开始绘制。
instanceCount
会影响到 WGSL 中的内置变量 instance_index
,你能够从这个内置变量获取以后绘制的次数,从而抉择不同的参数进行绘制。
这个办法与 WebGL 的 gl.drawArray 办法相似。
1.3 其余办法
渲染通道编码器上还有其余办法,不一一列举阐明,有余力的读者倡议在用到的时候查阅文档。
1.4 完结通道编码
实现对渲染通道编码器的工作安顿和数据设置后,调用其 endPass
办法完结此编码器的编码工作。一旦调用此办法,渲染通道编码器将不能再应用。
1.5 常见流程
// 启动(创立)通道
const renderPassEncoder = commandEncoder.beginRenderPass()
// 第一道绘制
renderPassEncoder.setPipeline(renderPipeline_0)
renderPassEncoder.setBindGroup(0, bindGroup_0)
renderPassEncoder.setBindGroup(1, bindGroup_1)
renderPassEncoder.setVertexBuffer(0, vbo, 0, size)
renderPassEncoder.draw(vertexCount)
// 第二道绘制
renderPassEncoder.setPipeline(renderPipeline_1)
renderPassEncoder.setBindGroup(1, another_bindGroup)
renderPassEncoder.draw(vertexCount)
// 完结通道编码
renderPassEncoder.endPass()
2 计算通道编码器
计算通道编码器能够组合多个计算管线实现一个简单的计算工作。
其接口类型是:
[Exposed=(Window, DedicatedWorker), SecureContext]
interface GPUComputePassEncoder {undefined setPipeline(GPUComputePipeline pipeline);
undefined dispatch(GPUSize32 x, optional GPUSize32 y = 1, optional GPUSize32 z = 1);
undefined dispatchIndirect(GPUBuffer indirectBuffer, GPUSize64 indirectOffset);
undefined beginPipelineStatisticsQuery(GPUQuerySet querySet, GPUSize32 queryIndex);
undefined endPipelineStatisticsQuery();
undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex);
undefined endPass();};
GPUComputePassEncoder includes GPUObjectBase;
GPUComputePassEncoder includes GPUProgrammablePassEncoder;
除了与渲染通道编码器相似的 setPipeline
办法之外,最罕用的办法是 dispatch
和 endPass
,其余办法的作用属于比拟高级的内容,如有须要的读者可自行查阅文档,本节会着重介绍 dispatch
办法。
2.1 创立
应用指令编码器的 beginComputePass
即可创立一个计算通道编码器:
const computePassEncoder = commandEncoder.beginComputePass({/* {}?: GPUComputePassDescriptor */
})
参数对象的类型是 GPUComputePassEncoder
是可选的,你能够不传递。
当然,这个类型啥都没有:
dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase {};
2.2 调度(dispatch)
与渲染管线的 draw
办法位置一样,都是触发执行管线定制好的计算的重要函数。当然在执行这个函数之前,要调用计算通道编码器的 setPipeline
办法来指定计算管线。
dispatch
,调度,是 WebGPU 通用计算中的一种操作,它负责编码一些计算指令,让计算着色器对某一块数据进行计算。
它有三个参数:x、y、z,其中 x 必须传递,其余两个是可选的,均为 unsigned long 类型的数字。
它们的含意是,在三个维度上进行多少次计算着色器入口函数(此时,入口函数被称为核函数)的调用。
译者注
有人可能会纳闷,调度多少次为什么不是单个参数?这就波及到计算着色器入口中的一个 attribute – workgroup_size
了。
workgroup_size
指定三个维度上要申请多少个 GPU 外围进行运算,这代表了核函数的运算能力大小。
设计成三个维度的,是为了不便与矩阵、向量、多维纹理这些对象进行通用计算。
举例,若 workgroup_size
指定三个维度的 GPU 外围数是 (4, 4, 3)
,那么一共有 4×4×3 = 48 个外围参加计算。
那么此时如何进行调度(dispatch)呢?
这取决于你的数据尺寸。如果你的数据是一个三通道的纹理图像,它的尺寸是 256×256,如果红绿蓝重量作为 z 维度的话,那么它的尺寸能够示意成 256×256×3,你要用上述 workgroup_size(4, 4, 3)
尺寸的核函数进行计算,显然在 x 和 y 维度上不够,在 z 维度上刚刚好。所以,此时调度的次数应为
- x = 256 / 4 = 64
- y = 256 / 4 = 64
- z = 3 / 3 = 1
当然,你也能够扩充 workgroup_size
,这取决于你的设施对象限度列表中无关的限度值。
那么在着色器中如何通晓核函数跑在哪一个外围呢?其内置变量 global_invocation_id
是一个三维向量,它的三个重量值就通知核函数以后跑在 workgroup_size
申请的外围中的哪一个。
因为并行运算的特点,如果各个方向上的 dispatch
数超过 1,那核函数是不晓得以后的调度次数的,所以须要认真设计并行通用运算这一点(心愿我浏览 WGSL 后本人打脸)。
2.3 完结通道编码
和渲染通道编码器一样,通过调用计算通道编码器的 endPass
办法实现编码。一旦实现编码,这个计算通道编码器对象将不再可用,即不能再进行设置管线、绑定资源等操作。