X-GIS

Concept

Performance you can measure.

X-GIS treats frame budget as something to measure, not guess. Three mechanisms carry the load: one shared GPU buffer with byte-aware eviction, data-driven paint that can move to an opt-in compute pass, and a real GPU-timestamp profiler that prints a GPU-vs-CPU verdict on screen.

The GPU arena

At zoom 14 over a dense basemap a frame touches on the order of ten thousand tile meshes. Giving each its own GPU buffer means a buffer swap per tile and thousands of tiny allocations. X-GIS instead packs tiles into one large shared buffer and hands out byte offsets.

01

One shared buffer, not thousands

The GPUArena is a linear allocator backing a single large GPU buffer (the polygon vertex / index arenas start at tens of megabytes). Tiles upload into it by offset, so the draw loop binds the arena buffer once instead of swapping a vertex buffer per tile — the precondition for indexed/indirect draws.
02

O(1) allocation with exact-footprint reuse

Each alloc is a free-list pop or a bump-pointer bump — constant time. The free-list is keyed by the exact 4-byte-aligned footprint, so a reused slot always fits the request and can never overrun its neighbour. Real tiles cluster around a few stride-aligned sizes, so reuse stays high during pan and zoom.
03

Byte-aware eviction, compaction, and auto-grow

Eviction is byte-aware, not count-only — large globe or extruded tiles can exhaust the arena long before any tile-count cap is reached, so the store triggers on byte pressure as well as the count cap. When the bump high-water mark crosses the high-water threshold the store evicts least-recently-used tiles; if the live set is fragmented it compacts (relocating into a fresh buffer), and if it genuinely exceeds capacity it grows. All of it runs in the post-submit safe window, never mid-render.

Compute kernels for paint

Data-driven paint — a colour or width chosen per feature from its properties — can be moved off the fragment shader and evaluated once per frame in a compute pass.

04

Per-feature paint, evaluated once on the GPU

The compiler can emit a WGSL compute kernel for feature-dependent paint — match, a conditional (case/ternary), and interpolate each have their own emitter — for the runtime's ComputeDispatcher to dispatch, writing one packed RGBA per feature into a storage buffer the draw pass reads. Routing is selective: only paint whose value depends on feature properties is eligible for the compute path; zoom-only paint stays elsewhere. High-arm match expressions switch from an if-else chain to a constant lookup table. This path is opt-in — armed with ?compute=1 (the enableComputePath option); by default feature-dependent paint still evaluates in the fragment shader, and no production style turns the compute path on yet.

Real GPU-time measurement

CPU wall-clock around a draw call tells you almost nothing — the GPU runs asynchronously. X-GIS reads the real device time with WebGPU timestamp queries.

05

WebGPU timestamp queries, not wall-clock guesses

When the device supports it (opt in with ?gpuprof=1), the GPUTimer plants timestamp queries around each render pass and resolves them through a small readback ring — so the number reported is actual GPU execution time, hidden behind a couple of frames of map latency rather than blocking the frame.
06

Per-pass GPU and per-phase CPU breakdown

Where supported, the GPU pass splits into named segments — background, raster, legacy, vector-tile, and the compute pass — so you see which pass costs what. On the CPU side, per-frame marks decompose the render loop into frame.prep, frame.encode, frame.submit, and per-pass encode times, each tracking its mean and its worst single frame. The marks are off by default — they cost real CPU — and arm with ?perfmarks=1 or ?gpuprof=1.

The on-screen verdict

Mobile testers can't open DevTools. The playground carries an on-screen scoreboard that runs a fixed camera sweep and prints — and one-tap copies — the numbers.

07

GPU-bound or CPU-bound, decided on screen

Load any demo with ?perf=1 for the scoreboard. Its GPU/CPU button runs a rotate-and-pitch sweep while a message-channel probe measures how long the main thread is busy: if the worst stall fills the frame the verdict is CPU-bound; if the main thread sits idle waiting on present, GPU-bound. It also captures the worst frame anywhere in the run and attributes it — inside-render (naming the pass) versus the tile drain chain — so a bursty stall a mean would hide gets pinned down.

Quality knobs

X-GIS generally avoids quality settings — the goal is to render correctly and fast, not to ship sliders. There is exactly one opt-in exception, because a GPU-bound scene physically can't hit a high frame rate without trading MSAA or pixel density.

08

One opt-in trade-off, always explicit

Named presets (performance, balanced, battery) and URL flags (?quality, ?msaa, ?dpr, ?adaptiveDpr) trade fidelity for budget. The default keeps full quality. Under balanced / battery (or an explicit ?adaptiveDpr=N) the renderer drops device-pixel-ratio during an active pan or zoom and restores it when the gesture settles — pan motion hides the lower density. It is inert otherwise. The engine never adapts quality on its own; the trade-off is always something a host opted into.

Where it is going

Roadmap · not yet shipped

These are planned, not built. They are on the roadmap, not in the engine today:

  • Automatic quality adaptation — a closed loop that watches measured frame time (or thermal pressure) and lowers DPR / MSAA on its own. Today the trade-off is opt-in only; there is no automatic, FPS- or thermal-driven downgrade.
  • Always-on perf HUD — the GPU/CPU verdict and frame-phase breakdown live in the playground scoreboard behind a URL flag, not as a product-facing overlay in the embedding API.

Tracked in the repo ROADMAP.md ↗.

See it live

Open the playground or pick a map from the examples gallery, then append ?perf=1&gpuprof=1 to the demo URL. Tap 측정 to run the sweep, or GPU/CPU 판정 for the bound-by verdict, then copy the report.

Specifications

See also