Skip to content

Start on machines with two GPU drivers instead of refusing to launch (#150, #145, #142, #67) - #157

Open
SwatX18 wants to merge 1 commit into
juv:masterfrom
SwatX18:upstream/two-gpu-startup
Open

Start on machines with two GPU drivers instead of refusing to launch (#150, #145, #142, #67)#157
SwatX18 wants to merge 1 commit into
juv:masterfrom
SwatX18:upstream/two-gpu-startup

Conversation

@SwatX18

@SwatX18 SwatX18 commented Aug 25, 2026

Copy link
Copy Markdown

Hi — this fixes the long-standing problem where vibranceGUI refuses to start when both an NVIDIA and an AMD driver are present. It addresses #150, #145, #142 and #67.

The problem

GraphicsAdapterHelper.GetAdapter() returns Ambiguous when both atiadlxy.dll and nvapi.dll exist in the system folder, and Program.cs then shows the DDU dialog and exits without creating a window.

The check is whether driver DLLs exist on disk, which says nothing about whether that GPU is in use. Both DLLs are present on any machine with an AMD CPU that has integrated graphics plus a discrete NVIDIA card — an extremely common desktop today — and on any machine where an old driver was never scrubbed. In both cases the app won't start, and the advice it gives (remove a driver) is wrong for the first case.

The approach

Asking Windows which adapter drives an attached display gives an unambiguous answer. On my machine:

NVIDIA GeForce RTX 5070 Ti   DISPLAY1, DISPLAY2, DISPLAY3 (primary)   attached
AMD Radeon(TM) Graphics      DISPLAY5-9                                none attached

WMI isn't enough — Win32_VideoController reports both adapters as status=OK. It's the DISPLAY_DEVICE_ATTACHED_TO_DESKTOP flag from EnumDisplayDevices that discriminates.

Three layers, so most users never see a prompt:

  1. Auto-detect the vendor driving an attached display. Exactly one supported vendor → resolve silently. Both, or neither → stay ambiguous rather than guess.
  2. A chooser dialog only when it really is ambiguous. It lists the actual adapter names from the display devices — a user recognises "NVIDIA GeForce RTX 5070 Ti" even if they don't know which chip their laptop has — and preselects whichever drives the primary display. Your DDU advice is kept, but as a link rather than the primary action, since it's right for leftover drivers and misleading for hybrid hardware.
  3. The choice is persisted to the INI so it's asked once. It's only consulted while both drivers are still installed, so swapping a card falls back to detection rather than honouring a stale answer.

Two things I was careful about

Not regressing machines that work today. Every entry point into the new code sits behind AreBothVendorDriversInstalled(). On a single-driver machine EnumDisplayDevices is never called, nothing is logged, and the chooser is never constructed — the single-vendor path in GetAdapter() is unchanged.

Not throwing before the main form exists. A throw there means the app never opens, which would be worse than the bug. The enumeration is wrapped, bounded at 64 devices, and returns an empty list rather than a partial one on either failure path — a half-read enumeration could show one vendor and hide the other. The chooser's construction is contained, so an unexpected failure degrades to the existing dialog rather than to no window.

One detail worth flagging: vendor names are matched as whole words. ATI occurs inside ordinary English words, so a bare substring test classifies "Workstation Virtual Display" and "Cinematic Display Driver" as AMD — turning an honest "cannot tell" into a confident wrong answer. Boundaries are letters rather than letters-or-digits, so ATI2VGA and AMD780G still match.

vibrance.GUI.exe --selftest-gpu checks the vendor corpus with no GPU, no driver and no disk access, so it can be run on a machine that can't start the app. It passes 22/22 and fails against a bare substring matcher.

Verified

On a machine with both drivers installed and only NVIDIA driving displays, the app now starts with no arguments — title vibranceGUI (NVIDIA, 2.3.1.1), status "Running!". Before the change it showed the DDU dialog and exited. Both Release and Debug x86 build clean, 0 warnings.

Not verified

I don't have a genuinely ambiguous machine — only one vendor drives a display here — so the chooser was exercised with synthetic adapter lists rather than two vendors actually driving attached displays. I also couldn't test a single-driver machine without uninstalling a driver, so that claim rests on the call-site gating described above rather than on a run.

Notes

Branched from master and deliberately kept to this one fix — no unrelated changes, and TargetFrameworkVersion is untouched at v4.0. Happy to split it up, drop the persistence or the self-test, or rework anything to fit how you'd prefer it done.

Developed with assistance from Claude Code; the commit carries a Co-Authored-By trailer.

vibranceGUI refuses to start when both an NVIDIA and an AMD driver are
installed. It shows a dialog recommending Display Driver Uninstaller and
then exits without creating a window.

That is wrong for the commonest hybrid desktop there is: an AMD CPU with
integrated graphics alongside a discrete NVIDIA card. Both driver DLLs
are present, neither should be removed, and the machine has an obvious
answer. It is equally wrong for anyone who upgraded cards without
scrubbing the old driver. Issues juv#150, juv#145, juv#142 and juv#67 all describe
this.

The flaw is the test itself. GetAdapter() checks whether driver DLLs
exist on disk, which says nothing about whether that GPU is in use.
Asking Windows which adapter drives an attached display gives an
unambiguous answer on the machine this was developed against:

  NVIDIA GeForce RTX 5070 Ti   DISPLAY1, DISPLAY2, DISPLAY3 (primary)
  AMD Radeon(TM) Graphics      DISPLAY5-9, none attached

WMI is not sufficient either - Win32_VideoController reports both
adapters as status OK. It is the DISPLAY_DEVICE_ATTACHED_TO_DESKTOP flag
from EnumDisplayDevices that discriminates.

Three layers, so that most users never see a prompt:

1. Auto-detect the vendor driving an attached display. Exactly one
   supported vendor attached resolves silently to that vendor. Both, or
   neither, remains ambiguous rather than guessing.
2. A chooser dialog only when it is genuinely ambiguous. It lists the
   real adapter names read from the display devices, because a user
   recognises "NVIDIA GeForce RTX 5070 Ti" while they may not know which
   chip their laptop has, and preselects whichever drives the primary
   display. The existing DDU advice is kept but demoted to a link, since
   it is legitimate for leftover drivers and misleading for hybrid
   hardware.
3. The choice is persisted to the INI so the question is asked once. It
   is only consulted when both drivers are still installed, so replacing
   a card falls back to detection rather than honouring a stale answer.

Not regressing a machine that works today

Every entry point into the new code sits behind
AreBothVendorDriversInstalled(). On a machine with a single driver,
EnumDisplayDevices is never called, nothing is logged and the chooser is
never constructed. The single-vendor path in GetAdapter() is unchanged.

This code runs before the main form exists

A throw here means the application never opens at all, which would be
worse than the bug being fixed. The display enumeration is wrapped,
bounded at 64 devices, and returns an empty list rather than a partial
one on either failure path - a half-read enumeration could show one
vendor and hide the other, which is worse than admitting ignorance. The
chooser's construction is contained, so an unexpected failure degrades to
the existing dialog rather than to no window.

Vendor names are matched as whole words. "ATI" occurs inside ordinary
English words, so a bare substring test classifies "Workstation Virtual
Display" and "Cinematic Display Driver" as AMD - turning an honest
"cannot tell" into a confident wrong answer in the one branch designed to
refuse to guess. Word boundaries are letters rather than letters or
digits, so ATI2VGA and AMD780G still match, and every occurrence is
examined so a glued match cannot mask a real one.

vibrance.GUI.exe --selftest-gpu checks the vendor classification corpus
with no GPU, no driver and no disk access, so it can be run on a machine
that cannot start the application. It passes 22/22 and fails against a
bare substring matcher.

Verified

On a machine with both drivers installed and only NVIDIA driving the
displays, the application now starts with no arguments: title
"vibranceGUI (NVIDIA, 2.3.1.1)", status "Running!". Before this change it
showed the DDU dialog and exited. Both configurations build clean.

Not verified: a genuinely ambiguous machine was not available, so the
chooser was exercised with synthetic adapter lists rather than two
vendors actually driving attached displays. A single-driver machine could
not be tested either, since both drivers are installed on the development
machine; that claim rests on the call-site gating described above.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@SwatX18

SwatX18 commented Aug 25, 2026

Copy link
Copy Markdown
Author

One thing I noticed after opening this, worth flagging rather than leaving for someone to trip over.

The resolution diagnostic this PR adds goes through VibranceGUI.Log(string), which writes vibranceGUI_log.txt relative to the current working directory — unlike Log(Exception), which writes to %APPDATA%\vibranceGUI. That's pre-existing behaviour, but this PR is the first code to call it on a normal launch, so it makes the difference visible: on a hybrid machine you'd get a log file appearing wherever the app happens to be started from, and on a Program Files install the write would simply fail.

It can't break startup — every call goes through a LogSafely wrapper that swallows exceptions, precisely because logging shouldn't be a reason the app doesn't open. So the worst case is a missing diagnostic line, not a crash.

Happy to do whichever you prefer: point the new diagnostic at the %APPDATA% path that Log(Exception) already uses, drop the logging from this PR entirely, or leave it as-is and treat the Log(string) destination as a separate question. I'd lean toward the first, but it's a one-line change either way and I didn't want to widen the diff without asking.

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.

1 participant