feat(image-pipeline): add wasm simd fallback backend scaffold
This commit is contained in:
@@ -18,6 +18,7 @@ import {
|
||||
createWebglPreviewBackend,
|
||||
isWebglPreviewPipelineSupported,
|
||||
} from "@/lib/image-pipeline/backend/webgl/webgl-backend";
|
||||
import { createWasmSimdBackend } from "@/lib/image-pipeline/backend/wasm/wasm-backend";
|
||||
|
||||
type BackendFallbackReason = "unsupported_api" | "flag_disabled" | "runtime_error";
|
||||
|
||||
@@ -249,6 +250,8 @@ type RolloutRouterState = {
|
||||
router: BackendRouter;
|
||||
webglAvailable: boolean;
|
||||
webglEnabled: boolean;
|
||||
wasmAvailable: boolean;
|
||||
wasmEnabled: boolean;
|
||||
};
|
||||
|
||||
let cachedRolloutState: RolloutRouterState | null = null;
|
||||
@@ -258,12 +261,15 @@ function getRolloutRouterState(): RolloutRouterState {
|
||||
const featureFlags = getBackendFeatureFlags();
|
||||
const capabilities = detectBackendCapabilities();
|
||||
const webglAvailable = capabilities.webgl;
|
||||
const wasmAvailable = capabilities.wasmSimd;
|
||||
const webglEnabled = featureFlags.webglEnabled && !featureFlags.forceCpu;
|
||||
const wasmEnabled = featureFlags.wasmEnabled && !featureFlags.forceCpu;
|
||||
const rolloutKey = JSON.stringify({
|
||||
forceCpu: featureFlags.forceCpu,
|
||||
webglEnabled: featureFlags.webglEnabled,
|
||||
wasmEnabled: featureFlags.wasmEnabled,
|
||||
webglAvailable,
|
||||
wasmAvailable,
|
||||
});
|
||||
|
||||
if (cachedRolloutState && cachedRolloutKey === rolloutKey) {
|
||||
@@ -272,18 +278,25 @@ function getRolloutRouterState(): RolloutRouterState {
|
||||
|
||||
cachedRolloutState = {
|
||||
router: createBackendRouter({
|
||||
backends: [cpuBackend, createWebglPreviewBackend()],
|
||||
defaultBackendId: "webgl",
|
||||
backends: [cpuBackend, createWasmSimdBackend(), createWebglPreviewBackend()],
|
||||
defaultBackendId:
|
||||
webglEnabled && webglAvailable ? "webgl" : wasmEnabled && wasmAvailable ? "wasm" : CPU_BACKEND_ID,
|
||||
backendAvailability: {
|
||||
webgl: {
|
||||
supported: webglAvailable,
|
||||
enabled: webglEnabled,
|
||||
},
|
||||
wasm: {
|
||||
supported: wasmAvailable,
|
||||
enabled: wasmEnabled,
|
||||
},
|
||||
},
|
||||
featureFlags,
|
||||
}),
|
||||
webglAvailable,
|
||||
webglEnabled,
|
||||
wasmAvailable,
|
||||
wasmEnabled,
|
||||
};
|
||||
cachedRolloutKey = rolloutKey;
|
||||
|
||||
@@ -293,11 +306,15 @@ function getRolloutRouterState(): RolloutRouterState {
|
||||
export function getPreviewBackendHintForSteps(steps: readonly PreviewBackendRequest["step"][]): BackendHint {
|
||||
const rolloutState = getRolloutRouterState();
|
||||
|
||||
if (!rolloutState.webglEnabled || !rolloutState.webglAvailable) {
|
||||
return CPU_BACKEND_ID;
|
||||
if (rolloutState.webglEnabled && rolloutState.webglAvailable) {
|
||||
return isWebglPreviewPipelineSupported(steps) ? "webgl" : CPU_BACKEND_ID;
|
||||
}
|
||||
|
||||
return isWebglPreviewPipelineSupported(steps) ? "webgl" : CPU_BACKEND_ID;
|
||||
if (rolloutState.wasmEnabled && rolloutState.wasmAvailable) {
|
||||
return "wasm";
|
||||
}
|
||||
|
||||
return CPU_BACKEND_ID;
|
||||
}
|
||||
|
||||
export function runPreviewStepWithBackendRouter(request: PreviewBackendRequest): void {
|
||||
|
||||
@@ -10,7 +10,7 @@ type CapabilityProbes = {
|
||||
probeOffscreenCanvas: () => boolean;
|
||||
};
|
||||
|
||||
const WASM_SIMD_PROBE_MODULE = new Uint8Array([
|
||||
export const WASM_SIMD_PROBE_MODULE = new Uint8Array([
|
||||
0x00,
|
||||
0x61,
|
||||
0x73,
|
||||
|
||||
37
lib/image-pipeline/backend/wasm/wasm-backend.ts
Normal file
37
lib/image-pipeline/backend/wasm/wasm-backend.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type {
|
||||
BackendPipelineRequest,
|
||||
BackendStepRequest,
|
||||
ImagePipelineBackend,
|
||||
} from "@/lib/image-pipeline/backend/backend-types";
|
||||
import {
|
||||
loadWasmKernelModule,
|
||||
type WasmKernelModule,
|
||||
} from "@/lib/image-pipeline/backend/wasm/wasm-loader";
|
||||
|
||||
type WasmBackendOptions = {
|
||||
loadModule?: () => WasmKernelModule;
|
||||
};
|
||||
|
||||
export function createWasmSimdBackend(options?: WasmBackendOptions): ImagePipelineBackend {
|
||||
const loadModule = options?.loadModule ?? loadWasmKernelModule;
|
||||
let kernelModule: WasmKernelModule | null = null;
|
||||
|
||||
function ensureModule(): WasmKernelModule {
|
||||
if (kernelModule) {
|
||||
return kernelModule;
|
||||
}
|
||||
|
||||
kernelModule = loadModule();
|
||||
return kernelModule;
|
||||
}
|
||||
|
||||
return {
|
||||
id: "wasm",
|
||||
runPreviewStep(request: BackendStepRequest): void {
|
||||
ensureModule().applyPreviewStep(request);
|
||||
},
|
||||
runFullPipeline(request: BackendPipelineRequest): void {
|
||||
ensureModule().applyFullPipeline(request);
|
||||
},
|
||||
};
|
||||
}
|
||||
65
lib/image-pipeline/backend/wasm/wasm-loader.ts
Normal file
65
lib/image-pipeline/backend/wasm/wasm-loader.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type {
|
||||
BackendPipelineRequest,
|
||||
BackendStepRequest,
|
||||
} from "@/lib/image-pipeline/backend/backend-types";
|
||||
import { WASM_SIMD_PROBE_MODULE } from "@/lib/image-pipeline/backend/capabilities";
|
||||
import { applyPipelineStep, applyPipelineSteps } from "@/lib/image-pipeline/render-core";
|
||||
|
||||
export type WasmKernelModule = {
|
||||
applyPreviewStep: (request: BackendStepRequest) => void;
|
||||
applyFullPipeline: (request: BackendPipelineRequest) => void;
|
||||
};
|
||||
|
||||
let cachedModule: WasmKernelModule | null = null;
|
||||
|
||||
function assertWasmSimdRuntimeSupport(): void {
|
||||
if (typeof WebAssembly === "undefined") {
|
||||
throw new Error("WebAssembly runtime is unavailable.");
|
||||
}
|
||||
|
||||
if (typeof WebAssembly.validate !== "function") {
|
||||
throw new Error("WebAssembly validation API is unavailable.");
|
||||
}
|
||||
|
||||
if (!WebAssembly.validate(WASM_SIMD_PROBE_MODULE)) {
|
||||
throw new Error("WebAssembly SIMD is unavailable.");
|
||||
}
|
||||
|
||||
const module = new WebAssembly.Module(WASM_SIMD_PROBE_MODULE);
|
||||
void new WebAssembly.Instance(module);
|
||||
}
|
||||
|
||||
export function loadWasmKernelModule(): WasmKernelModule {
|
||||
if (cachedModule) {
|
||||
return cachedModule;
|
||||
}
|
||||
|
||||
assertWasmSimdRuntimeSupport();
|
||||
|
||||
cachedModule = {
|
||||
applyPreviewStep(request): void {
|
||||
applyPipelineStep(
|
||||
request.pixels,
|
||||
request.step,
|
||||
request.width,
|
||||
request.height,
|
||||
request.executionOptions,
|
||||
);
|
||||
},
|
||||
applyFullPipeline(request): void {
|
||||
applyPipelineSteps(
|
||||
request.pixels,
|
||||
request.steps,
|
||||
request.width,
|
||||
request.height,
|
||||
request.executionOptions,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
return cachedModule;
|
||||
}
|
||||
|
||||
export function resetWasmKernelModuleCache(): void {
|
||||
cachedModule = null;
|
||||
}
|
||||
Reference in New Issue
Block a user