Les tableaux de bord C2 modernes n'affichent plus des centaines de pistes. Ils en affichent des dizaines de milliers. Images aériennes issues de réseaux radar fédérés, flux maritimes AIS, télémétrie d'essaims de drones, pings GPS d'unités au sol et pistes fusionnées multi-capteurs arrivent toutes sur le même écran d'opérateur. Une image opérationnelle commune exploitable en 2026 dépasse régulièrement 100 000 pistes simultanées. Soit la couche de visualisation tient 60 fps sous cette charge, soit l'opérateur perd confiance dans le système.

Cet article est un guide d'ingénierie sur la façon de maintenir les budgets d'image honnêtes à cette échelle. Nous couvrons l'instanciation WebGL, le batching de primitives Cesium, la composition de couches deck.gl, la stratégie LOD, les budgets mémoire GPU, l'arithmétique de cadencement et le harnais de test nécessaire pour détecter les régressions avant qu'un opérateur ne le fasse.

La réalité des 100 000 pistes

L'ancienne règle empirique — « un affichage tactique doit gérer quelques milliers de pistes » — était correcte en 2010 et dangereusement erronée en 2026. Les flux de fusion multi-capteurs OTAN, les grilles radar à basse altitude et les agrégateurs commerciaux ADS-B/AIS produisent régulièrement des comptages de pistes à six chiffres pendant les exercices et les opérations actives. La seule doctrine d'essaim de drones peut placer 200 à 500 émetteurs amis dans une boîte de 50 km.

L'écart de performance est important parce que la confiance de l'opérateur s'effondre en dessous de 30 fps. Dès que le panoramique saccade ou que la symbologie est en retard par rapport à la mise à jour de la piste sous-jacente, l'opérateur se méfie de tout le reste de l'affichage — et commence à vérifier l'image par rapport à une carte papier ou un écran secondaire. Ce temps de vérification croisée est exactement la latence opérationnelle que le système C2 a été conçu pour éliminer. Un tableau de bord C2 qui tombe en dessous de 60 fps sous une charge réaliste n'est pas un tableau de bord C2 lent. C'est un tableau de bord cassé.

L'objectif est fixe : 60 fps soutenus, avec 100 000+ pistes, sur un poste d'opérations typique de gamme intermédiaire (CPU 8 cœurs, GPU dédié milieu de gamme, 1440p). Atteindre cet objectif exige que chaque couche de la pile — géométrie, appels de dessin, mémoire GPU, ingestion réseau, mutation d'état — respecte le budget d'image de 16,67 ms.

Instanciation WebGL

Le premier levier est l'appel de dessin. Émettre 100 000 dessins individuels par image est impossible à 60 fps sur tout GPU actuel ; la surcharge du pilote côté CPU à elle seule consume tout le budget. Le rendu instancié regroupe des milliers de symboles en un seul appel de dessin. Une géométrie (le maillage du symbole), un shader, et un buffer d'attributs par instance portant position, cap, affiliation et identifiant de symbole.

Le modèle standard utilise ANGLE_instanced_arrays en WebGL 1 ou drawArraysInstanced natif en WebGL 2. Les attributs par instance circulent depuis un buffer compacté : typiquement 32 octets par piste (vec3 position, vec2 vélocité, uint32 flags compactés). À 100 000 pistes cela représente 3,2 Mo de données d'attributs de sommets — assez petit pour être ré-uploadé chaque image si nécessaire, bien que les mises à jour partielles via bufferSubData soient moins coûteuses.

Three.js expose l'instanciation via InstancedMesh ; deck.gl la gère nativement pour presque toutes les couches ; l'API Primitive de Cesium la prend en charge via des tableaux GeometryInstance. Les trois frameworks se situent sur le même spectre — Three.js donne le plus de liberté et le moins de math géospatiale incluse, deck.gl est le chemin le plus rapide vers une couche haute densité fonctionnelle, Cesium fournit la sémantique de globe 3D et l'occlusion du terrain que les deux autres n'ont pas.

Batching de primitives Cesium

L'API Entity de Cesium est le mauvais outil au-dessus de 5 000 pistes. Les entités allouent des objets JavaScript par piste, exécutent une boucle de mise à jour côté CPU et reconstruisent la géométrie lors des changements de propriété. Le coût est amorti correctement à petite échelle et catastrophique à grande échelle.

