Moderne C2-dashboards renderen niet langer honderden sporen. Ze renderen tienduizenden. Luchtbeelden van gefedereerde radarnetwerken, AIS-maritieme feeds, drone-zwermtelemetrie, GPS-pings van grondeenheden en gefuseerde multi-sensorsporen komen allemaal op hetzelfde operatorscherm binnen. Een werkbaar common operational picture in 2026 piekt routinematig boven de 100.000 gelijktijdige sporen. De visualisatielaag houdt 60fps aan onder die belasting, of de operator verliest vertrouwen in het systeem.

Dit artikel is een technische uitleiding over hoe u framebudgetten eerlijk houdt op die schaal. We behandelen WebGL-instancing, Cesium primitive batching, deck.gl-laagsamenstelling, LOD-strategie, GPU-geheugenbudgetten, frame-pacing-rekenwerk en het testframework dat u nodig hebt om regressies op te vangen voordat een operator dat doet.

De 100.000-Sporen-Realiteit

De oude vuistregel — "een tactisch display moet een paar duizend sporen aankunnen" — was correct in 2010 en gevaarlijk onjuist in 2026. NATO multi-sensorfusiefeeds, laagaltitude-radarrasters en commerciële ADS-B/AIS-aggregatoren produceren routinematig spoorcounts in de zes cijfers tijdens oefeningen en actieve operaties. Drone-zwarm-doctrine alleen al kan 200–500 vriendelijke emitters in een box van 50 km plaatsen.

Het prestatieverschil is van belang omdat het operatorvertrouwen instort onder 30fps. Zodra pannen hapert of symboliek de onderliggende sporenupdate achterloopt, wantrouwt de operator alles op het display — en begint het beeld te vergelijken met een papieren kaart of een secundair scherm. Die kruiscontrole-tijd is precies de operationele latentie die het C2-systeem was gebouwd om te elimineren. Een C2-dashboard dat onder realistische belasting onder 60fps zakt, is geen traag C2-dashboard. Het is een kapot dashboard.

Het doel is vastgesteld: 60fps aanhoudend, met 100.000+ sporen, op een typisch mid-tier operationeel werkstation (8-core CPU, mid-range dGPU, 1440p). Dit doel halen vereist dat elke laag van de stack — geometrie, draw calls, GPU-geheugen, netwerkingestie, toestandsmutatie — het framebudget van 16,67ms respecteert.

WebGL Instancing

De eerste hefboom is de draw call. 100.000 individuele draws per frame uitgeven is onmogelijk op 60fps op een huidige GPU; de CPU-side driver-overhead alleen al verbrandt het volledige budget. Instanced rendering vouwt duizenden symbolen samen in één draw call. Één geometrie (het symboolmesh), één shader en een per-instance-attribuutbuffer met positie, koers, affiliatie en symbool-ID.

Het standaardpatroon gebruikt ANGLE_instanced_arrays in WebGL 1 of native drawArraysInstanced in WebGL 2. Per-instance-attributen stromen uit een strak gepakt buffer: typisch 32 bytes per spoor (vec3-positie, vec2-snelheid, uint32-verpakte vlaggen). Bij 100k sporen is dat 3,2 MB vertex-attribuutdata — klein genoeg om elk frame opnieuw te uploaden indien nodig, hoewel gedeeltelijke updates via bufferSubData goedkoper zijn.

Three.js stelt instancing beschikbaar via InstancedMesh; deck.gl verwerkt het native voor bijna elke laag; Cesium's Primitive-API ondersteunt het via GeometryInstance-arrays. De drie frameworks vallen op hetzelfde spectrum — Three.js geeft de meeste vrijheid en de minste ingebouwde geospatiale wiskunde, deck.gl is het snelste pad naar een werkende hoogdichtheid-laag, Cesium biedt 3D-globusemantiek en terreinocclusie die de andere twee niet bieden.

Cesium Primitive Batching

Cesium's Entity-API is het verkeerde gereedschap boven 5.000 sporen. Entities alloceren per-spoor JavaScript-objecten, draaien een CPU-side updateloop en herbouwen geometrie bij eigenschapswijzigingen. De kosten zijn acceptabel bij kleine aantallen en catastrofaal bij grote aantallen.

Voor 100k-schaal rendering, schakel over naar de Primitive-API. Een PointPrimitiveCollection rendert tot ~1M punten in één draw call. Een BillboardCollection verwerkt tienduizenden pictogram-getextureerde sprites met een gedeelde textuuratlas. Het GeometryInstance-patroon groepeert duizenden statische geometrieën (bijv. reikwijdteringen, geofences) in één gebatched primitief bij aanmaakmomenten.

