voodoojs 0.4.6
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 +77 -0
- package/dist/chunk-234ZLC6W.js +401 -0
- package/dist/chunk-4HQEOXTK.js +10271 -0
- package/dist/chunk-5777LJVW.js +64 -0
- package/dist/chunk-5CKGDARU.js +1845 -0
- package/dist/chunk-A2UOVQBP.js +82 -0
- package/dist/chunk-E27NRARW.js +16 -0
- package/dist/chunk-JZIYRIY6.js +1196 -0
- package/dist/chunk-NNU6WOOU.js +641 -0
- package/dist/chunk-PQZEVFVZ.js +448 -0
- package/dist/chunk-RJUNPXQF.js +946 -0
- package/dist/chunk-U76IRJKH.js +72 -0
- package/dist/essential.cjs +13889 -0
- package/dist/essential.d.cts +24 -0
- package/dist/essential.d.ts +24 -0
- package/dist/essential.js +51 -0
- package/dist/gpu.cjs +2008 -0
- package/dist/gpu.d.cts +68 -0
- package/dist/gpu.d.ts +68 -0
- package/dist/gpu.js +273 -0
- package/dist/http.cjs +467 -0
- package/dist/http.d.cts +148 -0
- package/dist/http.d.ts +148 -0
- package/dist/http.js +7 -0
- package/dist/index-CaLD-0oh.d.cts +608 -0
- package/dist/index-CaLD-0oh.d.ts +608 -0
- package/dist/index-DTllqUtj.d.cts +261 -0
- package/dist/index-DTllqUtj.d.ts +261 -0
- package/dist/index.cjs +23063 -0
- package/dist/index.d.cts +1603 -0
- package/dist/index.d.ts +1603 -0
- package/dist/index.js +6924 -0
- package/dist/query-CKJ4oSpG.d.cts +1595 -0
- package/dist/query-DQFRmu3u.d.ts +1595 -0
- package/dist/reactivity.cjs +676 -0
- package/dist/reactivity.d.cts +188 -0
- package/dist/reactivity.d.ts +188 -0
- package/dist/reactivity.js +4 -0
- package/dist/socket.cjs +2685 -0
- package/dist/socket.d.cts +167 -0
- package/dist/socket.d.ts +167 -0
- package/dist/socket.js +238 -0
- package/dist/style-XEUAGGJK.js +5 -0
- package/dist/utils.cjs +397 -0
- package/dist/utils.d.cts +111 -0
- package/dist/utils.d.ts +111 -0
- package/dist/utils.js +4 -0
- package/dist/voodoo.core.js +8213 -0
- package/dist/voodoo.core.min.js +146 -0
- package/dist/voodoo.full.js +21193 -0
- package/dist/voodoo.full.min.js +1784 -0
- package/dist/voodoo.js +14185 -0
- package/dist/voodoo.min.js +420 -0
- package/package.json +127 -0
package/dist/gpu.cjs
ADDED
|
@@ -0,0 +1,2008 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Voodoo.js v0.4.6
|
|
7
|
+
* JavaScript feels like magic.
|
|
8
|
+
* (c) 2026 Voodoo.js contributors. MIT License.
|
|
9
|
+
*/
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
13
|
+
function handleError(err, context) {
|
|
14
|
+
console.error(`[Voodoo] error in ${context}:`, err);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// src/runtime/registry.ts
|
|
18
|
+
var config = {
|
|
19
|
+
prefix: "v-"};
|
|
20
|
+
var directives = /* @__PURE__ */ new Map();
|
|
21
|
+
var PRIORITY = {
|
|
22
|
+
DEFAULT: 0};
|
|
23
|
+
function defineDirective(name, setup, options = {}) {
|
|
24
|
+
directives.set(name, {
|
|
25
|
+
name,
|
|
26
|
+
setup,
|
|
27
|
+
priority: options.priority ?? PRIORITY.DEFAULT,
|
|
28
|
+
terminal: options.terminal ?? false
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function describeElement(el) {
|
|
32
|
+
if (!el) return "(no element)";
|
|
33
|
+
let out = el.tagName.toLowerCase();
|
|
34
|
+
if (el.id) out += `#${el.id}`;
|
|
35
|
+
const classes = (el.getAttribute("class") || "").trim().split(/\s+/).filter(Boolean);
|
|
36
|
+
if (classes.length) out += `.${classes.slice(0, 2).join(".")}`;
|
|
37
|
+
return `<${out}>`;
|
|
38
|
+
}
|
|
39
|
+
function warn(message) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/runtime/walker.ts
|
|
44
|
+
var nodeScopes = /* @__PURE__ */ new WeakMap();
|
|
45
|
+
var nodeCleanups = /* @__PURE__ */ new WeakMap();
|
|
46
|
+
var initialized = /* @__PURE__ */ new WeakSet();
|
|
47
|
+
var nodeEffectScopes = /* @__PURE__ */ new WeakMap();
|
|
48
|
+
function destroy(node) {
|
|
49
|
+
if (node.nodeType === 1) {
|
|
50
|
+
const children = [];
|
|
51
|
+
for (let child = node.firstChild; child; child = child.nextSibling) {
|
|
52
|
+
if (child.nodeType === 1 || child.nodeType === 3) children.push(child);
|
|
53
|
+
}
|
|
54
|
+
for (let i = children.length - 1; i >= 0; i--) destroy(children[i]);
|
|
55
|
+
}
|
|
56
|
+
const list = nodeCleanups.get(node);
|
|
57
|
+
if (list) {
|
|
58
|
+
nodeCleanups.delete(node);
|
|
59
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
60
|
+
try {
|
|
61
|
+
list[i]();
|
|
62
|
+
} catch (err) {
|
|
63
|
+
handleError(err, "cleanup");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (node.nodeType === 1) unindexElement(node);
|
|
68
|
+
nodeScopes.delete(node);
|
|
69
|
+
nodeEffectScopes.delete(node);
|
|
70
|
+
initialized.delete(node);
|
|
71
|
+
}
|
|
72
|
+
var directiveIndex = /* @__PURE__ */ new Map();
|
|
73
|
+
var directiveNamesOf = /* @__PURE__ */ new WeakMap();
|
|
74
|
+
function unindexElement(el) {
|
|
75
|
+
const names = directiveNamesOf.get(el);
|
|
76
|
+
if (!names) return;
|
|
77
|
+
for (const name of names) directiveIndex.get(name)?.delete(el);
|
|
78
|
+
directiveNamesOf.delete(el);
|
|
79
|
+
}
|
|
80
|
+
var attributeCache = /* @__PURE__ */ new WeakMap();
|
|
81
|
+
function isVoodooAttribute(name) {
|
|
82
|
+
return name.startsWith(config.prefix) || name.startsWith("data-v-") || name.charCodeAt(0) === 64 || name.charCodeAt(0) === 58 && name.length > 1;
|
|
83
|
+
}
|
|
84
|
+
function originalAttributes(el) {
|
|
85
|
+
const map = attributeCache.get(el);
|
|
86
|
+
if (map) return new Map(map);
|
|
87
|
+
const out = /* @__PURE__ */ new Map();
|
|
88
|
+
for (let i = 0; i < el.attributes.length; i++) {
|
|
89
|
+
const attr = el.attributes[i];
|
|
90
|
+
if (isVoodooAttribute(attr.name)) out.set(attr.name, attr.value);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/utils/index.ts
|
|
96
|
+
function parseDuration(value, fallback = 0) {
|
|
97
|
+
if (value == null || value === "") return fallback;
|
|
98
|
+
if (typeof value === "number") return value;
|
|
99
|
+
const match = /^\s*([\d.]+)\s*(ms|s|m|h)?\s*$/i.exec(String(value));
|
|
100
|
+
if (!match) return fallback;
|
|
101
|
+
const amount = parseFloat(match[1]);
|
|
102
|
+
switch ((match[2] || "ms").toLowerCase()) {
|
|
103
|
+
case "s":
|
|
104
|
+
return amount * 1e3;
|
|
105
|
+
case "m":
|
|
106
|
+
return amount * 6e4;
|
|
107
|
+
case "h":
|
|
108
|
+
return amount * 36e5;
|
|
109
|
+
default:
|
|
110
|
+
return amount;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// src/http/index.ts
|
|
115
|
+
var HttpError = class extends Error {
|
|
116
|
+
constructor(message, response, config2, cause) {
|
|
117
|
+
super(message);
|
|
118
|
+
__publicField(this, "response", response);
|
|
119
|
+
__publicField(this, "config", config2);
|
|
120
|
+
__publicField(this, "cause", cause);
|
|
121
|
+
this.name = "HttpError";
|
|
122
|
+
}
|
|
123
|
+
get status() {
|
|
124
|
+
return this.response?.status ?? 0;
|
|
125
|
+
}
|
|
126
|
+
/** `true` when error is network, timeout, or cancellation. */
|
|
127
|
+
get isNetworkError() {
|
|
128
|
+
return !this.response;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
var defaults = {
|
|
132
|
+
baseURL: "",
|
|
133
|
+
headers: { Accept: "application/json, text/html, */*" },
|
|
134
|
+
timeout: 3e4,
|
|
135
|
+
retry: 0,
|
|
136
|
+
retryDelay: 500,
|
|
137
|
+
credentials: "same-origin",
|
|
138
|
+
csrfMeta: "csrf-token",
|
|
139
|
+
csrfHeader: "X-CSRF-TOKEN"
|
|
140
|
+
};
|
|
141
|
+
var requestInterceptors = [];
|
|
142
|
+
var responseInterceptors = [];
|
|
143
|
+
var errorInterceptors = [];
|
|
144
|
+
var responseCache = /* @__PURE__ */ new Map();
|
|
145
|
+
function cacheKey(config2) {
|
|
146
|
+
return `${config2.method ?? "GET"} ${buildURL(config2)}`;
|
|
147
|
+
}
|
|
148
|
+
function clearCache(pattern) {
|
|
149
|
+
if (!pattern) {
|
|
150
|
+
responseCache.clear();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const test = typeof pattern === "string" ? (k) => k.includes(pattern) : (k) => pattern.test(k);
|
|
154
|
+
for (const key of [...responseCache.keys()]) if (test(key)) responseCache.delete(key);
|
|
155
|
+
}
|
|
156
|
+
var OFFLINE_KEY = "voodoo:offline-queue";
|
|
157
|
+
function readQueue() {
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse(localStorage.getItem(OFFLINE_KEY) || "[]");
|
|
160
|
+
} catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function writeQueue(list) {
|
|
165
|
+
try {
|
|
166
|
+
localStorage.setItem(OFFLINE_KEY, JSON.stringify(list));
|
|
167
|
+
} catch {
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function enqueueOffline(config2) {
|
|
171
|
+
if (typeof localStorage === "undefined") return;
|
|
172
|
+
const list = readQueue();
|
|
173
|
+
list.push({
|
|
174
|
+
url: buildURL(config2),
|
|
175
|
+
method: config2.method ?? "POST",
|
|
176
|
+
body: config2.body,
|
|
177
|
+
headers: config2.headers ?? {},
|
|
178
|
+
at: Date.now()
|
|
179
|
+
});
|
|
180
|
+
writeQueue(list);
|
|
181
|
+
}
|
|
182
|
+
async function flushOfflineQueue() {
|
|
183
|
+
if (typeof localStorage === "undefined") return 0;
|
|
184
|
+
const list = readQueue();
|
|
185
|
+
if (!list.length) return 0;
|
|
186
|
+
writeQueue([]);
|
|
187
|
+
let sent = 0;
|
|
188
|
+
for (let index = 0; index < list.length; index++) {
|
|
189
|
+
const item = list[index];
|
|
190
|
+
try {
|
|
191
|
+
await request({
|
|
192
|
+
url: item.url,
|
|
193
|
+
method: item.method,
|
|
194
|
+
body: item.body,
|
|
195
|
+
headers: item.headers,
|
|
196
|
+
offlineQueue: false
|
|
197
|
+
});
|
|
198
|
+
sent++;
|
|
199
|
+
} catch {
|
|
200
|
+
const newItems = readQueue();
|
|
201
|
+
writeQueue([...list.slice(index), ...newItems]);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return sent;
|
|
206
|
+
}
|
|
207
|
+
if (typeof window !== "undefined") {
|
|
208
|
+
window.addEventListener("online", () => {
|
|
209
|
+
void flushOfflineQueue();
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
function buildURL(config2) {
|
|
213
|
+
let url = config2.url;
|
|
214
|
+
const base = defaults.baseURL;
|
|
215
|
+
if (base && !/^https?:\/\//i.test(url) && !url.startsWith("//")) {
|
|
216
|
+
url = `${base.replace(/\/$/, "")}/${url.replace(/^\//, "")}`;
|
|
217
|
+
}
|
|
218
|
+
if (config2.params) {
|
|
219
|
+
const search = new URLSearchParams();
|
|
220
|
+
for (const [key, value] of Object.entries(config2.params)) {
|
|
221
|
+
if (value == null || value === "") continue;
|
|
222
|
+
search.append(key, String(value));
|
|
223
|
+
}
|
|
224
|
+
const query = search.toString();
|
|
225
|
+
if (query) url += (url.includes("?") ? "&" : "?") + query;
|
|
226
|
+
}
|
|
227
|
+
return url;
|
|
228
|
+
}
|
|
229
|
+
function csrfToken() {
|
|
230
|
+
if (typeof document === "undefined") return null;
|
|
231
|
+
const meta = document.querySelector(`meta[name="${defaults.csrfMeta}"]`);
|
|
232
|
+
return meta?.getAttribute("content") ?? null;
|
|
233
|
+
}
|
|
234
|
+
function prepareBody(body, headers) {
|
|
235
|
+
if (body == null) return void 0;
|
|
236
|
+
if (typeof FormData !== "undefined" && body instanceof FormData || typeof Blob !== "undefined" && body instanceof Blob || typeof URLSearchParams !== "undefined" && body instanceof URLSearchParams || typeof ArrayBuffer !== "undefined" && body instanceof ArrayBuffer || typeof body === "string") {
|
|
237
|
+
return body;
|
|
238
|
+
}
|
|
239
|
+
if (!headers["Content-Type"]) headers["Content-Type"] = "application/json";
|
|
240
|
+
return JSON.stringify(body);
|
|
241
|
+
}
|
|
242
|
+
async function parseResponse(response, type) {
|
|
243
|
+
if (response.status === 204 || response.status === 205) return null;
|
|
244
|
+
const contentType = response.headers.get("content-type") || "";
|
|
245
|
+
switch (type) {
|
|
246
|
+
case "json":
|
|
247
|
+
return response.json();
|
|
248
|
+
case "text":
|
|
249
|
+
return response.text();
|
|
250
|
+
case "blob":
|
|
251
|
+
return response.blob();
|
|
252
|
+
case "arrayBuffer":
|
|
253
|
+
return response.arrayBuffer();
|
|
254
|
+
case "formData":
|
|
255
|
+
return response.formData();
|
|
256
|
+
default:
|
|
257
|
+
if (contentType.includes("application/json") || contentType.includes("+json")) {
|
|
258
|
+
const text = await response.text();
|
|
259
|
+
return text ? JSON.parse(text) : null;
|
|
260
|
+
}
|
|
261
|
+
return response.text();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
var METODOS_SEGUROS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
|
|
265
|
+
function temChaveDeIdempotencia(headers) {
|
|
266
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
267
|
+
if (name.toLowerCase() === "idempotency-key" && String(value).trim() !== "") return true;
|
|
268
|
+
}
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
function podeRepetir(method, config2, headers, url) {
|
|
272
|
+
if (METODOS_SEGUROS.has(method)) return true;
|
|
273
|
+
if (config2.retryUnsafe === true) return true;
|
|
274
|
+
if (temChaveDeIdempotencia(headers)) return true;
|
|
275
|
+
if ((config2.retry ?? 0) > 0) ;
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
async function request(input) {
|
|
279
|
+
let config2 = {
|
|
280
|
+
method: "GET",
|
|
281
|
+
timeout: defaults.timeout,
|
|
282
|
+
retry: defaults.retry,
|
|
283
|
+
retryDelay: defaults.retryDelay,
|
|
284
|
+
credentials: defaults.credentials,
|
|
285
|
+
responseType: "auto",
|
|
286
|
+
...input,
|
|
287
|
+
headers: { ...defaults.headers, ...input.headers }
|
|
288
|
+
};
|
|
289
|
+
for (const interceptor of requestInterceptors) {
|
|
290
|
+
config2 = await interceptor(config2);
|
|
291
|
+
}
|
|
292
|
+
const method = (config2.method ?? "GET").toUpperCase();
|
|
293
|
+
if (config2.cache && method === "GET") {
|
|
294
|
+
const entry = responseCache.get(cacheKey(config2));
|
|
295
|
+
if (entry && entry.expires > Date.now()) return entry.value;
|
|
296
|
+
}
|
|
297
|
+
if (config2.offlineQueue && typeof navigator !== "undefined" && navigator.onLine === false && method !== "GET") {
|
|
298
|
+
enqueueOffline(config2);
|
|
299
|
+
return {
|
|
300
|
+
data: null,
|
|
301
|
+
status: 0,
|
|
302
|
+
statusText: "offline-queued",
|
|
303
|
+
headers: new Headers(),
|
|
304
|
+
ok: true,
|
|
305
|
+
raw: new Response(null, { status: 202 }),
|
|
306
|
+
config: config2
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const headers = { ...config2.headers };
|
|
310
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
311
|
+
const token = csrfToken();
|
|
312
|
+
if (token && !headers[defaults.csrfHeader]) headers[defaults.csrfHeader] = token;
|
|
313
|
+
}
|
|
314
|
+
headers["X-Requested-With"] || (headers["X-Requested-With"] = "XMLHttpRequest");
|
|
315
|
+
const body = prepareBody(config2.body, headers);
|
|
316
|
+
const url = buildURL(config2);
|
|
317
|
+
const attempts = podeRepetir(method, config2, headers) ? (config2.retry ?? 0) + 1 : 1;
|
|
318
|
+
let lastError;
|
|
319
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
320
|
+
const controller = new AbortController();
|
|
321
|
+
const externo = config2.signal;
|
|
322
|
+
let repassarAborto = null;
|
|
323
|
+
if (externo) {
|
|
324
|
+
if (externo.aborted) {
|
|
325
|
+
controller.abort(externo.reason);
|
|
326
|
+
} else {
|
|
327
|
+
repassarAborto = () => controller.abort(externo.reason);
|
|
328
|
+
externo.addEventListener("abort", repassarAborto, { once: true });
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const soltarSinal = () => {
|
|
332
|
+
if (repassarAborto && externo) externo.removeEventListener("abort", repassarAborto);
|
|
333
|
+
repassarAborto = null;
|
|
334
|
+
};
|
|
335
|
+
const timeoutId = config2.timeout && config2.timeout > 0 ? setTimeout(() => controller.abort(new DOMException("timeout", "TimeoutError")), config2.timeout) : null;
|
|
336
|
+
try {
|
|
337
|
+
const response = await fetch(url, {
|
|
338
|
+
method,
|
|
339
|
+
headers,
|
|
340
|
+
body: method === "GET" || method === "HEAD" ? void 0 : body,
|
|
341
|
+
credentials: config2.credentials,
|
|
342
|
+
signal: controller.signal
|
|
343
|
+
});
|
|
344
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
345
|
+
soltarSinal();
|
|
346
|
+
const data = await parseResponse(response, config2.responseType);
|
|
347
|
+
let result = {
|
|
348
|
+
data,
|
|
349
|
+
status: response.status,
|
|
350
|
+
statusText: response.statusText,
|
|
351
|
+
headers: response.headers,
|
|
352
|
+
ok: response.ok,
|
|
353
|
+
raw: response,
|
|
354
|
+
config: config2
|
|
355
|
+
};
|
|
356
|
+
if (!response.ok) {
|
|
357
|
+
if (response.status >= 500 && attempt < attempts - 1) {
|
|
358
|
+
await wait((config2.retryDelay ?? 500) * 2 ** attempt);
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
const error2 = new HttpError(
|
|
362
|
+
`Request failed with status ${response.status}`,
|
|
363
|
+
result,
|
|
364
|
+
config2
|
|
365
|
+
);
|
|
366
|
+
for (const interceptor of errorInterceptors) interceptor(error2);
|
|
367
|
+
throw error2;
|
|
368
|
+
}
|
|
369
|
+
for (const interceptor of responseInterceptors) {
|
|
370
|
+
result = await interceptor(result);
|
|
371
|
+
}
|
|
372
|
+
if (config2.cache && method === "GET") {
|
|
373
|
+
responseCache.set(cacheKey(config2), {
|
|
374
|
+
expires: Date.now() + config2.cache,
|
|
375
|
+
value: result
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
return result;
|
|
379
|
+
} catch (err) {
|
|
380
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
381
|
+
soltarSinal();
|
|
382
|
+
if (err instanceof HttpError) throw err;
|
|
383
|
+
lastError = err;
|
|
384
|
+
const aborted = err?.name === "AbortError" && config2.signal?.aborted;
|
|
385
|
+
if (aborted) break;
|
|
386
|
+
if (attempt < attempts - 1) {
|
|
387
|
+
await wait((config2.retryDelay ?? 500) * 2 ** attempt);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
const message = lastError?.name === "TimeoutError" ? `Timeout after ${config2.timeout}ms` : `Network failure accessing ${url}`;
|
|
393
|
+
const error = new HttpError(message, void 0, config2, lastError);
|
|
394
|
+
for (const interceptor of errorInterceptors) interceptor(error);
|
|
395
|
+
throw error;
|
|
396
|
+
}
|
|
397
|
+
function wait(ms) {
|
|
398
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
399
|
+
}
|
|
400
|
+
async function shortcut(config2) {
|
|
401
|
+
const response = await request(config2);
|
|
402
|
+
return response.data;
|
|
403
|
+
}
|
|
404
|
+
var http = {
|
|
405
|
+
defaults,
|
|
406
|
+
get(url, options = {}) {
|
|
407
|
+
return shortcut({ ...options, url, method: "GET" });
|
|
408
|
+
},
|
|
409
|
+
post(url, body, options = {}) {
|
|
410
|
+
return shortcut({ ...options, url, method: "POST", body });
|
|
411
|
+
},
|
|
412
|
+
put(url, body, options = {}) {
|
|
413
|
+
return shortcut({ ...options, url, method: "PUT", body });
|
|
414
|
+
},
|
|
415
|
+
patch(url, body, options = {}) {
|
|
416
|
+
return shortcut({ ...options, url, method: "PATCH", body });
|
|
417
|
+
},
|
|
418
|
+
delete(url, options = {}) {
|
|
419
|
+
return shortcut({ ...options, url, method: "DELETE" });
|
|
420
|
+
},
|
|
421
|
+
head(url, options = {}) {
|
|
422
|
+
return shortcut({ ...options, url, method: "HEAD" });
|
|
423
|
+
},
|
|
424
|
+
/** Full request with status and headers. */
|
|
425
|
+
request,
|
|
426
|
+
/** Upload files with real progress using XMLHttpRequest. */
|
|
427
|
+
upload(url, data, options = {}) {
|
|
428
|
+
return new Promise((resolve, reject) => {
|
|
429
|
+
const xhr = new XMLHttpRequest();
|
|
430
|
+
const finalUrl = buildURL({ url });
|
|
431
|
+
xhr.open(options.method ?? "POST", finalUrl);
|
|
432
|
+
for (const [key, value] of Object.entries({ ...defaults.headers, ...options.headers })) {
|
|
433
|
+
if (key.toLowerCase() === "content-type") continue;
|
|
434
|
+
xhr.setRequestHeader(key, value);
|
|
435
|
+
}
|
|
436
|
+
const token = csrfToken();
|
|
437
|
+
if (token) xhr.setRequestHeader(defaults.csrfHeader, token);
|
|
438
|
+
xhr.upload.addEventListener("progress", (event) => {
|
|
439
|
+
if (!event.lengthComputable) return;
|
|
440
|
+
options.onProgress?.(
|
|
441
|
+
Math.round(event.loaded / event.total * 100),
|
|
442
|
+
event.loaded,
|
|
443
|
+
event.total
|
|
444
|
+
);
|
|
445
|
+
});
|
|
446
|
+
xhr.addEventListener("load", () => {
|
|
447
|
+
const contentType = xhr.getResponseHeader("content-type") || "";
|
|
448
|
+
let data2 = xhr.responseText;
|
|
449
|
+
if (contentType.includes("json")) {
|
|
450
|
+
try {
|
|
451
|
+
data2 = JSON.parse(xhr.responseText);
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (xhr.status >= 200 && xhr.status < 300) resolve(data2);
|
|
456
|
+
else reject(new HttpError(`Upload failed with status ${xhr.status}`));
|
|
457
|
+
});
|
|
458
|
+
xhr.addEventListener("error", () => reject(new HttpError("Network failure during upload")));
|
|
459
|
+
xhr.addEventListener("abort", () => reject(new HttpError("Upload canceled")));
|
|
460
|
+
options.signal?.addEventListener("abort", () => xhr.abort());
|
|
461
|
+
xhr.send(data);
|
|
462
|
+
});
|
|
463
|
+
},
|
|
464
|
+
/** Server-Sent Events with automatic reconnection by the browser. */
|
|
465
|
+
sse(url, handlers = {}) {
|
|
466
|
+
const source = new EventSource(buildURL({ url }));
|
|
467
|
+
source.addEventListener("message", (event) => {
|
|
468
|
+
let data = event.data;
|
|
469
|
+
try {
|
|
470
|
+
data = JSON.parse(event.data);
|
|
471
|
+
} catch {
|
|
472
|
+
}
|
|
473
|
+
handlers.message?.(data, event);
|
|
474
|
+
});
|
|
475
|
+
if (handlers.error) source.addEventListener("error", handlers.error);
|
|
476
|
+
return source;
|
|
477
|
+
},
|
|
478
|
+
/** Read a streaming response line by line (NDJSON). */
|
|
479
|
+
async stream(url, onLine, options = {}) {
|
|
480
|
+
const response = await fetch(buildURL({ url, params: options.params }), {
|
|
481
|
+
headers: { ...defaults.headers, ...options.headers },
|
|
482
|
+
signal: options.signal
|
|
483
|
+
});
|
|
484
|
+
if (!response.body) return;
|
|
485
|
+
const reader = response.body.getReader();
|
|
486
|
+
const decoder = new TextDecoder();
|
|
487
|
+
let buffer = "";
|
|
488
|
+
for (; ; ) {
|
|
489
|
+
const { done, value } = await reader.read();
|
|
490
|
+
if (done) break;
|
|
491
|
+
buffer += decoder.decode(value, { stream: true });
|
|
492
|
+
const lines = buffer.split("\n");
|
|
493
|
+
buffer = lines.pop() ?? "";
|
|
494
|
+
for (const line of lines) if (line.trim()) onLine(line);
|
|
495
|
+
}
|
|
496
|
+
if (buffer.trim()) onLine(buffer);
|
|
497
|
+
},
|
|
498
|
+
interceptors: {
|
|
499
|
+
request: {
|
|
500
|
+
use(fn) {
|
|
501
|
+
requestInterceptors.push(fn);
|
|
502
|
+
return () => {
|
|
503
|
+
const i = requestInterceptors.indexOf(fn);
|
|
504
|
+
if (i > -1) requestInterceptors.splice(i, 1);
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
response: {
|
|
509
|
+
use(fn) {
|
|
510
|
+
responseInterceptors.push(fn);
|
|
511
|
+
return () => {
|
|
512
|
+
const i = responseInterceptors.indexOf(fn);
|
|
513
|
+
if (i > -1) responseInterceptors.splice(i, 1);
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
error: {
|
|
518
|
+
use(fn) {
|
|
519
|
+
errorInterceptors.push(fn);
|
|
520
|
+
return () => {
|
|
521
|
+
const i = errorInterceptors.indexOf(fn);
|
|
522
|
+
if (i > -1) errorInterceptors.splice(i, 1);
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
/** Set headers sent on every request. */
|
|
528
|
+
setHeader(name, value) {
|
|
529
|
+
if (value === null) delete defaults.headers[name];
|
|
530
|
+
else defaults.headers[name] = value;
|
|
531
|
+
},
|
|
532
|
+
/** Shortcut for token-based authentication. */
|
|
533
|
+
setToken(token, scheme = "Bearer") {
|
|
534
|
+
this.setHeader("Authorization", token ? `${scheme} ${token}` : null);
|
|
535
|
+
},
|
|
536
|
+
setBaseURL(url) {
|
|
537
|
+
defaults.baseURL = url;
|
|
538
|
+
},
|
|
539
|
+
clearCache,
|
|
540
|
+
flushOfflineQueue,
|
|
541
|
+
parseDuration
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
// src/gpu/types.ts
|
|
545
|
+
var BUFFER_USAGE = {
|
|
546
|
+
MAP_READ: 1,
|
|
547
|
+
MAP_WRITE: 2,
|
|
548
|
+
COPY_SRC: 4,
|
|
549
|
+
COPY_DST: 8,
|
|
550
|
+
UNIFORM: 64,
|
|
551
|
+
STORAGE: 128
|
|
552
|
+
};
|
|
553
|
+
var TEXTURE_USAGE = {
|
|
554
|
+
COPY_SRC: 1,
|
|
555
|
+
COPY_DST: 2,
|
|
556
|
+
TEXTURE_BINDING: 4,
|
|
557
|
+
STORAGE_BINDING: 8,
|
|
558
|
+
RENDER_ATTACHMENT: 16
|
|
559
|
+
};
|
|
560
|
+
var SHADER_STAGE = {
|
|
561
|
+
VERTEX: 1,
|
|
562
|
+
FRAGMENT: 2,
|
|
563
|
+
COMPUTE: 4
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// src/gpu/wgsl.ts
|
|
567
|
+
function stripWgslComments(source) {
|
|
568
|
+
let out = "";
|
|
569
|
+
let depth = 0;
|
|
570
|
+
let line = false;
|
|
571
|
+
for (let i = 0; i < source.length; i++) {
|
|
572
|
+
const ch = source[i];
|
|
573
|
+
const next = source[i + 1];
|
|
574
|
+
if (line) {
|
|
575
|
+
if (ch === "\n") {
|
|
576
|
+
line = false;
|
|
577
|
+
out += ch;
|
|
578
|
+
} else out += " ";
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (depth > 0) {
|
|
582
|
+
if (ch === "/" && next === "*") {
|
|
583
|
+
depth++;
|
|
584
|
+
out += " ";
|
|
585
|
+
i++;
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
if (ch === "*" && next === "/") {
|
|
589
|
+
depth--;
|
|
590
|
+
out += " ";
|
|
591
|
+
i++;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
out += ch === "\n" ? ch : " ";
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
if (ch === "/" && next === "/") {
|
|
598
|
+
line = true;
|
|
599
|
+
out += " ";
|
|
600
|
+
i++;
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
if (ch === "/" && next === "*") {
|
|
604
|
+
depth = 1;
|
|
605
|
+
out += " ";
|
|
606
|
+
i++;
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
out += ch;
|
|
610
|
+
}
|
|
611
|
+
return out;
|
|
612
|
+
}
|
|
613
|
+
var SCALAR_SIZE = { f32: 4, i32: 4, u32: 4, f16: 2, bool: 4 };
|
|
614
|
+
function roundUp(value, align) {
|
|
615
|
+
if (align <= 0) return value;
|
|
616
|
+
return Math.ceil(value / align) * align;
|
|
617
|
+
}
|
|
618
|
+
function splitGenerics(text) {
|
|
619
|
+
const open = text.indexOf("<");
|
|
620
|
+
if (open < 0) return { base: text.trim(), args: [] };
|
|
621
|
+
const base = text.slice(0, open).trim();
|
|
622
|
+
const inner = text.slice(open + 1, text.lastIndexOf(">"));
|
|
623
|
+
return { base, args: splitTopLevel(inner) };
|
|
624
|
+
}
|
|
625
|
+
function splitTopLevel(text) {
|
|
626
|
+
const out = [];
|
|
627
|
+
let depth = 0;
|
|
628
|
+
let current = "";
|
|
629
|
+
for (const ch of text) {
|
|
630
|
+
if (ch === "<" || ch === "(") depth++;
|
|
631
|
+
else if (ch === ">" || ch === ")") depth--;
|
|
632
|
+
if (ch === "," && depth === 0) {
|
|
633
|
+
out.push(current.trim());
|
|
634
|
+
current = "";
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
current += ch;
|
|
638
|
+
}
|
|
639
|
+
if (current.trim()) out.push(current.trim());
|
|
640
|
+
return out;
|
|
641
|
+
}
|
|
642
|
+
var UNKNOWN_TYPE = {
|
|
643
|
+
text: "",
|
|
644
|
+
kind: "unknown",
|
|
645
|
+
scalar: "f32",
|
|
646
|
+
size: 0,
|
|
647
|
+
align: 1,
|
|
648
|
+
components: 0
|
|
649
|
+
};
|
|
650
|
+
function describeWgslType(text, structs = {}) {
|
|
651
|
+
const clean = text.trim();
|
|
652
|
+
if (!clean) return { ...UNKNOWN_TYPE };
|
|
653
|
+
if (clean in SCALAR_SIZE) {
|
|
654
|
+
const size = SCALAR_SIZE[clean];
|
|
655
|
+
return {
|
|
656
|
+
text: clean,
|
|
657
|
+
kind: "scalar",
|
|
658
|
+
scalar: clean,
|
|
659
|
+
size,
|
|
660
|
+
align: size,
|
|
661
|
+
components: 1
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
const { base, args } = splitGenerics(clean);
|
|
665
|
+
const shortVector = /^vec([234])([fiuh])$/.exec(base);
|
|
666
|
+
if (shortVector) {
|
|
667
|
+
return describeWgslType(`vec${shortVector[1]}<${expandShort(shortVector[2])}>`, structs);
|
|
668
|
+
}
|
|
669
|
+
const shortMatrix = /^mat([234])x([234])([fh])$/.exec(base);
|
|
670
|
+
if (shortMatrix) {
|
|
671
|
+
return describeWgslType(
|
|
672
|
+
`mat${shortMatrix[1]}x${shortMatrix[2]}<${expandShort(shortMatrix[3])}>`,
|
|
673
|
+
structs
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
const vector = /^vec([234])$/.exec(base);
|
|
677
|
+
if (vector) {
|
|
678
|
+
const n = Number(vector[1]);
|
|
679
|
+
const scalar = args[0] ?? "f32";
|
|
680
|
+
const unit = SCALAR_SIZE[scalar] ?? 4;
|
|
681
|
+
return {
|
|
682
|
+
text: clean,
|
|
683
|
+
kind: "vector",
|
|
684
|
+
scalar,
|
|
685
|
+
size: n * unit,
|
|
686
|
+
// vec3 aligns like vec4: the classic gotcha when writing offsets by hand.
|
|
687
|
+
align: (n === 3 ? 4 : n) * unit,
|
|
688
|
+
components: n
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
const matrix = /^mat([234])x([234])$/.exec(base);
|
|
692
|
+
if (matrix) {
|
|
693
|
+
const columns = Number(matrix[1]);
|
|
694
|
+
const rows = Number(matrix[2]);
|
|
695
|
+
const scalar = args[0] ?? "f32";
|
|
696
|
+
const column = describeWgslType(`vec${rows}<${scalar}>`, structs);
|
|
697
|
+
return {
|
|
698
|
+
text: clean,
|
|
699
|
+
kind: "matrix",
|
|
700
|
+
scalar,
|
|
701
|
+
size: columns * column.align,
|
|
702
|
+
align: column.align,
|
|
703
|
+
components: columns * rows,
|
|
704
|
+
columns,
|
|
705
|
+
rows,
|
|
706
|
+
stride: column.align
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
if (base === "array") {
|
|
710
|
+
const element = describeWgslType(args[0] ?? "f32", structs);
|
|
711
|
+
const count = args[1] ? Number(args[1].replace(/[^\d]/g, "")) : 0;
|
|
712
|
+
const stride = roundUp(roundUp(element.size, element.align), 16);
|
|
713
|
+
return {
|
|
714
|
+
text: clean,
|
|
715
|
+
kind: "array",
|
|
716
|
+
scalar: element.scalar,
|
|
717
|
+
size: count > 0 ? stride * count : 0,
|
|
718
|
+
align: Math.max(element.align, 16),
|
|
719
|
+
components: count * element.components,
|
|
720
|
+
stride,
|
|
721
|
+
count,
|
|
722
|
+
element
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
const struct = structs[base];
|
|
726
|
+
if (struct) {
|
|
727
|
+
return {
|
|
728
|
+
text: clean,
|
|
729
|
+
kind: "struct",
|
|
730
|
+
scalar: "f32",
|
|
731
|
+
size: struct.size,
|
|
732
|
+
align: struct.align,
|
|
733
|
+
components: struct.fields.reduce((total, field) => total + field.type.components, 0),
|
|
734
|
+
struct: base
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
return { ...UNKNOWN_TYPE, text: clean };
|
|
738
|
+
}
|
|
739
|
+
function expandShort(letter) {
|
|
740
|
+
if (letter === "i") return "i32";
|
|
741
|
+
if (letter === "u") return "u32";
|
|
742
|
+
if (letter === "h") return "f16";
|
|
743
|
+
return "f32";
|
|
744
|
+
}
|
|
745
|
+
var STRUCT_RE = /\bstruct\s+([A-Za-z_]\w*)\s*\{([^}]*)\}/g;
|
|
746
|
+
var MEMBER_RE = /^(?:@\w+\s*(?:\([^)]*\)\s*)?)*([A-Za-z_]\w*)\s*:\s*([\s\S]+)$/;
|
|
747
|
+
function reflectStructs(source) {
|
|
748
|
+
const bodies = [];
|
|
749
|
+
STRUCT_RE.lastIndex = 0;
|
|
750
|
+
let match;
|
|
751
|
+
while ((match = STRUCT_RE.exec(source)) !== null) {
|
|
752
|
+
bodies.push({ name: match[1], body: match[2] });
|
|
753
|
+
}
|
|
754
|
+
const structs = {};
|
|
755
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
756
|
+
for (const { name, body } of bodies) {
|
|
757
|
+
structs[name] = layoutStruct(name, body, structs);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return structs;
|
|
761
|
+
}
|
|
762
|
+
function layoutStruct(name, body, structs) {
|
|
763
|
+
const fields = [];
|
|
764
|
+
let offset = 0;
|
|
765
|
+
let align = 1;
|
|
766
|
+
for (const raw of splitTopLevel(body.replace(/;/g, ","))) {
|
|
767
|
+
const parsed = MEMBER_RE.exec(raw.trim());
|
|
768
|
+
if (!parsed) continue;
|
|
769
|
+
const type = describeWgslType(parsed[2], structs);
|
|
770
|
+
if (type.kind === "unknown") continue;
|
|
771
|
+
offset = roundUp(offset, type.align);
|
|
772
|
+
fields.push({ name: parsed[1], type, offset });
|
|
773
|
+
offset += type.size;
|
|
774
|
+
align = Math.max(align, type.align);
|
|
775
|
+
}
|
|
776
|
+
const finalAlign = Math.max(align, 16);
|
|
777
|
+
return { name, fields, align: finalAlign, size: roundUp(offset, finalAlign) };
|
|
778
|
+
}
|
|
779
|
+
var BINDING_RE = /@(?:group|binding)\s*\(\s*\d+\s*\)\s*@(?:group|binding)\s*\(\s*\d+\s*\)\s*var(?:\s*<([^>]*)>)?\s+([A-Za-z_]\w*)\s*:\s*([^;]+);/g;
|
|
780
|
+
var GROUP_RE = /@group\s*\(\s*(\d+)\s*\)/;
|
|
781
|
+
var BINDING_INDEX_RE = /@binding\s*\(\s*(\d+)\s*\)/;
|
|
782
|
+
function reflectBindings(source, structs) {
|
|
783
|
+
const out = [];
|
|
784
|
+
BINDING_RE.lastIndex = 0;
|
|
785
|
+
let match;
|
|
786
|
+
while ((match = BINDING_RE.exec(source)) !== null) {
|
|
787
|
+
const head = match[0];
|
|
788
|
+
const group = Number(GROUP_RE.exec(head)?.[1] ?? 0);
|
|
789
|
+
const binding = Number(BINDING_INDEX_RE.exec(head)?.[1] ?? 0);
|
|
790
|
+
const space = splitTopLevel(match[1] ?? "");
|
|
791
|
+
const name = match[2];
|
|
792
|
+
const typeText = match[3].trim();
|
|
793
|
+
out.push(describeBinding(group, binding, space, name, typeText, structs));
|
|
794
|
+
}
|
|
795
|
+
return out.sort((a, b) => a.group - b.group || a.binding - b.binding);
|
|
796
|
+
}
|
|
797
|
+
function describeBinding(group, binding, space, name, typeText, structs) {
|
|
798
|
+
const address = space[0] ?? "";
|
|
799
|
+
const accessWord = space[1] ?? "";
|
|
800
|
+
const access = accessWord === "read_write" ? "read-write" : accessWord === "write" ? "write" : "read";
|
|
801
|
+
if (address === "uniform") {
|
|
802
|
+
const { base } = splitGenerics(typeText);
|
|
803
|
+
return {
|
|
804
|
+
group,
|
|
805
|
+
binding,
|
|
806
|
+
name,
|
|
807
|
+
kind: "uniform",
|
|
808
|
+
typeText,
|
|
809
|
+
access: "read",
|
|
810
|
+
struct: structs[base]
|
|
811
|
+
};
|
|
812
|
+
}
|
|
813
|
+
if (address === "storage") {
|
|
814
|
+
const { base } = splitGenerics(typeText);
|
|
815
|
+
return {
|
|
816
|
+
group,
|
|
817
|
+
binding,
|
|
818
|
+
name,
|
|
819
|
+
kind: "storage",
|
|
820
|
+
typeText,
|
|
821
|
+
access,
|
|
822
|
+
struct: structs[base]
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
if (typeText.startsWith("sampler")) {
|
|
826
|
+
return {
|
|
827
|
+
group,
|
|
828
|
+
binding,
|
|
829
|
+
name,
|
|
830
|
+
kind: "sampler",
|
|
831
|
+
typeText,
|
|
832
|
+
access: "read",
|
|
833
|
+
comparison: typeText.startsWith("sampler_comparison")
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
if (typeText.startsWith("texture_storage")) {
|
|
837
|
+
const { base, args } = splitGenerics(typeText);
|
|
838
|
+
return {
|
|
839
|
+
group,
|
|
840
|
+
binding,
|
|
841
|
+
name,
|
|
842
|
+
kind: "storage-texture",
|
|
843
|
+
typeText,
|
|
844
|
+
access: (args[1] ?? "write") === "read_write" ? "read-write" : "write",
|
|
845
|
+
viewDimension: base.replace("texture_storage_", ""),
|
|
846
|
+
sampleType: args[0]
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
if (typeText.startsWith("texture_")) {
|
|
850
|
+
const { base, args } = splitGenerics(typeText);
|
|
851
|
+
const dimension = base.replace("texture_multisampled_", "").replace("texture_depth_multisampled_", "").replace("texture_depth_", "").replace("texture_", "");
|
|
852
|
+
const depth = base.includes("depth");
|
|
853
|
+
const scalar = args[0] ?? "f32";
|
|
854
|
+
return {
|
|
855
|
+
group,
|
|
856
|
+
binding,
|
|
857
|
+
name,
|
|
858
|
+
kind: "texture",
|
|
859
|
+
typeText,
|
|
860
|
+
access: "read",
|
|
861
|
+
multisampled: base.includes("multisampled"),
|
|
862
|
+
comparison: depth,
|
|
863
|
+
viewDimension: dimension === "cube_array" ? "cube-array" : dimension.replace("_array", "-array"),
|
|
864
|
+
sampleType: depth ? "depth" : scalar === "i32" ? "sint" : scalar === "u32" ? "uint" : "float"
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
return { group, binding, name, kind: "unknown", typeText, access };
|
|
868
|
+
}
|
|
869
|
+
var ENTRY_RE = /@(vertex|fragment|compute)\s*((?:@\w+\s*(?:\([^)]*\)\s*)?)*)fn\s+([A-Za-z_]\w*)/g;
|
|
870
|
+
var WORKGROUP_RE = /@workgroup_size\s*\(([^)]*)\)/;
|
|
871
|
+
function reflectEntries(source) {
|
|
872
|
+
const out = [];
|
|
873
|
+
ENTRY_RE.lastIndex = 0;
|
|
874
|
+
let match;
|
|
875
|
+
while ((match = ENTRY_RE.exec(source)) !== null) {
|
|
876
|
+
const stage = match[1];
|
|
877
|
+
const entry = { stage, name: match[3] };
|
|
878
|
+
if (stage === "compute") {
|
|
879
|
+
const around = source.slice(Math.max(0, match.index - 120), match.index + match[0].length);
|
|
880
|
+
const sizes = WORKGROUP_RE.exec(around)?.[1];
|
|
881
|
+
const parts = sizes ? splitTopLevel(sizes).map((n) => Number(n) || 1) : [];
|
|
882
|
+
entry.workgroupSize = [parts[0] ?? 1, parts[1] ?? 1, parts[2] ?? 1];
|
|
883
|
+
}
|
|
884
|
+
out.push(entry);
|
|
885
|
+
}
|
|
886
|
+
return out;
|
|
887
|
+
}
|
|
888
|
+
function reflectWgsl(source) {
|
|
889
|
+
if (typeof source !== "string" || !source.trim()) {
|
|
890
|
+
return { structs: {}, bindings: [], entries: [] };
|
|
891
|
+
}
|
|
892
|
+
const clean = stripWgslComments(source);
|
|
893
|
+
const structs = reflectStructs(clean);
|
|
894
|
+
const bindings = reflectBindings(clean, structs);
|
|
895
|
+
const entries = reflectEntries(clean);
|
|
896
|
+
return {
|
|
897
|
+
structs,
|
|
898
|
+
bindings,
|
|
899
|
+
entries,
|
|
900
|
+
uniform: bindings.find((b) => b.kind === "uniform" && b.struct)
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
function findEntry(reflection, stage) {
|
|
904
|
+
return reflection.entries.find((entry) => entry.stage === stage);
|
|
905
|
+
}
|
|
906
|
+
function guessType(value) {
|
|
907
|
+
if (typeof value === "number") return "f32";
|
|
908
|
+
if (typeof value === "boolean") return "f32";
|
|
909
|
+
if (typeof value === "string") return parseColor(value) ? "vec4<f32>" : null;
|
|
910
|
+
if (Array.isArray(value)) {
|
|
911
|
+
if (value.length >= 2 && value.length <= 4 && value.every((v) => typeof v === "number")) {
|
|
912
|
+
return `vec${value.length}<f32>`;
|
|
913
|
+
}
|
|
914
|
+
if (value.length === 16) return "mat4x4<f32>";
|
|
915
|
+
if (value.length === 9) return "mat3x3<f32>";
|
|
916
|
+
}
|
|
917
|
+
return null;
|
|
918
|
+
}
|
|
919
|
+
function inferStruct(values, name = "Uniforms") {
|
|
920
|
+
const fields = [];
|
|
921
|
+
let offset = 0;
|
|
922
|
+
let align = 1;
|
|
923
|
+
for (const [key, value] of Object.entries(values)) {
|
|
924
|
+
const text = guessType(value);
|
|
925
|
+
if (!text) continue;
|
|
926
|
+
const type = describeWgslType(text);
|
|
927
|
+
offset = roundUp(offset, type.align);
|
|
928
|
+
fields.push({ name: key, type, offset });
|
|
929
|
+
offset += type.size;
|
|
930
|
+
align = Math.max(align, type.align);
|
|
931
|
+
}
|
|
932
|
+
const finalAlign = Math.max(align, 16);
|
|
933
|
+
return { name, fields, align: finalAlign, size: roundUp(offset, finalAlign) };
|
|
934
|
+
}
|
|
935
|
+
var HEX = /^#([0-9a-f]{3,8})$/i;
|
|
936
|
+
function parseColor(text) {
|
|
937
|
+
const match = HEX.exec(text.trim());
|
|
938
|
+
if (!match) return null;
|
|
939
|
+
let digits = match[1];
|
|
940
|
+
if (digits.length === 3 || digits.length === 4) {
|
|
941
|
+
digits = digits.split("").map((ch) => ch + ch).join("");
|
|
942
|
+
}
|
|
943
|
+
if (digits.length !== 6 && digits.length !== 8) return null;
|
|
944
|
+
const value = parseInt(digits.slice(0, 6), 16);
|
|
945
|
+
const alpha = digits.length === 8 ? parseInt(digits.slice(6, 8), 16) / 255 : 1;
|
|
946
|
+
return [(value >> 16 & 255) / 255, (value >> 8 & 255) / 255, (value & 255) / 255, alpha];
|
|
947
|
+
}
|
|
948
|
+
function flattenValue(value, components2) {
|
|
949
|
+
if (typeof value === "number") {
|
|
950
|
+
return new Array(components2).fill(value);
|
|
951
|
+
}
|
|
952
|
+
if (typeof value === "boolean") return new Array(components2).fill(value ? 1 : 0);
|
|
953
|
+
if (typeof value === "string") {
|
|
954
|
+
const color = parseColor(value);
|
|
955
|
+
if (!color) return [];
|
|
956
|
+
return color.slice(0, Math.max(1, components2));
|
|
957
|
+
}
|
|
958
|
+
if (Array.isArray(value)) {
|
|
959
|
+
const out = [];
|
|
960
|
+
for (const item of value) {
|
|
961
|
+
if (typeof item === "number") out.push(item);
|
|
962
|
+
else if (typeof item === "boolean") out.push(item ? 1 : 0);
|
|
963
|
+
else out.push(...flattenValue(item, 1));
|
|
964
|
+
}
|
|
965
|
+
return out;
|
|
966
|
+
}
|
|
967
|
+
if (value && typeof value === "object") {
|
|
968
|
+
const record = value;
|
|
969
|
+
const keys = ["x", "y", "z", "w"];
|
|
970
|
+
const alt = ["r", "g", "b", "a"];
|
|
971
|
+
const out = [];
|
|
972
|
+
for (let i = 0; i < components2; i++) {
|
|
973
|
+
const found = record[keys[i]] ?? record[alt[i]];
|
|
974
|
+
if (typeof found === "number") out.push(found);
|
|
975
|
+
}
|
|
976
|
+
return out;
|
|
977
|
+
}
|
|
978
|
+
return [];
|
|
979
|
+
}
|
|
980
|
+
function writeField(view, field, value) {
|
|
981
|
+
const { type, offset } = field;
|
|
982
|
+
const numbers = flattenValue(value, type.components);
|
|
983
|
+
if (numbers.length === 0) return false;
|
|
984
|
+
const little = true;
|
|
985
|
+
const put = (at, n) => {
|
|
986
|
+
if (at + 4 > view.byteLength) return;
|
|
987
|
+
if (type.scalar === "i32") view.setInt32(at, Math.trunc(n), little);
|
|
988
|
+
else if (type.scalar === "u32") view.setUint32(at, Math.max(0, Math.trunc(n)), little);
|
|
989
|
+
else view.setFloat32(at, n, little);
|
|
990
|
+
};
|
|
991
|
+
if (type.kind === "matrix" && type.columns && type.rows && type.stride) {
|
|
992
|
+
const unit2 = SCALAR_SIZE[type.scalar] ?? 4;
|
|
993
|
+
for (let column = 0; column < type.columns; column++) {
|
|
994
|
+
for (let row = 0; row < type.rows; row++) {
|
|
995
|
+
const n = numbers[column * type.rows + row];
|
|
996
|
+
if (n === void 0) continue;
|
|
997
|
+
put(offset + column * type.stride + row * unit2, n);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return true;
|
|
1001
|
+
}
|
|
1002
|
+
if (type.kind === "array" && type.element && type.stride) {
|
|
1003
|
+
const unit2 = SCALAR_SIZE[type.element.scalar] ?? 4;
|
|
1004
|
+
const per = Math.max(1, type.element.components);
|
|
1005
|
+
const total = type.count ?? Math.ceil(numbers.length / per);
|
|
1006
|
+
for (let i = 0; i < total; i++) {
|
|
1007
|
+
for (let c = 0; c < per; c++) {
|
|
1008
|
+
const n = numbers[i * per + c];
|
|
1009
|
+
if (n === void 0) continue;
|
|
1010
|
+
put(offset + i * type.stride + c * unit2, n);
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
1015
|
+
const unit = SCALAR_SIZE[type.scalar] ?? 4;
|
|
1016
|
+
for (let i = 0; i < Math.min(numbers.length, Math.max(1, type.components)); i++) {
|
|
1017
|
+
put(offset + i * unit, numbers[i]);
|
|
1018
|
+
}
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
function writeStruct(buffer, struct, values) {
|
|
1022
|
+
const view = new DataView(buffer);
|
|
1023
|
+
const written = [];
|
|
1024
|
+
for (const field of struct.fields) {
|
|
1025
|
+
if (!(field.name in values)) continue;
|
|
1026
|
+
const value = values[field.name];
|
|
1027
|
+
if (value === void 0 || value === null) continue;
|
|
1028
|
+
if (writeField(view, field, value)) written.push(field.name);
|
|
1029
|
+
}
|
|
1030
|
+
return written;
|
|
1031
|
+
}
|
|
1032
|
+
function packStruct(struct, values = {}) {
|
|
1033
|
+
const buffer = new ArrayBuffer(Math.max(16, struct.size));
|
|
1034
|
+
writeStruct(buffer, struct, values);
|
|
1035
|
+
return buffer;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/gpu/index.ts
|
|
1039
|
+
function supported() {
|
|
1040
|
+
try {
|
|
1041
|
+
return typeof navigator !== "undefined" && !!navigator.gpu;
|
|
1042
|
+
} catch {
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
function navigatorGpu() {
|
|
1047
|
+
if (!supported()) return null;
|
|
1048
|
+
return navigator.gpu;
|
|
1049
|
+
}
|
|
1050
|
+
async function init(options = {}) {
|
|
1051
|
+
const api = navigatorGpu();
|
|
1052
|
+
if (!api) return null;
|
|
1053
|
+
try {
|
|
1054
|
+
const adapter = await api.requestAdapter(
|
|
1055
|
+
options.powerPreference ? { powerPreference: options.powerPreference } : void 0
|
|
1056
|
+
);
|
|
1057
|
+
if (!adapter) return null;
|
|
1058
|
+
const features = (options.features ?? []).filter((name) => adapter.features.has(name));
|
|
1059
|
+
const device = await adapter.requestDevice({
|
|
1060
|
+
label: options.label ?? "voodoo",
|
|
1061
|
+
requiredFeatures: features,
|
|
1062
|
+
requiredLimits: options.limits
|
|
1063
|
+
});
|
|
1064
|
+
const format = api.getPreferredCanvasFormat?.() ?? "bgra8unorm";
|
|
1065
|
+
const gpu2 = {
|
|
1066
|
+
adapter,
|
|
1067
|
+
device,
|
|
1068
|
+
queue: device.queue,
|
|
1069
|
+
format,
|
|
1070
|
+
resources: /* @__PURE__ */ new Set(),
|
|
1071
|
+
destroyed: false
|
|
1072
|
+
};
|
|
1073
|
+
device.lost?.then((info) => {
|
|
1074
|
+
gpu2.destroyed = true;
|
|
1075
|
+
warn(`WebGPU device was lost (${info.reason}): ${info.message}`);
|
|
1076
|
+
}).catch(() => void 0);
|
|
1077
|
+
return gpu2;
|
|
1078
|
+
} catch (err) {
|
|
1079
|
+
return null;
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function live(gpu2) {
|
|
1083
|
+
return !!gpu2 && !gpu2.destroyed;
|
|
1084
|
+
}
|
|
1085
|
+
function track(gpu2, resource) {
|
|
1086
|
+
gpu2.resources.add(resource);
|
|
1087
|
+
}
|
|
1088
|
+
function untrack(gpu2, resource) {
|
|
1089
|
+
gpu2?.resources.delete(resource);
|
|
1090
|
+
}
|
|
1091
|
+
var sharedContext = null;
|
|
1092
|
+
function shared(options) {
|
|
1093
|
+
if (!sharedContext) sharedContext = init(options);
|
|
1094
|
+
return sharedContext;
|
|
1095
|
+
}
|
|
1096
|
+
function resetShared() {
|
|
1097
|
+
sharedContext = null;
|
|
1098
|
+
}
|
|
1099
|
+
var NO_SURFACE = {
|
|
1100
|
+
canvas: null,
|
|
1101
|
+
format: "",
|
|
1102
|
+
width: 0,
|
|
1103
|
+
height: 0,
|
|
1104
|
+
view: () => null,
|
|
1105
|
+
resize: () => void 0,
|
|
1106
|
+
destroy: () => void 0
|
|
1107
|
+
};
|
|
1108
|
+
function surface(gpu2, canvas, options = {}) {
|
|
1109
|
+
if (!live(gpu2) || !canvas) return NO_SURFACE;
|
|
1110
|
+
const context = canvas.getContext("webgpu");
|
|
1111
|
+
if (!context) return NO_SURFACE;
|
|
1112
|
+
const [minDpr, maxDpr] = options.dpr ?? [1, 2];
|
|
1113
|
+
const format = options.format ?? gpu2.format;
|
|
1114
|
+
const alphaMode = options.alpha ? "premultiplied" : "opaque";
|
|
1115
|
+
const maxSize = gpu2.device.limits.maxTextureDimension2D || 4096;
|
|
1116
|
+
let width = 0;
|
|
1117
|
+
let height = 0;
|
|
1118
|
+
let observer = null;
|
|
1119
|
+
let alive = true;
|
|
1120
|
+
context.configure({ device: gpu2.device, format, alphaMode });
|
|
1121
|
+
const resize = () => {
|
|
1122
|
+
if (!alive) return;
|
|
1123
|
+
const ratio = typeof devicePixelRatio === "number" ? devicePixelRatio : 1;
|
|
1124
|
+
const dpr = Math.min(Math.max(ratio, minDpr), maxDpr);
|
|
1125
|
+
const rect = canvas.getBoundingClientRect();
|
|
1126
|
+
const cssWidth = rect.width || canvas.clientWidth || canvas.width || 300;
|
|
1127
|
+
const cssHeight = rect.height || canvas.clientHeight || canvas.height || 150;
|
|
1128
|
+
const next = {
|
|
1129
|
+
w: Math.max(1, Math.min(maxSize, Math.round(cssWidth * dpr))),
|
|
1130
|
+
h: Math.max(1, Math.min(maxSize, Math.round(cssHeight * dpr)))
|
|
1131
|
+
};
|
|
1132
|
+
if (next.w === width && next.h === height) return;
|
|
1133
|
+
width = next.w;
|
|
1134
|
+
height = next.h;
|
|
1135
|
+
canvas.width = width;
|
|
1136
|
+
canvas.height = height;
|
|
1137
|
+
context.configure({ device: gpu2.device, format, alphaMode });
|
|
1138
|
+
};
|
|
1139
|
+
resize();
|
|
1140
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
1141
|
+
observer = new ResizeObserver(() => resize());
|
|
1142
|
+
observer.observe(canvas);
|
|
1143
|
+
}
|
|
1144
|
+
const handle = {
|
|
1145
|
+
canvas,
|
|
1146
|
+
format,
|
|
1147
|
+
get width() {
|
|
1148
|
+
return width;
|
|
1149
|
+
},
|
|
1150
|
+
get height() {
|
|
1151
|
+
return height;
|
|
1152
|
+
},
|
|
1153
|
+
view() {
|
|
1154
|
+
if (!alive || !live(gpu2)) return null;
|
|
1155
|
+
try {
|
|
1156
|
+
return context.getCurrentTexture().createView();
|
|
1157
|
+
} catch (err) {
|
|
1158
|
+
handleError(err, "V.gpu.surface");
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
},
|
|
1162
|
+
resize,
|
|
1163
|
+
destroy() {
|
|
1164
|
+
if (!alive) return;
|
|
1165
|
+
alive = false;
|
|
1166
|
+
observer?.disconnect();
|
|
1167
|
+
observer = null;
|
|
1168
|
+
try {
|
|
1169
|
+
context.unconfigure();
|
|
1170
|
+
} catch {
|
|
1171
|
+
}
|
|
1172
|
+
untrack(gpu2, handle);
|
|
1173
|
+
}
|
|
1174
|
+
};
|
|
1175
|
+
track(gpu2, handle);
|
|
1176
|
+
return handle;
|
|
1177
|
+
}
|
|
1178
|
+
var NO_TARGET = {
|
|
1179
|
+
texture: null,
|
|
1180
|
+
width: 0,
|
|
1181
|
+
height: 0,
|
|
1182
|
+
format: "",
|
|
1183
|
+
view: () => null,
|
|
1184
|
+
destroy: () => void 0
|
|
1185
|
+
};
|
|
1186
|
+
function target(gpu2, options) {
|
|
1187
|
+
if (!live(gpu2)) return NO_TARGET;
|
|
1188
|
+
const format = options.format ?? gpu2.format;
|
|
1189
|
+
const width = Math.max(1, Math.round(options.width));
|
|
1190
|
+
const height = Math.max(1, Math.round(options.height));
|
|
1191
|
+
let texture = null;
|
|
1192
|
+
let view = null;
|
|
1193
|
+
try {
|
|
1194
|
+
texture = gpu2.device.createTexture({
|
|
1195
|
+
label: options.label ?? "voodoo-target",
|
|
1196
|
+
size: { width, height },
|
|
1197
|
+
format,
|
|
1198
|
+
usage: TEXTURE_USAGE.RENDER_ATTACHMENT | TEXTURE_USAGE.TEXTURE_BINDING | TEXTURE_USAGE.COPY_SRC
|
|
1199
|
+
});
|
|
1200
|
+
view = texture.createView();
|
|
1201
|
+
} catch (err) {
|
|
1202
|
+
handleError(err, "V.gpu.target");
|
|
1203
|
+
return NO_TARGET;
|
|
1204
|
+
}
|
|
1205
|
+
const handle = {
|
|
1206
|
+
texture,
|
|
1207
|
+
width,
|
|
1208
|
+
height,
|
|
1209
|
+
format,
|
|
1210
|
+
view: () => view,
|
|
1211
|
+
destroy() {
|
|
1212
|
+
if (!texture) return;
|
|
1213
|
+
texture.destroy();
|
|
1214
|
+
texture = null;
|
|
1215
|
+
view = null;
|
|
1216
|
+
untrack(gpu2, handle);
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
track(gpu2, handle);
|
|
1220
|
+
return handle;
|
|
1221
|
+
}
|
|
1222
|
+
var EMPTY_STRUCT = { name: "Uniforms", fields: [], size: 0, align: 16 };
|
|
1223
|
+
function noUniforms(struct = EMPTY_STRUCT) {
|
|
1224
|
+
return {
|
|
1225
|
+
struct,
|
|
1226
|
+
buffer: null,
|
|
1227
|
+
values: {},
|
|
1228
|
+
set: () => void 0,
|
|
1229
|
+
destroy: () => void 0
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
function uniformsFromStruct(gpu2, struct, initial = {}, label = "voodoo-uniforms") {
|
|
1233
|
+
if (!live(gpu2) || struct.fields.length === 0) return noUniforms(struct);
|
|
1234
|
+
const bytes = packStruct(struct, initial);
|
|
1235
|
+
const values = { ...initial };
|
|
1236
|
+
let buffer = null;
|
|
1237
|
+
try {
|
|
1238
|
+
buffer = gpu2.device.createBuffer({
|
|
1239
|
+
label,
|
|
1240
|
+
size: bytes.byteLength,
|
|
1241
|
+
usage: BUFFER_USAGE.UNIFORM | BUFFER_USAGE.COPY_DST
|
|
1242
|
+
});
|
|
1243
|
+
gpu2.queue.writeBuffer(buffer, 0, bytes);
|
|
1244
|
+
} catch (err) {
|
|
1245
|
+
handleError(err, "V.gpu.uniforms");
|
|
1246
|
+
return noUniforms(struct);
|
|
1247
|
+
}
|
|
1248
|
+
const handle = {
|
|
1249
|
+
struct,
|
|
1250
|
+
get buffer() {
|
|
1251
|
+
return buffer;
|
|
1252
|
+
},
|
|
1253
|
+
values,
|
|
1254
|
+
set(next) {
|
|
1255
|
+
if (!buffer || !live(gpu2) || !next) return;
|
|
1256
|
+
const written = writeStruct(bytes, struct, next);
|
|
1257
|
+
if (written.length === 0) return;
|
|
1258
|
+
for (const name of written) values[name] = next[name];
|
|
1259
|
+
gpu2.queue.writeBuffer(buffer, 0, bytes);
|
|
1260
|
+
},
|
|
1261
|
+
destroy() {
|
|
1262
|
+
if (!buffer) return;
|
|
1263
|
+
buffer.destroy();
|
|
1264
|
+
buffer = null;
|
|
1265
|
+
untrack(gpu2, handle);
|
|
1266
|
+
}
|
|
1267
|
+
};
|
|
1268
|
+
track(gpu2, handle);
|
|
1269
|
+
return handle;
|
|
1270
|
+
}
|
|
1271
|
+
function uniforms(gpu2, initial = {}) {
|
|
1272
|
+
return uniformsFromStruct(gpu2, inferStruct(initial), initial);
|
|
1273
|
+
}
|
|
1274
|
+
function clock(_gpu) {
|
|
1275
|
+
let start = -1;
|
|
1276
|
+
let previous = -1;
|
|
1277
|
+
let time = 0;
|
|
1278
|
+
let delta = 0;
|
|
1279
|
+
let frame2 = 0;
|
|
1280
|
+
return {
|
|
1281
|
+
get time() {
|
|
1282
|
+
return time;
|
|
1283
|
+
},
|
|
1284
|
+
get delta() {
|
|
1285
|
+
return delta;
|
|
1286
|
+
},
|
|
1287
|
+
get frame() {
|
|
1288
|
+
return frame2;
|
|
1289
|
+
},
|
|
1290
|
+
tick(now) {
|
|
1291
|
+
const stamp = now ?? (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
1292
|
+
if (start < 0) {
|
|
1293
|
+
start = stamp;
|
|
1294
|
+
previous = stamp;
|
|
1295
|
+
}
|
|
1296
|
+
time = (stamp - start) / 1e3;
|
|
1297
|
+
delta = Math.min(0.25, Math.max(0, (stamp - previous) / 1e3));
|
|
1298
|
+
previous = stamp;
|
|
1299
|
+
frame2 += 1;
|
|
1300
|
+
},
|
|
1301
|
+
reset() {
|
|
1302
|
+
start = -1;
|
|
1303
|
+
previous = -1;
|
|
1304
|
+
time = 0;
|
|
1305
|
+
delta = 0;
|
|
1306
|
+
frame2 = 0;
|
|
1307
|
+
}
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
var FULLSCREEN_VERTEX = `
|
|
1311
|
+
struct VoodooFullscreenOut {
|
|
1312
|
+
@builtin(position) position: vec4<f32>,
|
|
1313
|
+
@location(0) uv: vec2<f32>,
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
@vertex
|
|
1317
|
+
fn voodooFullscreen(@builtin(vertex_index) indice: u32) -> VoodooFullscreenOut {
|
|
1318
|
+
var cantos = array<vec2<f32>, 3>(
|
|
1319
|
+
vec2<f32>(-1.0, -1.0),
|
|
1320
|
+
vec2<f32>( 3.0, -1.0),
|
|
1321
|
+
vec2<f32>(-1.0, 3.0)
|
|
1322
|
+
);
|
|
1323
|
+
let p = cantos[indice];
|
|
1324
|
+
var saida: VoodooFullscreenOut;
|
|
1325
|
+
saida.position = vec4<f32>(p, 0.0, 1.0);
|
|
1326
|
+
saida.uv = vec2<f32>((p.x + 1.0) * 0.5, 1.0 - (p.y + 1.0) * 0.5);
|
|
1327
|
+
return saida;
|
|
1328
|
+
}
|
|
1329
|
+
`;
|
|
1330
|
+
function layoutEntries(bindings, visibility) {
|
|
1331
|
+
const entries = [];
|
|
1332
|
+
for (const binding of bindings) {
|
|
1333
|
+
if (binding.group !== 0) continue;
|
|
1334
|
+
const base = { binding: binding.binding, visibility };
|
|
1335
|
+
if (binding.kind === "uniform") {
|
|
1336
|
+
entries.push({ ...base, buffer: { type: "uniform" } });
|
|
1337
|
+
} else if (binding.kind === "storage") {
|
|
1338
|
+
entries.push({
|
|
1339
|
+
...base,
|
|
1340
|
+
buffer: { type: binding.access === "read" ? "read-only-storage" : "storage" }
|
|
1341
|
+
});
|
|
1342
|
+
} else if (binding.kind === "sampler") {
|
|
1343
|
+
entries.push({ ...base, sampler: { type: binding.comparison ? "comparison" : "filtering" } });
|
|
1344
|
+
} else if (binding.kind === "texture") {
|
|
1345
|
+
entries.push({
|
|
1346
|
+
...base,
|
|
1347
|
+
texture: {
|
|
1348
|
+
sampleType: binding.sampleType ?? "float",
|
|
1349
|
+
viewDimension: binding.viewDimension ?? "2d",
|
|
1350
|
+
multisampled: !!binding.multisampled
|
|
1351
|
+
}
|
|
1352
|
+
});
|
|
1353
|
+
} else if (binding.kind === "storage-texture") {
|
|
1354
|
+
entries.push({
|
|
1355
|
+
...base,
|
|
1356
|
+
storageTexture: {
|
|
1357
|
+
access: binding.access === "read-write" ? "read-write" : "write-only",
|
|
1358
|
+
format: "rgba8unorm",
|
|
1359
|
+
viewDimension: binding.viewDimension ?? "2d"
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
return entries;
|
|
1365
|
+
}
|
|
1366
|
+
function bindFromReflection(gpu2, reflection, visibility, initial, textures, label) {
|
|
1367
|
+
const bindings = reflection.bindings.filter((b) => b.group === 0);
|
|
1368
|
+
const uniformBinding = reflection.uniform;
|
|
1369
|
+
const uniformValues = uniformBinding?.struct ? uniformsFromStruct(gpu2, uniformBinding.struct, initial, `${label}-uniforms`) : noUniforms();
|
|
1370
|
+
if (bindings.length === 0) {
|
|
1371
|
+
return { layout: null, group: null, uniforms: uniformValues, sampler: null, fromReflection: false };
|
|
1372
|
+
}
|
|
1373
|
+
let layout = null;
|
|
1374
|
+
try {
|
|
1375
|
+
layout = gpu2.device.createBindGroupLayout({
|
|
1376
|
+
label: `${label}-layout`,
|
|
1377
|
+
entries: layoutEntries(bindings, visibility)
|
|
1378
|
+
});
|
|
1379
|
+
} catch (err) {
|
|
1380
|
+
return { layout: null, group: null, uniforms: uniformValues, sampler: null, fromReflection: false };
|
|
1381
|
+
}
|
|
1382
|
+
let sampler = null;
|
|
1383
|
+
const resources = [];
|
|
1384
|
+
for (const binding of bindings) {
|
|
1385
|
+
if (binding.kind === "uniform" && uniformValues.buffer) {
|
|
1386
|
+
resources.push({ binding: binding.binding, resource: { buffer: uniformValues.buffer } });
|
|
1387
|
+
continue;
|
|
1388
|
+
}
|
|
1389
|
+
if (binding.kind === "sampler") {
|
|
1390
|
+
sampler ?? (sampler = gpu2.device.createSampler({
|
|
1391
|
+
label: `${label}-sampler`,
|
|
1392
|
+
magFilter: "linear",
|
|
1393
|
+
minFilter: "linear",
|
|
1394
|
+
addressModeU: "clamp-to-edge",
|
|
1395
|
+
addressModeV: "clamp-to-edge"
|
|
1396
|
+
}));
|
|
1397
|
+
resources.push({ binding: binding.binding, resource: sampler });
|
|
1398
|
+
continue;
|
|
1399
|
+
}
|
|
1400
|
+
const view = textures[binding.name];
|
|
1401
|
+
if (view) {
|
|
1402
|
+
resources.push({ binding: binding.binding, resource: view });
|
|
1403
|
+
continue;
|
|
1404
|
+
}
|
|
1405
|
+
return { layout, group: null, uniforms: uniformValues, sampler, fromReflection: false };
|
|
1406
|
+
}
|
|
1407
|
+
try {
|
|
1408
|
+
const group = gpu2.device.createBindGroup({
|
|
1409
|
+
label: `${label}-group`,
|
|
1410
|
+
layout,
|
|
1411
|
+
entries: resources
|
|
1412
|
+
});
|
|
1413
|
+
return { layout, group, uniforms: uniformValues, sampler, fromReflection: true };
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
return { layout, group: null, uniforms: uniformValues, sampler, fromReflection: false };
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
function reportCompilation(module, label, source) {
|
|
1419
|
+
if (typeof module.getCompilationInfo !== "function") return;
|
|
1420
|
+
const lines = source.split("\n");
|
|
1421
|
+
module.getCompilationInfo().then((info) => {
|
|
1422
|
+
const errors = info.messages.filter((m) => m.type === "error");
|
|
1423
|
+
if (errors.length === 0) return;
|
|
1424
|
+
const detail = errors.map((m) => ` line ${m.lineNum}: ${m.message}
|
|
1425
|
+
> ${(lines[m.lineNum - 1] ?? "").trim()}`).join("\n");
|
|
1426
|
+
handleError(new Error(`shader "${label}" did not compile:
|
|
1427
|
+
${detail}`), "V.gpu shader");
|
|
1428
|
+
}).catch(() => void 0);
|
|
1429
|
+
}
|
|
1430
|
+
function noEffect(reflection) {
|
|
1431
|
+
return {
|
|
1432
|
+
reflection,
|
|
1433
|
+
ok: false,
|
|
1434
|
+
uniforms: noUniforms(),
|
|
1435
|
+
set: () => void 0,
|
|
1436
|
+
draw: () => void 0,
|
|
1437
|
+
destroy: () => void 0
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1440
|
+
function effect2(gpu2, wgsl, options = {}) {
|
|
1441
|
+
const reflection = reflectWgsl(wgsl);
|
|
1442
|
+
if (!live(gpu2) || !wgsl) return noEffect(reflection);
|
|
1443
|
+
const label = options.label ?? "voodoo-effect";
|
|
1444
|
+
const hasVertex = !!findEntry(reflection, "vertex");
|
|
1445
|
+
const source = hasVertex ? wgsl : `${FULLSCREEN_VERTEX}
|
|
1446
|
+
${wgsl}`;
|
|
1447
|
+
const vertexEntry = hasVertex ? findEntry(reflection, "vertex").name : "voodooFullscreen";
|
|
1448
|
+
const fragmentEntry = options.entry ?? findEntry(reflection, "fragment")?.name;
|
|
1449
|
+
if (!fragmentEntry) {
|
|
1450
|
+
return noEffect(reflection);
|
|
1451
|
+
}
|
|
1452
|
+
let module;
|
|
1453
|
+
try {
|
|
1454
|
+
module = gpu2.device.createShaderModule({ label, code: source });
|
|
1455
|
+
} catch (err) {
|
|
1456
|
+
handleError(err, "V.gpu.effect");
|
|
1457
|
+
return noEffect(reflection);
|
|
1458
|
+
}
|
|
1459
|
+
reportCompilation(module, label, source);
|
|
1460
|
+
const bound = bindFromReflection(
|
|
1461
|
+
gpu2,
|
|
1462
|
+
reflection,
|
|
1463
|
+
SHADER_STAGE.VERTEX | SHADER_STAGE.FRAGMENT,
|
|
1464
|
+
options.set ?? {},
|
|
1465
|
+
options.textures ?? {},
|
|
1466
|
+
label
|
|
1467
|
+
);
|
|
1468
|
+
const descriptor = {
|
|
1469
|
+
label,
|
|
1470
|
+
layout: "auto",
|
|
1471
|
+
vertex: { module, entryPoint: vertexEntry },
|
|
1472
|
+
fragment: {
|
|
1473
|
+
module,
|
|
1474
|
+
entryPoint: fragmentEntry,
|
|
1475
|
+
targets: [{ format: options.format ?? gpu2.format }]
|
|
1476
|
+
},
|
|
1477
|
+
primitive: { topology: "triangle-list" }
|
|
1478
|
+
};
|
|
1479
|
+
let pipeline = null;
|
|
1480
|
+
try {
|
|
1481
|
+
if (bound.fromReflection && bound.layout) {
|
|
1482
|
+
descriptor.layout = gpu2.device.createPipelineLayout({
|
|
1483
|
+
label: `${label}-pipeline-layout`,
|
|
1484
|
+
bindGroupLayouts: [bound.layout]
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
pipeline = gpu2.device.createRenderPipeline(descriptor);
|
|
1488
|
+
} catch (err) {
|
|
1489
|
+
try {
|
|
1490
|
+
descriptor.layout = "auto";
|
|
1491
|
+
pipeline = gpu2.device.createRenderPipeline(descriptor);
|
|
1492
|
+
} catch {
|
|
1493
|
+
handleError(err, "V.gpu.effect");
|
|
1494
|
+
bound.uniforms.destroy();
|
|
1495
|
+
return noEffect(reflection);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
let alive = true;
|
|
1499
|
+
const handle = {
|
|
1500
|
+
reflection,
|
|
1501
|
+
ok: true,
|
|
1502
|
+
uniforms: bound.uniforms,
|
|
1503
|
+
set(values) {
|
|
1504
|
+
bound.uniforms.set(values);
|
|
1505
|
+
},
|
|
1506
|
+
draw(pass) {
|
|
1507
|
+
if (!alive || !pipeline) return;
|
|
1508
|
+
pass.setPipeline(pipeline);
|
|
1509
|
+
if (bound.group) pass.setBindGroup(0, bound.group);
|
|
1510
|
+
pass.draw(3);
|
|
1511
|
+
},
|
|
1512
|
+
destroy() {
|
|
1513
|
+
if (!alive) return;
|
|
1514
|
+
alive = false;
|
|
1515
|
+
pipeline = null;
|
|
1516
|
+
bound.uniforms.destroy();
|
|
1517
|
+
untrack(gpu2, handle);
|
|
1518
|
+
}
|
|
1519
|
+
};
|
|
1520
|
+
track(gpu2, handle);
|
|
1521
|
+
return handle;
|
|
1522
|
+
}
|
|
1523
|
+
function noCompute(reflection) {
|
|
1524
|
+
return {
|
|
1525
|
+
reflection,
|
|
1526
|
+
ok: false,
|
|
1527
|
+
uniforms: noUniforms(),
|
|
1528
|
+
set: () => void 0,
|
|
1529
|
+
dispatch: () => void 0,
|
|
1530
|
+
destroy: () => void 0
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
function compute(gpu2, wgsl, options = {}) {
|
|
1534
|
+
const reflection = reflectWgsl(wgsl);
|
|
1535
|
+
if (!live(gpu2) || !wgsl) return noCompute(reflection);
|
|
1536
|
+
const label = options.label ?? "voodoo-compute";
|
|
1537
|
+
const entry = options.entry ?? findEntry(reflection, "compute")?.name;
|
|
1538
|
+
if (!entry) {
|
|
1539
|
+
return noCompute(reflection);
|
|
1540
|
+
}
|
|
1541
|
+
let module;
|
|
1542
|
+
try {
|
|
1543
|
+
module = gpu2.device.createShaderModule({ label, code: wgsl });
|
|
1544
|
+
} catch (err) {
|
|
1545
|
+
handleError(err, "V.gpu.compute");
|
|
1546
|
+
return noCompute(reflection);
|
|
1547
|
+
}
|
|
1548
|
+
reportCompilation(module, label, wgsl);
|
|
1549
|
+
const bound = bindFromReflection(
|
|
1550
|
+
gpu2,
|
|
1551
|
+
reflection,
|
|
1552
|
+
SHADER_STAGE.COMPUTE,
|
|
1553
|
+
options.set ?? {},
|
|
1554
|
+
options.textures ?? {},
|
|
1555
|
+
label
|
|
1556
|
+
);
|
|
1557
|
+
const descriptor = {
|
|
1558
|
+
label,
|
|
1559
|
+
layout: "auto",
|
|
1560
|
+
compute: { module, entryPoint: entry }
|
|
1561
|
+
};
|
|
1562
|
+
let pipeline = null;
|
|
1563
|
+
try {
|
|
1564
|
+
if (bound.fromReflection && bound.layout) {
|
|
1565
|
+
descriptor.layout = gpu2.device.createPipelineLayout({
|
|
1566
|
+
label: `${label}-pipeline-layout`,
|
|
1567
|
+
bindGroupLayouts: [bound.layout]
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
pipeline = gpu2.device.createComputePipeline(descriptor);
|
|
1571
|
+
} catch (err) {
|
|
1572
|
+
try {
|
|
1573
|
+
descriptor.layout = "auto";
|
|
1574
|
+
pipeline = gpu2.device.createComputePipeline(descriptor);
|
|
1575
|
+
} catch {
|
|
1576
|
+
handleError(err, "V.gpu.compute");
|
|
1577
|
+
bound.uniforms.destroy();
|
|
1578
|
+
return noCompute(reflection);
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
const default_ = options.workgroups ?? [1, 1, 1];
|
|
1582
|
+
let alive = true;
|
|
1583
|
+
const handle = {
|
|
1584
|
+
reflection,
|
|
1585
|
+
ok: true,
|
|
1586
|
+
uniforms: bound.uniforms,
|
|
1587
|
+
set(values) {
|
|
1588
|
+
bound.uniforms.set(values);
|
|
1589
|
+
},
|
|
1590
|
+
dispatch(pass, workgroups) {
|
|
1591
|
+
if (!alive || !pipeline) return;
|
|
1592
|
+
const [x, y, z] = workgroups ?? default_;
|
|
1593
|
+
pass.setPipeline(pipeline);
|
|
1594
|
+
if (bound.group) pass.setBindGroup(0, bound.group);
|
|
1595
|
+
pass.dispatchWorkgroups(Math.max(1, x), y ?? 1, z ?? 1);
|
|
1596
|
+
},
|
|
1597
|
+
destroy() {
|
|
1598
|
+
if (!alive) return;
|
|
1599
|
+
alive = false;
|
|
1600
|
+
pipeline = null;
|
|
1601
|
+
bound.uniforms.destroy();
|
|
1602
|
+
untrack(gpu2, handle);
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
track(gpu2, handle);
|
|
1606
|
+
return handle;
|
|
1607
|
+
}
|
|
1608
|
+
var EMPTY_CLOCK = clock();
|
|
1609
|
+
function noFrame() {
|
|
1610
|
+
return {
|
|
1611
|
+
encoder: null,
|
|
1612
|
+
clock: EMPTY_CLOCK,
|
|
1613
|
+
clear: [0, 0, 0, 0],
|
|
1614
|
+
pass: () => void 0,
|
|
1615
|
+
compute: () => void 0
|
|
1616
|
+
};
|
|
1617
|
+
}
|
|
1618
|
+
function buildFrame(gpu2, encoder, relogio) {
|
|
1619
|
+
const frameObj = {
|
|
1620
|
+
encoder,
|
|
1621
|
+
clock: relogio,
|
|
1622
|
+
clear: [0, 0, 0, 0],
|
|
1623
|
+
pass(destino, ...operacoes) {
|
|
1624
|
+
const view = destino?.view() ?? null;
|
|
1625
|
+
if (!view) return;
|
|
1626
|
+
const [r, g, b, a] = frameObj.clear;
|
|
1627
|
+
let pass;
|
|
1628
|
+
try {
|
|
1629
|
+
pass = encoder.beginRenderPass({
|
|
1630
|
+
colorAttachments: [{ view, clearValue: { r, g, b, a }, loadOp: "clear", storeOp: "store" }]
|
|
1631
|
+
});
|
|
1632
|
+
} catch (err) {
|
|
1633
|
+
handleError(err, "V.gpu.frame");
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
for (const operacao of operacoes) operacao?.draw(pass);
|
|
1637
|
+
pass.end();
|
|
1638
|
+
},
|
|
1639
|
+
compute(...operacoes) {
|
|
1640
|
+
if (operacoes.length === 0) return;
|
|
1641
|
+
let pass;
|
|
1642
|
+
try {
|
|
1643
|
+
pass = encoder.beginComputePass();
|
|
1644
|
+
} catch (err) {
|
|
1645
|
+
handleError(err, "V.gpu.frame");
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
for (const operacao of operacoes) operacao?.dispatch(pass);
|
|
1649
|
+
pass.end();
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
return frameObj;
|
|
1653
|
+
}
|
|
1654
|
+
function frame(gpu2, build, relogio = EMPTY_CLOCK) {
|
|
1655
|
+
if (!live(gpu2)) {
|
|
1656
|
+
build(noFrame());
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
try {
|
|
1660
|
+
const encoder = gpu2.device.createCommandEncoder({ label: "voodoo-frame" });
|
|
1661
|
+
build(buildFrame(gpu2, encoder, relogio));
|
|
1662
|
+
gpu2.queue.submit([encoder.finish()]);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
handleError(err, "V.gpu.frame");
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
function frameLoop(gpu2, build) {
|
|
1668
|
+
if (!live(gpu2) || typeof requestAnimationFrame !== "function") return () => void 0;
|
|
1669
|
+
const relogio = clock();
|
|
1670
|
+
let handle = 0;
|
|
1671
|
+
let running = true;
|
|
1672
|
+
const step = (now) => {
|
|
1673
|
+
handle = 0;
|
|
1674
|
+
if (!running || !live(gpu2)) return;
|
|
1675
|
+
relogio.tick(now);
|
|
1676
|
+
frame(gpu2, build, relogio);
|
|
1677
|
+
if (running) handle = requestAnimationFrame(step);
|
|
1678
|
+
};
|
|
1679
|
+
handle = requestAnimationFrame(step);
|
|
1680
|
+
return () => {
|
|
1681
|
+
if (!running) return;
|
|
1682
|
+
running = false;
|
|
1683
|
+
if (handle) cancelAnimationFrame(handle);
|
|
1684
|
+
handle = 0;
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
function destroy2(gpu2) {
|
|
1688
|
+
if (!gpu2 || gpu2.destroyed) return;
|
|
1689
|
+
gpu2.destroyed = true;
|
|
1690
|
+
for (const resource of [...gpu2.resources]) {
|
|
1691
|
+
try {
|
|
1692
|
+
resource.destroy();
|
|
1693
|
+
} catch (err) {
|
|
1694
|
+
handleError(err, "V.gpu.destroy");
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
gpu2.resources.clear();
|
|
1698
|
+
try {
|
|
1699
|
+
gpu2.device.destroy();
|
|
1700
|
+
} catch {
|
|
1701
|
+
}
|
|
1702
|
+
sharedContext?.then((current) => {
|
|
1703
|
+
if (current === gpu2) resetShared();
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
var gpu = {
|
|
1707
|
+
supported,
|
|
1708
|
+
init,
|
|
1709
|
+
shared,
|
|
1710
|
+
surface,
|
|
1711
|
+
target,
|
|
1712
|
+
uniforms,
|
|
1713
|
+
clock,
|
|
1714
|
+
effect: effect2,
|
|
1715
|
+
compute,
|
|
1716
|
+
frame,
|
|
1717
|
+
frameLoop,
|
|
1718
|
+
destroy: destroy2,
|
|
1719
|
+
/** WGSL reading, useful on its own for inspecting a shader. */
|
|
1720
|
+
reflect: reflectWgsl
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
// src/directives/gpu.ts
|
|
1724
|
+
function classifyShaderSource(text) {
|
|
1725
|
+
const valor = text.trim();
|
|
1726
|
+
if (!valor) return "empty";
|
|
1727
|
+
if (valor.startsWith("#")) return "selector";
|
|
1728
|
+
if (/@(?:vertex|fragment|compute)\b/.test(valor)) return "inline";
|
|
1729
|
+
if (/\bfn\s+[A-Za-z_]/.test(valor)) return "inline";
|
|
1730
|
+
if (valor.includes("{") || valor.includes("\n")) return "inline";
|
|
1731
|
+
return "url";
|
|
1732
|
+
}
|
|
1733
|
+
async function resolveShaderSource(text) {
|
|
1734
|
+
const valor = text.trim();
|
|
1735
|
+
const kind = classifyShaderSource(valor);
|
|
1736
|
+
if (kind === "empty") return "";
|
|
1737
|
+
if (kind === "inline") return valor;
|
|
1738
|
+
if (kind === "selector") {
|
|
1739
|
+
if (typeof document === "undefined") return "";
|
|
1740
|
+
let target3 = null;
|
|
1741
|
+
try {
|
|
1742
|
+
target3 = document.querySelector(valor);
|
|
1743
|
+
} catch {
|
|
1744
|
+
target3 = null;
|
|
1745
|
+
}
|
|
1746
|
+
if (!target3) {
|
|
1747
|
+
return "";
|
|
1748
|
+
}
|
|
1749
|
+
return target3.textContent ?? "";
|
|
1750
|
+
}
|
|
1751
|
+
try {
|
|
1752
|
+
const response = await http.get(valor, { responseType: "text" });
|
|
1753
|
+
return typeof response === "string" ? response : "";
|
|
1754
|
+
} catch (err) {
|
|
1755
|
+
handleError(err, `v-shader when fetching "${valor}"`);
|
|
1756
|
+
return "";
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
function revealFallback(el, mount) {
|
|
1760
|
+
if (!el.firstChild || typeof document === "undefined") return () => void 0;
|
|
1761
|
+
const substitute = document.createElement("div");
|
|
1762
|
+
substitute.setAttribute("data-gpu-fallback", "");
|
|
1763
|
+
while (el.firstChild) substitute.appendChild(el.firstChild);
|
|
1764
|
+
el.parentNode?.insertBefore(substitute, el.nextSibling);
|
|
1765
|
+
const previousDisplay = el.style.display;
|
|
1766
|
+
el.style.display = "none";
|
|
1767
|
+
mount(substitute);
|
|
1768
|
+
return () => {
|
|
1769
|
+
destroy(substitute);
|
|
1770
|
+
while (substitute.firstChild) el.appendChild(substitute.firstChild);
|
|
1771
|
+
substitute.remove();
|
|
1772
|
+
if (previousDisplay) el.style.display = previousDisplay;
|
|
1773
|
+
else el.style.removeProperty("display");
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
function noSupport(el, reason, mount) {
|
|
1777
|
+
el.setAttribute("data-gpu", "unsupported");
|
|
1778
|
+
el.dispatchEvent(
|
|
1779
|
+
new CustomEvent("voodoo:gpu-unsupported", { bubbles: true, detail: { reason, el } })
|
|
1780
|
+
);
|
|
1781
|
+
warn(
|
|
1782
|
+
`v-shader at ${describeElement(el)} did not run: ${reason}. The content inside the <canvas> was used as a fallback. Listen to voodoo:gpu-unsupported to offer something else.`
|
|
1783
|
+
);
|
|
1784
|
+
return revealFallback(el, mount);
|
|
1785
|
+
}
|
|
1786
|
+
function expressionOfSet(el) {
|
|
1787
|
+
const atributos = originalAttributes(el);
|
|
1788
|
+
for (const nome of [":set", "v-bind:set", "data-v-bind:set"]) {
|
|
1789
|
+
const valor = atributos.get(nome);
|
|
1790
|
+
if (valor) return valor;
|
|
1791
|
+
}
|
|
1792
|
+
const proprio = el.getAttribute("v-shader-set") ?? el.getAttribute("data-v-shader-set");
|
|
1793
|
+
return proprio || null;
|
|
1794
|
+
}
|
|
1795
|
+
function dprRange(el) {
|
|
1796
|
+
const raw = el.getAttribute("v-shader-dpr") ?? el.getAttribute("data-v-shader-dpr");
|
|
1797
|
+
if (!raw) return [1, 2];
|
|
1798
|
+
const parts = raw.split(",").map((n) => parseFloat(n.trim()));
|
|
1799
|
+
const min = Number.isFinite(parts[0]) ? parts[0] : 1;
|
|
1800
|
+
const max = Number.isFinite(parts[1]) ? parts[1] : Math.max(min, 2);
|
|
1801
|
+
return [Math.max(0.5, min), Math.max(min, max)];
|
|
1802
|
+
}
|
|
1803
|
+
var AUTOMATIC_UNIFORMS = ["time", "delta", "frame", "resolution"];
|
|
1804
|
+
defineDirective("shader", (ctx) => {
|
|
1805
|
+
const { el, expression, modifiers, evaluate, effect: effect3, cleanup, scope, walk } = ctx;
|
|
1806
|
+
const mountOn = (node) => walk(node, scope);
|
|
1807
|
+
if (typeof HTMLCanvasElement === "undefined" || !(el instanceof HTMLCanvasElement)) {
|
|
1808
|
+
warn(
|
|
1809
|
+
`v-shader needs a <canvas>, but was used on ${describeElement(el)}. Change the tag to <canvas> so the shader has somewhere to draw.`
|
|
1810
|
+
);
|
|
1811
|
+
return;
|
|
1812
|
+
}
|
|
1813
|
+
const canvas = el;
|
|
1814
|
+
let canceled = false;
|
|
1815
|
+
cleanup(() => {
|
|
1816
|
+
canceled = true;
|
|
1817
|
+
canvas.removeAttribute("data-gpu");
|
|
1818
|
+
});
|
|
1819
|
+
if (!supported()) {
|
|
1820
|
+
const restore = noSupport(canvas, "this browser does not have WebGPU", mountOn);
|
|
1821
|
+
cleanup(restore);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
const setExpr = expressionOfSet(canvas);
|
|
1825
|
+
let values = {};
|
|
1826
|
+
let apply = null;
|
|
1827
|
+
if (setExpr) {
|
|
1828
|
+
effect3(() => {
|
|
1829
|
+
const read = evaluate(setExpr);
|
|
1830
|
+
values = read && typeof read === "object" ? read : {};
|
|
1831
|
+
apply?.(values);
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
const pausedExpr = canvas.getAttribute("v-shader-paused") ?? canvas.getAttribute("data-v-shader-paused");
|
|
1835
|
+
let paused = !!modifiers.paused;
|
|
1836
|
+
let visible = !modifiers.visible;
|
|
1837
|
+
let surface_obj = null;
|
|
1838
|
+
let effect_obj = null;
|
|
1839
|
+
let stopLoop = null;
|
|
1840
|
+
let singleFrame = 0;
|
|
1841
|
+
let observer_obj = null;
|
|
1842
|
+
let restoreFallback = null;
|
|
1843
|
+
let automaticList = [];
|
|
1844
|
+
const clock_obj = clock();
|
|
1845
|
+
cleanup(() => {
|
|
1846
|
+
stopLoop?.();
|
|
1847
|
+
stopLoop = null;
|
|
1848
|
+
observer_obj?.disconnect();
|
|
1849
|
+
observer_obj = null;
|
|
1850
|
+
if (singleFrame) cancelAnimationFrame(singleFrame);
|
|
1851
|
+
singleFrame = 0;
|
|
1852
|
+
effect_obj?.destroy();
|
|
1853
|
+
effect_obj = null;
|
|
1854
|
+
surface_obj?.destroy();
|
|
1855
|
+
surface_obj = null;
|
|
1856
|
+
apply = null;
|
|
1857
|
+
restoreFallback?.();
|
|
1858
|
+
restoreFallback = null;
|
|
1859
|
+
});
|
|
1860
|
+
const feedClock = () => {
|
|
1861
|
+
if (!effect_obj || automaticList.length === 0) return;
|
|
1862
|
+
const package_data = {
|
|
1863
|
+
time: clock_obj.time,
|
|
1864
|
+
delta: clock_obj.delta,
|
|
1865
|
+
frame: clock_obj.frame,
|
|
1866
|
+
resolution: [surface_obj?.width ?? 0, surface_obj?.height ?? 0]
|
|
1867
|
+
};
|
|
1868
|
+
const uniforms2 = {};
|
|
1869
|
+
for (const name of automaticList) uniforms2[name] = package_data[name];
|
|
1870
|
+
effect_obj.set(uniforms2);
|
|
1871
|
+
};
|
|
1872
|
+
const draw = (gpu2) => {
|
|
1873
|
+
feedClock();
|
|
1874
|
+
frame(gpu2, (frame2) => frame2.pass(surface_obj, effect_obj));
|
|
1875
|
+
};
|
|
1876
|
+
const isRunning = () => !!stopLoop;
|
|
1877
|
+
const syncLoop = (gpu2) => {
|
|
1878
|
+
if (canceled || !effect_obj) return;
|
|
1879
|
+
const shouldRun = !paused && visible && !modifiers.once;
|
|
1880
|
+
if (shouldRun && !isRunning()) {
|
|
1881
|
+
stopLoop = frameLoop(gpu2, (frame2) => {
|
|
1882
|
+
clock_obj.tick();
|
|
1883
|
+
feedClock();
|
|
1884
|
+
frame2.pass(surface_obj, effect_obj);
|
|
1885
|
+
});
|
|
1886
|
+
canvas.setAttribute("data-gpu", "ready");
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
if (!shouldRun && isRunning()) {
|
|
1890
|
+
stopLoop?.();
|
|
1891
|
+
stopLoop = null;
|
|
1892
|
+
canvas.setAttribute("data-gpu", "paused");
|
|
1893
|
+
}
|
|
1894
|
+
};
|
|
1895
|
+
const mount = (gpu2, source) => {
|
|
1896
|
+
surface_obj = surface(gpu2, canvas, { dpr: dprRange(canvas), alpha: true });
|
|
1897
|
+
effect_obj = effect2(gpu2, source, {
|
|
1898
|
+
set: values,
|
|
1899
|
+
format: surface_obj.format || void 0,
|
|
1900
|
+
label: `v-shader ${describeElement(canvas)}`
|
|
1901
|
+
});
|
|
1902
|
+
if (!effect_obj.ok) {
|
|
1903
|
+
canvas.setAttribute("data-gpu", "error");
|
|
1904
|
+
effect_obj.destroy();
|
|
1905
|
+
effect_obj = null;
|
|
1906
|
+
surface_obj.destroy();
|
|
1907
|
+
surface_obj = null;
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
apply = (v) => effect_obj?.set(v);
|
|
1911
|
+
const fields = effect_obj.reflection.uniform?.struct?.fields ?? [];
|
|
1912
|
+
automaticList = AUTOMATIC_UNIFORMS.filter((name) => fields.some((f) => f.name === name));
|
|
1913
|
+
if (pausedExpr) {
|
|
1914
|
+
effect3(() => {
|
|
1915
|
+
paused = !!evaluate(pausedExpr);
|
|
1916
|
+
syncLoop(gpu2);
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
if (modifiers.visible && typeof IntersectionObserver !== "undefined") {
|
|
1920
|
+
observer_obj = new IntersectionObserver((entries) => {
|
|
1921
|
+
for (const entry of entries) visible = entry.isIntersecting;
|
|
1922
|
+
syncLoop(gpu2);
|
|
1923
|
+
});
|
|
1924
|
+
observer_obj.observe(canvas);
|
|
1925
|
+
} else {
|
|
1926
|
+
visible = true;
|
|
1927
|
+
}
|
|
1928
|
+
if (modifiers.once) {
|
|
1929
|
+
singleFrame = requestAnimationFrame(() => {
|
|
1930
|
+
singleFrame = 0;
|
|
1931
|
+
if (canceled) return;
|
|
1932
|
+
clock_obj.tick();
|
|
1933
|
+
draw(gpu2);
|
|
1934
|
+
canvas.setAttribute("data-gpu", "ready");
|
|
1935
|
+
});
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
canvas.setAttribute("data-gpu", "ready");
|
|
1939
|
+
syncLoop(gpu2);
|
|
1940
|
+
};
|
|
1941
|
+
canvas.setAttribute("data-gpu", "loading");
|
|
1942
|
+
void (async () => {
|
|
1943
|
+
const source = await resolveShaderSource(expression);
|
|
1944
|
+
if (canceled) return;
|
|
1945
|
+
if (!source) {
|
|
1946
|
+
canvas.setAttribute("data-gpu", "error");
|
|
1947
|
+
restoreFallback = revealFallback(canvas, mountOn);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
const gpu2 = await shared();
|
|
1951
|
+
if (canceled) return;
|
|
1952
|
+
if (!gpu2) {
|
|
1953
|
+
restoreFallback = noSupport(canvas, "the WebGPU adapter did not open", mountOn);
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
try {
|
|
1957
|
+
mount(gpu2, source);
|
|
1958
|
+
} catch (err) {
|
|
1959
|
+
canvas.setAttribute("data-gpu", "error");
|
|
1960
|
+
handleError(err, `v-shader at ${describeElement(canvas)}`);
|
|
1961
|
+
}
|
|
1962
|
+
})();
|
|
1963
|
+
});
|
|
1964
|
+
|
|
1965
|
+
// src/gpu/plugin.ts
|
|
1966
|
+
var voodooGpu = {
|
|
1967
|
+
name: "gpu",
|
|
1968
|
+
install(V) {
|
|
1969
|
+
if (!V.gpu) V.gpu = gpu;
|
|
1970
|
+
}
|
|
1971
|
+
};
|
|
1972
|
+
var target2 = globalThis.V;
|
|
1973
|
+
if (target2 && typeof target2 === "object" && !target2.gpu) target2.gpu = gpu;
|
|
1974
|
+
var plugin_default = voodooGpu;
|
|
1975
|
+
|
|
1976
|
+
exports.classifyShaderSource = classifyShaderSource;
|
|
1977
|
+
exports.clock = clock;
|
|
1978
|
+
exports.compute = compute;
|
|
1979
|
+
exports.default = plugin_default;
|
|
1980
|
+
exports.describeWgslType = describeWgslType;
|
|
1981
|
+
exports.destroy = destroy2;
|
|
1982
|
+
exports.effect = effect2;
|
|
1983
|
+
exports.findEntry = findEntry;
|
|
1984
|
+
exports.flattenValue = flattenValue;
|
|
1985
|
+
exports.frame = frame;
|
|
1986
|
+
exports.frameLoop = frameLoop;
|
|
1987
|
+
exports.gpu = gpu;
|
|
1988
|
+
exports.inferStruct = inferStruct;
|
|
1989
|
+
exports.init = init;
|
|
1990
|
+
exports.packStruct = packStruct;
|
|
1991
|
+
exports.reflectBindings = reflectBindings;
|
|
1992
|
+
exports.reflectEntries = reflectEntries;
|
|
1993
|
+
exports.reflectStructs = reflectStructs;
|
|
1994
|
+
exports.reflectWgsl = reflectWgsl;
|
|
1995
|
+
exports.resetShared = resetShared;
|
|
1996
|
+
exports.resolveShaderSource = resolveShaderSource;
|
|
1997
|
+
exports.shared = shared;
|
|
1998
|
+
exports.splitTopLevel = splitTopLevel;
|
|
1999
|
+
exports.stripWgslComments = stripWgslComments;
|
|
2000
|
+
exports.supported = supported;
|
|
2001
|
+
exports.surface = surface;
|
|
2002
|
+
exports.target = target;
|
|
2003
|
+
exports.uniforms = uniforms;
|
|
2004
|
+
exports.voodooGpu = voodooGpu;
|
|
2005
|
+
exports.writeField = writeField;
|
|
2006
|
+
exports.writeStruct = writeStruct;
|
|
2007
|
+
//# sourceMappingURL=gpu.cjs.map
|
|
2008
|
+
//# sourceMappingURL=gpu.cjs.map
|