Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/architecture/02-tick-loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,15 @@ not see it - a silently evicted hook). Measured: Wind CPU at 1x 37.5 -> 3.1 ms p
starts 3-6 ms after a wheel notch that wakes it. The focus tracker installs its LOCATIONCHANGE
hook and 16 ms caret poll only while active, and is switched off at every zoom-out.

## Priority under background load (issue #334)

The tick thread runs at `THREAD_PRIORITY_HIGHEST` and the process opts out of power throttling
(`src/sched_priority.h`, both set once before the loop). At normal priority a saturated PC (renders,
builds) queued the tick behind every other normal thread: traces showed 139-737 ms stalls mid-zoom,
only while the machine was busy. One step above normal is enough to win against background work and
stays below DWM, so the tick never delays composition; the loop sleeps or timer-waits, so it costs
nothing at rest. It does not help when the GPU is the bottleneck (DWM does that work).

## Threads: hooks, the focus tracker, and the Magnification runtime

Wind has three threads that matter beyond the tick thread itself, each split off for its own
Expand Down
5 changes: 5 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ static void EnsureCompositePulse() {
#include "cursor_lock.h"
#include "inspect_focus.h"
#include "launch_quiesce.h"
#include "sched_priority.h" // tick thread priority + no power throttling (#334)
#include "resource.h"

using namespace wind;
Expand Down Expand Up @@ -3179,6 +3180,10 @@ int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR, int) {
int pacedHz = ts.hz; // hz the timer interval below is computed for
LARGE_INTEGER due; due.QuadPart = -(10000000LL / pacedHz);

// Background CPU load must not stall a zoom (#334): this thread runs the tick loop.
wind::RaiseTickThreadPriority();
wind::OptOutOfPowerThrottling();

bool running = true;
unsigned long long nextRecoverMs = 0; // device-lost recovery backoff gate (GetTickCount64)
// The transform model does no blocking present, so it can never self-pace via Present(1,0) or
Expand Down
30 changes: 30 additions & 0 deletions src/sched_priority.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// src/sched_priority.h - keep the tick loop on time when the rest of the PC is busy (issue #334).
//
// The tick thread ran at normal priority, so under heavy background CPU load (renders, builds) it
// queued behind every other normal thread and a zoom in/out froze for a few frames (traces: 139-737 ms
// stalls, only while the machine was saturated). Wind also never owns a foreground window, so Windows
// may power-throttle it as a background process.
//
// THREAD_PRIORITY_HIGHEST, not MMCSS or the realtime band: one step above normal work is enough to win
// against background load, and it stays below DWM, so the tick can never delay composition. The loop
// sleeps at 1x and waits on a timer when zoomed, so the raised priority costs nothing while idle.
#pragma once
#include <windows.h>

namespace wind {

// Call once on the tick thread, before the loop.
inline void RaiseTickThreadPriority() {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
}

// Never run Wind in EcoQoS / efficiency mode, and honour its timer resolution even with no visible window.
inline void OptOutOfPowerThrottling() {
PROCESS_POWER_THROTTLING_STATE s{};
s.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
s.ControlMask = PROCESS_POWER_THROTTLING_EXECUTION_SPEED | PROCESS_POWER_THROTTLING_IGNORE_TIMER_RESOLUTION;
s.StateMask = 0; // controlled bits off = never throttled
SetProcessInformation(GetCurrentProcess(), ProcessPowerThrottling, &s, sizeof(s));
}

} // namespace wind
4 changes: 2 additions & 2 deletions src/version.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#define WIND_VER_MAJOR 0
#define WIND_VER_MINOR 21
#define WIND_VER_PATCH 1
#define WIND_VER_PATCH 2

// String form for logs/snapshot/UI. Keep in sync with the numeric parts above.
#define WIND_VERSION_STR "0.21.1"
#define WIND_VERSION_STR "0.21.2"
2 changes: 1 addition & 1 deletion ui/src/controls/Preview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
const visible = (r) => !r.showIf || String(values[r.showIf.key]) === String(r.showIf.eq);
const set = (key, val) => { values[key] = val; calls.push([key, val]); };
const extra = {
runningModel: 'hybrid', mpoNeedsRestart: false, version: '0.21.1',
runningModel: 'hybrid', mpoNeedsRestart: false, version: '0.21.2',
onRestart: () => calls.push('restart'), onAction: (a) => calls.push(['action', a]),
onRepo: () => calls.push('repo'),
pick: async () => 'RDR2.exe',
Expand Down
Loading