|
|
& V& m0 \( {/ z% O
<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1>- S1 \, o9 V5 K! D: X( K
<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>
9 ]0 Q/ s2 ^* `1 D4 G, K<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>
/ O. Y- }8 C+ c' U: V$ `0 k<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>
0 c8 d5 U; ^, @, w2 \. j<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>
0 h9 a7 h8 j7 j& j) q- q<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })
2 M4 o: f, o4 U' u& mawait someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节
' Y3 O6 l* K; V
1 x- \, Y# n. ]6 c: i( R" E$ Y, U2 x, i1 N// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作
/ q8 Q7 B1 o% ]! x) }5 q: K; F</code></pre>
# ^; d: v5 | U! p<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>* |& s; A. Y, U/ g
<pre><code class="language-js">somebuffer.unmap()6 R y5 f6 o/ T/ o* A7 a! j
</code></pre>
8 v5 m* V7 e5 Y y5 j) K8 |<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
* l/ |3 i) x l1 z7 W<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
, c; Y$ s6 _9 q9 ^* W<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>
. W3 I# X# g4 I1 B9 j, ]4 R t<h2 id="创建时映射">创建时映射</h2>
7 s+ }9 G7 R& |+ P<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>& Y* h0 G! i! I
<pre><code class="language-js">const buffer = device.createBuffer({
4 N9 ]$ ~- ]1 J' M/ @ usage: GPUBufferUsage.UNIFORM,8 s- \7 J- M _* K9 R: H
size: 256,
, s, u, [* F: f; e mappedAtCreation: true,- Q% S. l/ l+ b
})
7 B0 r( `' ^; K+ Y9 K, i- y; C3 V// 然后马上就可以获取映射后的 ArrayBuffer7 m( J' w2 T1 S. P! P( b4 L. p
const mappedArrayBuffer = buffer.getMappedRange()2 W- M- b& V. Q t' f* }
0 t" r! }( j6 ^6 P0 X' r5 N/* 在这里执行一些写入操作 */
" t9 `" _1 ]2 i5 n# X) Q# c+ D4 i5 ^5 G. `: m$ {0 V B6 _
// 解映射,还管理权给 GPU
( i* P) M6 e+ B* `buffer.unmap()7 o) z, v& v5 N- r' Z
</code></pre>
. _* B1 }! [! e<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>4 t) }& `2 g |! Z% P7 P
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>
* j! E% w% }- ]<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>/ c5 h# ?$ D' p$ G% o
<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>; u9 V, C7 }# F Z- y' x$ G
<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>
7 N4 y" v0 x/ X. [! T, M<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>) F- ^8 S0 E& U5 j! ]( h& d1 A( d* x
<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>
. T3 u6 ^, `. A8 o0 {<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>% Y3 D) k" w- L, |( u3 C3 V
<pre><code class="language-js">const texture = getTheRenderedTexture()
2 X6 q8 d1 D. o2 W4 n& [; @% x T& A. }" L1 {2 P: h8 d" o
const readbackBuffer = device.createBuffer({
$ b7 `. E" f0 m* E! w usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,7 o! C" }- _$ @( n% g; `6 p: r( z
size: 4 * textureWidth * textureHeight,. _4 w7 ]9 [& o( C' ~8 m/ w! K
})7 k) g' N: ~6 ~+ S& f; j% c0 L1 F
0 }: Q; a4 G$ u" D" e
// 使用指令编码器将纹理拷贝到 GPUBuffer
( L! S! ^7 R0 @: Wconst encoder = device.createCommandEncoder()
* |3 M+ S- S4 f: q7 Sencoder.copyTextureToBuffer(
1 C5 u5 ]: H8 {: Y0 y { texture },' f2 |1 I- e' \7 f3 x# T
{ buffer, rowPitch: textureWidth * 4 },
7 y3 g9 i2 q# S4 n2 V8 f. E [textureWidth, textureHeight],2 M- b3 ?$ u; J; ]) U, {6 O, \
)- g' i4 w% G. \8 p- l- i
device.submit([encoder.finish()])) f \3 Q& g1 k) J5 ~' b
4 {, D3 n B/ X0 T6 d/ H4 e// 映射,令 CPU 端的内存可以访问到数据
6 I" O" x9 Q8 v/ s; i9 H$ Vawait buffer.mapAsync(GPUMapMode.READ)8 A% E; V- Z2 }6 h; i" l* c
// 保存屏幕截图
& X- j9 ~ B& i% isaveScreenshot(buffer.getMappedRange())
. G: {$ r3 x# W! b, F* B# I% K// 解映射4 ^+ o5 p5 z- S3 O% ~! d
buffer.unmap()
3 B- k% h: I; h& G! C/ G</code></pre>, A4 N. C; _) O6 v. y
0 N7 ~$ Z9 Z+ o' K& u `
|
|