|
3 }% A, L6 a X' M$ w2 r2 |: A) k<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1> F4 H0 ]3 a5 A& r* ]9 P/ U6 ^
<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>
4 u4 Q: Z) w8 L8 }, _$ q<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>7 y- U, ^/ _2 f. }
<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>: r9 s: A6 Q' W* t" g
<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>
! w- c- Y) Y* w8 L1 a<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })& a- B- z" u; Q1 [: \" Q6 w
await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节
- n$ w: {+ f" b' J$ A9 ?4 U
4 y0 S" v+ `5 F& a( s& w6 V6 ]// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作( W: `+ M P: w/ x4 D% L" m3 [6 |, V
</code></pre>
/ W& H* n, q2 s/ V9 {<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>& @6 f+ D' V7 ]' k$ l
<pre><code class="language-js">somebuffer.unmap()
8 x0 p6 B8 m8 U3 {/ `3 E3 z</code></pre>
" d! X; Q! J8 i, |- a! I# n<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
9 e8 X9 g) w4 f8 p, A<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
& t7 n" i" _- E2 Y. Z% {- c1 d* m* o<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>
: a$ F; b7 D3 Z# |6 J" B- l<h2 id="创建时映射">创建时映射</h2>/ r! R; P. Z* }) U- ?; s
<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>
0 }, S6 E2 w! S: v4 A+ [<pre><code class="language-js">const buffer = device.createBuffer({: Z) F% i! X1 m2 @# S
usage: GPUBufferUsage.UNIFORM,
% V) w- M8 z( u: _( I3 S2 H$ Q size: 256,, Y$ S5 h1 A) C6 k) |: V
mappedAtCreation: true,
4 e u8 S' p4 |2 J- R0 d& d})
0 g0 k* _* h* r U6 h$ \ N// 然后马上就可以获取映射后的 ArrayBuffer2 p/ R/ a& f7 I. }8 y0 F
const mappedArrayBuffer = buffer.getMappedRange()
+ V4 k5 u$ e" T4 M9 S- B- `6 H" f4 b' f, Z
/* 在这里执行一些写入操作 */
- Z' R8 t4 j; U! b! V. i7 |8 o: R% `; ^
// 解映射,还管理权给 GPU
/ p! L5 m( S. R% ibuffer.unmap()$ R3 j- @. |( t0 P. f. l
</code></pre>& R9 v4 ^7 b/ @7 U
<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>6 U' r" x2 z2 k2 e- X4 D, _! `1 @
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>
7 B q0 F8 B" u# `<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p># j/ f p8 L( u0 d3 l$ S
<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>& g9 O+ Y, \7 B9 i- M- d) h
<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>
1 g2 l$ o. ^4 {% @- T+ G3 i. c" J8 v<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>
; P4 t/ u, a3 P' I/ K. R<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>
# o) }$ ?3 A- H+ g- o, v: d<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>. n9 F" l4 V/ [
<pre><code class="language-js">const texture = getTheRenderedTexture()
" n) r# H! t( u: k% y* F9 r; w
/ Y& F! ]: S5 J. \const readbackBuffer = device.createBuffer({
+ o0 P. O: w5 D' v' L1 n usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
7 n. B+ ?1 H& M9 c4 Y size: 4 * textureWidth * textureHeight,* h& y$ M5 e3 j0 i& J
})+ n( t2 p3 i+ y# p7 p4 V3 |
% t+ @+ d: E/ I
// 使用指令编码器将纹理拷贝到 GPUBuffer
z' r; \9 ~# H: s& w; j Fconst encoder = device.createCommandEncoder()) k; @- c7 N4 R+ Q0 k# U
encoder.copyTextureToBuffer(; x" \) t, ~9 h2 ?# J9 m2 ~- s% d
{ texture },
0 ]7 V5 y, i9 ?2 E7 J( T { buffer, rowPitch: textureWidth * 4 },* |# A) t' s- ?" E3 _/ S: l
[textureWidth, textureHeight],! @) o! n! B; S$ N) a+ I$ T
)
1 ^( q8 `7 I' L8 o4 h. Y7 u2 F( odevice.submit([encoder.finish()])
% a8 \* M" E) f' Z! C7 R3 _+ @& R5 j# G" l- [
// 映射,令 CPU 端的内存可以访问到数据
% Q2 U1 `$ j# p4 _/ x, oawait buffer.mapAsync(GPUMapMode.READ)- V; Z* E7 s$ C" u4 W
// 保存屏幕截图
; `: ~7 ]4 y+ I* D% ~! C) YsaveScreenshot(buffer.getMappedRange())2 \( k% j# Y) D, y
// 解映射' O# s9 I H2 `. x2 W
buffer.unmap(); a M& h' C5 B- Z' v7 H, D
</code></pre>/ M; _7 E. V1 Y/ U4 X( G$ Q
( m. l" H8 T4 _2 _+ H& @8 m3 D7 ]/ K |
|