|
|
$ C O% S" m: K9 |<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1># F9 E' {/ ^0 W: n; Q) _
<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>
. _! q' V2 S6 ~& y) x<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>
# {, q2 A* g6 o0 ?<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>
* B1 ^; T6 i3 o* u, R<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>
/ A8 S5 t l4 q3 ?: c% w- A<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })% t3 u v. Y1 \3 o
await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节+ g/ L3 O6 [* S: O/ t _
* X/ Q' }/ O$ v! B1 k E0 B// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作0 J8 ` q7 o( c! c+ W" [0 h
</code></pre>. S2 X H% B7 v& E
<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>, ^* K, V/ X- i- S
<pre><code class="language-js">somebuffer.unmap()
3 w' h( R/ [2 U: m1 ^; l. ]2 z</code></pre>1 q( y' s8 l. `& Q
<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>+ I/ t+ H6 O2 N7 U5 X' D' g* ~
<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
# W$ ~+ c5 |( D<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>
: o5 U3 e; s- O+ b<h2 id="创建时映射">创建时映射</h2>
' v% b! v2 q7 U" k<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>9 g4 I8 B/ a- h0 ]
<pre><code class="language-js">const buffer = device.createBuffer({: k { R( X! j/ H5 T9 T
usage: GPUBufferUsage.UNIFORM,
% u( L R" \1 @% r8 L& I B4 @ Q size: 256,
' B$ h8 X0 o$ W1 |/ r+ [! B1 } mappedAtCreation: true,9 p+ t4 _" |2 o8 S/ W1 W. \) V
})
* s0 L& C2 L3 ?( F3 P// 然后马上就可以获取映射后的 ArrayBuffer/ Z1 c( j. W" Z5 E# n/ H
const mappedArrayBuffer = buffer.getMappedRange()
5 U; Y0 U4 ^& f- i: [
) G* g' m+ X% y! Z5 b" z/ z n/* 在这里执行一些写入操作 */9 Y# Y( P# i9 O8 h6 N) x
9 B8 r) |1 e6 ^5 [/ y0 c0 ~. A2 `// 解映射,还管理权给 GPU8 p+ H- [; W5 W
buffer.unmap()6 S6 L( k! ^% o5 c8 H; A
</code></pre>
9 P9 R8 t9 x# L! a" I. ^9 w) {<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>' y7 T. p0 g& i& h8 i3 `
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>7 n3 }" B# d( r
<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>0 Z0 D% v) l% E/ x: P8 E7 S% c
<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>
+ M8 {, H; f% T; D2 l4 |<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>
( u0 d8 ~7 u& x# `) x<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>: @9 m9 ^# G. R/ g& @3 g3 x
<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>
& v/ ?$ Q3 K& f/ z' P: E<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>
$ {+ v! k! P2 x' \- S! [3 m% ^4 w<pre><code class="language-js">const texture = getTheRenderedTexture()
/ v) L6 C' A8 x+ H" [% ^, O/ ]5 X: n4 u; ^/ u
const readbackBuffer = device.createBuffer({0 I; Y; S3 W, I, U d- I
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,* |% e; O: z) h& ` ?- [1 p
size: 4 * textureWidth * textureHeight,
( q& o1 r, J* K; t3 M' B* _}). s; X* q. N% ^1 ]; g. z/ ~7 }
* C0 H( C# o& E: R8 Q// 使用指令编码器将纹理拷贝到 GPUBuffer3 [/ o' V% @# ~* n; o+ I
const encoder = device.createCommandEncoder()
$ W% Q o6 \* j+ S3 ?4 Kencoder.copyTextureToBuffer(
0 j/ I; k' A, r7 z3 {2 f) J& F { texture },( J. k0 J0 g! Q8 ]' i. @' e
{ buffer, rowPitch: textureWidth * 4 },
7 s, Y: x+ k4 ^7 i9 N5 M. { [textureWidth, textureHeight],
5 P$ g2 Q- g1 ~ o2 ?)
2 m( i+ y* p; c* \7 l- V: vdevice.submit([encoder.finish()])
/ S1 i: A' x3 Y# N; A' e
7 L$ U& K4 M/ k9 {// 映射,令 CPU 端的内存可以访问到数据' A; I" o6 r; }" z; T
await buffer.mapAsync(GPUMapMode.READ)1 C5 D) l9 o3 s* d3 L& j
// 保存屏幕截图
. L; q0 T4 J6 e( a0 ~/ F3 e' [saveScreenshot(buffer.getMappedRange())
/ T1 N7 d# D/ A0 s5 m# p4 \// 解映射
5 H' c9 n+ U; e. _& K* Obuffer.unmap()# O1 ~* N5 H6 o6 N+ |" H+ E- F2 r
</code></pre>2 ~2 J& x( i' _. p$ c `' x
/ g+ C5 m* U( } |
|