Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes

- A 3D attempt that ends at the 640x480 fallback is remembered for that glasses
  model, so Play SteamVR no longer blacks out the glasses after every restart;
  Retry clears it and "3D for VR games" stays reachable
- A VITURE Mobile Dock on its own no longer counts as glasses; through a dock
  Play SteamVR is hidden and the sheet shows the output and why there is no control
- HDR is probed on the glasses when the session starts there
- Glasses frames with a mismatched checksum are marked in the status frame log
- SteamVR layer and driver vblank counters stay monotonic across refresh changes;
  one-eye render scale applies from activation and on mono/3D switches
- Compositor binds announce the refresh clients were last told; the presenter
  retries a failed first swapchain
- Dead strings and helpers removed; docs updated
This commit is contained in:
maxjivi05 committed 2026-10-14 11:30:22 -04:00
1 parent 0a1dd16647
commit bdf876d47c
47 files changed
+138 -110

No files matched your search

@@ -1523,7 +1523,7 @@ static void bind_output(struct wl_client *c, void *data, uint32_t ver, uint32_t
wl_list_insert(&g_outputs, wl_resource_get_link(r));
wl_output_send_geometry(r, 0, 0, 340, 190, WL_OUTPUT_SUBPIXEL_UNKNOWN,
"DroidDeck", "Wayland", WL_OUTPUT_TRANSFORM_NORMAL);
output_send_mode(r, atomic_load(&g_output_refresh_mhz));
output_send_mode(r, g_output_announced_refresh_mhz);
if (ver >= WL_OUTPUT_NAME_SINCE_VERSION) {
wl_output_send_name(r, "DroidDeck-1");
wl_output_send_description(r, "DroidDeck display");
@@ -1283,7 +1283,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
if (!CompositorHost.isStarted) {
val mode = SessionPrefs.prefMode(intent.getStringExtra(SessionService.EXTRA_MODE) ?: SessionService.MODE_STEAM)
val probe = HdrSupport.probe(this)
val probe = HdrSupport.probe(this, glassesScreen?.display)
WaylandCompositor.nativeSetHdrDisplay(
probe.displayId, probe.name, probe.formats, probe.hdr10,
probe.maxLuminance, probe.maxAverageLuminance, probe.minLuminance,
@@ -97,7 +97,7 @@ object Glasses {
private const val TAG = "Glasses"
private const val ACTION_PERMISSION = "com.droiddeck.launcher.glasses.USB_PERMISSION"
private const val PREFS = "glasses"
private const val KEY_SBS_SUPPORT = "sbsSupport"
private const val KEY_SBS_FAILED = "sbsFailed"
private const val SBS_CONFIRM_MS = 20_000L
private val _state = MutableStateFlow(GlassesState())
@@ -133,7 +133,6 @@ object Glasses {
usb = app.getSystemService(Context.USB_SERVICE) as UsbManager
displays = app.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
_settings.value = load()
app.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().remove(KEY_SBS_SUPPORT).apply()
handler = Handler(HandlerThread("droiddeck-glasses").apply { start() }.looper)
handler.post {
val filter = IntentFilter(ACTION_PERMISSION).apply {
@@ -180,7 +179,10 @@ object Glasses {
modeState.manual()
val s = _state.value
if (!s.canControl || (on && s.noSignal)) return@post
if (on) rememberSbs(GlassesState.SBS_UNKNOWN)
if (on) {
markSbsFailed(linkDevice?.productId, false)
rememberSbs(GlassesState.SBS_UNKNOWN)
}
sendMode(on)
}
@@ -213,7 +215,11 @@ object Glasses {
sendFilm(on)
}
fun setSbsSupport(support: Int) = post { rememberSbs(support.coerceIn(GlassesState.SBS_FAILS, GlassesState.SBS_WORKS)) }
fun setSbsSupport(support: Int) = post {
val s = support.coerceIn(GlassesState.SBS_FAILS, GlassesState.SBS_WORKS)
markSbsFailed(linkDevice?.productId, s == GlassesState.SBS_FAILS)
rememberSbs(s)
}
fun setGameOnGlasses(on: Boolean) = post { update { it.copy(gameOnGlasses = on) } }
fun setHeadTracking(on: Boolean) = post { update { it.copy(headTracking = on) } }
fun setAuto3d(on: Boolean) = post { update { it.copy(auto3d = on) } }
@@ -350,7 +356,7 @@ object Glasses {
GlassesModeState.Device(it.productId, if (permitted) runCatching { it.serialNumber }.getOrNull() else null)
}
if (modeState.updateDevice(identity, SystemClock.uptimeMillis())) {
rememberSbs(GlassesState.SBS_UNKNOWN)
rememberSbs(if (identity != null && sbsFailed(identity.productId)) GlassesState.SBS_FAILS else GlassesState.SBS_UNKNOWN)
}
if (device == null || (linkDevice != null && linkDevice?.deviceName != device.deviceName)) closeLink()
model = m
@@ -363,16 +369,20 @@ object Glasses {
when (modeState.observe(sbsByDisplay, SystemClock.uptimeMillis())) {
GlassesModeState.Confirmation.FAILED -> {
Log.w(TAG, "3D signal was not confirmed at ${width}x$height; restoring 2D")
if (noSignal) markSbsFailed(device?.productId, true)
rememberSbs(GlassesState.SBS_FAILS)
}
else -> Unit
}
if (sbsByDisplay) rememberSbs(GlassesState.SBS_WORKS)
if (sbsByDisplay) {
markSbsFailed(device?.productId, false)
rememberSbs(GlassesState.SBS_WORKS)
}
if (controllable && device != null && !permitted && (ask || !asked) && _state.value.permission != Permission.ASKING) askPermission(device)
if (device != null && permitted && link == null) openLink(device)
publish {
it.copy(
present = devices.isNotEmpty() || display != null,
present = device != null || display != null,
model = m?.name ?: display?.let(::displayName) ?: "VITURE",
controllable = controllable,
permission = when {
@@ -464,6 +474,17 @@ object Glasses {
publish { it.copy(sbsSupport = support) }
}
private fun sbsFailed(productId: Int): Boolean =
app.getSharedPreferences(PREFS, Context.MODE_PRIVATE).getStringSet(KEY_SBS_FAILED, null)?.contains(productId.toString(16)) == true
private fun markSbsFailed(productId: Int?, failed: Boolean) {
if (productId == null || sbsFailed(productId) == failed) return
val prefs = app.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
val ids = prefs.getStringSet(KEY_SBS_FAILED, null).orEmpty()
val id = productId.toString(16)
prefs.edit().putStringSet(KEY_SBS_FAILED, if (failed) ids + id else ids - id).apply()
}
private fun applyMode(force: Boolean) {
val s = _state.value
val m = model ?: return
@@ -521,9 +542,9 @@ object Glasses {
private fun linkListener(generation: Long) = object : VitureLink.Listener {
override fun onFrame(frame: VitureProtocol.Frame) = post {
if (generation != linkGeneration) return@post
val line = "%s 0x%04X %s".format(
val line = "%s 0x%04X %s%s".format(
when (frame.magic) { VitureProtocol.MAGIC_REPORT -> "FD"; VitureProtocol.MAGIC_COMMAND -> "FE"; else -> "FC" },
frame.msgId, frame.payload.joinToString("") { "%02X".format(it) },
frame.msgId, frame.payload.joinToString("") { "%02X".format(it) }, if (frame.crcOk) "" else " crc!",
)
synchronized(frames) {
frames.addLast(line)
@@ -85,14 +85,6 @@ object VitureProtocol {
return Model(name, family)
}
fun modeIsSbs(mode: Int): Boolean = mode == 0x32 || mode == 0x35 || mode == 0x42 || mode == 0x45
fun modeHz(mode: Int): Int = when (mode) {
0x34, 0x44 -> 120
0x33, 0x35, 0x43, 0x45 -> 90
else -> 60
}
private val CRC_TABLE = IntArray(256) { i ->
var c = i shl 8
repeat(8) { c = if (c and 0x8000 != 0) (c shl 1) xor 0x1021 else c shl 1 }
@@ -92,11 +92,13 @@ internal fun GlassesGroups(
stringResource(if (g.sbs) R.string.glasses_mode_3d else R.string.glasses_mode_2d)) else null
when {
g.noSignal -> SettingsRow(stringResource(R.string.glasses_output), stringResource(R.string.glasses_no_signal)) {}
!g.controllable && g.present && g.permission == Glasses.Permission.NONE && !g.connected ->
SettingsRow(stringResource(R.string.glasses_control), status ?: stringResource(R.string.glasses_no_control)) {}
!g.controllable -> {
if (status != null) SettingsRow(stringResource(R.string.glasses_output), status) {}
SettingsRow(stringResource(R.string.glasses_control), stringResource(R.string.glasses_no_control)) {}
}
g.permission == Glasses.Permission.ASKING ->
SettingsRow(stringResource(R.string.glasses_control), stringResource(R.string.glasses_asking)) {}
!g.connected && g.controllable -> ActionRow(stringResource(R.string.glasses_control),
!g.connected -> ActionRow(stringResource(R.string.glasses_control),
stringResource(if (g.permission == Glasses.Permission.DENIED) R.string.glasses_denied else R.string.glasses_allow_hint),
stringResource(R.string.glasses_allow), { Glasses.requestControl() })
status != null -> SettingsRow(stringResource(R.string.glasses_output), status) {}
@@ -115,9 +117,9 @@ internal fun GlassesGroups(
g.sbs, chipModifier = track("glasses-3d")) { Glasses.set3d(it) }
if (g.sbs90) ChoiceRow(host, "glasses-3d-hz", stringResource(R.string.glasses_3d_refresh), null,
listOf(60, 90).map { it to "$it Hz" }, s.sbsHz, chipModifier = track("glasses-3d-hz")) { Glasses.setSbsHz(it) }
ToggleRow(host, "glasses-auto3d", stringResource(R.string.glasses_auto3d), stringResource(R.string.glasses_auto3d_hint),
s.auto3d, chipModifier = track("glasses-auto3d")) { Glasses.setAuto3d(it) }
}
ToggleRow(host, "glasses-auto3d", stringResource(R.string.glasses_auto3d), stringResource(R.string.glasses_auto3d_hint),
s.auto3d, chipModifier = track("glasses-auto3d")) { Glasses.setAuto3d(it) }
ChoiceRow(host, "glasses-picture", stringResource(R.string.glasses_picture), stringResource(R.string.glasses_picture_hint),
listOf(
GlassesSettings.PICTURE_AUTO to stringResource(R.string.glasses_picture_auto),
@@ -104,7 +104,7 @@ internal fun SteamHome(s: FrontEndState, a: FrontEndActions, modifier: Modifier)
if (glasses.present) DropSheet(
drop, glasses.model, GlassesIcon, glassesTabs(glasses), covered = lifted,
beside = { enabled ->
PrimaryButton(stringResource(R.string.games_play_steamvr), enabled = !s.busy, large = true, icon = Icons.Filled.PlayArrow, fill = true,
if (glasses.canControl) PrimaryButton(stringResource(R.string.games_play_steamvr), enabled = !s.busy, large = true, icon = Icons.Filled.PlayArrow, fill = true,
modifier = Modifier.fillMaxWidth().testTag("play-steamvr")) { if (enabled) a.onPlaySteamVr() }
},
) { tab ->
@@ -20,8 +20,8 @@ class HdrProbe(
)
object HdrSupport {
fun probe(context: Context): HdrProbe {
val display: Display? = if (Build.VERSION.SDK_INT >= 30) {
fun probe(context: Context, output: Display? = null): HdrProbe {
val display: Display? = output ?: if (Build.VERSION.SDK_INT >= 30) {
try { context.display } catch (e: Exception) { null }
} else {
@Suppress("DEPRECATION")
-1
View File
@@ -2313,7 +2313,6 @@
<string name="glasses_refresh">معدل التحديث</string>
<string name="glasses_3d">3D (جنبًا إلى جنب)</string>
<string name="glasses_3d_hint">تحصل كل عين على نصفها الخاص من صورة بدقة 3840×1080. إذا انقطعت الصورة، فاضغط مطولًا على زر 2D/3D في النظارة.</string>
<string name="glasses_3d_unsupported">لا يمكن لهذا الهاتف إرسال إشارة 3840×1080 التي يحتاجها وضع 3D. تعرض ألعاب الواقع الافتراضي منظور عين واحدة، مع تتبّع الرأس.</string>
<string name="glasses_3d_failed">لم يتم تأكيد إشارة 3D. حاول مجددًا بعد عودة صورة 2D.</string>
<string name="glasses_no_signal">لا توجد صورة: النظارة في وضع لا يستطيع هذا الهاتف إرساله. اضغط مطولًا على زر 2D/3D فيها، أو افصلها ثم أعد توصيلها.</string>
<string name="glasses_3d_refresh">معدل تحديث 3D</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Честота на опресняване</string>
<string name="glasses_3d">3D (един до друг)</string>
<string name="glasses_3d_hint">Всяко око получава своята половина от картина 3840×1080. Ако картината изчезне, задръжте бутона 2D/3D на очилата.</string>
<string name="glasses_3d_unsupported">Този телефон не може да изпрати сигнала 3840×1080, нужен за 3D. VR игрите показват изгледа за едно око, с проследяване на главата.</string>
<string name="glasses_3d_failed">3D сигналът не беше потвърден. Опитайте отново, когато 2D картината се върне.</string>
<string name="glasses_no_signal">Няма картина: очилата са в режим, който този телефон не може да изпрати. Задръжте бутона им 2D/3D или ги изключете и включете отново.</string>
<string name="glasses_3d_refresh">Честота на опресняване в 3D</string>
-1
View File
@@ -2263,7 +2263,6 @@
<string name="glasses_refresh">Obnovovací frekvence</string>
<string name="glasses_3d">3D (vedle sebe)</string>
<string name="glasses_3d_hint">Každé oko dostane vlastní polovinu obrazu 3840×1080. Pokud obraz vypadne, podržte na brýlích tlačítko 2D/3D.</string>
<string name="glasses_3d_unsupported">Tento telefon neumí vyslat signál 3840×1080, který 3D vyžaduje. Hry VR zobrazí pohled jednoho oka se sledováním hlavy.</string>
<string name="glasses_3d_failed">Signál 3D se nepodařilo potvrdit. Zkuste to znovu, až se vrátí obraz 2D.</string>
<string name="glasses_no_signal">Žádný obraz: brýle jsou v režimu, který tento telefon neumí vyslat. Podržte jejich tlačítko 2D/3D, nebo je odpojte a znovu připojte.</string>
<string name="glasses_3d_refresh">Obnovovací frekvence ve 3D</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Opdateringshastighed</string>
<string name="glasses_3d">3D (side-by-side)</string>
<string name="glasses_3d_hint">Hvert øje får sin egen halvdel af et billede på 3840×1080. Forsvinder billedet, så hold brillens 2D/3D-knap inde.</string>
<string name="glasses_3d_unsupported">Denne telefon kan ikke sende det 3840×1080-signal, som 3D kræver. VR-spil viser ét øjes billede med hovedsporing.</string>
<string name="glasses_3d_failed">3D-signalet blev ikke bekræftet. Prøv igen, når 2D-billedet er tilbage.</string>
<string name="glasses_no_signal">Intet billede: brillen er i en tilstand, som telefonen ikke kan sende. Hold dens 2D/3D-knap inde, eller tag stikket ud og sæt det i igen.</string>
<string name="glasses_3d_refresh">3D-opdateringshastighed</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Bildwiederholrate</string>
<string name="glasses_3d">3D (Side-by-Side)</string>
<string name="glasses_3d_hint">Jedes Auge bekommt seine eigene Hälfte eines 3840×1080-Bildes. Fällt das Bild aus, halte die 2D/3D-Taste der Brille gedrückt.</string>
<string name="glasses_3d_unsupported">Dieses Smartphone kann das 3840×1080-Signal für 3D nicht ausgeben. VR-Spiele zeigen die Sicht eines Auges, mit Kopf-Tracking.</string>
<string name="glasses_3d_failed">Das 3D-Signal wurde nicht bestätigt. Versuche es erneut, sobald das 2D-Bild wieder da ist.</string>
<string name="glasses_no_signal">Kein Bild: Die Brille ist in einem Modus, den dieses Smartphone nicht ausgeben kann. Halte ihre 2D/3D-Taste gedrückt oder steck sie aus und wieder ein.</string>
<string name="glasses_3d_refresh">3D-Bildwiederholrate</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Ρυθμός ανανέωσης</string>
<string name="glasses_3d">3D (δίπλα-δίπλα)</string>
<string name="glasses_3d_hint">Κάθε μάτι παίρνει το δικό του μισό μιας εικόνας 3840×1080. Αν η εικόνα χαθεί, κρατήστε πατημένο το κουμπί 2D/3D των γυαλιών.</string>
<string name="glasses_3d_unsupported">Αυτό το τηλέφωνο δεν μπορεί να στείλει το σήμα 3840×1080 που χρειάζεται το 3D. Τα παιχνίδια VR δείχνουν την εικόνα του ενός ματιού, με παρακολούθηση κεφαλιού.</string>
<string name="glasses_3d_failed">Το σήμα 3D δεν επιβεβαιώθηκε. Δοκιμάστε ξανά μόλις επανέλθει η εικόνα 2D.</string>
<string name="glasses_no_signal">Δεν υπάρχει εικόνα: τα γυαλιά είναι σε λειτουργία που αυτό το τηλέφωνο δεν μπορεί να στείλει. Κρατήστε πατημένο το κουμπί 2D/3D τους ή αποσυνδέστε τα και συνδέστε τα ξανά.</string>
<string name="glasses_3d_refresh">Ρυθμός ανανέωσης 3D</string>
-1
View File
@@ -2246,7 +2246,6 @@
<string name="glasses_refresh">Frecuencia de actualización</string>
<string name="glasses_3d">3D (lado a lado)</string>
<string name="glasses_3d_hint">Cada ojo recibe su propia mitad de una imagen de 3840×1080. Si la imagen se pierde, mantén pulsado el botón 2D/3D de las gafas.</string>
<string name="glasses_3d_unsupported">Este teléfono no puede enviar la señal de 3840×1080 que necesita el 3D. Los juegos de RV muestran la vista de un ojo, con seguimiento de cabeza.</string>
<string name="glasses_3d_failed">No se ha confirmado la señal 3D. Inténtalo de nuevo cuando vuelva la imagen 2D.</string>
<string name="glasses_no_signal">Sin imagen: las gafas están en un modo que este teléfono no puede enviar. Mantén pulsado su botón 2D/3D o desconéctalas y vuelve a conectarlas.</string>
<string name="glasses_3d_refresh">Frecuencia en 3D</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Virkistystaajuus</string>
<string name="glasses_3d">3D (rinnakkain)</string>
<string name="glasses_3d_hint">Kumpikin silmä saa oman puoliskonsa 3840×1080-kuvasta. Jos kuva katoaa, pidä lasien 2D/3D-painiketta pohjassa.</string>
<string name="glasses_3d_unsupported">Tämä puhelin ei pysty lähettämään 3D:n vaatimaa 3840×1080-signaalia. VR-pelit näyttävät yhden silmän näkymän pään seurannalla.</string>
<string name="glasses_3d_failed">3D-signaalia ei voitu vahvistaa. Yritä uudelleen, kun 2D-kuva palautuu.</string>
<string name="glasses_no_signal">Ei kuvaa: lasit ovat tilassa, jota tämä puhelin ei pysty lähettämään. Pidä niiden 2D/3D-painiketta pohjassa tai irrota ja kytke ne uudelleen.</string>
<string name="glasses_3d_refresh">3D-virkistystaajuus</string>
-1
View File
@@ -2246,7 +2246,6 @@
<string name="glasses_refresh">Fréquence d\'affichage</string>
<string name="glasses_3d">3D (côte à côte)</string>
<string name="glasses_3d_hint">Chaque œil reçoit sa propre moitié d\'une image de 3840×1080. Si l\'image disparaît, maintenez le bouton 2D/3D des lunettes.</string>
<string name="glasses_3d_unsupported">Ce téléphone ne peut pas envoyer le signal 3840×1080 nécessaire à la 3D. Les jeux VR affichent la vue d\'un seul œil, avec le suivi de la tête.</string>
<string name="glasses_3d_failed">Le signal 3D n\'a pas été confirmé. Réessayez une fois l\'image 2D rétablie.</string>
<string name="glasses_no_signal">Pas d\'image : les lunettes sont dans un mode que ce téléphone ne peut pas envoyer. Maintenez leur bouton 2D/3D, ou débranchez-les puis rebranchez-les.</string>
<string name="glasses_3d_refresh">Fréquence en 3D</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">रिफ्रेश रेट</string>
<string name="glasses_3d">3D (साइड-बाय-साइड)</string>
<string name="glasses_3d_hint">हर आंख को 3840×1080 तस्वीर का अपना आधा हिस्सा मिलता है। अगर तस्वीर चली जाए, तो चश्मे का 2D/3D बटन दबाकर रखें।</string>
<string name="glasses_3d_unsupported">यह फ़ोन 3D के लिए ज़रूरी 3840×1080 सिग्नल नहीं भेज सकता। VR गेम हेड ट्रैकिंग के साथ एक आंख का व्यू दिखाते हैं।</string>
<string name="glasses_3d_failed">3D सिग्नल की पुष्टि नहीं हुई। 2D तस्वीर वापस आने पर फिर कोशिश करें।</string>
<string name="glasses_no_signal">कोई तस्वीर नहीं: चश्मा ऐसे मोड में है जिसे यह फ़ोन नहीं भेज सकता। उसका 2D/3D बटन दबाकर रखें, या केबल निकालकर फिर से लगाएं।</string>
<string name="glasses_3d_refresh">3D रिफ्रेश रेट</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Frissítési frekvencia</string>
<string name="glasses_3d">3D (egymás melletti)</string>
<string name="glasses_3d_hint">Mindkét szem egy 3840×1080-as kép saját felét kapja. Ha a kép eltűnik, tartsa lenyomva a szemüveg 2D/3D gombját.</string>
<string name="glasses_3d_unsupported">Ez a telefon nem tudja küldeni a 3D-hez szükséges 3840×1080-as jelet. A VR-játékok egy szem nézetét mutatják, fejkövetéssel.</string>
<string name="glasses_3d_failed">Nem sikerült megerősíteni a 3D-jelet. Próbáld újra, ha visszatér a 2D-kép.</string>
<string name="glasses_no_signal">Nincs kép: a szemüveg olyan módban van, amelyet ez a telefon nem tud küldeni. Tartsa lenyomva a 2D/3D gombját, vagy húzza ki, és dugja vissza.</string>
<string name="glasses_3d_refresh">3D frissítési frekvencia</string>
-1
View File
@@ -2188,7 +2188,6 @@
<string name="glasses_refresh">Refresh rate</string>
<string name="glasses_3d">3D (berdampingan)</string>
<string name="glasses_3d_hint">Setiap mata mendapat separuhnya sendiri dari gambar 3840×1080. Jika gambar hilang, tahan tombol 2D/3D kacamata.</string>
<string name="glasses_3d_unsupported">Ponsel ini tidak bisa mengirim sinyal 3840×1080 yang dibutuhkan 3D. Game VR menampilkan pandangan satu mata, dengan pelacakan kepala.</string>
<string name="glasses_3d_failed">Sinyal 3D belum terkonfirmasi. Coba lagi setelah gambar 2D kembali.</string>
<string name="glasses_no_signal">Tidak ada gambar: kacamata dalam mode yang tidak bisa dikirim ponsel ini. Tahan tombol 2D/3D-nya, atau cabut lalu colokkan lagi.</string>
<string name="glasses_3d_refresh">Refresh rate 3D</string>
-1
View File
@@ -2238,7 +2238,6 @@
<string name="glasses_refresh">Frequenza di aggiornamento</string>
<string name="glasses_3d">3D (affiancato)</string>
<string name="glasses_3d_hint">Ogni occhio riceve la propria metà di un\'immagine 3840×1080. Se l\'immagine scompare, tieni premuto il pulsante 2D/3D degli occhiali.</string>
<string name="glasses_3d_unsupported">Questo telefono non può inviare il segnale 3840×1080 richiesto dal 3D. I giochi VR mostrano la vista di un solo occhio, con il tracciamento della testa.</string>
<string name="glasses_3d_failed">Il segnale 3D non è stato confermato. Riprova quando torna l\'immagine 2D.</string>
<string name="glasses_no_signal">Nessuna immagine: gli occhiali sono in una modalità che questo telefono non può inviare. Tieni premuto il loro pulsante 2D/3D, oppure scollegali e ricollegali.</string>
<string name="glasses_3d_refresh">Frequenza di aggiornamento 3D</string>
-1
View File
@@ -2196,7 +2196,6 @@
<string name="glasses_refresh">リフレッシュレート</string>
<string name="glasses_3d">3D(サイドバイサイド)</string>
<string name="glasses_3d_hint">左右の目に 3840×1080 の画像の半分ずつを表示します。映像が消えたら、グラスの 2D/3D ボタンを長押ししてください。</string>
<string name="glasses_3d_unsupported">このスマートフォンは 3D に必要な 3840×1080 の信号を出力できません。VR ゲームはヘッドトラッキング付きで片目の映像を表示します。</string>
<string name="glasses_3d_failed">3D 信号を確認できませんでした。2D 映像が戻ってから再試行してください。</string>
<string name="glasses_no_signal">映像なし: グラスがこのスマートフォンで出力できないモードになっています。2D/3D ボタンを長押しするか、抜き差ししてください。</string>
<string name="glasses_3d_refresh">3D のリフレッシュレート</string>
-1
View File
@@ -2196,7 +2196,6 @@
<string name="glasses_refresh">주사율</string>
<string name="glasses_3d">3D (사이드 바이 사이드)</string>
<string name="glasses_3d_hint">각 눈이 3840×1080 화면의 절반씩을 봅니다. 화면이 끊기면 안경의 2D/3D 버튼을 길게 누르세요.</string>
<string name="glasses_3d_unsupported">이 휴대폰은 3D에 필요한 3840×1080 신호를 보낼 수 없습니다. VR 게임은 헤드 트래킹과 함께 한쪽 눈의 화면을 보여 줍니다.</string>
<string name="glasses_3d_failed">3D 신호를 확인하지 못했습니다. 2D 화면이 돌아오면 다시 시도하세요.</string>
<string name="glasses_no_signal">화면 없음: 안경이 이 휴대폰에서 보낼 수 없는 모드에 있습니다. 2D/3D 버튼을 길게 누르거나 안경을 뺐다가 다시 연결하세요.</string>
<string name="glasses_3d_refresh">3D 주사율</string>
-1
View File
@@ -2188,7 +2188,6 @@
<string name="glasses_refresh">Kadar penyegaran</string>
<string name="glasses_3d">3D (sebelah-menyebelah)</string>
<string name="glasses_3d_hint">Setiap mata mendapat separuh gambar 3840×1080 sendiri. Jika gambar hilang, tahan butang 2D/3D cermin mata.</string>
<string name="glasses_3d_unsupported">Telefon ini tidak dapat menghantar isyarat 3840×1080 yang diperlukan oleh 3D. Permainan VR memaparkan pandangan sebelah mata, dengan penjejakan kepala.</string>
<string name="glasses_3d_failed">Isyarat 3D tidak dapat disahkan. Cuba lagi selepas gambar 2D kembali.</string>
<string name="glasses_no_signal">Tiada gambar: cermin mata dalam mod yang tidak dapat dihantar oleh telefon ini. Tahan butang 2D/3D-nya, atau cabut dan pasang semula.</string>
<string name="glasses_3d_refresh">Kadar penyegaran 3D</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Oppdateringsfrekvens</string>
<string name="glasses_3d">3D (side ved side)</string>
<string name="glasses_3d_hint">Hvert øye får sin egen halvdel av et bilde på 3840×1080. Hvis bildet faller ut, holder du inne 2D/3D-knappen på brillene.</string>
<string name="glasses_3d_unsupported">Denne telefonen kan ikke sende 3840×1080-signalet som 3D krever. VR-spill viser bildet for ett øye, med hodesporing.</string>
<string name="glasses_3d_failed">3D-signalet ble ikke bekreftet. Prøv igjen når 2D-bildet er tilbake.</string>
<string name="glasses_no_signal">Ikke noe bilde: brillene er i en modus denne telefonen ikke kan sende. Hold inne 2D/3D-knappen deres, eller koble dem fra og til igjen.</string>
<string name="glasses_3d_refresh">3D-oppdateringsfrekvens</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Verversingssnelheid</string>
<string name="glasses_3d">3D (side-by-side)</string>
<string name="glasses_3d_hint">Elk oog krijgt een eigen helft van een beeld van 3840×1080. Valt het beeld weg, houd dan de 2D/3D-knop van de bril ingedrukt.</string>
<string name="glasses_3d_unsupported">Deze telefoon kan het 3840×1080-signaal voor 3D niet versturen. VR-games tonen het beeld van één oog, met hoofdtracking.</string>
<string name="glasses_3d_failed">Het 3D-signaal is niet bevestigd. Probeer het opnieuw zodra het 2D-beeld terug is.</string>
<string name="glasses_no_signal">Geen beeld: de bril staat in een modus die deze telefoon niet kan versturen. Houd de 2D/3D-knop ingedrukt, of koppel de bril los en sluit hem opnieuw aan.</string>
<string name="glasses_3d_refresh">3D-verversingssnelheid</string>
-1
View File
@@ -2263,7 +2263,6 @@
<string name="glasses_refresh">Częstotliwość odświeżania</string>
<string name="glasses_3d">3D (obok siebie)</string>
<string name="glasses_3d_hint">Każde oko dostaje własną połowę obrazu 3840×1080. Jeśli obraz zniknie, przytrzymaj przycisk 2D/3D na okularach.</string>
<string name="glasses_3d_unsupported">Ten telefon nie może wysłać sygnału 3840×1080 potrzebnego do 3D. Gry VR pokazują widok jednego oka ze śledzeniem głowy.</string>
<string name="glasses_3d_failed">Nie udało się potwierdzić sygnału 3D. Spróbuj ponownie, gdy wróci obraz 2D.</string>
<string name="glasses_no_signal">Brak obrazu: okulary są w trybie, którego ten telefon nie obsługuje. Przytrzymaj ich przycisk 2D/3D albo odłącz je i podłącz ponownie.</string>
<string name="glasses_3d_refresh">Częstotliwość odświeżania w 3D</string>
@@ -2238,7 +2238,6 @@
<string name="glasses_refresh">Taxa de atualização</string>
<string name="glasses_3d">3D (lado a lado)</string>
<string name="glasses_3d_hint">Cada olho recebe a sua metade de uma imagem de 3840×1080. Se a imagem desaparecer, mantenha premido o botão 2D/3D dos óculos.</string>
<string name="glasses_3d_unsupported">Este telemóvel não consegue enviar o sinal de 3840×1080 de que o 3D precisa. Os jogos de RV mostram a vista de um só olho, com seguimento da cabeça.</string>
<string name="glasses_3d_failed">O sinal 3D não foi confirmado. Tenta novamente quando a imagem 2D voltar.</string>
<string name="glasses_no_signal">Sem imagem: os óculos estão num modo que este telemóvel não consegue enviar. Mantenha premido o botão 2D/3D ou desligue-os e volte a ligá-los.</string>
<string name="glasses_3d_refresh">Taxa de atualização em 3D</string>
-1
View File
@@ -2238,7 +2238,6 @@
<string name="glasses_refresh">Taxa de atualização</string>
<string name="glasses_3d">3D (lado a lado)</string>
<string name="glasses_3d_hint">Cada olho recebe a própria metade de uma imagem de 3840×1080. Se a imagem sumir, mantenha pressionado o botão 2D/3D dos óculos.</string>
<string name="glasses_3d_unsupported">Este celular não consegue enviar o sinal de 3840×1080 de que o 3D precisa. Jogos de RV mostram a visão de um olho só, com rastreamento da cabeça.</string>
<string name="glasses_3d_failed">O sinal 3D não foi confirmado. Tente novamente quando a imagem 2D voltar.</string>
<string name="glasses_no_signal">Sem imagem: os óculos estão em um modo que este celular não consegue enviar. Mantenha pressionado o botão 2D/3D deles ou desconecte e reconecte os óculos.</string>
<string name="glasses_3d_refresh">Taxa de atualização em 3D</string>
-1
View File
@@ -2238,7 +2238,6 @@
<string name="glasses_refresh">Rată de reîmprospătare</string>
<string name="glasses_3d">3D (alăturat)</string>
<string name="glasses_3d_hint">Fiecare ochi primește propria jumătate dintr-o imagine de 3840×1080. Dacă imaginea dispare, ține apăsat butonul 2D/3D al ochelarilor.</string>
<string name="glasses_3d_unsupported">Acest telefon nu poate trimite semnalul de 3840×1080 de care are nevoie 3D-ul. Jocurile VR arată imaginea unui singur ochi, cu urmărirea capului.</string>
<string name="glasses_3d_failed">Semnalul 3D nu a fost confirmat. Încearcă din nou când revine imaginea 2D.</string>
<string name="glasses_no_signal">Nicio imagine: ochelarii sunt într-un mod pe care acest telefon nu îl poate trimite. Ține apăsat butonul lor 2D/3D sau deconectează-i și reconectează-i.</string>
<string name="glasses_3d_refresh">Rată de reîmprospătare 3D</string>
-1
View File
@@ -2271,7 +2271,6 @@
<string name="glasses_refresh">Частота обновления</string>
<string name="glasses_3d">3D (стереопара)</string>
<string name="glasses_3d_hint">Каждый глаз получает свою половину изображения 3840×1080. Если изображение пропадёт, удерживайте кнопку 2D/3D на очках.</string>
<string name="glasses_3d_unsupported">Этот телефон не может передать сигнал 3840×1080, нужный для 3D. VR-игры показывают вид одного глаза с отслеживанием головы.</string>
<string name="glasses_3d_failed">Сигнал 3D не подтверждён. Повторите попытку, когда вернётся изображение 2D.</string>
<string name="glasses_no_signal">Нет изображения: очки в режиме, который этот телефон не может передать. Удерживайте их кнопку 2D/3D или отключите и снова подключите очки.</string>
<string name="glasses_3d_refresh">Частота в 3D</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Uppdateringsfrekvens</string>
<string name="glasses_3d">3D (sida vid sida)</string>
<string name="glasses_3d_hint">Varje öga får sin egen halva av en bild på 3840×1080. Om bilden försvinner håller du in glasögonens 2D/3D-knapp.</string>
<string name="glasses_3d_unsupported">Den här telefonen kan inte skicka den 3840×1080-signal som 3D kräver. VR-spel visar ett ögas vy, med huvudspårning.</string>
<string name="glasses_3d_failed">3D-signalen kunde inte bekräftas. Försök igen när 2D-bilden är tillbaka.</string>
<string name="glasses_no_signal">Ingen bild: glasögonen är i ett läge som den här telefonen inte kan skicka. Håll in deras 2D/3D-knapp, eller koppla ur dem och anslut dem igen.</string>
<string name="glasses_3d_refresh">3D-uppdateringsfrekvens</string>
-1
View File
@@ -2188,7 +2188,6 @@
<string name="glasses_refresh">อัตรารีเฟรช</string>
<string name="glasses_3d">3D (แบบเคียงข้าง)</string>
<string name="glasses_3d_hint">ตาแต่ละข้างจะเห็นภาพคนละครึ่งของภาพขนาด 3840×1080 หากภาพหายไป ให้กดปุ่ม 2D/3D ของแว่นตาค้างไว้</string>
<string name="glasses_3d_unsupported">โทรศัพท์นี้ส่งสัญญาณ 3840×1080 ที่ 3D ต้องใช้ไม่ได้ เกม VR จะแสดงภาพของตาข้างเดียวพร้อมการติดตามศีรษะ</string>
<string name="glasses_3d_failed">ยังยืนยันสัญญาณ 3D ไม่ได้ ลองอีกครั้งเมื่อภาพ 2D กลับมา</string>
<string name="glasses_no_signal">ไม่มีภาพ: แว่นตาอยู่ในโหมดที่โทรศัพท์นี้ส่งสัญญาณให้ไม่ได้ กดปุ่ม 2D/3D ของแว่นตาค้างไว้ หรือถอดสายแล้วเสียบใหม่</string>
<string name="glasses_3d_refresh">อัตรารีเฟรช 3D</string>
-1
View File
@@ -2213,7 +2213,6 @@
<string name="glasses_refresh">Yenileme hızı</string>
<string name="glasses_3d">3D (yan yana)</string>
<string name="glasses_3d_hint">Her göz 3840×1080 görüntünün kendi yarısını alır. Görüntü kesilirse gözlüğün 2D/3D düğmesini basılı tutun.</string>
<string name="glasses_3d_unsupported">Bu telefon 3D\'nin gerektirdiği 3840×1080 sinyalini gönderemez. VR oyunları, kafa takibiyle tek gözün görüntüsünü gösterir.</string>
<string name="glasses_3d_failed">3D sinyali doğrulanamadı. 2D görüntü geri geldiğinde tekrar deneyin.</string>
<string name="glasses_no_signal">Görüntü yok: gözlük bu telefonun gönderemediği bir modda. Gözlüğün 2D/3D düğmesini basılı tutun ya da kablosunu çıkarıp yeniden takın.</string>
<string name="glasses_3d_refresh">3D yenileme hızı</string>
-1
View File
@@ -2263,7 +2263,6 @@
<string name="glasses_refresh">Частота оновлення</string>
<string name="glasses_3d">3D (стереопара)</string>
<string name="glasses_3d_hint">Кожне око отримує свою половину зображення 3840×1080. Якщо зображення зникне, утримуйте кнопку 2D/3D на окулярах.</string>
<string name="glasses_3d_unsupported">Цей телефон не може передати сигнал 3840×1080, потрібний для 3D. VR-ігри показують вигляд для одного ока з відстеженням голови.</string>
<string name="glasses_3d_failed">Сигнал 3D не підтверджено. Спробуйте знову, коли повернеться зображення 2D.</string>
<string name="glasses_no_signal">Немає зображення: окуляри в режимі, який цей телефон не може передати. Утримуйте їхню кнопку 2D/3D або від’єднайте й знову під’єднайте їх.</string>
<string name="glasses_3d_refresh">Частота оновлення в 3D</string>
-1
View File
@@ -2188,7 +2188,6 @@
<string name="glasses_refresh">Tốc độ làm mới</string>
<string name="glasses_3d">3D (cạnh nhau)</string>
<string name="glasses_3d_hint">Mỗi mắt nhận một nửa riêng của hình 3840×1080. Nếu mất hình, hãy giữ nút 2D/3D của kính.</string>
<string name="glasses_3d_unsupported">Điện thoại này không thể xuất tín hiệu 3840×1080 mà 3D cần. Game VR hiển thị góc nhìn của một mắt, có theo dõi đầu.</string>
<string name="glasses_3d_failed">Chưa xác nhận được tín hiệu 3D. Hãy thử lại khi hình ảnh 2D trở lại.</string>
<string name="glasses_no_signal">Không có hình: kính đang ở chế độ mà điện thoại này không thể xuất. Hãy giữ nút 2D/3D của kính, hoặc rút ra rồi cắm lại.</string>
<string name="glasses_3d_refresh">Tốc độ làm mới 3D</string>
@@ -2196,7 +2196,6 @@
<string name="glasses_refresh">刷新率</string>
<string name="glasses_3d">3D(左右分屏)</string>
<string name="glasses_3d_hint">每只眼睛各看 3840×1080 画面的一半。如果画面消失,请长按眼镜上的 2D/3D 按钮。</string>
<string name="glasses_3d_unsupported">这部手机无法输出 3D 所需的 3840×1080 信号。VR 游戏会显示单眼画面,并带有头部追踪。</string>
<string name="glasses_3d_failed">未能确认 3D 信号。请在 2D 画面恢复后重试。</string>
<string name="glasses_no_signal">无画面:眼镜处于这部手机无法输出的模式。请长按眼镜的 2D/3D 按钮,或拔下后重新连接。</string>
<string name="glasses_3d_refresh">3D 刷新率</string>
@@ -2196,7 +2196,6 @@
<string name="glasses_refresh">更新率</string>
<string name="glasses_3d">3D(左右並排)</string>
<string name="glasses_3d_hint">每隻眼睛各看 3840×1080 畫面的一半。如果畫面消失,請長按眼鏡上的 2D/3D 按鈕。</string>
<string name="glasses_3d_unsupported">這部手機無法輸出 3D 所需的 3840×1080 訊號。VR 遊戲會顯示單眼畫面,並配合頭部追蹤。</string>
<string name="glasses_3d_failed">未能確認 3D 訊號。請在 2D 畫面恢復後重試。</string>
<string name="glasses_no_signal">沒有畫面:眼鏡處於這部手機無法輸出的模式。請長按眼鏡的 2D/3D 按鈕,或拔除後重新連接。</string>
<string name="glasses_3d_refresh">3D 更新率</string>
@@ -2196,7 +2196,6 @@
<string name="glasses_refresh">更新率</string>
<string name="glasses_3d">3D(左右並排)</string>
<string name="glasses_3d_hint">每隻眼睛各看 3840×1080 畫面的一半。如果畫面消失,請長按眼鏡上的 2D/3D 按鈕。</string>
<string name="glasses_3d_unsupported">這支手機無法輸出 3D 所需的 3840×1080 訊號。VR 遊戲會顯示單眼畫面,並搭配頭部追蹤。</string>
<string name="glasses_3d_failed">未能確認 3D 訊號。請在 2D 畫面恢復後再試。</string>
<string name="glasses_no_signal">沒有畫面:眼鏡處於這支手機無法輸出的模式。請長按眼鏡的 2D/3D 按鈕,或拔除後重新連接。</string>
<string name="glasses_3d_refresh">3D 更新率</string>
-1
View File
@@ -2279,7 +2279,6 @@
<string name="glasses_refresh">Refresh rate</string>
<string name="glasses_3d">3D (side-by-side)</string>
<string name="glasses_3d_hint">Each eye gets its own half of a 3840×1080 picture. If the picture drops out, hold the glasses\' 2D/3D button.</string>
<string name="glasses_3d_unsupported">This phone can\'t send the 3840×1080 signal 3D needs. VR games show one eye\'s view, with head tracking.</string>
<string name="glasses_3d_failed">The 3D signal was not confirmed. Try again once the 2D picture returns.</string>
<string name="glasses_no_signal">No picture: the glasses are in a mode this phone can\'t send. Hold their 2D/3D button, or unplug and replug them.</string>
<string name="glasses_3d_refresh">3D refresh rate</string>
@@ -7,6 +7,7 @@ import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.view.Display
import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Assert.assertEquals
@@ -24,9 +25,11 @@ import org.robolectric.annotation.Implements
import org.robolectric.annotation.LooperMode
import org.robolectric.shadow.api.Shadow
import org.robolectric.shadows.ShadowDisplayManager
import org.robolectric.shadows.ShadowSystemClock
import org.robolectric.shadows.ShadowUsbManager
import org.robolectric.util.ReflectionHelpers
import org.robolectric.util.ReflectionHelpers.ClassParameter.from
import java.time.Duration
@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28], manifest = Config.NONE, shadows = [GlassesUsbTest.RecordingUsbManager::class])
@@ -75,7 +78,7 @@ class GlassesUsbTest {
refresh(ask = true)
assertTrue(manager.requests.isEmpty())
assertEquals(Glasses.Permission.NONE, state.value.permission)
assertTrue(state.value.present)
assertEquals(pid == 0x1201, state.value.present)
assertFalse(state.value.controllable)
assertFalse(state.value.connected)
}
@@ -97,6 +100,36 @@ class GlassesUsbTest {
assertEquals(Glasses.Permission.NONE, state.value.permission)
}
@Test fun noSignalStereoFailureIsRememberedPerModel() {
val prefs = RuntimeEnvironment.getApplication().getSharedPreferences("glasses", Context.MODE_PRIVATE)
prefs.edit().clear().commit()
val hdmi = ReflectionHelpers.getStaticField<Int>(Display::class.java, "TYPE_HDMI")
val displayId = ShadowDisplayManager.addDisplay("w640dp-h480dp", hdmi)
val displays = RuntimeEnvironment.getApplication().getSystemService(DisplayManager::class.java)
shadowOf(displays.getDisplay(displayId)).setFlags(Display.FLAG_PRESENTATION)
val pro = device(0x1019)
manager.devices[pro.deviceName] = pro
refresh()
assertTrue(state.value.noSignal)
(field("modeState").get(null) as GlassesModeState).sent(true, SystemClock.uptimeMillis())
ShadowSystemClock.advanceBy(Duration.ofSeconds(21))
refresh()
assertEquals(GlassesState.SBS_FAILS, state.value.sbsSupport)
assertEquals(setOf("1019"), prefs.getStringSet("sbsFailed", null))
ShadowDisplayManager.removeDisplay(displayId)
val xr = device(0x101d)
manager.devices.clear()
manager.devices[xr.deviceName] = xr
refresh()
assertEquals(GlassesState.SBS_UNKNOWN, state.value.sbsSupport)
manager.devices.clear()
manager.devices[pro.deviceName] = pro
refresh()
assertEquals(GlassesState.SBS_FAILS, state.value.sbsSupport)
assertFalse(state.value.can3d)
}
private fun refresh(ask: Boolean = false) {
Glasses::class.java.getDeclaredMethod("refresh", Boolean::class.javaPrimitiveType)
.apply { isAccessible = true }.invoke(Glasses, ask)
@@ -118,14 +118,6 @@ class VitureProtocolTest {
assertEquals("VITURE One", VitureProtocol.model(0x1011, "Something else")!!.name)
}
@Test fun modeHelpers() {
listOf(0x32, 0x35, 0x42, 0x45).forEach { assertTrue(VitureProtocol.modeIsSbs(it)) }
listOf(0x31, 0x33, 0x34, 0x41, 0x44).forEach { assertFalse(VitureProtocol.modeIsSbs(it)) }
assertEquals(120, VitureProtocol.modeHz(0x34))
assertEquals(90, VitureProtocol.modeHz(0x35))
assertEquals(60, VitureProtocol.modeHz(0x32))
}
@Test fun imuPrefersTheQuaternion() {
val half = Math.toRadians(30.0) / 2
val payload = ByteBuffer.allocate(40)
+1
View File
@@ -132,6 +132,7 @@ tools/droiddeckctl glasses raw 0x0007
Glasses status reports `deviceMode` from the MCU separately from `sbs`, which
requires a physical stereo display mode. `switching3d` marks the bounded
confirmation window. A successful mode-command reply alone does not confirm 3D.
In `frames`, a report whose checksum does not match ends in `crc!`.
A sleeping device suspends the session and hides the app's windows from `ui dump`; `wake` first.
+5 -2
View File
@@ -139,13 +139,16 @@ Verified on the AYN Thor (SD 8 Gen 2, Turnip): SteamVR 2.17.10 starts with the D
the compositor runs driver-direct at about 60 fps, the dashboard (Steam's library in VR, with the
controller's laser) shows cleanly in both eyes at the panel's resolution while Steam keeps running, and
a pose written to the shared file turns SteamVR's view. Started from the drawer button through the agent.
With VITURE glasses connected, the Steam page also offers **Play SteamVR**, which starts Steam and then
With VITURE glasses connected and under DroidDeck's control, the Steam page also offers **Play SteamVR**, which starts Steam and then
SteamVR (installing it first if needed), and stops SteamVR when the glasses are unplugged. **Play
Steam** never starts it: while SteamVR runs, Steam keeps its own window in front of flat games.
Through a VITURE Mobile Dock the glasses are only a display: the dock does not pass their controls or
head tracking to the phone, so there is no Play SteamVR, refresh, 3D or tracking setting.
Stereo also requires a physical side-by-side display mode. On the tested Astra 2
firmware the Pro XR command succeeds but DisplayPort falls back to 640×480;
direct-connection stereo remains unresolved. See the
direct-connection stereo remains unresolved. DroidDeck remembers such a fallback for that glasses
model and stops asking for 3D on its own; **Retry** in the glasses settings tries again. See the
[Astra/VITURE investigation](development/astra-viture-stereo.md) for measured
results, app recovery behavior, and the unverified powered-adapter design.
+16 -31
View File
@@ -60,9 +60,17 @@ checksum. The Astra negotiated 1920×1080/120, but enumerated only the dock's
USB interfaces, not the glasses' `35ca:101d` control/IMU interfaces. A one-second
DroidDeck IMU sample returned zero packets.
DroidDeck now keeps the dock's display available without requesting unsupported
USB control access. The updated build was installed on this connection: no USB
permission prompt appeared, video remained at 1080p/120 and tracking stayed off.
DroidDeck treats the glasses as a display behind the dock: no USB permission
prompt, video at 1080p/120 on the glasses, no Play SteamVR, refresh, 3D or tracking
settings. The dock alone, without glasses, does not count as glasses.
VITURE's updater calls this dock P9 (app `35ca:3011`, bootloader `35ca:3010`). Its
HID messages cover dock versions, battery, inputs and LEDs; none sets a display
mode or forwards glasses commands or IMU data. `2.01.03.0.03` is public release
`1.03.0.03_20250709`; the latest, `1.03.0.06_20260409`, adds Beast support and
updater fixes only. The updater also reads a Lontium LT8668 in the dock, a
DisplayPort receiver with its own EDID handling, so the tablet most likely sees the
dock as its DisplayPort sink rather than the glasses.
[VITURE's Pro Dock specifications](https://www.viture.com/product/viture-pro-mobile-dock)
confirm 3840×1080/60 video support and exclude phone features requiring
@@ -107,9 +115,11 @@ A wide screenshot or `wm size` override alone does not meet those conditions.
## App safeguards
DroidDeck waits up to 20 seconds for physical stereo confirmation. A temporary
low-resolution mode does not permanently blacklist the device. Failed attempts
offer manual retry once the 2D signal returns; automatic retries do not loop.
DroidDeck waits up to 20 seconds for physical stereo confirmation. When the
attempt ends at a low-resolution fallback, DroidDeck remembers it for that glasses
model on this device, so Play SteamVR stops requesting 3D after a restart; **Retry** in the
glasses settings clears it. A display that stays missing, such as an unanswered
cast prompt, fails only that attempt. Automatic retries do not loop.
USB mode reports are tracked separately from the physical display. When a session
enabled 3D, stopping it requests a return to 2D, including during negotiation;
this firmware can still require a cable reconnect. Output timing follows
@@ -118,28 +128,3 @@ the active glasses display rather than the tablet's requested refresh rate.
Keep hardware experiments separate from game loads. End the session, force-stop
DroidDeck, and reopen its home screen after each game test. Read current HAL
temperatures rather than cached thermal-service samples.
## Validation of this update
- Full debug APK build, including the Rust store engine; native-library, session
asset and runtime-input packaging checks passed.
- 846 Android unit tests across 141 suites passed, including mode deadlines,
reconnect ownership, stale USB callbacks, dock selection and permission handling,
truncated USB frames and model-specific protocol cases.
- Four host tests passed, including native refresh notification, event coalescing,
full-pipe handling and Cardboard output checks.
- On the Astra: brightness changed and was restored; IMU delivered 400 packets
in two seconds; Steam reached its first frame in 4.9 seconds and exited cleanly.
Wayland reported the actual 120 Hz tablet mode. Four complete steady-state
ten-second windows recorded 116.8–119.5 screen frames/s in Steam's UI.
- After reconnect: a fresh stereo attempt remained pending during the 640×480
fallback, failed at the 20-second deadline and issued one 2D request. The MCU
refused that request; another 28 seconds of observation showed no retry loop.
- DroidDeck was force-stopped and relaunched after testing. Debugging tools and
temporary logging/display overrides were restored. Current HAL temperatures
after the session were 25°C battery and 28.3°C skin.
- The dock checks ran without a game load; the idle battery temperature was 24°C.
These checks do not establish stereo-game performance or a sustained thermal
baseline. Direct-cable stereo and the powered-adapter design remain unverified
as working solutions on this Astra firmware.
+21 -6
View File
@@ -102,16 +102,26 @@ public:
void WaitForPresent() override {}
bool GetTimeSinceLastVsync(float *seconds, uint64_t *counter) override {
using namespace std::chrono;
double t = duration<double>(steady_clock::now().time_since_epoch()).count();
double period = VsyncPeriodSeconds();
*seconds = (float)fmod(t, period);
*counter = (uint64_t)(t / period);
const double period = VsyncPeriodSeconds();
std::lock_guard<std::mutex> lock(vsyncMutex_);
const double t = duration<double>(steady_clock::now().time_since_epoch()).count();
if (period != period_) {
vsyncBase_ = period_ > 0.0 ? vsyncBase_ + (uint64_t)((t - vsyncAt_) / period_) : (uint64_t)(t / period);
vsyncAt_ = t;
period_ = period;
}
const double since = t - vsyncAt_;
*seconds = (float)fmod(since, period);
*counter = vsyncBase_ + (uint64_t)(since / period);
return true;
}
private:
uint64_t frames_ = 0;
std::chrono::steady_clock::time_point last_;
std::mutex vsyncMutex_;
double period_ = 0.0, vsyncAt_ = 0.0;
uint64_t vsyncBase_ = 0;
};
class Vsync {
@@ -490,6 +500,8 @@ public:
if (pose_.Read(&hp) && hp.tan_half_fov > 0.0f) {
tanHalf_ = hp.tan_half_fov;
if (hp.eye_aspect > 0.0f) eyeAspect_ = hp.eye_aspect;
sentMono_ = (hp.flags & DROIDDECK_HEADPOSE_MONO) != 0;
renderScale_ = sentMono_ ? 1.0f : 1.4f;
}
if (pose_.Read(&hp) && hp.display_hz >= 24.0f) hz = hp.display_hz;
Vsync::SetRate(hz);
@@ -599,7 +611,8 @@ private:
void UpdateEye(const droiddeck_headpose &hp) {
sentTan_ = tanHalf_ = hp.tan_half_fov;
sentAspect_ = eyeAspect_ = hp.eye_aspect;
renderScale_ = (hp.flags & DROIDDECK_HEADPOSE_MONO) ? 1.0f : 1.4f;
sentMono_ = (hp.flags & DROIDDECK_HEADPOSE_MONO) != 0;
renderScale_ = sentMono_ ? 1.0f : 1.4f;
HmdRect2_t r;
r.vTopLeft.v[0] = -tanHalf_ * eyeAspect_;
r.vTopLeft.v[1] = -tanHalf_;
@@ -635,7 +648,8 @@ private:
if (fresh) {
lastSeq = hp.seq;
if (hp.eye_w > 0 && hp.tan_half_fov > 0.0f && hp.eye_aspect > 0.0f &&
(fabsf(hp.tan_half_fov - sentTan_) > 1e-3f || fabsf(hp.eye_aspect - sentAspect_) > 1e-3f))
(fabsf(hp.tan_half_fov - sentTan_) > 1e-3f || fabsf(hp.eye_aspect - sentAspect_) > 1e-3f ||
((hp.flags & DROIDDECK_HEADPOSE_MONO) != 0) != sentMono_))
UpdateEye(hp);
if (hp.display_hz >= 24.0f && fabsf(hp.display_hz - sentHz_) > 0.5f) UpdateRate(hp.display_hz);
const bool worn = (hp.flags & DROIDDECK_HEADPOSE_ENABLED) != 0;
@@ -690,6 +704,7 @@ private:
int32_t width_ = 1920, height_ = 1080;
std::atomic<float> tanHalf_{1.0f}, eyeAspect_{0.889f}, renderScale_{1.4f};
float sentTan_ = 0.0f, sentAspect_ = 0.0f, sentHz_ = 0.0f;
bool sentMono_ = false;
PropertyContainerHandle_t container_ = k_ulInvalidPropertyContainer;
public:
void SetHands(CardboardController *left, CardboardController *right) { hands_[0] = left; hands_[1] = right; }
+1 -2
View File
@@ -15,7 +15,6 @@ typedef unsigned long VisualID;
#include <algorithm>
#include <atomic>
#include <chrono>
#include <climits>
#include <condition_variable>
#include <cstdarg>
#include <cstdio>
@@ -460,7 +459,7 @@ private:
VkSemaphoreCreateInfo semci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0};
vk_.CreateSemaphore(dev_, &semci, nullptr, &acquired_);
vk_.CreateSemaphore(dev_, &semci, nullptr, &rendered_);
if (!CreateSwapchain()) return false;
if (!CreateSwapchain() && !wayland_) return false;
Log("presenter: Vulkan on %s, queue family %u", props.deviceName, qfam_);
return true;
}
+18 -1
View File
@@ -319,6 +319,23 @@ static uint32_t mode_mhz(void) {
static uint64_t frame_ns(void) { return 1000000000000ull / mode_mhz(); }
static pthread_mutex_t g_vblank_lock = PTHREAD_MUTEX_INITIALIZER;
static uint64_t g_vblank_base, g_vblank_at, g_vblank_frame;
static uint64_t vblank_count(void) {
uint64_t f = frame_ns();
pthread_mutex_lock(&g_vblank_lock);
uint64_t t = now_ns();
if (f != g_vblank_frame) {
g_vblank_base = g_vblank_frame ? g_vblank_base + (t - g_vblank_at) / g_vblank_frame : t / f;
g_vblank_at = t;
g_vblank_frame = f;
}
uint64_t n = g_vblank_base + (t - g_vblank_at) / f;
pthread_mutex_unlock(&g_vblank_lock);
return n;
}
static VkDisplayPropertiesKHR display_props(void) {
mode_init();
VkDisplayPropertiesKHR p = {0};
@@ -454,7 +471,7 @@ static VkResult VKAPI_CALL RegisterDisplayEventEXT(VkDevice d, VkDisplayKHR disp
}
static VkResult VKAPI_CALL GetSwapchainCounterEXT(VkDevice d, VkSwapchainKHR sc, VkSurfaceCounterFlagBitsEXT counter, uint64_t *value) {
mode_init();
*value = now_ns() / frame_ns();
*value = vblank_count();
return VK_SUCCESS;
}