Repository navigation
Apply the 32-bit CPU list on Proton builds without _NET_WM_WOW64 #2383
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,14 @@ package app.gamenative.powercontrol | |
| import app.gamenative.PluviaApp | ||
| import app.gamenative.display.GameNativeDrm | ||
| import app.gamenative.events.AndroidEvent | ||
| import com.winlator.container.Container | ||
| import com.winlator.core.ProcessHelper | ||
| import com.winlator.winhandler.OnGetProcessInfoListener | ||
| import com.winlator.winhandler.ProcessInfo | ||
| import com.winlator.winhandler.WinHandler | ||
| import timber.log.Timber | ||
| import java.util.concurrent.CountDownLatch | ||
| import java.util.concurrent.Executors | ||
| import java.util.concurrent.TimeUnit | ||
|
|
||
| /** | ||
|
|
@@ -97,6 +100,49 @@ object ContainerAffinity { | |
| } | ||
| } | ||
|
|
||
| private val windowProcessExecutor = Executors.newSingleThreadExecutor { Thread(it, "container-affinity-window") } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| /** | ||
| * X11 path for a Wine build that sets no _NET_WM_WOW64 on its windows (Proton 10 and later): | ||
| * the window cannot say whether its process is WoW64, so the winhandler's process list | ||
| * decides. The WoW64 list is only sent when it was set explicitly; with the untouched default, | ||
| * or without an answer from the winhandler, the process gets [mask], as before. | ||
| */ | ||
| fun applyToWindowProcess(winHandler: WinHandler, pid: Int, name: String, mask: Int, maskWoW64: Int) { | ||
| val defaultMaskWoW64 = ProcessHelper.getAffinityMask(Container.getFallbackCPUListWoW64()).toShort().toInt() | ||
| if (!isExplicitWoW64Mask(maskWoW64, defaultMaskWoW64)) { | ||
| if (mask != 0) winHandler.setProcessAffinity(pid, mask) | ||
| return | ||
| } | ||
| windowProcessExecutor.execute { | ||
| var processes: List<ProcessInfo>? = null | ||
| for (attempt in 1..REPLY_ATTEMPTS) { | ||
| if (WinHandler.getActiveInstance() !== winHandler) return@execute | ||
| processes = listProcesses(winHandler) | ||
| if (processes != null) break | ||
| } | ||
| val info = processes?.firstOrNull { it.pid == pid } | ||
|
Comment on lines
+121
to
+124
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| val target = windowProcessMask(info, mask, maskWoW64, defaultMaskWoW64) ?: return@execute | ||
| if (WinHandler.getActiveInstance() !== winHandler) return@execute | ||
| winHandler.setProcessAffinity(pid, target) | ||
| Timber.tag(TAG).i( | ||
| "Applied mask 0x${Integer.toHexString(target)} to $name (PID $pid, wow64=${info?.wow64Process ?: "unknown"})" | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| internal fun isExplicitWoW64Mask(maskWoW64: Int, defaultMaskWoW64: Int): Boolean = | ||
| maskWoW64 != 0 && maskWoW64 != defaultMaskWoW64 | ||
|
Comment on lines
+134
to
+135
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| /** | ||
| * The mask for a window's process: the WoW64 list when the winhandler reports it WoW64 and | ||
| * the list differs from the default, else the main list. | ||
| */ | ||
| internal fun windowProcessMask(info: ProcessInfo?, mask: Int, maskWoW64: Int, defaultMaskWoW64: Int): Int? { | ||
| val wow64 = info?.wow64Process == true && isExplicitWoW64Mask(maskWoW64, defaultMaskWoW64) | ||
| return (if (wow64) maskWoW64 else mask).takeIf { it != 0 } | ||
| } | ||
|
|
||
| /** | ||
| * The mask [info] should get, or null when the process is left alone. | ||
| */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -54,6 +54,40 @@ class ContainerAffinityTest { | |||||||
| assertNull(ContainerAffinity.targetMask(process("Game32.exe", wow64 = true), mask, 0, pinnedGame = null, powerControlOwnsGame = false)) | ||||||||
| } | ||||||||
|
|
||||||||
| private val defaultMaskWoW64 = 0xF0 // upper half of 8 cores | ||||||||
|
|
||||||||
| @Test | ||||||||
| fun windowProcessWoW64WithAnExplicitListGetsTheWoW64List() { | ||||||||
| assertEquals(maskWoW64, ContainerAffinity.windowProcessMask(process("Borderlands2.exe", wow64 = true), mask, maskWoW64, defaultMaskWoW64 = 0x0F)) | ||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| fun windowProcessWoW64WithTheDefaultListGetsTheMainList() { | ||||||||
| assertEquals(0xFF, ContainerAffinity.windowProcessMask(process("Borderlands2.exe", wow64 = true), 0xFF, defaultMaskWoW64, defaultMaskWoW64)) | ||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| fun windowProcessReported64BitGetsTheMainList() { | ||||||||
| assertEquals(mask, ContainerAffinity.windowProcessMask(process("Game.exe"), mask, maskWoW64, defaultMaskWoW64)) | ||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| fun windowProcessMissingFromTheListGetsTheMainList() { | ||||||||
| assertEquals(mask, ContainerAffinity.windowProcessMask(null, mask, maskWoW64, defaultMaskWoW64)) | ||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| fun windowProcessWithAnEmptyMaskGetsNoRequest() { | ||||||||
| assertNull(ContainerAffinity.windowProcessMask(process("Game.exe"), 0, maskWoW64, defaultMaskWoW64)) | ||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P3: This test no longer verifies the empty WoW64-list fallback. Add a WoW64 assertion showing that a zero, non-explicit WoW64 list selects the main mask, otherwise a regression in that branch can pass. Prompt for AI agents
Suggested change
|
||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| fun onlyANonDefaultNonEmptyWoW64ListIsExplicit() { | ||||||||
| assertTrue(ContainerAffinity.isExplicitWoW64Mask(0xC0, 0xF0)) | ||||||||
| assertFalse(ContainerAffinity.isExplicitWoW64Mask(0xF0, 0xF0)) | ||||||||
| assertFalse(ContainerAffinity.isExplicitWoW64Mask(0, 0xF0)) | ||||||||
| } | ||||||||
|
|
||||||||
| @Test | ||||||||
| fun reportedMaskComparesOnTheLowSixteenBits() { | ||||||||
| assertTrue(ContainerAffinity.sameMask(0xF0, 0xF0)) | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P3: This global executor is never shut down or cancelled at session exit, so the first X11 window leaves an idle worker for the app lifetime and queued retries can delay a later session. Make the executor session-scoped and cancel it with the WinHandler/session.
Prompt for AI agents