Hello Triangle
この回では、WebGPUでシンプルな三角形を描画してみようと思います。
WebGPUの初期化
WebGPUの初期化はWebGLよりも複雑です。
WebGLではCanvas要素からgetContext("webgl"または"webgl2")としてWebGLレンダリングコンテキストを取得するだけでした。
const gl = canvas.getContext("webgl2");
一方、WebGPUではcontextの取得だけではなくdeviceの取得という作業を行う必要があります。
// webgpuコンテキストの取得 const context = canvas.getContext('webgpu') as GPUCanvasContext; // deviceの取得 const g_adapter = await navigator.gpu.requestAdapter(); const g_device = await g_adapter.requestDevice();
adapterとdeviceの違いですが、adapterは物理デバイス(物理的なGPU)、deviceは論理デバイス(抽象化したGPU)を指しています。
Vulkanなどの一般的な低レベルAPIでは、物理デバイスからはGPUのベンダー情報など、多くのベンダー固有の情報を取得することができるようですが、現状のWebGPUではあまり固有の情報は取れないようです。
次に、contextの設定を行います。
const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); context.configure({ device: g_device, format: presentationFormat, alphaMode: 'opaque', // or 'premultiplied' });
context.configure関数でdevice, format, alphaModeの3つの指定を行います。
deviceには先ほど取得した論理デバイスg_deviceを指定します。
formatには、navigator.gpu.getPreferredCanvasFormat()でcanvasのネイティブのピクセルフォーマットが取得できるのでそれを指定します。この関数では通常は"rgba8unorm"または"bgra8unorm"というフォーマットが返されます。初歩的な利用では、この関数をおまじないとして呼ぶようにしましょう。
alphaModeには'opaque'という文字列を指定します。このalphaModeはCanvasと背景となるHTML要素との合成方法を設定するものです。'opaque'の場合は、WebGPUの描画内容で完全に上書きします。alphaModeには’premultiplied'という値も設定することができ、こちらの場合はWebGPUの描画の結果、Canvas側のピクセルのアルファ要素が1未満だった場合、そのピクセルはcanvas側と背景のHTML要素の色で混合されます。
RenderPipelineの設定
次に、RenderPipelineの設定を行います。
WebGLではGPUに対する設定を様々なgl関数を用いて五月雨式に設定していましたが、WebGPUをはじめとする現在の低レベルAPIでは、一般にPipelineStateObject (PSO)と呼ばれるオブジェクトに対して、GPUに対する大半の設定をまとめて行います。
PSOはGPUの各処理工程(パイプライン工程)に対する設定を抽象化したものです。この設定をあらかじめいくつも作り置きし、状況に応じてGPUにアタッチすることで、GPUのパイプライン状態を高速に変更することが可能となります。
WebGPUではこのPSOはRenderPipelineという名称で呼ばれています。
WebGPUの初期化が終わったら、次にこのRenderPipelineの設定に入ります。
RenderPipelineでは主に設定するカテゴリが5種類あります。
- GPUVertexState
- GPUFragmentState
- GPUPrimitiveState
- GPUDepthStencilState
- GPUMultisampleState
このうち、ほぼ必須なのは最初の3つです。
この3つを設定するコードを見てみましょう。
// create a render pipeline const pipeline = g_device.createRenderPipeline({ layout: 'auto', vertex: { module: g_device.createShaderModule({ code: vertWGSL, }), entryPoint: 'main', }, fragment: { module: g_device.createShaderModule({ code: fragWGSL, }), entryPoint: 'main', targets: [ // 0 { // @location(0) in fragment shader format: presentationFormat, }, ], }, primitive: { topology: 'triangle-list', }, });
まず最初のlayoutですが、ここにはほとんどの場合'auto'を指定します。最初はおまじないとして考えておけば結構です。
layout: 'auto',
次に、vertexです。論理デバイスg_deviceのcreateShaderModule関数にシェーダー文字列を渡しています。WebGPUのシェーダー言語はWGSLというWebGLのGLSLとは異なる言語なのですが、これについては後述します。
また、entiryPointでシェーダー内のエントリーポイントとなる関数名を指定しています。
vertex: { module: g_device.createShaderModule({ code: vertWGSL, }), entryPoint: 'main', },
次にfragmentです。moduleとentryPointに対する設定はvertexと同様ですが、他にtargetsというプロパティがあります。これは描画先のレンダーターゲットのフォーマットを指定するものです。ここにはcontextの設定時と同じnavigator.gpu.getPreferredCanvasFormat()で得られたフォーマットを指定しましょう。
fragment: { module: g_device.createShaderModule({ code: fragWGSL, }), entryPoint: 'main', targets: [ // 0 { // @location(0) in fragment shader format: presentationFormat, }, ],
最後にprimitiveです。ここでは描画しようとしているジオメトリのトポロジーの種類を文字列で指定します。
primitive: { topology: 'triangle-list', },