Refactoring start
This commit is contained in:
parent
6588930911
commit
b1acdff594
19 changed files with 528 additions and 97 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import { ErrorCode, getErrorMessage, RuntimeError } from '../error-handler';
|
||||
|
||||
export const initializeContext = ({
|
||||
device,
|
||||
canvas,
|
||||
|
|
@ -5,13 +7,49 @@ export const initializeContext = ({
|
|||
device: GPUDevice;
|
||||
canvas: HTMLCanvasElement;
|
||||
}): GPUCanvasContext => {
|
||||
const context = canvas.getContext('webgpu') as any as GPUCanvasContext;
|
||||
const context = canvas.getContext('webgpu' as any) as GPUCanvasContext | null;
|
||||
|
||||
context.configure({
|
||||
device: device,
|
||||
format: navigator.gpu.getPreferredCanvasFormat(),
|
||||
alphaMode: 'premultiplied',
|
||||
});
|
||||
if (!context) {
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_CONTEXT_UNAVAILABLE,
|
||||
'Could not create a WebGPU canvas context.',
|
||||
{
|
||||
details: {
|
||||
canvasHeight: canvas.height,
|
||||
canvasWidth: canvas.width,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const gpu = navigator.gpu;
|
||||
if (!gpu) {
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_UNSUPPORTED,
|
||||
'WebGPU is no longer available while configuring the canvas context.'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
context.configure({
|
||||
device: device,
|
||||
format: gpu.getPreferredCanvasFormat(),
|
||||
alphaMode: 'premultiplied',
|
||||
});
|
||||
} catch (error) {
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_CONTEXT_CONFIGURATION_FAILED,
|
||||
'Could not configure the WebGPU canvas context.',
|
||||
{
|
||||
cause: error,
|
||||
details: {
|
||||
causeMessage: getErrorMessage(error),
|
||||
canvasHeight: canvas.height,
|
||||
canvasWidth: canvas.width,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,150 @@
|
|||
import { ErrorHandler, Severity } from '../error-handler';
|
||||
import {
|
||||
ErrorCode,
|
||||
ErrorHandler,
|
||||
getErrorMessage,
|
||||
RuntimeError,
|
||||
Severity,
|
||||
} from '../error-handler';
|
||||
|
||||
const WEBGPU_BROWSER_SUPPORT_MESSAGE =
|
||||
'Fleeting Garden needs WebGPU. Try the latest Chrome, Edge, or another browser with WebGPU enabled.';
|
||||
|
||||
const REQUESTED_LIMIT_NAMES = [
|
||||
'maxBufferSize',
|
||||
'maxStorageBufferBindingSize',
|
||||
'maxComputeWorkgroupsPerDimension',
|
||||
] as const satisfies ReadonlyArray<keyof GPUSupportedLimits>;
|
||||
|
||||
const getRequiredLimits = (
|
||||
limits: GPUSupportedLimits
|
||||
): Record<(typeof REQUESTED_LIMIT_NAMES)[number], number> =>
|
||||
Object.fromEntries(REQUESTED_LIMIT_NAMES.map((name) => [name, limits[name]])) as Record<
|
||||
(typeof REQUESTED_LIMIT_NAMES)[number],
|
||||
number
|
||||
>;
|
||||
|
||||
const getAdapterInfo = (adapter: GPUAdapter): Record<string, unknown> => {
|
||||
try {
|
||||
const info = adapter.info;
|
||||
return {
|
||||
architecture: info.architecture,
|
||||
description: info.description,
|
||||
device: info.device,
|
||||
isFallbackAdapter: info.isFallbackAdapter,
|
||||
subgroupMaxSize: info.subgroupMaxSize,
|
||||
subgroupMinSize: info.subgroupMinSize,
|
||||
vendor: info.vendor,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
unavailableReason: getErrorMessage(error),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const requestAdapter = async (
|
||||
gpu: GPU,
|
||||
options?: GPURequestAdapterOptions
|
||||
): Promise<GPUAdapter | null> => {
|
||||
try {
|
||||
return await gpu.requestAdapter(options);
|
||||
} catch (error) {
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_ADAPTER_UNAVAILABLE,
|
||||
'Could not request a WebGPU adapter.',
|
||||
{
|
||||
cause: error,
|
||||
details: {
|
||||
causeMessage: getErrorMessage(error),
|
||||
powerPreference: options?.powerPreference ?? 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const initializeGpu = async (): Promise<GPUDevice> => {
|
||||
if (window.isSecureContext === false) {
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_INSECURE_CONTEXT,
|
||||
'WebGPU requires a secure context. Open Fleeting Garden over HTTPS or from localhost.'
|
||||
);
|
||||
}
|
||||
|
||||
const gpu = navigator.gpu;
|
||||
if (!gpu) {
|
||||
throw new Error('WebGPU is not supported in your browser');
|
||||
throw new RuntimeError(ErrorCode.WEBGPU_UNSUPPORTED, WEBGPU_BROWSER_SUPPORT_MESSAGE, {
|
||||
details: {
|
||||
hasNavigatorGpu: false,
|
||||
isSecureContext: window.isSecureContext,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const adapter = await gpu.requestAdapter({
|
||||
powerPreference: 'high-performance',
|
||||
});
|
||||
const adapter =
|
||||
(await requestAdapter(gpu, {
|
||||
powerPreference: 'high-performance',
|
||||
})) ?? (await requestAdapter(gpu));
|
||||
|
||||
if (!adapter) {
|
||||
throw new Error('Could not request adatper');
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_ADAPTER_UNAVAILABLE,
|
||||
'WebGPU is available, but this browser could not provide a compatible GPU adapter.'
|
||||
);
|
||||
}
|
||||
|
||||
ErrorHandler.addMetadata('features', adapter.features);
|
||||
ErrorHandler.addMetadata('limits', adapter.limits);
|
||||
|
||||
const gpuDevice = await adapter.requestDevice({
|
||||
requiredLimits: {
|
||||
maxBufferSize: adapter.limits.maxBufferSize,
|
||||
maxStorageBufferBindingSize: adapter.limits.maxStorageBufferBindingSize,
|
||||
maxComputeWorkgroupsPerDimension: adapter.limits.maxComputeWorkgroupsPerDimension,
|
||||
},
|
||||
const requiredLimits = getRequiredLimits(adapter.limits);
|
||||
ErrorHandler.addMetadata('webgpuAdapter', {
|
||||
features: Array.from(adapter.features).sort(),
|
||||
info: getAdapterInfo(adapter),
|
||||
requiredLimits,
|
||||
});
|
||||
|
||||
let gpuDevice: GPUDevice;
|
||||
try {
|
||||
gpuDevice = await adapter.requestDevice({
|
||||
requiredLimits,
|
||||
});
|
||||
} catch (error) {
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_DEVICE_UNAVAILABLE,
|
||||
'Could not create a WebGPU device for this adapter.',
|
||||
{
|
||||
cause: error,
|
||||
details: {
|
||||
causeMessage: getErrorMessage(error),
|
||||
requiredLimits,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (!gpuDevice) {
|
||||
throw new RuntimeError(
|
||||
ErrorCode.WEBGPU_DEVICE_UNAVAILABLE,
|
||||
'The browser returned an empty WebGPU device.'
|
||||
);
|
||||
}
|
||||
|
||||
gpuDevice.addEventListener('uncapturederror', (event: GPUUncapturedErrorEvent) =>
|
||||
ErrorHandler.addError(Severity.ERROR, event.error.message)
|
||||
ErrorHandler.addException(event.error, {
|
||||
code: ErrorCode.WEBGPU_UNCAPTURED_ERROR,
|
||||
severity: Severity.ERROR,
|
||||
})
|
||||
);
|
||||
|
||||
gpuDevice.lost.then((info) => {
|
||||
if (info.reason === 'destroyed') {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorHandler.addError(Severity.ERROR, info.message || 'The WebGPU device was lost.', {
|
||||
code: ErrorCode.WEBGPU_DEVICE_LOST,
|
||||
details: {
|
||||
reason: info.reason,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return gpuDevice;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { setUpFullScreenQuad } from './full-screen-quad';
|
||||
import { smartCompile } from './smart-compile';
|
||||
|
||||
const textureCache = new Map<string, GPUTexture>();
|
||||
const textureCache = new WeakMap<GPUDevice, Map<string, GPUTexture>>();
|
||||
const NOISE_TEXTURE_FORMAT: GPUTextureFormat = 'rgba8unorm';
|
||||
|
||||
export const generateNoise = ({
|
||||
device,
|
||||
|
|
@ -13,7 +14,13 @@ export const generateNoise = ({
|
|||
height: number;
|
||||
}): GPUTextureView => {
|
||||
const cacheKey = `${width}x${height}`;
|
||||
const cached = textureCache.get(cacheKey);
|
||||
let deviceCache = textureCache.get(device);
|
||||
if (!deviceCache) {
|
||||
deviceCache = new Map<string, GPUTexture>();
|
||||
textureCache.set(device, deviceCache);
|
||||
}
|
||||
|
||||
const cached = deviceCache.get(cacheKey);
|
||||
if (cached) {
|
||||
return cached.createView();
|
||||
}
|
||||
|
|
@ -45,7 +52,7 @@ export const generateNoise = ({
|
|||
entryPoint: 'fragment',
|
||||
targets: [
|
||||
{
|
||||
format: 'rgba16float',
|
||||
format: NOISE_TEXTURE_FORMAT,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
@ -60,7 +67,7 @@ export const generateNoise = ({
|
|||
height,
|
||||
depthOrArrayLayers: 1,
|
||||
},
|
||||
format: 'rgba16float',
|
||||
format: NOISE_TEXTURE_FORMAT,
|
||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
});
|
||||
|
||||
|
|
@ -84,6 +91,6 @@ export const generateNoise = ({
|
|||
passEncoder.end();
|
||||
|
||||
device.queue.submit([commandEncoder.finish()]);
|
||||
textureCache.set(cacheKey, colorTexture);
|
||||
deviceCache.set(cacheKey, colorTexture);
|
||||
return colorTexture.createView();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export class ResizableTexture {
|
|||
size: vec2
|
||||
) {
|
||||
this.copyPipeline = new CopyPipeline(this.device);
|
||||
this.size = size;
|
||||
this.size = vec2.clone(size);
|
||||
this.texture = this.createTexture(size);
|
||||
this.textureView = this.texture.createView();
|
||||
}
|
||||
|
|
@ -36,11 +36,15 @@ export class ResizableTexture {
|
|||
this.device.queue.submit([commandEncoder.finish()]);
|
||||
this.texture.destroy();
|
||||
|
||||
this.size = size;
|
||||
this.size = vec2.clone(size);
|
||||
this.texture = newTexture;
|
||||
this.textureView = newTextureView;
|
||||
}
|
||||
|
||||
public getSize(): vec2 {
|
||||
return vec2.clone(this.size);
|
||||
}
|
||||
|
||||
public getTextureView(): GPUTextureView {
|
||||
return this.textureView;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue