@adobe/ecs-gpu 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["Options","options","declare$","states","sameOptions","updated","release","declare$","states","Target.options","Target.size","updated","release","Target.resize","Target.options","Target.size","Surface.canvas","Surface.options","View.target","View.viewport","View.projection","Target.updated","Target.states","Surface.updated","Surface.states","View.updated","View.states"],"sources":["../src/cache/cache.ts","../src/cache/public.ts","../src/device/device.ts","../src/device/public.ts","../src/target/target.ts","../src/target/public.ts","../src/surface/surface.ts","../src/view/view.ts","../src/frame/frame.ts","../src/frame/public.ts","../src/surface/public.ts","../src/view/public.ts"],"sourcesContent":["// © 2026 Adobe. MIT License. See /LICENSE for details.\n\n/**\n * A cache of what `create` makes for each device and key: a pipeline, a layout, a sampler. Its values for a device go\n * with the device, so a device a world replaced, or one that was lost, holds nothing here. Keys are compared by\n * `keyOf`, by default their JSON.\n */\nexport const perDevice = <K, V>(create: (device: GPUDevice, key: K) => V, keyOf: (key: K) => string = key => JSON.stringify(key)): ((device: GPUDevice, key: K) => V) => {\n const devices = new WeakMap<GPUDevice, Map<string, { readonly value: V }>>();\n return (device, key) => {\n let made = devices.get(device);\n if (made === undefined) {\n made = new Map();\n devices.set(device, made);\n }\n const name = keyOf(key);\n const found = made.get(name);\n if (found !== undefined) return found.value;\n const value = create(device, key);\n made.set(name, { value });\n return value;\n };\n};\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nexport { perDevice } from \"./cache.js\";\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nimport { gpuDevice, System, type World } from \"@adobe/ecs\";\n\n/** What a device is requested with: the adapter's options, and the features and limits the device needs. */\nexport type Options = GPURequestAdapterOptions & {\n readonly requiredFeatures?: readonly GPUFeatureName[];\n readonly requiredLimits?: Readonly<Record<string, number>>;\n};\n\n/**\n * A device on this thread, or null where there's no WebGPU, no adapter for `options`, or the adapter refused the\n * device. Call it on the thread the world runs on: a device can't move between threads.\n */\nexport const request = async (options: Options = {}): Promise<GPUDevice | null> => {\n const { requiredFeatures, requiredLimits, ...adapterOptions } = options;\n const adapter = await globalThis.navigator?.gpu?.requestAdapter(adapterOptions);\n if (adapter === null || adapter === undefined) return null;\n try {\n return await adapter.requestDevice({ requiredFeatures, requiredLimits });\n } catch {\n return null;\n }\n};\n\n/** Sets the world's `gpuDevice`. */\nexport const set = System.create({}, ({ resources }, input: { readonly device: GPUDevice | null }) => {\n resources[gpuDevice] = input.device;\n});\n\n/**\n * Keeps the world supplied with a device: requests one now, and another each time the one it gave is lost. Resolves\n * once the first request settled, to whether it gave the world a device, and a function that stops keeping it. Call it\n * on the world's thread.\n */\nexport const keep = async (world: Pick<World, \"run\">, options: Options = {}): Promise<{ readonly supplied: boolean; readonly stop: () => void }> => {\n let stopped = false;\n const supply = async (): Promise<boolean> => {\n const device = await request(options);\n if (device === null || stopped) return false;\n await world.run(set, { device });\n void device.lost.then(() => { if (!stopped) void supply(); });\n return true;\n };\n const supplied = await supply();\n return { supplied, stop: () => { stopped = true; } };\n};\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nexport { keep, request, set, type Options } from \"./device.js\";\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nimport { type Entity, Finalizer, System, type Vec2, type Vec4 } from \"@adobe/ecs\";\n\n/**\n * What a render target draws into: its color format, null for the platform's preferred canvas format; its samples per\n * pixel, more than one drawing multisampled and resolving into its color; its depth format, null for none; and the\n * color it's cleared to each frame, null to keep what it holds.\n */\nexport type Options = {\n readonly format: GPUTextureFormat | null;\n readonly sampleCount: 1 | 4;\n readonly depthFormat: GPUTextureFormat | null;\n readonly clear: Vec4 | null;\n};\n\nexport const Options = {\n default: { format: null, sampleCount: 1, depthFormat: \"depth24plus\", clear: [0, 0, 0, 1] },\n} as const;\n\n/**\n * A target's textures on the world's device. `color` holds what's drawn, single-sampled, for reading and presenting;\n * with more than one sample, passes draw into `multisampled` and resolve into `color`. `size` is in pixels.\n */\nexport type State = {\n readonly entity: Entity;\n readonly device: GPUDevice;\n readonly options: Options;\n readonly format: GPUTextureFormat;\n readonly size: Vec2.U32;\n readonly color: GPUTexture;\n readonly multisampled: GPUTexture | null;\n readonly depth: GPUTexture | null;\n};\n\n/** An entity with these is a render target: what its textures are, and their size in pixels. */\nexport const options = Symbol(\"Target.options\");\nexport const size = Symbol(\"Target.size\");\n/** Each render target's textures on the world's device, by its entity: null until targets are first updated. */\nexport const states = Symbol(\"Target.states\");\n\ndeclare module \"@adobe/ecs\" {\n interface Components {\n [options]: { type: Options };\n [size]: { type: Vec2.U32 };\n }\n interface Resources {\n [states]: { type: ReadonlyMap<Entity, State> | null };\n }\n}\n\nconst sameOptions = (a: Options, b: Options): boolean =>\n a.format === b.format && a.sampleCount === b.sampleCount && a.depthFormat === b.depthFormat\n && (a.clear === b.clear || (a.clear !== null && b.clear !== null && a.clear.every((value, index) => value === b.clear?.[index])));\n\nconst destroyTextures = (state: State): void => {\n state.color.destroy();\n state.multisampled?.destroy();\n state.depth?.destroy();\n};\n\nconst created = (device: GPUDevice, entity: Entity, declared: Options, [width, height]: Vec2.U32): State => {\n const format = declared.format ?? navigator.gpu.getPreferredCanvasFormat();\n const extent = [width, height];\n const color = device.createTexture({\n label: `target ${entity} color`, size: extent, format,\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,\n });\n const multisampled = declared.sampleCount === 1 ? null : device.createTexture({\n label: `target ${entity} multisampled`, size: extent, format, sampleCount: declared.sampleCount, usage: GPUTextureUsage.RENDER_ATTACHMENT,\n });\n const depth = declared.depthFormat === null ? null : device.createTexture({\n label: `target ${entity} depth`, size: extent, format: declared.depthFormat, sampleCount: declared.sampleCount,\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,\n });\n return { entity, device, options: declared, format, size: [width, height], color, multisampled, depth };\n};\n\n/** A render target as an index lists it: its entity, options and size. */\nexport type Listed = { readonly entity: Entity; readonly [options]: Options; readonly [size]: Vec2.U32 };\n\n/**\n * Each render target's textures on `device`, made again where its options, its size or the device changed, destroying\n * those they replace; with no device, none. Gives `previous` itself when nothing changed.\n */\nexport const updated = (device: GPUDevice | null, previous: ReadonlyMap<Entity, State> | null, targets: { readonly length: number; at(index: number): Listed }): ReadonlyMap<Entity, State> => {\n const held = previous ?? new Map<Entity, State>();\n let next: Map<Entity, State> | null = null;\n for (let i = 0; i < targets.length; i++) {\n const target = targets.at(i);\n const old = held.get(target.entity);\n const [width, height] = target[size];\n if (device !== null && old !== undefined && old.device === device && old.size[0] === width && old.size[1] === height && sameOptions(old.options, target[options])) continue;\n if (device === null && old === undefined) continue;\n next ??= new Map(held);\n if (old !== undefined) destroyTextures(old);\n if (device === null) next.delete(target.entity);\n else next.set(target.entity, created(device, target.entity, target[options], target[size]));\n }\n return next ?? held;\n};\n\n/** Records clearing each target that declares a clear color: its color to that color, and its depth to 1. */\nexport const recordClears = (encoder: GPUCommandEncoder, targets: ReadonlyMap<Entity, State>): void => {\n for (const target of targets.values()) {\n const color = target.options.clear;\n if (color === null) continue;\n const [r, g, b, a] = color;\n encoder.beginRenderPass({\n label: `clear target ${target.entity}`,\n colorAttachments: [{\n view: (target.multisampled ?? target.color).createView(), resolveTarget: target.multisampled === null ? undefined : target.color.createView(),\n clearValue: { r, g, b, a }, loadOp: \"clear\", storeOp: \"store\",\n }],\n depthStencilAttachment: target.depth === null ? undefined : { view: target.depth.createView(), depthClearValue: 1, depthLoadOp: \"clear\", depthStoreOp: \"store\" },\n }).end();\n }\n};\n\n/** Destroys a render target's textures when it stops being one. */\nexport const release = Finalizer.create({ components: { with: [options, size] } }, (target, { resources }) => {\n const current = resources[states];\n const state = current?.get(target.entity);\n if (current === null || state === undefined) return;\n destroyTextures(state);\n const next = new Map(current);\n next.delete(target.entity);\n resources[states] = next;\n});\n\n/** Sets a render target's size in pixels, at least one by one. */\nexport const resize = System.create({ components: { with: [size] }, target: \"target\" }, (row, _context, input: { readonly target: Entity; readonly width: number; readonly height: number }) => {\n row[size] = [Math.max(1, Math.floor(input.width)), Math.max(1, Math.floor(input.height))];\n});\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nexport { Options, options, release, resize, size, states, type State } from \"./target.js\";\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nimport { type Entity, Finalizer, System, type Vec2, type World } from \"@adobe/ecs\";\nimport * as Target from \"../target/public.js\";\n\n/** How a surface presents: how its alpha composites with the page, its color space, and its tone mapping. */\nexport type Options = {\n readonly alphaMode: GPUCanvasAlphaMode;\n readonly colorSpace: PredefinedColorSpace;\n readonly toneMapping: GPUCanvasToneMappingMode;\n};\n\nexport const Options = {\n default: { alphaMode: \"opaque\", colorSpace: \"srgb\", toneMapping: \"standard\" },\n} as const;\n\n/** A surface's canvas, configured for the world's device in its target's format. */\nexport type State = {\n readonly entity: Entity;\n readonly canvas: OffscreenCanvas;\n readonly context: GPUCanvasContext;\n readonly device: GPUDevice;\n readonly format: GPUTextureFormat;\n readonly options: Options;\n};\n\n/**\n * A render target with these presents to a canvas: the canvas, on the world's thread, and how it presents. The\n * canvas's size follows the target's.\n */\nexport const canvas = Symbol(\"Surface.canvas\");\nexport const options = Symbol(\"Surface.options\");\n/** Each surface's configured canvas, by its entity: null until surfaces are first updated. */\nexport const states = Symbol(\"Surface.states\");\n\ndeclare module \"@adobe/ecs\" {\n interface Components {\n [canvas]: { type: OffscreenCanvas; transfer: true };\n [options]: { type: Options };\n }\n interface Resources {\n [states]: { type: ReadonlyMap<Entity, State> | null };\n }\n}\n\n/**\n * A surface presenting to `canvas`, moved to the world's thread, of `width` by `height` pixels: a canvas's\n * `transferControlToOffscreen()`, or an `OffscreenCanvas` of its own. Resolves to its entity.\n */\nexport const attach = System.create({}, ({ commands }, input: {\n readonly canvas: OffscreenCanvas;\n readonly width: number;\n readonly height: number;\n readonly target?: Target.Options;\n readonly surface?: Options;\n}) => commands.spawn([Target.options, Target.size, canvas, options])({\n [Target.options]: input.target ?? Target.Options.default,\n [Target.size]: [Math.max(1, Math.floor(input.width)), Math.max(1, Math.floor(input.height))],\n [canvas]: input.canvas,\n [options]: input.surface ?? Options.default,\n}));\n\nconst sameOptions = (a: Options, b: Options): boolean => a.alphaMode === b.alphaMode && a.colorSpace === b.colorSpace && a.toneMapping === b.toneMapping;\n\n/** A surface as an index lists it: its entity, canvas, options and its target's size. */\nexport type Listed = { readonly entity: Entity; readonly [canvas]: OffscreenCanvas; readonly [options]: Options; readonly [Target.size]: Vec2.U32 };\n\n/**\n * Each surface's canvas sized to its target and configured for `device` in its target's format, again where either\n * changed; with no device, or no target state, unconfigured. Gives `previous` itself when nothing changed.\n */\nexport const updated = (device: GPUDevice | null, targets: ReadonlyMap<Entity, Target.State>, previous: ReadonlyMap<Entity, State> | null, surfaces: { readonly length: number; at(index: number): Listed }): ReadonlyMap<Entity, State> => {\n const held = previous ?? new Map<Entity, State>();\n let next: Map<Entity, State> | null = null;\n for (let i = 0; i < surfaces.length; i++) {\n const surface = surfaces.at(i);\n const presented = surface[canvas];\n const [width, height] = surface[Target.size];\n if (presented.width !== width) presented.width = width;\n if (presented.height !== height) presented.height = height;\n const target = targets.get(surface.entity);\n const old = held.get(surface.entity);\n if (device === null || target === undefined) {\n if (old === undefined) continue;\n old.context.unconfigure();\n next ??= new Map(held);\n next.delete(surface.entity);\n continue;\n }\n const declared = surface[options];\n if (old !== undefined && old.device === device && old.format === target.format && old.canvas === presented && sameOptions(old.options, declared)) continue;\n const context = presented.getContext(\"webgpu\");\n if (context === null) continue;\n context.configure({\n device, format: target.format, usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_DST,\n alphaMode: declared.alphaMode, colorSpace: declared.colorSpace, toneMapping: { mode: declared.toneMapping },\n });\n next ??= new Map(held);\n next.set(surface.entity, { entity: surface.entity, canvas: presented, context, device, format: target.format, options: declared });\n }\n return next ?? held;\n};\n\n/** Unconfigures a surface's canvas when it stops being one. */\nexport const release = Finalizer.create({ components: { with: [canvas] } }, (surface, { resources }) => {\n const current = resources[states];\n const state = current?.get(surface.entity);\n if (current === null || state === undefined) return;\n state.context.unconfigure();\n const next = new Map(current);\n next.delete(surface.entity);\n resources[states] = next;\n});\n\n/** A canvas's size in device pixels, from what a `ResizeObserver` saw of it. */\nconst pixelsOf = (entry: ResizeObserverEntry): readonly [number, number] => {\n const [device] = entry.devicePixelContentBoxSize ?? [];\n if (device !== undefined) return [device.inlineSize, device.blockSize];\n const [box] = entry.contentBoxSize;\n const scale = globalThis.devicePixelRatio ?? 1;\n return box === undefined ? [entry.contentRect.width * scale, entry.contentRect.height * scale] : [box.inlineSize * scale, box.blockSize * scale];\n};\n\n/**\n * Keeps a surface's size the size of `element` on the page, in device pixels, on the thread the page is on: the world\n * may be here or on a worker that joined it. Returns a function that stops.\n */\nexport const observe = (element: Element, world: Pick<World, \"run\">, surface: Entity): (() => void) => {\n const observer = new ResizeObserver(entries => {\n const entry = entries.at(-1);\n if (entry === undefined) return;\n const [width, height] = pixelsOf(entry);\n world.run(Target.resize, { target: surface, width, height }).catch(reportError);\n });\n try {\n observer.observe(element, { box: \"device-pixel-content-box\" });\n } catch {\n observer.observe(element);\n }\n return () => observer.disconnect();\n};\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nimport { type Entity, Finalizer, type Mat4x4, type Vec4 } from \"@adobe/ecs\";\nimport { perDevice } from \"../cache/cache.js\";\nimport * as Target from \"../target/public.js\";\n\n/**\n * A view's place on its target's device: the target, its rectangle in the target in pixels (`x`, `y`, `width`,\n * `height`), and the uniform buffer and bind group holding its projection and size, for `wgsl`'s declarations.\n */\nexport type State = {\n readonly entity: Entity;\n readonly target: Target.State;\n readonly rectangle: readonly [number, number, number, number];\n readonly projection: Mat4x4;\n readonly buffer: GPUBuffer;\n readonly bindGroup: GPUBindGroup;\n};\n\n/**\n * An entity with these is a view: the render target it draws into, which owns it; its rectangle in that target as\n * fractions of its size (`x`, `y`, `width`, `height`); and the matrix taking its scene to clip space.\n */\nexport const target = Symbol(\"View.target\");\nexport const viewport = Symbol(\"View.viewport\");\nexport const projection = Symbol(\"View.projection\");\n/** Each view's state on the world's device, by its entity: null until views are first updated. */\nexport const states = Symbol(\"View.states\");\n\ndeclare module \"@adobe/ecs\" {\n interface Components {\n [target]: { type: Entity; reference: \"owner\" };\n [viewport]: { type: Vec4; default: [0, 0, 1, 1] };\n [projection]: { type: Mat4x4 };\n }\n interface Resources {\n [states]: { type: ReadonlyMap<Entity, State> | null };\n }\n}\n\n/** The uniform each view binds: its projection, then its size in pixels. */\nconst uniformBytes = 80;\n\nconst layouts = perDevice((device: GPUDevice, _key: null) => device.createBindGroupLayout({\n label: \"view\",\n entries: [{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE, buffer: { type: \"uniform\" } }],\n}), () => \"\");\n\n/** The bind group layout of a view's uniform, one per device: a pipeline's layout takes it at the group `wgsl` names. */\nexport const layout = (device: GPUDevice): GPUBindGroupLayout => layouts(device, null);\n\n/** The WGSL declarations of a view's uniform at bind group `group`: `view.projection` and `view.size`, in pixels. */\nexport const wgsl = (group: number): string =>\n `struct View { projection: mat4x4f, size: vec2f }\\n@group(${group}) @binding(0) var<uniform> view: View;\\n`;\n\nconst rectangleOf = ([x, y, width, height]: Vec4, [targetWidth, targetHeight]: readonly [number, number]): readonly [number, number, number, number] => {\n const left = Math.min(targetWidth - 1, Math.max(0, Math.round(x * targetWidth)));\n const top = Math.min(targetHeight - 1, Math.max(0, Math.round(y * targetHeight)));\n return [left, top, Math.max(1, Math.min(targetWidth - left, Math.round(width * targetWidth))), Math.max(1, Math.min(targetHeight - top, Math.round(height * targetHeight)))];\n};\n\nconst sameRectangle = (a: readonly number[], b: readonly number[]): boolean => a.every((value, index) => value === b[index]);\nconst sameMatrix = (a: Mat4x4, b: Mat4x4): boolean => a.every((value, index) => value === b[index]);\n\n/** A view as an index lists it: its entity, target, viewport and projection. */\nexport type Listed = { readonly entity: Entity; readonly [target]: Entity; readonly [viewport]: Vec4; readonly [projection]: Mat4x4 };\n\n/**\n * Each view placed in its target, its uniform written where its projection or rectangle changed; a view whose target\n * has no textures, as without a device, has no state. Gives `previous` itself when nothing changed.\n */\nexport const updated = (device: GPUDevice | null, targets: ReadonlyMap<Entity, Target.State>, previous: ReadonlyMap<Entity, State> | null, views: { readonly length: number; at(index: number): Listed }): ReadonlyMap<Entity, State> => {\n const held = previous ?? new Map<Entity, State>();\n let next: Map<Entity, State> | null = null;\n for (let i = 0; i < views.length; i++) {\n const view = views.at(i);\n const old = held.get(view.entity);\n const drawn = device === null ? undefined : targets.get(view[target]);\n if (device === null || drawn === undefined) {\n if (old === undefined) continue;\n old.buffer.destroy();\n next ??= new Map(held);\n next.delete(view.entity);\n continue;\n }\n const rectangle = rectangleOf(view[viewport], drawn.size);\n const matrix = view[projection];\n if (old !== undefined && old.target === drawn && sameRectangle(old.rectangle, rectangle) && sameMatrix(old.projection, matrix)) continue;\n const kept = old !== undefined && old.target.device === device;\n if (old !== undefined && !kept) old.buffer.destroy();\n const buffer = kept ? old.buffer : device.createBuffer({ label: `view ${view.entity}`, size: uniformBytes, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });\n const bindGroup = kept ? old.bindGroup : device.createBindGroup({ layout: layout(device), entries: [{ binding: 0, resource: { buffer } }] });\n device.queue.writeBuffer(buffer, 0, new Float32Array([...matrix, rectangle[2], rectangle[3], 0, 0]));\n next ??= new Map(held);\n next.set(view.entity, { entity: view.entity, target: drawn, rectangle, projection: matrix, buffer, bindGroup });\n }\n return next ?? held;\n};\n\n/** Destroys a view's uniform buffer when it stops being one. */\nexport const release = Finalizer.create({ components: { with: [target, viewport, projection] } }, (view, { resources }) => {\n const current = resources[states];\n const state = current?.get(view.entity);\n if (current === null || state === undefined) return;\n state.buffer.destroy();\n const next = new Map(current);\n next.delete(view.entity);\n resources[states] = next;\n});\n\n/**\n * Begins a render pass drawing into a view: its target's textures, loading what they hold, limited to its rectangle,\n * multisampled passes resolving into the target's color.\n */\nexport const begin = (encoder: GPUCommandEncoder, view: State, label?: string): GPURenderPassEncoder => {\n const { color, multisampled, depth } = view.target;\n const pass = encoder.beginRenderPass({\n label,\n colorAttachments: [{ view: (multisampled ?? color).createView(), resolveTarget: multisampled === null ? undefined : color.createView(), loadOp: \"load\", storeOp: \"store\" }],\n depthStencilAttachment: depth === null ? undefined : { view: depth.createView(), depthLoadOp: \"load\", depthStoreOp: \"store\" },\n });\n const [x, y, width, height] = view.rectangle;\n pass.setViewport(x, y, width, height, 0, 1);\n pass.setScissorRect(x, y, width, height);\n return pass;\n};\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nimport { gpuDevice, Index, System } from \"@adobe/ecs\";\nimport * as Surface from \"../surface/surface.js\";\nimport * as Target from \"../target/target.js\";\nimport * as View from \"../view/view.js\";\n\nconst everyTarget = Index.create({ key: [], components: { with: [Target.options, Target.size] } });\nconst everySurface = Index.create({ key: [], components: { with: [Surface.canvas, Surface.options, Target.size] } });\nconst everyView = Index.create({ key: [], components: { with: [View.target, View.viewport, View.projection] } });\n\n/**\n * Brings render targets, surfaces and views up to date with their entities and the world's device, then clears each\n * target that declares a clear color: its color to that color and its depth to 1. Draws go after it and before\n * `present`.\n */\nexport const update = System.create({\n lookup: { targets: { index: everyTarget }, surfaces: { index: everySurface }, views: { index: everyView } },\n}, ({ resources, lookup }) => {\n const device = resources[gpuDevice];\n const targets = Target.updated(device, resources[Target.states], lookup.targets());\n const surfaces = Surface.updated(device, targets, resources[Surface.states], lookup.surfaces());\n const views = View.updated(device, targets, resources[View.states], lookup.views());\n if (targets !== resources[Target.states]) resources[Target.states] = targets;\n if (surfaces !== resources[Surface.states]) resources[Surface.states] = surfaces;\n if (views !== resources[View.states]) resources[View.states] = views;\n if (device === null || targets.size === 0) return;\n const encoder = device.createCommandEncoder({ label: \"clear targets\" });\n Target.recordClears(encoder, targets);\n device.queue.submit([encoder.finish()]);\n});\n\n/**\n * Copies each surface's target into its canvas, in one body, so every draw of the run is in the frame the browser\n * presents, even where the run waited between them.\n */\nexport const present = System.render({\n components: { with: [Surface.canvas] },\n shader: { accessors: {} },\n after: [update],\n}, ({ encoder }, { resources }) => {\n const targets = resources[Target.states];\n for (const surface of resources[Surface.states]?.values() ?? []) {\n const target = targets?.get(surface.entity);\n if (target === undefined || target.device !== surface.device) continue;\n const current = surface.context.getCurrentTexture();\n const [width, height] = target.size;\n if (current.width !== width || current.height !== height) continue;\n encoder.copyTextureToTexture({ texture: target.color }, { texture: current }, [width, height]);\n }\n});\n\n/** Every system of a frame, in order: targets, surfaces and views updated and targets cleared, then surfaces presented. */\nexport const systems = System.create({ systems: [update, present] });\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nexport { present, systems, update } from \"./frame.js\";\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nexport { attach, canvas, observe, Options, options, release, states, type State } from \"./surface.js\";\n","// © 2026 Adobe. MIT License. See /LICENSE for details.\nexport { begin, layout, projection, release, states, target, viewport, wgsl, type State } from \"./view.js\";\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * A cache of what `create` makes for each device and key: a pipeline, a layout, a sampler. Its values for a device go
3
+ * with the device, so a device a world replaced, or one that was lost, holds nothing here. Keys are compared by
4
+ * `keyOf`, by default their JSON.
5
+ */
6
+ export declare const perDevice: <K, V>(create: (device: GPUDevice, key: K) => V, keyOf?: (key: K) => string) => ((device: GPUDevice, key: K) => V);
@@ -0,0 +1 @@
1
+ export { perDevice } from "./cache.js";
@@ -0,0 +1,24 @@
1
+ import { System, type World } from "@adobe/ecs";
2
+ /** What a device is requested with: the adapter's options, and the features and limits the device needs. */
3
+ export type Options = GPURequestAdapterOptions & {
4
+ readonly requiredFeatures?: readonly GPUFeatureName[];
5
+ readonly requiredLimits?: Readonly<Record<string, number>>;
6
+ };
7
+ /**
8
+ * A device on this thread, or null where there's no WebGPU, no adapter for `options`, or the adapter refused the
9
+ * device. Call it on the thread the world runs on: a device can't move between threads.
10
+ */
11
+ export declare const request: (options?: Options) => Promise<GPUDevice | null>;
12
+ /** Sets the world's `gpuDevice`. */
13
+ export declare const set: System<{
14
+ readonly device: GPUDevice | null;
15
+ }, void>;
16
+ /**
17
+ * Keeps the world supplied with a device: requests one now, and another each time the one it gave is lost. Resolves
18
+ * once the first request settled, to whether it gave the world a device, and a function that stops keeping it. Call it
19
+ * on the world's thread.
20
+ */
21
+ export declare const keep: (world: Pick<World, "run">, options?: Options) => Promise<{
22
+ readonly supplied: boolean;
23
+ readonly stop: () => void;
24
+ }>;
@@ -0,0 +1 @@
1
+ export { keep, request, set, type Options } from "./device.js";
@@ -0,0 +1,14 @@
1
+ import { System } from "@adobe/ecs";
2
+ /**
3
+ * Brings render targets, surfaces and views up to date with their entities and the world's device, then clears each
4
+ * target that declares a clear color: its color to that color and its depth to 1. Draws go after it and before
5
+ * `present`.
6
+ */
7
+ export declare const update: System<undefined, void>;
8
+ /**
9
+ * Copies each surface's target into its canvas, in one body, so every draw of the run is in the frame the browser
10
+ * presents, even where the run waited between them.
11
+ */
12
+ export declare const present: System;
13
+ /** Every system of a frame, in order: targets, surfaces and views updated and targets cleared, then surfaces presented. */
14
+ export declare const systems: System<undefined>;
@@ -0,0 +1 @@
1
+ export { present, systems, update } from "./frame.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * WebGPU devices, surfaces, render targets, views and frame passes for `@adobe/ecs`.
3
+ * @module @adobe/ecs-gpu
4
+ */
5
+ export * as Cache from "./cache/public.js";
6
+ export * as Device from "./device/public.js";
7
+ export * as Frame from "./frame/public.js";
8
+ export * as Surface from "./surface/public.js";
9
+ export * as Target from "./target/public.js";
10
+ export * as View from "./view/public.js";
@@ -0,0 +1 @@
1
+ export { attach, canvas, observe, Options, options, release, states, type State } from "./surface.js";
@@ -0,0 +1,81 @@
1
+ import { type Entity, Finalizer, System, type Vec2, type World } from "@adobe/ecs";
2
+ import * as Target from "../target/public.js";
3
+ /** How a surface presents: how its alpha composites with the page, its color space, and its tone mapping. */
4
+ export type Options = {
5
+ readonly alphaMode: GPUCanvasAlphaMode;
6
+ readonly colorSpace: PredefinedColorSpace;
7
+ readonly toneMapping: GPUCanvasToneMappingMode;
8
+ };
9
+ export declare const Options: {
10
+ readonly default: {
11
+ readonly alphaMode: "opaque";
12
+ readonly colorSpace: "srgb";
13
+ readonly toneMapping: "standard";
14
+ };
15
+ };
16
+ /** A surface's canvas, configured for the world's device in its target's format. */
17
+ export type State = {
18
+ readonly entity: Entity;
19
+ readonly canvas: OffscreenCanvas;
20
+ readonly context: GPUCanvasContext;
21
+ readonly device: GPUDevice;
22
+ readonly format: GPUTextureFormat;
23
+ readonly options: Options;
24
+ };
25
+ /**
26
+ * A render target with these presents to a canvas: the canvas, on the world's thread, and how it presents. The
27
+ * canvas's size follows the target's.
28
+ */
29
+ export declare const canvas: unique symbol;
30
+ export declare const options: unique symbol;
31
+ /** Each surface's configured canvas, by its entity: null until surfaces are first updated. */
32
+ export declare const states: unique symbol;
33
+ declare module "@adobe/ecs" {
34
+ interface Components {
35
+ [canvas]: {
36
+ type: OffscreenCanvas;
37
+ transfer: true;
38
+ };
39
+ [options]: {
40
+ type: Options;
41
+ };
42
+ }
43
+ interface Resources {
44
+ [states]: {
45
+ type: ReadonlyMap<Entity, State> | null;
46
+ };
47
+ }
48
+ }
49
+ /**
50
+ * A surface presenting to `canvas`, moved to the world's thread, of `width` by `height` pixels: a canvas's
51
+ * `transferControlToOffscreen()`, or an `OffscreenCanvas` of its own. Resolves to its entity.
52
+ */
53
+ export declare const attach: System<{
54
+ readonly canvas: OffscreenCanvas;
55
+ readonly width: number;
56
+ readonly height: number;
57
+ readonly target?: Target.Options;
58
+ readonly surface?: Options;
59
+ }, Entity>;
60
+ /** A surface as an index lists it: its entity, canvas, options and its target's size. */
61
+ export type Listed = {
62
+ readonly entity: Entity;
63
+ readonly [canvas]: OffscreenCanvas;
64
+ readonly [options]: Options;
65
+ readonly [Target.size]: Vec2.U32;
66
+ };
67
+ /**
68
+ * Each surface's canvas sized to its target and configured for `device` in its target's format, again where either
69
+ * changed; with no device, or no target state, unconfigured. Gives `previous` itself when nothing changed.
70
+ */
71
+ export declare const updated: (device: GPUDevice | null, targets: ReadonlyMap<Entity, Target.State>, previous: ReadonlyMap<Entity, State> | null, surfaces: {
72
+ readonly length: number;
73
+ at(index: number): Listed;
74
+ }) => ReadonlyMap<Entity, State>;
75
+ /** Unconfigures a surface's canvas when it stops being one. */
76
+ export declare const release: Finalizer;
77
+ /**
78
+ * Keeps a surface's size the size of `element` on the page, in device pixels, on the thread the page is on: the world
79
+ * may be here or on a worker that joined it. Returns a function that stops.
80
+ */
81
+ export declare const observe: (element: Element, world: Pick<World, "run">, surface: Entity) => (() => void);
@@ -0,0 +1 @@
1
+ export { Options, options, release, resize, size, states, type State } from "./target.js";
@@ -0,0 +1,78 @@
1
+ import { type Entity, Finalizer, System, type Vec2, type Vec4 } from "@adobe/ecs";
2
+ /**
3
+ * What a render target draws into: its color format, null for the platform's preferred canvas format; its samples per
4
+ * pixel, more than one drawing multisampled and resolving into its color; its depth format, null for none; and the
5
+ * color it's cleared to each frame, null to keep what it holds.
6
+ */
7
+ export type Options = {
8
+ readonly format: GPUTextureFormat | null;
9
+ readonly sampleCount: 1 | 4;
10
+ readonly depthFormat: GPUTextureFormat | null;
11
+ readonly clear: Vec4 | null;
12
+ };
13
+ export declare const Options: {
14
+ readonly default: {
15
+ readonly format: null;
16
+ readonly sampleCount: 1;
17
+ readonly depthFormat: "depth24plus";
18
+ readonly clear: readonly [0, 0, 0, 1];
19
+ };
20
+ };
21
+ /**
22
+ * A target's textures on the world's device. `color` holds what's drawn, single-sampled, for reading and presenting;
23
+ * with more than one sample, passes draw into `multisampled` and resolve into `color`. `size` is in pixels.
24
+ */
25
+ export type State = {
26
+ readonly entity: Entity;
27
+ readonly device: GPUDevice;
28
+ readonly options: Options;
29
+ readonly format: GPUTextureFormat;
30
+ readonly size: Vec2.U32;
31
+ readonly color: GPUTexture;
32
+ readonly multisampled: GPUTexture | null;
33
+ readonly depth: GPUTexture | null;
34
+ };
35
+ /** An entity with these is a render target: what its textures are, and their size in pixels. */
36
+ export declare const options: unique symbol;
37
+ export declare const size: unique symbol;
38
+ /** Each render target's textures on the world's device, by its entity: null until targets are first updated. */
39
+ export declare const states: unique symbol;
40
+ declare module "@adobe/ecs" {
41
+ interface Components {
42
+ [options]: {
43
+ type: Options;
44
+ };
45
+ [size]: {
46
+ type: Vec2.U32;
47
+ };
48
+ }
49
+ interface Resources {
50
+ [states]: {
51
+ type: ReadonlyMap<Entity, State> | null;
52
+ };
53
+ }
54
+ }
55
+ /** A render target as an index lists it: its entity, options and size. */
56
+ export type Listed = {
57
+ readonly entity: Entity;
58
+ readonly [options]: Options;
59
+ readonly [size]: Vec2.U32;
60
+ };
61
+ /**
62
+ * Each render target's textures on `device`, made again where its options, its size or the device changed, destroying
63
+ * those they replace; with no device, none. Gives `previous` itself when nothing changed.
64
+ */
65
+ export declare const updated: (device: GPUDevice | null, previous: ReadonlyMap<Entity, State> | null, targets: {
66
+ readonly length: number;
67
+ at(index: number): Listed;
68
+ }) => ReadonlyMap<Entity, State>;
69
+ /** Records clearing each target that declares a clear color: its color to that color, and its depth to 1. */
70
+ export declare const recordClears: (encoder: GPUCommandEncoder, targets: ReadonlyMap<Entity, State>) => void;
71
+ /** Destroys a render target's textures when it stops being one. */
72
+ export declare const release: Finalizer;
73
+ /** Sets a render target's size in pixels, at least one by one. */
74
+ export declare const resize: System<{
75
+ readonly target: Entity;
76
+ readonly width: number;
77
+ readonly height: number;
78
+ }, void>;
@@ -0,0 +1 @@
1
+ export { begin, layout, projection, release, states, target, viewport, wgsl, type State } from "./view.js";
@@ -0,0 +1,69 @@
1
+ import { type Entity, Finalizer, type Mat4x4, type Vec4 } from "@adobe/ecs";
2
+ import * as Target from "../target/public.js";
3
+ /**
4
+ * A view's place on its target's device: the target, its rectangle in the target in pixels (`x`, `y`, `width`,
5
+ * `height`), and the uniform buffer and bind group holding its projection and size, for `wgsl`'s declarations.
6
+ */
7
+ export type State = {
8
+ readonly entity: Entity;
9
+ readonly target: Target.State;
10
+ readonly rectangle: readonly [number, number, number, number];
11
+ readonly projection: Mat4x4;
12
+ readonly buffer: GPUBuffer;
13
+ readonly bindGroup: GPUBindGroup;
14
+ };
15
+ /**
16
+ * An entity with these is a view: the render target it draws into, which owns it; its rectangle in that target as
17
+ * fractions of its size (`x`, `y`, `width`, `height`); and the matrix taking its scene to clip space.
18
+ */
19
+ export declare const target: unique symbol;
20
+ export declare const viewport: unique symbol;
21
+ export declare const projection: unique symbol;
22
+ /** Each view's state on the world's device, by its entity: null until views are first updated. */
23
+ export declare const states: unique symbol;
24
+ declare module "@adobe/ecs" {
25
+ interface Components {
26
+ [target]: {
27
+ type: Entity;
28
+ reference: "owner";
29
+ };
30
+ [viewport]: {
31
+ type: Vec4;
32
+ default: [0, 0, 1, 1];
33
+ };
34
+ [projection]: {
35
+ type: Mat4x4;
36
+ };
37
+ }
38
+ interface Resources {
39
+ [states]: {
40
+ type: ReadonlyMap<Entity, State> | null;
41
+ };
42
+ }
43
+ }
44
+ /** The bind group layout of a view's uniform, one per device: a pipeline's layout takes it at the group `wgsl` names. */
45
+ export declare const layout: (device: GPUDevice) => GPUBindGroupLayout;
46
+ /** The WGSL declarations of a view's uniform at bind group `group`: `view.projection` and `view.size`, in pixels. */
47
+ export declare const wgsl: (group: number) => string;
48
+ /** A view as an index lists it: its entity, target, viewport and projection. */
49
+ export type Listed = {
50
+ readonly entity: Entity;
51
+ readonly [target]: Entity;
52
+ readonly [viewport]: Vec4;
53
+ readonly [projection]: Mat4x4;
54
+ };
55
+ /**
56
+ * Each view placed in its target, its uniform written where its projection or rectangle changed; a view whose target
57
+ * has no textures, as without a device, has no state. Gives `previous` itself when nothing changed.
58
+ */
59
+ export declare const updated: (device: GPUDevice | null, targets: ReadonlyMap<Entity, Target.State>, previous: ReadonlyMap<Entity, State> | null, views: {
60
+ readonly length: number;
61
+ at(index: number): Listed;
62
+ }) => ReadonlyMap<Entity, State>;
63
+ /** Destroys a view's uniform buffer when it stops being one. */
64
+ export declare const release: Finalizer;
65
+ /**
66
+ * Begins a render pass drawing into a view: its target's textures, loading what they hold, limited to its rectangle,
67
+ * multisampled passes resolving into the target's color.
68
+ */
69
+ export declare const begin: (encoder: GPUCommandEncoder, view: State, label?: string) => GPURenderPassEncoder;
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@adobe/ecs-gpu",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.0.1",
4
+ "description": "WebGPU devices, surfaces, render targets, views and frame passes for @adobe/ecs",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": true,
8
+ "private": false,
9
+ "independentVersion": true,
10
+ "publishConfig": {
11
+ "registry": "https://registry.npmjs.org/",
12
+ "access": "public"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src",
17
+ "!src/**/*.test.ts",
18
+ "!src/**/*.test-helper.ts",
19
+ "!src/**/*.test-worker.ts",
20
+ "LICENSE"
21
+ ],
22
+ "dependencies": {
23
+ "@adobe/ecs": "0.2.1"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.6.0",
27
+ "@vitest/browser-playwright": "^5.0.5",
28
+ "@webgpu/types": "^0.1.61",
29
+ "playwright": "^1.64.1",
30
+ "typescript": "^5.8.3",
31
+ "vite": "^8.3.4",
32
+ "vitest": "^5.0.5"
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "import": "./dist/index.js",
37
+ "types": "./dist/types/index.d.ts"
38
+ }
39
+ },
40
+ "scripts": {
41
+ "build": "cp ../../LICENSE . && tsc -p tsconfig.build.json && vite build",
42
+ "typecheck": "tsc && tsc -p tsconfig.build.json",
43
+ "test": "pnpm exec playwright install chromium && vitest --run"
44
+ }
6
45
  }
