Table of Contents

Class MarkerLayer

Namespace
LansMap.Markers.Bulk

GPU bulk marker tier: one instanced draw for a whole MarkerStore on the same transform as the tiles.

public sealed class MarkerLayer : IDisposable, IMarkerStyle
Inheritance
object
MarkerLayer
Implements

Constructors

MarkerLayer(MarkerStore)

Renders markers from a store managed by another layer, without copying.

Shares an existing store - typically another MarkerLayer's, or a store also fed to a SurfaceWorldMarkerRenderer - so the same markers render on this layer without copying, and a 2D-to-tabletop (or screen-to-in-texture) switch is a pure view swap. Mirrors SurfaceWorldMarkerRenderer(MarkerStore): the owning layer keeps its dirty-range fast path; this layer re-uploads whole when the store's Version moves.

This layer still builds its own SpatialIndex for TryPick(MapViewTransform, ScreenPoint, double, out int), but never attaches it to sharedStore.SpatialIndex: that property has a single slot, and a second attach would silently steal it from the owning layer, breaking that layer's own picking. So picking on a shared-store MarkerLayer never finds anything - use the owning layer for picking; this constructor is for a second render consumer of the same markers, such as an in-texture layer stack on SurfaceMapView.

public MarkerLayer(MarkerStore sharedStore)

Parameters

sharedStore MarkerStore

MarkerLayer(int)

Creates a layer that owns a new MarkerStore sized for initialCapacity markers.

public MarkerLayer(int initialCapacity)

Parameters

initialCapacity int

Store and GPU buffer hint, marker count.

Fields

FirstCustomShapeId

First shape id reserved for textures registered with RegisterShape(Texture2D, bool). Ids below this are procedural SDF shapes.

public const int FirstCustomShapeId = 16

Field Value

int

HideOffscreenMarkers

When true, markers whose center leaves the viewport are not drawn.

public bool HideOffscreenMarkers

Field Value

bool

HoverScale

Scale applied to the hovered marker in the vertex shader.

public float HoverScale

Field Value

float

HoveredId

Slot index of the hovered marker for the vertex-shader grow, or -1 for none. Not a MarkerStore id; use MarkerStore.SlotOf.

public int HoveredId

Field Value

int

Map

The view this layer's markers belong to, read once per PrepareDraw(MapViewTransform) purely for ElevationSampler - Store's altitude resolution needs the current ground sampler, and a bulk-tier MarkerLayer is constructed before its owning view exists (ScreenMapView.Awake), so this is set afterward rather than threaded through a constructor. Null, the default, means flat: the same "no sampler" convention CurrentSampler already documents.

public BaseMapView Map

Field Value

BaseMapView

MaxAltitudeMercator

Upper altitude bound (mercator units), inclusive. See MinAltitudeMercator. PositiveInfinity, the default, means no upper bound.

public float MaxAltitudeMercator

Field Value

float

MinAltitudeMercator

Lower altitude bound (mercator units, AltitudeMercator) a marker must meet to be uploaded/drawn by this layer, inclusive. NegativeInfinity, the default, means no lower bound - the layer draws everything. Per-layer-instance, not a per-type constant: the same MarkerLayer class backs a fullscreen view (unbounded) and a ground-only tier sharing a store with a SurfaceWorldMarkerRenderer (filtered to alt <= 0, so the two layers do not both draw the same floating marker).

public float MinAltitudeMercator

Field Value

float

SpatialIndex

Spatial index kept in sync with Store for the lifetime of this layer (attached before any marker is added, in the constructor), and reused across picking, clustering and viewport tracking so those three consumers never build competing views of the same store.

public readonly MarkerSpatialIndex SpatialIndex

Field Value

MarkerSpatialIndex

Store

Marker data this layer draws.

public readonly MarkerStore Store

Field Value

MarkerStore

Properties

ApplyZoomRange

When true, the default, each marker's stored min/max zoom window hides it outside that range. When false the windows stay stored but every marker on this layer is drawn and pickable at any zoom. Combined with the per-marker flag: both must be true to apply.

public bool ApplyZoomRange { get; set; }

