@motion-core/motion-gpu 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -157,6 +157,12 @@ npm i @motion-core/motion-gpu
157
157
 
158
158
  ---
159
159
 
160
+ # AI Documentation
161
+
162
+ MotionGPU documentation is also available for AI tools via [Context7](https://context7.com/motion-core/motion-gpu).
163
+
164
+ ---
165
+
160
166
  # Quick Start
161
167
 
162
168
  ## 1. Create a material and render it
@@ -10,6 +10,9 @@ export function createCurrentWritable(initialValue, onChange) {
10
10
  }
11
11
  };
12
12
  const set = (value) => {
13
+ if (Object.is(current, value)) {
14
+ return;
15
+ }
13
16
  current = value;
14
17
  notify(value);
15
18
  onChange?.(value);
@@ -19,6 +19,19 @@ export interface ShaderCompilationDiagnostic {
19
19
  lineLength?: number;
20
20
  sourceLocation: MaterialSourceLocation | null;
21
21
  }
22
+ /**
23
+ * Runtime context snapshot captured for shader compilation diagnostics.
24
+ */
25
+ export interface ShaderCompilationRuntimeContext {
26
+ materialSignature?: string;
27
+ passGraph?: {
28
+ passCount: number;
29
+ enabledPassCount: number;
30
+ inputs: string[];
31
+ outputs: string[];
32
+ };
33
+ activeRenderTargets: string[];
34
+ }
22
35
  /**
23
36
  * Structured payload attached to WGSL compilation errors.
24
37
  */
@@ -29,6 +42,7 @@ export interface ShaderCompilationDiagnosticsPayload {
29
42
  includeSources: Record<string, string>;
30
43
  defineBlockSource?: string;
31
44
  materialSource: MaterialSourceMetadata | null;
45
+ runtimeContext?: ShaderCompilationRuntimeContext;
32
46
  }
33
47
  /**
34
48
  * Attaches structured diagnostics payload to an Error.
@@ -56,6 +56,39 @@ function isShaderCompilationDiagnostic(value) {
56
56
  }
57
57
  return true;
58
58
  }
59
+ function isStringArray(value) {
60
+ return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
61
+ }
62
+ function isShaderCompilationRuntimeContext(value) {
63
+ if (value === null || typeof value !== 'object') {
64
+ return false;
65
+ }
66
+ const record = value;
67
+ if (record.materialSignature !== undefined && typeof record.materialSignature !== 'string') {
68
+ return false;
69
+ }
70
+ if (!isStringArray(record.activeRenderTargets)) {
71
+ return false;
72
+ }
73
+ const passGraph = record.passGraph;
74
+ if (passGraph === undefined) {
75
+ return true;
76
+ }
77
+ if (passGraph === null || typeof passGraph !== 'object') {
78
+ return false;
79
+ }
80
+ const passGraphRecord = passGraph;
81
+ if (typeof passGraphRecord.passCount !== 'number') {
82
+ return false;
83
+ }
84
+ if (typeof passGraphRecord.enabledPassCount !== 'number') {
85
+ return false;
86
+ }
87
+ if (!isStringArray(passGraphRecord.inputs) || !isStringArray(passGraphRecord.outputs)) {
88
+ return false;
89
+ }
90
+ return true;
91
+ }
59
92
  /**
60
93
  * Attaches structured diagnostics payload to an Error.
61
94
  */
@@ -98,6 +131,10 @@ export function getShaderCompilationDiagnostics(error) {
98
131
  if (record.materialSource !== null && !isMaterialSourceMetadata(record.materialSource)) {
99
132
  return null;
100
133
  }
134
+ if (record.runtimeContext !== undefined &&
135
+ !isShaderCompilationRuntimeContext(record.runtimeContext)) {
136
+ return null;
137
+ }
101
138
  return {
102
139
  kind: 'shader-compilation',
103
140
  diagnostics: record.diagnostics,
@@ -106,6 +143,9 @@ export function getShaderCompilationDiagnostics(error) {
106
143
  ...(record.defineBlockSource !== undefined
107
144
  ? { defineBlockSource: record.defineBlockSource }
108
145
  : {}),
109
- materialSource: (record.materialSource ?? null)
146
+ materialSource: (record.materialSource ?? null),
147
+ ...(record.runtimeContext !== undefined
148
+ ? { runtimeContext: record.runtimeContext }
149
+ : {})
110
150
  };
111
151
  }
@@ -2,6 +2,14 @@
2
2
  * Runtime phase in which an error occurred.
3
3
  */
4
4
  export type MotionGPUErrorPhase = 'initialization' | 'render';
5
+ /**
6
+ * Stable machine-readable error category code.
7
+ */
8
+ export type MotionGPUErrorCode = 'WEBGPU_UNAVAILABLE' | 'WEBGPU_ADAPTER_UNAVAILABLE' | 'WEBGPU_CONTEXT_UNAVAILABLE' | 'WGSL_COMPILATION_FAILED' | 'WEBGPU_DEVICE_LOST' | 'WEBGPU_UNCAPTURED_ERROR' | 'BIND_GROUP_MISMATCH' | 'TEXTURE_USAGE_INVALID' | 'TEXTURE_REQUEST_FAILED' | 'TEXTURE_DECODE_UNAVAILABLE' | 'TEXTURE_REQUEST_ABORTED' | 'MOTIONGPU_RUNTIME_ERROR';
9
+ /**
10
+ * Severity level for user-facing diagnostics.
11
+ */
12
+ export type MotionGPUErrorSeverity = 'error' | 'fatal';
5
13
  /**
6
14
  * One source-code line displayed in diagnostics snippet.
7
15
  */
@@ -20,10 +28,35 @@ export interface MotionGPUErrorSource {
20
28
  column?: number;
21
29
  snippet: MotionGPUErrorSourceLine[];
22
30
  }
31
+ /**
32
+ * Optional runtime context captured with diagnostics payload.
33
+ */
34
+ export interface MotionGPUErrorContext {
35
+ materialSignature?: string;
36
+ passGraph?: {
37
+ passCount: number;
38
+ enabledPassCount: number;
39
+ inputs: string[];
40
+ outputs: string[];
41
+ };
42
+ activeRenderTargets: string[];
43
+ }
23
44
  /**
24
45
  * Structured error payload used by UI diagnostics.
25
46
  */
26
47
  export interface MotionGPUErrorReport {
48
+ /**
49
+ * Stable machine-readable category code.
50
+ */
51
+ code: MotionGPUErrorCode;
52
+ /**
53
+ * Severity level used by diagnostics UIs and telemetry.
54
+ */
55
+ severity: MotionGPUErrorSeverity;
56
+ /**
57
+ * Whether runtime may recover without full renderer re-creation.
58
+ */
59
+ recoverable: boolean;
27
60
  /**
28
61
  * Short category title.
29
62
  */
@@ -56,6 +89,10 @@ export interface MotionGPUErrorReport {
56
89
  * Optional source context for shader-related diagnostics.
57
90
  */
58
91
  source: MotionGPUErrorSource | null;
92
+ /**
93
+ * Optional runtime context snapshot (material/pass graph/render targets).
94
+ */
95
+ context: MotionGPUErrorContext | null;
59
96
  }
60
97
  /**
61
98
  * Converts unknown errors to a consistent, display-ready error report.
@@ -98,53 +98,107 @@ function formatDiagnosticMessage(entry) {
98
98
  function classifyErrorMessage(message) {
99
99
  if (message.includes('WebGPU is not available in this browser')) {
100
100
  return {
101
+ code: 'WEBGPU_UNAVAILABLE',
102
+ severity: 'fatal',
103
+ recoverable: false,
101
104
  title: 'WebGPU unavailable',
102
105
  hint: 'Use a browser with WebGPU enabled (latest Chrome/Edge/Safari TP) and secure context.'
103
106
  };
104
107
  }
105
108
  if (message.includes('Unable to acquire WebGPU adapter')) {
106
109
  return {
110
+ code: 'WEBGPU_ADAPTER_UNAVAILABLE',
111
+ severity: 'fatal',
112
+ recoverable: false,
107
113
  title: 'WebGPU adapter unavailable',
108
114
  hint: 'GPU adapter request failed. Check browser permissions, flags and device support.'
109
115
  };
110
116
  }
111
117
  if (message.includes('Canvas does not support webgpu context')) {
112
118
  return {
119
+ code: 'WEBGPU_CONTEXT_UNAVAILABLE',
120
+ severity: 'error',
121
+ recoverable: true,
113
122
  title: 'Canvas cannot create WebGPU context',
114
123
  hint: 'Make sure this canvas is attached to DOM and not using an unsupported context option.'
115
124
  };
116
125
  }
117
126
  if (message.includes('WGSL compilation failed')) {
118
127
  return {
128
+ code: 'WGSL_COMPILATION_FAILED',
129
+ severity: 'error',
130
+ recoverable: true,
119
131
  title: 'WGSL compilation failed',
120
132
  hint: 'Check WGSL line numbers below and verify struct/binding/function signatures.'
121
133
  };
122
134
  }
123
135
  if (message.includes('WebGPU device lost') || message.includes('Device Lost')) {
124
136
  return {
137
+ code: 'WEBGPU_DEVICE_LOST',
138
+ severity: 'fatal',
139
+ recoverable: false,
125
140
  title: 'WebGPU device lost',
126
141
  hint: 'GPU device/context was lost. Recreate the renderer and check OS/GPU stability.'
127
142
  };
128
143
  }
129
144
  if (message.includes('WebGPU uncaptured error')) {
130
145
  return {
146
+ code: 'WEBGPU_UNCAPTURED_ERROR',
147
+ severity: 'error',
148
+ recoverable: true,
131
149
  title: 'WebGPU uncaptured error',
132
150
  hint: 'A GPU command failed asynchronously. Review details and validate resource/state usage.'
133
151
  };
134
152
  }
135
153
  if (message.includes('CreateBindGroup') || message.includes('bind group layout')) {
136
154
  return {
155
+ code: 'BIND_GROUP_MISMATCH',
156
+ severity: 'error',
157
+ recoverable: true,
137
158
  title: 'Bind group mismatch',
138
159
  hint: 'Bindings in shader and runtime resources are out of sync. Verify uniforms/textures layout.'
139
160
  };
140
161
  }
141
162
  if (message.includes('Destination texture needs to have CopyDst')) {
142
163
  return {
164
+ code: 'TEXTURE_USAGE_INVALID',
165
+ severity: 'error',
166
+ recoverable: true,
143
167
  title: 'Invalid texture usage flags',
144
168
  hint: 'Texture used as upload destination must include CopyDst (and often RenderAttachment).'
145
169
  };
146
170
  }
171
+ if (message.includes('Texture request failed')) {
172
+ return {
173
+ code: 'TEXTURE_REQUEST_FAILED',
174
+ severity: 'error',
175
+ recoverable: true,
176
+ title: 'Texture request failed',
177
+ hint: 'Verify texture URL, CORS policy and response status before retrying.'
178
+ };
179
+ }
180
+ if (message.includes('createImageBitmap is not available in this runtime')) {
181
+ return {
182
+ code: 'TEXTURE_DECODE_UNAVAILABLE',
183
+ severity: 'fatal',
184
+ recoverable: false,
185
+ title: 'Texture decode unavailable',
186
+ hint: 'Runtime lacks createImageBitmap support. Use a browser/runtime with image bitmap decoding.'
187
+ };
188
+ }
189
+ if (message.toLowerCase().includes('texture request was aborted')) {
190
+ return {
191
+ code: 'TEXTURE_REQUEST_ABORTED',
192
+ severity: 'error',
193
+ recoverable: true,
194
+ title: 'Texture request aborted',
195
+ hint: 'Texture load was cancelled. Retry the request when source inputs stabilize.'
196
+ };
197
+ }
147
198
  return {
199
+ code: 'MOTIONGPU_RUNTIME_ERROR',
200
+ severity: 'error',
201
+ recoverable: true,
148
202
  title: 'MotionGPU render error',
149
203
  hint: 'Review technical details below. If issue persists, isolate shader/uniform/texture changes.'
150
204
  };
@@ -167,6 +221,7 @@ export function toMotionGPUErrorReport(error, phase) {
167
221
  const defaultMessage = rawLines[0] ?? rawMessage;
168
222
  const defaultDetails = rawLines.slice(1);
169
223
  const source = buildSourceFromDiagnostics(error);
224
+ const context = shaderDiagnostics?.runtimeContext ?? null;
170
225
  const message = shaderDiagnostics && shaderDiagnostics.diagnostics[0]
171
226
  ? formatDiagnosticMessage(shaderDiagnostics.diagnostics[0])
172
227
  : defaultMessage;
@@ -178,6 +233,9 @@ export function toMotionGPUErrorReport(error, phase) {
178
233
  : [];
179
234
  const classification = classifyErrorMessage(rawMessage);
180
235
  return {
236
+ code: classification.code,
237
+ severity: classification.severity,
238
+ recoverable: classification.recoverable,
181
239
  title: classification.title,
182
240
  message,
183
241
  hint: classification.hint,
@@ -185,6 +243,7 @@ export function toMotionGPUErrorReport(error, phase) {
185
243
  stack,
186
244
  rawMessage,
187
245
  phase,
188
- source
246
+ source,
247
+ context
189
248
  };
190
249
  }
@@ -11,7 +11,7 @@ export { createMotionGPURuntimeLoop } from './runtime-loop.js';
11
11
  export { loadTexturesFromUrls } from './texture-loader.js';
12
12
  export { BlitPass, CopyPass, ShaderPass } from '../passes/index.js';
13
13
  export type { CurrentReadable, CurrentWritable, Subscribable } from './current-value.js';
14
- export type { MotionGPUErrorPhase, MotionGPUErrorReport, MotionGPUErrorSource, MotionGPUErrorSourceLine } from './error-report.js';
14
+ export type { MotionGPUErrorCode, MotionGPUErrorContext, MotionGPUErrorPhase, MotionGPUErrorReport, MotionGPUErrorSeverity, MotionGPUErrorSource, MotionGPUErrorSourceLine } from './error-report.js';
15
15
  export type { FrameCallback, FrameKey, FrameProfilingSnapshot, FrameRegistry, FrameRunTimings, FrameScheduleSnapshot, FrameStage, FrameStageCallback, FrameTask, FrameTaskInvalidation, FrameTaskInvalidationToken, FrameTimingStats, UseFrameOptions, UseFrameResult } from './frame-registry.js';
16
16
  export type { FragMaterial, FragMaterialInput, MaterialDefineValue, MaterialDefines, MaterialIncludes, ResolvedMaterial, TypedMaterialDefineValue } from './material.js';
17
17
  export type { MotionGPURuntimeLoop, MotionGPURuntimeLoopOptions } from './runtime-loop.js';
@@ -1,4 +1,14 @@
1
1
  import type { Renderer, RendererOptions } from './types.js';
2
+ /**
3
+ * Computes dirty float ranges between two uniform snapshots.
4
+ *
5
+ * Adjacent dirty ranges separated by a gap smaller than or equal to
6
+ * {@link DIRTY_RANGE_MERGE_GAP} are merged to reduce `writeBuffer` calls.
7
+ */
8
+ export declare function findDirtyFloatRanges(previous: Float32Array, next: Float32Array, mergeGapThreshold?: number): Array<{
9
+ start: number;
10
+ count: number;
11
+ }>;
2
12
  /**
3
13
  * Creates the WebGPU renderer used by `FragCanvas`.
4
14
  *
@@ -78,9 +78,45 @@ async function assertCompilation(module, options) {
78
78
  ...(options?.defineBlockSource !== undefined
79
79
  ? { defineBlockSource: options.defineBlockSource }
80
80
  : {}),
81
- materialSource: options?.materialSource ?? null
81
+ materialSource: options?.materialSource ?? null,
82
+ ...(options?.runtimeContext !== undefined ? { runtimeContext: options.runtimeContext } : {})
82
83
  });
83
84
  }
85
+ function toSortedUniqueStrings(values) {
86
+ return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
87
+ }
88
+ function buildPassGraphSnapshot(passes) {
89
+ const declaredPasses = passes ?? [];
90
+ let enabledPassCount = 0;
91
+ const inputs = [];
92
+ const outputs = [];
93
+ for (const pass of declaredPasses) {
94
+ if (pass.enabled === false) {
95
+ continue;
96
+ }
97
+ enabledPassCount += 1;
98
+ const needsSwap = pass.needsSwap ?? true;
99
+ const input = pass.input ?? 'source';
100
+ const output = pass.output ?? (needsSwap ? 'target' : 'source');
101
+ inputs.push(input);
102
+ outputs.push(output);
103
+ }
104
+ return {
105
+ passCount: declaredPasses.length,
106
+ enabledPassCount,
107
+ inputs: toSortedUniqueStrings(inputs),
108
+ outputs: toSortedUniqueStrings(outputs)
109
+ };
110
+ }
111
+ function buildShaderCompilationRuntimeContext(options) {
112
+ const passList = options.getPasses?.() ?? options.passes;
113
+ const renderTargetMap = options.getRenderTargets?.() ?? options.renderTargets;
114
+ return {
115
+ ...(options.materialSignature ? { materialSignature: options.materialSignature } : {}),
116
+ passGraph: buildPassGraphSnapshot(passList),
117
+ activeRenderTargets: Object.keys(renderTargetMap ?? {}).sort((a, b) => a.localeCompare(b))
118
+ };
119
+ }
84
120
  /**
85
121
  * Creates a 1x1 white fallback texture used before user textures become available.
86
122
  */
@@ -182,10 +218,19 @@ function createBindGroupLayoutEntries(textureBindings) {
182
218
  }
183
219
  return entries;
184
220
  }
221
+ /**
222
+ * Maximum gap (in floats) between two dirty ranges that triggers merge.
223
+ *
224
+ * Set to 4 (16 bytes) which covers one vec4f alignment slot.
225
+ */
226
+ const DIRTY_RANGE_MERGE_GAP = 4;
185
227
  /**
186
228
  * Computes dirty float ranges between two uniform snapshots.
229
+ *
230
+ * Adjacent dirty ranges separated by a gap smaller than or equal to
231
+ * {@link DIRTY_RANGE_MERGE_GAP} are merged to reduce `writeBuffer` calls.
187
232
  */
188
- function findDirtyFloatRanges(previous, next) {
233
+ export function findDirtyFloatRanges(previous, next, mergeGapThreshold = DIRTY_RANGE_MERGE_GAP) {
189
234
  const ranges = [];
190
235
  let start = -1;
191
236
  for (let index = 0; index < next.length; index += 1) {
@@ -203,7 +248,22 @@ function findDirtyFloatRanges(previous, next) {
203
248
  if (start !== -1) {
204
249
  ranges.push({ start, count: next.length - start });
205
250
  }
206
- return ranges;
251
+ if (ranges.length <= 1) {
252
+ return ranges;
253
+ }
254
+ const merged = [ranges[0]];
255
+ for (let index = 1; index < ranges.length; index += 1) {
256
+ const prev = merged[merged.length - 1];
257
+ const curr = ranges[index];
258
+ const gap = curr.start - (prev.start + prev.count);
259
+ if (gap <= mergeGapThreshold) {
260
+ prev.count = curr.start + curr.count - prev.start;
261
+ }
262
+ else {
263
+ merged.push(curr);
264
+ }
265
+ }
266
+ return merged;
207
267
  }
208
268
  /**
209
269
  * Determines whether shader output should perform linear-to-sRGB conversion.
@@ -339,6 +399,7 @@ export async function createRenderer(options) {
339
399
  };
340
400
  device.addEventListener('uncapturederror', handleUncapturedError);
341
401
  try {
402
+ const runtimeContext = buildShaderCompilationRuntimeContext(options);
342
403
  const convertLinearToSrgb = shouldConvertLinearToSrgb(options.outputColorSpace, format);
343
404
  const builtShader = buildShaderSourceWithMap(options.fragmentWgsl, options.uniformLayout, options.textureKeys, {
344
405
  convertLinearToSrgb,
@@ -352,7 +413,8 @@ export async function createRenderer(options) {
352
413
  ...(options.defineBlockSource !== undefined
353
414
  ? { defineBlockSource: options.defineBlockSource }
354
415
  : {}),
355
- materialSource: options.materialSource ?? null
416
+ materialSource: options.materialSource ?? null,
417
+ runtimeContext
356
418
  });
357
419
  const normalizedTextureDefinitions = normalizeTextureDefinitions(options.textureDefinitions, options.textureKeys);
358
420
  const textureBindings = options.textureKeys.map((key, index) => {
@@ -21,6 +21,9 @@ export interface MotionGPURuntimeLoopOptions {
21
21
  getDeviceDescriptor: () => GPUDeviceDescriptor | undefined;
22
22
  getOnError: () => ((report: MotionGPUErrorReport) => void) | undefined;
23
23
  reportError: (report: MotionGPUErrorReport | null) => void;
24
+ getErrorHistoryLimit?: () => number | undefined;
25
+ getOnErrorHistory?: () => ((history: MotionGPUErrorReport[]) => void) | undefined;
26
+ reportErrorHistory?: (history: MotionGPUErrorReport[]) => void;
24
27
  }
25
28
  export interface MotionGPURuntimeLoop {
26
29
  requestFrame: () => void;
@@ -33,12 +33,81 @@ export function createMotionGPURuntimeLoop(options) {
33
33
  const renderTextures = {};
34
34
  const canvasSize = { width: 0, height: 0 };
35
35
  let shouldContinueAfterFrame = false;
36
+ let activeErrorKey = null;
37
+ let errorHistory = [];
38
+ const getHistoryLimit = () => {
39
+ const value = options.getErrorHistoryLimit?.() ?? 0;
40
+ if (!Number.isFinite(value) || value <= 0) {
41
+ return 0;
42
+ }
43
+ return Math.floor(value);
44
+ };
45
+ const publishErrorHistory = () => {
46
+ options.reportErrorHistory?.(errorHistory);
47
+ const onErrorHistory = options.getOnErrorHistory?.();
48
+ if (!onErrorHistory) {
49
+ return;
50
+ }
51
+ try {
52
+ onErrorHistory(errorHistory);
53
+ }
54
+ catch {
55
+ // User-provided error history handlers must not break runtime error recovery.
56
+ }
57
+ };
58
+ const syncErrorHistory = () => {
59
+ const limit = getHistoryLimit();
60
+ if (limit <= 0) {
61
+ if (errorHistory.length === 0) {
62
+ return;
63
+ }
64
+ errorHistory = [];
65
+ publishErrorHistory();
66
+ return;
67
+ }
68
+ if (errorHistory.length <= limit) {
69
+ return;
70
+ }
71
+ errorHistory = errorHistory.slice(errorHistory.length - limit);
72
+ publishErrorHistory();
73
+ };
36
74
  const setError = (error, phase) => {
37
75
  const report = toMotionGPUErrorReport(error, phase);
76
+ const reportKey = JSON.stringify({
77
+ phase: report.phase,
78
+ title: report.title,
79
+ message: report.message,
80
+ rawMessage: report.rawMessage
81
+ });
82
+ if (activeErrorKey === reportKey) {
83
+ return;
84
+ }
85
+ activeErrorKey = reportKey;
86
+ const historyLimit = getHistoryLimit();
87
+ if (historyLimit > 0) {
88
+ errorHistory = [...errorHistory, report];
89
+ if (errorHistory.length > historyLimit) {
90
+ errorHistory = errorHistory.slice(errorHistory.length - historyLimit);
91
+ }
92
+ publishErrorHistory();
93
+ }
38
94
  options.reportError(report);
39
- options.getOnError()?.(report);
95
+ const onError = options.getOnError();
96
+ if (!onError) {
97
+ return;
98
+ }
99
+ try {
100
+ onError(report);
101
+ }
102
+ catch {
103
+ // User-provided error handlers must not break runtime error recovery.
104
+ }
40
105
  };
41
106
  const clearError = () => {
107
+ if (activeErrorKey === null) {
108
+ return;
109
+ }
110
+ activeErrorKey = null;
42
111
  options.reportError(null);
43
112
  };
44
113
  const scheduleFrame = () => {
@@ -127,6 +196,7 @@ export function createMotionGPURuntimeLoop(options) {
127
196
  if (isDisposed) {
128
197
  return;
129
198
  }
199
+ syncErrorHistory();
130
200
  let materialState;
131
201
  try {
132
202
  materialState = resolveActiveMaterial();
@@ -165,6 +235,7 @@ export function createMotionGPURuntimeLoop(options) {
165
235
  includeSources: materialState.includeSources,
166
236
  defineBlockSource: materialState.defineBlockSource,
167
237
  materialSource: materialState.source,
238
+ materialSignature: materialState.signature,
168
239
  uniformLayout: materialState.uniformLayout,
169
240
  textureKeys: materialState.textureKeys,
170
241
  textureDefinitions: materialState.textures,
@@ -443,6 +443,10 @@ export interface RendererOptions {
443
443
  column?: number;
444
444
  functionName?: string;
445
445
  } | null;
446
+ /**
447
+ * Stable material signature captured during resolution.
448
+ */
449
+ materialSignature?: string;
446
450
  /**
447
451
  * Resolved uniform layout.
448
452
  */
@@ -1,32 +1,11 @@
1
- import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types.js';
2
- export interface BlitPassOptions extends RenderPassFlags {
3
- enabled?: boolean;
4
- needsSwap?: boolean;
5
- input?: RenderPassInputSlot;
6
- output?: RenderPassOutputSlot;
7
- filter?: GPUFilterMode;
8
- }
1
+ import { FullscreenPass, type FullscreenPassOptions } from './FullscreenPass.js';
2
+ export type BlitPassOptions = FullscreenPassOptions;
9
3
  /**
10
4
  * Fullscreen texture blit pass.
11
5
  */
12
- export declare class BlitPass implements RenderPass {
13
- enabled: boolean;
14
- needsSwap: boolean;
15
- input: RenderPassInputSlot;
16
- output: RenderPassOutputSlot;
17
- clear: boolean;
18
- clearColor: [number, number, number, number];
19
- preserve: boolean;
20
- private readonly filter;
21
- private device;
22
- private sampler;
23
- private bindGroupLayout;
24
- private shaderModule;
25
- private readonly pipelineByFormat;
26
- private bindGroupByView;
6
+ export declare class BlitPass extends FullscreenPass {
7
+ protected getProgram(): string;
27
8
  constructor(options?: BlitPassOptions);
28
- private ensureResources;
29
- setSize(width: number, height: number): void;
30
- render(context: RenderPassContext): void;
31
- dispose(): void;
9
+ protected getVertexEntryPoint(): string;
10
+ protected getFragmentEntryPoint(): string;
32
11
  }