Better logic for choosing the display mode (#1039)

## Description
This PR improves the logic used to choose a display mode when refresh
rate and resolution switching are enabled.

As before, matching the refresh rate is prioritized over picking the
resolution. For example, with the display modes of 1080@30 & 720@60 for
a 720@30 video, 1080@30 is used because the frame rate exactly matches.

### Related issues
Fixes #1036

### Testing
Unit tests, added test cases for more situations

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-03-04 19:30:14 -05:00 committed by GitHub
parent 354577e2e8
commit 014bed1bf3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 188 additions and 15 deletions

View file

@ -158,33 +158,58 @@ class RefreshRateService
if (refreshRateSwitch) { if (refreshRateSwitch) {
displayModes displayModes
.filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth } .filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth }
.filter { .filter { frameRateMatches(it.refreshRateRounded, streamRate) }
it.refreshRateRounded % streamRate == 0 || // Exact multiple
it.refreshRateRounded == (streamRate * 2.5).roundToInt() // eg 24 & 60fps
}
} else { } else {
displayModes displayModes
.filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth } .filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth }
} }
// Timber.v("display modes candidates: %s", candidates.joinToString("\n")) // Timber.v("display modes candidates: %s", candidates.joinToString("\n"))
return if (!resolutionSwitch) { return if (!resolutionSwitch) {
candidates.maxByOrNull { it.physicalWidth * it.physicalHeight } candidates
.filter { it.refreshRateRounded == streamRate }
.maxByOrNull { it.physicalWidth * it.physicalHeight }
?: candidates.maxByOrNull { it.physicalWidth * it.physicalHeight }
} else { } else {
// Exact resolution & frame rate
candidates.firstOrNull { candidates.firstOrNull {
it.physicalWidth == streamWidth && it.physicalWidth == streamWidth &&
it.physicalHeight == streamHeight && it.physicalHeight == streamHeight &&
it.refreshRateRounded == streamRate it.refreshRateRounded == streamRate
} }
?: candidates.firstOrNull { // Next highest resolution, exact frame rate
it.physicalWidth == streamWidth && ?: candidates.lastOrNull {
it.physicalWidth >= streamWidth &&
it.physicalHeight >= streamHeight && it.physicalHeight >= streamHeight &&
it.refreshRateRounded == streamRate it.refreshRateRounded == streamRate
} }
// Exact resolution, acceptable frame rate
?: candidates.lastOrNull {
it.physicalWidth == streamWidth &&
it.physicalHeight == streamHeight &&
frameRateMatches(it.refreshRateRounded, streamRate)
}
// Next highest resolution, acceptable frame rate
?: candidates.lastOrNull {
it.physicalWidth >= streamWidth &&
it.physicalHeight >= streamHeight &&
frameRateMatches(it.refreshRateRounded, streamRate)
}
// Fall back to largest resolution, exact frame rate
?: candidates ?: candidates
.filter { it.refreshRateRounded == streamRate } .filter { it.refreshRateRounded == streamRate }
.maxByOrNull { it.physicalWidth * it.physicalHeight } .maxByOrNull { it.physicalWidth * it.physicalHeight }
// Finally, just the highest resolution
?: candidates.maxByOrNull { it.physicalWidth * it.physicalHeight }
} }
} }
fun frameRateMatches(
refreshRateRounded: Int,
streamRate: Int,
): Boolean {
return refreshRateRounded % streamRate == 0 || // Exact multiple
refreshRateRounded == (streamRate * 2.5).roundToInt() // eg 24 & 60fps
}
} }
} }

View file

