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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ That builds two programs in `.\build\Release\`: `GpuAutoOptimizer.exe`, the wind

### The app

Start `GpuAutoOptimizer.exe`. The window shows the GPU, live telemetry, the saved tune and what is applied right now; pick a preset and press **Optimize** (it offers to restart as administrator first, because tuning writes clocks and power limits). The run screen shows temperature, power and every probe as it happens; **Abort** restores stock. The results screen compares before and after and can turn on **apply at every logon**.
Start `GpuAutoOptimizer.exe`. The **Dashboard** shows the GPU with live telemetry, what is applied right now next to the saved tune (with **Apply saved tune**, the **Apply at logon** switch and **Revert to stock**), the four profiles and a log of what happened. On the **Optimize** page, pick a profile and press **Optimize GPU** (it offers to restart as administrator first, because tuning writes clocks and power limits). The run shows temperature, power and every probe as it happens; **Abort** restores stock. The results compare before and after and can turn on apply at every logon.

With apply-at-logon on, Windows starts the app in the tray at every logon. It applies the saved tune and then keeps it applied: every 30 seconds it reads back what the driver reports. If the tune was reset (a driver reset or TDR), it applies it again; if that happens four times within an hour it stops and tells you the tune is probably not stable; if another program such as MSI Afterburner changed the settings, it leaves them alone and tells you; after a driver update it does not apply the old tune and tells you to optimize again. While it keeps a tune applied (or a run is in progress), closing the window keeps it in the tray and **Exit** in the tray menu quits; otherwise closing the window quits. **Revert to stock** in the tray menu, or `gao --reset` from a shell, puts the card at stock and the watchdog leaves it there until you apply the tune again (window, tray menu or `gao --apply`). Only one optimize runs at a time, from the window or the command line.
With apply-at-logon on, Windows starts the app in the tray at every logon. It applies the saved tune and then keeps it applied: every 30 seconds it reads back what the driver reports. If the tune was reset (a driver reset or TDR), it applies it again; if that happens four times within an hour it stops and tells you the tune is probably not stable; if another program such as MSI Afterburner changed the settings, it leaves them alone and tells you; after a driver update it does not apply the old tune and tells you to optimize again. Closing the window keeps the app in the tray; **Exit** in the tray menu quits. **Revert to stock** in the tray menu, or `gao --reset` from a shell, puts the card at stock and the watchdog leaves it there until you apply the tune again (window, tray menu or `gao --apply`). Only one optimize runs at a time, from the window or the command line.

### The command line

Expand Down
2 changes: 1 addition & 1 deletion docs/hardware-checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ root; adjust the path for a Debug build.
| 34 | Watchdog backs off from another tool | tray running, set an offset in MSI Afterburner and Apply | no | a balloon says another program changed the settings; the watchdog does not fight it | |
| 35 | One instance | start the app twice | no | the second start brings the first window forward and exits | Pass (2026-09-27, after the P5 fix pass): the second start exits with code 0, the first keeps running. |
| 36 | CLI and tray side by side | tray running with the tune kept applied; `gao --reset`, wait 45 s, `gao --status`; then `gao --optimize` from a shell while the window runs an optimize | yes | after `--reset` the card stays at stock (the tray notes it and does not re-apply); after `gao --apply` the tray keeps the tune applied again; the second optimize refuses with `another optimize is already running` | Pass for reset and apply (2026-09-27): after `gao --reset` the card stayed at stock for 45 s with the tray running (no watchdog line); `gao --apply` applied the tune again. Two optimizes at once not yet tried. |
| 37 | Closing the window | close the window with no tune applied, then again with a tune kept applied | no / yes | without a tune the app exits; with one it hides to the tray | Pass without a tune (2026-09-27): the app exits on close. |
| 37 | Closing the window | close the window, then choose Exit in the tray menu | no | closing hides the window to the tray (a balloon says so the first time); Exit quits | |

## Notes

Expand Down
100 changes: 53 additions & 47 deletions src/app/gui/main_gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ struct App {
bool watch = false; // keep the saved tune applied (off after a revert or a reset by choice)
bool worker_was_running = false;
bool strike_pending = false; // a logon apply whose 2-minute grace has not passed yet
bool told_about_tray = false; // the "still running in the tray" balloon, once per session

// Crash dumps are written by a thread created up front (a crashing thread
// may have no stack or heap left to do it itself).
Expand Down Expand Up @@ -163,17 +164,6 @@ void recover_device() {
if (!g.device_lost) ImGui_ImplDX11_Init(g.device.Get(), g.ctx.Get());
}

void apply_dpi(float scale) {
ImGuiStyle& style = ImGui::GetStyle();
style = ImGuiStyle();
ImGui::StyleColorsDark();
style.WindowRounding = 0;
style.FrameRounding = 4;
style.ChildRounding = 6;
style.ScaleAllSizes(scale);
style.FontScaleDpi = scale;
}

// ---------------------------------------------------------------- tray

std::wstring widen(const std::string& s) {
Expand Down Expand Up @@ -211,8 +201,16 @@ void tray_icon(DWORD message, const wchar_t* tip = nullptr, const wchar_t* ballo
}
}

void notify(const std::string& text) {
g.ui.watchdog_note = gao::app::now_text() + " " + text;
// A line in the window's log for this session.
void note(const std::string& text, bool warn = false) {
g.ui.notes.push_back({gao::app::now_text(), text, warn});
if (g.ui.notes.size() > 100) g.ui.notes.erase(g.ui.notes.begin());
}

// A tray balloon. Pass log=false when the event is already in boot.log,
// which the window's log shows too.
void notify(const std::string& text, bool log = true) {
if (log) note(text, true);
tray_icon(NIM_MODIFY, nullptr, widen(text).c_str());
}

Expand Down Expand Up @@ -256,14 +254,16 @@ void refresh_status(bool with_task) {
}
if (g.gpu.read_applied) g.ui.applied = g.gpu.read_applied();
const auto log = gao::read_lines(gao::boot_log_path());
g.ui.last_boot = log && !log->empty() ? log->back() : std::string();
g.ui.boot_log.clear();
if (log) g.ui.boot_log.assign(log->size() > 200 ? log->end() - 200 : log->begin(), log->end());
if (with_task) g.ui.boot_on = gao::boot_task_exists();
}

// ---------------------------------------------------------------- actions

void hw_lost(); // below, with the other hardware helpers

void act_optimize(gao::Preset preset) {
g.ui.message.clear();
g.worker->start(preset); // the watchdog skips while it runs
}

Expand All @@ -285,48 +285,49 @@ void act_restart_elevated() {
g.exit_requested = true;
} else {
g.instance_mutex = CreateMutexW(nullptr, TRUE, kInstanceMutex);
g.ui.message = "Elevation was cancelled.";
note("Elevation was cancelled.");
}
}

bool refuse_while_tuning() {
if (g.worker->running()) return true;
if (!gao::app::tuning_in_progress()) return false;
g.ui.message = "An optimize is running on the command line; wait for it to finish.";
note("An optimize is running on the command line; wait for it to finish.", true);
return true;
}

void act_apply() {
if (refuse_while_tuning()) return;
if (!g.nvml_ok || !g.nvapi_ok) { g.ui.message = "The NVIDIA driver is not available right now."; return; }
if (!g.nvml_ok || !g.nvapi_ok) { note("The NVIDIA driver is not available right now.", true); return; }
std::string why;
if (!gao::app::prepare_state(&why)) { g.ui.message = why; return; }
if (!gao::app::prepare_state(&why)) { note(why, true); return; }
gao::Config cfg = gao::app::load_config();
cfg.boot_strikes = 0; // strikes only gate the logon apply
const std::string driver = g.nvml_ok ? g.nvml->DriverVersion() : std::string();
const auto d = gao::decide_boot(cfg, driver, g.nvml_ok ? g.nvml->GpuUuid(kGpu) : std::string());
if (d != gao::BootDecision::Apply) { g.ui.message = "Not applied: " + gao::app::decision_text(d, cfg, driver); return; }
if (!gao::apply_profile(g.gpu, *cfg.profile, &why)) { g.ui.message = "Not applied: " + why; return; }
if (d != gao::BootDecision::Apply) { note("Not applied: " + gao::app::decision_text(d, cfg, driver), true); return; }
if (!gao::apply_profile(g.gpu, *cfg.profile, &why)) { note("Not applied: " + why, true); return; }
g.watch = true;
g.watchdog = gao::Watchdog();
g.ui.message = "Applied " + gao::app::profile_text(*cfg.profile);
note("Applied " + gao::app::profile_text(*cfg.profile));
refresh_status(false);
}

void act_revert() {
if (refuse_while_tuning()) return;
if (!g.nvml_ok || !g.nvapi_ok) { g.ui.message = "The NVIDIA driver is not available right now."; return; }
if (!g.nvml_ok || !g.nvapi_ok) { note("The NVIDIA driver is not available right now.", true); return; }
const bool ok = g.gpu.reset_to_stock && g.gpu.reset_to_stock();
g.watch = false; // stock by choice: the watchdog must not undo it
g.ui.message = ok ? "Back at stock. The saved tune is not re-applied until you apply it again."
: "Reset to stock FAILED; try gao --reset.";
note(ok ? "Back at stock. The saved tune is not re-applied until you apply it again."
: "Reset to stock FAILED; try gao --reset.",
!ok);
refresh_status(false);
}

void act_boot(bool on) {
std::string message;
on ? gao::app::enable_boot(&message) : gao::app::disable_boot(&message);
g.ui.message = message;
const bool ok = on ? gao::app::enable_boot(&message) : gao::app::disable_boot(&message);
note(message, !ok);
refresh_status(true);
}

Expand All @@ -341,6 +342,11 @@ void render() {
act.apply_profile = act_apply;
act.revert_to_stock = act_revert;
act.set_boot = act_boot;
act.detect_gpu = [] { // e.g. after a driver update: re-create NVML and NVAPI now
if (refuse_while_tuning()) return;
hw_lost();
g.hw_retry_at = 0;
};
gao::gui::draw_ui(g.ui, g.worker->snapshot(), act);
ImGui::Render();
const float clear[4] = {0.08f, 0.08f, 0.10f, 1.0f};
Expand Down Expand Up @@ -460,21 +466,22 @@ void on_watchdog() {
std::string why;
if (gao::apply_profile(g.gpu, *cfg.profile, &why)) {
gao::app::boot_log("watchdog: the tune had been reset (driver reset or TDR); re-applied");
notify("The tune had been reset (driver reset or TDR) and was re-applied.");
notify("The tune had been reset (driver reset or TDR) and was re-applied.", false);
} else {
gao::app::boot_log("watchdog: re-apply failed: " + why);
notify("The tune had been reset and could not be re-applied: " + why);
notify("The tune had been reset and could not be re-applied: " + why, false);
}
break;
}
case gao::WatchAction::GiveUpUnstable:
gao::app::boot_log("watchdog: reset 4 times within an hour; stopped re-applying");
notify("The tune keeps getting reset (4 times within an hour), which usually means it is not stable. "
"Stopped re-applying it; optimize again.");
"Stopped re-applying it; optimize again.",
false);
break;
case gao::WatchAction::BackOffForeign:
gao::app::boot_log("watchdog: another program changed the GPU settings; leaving them alone");
notify("Another program (Afterburner, NVIDIA App...) changed the GPU settings. Leaving them alone.");
notify("Another program (Afterburner, NVIDIA App...) changed the GPU settings. Leaving them alone.", false);
break;
case gao::WatchAction::NotifyDriverChanged:
notify("The NVIDIA driver or the card changed since the tune was made. Optimize again to tune for it.");
Expand Down Expand Up @@ -504,10 +511,10 @@ LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
if (wp == static_cast<WPARAM>(gao::app::TrayNotice::TuneApplied)) {
g.watch = true;
g.watchdog = gao::Watchdog();
g.ui.watchdog_note = gao::app::now_text() + " Applied from the command line; kept applied from now on.";
note("Applied from the command line; kept applied from now on.");
} else {
g.watch = false;
g.ui.watchdog_note = gao::app::now_text() + " Set to stock from the command line; not re-applied until you apply it again.";
note("Set to stock from the command line; not re-applied until you apply it again.", true);
}
refresh_status(false);
return 0;
Expand All @@ -527,19 +534,19 @@ LRESULT CALLBACK wnd_proc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
}
return 0;
case WM_DPICHANGED: {
apply_dpi(HIWORD(wp) / 96.0f);
gao::gui::apply_style(HIWORD(wp) / 96.0f);
const RECT* r = reinterpret_cast<const RECT*>(lp);
SetWindowPos(hwnd, nullptr, r->left, r->top, r->right - r->left, r->bottom - r->top, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
case WM_CLOSE:
// With a tune to keep applied, or a run in progress, the window
// hides and the tray stays. Otherwise there is nothing to stay for.
if (g.watch || g.worker->running()) {
ShowWindow(hwnd, SW_HIDE);
g.visible = false;
} else {
g.exit_requested = true;
// Closing hides to the tray, like other tray apps; Exit in the tray
// menu quits. Say so the first time, or it looks like a quit.
ShowWindow(hwnd, SW_HIDE);
g.visible = false;
if (!g.told_about_tray) {
g.told_about_tray = true;
tray_icon(NIM_MODIFY, nullptr, L"Still running in the tray. Right-click the icon and choose Exit to quit.");
}
return 0;
case WM_TIMER:
Expand Down Expand Up @@ -627,11 +634,11 @@ int WINAPI wWinMain(HINSTANCE inst, HINSTANCE, PWSTR cmdline, int) {
wc.hCursor = LoadCursorW(nullptr, MAKEINTRESOURCEW(32512)); // IDC_ARROW
wc.lpszClassName = kWindowClass;
RegisterClassExW(&wc);
g.hwnd = CreateWindowW(kWindowClass, L"GPU Auto Optimizer", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 920, 640,
g.hwnd = CreateWindowW(kWindowClass, L"GPU Auto Optimizer", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 1180, 860,
nullptr, nullptr, inst, nullptr);
if (!g.hwnd || !create_device()) return 1;
const float scale = GetDpiForWindow(g.hwnd) / 96.0f; // the monitor the window actually opened on
SetWindowPos(g.hwnd, nullptr, 0, 0, static_cast<int>(920 * scale), static_cast<int>(640 * scale),
SetWindowPos(g.hwnd, nullptr, 0, 0, static_cast<int>(1180 * scale), static_cast<int>(860 * scale),
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
const BOOL dark = TRUE; // a dark title bar to match the dark window
DwmSetWindowAttribute(g.hwnd, DWMWA_USE_IMMERSIVE_DARK_MODE, &dark, sizeof(dark));
Expand All @@ -651,9 +658,8 @@ int WINAPI wWinMain(HINSTANCE inst, HINSTANCE, PWSTR cmdline, int) {
io.IniFilename = nullptr; // nothing to persist; the layout is fixed
wchar_t windows[MAX_PATH];
const UINT wn = GetWindowsDirectoryW(windows, MAX_PATH);
const std::string font = wn && wn < MAX_PATH ? (std::filesystem::path(windows) / L"Fonts" / L"segoeui.ttf").string() : "";
if (font.empty() || !io.Fonts->AddFontFromFileTTF(font.c_str(), 17.0f)) io.Fonts->AddFontDefault();
apply_dpi(scale);
gao::gui::load_fonts(wn && wn < MAX_PATH ? (std::filesystem::path(windows) / L"Fonts").string() : std::string());
gao::gui::apply_style(scale);
ImGui_ImplWin32_Init(g.hwnd);
ImGui_ImplDX11_Init(g.device.Get(), g.ctx.Get());

Expand All @@ -672,7 +678,7 @@ int WINAPI wWinMain(HINSTANCE inst, HINSTANCE, PWSTR cmdline, int) {
g.strike_pending = true;
SetTimer(g.hwnd, kTimerStrike, 2 * 60 * 1000, nullptr);
} else if (logon.decision != gao::BootDecision::NoProfile) {
notify(logon.message);
notify(logon.message, false); // apply_at_logon wrote boot.log
}
}
refresh_status(false);
Expand Down
Loading