How DisplayProfiles works internally, what was learned about NVAPI and Windows on the way, and what to keep in mind when reusing the core library. Observations are marked verified (reproduced and checked) or observed (seen, not systematically reproduced).
| Component | Responsibility |
|---|---|
DisplayProfiles.Core |
All display logic. No dependencies besides the .NET runtime, nvapi64.dll (installed with the NVIDIA driver) and user32.dll |
DisplayProfiles.Tray |
WinForms tray app, companion window, ASP.NET Core minimal API on 127.0.0.1:47811 |
DisplayProfiles.Cli |
Console front end for the same core functions |
Core files:
| File | Responsibility |
|---|---|
NvApi.cs |
NVAPI via nvapi_QueryInterface: GPUs, display IDs, EDID, Surround grids (read, validate, set), display config (real refresh rate) |
WinDisplay.cs |
Windows CCD API: read the active layout, SetDisplayConfig (validate / apply) incl. adapter LUID remapping |
Snapshot.cs |
Captures the complete state (NVIDIA + Windows), headset detection, derived values (real Hz, bezel corrected), layout description, profile JSON format |
Apply.cs |
The switch sequence below, incl. monitor check, backup and read-back comparison |
ProfileStore.cs |
Profile files: list, load, save, rename, archive, name validation |
DisplayService.cs |
Thread-safe facade for long-running hosts: status, profile CRUD, apply |
The tray menu and the companion window talk to the REST API like any other client; there is no
second code path. DisplayService serializes all driver access and caches the last state, so status
polling never touches the driver while a switch runs.
- Monitor check — every display of the profile must be connected on the same GPU output with the same EDID serial number. Otherwise nothing is changed (code 5).
- NVIDIA Surround — only if the Surround layout differs from the profile: validate with
NvAPI_Mosaic_ValidateDisplayGrids, then set withNvAPI_Mosaic_SetDisplayGrids, flags keep current GPU topology and no driver reload. A grid that would need a driver reload is refused. Displays of an active Surround that the profile does not use are split out as single displays. - Windows layout —
SetDisplayConfigwith the saved paths and modes: active displays, positions, resolution, refresh rate, primary display. Validated first, then applied and saved to the Windows display database. - Read-back — the state is captured again and compared with the profile. On a deviation the tool waits 2 s and reads once more before reporting it.
A backup of the previous state is written before step 2 (profiles\_backup\before-<name>-<time>.json).
Profiles are JSON snapshots in %LOCALAPPDATA%\DisplayProfiles\profiles\<name>.json. Besides readable
fields they contain the raw NVIDIA grid structures and the raw Windows CCD path/mode buffers (Base64);
that is what makes an exact restore possible. Overwritten profiles are copied to _backup, deleted
profiles are moved to _archive.
Creating a profile captures the state twice, 1.5 s apart, and refuses to save if the two reads differ — right after a mode change the display state can still be settling.
The headset is recognised by its EDID (manufacturer SFL, product 0x0102, name SVP-VG 3 —
Shiftall MeganeX superlight 8K). It reports connected on its GPU output as soon as it is plugged in
and powered, and active only while SteamVR drives it in direct mode. It also appears as a USB
composite device (USB\VID_28DE&PID_2300\…), which could serve as an independent trigger.
- Surround blocks a shared output. With the Surround grid dissolved, the headset plugged into the formerly shared DisplayPort output was detected without a reboot (verified). With Surround active it was not detected until a reboot (observed repeatedly before the tool existed).
- Switch before swapping cables. Whether dissolving Surround after the headset is plugged in also works is not tested.
- Do not plug or unplug displays during a switch. A display disappearing mid-switch triggers the Windows hot-plug handling, which overrides the layout (verified). Running the same switch again fixes it.
- A switched-off HDMI monitor still reports "connected" (its EDID stays readable). Validation
passes, but
SetDisplayConfigfails with error 31 (verified). DisplayPort monitors report "not connected" when switched off. - Short-lived deviations. Right after a mode change a DisplayPort monitor can drop its link for a moment, so an immediate read-back may differ from the final state (verified once). Hence the second read before a deviation is reported.
- Windows remembers a layout per monitor combination. Because the layout is saved to the Windows display database, Windows restores it automatically when the same set of monitors appears again — e.g. switching the Surround monitors off falls back to the single desktop monitor (verified).
- Surround does not come back by itself once it was dissolved — at least not with only two of the three monitors present (verified).
- Adding a display next to an active Surround resets the Surround mode. Turning on a fourth monitor in the Windows settings made Windows pick the plain Surround resolution at the highest refresh rate — bezel correction off, although the gap values stayed in the NVIDIA grid (verified). Set the bezel-corrected resolution in the Windows display settings again, then re-capture. The layout description shows "(Bezel)" only when the desktop really is bezel corrected.
- Taskbar on Surround. The NVIDIA Control Panel option Desktop → Surround Displays → Confine Taskbar to Single Display keeps the taskbar on the middle monitor. With a fourth display next to the Surround the taskbar spanned all Surround monitors; with the Surround alone it was confined again (observed). The option is not part of the public NVAPI and not documented.
- Duration. A switch that changes Surround takes ~13 s in the driver plus ~5 s settle time in the tool. Screens go black briefly. Never run a switch on a UI thread or in a real-time handler.
- Never fall back to driver restarts. Disabling/re-enabling the display adapter (e.g.
pnputil /restart-device) to "reset" the display state took down the Windows compositor and the VR headset and required a full reboot (observed). DisplayProfiles never does this.
NvAPI_Mosaic_EnumDisplayGridsandNvAPI_Mosaic_GetCurrentToporeport a fixed refresh rate (180 Hz on the test rig, the panels' maximum), regardless of what is actually driven. The real rate comes fromNvAPI_DISP_GetDisplayConfig(refreshRate1Kin the advanced target info).- The
applyWithBezelCorrectgrid flag reads as 0 even when bezel correction is active. The tool derives it: desktop width = columns × width − sum of the (negative) overlaps, e.g. 3 × 2560 + 2 × 43 = 7766. - Grid flag bit 1 (
immersiveGaming) marks GeForce Surround and must be kept when setting a grid, otherwise NVIDIA returns-113 NVAPI_NO_ACTIVE_SLI_TOPOLOGY. NvAPI_DISP_GetDisplayConfigneeds three passes: count → path array → targets / source modes.- Windows adapter LUIDs change on every boot; saved CCD paths and modes are remapped to the current
LUIDs by target ID before
SetDisplayConfig.
All calls go through nvapi_QueryInterface (cdecl, x64). IDs and layouts come from the NVAPI SDK
headers at https://github.com/NVIDIA/nvapi (see THIRD-PARTY-NOTICES.md).
| Function | ID | Writes? |
|---|---|---|
NvAPI_Initialize |
0x0150e828 |
no |
NvAPI_EnumPhysicalGPUs |
0xe5ac921f |
no |
NvAPI_GPU_GetFullName |
0xceee8e9f |
no |
NvAPI_GPU_GetAllDisplayIds |
0x785210a2 |
no |
NvAPI_GPU_GetEDID |
0x37d32e69 |
no |
NvAPI_DISP_GetDisplayIdByDisplayName |
0xae457190 |
no |
NvAPI_DISP_GetDisplayConfig |
0x11abccf8 |
no |
NvAPI_Mosaic_EnumDisplayGrids |
0xdf2887af |
no |
NvAPI_Mosaic_ValidateDisplayGrids |
0xcf43903d |
no |
NvAPI_Mosaic_SetDisplayGrids |
0x4d959a89 |
yes — always with flags 0x3 (current GPU topology + no driver reload) |
Structure sizes (x64); version word = size | (version << 16):
| Struct | Size | Version |
|---|---|---|
NV_GPU_DISPLAYIDS |
16 | 3 |
NV_EDID_V3 |
272 | 3 |
NV_MOSAIC_GRID_TOPO_V2 (header 20 + 64 displays × 28 + settings 20) |
1832 | 2 |
NV_MOSAIC_DISPLAY_TOPO_STATUS (header 16 + 128 × 16) |
2064 | 1 |
NV_DISPLAYCONFIG_PATH_INFO_V2 |
48 | 2 |
NV_DISPLAYCONFIG_PATH_TARGET_INFO_V2 |
24 | — |
NV_DISPLAYCONFIG_SOURCE_MODE_INFO_V1 |
32 | — |
NV_DISPLAYCONFIG_PATH_ADVANCED_TARGET_INFO_V1 |
128 | 1 |
DISPLAYCONFIG_PATH_INFO (Windows) |
72 | — |
DISPLAYCONFIG_MODE_INFO (Windows) |
64 | — |
When setting a grid: keep bits 1 (immersiveGaming), 2 (baseMosaic) and 5 (pixelShift) from the
saved grid, set bit 0 (applyWithBezelCorrect) from the derived value, always clear bit 3
(driverReloadAllowed). Write the rounded real refresh rate into the grid's display settings and let
the following SetDisplayConfig set the exact rate. Abort on any validation error flag and on the
warning DRIVER_RELOAD_REQUIRED.
DisplayProfiles.Core.dll can be referenced by another .NET 8 process, e.g. as a native plugin of a
host application. DisplayService is the entry point: it serializes driver access, caches the state
and offers exactly the operations of the REST API. A host that embeds the library must not run in
parallel with the tray app; alternatively it stays a client of the REST API
(rest-api.md).