|
|
- f% X7 k) Y! L<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1>
( \, C3 {5 S' `8 s2 z3 ]1 f<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>; _. _- L B. B0 @ D& E
<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>$ F9 s9 z* N0 ~( c2 P: T( f: }4 `
<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>
2 \( K, O: j; D' O `: |<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>5 U( N$ i6 ~6 M4 q+ q
<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })8 T2 F3 _, ]9 B' B6 ~0 n( L4 g
await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节
; s# a+ J$ g0 o; x; k. N
( [1 ?0 u* L1 i0 w8 K// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作3 L+ D ^2 G$ _1 N7 u. K _0 g3 ~
</code></pre>1 H; F: f' i2 x7 T. d2 T6 c( @) T
<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>2 w4 J* |. [, M% \# {& m
<pre><code class="language-js">somebuffer.unmap()# s0 \/ Y% I' e( S) H2 A
</code></pre>/ ?6 c4 y9 D& ]: ~
<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p> s+ k0 B2 `1 L# y, L, V1 l( A
<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
! g/ u) B$ y9 g4 R& D A<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>0 I5 G' G% x j1 L# M7 s2 G4 Z- X
<h2 id="创建时映射">创建时映射</h2>& U, Y. z1 j( F, i* n5 X
<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>
( U$ c4 o0 a; H& ^, J, y$ W; e<pre><code class="language-js">const buffer = device.createBuffer({
$ U3 L. s z* `. h. m usage: GPUBufferUsage.UNIFORM,
& B7 g% A( a: G- ~3 A3 r size: 256,& o g) f3 O" O9 D7 f! u, Z
mappedAtCreation: true,1 w: d% w& @0 p! a- `5 {
})$ O) F3 z- L+ x' V( r5 a6 T
// 然后马上就可以获取映射后的 ArrayBuffer0 F- Q1 R7 J5 i8 |. F; F0 R
const mappedArrayBuffer = buffer.getMappedRange()$ K# W) N6 x) N! H$ x" |% R+ z. M
) f% A1 y) M8 W9 O/* 在这里执行一些写入操作 */$ Q3 h h4 K( Z2 h! b3 O/ Z* J/ M
& Z2 m! W5 j1 p m3 ~// 解映射,还管理权给 GPU# G" j7 K C% O+ ?
buffer.unmap()- I* m" y/ ~! k, C- c/ W+ B
</code></pre>
6 z3 L8 r$ w( u; ]<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>9 P: ^5 c, D" A, g' ` W; Z
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>; A' B& J! H/ k$ n3 M( K* g, d' d
<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>( ~; g/ ^5 ~+ r1 e: {/ `8 I: w
<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>
: w! b' C5 Q8 w<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>; U: G4 b) B! W* I
<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>- b- k# {) @) Z$ Q. p
<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>2 A$ p4 [- Q7 n4 Z9 l6 E
<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>
+ F2 p7 o, H* q' Q& r9 K9 Q<pre><code class="language-js">const texture = getTheRenderedTexture()
% j$ A- {9 u8 q
7 T& {$ c: d3 a* Sconst readbackBuffer = device.createBuffer({* u3 K: ~' R3 }- B$ |' n( v
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,1 X( E' F# J' X8 l7 S! f
size: 4 * textureWidth * textureHeight,
( O3 @" _$ w2 a" r9 |5 z+ J- O})
# c( f1 i. d2 ?7 I% p4 n* f3 h: k/ u6 @7 G
// 使用指令编码器将纹理拷贝到 GPUBuffer
7 k' @' L" w% R2 n+ Oconst encoder = device.createCommandEncoder()& i$ d; G( ]! N) y9 r9 R0 U# z+ _
encoder.copyTextureToBuffer(# f& z R% p+ a6 I
{ texture },
0 j, n5 d& P j' T6 q { buffer, rowPitch: textureWidth * 4 },
; r: @- g5 e/ l9 S. s9 Q [textureWidth, textureHeight],
+ [( K+ d* o6 y6 U" R)8 v* }7 |" R: P( j
device.submit([encoder.finish()])
" a4 ~7 V' { @7 k3 k
I. v, I- F. m" R0 M& _// 映射,令 CPU 端的内存可以访问到数据
7 g6 _$ n5 R( n% Zawait buffer.mapAsync(GPUMapMode.READ)6 r5 H7 m2 v0 E" {) y2 ~
// 保存屏幕截图
4 W8 k8 ]$ H7 tsaveScreenshot(buffer.getMappedRange())
- z: e, \ x w% m- t2 N// 解映射
/ R- l. Z t# S. g; h2 ^; b& X; |3 n4 e4 Mbuffer.unmap()* m3 Z$ t5 n) m T
</code></pre>; U/ Z! l' z4 D% G# G$ c p Y5 ~
5 j( P# V/ ^( |$ d |
|