Upgrade to Pro — share decks privately, control downloads, hide ads and more …

WebGPU 触ってみた

WebGPU 触ってみた

3D何でも勉強会 https://preferred-networks.connpass.com/event/256382/ での発表資料。素人がホントに触ってみたというだけの初歩的な内容です。しかも内容が3Dではなくてゴメンナサイ。
(あっ…、資料に動画を貼っていましたが、これは見れないですね…)

Terada Yuichiro

August 29, 2022
Tweet

More Decks by Terada Yuichiro

Other Decks in Programming

Transcript

  1. 寺田 雄一郎 • Twitter: @u_1roh • GitHub: u1roh • 3D

    CAD 周辺の業界に長くいて、3年弱前に CADDi に入社 • キャディ株式会社 / テクノロジー本部 / MaP チーム ◦ MaP = Manufacturing Protocol • CADDi でのお仕事 ◦ 図面の画像を認識するアルゴリズムを Rust で書く ◦ 図面のCADデータ(DXF/DWG)を認識するアルゴリズムを F# で書く ◦ 図面を描画する Viewer を React + Three.js で書く ◦ 図面に替わる設計情報の machine-readable なプロトコルを考える
  2. ※ Firefox Nightly で動かせなかった… [at 2022-08-28] やったこと • Firefox Nightly

    をダウンロード • about:config で dom.webgpu.enabled を true に • 再起動して WebGPU Samples のページを開く 😭
  3. 4. React + TypeScript のプロジェクトを作る ※ もちろん React でなくてもよい。ReactとWebGPUは関係ない。 1.

    $ npx create-react-app <my-proj-name> —template typescript 2. $ npm install –save-dev @webgpu/types 3. tsconfig.json の “typeRoots” を設定 package.json tsconfig.json
  4. Wikipedia より https://ja.wikipedia.org/wiki/WebGPU • W3C の GPU for Web コミュニティグループで開発

    ◦ Apple, Mozilla, Microsoft, Google 等が参画 • 経緯 ◦ 2017 年、Apple が WebGPU を提案 ▪ Metal をベースとした提案 ◦ その後、Apple の最初の提案は「WebMetal」と改称 ▪ 将来の標準となる WebGPU との混同を避けるため
  5. WebGL との違い • 各種のグラフィックAPIを抽象化する、という意味では似ている • GPGPU (General Purpose computing with

    GPU) がやりやすい ◦ WebGL はグラフィック処理が基本。 GPGPUのために使うには適さなかった。 ◦ WebGPU は GPGPU を “first-class” サポート。 • より整理されたAPI体系 ◦ OpenGL・WebGLの「グローバルな状態遷移のつらみ」を解消。 ◦ CPUの処理とGPUの処理の区別が分かりやすいという印象。 ◦ CPU のマルチスレッドからの制御が可能。 ◦ 遅延レンダリング。
  6. ブラウザ、レンダリングエンジン、WebGPU実装 Chrome Chromium C++ Rust Rust Servo Blink すみません、Safari は状況

    がよく分からず…。今の Preview 版では動かない? Mac の環境を持ってないの で確認できず。
  7. GPUDevice (論理) GPU(物理) Driver (DirectX, Metal, Vulkan) GPUQueue GPUAdapter GPUCommandEncoder

    計算パス GPUBuffer GPUBuffer GPUBuffer GPUComputePipeline or GPURenderPipeline WebGPU APIモデル gpu.requestAdapter() adapter.requestDevice() queue.submit(...)
  8. GPUDevice を取得 const adapter = await navigator.gpu.requestAdapter(); const device =

    await adapter?.requestDevice(); GPUDevice (論理) GPU(物理) Driver (DirectX, Metal, Vulkan) GPUQueue GPUAdapter GPUCommandEncoder 計算パス GPUBuffer GPUBuffer GPUBuffer GPUComputePipeline or GPURenderPipeline gpu.requestAdapter() adapter.requestDevice() queue.submit(...)
  9. compute pipeline を動かす流れ const myComputeShader = device.createShaderModule({ code: ` @compute

    @workgroup_size(16) fn main(…) { … } ` }); const pipeline = device.createComputePipeline({ layout: "auto", compute: { module: myComputeShader, entryPoint: "main" } }); const outputBuf = device.createBuffer({…}); const bindGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 1, resource: { buffer: outputBuf } }] }); const encoder = device.createCommandEncoder(); const pass = encoder.beginComputePass(); pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); pass.dispatchWorkgroups(…); pass.end(); device.queue.submit([encoder.finish()]); GPUDevice (論理) GPUQueue GPUCommandEncoder 計算パス GPUBuffer GPUBuffer GPUBuffer GPUComputePipeline or GPURenderPipeline queue.submit(...)
  10. render pipeline を動かす流れ const myVertexShader = ` @vertex fn main(…)

    { … } `; const myFragmentShader = ` @fragment fn main(…) { … } `; const pipeline = device.createRenderPipeline({ layout: "auto", vertex: { … }, fragment: { … }, }); const buf= device.createBuffer({…}); const bindGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 1, resource: { buffer: buf } }] }); const encoder = device.createCommandEncoder(); const pass = encoder.beginRenderPass({ … }); pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); pass.draw(…); pass.end(); device.queue.submit([encoder.finish()]); GPUDevice (論理) GPUQueue GPUCommandEncoder 計算パス GPUBuffer GPUBuffer GPUBuffer GPUComputePipeline or GPURenderPipeline queue.submit(...)
  11. GPUCanvasContext を取得 const device: GPUDevice = … const canvas: HTMLCanvasElement

    = … const context = canvas.getContext('webgpu'); context?.configure({ device, format: navigator.gpu.getPreferredCanvasFormat(), alphaMode: "opaque" }); const encoder = device.createCommandEncoder(); const pass = encoder.beginRenderPass({ colorAttachments: [{ view: context.getCurrentTexture().createView(), clearValue: clearColor, loadOp: "clear", storeOp: "store" } as GPURenderPassColorAttachment] }); … pass.end(); device.queue.submit([encoder.finish()]); 描画先の Canvas を指定
  12. Vertex Shader と Fragment Shader Vertex Shader (WGSL) @vertex fn

    main( @builtin(vertex_index) i : u32 ) -> @builtin(position) vec4<f32> { let pos = array<vec2<f32>, 3>( vec2<f32>(0.0, 0.5), vec2<f32>(-0.5, -0.5), vec2<f32>(0.5, -0.5), ); return vec4<f32>(pos[i], 0.0, 1.0); } Fragment Shader (WGSL) @fragment fn main() -> @location(0) vec4<f32> { return vec4<f32>(1.0, 0.5, 0.0, 1.0); } 文法がそこはかとなく Rust っぽい
  13. やりかた GPUTexture compute pipeline render pipeline compute shader で マンデルブロ集合の

    テクスチャ画像を生成 スクリーン一杯に 四角形を描画して、 そこにテクスチャを貼り付ける もちろん、render pipeline の fragment shader でマンデルブロ集合を描画するほうが 簡単ですが、compute shader を試したかった ので、敢えてこの方法にトライ
  14. compute pipeline Compute Shader (WGSL) @group(0) @binding(1) var output: texture_storage_2d<rgba8unorm,

    write>; @compute @workgroup_size(16, 16) fn main( @builtin(global_invocation_id) global_id: vec3<u32> ) { // ここでマンデルブロ集合の漸化式を用いて色を決める var color: vec4<f32> = …; // テクスチャに色を書き込む textureStore( output, vec2<i32>(global_id.xy), color ); } パイプラインやパスの構築 const pipeline = device.createComputePipeline({ layout: "auto", compute: { … } }); const IMAGE_SIZE = 512; const texture = device.createTexture({ format: "rgba8unorm", size: [IMAGE_SIZE, IMAGE_SIZE], usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING }); const bindGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [{ binding: 1, resource: texture.createView() }] }); … pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); pass.dispatchWorkgroups( Math.ceil(IMAGE_SIZE / 16), Math.ceil(IMAGE_SIZE / 16));
  15. render pipeline Fragment Shader (WGSL) @group(0) @binding(0) var mySampler: sampler;

    @group(0) @binding(1) var myTexture: texture_2d<f32>; @fragment fn main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> { return textureSample(myTexture, mySampler, uv); } パイプラインやパスの構築 const pipeline = device.createRenderPipeline({ layout: "auto", vertex: { … }, fragment: { … }, }); const sampler = device.createSampler(); const mandelbrot = createMandelbrotTexture(device); const bindGroup = device.createBindGroup({ layout: pipeline.getBindGroupLayout(0), entries: [ { binding: 0, resource: sampler } { binding: 1, resource: mandelbrot.texture.createView() } ] }); … pass.setPipeline(pipeline); pass.setBindGroup(0, bindGroup); pass.draw(4); Vertex Shader (WGSL) const POS = array<vec2<f32>, 4>( vec2<f32>(-1.0, -1.0), vec2<f32>(1.0, -1.0), vec2<f32>(-1.0, 1.0), vec2<f32>(1.0, 1.0), ); const UV = array<vec2<f32>, 4>( vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 1.0), ); struct VertexOutput { @builtin(position) position: vec4<f32>, @location(0) uv: vec2<f32>, } @vertex fn main(@builtin(vertex_index) i : u32) -> VertexOutput { var output: VertexOutput; output.position = vec4<f32>(POS[i], 0.0, 1.0); output.uv = UV[i]; return output; } 四角形を描くので 4頂点
  16. まとめ、所感 • こんな未来が来たらステキ ◦ GPGPU がブラウザで手軽に動かせる ◦ プラットフォームに依存しないグラフィックス APIが確立される •

    とはいえ各プラットフォームの対応状況が気になる ◦ Firefox や wgpu (Rust)、頑張ってほしい ◦ Appleさんも頑張ってほしい、発端は Appleさんなので • wgpu も少し触ってみた ◦ Rust でネイティブGUIアプリで動かせる ◦ 三角形は普通にレンダリングできた。 compute shader はうまく動かなかった。(私がなにか間違えているだけかもしれない) • APIは整理されていて良い感じ ◦ compute pipeline と render pipeline が統一された設計になっていて使いやすい • TypeScript で驚くほど手軽に始められる 3Dのネタが作れなくてゴメンナサイ …