Directory portal: juejin.cn/post/701059…


Chapter 11, part 12 and chapter 17 of the original text are introduced.

1 Instruction cache: GPUCommandBuffer

An instruction buffer (also known as a command buffer), GPUCommandBuffer, is a storage container that can store GPU instructions in advance. It can be submitted to the GPUQueue for execution. Each GPU instruction represents a task to be performed by the GPU, which can be drawing, setting up data, copying resources, etc.

[Exposed=(Window, DedicatedWorker), SecureContext]
interface GPUCommandBuffer {
  readonly attribute Promise<double> executionTime;
};
GPUCommandBuffer includes GPUObjectBase;
Copy the code

It has a Promise with a double resolve value, which is the execution time of the pre-stored GPU instruction on the instruction cache.

If the measureExecutionTime of an instruction encoder is true when it is created, and the Promise is reject when it is false, you can catch an OperationError.

How to create

Call the Finish method of [instruction encoder](#2 instruction encoder: GPUCommandEncoder) to get the instruction cache object. It is generally used to submit to a queue:

device.queue.submit([
  commandEncoder.finish()
])
Copy the code

2 Instruction coder: GPUCommandEncoder

Purpose: ① create channel encoder; ② Duplicate GPUBuffer/GPUTexture; ③ Debugging, query and other functions (omitted)

This paper mainly introduces the common ①, ② two functions.

Note that after the instruction encoder is used up and submitted to the queue, it is no longer available.

Instruction encoders in Metal

Its WebIDL definition is as follows:

[Exposed=(Window, DedicatedWorker), SecureContext]
interface GPUCommandEncoder {
    GPURenderPassEncoder beginRenderPass(GPURenderPassDescriptor descriptor);
    GPUComputePassEncoder beginComputePass(optional GPUComputePassDescriptor descriptor = {});

    undefined copyBufferToBuffer(
        GPUBuffer source,
        GPUSize64 sourceOffset,
        GPUBuffer destination,
        GPUSize64 destinationOffset,
        GPUSize64 size);

    undefined copyBufferToTexture(
        GPUImageCopyBuffer source,
        GPUImageCopyTexture destination,
        GPUExtent3D copySize);

    undefined copyTextureToBuffer(
        GPUImageCopyTexture source,
        GPUImageCopyBuffer destination,
        GPUExtent3D copySize);

    undefined copyTextureToTexture(
        GPUImageCopyTexture source,
        GPUImageCopyTexture destination,
        GPUExtent3D copySize);

    undefined pushDebugGroup(USVString groupLabel);
    undefined popDebugGroup();
    undefined insertDebugMarker(USVString markerLabel);

    undefined writeTimestamp(GPUQuerySet querySet, GPUSize32 queryIndex);

    undefined resolveQuerySet(
        GPUQuerySet querySet,
        GPUSize32 firstQuery,
        GPUSize32 queryCount,
        GPUBuffer destination,
        GPUSize64 destinationOffset);

    GPUCommandBuffer finish(optional GPUCommandBufferDescriptor descriptor = {});
};
GPUCommandEncoder includes GPUObjectBase;
Copy the code

2.1 How can I Create a Vm?

Created by the device object’s createCommandEncoder method

const commandEncoder = device.createCommandEncoder()
Copy the code

It has an optional parameter Object, type is GPUCommandEncoderDescriptor, a JavaScript Object:

dictionary GPUCommandEncoderDescriptor : GPUObjectDescriptorBase {
  boolean measureExecutionTime = false;
};
Copy the code

Its purpose was described in the instruction cache creation section, and the optional attribute measureExecutionTime indicates whether the running time of the instruction can be measured.

2.2 Purpose: Start/create a programmable channel

BeginRenderPass and beginComputePass of the instruction encoder can start/create a rendering channel or a calculation channel respectively. The return values of these two methods are naturally GPURenderPassEncoder and GPUComputePassEncoder.

// Render channel encoder
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor)

// Calculate channel encoder
const computeEncoder = commandEncoder.beginComputePass()
Copy the code

See the channel encoder article for details of the parameter object renderPassDescriptor required by the channel encoder.

The instruction encoder is not responsible for the termination of the channel encoder, but rather the termination of the channel encoder itself.

2.2 Purpose: Cache Replication

The copyBufferToBuffer method is used for copying between GPUBuffers.

