@motion-core/motion-gpu 0.4.0 → 0.4.1

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.
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  import type { RenderTargetDefinitionMap } from './types.js';
2
3
  /**
3
4
  * Concrete render target configuration resolved for current canvas size.
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  import type { CurrentReadable, CurrentWritable } from './current-value.js';
2
3
  import { type FragMaterial } from './material.js';
3
4
  import { type MotionGPUErrorReport } from './error-report.js';
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  import type { TextureData, TextureDefinition, TextureDefinitionMap, TextureUpdateMode, TextureValue } from './types.js';
2
3
  /**
3
4
  * Texture definition with defaults and normalized numeric limits applied.
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  /**
2
3
  * Core runtime and API contracts used by MotionGPU's renderer, hooks and scheduler.
3
4
  */
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types.js';
2
3
  export interface CopyPassOptions extends RenderPassFlags {
3
4
  enabled?: boolean;
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  import type { RenderPass, RenderPassContext, RenderPassFlags, RenderPassInputSlot, RenderPassOutputSlot } from '../core/types.js';
2
3
  export interface FullscreenPassOptions extends RenderPassFlags {
3
4
  enabled?: boolean;
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  import { type MotionGPUErrorReport } from '../core/error-report.js';
2
3
  import type { FragMaterial } from '../core/material.js';
3
4
  import type { OutputColorSpace, RenderPass, RenderMode, RenderTargetDefinitionMap } from '../core/types.js';
@@ -1,5 +1,293 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Portal } from './Portal.js';
3
+ const MOTIONGPU_ERROR_OVERLAY_STYLES = `
4
+ .motiongpu-error-overlay {
5
+ --motiongpu-base-hue: var(--base-hue, 265);
6
+ --motiongpu-color-background: oklch(0.2178 0.0056 var(--motiongpu-base-hue));
7
+ --motiongpu-color-background-muted: oklch(0.261 0.007 var(--motiongpu-base-hue));
8
+ --motiongpu-color-foreground: oklch(1 0 0);
9
+ --motiongpu-color-foreground-muted: oklch(0.6699 0.0081 var(--motiongpu-base-hue));
10
+ --motiongpu-color-card: var(--motiongpu-color-background);
11
+ --motiongpu-color-accent: oklch(0.6996 0.181959 44.4414);
12
+ --motiongpu-color-accent-secondary: oklch(0.5096 0.131959 44.4414);
13
+ --motiongpu-color-border: oklch(0.928 0.013 var(--motiongpu-base-hue) / 0.05);
14
+ --motiongpu-color-white-fixed: oklch(1 0 0);
15
+ --motiongpu-shadow-card: var(
16
+ --shadow-2xl,
17
+ 0px 1px 1px -0.5px rgba(0, 0, 0, 0.06),
18
+ 0px 3px 3px -1.5px rgba(0, 0, 0, 0.06),
19
+ 0px 6px 6px -3px rgba(0, 0, 0, 0.06),
20
+ 0px 12px 12px -6px rgba(0, 0, 0, 0.06),
21
+ 0px 24px 24px -12px rgba(0, 0, 0, 0.05),
22
+ 0px 48px 48px -24px rgba(0, 0, 0, 0.06)
23
+ );
24
+ --motiongpu-radius-md: var(--radius-md, 0.5rem);
25
+ --motiongpu-radius-lg: var(--radius-lg, 0.75rem);
26
+ --motiongpu-radius-xl: var(--radius-xl, 1rem);
27
+ --motiongpu-font-sans: var(
28
+ --font-sans,
29
+ 'Inter',
30
+ 'Segoe UI',
31
+ 'Helvetica Neue',
32
+ Arial,
33
+ sans-serif
34
+ );
35
+ --motiongpu-font-mono: var(--font-mono, 'SFMono-Regular', 'Menlo', 'Consolas', monospace);
36
+ position: fixed;
37
+ inset: 0;
38
+ display: grid;
39
+ place-items: center;
40
+ padding: clamp(0.75rem, 1.4vw, 1.5rem);
41
+ background: rgba(0, 0, 0, 0.8);
42
+ backdrop-filter: blur(10px);
43
+ z-index: 2147483647;
44
+ font-family: var(--motiongpu-font-sans);
45
+ color-scheme: dark;
46
+ }
47
+
48
+ .motiongpu-error-dialog {
49
+ width: min(52rem, calc(100vw - 1.5rem));
50
+ max-height: min(84vh, 44rem);
51
+ overflow: auto;
52
+ margin: 0;
53
+ padding: 1.1rem;
54
+ border: 1px solid var(--motiongpu-color-border);
55
+ border-radius: var(--motiongpu-radius-xl);
56
+ max-width: calc(100vw - 1.5rem);
57
+ box-sizing: border-box;
58
+ font-size: 0.875rem;
59
+ font-weight: 400;
60
+ line-height: 1.45;
61
+ background: var(--motiongpu-color-card);
62
+ color: var(--motiongpu-color-foreground);
63
+ box-shadow: var(--motiongpu-shadow-card);
64
+ }
65
+
66
+ .motiongpu-error-header {
67
+ display: grid;
68
+ gap: 0.55rem;
69
+ padding-bottom: 0.9rem;
70
+ border-bottom: 1px solid var(--motiongpu-color-border);
71
+ }
72
+
73
+ .motiongpu-error-badge-wrap {
74
+ display: inline-flex;
75
+ align-items: center;
76
+ gap: 0.4rem;
77
+ width: fit-content;
78
+ padding: 0.18rem;
79
+ border-radius: 999px;
80
+ border: 1px solid var(--motiongpu-color-border);
81
+ background: var(--motiongpu-color-background-muted);
82
+ }
83
+
84
+ .motiongpu-error-phase {
85
+ display: inline-flex;
86
+ align-items: center;
87
+ margin: 0;
88
+ padding: 0.22rem 0.56rem;
89
+ border-radius: 999px;
90
+ font-size: 0.66rem;
91
+ letter-spacing: 0.08em;
92
+ line-height: 1;
93
+ font-weight: 500;
94
+ text-transform: uppercase;
95
+ color: var(--motiongpu-color-white-fixed);
96
+ background: linear-gradient(
97
+ 180deg,
98
+ var(--motiongpu-color-accent) 0%,
99
+ var(--motiongpu-color-accent-secondary) 100%
100
+ );
101
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.24);
102
+ }
103
+
104
+ .motiongpu-error-title {
105
+ margin: 0;
106
+ font-size: clamp(1.02rem, 1vw + 0.72rem, 1.32rem);
107
+ font-weight: 500;
108
+ line-height: 1.18;
109
+ letter-spacing: -0.02em;
110
+ text-wrap: balance;
111
+ color: var(--motiongpu-color-foreground);
112
+ }
113
+
114
+ .motiongpu-error-body {
115
+ display: grid;
116
+ gap: 0.62rem;
117
+ margin-top: 0.92rem;
118
+ }
119
+
120
+ .motiongpu-error-message {
121
+ margin: 0;
122
+ padding: 0.72rem 0.78rem;
123
+ border: 1px solid color-mix(in oklch, var(--motiongpu-color-accent) 28%, transparent);
124
+ border-radius: var(--motiongpu-radius-md);
125
+ background: color-mix(in oklch, var(--motiongpu-color-accent) 10%, transparent);
126
+ font-size: 0.82rem;
127
+ line-height: 1.4;
128
+ font-weight: 400;
129
+ color: var(--motiongpu-color-foreground);
130
+ }
131
+
132
+ .motiongpu-error-hint {
133
+ margin: 0;
134
+ font-size: 0.82rem;
135
+ line-height: 1.45;
136
+ font-weight: 400;
137
+ color: var(--motiongpu-color-foreground-muted);
138
+ }
139
+
140
+ .motiongpu-error-sections {
141
+ display: grid;
142
+ gap: 0.62rem;
143
+ margin-top: 0.95rem;
144
+ }
145
+
146
+ .motiongpu-error-source {
147
+ display: grid;
148
+ gap: 0.48rem;
149
+ margin-top: 0.96rem;
150
+ }
151
+
152
+ .motiongpu-error-source-title {
153
+ margin: 0;
154
+ font-size: 0.8rem;
155
+ font-weight: 500;
156
+ line-height: 1.3;
157
+ letter-spacing: 0.045em;
158
+ text-transform: uppercase;
159
+ color: var(--motiongpu-color-foreground);
160
+ }
161
+
162
+ .motiongpu-error-source-frame {
163
+ border: 1px solid var(--motiongpu-color-border);
164
+ border-radius: var(--motiongpu-radius-lg);
165
+ overflow: hidden;
166
+ background: var(--motiongpu-color-background-muted);
167
+ }
168
+
169
+ .motiongpu-error-source-tabs {
170
+ display: flex;
171
+ align-items: stretch;
172
+ border-bottom: 1px solid var(--motiongpu-color-border);
173
+ background: var(--motiongpu-color-background);
174
+ }
175
+
176
+ .motiongpu-error-source-tab {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ padding: 0.5rem 0.68rem;
180
+ font-size: 0.76rem;
181
+ font-weight: 400;
182
+ line-height: 1.2;
183
+ color: var(--motiongpu-color-foreground-muted);
184
+ border-right: 1px solid var(--motiongpu-color-border);
185
+ }
186
+
187
+ .motiongpu-error-source-tab-active {
188
+ color: var(--motiongpu-color-foreground);
189
+ background: var(--motiongpu-color-background-muted);
190
+ }
191
+
192
+ .motiongpu-error-source-tab-spacer {
193
+ flex: 1 1 auto;
194
+ }
195
+
196
+ .motiongpu-error-source-snippet {
197
+ display: grid;
198
+ background: var(--motiongpu-color-background-muted);
199
+ }
200
+
201
+ .motiongpu-error-source-row {
202
+ display: grid;
203
+ grid-template-columns: 2rem minmax(0, 1fr);
204
+ align-items: start;
205
+ gap: 0.42rem;
206
+ padding: 0.2rem 0.68rem;
207
+ }
208
+
209
+ .motiongpu-error-source-row-active {
210
+ background: color-mix(in oklch, var(--motiongpu-color-accent) 10%, transparent);
211
+ }
212
+
213
+ .motiongpu-error-source-line {
214
+ font-family: var(--motiongpu-font-mono);
215
+ font-size: 0.77rem;
216
+ font-weight: 400;
217
+ line-height: 1.3;
218
+ font-variant-numeric: tabular-nums;
219
+ font-feature-settings: 'tnum' 1;
220
+ border-right: 1px solid var(--motiongpu-color-border);
221
+ color: var(--motiongpu-color-foreground-muted);
222
+ text-align: left;
223
+ }
224
+
225
+ .motiongpu-error-source-code {
226
+ font-family: var(--motiongpu-font-mono);
227
+ font-size: 0.77rem;
228
+ font-weight: 400;
229
+ line-height: 1.3;
230
+ color: var(--motiongpu-color-foreground);
231
+ white-space: pre-wrap;
232
+ word-break: break-word;
233
+ }
234
+
235
+ .motiongpu-error-details {
236
+ border: 1px solid var(--motiongpu-color-border);
237
+ border-radius: var(--motiongpu-radius-lg);
238
+ overflow: hidden;
239
+ background: var(--motiongpu-color-background);
240
+ }
241
+
242
+ .motiongpu-error-details summary {
243
+ cursor: pointer;
244
+ padding: 0.56rem 0.68rem;
245
+ font-size: 0.7rem;
246
+ letter-spacing: 0.07em;
247
+ line-height: 1.2;
248
+ font-weight: 500;
249
+ text-transform: uppercase;
250
+ color: var(--motiongpu-color-foreground);
251
+ }
252
+
253
+ .motiongpu-error-details[open] summary {
254
+ border-bottom: 1px solid var(--motiongpu-color-border);
255
+ }
256
+
257
+ .motiongpu-error-details pre {
258
+ margin: 0;
259
+ padding: 0.62rem 0.68rem;
260
+ white-space: pre-wrap;
261
+ word-break: break-word;
262
+ overflow: auto;
263
+ background: var(--motiongpu-color-background-muted);
264
+ font-size: 0.74rem;
265
+ line-height: 1.4;
266
+ font-weight: 400;
267
+ color: var(--motiongpu-color-foreground);
268
+ font-family: var(--motiongpu-font-mono);
269
+ }
270
+
271
+ @media (max-width: 42rem) {
272
+ .motiongpu-error-overlay {
273
+ padding: 0.62rem;
274
+ }
275
+
276
+ .motiongpu-error-dialog {
277
+ padding: 0.85rem;
278
+ }
279
+
280
+ .motiongpu-error-title {
281
+ font-size: 1.02rem;
282
+ }
283
+ }
284
+
285
+ @media (prefers-reduced-motion: reduce) {
286
+ .motiongpu-error-overlay {
287
+ backdrop-filter: none;
288
+ }
289
+ }
290
+ `;
3
291
  function normalizeErrorText(value) {
4
292
  return value
5
293
  .trim()
@@ -11,42 +299,7 @@ function shouldShowErrorMessage(value) {
11
299
  }
12
300
  export function MotionGPUErrorOverlay({ report }) {
13
301
  const detailsSummary = report.source ? 'Additional diagnostics' : 'Technical details';
14
- return (_jsx(Portal, { children: _jsx("div", { className: "motiongpu-error-overlay", role: "presentation", style: {
15
- position: 'fixed',
16
- inset: 0,
17
- display: 'grid',
18
- placeItems: 'center',
19
- padding: '1rem',
20
- background: 'rgba(12, 12, 14, 0.38)',
21
- backdropFilter: 'blur(10px)',
22
- zIndex: 2147483647
23
- }, children: _jsxs("section", { role: "alertdialog", "aria-live": "assertive", "aria-modal": "true", "data-testid": "motiongpu-error", style: {
24
- width: 'min(52rem, calc(100vw - 1.5rem))',
25
- maxHeight: 'min(84vh, 44rem)',
26
- overflow: 'auto',
27
- margin: 0,
28
- padding: '1rem',
29
- border: '1px solid rgba(107, 107, 107, 0.2)',
30
- borderRadius: '1rem',
31
- boxSizing: 'border-box',
32
- background: '#ffffff',
33
- color: '#262626',
34
- fontSize: '0.875rem',
35
- lineHeight: 1.45
36
- }, children: [_jsxs("header", { style: { display: 'grid', gap: '0.5rem' }, children: [_jsx("p", { style: {
37
- margin: 0,
38
- fontSize: '0.66rem',
39
- letterSpacing: '0.08em',
40
- textTransform: 'uppercase',
41
- color: '#5f6672'
42
- }, children: report.phase }), _jsx("h2", { style: { margin: 0, fontSize: '1.1rem', lineHeight: 1.2 }, children: report.title })] }), _jsxs("div", { style: { display: 'grid', gap: '0.5rem', marginTop: '0.75rem' }, children: [shouldShowErrorMessage(report) ? _jsx("p", { style: { margin: 0 }, children: report.message }) : null, _jsx("p", { style: { margin: 0, color: '#5f6672' }, children: report.hint })] }), report.source ? (_jsxs("section", { "aria-label": "Source", style: { marginTop: '0.75rem' }, children: [_jsx("h3", { style: { margin: 0, fontSize: '0.8rem', textTransform: 'uppercase' }, children: "Source" }), _jsxs("div", { style: { marginTop: '0.4rem', border: '1px solid rgba(107, 107, 107, 0.2)' }, children: [_jsx("div", { style: {
43
- padding: '0.45rem 0.6rem',
44
- borderBottom: '1px solid rgba(107, 107, 107, 0.2)'
45
- }, children: _jsxs("span", { children: [report.source.location, report.source.column ? `, col ${report.source.column}` : ''] }) }), _jsx("div", { style: { display: 'grid' }, children: report.source.snippet.map((snippetLine) => (_jsxs("div", { style: {
46
- display: 'grid',
47
- gridTemplateColumns: '2rem minmax(0, 1fr)',
48
- gap: '0.42rem',
49
- padding: '0.2rem 0.5rem',
50
- background: snippetLine.highlight ? 'rgba(255, 105, 0, 0.08)' : undefined
51
- }, children: [_jsx("span", { children: snippetLine.number }), _jsx("span", { className: "motiongpu-error-source-code", children: snippetLine.code || ' ' })] }, `snippet-${snippetLine.number}`))) })] })] })) : null, report.details.length > 0 ? (_jsxs("details", { open: true, style: { marginTop: '0.75rem' }, children: [_jsx("summary", { children: detailsSummary }), _jsx("pre", { style: { whiteSpace: 'pre-wrap' }, children: report.details.join('\n') })] })) : null, report.stack.length > 0 ? (_jsxs("details", { style: { marginTop: '0.75rem' }, children: [_jsx("summary", { children: "Stack trace" }), _jsx("pre", { style: { whiteSpace: 'pre-wrap' }, children: report.stack.join('\n') })] })) : null] }) }) }));
302
+ return (_jsxs(Portal, { children: [_jsx("style", { children: MOTIONGPU_ERROR_OVERLAY_STYLES }), _jsx("div", { className: "motiongpu-error-overlay", role: "presentation", children: _jsxs("section", { className: "motiongpu-error-dialog", role: "alertdialog", "aria-live": "assertive", "aria-modal": "true", "data-testid": "motiongpu-error", children: [_jsxs("header", { className: "motiongpu-error-header", children: [_jsx("div", { className: "motiongpu-error-badge-wrap", children: _jsx("p", { className: "motiongpu-error-phase", children: report.phase }) }), _jsx("h2", { className: "motiongpu-error-title", children: report.title })] }), _jsxs("div", { className: "motiongpu-error-body", children: [shouldShowErrorMessage(report) ? (_jsx("p", { className: "motiongpu-error-message", children: report.message })) : null, _jsx("p", { className: "motiongpu-error-hint", children: report.hint })] }), report.source ? (_jsxs("section", { className: "motiongpu-error-source", "aria-label": "Source", children: [_jsx("h3", { className: "motiongpu-error-source-title", children: "Source" }), _jsxs("div", { className: "motiongpu-error-source-frame", role: "presentation", children: [_jsxs("div", { className: "motiongpu-error-source-tabs", role: "tablist", "aria-label": "Source files", children: [_jsxs("span", { className: "motiongpu-error-source-tab motiongpu-error-source-tab-active", role: "tab", "aria-selected": "true", children: [report.source.location, report.source.column ? `, col ${report.source.column}` : ''] }), _jsx("span", { className: "motiongpu-error-source-tab-spacer", "aria-hidden": "true" })] }), _jsx("div", { className: "motiongpu-error-source-snippet", children: report.source.snippet.map((snippetLine) => (_jsxs("div", { className: snippetLine.highlight
303
+ ? 'motiongpu-error-source-row motiongpu-error-source-row-active'
304
+ : 'motiongpu-error-source-row', children: [_jsx("span", { className: "motiongpu-error-source-line", children: snippetLine.number }), _jsx("span", { className: "motiongpu-error-source-code", children: snippetLine.code || ' ' })] }, `snippet-${snippetLine.number}`))) })] })] })) : null, _jsxs("div", { className: "motiongpu-error-sections", children: [report.details.length > 0 ? (_jsxs("details", { className: "motiongpu-error-details", open: true, children: [_jsx("summary", { children: detailsSummary }), _jsx("pre", { children: report.details.join('\n') })] })) : null, report.stack.length > 0 ? (_jsxs("details", { className: "motiongpu-error-details", children: [_jsx("summary", { children: "Stack trace" }), _jsx("pre", { children: report.stack.join('\n') })] })) : null] })] }) })] }));
52
305
  }
@@ -1,3 +1,4 @@
1
+ /// <reference types="@webgpu/types" />
1
2
  import type { Snippet } from 'svelte';
2
3
  import type { FragMaterial } from '../core/material';
3
4
  import { type MotionGPUErrorReport } from '../core/error-report';
@@ -94,14 +94,16 @@
94
94
 
95
95
  <style>
96
96
  .motiongpu-error-overlay {
97
- --motiongpu-color-background: var(--color-background, #ffffff);
98
- --motiongpu-color-background-muted: var(--color-background-inset, #f6f6f7);
99
- --motiongpu-color-foreground: var(--color-foreground, #262626);
100
- --motiongpu-color-foreground-muted: var(--color-foreground-muted, rgba(38, 38, 38, 0.64));
101
- --motiongpu-color-card: var(--color-background, #ffffff);
102
- --motiongpu-color-accent: var(--color-accent, #ff6900);
103
- --motiongpu-color-accent-secondary: var(--color-accent-secondary, #bd4d00);
104
- --motiongpu-color-border: var(--color-border, rgba(107, 107, 107, 0.2));
97
+ --motiongpu-base-hue: var(--base-hue, 265);
98
+ --motiongpu-color-background: oklch(0.2178 0.0056 var(--motiongpu-base-hue));
99
+ --motiongpu-color-background-muted: oklch(0.261 0.007 var(--motiongpu-base-hue));
100
+ --motiongpu-color-foreground: oklch(1 0 0);
101
+ --motiongpu-color-foreground-muted: oklch(0.6699 0.0081 var(--motiongpu-base-hue));
102
+ --motiongpu-color-card: var(--motiongpu-color-background);
103
+ --motiongpu-color-accent: oklch(0.6996 0.181959 44.4414);
104
+ --motiongpu-color-accent-secondary: oklch(0.5096 0.131959 44.4414);
105
+ --motiongpu-color-border: oklch(0.928 0.013 var(--motiongpu-base-hue) / 0.05);
106
+ --motiongpu-color-white-fixed: oklch(1 0 0);
105
107
  --motiongpu-shadow-card: var(
106
108
  --shadow-2xl,
107
109
  0px 1px 1px -0.5px rgba(0, 0, 0, 0.06),
@@ -128,12 +130,11 @@
128
130
  display: grid;
129
131
  place-items: center;
130
132
  padding: clamp(0.75rem, 1.4vw, 1.5rem);
131
- background:
132
- radial-gradient(125% 125% at 50% 0%, rgba(255, 105, 0, 0.12) 0%, rgba(255, 105, 0, 0) 56%),
133
- rgba(12, 12, 14, 0.38);
133
+ background: rgba(0, 0, 0, 0.8);
134
134
  backdrop-filter: blur(10px);
135
135
  z-index: 2147483647;
136
136
  font-family: var(--motiongpu-font-sans);
137
+ color-scheme: dark;
137
138
  }
138
139
 
139
140
  .motiongpu-error-dialog {
@@ -149,11 +150,7 @@
149
150
  font-size: 0.875rem;
150
151
  font-weight: 400;
151
152
  line-height: 1.45;
152
- background: linear-gradient(
153
- 180deg,
154
- var(--motiongpu-color-card) 0%,
155
- var(--motiongpu-color-background-muted) 100%
156
- );
153
+ background: var(--motiongpu-color-card);
157
154
  color: var(--motiongpu-color-foreground);
158
155
  box-shadow: var(--motiongpu-shadow-card);
159
156
  }
@@ -187,7 +184,7 @@
187
184
  line-height: 1;
188
185
  font-weight: 500;
189
186
  text-transform: uppercase;
190
- color: var(--motiongpu-color-background);
187
+ color: var(--motiongpu-color-white-fixed);
191
188
  background: linear-gradient(
192
189
  180deg,
193
190
  var(--motiongpu-color-accent) 0%,
@@ -215,9 +212,9 @@
215
212
  .motiongpu-error-message {
216
213
  margin: 0;
217
214
  padding: 0.72rem 0.78rem;
218
- border: 1px solid color-mix(in srgb, var(--motiongpu-color-accent) 28%, transparent);
215
+ border: 1px solid color-mix(in oklch, var(--motiongpu-color-accent) 28%, transparent);
219
216
  border-radius: var(--motiongpu-radius-md);
220
- background: color-mix(in srgb, var(--motiongpu-color-accent) 9%, var(--motiongpu-color-card));
217
+ background: color-mix(in oklch, var(--motiongpu-color-accent) 10%, transparent);
221
218
  font-size: 0.82rem;
222
219
  line-height: 1.4;
223
220
  font-weight: 400;
@@ -302,7 +299,7 @@
302
299
  }
303
300
 
304
301
  .motiongpu-error-source-row-active {
305
- background: color-mix(in srgb, var(--motiongpu-color-accent) 10%, transparent);
302
+ background: color-mix(in oklch, var(--motiongpu-color-accent) 10%, transparent);
306
303
  }
307
304
 
308
305
  .motiongpu-error-source-line {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-core/motion-gpu",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Framework-agnostic WebGPU runtime for fullscreen WGSL shaders with explicit Svelte and React adapter entrypoints.",
5
5
  "keywords": [
6
6
  "svelte",
@@ -69,7 +69,8 @@
69
69
  "dist"
70
70
  ],
71
71
  "scripts": {
72
- "build": "svelte-package -i src/lib -o dist && node ./scripts/build/transpile-react-tsx.mjs",
72
+ "build": "svelte-package -i src/lib -o dist && node ./scripts/build/transpile-react-tsx.mjs && node ./scripts/build/patch-webgpu-types-dts.mjs",
73
+ "build:watch": "svelte-package -i src/lib -o dist -w --preserve-output",
73
74
  "prepack": "bun run build",
74
75
  "check": "svelte-check --tsconfig ./tsconfig.json && publint",
75
76
  "test": "vitest run",
@@ -105,6 +106,9 @@
105
106
  "optional": true
106
107
  }
107
108
  },
109
+ "dependencies": {
110
+ "@webgpu/types": "^0.1.66"
111
+ },
108
112
  "devDependencies": {
109
113
  "@eslint/compat": "^2.0.2",
110
114
  "@eslint/js": "^10.0.1",
@@ -117,7 +121,6 @@
117
121
  "@types/react-dom": "^19.2.2",
118
122
  "@vitest/coverage-v8": "^4.0.18",
119
123
  "@types/node": "^25.3.3",
120
- "@webgpu/types": "^0.1.66",
121
124
  "eslint": "^10.0.2",
122
125
  "eslint-config-prettier": "^10.1.8",
123
126
  "eslint-plugin-svelte": "^3.14.0",
@@ -1,345 +0,0 @@
1
- import { createCurrentWritable as currentWritable } from '../core/current-value.js';
2
- import { toMotionGPUErrorReport, type MotionGPUErrorReport } from '../core/error-report.js';
3
- import type { FragMaterial } from '../core/material.js';
4
- import { createFrameRegistry } from '../core/frame-registry.js';
5
- import { createMotionGPURuntimeLoop } from '../core/runtime-loop.js';
6
- import type {
7
- OutputColorSpace,
8
- RenderPass,
9
- RenderMode,
10
- RenderTargetDefinitionMap
11
- } from '../core/types.js';
12
- import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';
13
- import { FrameRegistryReactContext } from './frame-context.js';
14
- import { MotionGPUErrorOverlay } from './MotionGPUErrorOverlay.js';
15
- import { MotionGPUReactContext, type MotionGPUContext } from './motiongpu-context.js';
16
-
17
- export interface FragCanvasProps {
18
- material: FragMaterial;
19
- renderTargets?: RenderTargetDefinitionMap;
20
- passes?: RenderPass[];
21
- clearColor?: [number, number, number, number];
22
- outputColorSpace?: OutputColorSpace;
23
- renderMode?: RenderMode;
24
- autoRender?: boolean;
25
- maxDelta?: number;
26
- adapterOptions?: GPURequestAdapterOptions;
27
- deviceDescriptor?: GPUDeviceDescriptor;
28
- dpr?: number;
29
- showErrorOverlay?: boolean;
30
- errorRenderer?: (report: MotionGPUErrorReport) => ReactNode;
31
- onError?: (report: MotionGPUErrorReport) => void;
32
- errorHistoryLimit?: number;
33
- onErrorHistory?: (history: MotionGPUErrorReport[]) => void;
34
- className?: string;
35
- style?: CSSProperties;
36
- children?: ReactNode;
37
- }
38
-
39
- interface RuntimePropsSnapshot {
40
- material: FragMaterial;
41
- renderTargets: RenderTargetDefinitionMap;
42
- passes: RenderPass[];
43
- clearColor: [number, number, number, number];
44
- outputColorSpace: OutputColorSpace;
45
- adapterOptions: GPURequestAdapterOptions | undefined;
46
- deviceDescriptor: GPUDeviceDescriptor | undefined;
47
- onError: ((report: MotionGPUErrorReport) => void) | undefined;
48
- errorHistoryLimit: number;
49
- onErrorHistory: ((history: MotionGPUErrorReport[]) => void) | undefined;
50
- }
51
-
52
- interface FragCanvasRuntimeState {
53
- registry: ReturnType<typeof createFrameRegistry>;
54
- context: MotionGPUContext;
55
- canvasRef: { current: HTMLCanvasElement | undefined };
56
- size: ReturnType<typeof currentWritable<{ width: number; height: number }>>;
57
- dprState: ReturnType<typeof currentWritable<number>>;
58
- maxDeltaState: ReturnType<typeof currentWritable<number>>;
59
- renderModeState: ReturnType<typeof currentWritable<RenderMode>>;
60
- autoRenderState: ReturnType<typeof currentWritable<boolean>>;
61
- requestFrameSignalRef: { current: (() => void) | null };
62
- requestFrame: () => void;
63
- invalidateFrame: () => void;
64
- advanceFrame: () => void;
65
- }
66
-
67
- function getInitialDpr(): number {
68
- if (typeof window === 'undefined') {
69
- return 1;
70
- }
71
-
72
- return window.devicePixelRatio ?? 1;
73
- }
74
-
75
- function createRuntimeState(initialDpr: number): FragCanvasRuntimeState {
76
- const registry = createFrameRegistry({ maxDelta: 0.1 });
77
- const canvasRef = { current: undefined as HTMLCanvasElement | undefined };
78
- const requestFrameSignalRef = { current: null as (() => void) | null };
79
- const requestFrame = (): void => {
80
- requestFrameSignalRef.current?.();
81
- };
82
- const invalidateFrame = (): void => {
83
- registry.invalidate();
84
- requestFrame();
85
- };
86
- const advanceFrame = (): void => {
87
- registry.advance();
88
- requestFrame();
89
- };
90
-
91
- const size = currentWritable({ width: 0, height: 0 });
92
- const dprState = currentWritable(initialDpr, requestFrame);
93
- const maxDeltaState = currentWritable(0.1, (value) => {
94
- registry.setMaxDelta(value);
95
- requestFrame();
96
- });
97
- const renderModeState = currentWritable<RenderMode>('always', (value) => {
98
- registry.setRenderMode(value);
99
- requestFrame();
100
- });
101
- const autoRenderState = currentWritable<boolean>(true, (value) => {
102
- registry.setAutoRender(value);
103
- requestFrame();
104
- });
105
- const userState = currentWritable<Record<string | symbol, unknown>>({});
106
-
107
- const context: MotionGPUContext = {
108
- get canvas() {
109
- return canvasRef.current;
110
- },
111
- size,
112
- dpr: dprState,
113
- maxDelta: maxDeltaState,
114
- renderMode: renderModeState,
115
- autoRender: autoRenderState,
116
- user: userState,
117
- invalidate: invalidateFrame,
118
- advance: advanceFrame,
119
- scheduler: {
120
- createStage: registry.createStage,
121
- getStage: registry.getStage,
122
- setDiagnosticsEnabled: registry.setDiagnosticsEnabled,
123
- getDiagnosticsEnabled: registry.getDiagnosticsEnabled,
124
- getLastRunTimings: registry.getLastRunTimings,
125
- getSchedule: registry.getSchedule,
126
- setProfilingEnabled: registry.setProfilingEnabled,
127
- setProfilingWindow: registry.setProfilingWindow,
128
- resetProfiling: registry.resetProfiling,
129
- getProfilingEnabled: registry.getProfilingEnabled,
130
- getProfilingWindow: registry.getProfilingWindow,
131
- getProfilingSnapshot: registry.getProfilingSnapshot
132
- }
133
- };
134
-
135
- return {
136
- registry,
137
- context,
138
- canvasRef,
139
- size,
140
- dprState,
141
- maxDeltaState,
142
- renderModeState,
143
- autoRenderState,
144
- requestFrameSignalRef,
145
- requestFrame,
146
- invalidateFrame,
147
- advanceFrame
148
- };
149
- }
150
-
151
- function getNormalizedErrorHistoryLimit(value: number): number {
152
- if (!Number.isFinite(value) || value <= 0) {
153
- return 0;
154
- }
155
-
156
- return Math.floor(value);
157
- }
158
-
159
- export function FragCanvas({
160
- material,
161
- renderTargets = {},
162
- passes = [],
163
- clearColor = [0, 0, 0, 1],
164
- outputColorSpace = 'srgb',
165
- renderMode = 'always',
166
- autoRender = true,
167
- maxDelta = 0.1,
168
- adapterOptions = undefined,
169
- deviceDescriptor = undefined,
170
- dpr = getInitialDpr(),
171
- showErrorOverlay = true,
172
- errorRenderer,
173
- onError = undefined,
174
- errorHistoryLimit = 0,
175
- onErrorHistory = undefined,
176
- className = '',
177
- style,
178
- children
179
- }: FragCanvasProps) {
180
- const runtimeRef = useRef<FragCanvasRuntimeState | null>(null);
181
- if (!runtimeRef.current) {
182
- runtimeRef.current = createRuntimeState(getInitialDpr());
183
- }
184
- const runtime = runtimeRef.current;
185
-
186
- const runtimePropsRef = useRef<RuntimePropsSnapshot>({
187
- material,
188
- renderTargets,
189
- passes,
190
- clearColor,
191
- outputColorSpace,
192
- adapterOptions,
193
- deviceDescriptor,
194
- onError,
195
- errorHistoryLimit,
196
- onErrorHistory
197
- });
198
- runtimePropsRef.current = {
199
- material,
200
- renderTargets,
201
- passes,
202
- clearColor,
203
- outputColorSpace,
204
- adapterOptions,
205
- deviceDescriptor,
206
- onError,
207
- errorHistoryLimit,
208
- onErrorHistory
209
- };
210
-
211
- const [errorReport, setErrorReport] = useState<MotionGPUErrorReport | null>(null);
212
- const [errorHistory, setErrorHistory] = useState<MotionGPUErrorReport[]>([]);
213
-
214
- useEffect(() => {
215
- runtime.renderModeState.set(renderMode);
216
- }, [renderMode, runtime]);
217
-
218
- useEffect(() => {
219
- runtime.autoRenderState.set(autoRender);
220
- }, [autoRender, runtime]);
221
-
222
- useEffect(() => {
223
- runtime.maxDeltaState.set(maxDelta);
224
- }, [maxDelta, runtime]);
225
-
226
- useEffect(() => {
227
- runtime.dprState.set(dpr);
228
- }, [dpr, runtime]);
229
-
230
- useEffect(() => {
231
- const limit = getNormalizedErrorHistoryLimit(errorHistoryLimit);
232
- if (limit <= 0) {
233
- if (errorHistory.length === 0) {
234
- return;
235
- }
236
- setErrorHistory([]);
237
- onErrorHistory?.([]);
238
- return;
239
- }
240
-
241
- if (errorHistory.length <= limit) {
242
- return;
243
- }
244
-
245
- const trimmed = errorHistory.slice(errorHistory.length - limit);
246
- setErrorHistory(trimmed);
247
- onErrorHistory?.(trimmed);
248
- }, [errorHistory, errorHistoryLimit, onErrorHistory]);
249
-
250
- useEffect(() => {
251
- const canvas = runtime.canvasRef.current;
252
- if (!canvas) {
253
- const report = toMotionGPUErrorReport(
254
- new Error('Canvas element is not available'),
255
- 'initialization'
256
- );
257
- setErrorReport(report);
258
- const historyLimit = getNormalizedErrorHistoryLimit(
259
- runtimePropsRef.current.errorHistoryLimit
260
- );
261
- if (historyLimit > 0) {
262
- const nextHistory = [report].slice(-historyLimit);
263
- setErrorHistory(nextHistory);
264
- runtimePropsRef.current.onErrorHistory?.(nextHistory);
265
- }
266
- runtimePropsRef.current.onError?.(report);
267
- return () => {
268
- runtime.registry.clear();
269
- };
270
- }
271
-
272
- const runtimeLoop = createMotionGPURuntimeLoop({
273
- canvas,
274
- registry: runtime.registry,
275
- size: runtime.size,
276
- dpr: runtime.dprState,
277
- maxDelta: runtime.maxDeltaState,
278
- getMaterial: () => runtimePropsRef.current.material,
279
- getRenderTargets: () => runtimePropsRef.current.renderTargets,
280
- getPasses: () => runtimePropsRef.current.passes,
281
- getClearColor: () => runtimePropsRef.current.clearColor,
282
- getOutputColorSpace: () => runtimePropsRef.current.outputColorSpace,
283
- getAdapterOptions: () => runtimePropsRef.current.adapterOptions,
284
- getDeviceDescriptor: () => runtimePropsRef.current.deviceDescriptor,
285
- getOnError: () => runtimePropsRef.current.onError,
286
- getErrorHistoryLimit: () => runtimePropsRef.current.errorHistoryLimit,
287
- getOnErrorHistory: () => runtimePropsRef.current.onErrorHistory,
288
- reportErrorHistory: (history) => {
289
- setErrorHistory(history);
290
- },
291
- reportError: (report) => {
292
- setErrorReport(report);
293
- }
294
- });
295
- runtime.requestFrameSignalRef.current = runtimeLoop.requestFrame;
296
-
297
- return () => {
298
- runtime.requestFrameSignalRef.current = null;
299
- runtimeLoop.destroy();
300
- };
301
- }, [runtime]);
302
-
303
- const canvasStyle: CSSProperties = {
304
- position: 'absolute',
305
- inset: 0,
306
- display: 'block',
307
- width: '100%',
308
- height: '100%',
309
- ...style
310
- };
311
-
312
- return (
313
- <FrameRegistryReactContext.Provider value={runtime.registry}>
314
- <MotionGPUReactContext.Provider value={runtime.context}>
315
- <div
316
- className="motiongpu-canvas-wrap"
317
- style={{
318
- position: 'relative',
319
- width: '100%',
320
- height: '100%',
321
- minWidth: 0,
322
- minHeight: 0,
323
- overflow: 'hidden'
324
- }}
325
- >
326
- <canvas
327
- className={className}
328
- style={canvasStyle}
329
- ref={(node) => {
330
- runtime.canvasRef.current = node ?? undefined;
331
- }}
332
- />
333
- {showErrorOverlay && errorReport ? (
334
- errorRenderer ? (
335
- errorRenderer(errorReport)
336
- ) : (
337
- <MotionGPUErrorOverlay report={errorReport} />
338
- )
339
- ) : null}
340
- {children}
341
- </div>
342
- </MotionGPUReactContext.Provider>
343
- </FrameRegistryReactContext.Provider>
344
- );
345
- }
@@ -1,129 +0,0 @@
1
- import type { MotionGPUErrorReport } from '../core/error-report.js';
2
- import { Portal } from './Portal.js';
3
-
4
- interface MotionGPUErrorOverlayProps {
5
- report: MotionGPUErrorReport;
6
- }
7
-
8
- function normalizeErrorText(value: string): string {
9
- return value
10
- .trim()
11
- .replace(/[.:!]+$/g, '')
12
- .toLowerCase();
13
- }
14
-
15
- function shouldShowErrorMessage(value: MotionGPUErrorReport): boolean {
16
- return normalizeErrorText(value.message) !== normalizeErrorText(value.title);
17
- }
18
-
19
- export function MotionGPUErrorOverlay({ report }: MotionGPUErrorOverlayProps) {
20
- const detailsSummary = report.source ? 'Additional diagnostics' : 'Technical details';
21
-
22
- return (
23
- <Portal>
24
- <div
25
- className="motiongpu-error-overlay"
26
- role="presentation"
27
- style={{
28
- position: 'fixed',
29
- inset: 0,
30
- display: 'grid',
31
- placeItems: 'center',
32
- padding: '1rem',
33
- background: 'rgba(12, 12, 14, 0.38)',
34
- backdropFilter: 'blur(10px)',
35
- zIndex: 2147483647
36
- }}
37
- >
38
- <section
39
- role="alertdialog"
40
- aria-live="assertive"
41
- aria-modal="true"
42
- data-testid="motiongpu-error"
43
- style={{
44
- width: 'min(52rem, calc(100vw - 1.5rem))',
45
- maxHeight: 'min(84vh, 44rem)',
46
- overflow: 'auto',
47
- margin: 0,
48
- padding: '1rem',
49
- border: '1px solid rgba(107, 107, 107, 0.2)',
50
- borderRadius: '1rem',
51
- boxSizing: 'border-box',
52
- background: '#ffffff',
53
- color: '#262626',
54
- fontSize: '0.875rem',
55
- lineHeight: 1.45
56
- }}
57
- >
58
- <header style={{ display: 'grid', gap: '0.5rem' }}>
59
- <p
60
- style={{
61
- margin: 0,
62
- fontSize: '0.66rem',
63
- letterSpacing: '0.08em',
64
- textTransform: 'uppercase',
65
- color: '#5f6672'
66
- }}
67
- >
68
- {report.phase}
69
- </p>
70
- <h2 style={{ margin: 0, fontSize: '1.1rem', lineHeight: 1.2 }}>{report.title}</h2>
71
- </header>
72
- <div style={{ display: 'grid', gap: '0.5rem', marginTop: '0.75rem' }}>
73
- {shouldShowErrorMessage(report) ? <p style={{ margin: 0 }}>{report.message}</p> : null}
74
- <p style={{ margin: 0, color: '#5f6672' }}>{report.hint}</p>
75
- </div>
76
-
77
- {report.source ? (
78
- <section aria-label="Source" style={{ marginTop: '0.75rem' }}>
79
- <h3 style={{ margin: 0, fontSize: '0.8rem', textTransform: 'uppercase' }}>Source</h3>
80
- <div style={{ marginTop: '0.4rem', border: '1px solid rgba(107, 107, 107, 0.2)' }}>
81
- <div
82
- style={{
83
- padding: '0.45rem 0.6rem',
84
- borderBottom: '1px solid rgba(107, 107, 107, 0.2)'
85
- }}
86
- >
87
- <span>
88
- {report.source.location}
89
- {report.source.column ? `, col ${report.source.column}` : ''}
90
- </span>
91
- </div>
92
- <div style={{ display: 'grid' }}>
93
- {report.source.snippet.map((snippetLine) => (
94
- <div
95
- key={`snippet-${snippetLine.number}`}
96
- style={{
97
- display: 'grid',
98
- gridTemplateColumns: '2rem minmax(0, 1fr)',
99
- gap: '0.42rem',
100
- padding: '0.2rem 0.5rem',
101
- background: snippetLine.highlight ? 'rgba(255, 105, 0, 0.08)' : undefined
102
- }}
103
- >
104
- <span>{snippetLine.number}</span>
105
- <span className="motiongpu-error-source-code">{snippetLine.code || ' '}</span>
106
- </div>
107
- ))}
108
- </div>
109
- </div>
110
- </section>
111
- ) : null}
112
-
113
- {report.details.length > 0 ? (
114
- <details open style={{ marginTop: '0.75rem' }}>
115
- <summary>{detailsSummary}</summary>
116
- <pre style={{ whiteSpace: 'pre-wrap' }}>{report.details.join('\n')}</pre>
117
- </details>
118
- ) : null}
119
- {report.stack.length > 0 ? (
120
- <details style={{ marginTop: '0.75rem' }}>
121
- <summary>Stack trace</summary>
122
- <pre style={{ whiteSpace: 'pre-wrap' }}>{report.stack.join('\n')}</pre>
123
- </details>
124
- ) : null}
125
- </section>
126
- </div>
127
- </Portal>
128
- );
129
- }
@@ -1,34 +0,0 @@
1
- import { useEffect, useState, type ReactNode } from 'react';
2
- import { createPortal } from 'react-dom';
3
-
4
- export interface PortalProps {
5
- target?: string | HTMLElement | null;
6
- children?: ReactNode;
7
- }
8
-
9
- function resolveTargetElement(input: string | HTMLElement | null | undefined): HTMLElement {
10
- if (typeof document === 'undefined') {
11
- throw new Error('Portal target resolution requires a browser environment');
12
- }
13
-
14
- return typeof input === 'string'
15
- ? (document.querySelector<HTMLElement>(input) ?? document.body)
16
- : (input ?? document.body);
17
- }
18
-
19
- export function Portal({ target = 'body', children }: PortalProps) {
20
- const [targetElement, setTargetElement] = useState<HTMLElement | null>(null);
21
-
22
- useEffect(() => {
23
- if (typeof document === 'undefined') {
24
- return;
25
- }
26
- setTargetElement(resolveTargetElement(target));
27
- }, [target]);
28
-
29
- if (!targetElement) {
30
- return null;
31
- }
32
-
33
- return createPortal(<div>{children ?? null}</div>, targetElement);
34
- }