|
|
# ~+ ^5 w2 q2 U8 z0 h! E<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1>; F! _8 L- H I
<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>
- O' J! j b/ r% b% p3 t2 P<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>
k" y3 a( _0 d2 z, ~<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>
' D& s7 `2 i9 ?+ k3 M% v3 y/ }<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>
; c. I, `6 X( v4 N<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })
8 D" B6 G9 s/ Y: T# G- yawait someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节8 M& L3 n! J+ t# O1 m5 w `
# f+ f2 z7 B2 x1 Y- i- u# `// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作
" E) G2 W$ c6 P6 Q+ z4 f</code></pre># I: _" ]. S! i
<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>2 ?% `, L1 @, a9 Q
<pre><code class="language-js">somebuffer.unmap()
' d* G# G) V( l6 X</code></pre>
$ |+ h9 T7 V% o! G4 Z<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
, g! h) z/ } w<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
( ^7 C2 y- [1 z `6 v- M: E j<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>
! O4 ]$ K* ?3 ~2 K: {: e- f$ j<h2 id="创建时映射">创建时映射</h2>0 @, b/ {+ T2 D' c" k
<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>
/ Q, `8 O$ i2 y<pre><code class="language-js">const buffer = device.createBuffer({- s/ P4 \3 M' {& J. i
usage: GPUBufferUsage.UNIFORM,3 E3 p% ^$ U5 H9 T" C& f
size: 256,
a* d* j ^2 y# ? mappedAtCreation: true,
" N; o- [; w' t* D3 f: B5 p& N) D0 f})
" G: p8 Z9 ], Q% h: g7 _7 b f o// 然后马上就可以获取映射后的 ArrayBuffer0 d( C9 I6 R0 l7 {: F1 H8 F: _
const mappedArrayBuffer = buffer.getMappedRange()
H, H* U- M+ t; b( x+ k7 f* s4 ?' R/ I& k7 a1 i1 \
/* 在这里执行一些写入操作 */
6 r# v: r8 Q# _0 ?' i# I; p' K9 Z$ @4 d+ L! m" q2 G' f
// 解映射,还管理权给 GPU
+ {2 H$ X3 [8 vbuffer.unmap()
8 h* { m% O# ]+ g) X: v. n</code></pre>
, A, n1 l1 w$ b( W/ S% H<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>" x* K( N5 A' l. Q! ^& c
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>7 E/ j4 D: n5 l- g. [% S
<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>
" z/ \* E! X! h' W9 O0 _ ^% }" g( h: z<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>4 `/ I. c& u/ I! O9 _
<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>$ s# x2 i6 _1 i* B; {% j% I
<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>
8 S: j# ~3 g- _9 N, a8 O<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>
$ t* H3 S- @* X<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>
# T2 F2 Y1 p$ D. _8 {2 W1 g0 N<pre><code class="language-js">const texture = getTheRenderedTexture()' I: L* D9 x2 l* q) I
( b/ R* h& d! Y2 lconst readbackBuffer = device.createBuffer({6 C: T$ g& E% \
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
# ~# J' `- d- `$ u) D' O6 N. K9 y5 L size: 4 * textureWidth * textureHeight,
4 V; F' ?* X0 w4 O# C# I}) S; z! { L: [
% ^* ~* s% J8 V& B! b) C0 F/ b5 C
// 使用指令编码器将纹理拷贝到 GPUBuffer
( @. E: ?/ n' ^' Mconst encoder = device.createCommandEncoder()& V; Y0 r. `3 O% \. e2 D# `7 Q
encoder.copyTextureToBuffer(" f# f$ n& f/ R& m' v5 ?
{ texture },
9 a* }# z5 _6 \0 N; y8 h: h' C/ O { buffer, rowPitch: textureWidth * 4 },* I$ D! i$ ~# y* v( o7 }! e$ F
[textureWidth, textureHeight],
5 I) ~% W+ h) \* a7 })5 |6 r9 c/ A! t% J& w4 D! G* p3 A
device.submit([encoder.finish()])
0 C2 H, b; k9 {* w5 x) q2 p$ q C$ N" P8 |' d! k
// 映射,令 CPU 端的内存可以访问到数据6 w H1 O" k% f2 ^
await buffer.mapAsync(GPUMapMode.READ)3 y! J) _6 s6 P+ G$ W1 P
// 保存屏幕截图
6 L4 j( x: m) g. osaveScreenshot(buffer.getMappedRange()). f9 D( k; E% B
// 解映射
* b9 N; _0 y! ~buffer.unmap()
/ s5 g) o% Q% F) ], Z</code></pre>4 V& Z! T) K: v, k* A- ~. f/ c* x( I
+ e" P& h8 C8 w/ {! E
|
|