在 WebGPU 中使用時間戳查詢


原文 https://github.com/OmarShehata/webgpu-compute-rasterizer/blob/main/how-to-use-timestamp-queries.md


本文如何使用 WebGPU 的時間戳查詢(timestamp-query)功能來計算你的 GPU 指令執行耗時。

在 WebGPU 中,時間戳查詢是一項可選功能,不一定全部實現版本都有。撰寫本文時,出於安全考慮,在瀏覽器上是禁用的(具體原因參考 gpuweb/gpuweb #2218

image

概述

下面先簡單介紹時間戳查詢的流程:

  • 請求設備對象時加上 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 中啓用時間戳查詢。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章