Retour au Blog
Plateforme

Récupérer après la perte d’un appareil WebGPU

Concevez une reprise WebGPU limitée qui conserve l’état de la page et empêche les tâches asynchrones périmées de remplacer un nouvel appareil.

BotBrowser Team

Documentation

Vous voulez la documentation structurée pour Plateforme ?

Cet article fait partie de la bibliothèque éditoriale. Pour les étapes de configuration, la référence et les mises à jour continues, passez directement à la section docs.

La perte d’un appareil WebGPU peut interrompre les graphismes alors que le reste de la page fonctionne encore. La spécification du W3C expose cet événement du cycle de vie au moyen de GPUDevice.lost ; MDN précise que sa promesse reste en attente pendant la vie de l’appareil et se résout avec des informations de perte lorsque celui-ci devient inutilisable. Conservez la tâche de la personne dans l’état normal de l’application, passez rapidement à une vue utilisable sans GPU et traitez toute nouvelle initialisation comme une nouvelle génération. Un jeton de génération permet d’ignorer les résultats tardifs de l’appareil remplacé.

L’appareil WebGPU passe de prêt à perdu, l’application conserve sa vue habituelle et une nouvelle tentative volontaire commence une autre génération

Séparer l’état de l’application des ressources GPU

L’appareil graphique est une ressource servant à présenter ou calculer un résultat. Il ne doit pas être le seul endroit où sont conservés un document, une sélection, des champs de formulaire ou d’autres éléments que la personne compte garder. Maintenez ces données dans l’état ordinaire de la page ou de l’application. La perte peut ainsi supprimer la ressource de rendu sans effacer la tâche.

GPUDevice.lost est une promesse propre à un appareil. Elle se résout lorsque cet appareil est perdu, mais ne garantit pas la disponibilité d’un remplaçant. La spécification WebGPU décrit un changement d’utilisabilité, pas un diagnostic d’un composant physique. L’interface doit indiquer précisément la fonction interrompue, conserver les données accessibles et ne pas prétendre connaître la cause de la perte sur une machine donnée.

Cette question diffère du sujet de confidentialité traité par la présentation du fingerprinting WebGPU, qui explique comment des signaux exposés peuvent servir au suivi. Elle diffère aussi de la validation d’une version de navigateur, destinée aux équipes qui évaluent une paire navigateur/profil et son retour arrière. Ce guide répond à une question d’exécution : que faire d’une fonction active lorsque son appareil courant n’est plus utilisable ?

Protéger la machine à états par un jeton de génération

Incrémentez un compteur monotone chaque fois qu’une initialisation est remplacée ou arrêtée. Chaque continuation asynchrone mémorise le compteur au démarrage. Avant de modifier l’état visible, elle doit encore correspondre à la génération actuelle. Le gestionnaire lost doit faire la même vérification : un événement tardif de l’ancien appareil ne doit pas remplacer l’état du nouveau.

Le contrôleur suivant illustre une approche réutilisable. L’application implémente showFallback, showGraphics, announce, stopRenderLoop, detachGraphics et disposeDeviceResources : arrêter l’ancienne boucle, détacher la vue, libérer les buffers et textures qu’elle possède, puis détruire l’appareil. Les données de l’application restent ailleurs. Appelez initialize uniquement à l’ouverture de la fonction ou après une action volontaire. Cet exemple ne relance rien automatiquement et ne compte pas les tentatives ; le responsable de la fonction applique toute limite produit hors du contrôleur.

let generation = 0;
let activeDevice = null;
const isCurrent = token => token === generation;

function stopGraphics() {
  generation += 1;
  const oldDevice = activeDevice;
  activeDevice = null;
  try {
    retireDevice(oldDevice);
  } finally {
    showFallback('Graphics stopped. Your page state is still available.');
  }
}

function retireDevice(device) {
  if (!device) return;
  try {
    stopRenderLoop(device);
  } finally {
    try {
      detachGraphics(device);
    } finally {
      try {
        disposeDeviceResources(device);
      } finally {
        device.destroy();
      }
    }
  }
}

