|
|
; }) S- ~, D' |+ j<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1>8 o* J/ c1 Q U. a' L ~7 O
<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>
- u9 U5 f& d8 D* W0 n v/ v<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>; \! W3 C1 k6 R0 ~$ V+ p
<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>3 r. H8 w. B1 d7 Z
<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>
9 a9 G1 {' v; U" F- ~7 O<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })
. n: ~0 {- A k% G. y# i" v( f5 F9 xawait someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节
* ^7 A" @" I" |$ q8 s3 [; @1 B. o8 u9 N' K9 `/ d1 g3 g. Z
// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作# } v% @' b: m# f2 a2 u9 W
</code></pre>
, ?$ \4 i9 k2 l# X4 m! o0 L<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>( k- g5 `+ J4 F# l
<pre><code class="language-js">somebuffer.unmap()
" [$ i, M' R1 { e</code></pre>
$ y4 a7 z) C$ B- M" V<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
2 n3 z, G3 i! ]8 h, R1 F<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>; G$ Q- f6 S& J( T+ j/ v
<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>1 l3 L) F `# B, A5 Q
<h2 id="创建时映射">创建时映射</h2>& W5 S! }2 n4 J; \) b+ `
<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>5 V2 g% u' g/ `8 G3 ?& Q+ m% R- J
<pre><code class="language-js">const buffer = device.createBuffer({
" d" f/ t6 o$ u* F; J% L usage: GPUBufferUsage.UNIFORM,( i/ X9 c2 g% ]$ j1 u
size: 256,
$ d3 m& I( w9 o1 I mappedAtCreation: true,4 X( s* s o2 T" b+ q6 m- @$ E$ e5 A
})0 d; P3 u+ d3 y3 B3 c# o- A
// 然后马上就可以获取映射后的 ArrayBuffer
) |8 w/ H" J; i5 F3 {. Fconst mappedArrayBuffer = buffer.getMappedRange()( O+ f4 W5 D Q9 X
# M2 Z! T4 ?' I q) d
/* 在这里执行一些写入操作 */
2 r- [$ _, ~) K- O* @0 S2 i4 e/ ^( `, t; R' U/ K
// 解映射,还管理权给 GPU0 q) q E. o. ~ H7 r5 F
buffer.unmap()
- r8 L$ S7 T2 Q% B) s0 j: A" p- f</code></pre>
" e" {' H' V2 a/ Z `" {6 i, Q<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>/ d, F4 ~4 q0 I) m& D4 b
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>
! p* q2 Y- S. }# y- i<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p># `( \/ s6 ] [( m( G/ C
<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>
8 Y. S4 x8 C3 t6 R+ h<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>- Y2 v; ?7 [5 a1 B% D8 M
<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>
6 |, U1 ^1 S/ t5 s<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>
/ ^; J( Z. \/ y; V- L- Y4 y<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>
b( ~! _& I& v<pre><code class="language-js">const texture = getTheRenderedTexture()
0 _+ ~# m/ H/ W" F. ^& _; N/ Q( z' z
const readbackBuffer = device.createBuffer({2 j4 `" w% e/ {/ K, `3 l
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
+ w: i, n. E* N$ I5 n. X size: 4 * textureWidth * textureHeight, x6 Z7 ~: N9 ]4 h! B$ T: D
})* y( h% w5 n' Y) G; a
L7 N K5 Y d' X! f7 v; v- u" B// 使用指令编码器将纹理拷贝到 GPUBuffer ?# L. R, A G# q- F
const encoder = device.createCommandEncoder()
0 v4 G. ^, [5 o+ }encoder.copyTextureToBuffer(, h% L: X3 p X6 I3 Z3 {8 K
{ texture },
4 }5 V$ ]* d1 G+ ~9 U3 \ { buffer, rowPitch: textureWidth * 4 },: r9 l4 V; G/ E$ ?6 t6 x
[textureWidth, textureHeight],0 @" e( N$ P& m4 b7 ? v% b9 j( X
)
5 V, b J+ w. w# c1 wdevice.submit([encoder.finish()])
! f% m- _- ?! B0 m) x4 Z, e+ y/ X$ t1 s0 n4 W2 F3 `' v5 F9 M- v
// 映射,令 CPU 端的内存可以访问到数据
- q8 q( R* m% Z e9 g5 X8 W% f- t2 eawait buffer.mapAsync(GPUMapMode.READ)2 b" Q2 o- U/ X8 z6 d* v
// 保存屏幕截图# q4 \9 a5 Y$ a7 |' r
saveScreenshot(buffer.getMappedRange())3 o* Z8 L. v5 B
// 解映射# X, H. ~* d5 Z* i0 @( o+ p; K
buffer.unmap()* U1 T1 v/ J1 O1 q7 O M
</code></pre>2 }9 t6 h$ h7 |' }
U; p8 M' D3 j. x, \
|
|