Назад к блогу
Платформа

Восстановление после потери устройства WebGPU

Спроектируйте ограниченное восстановление WebGPU, сохраните состояние страницы и не позволяйте устаревшим асинхронным операциям заменить новое устройство.

BotBrowser Team

Документация

Нужна структурированная документация по теме Платформа?

Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.

При потере устройства WebGPU графическая функция может остановиться, хотя остальная страница продолжит работать. Спецификация W3C представляет это событие жизненного цикла через GPUDevice.lost; MDN поясняет, что соответствующий Promise остается ожидающим в течение жизни устройства и завершается информацией о потере, когда устройство становится недоступным. Храните задачу пользователя в обычном состоянии приложения, быстро переключайтесь на работающее представление без GPU и считайте каждую новую инициализацию новым поколением. Токен поколения позволяет игнорировать запоздавшие результаты от замененного устройства.

Устройство WebGPU переходит из состояния готовности в потерянное, приложение сохраняет обычное представление, а явный повторный запуск начинает новое поколение

Отделяйте состояние приложения от ресурсов GPU

Графическое устройство служит ресурсом для отображения или вычисления результата. Оно не должно быть единственным местом хранения документа, выбранного элемента, значений формы или другой работы, которую пользователь рассчитывает сохранить. Держите эти данные в обычном состоянии страницы или приложения. Тогда потеря устройства удалит ресурс рендеринга, но не саму задачу.

GPUDevice.lost представляет собой Promise, связанный с конкретным устройством. Он завершается при потере этого устройства, но не обещает, что замена будет доступна. Спецификация WebGPU описывает изменение пригодности устройства к использованию, а не диагноз физического компонента. Интерфейс должен точно обозначить недоступную функцию, сохранить доступные данные и не утверждать, что известна причина потери на конкретной машине.

Это не тот вопрос конфиденциальности, который рассматривается в обзоре WebGPU-фингерпринтинга: там речь идет о применении доступных сигналов для отслеживания. Проверка выпуска браузера помогает операторам оценить пару версии браузера и профиля и план отката, но не описывает потерю устройства внутри сеанса. Эта статья отвечает на вопрос времени выполнения: что должна делать активная функция после того, как ее текущее устройство перестало работать?

Защитите автомат состояний токеном поколения

Увеличивайте монотонный счетчик при каждой остановке или замене инициализации. Каждое асинхронное продолжение сохраняет значение счетчика, с которым оно запустилось. Перед изменением видимого состояния оно должно проверить, что поколение все еще текущее. Такая же проверка нужна обработчику lost: запоздавшее событие старого устройства не должно менять состояние нового.

Ниже приведен пример контроллера, который можно адаптировать. Приложение реализует showFallback, showGraphics, announce, stopRenderLoop, detachGraphics и disposeDeviceResources: останавливает старый цикл, отключает представление, освобождает принадлежащие ему buffer и texture, затем уничтожает устройство. Данные приложения остаются вне контроллера. Вызывайте initialize только при открытии функции или явном действии пользователя. Пример не запускает повторы автоматически и не ведет счетчик; владелец функции задает продуктовый лимит за пределами контроллера.

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.');
      }
    }
  }
}

В реальном приложении обрабатывайте сведения о потере согласно API и потребностям поддержки, не превращая их в классификацию оборудования. Если requestDevice() завершается после stopGraphics() или нового вызова initialize(), проверка токена уничтожает устаревшее устройство, а не показывает его. Поздний отказ также игнорируется. Если старый Promise lost завершается после запуска нового поколения, его обработчик не сможет заменить новое представление.

Выберите понятный пользователю результат

Переход должен сообщать, что осталось доступно, а не предполагать причину потери.

WebGPU отсутствует, adapter равен null или инициализация отклонена. Оставьте обычное HTML-представление и сообщите, что расширенная графика не запустилась. Не повторяйте попытку автоматически; функция должна оставаться необязательной.

Promise lost готового устройства выполнен. Отметьте потерю только для этого поколения, сохранив данные страницы и элементы управления. Предлагайте повтор только тогда, когда у пользователя есть основания попробовать снова.

Новая инициализация заменяет активное устройство и завершается ошибкой или успехом. Перед запросом замены остановите и отсоедините старый renderer, освободите его ресурсы и уничтожьте устройство. При ошибке оставьте запасное представление и не восстанавливайте старое устройство; при успехе подключите только новое.

Новое поколение началось, пока старый запрос еще ожидает. Считайте завершение старого запроса устаревшим и освободите созданное им устройство. Не позволяйте ему менять состояние или представление нового поколения.

Явный повторный запуск успешен. Подключите новое устройство и отображайте данные из текущего состояния приложения. Отслеживайте Promise потери только нового устройства.