async function initialize(gpu = navigator.gpu) {
  const token = ++generation;
  const oldDevice = activeDevice;
  activeDevice = null;
  showFallback('Starting the graphics feature…');
  try {
    retireDevice(oldDevice);
    if (!gpu) throw new Error('WebGPU is unavailable in this context.');
    const adapter = await gpu.requestAdapter();
    if (!isCurrent(token)) return;
    if (!adapter) throw new Error('No adapter was returned.');
    const device = await adapter.requestDevice();
    if (!isCurrent(token)) {
      retireDevice(device);
      return;
    }
    activeDevice = device;
    showGraphics(device);
    announce('Graphics are ready.');
    void device.lost.then(info => {
      if (!isCurrent(token)) return;
      generation += 1;
      activeDevice = null;
      try {
        retireDevice(device);
      } finally {
        showFallback(`Graphics stopped (${info.reason || 'device lost'}). Your page state is still available.`);
      }
    });
  } catch {
    if (isCurrent(token)) {
      const failedDevice = activeDevice;
      activeDevice = null;
      try {
        retireDevice(failedDevice);
      } finally {
        showFallback('Graphics could not start. Your page state is still available.');
      }
    }
  }
}

Dans un produit, traitez les informations de perte selon le contrat de l’API et les besoins du support, sans en faire une classification matérielle. Si requestDevice() aboutit après stopGraphics() ou un nouvel appel à initialize(), le jeton entraîne la destruction de l’appareil périmé au lieu de l’afficher. Un rejet tardif est également ignoré. Si l’ancienne promesse lost se résout après le début d’une nouvelle génération, son gestionnaire ne peut pas remplacer la nouvelle vue.

Choisir un résultat compréhensible

La transition doit décrire ce qui reste possible, pas supposer la cause de la perte.

WebGPU est absent, l’adapter vaut null ou l’initialisation échoue. Gardez la vue HTML habituelle et indiquez que les graphismes avancés n’ont pas pu démarrer. Ne réessayez pas automatiquement ; gardez cette fonction facultative.

La promesse lost d’un appareil prêt se résout. Marquez uniquement cette génération comme perdue et gardez les données et commandes de la page disponibles. Ne proposez une nouvelle tentative que si la personne a une raison de réessayer.

Une nouvelle initialisation remplace un appareil actif et échoue ou réussit. Avant de demander son remplacement, arrêtez et détachez l’ancien renderer, libérez ses ressources et détruisez l’appareil. Si la demande échoue, gardez la solution de repli et ne restaurez pas l’ancien appareil ; si elle réussit, attachez uniquement le nouvel appareil.

Une nouvelle génération démarre alors qu’une ancienne requête attend encore. Considérez l’ancien résultat comme périmé et libérez tout appareil qu’il a créé. Ne le laissez pas modifier l’état ni la vue de la nouvelle génération.

Une tentative volontaire réussit. Connectez le nouvel appareil et effectuez le rendu à partir de l’état actuel de l’application. Observez uniquement la promesse de perte du nouvel appareil.

La tentative volontaire échoue ou le budget de nouvelles tentatives de l’application est épuisé. Gardez une solution de repli utilisable et signalez que les graphismes avancés restent indisponibles. Arrêtez les tentatives jusqu’à une nouvelle action de la personne ou une réinitialisation définie par l’application ; le budget relève du responsable de la fonction, pas de cet exemple.

Si le produit autorise une nouvelle tentative, utilisez un bouton avec des états de chargement et de désactivation explicites. Une boucle serrée peut allouer des ressources à répétition sans améliorer l’expérience. Cet exemple ne réessaie pas automatiquement et ne compte pas les tentatives ; le responsable applique hors du contrôleur un budget limité ou exige une nouvelle action après l’échec. Ni la spécification ni GPUDevice.lost ne garantissent que la même tâche pourra reprendre.

Rendre la reprise observable et testable

Le composant responsable de la tâche décide si la vue graphique est facultative, quelles données doivent être conservées et quelle vue peut continuer sans appareil. Le renderer peut signaler son indisponibilité, mais ne doit pas supprimer le document ni lancer seul une nouvelle tentative. La page coordonne l’état, les commandes et l’intention de la personne.

Traitez chaque initialisation comme une transaction courte : invalidez l’ancienne génération, détachez son chemin de rendu et conservez les données de l’application, puis demandez les ressources de la nouvelle génération. Ne publiez le nouvel appareil qu’après la dernière vérification du jeton. En cas d’erreur, seule la génération courante peut modifier l’interface.

La perte de l’appareil et l’arrêt de l’application peuvent se produire pendant le nettoyage. Invalidez le jeton avant de détruire ou détacher l’appareil actif, afin que les continuations en attente n’écrivent pas dans une ancienne génération. Le nettoyage doit pouvoir être répété sans changer le résultat ni détruire accidentellement l’appareil de remplacement.

