Vidéo
Nous continuons cette série en examinant comment configurer le pipeline de rendu de WebGPU pour rendre notre triangle de WebGPU :
Ne pouvez-vous pas attendre la fin de la série ?
Si vous souhaitez aller de l’avant sans attendre la prochaine vidéo de la série, je vous recommande de faire défiler jusqu’au code ci-dessous ou de consulter mon article : dessinons un triangle avec WebGPU.
Le code de la série vidéo
Au cours de cette série, nous étudierons ce code que vous pouvez retrouver dans cet article où on commence la série.
Avant de pouvoir rendre ce code, vous devez télécharger un navigateur capable d’exécuter du code WebGPU.
Le pipeline de rendu de WebGPU
Un pipeline de rendu est représenté comme une fonction complète exécutée par une combinaison du hardware GPU, des driveurs sous-jacents et de l’agent utilisateur.
Le but d’un pipeline de rendu est de traiter les données d’entrée, telles que les sommets, et de produire une sortie telle que les couleurs sur notre écran.
Les docs de WebGPU nous indiquent qu’un pipeline de rendu de WebGPU se compose avec les étapes suivantes dans cet ordre particulier :
- Stockage des sommets, contrôlé par
GPUVertexState.buffers
- Shader de vertex, contrôlé par
GPUVertexState
- Assemblage primitif, contrôlé par
GPUPrimitiveState
- Rastérisation, contrôlé par
GPUPrimitiveState
,GPUDepthStencilState
, etGPUMultisampleState
- Shader de fragment, contrôlé par
GPUFragmentState
- Test et opération Stencil, contrôlé par
GPUDepthStencilState
- Test de profondeur et écriture, contrôlé par
GPUDepthStencilState
- Fusion des sorties, contrôlé par
GPUFragmentState.targets
.
Nous avons déjà rencontré certaines de ces étapes au cours des deux derniers articles !
Par exemple, nous avons déjà défini et stocké nos sommets.
Nous avons également déjà défini nos shaders de vertex et de fragment.
La prochaine étape consistera à rassembler tous ces différents bits dans une configuration de pipeline de rendu cohérent de type GPURenderPipeline
. Faisons-le maintenant !
Configuration du pipeline de rendu WebGPU
Avant de passer au code, il y a encore un point important que je veux mentionner…
Notez que toutes les parties du pipeline de rendu ne sont pas configurables.
Étapes programmables
Les étapes du pipeline que nous pouvons configurer sont considérées comme programmables.
Quelques exemples d’étapes programmables seraient le contenu du shader de vertex et de fragment, car nous sommes responsables de l’écriture du code pour chacun de ces types de shader.
Étapes fixes
Les étapes du pipeline qui ne sont pas configurables sont considérées comme fixes.
Un exemple d’étapes fixes serait le traitement qu’un sommet subit avant la rastérisation.
Vous pouvez visiter ce lien pour en savoir plus sur les différentes étapes du pipeline.
Le code
Comme prévu, nous devons utiliser le GPUDevice
, device
, afin de communiquer avec notre GPU.
Afin de configurer notre pipeline de rendu, la méthode que nous devons appeler sur notre device
est createRenderPipeline()
.
const pipeline = device.createRenderPipeline({
layout: "auto",
vertex: {
module: shaderModule,
entryPoint: "vertex_main",
buffers: vertexBuffersDescriptors,
},
fragment: {
module: shaderModule,
entryPoint: "fragment_main",
targets: [
{
format: presentationFormat,
},
],
},
primitive: {
topology: "triangle-list",
},
});
Nous regarderons ensemble comment configurer notre pipeline dans les parties suivantes !
layout
Le champ layout
signifie le ‘layout’, ou configuration, de notre pipeline de WebGPU.
Nous y mettons une valeur d’auto
, qui signifie que nous voulons utiliser une configuration simple pour notre pipeline.
Cependant, faites attention avec la valeur auto
, car (les docs de WebGPU nous donnent l’avertissement)[https://www.w3.org/TR/webgpu/#pipeline-base] que le layout configuré avec auto
n’est pas recommandé pour les layouts plus complexes.
Pour l’instant, pour ne rendre qu’un triangle, auto
marche assez bien.
vertex, GPUVertexState
Regardons que le champ vertex
est de type GPUVertexState
:
vertex: {
module: shaderModule,
entryPoint: "vertex_main",
buffers: vertexBuffersDescriptors,
},
module
Le champ module
contiendra notre configuration de shaders. Nous avons déjà défini la valeur shaderModule
dans l’article précédent, mais je l’inclurai pour que vous ne deviez pas suivre le lien :
const shaderModule = device.createShaderModule({
code: `
struct VertexOut {
@builtin(position) position : vec4<f32>,
@location(0) color : vec4<f32>,
};
@vertex
fn vertex_main(@location(0) position: vec4<f32>,
@location(1) color: vec4<f32>) -> VertexOut
{
var output : VertexOut;
output.position = position;
output.color = color;
return output;
}
@fragment
fn fragment_main(fragData: VertexOut) -> @location(0) vec4<f32>
{
return fragData.color;
}
`,
});
entryPoint
Le champ entryPoint
signifie le nom de la fonction de notre shader de vertex.
Nous y mettons vertex_main
, car il correspond au nom que nous avons déjà défini dans notre définition du shader de vertex.
buffers
Le champ buffers
s’attend aux descriptors
de notre tampon de sommets.
Rappelons que nous avons déjà défini un descripteur, vertexBuffersDescriptors
, comme suit :
const vertexBuffersDescriptors = [
{
attributes: [
{
// Position
shaderLocation: 0,
offset: 0,
format: "float32x4",
},
{
// Color
shaderLocation: 1,
offset: 16,
format: "float32x4",
},
],
arrayStride: 32,
stepMode: "vertex", // https://www.w3.org/TR/webgpu/#enumdef-gpuvertexstepmode
},
];
fragment, GPUFragmentState
Ensuite, regardons le champ fragment
est de type GPUFragmentState
:
fragment: {
module: shaderModule,
entryPoint: "fragment_main",
targets: [
{
format: presentationFormat,
},
],
},
module
Nous avons déjà parlé de ce champ en parlant du champ vertex
. Il suffit donc d’utiliser la valeur shaderModule
.
entryPoint
Égale à la configuration du champ vertex
, nous mettons la valeur fragment_main
pour ce champ, car il correspond au nom que nous avons déjà défini dans notre définition du shader de fragment.
targets
Le champ targets
tient une liste de colorState
s.
Bref, on configure les cibles sur lesquelles nous voulons rendre.
Dans ce projet, nous n’avons qu’un seul cible. On configure ce cible en configurant son champ format
avec la valeur presentationFormat
… qui est le format preferré de notre élément canvas
Cette variable est liée à une configuration que nous verrons dans l’article suivante, où nous configurons un descripteur d’un render pass, renderPassDescriptor
.
primitive, GPUPrimitiveState
Le champ primitive
est de type GPUPrimitiveState
définit le type du primitive que nous voulons construire.
Pour le triangle, il suffit d’utiliser triangle-list
, car nous voulons rendre des triangles… ou dans notre cas, un triangle seul.
Le code de la partie 6
Vous pouvez trouver le code de cette partie dans ce GitHub Gist.
La suite
Nous continuerons ce projet sur YouTube ! À la prochaine !