@adobe/ecs-gpu 0.0.0-stage → 0.0.2

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.
Files changed (40) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +110 -2
  3. package/dist/index.js +688 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/types/cache/cache.d.ts +6 -0
  6. package/dist/types/cache/public.d.ts +1 -0
  7. package/dist/types/device/device.d.ts +24 -0
  8. package/dist/types/device/public.d.ts +1 -0
  9. package/dist/types/frame/frame.d.ts +14 -0
  10. package/dist/types/frame/public.d.ts +1 -0
  11. package/dist/types/guide/01-targets.d.ts +1 -0
  12. package/dist/types/guide/02-frames.d.ts +1 -0
  13. package/dist/types/index.d.ts +10 -0
  14. package/dist/types/surface/public.d.ts +1 -0
  15. package/dist/types/surface/surface.d.ts +81 -0
  16. package/dist/types/target/public.d.ts +1 -0
  17. package/dist/types/target/target.d.ts +78 -0
  18. package/dist/types/view/public.d.ts +1 -0
  19. package/dist/types/view/view.d.ts +69 -0
  20. package/package.json +49 -3
  21. package/src/cache/cache.ts +23 -0
  22. package/src/cache/public.ts +2 -0
  23. package/src/device/device.ts +46 -0
  24. package/src/device/public.ts +2 -0
  25. package/src/frame/frame.ts +53 -0
  26. package/src/frame/public.ts +2 -0
  27. package/src/guide/01-targets.md +13 -0
  28. package/src/guide/01-targets.out +5 -0
  29. package/src/guide/01-targets.ts +40 -0
  30. package/src/guide/02-frames.md +15 -0
  31. package/src/guide/02-frames.out +2 -0
  32. package/src/guide/02-frames.ts +33 -0
  33. package/src/guide/README.md +7 -0
  34. package/src/index.ts +11 -0
  35. package/src/surface/public.ts +2 -0
  36. package/src/surface/surface.ts +140 -0
  37. package/src/target/public.ts +2 -0
  38. package/src/target/target.ts +133 -0
  39. package/src/view/public.ts +2 -0
  40. package/src/view/view.ts +125 -0