Ne faites pas de chaque motif de perte une instruction de reprise. Les informations peuvent distinguer de grandes catégories définies par l’API pour l’état ou le support, mais elles n’identifient pas un GPU particulier et ne prouvent pas pourquoi l’environnement a changé. L’interface doit rester sûre si le motif manque, est inconnu ou n’est pas utile à la personne.

Vérifiez si la fonction peut être reconstruite depuis l’état ordinaire de l’application. Un graphique peut être recréé à partir des lignes et filtres enregistrés ; un résultat intermédiaire conservé uniquement en mémoire GPU peut être irrécupérable. Si la tâche exige de le garder, enregistrez des points de contrôle dans un stockage géré par l’application et indiquez le travail préservé ou à répéter.

La perte peut survenir pendant une action de la personne. Désactivez uniquement les commandes dépendant du chemin graphique ; conservez navigation, annulation, export et alternatives accessibles lorsqu’ils restent pertinents. Si l’action n’a pas encore rejoint l’état de l’application, terminez-la par une voie sans GPU ou expliquez qu’elle doit être répétée. Un état accessible peut annoncer le changement sans déplacer le focus.

Ne faites pas du rechargement de page la réponse par défaut. Il peut effacer un formulaire non envoyé, réinitialiser la navigation, répéter des requêtes réseau ou obliger la personne à recréer son contexte. La perte est un événement du cycle de vie d’une ressource, pas une raison automatique de redémarrer toute l’application. Ne rechargez que pour une raison distincte et après protection du travail en cours.

Un appel explicite à GPUDevice.destroy() termine également la durée de vie utile de l’appareil. Invalidez sa génération avant le nettoyage ; sinon, la résolution ultérieure de lost peut entrer en concurrence avec l’arrêt et ressembler à une panne inattendue. La perte de l’appareil actuel peut montrer la solution de repli, mais un événement d’une génération arrêtée ne doit plus modifier l’interface.

Utilisez des promesses différées pour tester les courses plutôt que des minuteries arbitraires. Lancez A en gardant sa demande d’adapter en attente, démarrez B et rendez B visible, puis résolvez A. La vue finale doit toujours appartenir à B. Recommencez en différant requestDevice() de A jusqu’à ce que B soit prêt ; l’appareil tardif de A doit être libéré sans être attaché au renderer.

Le double de test doit exposer les mêmes frontières asynchrones que l’API : demandes d’adapter et de device contrôlables, ainsi qu’une promesse lost propre à chaque appareil. Comptez les appels à destroy() et les attachements au renderer. Une fois remplacée, l’ancienne génération ne doit plus appeler les gestionnaires d’état et l’appareil courant doit rester attaché. Ce scénario ne dépend donc pas d’un adapter réel.

Ajoutez un test avec A déjà actif : initialisez-le, confirmez son attachement, puis appelez initialize() pour lancer B. Avant la demande d’adapter de B, arrêtez la boucle de rendu de A, détachez sa vue, libérez ses ressources et détruisez l’appareil. Si la demande d’adapter ou de device de B échoue, la solution de repli reste affichée et A ne revient pas ; si B réussit, seul B est attaché. Dans les deux cas, résoudre ensuite la promesse lost de A ne modifie pas l’état courant.

Ajoutez un test avec un appareil déjà actif : initialisez A, confirmez son attachement, puis appelez initialize() pour lancer B. Avant la demande d’adapter de B, arrêtez la boucle de rendu de A, détachez sa vue, libérez ses ressources et détruisez l’appareil. Si la demande d’adapter ou de device de B échoue, la solution de repli reste affichée et A ne revient pas ; si B réussit, seul B est attaché. Dans les deux cas, une résolution tardive de lost pour A ne modifie pas l’état.

Arrêter et réessayer sont deux actions distinctes. L’arrêt incrémente la génération, retire l’ancien appareil et revient à une solution de repli stable sans envoyer de nouvelle demande. Seule une nouvelle tentative volontaire ouvre une génération et affiche le chargement. En cas d’échec, retournez à la solution de repli selon la politique limitée. Le compteur désigne les tentatives de l’application, pas les événements de perte d’un appareil.

Vérifiez la propriété des ressources lorsqu’un appareil est remplacé. Un appareil créé tardivement par une promesse appartient à la génération obsolète et doit être libéré selon le cycle de vie de l’API. Un ancien catch ou gestionnaire lost ne doit pas effacer l’appareil courant. Séparez les références par génération lorsque possible et vérifiez le jeton avant de modifier un état partagé.