undefined copyBufferToBuffer(
  GPUBuffer source,
  GPUSize64 sourceOffset,
  GPUBuffer destination,
  GPUSize64 destinationOffset,
  GPUSize64 size
);
Copy the code

That is, copy from source to destination. In the article Buffer, there is a brief mention of the difference between this method and direct map/unmap to assign data, that is, this method is operated on GPU. Map /unmap writes data directly to the GPUBuffer on the CPU side.

For example,

const gpuWriteBuffer = device.createBuffer({ /* is used to write */})
const gpuReadBuffer = device.createBuffer({ /* Used to read */})

// Copy from one to another
copyEncoder.copyBufferToBuffer(
  gpuWriteBuffer /* Source video memory (object) */.0 /* Start byte (where to read from) */,
  gpuReadBuffer /* Target video memory (object) */.0 /* Start byte (where to start writing) */.4 /* The size of the copy, in byte */
);
Copy the code

2.3 Purpose: Image/texture replication

The main methods are copyBufferToTexture, copyTextureToBuffer, copyTextureToTexture, Used for copying between GPUBuffer and GPUTexture, GPUTexture and GPUTexture.

The GPUImageCopyBuffer, GPUImageCopyTexture and GPUExtent3D dictionary definitions are used.

Method definition

Go to the trouble of copying the definition again from GPUCommandEncoder above.

undefined copyBufferToTexture(
  GPUImageCopyBuffer source,
  GPUImageCopyTexture destination,
  GPUExtent3D copySize
);

undefined copyTextureToBuffer(
  GPUImageCopyTexture source,
  GPUImageCopyBuffer destination,
  GPUExtent3D copySize
);

undefined copyTextureToTexture(
  GPUImageCopyTexture source,
  GPUImageCopyTexture destination,
  GPUExtent3D copySize
);
Copy the code

They work at the instruction level and are arranged on instruction queues, i.e., operations on the GPU, not on the CPU, just like copyBufferToBuffer.

Each method has a compliance check, mainly to verify the parameters, will not expand. Three types are used here:

GPUImageCopyBuffer type

dictionary GPUImageCopyBuffer : GPUImageDataLayout {
  required GPUBuffer buffer;
};
Copy the code

Quite simply, it’s just a normal JavaScript object with a buffer field of type GPUBuffer.

GPUImageCopyTexture type

dictionary GPUImageCopyTexture {
  required GPUTexture texture;
  GPUIntegerCoordinate mipLevel = 0;
  GPUOrigin3D origin = {};
  GPUTextureAspect aspect = "all";
};
Copy the code

In addition to the texture field of the mandatory GPUTexture type, there are three optional parameters:

  • mipLevel, unsigned long, which copies the corresponding multilevel texture.
  • origin.GPUOrigin3DType, specify the texture copy starting point, here ignore the definition, there is a need for readers to consult the document, relatively simple;
  • aspect.GPUTextureAspectType that specifies what aspect of the texture to copy, as described in textures

GPUExtent3D type

dictionary GPUExtent3DDict {
  required GPUIntegerCoordinate width;
  GPUIntegerCoordinate height = 1;
  GPUIntegerCoordinate depthOrArrayLayers = 1;
};
typedef (sequence<GPUIntegerCoordinate> or GPUExtent3DDict) GPUExtent3D;
Copy the code

It is defined in two ways: number[] in TypeScript;

The other is the GPUExtent3DDict type:

  • widthRepresents the width of the range and must be passed
  • heightIndicates the range height. The default is 1
  • depthOrArrayLayersIndicates depth or number of layers. Default is 1

This means that you can pass an array directly, or you can pass a key-value object to represent the range required for each dimension.

3 Instruction queue: GPUQueue

