← Viewer

Streaming-Architektur

Server-Side Rendering: Vulkan + CUDA Interop + NVENC → WebSocket → WebCodecs → Canvas

Live Status
—
Render Service
—
Transport
—
Sessions
Netzwerk-Topologie (Prisma-konform)
Browser
<canvas> + WebCodecs
WebSocket (H.264 binary)
TCP :443 (TLS)
CloudFront
TLS Termination
viewer.<zone>
ALB (internal)
/ws/render + /ws/input
WebSocket upgrade
ECS GPU Task (g5.xlarge / g4dn.xlarge)
Render Service
Vulkan + CUDA + NVENC
:8080 HTTP · :8081 WS
Node.js API
WS Proxy + S3 Loader
:3000 HTTP
Private Subnet (keine public IP)
VPC Endpoint
S3 Asset Storage
.vq3d (preprocessed) · .CATPart (source)
WebSocket Transport
Protokoll WebSocket über TCP/TLS (:443)
Pfad Browser → CloudFront → ALB → Node.js :3000 → render-service :8081
Video WS binary (1-byte type + H.264 NALUs) → WebCodecs VideoDecoder → Canvas
Input WebSocket JSON (/ws/input)
Latenz (typisch) ~50 ms (encode ~3ms + TCP + decode ~10ms)
Firewall Nur HTTPS/443 — funktioniert in allen Corporate Networks
Prisma-Compliance Ja — Private Subnets, keine public IPs
Connection-Flow (WebSocket)
Browser generiert Session-ID ws-{timestamp}-{random}
WebSocket connect /ws/render?sessionId=X für Video-Frames
WebSocket connect /ws/input?sessionId=X für Input-Commands
Node.js proxy → render-service :8081 (Session wird automatisch erstellt)
Video: H.264 binary frames → WebCodecs decode → Canvas
Input: JSON messages (camera, pick, etc.) mit 60 Hz
Render-Pipeline (pro Frame)
Input empfangen (WebSocket JSON) → Camera-Matrix updaten
BVH Frustum + Occlusion Culling → sichtbare Draw-Calls
Vulkan Render Pass (Mesh + Pick-Buffer)
CUDA Interop Zero-Copy Export (LINEAR tiled framebuffer)
NVENC H.264 Encode (Main Profile, CABAC, 15 Mbps, ~3ms)
WebSocket binary send (1-byte type prefix + NALUs)
Komponenten

Render Service (C++)

ws_transport.cpp
WebSocket Server (:8081) — H.264 binary frames + JSON messages, RFC 6455
vulkan_renderer.cpp
Vulkan render pipeline — mesh + pick-buffer pass, BVH culling integration
cuda_interop.cpp
Zero-Copy Vulkan→CUDA Export — cuImportExternalMemory, LINEAR tiled framebuffer, ~3ms encode @ 50fps
nvenc_encoder.cpp
NVIDIA Hardware-Encoder — H.264 Main Profile (CABAC), 15 Mbps, GOP 30
session.cpp
Render-Loop mit Condition-Variable (wake-on-input), Camera-State, freie Orbit-Rotation (Pole-Crossing mit Up-Flip). Jede Session besitzt ihren eigenen VulkanRenderer — kein geteilter Scene- oder GPU-State. MAX_SESSIONS = 4 (env-überschreibbar bis 64).
http_signaling.cpp — parseRequest
Ursache des VEH1-SIGSEGV (behoben). Der Body wurde mit der ungeprüften Content-Length aus dem Header gelesen — über das Ende eines 64 KiB-Stack-Arrays hinaus (reproduziert: Exit 139). Zusätzlich warf std::stoi ungefangen in einem detached Thread (Exit 134). Jetzt auf die real empfangenen Bytes geklemmt, std::stol in try/catch, Catch-all im Thread. Der Health-Check pollt dauerhaft — deshalb trat es nur bei langen Ladevorgängen auf.
main.cpp — crashHandler
Async-signal-safer Backtrace (backtrace_symbols_fd) für SIGSEGV/SIGABRT/SIGBUS/SIGFPE, dauerhaft installiert — hat den Fehler oben gefunden. Build mit -g -fno-omit-frame-pointer -rdynamic, Binary wird nicht gestrippt.
vq3d_loader.cpp
Proprietäres .vq3d Binary-Format laden (Vertices, Normals, Indices, BVH, Part-IDs, Nahtlinien). Edge-Ranges werden nach dem Laden auf die real gelesenen Vertices geklemmt — sonst zeichnet die GPU über das Pufferende hinaus.

Infrastruktur (CDK)