@ -15,7 +15,25 @@ class TestDisplayModeChoice {
val UHD_30 = DisplayMode(4, 3840, 2160, 30f) val UHD_30 = DisplayMode(4, 3840, 2160, 30f)
val UHD_24 = DisplayMode(5, 3840, 2160, 24f) val UHD_24 = DisplayMode(5, 3840, 2160, 24f)
val ALL_MODES = listOf(UHD_24, UHD_30, UHD_60, HD_24, HD_30, HD_60) val HD720_60 = DisplayMode(6, 1280, 720, 60f)
val HD720_30 = DisplayMode(7, 1280, 720, 30f)
val HD720_24 = DisplayMode(8, 1280, 720, 24f)
val ALL_MODES =
listOf(
UHD_24,
UHD_30,
UHD_60,
HD_24,
HD_30,
HD_60,
HD720_60,
HD720_30,
HD720_24,
).sortedWith(
compareByDescending<DisplayMode>({ it.physicalWidth * it.physicalHeight })
.thenBy { it.refreshRateRounded },
)
} }
@Test @Test
@ -32,7 +50,7 @@ class TestDisplayModeChoice {
refreshRateSwitch = true, refreshRateSwitch = true,
resolutionSwitch = false, resolutionSwitch = false,
) )
Assert.assertEquals(3, result?.modeId) Assert.assertEquals(UHD_60.modeId, result?.modeId)
} }
@Test @Test
@ -49,7 +67,7 @@ class TestDisplayModeChoice {
refreshRateSwitch = true, refreshRateSwitch = true,
resolutionSwitch = true, resolutionSwitch = true,
) )
Assert.assertEquals(0, result?.modeId) Assert.assertEquals(HD_60.modeId, result?.modeId)
} }
@Test @Test
@ -66,7 +84,7 @@ class TestDisplayModeChoice {
refreshRateSwitch = true, refreshRateSwitch = true,
resolutionSwitch = false, resolutionSwitch = false,
) )
Assert.assertEquals(4, result?.modeId) Assert.assertEquals(UHD_30.modeId, result?.modeId)
} }
@Test @Test
@ -83,14 +101,14 @@ class TestDisplayModeChoice {
refreshRateSwitch = false, refreshRateSwitch = false,
resolutionSwitch = true, resolutionSwitch = true,
) )
Assert.assertEquals(1, result?.modeId) Assert.assertEquals(HD_30.modeId, result?.modeId)
} }
@Test @Test
fun testFraction() { fun testFraction() {
val streamWidth = 1920 val streamWidth = 1920
val streamHeight = 1080 val streamHeight = 1080
val streamRealFrameRate = 30f val streamRealFrameRate = 29.970f
val displayModes = val displayModes =
listOf( listOf(
@ -104,7 +122,7 @@ class TestDisplayModeChoice {
displayModes = displayModes, displayModes = displayModes,
streamWidth = streamWidth, streamWidth = streamWidth,
streamHeight = streamHeight, streamHeight = streamHeight,
targetFrameRate = 29.970f, targetFrameRate = streamRealFrameRate,
refreshRateSwitch = true, refreshRateSwitch = true,
resolutionSwitch = false, resolutionSwitch = false,
) )
@ -119,6 +137,136 @@ class TestDisplayModeChoice {
refreshRateSwitch = true, refreshRateSwitch = true,
resolutionSwitch = false, resolutionSwitch = false,
) )
Assert.assertEquals(1, result2?.modeId) Assert.assertEquals(HD_30.modeId, result2?.modeId)
}
private fun test(
expected: DisplayMode,
streamWidth: Int,
streamHeight: Int,
streamRealFrameRate: Float,
) {
val result =
RefreshRateService.findDisplayMode(
displayModes = ALL_MODES,
streamWidth = streamWidth,
streamHeight = streamHeight,
targetFrameRate = streamRealFrameRate,
refreshRateSwitch = false,
resolutionSwitch = true,
)
Assert.assertEquals(
"streamWidth=$streamWidth, streamHeight=$streamHeight, streamRealFrameRate=$streamRealFrameRate",
expected.modeId,
result?.modeId,
)
}
@Test
fun `Test choose best resolution`() {
test(HD720_30, 1280, 720, 30f)
test(HD720_30, 1280, 548, 30f)
test(HD720_30, 640, 480, 30f)
test(HD720_30, 960, 720, 30f)
test(HD720_24, 960, 720, 24f)
}
@Test
fun `Test 60fps for 24 without resolution switch`() {
val streamWidth = 1920
val streamHeight = 1080
val streamRealFrameRate = 24f
val displayModes =
listOf(
UHD_60,
HD_60,
HD_30,
)
val result =
RefreshRateService.findDisplayMode(
displayModes = displayModes,
streamWidth = streamWidth,
streamHeight = streamHeight,
targetFrameRate = streamRealFrameRate,
refreshRateSwitch = true,
resolutionSwitch = false,
)
Assert.assertEquals(UHD_60.modeId, result?.modeId)
}
@Test
fun `Test 60fps for 24 with resolution switch`() {
val streamWidth = 1920
val streamHeight = 1080
val streamRealFrameRate = 24f
val displayModes =
listOf(
HD_60,
HD_30,
)
val result =
RefreshRateService.findDisplayMode(
displayModes = displayModes,
streamWidth = streamWidth,
streamHeight = streamHeight,
targetFrameRate = streamRealFrameRate,
refreshRateSwitch = true,
resolutionSwitch = true,
)
Assert.assertEquals(HD_60.modeId, result?.modeId)
}
@Test
fun `Test prefer refresh rate over resolution`() {
val streamWidth = 1280
val streamHeight = 720
val streamRealFrameRate = 24f
// 720@60 is an acceptable refresh rate, but want to prioritize exact refresh rate
val displayModes =
listOf(
HD_24,
HD720_60,
)
val result =
RefreshRateService.findDisplayMode(
displayModes = displayModes,
streamWidth = streamWidth,
streamHeight = streamHeight,
targetFrameRate = streamRealFrameRate,
refreshRateSwitch = true,
resolutionSwitch = true,
)
Assert.assertEquals(HD_24.modeId, result?.modeId)
}
@Test
fun `Test prefer refresh rate over resolution2`() {
val streamWidth = 1280
val streamHeight = 720
val streamRealFrameRate = 30f
// 720@60 is an acceptable refresh rate, but want to prioritize exact refresh rate
val displayModes =
listOf(
HD_30,
HD720_60,
)
val result =
RefreshRateService.findDisplayMode(
displayModes = displayModes,
streamWidth = streamWidth,
streamHeight = streamHeight,
targetFrameRate = streamRealFrameRate,
refreshRateSwitch = true,
resolutionSwitch = true,
)
Assert.assertEquals(HD_30.modeId, result?.modeId)
} }
} }