Directory portal: juejin.cn/post/701059…


Pass D.

Multiple Pipelines are allowed.

“> < span style =” box-sizing: border-box; word-break: inherit! Important; word-break: inherit! Important;

< span style = “box-sizing: border-box; word-break: inherit! Important; word-break: inherit! Important;” When the encoding is complete, the instruction encoder is told to complete the encoding of a channel encoder and the instruction encoder is submitted to the render queue.

There are two kinds of channels

GPUProgrammablePassEncoder
├ GPURenderPassEncoder
└ GPUComputePassEncoder
Copy the code

This paper introduces part of chapters 13, 14 and 15 of the original text.

Programmable channel encoder: GPUProgrammablePassEncoder

Referred to as channel encoder, it has two subtypes, rendering channel encoder GPURenderPassEncoder and computing channel encoder GPUComputePassEncoder.

The main function of the channel encoder is to arrange different pipelines and binding groups together according to the actual needs to complete a complete rendering frame or a complete calculation.

The main function of the channel encoder is to set (switch) the binding group, VBO, and set (switch) the pipeline. In addition, you can debug, but debugging is not the focus of this article. Interested readers can consult the documentation for themselves.

Create channel encoder

Created by an instruction encoder, as described in the creation of the two encoders below.

Set the binding group setBindGroup

This method is a feature of each specific channel encoder, with two overloads

  • setBindGroup(index, bindGroup, dynamicOffsets)
  • setBindGroup(index, bindGroup, dynamicOffsetsData, dynamicOffsetsDataStart, dynamicOffsetsDataLength)

The purpose is to pass the GPUBindGroup object bindGroup to the bindGroupLayout at index position.

Usually, only the first two parameters are used.

Render channel encoder

1.1 create

A render channel encoder can be created using the beginRenderPass method of the instruction coder (GPUCommandEncoder) :

const renderPassEncoder = commandEncoder.beginRenderPass({
  /* {}: GPURenderPassDescriptor */
})
Copy the code

The parameter object is of type GPURenderPassDescriptor, it cannot be empty and must be passed.

dictionary GPURenderPassDescriptor : GPUObjectDescriptorBase {
  required sequence<GPURenderPassColorAttachment> colorAttachments;
  GPURenderPassDepthStencilAttachment depthStencilAttachment;
  GPUQuerySet occlusionQuerySet;
};
Copy the code

It has a choice of colorAttachments array parameters, the array element type is GPURenderPassColorAttachment, said the color attachment, the array length and 8 > 0 or less; If 0, the depthStencilAttachment parameter cannot be null.

There are two optional parameters, GPURenderPassDepthStencilAttachment type depthStencilAttachment, said the depth texture attachment; GPUQuerySet occlusionQuerySet indicates some query information set.

1.1.1 Color Accessories

dictionary GPURenderPassColorAttachment {
  required GPUTextureView view;
  GPUTextureView resolveTarget;

  required (GPULoadOp or GPUColor) loadValue;
  required GPUStoreOp storeOp;
};
Copy the code

A color attachment is a result container for where the colors output by the render pipeline are stored.

The color attachment object, which is a parameter when the render channel encoder is created, is used to describe. It has three required fields:

  • view.GPUTextureViewType, which texture (view) object the color will be exported to;
  • loadValue.GPULoadOpGPUColorType, specifying the behavior or color at initialization;
  • storeOp.GPUStoreOpType that specifies the behavior of the view to be stored after the render channel execution ends

There is also an optional resolveTarget, of type GPUTextureView, which is used to receive the color output when the view is multi-sampled, in conjunction with the multi-sampled anti-aliasing technique. If multiple samples are used, the view is “mid-stream” and the resolveTarget is typically set to the Canvas texture view.

See the following for GPULoadOp and GPUStoreOp:

GPULoadOp: Initialization behavior

It is an enumeration of single-string values:

enum GPULoadOp {
  "load"
};
Copy the code

If “load” is specified for the loadValue of the color attachment, the initial color of the color attachment will be the color already on the color attachment when the rendering channel starts.

Note that on some devices (such as mobile), it is much better to use GPUColor to initialize loadValue.

GPUStoreOp: storage behavior

enum GPUStoreOp {
  "store",
  "discard"
};
Copy the code

“Store” indicates that the color is saved after the channel ends. “Discard” indicates that the color is discarded after the channel ends.

1.1.2 Deep Template Attachments

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;
};
Copy the code

The depth template attachment is the second largest part of the render channel and is responsible for saving the depth and template values output after the end of the channel.

The view argument acts like a color attachment, providing a container.

DepthLoadValue, stencilLoadValue and loadValue in the color attachment function similarly, the difference is different in the type.

DepthStoreOp, stencilStoreOp, and storeOp in color attachments are similar.

The two additional optional fields depthReadOnly and stencilReadOnly default to false to indicate whether the depth/template portion of the view texture view is read-only.

The compliance verification of the depth template attachment is omitted and not complicated. Interested readers can consult the specification document themselves.

1.1.3.

Render channels are very similar to the Framebuffer in WebGL.

1.2 Methods related to trigger drawing function

The following methods are on the render channel encoder.

SetPipeline method

Method to set (or switch to) the pipeline with a GPURenderPipeline object.

undefined setPipeline(GPURenderPipeline pipeline);
Copy the code

SetVertexBuffer method

This is how to set the VertexBuffer.

Method signature:

undefined setVertexBuffer(
  GPUIndex32 slot, 
  GPUBuffer buffer, 
  optional GPUSize64 offset = 0, 
  optional GPUSize64 size
);
Copy the code

The slot argument, referring to location in the vertex shader entry function, is mandatory and < maxVertexBuffers in the device object’s limiting list;

