Table of Contents

Class S100VectorTileRenderer

Namespace
EncDotNet.S100.Renderers.Mapsui
Assembly
EncDotNet.S100.Renderers.Mapsui.dll

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

string

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

long

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

bool

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

bool

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

long

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

bool

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

double

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

bool

IsOffscreenRender

Whether the calling thread is inside a BeginOffscreenRender() scope.

public static bool IsOffscreenRender { get; }

Property Value

bool

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

bool

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

bool

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

layer ILayer
scene VectorScene

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

layer ILayer

The Mapsui layer this scene portrays.

scene VectorScene

The resolved paint operations to rasterise into tiles.

productLayerSet string

Stable 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 or styleStateHash is null/empty, the warm disk cache is bypassed for this layer.

styleStateHash string

A 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

canvas SKCanvas
viewport Viewport
layer ILayer
renderService RenderService

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

string

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

timeout TimeSpan

Maximum 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

bool

true if all workers drained within the timeout; otherwise false.

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

source ILayer

A layer a scene was bound to.

target ILayer

The layer to bind the filtered scene to.

keep Func<PaintOp, bool>

Returns true for each op to keep.

Returns

bool

true when source had a scene to copy.

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

source ILayer

A layer a scene was bound to.

target ILayer

The layer to bind the filtered scene to.

keep Func<PaintOp, bool>

Returns true for each op to keep.

replaces ILayer

The layer target replaces (for example the previous filtered copy of source), or null.

Returns

bool

true when source had a scene to copy.

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

layer ILayer

The layer a scene was bound to.

baseScene VectorScene

The tiled base plane (area/pattern fills and lines).

overlayScene VectorScene

The live overlay plane (point symbols and point-anchored text).

Returns

bool

true when a scene is bound; otherwise false.

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.