Skip to content

feat: add CPU Core Affinity tab with P-core/E-core awareness - #1081

Merged
laurentiu021 merged 1 commit into
mainfrom
feat/cpu-affinity
Jun 26, 2026
Merged

laurentiu021 merged 1 commit into
mainfrom
feat/cpu-affinity

Conversation

@laurentiu021

@laurentiu021 laurentiu021 commented Jun 26, 2026 •

Copy link
Copy Markdown
Owner

What

Implements CPU Core Affinity (Gaming & Profiles), replacing its WIP placeholder. Closes #327.

Pin a running process to specific CPU cores — useful for games on Intel hybrid CPUs where P-cores and E-cores behave differently.

How

  • CpuAffinityService:
    • Set/get affinity via .NET's Process.ProcessorAffinity (no P/Invoke for the set; the runtime calls SetProcessAffinityMask).
    • P/E detection via kernel32 GetLogicalProcessorInformationEx(RelationProcessorCore) — the variable-length buffer is walked by each record's Size (not Marshal.SizeOf, which would corrupt), reading EfficiencyClass at a fixed offset. Highest class = P-core; if all equal → homogeneous → "Standard". Classic [DllImport] (raw IntPtr buffer, no A/W variant → no EntryPoint).
    • Single processor group (≤64 logical CPUs) — covers all consumer hybrid CPUs.
  • Safety:
    • An empty mask is rejected — Windows treats 0 as "let the OS decide", so without this guard an empty selection would silently do the opposite of what the user picked.
    • Affinity is per-running-process and lost on exit → inherently temporary and reversible; the VM also snapshots the original mask for an explicit Restore.
    • No admin for your own processes; another user's / elevated process raises access-denied → surfaced as "needs admin", never an unhandled exception.
  • CpuAffinityViewModel: process picker, per-core checkbox map with P/E labels, P-cores / All cores presets, Apply + Restore.

Preview

Marked PREVIEW (sidebar pill + banner).

Tests

  • CpuAffinityServiceTests — AllCoresMask, MaskFromIndices (incl. out-of-range drop), IsCoreInMask, round-trip.
  • CpuCoreTests — display + P/E type flags.
  • Integration: NavLeaf_CpuAffinity_IsImplementedAndMarkedPreview pins the WIP→real-view swap + preview flag.

Verification

  • Build main + unit + integration: 0/0. No generic/empty catch, no debug code.
  • README + ARCHITECTURE updated (counts 43→44 implemented / 14→13 WIP). feat: -> minor 1.38.0.

Future scope (out of scope here)

Auto-apply-on-launch and per-game saved profiles need a watcher / launcher and are noted for a later pass.

Implements CPU Core Affinity (replaces its WIP placeholder in Gaming &
Profiles). Closes #327.

- CpuAffinityService: gets/sets per-process affinity via .NET's
  Process.ProcessorAffinity (no P/Invoke needed for the set), and detects
  P-core/E-core topology on Intel hybrid CPUs via kernel32
  GetLogicalProcessorInformationEx — walking the variable-length buffer by
  each record's Size and reading EfficiencyClass (max class = P-core).
  Single processor group (<=64 logical CPUs), which covers consumer CPUs.
- Pure, unit-tested mask helpers (AllCoresMask / MaskFromIndices /
  IsCoreInMask); an empty mask is rejected (0 means "OS decides" to Windows).
- CpuAffinityViewModel: process picker, per-core checkbox map with P/E
  labels, P-cores / All-cores presets, Apply + Restore (snapshot of the
  original mask). Access-denied on another user's process is surfaced as
  "needs admin", never an unhandled exception.
- Affinity is per-running-process and reverts on exit, so it's temporary
  and reversible; no admin for your own processes.
- Marked PREVIEW via the badge mechanism.

Auto-apply-on-launch + per-game save are noted as future scope.
{
uint length = 0;
// Pass 1: probe required size (must fail with ERROR_INSUFFICIENT_BUFFER).
if (NativeMethods.GetLogicalProcessorInformationEx(NativeMethods.RelationProcessorCore, IntPtr.Zero, ref length))
IntPtr buffer = Marshal.AllocHGlobal((int)length);
try
{
if (!NativeMethods.GetLogicalProcessorInformationEx(NativeMethods.RelationProcessorCore, buffer, ref length))
// No A/W variant (not a string function) → no EntryPoint suffix needed.
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetLogicalProcessorInformationEx(int relationshipType, IntPtr buffer, ref uint returnedLength);
[RelayCommand]
private void SelectPerformanceCores()
{
foreach (var c in Cores) c.IsSelected = IsHybrid ? c.Core.IsPerformance : true;
@laurentiu021
laurentiu021 merged commit fe92521 into main Jun 26, 2026
4 checks passed
@laurentiu021
laurentiu021 deleted the feat/cpu-affinity branch June 26, 2026 14:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Enhancement]: Gaming — CPU Core Affinity Manager (P-core/E-core aware)

2 participants