Pour le rendu à l'échelle des 100k, descendez à l'API Primitive. Une PointPrimitiveCollection affiche jusqu'à ~1 M de points en un seul appel de dessin. Une BillboardCollection gère des dizaines de milliers de sprites texturés avec un atlas de texture partagé. Le motif GeometryInstance regroupe des milliers de géométries statiques (par ex. cercles de portée, géoclôtures) en une primitive batchée au moment de la création.

Le rendu d'étiquettes est asymétrique. Une PointPrimitiveCollection de 100k se rend sans effort ; ajouter 100k étiquettes par-dessus casse instantanément le budget d'image. Les étiquettes passent par le chemin de texte SDF de Cesium, qui coûte à la fois la mémoire de l'atlas de glyphes et un dessin batché séparé. La solution est la visibilité des étiquettes pilotée par LOD : ne rendre les étiquettes que pour les pistes situées dans un rayon écran dépendant du zoom autour du curseur ou pour les pistes signalées « d'intérêt » par le moteur de fusion. Un écran d'opérateur typique n'a pas besoin de plus de 50 à 200 étiquettes visibles à la fois.

Couches deck.gl pour la défense

deck.gl se place au-dessus d'une carte de base MapboxGL ou MapLibre et fournit une pile de couches composables conçue exactement pour ce problème. Les couches pertinentes pour un affichage C2 :

ScatterplotLayer. Le cheval de bataille pour les positions brutes des pistes. Affiche des millions de points à 60 fps parce que chaque attribut (position, couleur, rayon) est lié au GPU. À utiliser pour les points de piste non symbolisés, les cercles de couverture de capteurs et les couches plates haute densité.

IconLayer. Rend les symboles MIL-STD-2525 depuis un atlas de texture. La performance suit la taille de l'atlas ; empaquetez la symbologie 2525 dans un seul atlas 4096×4096 avec toutes les variantes d'affiliation/échelon pré-rastérisées. À 100k icônes avec un atlas partagé unique, IconLayer maintient confortablement 60 fps sur du matériel milieu de gamme.

PathLayer. Pour les historiques de piste et les trajectoires projetées. Le coût évolue avec le nombre de sommets, pas le nombre de chemins — privilégier la décimation des longs historiques (Douglas-Peucker, avec epsilon lié au niveau de zoom) plutôt que de supprimer des chemins.

Couches d'agrégation GPU. ScreenGridLayer, HexagonLayer et HeatmapLayer agrègent des millions de points en visualisations binaires sommées sur le GPU. Utiles comme superpositions de densité en dézoom — à faible zoom vous ne voulez pas voir 100k symboles, vous voulez voir le gradient de densité de menace.

Stratégie de niveau de détail (LOD)

L'optimisation de performance la plus efficace est de ne pas rendre ce que l'opérateur ne peut pas voir de manière significative. À un niveau de zoom large de 2000 km, un affichage de 100k pistes ne peut pas résoudre les symboles individuels — chaque pixel d'écran couvre plusieurs pistes. Rendre la symbologie MIL-STD-2525 complète à ce zoom est du temps GPU gaspillé et produit une image illisible.

L'échelle LOD : au zoom éloigné, rendre la densité agrégée (carte de chaleur GPU binée ou couche hexagonale) ; au zoom moyen, rendre des points non symbolisés colorés par affiliation ; au zoom rapproché, rendre la symbologie 2525 complète avec étiquettes pour les pistes signalées ; au zoom maximal, rendre la symbologie complète avec étiquettes, traces d'historique et trajectoires projetées pour chaque piste visible.

Le clustering en espace écran est le complément. Même au zoom rapproché, les amas denses (un parking de véhicules, un port plein de navires) produisent des symboles superposés qui se masquent mutuellement. Une passe de clustering par k-d tree ou bin-grille (exécutée sur le thread worker, pas le thread principal) réduit les symboles superposés à un seul badge « N pistes ici » jusqu'à ce que l'opérateur zoome.

Le profil d'utilisation justifie un LOD agressif : un opérateur zoome une fois toutes les 30 à 60 secondes et passe la plupart de son temps à scruter l'image large. Optimiser le budget d'image de la vue large est payant en continu ; optimiser le budget d'image en zoom rapproché ne paie qu'aux moments d'intérêt actif.

Budgets mémoire GPU

