Description
On a mixed-DPI multi-monitor setup, dragging a Wails v3 window from an external monitor onto the (different scale factor) laptop display makes the embedded WebView2 browser process compute a degenerate scale(0,0) transform. The viz compositor rejects the resulting frame and kills the GPU process; the kill repeats on every submitted frame until Chromium's crash limit is hit, at which point the browser process itself exits and the window is left permanently blank — every subsequent controller COM call fails with ERROR_INVALID_STATE (0x8007139F) until the app is restarted.
(Before alpha2.112 this was a hard host-process crash rather than a blank window, because transient COM errors used to reach errorCallback → os.Exit(1) — the path audited in #5580. alpha2.112 made it survivable; this issue is about the underlying WebView2 death.)
Steps to reproduce
- Windows machine with monitors at different scale factors (repro box: laptop display + 3 external monitors, mixed scaling, Intel iGPU).
- Run any Wails v3 (v3.0.0-alpha2.112) app.
- Drag the window across the DPI boundary onto the differently-scaled display, possibly a few times.
- Window renders for a moment, blanks, and never recovers (pre-alpha2.112: host app crashes).
Reproduces reliably on the affected machine; needs the mixed-DPI hardware condition.
Evidence (chrome_debug.log from the WebView2 profile, --enable-logging --v=1)
The full chain from the dying session:
[25604:7252:0703/132849.877:ERROR:ui\gfx\geometry\transform.cc:505] NOTREACHED hit. [ 0 0 0 0
0 0 0 0
0 0 1 0
0 0 0 1 ]
is not invertible
[25604:7252:0703/132851.029:ERROR:content\browser\gpu\gpu_process_host.cc:1063] GPU process exited unexpectedly: exit_code=-2147483645
[25604:7252:0703/132900.954:ERROR:ui\gfx\geometry\transform.cc:505] NOTREACHED hit. [ 0 0 0 0 ...] is not invertible
[25604:7252:0703/132901.656:ERROR:content\browser\gpu\gpu_process_host.cc:1063] GPU process exited unexpectedly: exit_code=-2147483645
[25604:7252:0703/132905.150:ERROR:ui\gfx\geometry\transform.cc:505] NOTREACHED hit. [ 0 0 0 0 ...] is not invertible
[25604:7252:0703/132905.799:ERROR:content\browser\gpu\gpu_process_host.cc:1063] GPU process exited unexpectedly: exit_code=-2147483645
When the kills come fast enough, Chromium gives up and the browser process exits:
FATAL:content\browser\gpu\gpu_data_manager_impl_private.cc GPU process isn't usable. Goodbye.
exit_code=-2147483645 is 0x80000003 (STATUS_BREAKPOINT — a CHECK kill, not a GPU driver fault). No Crashpad dumps are produced; the browser side treats it as an orderly shutdown.
Analysis
wails runs WebView2 in the go-webview2 module's raw-pixels configuration: PutBoundsMode(USE_RAW_PIXELS) + PutShouldDetectMonitorScaleChanges(false), with the host pushing PutRasterizationScale from its WM_DPICHANGED handler (webview_window_windows.go WM_DPICHANGED path).
That host-managed mode is doc-legal, but it's a path almost no WebView2 embedder exercises (mainstream hosts run DIP bounds + detection on). During a monitor cross, the host's scale/bounds writes race the browser process's own internal display bookkeeping (which keeps running even with detection off), and the browser lands on a zero x/y scale when building the compositor transform. That looks like the same underlying race as #5677's shrink-then-disappear symptom — #5677's fix (re-asserting bounds after the scale re-put) treats one observable symptom of it.
Fix, validated
Two changes, one behavioral unit — make Edge the only writer of the rasterization scale:
- Re-enable
ShouldDetectMonitorScaleChanges (the WebView2 default) right after Embed. Bounds stay raw-pixels.
- Gate
resyncWebviewRasterizationScale off while detection is on, so the host never writes the scale concurrently with Edge. (With both writers active, a transient NOTREACHED + one GPU kill per crossing still occurred — self-healing, but visible as a blank flash.)
Branch: https://github.com/randalmurphal/wails/tree/fix/webview2-mixed-dpi-rasterization-scale (single commit off the v3.0.0-alpha2.112 tag).
After this change the repro machine shows zero NOTREACHED / GPU-process exits across repeated boundary drags, with correct rendering at every scale factor.
Caveats stated up front, since this touches scar tissue: it flips a deliberate go-webview2 design choice, and it's validated on one machine / one WebView2 runtime (149.0.4022.98, Windows 11 26200). The #5544 / #5605 minimize-restore scenarios route around the now-dormant host resync and were not individually re-tested — with detection on, Edge's own (heavily exercised) monitor-change path handles them, and the host makes strictly fewer COM calls into possibly-suspended controllers, but maintainer judgment on that history is exactly why this is an issue with a linked branch rather than a direct PR.
Happy to submit the branch as a PR if this direction looks right to you.
Environment
Description
On a mixed-DPI multi-monitor setup, dragging a Wails v3 window from an external monitor onto the (different scale factor) laptop display makes the embedded WebView2 browser process compute a degenerate
scale(0,0)transform. The viz compositor rejects the resulting frame and kills the GPU process; the kill repeats on every submitted frame until Chromium's crash limit is hit, at which point the browser process itself exits and the window is left permanently blank — every subsequent controller COM call fails withERROR_INVALID_STATE (0x8007139F)until the app is restarted.(Before alpha2.112 this was a hard host-process crash rather than a blank window, because transient COM errors used to reach
errorCallback→os.Exit(1)— the path audited in #5580. alpha2.112 made it survivable; this issue is about the underlying WebView2 death.)Steps to reproduce
Reproduces reliably on the affected machine; needs the mixed-DPI hardware condition.
Evidence (
chrome_debug.logfrom the WebView2 profile,--enable-logging --v=1)The full chain from the dying session:
When the kills come fast enough, Chromium gives up and the browser process exits:
exit_code=-2147483645is0x80000003(STATUS_BREAKPOINT — a CHECK kill, not a GPU driver fault). No Crashpad dumps are produced; the browser side treats it as an orderly shutdown.Analysis
wails runs WebView2 in the go-webview2 module's raw-pixels configuration:
PutBoundsMode(USE_RAW_PIXELS)+PutShouldDetectMonitorScaleChanges(false), with the host pushingPutRasterizationScalefrom itsWM_DPICHANGEDhandler (webview_window_windows.go WM_DPICHANGED path).That host-managed mode is doc-legal, but it's a path almost no WebView2 embedder exercises (mainstream hosts run DIP bounds + detection on). During a monitor cross, the host's scale/bounds writes race the browser process's own internal display bookkeeping (which keeps running even with detection off), and the browser lands on a zero x/y scale when building the compositor transform. That looks like the same underlying race as #5677's shrink-then-disappear symptom — #5677's fix (re-asserting bounds after the scale re-put) treats one observable symptom of it.
Fix, validated
Two changes, one behavioral unit — make Edge the only writer of the rasterization scale:
ShouldDetectMonitorScaleChanges(the WebView2 default) right afterEmbed. Bounds stay raw-pixels.resyncWebviewRasterizationScaleoff while detection is on, so the host never writes the scale concurrently with Edge. (With both writers active, a transient NOTREACHED + one GPU kill per crossing still occurred — self-healing, but visible as a blank flash.)Branch: https://github.com/randalmurphal/wails/tree/fix/webview2-mixed-dpi-rasterization-scale (single commit off the v3.0.0-alpha2.112 tag).
After this change the repro machine shows zero NOTREACHED / GPU-process exits across repeated boundary drags, with correct rendering at every scale factor.
Caveats stated up front, since this touches scar tissue: it flips a deliberate go-webview2 design choice, and it's validated on one machine / one WebView2 runtime (149.0.4022.98, Windows 11 26200). The #5544 / #5605 minimize-restore scenarios route around the now-dormant host resync and were not individually re-tested — with detection on, Edge's own (heavily exercised) monitor-change path handles them, and the host makes strictly fewer COM calls into possibly-suspended controllers, but maintainer judgment on that history is exactly why this is an issue with a linked branch rather than a direct PR.
Happy to submit the branch as a PR if this direction looks right to you.
Environment
ProcessFailedhandling that turns any browser-process death into a permanent blank window