It holds the instruction cache (#1 instruction cache: GPUCommandBuffer) and is responsible for submitting the instruction cache to the GPU.

The instruction queue object is a property of the device object and cannot be created by the user.

The definitions of the above three methods and the GPUQueue type are as follows:

[Exposed=(Window, DedicatedWorker), SecureContext]
interface GPUQueue {
  undefined submit(sequence<GPUCommandBuffer> commandBuffers);

  Promise<undefined> onSubmittedWorkDone();

  undefined writeBuffer(
    GPUBuffer buffer,
    GPUSize64 bufferOffset,
    [AllowShared] BufferSource data,
    optional GPUSize64 dataOffset = 0,
    optional GPUSize64 size
  );

  undefined writeTexture(
    GPUImageCopyTexture destination,
    [AllowShared] BufferSource data,
    GPUImageDataLayout dataLayout,
    GPUExtent3D size
  );

  undefined copyExternalImageToTexture(
    GPUImageCopyExternalImage source,
    GPUImageCopyTextureTagged destination,
    GPUExtent3D copySize
  );
};
GPUQueue includes GPUObjectBase;
Copy the code

Among them,

  • submitThe submit () method is used to submit an instruction cache array.
  • onSubmittedWorkDoneMethod returns a Promise that will resolve, but with no resolve value, once every instruction cache in the array of directives that have been submitted so far has been processed.

In addition to executing instructions on the instruction cache one by one, the queue objects themselves can perform some operations, such as:

  • WriteTexture: Writes textures
  • WriteBuffer: Write to the cache
  • CopyExternalImageToTexture: from the external image to write data to the texture

Such operations.

Among them:

The writeTexture method requires additional GPUImageCopyTexture, GPUImageDataLayout, and GPUExtent3D types. CopyExternalImageToTexture need GPUImageCopyExternalImage, GPUImageCopyTextureTagged and GPUExtent3D type.

It is important to note that the three write operations are at the queue level and have equal status to the single encoded instruction (i.e., the instruction cache), except that the instruction commit execution is asynchronous, whereas the three operations are synchronous.

3.1 writeBuffer method: writeBuffer

It allows you to write data of type BufferSource to a GPUBuffer object.

Allows you to specify data offset, size, and GPUBuffer offset.

BufferSource is an associative type, defined in JavaScript as an ArrayBuffer, an array of all types, and a associative type of DataView.

3.2 Writing texture data

There are two methods for writing texture data:

  • writeTextureMethod will beBufferSource(as mentioned in Section 3.1) data layout described by objects of type [GPUImageDataLayout](#GPUImageDataLayout type), Write to the texture object described by [GPUImageCopyTexture](#GPUImageCopyTexture type);
  • copyExternalImageToTextureMethods [GPUImageCopyExternalImage] (# GPUImageCopyExternalImage type) object description of external data sources (HTMLCanvasElement, ImageBitmap, etc.), Written to the [GPUImageCopyTextureTagged] (# GPUImageCopyTextureTagged type) object to describe texture in the object

GPUImageDataLayout type

dictionary GPUImageDataLayout {
  GPUSize64 offset = 0;
  GPUSize32 bytesPerRow;
  GPUSize32 rowsPerImage;
};
Copy the code

It represents what an image looks like in a byte array. Offset indicates where an image is read from the data source; BytesPerRow indicates how many pixels are in a row of images; RowsPerImage indicates how many rows the image has.

GPUImageCopyExternalImage type

dictionary GPUImageCopyExternalImage {
  required (ImageBitmap or HTMLCanvasElement or OffscreenCanvas) source;
  GPUOrigin2D origin = {};
};
Copy the code

An object of this type describes an external image data. Where, source passes in external image objects, the first two are commonly used; Origin indicates the origin of replication, relative to source.

GPUImageCopyTextureTagged type

It inherits from the [GPUImageCopyTexture](#GPUImageCopyTexture type) type:

dictionary GPUImageCopyTextureTagged : GPUImageCopyTexture {
  GPUPredefinedColorSpace colorSpace = "srgb";
  boolean premultipliedAlpha = false;
};
Copy the code

Where, the colorSpace field describes the colorSpace of external data encoding data, which can only be “SRGB” at present. PremultipliedAlpha defaults to false, which means whether the transparency in the data source is multiplied by the RGB color before being written to the texture. Canvas using WebGL can be controlled by WebGLContextAttributes, Canvas2D canvas is always pre-multiplied, and ImageBitmap is controlled by ImageBitmapOptions.

However, both parameters are optional and have default values, so you usually just set the GPUImageCopyTexture section.

For example,

CopyExternalImageToTexture method, for example, write to external webp image texture:

const img = document.createElement('img')
img.src = 'texture.webp'
await img.decode()
const imageBitmap = await createImageBitmap(img)
const texture = device.createTexture({
  size: [img.width, img.height], / / 256, 256
  format: "rgba8unorm".usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST
})

device.queue.copyExternalImageToTexture({
  imageBitmap: imageBitmap
}, {
  texture: texture
}, [img.width, img.height, 1])
Copy the code

Further reading

Metal instruction organization and execution mode