Labelweergave is asymmetrisch. Een 100k PointPrimitiveCollection rendert moeiteloos; 100k labels daarboven toevoegen verbreekt het framebudget onmiddellijk. Labels gaan via Cesium's SDF-tekstpad, wat zowel glyph-atlageheugen als een aparte gebatched draw kost. De oplossing is LOD-gestuurde labelzichtbaarheid: render labels alleen voor sporen binnen een zoom-afhankelijke schermruimtestraal van de cursor of voor sporen die door de fusiemotor als "van belang" zijn gemarkeerd. Een typisch operatorscherm heeft niet meer dan 50–200 zichtbare labels tegelijk nodig.

deck.gl Lagen voor Defensie

deck.gl ligt bovenop een MapboxGL- of MapLibre-basiskaart en biedt een samengestelde laagstack die precies voor dit probleem is ontworpen. De relevante lagen voor een C2-display:

ScatterplotLayer. Het werkpaard voor ruwe sporenposities. Rendert miljoenen punten op 60fps omdat elk attribuut (positie, kleur, straal) GPU-gebonden is. Gebruik het voor niet-gesymboliseerde sporendots, sensordekkingeringen en hoogdichtheid-vlakke lagen.

IconLayer. Rendert MIL-STD-2525-symbolen uit een textuuratlas. Prestaties schalen mee met de atlasgrootte; pak 2525-symboliek in een enkele 4096×4096-atlas met alle affiliatie/echelon-varianten vooraf gerasteriseerd. Bij 100k pictogrammen met één gedeelde atlas houdt IconLayer 60fps comfortabel vol op mid-tier hardware.

PathLayer. Voor sporenhistories en geprojecteerde koersen. Kosten schalen mee met het aantal vertices, niet met het aantal paden — verkies het decimeren van lange histories (Douglas-Peucker, met epsilon gebonden aan het zoomniveau) boven het weggooien van paden.

GPU-aggregatielagen. ScreenGridLayer, HexagonLayer en HeatmapLayer aggregeren miljoenen punten in bin-gesommeerde visualisaties op de GPU. Nuttig als zoom-out-dichtheidsoverlays — bij laag zoom wilt u geen 100k symbolen zien, u wilt de dreigingsdichtheidsgradiënt zien.

Level-of-Detail (LOD)-Strategie

De meest effectieve prestatieoptimalisatie is het niet renderen van wat de operator niet zinvol kan zien. Op een zoomniveau van 2000 km breed kan een 100k-sporendisplay geen individuele symbolen oplossen — elke schermpixel dekt meerdere sporen. Het weergeven van volledige MIL-STD-2525-symboliek bij dat zoom is verspilde GPU-tijd en produceert een onleesbaar beeld.

De LOD-ladder: bij ver zoom, render geaggregeerde dichtheid (GPU-gebinned heatmap of hex-laag); bij gemiddeld zoom, render niet-gesymboliseerde punten gekleurd op affiliatie; bij nabij zoom, render volledige 2525-symboliek met labels voor gemarkeerde sporen; bij maximaal zoom, render volledige symboliek met labels, historietrails en geprojecteerde koersen voor elk zichtbaar spoor.

Schermruimteclustering is het complement. Zelfs bij nabij zoom produceren dichte clusters (een parkeerplaats vol voertuigen, een haven vol schepen) overlappende symbolen die elkaar verbergen. Een k-d-boom- of rasterbinclustering-pass (uitgevoerd op de worker thread, niet de main thread) vouwt overlappende symbolen samen in een enkel "N sporen hier"-badge totdat de operator inzoomt.

Het gebruiksprofiel rechtvaardigt agressieve LOD: een operator zoomt één keer per 30–60 seconden in en besteedt het grootste deel van zijn tijd aan het scannen van het brede beeld. Het optimaliseren van het breedbeeld-framebudget levert continu rendement op; het optimaliseren van het close-zoom-framebudget levert alleen rendement op tijdens de momenten van actieve interesse.

GPU-Geheugenbudgetten

Geheugendruk is de stille moordenaar van hoogdichtheid-displays. Het zichtbare sporenaantal is slechts één deel van het budget. Textuuratlassen (symboolblad, terraintiles, basiskaart rastertiles), vertex-buffers (per-instance-attributen, historieenpaden), uniforme buffers, framebuffer-bijlagen en de eigen compositor van de browser putten allemaal uit dezelfde pool.

