@downdraft/engine 0.0.0 → 0.1.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/README.md +33 -0
- package/app/src/index.ts +10 -0
- package/app/src/renderer/bootstrap.ts +271 -0
- package/app/src/renderer/downdraft-base.css +93 -0
- package/app/src/renderer/feature-log.ts +200 -0
- package/app/src/renderer/game-module.ts +923 -0
- package/app/src/renderer/hooks.ts +88 -0
- package/app/src/renderer/hot-reload.ts +178 -0
- package/app/src/renderer/import-cache.ts +88 -0
- package/app/src/renderer/index.ts +206 -0
- package/app/src/renderer/mcp-harness.ts +144 -0
- package/app/src/renderer/save-store-factory.ts +239 -0
- package/app/src/renderer/sim-bridge.ts +183 -0
- package/app/src/renderer/standard-automation-tools.ts +771 -0
- package/app/src/shared/feature-log.ts +71 -0
- package/app/src/shared/gpu-info.ts +82 -0
- package/app/src/shared/import-cache-store.ts +142 -0
- package/app/src/shared/index.ts +6 -0
- package/app/src/shared/mcp-discovery.ts +47 -0
- package/app/src/shared/mcp-proxy.ts +82 -0
- package/app/src/shared/types.ts +324 -0
- package/asset-bake/src/bake-audio.ts +119 -0
- package/asset-bake/src/bake-gltf.ts +172 -0
- package/asset-bake/src/bake.ts +78 -0
- package/asset-bake/src/cache.ts +188 -0
- package/asset-bake/src/config.ts +186 -0
- package/asset-bake/src/index.ts +36 -0
- package/core/src/animation/animation-event.ts +41 -0
- package/core/src/animation/bone-mask.ts +122 -0
- package/core/src/animation/clip.ts +252 -0
- package/core/src/animation/index.ts +22 -0
- package/core/src/animation/mixamo.ts +113 -0
- package/core/src/animation/morph-target.ts +69 -0
- package/core/src/animation/player.ts +697 -0
- package/core/src/animation/retarget.ts +158 -0
- package/core/src/animation/skeleton-animator.ts +1399 -0
- package/core/src/animation/skeleton.ts +214 -0
- package/core/src/animation/state-machine.ts +280 -0
- package/core/src/assets/blob-store.ts +66 -0
- package/core/src/assets/cache.ts +186 -0
- package/core/src/assets/cubemap-converter.ts +155 -0
- package/core/src/assets/environment-manager.ts +121 -0
- package/core/src/assets/import-cache.ts +65 -0
- package/core/src/assets/import-settings.ts +72 -0
- package/core/src/assets/importer.ts +108 -0
- package/core/src/assets/index.ts +74 -0
- package/core/src/assets/irradiance-generator.ts +198 -0
- package/core/src/assets/loader-audio.ts +86 -0
- package/core/src/assets/loader-cubemap.ts +211 -0
- package/core/src/assets/loader-dds.ts +236 -0
- package/core/src/assets/loader-hdr.ts +474 -0
- package/core/src/assets/loader-mesh.ts +143 -0
- package/core/src/assets/loader-shader.ts +73 -0
- package/core/src/assets/loader-texture.ts +349 -0
- package/core/src/assets/lod.ts +198 -0
- package/core/src/assets/manager.ts +480 -0
- package/core/src/assets/manifest.ts +81 -0
- package/core/src/assets/material-bridge.ts +186 -0
- package/core/src/assets/model-importer.ts +72 -0
- package/core/src/assets/model-normalizer.ts +248 -0
- package/core/src/assets/model-to-gpu.ts +70 -0
- package/core/src/assets/model-to-mesh.ts +237 -0
- package/core/src/assets/prefilter-generator.ts +250 -0
- package/core/src/audio/engine.ts +177 -0
- package/core/src/audio/interface.ts +130 -0
- package/core/src/audio/listener.ts +32 -0
- package/core/src/audio/mixer.ts +141 -0
- package/core/src/audio/registry.ts +52 -0
- package/core/src/audio/sab.ts +87 -0
- package/core/src/audio/source.ts +89 -0
- package/core/src/change-detection/atomics.ts +1 -0
- package/core/src/change-detection/tracker.ts +47 -0
- package/core/src/debug-draw/lines.ts +64 -0
- package/core/src/debug-draw/points.ts +17 -0
- package/core/src/debug-draw/queue.ts +124 -0
- package/core/src/debug-draw/text.ts +25 -0
- package/core/src/ecs/archetype.ts +323 -0
- package/core/src/ecs/component.ts +188 -0
- package/core/src/ecs/entity.ts +25 -0
- package/core/src/ecs/events.ts +93 -0
- package/core/src/ecs/hierarchy.ts +124 -0
- package/core/src/ecs/hmr-swap-registry.ts +22 -0
- package/core/src/ecs/index.ts +25 -0
- package/core/src/ecs/query.ts +141 -0
- package/core/src/ecs/resource.ts +8 -0
- package/core/src/ecs/schedule.ts +181 -0
- package/core/src/ecs/system-params.ts +99 -0
- package/core/src/ecs/system.ts +42 -0
- package/core/src/ecs/world.ts +348 -0
- package/core/src/index.ts +352 -0
- package/core/src/input/context.ts +73 -0
- package/core/src/input/dom-handler.ts +380 -0
- package/core/src/input/local-player-manager.ts +218 -0
- package/core/src/input/mapping.ts +40 -0
- package/core/src/input/multi-sab-bridge.ts +276 -0
- package/core/src/input/multi-state.ts +54 -0
- package/core/src/input/sab-bridge.ts +71 -0
- package/core/src/input/state.ts +132 -0
- package/core/src/input/ui-router.ts +87 -0
- package/core/src/library/host.ts +317 -0
- package/core/src/library/index.ts +16 -0
- package/core/src/library/library.ts +292 -0
- package/core/src/material/compiler.ts +80 -0
- package/core/src/material/graph-bridge.ts +172 -0
- package/core/src/material/index.ts +9 -0
- package/core/src/material/library.ts +442 -0
- package/core/src/material/material.ts +156 -0
- package/core/src/material/variants.ts +61 -0
- package/core/src/math/mat4.ts +202 -0
- package/core/src/math/perlin-noise-2d.ts +77 -0
- package/core/src/math/perlin-noise-3d.ts +110 -0
- package/core/src/math/quat.ts +33 -0
- package/core/src/math/rng.ts +29 -0
- package/core/src/math/trig.ts +47 -0
- package/core/src/math/value-noise.ts +67 -0
- package/core/src/mesh/builder.ts +177 -0
- package/core/src/mesh/csg.ts +312 -0
- package/core/src/mesh/greased-line.ts +145 -0
- package/core/src/mesh/mesh.ts +17 -0
- package/core/src/mesh/parametric.ts +501 -0
- package/core/src/mesh/skinning.ts +67 -0
- package/core/src/mesh/vertex-layout.ts +91 -0
- package/core/src/module/cross-thread.ts +163 -0
- package/core/src/module/diagnostics.ts +123 -0
- package/core/src/module/host.ts +451 -0
- package/core/src/module/index.ts +22 -0
- package/core/src/module/module.ts +109 -0
- package/core/src/module/registry.ts +94 -0
- package/core/src/module/renderer-module.ts +247 -0
- package/core/src/module/ts-loader.ts +26 -0
- package/core/src/particles/compute-pass.ts +378 -0
- package/core/src/particles/emitter.ts +156 -0
- package/core/src/particles/index.ts +11 -0
- package/core/src/particles/particle-data.ts +47 -0
- package/core/src/particles/render-pass.ts +365 -0
- package/core/src/particles/simulator.ts +264 -0
- package/core/src/particles/system.ts +193 -0
- package/core/src/physics/body.ts +59 -0
- package/core/src/physics/ccd-heuristic.ts +55 -0
- package/core/src/physics/character.ts +282 -0
- package/core/src/physics/collider.ts +55 -0
- package/core/src/physics/collision-system.ts +91 -0
- package/core/src/physics/events.ts +133 -0
- package/core/src/physics/index.ts +34 -0
- package/core/src/physics/interface.ts +473 -0
- package/core/src/physics/interpolation-buffer.ts +192 -0
- package/core/src/physics/lifecycle.ts +87 -0
- package/core/src/physics/load-shedder.ts +164 -0
- package/core/src/physics/physics-accumulator.ts +101 -0
- package/core/src/physics/physics-system.ts +240 -0
- package/core/src/physics/ragdoll-presets.ts +272 -0
- package/core/src/physics/ragdoll-system.ts +220 -0
- package/core/src/physics/ragdoll.ts +182 -0
- package/core/src/physics/raycast.ts +143 -0
- package/core/src/physics/realm-manager.ts +499 -0
- package/core/src/physics/realm-worker-pool.ts +191 -0
- package/core/src/physics/realm-worker.ts +102 -0
- package/core/src/physics/realm.ts +189 -0
- package/core/src/physics/registry.ts +52 -0
- package/core/src/physics/safety.ts +247 -0
- package/core/src/physics/snapshot-manager.ts +141 -0
- package/core/src/physics/worker-protocol.ts +30 -0
- package/core/src/platform/bun-preload.ts +169 -0
- package/core/src/platform/css-stub.ts +4 -0
- package/core/src/platform/glob-polyfill.ts +124 -0
- package/core/src/platform/hidpi.ts +57 -0
- package/core/src/platform/render-surface.ts +77 -0
- package/core/src/platform/rpc.ts +98 -0
- package/core/src/platform/runtime.ts +132 -0
- package/core/src/plugin/context.ts +364 -0
- package/core/src/plugin/diagnostics.ts +61 -0
- package/core/src/plugin/discovery.ts +186 -0
- package/core/src/plugin/extension-loaders.ts +172 -0
- package/core/src/plugin/host.ts +659 -0
- package/core/src/plugin/index.ts +106 -0
- package/core/src/plugin/loader-asset.ts +124 -0
- package/core/src/plugin/loader-quickjs.ts +84 -0
- package/core/src/plugin/loader-wasm.ts +411 -0
- package/core/src/plugin/loader-worker.ts +261 -0
- package/core/src/plugin/manifest.ts +653 -0
- package/core/src/plugin/material-registry.ts +65 -0
- package/core/src/plugin/mcp-tools.ts +165 -0
- package/core/src/plugin/permissions.ts +175 -0
- package/core/src/plugin/quickjs-bridge.ts +294 -0
- package/core/src/plugin/registry.ts +107 -0
- package/core/src/plugin/sandbox-shim.ts +86 -0
- package/core/src/plugin/sandbox-worker.ts +215 -0
- package/core/src/plugin/wasm-abi.ts +152 -0
- package/core/src/plugin/wasm-worker.ts +229 -0
- package/core/src/plugin/workshop.ts +169 -0
- package/core/src/profiling/event-loop.ts +180 -0
- package/core/src/profiling/index.ts +69 -0
- package/core/src/profiling/iops/idb-patch.ts +242 -0
- package/core/src/profiling/iops/opfs-patch.ts +276 -0
- package/core/src/profiling/profiling-sab.ts +839 -0
- package/core/src/profiling/task-latency.ts +134 -0
- package/core/src/profiling/thread-metrics.ts +126 -0
- package/core/src/profiling/trace-event-writer.ts +205 -0
- package/core/src/profiling/warnings.ts +423 -0
- package/core/src/profiling/worker-prelude.ts +376 -0
- package/core/src/render/bind-group.ts +38 -0
- package/core/src/render/bindless/bindless-struct.ts +42 -0
- package/core/src/render/bindless/bindless.wgsl.ts +167 -0
- package/core/src/render/bindless/frame-bindings.ts +176 -0
- package/core/src/render/bindless/index.ts +10 -0
- package/core/src/render/bindless/material-manager.ts +257 -0
- package/core/src/render/bindless/texture-registry.ts +609 -0
- package/core/src/render/buffer.ts +82 -0
- package/core/src/render/camera-2d.ts +217 -0
- package/core/src/render/camera-controller.ts +440 -0
- package/core/src/render/camera.ts +409 -0
- package/core/src/render/canvas-resize-watcher.ts +46 -0
- package/core/src/render/compute-kernel.ts +172 -0
- package/core/src/render/constants.ts +2 -0
- package/core/src/render/device.ts +111 -0
- package/core/src/render/frame-graph.ts +768 -0
- package/core/src/render/frustum.ts +185 -0
- package/core/src/render/fullscreen-vs.ts +25 -0
- package/core/src/render/g-buffer.ts +186 -0
- package/core/src/render/game-renderer.ts +1452 -0
- package/core/src/render/gpu-driven/cull-pass.ts +394 -0
- package/core/src/render/gpu-driven/hzb.ts +215 -0
- package/core/src/render/gpu-driven/index.ts +7 -0
- package/core/src/render/gpu-driven/indirect-draw-pass.ts +324 -0
- package/core/src/render/gpu-driven/instance-buffer.ts +178 -0
- package/core/src/render/gpu-driven/mesh-table.ts +175 -0
- package/core/src/render/gpu-driven/passes/hzb-build-pass.ts +47 -0
- package/core/src/render/gpu-utils.ts +96 -0
- package/core/src/render/hdr.ts +88 -0
- package/core/src/render/hot-reload.ts +295 -0
- package/core/src/render/ibl-bind-group.ts +266 -0
- package/core/src/render/ibl.ts +180 -0
- package/core/src/render/index.ts +163 -0
- package/core/src/render/input-manager.ts +259 -0
- package/core/src/render/lighting/area-light.ts +196 -0
- package/core/src/render/lighting/cluster-grid.ts +288 -0
- package/core/src/render/lighting/cluster-types.ts +193 -0
- package/core/src/render/lighting/ies-parser.ts +162 -0
- package/core/src/render/lighting.ts +454 -0
- package/core/src/render/material-types/depth.wgsl +44 -0
- package/core/src/render/material-types/line.wgsl +50 -0
- package/core/src/render/material-types/matcap.wgsl +51 -0
- package/core/src/render/material-types/normal.wgsl +41 -0
- package/core/src/render/material-types/physical.wgsl +152 -0
- package/core/src/render/material-types/sprite.wgsl +46 -0
- package/core/src/render/material-types/sss.wgsl +79 -0
- package/core/src/render/material-types/toon.wgsl +79 -0
- package/core/src/render/passes/README.md +121 -0
- package/core/src/render/passes/atmosphere.ts +152 -0
- package/core/src/render/passes/cluster-lighting-pass.ts +476 -0
- package/core/src/render/passes/csm.ts +408 -0
- package/core/src/render/passes/cubemap-capture.ts +114 -0
- package/core/src/render/passes/debug-viz.ts +278 -0
- package/core/src/render/passes/debug.ts +538 -0
- package/core/src/render/passes/decal-mesh.ts +111 -0
- package/core/src/render/passes/decal-pass.ts +377 -0
- package/core/src/render/passes/deferred-lighting.ts +434 -0
- package/core/src/render/passes/depth-prepass.ts +163 -0
- package/core/src/render/passes/dome-360.ts +83 -0
- package/core/src/render/passes/fluid-render.ts +137 -0
- package/core/src/render/passes/gi-types.ts +118 -0
- package/core/src/render/passes/graph-compute.ts +300 -0
- package/core/src/render/passes/greased-line-pass.ts +232 -0
- package/core/src/render/passes/morph-target.ts +40 -0
- package/core/src/render/passes/opaque.ts +1028 -0
- package/core/src/render/passes/point-light-shadow.ts +266 -0
- package/core/src/render/passes/procedural-texture.ts +185 -0
- package/core/src/render/passes/reflection-probe.ts +140 -0
- package/core/src/render/passes/rsm-pass.ts +248 -0
- package/core/src/render/passes/sdf-text.ts +155 -0
- package/core/src/render/passes/shadow-map.ts +163 -0
- package/core/src/render/passes/shadow.ts +220 -0
- package/core/src/render/passes/skinning-vs.ts +121 -0
- package/core/src/render/passes/skinning.ts +326 -0
- package/core/src/render/passes/sky-dome.ts +404 -0
- package/core/src/render/passes/skybox.ts +162 -0
- package/core/src/render/passes/spot-light-shadow.ts +273 -0
- package/core/src/render/passes/terrain.ts +326 -0
- package/core/src/render/passes/transparent.ts +353 -0
- package/core/src/render/passes/underwater-fog.ts +185 -0
- package/core/src/render/passes/video-texture.ts +79 -0
- package/core/src/render/passes/volumetric-pass.ts +312 -0
- package/core/src/render/passes/volumetric-types.ts +171 -0
- package/core/src/render/passes/water.ts +753 -0
- package/core/src/render/pbr.ts +296 -0
- package/core/src/render/pipeline.ts +52 -0
- package/core/src/render/render-pass.ts +30 -0
- package/core/src/render/renderer-input-bus.ts +220 -0
- package/core/src/render/renderer-module-host.ts +623 -0
- package/core/src/render/resource-tracker.ts +67 -0
- package/core/src/render/sab-canvas-overlay.ts +122 -0
- package/core/src/render/shader-validator.ts +200 -0
- package/core/src/render/shaders/debug/debug-line.wgsl +27 -0
- package/core/src/render/shaders/debug/debug-point.wgsl +30 -0
- package/core/src/render/shaders/debug/debug-text.wgsl +42 -0
- package/core/src/render/shaders/debug-viz/aabb.wgsl +20 -0
- package/core/src/render/shaders/debug-viz/depth.wgsl +29 -0
- package/core/src/render/shaders/debug-viz/lod.wgsl +38 -0
- package/core/src/render/shaders/debug-viz/normals.wgsl +30 -0
- package/core/src/render/shaders/debug-viz/overdraw.wgsl +21 -0
- package/core/src/render/shaders/debug-viz/tangents.wgsl +29 -0
- package/core/src/render/shaders/debug-viz/wireframe.wgsl +33 -0
- package/core/src/render/shaders/shadow-chunks.ts +135 -0
- package/core/src/render/skybox.ts +146 -0
- package/core/src/render/splitscreen.ts +85 -0
- package/core/src/render/surface.ts +78 -0
- package/core/src/render/tonemap.ts +180 -0
- package/core/src/render/tracked-render-pass.ts +158 -0
- package/core/src/render/viewport.ts +109 -0
- package/core/src/render/wgsl-hmr.ts +70 -0
- package/core/src/sab/core-input-channel.ts +23 -0
- package/core/src/sab/define.ts +239 -0
- package/core/src/sab/errors.ts +76 -0
- package/core/src/sab/game-input.ts +343 -0
- package/core/src/sab/gamepad-devices.ts +259 -0
- package/core/src/sab/grid-buffer.ts +117 -0
- package/core/src/sab/input.ts +55 -0
- package/core/src/sab/layout.ts +219 -0
- package/core/src/sab/multi-input-channel.ts +50 -0
- package/core/src/sab/record-buffer.ts +155 -0
- package/core/src/sab/sab-polyfill.ts +56 -0
- package/core/src/sab/sim-channel.ts +453 -0
- package/core/src/sab/slot-buffer.ts +190 -0
- package/core/src/sab/types.ts +158 -0
- package/core/src/safety/bounds.ts +76 -0
- package/core/src/safety/json.ts +80 -0
- package/core/src/safety/path.ts +167 -0
- package/core/src/save/binary-format.ts +117 -0
- package/core/src/save/game-save-system.ts +161 -0
- package/core/src/save/grid-save-system.ts +158 -0
- package/core/src/save/migrate.ts +44 -0
- package/core/src/save/migration-registry.ts +94 -0
- package/core/src/save/persist-types.ts +208 -0
- package/core/src/save/schema.ts +46 -0
- package/core/src/save/serializer.ts +180 -0
- package/core/src/scene/builder.ts +65 -0
- package/core/src/scene/camera.ts +142 -0
- package/core/src/scene/checkpoint.ts +109 -0
- package/core/src/scene/game-loop.ts +198 -0
- package/core/src/scene/index.ts +24 -0
- package/core/src/scene/layer.ts +6 -0
- package/core/src/scene/prefab.ts +145 -0
- package/core/src/scene/scene-manager.ts +157 -0
- package/core/src/scene/scene.ts +192 -0
- package/core/src/scene/sky.ts +256 -0
- package/core/src/scene/spatial-grid.ts +176 -0
- package/core/src/scene/streaming.ts +304 -0
- package/core/src/scene/vegetation.ts +163 -0
- package/core/src/scene/world.ts +89 -0
- package/core/src/scripting/binding.ts +129 -0
- package/core/src/scripting/hot-reload.ts +144 -0
- package/core/src/scripting/script.ts +99 -0
- package/core/src/sim/hot-reload-pipeline.ts +49 -0
- package/core/src/sim/sim-state-helper.ts +47 -0
- package/core/src/sim/transient-state-registry.ts +77 -0
- package/core/src/sim/types.ts +113 -0
- package/core/src/sim/worker-loop.ts +222 -0
- package/core/src/stores/game-store.ts +158 -0
- package/core/src/stores/hot-reload-store.ts +44 -0
- package/core/src/telemetry/collector.ts +410 -0
- package/core/src/telemetry/gc-controller.ts +453 -0
- package/core/src/telemetry/gc-profiler.ts +126 -0
- package/core/src/telemetry/gc-tracker.ts +69 -0
- package/core/src/telemetry/gpu-profiler.ts +703 -0
- package/core/src/telemetry/gpu-resource-tracker.ts +285 -0
- package/core/src/telemetry/gpu-timer-pool.ts +234 -0
- package/core/src/telemetry/gpu-timer.ts +123 -0
- package/core/src/telemetry/gpu-timestamp-gate.ts +21 -0
- package/core/src/telemetry/reporter.ts +62 -0
- package/core/src/types/engine-types.ts +129 -0
- package/core/src/ui-font-scale.ts +112 -0
- package/core/src/util/broad-phase-grid.ts +82 -0
- package/core/src/util/feature-log.ts +220 -0
- package/core/src/util/index.ts +17 -0
- package/core/src/util/logger.ts +604 -0
- package/core/src/webgpu-destroy-augmentation.d.ts +25 -0
- package/core/src/worker/base-worker-host.ts +183 -0
- package/core/src/worker/buffer-sync.ts +550 -0
- package/core/src/worker/crash-recovery.ts +201 -0
- package/core/src/worker/entity-sim-worker-host.ts +190 -0
- package/core/src/worker/instrumented-worker-host.ts +171 -0
- package/core/src/worker/rpc.ts +253 -0
- package/core/src/worker/sim-worker-base.ts +913 -0
- package/core/src/worker/sim-worker-host.ts +358 -0
- package/core/src/worker/task-pool.ts +256 -0
- package/core/src/worker/task-worker.ts +83 -0
- package/libraries/asset-browser/src/index.ts +11 -0
- package/libraries/asset-browser/src/software-thumbnails.ts +949 -0
- package/libraries/audio-kira/README.md +17 -0
- package/libraries/audio-kira/native/Cargo.toml +14 -0
- package/libraries/audio-kira/native/src/engine.rs +27 -0
- package/libraries/audio-kira/native/src/lib.rs +185 -0
- package/libraries/audio-kira/native/src/mixer.rs +11 -0
- package/libraries/audio-kira/native/src/source.rs +80 -0
- package/libraries/audio-kira/src/backend.ts +469 -0
- package/libraries/audio-kira/src/index.ts +8 -0
- package/libraries/audio-kira/src/library.ts +82 -0
- package/libraries/blitz-ui/native/Cargo.toml +33 -0
- package/libraries/blitz-ui/native/assets/DejaVuSans.woff2 +0 -0
- package/libraries/blitz-ui/native/src/bridge.rs +100 -0
- package/libraries/blitz-ui/native/src/fonts.rs +20 -0
- package/libraries/blitz-ui/native/src/input.rs +80 -0
- package/libraries/blitz-ui/native/src/lib.rs +96 -0
- package/libraries/blitz-ui/native/src/shell.rs +301 -0
- package/libraries/blitz-ui/native-osr/Cargo.toml +24 -0
- package/libraries/blitz-ui/native-osr/src/events.rs +237 -0
- package/libraries/blitz-ui/native-osr/src/lib.rs +846 -0
- package/libraries/blitz-ui/native-osr/src/mutate.rs +385 -0
- package/libraries/blitz-ui/native-osr/src/resources.rs +45 -0
- package/libraries/blitz-ui/native-osr-spike.ts +70 -0
- package/libraries/blitz-ui/src/index.ts +15 -0
- package/libraries/blitz-ui/src/native-module.ts +221 -0
- package/libraries/blitz-ui/src/native-osr-ffi.ts +423 -0
- package/libraries/blitz-ui/src/router.ts +101 -0
- package/libraries/blitz-ui/src/shaders/ui-blit.wgsl.ts +35 -0
- package/libraries/blitz-ui/src/ui-blit-pass.ts +116 -0
- package/libraries/character/README.md +25 -0
- package/libraries/character/src/character-animator.ts +374 -0
- package/libraries/character/src/index.ts +24 -0
- package/libraries/character/src/loader.ts +375 -0
- package/libraries/character/src/locomotion.ts +124 -0
- package/libraries/character/src/preview.ts +287 -0
- package/libraries/devtools/README.md +51 -0
- package/libraries/devtools/native/Cargo.toml +12 -0
- package/libraries/devtools/native/src/lib.rs +1646 -0
- package/libraries/devtools/native/src/panels/console.rs +338 -0
- package/libraries/devtools/native/src/panels/dom_tree.rs +47 -0
- package/libraries/devtools/native/src/panels/generic.rs +317 -0
- package/libraries/devtools/native/src/panels/gpu.rs +107 -0
- package/libraries/devtools/native/src/panels/input.rs +88 -0
- package/libraries/devtools/native/src/panels/mod.rs +154 -0
- package/libraries/devtools/native/src/panels/perf_metrics.rs +91 -0
- package/libraries/devtools/native/src/panels/perf_recorder.rs +158 -0
- package/libraries/devtools/native/src/panels/scene.rs +72 -0
- package/libraries/devtools/native/src/state.rs +394 -0
- package/libraries/devtools/src/cdp-bridge.ts +349 -0
- package/libraries/devtools/src/collectors.ts +346 -0
- package/libraries/devtools/src/egui-ffi.ts +804 -0
- package/libraries/devtools/src/egui-renderer.ts +407 -0
- package/libraries/devtools/src/host.ts +363 -0
- package/libraries/devtools/src/index.ts +40 -0
- package/libraries/devtools/src/mirror.ts +794 -0
- package/libraries/devtools/src/native-providers.ts +744 -0
- package/libraries/devtools/src/verify.ts +167 -0
- package/libraries/devtools/src/web/browser.ts +102 -0
- package/libraries/devtools/src/web/host.ts +212 -0
- package/libraries/devtools/src/web/mirror.ts +430 -0
- package/libraries/devtools/src/web/server.ts +198 -0
- package/libraries/entities/README.md +17 -0
- package/libraries/entities/src/index.ts +4 -0
- package/libraries/entities/src/library.ts +64 -0
- package/libraries/entities/src/model-renderer.ts +1345 -0
- package/libraries/entities/src/shaders/model.wgsl +375 -0
- package/libraries/gamepad/src/feed-input.ts +83 -0
- package/libraries/gamepad/src/filters.ts +49 -0
- package/libraries/gamepad/src/glyphs.ts +93 -0
- package/libraries/gamepad/src/hub.ts +206 -0
- package/libraries/gamepad/src/index.ts +22 -0
- package/libraries/gamepad/src/library.ts +68 -0
- package/libraries/gamepad/src/source-sab.ts +70 -0
- package/libraries/gamepad/src/sysfs-enrich.ts +138 -0
- package/libraries/gaussian-splats/README.md +25 -0
- package/libraries/gaussian-splats/src/gpu-sort.ts +595 -0
- package/libraries/gaussian-splats/src/index.ts +31 -0
- package/libraries/gaussian-splats/src/library.ts +60 -0
- package/libraries/gaussian-splats/src/parser.ts +382 -0
- package/libraries/gaussian-splats/src/renderer.ts +427 -0
- package/libraries/gaussian-splats/src/sh-eval.ts +211 -0
- package/libraries/gaussian-splats/src/sorter.ts +110 -0
- package/libraries/gaussian-splats/src/tile-raster.ts +473 -0
- package/libraries/gpu-kernels/README.md +154 -0
- package/libraries/gpu-kernels/src/index.ts +11 -0
- package/libraries/gpu-kernels/src/kernel.ts +696 -0
- package/libraries/gpu-kernels/src/transpile.ts +958 -0
- package/libraries/html-ui-kit/src/behaviors.ts +250 -0
- package/libraries/html-ui-kit/src/components.ts +309 -0
- package/libraries/html-ui-kit/src/index.ts +30 -0
- package/libraries/html-ui-kit/src/nav/nav-controller.ts +238 -0
- package/libraries/html-ui-kit/src/nav/nav-input.ts +145 -0
- package/libraries/html-ui-kit/src/nav/osk.ts +153 -0
- package/libraries/html-ui-kit/src/nav/router.ts +167 -0
- package/libraries/html-ui-kit/src/nav/vcursor.ts +203 -0
- package/libraries/html-ui-kit/src/ten-foot.ts +182 -0
- package/libraries/html-ui-kit/src/theme.ts +238 -0
- package/libraries/lighting/README.md +20 -0
- package/libraries/lighting/src/index.ts +5 -0
- package/libraries/lighting/src/library.ts +66 -0
- package/libraries/lighting/src/light-system.ts +452 -0
- package/libraries/lighting/src/lighting-system.ts +68 -0
- package/libraries/lighting/src/shaders/light-debug.wgsl +30 -0
- package/libraries/marching-cubes/README.md +25 -0
- package/libraries/marching-cubes/src/chunked-field.ts +177 -0
- package/libraries/marching-cubes/src/deformation.ts +63 -0
- package/libraries/marching-cubes/src/generator.ts +407 -0
- package/libraries/marching-cubes/src/index.ts +32 -0
- package/libraries/marching-cubes/src/library.ts +80 -0
- package/libraries/marching-cubes/src/lod.ts +122 -0
- package/libraries/marching-cubes/src/marching-tetrahedra.ts +252 -0
- package/libraries/marching-cubes/src/sab.ts +45 -0
- package/libraries/marching-cubes/src/streaming-config.ts +48 -0
- package/libraries/marching-cubes/src/streaming-manager.ts +686 -0
- package/libraries/marching-cubes/src/tables.ts +323 -0
- package/libraries/marching-cubes/src/types.ts +18 -0
- package/libraries/models/README.md +25 -0
- package/libraries/models/src/bake-node-transforms.ts +171 -0
- package/libraries/models/src/codecs/basisu-codec.ts +180 -0
- package/libraries/models/src/codecs/draco-codec.ts +372 -0
- package/libraries/models/src/codecs/draco3dgltf.d.ts +40 -0
- package/libraries/models/src/codecs/index.ts +22 -0
- package/libraries/models/src/codecs/meshopt-codec.ts +91 -0
- package/libraries/models/src/codecs/registry.ts +221 -0
- package/libraries/models/src/dae.ts +753 -0
- package/libraries/models/src/default-part-selection.ts +56 -0
- package/libraries/models/src/dxf.ts +200 -0
- package/libraries/models/src/fbx/index.ts +457 -0
- package/libraries/models/src/fbx/interpreter/animation.ts +431 -0
- package/libraries/models/src/fbx/interpreter/blend-shapes.ts +323 -0
- package/libraries/models/src/fbx/interpreter/connections.ts +166 -0
- package/libraries/models/src/fbx/interpreter/diagnostics.ts +65 -0
- package/libraries/models/src/fbx/interpreter/geometry.ts +535 -0
- package/libraries/models/src/fbx/interpreter/global-settings.ts +92 -0
- package/libraries/models/src/fbx/interpreter/materials.ts +265 -0
- package/libraries/models/src/fbx/interpreter/nodes.ts +197 -0
- package/libraries/models/src/fbx/interpreter/skeleton.ts +476 -0
- package/libraries/models/src/fbx/parsers/binary-parser.ts +244 -0
- package/libraries/models/src/fbx/types.ts +119 -0
- package/libraries/models/src/gltf-extensions.ts +196 -0
- package/libraries/models/src/gltf.ts +935 -0
- package/libraries/models/src/index.ts +87 -0
- package/libraries/models/src/library.ts +75 -0
- package/libraries/models/src/loader.ts +265 -0
- package/libraries/models/src/material-adapter.ts +108 -0
- package/libraries/models/src/mini-xml.ts +174 -0
- package/libraries/models/src/normalize.ts +216 -0
- package/libraries/models/src/obj.ts +264 -0
- package/libraries/models/src/off.ts +95 -0
- package/libraries/models/src/ply.ts +348 -0
- package/libraries/models/src/pose-sampler.ts +219 -0
- package/libraries/models/src/sidecar/blender-extras.ts +37 -0
- package/libraries/models/src/sidecar/ddmeta.ts +114 -0
- package/libraries/models/src/sidecar/godot-import.ts +86 -0
- package/libraries/models/src/sidecar/resolver.ts +145 -0
- package/libraries/models/src/sidecar/types.ts +11 -0
- package/libraries/models/src/sidecar/unity-meta.ts +61 -0
- package/libraries/models/src/skeleton-synthesis.ts +132 -0
- package/libraries/models/src/stl.ts +149 -0
- package/libraries/models/src/threeds.ts +363 -0
- package/libraries/models/src/threemf.ts +201 -0
- package/libraries/models/src/types.ts +188 -0
- package/libraries/models/src/vtk.ts +218 -0
- package/libraries/models/test-fixtures/fbx/LICENSE-notice.txt +24 -0
- package/libraries/models/test-fixtures/fbx/README.md +46 -0
- package/libraries/models/test-fixtures/fbx/m01_cube_phong.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m02_geo_ngons.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m03_normals.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m04_material_properties.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m05_external_diffuse.png +0 -0
- package/libraries/models/test-fixtures/fbx/m05_textures.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m06_uv_transform.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m07_multimaterial.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m08_transforms.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m09_skinning.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m10_morph.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m11_node_anim.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m12_skeletal_anim.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m13_morph_anim.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m14_multiclip.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m15_camera_lights.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m16_axis_yup.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m16_axis_zup.fbx +0 -0
- package/libraries/models/test-fixtures/fbx/m16_units_254.fbx +0 -0
- package/libraries/navmesh/README.md +23 -0
- package/libraries/navmesh/src/crowd-system.ts +240 -0
- package/libraries/navmesh/src/debug-viz.ts +74 -0
- package/libraries/navmesh/src/index.ts +15 -0
- package/libraries/navmesh/src/library.ts +64 -0
- package/libraries/navmesh/src/navmesh-generator.ts +407 -0
- package/libraries/navmesh/src/navmesh.ts +159 -0
- package/libraries/navmesh/src/pathfinder.ts +252 -0
- package/libraries/navmesh/src/types.ts +63 -0
- package/libraries/networking/README.md +25 -0
- package/libraries/networking/src/authority.ts +121 -0
- package/libraries/networking/src/connection.ts +139 -0
- package/libraries/networking/src/discovery.ts +373 -0
- package/libraries/networking/src/enhanced-replication.ts +355 -0
- package/libraries/networking/src/epic-adapter.ts +129 -0
- package/libraries/networking/src/index.ts +45 -0
- package/libraries/networking/src/library.ts +69 -0
- package/libraries/networking/src/lobby.ts +108 -0
- package/libraries/networking/src/platform-adapter.ts +163 -0
- package/libraries/networking/src/rcs-adapter.ts +127 -0
- package/libraries/networking/src/remote-input.ts +188 -0
- package/libraries/networking/src/replication.ts +327 -0
- package/libraries/networking/src/rpc.ts +188 -0
- package/libraries/networking/src/session.ts +173 -0
- package/libraries/networking/src/transport.ts +201 -0
- package/libraries/networking/src/webrtc.ts +323 -0
- package/libraries/pathfinding-2d/README.md +15 -0
- package/libraries/pathfinding-2d/src/astar-grid.ts +312 -0
- package/libraries/pathfinding-2d/src/index.ts +11 -0
- package/libraries/persistence/README.md +25 -0
- package/libraries/persistence/src/autosave-manager.ts +112 -0
- package/libraries/persistence/src/binary-record-store.ts +244 -0
- package/libraries/persistence/src/browser.ts +51 -0
- package/libraries/persistence/src/file-save-store.ts +553 -0
- package/libraries/persistence/src/hash-utils.ts +59 -0
- package/libraries/persistence/src/index.ts +57 -0
- package/libraries/persistence/src/library.ts +74 -0
- package/libraries/persistence/src/mock-opfs.ts +141 -0
- package/libraries/persistence/src/node-fs-directory-handle.ts +161 -0
- package/libraries/persistence/src/opfs-save-store.ts +745 -0
- package/libraries/persistence/src/save-worker-proxy.ts +127 -0
- package/libraries/persistence/src/save-worker.ts +99 -0
- package/libraries/persistence/src/xxh3-ts.d.ts +10 -0
- package/libraries/physics-native/README.md +21 -0
- package/libraries/physics-native/native/Cargo.toml +11 -0
- package/libraries/physics-native/native/src/body.rs +403 -0
- package/libraries/physics-native/native/src/character.rs +226 -0
- package/libraries/physics-native/native/src/collider.rs +299 -0
- package/libraries/physics-native/native/src/joint.rs +81 -0
- package/libraries/physics-native/native/src/lib.rs +830 -0
- package/libraries/physics-native/native/src/query.rs +261 -0
- package/libraries/physics-native/native/src/realm.rs +33 -0
- package/libraries/physics-native/native/src/step.rs +54 -0
- package/libraries/physics-native/native/src/world.rs +148 -0
- package/libraries/physics-native/src/backend.ts +1046 -0
- package/libraries/physics-native/src/broadphase.ts +101 -0
- package/libraries/physics-native/src/ffi-spike-worker.ts +71 -0
- package/libraries/physics-native/src/index.ts +11 -0
- package/libraries/physics-native/src/library.ts +79 -0
- package/libraries/physics-native/src/narrowphase-shapes.ts +420 -0
- package/libraries/physics-native/src/narrowphase.ts +123 -0
- package/libraries/physics-native/src/solver.ts +141 -0
- package/libraries/physics-native/src/types.ts +76 -0
- package/libraries/physics-rapier/README.md +21 -0
- package/libraries/physics-rapier/bench/ffi-bench.ts +79 -0
- package/libraries/physics-rapier/bench/wasm-baseline.ts +90 -0
- package/libraries/physics-rapier/src/api.ts +452 -0
- package/libraries/physics-rapier/src/auto-backend.ts +59 -0
- package/libraries/physics-rapier/src/backend.ts +1207 -0
- package/libraries/physics-rapier/src/bulk-ops.ts +68 -0
- package/libraries/physics-rapier/src/ffi-backend.ts +21 -0
- package/libraries/physics-rapier/src/ffi-lib.ts +597 -0
- package/libraries/physics-rapier/src/index.ts +12 -0
- package/libraries/physics-rapier/src/library.ts +107 -0
- package/libraries/physics-rapier/src/rapier-backend.ts +1052 -0
- package/libraries/physics-rapier/src/realm-worker-script.ts +237 -0
- package/libraries/postfx/README.md +17 -0
- package/libraries/postfx/src/index.ts +19 -0
- package/libraries/postfx/src/library.ts +337 -0
- package/libraries/postfx/src/post-process-stack.ts +1752 -0
- package/libraries/postfx/src/shaders/post-process/afterimage.wgsl +14 -0
- package/libraries/postfx/src/shaders/post-process/ascii.wgsl +24 -0
- package/libraries/postfx/src/shaders/post-process/blit.wgsl +12 -0
- package/libraries/postfx/src/shaders/post-process/bloom-blur.wgsl +21 -0
- package/libraries/postfx/src/shaders/post-process/bloom-bright.wgsl +15 -0
- package/libraries/postfx/src/shaders/post-process/bloom-composite.wgsl +14 -0
- package/libraries/postfx/src/shaders/post-process/bloom-downsample.wgsl +60 -0
- package/libraries/postfx/src/shaders/post-process/bloom-soft.wgsl +46 -0
- package/libraries/postfx/src/shaders/post-process/bloom-upsample.wgsl +51 -0
- package/libraries/postfx/src/shaders/post-process/channel-mixer.wgsl +36 -0
- package/libraries/postfx/src/shaders/post-process/chromatic-aberration.wgsl +37 -0
- package/libraries/postfx/src/shaders/post-process/dithering.wgsl +64 -0
- package/libraries/postfx/src/shaders/post-process/dof.wgsl +131 -0
- package/libraries/postfx/src/shaders/post-process/edges.wgsl +45 -0
- package/libraries/postfx/src/shaders/post-process/fullscreen-vs.wgsl +15 -0
- package/libraries/postfx/src/shaders/post-process/fxaa.wgsl +102 -0
- package/libraries/postfx/src/shaders/post-process/gaussian-blur.wgsl +37 -0
- package/libraries/postfx/src/shaders/post-process/glow-blur.wgsl +33 -0
- package/libraries/postfx/src/shaders/post-process/glow-composite.wgsl +25 -0
- package/libraries/postfx/src/shaders/post-process/grain.wgsl +37 -0
- package/libraries/postfx/src/shaders/post-process/halftone.wgsl +80 -0
- package/libraries/postfx/src/shaders/post-process/highlight-blur.wgsl +33 -0
- package/libraries/postfx/src/shaders/post-process/highlight-composite.wgsl +32 -0
- package/libraries/postfx/src/shaders/post-process/lens-distortion.wgsl +43 -0
- package/libraries/postfx/src/shaders/post-process/lens-flare.wgsl +68 -0
- package/libraries/postfx/src/shaders/post-process/lut.wgsl +30 -0
- package/libraries/postfx/src/shaders/post-process/motion-blur.wgsl +55 -0
- package/libraries/postfx/src/shaders/post-process/occluder-chunk.wgsl +28 -0
- package/libraries/postfx/src/shaders/post-process/outline.wgsl +51 -0
- package/libraries/postfx/src/shaders/post-process/pixelation.wgsl +47 -0
- package/libraries/postfx/src/shaders/post-process/sharpen.wgsl +32 -0
- package/libraries/postfx/src/shaders/post-process/sobel.wgsl +26 -0
- package/libraries/postfx/src/shaders/post-process/split-tone.wgsl +36 -0
- package/libraries/postfx/src/shaders/post-process/ssao-blur.wgsl +38 -0
- package/libraries/postfx/src/shaders/post-process/ssao-composite.wgsl +25 -0
- package/libraries/postfx/src/shaders/post-process/ssao.wgsl +147 -0
- package/libraries/postfx/src/shaders/post-process/ssr.wgsl +155 -0
- package/libraries/postfx/src/shaders/post-process/taa.wgsl +98 -0
- package/libraries/postfx/src/shaders/post-process/tonemap.wgsl +40 -0
- package/libraries/postfx/src/shaders/post-process/watercolor.wgsl +82 -0
- package/libraries/postfx/src/shaders/post-process/white-balance.wgsl +41 -0
- package/libraries/recast/README.md +21 -0
- package/libraries/recast/src/backend.ts +150 -0
- package/libraries/recast/src/crowd-system.ts +352 -0
- package/libraries/recast/src/index.ts +12 -0
- package/libraries/recast/src/library.ts +91 -0
- package/libraries/recast/src/types.ts +86 -0
- package/libraries/sand/README.md +25 -0
- package/libraries/sand/src/cell.ts +43 -0
- package/libraries/sand/src/fields.ts +29 -0
- package/libraries/sand/src/fluid-grid.ts +261 -0
- package/libraries/sand/src/grid-dims.ts +74 -0
- package/libraries/sand/src/grid-sim-buffer.ts +271 -0
- package/libraries/sand/src/index.ts +81 -0
- package/libraries/sand/src/library.ts +92 -0
- package/libraries/sand/src/materials.ts +492 -0
- package/libraries/sand/src/palette.ts +93 -0
- package/libraries/sand/src/particles.ts +185 -0
- package/libraries/sand/src/render/sand-grid-pass.ts +430 -0
- package/libraries/sand/src/rng.ts +51 -0
- package/libraries/sand/src/rules/builtin-rules.ts +132 -0
- package/libraries/sand/src/rules/rule-compiler.ts +162 -0
- package/libraries/sand/src/rules/rule-engine.ts +231 -0
- package/libraries/sand/src/rules/rule-types.ts +87 -0
- package/libraries/sand/src/sand-step-pool.ts +484 -0
- package/libraries/sand/src/sand-step-worker.ts +97 -0
- package/libraries/sand/src/sand-world.ts +3227 -0
- package/libraries/stickman/README.md +25 -0
- package/libraries/stickman/src/index.ts +25 -0
- package/libraries/stickman/src/library.ts +61 -0
- package/libraries/stickman/src/proportions.ts +45 -0
- package/libraries/stickman/src/shader.ts +117 -0
- package/libraries/stickman/src/skeleton.ts +197 -0
- package/libraries/stickman/src/stickman-pass.ts +255 -0
- package/libraries/stickman/src/thick-line.ts +123 -0
- package/libraries/surface-nets/README.md +25 -0
- package/libraries/surface-nets/src/chunked-field.ts +14 -0
- package/libraries/surface-nets/src/deformation.ts +45 -0
- package/libraries/surface-nets/src/index.ts +36 -0
- package/libraries/surface-nets/src/library.ts +59 -0
- package/libraries/surface-nets/src/lod.ts +7 -0
- package/libraries/surface-nets/src/sab.ts +4 -0
- package/libraries/surface-nets/src/streaming-config.ts +5 -0
- package/libraries/surface-nets/src/streaming-manager.ts +15 -0
- package/libraries/surface-nets/src/surface-nets.ts +334 -0
- package/libraries/surface-nets/src/types.ts +23 -0
- package/libraries/water/README.md +25 -0
- package/libraries/water/src/buoyancy.ts +103 -0
- package/libraries/water/src/index.ts +43 -0
- package/libraries/water/src/library.ts +85 -0
- package/libraries/water/src/shore-damping.ts +93 -0
- package/libraries/water/src/water-buffer.ts +89 -0
- package/libraries/water/src/water-chunks.ts +13 -0
- package/libraries/water/src/water-physics.ts +376 -0
- package/libraries/water/src/water-renderer.ts +809 -0
- package/libraries/water/src/water-sab.ts +161 -0
- package/libraries/water/src/wave-sources.ts +84 -0
- package/libraries/weather/README.md +20 -0
- package/libraries/weather/src/index.ts +9 -0
- package/libraries/weather/src/library.ts +72 -0
- package/libraries/weather/src/types.ts +64 -0
- package/libraries/weather/src/weather-blend.ts +112 -0
- package/libraries/weather/src/weather-system.ts +271 -0
- package/libraries/weatherfx/README.md +24 -0
- package/libraries/weatherfx/src/cloud-provider.ts +31 -0
- package/libraries/weatherfx/src/cloud-system.ts +372 -0
- package/libraries/weatherfx/src/index.ts +9 -0
- package/libraries/weatherfx/src/library.ts +58 -0
- package/libraries/weatherfx/src/particle-system.ts +434 -0
- package/libraries/weatherfx/src/shaders/cloud.wgsl +111 -0
- package/libraries/weatherfx/src/shaders/particle-compute.wgsl +187 -0
- package/libraries/weatherfx/src/shaders/particle-render.wgsl +95 -0
- package/lint-plugin.mjs +41 -0
- package/mcp/src/client.ts +703 -0
- package/mcp/src/engine-context.ts +186 -0
- package/mcp/src/http-transport.ts +471 -0
- package/mcp/src/index.ts +13 -0
- package/mcp/src/prompts/add-entity.ts +42 -0
- package/mcp/src/prompts/create-scene.ts +44 -0
- package/mcp/src/prompts/debug-frame.ts +39 -0
- package/mcp/src/prompts/index.ts +12 -0
- package/mcp/src/resources/asset-list.ts +32 -0
- package/mcp/src/resources/checkpoint-list.ts +33 -0
- package/mcp/src/resources/entity-state.ts +55 -0
- package/mcp/src/resources/gpu-info.ts +103 -0
- package/mcp/src/resources/index.ts +57 -0
- package/mcp/src/resources/performance.ts +37 -0
- package/mcp/src/resources/scene-tree.ts +43 -0
- package/mcp/src/server.ts +375 -0
- package/mcp/src/tools/animation.ts +118 -0
- package/mcp/src/tools/asset.ts +161 -0
- package/mcp/src/tools/audio.ts +96 -0
- package/mcp/src/tools/build.ts +143 -0
- package/mcp/src/tools/camera.ts +167 -0
- package/mcp/src/tools/checkpoint.ts +100 -0
- package/mcp/src/tools/component.ts +199 -0
- package/mcp/src/tools/debug.ts +300 -0
- package/mcp/src/tools/entity.ts +361 -0
- package/mcp/src/tools/inspect.ts +210 -0
- package/mcp/src/tools/lighting.ts +263 -0
- package/mcp/src/tools/material.ts +243 -0
- package/mcp/src/tools/mesh.ts +239 -0
- package/mcp/src/tools/physics.ts +266 -0
- package/mcp/src/tools/scene.ts +153 -0
- package/mcp/src/tools/script.ts +122 -0
- package/mcp/src/types.ts +92 -0
- package/mcp/src/undo-redo.ts +77 -0
- package/modules/camera-controls/README.md +20 -0
- package/modules/camera-controls/src/index.ts +161 -0
- package/modules/controller-ui/src/index.ts +164 -0
- package/modules/devtools/README.md +25 -0
- package/modules/devtools/src/api.ts +461 -0
- package/modules/devtools/src/asset-utils.ts +105 -0
- package/modules/devtools/src/bind-debug-store.ts +48 -0
- package/modules/devtools/src/data-bridge.ts +286 -0
- package/modules/devtools/src/debug-overlay.ts +238 -0
- package/modules/devtools/src/debug-raycast.ts +328 -0
- package/modules/devtools/src/debug-store.ts +92 -0
- package/modules/devtools/src/debug-view-descriptors.ts +59 -0
- package/modules/devtools/src/doctor-panel.ts +182 -0
- package/modules/devtools/src/gizmo-module.ts +136 -0
- package/modules/devtools/src/graph-model.ts +509 -0
- package/modules/devtools/src/grid-renderer.ts +157 -0
- package/modules/devtools/src/height-ruler-renderer.ts +216 -0
- package/modules/devtools/src/index.ts +81 -0
- package/modules/devtools/src/init.ts +261 -0
- package/modules/devtools/src/label-overlay.ts +154 -0
- package/modules/devtools/src/material-stats-panel.ts +188 -0
- package/modules/devtools/src/profiling-bridge.ts +377 -0
- package/modules/devtools/src/profiling-hooks.ts +98 -0
- package/modules/devtools/src/renderer-adapter.ts +76 -0
- package/modules/devtools/src/scene-inspector.ts +637 -0
- package/modules/devtools/src/scene-store.ts +320 -0
- package/modules/devtools/src/scene-sync.ts +116 -0
- package/modules/devtools/src/shaders/transform-gizmo.wgsl +38 -0
- package/modules/devtools/src/sim-stats-panel.ts +272 -0
- package/modules/devtools/src/sim-stats-provider.ts +105 -0
- package/modules/devtools/src/skeleton-renderer.ts +351 -0
- package/modules/devtools/src/transform-gizmo.ts +928 -0
- package/modules/devtools/src/types.ts +290 -0
- package/modules/devtools/src/worker-expose.ts +67 -0
- package/modules/devtools/src/worker-sync.ts +137 -0
- package/modules/editor/src/commands/component.ts +156 -0
- package/modules/editor/src/commands/entity.ts +287 -0
- package/modules/editor/src/commands/hierarchy.ts +83 -0
- package/modules/editor/src/commands/index.ts +61 -0
- package/modules/editor/src/commands/prefab.ts +163 -0
- package/modules/editor/src/commands/registry.ts +331 -0
- package/modules/editor/src/commands/scene-io.ts +211 -0
- package/modules/editor/src/commands/selection.ts +73 -0
- package/modules/editor/src/commands/transform.ts +91 -0
- package/modules/editor/src/ddscene.ts +267 -0
- package/modules/editor/src/document.ts +123 -0
- package/modules/editor/src/editor-context.ts +172 -0
- package/modules/editor/src/index.ts +41 -0
- package/modules/editor/src/mcp.ts +103 -0
- package/modules/editor/src/module.ts +148 -0
- package/modules/editor/src/selection.ts +70 -0
- package/modules/editor/src/shell.ts +456 -0
- package/modules/editor/src/viewport.ts +199 -0
- package/modules/html-ui/src/composite.ts +135 -0
- package/modules/html-ui/src/create-html-ui.ts +188 -0
- package/modules/html-ui/src/doc-backend.ts +343 -0
- package/modules/html-ui/src/host.ts +646 -0
- package/modules/html-ui/src/index.ts +13 -0
- package/modules/html-ui/src/jsx-dev-runtime.ts +12 -0
- package/modules/html-ui/src/jsx-runtime.ts +126 -0
- package/modules/html-ui/src/protocol.ts +88 -0
- package/modules/html-ui/src/ui-worker.ts +16 -0
- package/modules/mcp/README.md +18 -0
- package/modules/mcp/src/index.ts +27 -0
- package/modules/movement-2d/README.md +19 -0
- package/modules/movement-2d/src/grid-character.ts +455 -0
- package/modules/movement-2d/src/index.ts +45 -0
- package/modules/movement-3d/README.md +21 -0
- package/modules/movement-3d/src/character-motor.ts +230 -0
- package/modules/movement-3d/src/index.ts +46 -0
- package/modules/native-osr/src/index.ts +22 -0
- package/modules/native-osr/src/input-router.ts +401 -0
- package/modules/native-osr/src/native-osr-manager.ts +247 -0
- package/modules/native-osr/src/native-texture-source.ts +86 -0
- package/modules/native-osr/src/types.ts +112 -0
- package/modules/native-osr/src/world-space-ui-pass.ts +351 -0
- package/modules/sailing/README.md +18 -0
- package/modules/sailing/src/index.ts +51 -0
- package/modules/terrain/README.md +18 -0
- package/modules/terrain/src/index.ts +78 -0
- package/modules/vitals/README.md +17 -0
- package/modules/vitals/src/index.ts +43 -0
- package/modules/vitals/src/vitals.ts +265 -0
- package/modules/xr/README.md +25 -0
- package/modules/xr/src/camera-rig.ts +146 -0
- package/modules/xr/src/debug.ts +59 -0
- package/modules/xr/src/frame-loop.ts +197 -0
- package/modules/xr/src/index.ts +16 -0
- package/modules/xr/src/input.ts +208 -0
- package/modules/xr/src/layer.ts +140 -0
- package/modules/xr/src/session.ts +156 -0
- package/modules/xr/src/sim-module.ts +23 -0
- package/modules/xr/src/types.ts +32 -0
- package/modules/xr/src/webxr.d.ts +182 -0
- package/modules/xr/src/xr-module.ts +179 -0
- package/package.json +232 -3
- package/shader-graph/src/chunks.ts +349 -0
- package/shader-graph/src/compiler.ts +732 -0
- package/shader-graph/src/compute-compiler.ts +453 -0
- package/shader-graph/src/compute-graph.ts +112 -0
- package/shader-graph/src/compute-profiles.ts +54 -0
- package/shader-graph/src/graph.ts +52 -0
- package/shader-graph/src/index.ts +53 -0
- package/shader-graph/src/profiles.ts +407 -0
- package/shader-graph/src/validator.ts +48 -0
- package/shader-graph/src/wgsl-binding-validator.ts +143 -0
- package/shader-graph/src/wgsl-struct-validator.ts +245 -0
- package/shader-graph/src/wgsl-struct.ts +397 -0
- package/test/src/index.ts +7 -0
- package/test/src/vision-test.ts +369 -0
|
@@ -0,0 +1,1452 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// GameRenderer — generic WebGPU render loop infrastructure
|
|
3
|
+
// Extracted from WebGPURenderer: device init, surface config, rAF loop,
|
|
4
|
+
// frame rate limiter, viewport management, telemetry, GPU UI pass, depth cache.
|
|
5
|
+
// Games register render passes via FrameGraph slots and provide game-specific
|
|
6
|
+
// logic through callback hooks.
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
import { UIInputRouter } from "../input/ui-router";
|
|
10
|
+
import type { RendererModule } from "../module/renderer-module";
|
|
11
|
+
import type { RenderSurface, RenderSurfaceContext } from "../platform/render-surface";
|
|
12
|
+
import { getHostCapabilities, getNativeHost } from "../platform/runtime";
|
|
13
|
+
import { TelemetryCollector } from "../telemetry/collector";
|
|
14
|
+
import type { GPUAdapterInfo as GPUAdapterInfoData } from "../telemetry/gpu-profiler";
|
|
15
|
+
import { GPUProfiler, type FrameGraphData, type GPUInfo } from "../telemetry/gpu-profiler";
|
|
16
|
+
import { GPUResourceTracker } from "../telemetry/gpu-resource-tracker";
|
|
17
|
+
import { createLogger } from "../util/logger";
|
|
18
|
+
import { CanvasResizeWatcher, type CanvasResizeHandler } from "./canvas-resize-watcher";
|
|
19
|
+
import { GPUDeviceManager } from "./device";
|
|
20
|
+
import type { RenderContext } from "./frame-graph";
|
|
21
|
+
import { FrameGraph, SlotRegistry, type TextureHandle } from "./frame-graph";
|
|
22
|
+
import { InputManager } from "./input-manager";
|
|
23
|
+
import { RendererModuleHost } from "./renderer-module-host";
|
|
24
|
+
import { installShaderValidationGuard } from "./shader-validator";
|
|
25
|
+
|
|
26
|
+
const log = createLogger();
|
|
27
|
+
|
|
28
|
+
export interface ViewportRect {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
w: number;
|
|
32
|
+
h: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The structural contract engine bootstrap code relies on (`startGame`,
|
|
37
|
+
* `startNativeGame`, `runNativeGameModule`, MCP automation). `GameRenderer`
|
|
38
|
+
* satisfies it; games with bespoke renderers should too. Everything is
|
|
39
|
+
* optional except `init`/`start`/`stop` — the methods that every consumer
|
|
40
|
+
* must be able to call.
|
|
41
|
+
*
|
|
42
|
+
* `renderOnce` is the canonical one-shot-frame name (deterministic capture,
|
|
43
|
+
* screenshots). Do not define a parallel `renderOneFrame` — override
|
|
44
|
+
* `renderOnce` instead.
|
|
45
|
+
*/
|
|
46
|
+
export interface GameRendererLike {
|
|
47
|
+
init(): boolean | Promise<boolean>;
|
|
48
|
+
start(): void;
|
|
49
|
+
stop(): void;
|
|
50
|
+
isRunning?(): boolean;
|
|
51
|
+
onResize?(cssWidth: number, cssHeight: number, dpr: number): void;
|
|
52
|
+
/** Render one frame on demand while the loop is stopped (or running). */
|
|
53
|
+
renderOnce?(): void;
|
|
54
|
+
getSurface?(): RenderSurface;
|
|
55
|
+
getCanvas?(): RenderSurface;
|
|
56
|
+
getDevice?(): GPUDevice | null;
|
|
57
|
+
getFormat?(): GPUTextureFormat;
|
|
58
|
+
getFPS?(): number;
|
|
59
|
+
useRendererModule?(module: RendererModule): void;
|
|
60
|
+
getWorkerHost?(): unknown;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface GameRendererConfig {
|
|
64
|
+
/**
|
|
65
|
+
* Pre-created device/adapter to borrow instead of requesting new ones.
|
|
66
|
+
* On the native host a single wgpu device is shared between the host and
|
|
67
|
+
* the renderer — this path avoids a second requestDevice() on the same
|
|
68
|
+
* surface. When omitted, init() falls back to the native host's device
|
|
69
|
+
* (getNativeHost()) before self-acquiring via navigator.gpu.
|
|
70
|
+
*/
|
|
71
|
+
device?: GPUDevice;
|
|
72
|
+
adapter?: GPUAdapter;
|
|
73
|
+
/**
|
|
74
|
+
* Configure the surface context at init. Defaults to true — set false
|
|
75
|
+
* when the surface was already configured by the device owner (e.g. the
|
|
76
|
+
* native host configures it at window creation). Automatically skipped
|
|
77
|
+
* when the device was borrowed from the native host.
|
|
78
|
+
*/
|
|
79
|
+
configureSurface?: boolean;
|
|
80
|
+
depthFormat?: GPUTextureFormat;
|
|
81
|
+
msaaSampleCount?: number;
|
|
82
|
+
/** Max number of cached depth textures (per resolution). Older entries are evicted. Default: 3. */
|
|
83
|
+
depthTextureCacheSize?: number;
|
|
84
|
+
/**
|
|
85
|
+
* Whether clicking the canvas auto-requests pointer lock (hides + confines
|
|
86
|
+
* the cursor). Default: false.
|
|
87
|
+
*
|
|
88
|
+
* Pointer lock is an opt-in FPS-style concern. 2D click-based games must
|
|
89
|
+
* leave this off so the cursor stays visible and free-moving for
|
|
90
|
+
* click-to-dig / click-to-place interactions. 3D games that want pointer
|
|
91
|
+
* lock can either enable this or manage pointer lock themselves (e.g.
|
|
92
|
+
* to-the-ocean uses its own RendererInputHandler and leaves this off).
|
|
93
|
+
*/
|
|
94
|
+
enablePointerLock?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Renderer mode:
|
|
97
|
+
* - "3d" (default): Camera-based rendering with viewports. Each viewport
|
|
98
|
+
* requires camera info from onViewport callback or renderer plugins.
|
|
99
|
+
* - "2d": No camera required. A default orthographic camera is used.
|
|
100
|
+
* The onViewport callback can return null (the default camera is used).
|
|
101
|
+
* Render passes receive a full-screen viewport with an identity-like
|
|
102
|
+
* camera. Depth texture is still provided but may be ignored by 2D passes.
|
|
103
|
+
* Set viewportCount to 0 and use the afterFrame callback for fully custom
|
|
104
|
+
* 2D rendering (the FrameGraph viewport loop is skipped entirely).
|
|
105
|
+
*/
|
|
106
|
+
mode?: "2d" | "3d";
|
|
107
|
+
/**
|
|
108
|
+
* Clear color for the canvas when viewportCount is 0 (2D mode with custom
|
|
109
|
+
* rendering in afterFrame). Default: { r: 0, g: 0, b: 0, a: 1 }.
|
|
110
|
+
* If null, the canvas is not cleared (the afterFrame callback must clear it).
|
|
111
|
+
*/
|
|
112
|
+
clearColor?: GPUColor | null;
|
|
113
|
+
/**
|
|
114
|
+
* Emit console warnings when a renderFrame / getCurrentTexture call exceeds
|
|
115
|
+
* 20ms. Off by default — the warnings themselves cause jank (console I/O in
|
|
116
|
+
* the frame loop is self-amplifying). Enable when profiling.
|
|
117
|
+
*/
|
|
118
|
+
debugTimingWarnings?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Enable the TelemetryCollector (frame/draw/graph stats for the profiling
|
|
121
|
+
* overlay + devtools). Default: true — set false in production builds that
|
|
122
|
+
* never surface telemetry to skip the per-frame recording overhead.
|
|
123
|
+
*/
|
|
124
|
+
enableTelemetry?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* GPU device-loss handling:
|
|
127
|
+
* - "auto" (default): attempt in-place recovery — re-request the adapter/
|
|
128
|
+
* device, reconfigure the canvas, re-prepare UI + all registered frame
|
|
129
|
+
* graph passes, and recompile the graph's transient textures. Falls back
|
|
130
|
+
* to a page reload if recovery fails.
|
|
131
|
+
* - "reload": reload the page after a short delay (legacy behavior). Use
|
|
132
|
+
* for games whose passes/modules can't recreate their GPU resources.
|
|
133
|
+
*
|
|
134
|
+
* Note: recovery recreates everything GameRenderer owns plus every
|
|
135
|
+
* registered pass's `prepare()`. Renderer modules that cached
|
|
136
|
+
* `ctx.getDevice()` at register time hold stale devices — modules should
|
|
137
|
+
* call `getDevice()` lazily or re-create resources in a pass's `prepare()`.
|
|
138
|
+
* Games can also hook `callbacks.onDeviceRecovered` for custom resources.
|
|
139
|
+
*/
|
|
140
|
+
deviceLossRecovery?: "auto" | "reload";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface FrameCallbacks {
|
|
144
|
+
beforeFrame?: (dt: number, elapsedTime: number) => void;
|
|
145
|
+
beforeViewports?: (dt: number, elapsedTime: number) => void;
|
|
146
|
+
onViewport?: (viewportIdx: number, dt: number, elapsedTime: number) => CameraViewportInfo | null;
|
|
147
|
+
afterViewports?: (dt: number, elapsedTime: number) => void;
|
|
148
|
+
afterFrame?: (dt: number, elapsedTime: number) => void;
|
|
149
|
+
onResize?: (cssWidth: number, cssHeight: number, dpr: number) => void;
|
|
150
|
+
getPostProcessInfo?: () => { pixelationEnabled: boolean; pixelSize: number; postProcessEffects: string[] };
|
|
151
|
+
/** Fired when the GPU device is lost, before recovery is attempted. */
|
|
152
|
+
onDeviceLost?: (info: GPUDeviceLostInfo) => void;
|
|
153
|
+
/**
|
|
154
|
+
* Fired after the engine re-acquired a device and re-prepared its own
|
|
155
|
+
* resources (context, UI, frame-graph passes). Recreate any game-owned GPU
|
|
156
|
+
* resources not held by a registered RenderPass here. Throwing (or a
|
|
157
|
+
* rejected promise) aborts recovery and falls back to a page reload.
|
|
158
|
+
*/
|
|
159
|
+
onDeviceRecovered?: (device: GPUDevice) => void | Promise<void>;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface CameraViewportInfo {
|
|
163
|
+
camera: {
|
|
164
|
+
position: [number, number, number];
|
|
165
|
+
target: [number, number, number];
|
|
166
|
+
up: [number, number, number];
|
|
167
|
+
fov: number;
|
|
168
|
+
near: number;
|
|
169
|
+
far: number;
|
|
170
|
+
aspect: number;
|
|
171
|
+
projectionMatrix?: Float32Array;
|
|
172
|
+
viewMatrix?: Float32Array;
|
|
173
|
+
};
|
|
174
|
+
viewport: ViewportRect;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface OffscreenMode {
|
|
178
|
+
type: "none" | "pixelation" | "postprocess";
|
|
179
|
+
getColorView: () => GPUTextureView;
|
|
180
|
+
getDepthView: () => GPUTextureView;
|
|
181
|
+
scaleViewport?: (v: ViewportRect) => ViewportRect;
|
|
182
|
+
applyPostprocess?: (encoder: GPUCommandEncoder, canvasView: GPUTextureView, w: number, h: number) => void;
|
|
183
|
+
ensureTargets?: (w: number, h: number) => void;
|
|
184
|
+
getSceneColorView?: () => GPUTextureView;
|
|
185
|
+
getSceneDepthView?: () => GPUTextureView;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export interface RenderTargetProvider {
|
|
189
|
+
getColorView(viewportIdx: number): GPUTextureView;
|
|
190
|
+
getDepthView(viewportIdx: number, w: number, h: number): GPUTextureView;
|
|
191
|
+
getViewportCount(): number;
|
|
192
|
+
getViewportRect(idx: number, screenW: number, screenH: number): ViewportRect;
|
|
193
|
+
beginFrame(): void;
|
|
194
|
+
endFrame(encoder: GPUCommandEncoder): void;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export type RAFSource = (callback: (time: number) => void) => number;
|
|
198
|
+
export type CancelRAF = (id: number) => void;
|
|
199
|
+
|
|
200
|
+
export class GameRenderer implements CanvasResizeHandler {
|
|
201
|
+
private canvas: RenderSurface;
|
|
202
|
+
private device: GPUDevice | null = null;
|
|
203
|
+
private adapter: GPUAdapter | null = null;
|
|
204
|
+
private context: RenderSurfaceContext | null = null;
|
|
205
|
+
private format: GPUTextureFormat = "bgra8unorm";
|
|
206
|
+
private config: GameRendererConfig;
|
|
207
|
+
|
|
208
|
+
private depthFormat: GPUTextureFormat = "depth32float";
|
|
209
|
+
private msaaSampleCount: number = 1;
|
|
210
|
+
private mode: "2d" | "3d" = "3d";
|
|
211
|
+
|
|
212
|
+
// Infrastructure
|
|
213
|
+
private deviceManager: GPUDeviceManager;
|
|
214
|
+
private resizeWatcher: CanvasResizeWatcher | null = null;
|
|
215
|
+
private inputManager: InputManager;
|
|
216
|
+
private frameGraph: FrameGraph;
|
|
217
|
+
private rendererModuleHost: RendererModuleHost | null = null;
|
|
218
|
+
|
|
219
|
+
// Telemetry & profiling
|
|
220
|
+
gpuProfiler: GPUProfiler | null = null;
|
|
221
|
+
gpuResourceTracker: GPUResourceTracker | null = null;
|
|
222
|
+
telemetryCollector: TelemetryCollector | null = null;
|
|
223
|
+
|
|
224
|
+
/** Input router for the active UI stack (html-ui via Blitz, or none). */
|
|
225
|
+
uiInputRouter: UIInputRouter | null = null;
|
|
226
|
+
/** Screen-space compositors (html-ui panels etc.) drawn in the UI pass. */
|
|
227
|
+
private uiCompositors = new Set<import("../module/renderer-module").ScreenUiCompositor>();
|
|
228
|
+
|
|
229
|
+
// Render loop state
|
|
230
|
+
private running = false;
|
|
231
|
+
private deviceLost = false;
|
|
232
|
+
/** True when init() borrowed the native host's device — the surface was
|
|
233
|
+
* already configured by the host, so the renderer must not reconfigure. */
|
|
234
|
+
private hostConfiguredSurface = false;
|
|
235
|
+
private lastTime = 0;
|
|
236
|
+
private elapsedTime = 0;
|
|
237
|
+
private fps = 0;
|
|
238
|
+
private frameCount = 0;
|
|
239
|
+
private fpsTimer = 0;
|
|
240
|
+
private lastResourceStatsTime = 0;
|
|
241
|
+
private static readonly RESOURCE_STATS_INTERVAL = 1000;
|
|
242
|
+
|
|
243
|
+
// Frame rate limiter
|
|
244
|
+
private targetFrameTime = 0;
|
|
245
|
+
private limiterActive = false;
|
|
246
|
+
private frameAccum = 0;
|
|
247
|
+
private lastLimiterTick = 0;
|
|
248
|
+
private rafInterval = 0;
|
|
249
|
+
private rafSum = 0;
|
|
250
|
+
private rafCount = 0;
|
|
251
|
+
private lastRafTime = 0;
|
|
252
|
+
|
|
253
|
+
// Viewport management
|
|
254
|
+
private viewportCount = 1;
|
|
255
|
+
private viewports: ViewportRect[] = [];
|
|
256
|
+
|
|
257
|
+
// Transient surface/depth handles (imported into the frame graph each frame)
|
|
258
|
+
private colorHandle: TextureHandle | null = null;
|
|
259
|
+
private depthHandle: TextureHandle | null = null;
|
|
260
|
+
private graphCompiled = false;
|
|
261
|
+
|
|
262
|
+
// Reusable per-frame RenderContext (mutated per viewport — see renderViewport)
|
|
263
|
+
private frameCtx: RenderContext | null = null;
|
|
264
|
+
|
|
265
|
+
// Depth texture cache (used when the graph does not own depth)
|
|
266
|
+
// LRU: Map insertion order = access order (delete + re-set on access).
|
|
267
|
+
private depthTextures = new Map<string, GPUTexture>();
|
|
268
|
+
private depthTextureCacheSize: number;
|
|
269
|
+
|
|
270
|
+
// Frame stats
|
|
271
|
+
private frameDrawCalls = 0;
|
|
272
|
+
private frameTriangles = 0;
|
|
273
|
+
|
|
274
|
+
// Callbacks
|
|
275
|
+
private callbacks: FrameCallbacks = {};
|
|
276
|
+
|
|
277
|
+
// Offscreen mode (set by game for postprocessing)
|
|
278
|
+
private offscreenMode: OffscreenMode | null = null;
|
|
279
|
+
|
|
280
|
+
// XR render target provider (overrides canvas surface when set)
|
|
281
|
+
private renderTargetProvider: RenderTargetProvider | null = null;
|
|
282
|
+
|
|
283
|
+
// rAF source override (for XR sessions)
|
|
284
|
+
private rafSource: RAFSource | null = null;
|
|
285
|
+
private cancelRaf: CancelRAF | null = null;
|
|
286
|
+
private currentRafId: number = 0;
|
|
287
|
+
// Dedup for the render chain: exactly one pending rAF may exist at a time.
|
|
288
|
+
// renderFrame() is also called outside the loop by renderOnce() (native
|
|
289
|
+
// screenshots, deterministic steps); letting it re-schedule leaked a
|
|
290
|
+
// permanent second (third, ...) render chain per call — each chain ran
|
|
291
|
+
// renderFrame() every pump tick, multiplying both real render work and the
|
|
292
|
+
// FPS counter (the "1100 fps" badge after MCP screenshot calls).
|
|
293
|
+
private rafPending = false;
|
|
294
|
+
|
|
295
|
+
// Dpr
|
|
296
|
+
private dpr = 1;
|
|
297
|
+
|
|
298
|
+
constructor(canvas: RenderSurface, config: GameRendererConfig = {}) {
|
|
299
|
+
this.canvas = canvas;
|
|
300
|
+
this.config = config;
|
|
301
|
+
this.depthFormat = config.depthFormat ?? "depth32float";
|
|
302
|
+
this.msaaSampleCount = config.msaaSampleCount ?? 1;
|
|
303
|
+
this.depthTextureCacheSize = config.depthTextureCacheSize ?? 3;
|
|
304
|
+
this.mode = config.mode ?? "3d";
|
|
305
|
+
this.deviceManager = new GPUDeviceManager();
|
|
306
|
+
this.inputManager = new InputManager(canvas, config.enablePointerLock ?? false);
|
|
307
|
+
this.frameGraph = new FrameGraph();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/** Build requiredLimits for the bindless binding model (clamped to adapter). */
|
|
311
|
+
private buildRequiredLimits(adapter: GPUAdapter): Record<string, number> {
|
|
312
|
+
const a = adapter.limits as unknown as Record<string, number>;
|
|
313
|
+
const clamp = (key: string, want: number): [string, number] | null => {
|
|
314
|
+
const have = a[key];
|
|
315
|
+
if (have === undefined) return null;
|
|
316
|
+
return [key, Math.min(want, Number(have))];
|
|
317
|
+
};
|
|
318
|
+
const entries: Array<[string, number]> = [];
|
|
319
|
+
[
|
|
320
|
+
clamp("maxTextureArrayLayers", 512),
|
|
321
|
+
clamp("maxStorageBuffersPerShaderStage", 8),
|
|
322
|
+
clamp("maxStorageBufferBindingSize", 64 * 1024 * 1024),
|
|
323
|
+
clamp("maxSampledTexturesPerShaderStage", 16),
|
|
324
|
+
].forEach((e) => {
|
|
325
|
+
if (e) entries.push(e);
|
|
326
|
+
});
|
|
327
|
+
return Object.fromEntries(entries);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async init(): Promise<boolean> {
|
|
331
|
+
try {
|
|
332
|
+
// Device resolution order: explicit config → native host's device →
|
|
333
|
+
// self-acquire. On native the host owns the only wgpu device and the
|
|
334
|
+
// surface context — borrowing it avoids a second requestDevice() and
|
|
335
|
+
// a reconfigure race on the same surface. In-process there is
|
|
336
|
+
// exactly one device.
|
|
337
|
+
const nativeHost = getNativeHost();
|
|
338
|
+
const borrowedDevice = this.config.device ?? nativeHost?.device;
|
|
339
|
+
const borrowedAdapter = this.config.adapter ?? nativeHost?.adapter;
|
|
340
|
+
// An adapter is only required when we must acquire the device ourselves;
|
|
341
|
+
// a borrowed device is already usable (adapter.info is null-tolerated
|
|
342
|
+
// below).
|
|
343
|
+
const adapter = borrowedAdapter ?? await this.requestAdapterWithFallback();
|
|
344
|
+
if (!adapter && !borrowedDevice) {
|
|
345
|
+
log.error("GameRenderer", "No GPU adapter found — check GPU drivers and /dev/dri permissions");
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
this.device = borrowedDevice ?? await this.requestDeviceFromAdapter(adapter!);
|
|
349
|
+
this.adapter = adapter;
|
|
350
|
+
// The host configured the surface with its own usage set at creation —
|
|
351
|
+
// reconfiguring here would tear down the swapchain and race the host's
|
|
352
|
+
// readback hook. Skip when borrowing the host's device.
|
|
353
|
+
this.hostConfiguredSurface =
|
|
354
|
+
borrowedDevice !== undefined && borrowedDevice === nativeHost?.device;
|
|
355
|
+
|
|
356
|
+
// Install the shader validation guard so all createShaderModule calls
|
|
357
|
+
// route through getCompilationInfo() validation.
|
|
358
|
+
installShaderValidationGuard(this.device);
|
|
359
|
+
|
|
360
|
+
// Wrap device with GPU resource tracker for VRAM visibility
|
|
361
|
+
this.gpuResourceTracker = new GPUResourceTracker();
|
|
362
|
+
this.gpuResourceTracker.wrapDevice(this.device);
|
|
363
|
+
|
|
364
|
+
// GPU profiler
|
|
365
|
+
const adapterInfo = adapter?.info ?? null;
|
|
366
|
+
this.context = this.canvas.getContext("webgpu")!;
|
|
367
|
+
this.format = navigator.gpu.getPreferredCanvasFormat();
|
|
368
|
+
this.gpuProfiler = new GPUProfiler();
|
|
369
|
+
this.gpuProfiler.init(this.device, adapterInfo, this.format, 32);
|
|
370
|
+
log.info("GameRenderer", `GPU timer pool supported: ${this.gpuProfiler.isGpuTimerSupported()} features: ${Array.from(this.device.features).join(", ")}`);
|
|
371
|
+
|
|
372
|
+
// Device lost handler
|
|
373
|
+
this.device.lost.then((info: GPUDeviceLostInfo) => {
|
|
374
|
+
this.handleDeviceLost(info);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Configure surface — skipped when the surface was already configured
|
|
378
|
+
// by the device owner (native host) or the caller opted out. A borrowed
|
|
379
|
+
// host surface is never reconfigured: the host's usage set and
|
|
380
|
+
// pre-present readback hook are bound to its configure call.
|
|
381
|
+
if (!this.hostConfiguredSurface && (this.config.configureSurface ?? true)) {
|
|
382
|
+
this.context.configure({
|
|
383
|
+
device: this.device,
|
|
384
|
+
format: this.format,
|
|
385
|
+
alphaMode: "premultiplied",
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// UI input router — the active UI stack (html-ui via Blitz) replaces
|
|
390
|
+
// this with its own router when its module registers.
|
|
391
|
+
this.uiInputRouter = new UIInputRouter();
|
|
392
|
+
this.inputManager.setUIInputRouter(this.uiInputRouter);
|
|
393
|
+
|
|
394
|
+
// Telemetry
|
|
395
|
+
this.telemetryCollector = new TelemetryCollector(this.config.enableTelemetry ?? true);
|
|
396
|
+
|
|
397
|
+
// Canvas resize watcher
|
|
398
|
+
this.resizeWatcher = new CanvasResizeWatcher(this.canvas, this);
|
|
399
|
+
|
|
400
|
+
// Input listeners
|
|
401
|
+
this.inputManager.setupListeners();
|
|
402
|
+
|
|
403
|
+
// Renderer plugin host — owns renderer-thread plugins (camera
|
|
404
|
+
// controllers, gizmos, XR frame loops, OSR). Created after InputManager
|
|
405
|
+
// so the host's input bus coexists with the FPS/pointer-lock layer.
|
|
406
|
+
this.rendererModuleHost = new RendererModuleHost(this.canvas, {
|
|
407
|
+
getSurface: () => this.canvas,
|
|
408
|
+
getDevice: () => this.device!,
|
|
409
|
+
getFormat: () => this.format,
|
|
410
|
+
getGraph: () => this.frameGraph,
|
|
411
|
+
getSlotRegistry: () => this.frameGraph.getSlotRegistry(),
|
|
412
|
+
setOffscreenMode: (mode) => this.setOffscreenMode(mode),
|
|
413
|
+
setRenderTargetProvider: (provider) => this.setRenderTargetProvider(provider),
|
|
414
|
+
setRAFSource: (src, cancel) => {
|
|
415
|
+
if (src && cancel) this.setRAFSource(src, cancel);
|
|
416
|
+
else this.clearRAFSource();
|
|
417
|
+
},
|
|
418
|
+
setViewportCount: (count) => this.setViewportCount(count),
|
|
419
|
+
getUIInputRouter: () => this.uiInputRouter,
|
|
420
|
+
registerUiCompositor: (c) => {
|
|
421
|
+
this.uiCompositors.add(c);
|
|
422
|
+
return () => this.uiCompositors.delete(c);
|
|
423
|
+
},
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Initial viewport layout
|
|
427
|
+
this.updateViewports(1);
|
|
428
|
+
|
|
429
|
+
log.info("GameRenderer", "initialized");
|
|
430
|
+
return true;
|
|
431
|
+
} catch (err) {
|
|
432
|
+
log.error("GameRenderer", `Init failed: ${err}`);
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// --- Device acquisition + loss recovery ---
|
|
438
|
+
|
|
439
|
+
/** Adapter fallback chain: high-performance → low-power → any. */
|
|
440
|
+
private async requestAdapterWithFallback(): Promise<GPUAdapter | null> {
|
|
441
|
+
let adapter = await navigator.gpu.requestAdapter({
|
|
442
|
+
powerPreference: "high-performance",
|
|
443
|
+
});
|
|
444
|
+
if (!adapter) {
|
|
445
|
+
log.warn("GameRenderer", "No high-performance GPU adapter, trying low-power...");
|
|
446
|
+
adapter = await navigator.gpu.requestAdapter({
|
|
447
|
+
powerPreference: "low-power",
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (!adapter) {
|
|
451
|
+
log.warn("GameRenderer", "No low-power adapter, trying any...");
|
|
452
|
+
adapter = await navigator.gpu.requestAdapter({});
|
|
453
|
+
}
|
|
454
|
+
return adapter;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
private async requestDeviceFromAdapter(adapter: GPUAdapter): Promise<GPUDevice> {
|
|
458
|
+
// Request timestamp-query features for GPU-side per-pass timing
|
|
459
|
+
const requiredFeatures: GPUFeatureName[] = [];
|
|
460
|
+
if (adapter.features.has("timestamp-query")) {
|
|
461
|
+
requiredFeatures.push("timestamp-query");
|
|
462
|
+
}
|
|
463
|
+
return adapter.requestDevice({
|
|
464
|
+
requiredFeatures,
|
|
465
|
+
requiredLimits: this.buildRequiredLimits(adapter),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private recoveringDevice = false;
|
|
470
|
+
|
|
471
|
+
private handleDeviceLost(info: GPUDeviceLostInfo): void {
|
|
472
|
+
if (this.deviceLost || this.recoveringDevice) return; // already handled
|
|
473
|
+
this.deviceLost = true;
|
|
474
|
+
log.error("GameRenderer", `WebGPU device lost: ${info?.reason ?? "unknown"} — ${info?.message ?? ""}`);
|
|
475
|
+
try { this.callbacks.onDeviceLost?.(info); } catch (err) {
|
|
476
|
+
log.error("GameRenderer", `onDeviceLost callback threw: ${err}`);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (
|
|
480
|
+
(this.config.deviceLossRecovery ?? "auto") === "reload" ||
|
|
481
|
+
info?.reason === "destroyed" ||
|
|
482
|
+
this.hostConfiguredSurface
|
|
483
|
+
) {
|
|
484
|
+
// "destroyed" = device.destroy() was called deliberately — a new device
|
|
485
|
+
// can't help, the app is tearing down or opted out. A borrowed host
|
|
486
|
+
// device can't be recovered in place either: the host's surface
|
|
487
|
+
// configure and readback hook are bound to the dead device, and every
|
|
488
|
+
// other holder of it (bridge, devtools hosts) would still point at
|
|
489
|
+
// the old handle. Restart instead.
|
|
490
|
+
this.reloadForDeviceLoss();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
this.recoverDevice();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
private reloadForDeviceLoss(): void {
|
|
497
|
+
setTimeout(() => {
|
|
498
|
+
// Host already tearing down (host.destroy() destroys the device,
|
|
499
|
+
// which resolves .lost with reason "destroyed") — restarting now
|
|
500
|
+
// would respawn a session on top of a dead window.
|
|
501
|
+
if ((globalThis as any).__nativeHost?.destroyed) return;
|
|
502
|
+
// Native runtime: window.location.reload() is a no-op. Route through
|
|
503
|
+
// the host's first-class restart hook (detached self-respawn — the
|
|
504
|
+
// real reload equivalent), then fall back to a dialog + clean quit if
|
|
505
|
+
// the restart budget is exhausted.
|
|
506
|
+
const req = (globalThis as any).downdraft?.requestRestart;
|
|
507
|
+
if (typeof req === "function" && req("GPU device lost")) return;
|
|
508
|
+
const nativeWin = (globalThis as any).__nativeWindow;
|
|
509
|
+
if (nativeWin?.showMessageBox) {
|
|
510
|
+
try {
|
|
511
|
+
nativeWin.showMessageBox(
|
|
512
|
+
"GPU Device Lost",
|
|
513
|
+
"The GPU device was lost and could not be recovered. The application will now close.",
|
|
514
|
+
);
|
|
515
|
+
} catch { /* window may be gone */ }
|
|
516
|
+
try { nativeWin.requestQuit?.(); } catch { /* best-effort */ }
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
log.warn("GameRenderer", "Attempting page reload for GPU recovery...");
|
|
520
|
+
window.location.reload();
|
|
521
|
+
}, 2000);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Attempt in-place device recovery: re-acquire adapter+device, reconfigure
|
|
526
|
+
* the surface, re-prepare UI/profiler and every registered frame-graph
|
|
527
|
+
* pass, then recompile the graph (transient textures get fresh allocations
|
|
528
|
+
* on the new device). Falls back to a page reload on any failure.
|
|
529
|
+
*/
|
|
530
|
+
private async recoverDevice(): Promise<void> {
|
|
531
|
+
this.recoveringDevice = true;
|
|
532
|
+
try {
|
|
533
|
+
const adapter = await this.requestAdapterWithFallback();
|
|
534
|
+
if (!adapter) throw new Error("no GPU adapter after device loss");
|
|
535
|
+
const device = await this.requestDeviceFromAdapter(adapter);
|
|
536
|
+
device.lost.then((info) => this.handleDeviceLost(info));
|
|
537
|
+
this.device = device;
|
|
538
|
+
this.adapter = adapter;
|
|
539
|
+
|
|
540
|
+
installShaderValidationGuard(device);
|
|
541
|
+
this.gpuResourceTracker = new GPUResourceTracker();
|
|
542
|
+
this.gpuResourceTracker.wrapDevice(device);
|
|
543
|
+
|
|
544
|
+
this.context!.configure({
|
|
545
|
+
device,
|
|
546
|
+
format: this.format,
|
|
547
|
+
alphaMode: "premultiplied",
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const adapterInfo = adapter.info ?? null;
|
|
551
|
+
this.gpuProfiler?.init(device, adapterInfo, this.format, 32);
|
|
552
|
+
|
|
553
|
+
// Re-prepare every registered pass — pipelines/bind groups built on the
|
|
554
|
+
// dead device are invalid. Passes that can't recreate resources should
|
|
555
|
+
// throw here so we fall back to reload.
|
|
556
|
+
for (const pass of this.frameGraph.getPasses()) {
|
|
557
|
+
pass.prepare(device);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Force graph recompile so transient textures are re-allocated on the
|
|
561
|
+
// new device — pooled physical textures and cached views all belong to
|
|
562
|
+
// the dead device and must not be reused.
|
|
563
|
+
this.frameGraph.invalidatePhysicalResources();
|
|
564
|
+
this.graphCompiled = false;
|
|
565
|
+
|
|
566
|
+
await this.onDeviceRecovered(device);
|
|
567
|
+
await this.callbacks.onDeviceRecovered?.(device);
|
|
568
|
+
|
|
569
|
+
this.deviceLost = false;
|
|
570
|
+
log.warn("GameRenderer", "GPU device recovered — rendering resumed without reload");
|
|
571
|
+
} catch (err) {
|
|
572
|
+
log.error("GameRenderer", `Device recovery failed: ${err}`);
|
|
573
|
+
this.reloadForDeviceLoss();
|
|
574
|
+
} finally {
|
|
575
|
+
this.recoveringDevice = false;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Subclass hook — called during device-loss recovery after the engine has
|
|
581
|
+
* re-prepared its own resources and all registered passes. Recreate
|
|
582
|
+
* subclass-owned GPU resources here. Throwing aborts recovery → reload.
|
|
583
|
+
*/
|
|
584
|
+
protected async onDeviceRecovered(_device: GPUDevice): Promise<void> {}
|
|
585
|
+
|
|
586
|
+
// --- CanvasResizeHandler ---
|
|
587
|
+
|
|
588
|
+
private resolutionScale = 1;
|
|
589
|
+
private lastCssW = 0;
|
|
590
|
+
private lastCssH = 0;
|
|
591
|
+
private lastRawDpr = 1;
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Render-resolution scale [0.25..1]. Multiplies the effective DPR so the
|
|
595
|
+
* swap chain renders fewer pixels (performance) and the compositor
|
|
596
|
+
* upscales. Re-applies the last known canvas size immediately.
|
|
597
|
+
*/
|
|
598
|
+
setResolutionScale(scale: number): void {
|
|
599
|
+
this.resolutionScale = Math.max(0.25, Math.min(1, scale));
|
|
600
|
+
if (!getHostCapabilities().hasDom) {
|
|
601
|
+
// On the native host the swapchain extent must equal the window's
|
|
602
|
+
// real size — wgpu compares the configured size against the surface's
|
|
603
|
+
// currentExtent at acquire time and returns Outdated forever when
|
|
604
|
+
// they differ. Shrinking canvas.width/height would also corrupt
|
|
605
|
+
// clientWidth (UI layout + input coords). Resolution scaling needs
|
|
606
|
+
// an offscreen-render + upscale path to work here; for now it is a
|
|
607
|
+
// no-op rather than a brick.
|
|
608
|
+
if (this.resolutionScale < 1) {
|
|
609
|
+
log.warn("GameRenderer", "resolutionScale is not supported on the native surface (swapchain must match the window size) — ignoring");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (this.lastCssW > 0) this.onResize(this.lastCssW, this.lastCssH, this.lastRawDpr);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
getResolutionScale(): number {
|
|
616
|
+
return this.resolutionScale;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
onResize(cssWidth: number, cssHeight: number, dpr: number): void {
|
|
620
|
+
this.lastCssW = cssWidth;
|
|
621
|
+
this.lastCssH = cssHeight;
|
|
622
|
+
this.lastRawDpr = dpr;
|
|
623
|
+
// Render at the display's real DPR — resolutionScale is the opt-in
|
|
624
|
+
// performance knob when fill rate is a concern. On native the
|
|
625
|
+
// swapchain is window-sized (see setResolutionScale) so the scale is
|
|
626
|
+
// never applied to the backing store.
|
|
627
|
+
const cappedDpr = dpr * (getHostCapabilities().hasDom ? this.resolutionScale : 1);
|
|
628
|
+
this.dpr = cappedDpr;
|
|
629
|
+
const w = Math.round(cssWidth * cappedDpr);
|
|
630
|
+
const h = Math.round(cssHeight * cappedDpr);
|
|
631
|
+
if (this.canvas.width !== w) this.canvas.width = w;
|
|
632
|
+
if (this.canvas.height !== h) this.canvas.height = h;
|
|
633
|
+
this.updateViewports(this.viewportCount);
|
|
634
|
+
this.callbacks.onResize?.(cssWidth, cssHeight, dpr);
|
|
635
|
+
this.rendererModuleHost?.dispatchResize(cssWidth, cssHeight, dpr);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// --- Viewport management ---
|
|
639
|
+
|
|
640
|
+
setViewportCount(count: number): void {
|
|
641
|
+
this.updateViewports(count);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
getViewportCount(): number {
|
|
645
|
+
return this.viewportCount;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
getViewport(idx: number): ViewportRect | null {
|
|
649
|
+
return this.viewports[idx] ?? null;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
getViewports(): ViewportRect[] {
|
|
653
|
+
return this.viewports;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
private updateViewports(count: number): void {
|
|
657
|
+
this.viewportCount = count;
|
|
658
|
+
this.viewports = [];
|
|
659
|
+
const w = this.canvas.width;
|
|
660
|
+
const h = this.canvas.height;
|
|
661
|
+
|
|
662
|
+
if (count === 1) {
|
|
663
|
+
this.viewports.push({ x: 0, y: 0, w, h });
|
|
664
|
+
} else if (count === 2) {
|
|
665
|
+
this.viewports.push({ x: 0, y: 0, w: w / 2, h });
|
|
666
|
+
this.viewports.push({ x: w / 2, y: 0, w: w / 2, h });
|
|
667
|
+
} else if (count === 3) {
|
|
668
|
+
this.viewports.push({ x: 0, y: 0, w, h: h / 2 });
|
|
669
|
+
this.viewports.push({ x: 0, y: h / 2, w: w / 2, h: h / 2 });
|
|
670
|
+
this.viewports.push({ x: w / 2, y: h / 2, w: w / 2, h: h / 2 });
|
|
671
|
+
} else if (count >= 4) {
|
|
672
|
+
this.viewports.push({ x: 0, y: 0, w: w / 2, h: h / 2 });
|
|
673
|
+
this.viewports.push({ x: w / 2, y: 0, w: w / 2, h: h / 2 });
|
|
674
|
+
this.viewports.push({ x: 0, y: h / 2, w: w / 2, h: h / 2 });
|
|
675
|
+
this.viewports.push({ x: w / 2, y: h / 2, w: w / 2, h: h / 2 });
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// --- Frame rate limiter ---
|
|
680
|
+
|
|
681
|
+
setFrameRateLimit(refreshRate: number): void {
|
|
682
|
+
if (refreshRate > 0) {
|
|
683
|
+
this.targetFrameTime = 1000 / refreshRate;
|
|
684
|
+
this.updateLimiterState();
|
|
685
|
+
} else {
|
|
686
|
+
this.targetFrameTime = 0;
|
|
687
|
+
this.limiterActive = false;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
private updateLimiterState(): void {
|
|
692
|
+
if (this.targetFrameTime <= 0 || this.rafInterval <= 0) {
|
|
693
|
+
this.limiterActive = this.targetFrameTime > 0;
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
this.limiterActive = this.rafInterval < this.targetFrameTime * 0.85;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// --- Render loop ---
|
|
700
|
+
|
|
701
|
+
start(): void {
|
|
702
|
+
this.running = true;
|
|
703
|
+
this.lastTime = performance.now();
|
|
704
|
+
// Go through scheduleRender() rather than invoking render() inline — a
|
|
705
|
+
// pending rAF (loop already primed) would otherwise survive alongside the
|
|
706
|
+
// new registration and double the chain.
|
|
707
|
+
this.scheduleRender();
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
stop(): void {
|
|
711
|
+
this.running = false;
|
|
712
|
+
// cancelRaf only exists when a rAF source override (XR) is installed —
|
|
713
|
+
// fall back to the global so the pending callback is actually removed.
|
|
714
|
+
const cancel = this.cancelRaf ?? (typeof cancelAnimationFrame === "function" ? cancelAnimationFrame : null);
|
|
715
|
+
if (cancel && this.currentRafId) {
|
|
716
|
+
cancel(this.currentRafId);
|
|
717
|
+
this.currentRafId = 0;
|
|
718
|
+
}
|
|
719
|
+
this.rafPending = false;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** True while the render loop is started (frames are being scheduled). */
|
|
723
|
+
isRunning(): boolean {
|
|
724
|
+
return this.running;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** Render a single frame on demand — deterministic/test mode and native
|
|
728
|
+
* screenshot capture call this while the loop is stopped. Bypasses the
|
|
729
|
+
* frame limiter; safe to call with the loop running or stopped. */
|
|
730
|
+
renderOnce(): void {
|
|
731
|
+
if (!this.device || !this.context || this.deviceLost) return;
|
|
732
|
+
const savedLimiter = this.limiterActive;
|
|
733
|
+
this.limiterActive = false;
|
|
734
|
+
try {
|
|
735
|
+
this.prepareForcedFrame();
|
|
736
|
+
this.renderFrame();
|
|
737
|
+
} finally {
|
|
738
|
+
this.limiterActive = savedLimiter;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/** Subclass hook: clear internal dirty-tracking so renderOnce() actually
|
|
743
|
+
* draws. Renderers that skip unchanged frames (e.g. MiningRenderer's
|
|
744
|
+
* forceDirty check) override this to force the next frame out. */
|
|
745
|
+
protected prepareForcedFrame(): void {}
|
|
746
|
+
|
|
747
|
+
/** Schedule the next render() invocation. Centralized here so renderFrame()
|
|
748
|
+
* stays scheduling-free — renderOnce() and test paths call renderFrame()
|
|
749
|
+
* directly and must never grow a parallel loop. */
|
|
750
|
+
private scheduleRender(): void {
|
|
751
|
+
if (this.rafPending) return;
|
|
752
|
+
this.rafPending = true;
|
|
753
|
+
this.currentRafId = this.rafSource ? this.rafSource(this.render) : requestAnimationFrame(this.render);
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private render = (): void => {
|
|
757
|
+
this.rafPending = false;
|
|
758
|
+
this.currentRafId = 0;
|
|
759
|
+
if (!this.running) return;
|
|
760
|
+
if (!this.device || !this.context) {
|
|
761
|
+
// init() hasn't finished — keep the chain alive until it has.
|
|
762
|
+
this.scheduleRender();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Measure rAF interval (rolling average over 60 samples)
|
|
767
|
+
const rafNow = performance.now();
|
|
768
|
+
if (this.lastRafTime > 0) {
|
|
769
|
+
this.rafSum += rafNow - this.lastRafTime;
|
|
770
|
+
this.rafCount++;
|
|
771
|
+
if (this.rafCount >= 60) {
|
|
772
|
+
this.rafInterval = this.rafSum / this.rafCount;
|
|
773
|
+
this.rafSum = 0;
|
|
774
|
+
this.rafCount = 0;
|
|
775
|
+
this.updateLimiterState();
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
this.lastRafTime = rafNow;
|
|
779
|
+
|
|
780
|
+
if (this.deviceLost) {
|
|
781
|
+
// Keep the loop alive while the device is lost — handleDeviceLost()
|
|
782
|
+
// re-creates the device and clears this flag; returning without
|
|
783
|
+
// re-scheduling would permanently stop rendering after a transient
|
|
784
|
+
// GPU crash.
|
|
785
|
+
this.scheduleRender();
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
try {
|
|
790
|
+
this.renderFrame();
|
|
791
|
+
} catch (err) {
|
|
792
|
+
log.error("GameRenderer", `Render loop error: ${(err as Error).message}\n${(err as Error).stack}`);
|
|
793
|
+
if (this.device?.lost) {
|
|
794
|
+
this.device.lost.then((info: GPUDeviceLostInfo) => {
|
|
795
|
+
this.handleDeviceLost(info);
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
this.scheduleRender();
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
private renderFrame(): void {
|
|
803
|
+
const now = performance.now();
|
|
804
|
+
|
|
805
|
+
// Frame rate limiter: phase accumulator (skip during XR — XR drives its own cadence)
|
|
806
|
+
// Accumulate real elapsed time in frame units — NOT rafInterval ratios.
|
|
807
|
+
// The ratio variant starved on hosts with a faster-than-vsync rAF pump
|
|
808
|
+
// (native SDL loop ticks at ~µs cadence): updateLimiterState() resets
|
|
809
|
+
// frameAccum every 60 rAF samples, and 60 × rafInterval/targetFrameTime
|
|
810
|
+
// never reached 1, so present() was never called.
|
|
811
|
+
if (!this.renderTargetProvider && this.limiterActive && this.targetFrameTime > 0) {
|
|
812
|
+
if (this.lastLimiterTick === 0) this.lastLimiterTick = now;
|
|
813
|
+
this.frameAccum += (now - this.lastLimiterTick) / this.targetFrameTime;
|
|
814
|
+
this.lastLimiterTick = now;
|
|
815
|
+
if (this.frameAccum < 1) {
|
|
816
|
+
// render() owns re-scheduling — just skip the frame's work.
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
// Cap carry at one frame — a stale lastLimiterTick (limiter toggled
|
|
820
|
+
// off→on) would otherwise burst-render to drain a huge accumulator.
|
|
821
|
+
this.frameAccum = Math.min(this.frameAccum - 1, 1);
|
|
822
|
+
}
|
|
823
|
+
const dt = Math.min(0.1, (now - this.lastTime) / 1000);
|
|
824
|
+
this.lastTime = now;
|
|
825
|
+
this.elapsedTime += dt;
|
|
826
|
+
|
|
827
|
+
// FPS counter
|
|
828
|
+
this.frameCount++;
|
|
829
|
+
this.fpsTimer += dt;
|
|
830
|
+
if (this.fpsTimer >= 1) {
|
|
831
|
+
this.fps = this.frameCount;
|
|
832
|
+
this.frameCount = 0;
|
|
833
|
+
this.fpsTimer = 0;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Before frame callback (game-specific: camera updates, input processing)
|
|
837
|
+
this.callbacks.beforeFrame?.(dt, this.elapsedTime);
|
|
838
|
+
this.rendererModuleHost?.dispatchFrame("beforeFrame", dt, this.elapsedTime);
|
|
839
|
+
|
|
840
|
+
// Before viewports callback (game-specific: particle ticks, pre-viewport setup)
|
|
841
|
+
this.callbacks.beforeViewports?.(dt, this.elapsedTime);
|
|
842
|
+
this.rendererModuleHost?.dispatchFrame("beforeViewports", dt, this.elapsedTime);
|
|
843
|
+
|
|
844
|
+
// If an uncaptured GPU error has fired, skip all GPU work this frame to
|
|
845
|
+
// avoid the per-frame cascade of native "is invalid due to a previous
|
|
846
|
+
// error" validation warnings. Game logic callbacks still run so the
|
|
847
|
+
// simulation stays responsive. The flag is cleared by recovery code that
|
|
848
|
+
// recreates the invalid resources.
|
|
849
|
+
const gpuError = this.gpuProfiler?.hasUncapturedError() ?? false;
|
|
850
|
+
|
|
851
|
+
// XR render target provider beginFrame hook
|
|
852
|
+
if (this.renderTargetProvider && !gpuError) {
|
|
853
|
+
this.renderTargetProvider.beginFrame();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Render each viewport
|
|
857
|
+
const offscreen = this.offscreenMode;
|
|
858
|
+
const useOffscreen = offscreen && offscreen.type !== "none";
|
|
859
|
+
|
|
860
|
+
if (offscreen && useOffscreen && offscreen.ensureTargets && !gpuError) {
|
|
861
|
+
offscreen.ensureTargets(this.canvas.width, this.canvas.height);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Collect command buffers from all phases and submit once at the end
|
|
865
|
+
// (reduces CPU→GPU sync points from 3+ per frame to 1).
|
|
866
|
+
const frameCommandBuffers: GPUCommandBuffer[] = [];
|
|
867
|
+
if (!gpuError) {
|
|
868
|
+
// 2D mode with viewportCount=0: clear the canvas if a clearColor is
|
|
869
|
+
// configured, then let the afterFrame callback do custom rendering.
|
|
870
|
+
if (this.viewportCount === 0 && this.device && this.context && this.config.clearColor !== null) {
|
|
871
|
+
const clearTex = this.getSurfaceTexture();
|
|
872
|
+
if (clearTex) {
|
|
873
|
+
const clearEncoder = this.device.createCommandEncoder();
|
|
874
|
+
const clearPass = clearEncoder.beginRenderPass({
|
|
875
|
+
colorAttachments: [{
|
|
876
|
+
view: clearTex.createView(),
|
|
877
|
+
clearValue: this.config.clearColor ?? { r: 0, g: 0, b: 0, a: 1 },
|
|
878
|
+
loadOp: "clear" as GPULoadOp,
|
|
879
|
+
storeOp: "store" as GPUStoreOp,
|
|
880
|
+
}],
|
|
881
|
+
});
|
|
882
|
+
clearPass.end();
|
|
883
|
+
frameCommandBuffers.push(clearEncoder.finish());
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
for (let v = 0; v < this.viewportCount; v++) {
|
|
888
|
+
const cb = this.renderViewport(v, dt, offscreen);
|
|
889
|
+
if (cb) frameCommandBuffers.push(cb);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Apply postprocessing
|
|
893
|
+
if (offscreen && useOffscreen && offscreen.applyPostprocess) {
|
|
894
|
+
const canvasView = this.getSurfaceTexture()?.createView();
|
|
895
|
+
if (canvasView) {
|
|
896
|
+
const postEncoder = this.device!.createCommandEncoder();
|
|
897
|
+
offscreen.applyPostprocess(postEncoder, canvasView, this.canvas.width, this.canvas.height);
|
|
898
|
+
frameCommandBuffers.push(postEncoder.finish());
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// After viewports callback
|
|
904
|
+
this.callbacks.afterViewports?.(dt, this.elapsedTime);
|
|
905
|
+
this.rendererModuleHost?.dispatchFrame("afterViewports", dt, this.elapsedTime);
|
|
906
|
+
|
|
907
|
+
// Submit all command buffers for this frame in a single queue.submit() call
|
|
908
|
+
if (frameCommandBuffers.length > 0) {
|
|
909
|
+
this.device!.queue.submit(frameCommandBuffers);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Read GPU timer results asynchronously (1-frame latency).
|
|
913
|
+
// Must be called AFTER queue.submit() — readGpuTimers() calls mapAsync on
|
|
914
|
+
// the read buffer, and a mapped/mapping-pending buffer cannot be used in a
|
|
915
|
+
// submitted command buffer (the copy-to-readBuffer was encoded above).
|
|
916
|
+
if (this.gpuProfiler) {
|
|
917
|
+
this.gpuProfiler.readGpuTimers().then(() => {
|
|
918
|
+
// Results available for next frame
|
|
919
|
+
}).catch(() => {});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Record telemetry
|
|
923
|
+
if (this.telemetryCollector) {
|
|
924
|
+
this.telemetryCollector.recordFrame(dt * 1000);
|
|
925
|
+
this.telemetryCollector.recordDrawStats(this.frameDrawCalls, this.frameTriangles);
|
|
926
|
+
this.telemetryCollector.recordGraphSample(dt * 1000);
|
|
927
|
+
|
|
928
|
+
if (this.gpuProfiler) {
|
|
929
|
+
for (const timing of this.gpuProfiler.getPassTimings()) {
|
|
930
|
+
this.telemetryCollector.recordPassTiming(timing);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (this.gpuResourceTracker && now - this.lastResourceStatsTime > GameRenderer.RESOURCE_STATS_INTERVAL) {
|
|
935
|
+
this.lastResourceStatsTime = now;
|
|
936
|
+
const resStats = this.gpuResourceTracker.getStats();
|
|
937
|
+
this.telemetryCollector.recordResourceStats({
|
|
938
|
+
textureCount: resStats.textureCount,
|
|
939
|
+
bufferCount: resStats.bufferCount,
|
|
940
|
+
totalBytes: resStats.totalBytes,
|
|
941
|
+
textureBytes: resStats.textureBytes,
|
|
942
|
+
bufferBytes: resStats.bufferBytes,
|
|
943
|
+
resources: resStats.resources.map((r) => ({
|
|
944
|
+
id: r.id,
|
|
945
|
+
type: r.type,
|
|
946
|
+
label: r.label,
|
|
947
|
+
size: r.size,
|
|
948
|
+
callsite: r.callsite,
|
|
949
|
+
width: r.width,
|
|
950
|
+
height: r.height,
|
|
951
|
+
format: r.format,
|
|
952
|
+
})),
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
this.frameDrawCalls = 0;
|
|
957
|
+
this.frameTriangles = 0;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// XR render target provider endFrame hook (skip on GPU error)
|
|
961
|
+
if (!gpuError && this.renderTargetProvider && this.device) {
|
|
962
|
+
this.renderTargetProvider.endFrame(this.device.createCommandEncoder());
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// After frame callback
|
|
966
|
+
this.callbacks.afterFrame?.(dt, this.elapsedTime);
|
|
967
|
+
this.rendererModuleHost?.dispatchFrame("afterFrame", dt, this.elapsedTime);
|
|
968
|
+
|
|
969
|
+
// Composite registered screen-space UI (html-ui panels) on top of the
|
|
970
|
+
// final image (skip on GPU error to avoid cascade). This must run AFTER
|
|
971
|
+
// afterFrame — 2D games (viewportCount=0) do their custom rendering in
|
|
972
|
+
// the afterFrame callback, so drawing UI earlier would put it underneath
|
|
973
|
+
// their clear pass.
|
|
974
|
+
if (!gpuError && this.device && this.context) {
|
|
975
|
+
const compositors = [...this.uiCompositors].filter((c) => c.hasContent());
|
|
976
|
+
const uiCanvasView = compositors.length > 0
|
|
977
|
+
? this.getSurfaceTexture()?.createView()
|
|
978
|
+
: undefined;
|
|
979
|
+
if (uiCanvasView) {
|
|
980
|
+
const uiEncoder = this.device.createCommandEncoder();
|
|
981
|
+
const uiPass = uiEncoder.beginRenderPass({
|
|
982
|
+
colorAttachments: [{
|
|
983
|
+
view: uiCanvasView,
|
|
984
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
985
|
+
loadOp: "load" as GPULoadOp,
|
|
986
|
+
storeOp: "store" as GPUStoreOp,
|
|
987
|
+
}],
|
|
988
|
+
});
|
|
989
|
+
compositors.forEach((c) => { c.render(uiPass, this.canvas.width, this.canvas.height); });
|
|
990
|
+
uiPass.end();
|
|
991
|
+
this.device.queue.submit([uiEncoder.finish()]);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// Present the surface (native wgpu requires explicit presentation;
|
|
996
|
+
// in browsers this is automatic at the end of the frame). Must be last —
|
|
997
|
+
// all submissions targeting the surface texture happen above.
|
|
998
|
+
if (this.context && (this.context as any).present) {
|
|
999
|
+
(this.context as any).present();
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const __rfTotal = performance.now() - now;
|
|
1003
|
+
if (this.config.debugTimingWarnings && __rfTotal > 20) log.warn("GameRenderer", `renderFrame took ${__rfTotal.toFixed(1)}ms`);
|
|
1004
|
+
// NOTE: renderFrame() intentionally does NOT schedule the next frame —
|
|
1005
|
+
// render() owns the rAF chain so one-shot callers (renderOnce, MCP
|
|
1006
|
+
// screenshot capture) can't leak parallel loops.
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
/**
|
|
1010
|
+
* Acquire this frame's swapchain texture. May return null on the native
|
|
1011
|
+
* runtime while the surface can't produce a frame (in-flight resize,
|
|
1012
|
+
* occluded window, transient swapchain failure) — callers must skip the
|
|
1013
|
+
* canvas-targeted GPU work for that frame instead of crashing on
|
|
1014
|
+
* `null.createView()`.
|
|
1015
|
+
*/
|
|
1016
|
+
private getSurfaceTexture(): GPUTexture | null {
|
|
1017
|
+
try {
|
|
1018
|
+
return (this.context?.getCurrentTexture() as unknown as GPUTexture | null) ?? null;
|
|
1019
|
+
} catch {
|
|
1020
|
+
return null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
private renderViewport(viewportIdx: number, dt: number, offscreen: OffscreenMode | null): GPUCommandBuffer | null {
|
|
1025
|
+
if (!this.device || !this.context) return null;
|
|
1026
|
+
|
|
1027
|
+
const xrProvider = this.renderTargetProvider;
|
|
1028
|
+
const useOffscreen = !xrProvider && offscreen && offscreen.type !== "none";
|
|
1029
|
+
|
|
1030
|
+
const origViewport = xrProvider
|
|
1031
|
+
? xrProvider.getViewportRect(viewportIdx, this.canvas.width, this.canvas.height)
|
|
1032
|
+
: this.viewports[viewportIdx];
|
|
1033
|
+
if (!origViewport) return null;
|
|
1034
|
+
|
|
1035
|
+
const viewport = (useOffscreen && offscreen?.scaleViewport)
|
|
1036
|
+
? offscreen.scaleViewport(origViewport)
|
|
1037
|
+
: origViewport;
|
|
1038
|
+
|
|
1039
|
+
// Get camera info: viewport camera provider (XR) takes priority, then
|
|
1040
|
+
// game callback, then renderer plugin host's camera controller fallback.
|
|
1041
|
+
// The game callback is always called (even when the provider returns
|
|
1042
|
+
// non-null) so side effects like per-eye uniform updates still run.
|
|
1043
|
+
const providerInfo = this.rendererModuleHost?.getViewportCameraInfo(viewportIdx, dt, this.elapsedTime) ?? null;
|
|
1044
|
+
const gameInfo = this.callbacks.onViewport?.(viewportIdx, dt, this.elapsedTime) ?? null;
|
|
1045
|
+
let camInfo = providerInfo ?? gameInfo;
|
|
1046
|
+
if (!camInfo && this.rendererModuleHost) {
|
|
1047
|
+
const camState = this.rendererModuleHost.getCameraState(viewportIdx);
|
|
1048
|
+
if (camState) {
|
|
1049
|
+
camInfo = { camera: camState, viewport };
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
// 2D mode: if no camera info was provided, use a default orthographic
|
|
1053
|
+
// camera that covers the full viewport. This allows 2D-only renderers
|
|
1054
|
+
// (sand games, tile games) to use GameRenderer without a camera system.
|
|
1055
|
+
if (!camInfo && this.mode === "2d") {
|
|
1056
|
+
camInfo = {
|
|
1057
|
+
camera: {
|
|
1058
|
+
position: [0, 0, 1],
|
|
1059
|
+
target: [0, 0, 0],
|
|
1060
|
+
up: [0, 1, 0],
|
|
1061
|
+
fov: 90,
|
|
1062
|
+
near: 0.1,
|
|
1063
|
+
far: 100,
|
|
1064
|
+
aspect: viewport.w / viewport.h,
|
|
1065
|
+
},
|
|
1066
|
+
viewport,
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
if (!camInfo) return null;
|
|
1070
|
+
|
|
1071
|
+
const isFirst = viewportIdx === 0;
|
|
1072
|
+
const isLast = viewportIdx === this.viewportCount - 1;
|
|
1073
|
+
|
|
1074
|
+
// Determine color/depth views — XR provider takes precedence, then offscreen, then canvas
|
|
1075
|
+
const __ctStart = performance.now();
|
|
1076
|
+
const colorView = xrProvider
|
|
1077
|
+
? xrProvider.getColorView(viewportIdx)
|
|
1078
|
+
: (useOffscreen && offscreen)
|
|
1079
|
+
? (offscreen.type === "pixelation"
|
|
1080
|
+
? offscreen.getColorView()
|
|
1081
|
+
: offscreen.type === "postprocess"
|
|
1082
|
+
? (offscreen.getSceneColorView?.() ?? this.getSurfaceTexture()?.createView())
|
|
1083
|
+
: this.getSurfaceTexture()?.createView())
|
|
1084
|
+
: this.getSurfaceTexture()?.createView();
|
|
1085
|
+
const __ctMs = performance.now() - __ctStart;
|
|
1086
|
+
// Swapchain couldn't produce a frame (native resize in flight, occluded
|
|
1087
|
+
// window, transient failure) — skip this viewport's GPU work; game
|
|
1088
|
+
// callbacks already ran above and the loop stays alive for next frame.
|
|
1089
|
+
if (!colorView) return null;
|
|
1090
|
+
if (this.config.debugTimingWarnings && __ctMs > 20) log.warn("GameRenderer", `getCurrentTexture took ${__ctMs.toFixed(1)}ms`);
|
|
1091
|
+
|
|
1092
|
+
const depthView = xrProvider
|
|
1093
|
+
? xrProvider.getDepthView(viewportIdx, origViewport.w, origViewport.h)
|
|
1094
|
+
: (useOffscreen && offscreen)
|
|
1095
|
+
? (offscreen.type === "pixelation"
|
|
1096
|
+
? offscreen.getDepthView()
|
|
1097
|
+
: offscreen.type === "postprocess"
|
|
1098
|
+
? (offscreen.getSceneDepthView?.() ?? this.createDepthTexture(origViewport.w, origViewport.h))
|
|
1099
|
+
: this.createDepthTexture(origViewport.w, origViewport.h))
|
|
1100
|
+
: this.createDepthTexture(origViewport.w, origViewport.h);
|
|
1101
|
+
|
|
1102
|
+
// Ensure graph handles exist for the imported color/depth views.
|
|
1103
|
+
if (!this.colorHandle) {
|
|
1104
|
+
this.colorHandle = this.frameGraph.importTextureView("color", null);
|
|
1105
|
+
this.frameGraph.markDirty();
|
|
1106
|
+
}
|
|
1107
|
+
if (!this.depthHandle) {
|
|
1108
|
+
this.depthHandle = this.frameGraph.importTextureView("depth", null);
|
|
1109
|
+
this.frameGraph.markDirty();
|
|
1110
|
+
}
|
|
1111
|
+
this.frameGraph.setImportedTextureView(this.colorHandle, colorView);
|
|
1112
|
+
this.frameGraph.setImportedTextureView(this.depthHandle, depthView);
|
|
1113
|
+
|
|
1114
|
+
if (isFirst) {
|
|
1115
|
+
this.gpuProfiler!.beginFrame();
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
if (this.frameGraph.isDirty() || !this.graphCompiled) {
|
|
1119
|
+
this.frameGraph.compile(this.device, this.canvas.width, this.canvas.height);
|
|
1120
|
+
this.graphCompiled = true;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
const encoder = this.device.createCommandEncoder();
|
|
1124
|
+
|
|
1125
|
+
// Reuse a single RenderContext across viewports/frames — allocating the
|
|
1126
|
+
// ~35-field literal per viewport per frame was pure GC churn. Fields that
|
|
1127
|
+
// vary per viewport are reassigned below; the closures capture `this` and
|
|
1128
|
+
// are created once.
|
|
1129
|
+
if (!this.frameCtx) {
|
|
1130
|
+
this.frameCtx = {
|
|
1131
|
+
device: this.device,
|
|
1132
|
+
encoder,
|
|
1133
|
+
pass: null,
|
|
1134
|
+
camera: camInfo.camera,
|
|
1135
|
+
viewport,
|
|
1136
|
+
viewportIdx,
|
|
1137
|
+
viewportCount: this.viewportCount,
|
|
1138
|
+
dt,
|
|
1139
|
+
elapsedTime: this.elapsedTime,
|
|
1140
|
+
isFirstViewport: isFirst,
|
|
1141
|
+
isLastViewport: isLast,
|
|
1142
|
+
width: viewport.w,
|
|
1143
|
+
height: viewport.h,
|
|
1144
|
+
viewProj: undefined,
|
|
1145
|
+
invViewProj: undefined,
|
|
1146
|
+
prevViewProj: undefined,
|
|
1147
|
+
cameraPos: camInfo.camera.position,
|
|
1148
|
+
lightData: null,
|
|
1149
|
+
lightViewProj: undefined,
|
|
1150
|
+
mesh: null,
|
|
1151
|
+
modelMatrix: undefined,
|
|
1152
|
+
shadowsEnabled: false,
|
|
1153
|
+
bloomEnabled: false,
|
|
1154
|
+
shadowSampler: null,
|
|
1155
|
+
debugQueue: null,
|
|
1156
|
+
opaqueVertexBuffer: null,
|
|
1157
|
+
opaqueIndexBuffer: null,
|
|
1158
|
+
opaqueIndexCount: 0,
|
|
1159
|
+
opaqueIndexFormat: "uint32",
|
|
1160
|
+
getView: (h: TextureHandle) => this.frameGraph.getTextureView(h),
|
|
1161
|
+
getTexture: (h: TextureHandle) => this.frameGraph.getTexture(h),
|
|
1162
|
+
addDrawCalls: (n: number) => { this.frameDrawCalls += n; },
|
|
1163
|
+
addTriangles: (n: number) => { this.frameTriangles += n; },
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
const ctx = this.frameCtx;
|
|
1167
|
+
ctx.device = this.device;
|
|
1168
|
+
ctx.encoder = encoder;
|
|
1169
|
+
ctx.camera = camInfo.camera;
|
|
1170
|
+
ctx.viewport = viewport;
|
|
1171
|
+
ctx.viewportIdx = viewportIdx;
|
|
1172
|
+
ctx.viewportCount = this.viewportCount;
|
|
1173
|
+
ctx.dt = dt;
|
|
1174
|
+
ctx.elapsedTime = this.elapsedTime;
|
|
1175
|
+
ctx.isFirstViewport = isFirst;
|
|
1176
|
+
ctx.isLastViewport = isLast;
|
|
1177
|
+
ctx.width = viewport.w;
|
|
1178
|
+
ctx.height = viewport.h;
|
|
1179
|
+
ctx.cameraPos = camInfo.camera.position;
|
|
1180
|
+
|
|
1181
|
+
this.frameGraph.execute(ctx);
|
|
1182
|
+
|
|
1183
|
+
// Resolve GPU timestamp queries on first viewport.
|
|
1184
|
+
// NOTE: readGpuTimers() (which calls mapAsync on the read buffer) must NOT
|
|
1185
|
+
// be called here — the command buffer containing the copy-to-readBuffer
|
|
1186
|
+
// hasn't been submitted yet. Calling mapAsync before submit puts the buffer
|
|
1187
|
+
// in a mapped-pending state, and the subsequent queue.submit() fails with
|
|
1188
|
+
// "Buffer used in submit while mapped." readGpuTimers() is called after
|
|
1189
|
+
// submit in renderFrame().
|
|
1190
|
+
if (isFirst) {
|
|
1191
|
+
this.gpuProfiler!.resolveGpuTimers(encoder);
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
return encoder.finish();
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// --- Depth texture cache ---
|
|
1198
|
+
|
|
1199
|
+
private createDepthTexture(w: number, h: number): GPUTextureView {
|
|
1200
|
+
if (!this.device) throw new Error("No device");
|
|
1201
|
+
const key = `${w}x${h}`;
|
|
1202
|
+
let tex = this.depthTextures.get(key);
|
|
1203
|
+
if (tex) {
|
|
1204
|
+
// LRU: move to most-recently-used by re-inserting.
|
|
1205
|
+
this.depthTextures.delete(key);
|
|
1206
|
+
this.depthTextures.set(key, tex);
|
|
1207
|
+
} else {
|
|
1208
|
+
tex = this.device.createTexture({
|
|
1209
|
+
size: [w, h],
|
|
1210
|
+
format: this.depthFormat,
|
|
1211
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT,
|
|
1212
|
+
});
|
|
1213
|
+
this.depthTextures.set(key, tex);
|
|
1214
|
+
// Evict oldest entries if over cap.
|
|
1215
|
+
while (this.depthTextures.size > this.depthTextureCacheSize) {
|
|
1216
|
+
const oldestKey = this.depthTextures.keys().next().value;
|
|
1217
|
+
if (oldestKey === undefined) break;
|
|
1218
|
+
const oldest = this.depthTextures.get(oldestKey);
|
|
1219
|
+
this.depthTextures.delete(oldestKey);
|
|
1220
|
+
oldest?.destroy();
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return tex.createView();
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// --- UI ---
|
|
1227
|
+
|
|
1228
|
+
// --- Offscreen mode ---
|
|
1229
|
+
|
|
1230
|
+
setOffscreenMode(mode: OffscreenMode | null): void {
|
|
1231
|
+
this.offscreenMode = mode;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
getOffscreenMode(): OffscreenMode | null {
|
|
1235
|
+
return this.offscreenMode;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// --- XR render target provider ---
|
|
1239
|
+
|
|
1240
|
+
setRenderTargetProvider(provider: RenderTargetProvider | null): void {
|
|
1241
|
+
this.renderTargetProvider = provider;
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
getRenderTargetProvider(): RenderTargetProvider | null {
|
|
1245
|
+
return this.renderTargetProvider;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// --- rAF source override (for XR sessions) ---
|
|
1249
|
+
|
|
1250
|
+
setRAFSource(request: RAFSource, cancel: CancelRAF): void {
|
|
1251
|
+
this.rafSource = request;
|
|
1252
|
+
this.cancelRaf = cancel;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
clearRAFSource(): void {
|
|
1256
|
+
this.rafSource = null;
|
|
1257
|
+
this.cancelRaf = null;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// --- Callbacks ---
|
|
1261
|
+
|
|
1262
|
+
setCallbacks(callbacks: FrameCallbacks): void {
|
|
1263
|
+
this.callbacks = callbacks;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/** Get the current frame callbacks (for wrapping/extending). */
|
|
1267
|
+
getCallbacks(): FrameCallbacks {
|
|
1268
|
+
return this.callbacks;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// --- Renderer plugins ---
|
|
1272
|
+
|
|
1273
|
+
useRendererModule(plugin: RendererModule): void {
|
|
1274
|
+
if (!this.rendererModuleHost) {
|
|
1275
|
+
throw new Error("RendererModuleHost not initialized — call init() first");
|
|
1276
|
+
}
|
|
1277
|
+
this.rendererModuleHost.registerModule(plugin);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
getRendererModuleHost(): RendererModuleHost | null {
|
|
1281
|
+
return this.rendererModuleHost;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
/**
|
|
1285
|
+
* Composite registered screen-space UI (html-ui panels via
|
|
1286
|
+
* ctx.registerUiCompositor) onto the current surface texture.
|
|
1287
|
+
*
|
|
1288
|
+
* Renderers with a custom drawFrame()/present path that bypass
|
|
1289
|
+
* renderFrame() must call this at end-of-frame — after post-processing
|
|
1290
|
+
* lands on the surface, before present — or module-registered UI will
|
|
1291
|
+
* never be drawn. renderFrame() itself uses the equivalent block inline;
|
|
1292
|
+
* this helper only runs the compositors.
|
|
1293
|
+
*/
|
|
1294
|
+
protected renderScreenUiCompositors(): void {
|
|
1295
|
+
const compositors = [...this.uiCompositors].filter((c) => c.hasContent());
|
|
1296
|
+
if (compositors.length === 0 || !this.device || !this.context) return;
|
|
1297
|
+
const canvasView = this.getSurfaceTexture()?.createView();
|
|
1298
|
+
if (!canvasView) return;
|
|
1299
|
+
const uiEncoder = this.device.createCommandEncoder();
|
|
1300
|
+
const uiPass = uiEncoder.beginRenderPass({
|
|
1301
|
+
colorAttachments: [{
|
|
1302
|
+
view: canvasView,
|
|
1303
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
1304
|
+
loadOp: "load" as GPULoadOp,
|
|
1305
|
+
storeOp: "store" as GPUStoreOp,
|
|
1306
|
+
}],
|
|
1307
|
+
});
|
|
1308
|
+
compositors.forEach((c) => { c.render(uiPass, this.canvas.width, this.canvas.height); });
|
|
1309
|
+
uiPass.end();
|
|
1310
|
+
this.device.queue.submit([uiEncoder.finish()]);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// --- Getters ---
|
|
1314
|
+
|
|
1315
|
+
getDevice(): GPUDevice | null {
|
|
1316
|
+
return this.device;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
getAdapter(): GPUAdapter | null {
|
|
1320
|
+
return this.adapter;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
getContext(): RenderSurfaceContext | null {
|
|
1324
|
+
return this.context;
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
getFormat(): GPUTextureFormat {
|
|
1328
|
+
return this.format;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
getDepthFormat(): GPUTextureFormat {
|
|
1332
|
+
return this.depthFormat;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
getMSAASampleCount(): number {
|
|
1336
|
+
return this.msaaSampleCount;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
/** The render surface this renderer draws into. Canonical accessor. */
|
|
1340
|
+
getSurface(): RenderSurface {
|
|
1341
|
+
return this.canvas;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
/** @deprecated Use getSurface() — the surface is not necessarily a DOM canvas. */
|
|
1345
|
+
getCanvas(): RenderSurface {
|
|
1346
|
+
return this.canvas;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
getCanvasWidth(): number {
|
|
1350
|
+
return this.canvas.width;
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
getCanvasHeight(): number {
|
|
1354
|
+
return this.canvas.height;
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
getFPS(): number {
|
|
1358
|
+
return this.fps;
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
getElapsedTime(): number {
|
|
1362
|
+
return this.elapsedTime;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
getInputManager(): InputManager {
|
|
1366
|
+
return this.inputManager;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
getGraph(): FrameGraph {
|
|
1370
|
+
return this.frameGraph;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
getSlotRegistry(): SlotRegistry {
|
|
1374
|
+
return this.frameGraph.getSlotRegistry();
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
getUIInputRouter(): UIInputRouter | null {
|
|
1378
|
+
return this.uiInputRouter;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
getTelemetryCollector(): TelemetryCollector | null {
|
|
1382
|
+
return this.telemetryCollector;
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
getGPUProfiler(): GPUProfiler | null {
|
|
1386
|
+
return this.gpuProfiler;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
getFrameGraph(): FrameGraphData | null {
|
|
1390
|
+
if (!this.gpuProfiler) return null;
|
|
1391
|
+
const passTimings = this.gpuProfiler.getPassTimings();
|
|
1392
|
+
const ppInfo = this.callbacks.getPostProcessInfo?.() ?? {
|
|
1393
|
+
pixelationEnabled: false, pixelSize: 4, postProcessEffects: [],
|
|
1394
|
+
};
|
|
1395
|
+
const passNames = this.frameGraph.getPassOrder();
|
|
1396
|
+
return GPUProfiler.buildFrameGraphData(passTimings, ppInfo, passNames);
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
getGPUResourceTracker(): GPUResourceTracker | null {
|
|
1400
|
+
return this.gpuResourceTracker;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
getGPUInfo(): GPUInfo | null {
|
|
1404
|
+
if (!this.gpuProfiler) return null;
|
|
1405
|
+
return this.gpuProfiler.getGPUInfo(this.canvas, this.msaaSampleCount);
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
getAdapterInfo(): GPUAdapterInfoData | null {
|
|
1409
|
+
return this.gpuProfiler?.getAdapterInfo() ?? null;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
getGPUErrors(): Array<{ timestamp: number; message: string; label?: string }> {
|
|
1413
|
+
return this.gpuProfiler?.getGPUErrors() ?? [];
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
clearGPUErrors(): void {
|
|
1417
|
+
this.gpuProfiler?.clearGPUErrors();
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
getDpr(): number {
|
|
1421
|
+
return this.dpr;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
// --- Frame stats (for game passes to update) ---
|
|
1425
|
+
|
|
1426
|
+
addDrawCalls(n: number): void {
|
|
1427
|
+
this.frameDrawCalls += n;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
addTriangles(n: number): void {
|
|
1431
|
+
this.frameTriangles += n;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// --- Destroy ---
|
|
1435
|
+
|
|
1436
|
+
destroy(): void {
|
|
1437
|
+
this.running = false;
|
|
1438
|
+
this.resizeWatcher?.destroy();
|
|
1439
|
+
this.resizeWatcher = null;
|
|
1440
|
+
this.rendererModuleHost?.disposeAll();
|
|
1441
|
+
this.rendererModuleHost = null;
|
|
1442
|
+
this.inputManager.destroy();
|
|
1443
|
+
this.gpuProfiler?.destroy();
|
|
1444
|
+
this.gpuProfiler = null;
|
|
1445
|
+
this.telemetryCollector = null;
|
|
1446
|
+
this.device = null;
|
|
1447
|
+
for (const tex of this.depthTextures.values()) {
|
|
1448
|
+
tex.destroy();
|
|
1449
|
+
}
|
|
1450
|
+
this.depthTextures.clear();
|
|
1451
|
+
}
|
|
1452
|
+
}
|