平行光線追蹤

檢視完整原始碼線上檢視已編譯的範例

這是一個使用 WebAssembly、Rust 和 wasm-bindgen 進行執行緒的範例,最終展示一個平行光線追蹤器。這個示範包含許多活動的部分,很遺憾地並不是最容易處理的,但希望這能讓您稍稍體驗在網頁上使用 Rust 執行緒和 Wasm 的感覺。

建置示範

執行緒 WebAssembly 的主要陷阱之一是 Rust 沒有提供啟用執行緒支援的預先編譯目標(例如標準函式庫)。這表示您需要使用適當的 rustc 旗標重新編譯標準函式庫,即 -C target-feature=+atomics,+bulk-memory,+mutable-globals。請注意,這需要 nightly Rust 工具鏈。

為此,您可以使用 Cargo 讀取的 RUSTFLAGS 環境變數

export RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals'

為了重新編譯標準函式庫,建議使用 Cargo 的 -Zbuild-std 功能

cargo build --target wasm32-unknown-unknown -Z build-std=panic_abort,std

請注意,您也可以透過 .cargo/config.toml 進行設定

[unstable]
build-std = ['std', 'panic_abort']

[build]
target = "wasm32-unknown-unknown"
rustflags = '-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals'

在此之後,cargo build 應該會產生一個啟用執行緒的 WebAssembly 檔案,並且標準函式庫也會被適當地編譯。

最後一步是像平常一樣執行 wasm-bindgen,而 wasm-bindgen 不需要額外的設定即可使用執行緒。例如,您可以繼續透過 wasm-pack 執行它。

執行示範

目前,需要使用 --target no-modules--target web 旗標搭配 wasm-bindgen 來執行執行緒程式碼。這是因為 WebAssembly 檔案導入記憶體而不是導出它,因此我們需要在此時掛接 wasm 模組的初始化以提供適當的記憶體物件。此示範使用 --target no-modules,因為 Firefox 不支援 Worker 中的模組。

使用 --target no-modules,您將能夠在每個 Web Worker 內部使用 importScripts 來導入 wasm-bindgen 產生的 shim JS,並使用來自主執行緒的共用記憶體實例來呼叫 wasm_bindgen 初始化函式。預期的用法是,主執行緒上的 WebAssembly 會將其記憶體物件發布到所有其他執行緒以進行實例化。

注意事項

不幸的是,目前在網頁上使用執行緒執行 Wasm 有許多注意事項,儘管有些注意事項是 wasm-bindgen 特有的。以下是一些需要考量和注意的部分,但我們一直在尋找改進的方法,如果您有任何想法,請提出 issue!

  • 瀏覽器中的主執行緒無法封鎖。這表示如果您在主執行緒上執行 WebAssembly 程式碼,您絕對不能封鎖,這表示您甚至無法取得互斥鎖。這是在網頁上很難處理的限制,儘管一個解決方法是在 Web Worker 中專門執行 Wasm,並在主執行緒上執行 JS。可以在所有執行緒上執行相同的 wasm,但您需要非常小心地與主執行緒同步。

  • 設定執行緒環境有點不穩定,而且今天感覺不太順暢。例如,不支援 --target bundler,並且在主執行緒和 Worker 執行緒上都需要非常特定的 shim。這些都可以使用,但有些脆弱,因為沒有標準的方法將 Web Worker 作為 Wasm 執行緒啟動。

  • 目前沒有「執行緒」的標準概念。例如,標準函式庫沒有可行的途徑來實作 std::thread 模組。因此,沒有執行緒結束的概念,TLS 解構子永遠不會執行。我們公開了一個輔助函式 __wbindgen_thread_destroy,它會釋放執行緒堆疊和 TLS。如果呼叫它,它必須是針對特定執行緒,從 Wasm 模組呼叫的最後一個函式。

  • 第一個執行緒之後啟動的任何執行緒,在初始化例程中可能會嘗試隱式阻塞。這是我們設定執行緒堆疊和 TLS 空間的方式所引入的限制。這表示如果你嘗試在主執行緒中執行 Wasm 模組,而你已經在 worker 中執行它,則可能會失敗。

  • 執行 WebAssembly 程式碼的 Web Worker 無法從 JS 接收事件。Web Worker 必須完全返回瀏覽器(理想情況下應偶爾這樣做)才能接收 JS 訊息等。這表示像 rayon 執行緒池這樣的常見範例無法直接應用於 Web。Web 的意圖是讓所有長時間阻塞都發生在瀏覽器本身,而不是在每個執行緒中,但生態系統中許多利用執行緒的 crate 不一定以這種方式設計。

這些注意事項基本上都是繼承自 Web 平台本身,在為執行緒設計應用程式時,它們非常重要。由於這些限制,你不太可能直接從現成的 crate 中「直接使用」。你需要仔細規劃並確保這些陷阱不會在未來造成問題。不過,如前所述,我們一直在積極開發這項支援,因此如果人們對如何改進有任何想法,或者如果 Web 標準發生變化,我們將嘗試更新此文件!

瀏覽器要求

此示範應可在目前最新的 Firefox 和 Chrome 版本中運作,其他瀏覽器也可能會跟進。請注意,執行緒和 SharedArrayBuffer 需要設定 HTTP 標頭才能正常運作。有關更多資訊,請參閱 MDN 上「安全性要求」下的文件,以及Firefox 的推出部落格文章。這表示在本地開發期間,你需要適當配置你的 Web 伺服器,或在瀏覽器中啟用變通方法。