Property Value

bool

CustomShapes

Registered shape sources in shape-id order, so index i is shape id FirstCustomShapeId + i. Baked SDF entries belong to this layer; the others are the caller's own textures.

public IReadOnlyList<Texture2D> CustomShapes { get; }

Property Value

IReadOnlyList<Texture2D>

OutlineColor

Layer-wide SDF outline color; alpha 0 disables.

public Color OutlineColor { get; set; }

Property Value

Color

OutlineWidthPx

Layer-wide SDF outline width in screen px.

public float OutlineWidthPx { get; set; }

Property Value

float

ShapeAtlas

The packed shape atlas, or null before the first RegisterShape(Texture2D, bool). Exposed for leak gates and debugging.

public Texture2D ShapeAtlas { get; }

Property Value

Texture2D

StemWidthPx

Vertical stem width in screen px, when StemsEnabled draws one.

public float StemWidthPx { get; set; }

Property Value

float

StemsEnabled

When true and any marker has altitude, draws vertical stems connecting raised markers to ground.

public bool StemsEnabled { get; set; }

Property Value

bool

Visible

When false, this layer is not drawn. Clustering uses this to swap in bubbles.

public bool Visible { get; set; }

Property Value

bool

WarmUpZoomThreshold

While this layer is invisible, PrepareDraw(MapViewTransform) starts chunking its first upload into idle frames once view.State.Zoom reaches this value, instead of paying the whole upload in the one frame Visible flips true. PositiveInfinity, the default, disables warm-up. This layer has no notion of a cluster threshold of its own - a caller that swaps Visible off below some zoom should set this a little below that same zoom.

public double WarmUpZoomThreshold { get; set; }

Property Value

double

Methods

BakeSdf(Texture2D, int)

Bakes a signed-distance field from the source alpha silhouette, encoded as alpha with 0.5 at the edge.

public static Texture2D BakeSdf(Texture2D src, int spreadPx = 6)

Parameters

src Texture2D

CPU-readable source texture.

spreadPx int

Search radius in texels.

Returns

Texture2D

RGBA32 texture, rgb white so the shader tints, alpha is the distance field.

Dispose()

Releases GPU buffers, the material, and any distance fields this layer baked.

public void Dispose()

FillCommandBuffer(CommandBuffer)

Records the prepared bulk draw into cb. Structured buffers bind through a per-draw property block: command buffers do not snapshot material buffer state.

public void FillCommandBuffer(CommandBuffer cb)

Parameters

cb CommandBuffer

Command buffer already bound to the camera target.

PrepareDraw(MapViewTransform)

Uploads dirty markers and stashes uniforms for view. Call from the game thread before FillCommandBuffer(CommandBuffer).

public void PrepareDraw(MapViewTransform view)

Parameters

view MapViewTransform

Current map view.

RegisterCustomShape(Texture2D, bool)

Registers a readable texture as a marker shape and returns the shape to pass to the store.

public MarkerShape RegisterCustomShape(Texture2D readableTexture, bool asSdf = false)

Parameters

readableTexture Texture2D

CPU-readable texture, for example a sprite rasterized from an imported SVG.

asSdf bool

True bakes the alpha silhouette into a distance field so the shape stays crisp at any size (monochrome, tinted by the marker color). False keeps a full-color sprite.

Returns

MarkerShape

Shape whose numeric value is at or above FirstCustomShapeId.

Remarks

The source texture must be CPU-readable (tick Read/Write Enabled on the texture importer), because both the distance-field bake and the atlas pack read its pixels. A layer holds at most 64 custom shapes; the sixty-fifth registration throws.

Every registration repacks the whole shape atlas. That is a registration-time cost and never a per-frame one, but registering many shapes one at a time pays it many times over. To register a set, use RegisterCustomShapes(IReadOnlyList<CustomShapeRequest>, List<MarkerShape>), which packs once.

Ownership: with asSdf true the layer bakes a new texture, owns it, and destroys it on Dispose(), while your source stays yours. With asSdf false the layer packs your texture and never destroys it, so keep it alive for as long as the layer draws that shape.

A full-color sprite shape:

