|
|
" r4 }$ v/ Z1 T* t& y5 g- h# q# i
<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1>
$ z4 P* D( b/ {! M# g) }- q<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>9 m: @5 B3 y! G! `( o
<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>
. c' }; o$ ~3 J3 \: A<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>
' F1 {- v3 X! k! e<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>2 I. ?9 b" b8 g, ]
<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })
. E& s0 t- ]/ Sawait someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节* G7 \% y( \0 E- L3 {5 j4 z- L
8 h1 Z! _! ~3 F/ C
// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作
- L3 E; h) J. N6 e/ b7 J5 p' C</code></pre>$ u* u2 Q5 } `# ]* t" O l
<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>
- s; U+ {% H* Z. r, p. p3 V<pre><code class="language-js">somebuffer.unmap()8 n8 l1 S# a* ~0 O. A0 |* X
</code></pre>
" O0 a( D+ W. r" l' o* W! ?1 B<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
$ j8 D) l" K' c' H) G<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
) j. `6 D3 ~8 t8 t' M2 f( Z<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>
) ~1 F9 E2 R T; {0 f$ b9 C' ~ s<h2 id="创建时映射">创建时映射</h2>
4 m0 L7 L& J- S<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>
# N' L6 ^1 ]6 K5 J: [% r$ i<pre><code class="language-js">const buffer = device.createBuffer({9 J# q+ `3 r# G0 k, P
usage: GPUBufferUsage.UNIFORM,# m+ ^" F5 o- z6 Y, g- P3 ~
size: 256,
( _/ W/ r* W9 m# ^" B mappedAtCreation: true,
' T1 w+ M& Q, N}), G8 d9 Q0 T+ T/ X( W$ I
// 然后马上就可以获取映射后的 ArrayBuffer
1 `, {& [) k" x: x* Tconst mappedArrayBuffer = buffer.getMappedRange(): B! Y. }$ c+ Z* a
+ F# s, u8 B9 p. @: A& f) [/* 在这里执行一些写入操作 */
9 ^! a/ ]: R9 O* ~- c3 t' B F' E1 L2 {" r' \2 C% Q9 o4 _+ ~
// 解映射,还管理权给 GPU4 U9 `) H: k Y, y% L* t- ?2 K1 z
buffer.unmap()
5 z5 @. I8 c: P5 u7 F9 }8 P8 b2 ]- ~' i</code></pre>
5 O7 r$ t: _# q; x) ]1 O<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>
4 i, @& F4 p' Y7 ^$ H) G<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>* b4 O' c' s) q; j" t- U
<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>
( a9 \* P5 s. i0 A$ M5 \<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>
: k9 y' l& ?5 z" ~<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>: b4 N9 n) \, S) l, o
<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>
2 f6 l+ q& O% @& F/ }<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>5 A2 w! h7 X# ^ ^# G- D
<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>
: w' o5 H& J& [" W; {. q" e<pre><code class="language-js">const texture = getTheRenderedTexture()
3 D; e5 k9 @4 Z1 P. w; Y6 `; W, h# L3 v$ {7 `
const readbackBuffer = device.createBuffer({' n1 ?: z' R$ a4 I0 w
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
* g. [+ s8 p+ c! ~7 l size: 4 * textureWidth * textureHeight,. j* L* {, m2 d3 W3 Y! m& a
})
* e: k |) m' G% H) K. F: Y# D8 N1 G4 d/ {# f& l" F+ u+ \, c
// 使用指令编码器将纹理拷贝到 GPUBuffer6 S% f- Q$ [2 B; H) a1 t
const encoder = device.createCommandEncoder()9 n1 j# j% k: h6 P
encoder.copyTextureToBuffer(
" w# k5 ?) P8 p* i: z; z# `6 K7 F { texture },) J; v, k& g% Y9 J% K0 n
{ buffer, rowPitch: textureWidth * 4 },
+ `1 N. \+ I8 C( @+ e: o [textureWidth, textureHeight]," H. V: P: a4 W# |# V& J
)
' P- o9 ^ {0 y# G2 F9 X$ |device.submit([encoder.finish()])
+ I; z3 b" S" v' b1 H1 v+ }9 E! S) f
" r8 }" u1 P, l" S# j) {) e. g ~// 映射,令 CPU 端的内存可以访问到数据
- X) ^) ~. _. N: c; e( b! r7 [0 r# g. wawait buffer.mapAsync(GPUMapMode.READ)1 C/ p$ t" \: d
// 保存屏幕截图* D3 |. E2 K5 z. R
saveScreenshot(buffer.getMappedRange())
0 I8 c8 I1 ~# s. F: e+ G1 g// 解映射
H) n# H) l# _4 s Mbuffer.unmap(): s3 t) h& E; t( q$ [, E0 v
</code></pre>
0 v( Z+ s. M' `! u! ?; `
. O* o& y/ t: h3 P. K4 `+ X |
|