关于gpu:WebGPU-规范篇-10-指令编码与队列

5次阅读

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

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


介绍原文 11 章、局部 12 章和 17 章的内容。

1 指令缓存:GPUCommandBuffer

指令缓存(又译作命令缓冲区),GPUCommandBuffer,是一个能当时存储 GPU 指令的存储容器。它能够提交给 [GPUQueue]() 执行。每个 GPU 指令代表一个要被 GPU 执行的工作,能够是绘图、设置数据、复制资源等。

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

它有一个 resolve 值是 double 数值的 Promise,其 resolve 值是指令缓存上的预存 GPU 指令的执行工夫。

如果在创立指令编码器时,其参数对象的 measureExecutionTime 若为 true 才无效,若为 false,这个 Promise 会 reject,你能够捕捉一个 OperationError 谬误。

如何创立

调用 指令编码器 的 finish 办法,即可获取指令缓存对象。它个别用来提交给队列:

device.queue.submit([commandEncoder.finish()
])

2 指令编码器:GPUCommandEncoder

用处:①创立通道编码器;②复制 GPUBuffer/GPUTexture;③调试、查问等其余性能(略)

本文次要介绍罕用的 ①、② 两种性能。

留神,指令编码器用完并提交给队列后,它就变得不再可用。

扩大浏览:Metal 中的指令编码器

它的 WebIDL 定义如下:

[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;

2.1 如何创立

由设施对象的 createCommandEncoder 办法创立

const commandEncoder = device.createCommandEncoder()

它有一个可选的参数对象,类型是 GPUCommandEncoderDescriptor,一个 JavaScript Object:

dictionary GPUCommandEncoderDescriptor : GPUObjectDescriptorBase {boolean measureExecutionTime = false;};

它的用处在指令缓存的创立大节已介绍过,可选属性 measureExecutionTime 示意是否能够测量指令的运行工夫。

2.2 用处:启动 / 创立一个可编程通道

通过指令编码器的 beginRenderPassbeginComputePass 能够别离启动 / 创立一个渲染通道 或 计算通道,这两个办法的返回值天然就是渲染通道编码器(GPURenderPassEncoder)和计算通道编码器(GPUComputePassEncoder)。

// 渲染通道编码器
const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor)

// 计算通道编码器
const computeEncoder = commandEncoder.beginComputePass()

其中,渲染通道编码器所需的参数对象 renderPassDescriptor 的细节见通道编码器文章。

指令编码器不负责通道编码器的完结,而是由通道编码器本人完结。

2.2 用处:缓存复制

copyBufferToBuffer 办法,用于 GPUBuffer 之间的拷贝。

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

即从 source 复制到 destination,在 Buffer 一文中有简略提及它与间接 map/unmap 来赋予数据的差别,那就是这个办法是在 GPU 中操作,而 map/unmap 间接写入是在 CPU 端操作 GPUBuffer 的数据。

举例

const gpuWriteBuffer = device.createBuffer({/* 用于写 */})
const gpuReadBuffer = device.createBuffer({/* 用于读 */})

// 从一个复制到另一个
copyEncoder.copyBufferToBuffer(
  gpuWriteBuffer /* 源显存(对象)*/,
  0 /* 起始字节(从哪开始读)*/,
  gpuReadBuffer /* 指标显存(对象)*/,
  0 /* 起始字节(从哪开始写)*/,
  4 /* 复制多大的内容,单位 byte */
);

2.3 用处:图像 / 纹理复制

次要是 copyBufferToTexturecopyTextureToBuffercopyTextureToTexture 办法,用于 GPUBuffer 和 GPUTexture、GPUTexture 和 GPUTexture 之间的复制。

须要用到 GPUImageCopyBufferGPUImageCopyTextureGPUExtent3D 这三种 dictionary 的定义。

办法定义

不嫌麻烦,从下面 GPUCommandEncoder 再复制一次定义。

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

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

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

它们作用的级别是指令级别,是被安顿在指令队列上的,即 GPU 中的操作,而不是在 CPU 端,这一点与 copyBufferToBuffer 是一样的。

每个办法都有合规性测验,次要是对参数进行校验,就不开展了。这里用到了三个类型:

GPUImageCopyBuffer 类型

dictionary GPUImageCopyBuffer : GPUImageDataLayout {required GPUBuffer buffer;};