Reconstruire ne signifie pas reconnecter d’anciennes ressources. Les buffers, textures, pipelines, bind groups et command encoders appartiennent à l’appareil qui les a créés. Lorsque le nouvel appareil est prêt, recréez uniquement les ressources du mode actif et reconstruisez les liaisons depuis les données de l’application. Si un contexte canvas utilise l’appareil, configurez le nouveau chemin avant d’envoyer du travail.

Conservez les données de modèle de référence côté CPU afin que les ressources neuves représentent la même tâche. Ne placez pas les ressources de l’ancien appareil dans un cache global en supposant qu’elles fonctionneront avec un autre. Seules les valeurs reconstruites depuis l’état de l’application traversent cette limite de cycle de vie.

Séparez la coordination de la reprise de l’implémentation du rendu. Le renderer peut préparer des ressources pour un appareil donné ; la page décide si cette préparation appartient toujours à la génération courante. Si la préparation est asynchrone, transmettez le même jeton et vérifiez-le encore avant de publier le résultat.

Une nouvelle perte ou une annulation peut arriver pendant le chargement de shaders, textures ou autres ressources. Annulez les demandes périmées lorsque c’est possible ; sinon, ignorez leurs résultats tardifs et libérez leurs ressources. L’ancien renderer ne continuera ainsi pas à envoyer de commandes à l’appareil remplacé.

Consignez les résultats par transition plutôt que par machine. Une initialisation réussie signifie que la fonction demandée est prête pour l’état actuel ; une perte signifie que le travail graphique concerné n’est plus utilisable ; une reprise réussie signifie que le nouvel appareil a recréé les ressources nécessaires à partir des données courantes. Ces résultats se vérifient sans inventer de catégorie matérielle ni promettre une reprise universelle.

Vérifiez que le renderer ne devient visible qu’une fois terminée la préparation de la génération courante. Si une nouvelle génération démarre pendant le chargement, l’ancien résultat ne doit même pas apparaître brièvement. La personne ne voit que le résultat de son choix le plus récent.

Testez les transitions avec des promesses contrôlées plutôt qu’avec une configuration graphique particulière. Un scénario utile laisse la première demande d’adapter ou de device en attente jusqu’au lancement d’une seconde initialisation. La première génération ne doit alors jamais publier son résultat ; si elle a créé un appareil, celui-ci doit être détruit. Dans un autre test, résolvez lost de l’ancien appareil après que le remplacement est prêt : la nouvelle vue et son état doivent rester inchangés.

Couvrez également les chemins habituels : API absente, adapter égal à null, rejet d’une demande d’adapter ou de device, perte après initialisation, annulation par la personne, réussite d’une nouvelle tentative et épuisement du budget. Vérifiez à chaque fois que les commandes restent utilisables, que les sélections sont conservées, que l’état est accessible et que la vue correspond aux données de l’application. Si une animation accompagne la reprise, testez séparément la réduction des mouvements ; le message de transition ne doit pas en dépendre.

Ne consignez que les informations opérationnelles nécessaires à la compréhension du changement, par exemple une étape approximative et l’affichage ou non de la solution de repli. Ne collectez pas de détails d’adapter, ne définissez pas de seuil matériel, ne sondez pas le GPU et ne traitez pas la perte comme un signal de fingerprinting. La spécification publique WebGPU et la référence MDN sur GPUDevice.lost fondent les comportements décrits ; aucune ne garantit une reprise identique sur tous les navigateurs et hôtes.

Garder une solution de repli utile

La solution de repli fait partie de la fonction ; ce n’est pas une page d’erreur. Gardez les informations et actions essentielles en HTML ordinaire, conservez les choix actuels et expliquez clairement que la vue avancée s’est arrêtée alors que le reste de la page reste accessible. Si un résultat existait uniquement en mémoire GPU et ne peut être reconstruit, dites-le au lieu de laisser croire qu’il a été enregistré.

BotBrowser documente des modes WebGPU sélectionnables que les équipes peuvent tester dans une configuration prise en charge, mais BotBrowser ne garantit pas la reprise après la perte d’un appareil, ne contrôle pas la récupération des ressources de l’hôte et ne rend pas WebGPU disponible dans tous les environnements. Vérifiez le comportement visible dans les environnements que vous prenez en charge et séparez ce contrat applicatif de l’identification d’adapter et du fingerprinting.

Sources

#WebGPU#Perte D’appareil#Reprise#Rendu Résilient

Faites passer BotBrowser de la recherche à la production

Utilisez ces guides pour comprendre le modèle, puis passez à la validation multi-plateforme, aux contextes isolés et au déploiement navigateur prêt pour l'échelle.