Явный повтор не удался или лимит приложения исчерпан. Оставьте рабочее запасное представление и сообщите, что расширенная графика недоступна. Не повторяйте попытку до нового действия пользователя или заданного приложением сброса; лимит обеспечивает владелец функции, а не этот пример.

Если продукт допускает повтор, используйте кнопку с понятными состояниями загрузки и блокировки. Частый цикл повторов может снова и снова выделять ресурсы, не улучшая результат. Этот пример не выполняет автоматические повторы и не считает их; владелец функции задает небольшой лимит вне контроллера или требует нового действия после неудачи. Ни спецификация, ни GPUDevice.lost не гарантируют, что повтор восстановит ту же задачу.

Сделайте восстановление наблюдаемым и проверяемым

Компонент, отвечающий за задачу, решает, является ли графическое представление необязательным, какие данные необходимо сохранить и какое представление продолжит работу без устройства. Renderer может сообщить о недоступности, но не должен удалять документ или самостоятельно запускать повтор. Страница согласует состояние, элементы управления и намерение пользователя.

Считайте каждую инициализацию короткой транзакцией: сделайте старое поколение недействительным, отключите прежний путь рендеринга и сохраните данные приложения, затем запросите ресурсы нового поколения. Публикуйте устройство только после последней проверки токена. При ошибке интерфейс может менять только текущее поколение.

Потеря устройства и остановка приложения могут совпасть с очисткой ресурсов. Перед уничтожением или отключением активного устройства увеличьте токен, чтобы отложенные Promise-продолжения не записали состояние в старое поколение. Очистка должна безопасно повторяться и не уничтожать устройство-замену.

Не превращайте каждую причину потери в указание для восстановления. Эти сведения могут обозначать общие категории API для статуса или поддержки, но не идентифицируют конкретный GPU и не доказывают, почему изменилось окружение. Интерфейс должен оставаться безопасным, если причина отсутствует, незнакома или бесполезна пользователю.

Проверьте, можно ли восстановить функцию из обычного состояния приложения. График можно построить заново из сохраненных строк и фильтров; промежуточный результат, существовавший только в памяти GPU, может быть невосстановим. Если задаче важно его сохранить, записывайте контрольные точки в хранилище приложения и объясняйте, какая работа сохранена, а какую операцию нужно повторить.

Устройство может потеряться во время действия пользователя. Отключайте только те элементы управления, которые зависят от графического пути; сохраняйте навигацию, отмену, экспорт и доступные альтернативы, если они еще полезны. Незавершенное действие можно выполнить без GPU или сообщить, что его нужно повторить. Объявление статуса для вспомогательных технологий должно сообщать о переходе, не перемещая фокус.

Не используйте перезагрузку страницы как стандартный способ восстановления. Она может очистить незаполненную форму, сбросить навигацию, повторить сетевые запросы или заставить пользователя заново собирать контекст. Потеря устройства является событием жизненного цикла ресурса, а не автоматической причиной перезапуска приложения. Перезагружайте страницу только по отдельной явной причине и после защиты незавершенной работы.

Явный вызов GPUDevice.destroy() также завершает полезный срок жизни устройства. Перед очисткой сделайте его поколение недействительным; иначе последующее завершение lost может состязаться с остановкой и выглядеть как неожиданная ошибка. Потеря текущего устройства может показать запасное представление, но событие от остановленного поколения больше не должно менять интерфейс.

Проверяйте гонки с отложенными Promise, а не произвольными таймерами. Запустите A и оставьте запрос adapter незавершенным, запустите B, покажите B и лишь затем завершите A. Итоговое представление должно принадлежать B. Повторите тест, задержав requestDevice() поколения A до готовности B; позднее устройство A должно быть освобождено и не подключено к renderer.

Тестовая подмена должна предоставлять те же асинхронные границы, что и API: управляемые запросы adapter и device, а также отдельный Promise lost для каждого устройства. Подсчитывайте вызовы destroy() и подключения к renderer. После замены старое поколение не должно вызывать обработчики состояния, а текущее устройство должно остаться подключенным. Такой тест работает без реального adapter.

Добавьте тест с активным A: успешно инициализируйте его, подтвердите подключение, затем вызовите initialize() для запуска B. До запроса adapter для B остановите цикл A, отключите его представление, освободите ресурсы и уничтожьте устройство. Если запрос adapter или device для B завершается ошибкой, остается запасное представление и A не возвращается; если B успешен, подключается только B. В обоих случаях позднее завершение lost устройства A не меняет текущее состояние.

