aigpu 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +85 -0
- package/bin/aigpu.js +15 -0
- package/client.d.ts +15 -0
- package/dist/agent-tools.d.ts +61 -0
- package/dist/agent-tools.d.ts.map +1 -0
- package/dist/agent-tools.js +107 -0
- package/dist/agent-tools.js.map +1 -0
- package/dist/agent.d.ts +56 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +243 -0
- package/dist/agent.js.map +1 -0
- package/dist/api-types.d.ts +56 -0
- package/dist/api-types.d.ts.map +1 -0
- package/dist/api-types.js +2 -0
- package/dist/api-types.js.map +1 -0
- package/dist/bind-cache.d.ts +19 -0
- package/dist/bind-cache.d.ts.map +1 -0
- package/dist/bind-cache.js +43 -0
- package/dist/bind-cache.js.map +1 -0
- package/dist/bundle.d.ts +30 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +127 -0
- package/dist/bundle.js.map +1 -0
- package/dist/claim-validation-encode.d.ts +14 -0
- package/dist/claim-validation-encode.d.ts.map +1 -0
- package/dist/claim-validation-encode.js +28 -0
- package/dist/claim-validation-encode.js.map +1 -0
- package/dist/claim-validation.d.ts +73 -0
- package/dist/claim-validation.d.ts.map +1 -0
- package/dist/claim-validation.js +135 -0
- package/dist/claim-validation.js.map +1 -0
- package/dist/cli/bin/aigpu.js +101 -0
- package/dist/cli/lib/check/run.js +153 -0
- package/dist/cli/lib/docs/commands/cat.js +12 -0
- package/dist/cli/lib/docs/commands/find.js +180 -0
- package/dist/cli/lib/docs/commands/grep.js +49 -0
- package/dist/cli/lib/docs/commands/ls.js +41 -0
- package/dist/cli/lib/docs/commands/path.js +12 -0
- package/dist/cli/lib/docs/commands/resolve.js +41 -0
- package/dist/cli/lib/docs/commands/shared.js +13 -0
- package/dist/cli/lib/docs/commands/symbols.js +25 -0
- package/dist/cli/lib/docs/errors.js +8 -0
- package/dist/cli/lib/docs/generate/check-drift.js +61 -0
- package/dist/cli/lib/docs/generate/cli.js +12 -0
- package/dist/cli/lib/docs/generate/generate.js +113 -0
- package/dist/cli/lib/docs/generate/manifest.js +218 -0
- package/dist/cli/lib/docs/generate/skill.js +150 -0
- package/dist/cli/lib/docs/help.js +21 -0
- package/dist/cli/lib/docs/index.js +62 -0
- package/dist/cli/lib/docs/model.js +7 -0
- package/dist/cli/lib/docs/run.js +24 -0
- package/dist/cli/lib/docs/service.js +119 -0
- package/dist/cli/lib/doctor/run.js +236 -0
- package/dist/cli/lib/examples/errors.js +13 -0
- package/dist/cli/lib/examples/hashing.js +9 -0
- package/dist/cli/lib/examples/help.js +13 -0
- package/dist/cli/lib/examples/local-source.js +142 -0
- package/dist/cli/lib/examples/paths.js +12 -0
- package/dist/cli/lib/examples/pull.js +58 -0
- package/dist/cli/lib/examples/run.js +71 -0
- package/dist/cli/lib/examples/search.js +6 -0
- package/dist/cli/lib/examples/service.js +118 -0
- package/dist/cli/lib/generated/docs-manifest.generated.js +4758 -0
- package/dist/cli/lib/install-dawn/run.js +24 -0
- package/dist/cli/lib/install-software-renderer/run.js +17 -0
- package/dist/cli/lib/mcp/http.js +93 -0
- package/dist/cli/lib/mcp/server.js +258 -0
- package/dist/cli/lib/mcp/stdio.js +34 -0
- package/dist/cli/lib/snapshot/png.js +62 -0
- package/dist/cli/lib/snapshot/render.js +22 -0
- package/dist/cli/lib/snapshot/run.js +79 -0
- package/dist/cli/lib/snapshot/shaders.js +11 -0
- package/dist/cli/package.json +4 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +2 -0
- package/dist/client.js.map +1 -0
- package/dist/clock.d.ts +26 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +50 -0
- package/dist/clock.js.map +1 -0
- package/dist/compute.d.ts +41 -0
- package/dist/compute.d.ts.map +1 -0
- package/dist/compute.js +132 -0
- package/dist/compute.js.map +1 -0
- package/dist/core/render-bundle.d.ts +30 -0
- package/dist/core/render-bundle.d.ts.map +1 -0
- package/dist/core/render-bundle.js +39 -0
- package/dist/core/render-bundle.js.map +1 -0
- package/dist/core/storage-buffer.d.ts +81 -0
- package/dist/core/storage-buffer.d.ts.map +1 -0
- package/dist/core/storage-buffer.js +91 -0
- package/dist/core/storage-buffer.js.map +1 -0
- package/dist/core/structured-uniform.d.ts +33 -0
- package/dist/core/structured-uniform.d.ts.map +1 -0
- package/dist/core/structured-uniform.js +110 -0
- package/dist/core/structured-uniform.js.map +1 -0
- package/dist/core/uniform-pool-internals.d.ts +12 -0
- package/dist/core/uniform-pool-internals.d.ts.map +1 -0
- package/dist/core/uniform-pool-internals.js +24 -0
- package/dist/core/uniform-pool-internals.js.map +1 -0
- package/dist/core/uniform-pool-types.d.ts +22 -0
- package/dist/core/uniform-pool-types.d.ts.map +1 -0
- package/dist/core/uniform-pool-types.js +2 -0
- package/dist/core/uniform-pool-types.js.map +1 -0
- package/dist/core/uniform-pool.d.ts +22 -0
- package/dist/core/uniform-pool.d.ts.map +1 -0
- package/dist/core/uniform-pool.js +128 -0
- package/dist/core/uniform-pool.js.map +1 -0
- package/dist/core/uniform.d.ts +55 -0
- package/dist/core/uniform.d.ts.map +1 -0
- package/dist/core/uniform.js +64 -0
- package/dist/core/uniform.js.map +1 -0
- package/dist/core/wgsl-alignment.d.ts +24 -0
- package/dist/core/wgsl-alignment.d.ts.map +1 -0
- package/dist/core/wgsl-alignment.js +81 -0
- package/dist/core/wgsl-alignment.js.map +1 -0
- package/dist/core.d.ts +13 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +7 -0
- package/dist/core.js.map +1 -0
- package/dist/dom.d.ts +37 -0
- package/dist/dom.d.ts.map +1 -0
- package/dist/dom.js +81 -0
- package/dist/dom.js.map +1 -0
- package/dist/draw-protocols.d.ts +36 -0
- package/dist/draw-protocols.d.ts.map +1 -0
- package/dist/draw-protocols.js +10 -0
- package/dist/draw-protocols.js.map +1 -0
- package/dist/draw.d.ts +248 -0
- package/dist/draw.d.ts.map +1 -0
- package/dist/draw.js +890 -0
- package/dist/draw.js.map +1 -0
- package/dist/effect.d.ts +53 -0
- package/dist/effect.d.ts.map +1 -0
- package/dist/effect.js +75 -0
- package/dist/effect.js.map +1 -0
- package/dist/entry-metadata.d.ts +16 -0
- package/dist/entry-metadata.d.ts.map +1 -0
- package/dist/entry-metadata.js +27 -0
- package/dist/entry-metadata.js.map +1 -0
- package/dist/errors.d.ts +96 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +616 -0
- package/dist/errors.js.map +1 -0
- package/dist/frame-protocols.d.ts +107 -0
- package/dist/frame-protocols.d.ts.map +1 -0
- package/dist/frame-protocols.js +20 -0
- package/dist/frame-protocols.js.map +1 -0
- package/dist/frame-state.d.ts +32 -0
- package/dist/frame-state.d.ts.map +1 -0
- package/dist/frame-state.js +62 -0
- package/dist/frame-state.js.map +1 -0
- package/dist/frame.d.ts +127 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +645 -0
- package/dist/frame.js.map +1 -0
- package/dist/group-claim.d.ts +9 -0
- package/dist/group-claim.d.ts.map +1 -0
- package/dist/group-claim.js +7 -0
- package/dist/group-claim.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/index.js.map +1 -0
- package/dist/indirect.d.ts +39 -0
- package/dist/indirect.d.ts.map +1 -0
- package/dist/indirect.js +61 -0
- package/dist/indirect.js.map +1 -0
- package/dist/init-from-device.d.ts +11 -0
- package/dist/init-from-device.d.ts.map +1 -0
- package/dist/init-from-device.js +56 -0
- package/dist/init-from-device.js.map +1 -0
- package/dist/init.d.ts +12 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +7 -0
- package/dist/init.js.map +1 -0
- package/dist/kernel.d.ts +104 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +151 -0
- package/dist/kernel.js.map +1 -0
- package/dist/lifecycle.d.ts +10 -0
- package/dist/lifecycle.d.ts.map +1 -0
- package/dist/lifecycle.js +21 -0
- package/dist/lifecycle.js.map +1 -0
- package/dist/live-kernel.d.ts +48 -0
- package/dist/live-kernel.d.ts.map +1 -0
- package/dist/live-kernel.js +67 -0
- package/dist/live-kernel.js.map +1 -0
- package/dist/mock.d.ts +42 -0
- package/dist/mock.d.ts.map +1 -0
- package/dist/mock.js +29 -0
- package/dist/mock.js.map +1 -0
- package/dist/node.d.ts +48 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +36 -0
- package/dist/node.js.map +1 -0
- package/dist/ping-pong.d.ts +19 -0
- package/dist/ping-pong.d.ts.map +1 -0
- package/dist/ping-pong.js +67 -0
- package/dist/ping-pong.js.map +1 -0
- package/dist/pipeline-store.d.ts +89 -0
- package/dist/pipeline-store.d.ts.map +1 -0
- package/dist/pipeline-store.js +366 -0
- package/dist/pipeline-store.js.map +1 -0
- package/dist/query-ring.d.ts +91 -0
- package/dist/query-ring.d.ts.map +1 -0
- package/dist/query-ring.js +178 -0
- package/dist/query-ring.js.map +1 -0
- package/dist/render-service.d.ts +23 -0
- package/dist/render-service.d.ts.map +1 -0
- package/dist/render-service.js +37 -0
- package/dist/render-service.js.map +1 -0
- package/dist/sampler.d.ts +19 -0
- package/dist/sampler.d.ts.map +1 -0
- package/dist/sampler.js +46 -0
- package/dist/sampler.js.map +1 -0
- package/dist/scene/camera.d.ts +91 -0
- package/dist/scene/camera.d.ts.map +1 -0
- package/dist/scene/camera.js +221 -0
- package/dist/scene/camera.js.map +1 -0
- package/dist/scene/geometries.d.ts +26 -0
- package/dist/scene/geometries.d.ts.map +1 -0
- package/dist/scene/geometries.js +26 -0
- package/dist/scene/geometries.js.map +1 -0
- package/dist/scene/geometry-descriptor.d.ts +120 -0
- package/dist/scene/geometry-descriptor.d.ts.map +1 -0
- package/dist/scene/geometry-descriptor.js +378 -0
- package/dist/scene/geometry-descriptor.js.map +1 -0
- package/dist/scene/geometry-recipe.d.ts +41 -0
- package/dist/scene/geometry-recipe.d.ts.map +1 -0
- package/dist/scene/geometry-recipe.js +40 -0
- package/dist/scene/geometry-recipe.js.map +1 -0
- package/dist/scene/geometry-src/camera-math.d.ts +6 -0
- package/dist/scene/geometry-src/camera-math.d.ts.map +1 -0
- package/dist/scene/geometry-src/camera-math.js +47 -0
- package/dist/scene/geometry-src/camera-math.js.map +1 -0
- package/dist/scene/geometry-src/camera.d.ts +10 -0
- package/dist/scene/geometry-src/camera.d.ts.map +1 -0
- package/dist/scene/geometry-src/camera.js +2 -0
- package/dist/scene/geometry-src/camera.js.map +1 -0
- package/dist/scene/geometry-src/capsule-data.d.ts +10 -0
- package/dist/scene/geometry-src/capsule-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/capsule-data.js +61 -0
- package/dist/scene/geometry-src/capsule-data.js.map +1 -0
- package/dist/scene/geometry-src/cone-data.d.ts +13 -0
- package/dist/scene/geometry-src/cone-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/cone-data.js +52 -0
- package/dist/scene/geometry-src/cone-data.js.map +1 -0
- package/dist/scene/geometry-src/cylinder-data.d.ts +14 -0
- package/dist/scene/geometry-src/cylinder-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/cylinder-data.js +58 -0
- package/dist/scene/geometry-src/cylinder-data.js.map +1 -0
- package/dist/scene/geometry-src/deg-to-rad.d.ts +2 -0
- package/dist/scene/geometry-src/deg-to-rad.d.ts.map +1 -0
- package/dist/scene/geometry-src/deg-to-rad.js +4 -0
- package/dist/scene/geometry-src/deg-to-rad.js.map +1 -0
- package/dist/scene/geometry-src/disk-data.d.ts +12 -0
- package/dist/scene/geometry-src/disk-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/disk-data.js +21 -0
- package/dist/scene/geometry-src/disk-data.js.map +1 -0
- package/dist/scene/geometry-src/errors.d.ts +3 -0
- package/dist/scene/geometry-src/errors.d.ts.map +1 -0
- package/dist/scene/geometry-src/errors.js +5 -0
- package/dist/scene/geometry-src/errors.js.map +1 -0
- package/dist/scene/geometry-src/icosphere-data.d.ts +8 -0
- package/dist/scene/geometry-src/icosphere-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/icosphere-data.js +58 -0
- package/dist/scene/geometry-src/icosphere-data.js.map +1 -0
- package/dist/scene/geometry-src/index.d.ts +36 -0
- package/dist/scene/geometry-src/index.d.ts.map +1 -0
- package/dist/scene/geometry-src/index.js +21 -0
- package/dist/scene/geometry-src/index.js.map +1 -0
- package/dist/scene/geometry-src/mesh-box.d.ts +8 -0
- package/dist/scene/geometry-src/mesh-box.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-box.js +57 -0
- package/dist/scene/geometry-src/mesh-box.js.map +1 -0
- package/dist/scene/geometry-src/mesh-cache.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-cache.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-cache.js +14 -0
- package/dist/scene/geometry-src/mesh-cache.js.map +1 -0
- package/dist/scene/geometry-src/mesh-capsule.d.ts +14 -0
- package/dist/scene/geometry-src/mesh-capsule.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-capsule.js +52 -0
- package/dist/scene/geometry-src/mesh-capsule.js.map +1 -0
- package/dist/scene/geometry-src/mesh-cone.d.ts +16 -0
- package/dist/scene/geometry-src/mesh-cone.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-cone.js +54 -0
- package/dist/scene/geometry-src/mesh-cone.js.map +1 -0
- package/dist/scene/geometry-src/mesh-cylinder.d.ts +18 -0
- package/dist/scene/geometry-src/mesh-cylinder.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-cylinder.js +67 -0
- package/dist/scene/geometry-src/mesh-cylinder.js.map +1 -0
- package/dist/scene/geometry-src/mesh-disk.d.ts +11 -0
- package/dist/scene/geometry-src/mesh-disk.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-disk.js +45 -0
- package/dist/scene/geometry-src/mesh-disk.js.map +1 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-dodecahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.d.ts +7 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.js +39 -0
- package/dist/scene/geometry-src/mesh-fullscreen-quad.js.map +1 -0
- package/dist/scene/geometry-src/mesh-icosahedron.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-icosahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-icosahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-icosahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-icosphere.d.ts +11 -0
- package/dist/scene/geometry-src/mesh-icosphere.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-icosphere.js +47 -0
- package/dist/scene/geometry-src/mesh-icosphere.js.map +1 -0
- package/dist/scene/geometry-src/mesh-octahedron.d.ts +4 -0
- package/dist/scene/geometry-src/mesh-octahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-octahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-octahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-plane.d.ts +13 -0
- package/dist/scene/geometry-src/mesh-plane.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-plane.js +51 -0
- package/dist/scene/geometry-src/mesh-plane.js.map +1 -0
- package/dist/scene/geometry-src/mesh-ring.d.ts +12 -0
- package/dist/scene/geometry-src/mesh-ring.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-ring.js +47 -0
- package/dist/scene/geometry-src/mesh-ring.js.map +1 -0
- package/dist/scene/geometry-src/mesh-sphere.d.ts +10 -0
- package/dist/scene/geometry-src/mesh-sphere.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-sphere.js +51 -0
- package/dist/scene/geometry-src/mesh-sphere.js.map +1 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.d.ts +5 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.js +10 -0
- package/dist/scene/geometry-src/mesh-tetrahedron.js.map +1 -0
- package/dist/scene/geometry-src/mesh-torus.d.ts +17 -0
- package/dist/scene/geometry-src/mesh-torus.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-torus.js +56 -0
- package/dist/scene/geometry-src/mesh-torus.js.map +1 -0
- package/dist/scene/geometry-src/mesh-types.d.ts +38 -0
- package/dist/scene/geometry-src/mesh-types.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh-types.js +2 -0
- package/dist/scene/geometry-src/mesh-types.js.map +1 -0
- package/dist/scene/geometry-src/mesh.d.ts +61 -0
- package/dist/scene/geometry-src/mesh.d.ts.map +1 -0
- package/dist/scene/geometry-src/mesh.js +49 -0
- package/dist/scene/geometry-src/mesh.js.map +1 -0
- package/dist/scene/geometry-src/orthographic-camera.d.ts +14 -0
- package/dist/scene/geometry-src/orthographic-camera.d.ts.map +1 -0
- package/dist/scene/geometry-src/orthographic-camera.js +9 -0
- package/dist/scene/geometry-src/orthographic-camera.js.map +1 -0
- package/dist/scene/geometry-src/perspective-camera.d.ts +12 -0
- package/dist/scene/geometry-src/perspective-camera.d.ts.map +1 -0
- package/dist/scene/geometry-src/perspective-camera.js +9 -0
- package/dist/scene/geometry-src/perspective-camera.js.map +1 -0
- package/dist/scene/geometry-src/plane-data.d.ts +12 -0
- package/dist/scene/geometry-src/plane-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/plane-data.js +25 -0
- package/dist/scene/geometry-src/plane-data.js.map +1 -0
- package/dist/scene/geometry-src/polyhedron-data.d.ts +7 -0
- package/dist/scene/geometry-src/polyhedron-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/polyhedron-data.js +42 -0
- package/dist/scene/geometry-src/polyhedron-data.js.map +1 -0
- package/dist/scene/geometry-src/polyhedron-mesh.d.ts +10 -0
- package/dist/scene/geometry-src/polyhedron-mesh.d.ts.map +1 -0
- package/dist/scene/geometry-src/polyhedron-mesh.js +37 -0
- package/dist/scene/geometry-src/polyhedron-mesh.js.map +1 -0
- package/dist/scene/geometry-src/polyhedron-seeds.d.ts +6 -0
- package/dist/scene/geometry-src/polyhedron-seeds.d.ts.map +1 -0
- package/dist/scene/geometry-src/polyhedron-seeds.js +51 -0
- package/dist/scene/geometry-src/polyhedron-seeds.js.map +1 -0
- package/dist/scene/geometry-src/primitive-data-utils.d.ts +9 -0
- package/dist/scene/geometry-src/primitive-data-utils.d.ts.map +1 -0
- package/dist/scene/geometry-src/primitive-data-utils.js +36 -0
- package/dist/scene/geometry-src/primitive-data-utils.js.map +1 -0
- package/dist/scene/geometry-src/ring-data.d.ts +13 -0
- package/dist/scene/geometry-src/ring-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/ring-data.js +24 -0
- package/dist/scene/geometry-src/ring-data.js.map +1 -0
- package/dist/scene/geometry-src/sphere-data.d.ts +11 -0
- package/dist/scene/geometry-src/sphere-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/sphere-data.js +31 -0
- package/dist/scene/geometry-src/sphere-data.js.map +1 -0
- package/dist/scene/geometry-src/srgb.d.ts +5 -0
- package/dist/scene/geometry-src/srgb.d.ts.map +1 -0
- package/dist/scene/geometry-src/srgb.js +23 -0
- package/dist/scene/geometry-src/srgb.js.map +1 -0
- package/dist/scene/geometry-src/torus-data.d.ts +11 -0
- package/dist/scene/geometry-src/torus-data.d.ts.map +1 -0
- package/dist/scene/geometry-src/torus-data.js +38 -0
- package/dist/scene/geometry-src/torus-data.js.map +1 -0
- package/dist/scene/geometry-src/vertex-layout.d.ts +2 -0
- package/dist/scene/geometry-src/vertex-layout.d.ts.map +1 -0
- package/dist/scene/geometry-src/vertex-layout.js +2 -0
- package/dist/scene/geometry-src/vertex-layout.js.map +1 -0
- package/dist/scene/geometry.d.ts +70 -0
- package/dist/scene/geometry.d.ts.map +1 -0
- package/dist/scene/geometry.js +71 -0
- package/dist/scene/geometry.js.map +1 -0
- package/dist/scene/orbit.d.ts +9 -0
- package/dist/scene/orbit.d.ts.map +1 -0
- package/dist/scene/orbit.js +15 -0
- package/dist/scene/orbit.js.map +1 -0
- package/dist/scene/tree/errors.d.ts +4 -0
- package/dist/scene/tree/errors.d.ts.map +1 -0
- package/dist/scene/tree/errors.js +19 -0
- package/dist/scene/tree/errors.js.map +1 -0
- package/dist/scene/tree/light.d.ts +42 -0
- package/dist/scene/tree/light.d.ts.map +1 -0
- package/dist/scene/tree/light.js +102 -0
- package/dist/scene/tree/light.js.map +1 -0
- package/dist/scene/tree/material.d.ts +79 -0
- package/dist/scene/tree/material.d.ts.map +1 -0
- package/dist/scene/tree/material.js +117 -0
- package/dist/scene/tree/material.js.map +1 -0
- package/dist/scene/tree/math.d.ts +26 -0
- package/dist/scene/tree/math.d.ts.map +1 -0
- package/dist/scene/tree/math.js +217 -0
- package/dist/scene/tree/math.js.map +1 -0
- package/dist/scene/tree/mesh-node.d.ts +17 -0
- package/dist/scene/tree/mesh-node.d.ts.map +1 -0
- package/dist/scene/tree/mesh-node.js +24 -0
- package/dist/scene/tree/mesh-node.js.map +1 -0
- package/dist/scene/tree/node.d.ts +70 -0
- package/dist/scene/tree/node.d.ts.map +1 -0
- package/dist/scene/tree/node.js +220 -0
- package/dist/scene/tree/node.js.map +1 -0
- package/dist/scene/tree/orbit-controls.d.ts +69 -0
- package/dist/scene/tree/orbit-controls.d.ts.map +1 -0
- package/dist/scene/tree/orbit-controls.js +226 -0
- package/dist/scene/tree/orbit-controls.js.map +1 -0
- package/dist/scene.d.ts +21 -0
- package/dist/scene.d.ts.map +1 -0
- package/dist/scene.js +11 -0
- package/dist/scene.js.map +1 -0
- package/dist/set-core.d.ts +50 -0
- package/dist/set-core.d.ts.map +1 -0
- package/dist/set-core.js +268 -0
- package/dist/set-core.js.map +1 -0
- package/dist/set-layouts.d.ts +20 -0
- package/dist/set-layouts.d.ts.map +1 -0
- package/dist/set-layouts.js +103 -0
- package/dist/set-layouts.js.map +1 -0
- package/dist/set-packing.d.ts +4 -0
- package/dist/set-packing.d.ts.map +1 -0
- package/dist/set-packing.js +82 -0
- package/dist/set-packing.js.map +1 -0
- package/dist/set-resources.d.ts +20 -0
- package/dist/set-resources.d.ts.map +1 -0
- package/dist/set-resources.js +140 -0
- package/dist/set-resources.js.map +1 -0
- package/dist/shader-source.d.ts +4 -0
- package/dist/shader-source.d.ts.map +1 -0
- package/dist/shader-source.js +21 -0
- package/dist/shader-source.js.map +1 -0
- package/dist/storage.d.ts +38 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +76 -0
- package/dist/storage.js.map +1 -0
- package/dist/surface.d.ts +89 -0
- package/dist/surface.d.ts.map +1 -0
- package/dist/surface.js +243 -0
- package/dist/surface.js.map +1 -0
- package/dist/target-offscreen.d.ts +41 -0
- package/dist/target-offscreen.d.ts.map +1 -0
- package/dist/target-offscreen.js +115 -0
- package/dist/target-offscreen.js.map +1 -0
- package/dist/target-utils.d.ts +37 -0
- package/dist/target-utils.d.ts.map +1 -0
- package/dist/target-utils.js +103 -0
- package/dist/target-utils.js.map +1 -0
- package/dist/target.d.ts +65 -0
- package/dist/target.d.ts.map +1 -0
- package/dist/target.js +2 -0
- package/dist/target.js.map +1 -0
- package/dist/timer.d.ts +78 -0
- package/dist/timer.d.ts.map +1 -0
- package/dist/timer.js +261 -0
- package/dist/timer.js.map +1 -0
- package/dist/uniforms-layout.d.ts +8 -0
- package/dist/uniforms-layout.d.ts.map +1 -0
- package/dist/uniforms-layout.js +84 -0
- package/dist/uniforms-layout.js.map +1 -0
- package/dist/uniforms.d.ts +43 -0
- package/dist/uniforms.d.ts.map +1 -0
- package/dist/uniforms.js +176 -0
- package/dist/uniforms.js.map +1 -0
- package/dist/visibility.d.ts +118 -0
- package/dist/visibility.d.ts.map +1 -0
- package/dist/visibility.js +290 -0
- package/dist/visibility.js.map +1 -0
- package/package.json +131 -0
package/dist/frame.js
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
1
|
+
import { claimedGroupValidationDone, discardClaimedGroupValidationResults, discardClaimedGroupValidationScopes, popLastClaimedGroupValidationScope, preferClaimedGroupValidationResult, pushClaimedGroupValidationScope, submittedWorkDone } from "./claim-validation.js";
|
|
2
|
+
import { endRenderPassWithClaimValidation } from "./claim-validation-encode.js";
|
|
3
|
+
import { assertDeviceUsable } from "./lifecycle.js";
|
|
4
|
+
import { claimedGroupNativeValidationError, frameAlreadySubmittedError, frameCanceledError, framePassActiveError, frameReentrantError, passClearDepthInvalidError, passClearStencilInvalidError, passDepthReadOnlyError, passDepthReadOnlyMsaaError, passPreserveClearDepthError, passPreserveClearStencilError, passPreserveMsaaError, passScissorInvalidError, passViewportInvalidError, queryNestedError, queryNoVisibilityError, surfaceNotInFrameError, targetRequiredError, timerInvalidError, AIGpuError, visibilityInvalidError } from "./errors.js";
|
|
5
|
+
import { enterFrame, isSurface, isSurfaceResizeCallbackActive, leaveFrame } from "./surface.js";
|
|
6
|
+
import { BUILT_IN_CLEAR_COLOR, hasStencilAspect, isTarget } from "./target-utils.js";
|
|
7
|
+
import { FRAME_PASS_ATTACHMENT, frameBundleOf, frameDrawableOf, framePassAttachmentOf } from "./frame-protocols.js";
|
|
8
|
+
import { frameState } from "./frame-state.js";
|
|
9
|
+
import { liveKernel } from "./live-kernel.js";
|
|
10
|
+
import { serviceToken } from "./kernel.js";
|
|
11
|
+
/**
|
|
12
|
+
* Opens a frame on `gpu`: advances the frame clock, runs `cb` against a fresh command encoder and
|
|
13
|
+
* submits it when the callback returns (or on the way out of a throw).
|
|
14
|
+
*
|
|
15
|
+
* Without a callback the frame is yours: encode passes at your own pace and finish it with
|
|
16
|
+
* `frame.submit()` or `frame.cancel()` — an unfinished frame holds every retain its passes took.
|
|
17
|
+
* Frames are not reentrant: opening one from inside another (or from a surface resize callback)
|
|
18
|
+
* throws `AIGPU-FRAME-REENTRANT`.
|
|
19
|
+
*/
|
|
20
|
+
export function frame(gpu, cb) {
|
|
21
|
+
return frameRunner(liveKernel(gpu, "frame")).frame(cb);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Runs `cb` once per animation frame until the returned handle is stopped.
|
|
25
|
+
*
|
|
26
|
+
* The loop belongs to the gpu's `scheduler` phase, so `gpu.dispose()` stops it before anything it
|
|
27
|
+
* could encode against is torn down; a loop that stops on its own drops that registration.
|
|
28
|
+
*/
|
|
29
|
+
export function frameLoop(gpu, cb, opts = {}) {
|
|
30
|
+
return frameRunner(liveKernel(gpu, "frameLoop")).loop(cb, opts);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* One runner per gpu: it carries the reentrancy guard, the frame clock and the loop registrations,
|
|
34
|
+
* so `frame()` and `frameLoop()` share the same state. Created on the first frame, never at `init()`.
|
|
35
|
+
*/
|
|
36
|
+
const frameRunnerToken = serviceToken("frame-runner");
|
|
37
|
+
function frameRunner(kernel) {
|
|
38
|
+
return kernel.service(frameRunnerToken, (self) => {
|
|
39
|
+
const state = frameState(self);
|
|
40
|
+
return new FrameRunner(() => {
|
|
41
|
+
let release = () => { };
|
|
42
|
+
const frame = new Frame(self.device, undefined, (error) => self.reportError(error), (promise) => { void self.trackDelivery(promise); }, () => release());
|
|
43
|
+
release = self.own("scheduler", () => frame.cancel());
|
|
44
|
+
return frame;
|
|
45
|
+
}, () => state.tick(), (handle) => self.own("scheduler", () => handle.stop()));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export class Frame {
|
|
49
|
+
device;
|
|
50
|
+
defaultTarget;
|
|
51
|
+
errorSink;
|
|
52
|
+
trackSettled;
|
|
53
|
+
releaseLifecycle;
|
|
54
|
+
/**
|
|
55
|
+
* Resolves after submitted GPU work completes and raw claimed-bind-group
|
|
56
|
+
* validation has been delivered to `gpu.onError`.
|
|
57
|
+
*
|
|
58
|
+
* This is a completion/timing signal only; it never rejects and is not an error
|
|
59
|
+
* channel.
|
|
60
|
+
*/
|
|
61
|
+
done = Promise.resolve();
|
|
62
|
+
#encoder;
|
|
63
|
+
#validations = [];
|
|
64
|
+
/**
|
|
65
|
+
* Everything a pass of this frame attached, as opaque {@link FrameOwner}s: timers and
|
|
66
|
+
* visibilities today, scene view generations later. The frame never learns what they are — it
|
|
67
|
+
* only guarantees each one sees exactly one `frameSubmitted` or `frameAbandoned`.
|
|
68
|
+
*/
|
|
69
|
+
#owners = new Set();
|
|
70
|
+
/**
|
|
71
|
+
* Owners whose per-frame bookkeeping a failed pass invalidated: their frame is neither finalized
|
|
72
|
+
* nor read back, so a throwing pass callback cannot leave a phantom result. Kept alongside the
|
|
73
|
+
* live set so a later pass re-attaching the same instance in this frame stays dropped too — the
|
|
74
|
+
* failed pass's span/slots are still in that instance's frame bookkeeping.
|
|
75
|
+
*/
|
|
76
|
+
#discardedOwners = new Set();
|
|
77
|
+
#submitted = false;
|
|
78
|
+
#canceled = false;
|
|
79
|
+
#passActive = false;
|
|
80
|
+
constructor(device, defaultTarget, errorSink, trackSettled, releaseLifecycle) {
|
|
81
|
+
this.device = device;
|
|
82
|
+
this.defaultTarget = defaultTarget;
|
|
83
|
+
this.errorSink = errorSink;
|
|
84
|
+
this.trackSettled = trackSettled;
|
|
85
|
+
this.releaseLifecycle = releaseLifecycle;
|
|
86
|
+
assertDeviceUsable(device, "Frame.constructor");
|
|
87
|
+
this.#encoder = device.gpu.createCommandEncoder({ label: "aigpu.frame" });
|
|
88
|
+
}
|
|
89
|
+
pass(target, body) {
|
|
90
|
+
// A canceled frame dropped its encoder: encoding into it would silently never run (and would
|
|
91
|
+
// re-take the telemetry retains cancel() just released), so reject the call instead. Checked
|
|
92
|
+
// before the device: `gpu.dispose()` cancels outstanding frames and *then* drops the device, so
|
|
93
|
+
// cancellation is the proximate cause there and names it better than a generic disposed device.
|
|
94
|
+
if (this.#canceled)
|
|
95
|
+
throw frameCanceledError("Frame.pass");
|
|
96
|
+
assertDeviceUsable(this.device, "Frame.pass");
|
|
97
|
+
const targetOnly = isTarget(target);
|
|
98
|
+
const cb = typeof body === "function" ? body : (p) => p.draw(body);
|
|
99
|
+
const resolvedTarget = targetOnly ? target : target.target ?? this.defaultTarget;
|
|
100
|
+
if (!resolvedTarget)
|
|
101
|
+
throw targetRequiredError("Frame.pass");
|
|
102
|
+
if (isSurface(resolvedTarget) && this.#submitted)
|
|
103
|
+
throw surfaceNotInFrameError("Frame.pass");
|
|
104
|
+
const clear = targetOnly ? undefined : target.clear;
|
|
105
|
+
const preserve = clear === false;
|
|
106
|
+
if (preserve && resolvedTarget.sampleCount === 4)
|
|
107
|
+
throw passPreserveMsaaError();
|
|
108
|
+
const clearDepth = targetOnly ? undefined : target.clearDepth;
|
|
109
|
+
if (clearDepth !== undefined) {
|
|
110
|
+
if (typeof clearDepth !== "number" || !(clearDepth >= 0 && clearDepth <= 1))
|
|
111
|
+
throw passClearDepthInvalidError(clearDepth);
|
|
112
|
+
if (preserve)
|
|
113
|
+
throw passPreserveClearDepthError();
|
|
114
|
+
// Dead-option rule, same as clearStencil below: without a depth attachment there is no
|
|
115
|
+
// depthClearValue in the descriptor, so clearDepth would silently do nothing.
|
|
116
|
+
if (!resolvedTarget.depth) {
|
|
117
|
+
throw passClearDepthInvalidError(clearDepth, "but the target has no depth attachment, so clearDepth would have no effect.", "Create the target with depth: true (or a depth format), or drop clearDepth.");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const clearStencil = targetOnly ? undefined : target.clearStencil;
|
|
121
|
+
if (clearStencil !== undefined) {
|
|
122
|
+
// WebGPU stencilClearValue is GPUStencilValue ([EnforceRange] u32); in-range values are masked to the stencil
|
|
123
|
+
// aspect's bit width by taking the LSBs, so values above 0xFF on stencil8 aspects are legal, not errors.
|
|
124
|
+
if (typeof clearStencil !== "number" || !Number.isInteger(clearStencil) || clearStencil < 0 || clearStencil > 0xFFFFFFFF) {
|
|
125
|
+
throw passClearStencilInvalidError(`received ${String(clearStencil)}; expected an integer in [0, 0xFFFFFFFF] (WebGPU GPUStencilValue).`);
|
|
126
|
+
}
|
|
127
|
+
if (preserve)
|
|
128
|
+
throw passPreserveClearStencilError();
|
|
129
|
+
const depthFormat = resolvedTarget.depth?.format;
|
|
130
|
+
if (!hasStencilAspect(depthFormat))
|
|
131
|
+
throw passClearStencilInvalidError(`received ${String(clearStencil)}, but the target's depth format ${depthFormat ? `"${depthFormat}"` : "(none)"} has no stencil aspect, so clearStencil would have no effect.`);
|
|
132
|
+
}
|
|
133
|
+
const depthReadOnly = targetOnly ? undefined : target.depthReadOnly;
|
|
134
|
+
if (depthReadOnly !== undefined && typeof depthReadOnly !== "boolean") {
|
|
135
|
+
throw passDepthReadOnlyError(`received ${previewValue(depthReadOnly)}; expected a boolean.`, "Pass depthReadOnly: true to open the pass with a read-only depth attachment, or omit it.");
|
|
136
|
+
}
|
|
137
|
+
if (depthReadOnly) {
|
|
138
|
+
// Dead-option and contradiction rules: color attachments still load/clear normally in a read-only
|
|
139
|
+
// pass, so clear (color) stays legal, but the read-only depth/stencil aspects omit their ops entirely
|
|
140
|
+
// and can never be cleared.
|
|
141
|
+
if (!resolvedTarget.depth)
|
|
142
|
+
throw passDepthReadOnlyError("is set, but the target has no depth attachment, so there is nothing to make read-only.", "Create the target with depth: true (or a depth format), or drop depthReadOnly.");
|
|
143
|
+
// Symmetric to the clear:false MSAA rule: an MSAA target's depth aspect is stored with
|
|
144
|
+
// storeOp "discard" (target-utils depthAttachment), so nothing survives the pass that wrote
|
|
145
|
+
// it. Reading it back read-only would depth-test every draw against discarded contents —
|
|
146
|
+
// silent garbage (with visibility attached, every query reports "hidden").
|
|
147
|
+
if (resolvedTarget.sampleCount === 4)
|
|
148
|
+
throw passDepthReadOnlyMsaaError();
|
|
149
|
+
if (clearDepth !== undefined)
|
|
150
|
+
throw passDepthReadOnlyError("cannot be combined with clearDepth; a read-only depth aspect omits its load/store ops and is never cleared.", "Remove clearDepth, or drop depthReadOnly.");
|
|
151
|
+
if (clearStencil !== undefined)
|
|
152
|
+
throw passDepthReadOnlyError("cannot be combined with clearStencil; a read-only stencil aspect omits its load/store ops and is never cleared.", "Remove clearStencil, or drop depthReadOnly.");
|
|
153
|
+
}
|
|
154
|
+
const viewport = targetOnly ? undefined : validatedViewport(target.viewport, this.device.gpu.limits, resolvedTarget.size);
|
|
155
|
+
const scissor = targetOnly ? undefined : validatedScissor(target.scissor, resolvedTarget.size);
|
|
156
|
+
// Owners this pass attached, in attach order: the rollback set if anything below throws.
|
|
157
|
+
const attached = [];
|
|
158
|
+
let encoder;
|
|
159
|
+
try {
|
|
160
|
+
// Attaching telemetry mutates per-frame bookkeeping before the native pass opens. Keep the whole
|
|
161
|
+
// attach/setup/body sequence atomic so any later validation/native setup failure rolls it back,
|
|
162
|
+
// not only exceptions thrown by the user callback.
|
|
163
|
+
const timer = targetOnly || target.timer === undefined ? undefined : this.#attach(target.timer, resolvedTarget, attached, timerAttachmentInvalidError);
|
|
164
|
+
const visibility = targetOnly || target.visibility === undefined ? undefined : this.#attach(target.visibility, resolvedTarget, attached, visibilityAttachmentInvalidError);
|
|
165
|
+
const occlusion = visibility?.occlusion;
|
|
166
|
+
// timestampWrites and occlusionQuerySet are target-independent pass state: decorate the descriptor after obtaining it from the target.
|
|
167
|
+
// Clear color precedence: the pass color, then the target's own default, then the built-in.
|
|
168
|
+
let descriptor = resolvedTarget.renderPassDescriptor({ clear: clear === undefined || clear === true || clear === false ? resolvedTarget.clearColor ?? BUILT_IN_CLEAR_COLOR : clear, preserve, clearDepth, clearStencil, depthReadOnly });
|
|
169
|
+
if (timer?.timestampWrites)
|
|
170
|
+
descriptor = { ...descriptor, timestampWrites: timer.timestampWrites };
|
|
171
|
+
// Mirrors the WebGPU pass descriptor rule: occlusionQuerySet must be a valid query set of type "occlusion".
|
|
172
|
+
if (occlusion)
|
|
173
|
+
descriptor = { ...descriptor, occlusionQuerySet: occlusion.querySet };
|
|
174
|
+
encoder = this.#encoder.beginRenderPass(descriptor);
|
|
175
|
+
if (viewport)
|
|
176
|
+
encoder.setViewport(viewport.x, viewport.y, viewport.width, viewport.height, viewport.minDepth, viewport.maxDepth);
|
|
177
|
+
if (scissor)
|
|
178
|
+
encoder.setScissorRect(scissor[0], scissor[1], scissor[2], scissor[3]);
|
|
179
|
+
this.#passActive = true;
|
|
180
|
+
try {
|
|
181
|
+
cb(new FramePass(encoder, resolvedTarget, this.#validations, depthReadOnly === true, occlusion, this, (where) => {
|
|
182
|
+
// Retained external devices can be destroyed mid-pass, so every FramePass entry point
|
|
183
|
+
// re-checks the device here instead of taking it as a separate constructor argument.
|
|
184
|
+
assertDeviceUsable(this.device, where);
|
|
185
|
+
if (this.#canceled)
|
|
186
|
+
throw frameCanceledError(where);
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
finally {
|
|
190
|
+
this.#passActive = false;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
// A failed pass — during setup or in its body — never ran successfully. Leaving telemetry
|
|
195
|
+
// registered makes submit() resolve unwritten queries as phantom results.
|
|
196
|
+
this.#discardOwners(attached);
|
|
197
|
+
discardClaimedGroupValidationResults(this.#validations);
|
|
198
|
+
this.#validations.length = 0;
|
|
199
|
+
discardClaimedGroupValidationScopes(this.device);
|
|
200
|
+
try {
|
|
201
|
+
encoder?.end();
|
|
202
|
+
}
|
|
203
|
+
catch { /* ignore cleanup failure after encode failure */ }
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
endRenderPassWithClaimValidation(this.device, encoder, this.#validations);
|
|
207
|
+
}
|
|
208
|
+
submit() {
|
|
209
|
+
// Closed either way: a re-submit has nothing left to flush, and a canceled frame dropped its
|
|
210
|
+
// encoder. Both are silent no-ops so `frame(gpu, cb)`'s submit-in-finally never masks a cancel()
|
|
211
|
+
// (or an exception) from inside the callback. Checked before the device so that submitting an
|
|
212
|
+
// already-closed frame stays a no-op even after `gpu.dispose()` took the device with it.
|
|
213
|
+
if (this.#submitted || this.#canceled)
|
|
214
|
+
return;
|
|
215
|
+
assertDeviceUsable(this.device, "Frame.submit");
|
|
216
|
+
this.#submitted = true;
|
|
217
|
+
this.releaseLifecycle?.();
|
|
218
|
+
// Timed and occlusion-queried frames append one resolveQuerySet of each instance's contiguous
|
|
219
|
+
// used range (plus the staging copy) to the still-open frame encoder — zero extra submissions.
|
|
220
|
+
for (const owner of this.#liveOwners())
|
|
221
|
+
owner.finalizeFrame(this, this.#encoder);
|
|
222
|
+
let commandBuffer;
|
|
223
|
+
const finishContext = this.#validations[0]?.context;
|
|
224
|
+
if (finishContext)
|
|
225
|
+
pushClaimedGroupValidationScope(this.device, finishContext);
|
|
226
|
+
try {
|
|
227
|
+
commandBuffer = this.#encoder.finish();
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
// finish() failed, so the resolves encoded above never reach the queue: nothing may be read
|
|
231
|
+
// back, but every instance still has to release the retain it took when it was attached.
|
|
232
|
+
this.#abandonOwners(this.#frameOwners());
|
|
233
|
+
const result = finishContext ? popLastClaimedGroupValidationScope(this.device) : undefined;
|
|
234
|
+
discardClaimedGroupValidationResults(this.#validations);
|
|
235
|
+
if (result)
|
|
236
|
+
discardClaimedGroupValidationResults([result]);
|
|
237
|
+
const context = result?.context ?? finishContext;
|
|
238
|
+
if (!context)
|
|
239
|
+
throw error;
|
|
240
|
+
this.done = this.#trackDone(this.#deliverValidationError(context.label, context.group, error));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (finishContext) {
|
|
244
|
+
const result = popLastClaimedGroupValidationScope(this.device);
|
|
245
|
+
if (result)
|
|
246
|
+
this.#validations[0] = this.#validations[0] ? preferClaimedGroupValidationResult(result, this.#validations[0]) : result;
|
|
247
|
+
}
|
|
248
|
+
const submitContext = this.#validations[0]?.context;
|
|
249
|
+
if (submitContext)
|
|
250
|
+
pushClaimedGroupValidationScope(this.device, submitContext);
|
|
251
|
+
try {
|
|
252
|
+
this.device.gpu.queue.submit([commandBuffer]);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
// Same as the finish() failure: the command buffer never ran, so release the retains and read
|
|
256
|
+
// nothing back — the resolve's staging bytes are stale, not this frame's results.
|
|
257
|
+
this.#abandonOwners(this.#frameOwners());
|
|
258
|
+
const result = submitContext ? popLastClaimedGroupValidationScope(this.device) : undefined;
|
|
259
|
+
discardClaimedGroupValidationResults(this.#validations);
|
|
260
|
+
if (result)
|
|
261
|
+
discardClaimedGroupValidationResults([result]);
|
|
262
|
+
const context = result?.context ?? submitContext;
|
|
263
|
+
if (!context)
|
|
264
|
+
throw error;
|
|
265
|
+
this.done = this.#trackDone(this.#deliverValidationError(context.label, context.group, error));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (submitContext) {
|
|
269
|
+
const result = popLastClaimedGroupValidationScope(this.device);
|
|
270
|
+
if (result)
|
|
271
|
+
this.#validations[0] = this.#validations[0] ? preferClaimedGroupValidationResult(result, this.#validations[0]) : result;
|
|
272
|
+
}
|
|
273
|
+
// The submit succeeded: start each owner's non-blocking readback of this frame's resolve.
|
|
274
|
+
for (const owner of this.#liveOwners())
|
|
275
|
+
owner.frameSubmitted(this);
|
|
276
|
+
// Instances a failed pass dropped are skipped above, so they still hold this frame's retain.
|
|
277
|
+
this.#abandonOwners(this.#discardedOwners);
|
|
278
|
+
this.done = this.#trackDone(claimedGroupValidationDone(this.device, this.#validations, { errorSink: this.errorSink }));
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Discards the frame without submitting it: the command encoder is dropped (nothing this frame
|
|
282
|
+
* encoded ever runs) and every telemetry instance it attached releases the retain it took on its
|
|
283
|
+
* query ring, so a `timer(gpu)` / `visibility(gpu)` can be disposed for good without waiting for
|
|
284
|
+
* `gpu.dispose()`. This is the explicit way out of the leak a manual `frame(gpu)` would otherwise
|
|
285
|
+
* hold: a frame is never assumed abandoned, because an old frame can still be submitted.
|
|
286
|
+
*
|
|
287
|
+
* Idempotent, like `submit()`: cancelling twice is a no-op, and `submit()` after `cancel()` does
|
|
288
|
+
* nothing. Cancelling a frame that was already submitted throws `AIGPU-FRAME-SUBMITTED` — its work
|
|
289
|
+
* is on the queue and cannot be taken back, so silently accepting the call would hide a real
|
|
290
|
+
* lifecycle bug.
|
|
291
|
+
*/
|
|
292
|
+
cancel() {
|
|
293
|
+
if (this.#canceled)
|
|
294
|
+
return;
|
|
295
|
+
if (this.#submitted)
|
|
296
|
+
throw frameAlreadySubmittedError("Frame.cancel");
|
|
297
|
+
// An active pass descriptor still references telemetry query sets. Releasing their retains here
|
|
298
|
+
// could destroy them before encoder.end(), and the callback could keep encoding after cancel.
|
|
299
|
+
if (this.#passActive)
|
|
300
|
+
throw framePassActiveError("Frame.cancel");
|
|
301
|
+
this.#canceled = true;
|
|
302
|
+
this.releaseLifecycle?.();
|
|
303
|
+
// Nothing is finalized and nothing is read back: the encoded passes never reach the queue, so
|
|
304
|
+
// decoding a resolve would report stale staging bytes as a phantom duration or "hidden".
|
|
305
|
+
this.#abandonOwners(this.#frameOwners());
|
|
306
|
+
this.#owners.clear();
|
|
307
|
+
this.#discardedOwners.clear();
|
|
308
|
+
// The claimed-group validation promises of the dropped encoder are never delivered: no draw of
|
|
309
|
+
// this frame ran, so any native error they carry is about work that was thrown away.
|
|
310
|
+
discardClaimedGroupValidationResults(this.#validations);
|
|
311
|
+
this.#validations.length = 0;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Ends the frame for telemetry instances that will never see a real frameSubmitted: a pass whose
|
|
315
|
+
* callback threw, a frame whose finish/submit failed, or a canceled frame. Each one took a retain
|
|
316
|
+
* on its query ring when it was attached to a pass descriptor (so a mid-frame dispose() cannot
|
|
317
|
+
* destroy a set the frame still points at); without the matching release, a dispose() after the
|
|
318
|
+
* failure leaves the ring alive forever. frameAbandoned() drops the instance's pending encoded
|
|
319
|
+
* state as it releases: a resolve that never reached the queue must not be decoded — its staging
|
|
320
|
+
* buffer holds stale bytes, which would surface as a phantom duration or a phantom "hidden".
|
|
321
|
+
*/
|
|
322
|
+
#abandonOwners(owners) {
|
|
323
|
+
for (const owner of [...owners])
|
|
324
|
+
owner.frameAbandoned(this);
|
|
325
|
+
}
|
|
326
|
+
/** Every owner this frame attached, discarded ones included. */
|
|
327
|
+
#frameOwners() {
|
|
328
|
+
return [...this.#owners, ...this.#discardedOwners];
|
|
329
|
+
}
|
|
330
|
+
/** Moves owners out of this frame's live set: they are neither finalized nor read back. */
|
|
331
|
+
#discardOwners(owners) {
|
|
332
|
+
for (const owner of [...owners]) {
|
|
333
|
+
this.#owners.delete(owner);
|
|
334
|
+
this.#discardedOwners.add(owner);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
#liveOwners() {
|
|
338
|
+
return [...this.#owners].filter((owner) => !this.#discardedOwners.has(owner));
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Attaches one `FramePassOptions` telemetry value to this pass through the nominal attachment
|
|
342
|
+
* protocol, so the frame never learns whether it is a timer span, a visibility or a future
|
|
343
|
+
* scene-view generation: it only records the owner it must settle exactly once.
|
|
344
|
+
*/
|
|
345
|
+
#attach(value, target, attached, invalid) {
|
|
346
|
+
const attachment = framePassAttachmentOf(value);
|
|
347
|
+
if (!attachment)
|
|
348
|
+
throw invalid(value);
|
|
349
|
+
let result;
|
|
350
|
+
try {
|
|
351
|
+
result = attachment[FRAME_PASS_ATTACHMENT]({ frame: this, device: this.device, target });
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
// A duplicate/capacity failure can land after the same instance already contributed an
|
|
355
|
+
// earlier pass to this frame, and a failed attach cannot report whose bookkeeping it left
|
|
356
|
+
// half-written. Drop every owner of the frame rather than let submit() report the earlier
|
|
357
|
+
// pass as a successful partial frame.
|
|
358
|
+
this.#discardOwners(this.#owners);
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
this.#owners.add(result.owner);
|
|
362
|
+
attached.push(result.owner);
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
365
|
+
async #deliverValidationError(label, group, cause) {
|
|
366
|
+
await submittedWorkDone(this.device);
|
|
367
|
+
assertDeviceUsable(this.device, "Frame.validation");
|
|
368
|
+
const error = claimedGroupNativeValidationError(label, group, cause);
|
|
369
|
+
if (this.errorSink)
|
|
370
|
+
await this.errorSink(error);
|
|
371
|
+
else
|
|
372
|
+
console.error(error);
|
|
373
|
+
}
|
|
374
|
+
#trackDone(promise) {
|
|
375
|
+
this.trackSettled?.(promise);
|
|
376
|
+
return promise;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
export class FramePass {
|
|
380
|
+
encoder;
|
|
381
|
+
target;
|
|
382
|
+
validations;
|
|
383
|
+
depthReadOnly;
|
|
384
|
+
occlusionSource;
|
|
385
|
+
frame;
|
|
386
|
+
assertFrameOpen;
|
|
387
|
+
#occlusionActive = false;
|
|
388
|
+
constructor(encoder, target, validations, depthReadOnly = false, occlusionSource, frame, assertFrameOpen) {
|
|
389
|
+
this.encoder = encoder;
|
|
390
|
+
this.target = target;
|
|
391
|
+
this.validations = validations;
|
|
392
|
+
this.depthReadOnly = depthReadOnly;
|
|
393
|
+
this.occlusionSource = occlusionSource;
|
|
394
|
+
this.frame = frame;
|
|
395
|
+
this.assertFrameOpen = assertFrameOpen;
|
|
396
|
+
}
|
|
397
|
+
draw(drawable, opts = {}) {
|
|
398
|
+
this.assertFrameOpen?.("FramePass.draw");
|
|
399
|
+
const encodable = frameDrawable(drawable);
|
|
400
|
+
if (this.depthReadOnly)
|
|
401
|
+
assertDrawableAllowedInReadOnlyPass(encodable, this.target);
|
|
402
|
+
encodable.encode(this.encoder, this.target, opts, (result) => this.validations.push(result));
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Wraps one or more draws in begin/endOcclusionQuery. The body ALWAYS executes; condition your
|
|
406
|
+
* real draws on `q.hidden` outside.
|
|
407
|
+
*/
|
|
408
|
+
occlusion(query, body) {
|
|
409
|
+
this.assertFrameOpen?.("FramePass.occlusion");
|
|
410
|
+
if (!this.occlusionSource)
|
|
411
|
+
throw queryNoVisibilityError();
|
|
412
|
+
// WebGPU beginOcclusionQuery: "no occlusion query must be active for this" — one scope at a time.
|
|
413
|
+
if (this.#occlusionActive)
|
|
414
|
+
throw queryNestedError();
|
|
415
|
+
const index = this.occlusionSource.beginQuery(query, this.frame);
|
|
416
|
+
this.encoder.beginOcclusionQuery(index);
|
|
417
|
+
this.#occlusionActive = true;
|
|
418
|
+
try {
|
|
419
|
+
if (typeof body === "function")
|
|
420
|
+
body();
|
|
421
|
+
else
|
|
422
|
+
this.draw(body);
|
|
423
|
+
}
|
|
424
|
+
finally {
|
|
425
|
+
// aigpu's scope shape makes an unclosed query structurally impossible: end in finally, so a
|
|
426
|
+
// throwing body can never leave a query open at pass end (which would invalidate the encoder).
|
|
427
|
+
this.#occlusionActive = false;
|
|
428
|
+
this.encoder.endOcclusionQuery();
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
bundles(...bundles) {
|
|
432
|
+
this.assertFrameOpen?.("FramePass.bundles");
|
|
433
|
+
// WebGPU executeBundles: "If this.[[depthReadOnly]] is true, bundle.[[depthReadOnly]] must be true.
|
|
434
|
+
// If this.[[stencilReadOnly]] is true, bundle.[[stencilReadOnly]] must be true." bundle always
|
|
435
|
+
// records bundles with both flags false, so no recorded bundle can replay into a read-only pass;
|
|
436
|
+
// reject up front instead of leaving it to native validation.
|
|
437
|
+
if (this.depthReadOnly)
|
|
438
|
+
throw passDepthReadOnlyError("pass cannot replay bundles: bundle records bundles with writable depth/stencil, and WebGPU only executes read-only-recorded bundles in a read-only pass.", "Encode the draws directly with pass.draw(...) inside the depthReadOnly pass.", "FramePass.bundles");
|
|
439
|
+
const recorded = bundles.map((entry) => frameBundleOf(entry) ?? invalidBundle());
|
|
440
|
+
// Staleness is checked for every bundle before the first one replays: a pass either replays the
|
|
441
|
+
// whole list or none of it.
|
|
442
|
+
for (const entry of recorded)
|
|
443
|
+
entry.assertReplayable(this.target);
|
|
444
|
+
this.encoder.executeBundles(recorded.map((entry) => entry.gpu));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Early equivalent of the WebGPU setPipeline device-timeline checks: "If pipeline.[[writesDepth]]:
|
|
449
|
+
* this.[[depthReadOnly]] must be false. If pipeline.[[writesStencil]]: this.[[stencilReadOnly]] must be
|
|
450
|
+
* false." A depthReadOnly pass marks the stencil aspect read-only too (combined formats), so both are
|
|
451
|
+
* validated here with actionable errors before encoding.
|
|
452
|
+
*/
|
|
453
|
+
function assertDrawableAllowedInReadOnlyPass(drawable, target) {
|
|
454
|
+
if (drawable.writesDepth()) {
|
|
455
|
+
throw passDepthReadOnlyError(`pass cannot encode draw '${drawable.label}': its depth state writes depth (the default is write: true). Give the draw depth: { write: false } (or depth: false to disable depth testing).`, "Use depth: { write: false } on the draw, or open the pass without depthReadOnly.", "FramePass.draw");
|
|
456
|
+
}
|
|
457
|
+
if (hasStencilAspect(target.depth?.format)) {
|
|
458
|
+
const ops = drawable.stencilWritingOps();
|
|
459
|
+
if (ops.length) {
|
|
460
|
+
throw passDepthReadOnlyError(`pass cannot encode draw '${drawable.label}': its stencil ops can write (${ops.join(", ")}), and the pass's stencil aspect is read-only too.`, `Use "keep" for those ops or stencil writeMask: 0, or open the pass without depthReadOnly.`, "FramePass.draw");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/** Nominal drawable protocol of a `Draw` or an `Effect`; the frame never imports either module. */
|
|
465
|
+
function frameDrawable(drawable) {
|
|
466
|
+
const encodable = frameDrawableOf(drawable);
|
|
467
|
+
// Historical message kept: "Invalid Effect instance" is what an unrecognized drawable reported
|
|
468
|
+
// when the frame resolved it by shape.
|
|
469
|
+
if (!encodable)
|
|
470
|
+
throw new TypeError("Invalid Effect instance: pass.draw() expects a Draw or an Effect created by this library.");
|
|
471
|
+
return encodable;
|
|
472
|
+
}
|
|
473
|
+
function invalidBundle() {
|
|
474
|
+
throw new AIGpuError({ code: "AIGPU-R3-BUNDLE-INVALID", message: "p.bundles() expected bundles created by bundle(gpu, { target }, cb).", where: "FramePass.bundles" });
|
|
475
|
+
}
|
|
476
|
+
function timerAttachmentInvalidError(value) {
|
|
477
|
+
return timerInvalidError(`FramePassOptions.timer received ${previewValue(value)}; expected a TimerSpan from timer.span(name).`, `Create const passTimer = timer(gpu) once, then pass passTimer.span("name") per pass.`, "Frame.pass");
|
|
478
|
+
}
|
|
479
|
+
function visibilityAttachmentInvalidError(value) {
|
|
480
|
+
return visibilityInvalidError(`FramePassOptions.visibility received ${previewValue(value)}; expected a Visibility from visibility(gpu).`, "Create const vis = visibility(gpu) once, then pass { target, visibility: vis } per pass.", "Frame.pass");
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Mirrors the WebGPU setViewport device-timeline validation (arguments are floats;
|
|
484
|
+
* bounds check against device limits, not the attachment): with maxViewportRange =
|
|
485
|
+
* maxTextureDimension2D × 2, requires x ≥ -maxViewportRange, y ≥ -maxViewportRange,
|
|
486
|
+
* 0 ≤ width/height ≤ maxTextureDimension2D, x + width ≤ maxViewportRange − 1,
|
|
487
|
+
* y + height ≤ maxViewportRange − 1, 0 ≤ minDepth ≤ 1, 0 ≤ maxDepth ≤ 1, and
|
|
488
|
+
* minDepth ≤ maxDepth.
|
|
489
|
+
*/
|
|
490
|
+
function validatedViewport(viewport, limits, targetSize) {
|
|
491
|
+
if (viewport === undefined)
|
|
492
|
+
return undefined;
|
|
493
|
+
if (typeof viewport !== "object" || viewport === null || Array.isArray(viewport))
|
|
494
|
+
throw passViewportInvalidError(`received ${previewValue(viewport)}; expected { x?, y?, width, height, minDepth?, maxDepth? }.`);
|
|
495
|
+
const { x = 0, y = 0, width, height, minDepth = 0, maxDepth = 1 } = viewport;
|
|
496
|
+
for (const [name, value] of [["x", x], ["y", y], ["width", width], ["height", height], ["minDepth", minDepth], ["maxDepth", maxDepth]]) {
|
|
497
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
498
|
+
throw passViewportInvalidError(`${name} received ${previewValue(value)}; expected a finite number.`);
|
|
499
|
+
}
|
|
500
|
+
const max = limits.maxTextureDimension2D;
|
|
501
|
+
const maxViewportRange = max * 2;
|
|
502
|
+
const sizeNote = `target is ${targetSize[0]}x${targetSize[1]}px, device maxTextureDimension2D is ${max}`;
|
|
503
|
+
if (!(width >= 0 && width <= max))
|
|
504
|
+
throw passViewportInvalidError(`width ${width} is outside [0, ${max}] (${sizeNote}).`);
|
|
505
|
+
if (!(height >= 0 && height <= max))
|
|
506
|
+
throw passViewportInvalidError(`height ${height} is outside [0, ${max}] (${sizeNote}).`);
|
|
507
|
+
if (!(x >= -maxViewportRange && x + width <= maxViewportRange - 1))
|
|
508
|
+
throw passViewportInvalidError(`x ${x} with width ${width} is outside [${-maxViewportRange}, ${maxViewportRange - 1}] (${sizeNote}).`);
|
|
509
|
+
if (!(y >= -maxViewportRange && y + height <= maxViewportRange - 1))
|
|
510
|
+
throw passViewportInvalidError(`y ${y} with height ${height} is outside [${-maxViewportRange}, ${maxViewportRange - 1}] (${sizeNote}).`);
|
|
511
|
+
if (!(minDepth >= 0 && minDepth <= 1))
|
|
512
|
+
throw passViewportInvalidError(`minDepth ${minDepth} is outside [0, 1].`);
|
|
513
|
+
if (!(maxDepth >= 0 && maxDepth <= 1))
|
|
514
|
+
throw passViewportInvalidError(`maxDepth ${maxDepth} is outside [0, 1].`);
|
|
515
|
+
if (!(minDepth <= maxDepth))
|
|
516
|
+
throw passViewportInvalidError(`minDepth ${minDepth} exceeds maxDepth ${maxDepth}.`);
|
|
517
|
+
return { x, y, width, height, minDepth, maxDepth };
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Mirrors the WebGPU setScissorRect validation: arguments are GPUIntegerCoordinate
|
|
521
|
+
* (non-negative integers), and the rectangle must satisfy x + width ≤ attachment
|
|
522
|
+
* width and y + height ≤ attachment height against the target's current size.
|
|
523
|
+
*/
|
|
524
|
+
function validatedScissor(scissor, targetSize) {
|
|
525
|
+
if (scissor === undefined)
|
|
526
|
+
return undefined;
|
|
527
|
+
if (!Array.isArray(scissor) || scissor.length !== 4)
|
|
528
|
+
throw passScissorInvalidError(`received ${previewValue(scissor)}; expected [x, y, width, height].`);
|
|
529
|
+
const [x, y, width, height] = scissor;
|
|
530
|
+
for (const [name, value] of [["x", x], ["y", y], ["width", width], ["height", height]]) {
|
|
531
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 0)
|
|
532
|
+
throw passScissorInvalidError(`${name} received ${previewValue(value)}; expected a non-negative integer.`);
|
|
533
|
+
}
|
|
534
|
+
const [targetWidth, targetHeight] = targetSize;
|
|
535
|
+
if (x + width > targetWidth || y + height > targetHeight) {
|
|
536
|
+
throw passScissorInvalidError(`[${x}, ${y}, ${width}, ${height}] exceeds the target's current size ${targetWidth}x${targetHeight}px (x + width <= ${targetWidth}, y + height <= ${targetHeight}).`);
|
|
537
|
+
}
|
|
538
|
+
return [x, y, width, height];
|
|
539
|
+
}
|
|
540
|
+
function previewValue(value) {
|
|
541
|
+
if (typeof value === "string")
|
|
542
|
+
return `'${value}'`;
|
|
543
|
+
if (Array.isArray(value))
|
|
544
|
+
return `[${value.map((entry) => previewValue(entry)).join(", ")}]`;
|
|
545
|
+
if (typeof value === "object" && value !== null)
|
|
546
|
+
return "an object";
|
|
547
|
+
return String(value);
|
|
548
|
+
}
|
|
549
|
+
/** True for the "the device this frame belongs to is gone" errors raised by the liveness guards. */
|
|
550
|
+
function isDeviceGoneError(error) {
|
|
551
|
+
const code = error?.code;
|
|
552
|
+
return code === "AIGPU-DEVICE-DISPOSED" || code === "AIGPU-DEVICE-LOST";
|
|
553
|
+
}
|
|
554
|
+
export class FrameRunner {
|
|
555
|
+
createFrame;
|
|
556
|
+
advance;
|
|
557
|
+
trackLoop;
|
|
558
|
+
#running = false;
|
|
559
|
+
/**
|
|
560
|
+
* @param trackLoop Lifecycle hook for the owning gpu: called with each started loop handle and
|
|
561
|
+
* returns the untrack function the handle runs when it stops on its own, so `gpu.dispose()` can
|
|
562
|
+
* stop the loops still running without holding on to the ones already stopped.
|
|
563
|
+
*/
|
|
564
|
+
constructor(createFrame, advance, trackLoop) {
|
|
565
|
+
this.createFrame = createFrame;
|
|
566
|
+
this.advance = advance;
|
|
567
|
+
this.trackLoop = trackLoop;
|
|
568
|
+
}
|
|
569
|
+
frame(cb) {
|
|
570
|
+
if (this.#running || isSurfaceResizeCallbackActive())
|
|
571
|
+
throw frameReentrantError();
|
|
572
|
+
this.#running = true;
|
|
573
|
+
enterFrame();
|
|
574
|
+
try {
|
|
575
|
+
this.advance();
|
|
576
|
+
const frame = this.createFrame();
|
|
577
|
+
if (cb) {
|
|
578
|
+
try {
|
|
579
|
+
cb(frame);
|
|
580
|
+
}
|
|
581
|
+
finally {
|
|
582
|
+
// A callback is allowed to dispose the owning gpu (gpu.dispose() inside a loop tick does
|
|
583
|
+
// exactly that). The device is then gone and the frame has nothing left to flush, so this
|
|
584
|
+
// implicit submit swallows that one error instead of throwing over the callback's own
|
|
585
|
+
// intent — the same reason a canceled frame submits as a no-op. An explicit
|
|
586
|
+
// frame.submit() on a dead device still reports it.
|
|
587
|
+
try {
|
|
588
|
+
frame.submit();
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
if (!isDeviceGoneError(error))
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return frame;
|
|
597
|
+
}
|
|
598
|
+
finally {
|
|
599
|
+
leaveFrame();
|
|
600
|
+
this.#running = false;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
loop(cb, opts = {}) {
|
|
604
|
+
let stopped = false;
|
|
605
|
+
const request = globalThis.requestAnimationFrame ?? ((fn) => setTimeout(() => fn(performance.now()), 16));
|
|
606
|
+
const cancel = globalThis.cancelAnimationFrame ?? ((id) => clearTimeout(id));
|
|
607
|
+
const minIntervalMs = opts.fps && opts.fps > 0 ? 1000 / opts.fps : 0;
|
|
608
|
+
let lastFrameMs;
|
|
609
|
+
let id = 0;
|
|
610
|
+
const tick = (timestamp) => {
|
|
611
|
+
if (stopped)
|
|
612
|
+
return;
|
|
613
|
+
if (shouldRunFrame(timestamp, lastFrameMs, minIntervalMs)) {
|
|
614
|
+
lastFrameMs = timestamp;
|
|
615
|
+
this.frame(cb);
|
|
616
|
+
}
|
|
617
|
+
// The callback may dispose the owning gpu, which stops this loop while the current tick is
|
|
618
|
+
// running. Do not enqueue one last (no-op) tick after stop() already canceled the old id.
|
|
619
|
+
if (!stopped)
|
|
620
|
+
id = request(tick);
|
|
621
|
+
};
|
|
622
|
+
id = request(tick);
|
|
623
|
+
// Registered with the owning gpu so gpu.dispose() stops it: a loop left running would keep
|
|
624
|
+
// encoding frames against a disposed device. Stopping is idempotent and drops the registration.
|
|
625
|
+
let untrack;
|
|
626
|
+
const handle = {
|
|
627
|
+
stop() {
|
|
628
|
+
stopped = true;
|
|
629
|
+
cancel(id);
|
|
630
|
+
untrack?.();
|
|
631
|
+
untrack = undefined;
|
|
632
|
+
},
|
|
633
|
+
};
|
|
634
|
+
untrack = this.trackLoop?.(handle);
|
|
635
|
+
return handle;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
function shouldRunFrame(timestamp, lastFrameMs, minIntervalMs) {
|
|
639
|
+
if (lastFrameMs === undefined)
|
|
640
|
+
return true;
|
|
641
|
+
if (minIntervalMs <= 0)
|
|
642
|
+
return true;
|
|
643
|
+
return timestamp - lastFrameMs >= minIntervalMs;
|
|
644
|
+
}
|
|
645
|
+
//# sourceMappingURL=frame.js.map
|