package/dist/index.js ADDED
@@ -0,0 +1,688 @@
1
+ import { Finalizer, Index, System, gpuDevice } from "@adobe/ecs";
2
+ //#region \0rolldown/runtime.js
3
+ var __defProp = Object.defineProperty;
4
+ var __exportAll = (all, no_symbols) => {
5
+ let target = {};
6
+ for (var name in all) __defProp(target, name, {
7
+ get: all[name],
8
+ enumerable: true
9
+ });
10
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
+ return target;
12
+ };
13
+ //#endregion
14
+ //#region src/cache/cache.ts
15
+ /**
16
+ * A cache of what `create` makes for each device and key: a pipeline, a layout, a sampler. Its values for a device go
17
+ * with the device, so a device a world replaced, or one that was lost, holds nothing here. Keys are compared by
18
+ * `keyOf`, by default their JSON.
19
+ */
20
+ const perDevice = (create, keyOf = (key) => JSON.stringify(key)) => {
21
+ const devices = /* @__PURE__ */ new WeakMap();
22
+ return (device, key) => {
23
+ let made = devices.get(device);
24
+ if (made === void 0) {
25
+ made = /* @__PURE__ */ new Map();
26
+ devices.set(device, made);
27
+ }
28
+ const name = keyOf(key);
29
+ const found = made.get(name);
30
+ if (found !== void 0) return found.value;
31
+ const value = create(device, key);
32
+ made.set(name, { value });
33
+ return value;
34
+ };
35
+ };
36
+ //#endregion
37
+ //#region src/cache/public.ts
38
+ var public_exports = /* @__PURE__ */ __exportAll({ perDevice: () => perDevice });
39
+ //#endregion
40
+ //#region src/device/device.ts
41
+ /**
42
+ * A device on this thread, or null where there's no WebGPU, no adapter for `options`, or the adapter refused the
43
+ * device. Call it on the thread the world runs on: a device can't move between threads.
44
+ */
45
+ const request = async (options = {}) => {
46
+ const { requiredFeatures, requiredLimits, ...adapterOptions } = options;
47
+ const adapter = await globalThis.navigator?.gpu?.requestAdapter(adapterOptions);
48
+ if (adapter === null || adapter === void 0) return null;
49
+ try {
50
+ return await adapter.requestDevice({
51
+ requiredFeatures,
52
+ requiredLimits
53
+ });
54
+ } catch {
55
+ return null;
56
+ }
57
+ };
58
+ /** Sets the world's `gpuDevice`. */
59
+ const set = System.create({}, ({ resources }, input) => {
60
+ resources[gpuDevice] = input.device;
61
+ }, {
62
+ "type": "object",
63
+ "properties": { "device": { "anyOf": [{ "allOf": [] }, { "type": "null" }] } }
64
+ });
65
+ /**
66
+ * Keeps the world supplied with a device: requests one now, and another each time the one it gave is lost. Resolves
67
+ * once the first request settled, to whether it gave the world a device, and a function that stops keeping it. Call it
68
+ * on the world's thread.
69
+ */
70
+ const keep = async (world, options = {}) => {
71
+ let stopped = false;
72
+ const supply = async () => {
73
+ const device = await request(options);
74
+ if (device === null || stopped) return false;
75
+ await world.run(set, { device });
76
+ device.lost.then(() => {
77
+ if (!stopped) supply();
78
+ });
79
+ return true;
80
+ };
81
+ return {
82
+ supplied: await supply(),
83
+ stop: () => {
84
+ stopped = true;
85
+ }
86
+ };
87
+ };
88
+ //#endregion
89
+ //#region src/device/public.ts
90
+ var public_exports$1 = /* @__PURE__ */ __exportAll({
91
+ keep: () => keep,
92
+ request: () => request,
93
+ set: () => set
94
+ });
95
+ //#endregion
96
+ //#region src/target/target.ts
97
+ const Options$1 = { default: {
98
+ format: null,
99
+ sampleCount: 1,
100
+ depthFormat: "depth24plus",
101
+ clear: [
102
+ 0,
103
+ 0,
104
+ 0,
105
+ 1
106
+ ]
107
+ } };
108
+ /** An entity with these is a render target: what its textures are, and their size in pixels. */
109
+ const options$1 = Symbol("Target.options");
110
+ declare$$2(options$1, "component", { default: Options$1.default });
111
+ const size = Symbol("Target.size");
112
+ declare$$2(size, "component", { type: {
113
+ "type": "array",
114
+ "items": {
115
+ "type": "integer",
116
+ "minimum": 0,
117
+ "maximum": 4294967295
118
+ },
119
+ "minItems": 2,
120
+ "maxItems": 2
121
+ } });
122
+ /** Each render target's textures on the world's device, by its entity: null until targets are first updated. */
123
+ const states$2 = Symbol("Target.states");
124
+ declare$$2(states$2, "resource", {});
125
+ const sameOptions$1 = (a, b) => a.format === b.format && a.sampleCount === b.sampleCount && a.depthFormat === b.depthFormat && (a.clear === b.clear || a.clear !== null && b.clear !== null && a.clear.every((value, index) => value === b.clear?.[index]));
126
+ const destroyTextures = (state) => {
127
+ state.color.destroy();
128
+ state.multisampled?.destroy();
129
+ state.depth?.destroy();
130
+ };
131
+ const created = (device, entity, declared, [width, height]) => {
132
+ const format = declared.format ?? navigator.gpu.getPreferredCanvasFormat();
133
+ const extent = [width, height];
134
+ const color = device.createTexture({
135
+ label: `target ${entity} color`,
136
+ size: extent,
137
+ format,
138
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST
139
+ });
140
+ const multisampled = declared.sampleCount === 1 ? null : device.createTexture({
141
+ label: `target ${entity} multisampled`,
142
+ size: extent,
143
+ format,
144
+ sampleCount: declared.sampleCount,
145
+ usage: GPUTextureUsage.RENDER_ATTACHMENT
146
+ });
147
+ const depth = declared.depthFormat === null ? null : device.createTexture({
148
+ label: `target ${entity} depth`,
149
+ size: extent,
150
+ format: declared.depthFormat,
151
+ sampleCount: declared.sampleCount,
152
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
153
+ });
154
+ return {
155
+ entity,
156
+ device,
157
+ options: declared,
158
+ format,
159
+ size: [width, height],
160
+ color,
161
+ multisampled,
162
+ depth
163
+ };
164
+ };
165
+ /**
166
+ * Each render target's textures on `device`, made again where its options, its size or the device changed, destroying
167
+ * those they replace; with no device, none. Gives `previous` itself when nothing changed.
168
+ */
169
+ const updated$2 = (device, previous, targets) => {
170
+ const held = previous ?? /* @__PURE__ */ new Map();
171
+ let next = null;
172
+ for (let i = 0; i < targets.length; i++) {
173
+ const target = targets.at(i);
174
+ const old = held.get(target.entity);
175
+ const [width, height] = target[size];
176
+ if (device !== null && old !== void 0 && old.device === device && old.size[0] === width && old.size[1] === height && sameOptions$1(old.options, target[options$1])) continue;
177
+ if (device === null && old === void 0) continue;
178
+ next ??= new Map(held);
179
+ if (old !== void 0) destroyTextures(old);
180
+ if (device === null) next.delete(target.entity);
181
+ else next.set(target.entity, created(device, target.entity, target[options$1], target[size]));
182
+ }
183
+ return next ?? held;
184
+ };
185
+ /** Records clearing each target that declares a clear color: its color to that color, and its depth to 1. */
186
+ const recordClears = (encoder, targets) => {
187
+ for (const target of targets.values()) {
188
+ const color = target.options.clear;
189
+ if (color === null) continue;
190
+ const [r, g, b, a] = color;
191
+ encoder.beginRenderPass({
192
+ label: `clear target ${target.entity}`,
193
+ colorAttachments: [{
194
+ view: (target.multisampled ?? target.color).createView(),
195
+ resolveTarget: target.multisampled === null ? void 0 : target.color.createView(),
196
+ clearValue: {
197
+ r,
198
+ g,
199
+ b,
200
+ a
201
+ },
202
+ loadOp: "clear",
203
+ storeOp: "store"
204
+ }],
205
+ depthStencilAttachment: target.depth === null ? void 0 : {
206
+ view: target.depth.createView(),
207
+ depthClearValue: 1,
208
+ depthLoadOp: "clear",
209
+ depthStoreOp: "store"
210
+ }
211
+ }).end();
212
+ }
213
+ };
214
+ /** Destroys a render target's textures when it stops being one. */
215
+ const release$2 = Finalizer.create({ components: { with: [options$1, size] } }, (target, { resources }) => {
216
+ const current = resources[states$2];
217
+ const state = current?.get(target.entity);
218
+ if (current === null || state === void 0) return;
219
+ destroyTextures(state);
220
+ const next = new Map(current);
221
+ next.delete(target.entity);
222
+ resources[states$2] = next;
223
+ });
224
+ /** Sets a render target's size in pixels, at least one by one. */
225
+ const resize = System.create({
226
+ components: { with: [size] },
227
+ target: "target"
228
+ }, (row, _context, input) => {
229
+ row[size] = [Math.max(1, Math.floor(input.width)), Math.max(1, Math.floor(input.height))];
230
+ }, {
231
+ "type": "object",
232
+ "properties": {
233
+ "target": {
234
+ "type": "integer",
235
+ "format": "entity"
236
+ },
237
+ "width": { "type": "number" },
238
+ "height": { "type": "number" }
239
+ }
240
+ });
241
+ function declare$$2(key, kind, declaration) {
242
+ (globalThis[Symbol.for("@adobe/ecs/declarations@1")] ??= /* @__PURE__ */ new Map()).set(key, {
243
+ kind,
244
+ declaration
245
+ });
246
+ }
247
+ //#endregion
248
+ //#region src/target/public.ts
249
+ var public_exports$4 = /* @__PURE__ */ __exportAll({
250
+ Options: () => Options$1,
251
+ options: () => options$1,
252
+ release: () => release$2,
253
+ resize: () => resize,
254
+ size: () => size,
255
+ states: () => states$2
256
+ });
257
+ //#endregion
258
+ //#region src/surface/surface.ts
259
+ const Options = { default: {
260
+ alphaMode: "opaque",
261
+ colorSpace: "srgb",
262
+ toneMapping: "standard"
263
+ } };
264
+ /**
265
+ * A render target with these presents to a canvas: the canvas, on the world's thread, and how it presents. The
266
+ * canvas's size follows the target's.
267
+ */
268
+ const canvas = Symbol("Surface.canvas");
269
+ declare$$1(canvas, "component", { transfer: true });
270
+ const options = Symbol("Surface.options");
271
+ declare$$1(options, "component", { default: Options.default });
272
+ /** Each surface's configured canvas, by its entity: null until surfaces are first updated. */
273
+ const states$1 = Symbol("Surface.states");
274
+ declare$$1(states$1, "resource", {});
275
+ /**
276
+ * A surface presenting to `canvas`, moved to the world's thread, of `width` by `height` pixels: a canvas's
277
+ * `transferControlToOffscreen()`, or an `OffscreenCanvas` of its own. Resolves to its entity.
278
+ */
279
+ const attach = System.create({}, ({ commands }, input) => commands.spawn([
280
+ options$1,
281
+ size,
282
+ canvas,
283
+ options
284
+ ])({
285
+ [options$1]: input.target ?? Options$1.default,
286
+ [size]: [Math.max(1, Math.floor(input.width)), Math.max(1, Math.floor(input.height))],
287
+ [canvas]: input.canvas,
288
+ [options]: input.surface ?? Options.default
289
+ }), {
290
+ "type": "object",
291
+ "properties": {
292
+ "canvas": { "allOf": [] },
293
+ "width": { "type": "number" },
294
+ "height": { "type": "number" },
295
+ "target": {
296
+ "type": "object",
297
+ "properties": {
298
+ "format": { "anyOf": [{ "allOf": [] }, { "type": "null" }] },
299
+ "sampleCount": { "anyOf": [{ "const": 1 }, { "const": 4 }] },
300
+ "depthFormat": { "anyOf": [{ "allOf": [] }, { "type": "null" }] },
301
+ "clear": { "anyOf": [{
302
+ "type": "array",
303
+ "items": {
304
+ "type": "number",
305
+ "precision": 1
306
+ },
307
+ "minItems": 4,
308
+ "maxItems": 4
309
+ }, { "type": "null" }] }
310
+ }
311
+ },
312
+ "surface": {
313
+ "type": "object",
314
+ "properties": {
315
+ "alphaMode": { "allOf": [] },
316
+ "colorSpace": { "allOf": [] },
317
+ "toneMapping": { "allOf": [] }
318
+ }
319
+ }
320
+ },
321
+ "required": [
322
+ "canvas",
323
+ "width",
324
+ "height"
325
+ ]
326
+ });
327
+ const sameOptions = (a, b) => a.alphaMode === b.alphaMode && a.colorSpace === b.colorSpace && a.toneMapping === b.toneMapping;
328
+ /**
329
+ * Each surface's canvas sized to its target and configured for `device` in its target's format, again where either
330
+ * changed; with no device, or no target state, unconfigured. Gives `previous` itself when nothing changed.
331
+ */
332
+ const updated$1 = (device, targets, previous, surfaces) => {
333
+ const held = previous ?? /* @__PURE__ */ new Map();
334
+ let next = null;
335
+ for (let i = 0; i < surfaces.length; i++) {
336
+ const surface = surfaces.at(i);
337
+ const presented = surface[canvas];
338
+ const [width, height] = surface[size];
339
+ if (presented.width !== width) presented.width = width;
340
+ if (presented.height !== height) presented.height = height;
341
+ const target = targets.get(surface.entity);
342
+ const old = held.get(surface.entity);
343
+ if (device === null || target === void 0) {
344
+ if (old === void 0) continue;
345
+ old.context.unconfigure();
346
+ next ??= new Map(held);
347
+ next.delete(surface.entity);
348
+ continue;
349
+ }
350
+ const declared = surface[options];
351
+ if (old !== void 0 && old.device === device && old.format === target.format && old.canvas === presented && sameOptions(old.options, declared)) continue;
352
+ const context = presented.getContext("webgpu");
353
+ if (context === null) continue;
354
+ context.configure({
355
+ device,
356
+ format: target.format,
357
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST,
358
+ alphaMode: declared.alphaMode,
359
+ colorSpace: declared.colorSpace,
360
+ toneMapping: { mode: declared.toneMapping }
361
+ });
362
+ next ??= new Map(held);
363
+ next.set(surface.entity, {
364
+ entity: surface.entity,
365
+ canvas: presented,
366
+ context,
367
+ device,
368
+ format: target.format,
369
+ options: declared
370
+ });
371
+ }
372
+ return next ?? held;
373
+ };
374
+ /** Unconfigures a surface's canvas when it stops being one. */
375
+ const release$1 = Finalizer.create({ components: { with: [canvas] } }, (surface, { resources }) => {
376
+ const current = resources[states$1];
377
+ const state = current?.get(surface.entity);
378
+ if (current === null || state === void 0) return;
379
+ state.context.unconfigure();
380
+ const next = new Map(current);
381
+ next.delete(surface.entity);
382
+ resources[states$1] = next;
383
+ });
384
+ /** A canvas's size in device pixels, from what a `ResizeObserver` saw of it. */
385
+ const pixelsOf = (entry) => {
386
+ const [device] = entry.devicePixelContentBoxSize ?? [];
387
+ if (device !== void 0) return [device.inlineSize, device.blockSize];
388
+ const [box] = entry.contentBoxSize;
389
+ const scale = globalThis.devicePixelRatio ?? 1;
390
+ return box === void 0 ? [entry.contentRect.width * scale, entry.contentRect.height * scale] : [box.inlineSize * scale, box.blockSize * scale];
391
+ };
392
+ /**
393
+ * Keeps a surface's size the size of `element` on the page, in device pixels, on the thread the page is on: the world
394
+ * may be here or on a worker that joined it. Returns a function that stops.
395
+ */
396
+ const observe = (element, world, surface) => {
397
+ const observer = new ResizeObserver((entries) => {
398
+ const entry = entries.at(-1);
399
+ if (entry === void 0) return;
400
+ const [width, height] = pixelsOf(entry);
401
+ world.run(resize, {
402
+ target: surface,
403
+ width,
404
+ height
405
+ }).catch(reportError);
406
+ });
407
+ try {
408
+ observer.observe(element, { box: "device-pixel-content-box" });
409
+ } catch {
410
+ observer.observe(element);
411
+ }
412
+ return () => observer.disconnect();
413
+ };
414
+ function declare$$1(key, kind, declaration) {
415
+ (globalThis[Symbol.for("@adobe/ecs/declarations@1")] ??= /* @__PURE__ */ new Map()).set(key, {
416
+ kind,
417
+ declaration
418
+ });
419
+ }
420
+ //#endregion
421
+ //#region src/view/view.ts
422
+ /**
423
+ * An entity with these is a view: the render target it draws into, which owns it; its rectangle in that target as
424
+ * fractions of its size (`x`, `y`, `width`, `height`); and the matrix taking its scene to clip space.
425
+ */
426
+ const target = Symbol("View.target");
427
+ declare$(target, "component", {
428
+ type: {
429
+ "type": "integer",
430
+ "format": "entity"
431
+ },
432
+ reference: "owner"
433
+ });
434
+ const viewport = Symbol("View.viewport");
435
+ declare$(viewport, "component", {
436
+ type: {
437
+ "type": "array",
438
+ "items": {
439
+ "type": "number",
440
+ "precision": 1
441
+ },
442
+ "minItems": 4,
443
+ "maxItems": 4
444
+ },
445
+ default: [
446
+ 0,
447
+ 0,
448
+ 1,
449
+ 1
450
+ ]
451
+ });
452
+ const projection = Symbol("View.projection");
453
+ declare$(projection, "component", { type: {
454
+ "type": "array",
455
+ "items": {
456
+ "type": "number",
457
+ "precision": 1
458
+ },
459
+ "minItems": 16,
460
+ "maxItems": 16
461
+ } });
462
+ /** Each view's state on the world's device, by its entity: null until views are first updated. */
463
+ const states = Symbol("View.states");
464
+ declare$(states, "resource", {});
465
+ /** The uniform each view binds: its projection, then its size in pixels. */
466
+ const uniformBytes = 80;
467
+ const layouts = perDevice((device, _key) => device.createBindGroupLayout({
468
+ label: "view",
469
+ entries: [{
470
+ binding: 0,
471
+ visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE,
472
+ buffer: { type: "uniform" }
473
+ }]
474
+ }), () => "");
475
+ /** The bind group layout of a view's uniform, one per device: a pipeline's layout takes it at the group `wgsl` names. */
476
+ const layout = (device) => layouts(device, null);
477
+ /** The WGSL declarations of a view's uniform at bind group `group`: `view.projection` and `view.size`, in pixels. */
478
+ const wgsl = (group) => `struct View { projection: mat4x4f, size: vec2f }\n@group(${group}) @binding(0) var<uniform> view: View;\n`;
479
+ const rectangleOf = ([x, y, width, height], [targetWidth, targetHeight]) => {
480
+ const left = Math.min(targetWidth - 1, Math.max(0, Math.round(x * targetWidth)));
481
+ const top = Math.min(targetHeight - 1, Math.max(0, Math.round(y * targetHeight)));
482
+ return [
483
+ left,
484
+ top,
485
+ Math.max(1, Math.min(targetWidth - left, Math.round(width * targetWidth))),
486
+ Math.max(1, Math.min(targetHeight - top, Math.round(height * targetHeight)))
487
+ ];
488
+ };
489
+ const sameRectangle = (a, b) => a.every((value, index) => value === b[index]);
490
+ const sameMatrix = (a, b) => a.every((value, index) => value === b[index]);
491
+ /**
492
+ * Each view placed in its target, its uniform written where its projection or rectangle changed; a view whose target
493
+ * has no textures, as without a device, has no state. Gives `previous` itself when nothing changed.
494
+ */
495
+ const updated = (device, targets, previous, views) => {
496
+ const held = previous ?? /* @__PURE__ */ new Map();
497
+ let next = null;
498
+ for (let i = 0; i < views.length; i++) {
499
+ const view = views.at(i);
500
+ const old = held.get(view.entity);
501
+ const drawn = device === null ? void 0 : targets.get(view[target]);
502
+ if (device === null || drawn === void 0) {
503
+ if (old === void 0) continue;
504
+ old.buffer.destroy();
505
+ next ??= new Map(held);
506
+ next.delete(view.entity);
507
+ continue;
508
+ }
509
+ const rectangle = rectangleOf(view[viewport], drawn.size);
510
+ const matrix = view[projection];
511
+ if (old !== void 0 && old.target === drawn && sameRectangle(old.rectangle, rectangle) && sameMatrix(old.projection, matrix)) continue;
512
+ const kept = old !== void 0 && old.target.device === device;
513
+ if (old !== void 0 && !kept) old.buffer.destroy();
514
+ const buffer = kept ? old.buffer : device.createBuffer({
515
+ label: `view ${view.entity}`,
516
+ size: uniformBytes,
517
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
518
+ });
519
+ const bindGroup = kept ? old.bindGroup : device.createBindGroup({
520
+ layout: layout(device),
521
+ entries: [{
522
+ binding: 0,
523
+ resource: { buffer }
524
+ }]
525
+ });
526
+ device.queue.writeBuffer(buffer, 0, new Float32Array([
527
+ ...matrix,
528
+ rectangle[2],
529
+ rectangle[3],
530
+ 0,
531
+ 0
532
+ ]));
533
+ next ??= new Map(held);
534
+ next.set(view.entity, {
535
+ entity: view.entity,
536
+ target: drawn,
537
+ rectangle,
538
+ projection: matrix,
539
+ buffer,
540
+ bindGroup
541
+ });
542
+ }
543
+ return next ?? held;
544
+ };
545
+ /** Destroys a view's uniform buffer when it stops being one. */
546
+ const release = Finalizer.create({ components: { with: [
547
+ target,
548
+ viewport,
549
+ projection
550
+ ] } }, (view, { resources }) => {
551
+ const current = resources[states];
552
+ const state = current?.get(view.entity);
553
+ if (current === null || state === void 0) return;
554
+ state.buffer.destroy();
555
+ const next = new Map(current);
556
+ next.delete(view.entity);
557
+ resources[states] = next;
558
+ });
559
+ /**
560
+ * Begins a render pass drawing into a view: its target's textures, loading what they hold, limited to its rectangle,
561
+ * multisampled passes resolving into the target's color.
562
+ */
563
+ const begin = (encoder, view, label) => {
564
+ const { color, multisampled, depth } = view.target;
565
+ const pass = encoder.beginRenderPass({
566
+ label,
567
+ colorAttachments: [{
568
+ view: (multisampled ?? color).createView(),
569
+ resolveTarget: multisampled === null ? void 0 : color.createView(),
570
+ loadOp: "load",
571
+ storeOp: "store"
572
+ }],
573
+ depthStencilAttachment: depth === null ? void 0 : {
574
+ view: depth.createView(),
575
+ depthLoadOp: "load",
576
+ depthStoreOp: "store"
577
+ }
578
+ });
579
+ const [x, y, width, height] = view.rectangle;
580
+ pass.setViewport(x, y, width, height, 0, 1);
581
+ pass.setScissorRect(x, y, width, height);
582
+ return pass;
583
+ };
584
+ function declare$(key, kind, declaration) {
585
+ (globalThis[Symbol.for("@adobe/ecs/declarations@1")] ??= /* @__PURE__ */ new Map()).set(key, {
586
+ kind,
587
+ declaration
588
+ });
589
+ }
590
+ //#endregion
591
+ //#region src/frame/frame.ts
592
+ const everyTarget = Index.create({
593
+ key: [],
594
+ components: { with: [options$1, size] }
595
+ });
596
+ const everySurface = Index.create({
597
+ key: [],
598
+ components: { with: [
599
+ canvas,
600
+ options,
601
+ size
602
+ ] }
603
+ });
604
+ const everyView = Index.create({
605
+ key: [],
606
+ components: { with: [
607
+ target,
608
+ viewport,
609
+ projection
610
+ ] }
611
+ });
612
+ /**
613
+ * Brings render targets, surfaces and views up to date with their entities and the world's device, then clears each
614
+ * target that declares a clear color: its color to that color and its depth to 1. Draws go after it and before
615
+ * `present`.
616
+ */
617
+ const update = System.create({ lookup: {
618
+ targets: { index: everyTarget },
619
+ surfaces: { index: everySurface },
620
+ views: { index: everyView }
621
+ } }, ({ resources, lookup }) => {
622
+ const device = resources[gpuDevice];
623
+ const targets = updated$2(device, resources[states$2], lookup.targets());
624
+ const surfaces = updated$1(device, targets, resources[states$1], lookup.surfaces());
625
+ const views = updated(device, targets, resources[states], lookup.views());
626
+ if (targets !== resources[states$2]) resources[states$2] = targets;
627
+ if (surfaces !== resources[states$1]) resources[states$1] = surfaces;
628
+ if (views !== resources[states]) resources[states] = views;
629
+ if (device === null || targets.size === 0) return;
630
+ const encoder = device.createCommandEncoder({ label: "clear targets" });
631
+ recordClears(encoder, targets);
632
+ device.queue.submit([encoder.finish()]);
633
+ });
634
+ /**
635
+ * Copies each surface's target into its canvas, in one body, so every draw of the run is in the frame the browser
636
+ * presents, even where the run waited between them.
637
+ */
638
+ const present = System.render({
639
+ components: { with: [canvas] },
640
+ shader: { accessors: {} },
641
+ after: [update]
642
+ }, ({ encoder }, { resources }) => {
643
+ const targets = resources[states$2];
644
+ for (const surface of resources[states$1]?.values() ?? []) {
645
+ const target = targets?.get(surface.entity);
646
+ if (target === void 0 || target.device !== surface.device) continue;
647
+ const current = surface.context.getCurrentTexture();
648
+ const [width, height] = target.size;
649
+ if (current.width !== width || current.height !== height) continue;
650
+ encoder.copyTextureToTexture({ texture: target.color }, { texture: current }, [width, height]);
651
+ }
652
+ });
653
+ /** Every system of a frame, in order: targets, surfaces and views updated and targets cleared, then surfaces presented. */
654
+ const systems = System.create({ systems: [update, present] });
655
+ //#endregion
656
+ //#region src/frame/public.ts
657
+ var public_exports$2 = /* @__PURE__ */ __exportAll({
658
+ present: () => present,
659
+ systems: () => systems,
660
+ update: () => update
661
+ });
662
+ //#endregion
663
+ //#region src/surface/public.ts
664
+ var public_exports$3 = /* @__PURE__ */ __exportAll({
665
+ Options: () => Options,
666
+ attach: () => attach,
667
+ canvas: () => canvas,
668
+ observe: () => observe,
669
+ options: () => options,
670
+ release: () => release$1,
671
+ states: () => states$1
672
+ });
673
+ //#endregion
674
+ //#region src/view/public.ts
675
+ var public_exports$5 = /* @__PURE__ */ __exportAll({
676
+ begin: () => begin,
677
+ layout: () => layout,
678
+ projection: () => projection,
679
+ release: () => release,
680
+ states: () => states,
681
+ target: () => target,
682
+ viewport: () => viewport,
683
+ wgsl: () => wgsl
684
+ });
685
+ //#endregion
686
+ export { public_exports as Cache, public_exports$1 as Device, public_exports$2 as Frame, public_exports$3 as Surface, public_exports$4 as Target, public_exports$5 as View };
687
+
688
+ //# sourceMappingURL=index.js.map