Proposal
Switch the D3D11 swap chain to the DXGI flip model (DXGI_SWAP_EFFECT_FLIP_DISCARD via IDXGIFactory2::CreateSwapChainForHwnd) and use borderless windowed fullscreen everywhere, instead of the current legacy BitBlt swap chain (DXGI_SWAP_EFFECT_DISCARD) + DXGI exclusive fullscreen.
Current state (DisplayDX11.cpp)
CreateDeviceAndSwapChain builds a DXGI_SWAP_CHAIN_DESC with the default swap effect → legacy BitBlt (DISCARD) model.
- The app uses DXGI exclusive fullscreen (
SetFullscreenState(TRUE)); only the screensaver opts out (SetDisableExclusiveFullscreen(m_isScreenSaverRun) in Player.cpp).
- So the screensaver is already borderless, but still on the BitBlt model — it never gets the compositor-bypass either.
Why flip mode — basic performance wins
This is a video/visual player, so flip mode does not raise fps. The wins are latency, power, GPU headroom, and frame pacing. Magnitude depends on the baseline:
vs. old exclusive fullscreen (fullscreen case): ~parity. Exclusive already bypassed the DWM compositor; flip-model independent flip also bypasses it (direct scanout). So flip mode keeps the performance we had while dropping exclusive mode's multi-monitor input bug. No regression.
vs. BitBlt model (windowed mode + the screensaver): real net-new win. BitBlt-windowed goes through a full DWM composition pass every frame:
- Latency: ~1 frame less on average (≈16 ms @ 60 Hz), up to ~2 frames worst case — the compositor hop is removed.
- GPU time / bandwidth: eliminates one full-screen copy per frame (~33 MB/frame at 4K). Sub-ms on a dGPU; a few % of GPU utilization and ~0.2–0.5 ms/frame on an integrated laptop GPU.
- Power (biggest practical win, esp. laptops): direct scanout lets the GPU down-clock and can engage a hardware overlay plane (MPO) so the display controller scans the video out directly — the classic fullscreen-video battery saver.
- Smoothness: cleaner vsync, fewer compositor-induced dropped/duplicated frames — probably the most user-visible benefit for a "dream" visual.
Caveats
- Independent flip only engages when the window truly covers the monitor with nothing on top (no notification popups/other topmost windows, compatible format). Plain windowed gets flip's modest benefits but not full independent flip.
- The MPO/overlay path is driver/GPU-dependent.
- Verify with PresentMon (reports
Hardware: Independent Flip / Composed: Flip per frame) that the fast path actually engages.
Implementation requirements (already satisfied by this renderer)
BufferCount >= 2 (currently 2). ✔
- No MSAA on the back buffer (
SampleDesc.Count == 1). ✔
- No GDI drawn on the back buffer (custom titlebar is a DX overlay). ✔
- Renderer re-binds its render target every frame (
CRendererDX11::BeginFrame → OMSetRenderTargets), which the flip model requires after each Present. ✔
- Use
CreateSwapChainForHwnd + DXGI_SWAP_CHAIN_DESC1; add MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER) so DXGI can't bounce Alt+Enter back into exclusive mode.
Related: the keyboard-focus bug (#617) — current diagnosis
#617 ("full screen on external monitor breaks keyboard commands") is what motivated this. My current read:
Root cause is a Win32 foreground/keyboard-focus ownership problem on multi-monitor, made worse by exclusive fullscreen. Keys go to whatever window holds keyboard focus. When the app is fullscreen on a secondary monitor and the user has been interacting on the primary, our fullscreen window is not the foreground window, so WM_KEYDOWN never reaches it. With exclusive fullscreen on the secondary monitor, even clicking the surface didn't recover focus (reported). It "works on the laptop/primary screen" because activation tends to stay there.
Attempted fix (in the working tree, builds, but NOT confirmed working):
- Flip-model swap chain + borderless windowed everywhere (this issue).
SetFullscreen(true) now claims foreground + focus and goes WS_EX_TOPMOST, matching the startup-fullscreen path.
WM_MOUSEMOVE in fullscreen reclaims foreground/focus if another window holds it.
Why it likely still isn't enough / where to look tomorrow:
SetForegroundWindow foreground-lock restrictions. Windows silently no-ops SetForegroundWindow when the calling thread isn't allowed to steal foreground (foreground belongs to another process, no recent input directed at us). So the WM_MOUSEMOVE focus-reclaim may simply never take effect. The robust pattern is the AttachThreadInput dance (attach to the current foreground thread, BringWindowToTop + SetForegroundWindow + SetFocus, detach), or AllowSetForegroundWindow / a brief SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT) workaround.
- Verify the plain click path first. In borderless mode a real mouse click on the window should activate it via normal Win32 rules (it didn't under exclusive). If clicking now works but mouse-move doesn't, that isolates the problem to the auto-reclaim, not input routing.
- Decide whether focus-follows-mouse is even wanted. Auto-stealing foreground on hover can be surprising; an explicit click-to-focus may be the better UX.
- Rule out multi-window event routing. Confirm there's a single top-level display window and that
WM_KEYDOWN → AppendKeyEvent → g_Player().Display() queue is the one the client actually drains.
Flip mode may help #617 (borderless makes normal activation work again) but is probably not sufficient on its own — the focus-reclaim robustness above is the likely missing piece.
Proposal
Switch the D3D11 swap chain to the DXGI flip model (
DXGI_SWAP_EFFECT_FLIP_DISCARDviaIDXGIFactory2::CreateSwapChainForHwnd) and use borderless windowed fullscreen everywhere, instead of the current legacy BitBlt swap chain (DXGI_SWAP_EFFECT_DISCARD) + DXGI exclusive fullscreen.Current state (
DisplayDX11.cpp)CreateDeviceAndSwapChainbuilds aDXGI_SWAP_CHAIN_DESCwith the default swap effect → legacy BitBlt (DISCARD) model.SetFullscreenState(TRUE)); only the screensaver opts out (SetDisableExclusiveFullscreen(m_isScreenSaverRun)inPlayer.cpp).Why flip mode — basic performance wins
This is a video/visual player, so flip mode does not raise fps. The wins are latency, power, GPU headroom, and frame pacing. Magnitude depends on the baseline:
vs. old exclusive fullscreen (fullscreen case): ~parity. Exclusive already bypassed the DWM compositor; flip-model independent flip also bypasses it (direct scanout). So flip mode keeps the performance we had while dropping exclusive mode's multi-monitor input bug. No regression.
vs. BitBlt model (windowed mode + the screensaver): real net-new win. BitBlt-windowed goes through a full DWM composition pass every frame:
Caveats
Hardware: Independent Flip/Composed: Flipper frame) that the fast path actually engages.Implementation requirements (already satisfied by this renderer)
BufferCount >= 2(currently 2). ✔SampleDesc.Count == 1). ✔CRendererDX11::BeginFrame→OMSetRenderTargets), which the flip model requires after eachPresent. ✔CreateSwapChainForHwnd+DXGI_SWAP_CHAIN_DESC1; addMakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER)so DXGI can't bounce Alt+Enter back into exclusive mode.Related: the keyboard-focus bug (#617) — current diagnosis
#617 ("full screen on external monitor breaks keyboard commands") is what motivated this. My current read:
Root cause is a Win32 foreground/keyboard-focus ownership problem on multi-monitor, made worse by exclusive fullscreen. Keys go to whatever window holds keyboard focus. When the app is fullscreen on a secondary monitor and the user has been interacting on the primary, our fullscreen window is not the foreground window, so
WM_KEYDOWNnever reaches it. With exclusive fullscreen on the secondary monitor, even clicking the surface didn't recover focus (reported). It "works on the laptop/primary screen" because activation tends to stay there.Attempted fix (in the working tree, builds, but NOT confirmed working):
SetFullscreen(true)now claims foreground + focus and goesWS_EX_TOPMOST, matching the startup-fullscreen path.WM_MOUSEMOVEin fullscreen reclaims foreground/focus if another window holds it.Why it likely still isn't enough / where to look tomorrow:
SetForegroundWindowforeground-lock restrictions. Windows silently no-opsSetForegroundWindowwhen the calling thread isn't allowed to steal foreground (foreground belongs to another process, no recent input directed at us). So theWM_MOUSEMOVEfocus-reclaim may simply never take effect. The robust pattern is theAttachThreadInputdance (attach to the current foreground thread,BringWindowToTop+SetForegroundWindow+SetFocus, detach), orAllowSetForegroundWindow/ a briefSystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT)workaround.WM_KEYDOWN→AppendKeyEvent→g_Player().Display()queue is the one the client actually drains.Flip mode may help #617 (borderless makes normal activation work again) but is probably not sufficient on its own — the focus-reclaim robustness above is the likely missing piece.