关于buffer:WebGPU-中的缓冲映射机制

32次阅读

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

1. 什么是缓冲映射

就不给定义了,间接简略的说,映射(Mapping)后的某块显存,就能被 CPU 拜访。

三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能拜访它了,此时留神,GPU 依然能够拜访这块显存。这就会导致一个问题:IO 抵触,这就须要程序考量这个问题了。

WebGPU 禁止了这个行为,改用传递“所有权”来示意映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边拜访显存的,也就防止了竞争和抵触。

当 JavaScript 申请映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的解决显存的操作。所以,GPUBuffer 的映射办法是一个异步办法:

const someBuffer = device.createBuffer({/* ... */})
await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节

// 之后就能够应用 getMappedRange 办法获取其对应的 ArrayBuffer 进行缓冲操作 

不过,解映射操作倒是一个同步操作,CPU 用完后就能够解映射:

somebuffer.unmap()

留神,mapAsync 办法将会间接在 WebGPU 外部往设施的默认队列中压入一个操作,此办法作用于 WebGPU 中三大时间轴中的 队列时间轴 。而且在 mapAsync 胜利后,内存才会减少(实测)。

当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜想)。

因为测试地不多,我在调用 destroy 办法后并未显著看到内存的变少,心愿有敌人能测试。

创立时映射

能够在创立缓冲时传递 mappedAtCreation: true,这样甚至都不须要申明其 usage 带有 GPUBufferUsage.MAP_WRITE

const buffer = device.createBuffer({
  usage: GPUBufferUsage.UNIFORM,
  size: 256,
  mappedAtCreation: true,
})
// 而后马上就能够获取映射后的 ArrayBuffer
const mappedArrayBuffer = buffer.getMappedRange()

/* 在这里执行一些写入操作 */

// 解映射,还管理权给 GPU
buffer.unmap()

2 缓冲数据的流向

2.1 CPU 至 GPU

JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射进去的 ArrayBuffer,而后随着解映射、提交指令缓冲到队列,最初传递给 GPU.

上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。

应用队列对象的 writeBuffer 办法写入缓冲对象是十分高效率的,然而与用来写入的映射后的一个 GPUBuffer 相比,writeBuffer 有一个额定的拷贝操作。揣测会影响性能,尽管官网举荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。

2.2 GPU 至 CPU

这样反向的传递比拟少,但也不是没有。譬如屏幕截图(保留色彩附件到 ArrayBuffer)、计算通道的后果统计等,就须要从 GPU 的计算结果中获取数据。

譬如,官网给的从渲染的纹理中获取像素数据例子:

const texture = getTheRenderedTexture()

const readbackBuffer = device.createBuffer({
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
  size: 4 * textureWidth * textureHeight,
})

// 应用指令编码器将纹理拷贝到 GPUBuffer
const encoder = device.createCommandEncoder()
encoder.copyTextureToBuffer({ texture},
  {buffer, rowPitch: textureWidth * 4},
  [textureWidth, textureHeight],
)
device.submit([encoder.finish()])

// 映射,令 CPU 端的内存能够拜访到数据
await buffer.mapAsync(GPUMapMode.READ)
// 保留屏幕截图
saveScreenshot(buffer.getMappedRange())
// 解映射
buffer.unmap()

正文完
 0