很简略,它就一个一般的 JavaScript 对象,有一个 GPUBuffer 类型的 buffer 字段。

GPUImageCopyTexture 类型

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

这个就稍显简单,除了必选的 GPUTexture 类型的 texture 字段外,还有三个可选参数:

  • mipLevel,unsigned long 类型,指定后,就复制对应的多级纹理;
  • originGPUOrigin3D 类型,指定纹理的复制终点,此处疏忽定义阐明,有须要的读者可自行查阅文档,比较简单;
  • aspectGPUTextureAspect 类型,指定要复制纹理的什么方面,在纹理一文中有介绍

GPUExtent3D 类型

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

它有两种定义,一种是 TypeScript 中的 number[]

另一种是 GPUExtent3DDict 类型:

  • width 示意范畴宽度,必须传递
  • height 示意范畴高度,默认是 1
  • depthOrArrayLayers 示意深度或者层数,默认是 1

意思是,你能够间接传递一个数组,也能够传递一个 key-value 对象来示意各个维度所需的范畴。

3 指令队列:GPUQueue

它保留 指令缓存,次要负责提交指令缓存到 GPU 上。

指令队列对象是设施对象的一个属性,不能够由用户创立。

上述三个办法的定义与 GPUQueue 类型定义如下:

[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;

其中,

  • submit 办法用于提交一个指令缓存数组。
  • onSubmittedWorkDone 办法返回一个 Promise,一旦所有目前为止提交的指令缓存数组中每一个指令缓存都解决实现,它就会 resolve,只不过没有 resolve 值。

除了挨个执行指令缓存器上的指令外,队列对象自身还能够本人就执行一些操作,例如:

  • writeTexture:写入纹理
  • writeBuffer:写入缓存
  • copyExternalImageToTexture:从内部图像写数据到纹理

等操作。

其中:

writeTexture 办法须要额定用到 GPUImageCopyTexture、GPUImageDataLayout、GPUExtent3D 类型;copyExternalImageToTexture 须要用到 GPUImageCopyExternalImage、GPUImageCopyTextureTagged 和 GPUExtent3D 类型。

须要留神的是,这三个写数据的操作的等级是队列级别,与单个编码后的指令(即指令缓存上的指令)是位置均等的,只不过指令提交执行是异步,而这三个操作是同步的。

3.1 writeBuffer 办法:写缓存

它容许把 BufferSource 类型的数据 data,写入到 GPUBuffer 对象中。

容许指定数据的 offset 和 size、GPUBuffer 的 offset。

BufferSource 是一种联结类型,在 JavaScript 中,定义为 ArrayBuffer、所有类型数组、DataView 的联结类型。

3.2 写纹理数据

写纹理数据有两个办法:

  • writeTexture 办法将 BufferSource(3.1 大节有提及)按 GPUImageDataLayout 类型的对象形容的数据布局,写入由 GPUImageCopyTexture 形容的纹理对象中;
  • copyExternalImageToTexture 办法将 GPUImageCopyExternalImage 对象形容的内部数据源(HTMLCanvasElement、ImageBitmap 等),写入到 GPUImageCopyTextureTagged 对象形容的纹理对象中

GPUImageDataLayout 类型

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

它示意一张图像在字节数组中的样子。offset 示意从数据源的什么地位读取一张图像;bytesPerRow 示意图像一行像素占多大内容;rowsPerImage 示意图像有多少行。

GPUImageCopyExternalImage 类型

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

此类型的对象形容一个内部图像数据。其中,source 传入内部图像对象,个别罕用的是前两个;origin 示意复制的原点,绝对于 source 而言。

GPUImageCopyTextureTagged 类型

它继承自 GPUImageCopyTexture 类型:

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

其中,colorSpace 字段形容内部数据数据编码的色彩空间,目前只能是 "srgb"premultipliedAlpha 默认值是 false,意思是,是否将数据源中的透明度与 rgb 色彩相乘再写入纹理。应用了 WebGL 的 canvas 能够用 WebGLContextAttributes 管制,Canvas2D 的 canvas 总是事后相乘,ImageBitmap 应用 ImageBitmapOptions 管制。

不过这两个参数是可选的,它们均有默认值,所以通常只需设置好 GPUImageCopyTexture 的局部即可。

举例

以 copyExternalImageToTexture 办法为例,写入内部 webp 图片纹理:

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])

扩大浏览

Metal 的指令组织和执行模式

正文完
 0