飞雪团队

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 16490|回复: 0

WebGPU 中的缓冲映射机制

[复制链接]

8573

主题

8661

帖子

2万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
28049
发表于 2022-2-12 14:35:42 | 显示全部楼层 |阅读模式

/ K5 u! o+ O* z" f, A) }. d<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1>
4 N5 [* G/ U* H8 V3 `5 ^<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>
. ^8 h& [% X9 d5 O<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>
1 M; P* R8 H6 n4 Y9 z: T<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>
. n+ u& L6 g. E. x) a/ A<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>( Q: i4 A2 N; r3 a- ^
<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })4 v$ j  u5 X$ @, D( y
await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节! \0 A: m2 }1 U, {% Q8 s2 ^9 P

6 B- M# b- ^" D) X// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作4 H4 H3 E- H: Z1 W) z$ E# B* C. m
</code></pre>  ~# T5 Z) E. N# P
<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>
( Z/ }6 `; d6 `1 h, i( ]2 t1 s<pre><code class="language-js">somebuffer.unmap()
7 v7 V; H) m$ f3 P, d- G6 M</code></pre>
% Q4 Q2 o# ~$ S' T. b<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
" A/ U1 M: V) K" x: m. {<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
! [; D# L' o- I3 g$ g<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p># m$ a- O9 ^" I- W
<h2 id="创建时映射">创建时映射</h2>
  S5 A, R& X5 O# _# ~5 s' d<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>  A) t, |# H0 N2 S
<pre><code class="language-js">const buffer = device.createBuffer({7 a9 d8 Z) f; U1 m
  usage: GPUBufferUsage.UNIFORM,
* m6 W# B9 n% J. O# c* a  size: 256,
8 W9 b8 u- m1 r% I. z  mappedAtCreation: true,' H2 z+ Y# z$ i
})
4 }$ Q" h0 e% R. ]+ M3 N& r// 然后马上就可以获取映射后的 ArrayBuffer
; D) ~% ~- n+ K* @const mappedArrayBuffer = buffer.getMappedRange()' C+ x5 _6 y) L9 n8 a! z0 a! D

9 Z5 A9 t  w% o0 D/* 在这里执行一些写入操作 */
# v2 h: R4 E) @5 @, y9 R5 m6 @+ ]. b6 C! V
// 解映射,还管理权给 GPU
) m9 U3 Z* G' u. \buffer.unmap()( V0 o6 G% [! X4 U: S2 {
</code></pre>& B9 N* x7 W2 |( r
<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>
" d* W9 m+ N# f& O2 h4 u<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>
1 S5 }+ j5 P) W# z' T' e<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>3 B% _* F! A3 I3 t9 S8 t" y$ I7 Z
<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>
6 _3 |8 f- j1 n, z<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>, W. d3 p- ~5 q
<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>
8 T9 c4 o; z1 V<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>2 v5 R( g9 A3 s" F8 a
<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>8 q# W; n* B5 y
<pre><code class="language-js">const texture = getTheRenderedTexture()5 E; E9 m) m! H- Z: U

, q1 z( X4 s& oconst readbackBuffer = device.createBuffer({) x. b3 j& a$ l  i
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
% ~9 s' U& {% {6 v  size: 4 * textureWidth * textureHeight,, |/ j* ~- m! G
})
0 k" N  A* X0 o$ @# p! }4 a  g5 N' Z1 Q* @0 D  Y
// 使用指令编码器将纹理拷贝到 GPUBuffer
/ Z  v4 s8 B" G+ x- z+ e% Y) Oconst encoder = device.createCommandEncoder()) J* T/ [) u6 l  l
encoder.copyTextureToBuffer() Q& U9 l' z& d* k( g% W' Y
  { texture },
/ i, s$ d- j0 i  I  { buffer, rowPitch: textureWidth * 4 },
3 Y' |9 m6 i( R! B  [textureWidth, textureHeight],
+ U8 }/ A. c1 i; z% [4 e)
7 v5 w1 _' v& c  S2 `+ \' Ddevice.submit([encoder.finish()])1 o+ s- i( ^, g" w7 r. ^
1 b! N0 E# m0 X" t/ o
// 映射,令 CPU 端的内存可以访问到数据( Z1 F4 `! T9 N2 v8 [+ A( {
await buffer.mapAsync(GPUMapMode.READ)' ]( O& a: L- \6 O' `. b- J7 A8 {
// 保存屏幕截图
* n/ L* D/ z8 ]+ h. {. Z, p" msaveScreenshot(buffer.getMappedRange())- a7 X* a6 f; ~! l0 v% h
// 解映射' q: t" X! Y: `, u# ~
buffer.unmap()( w) E5 f$ `: b8 ?+ A1 D2 Q8 S( @
</code></pre>
9 z* w8 p/ N" R  `3 S" K: b! L1 C
回复

使用道具 举报

懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|飞雪团队

GMT+8, 2026-4-19 18:31 , Processed in 0.062730 second(s), 22 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表