import type { Context } from "./engine_setup.js"; /** Quality preset for Gaussian Splat rendering/streaming. `"auto"` resolves per device * (mobile → `"low"`, otherwise `"high"`). See {@link resolveGaussianSplatQuality}. */ export type GaussianSplatQuality = "auto" | "high" | "medium" | "low"; /** Concrete Spark settings a {@link GaussianSplatQuality} resolves to. */ export type ResolvedGaussianSplatQuality = { /** Spherical-harmonics bands to fetch/keep (0-3). SH data dominates splat decode * cost, memory and bandwidth — 0 drops view-dependent color but is a step change * in CPU/memory load. */ maxSh: number; /** Minimum milliseconds between splat re-sorts. Spark's own default is 0 * (sort back-to-back whenever the view moved ≥1mm/~2.5°), which keeps a worker * core saturated during any interaction. */ minSortIntervalMs: number; /** Parallel chunk fetch/decode workers while paging (each can saturate a core). */ fetchers: number; /** Spark lodRenderScale — minimum on-screen pixel size for LOD splat selection. * HIGHER is coarser/cheaper: 1 selects splats down to 1px, values up to ~5 are * "often indistinguishable" per Spark's docs while skipping sub-pixel splats. */ lodRenderScale: number; /** Multiplier on Spark's per-device LOD splat budget (`defaultSplatTarget`: * Android 1M, iOS 1.5M, Quest 500k, desktop 2.5M). Lower is cheaper. */ lodSplatScale: number; /** Maximum standard deviations of the Gaussian to rasterize. Rendered quad AREA — and * with it splat fill cost — scales with the square: √8 (Spark default) → √4 halves * splat overdraw. Spark documents √4..√9 as acceptable. */ maxStdDev: number; /** Maximum on-screen pixel radius per splat (Spark default 512). Caps the worst * near-camera overdraw where single splats cover huge screen regions. */ maxPixelRadius: number; /** Spark cone-foveation detail scale at the edge of the foveation cone (default 0.4). * Lower = coarser splats OFF-CENTER, freeing the shared LOD splat budget for the * visible cone — on a phone's ~60-75° AR view the no-foveation cone (90°) covers the * screen, so this costs little visible quality. */ coneFoveate: number; /** Spark cone-foveation detail scale BEHIND the viewer (default 0.2). Lower = coarser. */ behindFoveate: number; /** Framebuffer scale for XR sessions (1 = native XR resolution). Splat content is * soft and tolerates resolution reduction well; fill cost drops with the square. * Applied as a DEFAULT for the next session start (WebXR cannot change it * mid-session) — an explicit {@link NeedleXRSession.framebufferScaleFactor} wins. */ xrFramebufferScale: number; /** Cap on Spark's resident paged-splat pool (0 = Spark's device default: iOS 6.3M, * other mobile 8.4M, desktop 16.8M splats). Bounds streaming MEMORY growth — zooming * into a large paged scene otherwise pages splats in until the tab dies (observed: * iOS Safari page crash on zoom). Only effective before the shared pager is created * (applied at renderer creation; runtime quality changes cannot shrink the pool). */ maxPagedSplats: number; /** Minimum peak alpha for a splat to render at all — Spark culls whole splats below * it in the VERTEX stage (the quad never rasterizes). Spark's default (0.5/255) * culls nothing; raising the floor a few /255 removes the large faint "fog" splats * that cost maximal overdraw for minimal visual contribution. Splat-only fill * lever, used by the deeper adaptation levels. */ minAlpha: number; /** Multiplier on {@link Context.resolutionScaleFactor} — the LAST-RESORT fill * lever, used only by the single deepest adaptation level after every splat-local * knob (LOD, overdraw caps, foveation, alpha floor) is exhausted, and like every * step it must verifiably buy fps or it reverts. Presets keep 1. */ resolutionScale: number; /** XR frame-rate cap (0 = uncapped). AR splat rendering pins the GPU and heats * phones within a minute; running at 30 roughly halves the power draw. Applied * through {@link Context.xrFrameRateLimit} → WebXR updateTargetFrameRate where * the runtime supports it (never by engine-side frame skipping — that flickers * on runtimes without a reprojecting compositor). */ xrMaxFps: number; }; /** Resolves a {@link GaussianSplatQuality} to concrete Spark settings. * @param isMobile overrides device detection (defaults to {@link DeviceUtilities.isMobileDevice}) */ export declare function resolveGaussianSplatQuality(quality: GaussianSplatQuality, isMobile?: boolean): ResolvedGaussianSplatQuality; /** Applies a quality preset to the context's SparkRenderer (shared per scene — the last * applied quality wins) and, when a mesh is provided, to that mesh's pager (per-mesh: * SH bands, fetcher count). Safe to call again at runtime, e.g. when the * {@link GaussianSplat} component's `quality` changes. * * `"auto"` additionally enables fps-driven ADAPTATION: the renderer-level LOD settings * degrade in steps while the measured frame rate stays below target (and recover when * it is comfortably above) — see {@link adaptGaussianSplatQuality}. An explicit * `"low"`/`"medium"`/`"high"` is fixed and disables adaptation. */ export declare function applyGaussianSplatQuality(context: Context, mesh: object | null, quality: GaussianSplatQuality): ResolvedGaussianSplatQuality; /** Highest adaptation level (most degraded). */ export declare const maxGaussianSplatAdaptationLevel: number; /** Returns `preset` degraded to the given adaptation `level` (0 = unchanged). Levels * coarsen the LOD selection (`lodRenderScale` — Spark documents values up to ~5 as often * indistinguishable), shrink the LOD splat budget (`lodSplatScale`) and, at higher * levels, tighten the overdraw caps. Pure — does not touch any renderer. */ export declare function adaptGaussianSplatQuality(preset: ResolvedGaussianSplatQuality, level: number): ResolvedGaussianSplatQuality; /** Read-only view of the context's current splat quality state, or `null` if no splat * content has been loaded: the applied quality, the current adaptation level and the * effective (adapted) renderer settings. */ export declare function getGaussianSplatQualityInfo(context: Context): { quality: GaussianSplatQuality; adaptationLevel: number; effective: ResolvedGaussianSplatQuality; } | null; /** One adaptation step. Exposed for tests (the fps sample is injectable there — in the * engine loop it is fed from `context.time.smoothedFps`); use * {@link getGaussianSplatQualityInfo} to observe the result. * * Design: degradation must EARN ITS KEEP. Splat scenes are frequently limited by * something LOD density cannot fix — an AR compositor pacing to 30 Hz, fill * rate, the sort worker — and in that situation an fps-only controller ratchets to * maximum degradation while the frame rate stays exactly as bad, ending up ugly AND * sluggish (observed on a Pixel 9 in AR; PlayCanvas at the same fps looks better * simply because it keeps full splat detail). So every degradation step is verified * against the fps it was supposed to buy, reverted if it bought nothing, and further * degradation is backed off — converging on full quality when quality is not the * problem. * @internal */ export declare function __internalStepGaussianSplatAdaptation(context: Context, sample: { smoothedFps: number; targetFps: number; isInXR: boolean; streaming?: boolean; }, now: number): void; /** Session-start XR framebuffer scale suggested by the splat quality system (undefined * until splat content set a quality). Consumed by NeedleXRSession when no explicit * `NeedleXRSession.framebufferScaleFactor` is set. * @internal */ export declare function getSystemXRFramebufferScale(): number | undefined; /** The dynamic resolution multiplier currently applied by the splat adaptation * (1 = none). Consumed by NeedleXRSession at session start so a session that begins * while the adaptation is at a resolution-reducing level starts reduced right away. * @internal */ export declare function getSystemXRDynamicResolutionScale(): number; /** * Register the engine's built-in custom model loaders (currently the Gaussian * Splat loader, backed by Spark). * * Called from {@link initNeedleLoader} rather than run as a bare module * side-effect. A side-effect-only `import "./engine_loaders.custom.js"` gets * tree-shaken away when an app bundles the engine — this file is not listed in * the package's `sideEffects` — which silently disabled splat loading (a * dropped `.ply` fell back to the GLTFLoader). A called export cannot be * eliminated. Idempotent, so it is safe to call on every engine init. * @internal */ export declare function registerBuiltinCustomLoaders(): void;