Class S100VectorTileRenderer
The TiledScene ("B") render subsystem, Phase 2: a Mapsui
custom layer renderer that draws the chart base plane as a pyramid
of cached tiles rasterised from the backend-agnostic
VectorScene IR on worker threads, then composited on the
UI/render thread. It generalises the Phase 1 single-surface renderer
(S100VectorSceneRenderer) by partitioning the over-render
margin into an origin-anchored EncDotNet.S100.Renderers.Mapsui.TileGrid so a constant-zoom pan
reuses every interior tile and only rasterises the newly-exposed perimeter —
making pan cost scale with perimeter, not viewport area
(docs/design/S100-Render-Subsystem-Design.md §3.2–§3.5).
public static class S100VectorTileRenderer
- Inheritance
-
S100VectorTileRenderer
- Inherited Members
Remarks
Compositor (UI thread, bounded). Each frame the renderer snaps the live resolution to a EncDotNet.S100.Renderers.Mapsui.TileGrid band, finds the visible tiles, and blits the best available for every slot: the exact band tile when cached, otherwise cached tiles from other bands scaled to fit (transient zoom blur) so a hole is never shown. Missing visible tiles are enqueued for the worker at visible priority; the compositor itself does zero rasterisation and never blocks.
Workers. A tier-sized pool of coalescing workers per layer drains the visible-miss set (replaced every frame, so tiles panned out of view are dropped before they render) in parallel. The pool size is TileWorkerCount (one on low-end hosts, scaling with cores on high-end). Each tile is rendered with a gutter (bleed beyond the tile bounds) and hard-clipped to its core on composite, so lines and area fills stay continuous across seams. Finished tiles enter the native-byte-bounded EncDotNet.S100.Renderers.Mapsui.TileCache and trigger a single repaint via the layer's per-session redraw sink.
Scope (Phase 2). Tiling + LRU cache + best-available compositor. No prediction/pre-warm (Phase 3), no disk cache or separate label plane (Phase 4). North-up only; a rotated viewport draws nothing for that frame.
Fields
RendererName
The Mapsui.Layers.ILayer.CustomLayerRendererName value that routes a layer through this renderer.
public const string RendererName = "s100.vector.tile"
Field Value
Properties
BudgetBytes
Hot-cache native-byte budget per layer. Sourced from
TileBudgetMb (seeded from
S100_VECTOR_TILE_BUDGET_MB, default 256 MB); captured per layer
when its tile state is created (applies on the next dataset reload).
public static long BudgetBytes { get; }
Property Value
CrossBandPrewarmEnabled
Whether idle cross-band pre-warm (issue #428) is enabled. When
on, and the tiled base plane is otherwise idle for a layer (no cold
visible misses this frame and cache headroom to spare), the renderer
speculatively rasterises the band ± 1 tiles covering the current
viewport at the lowest worker priority (behind visible and
same-band predicted work), so a subsequent zoom-in or zoom-out starts warm
instead of paying full cold-tile latency at the new band. Sourced from
TileCrossBandPrewarmEnabled (seeded
from S100_VECTOR_TILE_XBAND, default on except off by default on the
LowEnd tier — an explicit opt-in is still honoured there); read every frame
so a change takes effect live. Like the same-band prediction warm set, its
tiles never trigger a redraw and are cancelled (rebuilt) every frame.
public static bool CrossBandPrewarmEnabled { get; }
Property Value
DiskCacheEnabled
Whether the persistent disk tile cache (Phase 4) is enabled.
When on, a tile missing from the in-memory cache is looked up on disk
before being re-rasterised, and freshly rasterised tiles are persisted —
so a palette flip-back or a process restart re-uses warm tiles. Sourced
from TileDiskCacheEnabled (seeded from
S100_VECTOR_TILE_DISK, default on); the shared cache is created once
per process, so a change applies on restart. The on-disk key folds a
styleStateHash so a tile is never served for a different
mariner/palette state (design §3.4).
public static bool DiskCacheEnabled { get; }
Property Value
GpuBudgetBytes
Per-layer GPU-texture residency budget, in native bytes. The resident
GPU texture set is bounded independently of the CPU hot cache (it holds
only tiles actually blitted, so it tracks the viewport working set).
Sourced from TileGpuBudgetMb (seeded
from S100_VECTOR_TILE_GPU_MB, default 256 MB); sized when the
resident-texture cache is first created (applies on the next dataset
reload). Must comfortably exceed the visible + fallback working set or
promotion thrashes (re-upload each frame); the default holds ~100 guttered
tiles.
public static long GpuBudgetBytes { get; }
Property Value
GpuResidencyEnabled
Whether GPU texture residency (Phase 5) is enabled. When on,
and the live compositor surface is GPU-backed
(Context resolves to a GRContext),
each warm raster tile is uploaded once to a GPU-resident texture
(ToTextureImage(GRContext)) and reused on every
subsequent frame, instead of re-uploading the same pixels each paint —
the dominant per-frame cost identified in Appendix F. Sourced from
TileGpuResidencyEnabled (seeded from
S100_VECTOR_TILE_GPU, default off); read every frame so a change
takes effect live. On a software/CPU surface this is inert and the
renderer blits the raster tile directly (universal fallback, no regression).
public static bool GpuResidencyEnabled { get; }
Property Value
GutterDip
Gutter, in DIP, rasterised beyond each tile's bounds on every edge and
hard-clipped away on composite, so strokes crossing a tile seam keep
their joins/caps. Sourced from
TileGutterDip (seeded from
S100_VECTOR_TILE_GUTTER, default 64); only newly-rasterised tiles
pick up a live change.
public static double GutterDip { get; }
Property Value
HasVisibleTileWork
Whether any layer had visible tiles waiting to be drawn within the last half second. The renderer holds its own predicted and pre-warm tiles back while this is true; hosts can hold back their own speculative work (such as loading cells ahead of the viewport) the same way.
public static bool HasVisibleTileWork { get; }
Property Value
IsOffscreenRender
Whether the calling thread is inside a BeginOffscreenRender() scope.
public static bool IsOffscreenRender { get; }
Property Value
MetatileEnabled
Whether adjacent misses may be rasterised as a 2×2 metatile and
sliced into tile-granular cache entries (issue #427). Sourced from
TileMetatileEnabled and seeded by
S100_VECTOR_TILE_METATILE; default off pending the real-data gate.
public static bool MetatileEnabled { get; }
Property Value
PredictionEnabled
Whether speculative prediction / pre-warm (Phase 3) is enabled.
When on, each frame also rasterises a velocity-aimed warm set so
newly-exposed perimeter tiles are cached before a pan/zoom reveals them.
Sourced from TilePredictionEnabled
(seeded from S100_VECTOR_TILE_PREDICT, default on); read every frame
so a change takes effect live.
public static bool PredictionEnabled { get; }
Property Value
Methods
BeginOffscreenRender()
Marks every Render(SKCanvas, Viewport, ILayer, RenderService) call on the calling thread, until the
returned scope is disposed, as an off-screen render of the live
layers (for example an Avalonia RenderTargetBitmap capture or a
print preview) rather than a live frame.
public static IDisposable BeginOffscreenRender()
Returns
- IDisposable
A scope that ends the off-screen render when disposed.
Remarks
An off-screen frame is read-only with respect to tile scheduling: it composites whatever tiles are already cached (scaling them to its own device scale) and the live symbol/text overlay, but it never enqueues rasterisation, never changes the pending device scale, viewport or velocity the live view's workers use, and never touches GPU residency or the visible-tile eviction pins. A capture at 1x of a 2x window therefore cannot leave 1x tiles behind for the live view to blit. Scopes nest and are per thread.
BindScene(ILayer, VectorScene)
Binds the fully-resolved VectorScene for a layer and invalidates its tile cache (a new generation), so the next frame re-rasterises from the new scene. Equivalent to calling BindScene(ILayer, VectorScene, string?, string?) without a disk-cache key (the warm disk cache is bypassed for this layer).
public static void BindScene(ILayer layer, VectorScene scene)
Parameters
layerILayersceneVectorScene
BindScene(ILayer, VectorScene, string?, string?)
Binds the fully-resolved VectorScene for a layer and invalidates its tile cache (a new generation), so the next frame re-rasterises from the new scene.
public static void BindScene(ILayer layer, VectorScene scene, string? productLayerSet, string? styleStateHash)
Parameters
layerILayerThe Mapsui layer this scene portrays.
sceneVectorSceneThe resolved paint operations to rasterise into tiles.
productLayerSetstringStable identity of the dataset/cell + product, used (with
styleStateHash) as the persistent disk-cache namespace so warm tiles are reused across layer rebuilds and sessions. When either this orstyleStateHashis null/empty, the warm disk cache is bypassed for this layer.styleStateHashstringA hash that fully captures the mariner/palette style state (palette, display category, safety settings, symbol/text scale, …) so a tile is never served from disk for a different style state (design §3.4).
Render(SKCanvas, Viewport, ILayer, RenderService)
The render handler Mapsui invokes for tagged layers. Composites the best available tiles for the live viewport and enqueues missing visible tiles.
public static void Render(SKCanvas canvas, Viewport viewport, ILayer layer, RenderService renderService)
Parameters
canvasSKCanvasviewportViewportlayerILayerrenderServiceRenderService
ResolveTileDiskDirectory()
The effective warm-tile disk-cache root directory for this process: the TileDiskDirectory override (env var or host-assigned), or an OS-temp subdirectory when unset. Exposed so the host can locate the cache for a "clear caches" sweep.
public static string ResolveTileDiskDirectory()
Returns
ShutdownAndDrain(TimeSpan)
Signals every tile worker to stop and blocks until in-flight tile
rasterisation has finished, so the process can safely tear down native
Skia without a worker dereferencing freed Skia globals. Idempotent and
permanent: once called, no further tiles are rasterised for the lifetime
of the process. Call this on the host's shutdown path (before
desktop.Shutdown() / process exit).
public static bool ShutdownAndDrain(TimeSpan timeout)
Parameters
timeoutTimeSpanMaximum time to wait for in-flight workers to drain. A single tile rasterise is short; a few seconds is ample. If the timeout elapses with workers still running, returns false (the caller may still proceed, accepting the small teardown-race risk).
Returns
TryBindFilteredScene(ILayer, ILayer, Func<PaintOp, bool>)
Binds to target a copy of the scene bound to
source that keeps only the ops keep
accepts, so a layer derived from another (for example one whose S-101
depth features an S-98 rule suppressed) is still painted by the tiled
renderer, pattern fills included, rather than by its pick-target
features. The copy bypasses the warm disk cache: its content no longer
matches the source's namespace.
public static bool TryBindFilteredScene(ILayer source, ILayer target, Func<PaintOp, bool> keep)
Parameters
sourceILayerA layer a scene was bound to.
targetILayerThe layer to bind the filtered scene to.
keepFunc<PaintOp, bool>Returns true for each op to keep.
Returns
TryBindFilteredScene(ILayer, ILayer, Func<PaintOp, bool>, ILayer?)
Binds to target a filtered copy of the scene bound to
source, as
TryBindFilteredScene(ILayer, ILayer, Func<PaintOp, bool>),
for a layer that takes the place of replaces on the
map. Until its own tiles land, target shows the
tiles replaces already has, so the swap never blanks
the chart; replaces stops producing tiles.
public static bool TryBindFilteredScene(ILayer source, ILayer target, Func<PaintOp, bool> keep, ILayer? replaces)
Parameters
sourceILayerA layer a scene was bound to.
targetILayerThe layer to bind the filtered scene to.
keepFunc<PaintOp, bool>Returns true for each op to keep.
replacesILayerThe layer
targetreplaces (for example the previous filtered copy ofsource), or null.
Returns
TryGetPartitionedScene(ILayer, out VectorScene, out VectorScene)
Attempts to retrieve the partitioned base and overlay
VectorScenes most recently bound to layer
via BindScene(ILayer, VectorScene, string?, string?).
public static bool TryGetPartitionedScene(ILayer layer, out VectorScene baseScene, out VectorScene overlayScene)
Parameters
layerILayerThe layer a scene was bound to.
baseSceneVectorSceneThe tiled base plane (area/pattern fills and lines).
overlaySceneVectorSceneThe live overlay plane (point symbols and point-anchored text).
Returns
Remarks
A fidelity-verification / diagnostics accessor (no frame is rendered): it exposes the exact resolved paint operations the tiled subsystem will rasterise into the base plane and composite into the live overlay plane. It backs the issue #347 multi-product parity guard, which inspects the per-product overlay (point symbols + labels) at the op level rather than through the coarse perceptual pixel gate.