La pression mémoire est le tueur silencieux des affichages haute densité. Le nombre de pistes visibles n'est qu'une partie du budget. Les atlas de texture (feuille de symboles, tuiles de terrain, tuiles raster de carte de base), les buffers de sommets (attributs par instance, chemins d'historique), les buffers d'uniformes, les attachements de framebuffer et le compositeur du navigateur lui-même puisent tous dans le même pool.

Les budgets réels que nous planifions : un GPU intégré de 4 Go (Intel Iris Xe, AMD Radeon 780M) sur un ordinateur portable déployé a environ 2 à 2,5 Go utilisables pour le contexte WebGL après que l'OS, le navigateur et les autres onglets ont pris leur part. Un GPU discret de 16 Go (classe RTX 4070 / 5070) a 12+ Go utilisables. De nombreux déploiements conservateurs sans tête — centres d'opérations avec des stations durcies achetées il y a des années — utilisent encore du matériel de classe iGPU. Concevoir pour l'enveloppe iGPU est le défaut le plus sûr.

Chiffres pratiques pour un tableau de bord C2 de 100k pistes : buffers d'attributs par instance ~5–10 Mo ; atlas de symboles ~64 Mo (4096² RGBA) ; cache raster de carte de base ~200–400 Mo ; cache de tuiles de terrain ~300–600 Mo ; géométrie des chemins d'historique ~20–50 Mo selon la rétention. Total ~600 Mo–1,2 Go. Cela rentre dans l'enveloppe iGPU avec marge, mais seulement si chaque couche est disciplinée sur les tailles de texture et la croissance des buffers.

Latence et cadencement d'image

Le budget d'image de 16,67 ms se décompose grossièrement : 2–3 ms pour la gestion des entrées et la mutation d'état, 4–6 ms pour la mise à jour de couche (côté CPU, surtout recalcul des buffers d'attributs et culling), 6–8 ms pour le rendu GPU, 1–2 ms pour la surcharge du compositeur. Tout ce qui consomme plus que sa tranche vole à la tranche suivante et produit une image perdue.

Les pires pics se cachent dans des endroits faciles à négliger. Les jointures de corrélation de pistes sur le thread principal — exécution de la passe de corrélation du moteur de fusion en ligne avec le rendu — produisent des blocages de 50–200 ms chaque fois qu'un nouveau lot de capteurs arrive. La solution est d'exécuter la corrélation sur un thread worker et de poster des deltas de pistes immuables au thread principal. Les rafales de mises à jour poussées par le serveur (un websocket vidant 5 000 mises à jour de pistes en un tick) saturent la boucle d'événements JS ; limitez le débit et regroupez les deltas en une seule mise à jour bufferisée par image.

La collecte de déchets est la troisième source de pics. Allouer de nouveaux objets par piste par image produit des pauses GC en dent de scie toutes les quelques secondes. Utilisez des pools de tableaux typés et réutilisez les buffers ; évitez la création de littéraux d'objet par image dans les chemins chauds. L'attente du 60-fps-ou-rien est réelle, et une seule pause GC de 100 ms toutes les 10 secondes est exactement le type de saccade qui détruit la confiance de l'opérateur.

Tests à l'échelle

Vous ne pouvez pas livrer un affichage de 100k pistes sans un harnais de test qui génère et rejoue des charges à cette échelle. Trois composants : un générateur de pistes synthétiques, une suite de régression FPS automatisée, et une source de vérité de rejeu enregistré.

Le générateur synthétique produit des scénarios déterministes de 100k pistes — distributions aléatoires, amas denses, formations d'essaims de drones, scénarios de raid massif — chacun ensemencé pour qu'un run CI reproduise la même scène à chaque fois. Chaque scénario pilote un navigateur sans tête à travers un parcours de caméra scripté (panoramique, zoom avant, zoom arrière, déclutter, filtrage) tandis que le harnais échantillonne les histogrammes de delta performance.now() et rapporte les temps d'image p50/p95/p99.

La suite de régression FPS s'exécute sur chaque PR. Les seuils sont explicites : temps d'image p95 sous 18 ms, p99 sous 25 ms, aucune image perdue dépassant 50 ms sur un run scripté de 60 secondes. Tout commit qui pousse les chiffres au-delà du seuil bloque le merge. C'est la seule façon d'attraper les régressions par mille petites coupures où chaque changement individuel coûte 0,2 ms.

Le rejeu de capteurs enregistré en conditions réelles est la source de vérité. Les charges synthétiques attrapent les falaises de perf mais ratent les distributions asymétriques des données réelles — les amas, les trous, les motifs de rafale. Un enregistrement de type pcap d'un flux d'exercice live, rejoué à vitesse wall-clock contre le tableau de bord, est ce qu'on peut faire de plus proche d'une charge opérationnelle sans opérateur sur le siège. Associez le harnais synthétique à deux ou trois scènes enregistrées issues d'exercices de vérification et vous avez un filet de régression qui tient.