@motion-core/motion-gpu 0.4.1 → 0.5.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 +99 -0
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +14 -6
- package/dist/core/advanced.d.ts +1 -0
- package/dist/core/advanced.d.ts.map +1 -0
- package/dist/core/advanced.js +14 -5
- package/dist/core/compute-shader.d.ts +87 -0
- package/dist/core/compute-shader.d.ts.map +1 -0
- package/dist/core/compute-shader.js +205 -0
- package/dist/core/compute-shader.js.map +1 -0
- package/dist/core/current-value.d.ts +1 -0
- package/dist/core/current-value.d.ts.map +1 -0
- package/dist/core/current-value.js +35 -34
- package/dist/core/current-value.js.map +1 -0
- package/dist/core/error-diagnostics.d.ts +1 -0
- package/dist/core/error-diagnostics.d.ts.map +1 -0
- package/dist/core/error-diagnostics.js +70 -137
- package/dist/core/error-diagnostics.js.map +1 -0
- package/dist/core/error-report.d.ts +2 -1
- package/dist/core/error-report.d.ts.map +1 -0
- package/dist/core/error-report.js +247 -233
- package/dist/core/error-report.js.map +1 -0
- package/dist/core/frame-registry.d.ts +1 -0
- package/dist/core/frame-registry.d.ts.map +1 -0
- package/dist/core/frame-registry.js +546 -662
- package/dist/core/frame-registry.js.map +1 -0
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -12
- package/dist/core/material-preprocess.d.ts +1 -0
- package/dist/core/material-preprocess.d.ts.map +1 -0
- package/dist/core/material-preprocess.js +131 -152
- package/dist/core/material-preprocess.js.map +1 -0
- package/dist/core/material.d.ts +23 -6
- package/dist/core/material.d.ts.map +1 -0
- package/dist/core/material.js +290 -317
- package/dist/core/material.js.map +1 -0
- package/dist/core/recompile-policy.d.ts +1 -0
- package/dist/core/recompile-policy.d.ts.map +1 -0
- package/dist/core/recompile-policy.js +18 -13
- package/dist/core/recompile-policy.js.map +1 -0
- package/dist/core/render-graph.d.ts +8 -3
- package/dist/core/render-graph.d.ts.map +1 -0
- package/dist/core/render-graph.js +77 -68
- package/dist/core/render-graph.js.map +1 -0
- package/dist/core/render-targets.d.ts +1 -0
- package/dist/core/render-targets.d.ts.map +1 -0
- package/dist/core/render-targets.js +52 -53
- package/dist/core/render-targets.js.map +1 -0
- package/dist/core/renderer.d.ts +1 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/renderer.js +1337 -1081
- package/dist/core/renderer.js.map +1 -0
- package/dist/core/runtime-loop.d.ts +3 -2
- package/dist/core/runtime-loop.d.ts.map +1 -0
- package/dist/core/runtime-loop.js +353 -362
- package/dist/core/runtime-loop.js.map +1 -0
- package/dist/core/scheduler-helpers.d.ts +1 -0
- package/dist/core/scheduler-helpers.d.ts.map +1 -0
- package/dist/core/scheduler-helpers.js +52 -51
- package/dist/core/scheduler-helpers.js.map +1 -0
- package/dist/core/shader.d.ts +10 -1
- package/dist/core/shader.d.ts.map +1 -0
- package/dist/core/shader.js +109 -115
- package/dist/core/shader.js.map +1 -0
- package/dist/core/storage-buffers.d.ts +37 -0
- package/dist/core/storage-buffers.d.ts.map +1 -0
- package/dist/core/storage-buffers.js +95 -0
- package/dist/core/storage-buffers.js.map +1 -0
- package/dist/core/texture-loader.d.ts +1 -0
- package/dist/core/texture-loader.d.ts.map +1 -0
- package/dist/core/texture-loader.js +209 -273
- package/dist/core/texture-loader.js.map +1 -0
- package/dist/core/textures.d.ts +13 -0
- package/dist/core/textures.d.ts.map +1 -0
- package/dist/core/textures.js +111 -116
- package/dist/core/textures.js.map +1 -0
- package/dist/core/types.d.ts +147 -4
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +0 -4
- package/dist/core/uniforms.d.ts +1 -0
- package/dist/core/uniforms.d.ts.map +1 -0
- package/dist/core/uniforms.js +170 -191
- package/dist/core/uniforms.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -6
- package/dist/passes/BlitPass.d.ts +1 -0
- package/dist/passes/BlitPass.d.ts.map +1 -0
- package/dist/passes/BlitPass.js +23 -18
- package/dist/passes/BlitPass.js.map +1 -0
- package/dist/passes/ComputePass.d.ts +83 -0
- package/dist/passes/ComputePass.d.ts.map +1 -0
- package/dist/passes/ComputePass.js +92 -0
- package/dist/passes/ComputePass.js.map +1 -0
- package/dist/passes/CopyPass.d.ts +1 -0
- package/dist/passes/CopyPass.d.ts.map +1 -0
- package/dist/passes/CopyPass.js +58 -52
- package/dist/passes/CopyPass.js.map +1 -0
- package/dist/passes/FullscreenPass.d.ts +1 -0
- package/dist/passes/FullscreenPass.d.ts.map +1 -0
- package/dist/passes/FullscreenPass.js +127 -130
- package/dist/passes/FullscreenPass.js.map +1 -0
- package/dist/passes/PingPongComputePass.d.ts +104 -0
- package/dist/passes/PingPongComputePass.d.ts.map +1 -0
- package/dist/passes/PingPongComputePass.js +132 -0
- package/dist/passes/PingPongComputePass.js.map +1 -0
- package/dist/passes/ShaderPass.d.ts +1 -0
- package/dist/passes/ShaderPass.d.ts.map +1 -0
- package/dist/passes/ShaderPass.js +41 -37
- package/dist/passes/ShaderPass.js.map +1 -0
- package/dist/passes/index.d.ts +3 -0
- package/dist/passes/index.d.ts.map +1 -0
- package/dist/passes/index.js +6 -3
- package/dist/react/FragCanvas.d.ts +3 -2
- package/dist/react/FragCanvas.d.ts.map +1 -0
- package/dist/react/FragCanvas.js +234 -211
- package/dist/react/FragCanvas.js.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.js +200 -14
- package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
- package/dist/react/Portal.d.ts +1 -0
- package/dist/react/Portal.d.ts.map +1 -0
- package/dist/react/Portal.js +18 -21
- package/dist/react/Portal.js.map +1 -0
- package/dist/react/advanced.d.ts +1 -0
- package/dist/react/advanced.d.ts.map +1 -0
- package/dist/react/advanced.js +14 -6
- package/dist/react/frame-context.d.ts +1 -0
- package/dist/react/frame-context.d.ts.map +1 -0
- package/dist/react/frame-context.js +88 -94
- package/dist/react/frame-context.js.map +1 -0
- package/dist/react/index.d.ts +6 -2
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +12 -9
- package/dist/react/motiongpu-context.d.ts +1 -0
- package/dist/react/motiongpu-context.d.ts.map +1 -0
- package/dist/react/motiongpu-context.js +18 -15
- package/dist/react/motiongpu-context.js.map +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/react/use-motiongpu-user-context.js +83 -82
- package/dist/react/use-motiongpu-user-context.js.map +1 -0
- package/dist/react/use-texture.d.ts +1 -0
- package/dist/react/use-texture.d.ts.map +1 -0
- package/dist/react/use-texture.js +132 -152
- package/dist/react/use-texture.js.map +1 -0
- package/dist/svelte/FragCanvas.svelte +2 -2
- package/dist/svelte/FragCanvas.svelte.d.ts +3 -2
- package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/Portal.svelte.d.ts +1 -0
- package/dist/svelte/Portal.svelte.d.ts.map +1 -0
- package/dist/svelte/advanced.d.ts +1 -0
- package/dist/svelte/advanced.d.ts.map +1 -0
- package/dist/svelte/advanced.js +13 -6
- package/dist/svelte/frame-context.d.ts +1 -0
- package/dist/svelte/frame-context.d.ts.map +1 -0
- package/dist/svelte/frame-context.js +27 -27
- package/dist/svelte/frame-context.js.map +1 -0
- package/dist/svelte/index.d.ts +6 -2
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +12 -9
- package/dist/svelte/motiongpu-context.d.ts +1 -0
- package/dist/svelte/motiongpu-context.d.ts.map +1 -0
- package/dist/svelte/motiongpu-context.js +24 -21
- package/dist/svelte/motiongpu-context.js.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.js +69 -70
- package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
- package/dist/svelte/use-texture.d.ts +1 -0
- package/dist/svelte/use-texture.d.ts.map +1 -0
- package/dist/svelte/use-texture.js +125 -147
- package/dist/svelte/use-texture.js.map +1 -0
- package/package.json +12 -7
- package/src/lib/advanced.ts +6 -0
- package/src/lib/core/advanced.ts +12 -0
- package/src/lib/core/compute-shader.ts +326 -0
- package/src/lib/core/current-value.ts +64 -0
- package/src/lib/core/error-diagnostics.ts +236 -0
- package/src/lib/core/error-report.ts +535 -0
- package/src/lib/core/frame-registry.ts +1190 -0
- package/src/lib/core/index.ts +94 -0
- package/src/lib/core/material-preprocess.ts +295 -0
- package/src/lib/core/material.ts +748 -0
- package/src/lib/core/recompile-policy.ts +31 -0
- package/src/lib/core/render-graph.ts +173 -0
- package/src/lib/core/render-targets.ts +107 -0
- package/src/lib/core/renderer.ts +2161 -0
- package/src/lib/core/runtime-loop.ts +537 -0
- package/src/lib/core/scheduler-helpers.ts +136 -0
- package/src/lib/core/shader.ts +301 -0
- package/src/lib/core/storage-buffers.ts +142 -0
- package/src/lib/core/texture-loader.ts +482 -0
- package/src/lib/core/textures.ts +257 -0
- package/src/lib/core/types.ts +743 -0
- package/src/lib/core/uniforms.ts +282 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/passes/BlitPass.ts +54 -0
- package/src/lib/passes/ComputePass.ts +136 -0
- package/src/lib/passes/CopyPass.ts +80 -0
- package/src/lib/passes/FullscreenPass.ts +173 -0
- package/src/lib/passes/PingPongComputePass.ts +180 -0
- package/src/lib/passes/ShaderPass.ts +89 -0
- package/src/lib/passes/index.ts +9 -0
- package/src/lib/react/FragCanvas.tsx +345 -0
- package/src/lib/react/MotionGPUErrorOverlay.tsx +524 -0
- package/src/lib/react/Portal.tsx +34 -0
- package/src/lib/react/advanced.ts +36 -0
- package/src/lib/react/frame-context.ts +169 -0
- package/src/lib/react/index.ts +68 -0
- package/src/lib/react/motiongpu-context.ts +88 -0
- package/src/lib/react/use-motiongpu-user-context.ts +186 -0
- package/src/lib/react/use-texture.ts +233 -0
- package/src/lib/svelte/FragCanvas.svelte +249 -0
- package/src/lib/svelte/MotionGPUErrorOverlay.svelte +512 -0
- package/src/lib/svelte/Portal.svelte +31 -0
- package/src/lib/svelte/advanced.ts +32 -0
- package/src/lib/svelte/frame-context.ts +87 -0
- package/src/lib/svelte/index.ts +68 -0
- package/src/lib/svelte/motiongpu-context.ts +97 -0
- package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
- package/src/lib/svelte/use-texture.ts +232 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import type { TextureUpdateMode } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Options controlling bitmap decode behavior.
|
|
5
|
+
*/
|
|
6
|
+
export interface TextureDecodeOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Controls color-space conversion during decode.
|
|
9
|
+
*/
|
|
10
|
+
colorSpaceConversion?: 'default' | 'none';
|
|
11
|
+
/**
|
|
12
|
+
* Controls alpha premultiplication during decode.
|
|
13
|
+
*/
|
|
14
|
+
premultiplyAlpha?: 'default' | 'none' | 'premultiply';
|
|
15
|
+
/**
|
|
16
|
+
* Controls bitmap orientation during decode.
|
|
17
|
+
*/
|
|
18
|
+
imageOrientation?: 'none' | 'flipY';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options controlling URL-based texture loading and decode behavior.
|
|
23
|
+
*/
|
|
24
|
+
export interface TextureLoadOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Desired texture color space.
|
|
27
|
+
*/
|
|
28
|
+
colorSpace?: 'srgb' | 'linear';
|
|
29
|
+
/**
|
|
30
|
+
* Fetch options forwarded to `fetch`.
|
|
31
|
+
*/
|
|
32
|
+
requestInit?: RequestInit;
|
|
33
|
+
/**
|
|
34
|
+
* Decode options forwarded to `createImageBitmap`.
|
|
35
|
+
*/
|
|
36
|
+
decode?: TextureDecodeOptions;
|
|
37
|
+
/**
|
|
38
|
+
* Optional cancellation signal for this request.
|
|
39
|
+
*/
|
|
40
|
+
signal?: AbortSignal;
|
|
41
|
+
/**
|
|
42
|
+
* Optional runtime update strategy metadata attached to loaded textures.
|
|
43
|
+
*/
|
|
44
|
+
update?: TextureUpdateMode;
|
|
45
|
+
/**
|
|
46
|
+
* Optional runtime flip-y metadata attached to loaded textures.
|
|
47
|
+
*/
|
|
48
|
+
flipY?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Optional runtime premultiplied-alpha metadata attached to loaded textures.
|
|
51
|
+
*/
|
|
52
|
+
premultipliedAlpha?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Optional runtime mipmap metadata attached to loaded textures.
|
|
55
|
+
*/
|
|
56
|
+
generateMipmaps?: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Loaded texture payload returned by URL loaders.
|
|
61
|
+
*/
|
|
62
|
+
export interface LoadedTexture {
|
|
63
|
+
/**
|
|
64
|
+
* Source URL.
|
|
65
|
+
*/
|
|
66
|
+
url: string;
|
|
67
|
+
/**
|
|
68
|
+
* Decoded bitmap source.
|
|
69
|
+
*/
|
|
70
|
+
source: ImageBitmap;
|
|
71
|
+
/**
|
|
72
|
+
* Bitmap width in pixels.
|
|
73
|
+
*/
|
|
74
|
+
width: number;
|
|
75
|
+
/**
|
|
76
|
+
* Bitmap height in pixels.
|
|
77
|
+
*/
|
|
78
|
+
height: number;
|
|
79
|
+
/**
|
|
80
|
+
* Effective color space.
|
|
81
|
+
*/
|
|
82
|
+
colorSpace: 'srgb' | 'linear';
|
|
83
|
+
/**
|
|
84
|
+
* Effective runtime update strategy.
|
|
85
|
+
*/
|
|
86
|
+
update?: TextureUpdateMode;
|
|
87
|
+
/**
|
|
88
|
+
* Effective runtime flip-y metadata.
|
|
89
|
+
*/
|
|
90
|
+
flipY?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Effective runtime premultiplied-alpha metadata.
|
|
93
|
+
*/
|
|
94
|
+
premultipliedAlpha?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Effective runtime mipmap metadata.
|
|
97
|
+
*/
|
|
98
|
+
generateMipmaps?: boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Releases bitmap resources.
|
|
101
|
+
*/
|
|
102
|
+
dispose: () => void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface NormalizedTextureLoadOptions {
|
|
106
|
+
colorSpace: 'srgb' | 'linear';
|
|
107
|
+
requestInit?: RequestInit;
|
|
108
|
+
decode: Required<TextureDecodeOptions>;
|
|
109
|
+
signal?: AbortSignal;
|
|
110
|
+
update?: TextureUpdateMode;
|
|
111
|
+
flipY?: boolean;
|
|
112
|
+
premultipliedAlpha?: boolean;
|
|
113
|
+
generateMipmaps?: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface TextureResourceCacheEntry {
|
|
117
|
+
key: string;
|
|
118
|
+
refs: number;
|
|
119
|
+
controller: AbortController;
|
|
120
|
+
settled: boolean;
|
|
121
|
+
blobPromise: Promise<Blob>;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const resourceCache = new Map<string, TextureResourceCacheEntry>();
|
|
125
|
+
|
|
126
|
+
function createAbortError(): Error {
|
|
127
|
+
try {
|
|
128
|
+
return new DOMException('Texture request was aborted', 'AbortError');
|
|
129
|
+
} catch {
|
|
130
|
+
const error = new Error('Texture request was aborted');
|
|
131
|
+
(error as Error & { name: string }).name = 'AbortError';
|
|
132
|
+
return error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Checks whether error represents abort cancellation.
|
|
138
|
+
*/
|
|
139
|
+
export function isAbortError(error: unknown): boolean {
|
|
140
|
+
return (
|
|
141
|
+
error instanceof Error &&
|
|
142
|
+
(error.name === 'AbortError' || error.message.toLowerCase().includes('aborted'))
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function toBodyFingerprint(body: BodyInit | null | undefined): string | null {
|
|
147
|
+
if (body == null) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (typeof body === 'string') {
|
|
152
|
+
return `string:${body}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (body instanceof URLSearchParams) {
|
|
156
|
+
return `urlsearchparams:${body.toString()}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData) {
|
|
160
|
+
const entries = Array.from(body.entries()).map(([key, value]) => `${key}:${String(value)}`);
|
|
161
|
+
return `formdata:${entries.join('&')}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (body instanceof Blob) {
|
|
165
|
+
return `blob:${body.type}:${body.size}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (body instanceof ArrayBuffer) {
|
|
169
|
+
return `arraybuffer:${body.byteLength}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (ArrayBuffer.isView(body)) {
|
|
173
|
+
return `view:${body.byteLength}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return `opaque:${Object.prototype.toString.call(body)}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function normalizeRequestInit(requestInit: RequestInit | undefined): Record<string, unknown> {
|
|
180
|
+
if (!requestInit) {
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const headers = new Headers(requestInit.headers);
|
|
185
|
+
const headerEntries = Array.from(headers.entries()).sort(([a], [b]) => a.localeCompare(b));
|
|
186
|
+
const normalized: Record<string, unknown> = {};
|
|
187
|
+
|
|
188
|
+
normalized.method = (requestInit.method ?? 'GET').toUpperCase();
|
|
189
|
+
normalized.mode = requestInit.mode ?? null;
|
|
190
|
+
normalized.cache = requestInit.cache ?? null;
|
|
191
|
+
normalized.credentials = requestInit.credentials ?? null;
|
|
192
|
+
normalized.redirect = requestInit.redirect ?? null;
|
|
193
|
+
normalized.referrer = requestInit.referrer ?? null;
|
|
194
|
+
normalized.referrerPolicy = requestInit.referrerPolicy ?? null;
|
|
195
|
+
normalized.integrity = requestInit.integrity ?? null;
|
|
196
|
+
normalized.keepalive = requestInit.keepalive ?? false;
|
|
197
|
+
normalized.priority = requestInit.priority ?? null;
|
|
198
|
+
normalized.headers = headerEntries;
|
|
199
|
+
normalized.body = toBodyFingerprint(requestInit.body);
|
|
200
|
+
|
|
201
|
+
return normalized;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function normalizeTextureLoadOptions(options: TextureLoadOptions): NormalizedTextureLoadOptions {
|
|
205
|
+
const colorSpace = options.colorSpace ?? 'srgb';
|
|
206
|
+
|
|
207
|
+
const normalized: NormalizedTextureLoadOptions = {
|
|
208
|
+
colorSpace,
|
|
209
|
+
decode: {
|
|
210
|
+
colorSpaceConversion:
|
|
211
|
+
options.decode?.colorSpaceConversion ?? (colorSpace === 'linear' ? 'none' : 'default'),
|
|
212
|
+
premultiplyAlpha: options.decode?.premultiplyAlpha ?? 'default',
|
|
213
|
+
imageOrientation: options.decode?.imageOrientation ?? 'none'
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (options.requestInit !== undefined) {
|
|
218
|
+
normalized.requestInit = options.requestInit;
|
|
219
|
+
}
|
|
220
|
+
if (options.signal !== undefined) {
|
|
221
|
+
normalized.signal = options.signal;
|
|
222
|
+
}
|
|
223
|
+
if (options.update !== undefined) {
|
|
224
|
+
normalized.update = options.update;
|
|
225
|
+
}
|
|
226
|
+
if (options.flipY !== undefined) {
|
|
227
|
+
normalized.flipY = options.flipY;
|
|
228
|
+
}
|
|
229
|
+
if (options.premultipliedAlpha !== undefined) {
|
|
230
|
+
normalized.premultipliedAlpha = options.premultipliedAlpha;
|
|
231
|
+
}
|
|
232
|
+
if (options.generateMipmaps !== undefined) {
|
|
233
|
+
normalized.generateMipmaps = options.generateMipmaps;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return normalized;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Builds deterministic resource cache key from full URL IO config.
|
|
241
|
+
*/
|
|
242
|
+
export function buildTextureResourceCacheKey(
|
|
243
|
+
url: string,
|
|
244
|
+
options: TextureLoadOptions = {}
|
|
245
|
+
): string {
|
|
246
|
+
const normalized = normalizeTextureLoadOptions(options);
|
|
247
|
+
return JSON.stringify({
|
|
248
|
+
url,
|
|
249
|
+
colorSpace: normalized.colorSpace,
|
|
250
|
+
requestInit: normalizeRequestInit(normalized.requestInit),
|
|
251
|
+
decode: normalized.decode
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Clears the internal texture resource cache.
|
|
257
|
+
*/
|
|
258
|
+
export function clearTextureBlobCache(): void {
|
|
259
|
+
for (const entry of resourceCache.values()) {
|
|
260
|
+
if (!entry.settled) {
|
|
261
|
+
entry.controller.abort();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
resourceCache.clear();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function acquireTextureBlob(
|
|
268
|
+
url: string,
|
|
269
|
+
options: TextureLoadOptions
|
|
270
|
+
): {
|
|
271
|
+
entry: TextureResourceCacheEntry;
|
|
272
|
+
release: () => void;
|
|
273
|
+
} {
|
|
274
|
+
const key = buildTextureResourceCacheKey(url, options);
|
|
275
|
+
const existing = resourceCache.get(key);
|
|
276
|
+
if (existing) {
|
|
277
|
+
existing.refs += 1;
|
|
278
|
+
let released = false;
|
|
279
|
+
return {
|
|
280
|
+
entry: existing,
|
|
281
|
+
release: () => {
|
|
282
|
+
if (released) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
released = true;
|
|
286
|
+
existing.refs = Math.max(0, existing.refs - 1);
|
|
287
|
+
if (existing.refs === 0) {
|
|
288
|
+
if (!existing.settled) {
|
|
289
|
+
existing.controller.abort();
|
|
290
|
+
}
|
|
291
|
+
resourceCache.delete(key);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const normalized = normalizeTextureLoadOptions(options);
|
|
298
|
+
const controller = new AbortController();
|
|
299
|
+
const requestInit = {
|
|
300
|
+
...(normalized.requestInit ?? {}),
|
|
301
|
+
signal: controller.signal
|
|
302
|
+
} satisfies RequestInit;
|
|
303
|
+
const entry: TextureResourceCacheEntry = {
|
|
304
|
+
key,
|
|
305
|
+
refs: 1,
|
|
306
|
+
controller,
|
|
307
|
+
settled: false,
|
|
308
|
+
blobPromise: fetch(url, requestInit)
|
|
309
|
+
.then(async (response) => {
|
|
310
|
+
if (!response.ok) {
|
|
311
|
+
throw new Error(`Texture request failed (${response.status}) for ${url}`);
|
|
312
|
+
}
|
|
313
|
+
return response.blob();
|
|
314
|
+
})
|
|
315
|
+
.then((blob) => {
|
|
316
|
+
entry.settled = true;
|
|
317
|
+
return blob;
|
|
318
|
+
})
|
|
319
|
+
.catch((error) => {
|
|
320
|
+
resourceCache.delete(key);
|
|
321
|
+
throw error;
|
|
322
|
+
})
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
resourceCache.set(key, entry);
|
|
326
|
+
let released = false;
|
|
327
|
+
return {
|
|
328
|
+
entry,
|
|
329
|
+
release: () => {
|
|
330
|
+
if (released) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
released = true;
|
|
334
|
+
entry.refs = Math.max(0, entry.refs - 1);
|
|
335
|
+
if (entry.refs === 0) {
|
|
336
|
+
if (!entry.settled) {
|
|
337
|
+
entry.controller.abort();
|
|
338
|
+
}
|
|
339
|
+
resourceCache.delete(key);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function awaitWithAbort<T>(promise: Promise<T>, signal: AbortSignal | undefined): Promise<T> {
|
|
346
|
+
if (!signal) {
|
|
347
|
+
return promise;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (signal.aborted) {
|
|
351
|
+
throw createAbortError();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return new Promise<T>((resolve, reject) => {
|
|
355
|
+
const onAbort = (): void => {
|
|
356
|
+
reject(createAbortError());
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
360
|
+
|
|
361
|
+
promise.then(resolve, reject).finally(() => {
|
|
362
|
+
signal.removeEventListener('abort', onAbort);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Loads a single texture from URL and converts it to an `ImageBitmap`.
|
|
369
|
+
*
|
|
370
|
+
* @param url - Texture URL.
|
|
371
|
+
* @param options - Loading options.
|
|
372
|
+
* @returns Loaded texture object.
|
|
373
|
+
* @throws {Error} When runtime does not support `createImageBitmap` or request fails.
|
|
374
|
+
*/
|
|
375
|
+
export async function loadTextureFromUrl(
|
|
376
|
+
url: string,
|
|
377
|
+
options: TextureLoadOptions = {}
|
|
378
|
+
): Promise<LoadedTexture> {
|
|
379
|
+
if (typeof createImageBitmap !== 'function') {
|
|
380
|
+
throw new Error('createImageBitmap is not available in this runtime');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const normalized = normalizeTextureLoadOptions(options);
|
|
384
|
+
const { entry, release } = acquireTextureBlob(url, options);
|
|
385
|
+
let bitmap: ImageBitmap | null = null;
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
const blob = await awaitWithAbort(entry.blobPromise, normalized.signal);
|
|
389
|
+
|
|
390
|
+
const bitmapOptions: ImageBitmapOptions = {
|
|
391
|
+
colorSpaceConversion: normalized.decode.colorSpaceConversion,
|
|
392
|
+
premultiplyAlpha: normalized.decode.premultiplyAlpha,
|
|
393
|
+
imageOrientation: normalized.decode.imageOrientation
|
|
394
|
+
};
|
|
395
|
+
const allDefaults =
|
|
396
|
+
bitmapOptions.colorSpaceConversion === 'default' &&
|
|
397
|
+
bitmapOptions.premultiplyAlpha === 'default' &&
|
|
398
|
+
bitmapOptions.imageOrientation === 'none';
|
|
399
|
+
|
|
400
|
+
bitmap = allDefaults
|
|
401
|
+
? await createImageBitmap(blob)
|
|
402
|
+
: await createImageBitmap(blob, bitmapOptions);
|
|
403
|
+
|
|
404
|
+
if (normalized.signal?.aborted) {
|
|
405
|
+
bitmap.close();
|
|
406
|
+
throw createAbortError();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
let disposed = false;
|
|
410
|
+
const loaded: LoadedTexture = {
|
|
411
|
+
url,
|
|
412
|
+
source: bitmap,
|
|
413
|
+
width: bitmap.width,
|
|
414
|
+
height: bitmap.height,
|
|
415
|
+
colorSpace: normalized.colorSpace,
|
|
416
|
+
dispose: () => {
|
|
417
|
+
if (disposed) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
disposed = true;
|
|
421
|
+
bitmap?.close();
|
|
422
|
+
bitmap = null;
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
if (normalized.update !== undefined) {
|
|
427
|
+
loaded.update = normalized.update;
|
|
428
|
+
}
|
|
429
|
+
if (normalized.flipY !== undefined) {
|
|
430
|
+
loaded.flipY = normalized.flipY;
|
|
431
|
+
}
|
|
432
|
+
if (normalized.premultipliedAlpha !== undefined) {
|
|
433
|
+
loaded.premultipliedAlpha = normalized.premultipliedAlpha;
|
|
434
|
+
}
|
|
435
|
+
if (normalized.generateMipmaps !== undefined) {
|
|
436
|
+
loaded.generateMipmaps = normalized.generateMipmaps;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return loaded;
|
|
440
|
+
} catch (error) {
|
|
441
|
+
if (bitmap) {
|
|
442
|
+
bitmap.close();
|
|
443
|
+
}
|
|
444
|
+
throw error;
|
|
445
|
+
} finally {
|
|
446
|
+
release();
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Loads many textures in parallel from URLs.
|
|
452
|
+
*
|
|
453
|
+
* @param urls - Texture URLs.
|
|
454
|
+
* @param options - Shared loading options.
|
|
455
|
+
* @returns Promise resolving to loaded textures in input order.
|
|
456
|
+
*/
|
|
457
|
+
export async function loadTexturesFromUrls(
|
|
458
|
+
urls: string[],
|
|
459
|
+
options: TextureLoadOptions = {}
|
|
460
|
+
): Promise<LoadedTexture[]> {
|
|
461
|
+
const settled = await Promise.allSettled(urls.map((url) => loadTextureFromUrl(url, options)));
|
|
462
|
+
const loaded: LoadedTexture[] = [];
|
|
463
|
+
let firstError: unknown = null;
|
|
464
|
+
|
|
465
|
+
for (const entry of settled) {
|
|
466
|
+
if (entry.status === 'fulfilled') {
|
|
467
|
+
loaded.push(entry.value);
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
firstError ??= entry.reason;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (firstError) {
|
|
475
|
+
for (const texture of loaded) {
|
|
476
|
+
texture.dispose();
|
|
477
|
+
}
|
|
478
|
+
throw firstError;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return loaded;
|
|
482
|
+
}
|