Skip to content

Design Philosophy

Galavi is a WebGPU visualization library for large, multi-dimensional scientific data. It exists because the existing options in this domain force a choice between two compromises: a viewer-application with a closed scene model, or a low-level rendering kit with no opinions about state. Galavi takes a third path — a serializable scene state model paired with a small set of building blocks and a registry-based extension API — and builds a 3D-native, tiled, WebGPU pipeline on top of it.

This page describes the design and the motivations behind it. It is intentionally light on implementation detail; the Architecture Notes page covers the wiring.

Why a new library

State as a first-class citizen

The scene State — physical space, the ordered list of layers, the camera, exploration parameters — is a single serializable object. Everything that renders, interacts, or persists reads from and writes to that object. There is no hidden viewer state living inside a canvas or a view component.

This is the core differentiator from napari-style architectures, where state is owned by the viewer process, and from neuroglancer-style URL-state, which couples state shape to a specific application.

A serializable, centralized state model gives us, in the same primitive:

  • Multi-view scenes. Two views over the same dataset stay coherent because they read the same camera and the same layer list — not because they message each other.
  • Persistence and replay. Save a scene, restore it, diff it, snapshot it. State is the document.
  • A foundation for collaborative editing. Synchronizing a scene between peers is, at the model layer, the same problem as synchronizing it between two local views. (Galavi v1 ships local multi-view sync. Official transport plugins for remote peers are planned for v2.)

A library, not an application

Galavi is a library with a small, registry-based extension API. There are four building-block kinds — layers, controls, overlays, views — and each kind has a single registration entry point. Built-in implementations and third-party plugins go through the same path; nothing in the core is privileged.

The intent is that domain-specific functionality lives in plugins that sit outside the core. The first-party tile sources follow the same pattern: OME-Zarr support ships as the official @galavi/ome-zarr-adapter plugin rather than baked into the library, so the core stays generic over chunked-pyramid sources.

3D-native

Galavi treats true 3D as the default case, not a mode bolted onto a 2D viewer.

  • 3D layer types (volume, surface, points, vectors, tracks, segmentation, …) are first-class.
  • 3D navigation (orbit, fly) is built in, with view-owned input routing so that the active camera mode dictates which control runs.
  • 2D projections — slice views and a navigator view — are derived projections of the same shared 3D camera, not a separate world.

Two- and three-dimensional views co-exist because they share the camera, not because each maintains a synchronized copy.

Built for large tiled data

Scientific datasets routinely exceed memory and bandwidth budgets. Galavi's tile system is built into the core:

  • A generic chunked-pyramid contract — arbitrary source.fetch(coord) adapters, pyramid-level selection, request coalescing, eviction — that any data source can implement.
  • Tile machinery is shared by both volumetric and slice rendering, with a single planner producing the per-frame tile set.

Applications consume large data through a layer config and a source adapter. They do not assemble tile pools or manage GPU residency themselves.

WebGPU, wrapped

Galavi owns the WebGPU pipeline so applications and plugin authors do not have to. Device init, bind groups, scene uniforms, model matrices, color attachments — none of this is exposed at the application boundary. What is exposed is a declarative config: layers describe what they want bound; the pipeline does the binding.

The wrapping is intentionally thin enough that advanced configuration — appearance presets, colormaps, channel mixing, LOD biasing, custom shaders for registered layer types — remains expressive. Defaults exist so that a working scene needs only a data source; configuration exists so that a real application is not boxed in.

Separation of concerns

Two separations carry most of the architecture's weight:

  1. State vs Views. State is the portable scene; Views are local presentation — which layers a canvas renders, which controls it owns, which overlays sit on top. Two clients can render the same State with entirely different layouts.
  2. State Flow vs Render Flow. Every state mutation follows one path: input → view action → controls → view-level clamp → commit → notify → render. Controls are pure-by-construction — they are reducers (action, state) → state that return a fresh state object on change and the same reference on no-op, so the view's forward() short-circuits when nothing happened. Render-only triggers (async tile uploads, RAF camera transitions, surface-load completions) request a render against the existing state without re-running commit or notifying subscribers; layers reach this channel through an owner-injected requestRender wired by attach/detach, not by holding any reference to a view. Subscribers fire when state actually changes; renders happen when pixels need updating. The two are not the same event.

The four building blocks are aligned with this split:

  • Layers are pure data. They describe what they want bound and what they want loaded; they never touch the GPU.
  • Controls are pure reducers (action, state) → state, with no knowledge of DOM, canvas, or GPU.
  • Overlays are DOM-only presentation, reading from state and a live view adapter.
  • Views own rendering, input routing, and all GPU residency for their layers — pipelines, bind groups, tile pools, colormap textures, storage buffers — and translate the shared camera into projection matrices.

LOD is a representative case of the layered design: controls express semantic intent, the active view applies a safety cap based on its canvas metrics, and the layer applies a final backstop based on its actual pyramid bounds. Three tiers, each owning what it alone can know.

How layers, views, and the per-view pipeline actually wire together — attach/detach, getTileSpec/planTiles, the LayerRenderer per (view, layer), and what happens when one layer is rendered by multiple views — is in Architecture Notes.

No framework dependency

Galavi is pure TypeScript. It does not depend on a UI framework, a state library, or a bundler runtime. Applications written in Vue, React, or none of the above all consume the same API. Persistent objects are plain TypeScript types, not framework-flavored ones.

Constraints

These constraints are load-bearing for the design above and are non-negotiable in core:

  • No framework dependency.
  • No GPU resources inside State.
  • No direct inter-view communication; coordination is via shared state.
  • Config objects remain the primary application boundary; exported classes exist for extension, not for day-to-day use.

Scope of the public API

The public surface is deliberately small: createGalavi, the Galavi instance, the four register* entry points, the Base* classes for extension, the registered concrete config types, and a curated set of utilities for plugin authors — camera math (clampPitch, cameraDistance, cameraAngles, computePosition, computeForward), axes (resolveAxes, AxisMap), geometry primitives (UNIT_CUBE, aabbFromPositions, EMPTY_VERTEX_BUFFER), the tile / pyramid contract (TilePool, TileLoadQueue, TileManager, TileSource, TileLoader, planTiles, pickPyramidLevel, floatToFloat16, …), colormaps and appearance presets, and DOM input normalizers (normalizeWheel, normalizeDrag). These exist to be reused by registered building blocks; the same surface a plugin author sees is the surface the built-in layers and views were written against. Implementation-only machinery — the per-view ImagePipeline, scene uniform layout, view factory, internal parsers — stays internal. Applications consume tiled data through layer configs and source adapters; tiled-source plugins consume the public tile/pyramid contract.

This is what makes the extension story honest: the surface a plugin author sees is the same surface the built-in layers and views were written against.

Versioning policy

Galavi follows a no-backward-compatibility policy between major versions. Breaking changes happen in major bumps without deprecation shims or compatibility flags; minor and patch releases stay backwards-compatible within a major.

If you depend on Galavi, pin a major version and read the release notes before upgrading across one. The library is small enough that migrations are usually mechanical.

Released under the GPL-3.0 License.