docs: native OpenXR coexistence PoC spec
This commit is contained in:
parent
77c18e8b84
commit
1cbcd19191
116
docs/POC_VR_SPEC.md
Normal file
116
docs/POC_VR_SPEC.md
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
# SPEC — VR Coexistence PoC (native OpenXR, Quest 3)
|
||||||
|
|
||||||
|
Apka pomiarowa, **nie produkt**. Cel: udowodnić, że na Quest 3 da się jednocześnie
|
||||||
|
renderować scenę VR/MR **i** uruchamiać lokalny LLM **i** mówić (TTS) — wszystko
|
||||||
|
w jednym procesie, offline — oraz **zmierzyć**, ile to kosztuje klatki, CPU/GPU,
|
||||||
|
pamięć i temperaturę.
|
||||||
|
|
||||||
|
To jest pionowy plasterek Etapu 2 z ROADMAP, zbudowany jako narzędzie do pomiaru.
|
||||||
|
|
||||||
|
## 1. Co mierzymy (sens istnienia apki)
|
||||||
|
Podczas pracy ciągłej (render) + cyklicznej inferencji + TTS zbieramy:
|
||||||
|
- **FPS / stale frames** renderera w oknie generacji vs. w spoczynku,
|
||||||
|
- **CPU/GPU utilization** i **temperaturę** w czasie (soak ~15 min),
|
||||||
|
- **latencję i throughput** inferencji pod obciążeniem renderera (tok/s),
|
||||||
|
- **PSS** całego procesu.
|
||||||
|
|
||||||
|
Pomiar: **OVR Metrics Tool** (nakładka FPS/CPU/GPU/temp) + log apki do logcat.
|
||||||
|
|
||||||
|
## 2. Kryteria „zielono" (do walidacji, nie gwarancje)
|
||||||
|
- Renderer utrzymuje docelowe **≥72 FPS** w trakcie generacji, bez kaskady stale frames.
|
||||||
|
- Generacja pod obciążeniem renderera **≥ ~10 tok/s** (standalone było 23,6 @4 wątki).
|
||||||
|
- Całość **< 5,75 GiB** PSS (limit aplikacji immersyjnej Quest 3).
|
||||||
|
- Brak throttlingu do poziomu nieużywalnego w ciągu 15 min ciągłej pracy.
|
||||||
|
|
||||||
|
## 3. Architektura
|
||||||
|
Natywny C++ / Android NDK, jeden proces, arm64-v8a.
|
||||||
|
|
||||||
|
Wątki:
|
||||||
|
- **Render thread** — pętla klatek OpenXR (passthrough + panel tekstowy). Nigdy nie
|
||||||
|
blokuje się na inferencji ani TTS.
|
||||||
|
- **Inference worker** — llama.cpp in-process, ładuje Qwen2.5-1.5B Q4 raz, generuje
|
||||||
|
na żądanie. **Cap 4 wątki** (wynik z benchmarku: 6 wątków regresuje przez rdzenie
|
||||||
|
efficiency). **CPU affinity konfigurowalna** (patrz §6).
|
||||||
|
- **TTS/audio thread** — sherpa-onnx (offline) syntezuje PCM, oddtwarzanie przez Oboe.
|
||||||
|
|
||||||
|
Przepływ na trigger: worker generuje polecenie → tekst trafia do render threada
|
||||||
|
(panel) i do TTS threada (mowa). Render leci nieprzerwanie przez cały czas.
|
||||||
|
|
||||||
|
## 4. Punkt startu i zależności
|
||||||
|
**NIE pisać OpenXR od zera.** Start z sample'a passthrough z **Meta OpenXR Mobile
|
||||||
|
SDK** (np. `XrPassthrough`); dokładamy worker LLM, TTS, panel i triggery.
|
||||||
|
|
||||||
|
Wendorowane zależności (jako podprojekty CMake / prebuilt arm64):
|
||||||
|
- **llama.cpp** — `add_subdirectory`, build arm64-v8a, OpenMP on, bez GPU. API biblioteczne
|
||||||
|
(`llama` + `common`), nie serwer.
|
||||||
|
- **sherpa-onnx** — prebuilt Android arm64 (`OfflineTts`), używa głosu **pl_PL-gosia**
|
||||||
|
(.onnx, ten sam co na PC). Ogarnia espeak-ng.
|
||||||
|
- **Oboe** — audio out (AAudio/OpenSL wrapper).
|
||||||
|
- **stb_truetype.h** — rasteryzacja tekstu polecenia do tekstury GL.
|
||||||
|
|
||||||
|
Model i głos NIE w APK: wgrywane na `/sdcard/Android/data/<pkg>/files/` (lub
|
||||||
|
`/data/local/tmp`) i ładowane po ścieżce. Model ~935 MiB.
|
||||||
|
|
||||||
|
## 5. Generator treści (zgodny z zasadą gramatyki)
|
||||||
|
Port leksykonu i reguł renderowania z **`docs/LEXICON.md`** + **`docs/SPEC.md` §5**
|
||||||
|
do C++ (OBJECTS/COLORS/LOCATIONS/VERBS + `render()`). Zasada bez zmian: powierzchniowy
|
||||||
|
tekst tylko z leksykonu.
|
||||||
|
|
||||||
|
Rola LLM tutaj jest podwójna:
|
||||||
|
1. realistyczne **obciążenie obliczeniowe** (prawdziwa generacja N tokenów),
|
||||||
|
2. wybór elementów polecenia.
|
||||||
|
|
||||||
|
Worker odpala prawdziwą generację llama.cpp z naszym promptem; jeśli uda się sparsować
|
||||||
|
JSON z ID — renderuje z nich, jeśli nie — fallback na losowy wybór z leksykonu.
|
||||||
|
Tak czy siak **gramatyka jest poprawna**, a obciążenie CPU realne (o to chodzi w pomiarze).
|
||||||
|
|
||||||
|
## 6. Kontrakt wątków i CPU affinity (najważniejsze)
|
||||||
|
To tu rozstrzyga się koegzystencja. Na XR2 Gen 2 (2P + 4E):
|
||||||
|
- Render thread + kompozycja: zostawiamy je runtime'owi, ale apka **nie** odpala
|
||||||
|
inferencji na tym samym rdzeniu.
|
||||||
|
- Inference worker: **maska CPU konfigurowalna w runtime** (`sched_setaffinity` /
|
||||||
|
`pthread_setaffinity_np`), domyślnie tak, by NIE zajmować rdzenia renderera.
|
||||||
|
Liczba wątków konfigurowalna (domyślnie 4), priorytet niższy niż render.
|
||||||
|
- TTS thread: osobny, niski priorytet.
|
||||||
|
|
||||||
|
Apka ma pozwalać zmieniać maskę i liczbę wątków bez rekompilacji (np. plik konfig
|
||||||
|
albo argumenty intentu), żeby zmierzyć FPS przy różnych podziałach rdzeni.
|
||||||
|
|
||||||
|
## 7. UI / interakcja (minimalne)
|
||||||
|
- Passthrough on, jeden pływający panel przed użytkownikiem.
|
||||||
|
- Panel pokazuje: aktualne polecenie + live odczyt (ostatnia inferencja ms, tok/s,
|
||||||
|
przybliżony FPS, PSS).
|
||||||
|
- Trigger generacji: **przycisk kontrolera** (np. A) oraz opcjonalny **auto-loop**
|
||||||
|
co N sekund (do soak-testu).
|
||||||
|
Bez obiektów 3D, bez scen — tylko panel. Render ma głównie *istnieć i trzymać FPS*.
|
||||||
|
|
||||||
|
## 8. Logowanie i soak
|
||||||
|
Apka loguje do logcat na każdą generację: liczba tokenów, ms, tok/s, peak VmRSS
|
||||||
|
(`/proc/self/status`). Tryb soak: auto-trigger co ~10 s przez 15 min; równolegle
|
||||||
|
OVR Metrics Tool zapisuje FPS/CPU/GPU/temp. Wynik = wykres FPS i temperatury w czasie.
|
||||||
|
|
||||||
|
## 9. Budżet pamięci (orientacyjnie, < 5,75 GiB)
|
||||||
|
- model Q4: ~1,1 GiB
|
||||||
|
- KV cache (krótki kontekst): ~50–150 MiB
|
||||||
|
- onnxruntime + głos Piper: ~0,1 GiB
|
||||||
|
- OpenXR runtime + GL + tekstury: ~0,3–0,5 GiB
|
||||||
|
- razem: ~1,6–1,9 GiB → duży zapas pod limitem.
|
||||||
|
|
||||||
|
## 10. Build / deploy
|
||||||
|
Android NDK + CMake + Gradle. `arm64-v8a` only. Loader OpenXR z Meta SDK. Podpis
|
||||||
|
debug, `adb install`. Min SDK zgodny z Horizon OS (API 29+). Plan budowy fazami
|
||||||
|
(patrz niżej), nie jednym strzałem.
|
||||||
|
|
||||||
|
## 11. Poza zakresem
|
||||||
|
STT, ocena wykonania, UX terapeutyczny, obiekty 3D, hand tracking, pakowanie do Store.
|
||||||
|
|
||||||
|
## 12. Fazy budowy (dla Claude Code)
|
||||||
|
1. **Scaffold** — sample passthrough Meta uruchomiony na Queście (czysty render, FPS w OVR).
|
||||||
|
2. **LLM** — wlinkować llama.cpp, worker ładuje model, generacja na trigger (log tok/s),
|
||||||
|
na razie tekst tylko do logcat.
|
||||||
|
3. **Affinity** — maska/wątki konfigurowalne; zmierzyć wpływ na FPS.
|
||||||
|
4. **TTS** — sherpa-onnx + Oboe, wypowiada polecenie.
|
||||||
|
5. **Panel** — stb_truetype, tekst + live odczyt na panelu.
|
||||||
|
6. **Soak + pomiar** — auto-loop, logowanie, 15-min test.
|
||||||
|
|
||||||
|
Każdą fazę walidujemy na urządzeniu zanim ruszymy dalej.
|
||||||
Loading…
Reference in a new issue