The usage attribute of the buffer parameter, that is, the VertexBuffer itself, must include VERTEX.

Offset refers to the byte from which the vertex data is taken. It must be a multiple of 4.

Size usually refers to the size of VertexBuffer in bytes. If not passed, the default is buffer. size-offset.

SetBindGroup method

This method is GPUProgrammalePassEncoder method on the parent types, usage is consistent, is the main binding group. See above for details [set binding group setBindGroup](# set binding group setBindGroup).

SetIndexBuffer method

This is the way to set index data, and the classic example is the quadrilateral of four points or the quadrilateral of six points.

Method signature:

undefined setIndexBuffer(
  GPUBuffer buffer, 
  GPUIndexFormat indexFormat, 
  optional GPUSize64 offset = 0, 
  optional GPUSize64 size
);
Copy the code

Note that the usage attribute of the buffer must include INDEX and offset must be a multiple of sizeof(indexFormat).

This method is used only when index data is used to index vertices.

The draw method

The draw method is signed as follows:

undefined draw(
  GPUSize32 vertexCount, 
  optional GPUSize32 instanceCount = 1,
  optional GPUSize32 firstVertex = 0, 
  optional GPUSize32 firstInstance = 0
);
Copy the code

VertexCount is how many vertices to draw, instanceCount is how many times to draw these vertices, firstVertex is how many times to draw, firstInstance is how many times to draw.

InstanceCount affects the built-in variable instance_index in WGSL, from which you can get the current number of draws to select different parameters to draw.

This method is similar to WebGL’s GL.drawarray method.

1.3 Other Methods

There are other methods on the render channel encoder, not to mention all of them, but the reader who can afford them is advised to consult the documentation when they are used.

1.4 Ending Channel Coding

After completing the task arrangement and data setting of the render channel encoder, call its endPass method to end the coding task of the encoder. Once this method is called, the render channel encoder is no longer available.

1.5 Common Processes

// Start (create) the channel
const renderPassEncoder = commandEncoder.beginRenderPass()

// First draw
renderPassEncoder.setPipeline(renderPipeline_0)
renderPassEncoder.setBindGroup(0, bindGroup_0)
renderPassEncoder.setBindGroup(1, bindGroup_1)
renderPassEncoder.setVertexBuffer(0, vbo, 0, size)
renderPassEncoder.draw(vertexCount)

// Second draw
renderPassEncoder.setPipeline(renderPipeline_1)
renderPassEncoder.setBindGroup(1, another_bindGroup)
renderPassEncoder.draw(vertexCount)

// End channel encoding
renderPassEncoder.endPass()
Copy the code

2 Calculate the channel encoder

Computational channel encoder can combine multiple computational pipelines to complete a complex computational task.

The interface type is:

[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;
Copy the code

In addition to the setPipeline method similar to the render channel encoder, the most commonly used methods are Dispatch and endPass. The role of other methods belongs to the more advanced content. If necessary, readers can consult the document by themselves.

2.1 create

A compute channel encoder can be created using the beginComputePass of the instruction encoder:

const computePassEncoder = commandEncoder.beginComputePass({
  / * {}? : GPUComputePassDescriptor */
})
Copy the code

The parameter object of type GPUComputePassEncoder is optional, you don’t have to pass it.

Of course, this type has nothing:

dictionary GPUComputePassDescriptor : GPUObjectDescriptorBase {
};
Copy the code

2.2 Dispatch

As important as the draw method for rendering pipelines is the function that triggers the execution of the pipeline’s custom calculations. Of course, before executing this function, the calculation channel encoder’s setPipeline method is called to specify the calculation pipeline.

Dispatch is an operation in WebGPU general-purpose computing. It encodes some computation instructions and causes the computation shader to calculate a certain piece of data.

It takes three arguments: x, y, and z, of which X must be passed. The other two are optional numbers of type unsigned long.

They mean how many calls to compute the shader entry function (called the kernel function at this point) are made on the three dimensions.

.

One might wonder, how many times is it scheduled and why is it not a single parameter? This involves calculating an attribute-workgroup_size in the shader entry.

Workgroup_size Specifies the number of GPU cores to apply for computing in the three dimensions, which represents the computing capacity of the core function.

It is designed in three dimensions to facilitate general computation with matrices, vectors, and multidimensional textures.

For example, if the number of GPU cores in three dimensions specified by workgroup_size is (4, 4, 3), there are 48 cores in total.

So how to dispatch?

It depends on the size of your data. If your data is a three-channel texture image, its size is 256×256. If the red, green and blue components are the z dimensions, then its size can be expressed as 256×256×3. You will calculate it using the workgroup_size(4, 4, 3) size kernel function above. Obviously not enough in the X and y dimensions, just enough in the Z dimension. Therefore, The Times of scheduling at this time should be

  • x = 256 / 4 = 64
  • y = 256 / 4 = 64
  • z = 3 / 3 = 1

Of course, you can also expand workgroup_size, depending on the limits in your device object limits list.

So how do you know in a shader which core the kernel goes to? The built-in global_invocation_id variable is a three-dimensional vector whose three component values tell the core function which one of the workgroup_size applications is currently running in.

Due to the characteristics of parallel computing, if the dispatch number in each direction exceeds 1, the kernel function does not know the current scheduling times, so it needs to carefully design parallel universal computing (I hope I can face my own criticism after reading WGSL).

2.3 Ending Channel Coding

As with the rendering channel encoder, the encoding is done by calling the endPass method of the calculation channel encoder. Once the encoding is complete, the compute channel encoder object is no longer available for setting pipelines, binding resources, and so on.