gpucanvas 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +128 -2
- package/dist/chunk-VOFDBN5J.js +1715 -0
- package/dist/chunk-VOFDBN5J.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +26 -0
- package/dist/react.js +67 -0
- package/dist/react.js.map +1 -0
- package/dist/types-D8v10v6m.d.ts +101 -0
- package/package.json +74 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/camera.ts","../src/node-style.ts","../src/types.ts","../src/dom-backend.ts","../src/atlas.ts","../src/composite-shader.ts","../src/gpu-renderer.ts","../src/page.ts","../src/gpu-backend.ts","../src/index.ts"],"sourcesContent":["export interface Camera {\n /** World coordinate shown at the viewport's top-left corner. */\n x: number;\n y: number;\n zoom: number;\n}\n\nexport interface Rect {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport const MIN_ZOOM = 0.02;\nexport const MAX_ZOOM = 16;\n\nconst clampZoom = (zoom: number) => Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, zoom));\n\n/** Zooms by `factor` while keeping the world point under (screenX, screenY) fixed. */\nexport function zoomAt(camera: Camera, screenX: number, screenY: number, factor: number): Camera {\n const zoom = clampZoom(camera.zoom * factor);\n const worldX = camera.x + screenX / camera.zoom;\n const worldY = camera.y + screenY / camera.zoom;\n return { x: worldX - screenX / zoom, y: worldY - screenY / zoom, zoom };\n}\n\nexport function visibleWorldRect(\n camera: Camera,\n viewportWidth: number,\n viewportHeight: number,\n): Rect {\n return {\n x: camera.x,\n y: camera.y,\n width: viewportWidth / camera.zoom,\n height: viewportHeight / camera.zoom,\n };\n}\n\nexport const intersects = (a: Rect, b: Rect) =>\n a.x < b.x + b.width && b.x < a.x + a.width && a.y < b.y + b.height && b.y < a.y + a.height;\n\n/** Fits `bounds` into the viewport with `padding` CSS pixels on each side. */\nexport function fitCamera(\n bounds: Rect,\n viewportWidth: number,\n viewportHeight: number,\n padding = 40,\n): Camera {\n const zoom = clampZoom(\n Math.min(\n (viewportWidth - padding * 2) / Math.max(1, bounds.width),\n (viewportHeight - padding * 2) / Math.max(1, bounds.height),\n ),\n );\n return {\n x: bounds.x + bounds.width / 2 - viewportWidth / 2 / zoom,\n y: bounds.y + bounds.height / 2 - viewportHeight / 2 / zoom,\n zoom,\n };\n}\n\n/**\n * Wheel and drag input: trackpad scroll pans, pinch or ctrl/cmd + wheel zooms, dragging empty\n * space pans. Calls `onChange` with the next camera; the caller owns the camera value.\n */\nexport function bindCameraInput(\n element: HTMLElement,\n getCamera: () => Camera,\n onChange: (camera: Camera) => void,\n): () => void {\n const onWheel = (event: WheelEvent) => {\n event.preventDefault();\n const bounds = element.getBoundingClientRect();\n const camera = getCamera();\n if (event.ctrlKey || event.metaKey) {\n const factor = Math.exp(-event.deltaY * (event.deltaMode === 1 ? 0.05 : 0.01));\n onChange(zoomAt(camera, event.clientX - bounds.left, event.clientY - bounds.top, factor));\n } else {\n onChange({\n ...camera,\n x: camera.x + event.deltaX / camera.zoom,\n y: camera.y + event.deltaY / camera.zoom,\n });\n }\n };\n\n let drag: { pointerId: number; x: number; y: number } | null = null;\n const onPointerDown = (event: PointerEvent) => {\n // Only drags that start on the canvas background pan; nodes keep their own pointer events.\n // Shift-drags are left to the app, for a selection box.\n if ((event.button !== 0 && event.button !== 1) || event.shiftKey) return;\n if (\n event.target !== element &&\n !(event.target as Element).hasAttribute?.('data-gpucanvas-surface')\n )\n return;\n drag = { pointerId: event.pointerId, x: event.clientX, y: event.clientY };\n element.setPointerCapture(event.pointerId);\n };\n const onPointerMove = (event: PointerEvent) => {\n if (!drag || event.pointerId !== drag.pointerId) return;\n const camera = getCamera();\n onChange({\n ...camera,\n x: camera.x - (event.clientX - drag.x) / camera.zoom,\n y: camera.y - (event.clientY - drag.y) / camera.zoom,\n });\n drag.x = event.clientX;\n drag.y = event.clientY;\n };\n const onPointerUp = (event: PointerEvent) => {\n if (drag?.pointerId === event.pointerId) drag = null;\n };\n\n element.addEventListener('wheel', onWheel, { passive: false });\n element.addEventListener('pointerdown', onPointerDown);\n element.addEventListener('pointermove', onPointerMove);\n element.addEventListener('pointerup', onPointerUp);\n element.addEventListener('pointercancel', onPointerUp);\n return () => {\n element.removeEventListener('wheel', onWheel);\n element.removeEventListener('pointerdown', onPointerDown);\n element.removeEventListener('pointermove', onPointerMove);\n element.removeEventListener('pointerup', onPointerUp);\n element.removeEventListener('pointercancel', onPointerUp);\n };\n}\n","import type { Rect } from './camera';\n\n/** Sizes a node's element; containment keeps one node's changes from relaying out the others. */\nexport function applyNodeSize(element: HTMLElement, rect: Rect) {\n element.style.width = `${rect.width}px`;\n element.style.height = `${rect.height}px`;\n}\n\nexport function applyNodeBase(element: HTMLElement) {\n element.style.boxSizing = 'border-box';\n element.style.contain = 'strict';\n element.style.overflow = 'hidden';\n}\n\nexport function boundsOf(rects: Iterable<Rect>): Rect | null {\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const rect of rects) {\n minX = Math.min(minX, rect.x);\n minY = Math.min(minY, rect.y);\n maxX = Math.max(maxX, rect.x + rect.width);\n maxY = Math.max(maxY, rect.y + rect.height);\n }\n return minX === Infinity ? null : { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n","import type { Camera, Rect } from './camera';\n\nexport type GpuCanvasMode = 'gpu' | 'dom';\n\nexport interface GpuCanvasOptions {\n /** `auto` uses the GPU path when WebGPU and HTML-in-Canvas are available, otherwise DOM. */\n mode?: 'auto' | GpuCanvasMode;\n /** Canvas background, any CSS color. */\n background?: string;\n /** Outline of selected nodes, any CSS color. Default `#4f8cff`. */\n selectionColor?: string;\n camera?: Camera;\n /** Side of each atlas layer in texels. Default 4096, capped by the device. */\n atlasSize?: number;\n /** Atlas layers; each 4096 layer costs 64 MB of GPU memory. One holds thumbnails. Default 5. */\n atlasLayers?: number;\n /** Most snapshot pixels rasterized per frame; the rest wait for later frames. Default 24M. */\n rasterPixelBudget?: number;\n /** Most raster calls per frame; one call covers a whole page or one node. Default 12. */\n maxRastersPerFrame?: number;\n /** Main-thread milliseconds a frame may spend issuing rasters. Default 6. */\n rasterTimeBudgetMs?: number;\n /** Side of a page, the unit nodes are batched into for rasterizing, in CSS pixels. Default 2048. */\n pageSize?: number;\n onCameraChange?: (camera: Camera) => void;\n}\n\nexport interface GpuCanvasStats {\n mode: GpuCanvasMode;\n nodes: number;\n visible: number;\n /** Visible nodes still waiting for a snapshot at the current zoom. */\n pending: number;\n /** Nodes that got fresh pixels in the last frame. */\n rasterizedLastFrame: number;\n /** Raster calls in the last frame (gpu only). */\n rasterCallsLastFrame?: number;\n /** Main-thread milliseconds the last frame spent issuing rasters. */\n rasterMs: number;\n /** Pages nodes are batched into, and how many are drawable right now (gpu only). */\n pages?: number;\n drawablePages?: number;\n atlasLayersUsed: number;\n /** Main-thread milliseconds the last composite took. */\n frameMs: number;\n /** Frames composited since creation (gpu only). An idle canvas draws none. */\n frames?: number;\n /** Times a visible node that had pixels showed a placeholder instead: visible flicker. */\n lostPixels?: number;\n /** Times a sharp zoomed-in node fell back to its blurrier whole snapshot. */\n lostDetail?: number;\n /** Share of the thumbnail layer in use, 0 to 1 (gpu only). */\n thumbnailFill?: number;\n /** Visible nodes showing only their thumbnail while a sharper snapshot is drawn (gpu only). */\n onThumbnail?: number;\n /** Visible nodes the last frame planned to rasterize: missing, dirty, blurry, oversampled. */\n needs?: number[];\n}\n\nexport interface GpuCanvas {\n readonly mode: GpuCanvasMode;\n /** The element that receives camera input; the host's only child. */\n readonly surface: HTMLElement;\n /** Places `element` on the canvas at `rect` (world units). The element keeps its own DOM and events. */\n addNode(element: HTMLElement, rect: Rect): void;\n updateNode(element: HTMLElement, rect: Rect): void;\n removeNode(element: HTMLElement): void;\n /** Where `element` sits on the canvas, in world units, or null when it is not a node. */\n getNodeRect(element: HTMLElement): Rect | null;\n /** Nodes that overlap `rect` (world units), such as the ones under a selection box. */\n getNodesIn(rect: Rect): HTMLElement[];\n /** Outlines these nodes as selected, replacing the previous selection. Pass `[]` to clear. */\n setSelection(elements: Iterable<HTMLElement>): void;\n getCamera(): Camera;\n setCamera(camera: Camera): void;\n /**\n * Redraws every node. Call it after a change the nodes' own DOM does not show, such as a theme\n * class on `<html>` or a web font finishing loading.\n */\n invalidate(): void;\n /** Changes the canvas background, any CSS color. */\n setBackground(color: string): void;\n /** Fits all nodes in view. */\n fit(padding?: number): void;\n getStats(): GpuCanvasStats;\n destroy(): void;\n}\n\nexport const DEFAULT_CAMERA: Camera = { x: 0, y: 0, zoom: 1 };\nexport const DEFAULT_SELECTION_COLOR = '#4f8cff';\n/** Width of the selection outline, in CSS pixels at any zoom. */\nexport const SELECTION_WIDTH = 2;\n","import {\n bindCameraInput,\n fitCamera,\n intersects,\n visibleWorldRect,\n type Camera,\n type Rect,\n} from './camera';\nimport { applyNodeBase, applyNodeSize, boundsOf } from './node-style';\nimport {\n DEFAULT_CAMERA,\n DEFAULT_SELECTION_COLOR,\n SELECTION_WIDTH,\n type GpuCanvas,\n type GpuCanvasOptions,\n} from './types';\n\n/**\n * The fallback, and the way most infinite canvases work today: every node is absolutely\n * positioned inside one world element that the camera moves with a CSS transform.\n */\nexport function createDomCanvas(host: HTMLElement, options: GpuCanvasOptions): GpuCanvas {\n const surface = document.createElement('div');\n surface.dataset.gpucanvasSurface = '';\n Object.assign(surface.style, {\n position: 'relative',\n width: '100%',\n height: '100%',\n overflow: 'hidden',\n touchAction: 'none',\n background: options.background ?? 'transparent',\n });\n const world = document.createElement('div');\n world.dataset.gpucanvasSurface = '';\n Object.assign(world.style, {\n position: 'absolute',\n left: '0',\n top: '0',\n transformOrigin: '0 0',\n willChange: 'transform',\n });\n surface.append(world);\n host.append(surface);\n\n const nodes = new Map<HTMLElement, Rect>();\n let selected = new Set<HTMLElement>();\n // Outlines live inside the zoomed world, so their width divides the zoom back out.\n const outline = `calc(${SELECTION_WIDTH}px / var(--gpucanvas-zoom)) solid ${\n options.selectionColor ?? DEFAULT_SELECTION_COLOR\n }`;\n let camera = options.camera ?? DEFAULT_CAMERA;\n let frameMs = 0;\n\n const apply = () => {\n const start = performance.now();\n world.style.transform = `scale(${camera.zoom}) translate(${-camera.x}px, ${-camera.y}px)`;\n world.style.setProperty('--gpucanvas-zoom', String(camera.zoom));\n frameMs = performance.now() - start;\n };\n const setCamera = (next: Camera) => {\n camera = next;\n apply();\n options.onCameraChange?.(camera);\n };\n const unbindInput = bindCameraInput(surface, () => camera, setCamera);\n apply();\n\n return {\n mode: 'dom',\n surface,\n addNode(element, rect) {\n applyNodeBase(element);\n element.style.position = 'absolute';\n nodes.set(element, rect);\n this.updateNode(element, rect);\n world.append(element);\n },\n updateNode(element, rect) {\n nodes.set(element, rect);\n element.style.left = `${rect.x}px`;\n element.style.top = `${rect.y}px`;\n applyNodeSize(element, rect);\n },\n removeNode(element) {\n nodes.delete(element);\n selected.delete(element);\n element.remove();\n },\n getNodeRect: (element) => nodes.get(element) ?? null,\n getNodesIn(rect) {\n return [...nodes].filter(([, node]) => intersects(node, rect)).map(([element]) => element);\n },\n setSelection(elements) {\n const next = new Set([...elements].filter((element) => nodes.has(element)));\n for (const element of selected) if (!next.has(element)) element.style.outline = '';\n for (const element of next) if (!selected.has(element)) element.style.outline = outline;\n selected = next;\n },\n getCamera: () => camera,\n setCamera,\n // The browser repaints DOM nodes itself.\n invalidate() {},\n setBackground(color) {\n surface.style.background = color;\n },\n fit(padding) {\n const bounds = boundsOf(nodes.values());\n if (bounds) setCamera(fitCamera(bounds, surface.clientWidth, surface.clientHeight, padding));\n },\n getStats() {\n const view = visibleWorldRect(camera, surface.clientWidth, surface.clientHeight);\n let visible = 0;\n for (const rect of nodes.values()) if (intersects(rect, view)) visible++;\n return {\n mode: 'dom',\n nodes: nodes.size,\n visible,\n pending: 0,\n rasterizedLastFrame: 0,\n rasterMs: 0,\n atlasLayersUsed: 0,\n frameMs,\n };\n },\n destroy() {\n unbindInput();\n surface.remove();\n },\n };\n}\n","/** A rectangle in one layer of the atlas texture array, in texels. */\nexport interface AtlasSlot {\n layer: number;\n x: number;\n y: number;\n width: number;\n height: number;\n /** Bumped when the layer is evicted; a slot is valid while it matches the layer's generation. */\n generation: number;\n}\n\ninterface Shelf {\n y: number;\n height: number;\n x: number;\n}\n\ninterface Layer {\n shelves: Shelf[];\n nextY: number;\n generation: number;\n /** Frame number of the last allocation in, or draw from, this layer; the stalest is evicted. */\n lastUsed: number;\n /** Frame number of the last allocation; a layer filled this frame is never evicted. */\n lastAllocated: number;\n}\n\nconst PADDING = 1;\n\n/**\n * Shelf packer over the layers of one texture array. Nothing is freed individually: when every\n * layer is full, the least recently used layer is recycled. Its slots that are still on screen\n * (`keep`) are first copied into a spare layer on the GPU, so they never flash; the rest become\n * invalid and are rasterized again the next time they are visible. The recycled layer becomes\n * the new spare.\n *\n * Layer 0 is pinned: it holds small thumbnails that are never recycled (`allocatePinned`), so\n * every node keeps some pixels however much the other layers churn.\n */\nexport class Atlas {\n readonly texture: GPUTexture;\n readonly view: GPUTextureView;\n readonly size: number;\n /** Slots to rescue when their layer is recycled, most important first. */\n keep: () => Iterable<AtlasSlot | null> = () => [];\n private layers: Layer[];\n private spare: number;\n private frame = 0;\n\n constructor(\n private readonly device: GPUDevice,\n size: number,\n layerCount: number,\n ) {\n this.size = size;\n this.texture = device.createTexture({\n label: 'gpucanvas atlas',\n size: [size, size, layerCount],\n format: 'rgba8unorm',\n usage:\n GPUTextureUsage.TEXTURE_BINDING |\n GPUTextureUsage.COPY_SRC |\n GPUTextureUsage.COPY_DST |\n GPUTextureUsage.RENDER_ATTACHMENT,\n });\n this.view = this.texture.createView({ dimension: '2d-array' });\n this.layers = Array.from({ length: layerCount }, () => ({\n shelves: [],\n nextY: 0,\n generation: 0,\n lastUsed: -1,\n lastAllocated: -1,\n }));\n this.spare = layerCount - 1;\n }\n\n /** The layer `allocatePinned` fills; it is never recycled. */\n readonly pinned = 0;\n\n get layerCount() {\n return this.layers.length;\n }\n\n get usedLayers() {\n return this.layers.filter((layer, index) => index !== this.spare && layer.nextY > 0).length;\n }\n\n /** How much of the pinned layer is taken, 0 to 1. */\n get pinnedFill() {\n return this.layers[this.pinned].nextY / this.size;\n }\n\n nextFrame() {\n this.frame++;\n }\n\n /** Marks a layer as drawn from this frame, so eviction prefers others. */\n touch(layer: number) {\n this.layers[layer].lastUsed = this.frame;\n }\n\n isValid(slot: AtlasSlot | null): slot is AtlasSlot {\n return slot !== null && this.layers[slot.layer].generation === slot.generation;\n }\n\n allocate(width: number, height: number): AtlasSlot | null {\n const w = Math.ceil(width) + PADDING;\n const h = Math.ceil(height) + PADDING;\n if (w > this.size || h > this.size) return null;\n for (let index = 0; index < this.layers.length; index++) {\n if (index === this.spare || index === this.pinned) continue;\n const slot = this.allocateIn(index, w, h);\n if (slot) return slot;\n }\n // Never recycle a layer filled this frame: its slots are about to be drawn.\n let stalest = -1;\n for (let index = 0; index < this.layers.length; index++) {\n const layer = this.layers[index];\n if (index === this.spare || index === this.pinned || layer.lastAllocated === this.frame)\n continue;\n if (stalest < 0 || layer.lastUsed < this.layers[stalest].lastUsed) stalest = index;\n }\n if (stalest < 0) return null;\n const target = this.recycle(stalest);\n return this.allocateIn(target, w, h);\n }\n\n /** Space in the pinned layer, or null once it is full. */\n allocatePinned(width: number, height: number): AtlasSlot | null {\n return this.allocateIn(this.pinned, Math.ceil(width) + PADDING, Math.ceil(height) + PADDING);\n }\n\n isPinned(slot: AtlasSlot | null) {\n return slot !== null && slot.layer === this.pinned;\n }\n\n /** Moves the kept slots of `index` into the spare layer, wipes `index`, and swaps the two. */\n private recycle(index: number) {\n const target = this.spare;\n const from = this.layers[index];\n this.evict(target);\n const encoder = this.device.createCommandEncoder({ label: 'gpucanvas atlas rescue' });\n let copies = 0;\n const seen = new Set<AtlasSlot>();\n for (const slot of this.keep()) {\n if (!slot || seen.has(slot) || slot.layer !== index || slot.generation !== from.generation) {\n continue;\n }\n seen.add(slot);\n // Slots cut from a whole page start at fractional texels; copy whole texels around them.\n const x0 = Math.floor(slot.x);\n const y0 = Math.floor(slot.y);\n const x1 = Math.min(this.size, Math.ceil(slot.x + slot.width));\n const y1 = Math.min(this.size, Math.ceil(slot.y + slot.height));\n const moved = this.allocateIn(target, x1 - x0 + PADDING, y1 - y0 + PADDING);\n if (!moved) break;\n encoder.copyTextureToTexture(\n { texture: this.texture, origin: { x: x0, y: y0, z: index } },\n { texture: this.texture, origin: { x: moved.x, y: moved.y, z: target } },\n [x1 - x0, y1 - y0, 1],\n );\n copies++;\n slot.layer = target;\n slot.x = moved.x + (slot.x - x0);\n slot.y = moved.y + (slot.y - y0);\n slot.generation = moved.generation;\n }\n if (copies > 0) this.device.queue.submit([encoder.finish()]);\n this.evict(index);\n this.spare = index;\n return target;\n }\n\n private allocateIn(index: number, w: number, h: number): AtlasSlot | null {\n const layer = this.layers[index];\n let shelf = layer.shelves.find(\n (candidate) =>\n candidate.height >= h && candidate.height <= h * 1.5 && candidate.x + w <= this.size,\n );\n if (!shelf) {\n if (layer.nextY + h > this.size) return null;\n shelf = { y: layer.nextY, height: h, x: 0 };\n layer.shelves.push(shelf);\n layer.nextY += h;\n }\n const slot = {\n layer: index,\n x: shelf.x,\n y: shelf.y,\n width: w - PADDING,\n height: h - PADDING,\n generation: layer.generation,\n };\n shelf.x += w;\n layer.lastUsed = this.frame;\n layer.lastAllocated = this.frame;\n return slot;\n }\n\n private evict(index: number) {\n const layer = this.layers[index];\n layer.shelves = [];\n layer.nextY = 0;\n layer.generation++;\n }\n\n destroy() {\n this.texture.destroy();\n }\n}\n","/**\n * One instanced quad per visible node: its atlas snapshot, or a placeholder until one exists.\n * Selected nodes get one more quad on top, a ring just outside their edge.\n *\n * Snapshots are kept when zooming out, so a quad often covers fewer pixels than its texels.\n * Plain bilinear filtering would then skip texels and shimmer while the camera moves, so the\n * fragment shader takes up to 4 x 4 samples spread over the pixel's footprint (a box filter),\n * clamped to the node's own atlas rectangle so neighbors never bleed in.\n */\nexport const compositeShader = /* wgsl */ `\nstruct View {\n size: vec2f, // canvas size in CSS pixels\n origin: vec2f, // camera position, world units\n zoom: f32,\n dpr: f32,\n atlasSize: f32,\n outline: f32, // selection ring width, CSS pixels\n placeholder: vec4f, // premultiplied color for nodes without a snapshot\n selection: vec4f, // premultiplied selection ring color\n}\n\n@group(0) @binding(0) var<uniform> view: View;\n@group(0) @binding(1) var atlas: texture_2d_array<f32>;\n@group(0) @binding(2) var atlasSampler: sampler;\n\nstruct Instance {\n @location(0) rect: vec4f, // world x, y, width, height\n @location(1) uv: vec4f, // atlas texels x0, y0, x1, y1\n @location(2) info: vec4f, // layer, kind: 0 snapshot, 1 placeholder, 2 selection ring\n}\n\nstruct Varyings {\n @builtin(position) position: vec4f,\n @location(0) texel: vec2f,\n @location(1) @interpolate(flat) bounds: vec4f,\n @location(2) @interpolate(flat) info: vec2f,\n}\n\n@vertex\nfn vs(@builtin(vertex_index) vertex: u32, instance: Instance) -> Varyings {\n let corner = vec2f(f32(vertex & 1u), f32(vertex >> 1u));\n // A selection ring's quad reaches past the node by the ring's width.\n let ring = instance.info.y > 1.5;\n let grow = select(0.0, view.outline / view.zoom, ring);\n let start = instance.rect.xy - grow;\n let end = instance.rect.xy + instance.rect.zw + grow;\n // Snap edges to device pixels so a snapshot drawn at its raster scale stays crisp.\n let scale = view.zoom * view.dpr;\n let topLeft = round((start - view.origin) * scale) / view.dpr;\n let bottomRight = round((end - view.origin) * scale) / view.dpr;\n let point = mix(topLeft, bottomRight, corner);\n var out: Varyings;\n out.position = vec4f(point.x / view.size.x * 2.0 - 1.0, 1.0 - point.y / view.size.y * 2.0, 0.0, 1.0);\n if (ring) {\n // World units from the node's top left, and the node's own edges.\n out.texel = mix(vec2f(-grow), instance.rect.zw + grow, corner);\n out.bounds = vec4f(0.0, 0.0, instance.rect.zw);\n } else {\n out.texel = mix(instance.uv.xy, instance.uv.zw, corner);\n // Half a texel inside the node's rectangle: the outermost sample center that cannot blend\n // with whatever sits next to it in the atlas.\n out.bounds = vec4f(instance.uv.xy + 0.5, instance.uv.zw - 0.5);\n }\n out.info = instance.info.xy;\n return out;\n}\n\n@fragment\nfn fs(in: Varyings) -> @location(0) vec4f {\n // Derivatives first: WGSL only allows them in uniform control flow.\n let dx = dpdx(in.texel);\n let dy = dpdy(in.texel);\n if (in.info.y > 1.5) {\n // The ring covers what lies outside the node; the node itself shows through.\n let outside = max(in.bounds.xy - in.texel, in.texel - in.bounds.zw);\n return select(vec4f(0.0), view.selection, max(outside.x, outside.y) > 0.0);\n }\n if (in.info.y > 0.5) {\n return view.placeholder;\n }\n let layer = i32(in.info.x);\n // Texels covered by this pixel along each screen axis.\n let footprint = vec2f(length(vec2f(dx.x, dy.x)), length(vec2f(dx.y, dy.y)));\n let taps = vec2u(clamp(ceil(footprint), vec2f(1.0), vec2f(4.0)));\n var color = vec4f(0.0);\n for (var j = 0u; j < taps.y; j++) {\n for (var i = 0u; i < taps.x; i++) {\n let offset = (vec2f(f32(i), f32(j)) + 0.5) / vec2f(taps) - 0.5;\n let texel = clamp(in.texel + offset * footprint, in.bounds.xy, in.bounds.zw);\n color += textureSampleLevel(atlas, atlasSampler, texel / view.atlasSize, layer, 0.0);\n }\n }\n return color / f32(taps.x * taps.y);\n}\n`;\n","import type { Atlas } from './atlas';\nimport type { Camera } from './camera';\nimport { compositeShader } from './composite-shader';\n\nexport const FLOATS_PER_INSTANCE = 12;\n\n/**\n * Draws every visible node in one instanced draw call. The caller fills `instances` with\n * FLOATS_PER_INSTANCE floats per node: world rect, atlas texel rect, then layer and kind\n * (0 snapshot, 1 placeholder, 2 selection ring).\n */\nexport class CompositeRenderer {\n instances = new Float32Array(FLOATS_PER_INSTANCE * 4096);\n private instanceBuffer: GPUBuffer;\n private readonly uniforms: GPUBuffer;\n private readonly uniformData = new Float32Array(16);\n private readonly pipeline: GPURenderPipeline;\n private readonly bindGroup: GPUBindGroup;\n\n constructor(\n private readonly device: GPUDevice,\n private readonly context: GPUCanvasContext,\n format: GPUTextureFormat,\n private readonly atlas: Atlas,\n public background: [number, number, number, number],\n public placeholder: [number, number, number, number],\n public selection: [number, number, number, number],\n private readonly outline: number,\n ) {\n const module = device.createShaderModule({\n label: 'gpucanvas composite',\n code: compositeShader,\n });\n this.pipeline = device.createRenderPipeline({\n label: 'gpucanvas composite',\n layout: 'auto',\n vertex: {\n module,\n entryPoint: 'vs',\n buffers: [\n {\n arrayStride: FLOATS_PER_INSTANCE * 4,\n stepMode: 'instance',\n attributes: [\n { shaderLocation: 0, offset: 0, format: 'float32x4' },\n { shaderLocation: 1, offset: 16, format: 'float32x4' },\n { shaderLocation: 2, offset: 32, format: 'float32x4' },\n ],\n },\n ],\n },\n fragment: {\n module,\n entryPoint: 'fs',\n targets: [\n {\n format,\n // Snapshots are requested premultiplied.\n blend: {\n color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' },\n },\n },\n ],\n },\n primitive: { topology: 'triangle-strip' },\n });\n this.uniforms = device.createBuffer({\n size: this.uniformData.byteLength,\n usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,\n });\n this.bindGroup = device.createBindGroup({\n layout: this.pipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: { buffer: this.uniforms } },\n { binding: 1, resource: atlas.view },\n {\n binding: 2,\n resource: device.createSampler({ magFilter: 'linear', minFilter: 'linear' }),\n },\n ],\n });\n this.instanceBuffer = this.createInstanceBuffer();\n }\n\n /** Grows the CPU and GPU instance storage to hold at least `count` instances. */\n reserve(count: number) {\n const floats = count * FLOATS_PER_INSTANCE;\n if (this.instances.length >= floats) return;\n this.instances = new Float32Array(Math.max(floats, this.instances.length * 2));\n this.instanceBuffer.destroy();\n this.instanceBuffer = this.createInstanceBuffer();\n }\n\n draw(camera: Camera, cssWidth: number, cssHeight: number, dpr: number, count: number) {\n const { device } = this;\n this.uniformData.set([\n cssWidth,\n cssHeight,\n camera.x,\n camera.y,\n camera.zoom,\n dpr,\n this.atlas.size,\n this.outline,\n ...this.placeholder,\n ...this.selection,\n ]);\n device.queue.writeBuffer(this.uniforms, 0, this.uniformData);\n if (count > 0) {\n device.queue.writeBuffer(\n this.instanceBuffer,\n 0,\n this.instances,\n 0,\n count * FLOATS_PER_INSTANCE,\n );\n }\n const [r, g, b] = this.background;\n const encoder = device.createCommandEncoder();\n const pass = encoder.beginRenderPass({\n colorAttachments: [\n {\n view: this.context.getCurrentTexture().createView(),\n clearValue: { r, g, b, a: 1 },\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n });\n if (count > 0) {\n pass.setPipeline(this.pipeline);\n pass.setBindGroup(0, this.bindGroup);\n pass.setVertexBuffer(0, this.instanceBuffer);\n pass.draw(4, count);\n }\n pass.end();\n device.queue.submit([encoder.finish()]);\n }\n\n destroy() {\n this.instanceBuffer.destroy();\n this.uniforms.destroy();\n }\n\n private createInstanceBuffer() {\n return this.device.createBuffer({\n label: 'gpucanvas instances',\n size: this.instances.byteLength,\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n });\n }\n}\n","/**\n * A page is one `drawable` container holding many nodes, laid out side by side in CSS pixels.\n * The browser records a page as one snapshot, so a single `drawElementImageToTexture` call can\n * rasterize every node on it, and a source rectangle crops out one node when only it changed.\n * Each call costs roughly a quarter millisecond on top of its pixels, so batching nodes into\n * pages is what makes a 10,000-node overview fill in a second instead of a minute.\n */\n\n/** Space between nodes on a page, so filtering at a node's edge never reads its neighbor. */\nconst GAP = 4;\n\ninterface Shelf {\n y: number;\n height: number;\n x: number;\n}\n\nexport interface PageSlot {\n page: Page;\n x: number;\n y: number;\n}\n\nexport class Page {\n readonly element: HTMLDivElement;\n readonly width: number;\n readonly height: number;\n /** Nodes currently placed on this page. */\n count = 0;\n /** Has the `drawable` attribute and is rendered; parked pages use `content-visibility: hidden`. */\n drawable = false;\n /** Frame in which the page became drawable. Its snapshot exists from that frame's paint on. */\n promotedFrame = -1;\n /** Kept drawable for the live (hit-testable) or focused node. */\n pinned = false;\n /** Registered for hit testing with `updateElementGeometry`. */\n registered = false;\n private shelves: Shelf[] = [];\n private nextY = 0;\n\n constructor(width: number, height: number) {\n this.width = width;\n this.height = height;\n this.element = document.createElement('div');\n this.element.dataset.gpucanvasPage = '';\n Object.assign(this.element.style, {\n position: 'relative',\n width: `${width}px`,\n height: `${height}px`,\n // Pages share the canvas origin: each one pulls the next back up by its own height.\n marginBottom: `${-height}px`,\n contain: 'strict',\n overflow: 'hidden',\n // Parked pages keep their layout but skip style, layout updates and paint. Unlike\n // `display: none`, their nodes keep their size, so components that measure themselves\n // (charts with a ResizeObserver) don't re-render empty every time a page parks.\n contentVisibility: 'hidden',\n });\n }\n\n /** Reserves a spot for a `width` x `height` node, or returns null when the page is full. */\n place(width: number, height: number): PageSlot | null {\n const w = Math.ceil(width) + GAP;\n const h = Math.ceil(height) + GAP;\n if (w > this.width + GAP || h > this.height + GAP) return null;\n let shelf = this.shelves.find(\n (candidate) =>\n candidate.height >= h &&\n candidate.height <= h * 1.25 &&\n candidate.x + w <= this.width + GAP,\n );\n if (!shelf) {\n if (this.nextY + h > this.height + GAP) return null;\n shelf = { y: this.nextY, height: h, x: 0 };\n this.shelves.push(shelf);\n this.nextY += h;\n }\n const slot = { page: this, x: shelf.x, y: shelf.y };\n shelf.x += w;\n this.count++;\n return slot;\n }\n\n release() {\n this.count--;\n }\n\n setDrawable(drawable: boolean, frame: number) {\n if (this.drawable === drawable) return;\n this.drawable = drawable;\n this.registered = false;\n if (drawable) {\n this.promotedFrame = frame;\n this.element.style.contentVisibility = '';\n this.element.setAttribute('drawable', '');\n } else {\n this.element.removeAttribute('drawable');\n this.element.style.contentVisibility = 'hidden';\n this.element.style.transform = '';\n }\n }\n}\n","import { Atlas, type AtlasSlot } from './atlas';\nimport {\n bindCameraInput,\n fitCamera,\n intersects,\n visibleWorldRect,\n type Camera,\n type Rect,\n} from './camera';\nimport { CompositeRenderer, FLOATS_PER_INSTANCE } from './gpu-renderer';\nimport { boundsOf } from './node-style';\nimport { Page } from './page';\nimport {\n DEFAULT_CAMERA,\n DEFAULT_SELECTION_COLOR,\n SELECTION_WIDTH,\n type GpuCanvas,\n type GpuCanvasOptions,\n} from './types';\n\ninterface Node {\n element: HTMLElement;\n rect: Rect;\n page: Page;\n /** The node's position on its page, CSS pixels. */\n pageX: number;\n pageY: number;\n slot: AtlasSlot | null;\n /** Texels per CSS pixel the slot was rasterized at. */\n slotScale: number;\n /**\n * A THUMBNAIL_SCALE snapshot in the atlas's pinned layer. It is never evicted, so zoomed out\n * the node needs nothing else, and closer in it stands in while a sharper snapshot is drawn.\n */\n thumb: AtlasSlot | null;\n /** The node changed since its thumbnail was drawn. */\n thumbStale: boolean;\n /** When the thumbnail was last drawn, from performance.now(). */\n thumbAt: number;\n /** What the last composite showed for this node: 0 nothing, 1 placeholder, 2 snapshot, 3 sharp. */\n shown: number;\n shownFrame: number;\n /** Content changed since the slot was rasterized. */\n dirty: boolean;\n /**\n * Zoomed in past MAX_RASTER_SCALE the whole snapshot is only a backdrop: the part in view is\n * drawn sharp as tiles, filled center first within the frame budget. While content animates,\n * one half-resolution preview of the view stands in, since redrawing every tile each frame\n * would cost too much.\n */\n tiles: Tile[];\n preview: AtlasSlot | null;\n previewRect: Rect | null;\n previewScale: number;\n /** Scratch: this frame's preview rect and scale, when one is needed. */\n previewTarget: Rect | null;\n previewTargetScale: number;\n /** Last time the node's content changed, from performance.now(). */\n changedAt: number;\n /** Last time a change of the node's content reached the atlas. */\n refreshedAt: number;\n /**\n * The whole snapshot predates a change that only the preview and tiles show so far. It is\n * redrawn once the node is no longer zoomed in that far.\n */\n wholeStale: boolean;\n /** Alone on its page, so zoomed-in snapshots rasterize only this node. */\n solo: boolean;\n /** Scratch values for the current frame's plan. */\n targetScale: number;\n priority: number;\n}\n\n/** A sharp piece of a zoomed-in node, `rect` in world units. */\ninterface Tile {\n slot: AtlasSlot;\n rect: Rect;\n scale: number;\n column: number;\n row: number;\n /** Shows content from before the node's last change; stays until a fresh tile replaces it. */\n stale: boolean;\n}\n\ninterface TileJob {\n node: Node;\n rect: Rect;\n scale: number;\n column: number;\n row: number;\n priority: number;\n}\n\ninterface PagePlan {\n page: Page;\n /** Nodes that need a whole snapshot. */\n nodes: Node[];\n /** Nodes that need a preview, then tiles; both after any whole snapshot. */\n previews: Node[];\n tiles: TileJob[];\n priority: number;\n}\n\nconst MIN_RASTER_SCALE = 1 / 64;\n/**\n * Whole-node snapshots stop here (100% zoom on a 2x display); closer in, tiles of the visible\n * part take over. A snapshot costs about 12 ns per pixel on the main thread, so one large card\n * at 4x would already stall a frame.\n */\nconst MAX_RASTER_SCALE = 2;\nconst MAX_DETAIL_SCALE = 64;\n/** Side of a tile in device pixels: about 3 ms to draw, so two fit the frame budget. */\nconst TILE_PIXELS = 512;\n/**\n * Content that changed this recently counts as animating: zoomed in, it is drawn as one\n * half-resolution preview of the view, then as sharp tiles once it rests. The full screen at 8x\n * zoom is about ten million pixels, too many to redraw every frame.\n */\nconst CONTENT_SETTLE_MS = 150;\n\n/**\n * Live content (spinners, clocks, video) changes every frame. A node that small on screen shows\n * no difference between 60 redraws a second and a few, so below SMALL_NODE_PX it refreshes at\n * most every SMALL_REFRESH_MS. Thousands of tiny spinners then cost what a few large ones do.\n */\nconst SMALL_NODE_PX = 200;\nconst SMALL_REFRESH_MS = 250;\nconst right = (rect: Rect) => rect.x + rect.width;\nconst bottom = (rect: Rect) => rect.y + rect.height;\n/** How long the camera must rest before blurry snapshots are upgraded. */\nconst SETTLE_MS = 120;\n/** Events that use a node, and how long after one its transitions are followed frame by frame. */\nconst USE_EVENTS = ['click', 'input', 'change', 'keydown'] as const;\nconst USED_MS = 1000;\n/** A node that stops being live stays rasterizable this long, so hover-out transitions finish. */\nconst LINGER_MS = 300;\n/** Kept snapshots may be this many times sharper than needed before they are redrawn smaller. */\nconst MAX_OVERSAMPLING = 4;\nconst DEFAULT_PAGE_SIZE = 2048;\n/**\n * Thumbnails: a 1440 px wide screen is 90 texels across, as sharp as it gets on a 2x display at\n * the 3% zoom of a 5,000 node overview. Five thousand of them fit the pinned layer.\n */\nconst THUMBNAIL_SCALE = 1 / 16;\n/** Live content redraws its thumbnail at most this often; nobody reads a spinner at 3% zoom. */\nconst THUMBNAIL_REFRESH_MS = 1000;\n/**\n * Pages made drawable per frame. Chrome paints every drawable page in its rendering update,\n * outside our own timing, and a page of two dozen rich cards can take tens of milliseconds, so\n * the count adapts to how long frames actually take: halved after a slow frame, grown by one\n * after a fast one.\n */\nconst MAX_PROMOTIONS_PER_FRAME = 8;\nconst SLOW_FRAME_MS = 22;\nconst FAST_FRAME_MS = 12;\n\n/** Need classes, most urgent first. */\nconst MISSING = 0;\nconst DIRTY = 1;\nconst BLURRY = 2;\nconst OVERSAMPLED = 3;\n\nexport function supportsGpuCanvas(): boolean {\n return (\n typeof navigator !== 'undefined' &&\n 'gpu' in navigator &&\n typeof HTMLCanvasElement !== 'undefined' &&\n 'requestPaint' in HTMLCanvasElement.prototype &&\n 'getElementTransform' in HTMLCanvasElement.prototype &&\n typeof GPUQueue !== 'undefined' &&\n 'drawElementImageToTexture' in GPUQueue.prototype\n );\n}\n\n/**\n * Snapshots are rasterized at power-of-two scales so zooming reuses them; a snapshot up to 10%\n * too small is accepted before paying for the next scale.\n */\nfunction rasterScaleFor(deviceScale: number) {\n const scale = 2 ** Math.ceil(Math.log2(deviceScale) - 0.14);\n return Math.min(MAX_DETAIL_SCALE, Math.max(MIN_RASTER_SCALE, scale));\n}\n\nconst intersection = (a: Rect, b: Rect): Rect | null => {\n const x = Math.max(a.x, b.x);\n const y = Math.max(a.y, b.y);\n const right = Math.min(a.x + a.width, b.x + b.width);\n const bottom = Math.min(a.y + a.height, b.y + b.height);\n return right > x && bottom > y ? { x, y, width: right - x, height: bottom - y } : null;\n};\n\nconst contains = (outer: Rect, inner: Rect) =>\n inner.x >= outer.x &&\n inner.y >= outer.y &&\n inner.x + inner.width <= outer.x + outer.width &&\n inner.y + inner.height <= outer.y + outer.height;\n\nfunction cssColorToRgba(color: string): [number, number, number, number] {\n const context = document.createElement('canvas').getContext('2d')!;\n context.fillStyle = color;\n context.fillRect(0, 0, 1, 1);\n const [r, g, b, a] = context.getImageData(0, 0, 1, 1).data;\n return [r / 255, g / 255, b / 255, a / 255];\n}\n\nconst premultiplied = ([r, g, b, a]: [number, number, number, number]) =>\n [r * a, g * a, b * a, a] as [number, number, number, number];\n\nconst distanceToCenter = (rect: Rect, view: Rect) =>\n Math.hypot(\n rect.x + rect.width / 2 - (view.x + view.width / 2),\n rect.y + rect.height / 2 - (view.y + view.height / 2),\n );\n\n/**\n * The GPU backend.\n *\n * Nodes are live DOM, grouped into pages inside `<canvas content=\"drawable\">`. A page that has\n * nodes waiting for pixels is made drawable, rasterized into a texture-array atlas in the next\n * paint event (whole, or one node at a time when only a few changed), then parked with\n * `content-visibility: hidden` so it costs the browser nothing per frame. Compositing runs from\n * requestAnimationFrame and draws every visible node in one instanced call, so panning and\n * zooming never touch the DOM.\n *\n * Hit testing (Chrome 157): only drawables registered with `updateElementGeometry` are hit\n * tested, at their CSS position. Only the page under the pointer is registered, and its CSS\n * transform puts the node under the pointer exactly where the GPU drew it.\n */\nexport async function createGpuCanvas(\n host: HTMLElement,\n options: GpuCanvasOptions,\n): Promise<GpuCanvas> {\n const adapter = await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' });\n if (!adapter) throw new Error('gpucanvas: no WebGPU adapter');\n const device = await adapter.requestDevice();\n\n const surface = document.createElement('div');\n surface.dataset.gpucanvasSurface = '';\n Object.assign(surface.style, {\n position: 'relative',\n width: '100%',\n height: '100%',\n overflow: 'hidden',\n touchAction: 'none',\n });\n const canvas = document.createElement('canvas');\n canvas.setAttribute('content', 'drawable');\n canvas.dataset.gpucanvasSurface = '';\n Object.assign(canvas.style, { display: 'block', width: '100%', height: '100%' });\n surface.append(canvas);\n host.append(surface);\n\n const context = canvas.getContext('webgpu')!;\n const format = navigator.gpu.getPreferredCanvasFormat();\n context.configure({ device, format, alphaMode: 'opaque' });\n\n const atlasSize = Math.min(options.atlasSize ?? 4096, device.limits.maxTextureDimension2D);\n const atlas = new Atlas(\n device,\n atlasSize,\n Math.min(options.atlasLayers ?? 5, device.limits.maxTextureArrayLayers),\n );\n const pageSize = Math.min(options.pageSize ?? DEFAULT_PAGE_SIZE, atlasSize);\n const maxRasterCalls = options.maxRastersPerFrame ?? 12;\n const rasterTimeBudgetMs = options.rasterTimeBudgetMs ?? 6;\n const rasterPixelBudget = options.rasterPixelBudget ?? 24_000_000;\n // Nodes still waiting for pixels draw as a block slightly off the background.\n const colorsFor = (css: string) => {\n const background = cssColorToRgba(css);\n const shade = background[0] + background[1] + background[2] > 1.5 ? 0.92 : 1.25;\n const placeholder = background.map((channel) =>\n Math.min(1, channel * shade + (shade > 1 ? 0.02 : 0)),\n );\n placeholder[3] = 1;\n return [background, placeholder] as [typeof background, typeof background];\n };\n const renderer = new CompositeRenderer(\n device,\n context,\n format,\n atlas,\n ...colorsFor(options.background ?? '#f4f4f5'),\n premultiplied(cssColorToRgba(options.selectionColor ?? DEFAULT_SELECTION_COLOR)),\n SELECTION_WIDTH,\n );\n\n const nodes = new Map<HTMLElement, Node>();\n const selected = new Set<Node>();\n const pages = new Map<Page, Set<Node>>();\n // What is on screen survives a full atlas: whole snapshots first, then sharp tiles.\n atlas.keep = function* () {\n const onScreen = (tile: Tile) => intersects(tile.rect, frameView);\n for (const node of visible) yield node.slot;\n for (const node of visible) {\n for (const tile of node.tiles) {\n if (tile.scale === detailScale && !tile.stale && onScreen(tile)) yield tile.slot;\n }\n }\n for (const node of visible) {\n yield node.preview;\n for (const tile of node.tiles) if (onScreen(tile)) yield tile.slot;\n }\n };\n const pageByElement = new Map<Element, Page>();\n let openPage: Page | null = null;\n\n let camera = options.camera ?? DEFAULT_CAMERA;\n let lastCameraChange = -Infinity;\n let frameNumber = 0;\n let promotionsPerFrame = 2;\n let lastFrameAt = 0;\n let rafId = 0;\n let destroyed = false;\n /** The node under the pointer: the only one that receives pointer events. */\n let live: Node | null = null;\n let livePositioned = false;\n let lingering: Node | null = null;\n let lingerTimer = 0;\n let focused: Node | null = null;\n let pointer: { x: number; y: number } | null = null;\n let settleTimer = 0;\n const stats = {\n visible: 0,\n pending: 0,\n rasterizedLastFrame: 0,\n rasterCallsLastFrame: 0,\n rasterMs: 0,\n frameMs: 0,\n frames: 0,\n lostPixels: 0,\n lostDetail: 0,\n onThumbnail: 0,\n /** Last plan's nodes per need class: missing, dirty, blurry, oversampled. */\n needs: [0, 0, 0, 0],\n };\n const visible: Node[] = [];\n const plans = new Map<Page, PagePlan>();\n\n const schedule = () => {\n if (rafId || destroyed) return;\n rafId = requestAnimationFrame(() => {\n rafId = 0;\n frameNumber++;\n frame(false);\n });\n };\n\n const resizeObserver = new ResizeObserver(([entry]) => {\n const size = entry.devicePixelContentBoxSize?.[0];\n canvas.width = Math.max(\n 1,\n size ? size.inlineSize : Math.round(entry.contentRect.width * devicePixelRatio),\n );\n canvas.height = Math.max(\n 1,\n size ? size.blockSize : Math.round(entry.contentRect.height * devicePixelRatio),\n );\n schedule();\n });\n resizeObserver.observe(canvas, { box: 'device-pixel-content-box' });\n\n /** Readable nodes in view with CSS transitions running, and when the last one ends. */\n const transitioning = new Map<Node, number>();\n const isPinned = (page: Page) =>\n live?.page === page ||\n lingering?.page === page ||\n focused?.page === page ||\n [...transitioning.keys()].some((node) => node.page === page);\n\n /** This frame's texels per CSS pixel for tiles, and the world rect in view. */\n let detailScale = 1;\n let frameView: Rect = { x: 0, y: 0, width: 0, height: 0 };\n let contentTimer = 0;\n const scheduleAfterContentSettles = () => {\n clearTimeout(contentTimer);\n contentTimer = window.setTimeout(schedule, CONTENT_SETTLE_MS + 10);\n };\n let refreshTimer = 0;\n let refreshAt = Infinity;\n /** How long a changed node waits before it is redrawn: only small ones wait. */\n const refreshWait = (node: Node, now: number) =>\n Math.max(node.rect.width, node.rect.height) * camera.zoom < SMALL_NODE_PX\n ? node.refreshedAt + SMALL_REFRESH_MS - now\n : 0;\n const refreshLater = (wait: number) => {\n const at = performance.now() + wait;\n if (at >= refreshAt) return;\n clearTimeout(refreshTimer);\n refreshAt = at;\n refreshTimer = window.setTimeout(() => {\n refreshAt = Infinity;\n schedule();\n }, wait);\n };\n /** Builds this frame's raster plan: which visible nodes need pixels, grouped by page. */\n const planFrame = (view: Rect, desiredScale: number, deviceScale: number, moving: boolean) => {\n const now = performance.now();\n // Tiles are redrawn whenever the zoom settles anyway, so they use the exact device scale:\n // one tile texel per screen pixel.\n detailScale = Math.min(deviceScale, MAX_DETAIL_SCALE);\n frameView = view;\n visible.length = 0;\n plans.clear();\n stats.needs = [0, 0, 0, 0];\n for (const node of nodes.values()) {\n if (!intersects(node.rect, view)) continue;\n visible.push(node);\n const scale = Math.min(\n desiredScale,\n MAX_RASTER_SCALE,\n (atlasSize - 1) / node.rect.width,\n (atlasSize - 1) / node.rect.height,\n );\n if (node.dirty) node.changedAt = now;\n // Zoomed out this far the thumbnail is all the detail there is to see.\n const thumbnailEnough = scale <= THUMBNAIL_SCALE;\n if (thumbnailEnough && freshThumb(node) && !node.dirty && !node.wholeStale) {\n node.slot = node.thumb;\n node.slotScale = THUMBNAIL_SCALE;\n }\n const animating = now - node.changedAt < CONTENT_SETTLE_MS;\n const deep = desiredScale > scale;\n if (deep && !node.solo) lift(node);\n const jobs = deep ? planDetail(node, view, animating, moving) : [];\n if (!deep) {\n node.preview = null;\n node.tiles.length = 0;\n }\n const valid = atlas.isValid(node.slot);\n let need = -1;\n // A node with a thumbnail has something on screen, so empty ones go first.\n if (!valid) need = atlas.isValid(node.thumb) ? DIRTY : MISSING;\n // Zoomed in, the whole snapshot is a backdrop under the preview and tiles; a change can\n // wait for it until the node is seen at a smaller zoom.\n else if (deep) need = node.slotScale < scale / (moving ? 4 : 2) ? BLURRY : -1;\n else if (node.dirty || node.wholeStale) {\n const wait = refreshWait(node, now);\n if (wait > 0) refreshLater(wait);\n else need = DIRTY;\n }\n // In motion only snapshots under half the needed scale are redrawn, nearest the middle\n // first within the frame's budget, so a flight never shows thumbnails blown up.\n else if (node.slotScale < (moving ? scale / 2 : scale)) need = BLURRY;\n else if (!moving && node.slotScale > scale * MAX_OVERSAMPLING) need = OVERSAMPLED;\n if (need >= 0) stats.needs[need]++;\n if (need < 0 && !node.previewTarget && jobs.length === 0) continue;\n // Snapshots this small are thumbnails, and those have one fixed scale.\n node.targetScale = thumbnailEnough ? THUMBNAIL_SCALE : scale;\n node.priority = (need < 0 ? BLURRY : need) * 1e12 + distanceToCenter(node.rect, view);\n let plan = plans.get(node.page);\n if (!plan) {\n plan = { page: node.page, nodes: [], previews: [], tiles: [], priority: node.priority };\n plans.set(node.page, plan);\n }\n plan.priority = Math.min(plan.priority, node.priority);\n if (need >= 0) plan.nodes.push(node);\n if (node.previewTarget) plan.previews.push(node);\n plan.tiles.push(...jobs);\n }\n for (const plan of plans.values()) plan.tiles.sort((a, b) => a.priority - b.priority);\n };\n\n /**\n * Plans a zoomed-in node: a preview while its content animates, otherwise the tiles in view\n * it is missing at this frame's scale.\n */\n const planDetail = (node: Node, view: Rect, animating: boolean, moving: boolean) => {\n const shown = intersection(node.rect, view)!;\n node.previewTarget = null;\n node.tiles = node.tiles.filter((tile) => atlas.isValid(tile.slot));\n // Old tiles stay on screen under the preview until fresh ones replace them.\n if (node.dirty) for (const tile of node.tiles) tile.stale = true;\n if (!atlas.isValid(node.preview)) node.preview = null;\n if (animating) {\n const scale = detailScale / 2;\n const stale =\n !node.preview ||\n node.dirty ||\n node.previewScale !== scale ||\n !contains(node.previewRect!, shown);\n if (stale) {\n node.previewTarget = fitPreview(shown, scale);\n node.previewTargetScale = scale;\n }\n scheduleAfterContentSettles();\n return [];\n }\n // Panning at a steady zoom keeps filling tiles; while the zoom changes it waits to settle.\n const current = node.tiles.filter((tile) => tile.scale === detailScale && !tile.stale);\n if (moving && current.length === 0) return [];\n const size = Math.max(1, Math.floor(TILE_PIXELS / detailScale));\n const jobs: TileJob[] = [];\n const first = (edge: number, origin: number) => Math.floor((edge - origin) / size);\n for (\n let row = first(shown.y, node.rect.y);\n row <= first(bottom(shown) - 1e-6, node.rect.y);\n row++\n ) {\n for (\n let column = first(shown.x, node.rect.x);\n column <= first(right(shown) - 1e-6, node.rect.x);\n column++\n ) {\n if (current.some((tile) => tile.column === column && tile.row === row)) continue;\n const rect = intersection(node.rect, {\n x: node.rect.x + column * size,\n y: node.rect.y + row * size,\n width: size,\n height: size,\n })!;\n jobs.push({\n node,\n rect,\n scale: detailScale,\n column,\n row,\n priority: distanceToCenter(rect, view),\n });\n }\n }\n // Once the view is tiled at this scale, older tiles and the preview are hidden anyway.\n if (jobs.length === 0) {\n node.tiles = current;\n node.preview = null;\n }\n return jobs;\n };\n\n /** The world rect a preview covers: `shown`, shrunk around its center to fit one layer. */\n const fitPreview = (shown: Rect, scale: number): Rect => {\n const limit = (atlasSize - 2) / scale;\n const fit = (start: number, size: number) =>\n size <= limit ? [start, size] : [start + (size - limit) / 2, limit];\n const [x, width] = fit(shown.x, shown.width);\n const [y, height] = fit(shown.y, shown.height);\n return { x, y, width, height };\n };\n\n /** Draws one snapshot region into the atlas. Returns false when it could not. */\n const drawSnapshot = (\n page: Page,\n source: { x: number; y: number; width: number; height: number } | null,\n slot: AtlasSlot,\n ) => {\n try {\n device.queue.drawElementImageToTexture(\n source\n ? {\n source: page.element,\n sourceX: source.x,\n sourceY: source.y,\n sourceWidth: source.width,\n sourceHeight: source.height,\n }\n : { source: page.element },\n {\n texture: atlas.texture,\n origin: { x: slot.x, y: slot.y, z: slot.layer },\n size: { width: slot.width, height: slot.height },\n premultipliedAlpha: true,\n },\n );\n return true;\n } catch {\n // Not recorded yet; the next paint event retries.\n return false;\n }\n };\n\n /**\n * Atlas space for a `width` x `height` snapshot. A redraw at the same size overwrites the slot\n * it replaces, so redraws (a hover, a click, an animation) never fill the atlas. Thumbnails go\n * to the pinned layer while it has room.\n */\n const slotFor = (previous: AtlasSlot | null, width: number, height: number, thumbnail = false) =>\n atlas.isValid(previous) &&\n atlas.isPinned(previous) === thumbnail &&\n previous.width === width &&\n previous.height === height &&\n Number.isInteger(previous.x) &&\n Number.isInteger(previous.y)\n ? previous\n : ((thumbnail ? atlas.allocatePinned(width, height) : null) ?? atlas.allocate(width, height));\n const freshThumb = (node: Node) => atlas.isValid(node.thumb) && !node.thumbStale;\n /** Records a snapshot that reached the atlas; at thumbnail scale it is the node's thumbnail. */\n const drawn = (node: Node, slot: AtlasSlot, scale: number, now: number) => {\n node.slot = slot;\n node.slotScale = scale;\n if (scale === THUMBNAIL_SCALE) {\n node.thumb = slot;\n node.thumbStale = false;\n node.thumbAt = now;\n } else if (node.dirty) node.thumbStale = true;\n if (node.dirty) {\n // A preview from before the change would cover the fresh snapshot.\n node.preview = null;\n node.refreshedAt = now;\n }\n node.dirty = false;\n node.wholeStale = false;\n };\n /** Whole page slots per page, reused when the page is drawn again at the same size. */\n const pageSlots = new Map<Page, AtlasSlot>();\n\n /**\n * Rasterizes planned pages in priority order within the frame's budget. A page where many\n * nodes need pixels is drawn whole in one call; otherwise each node is cropped out.\n */\n const rasterize = (start: number) => {\n let calls = 0;\n let pixels = 0;\n let rasterized = 0;\n let pending = 0;\n const overBudget = () =>\n calls > 0 &&\n (calls >= maxRasterCalls ||\n pixels >= rasterPixelBudget ||\n performance.now() - start >= rasterTimeBudgetMs);\n const ordered = [...plans.values()].sort((a, b) => a.priority - b.priority);\n for (const plan of ordered) {\n const { page } = plan;\n // A page promoted this frame may still hold the snapshot from before it was parked.\n if (!page.drawable || page.promotedFrame >= frameNumber || overBudget()) {\n pending += plan.nodes.length + plan.previews.length + plan.tiles.length;\n continue;\n }\n const scale = plan.nodes.reduce((max, node) => Math.max(max, node.targetScale), 0);\n const members = pages.get(page)!;\n const neededArea = plan.nodes.reduce((sum, n) => sum + n.rect.width * n.rect.height, 0);\n // A whole page also redraws members nobody is looking at, so it is worth it only while\n // the page is small in the atlas: at most a quarter of a layer.\n const wholeFits = page.width * scale <= atlasSize / 2 && page.height * scale <= atlasSize / 2;\n const whole =\n wholeFits && (plan.nodes.length >= 6 || neededArea >= 0.25 * page.width * page.height);\n const wholeSlot = whole\n ? slotFor(\n pageSlots.get(page) ?? null,\n Math.ceil(page.width * scale),\n Math.ceil(page.height * scale),\n scale === THUMBNAIL_SCALE,\n )\n : null;\n if (wholeSlot) pageSlots.set(page, wholeSlot);\n if (wholeSlot && !drawSnapshot(page, null, wholeSlot)) {\n pending += plan.nodes.length + plan.previews.length + plan.tiles.length;\n continue;\n }\n\n if (wholeSlot) {\n const slot = wholeSlot;\n calls++;\n pixels += slot.width * slot.height;\n for (const node of members) {\n const part = {\n layer: slot.layer,\n generation: slot.generation,\n x: slot.x + node.pageX * scale,\n y: slot.y + node.pageY * scale,\n width: node.rect.width * scale,\n height: node.rect.height * scale,\n };\n drawn(node, part, scale, start);\n }\n rasterized += members.size;\n } else {\n for (const node of plan.nodes) {\n if (overBudget()) {\n pending++;\n continue;\n }\n const thumbnail = node.targetScale === THUMBNAIL_SCALE;\n const slot = slotFor(\n thumbnail ? node.thumb : node.slot,\n Math.max(1, Math.round(node.rect.width * node.targetScale)),\n Math.max(1, Math.round(node.rect.height * node.targetScale)),\n thumbnail,\n );\n const region = {\n x: node.pageX,\n y: node.pageY,\n width: node.rect.width,\n height: node.rect.height,\n };\n if (!slot || !drawSnapshot(page, region, slot)) {\n pending++;\n continue;\n }\n calls++;\n pixels += slot.width * slot.height;\n drawn(node, slot, node.targetScale, start);\n rasterized++;\n }\n }\n /** Crops `rect` (world units) of `node` out of its page into the atlas at `scale`. */\n // The first snapshot at a scale in a frame pays to rasterize the page at that scale;\n // further crops at the same scale are nearly free, so they skip the budget.\n const warm = new Set<number>();\n const drawRegion = (node: Node, rect: Rect, scale: number, previous: AtlasSlot | null) => {\n if (!warm.has(scale) && overBudget()) return null;\n const slot = slotFor(\n previous,\n Math.max(1, Math.round(rect.width * scale)),\n Math.max(1, Math.round(rect.height * scale)),\n );\n const region = {\n x: node.pageX + rect.x - node.rect.x,\n y: node.pageY + rect.y - node.rect.y,\n width: rect.width,\n height: rect.height,\n };\n if (!slot || !drawSnapshot(page, region, slot)) return null;\n calls++;\n warm.add(scale);\n pixels += slot.width * slot.height;\n // The node's whole snapshot now lags behind what the preview and tiles show.\n if (node.dirty) {\n node.dirty = false;\n node.wholeStale = true;\n node.thumbStale = true;\n node.refreshedAt = start;\n }\n return slot;\n };\n for (const node of plan.previews) {\n const slot = drawRegion(node, node.previewTarget!, node.previewTargetScale, node.preview);\n if (!slot) {\n pending++;\n continue;\n }\n node.preview = slot;\n node.previewRect = node.previewTarget;\n node.previewScale = node.previewTargetScale;\n node.previewTarget = null;\n }\n let tilesLeft = 0;\n for (const job of plan.tiles) {\n // A change since planning makes the job's content stale; the next plan redoes it.\n const { node, column, row, scale } = job;\n const old = node.tiles.findIndex(\n (tile) =>\n tile.stale && tile.scale === scale && tile.column === column && tile.row === row,\n );\n const previous = old >= 0 ? node.tiles[old].slot : null;\n const slot = node.dirty ? null : drawRegion(node, job.rect, scale, previous);\n if (!slot) {\n tilesLeft++;\n continue;\n }\n if (old >= 0) node.tiles.splice(old, 1);\n node.tiles.push({ slot, rect: job.rect, scale, column, row, stale: false });\n }\n pending += tilesLeft;\n // While the page is drawable, its nodes' thumbnails are nearly free: one small raster of\n // the page, then a crop per node.\n for (const node of members) {\n if (freshThumb(node) || node.dirty) continue;\n if (atlas.isValid(node.thumb) && start - node.thumbAt < THUMBNAIL_REFRESH_MS) continue;\n const slot = slotFor(\n node.thumb,\n Math.max(1, Math.round(node.rect.width * THUMBNAIL_SCALE)),\n Math.max(1, Math.round(node.rect.height * THUMBNAIL_SCALE)),\n true,\n );\n const region = {\n x: node.pageX,\n y: node.pageY,\n width: node.rect.width,\n height: node.rect.height,\n };\n if (!slot || !drawSnapshot(page, region, slot)) continue;\n calls++;\n node.thumb = slot;\n node.thumbStale = false;\n node.thumbAt = start;\n }\n const done =\n plan.nodes.every((node) => !node.dirty && atlas.isValid(node.slot)) &&\n plan.previews.every((node) => node.previewTarget === null) &&\n tilesLeft === 0;\n if (done && !isPinned(page)) page.setDrawable(false, frameNumber);\n }\n stats.rasterizedLastFrame = rasterized;\n stats.rasterCallsLastFrame = calls;\n return pending;\n };\n\n const composite = () => {\n let instances = visible.length;\n for (const node of visible)\n instances += node.tiles.length + (node.preview ? 1 : 0) + (selected.has(node) ? 1 : 0);\n renderer.reserve(instances);\n const data = renderer.instances;\n let count = 0;\n const push = (rect: Rect, slot: AtlasSlot | null) => {\n const offset = count++ * FLOATS_PER_INSTANCE;\n data[offset] = rect.x;\n data[offset + 1] = rect.y;\n data[offset + 2] = rect.width;\n data[offset + 3] = rect.height;\n if (slot) {\n atlas.touch(slot.layer);\n data[offset + 4] = slot.x;\n data[offset + 5] = slot.y;\n data[offset + 6] = slot.x + slot.width;\n data[offset + 7] = slot.y + slot.height;\n data[offset + 8] = slot.layer;\n data[offset + 9] = 0;\n } else {\n data[offset + 9] = 1;\n }\n };\n const pushRing = (rect: Rect) => {\n const offset = count++ * FLOATS_PER_INSTANCE;\n data.set([rect.x, rect.y, rect.width, rect.height], offset);\n data[offset + 9] = 2;\n };\n let onThumbnail = 0;\n for (const node of visible) {\n // Until its snapshot is drawn, a node shows its thumbnail.\n const base = atlas.isValid(node.slot)\n ? node.slot\n : atlas.isValid(node.thumb)\n ? node.thumb\n : null;\n if (base && base !== node.slot) onThumbnail++;\n const hasSlot = base !== null;\n const sharp = node.tiles.length > 0 || (node.preview !== null && atlas.isValid(node.preview));\n const shown = sharp ? 3 : hasSlot ? 2 : 1;\n // Flicker accounting: pixels a node showed last frame and lost this one. A node coming\n // into view is pop-in, not flicker.\n const before = node.shownFrame === stats.frames - 1 ? node.shown : 0;\n if (before >= 2 && shown === 1) stats.lostPixels++;\n if (before === 3 && shown === 2) stats.lostDetail++;\n node.shown = shown;\n node.shownFrame = stats.frames;\n push(node.rect, base);\n // Back to front: stale tiles and tiles from an earlier zoom, the preview of a change,\n // then this zoom's fresh tiles.\n const current = (tile: Tile) => tile.scale === detailScale && !tile.stale;\n for (const tile of node.tiles)\n if (!current(tile) && atlas.isValid(tile.slot)) push(tile.rect, tile.slot);\n if (node.preview && atlas.isValid(node.preview)) push(node.previewRect!, node.preview);\n for (const tile of node.tiles)\n if (current(tile) && atlas.isValid(tile.slot)) push(tile.rect, tile.slot);\n }\n stats.onThumbnail = onThumbnail;\n // Selection rings last, so no neighbor covers them.\n if (selected.size) for (const node of visible) if (selected.has(node)) pushRing(node.rect);\n const cssWidth = canvas.clientWidth;\n const dpr = canvas.width / Math.max(1, cssWidth);\n renderer.draw(camera, cssWidth, canvas.clientHeight, dpr, count);\n };\n\n /**\n * One frame. From requestAnimationFrame (`inPaint` false) it plans, promotes the pages that\n * need pixels, and either composites straight away or asks for a paint event. In the paint\n * event snapshots are current, so rasterizing happens there, followed by the composite.\n */\n const frame = (inPaint: boolean) => {\n if (destroyed) return;\n const start = performance.now();\n atlas.nextFrame();\n const cssWidth = canvas.clientWidth;\n const dpr = canvas.width / Math.max(1, cssWidth);\n const view = visibleWorldRect(camera, cssWidth, canvas.clientHeight);\n const moving = start - lastCameraChange < SETTLE_MS;\n planFrame(view, rasterScaleFor(camera.zoom * dpr), camera.zoom * dpr, moving);\n\n if (!inPaint && plans.size > 0) {\n // Only consecutive frames say how long a frame takes.\n const interval = start - lastFrameAt;\n if (interval < 100) {\n if (interval > SLOW_FRAME_MS) promotionsPerFrame = Math.max(1, promotionsPerFrame >> 1);\n else if (interval < FAST_FRAME_MS)\n promotionsPerFrame = Math.min(MAX_PROMOTIONS_PER_FRAME, promotionsPerFrame + 1);\n }\n lastFrameAt = start;\n let promoted = 0;\n for (const plan of [...plans.values()].sort((a, b) => a.priority - b.priority)) {\n if (plan.page.drawable) continue;\n if (promoted++ >= promotionsPerFrame) break;\n plan.page.setDrawable(true, frameNumber);\n }\n // The paint event follows in this same rendering update and finishes the frame.\n awaitingPaint = true;\n canvas.requestPaint();\n return;\n }\n\n let pending = 0;\n stats.rasterizedLastFrame = 0;\n stats.rasterCallsLastFrame = 0;\n if (inPaint && plans.size > 0) pending = rasterize(start);\n // A page promoted for a camera that has moved on has nothing left to draw: park it, or\n // Chrome keeps painting it every frame.\n for (const page of pages.keys()) {\n if (page.drawable && !plans.has(page) && page.promotedFrame < frameNumber && !isPinned(page))\n page.setDrawable(false, frameNumber);\n }\n stats.rasterMs = performance.now() - start;\n composite();\n stats.frames++;\n positionLivePage();\n stats.visible = visible.length;\n stats.pending = pending;\n stats.frameMs = performance.now() - start;\n if (pending > 0) schedule();\n };\n\n /** A frame asked for this paint event; others come from content animating on its own. */\n let awaitingPaint = false;\n canvas.onpaint = (event) => {\n // CSS-only changes (hover, focus rings, transitions) on pinned pages reach us only here.\n let changed = false;\n for (const element of event.changedElements) {\n const page = pageByElement.get(element);\n if (!page) continue;\n for (const node of [live, lingering, focused, ...transitioning.keys()]) {\n if (node?.page === page) {\n node.dirty = true;\n changed = true;\n }\n }\n }\n if (!awaitingPaint && !changed) return;\n awaitingPaint = false;\n frame(true);\n };\n\n const viewNow = () => visibleWorldRect(camera, canvas.clientWidth, canvas.clientHeight);\n\n /** The node an element belongs to: its ancestor whose parent is a page. */\n const nodeOf = (target: globalThis.Node): Node | undefined => {\n let element = target instanceof HTMLElement ? target : target.parentElement;\n while (element?.parentElement && !pageByElement.has(element.parentElement)) {\n element = element.parentElement;\n }\n return element ? nodes.get(element) : undefined;\n };\n\n // Content changes on any node, drawable or parked. Style that gpucanvas writes on page and\n // node elements is ignored.\n const mutations = new MutationObserver((records) => {\n let changed = false;\n for (const record of records) {\n const target = record.target as HTMLElement;\n if (\n record.type === 'attributes' &&\n (nodes.has(target) || pageByElement.has(target) || target === canvas)\n ) {\n continue;\n }\n const node = nodeOf(record.target);\n if (node && !node.dirty) {\n node.dirty = true;\n // Off screen it waits until it scrolls into view.\n if (!changed) changed = intersects(node.rect, viewNow());\n }\n }\n if (changed) schedule();\n });\n mutations.observe(canvas, {\n subtree: true,\n childList: true,\n characterData: true,\n attributes: true,\n });\n\n /** Releases a page from hit testing once nothing pins it; it parks after its next raster. */\n const unpin = (page: Page) => {\n if (isPinned(page)) return;\n if (page.registered) canvas.clearElementGeometry(page.element);\n page.registered = false;\n page.element.style.transform = '';\n schedule();\n };\n\n /**\n * Puts the live page's hit area under the live node. The draw matrix maps the page's\n * default-size snapshot (CSS px x dpr) to canvas pixels. A page promoted this frame has no\n * paint record yet, so it is positioned on the next frame.\n */\n const positionLivePage = () => {\n if (!live || livePositioned) return;\n const dpr = canvas.width / Math.max(1, canvas.clientWidth);\n const { zoom } = camera;\n const drawn = new DOMMatrix([\n zoom,\n 0,\n 0,\n zoom,\n ((live.rect.x - camera.x) * zoom - live.pageX * zoom) * dpr,\n ((live.rect.y - camera.y) * zoom - live.pageY * zoom) * dpr,\n ]);\n try {\n live.page.element.style.transform = canvas\n .getElementTransform(live.page.element, drawn)\n .toString();\n live.element.style.pointerEvents = 'auto';\n livePositioned = true;\n } catch {\n schedule();\n }\n };\n\n /** Moves the hit-test target to `node`: its page is registered and positioned under it. */\n const setLive = (node: Node | null) => {\n if (node === live) return;\n const previous = live;\n live = node;\n if (previous) {\n previous.element.style.pointerEvents = 'none';\n clearTimeout(lingerTimer);\n const earlier = lingering;\n lingering = previous;\n if (earlier && earlier !== previous) {\n earlier.dirty = true;\n unpin(earlier.page);\n }\n lingerTimer = window.setTimeout(() => {\n const last = lingering;\n if (!last) return;\n lingering = null;\n last.dirty = true;\n unpin(last.page);\n }, LINGER_MS);\n unpin(previous.page);\n }\n if (!node) return;\n const { page } = node;\n page.setDrawable(true, frameNumber);\n if (!page.registered) {\n canvas.updateElementGeometry(page.element, {});\n page.registered = true;\n }\n livePositioned = false;\n positionLivePage();\n schedule();\n };\n\n /** Makes the topmost node under the last pointer position live. */\n const pickLive = () => {\n if (!pointer) return setLive(null);\n const worldX = camera.x + pointer.x / camera.zoom;\n const worldY = camera.y + pointer.y / camera.zoom;\n let hit: Node | null = null;\n for (const node of nodes.values()) {\n const { x, y, width, height } = node.rect;\n if (worldX >= x && worldX < x + width && worldY >= y && worldY < y + height) hit = node;\n }\n setLive(hit);\n };\n\n const onPointerMove = (event: PointerEvent) => {\n const bounds = canvas.getBoundingClientRect();\n pointer = { x: event.clientX - bounds.left, y: event.clientY - bounds.top };\n // A press in progress belongs to the live node (text selection, sliders) or to a pan.\n if (event.buttons === 0) pickLive();\n };\n const onPointerLeave = () => {\n pointer = null;\n setLive(null);\n };\n const onFocusIn = (event: FocusEvent) => {\n const node = nodeOf(event.target as HTMLElement) ?? null;\n if (node === focused) return;\n const previous = focused;\n focused = node;\n if (previous) {\n previous.dirty = true;\n unpin(previous.page);\n }\n if (node) {\n node.page.setDrawable(true, frameNumber);\n node.dirty = true;\n schedule();\n }\n };\n const onFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget && canvas.contains(event.relatedTarget as globalThis.Node)) return;\n const previous = focused;\n focused = null;\n if (previous) {\n previous.dirty = true;\n unpin(previous.page);\n }\n };\n // An image that finishes loading changes pixels without changing the DOM.\n const onResourceLoad = (event: Event) => {\n const node = nodeOf(event.target as HTMLElement);\n if (!node) return;\n node.dirty = true;\n schedule();\n };\n canvas.addEventListener('load', onResourceLoad, true);\n // Snapshots taken before a web font arrived show the fallback font.\n const onFontsLoaded = () => {\n for (const node of nodes.values()) node.dirty = true;\n schedule();\n };\n document.fonts.addEventListener('loadingdone', onFontsLoaded);\n surface.addEventListener('pointermove', onPointerMove);\n surface.addEventListener('pointerleave', onPointerLeave);\n canvas.addEventListener('focusin', onFocusIn);\n canvas.addEventListener('focusout', onFocusOut);\n // A transition paints without touching the DOM, so a click (from a person or from script)\n // would leave a half-finished frame. A readable node in view that was just used keeps its page\n // pinned and redrawn on every paint until its transitions are over. Content that animates on\n // its own, like a looping indicator, keeps the cheaper refresh on mutations: pinning it would\n // repaint its whole page every frame. A deadline ends the pin, transitions in parked pages\n // never fire transitionend.\n const usedAt = new WeakMap<Node, number>();\n const onUse = (event: Event) => {\n const node = nodeOf(event.target as HTMLElement);\n if (node) usedAt.set(node, performance.now());\n };\n const transitionMs = (element: Element) => {\n const style = getComputedStyle(element);\n const seconds = (list: string) => list.split(',').map((value) => parseFloat(value) || 0);\n const durations = seconds(style.transitionDuration);\n const delays = seconds(style.transitionDelay);\n const longest = Math.max(...durations.map((d, i) => d + (delays[i % delays.length] ?? 0)));\n return Math.min(1000, longest * 1000);\n };\n const onTransitionRun = (event: Event) => {\n const node = nodeOf(event.target as HTMLElement);\n const now = performance.now();\n if (!node || now - (usedAt.get(node) ?? -Infinity) > USED_MS) return;\n if (!intersects(node.rect, viewNow())) return;\n if (Math.max(node.rect.width, node.rect.height) * camera.zoom < SMALL_NODE_PX) return;\n const ms = transitionMs(event.target as Element) + 32;\n const end = now + ms;\n if ((transitioning.get(node) ?? 0) >= end) return;\n transitioning.set(node, end);\n node.dirty = true;\n schedule();\n setTimeout(() => {\n if (transitioning.get(node) !== end) return;\n transitioning.delete(node);\n node.dirty = true;\n unpin(node.page);\n schedule();\n }, ms);\n };\n for (const type of USE_EVENTS) canvas.addEventListener(type, onUse, true);\n canvas.addEventListener('transitionrun', onTransitionRun);\n\n const setCamera = (next: Camera) => {\n camera = next;\n lastCameraChange = performance.now();\n // A moving canvas has no hover; the node under a still pointer becomes live once it rests.\n setLive(null);\n clearTimeout(settleTimer);\n settleTimer = window.setTimeout(() => {\n pickLive();\n schedule();\n }, SETTLE_MS);\n schedule();\n options.onCameraChange?.(camera);\n };\n const unbindInput = bindCameraInput(surface, () => camera, setCamera);\n\n const placeNode = (element: HTMLElement, rect: Rect) => {\n let slot = openPage?.place(rect.width, rect.height) ?? null;\n if (!slot) {\n const page = new Page(\n Math.max(pageSize, Math.ceil(rect.width)),\n Math.max(pageSize, Math.ceil(rect.height)),\n );\n pages.set(page, new Set());\n pageByElement.set(page.element, page);\n canvas.append(page.element);\n // Oversized nodes get a page of their own and leave the open page as it was.\n if (page.width === pageSize && page.height === pageSize) openPage = page;\n slot = page.place(rect.width, rect.height)!;\n }\n Object.assign(element.style, {\n position: 'absolute',\n left: `${slot.x}px`,\n top: `${slot.y}px`,\n width: `${rect.width}px`,\n height: `${rect.height}px`,\n });\n slot.page.element.append(element);\n return slot;\n };\n\n const releaseNode = (node: Node) => {\n const members = pages.get(node.page)!;\n members.delete(node);\n node.page.release();\n if (members.size === 0 && node.page !== openPage) {\n pages.delete(node.page);\n pageByElement.delete(node.page.element);\n node.page.element.remove();\n }\n };\n\n /**\n * Moves a node onto a page of its own. Chrome rasterizes a snapshot's whole source element at\n * the requested scale before cropping, so zoomed in, a crop from a shared page pays for every\n * node on it: about 100 ms at 16x for two dozen app screens, under 10 ms for one.\n * `moveBefore` keeps focus, running transitions and other DOM state across the move.\n */\n const lift = (node: Node) => {\n const previous = node.page;\n const wasLive = live === node;\n if (wasLive) setLive(null);\n const page = new Page(Math.ceil(node.rect.width), Math.ceil(node.rect.height));\n pages.set(page, new Set());\n pageByElement.set(page.element, page);\n canvas.append(page.element);\n page.place(node.rect.width, node.rect.height);\n const target = page.element as HTMLElement & {\n moveBefore?: (child: globalThis.Node, before: globalThis.Node | null) => void;\n };\n if (target.moveBefore) target.moveBefore(node.element, null);\n else target.append(node.element);\n node.element.style.left = '0px';\n node.element.style.top = '0px';\n releaseNode(node);\n node.page = page;\n node.pageX = 0;\n node.pageY = 0;\n node.solo = true;\n pages.get(page)!.add(node);\n // The whole snapshot is still valid: the node's pixels did not change, only its page.\n if (pages.has(previous) && !isPinned(previous)) {\n unpin(previous);\n previous.setDrawable(false, frameNumber);\n }\n if (wasLive) pickLive();\n };\n\n const attach = (node: Node) => {\n const slot = placeNode(node.element, node.rect);\n node.page = slot.page;\n node.pageX = slot.x;\n node.pageY = slot.y;\n node.slot = null;\n node.thumb = null;\n node.solo = false;\n node.dirty = true;\n pages.get(slot.page)!.add(node);\n };\n\n return {\n mode: 'gpu',\n surface,\n addNode(element, rect) {\n Object.assign(element.style, {\n boxSizing: 'border-box',\n contain: 'strict',\n overflow: 'hidden',\n pointerEvents: 'none',\n });\n const node = {\n element,\n rect,\n slotScale: 0,\n thumb: null,\n thumbStale: false,\n thumbAt: -Infinity,\n shown: 0,\n shownFrame: -1,\n targetScale: 0,\n tiles: [] as Tile[],\n preview: null,\n previewRect: null,\n previewScale: 0,\n previewTarget: null,\n previewTargetScale: 0,\n changedAt: -Infinity,\n refreshedAt: -Infinity,\n wholeStale: false,\n solo: false,\n priority: 0,\n } as Node;\n attach(node);\n nodes.set(element, node);\n schedule();\n },\n updateNode(element, rect) {\n const node = nodes.get(element);\n if (!node) return;\n const resized = node.rect.width !== rect.width || node.rect.height !== rect.height;\n node.rect = rect;\n if (resized) {\n if (live === node) setLive(null);\n releaseNode(node);\n attach(node);\n }\n schedule();\n },\n removeNode(element) {\n const node = nodes.get(element);\n if (!node) return;\n if (live === node) setLive(null);\n if (lingering === node) lingering = null;\n if (focused === node) focused = null;\n transitioning.delete(node);\n selected.delete(node);\n nodes.delete(element);\n releaseNode(node);\n element.remove();\n schedule();\n },\n getNodeRect: (element) => {\n const node = nodes.get(element);\n return node ? { ...node.rect } : null;\n },\n getNodesIn(rect) {\n const hits: HTMLElement[] = [];\n for (const node of nodes.values()) if (intersects(node.rect, rect)) hits.push(node.element);\n return hits;\n },\n setSelection(elements) {\n selected.clear();\n for (const element of elements) {\n const node = nodes.get(element);\n if (node) selected.add(node);\n }\n schedule();\n },\n getCamera: () => camera,\n setCamera,\n invalidate: onFontsLoaded,\n setBackground(color) {\n [renderer.background, renderer.placeholder] = colorsFor(color);\n schedule();\n },\n fit(padding) {\n const bounds = boundsOf([...nodes.values()].map((node) => node.rect));\n if (bounds) setCamera(fitCamera(bounds, canvas.clientWidth, canvas.clientHeight, padding));\n },\n getStats: () => ({\n mode: 'gpu',\n nodes: nodes.size,\n visible: stats.visible,\n pending: stats.pending,\n rasterizedLastFrame: stats.rasterizedLastFrame,\n rasterCallsLastFrame: stats.rasterCallsLastFrame,\n rasterMs: stats.rasterMs,\n pages: pages.size,\n drawablePages: [...pages.keys()].filter((page) => page.drawable).length,\n atlasLayersUsed: atlas.usedLayers,\n frameMs: stats.frameMs,\n frames: stats.frames,\n lostPixels: stats.lostPixels,\n lostDetail: stats.lostDetail,\n thumbnailFill: atlas.pinnedFill,\n onThumbnail: stats.onThumbnail,\n needs: stats.needs,\n }),\n destroy() {\n destroyed = true;\n cancelAnimationFrame(rafId);\n clearTimeout(settleTimer);\n clearTimeout(lingerTimer);\n clearTimeout(contentTimer);\n mutations.disconnect();\n document.fonts.removeEventListener('loadingdone', onFontsLoaded);\n canvas.removeEventListener('load', onResourceLoad, true);\n surface.removeEventListener('pointermove', onPointerMove);\n surface.removeEventListener('pointerleave', onPointerLeave);\n canvas.removeEventListener('focusin', onFocusIn);\n canvas.removeEventListener('focusout', onFocusOut);\n canvas.removeEventListener('transitionrun', onTransitionRun);\n for (const type of USE_EVENTS) canvas.removeEventListener(type, onUse, true);\n unbindInput();\n resizeObserver.disconnect();\n canvas.onpaint = null;\n surface.remove();\n renderer.destroy();\n atlas.destroy();\n device.destroy();\n },\n };\n}\n","import { createDomCanvas } from './dom-backend';\nimport { createGpuCanvas as createGpuBackend, supportsGpuCanvas } from './gpu-backend';\nimport type { GpuCanvas, GpuCanvasOptions } from './types';\n\nexport type { Camera, Rect } from './camera';\nexport { fitCamera, zoomAt } from './camera';\nexport type { GpuCanvas, GpuCanvasMode, GpuCanvasOptions, GpuCanvasStats } from './types';\nexport { supportsGpuCanvas };\n\n/**\n * Creates an infinite canvas inside `host`. Uses WebGPU + HTML-in-Canvas when the browser has\n * both (Chrome 157+), and plain DOM transforms everywhere else; the API is the same.\n */\nexport async function createGpuCanvas(\n host: HTMLElement,\n options: GpuCanvasOptions = {},\n): Promise<GpuCanvas> {\n const mode = options.mode ?? 'auto';\n if (mode === 'dom' || (mode === 'auto' && !supportsGpuCanvas())) {\n return createDomCanvas(host, options);\n }\n try {\n return await createGpuBackend(host, options);\n } catch (error) {\n if (mode === 'gpu') throw error;\n return createDomCanvas(host, options);\n }\n}\n"],"mappings":"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","names":["right","bottom","scale","drawn","createGpuCanvas"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { G as GpuCanvasOptions, a as GpuCanvas } from './types-D8v10v6m.js';
|
|
2
|
+
export { C as Camera, b as GpuCanvasMode, c as GpuCanvasStats, R as Rect, f as fitCamera, z as zoomAt } from './types-D8v10v6m.js';
|
|
3
|
+
|
|
4
|
+
declare function supportsGpuCanvas(): boolean;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates an infinite canvas inside `host`. Uses WebGPU + HTML-in-Canvas when the browser has
|
|
8
|
+
* both (Chrome 157+), and plain DOM transforms everywhere else; the API is the same.
|
|
9
|
+
*/
|
|
10
|
+
declare function createGpuCanvas(host: HTMLElement, options?: GpuCanvasOptions): Promise<GpuCanvas>;
|
|
11
|
+
|
|
12
|
+
export { GpuCanvas, GpuCanvasOptions, createGpuCanvas, supportsGpuCanvas };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
+
import { G as GpuCanvasOptions, a as GpuCanvas$1 } from './types-D8v10v6m.js';
|
|
4
|
+
|
|
5
|
+
interface GpuCanvasProps extends GpuCanvasOptions {
|
|
6
|
+
children?: ReactNode;
|
|
7
|
+
className?: string;
|
|
8
|
+
style?: CSSProperties;
|
|
9
|
+
onReady?: (canvas: GpuCanvas$1) => void;
|
|
10
|
+
}
|
|
11
|
+
/** An infinite canvas. Children are `<CanvasNode>`s; they mount once the canvas is ready. */
|
|
12
|
+
declare function GpuCanvas({ children, className, style, onReady, ...options }: GpuCanvasProps): react.JSX.Element;
|
|
13
|
+
interface CanvasNodeProps {
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
style?: CSSProperties;
|
|
21
|
+
}
|
|
22
|
+
/** A live DOM node placed on the canvas in world units. Events, focus and state work as usual. */
|
|
23
|
+
declare function CanvasNode({ x, y, width, height, children, className, style }: CanvasNodeProps): react.ReactPortal;
|
|
24
|
+
declare const useGpuCanvas: () => GpuCanvas$1 | null;
|
|
25
|
+
|
|
26
|
+
export { CanvasNode, type CanvasNodeProps, GpuCanvas, type GpuCanvasProps, useGpuCanvas };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import {
|
|
3
|
+
createGpuCanvas
|
|
4
|
+
} from "./chunk-VOFDBN5J.js";
|
|
5
|
+
|
|
6
|
+
// src/react.tsx
|
|
7
|
+
import {
|
|
8
|
+
createContext,
|
|
9
|
+
useContext,
|
|
10
|
+
useEffect,
|
|
11
|
+
useLayoutEffect,
|
|
12
|
+
useRef,
|
|
13
|
+
useState
|
|
14
|
+
} from "react";
|
|
15
|
+
import { createPortal } from "react-dom";
|
|
16
|
+
import { jsx } from "react/jsx-runtime";
|
|
17
|
+
var CanvasContext = createContext(null);
|
|
18
|
+
function GpuCanvas({ children, className, style, onReady, ...options }) {
|
|
19
|
+
const hostRef = useRef(null);
|
|
20
|
+
const [canvas, setCanvas] = useState(null);
|
|
21
|
+
const latest = useRef({ options, onReady });
|
|
22
|
+
latest.current = { options, onReady };
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
let disposed = false;
|
|
25
|
+
let created = null;
|
|
26
|
+
createGpuCanvas(hostRef.current, latest.current.options).then((instance) => {
|
|
27
|
+
if (disposed) return instance.destroy();
|
|
28
|
+
created = instance;
|
|
29
|
+
setCanvas(instance);
|
|
30
|
+
latest.current.onReady?.(instance);
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
disposed = true;
|
|
34
|
+
created?.destroy();
|
|
35
|
+
setCanvas(null);
|
|
36
|
+
};
|
|
37
|
+
}, []);
|
|
38
|
+
return /* @__PURE__ */ jsx("div", { ref: hostRef, className, style: { position: "relative", ...style }, children: /* @__PURE__ */ jsx(CanvasContext.Provider, { value: canvas, children: canvas && children }) });
|
|
39
|
+
}
|
|
40
|
+
function CanvasNode({ x, y, width, height, children, className, style }) {
|
|
41
|
+
const canvas = useContext(CanvasContext);
|
|
42
|
+
if (!canvas) throw new Error("<CanvasNode> must be inside <GpuCanvas>");
|
|
43
|
+
const [element] = useState(() => document.createElement("div"));
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
canvas.addNode(element, { x, y, width, height });
|
|
46
|
+
return () => canvas.removeNode(element);
|
|
47
|
+
}, [canvas, element]);
|
|
48
|
+
const mounted = useRef(false);
|
|
49
|
+
useLayoutEffect(() => {
|
|
50
|
+
if (mounted.current) canvas.updateNode(element, { x, y, width, height });
|
|
51
|
+
mounted.current = true;
|
|
52
|
+
}, [canvas, element, x, y, width, height]);
|
|
53
|
+
useLayoutEffect(() => {
|
|
54
|
+
element.className = className ?? "";
|
|
55
|
+
}, [element, className]);
|
|
56
|
+
return createPortal(
|
|
57
|
+
/* @__PURE__ */ jsx("div", { style: { width: "100%", height: "100%", ...style }, children }),
|
|
58
|
+
element
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
var useGpuCanvas = () => useContext(CanvasContext);
|
|
62
|
+
export {
|
|
63
|
+
CanvasNode,
|
|
64
|
+
GpuCanvas,
|
|
65
|
+
useGpuCanvas
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react.tsx"],"sourcesContent":["import {\n createContext,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport {\n createGpuCanvas,\n type GpuCanvas as GpuCanvasInstance,\n type GpuCanvasOptions,\n} from './index';\n\nconst CanvasContext = createContext<GpuCanvasInstance | null>(null);\n\nexport interface GpuCanvasProps extends GpuCanvasOptions {\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n onReady?: (canvas: GpuCanvasInstance) => void;\n}\n\n/** An infinite canvas. Children are `<CanvasNode>`s; they mount once the canvas is ready. */\nexport function GpuCanvas({ children, className, style, onReady, ...options }: GpuCanvasProps) {\n const hostRef = useRef<HTMLDivElement>(null);\n const [canvas, setCanvas] = useState<GpuCanvasInstance | null>(null);\n const latest = useRef({ options, onReady });\n latest.current = { options, onReady };\n\n useEffect(() => {\n let disposed = false;\n let created: GpuCanvasInstance | null = null;\n createGpuCanvas(hostRef.current!, latest.current.options).then((instance) => {\n if (disposed) return instance.destroy();\n created = instance;\n setCanvas(instance);\n latest.current.onReady?.(instance);\n });\n return () => {\n disposed = true;\n created?.destroy();\n setCanvas(null);\n };\n // The mode and GPU resources are fixed for the canvas's lifetime; remount to change them.\n }, []);\n\n return (\n <div ref={hostRef} className={className} style={{ position: 'relative', ...style }}>\n <CanvasContext.Provider value={canvas}>{canvas && children}</CanvasContext.Provider>\n </div>\n );\n}\n\nexport interface CanvasNodeProps {\n x: number;\n y: number;\n width: number;\n height: number;\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n}\n\n/** A live DOM node placed on the canvas in world units. Events, focus and state work as usual. */\nexport function CanvasNode({ x, y, width, height, children, className, style }: CanvasNodeProps) {\n const canvas = useContext(CanvasContext);\n if (!canvas) throw new Error('<CanvasNode> must be inside <GpuCanvas>');\n const [element] = useState(() => document.createElement('div'));\n\n useLayoutEffect(() => {\n canvas.addNode(element, { x, y, width, height });\n return () => canvas.removeNode(element);\n // Position changes go through updateNode below without remounting.\n }, [canvas, element]);\n\n const mounted = useRef(false);\n useLayoutEffect(() => {\n if (mounted.current) canvas.updateNode(element, { x, y, width, height });\n mounted.current = true;\n }, [canvas, element, x, y, width, height]);\n\n useLayoutEffect(() => {\n element.className = className ?? '';\n }, [element, className]);\n\n return createPortal(\n <div style={{ width: '100%', height: '100%', ...style }}>{children}</div>,\n element,\n );\n}\n\nexport const useGpuCanvas = () => useContext(CanvasContext);\n"],"mappings":";;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AACP,SAAS,oBAAoB;AA0CvB;AAnCN,IAAM,gBAAgB,cAAwC,IAAI;AAU3D,SAAS,UAAU,EAAE,UAAU,WAAW,OAAO,SAAS,GAAG,QAAQ,GAAmB;AAC7F,QAAM,UAAU,OAAuB,IAAI;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAmC,IAAI;AACnE,QAAM,SAAS,OAAO,EAAE,SAAS,QAAQ,CAAC;AAC1C,SAAO,UAAU,EAAE,SAAS,QAAQ;AAEpC,YAAU,MAAM;AACd,QAAI,WAAW;AACf,QAAI,UAAoC;AACxC,oBAAgB,QAAQ,SAAU,OAAO,QAAQ,OAAO,EAAE,KAAK,CAAC,aAAa;AAC3E,UAAI,SAAU,QAAO,SAAS,QAAQ;AACtC,gBAAU;AACV,gBAAU,QAAQ;AAClB,aAAO,QAAQ,UAAU,QAAQ;AAAA,IACnC,CAAC;AACD,WAAO,MAAM;AACX,iBAAW;AACX,eAAS,QAAQ;AACjB,gBAAU,IAAI;AAAA,IAChB;AAAA,EAEF,GAAG,CAAC,CAAC;AAEL,SACE,oBAAC,SAAI,KAAK,SAAS,WAAsB,OAAO,EAAE,UAAU,YAAY,GAAG,MAAM,GAC/E,8BAAC,cAAc,UAAd,EAAuB,OAAO,QAAS,oBAAU,UAAS,GAC7D;AAEJ;AAaO,SAAS,WAAW,EAAE,GAAG,GAAG,OAAO,QAAQ,UAAU,WAAW,MAAM,GAAoB;AAC/F,QAAM,SAAS,WAAW,aAAa;AACvC,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,yCAAyC;AACtE,QAAM,CAAC,OAAO,IAAI,SAAS,MAAM,SAAS,cAAc,KAAK,CAAC;AAE9D,kBAAgB,MAAM;AACpB,WAAO,QAAQ,SAAS,EAAE,GAAG,GAAG,OAAO,OAAO,CAAC;AAC/C,WAAO,MAAM,OAAO,WAAW,OAAO;AAAA,EAExC,GAAG,CAAC,QAAQ,OAAO,CAAC;AAEpB,QAAM,UAAU,OAAO,KAAK;AAC5B,kBAAgB,MAAM;AACpB,QAAI,QAAQ,QAAS,QAAO,WAAW,SAAS,EAAE,GAAG,GAAG,OAAO,OAAO,CAAC;AACvE,YAAQ,UAAU;AAAA,EACpB,GAAG,CAAC,QAAQ,SAAS,GAAG,GAAG,OAAO,MAAM,CAAC;AAEzC,kBAAgB,MAAM;AACpB,YAAQ,YAAY,aAAa;AAAA,EACnC,GAAG,CAAC,SAAS,SAAS,CAAC;AAEvB,SAAO;AAAA,IACL,oBAAC,SAAI,OAAO,EAAE,OAAO,QAAQ,QAAQ,QAAQ,GAAG,MAAM,GAAI,UAAS;AAAA,IACnE;AAAA,EACF;AACF;AAEO,IAAM,eAAe,MAAM,WAAW,aAAa;","names":[]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
interface Camera {
|
|
2
|
+
/** World coordinate shown at the viewport's top-left corner. */
|
|
3
|
+
x: number;
|
|
4
|
+
y: number;
|
|
5
|
+
zoom: number;
|
|
6
|
+
}
|
|
7
|
+
interface Rect {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
}
|
|
13
|
+
/** Zooms by `factor` while keeping the world point under (screenX, screenY) fixed. */
|
|
14
|
+
declare function zoomAt(camera: Camera, screenX: number, screenY: number, factor: number): Camera;
|
|
15
|
+
/** Fits `bounds` into the viewport with `padding` CSS pixels on each side. */
|
|
16
|
+
declare function fitCamera(bounds: Rect, viewportWidth: number, viewportHeight: number, padding?: number): Camera;
|
|
17
|
+
|
|
18
|
+
type GpuCanvasMode = 'gpu' | 'dom';
|
|
19
|
+
interface GpuCanvasOptions {
|
|
20
|
+
/** `auto` uses the GPU path when WebGPU and HTML-in-Canvas are available, otherwise DOM. */
|
|
21
|
+
mode?: 'auto' | GpuCanvasMode;
|
|
22
|
+
/** Canvas background, any CSS color. */
|
|
23
|
+
background?: string;
|
|
24
|
+
/** Outline of selected nodes, any CSS color. Default `#4f8cff`. */
|
|
25
|
+
selectionColor?: string;
|
|
26
|
+
camera?: Camera;
|
|
27
|
+
/** Side of each atlas layer in texels. Default 4096, capped by the device. */
|
|
28
|
+
atlasSize?: number;
|
|
29
|
+
/** Atlas layers; each 4096 layer costs 64 MB of GPU memory. One holds thumbnails. Default 5. */
|
|
30
|
+
atlasLayers?: number;
|
|
31
|
+
/** Most snapshot pixels rasterized per frame; the rest wait for later frames. Default 24M. */
|
|
32
|
+
rasterPixelBudget?: number;
|
|
33
|
+
/** Most raster calls per frame; one call covers a whole page or one node. Default 12. */
|
|
34
|
+
maxRastersPerFrame?: number;
|
|
35
|
+
/** Main-thread milliseconds a frame may spend issuing rasters. Default 6. */
|
|
36
|
+
rasterTimeBudgetMs?: number;
|
|
37
|
+
/** Side of a page, the unit nodes are batched into for rasterizing, in CSS pixels. Default 2048. */
|
|
38
|
+
pageSize?: number;
|
|
39
|
+
onCameraChange?: (camera: Camera) => void;
|
|
40
|
+
}
|
|
41
|
+
interface GpuCanvasStats {
|
|
42
|
+
mode: GpuCanvasMode;
|
|
43
|
+
nodes: number;
|
|
44
|
+
visible: number;
|
|
45
|
+
/** Visible nodes still waiting for a snapshot at the current zoom. */
|
|
46
|
+
pending: number;
|
|
47
|
+
/** Nodes that got fresh pixels in the last frame. */
|
|
48
|
+
rasterizedLastFrame: number;
|
|
49
|
+
/** Raster calls in the last frame (gpu only). */
|
|
50
|
+
rasterCallsLastFrame?: number;
|
|
51
|
+
/** Main-thread milliseconds the last frame spent issuing rasters. */
|
|
52
|
+
rasterMs: number;
|
|
53
|
+
/** Pages nodes are batched into, and how many are drawable right now (gpu only). */
|
|
54
|
+
pages?: number;
|
|
55
|
+
drawablePages?: number;
|
|
56
|
+
atlasLayersUsed: number;
|
|
57
|
+
/** Main-thread milliseconds the last composite took. */
|
|
58
|
+
frameMs: number;
|
|
59
|
+
/** Frames composited since creation (gpu only). An idle canvas draws none. */
|
|
60
|
+
frames?: number;
|
|
61
|
+
/** Times a visible node that had pixels showed a placeholder instead: visible flicker. */
|
|
62
|
+
lostPixels?: number;
|
|
63
|
+
/** Times a sharp zoomed-in node fell back to its blurrier whole snapshot. */
|
|
64
|
+
lostDetail?: number;
|
|
65
|
+
/** Share of the thumbnail layer in use, 0 to 1 (gpu only). */
|
|
66
|
+
thumbnailFill?: number;
|
|
67
|
+
/** Visible nodes showing only their thumbnail while a sharper snapshot is drawn (gpu only). */
|
|
68
|
+
onThumbnail?: number;
|
|
69
|
+
/** Visible nodes the last frame planned to rasterize: missing, dirty, blurry, oversampled. */
|
|
70
|
+
needs?: number[];
|
|
71
|
+
}
|
|
72
|
+
interface GpuCanvas {
|
|
73
|
+
readonly mode: GpuCanvasMode;
|
|
74
|
+
/** The element that receives camera input; the host's only child. */
|
|
75
|
+
readonly surface: HTMLElement;
|
|
76
|
+
/** Places `element` on the canvas at `rect` (world units). The element keeps its own DOM and events. */
|
|
77
|
+
addNode(element: HTMLElement, rect: Rect): void;
|
|
78
|
+
updateNode(element: HTMLElement, rect: Rect): void;
|
|
79
|
+
removeNode(element: HTMLElement): void;
|
|
80
|
+
/** Where `element` sits on the canvas, in world units, or null when it is not a node. */
|
|
81
|
+
getNodeRect(element: HTMLElement): Rect | null;
|
|
82
|
+
/** Nodes that overlap `rect` (world units), such as the ones under a selection box. */
|
|
83
|
+
getNodesIn(rect: Rect): HTMLElement[];
|
|
84
|
+
/** Outlines these nodes as selected, replacing the previous selection. Pass `[]` to clear. */
|
|
85
|
+
setSelection(elements: Iterable<HTMLElement>): void;
|
|
86
|
+
getCamera(): Camera;
|
|
87
|
+
setCamera(camera: Camera): void;
|
|
88
|
+
/**
|
|
89
|
+
* Redraws every node. Call it after a change the nodes' own DOM does not show, such as a theme
|
|
90
|
+
* class on `<html>` or a web font finishing loading.
|
|
91
|
+
*/
|
|
92
|
+
invalidate(): void;
|
|
93
|
+
/** Changes the canvas background, any CSS color. */
|
|
94
|
+
setBackground(color: string): void;
|
|
95
|
+
/** Fits all nodes in view. */
|
|
96
|
+
fit(padding?: number): void;
|
|
97
|
+
getStats(): GpuCanvasStats;
|
|
98
|
+
destroy(): void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { type Camera as C, type GpuCanvasOptions as G, type Rect as R, type GpuCanvas as a, type GpuCanvasMode as b, type GpuCanvasStats as c, fitCamera as f, zoomAt as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,76 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gpucanvas",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Put thousands of live DOM nodes on an infinite canvas and let the GPU pan and zoom them.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"canvas",
|
|
7
|
+
"infinite-canvas",
|
|
8
|
+
"webgpu",
|
|
9
|
+
"html-in-canvas",
|
|
10
|
+
"react",
|
|
11
|
+
"dom",
|
|
12
|
+
"design-tools"
|
|
13
|
+
],
|
|
14
|
+
"author": "Berkant Ay",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"homepage": "https://github.com/berkantay/gpucanvas#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/berkantay/gpucanvas.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": "https://github.com/berkantay/gpucanvas/issues",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"sideEffects": false,
|
|
24
|
+
"main": "./dist/index.js",
|
|
25
|
+
"module": "./dist/index.js",
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"import": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./react": {
|
|
33
|
+
"types": "./dist/react.d.ts",
|
|
34
|
+
"import": "./dist/react.js"
|
|
35
|
+
},
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist"
|
|
40
|
+
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"dev": "vite demo",
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"build:demo": "vite build demo",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"prepublishOnly": "tsc --noEmit && tsup"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"react": ">=18",
|
|
50
|
+
"react-dom": ">=18"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"react": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"react-dom": {
|
|
57
|
+
"optional": true
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@fontsource-variable/inter": "^5.3.0",
|
|
62
|
+
"@remixicon/react": "^4.9.0",
|
|
63
|
+
"@tailwindcss/vite": "^4.1.0",
|
|
64
|
+
"@types/react": "^19.2.0",
|
|
65
|
+
"@types/react-dom": "^19.2.0",
|
|
66
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
67
|
+
"@webgpu/types": "^0.1.60",
|
|
68
|
+
"react": "19.2.4",
|
|
69
|
+
"react-dom": "19.2.4",
|
|
70
|
+
"tailwindcss": "4.3.3",
|
|
71
|
+
"tsup": "8",
|
|
72
|
+
"typescript": "^5.9.0",
|
|
73
|
+
"vite": "^7.0.0"
|
|
74
|
+
},
|
|
75
|
+
"packageManager": "bun@1.3.12"
|
|
76
|
+
}
|