原文 https://github.com/OmarShehata/webgpu-compute-rasterizer/blob/main/how-to-use-timestamp-queries.md
本文如何使用 WebGPU 的時間戳查詢(timestamp-query)功能來計算你的 GPU 指令執行耗時。
在 WebGPU 中,時間戳查詢是一項可選功能,不一定全部實現版本都有。撰寫本文時,出於安全考慮,在瀏覽器上是禁用的(具體原因參考 gpuweb/gpuweb #2218)
概述
下面先簡單介紹時間戳查詢的流程:
-
請求設備對象時加上
timestamp-query
功能請求 -
創建一個容量爲 N 的查詢集(當前幀要存多少個時間戳)
-
創建一個存儲型緩衝對象(storage-buffer),大小爲 8×N 字節,因爲時間戳查詢的結果需要用 64 位存儲;
-
調用
commandEncoder.writeTimestamp
方法記錄時間戳;它會在所有的指令結束後記錄時間戳; -
調用
commandEncoder.resolveQuerySet
方法將時間戳查詢結果寫入存儲型緩衝 -
將存儲型緩衝複製到 CPU 可讀的內存中,解碼爲 BigInt64Array(參考 BigInt - JavaScript | MDN)
按步教學
可在這個 PR 中找到完整的示例:Example usage of timestamp queries by OmarShehata · Pull Request #5 · OmarShehata/webgpu-compute-rasterizer · GitHub
0. 讓瀏覽器具備時間戳查詢功能
默認瀏覽器是關閉不安全的 API 的,使用下列參數啓動 Chrome 瀏覽器即可:
--disable-dawn-features=disallow_unsafe_apis
譯者注:從
--disable-dawn-features
可以看出,這個啓動參數是 Chrome 系特有的,火狐和 Safari 不通用。具體操作可以右擊 Chrome 或 Edge 的快捷方式,在“屬性” - “目標”後跟隨這一串字符串。
1. 創建 Queryset 和緩衝對象
請求設備對象時,需要把 timestamp-query
添加進 requiredFeatures
數組中:
const device = await adapter.requestDevice({
requiredFeatures: ["timestamp-query"],
})
如果瀏覽器未啓用時間戳查詢或者不支持,就會報錯:
Uncaught (in promise) TypeError: Failed to execute 'requestDevice' on 'GPUAdapter': Unsupported feature: timestamp-query
然後,創建一個查詢集和查詢緩衝對象:
const capacity = 3 // 要存多少個查詢結果
const querySet = device.createQuerySet({
type: "timestamp",
count: capacity,
})
const queryBuffer = device.createBuffer({
size: 8 * capacity,
usage: GPUBufferUsage.QUERY_RESOLVE
| GPUBufferUsage.STORAGE
| GPUBufferUsage.COPY_SRC
| GPUBufferUsage.COPY_DST,
})
2. 寫入時間戳
在調配渲染管線的代碼之間,調用 commandEncoder.writeTimestamp(querySet, index)
方法記錄時間戳:
// 在各種指令編碼過程中記錄時間戳
commandEncoder.writeTimestamp(querySet, 0) // 初始時間戳
// draw(...)
commandEncoder.writeTimestamp(querySet, 1)
index
<= 定義的容量值 - 1.
3. 解析時間戳到緩衝對象中
在每一幀代碼的末尾,調用 commandEncoder.resolveQuerySet
方法,以正確寫入存儲型緩衝對象:
commandEncoder.resolveQuerySet(
querySet,
0, // 從哪個查詢開始
capacity, // 要寫入多少個查詢
queryBuffer,
0 // 寫入緩衝對象的偏移值
)
4. 讀取查詢結果
即從存儲型緩衝對象讀取數據。獲取 ArrayBuffer 後,用 BigInt 類型數組讀取即可。時間戳值的單位是納秒。
// === `commandEncoder.finish()` 調用之後 ===
// 使用下面的 readBuffer 函數讀取 queryBuffer 對象中的數據
const arrayBuffer = await readBuffer(device, queryBuffer);
// 使用 BigInt 類型數組讀取數據
const timingsNanoseconds = new BigInt64Array(arrayBuffer);
const readBuffer = async (device, buffer) => {
const size = buffer.size
const gpuReadBuffer = device.createBuffer({
size,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
})
const copyEncoder = device.createCommandEncoder()
copyEncoder.copyBufferToBuffer(buffer, 0, gpuReadBuffer, 0, size)
const copyCommands = copyEncoder.finish()
device.queue.submit([copyCommands])
await gpuReadBuffer.mapAsync(GPUMapMode.READ)
return gpuReadBuffer.getMappedRange()
}
5. (可選)添加標籤
爲了讓輸出信息更加友好,可將每個時間戳加上標籤,輸出的時候有利於辨別差異。將納秒轉爲毫秒也有用。
致謝
非常感謝 Markus Schütz 在 Potree 關於 WebGPU 有關實現中 提供的時間戳查詢示例。
感謝顧揚在 問題 3354 中解釋瞭如何在 Chrome 中啓用時間戳查詢。