Skip to content

Windows F2 overlay GPU % shows frame-budget ratio, not actual GPU utilization #588

Description

@scottdraves

Problem

The F2 status overlay labels its GPU number as utilization, but on Windows the value shown is frameTimeMs / vsyncBudget × 100 — i.e. "how much of the vsync budget we spent rendering," not "how much of the GPU hardware is in use."

These are different measurements and can diverge significantly:

  • With vsync on, the GPU may finish rendering and sit idle for the rest of the interval. Frame-budget ratio reports (say) 80% while the hardware is actually ~40% utilized.
  • Under heavy fragment-shader work (e.g. cubic frame blending at 4K), frame-budget ratio can hit 100% while the GPU still has compute engines idle.
  • Decode work on the Video Decode engine is invisible to the frame-budget ratio entirely.

The HUD label says "total" which users read as overall device utilization (matching Task Manager's GPU column), but that's not what they're getting on Windows.

Current state

Two separate measurements exist and are combined on the HUD at client.h:1974-1978:

float gpuMs  = g_Player().Renderer()->GetGPUFrameTimeMs();
float gpuPct = g_Player().Renderer()->GetGPUUtilization();
std::string sysGpu = (m_GpuUsage >= 0) ? string_format(", %i%%", m_GpuUsage) : "";
// displayed as: "%.2fms (%.0f%%)%s total"
//                         ^^^^   ^^
//                   render%     sysGpu
  • GetGPUUtilization() (renderer-provided) — frame-budget ratio. Implemented on both platforms.
  • GetGpuUsage() (platform-provided) — real device utilization. On Mac this reads IOKit's IOAcceleratorPerformanceStatistics"Device Utilization %" (cpu_usage_mac.h:181-240) and appears on the HUD as ", NN% total".
  • On Windows, GetGpuUsage() in MSVC/cpu_usage_win32.h:128-131 just returns -1, so the "real" suffix is never shown. All the user sees is the misleading frame-budget ratio.

Proposed fix

Implement GetGpuUsage() on Windows using PDH performance counters — the same API Task Manager uses.

Scope: process-scoped (our PID only), max across engines. This answers "how much GPU is this app using" and matches the mental model users have when they see a GPU % next to a process.

Implementation sketch (in MSVC/cpu_usage_win32.h):

  1. Open a PDH query on first call (PdhOpenQuery).
  2. Enumerate instances of GPU Engine via PdhEnumObjectItems; filter to instances whose name contains pid_<our_pid>_ (PDH's GPU-engine instance naming convention: pid_<pid>_luid_<luid>_phys_<n>_eng_<m>_engtype_<type>).
  3. PdhAddEnglishCounter for \GPU Engine(<instance>)\Utilization Percentage for each matching instance.
  4. Cache on ~1s sampling (match Mac's GetGpuUsage cadence) to avoid PDH overhead per frame.
  5. On each sample: PdhCollectQueryData, read each counter's formatted value, take the max across engines, clamp to [0,100].
  6. Return -1 if PDH init fails, so the HUD suppression at client.h:1974 keeps working.

Handle the case where new child engines appear (rebuild the counter set periodically, or on a miss).

Follow-up consideration (separate issue?): the frame-budget ratio shown alongside is still useful — arguably rename it on the HUD so its meaning is clear (e.g. %.2fms (%.0f%% budget), %i%% GPU). Right now both numbers being labeled as GPU-related is what makes the "80% GPU for a video" reading surprising.

Repro

  1. Run a dream on Windows with F2 overlay enabled.
  2. Compare the HUD GPU % against Task Manager → Performance → GPU, or GPU-Z.
  3. Open Task Manager Details tab, add the "GPU Engine" column on the process — that's the ground truth we should match.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions