Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ fun PolarisGame.resolveLaunchModeChoice(defaultToVirtualDisplay: Boolean, client
val virtualAvailable = modeAvailability(clientSettings, "virtual_display")
val headlessAllowed = (contract?.allows("headless") ?: true) && headlessAvailable != false
val virtualDisplayAllowed = (contract?.allows("virtual_display") ?: true) && virtualAvailable != false
val hostDefaultMode = PolarisGame.resolveLaunchMode(
clientSettings?.desired?.streamDisplayMode?.takeIf { it.isNotBlank() } ?: clientSettings?.effective?.streamDisplayMode ?: "",
headlessAllowed,
virtualDisplayAllowed
)
val hostRequestedMode = clientSettings?.desired?.streamDisplayMode?.takeIf { it.isNotBlank() }
?: clientSettings?.effective?.streamDisplayMode?.takeIf { it.isNotBlank() }
?: ""
val hostDefaultMode = hostRequestedMode.takeIf { it.isNotBlank() }?.let {
PolarisGame.resolveLaunchMode(it, headlessAllowed, virtualDisplayAllowed)
} ?: ""
val fallbackMode = if (defaultToVirtualDisplay && virtualDisplayAllowed) "virtual_display" else "headless"
val preferredMode = PolarisGame.resolveLaunchMode(contract?.preferredMode?.takeIf { it.isNotBlank() } ?: fallbackMode, headlessAllowed, virtualDisplayAllowed)
val recommendedMode = PolarisGame.resolveLaunchMode(hostDefaultMode.takeIf { it.isNotBlank() } ?: contract?.recommendedMode?.takeIf { it.isNotBlank() } ?: preferredMode, headlessAllowed, virtualDisplayAllowed)
Expand All @@ -35,16 +36,22 @@ private val VIRTUAL_DISPLAY_MODE_ALIASES = setOf("virtual_display", PolarisClien

private fun modeAvailability(clientSettings: PolarisClientSettings?, mode: String): Boolean? {
val modes = clientSettings?.capabilities?.modes ?: return null
val aliases = aliasesForMode(mode)
val matches = modes.filter { it.value in aliases }
val matches = matchingModes(modes, mode)
if (matches.isEmpty()) return null
return matches.any { it.available }
}

private fun modeUnavailableReason(clientSettings: PolarisClientSettings?, mode: String): String {
val modes = clientSettings?.capabilities?.modes ?: return ""
return matchingModes(modes, mode).firstOrNull { !it.available }?.reason.orEmpty()
}

private fun matchingModes(modes: List<PolarisClientSettings.ModeOption>, mode: String): List<PolarisClientSettings.ModeOption> {
val aliases = aliasesForMode(mode)
return modes.firstOrNull { it.value in aliases && !it.available }?.reason.orEmpty()
val normalizedMode = PolarisGame.normalizeLaunchMode(mode)
return modes.filter { option ->
option.value in aliases || PolarisGame.normalizeLaunchMode(option.value) == normalizedMode
}
}

private fun aliasesForMode(mode: String): Set<String> {
Expand Down
8 changes: 7 additions & 1 deletion app/src/main/java/com/papi/nova/api/PolarisSessionStatus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ data class PolarisSessionStatus(
val isShuttingDown get() = shutdownRequested || normalizedState == "tearing_down"
val isResumable get() = !isShuttingDown && gameId > 0 && (isSessionAlive || isPausedForResume)
val isTenBitActive get() = dynamicRange > 0 || encoder.targetFormat.equals("p010", ignoreCase = true)
val isGpuPath get() = encoder.targetResidency.equals("gpu", ignoreCase = true)
val isGpuPath get() = encoder.targetResidency.equals("gpu", ignoreCase = true) ||
(encoder.targetResidency.isBlank() && encoder.targetDevice.isCudaGpuTarget)
val isHeadlessMode get() = displayMode.effectiveHeadless
val isVirtualDisplayMode get() = displayMode.virtualDisplay
val sessionModeLabel get() = when {
Expand All @@ -277,6 +278,11 @@ data class PolarisSessionStatus(
val hasExplicitDisplayModeChoice get() = displayMode.explicitChoice
val canAdjustHostTuning get() = controls.hostTuningAllowed || (ownedByClient && !isViewer)
val canQuit get() = controls.quitAllowed || (ownedByClient && !isViewer)

private val String.isCudaGpuTarget: Boolean
get() = equals("cuda", ignoreCase = true) ||
equals("gpu", ignoreCase = true) ||
equals("nvidia", ignoreCase = true)
val isClientPresentationSynced get() = clientPresentation.status.equals("synced", ignoreCase = true)
val hasOptimizerSync get() = syncStatus.available
val optimizationSourceLabel get() = when {
Expand Down
22 changes: 17 additions & 5 deletions app/src/main/java/com/papi/nova/ui/NovaPolarisSyncUiState.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ object NovaPolarisSyncUiStateMapper {
unsetLabel: String = "Unset"
): NovaPolarisSyncUiState {
val fallback = if (settingsUnavailable) unavailableLabel else loadingLabel
val selectedMode = settings?.desired?.streamDisplayMode?.takeIf { it.isNotBlank() }
?: settings?.effective?.streamDisplayMode.orEmpty()
val selectedMode = canonicalDisplayMode(
settings?.desired?.streamDisplayMode?.takeIf { it.isNotBlank() }
?: settings?.effective?.streamDisplayMode.orEmpty()
)
val availableModes = settings?.capabilities?.modes
?.takeIf { it.isNotEmpty() }
?.associateBy { it.value }
?.groupBy { canonicalDisplayMode(it.value) }
?.mapValues { (_, modes) -> modes.any { it.available } }
val profileState = PolarisProfileSync.compare(novaDisplayMode, novaBitrateKbps, settings)
val hasPolarisProfile = settings?.let { PolarisProfileSync.polarisOverrideProfile(it) } != null
val aiAvailable = settings?.capabilities?.aiAutoQualityControl == true ||
Expand All @@ -81,10 +84,11 @@ object NovaPolarisSyncUiStateMapper {
desiredModeLabel = settings?.desiredModeLabel?.ifBlank { unsetLabel } ?: fallback,
effectiveModeLabel = settings?.effectiveModeLabel?.ifBlank { unsetLabel } ?: fallback,
modes = DISPLAY_MODES.map { mode ->
val available = availableModes?.get(mode)?.available ?: true
val canonicalMode = canonicalDisplayMode(mode)
val available = availableModes?.get(canonicalMode) ?: true
NovaPolarisModeUiState(
mode = mode,
selected = selectedMode == mode,
selected = selectedMode == canonicalMode,
enabled = settings != null && available && !busy
)
},
Expand All @@ -100,4 +104,12 @@ object NovaPolarisSyncUiStateMapper {
autoSyncEnabled = hasServerUuid && settings != null && !busy
)
}

private fun canonicalDisplayMode(mode: String): String {
return when (mode.trim().lowercase()) {
"headless", PolarisClientSettings.MODE_HEADLESS_STREAM, "host_display" -> PolarisClientSettings.MODE_HEADLESS_STREAM
"virtual_display", PolarisClientSettings.MODE_HOST_VIRTUAL_DISPLAY -> PolarisClientSettings.MODE_HOST_VIRTUAL_DISPLAY
else -> mode.trim().lowercase()
}
}
}
17 changes: 17 additions & 0 deletions app/src/test/java/com/papi/nova/api/PolarisApiClientParsingTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,23 @@ class PolarisApiClientParsingTest {
assertEquals("Black Myth: Wukong", body.getString("game"))
}

@Test
fun parseSessionStatus_cudaTargetDeviceImpliesGpuPathWhenResidencyMissing() {
val status = PolarisApiClient.parseSessionStatusResponse(
JSONObject(
"{\"state\":\"streaming\",\"streaming_active\":true," +
"\"display_mode\":{\"label\":\"Virtual Display\",\"selection\":\"virtual_display\"," +
"\"virtual_display\":true,\"effective_headless\":false}," +
"\"capture\":{\"transport\":\"dmabuf\"}," +
"\"encoder\":{\"codec\":\"hevc_nvenc\",\"target_device\":\"cuda\",\"target_format\":\"p010\"}}"
)
)

assertEquals("cuda", status.encoder.targetDevice)
assertTrue(status.isGpuPath)
assertTrue(status.isTenBitActive)
}

@Test
fun parseGameResponse_includesLaunchModeContract() {
val json = JSONObject(
Expand Down
20 changes: 20 additions & 0 deletions app/src/test/java/com/papi/nova/ui/NovaGameDetailUiStateTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ class NovaGameDetailUiStateTest {
assertEquals("quality", state.profilePreference)
}

@Test
fun virtualRecommendationWinsWhenClientSettingsHaveNoDisplayMode() {
val state = NovaGameDetailUiState.from(
game = game(
launchMode = PolarisGame.LaunchModeContract(
preferredMode = "headless",
recommendedMode = "virtual_display",
allowedModes = listOf("headless", "virtual_display")
)
),
defaultToVirtualDisplay = false,
clientSettings = PolarisClientSettings(),
profilePreference = "auto"
)

assertEquals("virtual_display", state.playMode)
assertTrue(state.playUsesVirtualDisplay)
assertTrue(state.playEnabled)
}

@Test
fun unavailableVirtualDisplayFallsBackToHeadlessAndShowsUnavailableState() {
val state = NovaGameDetailUiState.from(
Expand Down
30 changes: 30 additions & 0 deletions app/src/test/java/com/papi/nova/ui/NovaHudUiStateTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,36 @@ class NovaHudUiStateTest {
assertEquals(listOf(55f, 58f, 60f), state.sparklineSamples)
}

@Test
fun cudaTargetDeviceKeepsGpuPathInStreamModeLabelWhenResidencyMissing() {
val state = NovaHudUiState.from(
mode = NovaHudMode.DEBUG,
fps = 59.8,
targetFps = 60.0,
latencyMs = 18,
codec = "hevc_nvenc",
bitrateKbps = 20000,
width = 1920,
height = 1080,
status = status(
encoder = PolarisSessionStatus.EncoderStatus(
codec = "hevc_nvenc",
targetDevice = "cuda",
targetResidency = "",
targetFormat = "p010"
),
capture = PolarisSessionStatus.CaptureStatus(
transport = "dmabuf",
residency = ""
)
),
sparklineSamples = emptyList()
)

assertTrue(state.streamModeLabel.contains("GPU"))
assertTrue(state.streamModeLabel.contains("10b"))
}

@Test
fun hudModesMapCasualPerformanceAndDebugPreferences() {
assertEquals(NovaHudMode.MINIMAL, NovaHudMode.fromPreference("minimal"))
Expand Down
28 changes: 28 additions & 0 deletions app/src/test/java/com/papi/nova/ui/NovaPolarisSyncUiStateTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,34 @@ class NovaPolarisSyncUiStateTest {
assertTrue(state.clearProfileEnabled)
}

@Test
fun normalizedVirtualDisplayModeSelectsAndDisablesCanonicalRow() {
val state = NovaPolarisSyncUiStateMapper.build(
settings = PolarisClientSettings(
desired = PolarisClientSettings.Desired(streamDisplayMode = "virtual_display"),
capabilities = PolarisClientSettings.Capabilities(
modes = listOf(
PolarisClientSettings.ModeOption(
value = "virtual_display",
available = false,
reason = "CUDA capture path is disabled"
)
)
)
),
busy = false,
settingsUnavailable = false,
autoSyncEnabled = false,
hasServerUuid = true,
novaDisplayMode = "1920x1080@60",
novaBitrateKbps = 30000
)

val virtual = state.modes.first { it.mode == PolarisClientSettings.MODE_HOST_VIRTUAL_DISPLAY }
assertTrue(virtual.selected)
assertFalse(virtual.enabled)
}

@Test
fun busyStateDisablesAllMutatingActions() {
val state = NovaPolarisSyncUiStateMapper.build(
Expand Down
Loading