Остановка и повторный запуск являются разными действиями. Остановка увеличивает поколение, удаляет старое устройство и возвращает устойчивое запасное представление без нового запроса. Только явный повтор пользователя начинает поколение и показывает загрузку. При отказе вернитесь к запасному представлению согласно ограниченной политике. Счетчик отражает попытки приложения, а не события потери одного устройства.

Проверяйте владельца ресурсов после замены устройства. Устройство, созданное поздно завершившимся Promise, принадлежит устаревшему поколению и должно освобождаться согласно жизненному циклу API. Старый catch или обработчик lost не должен сбрасывать текущее устройство. По возможности храните ссылки отдельно для каждого поколения и проверяйте токен перед изменением общего состояния.

Восстановление не означает повторное подключение старых ресурсов. Buffer, texture, pipeline, bind group и command encoder принадлежат устройству, которое их создало. После готовности нового устройства создавайте только ресурсы активного режима и формируйте привязки из данных приложения. Если canvas-контекст использует устройство, настройте новый путь рендеринга до отправки команд.

Храните исходные данные модели на CPU, чтобы новые ресурсы представляли ту же задачу. Не помещайте ресурсы старого устройства в общий кеш, предполагая, что их можно передать другому устройству. Границу жизненного цикла пересекают только значения, восстановленные из состояния приложения.

Отделяйте координацию восстановления от реализации рендеринга. Renderer может подготовить ресурсы для переданного устройства, а страница решает, относится ли результат к текущему поколению. Если подготовка асинхронна, передавайте тот же токен и повторно проверяйте его перед публикацией результата.

Повторная потеря или отмена пользователя может произойти во время загрузки shader, texture или других ресурсов. По возможности отменяйте устаревшие запросы; иначе игнорируйте их поздние результаты и освобождайте созданные ими ресурсы. Тогда старый renderer не продолжит отправлять команды замененному устройству.

Записывайте ожидаемое поведение по переходам, а не по устройствам. Успешная инициализация означает, что нужная функция готова для текущего состояния страницы; потеря означает, что соответствующая графическая работа больше недоступна; успешное восстановление означает, что новое устройство заново создало необходимые ресурсы из текущих данных приложения. Это можно проверить без вымышленных категорий оборудования и всеобщих гарантий восстановления.

Проверьте, что renderer становится видимым лишь после завершения подготовки текущего поколения. Если новая инициализация началась во время загрузки ресурсов, старый результат не должен мелькнуть даже на короткое время. Пользователь видит только результат последнего выбора.

Проверяйте переходы с управляемыми Promise, а не на конкретной графической конфигурации. Полезный тест оставляет первый запрос adapter или device незавершенным до начала второй инициализации. Ожидаемый результат: первое поколение не публикует свое значение; если оно создало устройство, это устройство уничтожается. В другом тесте завершите lost старого устройства уже после готовности замены: новое представление и статус должны остаться неизменными.

Покройте и обычные ветви: API отсутствует, adapter возвращает null, запрос adapter или device отклонен, потеря после успешной инициализации, отмена пользователем, успешный повтор и исчерпание лимита. В каждом случае проверьте, что элементы управления работают, выбор сохранен, статус доступен вспомогательным технологиям, а представление соответствует состоянию приложения. Если восстановление анимировано, отдельно проверьте режим уменьшенного движения; переход не должен зависеть от анимации.

Собирайте только необходимые для понимания перехода операционные сведения, например примерный этап и факт показа запасного представления. Не собирайте данные adapter, не задавайте аппаратные пороги, не исследуйте GPU и не считайте потерю сигналом для фингерпринтинга. Описанное поведение основано на спецификации WebGPU и справке MDN о GPUDevice.lost; ни один из источников не гарантирует одинаковое восстановление во всех браузерах и средах.

Сохраните полезность запасного представления

Запасное представление является частью функции, а не страницей ошибки. Оставьте важные сведения и действия в обычном HTML, сохраните текущий выбор и прямо сообщите, что расширенное представление остановилось, а остальная страница доступна. Если результат существовал только в памяти GPU и его нельзя восстановить, скажите об этом, а не создавайте впечатление, что он сохранен.

BotBrowser позволяет проверить выбираемые режимы WebGPU в поддерживаемой конфигурации приложения, но BotBrowser не гарантирует восстановление после потери устройства, не управляет возвратом ресурсов хостом и не обеспечивает наличие WebGPU во всех средах. Проверяйте видимое для пользователя восстановление в поддерживаемых средах и отделяйте контракт приложения от идентификации adapter и фингерпринтинга.

Источники

#WebGPU#Потеря Устройства#Восстановление#Устойчивый Рендеринг

Переведите BotBrowser из исследований в продакшн

Используйте эти руководства, чтобы понять модель, а затем перейти к кроссплатформенной валидации, изолированным контекстам и масштабируемому браузерному развертыванию.