ecs-service.ts
EC2 GPU ASG (g5/g4dn Mixed Instances) in Private Subnets, aktuell min 1 / max 1 — keine horizontale Skalierung. ActiveSessions ist nur Dashboard-Metrik, an keine Scaling-Policy gebunden.
alb.ts
Internal ALB mit WebSocket support, CloudFront origin header validation
cloudfront.ts
CloudFront Distribution — Static Assets (S3) + API (ALB origin), TLS
ingest-pipeline.ts
S3-Event-triggered ECS Task — CATPart → .vq3d Konvertierung
Offline Pipeline (CAD → Render-Ready)
CATPart aus S3 catparts/ — auf Anfrage (on-demand) oder Bulk-Pipeline
CatPartReader (TypeScript) — Binary-Parser, VizProxy + CGR + Block Extraction, 99.5% Coverage
Mesh Optimization — Vertex Cache, Overdraw Reduction, Index Reordering
BVH Generation — SAH-Split, für Frustum/Occlusion Culling + GPU Picking
.vq3d Export → S3 vq3d/ cached — kompaktes Binary (Vertices, Normals, Indices, BVH, Part-IDs, Face-IDs)
Optimierungen — Status
Maßnahme Status Implementierung
Import/Render getrennt
Render-Worker lädt nur .vq3d, nie CATPart
Aktiv Ingest-Pipeline → S3 → vq3d_loader.cpp
Mesh-Cache (S3 + SSD)
SHA-basiert, 2. Aufruf < 500ms
Aktiv s3-loader.ts (ETag), asset_cache.cpp (LRU)
BVH + Frustum/Occlusion Culling
SAH-BVH, nur sichtbare Draw-Calls
Aktiv bvh-builder.ts (offline), bvh_culler.cpp (runtime)
GPU-freundliches Binary
Interleaved Pos+Normal+PartID, zero-copy upload
Aktiv .vq3d Format, vq3d_loader.cpp
CUDA Interop Zero-Copy Encoding
Vulkan→CUDA→NVENC ohne CPU-Roundtrip, ~3ms/frame @ 50fps
Aktiv cuda_interop.cpp + nvenc_encoder.cpp
Variable FPS (wake-on-input)
Idle: kein Rendern. Input → sofort 60fps
Aktiv session.cpp (condition variable)
GPU Picking
Part-ID + Face-ID Render Buffer, <5ms
Aktiv pick_buffer.cpp
Persistent GPU Workers
Warme GPU, kompilierte Shader, kein Cold-Start
Aktiv ECS EC2 GPU Tasks (langlebig)
WebSocket Transport
~50ms Latenz, Prisma-konform (private Subnets)
Aktiv ws_transport.cpp + WebCodecs (Browser)
On-demand Konvertierung
Kein Vorverarbeiten nötig — Part wird bei erstem Zugriff konvertiert + gecacht
Aktiv resolveVq3d() → CatPartReader → S3
Auto-Connect + Reconnect
Viewer verbindet automatisch, kein manueller Button
Aktiv viewer.html (WebSocket, 3s Retry)
Freie Orbit-Rotation
Keine vertikale Begrenzung, Up-Vektor flippt an Polen
Aktiv session.cpp (processOrbit)
meshoptimizer
Vertex-Cache, Overdraw, Fetch-Optimierung
Geplant In Pipeline nach Tessellierung
Dynamische Deflection
BBox-abhängig statt konstant → 50-80% weniger Polygone
Geplant OCCT BRepMesh parameter
Progressive LOD
Grobes Mesh sofort, verfeinern im Hintergrund
Geplant Multi-LOD im .vq3d Format
GPU Instancing
Identische Parts (Schrauben etc.) nur 1x uploaden
Geplant Part-Hash → Instance-Gruppen
Performance-Budget (Ziel)

Offline (Ingest)

CATPart → .vq3d
Vorverarbeitung: Parse + Tessellierung + BVH + Export. Einmalig pro Datei. Ergebnis in S3 cached.
Ziel: < 30s pro Part
Abhängig von Komplexität. Einfache Parts: 2-5s. Komplexe NURBS-Flächen: bis 30s.

Runtime (Streaming)

.vq3d → erstes Bild
Laden von S3/SSD-Cache + GPU-Upload + erster Frame. Kein Parsing, keine Tessellierung.
Fahrzeug laden< 3s
Kamera-Interaktion< 50ms
Picking< 5ms
Frame-Rate60 fps
Architekturprinzip: Import ≠ Render
Import (offline, einmalig)
CATPart
CATIA V5 Binary
▼
CatPartReader
VizProxy/CGR/Block Extraction
▼
Mesh Optimization + BVH
SAH-Partitionierung
▼
.vq3d → S3
Gecacht, on-demand
Sekunden pro Part · Ergebnis gecacht
Render (runtime, pro Session)
.vq3d → GPU Upload
Zero-Parse, direkt in VkBuffer · Buffer wachsen geometrisch (1,5× + 4 MiB), nur der neue Teil wird übertragen
▼
BVH Frustum + Occlusion Culling
Nur sichtbare Draw-Calls
▼
Vulkan + CUDA Interop + NVENC
Zero-Copy Encoding ~3ms
▼
WebSocket → Browser
Video-Stream, kein Mesh
Millisekunden pro Frame · 60 fps