Utils Module Architecture
The Utils module is the shared foundation for Visionary. Instead of being a single-purpose math library, it is divided into four cooperating subsystems that feed the renderer, managers, and IO pipelines:
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ (Renderers · Managers · IO · Controllers · Diagnostics) │
├─────────────────────────────────────────────────────────────┤
│ Utils Module │
│ ┌────────────┬──────────────┬──────────────┬─────────────┐ │
│ │ Math Core │ Data Layout │ GPU Runtime │ Renderer Ops│ │
│ │ (vec/aabb) │ (float16/SH) │ (read/profil)│ (env/init) │ │
│ └────────────┴──────────────┴──────────────┴─────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ gl-matrix · WebGPU · three/webgpu │
└─────────────────────────────────────────────────────────────┘
Architectural Principles
- Single Responsibility – each file addresses exactly one family of problems (e.g.,
half.tsowns every float16 concern). - Allocation Awareness – hot paths reuse typed arrays and staging buffers to stay GC-friendly.
- Immutable Inputs – math helpers (
Aabb,planeFromPoints, camera math) clone or return new values to avoid upstream state leaks. - Fail-Fast Debuggability – renderer and GPU helpers perform defensive checks (device availability, buffer usage flags, timestamp feature detection) and provide explicit logs to shorten feedback loops.
Subsystems
1. Math Core
Components: aabb.ts, camera-math.ts, transforms.ts, vector-math.ts
Aabbencapsulates min/max bounds and provides read-onlycenter()/radius()helpers. Internally it clones vectors withvec3.cloneto guarantee immutability.- Camera math functions implement the pinhole camera model (
fov = 2 * atan(pixels / (2 * focal))) and live in their own module so they can be tree-shaken into both IO and runtime controllers. VIEWPORT_Y_FLIPis precomputed once to prevent repeatedmat4.fromValuescalls inside render loops.vector-mathexposes simple tuple-based operations andplaneFromPoints. The plane fitter relies on a covariance build + power-iteration eigenvector finder; it can optionally enforce an upward normal to match UI expectations.
Design goals:
- zero shared state,
- always numeric-safe (returns NaN triplets only when a vector cannot be normalized), and
- easily portable to workers if heavy preprocessing ever moves off the main thread.
2. Data Layout & Float16
Components: half.ts, relevant exports in gpu.ts
f32_to_f16/f16_to_f32wrap an IEEE‑754 compliant conversion pipeline. Features such as configurable rounding, FTZ, overflow saturation, canonical NaN, and legacy exponent cutoffs let us mimic historical datasets when validating regressions.packF16Array/unpackF16Arrayare batch helpers used when loading ONNX weights or streaming Gaussian parameters.makeCopySH_PackedF16repacks SH coefficients from channel-major source properties (f_dc_*,f_rest_*) into the shader-friendly[R0,G0,B0, R1,G1,B1, ...]order. It always writes 48 half values (24u32) so WGSL structs can assume constant strides no matter which SH degree is used.shNumCoefficients/shDegreeFromNumCoeffsallow managers to validate payload metadata before attempting to allocate GPU buffers.
Data Flow:
PLY/ONNX row → float32 math (sigmoid/exp/cov) → packF16Array/makeCopySH → Uint32Array buffer → GPU upload
The conversion subsystem is intentionally stateless; the IO module orchestrates it, but Utils guarantees the math and layout are consistent across every importer.
3. GPU Runtime & Instrumentation
Components: gpu.ts, debug-gpu-buffers.ts
align4/align8apply bitwise rounding to satisfy WebGPU copy & map constraints. They are reused byreadWholeBufferas well as ONNX upload helpers.readWholeBufferperforms an end-to-end readback: create staging buffer → copy → wait → map → slice requested bytes → destroy staging buffer. Optionalqueue.onSubmittedWorkDone()calls are guarded so it runs on browsers that lack the API.GPUStopwatchwraps timestamp query sets. It enforces one label per measurement, prevents capacity overflow, and resolves queries into its own buffer before mapping. All timings are returned in nanoseconds.- Gaussian math helpers (
buildCov,sigmoid) live here because they run immediately before GPU uploads. debug-gpu-buffers.tsadds higher-level diagnostics: reading ONNX count buffers, comparing values across buffers, and tracing the entire count pipeline. These helpers are safe to call in production builds because they fence GPU queues and destroy temporary buffers after use.createShaderDebugBuffer/readShaderDebugBuffergive shader authors a sanctioned pathway to dump intermediate compute results back to JS for inspection.
4. Renderer & Environment Operations
Components: env-map-helper.ts, renderer-init-helper.ts
EnvMapHelpermanages everything related to HDR environments: loading.hdrfiles viaRGBELoader, generating PMREMs with WebGPU-compatiblePMREMGenerator, synchronizing tone-mapping/exposure with an existing renderer, and updating scenes using WebGPU’supdateEnvironmenthook when available. Every step validates renderer backend/device availability because build outputs may asynchronously hydrate Three.js internals.RendererInitHelperis the orchestrator that clones renderer state while ensuring GPU resources are not shared. It exposes:RendererInitOptionsfor passing a source renderer, original HDR texture, desired size, and fallback HDR URLs.initializeRendererwhich (1) copies config from a source renderer or applies defaults, (2) enforces size/pixel ratio, (3) decides whether to reuse an existing environment or load the fallback HDR, (4) creates a fresh PMREM texture per renderer, and (5) updates renderer + scene references.isRendererInitializedfor guard clauses before performing expensive operations (e.g., screenshot capture).- Internally the helper splits responsibilities: configuration copying (
setupClearColor, tone mapping, color space, lighting flags), environment sourcing (setupEnvironmentFromSource,setupDefaultEnvironment), and renderer updates. This decomposition keeps logic composable—callers can skip certain steps if they only need environment cloning.
Renderer Resource Rule:
Configuration (tone mapping, clear color) → shared via copy
GPU resources (PMREM textures, buffers) → re-created per renderer
Effects/passes → recreated by caller
Key Data Flows
Environment Bootstrap
sourceRenderer? ──► setupRendererConfig ──► renderer
│
└─ has scene.environment?
│ yes no
▼ ▼
setupEnvironmentFromSource setupDefaultEnvironment
│ │
originalTexture? ──► createEnvironmentMap ◄─ fallback HDR
│
▼
scene.environment / background updated
ONNX Count Debug Pipeline
ONNX Output Buffer ──► readONNXCountBuffer (u32)
│
▼
ModelParams Buffer ──► readModelParamsNumPoints (u32)
│
▼
Shader Storage ──► compareBufferValues / readShaderDebugBuffer
This pipeline gives immediate visibility into where a mismatch first occurs, allowing engineers to distinguish ONNX inference failures from GPU copy timing issues.
Memory & Reliability Strategies
- View Reuse –
half.tsmaintains oneFloat32Array/Uint32Arraypair for all conversions;makeCopySH_PackedF16packs into a temporaryUint16Array(48)per point to keep code straightforward yet predictable. - Explicit Cleanup – every staging buffer (
readWholeBuffer, debug helpers) is explicitly unmapped/destroyed; PMREM generators are disposed as soon as an environment texture is produced. - Input Validation – renderer helpers bail early if the backend or device is missing, and GPU debug helpers refuse to start when buffers are undefined.
- Deterministic Layouts – SH packing pads to a constant stride, and float16 packing exposes the
wordsPerPointmetadata so downstream GPU structs never guess.
Integration Points
- Managers & Controllers: call renderer helpers to clone views for thumbnails, re-use math utilities for camera constraints, and rely on
GPUStopwatchfor per-stage timing overlays. - IO Module: owns file parsing but delegates every float16/SH conversion to Utils to ensure ONNX, KSplat, and PLY feeds produce identical GPU payloads.
- Renderer: consumes
Aabb, covariance math, and transform constants inside the Three.js/WebGPU pipeline. - Diagnostics & QA: run
debugCountPipelineand shader debug buffers during field testing without building custom scripts.
By isolating these responsibilities while keeping the APIs tiny, the Utils module stays stable even as Visionary evolves—new formats or renderer modes can reuse the same primitives without duplicating brittle math or GPU code.