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) {
displayModes
.filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth }
.filter {
it.refreshRateRounded % streamRate == 0 || // Exact multiple
it.refreshRateRounded == (streamRate * 2.5).roundToInt() // eg 24 & 60fps
}
.filter { frameRateMatches(it.refreshRateRounded, streamRate) }
} else {
displayModes
.filterNot { it.physicalHeight < streamHeight || it.physicalWidth < streamWidth }
}
// Timber.v("display modes candidates: %s", candidates.joinToString("\n"))
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 {
// Exact resolution & frame rate
candidates.firstOrNull {
it.physicalWidth == streamWidth &&
it.physicalHeight == streamHeight &&
it.refreshRateRounded == streamRate
}
?: candidates.firstOrNull {
it.physicalWidth == streamWidth &&
// Next highest resolution, exact frame rate
?: candidates.lastOrNull {
it.physicalWidth >= streamWidth &&
it.physicalHeight >= streamHeight &&
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
.filter { it.refreshRateRounded == streamRate }
.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_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
@ -32,7 +50,7 @@ class TestDisplayModeChoice {
refreshRateSwitch = true,
resolutionSwitch = false,
)
Assert.assertEquals(3, result?.modeId)
Assert.assertEquals(UHD_60.modeId, result?.modeId)
}
@Test
@ -49,7 +67,7 @@ class TestDisplayModeChoice {
refreshRateSwitch = true,
resolutionSwitch = true,
)
Assert.assertEquals(0, result?.modeId)
Assert.assertEquals(HD_60.modeId, result?.modeId)
}
@Test
@ -66,7 +84,7 @@ class TestDisplayModeChoice {
refreshRateSwitch = true,
resolutionSwitch = false,
)
Assert.assertEquals(4, result?.modeId)
Assert.assertEquals(UHD_30.modeId, result?.modeId)
}
@Test
@ -83,14 +101,14 @@ class TestDisplayModeChoice {
refreshRateSwitch = false,
resolutionSwitch = true,
)
Assert.assertEquals(1, result?.modeId)
Assert.assertEquals(HD_30.modeId, result?.modeId)
}
@Test
fun testFraction() {
val streamWidth = 1920
val streamHeight = 1080
val streamRealFrameRate = 30f
val streamRealFrameRate = 29.970f
val displayModes =
listOf(
@ -104,7 +122,7 @@ class TestDisplayModeChoice {
displayModes = displayModes,
streamWidth = streamWidth,
streamHeight = streamHeight,
targetFrameRate = 29.970f,
targetFrameRate = streamRealFrameRate,
refreshRateSwitch = true,
resolutionSwitch = false,
)
@ -119,6 +137,136 @@ class TestDisplayModeChoice {
refreshRateSwitch = true,
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)
}
}