@motion-core/motion-gpu
Version:
Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte, React, and Vue adapter entrypoints.
153 lines (143 loc) • 4.87 kB
text/typescript
import {
assertFloatRenderableFormat,
assertFloatSampledFormat,
assertRenderableFormat,
createFormatCapabilityError
} from './format-capabilities.js';
import { builtInRenderPassBrand } from './pass-brand.js';
import {
isBuiltInRenderPass,
isManagedComputePass,
isManagedFeedbackPass
} from './pass-contract.js';
import type {
AnyPass,
RenderPass,
RenderPassInputSlot,
RenderPassOutputSlot,
RenderTargetDefinitionMap
} from './types.js';
export type RenderTargetFormatMap = Readonly<Record<string, GPUTextureFormat>>;
/** Validates the renderer-owned working texture used by scene and presentation pipelines. */
export function validateWorkingFormat(format: unknown, deviceFeatures: ReadonlySet<string>): void {
const diagnostic = {
format,
target: 'workingFormat',
pass: 'Scene pipeline',
deviceFeatures
};
assertFloatRenderableFormat(diagnostic);
assertFloatSampledFormat(diagnostic);
}
/** Validates named color targets and returns their effective formats. */
export function validateRenderTargetFormats(
definitions: RenderTargetDefinitionMap | undefined,
defaultFormat: GPUTextureFormat,
deviceFeatures: ReadonlySet<string>
): RenderTargetFormatMap {
const formats: Record<string, GPUTextureFormat> = {};
for (const key of Object.keys(definitions ?? {}).sort()) {
const format = definitions?.[key]?.format ?? defaultFormat;
assertRenderableFormat({
format,
target: key,
pass: 'Named render target',
deviceFeatures
});
formats[key] = format;
}
return formats;
}
function resolveInputFormat(
slot: RenderPassInputSlot,
workingFormat: GPUTextureFormat,
namedFormats: RenderTargetFormatMap
): GPUTextureFormat | undefined {
return slot === 'source' || slot === 'target' ? workingFormat : namedFormats[slot];
}
function resolveOutputFormat(
slot: RenderPassOutputSlot,
workingFormat: GPUTextureFormat,
namedFormats: RenderTargetFormatMap
): GPUTextureFormat | undefined {
if (slot === 'source' || slot === 'target') return workingFormat;
if (slot === 'canvas') return workingFormat;
return namedFormats[slot];
}
/** Validates only nominally branded MotionGPU render passes; structural custom passes stay open. */
export function validateBuiltInRenderPassFormats(input: {
passes: readonly AnyPass[];
workingFormat: GPUTextureFormat;
namedFormats: RenderTargetFormatMap;
deviceFeatures: ReadonlySet<string>;
}): void {
for (const candidate of input.passes) {
if (candidate.enabled === false || !isBuiltInRenderPass(candidate)) continue;
const pass = candidate as RenderPass;
const contract = candidate[builtInRenderPassBrand];
const inputSlot = pass.input ?? 'source';
const outputSlot = pass.output ?? ((pass.needsSwap ?? true) ? 'target' : 'source');
const inputFormat = resolveInputFormat(inputSlot, input.workingFormat, input.namedFormats);
const outputFormat = resolveOutputFormat(outputSlot, input.workingFormat, input.namedFormats);
if (inputFormat !== undefined) {
assertFloatSampledFormat({
format: inputFormat,
target: String(inputSlot),
pass: contract.passName,
deviceFeatures: input.deviceFeatures
});
}
if (outputFormat !== undefined) {
assertFloatRenderableFormat({
format: outputFormat,
target: String(outputSlot),
pass: contract.passName,
deviceFeatures: input.deviceFeatures
});
}
}
}
/** Resolves the slot sampled by the implicit presentation pass for a static pass list. */
export function resolvePresentationSourceSlot(
passes: readonly AnyPass[]
): RenderPassOutputSlot | null {
let finalOutput: RenderPassOutputSlot | null = null;
for (const candidate of passes) {
if (
candidate.enabled === false ||
isManagedComputePass(candidate) ||
isManagedFeedbackPass(candidate)
) {
continue;
}
const pass = candidate as RenderPass;
finalOutput = (pass.needsSwap ?? true) ? 'source' : (pass.output ?? 'source');
}
return finalOutput;
}
/** Validates the implicit fullscreen presentation pass input before graph execution. */
export function validatePresentationSourceFormat(input: {
slot: RenderPassOutputSlot;
workingFormat: GPUTextureFormat;
namedFormats: RenderTargetFormatMap;
deviceFeatures: ReadonlySet<string>;
requiresFilterableInput: boolean;
}): void {
const format = resolveOutputFormat(input.slot, input.workingFormat, input.namedFormats);
if (format === undefined) return;
const capabilities = assertFloatSampledFormat({
format,
target: String(input.slot),
pass: 'Presentation pass',
deviceFeatures: input.deviceFeatures
});
if (input.requiresFilterableInput && !capabilities.filterable) {
throw createFormatCapabilityError({
target: String(input.slot),
format,
pass: 'Presentation pass',
capability: 'filterable float texture sampling',
detail: 'End the graph on workingFormat or enable the format-specific filtering feature.'
});
}
}