MarkerShape cafe = layer.RegisterCustomShape(cafeSprite);
layer.Store.Add(new LatLon(48.8566, 2.3522), 0, 24f,
    MarkerSizeMode.Pixels, Color.white, cafe);

A monochrome silhouette baked to a distance field, so it stays crisp at any size and takes the marker color as its tint:

MarkerShape arrow = layer.RegisterCustomShape(arrowSilhouette, asSdf: true);
layer.Store.Add(new LatLon(48.8566, 2.3522), 0, 24f,
    MarkerSizeMode.Pixels, Color.red, arrow);

RegisterCustomShapes(IReadOnlyList<CustomShapeRequest>, List<MarkerShape>)

Registers several shapes at once, packing the shape atlas exactly once.

public void RegisterCustomShapes(IReadOnlyList<CustomShapeRequest> requests, List<MarkerShape> ids)

Parameters

requests IReadOnlyList<CustomShapeRequest>

Shapes to register, in the order their ids are wanted.

ids List<MarkerShape>

Cleared, then filled with one shape per request, in request order.

Remarks

Same per-shape contract as RegisterCustomShape(Texture2D, bool): CPU-readable sources, at most 64 shapes per layer, baked distance fields owned by the layer and plain sprites owned by you.

The one difference is cost. Registering n shapes individually packs the atlas n times; this packs once for the whole batch, which is why it exists.

A batch is all or nothing. Whether it is refused for exceeding the 64-shape limit or one of its textures turns out not to be readable, the layer is left exactly as it was, so the same batch can be handed back after the bad texture is fixed without the failed attempt having consumed any shape slots.

RegisterShape(Texture2D, bool)

Registers a readable texture as a marker shape and returns the id to store in MarkerInstanceData.Shape.

public int RegisterShape(Texture2D readableTexture, bool asSdf = false)

Parameters

readableTexture Texture2D

CPU-readable texture, for example a sprite rasterized from an imported SVG.

asSdf bool

True bakes the alpha silhouette into a distance field so the shape stays crisp at any size (monochrome, tinted by the marker color). False keeps a full-color sprite.

Returns

int

Shape id at or above FirstCustomShapeId, as an int.

Remarks

Identical to RegisterCustomShape(Texture2D, bool) except for the return type. Prefer RegisterCustomShape(Texture2D, bool), which hands back the MarkerShape the store methods already take, so no cast is needed at the call site.

Render(MapViewTransform)

Uploads dirty markers if needed and draws the bulk tier for this view.

public void Render(MapViewTransform view)

Parameters

view MapViewTransform

Current map view.

TryPick(MapViewTransform, ScreenPoint, double, out int)

Nearest marker to screen within radiusPx, or false when this layer is hidden.

public bool TryPick(MapViewTransform view, ScreenPoint screen, double radiusPx, out int slot)

Parameters

view MapViewTransform

Current map view.

screen ScreenPoint

Pick point in screen px, origin top-left.

radiusPx double

Maximum distance in screen px.

slot int

Hit slot index, or -1. Use MarkerStore.HandleOf for an id.

Returns

bool

True when a marker is within radiusPx.

Remarks

Every interaction path goes through here rather than calling MarkerPicker directly, because Visible was consulted by the renderer alone: below the cluster zoom the markers were not drawn yet stayed hoverable, draggable, clickable and viewport-tracked, 100,000 of them in LansMapStress. Reports a slot index, like MarkerPicker; MarkerStore.HandleOf turns it into an id.

TryPick(MapViewTransform, ScreenPoint, double, out int, out double)

Nearest marker to screen within radiusPx, or false when this layer is hidden.

public bool TryPick(MapViewTransform view, ScreenPoint screen, double radiusPx, out int slot, out double distanceSq)

Parameters

view MapViewTransform

Current map view.

screen ScreenPoint

Pick point in screen px, origin top-left.

radiusPx double

Maximum distance in screen px.

slot int

Hit slot index, or -1. Use MarkerStore.HandleOf for an id.

distanceSq double

Squared distance to the hit in screen px squared.

Returns

bool

True when a marker is within radiusPx.