@@ -0,0 +1,23 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+
3
+ /**
4
+ * A cache of what `create` makes for each device and key: a pipeline, a layout, a sampler. Its values for a device go
5
+ * with the device, so a device a world replaced, or one that was lost, holds nothing here. Keys are compared by
6
+ * `keyOf`, by default their JSON.
7
+ */
8
+ export const perDevice = <K, V>(create: (device: GPUDevice, key: K) => V, keyOf: (key: K) => string = key => JSON.stringify(key)): ((device: GPUDevice, key: K) => V) => {
9
+ const devices = new WeakMap<GPUDevice, Map<string, { readonly value: V }>>();
10
+ return (device, key) => {
11
+ let made = devices.get(device);
12
+ if (made === undefined) {
13
+ made = new Map();
14
+ devices.set(device, made);
15
+ }
16
+ const name = keyOf(key);
17
+ const found = made.get(name);
18
+ if (found !== undefined) return found.value;
19
+ const value = create(device, key);
20
+ made.set(name, { value });
21
+ return value;
22
+ };
23
+ };
@@ -0,0 +1,2 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export { perDevice } from "./cache.js";
@@ -0,0 +1,46 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { gpuDevice, System, type World } from "@adobe/ecs";
3
+
4
+ /** What a device is requested with: the adapter's options, and the features and limits the device needs. */
5
+ export type Options = GPURequestAdapterOptions & {
6
+ readonly requiredFeatures?: readonly GPUFeatureName[];
7
+ readonly requiredLimits?: Readonly<Record<string, number>>;
8
+ };
9
+
10
+ /**
11
+ * A device on this thread, or null where there's no WebGPU, no adapter for `options`, or the adapter refused the
12
+ * device. Call it on the thread the world runs on: a device can't move between threads.
13
+ */
14
+ export const request = async (options: Options = {}): Promise<GPUDevice | null> => {
15
+ const { requiredFeatures, requiredLimits, ...adapterOptions } = options;
16
+ const adapter = await globalThis.navigator?.gpu?.requestAdapter(adapterOptions);
17
+ if (adapter === null || adapter === undefined) return null;
18
+ try {
19
+ return await adapter.requestDevice({ requiredFeatures, requiredLimits });
20
+ } catch {
21
+ return null;
22
+ }
23
+ };
24
+
25
+ /** Sets the world's `gpuDevice`. */
26
+ export const set = System.create({}, ({ resources }, input: { readonly device: GPUDevice | null }) => {
27
+ resources[gpuDevice] = input.device;
28
+ });
29
+
30
+ /**
31
+ * Keeps the world supplied with a device: requests one now, and another each time the one it gave is lost. Resolves
32
+ * once the first request settled, to whether it gave the world a device, and a function that stops keeping it. Call it
33
+ * on the world's thread.
34
+ */
35
+ export const keep = async (world: Pick<World, "run">, options: Options = {}): Promise<{ readonly supplied: boolean; readonly stop: () => void }> => {
36
+ let stopped = false;
37
+ const supply = async (): Promise<boolean> => {
38
+ const device = await request(options);
39
+ if (device === null || stopped) return false;
40
+ await world.run(set, { device });
41
+ void device.lost.then(() => { if (!stopped) void supply(); });
42
+ return true;
43
+ };
44
+ const supplied = await supply();
45
+ return { supplied, stop: () => { stopped = true; } };
46
+ };
@@ -0,0 +1,2 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export { keep, request, set, type Options } from "./device.js";
@@ -0,0 +1,53 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { gpuDevice, Index, System } from "@adobe/ecs";
3
+ import * as Surface from "../surface/surface.js";
4
+ import * as Target from "../target/target.js";
5
+ import * as View from "../view/view.js";
6
+
7
+ const everyTarget = Index.create({ key: [], components: { with: [Target.options, Target.size] } });
8
+ const everySurface = Index.create({ key: [], components: { with: [Surface.canvas, Surface.options, Target.size] } });
9
+ const everyView = Index.create({ key: [], components: { with: [View.target, View.viewport, View.projection] } });
10
+
11
+ /**
12
+ * Brings render targets, surfaces and views up to date with their entities and the world's device, then clears each
13
+ * target that declares a clear color: its color to that color and its depth to 1. Draws go after it and before
14
+ * `present`.
15
+ */
16
+ export const update = System.create({
17
+ lookup: { targets: { index: everyTarget }, surfaces: { index: everySurface }, views: { index: everyView } },
18
+ }, ({ resources, lookup }) => {
19
+ const device = resources[gpuDevice];
20
+ const targets = Target.updated(device, resources[Target.states], lookup.targets());
21
+ const surfaces = Surface.updated(device, targets, resources[Surface.states], lookup.surfaces());
22
+ const views = View.updated(device, targets, resources[View.states], lookup.views());
23
+ if (targets !== resources[Target.states]) resources[Target.states] = targets;
24
+ if (surfaces !== resources[Surface.states]) resources[Surface.states] = surfaces;
25
+ if (views !== resources[View.states]) resources[View.states] = views;
26
+ if (device === null || targets.size === 0) return;
27
+ const encoder = device.createCommandEncoder({ label: "clear targets" });
28
+ Target.recordClears(encoder, targets);
29
+ device.queue.submit([encoder.finish()]);
30
+ });
31
+
32
+ /**
33
+ * Copies each surface's target into its canvas, in one body, so every draw of the run is in the frame the browser
34
+ * presents, even where the run waited between them.
35
+ */
36
+ export const present = System.render({
37
+ components: { with: [Surface.canvas] },
38
+ shader: { accessors: {} },
39
+ after: [update],
40
+ }, ({ encoder }, { resources }) => {
41
+ const targets = resources[Target.states];
42
+ for (const surface of resources[Surface.states]?.values() ?? []) {
43
+ const target = targets?.get(surface.entity);
44
+ if (target === undefined || target.device !== surface.device) continue;
45
+ const current = surface.context.getCurrentTexture();
46
+ const [width, height] = target.size;
47
+ if (current.width !== width || current.height !== height) continue;
48
+ encoder.copyTextureToTexture({ texture: target.color }, { texture: current }, [width, height]);
49
+ }
50
+ });
51
+
52
+ /** Every system of a frame, in order: targets, surfaces and views updated and targets cleared, then surfaces presented. */
53
+ export const systems = System.create({ systems: [update, present] });
@@ -0,0 +1,2 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export { present, systems, update } from "./frame.js";
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ /**
3
+ * WebGPU devices, surfaces, render targets, views and frame passes for `@adobe/ecs`.
4
+ * @module @adobe/ecs-gpu
5
+ */
6
+ export * as Cache from "./cache/public.js";
7
+ export * as Device from "./device/public.js";
8
+ export * as Frame from "./frame/public.js";
9
+ export * as Surface from "./surface/public.js";
10
+ export * as Target from "./target/public.js";
11
+ export * as View from "./view/public.js";
@@ -0,0 +1,2 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ export { attach, canvas, observe, Options, options, release, states, type State } from "./surface.js";