Advance WebGPU volume and bounded workflows

This commit is contained in:
mes123456
2026-08-14 18:08:29 -04:00
parent 3da1dfc804
commit 68d50f810f
119 changed files with 9028 additions and 430 deletions

View File

@@ -67,6 +67,21 @@ export interface SequencerRuntimeCapabilityIR {
localEncoding: "BLOCKED";
}
export interface SequencerFrameStripIR {
stripId: string;
channel: number;
sourceFrame: number;
dependencyStripIds: string[];
}
export interface SequencerTransitionFrameIR {
effectStripId: string;
effectType: "CROSS" | "GAMMA_CROSS";
factor: number;
from: { stripId: string; sourceFrame: number };
to: { stripId: string; sourceFrame: number };
}
export class SequencerValidationError extends Error {
readonly code: ErrorCode;
@@ -236,6 +251,54 @@ export function sequencerSourceFrame(strip: SequencerStripIR, timelineFrame: num
return Math.min(strip.sourceEnd, Math.max(strip.sourceStart, strip.sourceStart + (timelineFrame - strip.frameStart) * strip.speed));
}
export function resolveSequencerFrame(value: unknown, timelineFrame: number): SequencerFrameStripIR[] {
const timeline = parseSequencerTimeline(value);
if (!Number.isFinite(timelineFrame) || timelineFrame < timeline.frameStart || timelineFrame > timeline.frameEnd) throw new SequencerValidationError("SEQUENCER_SCHEMA_INVALID", `Timeline frame ${timelineFrame} is outside the scene range`);
const active = timeline.strips.filter((strip) => !strip.muted && timelineFrame >= strip.frameStart && timelineFrame < strip.frameEnd);
const activeIds = new Set(active.map((strip) => strip.id));
const hiddenByMeta = new Set(active.filter((strip) => strip.type === "META").flatMap((strip) => strip.childStripIds ?? []));
const result = active.filter((strip) => !hiddenByMeta.has(strip.id)).map((strip): SequencerFrameStripIR => {
const dependencies = [...(strip.inputStripIds ?? []), ...(strip.childStripIds ?? [])];
if (dependencies.some((id) => !activeIds.has(id))) throw new SequencerValidationError("SEQUENCER_RESOURCE_MISSING", `${strip.id} has an inactive frame dependency`);
return { stripId: strip.id, channel: strip.channel, sourceFrame: sequencerSourceFrame(strip, timelineFrame), dependencyStripIds: [...dependencies] };
});
result.sort((left, right) => left.channel - right.channel || left.stripId.localeCompare(right.stripId));
return result;
}
/** Resolves the ordered inputs and bounded progress for the verified cross-transition subset. */
export function resolveSequencerTransitionFrame(
value: unknown,
effectStripId: string,
timelineFrame: number,
): SequencerTransitionFrameIR {
const timeline = parseSequencerTimeline(value);
const effect = timeline.strips.find((strip) => strip.id === effectStripId);
if (!effect || effect.type !== "EFFECT" ||
(effect.effectType !== "CROSS" && effect.effectType !== "GAMMA_CROSS") ||
effect.inputStripIds?.length !== 2) {
throw new SequencerValidationError("SEQUENCER_SCHEMA_INVALID", `${effectStripId} is not a supported two-input cross transition`);
}
if (!Number.isFinite(timelineFrame) || timelineFrame < effect.frameStart || timelineFrame >= effect.frameEnd) {
throw new SequencerValidationError("SEQUENCER_SCHEMA_INVALID", `${effectStripId} is inactive at frame ${timelineFrame}`);
}
const [fromId, toId] = effect.inputStripIds;
const from = timeline.strips.find((strip) => strip.id === fromId);
const to = timeline.strips.find((strip) => strip.id === toId);
if (!from || !to || from.muted || to.muted || timelineFrame < from.frameStart || timelineFrame >= from.frameEnd ||
timelineFrame < to.frameStart || timelineFrame >= to.frameEnd) {
throw new SequencerValidationError("SEQUENCER_RESOURCE_MISSING", `${effectStripId} has an inactive transition input`);
}
const factor = (timelineFrame - effect.frameStart) / (effect.frameEnd - effect.frameStart);
return {
effectStripId,
effectType: effect.effectType,
factor,
from: { stripId: from.id, sourceFrame: sequencerSourceFrame(from, timelineFrame) },
to: { stripId: to.id, sourceFrame: sequencerSourceFrame(to, timelineFrame) },
};
}
export function sequencerRuntimeCapabilities(scope: typeof globalThis = globalThis): SequencerRuntimeCapabilityIR {
return {
webCodecsVideo: "VideoDecoder" in scope ? "PROBE_REQUIRED" : "UNAVAILABLE",