|
|
7 K( M# v- S+ Q1 [; ]<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1>7 P; S2 c9 ~9 n6 A/ O2 V
<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>) O" D( P% x# k; q3 T) l- e
<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>
2 T ?5 N" ^4 v! v<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>2 i% u9 v1 s8 ?& m1 b
<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>% Z. U* Y/ T3 k. K
<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })) }- p3 B) m! P; c5 X' Z; k, A3 [
await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节' x; ^5 r) ^& S2 ?9 A! n
* P: y$ c2 [' ? s! q( P2 b, t// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作
& P( ?/ a3 D# \- g</code></pre># s' X; P% F2 s6 u/ G/ T& a1 H; L
<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>4 E; _; _: ? J" z5 d, }
<pre><code class="language-js">somebuffer.unmap()/ Q7 D1 g0 B! k7 i/ Y$ M
</code></pre>; y+ v. m$ I) O# D/ t# F1 I
<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
& _8 {, `0 z2 V) r5 q( c9 Y<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
: f% d/ F3 ^. x/ {<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>
# n- ~- x: R) r5 I5 q5 L<h2 id="创建时映射">创建时映射</h2>
) [0 b/ p3 I! y, W<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>( L( H' ?/ P) i3 q. t' M0 Q. R0 n/ u
<pre><code class="language-js">const buffer = device.createBuffer({+ w3 ?" q( |8 X6 c) ?( i- w
usage: GPUBufferUsage.UNIFORM,( Y- a- l# p% e6 o. Q& O' I
size: 256,3 d) P8 J: p6 S# W
mappedAtCreation: true,0 P: n; P2 v2 {) M
})
& d, \ F3 w4 |/ I// 然后马上就可以获取映射后的 ArrayBuffer
4 V5 W, e2 F/ o( Y& gconst mappedArrayBuffer = buffer.getMappedRange()
5 b; J1 J6 Y( `+ O! X4 `% Y% o' t$ |) H& {( }. j1 T, o* V
/* 在这里执行一些写入操作 */# n6 N0 e( A) K: Z3 k) P Q0 s3 k4 s
. ~3 H# ?" `# Q- Z2 F8 S* x1 O// 解映射,还管理权给 GPU0 F" ^( _! ~( G L4 N: |4 ]" V, v
buffer.unmap()+ M9 ?$ o Z/ I
</code></pre>
U# Z* L* t# E i. Y<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>) O# f8 e7 [9 N0 a* j
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2> G: v( Y4 F2 {) W$ J
<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>: M9 A- N0 z4 F) J% M4 J9 \
<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>" B2 f/ e3 S8 E! X% b+ b6 S
<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>1 ^/ Y% p/ m1 f G. T3 }
<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>
# P- s" w3 Y: {" L( q! Q0 O2 X<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>* k9 t& C/ n9 H0 o3 e; I
<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>9 D; R5 A' L& A! T# M8 u
<pre><code class="language-js">const texture = getTheRenderedTexture()
2 ? b' \9 K" Z; Q3 z
2 q! R) ~5 L! S5 S. ^const readbackBuffer = device.createBuffer({/ Y8 M) G; y7 D5 r7 M
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,: ^, y* J8 _5 M# K5 X- L
size: 4 * textureWidth * textureHeight,; Q/ {- V+ Q6 B1 }
})3 L! C6 [& c6 s4 S' ?3 H
+ u" t g# `6 z$ l+ B# E4 p
// 使用指令编码器将纹理拷贝到 GPUBuffer
~1 k' L" r! t, }2 b& Q% Rconst encoder = device.createCommandEncoder()& H: V/ q: f. w0 K8 T w; q1 z- @
encoder.copyTextureToBuffer(
$ T9 x1 G5 r1 H# e' l { texture },
! W' q& s/ M# R& ` k { buffer, rowPitch: textureWidth * 4 },
1 S1 T8 H8 o0 V# V/ p [textureWidth, textureHeight], L% @9 ?; K6 f' b
)
% s# R/ I7 e2 f4 ~* q( Rdevice.submit([encoder.finish()])% _+ D' r) B( W/ r
0 q `3 p* A; a3 x" w4 @
// 映射,令 CPU 端的内存可以访问到数据
+ H, S+ X* F) [5 O) z- }5 z0 a: Oawait buffer.mapAsync(GPUMapMode.READ)
* v; ^- |1 ~0 T% J* ]- u0 z1 V// 保存屏幕截图+ _2 S4 N) k2 y- p6 e
saveScreenshot(buffer.getMappedRange())1 n2 ^' R8 H( E
// 解映射
, B3 ~0 X+ ]. u" l: C. bbuffer.unmap()6 Z5 i! L. b) c
</code></pre>
, a. a; T4 U! e& D. L& \7 Z9 |
, J" R" v- {/ |. F |
|