De praktijkbudgetten waartegen we plannen: een 4 GB geïntegreerde GPU (Intel Iris Xe, AMD Radeon 780M) op een geïmplementeerde laptop heeft ruwweg 2–2,5 GB bruikbaar voor de WebGL-context nadat het OS, de browser en andere tabs hun deel hebben opgenomen. Een 16 GB discrete GPU (RTX 4070/5070-klasse) heeft 12+ GB bruikbaar. Veel conservatieve implementaties — operatiecentra met geharde werkstations die jaren geleden zijn aangeschaft — draaien nog steeds iGPU-klasshardware. Ontwerpen voor de iGPU-envelop is de veiligere standaard.

Praktijkcijfers voor een 100k-sporen C2-dashboard: per-instance-attribuutbuffers ~5–10 MB; symboolatlas ~64 MB (4096² RGBA); basiskaart rastercache ~200–400 MB; terraintile-cache ~300–600 MB; historiepadgeometrie ~20–50 MB afhankelijk van retentie. Totaal ~600 MB–1,2 GB. Dat past in de iGPU-envelop met marge, maar alleen als elke laag gedisciplineerd is over textuurgroottes en bufferg groei.

Latentie en Frame-Pacing

Het framebudget van 16,67ms verdeelt zich ruwweg als volgt: 2–3ms voor invoerverwerking en toestandsmutatie, 4–6ms voor laagupdate (CPU-side, voornamelijk attribuutbufferherberekening en culling), 6–8ms voor GPU-rendering, 1–2ms voor compositor-overhead. Alles wat meer dan zijn aandeel verbruikt, steelt van het volgende aandeel en produceert een gemist frame.

De ergste pieken schuilen op plaatsen die gemakkelijk over het hoofd worden gezien. Spoorcoörrelatiejoins op de main thread — de correlatiepas van de fusiemotor inline met rendering uitvoeren — produceren 50–200ms stilstanden elke keer dat een nieuwe sensorbatch binnenkomt. De oplossing is om correlatie op een worker thread uit te voeren en onveranderlijke sporendeltas naar de main thread te sturen. Door server gepushte updatebursts (een websocket die 5.000 spoorenupdates in één tick dumpt) verzadigen de JS-eventloop; rate-limit en batch deltas tot één gebufferde update per frame.

Garbage collection is de derde pieken-bron. Het alloceren van nieuwe objecten per spoor per frame produceert zaagtand GC-pauzes elke paar seconden. Gebruik typed-array-pools en hergebruik buffers; vermijd per-frame-objectliteraalaanmaak in hot paths. De 60fps-of-niets-verwachting is reëel, en een enkele GC-pauze van 100ms elke 10 seconden is precies het soort haperingen dat het operatorvertrouwen vernietigt.

Testen op Schaal

U kunt geen 100k-sporendisplay verzenden zonder een testframework dat lasten op die schaal genereert en herhaalt. Drie componenten: een synthetische spoorgenerator, een geautomatiseerde FPS-regressiesuite en een opgenomen-weergave-waarheidsbron.

De synthetische generator produceert deterministische 100k-sporenschenario's — willekeurige distributies, dichte clusters, drone-zwermformaties, massale aanvalsscenario's — elk gezaaid zodat een CI-run elke keer dezelfde scène reproduceert. Elk scenario stuurt een hoofdloze browser door een geschreven camerapad (pannen, inzoomen, uitzoomen, opruimen, filteren) terwijl het framework performance.now()-delta-histogrammen samples en p50/p95/p99-frametijden rapporteert.

De FPS-regressiesuite draait bij elke PR. De drempelwaarden zijn expliciet: p95-frametijd onder 18ms, p99 onder 25ms, geen gemiste frames boven 50ms gedurende een scripted run van 60 seconden. Elke commit die de cijfers voorbij de drempel duwt, blokkeert de merge. Dit is de enige manier om de dood-door-duizend-sneden-regressies op te vangen waarbij elke individuele wijziging 0,2ms kost.

Opgenomen sensorweergave uit de echte wereld is de waarheidsbron. Synthetische lasten vangen prestatiekliffen maar missen de asymmetrische distributies van echte data — de clusters, de hiaten, de burstpatronen. Een pcap-stijl opname van een live oefensfeed, teruggespeld op kloktijdsnelheid tegen het dashboard, is het dichtst bij operationele belasting zonder een operator in de stoel. Combineer het synthetische framework met twee of drie opgenomen scènes van verificatieoefeningen en u heeft een regressienet dat standhoudt.