WebGPUが未サポートのブラウザでは「使えません」と表示したい
WebGPUを使うアプリのつくりかたを最初に学ぶ際には、次のようなコードを目にすることだろうと思います。
async function init() {
if (!navigator.gpu) {
throw Error("WebGPU not supported.");
}
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
throw Error("Couldn't request WebGPU adapter.");
}
// Create a GPUDevice
let device = await adapter.requestDevice(descriptor);
// Use lost to handle lost devices
device.lost.then((info) => {
console.error(`WebGPU device was lost: ${info.message}`);
device = null;
if (info.reason !== "destroyed") {
init();
}
});
// ...
}
mdn web docsの GPUDevice: lost property や、Google codelabsの「初めてのWebGPUアプリ 3. WebGPU を初期化する」などで、こうした内容でのコードが示されています。要するに、navigator.gpuプロパティが存在するかどうかをチェックし、WebGPUが未サポートであることを検出し、consoleに(こっそりと)エラーを出力する、という動作です。
ただし、通常のユーザーがデベロッパーコンソールを開いていることは期待できません。また、プログラムの内容によっては、単にデベロッパーコンソールを開いているだけでも処理のパフォーマンスが顕著に低下します。
WebGPUが未サポートのブラウザを使っているユーザに対しては、画面上で明示的に「この環境では使えません」という内容の警告を表示してあげるようにしたいところです。さらには、コンピュートシェーダーで処理をするような内容では、WebAssemblyを用いた実装にフォールバックする、画面描画はcanvasを用いた実装にフォールバックする...というような作り込みをすることについても、検討をするべきかもしれません。
というわけで、React Contextで、WebGPUが利用できる場合にだけGPUDeviceのインスタンスを提供するとともに、WebGPUが利用できない場合にはReactNodeで指定したエラーメッセージを画面に表示できるようなものを作ることにしました。
useWebGPUDevice.tsx
以下のコードをコピペして使うか、NPMパッケージとしてインストールしてください。
pnpm add react-webgpu-context
ライブラリをGitHubリポジトリとしても公開しています。
import React, { ReactNode, useLayoutEffect, useState } from 'react';
type WebGPUDeviceContextType = GPUDevice | null;
export const WebGPUDeviceContext =
React.createContext<WebGPUDeviceContextType>(null);
export const WebGPUDeviceContextProvider = ({
loadingMessage,
notSupportedMessage,
children,
}: {
loadingMessage?: ReactNode;
notSupportedMessage?: ReactNode;
children?: ReactNode;
}) => {
const [device, setDevice] = useState<GPUDevice | null>(null);
const [isWebGPUSupported, setIsWebGPUSupported] = useState<boolean | null>(
null
);
useLayoutEffect(() => {
(async () => {
const initWebGPU = async function (
callback: (device: GPUDevice | undefined) => void
) {
const requestDevice = async (): Promise<GPUDevice | undefined> => {
if (!navigator.gpu || !navigator.gpu.requestAdapter) {
setIsWebGPUSupported(false);
return;
}
try {
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) {
setIsWebGPUSupported(false);
return;
}
const device = await adapter.requestDevice();
device.lost.then(async (info) => {
console.error(
`WebGPU device was lost: ${info.message}: ${info.reason}`
);
// 'reason' will be 'destroyed' if we intentionally destroy the device.
if (info.reason !== 'destroyed') {
// try again
console.error('Trying to recreate the device...');
return callback(await requestDevice());
}
});
return device;
} catch (ex: any) {
console.error('Trying to recreate the device...' + ex);
return await requestDevice();
}
};
callback(await requestDevice());
};
await initWebGPU((device: GPUDevice | undefined) => {
if (!navigator.gpu || !device) {
setIsWebGPUSupported(false);
return;
}
setIsWebGPUSupported(true);
setDevice(device);
});
})();
return () => {
device?.destroy();
};
}, []);
if (isWebGPUSupported === null) {
return loadingMessage ? loadingMessage : <p>Loading...</p>;
}
if (!isWebGPUSupported) {
return notSupportedMessage ? (
notSupportedMessage
) : (
<p>WebGPU is not supported on this browser.</p>
);
}
return !device ? null : (
<WebGPUDeviceContext.Provider value={device}>
{children}
</WebGPUDeviceContext.Provider>
);
};
export const useWebGPUDevice = () => {
const device = React.useContext(WebGPUDeviceContext);
if (device == null) {
throw new Error(
'useWebGPUDevice must be used within a WebGPUDeviceContextProvider'
);
}
return device;
};
WebGPUDeviceContextProviderの設置
WebGPUを使うコンポーネント(複数個のWebGPUを使うコンポーネント)の祖先要素として、WebGPUDeviceContextProvider
コンポーネントを設置してください。
このとき、次のようなプロパティを指定できます。
-
loadingMessage
プロパティ: 読み込み中に表示するコンポーネント -
notSupportedMessage
プロパティ: WebGPUが使えない環境であることを表示するコンポーネント
コード例は次のようになります。
import { WebGPUDeviceContextProvider } from './useWebGPUDevice';
import {WebGPUApp} from './WebGpuApp'
export const App = ()=>{
return (
<WebGPUDeviceContextProvider
loadingMessage={(<p>読み込み中</p>)}
notSupportedMessage={(<p>あなたのブラウザではWebGPUが利用できません</p>)}>
<WebGPUApp />
</WebGPUDeviceContextProvider>
);
}
このようにWebGPUDeviceContextProviderを配置することで、WebGPUアプリを使える場合・使えない場合のそれぞれに対応したコンポーネントを表示できるようになります。
React hookによるGPUDeviceインスタンスの参照
WebGPUを用いるアプリ内では、次のように、useWebGPUDevice
フックを利用すると、祖先のWebGPUDeviceContextProvider
で取得済みのGPUDevice
のインスタンスを参照することができます。
import { useWebGPUDevice } from './useWebGPUDevice';
export const WebGPUApp = ()=>{
const device: GPUDevice = useWebGPUDevice();
// deviceを使っていろいろする
// 略
}
利用例
この記事で紹介したWebGPUDeviceContextProvider
の中に配置した形で、WebGPUコンピュートシェーダとWebGPUによる画面描画を用いたアプリをつくりました。もしも、あなたのブラウザが、WebGPU未対応の場合には、ブラウザの画面上には、次のような表示がなされているはずです。
この記事はこれで終わりです。