|
' s: L8 m8 \. V: E1 [$ i/ l6 h<h1 id="1-什么是缓冲映射">1. 什么是缓冲映射</h1> |- i9 x- g% P; A6 } J
<p>就不给定义了,直接简单的说,映射(Mapping)后的某块显存,就能被 CPU 访问。</p>
5 N5 t. E5 _5 O4 ]8 B# t<p>三大图形 API(D3D12、Vulkan、Metal)的 Buffer(指显存)映射后,CPU 就能访问它了,此时注意,GPU 仍然可以访问这块显存。这就会导致一个问题:IO冲突,这就需要程序考量这个问题了。</p>
( B4 |9 j! p; \: P<p>WebGPU 禁止了这个行为,改用传递“所有权”来表示映射后的状态,颇具 Rust 的哲学。每一个时刻,CPU 和 GPU 是单边访问显存的,也就避免了竞争和冲突。</p>0 t3 Z" G& v; U1 `% `1 e2 U
<p>当 JavaScript 请求映射显存时,所有权并不是马上就能移交给 CPU 的,GPU 这个时候可能手头上还有别的处理显存的操作。所以,<code>GPUBuffer</code> 的映射方法是一个异步方法:</p>+ l/ d0 r, i; V" \, L* R
<pre><code class="language-js">const someBuffer = device.createBuffer({ /* ... */ })+ @* {5 ^9 K* D5 R) K
await someBuffer.mapAsync(GPUMapMode.READ, 0, 4) // 从 0 开始,只映射 4 个字节, x: p0 S$ d, c8 D+ O' f, z
@6 F D4 U5 h: u; p7 |// 之后就可以使用 getMappedRange 方法获取其对应的 ArrayBuffer 进行缓冲操作
( }; } J, _/ q4 m</code></pre>; O' t7 z/ X" G
<p>不过,解映射操作倒是一个同步操作,CPU 用完后就可以解映射:</p>4 U9 ~+ P' Z6 ]6 f
<pre><code class="language-js">somebuffer.unmap()) t" P- s$ Y0 R/ e
</code></pre>
& [+ d) c: r* c! F7 s/ z7 L<p>注意,<code>mapAsync</code> 方法将会直接在 WebGPU 内部往设备的默认队列中压入一个操作,此方法作用于 WebGPU 中三大时间轴中的 <strong>队列时间轴</strong>。而且在 mapAsync 成功后,内存才会增加(实测)。</p>
J6 K8 `9 s9 g! ~* H* G2 j) x<p>当向队列提交指令缓冲后(此指令缓冲的某个渲染通道要用到这块 GPUBuffer),内存上的数据才会提交给 GPU(猜测)。</p>
& c7 T' { ^6 d' A' A<p>由于测试地不多,我在调用 <code>destroy</code> 方法后并未显著看到内存的变少,希望有朋友能测试。</p>
( @/ E6 D6 h: C D. F# k- F<h2 id="创建时映射">创建时映射</h2>
6 W+ m8 h$ j y- d# O<p>可以在创建缓冲时传递 <code>mappedAtCreation: true</code>,这样甚至都不需要声明其 usage 带有 <code>GPUBufferUsage.MAP_WRITE</code></p>
2 ?' ^* Q/ |' I9 {. z/ p% D! v3 u- w& i<pre><code class="language-js">const buffer = device.createBuffer({! u0 `4 ]9 ~( G) o
usage: GPUBufferUsage.UNIFORM,
$ S: [' s) Y% b# _. V8 w size: 256,
' |7 G- l. `! F; [1 i+ W" } mappedAtCreation: true,
1 `1 q) c* a9 O, C& r D}). k8 ?/ Q0 {- Z; {8 E" ]( V; n. ^) S+ `
// 然后马上就可以获取映射后的 ArrayBuffer# _7 B+ S* N3 q" J! @3 a
const mappedArrayBuffer = buffer.getMappedRange()
. i% E, X" f- y4 ?% R4 R& D4 U! q( }* l1 E+ K2 |% l
/* 在这里执行一些写入操作 */" Z& S3 B, G* Y. e5 d& i7 E
; I* p2 ]: [$ u
// 解映射,还管理权给 GPU; e) C7 k0 S3 Y
buffer.unmap()
5 T; x5 J m0 Q" [</code></pre>
/ L& {% U8 b+ {) c9 k3 u<h1 id="2-缓冲数据的流向">2 缓冲数据的流向</h1>( ^& T3 D) s* _, X8 r6 K* u0 ~ n
<h2 id="21-cpu-至-gpu">2.1 CPU 至 GPU</h2>& G& m* g6 w; X9 }6 I
<p>JavaScript 这端会在 rAF 中频繁地将大量数据传递给 GPUBuffer 映射出来的 ArrayBuffer,然后随着解映射、提交指令缓冲到队列,最后传递给 GPU.</p>
" V% O/ @* j) W+ [1 _) M<p>上述最常见的例子莫过于传递每一帧所需的 VertexBuffer、UniformBuffer 以及计算通道所需的 StorageBuffer 等。</p>: g/ K% B# U# r5 ^4 x- ?
<p>使用队列对象的 <code>writeBuffer</code> 方法写入缓冲对象是非常高效率的,但是与用来写入的映射后的一个 GPUBuffer 相比,<code>writeBuffer</code> 有一个额外的拷贝操作。推测会影响性能,虽然官方推荐的例子中有很多 writeBuffer 的操作,大多数是用于 UniformBuffer 的更新。</p>7 O9 K8 n' A! D+ ], J- L2 x( x( [
<h2 id="22-gpu-至-cpu">2.2 GPU 至 CPU</h2>
( ] T+ Y0 ~+ K<p>这样反向的传递比较少,但也不是没有。譬如屏幕截图(保存颜色附件到 ArrayBuffer)、计算通道的结果统计等,就需要从 GPU 的计算结果中获取数据。</p>
! Y" S; v1 I4 J<p>譬如,官方给的从渲染的纹理中获取像素数据例子:</p>
0 V: ^: I) b) l- F<pre><code class="language-js">const texture = getTheRenderedTexture()
( B8 y7 B/ O. R' n+ I+ h3 S& O9 v( l, s9 _; [9 J* \( y b; V' S
const readbackBuffer = device.createBuffer({2 C+ z' ]- q, q
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
% m3 v# z0 a! u0 `, c$ G; U size: 4 * textureWidth * textureHeight,
N+ g+ r4 x& ? r/ b5 W})
8 [& g+ J9 p7 P
( }8 B( F) l4 U2 h- k) a+ j9 g// 使用指令编码器将纹理拷贝到 GPUBuffer
/ M" e* z+ R7 Oconst encoder = device.createCommandEncoder()
! r/ w7 m* t0 I2 l& j5 }encoder.copyTextureToBuffer(
' M. t- T, C# y& M% o { texture },# s% Z2 C: s( m* `& g) q* _
{ buffer, rowPitch: textureWidth * 4 },
8 o# O. ?6 z3 Q. o% q# w [textureWidth, textureHeight],6 Z7 V+ A6 T: `/ ~0 T4 I0 Z0 ?
)
- G. q9 {7 N1 ^ v4 T+ K$ m K1 Zdevice.submit([encoder.finish()])+ H1 f9 z6 P. N; c2 r( v
* j: D: Q+ c7 i, W& D
// 映射,令 CPU 端的内存可以访问到数据2 G/ w8 s8 E$ A' r* q1 ^8 B, z* w
await buffer.mapAsync(GPUMapMode.READ)
* j" g3 k) U1 S5 q2 @5 a( d* y! x0 s// 保存屏幕截图
0 f2 h: z; _+ IsaveScreenshot(buffer.getMappedRange())" u( t8 {( K/ _) J. t
// 解映射9 n: g' J& T' v0 N! @; N1 O! h
buffer.unmap()
* ]/ W# u6 ?! O' O( Q4 {</code></pre>
3 u' s+ r$ w% }7 D) g$ n
/ W! e2 j2 h7 u5 c% U. S( Q |
|